// 20260401 Codex #include #include #include #include #include #include #include #include "tbeam_supreme_adapter.h" #ifndef NODE_LABEL #define NODE_LABEL "NODE" #endif #ifndef OLED_SDA #define OLED_SDA 17 #endif #ifndef OLED_SCL #define OLED_SCL 18 #endif static const uint32_t kSerialDelayMs = 1500; static const uint32_t kPollIntervalMs = 200; static const uint32_t kStartupQuietMs = 5000; static const uint32_t kStartupReplayWindowMs = 20000; static const uint32_t kStartupReplayPeriodMs = 2000; static const uint32_t kFreqs[] = { 400000, 1000000, 4000000, 10000000 }; struct PinSnapshot { int cs = -1; int sck = -1; int miso = -1; int mosi = -1; }; struct ProbeSummary { uint8_t ffCount = 0; uint8_t zeroCount = 0; uint8_t otherCount = 0; uint8_t firstBytes[8] = {0}; }; enum class DebugState : uint8_t { PMU_FAIL = 0, RAIL_OFF, BUS_FLOAT, BUS_LOW, BUS_CHATTER, SD_BEGIN_FAIL, CARD_NONE, FS_FAIL, MOUNT_OK }; struct DebugSnapshot { DebugState state = DebugState::PMU_FAIL; bool pmuOk = false; bool railOn = false; float vbusV = -1.0f; float battV = -1.0f; PinSnapshot pins{}; ProbeSummary probeH{}; ProbeSummary probeF{}; const char* mountBus = "none"; uint32_t mountHz = 0; uint8_t cardType = CARD_NONE; uint64_t cardSizeMB = 0; bool rootOk = false; }; static XPowersLibInterface* g_pmu = nullptr; static U8G2_SH1106_128X64_NONAME_F_HW_I2C g_oled(U8G2_R0, U8X8_PIN_NONE); static SPIClass g_spiH(HSPI); static SPIClass g_spiF(FSPI); static uint32_t g_sampleCount = 0; static uint32_t g_bootMs = 0; static uint32_t g_lastStartupReplayMs = 0; static const char* resetReasonToString(esp_reset_reason_t reason) { switch (reason) { case ESP_RST_UNKNOWN: return "UNKNOWN"; case ESP_RST_POWERON: return "POWERON"; case ESP_RST_EXT: return "EXT"; case ESP_RST_SW: return "SW"; case ESP_RST_PANIC: return "PANIC"; case ESP_RST_INT_WDT: return "INT_WDT"; case ESP_RST_TASK_WDT: return "TASK_WDT"; case ESP_RST_WDT: return "WDT"; case ESP_RST_DEEPSLEEP: return "DEEPSLEEP"; case ESP_RST_BROWNOUT: return "BROWNOUT"; case ESP_RST_SDIO: return "SDIO"; default: return "OTHER"; } } static const char* flashModeToString(FlashMode_t mode) { switch (mode) { case FM_QIO: return "QIO"; case FM_QOUT: return "QOUT"; case FM_DIO: return "DIO"; case FM_DOUT: return "DOUT"; case FM_FAST_READ: return "FAST"; case FM_SLOW_READ: return "SLOW"; default: return "UNKNOWN"; } } static void printBoardIdentity() { uint64_t mac = ESP.getEfuseMac(); uint32_t chipId = 0; for (int i = 0; i < 17; i += 8) { chipId |= ((mac >> (40 - i)) & 0xFF) << i; } Serial.println("BOARD IDENTITY"); Serial.printf("chip_model=%s\r\n", ESP.getChipModel()); Serial.printf("chip_revision=%u\r\n", (unsigned)ESP.getChipRevision()); Serial.printf("chip_cores=%u\r\n", (unsigned)ESP.getChipCores()); Serial.printf("sdk_version=%s\r\n", ESP.getSdkVersion()); Serial.printf("cpu_mhz=%u\r\n", (unsigned)ESP.getCpuFreqMHz()); Serial.printf("flash_size=%u\r\n", (unsigned)ESP.getFlashChipSize()); Serial.printf("flash_speed=%u\r\n", (unsigned)ESP.getFlashChipSpeed()); Serial.printf("flash_mode=%s\r\n", flashModeToString(ESP.getFlashChipMode())); Serial.printf("efuse_mac=%012llX\r\n", mac); Serial.printf("chip_id=%06lX\r\n", (unsigned long)chipId); Serial.printf("reset_reason=%s\r\n", resetReasonToString(esp_reset_reason())); Serial.printf("arduino_board=%s\r\n", ARDUINO_BOARD); } static void printStartupBanner() { Serial.println(); Serial.println("STARTUP REPLAY"); Serial.printf("uptime_ms=%lu\r\n", (unsigned long)(millis() - g_bootMs)); printBoardIdentity(); } static void forceSpiDeselected() { pinMode(tbeam_supreme::sdCs(), OUTPUT); digitalWrite(tbeam_supreme::sdCs(), HIGH); pinMode(tbeam_supreme::imuCs(), OUTPUT); digitalWrite(tbeam_supreme::imuCs(), HIGH); } static PinSnapshot readPins() { PinSnapshot s; s.cs = gpio_get_level((gpio_num_t)tbeam_supreme::sdCs()); s.sck = gpio_get_level((gpio_num_t)tbeam_supreme::sdSck()); s.miso = gpio_get_level((gpio_num_t)tbeam_supreme::sdMiso()); s.mosi = gpio_get_level((gpio_num_t)tbeam_supreme::sdMosi()); return s; } static void readPmu(DebugSnapshot& snap) { snap.pmuOk = (g_pmu != nullptr); if (!g_pmu) { return; } snap.railOn = g_pmu->isPowerChannelEnable(XPOWERS_BLDO1); snap.vbusV = g_pmu->getVbusVoltage() / 1000.0f; snap.battV = g_pmu->getBattVoltage() / 1000.0f; } static ProbeSummary runIdleProbe(SPIClass& bus) { ProbeSummary out; SD.end(); bus.end(); delay(2); forceSpiDeselected(); bus.begin( tbeam_supreme::sdSck(), tbeam_supreme::sdMiso(), tbeam_supreme::sdMosi(), tbeam_supreme::sdCs() ); digitalWrite(tbeam_supreme::sdCs(), HIGH); delay(1); for (int i = 0; i < 8; ++i) { uint8_t b = bus.transfer(0xFF); out.firstBytes[i] = b; if (b == 0xFF) out.ffCount++; else if (b == 0x00) out.zeroCount++; else out.otherCount++; } return out; } static const char* cardTypeToString(uint8_t type) { switch (type) { case CARD_MMC: return "MMC"; case CARD_SD: return "SDSC"; case CARD_SDHC: return "SDHC"; default: return "NONE"; } } static bool tryMount(SPIClass& bus, const char* busName, uint32_t hz, DebugSnapshot& snap) { SD.end(); bus.end(); delay(2); forceSpiDeselected(); bus.begin( tbeam_supreme::sdSck(), tbeam_supreme::sdMiso(), tbeam_supreme::sdMosi(), tbeam_supreme::sdCs() ); digitalWrite(tbeam_supreme::sdCs(), HIGH); delay(1); for (int i = 0; i < 10; ++i) { bus.transfer(0xFF); } if (!SD.begin(tbeam_supreme::sdCs(), bus, hz)) { snap.state = DebugState::SD_BEGIN_FAIL; return false; } snap.cardType = SD.cardType(); snap.mountBus = busName; snap.mountHz = hz; if (snap.cardType == CARD_NONE) { SD.end(); snap.state = DebugState::CARD_NONE; return false; } snap.cardSizeMB = SD.cardSize() / (1024ULL * 1024ULL); File root = SD.open("/", FILE_READ); snap.rootOk = (bool)root; if (root) { root.close(); } SD.end(); snap.state = snap.rootOk ? DebugState::MOUNT_OK : DebugState::FS_FAIL; return snap.rootOk; } static DebugState classifyProbe(const ProbeSummary& probe) { if (probe.ffCount == 8) return DebugState::BUS_FLOAT; if (probe.zeroCount == 8) return DebugState::BUS_LOW; return DebugState::BUS_CHATTER; } static DebugSnapshot captureSnapshot() { DebugSnapshot snap; readPmu(snap); snap.pins = readPins(); if (!snap.pmuOk) { snap.state = DebugState::PMU_FAIL; return snap; } if (!snap.railOn) { snap.state = DebugState::RAIL_OFF; return snap; } snap.probeH = runIdleProbe(g_spiH); snap.probeF = runIdleProbe(g_spiF); snap.state = classifyProbe(snap.probeH); for (size_t i = 0; i < (sizeof(kFreqs) / sizeof(kFreqs[0])); ++i) { if (tryMount(g_spiH, "HSPI", kFreqs[i], snap)) { return snap; } } for (size_t i = 0; i < (sizeof(kFreqs) / sizeof(kFreqs[0])); ++i) { if (tryMount(g_spiF, "FSPI", kFreqs[i], snap)) { return snap; } } return snap; } static const char* stateToString(DebugState state) { switch (state) { case DebugState::PMU_FAIL: return "PMU_FAIL"; case DebugState::RAIL_OFF: return "RAIL_OFF"; case DebugState::BUS_FLOAT: return "NO_RESP"; case DebugState::BUS_LOW: return "BUS_LOW"; case DebugState::BUS_CHATTER: return "BUS_CHAT"; case DebugState::SD_BEGIN_FAIL: return "BEGIN_FAIL"; case DebugState::CARD_NONE: return "CARD_NONE"; case DebugState::FS_FAIL: return "FS_FAIL"; case DebugState::MOUNT_OK: return "MOUNT_OK"; default: return "UNKNOWN"; } } static void printSnapshot(const DebugSnapshot& snap) { Serial.printf( "sample=%lu state=%s rail=%s vbus=%.2f batt=%.2f pins=%d/%d/%d/%d " "probeH(ff=%u z=%u o=%u %02X %02X %02X %02X) " "probeF(ff=%u z=%u o=%u %02X %02X %02X %02X) " "mount=%s@%lu type=%s size=%lluMB root=%s\r\n", (unsigned long)g_sampleCount, stateToString(snap.state), snap.railOn ? "ON" : "OFF", snap.vbusV, snap.battV, snap.pins.cs, snap.pins.sck, snap.pins.miso, snap.pins.mosi, (unsigned)snap.probeH.ffCount, (unsigned)snap.probeH.zeroCount, (unsigned)snap.probeH.otherCount, snap.probeH.firstBytes[0], snap.probeH.firstBytes[1], snap.probeH.firstBytes[2], snap.probeH.firstBytes[3], (unsigned)snap.probeF.ffCount, (unsigned)snap.probeF.zeroCount, (unsigned)snap.probeF.otherCount, snap.probeF.firstBytes[0], snap.probeF.firstBytes[1], snap.probeF.firstBytes[2], snap.probeF.firstBytes[3], snap.mountBus, (unsigned long)snap.mountHz, cardTypeToString(snap.cardType), snap.cardSizeMB, snap.rootOk ? "OK" : "FAIL" ); } static void showSnapshot(const DebugSnapshot& snap) { char line1[24]; char line2[24]; char line3[24]; char line4[24]; char line5[24]; snprintf(line1, sizeof(line1), "%s TF HWDBG", NODE_LABEL); snprintf(line2, sizeof(line2), "STATE %s", stateToString(snap.state)); snprintf(line3, sizeof(line3), "H %u/%u/%u F %u/%u/%u", (unsigned)snap.probeH.ffCount, (unsigned)snap.probeH.zeroCount, (unsigned)snap.probeH.otherCount, (unsigned)snap.probeF.ffCount, (unsigned)snap.probeF.zeroCount, (unsigned)snap.probeF.otherCount); snprintf(line4, sizeof(line4), "%s %luk %s", snap.mountBus, (unsigned long)(snap.mountHz / 1000UL), cardTypeToString(snap.cardType)); snprintf(line5, sizeof(line5), "P %d%d%d%d R%s %lu", snap.pins.cs, snap.pins.sck, snap.pins.miso, snap.pins.mosi, snap.railOn ? "1" : "0", (unsigned long)g_sampleCount); g_oled.clearBuffer(); g_oled.setFont(u8g2_font_5x8_tf); g_oled.drawUTF8(0, 12, line1); g_oled.drawUTF8(0, 24, line2); g_oled.drawUTF8(0, 36, line3); g_oled.drawUTF8(0, 48, line4); g_oled.drawUTF8(0, 60, line5); g_oled.sendBuffer(); } void setup() { Serial.begin(115200); g_bootMs = millis(); delay(kSerialDelayMs); Wire.begin(OLED_SDA, OLED_SCL); g_oled.begin(); g_oled.clearDisplay(); tbeam_supreme::initPmuForPeripherals(g_pmu, &Serial); forceSpiDeselected(); Serial.println(); Serial.println("constantTFCard hardware debug"); Serial.printf("Node: %s\r\n", NODE_LABEL); Serial.printf("Poll interval: %lu ms\r\n", (unsigned long)kPollIntervalMs); Serial.println("States: PMU_FAIL RAIL_OFF NO_RESP BUS_LOW BUS_CHAT BEGIN_FAIL CARD_NONE FS_FAIL MOUNT_OK"); Serial.printf("Startup quiet delay: %lu ms\r\n", (unsigned long)kStartupQuietMs); Serial.printf("Startup replay window: %lu ms\r\n", (unsigned long)kStartupReplayWindowMs); g_oled.clearBuffer(); g_oled.setFont(u8g2_font_5x8_tf); g_oled.drawUTF8(0, 12, "TF HWDBG"); g_oled.drawUTF8(0, 24, "startup hold"); g_oled.drawUTF8(0, 36, "attach monitor"); g_oled.drawUTF8(0, 48, "waiting..."); g_oled.sendBuffer(); delay(kStartupQuietMs); printStartupBanner(); } void loop() { static uint32_t lastPollMs = 0; uint32_t now = millis(); if (now - lastPollMs < kPollIntervalMs) { delay(5); return; } lastPollMs = now; g_sampleCount++; DebugSnapshot snap = captureSnapshot(); if (now - g_bootMs <= kStartupReplayWindowMs && now - g_lastStartupReplayMs >= kStartupReplayPeriodMs) { g_lastStartupReplayMs = now; printStartupBanner(); } printSnapshot(snap); showSnapshot(snap); }