Initial pass on the design documentation.

The design doc and software design doc.
The UML images are generated with plantUML from the *.puml files.
This commit is contained in:
2025-07-18 12:14:01 -04:00
parent 1181759ace
commit 1c593aff9b
6 changed files with 367 additions and 0 deletions

243
docs/sdd.md Normal file
View File

@ -0,0 +1,243 @@
# 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