443 lines
12 KiB
C++
443 lines
12 KiB
C++
// 20260401 Codex
|
|
|
|
#include <Arduino.h>
|
|
#include <esp_system.h>
|
|
#include <SPI.h>
|
|
#include <SD.h>
|
|
#include <Wire.h>
|
|
#include <U8g2lib.h>
|
|
#include <driver/gpio.h>
|
|
|
|
#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);
|
|
}
|