Introduction : L'ère du « matériel » change de langage
\n\nSi vous avez déjà écrit un firmware pour microcontrôleur en C, vous connaissez cette sensation. Cette sensation où, après trois heures de débogage, vous trouvez un plantage — un pointeur parti dans le vide, et le watchdog a tout encaissé. Le développement embarqué a longtemps été le territoire du C et de l'assembleur. Mais ces dernières années, un nouvel acteur est entré en scène — Rust. Et ce n'est pas juste « un langage de plus », mais une véritable avancée pour les systèmes embarqués.
\n\nDans cet article, nous verrons pourquoi le développement embarqué en Rust n'est pas un effet de mode, mais une nécessité. Nous examinerons des exemples de code réels, comparerons avec le C et comprendrons par où commencer. Si vous programmez sur STM32, ESP32 ou ARM Cortex-M, cet article est pour vous.
\n\nPourquoi Rust pour l'embarqué est un mariage idéal
\n\nLes systèmes embarqués sont un monde de contraintes. Peu de mémoire, pas d'OS, pas de heap, pas d'exceptions. Une erreur de compilation, c'est bien ; une erreur à l'exécution, c'est grave. Rust a été conçu précisément pour ces conditions.
\n\nSécurité mémoire sans ramasse-miettes
\n\nLe principal problème du développeur embarqué en C, ce sont les use-after-free, les doubles libérations mémoire et les dépassements de tampon. Rust résout cela au niveau des types. Le modèle de possession (ownership) et d'emprunt (borrowing) fonctionne même dans un environnement no_std — sans allocateur, sans OS, sans GC.
Imaginez que vous écrivez un pilote pour une LED. En C, vous écririez :
\n\n// Code C\nvoid led_on(GPIO_TypeDef *gpio, uint16_t pin) {\n gpio->BSRR = pin;\n}\n\nTout va bien, jusqu'à ce que quelqu'un passe un pointeur nul. Rust ne vous permettra pas de faire cela :
\n\n// Code Rust\nfn led_on(gpio: &mut GpioRegisters, pin: u16) {\n gpio.bsrr.write(|w| w.bits(pin.into()));\n}\n\nIci, &mut GpioRegisters est une garantie que la référence est valide et que l'accès aux registres est exclusif. Le compilateur ne laissera tout simplement pas passer le code dangereux.
Abstractions à coût zéro
\n\nL'une des principales craintes des développeurs embarqués est qu'un langage de haut niveau « bouffe » des cycles processeur. Rust adhère au principe des abstractions à coût zéro : vous ne payez que pour ce que vous utilisez. Itérateurs, fermetures, traits — tout cela se compile en un code machine aussi efficace que le déroulement manuel de boucles en C.
\n\nDe plus, dans l'écosystème embarqué de Rust, il existe des crates qui fonctionnent au niveau des instructions individuelles. Par exemple, cortex-m-rt génère le vecteur d'interruptions et le code de démarrage sans une seule instruction superflue.
Projet réel : faire clignoter une LED en Rust (STM32)
\n\nÉcrivons un programme simple pour la carte STM32F3Discovery. Nous utiliserons cortex-m-quickstart et le HAL de la communauté rust embarqué.
Étape 1 : Configuration du projet
\n\nAssurez-vous d'avoir installé Rust et la chaîne d'outils pour ARM Cortex-M :
\n\nrustup target add thumbv7em-none-eabihf\ncargo install cargo-generate\n\nCréons un projet à partir du modèle :
\n\ncargo generate --git https://github.com/rust-embedded/cortex-m-quickstart\n\nÉtape 2 : Écrire le code
\n\nOuvrons src/main.rs et écrivons le classique Blinky :
#![no_std]\n#![no_main]\n\nuse cortex_m_rt::entry;\nuse panic_halt as _;\nuse stm32f3xx_hal::{pac, prelude::*};\n\n#[entry]\nfn main() -> ! {\n // Accès aux périphériques\n let dp = pac::Peripherals::take().unwrap();\n let mut rcc = dp.RCC.constrain();\n let mut gpioe = dp.GPIOE.split(&mut rcc.ahb);\n\n // Configuration de la broche PE9 en sortie (LED LD1)\n let mut led = gpioe\n .pe9\n .into_push_pull_output(&mut gpioe.moder, &mut gpioe.otyper);\n\n loop {\n led.set_high().unwrap();\n cortex_m::asm::delay(8_000_000);\n led.set_low().unwrap();\n cortex_m::asm::delay(8_000_000);\n }\n}\n\nQue se passe-t-il ici ?
\n- \n
#![no_std]— nous n'utilisons pas la bibliothèque standard \n