Getting Started With Rust and Stm32F411 Discovery Board
Queste sono semplici note sullo sviluppo di una semplice applicazione Rust per la scheda di sviluppo STM 32F411 Discovery.
Il tutto è estratto dalla sezione Hardware dell’ottimo libro The Embedded Rust book
Compilare un progetto di esempio ed eseguirlo
$ cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart
🤷 Project Name : blinkstm32
🔧 Destination: /home/happycactus/Documents/devel/Tutorial/Rust/embed/stm32f411/blinkstm32 ...
🔧 Generating template ...
...
[25/25] Done: src
🔧 Moving generated files into: `/home/happycactus/Documents/devel/Tutorial/Rust/embed/stm32f411/blinkstm32`...
💡 Initializing a fresh Git repository
✨ Done! New project created /home/.../stm32f411/blinkstm32
Il progetto è stato generato, ora si deve personalizzare i file di costruzione.
Si inizi a personalizzare il file .cargo/config.toml
L’STM32F411VE è una cpu con core Cortex-M4 con Unità floating point, quindi si seleziona
target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
Inoltre occorre configurare la mappa di memoria editando il file memory.x
La CPU in uso ha 512kB di Flash (all’indirizzo di memoria 0800 0000
1) e 128kB di RAM.
MEMORY
{
/* NOTE 1 K = 1 KiBi = 1024 bytes */
FLASH : ORIGIN = 0x08000000, LENGTH = 512K
RAM : ORIGIN = 0x20000000, LENGTH = 128K
}
A questo punto si può iniziare a compilare l’esempio denominato hello.rs
$ cargo build --example hello
e ad eseguirlo, utilizzando openocd con l’STLink.
Verificare che OpenOCD sia correttamente configurato
OpenOCD viene configurato dal file openocd.cfg
, occorre verificare che il target sia corretto:
source [find interface/stlink.cfg]
source [find target/stm32f4x.cfg]
(di default è impostato stm32f3).
A questo punto si esegua openocd in una finestra e gdb-multiarch nell’altra:
$ gdb-multiarch -q target/thumbv7em-none-eabihf/debug/examples/hello
Reading symbols from target/thumbv7em-none-eabihf/debug/examples/hello...
(gdb) target remote :3333
Remote debugging using :3333
0x08003464 in ?? ()
(gdb) load
Loading section .vector_table, size 0x400 lma 0x8000000
Loading section .text, size 0x1554 lma 0x8000400
Loading section .rodata, size 0x418 lma 0x8001954
Start address 0x08000400, load size 7532
Transfer rate: 13 KB/sec, 2510 bytes/write.
(gdb) monitor arm semihosting enable
semihosting is enabled
(gdb) break main
Breakpoint 1 at 0x80004a2: file examples/hello.rs, line 11.
Note: automatically using hardware breakpoints for read-only addresses.
(gdb) c
Continuing.
Breakpoint 1, hello::__cortex_m_rt_main_trampoline () at examples/hello.rs:11
11 #[entry]
(gdb) s
halted: PC: 0x080004a8
hello::__cortex_m_rt_main () at examples/hello.rs:13
13 hprintln!("Hello, world!").unwrap();
(gdb) n
halted: PC: 0x080004b2
...
Nella finestra con openocd comparirà il messaggio di Hello:
[...]
Info : halted: PC: 0x080004b8
Info : halted: PC: 0x08000562
Hello, world!
Info : halted: PC: 0x080004be
[...]
Funziona!
Personalizzare gdb
Una parte di gdb è già stata personalizzata dal template. Il file openocd.gdb
riporta l’elenco delle istruzioni che vogliamo siano eseguite automaticamente così da non aver bisogno di avviare manualmente né gdb né i restanti comandi.
Se nel config.toml
si abilita la linea:
runner = "gdb-multiarch -q -x openocd.gdb"
Sarà possibile avviare il programma semplicemente con:
$ cargo run --example hello
Qualcosa di più complesso
Creato così il progetto e importate le librerie del core Cortex, è possibile già operare sul processore, e accedere ai registri principali della CPU. Tuttavia operare direttamente sui registri non è né facile né agevole, molto meglio utilizzare librerie adeguate: delle PAC (Peripheral Access Crate, ossia librerie per l’accesso alle periferiche) o, ancora meglio, un HAL (Hardware Abstraction Layer).
Per una trattazione completa si può vedere questo ottimo articolo introduttivo.
Importiamo ora una HAL adatta al processore in uso, come detto un STM32F411.
Cercando su crates.io troviamo che stm32_hal2
è la libreria che fa al caso nostro.
Seguendo le istruzioni della pagina Getting started, inseriamo la dipendenza nel file cargo.toml
:
stm32-hal2 = { version = "^1.5.3", features = ["f401", "f4rt"]}
A questo punto possiamo importare nel main.rs
anche gli oggetti che ci servono, per esempio:
use stm32_hal2::{
clocks::{Clocks},
timer::{Timer, TimerInterrupt},
gpio::{Pin, Port, PinMode},
pac::{self, tim3, interrupt}
};
Configurare diverse funzioni panic
per debug e release
In caso di panic
rust chiama una funzione che su embedded blocca la CPU.
Tuttavia esistono possibilità diverse, che si sia in produzione, ossia senza alcuna seriale o sistema di output stream a disposizione, oppure in debug, magari con il debugger attivo. In questo caso, si può configurare funzioni di panic diverse, in questo modo:
// dev profile: easier to debug panics; can put a breakpoint on `rust_begin_unwind`
#[cfg(debug_assertions)]
use panic_halt as _;
// release profile: minimize the binary size of the application
#[cfg(not(debug_assertions))]
use panic_abort as _;
Nello specifico:
- panic_halt: il thread corrente viene messo in loop senza arresto della CPU, in questo caso è possibile inserire un breakpoint in
rust_begin_unwind
e intercettare l’evento - panic_abort: esegue l’istruzione abort (la cpu si ferma)
- panic-itm: il messaggio di panic viene inviato all’ITM, una periferica specifica di Arm Cortex
- panic-semihosting: il messaggio viene inviato al debugger tramite il meccanismo del semihosting, è così possibile debuggare senza l’uso di breakpoint.
Ricordarsi di inserire in Cargo.toml
le relative importazioni, ad esempio
panic-halt = "0.2.0"
panic-semihosting = "0.6.0"
-
Tale indirizzo è in alias anche all’indirizzo
0000 0000
, ma l’indirizzo fisico è quello specificato sopra, quindi attenzione a non mappare nella zona sbagliata! ↩︎