Programming the GD32V Longan Nano

Plasma on GD32V

RISC-V is gaining traction and some development boards have already popped up. One of them is the widely available Sipeed Longan Nano. Written information is a bit sparse at the moment. Let’s try to fix this with a quick writeup on wiring and programming the board. If you just want to see what the board can do here is a video instead.

Nuclei SDK

For programming a GD32V series SoC best choice at the moment is the Nuclei SDK. It seems to be well maintained, exceptionally well structured and is quite easy to learn. Developing is done with your favourite text editor.

The SDK supports three different real time operating systems: FreeRTOS, UCOSII and RT-Thread. Since Longan Nano has only 32kB SRAM you might want to stay baremetal instead.

Nuclei SDK does support Longan Nano out of the box. Basic hello world and the Makefile would look like this.

#include <stdio.h>

void main(void)
{
    printf("Hello world\r\n");
}
TARGET = firmware
NUCLEI_SDK_ROOT = ../nuclei-sdk
SRCDIRS = .

include $(NUCLEI_SDK_ROOT)/Build/Makefile.base

You would compile and upload it with the following commands.

$ make SOC=gd32vf103 BOARD=gd32vf103c_longan_nano all
$ make SOC=gd32vf103 BOARD=gd32vf103c_longan_nano upload

The SDK will take care of basic things such use redirecting STDOUT to USART. This is where the Sipeed USB-JTAG/TTL RISC-V Debugger really pays off. In addition to the JTAG interface it also acts as an USB to TTL converter.

$ screen /dev/ttyUSB1 115200

Nuclei SDK Build Time: Nov 14 2020, 23:17:41
Download Mode: FLASHXIP
CPU Frequency 108540000 Hz
Hello world

Uploading with the Sipeed RISC-V debugger

To make both JTAG and serial interface work you need to connect all pins except NC (duh!) between the debugger and Longan Nano. If nitpicking second ground is also optional. Longan Nano RST is pin number 7 on the left side of the USB socket.

Wiring GD32V with Sipeed debugger

Debugger Longan Nano
GND GND
RXD T0
TXD R0
NC
GND GND (optional)
TDI JTDI
RST RST
TMS JTMS
TDO JTDO
TCK JTCK

When flashing you also need to connect the USB-C socket to provide power. When using Nuclei SDK you can flash the firmware with make.

$ make SOC=gd32vf103 BOARD=gd32vf103c_longan_nano upload

Wiring GD32V with J-Link

Debugger Longan Nano
VREF 3v3
GND GND
TDI JTDI
RST RST
TMS JTMS
TDO JTDO
TCK JTCK

You can also use SEGGER J-Link Commander to upload the firmware. The command line utility requires the firmare to be in hex format.

$ riscv-nuclei-elf-objcopy firmware.elf -O ihex firmware.hex

You can connect to Longan Nano’s JTAG interface automatically with the following.

$ JLinkExe -device GD32VF103VBT6 -speed 4000 -if JTAG \
  -jtagconf -1,-1 -autoconnect 1

SEGGER J-Link Commander V6.86e (Compiled Oct 16 2020 18:21:57)
DLL version V6.86e, compiled Oct 16 2020 18:21:45

...

J-Link>

To upload a new firmware manually, first halt the CPU and load the firmware.hex from above. Reset the core and peripherals. Set the program counter to 0x08000000 and finally enable the CPU and exit the command line utility.

J-Link>halt
J-Link>loadfile firmware.hex
J-Link>r
J-Link>setPC 0x08000000
J-Link>go
J-Link>q

If the new firmware does no run automatically you might need to powercycle the board.

While manually poking the internals might be fun it gets bothersome in the long run. You can also put the above commands to an external file and pass it to JLinkExe to do all of the above automatically.

$ cat upload.jlink
halt
loadfile firmware.hex
r
setPC 0x08000000
go
q
$ JLinkExe -device GD32VF103VBT6 -speed 4000 -if JTAG \
  -jtagconf -1,-1 -autoconnect 1 -CommanderScript upload.jlink

