diff --git a/exercises/05_SD_Card_Watcher/src/main.cpp b/exercises/05_SD_Card_Watcher/src/main.cpp index f3f4373..dd778d7 100644 --- a/exercises/05_SD_Card_Watcher/src/main.cpp +++ b/exercises/05_SD_Card_Watcher/src/main.cpp @@ -1,4 +1,4 @@ -// 20260214 ChatGPT +// 20260213 ChatGPT // $Id$ // $HeadURL$ @@ -7,19 +7,8 @@ #include #include #include -#include "driver/gpio.h" // gpio_get_level() #include "tbeam_supreme_adapter.h" -// ------------------------- -// Configuration toggles -// ------------------------- -#define ENABLE_SD_RAIL_CYCLE 1 // Power-cycle AXP2101 BLDO1 (SD rail) at boot. -#define ENABLE_PIN_DUMPS 1 // Log SPI pin logic levels at key points (NON-INTRUSIVE). -#define STARTUP_SERIAL_DELAY_MS 5000 - -// ------------------------- -// Globals -// ------------------------- static SPIClass sdSpiH(HSPI); static SPIClass sdSpiF(FSPI); static SPIClass* g_sdSpi = nullptr; @@ -27,10 +16,10 @@ static const char* g_sdBusName = "none"; static uint32_t g_sdFreq = 0; static XPowersLibInterface* g_pmu = nullptr; -static const char* kRootTestFile = "/Exercise_05_test.txt"; -static const char* kNestedDir = "/test/testsub1/testsubsub1"; +static const char* kRootTestFile = "/Exercise_05_test.txt"; +static const char* kNestedDir = "/test/testsub1/testsubsub1"; static const char* kNestedTestFile = "/test/testsub1/testsubsub1/Exercise_05_test.txt"; -static const char* kPayload = "This is a test"; +static const char* kPayload = "This is a test"; enum class WatchState : uint8_t { UNKNOWN = 0, @@ -44,127 +33,37 @@ static uint8_t g_absentVotes = 0; static uint32_t g_lastPollMs = 0; static uint32_t g_lastFullScanMs = 0; static uint32_t g_lastPeriodicActionMs = 0; - -static const uint32_t kPollIntervalAbsentMs = 1000; +static const uint32_t kPollIntervalAbsentMs = 1000; static const uint32_t kPollIntervalMountedMs = 2000; -static const uint32_t kFullScanIntervalMs = 10000; -static const uint32_t kPeriodicActionMs = 15000; - -static const uint8_t kVotesToPresent = 2; -static const uint8_t kVotesToAbsent = 5; // More votes needed to declare absent to prevent false removes. -static const uint32_t kStartupWarmupMs = 1500; // Allow PMU and SD rail to stabilize. +static const uint32_t kFullScanIntervalMs = 10000; +static const uint32_t kPeriodicActionMs = 15000; +static const uint8_t kVotesToPresent = 2; +//static const uint8_t kVotesToAbsent = 4; +static const uint8_t kVotesToAbsent = 5; // More votes needed to declare absent to prevent false removes on transient errors. +//static const uint32_t kStartupWarmupMs = 800; +static const uint32_t kStartupWarmupMs = 1500; // Longer warmup to allow PMU and card stabilization after power-on. static uint32_t g_logSeq = 0; -// ------------------------- -// Logging helpers -// ------------------------- static void logf(const char* fmt, ...) { char msg[192]; va_list args; va_start(args, fmt); vsnprintf(msg, sizeof(msg), fmt, args); va_end(args); - Serial.printf("[%10lu][%06lu] %s\r\n", - (unsigned long)millis(), - (unsigned long)g_logSeq++, - msg); -} - -static void dumpPmu(const char* tag, XPowersLibInterface* pmu) { - if (!pmu) { - logf("PMU(%s): pmu=null", tag); - return; - } - - bool bldo1 = pmu->isPowerChannelEnable(XPOWERS_BLDO1); - int vbus = pmu->getVbusVoltage(); - int batt = pmu->getBattVoltage(); - - logf("PMU(%s): BLDO1(SD)=%s VBUS=%dmV VBAT=%dmV", - tag, bldo1 ? "ON" : "OFF", vbus, batt); -} - -// IMPORTANT: this function MUST NOT modify pin modes (regression cause). -static void dumpSdPins(const char* tag) { -#if ENABLE_PIN_DUMPS - const gpio_num_t CS = (gpio_num_t)tbeam_supreme::sdCs(); - const gpio_num_t SCK = (gpio_num_t)tbeam_supreme::sdSck(); - const gpio_num_t MISO = (gpio_num_t)tbeam_supreme::sdMiso(); - const gpio_num_t MOSI = (gpio_num_t)tbeam_supreme::sdMosi(); - - int cs = gpio_get_level(CS); - int sck = gpio_get_level(SCK); - int miso = gpio_get_level(MISO); - int mosi = gpio_get_level(MOSI); - - logf("PINS(%s): CS=%d SCK=%d MISO=%d MOSI=%d", tag, cs, sck, miso, mosi); -#else - (void)tag; -#endif -} - -// ------------------------- -// Power + bus conditioning -// ------------------------- -static void forceSpiDeselected() { - pinMode(tbeam_supreme::sdCs(), OUTPUT); - digitalWrite(tbeam_supreme::sdCs(), HIGH); - - pinMode(tbeam_supreme::imuCs(), OUTPUT); - digitalWrite(tbeam_supreme::imuCs(), HIGH); + Serial.printf("[%10lu][%06lu] %s\r\n", (unsigned long)millis(), (unsigned long)g_logSeq++, msg); } static bool initPmuForSdPower() { - bool ok = tbeam_supreme::initPmuForPeripherals(g_pmu, &Serial); - if (!ok) { - logf("ERROR: PMU init failed"); - return false; - } - - logf("PMU adapter: AXP2101 ready, BLDO1(SD)=%s", - g_pmu && g_pmu->isPowerChannelEnable(XPOWERS_BLDO1) ? "ON" : "OFF"); - return true; + return tbeam_supreme::initPmuForPeripherals(g_pmu, &Serial); } -static void cycleSdRail(XPowersLibInterface* pmu, - uint32_t off_ms = 250, - uint32_t on_settle_ms = 600) { -#if ENABLE_SD_RAIL_CYCLE - if (!pmu) { - logf("SD rail cycle skipped: pmu=null"); - return; - } - - dumpPmu("pre-sd-cycle", pmu); - - // Ensure the card is NOT selected while power is unstable. - forceSpiDeselected(); - dumpSdPins("pre-sd-cycle"); - - pmu->disablePowerOutput(XPOWERS_BLDO1); - delay(off_ms); - - pmu->setPowerChannelVoltage(XPOWERS_BLDO1, 3300); - pmu->enablePowerOutput(XPOWERS_BLDO1); - delay(on_settle_ms); - - dumpPmu("post-sd-cycle", pmu); - dumpSdPins("post-sd-cycle"); -#else - (void)pmu; (void)off_ms; (void)on_settle_ms; -#endif -} - -// ------------------------- -// SD helpers -// ------------------------- static const char* cardTypeToString(uint8_t type) { switch (type) { - case CARD_MMC: return "MMC"; - case CARD_SD: return "SDSC"; + case CARD_MMC: return "MMC"; + case CARD_SD: return "SDSC"; case CARD_SDHC: return "SDHC/SDXC"; - default: return "UNKNOWN"; + default: return "UNKNOWN"; } } @@ -173,26 +72,22 @@ static bool tryMountWithBus(SPIClass& bus, const char* busName, uint32_t hz, boo bus.end(); delay(10); - forceSpiDeselected(); + // Keep inactive devices deselected on shared bus lines. + pinMode(tbeam_supreme::sdCs(), OUTPUT); + digitalWrite(tbeam_supreme::sdCs(), HIGH); + pinMode(tbeam_supreme::imuCs(), OUTPUT); + digitalWrite(tbeam_supreme::imuCs(), HIGH); bus.begin(tbeam_supreme::sdSck(), tbeam_supreme::sdMiso(), tbeam_supreme::sdMosi(), tbeam_supreme::sdCs()); - - // SD SPI "idle clocks" ritual: CS HIGH + >= 74 clocks with MOSI high (0xFF). - digitalWrite(tbeam_supreme::sdCs(), HIGH); delay(2); - for (int i = 0; i < 10; i++) { - bus.transfer(0xFF); // 80 clocks total - } - delay(2); - - dumpSdPins("after-idle-clocks"); if (verbose) { logf("SD: trying bus=%s freq=%lu Hz", busName, (unsigned long)hz); } - if (!SD.begin(tbeam_supreme::sdCs(), bus, hz)) { - if (verbose) logf("SD: mount failed (possible non-FAT format, power, or bus issue)"); + if (verbose) { + logf("SD: mount failed (possible non-FAT format, power, or bus issue)"); + } return false; } @@ -212,7 +107,7 @@ static bool mountPreferred(bool verbose) { return tryMountWithBus(sdSpiH, "HSPI", 400000, verbose); } -static bool mountCardFullScan() { +static bool mountCard() { const uint32_t freqs[] = {400000, 1000000, 4000000, 10000000}; for (uint8_t i = 0; i < (sizeof(freqs) / sizeof(freqs[0])); ++i) { @@ -236,8 +131,8 @@ static bool mountCardFullScan() { static void printCardInfo() { uint8_t cardType = SD.cardType(); uint64_t cardSizeMB = SD.cardSize() / (1024ULL * 1024ULL); - uint64_t totalMB = SD.totalBytes() / (1024ULL * 1024ULL); - uint64_t usedMB = SD.usedBytes() / (1024ULL * 1024ULL); + uint64_t totalMB = SD.totalBytes() / (1024ULL * 1024ULL); + uint64_t usedMB = SD.usedBytes() / (1024ULL * 1024ULL); logf("SD type: %s", cardTypeToString(cardType)); logf("SD size: %llu MB", cardSizeMB); @@ -248,7 +143,9 @@ static void printCardInfo() { static bool ensureDirRecursive(const char* path) { String full(path); - if (!full.startsWith("/")) full = "/" + full; + if (!full.startsWith("/")) { + full = "/" + full; + } int start = 1; while (start > 0 && start < (int)full.length()) { @@ -263,9 +160,12 @@ static bool ensureDirRecursive(const char* path) { } } - if (slash < 0) break; + if (slash < 0) { + break; + } start = slash + 1; } + return true; } @@ -319,7 +219,9 @@ static void permissionsDemo(const char* path) { static bool verifyMountedCard() { File root = SD.open("/", FILE_READ); - if (!root) return false; + if (!root) { + return false; + } root.close(); return true; } @@ -343,9 +245,6 @@ static void runCardWorkflow() { permissionsDemo(kRootTestFile); } -// ------------------------- -// Watcher state transitions -// ------------------------- static void setStateMounted() { if (g_watchState != WatchState::MOUNTED) { logf("EVENT: card inserted/mounted"); @@ -365,22 +264,13 @@ static void setStateAbsent() { g_watchState = WatchState::ABSENT; } -// ------------------------- -// Arduino entry points -// ------------------------- void setup() { Serial.begin(115200); Serial.println("[WATCHER: startup]"); + Serial.println("Sleeping for 5 seconds to allow Serial Monitor connection..."); + delay(5000); // Time to open Serial Monitor after reset - // De-select SPI devices immediately. - forceSpiDeselected(); - dumpSdPins("very-early"); - - logf("Sleeping for %lu ms to allow Serial Monitor connection...", (unsigned long)STARTUP_SERIAL_DELAY_MS); - delay(STARTUP_SERIAL_DELAY_MS); - - Serial.println(); - Serial.println("=================================================="); + Serial.println("\r\n=================================================="); Serial.println("Exercise 05: SD Card Watcher"); Serial.println("=================================================="); Serial.printf("Pins: CS=%d SCK=%d MISO=%d MOSI=%d\r\n", @@ -388,20 +278,13 @@ void setup() { tbeam_supreme::sdMiso(), tbeam_supreme::sdMosi()); Serial.printf("PMU I2C: SDA1=%d SCL1=%d\r\n", tbeam_supreme::i2cSda(), tbeam_supreme::i2cScl()); - Serial.println("Note: SD must be FAT16/FAT32 for Arduino SD library."); - Serial.println(); + Serial.println("Note: SD must be FAT16/FAT32 for Arduino SD library.\r\n"); initPmuForSdPower(); - dumpPmu("post-pmu-init", g_pmu); - - // Software equivalent of "remove/insert card". - cycleSdRail(g_pmu); - logf("Watcher: waiting %lu ms for SD rail/card stabilization", (unsigned long)kStartupWarmupMs); delay(kStartupWarmupMs); - dumpSdPins("pre-warmup-mount"); - + // Warm-up attempts before first status decision. bool warmMounted = false; for (uint8_t i = 0; i < 3; ++i) { if (mountPreferred(false)) { @@ -410,7 +293,6 @@ void setup() { } delay(200); } - if (warmMounted) { logf("Watcher: startup warmup mount succeeded"); setStateMounted(); @@ -422,9 +304,9 @@ void setup() { void loop() { const uint32_t now = millis(); - const uint32_t pollInterval = - (g_watchState == WatchState::MOUNTED) ? kPollIntervalMountedMs : kPollIntervalAbsentMs; - + const uint32_t pollInterval = (g_watchState == WatchState::MOUNTED) + ? kPollIntervalMountedMs + : kPollIntervalAbsentMs; if ((uint32_t)(now - g_lastPollMs) < pollInterval) { delay(10); return; @@ -439,14 +321,14 @@ void loop() { g_lastPeriodicActionMs = now; } g_presentVotes = 0; - g_absentVotes = 0; + g_absentVotes = 0; return; } - // One immediate remount attempt prevents false removes on transient SPI errors. + // One immediate remount attempt prevents false remove events on transient SPI errors. if (mountPreferred(false) && verifyMountedCard()) { g_presentVotes = 0; - g_absentVotes = 0; + g_absentVotes = 0; return; } @@ -459,12 +341,11 @@ void loop() { return; } - // ABSENT/UNKNOWN state bool mounted = mountPreferred(false); if (!mounted && (uint32_t)(now - g_lastFullScanMs) >= kFullScanIntervalMs) { g_lastFullScanMs = now; logf("Watcher: preferred probe failed, running full scan"); - mounted = mountCardFullScan(); + mounted = mountCard(); } if (mounted) {