GigaDevice GD32VF103C-START development board based on GD32 VF103 microcontroller

Porting Apache Mynewt OS to GigaDevice GD32 VF103 on RISC-V

Still in progress but lemme explain why it’s important…

Head over to the Seeed online store for embedded gadgets… And you’ll see a couple of new, ridiculously low-cost development boards based on GigaDevice GD32 VF103 and the RISC-V processor

New, ridiculously low-cost development boards based on GigaDevice GD32 VF103 and the RISC-V processor

(The $16 bundle at the right includes a 2.8-inch touchscreen… Without the touchscreen it’s only $7… Which includes 8 MB of onboard flash memory!)

I expect a flood of cheap GD32 VF103 RISC-V developer boards coming real soon… Because of its odd connection to STM32 Blue Pill, a popular and cheap development board based on the Arm processor.

But RISC-V and Arm are completely different CPUs… How could the GD32 VF103 be connected to STM32 Blue Pill?

Let’s connect the dots…

1️⃣ STM32 Blue Pill is a development board based on a microcontroller built by STMicroelectronics… STM32 F103

2️⃣ Yes, the STM32 F103 microcontroller contains an Arm Cortex-M3 CPU

3️⃣ What’s the name of the new low-cost microcontroller with a RISC-V CPU? GD32 VF103… Sounds familiar?

4️⃣ No coincidence that GD32 VF103 sounds similar to STM32 F103… But their brains are different… RISC-V vs Arm!

5️⃣ Has someone surgically transplanted a RISC-V Brain into a Blue Pill Body…?

It sounds like a Frankenstein experiment… But this is no Halloween joke, we really have a strange creature here that works like Blue Pill but has the brains of a RISC-V processor!

I’m confused myself… But since I’m 100% certain we’ll see many more Frankenstein 103… er… GD32 VF103 creatures around us, I have decided to port the Apache Mynewt Embedded OS to harness the power of this new creature. (So that we can all exploit it for meaningful purposes, like IoT)

Here is my story so far…

Frankenstein in action: GigaDevice GD32VF103C-START development board

GD32 F103 vs VF103: One extra “V” makes a huge difference!

GigaDevice makes two lines of F103 products…

1️⃣ GD32 F103 (without the “V”) is based on Arm. It’s a drop-in replacement for STM32 F103. Forget about this, we won’t be talking about it, since it works exactly like STM32 F103.

2️⃣ GD32 VF103 (with the “V”) is based on RISC-V. It’s not a drop-in replacement for STM32 F103. This is the oddly-similar-yet-different gadget that we’ll be discussing here.

Can we have GD32 F103 AND VF103 on the same board?

Yes we can! We’ll see a bizarre example later in the article.

STM32 F103 (Blue Pill) vs GigaDevice GD32 VF103

System Architecture: GD32 VF103 (left) vs STM32 F103 (right)

When I first saw the documents for GigaDevice’s GD32 VF103 I was totally amazed… What magic did they use to swap the Arm processor in STM32 F103 (the heart of Blue Pill) to a RISC-V processor?

The GD32 VF103 looks exactly like the STM32 F103… same pinouts!

Pinouts: GD32 VF103 (left) vs STM32 F103 (right)

But inside there are differences. The GD32 VF103 supports USB Host Mode, not found in STM32 F103. So we could connect (in theory) a mouse, keyboard and USB drive to GD32 VF103…

USB Support: GD32 VF103 (left) vs STM32 F103 (right)

To add to the confusion, some features are named differently… STM32 F103’s Low Power “Stop Mode” becomes GD32 VF103’s “Deep-Sleep Mode”

Low Power Modes: GD32 VF103 (left) vs STM32 F103 (right)

The Memory Maps look similar… But different. RAM and ROM are still in their usual slots at 0x2000 0000 and 0x0800 0000, the sizes are the same (20 KB RAM, 64 KB ROM). But some peripherals are mapped to different addresses… Don’t blindly copy peripheral addresses!

