Ormindo Ormindo - 3 months ago 12
C Question

C pointer casting for operations

I'm fiddling around with GPIO access on the Raspberry Pi, so of course memory gets involved.

The idea is that I have a base address:

gpios
. Depending on what I want to do, I'll reach for some address further up that base address and change some bits.

volatile uint32_t* gpios;


In this example, I want to read the logic value of the pin. For this, there are two registers, the first at address
(gpios + GPIO_INPUT_READ_OFFSET)
for the 32 first gpios, and the second at address
(gpios + GPIO_INPUT_READ_OFFSET + 4)
for the remaining gpios.

I've always written such operations like this:

volatile uint32_t* input = (uint32_t*)
((uint32_t)gpios + GPIO_INPUT_READ_OFFSET + 4*(gpio/32));


With
gpio
simply being the gpio number being manipulated in the current function.

But I've been thinking, and if I'm not mistaken, it could very well be written like this :

volatile uint32_t* input = gpios + GPIO_INPUT_READ_OFFSET/4 + (gpio/32);


Because of pointer shenanigans.

I like both, but I can't really tell which should be preferred over another.

Is there a "best practice" regarding this type of address fiddling?

Answer

IMO the clearest way of doing this in C is to use a pointer to a struct. Looking at the RPi register documentation I can see that the GPIO registers are:

0x7E20 0000 | GPFSEL0 GPIO Function Select 0 | 32 | R/W
0x7E20 0004 | GPFSEL1 GPIO Function Select 1 | 32 | R/W
0x7E20 0008 | GPFSEL2 GPIO Function Select 2 | 32 | R/W
0x7E20 000C | GPFSEL3 GPIO Function Select 3 | 32 | R/W
0x7E20 0010 | GPFSEL4 GPIO Function Select 4 | 32 | R/W
0x7E20 0014 | GPFSEL5 GPIO Function Select 5 | 32 | R/W
0x7E20 0018 | - Reserved                     | -  | -
0x7E20 001C | GPSET0 GPIO Pin Output Set 0   | 32 | W
0x7E20 0020 | GPSET1 GPIO Pin Output Set 1   | 32 | W
0x7E20 0024 | - Reserved                     | -  | -
0x7E20 0028 | GPCLR0 GPIO Pin Output Clear 0 | 32 | W
0x7E20 002C | GPCLR1 GPIO Pin Output Clear 1 | 32 | W 
0x7E20 0030 | - Reserved                     | -  | -
0x7E20 0034 | GPLEV0 GPIO Pin Level 0        | 32 | R
0x7E20 0038 | GPLEV1 GPIO Pin Level 1        | 32 | R
....

What I would do is to create a structure following this layout:

typedef struct {
    volatile       uint32_t gpfsel0;
    volatile       uint32_t gpfsel1;
    volatile       uint32_t gpfsel2;
    volatile       uint32_t gpfsel3;
    volatile       uint32_t gpfsel4;
    volatile       uint32_t gpfsel5;
    volatile       uint32_t : 32;
    volatile       uint32_t gpset0;
    volatile       uint32_t gpset1;
    volatile       uint32_t : 32;
    volatile       uint32_t gpclr0;
    volatile       uint32_t gpclr1;
    volatile       uint32_t : 32;
    volatile const uint32_t gplev0;
    volatile const uint32_t gplev1;
    ....
} RPiGpio;

Then take a RPiGpio pointer to the base address and change / read some bits:

RPiGpio* rPiGpio = (RPiGpio*)(0x7E200000);

rPiGpio->gpclr1 = 0x0001;
uint32_t pins = rPiGpio->gplev0;

Reading a GPIO pin

If you want to choose which register to read based on the gpio pin number, then what I would probably do is change the appropriate struct members to an array and access the register this way:

// Change this
typedef struct {
    ....
    volatile const uint32_t gplev0;
    volatile const uint32_t gplev1;
    ....
} RPiGpio;

// To this
typedef struct {
    ....
    volatile const uint32_t gplev[2];
    ....
} RPiGpio;

// Read a pin
bool isPinHigh(unsigned pinNum)
{
    const RPiGpio* rPiGpio = (const RPiGpio*)(0x7E200000);

    return rPiGpio->gplev[pinNum / 32] & (1 << (pinNum % 32));
}

pinNum is the GPIO pin number 0-63 (I don't know how many pins you can actually access on the Raspberry Pi so I just assumed gpio 0 through gpio 63).

pinNum / 32 selects the appropriate register (0, or 1).

1 << (pinNum % 32) selects the appropriate bit.

& will mask off other bits leaving the state of the desired bit.

Some notes:

Members in the struct are declared volatile. This is really important when reading a register as the register value could change between read calls. If volatile was not included then the compiler might optimize away reads because to the compiler's eyes the value won't change. Volatile tells the compiler that the value may unexpectedly change.

Read-only register are declared volatile const. This will prevent you from accidentally writing to a read-only section of memory (or at least throw a compiler error if you do).

For reserved fields use uint32_t : 32; which is an unnamed 32-bit bitfield. Bitfields are rarely useful because of how little is actually guaranteed about them, but here they work just fine.

Comments