Les GPIO (General Purpose Input Output) sont des broches du microprocesseur permettant de réaliser des opérations d’entrée-sortie électriques programmables. Chaque broche peut être affectée en entrée ou en sortie par programmation et utilisée aisément pour communiquer avec des périphériques externes.
La Pandaboard utilise un processeur Texas OMAP4430, construit autour d’un coeur Cortex A9. La documentation techique de l’OMAP4430 (plus de 5500 pages…) précise que le processeur dispose de six modules GPIO, chacun offrant 32 lignes d’entrées-sorties. Sur les 192 ports GPIO théoriquement présents, une bonne partie est déjà employée pour la carte elle-même mais quelques uns sont accessibles à l’utilisateur.
Connecteurs d’extension
Nous trouvons sur la Pandaboard deux connecteurs utlisables pour des expérimentations et extensions futures. Numérotés J3 et J6, ils sont également repérés par les libellés « Expansion Connector A » et « Expansion Connector B » comme on peut le voir sur la photo ci-contre.
Le détail des broches disponibles est fourni dans le manuel de référence de la Pandaboard (pages 43 et 44), certaines d’entre elles ayant une signification pour le système.
Par exemple, dans le tableau ci-dessous nous pouvons remarquer que la broche numéro 10 est associée à deux fonctionnalités : un signal Chip Select pour une communication avec une autre carte ou une entrée sortie GPIO numéro 138.
Certaines broches ont des affectations figées car elles sont reliées à des composants sur la carte, mais d’autres peuvent être utilisées pour réaliser des entrées sorties personnalisées.
Nous remarquons la présence sur la broche numéro 1 d’un signal d’alimentation +1.8 V que nous pourrons utiliser pour l’envoyer sur les entrées de notre choix (dans le prochain article) et de la masse du signal sur les broches 27 et 28.
Attention, les opérations sur ce connecteur sont très risquées car elles se répercutent directement sur les broches du processeur, sans protection. Je décline donc toute responsabilité si une manipualtion décrite ici sonne le glas de votre Pandaboard. Pour vous convaincre encore, sachez que j’ai grillé une carte Pandaboard (enfin, un processeur OMAP4 mais c’est le principal) simplement en confondant la broche 2 (alimentation +5V) et la broche 1 (alimentation +1.8V) pour envoyer un signal sur une entrée GPIO. Donc : prudence et concentration sont de rigueur !
Nous allons donc souder sur le connecteur quelques fils afin d’accéder facilement aux broches 1, 10 et 28.
La broche 1 de chaque connecteur (en haut à gauche lorsqu’on le regarde de face) est repérée par un carré, la 2 se trouve en dessous, la 3 à droite du 1, la 4 en dessous de la 3 et ainsi de suite.
Naturellement, la soudure se fait plutôt sur la face opposée, où les numérotations sont inversées.
Pour cet article, nous n’utiliserons pas l’alimentation (broche 1), mais j’ai profité de l’occasion pour la souder en même temps les deux autres.
Accès aux GPIO depuis l’espace utilisateur avec le shell
Démarrons notre carte en utilisant un système fait maison, comme décrit dans les articles numéro 1, numéro 2, numéro 3, numéro 4 et numéro 5 d’une précédente série. Puis connectons depuis un PC distant en utilisant le protocole SSH.
[~]$ ssh root@192.168.5.152 root@192.168.5.152's password: [Panda]#
Le prompt du shell sur la Pandaboard est préfixé par « [Panda] » pour éviter les confusions avec le système hôte.
Vérifions pour commencer la tension sur la broche 10 avec un simple voltmètre.
La tension est nulle, mais la broche est par défaut affectée en entrée (avec une haute impédance). Nous allons modifier cela.
[Panda]# cd /sys/class/gpio/ [Panda]# ls export gpio62 gpiochip128 gpiochip32 gpiochip96 gpio1 gpiochip0 gpiochip160 gpiochip64 unexport [Panda]#
Par défaut, le port GPIO 138 n’est pas accessible depuis le pseudo système de fichier /sys/
, mais nous pouvons le réclamer ainsi.
[Panda]# echo 138 > export [Panda]# ls export gpio138 gpiochip0 gpiochip160 gpiochip64 unexport gpio1 gpio62 gpiochip128 gpiochip32 gpiochip96 [Panda]#
Le port 138 est maintenant accessible vérifions son sens de fonctionnement.
[Panda]# cd gpio138/ [Panda]# cat direction in [Panda]#
Il est affecté en entrée par sécurité au boot. Basculons-le en sortie.
[Panda]# echo out > direction [Panda]# cat direction out [Panda]# cat value 0 [Panda]#
La valeur inscrite sur le port est 0
, ce qui ne nous surprend pas puisque la tension sur la borne est nulle. Modifions la valeur.
[Panda]# echo 1 > value [Panda]#
Le voltmètre affiche maintenant 1,8 V (la tension de référence pour les GPIO, à ne pas dépasser pour les entrées !).
Écrivons à nouveau sur le port.
[Panda]# echo 0 > value [Panda]#
Le voltmètre affiche à nouveau 0.00. Libérons le port GPIO pour terminer cette expérience.
[Panda]# cd /sys/class/gpio/ [Panda]# echo 138 > unexport [Panda]#
Application des GPIO avec un script shell
Nous savons écrire une valeur sur une broche de sortie depuis la ligne de commande, automatisons cela dans un petit script shell qui va faire osciller la broche 10.
/usr/local/bin/oscillateur-gpio.sh : #! /bin/sh GPIO=138 # Broche 10 du port "Expansion A" cd /sys/class/gpio echo ${GPIO} > export cd gpio${GPIO} echo out > direction while true do echo 1 > value usleep 1000 echo 0 > value usleep 1000 done
Le script réalise de petites pause de 1000 microsecondes, soit une milliseconde après chaque changement d’état de la broche.
Connectons un oscilloscope à la place du voltmètre pour voir évoluer le signal.
Nous apercevons sur l’écran un signal périodique qui alterne entre 0 et 1,8V. Toutefois, en regardant de plus près, les durées que nous avions indiquées dans le script ne sont pas respectées.
Le signal change de niveau toutes les 3,3 millisecondes environ et non pas à chaque milliseconde. Il s’agit de la granularité du usleep
qui pose problème.Nous pouvons améliorer ceci en utilisant un petit programme en C qui réalise le même travail mais s’appuie directement sur la libC en passant outre les latences dûes au shell et aux utilitaires système (issus de la Busybox en l’occurrence).
oscillateur-gpio-user.c : #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> static volatile int exit_loop = 0; void handler_signal(int unused) { exit_loop = 1; } int main(int argc, char * argv[]) { int value = 1; char path[256]; int gpio = 138; FILE * fp; // En argument on peut preciser un GPIO different de 138 if ((argc > 2) || ((argc == 2) && (sscanf(argv[1], "%d", & gpio) != 1))) { fprintf(stderr, "usage: %s [gpio]n", argv[0]); exit(EXIT_FAILURE); } // Exporter le GPIO dans le systeme de fichiers snprintf(path, 256, "/sys/class/gpio/export"); if ((fp = fopen(path, "w")) == NULL) { perror(path); exit(EXIT_FAILURE); } fprintf(fp, "%dn", gpio); fclose(fp); // Intercepter Controle-C pour finir proprement signal(SIGINT, handler_signal); // Basculer le GPIO en sortie snprintf(path, 256, "/sys/class/gpio/gpio%d/direction", gpio); if ((fp = fopen(path, "w")) == NULL) { perror(path); exit(EXIT_FAILURE); } fprintf(fp, "outn"); fclose(fp); // Ecrire alternativement la valeur du GPIO snprintf(path, 256, "/sys/class/gpio/gpio%d/value", gpio); if ((fp = fopen(path, "w")) == NULL) { perror(path); exit(EXIT_FAILURE); } while (! exit_loop) { fprintf(fp, "%dn", value); fflush(fp); value = 1 - value; usleep(1000); } // De-exporter le GPIO du systeme de fichiers if ((fp = fopen("/sys/class/gpio/unexport", "w")) == NULL) { perror("/sys/class/gpio/unexport"); exit(EXIT_FAILURE); } fprintf(fp, "%dn", gpio); fclose(fp); return EXIT_SUCCESS; }
Après compilation (en utilisant un cross-compiler comme indiqué dans cet article), nous transférons le code sur la cible.
$ ~/cross-panda/usr/bin/arm-linux-gcc -Wall -o oscillateur-gpio-user oscillateur-gpio-user.c $ scp oscillateur-gpio-user root@192.168.5.152:/root/ root@192.168.5.152's password: oscillateur-gpio-user 100% 6270 6.1KB/s 00:00 $
Sur la cible nous lançons le programme (et nous pouvons l’arrêter avec Contrôle-C).
[Panda]# /root/oscillateur-gpio-user (Contrôle-C) [Panda]#
Le signal est plus précis, nous pourrions calibrer la durée des sommeils pour avoir exactement une milliseconde, ou utiliser un timer logiciel comme ceux founis par setitimer()
.
Toutefois, sur l’écran de l’oscilloscope, le signal n’est pas très stable, ses fronts montants et descendants tremblent en permanence, signe classique d’un comportement lié à l’ordonnancement en temps partagé.
Pour améliorer la précision nous pouvons lancer le programme en ordonnancement temps réel souple avec chrt
.
[Panda]# chrt -f99 /root/oscillateur-gpio-user (Contrôle-C) [Panda]#
Le signal est beaucoup plus stable, mais il y a encore des petits « sauts » de temps à autres. Avec un oscilloscope à mémoire, nous pourrions voir apparaître parfois des fronts décalés d’une durée assez significative.
Conclusion
Afin d’améliorer la précision d’un signal, il est nécessaire d’adopter une approche temps réel. Ceci peut se réaliser de différentes manières. Dans le prochain article nous verrons comment gérer les GPIO depuis l’espace kernel en écrivant un module pour le noyau Linux standard, puis nous nous rapprocherons du temps réel strict en pilotant les GPIO depuis un module kernel de Xenomai, avec l’API RTDM.