I'm trying to control the GPIOs on my Raspberry Pi 4B through memory mapped I/O.
Here's my code (simplified as much as possible, reproduces the problem on my Pi):
// file: main.c
//
// gcc main.c -lpigpio
//
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdbool.h>
#include <string.h>
#include <pigpio.h>
#define PERIPHERAL_BASE 0xFE000000
static bool use_pigpio = false;
void wait() {
usleep(10E3);
}
int main(int argc, const char **argv) {
if (argc >= 2) {
if (strcasecmp(argv[1], "--use-pigpio") == 0) {
use_pigpio = true;
fprintf(stderr, "Using pigpio\n");
}
}
if (use_pigpio) {
if (gpioInitialise() < 0) {
fprintf(stderr, "Unable to initialise pigpio\n");
return EXIT_FAILURE;
}
fprintf(stderr, "pigpio initialised\n");
}
int fd = open("/dev/mem", O_RDWR | O_SYNC);
if (0 > fd) {
fprintf(stderr, "Could not open /dev/mem\n");
return EXIT_FAILURE;
}
volatile uint32_t *gpio_base_addr = (uint32_t *)mmap(
0,
// length
0xF4,
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_LOCKED,
// file descriptor to /dev/mem
fd,
// address
PERIPHERAL_BASE + 0x00200000
);
if (gpio_base_addr == MAP_FAILED) {
fprintf(stderr, "Could not mmap\n");
return EXIT_FAILURE;
}
volatile uint32_t *GPFSEL1 = (volatile uint32_t *)(((unsigned char *)gpio_base_addr) + 0x04);
volatile uint32_t *GPSET0 = (volatile uint32_t *)(((unsigned char *)gpio_base_addr) + 0x1C);
volatile uint32_t *GPCLEAR0 = (volatile uint32_t *)(((unsigned char *)gpio_base_addr) + 0x28);
uint32_t GPFSEL = *GPFSEL1;
// clear bits 29-27, making BCM pin 19 temporarily an input
GPFSEL &= 0xc7ffffffu;
// set bit 27 making BCM pin 19 as output
GPFSEL |= 0x8000000u;
// clear bits 11-9, making BCM pin 13 temporarily an input
GPFSEL &= 0xfffff1ffu;
// set bit 9 making BCM pin 13 as output
GPFSEL |= 0x200u;
// write GPFSEL1
*GPFSEL1 = GPFSEL;
fprintf(stderr, "GPFSEL1 is %8.8X\n", *GPFSEL1);
while (1) {
if (!use_pigpio) {
*(GPCLEAR0) |= (1u << 19);
} else {
gpioWrite(19, 0);
}
wait();
if (!use_pigpio) {
*(GPSET0) |= (1u << 19);
} else {
gpioWrite(19, 1);
}
wait();
}
}
What it does it toggles BCM Pin 19, but my tests show that the neighbouring Pin (BCM Pin 13) also gets toggled at the same frequency. I have confirmed this with two different Raspberry Pi 4B Models and with an oscilloscope. So I can be very sure it's not a hardware issue. (Writing the GPIOs through WiringPi/pigpio works without setting the adjacent pin).
I tried to have a look at the schematic of my Pi's model, but I was not able to find a full schematic on Raspberry Pi's official website.
Unless I'm missing something very obvious, I have no idea why the following piece of code does not work as expected.
I have the suspicion that it's maybe because of an alternative pin function, but shouldn't setting GPFSEL1
fully determine the pin's mode?
(I already tried rebooting my raspberry pi (to reset any misconfiguration) and then running the same code again. However, the problem persists)
For completeness sake, I printed the value of GPFSEL1
to console and this is what value it has 0x08012224
which is:
GPIO -- 19 18 17 16 15 14 13 12 11 10
00 001 000 000 000 010 010 001 000 100 100
So GPIO19
and GPIO13
both have 001
set which (according to the datasheet of BCM2835 (page 92) is:
001 = GPIO Pin X is an output
Update 1
I have updated my code to switch between direct MMIO and the library pigpio. One can switch between them by running the program with --use-pigpio
:
$ ./a.out # use direct MMIO
$ ./a.out --use-pigpio # use pigpio
Update 2
I created a small bash script to run the program once with MMIO and once with pigpio so I can record a video of whats happening.
The script is:
#!/bin/bash -eufx
rm -f SO
gcc SO.c -lpigpio -o SO
./SO &
SO_PID="$!"
sleep 3
kill -TERM "$SO_PID"
sleep 2
./SO --use-pigpio &
SO_PID="$!"
sleep 3
kill -TERM "$SO_PID"
Here's the video (as a gif):
You could at least define some symbolic constants to make it more readable.
I'm using three different registers through perfectly named variables:GPFSEL1
is a pointer to the GPFSEL1 register and so on.None of those who write such code would use /dev/mem when /dev/gpiomem is available.
why are you making the assumption that they would use/dev/gpiomem
instead of/dev/mem/
when it is available? Have you actually looked at the code of, let's say, pigpio? It uses/dev/mem/
and not/dev/gpiomem
.