Skip to content

Commit c1bb273

Browse files
committed
store the bootloader baud in boot metadata, settable with Reset
1 parent ad78adb commit c1bb273

27 files changed

Lines changed: 516 additions & 87 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,42 @@ Releases up through `v0.4.0` were workspace-wide and tagged `vX.Y.Z`. From `0.4.
44

55
## [Unreleased]
66

7+
## [tinyboot 0.5.0] - 2026-10-20
8+
9+
### Added
10+
11+
- `tinyboot baud <rate>` stores the bootloader's baud and resets the device back into its current mode; in bootloader mode it reconnects at the new rate to check the bootloader answers. `tinyboot baud default` restores the build default.
12+
13+
## [tinyboot-ch32 0.7.0] - 2026-10-20
14+
15+
### Added
16+
17+
- 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.
18+
- `app::BaudRate` re-export for `App::set_baud` / `App::baud`.
19+
20+
### Changed
21+
22+
- **Breaking:** `BaudRate` is `tinyboot_protocol::BaudRate`, a `u8` index enum with `hz()`, instead of a `u32` enum whose discriminant was the rate.
23+
- **Breaking:** boot metadata is 12 bytes: a `baud` index byte (`0xFF` = build default) and 3 reserved bytes follow `app_size`. Existing metadata reads as the build default.
24+
25+
## [tinyboot-core 0.6.0] - 2026-10-20
26+
27+
### Added
28+
29+
- `Reset` with `ResetFlags::BAUD` stores addr byte 0 as the bootloader's baud before resetting, in the bootloader and in `App::poll`.
30+
- `App::baud` returns the stored bootloader baud; `App::set_baud` stores one, skipping the flash write when it is already stored.
31+
32+
### Changed
33+
34+
- **Breaking:** `BootMetaStore` has `baud` and `set_baud`, and `refresh` keeps the stored baud.
35+
36+
## [tinyboot-protocol 0.5.0] - 2026-10-20
37+
38+
### Added
39+
40+
- `BaudRate`, the 14 rates from 9600 to 3M as append-only `u8` indices, with `hz`, `from_u8`, `from_hz` and `ALL`.
41+
- `ResetFlags::BAUD`: store addr byte 0 as the bootloader's baud before resetting.
42+
743
## [tinyboot-protocol 0.4.1] - 2026-10-19
844

945
### Fixed

‎ch32/Cargo.toml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "tinyboot-ch32"
33
description = "tinyboot ch32 implementation"
4-
version = "0.6.0"
4+
version = "0.7.0"
55
edition.workspace = true
66
repository.workspace = true
77
license.workspace = true
@@ -56,8 +56,8 @@ repository = "https://github.com/OpenServoCore/tinyboot"
5656
license = "MIT OR Apache-2.0"
5757

5858
[workspace.dependencies]
59-
tinyboot-core = { version = "0.5.0", path = "../lib/core", default-features = false }
60-
tinyboot-ch32 = { version = "0.6.0", path = ".", default-features = false }
59+
tinyboot-core = { version = "0.6.0", path = "../lib/core", default-features = false }
60+
tinyboot-ch32 = { version = "0.7.0", path = ".", default-features = false }
6161
embedded-hal = "1"
6262
embedded-io = "0.7"
6363
embedded-storage = "0.3"

‎ch32/src/app.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::platform::BootMetaStore;
66
pub use crate::hal::Pin;
77
pub use crate::hal::gpio::Level;
88
pub use crate::platform::BootCtl;
9+
pub use tinyboot_core::BaudRate;
910
pub use tinyboot_core::app::{App, AppConfig};
1011
pub use tinyboot_core::traits;
1112
pub use tinyboot_core::{app_version, pkg_version};

‎ch32/src/hal/usart/usart_common.rs‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
pub use ch32_metapac::usart::Usart as Regs;
22

33
#[inline(always)]
4-
pub fn init(r: Regs, pclk: u32, baud: u32, half_duplex: bool) {
4+
pub fn init(r: Regs, brr: u32, half_duplex: bool) {
55
// HDSEL before TE — the reverse order latches the TX output LOW until
66
// the first own transmission, clamping the wire from init.
77
if half_duplex {
@@ -14,7 +14,6 @@ pub fn init(r: Regs, pclk: u32, baud: u32, half_duplex: bool) {
1414
w.set_re(true);
1515
});
1616

17-
let brr = (pclk + baud / 2) / baud;
1817
r.brr().write_value(ch32_metapac::usart::regs::Brr(brr));
1918

2019
r.ctlr1().modify(|w| w.set_ue(true));

‎ch32/src/platform/boot_meta_store.rs‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ pub struct BootMetaStore {
1717
trials: u8,
1818
checksum: u16,
1919
app_size: u32,
20+
baud: u8,
21+
// Pads the flash write to whole words; carried as read.
22+
_reserved: [u8; 3],
23+
}
24+
25+
/// Stored bootloader baud index, read straight from flash.
26+
#[inline(always)]
27+
pub fn stored_baud() -> u8 {
28+
let addr = flash::meta_addr() as usize + core::mem::offset_of!(BootMetaStore, baud);
29+
unsafe { core::ptr::read_volatile(addr as *const u8) }
2030
}
2131

2232
impl Default for BootMetaStore {
@@ -72,6 +82,16 @@ impl TBBootMetaStore for BootMetaStore {
7282
self.app_size
7383
}
7484

85+
fn baud(&self) -> u8 {
86+
self.baud
87+
}
88+
89+
fn set_baud(&mut self, baud: u8) -> Result<(), Self::Error> {
90+
self.baud = baud;
91+
self.write();
92+
Ok(())
93+
}
94+
7595
fn advance(&mut self) -> Result<BootState, Self::Error> {
7696
let next = self
7797
.step_down(0, BootState::Validating as u8)

‎ch32/src/platform/transport/usart/mod.rs‎

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,47 @@ use crate::hal::gpio::PinMode;
77
use crate::hal::gpio::Pull;
88
use crate::hal::{Pin, UsartMapping};
99
use crate::hal::{afio, gpio, rcc, usart};
10+
use crate::platform::boot_meta_store;
1011

11-
#[derive(Copy, Clone)]
12-
#[repr(u32)]
13-
pub enum BaudRate {
14-
B9600 = 9600,
15-
B19200 = 19200,
16-
B38400 = 38400,
17-
B57600 = 57600,
18-
B115200 = 115200,
19-
B230400 = 230_400,
20-
B460800 = 460_800,
21-
B500000 = 500_000,
22-
B921600 = 921_600,
23-
B1000000 = 1_000_000,
24-
B1500000 = 1_500_000,
25-
B2000000 = 2_000_000,
26-
B2500000 = 2_500_000,
27-
B3000000 = 3_000_000,
12+
pub use tinyboot_core::BaudRate;
13+
14+
/// Rounded BRR for `hz`; folds to a constant when both args are.
15+
#[inline(always)]
16+
const fn brr(pclk: u32, hz: u32) -> u32 {
17+
(pclk + hz / 2) / hz
18+
}
19+
20+
/// BRR for the stored baud index, or `default` when the index is unknown
21+
/// or the rate is out of reach (BRR < 16) from `pclk`. Every arm divides
22+
/// constants, so this compiles to a table lookup with no runtime divide.
23+
#[inline(always)]
24+
fn stored_brr(pclk: u32, default: u32) -> u32 {
25+
macro_rules! arms {
26+
($idx:expr, $($b:ident),*) => {
27+
match $idx {
28+
$(i if i == BaudRate::$b as u8 => brr(pclk, BaudRate::$b.hz()),)*
29+
_ => default,
30+
}
31+
};
32+
}
33+
let v = arms!(
34+
boot_meta_store::stored_baud(),
35+
B9600,
36+
B19200,
37+
B38400,
38+
B57600,
39+
B115200,
40+
B230400,
41+
B460800,
42+
B500000,
43+
B921600,
44+
B1000000,
45+
B1500000,
46+
B2000000,
47+
B2500000,
48+
B3000000
49+
);
50+
if v < 16 { default } else { v }
2851
}
2952

3053
/// RS-485 DE/RE pin configuration.
@@ -36,6 +59,9 @@ pub struct TxEnConfig {
3659
}
3760

3861
pub struct UsartConfig {
62+
/// Build default. A baud stored in boot metadata (`Reset` with the
63+
/// `BAUD` flag, or the app's `set_baud`) overrides it when `pclk`
64+
/// can reach it.
3965
pub baud: BaudRate,
4066
/// USART peripheral clock (Hz). Used for BRR = pclk / baud.
4167
///
@@ -104,12 +130,8 @@ impl Usart {
104130
gpio::set_level(tx_en.pin, invert(tx_en.tx_level));
105131
}
106132

107-
usart::init(
108-
regs,
109-
config.pclk,
110-
config.baud as u32,
111-
cfg!(feature = "half-duplex"),
112-
);
133+
let brr = stored_brr(config.pclk, brr(config.pclk, config.baud.hz()));
134+
usart::init(regs, brr, cfg!(feature = "half-duplex"));
113135

114136
Usart {
115137
regs,

‎cli/Cargo.lock‎

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎cli/Cargo.toml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "tinyboot"
33
description = "CLI firmware flasher for tinyboot bootloader"
4-
version = "0.4.1"
4+
version = "0.5.0"
55
edition = "2024"
66
repository = "https://github.com/OpenServoCore/tinyboot"
77
license = "MIT OR Apache-2.0"
@@ -14,7 +14,7 @@ name = "tinyboot"
1414
path = "src/main.rs"
1515

1616
[dependencies]
17-
tinyboot-protocol = { version = "0.4.0", path = "../lib/protocol" }
17+
tinyboot-protocol = { version = "0.5.0", path = "../lib/protocol" }
1818
embedded-io = "0.7"
1919
serialport = "4"
2020
clap = { version = "4", features = ["derive"] }

‎cli/README.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ Reset the device. Use `--bootloader` to reboot into the bootloader instead of th
5050
tinyboot reset [--port /dev/ttyUSB0] [--baud 115200] [--bootloader]
5151
```
5252

