I recently began working through some tutorials on embedded Rust, beginning with the discovery tutorial for the microbit (v2). In this article, I wanted to share a bit about what I learned and the hurdles I encountered.
The biggest roadblock along the way on this journey was that the tutorial relies on gdb as a debugger. Gdb is not supported on Apple silicon. Most discussions of this fact end with the glib advice, “Use lldb”. Well, OK, I tried that of course. Although I found some good gdb to lldb cheat sheets, I wasn’t able to get lldb to debug the rust target. (I’m apparently not the only one — this thread echoes many of the errors I bumped into).
It appears that gdb is the path of least resistance for almost all embedded work, so if you’re doing that sort of thing and shopping for hardware, you might consider a Linux box instead. I’m going to try this exercise on such a machine shortly, but I wanted to document some results on the Mac first.
Other Than That, Mrs. Lincoln, How Did You Enjoy the Play?
Outside of the debugger issue, there were only some minor issues with the tutorial. I did the setup steps for Mac as documented.
$ # Arm GCC toolchain
$ brew tap ArmMbed/homebrew-formulae
$ brew install arm-none-eabi-gcc
$ # Minicom
$ brew install minicom
In addition to these, I glossed over a few tutorial steps the first time through, so let me mention these.
For the 03-setup section, I first had to make sure I had cargo-embed available.
cargo install cargo-embed
One additional caveat. (Maybe you’re smart enough not to do the following thing, but I wasn’t, so let me mention it.)
It’s important to only use “cargo install” for this. If you add it to your Cargo.toml file using “cargo add” the ARM target won’t build because cargo-embed will not build in the presence of #![no_std]
.
In the 05-led-roulette section, in order to get cargo readobj
to work, I had to run the following command:
rustup component add llvm-tools-preview
Changes to Embed.toml
Because I wasn’t using gdb, I also had to make some changes to Embed.toml. Here’s the file I used:
[default.general]
chip = "nrf52833_xxAA" # uncomment this line for micro:bit V2
# chip = "nrf51822_xxAA" # uncomment this line for micro:bit V1
[default.reset]
halt_afterwards = false
[default.rtt]
enabled = false
[default.gdb]
enabled = false
The main change here was under default.gdb: setting enabled to false since I wanted the process to flash and to the microbit without stopping in the debugger. I discovered the default.reset halt_afterwards
setting later. By switching this to false, I avoided having to press the restart button on the back of the microbit each time after flashing to start the program.
The Challenge Code
With these steps in place, I was able to get my LEDs to flash as per the challenge. Here are the video, the command to flash it to the microbit, and the code.
To flash the code, I used this command (which is why the cargo-embed crate was needed earlier):
cargo embed --features v2 --target thumbv7em-none-eabihf
Here’s the code for this (from main.rs).
#![deny(unsafe_code)]
#![no_main]
#![no_std]
use cortex_m_rt::entry;
use rtt_target::rtt_init_print;
use panic_rtt_target as _;
use microbit::{
board::Board,
display::blocking::Display,
hal::{prelude::*, Timer},
};
// Return an array representing the lit LEDs.
fn get_array(current: usize) -> [[u8; 5]; 5] {
// Which one is lit depends on where we are as
// we go around the outer edge.
let indices = [
(0,0),
(0,1),
(0,2),
(0,3),
(0,4),
(1,4),
(2,4),
(3,4),
(4,4),
(4,3),
(4,2),
(4,1),
(4,0),
(3,0),
(2,0),
(1,0)
];
let (x, y) = indices[current];
// All off for now but...
let mut leds = [
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
];
// ...set the lit one
leds[x][y] = 1;
leds
}
#[entry]
fn main() -> ! {
rtt_init_print!();
let board = Board::take().unwrap();
let mut timer = Timer::new(board.TIMER0);
let mut display = Display::new(board.display_pins);
// There are sixteen LEDs total, so we increment to 14, inclusive
// to get zero-based 15 index
const MAX_TO_INC: usize = 14;
let mut index: usize = 0;
loop {
let lights = get_array(index);
// Show light_it_all for 1000ms
display.show(&mut timer, lights, 100);
// clear the display again
display.clear();
timer.delay_ms(20_u32);
// Increment
match index {
0..=MAX_TO_INC => index = index +1,
_ => index = 0,
}
}
}
You’ll also need a hidden .cargo/config file to make this work. To get the full source code, see this embedded project directory on GitHub.
Enjoy!