Memory Map: GD32 VF103 (left) vs STM32 F103 (right)

As for the actual coding, GigaDevice provides a GD32 VF103 Firmware Library that looks somewhat similar to the STM32 F103 HAL provided by STMicroelectronics. Here’s a comparison of the coding for Timers…

Timer Code: STM32 F103 (top) vs GD32 VF103 (bottom)

GigaDevice deliberately made GD32 F103 code look like STM32 F103, here’s how…

Arm processors always assume that there’s an Interrupt Vector Table at the start of the program. (The table also includes the Reset_Handler address, at which the program code actually starts.)

On RISC-V there’s no requirement for the Interrupt Vector Table to be located at the start of the program. But yet if we look below, the GigaDevice Firmware Library actually implements the Interrupt Vector Table at the start of the program… By jumping over the table to Reset_Handler!

Interrupt Vectors: GD32 VF103 (left and centre) vs STM32 F103 (right)

The sequence of the Interrupt Vectors also looks similar (but not identical).

Is this blatant copying? Is it really a bad thing?

I find it remarkable that GigaDevice can roll out a RISC-V microcontroller that has everything in Blue Pill and more… At a lower price! A RISC-V microcontroller with so many features will surely face lots of porting hurdles.

So I’m glad that GigaDevice made my job easier by making their RISC-V microcontroller work like Blue Pill, one of the most popular low-cost microcontrollers of the decade. (Blue Pill / STM32 F103 was released in June 2007)

But it gets confusing and frustrating sometimes… Mynewt OS ported to GD32 VF103 looks like a cross between the STM32 F103 and SiFive RISC-V ports. And we need to dig deep into the details and find out how exactly the GD32 VF103 differs from STM32 F103.

Board Support Package, Microcontroller Package and Compiler Package in Mynewt OS

If you recall my articles on customising Mynewt OS for the STM32 L476 and Nordic nRF52 development boards, the Board Support Package is the first place to start for porting Mynewt OS. Here’s my Board Support Package for the GigaDevice GD32VF103C-START development board…

The Board Support Package refers to a Microcontroller (MCU) Package. This Microcontroller Package is meant to be reused by all boards (Sipeed, Seeed, …) that are based on the GD32 VF103 microcontroller. Most of the porting work happens inside this custom Microcontroller Package for GD32 VF103…

The ext folder inside the Microcontroller Package is important: It contains the official GD32 VF103 Firmware Library distributed by GigaDevice. (Click here and look for “GD32VF103 Firmware Library”)

The code in hal_system.c, hal_timer.c, … are wrappers around the official GD32 VF103 Firmware Library to expose the standard Mynewt HAL Interface that’s common to all microcontrollers. (That’s why it’s easy to write a Mynewt application that runs on multiple microcontrollers.)

The Board Support Package also specifies the compiler that Mynewt should use to build the Bootloader and Application Firmware Images. Here’s the custom Compiler Package that defines the RISC-V rv32imac compiler we’re using (downloaded from the GCC xPack website)…

Isn’t it amazing how easily we can switch Mynewt compilers from Arm to RISC-V… Just by changing one file?

SiFive HiFive1 vs GigaDevice GD32 VF103

Mynewt (and Zephyr) has already been ported to a RISC-V development board: SiFive HiFive1 based on the SiFive FE310-G000 microcontroller. So why don’t we start with the HiFive1 code (Board Support Package + Microcontroller Package) and tweak it for GD32 VF103?

That’s how I started… And I quickly ran into problems.

Some RISC-V features work the same way for HiFive1 and GD32 VF103… Like the msip register described below (which we use to trigger an interrupt for context switching)…

Porting Mynewt OS code (left) by reconciling the SiFive specification (centre) and the GD32 VF103 specification (right)

So in theory we could reconcile the SiFive FE310 specification with the GD32 VF103 specification… And do the mapping in code. (Note that the msip register is mapped to different addresses on SiFive FE310 and GD32 VF103)