53+
### `tinyboot baud`
54+
55+
Store the baud the bootloader runs at from its next start, then reset the device back into its current mode. In bootloader mode the CLI reconnects at the new rate to check that the bootloader answers. `default` restores the bootloader's build default.
56+
57+
```sh
58+
tinyboot baud 1000000 [--port /dev/ttyUSB0] [--baud 115200]
59+
tinyboot baud default [--port /dev/ttyUSB0] [--baud 1000000]
60+
```
61+
5362
### `tinyboot bin`
5463

5564
Convert an ELF to a flat binary (same extraction logic as `flash`).

‎cli/src/client.rs‎

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -239,15 +239,22 @@ impl<T: embedded_io::Read + embedded_io::Write> Client<T> {
239239
/// Reset the device. Does not wait for a response since the device resets immediately.
240240
/// `bootloader=true`: enter bootloader. `bootloader=false`: boot app.
241241
pub fn reset(&mut self, bootloader: bool) {
242+
self.send_reset(bootloader, None);
243+
}
244+
245+
/// Store `baud` (a `BaudRate` index; `0xFF` = the build default) for
246+
/// the bootloader, then reset into `bootloader` or the app.
247+
pub fn set_baud(&mut self, baud: u8, bootloader: bool) {
248+
self.send_reset(bootloader, Some(baud));
249+
}
250+
251+
fn send_reset(&mut self, bootloader: bool, baud: Option<u8>) {
252+
let mut flags = ResetFlags::empty();
253+
flags.set(ResetFlags::BOOTLOADER, bootloader);
254+
flags.set(ResetFlags::BAUD, baud.is_some());
242255
self.frame.cmd = Cmd::Reset;
243-
self.frame.set_addr(0);
244-
self.frame.flags = Flags {
245-
reset: if bootloader {
246-
ResetFlags::BOOTLOADER
247-
} else {
248-
ResetFlags::empty()
249-
},
250-
};
256+
self.frame.set_addr(baud.unwrap_or(0) as u32);
257+
self.frame.flags = Flags { reset: flags };
251258
self.frame.len = 0;
252259
self.frame.status = Status::Request;
253260
let _ = self.frame.send(&mut self.transport);

0 commit comments

Comments
 (0)