Skip to content

Latest commit

 

History

History
563 lines (383 loc) · 36.3 KB

File metadata and controls

563 lines (383 loc) · 36.3 KB

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[1.8.0] - unreleased

A card that stopped responding after a reset now starts normally. Cards that sat at the edge of the driver's read timing carry full margin, both SD sockets on a board behave alike, and high-speed mode can be left without stranding the card.

Bug Fixes

  • Compacting a file no longer damages an unrelated file. compactFile() looks for a run of free clusters to move a file into, and that search read the first 512 entries of the allocation table from stale memory instead of from the card. On a card whose clusters are small enough that files span several of them — which is typical of cards up to a few gigabytes — it could therefore treat clusters belonging to another file as free, move data across them, and leave that other file pointing at space the card considers unused. The file's own directory entry still looked correct, so the damage was silent until the volume was checked, and the same fault made the free-space total reported at unmount unreliable. Cards formatted with large clusters were unaffected, because a file that fits in one cluster is never compacted.

  • A card that fails high-speed verification now reports why. attemptHighSpeed() documents that ERROR() distinguishes a card that declined the feature from one that could not be asked or failed verification. One path did not follow it: when the card switched to high speed but the verification read came back with the wrong data, the driver fell back correctly but reported no error — so a card whose link corrupts data at high speed looked exactly like a card that simply does not offer the feature. ERROR() now returns E_IO_ERROR on that path, as it already did for every other high-speed failure.

  • A card can be left unusable by the boot process, and now recovers on its own. On boards where the microSD socket shares pins with the boot flash — including the P2 Edge — the boot sequence drives those pins before any user code runs, and the two roles do not line up: the pin the card uses for chip-select is the flash clock. A card that was in use before the reset can be left mid-transfer by this, after which it is streaming rather than listening, and mount() reports E_NO_CARD on a card that is present and healthy. Only removing power cleared it.

    The driver now issues a stop-transmission command at the start of card initialisation, which returns such a card to a state where it accepts commands. This runs on every start, costs one command, and does nothing on a card that is already idle.

    The symptom was intermittent and long-standing: it required a card that had been used before the reset, and it appeared most readily on marginal or counterfeit cards. If you have seen a card work from a cold power-up and fail after re-downloading a program, this was why.

Changes

  • Streamer read alignment moved to the centre of its measured passing band. The phase pad the read path uses to sample MISO previously sat at the band's lower edge, which is where cards with slow clock-to-output timing, and sockets with longer wiring, ran out of margin first. Cards that were marginal on an external adapter socket, or at elevated clocks, now sit in the middle of the window on both sockets.

  • setSPISpeed() is bounded at the card's declared maximum, itself capped at the SD SPI-mode 25 MHz, and lifted to 50 MHz only while verified high-speed mode is active. Requests above the bound return the clamped frequency rather than the request. Above-spec operation was measured producing silent whole-sector write corruption on some cards, and is now reserved for characterization tools via debugSetOverspeedAllowed() under SD_INCLUDE_DEBUG.

Bug Fixes

  • Leaving high-speed mode no longer strands the card. The CMD6 high-speed switch is sticky at the card: a host that lowered its clock without switching the card back left the two running on different timings, and every subsequent operation failed with E_IO_ERROR until the card was re-initialised. All four ways out of high-speed mode — a failed switch, a failed verification, a later setSPISpeed(), and unmount() — now switch the card back.

  • unmount() returns the card in default speed mode, rather than handing the next user a card in a mode they never asked for.

Breaking Changes

  • isHighSpeedActive() reports the card's mode, not a clock threshold. It previously answered TRUE only at 50 MHz or above. The SPI clock is sysclk / (2 x hp) with integer hp, so most system clocks cannot produce exactly 50 MHz — at 350 MHz sysclk high-speed mode runs at 43.75 MHz, and the old test answered FALSE while the mode was genuinely active. Callers that used it as a proxy for "is the clock fast" should read getSPIFrequency() instead.

