Pilotage de GPIO avec l’API Libgpiod (partie 2)

Publié par cpb
Oct 22 2018

Dans l’article précédent de cette série, nous avons examiné les commandes disponibles au niveau du shell pour piloter des lignes GPIO avec la nouvelle API proposée par le noyau Linux.

Nous allons à présent nous intéresser à l’accès depuis un programme C/C++ en utilisant cette API.

Avec la nouvelle API de Linux, l’accès aux lignes GPIO depuis un programme peut se faire de deux façons différentes :

  • En accédant directement au fichier de /dev représentant le contrôleur de GPIO par l’intermédiaire d’appels sytème ioctl(). Ceci est possible sur tous systèmes Linux depuis le noyau 4.8.
  • En utilisant la bibliothèque libgpiod (que nous avons installée au début de l’article précédent) et qui nous offre des fonctionnalités de plus haut niveau pour accéder aux lignes GPIO.

Nous allons étudier ces deux méthodes.

ACCÈS PAR IOCTL() VERS LE CONTRÔLEUR DE GPIO

Les fichiers sources des exemples décrits dans ce paragraphe se trouvent dans le dépôt Framagit suivant : https://framagit.org/cpb/ioctl-access-to-gpio.

Lecture d’informations concernant les lignes GPIO disponibles

Quelques structures et constantes symboliques sont décrites dans le fichier <linux/gpio.h>. Les premières nous donnent des informations sur les lignes GPIO.

struct gpiochip_info {
    char name[32];
    char label[32];
    __u32 lines;
};
define GPIOLINE_FLAG_KERNEL (1UL << 0)
define GPIOLINE_FLAG_IS_OUT (1UL << 1)
define GPIOLINE_FLAG_ACTIVE_LOW (1UL << 2)
define GPIOLINE_FLAG_OPEN_DRAIN (1UL << 3)
define GPIOLINE_FLAG_OPEN_SOURCE (1UL << 4)
struct gpioline_info {
__u32 line_offset;
__u32 flags;
char name[32];
char consumer[32];
};

Pour obtenir les éléments concernant un contrôleur GPIO, on exécute les opérations suivantes (pour des raisons de lisibilité, j’ai supprimé les gestions d’erreur, mais elles sont présentes dans les programmes d’exemple de cet article).

int fd;
struct gpiochip_info info;
fd = open("/dev/gpiochip0", O_RDONLY); ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info); printf("name = %s, label = %s, lines = %d\n",
info.name, info.label, info.lines);

On peut ensuite parcourir les numéros de lignes et afficher les informations. On appelle offset d’une ligne GPIO son numéro au sein de son contrôleur.

