2023-01-27 08:59:55 +01:00
2023-01-19 15:24:55 +01:00
2023-01-24 15:12:31 +01:00
2022-12-27 12:40:19 +01:00
2022-12-29 12:02:28 +01:00
2022-08-18 16:00:28 +01:00
2023-01-27 07:08:40 +01:00
wip
2023-01-26 15:06:12 +01:00

:imagesdir: doc/png
:source-highlighter: rouge

# Yet Another Picoprobe

:toc:

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 https://github.com/raspberrypi/picoprobe[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 **Y**et **A**nother **Picoprobe** around, the YAPicoprobe.



## Features
### Plus

* **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 https://github.com/ARMmbed/DAPLink[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

### Other Benefits
* 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

### Minus
* custom Picoprobe protocol has been dropped



## Details
### Known Devices
* 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

### Code Inherited
[%header]
|===
| |

| https://github.com/raspberrypi/picoprobe[Picoprobe] | the original

| https://github.com/essele/pico_debug[pico_debug]
| another probe which gave ideas for PIO code

| https://github.com/pico-coder/sigrok-pico[sigrok-pico]
| original RP2040 based sigrok logic analyzer / oscilloscope

| https://github.com/ARMmbed/DAPLink[DAPLink]
| The SWD probe software for a lot of targets and boards

|===


### Wiring
Picoprobe documentation can be found in the https://datasheets.raspberrypi.com/pico/getting-started-with-pico.pdf[Pico Getting Started Guide].
See "Appendix A: Using Picoprobe".

image::board_schematic_bb.png[Wiring]

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.

.Pins Assignments Rasberry Pi Pico
[%header]
|===
| 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

|===

.Other Pin Assigments RP2040
[%header]
|===
| Pin | Description

| GPIO0       | spare
| GPIO7..9    | spare
| GPIO18      | spare
| GPIO19..21  | debug pins (for probe debugging)
| GPIO22      | spare
| GPIO23      | power supply control
| GPIO24      | USB sense
| GPIO25      | LED
| GPIO29/ADC3 | VSYS/3
|===


### Tool Compatibility

.Tool Compatibility
[%header]
|===
|Tool | Linux | Windows (10) | Command line

|openocd
|yes 
|yes 
|`openocd -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c "adapter speed 25000"    -c "program {firmware.elf}  verify reset; shutdown;"`

|pyocd
|yes
|no
|`pyocd flash -f 2500000 -firmware.elf`

|cp / copy
|yes
|yes
|`cp firmware.uf2 /media/picoprobe`
|===


### SWD Adapter Speed
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. `pyocd reset -f 5000000` sets SWCLK to 5MHz.
====

[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.
====


### MSC - Mass Storage Device Class
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]
====
* RP2040: flash erase takes place on a 64KByte base:  on the first write to a 64 KByte page, 
  the corresponding page is erased.  That means, that multiple UF2 images can be flashed into the 
  target as long as there is no overlapping within 64 KByte boundaries
* nRF52: whole chip is erased on first write operation of an UF2 image which means that
  only one UF2 image can be flashed
====

Because CMSIS-DAP access should be generic, flashing of other SWD compatible devices is tool dependant
(openocd/pyocd).


### RTT - Real Time Transfer
https://www.segger.com/products/debug-probes/j-link/technology/about-real-time-transfer/[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]
====
* only the devices RAM is scanned for an RTT control block, for unknown devices
  RAM in the range 0x20000000-0x2003ffff is assumed
* don't be too overwhelmed about Seggers numbers in
  the above mentioned document.  The data must still be
  transferred which is not taken into account in the diagram
  (of course the target processor has finished
  after writing the data)
* only one of CMSIS-DAP / MSC / RTT can access the
  target at the same time.  RTT is disconnected in 
  case CMSIS-DAP or MSC are claiming access
====


### sigrok - Data Collection
The probe allows data collection for a https://sigrok.org/[sigrok] compatible
environment.  Meaning the probe can act also as a logic analyzer / oscilloscope backend. 
The module is based on work taken from https://github.com/pico-coder/sigrok-pico[sigrok-pico].
This also means, that at the moment https://sigrok.org/wiki/Libsigrok[libsigrok] has to be
adopted accordingly, see https://github.com/pico-coder/sigrok-pico/blob/main/SigrokBuildNotes.md[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 https://github.com/pico-coder/sigrok-pico/blob/main/AnalyzerDetails.md[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


### LED Indications

.LED Indications
[%header]
|===
| 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)
|===


### Configuration

#### udev rules for MSC and CMSIS-DAP

/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
https://platformio.org/[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` or similar.  If this is too slow, use `pyocd reset -f 50000000`.


##### RTT
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:
[source,C]
----
...
#include <RTTStream.h>
...
RTTStream rtt;
...
rtt.println("main module");
----

* in other modules:
[source,C]
----
...
#include <RTTStream.h>
...
extern RTTStream rtt;
...
rtt.println("sub module");
----


## Optimizations

### SWD / Benchmarking
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 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(?).

.CMSIS-DAP Benchmarks
[%header]
|===
|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
|===


### SWD / PIO
Several PIO optimizations has been implemented.  Main idea of PIO control has
been taken from https://github.com/essele/pico_debug/blob/main/swd.pio[pico_debug].

To monitor the progress between the several versions,
https://sigrok.org/wiki/PulseView[PulseView] has been used. LA probe was
https://github.com/pico-coder/sigrok-pico[sigrok-pico].

#### First Version (03.01.2023 - e2b4a67)
image::Screenshot_20230103_074404.png[First Version]

#### (Currently) Final Version (06.01.2023 - 28fd8db)
image::Screenshot_20230106_153629.png[06.01.2023]

#### Explanation / Conclusion
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).


### SWDIR
Level shifter must be used to allow different voltage levels on probe and target.
There are different switching circuits out there, e.g.

* https://www.ti.com/product/TXS0108E[TXS0108E] (or TXS0102/4E) which
  allows 3.3V on probe side and up to 5V on target side for up to 8 signals
* https://www.ti.com/product/SN74LXC1T45[74LXC1T45] which allows the same voltage levels
  for a single signal (depending of type)

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.

image::Screenshot_20230124_140906.png[SWDIR]

[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.
====



## Notes
* 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 


## Misc
### Hardware
* use 2x https://www.ti.com/product/SN74LXC1T45[74LXC1T45] for the SWD IF,
  largest package: 6 pin SOT-23
* 7803 for power supply of target
* https://www.ti.com/product/SN74LVC8T245[74LVC8T245] level shifter for sigrok input,
  24 pin SOIC / _SOP_ packages are visible for soldering



### TODO / Known Bugs

* Features
** Wireless with PicoW
*** USBIP
**** https://usbip.sourceforge.net/
**** https://github.com/thevoidnn/esp8266-wifi-cmsis-dap
**** https://github.com/windowsair/wireless-esp8266-dap
*** https://arm-software.github.io/CMSIS_5/Driver/html/group__wifi__interface__gr.html[CMSIS WiFi interface]
** semihosting?
* Bugs
** check the benchmark "miracle" with the NDEBUG version 
** if `configTICK_RATE_HZ` is around 100, the SWD IF no longer works
* TODO
** voltage of SWD IF is VDD
** DAP_PACKET_SIZE: how to increase?
** description of the several COM ports
** MSD/MSC commands just like in DAPLink
* tests
** Reset line between probe and target have to be reviewed
** Win10 (tools) compatibility

Languages
C 96.4%
CMake 1.7%
Makefile 0.8%
Assembly 0.7%
C++ 0.4%