Skip to content
Eliott65wiki
Contents

The bench tools

Three wires on the SWD connector, and the PC can see inside the running RP2040. Almost all of the project’s tools go through it, right down to typing on the keyboard or copying files to the USB stick.

On this page

The probe

The bench uses a CMSIS-DAP probe connected to the board’s SWD1 connector: a Raspberry Pi Debugprobe over USB, or, since v0.59, Eliott65Sonde, a wireless probe written by the project, which goes over Wi-Fi; it has its own wiki. A single variable, E65_SONDE, sends every tool through it; with the current wiring, flashing is done at 200 kHz (E65_SWD_KHZ=200). SWD is the debug port of ARM processors: it can read and write the RP2040’s memory, and control its two cores, while the firmware is running.

The PC-side software is OpenOCD, in its development version: version 0.12.0, as shipped by distributions, does not recognise the board’s Puya flash.

Flashing without being overwritten

One day, flashing started to fail (“double fault”, “failed to exit flash XIP mode”). The cause was a delightful one: OpenOCD runs its flash-writing program in RAM, by default at address 0x20010000… which is the 6502’s memory. With the DMA bus still running, the 6502 was incrementing a counter in zero page and overwriting OpenOCD’s program.

The flashing script now places this work area elsewhere, in unused RAM, and fails loudly if verification does not pass.

The main tools

ToolWhat it does
flasher.shflashes the firmware over SWD, with verification
sonde.pyreads the kernel state and the 6502’s memory, and measures its frequency
verifier_carte.py, run_tests.sh --cartefull acceptance test on the board
ecran.pyrebuilds the expected image and decodes the video buffers actually sent out
usb.pydisplays the USB driver log and the keyboard state
appel.pymakes system calls from the PC
copier_sur_cle.py, verifier_cle.pycopy files to the USB stick, then verify, without removing it
taper.py, touches.pytype on the 6502’s keyboard, control keys included
redemarrer_carte.shrestarts the board through the watchdog

Calling the kernel from the PC

appel.py uses the most useful property of the board’s 6502: it can be frozen. The tool stops the bus PIO state machine (the 6502, a static core, waits without complaint), places a parameter block, triggers the gate, waits for the kernel, then restores everything and restarts the 6502. It notices nothing.

This is how the first tests of writing to the USB stick were done, before any 6502 program even knew how to write a file.

Copying to the USB stick without unplugging it

At the user’s request, programs are copied to the USB stick through the probe, without removing it from the board. The tool freezes the 6502, goes through the kernel’s STOCKAGE calls, and uses a cautious FAT writer: it backs up each block before modifying it, refuses duplicates, updates both copies of the FAT and reads back every block written. A read-only “dry run” always precedes the real copy.

To make all this fast, a persistent OpenOCD session is driven through its command port: an operation takes a few milliseconds instead of about half a second.

Typing without touching the board

taper.py places characters as a real keyboard would. For control keys (arrows, Esc, and kernel shortcuts such as Ctrl+Tab), touches.py goes one level lower: it places a complete HID report in a firmware mailbox, processed exactly like a report from the real keyboard. The idea comes from the Trinity project, which drives its keyboard in the same way.

The probe’s pitfalls

  • A restart through the watchdog did nothing without a register that the SDK writes before triggering it: earlier “restarts” had in fact been done by flashing.
  • When OpenOCD leaves a core halted, the RP2040’s timer pauses, and the kernel’s waits never end. The firmware disables this pause.
  • The SWD link proved unstable on several occasions (“Failed to connect multidrop”); at times it took a physical intervention on the board. Rule adopted: check the link with several reads before writing to the USB stick, and retry a single read that fails.

Sources in the repository : docs/journal/02-outils.md, docs/journal/10-fichiers.md, docs/journal/11-multitache.md, README.md.