Skip to main content

RaspberryPI GPIO Access with Memory Mapped IOs

·1576 words·8 mins· loading ·
Table of Contents

Raspberry Pi GPIO Access with Memory Mapped IOs
#

The Raspberry Pi is a fantastic platform for hobbyists and professionals, offering interface to physical world through its General Purpose Input/Output (GPIO) pins.

While many developers rely on high-level libraries to control these pins, understanding and utilizing memory mapped I/O (MMIO) opens up a world of low-level control and performance improvements.

The blog tries to consolidate most of the difficult to find information and deep dive into low level control of Raspberry PI. Eventually, the more complex peripherals will be also covered.


What is Memory Mapped I/O?
#

Memory mapped I/O is a mechanism that allows peripheral devices, like the GPIO controller, to be accessed by mapping their registers into the system’s address space RAM.

This method bypasses the overhead of kernel-mediated interfaces (such as the sysfs or the newer GPIO character device interface), which can be especially advantageous for time-critical applications.

If you have ever dealt with Raspberry PI GPIOs with python, you might have observed that getting anything high speed (MHz range) gets difficult and and harder realtime is elusive.

Here is an example:

I am using a logic analyzer to analyze the GPIO pin 3 on a Raspberry PI Compute Module (Pinout is similar to Raspberry Pi 4) pinout as shown in the figure:

Raspberry PI Setup
import RPi.GPIO as GPIO
import time

GPIO.setmode (GPIO.BCM)
GPIO.setup (3, GPIO.OUT)

while True:
    GPIO.output(3, True)
    time.sleep(1/1000000)
    GPIO.output(3, False)
    time.sleep(1/1000000)

The code above results in toggle frequency of 6.401 KHz which is far cry from 500 KHz expected from the code.

Logic Analyzeer Capture 1

Why Use Memory Mapped I/O?
#

Performance: Direct register access reduces latency compared to higher-level abstractions. One of the reason for latency is System calls

Linux System Calls

Control: Fine-tuned manipulation of the GPIO registers lets you implement custom functionality not available via standard libraries.

Learning: Understanding MMIO gives insights into how hardware interacts with software at a low level.


Understanding the Raspberry Pi Memory Map
#

Each Raspberry Pi model may have slight variations in its memory map, but the concept remains consistent. The GPIO peripheral is located at a fixed physical address. For example:

  • Raspberry Pi 1: The peripheral base address is typically 0x20000000.
  • Raspberry Pi 2, 3, and Zero: The base address is usually 0x3F000000.
  • Raspberry Pi 4: The base address is usually 0xFE000000.

The GPIO registers themselves are located at an offset from the peripheral base. For many models, the GPIO registers begin at an offset of 0x200000 from the peripheral base address.

Information about register and base address can be found on respective datasheets of the processor:

Raspberry Pi 1 uses Broadcom BCM2835 processor. Link

Raspberry Pi 2,3 and zero uses Broadcom BCM2836 processor. Link

Raspberry Pi 4 uses Broadcom BCM2711 processor. Link

Note: Always verify the base addresses and offsets for your particular Raspberry Pi model as they may change with hardware revisions.

Note: RaspberryPI 5 is not covered.


Accessing GPIO with /dev/mem
#

To manipulate the GPIO registers directly, you’ll need to map the physical memory region into your process’s virtual address space using the mmap system call. This requires root privileges since you are accessing hardware-level resources.

Raspberry Pi Pinout Map
#

Raspberry PI Pinout

The Process
#

  1. Open /dev/mem: This special file represents the physical memory (RAM) of your device. This can be used to manipulate any memory space in the RAM. Note: Caution this can crash the OS or damage a device

  2. Use mmap: Map the physical address range corresponding to the GPIO registers into your process’s address space.

Now the we need to map our process virtual address space is due to fact taht physical memory (Real RAM) is translated to an virtual address space by Kernel with the help of Memory Management Unit.

This is a whole topic in itself so I recommend to read some online resource to understand it further. Link.

In the following example we map base address of GPIO 0xFE200000 for Raspberry PI 4 (more details in Data sheet of Raspberry Pi CPU)

