Compare commits

..

No commits in common. "0217ece5e5c6a40099abfab544f55e57242f2735" and "432f17b2be2c8420b3ea8b66d84dc5ba2a81d48f" have entirely different histories.

View file

@ -1,4 +1,4 @@
// 20260214 ChatGPT // 20260213 ChatGPT
// $Id$ // $Id$
// $HeadURL$ // $HeadURL$
@ -7,19 +7,8 @@
#include <FS.h> #include <FS.h>
#include <SD.h> #include <SD.h>
#include <SPI.h> #include <SPI.h>
#include "driver/gpio.h" // gpio_get_level()
#include "tbeam_supreme_adapter.h" #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 sdSpiH(HSPI);
static SPIClass sdSpiF(FSPI); static SPIClass sdSpiF(FSPI);
static SPIClass* g_sdSpi = nullptr; static SPIClass* g_sdSpi = nullptr;
@ -27,10 +16,10 @@ static const char* g_sdBusName = "none";
static uint32_t g_sdFreq = 0; static uint32_t g_sdFreq = 0;
static XPowersLibInterface* g_pmu = nullptr; static XPowersLibInterface* g_pmu = nullptr;
static const char* kRootTestFile = "/Exercise_05_test.txt"; static const char* kRootTestFile = "/Exercise_05_test.txt";
static const char* kNestedDir = "/test/testsub1/testsubsub1"; static const char* kNestedDir = "/test/testsub1/testsubsub1";
static const char* kNestedTestFile = "/test/testsub1/testsubsub1/Exercise_05_test.txt"; 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 { enum class WatchState : uint8_t {
UNKNOWN = 0, UNKNOWN = 0,
@ -44,127 +33,37 @@ static uint8_t g_absentVotes = 0;
static uint32_t g_lastPollMs = 0; static uint32_t g_lastPollMs = 0;
static uint32_t g_lastFullScanMs = 0; static uint32_t g_lastFullScanMs = 0;
static uint32_t g_lastPeriodicActionMs = 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 kPollIntervalMountedMs = 2000;
static const uint32_t kFullScanIntervalMs = 10000; static const uint32_t kFullScanIntervalMs = 10000;
static const uint32_t kPeriodicActionMs = 15000; static const uint32_t kPeriodicActionMs = 15000;
static const uint8_t kVotesToPresent = 2;
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. static const uint8_t kVotesToAbsent = 5; // More votes needed to declare absent to prevent false removes on transient errors.
static const uint32_t kStartupWarmupMs = 1500; // Allow PMU and SD rail to stabilize.
//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; static uint32_t g_logSeq = 0;
// -------------------------
// Logging helpers
// -------------------------
static void logf(const char* fmt, ...) { static void logf(const char* fmt, ...) {
char msg[192]; char msg[192];
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
vsnprintf(msg, sizeof(msg), fmt, args); vsnprintf(msg, sizeof(msg), fmt, args);
va_end(args); va_end(args);
Serial.printf("[%10lu][%06lu] %s\r\n", Serial.printf("[%10lu][%06lu] %s\r\n", (unsigned long)millis(), (unsigned long)g_logSeq++, msg);
(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);
} }
static bool initPmuForSdPower() { static bool initPmuForSdPower() {
bool ok = tbeam_supreme::initPmuForPeripherals(g_pmu, &Serial); return 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;
} }
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) { static const char* cardTypeToString(uint8_t type) {
switch (type) { switch (type) {
case CARD_MMC: return "MMC"; case CARD_MMC: return "MMC";
case CARD_SD: return "SDSC"; case CARD_SD: return "SDSC";
case CARD_SDHC: return "SDHC/SDXC"; 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(); bus.end();
delay(10); 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()); 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); delay(2);
for (int i = 0; i < 10; i++) {
bus.transfer(0xFF); // 80 clocks total
}
delay(2);
dumpSdPins("after-idle-clocks");
if (verbose) { if (verbose) {
logf("SD: trying bus=%s freq=%lu Hz", busName, (unsigned long)hz); logf("SD: trying bus=%s freq=%lu Hz", busName, (unsigned long)hz);
} }
if (!SD.begin(tbeam_supreme::sdCs(), bus, 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; return false;
} }
@ -212,7 +107,7 @@ static bool mountPreferred(bool verbose) {
return tryMountWithBus(sdSpiH, "HSPI", 400000, verbose); return tryMountWithBus(sdSpiH, "HSPI", 400000, verbose);
} }
static bool mountCardFullScan() { static bool mountCard() {
const uint32_t freqs[] = {400000, 1000000, 4000000, 10000000}; const uint32_t freqs[] = {400000, 1000000, 4000000, 10000000};
for (uint8_t i = 0; i < (sizeof(freqs) / sizeof(freqs[0])); ++i) { for (uint8_t i = 0; i < (sizeof(freqs) / sizeof(freqs[0])); ++i) {
@ -236,8 +131,8 @@ static bool mountCardFullScan() {
static void printCardInfo() { static void printCardInfo() {
uint8_t cardType = SD.cardType(); uint8_t cardType = SD.cardType();
uint64_t cardSizeMB = SD.cardSize() / (1024ULL * 1024ULL); uint64_t cardSizeMB = SD.cardSize() / (1024ULL * 1024ULL);
uint64_t totalMB = SD.totalBytes() / (1024ULL * 1024ULL); uint64_t totalMB = SD.totalBytes() / (1024ULL * 1024ULL);
uint64_t usedMB = SD.usedBytes() / (1024ULL * 1024ULL); uint64_t usedMB = SD.usedBytes() / (1024ULL * 1024ULL);
logf("SD type: %s", cardTypeToString(cardType)); logf("SD type: %s", cardTypeToString(cardType));
logf("SD size: %llu MB", cardSizeMB); logf("SD size: %llu MB", cardSizeMB);
@ -248,7 +143,9 @@ static void printCardInfo() {
static bool ensureDirRecursive(const char* path) { static bool ensureDirRecursive(const char* path) {
String full(path); String full(path);
if (!full.startsWith("/")) full = "/" + full; if (!full.startsWith("/")) {
full = "/" + full;
}
int start = 1; int start = 1;
while (start > 0 && start < (int)full.length()) { 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; start = slash + 1;
} }
return true; return true;
} }
@ -319,7 +219,9 @@ static void permissionsDemo(const char* path) {
static bool verifyMountedCard() { static bool verifyMountedCard() {
File root = SD.open("/", FILE_READ); File root = SD.open("/", FILE_READ);
if (!root) return false; if (!root) {
return false;
}
root.close(); root.close();
return true; return true;
} }
@ -343,9 +245,6 @@ static void runCardWorkflow() {
permissionsDemo(kRootTestFile); permissionsDemo(kRootTestFile);
} }
// -------------------------
// Watcher state transitions
// -------------------------
static void setStateMounted() { static void setStateMounted() {
if (g_watchState != WatchState::MOUNTED) { if (g_watchState != WatchState::MOUNTED) {
logf("EVENT: card inserted/mounted"); logf("EVENT: card inserted/mounted");
@ -365,22 +264,13 @@ static void setStateAbsent() {
g_watchState = WatchState::ABSENT; g_watchState = WatchState::ABSENT;
} }
// -------------------------
// Arduino entry points
// -------------------------
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);
Serial.println("[WATCHER: startup]"); 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. Serial.println("\r\n==================================================");
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("Exercise 05: SD Card Watcher"); Serial.println("Exercise 05: SD Card Watcher");
Serial.println("=================================================="); Serial.println("==================================================");
Serial.printf("Pins: CS=%d SCK=%d MISO=%d MOSI=%d\r\n", 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()); tbeam_supreme::sdMiso(), tbeam_supreme::sdMosi());
Serial.printf("PMU I2C: SDA1=%d SCL1=%d\r\n", Serial.printf("PMU I2C: SDA1=%d SCL1=%d\r\n",
tbeam_supreme::i2cSda(), tbeam_supreme::i2cScl()); tbeam_supreme::i2cSda(), tbeam_supreme::i2cScl());
Serial.println("Note: SD must be FAT16/FAT32 for Arduino SD library."); Serial.println("Note: SD must be FAT16/FAT32 for Arduino SD library.\r\n");
Serial.println();
initPmuForSdPower(); 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); logf("Watcher: waiting %lu ms for SD rail/card stabilization", (unsigned long)kStartupWarmupMs);
delay(kStartupWarmupMs); delay(kStartupWarmupMs);
dumpSdPins("pre-warmup-mount"); // Warm-up attempts before first status decision.
bool warmMounted = false; bool warmMounted = false;
for (uint8_t i = 0; i < 3; ++i) { for (uint8_t i = 0; i < 3; ++i) {
if (mountPreferred(false)) { if (mountPreferred(false)) {
@ -410,7 +293,6 @@ void setup() {
} }
delay(200); delay(200);
} }
if (warmMounted) { if (warmMounted) {
logf("Watcher: startup warmup mount succeeded"); logf("Watcher: startup warmup mount succeeded");
setStateMounted(); setStateMounted();
@ -422,9 +304,9 @@ void setup() {
void loop() { void loop() {
const uint32_t now = millis(); const uint32_t now = millis();
const uint32_t pollInterval = const uint32_t pollInterval = (g_watchState == WatchState::MOUNTED)
(g_watchState == WatchState::MOUNTED) ? kPollIntervalMountedMs : kPollIntervalAbsentMs; ? kPollIntervalMountedMs
: kPollIntervalAbsentMs;
if ((uint32_t)(now - g_lastPollMs) < pollInterval) { if ((uint32_t)(now - g_lastPollMs) < pollInterval) {
delay(10); delay(10);
return; return;
@ -439,14 +321,14 @@ void loop() {
g_lastPeriodicActionMs = now; g_lastPeriodicActionMs = now;
} }
g_presentVotes = 0; g_presentVotes = 0;
g_absentVotes = 0; g_absentVotes = 0;
return; 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()) { if (mountPreferred(false) && verifyMountedCard()) {
g_presentVotes = 0; g_presentVotes = 0;
g_absentVotes = 0; g_absentVotes = 0;
return; return;
} }
@ -459,12 +341,11 @@ void loop() {
return; return;
} }
// ABSENT/UNKNOWN state
bool mounted = mountPreferred(false); bool mounted = mountPreferred(false);
if (!mounted && (uint32_t)(now - g_lastFullScanMs) >= kFullScanIntervalMs) { if (!mounted && (uint32_t)(now - g_lastFullScanMs) >= kFullScanIntervalMs) {
g_lastFullScanMs = now; g_lastFullScanMs = now;
logf("Watcher: preferred probe failed, running full scan"); logf("Watcher: preferred probe failed, running full scan");
mounted = mountCardFullScan(); mounted = mountCard();
} }
if (mounted) { if (mounted) {