Nous avons vu dans le premier article de cette série comment construire une image de Zephyr OS pour Raspberry Pi Pico en utilisant un exemple basique (« blinky
« ).
Nous allons à présent voir comment développer une application personnelle très simple, toujours sur le microcontrôleur pour Raspberry Pico.
Nous devons commencer par nous replacer dans le répertoire de travail créé dans l’article précédent, puis initialiser l’environnement virtuel de Python :
[Zephyr-lab]$ ls
zephyr-project zephyr-sdk-0.16.8
[Zephyr-lab]$ source .venv/bin/activate
(.venv)[Zephyr-lab]$
Contrairement à un développement sur un OS comme Linux, on ne sépare pas avec Zephyr la production du kernel et des librairies de celle du code applicatif. L’outil west
se charge de produire simultanément notre code et de l’insérer dans une image contenant le kernel.
–
Code personnel
Nous allons créer un répertoire pour accueillir nos essais successifs. On peut le placer à différents endroits. J’aime bien installer ce répertoire à côte de celui qui contient le kernel Zephyr et de ceux qui incluent les modules et les outils.
(.venv)[Zephyr-lab]$ cd zephyr-project/
(v.venv)[zephyr-project]$ ls
bootloader modules tools zephyr
(.venv)[zephyr-project]$ mkdir zephyr-apps
(.venv)[zephyr-project]$ ls
bootloader modules zephyr-apps tools zephyr
Pour cette première application personnalisée nous allons nous contenter d’une variation sur le thème de l’exemple blinky
où nous allons ajouter une LED externe et un mini bouton-poussoir. J’appelle cette application gpio-01
.
(.venv)[zephyr-project]$ mkdir zephyr-apps/gpio-01
Une application typique pour Zephyr qui manipule des GPIOs contiendra au minimum trois sous-répertoires :
boards/
regroupera les overlays de device tree des microcontrôleurs que nous supporterons. Un overlay de device tree est une surcharge de la description des périphériques pour ajouter, supprimer ou modifier une configuration d’un équipement. Dans notre casboards/
contiendra un seul fichierrpi_pico.overlay
qui affecte deux fonctionnalités (my-led
etmy-switch
) à deux GPIOs du Raspberry Pi Pico.dts/
contient les fonctionnalités spécifiquement définies que nous utilisons dans le device tree. Dans notre cas,dts/
contiendra un sous-répertoirebindings/
qui regroupera les deux classesmy-led.yaml
etmy-switch.yaml
.src/
contient les sources de l’application. Nous nous contenterons d’un simplemain.c
.
Si l’application contient plusieurs fichiers sources, il est habituel de placer les fichiers headers, contenant les définitions des méthodes et variables publiques dans un sous répertoire include/
à côté de src/
.
En outre deux fichiers devront être ajoutés à la racine de notre répertoire d’application :
CMakeLists.txt
: le fichier de compilation utilisé parcmake
(invoqué parwest
pour produire le code).prj.conf
: un « fragment » de configuration pour le noyau Zephyr. Il s’agit des options de compilation du kernel nécessaires pour notre application.
Enfin, il est courant d’ajouter un fichier README
au format Markdown (.md
) ou ReStructuredText (.rst
).
Voici un schéma de notre arborescence personnelle.
Je crée mon arborescence avec quelques commandes :
(.venv)[zephyr-project]$ mkdir zephyr-apps/gpio-01/boards
(.venv)[zephyr-project]$ mkdir -p zephyr-apps/gpio-01/dts/bindings/
(.venv)[zephyr-project]$ mkdir zephyr-apps/gpio-01/src
(.venv)[zephyr-project]$
Connexions électriques
J’ajoute sur mon Raspberry Pi Pico une LED (protégée par une résistance de quelques dizaines d’Ohms) et un mini bouton-poussoir avec une résistance de pulldown de 10 kOhms environ. (Il est possible que le pulldown soit déjà intégré dans l’entrée GPIO, je n’ai pas vérifié). J’ai choisi arbitrairement deux lignes GPIO, l’une en entrée (GPIO 15) et l’autre en sortie (GPIO 6).
Pour plus de détails sur les connexions électriques du Raspberry Pico, on pourra se reporter à sa documentation officielle (lien ci-dessous).
Fichiers de configuration
Je commence par créer la modification du device tree qui décrit ces deux lignes GPIO :
zephyr-apps/gpio-01/boards/rpi_pico.overlay:
/ {
led1: led1 {
compatible = "my-led";
gpios = <&gpio0 6 GPIO_ACTIVE_HIGH>;
};
switch1: switch1 {
compatible = "my-switch";
gpios = <&gpio0 15 GPIO_ACTIVE_HIGH>;
};
};
Puis je définis les deux catégories my-switch
et my-led
. La seule propriété qui m’intéresse est gpios
, elle est obligatoire et décrit une ligne GPIO en regroupant trois paramètres :
- identifiant du contrôleur de GPIO concerné
- numéro de la ligne GPIO dans le contrôleur
- polarité de la ligne.
zephyr-apps/gpio-01/dts/bindings/my-led.yaml:
description: Custom Output GPIO.
compatible: "my-led"
properties:
gpios:
type: phandle-array
required: true
description: The output GPIO description.
zephyr-apps/gpio-01/dts/bindings/my-switch.yaml:
description: Custom Input GPIO.
compatible: "my-switch"
properties:
gpios:
type: phandle-array
required: true
description: The input GPIO description.
J’ajoute également le fichier de fragment de configuration qui s’assure que l’option GPIO
du kernel Zephyr soit activée :
zephyr-apps/gpio-01/prj.conf:
CONFIG_GPIO=y
Ainsi qu’un fichier de configuration pour cmake
que j’ai écrit en m’inspirant de ceux fournis en exemple avec Zephyr :
zephyr-apps/gpio-01/CMakeLists.txt:
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(gpio-01)
target_sources(app PRIVATE src/main.c)
Code applicatif
Enfin, voici le fichier source principal. Pour plus de précisions sur les fonctions et macros utilisées, reportez-vous au lien ci-dessous.
zephyr-apps/gpio-01/src/main.c:
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#if !DT_NODE_EXISTS(DT_NODELABEL(led1))
# error "led1 component not properly defined."
#endif
#if !DT_NODE_EXISTS(DT_NODELABEL(switch1))
# error "switch1 component not properly defined."
#endif
int main(void)
{
struct gpio_dt_spec led_1 = GPIO_DT_SPEC_GET(DT_NODELABEL(led1), gpios);
struct gpio_dt_spec switch_1 = GPIO_DT_SPEC_GET(DT_NODELABEL(switch1), gpios);
if (!gpio_is_ready_dt(&led_1))
return 0;
if (gpio_pin_configure_dt(&led_1, GPIO_OUTPUT_ACTIVE) < 0)
return 0;
for (;;) {
if (gpio_pin_get_dt(&switch_1)) {
while(gpio_pin_get_dt(&switch_1))
k_msleep(1);
if (gpio_pin_toggle_dt(&led_1) < 0)
return 0;
}
}
return 0;
}
On peut voir dans ce code plusieurs éléments intéressants. Tout d’abord l’initialisation des lignes GPIO :
- la vérification de l’existence des composants
led1
etswitch1
dans le device tree sous peine d’échec de compilation, grâce aux macrosDT_NODE_EXIST()
etDT_NODELABEL()
, - la récupération de la propriété
gpios
de ces composants grâce à la macroGPIO_DT_SPEC_GET()
, - la configuration en sortie de la LED. Les GPIO sont par défaut en entrée, ce n’est donc pas nécessaire pour le bouton.
Ensuite la boucle principale se passe en deux temps :
- Lecture par polling du bouton
switch_1
avec la fonctiongpio_pin_get_dt()
. - Si le bouton n’est pas pressé, on reprend la boucle.
- Si le bouton est pressé, on attend qu’il soit relâché en faisant des petites attentes d’une milliseconde avec la fonction
k_msleep()
. - Une fois qu’il est relâché on bascule l’état de la LED avec
gpio_pin_toggle_dt()
.
Donc la LED changera d’état à chaque pression sur le bouton.
Compilation et installation
Pour compiler notre code nous allons nous placer dans le répertoire zephyr-project
, et appeler :
(.venv)[zephyr-project]$ west build -p always -b rpi_pico zephyr-apps/gpio-01
[...]
(.venv)[zephyr-project]$ ls
bootloader build modules tools zephyr zephyr-apps
Lorsque nous lançons cette commande, west
re-génère à chaque fois (option -p always
) un répertoire build/
qui contient les résultats de la compilation :
(.venv)[zephyr-project]$ ls build/
app CMakeCache.txt compile_commands.json sysbuild_modules.txt zephyr_settings.txt
bootloader CMakeFiles Kconfig zephyr
build.ninja cmake_install.cmake modules zephyr_modules.txt
Dans le sous-répertoire build/zephyr/
nous trouvons le fichier zephyr.uf2
à placer dans la mémoire flash du Raspberry Pico. On le branche sur le PC en conservant le bouton bootsel
appuyé et au bout de cinq secondes, il devient accessible sous forme d’interface USB storage. On peut alors copier le fichier produit :
(.venv)[zephyr-project]$ cp build/zephyr/zephyr.uf2 /media/cpb/RPI-RP2/
Le Pico redémarre aussitôt la copie terminée et nous pouvons voir les changements d’états de la LED.
Sources
- API de programmation avec Zephyr : https://docs.zephyrproject.org/apidoc/latest/index.html
- Documentation du Raspberry Pico : https://datasheets.raspberrypi.com/pico/pico-datasheet.pdf
Conclusion
Nous avons vu comment écrire et compiler une application minimale pour piloter les lignes GPIO d’un micro-contrôleur Raspberry Pi Pico. Nous reviendrons sur la gestion des GPIO dans un prochain article, mais en utilisant un autre microcontrôleur très répandu (ESP32).
–
Dans le prochain chapitre nous verrons un premier aperçu de l’apport principal de Zephyr par rapport à une programmation « bare metal » sans OS : le multitâche et l’ordonnancement.
–
Si vous préférez assister à une session de formation sur « Zephyr OS » en mode présentiel ou distanciel, ou disposer d’une assistance technique personnalisée pour vos développement n’hésitez pas à me contacter.
.