This commit is contained in:
2026-10-23 10:48:05 +02:00
commit 9615b5d42f
10 changed files with 529 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
target extended-remote /dev/ttyACM0
load
monitor resset
monitor reset
run
c
exit
+9
View File
@@ -0,0 +1,9 @@
build/
*.elf
*.bin
*.hex
*.map
*.lst
*.o
compile_commands.json
.cache/
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 ch32fun-template contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+57
View File
@@ -0,0 +1,57 @@
# SPDX-License-Identifier: MIT
#
# Fully open-source CH32V003 template using ch32fun (MIT).
#
# Toolchain: Arch packages riscv64-elf-gcc + riscv64-elf-newlib
# Flashing: minichlink (MIT, part of ch32fun) or wchisp
#
# ch32fun is used as an external SDK. Point CH32FUN_ROOT at your checkout:
# git clone https://github.com/cnlohr/ch32fun ~/code/ch32fun
#
# This build does NOT use the WCH EVT SDK, WCH's GCC, libprintf, or IQmath.
TARGET ?= firmware
TARGET_MCU ?= CH32V003
# Root of the ch32fun checkout (the directory containing ch32fun/ and minichlink/).
CH32FUN_ROOT ?= $(HOME)/code/ch32fun
CH32FUN ?= $(CH32FUN_ROOT)/ch32fun
MINICHLINK ?= $(CH32FUN_ROOT)/minichlink
# Arch's riscv64-elf-newlib installs its headers here. ch32fun only needs the
# headers; it never links newlib (see -nostdlib below).
NEWLIB ?= /usr/riscv64-elf/include
# Arch's bare-metal RISC-V GCC. ch32fun auto-detects this too, but pinning it
# makes the build reproducible. Override for another toolchain, e.g.
# make PREFIX=riscv32-unknown-elf
PREFIX ?= riscv64-elf
ifeq ($(wildcard $(CH32FUN)/ch32fun.mk),)
$(error ch32fun not found at $(CH32FUN). Set CH32FUN_ROOT=/path/to/ch32fun)
endif
ifeq ($(wildcard $(NEWLIB)/stdio.h),)
$(error newlib headers not found at $(NEWLIB). Install riscv64-elf-newlib or set NEWLIB=/path/to/include)
endif
ifeq ($(shell command -v $(PREFIX)-gcc 2>/dev/null),)
$(error $(PREFIX)-gcc not found. Install riscv64-elf-gcc or set PREFIX=/path/to/toolchain-prefix)
endif
# ch32fun's build rules:
# - compiles ch32fun.c and $(TARGET).c
# - uses ch32fun.ld, -nostdlib and ch32fun/misc/libgcc.a
# - emits $(TARGET).elf/.bin/.hex/.lst/.map
include $(CH32FUN)/ch32fun.mk
# Build firmware only; never flash implicitly.
all: $(TARGET).bin
flash: cv_flash
clean: cv_clean
size: $(TARGET).elf
$(PREFIX)-size $<
.PHONY: all flash clean size
+156
View File
@@ -0,0 +1,156 @@
<!-- SPDX-License-Identifier: MIT -->
# CH32V003 ch32fun template (fully open source)
A minimal `make` project for the CH32V003 built on
[ch32fun](https://github.com/cnlohr/ch32fun) (MIT). It uses no WCH EVT SDK,
no WCH GCC, no `libprintf`, and no IQmath.
The example blinks an LED on `PD0`. Put your own application in `firmware.c`.
## Stack
| Component | Role | License |
|---|---|---|
| ch32fun | startup, headers, GPIO helpers, `printf`, linker script | MIT |
| `riscv64-elf-gcc` / binutils | compiler and binutils | GPL |
| `riscv64-elf-newlib` | **headers only** (`stdio.h`, `string.h`, ...) | BSD |
| `ch32fun/misc/libgcc.a` | compiler runtime | GCC runtime license |
| `minichlink` | flashing / SWIO monitor | MIT |
| `wchisp` (optional) | alternative flasher | GPL-2.0 |
### Why the missing `rv32ec` newlib multilib does not matter
Arch's `riscv64-elf-newlib` has no `rv32ec`/`ilp32e` library, but ch32fun
builds with `-nostdlib` and links its own `misc/libgcc.a`:
```text
-nostdlib ... -L.../misc -lgcc
```
Only newlib's **headers** are used. The CH32V003 startup, `printf`, and
runtime support come from ch32fun, so no WCH or newlib object code is linked.
## Requirements
Arch packages:
```bash
sudo pacman -S --needed base-devel riscv64-elf-gcc riscv64-elf-newlib libusb
```
`libusb` is only needed to build `minichlink` for flashing. `systemd-libs`
provides `libudev` and is normally already installed.
ch32fun checkout:
```bash
git clone https://github.com/cnlohr/ch32fun ~/code/ch32fun
```
The Makefile defaults to `~/code/ch32fun`. If yours is elsewhere:
```bash
make CH32FUN_ROOT=/path/to/ch32fun
```
It also defaults to the Arch newlib header directory
`/usr/riscv64-elf/include` and the compiler prefix `riscv64-elf`. Override
with `NEWLIB=...` or `PREFIX=...` if needed.
## Build
```bash
cd ch32fun-template
make
make size
```
Outputs:
```text
firmware.elf
firmware.bin
firmware.hex
firmware.lst
firmware.map
```
Only `firmware.bin` is written to the chip. `make` never flashes implicitly.
The template currently builds to about 620 bytes of flash:
```text
text data bss dec hex
620 0 0 620 26c firmware.elf
```
## Flash and monitor
`make flash` builds `minichlink` on first use and writes `firmware.bin`:
```bash
make flash
```
`minichlink` needs permission to access the WCH-Link. Install its udev rules
once:
```bash
sudo make -C ~/code/ch32fun/minichlink install_udev_rules
```
Then reconnect the WCH-LinkE. Wiring for SWIO programming:
```text
WCH-LinkE CH32V003
3V3 <--> 3V3
GND <--> GND
SWIO <--> PD1
```
View `printf` output over the SWIO debug channel:
```bash
make monitor
```
`make terminal` and `make unbrick` are also provided by ch32fun's build rules.
If you prefer `wchisp` from the AUR:
```bash
yay -S wchisp
wchisp flash firmware.bin
```
## Customize
- `firmware.c` — application code; `LED_PIN` is `PD0`.
- `funconfig.h` — clock and `printf` configuration.
- `TARGET` — output basename. It must match the application source name, so
`TARGET := firmware` uses `firmware.c`.
- `TARGET_MCU` — defaults to `CH32V003`; change it to target another ch32fun
MCU.
Useful overrides:
```bash
make TARGET_MCU=CH32V003
make CH32FUN_ROOT=/path/to/ch32fun
make PREFIX=riscv32-unknown-elf
make clean
```
For a Linux-hosted cross compiler instead of the bare-metal package:
```bash
make PREFIX=riscv64-linux-gnu EXTRA_CFLAGS=-no-pie
```
That path also works but is less clean; `riscv64-elf-gcc` is recommended.
## License
The template files (`Makefile`, `firmware.c`, `funconfig.h`, this README) are
MIT-licensed; see `LICENSE`. ch32fun is MIT-licensed. GCC, binutils, newlib,
and `minichlink` are open source under their respective licenses.
+28
View File
@@ -0,0 +1,28 @@
/* SPDX-License-Identifier: MIT */
/*
* Minimal ch32fun application template for the CH32V003.
*
* Blinks an LED on PD0. Replace the body of main() with your application.
* ch32fun provides SystemInit(), the GPIO helpers, delays, printf and the
* interrupt/startup code; no WCH EVT or WCH toolchain is involved.
*/
#include "ch32fun.h"
#define LED_PIN PD0
int main(void)
{
SystemInit();
funGpioInitAll();
funPinMode(LED_PIN, FUN_OUTPUT);
while (1) {
funDigitalWrite(LED_PIN, FUN_HIGH);
Delay_Ms(250);
funDigitalWrite(LED_PIN, FUN_LOW);
Delay_Ms(250);
}
}
+16
View File
@@ -0,0 +1,16 @@
/* SPDX-License-Identifier: MIT */
#ifndef _FUNCONFIG_H
#define _FUNCONFIG_H
/* CH32V003: 48 MHz from the internal HSI + PLL. */
#define FUNCONF_USE_HSI 1
#define FUNCONF_USE_PLL 1
#define FUNCONF_SYSTEM_CORE_CLOCK 48000000
/*
* printf() goes to the SWIO debug channel by default.
* Use `make monitor` (minichlink -T) to view it.
*/
#define FUNCONF_USE_DEBUGPRINTF 1
#endif
+231
View File
@@ -0,0 +1,231 @@
pwndbg: loaded 199 pwndbg commands. Type pwndbg [filter] for a list.
pwndbg: created 12 GDB functions (can be used with print/break). Type help function to see them.
Reading symbols from firmware.elf...
------- tip of the day (disable with set show-tips off) -------
Try splitting the context output into multiple TUI windows using layout pwndbg (tui disable or ctrl-x + a to go back to CLI mode)
Remote debugging using /dev/ttyACM0
0x0000028c in ?? ()
Warning: Avoided exploring possible address 0x200003fc.
You can explicitly explore it with `vmmap-explore 0x20000000` or disable automatic exploration with `set auto-explore-pages no`.
Warning: Avoided exploring possible address 0x674.
You can explicitly explore it with `vmmap-explore 0x0` or disable automatic exploration with `set auto-explore-pages no`.
Unknown register: 'a6'
Unknown register: 'a7'
Unknown register: 's2'
Unknown register: 's3'
Unknown register: 's4'
Unknown register: 's5'
Unknown register: 's6'
Unknown register: 's7'
Unknown register: 's8'
Unknown register: 's9'
Unknown register: 's10'
Unknown register: 's11'
Unknown register: 't3'
Unknown register: 't4'
Unknown register: 't5'
Unknown register: 't6'
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
─────────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────────────────────────────────────────────────────────────
GP 0x200003fc ◂— 0x8733c872
TP 0x95596104
T0 0x674 ◂— 0xffffffff
T1 1 (InterruptVectorDefault+1) ◂— 0xfa000
T2 0x20740c40 ('@\x0ct ')
S0 0x129031c0
S1 0x98029234
A0 0x2046a5c8
A1 0xfffc0600
A2 0x16e360
A3 0x40011000
A4 0xe000f000
A5 0x40011400
SP 0x200007fc —▸ 0x3ee ◂— 0xffffffff
RA 0x21e (main+6) ◂— 0x40021737
PC 0x28c ◂— 0x8d89470c
───────────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM / rv32 / set emulate on ]───────────────────────────────────────────────────────────────────────────────────────────────────────
► 0x28c c.lw a1, 8(a4) <Cannot dereference [0xe000f008]>
0x28e c.sub a1, a0
0x290 bltz a1, 0x28c <0x28c>
0x294 c.lui a1, InterruptVectorDefault+16 A1 => 0x10000
0x296 c.sw a1, 0x10(a5)
0x298 lui a0, handle_reset+6 A0 => 0x100000
0x29c c.sw a0, 0x10(a5)
0x29e lui a0, 0x400 A0 => 0x400000
0x2a2 c.sw a0, 0x10(a5)
0x2a4 c.sw a1, 0x10(a3)
0x2a6 c.lw a0, 8(a4)
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ sp 0x200007fc —▸ 0x3ee ◂— 0xffffffff
<Could not read memory at 0x20000800>
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────────────────────────────────────────────────────
► 0 0x28c
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Loading section .init, size 0xa0 lma 0x0
Loading section .text, size 0x1cc lma 0xa0
Start address 0x00000000, load size 620
Transfer rate: 5 KB/sec, 310 bytes/write.
❌️ Target does not support this command.
Starting program: /home/lukas/code/ch32v003/ch32fun-template/firmware.elf
Program received signal SIGTRAP, Trace/breakpoint trap.
0x00000262 in DelaySysTick (n=0x16e360) at /home/lukas/code/ch32fun/ch32fun/ch32fun.c:1890
1890 while( ((int32_t)( SysTick->CNT - targend )) < 0 );
Warning: Avoided exploring possible address 0x200003fc.
You can explicitly explore it with `vmmap-explore 0x20000000` or disable automatic exploration with `set auto-explore-pages no`.
Warning: Avoided exploring possible address 0x674.
You can explicitly explore it with `vmmap-explore 0x0` or disable automatic exploration with `set auto-explore-pages no`.
Unknown register: 'a6'
Unknown register: 'a7'
Unknown register: 's2'
Unknown register: 's3'
Unknown register: 's4'
Unknown register: 's5'
Unknown register: 's6'
Unknown register: 's7'
Unknown register: 's8'
Unknown register: 's9'
Unknown register: 's10'
Unknown register: 's11'
Unknown register: 't3'
Unknown register: 't4'
Unknown register: 't5'
Unknown register: 't6'
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
─────────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────────────────────────────────────────────────────────────
GP 0x200003fc ◂— 0x8733c872
TP 0x95596104
T0 0x674 ◂— 0xffffffff
T1 1 (InterruptVectorDefault+1) ◂— 0xfa000
T2 0x20740c40 ('@\x0ct ')
S0 0 (InterruptVectorDefault) ◂— j handle_reset /* 'o' */
S1 0x98029234
A0 0x10000
A1 0x2a5250d0
A2 0xfff49eb2
A3 0x16e360
A4 0xe000f000
A5 0x40011400
SP 0x200007fc —▸ 0x21e (main+6) ◂— 0x40021737
RA 0x21e (main+6) ◂— 0x40021737
PC 0x262 (main+74) ◂— 0x8e0d4710
───────────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM / rv32 / set emulate on ]───────────────────────────────────────────────────────────────────────────────────────────────────────
► 0x262 <main+74> c.lw a2, 8(a4) <Cannot dereference [0xe000f008]>
0x264 <main+76> c.sub a2, a1
0x266 <main+78> bltz a2, main+74 <main+74>
0x26a <main+82> c.j main+52 <main+52>
↓
0x24c <main+52> sw t1, 0x10(a5)
0x250 <main+56> c.lw a1, 8(a4)
0x252 <main+58> c.add a1, a3
0x254 <main+60> c.lw a2, 8(a4)
0x256 <main+62> c.sub a2, a1
0x258 <main+64> bltz a2, main+60 <main+60>
0x25c <main+68> c.sw a0, 0x10(a5)
───────────────────────────────────────────────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────────────────────────────────────────────────────────────────────────────────
In file: /home/lukas/code/ch32fun/ch32fun/ch32fun.c:1890
1884 #endif
1885
1886 void DelaySysTick( uint32_t n )
1887 {
1888 #if defined(CH32V003) || defined(CH32V00x)
1889 uint32_t targend = SysTick->CNT + n;
► 1890 while( ((int32_t)( SysTick->CNT - targend )) < 0 );
1891 #elif defined(CH32V20x) || defined(CH32V30x) || defined(CH32X03x) || defined(CH32L103) || defined(CH582_CH583) || defined(CH591_CH592)
1892 uint64_t targend = SysTick->CNT + n;
1893 while( ((int64_t)( SysTick->CNT - targend )) < 0 );
1894 #elif defined(CH32V10x) || defined(CH570_CH572) || defined(CH584_CH585) || defined(CH32H41x)
1895 uint32_t targend = SysTick->CNTL + n;
1896 while( ((int32_t)( SysTick->CNTL - targend )) < 0 );
1897 #elif defined(CH571_CH573)
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ sp 0x200007fc —▸ 0x21e (main+6) ◂— 0x40021737
<Could not read memory at 0x20000800>
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────────────────────────────────────────────────────
► 0 0x262 main+74
1 0x262 main+74
2 0x21e main+6
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Continuing.
Program received signal SIGTRAP, Trace/breakpoint trap.
0x00000264 in DelaySysTick (n=0x16e360) at /home/lukas/code/ch32fun/ch32fun/ch32fun.c:1890
1890 while( ((int32_t)( SysTick->CNT - targend )) < 0 );
Unknown register: 'a6'
Unknown register: 'a7'
Unknown register: 's2'
Unknown register: 's3'
Unknown register: 's4'
Unknown register: 's5'
Unknown register: 's6'
Unknown register: 's7'
Unknown register: 's8'
Unknown register: 's9'
Unknown register: 's10'
Unknown register: 's11'
Unknown register: 't3'
Unknown register: 't4'
Unknown register: 't5'
Unknown register: 't6'
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
─────────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────────────────────────────────────────────────────────────
GP 0x200003fc ◂— 0x8733c872
TP 0x95596104
T0 0x674 ◂— 0xffffffff
T1 1 (InterruptVectorDefault+1) ◂— 0xfa000
T2 0x20740c40 ('@\x0ct ')
S0 0 (InterruptVectorDefault) ◂— j handle_reset /* 'o' */
S1 0x98029234
A0 0x10000
*A1 0x2c77a1eb
*A2 0x2c6fd6a6
A3 0x16e360
A4 0xe000f000
A5 0x40011400
SP 0x200007fc —▸ 0x21e (main+6) ◂— 0x40021737
RA 0x21e (main+6) ◂— 0x40021737
*PC 0x264 (main+76) ◂— 0x4ee38e0d
───────────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM / rv32 / set emulate on ]───────────────────────────────────────────────────────────────────────────────────────────────────────
0x262 <main+74> c.lw a2, 8(a4) <Cannot dereference [0xe000f008]>
► 0x264 <main+76> c.sub a2, a1 A2 => 0xfff834bb (0x2c6fd6a6 - 0x2c77a1eb)
0x266 <main+78> ✔ bltz a2, main+74 <main+74>
↓
0x262 <main+74> c.lw a2, 8(a4) <Cannot dereference [0xe000f008]>
0x264 <main+76> c.sub a2, a1 A2 => 0x2c6fd6a6 - 0x2c77a1eb
0x266 <main+78> bltz a2, main+74 <main+74>
0x26a <main+82> c.j main+52 <main+52>
↓
0x24c <main+52> sw t1, 0x10(a5)
0x250 <main+56> c.lw a1, 8(a4)
0x252 <main+58> c.add a1, a3
0x254 <main+60> c.lw a2, 8(a4)
───────────────────────────────────────────────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────────────────────────────────────────────────────────────────────────────────
In file: /home/lukas/code/ch32fun/ch32fun/ch32fun.c:1890
1884 #endif
1885
1886 void DelaySysTick( uint32_t n )
1887 {
1888 #if defined(CH32V003) || defined(CH32V00x)
1889 uint32_t targend = SysTick->CNT + n;
► 1890 while( ((int32_t)( SysTick->CNT - targend )) < 0 );
1891 #elif defined(CH32V20x) || defined(CH32V30x) || defined(CH32X03x) || defined(CH32L103) || defined(CH582_CH583) || defined(CH591_CH592)
1892 uint64_t targend = SysTick->CNT + n;
1893 while( ((int64_t)( SysTick->CNT - targend )) < 0 );
1894 #elif defined(CH32V10x) || defined(CH570_CH572) || defined(CH584_CH585) || defined(CH32H41x)
1895 uint32_t targend = SysTick->CNTL + n;
1896 while( ((int32_t)( SysTick->CNTL - targend )) < 0 );
1897 #elif defined(CH571_CH573)
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ sp 0x200007fc —▸ 0x21e (main+6) ◂— 0x40021737
<Could not read memory at 0x20000800>
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────────────────────────────────────────────────────
► 0 0x264 main+76
1 0x264 main+76
2 0x21e main+6
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Detaching from program: /home/lukas/code/ch32v003/ch32fun-template/firmware.elf, Remote target
[Inferior 1 (Remote target) detached]
+1
Submodule CH32V003/rv003usb added at 5524a833c6