3
\$\begingroup\$

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?

\$\endgroup\$
  • \$\begingroup\$ If you don’t use LATSET CLR and you use interrupts your code will cause issues. \$\endgroup\$ – Erik Friesen Feb 19 '18 at 2:44
2
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ This answers my question and also expands on how I should use the macros with the corresponding SET, CLR and INV registers. I am thus marking this as the answer to my question. Thanks for pointing out the possible issues due to the read-modify-writes. Can I still use the PIN_GET macro as it is defined in my question? Since it doesn't perform any modification/write of a register, is shouldn't be susceptible to the issues as with the PIN_SET, PIN_CLR and PIN_FLP macros? \$\endgroup\$ – wave.jaco Feb 19 '18 at 22:18
  • \$\begingroup\$ That is right, reads to ports are fine,so I left your other macros alone. \$\endgroup\$ – Erik Friesen Feb 20 '18 at 2:47
1
\$\begingroup\$

Take a look at the diagram for one typical I/O port.

Notice that WR tris overrides all other write operations, so you have to control the port in the proper sequence. WR TRIS should be first, then WR LAT, then WR ODC. WR LAT and WR PORT go to a OR gate, so either one puts data on the pin.

It works manually because you implicitly define the order, but as a global command things may not occur in the right order, especially if you get a toggling effect.

Consider re-wording your type def to include a specific order, so the data does not loop back to itself (toggle). AS part of the HAL, it must also contain the proper sequence.

enter image description here

\$\endgroup\$
  • \$\begingroup\$ This provides a valuable and interesting insight into the port operation in general. Thank you for your answer. \$\endgroup\$ – wave.jaco Feb 19 '18 at 22:33
1
\$\begingroup\$

I'm not familiar with this microcontroller. But the virtual addresses in that table (6040, 6050, 6060, and 6070) seem like they are 16 bytes apart. If an unsigned int is 32-bits then your structure places them only 4 bytes apart.

Does your first method work if you use the following structure definition?

typedef struct {
 volatile unsigned int TRIS; //direction register - offset 0x0000
 volatile unsigned int reserved1[3];
 volatile unsigned int PORT; //input data register
 volatile unsigned int reserved2[3];
 volatile unsigned int LAT;  //output data register
 volatile unsigned int reserved3[3];
 volatile unsigned int ODC;  //open drain register
} GPIO_TypeDef;    //gpio type definitions

If so, then perhaps all the bytes from 6044 to 604F alias down to the TRIS register and that is why you saw the TRIS register change when you unintentionally set 6048 through 604B.

\$\endgroup\$
  • \$\begingroup\$ I have completely missed the fact that the virtual addresses are actually 16 bytes apart. It seems that I have indeed neglected the SET, CLR and INV registers for each port register. Thanks for pointing out my mistake. \$\endgroup\$ – wave.jaco Feb 19 '18 at 22:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.