Programmez en Rust sur la carte XIAO RP2040

5
(3)

Le XIAO RP2040 de Seeed Studio est une carte miniature compatible avec l’écosystème Raspberry Pi Pico car ils utilisent le même microcontrôleur RP2040. La documentation de Seeed Studio indique qu’il est possible de développer avec plusieurs langages sur cette carte, dont C / MicroPython / CircuitPython. Nous allons voir dans ce tuto qu’il est également possible de la programmer en Rust.

Pourquoi Rust ?

Rust est un langage de programmation développé par Mozilla comme une alternative au C/C++ orienté sur la performance et la sécurité.

Le langage Rust possède de nombreux avantages :

  • Une gestion sûre de la mémoire (point faible du C)
  • De très bonnes performances d’exécution (point faible du Python)
  • Une documentation très riche (livre gratuit en ligne)
  • Un compilateur très convivial qui vous propose des suggestions

Installer Rust

Pour installer Rust c’est très simple, sous Linux il suffit de lancer la commande suivante dans un terminal :

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Et sélectionner l’option “1) Proceed with installation (default)” lorsque la question est posée.

Il suffit ensuite de compléter l’installation en entrant la commande suivante dans le terminal :

source "$HOME/.cargo/env"

Si vous êtes sous Windows, les instructions d’installation de Rust se trouvent ici.

Installer le support de Rust pour le microcontrôleur RP2040

Tout d’abord assurez-vous d’avoir la dernière version de Rust :

rustup self update
rustup update stable

Il faut ajouter la cible thumbv6m-none-eabi pour le support de l’architecture ARM Cortex-M0+ du RP2040 dans la chaîne de compilation croisée de Rust :

rustup target add thumbv6m-none-eabi

Installer ensuite le package elf2uf2-rs qui va permettre de créer les images UF2 pour le bootloader du RP2040 :

sudo apt install libudev-dev
cargo install elf2uf2-rs

Nous allons utiliser le Workspace rp-hal. Ce dépôt git est un référentiel contenant une collection de pilotes de haut niveau pour le microcontrôleur RP2040.

git clone https://github.com/rp-rs/rp-hal

L’exemple seeeduino_xiao_rp2040_blinky

Le référentiel rp-hal contient des exemples pour chaque microcontrôleur. Pour commencer simplement nous allons utiliser l’exemple seeeduino_xiao_rp2040_blinky qui permet de faire clignoter la LED interne de notre XIAO RP2040.

Il faut tout d’abord compiler le programme :

cd rp-hal
cargo build --example seeeduino_xiao_rp2040_blinky

Ensuite il faut télécharger le programme dans le XIAO RP2040. Pour cela il faut connecter la carte XIAO RP2040 au PC avec un câble USB tout en appuyant sur le bouton BOOT. Vous pouvez ensuite relâcher l’appui sur ce bouton une fois le câble connecté. La carte entre en mode “bootloader” et le apparaît comme un stockage de masse.

Appuyer sur le bouton BOOT en connectant le XIAO 2040 au PC par le câble USB afin d’entrer en mode “bootloader”

Une fois que le XIAO RP2040 est vu comme un stockage de masse sur votre PC (comme une clé USB), vous pouvez lancer le téléchargement et l’exécution du programme sur le RP2040 avec la commande :

cargo run --example seeeduino_xiao_rp2040_blinky

Si tout se déroule normalement, vous devriez voir le programme transféré à 100% et la LED clignoter sur le RP2040.

$cargo run --example seeeduino_xiao_rp2040_blinky
    Finished dev [unoptimized + debuginfo] target(s) in 0.11s
     Running `elf2uf2-rs -d target/thumbv6m-none-eabi/debug/examples/seeeduino_xiao_rp2040_blinky`
Found pico uf2 disk /media/tutoduino/RPI-RP2
Transfering program to pico
95.50 KB / 95.50 KB [==================================================================================] 100.00 % 147.52 KB/s

Dès que le transfert est terminé, la carte XIAO RP2040 se réinitialise et le programme démarre.

