Here is a detailed breakdown of the technical concepts discussed in the text:
1. The UI Framework: "Buoyant"
The author is using a library called Buoyant, which appears to be a declarative UI framework similar to SwiftUI or Jetpack Compose, but designed for embedded systems.
- Declarative Structure: The code uses
VStack(Vertical Stack) andHStack(Horizontal Stack) to define layout, rather than absolute X/Y coordinates. - State Management (Lenses): The use of
Lenssuggests a way to "zoom in" on a specific part of the global state (e.g., the PIN digits) and provide a mutable reference to it for a specific component without passing the entire state object around. - The "Type Explosion" Problem: The author highlights a common issue in Rust when nesting many generics. Because every modifier (padding, color, etc.) wraps the previous component in a new type, the resulting type for the rest of the screen becomes a "monster" (as seen in the 150-line type definition). This leads to binary bloat because the compiler generates specific code for every single unique combination of these types.
2. Solving the "Flicker" (The Quadtree Solution)
Most modern displays use a framebuffer (a slice of RAM that holds every pixel), which is then pushed to the screen. However, this MCU only has 32KB of RAM, which is far too small for a full-color framebuffer.
The Problem: Without a framebuffer, the MCU must send drawing commands directly to the display. If you redraw the whole screen every frame, the display flickers because the "erase" and "redraw" steps are visible.
The Solution: A Quadtree-based Dirty Region Tracking system.
- Dirty Tree: Tracks areas of the screen that have changed and must be redrawn.
- Overdrawn Tree: Tracks areas that were just drawn over by a larger component (meaning anything beneath them doesn't need to be redrawn).
- Why a Quadtree? A single bounding box for all changes is inefficient if two small things change on opposite corners of the screen (it would force the entire middle to redraw). A quadtree allows the system to isolate multiple small "dirty" rectangles, minimizing the data sent over the SPI/I2C bus.
3. The Hardware "Dance" (Clock & CAN Bus)
The most "low-level" part of the post describes a nightmare scenario in embedded programming: Bootloader Interference.
- The Conflict: The bootloader (the first piece of code that runs) initialized the system clocks. When the main application took over, it tried to set the clocks again.
- The Bug: In many MCUs, you cannot change the clock divisor or source while the clock is actively running. Because the bootloader left the clocks "on," the application's clock configuration failed silently.
- The Symptom: The CAN bus (Controller Area Network, used for communication between the scooter's motor controller and display) failed. Since CAN bus timing is extremely sensitive to the clock frequency, the incorrect clock caused "framing errors."
- The Fix: The author had to write a "tear down" sequence. Before configuring the clocks, they manually reset the
CRM(Clock Reset and Management) registers and disabled peripherals to return the hardware to a "clean slate," allowing the clock configuration to actually take effect.
Summary of Trade-offs
| Feature | Benefit | Cost/Trade-off |
|---|---|---|
| Declarative UI | Rapid development, easy layout | Massive binary size (code bloat) |
| Quadtree Redraw | No flickering, low RAM usage | Complexity in the UI engine |
| Rust Language | Memory safety, strong typing | Steep learning curve for generic types |
| Custom Firmware | Total control over hardware | Must manually handle bootloader clock conflicts |
Groetjes,