info 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.

tips_and_updates
Why Choose CH32V003?
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

memory

RISC-V Core

32-bit RV32EC core running at 48MHz

storage

16KB Flash

Plenty of room for your embedded applications

speed

Rich Peripherals

UART, I2C, SPI, ADC, Timers, PWM

attach_money

Ultra Low Cost

Under $0.10 per chip in volume

import_export

Coming from Arduino?

If you're familiar with Arduino development, transitioning to CH32V003 is straightforward. The concepts are similar, and we have a dedicated migration guide to help you get started.

View Arduino to RISC-V Migration Guide arrow_forward

description Technical Specifications

FeatureSpecification
Core32-bit RISC-V (RV32EC) QingKe V2A
Clock Speed48 MHz (Internal Oscillator)
Flash Memory16 KB
SRAM2 KB
GPIO Pins18 (max, depending on package)
ADC10-bit, 8 channels
Timers1x 16-bit advanced, 1x 16-bit general-purpose
Communication1x USART, 1x I2C, 1x SPI
Operating Voltage3.3V / 5V tolerant
Package OptionsTSSOP20, SOP16, SOP8, QFN20
Operating Temperature-40°C to +85°C

build 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 CH32V003

2Hardware 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 PinCH32V003 Pin
SWDIOPD1 (SWDIO)
GNDGND
3V3VCC (3.3V)
warning
Important: The CH32V003 uses a single-wire debug interface (SWDIO only). Make sure your WCH-LinkE is set to the correct mode for single-wire debugging.

4Create Your First Project

In MounRiver Studio:

  1. File → New → MounRiver Project
  2. Select CH32V003F4P6 as target
  3. Choose a project template (e.g., GPIO)
  4. Build with Ctrl+B, Flash with F8

touch_app Tutorial: Read Pushbutton

Learn to read digital inputs by connecting a pushbutton. This example demonstrates GPIO input configuration with internal pull-up resistors and simple debouncing.

Hardware Setup

  • LED connected to PC1 via 1kΩ resistor
  • Pushbutton connected between PC2 and GND
  • Internal pull-up resistor enabled (no external pull-up needed)

Code

code 05_Read_Pushbutton.c
/*
 * File: Main.c
 * Author: Armstrong Subero
 * MCU: CH32V003F4P6 w/Int OSC @ 48 MHz, 3.3v
 * Program: 05_Read_Pushbutton
 */

#include "debug.h"

void initGPIO(void);

void initMain(void) {
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    SystemCoreClockUpdate();
    Delay_Init();
}

int main(void) {
    uint8_t buttonState = 0;
    initMain();
    initGPIO();

    while(1) {
        buttonState = GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_2);
        if(buttonState == 0)
            GPIO_WriteBit(GPIOC, GPIO_Pin_1, Bit_RESET);
        else
            GPIO_WriteBit(GPIOC, GPIO_Pin_1, Bit_SET);
        Delay_Ms(50);
    }
}

void initGPIO(void) {
    GPIO_InitTypeDef GPIO_InitStructure = {0};
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_30MHz;
    GPIO_Init(GPIOC, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOC, &GPIO_InitStructure);
}

Key Concepts

  • GPIO_Mode_IPU - Input with internal Pull-Up resistor
  • GPIO_ReadInputDataBit() - Read the current state of an input pin
  • Active-Low Logic - Button reads 0 when pressed (connected to GND)
  • Debounce Delay - 50ms delay prevents false triggers from contact bounce

timer 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

code 10_SysTick_Timer.c
/*
 * 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;
}
tips_and_updates
Frequency Formula: Interrupt frequency = SystemCoreClock / (CMP + 1)
For 10Hz: SysTick->CMP = (SystemCoreClock/10) - 1;

cable 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

code 12_UART.c
/*
 * 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);
}

graphic_eq Tutorial: PWM Generation

Pulse Width Modulation (PWM) is essential for controlling LED brightness, motor speed, and servo positions.

Code

code 12_UART.c


/*
 * 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 CyclePulse Value (ARR=2399)
0% (Off)0
25%600
50%1200
75%1800
100% (Full On)2399

download Downloads & Resources

Get the latest tools, libraries, and documentation for CH32V003 development:

download Start Working Now!

External Resources