Raspberry Pi PWM and GPIO Support Using Memory Map - cu-ecen-aeld/buildroot-assignments-base GitHub Wiki

Overview

This page describes how the mmap system call can be used to enable and use the hardware peripheral registers on a Raspberry Pi. A working example for this can be found here.

System Memory - /dev/mem

/dev/mem is a character device file which is an image of the main memory of the system. This file allows direct access to physical addresses. The CONFIG_STRICT_DEVMEM kernel configuration flag must be disabled or set to "n" to allow access to the /dev/mem. To check this flag, the kernel's .config file must be viewed. On the Raspberry Pi the following instructions should be followed:

  1. sudo modprobe configs
  2. zcat /proc/config.gz > .config
  3. cat .config | grep CONFIG_STRICT_DEVMEM

The output looks like this:

This file can be accessed by programs that have appropriate privileges, such as system utilities and some drivers. The permission can viewed using the ls -l command on /dev/mem.

The CONFIG_STRICT_DEVMEM flag can be viewed in the buildroot repository by typing the make linux-menuconfig and searching for the STRICT_DEVMEM flag. The output should look like this:

image

Accessing /dev/mem allows programs to read and write directly to the main memory of the system, which can be useful for debugging and other low-level tasks. Reference: https://man7.org/linux/man-pages/man4/mem.4.html

Mmap

Memory mapping can be used as means to allow programs in userspace to directly access device memory. Mmap is a system call that is used to map files or devices into memory. It is commonly used by programs to access and manipulate files/devices without reading/writing the entire contents of the file into memory. The mmap system call takes six arguments: the address in memory where the file or device should be mapped, the size of the mapping, the physical address to be mapped and various flags that control the behavior of the mapping. The prot argument specifies the memory protection or access. PROT_READ | PROT_WRITE can be used to allow for read and write to the physical memory. The flags argument specifies whether the mapping can be viewed by other processes. The system call returns a pointer to block of memory that has been mapped. Upon invocation of mmap, the operating system creates a virtual memory mapping for the file or device, which the invoking program can then access as if it were a block of memory.

Reference: https://man7.org/linux/man-pages/man2/mmap.2.html

Using mmap to access hardware peripheral registers on a Raspberry Pi

Hardware peripherals are typically located at a definite offset within the memory map of the Raspberry Pi. These offsets depict the exact physical memory location of that peripheral. The mmap system call can be used to map the addresses of these peripherals into the virtual memory space of the process. This allows the process to control and manipulate the peripheral's register values. A major advantage to using this utility for embedded programming is that it is analogous to bare-metal programming with direct access to registers and provides a greater degree of control. A disadvantage is the security concern of writing to memory that might hold sensitive information. It's usage can also cause concurrency bugs when accessing the registers as multiple similar programs can be invoked.

PWM and GPIO Setup using MMAP

This capability can be used to drive GPIO pins and control the PWM peripherals on a Raspberry Pi amongst other peripherals. The BCM2711 peripheral manual was referred to find information on the GPIO, PWM and Clock Management peripherals. User defined structures can be used to map these peripheral registers. An example of this is the GPIO mapping shown below:

// Structure that maps the exact layout for the GPIO peripheral registers on the Raspberry Pi

typedef struct

{

uint32_t fsel[6]; // GPIO Function Select

uint32_t resvd_0x18;

uint32_t set[2]; // GPIO Pin Output Set

uint32_t resvd_0x24;

uint32_t clr[2]; // GPIO Pin Output Clear

uint32_t resvd_0x30;

uint32_t lev[2]; // GPIO Pin Level

uint32_t resvd_0x3c;

uint32_t eds[2]; // GPIO Pin Event Detect Status

uint32_t resvd_0x48;

uint32_t ren[2]; // GPIO Pin Rising Edge Detect Enable

uint32_t resvd_0x54;

uint32_t fen[2]; // GPIO Pin Falling Edge Detect Enable

uint32_t resvd_0x60;

uint32_t hen[2]; // GPIO Pin High Detect Enable

uint32_t resvd_0x6c;

uint32_t len[2]; // GPIO Pin Low Detect Enable

uint32_t resvd_0x78;

uint32_t aren[2]; // GPIO Pin Async Rising Edge Detect

uint32_t resvd_0x84;

uint32_t afen[2]; // GPIO Pin Async Falling Edge Detect

uint32_t resvd_0x90;

uint32_t pud; // GPIO Pin Pull up/down Enable

uint32_t pudclk[2]; // GPIO Pin Pull up/down Enable Clock

uint32_t resvd_0xa0[4];

uint32_t test;

} __attribute__((packed, aligned(4))) gpio_t;

The source code for this structure can be found in this library. This mapping follows the exact addresses for all the registers present in the Raspberry Pi 4. Similarly, user-defined structures have been created for the PWM and Clock Management peripherals. The mmap system call can be used as follows:

mem_fd = open("/dev/mem", O_RDWR | O_SYNC);
gpio = (char *)mmap(0, sizeof(gpio_t), PROT_READ|PROT_WRITE, MAP_SHARED, mem_fd, GPIO_OFFSET);

Here the GPIO_OFFSET is the actual physical address offset for the GPIO peripheral.

#define  GPIO_OFFSET (0xfe200000)

Once the mapping is done, the pointer gpio referenced above can be used to control the GPIO peripherals. The following lines of code depict setting and clearing of a GPIO pin.

gpio->fsel[2] |= (0x1 << 9); // Pin 23 is selected as output pin
gpio->clr[0] |= (1 << 23); // Set the pin
gpio->set[0] |= (1 << 23); // Set the pin

The source code and a basic explanation can be found in this issue.

PWM-Test

A simple PWM test program was written that demonstrates all the capabilities mentioned above. This program takes in user input through command line arguments and changes the duty cycle on the PWM peripheral selected. The source code can be found in the forked repository here. This code is based on the WS281x LED Matrix Rpi library and has been modified to test the PWM peripheral on the Raspberry Pi.

The command line arguments determine the GPIO number and PWM channel to be used as well as the duty cycle for the PWM wave. The test program enables the PWM, GPIO and Clock peripherals and configures the PWM registers in PWM mode. The main contains a while loop that can be terminated by a SIGINT or SIGTERM signal. The duty cycle of the PWM continuously varies from low to high and high to low over the course of the loop. This program can be tested by constructing a basic LED circuit connected to the pin used for PWM to view the changing duty cycles.

This program can be compiled using the Makefile and requires root privileges to be executed. The Makefile has been modified so that it can be used with a build system. The forked repository can be added and run as a package on a custom build. The functionality can be viewed on an LED by running this program with the circuit connected to the specified PWM pin.

The following image depicts the test results on a Raspberry Pi 4. The PWM functionality on an LED circuit can be viewed here. The Buildroot package for the same can be found here.