La démarche pour la création d'un système embarqué est généralement la suivante :
L'utilisation de Yocto Project, telle que nous l'avons vue jusqu'ici, permettra de mener à bien la seconde phase du projet, c'est-à-dire l'implémentation sur le kit de développement du module SOM. En effet la plupart des fabricants de modules proposent un layer pour Yocto Project regroupant les recettes nécessaires au fonctionnement sur leur kit de développement.
Nous allons nous intéresser dans cette séquence à l'étape ultérieure : le support de périphériques non intégrés dans le kit de développement (par exemple des convertisseurs analogiques-numériques, des capteurs de température, de lumière, de présence…).
Pour supporter un périphérique non présent dans la configuration du kit de développement du SOM, deux étapes sont nécessaires :
Le noyau (kernel) Linux assure la mise à disposition des ressources matérielles pour les applications. C'est lui qui contient les pilotes (drivers) de périphériques qui communiquent avec les équipements autour du processeur. Mais il contient également beaucoup d'autres éléments : systèmes de fichiers, protocoles réseau, gestion de la mémoire, mécanisme d'ordonnancement…
Le noyau comporte des milliers d'options de configuration, que nous pouvons ajuster par l'intermédiaire d'un menu. Les options que l'on modifie le plus souvent en préparant un système embarqué concernent des drivers qu'il faut intégrer dans le noyau. Malheureusement pour notre expérimentation, l'essentiel des drivers susceptibles de nous intéresser sont déjà inclus dans la configuration du noyau fournie par le layer pour Raspberry Pi.
Nous allons donc modifier des options qui ne sont pas directement intéressantes pour nous, mais qui ont des effets visibles depuis la ligne de commande :
uname -a
».
Nous allons tout d'abord vérifier que ces options sont bien absentes d'un noyau compilé avec les options par défaut :
My experimental distro 1.0 mybox ttyS0 mybox login: root Password: (linux) root@mybox:~# uname -a Linux mybox 6.6.22-v7l #1 SMP Tue Mar 19 17:41:59 UTC 2024 armv7l GNU/Linux
Pas de mot-clé «PREEMPT
»
dans le résultat de uname
. Vérifions les triggers disponibles pour la
led :
root@mybox:~# ls /sys/class/leds/ ACT PWR default-on mmc0 mmc0:: root@mybox:~# cat /sys/class/leds/ACT/trigger none rc-feedback kbd-scrolllock kbd-numlock kbd-capslock kbd-kanalock kbd-shiftlock kbd-altgrlock kbd-ctrllock kbd-al tlock kbd-shiftllock kbd-shiftrlock kbd-ctrlllock kbd-ctrlrlock timer oneshot heartbeat backlight cpu cpu0 cpu1 cpu2 cpu3 default-on input panic actpwr mmc1 [mmc0] rfkill-any rfkill-none rfkill0 rfkill1 root@mybox:~#
Le trigger sélectionné est encadré par des crochets ;
c'est «mmc0
». Dans la liste des triggers
disponibles, aucune trace de «activity
».
La configuration du kernel ressemble un peu à celle de Busybox
que nous avons vue dans
la
séquence précédente (plus exactement Busybox utilise le mécanisme
de configuration mis au point pour le noyau Linux). Nous appelons
bitbake
avec la même option «-c
menuconfig
» que précédemment. Toutefois le nom de la
recette est particulier : comme nous ne connaissons pas le nom
exact de la recette gérant le kernel, et que cela peut varier en
fonction de la machine cible, de la version de Yocto project, etc. on utilise
un nom générique «virtual/kernel
»
qui sera traduit par bitbake
:
[build-rpi]$ bitbake -c menuconfig virtual/kernel
Une nouvelle fenêtre (ou un nouvel onglet de terminal) s'ouvre. Je vous
encourage à parcourir quelques menus pour vous familiariser avec la
configuration du kernel Linux, notamment le menu «Device
Drivers».
Pour avoir un peu d'explication sur une option, on peut appuyer sur
«?
».
Deux options ont retenu notre attention.
La première est dans le menu «General Setup» (figure IV.3-1)…
… sous-menu «Preemption Model» (figure IV.4-2)…
… sélectionner l'option «Preemptible Kernel (Low-Latency Desktop)» (figure IV.4-3).
La seconde option se trouve en ouvrant le menu «Device Drivers» (figure IV.4-4)…
… en descendant jusqu'au sous-menu «LED Support» (figure IV.4-5)…
… puis le sous-menu «LED Trigger Support» (figure IV.4-6)…
… il faut activer (en pressant «Y
»)
l'option «LED activity Trigger»
(figure IV.4-7).
On quitte ensuite le menu en demandant à sauvegarder la nouvelle configuration.
Comme lors de la configuration de Busybox, nous demandons à
bitbake
de nous préparer un fragment de
configuration regroupant les options qui diffèrent des valeurs par
défaut :
[build-rpi]$ bitbake -c diffconfig virtual/kernel Config fragment has been dumped into: /home/Builds/Lab/build-rpi/tmp/work/raspberrypi4-poky-linux-gnueabi/linux-raspberrypi/6.6.22+git/fragment.cfg NOTE: Tasks Summary: Attempted 476 tasks of which 475 didn't need to be rerun and all succeeded.
Et nous appelons recipetool
pour qu'il crée l'extension de
recette nécessaire en intégrant le fragment dont nous lui
fournissons le chemin.
[build-rpi]$ recipetool appendsrcfile -w ../../layers/meta-my-layer/ virtual/kernel tmp/work/raspberrypi4-poky-linux-gnueabi/linux-raspberrypi/6.6.22+git/fragment.cfg
À l'issue de cette opération, nous voyons qu'un nouveau sous-répertoire
«recipes-kernel
» est apparu dans notre
layer et qu'il contient une extension de recette pour le
noyau :
[build-rpi]$ ls ../../layers/meta-my-layer/ conf COPYING.MIT README recipes-core recipes-custom recipes-example recipes-kernel recipes-support [build-rpi]$ ls ../../layers/meta-my-layer/recipes-kernel/ linux [build-rpi]$ ls ../../layers/meta-my-layer/recipes-kernel/linux/ linux-raspberrypi linux-raspberrypi_%.bbappend [build-rpi]$ ls ../../layers/meta-my-layer/recipes-kernel/linux/linux-raspberrypi/ fragment.cfg [build-rpi]$
Avant de relancer un build de l'image nous forçons l'effacement
de la recette «virtual/kernel
» pour éviter les
warnings de bitbake
:
[build-rpi]$ bitbake -c clean virtual/kernel [...] [build-rpi]$ bitbake my-image [...]
Une fois la compilation terminée, et l'image installée sur le Raspberry Pi, nous allons vérifier le résultat de nos modifications :
My experimental distro 1.0 mybox ttyS0 mybox login: root Password: (linux) root@mybox:~# uname -a Linux mybox 6.6.22-v7l #1 SMP PREEMPT Tue Mar 19 17:41:59 UTC 2024 armv7l GNU/Linux
Notre noyau est bien marqué comme préemptible. Voyons la configuration de la led verte, et les triggers disponibles :
root@mybox:~# cat /sys/class/leds/ACT/trigger none rc-feedback kbd-scrolllock kbd-numlock kbd-capslock kbd-kanalock kbd-shiftlock kbd-altgrlock kbd-ctrllock kbd-altlock kbd-shiftllock kbd-shiftrlock kbd-ctrlllock kbd-ctrlrlock timer oneshot heartbeat backlight cpu cpu0 cpu1 cpu2 cpu3 activity default-on input panic actpwr mmc1 [mmc0] rfkill-any rfkill-none rfkill0 rfkill1
Le trigger «activity
» est apparu. C'est
toujours «mmc0
» qui est sélectionné par défaut,
mais nous pouvons en changer :
root@mybox:~# echo activity > /sys/class/leds/ACT/trigger root@mybox:~# cat /sys/class/leds/ACT/trigger none rc-feedback kbd-scrolllock kbd-numlock kbd-capslock kbd-kanalock kbd-shiftlock kbd-altgrlock kbd-ctrllock kbd-altlock kbd-shiftllock kbd-shiftrlock kbd-ctrlllock kbd-ctrlrlock timer oneshot heartbeat backlight cpu cpu0 cpu1 cpu2 cpu3 [activity] default-on input panic actpwr mmc1 mmc0 rfkill-any rfkill-none rfkill0 rfkill1
La led verte clignote avec un bref flash toutes les secondes, le processeur est très faiblement chargé. Lançons une petite boucle active :
root@mybox:~# while true ; do : ; done
Instantanément, la led clignote plus intensément. Elle revient à son
motif initial dès que l'on presse Contrôle-C. Si on lance
plusieurs instances de cette boucle en parallèle (en faisant suivre le
«done
» d'un «&
»),
la led est quasiment allumée en permanence.
Nous voyons que les modifications de configuration que nous avons apportées au noyau Linux sont bien visibles sur notre cible. Dans un projet réel, il s'agit le plus souvent d'activer des drivers, des protocoles réseau, etc. qui ne le sont pas dans la configuration par défaut.
Lorsque le noyau démarre, il n'a que très peu d'informations sur le système sur lequel il s'exécute. Il doit déterminer de nombreux paramètres concernant le processeur (nombre de cœurs et fréquence, type et adresse du contrôleur d'interruption, nombre et adresses des timers, etc.), la mémoire (quantité disponible, adresses, etc.), les bus de communication avec les périphériques, les périphériques effectivement présents…
Sur un PC par exemple, l'ensemble de ces informations est maintenu par le BIOS que l'on peut partiellement configurer avec le menu Setup à la mise sous tension.
Sur l'architecture ARM — largement majoritaire pour les systèmes embarqués — il n'y a pas de BIOS. C'est également le cas pour les architectures RiscV ou PowerPC par exemple. Le paramétrage doit être explicitement fourni au noyau. C'est le rôle du Device Tree.
Le Device Tree pour un système donné est
renseigné manuellement dans un fichier avec l'extension
«.dts
» (Device Tree Source).
Celui-ci peut hériter de paramètres génériques (concernant par exemple
la famille de System On Chip) se trouvant dans des fichiers
d'extention «.dtsi
»
(Device Tree Source Include).
Les fichiers «.dts
» et
«.dtsi
» sont livrés avec le noyau Linux pour
un grand nombre de plateformes (dont les Raspberry Pi).
On utilise ensuite un outil nommé
«dtc
» (Device Tree Compiler)
pour obtenir une représentation binaire du Device Tree prête à
être interprétée par le noyau. Cette image est inscrite dans
un fichier d'extension
«.dtb
» (Device Tree Blob)
qui sera chargé en mémoire par le
bootloader et transmis ainsi au kernel.
Pour nous donner un but réaliste, nous allons ajouter un petit capteur
de température qui communique avec le processeur par l'intermédiaire
du bus i2c. J'ai choisi un capteur MCP9808
monté sur un module simple à connecter comme on le voit sur
la figure IV.3-8.
Ce petit module peut facilement être installé sur une plaquette d'essai. J'ai utilisé une plaquette que nous employons en session de formation (le «RPI 5» sur la figure IV.3-9 est le numéro de la platine d'essai, il s'agit bien d'un Raspberry Pi 4).
Sur certains bus (PCI, USB…) il est possible d'énumérer et d'identifier les périphériques présents. Sur d'autres bus (SPI, i2c…) ce n'est pas possible et il faut indiquer au noyau quels périphériques sont connectés, ainsi que leurs adresses de communication. On réalise traditionnellement cela dans le Device Tree.
Pendant la phase de prototypage avec un montage volant de ce type, on
peut généralement se dispenser de modifier le Device Tree et
agir sur les entrées bind
dans l'arborescence
/sys/
. Nous allons imaginer que nous sommes à l'étape de
validation d'une carte porteuse et que l'on souhaite ajuster
précisément le Device Tree du module SOM.
Les connexions sont réalisées comme suit :
00011000
en binaire soit 0x18
en
hexadécimal.
Les points importants pour le support dans le noyau sont :
i2c1
».
0x18
».
Documentation/
des sources du kernel. Le
driver capable de piloter ce composant et plusieurs
autres est «jc42
» du sous-système
hardware monitoring (ce capteur peut être utilisé pour
surveiller la température interne d'un PC par exemple). Le nom
à indiquer dans le Device Tree pour identifier le
driver est :
«edec,jc-42.4-temp
».
Nous allons faire un patch sur le noyau Linux en modifiant
l'extrait de Device Tree concerné. Nous avons déjà modifié
un fichier source d'un package (nano
) dans
la section II-3
en utilisant «devtool
»,
nous allons procéder de même.
[build-rpi]$ devtool modify virtual/kernel [...] INFO: Mapping virtual/kernel to linux-raspberrypi [...] [build-rpi]$ cd workspace/sources/linux-raspberrypi/
Nous sommes dans les sources du kernel (qui comptent plus de 80.000 fichiers !).
Les fichiers sources des Device Tree pour l'architecture ARM se trouvent dans
le sous-répertoire «arch/arm/boot/dts/
».
Dans les versions assez récentes du noyau, les fichiers sont organisés par fondeurs.
Celui du Raspberry Pi 4 se trouve dans le sous-répertoire
«broadcom
» et s'appelle bcm2711-rpi-4-b.dts
(«bcm2711
» faisant référence au System On Chip
Broadcom 2711 utilisé dans cette version du Raspberry Pi).
[linux-raspberrypi]$ nano arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts
En examinant le contenu du fichier, on trouve la section :
[...] &i2c1 { pinctrl-names = "default"; pinctrl-0 = <&i2c1_pins>; clock-frequency = <100000>; } [...]
Pour un vrai projet industriel, on crée un nouveau fichier .dts
pour représenter le projet. Ici, j'édite seulement le fichier
original pour ajouter les lignes suivantes :
[linux]$ nano arch/arm/boot/dts/bcm2711-rpi-4-b.dts [...] &i2c1 { pinctrl-names = "default"; pinctrl-0 = <&i2c1_pins>; clock-frequency = <100000>; // Enable the i2c1 bus: status = "okay"; // Add a device at i2c address 18 temp@18 { // manufacturer and driver ident (drivers/hwmon/jc42.c) compatible = "jedec,jc-42.4-temp"; // i2c address: reg = <0x18>; // i2c address size: #address-cells = <0x1>; // Number of registers for child devices: #size-cells = <0x0>; }; }; }; [...]
Notre propos n'est pas ici de détailler le contenu du Device Tree mais de voir comment intégrer nos modifications au build de Yocto Project. J'ai toutefois inséré des commentaires pour comprendre la signification des lignes ajoutées.
Commitons notre modification et validons le patch :
[linux-raspberrypi]$ git commit arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts -m "add i2c device in dts" [linux-raspberrypi]$ cd ../../../ [build-rpi]$ devtool update-recipe linux-raspberrypi -a ../../layers/meta-my-layer/ [...] NOTE: Writing append file /home/Builds/Lab/layers/meta-my-layer/recipes-kernel/linux/linux-raspberrypi_6.6.bbappend NOTE: Copying 0001-add-i2c-device-in-dts.patch to /home/Builds/Lab/layers/meta-my-layer/recipes-kernel/linux/linux-raspberrypi/0001-add-i2c-device-in-dts.patch [...]
Il me reste à regénérer mon image (après avoir effacé le noyau précédemment compilé pour éviter les warnings) :
[build-rpi]$ bitbake -c clean virtual/kernel [build-rpi]$ bitbake my-image
Avant d'installer mon image sur le Raspberry Pi, je vérifie les périphériques Hardware Monitoring déjà présents :
root@mybox:~# ls /sys/class/hwmon/ hwmon0 hwmon1 root@mybox:~# ls /sys/class/hwmon/hwmon0/ device name power subsystem temp1_input uevent root@mybox:~# ls /sys/class/hwmon/hwmon1/ device in0_lcrit_alarm name of_node power subsystem uevent root@mybox:~#
J'installe ma nouvelle version sur la carte SD.
Si tout se passe bien, je dois avoir dès le démarrage une nouvelle
entrée dans le sous-répertoire «hwmon/
»
(Hardware Monitoring) de /sys/class
:
My experimental distro 1.0 mybox ttyS0 mybox login: root Password: (linux) root@mybox:~# ls /sys/class/hwmon/ hwmon0 hwmon1 hwmon2 root@mybox:~# ls /sys/class/hwmon/hwmon0/ device name power subsystem temp1_input uevent root@mybox:~# ls /sys/class/hwmon/hwmon1 device of_node subsystem temp1_crit_alarm temp1_input temp1_max_alarm temp1_min uevent name power temp1_crit temp1_crit_hyst temp1_max temp1_max_hyst temp1_min_alarm update_interval root@mybox:~# ls /sys/class/hwmon/hwmon2/ device in0_lcrit_alarm name of_node power subsystem uevent
Visiblement les périphériques ont été renumérotés et c'est le
device hwmon1
qui a été ajouté. Cette entrée
a un contenu typique des capteurs de températures servant à surveiller des
seuils. Voyons la température actuelle (en millidegrés Celsius) :
root@mybox:~# cat /sys/class/hwmon/hwmon1/temp1_input 22875
22,875 °C, température correcte pour un matin de fin juillet à Paris. Je pose le doigt quelques secondes sur le boîtier du capteur :
root@mybox:~# cat /sys/class/hwmon/hwmon1/temp1_input 29625
Je retire mon doigt et la température descend doucement :
root@mybox:~# cat /sys/class/hwmon/hwmon1/temp1_input 27625 root@mybox:~# cat /sys/class/hwmon/hwmon1/temp1_input 26375 root@mybox:~# cat /sys/class/hwmon/hwmon1/temp1_input 26062 root@mybox:~#
Cette séquence nous aura permis d'intervenir dans deux points critiques de l'image produite par Yocto Project : le noyau Linux et le Device Tree. Aujourd'hui c'est une part importante du travail nécessaire pour supporter une nouvelle carte, celle qui concerne la communication avec le hardware.
Nous avons vu au long de ce cours en ligne de nombreux aspects de l'utilisation de Yocto Project pour produire une image au contenu parfaitement maitrisé.
J'espère que ces documents vous seront utiles. Les lecteurs souhaitant bénéficier d'un support technique pour leur projet, d'une formation spécifique (dans leurs locaux, adaptée à leur matériel personnel, etc.) peuvent me contacter. Ceux qui préfèrent au contraire suivre un cours classique en format inter-entreprise pourront s"inscrire à mes formations "Développeur Linux embarqué avec Yocto Project" ou "Yocto Project avancé" .
Ce document est placé sous licence Creative Commons CC BY-NC. Vous pouvez copier son contenu et le réemployer à votre gré pour une utilisation non-commerciale. Vous devez en outre mentionner sa provenance.
Le nom Yocto Project est une marque déposée par la Linux Foundation. Le présent document n'est en aucune façon approuvé par Yocto Project ou la Linux Foundation.