How not to use sysfs for GPIO on a Raspberry Pi (& how you should do it in 2023!)


A photograph of a Raspberry Pi connected to a breadboard, with a green LED on it

Some time ago (11 years ago, in fact – all the way back in 2012), I wrote a blog post about how to control GPIO on a Raspberry Pi using sysfs and the /sys/class/gpio construct it provided.

Well since then, time has passed, and in the way of all things, so has the “GPIO Sysfs Interface for Userspace” (as it’s formally known) – which has been deprecated. In fact it’s been depreciated since 2015 – but I (like a lot of other people, I suspect) didn’t notice. In fact I didn’t really notice until about 2020 when we passed the point where the Linux maintainers had said they might remove it. But, they didn’t – so again the world moved on…

Then earlier this year, user Aristide commented to ask about whether they could (or should) still use it…

So I thought that I should really get around to updating that old post – with a 2023 version of how to do it using the new fangled character device.

Using GPIO from the Command-Line

I won’t recap with details about how to use the old method (see the previous blog & it’s companion part two if you’re still interested); but rather will jump ahead to the new method. Introduced around the same time as the sysfs method was deprecated the new character device exposes any GPIO present as /dev/gpiochipN (where the N pertains to each GPIO controller chip that the system has present).

A screenshot of a terminal running on a Raspberry Pi 4, showing the gpiodetect utility running. The tool has identified two gpio chips.
Running gpiodtect and finding two gpio chips

You can install the libraries and tools to interact with this device, using sudo apt install gpiod. This will install the gpiod tools, and the libgpiod2 library to let you interact with the device from your own code.

If we run the gpiodtect utility on a Pi 4 (I haven’t tried any other versions – although they’ll behave similarly, the specifics will likely be a little different), then we see two GPIO devices – the first of these (the BCM2711) is our usual Raspberry Pi GPIO. We can see more about it, if we run gpioinfo.

A screenshot of a terminal running on a Raspberry Pi 4, showing the gpioinfo utility running. The tool has printed information about all of the Pi’s GPIO pins.
The gpioinfo tool showing us the status of all of the Pi’s GPIO pins

From here we can set the pin status that we want, using gpioset. So, for example, to turn on GPIO pin 16 in the example above – we note it’s on line 16, so we can run gpioset gpiochip0 16=1 to turn it on, and gpioset gpiochip0 16=0 to turn it off. We can also use the counterpart gpioget to read the value of a pin (using the same syntax).

There’s also lots of additional actions that we can take using gpioset – to provide all of the functionality we would have had to set internal pull-up or pull-down resistors, etc. Take a look at the man page for gpioset – or just read the help.

A screenshot of a terminal running on a Raspberry Pi 4, showing the result of running gpioset –help.
The help message from gpioset

One thing to note is that (as the last paragraph of the help for gpioset states) the output state of a pin isn’t guaranteed to persist after the tool exits. In my experimentation with a Raspberry Pi 4, it does seem to (e.g. setting a pin HIGH seems to stick until you set it LOW again); but that’s not necessarily always going to be the case.

GPIO Control from C

So much for doing it from the command-line. What about doing it programatically?

If you want to use Python, then there are probably lots of better options for driving the GPIO pins of a Pi (like GPIO Zero). So, as with last time, I will focus on using C.

Unlike last time however, I won’t go through how to do it without using a library – since it’s a little more complex, and the libgpiod library really does do everything for us.

Before we can start, we’ll need to install the libgpiod-dev package on Raspbian (sudo apt install libgpiod-dev). 

Now we can open a file in our favourite editor, and write some code. The following is a minimum example – and note that I’m using pin 14 for the LED (for convenience as it makes it easy to connect to the header), but that it’s normally used for serial data – so a different pin might be preferable in a real-world situation.

 1// Include gpiod.h for all of the GPIO stuff...
 2// and unistd.h for usleep()
 3
 4#include <gpiod.h>
 5#include <unistd.h>
 6
 7int main(int argc, char **argv)
 8{
 9    const char *chipname = "gpiochip0";
10    struct gpiod_chip *chip;
11    struct gpiod_line *led;
12    int state, counter;
13
14    // Setup the GPIO Chip struct...
15    chip = gpiod_chip_open_by_name(chipname);
16
17    // Setup the GPIO "line" (pin 14)
18    led = gpiod_chip_get_line(chip, 14);
19
20    // Set the LED "line" for output
21    gpiod_line_request_output(led, "example1", 0);
22    state = 0;
23
24    // Now we'll blink the LED for a bit...
25    for (counter=0;counter<100;counter++)
26    {
27        gpiod_line_set_value(led, state);
28        state = !(state);
29        // With a sleep of 100000 microseconds === 0.1 seconds
30        usleep(100000);
31    }
32
33    // Release lines and chip & exit
34    gpiod_line_set_value(led, 0);
35    gpiod_line_release(led);
36    gpiod_chip_close(chip);
37    return 0;
38}

We can build this simply by running gcc test.c -o test -lgpiod.

That last part is important, as we’re using the gpiod library, we need to tell the linker called by gcc to link the library into the resulting binary.

So we’ve pretty much got back to where we were with sysfs now; except that we’ve not really answered the question of how the gpiod command-line tools & the libgpiod library actually interact with the kernel.

So what actually is a character device?

Well /dev/gpiochip0 is a special file, in the best all things in Linux are files (but some files aren’t really files) traditions of Linux. So we are still reading and writing to that file – but unlike with sysfs where those operations were pretty straightforward, this time it’s all a bit more complex.

I should note at this stage that there’s absolutely no reason not just use libgpiod as in the example above – however to understand what that library does we’re going to dig into it a little.

The actual code for libgpiod is now a part of the Linux project – so if you want to see the very latest version you’ll need to go to https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/ and get a copy.1

Either way, we can see that most of the code is about performing quite simple file operations on quite complex data structures… (Here’s the definition of the gpiod_line structure from the older version of the code on GitHub, for example).

A screenshot of the libgpiod code from GitHub – showing the definition of the gpiod_line structure.
The gpiod_line struct – as seen on the project’s GitHub page

If we look through enough of the code we’ll eventually see plain old file open() functions being called; along with the more esoteric ioctl() function (see https://man7.org/linux/man-pages/man2/ioctl.2.html) to manipulate the file.

Conclusions

In conclusion, the short-version of all of this is (as I noted before) that this is all much more complex than the old sysfs system; but by virtue of being given the libgpiod library to use – it actually becomes easier for us to use the GPIO, which is ultimately what it’s all about.

For now, you can still use the old method if you want (until or unless it’s finally removed); and remember that ultimately both methods are really just wrappers around directly interfacing with the GPIO registers. If you want to really see what’s going on, take a look at https://abyz.me.uk/rpi/pigpio/examples.html#Misc_minimal_gpio and the Minimal GPIO Access example which works by directly accessing /dev/mem. That looks like a fun piece of code; so maybe I can go through that as an example another time.


  1. You can also see an older version of the libgpiod code on GitHub https://github.com/brgl/libgpiod – although the code’s been restructured a bit since then. ↩︎