Raspberry Pi IoT In C - Pi 5 Memory Mapped GPIO
Written by Harry Fairhead   
Wednesday, 10 January 2024
Article Index
Raspberry Pi IoT In C - Pi 5 Memory Mapped GPIO
Registers
RIO
A Fast Pulse

Registered IO Block

At this point you might think that we are ready to access the state of the GPIO lines for general input and output. This isn’t quite the whole story. To accommodate the fact that the processor has two cores, and to make access to important devices faster, there is a special connection between the cores and the GPIO, the RIO or Registered IO Block.

Notice that the GPIO lines are multipurpose and to use a GPIO line via the RIO you have to set its mode to RIO via direct access to its control register. The low-order five bits of the GPIO control register set the function of the GPIO line. You can see the values needed for any given function in the documentation, but to assign the GPIO line to RIO control you have to set it to function 0x05. In this sense the RIO is just another peripheral that can take control of a GPIO line.

The RIO provides a set of registers that makes using the GPIO much faster and much easier to use. The basic registers are:

0x00

OUT

Sets GPIO lines to high or low

0x04

OE

Sets GPIO line to output driver or high impedance

0x08

IN

Reads GPIO lines

0x0C

INSYNC

Reads GPIO lines synchronized to clk_sys

Each register holds a bit pattern such that bit n relates to GPIOn. For example, the first bit of OUT sets the level of GPIO 0, the fifth bit of IN reflects the current state of GPIO 5 and so on.

There is also a general principle that every register has a set of four aliases. If the register is at address X then:

 

X+0x0000

Normal read/write access

X+0x1000

Atomic XOR on write and reads have no side effects

X+0x2000

Atomic SET on write and normal read access

X+0x3000

Atomic CLR on write and normal read access

It is difficult to know from the documentation if there are any exceptions to this, but it does seem to be a general mechanism.

 

For the RIO registers the availability of XOR, SET and CLR registers is a great simplification. For example, if you want to set GPIO 0 to a high output without modifying any of the other GPIO lines then you would need to first read OUT, OR the value with 0x01 and then store the resulting bit pattern in OUT. Using SET you would simply store 0x01 in SET as the only line to be set is GPIO 0.

The RIO registers are at 0x400e0000, which makes its offset 0xe0000. We can use the same technique of defining a struct to allow access to the registers:

typedef struct
{
    uint32_t Out;
    uint32_t OE;
    uint32_t In;
    uint32_t InSync;
} rioregs;
#define rio ((rioregs *)RIOBase)

where RIOBase is:

uint32_t *RIOBase = PERIBase + 0xe0000 / 4;

We can use the same technique to access the XOR, SET and CLR registers:

#define rioXOR ((rioregs *)(RIOBase + 0x1000 / 4))
#define rioSET ((rioregs *)(RIOBase + 0x2000 / 4))
#define rioCLR ((rioregs *)(RIOBase + 0x3000 / 4))

PAD Control

Using the RIO we can read and write the GPIO lines, but there is a further step we have to complete before we can actually make use of them. As described in the previous chapter, each GPIO line has its own PAD and the Pi 5 PAD is very similar to that found in previous devices:

padcontrol5

The control of the PAD is also similar to that described in the previous chapter. The PAD registers for the GPIO bank we are using is at 0x400f0000 which means its offset is 0xf0000:

uint32_t *PADBase = PERIBase + 0xf0000 / 4;

The only complication is that the first register is a VOLTAGE_SELECT register which sets the voltage that all of the GPIO lines in bank 0 work with. A zero sets 3.3V and a 1 sets 1.8V. The default is 3.3V and you can ignore this register unless you need to work at 1.8V.

After the VOLTAGE_SELECT register there is a PAD register for each GPIO line:

Bits

Name

Description

Type

Reset

31:8

Reserved

 

 

 

7

OD

Output disable. Has priority over output enable from peripherals

RW

0x1

6

IE

Input enable

RW

0x0

5:4

DRIVE

Drive strength.
0x0 → 2mA
0x1 → 4mA
0x2 → 8mA
0x3 → 12mA

RW

0x1

3

PUE

Pull-up enable

RW

Varies

2

PDE

Pull-down enable

RW

Varies

1

SCHMITT

Enable Schmitt trigger

RW

0x1

0

SLEWFAST

Slew rate control. 1 = Fast, 0 = Slow

RW

0x0

 

We can access the PAD registers using:

uint32_t *pad = PADBase + 1;

For example, if we want to set the PAD to output with a default drive, no pull-up/down, no Schmitt trigger and slow slew rate we would use:

pad[pin]= 0x10;

Notice that output is set by not enabling input.



Last Updated ( Wednesday, 10 January 2024 )