In the previous post I introduced the concept of ARM Semihosting. In this post I am going to set up a simple “Hello World” program running on the Raspberry Pi Pico 2 (RP2350) which shows how to get semihosting working to print out basic I/O output on the debugger console.

RPi Pico Development Setup

The RP2350 is a versatile dual-core MCU that can be configured to run either as ARM Cortex-M33 or Hazard3 RISC-V (for my examples I’ll only be running the ARM cores).
I am also going to be using another Raspberry Pi Pico 1 (RP2040) as a debugger (flashed with the CMSIS-DAP debugprobe image) connected as follows:

picoprobewiring.jpg

I created the following GitHub Repo where I will be pushing all of the examples and builds for this project.

In order to make the development simpler to replicate, I have included a devcontainer setup in vscode that builds the image and container necessary to develop on the RPi Pico boards. This includes the necessary SDK, dependencies, and tools to debug the code.

Pico-SDK Setup

Follow the instruction in the Pico-SDK repo to clone the RPi Pico-SDK and set your PICO_SDK_PATH environment variable to point to this location.

OpenOCD & GDB Setup

The easiest way to get OpenOCD to run with the RP2350 is to get it directly from the pico-sdk-tools repo (release page). Alternatively you can build from source (takes a while):

# Option 1 - download pre-built OpenOCD image
$ cd $PICO_SDK_PATH
$ mkdir -p openocd && cd openocd
$ wget https://github.com/raspberrypi/pico-sdk-tools/releases/download/v2.2.0-3/openocd-0.12.0+dev-x86_64-lin.tar.gz
$ tar -zxvf *.tar.gz && rm *.tar.gz

# Option 2 - build from source
$ git clone https://github.com/raspberrypi/pico-sdk-tools && cd pico-sdk-tools
$ sudo apt update && ./build_linux.sh

The GDB client I’m going to be using is gdb-multiarch (alternatively you can use arm-none-eabi-gdb) which I have installed via:

  • $ sudo apt install gdb-multiarch

Pico debugprobe Setup

The simplest way to set up the RP2040 as a CMSIS-DAP debugger probe is:

  1. Download the latest debugprobe.uf2 firmware from the debugprobe releases
  2. Hold the BOOTSEL button on the RP2040 while connecting it to your computer via USB
  3. Drag and drop the debugprobe.uf2 file to the mounted drive
  4. The RP2040 will reboot and appear as a debug probe device

Connect the debug probe to your RP2350 target:

  • GND to GND
  • GP2 (SWCLK) to SWCLK on target
  • GP3 (SWDIO) to SWDIO on target
  • GP4/GP5 for UART (optional, for serial output, which we’re not using in this example)

The probe provides both SWD debugging interface and UART passthrough.

Project Setup

For the most basic setup we’re going to need 3 files:

  1. The C source file where we are exercising the ARM Semihosting functionality.
  2. CMakeLists.txt build file.
  3. pico_sdk_import.cmake from the Pico-SDK which contains definitions needed to properly compile the project.
    1. You can copy over the pico_sdk_import.cmake file from the Pico-SDK via $ cp $PICO_SDK_PATH/external/pico_sdk_import.cmake .

Semihosting Example

C Source

The simplest “Hello World” program to test semihosting functionality would look something as follows:

#include <stdio.h>
#include "pico/stdio_semihosting.h"

int main()
{
    stdio_semihosting_init();

    printf("=== ARM Semihosting Test ===\n");
    printf("This message appears in the debugger console.\n");
    printf("Counter test: ");
    for (int i = 0; i < 5; i++)
    {
        printf("%d", i);
    }
    printf("\n");
    
    return 0;
}

CMake

The CMakeLists.txt file looks as follows:

cmake_minimum_required(VERSION 3.13...3.27)

# note: this must happen before project()
include(pico_sdk_import.cmake)

set(TARGET_NAME hello_world)
project(${TARGET_NAME} C CXX ASM)

# initialize the Raspberry Pi Pico SDK
pico_sdk_init()

add_executable(${TARGET_NAME} hello_world.c)

target_link_libraries(${TARGET_NAME} pico_stdlib pico_stdio_semihosting)

# Link with semihosting library (requires --specs=rdimon.specs)
target_link_options(${TARGET_NAME} PRIVATE --specs=rdimon.specs)

# create map/bin/hex/uf2 file in addition to ELF.
pico_add_extra_outputs(${TARGET_NAME})

Note:
We can achieve the correct linking via one of the following ways:

  1. call stdio_semihosting_init() in main() (and include "pico/stdio_semihosting.h")
  2. or, set pico_enable_stdio_semihosting(hello_world ENABLED) in the CMakeLists.txt (and then in main() we just call stdio_init_all())

Both achieve the same result.

Build

In my case I am building for a Pico 2 W (same as the Pico 2 with the addition of an Infineon Wi-Fi module), so I specify the board using the PICO_BOARD variable:

$ cmake -S . -B build -DPICO_BOARD=pico2_w
$ cmake --build build --target hello_world

Run OpenOCD

In a terminal shell we can start OpenOCD as follows:

$ pushd $PICO_SDK_PATH/openocd
$ ./openocd -c "gdb port 50000" -s scripts -f interface/cmsis-dap.cfg -f target/rp2350.cfg -c "adapter speed 5000"

Which will look for a CMSIS-DAP device to connect to, and starts up a gdbserver listening on port 50000.

Run GDB

In a separate terminal run gdb-multiarch as follows:

$ cd ./build
$ gdb-multiarch --se=hello_world.elf 
> target extended-remote localhost:50000
> monitor reset init
> monitor arm semihosting enable
> monitor arm semihosting_fileio enable
> load hello_world.elf
> tbreak main
> continue
> continue

What we are doing here is:

  • invoke gdb and tell it to load hello_world.elf as the executable and symbol file
  • connect to the remote OpenOCD gdbserver running on port 50000
  • the monitor tell OpenOCD to:
    • reset init - halt the target, and execute the reset-init script
    • arm semihosting enable - allows for code executing on an ARM target to use the I/O facilities on the host computer
    • arm semihosting_fileio enable - allow forwarding semihosting I/O to GDB process using the File-I/O remote protocol extension
  • load hello_world.elf - flash the executable onto the device
  • tbreak main - set a temporary breakpoint in main
  • continue - runs to the breakpoint, and another one to let the target run.

Output

When everything is properly configured we will see the following on the gdb console:

=== ARM Semihosting Test ===
This message appears in the debugger console.
Counter test: 01234

Showing that we’ve successfully compiled a simple program on the RP2350 that redirects printf output to the attached debugger via semihosting.