444 lines
9.8 KiB
C++
444 lines
9.8 KiB
C++
#include "sd_manager.h"
|
|
|
|
#include "storage_config.h"
|
|
|
|
#include <driver/gpio.h>
|
|
|
|
// Both backends are compiled into the image so that the SDIO path is
|
|
// compile-checked even while the system still runs SPI for bring-up.
|
|
#include <SD.h>
|
|
#include <SD_MMC.h>
|
|
|
|
namespace
|
|
{
|
|
|
|
class SpiBackend : public StorageBackend
|
|
{
|
|
public:
|
|
bool begin() override
|
|
{
|
|
// Explicit SPI instance so the pin wiring is driven from
|
|
// storage_config.h rather than the board defaults. The second
|
|
// spi.begin() call made inside SDFS::begin() is a no-op because the
|
|
// bus is already started with these pins.
|
|
static SPIClass spi;
|
|
|
|
spi.begin(
|
|
STORAGE_SPI_SCK,
|
|
STORAGE_SPI_MISO,
|
|
STORAGE_SPI_MOSI,
|
|
STORAGE_SPI_CS
|
|
);
|
|
|
|
// Cheap breakout modules often omit the pull-up resistors the SD
|
|
// spec expects on the idle-high lines. The ESP32 SPI HAL clears the
|
|
// internal pull-ups when it attaches a pin (esp32-hal-spi.c), so a
|
|
// floating MISO/CS means the card never answers CMD0 during init
|
|
// ("Card Failed! cmd: 0x00"). Re-enable the pull-ups directly so we
|
|
// do not disturb the pin's peripheral function.
|
|
gpio_pullup_en((gpio_num_t)STORAGE_SPI_MISO);
|
|
gpio_pullup_en((gpio_num_t)STORAGE_SPI_CS);
|
|
|
|
// Give the card a moment to stabilize after power-on before the init
|
|
// handshake starts (the framework sends 74+ dummy clocks, but some
|
|
// cards need a little more settling time on a fresh mount attempt).
|
|
delay(20);
|
|
|
|
return SD.begin(
|
|
STORAGE_SPI_CS,
|
|
spi,
|
|
STORAGE_SPI_FREQ,
|
|
STORAGE_MOUNT_POINT,
|
|
STORAGE_MAX_OPEN_FILES,
|
|
false // format_if_empty: never auto-format
|
|
);
|
|
}
|
|
|
|
void end() override
|
|
{
|
|
SD.end();
|
|
}
|
|
|
|
fs::FS& fs() override
|
|
{
|
|
return SD;
|
|
}
|
|
|
|
StorageCardType cardType() override
|
|
{
|
|
return mapType(SD.cardType());
|
|
}
|
|
|
|
uint64_t totalBytes() override
|
|
{
|
|
return SD.totalBytes();
|
|
}
|
|
|
|
uint64_t usedBytes() override
|
|
{
|
|
return SD.usedBytes();
|
|
}
|
|
|
|
private:
|
|
static StorageCardType mapType(sdcard_type_t type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case CARD_MMC: return StorageCardType::MMC;
|
|
case CARD_SD: return StorageCardType::SD;
|
|
case CARD_SDHC: return StorageCardType::SDHC;
|
|
case CARD_NONE: return StorageCardType::None;
|
|
default: return StorageCardType::Unknown;
|
|
}
|
|
}
|
|
};
|
|
|
|
class SdmmcBackend : public StorageBackend
|
|
{
|
|
public:
|
|
bool begin() override
|
|
{
|
|
// The plain esp32dev variant does not pre-wire the SDMMC pins, so
|
|
// they must always be set explicitly. The classic ESP32 routes the
|
|
// SDMMC peripheral through the GPIO matrix, so the pins defined in
|
|
// storage_config.h are fully re-routable.
|
|
if (!SD_MMC.setPins(
|
|
STORAGE_SDMMC_CLK,
|
|
STORAGE_SDMMC_CMD,
|
|
STORAGE_SDMMC_D0,
|
|
STORAGE_SDMMC_D1,
|
|
STORAGE_SDMMC_D2,
|
|
STORAGE_SDMMC_D3))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return SD_MMC.begin(
|
|
STORAGE_MOUNT_POINT,
|
|
STORAGE_SDMMC_MODE_1BIT, // false == 4-bit bus
|
|
STORAGE_SDMMC_FORMAT_IF_FAILED, // never auto-format
|
|
STORAGE_SDMMC_FREQ_HZ, // 40 MHz == SDMMC_FREQ_HIGHSPEED
|
|
STORAGE_MAX_OPEN_FILES
|
|
);
|
|
}
|
|
|
|
void end() override
|
|
{
|
|
SD_MMC.end();
|
|
}
|
|
|
|
fs::FS& fs() override
|
|
{
|
|
return SD_MMC;
|
|
}
|
|
|
|
StorageCardType cardType() override
|
|
{
|
|
return mapType(SD_MMC.cardType());
|
|
}
|
|
|
|
uint64_t totalBytes() override
|
|
{
|
|
return SD_MMC.totalBytes();
|
|
}
|
|
|
|
uint64_t usedBytes() override
|
|
{
|
|
return SD_MMC.usedBytes();
|
|
}
|
|
|
|
private:
|
|
static StorageCardType mapType(sdcard_type_t type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case CARD_MMC: return StorageCardType::MMC;
|
|
case CARD_SD: return StorageCardType::SD;
|
|
case CARD_SDHC: return StorageCardType::SDHC;
|
|
case CARD_NONE: return StorageCardType::None;
|
|
default: return StorageCardType::Unknown;
|
|
}
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
|
|
SDManager::SDManager()
|
|
:
|
|
backend(nullptr),
|
|
mounted(false)
|
|
{
|
|
}
|
|
|
|
|
|
bool SDManager::begin()
|
|
{
|
|
if (mounted)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
#if STORAGE_IFACE == STORAGE_IFACE_SDMMC
|
|
backend = new SdmmcBackend();
|
|
#else
|
|
backend = new SpiBackend();
|
|
#endif
|
|
|
|
if (backend == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
mounted = backend->begin();
|
|
|
|
if (!mounted)
|
|
{
|
|
Serial.println("[Storage] SD card mount FAILED");
|
|
delete backend;
|
|
backend = nullptr;
|
|
}
|
|
|
|
return mounted;
|
|
}
|
|
|
|
|
|
void SDManager::end()
|
|
{
|
|
if (backend != nullptr)
|
|
{
|
|
backend->end();
|
|
delete backend;
|
|
backend = nullptr;
|
|
}
|
|
|
|
mounted = false;
|
|
}
|
|
|
|
|
|
bool SDManager::isMounted() const
|
|
{
|
|
return mounted;
|
|
}
|
|
|
|
|
|
fs::FS& SDManager::fs()
|
|
{
|
|
return backend->fs();
|
|
}
|
|
|
|
|
|
StorageCardType SDManager::cardType() const
|
|
{
|
|
if (mounted && backend != nullptr)
|
|
{
|
|
return backend->cardType();
|
|
}
|
|
return StorageCardType::None;
|
|
}
|
|
|
|
|
|
const char* SDManager::cardTypeName() const
|
|
{
|
|
return cardTypeName(cardType());
|
|
}
|
|
|
|
|
|
uint64_t SDManager::totalBytes() const
|
|
{
|
|
if (mounted && backend != nullptr)
|
|
{
|
|
return backend->totalBytes();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
uint64_t SDManager::usedBytes() const
|
|
{
|
|
if (mounted && backend != nullptr)
|
|
{
|
|
return backend->usedBytes();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
uint64_t SDManager::freeBytes() const
|
|
{
|
|
uint64_t total = totalBytes();
|
|
uint64_t used = usedBytes();
|
|
return (total > used) ? (total - used) : 0;
|
|
}
|
|
|
|
|
|
uint32_t SDManager::measureWriteSpeed()
|
|
{
|
|
if (!mounted || backend == nullptr)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
fs::FS& files = backend->fs();
|
|
|
|
const char* scratchPath = STORAGE_SPEED_MEASURE_PATH;
|
|
const size_t bufferSize = 16 * 1024;
|
|
|
|
// Remove any leftover from a previous crashed run.
|
|
files.remove(scratchPath);
|
|
|
|
uint8_t* buffer = (uint8_t*)malloc(bufferSize);
|
|
if (buffer == nullptr)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
memset(buffer, 0xA5, bufferSize);
|
|
|
|
File file = files.open(scratchPath, FILE_WRITE);
|
|
if (!file)
|
|
{
|
|
free(buffer);
|
|
return 0;
|
|
}
|
|
|
|
uint32_t remaining = STORAGE_SPEED_MEASURE_BYTES;
|
|
uint32_t startUs = micros();
|
|
|
|
while (remaining > 0)
|
|
{
|
|
size_t toWrite = (remaining < bufferSize) ? remaining : bufferSize;
|
|
size_t written = file.write(buffer, toWrite);
|
|
|
|
if (written == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
remaining -= written;
|
|
}
|
|
|
|
file.close();
|
|
|
|
uint32_t elapsedUs = micros() - startUs;
|
|
Serial.print("Elapsed microseconds: ");
|
|
Serial.println(elapsedUs);
|
|
|
|
files.remove(scratchPath);
|
|
free(buffer);
|
|
|
|
if (remaining != 0 || elapsedUs == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
uint64_t writtenBytes = (uint64_t)STORAGE_SPEED_MEASURE_BYTES - remaining;
|
|
return (uint32_t)((writtenBytes * 1000000ULL) / elapsedUs);
|
|
}
|
|
|
|
|
|
const char* SDManager::cardTypeName(StorageCardType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case StorageCardType::MMC: return "MMC";
|
|
case StorageCardType::SD: return "SD";
|
|
case StorageCardType::SDHC: return "SDHC";
|
|
case StorageCardType::Unknown: return "Unknown";
|
|
case StorageCardType::None:
|
|
default: return "None";
|
|
}
|
|
}
|
|
|
|
|
|
void SDManager::printCardInfo()
|
|
{
|
|
if (!mounted)
|
|
{
|
|
Serial.println("[Storage] Card info unavailable (not mounted)");
|
|
return;
|
|
}
|
|
|
|
uint64_t total = backend->totalBytes();
|
|
uint64_t used = backend->usedBytes();
|
|
uint64_t free = (total > used) ? (total - used) : 0;
|
|
|
|
Serial.println("----------------------------------------");
|
|
Serial.println("SD card information");
|
|
Serial.println("----------------------------------------");
|
|
Serial.printf(" Type : %s\n", cardTypeName(backend->cardType()));
|
|
Serial.printf(" Total : %llu bytes\n", (unsigned long long)total);
|
|
Serial.printf(" Used : %llu bytes\n", (unsigned long long)used);
|
|
Serial.printf(" Free : %llu bytes\n", (unsigned long long)free);
|
|
Serial.println("----------------------------------------");
|
|
}
|
|
|
|
|
|
void SDManager::listFiles()
|
|
{
|
|
if (!mounted)
|
|
{
|
|
Serial.println("[Storage] Cannot list files (not mounted)");
|
|
return;
|
|
}
|
|
|
|
Serial.println("Files on SD card:");
|
|
Serial.println("----------------------------------------");
|
|
|
|
char path[256];
|
|
snprintf(path, sizeof(path), "/");
|
|
|
|
listFilesRecursive(backend->fs(), path, sizeof(path), 0);
|
|
|
|
Serial.println("----------------------------------------");
|
|
Serial.println("End of listing");
|
|
}
|
|
|
|
|
|
void SDManager::listFilesRecursive(fs::FS& files, char* path, size_t pathSize, uint8_t depth)
|
|
{
|
|
if (depth > STORAGE_LIST_MAX_DEPTH)
|
|
{
|
|
Serial.printf(" ... (max depth %u reached)\n", STORAGE_LIST_MAX_DEPTH);
|
|
return;
|
|
}
|
|
|
|
File dir = files.open(path);
|
|
|
|
if (!dir)
|
|
{
|
|
Serial.printf(" [error] cannot open: %s\n", path);
|
|
return;
|
|
}
|
|
|
|
if (!dir.isDirectory())
|
|
{
|
|
Serial.printf(" %s (%llu bytes)\n", path, (unsigned long long)dir.size());
|
|
dir.close();
|
|
return;
|
|
}
|
|
|
|
File entry;
|
|
while ((entry = dir.openNextFile()))
|
|
{
|
|
size_t base = strlen(path);
|
|
|
|
if (entry.isDirectory())
|
|
{
|
|
snprintf(path + base, pathSize - base, "/%s", entry.name());
|
|
Serial.printf(" %s/\n", path);
|
|
listFilesRecursive(files, path, pathSize, depth + 1);
|
|
path[base] = '\0';
|
|
}
|
|
else
|
|
{
|
|
snprintf(path + base, pathSize - base, "/%s", entry.name());
|
|
Serial.printf(" %s (%llu bytes)\n", path, (unsigned long long)entry.size());
|
|
}
|
|
|
|
entry.close();
|
|
}
|
|
|
|
dir.close();
|
|
}
|