New Features

  • The driver can now report its own version. driverVersion() returns the major, minor and patch numbers; driverVersionString() returns them as printable text. Both are available in every build, including a core-only one with no feature flags set. Performance figures depend on the driver that produced them as much as on the card, so a measurement or a log can now name the driver it came from.

  • debugSetOverspeedAllowed() and debugSetAlignFloorRuleEnabled() (SD_INCLUDE_DEBUG) lift the two production guards described above for characterization work. Documented in DOCs/SPI-PHASE-MARGIN-API.md. Production applications should not call either.

Documentation

  • SD-CARD-PERFORMANCE.md section 7 stated that CMD6 High Speed mode "fails on all tested cards". That was wrong. Of four modern cards retested, three negotiate high speed and hold it at 43.75 MHz. Sequential reads gain up to 47%. Write behaviour at that clock varies by card and is under investigation, so the driver still does not negotiate high speed on its own — attemptHighSpeed() remains opt-in.

1.7.0 - 2026-08-14

Data-losing file operations are fixed, failures the driver detected but discarded are now reported, and files a PC wrote behave correctly.

New Features

  • handleError(handle) reports why the last readHandle() or writeHandle() on a handle came up short. Both return a byte count, and a partial count is positive, so the read loop the tutorial shows ends the same way at end of file and on a card failure part-way through a file.
  • The write path reports E_NO_CONTIGUOUS_SPACE when a pre-allocated contiguous reservation runs out, and E_BAD_CHAIN when a cluster chain walk lands in the metadata region. Both were previously reported as a zero-byte write.
  • lastFlushError() and clearFlushError() report failures of the automatic idle flush. That flush is started by the worker cog, so a failure had no caller to return to: the data did not reach the card and every later call still reported success.
  • SD_INCLUDE_TEST_HOOKS builds in fault injection — setTestFailSector(), setTestFailWriteAfter(), getTestWriteCallCount(), setTestMaxClusters(), clearTestErrors(). Enabled by SD_INCLUDE_ALL, and deliberately not by SD_INCLUDE_DEBUG.
  • debugSetTxAlignDelay() and debugGetTxAlignDelay() measure write timing margin on an unfamiliar board or card socket. Diagnostic only, gated behind SD_INCLUDE_DEBUG; the shipped default needs no adjustment. Documented in DOCs/SPI-PHASE-MARGIN-API.md.
  • DOCs/ERROR-HANDLING-GUIDE.md covers detecting and responding to every error the driver reports. DOCs/MIGRATION-GUIDE-v1.7.0.md covers moving from v1.6.x.

