From c40e5925f1c9fde08df8dc87ac6fa2319ee52300 Mon Sep 17 00:00:00 2001 From: Aaron Qian Date: Thu, 7 May 2026 02:06:00 -0700 Subject: [PATCH 1/5] expand BaudRate enum upto 3Mbps. --- ch32/src/platform/transport/usart/mod.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ch32/src/platform/transport/usart/mod.rs b/ch32/src/platform/transport/usart/mod.rs index cbc87fb..409ddbe 100644 --- a/ch32/src/platform/transport/usart/mod.rs +++ b/ch32/src/platform/transport/usart/mod.rs @@ -21,6 +21,15 @@ pub enum BaudRate { B38400 = 38400, B57600 = 57600, B115200 = 115200, + B230400 = 230_400, + B460800 = 460_800, + B500000 = 500_000, + B921600 = 921_600, + B1000000 = 1_000_000, + B1500000 = 1_500_000, + B2000000 = 2_000_000, + B2500000 = 2_500_000, + B3000000 = 3_000_000, } /// RS-485 DE/RE pin configuration. From 6027bed7f045881efd8e949525f614784baead31 Mon Sep 17 00:00:00 2001 From: Aaron Qian Date: Thu, 7 May 2026 02:11:52 -0700 Subject: [PATCH 2/5] re-export metapac for bootloader main.rs register customizations. --- ch32/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ch32/src/lib.rs b/ch32/src/lib.rs index 2e6b1e6..aba5195 100644 --- a/ch32/src/lib.rs +++ b/ch32/src/lib.rs @@ -32,3 +32,5 @@ pub mod app; pub mod boot; pub mod hal; pub mod platform; + +pub use ch32_metapac as pac; From 309b811a20f1046baa601fc2f1f24c65185a1015 Mon Sep 17 00:00:00 2001 From: Aaron Qian Date: Thu, 7 May 2026 02:15:37 -0700 Subject: [PATCH 3/5] eliminate echo for tinyboot cli when talking to single wire. --- cli/src/client.rs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/cli/src/client.rs b/cli/src/client.rs index e665154..cca1ad3 100644 --- a/cli/src/client.rs +++ b/cli/src/client.rs @@ -52,10 +52,22 @@ impl Client { self.frame .send(&mut self.transport) .map_err(|_| FlashError::Io)?; - let parse_status = self - .frame - .read(&mut self.transport) - .map_err(|_| FlashError::Io)?; + + // On single-wire setups like the OSC-Dev-V006 board — where MCU TX/RX + // and the host's TX/RX all land on the same DXL-TTL data line — the + // host hears its own request frame before the device replies. Skip + // frames whose status is `Request`: those can only be our own echo, + // since devices never reply with that status. + let parse_status = loop { + let s = self + .frame + .read(&mut self.transport) + .map_err(|_| FlashError::Io)?; + if s == Status::Ok && self.frame.status == Status::Request { + continue; + } + break s; + }; debug!( "<< {:?} status={:?} len={}", From f63cdc6a7068d1b2027a2fd709286417289f5e17 Mon Sep 17 00:00:00 2001 From: Aaron Qian Date: Thu, 7 May 2026 02:15:50 -0700 Subject: [PATCH 4/5] update docs --- cli/README.md | 4 ++++ docs/transports.md | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/cli/README.md b/cli/README.md index 628a930..0d1b778 100644 --- a/cli/README.md +++ b/cli/README.md @@ -62,6 +62,10 @@ tinyboot bin firmware.elf -o firmware.bin If `--port` is omitted, the CLI probes USB serial ports (usbmodem, ttyACM, ttyUSB) by sending an Info command with a 100ms timeout. Non-USB serial ports are skipped. Both the bootloader and apps running `poll()` respond to Info, so auto-detection works in either mode. +## Single-wire bus echo + +On a single-wire bus where the host's TX and RX are tied to the data line (DXL chains, most RS-485 hookups), the host hears its own request frame before the device replies. The CLI skips any frame whose status is `Request` — devices never reply with that status — so single-wire setups work without extra flags. + ## ELF handling When given an ELF file, the CLI extracts ALLOC sections using physical addresses (LMA) from PT_LOAD segments. Sections named `.uninit*` are skipped. LMAs below `0x0800_0000` are adjusted by adding the CH32 flash base offset. diff --git a/docs/transports.md b/docs/transports.md index 6a4fcd3..6e17d30 100644 --- a/docs/transports.md +++ b/docs/transports.md @@ -76,6 +76,16 @@ When configured, the driver toggles the direction pin around every frame: This keeps the transceiver in RX the rest of the time, so host bytes can reach the MCU's RX pin without contention. +## Baud rate + +`BaudRate` covers the standard ladder from 9600 up to 3 Mbps: `B9600`, `B19200`, `B38400`, `B57600`, `B115200`, `B230400`, `B460800`, `B500000`, `B921600`, `B1000000`, `B1500000`, `B2000000`, `B2500000`, `B3000000`. + +The achievable accuracy depends on `pclk`: the USART divisor is `pclk / baud`, so non-integer ratios accumulate framing error. For high baud rates you usually need to bump the core clock — e.g. the V00x example calls `rcc::init_48mhz_hsi_pll()` so PCLK = 48 MHz, which divides exactly to 3 Mbps. The CH32V003's reset-default 8 MHz PCLK is fine up to ~115200 but not for the megabit rates. + +## Single-wire buses (DXL daisy chains, RS-485 segments) + +On a single-wire bus where the host's TX and RX are also tied to the data line — typical for DXL chains and most RS-485 hookups — the host hears its own request frame echoed back before the device replies. The shipped `tinyboot` CLI handles this automatically by skipping any frame whose status is `Request` (devices never reply with that status). No host-side configuration needed; just match the device's baud and `tx_en` polarity. + ## Pin remaps `UsartMapping` picks the AFIO remap and selects which physical pins carry TX / RX. Available mappings are codegen'd per chip — check the generated `UsartMapping` enum in `tinyboot-ch32`, and cross-reference against the USART / AFIO sections of your chip's datasheet for the pin assignments. From 3238728392f3eb882cbc1f2b278e05995db0de4d Mon Sep 17 00:00:00 2001 From: Aaron Qian Date: Thu, 7 May 2026 03:46:25 -0700 Subject: [PATCH 5/5] set v00x to use DATA line --- examples/ch32/v00x/app/src/main.rs | 8 ++++---- examples/ch32/v00x/boot/src/main.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/ch32/v00x/app/src/main.rs b/examples/ch32/v00x/app/src/main.rs index c70af5d..3bfe0c4 100644 --- a/examples/ch32/v00x/app/src/main.rs +++ b/examples/ch32/v00x/app/src/main.rs @@ -59,10 +59,10 @@ fn main() -> ! { let (tx, rx) = uart.split(); let mut rx = transport::Rx(rx); - // RS-485 DE/RE on PC2, matching the bootloader. tx_level=Low means idle-RX - // drives the pin High (inverse), which tri-states U4A on the V006 dev board - // so LinkE UART TX can reach MCU_RX without contention. - let tx_level = Level::Low; + // RS-485 DE/RE on PC2, matching the bootloader. Active-high DE: pin goes + // High during TX so the on-board transceiver drives the DXL-TTL data line, + // and Low at idle so the MCU listens. + let tx_level = Level::High; let tx_en_pin = Output::new(p.PC2, transport::invert(tx_level), Default::default()); let mut tx = transport::Tx { uart: tx, diff --git a/examples/ch32/v00x/boot/src/main.rs b/examples/ch32/v00x/boot/src/main.rs index 200e91c..466a982 100644 --- a/examples/ch32/v00x/boot/src/main.rs +++ b/examples/ch32/v00x/boot/src/main.rs @@ -39,7 +39,7 @@ fn main() -> ! { pclk: 8_000_000, mapping: UsartMapping::Usart1Remap3, rx_pull: Pull::None, - tx_en: Some(TxEnConfig { pin: Pin::PC2, tx_level: Level::Low }), + tx_en: Some(TxEnConfig { pin: Pin::PC2, tx_level: Level::High }), }); tinyboot_ch32::boot::run(transport, BootCtl::new()); }