Le programme seeeduino_xiao_rp2040_blinky agit sur la “USER LED” 3 couleurs. Il commence par faire clignoter 5 fois la LED en bleue puis fais un “fading” sur la LED en rouge (0%->100%->0%) en utilisant le principe du PWM (voir mon article qui explique ce principe).

Créez votre programme

Nous allons maintenant voir les différentes étapes qui vous permettrons de créer votre propre programme pour le XIAO RP2040.
Commençons par créer un nouveau projet nommé xiao_rp2040_rs en utilisant le gestionnaire de paquet cargo :

cargo new xiao_rp2040_rs

Il faut ensuite modifier le fichier Cargo.toml pour y inclure les dépendances utilisées dans notre programme :

[package]
name = "xiao_rp2040_rs"
version = "0.1.0"
edition = "2021"
description = "XIAO RP2040 RGB LED Blinking"
authors = [
    "Stephane POTIER <tutoduino@gmx.fr>",
    "Thomas POTIER <d34db4b3@protonmail.com>",
]
homepage = "https://tutoduino.fr/en/tutorials/programing-in-rust-the-xiao-rp2040-board/"
license = "MIT OR Apache-2.0"
repository = "https://github.com/rp-rs/rp-hal.git"
[dependencies]
cortex-m = "0.7.6"
seeeduino-xiao-rp2040 = "0.2.0"
panic-halt = "0.2.0"
embedded-hal = "0.2.7"
cortex-m-rt = "0.7"
rp2040-boot2 = "0.2.0"
ws2812-pio = "0.4.0"
smart-leds = "0.3.0"

Il est nécessaire de créer le fichier memory.x afin d’y décrire la disposition de la mémoire du microprocesseur :

MEMORY {
    BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100
    FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100
    RAM   : ORIGIN = 0x20000000, LENGTH = 256K
}
EXTERN(BOOT2_FIRMWARE)
SECTIONS {
    /* ### Boot loader */
    .boot2 ORIGIN(BOOT2) :
    {
        KEEP(*(.boot2));
    } > BOOT2
} INSERT BEFORE .text;

Le fichier memory.x doit être placé dans le répertoire du projet à côte du fichier Cargo.toml :

Contenu du répertoire xiao_rp2040_rs

Il faut ensuite créer le répertoire .cargo et y créer le fichier .cargo/config.toml avec le contenu suivant :

# set default build target for rp2040
[build]
target = "thumbv6m-none-eabi"
# upload binary to rp2040 instead of running on host
[target.thumbv6m-none-eabi]
runner = "elf2uf2-rs -d"
# use appropriate memory layout
rustflags = ["-C", "link-arg=-Tlink.x"]

Il ne reste plus qu’à finaliser notre code dans le fichier src/main.rs

//! XIAO RP2040 Blinking LED Example
//!
//! https://tutoduino.fr/
//!
//! Blinks the LED on a Seeed Studio XIAO RP2040 board.
//!
#![no_std]
#![no_main]
use embedded_hal::digital::v2::OutputPin;
use panic_halt as _;
use seeeduino_xiao_rp2040::entry;
use seeeduino_xiao_rp2040::hal;
use seeeduino_xiao_rp2040::hal::pac;
use seeeduino_xiao_rp2040::hal::prelude::*;
/// Entry point to our bare-metal application.
///
/// The `#[entry]` macro ensures the Cortex-M start-up code calls this function
/// as soon as all global variables are initialised.
///
/// The function configures the RP2040 peripherals, then blinks the LED in an
/// infinite loop.
#[entry]
fn main() -> ! {
    // Grab our singleton objects
    let mut pac = pac::Peripherals::take().unwrap();
    let core = pac::CorePeripherals::take().unwrap();
    // Set up the watchdog driver - needed by the clock setup code
    let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);
    // Configure the clocks
    //
    // The default is to generate a 125 MHz system clock
    let clocks = hal::clocks::init_clocks_and_plls(
        seeeduino_xiao_rp2040::XOSC_CRYSTAL_FREQ,
        pac.XOSC,
        pac.CLOCKS,
        pac.PLL_SYS,
        pac.PLL_USB,
        &mut pac.RESETS,
        &mut watchdog,
    )
    .ok()
    .unwrap();
    // The single-cycle I/O block controls our GPIO pins
    let sio = hal::Sio::new(pac.SIO);
    // Set the pins up according to their function on this particular board
    let pins = seeeduino_xiao_rp2040::Pins::new(
        pac.IO_BANK0,
        pac.PADS_BANK0,
        sio.gpio_bank0,
        &mut pac.RESETS,
    );
    // The delay object lets us wait for specified amounts of time (in
    // milliseconds)
    let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz());
    // Configure the pins to operate as a push-pull output
    let mut led_blue_pin = pins.led_blue.into_push_pull_output();
    let mut led_green_pin = pins.led_green.into_push_pull_output();
    let mut led_red_pin = pins.led_red.into_push_pull_output();
    // Set green and red colors of the USER LED OFF
    led_green_pin.set_high().unwrap();
    led_red_pin.set_high().unwrap();
    loop {
        // Turn on blue color of USER LED for 1s
        led_blue_pin.set_low().unwrap();
        delay.delay_ms(1000);
        // Turn off blue color of USER LED for 500ms
        led_blue_pin.set_high().unwrap();
        delay.delay_ms(500);
    }
}

