Exploring the CH32V003

by CanHobby in Circuits > Microcontrollers

9 Views, 0 Favorites, 0 Comments

Exploring the CH32V003

CH_ExploreL.png

In this Instructable we will explore the world of a tiny RISC-V microcontroller, the CH32V003.

Hardware Design

CH_Pico_3D_top.png

The recent introduction of the WCH CH32V003 microcontroller - sometimes referred to as the 10 cent MCU - piqued my curiousity so I decided to try my hand at designing my own dev board.

Design details - Most RISC-V MCUs are quite easy to design with, they are very straightforward - as RISC-V should be, both in Hardware and Software. There are really no hidden gotchas. Everything is orthagonal. The only decision up to the designer is where to bring the pins out to the user. In the case of a generic dev board such as this design it is a simple as 1.. 2.. 3... There are 18 GPIOs available, C0-C7 as well as D0-D7. There are 2 outliers, A0 and A1 which can be used as GPIOs, but whose main function is for connecting an external crystal. So I added the C and D port pins in order on each side of the PCB, with a Gnd and Vcc to round out the pin offering. At this point we should note that the -003 does not offer extensive pin re-mapping like the Espressif ESP chips. It does offer up to 4 remap options for the timers, only 3 for most others, and just 2 for the SPI interface. The crystal is a through hole, so not installing it gives the user 2 extra GPIO pins. The CH32V003 is a very nice about defaulting to it's handy internal oscillator if it does not encounter a crystal present.

The only embellishments I added to the circuit were LEDs and headers. A 10 pin header allows connection directly to the WLinkE programmer. Because the LinkE supplies both 3V3 and +5V I also added a jumper to select between them. The 003 will run happily with either voltage. I also added 3 LEDs, a green for power, and a blue and a yellow as user programmable LEDs on C0 and D0.

NOTE: with regard to the Link header - The 003 uses a one-wire programming interface which is only supported by the LinkE, and is not supported by the older 'RV' or 'V1.1' versions.

Sound simple, and it was. I use JLCPCB as my PCB fabricator and SMT assembler, so, of course, I used Easy-EDA to do the layout. With their close integration with LCSC, a large components distributor, my board was ready to ship in a couple of days. It arrived a few days later. I took it out of the box, plugged in the LinkE and I was up and running. I changed the GPIOs in the LED Toggle program example from the EVT section of the WCH github to reflect my LEDs, et voila . Within minutes I was at "blinky". I want to note that I did add a solder pad on the bottom of the board to enable operation of the LEDs. One of the advantages of RISC-V is low current draw so I didn't want to ruin this advantage for use in a battery operated environment.


My board is available at: CanHobby, eBay and Tindie.

Chinese clones available at CanHobby and eBay.

Programming the CH32V003.

LinkE_X_horizLONG.png

For software development on the WCH line of MCUs I recommend the Moun River Studios (MRS) IDE. It is WCH's Eclipse adaptation so should be a familiar environment for most programmers. I did try their Arduino boards package when it was first released but quickly scurried back to MRS.

I wanted to write example software for each of the peripherals contained within this MCU. Because typical demos of any peripheral require running them in an endless loop, my first thought was to use freeRTOS to simulate this action. BUT, while the -003 is a powerful little MCU, it is a little underwhelming when it comes to memory. So I winged a couple of boidys with 1 stone by choosing the TickTimer as my first peripheral. This is what freeRTOS uses to switch tasks anyway.

In the Periphs.h header file there are #defines to enable each peripheral demo that I have implemented so far. They can all run at the same time.

For ease of use I normally upload a full .zip of the entire MRS workspace to my github. Just unzip it into a separate directory and specify it as the workspace to use when you open MRS.

Here is a brief discussion on each of the peripherals: I try to have meaningful comments in the code. Besides the peripheral itself I try to feature different operating modalities such as Interrupts, DMA, pin remapping, etc. wherever possible.

Most all EVT examples begin with some system initialization. Setting the clock speed is very important. The default clock setting is 24MHz and is enabled with the:

SystemCoreClockUpdate(); function call.

to see what it does, highlight the function in the editor, and press the handy Eclipse 'F2' or 'F3' to have a closer look. F2 typically only applies to functions that are part of the current project, whereas, F3 will open the appropriate "driver" file for your examination.

For most of my examples I run the MCU at full throttle - 48MHz. This means uncommenting line 22 in the system_ch32v00x.c file which should be in the user directory along with main.c, as well as commenting out the line referring to the 24 MHz clock.

#define SYSCLK_FREQ_48MHZ_HSI 48000000

Be sure to make the appropriate selection: HSE (external crystal) or HSI (internal oscillator).

This is normally followed with the Delay_Init(); function call so that we can use the delay functions.

After that the USART_Printf_Init(115200); should be self explanatory, it allows us to print debug statements to the serial port using printf. Note that scanf is not available.

MRS does not offer an integrated monitor window. I like to use Putty to view the serial output in a separate window on my Linux Mint system.

If our board has a 10 pin connector for the LinkE programmer and the Tx/Rx pins are connected we do not need to add an additional USB/UART convertor or USB cable. The LinkE includes all of this - the appropriate pins should line up, BUT, always check, there are sometimes differences.

Soooo... now that we have the system up and running we can start exercising the peripherals:


UART - featuring Interrupts:


Most all of the WCH "EVT" example include setup of the UART to be used with printf - this part is easy.

..... more to come on this topic....


ChipId - featuring register access.


Many times it is convenient to know exactly version of the MCU our software is running on and make decisions accordingly.

Using the DBGMCU_GetCHIPID() system call reads the register at address 0x1FFFF7C4 and returns a value reflective of the version of the actual MCU. My ChipID() routine does this and prints meaningful results to the serial monitor. You can see that I use the register data returned to modify the output to the screen.

eg. Chip = CH32V003 F4U6


GPIO_Setup - featuring Initialization structures.


The ecosystem we are working in typically uses structures to supply initialization parameters to peripherals. this may be different to Arduino aficionados but it is quite simple and intuititive and als very code efficient. Program flow involves declaring an empty struct, filling it in, and then subnitting the address of the struct to the Initialization routine. Because I use the on-board LEDs in 2 of the examples we need to initialize the corresponding GPIOs. This is done in:

GPIO_Setup() in GPIO_Demo.c

You can see where I create the empty struct with:

GPIO_InitTypeDef GPIO_InitStructure = {0};

// We will be re-using this struct for each LED GPIO so we
// preload it with 2 parameters common to the LEDs:
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_30MHz;

// We then submit it to the Initialization routine for the Blue LED:
RCC_APB2PeriphClockCmd( BlueLED_ClkPort, ENABLE);
GPIO_InitStructure.GPIO_Pin = BlueLED; // the 3rd parameter is the pin number
GPIO_Init( BlueLED_Port, &GPIO_InitStructure );

and the Blue LED GPIO is all set up.. We then continue on to do something very similar for the Yellow LED.


SisTick - featuring Interrupt.


The SisTick is just a simple counter that repeats. We set it to generate an interrupt at a certain count (CMP).

NVIC_EnableIRQ( SysTicK_IRQn ); // Enable SysTicK_IRQ

SysTick->SR &= ~(1 << 0); // Clear the Compare bit (IRQ)
SysTick->CMP = SystemCoreClock / 16; // divide by more to run faster
SysTick->CNT = 0; // set Counter to 0
SysTick->CTLR = 0x0B; // use SysClk / 8 as clock source

We set up the SisTick Interrupt Service Routine to toggle the Blue LED as follow:

void SysTick_Handler(void) __attribute__((interrupt("WCH-Interrupt-fast")));

void SysTick_Handler(void) // SisTick Interrupt Handler
{
GPIO_WriteBit( BlueLED_Port, BlueLED, !GPIO_ReadInputDataBit( BlueLED_Port, BlueLED ) );

SysTick->SR = 0; // clear IRQ
}