
En este tutorial te daré los pasos para que construyas un sensor de angular a través del chip AS5048A de AMS Osram y de un microcontrolador PIC18F13K50 funcionando como USB HID. La lectura de datos del lado de la PC se hará a través de Python.
Material necesario
Analizador lógico de 8 canales económico para depurar la comunicación SPI
Proyecto completo en Gitlab
Todo lo necesario para llevar a cabo este pequeño proyecto está contenido en este proyecto en Gitlab:
Código fuente del PIC18F13K50
Esquemático en Kicad
Software en Python para ploteo en tiempo real de la distancia vía USB
Las versiones que utilicé para los diversos softwares de desarrollo son como sigue aunque puedes usar software más reciente.
MPLABX-v6.20-windows-installer.exe
kicad-10.0.4-x86_64.exe
mla_v2018_11_26_windows_installer.exe
xc8-v4.00-full-install-windows-x64-installer.exe
Microchip.PIC18F-K_DFP.1.16.308.atpack
Proyecto origen de Microchip
Para poder generar el código fuente de mi proyecto usé como base el ejemplo de Microchip siguiente:
Este proyecto es fácil de comprender y de migrar hacia algún otro proyecto específico como el mío. Debo mencionar que el proyecto tiene algunos problemas con el compilador XC8 reciente de Microchip ya que declara unos #define que anulan al atributo __interrupt() de las funciones de interrupción del código. Esto hace que éstas NO sean linkeadas en el binario final. Las correcciones a hacer fueron como sigue:
I2C-Temperature-Data-Logger/Firmware/USB/include/usb_ch9.h at main · MicrochipTech/I2C-Temperature-Data-Logger · GitHub Debes eliminar el #define __attribute__(a).
I2C-Temperature-Data-Logger/Firmware/USB/include/usb_device.h at main · MicrochipTech/I2C-Temperature-Data-Logger · GitHub Debes eliminar el mismo define.
Afortunadamente, la gente de soporte de Microchip me apoyó a encontrar este defecto en el código y después de ello mi código funcionó correctamente.
Objetivo
Obtener las mediciones angulares de un sensor AS5048A a través del microcontrolador PIC18F13K50 usando el puerto SPI y enviar los datos obtenidos a una PC a través de la conexión USB del PIC en modo HID (Human Interface Device).
Conexiones
El sensor AS5048A es un chip que mediante sensores Hall, convertidores analógico/digital y procesadores de señales digitales, mide el ángulo de un imán diamétricamente magnetizado colocado frente a él. Estos ángulos tienen una resolución de 14 bits, por lo que puedes medir de 0° a 360° divididos en 16,384 (~22 mili grados de resolución).
Si no usas un imán diamétricamente magnetizado de manera cercana al sensor, el sensor no medirá correctamente el giro del imán respecto a él.
Este sensor puede encontrarse ya soldado a una PCB con headers estándar de 2.54mm. Debes revisar la documentación de la PCB que compres para evitar problemas de voltaje o dañar tu sensor de distancia.
La PCB (breakout board) cuenta con 6 pines para SPI (los demás podrían ser para I2C para el modelo AS5048B):
5V - Voltaje de alimentación
3.3V - Salida de un regulador low dropout de 3.3V a la que hay que conectar un capacitor de 10uF (no colocarle cargas, revisa si tu breakout board ya trae el capacitor)
PWM - Salida PWM equivalente al ángulo del imán (no la usaremos)
CSn - Chip select de SPI
SCK - Reloj de SPI (máxima frecuencia de 10MHz)
MOSI - Master Output Slave Input
MISO - Master Input Slave Output
GND - Tierra
El microcontrolador que echará a andar el sensor es un PIC18F13K50. Lo elegí por su bajo precio y porque tiene prestaciones suficientes para mi aplicación básica: 8kB de ROM, 512B de RAM, puerto SPI maestro con capacidad de funcionar a 3MHz y puerto USB en modo device capaz de funcionar en Full Speed (12Mbps). Debo mencionar que depurar el microcontrolador NO es posible con el PICkit 3 que tengo por 2 razones: una es que los pines debug (ICSP_PGD e ICSP_PGC) están multiplexados con los pines USB (D+ y D-) y la otra es que se requiere un header de depuración para mi PICkit 3 que no conozco. Entonces, la mayoría de pasos para depurar los hice a través de un analizador lógico de 24MHz económico para observar la comunicación SPI entre el PIC y el sensor. Considera esto en tu diseño ya que te puede hacer pasar malos ratos. 😇
El microcontrolador se conectará con el sensor como se ve en el esquemático:
Recuerda no colocar cables muy largos entre el sensor y tu PIC ya que SPI está pensado para comunicación corta entre chips.
La conexión USB del PIC hacia la PC se hace de manera trivial usando los pines RA0 y RA1. IMPORTANTE: si vas a programar tu PIC, debes desconectar el cable USB para evitar problemas de voltaje con la PC.
En RC3 conecté un LED que nos va a indicar si la PC enumeró correctamente la conexión USB con nuestro PIC. Este LED lo activamos en el evento EVENT_CONFIGURED de los callbacks que la librería USB de Microchip pone a nuestra disposición. EVENT_CONFIGURED significa que la enumeración con la PC fue exitosa.
Si modificamos el archivo usb_descriptors.c, específicamente la variable sd001, ahí podemos elegir un número de serie que el firmware compilado del PIC tendrá. Coloqué "0001" y cuando Python desee comunicarse con mi PIC 0001, la librería deberá buscar ese número de serie para encontrarlo.
Conecta el resto de componentes como el esquemático mostrado arriba.
Código fuente del PIC
El código fuente del PIC está organizado de la siguiente manera.
system.c
Este archivo contiene la configuración de los registros de operación del PIC18F13K50 para que opere con un cristal externo de 12MHz y eleve su frecuencia de operación a 48MHz con PLL 4x (12MIPS para la frecuencia de CPU). Este archivo también contiene la función de interrupción que se encargará de atender todos los eventos del puerto USB.
Todos los eventos de USB se atienden por interrupción. Hacerlo por polling complica el código porque las funciones de SPI que empleamos son bloqueantes y si desatendemos las peticiones USB de la PC por mucho tiempo, nos desenumera la conexión USB.
usb_descriptors.c
Este archivo contiene las estructuras USB con las que el PIC se presenta en el handshake con la PC para que ella lo controle como un dispositivo HID. Aquí se declara el Vendor ID (0x04D8 de Microchip) y el Product Id (0x0AB0 para nuestro ejemplo). En este archivo también se define el número de serie único con el que flashearemos al PIC para que la PC pueda hablar con diversos PICs conectados a ella midiendo distancias.
Para lograr esto, se debe modificar el string sd001 de "0001" a cualquier otro número deseado. Cuando Python busca al PIC, lo hará con el VID, PID y número de serie deseados.
usb_config.h
Aquí se definen:
- Tamaño de los endpoints HID (buffers de comunicación entre PC y PIC), los cuales asigné en 8 bytes cada uno
- Modo de operación de la librería USB de Microchip (por interrupción)
- Habilitación de pull-ups internas del PIC para USB (determinan si el PIC se presenta como Low Speed o Full Speed)
- Modo de operación del PIC (Full Speed a 12Mbps)
- Y otros detalles de la enumeración USB
HardwareProfile.h
Aquí se define el pinout del PIC a través de defines. También definimos el pinout del sensor AS5048A. Aquí también se define la velocidad del reloj del PIC.
main.c
Aquí incluyo el código que comunica al PIC con el sensor angular. main() comienza configurando el hardware, levantando el periférico USB y configurando el pinout específico de mi PIC en UserSystemInit(). El pinout se configura como sigue:
- RB4 SDI MISO Entrada
- RC7 SDO MOSI Salida
- RB6 SCK CLK Salida
- RB7 CS Salida
- RC3 USB ok Salida
- RB5 Entrada (este pin lo reusé en una PCB y tú puedes configurarlo como prefieras)
- RC6 SS Entrada (este pin NO lo usé y por eso lo declaré como entrada)
La inicialización de SPI se hace de manera sencilla:
SSPSTAT = 0
SMP = 0, los datos se muestrean a la mitad del tiempo de salida de datos
CKE = 0, la transmisión de datos se hace en el cambio de 0 a 1
SSPCON1 = 0x21
SSPEN = 1, habilita el módulo SPI
CKP = 0, el reloj está inactivo en 0
SSPM = 1, SPI CLK = Fosc / 16 = 48MHz / 16 = 3MHz
La datasheet del AS5048A pide el siguiente modo de comunicación SPI.
La comunicación SPI con el AS5048A es peculiar ya que se envían 2 bytes con el comando por MOSI y el sensor concurrentemente envía 2 bytes por MOSI. La respuesta a nuestro comando SPI NO se recibe al momento. Esta se recibe cuando uno envía otro comando (otros 2 bytes); es decir, la respuesta llega con retraso.
La comunicación de SPI deberá verse como sigue:
El primer comando que manda el PIC al AS5048A es para limpiar error flags (0x4001). Después el PIC envía el comando NOP (0x0000) y el sensor responde al comando anterior con 0x0000. Toda comunicación con el sensor funciona con ese retraso de respuesta.
También existe una paridad cuando uno envía datos al sensor. Se calcula como la función AS5048A_Parity() indica.
El bit resultante de la paridad se coloca en el bit 15 del comando que envíamos al sensor. El sensor también nos envía una paridad en sus respuestas. Puedes verificarla si deseas agregar robustez a tu código.
Después de enviar el comando para limpiar error flags, se envía el comando de leer address 0x03FF, que corresponde a leer el ángulo de medición. Ese ángulo se recibe con el MSB primero y el LSB después. Basta acomodarlo para obtener una medición que va de 0 a 16383.
Debes multiplicar el resultado de 360°/16384 por el dato de 16 bits que te envió el sensor para obtener el ángulo resultante del imán colocado frente a él.
El sensor nos envía la lectura en 16 bits, pero si hubo algún error de comunicación, nos activará el bit 14 de tal medición.
Deberás corroborar que tu modo SPI esté funcionando conforme a lo que la datasheet del AS5048A pide. Si sí hubo una lectura correcta, ponemos la distancia en el endpoint de HID y la envíamos a la PC con HIDTxPacket(). Si la lectura fue incorrecta, no se envían datos a la PC.
El consumo final de recursos de mi PIC es como sigue:
Python leyendo al PIC18F13K50
Desarrollar el script de Python es muy sencillo (Gemini me ayudó). Se hace uso de la librería HID API de Python que funciona para Windows, Linux o Mac indistintamente. Se abre un dispositivo HID con el VID 0x04D8, un PID 0x0AB0 y el número serial que se haya flasheado en el PIC (0001 es el default). Basta con leer los datos del micro y reacomodar el endianness de los bytes para obtener la distancia.
Empleé la librería pyqtgraph para el ploteo en tiempo real de los datos.
Gracias por leer este tutorial. 😎