HALPI2 is a Boat Computer based on the Raspberry Pi Compute Module 5 (CM5), designed and manufactured by Hat Labs. Among other things, it features a RP2040 microcontroller ("Controller") that handles the power management, communication with the Raspberry Pi, and the control of the peripherals. The firmware for the RP2040 is written in Rust.
The firmware is implemented using Rust and the Embassy framework.
The controller GPIO pins are documented below.
GPIO # | Name | Description |
---|---|---|
0 | RGBLED | Data output for the five SK6805 (WS2812 style) RGB LEDs. |
1 | PCIe_LED | PCIe LED indicator. Active low. |
2 | PWR_BTN_IN | Input from the physical power button. Active low. |
3 | USER_BTN | Input from the user-defined button. Active low. |
4 | PCIESLEEP | Pull high to put the PCIe device to sleep. |
5 | GPIO05 | Connected to the GPIO header. Not used. |
6 | GPIO06 | Connected to the GPIO header. Not used. |
7 | GPIO07 | Connected to the GPIO header. Not used. |
8 | GPIO08 | Connected to the GPIO header. Not used. |
9 | PWR_BTN_OUT | Output to the CM5 power button pin. Active low. |
10 | LED_PWR | Power LED state from CM5. Active low. |
11 | LED_ACTIVE | Active LED state from CM5. Active low. |
12 | N/C | Not connected. |
13 | CM_ON | 3.3V output of the CM5. Used to detect the CM power state. |
14 | I2C1_SDA | I2C1 data line. CM5 is primary, the controller is secondary. |
15 | I2C1_SCL | I2C1 clock line. CM5 is primary, the controller is secondary. |
16 | TEST_MODE | Input to enable test mode. Pull-high, active low. |
17 | GPIO17 | Connected to the test pad. Not used. |
18 | PG_5V | Power Good input from the 5V buck converter. Active high. |
19 | VEN | Voltage Enable output for the 5V buck converter. Active high. |
20 | I2Cm_SDA | I2Cm data line. Controller is primary. |
21 | I2Cm_SCL | I2Cm clock line. Controller is primary. |
22 | DIS_USB3 | USB3 disable signal. Active high. |
23 | DIS_USB2 | USB2 disable signal. Active high. |
24 | DIS_USB1 | USB1 disable signal. Active high. |
25 | DIS_USB0 | USB0 disable signal. Active high. |
26 | VinS | Analog: Scaled input voltage level. |
27 | VscapS | Analog: Scaled supercap voltage level. |
28 | Iin | Analog: Input current level. |
29 | GPIO29_ADC3 | Analog: ADC channel 3 input. Unused. |
The controller implements a hierarchical state machine to manage the power states of the system. The transition diagram is shown below. Super-states are not shown, only the transitions between the concrete states.
---
config:
look: handDrawn
---
stateDiagram-v2
[*] --> PowerOff
PowerOff --> OffCharging : VIN > threshold
OffCharging --> PowerOff : VIN ≤ threshold
OffCharging --> SystemStartup : vscap ≥ threshold
SystemStartup --> PowerOff : VIN ≤ threshold
SystemStartup --> OperationalSolo : ComputeModuleOn
OperationalSolo --> OperationalCoOp : SetWatchdogTimeout(>0)
OperationalCoOp --> OperationalSolo : SetWatchdogTimeout(0)
OperationalSolo --> BlackoutSolo : VIN ≤ threshold
OperationalCoOp --> BlackoutCoOp : VIN ≤ threshold
OperationalCoOp --> HostUnresponsive : watchdog timeout
OperationalSolo --> ManualShutdown : Shutdown
OperationalCoOp --> ManualShutdown : Shutdown
OperationalSolo --> EnteringStandby : StandbyShutdown
OperationalCoOp --> EnteringStandby : StandbyShutdown
BlackoutSolo --> OperationalSolo : VIN > threshold
BlackoutCoOp --> OperationalCoOp : VIN > threshold
BlackoutSolo --> BlackoutShutdown : timeout
BlackoutCoOp --> BlackoutShutdown : Shutdown
HostUnresponsive --> OperationalCoOp : WatchdogPing
HostUnresponsive --> PoweredDownBlackout : timeout
ManualShutdown --> PoweredDownManual : timeout
ManualShutdown --> PoweredDownManual : ComputeModuleOff
EnteringStandby --> Standby : ComputeModuleOff
EnteringStandby --> Standby : timeout
Standby --> OperationalSolo : ComputeModuleOn
BlackoutShutdown --> PoweredDownBlackout : timeout
BlackoutShutdown --> PoweredDownBlackout : ComputeModuleOff
OperationalSolo --> PoweredDownManual : ComputeModuleOff
OperationalCoOp --> PoweredDownManual : ComputeModuleOff
HostUnresponsive --> PoweredDownManual : ComputeModuleOff
EnteringStandby --> PoweredDownManual : ComputeModuleOff
OperationalSolo --> PoweredDownManual : Off
OperationalCoOp --> PoweredDownManual : Off
HostUnresponsive --> PoweredDownManual : Off
EnteringStandby --> PoweredDownManual : Off
PoweredDownBlackout --> [*] : timeout (sys_reset)
PoweredDownBlackout --> [*] : PowerButtonPress (sys_reset)
PoweredDownManual --> [*] : timeout + auto_restart (sys_reset)
PoweredDownManual --> [*] : PowerButtonPress (sys_reset)
PoweredDownManual --> [*] : VIN blackout (sys_reset)
HALPI2 has a bar of five RGB LEDs that can be controlled by the controller. The LEDs indicate the supercap voltage level and the power state of the system.
The LEDs act as a bar graph, with the first LED indicating a voltage level between 5.0V and 6.0V, and the last LED indicating a voltage level between 9.0V and 10.0V.
When the system is powered off, the bar is red. When the system is booting but not yet communicating with the controller, the bar is yellow. When the system is powered on, the bar is green. When the system is shutting down, the bar is purple.
A depleting state is indicated by an animation scrolling right to left. An overvoltage state is indicated by rapid flashing of the first LED.
The controller communicates with the CM5 over I2C. The CM5 is the primary device on the I2C bus, and the controller is the secondary device. The controller responds to I2C requests using bus address 0x6d. The list of commands is given below:
R/W | Command | R/W Type | Value | Description |
---|---|---|---|---|
Read | 0x01 | u8 | 0x00 | Query legacy hardware version |
Read | 0x02 | u8 | 0xff | Query legacy firmware version |
Read | 0x03 | u32 | Query hardware version (4 bytes) | |
Read | 0x04 | u32 | Query firmware version (4 bytes) | |
Read | 0x10 | u8 | Query Raspi power state (0=off, 1=on) | |
Write | 0x10 | u8 | 0x00 | Set Raspi power off |
Read | 0x12 | u16 | Query watchdog timeout (ms, big-endian) | |
Write | 0x12 | u16 | Set watchdog timeout to NNNN ms (u16, big-endian) | |
Write | 0x12 | u16 | 0x0000 | Disable watchdog |
Read | 0x13 | u16 | Query power-on supercap threshold voltage (centivolts) | |
Write | 0x13 | u16 | Set power-on supercap threshold to 0.01*NNNN V | |
Read | 0x14 | u16 | Query power-off supercap threshold voltage (centivolts) | |
Write | 0x14 | u16 | Set power-off supercap threshold to 0.01*NNNN V | |
Read | 0x15 | u8 | Query state machine state | |
Read | 0x16 | u8 | 0x00 | Query watchdog elapsed (always returns 0x00) |
Read | 0x17 | u8 | Query LED brightness setting | |
Write | 0x17 | u8 | Set LED brightness to NN | |
Read | 0x18 | u8 | Query auto restart setting (0=disabled, 1=enabled) | |
Write | 0x18 | u8 | Set auto restart to NN (0=disabled, 1=enabled) | |
Read | 0x19 | u32 | Query solo depleting timeout (ms, big-endian) | |
Write | 0x19 | u32 | Set solo depleting timeout to NNNNNNNN ms (big-endian) | |
Read | 0x20 | u16 | Query DC IN voltage (scaled u16) | |
Read | 0x21 | u16 | Query supercap voltage (scaled u16) | |
Read | 0x22 | u16 | Query DC IN current (scaled u16) | |
Read | 0x23 | u16 | Query MCU temperature (scaled u16) | |
Read | 0x24 | u16 | Query PCB temperature (scaled u16) | |
Write | 0x30 | any | Initiate shutdown | |
Write | 0x31 | any | Initiate sleep shutdown | |
Write | 0x40 | u32 | Start DFU, firmware size is NNNNNNNN bytes (big-endian) | |
Read | 0x41 | u8 | Read DFU status (see DFUState enum) | |
Read | 0x42 | u16 | Read number of DFU blocks written (big-endian) | |
Write | 0x43 | [var] | Upload DFU block (CRC32, blocknum, len, data) | |
Write | 0x44 | any | Commit the uploaded DFU data | |
Write | 0x45 | any | Abort the DFU process | |
Read | 0x50 | f32 | Query VIN correction scale (big-endian) | |
Write | 0x50 | f32 | Set VIN correction scale (big-endian) | |
Read | 0x51 | f32 | Query VSCAP correction scale (big-endian) | |
Write | 0x51 | f32 | Set VSCAP correction scale (big-endian) | |
Read | 0x52 | f32 | Query IIN correction scale (big-endian) | |
Write | 0x52 | f32 | Set IIN correction scale (big-endian) |
For native flashing and debug log viewing, you need a Raspberry Pi Debug Probe connected to the debug header next to the RP2040 MCU. The header has three pins labeled:
- SWC (SWCLK)
- GND (Ground)
- SWD (SWDIO)
Connect the debug probe cables to these pins for probe-rs flashing and defmt log output.
Alternatively, use bootsel mode (no probe required): hold the BOOTSEL button while connecting USB, then copy .uf2
files to the mounted drive.
Install required tools:
# Rust target for RP2040
rustup target add thumbv6m-none-eabi
# Flashing and debugging
cargo install probe-rs --features cli
# UF2 conversion for bootsel mode (requires picotool)
# On macOS: brew install picotool
# On Ubuntu/Debian: build from source (see GitHub workflow for instructions)
Use the ./run
script for development tasks:
./run build # Debug build
./run build --release # Release build
./run build-uf2 # Build and convert to UF2
./run build-bootloader # Build bootloader only
./run build-all # Build everything + Debian package
./run clean # Clean build artifacts
With Debug Probe:
# Flash via probe-rs
./run flash
# Flash and immediately attach monitor
./run flash-and-monitor
# Monitor running firmware (view defmt logs)
./run monitor
# Flash bootloader only
./run flash-bootloader
Bootsel Mode (no probe):
- Hold BOOTSEL button while connecting USB
- Copy
.uf2
file to mounted drive - Device resets automatically
The firmware uses defmt
for structured logging over the debug probe. Log output shows:
- State machine transitions
- I2C command processing
- Power management events
- Error conditions
Example log output:
INFO State machine task initialized
INFO Transitioning from PowerOff to OffCharging
DEBUG I2C command 0x20 (read VIN voltage)
WARN Supercapacitor overvoltage alarm activated!
firmware/src/
├── main.rs # Entry point and task spawning
├── config.rs # Hardware constants and defaults
├── config_resources.rs # Resource allocation (assign-resources)
└── tasks/
├── state_machine.rs # Power management state machine
├── i2c_secondary.rs # I2C command interface
├── gpio_input.rs # Analog/digital input monitoring
├── led_blinker.rs # RGB LED control
├── power_button.rs # Power button handling
├── config_manager.rs # Persistent configuration storage
├── flash_writer.rs # Firmware update handling
└── watchdog_feeder.rs # System watchdog management
State Machine: Manages power states and transitions based on:
- VIN power availability (>9V threshold)
- Supercap voltage (8.0V power-on, 5.5V power-off)
- CM5 status (3.3V rail monitoring)
- Watchdog timeouts and user commands
I2C Interface: Secondary device at 0x6d providing:
- System status queries (voltage, temperature, state)
- Configuration commands (thresholds, watchdog, LED brightness)
- Control commands (shutdown, DFU updates)
Task System: Embassy-based async tasks communicate via channels:
INPUTS
mutex for sensor readingsSTATE_MACHINE_EVENT_CHANNEL
for state transitionsLED_BLINKER_EVENT_CHANNEL
for LED patternsFLASH_WRITE_REQUEST_CHANNEL
for configuration updates
Persistent settings stored in flash:
- Voltage thresholds and correction scales
- Watchdog and shutdown timeouts
- LED brightness and auto-restart behavior
Access via I2C commands or modify defaults in config.rs
.
Connect hardware and verify:
-
Power Management:
- Apply/remove VIN power (9-40V)
- Monitor state transitions via LEDs
- Check CM5 power control
-
I2C Communication:
# Read firmware version i2cget -y 1 0x6d 0x04 i # Read VIN voltage i2cget -y 1 0x6d 0x20 w
-
Supercap Testing:
- Charge supercap to 8V+ (system starts)
- Remove VIN power (blackout mode)
- Verify graceful shutdown behavior
Build Issues:
- Ensure
thumbv6m-none-eabi
target installed - Check Rust version (requires stable)
Flashing Issues:
- Verify debug probe connection (SWC, GND, SWD pins)
- Check probe detection:
probe-rs list
- Try bootsel mode if probe fails
Runtime Issues:
- Check defmt logs via
./run monitor
(requires debug probe) - Verify I2C address conflicts (should be 0x6d)
- Monitor voltage levels and thresholds
See RELEASE_PROCESS.md
for release procedures. All changes should:
- Maintain backward compatibility in I2C interface
- Include appropriate defmt logging
- Test power state transitions
- Update documentation for new features