int offset;
struct gpioline_info info;
for (offset = 0; offset < info.lines; offset ++) { memset(&info, 0, sizeof(struct gpioline_info)); info.line_offset = offset; ioctl(fd, GPIO_GET_LINEINFO_IOCTL, &info); printf(" %d: name = %s, consumer = %s, flags = %s %s %s %s %s\n", info.line_offset, info.name, info.consumer, info.flags & GPIOLINE_FLAG_IS_OUT ? "OUT" : "IN ", info.flags & GPIOLINE_FLAG_ACTIVE_LOW ? "ACTIVE_LOW " : "ACTIVE_HIGH", info.flags & GPIOLINE_FLAG_OPEN_DRAIN ? "OPEN_DRAIN" : "", info.flags & GPIOLINE_FLAG_OPEN_SOURCE ? "OPEN_SOURCE" : "", info.flags & GPIOLINE_FLAG_KERNEL ? "KERNEL" : "");

Le programme ioct-gpio-list.c réalise ce travail. Voici un exemple d’exécution sur Raspberry Pi 3.

$ ./ioctl-gpio-list 
file = /dev/gpiochip0, name = gpiochip0, label = pinctrl-bcm2835, lines = 54
offset = 0, name = , consumer = , flags = IN ACTIVE_HIGH
offset = 1, name = , consumer = , flags = IN ACTIVE_HIGH
offset = 2, name = PIN-#03, consumer = , flags = IN ACTIVE_HIGH
offset = 3, name = PIN-#05, consumer = , flags = IN ACTIVE_HIGH
offset = 4, name = PIN-#07, consumer = , flags = IN ACTIVE_HIGH
offset = 5, name = PIN-#29, consumer = , flags = IN ACTIVE_HIGH
offset = 6, name = PIN-#31, consumer = , flags = IN ACTIVE_HIGH
offset = 7, name = PIN-#26, consumer = , flags = IN ACTIVE_HIGH
offset = 8, name = PIN-#24, consumer = , flags = IN ACTIVE_HIGH
offset = 9, name = PIN-#21, consumer = , flags = IN ACTIVE_HIGH
offset = 10, name = PIN-#19, consumer = , flags = IN ACTIVE_HIGH
offset = 11, name = PIN-#23, consumer = , flags = IN ACTIVE_HIGH
offset = 12, name = PIN-#32, consumer = , flags = IN ACTIVE_HIGH
offset = 13, name = PIN-#33, consumer = , flags = IN ACTIVE_HIGH
offset = 14, name = PIN-#08, consumer = , flags = IN ACTIVE_HIGH
offset = 15, name = PIN-#10, consumer = , flags = OUT ACTIVE_HIGH
offset = 16, name = PIN-#36, consumer = , flags = IN ACTIVE_HIGH
offset = 17, name = PIN-#11, consumer = , flags = IN ACTIVE_HIGH
offset = 18, name = PIN-#12, consumer = , flags = IN ACTIVE_HIGH
offset = 19, name = PIN-#35, consumer = , flags = IN ACTIVE_HIGH
offset = 20, name = PIN-#38, consumer = , flags = IN ACTIVE_HIGH
offset = 21, name = PIN-#40, consumer = , flags = IN ACTIVE_HIGH
offset = 22, name = PIN-#15, consumer = , flags = OUT ACTIVE_HIGH
offset = 23, name = PIN-#16, consumer = , flags = IN ACTIVE_HIGH
offset = 24, name = PIN-#18, consumer = , flags = IN ACTIVE_HIGH
offset = 25, name = PIN-#22, consumer = , flags = IN ACTIVE_HIGH
offset = 26, name = PIN-#37, consumer = , flags = IN ACTIVE_HIGH
offset = 27, name = PIN-#13, consumer = , flags = IN ACTIVE_HIGH
offset = 28, name = , consumer = , flags = IN ACTIVE_HIGH
offset = 29, name = , consumer = , flags = IN ACTIVE_HIGH
offset = 30, name = , consumer = , flags = IN ACTIVE_HIGH
offset = 31, name = , consumer = , flags = IN ACTIVE_HIGH
offset = 32, name = , consumer = , flags = IN ACTIVE_HIGH
offset = 33, name = , consumer = , flags = IN ACTIVE_HIGH
offset = 34, name = , consumer = , flags = IN ACTIVE_HIGH
offset = 35, name = , consumer = , flags = IN ACTIVE_HIGH
offset = 36, name = , consumer = , flags = IN ACTIVE_HIGH
offset = 37, name = , consumer = , flags = IN ACTIVE_HIGH
offset = 38, name = , consumer = , flags = IN ACTIVE_HIGH
offset = 39, name = , consumer = , flags = IN ACTIVE_HIGH
offset = 40, name = , consumer = , flags = IN ACTIVE_HIGH
offset = 41, name = , consumer = , flags = IN ACTIVE_HIGH
offset = 42, name = , consumer = , flags = IN ACTIVE_HIGH
offset = 43, name = , consumer = , flags = IN ACTIVE_HIGH
offset = 44, name = , consumer = , flags = IN ACTIVE_HIGH
offset = 45, name = , consumer = , flags = IN ACTIVE_HIGH offset = 46, name = , consumer = , flags = IN ACTIVE_HIGH offset = 47, name = , consumer = , flags = OUT ACTIVE_HIGH offset = 48, name = , consumer = , flags = IN ACTIVE_HIGH offset = 49, name = , consumer = , flags = IN ACTIVE_HIGH offset = 50, name = , consumer = , flags = IN ACTIVE_HIGH offset = 51, name = , consumer = , flags = IN ACTIVE_HIGH offset = 52, name = , consumer = , flags = IN ACTIVE_HIGH offset = 53, name = , consumer = , flags = IN ACTIVE_HIGH file = /dev/gpiochip1, name = gpiochip1, label = brcmexp-gpio, lines = 8 offset = 0, name = , consumer = , flags = OUT ACTIVE_HIGH offset = 1, name = , consumer = , flags = OUT ACTIVE_HIGH offset = 2, name = , consumer = , flags = OUT ACTIVE_HIGH offset = 3, name = , consumer = , flags = OUT ACTIVE_HIGH offset = 4, name = , consumer = , flags = IN ACTIVE_HIGH offset = 5, name = , consumer = , flags = OUT ACTIVE_HIGH offset = 6, name = , consumer = , flags = OUT ACTIVE_HIGH offset = 7, name = , consumer = led1, flags = IN ACTIVE_HIGH KERNEL file = /dev/gpiochip2, name = gpiochip2, label = brcmvirt-gpio, lines = 2 offset = 0, name = , consumer = led8, flags = OUT ACTIVE_HIGH KERNEL offset = 1, name = , consumer = , flags = IN ACTIVE_HIGH $

Écriture sur une ligne GPIO de sortie

Pour accéder (en lecture ou en écriture) à des lignes GPIO, on réclame au kernel la mise à disposition d’un handle sur lequel on agira. Ce handle permet d’opérer simultanément sur plusieurs lignes GPIO si on le souhaite. Tout d’abord il faut remplir une structure gpiohandle_request contenant un tableau d’offsets — contenant au maximum GPIOHANDLES_MAX (64) lignes pour un même contrôleur — avec les numéros de lignes nous intéressant. On indique également s’il s’agit de lectures ou d’écritures. Les lignes sur lesquelles on agit simultanément doivent toutes être accédées dans la même direction, entrée ou sortie.

Le programme ioct-toggle-gpio.c fait clignoter une ligne GPIO (indiquée sur sa ligne de commande) avec une fréquence d’une seconde. Le tableau lineoffsets ne contient donc qu’une seule entrée, ce qui est précisé dans le champs lines.

int output_fd;
int output_offset;
struct gpiohandle_request output_request;

[...]
memset(&output_request, 0, sizeof(struct gpiohandle_request));
output_request.lineoffsets[0] = output_offset;
output_request.flags = GPIOHANDLE_REQUEST_OUTPUT;
output_request.lines = 1;

ioctl(output_fd, GPIO_GET_LINEHANDLE_IOCTL, &output_request);

Le résultat de l’ioctl ci-dessus est un nouveau descripteur de fichiers, fourni dans le champs fd de cette structure. C’est le handle que l’on pourra utiliser pour lire ou écrire sur les lignes GPIO réservées. Les valeurs lues ou écrites sont regroupées dans un tableau contenu dans une structure gpiohandle_data.

Voici un exemple d’écriture, notez bien que l’ioctl() est exécuté sur le descripteur obtenu précédemment dans la structure gpiohandle_request, pas sur le descripteur initial ouvert sur gpiochip0.

struct gpiohandle_data output_values;

[...]
output_values.values[0] = 0;
for (;;) {
    output_values.values[0] = 1 - output_values.values[0];
    ioctl(output_request.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &output_values);
    usleep(500000);
}

Pour que ce programme fasse clignoter la ligne GPIO 22 (broche 15) d’un Raspberry Pi, on le lance ainsi :

$ ./ioctl-toggle-gpio /dev/gpiochip0 22

Comme indiqué plus haut, la sortie change d’état toutes les 0,5 secondes grâce à l’attente dans usleep(). Si on supprime cette instruction, le code fait basculer la sortie le plus vite possible, simplement limité par la vitesse du processeur et le temps d’échange entre l’espace utilisateur et le kernel. Sur un Raspberry Pi 3, la fréquence la plus élevée obtenue ainsi est 580 kHZ environ, soit 1,72 microsecondes pour un cycle complet. On peut ainsi considérer que l’écriture sur une ligne GPIO depuis l’espace utilisateur prend 0,86 microseconde.

Oscillation d’une GPIO à la fréquence maximale depuis l’espace utilisateur

Anecdote amusante : lorsque j’ai fait cette mesure une première fois, j’ai obtenu une période de 3.5 microsecondes pour le cycle complet soit le double de la valeur mesurée ici. En fait, mon Raspberry Pi était alimenté par un adaptateur USB trop faible, et il passait en mode « économie d’énergie » en limitant sa fréquence processeur à 600 MHz. Lorsque je l’ai alimenté avec une véritable alimentation de labo, il a pu utiliser sa fréquence processeur maximale de 1,2 GHz.

Lecture sur une ligne GPIO d’entrée

Voyons à présent un exemple de lecture. Le programme ioct-invert-gpio.c affiche sur une ligne GPIO de sortie l’état inverse de celui d’une ligne d’entrée. Le code est en substance le suivant.

int input_fd;
int output_fd;
int input_offset;
int output_offset;
struct gpiohandle_request input_request;
struct gpiohandle_request output_request;
struct gpiohandle_data input_values;
struct gpiohandle_data output_values;

[...]
memset(&input_request, 0, sizeof(struct gpiohandle_request));
input_request.lineoffsets[0] = input_offset;
input_request.flags = GPIOHANDLE_REQUEST_INPUT;
input_request.lines = 1;
ioctl(input_fd, GPIO_GET_LINEHANDLE_IOCTL, &input_request);
memset(&output_request, 0, sizeof(struct gpiohandle_request));
output_request.lineoffsets[0] = output_offset;
output_request.flags = GPIOHANDLE_REQUEST_OUTPUT;
output_request.lines = 1;
ioctl(output_fd, GPIO_GET_LINEHANDLE_IOCTL, &output_request);
for (;;) {
ioctl(input_request.fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &input_values);
output_values.values[0] = (input_values.values[0] == 0) ? 1 : 0;
ioctl(output_request.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &output_values);
}

On peut lancer le programme pour afficher sur la sortie 22 (broche 15) l’inverse de la valeur lue sur l’entrée 23 (broche 16).

$ ./ioctl-invert-gpio /dev/gpiochip0 23 /dev/gpiochip0 22

Attente d’événements sur des lignes GPIO

Il est souvent nécessaire d’attendre l’occurrence d’un événement sur une ligne GPIO (par exemple le passage d’une entrée d’un niveau bas à un niveau haut). Pour cela on utilisera l’appel système poll() qui permet d’attendre passivement (en endormant la tâche appelante) que la transition demandée se produise sur l’une des lignes GPIO surveillées. On peut également ajouter un délai maximal d’attente avant échec de l’appel système.

On remplit une structure gpioevent_request avec l’offset de la ligne GPIO qui nous intéresse et le type d’événement que l’on souhaite attendre : GPIOEVENT_REQUEST_RISING_EDGE (front montant), GPIOEVENT_REQUEST_FALLING_EDGE (front descendant) ou GPIOEVENT_REQUEST_BOTH_EDGES (les deux types de fronts). Après invocation de l’ioctl, la structure contiendra un descripteur dans son champ fd que l’on pourra surveiller avec poll(). Bien entendu il est possible de surveiller simultanément plusieurs descripteurs, éventuellement de différents types (GPIO, socket, pipe, fichiers spéciaux de périphériques, etc.).

Lorsque poll() se termine avec succès, nous pouvons interroger le noyau pour savoir quel type d’événement s’est produit en lisant avec l’appel systeme read() le descripteur précédemment soumis à poll(), afin d’obtenir une structure gpioevent_data. Son champs id contiendra le type d’événement (GPIOEVENT_EVENT_RISING_EDGE ou GPIOEVENT_EVENT_FALLING_EDGE)) et son champs timestamp un horodatage sur 64 bits représentant le nombre de nanosecondes écoulées depuis le 1er janvier 1970.

