Hello World with CH32V003
I make a lot of my DIY/hobby electronics purchases from AliExpress. This is a common practice, as most of the more powerful silicon that you can get, that also has an open datasheet and a wide community, comes from China. For example, for the low price of a couple of dollars you can get an Allwinner V3s SIP, which is an ARM Cortex processor packaged with 64MB of DDR2 RAM right on the die. This is a Chinese ARM processor, made by one of many random chip foundries, and the part has no documend user service agreement, end of life agreement, or gurantee of availability. However, the chip does have a fully open datasheet, there are several people online who have used it in various single board computer designs, and there’s even a forum if you want to try to venture a question to Allwinner itself (your mileage may vary). While this would obviously be a rough option if you were trying to make a commercial product (depending on your industry I guess), this kind of documentation is straight up not available for this sort of silicon in the US market. TI doesn’t sell microprocessors, and if you want to get something like a datasheet for any Intel or AMD offerings you need to be a company with a signed NDA. While people may balk at the Chinese electronics market, for many projects there’s simply no choice.
Anyway, lately I was making a purchase from AliExpress and I saw that several vendors had CH32V003 microcontrollers in stock. Specifically I found the CH32V003F4P6, which is the TSOP-20 package. The CH32V003 was and is a very popular microcontroller that’s gotten quite a following online. Produced by WCH (the same company that designed the CH340 USB to TTY converter), this MCU has a RISC-V core based on the QingKe RISC-V2A core design, a large number of supported hardware protocols (including a UART, I2C, SPI, RTC, two comparators, two groups of ADCs, hardware interrupts, etc), is reprogrammable (not OTP), has 64KB of program memory, and amazingly is only 10 cents per unit at quantity (when you can find them at quantity). Since there’s no ARM or Intel royalties to pay, WCH can apparently get away with selling this thing very cheap and still making a profit. Most amazing of all however, is that there is an easy to use IDE built around this line of chips, and the compiler is simply using gcc cross compilation for RISC-V. So it’s cheap, featureful, reprogammable, and comes with a software ecosystem that’s very easy to get up and running with no proprietary code. Quite amazing. For a list of all of it’s features, here is the datasheet for the MCU.
Dave Jones, who hosts the EEVBlog channel on YouTube, started the popularity frenzy regarding this MCU back in 2023 with this video where he showed how easy it was to install the IDE, get the programmer connected, and do an “LED blinky” test.
I ordered a CH32V003 dev board, a very simple board that basically just has a programming header, USB-C port (I’m pretty sure just for power), an LDO for 3.3V and breakouts for all of the pins. I also bought a programmer, and I ordered 100 of the units cause they were on sale at a 14 cent price point which I thought was pretty good. Somehow, and I’m really not sure how, part of my order got doubled (I’ve checked, I only paid once lol) and I ended up with over 200 of the MCUs (as well as multiple copies of a bunch of other stuff I was buying). Now that I have so many MCUs it seemed like a good idea to sit down and figure out how to develop with the things.
Following Dave’s video I downloaded the Eclipse based MounRiver IDE and got it running on Linux. Some people have reported issues getting the programmer to work correctly (the programmer comes with two separate programming modes, serial and flash, and apparently some firmware versions have difficulty switching between the two), but I didn’t have issues myself. I simply plugged the programmer in and it was recognized by MounRiver. WCH has a large amount of example code developed for the MCU in their reference function zipfile available on the WCH website. Taking the provided GPIO example I followed Dave’s video, switching the output GPIO to pin D4, which caused the onboard LED to blink.
While this was nice, I wanted a little more substance in my Hello World program. Last year while practicing leaning KiCad and making PCBs, I made a little test board with an SMD 74HC595 shift register, 8 LEDs, and 8 current limiting resistors. Breakouts for the inputs to the 74HC595 were routed down to the base of the board on a 2.54mm pin header. The idea was just to design something simple and functional, to learn the basics of PCB design, routing, using vias, and SMD soldering. That seemed like a fun thing to try to drive with the CH32 MCU as a Hello World project.
Driving a shift register (well, driving any number of shift registers in series) requires three pins:
- A data pin that serials the data into the shift register one bit at a time.
- A ‘shift register clock’ that toggles whenever the data line is ready to shift a bit into the register.
- A ‘storage register clock’ (also known as a latch) that toggles to move all bits from the internal register to the storage register that’s connected to the output pins. Not all shift registers have this feature, some shift registers simply connect the output pins directly to the shift register, the downside of this being that the contents of the shift register are always shown, even when you’re in the middle of a write operation.
The datasheet for the 74HC595 register on this breakout board can be found here.
Setting up the hardware and code was fairly simple. Simply pick three GPIOs (I used three GPIOs off of port D, since we were already configuring GPIO port D in the GPIO example), and declare one pin for each use. Since there’s 8 LEDs/output pins on the shift register, we declare an array of characters for our patterns. Each character defines a “state” of the shift register output. The program loop is pretty simple:
- Grab whatever pattern that should be shown out of the array.
- Shift that byte out to the shift register, cycling the shift register clock at each bit.
- Cycle the storage register clock, so the output shows on the LEDs (aka storage register).
- Wait a defined period of time.
- Pick the next pattern from the array (or whatever pattern it’s supposed to pick next), and start back at the top.
I’ve gone ahead and posted this code up to GitHub. Fair warning, it looks like there’s a lot of code in there but there’s really not, I just need to think of a better .gitignore file for this kind of project. The only “actual code” in there that is different from the header files, etc that come with the WCH toolchain is main.c, which is located in User/. It worked fine, but I did have a couple of issues that I’ll talk about next.
So clearly success was found in the end, but it actually took a night of troubleshooting to get there. For my GPIOs, I first selected PD5, PD6, and PD7. These made sense since they were all on GPIO port D and I could go ahead and just add them to the pin configuration without much fuss. After hooking everything up and writing a basic program however, the shift register breakout was unresponsive. What I eventually found was that, at least on my dev board, PD7 does not appear to be a great choice for ‘high current’ outputs. I wrote a few test programs, playing around with the various pins, and I could never get PD7 to output hardly any current at all. It would barely glow the LEDs when testing, and it appeared to not even source enough current to drive a high impedence input on the shift register. I’m not entirely sure what is going on here, but I believe it’s one of two things.
Either:
- My dev board, or rather the MCU soldered on my dev board, is faulty.
- My initial pin setup was incorrect, and PD7 was defaulting to some operation other then being a general purpose output.
The second one is more likely I believe. Regardless, when I noticed that PD7 wasn’t behaving as expected, I moved that pin to PD4 and everything started working as designed.
Another gotcha I experienced, I had written a nested for loop and I had accidentally assigned ‘i’ as the iterator in both loops. Normally (at least in Java, my normal programming language at work) the compiler would balk at this name space confliction and you’d get an error (or at least a warning). The compiler here did not think twice about it and operated with very odd behavior until I caught that. C is famously like that (enough rope to shoot yourself in the foot, or so it’s said), so I don’t think that’s any kind of error, just something to watch out for.
Now that I’ve driven this shift register breakout properly and done what I consider a ‘proper’ Hello World with this MCU, I’d like to expand a little bit into other peripherals. I think in the next week or so I’d like to work out driving a seven segment with it (dynamic 7 segment driving, so using only a couple of pins to matrix it together), and probably start messing around with I2C control as well with the hardware peripheral. Oh, and I’d like to get the UART working. Lot’s of fun, stay tuned!