CH32V003F4P6 - Setup and blinky, introduction to GPIOs

In this article, I am aiming to begin something big. My goal is to create a video and article series through which I introduce people to the world of CH32 RISC-V microcontrollers.

The reason why I picked this MCU is simple. It is cheap and accessible. The CH32V003F4P6 board and programmer I am going to use in this series cost less than $3. It is cheaper than an Arduino Nano. Also, there was a lot of hype around it when it appeared on the market, but it seems that no one uses it or creates projects and tutorials around it. When I search for “ch32v003” on YouTube, there are only a few good projects and demos, but there are no good tutorials on it. Even big YouTubers seem to neglect it, and all they use the microcontroller for is to push their affiliate links to their audience. But there’s no added value in their content. So, my goal here is to (hopefully) fill the market gap. Although, I will also share my affiliate links because it would be nice to get some of the money back that I invested in this project. It does not cost anything for you to use my links anyway and I can promise that the content will be valuable.

Introduction

So, the tutorial series, at least the first level will only use the CH32V003F4P6 chip. This is a rather powerful little chip, especially if we consider its price. It is more or less comparable with an Arduino Nano (Atmega328p). The CH32 microcontroller has a 3-times faster clock (48 MHz vs 16 MHz), it has the same amount of RAM (2 kB) and half the flash (16 kB vs 32 kB). It has fewer GPIO pins (18 vs 23), but it has similar peripherals (UART, SPI, i2c) and the same ADC resolution (10-bit).

The Arduino’s real advantage is the software support. It has a nearly two decades history, the first Arduino board was released in 2006, but the software’s history is even older. The CH32 started to pop up around 2022, but I could not find the exact date, unfortunately. Therefore, it is still an immature device in terms of community and support. Actually, there are some attempts to build an Arduino core for these microcontrollers, but it is not as good as for example the stm32duino core. The CH32 has its own developing environment called MounRiver Studio and there are a bunch of examples and documentation are provided for the chip. The programming style is somewhat similar to STM32’s HAL using the STM32CubeIDE. So, if you are familiar with that ecosystem, this won’t cause issues to you either. However, if you can already develop code in STM32CubeIDE, then you already surpassed the “Arduino barrier” and probably these tutorials are not relevant or too easy for you. I will base my tutorials on the provided examples, however, I will expand them with more functions and explain everything carefully.

 

WCH-LinkE mini programmer // Clicking the image forwards you to AliExpress via my affiliate link!

 

CH32V003F4P6 microcontroller board // Clicking the image forwards you to AliExpress via my affiliate link!

Getting started

 

This part does not require too much explanation. Just install the MounRiver Studio to get everything on your PC. Then wire the WCH LinkE to the microcontroller. This requires a little attention. The power wires are self-explanatory, GND to GND and 3V3 to VCC. Then SWD goes to SWIO and SWC goes to NC. Actually, NC means “not connected” here, so although the wire for the SW clock is physically there, it is not used. This is because the microcontroller uses SWD which is also known as Single Wire Debug interface. Furthermore, we will use the USART, so let’s already connect RXD to PD5 and TXD to PD6. The programmer dongle is not only useful for programming and debugging the microcontroller, but it also acts as an “FTDI converter”, basically a USART to USB adapter. Since the CH32V003F4P6 chip I use does not have a native USB, this capability of the programmer is super useful.


Now a new project must be created in MounRiver Studio. Under File/New/MounRiver Project, create a new project. First, select the chip family (CH32V003), then the exact chip (CH32V003F4P6), then give a name to your project (e.g. CH32V003F4P6_GPIO_Exercise) and press finish. Then in the project explorer, select the project, expand it and search for the folder named User. Open it, and then open the main.c source file. This is the "equivalent” file to the .ino file when you write an Arduino code. The code will be already populated with some initial configuration which is prepared so the microcontroller sends some data to the serial terminal. To upload this “raw” code, just build the solution (F7) and then “download” it (F8). I put the “download” in quotes because I don’t quite agree with the choice of the word. Even though I am not a native English speaker, downloading would imply fetching the code on the device I directly use. And then uploading would mean that I send it to the microcontroller or to another device that I am not using directly. The Arduino IDE uses this word (“upload”) and it makes more sense. But don’t let this distract us, just press “Download” to upload the data to the MCU.

