Boards

The WCH CH32V003

A 48 MHz RISC-V chip with 16 KB of flash and 2 KB of RAM, on a twenty-pin breakout. Mokxi runs Blink, Serial and the ADC on it.

The overview, with examples running, is on the board page.

What it is

The CH32V003 is WCH's cheapest microcontroller: a QingKe V2A core that implements RV32EC (the RISC-V base with sixteen registers, compressed instructions and no multiply or divide), clocked at up to 48 MHz from an internal 24 MHz oscillator and a PLL. The part here is the CH32V003F4P6, the TSSOP-20 package, on a breakout that brings its pins out to two rows of 0.1 inch holes 0.6 inch apart, so it straddles a breadboard's channel. It has no user LED and no buttons. VDD is a 3.3 V supply and GND is ground, always on.

Pins and pin numbers

The twenty pins are named as the openwch Arduino core names them: PA1, PA2, PC0 to PC7 and PD0 to PD7, plus VDD and GND. A sketch writes pinMode(PD2, OUTPUT). A bare number is the port times 8 plus the bit, with A at 0, C at 8 and D at 16, so PD2 is 18.

Pin Name Also
1 PD4 A7
2 PD5 A5, USART1 TX
3 PD6 A6, USART1 RX
4 PD7 a plain GPIO here (see below)
5 PA1 A1
6 PA2 A0
7 GND ground
8 VDD 3.3 V supply
9 PC0
10 PC1 I2C SDA on the real chip
11 PC2 I2C SCL on the real chip
12 PC3
13 PC4 A2
14 PC5 SPI SCK on the real chip
15 PC6 SPI MOSI on the real chip
16 PC7 SPI MISO on the real chip
17 PD0
18 PD1 SWIO, the programming pin
19 PD2 A3
20 PD3 A4

On the breakout the left row is pins 1 to 10 from the top and the right row is pins 20 down to 11, as a package is numbered.

A0 to A7 are ADC1 channels 0 to 7. analogRead() returns ten bits, 0 to 1023, against VDD.

The clock, and ports that are switched off

The program sets the clock. After a reset the chip runs from the 24 MHz oscillator divided by three, 8 MHz; the Mokxi runtime turns on the PLL and runs at 48 MHz before setup(), as the Arduino core does, and instructions take 1/48 of a microsecond each. A program that leaves the clock alone runs six times slower, as it would on the bench.

A port ignores writes until its clock is enabled in RCC_APB2PCENR, and reads as 0 until then. That is the chip's rule, and the first thing that bites a program that writes the registers directly. pinMode() enables the clock for you.

Serial

Serial is USART1, with TX on PD5 and RX on PD6, wired to the serial monitor. It is polled. There is no receive buffer and no interrupt yet, and the USART holds one byte, so a second byte that arrives before the first is read is lost, as it is on the chip with its interrupt off. The Serial example checks Serial.available() all the time and never waits in delay().

What is modeled

The core as RV32EC at one instruction per clock, the clock tree (HSI, the PLL and the AHB prescaler), GPIO ports A, C and D with push-pull, open drain, pull-up, pull-down and analog modes and the clock gating above, USART1 on the default pins, ADC1 as a software-triggered ten-bit conversion, and SysTick behind millis(), micros() and delay(). Register addresses and offsets are the reference manual's; the contract in docs/plans/ch32v003.md lists the few that are believed rather than checked.

What is not modeled

  • Interrupts. The PFIC is not modeled, so SysTick, EXTI and USART interrupts never fire and attachInterrupt() does not exist. A program polls.
  • PWM, so no analogWrite() or tone(), and I2C and SPI, so no Wire or SPI. These are the next version.
  • USART and AFIO pin remaps, the option bytes, flash programming, the independent watchdog and auto wakeup.
  • WCH's XW compressed instructions, and the trap the real core takes on registers x16 and up. A program built with WCH's own compiler that uses XW traps here.
  • PD7 as the reset pin. On a factory chip PD7 can be NRST depending on its option bytes; here it is a plain GPIO.
  • String, Stream and the libraries: 16 KB of flash and 2 KB of RAM do not hold them, so the Arduino library catalog does not apply to this board.

Common mistakes

Writing to a port register before enabling its clock. Nothing happens, on the chip and here.

Expecting delay() to sleep. It is a busy wait on SysTick, so the simulation runs every instruction of it.

Wiring an LED to the board and expecting LED_BUILTIN. The breakout has none; wire an LED and a resistor from a pin to GND.

Starter projects

Blink flashes an LED on PD2 through a 330 ohm resistor. Button lights it while a push button on PC4 is held. Serial prints a count each second and echoes what you type. Knob reads a potentiometer on PC4 with the ADC.

  • The board page: /boards/ch32v003
  • A RISC-V neighbor with a lot more room: the ESP32-C3
  • The plan and contract for the chip model: docs/plans/ch32v003.md