Bug Fixes

  • Renaming a file to a full 12-character 8.3 name (DATALOG1.TXT) keeps the file usable. The last character of the extension was written over the directory entry's attribute byte, which set the directory and system bits; the file then could not be opened and no longer appeared in listings. Names of 11 characters or fewer were unaffected.
  • File operations no longer match the volume-label entry. rename() on a dot-less label's name silently relabelled the volume; creating that name failed E_FILE_EXISTS, and opening it returned an empty file.
  • Seeking a write handle to another sector, and reading through a handle that has unwritten data, both flush that data first. Either one previously discarded it, and after a seek every later flush from that handle was addressed to a sector that does not exist.
  • An unreadable directory is no longer indistinguishable from a name that is not there, so a create no longer writes a second entry for a file that already exists.
  • A metadata write that fails part-way no longer leaves the filesystem in a state that reports success. Cluster allocation, delete, directory creation, rename and move each check every write and leave the recoverable outcome on the card.
  • Appending to an empty file created by a PC works; the first write previously failed E_BAD_CHAIN and the file could never be appended.
  • createFileContiguous() on a name that already exists leaves free space unchanged; the rejected call previously stranded its pre-allocated clusters until an fsck.
  • changeDirectory() onto an empty file returns E_NOT_A_DIR instead of succeeding and silently changing to the root directory.
  • FAT entries carrying the reserved high bits — legal on volumes written by other systems — no longer send cluster-chain walks and free-space counts to wrong results.
  • Multi-block writes and block erases invalidate the cached copies of the sectors they change, as single-sector writes already did. A later read could return the pre-write contents.
  • A read that fails part-way no longer leaves a valid cache entry over the incomplete data, and reading past the end of a cluster chain no longer leaves the previous sector's identity attached to a zero-filled buffer.
  • unmount() clears the cached sectors and cancels the background flush timer. A bare unmount() was followed about 200 ms later by the worker reading and writing FSInfo on a card the caller had finished with.
  • Closing a file whose final flush failed no longer records the new size and timestamp in the directory. The entry advertised bytes that never reached the card.
  • mount() on a corrupted card whose boot record claims zero sectors per cluster fails with E_NOT_FAT32 instead of hanging the worker cog.
  • writeSectorsRaw() stores the bytes you gave it. A timing fault in the raw multi-block write path could put the outgoing data one bit out of phase with the clock, so the card stored a corrupted sector and reported success. File operations were never affected — they use a different path. See the upgrade note below.
  • error() describes the operation that just completed rather than the last failure since boot. Every method that issues a command records its outcome on both exit paths; the pure accessors are exempt so a diagnostic call cannot erase the error being diagnosed.
  • sync() reports failure, and keeps the pending directory entry so a later sync can still write it. It previously discarded the entry whether or not it reached the card.
  • freeSpace() returns 0 with an error rather than the partial count from an interrupted FAT scan.
  • stop() returns the status of its final unmount. It halts the worker cog immediately afterward, so nothing else could report it.
  • A worker-cog stack-guard violation reaches error() instead of only the debug channel, which the driver ships with disabled.
  • getResult() and cancelAsync() refuse a cog that did not start the operation. They release the API lock as their last act, so such a call released a lock the caller never held.
  • The card registers, high-speed negotiation and CMD6 paths report the specific failure instead of a single false. A card that lacks high-speed support is reported as lacking it, not as a failed query.
  • A create that grows a directory reports the actual failure; E_IO_ERROR from a failed FAT write is no longer reported as E_DISK_FULL.
  • A blocking call from the cog that owns an in-flight async operation returns E_ASYNC_BUSY — from every API, info getters included — instead of deadlocking; a losing concurrent start*() gets the same answer promptly instead of blocking for the winner's operation.
  • getResult() and cancelAsync() report E_STACK_OVERFLOW when the worker's stack guard was violated during the operation, as blocking calls already do.
  • setDate() rejects every out-of-range date or time field with E_INVALID_PARAM; negative values were previously accepted.
  • attemptHighSpeed() detects a failed 50 MHz switch instead of reporting success. Its previous check compared the card against itself and could not fail. Nothing is written to the card at a speed that has not been verified.
  • SD_FAT32_fsck recovers directory entries stranded past a spurious end-of-directory marker, rewriting the marker as a deleted-entry tombstone so a conforming scan reaches them. It previously freed their cluster chains and left the entries in place pointing at them.
  • SD_FAT32_audit and SD_FAT32_fsck report a card they cannot read instead of reporting CLEAN. A failed read previously went unnoticed and the stale buffer contents were validated as if they were real directory or FAT data.
  • SD_card_identify names Gigastone OEM cards (manufacturer ID $12) instead of reporting Unknown. SD_card_characterize already named them.
  • The read/write example no longer null-terminates its buffer at a negative index when a read fails. The demo shell reports a read that failed part-way through type and hexdump instead of printing a byte count as if the file were complete, reports a copy whose writes were refused or short instead of reporting the source size as copied, and distinguishes a card with no free space from a free-space query that failed.

Upgrade / recovery note

Ordinary file operations were not affected. The write-path fault above could only be reached through writeSectorsRaw(), the raw multi-block sector API available under SD_INCLUDE_RAW. Everything the filesystem does — reading and writing files, directories, timestamps — goes through a different path that was tested clean on the affected releases.

