I wanted to see if an ESP32-S3 could be used as a programmer for the WCH CH32V003 instead of using a dedicated WCH-Link.
The final setup is:
PC → ESP32-S3 → SWIO → CH32V003
The ESP32-S3 handles the timing-sensitive SWIO communication and the CH32 debug/DMI interface. On top of that I implemented target detection, memory access, flash unlock, page erase, programming, read-back verification and reset/run.
Hardware
- ESP32-S3 N8R2
- CH32V003A4M6 (SOP-16)
- 4.7kΩ–10kΩ SWIO pull-up
- CP6208 motor driver
- Small DC motor
- 3.7V Li-ion battery
- Breadboard
Important connections:
- ESP32-S3 GPIO10 → CH32V003 SWIO
- ESP32-S3 3.3V → CH32V003 VDD
- Common GND
- External pull-up on SWIO
- CH32V003 PC4 → CP6208 control input
The software stack
The programmer is split into several layers:
text
PC
│
│ Python host tool
▼
ESP32-S3
│
│ SWIO
▼
WCH DMI
│
▼
CH32V003 debug module
│
▼
Abstract commands / program buffer
│
▼
Flash controller
The ESP32-S3 is doing the SWIO timing directly rather than relying on a separate programmer IC.
I used existing open-source CH32/SWIO implementations as references, particularly CNLohr's CH32V003 work and BlueSyncLine's SWIO implementation.
Getting SWIO working
The first versions did not work.
One of the early failures was:
SWIO sync: FAILED
DMCFGR = 0xFFFFFFFF
I had to work through the SWIO startup sequence, timing, receive behavior and physical wiring before getting reliable target responses.
Once it was working, the programmer reported:
SWIO sync: OK
DMI communication: OK
Target detect: OK
CH32 ID = 0x0713BB91
Target memory read: OK
That gave me a stable base for the flash implementation.
Flash programming
I then added the flash controller operations:
flash unlock
64-byte page erase
fast page programming
read-back verification
target reset/run
One useful milestone was observing the flash lock transition:
FLASH_CTLR before unlock: 0x00008080
FLASH_CTLR after unlock: 0x00000200
After that I tested programming and verification using deterministic data.
The programmer was able to write data to the CH32V003 flash and read it back with zero mismatches.
The first real application test
For the first application I built a tiny bare-metal CH32V003 firmware that drives PC4.
Instead of an LED, I used a CP6208 motor driver and a small DC motor.
The first attempt failed even though the firmware had been programmed and verified.
The CH32 was actually running and PC4 was at about 3.3V.
The problem turned out to be the CP6208 input state: the second input was HIGH instead of LOW. That put the driver into the wrong state.
After grounding that input, the motor started.
That was probably the most useful debugging lesson from the entire project: the programming path can be completely correct while the final hardware application is still wrong.
Final host workflow
The programmer now has a Python command-line tool.
For example:
python tools/flash_tool.py --port COM10 --bin firmware/ch32_blink/blink.bin --addr 0x08000000 --reset
The final test performed on real hardware was:
Target detect: OK
Programming: OK
Verification: OK
Reset/run: OK
The programmed CH32V003 then executed the application and drove the motor.
Source code
GitHub:
https://github.com/Ishu1519/esp32s3-ch32-programmer
The repository includes:
ESP32-S3 programmer firmware
CH32V003 target example
Python host flasher
hardware documentation
SWIO/DMI documentation
build instructions
What's next
The current implementation is focused on the CH32V003.
The next interesting question is how far the same ESP32-S3 transport can be extended to other WCH RISC-V devices, and whether more debugging functionality can be added beyond programming.

# I Built an ESP32-S3 CH32V003 SWIO Programmer



Top comments (0)