STM32 Rust Page
Introduction
This is meant to be a general page for rust when using STM32
Install Rust For Embedded
First we install rust. This adds something to ~/.bashrc so rebooting is the quickest way
sudo apt install curl
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
For stm32 we need the toolchain
rustup target add thumbv7em-none-eabihf
cargo install cargo-binutils
cargo install cargo-generate
Install the gcc tools
sudo apt install gdb-multiarch openocd qemu-system-arm
Now we need to install the arm software. For me I went to the arm download page and took the latest version.[here]
I unzipped it and put it in the /opt directory, currently arm-gnu-toolchain-14.2, and add it to my path in .bashrc
Blinky In Rust
The new classic since hello world
#[entry]
fn main() -> ! {
if let Some(dp) = pac::Peripherals::take() {
// Set up the system clock.
let mut rcc = dp.RCC.constrain();
// Get the GPIOB peripheral
let mut gpiob = dp.GPIOB.split(&mut rcc.ahb);
// (Re-)configure PE13 as output
let mut led = gpiob.pb13.into_push_pull_output(&mut gpiob.moder, &mut gpiob.otyper);
led.set_low().unwrap();
loop {
led.toggle().unwrap();
// Wait for 1_000_000 cycles
cortex_m::asm::delay(1_000_000);
if led.is_set_low().unwrap() {
led.set_high().unwrap();
} else {
led.set_low().unwrap();
}
// Wait for 1_000_000 cycles
cortex_m::asm::delay(1_000_000);
}
}
loop {
cortex_m::asm::nop();
}
}
Interrupts In Rust
This involves creating Mutexes to make the shared resource available. First we create these Mutexes
type LedPin = gpio::PB10<Output<PushPull>>;
static LED: Mutex<RefCell<Option<LedPin>>> = Mutex::new(RefCell::new(None));
type ButtonPin = gpio::PC13<Input>;
static BUTTON: Mutex<RefCell<Option<ButtonPin>>> = Mutex::new(RefCell::new(None));
Next we setup the interrupt
#[entry]
fn main() -> ! {
...
// Moving ownership of the led to the global LED
critical_section::with(|cs| *LED.borrow(cs).borrow_mut() = Some(led));
// Configuring the user button to trigger an interrupt when the button is pressed.
let mut user_button = gpioc.pc13;
// Make button an interrupt source
syscfg.select_exti_interrupt_source(&user_button);
// Set when to trigger
user_button.trigger_on_edge(&mut exti, Edge::Rising);
// Enable the interrupt for button
user_button.enable_interrupt(&mut exti);
// Get the interrupt number
let interrupt_num = user_button.interrupt();
// Moving ownership to the global BUTTON
critical_section::with(|cs| *BUTTON.borrow(cs).borrow_mut() = Some(user_button));
// Enable Interrupt
unsafe { NVIC::unmask(interrupt_num) };
loop {
asm::wfi();
}
}
After this we need to create the interrupt. The name of the function is derived from the line to NVIC. The is PC15 so EXTI15_10 is the correct name as EXI15_10 is Pin 10-15 or the GPIOs. We need PB10 so this is the correct one. If it was PB9 then this would be EXTI9_5.
// This may be called more than once per button press from the user since the button may not be debounced.
#[interrupt]
fn EXTI15_10() {
critical_section::with(|cs| {
// Toggle the LED
LED.borrow(cs)
.borrow_mut()
.as_mut()
.unwrap()
.toggle()
.unwrap();
// Clear the interrupt pending bit so we don't infinitely call this routine
BUTTON
.borrow(cs)
.borrow_mut()
.as_mut()
.unwrap()
.clear_interrupt();
})
}
Debbugging in Rust
VS Code Launch Settings
This is the current setup as of April 2024. The SVD enables to XPERIPHERALS under breakpoints.
{
"version": "0.2.0",
"configurations": [
{
"type": "cortex-debug",
"request": "launch",
"servertype": "openocd",
"cwd": "${workspaceRoot}",
"executable": "./target/thumbv7em-none-eabihf/debug/blink",
"name": "Debug (OpenOCD)",
"device": "STM32F302RETx",
"showDevDebugOutput": "parsed",
"preLaunchTask": "cargo build",
"runToEntryPoint": "true",
"configFiles": [
"interface/stlink-v2-1.cfg",
"target/stm32f3x.cfg"
],
"svdFile": "${workspaceRoot}/STM32F302.svd",
"swoConfig": {
"enabled": true,
"cpuFrequency": 8000000,
"swoFrequency": 2000000,
"source": "probe",
"decoders": [
{ "type": "console", "label": "ITM", "port": 0 }
]
}
}
]
}
Serial Wire Out SWO Debugging
You can output text using the SWO console which is like semi-hosting. Not the settings in the VSCode Launch
#[entry]
fn main() -> ! {
if
let (Some(dp), Some(cp)) = (
pac::Peripherals::take(),
cortex_m::peripheral::Peripherals::take(),
)
{
...
let mut itm = cp.ITM;
iprintln!(&mut itm.stim[0], "Hello World for the time");
...