Files
microbadge/docs/sdd.md
Myrddin Dundragon 1c593aff9b Initial pass on the design documentation.
The design doc and software design doc.
The UML images are generated with plantUML from the *.puml files.
2025-07-18 12:14:01 -04:00

244 lines
8.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 users 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 badges 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 the `App` 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 of `Position` 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
1. User presses a button.
2. The button task sends a message to the channel.
3. The app reads the button event from the channel.
4. The app updates internal state (e.g., direction or selection).
5. The app prepares a frame and sends it to the frame channel.
6. 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
04 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.
### 9.3 UI Polish