How not to use sysfs for GPIO on a Raspberry Pi (& how you should do it in 2023!)…
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).
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
.
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.
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.
#include <gpiod.h> // All of the GPIO stuff... #include <unistd.h> // usleep() int main(int argc, char **argv) { const char *chipname = "gpiochip0"; struct gpiod_chip *chip; struct gpiod_line *led; int state, counter; // Setup the GPIO Chip struct... chip = gpiod_chip_open_by_name(chipname); // Setup the GPIO "line" (pin 14) led = gpiod_chip_get_line(chip, 14); // Set the LED "line" for output gpiod_line_request_output(led, "example1", 0); state = 0; // Now we'll blink the LED for a bit... for (counter=0;counter<100;counter++) { gpiod_line_set_value(led, state); state = !(state); // With a sleep of 100000 microseconds === 0.1 seconds usleep(100000); } // Release lines and chip & exit gpiod_line_set_value(led, 0); gpiod_line_release(led); gpiod_chip_close(chip); return 0; }
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. You can also see an older version on GitHub https://github.com/brgl/libgpiod – although the code’s been restructured a bit since then.
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).
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.
3 thoughts on “How not to use sysfs for GPIO on a Raspberry Pi (& how you should do it in 2023!)…”
Leave a Reply Cancel reply
This site uses Akismet to reduce spam. Learn how your comment data is processed.
Filed under: Raspberry Pi - @ August 18, 2023 12:18
Tags: Linux, Raspberry Pi, sysfs
Great article – I learned some things!
Old thread, but nicely done! All good things — change… And the ABI V1 ioctl() access for GPIO is deprecated. Thankfully, Linus has created an ABI V2 ioctl() interface for GPIO. Just a different way of doing the same thing — with completely NEW macro definitions. For example instead of GPIO_GET_LINEHANDLE_IOCTL, you now have GPIO_V2_GET_LINE_IOCTL, and so on… Not too bad. Lesson: just take the time to learn to access the Pi hardware via the Linux kernel facilities. You are never dependent on somebody else’s library. Learn it once and you are done — until Linus changes it…
Great work! Just need to add monitoring of an output pin and I’ll switch right away. Lots of functionality was lost when the /sys/class/gpio filesystem was trashed. No migration path. huge disservice to the community. Breaks every program and every howto on the internet.