Wolfmans Howlings

A programmers Blog about Programming solutions and a few other issues

Bare metal gpio twiddling for RISC-V on rpi pico2

Posted by Jim Morris on Sun Mar 23 08:16:32 -0700 2025

My little project this week was to try to a get bare metal RISC-V assembler coded GPIO twiddler working on a pi pico2. (Actually PICO2-W)

With little to no useful help from chatgpt 4o, I finally managed to get something working although it appears a regular daplink probe is a little flaky with the pico and openocd, maybe a pi pico programmed with daplink would be better?

It turns out the pico2 needs a little more than the pico to get a simple gpio to work, so reading the manual was required.

I'm not sure what advantage using the RISC-V in the pico is over the arm, it is certainly less well documented and supported, but it did eventually work, and the reduced instruction set is certainly easier to remember than arm assembly code.

I'm not sure I'll bother with RISC-V for the moment though as it is fiddly to get working.

For anyone trying to get this working (and googling did not yield much) here are some pointers...

I had to fetch the RISC-V toolchain from here... https://embecosm.com/downloads/tool-chain-downloads/#core-v-top-of-tree-compilers which is the recommended one.

The build parameters are...

riscv32-corev-elf-as -g -march=rv32imac -mabi=ilp32 -o startup.o startup.s
riscv32-corev-elf-ld -g -m elf32lriscv -T linker.ld -o blink.elf startup.o
riscv32-corev-elf-objcopy -O binary blink.elf blink.bin
riscv32-corev-elf-objdump -f blink.elf

The linker.ld I used was...

    OUTPUT_ARCH(riscv)
    ENTRY(_start)

    MEMORY {
        FLASH (rx)  : ORIGIN = 0x10000000, LENGTH = 2M
        RAM   (rwx) : ORIGIN = 0x20000000, LENGTH = 256K
    }

    SECTIONS {
        .text : {
            *(.text*)
        } > FLASH

        .data : {
            *(.data*)
        } > RAM

        .bss : {
            *(.bss*)
        } > RAM

        .stack (NOLOAD) : {
            . = ALIGN(8);
            _stack_top = . + 4K; /* 4KB stack */
        } > RAM
    }

The pico2 refence guide is quite good although somewhat hard to find details although they are there scattered around. Also the current version of pico-sdk does support RISC-V but at the c level not assembler.

You need to add a secret block of numbers to your code to get the bootloader to recognize a RISC-V program and get it to switch to the riscv cores properly (well not so secret, it is documented although poorly).

The block I used was:

.word 0xffffded3
.word 0x11010142
.word 0x00000344
.word _start
.word _stack_top
.word 0x000004ff
.word 0x00000000
.word 0xab123579

This sets it to a RISC-V executable and tells it where to start the execution from.

You need to clear a bit in the PAD_ISO_REG to get GPIO to work in addition to the other normal settings you would use on a regualr pico, oh also you need to set the IOMUX_BASE to function 5 for a regular gpio pin (offset for the actual GPIO you are using), again not needed in a pico.

Here is the full assembly code for toggling GPIO15 on a pipco2 W (I used GPIO15 becuase the PICO2-W does not have the led on a regular GPIO pin)

.section .text
.global _start

.equ SYSCTL_BASE,    0x40000000
.equ CLK_EN_REG,     SYSCTL_BASE + 0x100   # Clock enable register

.equ PAD_ISO_REG,    0x40038000 + 0x40   # Pad isolation control register for GPIO15

.equ IOMUX_BASE,     0x40028000
.equ IOMUX_GPIO15,   IOMUX_BASE + 0x7C     # IOMUX register for GPIO15

.equ SIO_BASE,       0xD0000000
.equ GPIO_OUT_REG,   SIO_BASE + 0x10       # GPIO output register
.equ GPIO_OUT_SET,   SIO_BASE + 0x14       # GPIO output set register
.equ GPIO_OUT_CLR,   SIO_BASE + 0x18       # GPIO output clear register
.equ GPIO_DIR_REG,   SIO_BASE + 0x30       # ✅ **Corrected: GPIO direction register**

.equ GPIO15_MASK,    (1 << 15)             # Bitmask for GPIO15

_start:
    la sp, _stack_top   # Load stack pointer
    call main           # Call main
    wfi                 # Wait for interrupt (to save power)
    j _start            # Loop forever (should never reach here)

# -----------------------------------------------------------------------------
.p2align 8 # This special signature must appear within the first 4 kb of
image_def: # the memory image to be recognised as a valid RISC-V binary.
# -----------------------------------------------------------------------------

.word 0xffffded3
.word 0x11010142
.word 0x00000344
.word _start
.word _stack_top
.word 0x000004ff
.word 0x00000000
.word 0xab123579

.section .text
.global main

main:
    # 1 Enable clock for GPIO peripheral (NOT NEEDED)
    ; li t0, CLK_EN_REG
    ; lw t1, 0(t0)         # Read current clock enable register
    ; li t2, 0x00000020    # Enable GPIO clock (check RP2350 datasheet)
    ; or t1, t1, t2
    ; sw t1, 0(t0)         # Write back to enable GPIO clock

    # 2 Configure IOMUX for GPIO15
    li t0, IOMUX_GPIO15
    lw t1, 0(t0)
    li t2, ~0x1F
    and t1, t1, t2   # clear it first
    ori t1, t1, 5        # Function 5 selects GPIO mode
    sw t1, 0(t0)         # Set IOMUX for GPIO15

    # 3 Set GPIO15 as an output
    li t0, GPIO_DIR_REG
    lw t1, 0(t0)         # Read current GPIO direction register
    li t2, GPIO15_MASK
    or t1, t1, t2
    sw t1, 0(t0)         # Set GPIO15 as output

    # 4 Clear Pad Isolation for GPIO
    li t0, PAD_ISO_REG
    lw t1, 0(t0)
    li t2, ~0x100
    and t1, t1, t2  # Clear GPIO15 isolation bit
    sw t1, 0(t0)

loop:
    # 5 Turn LED ON (Use SIO fast register access)
    li t0, GPIO_OUT_REG
    lw t1, 0(t0)        # Read current GPIO_OUT
    li t2, GPIO15_MASK
    or t1, t1, t2
    sw t1, 0(t0)        # Set GPIO15 high

    call delay

    # 6 Turn LED OFF
    li t0, GPIO_OUT_REG
    lw t1, 0(t0)
    li t2, ~GPIO15_MASK
    and t1, t1, t2
    sw t1, 0(t0)        # Set GPIO15 low

    call delay
    j loop

delay:
    li t0, 500000        # Simple delay loop
1:
    addi t0, t0, -1
    bnez t0, 1b
    ret

FWIW I did ask chatgpt to write it, but it got 80% of it wrong :) this is the code it gave me that I had to mostly correct. It got most of the register addresses wrong, and missed many of the steps until I corrected it, it also got some of the assembly wrong, and totally missed the chunk of data needed for the bootloader to work. Anyway it kept me busy for a while, and was an interesting exercise :)

Posted in pipico,assembler,RISC-V  |  Tags risc-v,pico2  |  no comments

Comments

(leave email »)