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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ jobs:
run: cd examples/ch32/v003 && cargo +nightly clippy -p boot --no-default-features --features "${{ matrix.chip }},system-flash" -- -D warnings
- name: Build boot (system-flash)
run: cd examples/ch32/v003 && cargo +nightly build -p boot --no-default-features --features "${{ matrix.chip }},system-flash" --release
- name: Size gate (every option at once fits system flash)
run: cd examples/ch32/v003 && cargo +nightly build -p boot --bin heaviest --no-default-features --features "${{ matrix.chip }},system-flash" --release
- name: Clippy boot (user-flash)
run: cd examples/ch32/v003 && cargo +nightly clippy -p boot --no-default-features --features "${{ matrix.chip }},user-flash" -- -D warnings
- name: Build boot (user-flash)
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ Releases up through `v0.4.0` were workspace-wide and tagged `vX.Y.Z`. From `0.4.

### Added

- Addressable devices for a shared bus. `boot::run` takes the bus addressing: `Addressable::new(chip_uid)` keeps the bootloader silent until a host selects it by the chip's 96-bit unique id, `Unaddressed` answers every frame as before. `app::new_addressable_app(ctl, wait_bytes)` does the same in the app. `chip_uid` reads the ESIG unique id, and `Usart` implements `Transport::wait_bytes` from its baud divisor.
- `boot_version!(compat = "name")` and `app_version!(compat = "name")` (re-exported from core): a bootloader with a compatibility key accepts only app images with the same key. The V00x and V103 examples set one.
- The bootloader runs its USART at the baud stored in boot metadata, falling back to `UsartConfig::baud` when nothing is stored or `pclk` cannot reach the rate (divisor below 16). Divisors are computed at build time, so there is no runtime divide; the CH32V003 example bootloader grows by 64 bytes.
- `app::BaudRate` re-export for `App::set_baud` / `App::baud`.
- `boot::run_with_payload::<N>` runs the bootloader with an `N`-byte frame payload (at least 18; a Write may span flash pages); `boot::run` uses the 52-byte default.

### Changed

- **Breaking:** `boot::run` and `boot::run_with_payload` take a third argument, the bus addressing (`Unaddressed` for the previous behavior).
- **Breaking:** `BaudRate` is `tinyboot_protocol::BaudRate`, a `u8` index enum with `hz()`, instead of a `u32` enum whose discriminant was the rate.
- **Breaking:** the USART wire and DE pin are chosen by type, and the `half-duplex` cargo feature is gone. `UsartConfig` takes `wire: FullDuplex { rx_pull, drive }` (`PushPull` / `OpenDrain`) or `HalfDuplex { drive }` (`PushPullWhileSending` / `OpenDrain`), and `tx_en: NoTxEn` or `TxEn { pin, tx_level }` (was `Option<TxEnConfig>`). A bootloader builds only the pin switching its setup needs, with each switch precomputed in `Usart::new` (one BSHR store for the DE pin, one CNF0 bit flip for push-pull while sending), and `HalfDuplex { drive: PushPull }` does not compile. Bootloaders shrink: CH32V003 example 40 bytes, CH32V00x 48, CH32V103 68, a half-duplex CH32V006 board 164.
- The DE pin's GPIO clock is enabled in `Usart::new`'s single RCC write, and `set_boot_mode` is shared by the hand-off and Reset paths.
Expand Down
40 changes: 29 additions & 11 deletions ch32/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,48 @@ use crate::hal::flash;
use crate::platform::BootMetaStore;

pub use crate::hal::Pin;
pub use crate::hal::esig::uid as chip_uid;
pub use crate::hal::gpio::Level;
pub use crate::platform::BootCtl;
pub use tinyboot_core::BaudRate;
pub use tinyboot_core::addressing::{Addressable, Addressing, Unaddressed};
pub use tinyboot_core::app::{App, AppConfig};
pub use tinyboot_core::traits;
pub use tinyboot_core::{app_version, pkg_version};

/// Create an [`App`] configured for CH32 hardware.
pub fn new_app(ctl: BootCtl) -> App<BootCtl, BootMetaStore> {
App::new(config(), ctl, BootMetaStore::default())
}

/// Create an addressable [`App`]: silent on a shared bus until a host
/// selects it by the chip's unique id. `wait_bytes(n)` waits about `n`
/// byte-times at the bus baud, to stagger `Select` replies.
pub fn new_addressable_app(
ctl: BootCtl,
wait_bytes: fn(u8),
) -> App<BootCtl, BootMetaStore, { tinyboot_core::frame::DEFAULT_PAYLOAD }, impl Addressing> {
App::with_addressing(
config(),
ctl,
BootMetaStore::default(),
Addressable::new(chip_uid),
wait_bytes,
)
}

fn config() -> AppConfig {
unsafe extern "C" {
static __tb_boot_tail: tinyboot_core::ImageTail;
static __tb_app_capacity: u8;
}
let boot_tail = &raw const __tb_boot_tail;
let app_capacity = unsafe { &__tb_app_capacity as *const u8 as u32 };
App::new(
AppConfig {
capacity: app_capacity,
erase_size: flash::PAGE_SIZE as u16,
boot_version: unsafe { (*boot_tail).version },
boot_compat: unsafe { (*boot_tail).compat },
app_version: tinyboot_core::tinyboot_version(),
},
ctl,
BootMetaStore::default(),
)
AppConfig {
capacity: app_capacity,
erase_size: flash::PAGE_SIZE as u16,
boot_version: unsafe { (*boot_tail).version },
boot_compat: unsafe { (*boot_tail).compat },
app_version: tinyboot_core::tinyboot_version(),
}
}
22 changes: 17 additions & 5 deletions ch32/src/boot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,38 @@ pub use crate::platform::{
PushPullWhileSending, TxEn, TxEnable, Usart, UsartConfig, Wire,
};

pub use crate::hal::esig::uid as chip_uid;
pub use crate::hal::gpio::{Level, Pull};
pub use crate::hal::{Pin, UsartMapping};
pub use tinyboot_core::Platform;
pub use tinyboot_core::addressing::{Addressable, Addressing, Unaddressed};
use tinyboot_core::frame::{DEFAULT_PAYLOAD, InfoData};
pub use tinyboot_core::{boot_version, pkg_version};

/// Common imports for bootloader binaries.
pub mod prelude {
pub use super::{
BaudRate, BootCtl, FullDuplex, HalfDuplex, Level, NoTxEn, OpenDrain, Pin, Pull, PushPull,
PushPullWhileSending, TxEn, Usart, UsartConfig, UsartMapping,
Addressable, BaudRate, BootCtl, FullDuplex, HalfDuplex, Level, NoTxEn, OpenDrain, Pin,
Pull, PushPull, PushPullWhileSending, TxEn, Unaddressed, Usart, UsartConfig, UsartMapping,
chip_uid,
};
}

pub const PAGE_SIZE: usize = crate::hal::flash::PAGE_SIZE;

/// Run the bootloader with the default 52-byte frame payload. Does not
/// return.
///
/// `addressing` is [`Unaddressed`] for one device per bus, or
/// `Addressable::new(chip_uid)` for many devices on one bus, each
/// silent until a host selects it by its unique id.
#[inline(always)]
pub fn run(transport: impl tinyboot_core::traits::Transport, ctl: BootCtl) -> ! {
run_with_payload::<DEFAULT_PAYLOAD>(transport, ctl)
pub fn run(
transport: impl tinyboot_core::traits::Transport,
ctl: BootCtl,
addressing: impl Addressing,
) -> ! {
run_with_payload::<DEFAULT_PAYLOAD>(transport, ctl, addressing)
}

/// Run the bootloader with a `P`-byte frame payload, at least the 18-byte
Expand All @@ -37,10 +48,11 @@ pub fn run(transport: impl tinyboot_core::traits::Transport, ctl: BootCtl) -> !
pub fn run_with_payload<const P: usize>(
transport: impl tinyboot_core::traits::Transport,
ctl: BootCtl,
addressing: impl Addressing,
) -> ! {
const {
assert!(P >= InfoData::SIZE as usize);
}
let platform = Platform::new(transport, Storage::default(), BootMetaStore::default(), ctl);
tinyboot_core::Core::<_, _, _, _, PAGE_SIZE, P>::new(platform).run()
tinyboot_core::Core::<_, _, _, _, PAGE_SIZE, P, _>::with_addressing(platform, addressing).run()
}
11 changes: 11 additions & 0 deletions ch32/src/hal/esig.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//! ESIG electronic signature: the factory-programmed 96-bit unique id in
//! system flash, at the same address on every supported family.

const UNIID: *const u8 = 0x1FFF_F7E8 as *const u8;

/// The chip's 96-bit unique id, 12 bytes least significant first.
#[inline(always)]
pub fn uid() -> &'static [u8; 12] {
// SAFETY: ESIG is always-readable factory flash, at any access width.
unsafe { &*(UNIID as *const [u8; 12]) }
}
1 change: 1 addition & 0 deletions ch32/src/hal/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod generated {
pub use generated::{Pin, UsartMapping};

pub mod afio;
pub mod esig;
pub mod flash;
pub mod gpio;
pub mod iwdg;
Expand Down
22 changes: 22 additions & 0 deletions ch32/src/hal/usart/usart_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,28 @@ pub fn write_byte(r: ch32_metapac::usart::Usart, byte: u8) {
r.datar().write(|w| w.set_dr(byte as u16));
}

/// Spin at least `n` byte-times: BRR is PCLK cycles per bit, each spin
/// takes at least one cycle, and a byte is counted as 16 bits.
#[inline(always)]
pub fn wait_bytes(r: ch32_metapac::usart::Usart, n: u8) {
let bit = r.brr().read().0;
// Nested, not one n * 16 * bit count: no multiply on V003.
let mut bits = (n as u32) << 4;
while bits != 0 {
// Bottom-tested: an enabled USART's BRR is at least 16.
let mut c = bit;
loop {
// SAFETY: empty asm, only keeps the loop.
unsafe { core::arch::asm!("") };
c -= 1;
if c == 0 {
break;
}
}
bits -= 1;
}
}

#[inline(always)]
pub fn flush(r: ch32_metapac::usart::Usart) {
while !r.statr().read().tc() {}
Expand Down
7 changes: 6 additions & 1 deletion ch32/src/platform/transport/usart/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,12 @@ pub struct Usart<W: Wire, E: TxEnable> {
tx_en: E::Switch,
}

impl<W: Wire, E: TxEnable> tinyboot_core::traits::Transport for Usart<W, E> {}
impl<W: Wire, E: TxEnable> tinyboot_core::traits::Transport for Usart<W, E> {
#[inline(always)]
fn wait_bytes(&mut self, n: u8) {
usart::wait_bytes(self.regs, n);
}
}

impl<W: Wire, E: TxEnable> Usart<W, E> {
#[cfg_attr(split_sysflash, inline(never))]
Expand Down
13 changes: 12 additions & 1 deletion examples/ch32/v003/app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ fn main() -> ! {
tim.start();
unsafe { ch32_hal::interrupt::TIM2.enable() };

let mut app = tinyboot_ch32::app::new_app(tinyboot_ch32::app::BootCtl::new());
let mut app =
tinyboot_ch32::app::new_addressable_app(tinyboot_ch32::app::BootCtl::new(), wait_bytes);

// USART1 blocking — must match the bootloader's pin mapping.
// ch32-hal generic param picks the remap:
Expand Down Expand Up @@ -85,6 +86,16 @@ fn main() -> ! {
}
}

/// Stagger `Select` replies: about `n` byte-times at USART1's baud (BRR
/// is clock cycles per bit).
fn wait_bytes(n: u8) {
let bit = pac::USART1.brr().read().0;
for _ in 0..n as u32 * 10 * bit {
// SAFETY: empty asm, only keeps the loop.
unsafe { core::arch::asm!("") };
}
}

#[qingke_rt::interrupt]
fn TIM2() {
pac::TIM2.intfr().modify(|w| w.set_uif(false));
Expand Down
35 changes: 35 additions & 0 deletions examples/ch32/v003/boot/src/bin/heaviest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//! Size gate, built by CI beside the example: every option that costs
//! flash at once (one-wire push-pull, DE pin, compatibility key,
//! addressable, a 256-byte payload). Its system-flash build must still
//! link into the 1920-byte system flash.

#![no_std]
#![no_main]

use panic_halt as _;
use tinyboot_ch32_rt as _;

tinyboot_ch32::boot::boot_version!(compat = "tinyboot-example-v003");

use tinyboot_ch32::boot::prelude::*;

#[unsafe(export_name = "main")]
fn main() -> ! {
let transport = Usart::new(&UsartConfig {
baud: BaudRate::B115200,
pclk: 8_000_000,
mapping: UsartMapping::Usart1Remap0,
wire: HalfDuplex {
drive: PushPullWhileSending,
},
tx_en: TxEn {
pin: Pin::PC2,
tx_level: Level::Low,
},
});
tinyboot_ch32::boot::run_with_payload::<256>(
transport,
BootCtl::new(),
Addressable::new(chip_uid),
);
}
5 changes: 4 additions & 1 deletion examples/ch32/v003/boot/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,8 @@ fn main() -> ! {
tx_level: Level::Low,
},
});
tinyboot_ch32::boot::run(transport, BootCtl::new());
// Addressable: silent on a shared bus until a host selects this chip by
// its unique id; the CLI does that for a single device on its own. One
// device per bus and nothing to select: `Unaddressed`.
tinyboot_ch32::boot::run(transport, BootCtl::new(), Addressable::new(chip_uid));
}
13 changes: 12 additions & 1 deletion examples/ch32/v00x/app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ fn main() -> ! {
tim.start();
unsafe { ch32_hal::interrupt::TIM2.enable() };

let mut app = tinyboot_ch32::app::new_app(tinyboot_ch32::app::BootCtl::new());
let mut app =
tinyboot_ch32::app::new_addressable_app(tinyboot_ch32::app::BootCtl::new(), wait_bytes);

// USART1 blocking — must match the bootloader's pin mapping.
// ch32-hal generic param picks the remap:
Expand Down Expand Up @@ -88,6 +89,16 @@ fn main() -> ! {
}
}

/// Stagger `Select` replies: about `n` byte-times at USART1's baud (BRR
/// is clock cycles per bit).
fn wait_bytes(n: u8) {
let bit = pac::USART1.brr().read().0;
for _ in 0..n as u32 * 10 * bit {
// SAFETY: empty asm, only keeps the loop.
unsafe { core::arch::asm!("") };
}
}

#[qingke_rt::interrupt]
fn TIM2() {
pac::TIM2.intfr().modify(|w| w.set_uif(false));
Expand Down
5 changes: 4 additions & 1 deletion examples/ch32/v00x/boot/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,8 @@ fn main() -> ! {
tx_level: Level::High,
},
});
tinyboot_ch32::boot::run(transport, BootCtl::new());
// Addressable: silent on a shared bus until a host selects this chip by
// its unique id; the CLI does that for a single device on its own. One
// device per bus and nothing to select: `Unaddressed`.
tinyboot_ch32::boot::run(transport, BootCtl::new(), Addressable::new(chip_uid));
}
25 changes: 18 additions & 7 deletions examples/ch32/v103/app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,17 @@ fn main() -> ! {

// system-flash: pass BOOT0 pin, the level that selects system flash,
// and the reset delay in cycles (RC ~1ms @ 8MHz = 8000; flip-flop: 0).
let mut app = tinyboot_ch32::app::new_app(core::cfg_select! {
feature = "system-flash" => {
tinyboot_ch32::app::BootCtl::new(
let mut app = tinyboot_ch32::app::new_addressable_app(
core::cfg_select! {
feature = "system-flash" => tinyboot_ch32::app::BootCtl::new(
tinyboot_ch32::app::Pin::PB1,
tinyboot_ch32::app::Level::High,
8000,
)
}
_ => tinyboot_ch32::app::BootCtl::new(),
});
),
_ => tinyboot_ch32::app::BootCtl::new(),
},
wait_bytes,
);

// USART1 blocking — must match the bootloader's pin mapping.
// ch32-hal generic param picks the remap:
Expand Down Expand Up @@ -94,6 +95,16 @@ fn main() -> ! {
}
}

/// Stagger `Select` replies: about `n` byte-times at USART1's baud (BRR
/// is clock cycles per bit).
fn wait_bytes(n: u8) {
let bit = pac::USART1.brr().read().0;
for _ in 0..n as u32 * 10 * bit {
// SAFETY: empty asm, only keeps the loop.
unsafe { core::arch::asm!("") };
}
}

#[qingke_rt::interrupt]
fn TIM2() {
pac::TIM2.intfr().modify(|w| w.set_uif(false));
Expand Down
5 changes: 4 additions & 1 deletion examples/ch32/v103/boot/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,8 @@ fn main() -> ! {
_ => BootCtl::new(),
};

tinyboot_ch32::boot::run(transport, ctl);
// Addressable: silent on a shared bus until a host selects this chip by
// its unique id; the CLI does that for a single device on its own. One
// device per bus and nothing to select: `Unaddressed`.
tinyboot_ch32::boot::run(transport, ctl, Addressable::new(chip_uid));
}
Loading