Merge branch 's1': the SD driver's ready test, and sd card

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
2026-10-05 23:25:55 +02:00
co-authored by Claude Opus 5.5
17 changed files with 1484 additions and 6 deletions
+2 -1
View File
@@ -76,6 +76,7 @@ The LoRa Scanner (docs/milestones/M3.md) listens with the Cap's radio and **neve
| `short` / `normal` | Screen timeouts 5 s / 10 s, or 30 s / 60 s |
| `wifi add <ssid><TAB><password>` | Adds a Saved Network (so credentials stay out of the repo) |
| `log <text>` | Appends a line to a test IRC Log (`/irc/dev/#test/<date>.log`) |
| `sd card` | What the SD card says it is: type, size, and its identity register (maker, name, revision, serial, date) |
| `sd list` | Lists the files of each Storage Clean-up category |
| `cat <path>` | Prints the first ~1.2 KB of a file on the SD card |
| `irc start` | Starts the IRC Service (normally done by opening the IRC App) |
@@ -85,7 +86,7 @@ The LoRa Scanner (docs/milestones/M3.md) listens with the Cap's radio and **neve
| `irc say <buffer> <text>` | Types into a Buffer, commands included (`irc say 0 /join #test`) |
| `irc dump` | Prints IRC status, memory, and the last lines of each Buffer |
| `wifi status` | Prints Wi-Fi state, network, signal, clock and free heap |
| `info` | Firmware, uptime, last start reason, memory, Wi-Fi, and both app slots with their versions and OTA states |
| `info` | Firmware, uptime, last start reason, memory, Wi-Fi, the SD card and its write faults since boot, and both app slots with their versions and OTA states |
| `tasks` | FreeRTOS tasks: state, priority, lowest free stack, CPU share |
| `reboot` / `boot other` | Restart, or restart into the other app slot (a manual Rollback) |
| `log level <0-5>` | ESP-IDF log level |
@@ -0,0 +1,20 @@
# Our own copy of the SD driver, for one missing byte
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`): `info` shows the count, and the Debug Console's `put` prints 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/SD` with the new `libraries/SD` and carry the `roro:` changes over. If upstream fixes the ready test, drop the copy. Reported as [arduino-esp32#12970](https://github.com/espressif/arduino-esp32/issues/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 `DataToken` faults ever show in `info`, 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.
+7
View File
@@ -0,0 +1,7 @@
{
"name": "SD",
"version": "3.3.12",
"description": "roro9stack's copy of Arduino-ESP32's SD library (Apache-2.0), shadowing the framework's. Only sd_diskio.cpp is changed (marked \"roro:\"): it records why a write failed and resends a block the card rejects. See issue #21.",
"frameworks": "arduino",
"platforms": "espressif32"
}
+134
View File
@@ -0,0 +1,134 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "vfs_api.h"
#include "sd_diskio.h"
#include "ff.h"
#include "FS.h"
#include "SD.h"
using namespace fs;
SDFS::SDFS(FSImplPtr impl) : FS(impl), _pdrv(0xFF) {}
SDFS::~SDFS() {
end();
}
bool SDFS::begin(uint8_t ssPin, SPIClass &spi, uint32_t frequency, const char *mountpoint, uint8_t max_files, bool format_if_empty) {
if (_pdrv != 0xFF) {
return true;
}
if (!spi.begin()) {
return false;
}
_pdrv = sdcard_init(ssPin, &spi, frequency);
if (_pdrv == 0xFF) {
return false;
}
if (!sdcard_mount(_pdrv, mountpoint, max_files, format_if_empty)) {
sdcard_unmount(_pdrv);
sdcard_uninit(_pdrv);
_pdrv = 0xFF;
return false;
}
_impl->mountpoint(mountpoint);
return true;
}
void SDFS::end() {
if (_pdrv != 0xFF) {
_impl->mountpoint(NULL);
sdcard_unmount(_pdrv);
sdcard_uninit(_pdrv);
_pdrv = 0xFF;
}
}
sdcard_type_t SDFS::cardType() {
if (_pdrv == 0xFF) {
return CARD_NONE;
}
return sdcard_type(_pdrv);
}
uint64_t SDFS::cardSize() {
if (_pdrv == 0xFF) {
return 0;
}
size_t sectors = sdcard_num_sectors(_pdrv);
size_t sectorSize = sdcard_sector_size(_pdrv);
return (uint64_t)sectors * sectorSize;
}
size_t SDFS::numSectors() {
if (_pdrv == 0xFF) {
return 0;
}
return sdcard_num_sectors(_pdrv);
}
size_t SDFS::sectorSize() {
if (_pdrv == 0xFF) {
return 0;
}
return sdcard_sector_size(_pdrv);
}
uint64_t SDFS::totalBytes() {
FATFS *fsinfo;
DWORD fre_clust;
char drv[3] = {(char)(48 + _pdrv), ':', 0};
if (f_getfree(drv, &fre_clust, &fsinfo) != 0) {
return 0;
}
uint64_t size = ((uint64_t)(fsinfo->csize)) * (fsinfo->n_fatent - 2)
#if _MAX_SS != 512
* (fsinfo->ssize);
#else
* 512;
#endif
return size;
}
uint64_t SDFS::usedBytes() {
FATFS *fsinfo;
DWORD fre_clust;
char drv[3] = {(char)(48 + _pdrv), ':', 0};
if (f_getfree(drv, &fre_clust, &fsinfo) != 0) {
return 0;
}
uint64_t size = ((uint64_t)(fsinfo->csize)) * ((fsinfo->n_fatent - 2) - (fsinfo->free_clst))
#if _MAX_SS != 512
* (fsinfo->ssize);
#else
* 512;
#endif
return size;
}
bool SDFS::readRAW(uint8_t *buffer, uint32_t sector) {
return sd_read_raw(_pdrv, buffer, sector);
}
bool SDFS::writeRAW(uint8_t *buffer, uint32_t sector) {
return sd_write_raw(_pdrv, buffer, sector);
}
SDFS SD = SDFS(FSImplPtr(new VFSImpl()));
+55
View File
@@ -0,0 +1,55 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _SD_H_
#define _SD_H_
#include "FS.h"
#include "SPI.h"
#include "sd_defines.h"
namespace fs {
class SDFS : public FS {
protected:
uint8_t _pdrv;
public:
SDFS(FSImplPtr impl);
~SDFS();
bool begin(
uint8_t ssPin = SS, SPIClass &spi = SPI, uint32_t frequency = 4000000, const char *mountpoint = "/sd", uint8_t max_files = 5, bool format_if_empty = false
);
void end();
sdcard_type_t cardType();
uint64_t cardSize();
size_t numSectors();
size_t sectorSize();
uint64_t totalBytes();
uint64_t usedBytes();
bool readRAW(uint8_t *buffer, uint32_t sector);
bool writeRAW(uint8_t *buffer, uint32_t sector);
};
} // namespace fs
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SD)
extern fs::SDFS SD;
#endif
using namespace fs;
typedef fs::File SDFile;
typedef fs::SDFS SDFileSystemClass;
#define SDFileSystem SD
#endif /* _SD_H_ */
+25
View File
@@ -0,0 +1,25 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _SD_DEFINES_H_
#define _SD_DEFINES_H_
typedef enum {
CARD_NONE,
CARD_MMC,
CARD_SD,
CARD_SDHC,
CARD_UNKNOWN
} sdcard_type_t;
#endif /* _SD_DISKIO_H_ */
+949
View File
@@ -0,0 +1,949 @@
// roro9stack's copy of the SD-over-SPI driver from Arduino-ESP32 3.3.12 (libraries/SD/src/
// sd_diskio.cpp), linked instead of the framework's: defining every function the SD class needs
// keeps the library's file out of the link. Changes are marked "roro:". Why (issue #21): the
// original gives up on a write without saying why, and never resends a block the card rejects.
//
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Disable the automatic pin remapping of the API calls in this file
#define ARDUINO_CORE_BUILD
#include "Arduino.h"
#include "sd_diskio.h"
#include "esp_system.h"
#include "esp32-hal-periman.h"
extern "C" {
#include "ff.h"
#include "diskio.h"
#if ESP_IDF_VERSION_MAJOR > 3
#include "diskio_impl.h"
#endif
//#include "esp_vfs.h"
#include "esp_vfs_fat.h"
char CRC7(const char *data, int length);
unsigned short CRC16(const char *data, int length);
}
// roro: why the last write failed.
#include "sd_fault.h"
static roro::SdFault s_fault;
static bool sdFault(roro::SdFault::Step step, uint8_t token = 0, uint32_t resp = 0) {
s_fault.step = step;
s_fault.token = token;
s_fault.resp = resp;
s_fault.count++;
return false;
}
namespace roro {
SdFault sdLastFault() { return s_fault; }
} // namespace roro
typedef enum {
GO_IDLE_STATE = 0,
SEND_OP_COND = 1,
SEND_CID = 2,
SEND_RELATIVE_ADDR = 3,
SEND_SWITCH_FUNC = 6,
SEND_IF_COND = 8,
SEND_CSD = 9,
STOP_TRANSMISSION = 12,
SEND_STATUS = 13,
SET_BLOCKLEN = 16,
READ_BLOCK_SINGLE = 17,
READ_BLOCK_MULTIPLE = 18,
SEND_NUM_WR_BLOCKS = 22,
SET_WR_BLK_ERASE_COUNT = 23,
WRITE_BLOCK_SINGLE = 24,
WRITE_BLOCK_MULTIPLE = 25,
APP_OP_COND = 41,
APP_CLR_CARD_DETECT = 42,
APP_CMD = 55,
READ_OCR = 58,
CRC_ON_OFF = 59
} ardu_sdcard_command_t;
// Align with ESP-IDF sdmmc SPI init (ACMD41 timeout must be >1s per SD spec)
static constexpr uint32_t sd_go_idle_delay_ms = 20;
static constexpr uint32_t sd_op_cond_timeout_ms = 3000;
typedef struct {
uint8_t ssPin;
SPIClass *spi;
int frequency;
char *base_path;
sdcard_type_t type;
unsigned long sectors;
bool supports_crc;
int status;
} ardu_sdcard_t;
static ardu_sdcard_t *s_cards[FF_VOLUMES] = {NULL};
#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_ERROR
const char *fferr2str[] = {
"(0) Succeeded",
"(1) A hard error occurred in the low level disk I/O layer",
"(2) Assertion failed",
"(3) The physical drive cannot work",
"(4) Could not find the file",
"(5) Could not find the path",
"(6) The path name format is invalid",
"(7) Access denied due to prohibited access or directory full",
"(8) Access denied due to prohibited access",
"(9) The file/directory object is invalid",
"(10) The physical drive is write protected",
"(11) The logical drive number is invalid",
"(12) The volume has no work area",
"(13) There is no valid FAT volume",
"(14) The f_mkfs() aborted due to any problem",
"(15) Could not get a grant to access the volume within defined period",
"(16) The operation is rejected according to the file sharing policy",
"(17) LFN working buffer could not be allocated",
"(18) Number of open files > FF_FS_LOCK",
"(19) Given parameter is invalid"
};
#endif
/*
* SD SPI
* */
bool sdWait(uint8_t pdrv, int timeout) {
char resp;
uint32_t start = millis();
do {
resp = s_cards[pdrv]->spi->transfer(0xFF);
} while (resp == 0x00 && (millis() - start) < (unsigned int)timeout);
if (!resp) {
log_w("Wait Failed");
}
return (resp > 0x00);
}
void sdStop(uint8_t pdrv) {
s_cards[pdrv]->spi->write(0xFD);
}
void sdDeselectCard(uint8_t pdrv) {
ardu_sdcard_t *card = s_cards[pdrv];
digitalWrite(card->ssPin, HIGH);
}
bool sdSelectCard(uint8_t pdrv) {
ardu_sdcard_t *card = s_cards[pdrv];
digitalWrite(card->ssPin, LOW);
// roro: one dummy byte before asking whether the card is ready, as ChaN's reference driver does.
// A card that has just been given a write takes about a byte of clock to signal busy; without
// this, that first byte reads 0xFF, "ready", and the next command (the status check after a
// write) goes out while the card is still programming and comes back as garbage (#21).
card->spi->transfer(0xFF);
bool s = sdWait(pdrv, 500);
if (!s) {
log_e("Select Failed");
digitalWrite(card->ssPin, HIGH);
return false;
}
return true;
}
char sdCommand(uint8_t pdrv, char cmd, unsigned int arg, unsigned int *resp) {
char token;
ardu_sdcard_t *card = s_cards[pdrv];
for (int f = 0; f < 3; f++) {
if (cmd == SEND_NUM_WR_BLOCKS || cmd == SET_WR_BLK_ERASE_COUNT || cmd == APP_OP_COND || cmd == APP_CLR_CARD_DETECT) {
token = sdCommand(pdrv, APP_CMD, 0, NULL);
sdDeselectCard(pdrv);
if (token > 1) {
break;
}
if (!sdSelectCard(pdrv)) {
token = 0xFF;
break;
}
}
char cmdPacket[7];
cmdPacket[0] = cmd | 0x40;
cmdPacket[1] = arg >> 24;
cmdPacket[2] = arg >> 16;
cmdPacket[3] = arg >> 8;
cmdPacket[4] = arg;
if (card->supports_crc || cmd == GO_IDLE_STATE || cmd == SEND_IF_COND) {
cmdPacket[5] = (CRC7(cmdPacket, 5) << 1) | 0x01;
} else {
cmdPacket[5] = 0x01;
}
cmdPacket[6] = 0xFF;
card->spi->writeBytes((uint8_t *)cmdPacket, (cmd == STOP_TRANSMISSION) ? 7 : 6);
for (int i = 0; i < 9; i++) {
token = card->spi->transfer(0xFF);
if (!(token & 0x80)) {
break;
}
}
if (token == 0xFF) {
log_w("no token received");
sdDeselectCard(pdrv);
delay(100);
sdSelectCard(pdrv);
continue;
} else if (token & 0x08) {
log_w("crc error");
sdDeselectCard(pdrv);
delay(100);
sdSelectCard(pdrv);
continue;
} else if (token > 1) {
log_w("token error [%u] 0x%x", cmd, token);
break;
}
if (cmd == SEND_STATUS && resp) {
*resp = card->spi->transfer(0xFF);
} else if ((cmd == SEND_IF_COND || cmd == READ_OCR) && resp) {
*resp = card->spi->transfer32(0xFFFFFFFF);
}
break;
}
if (token == 0xFF) {
log_e("Card Failed! cmd: 0x%02x", cmd);
card->status = STA_NOINIT;
}
return token;
}
bool sdReadBytes(uint8_t pdrv, char *buffer, int length) {
char token;
unsigned short crc;
ardu_sdcard_t *card = s_cards[pdrv];
uint32_t start = millis();
do {
token = card->spi->transfer(0xFF);
} while (token == 0xFF && (millis() - start) < 500);
if (token != 0xFE) {
return false;
}
card->spi->transferBytes(NULL, (uint8_t *)buffer, length);
crc = card->spi->transfer16(0xFFFF);
return (!card->supports_crc || crc == CRC16(buffer, length));
}
char sdWriteBytes(uint8_t pdrv, const char *buffer, char token) {
ardu_sdcard_t *card = s_cards[pdrv];
unsigned short crc = (card->supports_crc) ? CRC16(buffer, 512) : 0xFFFF;
if (!sdWait(pdrv, 500)) {
return 0;
}
card->spi->write(token);
card->spi->writeBytes((uint8_t *)buffer, 512);
card->spi->write16(crc);
return (card->spi->transfer(0xFF) & 0x1F);
}
/*
* SPI SDCARD Communication
* */
char sdTransaction(uint8_t pdrv, char cmd, unsigned int arg, unsigned int *resp) {
if (!sdSelectCard(pdrv)) {
return 0xFF;
}
char token = sdCommand(pdrv, cmd, arg, resp);
sdDeselectCard(pdrv);
return token;
}
bool sdReadSector(uint8_t pdrv, char *buffer, unsigned long long sector) {
for (int f = 0; f < 3; f++) {
if (!sdSelectCard(pdrv)) {
return false;
}
if (!sdCommand(pdrv, READ_BLOCK_SINGLE, (s_cards[pdrv]->type == CARD_SDHC) ? sector : sector << 9, NULL)) {
bool success = sdReadBytes(pdrv, buffer, 512);
sdDeselectCard(pdrv);
if (success) {
return true;
}
} else {
break;
}
}
sdDeselectCard(pdrv);
return false;
}
bool sdReadSectors(uint8_t pdrv, char *buffer, unsigned long long sector, int count) {
for (int f = 0; f < 3;) {
if (!sdSelectCard(pdrv)) {
return false;
}
if (!sdCommand(pdrv, READ_BLOCK_MULTIPLE, (s_cards[pdrv]->type == CARD_SDHC) ? sector : sector << 9, NULL)) {
do {
if (!sdReadBytes(pdrv, buffer, 512)) {
f++;
break;
}
sector++;
buffer += 512;
f = 0;
} while (--count);
if (sdCommand(pdrv, STOP_TRANSMISSION, 0, NULL)) {
log_e("command failed");
break;
}
sdDeselectCard(pdrv);
if (count == 0) {
return true;
}
} else {
break;
}
}
sdDeselectCard(pdrv);
return false;
}
bool sdWriteSector(uint8_t pdrv, const char *buffer, unsigned long long sector) {
using roro::SdFault;
for (int f = 0; f < 3; f++) {
if (!sdSelectCard(pdrv)) {
return sdFault(SdFault::Select); // roro: say why
}
if (!sdCommand(pdrv, WRITE_BLOCK_SINGLE, (s_cards[pdrv]->type == CARD_SDHC) ? sector : sector << 9, NULL)) {
char token = sdWriteBytes(pdrv, buffer, 0xFE);
sdDeselectCard(pdrv);
if (token == 0x0A) {
continue;
} else if (token == 0x0C) {
return sdFault(SdFault::DataToken, token);
}
unsigned int resp;
char status = sdTransaction(pdrv, SEND_STATUS, 0, &resp);
if (status || resp) {
return token != 0x05 ? sdFault(SdFault::DataToken, token, resp) : sdFault(SdFault::Status, status, resp);
}
return true;
} else {
break;
}
}
sdDeselectCard(pdrv);
return sdFault(SdFault::Command);
}
bool sdWriteSectors(uint8_t pdrv, const char *buffer, unsigned long long sector, int count) {
using roro::SdFault;
char token;
const char *currentBuffer = buffer;
unsigned long long currentSector = sector;
int currentCount = count;
ardu_sdcard_t *card = s_cards[pdrv];
SdFault::Step why = SdFault::Command; // roro: what stopped it, for the last return
uint8_t whyToken = 0;
for (int f = 0; f < 3;) {
if (card->type != CARD_MMC) {
char refused = sdTransaction(pdrv, SET_WR_BLK_ERASE_COUNT, currentCount, NULL);
if (refused) {
return sdFault(SdFault::EraseCount, refused);
}
}
if (!sdSelectCard(pdrv)) {
return sdFault(SdFault::Select);
}
if (!sdCommand(pdrv, WRITE_BLOCK_MULTIPLE, (card->type == CARD_SDHC) ? currentSector : currentSector << 9, NULL)) {
do {
token = sdWriteBytes(pdrv, currentBuffer, 0xFC);
if (token != 0x05) {
f++;
break;
}
currentBuffer += 512;
f = 0;
} while (--currentCount);
if (!sdWait(pdrv, 500)) {
why = SdFault::BusyAfter;
break;
}
if (currentCount == 0) {
sdStop(pdrv);
card->spi->transfer(0xFF); // roro: the byte the card takes to go busy after Stop Tran (#21)
sdDeselectCard(pdrv);
unsigned int resp;
char status = sdTransaction(pdrv, SEND_STATUS, 0, &resp);
if (status || resp) {
return sdFault(SdFault::Status, status, resp);
}
return true;
} else {
if (sdCommand(pdrv, STOP_TRANSMISSION, 0, NULL)) {
why = SdFault::StopCommand;
whyToken = token;
break;
}
if (token == 0x0A) {
sdDeselectCard(pdrv);
unsigned int writtenBlocks = 0;
if (card->type != CARD_MMC && sdSelectCard(pdrv)) {
if (!sdCommand(pdrv, SEND_NUM_WR_BLOCKS, 0, NULL)) {
char acmdData[4];
if (sdReadBytes(pdrv, acmdData, 4)) {
writtenBlocks = acmdData[0] << 24;
writtenBlocks |= acmdData[1] << 16;
writtenBlocks |= acmdData[2] << 8;
writtenBlocks |= acmdData[3];
}
}
sdDeselectCard(pdrv);
}
currentBuffer = buffer + (writtenBlocks << 9);
currentSector = sector + writtenBlocks;
currentCount = count - writtenBlocks;
continue;
} else {
why = SdFault::DataToken;
whyToken = token;
break;
}
}
} else {
break;
}
}
sdDeselectCard(pdrv);
return sdFault(why, whyToken);
}
unsigned long sdGetSectorsCount(uint8_t pdrv) {
for (int f = 0; f < 3; f++) {
if (!sdSelectCard(pdrv)) {
return 0;
}
if (!sdCommand(pdrv, SEND_CSD, 0, NULL)) {
char csd[16];
bool success = sdReadBytes(pdrv, csd, 16);
sdDeselectCard(pdrv);
if (success) {
if ((csd[0] >> 6) == 0x01) {
unsigned long size = (((unsigned long)(csd[7] & 0x3F) << 16) | ((unsigned long)csd[8] << 8) | csd[9]) + 1;
return size << 10;
}
unsigned long size = (((unsigned long)(csd[6] & 0x03) << 10) | ((unsigned long)csd[7] << 2) | ((csd[8] & 0xC0) >> 6)) + 1;
size <<= ((((csd[9] & 0x03) << 1) | ((csd[10] & 0x80) >> 7)) + 2);
size <<= (csd[5] & 0x0F);
return size >> 9;
}
} else {
break;
}
}
sdDeselectCard(pdrv);
return 0;
}
namespace {
struct AcquireSPI {
ardu_sdcard_t *card;
explicit AcquireSPI(ardu_sdcard_t *card) : card(card) {
card->spi->beginTransaction(SPISettings(card->frequency, MSBFIRST, SPI_MODE0));
}
AcquireSPI(ardu_sdcard_t *card, int frequency) : card(card) {
card->spi->beginTransaction(SPISettings(frequency, MSBFIRST, SPI_MODE0));
}
~AcquireSPI() {
card->spi->endTransaction();
}
private:
AcquireSPI(AcquireSPI const &);
AcquireSPI &operator=(AcquireSPI const &);
};
} // namespace
/*
* FATFS API
* */
/**
* @brief Initialize an SD card for use with FatFs
*
* This function implements the complete SD card initialization sequence according to
* the SD card specification. It performs card detection, type identification,
* and configuration for SPI mode operation.
*
* The initialization sequence follows the SD card protocol (SPI mode, aligned with IDF):
* 1. Power-up sequence with 74+ clock cycles
* 2. Two GO_IDLE_STATE attempts to enter SPI mode
* 3. CRC_ON_OFF to enable CRC checking (with retry)
* 4. SEND_IF_COND to identify SDHC/SDXC cards
* 5. APP_OP_COND / SEND_OP_COND (SPI args; timeout >1s)
* 6. Card type detection (SD/SDHC/MMC)
* 7. Final configuration and sector count retrieval
*
* @param pdrv Physical drive number (0-9)
* @return DSTATUS Status of the initialization (0 = success, STA_NOINIT = failed)
*/
DSTATUS ff_sd_initialize(uint8_t pdrv) {
char token;
unsigned int resp;
unsigned int start;
// Get the card structure for the given drive number
ardu_sdcard_t *card = s_cards[pdrv];
// If the card is already initialized, return its current status
if (!(card->status & STA_NOINIT)) {
return card->status;
}
// Lock the SPI bus and set it to a low frequency (400kHz) for initialization
// Low frequency is required during initialization for reliable communication
AcquireSPI card_locked(card, 400000);
// Step 1: Power-up sequence - Send at least 74 clock cycles with CS high and MOSI high
// This is required by the SD card specification to ensure proper card state reset
// We send 20 bytes (160 clock cycles) to exceed the minimum requirement
digitalWrite(card->ssPin, HIGH);
for (uint8_t i = 0; i < 20; i++) {
card->spi->transfer(0XFF);
}
// Step 2: Perform two GO_IDLE_STATE (CMD0) attempts in SPI mode.
// Per SD Simplified Spec (figure 4-1) / IDF: some cards enter SD mode on the
// first attempt, so the first response may fail; the second must succeed.
// (sdCommand may also retry internally on no-token/CRC errors.)
// Fix mount issue - sdWait fail ignored before each CMD0 attempt
digitalWrite(card->ssPin, LOW);
if (!sdWait(pdrv, 500)) {
log_w("sdWait fail ignored, card initialize continues");
}
(void)sdCommand(pdrv, GO_IDLE_STATE, 0, NULL);
sdDeselectCard(pdrv);
delay(sd_go_idle_delay_ms);
digitalWrite(card->ssPin, LOW);
if (!sdWait(pdrv, 500)) {
log_w("sdWait fail ignored, card initialize continues");
}
if (sdCommand(pdrv, GO_IDLE_STATE, 0, NULL) != 1) {
sdDeselectCard(pdrv);
log_w("GO_IDLE_STATE failed");
goto unknown_card;
}
sdDeselectCard(pdrv);
delay(sd_go_idle_delay_ms);
// Step 3: Configure CRC checking
// Enable CRC for data transfers in SPI mode (required for reliable communication).
// Some cards reject the first CRC_ON_OFF; retry once (same as IDF).
token = sdTransaction(pdrv, CRC_ON_OFF, 1, NULL);
if (token != 1 && token != 0x5) {
delay(10);
token = sdTransaction(pdrv, CRC_ON_OFF, 1, NULL);
}
if (token == 0x5) {
// Old card that doesn't support CRC - disable CRC checking
card->supports_crc = false;
} else if (token != 1) {
log_w("CRC_ON_OFF failed: %u", token);
goto unknown_card;
}
// Step 4: Card type detection and initialization
// Try to identify SDHC/SDXC cards using SEND_IF_COND command
if (sdTransaction(pdrv, SEND_IF_COND, 0x1AA, &resp) == 1) {
// Card responded to SEND_IF_COND - likely SDHC/SDXC
if ((resp & 0xFFF) != 0x1AA) {
log_w("SEND_IF_COND failed: %03" PRIX32, (uint32_t)(resp & 0xFFF));
goto unknown_card;
}
// Read Operating Conditions Register to check card capabilities
if (sdTransaction(pdrv, READ_OCR, 0, &resp) != 1 || !(resp & (1 << 20))) {
log_w("READ_OCR failed: %X", resp);
goto unknown_card;
}
// Send APP_OP_COND to set operating conditions for SDHC/SDXC.
// In SPI mode only HCS (bit 30) is valid; voltage bits must be 0 (same as IDF).
// Timeout must be >1s per SD spec (IDF uses ~3s).
start = millis();
do {
token = sdTransaction(pdrv, APP_OP_COND, 0x40000000, NULL);
} while (token == 1 && (millis() - start) < sd_op_cond_timeout_ms);
if (token) {
log_w("APP_OP_COND failed: %u", token);
goto unknown_card;
}
// Determine if it's SDHC (high capacity) or regular SD
if (!sdTransaction(pdrv, READ_OCR, 0, &resp)) {
if (resp & (1 << 30)) {
card->type = CARD_SDHC; // High capacity card (SDHC/SDXC)
} else {
card->type = CARD_SD; // Standard capacity card
}
} else {
log_w("READ_OCR failed: %X", resp);
goto unknown_card;
}
} else {
// Card didn't respond to SEND_IF_COND - try SD or MMC initialization
if (sdTransaction(pdrv, READ_OCR, 0, &resp) != 1 || !(resp & (1 << 20))) {
log_w("READ_OCR failed: %X", resp);
goto unknown_card;
}
// Try SD card initialization first (SPI mode: ACMD41 arg must be 0)
start = millis();
do {
token = sdTransaction(pdrv, APP_OP_COND, 0, NULL);
} while (token == 0x01 && (millis() - start) < sd_op_cond_timeout_ms);
if (!token) {
card->type = CARD_SD; // Standard SD card
} else {
// Try MMC card initialization (SPI mode: CMD1 arg must be 0)
start = millis();
do {
token = sdTransaction(pdrv, SEND_OP_COND, 0, NULL);
} while (token != 0x00 && (millis() - start) < sd_op_cond_timeout_ms);
if (token == 0x00) {
card->type = CARD_MMC; // MMC card
} else {
log_w("SEND_OP_COND failed: %u", token);
goto unknown_card;
}
}
}
// Step 5: Clear card detection for SD cards (not needed for MMC)
if (card->type != CARD_MMC) {
if (sdTransaction(pdrv, APP_CLR_CARD_DETECT, 0, NULL)) {
log_w("APP_CLR_CARD_DETECT failed");
goto unknown_card;
}
}
// Step 6: Set block length for non-SDHC cards
// SDHC cards have fixed 512-byte blocks, others need explicit block length setting
if (card->type != CARD_SDHC) {
if (sdTransaction(pdrv, SET_BLOCKLEN, 512, NULL) != 0x00) {
log_w("SET_BLOCKLEN failed");
goto unknown_card;
}
}
// Step 7: Get card capacity and finalize initialization
card->sectors = sdGetSectorsCount(pdrv);
// Limit frequency to 25MHz for compatibility (SD spec maximum for non-UHS cards)
if (card->frequency > 25000000) {
card->frequency = 25000000;
}
// Mark card as initialized
card->status &= ~STA_NOINIT;
return card->status;
unknown_card:
// Mark card as unknown type if initialization failed
card->type = CARD_UNKNOWN;
return card->status;
}
DSTATUS ff_sd_status(uint8_t pdrv) {
ardu_sdcard_t *card = s_cards[pdrv];
AcquireSPI lock(card);
if (sdTransaction(pdrv, SEND_STATUS, 0, NULL)) {
log_e("Check status failed");
return STA_NOINIT;
}
return s_cards[pdrv]->status;
}
DRESULT ff_sd_read(uint8_t pdrv, uint8_t *buffer, DWORD sector, UINT count) {
ardu_sdcard_t *card = s_cards[pdrv];
if (card->status & STA_NOINIT) {
return RES_NOTRDY;
}
DRESULT res = RES_OK;
AcquireSPI lock(card);
if (count > 1) {
res = sdReadSectors(pdrv, (char *)buffer, sector, count) ? RES_OK : RES_ERROR;
} else {
res = sdReadSector(pdrv, (char *)buffer, sector) ? RES_OK : RES_ERROR;
}
return res;
}
DRESULT ff_sd_write(uint8_t pdrv, const uint8_t *buffer, DWORD sector, UINT count) {
ardu_sdcard_t *card = s_cards[pdrv];
if (card->status & STA_NOINIT) {
return RES_NOTRDY;
}
if (card->status & STA_PROTECT) {
return RES_WRPRT;
}
DRESULT res = RES_OK;
AcquireSPI lock(card);
if (count > 1) {
res = sdWriteSectors(pdrv, (const char *)buffer, sector, count) ? RES_OK : RES_ERROR;
} else {
res = sdWriteSector(pdrv, (const char *)buffer, sector) ? RES_OK : RES_ERROR;
}
return res;
}
DRESULT ff_sd_ioctl(uint8_t pdrv, uint8_t cmd, void *buff) {
switch (cmd) {
case CTRL_SYNC:
{
AcquireSPI lock(s_cards[pdrv]);
if (sdSelectCard(pdrv)) {
sdDeselectCard(pdrv);
return RES_OK;
}
}
return RES_ERROR;
case GET_SECTOR_COUNT: *((unsigned long *)buff) = s_cards[pdrv]->sectors; return RES_OK;
case GET_SECTOR_SIZE: *((WORD *)buff) = 512; return RES_OK;
case GET_BLOCK_SIZE: *((uint32_t *)buff) = 1; return RES_OK;
}
return RES_PARERR;
}
bool sd_read_raw(uint8_t pdrv, uint8_t *buffer, DWORD sector) {
return ff_sd_read(pdrv, buffer, sector, 1) == ESP_OK;
}
bool sd_write_raw(uint8_t pdrv, uint8_t *buffer, DWORD sector) {
return ff_sd_write(pdrv, buffer, sector, 1) == ESP_OK;
}
/*
* Public methods
* */
uint8_t sdcard_uninit(uint8_t pdrv) {
ardu_sdcard_t *card = s_cards[pdrv];
if (pdrv >= FF_VOLUMES || card == NULL) {
return 1;
}
{
AcquireSPI lock(card);
sdTransaction(pdrv, GO_IDLE_STATE, 0, NULL);
} // lock is destructed here
ff_diskio_register(pdrv, NULL);
s_cards[pdrv] = NULL;
esp_err_t err = ESP_OK;
if (card->base_path) {
err = esp_vfs_fat_unregister_path(card->base_path);
free(card->base_path);
}
free(card);
return err;
}
uint8_t sdcard_init(uint8_t cs, SPIClass *spi, int hz) {
uint8_t pdrv = 0xFF;
if (ff_diskio_get_drive(&pdrv) != ESP_OK || pdrv == 0xFF) {
return pdrv;
}
ardu_sdcard_t *card = (ardu_sdcard_t *)malloc(sizeof(ardu_sdcard_t));
if (!card) {
return 0xFF;
}
card->base_path = NULL;
card->frequency = hz;
card->spi = spi;
card->ssPin = digitalPinToGPIONumber(cs);
card->supports_crc = true;
card->type = CARD_NONE;
card->status = STA_NOINIT;
pinMode(card->ssPin, OUTPUT);
digitalWrite(card->ssPin, HIGH);
perimanSetPinBusExtraType(card->ssPin, "SD_SS");
s_cards[pdrv] = card;
static const ff_diskio_impl_t sd_impl = {
.init = &ff_sd_initialize, .status = &ff_sd_status, .read = &ff_sd_read, .write = &ff_sd_write, .ioctl = &ff_sd_ioctl
};
ff_diskio_register(pdrv, &sd_impl);
return pdrv;
}
uint8_t sdcard_unmount(uint8_t pdrv) {
ardu_sdcard_t *card = s_cards[pdrv];
if (pdrv >= FF_VOLUMES || card == NULL) {
return 1;
}
card->status |= STA_NOINIT;
card->type = CARD_NONE;
char drv[3] = {(char)('0' + pdrv), ':', 0};
f_mount(NULL, drv, 0);
return 0;
}
bool sdcard_mount(uint8_t pdrv, const char *path, uint8_t max_files, bool format_if_empty) {
ardu_sdcard_t *card = s_cards[pdrv];
if (pdrv >= FF_VOLUMES || card == NULL) {
return false;
}
if (card->base_path) {
free(card->base_path);
}
card->base_path = strdup(path);
FATFS *fs;
char drv[3] = {(char)('0' + pdrv), ':', 0};
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(6, 0, 0)
esp_err_t err = esp_vfs_fat_register(path, drv, max_files, &fs);
#else
esp_vfs_fat_conf_t conf = {.base_path = path, .fat_drive = drv, .max_files = max_files};
esp_err_t err = esp_vfs_fat_register(&conf, &fs);
#endif
if (err == ESP_ERR_INVALID_STATE) {
log_e("esp_vfs_fat_register failed 0x(%x): SD is registered.", err);
return false;
} else if (err != ESP_OK) {
log_e("esp_vfs_fat_register failed 0x(%x)", err);
return false;
}
FRESULT res = f_mount(fs, drv, 1);
if (res != FR_OK) {
log_e("f_mount failed: %s", fferr2str[res]);
if (res == 13 && format_if_empty) {
BYTE *work = (BYTE *)malloc(sizeof(BYTE) * FF_MAX_SS);
if (!work) {
log_e("alloc for f_mkfs failed");
return false;
}
//FRESULT f_mkfs (const TCHAR* path, const MKFS_PARM* opt, void* work, UINT len);
const MKFS_PARM opt = {(BYTE)FM_ANY, 0, 0, 0, 0};
res = f_mkfs(drv, &opt, work, sizeof(BYTE) * FF_MAX_SS);
free(work);
if (res != FR_OK) {
log_e("f_mkfs failed: %s", fferr2str[res]);
esp_vfs_fat_unregister_path(path);
return false;
}
res = f_mount(fs, drv, 1);
if (res != FR_OK) {
log_e("f_mount failed: %s", fferr2str[res]);
esp_vfs_fat_unregister_path(path);
return false;
}
} else {
esp_vfs_fat_unregister_path(path);
return false;
}
}
AcquireSPI lock(card);
card->sectors = sdGetSectorsCount(pdrv);
return true;
}
uint32_t sdcard_num_sectors(uint8_t pdrv) {
ardu_sdcard_t *card = s_cards[pdrv];
if (pdrv >= FF_VOLUMES || card == NULL) {
return 0;
}
return card->sectors;
}
uint32_t sdcard_sector_size(uint8_t pdrv) {
if (pdrv >= FF_VOLUMES || s_cards[pdrv] == NULL) {
return 0;
}
return 512;
}
// roro: the card's CID. CMD10 in SPI mode (the enum's SEND_CID, 2, is the SD-mode command).
namespace roro {
bool sdReadCid(uint8_t cid[16]) {
for (uint8_t pdrv = 0; pdrv < FF_VOLUMES; ++pdrv) {
ardu_sdcard_t *card = s_cards[pdrv];
if (!card) {
continue;
}
AcquireSPI lock(card);
if (!sdSelectCard(pdrv)) {
return false;
}
bool ok = !sdCommand(pdrv, 10, 0, NULL) && sdReadBytes(pdrv, (char *)cid, 16);
sdDeselectCard(pdrv);
return ok;
}
return false;
}
} // namespace roro
sdcard_type_t sdcard_type(uint8_t pdrv) {
ardu_sdcard_t *card = s_cards[pdrv];
if (pdrv >= FF_VOLUMES || card == NULL) {
return CARD_NONE;
}
return card->type;
}
+34
View File
@@ -0,0 +1,34 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _SD_DISKIO_H_
#define _SD_DISKIO_H_
#include "Arduino.h"
#include "SPI.h"
#include "sd_defines.h"
// #include "diskio.h"
uint8_t sdcard_init(uint8_t cs, SPIClass *spi, int hz);
uint8_t sdcard_uninit(uint8_t pdrv);
bool sdcard_mount(uint8_t pdrv, const char *path, uint8_t max_files, bool format_if_empty);
uint8_t sdcard_unmount(uint8_t pdrv);
sdcard_type_t sdcard_type(uint8_t pdrv);
uint32_t sdcard_num_sectors(uint8_t pdrv);
uint32_t sdcard_sector_size(uint8_t pdrv);
bool sd_read_raw(uint8_t pdrv, uint8_t *buffer, uint32_t sector);
bool sd_write_raw(uint8_t pdrv, uint8_t *buffer, uint32_t sector);
#endif /* _SD_DISKIO_H_ */
+61
View File
@@ -0,0 +1,61 @@
/* SD/MMC File System Library
* Copyright (c) 2014 Neil Thiessen
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const char m_CRC7Table[] = {0x00, 0x09, 0x12, 0x1B, 0x24, 0x2D, 0x36, 0x3F, 0x48, 0x41, 0x5A, 0x53, 0x6C, 0x65, 0x7E, 0x77, 0x19, 0x10, 0x0B, 0x02, 0x3D, 0x34,
0x2F, 0x26, 0x51, 0x58, 0x43, 0x4A, 0x75, 0x7C, 0x67, 0x6E, 0x32, 0x3B, 0x20, 0x29, 0x16, 0x1F, 0x04, 0x0D, 0x7A, 0x73, 0x68, 0x61,
0x5E, 0x57, 0x4C, 0x45, 0x2B, 0x22, 0x39, 0x30, 0x0F, 0x06, 0x1D, 0x14, 0x63, 0x6A, 0x71, 0x78, 0x47, 0x4E, 0x55, 0x5C, 0x64, 0x6D,
0x76, 0x7F, 0x40, 0x49, 0x52, 0x5B, 0x2C, 0x25, 0x3E, 0x37, 0x08, 0x01, 0x1A, 0x13, 0x7D, 0x74, 0x6F, 0x66, 0x59, 0x50, 0x4B, 0x42,
0x35, 0x3C, 0x27, 0x2E, 0x11, 0x18, 0x03, 0x0A, 0x56, 0x5F, 0x44, 0x4D, 0x72, 0x7B, 0x60, 0x69, 0x1E, 0x17, 0x0C, 0x05, 0x3A, 0x33,
0x28, 0x21, 0x4F, 0x46, 0x5D, 0x54, 0x6B, 0x62, 0x79, 0x70, 0x07, 0x0E, 0x15, 0x1C, 0x23, 0x2A, 0x31, 0x38, 0x41, 0x48, 0x53, 0x5A,
0x65, 0x6C, 0x77, 0x7E, 0x09, 0x00, 0x1B, 0x12, 0x2D, 0x24, 0x3F, 0x36, 0x58, 0x51, 0x4A, 0x43, 0x7C, 0x75, 0x6E, 0x67, 0x10, 0x19,
0x02, 0x0B, 0x34, 0x3D, 0x26, 0x2F, 0x73, 0x7A, 0x61, 0x68, 0x57, 0x5E, 0x45, 0x4C, 0x3B, 0x32, 0x29, 0x20, 0x1F, 0x16, 0x0D, 0x04,
0x6A, 0x63, 0x78, 0x71, 0x4E, 0x47, 0x5C, 0x55, 0x22, 0x2B, 0x30, 0x39, 0x06, 0x0F, 0x14, 0x1D, 0x25, 0x2C, 0x37, 0x3E, 0x01, 0x08,
0x13, 0x1A, 0x6D, 0x64, 0x7F, 0x76, 0x49, 0x40, 0x5B, 0x52, 0x3C, 0x35, 0x2E, 0x27, 0x18, 0x11, 0x0A, 0x03, 0x74, 0x7D, 0x66, 0x6F,
0x50, 0x59, 0x42, 0x4B, 0x17, 0x1E, 0x05, 0x0C, 0x33, 0x3A, 0x21, 0x28, 0x5F, 0x56, 0x4D, 0x44, 0x7B, 0x72, 0x69, 0x60, 0x0E, 0x07,
0x1C, 0x15, 0x2A, 0x23, 0x38, 0x31, 0x46, 0x4F, 0x54, 0x5D, 0x62, 0x6B, 0x70, 0x79};
char CRC7(const char *data, int length) {
char crc = 0;
for (int i = 0; i < length; i++) {
crc = m_CRC7Table[(crc << 1) ^ data[i]];
}
return crc;
}
const unsigned short m_CRC16Table[256] = {
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273,
0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7,
0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B,
0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF,
0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B,
0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6,
0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C,
0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0,
0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676,
0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D,
0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D,
0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9,
0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
};
unsigned short CRC16(const char *data, int length) {
unsigned short crc = 0;
for (int i = 0; i < length; i++) {
crc = (crc << 8) ^ m_CRC16Table[((crc >> 8) ^ data[i]) & 0x00FF];
}
return crc;
}
+33
View File
@@ -0,0 +1,33 @@
#pragma once
#include <cstdint>
namespace roro {
// Why the SD driver last gave up on a write (see sd_diskio.cpp, issue #21). The framework's driver
// fails without saying why; ours records it.
struct SdFault {
enum Step : uint8_t {
None,
EraseCount, // ACMD23 before a multi-block write was refused
Select, // the card stayed busy for 500 ms
Command, // the write command itself was refused
DataToken, // the card's answer to a data block: 0x0B CRC error, 0x0D write error
BusyAfter, // still busy 500 ms after the last block
Status, // CMD13 after the write reported an error (resp)
StopCommand, // CMD12 after a rejected block was refused
};
Step step = None;
uint8_t token = 0; // the driver's or the card's answer at that step
uint32_t resp = 0; // CMD13's status bits, for Status
uint32_t count = 0; // failed writes since boot
uint32_t retried = 0; // blocks resent after a CRC error, since boot
};
SdFault sdLastFault();
// The mounted card's identity register (CID, CMD10): who made it, its name, serial and date.
// Call it where card access is allowed (the storage task).
bool sdReadCid(uint8_t cid[16]);
} // namespace roro
+53
View File
@@ -0,0 +1,53 @@
#include "sd_card_id.h"
#include <cstdio>
namespace roro {
namespace {
char printable(uint8_t b) { return b >= 0x20 && b < 0x7F ? static_cast<char>(b) : '?'; }
} // namespace
SdCardId parseSdCid(const uint8_t cid[16]) {
SdCardId id;
id.manufacturer = cid[0];
for (int i = 0; i < 2; ++i) id.oem[i] = printable(cid[1 + i]);
for (int i = 0; i < 5; ++i) id.product[i] = printable(cid[3 + i]);
id.revisionMajor = cid[8] >> 4;
id.revisionMinor = cid[8] & 0x0F;
id.serial = static_cast<uint32_t>(cid[9]) << 24 | cid[10] << 16 | cid[11] << 8 | cid[12];
// 4 reserved bits, 8 bits of year since 2000, 4 bits of month.
id.year = 2000 + (((cid[13] & 0x0F) << 4) | (cid[14] >> 4));
id.month = cid[14] & 0x0F;
return id;
}
const char* sdManufacturerName(uint8_t id) {
switch (id) {
case 0x01: return "Panasonic";
case 0x02: return "Toshiba/Kioxia";
case 0x03: return "SanDisk";
case 0x1B: return "Samsung";
case 0x1D: return "ADATA";
case 0x27: return "Phison";
case 0x28: return "Lexar";
case 0x31: return "Silicon Power";
case 0x41: return "Kingston";
case 0x74: return "Transcend";
case 0x76: return "Patriot";
case 0x82: return "Sony";
case 0x9C: return "Angelbird/Hoodman";
case 0xAD: return "Longsys/Lexar";
default: return "unknown";
}
}
std::string sdCardSummary(const SdCardId& id) {
char s[112];
std::snprintf(s, sizeof s, "%s (0x%02X) \"%s\" \"%s\" rev %u.%u, serial %08x, made %04d-%02d",
sdManufacturerName(id.manufacturer), id.manufacturer, id.oem, id.product, id.revisionMajor,
id.revisionMinor, static_cast<unsigned>(id.serial), id.year, id.month);
return s;
}
} // namespace roro
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include <cstdint>
#include <string>
namespace roro {
// What an SD card says it is: its CID register, 16 bytes (SD Physical Layer spec, 5.2).
struct SdCardId {
uint8_t manufacturer = 0; // assigned by the SD Association; see sdManufacturerName()
char oem[3] = {}; // two characters
char product[6] = {}; // five characters
uint8_t revisionMajor = 0, revisionMinor = 0;
uint32_t serial = 0;
int year = 0, month = 0; // manufactured
};
SdCardId parseSdCid(const uint8_t cid[16]);
// The usual holder of a manufacturer ID. The SD Association doesn't publish its list: these are
// the commonly reported ones, and resold or counterfeit cards carry whatever their maker chose.
const char* sdManufacturerName(uint8_t id);
// "SanDisk (0x03) "SD" "SU08G" rev 8.0, serial 1234abcd, made 2014-03".
std::string sdCardSummary(const SdCardId& id);
} // namespace roro
+17 -3
View File
@@ -23,6 +23,8 @@
#include "platform/crash_report.h"
#include "platform/nvs_store.h"
#include "platform/system_info.h"
#include "sd_card_id.h"
#include "sd_fault.h"
#include "service_manager.h"
#include "platform/identity.h"
#include "services/battery_service.h"
@@ -375,7 +377,7 @@ static const char* const kHelp =
"gemini get <url> fetch a Gemini page and report header, size, certificate, heap\n"
"irc start | irc stop | irc dump | irc say <buffer> <text>\n"
"ls [folder] | rm <path> | install <path.ota> (Update from SD)\n"
"sd list | cat <path> | log <text> | burst | sound on|off | short | normal\n"
"sd card | sd list | cat <path> | log <text> | burst | sound on|off | short | normal\n"
#ifdef RORO_DEBUG
"crash abort|wdt crash on purpose (to test crash reports and Safe Mode)\n"
"lora inject <hex> [rssi] [snr] a packet into the LoRa Scanner as if received (nothing is sent)\n"
@@ -400,8 +402,8 @@ static void runCommand(String line) {
if (line == "help") console.print(kHelp);
if (line == "info") {
system_info::printSystem(console);
console.printf("wifi: %s, ip %s, rssi %d | sd: %s\n", wifi->ssid().c_str(), wifi->ip().c_str(), wifi->rssi(),
storageService->state().present ? "present" : "none");
console.printf("wifi: %s, ip %s, rssi %d | sd: %s, %u write faults\n", wifi->ssid().c_str(), wifi->ip().c_str(),
wifi->rssi(), storageService->state().present ? "present" : "none", (unsigned)sdLastFault().count);
console.printf("update: %s\n", update->onProbation() ? "on probation" : "confirmed");
system_info::printSlots(console, nvs);
}
@@ -537,6 +539,18 @@ static void runCommand(String line) {
}
}
if (line.startsWith("sd put ")) startUpload(line.substring(7)); // then raw bytes: see uploadStep()
if (line == "sd card") { // what the card says it is, from its CID register
if (!storageService->state().present) return (void)console.println("sd card: no card");
storageService->runJob([]() {
uint8_t cid[16];
const char* type = SD.cardType() == CARD_SDHC ? "SDHC/SDXC" : SD.cardType() == CARD_SD ? "SDSC" : "MMC or unknown";
if (!sdReadCid(cid)) return (void)console.println("sd card: the card didn't answer");
console.printf("sd card: %s, %.1f GB, %s\n", type, SD.cardSize() / 1e9, sdCardSummary(parseSdCid(cid)).c_str());
console.printf("sd card: CID");
for (uint8_t b : cid) console.printf(" %02x", b);
console.println();
});
}
if (line == "sd list") {
storageService->requestListing();
listingWanted = true;
+10
View File
@@ -17,6 +17,7 @@
#include "file_receiver.h"
#include "sha256.h"
#include "platform/console.h"
#include "sd_fault.h"
#include "version.h"
namespace roro {
@@ -232,7 +233,16 @@ void DebugConsole::put(NetworkClient& client, const std::string& args) {
while (r.state() == S::Receiving || r.state() == S::Writing) {
if (r.state() == S::Writing) {
const auto& c = r.chunk();
uint32_t t0 = millis();
bool ok = f.write(c.data(), c.size()) == c.size();
uint32_t took = millis() - t0;
// Why, from our copy of the SD driver (lib/SD, #21): the step that gave up and what
// the card answered. How long it took tells a 500 ms busy timeout from a refusal.
if (!ok) {
SdFault sd = sdLastFault();
console.printf("put: write failed at %u after %u ms: SD step %u, answer 0x%02X, status 0x%04X (%u so far)\n",
(unsigned)r.received(), (unsigned)took, sd.step, sd.token, (unsigned)sd.resp, (unsigned)sd.count);
}
// A card can fail one write and take the next. FATFS keeps a failed file in error,
// so: close, cut back to the last good byte, reopen, try again. Earlier chunks may
// have been lost with the write buffer (M3: 3 KB came back as zeros): never extend
+2 -2
View File
@@ -166,7 +166,7 @@ void StorageService::format() {
mounted_ = false;
bool ok = false;
uint8_t pdrv = sdcard_init(pins::kSdCs, &sharedSpi(), 20000000);
uint8_t pdrv = sdcard_init(pins::kSdCs, &sharedSpi(), kSdHz);
if (pdrv != 0xFF) {
constexpr size_t kWorkSize = 4096; // FF_MAX_SS
std::unique_ptr<uint8_t[]> work(new uint8_t[kWorkSize]);
@@ -182,7 +182,7 @@ void StorageService::format() {
}
bool StorageService::mount() {
return SD.begin(pins::kSdCs, sharedSpi(), 20000000, "/sd", 5, false);
return SD.begin(pins::kSdCs, sharedSpi(), kSdHz, "/sd", 5, false);
}
void StorageService::poll() {
+1
View File
@@ -56,6 +56,7 @@ class StorageService : public Service {
static constexpr uint32_t kWakeMs = 1000;
static constexpr uint32_t kPollEvery = 15; // wake-ups between usage checks
static constexpr size_t kMaxPendingBytes = 16 * 1024;
static constexpr uint32_t kSdHz = 20000000; // the card's SPI clock; 10 MHz changed nothing (ADR 0007)
static void taskEntry(void* self);
void loop();
+54
View File
@@ -0,0 +1,54 @@
#include <unity.h>
#include "sd_card_id.h"
using namespace roro;
void setUp() {}
void tearDown() {}
// A SanDisk 8 GB card's CID: manufacturer 0x03, OEM "SD", product "SU08G", revision 8.0,
// serial 0x1234ABCD, made in March 2014.
static const uint8_t kSanDisk[16] = {0x03, 'S', 'D', 'S', 'U', '0', '8', 'G', 0x80,
0x12, 0x34, 0xAB, 0xCD, 0x00, 0xE3, 0xFF};
void test_fields() {
SdCardId id = parseSdCid(kSanDisk);
TEST_ASSERT_EQUAL_HEX8(0x03, id.manufacturer);
TEST_ASSERT_EQUAL_STRING("SD", id.oem);
TEST_ASSERT_EQUAL_STRING("SU08G", id.product);
TEST_ASSERT_EQUAL_UINT8(8, id.revisionMajor);
TEST_ASSERT_EQUAL_UINT8(0, id.revisionMinor);
TEST_ASSERT_EQUAL_HEX32(0x1234ABCD, id.serial);
TEST_ASSERT_EQUAL_INT(2014, id.year);
TEST_ASSERT_EQUAL_INT(3, id.month);
}
// Clone cards put anything in the name: unprintable bytes mustn't reach the console.
void test_unprintable_names() {
uint8_t cid[16] = {0x00, 0x00, 0x01, 'A', 0x00, 0xFF, 'B', ' ', 0x10};
SdCardId id = parseSdCid(cid);
TEST_ASSERT_EQUAL_STRING("??", id.oem);
TEST_ASSERT_EQUAL_STRING("A??B ", id.product);
TEST_ASSERT_EQUAL_UINT8(1, id.revisionMajor);
}
void test_manufacturer_names() {
TEST_ASSERT_EQUAL_STRING("SanDisk", sdManufacturerName(0x03));
TEST_ASSERT_EQUAL_STRING("Samsung", sdManufacturerName(0x1B));
TEST_ASSERT_EQUAL_STRING("unknown", sdManufacturerName(0xEE));
}
void test_summary_line() {
TEST_ASSERT_EQUAL_STRING("SanDisk (0x03) \"SD\" \"SU08G\" rev 8.0, serial 1234abcd, made 2014-03",
sdCardSummary(parseSdCid(kSanDisk)).c_str());
}
int main() {
UNITY_BEGIN();
RUN_TEST(test_fields);
RUN_TEST(test_unprintable_names);
RUN_TEST(test_manufacturer_names);
RUN_TEST(test_summary_line);
return UNITY_END();
}