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
A Fast Pulse

The Pi 5 isn't hardware compatible with the other versions of the Pi. If you want to use the GPIO lines or any of the peripherals directly you are going to have to resort to memory mapping. This is an extract from Raspberry Pi IoT in C, Third Edition.

Raspberry Pi And The IoT In C Third Edition

By Harry Fairhead


Buy from Amazon.


  1. Choosing A Pi For IoT
  2. Getting Started
  3. Getting Started With The GPIO
  4. Simple Output
  5. Some Electronics
  6. Simple Input
  7. GPIO The Linux Way
  8. Advanced Input – Events, Threads, Interrupts
  9. Pulse Width Modulation - Servos And More
  10. Using The I2C Bus
  11. The DHT22 Sensor Implementing A Custom Protocol
  12. Exploring - 1‑Wire Bus Basics 
  13. Using iButtons
  14. DS18B20 Temperature Sensor
  15. The Multidrop 1‑Wire Bus
  16. The Serial Port
  17. Getting Started With The SPI Bus
  18. A to D With The SPI Bus
  19. Connecting With The Web - Sockets
  20. Memory-Mapped GPIO
  21. The Pi 5
    Extract: Memory Mapped GPIO ***NEW!!!
  22. Almost Real-Time Linux
  23. Appendix I GPIO Sysfs Interface
  24. Appendix II Using VS Code For Remote C Development

The Pi 5 uses the custom RP1 chip to provide the connection to all of the peripherals. This is very different from the bcm2835 which is used in the earlier versions of the Pi. The RP1 may be new but it is essentially a modified RP2040 which powers the Pico. What this means is that the Pico SDK contains software that, with some modification, can be used with the Pi 5’s peripherals. As an example, in this chapter, we look at how to get started with the GPIO hardware, which is very different from the implemented GPIO lines and its complexity can be something of a shock. We’ll use the Linux file memory mapping detailed in the previous chapter.

Accessing The Memory

The Pi 5 has a huge peripheral area starting at 0x40000000. This is the physical address which is translated to 0x1f00000000 in the 40-bit address space. The /dev/mem file can be used in the usual way but you have to be running with root privileges. The /dev/gpiomem0 file only maps the GPIO area starting at 0x400d0000, or 0x1f000d0000, but it doesn’t need root privileges. In the examples that follow, the mem file is used with root privileges because it works with peripherals other than the GPIO lines.

To access the GPIO area of memory you can map the entire peripherals area into user space using:

 int memfd = open("/dev/mem", O_RDWR | O_SYNC);
    uint32_t *map = (uint32_t *)mmap(
         64 * 1024* 1024,
    if (map == MAP_FAILED)
        printf("mmap failed: %s\n", strerror(errno));
        return (-1);


This maps the entire 64MByte area – if you only want to use the GPIO lines then you can make this smaller, but there is no problem accommodating it in the 4,096MByte, 32-bit address space.

Once you have the address of the peripheral area you can access the registers that control them using offsets from the hardware-specified base address. For example, the GPIO control registers have an address of 0x400d0000, which gives an offset of 0xd0000. As the pointers are to uint32_t we have to remember to divide by 4 so that the correct offset is added. That is, the GPIO registers are located at:

    uint32_t *PERIBase = map;
    uint32_t *GPIOBase = PERIBase + 0xd0000 / 4;

You can check that GPIOBase references a location in user space 0xd000 higher than PERIBase.

With this information we can now start to work with the GPIO registers:


Register Name




GPIO status



GPIO control including function select and overrides



GPIO status



GPIO control including function select and overrides

… and so on down to



GPIO control including function select and overrides

You can see that there are two registers for each GPIO line from GPIO 0 to GPIO 27, one control register and one status register.

Last Updated ( Wednesday, 10 January 2024 )