But some RISC-V features can’t be reconciled. Below we see that interrupt handlers are implemented differently for SiFive FE310 and GD32 VF103. And the same Mynewt Kernel function can’t possibly cater for both types of interrupt handlers…

Unable to port Mynewt OS code (left) because the SiFive specification (centre) can’t be reconciled with the GD32 VF103 specification (right)

Why the Irreconcilable Differences? Isn’t RISC-V a standard specification?

Remember that RISC-V is an open-source specification… Which is bound to have variations in implementation. (Pointless to create the exact same thing and compete with others)

The RISC-V CPU implemented for GigaDevice GD32 VF103 is actually the Nuclei N200 “Bumblebee” Core, created by another company (Nuclei).

The RISC-V Instruction Set is well defined. So no worries about the GCC compiler generating some dubious code that will crash a RISC-V CPU designed by SiFive or Nuclei.

The problem is with things like interrupt handlers: enabling/disabling specific interrupts, setting interrupt priorities. These are based on memory-mapped registers, which can be implemented differently by the RISC-V CPU makers.

And the RISC-V CPU makers can implement unique features as well…

1️⃣ SiFive has a “Claim Interrupt” feature that’s unique to them (Check the SiFive FE310-G000 datasheet and manual)

2️⃣ Nuclei implements an interrupt vector system that’s similar to Arm/STM32, because their CPU is supposed to work like Blue Pill on RISC-V (Check the Nuclei N200 “Bumblebee” Core docs)

So tweaking the SiFive code to make it work for GD32 VF103 will be very difficult… Might as well port the code from STM32 F103 to GD32 VF103 since they are somewhat similar! (Which I did for the HAL Timer code)

Mynewt Kernel Support for RISC-V

SiFive HiFive1 was the first and only port of Mynewt to RISC-V. Who knew that somebody someday would create a Franken-microcontroller with RISC-V for brains and Blue Pill for the body?

So as expected we have some portability issues for RISC-V in the Mynewt Kernel… After all, we have seen that different RISC-V CPUs can implement interrupts wildly differently.

I’m making some ugly patches to the RISC-V kernel files below. Hopefully when Mynewt runs on GD32 VF103, we’ll find a good way to merge my patches.

Flashing and Debugging with JTAG and OpenOCD

The ST-Link USB Programmer works really well for flashing and debugging STM32 Blue Pill, even the Nordic nRF52. Why not use it for GD32 VF103?

Well ST-Link runs on the SWD (Serial Wire Debug) protocol, which was created by Arm and is implemented inside the Arm CPU. Running SWD on RISC-V is simply not possible.

Instead, my GD32VF103C-START board connects directly to my computer via the JTAG protocol on USB, for flashing and debugging. (GigaDevice calls this interface GD-Link)

When I connect the board to my computer’s USB port, something interesting happens…

GD32VF103C-START board connected to macOS

macOS identifies the RISC-V board as a CMSIS-DAP Adapter that’s manufactured by “GD32 ARM”! Why would a RISC-V board identify itself as an Arm board?

GD32VF103C-START board containing GD32 VF103 on RISC-V (left) and GD32 F103 on Arm (right)

That’s because this particular board uses a Secondary GD32 F103 (Arm) microcontroller to serve as the JTAG flashing and debugging controller for the Primary GD32 VF103 (RISC-V) microcontroller! (Remember that GigaDevice makes Arm and RISC-V microcontrollers)

Fortunately the OpenOCD scripts for GD32 VF103 are published on the TencentOS website. (TencentOS is a lightweight embedded OS that talks to Tencent Cloud)

Based on TencentOS’ OpenOCD scripts, I was able to derive and test successfully the flash scripts and GDB Debugger script for Mynewt OS. The debugger is fully integrated with Visual Studio Code.

The OpenOCD scripts require a fork of OpenOCD that supports RISC-V. I have built the RISC-V fork of OpenOCD successfully on macOS (but not on Windows yet)

