logo
CODEMBIT
Reliable software engineering partner
Home Blog Tools

Getting started with Rust, STM32F0 Discovery kit and vscode

By Toni Akkala, 23rd of April, 2023

Overview

Some time ago, I was reading the Rust book to learn the differences between Rust and other programming languages. Because Rust is getting popular also in embedded systems, I wanted to setup an environment to practice programming embedded devices by using the Rust language. I had already browsed through the embedded Rust book and noticed that I had a similar development board that was used as an example in that book. This post covers setting up the environment to build and debug embedded Rust applications using STM32F0 discovery kit and vscode.

Setting up the environment

Installation

I won't cover the whole installation process here, I followed the instructions mentioned in the embedded Rust book to install Rust, ARM embedded toolchain and OpenOCD for Windows. I had STLink driver already installed. Because I was cross compiling for STM32F072 ARM Cortex-M0, I added the thumbv6m-none-eabi compilation target with rustup command.

rustup target add thumbv6m-none-eabi

Now I had tools installed and I was ready to verify OpenOCD connection to the board and the compiler version:

OpenOCD connection to STM32F0 discovery kit
Figure: OpenOCD connection to STM32F0 discovery kit

Sources

I took the cortex-m-quickstart project as a basis for my trials and used the app name for that to make sure I could follow the Rust book examples:

cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart

I modified .cargo/config.toml to select correct toolchain for ARM Cortex-M0. Then, I checked the microcontroller (MCU) reference manual to find out the memory organization and changed the memory sections in memory.x to match the memory layout and the sizes of my MCU.

FLASH : ORIGIN = 0x08000000, LENGTH = 128K RAM : ORIGIN = 0x20000000, LENGTH = 16K

I used the following settings in openocd.cfg:

source [find interface/stlink.cfg] source [find target/stm32f0x.cfg]

Now I had configured everything and I was ready to follow the Rust book to build hello world example and start debugging that:

cargo run --example hello
Active GDB session
Figure: Active GDB session
Hello world printed with semihosting
Figure: Hello world printed with semihosting

vscode

After getting debugging working with the OpenOCD and GDB I wanted to have the same functionality working in the Integrated Development Environment (IDE) so I installed vscode. After opening the project folder with vscode, it suggested automatically to install necessary extensions, rust-analyzer and Cortex-Debug. After this, the only thing I needed to do, was to modify .vscode/launch.json to match my settings.

/* STM32F0 Discovery kit configuration */ "type": "cortex-debug", "request": "launch", "name": "Debug (OpenOCD)", "servertype": "openocd", "cwd": "${workspaceRoot}", "preLaunchTask": "Cargo Build (debug)", "runToEntryPoint": "main", "executable": "./target/thumbv6m-none-eabi/debug/examples/hello", "device": "STM32F072RB76", "configFiles": [ "interface/stlink.cfg", "target/stm32f0x.cfg" ],

With these settings, I was able to start a debug session from a Run and Debug tab:

VSCodium debug session
Figure: vscode debug session

The README.md in .vscode folder also instructs to install System View Description file to describe the MCU register contents. I downloaded the file from the ST website, copied it to .vscode folder and added that to the launch configuration with the following code:

"svdFile": "${workspaceRoot}/.vscode/STM32F0x2.svd",

Now I had a working debugging environment and I was able to see MCU peripherals registers directly on the IDE:

Cortex-M0 peripehrals visible in debug session
Figure: Cortex-M0 peripehrals visible in debug session

Coding

Crates

Before writing my own code I needed to get familiar with the embedded Rust. Rust offers different abstraction levels of the microcontroller. The lowest level include Micro-architecture Crate (including general ARM functionalities) and Peripheral Access Crate (PAC, including various memory wrapper registers). Above these are the Hardware Abstraction layer and the board specific layers. In other words, there are multiple ways of accessing MCU resources. Because I felt being more familiar with the MCU resoures than Rust, I started from the lowest level of abstraction.

My code for the LEDs

As usual, I will practise debugging with the LEDs. STM32F0 discovery kit has 4 user controlled LEDs connected to I/O pims PC6 .. PC9. I needed to find a way to access these LEDs from Rust. Here is a function to set blue LED (PC7) on by using core library to write memory mapped I/O registers. Note that this will overwrite the current register value without reading it first.

use core::ptr; fn set_blue_led_on() { // Write directly to the memory mapped I/O register address unsafe { // GPIOC peripheral is memory mapped to this address const GPIOC_ADDR: u32 = 0x48000800; const GPIOC_MODER: u32 = GPIOC_ADDR + 0x00; const GPIOC_ODR: u32 = GPIOC_ADDR + 0x14; const RCC_AHBENR: u32 = 0x40021000 + 0x14; // Enable clock for GPIOC peripheral ptr::write_volatile(RCC_AHBENR as *mut u32, 1 << 19); // Set MODERC for pin output ptr::write_volatile(GPIOC_MODER as *mut u32, 1 << 14); // and set pin value ptr::write_volatile(GPIOC_ODR as *mut u32, 1 << 7); } }

If I want to use higher abstraction level, the next option would be to use PAC crate. To use it, I added the following code to the Cargo.toml which will also enable features for my MCU variant:

[dependencies.stm32f0] version = "0.15.1" features = ["stm32f0x2"]

Then I added a function to set red LED (PC6) on. Note that I used modify function here to update register value and to keep other LEDs also on (and not to overwrite the existing register value).

use stm32f0::stm32f0x2; fn set_red_led_on() { // Get pointer to the GPIO and Reset and Clock Control let mut peripherals = stm32f0x2::Peripherals::take().unwrap(); let gpioc = &peripherals.GPIOC; let rcc = &peripherals.RCC; // Enable the clock for GPIOC peripheral rcc.ahbenr.write(|w| w.iopcen().set_bit()); // Set PC6 pin to output mode gpioc.moder.modify(|_, w| w .moder6().output() ); // Set PC6 pin to high state (set red LED on) gpioc.odr.modify(|_, w| w .odr6().set_bit() ); }
MODER register updated with modify
Figure: MODER register updated with modify

The last thing I will try is to use the board level crate stm32f072b-disco. The following code is a clip from the board crate example code, I modified the code to control the green LED only instead of toggling all LEDs in turn.

stm32f072b-disco = "0.2.1" #[dependencies.stm32f0] #version = "0.15.1" #features = ["stm32f0x2"]
use stm32f072b_disco as board; use board::hal::{prelude::*, stm32}; use board::{green}; use cortex_m::peripheral::Peripherals; fn set_red_green_on() { if let (Some(mut p), Some(cp)) = (stm32::Peripherals::take(), Peripherals::take()) { cortex_m::interrupt::free(|cs| { let mut rcc = p.RCC.configure().sysclk(8.mhz()).freeze(&mut p.FLASH); let gpioc = p.GPIOC.split(&mut rcc); let mut green = green!(gpioc, cs); green.set_high(); }); } }

Conclusion

I have to admit that I need more practise with Rust before being very efficient. For me, I think those lower level crates were the most fluent way to access MCU resources. But now I have a working development environment to continue learning and developing rusty embedded applications.

References