How to debug a Raspberry Pi Pico with a Mac, SWD and… another Pico

When you’ve used Serial Wire Debug (SWD) to help you correct the C or C++ code running on your Raspberry Pi Pico, you’ll never want to go back to USB and the UF2 file system again. I don’t — no more messing about unplugging and re-plugging cables for me.

The Raspberry Pi Pico
The Raspberry Pi Pico is ready for Serial Wire Debugging

Note If you haven’t read the previous article in this series, you should check it out — it covers setting up the Pico C/C++ SDK and the ARM toolchain, both of which are pre-requisites for what follows.

SWD uses three pins: one for data, another for a clock signal and a third for ground. It’s an ARM-developed technology supported by many MCU designs based on ARM’s core architecture. Essentially, it’s used to program MCUs and to do on-chip debugging (OCD): to run the code under the control of a remote bug-hunting tool, allowing you to do really useful diagnostic stuff like pause the program mid-flow to check the state of your application.

The Pico breaks out its RP2040 chip’s SWD pins as DEBUG; just solder on some header pins. But how do you make use of it? And how do you do so a Mac?

With a great deal of difficulty, seemed the answer to the second question, at least at first. Not so, though, and we have the Raspberry Pi Foundation to thank — yet again — for that. It has produced picoprobe, a program that runs on the Pico itself and turns it into a pocket USB-to-SWD adaptor. It’s a two-Pico setup: you have one Pico on which you’ll run your code and a second one that operates as a bridge between debugger software running on your host machine and the target MCU, accessed through its SWD pins.

Yes, that means you have to sacrifice a Pico to the development process, but since it only cost you £3.60, so what? And it’s not actually lost for good. picoprobe is installed in the usual way: by mounting the host device’s storage and dragging a .UF2 file across. And if you can do it once, you can do it again with a different application when you’ve finished with picoprobe.

For me, there’s no Earthly reason not to devote a Pico to this role given the benefits: fully interactive on-chip debugging and no faffing around with USB cables.

Build Yourself a Picoprobe

So how do you set it up?

There’s some extra software required, so you’ll need to install that, plus a further Visual Studio Code extension so that you can run it all from this editor.

Here are the setup steps. As before, I’ll assume you’re storing everything in a directory whose path is ~/git and your project is called PicoTest, but you can change either or both of those values.

Build and install picoprobe

  1. Launch Terminal.
  2. cd ~/git
  3. git clone https://github.com/raspberrypi/picoprobe.git
  4. cd picoprobe
  5. mkdir build
  6. cd build
  7. cmake ..
  8. make -j4
  9. open .
  10. Mount the Pico: hold down BOOTSEL and connect to USB.
  11. Drag the file picoprobe.uf2 to the mounted Pico storage.
  12. cd ~/git
  13. rm -rf picoprobe (optional)

Build and install OpenOCD

OpenOCD is the host-side tool that enables on-chip debugging: it manages the communication between MCU and debugger in co-operation with picoprobe on the Pico.

Note 1 You may already have some of the command line tools installed with brew. Don’t worry, brew will upgrade them if necessary.

Note 2 If you’re working on an Apple Silicon Mac, change line 2 to export PATH="/opt/homebrew/opt/texinfo/bin:$PATH"

  1. brew install libtool automake texinfo wget gcc pkg-config libusb
  2. export PATH="/usr/local/opt/texinfo/bin:$PATH"
  3. git clone https://github.com/raspberrypi/openocd.git \
      --branch picoprobe --depth=1
  4. cd openocd
  5. ./bootstrap
  6. ./configure --enable-picoprobe --disable-werror
  7. make -j4
  8. make install
  9. cd ~/git
  10. rm -rf openocd (optional)

Configure Visual Studio Code

  1. Run Visual Studio Code.
  2. Click on the Extensions icon in the left side toolbar.
  3. Search for cortex-debug and install it. It’s the one by marus25. You only need install Cortex-Debug; you don’t need the device support extensions.

Configure Your Project

  1. In Terminal, cd ~/git/PicoTest
  2. Paste the following code into a new file and save it to ~/git/PicoTest/.vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    { "name": "Pico Debug",
      "device": "RP2040",
      "gdbPath": "arm-none-eabi-gdb",
      "cwd": "${workspaceRoot}",
      "executable": "${command:cmake.launchTargetPath}",
      "request": "launch",
      "type": "cortex-debug",
      "servertype": "openocd",
      "configFiles": [
        "/interface/picoprobe.cfg",
        "/target/rp2040.cfg"
      ],
      "svdFile": "${env:PICO_SDK_PATH}/src/rp2040/hardware_regs/rp2040.svd",
      "runToMain": true,
      "postRestartCommands": [
        "break main",
        "continue"
      ]
    }
  ]
 }