PlatformIO was a valuable reference for checking that the Mynewt Bootloader and Application Firmware Builds look correct. (I eyeballed the disassembled RISC-V code, check the “References” section below)

Rust on RISC-V

My custom Mynewt build scripts have been updated to build Rust Applications for hosting on GD32 VF103 (cargo build for target riscv32imac-unknown-none-elf). The Rust source code may be found in the following folders…

rust/app: Simple Rust Application that polls a temperature sensor by calling Mynewt Sensor Framework (More details)

rust/mynewt: Rust Wrappers for Mynewt API (More details)

rust/macros: Rust Procedural Macros for generating Mynewt Rust Wrappers (More details)

For the Rust Application Build Log and RISC-V Disassembly, check the “References” section below.

UPDATE 1 Oct 2020: My custom Mynewt build script needs to be updated for the latest Rust Compiler…

1️⃣ The Rust library should be built as a Static Library, not a Dynamic Library. Change rust/app/Cargo.toml from…

name = "app" # Output will be named `libapp.rlib`
test = false
bench = false


name = "app" # Output will be named `libapp.lib`
test = false
bench = false
crate-type = ["staticlib"]

2️⃣ The build script needs to link the Static Library instead of the Dynamic Library. Change scripts/ from…

#  Location of the compiled Rust app and external libraries.  The Rust compiler generates a *.rlib archive for the Rust app and each external Rust library here.rust_build_dir=$PWD/target/$rust_build_target/$rust_build_profile/deps


#  Location of the compiled Rust app and external libraries.  The Rust compiler generates a *.lib archive for the Rust app and each external Rust library here.rust_build_dir=$PWD/target/$rust_build_target/$rust_build_profile

3️⃣ Also change scripts/ from …

#  Delete the compiled Rust app to force the Rust build to relink the Rust app.  Sometimes there are multiple copies of the compiled app, this deletes all copies.rust_app_build=$rust_build_dir/libapp*.rlib


#  Delete the compiled Rust app to force the Rust build to relink the Rust app.rust_app_build=$rust_build_dir/libapp.a

4️⃣ Finally change scripts/ from …

#  Extract the object (*.o) files in the compiled Rust output (*.rlib).rust_build=$rust_build_dir/*.rlib


#  Extract the object (*.o) files in the compiled Rust output.rust_build=$rust_build_dir/libapp.a

For reference, compare with the nRF52 version of the build script for PineTime Smart Watch:

What’s Next?

Fixing Mynewt context switching on GD32 VF103 with Visual Studio Code Debugger

The work-in-progress port of Mynewt OS to GD32 VF103 may be found in the gd32vf103 branch of this repository…

As shown in the screen above, the Visual Studio Code debugger now works great with Mynewt OS on GD32 VF103. The porting work continues…

Check this article for updates…

UPDATE: Embedded Rust with Mynewt on GD32 VF103 is ALIVE! Now combing through the Microcontroller Package and fixing things…

[Watch the video on YouTube]

Embedded Rust with Mynewt on GD32 VF103 is ALIVE! With Visual Studio Code Debugger


GigaDevice GD32 VF103 documents and firmware library may be found here…

Useful documents and code from the above link…

  1. GD32VF103 User Manual”
  2. GD32VF103 Datasheet”
  3. GD32VF103 Firmware Library”
  4. “GD-Link Adapter User Manual”

Nuclei N200 “Bumblebee” Core (the RISC-V CPU for GD32 VF103) documents may be found here…

The following logs were recorded during the building and flashing of the Bootloader and Application Firmware Images…

  1. Bootloader Build Log
  2. Application Build Log (includes Rust)
  3. Image Application Log
  4. Flash Bootloader Log
  5. Flash Application Log
  6. Bootloader RISC-V Disassembly
  7. Bootloader Linker Map
  8. Application RISC-V Disassembly (includes Rust)
  9. Application Linker Map (includes Rust)