ARM Semihosting (Part 2) - Example IO
Table of Contents
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:
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:
- Download the latest
debugprobe.uf2firmware from the debugprobe releases - Hold the
BOOTSELbutton on the RP2040 while connecting it to your computer via USB - Drag and drop the
debugprobe.uf2file to the mounted drive - 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:
- The C source file where we are exercising the ARM Semihosting functionality.
- CMakeLists.txt build file.
pico_sdk_import.cmakefrom the Pico-SDK which contains definitions needed to properly compile the project.- You can copy over the
pico_sdk_import.cmakefile from the Pico-SDK via$ cp $PICO_SDK_PATH/external/pico_sdk_import.cmake .
- You can copy over the
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:
- call
stdio_semihosting_init()inmain()(and include"pico/stdio_semihosting.h")- or, set
pico_enable_stdio_semihosting(hello_world ENABLED)in the CMakeLists.txt (and then inmain()we just callstdio_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.elfas the executable and symbol file - connect to the remote OpenOCD gdbserver running on port
50000 - the
monitortell OpenOCD to:reset init- halt the target, and execute the reset-init scriptarm semihosting enable- allows for code executing on an ARM target to use the I/O facilities on the host computerarm 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 devicetbreak main- set a temporary breakpoint in maincontinue- 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.