Running assembly on the BL602 RISC-V microcontroller and directly controlling GPIO


Tags: RISC-V BL602 Assembly C

The Boufallo Lab BL602 is a 32 bit RISC-V microcontroller with support for 2.4 GHz WiFi and Bluetooth Low Energy 5.0. It has an official SDK, however even the “Hello World example” includes a large amount of boilerplate. This article will show how to manipulate the GPIO pins using assembly and no dependencies.

Git repository

The full assembly and a makefile can be found in this Github repository.


As seen on the Pine64 BL602 EVB ver 1.1 schematics LED1 is three separate diodes controlled through GPIO17 (Red), GPIO14 (Green) and GPIO11 (Blue). LED1 is not to be confused with LED2 which just lights up red when the board is plugged in.

Page 18 of the BL602 reference manual shows that the XIP (eXecute In Place) memory starts at address 0x23000000. blflash version 0.3.0 writes binary files to address 0x23000000 by default so no linker scripts are necessary.

GPIO on the BL602

GPIO on the BL602 can be controlled through several pre defined registers. Page 24 to 29 of the BL602 reference manual describes the general approach.

In order to initialize and control the GPIO pins the following must be done:

  1. The GPIO pin function and resistor type must be set. The exact register depends on the GPIO pin.
  2. The relevant bit in the GPIO Output Enable register (GPIO_CFGCTL34_OFFSET) must be set.
  3. The relevant bit in the GPIO Output register (GPIO_CFGCTL32_OFFSET) must be set.

The locations are described in the unofficial hardware notes.

Exact plan for GPIO11

The Global Register (GLB) is located at address 0x40000000.

Page 28 and 29 of the BL602 reference manual list GPIO_CFGCTL5 as the register for configuring GPIO10 and GPIO11. The register overview on page 36 describes the 16 most significant bits as being used for GPIO11 and the 16 least significant bits as being used for GPIO10. The two parts of the register have the same layout, GPIO11 is just bitshifted left 16:


Where reserved bits are blank and following symbols are used:

FFunction select
PDPull-Down Resistor
PUPull-Up Resistor
DRVDriving control
SMTSchmitt trigger
IEInput enabled

The function select should be set to 11 (0b1011) in order to choose Software GPIO, and the pull up resistor should be enabled. This means we will have to write the value 0b0000101100100000 << 16 to the GPIO_CFGCTL5 register at address 0x40000144. The LEDs require sinking current into the GPIO pins, so it should be off until you explicitly pull it LOW. If the pulldown resistor was enabled the LED would be on by default.

Next, the 11th bit of the Output Enable register (GPIO_CFGCTL34_OFFSET) at address 0x40000190 must be set. Bit shifting 1 << 11 will work.

Finally the 11th bit of the Output register (GPIO_CFGCTL32_OFFSET) at address 0x40000188 must be set LOW.

C and assembly examples

The C code is provided for reference only and has not been tested. In C we would do:

int main() {
    int* GPIO_CFGCTL5 = 0x40000114;
    int GPIO_BITSET_CFGCTL5 = 0b0000101100100000 << 16;

    int* GPIO_CFGCTL32 = 0x40000188;
    int GPIO_BITSET_OUTPUT_ENABLE = 1 << 11;

    int* GPIO_CFGCTL34 = 0x40000190;
    int GPIO_BITSET_OUTPUT = 0 << 11;
    while (1) {
        // Trap execution to prevent weird results.

        // Continuously set GPIO status to ensure it isn't
        // quickly toggled off for some reason.

In assembly we do almost the same, except we use the GLB as a reference point and compute relative addresses using the sw instruction.

# RM = BL602 Reference Manual 1.2. See the Pine64 bl602-docs repo.

# Global Register base address. Page 23 of the RM.
.set GLB_BASE, 0x40000000
# GPIO 10 and 11 configuration. Page 36 of the RM.

# GPIO Output Register. Not described in RM.
.set GPIO_CFGCTL32_OFFSET, 0x188

# GPIO Output Enable register. Not described in RM.
.set GPIO_CFGCTL34_OFFSET, 0x190

# Bitset to set GP11FUNC to 11 (SWGPIO), Pullup to 1
# and rest to zero. Page 36 of the RM.
# GPIO11 are the 16 highest bits.
.set GPIO_BITSET_CFGCTL5, 0b0000101100010000 << 16

# Bitset for Output and Output Enable register. 11th bit = GPIO11.
# High enables the output.

# Low enables the LED because the GPIO sinks current.
.set GPIO_BITSET_OUTPUT, 0 << 11

.section .text

    # la = Load Address
    # Can be used to compute an offset from address, 
    # but not used here.
    # Load GLB_BASE pointer into 'temporary1'
    # int* t1 = (int*) GLB_BASE;
    la t1, GLB_BASE

    # Load bitset for config control 5 into 'temporary2'
    # int t2 = GPIO_BITSET_CFGCTL5;

    # sw = Store Word (32 bits)
    # Store 'temporary2' in GLB_BASE pointer, plus the
    # GPIO10/GPIO11 register offset.
    # *(t1 + GPIO_CFGCTL5_OFFSET) = t2;
    sw t2, GPIO_CFGCTL5_OFFSET(t1)

    # Load bitset for enabling GPIO11 into 'temporary2'.

    # Store 'temporary2' in GLB_BASE pointer, plus the
    # Output Enable register offset.
    # *(t1 + GPIO_CFGCTL34_OFFSET) = t2;
    sw t2, GPIO_CFGCTL34_OFFSET(t1)

    # The bitset for output enable should not be used for
    # output.

# Loop label.

    # Store 'temporary2' in GLB_BASE pointer, plus the
    # Out register offset.
    # This is the bit you would switch in order to blink
    # LED.
    # *(t1 + GPIO_CFGCTL32_OFFSET) = t2;
    sw t2, GPIO_CFGCTL32_OFFSET(t1)

    # Unconditionally Jump to loop.
    # goto loop;
    j loop

Building, linking and flashing

The general approach is to:

  1. build the assembly with as,
  2. link it with ld,
  3. convert the ELF file to binary with objcopy,
  4. flash it to the board with blflash.

The Pine64 version of the repository include the necessary riscv64-unknown-elf versions of every tool mentioned above (in toolchain/riscv/Linux/bin), except for blflash which can be found at the Github repository.

Exact commands can be found in the Makefile at the git repository.