To open the console and see what’s going on in the serial terminal, go to Window, click on Show View and open the Other item. Then, open the Terminal. This will create a tab for the serial terminal in the main window. To open the connection, click the green terminal icon and make sure that the correct parameters such as the serial port, baud rate and encoding are selected. Press OK when everything is set up correctly and you are ready to see whatever is sent to the PC from the microcontroller. However, I don’t really like the built-in terminal. It is a bit buggy in my opinion. So, in my demonstrations, I will probably use CoolTerm most of the time.

 

Create a new project

Select the microcontroller

Coding

The final main.c file is uploaded to my GitHub. If you want, please feel free to grab it, and if you can afford it, please consider becoming my YouTube channel’s member or leave a small donation so I can put more effort into similar tutorials and projects.

So, as I said, my tutorials are inspired by the examples provided by CH32, however, I expand them with more functions and I also carefully explain how the code works and so on. Since the default project file already contains the USART code, I will build on it, however, this tutorial will be about GPIOs. I also want to mention that the whole code is more carefully explained in the video at the top of the page, here I just highlight some of the more peculiar lines. Furthermore, make sure you study the ch32v00x_gpio.h and ch32v00x_gpio.c files under the Peripheral/inc/and Peripheral/src/ folders in the project. It will help to understand the things I explain.

In the default code that comes with the main.c when the project is created, there’s already an implemented USART part. The USART pins are on PD5 (TX) and PD6 (RX) and they are connected to the corresponding pins on the programmer so we can communicate with the microcontroller via a serial terminal. There are two things worth mentioning in the initializing code of the USART pins:

 
 

The first line belongs to the PD5 pin and it means that we are using the alternate function (AF) of the pin, which is in this case the USART function. Also, PP means Push-Pull, so the impedance is controlled by the microcontroller, based on the actual set value (high or low). On the contrary, the PD6 pin is an input pin (IN)(which makes sense, since this is the RX pin) and it is set to floating which means that whatever is connected to the pin should control whether it is high or low. In this case, it is done by the TX pin of the WCH dongle.

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;

But these are just the default USART pins. I want to show you how to read the pins by polling them (interrupts come in another article) and how to write them. The easiest way to do this is by using buttons and LEDs. I will use 4 buttons and 4 LEDs to have a bit more spectacular demonstration.

 

I show a typical pin definition with a little twist. If you want to define multiple pins with the same function, they can be combined with the bitwise OR (“|”) operation like I did in the first line. Then, I set the pins as inputs in floating mode because I wired the buttons with a pullup resistor. I picked a random speed as 50 MHz, but if you read the ch32v00x_gpio.h, it will be redefined to 30 MHz. Probably overkill anyway. Then finally I tell the code that I want pins 0, 2, 3 and 4 on Port C (GPIOC). Then, since these 3 parameters are stored in the GPIO_InitStructure struct already, we can pass them to the GPIO_Init() function by passing its address and at the same time we can also tell the function that we want to apply these settings on the pins on port C (GPIOC).

 
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4; 
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
 

So, we want to read these pins somehow. This can be done very easily. Using the GPIO_ReadInputDataBit() function, we can read the value of the bit. We just need to tell the function which port and which pin we are interested in. Then the function returns SET (high) or RESET (low).

Along this idea, we can then check if the bit is SET or RESET and do something.

 
GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_0)
if(GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_0) == Bit_SET)
if(GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_0) == Bit_RESET)
 

Similarly, we can write the GPIO pins by, instead of reading the bits, writing the bits. The GPIO_WriteBit() function can do this it we tell which port, which pin and which value we want to apply. If you notice, this is actually not super far from the Arduino’s digitalWrite(pin number, pin state) logic.

 

Then, let’s spice this up a bit more by combining reading and writing. Basically, we can do toggling in a single line. For example, the code I show here modifies the PD0 pin (port D, pin 0) in the following way: It reads the PD0 pin, and it check if it is set to high. If it is set to high (SET), the pin will be set to low (RESET). Otherwise, if the pin was set to low (RESET), the pin will be set to high (SET).

 
GPIO_WriteBit(GPIOD, GPIO_Pin_0, Bit_SET);
GPIO_WriteBit(GPIOD, GPIO_Pin_0, Bit_RESET);
 
GPIO_WriteBit(GPIOD, GPIO_Pin_0, (GPIO_ReadOutputDataBit(GPIOD, GPIO_Pin_0) == Bit_SET) ? Bit_RESET : Bit_SET);
 

If you found this content useful, please consider joining my YouTube channel’s membership or leaving a donation.

Also, please consider using my affiliate links when buying relevant gadgets.

Previous
Previous

CH32V003F4P6 - USART basics

Next
Next

USB PD Decoy breadboard power supply