diff --git a/platform/README.md b/platform/README.md index 84a7a268..d32cde41 100644 --- a/platform/README.md +++ b/platform/README.md @@ -211,6 +211,18 @@ list builds stayed byte-identical. Not yet run on a board. ## Changes since the copies were taken +- 2026-10-08: CHSd clocks one idle byte before each command (`cmd()` in + `SdSpi.cpp`), the SD spec's N_RC: at least 8 clocks between a response + and the next command. Identification sent its commands back to back + (`read()` and `stream()` already waited first). A SanDisk 32 GB card + ("SK32G") took them misaligned and stopped answering, so CHStlView and + the word games found no card where the menu, whose `sd.c` always sent the + byte, read it. Contributed with a board run on that card (PR #21). + `tests/test_spi.cpp`'s card model now counts a command sent with no gap + as a failure (11 or 12 per init without the fix). +8 B in each of the + four sketches on CHSd (CHWords, CHWordWheel, CHCrossword, CHStlView), + RAM unchanged; the simulator does not run `SdSpi.cpp`. + - 2026-10-08: the three libraries link from archives (`dot_a_linkage=true` in CHGfx's, CHGame's and CHSd's `library.properties`). A developer using only `audio::` and the buttons, with their own display code, found every diff --git a/platform/board/CHANGELOG.md b/platform/board/CHANGELOG.md index a6987024..30550ac5 100644 --- a/platform/board/CHANGELOG.md +++ b/platform/board/CHANGELOG.md @@ -29,6 +29,15 @@ file as the GitHub release notes. ### Fixed +- **CHSd works with more microSD cards.** It sent each command right after + the card's previous response, where the SD Physical Layer Simplified + Specification asks for at least 8 clocks in between (N_RC, SPI mode + timing). Many cards do without them; a strict one (a SanDisk 32 GB, + "SK32G") took the next command misaligned and stopped answering, so every + sketch on CHSd (CHStlView, the word games' card files) said there was no + card while the SD menu bootloader read it. `cmd()` now clocks one idle + byte first, as the bootloader's `sd.c` does: one byte per command, not per + block. - **EEPROM no longer writes the CH32X035's option bytes.** The library is the CH32V003's: its `commit()` erased the option-byte block and rewrote it a half-word at a time, which the CH32X035 is not documented to accept. A diff --git a/platform/board/arduino/CHGame/libraries/CHSd/src/SdSpi.cpp b/platform/board/arduino/CHGame/libraries/CHSd/src/SdSpi.cpp index 8e7f3706..b416a7e8 100644 --- a/platform/board/arduino/CHGame/libraries/CHSd/src/SdSpi.cpp +++ b/platform/board/arduino/CHGame/libraries/CHSd/src/SdSpi.cpp @@ -80,7 +80,16 @@ static uint8_t wait(bool tok) { // bit. Only CMD0 and CMD8 are checked by a card in SPI mode (CRC is left // off): their CRCs are the two constants. R1 arrives within 8 bytes; bit 7 // set means nothing answered. +// +// One idle byte goes first: the SD spec's N_RC, at least 8 clocks between a +// response and the next command (Physical Layer Simplified Specification, +// SPI mode timing, 7.5). Many cards do without it; a strict one (a SanDisk +// 32 GB, "SK32G") is still finishing its last response, takes the command +// misaligned (CMD0 answered 01, then C1 7F, then every answer 6 bits off) +// and stops answering until it loses power. The bootloader's sd.c does the +// same. It costs one byte per command, not per block. static uint8_t cmd(uint8_t c, uint32_t arg) { + xfer(0xFF); xfer((uint8_t)(0x40 | c)); for (int sh = 24; sh >= 0; sh -= 8) xfer((uint8_t)(arg >> sh)); xfer(c == 8 ? 0x87 : 0x95); diff --git a/platform/board/arduino/CHGame/libraries/CHSd/tests/test_spi.cpp b/platform/board/arduino/CHGame/libraries/CHSd/tests/test_spi.cpp index e4df3c9c..de18e54a 100644 --- a/platform/board/arduino/CHGame/libraries/CHSd/tests/test_spi.cpp +++ b/platform/board/arduino/CHGame/libraries/CHSd/tests/test_spi.cpp @@ -3,7 +3,10 @@ // card keeps across an MCU reset (it stays powered): its state and whether // CRC checking is on. CHSDtoUSB turns checking on, and the model's CMD0 does // not turn it off, the worst case (the bootloader's test/native/sd_model.c -// assumes the same). tests/run_tests.py builds and runs it. +// assumes the same). It is also a strict card about N_RC: a command must not +// start on the byte right after the last byte of a response (some cards, +// such as a SanDisk 32 GB "SK32G", take it misaligned and stop answering). +// tests/run_tests.py builds and runs it. #include #include #include "Arduino.h" @@ -36,7 +39,8 @@ struct Card { enum State { POWERUP, IDLE, READY } state = POWERUP; bool crcOn = false, acmd = false; int acmd41Left = 0; - uint32_t clocksCsHigh = 0, badCrc = 0, fastIdent = 0, conflicts = 0, blocks = 1024; + uint32_t clocksCsHigh = 0, badCrc = 0, fastIdent = 0, conflicts = 0, noGap = 0, blocks = 1024; + bool justAnswered = false; // the last byte out ended a response uint8_t cmd[6]; int cmdLen = 0; uint8_t out[600]; @@ -96,10 +100,14 @@ struct Card { if (type == NONE) return 0xFF; if (!csLow) { if (clocksCsHigh < 1000) clocksCsHigh += 8; + justAnswered = false; return 0xFF; // DO released: the pull-up } + bool noRc = justAnswered; + justAnswered = outPos + 1 == outLen; uint8_t o = outPos < outLen ? out[outPos++] : 0xFF; if (cmdLen == 0) { + if ((in & 0xC0) == 0x40 && noRc) noGap++; // N_RC: 8 clocks at least if ((in & 0xC0) == 0x40) cmd[cmdLen++] = in; } else { cmd[cmdLen++] = in; @@ -140,6 +148,7 @@ static void initAndRead(const char *name, bool expectOk) { CHECK(t_spi.CTLR1 == lcd, "SPI1 not handed back as CHGfx left it"); CHECK(card.conflicts == 0, "the panel and the card selected together"); CHECK(card.fastIdent == 0, "identification above 400 kHz"); + CHECK(card.noGap == 0, "%u commands without N_RC", (unsigned)card.noGap); CHECK(card.badCrc == 0, "%u commands refused for their CRC", (unsigned)card.badCrc); if (!expectOk) { CHECK(now_us - t0 < 20000, "an empty slot took %u us", (unsigned)(now_us - t0));