✧ 10-CENT RISC-V FIELD GUIDE ✧

CH32V003

48 MHZ · 16KB FLASH · 2KB RAM · ONE WIRE TO RULE IT

WHY THIS CHIP

The CH32V003 is WCH's famously cheap 32-bit RISC-V microcontroller. Ten-ish cents for the bare chip, a couple bucks for a dev board. It is the "just enough computer" — perfect for haptics, LEDs, sensors, and dedicated single-job electronics where an ESP32 is overkill.

48 MHzQingKe V2A RISC-V core (RV32EC)
16 KBFlash — small; write lean C
2 KBSRAM — no room for bloat
10-bitADC, 8 external channels
16-bit timers with hardware PWM
1× eachUSART · I²C · SPI
2.7–5.5 VVDD range — runs on 3.3 V or 5 V
1 wireSWIO debug/flash via WCH-LinkE

No USB, no WiFi, no Bluetooth on-chip. That is the point. It does one job, costs nothing, and boots instantly.

BILL OF MATERIALS

Everything used in the haptic build on this page. Prices are typical AliExpress/eBay singles — buying multi-packs cuts them roughly in half.

PARTROLENOTESEST. USD
CH32V003F4P6 dev board (TENSTAR style) The brain USB-C powered minimum system board. All V pads = one rail (~5 V on USB-C). No user LED on this revision. $1.50
WCH-LinkE programmer Flash + debug + UART Must be the E version — plain WCH-Link cannot speak the '003's 1-wire SDI. One-time buy, reused forever. $5.00
MAX4466 electret mic module Ears Analog out, adjustable gain pot on the back. Powered from the board's V rail so its output always stays within ADC range. $1.50
Mini H-bridge module (L9110S / DRV8833 class) Muscle driver Two direction inputs (IN1/IN2). Drives the vibration motor both ways for tap and spin patterns. $1.00
Coin vibration motor (10 mm ERM) The haptics ~70–100 mA draw. Never wire it straight to a GPIO — always through the H-bridge. $0.80
3 mm LED + 220 Ω–1 kΩ resistor Volume meter Hardware-PWM'd on PC3. The resistor is not optional. $0.10
CR2032 coin cell + holder Untethered power (optional) 3 V, ~220 mAh. Fine for MCU + LED. Motor spikes will sag it — see POWER section for the honest math. $0.50
TOTAL (with programmer)$0.00
TOTAL (programmer already owned)$0.00

TOOLCHAIN SETUP (WINDOWS)

One-time setup. After this, every CH32V003 project is just pio run -t upload.

  1. Install PlatformIO — VS Code extension, or pip install platformio.
  2. Install the community CH32V platform:
    pio pkg install -g -p https://github.com/Community-PIO-CH32V/platform-ch32v.git
    This pulls the RISC-V GCC toolchain, OpenOCD, minichlink and wlink automatically.
  3. Install the WCH-LinkE USB drivers — download the WCHLink driver package, unzip, then run both installers: WCHLink\SETUP.EXE (debug interface) and WCHLinkSER\SETUP.EXE (serial port).
  4. Verify LinkE mode — plug it in and check Device Manager. It must enumerate as VID 1A86 / PID 8010 (RISC-V mode). If you see 8012 it is stuck in ARM mode: hold the ModeS button while plugging in, or flip it with WCH-LinkUtility.

Minimal platformio.ini for any '003 project:

[env:genericCH32V003F4P6]
platform = ch32v
board = genericCH32V003F4P6
framework = ch32v003fun
upload_protocol = wch-link

Framework pick: ch32v003fun (register-level, tiny, fast) fits this chip best. Arduino cores exist but eat the 16 KB flash alarmingly fast.

FLASHING: THREE WIRES

The CH32V003 uses a 1-wire debug interface (SDI). No SWCLK. On the TENSTAR board the programming header is the three center pads on the underside:

WCH-LINKEBOARD PADWHAT IT IS
SWDIO / TMSSWDInternally the MCU's PD1/SWIO pin
3V3VPower — ONLY when USB-C is unplugged
GNDGCommon ground — always connected

The LinkE's RX/TX pins are a bonus USB-serial adapter (wire LinkE RX → PD5 for printf), not programming pins.

Then from the project folder:

pio run            # build
pio run -t upload  # flash via LinkE

RECOVERY

  • Upload fails → recheck the three wires, then try upload_protocol = minichlink.
  • Chip seems bricked (SWIO reused as GPIO) → WCH-LinkUtility → "Clear All Code Flash By Pin NRST".
  • Probe invisible → reinstall both driver SETUP.EXEs, replug.

PINOUT (TSSOP-20 BOARD)

Every pin, what it multiplexes to, and whether it has a trap. The two trap pins are highlighted.

