YAPicoprobe allows a Pico / RP2040 to be used as USB → SWD and UART bridge. This means it can be used as a debugger and serial console for another Pico or any other SWD compatible controller.
YAPicoprobe is a fork of the original Picoprobe and was created due to my lazyness to follow the PR discussions and delays with unknown outcome.
Another reason for this fork is that I wanted to play around with SWD, RTT, PIO etc pp, so the established development process was a little bit hindering.
Finally there is Yet Another Picoprobe around, the YAPicoprobe.
-
FAST, at least faster than most other picoprobes
-
standard debug tool connectivity
-
CMSIS-DAPv2 WinUSB (driver-less vendor-specific bulk) - CMSIS compliant debug channel
-
CMSIS-DAPv1 HID - CMSIS compliant debug channel as a fallback
-
-
MSC - drag-n-drop support of UF2 firmware image à la DAPLink for RP2040 Pico / PicoW and nRF52832/833/840 targets
-
CDC - virtual com port for logging of the target
-
UART connection between target and probe is redirected
-
RTT terminal channel is automatically redirected into this CDC (if there is no CMSIS-DAPv2/MSC connection)
-
-
CDC - virtual com port for logging of the probe
-
sigrok probe - data collection on eight digital and three analog channels (logic analyzer and oscilloscope) with auto-trigger capability
-
LED for state indication
-
on Windows no more Zadig fiddling because the underlying protocols of CMSIS-DAPv1 and v2 are driver-less
-
easy drag-n-drop (or copy) upload of a firmware image to the target via the probe
-
no more reset push button (or disconnect/connect cycle) to put the target into BOOTSEL mode
-
auto detection of RP2040/nRF52/Generic targets
-
RP2040 (and its flash "win w25q16jv"), size of the targets flash is detected but it’s not tested, if MSC flashing does work
-
nRF52832/833/840
-
other SWD compatible (Arm) devices with limitations in MSC access
the original |
|
another probe which gave ideas for PIO code |
|
original RP2040 based sigrok logic analyzer / oscilloscope |
|
The SWD probe software for a lot of targets and boards |
Picoprobe documentation can be found in the Pico Getting Started Guide. See "Appendix A: Using Picoprobe".
Wires between probe and target board are the same as before, but the target UART wires can be omitted if RTT is used for logging on the target device.
Additionally there is an additional /RESET line and the sigrok digital and analog inputs.
I recommend to use a simple Raspberry Pi Pico board as the probe. The Pico W board does not add any features, instead the LED indicator does not work there.
Pin | Description |
---|---|
GP1 |
SWDIR |
GP2 |
target SWCLK |
GP3 |
target SWDIO |
GP4 |
target UART-RX |
GP5 |
target UART-TX |
GP6 |
target /RESET (RUN) |
GP10..17 |
sigrok digital inputs |
GP26/ADC0 |
sigrok ADC0 |
GP27/ADC1 |
sigrok ADC1 |
GP28/ADC2 |
sigrok ADC2 |
Pin | Description | Pico W |
---|---|---|
GPIO0 |
spare |
|
GPIO7..9 |
spare |
|
GPIO18 |
spare |
|
GPIO19..21 |
debug pins (for probe debugging) |
|
GPIO22 |
spare |
|
GPIO23 |
power supply control |
WL_ON |
GPIO24 |
USB sense |
WL_D |
GPIO25 |
LED |
WL_CS |
GPIO29/ADC3 |
VSYS/3 |
WL_CLK |
Tool | Linux | Windows (10) | Command line |
---|---|---|---|
openocd |
yes |
yes |
|
pyocd |
yes |
no |
|
cp / copy |
yes |
yes |
|
YaPicoprobe tries to identify the connecting tool and sets some internal parameters for best performance. Those settings are:
Tool | Parameter |
---|---|
pyocd / CMSIS-DAPv2 |
DAP_PACKET_COUNT=1 |
openocd / CMSIS-DAPv2 |
DAP_PACKET_COUNT=2 |
CMSIS-DAPv1 HID |
DAP_PACKET_COUNT=1 |
The tools above allow specification of the adapter speed. This is the clock frequency between probe and target device.
Unfortunately DAP converts internally the frequency into delays which are always even multiples of clock cycles.
That means that actual clock speeds are 125MHz / (2*n)
, n>=3
→ 20833kHz, 12500kHz, 10417kHz, …
Normally the requested frequency is rounded down according to the possible values from above. But if the specified frequency is completely out of range, the allowed maximum SWD frequency of the RP2040 is used, which is 24MHz.
Actually usable frequency depends on cabling and the DAP speed. If the DAP cannot access memory with speed determined by the host, it responds with WAIT and the host needs to retry.
Effects of cabling should be clear: the longer the cables plus some more effects, the worse the signals. Which effectively means slowing down clock frequency is required to get the data transported.
Tip
|
SWCLK speed for MSC and RTT (below) is set according to the latest used tool setup.
E.g. |
Note
|
SWD clock frequency is also limited by the target controller. For nRF52 targets default clock is set to 8MHz, for unknown SWD targets 2MHz are used. |
Via MSC the so called "drag-n-drop" supported is implemented. Actually this also helps in copying a UF2 image directly into the target via command line.
MSC write access, i.e. flashing of the target, is device dependent and thus works only for a few selected
devices which are in my range of interest. Those devices are the RP2040 (and its flash "win w25q16jv") and the
Nordic nRF52 family (namely nRF52832/833/840).
For the RP2040 some special flash routines has been implemented. For nRF52 flashing
regular DAPLink modules have been taken. Which also implies, that extending the probes capabilities shouln’t be
too hard.
Note
|
|
Because CMSIS-DAP access should be generic, flashing of other SWD compatible devices is tool dependant (openocd/pyocd).
RTT allows transfer from the target to the host in "realtime". YAPicoprobe currently reads channel 0 of the targets RTT and sends it into the CDC of the target. Effectively this allows RTT debug output into a terminal.
Note
|
|
The probe allows data collection for a sigrok compatible environment. Meaning the probe can act also as a logic analyzer / oscilloscope backend. The module is based on work taken from sigrok-pico. This also means, that at the moment libsigrok has to be adopted accordingly, see here. Benefit is, that this allows the Pico as a mixed-signal device and RLE compression of the collected data.
Specification of the module is:
-
8 digital channels at GP10..GP17
-
3 analog channels at GP26..GP28 with 8bit resolution
-
internal buffer of 100KByte which allows depending on setup between 25000 and two hundred thousand samples with highest sample speed
-
digital sampling rate can be up to 100MHz for a short period of time, see here
-
analog sampling rate can be up to 500kHz with one channel
-
continuous digital sampling can be up to 10MHz depending on data stream and USB connection/load
-
auto-trigger for sampling rates ⇐ 24MHz
Drawbacks:
-
digital channel numbering in sigrok is confusing, because D2 corresponds to GP10…
-
for best performance digital channels must be assigned from GP10 consecutively
-
currently no hardware triggering supported
state | indication |
---|---|
no target found |
5Hz blinking |
DAPv1 connected |
LED on, off for 100ms once per second |
DAPv2 connected |
LED on, off for 100ms twice per second |
MSC active |
LED on, off for 100ms thrice per second |
UART data from target |
slow flashing: 300ms on, 700ms off |
target found |
LED off, flashes once per second for 20ms |
RTT control block found |
LED off, flashes twice per second for 20ms |
RTT data received |
LED off, flashes thrice per second for 20ms |
sigrok running |
10Hz flashing |
sigrok waiting for auto trigger |
10Hz negative flashing (flicker) |
Note
|
pyocd does not disconnect correctly at an end of a gdb debug session so the LED still shows a connection.
To get out of this situation issue |
/etc/udev/rules.d/90-picoprobes.rules:
# set mode to allow access for regular user
SUBSYSTEM=="usb", ATTR{idVendor}=="2e8a", ATTR{idProduct}=="000c", MODE:="0666"
# create COM port for target CDC
ACTION=="add", SUBSYSTEMS=="usb", KERNEL=="ttyACM[0-9]*", ATTRS{interface}=="YAPicoprobe CDC-UART", MODE:="0666", SYMLINK+="ttyPicoTarget"
ACTION=="add", SUBSYSTEMS=="usb", KERNEL=="ttyACM[0-9]*", ATTRS{interface}=="YAPicoprobe CDC-DEBUG", MODE:="0666", SYMLINK+="ttyPicoProbe"
ACTION=="add", SUBSYSTEMS=="usb", KERNEL=="ttyACM[0-9]*", ATTRS{interface}=="YAPicoprobe CDC-SIGROK", MODE:="0666", SYMLINK+="ttyPicoSigRok
# mount Picoprobe to /media/picoprobe
ACTION=="add", SUBSYSTEMS=="usb", SUBSYSTEM=="block", ENV{ID_FS_USAGE}=="filesystem", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="000c", RUN+="/usr/bin/logger --tag picoprobe-mount Mounting what seems to be a Raspberry Pi Picoprobe", RUN+="/usr/bin/systemd-mount --no-block --collect --fsck=0 -o uid=hardy,gid=hardy,flush $devnode /media/picoprobe"
ACTION=="remove", SUBSYSTEMS=="usb", SUBSYSTEM=="block", ENV{ID_FS_USAGE}=="filesystem", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="000c", RUN+="/usr/bin/logger --tag picoprobe-mount Unmounting what seems to be a Raspberry Pi Picoprobe", RUN+="/usr/bin/systemd-umount /media/picoprobe"
# mount RPi bootloader to /media/pico
ACTION=="add", SUBSYSTEMS=="usb", SUBSYSTEM=="block", ENV{ID_FS_USAGE}=="filesystem", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0003", RUN+="/usr/bin/logger --tag rpi-pico-mount Mounting what seems to be a Raspberry Pi Pico", RUN+="/usr/bin/systemd-mount --no-block --collect --fsck=0 -o uid=hardy,gid=hardy,flush $devnode /media/pico"
ACTION=="remove", SUBSYSTEMS=="usb", SUBSYSTEM=="block", ENV{ID_FS_USAGE}=="filesystem", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0003", RUN+="/usr/bin/logger --tag rpi-pico-mount Unmounting what seems to be a Raspberry Pi Pico", RUN+="/usr/bin/systemd-umount /media/pico"
PlatformIO configuration in platformio.ini
is pretty straight forward:
[env:pico]
framework = arduino
platform = https://github.com/maxgerhardt/platform-raspberrypi
board = rpipicow
board_build.core = earlephilhower
upload_protocol = cmsis-dap
debug_tool = cmsis-dap
monitor_speed = 115200
monitor_port = /dev/ttyPicoTarget
The firmware image can alternativly copied directly (and faster) via MSC with custom upload:
[env:pico_cp]
...
upload_protocol = custom
upload_command = cp .pio/build/pico_cp/firmware.uf2 /media/picoprobe
...
I’m sure there are smarter ways to specify the image path directly.
There is also a special PlatformIO handling in the probe: it ignores the defensive 1MHz clock setting which is used by
the above contained openocd. Standard clock is thus 15MHz. If this is too fast, set the frequency with
pyocd reset -f 1100000 -t rp2040
or similar. If this is too slow, use pyocd reset -f 50000000 -t rp2040
.
To use RTT for debug/console output the following has to be done:
-
in
platformio.ini
:
[env:pico] ... lib_deps = ... koendv/RTT Stream
-
in main.cpp:
... #include <RTTStream.h> ... RTTStream rtt; ... rtt.println("main module");
-
in other modules:
... #include <RTTStream.h> ... extern RTTStream rtt; ... rtt.println("sub module");
Benchmarking is done with an image with a size around 400KByte. Command lines are as follows:
-
cp:
time cp firmware.uf2 /media/picoprobe/
-
openocd 0.12.0-rc2 (CMSIS-DAP)v2:
time openocd -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c "adapter speed 25000" -c "program {firmware.elf} verify reset; shutdown;"
-
openocd 0.12.0-rc2 (CMSIS-DAP)v1:
time openocd -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c "cmsis_dap_backend hid; adapter speed 25000" -c "program {firmware.elf} verify reset; shutdown;"
-
pyocd 0.34.3:
time pyocd flash -f 25000000 -t rp2040 firmware.elf
, pyocd ignores silently "-O cmsis_dap.prefer_v1=true", except for the "list" option
Note that benchmarking takes place under Linux. Surprisingly openocd
and pyocd
behave differently under Windows.
DAPv2 is always used, because DAPv1 does not run under Linux(?).
command / version | cp | openocd DAPv1 | openocd DAPv2 | pyocd DAPv2 | comment |
---|---|---|---|---|---|
very early version |
- |
- |
10.4s |
- |
|
v1.00 |
6.4s |
- |
8.1s |
16.5s |
|
git-3120a90 |
5.7s |
- |
7.8s |
15.4s |
|
- same but NDEBUG - |
7.3s |
- |
9.5s |
16.6s |
a bad miracle… to make things worse, pyocd is very instable |
git-bd8c41f |
5.7s |
28.6s |
7.7s |
19.9s |
there was a python update :-/ |
git-0d6c6a8 |
5.7s |
28.5s |
6.8s |
20.2s |
|
- same but optimized for openocd |
5.7s |
28.5s |
6.1s |
- |
pyocd crashes |
git-0eba8bf |
4.9s |
28.6s |
6.5s |
13.8s |
cp shows sometimes 5.4s |
- same but optimized for openocd |
4.9s |
28.6s |
5.8s |
- |
pyocd crashes |
git-e38fa52 |
4.8s |
28.6s |
6.6s |
14.0s |
cp shows sometimes 5.4s |
- same but optimized for openocd |
4.8s |
28.6s |
5.9s |
- |
pyocd crashes |
git-28fd8db |
4.1s |
28.6s |
6.2s |
13.9s |
cp shows sometimes 4.6s, SWCLK tuned to 25MHz |
- same but optimized for openocd |
4.1s |
28.6s |
5.7s |
- |
pyocd crashes |
There is a good benchmark (actually the functional tests) in pyocd:
automated_test.py
. For configuration check https://github.com/pyocd/pyOCD/blob/main/docs/automated_tests.md
Comparing the original picoprobe firmware (running on the official Raspi Pico Debug Probe) and YAPicoprobe shows the following picture:
Probe HW | Probe FW | Target | 1.1MHz | 2MHz | 4MHz | 6MHz | 8MHz |
---|---|---|---|---|---|---|---|
Debug Probe |
Original Picoprobe (download) |
nRF52840 dongle |
586s |
583s |
580s |
581s |
(-) |
Debug Probe |
YAPicoprobe |
nRF52840 dongle |
327s |
236s |
192s |
171s (*) |
(-) |
Pi Pico W |
YAPicoprobe |
nRF52840 dongle |
332s |
234s |
192s |
177s |
(-) |
Debug Probe |
Original Picoprobe (download) |
PCA10056 |
586s |
582s |
581s |
580s |
580s |
Debug Probe |
YAPicoprobe |
PCA10056 |
322s |
233s |
189s |
183s |
244s |
Pi Pico W |
YAPicoprobe |
PCA10056 |
237s |
193s |
178s |
173s |
(*) unexpected error only observed in conjuntion with the nRF52840 dongle.
Tests were performed with the following configuration:
probes:
<probe-unid>:
frequency: [12468]000000
target_override: nrf52840
test_binary: l1_nrf52840-dk.bin
Findings are not completely clear:
-
the original probe firmware is very slow, no clue about the actual reason. Weird is that runtime with the original firmware is not frequency dependant
-
there seems to be a frequency limit of the debug probe HW at around 6MHz due to its in-series resistors (runtime increase from 6 to 8MHz)
-
the nRF52840 dongle has problems with >=6MHz as well. Either a PCB or a nRF52840 part problem. Max frequency of 4MHz should be used
-
good setups are
-
YAPicoprobe + PCA10056 @ 6MHz
-
YAPicoprobe + nRF52840 dongle @ 4MHz
-
Several PIO optimizations has been implemented. Main idea of PIO control has been taken from pico_debug.
To monitor the progress between the several versions, PulseView has been used. LA probe was sigrok-pico.
The plots above were taken at SWCLK=15MHz. Absolute time of the four command sequences shrunk from ~25us to 18us. Not bad.
Nevertheless there are still gaps which may give more optimization opportunities. Switching times between read / write and the gap between two commands are candidates. Note that moving code into RAM did not really help (and optimization is still a non/slow-working mystery).
Level shifter must be used to allow different voltage levels on probe and target. There are different switching circuits out there, e.g.
Because SWDIO is a bidirectional signal, the level shifter must switch between input and output. The TXS010xx does this automatically while the 74LXCxT45 requires an SWDIR signal to control direction.
Drawback of the automatic switching are much lower frequencies (<=24MHz) which may pass the component and the condition Vcca<=Vccb. So the TXS0108E is actually not recommended for this purpose.
For a clean implementation SWDIR has been provided to allow support of the 74LXCxT45. The following image shows the timing of SWDIR, SWCLK and SWDIO.
Note
|
For the sigrok input signals it’s also good practice to use level shifter if the target uses other voltage levels than the probe. |
-
Frequencies
-
the CPU is overclocked to 168MHz (=7*24MHz)
-
SWD frequency limit is 25MHz, actually allowed are 24MHz
-
-
sigrok
-
PIO is running 7x faster in auto trigger mode than the specified sample rate
-
-
use 2x 74LXC1T45 for the SWD IF, largest package: 6 pin SOT-23
-
7803 for power supply of target
-
74LVC8T245 level shifter for sigrok input, 24 pin SOIC / SOP packages are visible for soldering