J’ai assisté il y a quelques jours, lors de l’édition 2018 des Kernel Recipes à une présentation par Bartosz Golaszewski de la nouvelle interface des GPIO pour l’espace utilisateur de Linux. J’en avais eu un bref aperçu il y a quelques mois mais je n’avais pas encore pris le temps d’essayer cette API. Cet article est donc une brève présentation et mise en œuvre de ces outils.
Disponible depuis le noyau 4.8, cette API est amenée à remplacer l’accès via /sys/class/gpio
qui est dorénavant considéré comme deprecated.
L’accès se fait via le système de fichiers devtmpfs
monté sur le répertoire /dev
. On y trouve une entrée par contrôleur de GPIO.
Voici un exemple sur une carte Raspberry Pi 3, sur laquelle est installée la distribution Raspbian 2018-06-27
.
$ uname -a Linux raspberrypi 4.14.34-v7+ #1110 SMP Mon Apr 16 15:18:51 BST 2018 armv7l GNU/Linux $ ls -l /dev/gpiochip* crw-rw---- 1 root gpio 254, 0 Sep 16 08:34 /dev/gpiochip0 crw-rw---- 1 root gpio 254, 1 Sep 16 08:34 /dev/gpiochip1 crw-rw---- 1 root gpio 254, 2 Sep 16 08:34 /dev/gpiochip2
$
Trois contrôleurs sont donc présents sur cette carte.
Accès depuis la ligne de commandes
[EDIT 2019] Dans les distributions Raspbian récentes, il suffit d’installer les packages nécessaires ainsi :
$ sudo apt install -y gpiod libgpiod-dev
Dans les versions précédentes, la bibliothèque Libgpiod et ses utilitaires n’étaient pas encore packagés par Raspbian, il était alors nécessaire de faire les opérations suivantes. Je les laisse ici pour mémoire, d’autant que la version fournie par Raspbian 2018.11.13 est Libgpiod 1.0.1, et que la version actuelle est Libgpiod 1.2. Il y a des incompatibilités entre ces deux versions conduisant à des erreurs de compilation lorsqu’on emploie les fonctionnalités traitées dans le second article de cette série. En attendant l’actualisation de Raspbian, je vous conseille de compiler la dernière version stable comme suit.
$ git clone https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod
Cloning into 'libgpiod'…
remote: Counting objects: 3909, done.
remote: Total 3909 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (3909/3909), 569.10 KiB | 0 bytes/s, done.
Resolving deltas: 100% (2691/2691), done.
$ cd libgpiod/
$ ls
autogen.sh bindings configure.ac COPYING Doxyfile include libgpiod.pc.in Makefile.am NEWS README src tests
$ sudo apt install autoconf autoconf-archive libtool
[...]
$ ./autogen.sh --enable-tools=yes --prefix=/usr
autoreconf: Entering directory `.'
autoreconf: configure.ac: not using Gettext
autoreconf: running: aclocal --force -I m4
[...]
config.status: creating config.h
config.status: executing depfiles commands
config.status: executing libtool commands
$ make
make all-recursive
make[1]: Entering directory '/home/pi/libgpiod'
Making all in include
[...]
make[2]: Entering directory '/home/pi/libgpiod'
make[2]: Leaving directory '/home/pi/libgpiod'
make[1]: Leaving directory '/home/pi/libgpiod'
$ sudo make install
Making install in include
make[1]: Entering directory '/home/pi/libgpiod/include'
make[2]: Entering directory '/home/pi/libgpiod/include'
make[2]: Nothing to be done for 'install-exec-am'.
[...]
/usr/bin/install -c -m 644 libgpiod.pc '/usr/lib/pkgconfig'
make[2]: Leaving directory '/home/pi/libgpiod'
make[1]: Leaving directory '/home/pi/libgpiod'
$
Nous pouvons vérifier tout de suite la liste des nouveaux outils installés en tapant gpio
suivi de deux tabulations pour que le shell propose les complétions disponibles.
$ gpio [TAB] [TAB] gpio gpiodetect gpiofind gpioget gpioinfo gpiomon gpioset
$
Hormis la commande gpio
elle-même qui est associée à la bibliothèque WiringPi spécifique au Raspberry Pi, toutes les commande disponibles viennent d’être ajoutées. Nous allons les examiner l’une après l’autre.
gpiodetect
La commande gpiodetect
affiche la liste des contrôleurs GPIO détectés, et pour chacun d’entre-eux le nom du driver associé et le nombre de lignes GPIO gérées.
$ gpiodetect
gpiochip0 [pinctrl-bcm2835] (54 lines)
gpiochip1 [brcmexp-gpio] (8 lines)
gpiochip2 [brcmvirt-gpio] (2 lines)
$
Il s’agit bien sûr de la même liste que celle de /dev/gpio*
. Ces informations étaient déjà accessibles dans l’arborescence /sys/
ainsi :
$ cd /sys/class/gpio/
$ for chip in gpiochip*; do echo $chip $(cat $chip/label) $(cat $chip/ngpio); done
gpiochip0 pinctrl-bcm2835 54
gpiochip100 brcmvirt-gpio 2
gpiochip128 brcmexp-gpio 8
$
gpioinfo
La commande gpioinfo
présente l’état des lignes GPIO du contrôleur demandé.
$ gpioinfo gpiochip2
gpiochip2 - 2 lines:
line 0: unnamed "led0" output active-high [used]
line 1: unnamed unused input active-high
$
Si on ne précise aucun contrôleur sur sa ligne de commande, gpioinfo
affiche les informations pour tous les contrôleurs.
$ gpioinfo
gpiochip0 - 54 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 input 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: unnamed unused input active-high
line 15: unnamed unused input active-high
line 16: unnamed unused input active-high
line 17: unnamed unused input active-high
line 18: unnamed unused input active-high
line 19: unnamed unused input active-high
line 20: unnamed unused input active-high
line 21: unnamed unused input active-high
line 22: unnamed unused input active-high
line 23: unnamed unused input active-high
line 24: unnamed unused input active-high
line 25: unnamed unused input active-high
line 26: unnamed unused input active-high
line 27: unnamed unused input active-high
line 28: unnamed unused input active-high
line 29: unnamed unused input active-high
line 30: unnamed unused input active-high
line 31: unnamed unused input active-high
line 32: unnamed unused input active-high
line 33: unnamed unused input active-high
line 34: unnamed unused input active-high
line 35: unnamed unused input active-high
line 36: unnamed unused input active-high
line 37: unnamed unused input active-high
line 38: unnamed unused input active-high
line 39: unnamed unused input active-high
line 40: unnamed unused input active-high
line 41: unnamed unused input active-high
line 42: unnamed unused input active-high
line 43: unnamed unused input active-high
line 44: unnamed unused input active-high
line 45: unnamed unused input active-high
line 46: unnamed unused input active-high
line 47: unnamed unused output active-high
line 48: unnamed unused input active-high
line 49: unnamed unused input active-high
line 50: unnamed unused input active-high
line 51: unnamed unused input active-high
line 52: unnamed unused input active-high
line 53: unnamed unused input active-high
gpiochip1 - 8 lines:
line 0: unnamed unused output active-high
line 1: unnamed unused output active-high
line 2: unnamed unused output active-high
line 3: unnamed unused output active-high
line 4: unnamed unused input active-high
line 5: unnamed unused output active-high
line 6: unnamed unused output active-high
line 7: unnamed "led1" input active-high [used]
gpiochip2 - 2 lines:
line 0: unnamed "led0" output active-high [used]
line 1: unnamed unused input active-high
$
Les informations affichées sont les suivantes.
- Offset : le numéro de la ligne GPIO pour le contrôleur. Dans la documentation de la plupart des Systems-On-Chip, les GPIO sont identifiées par un couple contrôleur:offset. Par exemple
IO03:05
pour indiquer la sixième ligne (la numérotation commence à zéro) du contrôleur 3. C’est la valeur que l’on obtient ici.
Pour le Raspberry Pi, nous avons eu l’habitude jusqu’ici d’utiliser directement les offsets du premier contrôleurgpiochip0
. Par exemple, la broche 16 du connecteur H1 du Raspberry Pi 3 est accessible par la GPIOgpiochip0:23
. - Name le nom attribué à cette ligne GPIO. Ce nom est configurable dans le device tree. Nous en reparlerons plus loin.
- Consumer : le nom du driver ou du sous-système qui a réservé l’accès à cette ligne GPIO. Nous voyons par exemple «
led1
» et «led0
» pour les lignesgpiochip1:7
etgpiochip2:0
.
Lorsqu’une ligne est exportée dans l’arborescence/sys
, la réservation est faite au nom desysfs
. - Direction :
input
ououtput
en fonction du sens d’utilisation de la ligne GPIO. Au boot les Systems-On-Chip configurent la plupart de leurs lignes en entrées. - Active state :
active-high
ouactive-low
selon le type d’activation de la ligne GPIO. - flags: une série d’attributs peuvent être indiquées entre crochets :
used
: la ligne GPIO est en cours d’utilisation par un driver ou sous-système du noyau.open-drain
(collecteur ouvert) : la sortie de la GPIO est assurée par un transistor simplement connecté à la masse. Écrire un1
sur la ligne GPIO la reliera à la masse. Écrire un0
la laissera flottante. Il faut généralement ajouter une résistance de pull-up. Ceci est principalement utile lorsque plusieurs sorties doivent être reliées ensemble, comme dans le cas d’un bus.open-source
(emetteur ouvert): configuration inverse de la précédente, le collecteur du transistor de sortie est relié à l’alimentation (+3.3V par exemple) et l’émetteur est flottant. On ajoute généralement une résistance de pull-down.
gpioget
La commande gpioget
sert à lire l’état d’une broche GPIO. Nous allons l’utiliser pour lire la valeur d’entrée de la broche numéro 16, qui correspond à la ligne 23 du contrôleur gpiochip0
.
$ gpioget gpiochip0 23
0
$
L’entrée est libre et les broches du connecteur du Raspberry Pi disposent d’une résistance de pull-down. Nous lisons donc une valeur nulle.
Relions à présent cette entrée et la broche numéro 1 (+3.3V).
$ gpioget gpiochip0 23
1
$
Nous pouvons donc lire facilement la valeur d’entrée. Notez qu’il est possible de lire plusieurs entrées en une seule fois. Ce qui est plus simple qu’avec l’interface /sys/class/gpio
.
$ gpioget gpiochip0 23 22 21 20 1 0 0 0
$
gpioset
Tout naturellement, la commande gpioset
sert à fixer une valeur de sortie sur une ligne GPIO. Toutefois son comportement est un peu surprenant a priori.
L’un des principaux reproches que l’on faisait à l’API reposant sur sysfs était que l’accès à une GPIO n’était pas associé à un processus. Une fois qu’une valeur était inscrite dans /sys/class/gpio/gpio23/value
elle restait inscrite sur la broche correspondante (16 en l’occurrence) même si l’application qui gérait les entrées-sorties était finie depuis longtemps.
L’idée avec la nouvelle API est qu’à la fin du processus ayant ouvert une GPIO (fin volontaire sur exit()
ou crash involontaire par signal), la ligne GPIO soit automatiquement libérée. Suivant la configuration du contrôleurs GPIO elle pourra alors reprendre immédiatement un état de haute impédance par mesure de précaution.
La commande gpioset
propose donc plusieurs modes de fonctionnement :
- exit : comportement par défaut, où sitôt la sortie configurée la commande se termine et libère la GPIO. Il s’agit donc d’un mode utilisé pour générer des impulsions sur une ligne de sortie.
- wait : après activation de la GPIO de sortie, la commande attend un retour chariot sur son entrée standard. Ce mode est utile pour un pilotage interactif depuis la ligne de commande.
- time : la commande
gpioset
attend avant de se terminer une durée que l’on précise avec ses options-s
suivie d’une durée en seconde ou-u
suvie d’une durée en microsecondes. Il est possible d’ajouter une option-b
(pour background) afin que l’attente se fasse à l’arrière-plan. - signal : une fois la GPIO configurée la commande attend de recevoir un signal
SIGINT
(que le terminal envoie lors d’une pression sur Contrôle-C) ouSIGTERM
(signal envoyé par défaut par la commandekill
). Comme pour le mode time, l’ajout de l’option-b
permet de laisser la commande en attente à l’arrière-plan. C’est le mode le plus utile pour les scripts.
Je relie la broche 16 du Raspberry Pi (ligne 23 du contrôleur gpiochip0
) et la broche 15 (ligne 22 du même contrôleur). Je vais écrire un signal sur la sortie 22, et profiter du délai de cinq secondes où la commande sera à l’arrière-plan pour consulter l’entrée 23.
$ gpioget gpiochip0 23
0
$ gpioset -m time -s 5 -b gpiochip0 22=1
$ gpioget gpiochip0 23
1
(attente cinq secondes)
$ gpioget gpiochip0 23
0
$
Dans ce second essai, je vais laisser la commande en attente d’un signal. Pour connaître son PID et lui envoyer ultérieurement un signal, je vais consulter la variable $!
du shell qui contient le PID du dernier processus envoyé à l’arrière-plan par le shell. C’est pour cette raison que je vais utiliser le &
du shell et non pas l’option -b
de gpioset
.
$ gpioset -m signal gpiochip0 22=1 &
$ PID=$!
$ gpioget gpiochip0 23
1
$ kill $PID
$ gpioget gpiochip0 23
0
$
J’ai légèrement édité la capture d’écran pour supprimer les messages parasites du shell lorsqu’il envoie un processus à l’arrière-plan ou que ce dernier se termine.
On notera également que gpioset
permet de configurer la sortie en mode active-low avec son option -l
ainsi l’écriture d’un 0
activera la sortie (qui passera à +3.3V) et un 1
la ramènera à la masse.
gpiomon
La commande gpiomon
permet de réaliser un travail qui n’était pas possible directement depuis la ligne de commande avec l’API reposant sur sysfs mais nécessitait d’écrire un programme spécifique (comme je l’avais fait dans l’article « Attente passive sur une GPIO« ).
Il s’agit de monitorer les événements se passant sur une ou plusieurs broches d’entrée, d’afficher ou d’attendre l’arrivée d’un front montant ou descendant. Par défaut, gpiomon
affiche les événements au fur et à mesure qu’ils se produisent (avec un format configurable). Il est possible d’utiliser son option -n
suivi d’un nombre d’occurrences pour demander que la commande se termine une fois le nombre d’événements survenus.
Par défaut, les événements sont des changements d’état de la ligne d’entrée. Il est possible d’utiliser l’option -r
(pour rising) afin de ne considérer que les transitions montantes (0 vers 1) ou l’option -f
(pour falling) pour ne conserver que les transitions descendantes.
Dans l’exemple suivant, je vais invoquer gpioset
pour qu’il active la sortie sur la ligne GPIO 22 (broche 15) pendant cinq secondes puis se termine. La broche 15 est reliée à la broche 16 comme précédemment. J’invoque gpiomon
pour qu’il attende la transition descendante quand la première commande se terminera.
$ gpioset -m time -s 5 -b gpiochip0 22=1
$ gpiomon -f -n 1 gpiochip0 23 (Cinq secondes s'écoulent) event: FALLING EDGE offset: 23 timestamp: [1538116251.893018202]
$
Dans ce second exemple, je laisse la commande gpiomon
afficher les événements observés sur la ligne GPIO 23 tandis que, depuis un autre terminal, je fais basculer la ligne GPIO 22 reliée à la précédente.
[1]$ gpiomon gpiochip0 23
[2]$ for i in $(seq 1 10); do gpioset -m time -s 1 gpiochip0 22=1; done
event: RISING EDGE offset: 23 timestamp: [1538119622.397979178]
event: FALLING EDGE offset: 23 timestamp: [1538119623.399084866]
event: RISING EDGE offset: 23 timestamp: [1538119623.406256964]
event: FALLING EDGE offset: 23 timestamp: [1538119624.407333330]
event: RISING EDGE offset: 23 timestamp: [1538119624.414514282]
event: FALLING EDGE offset: 23 timestamp: [1538119625.415573773]
event: RISING EDGE offset: 23 timestamp: [1538119625.422415612]
event: FALLING EDGE offset: 23 timestamp: [1538119626.423968121]
event: RISING EDGE offset: 23 timestamp: [1538119626.431198291]
event: FALLING EDGE offset: 23 timestamp: [1538119627.432301011]
event: RISING EDGE offset: 23 timestamp: [1538119627.439238110]
event: FALLING EDGE offset: 23 timestamp: [1538119628.440283122]
event: RISING EDGE offset: 23 timestamp: [1538119628.447077045]
event: FALLING EDGE offset: 23 timestamp: [1538119629.448084557]
event: RISING EDGE offset: 23 timestamp: [1538119629.455423946]
event: FALLING EDGE offset: 23 timestamp: [1538119630.456473697]
event: RISING EDGE offset: 23 timestamp: [1538119630.463395900]
event: FALLING EDGE offset: 23 timestamp: [1538119631.464926222]
event: RISING EDGE offset: 23 timestamp: [1538119631.472085872]
event: FALLING EDGE offset: 23 timestamp: [1538119632.473167915]
(Contrôle-C)
$
gpiofind
Dans l’exemple d’utilisation de la commande gpioinfo
, nous avons pu voir que la colonne des noms des lignes GPIO est remplie de unnamed. Ces noms doivent être attribués dans le device tree mais il n’y en a pas dans celui livré avec la distribution Raspbian. Je me suis amusé à remplir les noms des lignes GPIO qui apparaissent sur le connecteur H1. Après avoir recompilé mon device tree et avoir rebooté, j’obtiens la liste suivante.
$ gpioinfo
gpiochip0 - 54 lines:
line 0: unnamed unused input active-high
line 1: unnamed unused input active-high
line 2: "PIN-#03" unused input active-high
line 3: "PIN-#05" unused input active-high
line 4: "PIN-#07" unused input active-high
line 5: "PIN-#29" unused input active-high
line 6: "PIN-#31" unused input active-high
line 7: "PIN-#26" unused input active-high
line 8: "PIN-#24" unused input active-high
line 9: "PIN-#21" unused input active-high
line 10: "PIN-#19" unused input active-high
line 11: "PIN-#23" unused input active-high
line 12: "PIN-#32" unused input active-high
line 13: "PIN-#33" unused input active-high
line 14: "PIN-#08" unused input active-high
line 15: "PIN-#10" unused input active-high
line 16: "PIN-#36" unused input active-high
line 17: "PIN-#11" unused input active-high
line 18: "PIN-#12" unused input active-high
line 19: "PIN-#35" unused input active-high
line 20: "PIN-#38" unused input active-high
line 21: "PIN-#40" unused input active-high
line 22: "PIN-#15" unused input active-high
line 23: "PIN-#16" unused input active-high
line 24: "PIN-#18" unused input active-high
line 25: "PIN-#22" unused input active-high
line 26: "PIN-#37" unused input active-high
line 27: "PIN-#13" unused input active-high
line 28: unnamed unused input active-high
line 29: unnamed unused input active-high
line 30: unnamed unused input active-high
[…]
En réalité un bug dans le kernel que j’utilisais empêchait d’avoir une liste partielle de lignes GPIO nommées. La correction est dans la branche linux-next et devrait apparaître dans le noyau 4.20 (ou 5.0 si la logique de changement de numéro majeur est la même que pour le passage de la branche 3.x à la branche 4.x).
J’ai laissé les broches non apparentes sur le connecteur H1 non nommées. La commande gpiofind
permet de rechercher le numéro de contrôleur et le numéro de ligne GPIO en indiquant son nom.
$ gpiofind PIN-#16
gpiochip0 23
$ gpiofind PIN-#15
gpiochip0 22
$ gpiofind PIN-#00
$
Comme on le voit, la recherche d’un nom inexistant échoue sans afficher d’erreur. Néanmoins le code de retour de la commande est zéro si elle réussit à trouver une ligne GPIO et 1
sinon.
$ gpiofind PIN-#16
gpiochip0 23
$ echo $?
0
$ gpiofind PIN-#00
$ echo $?
1
$
Notez que l’on peut imbriquer cette commande dans l’invocation de gpioget
ou gpioset
, en prenant la précaution de vérifier sa valeur de retour.
$ LINE=$(gpiofind "PIN-#16")
$ if [ $? -eq 0 ]; then VALUE=$(gpioget $LINE); else VALUE=-1; fi
$ echo $VALUE
0
$
La même opération avec un nom inexistant donne :
$ LINE=$(gpiofind "PIN-#00")
$ if [ $? -eq 0 ]; then VALUE=$(gpioget $LINE); else VALUE=-1; fi>
$ echo $VALUE
-1
$
Conclusion
Nous voyons dans ce premier article que ces nouvelles commandes permettent de piloter efficacement et aisément les GPIO depuis le shell. Dans le prochain article je présenterai l’accès depuis un programme C.
OK, j’ai trouvé,
il faut ajouter la librairie à la commande de compilation
ex : gcc read-gpio.c -lgpiod
Il serait bien que vous l’ajoutiez au Tuto
Merci
Thierry
NB : excellent travail.
Bonjour,
Merci de cette information.
À noter, il est plus simple d’utiliser la commande
make
, le fichierMakefile
du projet contenant l’option-lgpiod
.Je vais revoir ces articles, car il me semble qu’il y a eu des modifications dans la
libgpiod
récemment.Merci pour ce tuto très pratique et accessible.
Comment peut-on faire l’equivalent wui existe avec wiringPi
gpio mode 0 down
gpio mode 0 up
Bonjour,
À ma connaissance il n’y a pas d’équivalent avec Libgpiod à ces commandes spécifiques de Wiring Pi.
Elles servent à configurer les résistances de pull-up ou pull-down du Raspberry Pi, mais ne sont pas portables sur d’autres contrôleurs de GPIO.
Comme il s’agit de paramètres qui dépendent du matériel présent, je suppose que la bonne approche est d’établir cette configuration dans le Device Tree.
Bonjour,
Est il possible de mettre l’extrait du device tree permettant de nommer les pins à notre grès ?
En tout cas , merci pour le tuto, c’est vraiment bien fait. (comme toujours en fait ^^)
Salutations.
Bonjour Christophe,
D’après les captures d’écran, il me semble que les commandes sont passées par un utilisateur standard ($) et non root (#). Cependant, après avoir construit un système minimal avec Buildroot et installé la librairie, il est impossible de l’utiliser sans être root:
$ gpiodetect
gpiodetect: unable to access GPIO chips: Permission denied
ce qui semble assez logique vu que l’on accède au matériel.
Y a-t-il une manip spéciale à effectuer pour pouvoir l’utiliser en tant qu’utilisateur satandard (rpi par exemple)?
Cordialement,
Dear Christophe,
I’m working on a SoM board with kernel 5.4 and I’d like to use libgpiod; on my mobile PC I’ve the environment ready to build all for my board.
My question is: can I build for my system iMX.6-based?
If I write the « source » string to set the environment, and successively I follow you step starting from « ./autogen.sh –enable-tools=yes –prefix=/usr », do you think I obtain my libraries and executables to be used in my board?
If not, may you give me the right steps to do so?
Thanks
Best Regards
Paolo
Hello Paolo
I think that the above procedure should work on an i.MX6-based board, providing that the needed tools and libraries are installed.
But you said that you have an environment string to `source`, are you using a Yocto Project-based system. If so, you could prefer to add the [libgpiod recipe](https://layers.openembedded.org/layerindex/recipe/82174/) in your image.
For a Buildroot-based image, the corresponding option is in « Target Packages »-> »Libraries »-> »Hardware handling »-> »libgpiod » + « install tools ».
On a Debian-based distro, `apt install gpiod libgpiod-dev` would be easier.
Thanks for your answer.
I would ask another: since I don’t want to use recipe, but all modifcations done both in Kernel or filesystem was done without Yocto recipes (i’m not skilled in it, sorry), in the past for GPIO I used « gpio » and « gpio-event-drv » taken from Gumstix and compiled using its makefile and placed as external modules.
Not clear how I should proceed with libgpiod.
A basic explanation made by you will be very appreciated, since I read your writing style , very clear and precise.
Thanks
Best Regards
Paolo