void setup_gpio() {
    int mem_fd;
    void *gpio_map;

    if ((mem_fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) {
        perror("Failed to open /dev/mem, try running as root");
        exit(EXIT_FAILURE);
    }

    // Map GPIO memory to our address space
    gpio_map = mmap(
        NULL,                 // Any address in our space will do
        BLOCK_SIZE,           // Map length
        PROT_READ | PROT_WRITE, // Enable reading & writing to mapped memory
        MAP_SHARED,           // Shared with other processes
        mem_fd,               // File descriptor for /dev/mem
        GPIO_BASE             // Offset to GPIO peripheral
    );

    close(mem_fd);

    if (gpio_map == MAP_FAILED) {
        perror("mmap error");
        exit(EXIT_FAILURE);
    }

    // volatile pointer to prevent compiler optimizations
    gpio = (volatile unsigned int *)gpio_map;
}
  1. Manipulate the Registers: Once mapped, you can read and write to the mapped registers directly.

Here we manipulate gpio = (volatile unsigned int *)gpio_map; which is GPIOSEL0 register given on page 67-68 of BCM2711 Data sheet

Datasheet 1 Datasheet 2

To set a GPIO pin as out we need to set 001 is respective register bit.

void set_gpio_output(int pin) {
    // As shown in the figure above the pin in 
    // GPIOSEL0 goes from 0-9 and next pins 10-19
    // is on reg GPIOSEL1 and so on
    int reg = pin / 10; 
    int shift = (pin % 10) * 3;
    // Clear the 3 bits for the pin and set it to 001 (output)
    gpio[reg] = (gpio[reg] & ~(7 << shift)) | (1 << shift);
}

// GPSET0 register and respective pin is set
// 7 is offset 0x1C / 0x04. See figure above
void set_gpio(int pin) {
    gpio[7] = (1 << pin);
}

// GPCLR0 register and respective pin is cleared
// 10 is offset 0x28 / 0x04. See figure above
void reset_gpio(int pin) {
    gpio[10] = (1 << pin);
}
  1. Clean Up: Unmap the memory region when you’re done to free up resources.

Implementation in C from User Space
#

Below is a simplified example in C demonstrating how to map the GPIO registers and toggle a GPIO pin (for example, turning an LED on and off). This code is for educational purposes; error checking and additional functionality might be needed for production applications.

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdint.h>


// #define BCM2835_PERI_BASE      0x3F000000   // For Raspberry Pi 2, 3, Zero
// #define BCM2835_PERI_BASE      0x20000000   // For Raspberry Pi 1
#define BCM2835_PERI_BASE      0xFE000000   // For Raspberry Pi 4

#define GPIO_BASE              (BCM2835_PERI_BASE + 0x200000) // GPIO controller base
#define BLOCK_SIZE             (4*1024)

// Pointer to the mapped memory
volatile unsigned int *gpio;

// Function to set up the memory mapping for GPIO access
void setup_gpio() {
    int mem_fd;
    void *gpio_map;

    if ((mem_fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) {
        perror("Failed to open /dev/mem, try running as root");
        exit(EXIT_FAILURE);
    }

    // Map GPIO memory to our address space
    gpio_map = mmap(
        NULL,                 // Any address in our space will do
        BLOCK_SIZE,           // Map length
        PROT_READ | PROT_WRITE, // Enable reading & writing to mapped memory
        MAP_SHARED,           // Shared with other processes
        mem_fd,               // File descriptor for /dev/mem
        GPIO_BASE             // Offset to GPIO peripheral
    );

    close(mem_fd);

    if (gpio_map == MAP_FAILED) {
        perror("mmap error");
        exit(EXIT_FAILURE);
    }

    // volatile pointer to prevent compiler optimizations
    gpio = (volatile unsigned int *)gpio_map;
}

// Set GPIO pin as output
void set_gpio_output(int pin) {
    int reg = pin / 10;
    int shift = (pin % 10) * 3;
    // Clear the 3 bits for the pin and set it to 001 (output)
    gpio[reg] = (gpio[reg] & ~(7 << shift)) | (1 << shift);
}

// Set GPIO function
void set_gpio(int pin) {
    gpio[7] = (1 << pin);
}

// Reset GPIO function
void reset_gpio(int pin) {
    gpio[10] = (1 << pin);
}

int main() {
    // Toggle GPIO pin 3
    const int pin = 3;
    setup_gpio();
    set_gpio_output(pin);
    printf("Toggling GPIO %d. Press Ctrl+C to exit.\n", pin);

    // Toggle loop: alternately set and clear the pin
    while (1) {
        set_gpio(pin);    // Set pin high
        usleep(1);        // Wait for 1 us
        reset_gpio(pin);  // Set pin low
        usleep(1);        // Wait for 1 us
    }
    return 0;
}

Save the above file as gpio_toggle.c and to compile the code run following commands in shell

gcc -O3 gpio_toggle.c -o gpio_toggle

and to run the code

sudo ./gpio_toggle

Even with all this optimization and use of C code the maximum frequency of 8.554 KHz was achieved when a frequency of 500 KHz was expected.

Logic 2

In my next blog posts I will continue the journey to achieve our desired frequency of 500KHz and more.

Next blog: Raspberry PI GPIO Linux Driver


Tags: #raspberry #pi #gpio #RPi


DISCLAIMER: This code is provided for educational and demonstration purposes only.

USE AT YOUR OWN RISK.

The authors code disclaim any and all liability for any direct, indirect, incidental, special, or consequential damages arising out of or in connection with the use, modification, or distribution of this software. This code is provided “AS IS” without any express or implied warranties, including, but not limited to, the implied warranties of merchantability, fitness for a particular purpose, or non-infringement.

By using this code, you agree that you are solely responsible for ensuring that its use complies with all applicable laws, regulations, and licensing requirements. It is your responsibility to thoroughly test and verify the module in your environment prior to any production use. The author assumes no responsibility for any damage to your systems, data loss, or other adverse consequences that may result from the use or misuse of this code. If you do not agree with the terms of this disclaimer, do not use the code provided above.