If your application never calls writeSectorsRaw(), there is nothing to check.

If it does, and the data matters: open a file and look at it. The corruption is not subtle — it makes text unreadable and binary structures obviously malformed. Rewriting an affected file with a v1.7.0 build is enough; the card does not need reformatting. Note that SD_FAT32_audit and SD_FAT32_fsck cannot find this, and a CLEAN result from either says nothing either way — the filesystem structures stay intact and only the contents of data sectors would be wrong.

Reads were never affected.

Breaking Changes

Five changes can affect existing code. Only the first can break a program that currently works — the rest surface as a different error code than before.

If your code… …then
ends a read loop only on n == 0 it will not terminate on a persistent read failure. Change to n =< 0.
tests eofHandle() or isFileContiguous() for a negative value remove the test; both return a boolean now
branches on E_NOT_A_DIR to mean "no such directory" test E_FILE_NOT_FOUND instead
deletes a directory without emptying it, or a file with a handle still open close/empty first; the delete now refuses
matches unmount()'s exact error code on a corrupt card it is now E_BAD_FSINFO (-24)

Nothing else in this release requires a source change.

  • BREAKING: readHandle() and writeHandle() return their error code when nothing at all was transferred, so 0 from readHandle() now means end of file and nothing else. A read that failed on its first sector previously returned 0, and a file on a failing card was processed as a complete file. A partial transfer still returns the partial count.
  • BREAKING: eofHandle() and isFileContiguous() return a boolean only. They previously returned TRUE, FALSE, or a negative error code — and TRUE is -1 in Spin2 while E_TIMEOUT is also -1, so there was no correct way to call either. On a failed query eofHandle() reports TRUE and isFileContiguous() reports FALSE; error() distinguishes.
  • BREAKING: changeDirectory() on a name with no entry returns E_FILE_NOT_FOUND (-40). E_NOT_A_DIR (-43) now means the name exists and is not a directory; the two conditions previously shared it.
  • BREAKING: deleteFile() refuses two cases it used to accept, and deletes nothing in either. A directory that still contains entries returns E_DIR_NOT_EMPTY (-44) — it previously freed only the directory's own chain, leaving every file inside allocated and unreachable until reformat. A file that any handle still has open returns E_FILE_OPEN (-47) — the delete previously succeeded, after which the background flush could write that handle's data into a cluster already given to another file. Empty directories before removing them, children before parents; there is no recursive delete.
  • BREAKING: unmount() on a card with invalid FSInfo signatures reports E_BAD_FSINFO (-24) rather than E_IO_ERROR.

Additive, listed here because they belong with the above:

  • New error constants: E_BAD_FSINFO (-24), E_BAD_CHAIN (-25), E_STACK_OVERFLOW (-26), E_DIR_NOT_EMPTY (-44), E_FILE_OPEN (-47), E_NO_COG (-65). E_NO_COG replaces E_NO_LOCK when start() cannot get a cog. E_FILE_NOT_OPEN (-45) is documented as reserved; no code path produces it.
  • stop() and closeDirectoryHandle() return a status where they previously returned nothing. Existing calls compile and behave as before.

1.6.1 - 2026-07-27

Utility output and build instructions, and one debug method renamed to match what it does.

Bug Fixes

  • SD_format_card, SD_FAT32_audit and SD_FAT32_fsck build with pnut-ts -d -I .., the command their documentation gives. The other three utilities were unaffected.
  • SD_card_identify and SD_card_characterize print the manufacturing date as a date — 2021/09, 2021-09 — in a fixed-width field that sorts positionally.
  • SD_FAT32_audit reports each finding as needs repair: and states that the card was not modified. SD_FAT32_fsck reports the same findings as repaired:.
  • SD_format_card prints its closing success or failure line in full.
  • Error messages in the audit, format and benchmark utilities name the operation that failed.