Uploading via USB

If you don’t have an external debugger it is also possible to upload via USB. At the time of writing you need to use latest dfu-util built from source.

$ git clone git://git.code.sf.net/p/dfu-util/dfu-util
$ cd dfu-util
$ ./autogen.sh
$ ./configure --prefix=/opt/dfu-util
$ make -j8 install

Then add /opt/dfu-util/bin to your $PATH and you should be able to flash the firmware via USB.

$ make SOC=gd32vf103 BOARD=gd32vf103c_longan_nano bin
$ dfu-util -d 28e9:0189 -a 0 --dfuse-address 0x08000000:leave -D firmware.bin

Before running dfu-util you need to put the board to download mode. Do this by holding down the BOOT and RESET buttons and then release the BOOT button to enter download mode.

Hello World on Screen

For graphics programming you could use HAGL. As the name implies HAGL is a hardware agnostic graphics library. To make it work with Longan Nano you also need a HAGL GD32V HAL

$ cd lib
$ git clone [email protected]:tuupola/hagl.git
$ git clone [email protected]:tuupola/hagl_gd32v_mipi.git hagl_hal

Add both dependencies to the project Makefile.

TARGET = firmware
NUCLEI_SDK_ROOT = ../nuclei-sdk
SRCDIRS = . lib/hagl/src lib/hagl_hal/src
INCDIRS = . lib/hagl/include lib/hagl_hal/include

include $(NUCLEI_SDK_ROOT)/Build/Makefile.base

With all this in place you can create a flashing RGB Hello world!

#include <nuclei_sdk_hal.h>
#include <hagl_hal.h>
#include <hagl.h>
#include <font6x9.h>

void main()
{
    color_t red = hagl_color(255, 0, 0);
    color_t green = hagl_color(0, 255, 0);
    color_t blue = hagl_color(0, 0, 255);

    hagl_init();
    hagl_clear_screen();

    while (1) {
        hagl_put_text(L"Hello world!", 48, 32, red, font6x9);
        delay_1ms(100);

        hagl_put_text(L"Hello world!", 48, 32, green, font6x9);
        delay_1ms(100);

        hagl_put_text(L"Hello world!", 48, 32, blue, font6x9);
        delay_1ms(100);
    };
}

Animations on screen

For testing purpose lets assume we have three functions to initialise, animate and render bouncing balls on the screen.

balls_init();
balls_animate();
balls_render();

You can find the actual implementation in GitHub. With above functions we can implement the main loop.

#include <hagl_hal.h>
#include <hagl.h>

void main()
{
    hagl_init();
    balls_init();

    while (1) {
        balls_animate();
        hagl_clear_screen();
        balls_render();
    };
}

Bouncing balls with GD32V

Writing directly to display is fine for unanimated content. However above code will have a horrible flicker. Problem can be fixed by enabling double buffering in hte Makefile.

COMMON_FLAGS += -DHAGL_HAL_USE_DOUBLE_BUFFER

With double buffering enabled we also need to flush the contents from back buffer to display ie. front buffer. Here I also add some delay to slow down the animation.

#include <nuclei_sdk_hal.h>
#include <hagl_hal.h>
#include <hagl.h>

void main()
{
    hagl_init();
    balls_init();

    while (1) {
        balls_animate();
        hagl_clear_screen();
        balls_render();

        hagl_flush();
        delay_1ms(30);
    };
}

This is very naive approach and you need to manually adjust the delay to avoid tearing. It would be better to implement an fps limiter and flush the contents, for example 30 times per second.

Even though the Longan Nano is not the fastest and has only 32kB of memory the Nuclei SDK makes it pleasant to work with. Despite being tiny you can still do interesting stuff such as old school demo effects with it.

Additional reading

Posted in

Electronics GD32V HAGL