Suite à une question posée par Chriss en commentaire d’un précédent article, j’ai eu envie de revenir rapidement sur un élément essentiel pour la mise au point d’un système embarqué : le signe de vie. Il s’agit simplement de faire effectuer par le système une tâche régulière, avec un effet facilement observable par l’utilisateur, afin de s’assurer du bon fonctionnement global du dispositif. Ce signe de vie peut prendre diverses formes : signal électrique visible à l’oscilloscope sur une broche de test de la carte, trame vide émise régulièrement sur un port réseau, compteur incrémenté périodiquement dans une zone de mémoire partagée, etc. Le signe de vie le plus simple à mettre en oeuvre sur un système embarqué est le clignotement d’une LED.
Premier exemple sur carte Pandaboard
La carte Pandaboard, qui a déjà fait l’objet de plusieurs articles, dispose de trois LEDs. Les deux premières se trouvent à côté du support pour mémoire MicroSD, la troisième est placée à côté du connecteur d’alimentation. Cette dernière n’est pas contrôlable par l’utilisateur, elle indique une surtension d’alimentation. Nous allons donc nous intéresser uniquement aux LEDs 1 et 2.
La première méthode pour accéder à ces LEDs, et en conserver un contrôle complet, va consister à les piloter en programmant les broches GPIO (General Purpose Input Output) du processeur auxquelles les LEDs sont reliées. Nous démarrons la carte en utilisant un kernel Linux 3.0-rc7 compilé (avec ce fichier .config
) sans support particulier pour les LEDs. Nous avons simplement activé
- le support GPIO (dans le menu « Device Drivers »),
- l’interface «
/sys/class/gpio/
» (dans le menu « GPIO Support » activé précédemment), - et le driver pour Texas Instruments TWL 4030 et ultérieurs.
Connectons-nous sur la carte Pandaboard, et examinons le contenu du répertoire /sys/class/gpio/
.
[~]$ telnet 192.168.3.152 Trying 192.168.3.152... Connected to 192.168.3.152. Escape character is '^]'. (none) login: root Password: BusyBox v1.18.4 (2011-06-22 12:27:46 CEST) built-in shell (ash) Enter 'help' for a list of built-in commands. (panda)[root]$ cd /sys/class/gpio/ (panda)[gpio]$ ls export gpio62 gpiochip128 gpiochip32 gpiochip96 gpio1 gpiochip0 gpiochip160 gpiochip64 unexport (panda)[gpio]$
Nous voyons que dans ce répertoire le noyau nous propose l’accès à certaines broches GPIO (1 et 62). Toutefois ce ne sont pas celles qui correspondent aux LEDs. En consultant les spécifications de notre carte (pages 48-49), nous voyons que les LEDs 1 et 2 sont connectées aux GPIO 7 et 8. Demandons au noyau d’exporter (c’est-à-dire de nous rendre accessible) la broche GPIO 8. Nous remarquons alors que le contenu du répertoire /sys/class/gpio/
est modifié.
(panda)[gpio]$ echo 8 > export (panda)[gpio]$ ls export gpio62 gpiochip0 gpiochip160 gpiochip64 unexport gpio1 gpio8 gpiochip128 gpiochip32 gpiochip96 (panda)[gpio]$
L’entrée gpio8
a fait son apparition, il s’agit d’un sous-répertoire contenant des fichiers virtuels, dont les contenus représentent des paramètres internes du noyau. Voyons donc la direction par défaut de la broche GPIO 8.
(panda)[gpio]$ ls gpio8/ active_low edge subsystem value direction power uevent (panda)[gpio]$ cat gpio8/direction in (panda)[gpio]$
Le processeur initialise par défaut ses broches GPIO en entrée, pour des raisons de sécurité. Elles présentent alors un état de haute impédance, et les manipulations externes (mises à la masse ou au +5V, court-circuit, etc.) n’ont pas d’influence. Toutefois, pour piloter la LED 2, il faut basculer la broche en sortie.
(panda)[gpio]$ echo out > gpio8/direction (panda)[gpio]$
Nous pouvons alors allumer et étendre la LED 2 ainsi :
(panda)[gpio]$ echo 1 > gpio8/value (panda)[gpio]$ echo 0 > gpio8/value (panda)[gpio]$
Il devient alors facile de programmer un petit signe de vie en utilisant un script shell simple :
/usr/bin/heartbeat.sh #! /bin/sh echo 7 > /sys/class/gpio/export echo 8 > /sys/class/gpio/export echo out > /sys/class/gpio/gpio7/direction echo out > /sys/class/gpio/gpio8/direction while true do echo 1 > /sys/class/gpio/gpio7/value usleep 150000 echo 0 > /sys/class/gpio/gpio7/value usleep 100000 echo 1 > /sys/class/gpio/gpio8/value usleep 150000 echo 0 > /sys/class/gpio/gpio8/value usleep 100000 usleep 500000 done
Lancé en arrière-plan dans le script de démarrage /etc/init.d/rcS
ainsi
/usr/bin/heartbeat.sh &
ce petit script nous permet de contrôler visuellement que le système est opérationnel, et que l’ordonnancement des processus fonctionne normalement (sans qu’une tâche temps-réel ne consomme tout le CPU disponible par exemple).
Deuxième exemple, sur carte IGEP
Sur la carte IGEP v2, que nous avons également abordée précédement, il existe deux LEDs bicolores. La couleur verte de l’une d’entre elles est programmable par l’I2C. Les trois autres couleurs sont accessibles via les GPIO 26, 27, et 28. À noter : lorsque les couleurs rouge et verte de la même LED sont allumées simultanément, la couleur visible est jaune orangée. On peut donc obtenir un signe de vie visuellement plus riche en jouant sur les changements de couleur.
Voici un petit programme C qui assure un scintillement permanent des LEDs et remplit un rôle similaire au premier script ci-dessus.
sdv-igepv2.c #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(void) { int i; int compteur = 0; int fd[3]; char buffer[80]; fd[0] = open("/sys/class/gpio/gpio26/value", O_WRONLY); fd[1] = open("/sys/class/gpio/gpio27/value", O_WRONLY); fd[2] = open("/sys/class/gpio/gpio28/value", O_WRONLY); if ((fd[0] < 0) || (fd[1] < 0) || (fd[2] < 0)) { perror("open"); exit(EXIT_FAILURE); } while(1) { for (i = 0; i < 3; i ++) { sprintf(buffer, "%d", (compteur >> i) & 1); write(fd[i], buffer, 2); } compteur ++; compteur %= 8; usleep(100000); } }
Troisième exemple, avec les triggers du noyau Linux
Le kernel Linux nous offre une interface pour accéder aux LEDs de notre système, et propose même des triggers, c’est-à-dire des comportements prédéfinis pour leur clignotement. Pour que le noyau puisse accéder aux LEDs et nous offrir un moyen de les contrôler, il est nécessaires de sélectionner les options suivantes lors de sa compilation (cliquez sur l’aperçu ci-dessous). Dans le sous-menu « LED Support » du menu « Device Drivers ».
Vous pouvez utiliser directement ce fichier de configuration .config
pour Linux 3.0-rc7 sur Pandaboard.
Dans cette configuration, nous ajoutons le support pour les LEDs pilotées par un GPIO, ainsi que la configuration spécifique à la plateforme. Cette configuration se trouve dans le fichier arch/arm/mach-omap2/board-omap4panda.c
des sources du kernel.
[...] static struct gpio_led gpio_leds[] = { { .name = "pandaboard::status1", .default_trigger = "heartbeat", .gpio = 7, }, { .name = "pandaboard::status2", .default_trigger = "mmc0", .gpio = 8, }, }; static struct gpio_led_platform_data gpio_led_info = { .leds = gpio_leds, .num_leds = ARRAY_SIZE(gpio_leds), }; [...]
On y retrouve bien les numéros de GPIO que nous avons utilisés plus tôt. Après compilation du noyau et reboot de la carte, nous trouvons une nouvelle interface dans /sys
.
(panda)[root]$ cd /sys/class/ (panda)[class]$ ls backlight display graphics i2c-dev leds misc net rtc scsi_host ubi vtconsole bdi firmware hwmon input mdio_bus mmc_host power_supply scsi_device spi_master usb_device block gpio i2c-adapter lcd mem mtd regulator scsi_disk tty vc (panda)[class]$
Allons voir le contenu de cette classe LEDs.
(panda)[class]$ cd leds/ (panda)[leds]$ ls pandaboard::status1 pandaboard::status2 (panda)[leds]$ ls pandaboard::status1/ brightness device max_brightness power subsystem trigger uevent (panda)[leds]$
Nous y trouvons deux sous-répertoires, un pour chaque LED, avec les noms observés plus haut dans le code source du noyau. Le pseudo-fichier trigger
indique l’évenement qui déclenche le changement d’état de la LED.
(panda)[leds]$ cat pandaboard::status1/trigger none nand-disk mmc0 mmc1 timer [heartbeat] gpio default-on (panda)[leds]$
Par défaut il s’agit du trigger heartbeat, qui simule un battement de coeur, dont la fréquence augmente en fonction de la charge du système, mais on peut en choisir d’autres :
none
nous donne l’accès direct à la LED depuis l’entréebrightness
du sous-répertoire,nand-disk
fait scintiller la LED lors de l’accès à la mémoire Flash interne (inutilisée sur mon système),mmc0
etmmc1
signalent les accès aux partitions de la carte MicroSD, rappelant les LEDs d’accès aux disques durs,timer
allume et éteint la LED périodiquement : elle reste allumée pendant la durée en millisecondes indiquée dansdelay_on
puis s’éteint pendantdelay_off
millisecondes,gpio
a un comportement proche de celui denone
, l’entréebrightness
permettant d’allumer ou d’éteindre à volonté la LED.default-on
: allumer la LED dès le boot.
Voici par exemple la configuration pour une LED clignotant à 10 Hz, et dont la durée d’allumage est de 10 ms à chaque cycle.
(panda)[leds]$ echo timer > pandaboard::status2/trigger (panda)[leds]$ echo 10 > pandaboard::status2/delay_on (panda)[leds]$ echo 90 > pandaboard::status2/delay_off (panda)[leds]$
Conclusion
Nous voyons que les cartes embarquées sur lesquelles on fait couramment fonctionner Linux nous permettent aisément de disposer d’un petit signe de vie en employant des LEDs. Bien sûr ces LEDs peuvent servir à d’autres utilisations (indication de l’état d’un serveur, de la disponibilité des ressources, etc.). L’avantage d’un signe de vie programmé dans l’espace utilisateur sur celui intégré dans le kernel, est de nous indiquer réellement si le système est utilisable pour les applications, et pas seulement le bon fonctionnement de l’espace noyau. J’en emploie également sur les systèmes embarquant des applications temps-réel, afin de vérifier facilement si CPU est surchargé ou s’il reste du temps processeur pour les tâches temps-partagé.