The design doc and software design doc. The UML images are generated with plantUML from the *.puml files.
8.4 KiB
MicroBadge Software Design Document
1. Introduction
1.1 Purpose
MicroBadge is a software application suite for the BBC micro:bit v2, designed as a digital conference badge. It serves both functional and social purposes; displaying the user’s name, hosting small interactive demos, and offering contact sharing via NFC.
This project serves as a conversation starter and technical showcase during events like conferences, meetings, and interviews.
1.2 Scope
This document focuses exclusively on the software implementation of MicroBadge.
It covers the architecture, data structures, behavior, and design choices used
to implement the badge’s app-switching system and core applications using Rust
and the embassy
async runtime.
1.3 Audience
This document is intended for:
- Reviewers evaluating its design.
- Recruiters or interviewers reviewing technical work.
- Anyone trying to learn how to write Rust on an embedded platform.
2. System Overview
MicroBadge is an embedded application for the micro:bit v2. It uses the Embassy async runtime to manage multiple cooperative tasks without a traditional RTOS. The system is modular and consists of an app switcher, an LED display task, button listeners, and multiple interactive apps.
2.1 Runtime and Concurrency
MicroBadge uses Embassy's async executor. It runs the following tasks:
display_task
-- Consumes frame buffers and drives the LED matrix.button_listener
-- One per button (A, B, Start). Waits for input and debounces it before sending an event.app_task
-- Runs the currently selected app. Allows apps to yield and re-enter on each loop.
All communication is channel-based using embassy_sync::channel::Channel
.
[Task UML][./uml/tasks.png]
2.2 App Switcher
The Switcher
manages app selection and transition. It displays a menu and
uses the A, B, and Start buttons to navigate between apps.
Each app implements a shared App
trait with an async run()
method.
Apps are isolated and run cooperatively, returning control when done.
Current apps:
- Menu. The top-level app that allows selecting from installed apps.
- Badge. Scrolls a string (e.g. your name) across the LED matrix.
- Snake. A basic snake game with food, direction control, and score.
- NFC Card (in development). Will present contact info via NFC.
[Switcher UML][./uml/switcher.png]
2.3 Input System
Each button is handled by a separate button_listener
task. When a button
is pressed, it sends a Button
enum into a shared channel.
Apps listen for button input using the receiver end of the channel.
- A and B buttons are mapped to actions like turn left and right.
- Start is used to confirm or start an app. It is mapped to the capacitive touch sensor logo.
- A debounce delay of 100 ms is used for stability.
2.4 Rendering System
The rendering system uses a frame buffer that is written by the active app
and read by the display_task
.
Apps write into this buffer using a Renderer
abstraction. Drawing is done
in an offscreen buffer that is later pushed to the display.
- The screen is a 5x5 LED grid.
- Per-frame updates allow for animations and dynamic content.
- LED brightness levels are supported.
2.5 Code Organization
The system is split into modules for clarity and reuse:
app
. Defines theApp
trait and shared app interface.display
. Low-level display driver and LED control.renderer
. Provides drawing primitives for apps.channel
. Shared async channels for button and frame messages.switcher
. App selection logic and switching behavior.snake
,menu
,badge
. App implementations.microbit
. Definitions for button identifiers and device pins.
Each module is self-contained and uses only the shared channels and traits for interaction.
3. Application Features
3.1 Name Scroller
- Scrolls a configured name across the LED display.
- Uses an async timer to advance frames.
- Simple input handling: Any Button returns to the menu.
3.2 Snake Game
- 5x5 LED grid snake game using a wrapped grid (
WrappedU8<0, 4>
). - Buttons A and B turn the snake left/right.
- Food spawns randomly in empty grid cells.
- On collision with self, enters game-over state and displays score.
3.3 NFC Business Card (WIP)
- Intended to broadcast a vCard or custom URI over NFC.
- Plan to use the BLE softdevice on the chip.
- Currently under development.
4. System Architecture
This system uses Embassy's async runtime to coordinate application execution, hardware input, and rendering on the micro:bit v2 board. It is divided into distinct tasks: input listeners, a display task, and an app task.
The overall architecture is message-passing oriented. Input events and screen updates are communicated over embassy channels.
Application logic is encapsulated in independent modules conforming to a shared
App
trait. The Switcher manages the active app and transitions between them.
4.1 Components
main.rs
: Entry point. Spawns system tasks using Embassy.Display
: Renders 5x5 LED frames from a channel receiver using PWM.ButtonListener
: Listens for button presses and sends events via channel.Switcher
: Manages app lifecycle and transitions.App
: Trait for any runnable application module.menu
,badge
,snake
,nfc
: App implementations.
5. Data Structures and State
5.1 Position, Direction, and Snake Body
The board is a fixed 5×5 grid. Positions are stored using a custom Position
struct, which holds a ClampedU8
for both x
and y
axes, ensuring values
remain within bounds.
Position
: Represents a coordinate on the board with safe bounds.Direction
: Enum for movement direction: Up, Down, Left, Right.Snake
: Maintains a list ofPosition
elements representing the snake's body. The first item is always the head.
Snake direction is updated via input, and movement wraps to stay within the board.
5.2 Message-Passing and Input State
User input is handled asynchronously via Embassy channels.
- Button presses are detected using
button_listener
tasks. - Events are sent to the
ButtonChannel
. - Applications read input non-blockingly using
try_receive()
.
This decouples physical input handling from application logic and allows clean, testable state transitions.
6. Component Interactions
6.1 How Components Interact Over Time
At runtime, three core tasks are running:
display_task
: Receives rendered frames and presents them on the display.button_listener
: Spawns three tasks, one per button (A, B, Start).app_task
: Owns the app switcher and runs the current app.
All interactions are asynchronous and use message-passing over embassy channels.
6.2 Flow of Control
- User presses a button.
- The button task sends a message to the channel.
- The app reads the button event from the channel.
- The app updates internal state (e.g., direction or selection).
- The app prepares a frame and sends it to the frame channel.
- The display task renders the frame.
This loop repeats, giving a responsive, concurrent embedded UI.
7. Development Environment
7.1 Rust + Embassy
This project uses Rust with the embassy
async runtime. It provides
interrupt-driven, non-blocking execution suitable for low-power embedded
devices.
7.2 Tools
probe-rs
: For flashing and debugging firmware.defmt
: Lightweight logging for embedded targets.panic-probe
: Panic handler integrated with defmt output.cargo-embed
: For development workflow and flashing.
Development was done on Linux using vim and CLI tooling.
8. Design Decisions
8.1 Why Embassy
Embassy was chosen for its async-first architecture, which maps well to reactive, event-driven embedded applications like games and UI. It allows multiple concurrent tasks without needing an RTOS or blocking code.
8.2 Fixed Board Size
The micro:bit's 5×5 LED matrix is inherently fixed. Game logic and rendering are simplified by using a constant-size grid, avoiding the need for dynamic allocation or scaling logic.
8.3 Data Wrapping and Clamping
Out-of-bounds positions are prevented using custom ClampedU8
types. These
provide safe arithmetic that prevents overflow and keeps all positions within
0–4 inclusive. This reduces bugs and runtime checks in critical loops.
9. Future Work
9.1 NFC Business Card App
An in-progress app will emulate a contact card via NFC. The goal is to allow devices to scan the badge and receive contact information, a URL, or a vCard.