Le programme ioctl-poll-gpio.c surveille la ligne GPIO d’entrée qui lui est soumise sur sa ligne de commande et affiche l’horodatage et le type d’événement survenu. En outre il affiche toutes les secondes un retour-chariot faisant ainsi « scroller » l’écran et montrant que le programme est actif. On peut le lancer ainsi pour surveiller l’entrée 23 (broche 16) du Raspberry Pi.

$ ./ioctl-poll-gpio /dev/gpiochip0 23 
[1538291737.756504167] RISING
[1538291737.756514479] RISING
[1538291737.756518646] RISING
[1538291740.444199629] RISING
[1538291740.444257805] RISING
[1538291740.444260774] RISING
[1538291740.444285513] FALLING
[1538291740.444306086] RISING
[1538291740.444329419] RISING
[1538291740.444343794] RISING
[1538291740.444358273] RISING
[1538291740.444360513] FALLING
[1538291740.444363638] FALLING
[1538291740.444382492] FALLING
[1538291740.444403377] FALLING
(Contrôle C)

Pendant l’exécution j’ai simplement mis en contact la broche 16 (ligne GPIO 23) et la broche 1 (alimentation +3.3V) puis retiré ce contact trois secondes plus tard. On voit bien que le contact mécanique n’est jamais vraiment franc et que de multiples rebonds se produisent, espacés de quelques dizaines de microsecondes. Il est généralement nécessaire de prendre en considération ce problème avec un mécanisme (électronique ou logiciel) d’anti-rebond.

