Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .zed/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"languages": {
"Rust": {
"format_on_save": "on"
},
"Markdown": {
"soft_wrap": "none"
}
},
"lsp": {
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# tinyboot

Rust bootloader for resource-constrained microcontrollers. Fits in the CH32V003's 1920-byte system flash with full trial boot, CRC16 app validation, OB-based metadata, and version reporting — leaving the entire 16KB user flash for the application.
Rust bootloader for resource-constrained microcontrollers. Fits in the CH32V003's 1920-byte system flash with full trial boot, CRC16 app validation, and version reporting — leaving all but one page of user flash for the application (64 bytes reserved for boot metadata on V003; page size varies by chip).

![tinyboot demo](docs/demo.gif)

Expand Down Expand Up @@ -36,7 +36,7 @@ tinyboot currently supports **UART / RS-485** transport. The table below tracks
- **Tiny** — Fits in 1920 bytes of CH32V003 system flash, leaving all 16KB user flash for the application
- **CRC16 validation** — Every frame is CRC16-CCITT protected; app image is verified end-to-end after flashing
- **Trial boot** — New firmware gets a limited number of boot attempts; if the app doesn't confirm, the bootloader takes over automatically
- **Boot state machine** — Idle / Updating / Validating lifecycle tracked in option bytes with forward-only bit transitions (no erase needed for state advances)
- **Boot state machine** — Idle / Updating / Validating lifecycle tracked in a reserved flash page with forward-only bit transitions (no erase needed for state advances)
- **Version reporting** — Boot and app versions packed into flash, queryable over the wire
- **Configurable transport** — The protocol runs over any `embedded_io::Read + Write` stream. The CH32 implementation supports UART with configurable pins, baud rate, and optional TX-enable for RS-485 / DXL TTL, but the core is transport-agnostic — USB, SPI, Bluetooth, or WiFi would work just as well
- **App-side integration** — The app can confirm a successful boot and request bootloader entry over the wire, enabling fully remote firmware updates without physical access
Expand Down Expand Up @@ -67,7 +67,7 @@ tinyboot currently supports **UART / RS-485** transport. The table below tracks
| [`tinyboot`](tinyboot/) | core | Platform-agnostic bootloader core (protocol dispatcher, boot state machine, app validation) |
| [`tinyboot-protocol`](tinyboot-protocol/) | core | Wire protocol (frame format, CRC16, commands) |
| [`tinyboot-ch32-hal`](tinyboot-ch32-hal/) | ch32 | Minimal HAL (flash, GPIO, USART, RCC) |
| [`tinyboot-ch32-boot`](tinyboot-ch32-boot/) | ch32 | Bootloader platform (storage, boot control, OB metadata) |
| [`tinyboot-ch32-boot`](tinyboot-ch32-boot/) | ch32 | Bootloader platform (storage, boot control, boot metadata) |
| [`tinyboot-ch32-app`](tinyboot-ch32-app/) | ch32 | App-side boot client (confirm, request update) |
| [`tinyboot-cli`](tinyboot-cli/) | host | CLI firmware flasher over UART |
| [`examples/ch32/system-flash`](examples/ch32/system-flash/) | example | Full-featured bootloader in 1920 bytes of system flash, all 16KB free for app |
Expand All @@ -90,7 +90,7 @@ Porting to an entirely new MCU family (e.g. STM32) requires a parallel set of cr

Low-level register access shared between the boot and app crates. Provides the bare minimum operations both sides need:

- **Flash** — unlock, erase page, write halfword/word, lock, option byte access
- **Flash** — unlock, erase page, write page, lock
- **GPIO** — configure pin mode, set high/low (for TX-enable if using RS-485)
- **USART** — init with baud rate, blocking read byte, blocking write byte, flush
- **RCC/clock** — enable peripheral clocks
Expand All @@ -106,7 +106,7 @@ Implements the core boot traits using the HAL. Four traits from `tinyboot::trait
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `Transport` | Any `embedded_io::Read + Write` stream — UART, RS-485, USB, SPI, even WiFi or Bluetooth. The protocol doesn't care what carries the bytes |
| `Storage` | Implement `embedded_storage::NorFlash` (erase, write) and provide `as_slice()` for zero-copy flash reads, plus `unlock()` |
| `BootMetaStore` | Read/write boot state, trial counter, and app checksum from your chip's equivalent of option bytes or a reserved flash page |
| `BootMetaStore` | Read/write boot state, trial counter, and app checksum from a reserved flash page (address defined by linker symbol) |
| `BootCtl` | `is_boot_requested()` checks your boot flag (OB bit, RAM magic, GPIO pin, etc.); `system_reset()` resets or jumps to app |

Wire them together in a `Platform` struct and pass it to `Core::new(platform).run()`.
Expand Down Expand Up @@ -147,10 +147,10 @@ Beyond the usual Cargo profile tricks (`opt-level = "z"`, LTO, `codegen-units =
- **`repr(C)` frame with union data** — CRC is computed directly over the struct memory via pointer cast; no serialization step, no intermediate buffer
- **`MaybeUninit` frame buffer** — the 76-byte `Frame` struct is reused every iteration without zero-initialization
- **Bit-bang CRC16** — no lookup table, trades speed for ~512 bytes of flash savings
- **OB bit-clear state transitions** — forward state changes (Idle→Updating, trial consumption) flip 1→0 bits without erasing, avoiding the cost of a full erase+rewrite cycle and the code to preserve OB contents
- **Bit-clear state transitions** — forward state changes (Idle→Updating, trial consumption) flip 1→0 bits without erasing, avoiding the cost of a full erase+rewrite cycle
- **Avoid `memset`/`memcpy`** — these pull in expensive core routines; manual byte loops and volatile writes keep the linker from dragging in library code
- **`.write()` over `.modify()`** — register writes use direct writes instead of read-modify-write, saving the read and mask operations
- **Aggressive code deduplication** — shared flash operation primitives across erase, write, and OB operations (see the flash HAL)
- **Aggressive code deduplication** — shared flash operation primitives across erase and write (see the flash HAL)

### Design approach

Expand Down
9 changes: 6 additions & 3 deletions examples/ch32/app/memory_x/system-flash/ch32v003a4m6.x
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
/* CH32V003 application memory layout for system-flash bootloader.
*
* The application occupies the full 16KB user flash. The bootloader lives
* in the separate system flash region, so no space is reserved here.
* The application occupies user flash minus the last page (boot metadata).
* The bootloader lives in the separate system flash region.
*
* Addresses use the code-execution alias (0x0000_0000 base).
* The FPEC programming offset (0x0800_0000) is handled by the HAL.
*/
MEMORY
{
FLASH : ORIGIN = 0x00000000, LENGTH = 16K
FLASH : ORIGIN = 0x00000000, LENGTH = 16K - 64
META : ORIGIN = 0x00000000 + 16K - 64, LENGTH = 64
RAM : ORIGIN = 0x20000000, LENGTH = 2K
}

Expand All @@ -19,3 +20,5 @@ REGION_ALIAS("REGION_DATA", RAM);
REGION_ALIAS("REGION_BSS", RAM);
REGION_ALIAS("REGION_HEAP", RAM);
REGION_ALIAS("REGION_STACK", RAM);

__tinyboot_meta_start = ORIGIN(META) + 0x08000000;
9 changes: 6 additions & 3 deletions examples/ch32/app/memory_x/system-flash/ch32v003f4p6.x
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
/* CH32V003 application memory layout for system-flash bootloader.
*
* The application occupies the full 16KB user flash. The bootloader lives
* in the separate system flash region, so no space is reserved here.
* The application occupies user flash minus the last page (boot metadata).
* The bootloader lives in the separate system flash region.
*
* Addresses use the code-execution alias (0x0000_0000 base).
* The FPEC programming offset (0x0800_0000) is handled by the HAL.
*/
MEMORY
{
FLASH : ORIGIN = 0x00000000, LENGTH = 16K
FLASH : ORIGIN = 0x00000000, LENGTH = 16K - 64
META : ORIGIN = 0x00000000 + 16K - 64, LENGTH = 64
RAM : ORIGIN = 0x20000000, LENGTH = 2K
}

Expand All @@ -19,3 +20,5 @@ REGION_ALIAS("REGION_DATA", RAM);
REGION_ALIAS("REGION_BSS", RAM);
REGION_ALIAS("REGION_HEAP", RAM);
REGION_ALIAS("REGION_STACK", RAM);

__tinyboot_meta_start = ORIGIN(META) + 0x08000000;
9 changes: 6 additions & 3 deletions examples/ch32/app/memory_x/system-flash/ch32v003f4u6.x
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
/* CH32V003 application memory layout for system-flash bootloader.
*
* The application occupies the full 16KB user flash. The bootloader lives
* in the separate system flash region, so no space is reserved here.
* The application occupies user flash minus the last page (boot metadata).
* The bootloader lives in the separate system flash region.
*
* Addresses use the code-execution alias (0x0000_0000 base).
* The FPEC programming offset (0x0800_0000) is handled by the HAL.
*/
MEMORY
{
FLASH : ORIGIN = 0x00000000, LENGTH = 16K
FLASH : ORIGIN = 0x00000000, LENGTH = 16K - 64
META : ORIGIN = 0x00000000 + 16K - 64, LENGTH = 64
RAM : ORIGIN = 0x20000000, LENGTH = 2K
}

Expand All @@ -19,3 +20,5 @@ REGION_ALIAS("REGION_DATA", RAM);
REGION_ALIAS("REGION_BSS", RAM);
REGION_ALIAS("REGION_HEAP", RAM);
REGION_ALIAS("REGION_STACK", RAM);

__tinyboot_meta_start = ORIGIN(META) + 0x08000000;
9 changes: 6 additions & 3 deletions examples/ch32/app/memory_x/system-flash/ch32v003j4m6.x
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
/* CH32V003 application memory layout for system-flash bootloader.
*
* The application occupies the full 16KB user flash. The bootloader lives
* in the separate system flash region, so no space is reserved here.
* The application occupies user flash minus the last page (boot metadata).
* The bootloader lives in the separate system flash region.
*
* Addresses use the code-execution alias (0x0000_0000 base).
* The FPEC programming offset (0x0800_0000) is handled by the HAL.
*/
MEMORY
{
FLASH : ORIGIN = 0x00000000, LENGTH = 16K
FLASH : ORIGIN = 0x00000000, LENGTH = 16K - 64
META : ORIGIN = 0x00000000 + 16K - 64, LENGTH = 64
RAM : ORIGIN = 0x20000000, LENGTH = 2K
}

Expand All @@ -19,3 +20,5 @@ REGION_ALIAS("REGION_DATA", RAM);
REGION_ALIAS("REGION_BSS", RAM);
REGION_ALIAS("REGION_HEAP", RAM);
REGION_ALIAS("REGION_STACK", RAM);

__tinyboot_meta_start = ORIGIN(META) + 0x08000000;
14 changes: 9 additions & 5 deletions examples/ch32/app/memory_x/user-flash/ch32v003a4m6.x
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
/* CH32V003 application memory layout for user-flash bootloader.
*
* The application occupies the upper 8KB of user flash, starting after the
* bootloader+meta region.
* The application occupies user flash between the bootloader and
* boot metadata regions.
*
* Flash map (see boot/memory.x for the full picture):
* 0x0000_0000 .. 0x0000_1FFF bootloader 8KB
* 0x0000_2000 .. 0x0000_3FFF application 8KB
* 0x0000_0000 .. 0x0000_1FFF bootloader 8KB
* 0x0000_2000 .. 0x0000_3FBF application 8KB - 64B
* 0x0000_3FC0 .. 0x0000_3FFF boot meta 64B
*
* Note: addresses here use the code-execution alias (0x0000_0000 base).
* The FPEC programming offset (0x0800_0000) is handled by the HAL.
*/
MEMORY
{
FLASH : ORIGIN = 0x00002000, LENGTH = 8K
FLASH : ORIGIN = 0x00000000 + 8K, LENGTH = 8K - 64
META : ORIGIN = 0x00000000 + 16K - 64, LENGTH = 64
RAM : ORIGIN = 0x20000000, LENGTH = 2K
}

Expand All @@ -23,3 +25,5 @@ REGION_ALIAS("REGION_DATA", RAM);
REGION_ALIAS("REGION_BSS", RAM);
REGION_ALIAS("REGION_HEAP", RAM);
REGION_ALIAS("REGION_STACK", RAM);

__tinyboot_meta_start = ORIGIN(META) + 0x08000000;
14 changes: 9 additions & 5 deletions examples/ch32/app/memory_x/user-flash/ch32v003f4p6.x
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
/* CH32V003 application memory layout for user-flash bootloader.
*
* The application occupies the upper 8KB of user flash, starting after the
* bootloader+meta region.
* The application occupies user flash between the bootloader and
* boot metadata regions.
*
* Flash map (see boot/memory.x for the full picture):
* 0x0000_0000 .. 0x0000_1FFF bootloader 8KB
* 0x0000_2000 .. 0x0000_3FFF application 8KB
* 0x0000_0000 .. 0x0000_1FFF bootloader 8KB
* 0x0000_2000 .. 0x0000_3FBF application 8KB - 64B
* 0x0000_3FC0 .. 0x0000_3FFF boot meta 64B
*
* Note: addresses here use the code-execution alias (0x0000_0000 base).
* The FPEC programming offset (0x0800_0000) is handled by the HAL.
*/
MEMORY
{
FLASH : ORIGIN = 0x00002000, LENGTH = 8K
FLASH : ORIGIN = 0x00000000 + 8K, LENGTH = 8K - 64
META : ORIGIN = 0x00000000 + 16K - 64, LENGTH = 64
RAM : ORIGIN = 0x20000000, LENGTH = 2K
}

Expand All @@ -23,3 +25,5 @@ REGION_ALIAS("REGION_DATA", RAM);
REGION_ALIAS("REGION_BSS", RAM);
REGION_ALIAS("REGION_HEAP", RAM);
REGION_ALIAS("REGION_STACK", RAM);

__tinyboot_meta_start = ORIGIN(META) + 0x08000000;
14 changes: 9 additions & 5 deletions examples/ch32/app/memory_x/user-flash/ch32v003f4u6.x
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
/* CH32V003 application memory layout for user-flash bootloader.
*
* The application occupies the upper 8KB of user flash, starting after the
* bootloader+meta region.
* The application occupies user flash between the bootloader and
* boot metadata regions.
*
* Flash map (see boot/memory.x for the full picture):
* 0x0000_0000 .. 0x0000_1FFF bootloader 8KB
* 0x0000_2000 .. 0x0000_3FFF application 8KB
* 0x0000_0000 .. 0x0000_1FFF bootloader 8KB
* 0x0000_2000 .. 0x0000_3FBF application 8KB - 64B
* 0x0000_3FC0 .. 0x0000_3FFF boot meta 64B
*
* Note: addresses here use the code-execution alias (0x0000_0000 base).
* The FPEC programming offset (0x0800_0000) is handled by the HAL.
*/
MEMORY
{
FLASH : ORIGIN = 0x00002000, LENGTH = 8K
FLASH : ORIGIN = 0x00000000 + 8K, LENGTH = 8K - 64
META : ORIGIN = 0x00000000 + 16K - 64, LENGTH = 64
RAM : ORIGIN = 0x20000000, LENGTH = 2K
}

Expand All @@ -23,3 +25,5 @@ REGION_ALIAS("REGION_DATA", RAM);
REGION_ALIAS("REGION_BSS", RAM);
REGION_ALIAS("REGION_HEAP", RAM);
REGION_ALIAS("REGION_STACK", RAM);

__tinyboot_meta_start = ORIGIN(META) + 0x08000000;
14 changes: 9 additions & 5 deletions examples/ch32/app/memory_x/user-flash/ch32v003j4m6.x
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
/* CH32V003 application memory layout for user-flash bootloader.
*
* The application occupies the upper 8KB of user flash, starting after the
* bootloader+meta region.
* The application occupies user flash between the bootloader and
* boot metadata regions.
*
* Flash map (see boot/memory.x for the full picture):
* 0x0000_0000 .. 0x0000_1FFF bootloader 8KB
* 0x0000_2000 .. 0x0000_3FFF application 8KB
* 0x0000_0000 .. 0x0000_1FFF bootloader 8KB
* 0x0000_2000 .. 0x0000_3FBF application 8KB - 64B
* 0x0000_3FC0 .. 0x0000_3FFF boot meta 64B
*
* Note: addresses here use the code-execution alias (0x0000_0000 base).
* The FPEC programming offset (0x0800_0000) is handled by the HAL.
*/
MEMORY
{
FLASH : ORIGIN = 0x00002000, LENGTH = 8K
FLASH : ORIGIN = 0x00000000 + 8K, LENGTH = 8K - 64
META : ORIGIN = 0x00000000 + 16K - 64, LENGTH = 64
RAM : ORIGIN = 0x20000000, LENGTH = 2K
}

Expand All @@ -23,3 +25,5 @@ REGION_ALIAS("REGION_DATA", RAM);
REGION_ALIAS("REGION_BSS", RAM);
REGION_ALIAS("REGION_HEAP", RAM);
REGION_ALIAS("REGION_STACK", RAM);

__tinyboot_meta_start = ORIGIN(META) + 0x08000000;
4 changes: 2 additions & 2 deletions examples/ch32/app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ const BOOT_BASE: u32 = 0x1FFF_F000;
#[cfg(feature = "system-flash")]
const BOOT_SIZE: u32 = 1920;
#[cfg(feature = "system-flash")]
const APP_SIZE: u32 = 16 * 1024;
const APP_SIZE: u32 = 16 * 1024 - 64;

#[cfg(feature = "user-flash")]
const BOOT_BASE: u32 = 0x0800_0000;
#[cfg(feature = "user-flash")]
const BOOT_SIZE: u32 = 8 * 1024;
#[cfg(feature = "user-flash")]
const APP_SIZE: u32 = 8 * 1024;
const APP_SIZE: u32 = 8 * 1024 - 64;

const ERASE_SIZE: u16 = 64;

Expand Down
4 changes: 3 additions & 1 deletion examples/ch32/boot/memory_x/system-flash/ch32v003a4m6.x
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
* while programming requires the physical address (FLASH).
*
* This frees all 16KB of user flash (0x08000000) for the application.
* Boot metadata (state, trial counter) is stored in option bytes (0x1FFFF800).
* Boot metadata is stored in the last page of user flash.
*/
MEMORY
{
CODE : ORIGIN = 0x00000000, LENGTH = 1920
FLASH : ORIGIN = 0x1FFFF000, LENGTH = 1920
RAM : ORIGIN = 0x20000000, LENGTH = 2K
}

__tinyboot_meta_start = 0x08000000 + 16K - 64;
4 changes: 3 additions & 1 deletion examples/ch32/boot/memory_x/system-flash/ch32v003f4p6.x
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
* while programming requires the physical address (FLASH).
*
* This frees all 16KB of user flash (0x08000000) for the application.
* Boot metadata (state, trial counter) is stored in option bytes (0x1FFFF800).
* Boot metadata is stored in the last page of user flash.
*/
MEMORY
{
CODE : ORIGIN = 0x00000000, LENGTH = 1920
FLASH : ORIGIN = 0x1FFFF000, LENGTH = 1920
RAM : ORIGIN = 0x20000000, LENGTH = 2K
}

__tinyboot_meta_start = 0x08000000 + 16K - 64;
4 changes: 3 additions & 1 deletion examples/ch32/boot/memory_x/system-flash/ch32v003f4u6.x
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
* while programming requires the physical address (FLASH).
*
* This frees all 16KB of user flash (0x08000000) for the application.
* Boot metadata (state, trial counter) is stored in option bytes (0x1FFFF800).
* Boot metadata is stored in the last page of user flash.
*/
MEMORY
{
CODE : ORIGIN = 0x00000000, LENGTH = 1920
FLASH : ORIGIN = 0x1FFFF000, LENGTH = 1920
RAM : ORIGIN = 0x20000000, LENGTH = 2K
}

__tinyboot_meta_start = 0x08000000 + 16K - 64;
4 changes: 3 additions & 1 deletion examples/ch32/boot/memory_x/system-flash/ch32v003j4m6.x
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
* while programming requires the physical address (FLASH).
*
* This frees all 16KB of user flash (0x08000000) for the application.
* Boot metadata (state, trial counter) is stored in option bytes (0x1FFFF800).
* Boot metadata is stored in the last page of user flash.
*/
MEMORY
{
CODE : ORIGIN = 0x00000000, LENGTH = 1920
FLASH : ORIGIN = 0x1FFFF000, LENGTH = 1920
RAM : ORIGIN = 0x20000000, LENGTH = 2K
}

__tinyboot_meta_start = 0x08000000 + 16K - 64;
12 changes: 7 additions & 5 deletions examples/ch32/boot/memory_x/user-flash/ch32v003a4m6.x
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
/* CH32V003 user-flash bootloader memory layout.
*
* The bootloader occupies the first 8KB of the 16KB user flash, with
* boot metadata (state, trial counter) in the last 64 bytes of that region.
* The remaining 8KB is entirely available for the application.
* The bootloader occupies the first 8KB of the 16KB user flash.
* Boot metadata is stored in the last page of user flash.
*
* CODE is the execution address (flash mirrored at 0x00000000).
* FLASH is the programming address (FPEC requires 0x08000000-based addresses).
*
* Flash map:
* 0x0800_0000 .. 0x0800_1FFF bootloader 8KB
* 0x0800_2000 .. 0x0800_3FFF application 8KB
* 0x0800_0000 .. 0x0800_1FFF bootloader 8KB
* 0x0800_2000 .. 0x0800_3FBF application 8KB - 64B
* 0x0800_3FC0 .. 0x0800_3FFF boot meta 64B
*/
MEMORY
{
CODE : ORIGIN = 0x00000000, LENGTH = 8K /* execution alias */
FLASH : ORIGIN = 0x08000000, LENGTH = 8K /* physical address */
RAM : ORIGIN = 0x20000000, LENGTH = 2K
}

__tinyboot_meta_start = 0x08000000 + 16K - 64;
Loading
Loading