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 ch32/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ pub mod app;
pub mod boot;
pub mod hal;
pub mod platform;

pub use ch32_metapac as pac;
9 changes: 9 additions & 0 deletions ch32/src/platform/transport/usart/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
20 changes: 16 additions & 4 deletions cli/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,22 @@ impl<T: embedded_io::Read + embedded_io::Write> Client<T> {
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={}",
Expand Down
10 changes: 10 additions & 0 deletions docs/transports.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 4 additions & 4 deletions examples/ch32/v00x/app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion examples/ch32/v00x/boot/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}