Nous voyons qu’il est possible de piloter les lignes GPIO directement avec les appels systèmes open(), ioctl(), poll() et read(). Toutefois ces méthodes sont un peu rébarbatives et la manipulation des structures un peu complexe.
C’est pourquoi Bartosz Golaszewski a écrit une bibliothéque nommée libgpiod qui permet d’accéder plus aisément à ces lignes d’entrées-sorties.

PROGRAMMATION AVEC LA BIBLIOTHÈQUE LIBGPIOD

Le fichier d’en-tête nécessaire pour utiliser la bibliothèque Libgpiod est nommé gpiod.h. Il existe deux niveaux d’accès aux fonctionnalités de la bibliothèques :

  • les fonctions context-less permettent un accès rapide et simple aux lignes GPIO,
  • les fonctions bas-niveaux permettent un accès plus fin aux contrôleurs GPIO, au prix d’une manipulation un peu plus compliquée s’appuyant sur plusieurs structures.

Les fichiers sources des exemples décrits dans ce paragraphe se trouvent dans le dépôt Framagit suivant : https://framagit.org/cpb/example-programs-using-libgpiod.

Les fonctions context-less de la bibliothèque libGPIOd

Les fonctions context-less permettent un accès simplifié en une seule opération, où l’on indique le contrôleur concerné, l’offset de la ligne GPIO, le sens d’activité (0 = normal, 1 = inversé) et le nom à inscrire temporairement comme consumer de la ligne.

