This document details useful information on how to use a Raspberry Pi Pico as a SWD probe for another Pico using the Picoprobe software. This information assumes you have followed all of the steps in Appendix A of the Getting Started with Raspberry Pi Pico Guide and that you are doing development on a Linux system.
-
Add the below files to your system to enable non-root access to the devices
/etc/udev/rules.d/99-openocd.rules:
# Raspberry Pi Picoprobe ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0004", MODE="0666"
/etc/udev/rules.d/95-pi-pico-udev.rules:
# Raspberry Pi Pico ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0003", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
-
Then run the following command:
sudo udevadm control --reload-rules && sudo udevadm trigger
-
Use terminal multiplexer of some sort (e.g.
screen
ortmux
) or just two windows to spawn two shells, one to build your code and another with GDB to step through your code.NOTE: This setup unfortunately has not worked with semihosting output for me.
-
In the GDB shell, run
gdb-multiarch
with your desired ELF executable. See section GDB Init File for example invocation and usage of init files. -
Still in GDB, run
load
followed by whatever breakpoints and other things you'd like to set before running the program. -
Finally, still in GDB, run
continue
orc
to run the program on the chip.
The following assumes that you have followed Essential Setup to enable user-land usage of the Raspberry Pi Pico both as Picoprobe and other programs and have followed the debug probe setup in Appendix A of the Getting Started with Raspberry Pi Pico Guide (or have setup similar otherwise).
When simply flashing and running a program on the Pico when attached directly to the development machine via USB (i.e. no middleman debug probe), output from the Pico can be viewed in terminal by installing minicom
and running it via:
minicom -b 115200 -D /dev/ttyACMn
, where n
is some number like 0, 1, 2, etc.
If you have more than one Pico or similar device plugged in to your development machine via USB, there will be more than one ttyACM
file.
To get program output while using a debug probe, enable Semihosting, your very slow partner in debugging crime, do the following (assuming you're using Pico-SDK's stdio):
NOTE: You must run OpenOCD separately to get output from the device. See GDB Init File for details.
-
In your project's CMake file add the following:
pico_enable_stdio_semihosting(<target_name> 1)
-
Re-run both
cmake
and your build system (probably Make, possibly Ninja). -
Assuming OpenOCD is running, start GDB with the commands in GDB Init File in addition to:
The second command disables logging to ensure semihosting output is not hidden by log output.
monitor arm semihosting enable monitor debug_level -2
-
Finally, flash the device and run your program via GDB at which point you should get output from the device in the OpenOCD shell, albeit very slowly.
To simplify debugging setup, run GDB with the --init-command
flag before you enter interactive GDB session to run desired commands. Example usage is as follows:
gdb-multiarch --init-command=/path/to/gdbinit/file /path/to/exec.elf
GDB Init File: (first line spawns OpenOCD from GDB and pipes output to GDB)
tar ext | openocd -c "gdb_port pipe" -f interface/picoprobe.cfg -f target/rp2040.cfg -s tcl
monitor reset halt
Leave running in one shell: openocd -f interface/picoprobe.cfg -f target/rp2040.cfg -s tcl
Next, in another shell: gdb-multiarch --init-command=/path/to/gdbinit/file /path/to/exec.elf
GDB Init File: (first line connects to OpenOCD TCP server)
tar ext :3333
monitor arm semihosting enable
monitor debug_level -2
monitor reset halt
Functions to check for existence of /media/RPI-RP2
and copy UF2 file to Raspberry Pi Pico for flashing via UF2 bootloader. Remove sudo
in commands if the user you're logged in as has proper permissions.
Example usage:
rpi-check && rpi-copy /path/to/exec.uf2
rpi-copy --mount-from /path/to/device/dir /path/to/exec.uf2
Functions (tested in zsh):
RPI_STANDARD_DIR=/media/alex/RPI-RP2
rpi-copy() {
# Copy executable to RaspberryPi Pico
# Verify args
if [ "$#" -ne 1 ] && [ "$#" -ne 3 ]; then
echo "usage: rpi-copy [--mount-from MOUNT] EXECUTABLE" >&2
return 1
fi
if [ "$#" -eq 1 ]; then
# Standard copy executable
#
# $1: Executable
sudo cp $1 $RPI_STANDARD_DIR
else
# Mount and then copy
# Sometimes RPI shows up in weird places
#
# $1: --mount
# $2: Mount source
# $3: Executable
RPI_MOUNT=/mnt/rpi
sudo mkdir $RPI_MOUNT
sudo mount $2 $RPI_MOUNT
sudo cp $3 $RPI_MOUNT
sudo umount $RPI_MOUNT
fi
return 0
}
rpi-check-dir() {
if [ -d $RPI_STANDARD_DIR ]; then
echo "device mounted to standard directory \"$RPI_STANDARD_DIR\""
return 0;
else
echo "rpi-check-dir: device not mounted to standard directory "\
"\"$RPI_STANDARD_DIR\"" >&2
return 1;
fi
}