PINANALOGTIMER PWMSERIALNOTES
PA1A1T1CH2Also crystal OSCI
PA2A0T1CH2NAlso crystal OSCO
PC0T2CH3Free
PC1T2CH4I²C SDAFree
PC2T2CH2I²C SCLFree
PC3T1CH3Volume LED in this build
PC4A2T1CH4MAX4466 mic in this build
PC5T1ETRSPI SCK5V-tolerant (FT)
PC6SPI MOSI5V-tolerant (FT)
PC7T1CH2_SPI MISOFree
PD0T1CH1NFree
PD1T1CH3N⚠ SWIO — the programming pin. Leave it alone.
PD2A3T1CH1H-bridge IN1 (forward) in this build
PD3A4T2CH2H-bridge IN2 (reverse) in this build
PD4A7T2CH1ETRFree
PD5A5UART TXprintf output → LinkE RX
PD6A6UART RXFree / serial in
PD7T2CH4⚠ NRST — drive it low and the chip RESETS. Found out the hard way.

PD VS PC — WHAT'S THE DIFFERENCE?

Nothing mystical. They are just two GPIO banks (Port C, Port D) with their own registers. What differs per pin is (1) which peripherals it routes to, (2) 5 V tolerance — only PC5/PC6 are FT, and (3) special roles, which both live on Port D: PD1 = SWIO, PD7 = NRST. Pick pins by peripheral, not by letter.

GOTCHAS — LEARNED LIVE

Every one of these was hit while building this project. Read them and skip the pain.

Wired a blink LED to PD7. It turned on… and never off. Why: PD7 doubles as NRST. Driving it low to turn the LED off yanks reset, the chip reboots, sets the pin high again, forever. Looks exactly like a hang with the LED lit. Use PC3 (or any non-trap pin) instead.

Confirmed with a meter: every pad marked V on this board — including the center programming-header V — is one shared rail, and it sits at ~5 V whenever USB-C is plugged in. If the LinkE's 3V3 output were connected at the same time, 5 V would be backfed into the programmer. Rule: one power source at a time. USB-C powered → connect only SWD + GND to the LinkE.

The CH32V003's ADC full-scale reference is VDD itself. Run the whole board at 5 V and the ADC range is 0–5 V; power the MAX4466 from the same rail and its output physically cannot exceed VDD. The danger case is only a mixed-rail setup (5 V mic into a 3.3 V-powered chip). Single rail = automatically safe.

The WCH-LinkE has two personalities. USB PID 8010 = RISC-V mode (what you want). 8012 = ARM/DAP mode (uploads silently fail). Hold ModeS while plugging in to switch back.

The '003 introduced a 1-wire SDI interface. The older WCH-Link (no E) does not support it. When ordering, the E matters.

The haptics FFT is a 64-point radix-2 in Q15 fixed-point: int16 buffers, a 17-entry quarter-wave sine table, zero heap, zero floats. Whole firmware: ~3 KB flash, ~280 bytes RAM. Write like it's 1985 and the chip rewards you.

THE HAPTIC BUILD

Mic in → tiny FFT → three frequency bands → different motor pattern per band, plus an LED that breathes with volume. All hardware PWM.

MAX4466 → PC4/ADC 64-pt Q15 FFT @ 8 kHz LOW / MID / HIGH H-BRIDGE PATTERNS
BANDRANGEMOTOR PATTERN
LOW~125–375 Hztap tap (two short forward pulses)
MID~500–1125 Hzcontinuous spin, duty tracks energy
HIGH~1250–3000 Hztap taaaap tap taaaap (reversing)
WIREFROMTO
Mic signalMAX4466 OUTPC4
Mic powerMAX4466 VCC/GNDBoard V / G (same rail!)
Motor fwdPD2 (TIM1_CH1)H-bridge IN1
Motor revPD3 (TIM2_CH2)H-bridge IN2
MotorH-bridge OUT A/BVibration motor
Volume LEDPC3 → resistorLED+ … LED− → GND

Firmware lives in src/main.c next to this page. Key tuning knobs at the top of the file: ENERGY_GATE (noise floor), BAND_MARGIN (band separation), PWM_DUTY_FLOOR/MAX (motor kick and ceiling), LED_VOLUME_GATE/MAX (LED range), TAP_*_MS (rhythm), BIN_* (band edges).

Firmware guarantees only one H-bridge input is PWM'd at a time and coasts between direction flips — no shoot-through.

POWER: USB-C, LINKE, COIN CELL

SOURCEV RAILVERDICT
USB-C~5 VBest for the full haptic rig. Disconnect LinkE 3V3 first.
WCH-LinkE 3V3~3.3 VFine for flashing + mic + LED dev. Marginal for the motor.
CR2032 coin cell3 V nominalMCU + LED: yes. Motor: short bursts only — see below.

THE COIN CELL TRUTH

A CR2032 stores ~220 mAh but has high internal resistance (~10–30 Ω). The CH32V003 idles in single-digit mA — weeks of runtime. But an ERM vibration motor pulls 70–100 mA spikes, which sag a CR2032 below the MCU's brown-out and cause resets. If you want untethered haptics:

  • Add a big electrolytic (220–470 µF) across the H-bridge supply to soak up spikes, and keep patterns short — the tap patterns here help.
  • Better: a rechargeable LIR2032 (3.6–3.7 V, lower ESR) or two CR2032 in parallel.
  • Best: a small LiPo (3.7 V, 100+ mAh) — same voltage class, none of the sag.

Whatever the source: motor power goes through the H-bridge, grounds are shared, and a motor never ever touches a GPIO directly.