To control GPIO lines in the past, the sysfs mechanism was used. The intention was to send something to a GPIO pin with a certain number through the "echo" command. Something like this: echo 24 >/sys/class/gpio/export. This will create an entry gpio24 in the /sys/class/gpio directory defining the whole pin with all its posible states.
You can now set the direction of the pin as input or output like so: echo in >
See this web page for some more detailed information on how to use the sysfs system.
However, the sysfs system has some disadvantages:
You had to use numbers to address pins
Multithreading was very difficult
Crashing a GPIO driver was keeping the pin to a certain state
...
Because of this, the libgpiod mechanism is developed. libgpiod stands for library general purpose input output device. So, the 'd' has nothing to do with a daemon or so!
The rest of this page goes deeper into the libgpiod mechanism: how to build it and how to use it.
Apparently it's not advised for the Libre Computers to use the pigpiod and wiringPi libraries. According to them, they're HUGE and massive security breaches!
All should go through the new libgpiod library recently introduced in the Linux kernel as a replacement for the now-deprecated sysfs interface for GPIO handling.
A nice article about how to work with the libgpiod library, both from a command line as well as from a code perspective can be found here.
Note: libgpiod stands for library general purpose input output device, so it's not the 'd' for daemon!!!!
Command line commands can be:
gpiodetect
List all GPIO chips present on the system, their names, labels and number of GPIO lines.
gpioinfo
List all lines of specified GPIO chips, their names, consumers, direction, active state and additional flags.
gpioget
Read values of specified GPIO lines.
gpioset
Set values of specified GPIO lines, and potentially keep the lines exported and wait until timeout, user input or signal.
gpiofind
Find the GPIO chip name and line offset given the line name.
gpiomon
Wait for events on GPIO lines, specifying which events to watch, how many events to process before exiting or if the events should be reported to the console.
To get it up and running on the Le Potato, the following has to be done:
install libgpio-dev to get the gpiod.h header file into the system (among others, of course): sudo apt install libgpiod-dev
libgpiod.[a,so] as well as libgiodcxx.[a,so] are already part of the installed Le Potato image.
Small test app will look like this:
#include <gpiod.h>
int main(int argc, char **argv) {
const char *chipname = "gpiochip0";
struct gpiod_chip *chip;
chip = gpiod_chip_open_by_name(chipname);
return 0;
}
This, of course won't do anything. But you will know if all is in place to build and run the application without issues. If so, you can later on build upon the base. I'll give a real C example in the next chapter.
To compile, run the command gcc <name>.c -o <output>.c -l gpiod
Don't forget the last parameter -l as this will tell the linker to link against the libgpiod library so that it knows where to find the gpiod... calls in the C code. Also, make sure the library is the last parameter in the command line so that the linker can find what is defined in the C file.
libgpiod documentation can be found here.
libgpiod source code can be found here.
So, installing the current libgpiod distro version can be done executing the command
sudo apt install libgpiod libgpiod-dev
Note that this will most probably not the latest version but the version that is officially released for the Raspberry Pi or Libre Computer distro currently used.
Indeed, the current installed version through sudo apt is version 1.6.3 as of this writing (december 2022) while the latest version in the Github repository is 2.0-develop.
Since libgpiod is open source you can get the source files and help developing the feature. To get the source files locally, clone the following GitHub repository:
https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/
The command to clone is:
git clone https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git
Check out the master branch if needed to get the bleeding edge development: git checkout master
Once the source tree is copied locally it's time to compile the tools and libraries. Do this following the steps below:
Move to the libgpiod directory
Run the command ./autogen.sh --enable-tools=yes --prefix=<install path>
The install path must be a fully qualified path, like /home/pi/mystuff/libgpiod/output
When running on a freshly installed Raspberry Pi or Libre Computer SOC, you will most likely run into the following issues:
./autogen.sh: 12: autoreconf: not found
Solution: sudo apt install autoconf
On a
Raspberry Pi:
configure.ac:205: error: Unexpanded AX_ macro found. Please install GNU autoconf-archive.
Solution: sudo apt install autoconf-archive
Libre Computer:
TBC
bindings/cxx/Makefile.am:4: error: Libtool library used but 'LIBTOOL' is undefined
Solution: sudo apt install libtool
Once those items are resolved, the configuration tool should be able to finish and create the necessary Makefile
Run the command make: this will generate the tools as well as the libgpiod library
Run the command make install: this will create the following subdirectories into the directory specified by the parameter <install_path> above and install the following files
bin: all tools files:
gpiodetect
gpioget
gpioinfo
gpiomon
gpionotify
gpioset
include: will contain the gpiod.h header file needed by the C/C++ applications who want to access libgpiod functions.
lib: the library libgpiod containing all the implementations of the source files. Will be needed by the gcc/g++ linker to create an app or lib from the C/C++ application.
From here onwards the libgpiod can be used in both C/C++ as well as in Java applications (directly or indirectly in JNI environments).
To compile libgpiod with the absolute minimum it's sufficient to simply run ./autogen.sh. This will create the necessary libgpiod and further nothing when running the make command after ./autogen.sh has created the Makefile.
Running sudo make install (if you don't specify a prefix to install the library locally, you must run the installation command as sudo) will then install the libgpiod and related header files into the following directories:
Header file: /usr/local/include/gpiod.h
Library files: /usr/local/lib
Since the file /etc/ld.so.conf.d/arm-linux-gnueabihf.conf contains the following content:
# Multiarch support
/usr/local/lib/arm-linux-gnueabihf
/lib/arm-linux-gnueabihf
/usr/lib/arm-linux-gnueabihf
then linking against the libgpiod using the linking option -lgpiod during a compile and linking process will find the libgpiod.so files it needs to satisfy the linking process and bring it to a successful executable
However, there's much more that can be done. But first, it's interesting to know how the ./autogen.sh file actually works. When looking to the content of the bash script then we can see the autoreconf command is called. autoreconf is part of the Autotool package which also contains other tools like autoconf, automake etc.
Difference between autoconf and autoreconf:
autoconf: used to create the initial configure script
autoreconf: used to update and regenerate the configure script when needed
autoreconf is taking configure.ac and Makefile.am as input files so that it knows exactly what to fetch from the command line and how to deal with that.
If we look to the content of configure.ac then we see options like this:
AC_ARG_ENABLE([tools],
[AS_HELP_STRING([--enable-tools],[enable libgpiod command-line tools [default=no]])],
[if test "x$enableval" = xyes; then with_tools=true; fi],
[with_tools=false])
AM_CONDITIONAL([WITH_TOOLS], [test "x$with_tools" = xtrue])
As you can see, this section checks if the tools have to be compiled or not.
AC_ARG_ENABLE() has 4 sections (see here):
option name (here: tools)
help string (here: AS_HELP_STRING which is again a predefined macro that takes care of nicely aligned text and so on, see again the above web page)
action if present: will set a variable (here: with_tools) to the value given to the option (here: --enable-tools), be it true or false
action if not present: will set a variable to a value that represents the absence of the tool (here: false)
Therefor, it checks if the option --enable-tools is given when invoking the Makefile. If not, then it defaults to no. iIf it is, then the test inside the script checks if "x$enableval" = xyes. the 'x' is used as a "control sign" so that we can't make mistakes in our comparison.
At the end of the block there's a check for the condition of with_tools through the macro AM_CONDITIONAL(). This will define a flag WITH_TOOLS if and only if the test following the flag is positive. Else, the WITH_TOOLS will not be defined and when other tools check the existence of WITH_TOOLS they will skip the part that otherwise would have been executed.
This is the basic principle how such configuration works. Defining flags if an option is given positively, not defining the flag if the option is not given at all or given negatively.
Looking into the rest of the configuration file we can distinct the following options that can be given to the command ./autogen.sh:
--enable-tools: check if the tools have to be generated for libgpiod (that is: gpiodetect, gpioset, gpioinfo,...)
--enable-gpioset-interactive: check if the GPIO pins have to be manipulated interactively by the user
--enable-tests: check if the tests have to be compiled
--enable-profiling: check if profiling must be done
--enable-examples: check if the examples have to be compiled
--enable-bindings-cxx: check if the CXX bindings have to be compiled
--enable-bindings-python: check if the Python bindings have to be compiled
--enable-bindings-rust: check if the Rust bindings have to be compiled
There's another parameter we can pass to the configuration builder: --prefix. This parameter is checked in a file called configure and is also taken into account by the Linux make mechanism. There's different possibilities like -exec-prefix, --exec_prefix, --exec-prefix,... and also --prefix. See the content of the configure file and search for "prefix" to see all the possible combinations.
This parameter defines where the resulting library or executable (here: libgpiod) has to be installed. As said in the beginning of this section, when no prefix is specified it will install the stuff in the standard Linux directories for which you have to invoke the make as sudo. Else, all will be installed in the location specified by the --prefix switch.
Example of a customised autogen configuration:
./autogen.sh --enable-tools=yes --enable-bindings-cxx=yes --enable-examples=yes --prefix=/home/pi/mystuff/gvcresult
This will:
generate the tools
generate the CXX binding code
will install the generated files into the directory given; for this project (and maybe for all, that I don't know yet) the following subdirectories will be generated:
./include: contains the header file(s)
./lib: contains the lib libgpiod
./bin: contains all the tools generated (since we gave --enable-tools=yes as one of the input parameters to the autogen.sh script)
Note that the more options are given the longer it will take to execute the autogen command. Also the make will take more time as it will have to compile more targets, obviously...
More interesting info about the prefix stuff can be found here.
Note: the tests below are done with libgpiod version 1.6.3. They will not work anymore once you move over to version 2.0 (still in development but soon the switch will be done to the 2.0 version).
To get some practise with the whole libgpiod mechanism, I installed a test. The setup is the following:
Connect a LED with the anode to pin 40 of the header; connect the cathode with a resistor of 270 Ohm in series to ground
Connect a LED with the anode to pin 39 of the header; connect the cathode with a resistor of 270 Ohm in series to ground
Connect a LED with the anode to pin 38 of the header; connect the cathode with a resistor of 270 Ohm in series to ground
To know the different "flavours" for a given SOC, run the command gpiodetect. The output for the Le Potato SBC is as follows:
pi@librecomputer:~/mystuff/cpp_tests $ gpiodetect
gpiochip0 [aobus-banks] (11 lines)
gpiochip1 [periphs-banks] (100 lines)
Then, to know which header pin corresponds to which line in the libgpiod library run the command gpioinfo on the command line. This will output a bunch of lines containing all the exported functionality of the CPU. A small snapshot looks like so:
pi@librecomputer:~/mystuff/cpp_tests $ gpioinfo
gpiochip0 - 11 lines:
line 0: "UART TX" unused input active-high
line 1: "UART RX" unused input active-high
line 2: "Blue LED" "librecomputer:blue" output active-high [used]
line 3: "SDCard Voltage Switch" "VCC_CARD" output active-high [used]
line 4: "7J1 Header Pin5" unused input active-high
line 5: "7J1 Header Pin3" unused input active-high
line 6: "7J1 Header Pin12" unused input active-high
line 7: "IR In" unused input active-high
line 8: "9J3 Switch HDMI CEC/7J1 Header " unused input active-high
line 9: "7J1 Header Pin13" unused input active-high
line 10: "7J1 Header Pin15" unused output active-high
gpiochip1 - 100 lines:
line 0: unnamed unused input active-high
line 1: unnamed unused input active-high
line 2: unnamed unused input active-high
line 3: unnamed unused input active-high
line 4: unnamed unused input active-high
line 5: unnamed unused output active-high
line 6: unnamed unused input active-high
line 7: unnamed unused input active-high
line 8: unnamed unused input active-high
line 9: unnamed unused input active-high
line 10: unnamed unused input active-high
line 11: unnamed unused input active-high
line 12: unnamed unused input active-high
line 13: unnamed unused input active-high
line 14: "Eth Link LED" unused input active-high
line 15: "Eth Activity LED" unused input active-high
line 16: "HDMI HPD" unused input active-high
line 17: "HDMI SDA" unused input active-high
line 18: "HDMI SCL" unused input active-high
line 19: "HDMI_5V_EN" "regulator-hdmi-5v" output active-high [used]
line 20: "9J1 Header Pin2" unused input active-high
There's much more info available for gpiochip1, a total of 100 lines (from 0 to 99).
Then check in which gpiochip the header pin is located. Then use that line number.
For example: header pin 40 is located in gpiochip1. Run gpioinfo and search for 7J1 Header Pin40):
gpioinfo | grep Pin40
Result:
line 83: "7J1 Header Pin40" unused output active-high
As you can see, the corresponding line parameter is 83. So when you want to access that pin through the CLI command set of libgpiod, this number has to be used.
To set the output of header pin 40 to high, issue the following command:
gpioset <chip_bank> 83=1, where <chip_bank> can be one of the following:
1, since header pin 40 is located in gpiochip1
gpiochip1 itself
periphs-banks
So, the final command can be one of:
gpioset 1 83=1
gpioset gpiochip1 83=1
gpioset periphs-banks 83=1
All 3 commands will/should result in activating the LED that is connected to header pin 40.
To set a pin high or low for a given amount of time, use the --mode=time.
Example: gpioset --mode=time -s 1 0 24=1. This command will put line 24 (24=1) of GPIO chip 0 (0) high for 1 second (parameter -s 1) and then release the line again.
The below C-code is using 3 pins of the 40 pin header to create a binary pattern counter from 0 to 7 and 1 pin of the 40 pins header to create an input pin.
The counter will start to run counting from 0 up until 7 and then restart again. This happens forever unless the input pin is pulled low on which the while loop will be exited and the lines will be released. Finally, the gpio chip will be closed too and the app will return 0.
File content:
// This will only work with libgpiod version 1.6.3, not anymore with version 2.0 a
#include <gpiod.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv) {
const char *chipname = "gpiochip1";
struct gpiod_chip *chip;
struct gpiod_line *lineRed;
struct gpiod_line *lineGreen;
struct gpiod_line *lineYellow;
struct gpiod_line *lineButton;
int i;
int val;
// Open GPIO chip
chip = gpiod_chip_open_by_name(chipname);
// Open GPIO lines
lineRed = gpiod_chip_get_line(chip, 83); // Header pin 40
lineGreen = gpiod_chip_get_line(chip, 82); // Header pin 38
lineYellow = gpiod_chip_get_line(chip, 81); // Header pin 36
lineButton = gpiod_chip_get_line(chip, 95); // Header pin 32
// Open LED lines for output
gpiod_line_request_output(lineRed, "lineRed", 0);
gpiod_line_request_output(lineGreen, "lineGreen", 0);
gpiod_line_request_output(lineYellow, "lineYellow", 0);
// Open switch line for input
gpiod_line_request_input(lineButton, "lineButton");
// Blink LEDs in a binary pattern
i = 0;
while (true) {
gpiod_line_set_value(lineRed, (i & 1) != 0);
gpiod_line_set_value(lineGreen, (i & 2) != 0);
gpiod_line_set_value(lineYellow, (i & 4) != 0);
val = gpiod_line_get_value(lineButton);
if (val == 0) {
break;
}
usleep(100000);
i++;
}
// Release lines and chip
gpiod_line_release(lineRed);
gpiod_line_release(lineGreen);
gpiod_line_release(lineYellow);
gpiod_line_release(lineButton);
gpiod_chip_close(chip);
return 0;
}
What does this file do?
Include the gpiod header file to access the prototypes of libgpiod
Create a chip name: gpiochip1.
This is (one of) the name(s) shown when running the command gpiodetect. You can also use the label or the number, but using the chip name is more comprehensive and consistent.
Create a gpio chip structure which is a pointer to the struct gpiod_chip
Create 3 outputs and 1 input, all being a pointer to the struct gpiod_line
Opens the GPIO chip addressed by name (see item 2 above)
Require and request the 3 output lines on the given line numbers.
Use gpioinfo <gpio_chip_name> to find out what line number corresponds to which pin number. Do this for the following pin numbers: 36/38/40.
Require and request the 1 input line on the given line number. Do again the same as with point 6 to find out which line number belongs to which header pin number. Here, the header pin number chosen is 32.
Note that all the lines requested are given a name so they can be spotted when running gpioinfo gpiochip1. They will also be marked as "used" in the overview given:
.
.
line 80: "7J1 Header Pin26" unused input active-high
line 81: "7J1 Header Pin36" "lineYellow" output active-high [used]
line 82: "7J1 Header Pin38" "lineGreen" output active-high [used]
line 83: "7J1 Header Pin40" "lineRed" output active-high [used]
line 84: "7J1 Header Pin37" unused input active-high
.
.
line 94: "7J1 Header Pin18" unused input active-high
line 95: "7J1 Header Pin32" "lineButton" input active-high [used]
line 96: "7J1 Header Pin29" unused input active-high
.
.
Run an infinite while loop and do the following things:
use header pin 40 to control the lowest bit of the 3 bits counter
use header pin 38 to control the second bit of the 3 bits counter
use header pin 36 to control the highest bit of the 3 bits counter
use header pin 32 to check if the button connected to it is pressed or not. If not, continue else break.
To avoid floating input, the input pin is connected to 3V3 with a resistor of 4k7 which will make the level on the input pin default high. If the button is pressed, the pin is pulled low to ground resulting in reading a 0.
Release all requested lines
Close the connection to the chip
Schematic used: see image below.
Some of the commands are totally different with libgpiod version 2.0-develop.
One of them is gpioset which should be used as follows:
gpioset -c 1 83=1
This will take line 83 of GPIO chip 1 (= header pin 40 on the Le Potato SBC) and put the line high. Then it will wait until a Ctrl-C is pressed, otherwise the application hangs.
gpioset -c 1 -t 1000 83=0
This command will start by setting line 83 low and after 1000ms it will start toggling the line until forever + 1 day or until you press the Ctrl-C key combination again.
gpioset -c 1 -t 1000, 1000,1000,0 83=0
This command will start by setting line 83 low for 1 second, then high for 1 second then low again for one second, then high again for one second and then stop the application.
This is different compared to the above two commands where you had to press the Ctrl-C key combination to stop the application.
It's the final 0 that makes the difference here. If a 0 is encountered, the application will stop. See the source code of gpioset.c for more details.