tinyboot-protocol
Part of the tinyboot project — see the main README to get started.
Wire protocol for tinyboot. Defines the frame format used between host and device over UART/RS-485.
Frame format
A single Frame struct is used for both requests (host to device) and responses (device to host), so we keep code size tiny.
0 1 2 3 4 5 6 7 8 9 10 10+len 10+len+2
+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+- - - -+-------+-------+
| SYNC0 | SYNC1 | CMD |STATUS | ADDR (u32 LE) | LEN_LO LEN_HI | DATA... | CRC_LO CRC_HI |
| 0xAA | 0x55 | | | | | | |
+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+- - - -+-------+-------+
|<--------------------- header (10 bytes) --------------------->|<- payload ->|<--- CRC --->|
Total frame size = 12 bytes overhead + payload. Maximum payload is 64 bytes (MAX_PAYLOAD).
| Field | Size | Description |
|---|---|---|
| SYNC | 2 bytes | Preamble 0xAA 0x55 for frame synchronization |
| CMD | 1 byte | Command code |
| STATUS | 1 byte | Request (0x00) for requests, result status for responses |
| ADDR | 4 bytes | Flash address (u32 LE). Echoed in responses |
| LEN | 2 bytes | Data payload length (u16 LE, 0..64) |
| DATA | 0..64 | Payload bytes |
| CRC | 2 bytes | CRC16-CCITT (LE) over SYNC + CMD + STATUS + ADDR + LEN + DATA |
Commands
| Code | Name | Direction | Description |
|---|---|---|---|
| 0x00 | Info | Host to Device | Query device info (capacity, erase size, versions, mode) |
| 0x01 | Erase | Host to Device | Erase byte_count bytes at addr (first erase transitions Idle → Updating) |
| 0x02 | Write | Host to Device | Write data at address |
| 0x03 | Verify | Host to Device | Compute CRC16 over addr bytes of app, store checksum + state in OB |
| 0x04 | Reset | Host to Device | Reset the device |
Info response
Returns 12 bytes via the InfoData struct:
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 bytes | capacity | App region capacity in bytes (u32 LE) |
| 4 | 2 bytes | erase_size | Erase page size in bytes (u16 LE) |
| 6 | 2 bytes | boot_version | Boot version (packed u16 LE, 0xFFFF=none) |
| 8 | 2 bytes | app_version | App version (packed u16 LE, 0xFFFF=none) |
| 10 | 2 bytes | mode | 0 = bootloader, 1 = app |
Versions are packed as (major << 11) | (minor << 6) | patch and read from the last 2 bytes of each binary (boot and app).
Erase
Erases byte_count bytes starting at addr. Both addr and byte_count must be aligned to the device's erase size.
Request payload (2 bytes via EraseData):
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 2 bytes | byte_count | Number of bytes to erase (u16 LE) |
Verify
The addr field carries the application size in bytes. The device computes CRC16 over the first addr bytes of flash (the actual firmware, not the full region), stores the checksum and app size in OB metadata, and transitions to Validating state.
Response returns 2 bytes via the VerifyData struct:
| Offset | Size | Description |
|---|---|---|
| 0 | 2 bytes | CRC16 of app firmware bytes (u16 LE) |
Status codes
| Code | Name | Description |
|---|---|---|
| 0x00 | Request | Frame is a request (not a response) |
| 0x01 | Ok | Success |
| 0x02 | WriteError | Flash write/erase failed |
| 0x03 | CrcMismatch | CRC verification failed |
| 0x04 | AddrOutOfBounds | Address or length out of range |
| 0x05 | Unsupported | Command not valid in current state |
| 0x06 | PayloadOverflow | Frame payload exceeds maximum size |
CRC
CRC16-CCITT with polynomial 0x1021 and initial value 0xFFFF. Computed over the entire frame body (SYNC through DATA, excluding the CRC field itself). Bit-bang implementation with no lookup table for minimal flash footprint.
Protocol flow
- Host sends a request frame with
status = Request (0x00) - Device reads the frame, processes the command
- Device sends a response frame with
cmdandaddrechoed from the request,statusset to the result
The same Frame struct is reused: after read(), the device modifies status, len, and data, then calls send(). The cmd and addr fields carry over automatically.
Data union
The data field is a #[repr(C)] union with typed variants for structured payloads:
pub union Data
Data starts at offset 10 (even-aligned), so u16 fields in the union variants are naturally aligned.
Example: flash sequence
Host -> Info request
Device -> Info response (capacity=16384, erase_size=64, mode=0)
Host -> Erase addr=0x0000 byte_count=16384
Device -> Ok
...
Host -> Write addr=0x0000 data=[64 bytes]
Device -> Ok
Host -> Write addr=0x0040 data=[64 bytes]
Device -> Ok
...
Host -> Verify addr=5110 (app_size)
Device -> Ok crc=0x1234
Host -> Reset
Device -> Ok (then resets)