Pour lire l’état d’une ou plusieurs broches on utilisera

int gpiod_ctxless_get_value(const char *device,
unsigned int offset, bool active_low,
const char *consumer);

int gpiod_ctxless_get_value_multiple(const char *device,
unsigned int *offsets,
int *values,
unsigned int num_lines, bool active_low,
const char *consumer);

Le programme read-gpio.c utilise ce mécanisme pour afficher le contenu d’une broche demandée en argument.

#include <gpiod.h>

[...] int main(int argc, char *argv[]) {
int value;
int offset;

sscanf(argv[2], "%d", &offset);
value = gpiod_ctxless_get_value(argv[1], offset, 0, argv[0]);
[...]
}

On peut utiliser plusieurs notations pour le nom du contrôleur GPIO :

$ ./read-gpio /dev/gpiochip0 23
0
$ ./read-gpio gpiochip0 23
0
$ ./read-gpio 0 23
0

Pour fixer l’état d’une ou plusieurs lignes GPIO on utilisera les fonctions suivantes :

int gpiod_ctxless_set_value(const char *device,
                            unsigned int offset,
                            int value,
                            bool active_low,
                            const char *consumer,
                            gpiod_ctxless_set_value_cb cb,
                            void *data);

int gpiod_ctxless_set_value_multiple(const char *device,
                              const unsigned int *offsets,
                              const int *values,
                              unsigned int num_lines,
                              bool active_low,
                              const char *consumer,
                              gpiod_ctxless_set_value_cb cb,
void *data);