Breaking Changes

  • BREAKING: debugClearRootDir() is renamed debugZeroRootSector() (SD_INCLUDE_DEBUG). It zeroes the first root-directory sector and frees no cluster chains, so entries in later sectors become unreachable and their clusters stay allocated. Treat the card as scratch afterwards; SD_format_card returns it to a clean state.

Known Issues

  • Maxwell NCard 4GB: formatCard() does not produce a mountable filesystem on this card. Other cards in the catalog format normally.

1.6.0 - 2026-07-25

Write-path corruption fixes; broader card compatibility; write-error reliability.

Two data-corruption defects are fixed, both in rewriting an existing file. Files created once and written front to back — including data loggers that only append sector-aligned records — were never affected.

Bug Fixes

  • Reopening a file and overwriting across a cluster boundary (openFileWrite() + seekHandle() back into the file, then writeHandle()) now follows the file's existing chain. The data past the rewritten region survives instead of being orphaned.
  • Appending to a file whose length isn't a multiple of 512 (openFileWrite() + writeHandle()) now preserves the bytes already in that final sector instead of zeroing them.
  • Write failures are no longer silent. A write that fails — full card, failing card, unresponsive card — returns an error instead of reporting success, and the error says what actually went wrong. This covers writing, closing, syncing, unmounting, and deleting.
  • Bulk reads use the same clock-timing correction as writes, removing a read-reliability difference at some SPI speeds.
  • Remounting from a different binary no longer fails with a spurious E_NO_CARD.
  • A stray write can no longer damage the filesystem's own structureswriteHandle() refuses to write below the data region.

Upgrade / recovery note

A card written by an earlier release may carry silent damage — but only if you rewrote existing files as described above.

  • Run SD_FAT32_audit (or SD_FAT32_fsck to also repair) to find lost clusters left by a cross-boundary overwrite. fsck reclaims the space; it cannot restore the file's lost tail content.
  • Zeroed leading bytes from a mid-sector append leave no filesystem footprint — no tool can detect them. Verify or restore affected files from backup.

Changes

  • SD_FAT32_audit now does the deep scan, read-only. It performs the same full check as SD_FAT32_fsck — including finding lost clusters — but changes nothing. Use audit to look, fsck to repair. The separate SD_FAT32_check tool is removed; audit replaces it.

New Features

  • Cards that report placeholder data checksums now work. Some counterfeit and marginal SDSC cards previously failed every read. They are detected at mount, flagged in cardWarnings() as CW_NO_DATA_CRC, and run at a clock speed probed as safe for that card. Cards with real checksums keep strict validation.
  • macOS-formatted cards mount and audit cleanly, including small (SDSC) cards.
  • SD_PINS_EXTERNAL build flag: use an external SD header (base pins 16–21) instead of the P2 Edge onboard slot.

1.5.3 - 2026-05-07

Timeout handling on SDSC (≤2 GB) cards.

SDSC cards could fail every read and write at some system clocks: mount() succeeded at the slow init speed, then all traffic afterwards reported a timeout. SDHC and SDXC cards were unaffected.

Bug Fixes

  • SDSC cards no longer report spurious timeouts. The condition depended on system clock — a card that failed completely at one _CLKFREQ could work at another.
  • SDSC cards that legitimately need long write times are honored. The driver now allows up to 30 seconds for a write where it previously gave up after 1 second, and caps reads at 5 seconds against cards reporting implausible values.

Field Reports

Found and diagnosed by @macca on a 1GB SanDisk SU01G, including a confirmed fix. Many thanks.

1.5.2 - 2026-05-06

Write-path timing margin; erase-block size getter.

Bug Fixes

  • Bulk writes have more clock-timing margin on cards with tight timing, completing the read-path work from v1.5.1. Both single-block and multi-block writes are covered.
  • readCID() works in SD_INCLUDE_ALL builds. Enabling the debug and register features together could shadow it.

