Background
#18 was fixed in v2.1.5 by sending SD data byte-by-byte. That is correct and — on SAMD — costs nothing versus the old block call, because the core's transfer(void*, size_t) is itself a per-byte loop. But the CPU still busy-waits through every byte: at 4 MHz SPI, roughly 1 ms of blocked CPU per 512-byte sector, on every sector read or written.
Opportunity
The Seeeduino/Adafruit SAMD core provides a DMA-capable overload:
void SPIClass::transfer(const void *txbuf, void *rxbuf, size_t count, bool block);
Expected gain is not raw throughput (bounded by the SPI clock) but CPU availability during sector I/O — important for sample-while-logging workloads (exactly the accelerometer logger from #18, where a 1 ms busy-wait per sector competes with the sampling loop), plus reduced jitter.
Constraints to design around
- Platform guard — this overload only exists on the SAMD core. Use
#ifdef ARDUINO_ARCH_SAMD (or equivalent) with the byte-by-byte path as fallback on other cores.
- DMA channel exhaustion — inside the core, if the TX DMA channel allocation fails, the transfer is silently skipped. A PR needs either a documented compile-time opt-in or a runtime fallback for this case.
- Token bytes, CRC (
transfer16), and the sdReadBytes() token-wait loop must stay single-byte — only the 512-byte data payload is a candidate for DMA.
sdWriteSectors() is covered automatically (it routes through sdWriteBytes()).
Acceptance criteria for a PR
Hardware: any Wio Terminal + microSD. The reproduction firmware from #18 is a good starting workload.
help wanted — happy to review.
Background
#18 was fixed in v2.1.5 by sending SD data byte-by-byte. That is correct and — on SAMD — costs nothing versus the old block call, because the core's
transfer(void*, size_t)is itself a per-byte loop. But the CPU still busy-waits through every byte: at 4 MHz SPI, roughly 1 ms of blocked CPU per 512-byte sector, on every sector read or written.Opportunity
The Seeeduino/Adafruit SAMD core provides a DMA-capable overload:
sdWriteBytes()):transfer(buffer, nullptr, 512, true)— TX DMA only (no RX channel is allocated), source buffer is never touched, so the SD writes corrupt the filesystem: sdWriteBytes() passes the FatFs window buffer to the in-place SPIClass::transfer(void*, size_t) #18 corruption class cannot recur.sdReadBytes()):transfer(nullptr, buffer, 512, true)— the core issues dummy 0xFF bytes via TX DMA while RX DMA fills the buffer.Expected gain is not raw throughput (bounded by the SPI clock) but CPU availability during sector I/O — important for sample-while-logging workloads (exactly the accelerometer logger from #18, where a 1 ms busy-wait per sector competes with the sampling loop), plus reduced jitter.
Constraints to design around
#ifdef ARDUINO_ARCH_SAMD(or equivalent) with the byte-by-byte path as fallback on other cores.transfer16), and thesdReadBytes()token-wait loop must stay single-byte — only the 512-byte data payload is a candidate for DMA.sdWriteSectors()is covered automatically (it routes throughsdWriteBytes()).Acceptance criteria for a PR
Hardware: any Wio Terminal + microSD. The reproduction firmware from #18 is a good starting workload.
help wanted— happy to review.