Semihosting with VS Code on RP2040

With semihosting I can use standard I/O function like printf() and I can read and write data on the host through the debug connection. If used with care, this is a great feature especially for unit testing.

Raspberry Pi Pico-W (RP2040) board

Outline

Semihosting is a technology originally defined by ARM to allow communication between the host and the target. It is named ‘semi’ because half of the work is on the target and half of the work is on the host. Basically with semihosting the target runs into a special breakpoint, the debugger catches the breakpoint, reads the target command and executes read and write operations, depending on the command. For details see my other article: Using Semihosting the direct Way.

With semihosting I can

  • redirect standard I/O like printf() or scanf() to the host (telnet or gdb console)
  • open/read/write files on the host
  • Other operations as implemented in McuSemihost.c

In this article I show how I can use semihosting with the Raspberry Pi Pico (RP2040, for example the pico board or the pico-w board, Visual Studio Code, the Cortex-Debug extension and using a SEGGER J-Link.

Semihosting Standard I/O Library

To use semihosting, link your application with the pico_stdio_semihosting library. One way is to add it with target_link_libraries:

adding pico_stdio_semihosting library

💡 You might notice the other stdio libraries I have added above: the nice thing with the RP2040 SDK is that I can use standard I/O redirection to USB CDC, UART and Semihosting, all the same time. If I have enabled standard I/O for all of them, e.g. text with printf() is written to all enabled output channels.

The other way is to add the following to the CMakeLists.txt:

pico_enable_stdio_semihosting(${CMAKE_PROJECT_NAME} ENABLED)

Initialization

In the application initialization code, I have to init the library:

First, include the header file:

#include "pico/stdio_semihosting.h"

Then initialize the library:

stdio_semihosting_init();

Further, I can configure how CR-LF is treated:

stdio_set_translate_crlf(&stdio_semihosting, false);

Semihosting Hard Fault Handler

Semihosting works with debugger breakpoints. If no debugger is attached, the breakpoint will cause an exception and the application won’t run. For this I’m using a special hardfault handler which allows me to run the application with no active debug session. The handler is implemented in the McuLib and the module McuHardFault.

I include the header file:

#include "McuHardFault.h"

and initialize it, so it overwrites the default Hardfault handler of the SDK:

McuHardFault_Init();

With this, any semihosting exceptions will be skipped in the handler, allowing the application to run.

Cortex-Debug

The Cortex-Debug extension supports semihosting, and requires special launch settings to work. Below is my debug.json using a J-Link:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "TSM_PicoW_Blinky Cortex-Debug",
            "type": "cortex-debug",
            "request": "launch",
            "servertype": "jlink",
            "serverpath": "${env:PICO_SEGGER_PATH}/JLinkGDBServerCL.exe", // or: set in global json: cortex-debug.JLinkGDBServerPath
            "cwd": "${workspaceRoot}",
            "executable": "${command:cmake.launchTargetPath}",
            "armToolchainPath": "${env:PICO_TOOLCHAIN_PATH}",  // needed for the gdb
            "device": "RP2040_M0_0",
            "interface": "swd",
            "serialNumber": "", // add J-Link serial number if having multiple attached the same time.
            "runToEntryPoint": "main", // "_reset_handler" or for example "main"
            "postLaunchCommands": [
                "monitor semihosting enable",
                "monitor semihosting ioclient 3", // 1: telnet (port 2333); 2: gdb; 3: both telnet and gdbclient output
            ],
            "postRestartCommands": [],
            "postResetCommands": [],
             "rtos": "FreeRTOS",
            "svdFile": "${env:PICO_SDK_PATH}/src/rp2040/hardware_regs/rp2040.svd",
            "rttConfig": {
                "enabled": false,
                "address": "auto",
                "decoders": [
                    {
                        "label": "",
                        "port": 0,
                        "type": "console"
                    }
                ]
            },
            "showDevDebugOutput": "none",
        }
    ]
}

The important part is this:

"postLaunchCommands": [
                "monitor semihosting enable",
                "monitor semihosting ioclient 3", // 1: telnet (port 2333); 2: gdb; 3: both telnet and gdbclient output
            ],

This enables semihosting with the J-Link debug probe and uses telnet and gdbclient for the communication.

Debug

Time to check it: use some printf() in your code and the output will be written to the Debug Console:

Semihosting with VS Code

Summary

Semihosting is just another nice way for standard I/O communication with the target. Using the RP2040 SDK I need to link with a library and initialize it. I recommend to use the special McuHardFault handler to enable the application running without debugger too. Just keep in mind that standard I/O semihosting and semihosting in general can be intrusive. I recommend to use the McuSemihosting library which is faster and includes file I/O and other features.

Happy hosting 🙂

Links

11 thoughts on “Semihosting with VS Code on RP2040

    • Hi Liviu,
      yes, the RP2040 is a really nice MCU and platform. Compared to other devices it is rather simple (on the plus side: less complex), but very capable. Semihosting is something I usually avoid in the application because it is rather intrusive and slow (e.g. compared to RTT), but for unit testing it gives the needed flexibility and way to send and receive data to the target in an easy way.
      Interesting to see uCOS in use: I have abandoned it years back with the uCOS ownership changes and troubles. Nice OS, but pretty much obsolete with the dominance of FreeRTOS now?

      Like

  1. In the same context, to be noted that running semihosted unit tests does not require physical boards, I actually run most of my tests on QEMU emulated platforms, like Cortex-M0, Cortex-M7F, Cortex-A15, Cortex-A72, RISC-V RV32IMAC and RV64IMAFDC (for example https://github.com/micro-os-plus/utils-lists-xpack/blob/xpack/tests/platform-qemu-cortex-m7f/CMakeLists.txt), with the obvious advantage that this also works in CI environments, like GitHub Actions.

    Like

    • That approach with QEMU works fine for an RTOS testing and if you only need to core, an not much more. I might need to look at QEMU again, but I think it does not provide a FCS (full chip simulation)? I see that this should come from the silicon vendors, but I don’t see them providing it? On the other side: if the testing is on the application logic with no hardware required (or only mocks of it), QEMU is a viable solution.

      Like

      • Right, QEMU is great for unit-testing portable libraries. The challenge is to modularise a project into portable libraries, which should start at design time. Once this is done properly, and dependencies streamlined, there are many advantages, including simplified testing, which can also be done as native applications in a POSIX environment (an example from the same project is https://github.com/micro-os-plus/utils-lists-xpack/blob/xpack/tests/platform-native/CMakeLists.txt). To be noted that in all cases (native process, semihosted QEMU emulated or semihosted OpenOCD), the source code of the tests is exactly the same, the code itself is completely unaware of the environment it runs on. Big advantage!

        Like

        • Hi Liviu,
          yes, that approach works fine for independent logic or something which is not really tied to the hardware. Unfortunately this is not always the case, especially for embedded targets. Yes, some part like the ‘business logic’ can be abstracted easily, but not if it needs to deal with the real hardware.

          Like

      • Hi Erich, enjoyed your write up.

        Coming from an app-dev background I’d be curious what kind of challenges interfacing with hardware poses – and how much of the limitation is inherent vs tooling related.

        Considering a project to cut my teeth on and thought developing tooling in the area could be fun. Things like recording/playing back signals and asserting the expected outputs. Thanks!

        Like

        • Hi Mike,
          tooling is not that much of a problem. The problem are the limited communication ways of deep embedded systems compared to ‘normal’ applications on the host. For example in many cases all what you have is an UART connection (if any). So you need sophisticated ways on the target or in the target firmware to get the data in and out.

          Like

  2. Pingback: Implementing File I/O Semihosting for the RP2040 and VS Code #RP2040 #RaspberryPi @raspberry_Pi @McuOnEclipse « Adafruit Industries – Makers, hackers, artists, designers and engineers!

What do you think?

This site uses Akismet to reduce spam. Learn how your comment data is processed.