New Features

  • eraseBlockSectors(): the card's erase block size in sectors. Useful for aligning write batching or log rotation to flash erase boundaries — typically 32 (SDSC) or 128 (SDHC/SDXC).

Diagnostic API (gated by SD_INCLUDE_DEBUG, NOT FOR PRODUCTION USE)

  • debugEraseBlock(start_sector): erase one erase-block-sized region. For tools distinguishing recoverable card flash from failing flash. Production code must not call this — the card erases internally on normal writes, and misuse risks filesystem corruption.

Documentation

  • Card catalog: SanDisk SU01G 1GB SDSC entry, and a new "E" speed rating (SDSC class, ≤12.5 MHz recommended).

1.5.1 - 2026-05-05

Read-path timing margin for marginal cards.

Older and slower cards could fail intermittently at certain system clocks. No application changes are needed to get the improved behavior; at high system clocks, behavior is byte-identical to prior releases.

Bug Fixes

  • Reads are more reliable on slower and older cards. The driver now adapts how it samples data to the system-clock-to-SCK ratio, giving more margin where the bit cell is narrow.
  • Bulk reads start on schedule at low system clocks. The first clock pulse could arrive late, so the driver began sampling before the card was driving data.
  • SPI works with any pin layout, not only the default P2 Edge arrangement.
  • mount() and initCardOnly() report a specific error when the worker cog fails to start, instead of a generic one.

New Features

  • E_BAD_PIN_CONFIG (-9): mount() fails early when SCK is more than ±3 pins from MOSI or MISO.

Diagnostic API (gated by SD_INCLUDE_DEBUG, NOT FOR PRODUCTION USE)

For tooling that characterizes cards that misbehave. Production applications must not call these — the driver picks the right values itself — and they may change without notice: debugSetSampleMode(), debugSetPreEdgeThreshold(), debugGetEffectiveSampleMode(), debugGetCurrentHp(), debugSetAlignDelayOffset(), debugGetEffectiveAlignDelay(), and debugOnClockChange() (refresh clock-dependent state after a runtime clkset(); production guidance remains unmount()clkset()mount()).

1.5.0 - 2026-04-02

Stale directory cluster fix, new API, feature flag reorganization.

Bug Fixes

  • newDirectory(): a directory created in a recycled cluster no longer inherits stale data — the cluster is zeroed before use.

New Features

  • sectorsPerCluster(): public getter for the filesystem's cluster size.
  • SD_INCLUDE_ASYNC and SD_INCLUDE_DEFRAG: application-level feature flags, both included by SD_INCLUDE_ALL.

1.4.2 - 2026-03-26

Audit fix, allocator wrap-around fix, theory doc updated.

Bug Fixes

  • Audit: Volume label scan searches all root directory entries (not just offset 0)
  • allocateCluster(): Next-fit scan wraps correctly when test_max_clusters limit is active

Improvements

  • Theory of Operations: New sections for cluster allocation, auto-flush, defragmentation, async I/O
  • Theory of Operations: Updated feature flags, command opcodes, error codes, and API tables
  • Demo shell: Defrag commands enabled via SD_INCLUDE_DEFRAG

1.4.1 - 2026-03-25

Defragmentation support, next-fit allocation, contiguous file creation.

New Features

  • SD_INCLUDE_DEFRAG: fileFragments(), isFileContiguous(), compactFile(), createFileContiguous() APIs
  • compactFile() relocates a file's clusters into a contiguous chain with mandatory read-back verification
  • createFileContiguous() pre-allocates a contiguous cluster chain for guaranteed non-fragmented writes
  • FSCK/Audit reports fragmented file count and total fragments in summary output

Improvements

  • allocateCluster(): Next-fit scanning reduces fragmentation on sequential writes
  • Allocation locality hint (fsi_nxt_free) persisted across mount/unmount cycles

Bug Fixes

  • readSector(): CRC match counter no longer incremented when CRC validation is disabled
  • Disk-full simulation tests: Cluster cleanup between test phases prevents false failures

