GPIOs mit Debian nutzen

Was sind GPIOs?

Viele nutzen ein Raspberry Pi, weil es GPIOs (General Purpose Input/Output) besitzt. Diese GPIOs kann man nahezu beliebig als Eingang oder Ausgang nutzen. Man kann etwas schalten oder den Zustand eines angeschlossenen Gerätes abfragen.

GPIO Pin Belegung von tutorials-raspberrypi.de
Quelle: https://tutorials-raspberrypi.de

Die GPIOs werden von einem extra Chip verwaltet. Die Modelle Raspberry Pi 3 und 4 besitzen noch einen weiteren Chip für weitere GPIOs. Auflisten kann man die Chips mit:

ls -lh /dev/gpiochip*

Um GPIOs nutzen zu können, muss eine Verbindung zu dem GPIO-Chip aufgebaut und entsprechende Daten gesendet bzw. empfangen werden.

Auf einem Debian-Grundsystem ist im Gegensatz zu RaspiOS keinerlei Software für Zugriffe zum CPIO-Chip vorhanden. Deshalb muss diese installiert werden.

Bisher nutzte man sysfs, um dann mittels /sys/class/gpio/... GPIOs zu nutzen. Diese Variante wird aber nicht mehr von der Debian-Gemeinschaft gepflegt und sollte bei neuen Projekten nicht mehr angewendet werden.

gpiod installieren

Die neue Variante nennt sich gpiod. Hier ist Software enthalten, welche eine Verbindung zum GPIO-Chip erstellt und entsprechende Daten übermittelt. Installieren kann man das Packet mit:

sudo apt install gpiod

Wer selbst eigene C- und C++-Programme mit Zugriff auf GPIOs erstellen möchte, braucht dazu das Packet libgpiod-dev:

sudo apt install libgpiod-dev

Probieren wir nun mal:

gpioinfo

Und hier folgt das nächste Problem, wir haben keinen Zugriff auf den GPIO-Chip. Der Grund dafür liegt in den Benutzerrechten der Gerätedatei /dev/gpiochip0.

ls -lh /dev/gpiochip*

Probieren wir hingegen

sudo gpioinfo

, erhalten wir Informationen über die Nutzung der GPIOs.

GPIO-Zugriffe für Nutzer freigeben

Ich bin kein Freund davon, Programme ständig als root laufen zu lassen, nur um mal auf GPIOs zuzugreifen. Natürlich könnte man sich Rechte für die Datei /dev/gpiochip0 zuteilen, aber das Problem besteht darin, dass diese Rechte nach einem Neustart wieder verschwunden sind, da der GPIO-Chip neu erkannt wird. Was nun?

Ich habe mich für einen Weg entschieden, der einen vernünftigen Zugriff auch bei mehreren Benutzern regelt. Dieser ähnelt dem des RaspiOS.

Man erstellt eine Gruppe namens gpio:

sudo addgroup gpio

Man fügt sich selbst der Gruppe gpio hinzu:

sudo adduser klaus gpio

Damit diese Änderung wirksam wird, meldet man sich ab und erneut an. Mittels

id

kann diese Änderung geprüft werden.

Nun brauchen wir Lese- und Schreibrechte der Gerätedatei für die Gruppe gpio. Und die sollten dann auch nach einem Neustart gesetzt sein. Dazu erstellen wir uns einen winzigen Dienst:

Wir erstellen mit root-Rechten die Datei /etc/systemd/system/gpiochip.service mit folgendem Inhalt.

[Unit]
Description=gpiochip0 für Gruppe gpio freigeben

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStartPre=/usr/bin/chown root:gpio /dev/gpiochip0
ExecStart=/usr/bin/chmod g+rw /dev/gpiochip0

[Install]
WantedBy=multi-user.target

Nun starten wir diesen Dienst.

sudo systemctl start gpiochip.service

Mittels

ls -lh /dev/gpiochip0

und

gpioinfo gpiochip0

sehen wir nun, ob wir Zugriff auf den GPIO-Chip besitzen. Damit dieser Dienst automatisch bei einem Neustart ausgeführt wird, aktivieren wir ihn.

sudo systemctl enable gpiochip.service

Nun hat jedes Mitglied der Gruppe gpio Zugriff auf den GPIO-Chip.

Vielleicht noch ein Hinweis zu GPIOs: Wird ein GPIO von einer Software genutzt, kann eine weitere Software diesen nicht nutzen, da er "belegt" ist. Erst muss die erste Software den GPIO freigeben oder beendet werden.

Anwendungsbeispiele zu gpiod

Alle folgenden Beispiele funktionieren nur, wenn die GPIOs nicht von einer anderen Anwendung genutzt wird.

Um GPIO17 als Eingang zu nutzen und den Wert abzufragen, nutzt man gpioget:

