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.
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 :
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.
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.
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.