A stethoscope for an open-source heart.
"You can't trust what you can't measure."
HW Tools is a hardware diagnostic utility for the Precursor platform running Xous OS. It combines a System Monitor, GPIO Tester, and UART Monitor into a single tabbed application that gives you real-time visibility into what your hardware is actually doing. Battery voltage, charge current, die temperature, GPIO pin states, serial traffic -- all live, all on-device, no external tools required.
This is not a benchmark. It is not a stress test. It is an instrument panel for a machine whose blueprints are public but whose runtime behavior is opaque without something to observe it. HW Tools turns the invisible into the legible.
bunnie Huang built Precursor so you could audit the hardware. The schematics are open. The FPGA gateware is open. The bootloader, the kernel, the operating system -- all open, all auditable. His book The Hardware Hacker makes the case that open hardware is a prerequisite for trust: if you cannot inspect the system, you cannot know what it is doing.
But open hardware means nothing if you cannot observe it running.
Schematics tell you what the hardware should do. Firmware tells you what the software intends to do. Only measurement tells you what the system is doing. HW Tools extends the audit principle from design-time to runtime. You can read the schematic for the battery charge controller, but can you see the charge current right now? You can review the GPIO pin mux logic in the FPGA source, but can you confirm which pins are actually asserting? You can trace the UART path through the kernel, but can you see what bytes are crossing the wire?
Open hardware demands open diagnostics. HW Tools is Precursor's instrument cluster -- the runtime half of the trust equation.
Precursor's constraints shaped this app in ways that matter.
The COM service mediates all communication with the embedded controller (EC), which owns the battery gas gauge, power management, and sensor subsystems. There is no direct register access from the SoC -- every measurement is an IPC round-trip through the COM server, which polls the EC over an internal SPI bus. The telemetry refresh rate is bounded by this pipeline, not by display speed.
The llio API exposes GPIO direction control but not output data registers directly. Setting a pin to output mode is straightforward; driving a specific logic level requires working within the API's current surface. The GPIO tester maps what is possible today and documents what is not yet exposed.
Renode versus real hardware creates a persistent gap. The emulator faithfully models the SoC and can run the app, but COM service responses for battery data, sensor readings, and ADC values are either stubbed or absent. HW Tools is most useful on real Precursor hardware, where the measurements correspond to physical reality. In Renode, the UI renders but the numbers are placeholders.
These are not complaints. They are the boundaries of the system, and boundaries are what make engineering possible.
Real-time hardware telemetry and system information, refreshed every 2 seconds via a pump thread:
| Category | Data Points |
|---|---|
| Battery | Voltage, charge %, current draw, remaining capacity |
| Charging | Status indicator with visual progress bar |
| EC Info | Uptime, git revision, firmware version |
| SoC Info | Git revision, unique DNA identifier |
| ADC | VBUS voltage, die temperature, VccInt, VccAux |
| Sensors | Gyroscope/accelerometer X/Y/Z values |
Keyboard:
| Key | Action |
|---|---|
r |
Force refresh all stats |
Monitor and control the 8 GPIO pins exposed in the battery compartment:
| Pin | ADC | Description |
|---|---|---|
| 0 | No | General purpose I/O |
| 1 | No | General purpose I/O |
| 2 | Yes | ADC capable -- voltage measurement |
| 3 | No | General purpose I/O |
| 4 | No | General purpose I/O |
| 5 | Yes | ADC capable -- voltage measurement |
| 6 | No | General purpose I/O |
| 7 | No | General purpose I/O |
Features:
- Direction control (input/output) for all 8 pins
- Analog voltage measurement for pins 2 and 5
- Visual selection cursor and pin state display
- Settings persistence across sessions
Keyboard:
| Key | Action |
|---|---|
0-7 |
Toggle GPIO pin (or select and set to output) |
i |
Set selected pin to input mode |
o |
Set selected pin to output mode |
↑ / ↓ |
Select pin (cursor) |
Space |
Toggle selected output pin |
a |
Show ADC value (pins 2 and 5 only) |
Serial interface monitoring and control:
- Mux Selector: Switch between Kernel, Log, and Application UARTs
- Traffic Log: Timestamped message display with scroll support
- TX Input: Text input field for sending data
- Pause/Resume: Control log capture
Keyboard:
| Key | Action |
|---|---|
c |
Clear log buffer |
p |
Pause/Resume capture |
m |
Cycle UART mux (Kernel, Log, App) |
↑ / ↓ |
Scroll log |
Enter |
Send TX buffer |
Backspace |
Delete character |
| Type | Add characters to TX buffer |
| Key | Action |
|---|---|
1 / 2 / 3 |
Switch to tab 1/2/3 |
← / → |
Previous/Next tab |
q |
Quit application |
apps/hwtools/
├── Cargo.toml # Dependencies: xous, gam, llio, com, pddb, ticktimer
└── src/
├── main.rs # Entry point, pump thread, event loop, focus management
├── app.rs # Tab state machine, rendering dispatch, key routing
├── system_tab.rs # COM/llio telemetry queries, system info display
├── gpio_tab.rs # GPIO direction/state control, ADC readout
├── uart_tab.rs # UART mux switching, RX log buffer, TX input
├── storage.rs # PDDB settings persistence (binary format)
└── ui.rs # Shared drawing helpers, screen clear
Chat UX (UxType::Chat): Uses the standard managed canvas rather than raw framebuffer. Text-heavy diagnostic output benefits from the GAM's text rendering pipeline. Tab headers use rounded rectangles with inverted text for the active tab.
Pump thread pattern: A dedicated thread sends blocking scalar messages to the main loop at 2-second intervals, triggering telemetry refresh. The main loop acknowledges each pump, providing natural backpressure. The pump thread is paused when the app goes to background and resumed on foreground -- no wasted cycles reading hardware that nobody is watching.
Tab state machine: Three tabs (System, Gpio, Uart) with a simple enum-based state machine. Each tab module owns its own data, drawing, and key handling. The app.rs coordinator dispatches to the active tab and manages transitions. Tab switching triggers a full redraw; periodic refreshes only redraw the content area.
Binary settings format: Rather than JSON or any text serialization, settings are stored as a compact 9-byte binary blob: [last_tab, gpio_directions, gpio_outputs, uart_mux, auto_refresh, refresh_interval_ms(4)]. This minimizes PDDB overhead and parse complexity.
UART Mux Modes:
| Mode | Baud | Description |
|---|---|---|
| Kernel | 115200 | Debug output from kernel |
| Log | 115200 | Log server output (default) |
| Application | 115200 | Application UART (power-sensitive) |
ADC Specifications:
| Channel | Range | Resolution |
|---|---|---|
| VBUS | 0-5V | 12-bit |
| Temperature | ~0-100C | Approximate |
| VccInt | 0-3V | 12-bit |
| VccAux | 0-3V | 12-bit |
| GPIO 2/5 | 0-3.3V | 12-bit |
All settings stored under dictionary hwtools.settings, key config:
| Field | Byte Offset | Size | Persistence |
|---|---|---|---|
last_tab |
0 | 1 byte | Per session |
gpio_directions |
1 | 1 byte (bitmask) | Across sessions |
gpio_outputs |
2 | 1 byte (bitmask) | Across sessions |
uart_mux |
3 | 1 byte | Across sessions |
auto_refresh |
4 | 1 byte (bool) | Across sessions |
refresh_interval_ms |
5-8 | 4 bytes (u32 LE) | Across sessions |
Total footprint: 9 bytes.
| Feature | Status | Notes |
|---|---|---|
| GPIO Output | Direction only | gpio_data_out() not exposed in llio API |
| GPIO Input | Pins 2 and 5 only | Other pins require direct register access |
| UART RX | Placeholder | Bidirectional serial requires kernel support |
| Temperature | Approximate | Raw ADC conversion, not calibrated |
| Gyroscope | Raw values | Accelerometer readings, not calibrated |
HW Tools is a Xous app. It builds as part of the xous-core workspace.
-
Clone into xous-core:
cd xous-core/apps git clone https://github.com/tbcolby/precursor-hwtools.git hwtools -
Add to workspace
Cargo.toml:"apps/hwtools", -
Add to
apps/manifest.json:"hwtools": { "context_name": "HW Tools", "menu_name": { "appmenu.hwtools": { "en": "HW Tools", "en-tts": "Hardware Tools" } } }
-
Build for Renode emulator:
cargo xtask renode-image hwtools
-
Build for hardware:
cargo xtask app-image hwtools
-
Build with XIP (execute-in-place, saves RAM):
cargo xtask app-image-xip hwtools
After flashing, the app appears in Menu -> Switch to App -> HW Tools.
[dependencies]
xous = "0.9.69"
xous-ipc = "0.10.9"
gam = { path = "../../services/gam" }
llio = { path = "../../services/llio" }
com = { path = "../../services/com" }
pddb = { path = "../../services/pddb" }
ticktimer-server = { package = "xous-api-ticktimer", version = "0.9.68" }
log-server = { package = "xous-api-log", version = "0.1.68" }
xous-names = { package = "xous-api-names", version = "0.9.70" }
xous-semver = "0.1.5"
num-derive = { version = "0.4.2", default-features = false }
num-traits = { version = "0.2.14", default-features = false }
log = "0.4.14"Screenshots will be added after Renode capture.
This app was developed using the methodology described in xous-dev-toolkit -- an LLM-assisted approach to Precursor app development on macOS ARM64.
Made by Tyler Colby -- Colby's Data Movers, LLC
Contact: tyler@colbysdatamovers.com | GitHub Issues
Licensed under the Apache License, Version 2.0.
See LICENSE for the full text.