Weather and air quality station: Part 2

Before starting with any kind of development on Raspberry Pi Pico2W I first need to setup the toolchain: SDK, compiler, linker and debugger. In this post I describe the following steps:

It’s important to note, these steps work well on my machine (x86-64, Arch Linux, btw), some slight changes are probably required if there is a need to build on other machine and/or different Linux distro. To verify I have everything I need, and everything is setup properly, I’ll develop a simple LED blinky program, and enable UART.

Build environment

To set-up my build environment for Raspberry Pi Pico2_W, i’ll combine instructions from Raspberry Pi Pico-series C/C++ SDK document and instructions from riscv-gnu-toolchain Github page.

First I’ll install all dependencies: $ sudo pacman -Syu curl python3 libmpc mpfr gmp base-devel texinfo gperf patchutils bc zlib expat libslirp

Then I want to build a cross compiler that runs on my host machine (e.g. x86-64 Linux PC) but produces executables for a different target architecture (e.g. RISC-V).

Host = where the compiler runs. Target = where the generated program runs.

There are two flavors of a cross compiler for x86-64 Linux host:

  1. Newlib cross compiler (riscv64-unknown-elf-gcc) Target: bare-metal systems (no OS, or minimal runtime). Uses Newlib as the C standard library. Good for firmware, bootloaders, small MCUs.

  2. Linux cross compiler (riscv64-unknown-linux-gnu-gcc) Target: RISC-V systems running Linux. Uses glibc (GNU C library), same as native Linux distros. Produces programs you can run under a Linux kernel on RISC-V hardware (or emulators like QEMU).

I’ll now proceed with building Newlib cross compiler and the acompanying binuitls. First I’ll create a directory where the sources and the built binaries will be stored. This directory (install path) needs to be writeable. $ mkdir -p ~/rp2350/gcc-rp2350-no-zcmp

Change into ~/rp2350 directory, clone the riscv-gnu-toolchain repository and change into the riscv-gnu-toolchain directory: $ cd ~/rp2350 $ git clone https://github.com/riscv-collab/riscv-gnu-toolchain.git $ cd risc-vgnu-toolchain.git

Configure the build process to target the RISC-V ISA extensions supported by the RP2350, and to place the built binaries into the ~/rp2350/gcc-rp2350-no-zcmp directory: $ ./configure –prefix=~/rp2350/gcc-rp2350-no-zcmp –with-arch=rv32ima_zicsr_zifencei_zba_zbb_zbs_zbkb_zca_zcb –with-abi=ilp32 –with-multilib-generator=”rv32ima_zicsr_zifencei_zba_zbb_zbs_zbkb_zca_zcb-ilp32–;rv32imac_zicsr_zifencei_zba_zbb_zbs_zbkb-ilp32–”

Build the binaries: make -j”$(nproc)”

Optional reading:

When I first built the RISC-V bare-metal toolchain, I’ve blindly copy-pasted ./configure arguments, but I wandered what do they mean? We’ll here is the explanation:

RISC-V --with-arch breakdown

--with-arch=rv32ima_zicsr_zifencei_zba_zbb_zbs_zbkb_zca_zcb

Components

RISC-V --with-abi=ilp32 breakdown

--with-abi=ilp32

What it sets

When to use

If the RP2350 had single-precision FPU, I’d use ilp32f; if it had double-precision FPU, I’d use ilp32d.

Practical effects

What --with-multilib-generator="…" does (RISC-V)

--with-multilib-generator="rv32ima_zicsr_zifencei_zba_zbb_zbs_zbkb_zca_zcb-ilp32--;rv32imac_zicsr_zifencei_zba_zbb_zbs_zbkb-ilp32--"

Purpose

--with-multilib-generator= tells the bare-metal RISC-V toolchain build exactly which multilib variants (prebuilt libgcc, newlib, etc.) to produce.
The value is a semicolon-separated list of multilib configs.
Each config has four parts:

<arch string>-<ABI>-<reuse rule with arch>-<reuse rule with sub-extension>

(Empty fields mean “no reuse rule here”. This option is supported for riscv*--elf builds.)

What your string builds

You’re asking the build to create two multilib variants:

1) rv32ima_zicsr_zifencei_zba_zbb_zbs_zbkb_zca_zcb + ilp32

2) rv32imac_zicsr_zifencei_zba_zbb_zbs_zbkb + ilp32

In short: build two library sets—one for an RV32 IMA core with Z* + Zc* compressed subsets, and one for an RV32 IMAC core with Z* (and C)—both using the ilp32 ABI.

Why/when to do this

How to use & verify

Configure (example):

./configure --prefix=/opt/riscv \
  --with-multilib-generator="rv32ima_zicsr_zifencei_zba_zbb_zbs_zbkb_zca_zcb-ilp32--;rv32imac_zicsr_zifencei_zba_zbb_zbs_zbkb-ilp32--"
make -j"$(nproc)"

See what was built:

riscv64-unknown-elf-gcc --print-multi-lib

(Shows the two multilibs you requested.)

Pick one at compile time (GCC will choose automatically if compatible, but you can be explicit):

# Matches variant (1)
-march=rv32ima_zicsr_zifencei_zba_zbb_zbs_zbkb_zca_zcb -mabi=ilp32

# Matches variant (2)
-march=rv32imac_zicsr_zifencei_zba_zbb_zbs_zbkb -mabi=ilp32

If you request an -march/-mabi combo with no compatible multilib, you can hit link/ABI errors—add another entry to the generator string or adjust your compile flags.

Reference: See the toolchain README section “Build with customized multi-lib configure” for the full format, examples, and notes on reuse rules.

export PICO_TOOLCHAIN_PATH=/ssd1/workspace/pico2w/build-tools/gcc15-rp2350-no-zcmp export PICO_PLATFORM=rp2350-riscv export PICO_SDK_PATH=/ssd1/workspace/pico2w/pico-sdk export PICO_BOARD=pico2_w

The next thing we want to do is we want to test if the board is working correctly. In the pico-sdk-examples directory there are couple of examples. I’ll use the blink example to see if I can actually program this thing and the hello_world USB example to see if UART is working correctly. Both of these things will be very useful in debugging.

#TODO:

Let’s now build our bare metal ‘Hello UART’ application. I’ve defined the following goals:

An overview how code is executed on RP2350

The RP2350 contains a (fixed) bootrom and this bootrom is executed first, not the user application. The boot outcome depends on multiple variables, all described in Chapter 5.2.1 Boot outcomes in [1]. In our case, we want to run our application from the USB bootloader. The USB bootloader will load the application stored in the UF2 [reference] format, and it will write this application to flash. But how does the bootrom know on which core do we want to run this application, RISC-V or ARM? Bootrom needs to locate and viable metadata. This metadata is defined in the Chapter 5.9 Metadata block details in [1]. The Chapter 5.9.5.2 describes Minimum RISC-V IMAGE_DEF. We will use this information