Electronic – PIC32 GPIO pins Hardware Abstraction Layer question

gpiohal-librarymicrocontrollerpic

I am attempting to write a Hardware Abstraction Layer (HAL) for a PIC32 (PIC32MX664F128H to be precise), in which I can use generic defines for the ports in question without having to use the exact port names and port registers. Ideally I would like to only use these defines for the various pins on the PIC, and have a "wrapper" module in which I can specify which pins the defines refer to.

I have read through this forum post, which basically explains exactly what I am trying to achieve. Post #4 (reply #3) is of particular interest here. The basic idea is to use a struct (GPIO_TypeDef) to define a type that contains the 4 registers (TRIS, PORT, LAT and ODC) for each port (note that the forum post was for a PIC24 (16-bit processor), and that I've adapted the code for the PIC32 (32-bit processor)):

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

The entire port is then cast and bound to a define which is used to point to the port address in memory:

#define GPIOA  ((GPIO_TypeDef *) &TRISA)
#define GPIOB  ((GPIO_TypeDef *) &TRISB)
etc...

The TRIS register's address is used for the pointer binding, since it is the first of the four register to occur in memory for each port. This can be seen from the PIC's datasheet (using PORTB as an example):

PORTB register map

Using PORTB for the rest of this post, the define GPIOB thus should point to the TRISB register (with base address 0xBF886040), or rather, GPIOB should point to the address of the first element of the TRISB register.

A set of macros is then defined for the port operations:

#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

In the user code, one would define e.g. a pin that controls an LED as follows (using PORTB instead of PORTC as in the forum post):

#define LED_PORT  GPIOB
#define LED       (1<<4) //led on PORTB.4

The LED can then be turned on/off as follows:

PIN_SET(LED_PORT, LED); //turn LED on
PIN_CLR(LED_PORT, LED); //turn LED off

However, when I use the above code, the corresponding pin in the TRISB register is toggled, instead of the LATB register. Furthermore, attempting to turn the LED off, the corresponding bit that was set in the TRISB register doesn't toggle back to 0. Why is this not working?

What is interesting is if I define seperate structs for each of the four port registers as follows:

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)

and toggle the corresponding bit (bit 4) in each of these registers as follows:

PIN_SET(IO_LATB, LED);
PIN_CLR(IO_LATB, LED);

it works perfectly. The same goes for when I do the same with the TRIS, PORT and ODC registers (and of course modifying the macros accordingly). Why does this work, and not the first method?

Best Answer

I think the real problem may lie in that you don't have your struct layed out correctly to include each sfr's atomic instruction sfr. In MplabX you can ctrl click on an sfr like PORTA to go to the header. For example, for a PIC32MM series, I get this

extern volatile __LATAbits_t LATAbits __asm__ ("LATA") __attribute__((section("sfrs"), address(0xBF802630)));
extern volatile unsigned int        LATACLR __attribute__((section("sfrs"),address(0xBF802634)));
extern volatile unsigned int        LATASET __attribute__((section("sfrs"),address(0xBF802638)));
extern volatile unsigned int        LATAINV __attribute__((section("sfrs"),address(0xBF80263C)));

So to do this correctly, your struct definition should be something like

typedef struct {
     volatile unsigned int TRIS; //direction register - offset 0x0000
     volatile unsigned int TRISCLR;
     volatile unsigned int TRISSET;
     volatile unsigned int TRISINV;
     volatile unsigned int PORT; //input data register
     volatile unsigned int PORTCLR;
     volatile unsigned int PORTSET;
     volatile unsigned int PORTINV;
     volatile unsigned int LAT;  //output data register
     volatile unsigned int LATCLR;
     volatile unsigned int LATSET;
     volatile unsigned int LATINV;
     volatile unsigned int ODC;  //open drain register
     volatile unsigned int ODCCLR;
     volatile unsigned int ODCSET;
     volatile unsigned int ODCINV;
} GPIO_TypeDef;    //gpio type definitions

also as a bonus answer to extend my comment, you must use the atomic instructions with the pic32 or cross interrupt writes to latches will be corrupted due to read modify writes. So to correct your examples, they should be something like

#define PIN_SET(port, pin) port->LATSET = (1UL << pin) //set pins on port
#define PIN_CLR(port, pin) port->LATCLR = (1UL << pin) //clear pins on port
#define PIN_FLP(port, pin) port->LATINV = (1UL << pin) //flip pins on port

kkrambo's answer deserves some mention here, as it has shades of the correct answer.