A 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 every byte of user flash free for your application.
Note
A note on AI assistance. Parts of this project — code, tests, and the handbook — were written with AI assistance. We disclose that upfront so you can factor it into your evaluation. Humans still own the architecture, code quality, and correctness calls: every change is reviewed by a person, validated on real hardware where it touches flash / peripherals / boot, and has to keep fitting in the system-flash size budget. AI helps with throughput, not judgment. Full policy in the contributing guide.
| Family | Status |
|---|---|
| CH32V003 | ✅ Supported |
| CH32V00x (V002 / V004 / V005 / V006 / V007) | ✅ Supported |
| CH32V103 | ✅ Supported (needs a small BOOT0 circuit — see boot-ctl) |
| CH32X03x | 📋 Planned |
Porting to a new MCU family is a few hundred lines of glue.
Five minutes from a blank chip to an app that updates itself over UART.
1. Install the tools.
rustup toolchain install nightly
rustup component add rust-src --toolchain nightly
cargo install wlink # flash system flash via WCH-LinkE
cargo install tinyboot # host CLI for UART flashing2. Clone the repo and flash the bootloader.
git clone https://github.com/OpenServoCore/tinyboot
cd tinyboot/examples/ch32/v003/boot
cargo build --release
wlink flash --address 0x1FFFF000 target/riscv32ec-unknown-none-elf/release/boot
wlink set-power disable3v3 && wlink set-power enable3v3 # power-cycle3. Build and flash the demo app over UART.
Connect a USB-UART adapter (TX ↔ PD6, RX ↔ PD5, GND shared), then:
cd ../app
cargo build --release
tinyboot flash target/riscv32ec-unknown-none-elf/release/app --reset4. Confirm it's running.
tinyboot info
# capacity: 16320
# erase_size: 64
# boot_version: 0.4.0
# app_version: 1.2.3
# mode: 1 ← 1 means the app is runningLED should be blinking. To kick the device back into bootloader mode at any time:
tinyboot reset --bootloaderOn CH32V00x or CH32V103, the flow is the same — swap the example directory. See Getting Started for chip-specific notes.
The full documentation lives at openservocore.github.io/tinyboot. Highlights:
- Getting Started — expanded tutorial with more detail and per-chip notes
- CLI reference —
tinyboot flash / info / erase / reset / bin - App integration — put
poll()andconfirm()into your own firmware - Remote firmware updates — end-to-end OTA flow
- Troubleshooting — things that go wrong and how to fix them
- Porting to a new MCU — four traits, one HAL
- Design notes — motivation, the 1920-byte budget,
unsafepolicy
The CH32 factory bootloader is fixed to 115200 baud on PD5/PD6 with a sum-mod-256 checksum and no error reporting. embassy-boot is a well-designed bootloader but needs ~8 KB of flash — half the V003's total. tinyboot fits a real protocol (CRC16, trial boot, configurable transport) into 1920 bytes so every byte of user flash is yours.
For the full story and how it fits in 1920 bytes, see the design notes.
lib/core/ tinyboot-core — boot state machine, protocol dispatcher
lib/protocol/ tinyboot-protocol — wire protocol, frame format, CRC16
ch32/ tinyboot-ch32 — HAL + platform
ch32/rt/ tinyboot-ch32-rt — minimal bootloader runtime
cli/ tinyboot — host CLI flasher
examples/ch32/ per-chip boot + app examples (also CI test targets)
docs/ user guide
Contributions are very welcome — especially new chip ports and transports. See the contributing guide and please open an issue before starting anything big.
Licensed under either of Apache License, Version 2.0 or MIT License at your option.