Wire up the Hardware

Now for the hardware. Just connect the two Picos as shown below. The one with the USB cable, which you hook up to your Mac, is the programmer. You can power the target Pico from the picoprobe unit:

Basic Picoprobe wiring

Debug Your Project

Note If you’ve already built your code once, trash the existing build folder. This will save some toolchain confusion that might impede your progress when you run the debugger. You’ll probably have to choose your compiler (the ‘kit’) again: select arm-none-eabi-gcc x.y.z.

In Visual Studio Code, open your project folder. Hit CmdShiftD or click Visual Studio Code’s Run icon in the left side toolbar. The debugger will open. It will ask you to specify your target — choose the .elf option that matches your project name.

Now click the green arrow next to Pico Debug. Your code will build, be transferred to the target and run:

Visual Studio Code's Debug starter
Click to start debugging…

The debugger will then halt at the start of your program’s main() function. You control program flow using the play/pause, step over, step in, step out, restart and play buttons right at the top of Visual Studio Code:

The debug controls
The debug controls

You can set breakpoints by clicking to the left of the line number identifying a line of code:

Set breakpoints to pause your program and inspect its state
Set breakpoints to pause your program and inspect its state

The debugger will pause execution when it hits a breakpoint and display variables and their values in the left-hand column:

The debugger will show you your variables’ values
The debugger will show you your variables’ values

Step through the code line by line to see how these values change. At a function call, you can click the Step In button to jump into that function’s code, or click Step Over to run the function and stop again at the next line.

If you make some changes to your code, click the Stop button to stop debugging, and then click the green Pico Debug arrow again to build and load the updated code, and to begin debugging again.

A full debug session in Visual Studio Code

Troubleshooting

If you see debugging errors, check your wiring. I started out getting nothing back from OpenOCD but DAP Init Failed errors. I traced it down to an incorrectly grounded SWD GND pin. You may need to try different GND pins on the picoprobe Pico if this persists.

It’s also easy to mis-select the target or the compiler.

I’ve updated my Pico repository on GitHub with the new, debugger-friendly launch.json file.

More on the Raspberry Pi Pico

7 thoughts on “How to debug a Raspberry Pi Pico with a Mac, SWD and… another Pico

  1. Leon

    You need to initialize the submodules of pico-sdk to run `make -j4`. Just run `git submodule update –init` in the sdk directory.

  2. Peter Lawrence

    Just to add to my previous comment: to adapt the above example to work with pico-debug, one needs to use the newer pico-sdk and openocd referenced in the pico-debug howto subdirectory, and then the launch.js changes like so (WP will probably lose the line feeds):

    Replace:
    “configFiles”: [
    “/interface/picoprobe.cfg”,
    “/target/rp2040.cfg”
    ],
    with:
    “configFiles”: [
    “interface/cmsis-dap.cfg”,
    “target/rp2040-core0.cfg”
    ],
    “openOCDLaunchCommands”: [
    “transport select swd”,
    “adapter speed 4000”
    ],

  3. Peter Lawrence

    Have you tried pico-debug? I wrote it to debug with just a single RP2040: https://github.com/majbthrd/pico-debug/ As long as you are not trying to debug RP2040 code that uses the USB peripheral, OpenOCD talks directly to the very RP2040 being debugged, and no extra wiring is needed. There are instructions in the howto subdirectory. Anyone doing a web search at the moment for pico debugging gets told that they have to use picoprobe and two RP2040, and I wanted to point out that this is not true. Thanks.

    1. smittytone Post author

      Interesting. I shall try it. Initially, it looks like there may be Mac installation issues — and Mac is the focus of the Pico coverage here — due to using packages available on apt but not brew, but we’ll see.

      1. Peter Lawrence

        I presume that you are talking about hidapi? The CMSIS-DAP protocol has been around since 2013, and was intended from the start to work with macOS, Linux, and Windows. I did a web search for “macos openocd cmsis-dap hid”; this link https://gist.github.com/technobly/f2c14eaa7334db80849d45614250bdf0 provides steps; it is not clear to me why they use the Dashlane fork instead of the original signal11 repo. Obviously, you don’t want to use the openocd download link in that gist; the recent openocd with Pico support pointed to in the pico-debug howto is needed.

Comments are closed.