Working with RP2s in upstream Pigweed#
RP2 is a family of microcontrollers (MCUs) from Raspberry Pi. The RP2040 MCU (which powers the Pico 1) and the RP2350 MCU (which powers the Pico 2) are both part of the RP2 family.
This guide shows Upstream Pigweed maintainers how to do common RP2-related development tasks in upstream Pigweed, such as building the upstream repo for the RP2350, running on-device unit tests on a Pico 2, etc.
Most maintainers should use the newer Bazel-based workflows. If you need to use the older GN-based workflows, see GN workflows (less maintained).
Target Hardware#
Target |
Supported |
|---|---|
RP2040 |
✅ Yes |
RP2350 |
✅ Yes |
Board |
Support |
|---|---|
Pico 1 |
✅ Yes |
Pico 2 |
✅ Yes |
Pico 1W |
❌ No |
Pico 2W |
❌ No |
Additional Debug Hardware#
The core Pigweed team uses PicoPico, a custom development board that makes parallel on-device testing easier.
If you don’t have access to a PicoPico board, the next best option is a Pico 2 and a Debug Probe (either the official product, or a second Pico or Pico 2 to use as one).
The debug probe also is the way to go if you are are working with a project like our Sense, where the target Pico is connected to other hardware.
Setting up a debug probe#
After setting one up, using a debug probe is the most hands-off way of interacting with the target device.
You won’t need to use the BOOTSEL button when flashing the target afterwards, as the debug probe will be able to drive the flashing process.
You will also be able to debug issues on the target with gdb and
OpenOCD
Using the official Raspberry Pi debug probe product#
The Debug Probe itself is really just a RP2040 on a small board with a few extra connectors to make wiring it to your target Pi Pico easier. You can learn more about it on the Raspberry Pi website.
Their website also has good setup instructions.
Using a second Pi Pico as a debug probe#
1. First, flash your second Pi Pico with the latest debugprobe_on_pico.uf2 from
debugprobe releases.
Connect the two Pico boards as follows:
Debug connection
2nd debug probe Pico GND ⭤ target Pico GND
2nd debug probe Pico GP2 ⭤ target Pico SWCLK
2nd debug probe Pico GP3 ⭤ target Pico SWDIO
UART connection
2nd debug probe Pico GP4/UART1 TX ⭤ target Pico GP1/UART0 RX
2nd debug probe Pico GP5/UART1 RX ⭤ target Pico GP0/UART0 TX
Tip
You can also connect the VSYS pins between the two boards to power both off of the USB connection to the debug probe Pico. Then you won’t need to power the target Pico via its USB connection.
For more detailed instructions on using a debug probe Pico, including wiring
pictures, see Appendix A: Debugprobe of the
Getting started with Raspberry Pi Pico
guide.
Additional Software#
Setting up udev rules#
On Linux, you may need to update your udev rules to access the device as a normal user (not root).
Add the following rules to /etc/udev/rules.d/49-pico.rules or
/usr/lib/udev/rules.d/49-pico.rules. Create the file if it doesn’t exist.
# RaspberryPi Debug probe: https://github.com/raspberrypi/debugprobe
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="000c", MODE:="0666"
KERNEL=="ttyACM*", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="000c", MODE:="0666"
# RaspberryPi Legacy Picoprobe (early Debug probe version)
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0004", MODE:="0666"
KERNEL=="ttyACM*", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0004", MODE:="0666"
# RP2040 Bootloader mode
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0003", MODE:="0666"
KERNEL=="ttyACM*", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0003", MODE:="0666"
# RP2040 USB Serial
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="000a", MODE:="0666"
KERNEL=="ttyACM*", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="000a", MODE:="0666"
# RP2350 Bootloader mode
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="000f", MODE:="0666"
KERNEL=="ttyACM*", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="000f", MODE:="0666"
# RP2350 USB Serial
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0009", MODE:="0666"
KERNEL=="ttyACM*", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0009", MODE:="0666"
Then reload the rules:
sudo udevadm control --reload-rules
sudo udevadm trigger
OpenOCD (The Open On-Chip-Debugger)#
OpenOCD supports a variety of embedded targets, and supports a variety of programming (flashing) and other low-level debugging operations. It also acts as a server for GDB to connect to for debugging higher-level languages like C and C++.
You can obtain OpenOCD in many ways, including your operating system package manager. Note that OpenOCD does not support the RP2350 as of the 0.12.0 release. However changes since that release have added support, and you may have to built it from source if you need it.
An alternative is to get a fork of OpenOCD from the Raspberry Pi Foundation’s GitHub repositories, which does include RP2350 support, however their fork lags behind the latest upstream sources.
Prebuilt binaries: raspberrypi/pico-sdk-tools
Source code: raspberrypi/openocd
Note
If you are building OpenOCD from it source code, you can find some notes on the thread just below.
https://forums.raspberrypi.com/viewtopic.php?p=2322149#p2322149
The cmsis-dap.cfg driver support for the Debug Probe is optional, and is
only included in the build by the configure step if your host’s equivalent
to the libusb-1.0-0-dev package is found.
The base command for using OpenOCD with a RP2x target is below, and without
any other commands (-c option) it will just act as a server for GDB to
connect to.
$ openocd -f interface/cmsis-dap.cfg \
-f target/rp2040.cfg # or rp2350 \
-c "adapter speed 5000"
Common Build notes#
MCU selection flags (--config and --chip)#
If your command requires a -config flag (e.g. --config=<mcu>)
or a --chip flag (e.g. --chip <mcu>) then you must replace
the <mcu> placeholder with one of these values:
rp2040rp2350
For example, to build upstream Pigweed for a Pico 1 you run:
$ bazelisk build --config=rp2040 //...
Whereas to build upstream Pigweed for a Pico 2 you run:
$ bazelisk build --config=rp2350 //...
Important
If the build command uses target path that begins //targets/rp2040,
then you should use for both the RP2040 and the RP2350.
Bug b/449742221 tracks the effort to clean this up, so that the target
path would become the more generic //targets/rp2.
When we originally created //targets/rp2040, the RP2350 did not yet
exist, and we didn’t know there would be a future product that was mostly
compatible, which is why the RP2350 uses the RP2040 target configuration,
but is then modified by the --config and --chip flags where needed.
If you forget, you will get a error that the target package dos not exist, which may look like:
ERROR: no such package 'targets/rp2350/py': BUILD file not found in any
of the following directories. Add a BUILD file to a directory to mark it
as a package.
Bazel workflows#
Building#
This builds everything for <mcu>
$ bazelisk build --config=<mcu> //...
Flashing#
Some modules have flash targets named “flash_<mcu>” which can be used to flash a binary to the target, using a debug-probe or picopico connected to your host via USB.
The --config does not need to be used for those.
$ bazelisk run //path/to:flash_<mcu>
For example:
$ bazelisk run //pw_system:flash_rp2350
If you need to flash in some other way, you will have to build the binary, and
flash it from the bazel-bin/ build path.
Run on-device tests#
Set up your hardware:
Connect the USB-Micro port of the DEBUG PROBE Pico to a USB port on your development host. Don’t connect the DEVICE UNDER TEST Pico to anything.
Make sure that your Debug Probe is running firmware version 2.0.1 or later. See Updating the firmware on the Debug Probe.
Wire up your Pico to the Debug Probe as described in Getting Started.
You can run Pigweed’s tests with a single Pico and no additional hardware with the following limitations:
Tests will not be parallelized if more than one Pico is attached.
If the Pico crashes during a test, the failure will cascade into subsequent tests. You’ll need to manually disconnect and re-connect the device to get it working again.
Start the test runner server:
# For the RP2040 use this: $ bazelisk run //targets/rp2040/py:unit_test_server -- --chip RP2040 # For the RP2350 use the same target, but specify "--chip RP2350" $ bazelisk run //targets/rp2040/py:unit_test_server -- --chip RP2350
# For the RP2040 use this: $ bazelisk run //targets/rp2040/py:unit_test_server -- --chip RP2040 --debug-probe-only # For the RP2350 use the same target, but specify "--chip RP2350" $ bazelisk run //targets/rp2040/py:unit_test_server -- --chip RP2350 --debug-probe-only
# For the RP2040 use this: $ bazelisk run //targets/rp2040/py:unit_test_server -- --chip RP2040 # For the RP2350 use the same target, but specify "--chip RP2350" $ bazelisk run //targets/rp2040/py:unit_test_server -- --chip RP2350
Open another terminal and run the tests:
$ bazelisk test --config=<mcu> //...
GN workflows (less maintained)#
The following guides may be outdated. We’re keeping them around for Pigweed contributors that are maintaining the upstream GN build system.
First-time setup#
To build for a RP2 target, Pigweed must be set up to use FreeRTOS and the Pico SDK HAL.
For the GN build, the supported repositories can be downloaded via
pw package, and then the build must be manually configured to point to the
locations the repositories were downloaded to.
When using Bazel, those dependencies are automatically installed.
Warning
The packages downloaded do not include libusb-1.0 as needed by
picotool. If the picotool installation fails due to missing
headers, it can be fixed by installing them manually.
$ sudo apt-get install libusb-1.0-0-dev
Note
These instructions assume a Debian/Ubuntu Linux distribution.
$ brew install libusb
$ brew install pkg-config
Note
These instructions assume a brew is installed and used for package management.
$ pw package install freertos
$ pw package install pico_sdk
$ pw package install picotool
$ gn gen out --export-compile-commands --args="
dir_pw_third_party_freertos=\"//environment/packages/freertos\"
PICO_SRC_DIR=\"//environment/packages/pico_sdk\"
"
Tip
Instead of the gn gen out with args set on the command line above you can
run:
$ gn args out
Then add the following lines to that text file:
dir_pw_third_party_freertos = getenv("PW_PACKAGE_ROOT") + "/freertos"
PICO_SRC_DIR = getenv("PW_PACKAGE_ROOT") + "/pico_sdk"
Building#
Once the Pico SDK is configured, the Pi Pico will build as part of the default GN build:
$ ninja -C out
The pw_system example is available as a separate build target:
$ ninja -C out pw_system_demo
Running unit tests#
Unlike most other targets in Pigweed, the RP2040 uses RPC-based unit testing. This makes it easier to fully automate on-device tests in a scalable and maintainable way.
Step 1: Start test server#
To allow Ninja to properly serialize tests to run on device, Ninja will send
test requests to a server running in the background. The first step is to launch
this server. By default, the script will attempt to automatically detect an
attached Pi Pico running an application with USB serial enabled or a Pi Debug
Probe, then use it for testing. To override this behavior, provide a custom
server configuration file with --server-config.
$ python -m rp2040_utils.unit_test_server --chip RP2040
Tip
If the server can’t find any attached devices, ensure your Pi Pico is already running an application that utilizes USB serial.
Warning
If you connect or disconnect any boards, you’ll need to restart the test server for hardware changes to take effect.
Step 2: Configure GN#
By default, this hardware target has incremental testing disabled. Enabling the
pw_targets_ENABLE_RP2040_TEST_RUNNER build arg tells GN to send requests to
a running rp2040_utils.unit_test_server.
$ gn args out
# Modify and save the args file to use pw_target_runner.
pw_targets_ENABLE_RP2040_TEST_RUNNER = true
Step 3: Build changes#
Now, whenever you run ninja -C out pi_pico, all tests affected by changes
since the last build will be rebuilt and then run on the attached device.
Alternatively, you may use pw watch to set up Pigweed to trigger
builds/tests whenever changes to source files are detected.
Flashing#
Using the mass-storage bootloader#
Hold down the BOOTSEL button when plugging in the Pico and it will appear
as a mass storage device. Copy the UF2 firmware image (for example,
out/rp2040.size_optimized/obj/pw_system/system_example.uf2) to
your Pico, and it will restart and run it.
Tip
This is the simplest solution if you are fine with physically interacting with your Pico whenever you want to flash a new firmware image.
Flashing with OpenOCD#
You will need (OpenOCD), and a Debug Probe (Setting up a debug probe).
On Linux you will also need to follow Setting up udev rules.
You can then flash from the command line:
$ openocd -f interface/cmsis-dap.cfg \
-f target/rp2040.cfg -c "adapter speed 5000" \
-c "program out/rp2040.size_optimized/obj/pw_system/bin/system_example.elf verify reset exit"
$ openocd -f interface/cmsis-dap.cfg \
-f target/rp2350.cfg -c "adapter speed 5000" \
-c "program out/rp2350.size_optimized/obj/pw_system/bin/system_example.elf verify reset exit"
Typical output:
xPack Open On-Chip Debugger 0.12.0+dev-01312-g18281b0c4-dirty (2023-09-05-01:33)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : Hardware thread awareness created
Info : Hardware thread awareness created
adapter speed: 5000 kHz
Info : Using CMSIS-DAPv2 interface with VID:PID=0x2e8a:0x000c, serial=415032383337300B
Info : CMSIS-DAP: SWD supported
Info : CMSIS-DAP: Atomic commands supported
Info : CMSIS-DAP: Test domain timer supported
Info : CMSIS-DAP: FW Version = 2.0.0
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : SWCLK/TCK = 0 SWDIO/TMS = 0 TDI = 0 TDO = 0 nTRST = 0 nRESET = 0
Info : CMSIS-DAP: Interface ready
Info : clock speed 5000 kHz
Info : SWD DPIDR 0x0bc12477, DLPIDR 0x00000001
Info : SWD DPIDR 0x0bc12477, DLPIDR 0x10000001
Info : [rp2040.core0] Cortex-M0+ r0p1 processor detected
Info : [rp2040.core0] target has 4 breakpoints, 2 watchpoints
Info : [rp2040.core1] Cortex-M0+ r0p1 processor detected
Info : [rp2040.core1] target has 4 breakpoints, 2 watchpoints
Info : starting gdb server for rp2040.core0 on 3333
Info : Listening on port 3333 for gdb connections
Warn : [rp2040.core1] target was in unknown state when halt was requested
[rp2040.core0] halted due to debug-request, current mode: Thread
xPSR: 0xf1000000 pc: 0x000000ee msp: 0x20041f00
[rp2040.core1] halted due to debug-request, current mode: Thread
xPSR: 0xf1000000 pc: 0x000000ee msp: 0x20041f00
** Programming Started **
Info : Found flash device 'win w25q16jv' (ID 0x001540ef)
Info : RP2040 B0 Flash Probe: 2097152 bytes @0x10000000, in 32 sectors
Info : Padding image section 1 at 0x10022918 with 232 bytes (bank write end alignment)
Warn : Adding extra erase range, 0x10022a00 .. 0x1002ffff
** Programming Finished **
** Verify Started **
** Verified OK **
** Resetting Target **
shutdown command invoked
Connect with pw_console#
Once the board has been flashed, you can connect to it and send RPC commands via the Pigweed console:
# RP2040
$ bazel run --config=rp2040 //pw_system:system_example_console
# RP2350
$ bazel run --config=rp2350 //pw_system:system_example_console
# RP2040
$ pw-system-console --device /dev/{ttyX} --baudrate 115200 \
--token-databases \
out/rp2040.size_optimized/obj/pw_system/bin/system_example.elf
# RP2350
$ pw-system-console --device /dev/{ttyX} --baudrate 115200 \
--token-databases \
out/rp2350.size_optimized/obj/pw_system/bin/system_example.elf
Replace {ttyX} with the appropriate device on your machine. On Linux
this may look like ttyACM0, and on a Mac it may look like
cu.usbmodem***. If --device is omitted the first detected port
will be used if there is only one. If multiple ports are detected an
interactive prompt will be shown.
When the console opens, try sending an Echo RPC request. You should get back the same message you sent to the device.
>>> device.rpcs.pw.rpc.EchoService.Echo(msg="Hello, Pigweed!")
(Status.OK, pw.rpc.EchoMessage(msg='Hello, Pigweed!'))
You can also try out our thread snapshot RPC service, which should return a stack usage overview of all running threads on the device in Host Logs.
>>> device.snapshot_peak_stack_usage()
Example output:
20220826 09:47:22 INF PendingRpc(channel=1, method=pw.thread.ThreadSnapshotService.GetPeakStackUsage) completed: Status.OK
20220826 09:47:22 INF Thread State
20220826 09:47:22 INF 5 threads running.
20220826 09:47:22 INF
20220826 09:47:22 INF Thread (UNKNOWN): IDLE
20220826 09:47:22 INF Est CPU usage: unknown
20220826 09:47:22 INF Stack info
20220826 09:47:22 INF Current usage: 0x20002da0 - 0x???????? (size unknown)
20220826 09:47:22 INF Est peak usage: 390 bytes, 76.77%
20220826 09:47:22 INF Stack limits: 0x20002da0 - 0x20002ba4 (508 bytes)
20220826 09:47:22 INF
20220826 09:47:22 INF ...
You are now up and running!
See also
The pw_console User Guide for more info on using the pw_console UI.
Debugging with GDB#
To interactively debug a Pico, first ensure you have OpenOCD.
In one terminal window, start OpenOCD as a GDB server with the following command:
$ openocd -f interface/cmsis-dap.cfg \
-f target/rp2040.cfg -c "adapter speed 5000"
$ openocd -f interface/cmsis-dap.cfg \
-f target/rp2350.cfg -c "adapter speed 5000"
Note
If you need to flash the target through some other software (such as a Bazel
build), you will need to stop openocd as it will have exclusive access
to the target.
In a second terminal window, connect to the open GDB server, passing the binary you will be debugging:
$ arm-none-eabi-gdb -ex "target extended-remote :3333" \
out/rp2040.size_optimized/obj/pw_system/bin/system_example.elf
$ arm-none-eabi-gdb -ex "target extended-remote :3333" \
out/rp2350.size_optimized/obj/pw_system/bin/system_example.elf
Tip
You can pass in additional commands to run on the command line with
additional -ex flags. Or you can stick multiple commands in a file and
use -x <path> to execute them all.
Some useful commands to add to startup:
-ex monitor reset halt— Resets and halt after connecting-ex monitor cortex_m vector_catch all— Breaks on hardware exceptions, such as bus errors (bad memory access) and dividing by zero, as that does not otherwise happen by default!
Note: both the RP2040 (ARM Cortex-M0+) and RP2350 (ARM Cortex-M33) are compatible with the cortex_m commands like the last one.
Quick GDB cheat-sheet#
Action |
shortcut / command |
|---|---|
Ask openocd to reset the running device, and breaking before running |
|
Execute an arbitrary OpenOCD command in gdb |
|
Break while running |
Ctrl + C |
Continue execution until pause or breakpoint |
|
Set a breakpoint |
|
Set a watchpoint (break on write) |
|
Show backtrace |
|
Switch context to backtrace frame N |
|
Print a value |
|
Examine memory |
|
Examine 64 bytes of memory as hex |
|
Examine 64 bytes of memory as decimal 4-byte words |
|
See also the GDB Users Manual