862 lines
23 KiB
C++
862 lines
23 KiB
C++
// 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 "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);
|
|
}
|
|
|
|
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;
|
|
|
|
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);
|
|
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) {
|
|
for (int f = 0; f < 3; f++) {
|
|
if (!sdSelectCard(pdrv)) {
|
|
return false;
|
|
}
|
|
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 false;
|
|
}
|
|
|
|
unsigned int resp;
|
|
if (sdTransaction(pdrv, SEND_STATUS, 0, &resp) || resp) {
|
|
return false;
|
|
}
|
|
return true;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
sdDeselectCard(pdrv);
|
|
return false;
|
|
}
|
|
|
|
bool sdWriteSectors(uint8_t pdrv, const char *buffer, unsigned long long sector, int count) {
|
|
char token;
|
|
const char *currentBuffer = buffer;
|
|
unsigned long long currentSector = sector;
|
|
int currentCount = count;
|
|
ardu_sdcard_t *card = s_cards[pdrv];
|
|
|
|
for (int f = 0; f < 3;) {
|
|
if (card->type != CARD_MMC) {
|
|
if (sdTransaction(pdrv, SET_WR_BLK_ERASE_COUNT, currentCount, NULL)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!sdSelectCard(pdrv)) {
|
|
return false;
|
|
}
|
|
|
|
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)) {
|
|
break;
|
|
}
|
|
|
|
if (currentCount == 0) {
|
|
sdStop(pdrv);
|
|
sdDeselectCard(pdrv);
|
|
|
|
unsigned int resp;
|
|
if (sdTransaction(pdrv, SEND_STATUS, 0, &resp) || resp) {
|
|
return false;
|
|
}
|
|
return true;
|
|
} else {
|
|
if (sdCommand(pdrv, STOP_TRANSMISSION, 0, NULL)) {
|
|
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 {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
sdDeselectCard(pdrv);
|
|
return false;
|
|
}
|
|
|
|
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:
|
|
* 1. Power-up sequence with 74+ clock cycles
|
|
* 2. GO_IDLE_STATE command to reset the card
|
|
* 3. CRC_ON_OFF to enable/disable CRC checking
|
|
* 4. SEND_IF_COND to identify SDHC/SDXC cards
|
|
* 5. APP_OP_COND to set operating conditions
|
|
* 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: Select the card and send GO_IDLE_STATE command
|
|
// This command resets the card to idle state and enables SPI mode
|
|
// Fix mount issue - sdWait fail ignored before command GO_IDLE_STATE
|
|
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);
|
|
|
|
// Step 3: Configure CRC checking
|
|
// Enable CRC for data transfers in SPI mode (required for reliable communication)
|
|
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: %03X", 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
|
|
// Wait up to 1 second for the card to become ready
|
|
start = millis();
|
|
do {
|
|
token = sdTransaction(pdrv, APP_OP_COND, 0x40100000, NULL);
|
|
} while (token == 1 && (millis() - start) < 1000);
|
|
|
|
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
|
|
start = millis();
|
|
do {
|
|
token = sdTransaction(pdrv, APP_OP_COND, 0x100000, NULL);
|
|
} while (token == 0x01 && (millis() - start) < 1000);
|
|
|
|
if (!token) {
|
|
card->type = CARD_SD; // Standard SD card
|
|
} else {
|
|
// Try MMC card initialization
|
|
start = millis();
|
|
do {
|
|
token = sdTransaction(pdrv, SEND_OP_COND, 0x100000, NULL);
|
|
} while (token != 0x00 && (millis() - start) < 1000);
|
|
|
|
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};
|
|
esp_err_t err = esp_vfs_fat_register(path, drv, max_files, &fs);
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|