El plan que tenía para el compás era usar un módulo hmc5883l ampliamente usado en el ámbito del desarrollo libre con muchas bibliotecas disponibles y mucha información. El primer magnetómetro que compré estaba defectuoso, las lecturas eran incorrectas y había problemas con la comunicación. El segundo magnetómetro que me llego pese a estar anunciado como un módulo hmc5883l teniendo incluso la serigrafía del mísmo, no recibía los mensajes. Unas sencillas pruebas me mostraron que realmente se trataba de un módulo qmc5883l el cual tiene otra dirección y configuraciónes, y para el cual no hay mucha información online. Pese a todo he terminado desarrollando el proyecto con él y funciona sin problemas.
La comunicación con el magnetómetro se realiza usando el protocolo de bus I2C. I2C utiliza dos lineas de señal, un reloj (CLK) y una linea serie de datos (SDA), ambas se conectan en paralelo a todos los dispositivos conectados, tanto maestro como esclavos mediante una resistencia pull-up a la alimentación. La comunicación se realiza enviando paquetes de 8bits que pueden tener o no, un ACK por cada uno. Para indicar con qué esclavo se está comunicando el maestro, el primer paquete de 8bits contiene 7bits de la dirección del dispositivo para el qmc5883l 0x0D y un bit que señala si queremos realizar una lectura o una escritura (1 lectura 0 escritura).
Para la comunicación con el dispositivo vamos a realizar dos operaciones distintas. Al inicializar el dispositivo lo configuraremos y durante la ejecución leeremos los registros en los que se almacenan los valores. El funcionamiento de los dispositivos I2C en general es tener una serie de registros y un puntero al activo. Si le pedimos al dispositivo leer sin especificar nada más estaremos modificando el registro apuntado por el dispositivo. Para modificar el puntero debemos enviar un comando de escritura a la dirección del dispositivo seguido por la dirección del registro al que queremos apuntar.
En las imágenes inferiores tenemos unas capturas de osciloscopio, a la izquierda la señal de reloj y a la derecha la señal de datos. Como podemos ver ambas lineas en estado normal se encuentra en estado alto y la comunicación empieza cuando la linea de reloj desciende. Con cada flanco de reloj se envía un valor, en total 18, debido a que se envían dos bytes de datos y un ACK por cada uno. Los ACKs son los flancos más estrechos que se pueden ver en la señal SDA, estamos enviando la dirección del dispositivo de escritura y la dirección 0.
CLK
SDA
Para la programación del magnetómetro he seguido el datasheet disponible en https://nettigo.pl/attachments/440. El dispositivo tiene los registros mostrados en la imagen inferior:
Los primeros 6 registros contienen los valores de los tres ejes del magnetómetro como enteros de 16bit con signo, separados en los 8bits menos significativos y los 8bits más significativos. Del resto de registros los únicos que nos interesan son los de configuración 0x09 y 0x0A además del 0x0B.
En el registro de configuración 1 (0x09) tenemos los campos:
Debemos dejar todos los campos por defecto a 0b00 menos el campo de modo de control que se debe modificar a 0b01 para que se realicen lecturas continuas. Por tanto a la dirección 0x09 se debe enviar el dato 0x01.
En el registro de configuración 2 (0x0A) tenemos los registros:
Debemos dejar todos los bits a 0 menos el que permite que el puntero de registro se modifique automáticamente para poder leer los datos de golpe. Por tanto en la dirección 0x0A debemos escribir 0x40.
Por último el registro de periodo 0x0B en la documentación oficial recomiendan colocar 0x01 por lo que escribimos ese valor en el registro.
Para las lecturas modificamos el puntero para apuntar al registro 0x00, y como hemos configurado el puntero para que se modifique de manera automática leemos 6 bytes seguidos para obtener todos los valores seguidos.
Para la implementación de esta parte he usado las bibliotecas que proporciona el entorno de desarrollo y que se incluyen en el ESP32. En concreto tenemos disponible el driver I2C que permite comunicarnos con dispositivos I2C con hardware especializado en los GPIO 21 y 22, SDA y CLK respectivamente. En el trozo de código mostrado abajo se puede ver como se crea un comando, que es el que almacena las ordenes a enviar. A este comando se van añadiendo las ordenes de configuración del registro de control 1 con el valor correspondiente. Al terminar se selecciona uno de los dos drivers disponibles de comunicación I2C, aunque en mi caso solo uso uno.
Para las lecturas debajo tenemos el código que uso para leer los valores de los registros de datos. En el primer comando modificamos el puntero al registro 0, y en el segundo comando leemos 6 bytes de golpe que luego transformamos a datos de tipo entero 16bits.