M4: the board reads the disk itself
The Pico decodes the disk now. In M3 it shipped raw flux timings over USB and a Python script on the Mac turned them into sectors. In M4 core 1 does the GCR decode on the RP2350 and hands back finished 512-byte blocks. The whole REF800K test disk came back byte-identical in 26.1 s. Same SHA-256 as the image I wrote it from.
readdisk: 26102 ms, decode 1179 ns/interval (mean), ring backlog max 37 of 8192,
0 failed track sides, 0 with soft errors
compare vs REF800K.po: 0 blocks differ []
| M3 (decode on the Mac) | M4 (decode on the RP2350) | |
|---|---|---|
| Whole disk | 2 min 12 s | 26.1 s |
| Blocks OK | 1600/1600 | 1600/1600 |
| Image SHA-256 | matches | matches (bfed064f...657742ab) |
This is Drive A only. Drive B sat this one out.
How it’s split across the two cores
Core 1 owns the mechanism. A PIO program times every flux edge in 40 ns counts and DMA drops them into a 16 KB ring that never stops. Core 1 pulls intervals out of the ring and decodes them as they arrive, so a track side never has to be buffered as raw flux. Core 0 runs the USB console and talks to core 1 through two small lock-free queues.
The retry rules come from the original drive ROM, read as a reference: 12 revolutions per sector with a recalibrate after 6, a budget of 4 wrong-track headers, and a 5 s cap. The status codes are the ROM’s too, so a missing disk comes back as AF and an unreadable sector as A7. That matters later when the Apple asks for blocks and expects those exact codes.
| Command | What comes back |
|---|---|
readblk <n> | BLK n ss ff plus 512 bytes as hex |
readdisk | every track side as binary with a CRC, then DISK END with timing stats |
Single blocks first
Blocks 0, 1, 23, 400, 900, 1300 and 1599 cover all five speed zones and both sides. Every one came back status 00, no soft errors, and matched the reference image.
The error paths behaved too. readblk 1600 is ERR bad command. Mechanism commands get ERR server on while core 1 owns the drive. With the disk ejected, readblk 0 returns BLK 0 af 00 and the mechanism doesn’t move.
Decode headroom
The mean decode cost is 1179 ns per flux interval. At the fastest flux rate an interval shows up every 2 us, so core 1 would be about 60% busy in the worst case. The DMA ring never got more than 37 intervals behind out of 8192. That’s fine for reads. M5 adds GCR encoding for writes, so I’m keeping an eye on it.
OpenOCD was leaving core 1 halted
After every flash, server on said ERR server did not start. Core 1 was parked in the boot ROM at PC 0xda. A cold power cycle fixed it, and resetting core 1 in firmware before launching it didn’t.
If core 1 on an RP2350 won’t start after flashing with OpenOCD, add -c "set USE_CORE 0" before -f target/rp2350.cfg. By default OpenOCD attaches to both cores and leaves core 1 halted under debug. With that line, server on works straight after a reset.
The disk was rubbing on the desk
Halfway through, every block came back A7 and the decoder saw no sector headers at all. The plain flux capture from M3 also got 0 intervals. I flashed the old M3 firmware to A/B it and that got nothing either, so the new code was off the hook. Voltages at the mechanism connector were normal this time.
The spinning disk was touching the desk. Once it was free, flux came back and every M4 step passed.
My guess is the mechanism mutes RD when the spindle speed is out of tolerance. That would also explain M3, when the +5V sag killed reads while status, tach and stepping kept working. It’s not confirmed. Next time RD goes quiet I’ll check tach and READY before anything else.
What the reviews caught
Each piece got a review before it went near the bench, plus one over the whole branch. The ones worth remembering:
- The decoder’s PLL could wedge outside its lock range and never recover. It’s now clamped to 0.87 to 1.19 of the nominal cell, which is the range where 1, 2 and 3 cell intervals still sort correctly.
- A stale reply from core 1, with a valid CRC, could land in the middle of the next
readdiskand corrupt it. Replies are now matched to their request. - The 5 s cap counted retries but not how long the mechanism actually spent, so a slow seek could blow through it.
- A persistent DMA overrun could loop forever.
- The decoder’s test fixtures were silently git-ignored, so the tests passed locally and would fail on a clean checkout.
Still open
- Drive B whole-disk read.
- Mount the mechanism on standoffs so the hub and disk can’t touch anything.
- Does RD really mute on a speed fault? Check
tachand READY the next time it happens. - Decode headroom once write encoding shares core 1.
- M5: write and format. First time anything drives /WRTGATE.