1

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):

enter image description here

5
  • 1
    Try putting a pull (high or low) on GPIO13. Sometimes floating GPIO inputs can resonate with another GPIO. Does it still happen if GPIO13 is set as an output?
    – joan
    Oct 30, 2022 at 9:07
  • @Milliways My code is perfectly readable. I do not know what you mean by 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.
    – Marco
    Oct 30, 2022 at 15:25
  • @joan Thanks for the suggestion. If GPIO13 is set as an input, it does not follow GPIO19. Which makes sense to me, an input should not be able to drive any load. The thing is, I want to use GPIO19 and GPIO13 (I could use other pins, but let me explain) together. If I set GPIO13 as an output it follows GPIO19 but only if I'm using my code. The issue is not present when using a third party library. This makes me believe that this is not a hardware (or "electrical") issue but a software issue. I'm actually trying to learn how to access low-level peripherals through high-level code. [...]
    – Marco
    Oct 30, 2022 at 15:33
  • [...] This is why I want to understand why this code isn't doing what it's supposed to. It's very frustrating to me because it should be a simple matter of mapping some memory and then setting some bits. I really have no idea why this is happening :(
    – Marco
    Oct 30, 2022 at 15:37
  • I will probably reset (clean install) my Raspberry Pi and see if that changes anything..
    – Marco
    Oct 30, 2022 at 15:38

1 Answer 1

0

After digging through the pigpio library code some more I found that the gpioWrite function does not use bitwise ORing, but rather sets the bits directly:

#define BANK (gpio>>5)
#define BIT  (1<<(gpio&0x1F))

// ...

int gpioWrite(unsigned gpio, unsigned level) {
    // ...

    // BANK is 0 here (19>>5) is 0
    // BIT is 0x80000 here (1<<(gpio&0x1F)) is 524288

    if (level == PI_OFF) *(gpioReg + GPCLR0 + BANK) = BIT;
    else                 *(gpioReg + GPSET0 + BANK) = BIT;
    // ...
}

Changing my code to do the same resolved the issue:

while (1) {
    if (!use_pigpio) {
        *(GPCLEAR0) = (1u << 19);
    } else {
        gpioWrite(19, 0);
    }
    wait();

    if (!use_pigpio) {
        *(GPSET0) = (1u << 19);
    } else {
        gpioWrite(19, 1);
    }
    wait();
}

For those wondering why to use = instead of |= I asked on electrical engineering and got the following answer:

This behavior is rather typical for set-and-clear register pairs. When you read it, you get the actual state of the register back. When you write to the clear register, any 1 bits in the written value will cause the corresponding register bit to be cleared. [...] The correct way to clear a bit is to write only that bit to the GPCLEAR register. All other bits have to be 0. The same goes for GPSET. That's why you have to use =, not |=.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.