A simple embedded Rust project running on the microbit v2, built with Embassy async framework and no_std runtime.
FREE Reverse Engineering Self-Study Course HERE
- 5x5 LED Matrix Display: Async display driver with custom fonts and animations
- Dual Button Support: Button A and Button B with debouncing
- Motion Sensing: LSM303AGR accelerometer and magnetometer support
- Audio: Speaker and microphone interfaces
- Bluetooth Low Energy: Optional BLE support with trouble stack
- GPIO Access: All edge connector pins available (P0-P20)
- Communication: UART, I2C, SPI peripheral access
- Timers & PWM: Multiple timer and PWM channel support
- Async/Await: Built on Embassy's cooperative scheduler
- No Heap: Runs entirely in static memory with deterministic behavior
src/display/
: 5x5 LED matrix driver with fonts and bitmap supportexamples/
: Various example applications demonstrating features
Demonstrates LED matrix control with button interactions:
cd examples/display
cargo run --release
-
Startup
- The nRF52833 boot ROM loads your program from flash memory
- The Cortex-M
cortex-m-rt
runtime (#[no_main]
) bypasses traditionalmain()
- The reset vector jumps to
__cortex_m_rt_main_trampoline
- Embassy executor is initialized and starts the async runtime
-
Board Initialization
embassy_nrf::init()
configures clocks, GPIO, and peripherals- The
Microbit::default()
creates instances of all peripherals:- 5x5 LED matrix (rows: P0_21, P0_22, P0_15, P0_24, P0_19)
- Button A (P0_14) and Button B (P0_23)
- Speaker (P0_00), Microphone (P0_05)
- All edge connector pins (P0-P20)
- Internal I2C for accelerometer/magnetometer
-
Executor Task Management
- Embassy's executor uses a lock-free task queue for cooperative scheduling
- Tasks are enqueued when spawned or when wakers are triggered
- The executor polls tasks in FIFO order
- When all tasks are pending, CPU enters WFI (Wait-For-Interrupt) for power efficiency
-
Peripheral Abstractions
- LED Matrix: Time-multiplexed 5x5 display with async frame timing
- Buttons: GPIO inputs with internal pull-ups, async edge detection
- Motion Sensors: I2C communication with LSM303AGR via async interface
- Audio: PWM-based speaker control and ADC microphone sampling
- BLE: Optional Bluetooth stack integration with async event handling
-
Async Event Handling
- GPIO interrupts trigger task wakers for button presses
- Timer interrupts handle display refresh and delays
- I2C/SPI interrupts manage sensor communication
- All operations are non-blocking, allowing concurrent execution
-
Memory Management
- Static allocation only - no heap fragmentation
- Compile-time memory layout with predictable behavior
- Embassy's static task allocation ensures deterministic performance
- Enqueue Operation: Tasks are added to the tail of a bounded queue when spawned or awakened
- Dequeue Operation: Executor pops tasks from the head (FIFO) for polling
- Cooperative Scheduling: Tasks must yield (await) to allow others to run
- Waker System: Peripheral interrupts trigger task re-scheduling via wakers
Flash Memory:
├── Vector Table (0x00000000)
├── Program Code
├── Static Data
└── Embassy Runtime
RAM Memory:
├── Task Queue (statically allocated)
├── Task Control Blocks
├── Stack Space
└── Peripheral Buffers
- GPIOTE: Button press/release detection
- RTC: Timer-based delays and scheduling
- TWI: I2C sensor communication
- PWM: Audio output generation
- RADIO: Bluetooth communication (optional)
Function | Pin | Description |
---|---|---|
LED Matrix Rows | P0_21, P0_22, P0_15, P0_24, P0_19 | Row drivers |
LED Matrix Cols | P0_28, P0_11, P0_31, P1_05, P0_30 | Column drivers |
Button A | P0_14 | Pull-up enabled |
Button B | P0_23 | Pull-up enabled |
Speaker | P0_00 | PWM audio output |
Microphone | P0_05 | ADC input |
Mic Enable | P0_20 | Microphone power |
Internal I2C SCL | P0_08 | Accelerometer/Magnetometer |
Internal I2C SDA | P0_16 | Accelerometer/Magnetometer |
Connector | Pin | GPIO | Description |
---|---|---|---|
P0 | Large | P0_02 | General purpose I/O |
P1 | Large | P0_03 | General purpose I/O |
P2 | Large | P0_04 | General purpose I/O |
P8 | Small | P0_10 | General purpose I/O |
P9 | Small | P0_09 | General purpose I/O |
P12 | Small | P0_12 | General purpose I/O |
P13 | Small | P0_17 | General purpose I/O |
P14 | Small | P0_01 | General purpose I/O |
P15 | Small | P0_13 | General purpose I/O |
P16 | Small | P1_02 | General purpose I/O |
P19 | Small | P0_26 | General purpose I/O |
P20 | Small | P1_00 | General purpose I/O |
Software:
- Rust toolchain with
thumbv7em-none-eabihf
target probe-rs
for flashing and debuggingrustup
for Rust installation
Hardware:
- BBC micro:bit v2
- USB cable for power and programming
- Optional: External debugger probe for advanced debugging
# Install Rust if not already installed
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Add the target for micro:bit
rustup target add thumbv7em-none-eabihf
# Install probe-rs
cargo install probe-rs-tools --locked
# Build the library
cargo build
# Build with optimizations
cargo build --release
# Build specific example
cd examples/display
cargo build --release
# Flash example to micro:bit
cd examples/display
cargo run --release
# Flash with probe-rs directly
probe-rs run --chip nRF52833_xxAA target/thumbv7em-none-eabihf/release/display-example
# Flash and attach debugger
probe-rs run --chip nRF52833_xxAA --attach-under-reset target/thumbv7em-none-eabihf/release/display-example
# Start GDB session
probe-rs gdb --chip nRF52833_xxAA target/thumbv7em-none-eabihf/release/display-example
# View defmt logs
probe-rs run --chip nRF52833_xxAA target/thumbv7em-none-eabihf/release/display-example
Feature | Description | Dependencies |
---|---|---|
default |
Basic functionality with defmt logging | defmt |
defmt |
Logging and debugging support | defmt crates |
trouble |
Bluetooth Low Energy support | nrf-sdc, nrf-mpsl, static_cell |
Enable features in Cargo.toml:
[dependencies]
microbit-bsp = { version = "0.4.0", features = ["trouble"] }
use microbit_bsp::*;
use embassy_time::Duration;
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let board = Microbit::default();
let mut display = board.display;
// Show a heart pattern
let heart = display::bitmap![
[0, 1, 0, 1, 0]
[1, 1, 1, 1, 1]
[1, 1, 1, 1, 1]
[0, 1, 1, 1, 0]
[0, 0, 1, 0, 0]
];
display.display(heart, Duration::from_secs(2)).await;
}
use embassy_futures::select::{select, Either};
loop {
match select(board.btn_a.wait_for_low(), board.btn_b.wait_for_low()).await {
Either::First(_) => {
// Button A pressed
display.display(display::fonts::ARROW_LEFT, Duration::from_millis(500)).await;
}
Either::Second(_) => {
// Button B pressed
display.display(display::fonts::ARROW_RIGHT, Duration::from_millis(500)).await;
}
}
}
- Rust: Nightly channel (for async embedded features)
- Target: thumbv7em-none-eabihf (Cortex-M4F with hardware FPU)
- Hardware: BBC micro:bit v2 with nRF52833 SoC
- Memory: 512KB Flash, 128KB RAM
- Clock: 64MHz ARM Cortex-M4F with FPU
Apache-2.0 License
- Embassy Framework - Embedded async executor
- microbit BSP - microbit BSP w/ Embassy
- BBC micro:bit v2 Datasheet - Hardware specifications
- nRF52833 Product Specification - Nordic SoC documentation
- Rust Embedded Book - Embedded Rust programming guide
- probe-rs Documentation - Debugging and flashing tool