Embedded Rust Stm32f4xx Hal

Embedded Rust

I have been using Rust on and off for some time now and i have to say that my appreciation for the language keeps growing. Additionally i finaly managed to return to my hobby which is embedded electronics. I decided however to use Rust for this instead of good old C. I'm not going to be repeating all the good stuff Rust has that make it a gread language, if someone reads this then you are most likely familiar with it. All the great Rust features are equally usefull on the system that lack OS, the embedded systems, or bare-metal.

Getting started

Embedded Rust is still very new concept and the enviroment definitely cannot be called mature. There is however a bunch of resorces to get started with embedded. The purpose of this post is to hopefully expand the reach of embedded Rust for larger audience. The very first resource to check when starting the adventure with embedded and Rust should be The Embedded Rust Book. It covers in great detail all the basic informations on how to start and prepare your dev enviroment.

In there you will find another great resource which is The Discovery Book where you can start some hands on exercises with controling peripherals on a stm32 ARM microcontroller.

All those resources are authored by the driving force behind the embedded Rust Jorge Aparicio

embedded-hal

Rust by its nature allows to create a abstract, platform agnostic drivers. There is a framework called embedded-hal which offers a bunch interfaces (based on Traits) that allow a straighforward interaction with the microcontroller peripherals. HAL stands for Hardware Apstraction Layer and it is a method of interacting with system on a layer that is just above the "drivers layer".

The actual HAL one would write for a specific chipset such as stm32f401, stm32f103, LPC4300, etc but every HAL would expose the same interface that is defined within the embedded-hal

Enter stm32f4xx-hal

The board i had available for testing was the STM32F401-DISCO: Its a old but great board with the stm32f4 ARM chip, 4 LEDs, LSM303DLHC 3D accelerometer and L3GD20 3 axis gyroscope. There is already an stm32f4xx-hal available for the stm32f4 ARM series.

We can start straight away with a simple LED blink. STM32F401-DISCO has 4 LEDs attached to the PD port. The LED mapping is as follows:

  • green --> PD12
  • orange --> PD13
  • red --> PD14
  • blue --> PD15

Configuring the pin as output is as simple as:

let mut led_green = gpiod.pd12.into_push_pull_output();
led_green.set_high();

Flash the LED

No embedded programming tutorial can be complete without a full LED flashing example. In order to do so we will need some additional helpers like a delay. The stm32f4xx-hal comes with a delay module that will allow us to easily specify a delay with a determined duration.

use cortex_m_rt::entry;
use stm32f4xx_hal as hal;

use hal::delay::Delay;
use hal::rcc::Clocks;
use hal::stm32;


#[entry]
fn main() -> ! {
    let cp = cortex_m::Peripherals::take().unwrap();
    let syst = cp.SYST;

	let p = stm32::Peripherals::take().unwrap();
    let rcc = p.RCC.constrain();

    let clocks: Clocks = rcc
        .cfgr
        .sysclk(64.mhz())
        .pclk1(32.mhz())
        .pclk2(64.mhz())
        .freeze();

    let gpiod = p.GPIOD.split();
    let mut delay = Delay::new(syst, clocks);

    let mut led_green = gpiod.pd12.into_push_pull_output();

    loop {
        led_red.set_high();
        delay.delay_ms(500u16);
        led_red.set_low();
        delay.delay_ms(500u16);
    }
}

The above example is sligtly longer but quite straigh forward. Lets analyze it step by step:

	let cp = cortex_m::Peripherals::take().unwrap();
	let syst = cp.SYST;

We start with obtaining the core stm32 ARM peripherals to obtain the handle on the SYST struct which is going to be used to create out delay object. The Delay uses the SysTick timer internaly to provide the required delay functionality.

	let p = stm32::Peripherals::take().unwrap();
    let rcc = p.RCC.constrain();

    let clocks: Clocks = rcc
        .cfgr
        .sysclk(64.mhz())
        .pclk1(32.mhz())
        .pclk2(64.mhz())
        .freeze();

We then proceed to obtain the handle of the stm32f4 specific peripherals. Once we can access the RCC register we can configure the internal BUS and clock speeds. The sysclk is the internal main clock from which all the other clocks are derived. The pclk1 and pclk2 are the clocks that feed the stm32 peripherals. The freeze() method builds and applies the configuration. The resulting Clocks object can be used for configuring several peripherals on the chip such as I2C, UART, etc.

let mut delay = Delay::new(syst, clocks);

Since we now have both: clocks and syst we can now create the delay object.

    let gpiod = p.GPIOD.split();
    let mut led_green = gpiod.pd12.into_push_pull_output();

Just as before we need to configure the pin to which the LED is connected to act as output pin which we can use to drive the LED.

loop {
       led_red.set_high();
       delay.delay_ms(500u16);
       led_red.set_low();
       delay.delay_ms(500u16);
   }

Now we can just simply combine our new skill of creating the delays and controling the state of the pin to create this beautiful LED ON -> LED OFF pattern.

Please notice that the loop never terminates so that we stay true to what we promised the Rust compiler by specifying the main function as:

fn main() -> ! {}

The ! means that the function will never return. Which very important in the bare-metal system as there is no OS to return to.

Conclusion

This is just a simple demonstration of the stm32f4xx-hal crate. I hope to write additional posts about some more stuff related to embedded Rust in the near future.