Ces fonctions nécessitent une petite explication. Les premiers champ – device, offset, value, active_low et consumer – sont assez évidents et semblables à ceux des fonctions de lecture. Vient ensuite un champ cb (pour callback) qui est un pointeur sur une fonction du type suivant.

void set_value_callback (void *arg);

Cette routine sera appelée dès que la ligne GPIO aura été configurée comme demandée. Elle recevra en argument le paramètre data de gpiod_ctxless_set_value().

Il faut comprendre que l’un des buts de la nouvelle API des GPIO est de ne jamais laisser une ligne GPIO de sortie active alors que le processus qui l’a configurée est terminé depuis longtemps. Aussi dès que la fonction gpiod_ctxless_set_value() se termine, le descripteur de fichier accédant à la ligne GPIO est libéré et la sortie est relâchée, retournant à un état de haute impédance.

Ainsi, si l’on essaye de faire clignoter une sortie en utilisant

for (;;) {
gpiod_ctxless_set_value(argv[1], offset, value, 0,
argv[0], NULL, NULL);
value = 1 - value;
usleep(500000);
}

La sortie ne passera à l’état désiré qu’une brève fraction de seconde avant de revenir à son état par défaut.

Si l’on souhaite laisser la ligne en sortie pendant une durée déterminée, on peut employer une fonction callback d’attente.

void output_delay(void *arg_delay)
{
unsigned long delay = (unsigned long) arg_delay;

usleep(delay);
}

Et basculer  la sortie ainsi :

for (;;) {
gpiod_ctxless_set_value(argv[1], offset, value, 0, argv[0],
output_delay, (void *)500000);
value = 1 - value;
}

Dans le programme toggle-gpio.c j’ai même pris un raccourci supplémentaire :

  gpiod_ctxless_set_value(argv[1], offset, value, 0, argv[0],
(void (*)(void *))usleep, (void *)500000);

Bien sûr, l’approche avec une callback à prolonger tant que la ligne doit rester active se complique si l’on doit gérer de nombreuses sorties, qui doivent fonctionner indépendamment les unes des autres pendant des durées non connues à l’avance. Pour cela on se tournera plutôt vers l’API bas-niveau de Libgpiod.

Il est également possible de superviser une ou plusieurs lignes GPIO pour détecter les changements d’état en utilisant les fonctions suivantes :

int gpiod_ctxless_event_monitor(
                        const char *device,
                        int event_type,
                        unsigned int offset,
                        bool active_low,
                        const char *consumer,
                        const struct timespec *timeout,
                        gpiod_ctxless_event_poll_cb poll_cb,
                        gpiod_ctxless_event_handle_cb event_cb,
                        void *data);

int gpiod_ctxless_event_monitor_multiple(
                       const char *device,
                       int event_type,
                       const unsigned int *offsets,
                       unsigned int num_lines,
                       bool active_low,
                       const char *consumer,
                       const struct timespec *timeout,
                       gpiod_ctxless_event_poll_cb poll_cb,
                       gpiod_ctxless_event_handle_cb event_cb,
                       void *data);

Ces routines attendent sur une ou plusieurs lignes GPIO (décrites par les champs device, offset, active_low et consumer) les événements contenus dans le paramètre event_type :

  • GPIOD_CTXLESS_EVENT_RISING_EDGE : détection des montées de 0 à 1,
  • GPIOD_CTXLESS_EVENT_FALLING_EDGE : détection des descentes de 1 à 0,
  • GPIOD_CTXLESS_EVENT_BOTH_EDGE : détection de toutes les transitions.

Lorsque la transition attendue est détectée, la routine invoque notre callback event_cb(), du type suivant :

int event_cb(int event, unsigned int offset, const struct timespec *timestamp, void *arg);

En lui passant en argument le type d’événement détecté, l’offset de la ligne, l’horodatage et le pointeur data que l’on avait éventuellement transmis en dernier argument de gpiod_ctxless_event_monitor().

