URL: https://linuxfr.org/users/unetanche/journaux/decouverte-de-rust-pour-l-embarque Title: Découverte de rust pour l'embarqué Authors: uneTanche Date: 2026年03月09日T12:06:49+01:00 License: CC By-SA Tags: rust, embarqué et microcontrôleur Score: 38 Rust sur microcontrôleur ======================== J'avais déjà fait quelques essais de rust mais exclusivement pour des outils *cli*. Cependant mon cœur de métier et ce que j'apprécie c'est programmer des microcontrôleurs. Donc, en ce début d'année je me suis motivé, je suis sorti de ma zone de confort et j'ai écrit un petit firmware pour un stm32 en rust. Quelque chose de tout simple, un programme qui fait juste clignoter une led (l'équivalent du traditionnel *hello world* pour l'embarqué). J'ai suivi différents tuto mais la plupart se contentent d'indiquer des *crates* à utiliser et quelles fonctions appeler. Moi je voulais comprendre comment fonctionne la cross-compilation, comprendre comment fonctionne l'édition de liens, comprendre l'utilité de chaque *crate* utilisé... J'ai donc écrit un petit mémo suite à ces essais: [mémo](https://www.ylbn.fr/project/rust_embedded_1) Ce n'est pas parfait, ce n'est pas exhaustif. Je n'ai, par exemple, pas approfondi l'initialisation de la mémoire, ni l'appel de la fonction *main*... Mais on ne sait jamais, ça peut peut-être aider d'autres personnes. Intro ===== J'ai déjà pu expérimenter l'utilisation du langage rust et de son environnement dans le cadre du dev d'outil *cli*, mais pas encore pour de l'embarqué *bare metal*. Cet article va donc me servir de mémo quant à ces essais. Pour ces tests, j'utiliserai une carte de dev ST *nucleo* stm32l031k6. Pour ce qui est de la toolchain rust, j'utilise la version 1.89.0. Vous pouvez retrouver le [répertoire du projet ici](https://codeberg.org/tanche/embedded_rust_test). Premièrement petit tour de la doc: - [le rustbook générique](https://doc.rust-lang.org/stable/book) - [le rustbook embarqué](https://doc.rust-lang.org/stable/embedded-book/index.html) - [le rustbook de cargo](https://doc.rust-lang.org/stable/cargo/index.html) - [ferrous system](https://rust-training.ferrous-systems.com/latest/book/ferrocene-installing) Système de recette ================== Première bonne surprise, cargo (l'utilitaire de construction rust), gère nativement la cross-compilation. Pour cela, on peut soit lui passer l'option `--target` avec le *code triple* de la cible. Soit créer un fichier `.cargo/config.toml` à la racine de notre projet, dans lequel on spécifie lui indique la cible. Donc pour le stm32l032k6, on utilse soit: - `cargo build --target thumbv6m-none-eabi` - le fichier de config cargo avec: ```toml [build] target = thumbv6m-none-eabi ``` Ainsi lorsqu'on appellera `cargo build` il utilisera la cible spécifier dans le fichier de config Description du matériel ----------------------- Contrairement au C, *st* ne fournis pas de *HAL* ou de *LL* rust. En revanche, la communauté rust étant très active, on trouve des crates faisant un travail équivalent. On distingue trois niveaux d'intégration 1. les crates *cortex-m* et *cortex-m-rt* réalisant l'abstraction des périphériques standard des coeur *arm cortex-m* 2. les crates de description matériel (PAC pour Peripherals Abstraction Crate). Ils gèrent le mapping des périphériques du microcontrôleur. Ils sont donc spécifique à la cible. ils nous permettent d'accéder aux registres de chaque périphérique par un ensemble de fonction/structure de données plutôt qu'en spécifiant des adresses mémoire. 3. Les crates d'abstraction matériel (HAL pour Hardware Abstraction Layer). Ils sont basés sur les *PAC* et offrent une interface *haut niveau* et standardisée pour configurer et interagir avec les périphériques. Dans cette première partie, nous n'utiliserons pas de HAL. Les crates ---------- Les crates est le nom donné en rust aux dépendances du projet. Elles sont la plupart du temps disponibles sur le site [crates.io](https://crates.io), mais on peut aussi spécifier des dépendances locales ou des répertoires git. Pour utiliser une crate, il y a deux possibilités: 1. via cargo avec `cargo add le_nom_du_crate` Cargo va alors chercher le crate en question sur [crates.io](https://crates.io) et s'il existe, il va ajouter la dépendance dans le fichier `Cargo.toml` et la télécharger dans le cache local. 2. En éditant le fichier `Cargo.toml`. Dans le cas de notre mcu, il s'agit d'un coeur cortex M0+, nous allons donc utiliser les crates suivant: * [cortex-m](https://docs.rs/cortex-m/latest/cortex_m") pour la description des périphériques liés au coeur arm. * [cortex-m-rt](https://docs.rs/cortex-m-rt/latest/cortex_m_rt/) pour la gestion de la compilation et de l'édition de liens. * [panic-abort](https://docs.rs/panic-abort/latest/panic_abort/) pour avoir une implémentation par défaut de la gestion des erreurs non recouvrable. * [stm32l0](https://docs.rs/stm32l0/latest/stm32l0/) pour la description des périphériques du stm32l031. * [stm32l0xx-hal](https://docs.rs/crate/stm32l0xx-hal/latest) pour la HAL (nous l'utiliserons dans un second temps). Pour les crates *cortex-m*, *stm32l0* nous devons spécifier des options pour pouvoir les utiliser. Ce qui nous donne la liste de dépendance suivante: ```toml [dependencies] cortex-m-rt = "0.7.5" panic-abort = "0.3.2" cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] } stm32l0 = { version = "0.16.0", features = ["stm32l0x1", "rt"]} ``` Édition de liens ---------------- Le crate *cortex-m-rt fournis un script d'édition de liens par défaut. Il nous réclame simplement de créer un fichier `memory.x` à la racine du projet indiquant la configuration de la *RAM* et la *FLASH*. Pour ce qui est de la configuration de la table des vecteurs d'interruption, le crate *cortex-m-rt* offre un mécanisme permettant à l'utilisateur ou à d'autre crate [d'insérer leur définition.](https://docs.rs/cortex-m-rt/latest/cortex_m_rt/#incorporating-device-specific-interrupts). Dans notre cas c'est le crate *stm32l0* qui s'en charge via la feature *rt*. En ce qui concerne la configuration mémoire, notre micro (stm32l031k6) dispose de 32ko de flash et 8ko de ram. On va donc créer un fichier `memory.x` contenant: ``` MEMORY { RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 8K FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 32K } ``` Il ne nous reste plus qu'à éditer le fichier de config de cargo (.cargo/config.toml) pour ajouter un flag de compilation indiquant à rustc d'utiliser le script d'édition de liens du crate *cortex-m-rt* nommé `link.x`. ```toml [build] target = "thumbv6m-none-eabi" # flag pour l'édition de lien [target.thumbv6m-none-eabi] rustflags = ["-C", "link-arg=-Tlink.x",] ``` ### Pour aller plus loin + Si on le souhaite, on peut aussi utiliser un script de liens personnel ([doc](https://docs.rs/cortex-m-rt/latest/cortex_m_rt/#custom-linker-script)). + Si on souhaite analyser le fichier elf avec `objdump` [ce morceau de doc](https://docs.rs/cortex-m-rt/latest/cortex_m_rt/#inspection>) explicite les différentes sections. + script de liens par défaut: [link.x](https://github.com/rust-embedded/cortex-m/blob/master/cortex-m-rt/link.x.in) Un simple clignotement ====================== Commençons par le *hello world* de l'embarqué: faire clignoter une led... On commence par créer un dossier: `mkdir rust_stm32l031 && cd rust_stm32l031`. Ensuite on initialise le projet avec cargo: `cargo init --bin`. Cette commande va créer les fichiers de config (Cargo.toml et Carlo.lock) ainsi qu'un répertoire *src* et le fichier *main.rs*. Mise en place de la fonction *main* ----------------------------------- Pour pouvoir compiler notre projet pour la cible stm32l0, on doit spécifier que le programme n'utilise pas la bibliothèque standard. Pour cela on utilise la directive: `#![no_std]` On doit aussi indiquer au compilateur que l'on souhaite utiliser une fonction de démarrage custom (fonction qui initialise les variables et appelle la fonction *main*). En effet, notre `main()` sera appelée depuis la fonction `Reset` implémentée par *cortex-m-rt*. Pour ce faire on utilise la directive: `#![no_main]`. De son côté, le crate *cortex-m-rt* expose la directive `#[entry]` pour spécifier la fonction d'entrée. Enfin, on doit indiquer au compilateur comment la programme doit réagir en cas de détection de comportement interdit (panic). Pour ce faire, le compilateur attend une fonction: ```rust #[panic_handler] fn panic( info: &PanicInfo ) -> !{} ``` Plutôt que de créer notre propre implémentation, on utilise le crate *panic_abort* afin qu'en cas d'erreur on déclenche un *[hardfault](https://doc.rust-lang.org/core/intrinsics/fn.abort.html)*. On va donc avoir un fichier *main.rs* qui ressemble à ça: ```rust #![no_main] #![no_std] use cortex_m_rt::entry; user panic_abort as _; #[entry] fn main() -> ! { loop{} } ``` Voila vous pouvez maintenant compiler et flasher ce programme sur votre carte électronique. Ça n'a aucun intérêt puisque ce programme ne fait rien mais ça fonctionne! Configurer les périphériques --------------------------- Nous avons mis en place notre chaîne de compilation, nous pouvons maintenant attaquer la configuration des périphériques. Pour cela, nous allons utiliser deux crates: - *cortex-m* pour l'accès aux périphériques *arm*, par exemple: systick, nvic ... - *stm32l0* pour l'accès aux périphériques stm32, par exemple: adc, gpio ... Les objectifs sont: - configurer l'horloge pour utiliser le HSI 16MHz comme source - configurer le GPIO B3 en sortie pour faire clignoter une led - configurer le Systick pour cadencer le clignotement de la led Ce qui donne le fichier *main.rs* suivant: ```rust #![no_std] #![no_main] use cortex_m::{Peripherals, delay}; use cortex_m_rt::entry; use panic_abort as _; use stm32l0::stm32l0x1; #[entry] fn main() -> ! { // acces aux périphériques let peripherals = stm32l0x1::Peripherals::take().unwrap(); // on récupère le périphérique d'horloge et on configure le HSI let rcc = peripherals.RCC; rcc.apb2enr().write(|w| w.syscfgen().enabled()); rcc.cr().write(|w| w.hsi16on().enabled()); while rcc.cr().read().hsi16rdyf().bit_is_clear() {} rcc.cfgr().write(|w| w.sw().hsi16()); // configuration du Systick pour une clock à 16 MHz et // utilisation de la structure delay de cortex-m let peripheral = Peripherals::take().unwrap(); let mut delay = delay::Delay::new(peripheral.SYST, 16_000_000); // on active l'horloge pour le GPIOB rcc.iopenr().write(|w| w.iopben().enabled()); // On configure le GPIOB3 en sortie push-pull let gpiob = peripherals.GPIOB; gpiob.moder().write(|w| w.mode3().output()); loop { // on commence par lire l'état de la sortie via 'r' -> r.od3().bit() // On inverse la valeur à écrire par rapport à la valeur lu via '!' // et on écrit met à jour la sortie 'w' -> w.od3().bit(x) gpiob.odr().modify(|r, w| w.od3().bit(!r.od3().bit())); // attente de 500 ms delay.delay_ms(500); } } ``` Et voilà un clignotement de led en bonne et due forme.

AltStyle によって変換されたページ (->オリジナル) /