/dev/ has Debug Builds and the Debug Console (builds and the token, the console and its protocol, files and screenshots, driving the UI, crashes and Safe Mode, the command reference), Build, test and release (including how an update works), the architecture decisions and the milestone plans. Generated from the repository by site/tools/gen_dev_docs.py: the ADRs, the milestones, the README's sections, and the command reference, read from the firmware's own `help` text. The pages are committed (Zola cannot read outside its folder); the Site workflow checks they are current, and now also runs when src/main.cpp changes. M0, M1 and CONTEXT.md are not published. README: the gnss commands that the table lacked. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
3.2 KiB
+++ title = "Our own copy of the SD driver, for one missing byte" description = "Arduino-ESP32's SD library talks to the card over SPI through sd_diskio.cpp. That driver gives up on a write without saying why, and about once in 1,500 multi-block writes it gave up on one that had worked (issue #21). A 1.7 MB…" weight = 7
[extra]
docs = true
source = "docs/adr/0007-own-copy-of-the-sd-driver.md"
tag = "ADR 0007"
+++
Arduino-ESP32's SD library talks to the card over SPI through sd_diskio.cpp. That driver gives up on a write without saying why, and about once in 1,500 multi-block writes it gave up on one that had worked (issue #21). A 1.7 MB upload failed about three times in ten; before M3, the retry on top of it then filled the gap with zeros.
The cause, measured with a driver that records where it stops: after the "Stop Tran" token that ends a multi-block write, a card takes about a byte of clock to signal busy. The driver deselects, selects again, and reads one byte to see whether the card is ready. Read too early, that byte is 0xFF, "ready"; the status check (CMD13) then goes out while the card is still programming, and its answer (0xFF, 0x1F) is taken for an error. Every failure seen was this one: all blocks accepted, then a status that isn't one. ChaN's reference driver, which FatFs ships as its example, sends a dummy byte after selecting the card for this reason. Arduino's doesn't.
PlatformIO links the framework's library objects directly, so one file can't be replaced from src. A project library with the same name takes its place: lib/SD is Arduino-ESP32 3.3.12's SD library (Apache-2.0), with sd_diskio.cpp changed and the other files as they came. The changes are marked roro::
- A dummy byte after selecting the card, before the ready test, and one after Stop Tran.
- Each place a write gives up records the step and the card's answer (
sd_fault.h):infoshows the count, and the Debug Console'sputprints the detail.
Halving the SPI clock to 10 MHz didn't change the failure rate, so the card stays at 20 MHz.
Consequences
- 30 uploads of 1.7 MB in a row, each read back and compared by SHA-256, ten of them with the LoRa radio listening on the same bus: no write fault. Before: 3 failures in 10.
- Every writer gains: Logs, Tracks, Gemini pages, Saved Pages, Captures and Update Files installed from the card all go through this driver, and none of them checked.
- The copy has to follow the framework. When the platform is updated, compare
lib/SDwith the newlibraries/SDand carry theroro:changes over. If upstream fixes the ready test, drop the copy. Reported as arduino-esp32#12970; issue #39 follows it. - One more defect was read in the code and left alone, because nothing here exercises it: the driver tests the card's answer to a data block against 0x0A and 0x0C, values it can't take (accepted is 0x05, CRC error 0x0B, write error 0x0D), so a block rejected for a CRC error is never resent. No such rejection was seen in any failure. If
DataTokenfaults ever show ininfo, that's the next fix. - A fault is now counted and explained instead of silent, so the next cause, if there is one, starts with evidence.