Severity: CRITICAL — every CH32V board boots at its reset-default HSI clock, 2–18× slower than spec. FastLED clockless LED timing (the flagship downstream consumer) is broken on all parts, and the failure hides from a plain blink test.
Problem, in plain terms
The OpenWCH Arduino core does not configure the PLL by itself. It expects the build system to pass a macro like -DSYSCLK_FREQ_144MHz_HSI=144000000 — that macro selects which SetSysClock() branch gets compiled. At the pinned core commit (d767162), every system/<series>/USER/system_*.c ships with all SYSCLK_FREQ_* lines commented out, and the file says: "If none of the define below is enabled, the HSI is used as System clock source (default after reset)". So with no macro, SetSysClock() compiles to a no-op and the chip stays on raw HSI.
fbuild's board JSONs carry exactly the data needed (build.f_cpu + build.clock_source in all 18 CH32 JSONs) — but no Rust code reads clock_source, and nothing emits a SYSCLK_FREQ_* define. grep -r SYSCLK crates/ → zero hits (verified). The orchestrator (crates/fbuild-build-mcu/src/ch32v/orchestrator.rs:122-131) emits only F_CPU and the board extra_flags.
PlatformIO's community platform synthesizes the macro in builder/frameworks/common_clk_config.py from the same two fields; the vendor boards.txt clock menu does the equivalent. fbuild is the odd one out.
Worked example (what the bench will show)
CH32V203 board, f_cpu = 144000000:
- fbuild passes
-DF_CPU=144000000L and no SYSCLK define (verified in a real compile response file).
- Firmware boots at 8 MHz HSI instead of 144 MHz — 18× slow.
SystemCoreClock is set to HSI_VALUE by the fallback branch, so millis()/delay()/UART baud are all self-consistent — blink and serial look normal.
- But anything compiled against
F_CPU believes it runs at 144 MHz. FastLED's cycle-counted WS281x driver emits waveforms 18× too slow → dead/garbage LEDs.
Per-family reset-default vs claimed: V003/V006 24 MHz vs 48 (2×); V103 8 vs 72 (9×); V203/V208/V303/V307 8 vs 144 (18×); L103 8 vs 96 (12×); X035 48 MHz HSI but SystemInit sets HPRE=/6 → X035 blink is visibly ~6× slow (the one family where a naive test catches it).
Fix direction
In the CH32V orchestrator, map f_cpu + clock_source → the series' exact macro name and insert into defines alongside F_CPU. Two traps:
- Spelling differs per series/file: pinned
CH32V00x/CH32VM00X use SYSCLK_FREQ_48MHz_HSI (MHz), the 1.0.4-era V00x file used SYSCLK_FREQ_48MHZ_HSI (MHZ). Take the exact strings from each pinned USER/system_*.c — do not generate blindly.
- Validate
f_cpu against each series' supported PLL table (e.g. V203 supports 48/56/72/96/120/144 HSI) and fail loudly on an unsupported value instead of silently booting at HSI.
Model on PIO's common_clk_config.py (same input fields).
Acceptance
- Compile response file for
tests/platform/ch32v203 contains -DSYSCLK_FREQ_144MHz_HSI=144000000 (exact spelling from the pinned system_ch32v20x.c).
- All 9
tests/platform/ch32* envs build.
- Bench: X035 blink runs at correct speed; a V203 board drives WS2812 LEDs correctly via FastLED.
- Unit test: unsupported
f_cpu for a series → hard error naming the supported values.
Found during the 2026-07-21 CH32V bench-readiness sweep. Independently confirmed by two audit passes (code sweep + board-data sweep) and against the pinned core sources.
Severity: CRITICAL — every CH32V board boots at its reset-default HSI clock, 2–18× slower than spec. FastLED clockless LED timing (the flagship downstream consumer) is broken on all parts, and the failure hides from a plain blink test.
Problem, in plain terms
The OpenWCH Arduino core does not configure the PLL by itself. It expects the build system to pass a macro like
-DSYSCLK_FREQ_144MHz_HSI=144000000— that macro selects whichSetSysClock()branch gets compiled. At the pinned core commit (d767162), everysystem/<series>/USER/system_*.cships with allSYSCLK_FREQ_*lines commented out, and the file says: "If none of the define below is enabled, the HSI is used as System clock source (default after reset)". So with no macro,SetSysClock()compiles to a no-op and the chip stays on raw HSI.fbuild's board JSONs carry exactly the data needed (
build.f_cpu+build.clock_sourcein all 18 CH32 JSONs) — but no Rust code readsclock_source, and nothing emits aSYSCLK_FREQ_*define.grep -r SYSCLK crates/→ zero hits (verified). The orchestrator (crates/fbuild-build-mcu/src/ch32v/orchestrator.rs:122-131) emits onlyF_CPUand the boardextra_flags.PlatformIO's community platform synthesizes the macro in
builder/frameworks/common_clk_config.pyfrom the same two fields; the vendorboards.txtclock menu does the equivalent. fbuild is the odd one out.Worked example (what the bench will show)
CH32V203 board,
f_cpu = 144000000:-DF_CPU=144000000Land no SYSCLK define (verified in a real compile response file).SystemCoreClockis set toHSI_VALUEby the fallback branch, somillis()/delay()/UART baud are all self-consistent — blink and serial look normal.F_CPUbelieves it runs at 144 MHz. FastLED's cycle-counted WS281x driver emits waveforms 18× too slow → dead/garbage LEDs.Per-family reset-default vs claimed: V003/V006 24 MHz vs 48 (2×); V103 8 vs 72 (9×); V203/V208/V303/V307 8 vs 144 (18×); L103 8 vs 96 (12×); X035 48 MHz HSI but SystemInit sets HPRE=/6 → X035 blink is visibly ~6× slow (the one family where a naive test catches it).
Fix direction
In the CH32V orchestrator, map
f_cpu+clock_source→ the series' exact macro name and insert intodefinesalongsideF_CPU. Two traps:CH32V00x/CH32VM00XuseSYSCLK_FREQ_48MHz_HSI(MHz), the 1.0.4-era V00x file usedSYSCLK_FREQ_48MHZ_HSI(MHZ). Take the exact strings from each pinnedUSER/system_*.c— do not generate blindly.f_cpuagainst each series' supported PLL table (e.g. V203 supports 48/56/72/96/120/144 HSI) and fail loudly on an unsupported value instead of silently booting at HSI.Model on PIO's
common_clk_config.py(same input fields).Acceptance
tests/platform/ch32v203contains-DSYSCLK_FREQ_144MHz_HSI=144000000(exact spelling from the pinnedsystem_ch32v20x.c).tests/platform/ch32*envs build.f_cpufor a series → hard error naming the supported values.Found during the 2026-07-21 CH32V bench-readiness sweep. Independently confirmed by two audit passes (code sweep + board-data sweep) and against the pinned core sources.