Note : Vous remarquez dans le code ci-dessus que les broches de la USER LED 3 couleurs sont mises à leur état bas (set_low) pour allumer la LED sur le XIAO RP2040. Le schéma de la carte montre en effet que les broches de sortie du RP2040 contrôlant chaque couleur de cette LED (IO25_RGB-B, IO16_RGB-G et IO17_RGB-R) doivent être mises à leur état bas pour que le courant circule. Plus d’information sont disponibles sur le wiki de Seeed.

Schéma de la USER LED 3 couleurs

Et ensuite comme expliqué pour l’exemple précédent, connecter le câble USB entre le PC et la carte XIAO RP2040 en restant appuyer sur le bouton BOOT.

Puis compilez et téléversez (via elf2uf2-rs défini dans config.toml) le programme sur le XIAO RP2040 :

cargo run --release

Si tout se déroule normalement, vous devriez voir le programme transféré à 100% et la USER LED clignoter en bleu sur le RP2040.

led blinking rp2040

La LED RGB du XIAO RP2040

La LED RGB est une LED NeoPixel adressable WS2812B, c’est le type de LED qui est généralement utilisée en bandeau.

Principe des LED NeoPixel

Les LED WS2812B incorporent un circuit intégré qui permet de cascader les LED en utilisant une communication série sur un fil. La broche DOUT de chaque LED étant reliée à la broche DIN de la suivante.

La première LED reçoit les données du contrôleur sur sa broche DIN, elle prélève sa consigne de couleur qui est codée sur les 3 premiers octets (couleur RGB sur 24 bits). Ensuite la LED cascade les données vers la prochaine LED via sa broche DOUT. La seconde LED reçoit sur sa broche DIN sa consigne de couleur codée sur les 3 premiers octets qu’elle reçoit ainsi que les consignes de couleur des LED suivantes qu’elle enverra à son tour via sa broche DOUT.

Programme contrôlant la USER LED et la RGB LED

Dans notre programme, nous allons utiliser le crate ws2812-pio. Ce pilote implémente la commande d’une bande LED RGB WS2812 en utilisant le IO programmables (PIO) de la puce RP2040.