1.4.0 - 2026-03-17

Worker loop restructure, live timestamps, auto-flush, non-blocking async I/O.

New Features

  • setDate(), getDate(): Live clock with 2-second auto-advance for file timestamps
  • Auto-flush dirty handles after 200ms idle (zero cost during active I/O)
  • SD_INCLUDE_ASYNC: Non-blocking file I/O via startReadHandle(), startWriteHandle(), isComplete(), getResult(), cancelAsync()
  • Demo shell: date command to show/set driver clock; directory listings show modification timestamps

Improvements

  • Worker cog loop restructured with dedicated clock tick and idle flush slots
  • run_regression.sh: --run-only flag recompiles only stale .bin files (checks source and driver timestamps)

1.3.2 - 2026-03-10

NCO write alignment fix, selective debug channels, controller-specific code removed.

Bug Fixes

  • writeSector(), writeSectors(): Streamer write timing corrected for power-of-2 SPI half-period values (hp=4, hp=8) via NCO frequency adjustment
  • Controller-specific SPI speed limiting removed; all cards use reported max speed capped at 25 MHz

Improvements

  • Debug output is selectable by subsystem rather than all-or-nothing. DEBUG_MASK replaces DEBUG_DISABLE as the single control: 0 for production, or set channel bits to see only the traffic you care about — INIT, MOUNT, FILE, DIR, SECTOR, STATUS, IDENT, HSPEED, API, RECOVER. The driver requires {Spin2_v46} or later for channel support.

1.3.1 - 2026-03-07

CMD13 STATUS byte fix, reduced driver footprint, updated memory sizing reference.

Bug Fixes

  • checkCardStatus(): STATUS byte errors now detected correctly (was checking uninitialized return variable instead of R2 response)
  • last_cmd13_status: Diagnostic field now captures the actual STATUS byte from CMD13

Improvements

  • Driver hub footprint reduced through SPI transaction consolidation

Documentation

  • Memory sizing guide updated with current v1.3.x footprint data across all configurations

1.3.0 - 2026-03-07

R1 response parsing fix, CMD12 tolerance, and CMD23 block-count support.

Bug Fixes

  • R1 response parsing: skip bytes with bit 7 set per SD spec Section 7.3.2.1 (fixes CMD13 false errors on cards with bus artifacts)
  • CMD12 tolerance: multi-block reads recover via CS deassert on cards with aggressive read-ahead pipelines

New Features

  • cardWarnings(): Bitmask API for init-time capability discoveries
  • CMD23 probing: automatic SET_BLOCK_COUNT detection and use when supported in SPI mode

Improvements

  • recoverToIdle(): CS deassert recovery per SD spec Section 7.2.2
  • readSectors(): CMD23 path with auto-stop verification and fallback to CMD12

1.2.9 - 2026-03-06

Prerelease for v1.3.0. Diagnostic capture for card status reporting, released for field testing. Everything here also ships in v1.3.0 — use that instead.

1.2.1 - 2026-03-05

Audit severity corrections for conditions that are normal on FAT32 media.

Improvements

  • Audit: Partition type $0B (FAT32 CHS) accepted as valid alongside $0C (FAT32 LBA)
  • Audit: Backup FSInfo mismatch downgraded from error to warning (common on FAT32 media)

1.2.0 - 2026-03-03

Consistent error codes across the entire public API, card presence detection, and transport-layer diagnostics.

Breaking Changes

  • 17 PUB methods now return SUCCESS (0) / negative error code instead of true/false: mount, unmount, sync, newDirectory, changeDirectory, deleteFile, rename, moveFile, setVolumeLabel, initCardOnly, readSectorRaw, writeSectorRaw, readVBRRaw, readCIDRaw, readCSDRaw, readSCRRaw, readSDStatusRaw
  • Code testing if sd.mount(...) (truthy = success) must change to if sd.mount(...) == sd.SUCCESS -- see Migration Guide

