Overview
The CH32V003 is an ultra-low-cost 32-bit RISC-V microcontroller from WCH with impressive performance and power efficiency. Built on the open RISC-V architecture, it offers a compelling alternative to traditional ARM-based MCUs and as replacement for 8-bit devices while maintaining excellent compatibility with existing development tools.
With its compact footprint, rich peripheral set, and growing ecosystem, the CH32V003 is perfect for embedded projects ranging from simple LED controllers to complex IoT devices.
Open-source RISC-V architecture, extremely low cost (under $0.10 in quantity), rich peripherals, and a growing community of developers making the transition from Arduino and PIC.
Key Features
RISC-V Core
32-bit RV32EC core running at 48MHz
16KB Flash
Plenty of room for your embedded applications
Rich Peripherals
UART, I2C, SPI, ADC, Timers, PWM
Ultra Low Cost
Under $0.10 per chip in volume
Technical Specifications
| Feature | Specification |
|---|---|
| Core | 32-bit RISC-V (RV32EC) QingKe V2A |
| Clock Speed | 48 MHz (Internal Oscillator) |
| Flash Memory | 16 KB |
| SRAM | 2 KB |
| GPIO Pins | 18 (max, depending on package) |
| ADC | 10-bit, 8 channels |
| Timers | 1x 16-bit advanced, 1x 16-bit general-purpose |
| Communication | 1x USART, 1x I2C, 1x SPI |
| Operating Voltage | 3.3V / 5V tolerant |
| Package Options | TSSOP20, SOP16, SOP8, QFN20 |
| Operating Temperature | -40°C to +85°C |
Setup & Installation
Development Environment Options
There are several ways to develop for the CH32V003:
- MounRiver Studio II - Official IDE from WCH (Windows, based on Eclipse)
- PlatformIO - Cross-platform with VS Code integration
- Command Line - Using GCC RISC-V toolchain directly
- CH32FUN - CH32FUN Open source RISC-V tooling environment
1Install MounRiver Studio II (Recommended)
Download and install MounRiver Studio II from the official WCH website:
# Download from:
# http://www.mounriver.com/download
# MounRiver Studio includes:
# - GCC8 RISC-V Toolchain
# - OpenOCD for debugging
# - Project templates for CH32V0032Hardware Requirements
- CH32V003F4P6 development board or bare chip
- WCH-LinkE programmer/debugger
- USB cable
- Breadboard and jumper wires (for experiments)
3Connect the Programmer
Wire the WCH-LinkE to your CH32V003:
| WCH-LinkE Pin | CH32V003 Pin |
|---|---|
| SWDIO | PD1 (SWDIO) |
| GND | GND |
| 3V3 | VCC (3.3V) |
4Create Your First Project
In MounRiver Studio:
- File → New → MounRiver Project
- Select CH32V003F4P6 as target
- Choose a project template (e.g., GPIO)
- Build with Ctrl+B, Flash with F8
Tutorial: Blink LED
The classic "Hello World" of embedded systems! This program blinks an LED connected to pin PD0, demonstrating basic GPIO output configuration on the CH32V003.
Hardware Setup
- CH32V003F4P6 development board
- LED (any color)
- 1kΩ resistor
- Connect LED + resistor from PD0 to GND
Code
/*
* File: Main.c
* Author: Armstrong Subero
* MCU: CH32V003F4P6 w/Int OSC @ 48 MHz, 5.0v
* Program: 01_Blink
*/
#include "debug.h"
void initMain()
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
SystemCoreClockUpdate();
Delay_Init();
}
void initGPIO()
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_30MHz;
GPIO_Init(GPIOD, &GPIO_InitStructure);
}
void blinkLed()
{
GPIO_WriteBit(GPIOD, GPIO_Pin_0, Bit_SET);
Delay_Ms(250);
GPIO_WriteBit(GPIOD, GPIO_Pin_0, Bit_RESET);
Delay_Ms(250);
}
int main(void)
{
initMain();
initGPIO();
while(1) { blinkLed(); }
}Key Concepts
- RCC Clock Enable - Always enable the peripheral clock before using GPIO
- GPIO_Mode_Out_PP - Push-Pull output mode for driving LEDs
- GPIO_WriteBit() - Set pin high (Bit_SET) or low (Bit_RESET)
- Delay_Ms() - Blocking delay function from the debug library
Tutorial: SysTick Timer
The SysTick timer is a built-in system timer perfect for creating precise timing intervals. This example configures SysTick to generate a 1Hz interrupt that toggles an LED.
Code
/*
* File: Main.c
* Author: Armstrong Subero
* MCU: CH32V003F4P6 w/Int OSC @ 48 MHz, 3.3V
* Program: 10_SysTick_Timer
*/
#include "debug.h"
static void initSysTick_1Hz(void) {
NVIC_EnableIRQ(SysTick_IRQn);
SysTick->SR &= ~(1U << 0);
SysTick->CMP = (SystemCoreClock/1) - 1;
SysTick->CNT = 0;
SysTick->CTLR = 0xF;
}
void SysTick_Handler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void SysTick_Handler(void) {
if (GPIO_ReadOutputDataBit(GPIOD, GPIO_Pin_0))
GPIO_WriteBit(GPIOD, GPIO_Pin_0, Bit_RESET);
else
GPIO_WriteBit(GPIOD, GPIO_Pin_0, Bit_SET);
SysTick->SR = 0;
}For 10Hz:
SysTick->CMP = (SystemCoreClock/10) - 1;Tutorial: UART Communication
UART enables serial communication with your computer or other devices. This example creates an interactive program that responds to input.
Hardware Setup
- TX: PD5 (Alternate Function Push-Pull)
- RX: PD6 (Floating Input)
- Baud Rate: 115200
Code
/*
* File: Main.c
* Author: Armstrong Subero
* Program: 12_UART
*/
#include "debug.h"
#include <string.h>
#include <stdlib.h>
static void USART1_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure = {0};
USART_InitTypeDef USART_InitStructure = {0};
RCC_APB2PeriphClockCmd(
RCC_APB2Periph_GPIOD | RCC_APB2Periph_USART1, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_30MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOD, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_Init(USART1, &USART_InitStructure);
USART_Cmd(USART1, ENABLE);
}Tutorial: PWM Generation
Pulse Width Modulation (PWM) is essential for controlling LED brightness, motor speed, and servo positions.
Code
/*
* File: Main.c
* Author: Armstrong Subero
* MCU: CH32V003F4P6 w/Int OSC @ 48 MHz, 3.3v
* Program: 15_Pulse_Width_Modulation
* Compiler: WCH Toolchain (GCC8, MounRiver Studio v2.3.0)
* Program Version: 1.1
* * Cleaned up code
* * Added additional comments
*
* Program Description: This Program allows the CH32V003F4P6 to demonstrate
* the use of the Pulse Width Modulation module
*
* Hardware Description: An LED is connected to pin D2 via a 1k resistor.
*
* Created June 19th, 2025, 12:39 PM
* Updated January 03rd, 2026, 02:03 PM
*/
/*******************************************************************************
*Includes and defines
******************************************************************************/
#include "debug.h"
// Constants
#define PWM_MODE1 0
#define PWM_MODE2 1
// Select PWM Mode
// You can switch to PWM_MODE2 if desired
#define PWM_MODE PWM_MODE1
void initPWM_TIM1(uint16_t period, uint16_t prescaler, uint16_t duty);
void initPWM_GPIO();
/*******************************************************************************
* Function: void initMain()
*
* Returns: Nothing
*
* Description: Contains initializations for main
*
* Usage: initMain()
******************************************************************************/
void initMain()
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
SystemCoreClockUpdate();
Delay_Init();
}
/*******************************************************************************
* Function: Main
*
* Returns: Nothing
*
* Description: Program entry point
******************************************************************************/
int main(void)
{
uint16_t period = 2399; // ARR
uint16_t prescaler = 0; // PSC
initMain(); // Core system setup
initPWM_GPIO(); // Setup PD2 for PWM
initPWM_TIM1(period, prescaler, 1200); // PWM: 20kHz freq, 50% duty cycle
while(1)
{
// PWM signal continues running in hardware
}
}
/*******************************************************************************
* Function: void initPWM_TIM1(uint16_t period, uint16_t prescaler, uint16_t duty)
*
* Returns: Nothing
*
* Description: Initialize Timer1 in PWM Output Mode on Channel 1
*
* Usage: initPWM_TIM1(period, prescaler, duty)
******************************************************************************/
void initPWM_TIM1(uint16_t period, uint16_t prescaler, uint16_t duty)
{
TIM_OCInitTypeDef TIM_OCInitStructure = {0};
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure = {0};
// Enable clock for TIM1
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
// Basic timer setup
TIM_TimeBaseInitStructure.TIM_Period = period;
TIM_TimeBaseInitStructure.TIM_Prescaler = prescaler;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure);
#if (PWM_MODE == PWM_MODE1)
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
#elif (PWM_MODE == PWM_MODE2)
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
#endif
// PWM configuration
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = duty;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM1, &TIM_OCInitStructure);
// Enable PWM output and start timer
TIM_CtrlPWMOutputs(TIM1, ENABLE);
TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Disable);
TIM_ARRPreloadConfig(TIM1, ENABLE);
TIM_Cmd(TIM1, ENABLE);
}
/*******************************************************************************
* Function: void initPWM_GPIO()
*
* Returns: Nothing
*
* Description: Sets up GPIO for PWM output on PD2
*
* Usage: initPWM_GPIO()
******************************************************************************/
void initPWM_GPIO()
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
// Enable clock for GPIOD
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
// Configure PD2 as Alternate Function Push-Pull
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOD, &GPIO_InitStructure);
}
Common Duty Cycle Values
| Duty Cycle | Pulse Value (ARR=2399) |
|---|---|
| 0% (Off) | 0 |
| 25% | 600 |
| 50% | 1200 |
| 75% | 1800 |
| 100% (Full On) | 2399 |
Downloads & Resources
Get the latest tools, libraries, and documentation for CH32V003 development: