Estoy intentando escribir una capa de abstracción de hardware (HAL) para un PIC32 (PIC32MX664F128H para ser preciso), en el que puedo usar definiciones genéricas para los puertos en cuestión sin tener que usar los nombres de puertos y los registros de puertos exactos. Idealmente, me gustaría usar solo estas definiciones para los diferentes pines en el PIC, y tener un módulo "wrapper" en el que puedo especificar a qué pines se refieren las definiciones.
He leído a través de esta publicación en el foro , que básicamente explica exactamente lo que estoy tratando de lograr. La publicación # 4 (respuesta # 3) es de particular interés aquí. La idea básica es usar una estructura ( GPIO_TypeDef
) para definir un tipo que contenga los 4 registros (TRIS, PORT, LAT y ODC) para cada puerto (tenga en cuenta que la publicación del foro fue para un PIC24 (procesador de 16 bits) , y que he adaptado el código para el PIC32 (procesador de 32 bits)):
typedef struct {
volatile unsigned int TRIS; //direction register - offset 0x0000
volatile unsigned int PORT; //input data register
volatile unsigned int LAT; //output data register
volatile unsigned int ODC; //open drain register
} GPIO_TypeDef; //gpio type definitions
Luego, todo el puerto se convierte y se vincula a una definición que se usa para apuntar a la dirección del puerto en la memoria:
#define GPIOA ((GPIO_TypeDef *) &TRISA)
#define GPIOB ((GPIO_TypeDef *) &TRISB)
etc...
La dirección del registro TRIS se usa para el enlace del puntero, ya que es el primero de los cuatro registros que se producen en la memoria para cada puerto. Esto se puede ver en la hoja de datos del PIC (usando PORTB como ejemplo):
Usando PORTB para el resto de esta publicación, la definición GPIOB
debería apuntar al registro TRISB (con la dirección base 0xBF886040), o más bien, GPIOB
debería apuntar a la dirección del primer elemento del registro TRISB .
Luego se define un conjunto de macros para las operaciones de puerto:
#define PIN_SET(port, pins) port->LAT |= (pins) //set pins on port
#define PIN_CLR(port, pins) port->LAT &=~(pins) //clear pins on port
#define PIN_FLP(port, pins) port->LAT ^= (pins) //flip pins on port
#define PIN_GET(port, pins) ((port->PORT) & (pins)) //get pins
#define PIN_OUT(port, pins) port->TRIS &=~(pins) //pins as output
En el código de usuario, uno definiría, por ejemplo, un pin que controla un LED de la siguiente manera (usando PORTB en lugar de PORTC como en la publicación del foro):
#define LED_PORT GPIOB
#define LED (1<<4) //led on PORTB.4
El LED se puede encender / apagar de la siguiente manera:
PIN_SET(LED_PORT, LED); //turn LED on
PIN_CLR(LED_PORT, LED); //turn LED off
Sin embargo, cuando uso el código anterior, el pin correspondiente en el registro TRISB se alterna, en lugar del registro LATB. Además, al intentar apagar el LED, el bit correspondiente que se estableció en el registro TRISB no vuelve a cambiar a 0. ¿Por qué esto no funciona?
Lo que es interesante es si defino estructuras separadas para cada uno de los cuatro registros de puertos de la siguiente manera:
typedef struct
{
volatile unsigned int LAT;
} IO_LAT;
typedef struct
{
volatile unsigned int PORT;
} IO_PORT;
typedef struct
{
volatile unsigned int TRIS;
} IO_TRIS;
typedef struct
{
volatile unsigned int ODC;
} IO_ODC;
#define IO_LATB ((IO_LAT *)&LATB)
y alterne el bit correspondiente (bit 4) en cada uno de estos registros de la siguiente manera:
PIN_SET(IO_LATB, LED);
PIN_CLR(IO_LATB, LED);
funciona perfectamente. Lo mismo ocurre cuando hago lo mismo con los registros TRIS, PORT y ODC (y, por supuesto, modificando las macros en consecuencia). ¿Por qué funciona esto, y no el primer método?