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 :
Poky (Yocto Project Reference Distro) 3.3.1 mybox ttyS0 mybox login: root Password: (linux) root@mybox:~# uname -a Linux mybox 5.10.31-v7l #1 SMP Fri Apr 23 15:16:49 UTC 2021 armv7l armv7l armv7l GNU/Linux
Pas de mot-clé «PREEMPT
» dans le résultat de
uname
. Vérifions les triggers disponibles pour la
led :
root@mybox:~# cat /sys/class/leds/led0/trigger none rc-feedback kbd-scrolllock kbd-numlock kbd-capslock kbd-kanalock kbd-shiftlock kbd-altgrl ock kbd-ctrllock kbd-altlock kbd-shiftllock kbd-shiftrlock kbd-ctrlllock kbd-ctrlrlock timer o neshot heartbeat backlight gpio cpu cpu0 cpu1 cpu2 cpu3 default-on input panic actpwr mmc1 [mm c0] rfkill-any rfkill-none rfkill 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/testing/Build/Trainings/build-rpi/tmp/work/raspberrypi4-poky-linux-gnueabi/linux-raspberrypi/1_5.10.31+gitAUTOINC+a19886b00e_89399e6e7e-r0/fragment.cfg NOTE: Tasks Summary: Attempted 300 tasks of which 299 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 ../meta-my-layer/ virtual/kernel tmp/work/raspberrypi4-poky-linux-gnueabi/linux-raspberrypi/1_4.19.75+gitAUTOINC+642e12d892-r0/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 ../meta-my-layer/ conf COPYING.MIT README recipes-core recipes-custom recipes-example recipes-kernel recipes-support [build-rpi]$ ls ../meta-my-layer/recipes-kernel/ linux [build-rpi]$ ls ../meta-my-layer/recipes-kernel/linux/ linux-raspberrypi linux-raspberrypi_%.bbappend [build-rpi]$ ls ../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 :
Poky (Yocto Project Reference Distro) 3.3.1 mybox ttyS0 mybox login: root Password: (linux) root@mybox:~# uname -a Linux mybox 5.10.31-v7l #1 SMP PREEMPT Fri Apr 23 15:16:49 UTC 2021 armv7l armv7l armv7l GNU/L inux
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/led0/trigger none rc-feedback kbd-scrolllock kbd-numlock kbd-capslock kbd-kanalock kbd-shiftlock kbd-altgrl ock kbd-ctrllock kbd-altlock kbd-shiftllock kbd-shiftrlock kbd-ctrlllock kbd-ctrlrlock timer o neshot heartbeat backlight gpio cpu cpu0 cpu1 cpu2 cpu3 activity default-on input panic actpwr mmc1 [mmc0] rfkill-any rfkill-none rfkill0
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/led0/trigger root@mybox:~# cat /sys/class/leds/led0/trigger none rc-feedback kbd-scrolllock kbd-numlock kbd-capslock kbd-kanalock kbd-shiftlock kbd-altgrlock kbd-ctrl lock kbd-altlock kbd-shiftllock kbd-shiftrlock kbd-ctrlllock kbd-ctrlrlock timer oneshot heartbeat backlig ht gpio cpu cpu0 cpu1 cpu2 cpu3 [activity] default-on input panic mmc1 mmc0 rfkill-any rfkill-none rfkill0
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 et 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, pas celui d'un Raspberry Pi du futur !)
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é. Pour cela nous devons
vérifier quelle version du noyau est compilée par Poky. La commande
«uname -a
» nous a déjà indiqué
5.10.31
.
En cherchant dans le layer
«meta-raspberrypi
», nous trouvons une recette
meta-raspberrypi/recipes-kernel/linux/linux-raspberrypi_5.10.bb
contenant entre autres :
LINUX_VERSION ?= "5.10.31" LINUX_RPI_BRANCH ?= "rpi-5.10.y" [...] SRCREV_machine = "89399e6e7e33d6260a954603ca03857df594ffd3" [...] SRC_URI = " \ git://github.com/raspberrypi/linux.git;name=machine;branch=${LINUX_RPI_BRANCH} [...]
Voici les éléments qui nous intéressent :
SRC_URI
)
est «git
»
SRC_URI
)
git://github.com/raspberrypi/linux.git
)
git
(variable
LINUX_RPI_BRANCH
) :
«rpi-5.10.y
»
SRCREV
) :
89399e6e7e33d6260a954603ca03857df594ffd3
Nous pouvons donc, dans un répertoire de travail, télécharger ces (volumineuses) sources et extraire le numéro de commit qui nous intéresse :
[build-rpi]$ git clone git://github.com/raspberrypi/linux.git -b rpi-5.10.y Clonage dans 'linux'... remote: Enumerating objects: 9149810, done. remote: Counting objects: 100% (19693/19693), done. remote: Compressing objects: 100% (4065/4065), done. remote: Total 9149810 (delta 16203), reused 18698 (delta 15617), pack-reused 9130117 Réception d'objets: 100% (9149810/9149810), 2.69 Gio | 33.54 Mio/s, fait. Résolution des deltas: 100% (7653210/7653210), fait. Mise à jour des fichiers: 100% (71144/71144), fait. [build-rpi]$ [build-rpi]$ cd linux/ [linux]$ git checkout 89399e6e Mise à jour des fichiers: 100% (3122/3122), fait. Note : basculement sur '89399e6e'. [...]
Nous avons les sources du noyau. Les fichiers sources de Device
Tree se trouvent dans le sous-répertoire
arch/arm/boot/dts/
. Celui du Raspberry Pi 4 est nommé
bcm2711-rpi-4-b.dts
(«bcm2711
»
faisant référence au System On Chip Broadcom 2711 utilisé dans
cette version du Raspberry Pi).
En examinant le contenu du fichier, on trouve la section
[...] &i2c1 { pinctrl-names = "default"; pinctrl-0 = <&i2c1_pins>; clock-frequency = <100000>; } [...]
J'édite alors le fichier 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>; status = "okay"; temp@18 { compatible = "jedec,jc-42.4-temp"; reg = <0x18>; #address-cells = <0x1>; #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. Pour produire un patch, le plus
simple est de demander à git
de s'en charger :
[linux]$ git diff > ../001-add-i2c-device-in-dts.patch [linux]$ cd ..
Je peux alors l'outil recipetool
comme nous l'avions fait
dans la
séquence II.3 :
[build-rpi]$ recipetool appendsrcfile ../meta-my-layer/ virtual/kernel 001-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) et à la charger sur le Raspberry Pi :
[build-rpi]$ bitbake -c clean virtual/kernel [build-rpi]$ bitbake my-image
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
:
Poky (Yocto Project Reference Distro) 3.3.1 mybox ttyS0 mybox login: root Password: (linux) root@mybox:~# ls /sys/class/hwmon/ hwmon0 hwmon1 hwmon2
En effet, normalement le Raspberry Pi 4 n'a que deux entrées
hwmon0
et hwmon1
, dont les contenus sont les
suivants :
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 power subsystem uevent
Voyons le contenu de la troisième entrée :
root@mybox:~# ls /sys/class/hwmon/hwmon2/ device power temp1_crit_alarm temp1_max temp1_min name subsystem temp1_crit_hyst temp1_max_alarm temp1_min_alarm of_node temp1_crit temp1_input temp1_max_hyst uevent
Ceci est 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/hwmon2/temp1_input 25375
25,375 °C, il fait trop chaud dans mon bureau ! Même pour un mois de juillet. Je pose le doigt quelques secondes sur le boîtier du capteur :
root@mybox:~# cat /sys/class/hwmon/hwmon2/temp1_input 30500
Je retire mon doigt et la température descend doucement :
root@mybox:~# cat /sys/class/hwmon/hwmon2/temp1_input 29250 root@mybox:~# cat /sys/class/hwmon/hwmon2/temp1_input 28812 root@mybox:~# cat /sys/class/hwmon/hwmon2/temp1_input 26187 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 Projectpour 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.) ou qui préfèrent au contraire suivre un cours classique en format inter-entreprise pourront me retrouver chez Logilin.
Ce document est placé sous licence Creative Common 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.