//! # Tutoduino XIAO RP2040 Blinking LED Example
//!
//! Blinks the LED on a Seeed Studio XIAO RP2040 board.
//!
#![no_std]
#![no_main]
use embedded_hal::digital::v2::OutputPin;
use hal::pio::PIOExt;
use hal::Timer;
use panic_halt as _;
use seeeduino_xiao_rp2040::entry;
use seeeduino_xiao_rp2040::hal;
use seeeduino_xiao_rp2040::hal::pac;
use seeeduino_xiao_rp2040::hal::prelude::*;
use smart_leds::{SmartLedsWrite, RGB8};
use ws2812_pio::Ws2812;
const RED: RGB8 = RGB8::new(255, 0, 0);
const GREEN: RGB8 = RGB8::new(0, 255, 0);
const BLUE: RGB8 = RGB8::new(0, 0, 255);
const WHITE: RGB8 = RGB8::new(255, 255, 255);
/// Entry point to our bare-metal application.
///
/// The `#[entry]` macro ensures the Cortex-M start-up code calls this function
/// as soon as all global variables are initialised.
///
/// The function configures the RP2040 peripherals, then blinks the LED in an
/// infinite loop.
#[entry]
fn main() -> ! {
    // Grab our singleton objects
    let mut pac = pac::Peripherals::take().unwrap();
    let core = pac::CorePeripherals::take().unwrap();
    // Set up the watchdog driver - needed by the clock setup code
    let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);
    // Configure the clocks
    //
    // The default is to generate a 125 MHz system clock from 12 Mhz crystal
    let clocks = hal::clocks::init_clocks_and_plls(
        seeeduino_xiao_rp2040::XOSC_CRYSTAL_FREQ,
        pac.XOSC,
        pac.CLOCKS,
        pac.PLL_SYS,
        pac.PLL_USB,
        &mut pac.RESETS,
        &mut watchdog,
    )
    .ok()
    .unwrap();
    // The single-cycle I/O block controls our GPIO pins
    let sio = hal::Sio::new(pac.SIO);
    // Set the pins up according to their function on this particular board
    let pins = seeeduino_xiao_rp2040::Pins::new(
        pac.IO_BANK0,
        pac.PADS_BANK0,
        sio.gpio_bank0,
        &mut pac.RESETS,
    );
    // The delay object lets us wait for specified amounts of time (in
    // milliseconds)
    let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz());
    let timer = Timer::new(pac.TIMER, &mut pac.RESETS);
    // Setup PIO
    let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS);
    // Setup Neopixel RGB LED
    let mut ws = Ws2812::new(
        pins.neopixel_data.into_mode(),
        &mut pio,
        sm0,
        clocks.peripheral_clock.freq(),
        timer.count_down(),
    );
    // Turn on Neopixel RGB LED
    let mut neopixel_power = pins.neopixel_power.into_push_pull_output();
    neopixel_power.set_high().unwrap();
    // Configure the USER LED pins to operate as a push-pull output
    let mut led_blue_pin = pins.led_blue.into_push_pull_output();
    let mut led_green_pin = pins.led_green.into_push_pull_output();
    let mut led_red_pin = pins.led_red.into_push_pull_output();
    loop {
        // Set USER LED to blue
        led_blue_pin.set_low().unwrap();
        led_red_pin.set_high().unwrap();
        led_green_pin.set_high().unwrap();
        // Set RGB LED to blue
        ws.write([BLUE].iter().copied()).unwrap();
        delay.delay_ms(500);
        // Set USER LED to red
        led_blue_pin.set_high().unwrap();
        led_red_pin.set_low().unwrap();
        led_green_pin.set_high().unwrap();
        // Set RGB LED to red
        ws.write([RED].iter().copied()).unwrap();
        delay.delay_ms(500);
        // Set USER LED to green
        led_blue_pin.set_high().unwrap();
        led_red_pin.set_high().unwrap();
        led_green_pin.set_low().unwrap();
        // Set RGB LED to red
        ws.write([GREEN].iter().copied()).unwrap();
        delay.delay_ms(500);
        // Set USER LED to white
        led_blue_pin.set_low().unwrap();
        led_red_pin.set_low().unwrap();
        led_green_pin.set_low().unwrap();
        // Set RGB LED to white
        ws.write([WHITE].iter().copied()).unwrap();
        delay.delay_ms(500);
    }
}

Ce programme est disponible sur mon github.

Conclusion

Programmer un microcontrôleur en Rust est relativement simple grâce aux nombreux pilotes disponibles. La carte XIAO RP2040 offre un très bon compromis performances/coût et sa taille réduite permet d’envisager un grand nombre d’applications.

Note : Seeed Studio vient d’ouvrir un entrepôt en UE, ce qui signifie que les clients français vont profiter de délais de livraison plus courts et de frais de port réduits.

Votre avis compte !

Note moyenne : 5 / 5. Nombre de votes : 3

Pas encore de vote pour ce tutoriel

Désolé si cet article ne vous a pas intéressé

Merci de commenter afin que je puisse l’améliorer.

Dites-moi comment améliorer cette page.