Skip to content

Add CH32H4 family (CH32H417/H416/H415) support - #39

Merged
andelf merged 41 commits into
mainfrom
feat/ch32h417
May 20, 2026
Merged

Add CH32H4 family (CH32H417/H416/H415) support#39
andelf merged 41 commits into
mainfrom
feat/ch32h417

Conversation

@andelf

@andelf andelf commented May 17, 2026

Copy link
Copy Markdown
Contributor

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) — done
  • CH32H417MEU6 (QFN88) — done
  • CH32H417WEU6 (QFN68, no USBFS/USBPD/SPI4/USART8) — done
  • CH32H416RDU6 (QFN60, 480K Flash variant) — done
  • CH32H415REU6 (QFN60, no USBSS — USB 2.0 only) — done

All 10 chip-feature builds (5 chips × qingke-v3f / qingke-v5f) pass cargo check.

Peripheral catalogue (~64 per chip)

  • New _h4 register 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_h4
  • Reused existing versions: rtc_common, i2c_v3, lptim_l1, wwdg_common, exti_common, gpio_v3, systick_v3f_v5f
  • USBHS/USBSS use the V3 USBHS extends: 3-block pattern for host/device alternate-register views

Interrupt table (148 vectors)

Cross-checked against SDK ch32h417.h IRQn 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.yaml holds only always-present peripherals
  • data/peripherals/H4_USART8.yaml, H4_SPI4.yaml, H4_USBFS.yaml, H4_USBSS.yaml, H4_USBPD.yaml for the variable peripherals
  • Each chip yaml explicitly opts in to the optional fragments it actually has

Notable findings (in commit messages)

  • RCC SVD bugs fixed (in 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).
  • H4 bus rename: APB1/APB2 are now HB1/HB2 in H4 RCC fields — original family yaml referenced the old names by accident; fixed.
  • USART H4 superset: adds CTLR4 register, CTLR1.M_EXT (7-bit data length), and CTLR3 LPWKUP family (LPUART-style low-power wakeup).
  • AFIO redesign: STM32F4-style 4-bit-per-pin AF mux (AFLR/AFHR) replacing V3-style PCFR remap codes.
  • Timer H4 = V3 timer with AUX in place of SPEC: same register layout at offset 0x50, but H4's AUX is dual-edge capture / asymmetric dead-time / brake source select (RM 14.4.21), whereas V3's SPEC was PWM toggle.
  • DMA H4 architectural upgrade: 2× 8-channel DMA + DMAMUX request router + double-buffer mode (new M1ADDR per channel), vs V3's fixed peripheral-to-channel mapping.
  • SPI H4 vs V3 only one real bit: HSCR.HSRXEN2 (RM 23.4.10), a second high-speed read enable that activates when HSRXEN is set AND SPI HCLK ≥ 120 MHz. Otherwise H4 SDK and V3 SDK use identical TypeDef naming for SPI — the H4 SVD's SPI_I2S_CFGR prefix was an SVD artefact, aligned back to SDK I2SCFGR.
  • USB host/device 3-block pattern: usbhs_h4 and usbss_h4 use 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

  • Not wired yet: CAN1-3 (all chips have 3), QSPI1/QSPI2, SDIO, SDMMC, UHSIF, FMC (FSMC/SDRAM), SERDES, LTDC, GPHA, SAI, SWPMI, I3C, DVP, ECDC, PIOC. The register files exist for some via chiptool extract in tmp/h4/ but haven't been promoted to data/registers/ and wired up.
  • TouchKey (ACT_DCG / TKACT_DCG) belongs in a future tkey_h4 register file — the H4 SVD marks it as an alternateRegister of ADC RDATAR (RM §13). Already noted in adc_h4 commit message.
  • Field-level cross-validation pending for USBHS / USBSS / DFSDM / HSADC / OPA — current files are chiptool-extract bootstraps. SDK ch32h417_usb.h doesn't expose controller TypeDefs (raw register access pattern), so this is best done when ch32-hal starts driving each.
  • device_id placeholders (set to 0) on all five H4 chip yamls; needs the actual flash IAP signature when WCH publishes it.
  • Pin maps per smaller package: chip yamls currently inherit the QFN128 OCR map. This is fine — empirically the AF mapping is identical across H417/H416/H415 packages; small packages are simply pin subsets (HAL handles bond-out).

Test plan

  • cargo run -p ch32-data-gen — succeeds, all 5 H4 chips parsed
  • cargo run -p ch32-metapac-gen -- CH32H417QEU6 CH32H417MEU6 CH32H417WEU6 CH32H416RDU6 CH32H415REU6 — all peripheral .rs files generated
  • cargo check --features <chip>-qingke-{v3f,v5f} — passes for all 10 chip-core combinations
  • Hardware blink on a CH32H417QEU6 EVT board (deferred to ch32-hal PR)
  • Field-level RM audit per peripheral as ch32-hal needs each

@andelf
andelf marked this pull request as ready for review May 17, 2026 12:58
@andelf
andelf requested a review from Copilot May 17, 2026 12:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +491 to +498
- 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
Comment on lines +198 to +207
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
Comment thread data/registers/opa_h4.yaml Outdated
Comment on lines +124 to +125
- name: PGADIF1
description: OPA2 is used with NSEL1 as PGA and the N-terminal is connected to OPA2_CHN1 (PA7).
Comment thread data/registers/hsem_h4.yaml Outdated
Comment on lines +64 to +68
- 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
Comment thread data/registers/hsem_h4.yaml Outdated
Comment on lines +87 to +91
- 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
Comment on lines +262 to +268
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
Comment on lines +4 to +6
- name: FLACAP
description: Flash capecity register.
byte_offset: 0
Comment thread data/registers/afio_h4.yaml Outdated
@aq1018

aq1018 commented May 18, 2026

Copy link
Copy Markdown
Contributor

@andelf

Just a heads up, #36 has some structural changes such as:

  • moved device_id from top level to package level. This is because each package as a different device_id, and it is used to auto detect chips in the forked ch32/probe-rs repo.
  • rich memory layouts
  • OB / ESIG in nv folder ( same structure as registers ).

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.

andelf added 25 commits May 20, 2026 23:43
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.
andelf and others added 15 commits May 20, 2026 23:43
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.
@andelf
andelf merged commit 5e8d693 into main May 20, 2026
1 check passed
@andelf
andelf deleted the feat/ch32h417 branch May 20, 2026 17:20
@andelf

andelf commented May 20, 2026

Copy link
Copy Markdown
Contributor Author

@aq1018 merged. let's ship #38 🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants