Embassy

Dans cette partie du TP, nous allons utiliser Embassy, un framework pour les applications Rust embarquées utilisant la programmation asynchrone.

L'utilisation de la programmation asynchrone permet une écriture souvent plus intuitive qu'avec un intergiciel classique. Cet exemple tiré de la comparaison d'Embassy avec FreeRTOS illustre la facilité d'utilisation du HAL asynchrone :

#[embassy::task]
async fn my_task(mut button: ExtiInput<'static, PC13>) {
    loop {
        button.wait_for_rising_edge().await;
        info!("Pressed!");
        button.wait_for_falling_edge().await;
        info!("Released!");
    }
}

Dans cet exemple, l'attente du changement d'état du bouton poussoir connecté sur PC13 se fait par interruption sans qu'on ait à le demander explicitement. L'interruption 13 de l'EXTI est également acquittée automatiquement. Cela signifie notamment que le cœur du microcontrôleur peut entrer en veille s'il n'a rien d'autre à faire en attendant que le bouton poussoir soit actionné.

Un autre exemple complet, tiré d'un tutoriel sur Embassy, montre la facilité d'utilisation d'une UART en utilisant des transferts DMA entre la mémoire et le périphérique sans monopoliser le processeur :

#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]

use embassy_executor::Spawner;
use embassy_stm32::interrupt;
use embassy_stm32::usart::{Config, Uart};
use panic_halt as _;

#[embassy_executor::main]
async fn main(_spawner: Spawner) {
    let p = embassy_stm32::init(Default::default());

    let irq = interrupt::take!(USART2);
    let mut usart = Uart::new(
        p.USART2,
        p.PA3,
        p.PA2,
        irq,
        p.DMA1_CH6,
        p.DMA1_CH5,
        Config::default(),
    );

    usart.write(b"Starting Echo\r\n").await.unwrap();

    let mut msg: [u8; 8] = [0; 8];

    loop {
        usart.read(&mut msg).await.unwrap();
        usart.write(&msg).await.unwrap();
    }
}

On pourra se remémorer les différentes étapes à effectuer lorsqu'on utilise habituellement de telles fonctions. Là aussi le cœur du microcontrôleur se mettra en veille en attendant que chaque message soit lu ou transmis depuis le port série.

Toutes ces fonctions utilisent un HAL asynchrone adaptée à l'architecture ciblée. Dans notre cas, cela sera le HAL pour STM32.

💡 Il est important dans la documentation du HAL de choisir (sur la ligne du haut) le modèle de microcontrôleur approprié. Ici, nous utiliserons un stm32l475vg, disponible sur nos cartes IoT node.