Tag Archives: ws2812

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.