New Features

  • E_NO_CARD (-8): mount() and initCardOnly() now detect missing cards using a P2 internal pull-up on MISO and return a specific error code instead of generic E_INIT_FAILED
  • setSPISpeed(): Public API method for setting SPI clock frequency at runtime
  • SD_INCLUDE_STACK_CHECK: Conditional feature flag for worker cog stack depth measurement

Improvements

  • Transport layer returns specific error codes (E_TIMEOUT, E_CRC_ERROR, E_BAD_RESPONSE, E_WRITE_REJECTED, E_CARD_BUSY, E_IO_ERROR) instead of bare -1 across readSector, writeSector, allocateCluster, and all SPI wait/response methods
  • writeSector(): Returns 0/negative error codes instead of boolean, with specific failure reasons for timeout, CRC reject, card busy, and programming errors

Documentation

  • Migration Guide for updating v1.0/v1.1 code to v1.2 error-code patterns
  • Card Presence Detection reference with electrical analysis and SD spec research
  • Memory Sizing Guide for hub RAM planning
  • Theory of Operations expanded: card presence detection, card identification and adaptive timing
  • Architecture Decision 13: Card presence detection via P2 internal pull-up

1.1.0 - 2026-02-28

FSCK scales to any card size, CRC error injection for fault testing, V1 legacy API removed.

Breaking Changes

  • V1 legacy API removed (readFile(), writeFile(), readDirectory(), etc.) -- use handle-based API

New Features

  • CRC error injection hooks for hardware-level fault testing (setTestForceReadError, setTestForceWriteError)
  • readVBRRaw() available with SD_INCLUDE_REGISTERS alone (no longer requires SD_INCLUDE_RAW)

Improvements

  • Volume label scan follows full root directory cluster chain
  • FAT chain addressing supports cards up to 2 TB
  • FSCK full validation works on cards of any size (windowed bitmap for cards >64 GB)
  • Cross-compilation support for Spin Tools IDE and flexspin

1.0.0 - 2026-02-25

Initial release.

New Features

  • FAT32-compliant SD card filesystem for the Parallax Propeller 2
  • Smart pin SPI with streamer DMA for hardware-accelerated transfers
  • Dedicated worker cog with hardware lock serialization
  • Up to 6 simultaneous file and directory handles (configurable)
  • Per-cog current working directory for safe multi-cog navigation
  • Handle-based file and directory API: open, read, write, seek, enumerate, close
  • Directory operations: create, navigate, delete, rename
  • Raw sector read/write and multi-sector bulk transfers (CMD18/CMD25)
  • Hardware-accelerated CRC-16 on all data transfers
  • SDHC and SDXC cards supported; tested with cards up to 128 GB across 9 manufacturers

Utilities

  • FAT32 format with cross-OS compatibility (Windows, macOS, Linux)
  • 4-pass filesystem check and repair (fsck)
  • Read-only filesystem audit
  • Card characterization, SPI speed testing, and performance benchmark
  • Interactive terminal shell with DOS and Unix-style commands

Documentation

  • Driver tutorial, theory of operations, card catalog
  • SD card performance guide with ranked comparisons

0.9.3 - 2026-02-24

Improvements

  • File handle limit increased from 4 to 6 (default)
  • writeSector(): Cross-buffer cache coherence verified across all three sector caches
  • File and directory creation validates entry address before writing (prevents MBR corruption)
  • rename(): 8.3 extension parsing handles mixed-case filenames
  • File and directory lookup uses case-insensitive matching
  • Demo shell: Line ending and prompt display corrected

Documentation

  • External SD header guide with 8-pin header group reference table

0.9.2 - 2026-02-10

Improvements

  • Demo shell: Help text formatted for 80x25 terminal

0.9.1 - 2026-02-09

Initial testing release -- driver, utilities, demo shell, and 263+ regression tests.

0.9.0 - 2026-02-09

Packaging-only tag. Release workflow and user-facing documentation; no driver code.