Add CH32H4 family (CH32H417/H416/H415) support - #39
Conversation
There was a problem hiding this comment.
Pull request overview
Bootstraps CH32H4 family support in ch32-data, introducing dual-core (Qingke V3F + V5F) chip definitions plus the foundational peripheral/register descriptions (including USBFS/USBPD/USBSS, PFIC, RCC, timers, DMA/DMAMUX, etc.) and a full interrupt map for codegen/metapac generation.
Changes:
- Added CH32H4 chip definitions for 5 variants, each with V3F and V5F core configurations and included peripheral fragments.
- Added CH32H4 interrupt table and a set of new H4-specific peripheral fragments.
- Added many new H4 register descriptions (and a few common/shared ones) to drive code generation.
Reviewed changes
Copilot reviewed 39 out of 41 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| data/registers/wwdg_common.yaml | Adds common WWDG register description used by CH32H4. |
| data/registers/usbpd_h4.yaml | Adds CH32H4 USBPD register description. |
| data/registers/usbfs_h4.yaml | Adds CH32H4 USBFS (OTG FS-style) register description. |
| data/registers/usart_h4.yaml | Adds CH32H4 USART register description (incl. CTLR4/M_EXT/LPWKUP fields). |
| data/registers/timer_h4.yaml | Adds CH32H4 timer register descriptions (incl. AUX in place of V3 SPEC). |
| data/registers/systick_v3f_v5f.yaml | Adds dual-core SysTick register description shared by V3F/V5F. |
| data/registers/spi_h4.yaml | Adds CH32H4 SPI register description (incl. HSCR.HSRXEN2). |
| data/registers/rng_h4.yaml | Adds CH32H4 RNG register description. |
| data/registers/pwr_h4.yaml | Adds CH32H4 PWR register description. |
| data/registers/pfic_h4.yaml | Adds CH32H4 PFIC register description for the expanded vector/dual-core environment. |
| data/registers/opa_h4.yaml | Adds CH32H4 OPA/CMP register description. |
| data/registers/iwdg_h4.yaml | Adds CH32H4 IWDG register description. |
| data/registers/ipc_h4.yaml | Adds CH32H4 inter-processor communication (IPC) register description. |
| data/registers/hsem_h4.yaml | Adds CH32H4 hardware semaphore (HSEM) register description. |
| data/registers/hsadc_h4.yaml | Adds CH32H4 high-speed ADC (HSADC) register description. |
| data/registers/flash_h4.yaml | Adds CH32H4 FLASH register description (incl. boot mode key). |
| data/registers/esig_h4.yaml | Adds CH32H4 ESIG/UID register description. |
| data/registers/dmamux_h4.yaml | Adds CH32H4 DMAMUX register description. |
| data/registers/dma_h4.yaml | Adds CH32H4 DMA register description (incl. M1ADDR/double-buffer support). |
| data/registers/dfsdm_h4.yaml | Adds CH32H4 DFSDM register description. |
| data/registers/dac_h4.yaml | Adds CH32H4 DAC register description. |
| data/registers/crc_h4.yaml | Adds CH32H4 CRC register description. |
| data/registers/afio_h4.yaml | Adds CH32H4 AFIO register description (STM32F4-style AFR mux). |
| data/registers/adc_h4.yaml | Adds CH32H4 ADC register description (incl. AUX time register). |
| data/peripherals/H4_USBSS.yaml | Adds CH32H4 USBSS peripheral fragment with RCC + interrupts. |
| data/peripherals/H4_USBPD.yaml | Adds CH32H4 USBPD peripheral fragment with RCC + interrupts + pins. |
| data/peripherals/H4_USBFS.yaml | Adds CH32H4 USBFS peripheral fragment with RCC + interrupts + pins. |
| data/peripherals/H4_USART8.yaml | Adds CH32H4 USART8 peripheral fragment with RCC + interrupts + pins. |
| data/peripherals/H4_SPI4.yaml | Adds CH32H4 SPI4 peripheral fragment with RCC + interrupts + pins. |
| data/interrupts/CH32H4.yaml | Adds CH32H4 interrupt vector mapping. |
| data/chips/CH32H417WEU6.yaml | Adds CH32H417WEU6 chip definition (dual-core) and included peripherals. |
| data/chips/CH32H417QEU6.yaml | Adds CH32H417QEU6 chip definition (dual-core) and included peripherals. |
| data/chips/CH32H417MEU6.yaml | Adds CH32H417MEU6 chip definition (dual-core) and included peripherals. |
| data/chips/CH32H416RDU6.yaml | Adds CH32H416RDU6 chip definition (dual-core) and included peripherals. |
| data/chips/CH32H415REU6.yaml | Adds CH32H415REU6 chip definition (dual-core) and included peripherals. |
| .gitignore | Ignores .trash/ directory. |
Comments suppressed due to low confidence (1)
data/registers/esig_h4.yaml:23
- Typo in description: "capecity" → "capacity".
fieldset/FLACAP:
description: Flash capecity register.
bit_size: 16
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| - name: UEP6_TX_EN | ||
| description: enable USB endpoint 6 transmittal (IN). | ||
| bit_offset: 6 | ||
| bit_size: 1 | ||
| - name: UEP3_RX_EN | ||
| description: enable USB endpoint 6 receiving (OUT). | ||
| bit_offset: 7 | ||
| bit_size: 1 |
| description: channel configuration 0 register. | ||
| fields: | ||
| - name: SITP | ||
| description: Channel 0 Serial Interface Type. | ||
| bit_offset: 0 | ||
| bit_size: 1 | ||
| - name: SPICKSEL | ||
| description: Channel 0 SPI Clock Selection. | ||
| bit_offset: 2 | ||
| bit_size: 2 |
| - name: PGADIF1 | ||
| description: OPA2 is used with NSEL1 as PGA and the N-terminal is connected to OPA2_CHN1 (PA7). |
| - name: match_PID | ||
| description: PID value to match against each locked channel's PID for batch unlock. | ||
| bit_offset: 0 | ||
| bit_size: 8 | ||
| - name: match_CID |
| - name: auto_1step_clr | ||
| description: When set, a 1-step lock failure auto-clears the corresponding interrupt status bit. | ||
| bit_offset: 0 | ||
| bit_size: 1 | ||
| - name: auto_2step_clr |
| fieldset/AUX: | ||
| description: Double-side egde capture register. | ||
| bit_size: 16 | ||
| fields: | ||
| - name: CAP_ED_CH2 | ||
| description: Double-side egde capture is enable for channel2. | ||
| bit_offset: 0 |
| - name: FLACAP | ||
| description: Flash capecity register. | ||
| byte_offset: 0 |
|
Just a heads up, #36 has some structural changes such as:
If you'd like, I can wait for you to merge this one in, and I will rebase #36 and perform the structural migrations mentioned above. Let me know. Thanks! p.s. our probe-rs fork as well as #36 and ch32-rs/flash-algorithms#3 are finally stable. See my hardware tests on various chips. |
Extends the V3-era USART layout with the new features introduced on CH32H4 (currently CH32H417): - New CTLR4 register at byte offset 28 (MS_ERRIE, CHECK_SEL: MARK/SPACE check error interrupt enable and check function selection) - CTLR1.M_EXT bit 14, 2-bit: data length extension - CTLR3.LPWKUP_EN/_CK_SRC/_DLY_CFG bits 11-15: low-power wake-up enable, clock source and reception delay - STATR.RX_BUSY bit 10, MS_ERR bit 11, LPWKUP_ACT_FLAG bit 15: receive-busy indicator, MARK/SPACE check error flag and low-power wake-up indicator flag Common file usart_common.yaml is left untouched; CH32H4 chip yamls will reference \`version: h4\` for USART peripherals.
ch32-data did not yet ship a WWDG register file (no V-family chip yaml
referenced one). Add wwdg_common.yaml using the clean field naming
observed in the CH32H417 SVD:
- Register CFGR (not CFR — V30x SVD had this as a description typo).
- STATR.EWIF (Early Wakeup Interrupt Flag). The V30x patched SVD has
this field as WEIF; per the description ("Early Wakeup Interrupt
Flag") EWIF is the correct spelling, and CH32H417 SVD already uses
EWIF. Settle on EWIF as the canonical name so any future V-family
chip yaml that adopts WWDG inherits the correct identifier.
Common file only — no family-specific extensions yet. CH32H417 chips
will reference \`version: common\` for WWDG.
CH32H417 has a different SysTick than V2/V3/V4: two independent 32-bit up/down counters, each with its own enable / autoreload / clock source / interrupt enable, and a per-counter CID field that routes the interrupt to a specific hart (V3F or V5F). A single shared ISR register exposes the per-counter interrupt flags. The name reflects the *topology* (dual-counter, dual-core routable) rather than a specific Qingke core version — if a future WCH MCU exposes the same SysTick block, it can reuse \`version: dual_core\`. Block layout taken from CH32H417 RM V1.7 \xc2\xa74.6: STK_CTLR_0 @ 0x00, STK_ISR @ 0x04, STK_CNT_0 @ 0x08, STK_CMP_0 @ 0x10 STK_CTLR_1 @ 0x80, STK_CNT_1 @ 0x88, STK_CMP_1 @ 0x90 CTLR_0 and CTLR_1 share fieldset CTLR (EN, IE, NO_RTC, AUTO_RELOAD, DOWN_MODE, CID). NO_RTC uses enum STCLK (HCLK_DIV8 / HCLK), DOWN_MODE uses enum MODE (Upcount / Downcount).
CH32H417 HSEM is a 32-channel multi-master semaphore at 0xE000C000.
Each channel exposes:
- HSEM_RXn (0x000 + 4n): write-lock register, PID/CID/LOCK fields
- HSEM_RLRXn (0x100 + 4n): read-lock register, same fields RO
Plus a few control/status registers:
- LSE (0x200): global lock-status bitmap
- CLR (0x208): batch unlock with PID/CID match + key
- KEY (0x20C): unlock key (default 0x5AA5) + auto-clear flags
- IER/ISR/ISM (0x300/0x308/0x310): per-channel interrupt enable,
status (W1C) and mask (RO = IER & ISR)
- LSM (0x318): per-channel "locked by THIS hart" bitmap
Modeled with:
- Block-item array on RX and RLRX (len 32, stride 4), sharing a
single LOCK fieldset (RW for RX, RO for RLRX).
- Single CHBITS fieldset (CH[32]) reused by LSE / IER / ISR / ISM /
LSM so per-channel access reads naturally as \`ier.read().ch(n)\`.
- Dedicated CLR and KEY fieldsets for the specific bit layouts.
Field layout taken from CH32H417 RM V1.7 \xc2\xa74.5. CID is 2-bit per RM
(channels 0 and 1); the chiptool-extracted SVD has it as 4-bit which
is a SVD-side over-declaration — going with the RM value here.
CH32H417 IPC is a 4-channel inter-processor communication block at
0xE000D000. Each channel carries:
- 8 independent status / enable / set / clear bits per channel
- Programmable TX_CID and RX_CID for routing the TX/RX interrupts
to either hart at runtime
- TX_IER / RX_IER / AUTOEN / LOCK configuration in CTLR
Plus four 32-bit shared message slots (MSG0..MSG3) for short messages
without going through shared SRAM.
Modeled with:
- CTLR: six fields (TX_CID, RX_CID, TX_IER, RX_IER, AUTOEN, LOCK)
each as a 1-bit array with stride 8, so per-channel access is
\`ctlr.read().tx_ier(channel)\` etc.
- CHANNELS fieldset reused by ISR / ISM / ENA / STS / SET / CLR:
a single CH[4] field, each entry an 8-bit byte (channel n at
bits 8n..8n+7).
- MSG: block-item array (len 4, stride 4), single 32-bit DATA field.
Per-register access:
- ISR / ISM: read-only
- ENA / STS: read/write
- SET / CLR: write-only
Layout from CH32H417 RM V1.7 \xc2\xa74.4. This is the first IPC peripheral
in ch32-data and is currently CH32H4-specific; reusable by future
WCH dual-core MCUs with the same block.
First CH32H4-family chip yaml. Skeleton covers what we have register
files for so far: GPIOA..F (reusing gpio_v3 — diff-clean against H4),
SYSTICK (dual_core), HSEM (h4), IPC (h4), EXTI (common), WWDG (common).
RCC / AFIO / PFIC / DMA / FLASH and the remaining peripherals will land
incrementally as their *_h4.yaml register files get authored.
New files:
- data/family/CH32H4.yaml — shared peripheral list visible to both
V3F and V5F cores.
- data/interrupts/CH32H4.yaml — full 148-entry IRQ table from CH32H417
RM section 4.7. SysTick0/SysTick1/SW (vectors 12-14) are core
interrupts handled by qingke-rt and intentionally omitted.
- data/chips/CH32H417QEU6.yaml — QFN128 package variant. Declares two
cores (qingke-v3f and qingke-v5f), both including the same family
peripheral list and interrupt table. Memory map mirrors RM section
1.2: 960K Flash, 128K ITCM, 256K DTCM, 512K shared SRAM.
Pipeline verified end-to-end:
cargo run -p ch32-data-gen -> build/data/chips/CH32H417QEU6.json
cargo run -p ch32-metapac-gen -- -> build/ch32-metapac/src/chips/ch32h417qeu6-qingke-{v3f,v5f}/
cargo check --features ch32h417qeu6-qingke-v3f --target riscv32imac-unknown-none-elf -> OK
cargo check --features ch32h417qeu6-qingke-v5f --target riscv32imac-unknown-none-elf -> OK
device_id is a placeholder pending IAP flash table extraction; FIXME
comment marks this. Other package variants (CH32H417MEU6 QFN88,
CH32H417WEU6 QFN68) can be added by copying the QEU6 yaml and adjusting
memory + interrupt subset.
Names the actual Qingke core variants that share this SysTick block (V3F hart 0 + V5F hart 1) instead of the generic "dual_core" topology label. If a future WCH MCU pairs different cores with the same dual- counter block, it can use a different version string. Touches: - data/registers/systick_dual_core.yaml -> systick_v3f_v5f.yaml (rename) - data/family/CH32H4.yaml SYSTICK version: dual_core -> v3f_v5f - description text updated to drop the now-redundant topology note Pipeline re-verified: ch32-data-gen and ch32-metapac-gen produce "systick v3f_v5f" peripheral; the resulting ch32-metapac compiles for ch32h417qeu6-qingke-v3f and -qingke-v5f features on riscv32imac-unknown-none-elf.
CH32H417 RCC differs substantially from V30x (rcc_v3_d8c):
Register-level changes:
- APBxPRSTR / APBxPCENR / AHBPCENR / AHBRSTR renamed to HBxPRSTR /
HBxPCENR / HBPCENR / HBRSTR. WCH retitled the bus terminology from
APB to HB in H4.
- Two new PLL configuration registers:
PLLCFGR @ 0x008 — main PLL configuration
PLLCFGR2 @ 0x034 — secondary PLL (SerDes / USBSS / USBHS / ETH)
Field-level expansion is broad: more peripheral clock enable / reset
bits to cover H4-specific peripherals (IPC, HSEM, SerDes, USBSS,
USBHS, LTDC, GPHA, DFSDM, SAI, SWPMI, I3C, QSPI1/2, HSADC, LPTIM1/2,
extra USARTs / SPIs / I2Cs / TIMs), plus new CFGR2 clock-source
selections.
The base file is the raw chiptool extract from svd/ch32h417.svd.patched
formatted via \`chiptool fmt\`. Field-level descriptions can be tightened
later; the layout matches CH32H417 RM section 3.4.
Wired into data/family/CH32H4.yaml with version: h4, base 0x40021000.
Pipeline re-verified end-to-end: CH32H417QEU6 now exports RCC alongside
GPIO / SYSTICK / HSEM / IPC / EXTI / WWDG, and both qingke-v3f and
qingke-v5f feature builds of ch32-metapac compile clean.
The initial rcc_h4.yaml was bootstrapped straight from chiptool extract and had zero enum definitions. Bring the standard RCC enums in line with rcc_v3_d8c — these encodings are identical across V30x and H4 per CH32H417 RM section 3.4 (no encoding changes, only the bus / field naming was updated): CFGR0.SW (2-bit) -> enum SW (HSI / HSE / PLL) CFGR0.SWS (2-bit) -> enum SW (status mirror of SW) CFGR0.HPRE (4-bit)-> enum HPRE (DIV1/2/4/8/16/64/128/256/512) CFGR0.PPRE1 (3-bit) -> enum PPRE (DIV1/2/4/8/16) CFGR0.PPRE2 (3-bit) -> enum PPRE CFGR0.ADCPRE (2-bit) -> enum ADCPRE (DIV2/4/6/8) BDCTLR.RTCSEL (2-bit) -> enum RTCSEL (NO_CLK / LSE / LSI / HSE_DIV512) Tightened a few field descriptions in CFGR0 to match RM wording (e.g. "Timer prescaler (PB1)" -> "PB1 (low-speed APB) prescaler"). H4-specific multi-bit fields with new clock-source semantics (MCO 4-bit, USBHSPLLSRC, LTDCSRC, USBFSSRC, HSADCSRC, ETH1GSRC, SYSPLL_SEL, PLLSRC, ...) deliberately left without enums in this pass — their variant encodings need RM cross-reference and will land in a follow-up commit. Re-verified end-to-end: ch32-data-gen + ch32-metapac-gen produce a clean ch32-metapac for CH32H417QEU6 on both qingke-v3f and qingke-v5f feature configurations.
Continues the enum follow-up after the first SW/HPRE/PPRE/ADCPRE/RTCSEL
pass. Adds 14 more enums for the H4-specific clock-tree controls,
derived from CH32H417 RM sections 3.4.3 (PLLCFGR), 3.4.13 (CFGR2) and
3.4.14 (PLLCFGR2):
PLLCFGR.PLLSRC -> PLLSRC (HSI / HSE / USBHS_PLL / ETH_PLL /
USBSS_PLL / SERDES_PLL_DIV2)
PLLCFGR.SYSPLL_SEL -> SYSPLL_SEL (PLL_CLK / USBHS_PLL / ETH_PLL /
SERDES_PLL_DIV2 / USBSS_PLL)
PLLCFGR.PLLMUL -> PLLMUL (32 variants: 4x..40x + 59x, with
fractional 8.5x/9.5x/10.5x/11.5x/
12.5x mid-range)
PLLCFGR2.USBHSPLLSRC -> USBHSPLLSRC (HSE / HSI / ETHCLK_20M /
SYS_PLL_DIV)
PLLCFGR2.USBHSPLL_REFSEL -> USBHSPLL_REFSEL (25/20/24/32 MHz)
PLLCFGR2.USBSSPLL_REFSEL -> USBSSPLL_REFSEL (20/24/25/30/32/40/60/
80 MHz)
PLLCFGR2.SERDESPLL_MUL -> SERDESPLL_MUL (16 variants 25x..90x)
CFGR2.UHSIFSRC -> UHSIFSRC (SYSCLK / PLL_CLK / USBHS_PLL / ETH_PLL)
CFGR2.LTDCSRC -> LTDCSRC (PLL_CLK / SERDES_PLL_DIV2 / ETH_PLL /
USBHS_PLL)
CFGR2.USBFSDIV -> USBFSDIV (16 integer + half-integer variants)
CFGR2.USBFSSRC -> USBFSSRC (PLL / USBHS_PLL)
CFGR2.RNGSRC / I2S2SRC / I2S3SRC -> CLK_SRC_PLL (SYSCLK / PLL_CLK,
shared 1-bit enum)
CFGR2.HSADCSRC -> HSADCSRC (SYSCLK / PLL_CLK / USBHS_PLL / ETH_PLL)
CFGR2.ETH1GSRC -> ETH1GSRC (PLL_CLK / USBSS_PLL / ETH_PLL_DIV4 /
SERDES_PLL_DIV8)
Also fixes a CH32H417 SVD bug where USBSSPLL_REFSEL was declared as
2-bit at PLLCFGR2[5:4]; RM section 3.4.14 specifies the field is 3-bit
at [6:4]. Corrected the bit_size in our yaml; downstream SVD will need
a matching fix when the H4 PAC ships through the ch32-rs path.
Field descriptions tightened to match RM (typos "intput" -> "input"
fixed, added "Writable only while ... =0" hints where the RM mandates
gating).
Final enum density: 19 enum/ definitions, 23 field-level enum:
references — comparable to rcc_v3_d8c (10 / 14) but covering the
much-larger H4 clock tree.
Pipeline re-verified: CH32H417QEU6 compiles in both qingke-v3f and
qingke-v5f feature configurations on riscv32imac-unknown-none-elf.
Last batch of RCC enum follow-ups, covering CFGR0 fields the previous
two commits left untouched. Source: CH32H417 RM section 3.4.2.
CFGR0.MCO -> MCO (NO_CLK / SYSCLK / HSI / HSE / PLL_DIV2 /
UTMI / USBSS_PLL_DIV2 / ETH_PLL_DIV8 /
SERDES_PLL_DIV16)
CFGR0.FPRE -> FPRE (DIV1 / DIV2 / DIV4) — V5F core prescaler
CFGR0.ADCSRC -> ADCSRC (HCLK / USBHS_PLL)
Also fixes a CH32H417 SVD bug: CFGR0[17:16] is FPRE[1:0] (2-bit) per
RM section 3.4.2. The chiptool-extracted SVD splits these into two
1-bit fields named "FPRE" (bit 16) and "PLLXTPRE" (bit 17), the latter
being a holdover from V30x where PLLXTPRE was a separate 1-bit HSE
divider. Removed the spurious PLLXTPRE field and corrected FPRE to
its proper 2-bit form. Downstream SVD will need the same fix when the
H4 PAC ships through the ch32-rs path.
Tightened several CFGR0 field descriptions to match RM wording
(typos "intput" -> "input", clarified ADC_DUTY_SEL gating, named FPRE
as "V5F core prescaler" rather than the generic "HCLK prescaler").
Final enum density: 22 enum definitions, 26 field-level references.
This is the last RCC enum pass — remaining 1-bit flag fields (HSION,
HSEON, PLLON, ...) are simple booleans and don't need typed wrappers.
Pipeline re-verified: CH32H417QEU6 compiles in both qingke-v3f and
qingke-v5f feature configurations on riscv32imac-unknown-none-elf.
…c typo)
Cross-validated all rcc_h4 enums against the EVT SDK header
(SRC/Peripheral/inc/ch32h417_rcc.h). Every enum encoding matches the
SDK macros except HSADCSRC, where RM and SDK disagree:
CH32H417 RM V1.7 section 3.4.13 table for CFGR2[29:28] HSADCSRC[1:0]:
00 : SYSCLK
01 : PLL_CLK
10 : USBHS_PLL
11 : ETH_PLL
WCH SDK ch32h417_rcc.h:
RCC_HSADCSource_PLLCLK 0x00
RCC_HSADCSource_SYSCLK 0x01
RCC_HSADCSource_USBHS 0x02
RCC_HSADCSource_ETH 0x03
WCH SDK ch32h417_rcc.c line 1315:
RCC->CFGR2 |= (RCC_HSADCSource << 28); // raw write, no shift
EVT example HSADC/HSADC/Common/hardware.c:28 explicitly calls
RCC_HSADCCLKConfig(RCC_HSADCSource_PLLCLK);
i.e. value 0x00 is the canonical way to obtain PLL_CLK on HSADC.
Since the shipping SDK and example treat 00 as PLL_CLK, trust that as
the silicon truth and treat the RM table as a documentation typo for
the 00/01 row. Swap PLL_CLK and SYSCLK in our enum.
Updated enum description records the SDK / RM contradiction so a
future hardware test can validate definitively.
All other CFGR / PLLCFGR / CFGR2 enums (SW, HPRE, PPRE, ADCPRE, FPRE,
MCO, RTCSEL, PLLSRC, PLLMUL, SYSPLL_SEL, USBHSPLLSRC, UHSIFSRC,
LTDCSRC, USBFSSRC, USBFSDIV, ETH1GSRC, CLK_SRC_PLL) verified to match
both RM and SDK.
Final pass: exhaustive bit-level audit of every rcc_h4 field against
CH32H417 RM V1.7 section 3.4 register tables and the WCH SDK header
(SRC/Peripheral/inc/ch32h417_rcc.h with bit positions decoded from
RCC_HBPeriph_* / RCC_HB1Periph_* / RCC_HB2Periph_* macros). 14
registers, ~230 fields total.
Discrepancies found and fixed in this commit:
1. HBPCENR.POICEN -> PIOCEN (bit 22)
SVD-side typo; SDK and RM both use "PIOC" (Pin I/O Controller).
2. HBRSTR.POICRST -> PIOCRST (bit 22)
Same typo on the reset register side.
3. CTLR missing HSICAL field
Per RM section 3.4.1 CTLR[15:8] is HSICAL[7:0] — the hardware-set
8-bit calibration value for the internal HSI oscillator. The
vendor SVD omits it; SDK header doesn't expose a macro for it
either (read-only hardware-calibrated value), but the bits are
real per RM. Added as read-only 8-bit field at offset 8.
Verified clean across all 14 registers:
- CTLR : 19 fields (now matches RM)
- CFGR0 : 14 fields, enums verified
- PLLCFGR : 5 fields, enums verified
- INTR : 23 fields (8 flags + 7 IE + 8 clear, CSSF has no
IE bit by hardware design — RM and SDK agree)
- HB2PRSTR : 27 fields (bit-aligned with HB2PCENR)
- HB1PRSTR : 31 fields (bit-aligned with HB1PCENR)
- HBPCENR : 15 fields (now matches SDK RCC_HBPeriph_* exactly)
- HB2PCENR : 27 fields (matches SDK RCC_HB2Periph_*; GPIO names
use V30x-style IOPxEN vs SDK's GPIOA-F — equivalent)
- HB1PCENR : 31 fields (matches SDK RCC_HB1Periph_* exactly)
- BDCTLR : 10 fields, RTCSEL enum verified
- RSTSCKR : 9 fields (matches RM section 3.4.11)
- HBRSTR : 14 fields (now matches HBPCENR with RST suffix)
- CFGR2 : 11 fields, 9 enums
- PLLCFGR2 : 5 fields, 4 enums
Pipeline re-verified end-to-end: CH32H417QEU6 produces a clean
ch32-metapac for both qingke-v3f and qingke-v5f features on
riscv32imac-unknown-none-elf.
rcc_h4 is now considered final; further refinement would be
description polish or HBPeriph/HB1Periph/HB2Periph clock-gating helper
methods on the HAL side, not register-layout changes.
Make explicit that PFIC is intentionally not in the H4 family yaml. Rationale: - qingke::pfic already covers the universal PFIC primitives (enable, disable, pend, priority, is_pending). These are shared across all WCH Qingke V2 / V3 / V4 / V5 cores. - H4's dual-core extensions (HartId reading SCTLR.HART_ID, wake_other_core writing WAKEIP0/1 + SCTLR.SENDEVENT) were added to qingke under the dual-core feature gate. - H4's PFIC is V5-class (per CH32H417 RM section 4.7 and V5 IP manual section 3.1) with event wake-up registers (EENR/EPR/EWUPR) not present in pfic_rv4. Building a full pfic_h4.yaml would duplicate 99% of rv4 with minor extensions for marginal benefit. - ch32-hal's existing use of metapac PAC for PFIC is limited to a single SCTLR.SEVONPEND toggle (src/embassy/mod.rs:28). If/when more typed PFIC access is needed, the cleaner path is to add a helper in qingke rather than ship a redundant register layout in metapac. Updates the family yaml header comment to record this decision so a future contributor doesn't try to fill in a missing PFIC entry.
Reverses the earlier "PFIC stays in qingke only" decision once it became clear every other RISC-V family yaml lists PFIC: CH32V003 / CH32V00X -> pfic rv2 CH32V1 / CH32L1 -> pfic rv3 CH32V2 / CH32V3 / CH32X0 -> pfic rv4 CH32H4 -> pfic h4 (this commit) The runtime / convenience layer is still qingke::pfic — the metapac PFIC type just gives downstream code typed access to specific fields when needed (e.g. ch32-hal currently touches SCTLR.SEVONPEND once through metapac PFIC; with this commit that path stays available on H4). pfic_h4.yaml is bootstrapped from the chiptool extract of the patched H4 SVD (svd/ch32h417.svd.patched). It captures the V5-class PFIC layout (8-bank IxxRx for 256 interrupts, VTFIDR-style VTF, EENR/EPR/ EWUPR event-wake regs, CSTAR0/1, IPRIOR/IALLOCR/IAUTR) plus the CH32H417 dual-core additions (WAKEIP0/1 at 0x720/0x724, SCTLR.HART_ID at bits [23:16]). All bit positions verified against CH32H417 RM section 4.7.5. Small cleanup applied on top of the raw extract: - SETEVENT -> SENDEVENT (matches RM and qingke naming; same bit 5) - Tightened description text on WFITOWFE, SEVONPEND, SENDEVENT, HART_ID, removed the "ues WFI as WFE" typo, made it clear how HART_ID maps to V3F (0) / V5F (1) on CH32H417. Family yaml comment updated to describe the new dual-ownership: the PFIC peripheral lives in metapac for typed access; qingke owns the runtime primitives and the dual-core extensions. CH32H417QEU6 chip yaml gains PFIC automatically through the family include. Pipeline re-verified: CH32H417QEU6 now emits 13 peripherals (was 12), and both qingke-v3f / qingke-v5f feature builds compile clean.
H4 redesigns AFIO with STM32F4-style per-pin AF mux (GPIOA-F × AFLR/AFHR, 4 bits per pin) instead of V3's PCFR remap codes, plus split EXTICR1/2 for EXTI line 0..15 GPIO port selection. EXTI_PORT enum encodes PA..PF. Wires AFIO into the CH32H4 family at 0x40010000 with HB2 clock/reset.
The initial extract had EXTICR1/EXTICR2 as separate registers with 16
explicit EXTI0..EXTI15 fields, plus 12 structurally-identical
GPIO?_AF{L,H}R fieldsets — 650 lines, 4-5x the peer afio yamls.
Match afio_v1/v3/x0/l1 style:
- Single EXTICR with array len:2, fieldset has EXTI field array len:8.
- Single shared AFR fieldset with one AFR field array len:8; block items
still name each port-half (GPIOA_AFLR..GPIOF_AFHR) per RM/SDK.
650 -> 164 lines. PAC API: afio.exticr(n).set_exti(line, ExtiPort::PB)
and afio.gpioa_aflr().set_afr(pin, af).
H4 renames APB→PB and reorganizes the bus topology; GPIO ports and AFIO sit on HB2 (HB2PCENR/HB2PRSTR), WWDG sits on HB1 (HB1PCENR/HB1PRSTR). The original APB2*/APB1* references were carried over from V30x without verification — those register names don't exist in rcc_h4 at all. The pipeline doesn't cross-validate rcc.enable/reset against the actual rcc fieldset, which is why this slipped through. Caught while wiring AFIO; verified IOPA..F EN/RST fields and WWDGEN/WWDGRST live in the H[12] groups.
Bootstraps the basic peripheral set needed for HAL boot — FLASH wait states + ACR enhanced mode, PWR for PVD/low-power, IWDG, CRC, and the ESIG unique-id / flash-size block. Naming as _h4 even for IWDG/CRC/ESIG (which look generic) — no other family yaml currently wires these, so promoting to common would be speculative. Easy to rename once a second consumer appears. Family wiring: PWR on HB1 (PWREN/PWRRST), CRC on HB (CRCEN, no reset bit exposed), FLASH with IRQ #34, IWDG/ESIG without RCC entries.
USART1 is on HB2 (PCLK2, USART1EN/RST), USART2-8 are on HB1 (PCLK1). All use the H4 usart register version which extends the common layout with CTLR4, M_EXT, and the LPWKUP family of fields. RCC peripheral was already present but its global interrupt (RCC_IRQn, #35 — clock-failure / CSS / PLL-ready events) was unwired. Adding it to round out the IRQ coverage for already-instantiated peripherals. AF pin maps deferred to a follow-up commit (need to walk the datasheet pinout table per package per signal).
Pin map per USART instance, listing every (pin, signal, af) triple a HAL can pick from. Five signals per instance (TX/RX/CK/CTS/RTS). Source: CH32H417 datasheet V1.7 §2.2 "引脚定义" (pin definition table 2-1-1), rendered to PNG and OCR'd into a per-pin AF map (95 GPIOs, ~1073 signal/AF entries), then inverted into USART_n_<signal> -> [ (pin, af) ] candidate lists. H4 uses STM32F4-style 4-bit AF mux (AFLR/AFHR), so each entry pairs a pin with the AF number that selects it. Standard pin sets show up: USART1 TX/RX/CK/CTS/RTS on PA9/PA10/PA8/PA11/PA12 (AF7), PB14/PB15 (AF4), PD11..PD15 (AF14). Datasheet ambiguities (CAN_TX/CAN_RX without a peripheral index on PA13/PA14) were preserved verbatim — to be resolved when wiring CAN.
…egister versions Diff-based analysis against existing register yamls showed these peripherals match nearly 1:1 with already-authored versions: - I2C1-4 -> i2c_v3 (only delta: v3 has a "MUST1" reserved bit declaration that H4 omits — irrelevant for functional code) - LPTIM1-2 -> lptim_l1 (zero structural delta) - RNG -> rng_v3 (note: rng_v3 declares the DR register but ships without a fieldset, so RNDATA must be accessed raw; H4 datasheet shows the same 32-bit DR) Wiring: I2C1-3 + LPTIM1-2 on HB1 (PCLK1), I2C4 on HB2 (PCLK2), RNG on HB (HCLK). I2C uses split EV/ER interrupt signals; LPTIM exposes the global IRQ (LPTIMn_WakeUP wired via WAKEUP signal). AF pin maps from datasheet table 2-1-1 OCR (95-GPIO inverse index): - I2C: SDA/SCL/SMBA per instance - LPTIM: CH1/CH2/ETR/OC per instance - RNG has no GPIO signals CH32H417QEU6 peripheral count: 27 -> 34.
H4 advanced/general/basic timer register layout matches V3 (same chiptool block topology: ADTM/GPTM/GPTM32/BCTM, shared fieldsets, same array form). One real architectural difference: H4 replaces V3's TIMx_SPEC register (PWM toggle) at offset 0x50 with TIMx_AUX (dual-edge capture + asymmetric dead-time + brake source select). RM section 14.4.21 verifies the 7 AUX fields (DT_VLU2 / DTN_MODE / DTP_MODE / BK_SEL / CAP_ED_CH2/3/4). timer_h4 is constructed by copying timer_v3 (fieldsets stay verbatim — they are already in chiptool array form), replacing fieldset/SPEC and block-item SPEC -> AUX. Other field-level H4 additions (CAPLVL/CAPOV in CTLR1 for dual-edge capture, AUX-related bits) deferred until HAL needs them. Family wiring (12 TIM instances): - TIM1/8 advanced on HB2 (PCLK2), 4-vector IRQ each - TIM2/3/4/5 16-bit GP on HB1 (PCLK1) - TIM6/7 basic on HB1 - TIM9/10/11/12 32-bit GP on HB2 (note: H4 moves GP32 to high-speed bus) AF pin maps from datasheet OCR inverse index: ETR/CH1..4/BKIN/CH1N..3N per instance. CH32H417QEU6 peripheral count: 34 -> 46.
Field-level comparison vs spi_v3 shows H4 SPI is nearly identical to the V3 SPI IP: CTLR1 (14 fields), CTLR2 (6), STATR (8), CRCR/RCRCR/TCRCR identical. Minor deltas: - HSCR: H4 adds HSRXEN2 (bit 2) on top of v3's HSRXEN — not needed for basic master/slave operation, ignored for now. - DATAR: v3 names the data field "DATAR", H4 SVD calls it "DR" — same layout (16-bit at offset 0). PAC accessor will be .datar() per v3. - I2S registers: v3 names them I2S_CFGR / I2SPR; H4 SVD prefixes with SPI_. We use v3 naming. v3's single-block design exposes I2S regs on every SPI instance even though SPI1/SPI4 don't implement I2S in hardware. Accessing those regs on those instances has no effect — same compromise as V3 family. Wiring: SPI1 on HB2 (PCLK2), SPI2-4 on HB1 (PCLK1). Pin signals: SCK/MOSI/MISO/NSS from datasheet OCR inverse index. CH32H417QEU6 peripheral count: 46 -> 50.
stm32-data design philosophy: one register file per (kind, version, i2s-variant), single block, chip-wide selection. CH32 follows the same pattern. Earlier commit (6445242) reused spi_v3 for H4 SPI as a quick win, but H4 SVD has three real differences that warrant a dedicated spi_h4 file: - DATAR fieldset: V3 SVD names the data field "DATAR" (same as the register), H4 calls it "DR" — H4 matches STM32 convention. - HSCR fieldset: H4 adds HSRXEN2 (bit 2) on top of HSRXEN. WCH uses these to gate the high-speed receive path. - I2S registers: V3 names them I2S_CFGR / I2SPR; H4 SVD prefixes with SPI_, so the H4 PAC exposes spi_i2s_cfgr() / spi_i2spr() to make the SPI/I2S dual nature explicit at the API surface. Single-block design retained (matching stm32-data spi_vN_i2s pattern): all H4 SPI instances reference the same fieldset list, even though SPI1/SPI4 don't physically implement I2S — accessing those registers on those instances has no hardware effect. embassy-stm32 makes the same compromise. Generation cost is zero: ch32-metapac uses #[path] to conditionally include only the peripheral .rs files an enabled chip feature actually references, so adding spi_h4 doesn't bloat other chips.
rng_v3 declares the DR register but ships without a fieldset, so the generated PAC exposes Reg<()> at .dr() — readers can't get a typed u32 out without raw pointer access. rng_h4 adds the missing fieldset. Cross-checked against: - SDK ch32h417_rng.h: same flags (DRDY/CECS/SECS), same interrupts (CEI/SEI). No new fields vs v3. - RM section 30.3 (R32_RNG_CR, R32_RNG_SR, R32_RNG_DR): identical CR/SR layout to v3; DR is RO 32-bit RNDATA[31:0]. Generated API gains: rng.dr().read().rndata() instead of raw access. Investigated for the same precision pass but kept as-is (truly identical to existing register files): - i2c_h4 unnecessary: H4 I2C matches i2c_v3 field-for-field. The MUST1 bit (OADDR1 bit 14) is a STM32-wide hardware requirement, not a V3 oddity — H4 SDK programs it the same way. - lptim_h4 unnecessary: H4 LPTIM and lptim_l1 are byte-identical at fieldset level (8 registers, 45 fields, all offsets/widths match).
H4's DMA subsystem is a major architectural change from V3: - 2x DMA controllers, 8 channels each (16 total) — vs V3's 7/11 chans - Per-channel double-buffer support via new M1ADDR register, controlled by CFGR.DOUBLE_MODE; DMA toggles between MADDR and M1ADDR each cycle and reports active buffer via FLAG_CUR_MEM - DMAMUX request router (4x 32-bit CFGR registers, four 7-bit CHANNEL_MUX selectors each) routes peripheral DMA requests to any of the 16 channels — V3's DMA had fixed peripheral-to-channel mapping - New PSIZE encoding includes 256-bit transfer width (DMA_PeripheralDataSize_256 in SDK) dma_h4 follows the stm32-data / dma_v1 two-block layout (CH cluster + DMA controller pointing to CH array); INTFR / INTFCR collapsed to array form (8x GIF/TCIF/HTIF/TEIF). 168 lines vs the 1075-line chiptool expanded extract. dmamux_h4 collapses 4 CFGRn registers into a single CFGR array of 4 with a CHANNEL_MUX array of 4 inside (4 x 7-bit per register). Wiring: - DMA1 @ 0x40020000, HBPCENR.DMA1EN, IRQs 38-44+57 (CH1=57, CH2-8=38-44) - DMA2 @ 0x40020400, HBPCENR.DMA2EN, IRQs 102-109 - DMAMUX @ 0x40020800, no independent RCC gate (shares DMA1/2 clock) CH32H417QEU6 peripheral count: 50 -> 53.
H4 ADC is the V3 ADC plus the AUX register (sample-time extension for slow channels at >=2.5 cycles, plus ADC_TO_DFSDM routing to feed the DFSDM modulator from ADC samples). Extract preserved from chiptool with three RM-style renames: - SAMPTR1_CHARGE1 -> SAMPTR1 (RM 11.3, table 11-5) - SAMPTR2_CHARGE2 -> SAMPTR2 - IDATAR1_CHGOFFSET -> IDATAR1 - RDATAR_DR -> RDATAR Block rename: ADC1 -> ADC (one register file shared by both ADC instances). Dropped ACT_DCG register from the ADC block: the H4 SVD marks it as an <alternateRegister> of RDATAR at offset 0x4C, but the RM places it in the TouchKey module (chapter 13, TKEYx_ACT_DCG at the same address as ADCx_RDATAR + 0x0). chiptool doesn't accept non-array overlapping registers in a single block. ACT_DCG belongs in a future tkey_h4 register file with its own block at the same base address — the ADC and TKEY peripherals share physical address space because TKEY drives the ADC analog frontend. Family wiring (HB2 / PCLK2, kernel_clock: ADC): - ADC1 @ 0x40012400, HB2PCENR.ADC1EN, IRQ 69 (ADC1_2 shared) - ADC2 @ 0x40012800, HB2PCENR.ADC2EN, IRQ 69 - Pins IN0..IN15 from OCR inverse index (PA0-7, PB0-1, PC0-5), signal naming follows V3 convention (no ADC_ prefix). Internal channels deferred: - IN16 (TempSensor) and IN17 (Vrefint) are internal, no pin mapping. CH32H417QEU6 peripheral count: 53 -> 55.
Five analog peripherals in one pass. Field-level findings:
- RTC: H4 RTC matches rtc_common byte-for-byte (10 registers + 10
fieldsets all IDENTICAL). Earlier worry about "H4 uses 32-bit
ALR/PRL" was wrong — H4 keeps the H/L pair design. Reuse common.
- DAC: H4 doubles the channel count vs V3, with mostly-renamed
registers per RM table 18-2:
V3 CR/SWTRIGR/DHR12R/DHR12L/DHR8R/DOR
H4 CTLR/SWTR/R12BDHR1/2 + L12BDHR1/2 + R8BDHR1/2 + DOR1/2
(dual-mode combined regs RD12BDHR/LD12BDHR/RD8BDHR preserved)
Bootstrapped from chiptool extract (217 lines), names match RM.
Pins: OUT1=PA4, OUT2=PA5.
- HSADC: 10-bit 20Msps high-speed ADC (RM ch12), brand new vs other
CH32 families. 152-line bootstrap. Single instance, 4 channels on
small packages, 7 on QFN128.
- OPA: bootstrap of the operational amplifier / PGA block (3 OPA on
H417QEU6). Shares an RCC enable bit (OPCMEN) with CMP — CMP block
to be added later under the same gate.
- DFSDM: digital filter for sigma-delta modulators (RM ch?), 921-line
bootstrap. Two filter instances (FLT0/FLT1) with separate IRQs.
Family wiring buses:
- RTC n/a (LSE/LSI), IRQs 143 + 66 (alarm via EXTI)
- DAC HB1 / PCLK1
- HSADC/OPA/DFSDM HB2 / PCLK2 (HSADC has dedicated kernel clock)
Field-level cross-check vs RM and per-fieldset enum work deferred —
chiptool extracts pass check; deeper validation when HAL touches each.
CH32H417QEU6 peripheral count: 55 -> 60.
H4 SDK ch32h417.h SPI_TypeDef uses the same register names as V3 SDK ch32v30x.h (CTLR1/CTLR2/STATR/DATAR/.../I2SCFGR/I2SPR/HSCR). The H4 SVD added a "SPI_" prefix to the I2S registers, which my earlier spi_h4 carried through — that diverged from both the SDK and from the existing spi_v3.yaml. Fixed: - fieldset SPI_I2S_CFGR -> I2SCFGR - fieldset SPI_I2SPR -> I2SPR - fieldset DATAR field name DR -> DATAR (matches both SDKs and v3 yaml) After this pass, the only structural delta between spi_h4 and spi_v3 is HSCR.HSRXEN2 (bit 2) — a genuine new feature: per RM 23.4.10, it's a second high-speed read enable that activates when HSRXEN is already set AND SPI HCLK >= 120MHz, giving the master read path additional speed-up at H4's higher core clocks. V3 cores top out below this threshold so the bit doesn't exist there. stm32-data pattern confirmed: a single-bit silicon delta justifies a full standalone version file (no shared-base patching). spi_v3 yaml's I2S_CFGR underscore is a separate cleanup item — it doesn't match V3 SDK either, but touching it affects every V3-family consumer.
H4 ships an unusually rich USB subsystem (RM ch 24-27):
- USBPD (ch 24): USB Type-C Power Delivery controller + PHY
- USBHS (ch 25): USB2.0 high-speed host/device, dedicated DP/DM pins
- USBFS (ch 26): USB1.1 full-speed host/device/OTG (chiptool extract
block name USB_OTG_FS)
- USBSS (ch 27): USB3.0 SuperSpeed host/device, dedicated SS pairs,
self-developed controller per RM ("自研控制器和收发器, 实测每秒
450Mbytes")
Each is a separate _h4 register file bootstrapped from chiptool
extract:
- usbhs_h4 2233 lines (single block - chiptool extract is flat,
unlike usbhs_v3's host/device/common split)
- usbss_h4 4267 lines (USB3.0 link/host/device/global, dropped two
UH_*_CTRL registers that the SVD marks as
alternateRegister of UEP0_*_CTRL; chiptool
rejects overlapping non-array registers, so
the device-mode view wins and host mode
accesses the same offsets via raw register
access)
- usbfs_h4 824 lines (chiptool block name USB_OTG_FS)
- usbpd_h4 288 lines (renamed block USB_PD -> USBPD so chiptool's
camelCase->Rust gives Usbpd instead of UsbPd,
matching the chip yaml's expected type name)
Wiring (all on HB / HCLK):
- USBPD 0x40024400, IRQs 54 (global) + 55 (wake-up); pins CC1/CC2 = PB3/PB4 AF4
- USBHS 0x40030000, IRQs 56 + 64 (wake-up); pins DP/DM = PB8/PB9 (no AF)
- USBSS 0x40034000, IRQs 62 (global) + 63 (link) + 65 (wake-up);
5Gbps differential pairs (SSRXA/SSRXB/SSTXA/SSTXB) on
dedicated pins, not GPIO-routable, pin map omitted.
- USBFS 0x50000000, IRQs 67 + 68 (wake-up); OTG_FS pins
VBUS/ID/DM/DP = PA9/PA10/PA11/PA12 (no AF, USB analog mode).
Field-level cross-validation against RM and ch32h417_usb.h SDK deferred
- the SDK doesn't expose controller TypeDefs (uses raw register access
patterns), so field-by-field audit comes when ch32-hal starts driving
each controller.
CH32H417QEU6 peripheral count: 60 -> 64.
Refactor to V3-family layering: family yaml lists only always-present
peripherals; instance fragments for variant-only peripherals live in
data/peripherals/H4_*.yaml; each chip yaml explicitly opts in.
Optional peripherals split out (per DS CH32H417/416/415 table 1-1):
- H4_USART8 — present on H417QEU6/MEU6 and H415REU6; absent on
H417WEU6 (only 7 USARTs) and H416RDU6
- H4_SPI4 — present on H417QEU6/MEU6 and H415REU6; absent on
H417WEU6 (only 3 SPI) and H416RDU6
- H4_USBFS — present on QEU6/MEU6/RDU6/REU6; absent on H417WEU6
- H4_USBSS — present on QEU6/MEU6/WEU6/RDU6; absent on H415REU6
(H415 is the USB2.0-only variant)
- H4_USBPD — present on QEU6/MEU6/RDU6/REU6; absent on H417WEU6
Chip variants added (memory + peripheral matrix per DS resource table):
- CH32H417MEU6 (QFN88, 65 GPIO, 960K Flash) — all five optional present
- CH32H417WEU6 (QFN68, 50 GPIO, 960K Flash) — only USBSS optional
- CH32H416RDU6 (QFN60, 48 GPIO, 480K Flash) — USBFS/USBSS/USBPD, no USART8/SPI4
- CH32H415REU6 (QFN60, 54 GPIO, 960K Flash) — USART8/SPI4/USBFS/USBPD, no USBSS
family yaml is the right place for: PFIC/SYSTICK/RCC/EXTI/HSEM/IPC/
FLASH/CRC/ESIG/IWDG/WWDG/PWR/AFIO/GPIOA-F/RTC/RNG/DMA1/DMA2/DMAMUX/
TIM1-12/LPTIM1/2/I2C1-4/CAN(future)/USART1-7/SPI1-3/ADC1/2/HSADC/OPA/
DFSDM/DAC/USBHS — all present on every CH32H4 chip variant per the DS
resource table. DAC and OPA have per-chip channel/pin subset, handled
at the pin-map level (the registers are always there).
Per-chip pin maps for the smaller packages are TODO — current chip
yamls inherit the full QFN128 OCR-derived pin map and will reference
non-existent pins on smaller packages. Will be trimmed when wiring HAL
for each package.
cargo check pass for all 10 features:
ch32h{415,416,417}{q,m,w,r}eu6/rdu6-qingke-{v3f,v5f}
Adopt the same intra-file block-inheritance pattern usbhs_v3 already uses: a small shared base block plus extends-derived device-mode and host-mode blocks. chiptool's overlap check is intra-block only, so host registers (UH_*) can occupy the same offsets as device-mode registers (UEP*) without colliding — recovering the UH_TX_CTRL and UH_RX_CTRL registers that were dropped in commit a8a3127. usbhs_h4 (121 items total): - block/USBHS 12 USB_* base registers (CTRL/INT/DEV_AD/...) - block/USBHS_DEVICE 87 UEP* device-endpoint registers, extends USBHS - block/USBHS_HOST 22 UH_* host control registers, extends USBHS usbss_h4 (194 items total): - block/USBSS 26 LINK_* / USBSS_* / STATUS / ITP / TP_RX_DATA* - block/USBSS_DEVICE 158 UEP* device endpoint registers, extends USBSS - block/USBSS_HOST 10 UH_* / HOST_* host registers, extends USBSS Family / chip yaml unchanged: instances still reference `block: USBHS` and `block: USBSS` (the shared base) which is what V3 family does for its USBHS instance. The HAL casts the base pointer to *Device / *Host type to access mode-specific accessors. Generated PAC sanity check: pub struct Usbhs { ... 22 methods } (base accessors) pub struct UsbhsDevice { ... 109 methods } (base + 87 device) pub struct UsbhsHost { ... 44 methods } (base + 22 host) pub struct Usbss { ... 27 methods } pub struct UsbssDevice { ... 185 methods } pub struct UsbssHost { ... 37 methods } All 10 chip-feature builds (5 chips × v3f/v5f) pass cargo check.
bxCAN-compatible controller, 3 instances on every H4 chip. Followed
stm32-data's can_bxcan.yaml structure: a small base block plus three
sub-blocks for mailbox / FIFO / filter clusters that the main block
references via item arrays. Chiptool extract is 12959 lines of expanded
TXMI0R/TXMI1R/TXMI2R + F0R1/F0R2 ... F41R1/F41R2; the sub-block form
collapses to 1639 lines.
Block layout:
block/CAN base (CTLR/STATR/TSTATR/RFIFO[2]/INTENR/ERRSR/BTIMR/
TTCTLR/TTCNT/TERR_CNT)
+ TX array{len:3, stride:16} block: TX @ 0x180
+ RX array{len:2, stride:16} block: RX @ 0x1B0
+ filter config (FCTLR / FMCFGR / FMCFGR_CAN3 / FSCFGR /
FSCFGR_CAN3 / FAFIFOR / FAFIFOR_CAN3 /
FWR / FWR_CAN3) — H4 doubles the filter
config registers since 3 CAN instances
share the 42-bank filter pool (RM-aligned
naming; SVD extract used _1 suffix which
I dropped in favour of _CAN3 per SDK)
+ FB array{len:42, stride:8} block: FB @ 0x240
block/TX TXMIR / TXMDTR / TXMDLR / TXMDHR (canonical names from
SDK CAN_TxMailBox_TypeDef)
block/RX RXMIR / RXMDTR / RXMDLR / RXMDHR (read-only)
block/FB FR1 / FR2
Generated PAC: pub struct Can / Tx / Rx / Fb. HAL pattern:
can.tx(mailbox_idx).txmir().write(...)
can.rx(fifo_idx).rxmdlr().read()
can.fb(bank).fr1().write(...)
Wiring (HB1 / PCLK1, IRQ TX/RX0/RX1/SCE per instance):
CAN1 @ 0x40006400, IRQs 59/60/61/58
CAN2 @ 0x40006800, IRQs 113/114/115/112
CAN3 @ 0x40007800, IRQs 137/138/139/136 (note: SVD has CAN3 at
0x40007800 not 0x40006C00 as the offset pattern
would suggest)
Filter banks are physically shared across CAN1/CAN2/CAN3; the
FMCFGR_CAN3 / FSCFGR_CAN3 / FAFIFOR_CAN3 / FWR_CAN3 registers steer
which banks are owned by CAN3 (the standard FMCFGR etc cover CAN1/CAN2,
matching STM32 bxCAN with dual-CAN; H4 just adds the CAN3 partition).
All 10 chip-feature builds pass (5 chips × v3f/v5f). H417QEU6
peripheral count: 64 → 67.
Audit (per-yaml block items vs SDK ch32h417.h __IO field list) found three drift spots. Two trivial fixes here: - flash_h4: add R32_BOOT_MODEKEYR @ 0x28 (RM §46.4.10, FLASH_TypeDef has BOOT_MODEKEYR between MODEKEYR and the next reserved word). This is the key register used to unlock the boot-mode switch — same KEY1/KEY2 sequence as KEYR. Was missing from the chiptool extract because the SVD only declared MODEKEYR + the dual-mode CFGR0 status register. SDK FLASH_TypeDef has the right ordering; RM tables 46-x list both BOOT_MODEKEYR @ 0x28 and CFGR0 @ 0x2C as separate registers. - opa_h4: rename CTLR1/CTLR2/CTLR3 block items (yaml had OPA_CTLR1 prefix from the chiptool extract — SVD-side artefact). SDK OPA_TypeDef uses unprefixed CTLR1/CTLR2/CTLR3 and the register tables in RM §17.3 match. Now `opa.ctlr1()` / `opa.ctlr2()` / `opa.ctlr3()` in the PAC instead of `opa.opa_ctlr1()` etc. Third audit item (adc_h4 missing DRV register, ADC2-only TouchKey multiplexer mask at 0x58 per RM §11.3.16) deferred to a separate commit — needs the 3-block extends pattern (ADC base + ADC2 view adding DRV) since ADC1 doesn't physically have DRV. All 10 chip-feature builds pass.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CH32H4 routes pin signals via per-pin AF numbers in AFIO.GPIOx_AFR, not the central PCFR remap groups used by V0/V1/V2/V3/X0/L1/641/643. The existing `Pin.remap: Option<u8>` field carries the group value for PCFR-style chips; mirror stm32-data and add a sibling `Pin.af: Option<u8>` for the AF number on AFR-style chips. Mutually exclusive — V3 chip yaml fills `remap`, H4 chip yaml fills `af`. ch32-hal build.rs picks the right path per pin. Updated in three places to keep the schema in sync: - ch32-data-serde/src/lib.rs — yaml frontend (Serialize+Deserialize) - ch32-metapac-gen/src/data.rs — internal struct (Deserialize) - ch32-metapac-gen/res/src/metadata.rs — runtime struct Verified: re-running ch32-data-gen + ch32-metapac-gen now produces `af: Some(N)` lines for H4 USART/SPI/I2C/TIM pin entries (415 of them for CH32H417QEU6); V307 metapac unchanged (0 `af:` entries, all `remap: ...` as before).
The original yaml exposed `GPIOA_AFLR`, `GPIOA_AFHR`, `GPIOB_AFLR`,
... `GPIOF_AFHR` as 12 separately-named items, which would force
ch32-hal driver code to write a 12-arm `match (port, pin/8)` to pick
the right setter. Fold them into one `gpio_afr` array indexed by
`port_idx * 2 + (pin / 8)` (with port_idx mapping A=0..F=5), so
driver code collapses to a single accessor call:
crate::pac::AFIO
.gpio_afr(port * 2 + (pin / 8))
.modify(|w| w.set_afr(pin % 8, af_num));
chiptool generates `AFIO.gpio_afr(n: usize)` with `assert!(n < 12)`,
verified by re-running the data + metapac generators against
CH32H417QEU6.
Address 7 outstanding reviewer findings (the 8th — afio_h4 "intput" → "input" — already landed via the upstream autofix commit 46e407a): 1. usbfs_h4 UEP5_6_MOD: rename UEP3_RX_EN → UEP6_RX_EN (bit 7, copy-paste from UEP2_3_MOD — description already said "endpoint 6"). 2. dfsdm_h4 CH1CFGR1.SITP: fix bit_size 2 → 1. RM (§42.6.1) defines SITP at bit [0] (1 bit, 0=rising/1=falling); bit [1] is Reserved. CH0CFGR1.SITP was already correct. 3. opa_h4 CTLR2: rename PGADIF1 → PGADIF2 and fix description per RM §35.2.1 (OPA2 pairs with NSEL2 and N-terminal connects to PF12, not OPA1's NSEL1/PA7). 4-5. hsem_h4 CLR/KEY: uppercase MATCH_PID/MATCH_CID/AUTO_1STEP_CLR/ AUTO_2STEP_CLR to match this file's other fields (PID_MASK, CID_MASK, CLR_KEY, KEY_VALUE) and the project-wide convention. The RM uses lowercase here but is itself inconsistent within the same chapter; chiptool lowercases the Rust API either way. 6. timer_h4 AUX: typo "egde" → "edge" (5 places: register description plus CAP_ED_CH2/3/4 descriptions). 7. esig_h4 FLACAP: typo "capecity" → "capacity" (block item + fieldset descriptions). Plus one adjacent copy-paste bug discovered while verifying #3: 8. opa_h4 CTLR3.PGADIF3 description: NSEL1/(PA7) → NSEL3/(PA3) per RM §35.2.1 (the PGADIF3 field name itself was already correct). Verified with ch32-data-gen + ch32-metapac-gen "CH32H4*" — clean.
4 of the 5 H4 chip yamls had a leftover visual separator between the USB-family keywords and the analog/SoC keywords. The YAML parser tolerated them as empty list items, but they are noise and inconsistent with CH32H417QEU6.yaml.
Summary
Bootstrap CH32H4 family support in
ch32-data— the first dual-core (Qingke V3F + V5F) RISC-V MCU from WCH, with USB 3.0, Gigabit Ethernet (planned), high-end SerDes, LTDC, and a much-redesigned peripheral set vs the V3 family.This is a draft — covers ~64 peripherals across 5 chip variants but several less-urgent peripherals (CAN, QSPI, SDIO, SDMMC, UHSIF, FMC, …) are not yet wired. Field-level cross-validation against RM is done for the foundational peripherals (RCC, PFIC, AFIO, USART, SPI, ADC, DAC, DMA, TIM, RTC, USB top-level layout) but deferred for higher-end controllers until ch32-hal drives them.
What's in
5 chip variants
CH32H417QEU6(QFN128, full feature) — doneCH32H417MEU6(QFN88) — doneCH32H417WEU6(QFN68, no USBFS/USBPD/SPI4/USART8) — doneCH32H416RDU6(QFN60, 480K Flash variant) — doneCH32H415REU6(QFN60, no USBSS — USB 2.0 only) — doneAll 10 chip-feature builds (5 chips ×
qingke-v3f/qingke-v5f) passcargo check.Peripheral catalogue (~64 per chip)
_h4register files:pfic_h4,rcc_h4,afio_h4,usart_h4,flash_h4,pwr_h4,iwdg_h4,crc_h4,esig_h4,hsem_h4,ipc_h4,timer_h4,dma_h4,dmamux_h4,adc_h4,dac_h4,hsadc_h4,opa_h4,dfsdm_h4,spi_h4,rng_h4,usbhs_h4,usbss_h4,usbfs_h4,usbpd_h4rtc_common,i2c_v3,lptim_l1,wwdg_common,exti_common,gpio_v3,systick_v3f_v5fextends:3-block pattern for host/device alternate-register viewsInterrupt table (148 vectors)
Cross-checked against SDK
ch32h417.hIRQn enum, including the dual-core SysTick0/SysTick1, IPC channel IRQs, HSEM, and 9 wake-up vectors.Datasheet-derived AF pin map (95 GPIO, ~1073 signal/AF triples)
OCR'd the H417QEU6 pinout table (DS §2.2, 16 pages) and inverted into per-peripheral
pins:blocks. Verified empirically that smaller-package pinout tables are proper subsets — same pin/AF mapping, fewer peripherals routed.Peripheral wiring layering
Per the V3-family pattern documented in the maintenance skill:
family/CH32H4.yamlholds only always-present peripheralsdata/peripherals/H4_USART8.yaml,H4_SPI4.yaml,H4_USBFS.yaml,H4_USBSS.yaml,H4_USBPD.yamlfor the variable peripheralsNotable findings (in commit messages)
devices/ch32h417.yaml+rcc_h4.yaml): CFGR0.FPRE width, PLLCFGR2.USBSSPLL_REFSEL width, HBPCENR.PIOCEN typo, HSADCSRC encoding diverges from RM (SDK is shipping silicon).AUXis dual-edge capture / asymmetric dead-time / brake source select (RM 14.4.21), whereas V3'sSPECwas PWM toggle.SPI_I2S_CFGRprefix was an SVD artefact, aligned back to SDKI2SCFGR.extends:(V3 usbhs_v3 pattern) so device-mode UEP* and host-mode UH_* registers can coexist at the same offsets. chip yaml points at the base block; HAL casts pointer to *Device / *Host views.Known gaps / follow-ups
tmp/h4/but haven't been promoted todata/registers/and wired up.ACT_DCG/TKACT_DCG) belongs in a futuretkey_h4register file — the H4 SVD marks it as an alternateRegister of ADC RDATAR (RM §13). Already noted inadc_h4commit message.ch32h417_usb.hdoesn't expose controller TypeDefs (raw register access pattern), so this is best done when ch32-hal starts driving each.device_idplaceholders (set to 0) on all five H4 chip yamls; needs the actual flash IAP signature when WCH publishes it.Test plan
cargo run -p ch32-data-gen— succeeds, all 5 H4 chips parsedcargo run -p ch32-metapac-gen -- CH32H417QEU6 CH32H417MEU6 CH32H417WEU6 CH32H416RDU6 CH32H415REU6— all peripheral .rs files generatedcargo check --features <chip>-qingke-{v3f,v5f}— passes for all 10 chip-core combinations