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
55 changes: 55 additions & 0 deletions .github/workflows/book.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Book

on:
push:
branches: [main]
paths:
- "docs/**"
- "book.toml"
- ".github/workflows/book.yml"
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- uses: dtolnay/rust-toolchain@stable

- uses: Swatinem/rust-cache@v2
with:
key: mdbook

- name: Install mdbook
run: cargo install mdbook --locked

- name: Build book
run: mdbook build

- uses: actions/configure-pages@v5

- uses: actions/upload-pages-artifact@v3
with:
path: ./book

deploy:
name: Deploy
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v4
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ target
Cargo.lock
!cli/Cargo.lock
!examples/**/Cargo.lock

# mdbook output
/book

216 changes: 68 additions & 148 deletions README.md

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions book.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[book]
title = "tinyboot"
description = "A Rust bootloader for resource-constrained microcontrollers"
authors = ["Aaron Qian"]
src = "docs"
language = "en"

[output.html]
default-theme = "navy"
preferred-dark-theme = "navy"
git-repository-url = "https://github.com/OpenServoCore/tinyboot"
edit-url-template = "https://github.com/OpenServoCore/tinyboot/edit/main/{path}"
no-section-label = true
41 changes: 12 additions & 29 deletions ch32/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# tinyboot-ch32