gpioget gpiochip0 17

Hierbei wird GPIO 17 als Eingang definiert, der Wert ausgelesen und GPIO 17 wieder für andere Anwendungen freigegeben.

Da Raspberry Pis mehrere Gpio-Chips besitzen können, muss der Chip mit angegeben werden. Die Kurzform wäre:

gpioget 0 17

Man kann auch mehrere GPIOs gleichzeitig abfragen:

gpioget gpiochip0 17 22 5 6

Um GPIO 12 als Ausgang zu deklarieren und ihn auf HIGH zu setzen (einzuschalten), kann man gpioset nutzen.

gpioset -m wait gpiochip0 12=1

Da wird GPIO 12 als Ausgang deklariert und auf HIGH gesetzt, bis man die ENTER-Taste drückt. Danach wir GPIO 12 für andere Anwendungen freigegeben.

Bei der Freigabe fallen die Ausgänge in der Regel ab und sind irgendwas zwischen LOW und HIGH, und das wird viele stören. Um vielleicht in einem Shell-Script ein Blinklicht zu basteln, ist gpioset so nicht einsetzbar.

Und hier werden viele sagen:"Da war aber sysfs besser.", was ich verstehen kann. Sysfs gibt es noch, aber wird nicht mehr gepflegt.

Um ein Blinklicht zu basteln, muss man leider in den sauren Apfel beißen und sich in der Programmiersprache seiner Wahl die Anwendung von GPIOs lernen.

Blinklicht mit C++

GPIO mit C oder C++ zu nutzen, braucht man das Packet libgpiod-dev. Zum Kompilieren brauchen wir g++. Wer es noch nicht installiert hat, nutzt:

sudo apt update && sudo apt install g++ libgpiod-dev

Und hier der Quelltext (beispiel.cpp):


#include <iostream>  // std::cout
#include <gpiod.h>   // gpiod
#include <thread>    // ssleep
#include <chrono>    // ssleep

#define OUTPUT 12    // genutzer GPIO

/* Programm für Sekunden anhalten */
inline void ssleep(int value)
  {std::this_thread::sleep_for(std::chrono::seconds(value));}

/* Funktion main */
int main()
  {
  /* Verbindung zum gpiochip0 erstellen */
  struct gpiod_chip * chip = gpiod_chip_open("/dev/gpiochip0");
  if(!chip)
    {
    std::cerr << "Kann den Chip /dev/gpiochip0 nicht nutzen! Fehlen dir vielleicht die Benutzerrechte?" << std::endl;
    return 127;
    }

  /* GPIOs werden mit sogenannten lines angesprochen. Jeder GPIO braucht eine eigene line */
  gpiod_line * line = gpiod_chip_get_line(chip, OUTPUT);
  if(!line)
    {
    std::cerr << "Kann line von gpiochip0 GPIO" << OUTPUT << " nicht erhalten." << std::endl;
    return 127;
    }

  /* GPIO als Ausgang deklarieren */
  int ret = gpiod_line_request_output(line, "Blinklicht", 0);
  if(ret < 0)
    {
    std::cerr << "Kann GPIO " << OUTPUT << " nicht als Ausgang nutzen. Benutzt vielleicht eine andere Anwendung diesen GPIO?" << std::endl;
    return 127;
    }

  /* 20 mal blinken lassen */
  for(short s = 0; s < 20; s++)
    {
    int ret1 = gpiod_line_set_value(line, 1);
    if(ret1 < 0) std::cerr << "Fehler beim Setzen des Ausgangs!" << std::endl;
    std::cout << gpiod_line_get_value(line) << " " lt;< std::flush;
    ssleep(1);
    ret1 = gpiod_line_set_value(line, 0);
    if(ret1 < 0) std::cerr << "Fehler beim Setzen des Ausgangs!" << std::endl;
    std::cout << gpiod_line_get_value(line) << " " << std::flush;
    ssleep(1);
    }

  /* line vom GPIO lösen */
  gpiod_line_release(line);

  /* Verbindung zum Chip schließen */
  gpiod_chip_close(chip);

  std::cout << "\n";

  return 0;
  }

Um den Quellcode zu kompilieren, braucht man die Option -lgpiod:

g++ -Wall -o beispiel beispiel.cpp -lgpiod

Öfters bekomme ich mit, dass Neulinge nicht wissen, wie sie ihr eigenes Script oder Programm starten. Deshalb hier nochmal:

./beispiel

Ich gebe zu, ich habe hier die C-Variante und nicht die C++-Variante von libgpiod-dev angewendet. Der Grund dafür ist, dass ich die C++-Variante nicht kenne. Ich habe versucht, mich da einzulesen, bin aber gnadenlos an meinen Fremdsprachenkenntnisse gescheitert. Das gebe ich zu.