Embedded Rust: Timer Timeout Problem

TL;DR: When doing timing critical stuff, use the --release flag to get a faster binary!
For example: cargo embed --release.

I’m learning Embedded Rust on a STM32 Bluepill board (with a STM32F103 microcontroller). At the time of writing there seems to be two toolchain options:

  1. The “official” Embedded Rust way, using OpenOCD and ARM-compatible GDB.
  2. Up-and-coming probe-rs that is working on having everything in Rust and installable via cargo. Their tool cargo-embed basically replaces OpenOCD and GDB.

OpenOCD + GDB is true and tested, but a lot more work to set up. Probe-rs is litteraly just cargo install cargo-embed, but it is a work in progress and far from feature-complete. I tried both, but this particular thing caught me while using cargo-embed, so that’s the command I will be showing.

The Timer Problem

I wanted to talk to a ws2812 adressable RGB LED (also known as NeoPixel). I found the crate smart-leds that seemed perfect. It comes with several “companion crates” with device drivers that support different LEDs and several options for different ways of driving the ws2812, like the ws2812-spi and ws2812-timer-delay.

The SPI crate unfortunately did not work in my attempts so far. It manages to write to my LED once, then panics with the error “Overrun”. Probably I’m using a newer version of the embedded-hal and/or stm32f1xx-hal than it was written for. Maybe a topic for another day.

The Timer Delay crate also did not work at first. I broke out my Analog Discovery 2 to look at the data signal:

Delta-X column below the graph shows the time between the red lines – right above 200 us.

The time between bits was around 200 us. To get a comparison, I fired up a Platformio project for the same STM32 Bluepill board and imported Adafruit’s Neopixel library. Now, the LED of course worked perfectly and the problem was obvious:

With Adafruit’s Arduino library, the time between pulses is around 1.4 us.

The time between the bits was now only around 1,4 us. I will spare you the details of all the things I tried while wrongly thinking either the entire MCU or the timer was running at the wrong frequency.

The solution turns out to be almost silly: Rust binaries can be really slow if you do not compile them in release mode. Just add the --release flag and all is well! 💩

Solution:

cargo embed --release

There is apparently a way to override this per-dependency in Cargo.toml, that might be worth a try if you need it.

Update:

I tried adding the following to Cargo.toml to make all dependencies build with the highest optimization level, but this still was not enough to make the LED work in my case.

# Cargo.toml 
[profile.dev.package."*"]
opt-level = 3

I also tried increasing the optimization level for the whole dev profile. This worked already from level 2:

# Cargo.toml 
[profile.dev]
opt-level = 2

Stepping through code compiled like this with a debugger might not work as well though so you might as well use the release profile all the time and only drop down to dev for debugging.


Comments

One response to “Embedded Rust: Timer Timeout Problem”

  1. […] recently started learning Embedded Rust. As I mentioned at the top of the last post, there are a couple of toolchain […]

Leave a Reply

Your email address will not be published. Required fields are marked *

Are you a robot? * Time limit is exhausted. Please reload CAPTCHA.

This site uses Akismet to reduce spam. Learn how your comment data is processed.