Part of the [tinyboot](https://github.com/OpenServoCore/tinyboot) project — see the main README to get started.
Part of the [tinyboot](https://github.com/OpenServoCore/tinyboot) project — start with the [top-level README](https://github.com/OpenServoCore/tinyboot#quick-start-ch32v003) and the [handbook](https://openservocore.github.io/tinyboot/).

CH32 HAL and tinyboot platform for CH32V003, CH32V00x (V002/V004/V005/V006/V007), and CH32V103. Exposes a bootloader-side entry point ([`boot`]) and an app-side client ([`app`]) built on a small in-crate HAL ([`hal`]).
CH32 HAL and tinyboot platform for CH32V003, CH32V00x (V002 / V004 / V005 / V006 / V007), and CH32V103. Exposes a bootloader-side entry point ([`boot`]) and an app-side client ([`app`]) built on a small in-crate HAL ([`hal`]).

## Installation

As of v0.4.0, `tinyboot-ch32` is **consumed from git**, not crates.io. It depends on [`ch32-metapac`](https://github.com/ch32-rs/ch32-metapac) as a git-only dependency for CH32V00x flash support, which crates.io does not allow. Add it to your `Cargo.toml` like so:
As of v0.4.0, `tinyboot-ch32` is **consumed from git**, not crates.io. It depends on [`ch32-metapac`](https://github.com/ch32-rs/ch32-metapac) as a git-only dependency for CH32V00x flash support, which crates.io does not allow.

```toml
[dependencies]
Expand All @@ -25,7 +25,7 @@ tinyboot-ch32-rt = "0.4" # optional, bootloader-only; on crates.io
| `hal` | Both | `flash`, `gpio`, `usart`, `afio`, `rcc`, `pfic`, `iwdg`; auto-generated `Pin` and `UsartMapping` |
| `platform` | (internal) | `tinyboot_core::traits` impls for Storage, Transport, BootCtl, BootMetaStore |

## Bootloader example
## Minimal bootloader

```rust
use panic_halt as _;
Expand All @@ -51,23 +51,9 @@ fn main() -> ! {

`Storage` and `BootMetaStore` are initialized from linker symbols automatically. `boot_version!()` places the crate's `Cargo.toml` version into the `.tb_version` section; the core reads it via `__tb_version`.

For CH32V103 in `system-flash` mode, `BootCtl::new` takes a GPIO pin driving the external BOOT0 circuit, the level that selects system flash, and a reset-delay cycle count (RC settle time):
For configuring RS-485 half-duplex, DXL TTL, or alternate pin remaps, see the [transports guide](https://openservocore.github.io/tinyboot/transports.html). For CH32V103 in system-flash mode, `BootCtl::new` takes additional arguments for the external BOOT_CTL circuit — see [boot-ctl](https://openservocore.github.io/tinyboot/boot-ctl.html).

```rust
BootCtl::new(Pin::PB1, Level::High, 8000)
```

For RS-485 half-duplex with a DE/RE pin:

```rust
Usart::new(&UsartConfig {
duplex: Duplex::Half,
tx_en: Some(TxEnConfig { pin: Pin::PC2, tx_level: Level::High }),
..
})
```

## App example
## Minimal app

```rust
tinyboot_ch32::app::app_version!();
Expand All @@ -80,14 +66,9 @@ loop {
}
```

`app::poll` handles Info and Reset:

- **Info** — responds with capacity, erase size, versions, and `mode=1`.
- **Reset** — resets the device; `addr=1` reboots into the bootloader, `addr=0` reboots into the app.
`app::poll` handles `Info` (responds with capacity, erase size, versions, `mode = 1`) and `Reset` (resets the device; `addr = 1` reboots into the bootloader, `addr = 0` reboots into the app). All other commands return `Status::Unsupported`.

All other commands return `Status::Unsupported`.

For CH32V103 `system-flash` apps, pass the same `BootCtl::new(pin, level, delay)` as the bootloader. Apps on V003 or in `user-flash` mode use the unit-arg form `BootCtl::new()`.
See the [app integration guide](https://openservocore.github.io/tinyboot/app-integration.html) for a complete app including `embedded_io` transport wrapping and peripheral ownership.

## Features

Expand All @@ -98,7 +79,9 @@ For CH32V103 `system-flash` apps, pass the same `BootCtl::new(pin, level, delay)
| `ch32v103c6t6` / `c8t6` / `c8u6` / `r8t6` | CH32V103 chip variants |
| `system-flash` | Build for the system-flash bootloader region |

Complete boot + app examples live in [`examples/ch32/v003`](../examples/ch32/v003/), [`examples/ch32/v00x`](../examples/ch32/v00x/), and [`examples/ch32/v103`](../examples/ch32/v103/).
See the [flash modes guide](https://openservocore.github.io/tinyboot/flash-modes.html) for when to pick `system-flash` vs user-flash.

Complete boot + app examples live in [`examples/ch32/v003`](https://github.com/OpenServoCore/tinyboot/tree/main/examples/ch32/v003), [`examples/ch32/v00x`](https://github.com/OpenServoCore/tinyboot/tree/main/examples/ch32/v00x), and [`examples/ch32/v103`](https://github.com/OpenServoCore/tinyboot/tree/main/examples/ch32/v103).

## Linker scripts

Expand All @@ -109,4 +92,4 @@ cargo:rustc-link-arg=-Ttb-boot.x
cargo:rustc-link-arg=-Ttb-run-mode.x
```

The core linker scripts (`tb-boot.x`, `tb-app.x`) are shipped by `tinyboot-core`.
The core linker scripts (`tb-boot.x`, `tb-app.x`) are shipped by `tinyboot-core`. For the linker region contract every `memory.x` must satisfy, see the [porting guide](https://openservocore.github.io/tinyboot/porting.html#linker-region-contract).
4 changes: 2 additions & 2 deletions cli/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# tinyboot

Part of the [tinyboot](https://github.com/OpenServoCore/tinyboot) project — see the main README to get started.
Part of the [tinyboot](https://github.com/OpenServoCore/tinyboot) project — start with the [top-level README](https://github.com/OpenServoCore/tinyboot#quick-start-ch32v003) and the [handbook](https://openservocore.github.io/tinyboot/).

Host-side CLI for flashing firmware to tinyboot devices over UART/RS-485.
Host-side CLI for flashing firmware to tinyboot devices over UART / RS-485.

## Install

Expand Down
35 changes: 35 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# tinyboot docs

Documentation for using, integrating, and extending tinyboot.

If you're new here, start with the [top-level README](https://github.com/OpenServoCore/tinyboot#quick-start-ch32v003) quick start, then come back for deeper topics.

## Tutorial

- [Getting Started](getting-started.md) — toolchain, tools, and your first successful flash

## Guides

- [Flash modes: system-flash vs user-flash](flash-modes.md)
- [Transports: UART, RS-485, DXL TTL](transports.md)
- [GPIO-controlled boot mode selection](boot-ctl.md) — BOOT0 circuits for chips with hardware boot pins
- [App integration](app-integration.md) — wire the tinyboot app side into your firmware
- [Remote firmware updates](remote-updates.md) — the end-to-end OTA flow
- [Building your bootloader from an example](examples.md)

## Reference

- [Porting to a new MCU family](porting.md)
- [Design notes](design.md) — motivation, the 1920-byte budget, unsafe policy
- [Protocol reference](https://github.com/OpenServoCore/tinyboot/tree/main/lib/protocol) — wire format, frames, commands
- [Boot state machine](https://github.com/OpenServoCore/tinyboot/tree/main/lib/core) — state transitions, metadata layout
- [CLI reference](https://github.com/OpenServoCore/tinyboot/tree/main/cli)
- [`tinyboot-ch32` reference](https://github.com/OpenServoCore/tinyboot/tree/main/ch32)

## Troubleshooting

- [Troubleshooting guide](troubleshooting.md) — symptoms, likely causes, fixes

## Contributing

- [Contributing](contributing.md) — dev setup, tests, hardware validation
29 changes: 29 additions & 0 deletions docs/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Summary

[Introduction](README.md)

# Tutorial

- [Getting Started](getting-started.md)

# Guides

- [Flash modes](flash-modes.md)
- [Transports](transports.md)
- [GPIO-controlled boot mode selection](boot-ctl.md)
- [App integration](app-integration.md)
- [Remote firmware updates](remote-updates.md)
- [Building your bootloader from an example](examples.md)

# Reference

- [Porting to a new MCU](porting.md)
- [Design notes](design.md)

# Troubleshooting

- [Troubleshooting](troubleshooting.md)

# Contributing

- [Contributing](contributing.md)
85 changes: 85 additions & 0 deletions docs/app-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# App integration

The tinyboot bootloader is only half the story — to support remote firmware updates, your app has to cooperate with it. This guide walks through what the app needs to do and shows a minimal integration.

## What the app is responsible for

1. **Declare its version** so the host can see it via `tinyboot info`.
2. **Confirm successful boot** so the bootloader stops retrying.
3. **Poll the transport** for `Info` and `Reset` requests.

That's it. The bootloader handles everything else — flashing, verification, state transitions, trial boot.

## Minimal app

```rust
#![no_std]
#![no_main]

// Embed version into the app's binary so tinyboot can find it.
tinyboot_ch32::app::app_version!();

#[qingke_rt::entry]
fn main() -> ! {
// Your usual peripheral setup.
let p = ch32_hal::init(Default::default());

// UART wired the same way as the bootloader's.
let uart = Uart::new_blocking::<0>(p.USART1, p.PD6, p.PD5, uart_config).unwrap();
let (tx, rx) = uart.split();

// Adapt your tx/rx to embedded_io::Read + Write (see examples/ for a sample).
let mut rx = /* wrap rx */;
let mut tx = /* wrap tx */;

// Create the app handle and confirm that this boot succeeded.
let mut app = tinyboot_ch32::app::new_app(tinyboot_ch32::app::BootCtl::new());
app.confirm();

loop {
// Your app's real work goes here, alongside polling.
app.poll(&mut rx, &mut tx);
}
}
```

See [`examples/ch32/v003/app/`](https://github.com/OpenServoCore/tinyboot/tree/main/examples/ch32/v003/app) for a complete example including a `transport.rs` module that wraps ch32-hal's `Uart` in the `embedded_io` traits plus optional RS-485 DE-pin handling.

## `app::confirm()` — trial boot handshake

The bootloader tracks newly-flashed firmware in a trial state. Every boot in `Validating` state consumes one trial; when trials run out, the bootloader assumes the app is broken and takes over on the next reset.

`app::confirm()` tells the bootloader the new firmware is alive. Call it **after** your app is initialized to the point where you're confident it's running correctly — early enough that it always runs on a successful boot, but late enough to catch major initialization failures.

Once called, the app is considered confirmed and will boot normally on every subsequent reset (until the next firmware update starts the cycle again).

If `confirm()` is never reached (panic, watchdog, init deadlock), the trials get consumed across resets and the bootloader eventually takes back control.

## `app::poll()` — handling bootloader commands

`poll()` reads a single frame from your transport and handles it. In the app, two commands do something; the rest are rejected with `Status::Unsupported`:

| Command | Behavior in app |
| -------- | -------------------------------------------------------------------------------- |
| `Info` | Responds with capacity, erase size, boot + app versions, `mode = 1` (app mode). |
| `Reset` | Resets the device. `addr = 1` reboots into the bootloader; `addr = 0` reboots into the app. |

This is enough for the host CLI to do `tinyboot info` and `tinyboot reset --bootloader` while the app is running, which is how remote updates get kicked off — see the [remote updates guide](remote-updates.md).

Because `poll()` is blocking on a read, a typical app runs it in a dedicated task or a loop iteration alongside its other work. For timing-sensitive apps, consider running the transport on an interrupt-driven reader and feeding `poll()` asynchronously; `poll()` itself is CPU-cheap.

## UART sharing notes

The bootloader and app normally share the same USART. A few gotchas:

- **Matching config** — the app's baud rate, pins, and DE polarity must match the bootloader's. See [transports.md](transports.md).
- **DE pin polarity** — on boards where RS-485 transceiver contention is possible (e.g. some OpenServoCore V006 layouts), use a `tx_level` that leaves the bus driver **disabled** when idle, so the host's TX line can reach MCU_RX.
- **Half-duplex flush** — when sending multi-byte responses on half-duplex, make sure your `embedded_io::Write` implementation flushes the UART before releasing DE.

## Passing peripherals to `poll()`

`poll()` takes your transport as split rx/tx types implementing `embedded_io::Read` and `embedded_io::Write`. This lets your app keep full ownership of peripheral initialization — tinyboot doesn't take over USART registers, and you can layer extra features (logging, RTU framing) on top of the same UART if you adapt them correctly.

## BootCtl in the app

`BootCtl::new()` takes the same arguments in the app as it does in the bootloader — for CH32V003 / V00x that's `BootCtl::new()`, for CH32V103 in system-flash mode it's `BootCtl::new(pin, level, delay)`. The app needs this so that `Reset` with the `BOOTLOADER` flag can set the run-mode marker before resetting.
Loading