feat: add universal microSD diagnostics

This commit is contained in:
Michael A. Cojocari committed 2026-09-13 18:40:11 -04:00
1 parent 16d09696ca
commit db11fcf2c5
6 files changed
+197 -40

No files matched your search

+29 -8
View File
@@ -1028,11 +1028,19 @@ void setup() {
// edge shows up as a mid-session "sdmmc_host_wait_for_event returned 0x107"
// (ESP_ERR_TIMEOUT) that takes prefs, contacts and chat history down with it. The
// T-Display P4 hot-insert path already drops to this rate for the same reason.
sdMountDiagBegin();
bool wio_l2_sd_begun = false;
if (WioTrackerL2Io::ready() && WioTrackerL2Io::setSdPower(true) &&
SD_MMC.setPins(2, 3, 1) &&
(wio_l2_sd_begun = SD_MMC.begin("/sdcard", true, false, SDMMC_FREQ_DEFAULT)) &&
SD_MMC.cardType() != CARD_NONE) {
const bool wio_l2_sd_bus_ready = WioTrackerL2Io::ready() &&
WioTrackerL2Io::setSdPower(true) &&
SD_MMC.setPins(2, 3, 1);
if (wio_l2_sd_bus_ready)
wio_l2_sd_begun = SD_MMC.begin("/sdcard", true, false, SDMMC_FREQ_DEFAULT);
const bool wio_l2_sd_ready = wio_l2_sd_begun && SD_MMC.cardType() != CARD_NONE;
if (wio_l2_sd_bus_ready)
sdMountDiagAttempt((uint32_t)SDMMC_FREQ_DEFAULT * 1000u, wio_l2_sd_begun, wio_l2_sd_ready);
sdMountDiagSetMounted(wio_l2_sd_ready,
wio_l2_sd_ready ? (uint32_t)SDMMC_FREQ_DEFAULT * 1000u : 0);
if (wio_l2_sd_ready) {
sd_storage = store.useSdMmcStorage();
g_contacts_on_sd = sd_storage;
g_full_data_on_sd = sd_storage;
@@ -1087,6 +1095,7 @@ void setup() {
#endif
bool sd_mounted = false;
#if defined(TLORA_PAGER)
sdMountDiagBegin();
if (!_spi) {
Serial.println("[BOOT] SD: shared SPI unavailable");
} else if (!board.sdCardPresent()) {
@@ -1099,6 +1108,7 @@ void setup() {
Serial.println("[BOOT] SD: card detected; mounting at 4 MHz");
const bool sd_begin_ok = SD.begin(PIN_SD_CS, *_spi, 4000000, "/sd", 6);
sd_mounted = sd_begin_ok && SD.cardType() != CARD_NONE;
sdMountDiagAttempt(4000000, sd_begin_ok, sd_mounted);
if (!sd_mounted) {
if (sd_begin_ok) {
// Release only the unusable mount created above. SD.end() unregisters
@@ -1112,7 +1122,10 @@ void setup() {
}
Serial.printf("[BOOT] SD mount: %s\n", sd_mounted ? "ok" : "failed");
}
sdMountDiagSetMounted(sd_mounted, sd_mounted ? 4000000 : 0);
#else
sdMountDiagBegin();
uint32_t mounted_hz = 0;
if (_spi) {
// Try to mount the card on EVERY boot: even a device that keeps identity on SPIFFS
// wants its churn-heavy contacts/channels on the card.
@@ -1131,11 +1144,13 @@ void setup() {
{ 300, 1000000 }, { 450, 1000000 }, { 650, 400000 }, { 900, 400000 },
};
int tries = want_full_sd ? 7 : 3;
uint32_t mounted_hz = 0;
for (int a = 0; a < tries && !sd_mounted; ++a) {
SD.end();
delay(kBootMount[a].settle_ms);
if (SD.begin(PIN_SD_CS, *_spi, kBootMount[a].hz, "/sd", 6) && SD.cardType() != CARD_NONE) {
const bool begin_ok = SD.begin(PIN_SD_CS, *_spi, kBootMount[a].hz, "/sd", 6);
const bool card_ready = begin_ok && SD.cardType() != CARD_NONE;
sdMountDiagAttempt(kBootMount[a].hz, begin_ok, card_ready);
if (card_ready) {
sd_mounted = true;
mounted_hz = kBootMount[a].hz;
}
@@ -1151,13 +1166,18 @@ void setup() {
if (sd_mounted && mounted_hz < 4000000) {
SD.end();
delay(60);
if (SD.begin(PIN_SD_CS, *_spi, 4000000, "/sd", 6) && SD.cardType() != CARD_NONE) {
const bool fast_begin_ok = SD.begin(PIN_SD_CS, *_spi, 4000000, "/sd", 6);
const bool fast_ready = fast_begin_ok && SD.cardType() != CARD_NONE;
sdMountDiagAttempt(4000000, fast_begin_ok, fast_ready);
if (fast_ready) {
Serial.printf("[BOOT] SD renegotiated %lu -> 4000000 Hz\n", (unsigned long)mounted_hz);
mounted_hz = 4000000;
} else {
SD.end();
delay(120);
sd_mounted = SD.begin(PIN_SD_CS, *_spi, mounted_hz, "/sd", 6) && SD.cardType() != CARD_NONE;
const bool fallback_begin_ok = SD.begin(PIN_SD_CS, *_spi, mounted_hz, "/sd", 6);
sd_mounted = fallback_begin_ok && SD.cardType() != CARD_NONE;
sdMountDiagAttempt(mounted_hz, fallback_begin_ok, sd_mounted);
if (sd_mounted) Serial.printf("[BOOT] SD stays at %lu Hz (4 MHz renegotiation failed)\n", (unsigned long)mounted_hz);
}
}
@@ -1165,6 +1185,7 @@ void setup() {
if (sd_mounted) { mounted_hz = sdTryFastClock(PIN_SD_CS, *_spi, mounted_hz, "BOOT"); sd_mounted = mounted_hz != 0; }
if (sd_mounted) { extern uint32_t g_sd_operating_hz; g_sd_operating_hz = mounted_hz; } // About-page readout (UITask.cpp)
}
sdMountDiagSetMounted(sd_mounted, mounted_hz);
#endif
if (sd_mounted) {
g_contacts_on_sd = true; // every branch below routes contacts/channels to the card
+143 -29
View File
@@ -2484,6 +2484,49 @@ static lv_color_t* g_draw_buffer = nullptr;
// every mount/remount site (main.cpp boot adoption + the two UITask mounts) because SD.begin's
// clock is the session clock; Settings -> About reports it on the R8 (no serial console there).
uint32_t g_sd_operating_hz = 0;
// Most supported boards expose no card-detect GPIO, so a completed filesystem
// mount is the only positive proof that a card is connected. Preserve existing
// mount attempts for the on-device diagnostic instead of issuing extra probes.
struct SdMountDiagState {
bool checked = false;
bool mounted = false;
bool last_begin_ok = false;
uint8_t attempts = 0;
uint32_t last_attempt_hz = 0;
uint32_t mounted_hz = 0;
};
static volatile SdMountDiagState s_sd_diag;
void sdMountDiagBegin() {
s_sd_diag.checked = true;
s_sd_diag.mounted = false;
s_sd_diag.last_begin_ok = false;
s_sd_diag.attempts = 0;
s_sd_diag.last_attempt_hz = 0;
s_sd_diag.mounted_hz = 0;
}
void sdMountDiagAttempt(uint32_t hz, bool begin_ok, bool card_ready) {
s_sd_diag.checked = true;
if (s_sd_diag.attempts < 0xFF) ++s_sd_diag.attempts;
s_sd_diag.last_attempt_hz = hz;
s_sd_diag.last_begin_ok = begin_ok;
s_sd_diag.mounted = card_ready;
if (card_ready) s_sd_diag.mounted_hz = hz;
}
void sdMountDiagSetMounted(bool mounted, uint32_t hz) {
s_sd_diag.checked = true;
s_sd_diag.mounted = mounted;
if (!mounted) s_sd_diag.mounted_hz = 0;
else if (hz) s_sd_diag.mounted_hz = hz;
}
static void sdDiagClockText(uint32_t hz, char* out, size_t cap) {
if (hz >= 1000000) snprintf(out, cap, "%lu MHz", (unsigned long)(hz / 1000000));
else if (hz > 0) snprintf(out, cap, "%lu kHz", (unsigned long)(hz / 1000));
else snprintf(out, cap, "unknown");
}
static uint32_t g_draw_buf_px = 240 * LV_DRAW_BUF_LINES; // actual buffer size in px; shrinks if the full alloc fails at boot
#if CAP_LARGE_SCREEN
// UI resolution scaling (Tanmatsu, no touchscreen). LVGL renders at s_lv_pw x s_lv_ph (PHYSICAL
@@ -12431,11 +12474,11 @@ static void sysInfoTextRest(char* buf, size_t cap) {
(unsigned)(sketch_size / 1024u),
(unsigned)(sketch_free / 1024u));
#if CAP_SD || defined(TLORA_PAGER)
// microSD size + free. The FAT scan (usedBytes()) is done by the core-0 worker — never
// on this UI thread — so the About sheet can't freeze on it. We just read the worker's
// cached result here and request a (re)scan at most every 30 s.
#if CAP_MICROSD
{
#if CAP_SD
// SPI-SD size + free. This existing FAT scan stays on the core-0 worker so
// opening About cannot freeze the UI; SD_MMC builds remain fully passive.
static uint32_t sd_req_ms = 0;
const uint32_t nowm = millis();
// Once armed, only the worker may clear this request (after publishing
@@ -12446,16 +12489,56 @@ static void sysInfoTextRest(char* buf, size_t cap) {
if (ensureTileFetchTaskRunning())
s_sdinfo_request = true; // worker rescans off-thread
}
if (!s_sdinfo_done)
p += snprintf(buf + p, cap - p, "microSD\n \xE2\x80\xA6\n\n"); // … (computing)
else if (s_sdinfo_ok)
#endif
char sd_clock[16];
sdDiagClockText(s_sd_diag.mounted ? s_sd_diag.mounted_hz
: s_sd_diag.last_attempt_hz,
sd_clock, sizeof sd_clock);
if (!s_sd_diag.checked) {
p += snprintf(buf + p, cap - p, "microSD\n status: not checked\n\n");
} else if (!s_sd_diag.mounted && s_sd_diag.attempts == 0) {
p += snprintf(buf + p, cap - p,
"microSD\n size: %llu MB\n free: %llu MB\n\n",
"microSD\n status: not mounted\n mount was not attempted\n\n");
} else if (!s_sd_diag.mounted) {
p += snprintf(buf + p, cap - p,
"microSD\n status: not mounted (missing or unreadable)\n"
" last mount: %u attempt%s, %s\n last init: %s\n\n",
(unsigned)s_sd_diag.attempts,
s_sd_diag.attempts == 1 ? "" : "s",
sd_clock, s_sd_diag.last_begin_ok ? "ok" : "failed");
#if CAP_SD
} else if (!s_sdinfo_done) {
p += snprintf(buf + p, cap - p,
"microSD\n status: connected / mounted\n"
" mount: %u attempt%s @ %s\n size: checking...\n\n",
(unsigned)s_sd_diag.attempts,
s_sd_diag.attempts == 1 ? "" : "s", sd_clock);
} else if (s_sdinfo_ok) {
p += snprintf(buf + p, cap - p,
"microSD\n status: connected / mounted\n"
" mount: %u attempt%s @ %s\n size: %llu MB\n free: %llu MB\n\n",
(unsigned)s_sd_diag.attempts,
s_sd_diag.attempts == 1 ? "" : "s", sd_clock,
(unsigned long long)(s_sdinfo_tot / (1024ull * 1024ull)),
(unsigned long long)(s_sdinfo_free / (1024ull * 1024ull)));
else
p += snprintf(buf + p, cap - p, "microSD\n not mounted\n\n");
} else {
p += snprintf(buf + p, cap - p,
"microSD\n status: mounted, capacity read failed\n"
" mount: %u attempt%s @ %s\n\n",
(unsigned)s_sd_diag.attempts,
s_sd_diag.attempts == 1 ? "" : "s", sd_clock);
#else
} else {
p += snprintf(buf + p, cap - p,
"microSD\n status: connected / mounted\n"
" mount: %u attempt%s @ %s\n\n",
(unsigned)s_sd_diag.attempts,
s_sd_diag.attempts == 1 ? "" : "s", sd_clock);
#endif
}
}
#else
p += snprintf(buf + p, cap - p, "microSD\n not available on this hardware\n\n");
#endif
// NVS usage across the whole 'nvs' partition (shared by Wi-Fi creds, the
@@ -12541,7 +12624,7 @@ static void refreshSysInfo(unsigned long now) {
if (lv_indev_get_scroll_dir(in) != LV_DIR_NONE) { next = now + 120; return; }
}
next = now + 1000;
char buf[832];
char buf[1024];
sysInfoTextLive(buf, sizeof buf);
lv_label_set_text(s_sysinfo_lbl, buf);
// Slow tier: pushing identical text would still invalidate + repaint the whole
@@ -12614,7 +12697,7 @@ static void refreshSleepDiag(unsigned long now) {
static void buildSystemInfoSettings() {
lv_obj_t* body = createSettingsModal(TR("System Information"), SettingsModalKind::SystemInfo);
char buf[832];
char buf[1024];
// FLEX COLUMN, deliberately not the manual y cursor the rest of this file uses.
// Both labels below are re-texted AFTER build with text whose LINE COUNT
@@ -21868,8 +21951,16 @@ static bool sdAdoptLiveMount() {
s_sd_mounted = true;
s_sd_size = SD.cardSize();
s_sd_retry_after_ms = 0;
sdMountDiagSetMounted(true, g_sd_operating_hz);
return true;
}
static bool sdBeginTracked(SPIClass* spi, uint32_t hz, bool* begin_ok_out = nullptr) {
const bool begin_ok = spi && SD.begin(PIN_SD_CS, *spi, hz, "/sd", 6);
const bool card_ready = begin_ok && SD.cardType() != CARD_NONE;
if (begin_ok_out) *begin_ok_out = begin_ok;
sdMountDiagAttempt(hz, begin_ok, card_ready);
return card_ready;
}
static bool sdRuntimeLifecycleBusy();
static bool fmSdTryMount() {
// main.cpp may already have mounted this global SD object for DataStore.
@@ -21902,12 +21993,13 @@ static bool fmSdTryMount() {
#endif
SPIClass* spi = sdSharedSPI();
if (!spi) return false;
sdMountDiagBegin();
#if defined(TLORA_PAGER)
// Vendor-compatible Pager sequence: the shared display/radio bus is already
// running, all other CS lines are parked HIGH, and the card gets one 4 MHz
// mount attempt. Never tear down that live shared bus or add a retry ladder.
const bool begin_ok = SD.begin(PIN_SD_CS, *spi, 4000000, "/sd", 6);
const bool mounted = begin_ok && SD.cardType() != CARD_NONE;
bool begin_ok = false;
const bool mounted = sdBeginTracked(spi, 4000000, &begin_ok);
#else
// Cold microSD cards — especially the first mount after boot — often fail
// the initial SD.begin and historically only recovered after a physical
@@ -21950,7 +22042,7 @@ static bool fmSdTryMount() {
for (int attempt = 0; attempt < kAttempts; ++attempt) {
SD.end();
delay(kMountLadder[attempt].settle_ms);
if (SD.begin(PIN_SD_CS, *spi, kMountLadder[attempt].hz, "/sd", 6) && SD.cardType() != CARD_NONE) {
if (sdBeginTracked(spi, kMountLadder[attempt].hz)) {
mounted = true;
mounted_hz = kMountLadder[attempt].hz;
break;
@@ -21963,10 +22055,10 @@ static bool fmSdTryMount() {
if (mounted && mounted_hz < 4000000) {
SD.end();
delay(60);
if (!(SD.begin(PIN_SD_CS, *spi, 4000000, "/sd", 6) && SD.cardType() != CARD_NONE)) {
if (!sdBeginTracked(spi, 4000000)) {
SD.end();
delay(120);
mounted = SD.begin(PIN_SD_CS, *spi, mounted_hz, "/sd", 6) && SD.cardType() != CARD_NONE;
mounted = sdBeginTracked(spi, mounted_hz);
} else {
mounted_hz = 4000000;
}
@@ -21986,6 +22078,7 @@ static bool fmSdTryMount() {
// later mount only restores the independently SD-backed data consumers.
s_sd_data_warn_next_ms = 0;
#endif
sdMountDiagSetMounted(true, mounted_hz);
return true;
}
#if defined(TLORA_PAGER)
@@ -21998,6 +22091,7 @@ static bool fmSdTryMount() {
#else
SD.end(); // clean up on failure
#endif
sdMountDiagSetMounted(false, 0);
s_sd_retry_after_ms = millis() + 10000; // back off so we don't hammer the card
return false;
}
@@ -22009,6 +22103,7 @@ static void fmSdUnmount() {
#endif
s_sd_mounted = false;
s_sd_size = 0;
sdMountDiagSetMounted(false, 0);
}
s_sd_retry_after_ms = 0; // a reinsert should be able to mount right away
}
@@ -23365,23 +23460,39 @@ static bool tanSdTryMount() {
if (luaAudioStorageBusy()) return false;
#endif
if (millis() < s_tan_sd_retry_after) return false;
const uint32_t mount_hz = (uint32_t)SDMMC_FREQ_DEFAULT * 1000u;
if (SD_MMC.cardType() != CARD_NONE) {
s_tan_sd_mounted = true;
s_tan_sd_size = SD_MMC.cardSize();
sdMountDiagSetMounted(true, 0); // preserve the boot-recorded clock when adopting its mount
return true;
}
sdMountDiagBegin();
bool attempted = false;
bool begin_ok = false;
#if defined(HAS_WIO_TRACKER_L2)
// 20 MHz to match main.cpp's boot mount — re-begin()ning at Arduino's 40 MHz
// default would quietly re-clock a card the boot already brought up at 20.
if (SD_MMC.cardType() != CARD_NONE ||
(WioTrackerL2Io::ready() && WioTrackerL2Io::setSdPower(true) &&
SD_MMC.setPins(2, 3, 1) &&
SD_MMC.begin("/sdcard", true, false, SDMMC_FREQ_DEFAULT) &&
SD_MMC.cardType() != CARD_NONE)) {
if (WioTrackerL2Io::ready() && WioTrackerL2Io::setSdPower(true) &&
SD_MMC.setPins(2, 3, 1)) {
attempted = true;
begin_ok = SD_MMC.begin("/sdcard", true, false, SDMMC_FREQ_DEFAULT);
}
#elif defined(HAS_TDISPLAY_P4)
// Hot-insert path (no-op when main.cpp already mounted at boot): 20 MHz like the boot ladder,
// not Arduino's 40 MHz HIGHSPEED default.
if (SD_MMC.begin("/sdcard", false /*4-bit*/, false, SDMMC_FREQ_DEFAULT) && SD_MMC.cardType() != CARD_NONE) {
attempted = true;
begin_ok = SD_MMC.begin("/sdcard", false /*4-bit*/, false, SDMMC_FREQ_DEFAULT);
#else
if (SD_MMC.begin("/sdcard", false /*4-bit*/) && SD_MMC.cardType() != CARD_NONE) {
attempted = true;
begin_ok = SD_MMC.begin("/sdcard", false /*4-bit*/);
#endif
const bool card_ready = begin_ok && SD_MMC.cardType() != CARD_NONE;
if (attempted) sdMountDiagAttempt(mount_hz, begin_ok, card_ready);
if (card_ready) {
s_tan_sd_mounted = true;
s_tan_sd_size = SD_MMC.cardSize();
sdMountDiagSetMounted(true, mount_hz);
return true;
}
#if defined(HAS_WIO_TRACKER_L2)
@@ -23400,6 +23511,7 @@ static bool tanSdTryMount() {
#else
SD_MMC.end();
#endif
sdMountDiagSetMounted(false, 0);
s_tan_sd_retry_after = millis() + 8000; // don't grind a missing card on every roots render
return false;
}
@@ -25393,6 +25505,7 @@ static void fileTransferStorageIoFailed() {
#else
s_tan_sd_mounted = false;
s_tan_sd_retry_after = millis() + 8000;
sdMountDiagSetMounted(false, 0);
#endif
}
@@ -29638,7 +29751,7 @@ static void tileFetchTaskFn(void* arg) {
if (luaNetWorkerPending()) { luaNetWorkerService(&client, &http); continue; }
}
#endif
#if CAP_SD || defined(TLORA_PAGER)
#if CAP_SD
// microSD usage for the About page — the FAT scan runs here, off the UI thread.
if (s_sdinfo_request) {
// Publish busy before clearing request so the loop task never observes a
@@ -57492,21 +57605,22 @@ static void sdHealthTick() {
#endif
SPIClass* spi = sdSharedSPI();
if (!spi) return;
sdMountDiagBegin();
#if defined(TLORA_PAGER)
// The Pager shares this SPIClass with display + radio: one 4 MHz attempt,
// with every other CS parked HIGH and no SD.end() of a potentially live bus.
const bool begin_ok = SD.begin(PIN_SD_CS, *spi, 4000000, "/sd", 6);
const bool remounted = begin_ok && SD.cardType() != CARD_NONE;
bool begin_ok = false;
const bool remounted = sdBeginTracked(spi, 4000000, &begin_ok);
#else
SD.end();
bool remounted = SD.begin(PIN_SD_CS, *spi, 4000000, "/sd", 6) &&
SD.cardType() != CARD_NONE;
bool remounted = sdBeginTracked(spi, 4000000);
if (remounted) {
const uint32_t hz = sdTryFastClock(PIN_SD_CS, *spi, 4000000, "SD"); // no-op unless SD_SPI_FAST_HZ
remounted = hz != 0;
if (remounted) g_sd_operating_hz = hz;
}
#endif
sdMountDiagSetMounted(remounted, remounted ? g_sd_operating_hz : 0);
if (remounted) {
s_sd_mounted = true;
s_sd_size = SD.cardSize();
+4
View File
@@ -27,6 +27,10 @@ struct ContactInfo;
/** Maps bottom tabs (Home, Chats, Contacts, Set — no separate Net tab). */
enum class TouchUiScreen : uint8_t { Home = 0, ChatInbox = 1, Contacts = 2, Settings = 3 };
void sdMountDiagBegin();
void sdMountDiagAttempt(uint32_t hz, bool begin_ok, bool card_ready);
void sdMountDiagSetMounted(bool mounted, uint32_t hz);
class UITask : public AbstractUITask {
public:
static const int MAX_UI_MESSAGES = 500;
+9
View File
@@ -161,6 +161,15 @@
#define CAP_LOCK_SCREEN 0
#endif
// Physical removable microSD slot, independent of the filesystem API used to
// drive it. CAP_SD specifically means Arduino SD over SPI; these three boards
// use SD_MMC instead but still need the same user-facing card diagnostics.
#if CAP_SD || defined(HAS_WIO_TRACKER_L2) || defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4)
#define CAP_MICROSD 1
#else
#define CAP_MICROSD 0
#endif
// Persisted, restart-to-apply UI-size selector. Large-screen boards already
// expose it; the Pager adds font-only presets because its 480x222 viewport is
// wide enough for larger type but too short for global geometry scaling.
+5 -1
View File
@@ -379,7 +379,11 @@ static void wadameshSetup() {
// the build (-Og "worked by luck"; -Os made the exists()-gated identity + prefs loads come up
// empty: fresh node identity, default name, profile changes lost every reboot). The card's FAT
// metadata is truthful, so the WHOLE store lives there now; FFat remains only the no-card fallback.
g_sd_ok = SD_MMC.begin("/sdcard", false /*1-bit*/) && SD_MMC.cardType() != CARD_NONE;
sdMountDiagBegin();
const bool sd_begin_ok = SD_MMC.begin("/sdcard", false /*1-bit*/);
g_sd_ok = sd_begin_ok && SD_MMC.cardType() != CARD_NONE;
sdMountDiagAttempt((uint32_t)SDMMC_FREQ_DEFAULT * 1000u, sd_begin_ok, g_sd_ok);
sdMountDiagSetMounted(g_sd_ok, g_sd_ok ? (uint32_t)SDMMC_FREQ_DEFAULT * 1000u : 0);
printf("[storage] SD_MMC.begin = %s\n", g_sd_ok ? "OK" : "no card");
if (g_sd_ok) {
SD_MMC.mkdir("/meshcomod");
+7 -2
View File
@@ -212,13 +212,18 @@ static void wadameshSetup() {
{ true, SDMMC_FREQ_DEFAULT, "1-bit 20MHz" },
{ true, 5000, "1-bit 5MHz" },
};
sdMountDiagBegin();
uint32_t mounted_hz = 0;
for (auto &t : tries) {
g_sd_ok = SD_MMC.begin("/sdcard", t.onebit, false, t.khz) && SD_MMC.cardType() != CARD_NONE;
const bool begin_ok = SD_MMC.begin("/sdcard", t.onebit, false, t.khz);
g_sd_ok = begin_ok && SD_MMC.cardType() != CARD_NONE;
sdMountDiagAttempt((uint32_t)t.khz * 1000u, begin_ok, g_sd_ok);
printf("[storage] SD_MMC try %s -> %s\n", t.tag, g_sd_ok ? "OK" : "fail");
if (g_sd_ok) break;
if (g_sd_ok) { mounted_hz = (uint32_t)t.khz * 1000u; break; }
SD_MMC.end();
delay(120);
}
sdMountDiagSetMounted(g_sd_ok, mounted_hz);
}
printf("[storage] SD_MMC = %s\n", g_sd_ok ? "OK" : "no card");
if (g_sd_ok) {