Attention, l’événement détecté est d’un type différent de celui utilisé pour enregistrer le monitoring. Les constantes symboliques sont différentes (_CB_ en plus) et n’ont pas les mêmes valeurs :

  • GPIOD_CTXLESS_EVENT_CB_RISING_EDGE : un front montant a été observé,
  • GPIOD_CTXLESS_EVENT_CB_FALLING_EDGE : un front descendant a été observé,
  • GPIOD_CTXLESS_EVENT_CB_TIMEOUT : il ne s’est rien passé pendant la durée de timeout indiquée à gpiod_ctxless_event_monitor().

La fonction callback doit ensuite renvoyer GPIOD_CTXLESS_EVENT_CB_RET_OK s’il faut continuer le monitoring, GPIOD_CTXLESS_EVENT_CB_RET_STOP s’il faut l’arrêter sur une condition normale et GPIOD_CTXLESS_EVENT_CB_RET_ERR s’il faut l’arrêter en indiquant une erreur.

Pour se mettre en attente passive des changements d’état la fonction gpiod_ctxless_event_monitor() invoque la callback poll_cb() qu’on lui passe en argument ou utilise la routine système ppoll() si cet argument poll_cb est NULL.

Le programme monitor-gpio.c surveille une ligne d’entrée GPIO indiquée en argument.

$ ./monitor-gpio "0" 23
[1540133528.306326156] rising
[1540133528.496137727] falling
[1540133528.728579646] rising
[1540133528.914934773] falling
[1540133529.442333930] rising
[1540133529.820589058] falling
[1540133530.189641150] rising
[1540133530.442220331] falling

CONCLUSION

Nous voyons ici une API élégante et relativement simple pour piloter des lignes GPIO. Le seul bémol à mon avis est la nécessité d’utiliser des callbacks pour maintenir les sorties actives. Ceci peut compliquer sensiblement l’écriture d’une application gérant de multiples lignes et l’on préférera sans doute utiliser la version bas-niveau de Libgpiod, que nous aborderons dans le prochain article.

RÉFÉRENCES

3 Réponses

  1. Thierry dit :

    Bonjour,
    Mon soucis apparaît au moment de la compilation, gcc ne trouve pas les .h du répertoire /libgpiod. Il me renvoie le message :
    « référence indéfinie vers gpiod_ctxless_get_value » (ou autres fonctions selon le .h déclaré)
    J’ai déplacé le répertoire complet à différent emplacement de l’arborescence, sans amélioration. Ma variable LD_LIBRARY_PATH est vide !!!
    Avez vous une idée de mon PB ?
    Merci

  2. Philippe Maréchal dit :

    Bonjour Christophe,

    Tout d’abord je tiens à vous remercier pour vos articles et aussi pour vos livres que j’apprécie beaucoup – des ouvrages sur ces sujets en français sont aussi rares qu’indispensables pour les jeunes passionnés ou pour les vieux programmeurs retraités comme moi.
    J’ai testé plusieurs exemples de code pour les ‘gpio’ de mon Raspberry Pi et je voulais juste vous informer d’une petite modification à faire sur le code de l’exemple ‘ ioctl-toggle-gpio.c’.
    Deux descripteurs de fichiers sont ouverts, ‘output_fd’ d’une part, mais aussi ‘output_request.fd’.
    Si bien que si l’on souhaite répéter l’exécution du programme plusieurs fois de suite, dès la seconde exécution le programme est stoppé avec l’erreur ‘Device or resource busy’ car le descripteur ‘output_request.fd’ n’est pas fermé par close.
    Donc les trois dernières lignes de l’exemple de code devraient être (sauf erreur de ma part) :

    perror(argv[1]);
    close(output_request.fd);
    close(output_fd);
    return EXIT_SUCCESS;

    Pour ma part j’ai préféré l’approche ‘ioctl’ à ‘libgpiod’ même si la bibliothèque est excellente et bienvenue.

    Cordiales salutations et beaucoup de succès,
    Philippe Maréchal

URL de trackback pour cette page