Raspberry Pi IoT In C - Pi 5 Memory Mapped GPIO |
Written by Harry Fairhead | ||||||||||||||||||||||||||
Wednesday, 10 January 2024 | ||||||||||||||||||||||||||
Page 1 of 4 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 EditionBy Harry FairheadBuy from Amazon. Contents
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 MemoryThe 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( NULL, 64 * 1024* 1024, (PROT_READ | PROT_WRITE), MAP_SHARED, memfd, 0x1f00000000 ); if (map == MAP_FAILED) { printf("mmap failed: %s\n", strerror(errno)); return (-1); }; close(memfd);
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:
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 ) |