Compare commits
6 commits
643042e8a2
...
41c1fe6819
| Author | SHA1 | Date | |
|---|---|---|---|
| 41c1fe6819 | |||
| c7646e169e | |||
| ab37d32b6d | |||
| 0395fed907 | |||
| b5ff96d6a9 | |||
| 76c4b010bf |
35 changed files with 4136 additions and 80 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -17,3 +17,4 @@
|
|||
/hold/
|
||||
.platformio_local/
|
||||
|
||||
.codex
|
||||
|
|
|
|||
0
docs/gps.md
Normal file
0
docs/gps.md
Normal file
134
docs/pio.md
Normal file
134
docs/pio.md
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
# pio
|
||||
pio stands for "PlatformIO".
|
||||
|
||||
In your context (ESP32 / T-Beam / PlatformIO workflows), **“pio” stands for:**
|
||||
|
||||
> **PlatformIO**
|
||||
|
||||
More precisely, `pio` is the **command-line interface (CLI)** for the PlatformIO environment.
|
||||
|
||||
---
|
||||
|
||||
## What PlatformIO is (practically speaking)
|
||||
|
||||
PlatformIO is an embedded development framework that wraps:
|
||||
|
||||
* toolchains (GCC for ESP32, ARM, etc.)
|
||||
* build system
|
||||
* library manager
|
||||
* uploader (esptool, etc.)
|
||||
* serial monitor
|
||||
|
||||
So instead of juggling:
|
||||
|
||||
```bash
|
||||
xtensa-esp32-elf-gcc
|
||||
esptool.py
|
||||
arduino-cli
|
||||
```
|
||||
|
||||
…you use:
|
||||
|
||||
```bash
|
||||
pio run
|
||||
pio run -t upload
|
||||
pio device monitor
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What `pio` actually does in your workflow
|
||||
|
||||
Given your T-Beam usage (as shown in the LilyGO docs, which explicitly include PlatformIO as a supported environment ), `pio` is orchestrating:
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
pio run
|
||||
```
|
||||
|
||||
* compiles your firmware using the environment in `platformio.ini`
|
||||
|
||||
### Upload
|
||||
|
||||
```bash
|
||||
pio run -t upload
|
||||
```
|
||||
|
||||
* invokes the correct uploader (ESP32 → `esptool`)
|
||||
* uses the selected `/dev/tty*` device
|
||||
|
||||
### Monitor
|
||||
|
||||
```bash
|
||||
pio device monitor
|
||||
```
|
||||
|
||||
* serial console at configured baud
|
||||
|
||||
---
|
||||
|
||||
## Why it exists (the “systems view”)
|
||||
|
||||
PlatformIO abstracts away:
|
||||
|
||||
* board definitions
|
||||
* pin mappings
|
||||
* compiler flags
|
||||
* library dependencies
|
||||
|
||||
In your `platformio.ini`, for example:
|
||||
|
||||
```ini
|
||||
[env:node_a]
|
||||
platform = espressif32
|
||||
board = esp32-s3-devkitc-1
|
||||
```
|
||||
|
||||
That single block drives:
|
||||
|
||||
* toolchain selection
|
||||
* linker scripts
|
||||
* upload protocol
|
||||
* memory layout
|
||||
|
||||
---
|
||||
|
||||
## Important nuance (since you’re working low-level)
|
||||
|
||||
`pio` is **not**:
|
||||
|
||||
* a compiler
|
||||
* a flasher
|
||||
* an IDE
|
||||
|
||||
It is a **build orchestration layer** (think “make + package manager + uploader + board database”).
|
||||
|
||||
---
|
||||
|
||||
## Quick mental model
|
||||
|
||||
If we translate to Unix analogies:
|
||||
|
||||
| Concept | Analogy |
|
||||
| ------------------- | -------------------------------------------- |
|
||||
| PlatformIO | `portage + make + gcc + flash tool` combined |
|
||||
| `pio run` | `make` |
|
||||
| `pio run -t upload` | `make install` (but to hardware) |
|
||||
| `platformio.ini` | hybrid of `Makefile` + config |
|
||||
|
||||
---
|
||||
|
||||
## Bottom line
|
||||
|
||||
When you type:
|
||||
|
||||
```bash
|
||||
pio run -t upload
|
||||
```
|
||||
|
||||
You are saying:
|
||||
|
||||
> “Use PlatformIO to build this firmware for the specified environment and flash it to the target device.”
|
||||
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
## Exercise 09: GPS Time (L76K)
|
||||
## Exercise 09: GPS Time (L76K + UBLOX)
|
||||
|
||||
This exercise boots the T-Beam Supreme and verifies GPS behavior at startup.
|
||||
|
||||
|
|
@ -13,11 +13,10 @@ Implemented behavior:
|
|||
1. Initializes PMU, OLED, and SD startup watcher (same startup SD path used in Exercise 08).
|
||||
2. Probes GPS at startup for NMEA traffic, module identity, satellite count, and UTC time availability.
|
||||
- Uses explicit GPS UART pins and an active startup probe (multi-baud + common GPS query commands), aligned with the approach validated in Exercise 10.
|
||||
3. If L76K is detected, normal GPS-time flow continues.
|
||||
4. If L76K is not detected and Quectel-style module text is detected, OLED shows a hard TODO error:
|
||||
- Quectel detected
|
||||
- L76K required
|
||||
- Quectel support is TODO
|
||||
3. Supports both module profiles via `platformio.ini` build flags:
|
||||
- `node_a` / `node_b`: `GPS_L76K`
|
||||
- `node_c`: `GPS_UBLOX`
|
||||
4. If detected module data conflicts with the selected node profile, OLED shows a `GPS module mismatch` error.
|
||||
5. Every minute:
|
||||
- If GPS UTC is valid: shows GPS UTC time and satellites on OLED.
|
||||
- If satellites are seen but UTC is not valid yet: shows that condition and RTC time.
|
||||
|
|
@ -31,8 +30,12 @@ Implemented behavior:
|
|||
Notes:
|
||||
|
||||
- GPS time displayed is UTC from NMEA RMC with valid status.
|
||||
- Satellite count uses best available from GGA/GSV.
|
||||
- Satellite count uses best available from GGA/GSA/GSV.
|
||||
- RTC fallback reads PCF8563 via Wire1.
|
||||
- For UBLOX hardware use `-e node_c`.
|
||||
- The UBLOX MAX-M10S path is given a longer startup window than L76K because cold starts are slower, especially if backup power/orbit data are unavailable.
|
||||
- On T-Beam Supreme, `GPS_WAKEUP_PIN=7` is relevant for the L76K variant; the UBLOX MAX-M10S does not use that wake pin in the same way.
|
||||
- For fastest UBLOX reacquisition, test with the 18650 attached so the GNSS backup domain can preserve assistance state across resets/power cycles.
|
||||
|
||||
## Build
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ build_flags =
|
|||
-D GPS_1PPS_PIN=6
|
||||
-D ARDUINO_USB_MODE=1
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||
-D GPS_L76K
|
||||
|
||||
[env:node_a]
|
||||
build_flags =
|
||||
|
|
@ -39,3 +40,9 @@ build_flags =
|
|||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"B\"
|
||||
|
||||
[env:node_c]
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"C\"
|
||||
-D GPS_UBLOX
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -28,6 +28,7 @@ build_flags =
|
|||
-D GPS_1PPS_PIN=6
|
||||
-D ARDUINO_USB_MODE=1
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||
-D GPS_L76K
|
||||
|
||||
[env:node_a]
|
||||
build_flags =
|
||||
|
|
@ -38,3 +39,9 @@ build_flags =
|
|||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"B\"
|
||||
|
||||
[env:node_c]
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"C\"
|
||||
-D GPS_UBLOX
|
||||
|
|
@ -28,6 +28,7 @@ build_flags =
|
|||
-D GPS_TX_PIN=8
|
||||
-D GPS_WAKEUP_PIN=7
|
||||
-D GPS_1PPS_PIN=6
|
||||
-D GPS_L76K
|
||||
-D ARDUINO_USB_MODE=1
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||
|
||||
|
|
|
|||
11
exercises/12_FiveTalk/READEME.md
Normal file
11
exercises/12_FiveTalk/READEME.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
|
||||
main.cpp needs to be modified to reflect the number of units. It is a zero-based array, so for 7 possible unite, the value of 6 is used in both lines below:
|
||||
|
||||
#if (NODE_SLOT_INDEX < 0) || (NODE_SLOT_INDEX > 6)
|
||||
#error "NODE_SLOT_INDEX must be 0..6"
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
INSERT SCREENSHOT HERE.
|
||||
|
|
@ -27,6 +27,8 @@ build_flags =
|
|||
-D GPS_TX_PIN=8
|
||||
-D GPS_WAKEUP_PIN=7
|
||||
-D GPS_1PPS_PIN=6
|
||||
-D GPS_L76K
|
||||
-D NODE_SLOT_COUNT=7
|
||||
-D LORA_CS=10
|
||||
-D LORA_MOSI=11
|
||||
-D LORA_SCK=12
|
||||
|
|
@ -77,3 +79,22 @@ build_flags =
|
|||
-D NODE_LABEL=\"Ed\"
|
||||
-D NODE_SHORT=\"E\"
|
||||
-D NODE_SLOT_INDEX=4
|
||||
|
||||
[env:flo]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"Flo\"
|
||||
-D NODE_SHORT=\"F\"
|
||||
-D NODE_SLOT_INDEX=5
|
||||
|
||||
[env:guy]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"Guy\"
|
||||
-D NODE_SHORT=\"G\"
|
||||
-D NODE_SLOT_INDEX=6
|
||||
-D GPS_UBLOX
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@
|
|||
#define NODE_SHORT "?"
|
||||
#endif
|
||||
|
||||
#ifndef NODE_SLOT_COUNT
|
||||
#define NODE_SLOT_COUNT 7
|
||||
#endif
|
||||
|
||||
#ifndef NODE_SLOT_INDEX
|
||||
#define NODE_SLOT_INDEX 0
|
||||
#endif
|
||||
|
|
@ -63,8 +67,8 @@
|
|||
#define FW_BUILD_UTC "unknown"
|
||||
#endif
|
||||
|
||||
#if (NODE_SLOT_INDEX < 0) || (NODE_SLOT_INDEX > 4)
|
||||
#error "NODE_SLOT_INDEX must be 0..4"
|
||||
#if (NODE_SLOT_INDEX < 0) || (NODE_SLOT_INDEX >= NODE_SLOT_COUNT)
|
||||
#error "NODE_SLOT_INDEX must be 0..NODE_SLOT_COUNT-1"
|
||||
#endif
|
||||
|
||||
static const uint32_t kSerialDelayMs = 1000;
|
||||
|
|
@ -156,6 +160,20 @@ static uint8_t bestSatelliteCount()
|
|||
return (g_gps.satsUsed > g_gps.satsInView) ? g_gps.satsUsed : g_gps.satsInView;
|
||||
}
|
||||
|
||||
static uint32_t computeFrameSeconds(uint32_t requiredSeconds)
|
||||
{
|
||||
uint32_t frame = ((requiredSeconds + 4U) / 5U) * 5U; // round up to 5s
|
||||
while (frame <= 60U && (60U % frame) != 0U)
|
||||
{
|
||||
frame += 5U;
|
||||
}
|
||||
if (frame == 0U || frame > 60U)
|
||||
{
|
||||
frame = 60U; // fallback
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
static void logf(const char *fmt, ...)
|
||||
{
|
||||
char msg[256];
|
||||
|
|
@ -1030,7 +1048,7 @@ static bool initRadio()
|
|||
return false;
|
||||
}
|
||||
|
||||
logf("Radio ready for %s (%s), slot=%d sec=%d", NODE_LABEL, NODE_SHORT, NODE_SLOT_INDEX, NODE_SLOT_INDEX * 2);
|
||||
logf("Radio ready for %s (%s), slot=%d/%d (2s each)", NODE_LABEL, NODE_SHORT, NODE_SLOT_INDEX, NODE_SLOT_COUNT);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -1051,8 +1069,15 @@ static void runTxScheduler()
|
|||
if (!getCurrentUtc(now, epoch))
|
||||
return;
|
||||
|
||||
int slotSecond = NODE_SLOT_INDEX * (int)kSlotSeconds;
|
||||
int secInFrame = now.second % 10;
|
||||
uint32_t requiredTxSeconds = (uint32_t)NODE_SLOT_COUNT * kSlotSeconds;
|
||||
uint32_t frameSeconds = computeFrameSeconds(requiredTxSeconds);
|
||||
uint32_t slotSecond = (uint32_t)NODE_SLOT_INDEX * kSlotSeconds;
|
||||
if (slotSecond >= frameSeconds)
|
||||
return;
|
||||
|
||||
uint32_t secInFrame = (uint32_t)now.second % frameSeconds;
|
||||
if (secInFrame >= requiredTxSeconds)
|
||||
return; // idle guard interval
|
||||
if (secInFrame != slotSecond)
|
||||
return;
|
||||
|
||||
|
|
|
|||
16
exercises/15_RAM/README.md
Normal file
16
exercises/15_RAM/README.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# Exercise 15: RAM
|
||||
|
||||
This exercise shows available RAM on the console and on the OLED display on a T-Beam Supreme.
|
||||
|
||||
Behavior:
|
||||
- Reports heap statistics every second over serial.
|
||||
- Shows live heap status on the OLED display.
|
||||
- Designed as the first step toward volatile /tmp RAM-backed storage.
|
||||
|
||||
Build and upload:
|
||||
|
||||
```bash
|
||||
cd /usr/local/src/microreticulum/microReticulumTbeam/exercises/15_RAM
|
||||
pio run -e amy -t upload
|
||||
pio device monitor -b 115200
|
||||
```
|
||||
55
exercises/15_RAM/platformio.ini
Normal file
55
exercises/15_RAM/platformio.ini
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
; 20260403 ChatGPT
|
||||
; Exercise 15_RAM
|
||||
|
||||
[platformio]
|
||||
default_envs = amy
|
||||
|
||||
[env]
|
||||
platform = espressif32
|
||||
framework = arduino
|
||||
board = esp32-s3-devkitc-1
|
||||
monitor_speed = 115200
|
||||
extra_scripts = pre:scripts/set_build_epoch.py
|
||||
lib_deps =
|
||||
Wire
|
||||
olikraus/U8g2@^2.36.4
|
||||
|
||||
build_flags =
|
||||
-I ../../shared/boards
|
||||
-I ../../external/microReticulum_Firmware
|
||||
-D BOARD_MODEL=BOARD_TBEAM_S_V1
|
||||
-D OLED_SDA=17
|
||||
-D OLED_SCL=18
|
||||
-D OLED_ADDR=0x3C
|
||||
-D ARDUINO_USB_MODE=1
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||
|
||||
[env:amy]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"AMY\"
|
||||
|
||||
[env:bob]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"BOB\"
|
||||
|
||||
[env:cy]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"CY\"
|
||||
|
||||
[env:dan]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"DAN\"
|
||||
|
||||
[env:ed]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"ED\"
|
||||
12
exercises/15_RAM/scripts/set_build_epoch.py
Normal file
12
exercises/15_RAM/scripts/set_build_epoch.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import time
|
||||
Import("env")
|
||||
|
||||
epoch = int(time.time())
|
||||
utc_tag = time.strftime("%Y%m%d_%H%M%S_z", time.gmtime(epoch))
|
||||
|
||||
env.Append(
|
||||
CPPDEFINES=[
|
||||
("FW_BUILD_EPOCH", str(epoch)),
|
||||
("FW_BUILD_UTC", '\\"%s\\"' % utc_tag),
|
||||
]
|
||||
)
|
||||
272
exercises/15_RAM/src/main.cpp
Normal file
272
exercises/15_RAM/src/main.cpp
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
// 20260403 ChatGPT
|
||||
// Exercise 15_RAM
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include <U8g2lib.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifndef NODE_LABEL
|
||||
#define NODE_LABEL "RAM"
|
||||
#endif
|
||||
|
||||
#ifndef OLED_SDA
|
||||
#define OLED_SDA 17
|
||||
#endif
|
||||
#ifndef OLED_SCL
|
||||
#define OLED_SCL 18
|
||||
#endif
|
||||
#ifndef OLED_ADDR
|
||||
#define OLED_ADDR 0x3C
|
||||
#endif
|
||||
|
||||
static U8G2_SH1106_128X64_NONAME_F_HW_I2C g_oled(U8G2_R0, U8X8_PIN_NONE);
|
||||
|
||||
static const char *kTmpPath = "/tmp/AMY_output.log";
|
||||
static const size_t kTmpFileCapacity = 32768;
|
||||
static char g_tmpFileBuffer[kTmpFileCapacity];
|
||||
static size_t g_tmpFileSize = 0;
|
||||
static unsigned g_tmpLineNumber = 0;
|
||||
|
||||
static void oledShowLines(const char *l1,
|
||||
const char *l2 = nullptr,
|
||||
const char *l3 = nullptr,
|
||||
const char *l4 = nullptr,
|
||||
const char *l5 = nullptr)
|
||||
{
|
||||
g_oled.clearBuffer();
|
||||
g_oled.setFont(u8g2_font_5x8_tf);
|
||||
if (l1) g_oled.drawUTF8(0, 12, l1);
|
||||
if (l2) g_oled.drawUTF8(0, 24, l2);
|
||||
if (l3) g_oled.drawUTF8(0, 36, l3);
|
||||
if (l4) g_oled.drawUTF8(0, 48, l4);
|
||||
if (l5) g_oled.drawUTF8(0, 60, l5);
|
||||
g_oled.sendBuffer();
|
||||
}
|
||||
|
||||
static size_t getAvailableRamBytes()
|
||||
{
|
||||
return ESP.getFreeHeap();
|
||||
}
|
||||
|
||||
static void getTimestamp(char *out, size_t outSize)
|
||||
{
|
||||
const time_t now = time(nullptr);
|
||||
if (now > 1700000000) {
|
||||
struct tm tmNow;
|
||||
localtime_r(&now, &tmNow);
|
||||
snprintf(out, outSize, "%04d-%02d-%02d %02d:%02d:%02d",
|
||||
tmNow.tm_year + 1900,
|
||||
tmNow.tm_mon + 1,
|
||||
tmNow.tm_mday,
|
||||
tmNow.tm_hour,
|
||||
tmNow.tm_min,
|
||||
tmNow.tm_sec);
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t sec = millis() / 1000;
|
||||
const uint32_t hh = sec / 3600;
|
||||
const uint32_t mm = (sec % 3600) / 60;
|
||||
const uint32_t ss = sec % 60;
|
||||
snprintf(out, outSize, "uptime %02u:%02u:%02u", (unsigned)hh, (unsigned)mm, (unsigned)ss);
|
||||
}
|
||||
|
||||
static void appendTimestampLine()
|
||||
{
|
||||
char timestamp[32];
|
||||
getTimestamp(timestamp, sizeof(timestamp));
|
||||
|
||||
char line[96];
|
||||
const int written = snprintf(line, sizeof(line), "%u, %s\r\n", g_tmpLineNumber + 1, timestamp);
|
||||
if (written <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t lineLen = (size_t)written;
|
||||
if (g_tmpFileSize + lineLen > kTmpFileCapacity - 1) {
|
||||
Serial.println("Warning: /tmp log full, stopping writes");
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(g_tmpFileBuffer + g_tmpFileSize, line, lineLen);
|
||||
g_tmpFileSize += lineLen;
|
||||
g_tmpLineNumber++;
|
||||
}
|
||||
|
||||
static void printRamStatus()
|
||||
{
|
||||
const size_t freeBytes = getAvailableRamBytes();
|
||||
const size_t totalBytes = ESP.getHeapSize();
|
||||
const size_t maxAlloc = ESP.getMaxAllocHeap();
|
||||
|
||||
Serial.printf("RAM total=%u free=%u maxAlloc=%u\r\n", (unsigned)totalBytes, (unsigned)freeBytes, (unsigned)maxAlloc);
|
||||
|
||||
char line1[32];
|
||||
char line2[32];
|
||||
char line3[32];
|
||||
|
||||
snprintf(line1, sizeof(line1), "Exercise 15 RAM");
|
||||
snprintf(line2, sizeof(line2), "Node: %s", NODE_LABEL);
|
||||
snprintf(line3, sizeof(line3), "Free: %u KB", (unsigned)(freeBytes / 1024U));
|
||||
char line4[32];
|
||||
snprintf(line4, sizeof(line4), "Total: %u KB", (unsigned)(totalBytes / 1024U));
|
||||
char line5[32];
|
||||
snprintf(line5, sizeof(line5), "Lines: %u", (unsigned)g_tmpLineNumber);
|
||||
|
||||
oledShowLines(line1, line2, line3, line4, line5);
|
||||
}
|
||||
|
||||
static void showHelp()
|
||||
{
|
||||
Serial.println("RAM command list:");
|
||||
Serial.println(" help - show this menu");
|
||||
Serial.println(" stat - show /tmp file state");
|
||||
Serial.println(" read - read /tmp contents");
|
||||
Serial.println(" clear - clear /tmp contents");
|
||||
Serial.println(" write <text> - write text to /tmp");
|
||||
Serial.println(" append <text> - append text to /tmp");
|
||||
}
|
||||
|
||||
static void printTmpFileStat()
|
||||
{
|
||||
Serial.printf("Path: %s\r\n", kTmpPath);
|
||||
Serial.printf("Size: %u bytes\r\n", (unsigned)g_tmpFileSize);
|
||||
Serial.printf("Lines: %u\r\n", (unsigned)g_tmpLineNumber);
|
||||
Serial.printf("Capacity: %u bytes\r\n", (unsigned)kTmpFileCapacity);
|
||||
}
|
||||
|
||||
static void printTmpFileContents()
|
||||
{
|
||||
if (g_tmpFileSize == 0) {
|
||||
Serial.println("/tmp file is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.print("/tmp contents: ");
|
||||
Serial.write((const uint8_t *)g_tmpFileBuffer, g_tmpFileSize);
|
||||
if (g_tmpFileBuffer[g_tmpFileSize - 1] != '\n')
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
static void setTmpFileContent(const char *text)
|
||||
{
|
||||
if (!text) {
|
||||
g_tmpFileSize = 0;
|
||||
return;
|
||||
}
|
||||
const size_t newLen = strlen(text);
|
||||
if (newLen > kTmpFileCapacity - 1) {
|
||||
Serial.printf("Error: content too large (%u/%u)\r\n", (unsigned)newLen, (unsigned)kTmpFileCapacity);
|
||||
return;
|
||||
}
|
||||
memcpy(g_tmpFileBuffer, text, newLen);
|
||||
g_tmpFileSize = newLen;
|
||||
}
|
||||
|
||||
static void appendTmpFileContent(const char *text)
|
||||
{
|
||||
if (!text || text[0] == '\0') return;
|
||||
const size_t textLen = strlen(text);
|
||||
if (g_tmpFileSize + textLen > kTmpFileCapacity - 1) {
|
||||
Serial.printf("Error: append would exceed %u bytes\r\n", (unsigned)kTmpFileCapacity);
|
||||
return;
|
||||
}
|
||||
memcpy(g_tmpFileBuffer + g_tmpFileSize, text, textLen);
|
||||
g_tmpFileSize += textLen;
|
||||
}
|
||||
|
||||
static void processSerialCommand(const char *line)
|
||||
{
|
||||
if (!line || line[0] == '\0') return;
|
||||
|
||||
char tmp[384];
|
||||
strncpy(tmp, line, sizeof(tmp) - 1);
|
||||
tmp[sizeof(tmp) - 1] = '\0';
|
||||
|
||||
char *cmd = strtok(tmp, " \t\r\n");
|
||||
if (!cmd) return;
|
||||
|
||||
if (strcasecmp(cmd, "help") == 0) {
|
||||
showHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcasecmp(cmd, "stat") == 0) {
|
||||
printTmpFileStat();
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcasecmp(cmd, "read") == 0) {
|
||||
printTmpFileContents();
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcasecmp(cmd, "clear") == 0) {
|
||||
g_tmpFileSize = 0;
|
||||
Serial.println("/tmp cleared");
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcasecmp(cmd, "write") == 0 || strcasecmp(cmd, "append") == 0) {
|
||||
const char *payload = line + strlen(cmd);
|
||||
while (*payload == ' ' || *payload == '\t') payload++;
|
||||
if (strcasecmp(cmd, "write") == 0)
|
||||
setTmpFileContent(payload);
|
||||
else
|
||||
appendTmpFileContent(payload);
|
||||
|
||||
Serial.printf("%s: %u bytes\r\n", cmd,
|
||||
(unsigned)g_tmpFileSize);
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.println("Unknown command (help for list)");
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
delay(800);
|
||||
Serial.println("Exercise 15_RAM boot");
|
||||
|
||||
Wire.begin(OLED_SDA, OLED_SCL);
|
||||
g_oled.setI2CAddress(OLED_ADDR << 1);
|
||||
g_oled.begin();
|
||||
oledShowLines("Exercise 15_RAM", "Node: " NODE_LABEL, "Booting...");
|
||||
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
static uint32_t lastMs = 0;
|
||||
const uint32_t now = millis();
|
||||
|
||||
// check serial commands at all times
|
||||
static char rxLine[384];
|
||||
static size_t rxLen = 0;
|
||||
|
||||
while (Serial.available()) {
|
||||
int c = Serial.read();
|
||||
if (c <= 0) continue;
|
||||
if (c == '\r' || c == '\n') {
|
||||
if (rxLen > 0) {
|
||||
rxLine[rxLen] = '\0';
|
||||
processSerialCommand(rxLine);
|
||||
rxLen = 0;
|
||||
}
|
||||
} else if (rxLen + 1 < sizeof(rxLine)) {
|
||||
rxLine[rxLen++] = (char)c;
|
||||
}
|
||||
}
|
||||
|
||||
if (now - lastMs < 1000) {
|
||||
delay(10);
|
||||
return;
|
||||
}
|
||||
lastMs = now;
|
||||
|
||||
appendTimestampLine();
|
||||
printRamStatus();
|
||||
}
|
||||
32
exercises/16_PSRAM/README.md
Normal file
32
exercises/16_PSRAM/README.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Exercise 16: PSRAM
|
||||
|
||||
This exercise demonstrates usage of PSRAM (Pseudo SRAM) on an ESP32-S3 board, alongside regular RAM metrics.
|
||||
|
||||
Behavior:
|
||||
- Reports heap and PSRAM statistics every second over serial.
|
||||
- Shows live heap and PSRAM status on the OLED display (both on same line).
|
||||
- Allows you to write/append/read/clear data in a PSRAM-backed buffer (up to ~2MB).
|
||||
- Designed as an extension of Exercise 15_RAM to explore larger volatile storage.
|
||||
|
||||
Note: the exercise now targets a PSRAM-enabled ESP32-S3 board definition (`freenove_esp32_s3_wroom`). This board profile has 8MB flash + 8MB PSRAM, matching the T-Beam Supreme specifications. If your hardware differs, adjust accordingly.
|
||||
|
||||
Sources:
|
||||
- LilyGo T-Beam SUPREME datasheet/wiki: https://wiki.lilygo.cc/get_started/en/LoRa_GPS/T-Beam-SUPREME/T-Beam-SUPREME.html
|
||||
- PlatformIO board definition: https://docs.platformio.org/page/boards/espressif32/freenove_esp32_s3_wroom.html
|
||||
- Local PlatformIO board metadata: ~/.platformio/platforms/espressif32/boards/freenove_esp32_s3_wroom.json
|
||||
|
||||
Build and upload:
|
||||
|
||||
```bash
|
||||
cd /usr/local/src/microreticulum/microReticulumTbeam/exercises/16_PSRAM
|
||||
pio run -e amy -t upload
|
||||
pio device monitor -b 115200
|
||||
```
|
||||
|
||||
Commands:
|
||||
- `help` - show command menu
|
||||
- `stat` - show PSRAM buffer state
|
||||
- `read` - read PSRAM buffer contents
|
||||
- `clear` - clear PSRAM buffer
|
||||
- `write <text>` - write text to PSRAM buffer
|
||||
- `append <text>` - append text to PSRAM buffer
|
||||
54
exercises/16_PSRAM/platformio.ini
Normal file
54
exercises/16_PSRAM/platformio.ini
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
; 20260403 ChatGPT
|
||||
; Exercise 16_PSRAM
|
||||
|
||||
[platformio]
|
||||
default_envs = amy
|
||||
|
||||
[env]
|
||||
platform = espressif32
|
||||
framework = arduino
|
||||
board = freenove_esp32_s3_wroom
|
||||
monitor_speed = 115200
|
||||
extra_scripts = pre:scripts/set_build_epoch.py
|
||||
lib_deps =
|
||||
Wire
|
||||
olikraus/U8g2@^2.36.4
|
||||
|
||||
build_flags =
|
||||
-I ../../shared/boards
|
||||
-I ../../external/microReticulum_Firmware
|
||||
-D BOARD_MODEL=BOARD_TBEAM_S_V1
|
||||
-D OLED_SDA=17
|
||||
-D OLED_SCL=18
|
||||
-D OLED_ADDR=0x3C
|
||||
-D ARDUINO_USB_MODE=1
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||
|
||||
board_build.flash_mode = qio
|
||||
board_build.psram = 1
|
||||
board_build.psram_type = spi
|
||||
board_build.arduino.memory_type = qio_qspi
|
||||
|
||||
[env:amy]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"AMY\"
|
||||
|
||||
[env:bob]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"BOB\"
|
||||
|
||||
[env:cy]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"CY\"
|
||||
|
||||
[env:dan]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"DAN\"
|
||||
12
exercises/16_PSRAM/scripts/set_build_epoch.py
Normal file
12
exercises/16_PSRAM/scripts/set_build_epoch.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import time
|
||||
Import("env")
|
||||
|
||||
epoch = int(time.time())
|
||||
utc_tag = time.strftime("%Y%m%d_%H%M%S_z", time.gmtime(epoch))
|
||||
|
||||
env.Append(
|
||||
CPPDEFINES=[
|
||||
("FW_BUILD_EPOCH", str(epoch)),
|
||||
("FW_BUILD_UTC", '\\"%s\\"' % utc_tag),
|
||||
]
|
||||
)
|
||||
342
exercises/16_PSRAM/src/main.cpp
Normal file
342
exercises/16_PSRAM/src/main.cpp
Normal file
|
|
@ -0,0 +1,342 @@
|
|||
// 20260403 ChatGPT
|
||||
// Exercise 16_PSRAM - Extended Exercise 15_RAM with PSRAM support
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include <U8g2lib.h>
|
||||
#include <time.h>
|
||||
#include <esp_heap_caps.h>
|
||||
|
||||
#ifndef NODE_LABEL
|
||||
#define NODE_LABEL "PSRAM"
|
||||
#endif
|
||||
|
||||
#ifndef OLED_SDA
|
||||
#define OLED_SDA 17
|
||||
#endif
|
||||
#ifndef OLED_SCL
|
||||
#define OLED_SCL 18
|
||||
#endif
|
||||
#ifndef OLED_ADDR
|
||||
#define OLED_ADDR 0x3C
|
||||
#endif
|
||||
|
||||
static U8G2_SH1106_128X64_NONAME_F_HW_I2C g_oled(U8G2_R0, U8X8_PIN_NONE);
|
||||
|
||||
static const size_t kTmpFileCapacity = 2097152; // 2MB in PSRAM
|
||||
static char *g_tmpFileBuffer = nullptr;
|
||||
static size_t g_tmpFileSize = 0;
|
||||
static unsigned g_tmpLineNumber = 0;
|
||||
|
||||
static void oledShowLines(const char *l1,
|
||||
const char *l2 = nullptr,
|
||||
const char *l3 = nullptr,
|
||||
const char *l4 = nullptr,
|
||||
const char *l5 = nullptr)
|
||||
{
|
||||
g_oled.clearBuffer();
|
||||
g_oled.setFont(u8g2_font_5x8_tf);
|
||||
if (l1) g_oled.drawUTF8(0, 12, l1);
|
||||
if (l2) g_oled.drawUTF8(0, 24, l2);
|
||||
if (l3) g_oled.drawUTF8(0, 36, l3);
|
||||
if (l4) g_oled.drawUTF8(0, 48, l4);
|
||||
if (l5) g_oled.drawUTF8(0, 60, l5);
|
||||
g_oled.sendBuffer();
|
||||
}
|
||||
|
||||
static size_t getAvailableRamBytes()
|
||||
{
|
||||
return ESP.getFreeHeap();
|
||||
}
|
||||
|
||||
static size_t getTotalRamBytes()
|
||||
{
|
||||
return ESP.getHeapSize();
|
||||
}
|
||||
|
||||
static size_t getPSRAMFreeBytes()
|
||||
{
|
||||
size_t freeBytes = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
|
||||
if (freeBytes == 0 && ESP.getFreePsram() > 0) {
|
||||
freeBytes = ESP.getFreePsram();
|
||||
}
|
||||
return freeBytes;
|
||||
}
|
||||
|
||||
static size_t getPSRAMTotalBytes()
|
||||
{
|
||||
size_t totalBytes = heap_caps_get_total_size(MALLOC_CAP_SPIRAM);
|
||||
if (totalBytes == 0 && ESP.getPsramSize() > 0) {
|
||||
totalBytes = ESP.getPsramSize();
|
||||
}
|
||||
return totalBytes;
|
||||
}
|
||||
|
||||
static void getTimestamp(char *out, size_t outSize)
|
||||
{
|
||||
const time_t now = time(nullptr);
|
||||
if (now > 1700000000) {
|
||||
struct tm tmNow;
|
||||
localtime_r(&now, &tmNow);
|
||||
snprintf(out, outSize, "%04d-%02d-%02d %02d:%02d:%02d",
|
||||
tmNow.tm_year + 1900,
|
||||
tmNow.tm_mon + 1,
|
||||
tmNow.tm_mday,
|
||||
tmNow.tm_hour,
|
||||
tmNow.tm_min,
|
||||
tmNow.tm_sec);
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t sec = millis() / 1000;
|
||||
const uint32_t hh = sec / 3600;
|
||||
const uint32_t mm = (sec % 3600) / 60;
|
||||
const uint32_t ss = sec % 60;
|
||||
snprintf(out, outSize, "uptime %02u:%02u:%02u", (unsigned)hh, (unsigned)mm, (unsigned)ss);
|
||||
}
|
||||
|
||||
static void appendTimestampLine()
|
||||
{
|
||||
if (!g_tmpFileBuffer) return;
|
||||
|
||||
char timestamp[32];
|
||||
getTimestamp(timestamp, sizeof(timestamp));
|
||||
|
||||
char line[96];
|
||||
const int written = snprintf(line, sizeof(line), "%u, %s\r\n", g_tmpLineNumber + 1, timestamp);
|
||||
if (written <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t lineLen = (size_t)written;
|
||||
if (g_tmpFileSize + lineLen > kTmpFileCapacity - 1) {
|
||||
Serial.println("Warning: PSRAM log full, stopping writes");
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(g_tmpFileBuffer + g_tmpFileSize, line, lineLen);
|
||||
g_tmpFileSize += lineLen;
|
||||
g_tmpLineNumber++;
|
||||
}
|
||||
|
||||
static void printRamStatus()
|
||||
{
|
||||
const size_t freeRam = getAvailableRamBytes();
|
||||
const size_t totalRam = getTotalRamBytes();
|
||||
const size_t maxAllocRam = ESP.getMaxAllocHeap();
|
||||
|
||||
const size_t freePSRAM = getPSRAMFreeBytes();
|
||||
const size_t totalPSRAM = getPSRAMTotalBytes();
|
||||
|
||||
Serial.printf("RAM total=%u free=%u maxAlloc=%u | PSRAM total=%u free=%u\r\n",
|
||||
(unsigned)totalRam, (unsigned)freeRam, (unsigned)maxAllocRam,
|
||||
(unsigned)totalPSRAM, (unsigned)freePSRAM);
|
||||
|
||||
char line1[32];
|
||||
char line2[32];
|
||||
char line3[32];
|
||||
char line4[32];
|
||||
char line5[32];
|
||||
|
||||
snprintf(line1, sizeof(line1), "Exercise 16 PSRAM");
|
||||
snprintf(line2, sizeof(line2), "Node: %s", NODE_LABEL);
|
||||
|
||||
// Display format: "Free XXXKb/8.0Mbs"
|
||||
const float psramMb = totalPSRAM / (1024.0f * 1024.0f);
|
||||
const size_t ramKb = freeRam / 1024U;
|
||||
snprintf(line3, sizeof(line3), "Free %uKb/%.1fMbs", (unsigned)ramKb, psramMb);
|
||||
|
||||
snprintf(line4, sizeof(line4), "PSRAM: %u KB", (unsigned)(freePSRAM / 1024U));
|
||||
snprintf(line5, sizeof(line5), "Lines: %u", (unsigned)g_tmpLineNumber);
|
||||
|
||||
oledShowLines(line1, line2, line3, line4, line5);
|
||||
}
|
||||
|
||||
static void showHelp()
|
||||
{
|
||||
Serial.println("PSRAM command list:");
|
||||
Serial.println(" help - show this menu");
|
||||
Serial.println(" stat - show PSRAM buffer state");
|
||||
Serial.println(" read - read PSRAM buffer contents");
|
||||
Serial.println(" clear - clear PSRAM buffer contents");
|
||||
Serial.println(" write <text> - write text to PSRAM buffer");
|
||||
Serial.println(" append <text> - append text to PSRAM buffer");
|
||||
}
|
||||
|
||||
static void printPSRAMFileStat()
|
||||
{
|
||||
Serial.printf("PSRAM Buffer Capacity: %u bytes\r\n", (unsigned)kTmpFileCapacity);
|
||||
Serial.printf("Current Size: %u bytes\r\n", (unsigned)g_tmpFileSize);
|
||||
Serial.printf("Lines: %u\r\n", (unsigned)g_tmpLineNumber);
|
||||
Serial.printf("PSRAM Total: %u bytes (%.2f MB)\r\n", (unsigned)getPSRAMTotalBytes(),
|
||||
getPSRAMTotalBytes() / (1024.0f * 1024.0f));
|
||||
Serial.printf("PSRAM Free: %u bytes\r\n", (unsigned)getPSRAMFreeBytes());
|
||||
}
|
||||
|
||||
static void printPSRAMFileContents()
|
||||
{
|
||||
if (!g_tmpFileBuffer) {
|
||||
Serial.println("PSRAM buffer not allocated");
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_tmpFileSize == 0) {
|
||||
Serial.println("PSRAM buffer is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.print("PSRAM contents: ");
|
||||
Serial.write((const uint8_t *)g_tmpFileBuffer, g_tmpFileSize);
|
||||
if (g_tmpFileBuffer[g_tmpFileSize - 1] != '\n')
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
static void setPSRAMFileContent(const char *text)
|
||||
{
|
||||
if (!g_tmpFileBuffer) {
|
||||
Serial.println("Error: PSRAM buffer not allocated");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!text) {
|
||||
g_tmpFileSize = 0;
|
||||
g_tmpLineNumber = 0;
|
||||
return;
|
||||
}
|
||||
const size_t newLen = strlen(text);
|
||||
if (newLen > kTmpFileCapacity - 1) {
|
||||
Serial.printf("Error: content too large (%u/%u)\r\n", (unsigned)newLen, (unsigned)kTmpFileCapacity);
|
||||
return;
|
||||
}
|
||||
memcpy(g_tmpFileBuffer, text, newLen);
|
||||
g_tmpFileSize = newLen;
|
||||
g_tmpLineNumber = 0;
|
||||
}
|
||||
|
||||
static void appendPSRAMFileContent(const char *text)
|
||||
{
|
||||
if (!g_tmpFileBuffer) {
|
||||
Serial.println("Error: PSRAM buffer not allocated");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!text || text[0] == '\0') return;
|
||||
const size_t textLen = strlen(text);
|
||||
if (g_tmpFileSize + textLen > kTmpFileCapacity - 1) {
|
||||
Serial.printf("Error: append would exceed %u bytes\r\n", (unsigned)kTmpFileCapacity);
|
||||
return;
|
||||
}
|
||||
memcpy(g_tmpFileBuffer + g_tmpFileSize, text, textLen);
|
||||
g_tmpFileSize += textLen;
|
||||
}
|
||||
|
||||
static void processSerialCommand(const char *line)
|
||||
{
|
||||
if (!line || line[0] == '\0') return;
|
||||
|
||||
char tmp[384];
|
||||
strncpy(tmp, line, sizeof(tmp) - 1);
|
||||
tmp[sizeof(tmp) - 1] = '\0';
|
||||
|
||||
char *cmd = strtok(tmp, " \t\r\n");
|
||||
if (!cmd) return;
|
||||
|
||||
if (strcasecmp(cmd, "help") == 0) {
|
||||
showHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcasecmp(cmd, "stat") == 0) {
|
||||
printPSRAMFileStat();
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcasecmp(cmd, "read") == 0) {
|
||||
printPSRAMFileContents();
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcasecmp(cmd, "clear") == 0) {
|
||||
g_tmpFileSize = 0;
|
||||
g_tmpLineNumber = 0;
|
||||
Serial.println("PSRAM buffer cleared");
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcasecmp(cmd, "write") == 0 || strcasecmp(cmd, "append") == 0) {
|
||||
const char *payload = line + strlen(cmd);
|
||||
while (*payload == ' ' || *payload == '\t') payload++;
|
||||
if (strcasecmp(cmd, "write") == 0)
|
||||
setPSRAMFileContent(payload);
|
||||
else
|
||||
appendPSRAMFileContent(payload);
|
||||
|
||||
Serial.printf("%s: %u bytes\r\n", cmd,
|
||||
(unsigned)g_tmpFileSize);
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.println("Unknown command (help for list)");
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
delay(800);
|
||||
Serial.println("Exercise 16_PSRAM boot");
|
||||
|
||||
// Boot-time PSRAM diagnostics
|
||||
Serial.printf("Boot PSRAM size: %u bytes\r\n", (unsigned)ESP.getPsramSize());
|
||||
Serial.printf("Boot PSRAM free: %u bytes\r\n", (unsigned)ESP.getFreePsram());
|
||||
|
||||
// Allocate PSRAM buffer
|
||||
g_tmpFileBuffer = (char *)heap_caps_malloc(kTmpFileCapacity, MALLOC_CAP_SPIRAM);
|
||||
if (!g_tmpFileBuffer) {
|
||||
Serial.println("ERROR: Failed to allocate PSRAM buffer!");
|
||||
oledShowLines("Exercise 16_PSRAM", "Node: " NODE_LABEL, "PSRAM alloc FAILED");
|
||||
} else {
|
||||
Serial.printf("PSRAM buffer allocated: %u bytes\r\n", (unsigned)kTmpFileCapacity);
|
||||
}
|
||||
|
||||
Wire.begin(OLED_SDA, OLED_SCL);
|
||||
g_oled.setI2CAddress(OLED_ADDR << 1);
|
||||
g_oled.begin();
|
||||
oledShowLines("Exercise 16_PSRAM", "Node: " NODE_LABEL, "Booting...");
|
||||
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
static uint32_t lastMs = 0;
|
||||
const uint32_t now = millis();
|
||||
|
||||
// check serial commands at all times
|
||||
static char rxLine[384];
|
||||
static size_t rxLen = 0;
|
||||
|
||||
while (Serial.available()) {
|
||||
int c = Serial.read();
|
||||
if (c <= 0) continue;
|
||||
if (c == '\r' || c == '\n') {
|
||||
if (rxLen > 0) {
|
||||
rxLine[rxLen] = '\0';
|
||||
processSerialCommand(rxLine);
|
||||
rxLen = 0;
|
||||
}
|
||||
} else if (rxLen + 1 < sizeof(rxLine)) {
|
||||
rxLine[rxLen++] = (char)c;
|
||||
}
|
||||
}
|
||||
|
||||
if (now - lastMs < 1000) {
|
||||
delay(10);
|
||||
return;
|
||||
}
|
||||
lastMs = now;
|
||||
|
||||
if (g_tmpFileBuffer) {
|
||||
appendTimestampLine();
|
||||
}
|
||||
printRamStatus();
|
||||
}
|
||||
32
exercises/17_Flash/README.md
Normal file
32
exercises/17_Flash/README.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Exercise 17_Flash
|
||||
|
||||
This exercise demonstrates using Flash storage as a persistent directory-like file system on an ESP32-S3 board.
|
||||
|
||||
Behavior:
|
||||
- Mounts SPIFFS at boot and reports total / used / free flash space.
|
||||
- Ensures a flash directory at `/flash_logs` exists.
|
||||
- Creates a new log file when the device boots, based on the current timestamp: `YYYYMMDD_HHMM.log`.
|
||||
- Writes a timestamped line into the new log file once per second.
|
||||
- Supports console commands to inspect the current file, read it, clear it, append or rewrite it, and list stored files.
|
||||
- Files persist across reboots and are stored in flash.
|
||||
|
||||
Build and upload:
|
||||
|
||||
```bash
|
||||
cd /usr/local/src/microreticulum/microReticulumTbeam/exercises/17_Flash
|
||||
pio run -e amy -t upload
|
||||
pio device monitor -b 115200
|
||||
```
|
||||
|
||||
Commands:
|
||||
- `help` - show command menu
|
||||
- `stat` - show flash / current file status
|
||||
- `list` - list files under `/flash_logs`
|
||||
- `read` - read the current flash file contents
|
||||
- `clear` - clear the current flash file contents
|
||||
- `write <text>` - overwrite the current flash file with text
|
||||
- `append <text>` - append text to the current flash file
|
||||
|
||||
Notes:
|
||||
- If the current timestamp file name already exists, the exercise will append a numeric suffix to keep the file unique.
|
||||
- On each reboot a new file is created so persistent flash logs accumulate.
|
||||
50
exercises/17_Flash/platformio.ini
Normal file
50
exercises/17_Flash/platformio.ini
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
; 20260403 ChatGPT
|
||||
; Exercise 17_Flash
|
||||
|
||||
[platformio]
|
||||
default_envs = amy
|
||||
|
||||
[env]
|
||||
platform = espressif32
|
||||
framework = arduino
|
||||
board = esp32-s3-devkitc-1
|
||||
monitor_speed = 115200
|
||||
extra_scripts = pre:scripts/set_build_epoch.py
|
||||
lib_deps =
|
||||
Wire
|
||||
olikraus/U8g2@^2.36.4
|
||||
lewisxhe/XPowersLib@0.3.3
|
||||
|
||||
build_flags =
|
||||
-I ../../shared/boards
|
||||
-I ../../external/microReticulum_Firmware
|
||||
-D BOARD_MODEL=BOARD_TBEAM_S_V1
|
||||
-D OLED_SDA=17
|
||||
-D OLED_SCL=18
|
||||
-D OLED_ADDR=0x3C
|
||||
-D ARDUINO_USB_MODE=1
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||
|
||||
[env:amy]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"AMY\"
|
||||
|
||||
[env:bob]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"BOB\"
|
||||
|
||||
[env:cy]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"CY\"
|
||||
|
||||
[env:dan]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"DAN\"
|
||||
11
exercises/17_Flash/read_partition_bin.py
Normal file
11
exercises/17_Flash/read_partition_bin.py
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import struct
|
||||
with open('AMY_test_partitions_read.bin', 'rb') as f:
|
||||
data = f.read()
|
||||
seq0 = struct.unpack('<I', data[0:4])[0]
|
||||
seq1 = struct.unpack('<I', data[32:36])[0]
|
||||
print(f"OTA seq0: {seq0:08x}")
|
||||
print(f"OTA seq1: {seq1:08x}")
|
||||
if seq0 > seq1:
|
||||
print("→ app0 is active, new uploads go to app1")
|
||||
else:
|
||||
print("→ app1 is active, new uploads go to app0")
|
||||
12
exercises/17_Flash/scripts/set_build_epoch.py
Normal file
12
exercises/17_Flash/scripts/set_build_epoch.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import time
|
||||
Import("env")
|
||||
|
||||
epoch = int(time.time())
|
||||
utc_tag = time.strftime("%Y%m%d_%H%M%S_z", time.gmtime(epoch))
|
||||
|
||||
env.Append(
|
||||
CPPDEFINES=[
|
||||
("FW_BUILD_EPOCH", str(epoch)),
|
||||
("FW_BUILD_UTC", '\"%s\"' % utc_tag),
|
||||
]
|
||||
)
|
||||
26
exercises/17_Flash/show_partition_table.py
Normal file
26
exercises/17_Flash/show_partition_table.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import struct
|
||||
|
||||
with open('partitions_backup.bin', 'rb') as f:
|
||||
data = f.read()
|
||||
|
||||
print("Name | Type | SubType | Offset | Size | Flags")
|
||||
print("-" * 75)
|
||||
|
||||
for i in range(0, len(data), 32):
|
||||
entry = data[i:i+32]
|
||||
if len(entry) < 32:
|
||||
break
|
||||
|
||||
magic = struct.unpack('<H', entry[0:2])[0]
|
||||
|
||||
if magic == 0x50aa: # Valid partition magic
|
||||
type_val = entry[2]
|
||||
subtype = entry[3]
|
||||
offset = struct.unpack('<I', entry[4:8])[0]
|
||||
size = struct.unpack('<I', entry[8:12])[0]
|
||||
flags = struct.unpack('<H', entry[12:14])[0]
|
||||
name = entry[16:32].rstrip(b'\x00').decode('ascii', errors='ignore')
|
||||
|
||||
print(f"{name:<13} | {type_val:02x} | {subtype:02x} | 0x{offset:08x} | 0x{size:08x} | {flags:04x}")
|
||||
elif magic == 0xebeb:
|
||||
break # End marker
|
||||
608
exercises/17_Flash/src/main.cpp
Normal file
608
exercises/17_Flash/src/main.cpp
Normal file
|
|
@ -0,0 +1,608 @@
|
|||
// 20260403 ChatGPT
|
||||
// Exercise 17_Flash
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include <U8g2lib.h>
|
||||
#include <time.h>
|
||||
#include <SPIFFS.h>
|
||||
|
||||
#include "tbeam_supreme_adapter.h"
|
||||
|
||||
#ifndef NODE_LABEL
|
||||
#define NODE_LABEL "FLASH"
|
||||
#endif
|
||||
|
||||
#ifndef RTC_I2C_ADDR
|
||||
#define RTC_I2C_ADDR 0x51
|
||||
#endif
|
||||
|
||||
#ifndef OLED_SDA
|
||||
#define OLED_SDA 17
|
||||
#endif
|
||||
#ifndef OLED_SCL
|
||||
#define OLED_SCL 18
|
||||
#endif
|
||||
#ifndef OLED_ADDR
|
||||
#define OLED_ADDR 0x3C
|
||||
#endif
|
||||
|
||||
static U8G2_SH1106_128X64_NONAME_F_HW_I2C g_oled(U8G2_R0, U8X8_PIN_NONE);
|
||||
static const char *kFlashDir = "/flash_logs";
|
||||
static char g_currentFilePath[64] = {0};
|
||||
static File g_flashFile;
|
||||
static unsigned g_flashLineNumber = 0;
|
||||
static XPowersLibInterface* g_pmu = nullptr;
|
||||
static bool g_hasRtc = false;
|
||||
static bool g_rtcLowVoltage = false;
|
||||
|
||||
struct RtcDateTime {
|
||||
uint16_t year;
|
||||
uint8_t month;
|
||||
uint8_t day;
|
||||
uint8_t hour;
|
||||
uint8_t minute;
|
||||
uint8_t second;
|
||||
uint8_t weekday;
|
||||
};
|
||||
|
||||
static void oledShowLines(const char *l1,
|
||||
const char *l2 = nullptr,
|
||||
const char *l3 = nullptr,
|
||||
const char *l4 = nullptr,
|
||||
const char *l5 = nullptr)
|
||||
{
|
||||
g_oled.clearBuffer();
|
||||
g_oled.setFont(u8g2_font_5x8_tf);
|
||||
if (l1) g_oled.drawUTF8(0, 12, l1);
|
||||
if (l2) g_oled.drawUTF8(0, 24, l2);
|
||||
if (l3) g_oled.drawUTF8(0, 36, l3);
|
||||
if (l4) g_oled.drawUTF8(0, 48, l4);
|
||||
if (l5) g_oled.drawUTF8(0, 60, l5);
|
||||
g_oled.sendBuffer();
|
||||
}
|
||||
|
||||
static size_t getFlashTotalBytes()
|
||||
{
|
||||
return SPIFFS.totalBytes();
|
||||
}
|
||||
|
||||
static size_t getFlashUsedBytes()
|
||||
{
|
||||
return SPIFFS.usedBytes();
|
||||
}
|
||||
|
||||
static size_t getFlashFreeBytes()
|
||||
{
|
||||
const size_t total = getFlashTotalBytes();
|
||||
const size_t used = getFlashUsedBytes();
|
||||
return total > used ? total - used : 0;
|
||||
}
|
||||
|
||||
static uint8_t toBcd(uint8_t v) {
|
||||
return ((v / 10U) << 4U) | (v % 10U);
|
||||
}
|
||||
|
||||
static uint8_t fromBcd(uint8_t b) {
|
||||
return ((b >> 4U) * 10U) + (b & 0x0FU);
|
||||
}
|
||||
|
||||
static bool isRtcDateTimeValid(const RtcDateTime& dt) {
|
||||
if (dt.year < 2020 || dt.year > 2099) return false;
|
||||
if (dt.month < 1 || dt.month > 12) return false;
|
||||
if (dt.day < 1 || dt.day > 31) return false;
|
||||
if (dt.hour > 23 || dt.minute > 59 || dt.second > 59) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool rtcRead(RtcDateTime& out, bool& lowVoltageFlag) {
|
||||
Wire1.beginTransmission(RTC_I2C_ADDR);
|
||||
Wire1.write(0x02);
|
||||
if (Wire1.endTransmission(false) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t need = 7;
|
||||
uint8_t got = Wire1.requestFrom((int)RTC_I2C_ADDR, (int)need);
|
||||
if (got != need) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t sec = Wire1.read();
|
||||
uint8_t min = Wire1.read();
|
||||
uint8_t hour = Wire1.read();
|
||||
uint8_t day = Wire1.read();
|
||||
uint8_t weekday = Wire1.read();
|
||||
uint8_t month = Wire1.read();
|
||||
uint8_t year = Wire1.read();
|
||||
|
||||
lowVoltageFlag = (sec & 0x80U) != 0;
|
||||
out.second = fromBcd(sec & 0x7FU);
|
||||
out.minute = fromBcd(min & 0x7FU);
|
||||
out.hour = fromBcd(hour & 0x3FU);
|
||||
out.day = fromBcd(day & 0x3FU);
|
||||
out.weekday = fromBcd(weekday & 0x07U);
|
||||
out.month = fromBcd(month & 0x1FU);
|
||||
uint8_t yy = fromBcd(year);
|
||||
bool century = (month & 0x80U) != 0;
|
||||
out.year = century ? (1900U + yy) : (2000U + yy);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool initRtc() {
|
||||
if (!tbeam_supreme::initPmuForPeripherals(g_pmu, &Serial)) {
|
||||
Serial.println("RTC init: PMU/i2c init failed");
|
||||
return false;
|
||||
}
|
||||
RtcDateTime now{};
|
||||
if (!rtcRead(now, g_rtcLowVoltage) || !isRtcDateTimeValid(now)) {
|
||||
Serial.println("RTC init: no valid time available");
|
||||
return false;
|
||||
}
|
||||
g_hasRtc = true;
|
||||
Serial.printf("RTC init: %04u-%02u-%02u %02u:%02u:%02u%s\r\n",
|
||||
(unsigned)now.year, (unsigned)now.month, (unsigned)now.day,
|
||||
(unsigned)now.hour, (unsigned)now.minute, (unsigned)now.second,
|
||||
g_rtcLowVoltage ? " [LOW_BATT]" : "");
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool getRtcTimestamp(char *out, size_t outSize) {
|
||||
if (!g_hasRtc) {
|
||||
return false;
|
||||
}
|
||||
RtcDateTime now{};
|
||||
bool low = false;
|
||||
if (!rtcRead(now, low) || !isRtcDateTimeValid(now)) {
|
||||
return false;
|
||||
}
|
||||
g_rtcLowVoltage = low;
|
||||
snprintf(out, outSize, "%04u-%02u-%02u %02u:%02u:%02u",
|
||||
now.year,
|
||||
now.month,
|
||||
now.day,
|
||||
now.hour,
|
||||
now.minute,
|
||||
now.second);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void getTimestamp(char *out, size_t outSize)
|
||||
{
|
||||
if (getRtcTimestamp(out, outSize)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const time_t now = time(nullptr);
|
||||
if (now > 1700000000) {
|
||||
struct tm tmNow;
|
||||
localtime_r(&now, &tmNow);
|
||||
snprintf(out, outSize, "%04d-%02d-%02d %02d:%02d:%02d",
|
||||
tmNow.tm_year + 1900,
|
||||
tmNow.tm_mon + 1,
|
||||
tmNow.tm_mday,
|
||||
tmNow.tm_hour,
|
||||
tmNow.tm_min,
|
||||
tmNow.tm_sec);
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t sec = millis() / 1000;
|
||||
const uint32_t hh = sec / 3600;
|
||||
const uint32_t mm = (sec % 3600) / 60;
|
||||
const uint32_t ss = sec % 60;
|
||||
snprintf(out, outSize, "uptime %02u:%02u:%02u", (unsigned)hh, (unsigned)mm, (unsigned)ss);
|
||||
}
|
||||
|
||||
static void getFilenameTimestamp(char *out, size_t outSize)
|
||||
{
|
||||
if (g_hasRtc) {
|
||||
RtcDateTime now{};
|
||||
bool low = false;
|
||||
if (rtcRead(now, low) && isRtcDateTimeValid(now)) {
|
||||
snprintf(out, outSize, "%04u%02u%02u_%02u%02u",
|
||||
now.year,
|
||||
now.month,
|
||||
now.day,
|
||||
now.hour,
|
||||
now.minute);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const time_t now = time(nullptr);
|
||||
if (now > 1700000000) {
|
||||
struct tm tmNow;
|
||||
localtime_r(&now, &tmNow);
|
||||
snprintf(out, outSize, "%04d%02d%02d_%02d%02d",
|
||||
tmNow.tm_year + 1900,
|
||||
tmNow.tm_mon + 1,
|
||||
tmNow.tm_mday,
|
||||
tmNow.tm_hour,
|
||||
tmNow.tm_min);
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t sec = millis() / 1000;
|
||||
const uint32_t hh = sec / 3600;
|
||||
const uint32_t mm = (sec % 3600) / 60;
|
||||
const uint32_t ss = sec % 60;
|
||||
snprintf(out, outSize, "uptime_%02u%02u%02u", (unsigned)hh, (unsigned)mm, (unsigned)ss);
|
||||
}
|
||||
|
||||
static String getNewFlashFilePath()
|
||||
{
|
||||
char baseName[64];
|
||||
getFilenameTimestamp(baseName, sizeof(baseName));
|
||||
|
||||
char candidate[96];
|
||||
snprintf(candidate, sizeof(candidate), "%s/%s.log", kFlashDir, baseName);
|
||||
if (!SPIFFS.exists(candidate)) {
|
||||
return String(candidate);
|
||||
}
|
||||
|
||||
int suffix = 1;
|
||||
do {
|
||||
snprintf(candidate, sizeof(candidate), "%s/%s-%d.log", kFlashDir, baseName, suffix);
|
||||
suffix += 1;
|
||||
} while (SPIFFS.exists(candidate));
|
||||
|
||||
return String(candidate);
|
||||
}
|
||||
|
||||
static bool ensureFlashDirectory()
|
||||
{
|
||||
if (SPIFFS.exists(kFlashDir)) {
|
||||
return true;
|
||||
}
|
||||
if (!SPIFFS.mkdir(kFlashDir)) {
|
||||
Serial.printf("Warning: failed to create %s\r\n", kFlashDir);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool openCurrentFlashFile(bool truncate = false)
|
||||
{
|
||||
if (g_flashFile) {
|
||||
g_flashFile.close();
|
||||
}
|
||||
|
||||
if (truncate) {
|
||||
g_flashFile = SPIFFS.open(g_currentFilePath, FILE_WRITE);
|
||||
} else {
|
||||
g_flashFile = SPIFFS.open(g_currentFilePath, FILE_APPEND);
|
||||
}
|
||||
|
||||
if (!g_flashFile) {
|
||||
Serial.printf("ERROR: cannot open %s\r\n", g_currentFilePath);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool createFlashLogFile()
|
||||
{
|
||||
if (!ensureFlashDirectory()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String path = getNewFlashFilePath();
|
||||
path.toCharArray(g_currentFilePath, sizeof(g_currentFilePath));
|
||||
|
||||
g_flashFile = SPIFFS.open(g_currentFilePath, FILE_WRITE);
|
||||
if (!g_flashFile) {
|
||||
Serial.printf("ERROR: could not create %s\r\n", g_currentFilePath);
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *header = "FLASH log file created\r\n";
|
||||
g_flashFile.print(header);
|
||||
g_flashFile.flush();
|
||||
g_flashLineNumber = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void appendFlashTimestampLine()
|
||||
{
|
||||
if (!g_flashFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
char timestamp[32];
|
||||
getTimestamp(timestamp, sizeof(timestamp));
|
||||
|
||||
char line[96];
|
||||
const int written = snprintf(line, sizeof(line), "%u, %s\r\n", g_flashLineNumber + 1, timestamp);
|
||||
if (written <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t lineLen = (size_t)written;
|
||||
if (g_flashFile.write(reinterpret_cast<const uint8_t *>(line), lineLen) != lineLen) {
|
||||
Serial.println("Warning: flash write failed");
|
||||
return;
|
||||
}
|
||||
g_flashFile.flush();
|
||||
g_flashLineNumber += 1;
|
||||
}
|
||||
|
||||
static void printFlashStatus()
|
||||
{
|
||||
const size_t total = getFlashTotalBytes();
|
||||
const size_t used = getFlashUsedBytes();
|
||||
const size_t freeBytes = getFlashFreeBytes();
|
||||
|
||||
Serial.printf("FLASH total=%u used=%u free=%u\r\n",
|
||||
(unsigned)total, (unsigned)used, (unsigned)freeBytes);
|
||||
|
||||
char line1[32];
|
||||
char line2[32];
|
||||
char line3[32];
|
||||
char line4[32];
|
||||
char line5[32];
|
||||
|
||||
snprintf(line1, sizeof(line1), "Exercise 17 Flash");
|
||||
snprintf(line2, sizeof(line2), "Node: %s", NODE_LABEL);
|
||||
snprintf(line3, sizeof(line3), "Free: %u KB", (unsigned)(freeBytes / 1024U));
|
||||
snprintf(line4, sizeof(line4), "Used: %u KB", (unsigned)(used / 1024U));
|
||||
snprintf(line5, sizeof(line5), "Lines: %u", (unsigned)g_flashLineNumber);
|
||||
|
||||
oledShowLines(line1, line2, line3, line4, line5);
|
||||
}
|
||||
|
||||
static void showHelp()
|
||||
{
|
||||
Serial.println("Flash command list:");
|
||||
Serial.println(" help - show this menu");
|
||||
Serial.println(" stat - show flash/file state");
|
||||
Serial.println(" rtc - show RTC time status");
|
||||
Serial.println(" list - list files in /flash_logs");
|
||||
Serial.println(" read - read current flash file");
|
||||
Serial.println(" clear - clear current flash file");
|
||||
Serial.println(" write <text> - overwrite current flash file");
|
||||
Serial.println(" append <text> - append text to current flash file");
|
||||
}
|
||||
|
||||
static void printFlashFileStat()
|
||||
{
|
||||
Serial.printf("Current file: %s\r\n", g_currentFilePath);
|
||||
if (!SPIFFS.exists(g_currentFilePath)) {
|
||||
Serial.println("Current file missing");
|
||||
return;
|
||||
}
|
||||
|
||||
File file = SPIFFS.open(g_currentFilePath, FILE_READ);
|
||||
if (!file) {
|
||||
Serial.println("Unable to open current file for stats");
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.printf("Size: %u bytes\r\n", (unsigned)file.size());
|
||||
Serial.printf("Lines written: %u\r\n", (unsigned)g_flashLineNumber);
|
||||
file.close();
|
||||
}
|
||||
|
||||
static void printFlashFileContents()
|
||||
{
|
||||
if (!SPIFFS.exists(g_currentFilePath)) {
|
||||
Serial.println("Current flash file does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
File file = SPIFFS.open(g_currentFilePath, FILE_READ);
|
||||
if (!file) {
|
||||
Serial.println("Unable to open current flash file");
|
||||
return;
|
||||
}
|
||||
|
||||
if (file.size() == 0) {
|
||||
Serial.println("Current flash file is empty");
|
||||
file.close();
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.print("Flash file contents: ");
|
||||
while (file.available()) {
|
||||
Serial.write(file.read());
|
||||
}
|
||||
if (file.size() > 0) {
|
||||
Serial.println();
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
static void clearFlashFileContents()
|
||||
{
|
||||
if (!SPIFFS.exists(g_currentFilePath)) {
|
||||
Serial.println("No current file to clear");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!openCurrentFlashFile(true)) {
|
||||
return;
|
||||
}
|
||||
g_flashFile.close();
|
||||
g_flashLineNumber = 0;
|
||||
openCurrentFlashFile(false);
|
||||
Serial.println("Current flash file cleared");
|
||||
}
|
||||
|
||||
static void setFlashFileContent(const char *text)
|
||||
{
|
||||
if (!text) {
|
||||
clearFlashFileContents();
|
||||
return;
|
||||
}
|
||||
|
||||
File file = SPIFFS.open(g_currentFilePath, FILE_WRITE);
|
||||
if (!file) {
|
||||
Serial.println("Unable to overwrite current flash file");
|
||||
return;
|
||||
}
|
||||
|
||||
file.print(text);
|
||||
file.close();
|
||||
openCurrentFlashFile(false);
|
||||
g_flashLineNumber = 0;
|
||||
}
|
||||
|
||||
static void appendFlashFileContent(const char *text)
|
||||
{
|
||||
if (!text || text[0] == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!openCurrentFlashFile(false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_flashFile.print(text);
|
||||
g_flashFile.flush();
|
||||
}
|
||||
|
||||
static void listFlashFiles()
|
||||
{
|
||||
File dir = SPIFFS.open(kFlashDir);
|
||||
if (!dir || !dir.isDirectory()) {
|
||||
Serial.printf("Unable to list files in %s\r\n", kFlashDir);
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.printf("Files in %s:\r\n", kFlashDir);
|
||||
File file = dir.openNextFile();
|
||||
while (file) {
|
||||
Serial.printf(" %s (%u bytes)\r\n", file.name(), (unsigned)file.size());
|
||||
file = dir.openNextFile();
|
||||
}
|
||||
dir.close();
|
||||
}
|
||||
|
||||
static void processSerialCommand(const char *line)
|
||||
{
|
||||
if (!line || line[0] == '\0') return;
|
||||
|
||||
char tmp[384];
|
||||
strncpy(tmp, line, sizeof(tmp) - 1);
|
||||
tmp[sizeof(tmp) - 1] = '\0';
|
||||
|
||||
char *cmd = strtok(tmp, " \t\r\n");
|
||||
if (!cmd) return;
|
||||
|
||||
if (strcasecmp(cmd, "help") == 0) {
|
||||
showHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcasecmp(cmd, "stat") == 0) {
|
||||
printFlashFileStat();
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcasecmp(cmd, "rtc") == 0) {
|
||||
if (g_hasRtc) {
|
||||
char ts[32];
|
||||
if (getRtcTimestamp(ts, sizeof(ts))) {
|
||||
Serial.printf("RTC now: %s\r\n", ts);
|
||||
if (g_rtcLowVoltage) {
|
||||
Serial.println("RTC low-voltage flag is set");
|
||||
}
|
||||
} else {
|
||||
Serial.println("RTC present but time read failed");
|
||||
}
|
||||
} else {
|
||||
Serial.println("RTC unavailable");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcasecmp(cmd, "list") == 0) {
|
||||
listFlashFiles();
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcasecmp(cmd, "read") == 0) {
|
||||
printFlashFileContents();
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcasecmp(cmd, "clear") == 0) {
|
||||
clearFlashFileContents();
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcasecmp(cmd, "write") == 0 || strcasecmp(cmd, "append") == 0) {
|
||||
const char *payload = line + strlen(cmd);
|
||||
while (*payload == ' ' || *payload == '\t') payload++;
|
||||
if (strcasecmp(cmd, "write") == 0)
|
||||
setFlashFileContent(payload);
|
||||
else
|
||||
appendFlashFileContent(payload);
|
||||
|
||||
Serial.printf("%s: %s\r\n", cmd, payload);
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.println("Unknown command (help for list)");
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
delay(800);
|
||||
Serial.println("Exercise 17_Flash boot");
|
||||
|
||||
initRtc();
|
||||
|
||||
if (!SPIFFS.begin(true)) {
|
||||
Serial.println("ERROR: SPIFFS mount failed");
|
||||
oledShowLines("Exercise 17_Flash", "Node: " NODE_LABEL, "SPIFFS mount FAILED");
|
||||
} else {
|
||||
Serial.println("SPIFFS mounted successfully");
|
||||
if (createFlashLogFile()) {
|
||||
Serial.printf("Current flash file: %s\r\n", g_currentFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
Wire.begin(OLED_SDA, OLED_SCL);
|
||||
g_oled.setI2CAddress(OLED_ADDR << 1);
|
||||
g_oled.begin();
|
||||
oledShowLines("Exercise 17_Flash", "Node: " NODE_LABEL, "Booting...");
|
||||
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
static uint32_t lastMs = 0;
|
||||
const uint32_t now = millis();
|
||||
|
||||
static char rxLine[384];
|
||||
static size_t rxLen = 0;
|
||||
|
||||
while (Serial.available()) {
|
||||
int c = Serial.read();
|
||||
if (c <= 0) continue;
|
||||
if (c == '\r' || c == '\n') {
|
||||
if (rxLen > 0) {
|
||||
rxLine[rxLen] = '\0';
|
||||
processSerialCommand(rxLine);
|
||||
rxLen = 0;
|
||||
}
|
||||
} else if (rxLen + 1 < sizeof(rxLine)) {
|
||||
rxLine[rxLen++] = (char)c;
|
||||
}
|
||||
}
|
||||
|
||||
if (now - lastMs < 1000) {
|
||||
delay(10);
|
||||
return;
|
||||
}
|
||||
lastMs = now;
|
||||
|
||||
if (g_flashFile) {
|
||||
appendFlashTimestampLine();
|
||||
}
|
||||
printFlashStatus();
|
||||
}
|
||||
95
notes_Feb_18_2026.txt
Normal file
95
notes_Feb_18_2026.txt
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
|
||||
|
||||
|
||||
|
||||
A:
|
||||
pio run -e node_a -t upload --upload-port /dev/ttyACM0
|
||||
then:
|
||||
date; screen /dev/ttyACM0 115200
|
||||
|
||||
B:
|
||||
pio run -e node_b -t upload --upload-port /dev/ttyACM1
|
||||
then:
|
||||
date; screen /dev/ttyACM1 115200
|
||||
|
||||
|
||||
|
||||
tbeam:
|
||||
Console 1:
|
||||
cd /usr/local/src/sx1302_hal/packet_forwarder
|
||||
sudo ./lora_pkt_fwd -c global_conf.reticulum_915000000_sf8_bw125.json
|
||||
|
||||
Console 2:
|
||||
cd /usr/local/src/sx1302_hal/util_net_downlink
|
||||
./net_downlink -P 1730 -l uplinks_$(date +%Y%m%d_%H%M%S).csv
|
||||
|
||||
|
||||
Example capture:
|
||||
INFO: Received pkt from mote: 65732042 (fcnt=29540)
|
||||
|
||||
JSON up: {"rxpk":[{"jver":1,"tmst":413765588,"chan":8,"rfch":0,"freq":915.000000,"mid":16,"stat":1,"modu":"LORA","datr":"SF7BW125","codr":"4/5","rssis":-16,"lsnr":10.8,"foff":-45,"rssi":-15,"size":29,"data":"IEIgc2VuZHMgZ3JlZXRpbmdzLiBpdGVyPTE1MDQ="}]}
|
||||
INFO: [up] PUSH_ACK received in 31 ms
|
||||
|
||||
INFO: Received pkt from mote: 65732041 (fcnt=29540)
|
||||
|
||||
JSON up: {"rxpk":[{"jver":1,"tmst":414742074,"chan":8,"rfch":0,"freq":915.000000,"mid":16,"stat":1,"modu":"LORA","datr":"SF7BW125","codr":"4/5","rssis":-16,"lsnr":10.2,"foff":-297,"rssi":-15,"size":29,"data":"IEEgc2VuZHMgZ3JlZXRpbmdzLiBpdGVyPTE0OTg="}]}
|
||||
INFO: [up] PUSH_ACK received in 31 ms
|
||||
|
||||
INFO: Received pkt from mote: 65732042 (fcnt=29540)
|
||||
|
||||
JSON up: {"rxpk":[{"jver":1,"tmst":415766626,"chan":8,"rfch":0,"freq":915.000000,"mid":16,"stat":1,"modu":"LORA","datr":"SF7BW125","codr":"4/5","rssis":-16,"lsnr":11.0,"foff":-53,"rssi":-15,"size":29,"data":"IEIgc2VuZHMgZ3JlZXRpbmdzLiBpdGVyPTE1MDU="}]}
|
||||
INFO: [up] PUSH_ACK received in 31 ms
|
||||
|
||||
INFO: Received pkt from mote: 65732041 (fcnt=29540)
|
||||
|
||||
JSON up: {"rxpk":[{"jver":1,"tmst":416744088,"chan":8,"rfch":0,"freq":915.000000,"mid":16,"stat":1,"modu":"LORA","datr":"SF7BW125","codr":"4/5","rssis":-16,"lsnr":10.2,"foff":-289,"rssi":-15,"size":29,"data":"IEEgc2VuZHMgZ3JlZXRpbmdzLiBpdGVyPTE0OTk="}]}
|
||||
INFO: [up] PUSH_ACK received in 31 ms
|
||||
INFO: [down] PULL_ACK received in 31 ms
|
||||
|
||||
tbeam /usr/local/src/sx1302_hal/util_net_downlink # tail uplinks_20260218_155659.csv
|
||||
445782393,,8,0,915.000000,16,1,LORA,7,125,4/5,-15.0,-16.0,10.8,29,20422073656e6473206772656574696e67732e20697465723d31353230
|
||||
446764659,,8,0,915.000000,16,1,LORA,7,125,4/5,-15.0,-16.0,10.5,29,20412073656e6473206772656574696e67732e20697465723d31353134
|
||||
|
||||
|
||||
==== With all 5 units ======
|
||||
Amy:
|
||||
set the tab:
|
||||
echo -ne "\033]30;Amy\007"
|
||||
pio run -e amy -t upload --upload-port /dev/ttyACM0
|
||||
|
||||
date; screen /dev/ttyACM0 115200
|
||||
|
||||
Bob:
|
||||
echo -ne "\033]30;Bob\007"
|
||||
pio run -e bob -t upload --upload-port /dev/ttyACM1
|
||||
|
||||
date; screen /dev/ttyACM1 115200
|
||||
|
||||
Cy:
|
||||
source ~/rnsenv/bin/activate
|
||||
echo -ne "\033]30;Cy\007"
|
||||
pio run -e cy -t upload --upload-port /dev/ttyACM2
|
||||
|
||||
date; screen /dev/ttyACM2 115200
|
||||
|
||||
Dan:
|
||||
source ~/rnsenv/bin/activate
|
||||
echo -ne "\033]30;Dan\007"
|
||||
pio run -e cy -t upload --upload-port /dev/ttyACM3
|
||||
|
||||
date; screen /dev/ttyACM3 115200
|
||||
|
||||
Ed:
|
||||
source ~/rnsenv/bin/activate
|
||||
echo -ne "\033]30;Ed\007"
|
||||
pio run -e cy -t upload --upload-port /dev/ttyACM4
|
||||
date; screen /dev/ttyACM4 115200
|
||||
|
||||
|
||||
Logging:
|
||||
|
||||
cd /usr/local/src/sx1302_hal/util_net_downlink
|
||||
./net_downlink -P 1730 -l uplinks_$(date +%Y%m%d_%H%M%S).csv
|
||||
|
||||
ls -lath /usr/local/src/sx1302_hal/util_net_downlink |head -n 3
|
||||
|
||||
190
tools/constantTFCard/hw_debug/1st_run.log
Normal file
190
tools/constantTFCard/hw_debug/1st_run.log
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
John's interaction with the events below:
|
||||
|
||||
14149 Pressed down
|
||||
[ 17543] let up
|
||||
[ 30325] Pressed down
|
||||
35143 let up
|
||||
39993 killed.
|
||||
|
||||
Compiled and uploaded: Wed Apr 1 15:07:20 PDT 2026
|
||||
|
||||
[ 12793][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 12799][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
sample=3 state=BEGIN_FAIL rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=none@0 type=NONE size=0MB root=FAIL
|
||||
[ 13265][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 13271][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 13277][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 13701][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 13707][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 13713][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 14137][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 14143][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 14149][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
sample=4 state=MOUNT_OK rail=ON vbus=5.19 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@10000000 type=SDHC size=14910MB root=OK
|
||||
sample=5 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=6 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=7 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=8 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=9 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=10 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=11 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=12 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=13 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=14 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=15 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=16 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
[ 17543][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0xsample=17 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
37
|
||||
[ 17549][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 17555][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 17979][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 17985][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 17991][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 18415][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 18421][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 18427][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 18851][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 18857][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 18863][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 19287][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 19293][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 19299][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 19723][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 19729][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 19735][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 20159][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 20165][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 20171][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 20595][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 20601][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 20607][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
sample=18 state=BEGIN_FAIL rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=none@0 type=NONE size=0MB root=FAIL
|
||||
[ 21073][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 21079][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 21085][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 21509][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 21515][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 21521][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 21945][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 21951][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 21957][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 22381][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 22387][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 22393][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 22817][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 22823][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 22829][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 23253][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 23259][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 23265][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 23689][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 23695][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 23701][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 24125][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 24131][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 24137][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 24603][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0xsample=19 state=BEGIN_FAIL rail=ON vbus=5.19 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=none@0 type=NONE size=0MB root=FAIL
|
||||
37
|
||||
[ 24609][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 24615][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 25039][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 25045][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 25051][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 25475][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 25481][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 25487][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 25911][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 25917][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 25923][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 26347][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 26353][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 26359][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 26783][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 26789][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 26795][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 27219][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 27225][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 27231][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 27655][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 27661][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 27667][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
sample=20 state=BEGIN_FAIL rail=ON vbus=5.19 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=none@0 type=NONE size=0MB root=FAIL
|
||||
[ 28133][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 28139][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 28145][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 28569][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 28575][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 28581][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 29005][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 29011][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 29017][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 29441][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 29447][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 29453][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 29877][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 29883][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 29889][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 30313][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 30319][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 30325][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
sample=21 state=MOUNT_OK rail=ON vbus=5.19 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=FSPI@4000000 type=SDHC size=14910MB root=OK
|
||||
sample=22 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=23 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=24 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=25 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=26 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=27 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=28 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=29 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=30 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=31 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=32 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=33 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=34 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=35 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=36 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=37 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=38 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=39 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
sample=40 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
[ 35143][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0xsample=41 state=MOUNT_OK rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
37
|
||||
[ 35149][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 35155][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 35579][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 35585][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 35591][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 36015][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 36021][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 36027][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 36451][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 36457][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 36463][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 36887][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 36893][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 36899][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 37323][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 37329][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 37335][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 37759][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 37765][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 37771][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 38195][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 38201][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 38207][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
sample=42 state=BEGIN_FAIL rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=none@0 type=NONE size=0MB root=FAIL
|
||||
[ 38673][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 38679][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 38685][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 39109][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 39115][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 39121][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 39545][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 39551][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 39557][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
[ 39981][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x37
|
||||
[ 39987][E][sd_diskio.cpp:199] sdCommand(): Card Failed! cmd: 0x29
|
||||
[ 39993][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
|
||||
(rnsenv) jlpoole@jp /usr/local/src/microreticulum/microReticulumTbeam $ date
|
||||
Wed Apr 1 15:08:35 PDT 2026
|
||||
(rnsenv) jlpoole@jp /usr/local/src/microreticulum/microReticulumTbeam $
|
||||
44
tools/constantTFCard/hw_debug/amy_vs_bob_identifications.txt
Normal file
44
tools/constantTFCard/hw_debug/amy_vs_bob_identifications.txt
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
April 1, 2026 ~ 15:34
|
||||
|
||||
From AMY (broken SD Card):
|
||||
|
||||
[ 15549][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (13) There is no valid FAT volume
|
||||
|
||||
STARTUP REPLAY
|
||||
uptime_ms=15438
|
||||
BOARD IDENTITY
|
||||
chip_model=ESP32-S3
|
||||
chip_revision=0
|
||||
chip_cores=2
|
||||
sdk_version=v4.4.7-dirty
|
||||
cpu_mhz=240
|
||||
flash_size=8388608
|
||||
flash_speed=80000000
|
||||
flash_mode=QIO
|
||||
efuse_mac=68BF5B43CA48
|
||||
chip_id=5BBF68
|
||||
reset_reason=UNKNOWN
|
||||
arduino_board=Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||
sample=34 state=BEGIN_FAIL rail=ON vbus=5.18 batt=0.00 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=none@0 type=NONE size=0MB root=FAIL
|
||||
[ 15618][E][sd_diskio.cpp:806] sdcard_mount(): f_mount failed: (13) There is no valid FAT volume
|
||||
|
||||
from BOB:
|
||||
|
||||
sample=30 state=MOUNT_OK rail=ON vbus=5.03 batt=3.86 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
|
||||
STARTUP REPLAY
|
||||
uptime_ms=13167
|
||||
BOARD IDENTITY
|
||||
chip_model=ESP32-S3
|
||||
chip_revision=0
|
||||
chip_cores=2
|
||||
sdk_version=v4.4.7-dirty
|
||||
cpu_mhz=240
|
||||
flash_size=8388608
|
||||
flash_speed=80000000
|
||||
flash_mode=QIO
|
||||
efuse_mac=DC935A43CA48
|
||||
chip_id=5A93DC
|
||||
reset_reason=UNKNOWN
|
||||
arduino_board=Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||
sample=31 state=MOUNT_OK rail=ON vbus=5.03 batt=3.86 pins=1/0/1/0 probeH(ff=8 z=0 o=0 FF FF FF FF) probeF(ff=8 z=0 o=0 FF FF FF FF) mount=HSPI@400000 type=SDHC size=14910MB root=OK
|
||||
31
tools/constantTFCard/hw_debug/platformio.ini
Normal file
31
tools/constantTFCard/hw_debug/platformio.ini
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
; 20260401 Codex
|
||||
; constantTFCard hardware debug monitor
|
||||
|
||||
[platformio]
|
||||
default_envs = amy
|
||||
|
||||
[env]
|
||||
platform = espressif32
|
||||
framework = arduino
|
||||
board = esp32-s3-devkitc-1
|
||||
monitor_speed = 115200
|
||||
lib_deps =
|
||||
lewisxhe/XPowersLib@0.3.3
|
||||
Wire
|
||||
olikraus/U8g2@^2.36.4
|
||||
|
||||
build_flags =
|
||||
-I ../../../shared/boards
|
||||
-I ../../../external/microReticulum_Firmware
|
||||
-D BOARD_MODEL=BOARD_TBEAM_S_V1
|
||||
-D OLED_SDA=17
|
||||
-D OLED_SCL=18
|
||||
-D OLED_ADDR=0x3C
|
||||
-D ARDUINO_USB_MODE=1
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||
|
||||
[env:amy]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"AMY\"
|
||||
443
tools/constantTFCard/hw_debug/src/main.cpp
Normal file
443
tools/constantTFCard/hw_debug/src/main.cpp
Normal file
|
|
@ -0,0 +1,443 @@
|
|||
// 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);
|
||||
}
|
||||
31
tools/constantTFCard/platformio.ini
Normal file
31
tools/constantTFCard/platformio.ini
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
; 20260401 Codex
|
||||
; constantTFCard
|
||||
|
||||
[platformio]
|
||||
default_envs = amy
|
||||
|
||||
[env]
|
||||
platform = espressif32
|
||||
framework = arduino
|
||||
board = esp32-s3-devkitc-1
|
||||
monitor_speed = 115200
|
||||
lib_deps =
|
||||
lewisxhe/XPowersLib@0.3.3
|
||||
Wire
|
||||
olikraus/U8g2@^2.36.4
|
||||
|
||||
build_flags =
|
||||
-I ../../shared/boards
|
||||
-I ../../external/microReticulum_Firmware
|
||||
-D BOARD_MODEL=BOARD_TBEAM_S_V1
|
||||
-D OLED_SDA=17
|
||||
-D OLED_SCL=18
|
||||
-D OLED_ADDR=0x3C
|
||||
-D ARDUINO_USB_MODE=1
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||
|
||||
[env:amy]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"AMY\"
|
||||
2
tools/constantTFCard/raw_probe/.gitignore
vendored
Normal file
2
tools/constantTFCard/raw_probe/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
*.log
|
||||
|
||||
55
tools/constantTFCard/raw_probe/platformio.ini
Normal file
55
tools/constantTFCard/raw_probe/platformio.ini
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
; 20260401 Codex
|
||||
; constantTFCard raw SPI/SD probe
|
||||
|
||||
[platformio]
|
||||
default_envs = amy
|
||||
|
||||
[env]
|
||||
platform = espressif32
|
||||
framework = arduino
|
||||
board = esp32-s3-devkitc-1
|
||||
monitor_speed = 115200
|
||||
lib_deps =
|
||||
lewisxhe/XPowersLib@0.3.3
|
||||
Wire
|
||||
olikraus/U8g2@^2.36.4
|
||||
|
||||
build_flags =
|
||||
-I ../../../shared/boards
|
||||
-I ../../../external/microReticulum_Firmware
|
||||
-D BOARD_MODEL=BOARD_TBEAM_S_V1
|
||||
-D OLED_SDA=17
|
||||
-D OLED_SCL=18
|
||||
-D OLED_ADDR=0x3C
|
||||
-D ARDUINO_USB_MODE=1
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||
|
||||
[env:amy]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"AMY\"
|
||||
|
||||
[env:bob]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"BOB\"
|
||||
|
||||
[env:cy]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"CY\"
|
||||
|
||||
[env:dan]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"DAN\"
|
||||
|
||||
[env:ed]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"ED\"
|
||||
463
tools/constantTFCard/raw_probe/src/main.cpp
Normal file
463
tools/constantTFCard/raw_probe/src/main.cpp
Normal file
|
|
@ -0,0 +1,463 @@
|
|||
// 20260401 Codex
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <SD.h>
|
||||
#include <SPI.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 kSpiHz = 400000;
|
||||
static const uint32_t kReadyHeartbeatMs = 2000;
|
||||
|
||||
enum class RawState : uint8_t {
|
||||
PMU_FAIL = 0,
|
||||
RAIL_OFF,
|
||||
BUS_FLOAT,
|
||||
BUS_LOW,
|
||||
CMD0_TIMEOUT,
|
||||
CMD0_NOT_IDLE,
|
||||
CMD8_TIMEOUT,
|
||||
CMD8_BAD_R1,
|
||||
ACMD41_TIMEOUT,
|
||||
CMD58_TIMEOUT,
|
||||
READY
|
||||
};
|
||||
|
||||
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 bytes[8] = {0};
|
||||
};
|
||||
|
||||
struct RawSnapshot {
|
||||
RawState state = RawState::PMU_FAIL;
|
||||
bool pmuOk = false;
|
||||
bool railOn = false;
|
||||
PinSnapshot pins{};
|
||||
ProbeSummary idle{};
|
||||
uint8_t cmd0 = 0xFF;
|
||||
uint8_t cmd8r1 = 0xFF;
|
||||
uint8_t cmd8data[4] = {0xFF, 0xFF, 0xFF, 0xFF};
|
||||
uint8_t acmd41 = 0xFF;
|
||||
uint8_t cmd58r1 = 0xFF;
|
||||
uint8_t ocr[4] = {0xFF, 0xFF, 0xFF, 0xFF};
|
||||
};
|
||||
|
||||
static XPowersLibInterface* g_pmu = nullptr;
|
||||
static U8G2_SH1106_128X64_NONAME_F_HW_I2C g_oled(U8G2_R0, U8X8_PIN_NONE);
|
||||
static SPIClass g_spi(HSPI);
|
||||
static uint32_t g_sampleCount = 0;
|
||||
static uint32_t g_markCount = 0;
|
||||
static char g_inputLine[32] = {0};
|
||||
static uint8_t g_inputLen = 0;
|
||||
|
||||
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 beginBus() {
|
||||
SD.end();
|
||||
g_spi.end();
|
||||
delay(2);
|
||||
forceSpiDeselected();
|
||||
g_spi.begin(
|
||||
tbeam_supreme::sdSck(),
|
||||
tbeam_supreme::sdMiso(),
|
||||
tbeam_supreme::sdMosi(),
|
||||
tbeam_supreme::sdCs()
|
||||
);
|
||||
digitalWrite(tbeam_supreme::sdCs(), HIGH);
|
||||
}
|
||||
|
||||
static ProbeSummary idleProbe() {
|
||||
ProbeSummary out;
|
||||
beginBus();
|
||||
delay(1);
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
uint8_t b = g_spi.transfer(0xFF);
|
||||
out.bytes[i] = b;
|
||||
if (b == 0xFF) out.ffCount++;
|
||||
else if (b == 0x00) out.zeroCount++;
|
||||
else out.otherCount++;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static uint8_t waitR1(uint16_t tries = 16) {
|
||||
for (uint16_t i = 0; i < tries; ++i) {
|
||||
uint8_t r = g_spi.transfer(0xFF);
|
||||
if ((r & 0x80) == 0) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
static uint8_t sendCommand(uint8_t cmd, uint32_t arg, uint8_t crc) {
|
||||
g_spi.transfer(0xFF);
|
||||
digitalWrite(tbeam_supreme::sdCs(), LOW);
|
||||
g_spi.transfer(0x40 | cmd);
|
||||
g_spi.transfer((arg >> 24) & 0xFF);
|
||||
g_spi.transfer((arg >> 16) & 0xFF);
|
||||
g_spi.transfer((arg >> 8) & 0xFF);
|
||||
g_spi.transfer(arg & 0xFF);
|
||||
g_spi.transfer(crc);
|
||||
uint8_t r1 = waitR1();
|
||||
return r1;
|
||||
}
|
||||
|
||||
static void endCommand() {
|
||||
digitalWrite(tbeam_supreme::sdCs(), HIGH);
|
||||
g_spi.transfer(0xFF);
|
||||
}
|
||||
|
||||
static const char* stateToString(RawState state) {
|
||||
switch (state) {
|
||||
case RawState::PMU_FAIL: return "PMU_FAIL";
|
||||
case RawState::RAIL_OFF: return "RAIL_OFF";
|
||||
case RawState::BUS_FLOAT: return "BUS_FLOAT";
|
||||
case RawState::BUS_LOW: return "BUS_LOW";
|
||||
case RawState::CMD0_TIMEOUT: return "CMD0_TO";
|
||||
case RawState::CMD0_NOT_IDLE: return "CMD0_BAD";
|
||||
case RawState::CMD8_TIMEOUT: return "CMD8_TO";
|
||||
case RawState::CMD8_BAD_R1: return "CMD8_BAD";
|
||||
case RawState::ACMD41_TIMEOUT: return "ACMD41_TO";
|
||||
case RawState::CMD58_TIMEOUT: return "CMD58_TO";
|
||||
case RawState::READY: return "READY";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static RawSnapshot captureSnapshot() {
|
||||
RawSnapshot snap;
|
||||
snap.pins = readPins();
|
||||
snap.pmuOk = (g_pmu != nullptr);
|
||||
if (!snap.pmuOk) {
|
||||
snap.state = RawState::PMU_FAIL;
|
||||
return snap;
|
||||
}
|
||||
|
||||
snap.railOn = g_pmu->isPowerChannelEnable(XPOWERS_BLDO1);
|
||||
if (!snap.railOn) {
|
||||
snap.state = RawState::RAIL_OFF;
|
||||
return snap;
|
||||
}
|
||||
|
||||
snap.idle = idleProbe();
|
||||
if (snap.idle.ffCount == 8) {
|
||||
snap.state = RawState::BUS_FLOAT;
|
||||
} else if (snap.idle.zeroCount == 8) {
|
||||
snap.state = RawState::BUS_LOW;
|
||||
}
|
||||
|
||||
beginBus();
|
||||
delay(1);
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
g_spi.transfer(0xFF);
|
||||
}
|
||||
|
||||
snap.cmd0 = sendCommand(0, 0, 0x95);
|
||||
endCommand();
|
||||
if (snap.cmd0 == 0xFF) {
|
||||
snap.state = RawState::CMD0_TIMEOUT;
|
||||
return snap;
|
||||
}
|
||||
if (snap.cmd0 != 0x01) {
|
||||
snap.state = RawState::CMD0_NOT_IDLE;
|
||||
return snap;
|
||||
}
|
||||
|
||||
snap.cmd8r1 = sendCommand(8, 0x000001AAUL, 0x87);
|
||||
if (snap.cmd8r1 == 0xFF) {
|
||||
endCommand();
|
||||
snap.state = RawState::CMD8_TIMEOUT;
|
||||
return snap;
|
||||
}
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
snap.cmd8data[i] = g_spi.transfer(0xFF);
|
||||
}
|
||||
endCommand();
|
||||
if (!(snap.cmd8r1 == 0x01 || snap.cmd8r1 == 0x05)) {
|
||||
snap.state = RawState::CMD8_BAD_R1;
|
||||
return snap;
|
||||
}
|
||||
|
||||
uint8_t ready = 0xFF;
|
||||
for (int attempt = 0; attempt < 12; ++attempt) {
|
||||
uint8_t r1 = sendCommand(55, 0, 0x65);
|
||||
endCommand();
|
||||
if (r1 == 0xFF) {
|
||||
continue;
|
||||
}
|
||||
ready = sendCommand(41, 0x40000000UL, 0x77);
|
||||
endCommand();
|
||||
if (ready == 0x00) {
|
||||
break;
|
||||
}
|
||||
delay(10);
|
||||
}
|
||||
snap.acmd41 = ready;
|
||||
if (snap.acmd41 != 0x00) {
|
||||
snap.state = RawState::ACMD41_TIMEOUT;
|
||||
return snap;
|
||||
}
|
||||
|
||||
snap.cmd58r1 = sendCommand(58, 0, 0xFD);
|
||||
if (snap.cmd58r1 == 0xFF) {
|
||||
endCommand();
|
||||
snap.state = RawState::CMD58_TIMEOUT;
|
||||
return snap;
|
||||
}
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
snap.ocr[i] = g_spi.transfer(0xFF);
|
||||
}
|
||||
endCommand();
|
||||
|
||||
snap.state = RawState::READY;
|
||||
return snap;
|
||||
}
|
||||
|
||||
static void printSnapshot(const RawSnapshot& snap) {
|
||||
Serial.printf(
|
||||
"sample=%lu state=%s rail=%s pins=%d/%d/%d/%d "
|
||||
"idle(ff=%u z=%u o=%u %02X %02X %02X %02X) "
|
||||
"cmd0=%02X cmd8=%02X [%02X %02X %02X %02X] "
|
||||
"acmd41=%02X cmd58=%02X [%02X %02X %02X %02X]\r\n",
|
||||
(unsigned long)g_sampleCount,
|
||||
stateToString(snap.state),
|
||||
snap.railOn ? "ON" : "OFF",
|
||||
snap.pins.cs,
|
||||
snap.pins.sck,
|
||||
snap.pins.miso,
|
||||
snap.pins.mosi,
|
||||
(unsigned)snap.idle.ffCount,
|
||||
(unsigned)snap.idle.zeroCount,
|
||||
(unsigned)snap.idle.otherCount,
|
||||
snap.idle.bytes[0],
|
||||
snap.idle.bytes[1],
|
||||
snap.idle.bytes[2],
|
||||
snap.idle.bytes[3],
|
||||
snap.cmd0,
|
||||
snap.cmd8r1,
|
||||
snap.cmd8data[0],
|
||||
snap.cmd8data[1],
|
||||
snap.cmd8data[2],
|
||||
snap.cmd8data[3],
|
||||
snap.acmd41,
|
||||
snap.cmd58r1,
|
||||
snap.ocr[0],
|
||||
snap.ocr[1],
|
||||
snap.ocr[2],
|
||||
snap.ocr[3]
|
||||
);
|
||||
}
|
||||
|
||||
static void printReadyHeartbeat() {
|
||||
Serial.printf("[%10lu] READY heartbeat\r\n", (unsigned long)millis());
|
||||
}
|
||||
|
||||
static bool sameSnapshot(const RawSnapshot& a, const RawSnapshot& b) {
|
||||
if (a.state != b.state) return false;
|
||||
if (a.railOn != b.railOn) return false;
|
||||
if (a.pins.cs != b.pins.cs || a.pins.sck != b.pins.sck ||
|
||||
a.pins.miso != b.pins.miso || a.pins.mosi != b.pins.mosi) return false;
|
||||
if (a.idle.ffCount != b.idle.ffCount ||
|
||||
a.idle.zeroCount != b.idle.zeroCount ||
|
||||
a.idle.otherCount != b.idle.otherCount) return false;
|
||||
if (a.cmd0 != b.cmd0 || a.cmd8r1 != b.cmd8r1 ||
|
||||
a.acmd41 != b.acmd41 || a.cmd58r1 != b.cmd58r1) return false;
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (a.cmd8data[i] != b.cmd8data[i]) return false;
|
||||
if (a.ocr[i] != b.ocr[i]) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void showSnapshot(const RawSnapshot& snap) {
|
||||
char l1[24];
|
||||
char l2[24];
|
||||
char l3[24];
|
||||
char l4[24];
|
||||
char l5[24];
|
||||
|
||||
snprintf(l1, sizeof(l1), "%s RAW SD", NODE_LABEL);
|
||||
snprintf(l2, sizeof(l2), "STATE %s", stateToString(snap.state));
|
||||
snprintf(l3, sizeof(l3), "CMD0 %02X C8 %02X A41 %02X", snap.cmd0, snap.cmd8r1, snap.acmd41);
|
||||
snprintf(l4, sizeof(l4), "OCR %02X%02X%02X%02X",
|
||||
snap.ocr[0], snap.ocr[1], snap.ocr[2], snap.ocr[3]);
|
||||
snprintf(l5, sizeof(l5), "IDL %u/%u/%u #%lu",
|
||||
(unsigned)snap.idle.ffCount,
|
||||
(unsigned)snap.idle.zeroCount,
|
||||
(unsigned)snap.idle.otherCount,
|
||||
(unsigned long)g_sampleCount);
|
||||
|
||||
g_oled.clearBuffer();
|
||||
g_oled.setFont(u8g2_font_5x8_tf);
|
||||
g_oled.drawUTF8(0, 12, l1);
|
||||
g_oled.drawUTF8(0, 24, l2);
|
||||
g_oled.drawUTF8(0, 36, l3);
|
||||
g_oled.drawUTF8(0, 48, l4);
|
||||
g_oled.drawUTF8(0, 60, l5);
|
||||
g_oled.sendBuffer();
|
||||
}
|
||||
|
||||
static void handleSerialInput() {
|
||||
auto handleCommand = []() {
|
||||
if (g_inputLen == 0) {
|
||||
Serial.println();
|
||||
return;
|
||||
}
|
||||
|
||||
g_inputLine[g_inputLen] = '\0';
|
||||
if (strcmp(g_inputLine, "m") == 0 || strcmp(g_inputLine, "M") == 0) {
|
||||
g_markCount++;
|
||||
Serial.printf("----- MARK %lu -----\r\n", (unsigned long)g_markCount);
|
||||
} else if (strcmp(g_inputLine, "ls") == 0 || strcmp(g_inputLine, "LS") == 0) {
|
||||
Serial.println("----- LS / -----");
|
||||
beginBus();
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
g_spi.transfer(0xFF);
|
||||
}
|
||||
|
||||
if (!SD.begin(tbeam_supreme::sdCs(), g_spi, kSpiHz)) {
|
||||
Serial.println("ls: SD.begin failed");
|
||||
g_inputLen = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
File root = SD.open("/", FILE_READ);
|
||||
if (!root) {
|
||||
Serial.println("ls: open / failed");
|
||||
SD.end();
|
||||
g_inputLen = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
File entry = root.openNextFile();
|
||||
if (!entry) {
|
||||
Serial.println("ls: root empty or unreadable");
|
||||
}
|
||||
|
||||
while (entry) {
|
||||
Serial.printf("%s%s %lu\r\n",
|
||||
entry.name(),
|
||||
entry.isDirectory() ? "/" : "",
|
||||
(unsigned long)entry.size());
|
||||
entry.close();
|
||||
entry = root.openNextFile();
|
||||
}
|
||||
|
||||
root.close();
|
||||
SD.end();
|
||||
Serial.println("----- END LS -----");
|
||||
} else {
|
||||
Serial.printf("unknown command: %s\r\n", g_inputLine);
|
||||
}
|
||||
|
||||
g_inputLen = 0;
|
||||
};
|
||||
|
||||
while (Serial.available() > 0) {
|
||||
int ch = Serial.read();
|
||||
if (ch == '\r' || ch == '\n') {
|
||||
handleCommand();
|
||||
} else if (ch == 0x08 || ch == 0x7F) {
|
||||
if (g_inputLen > 0) {
|
||||
g_inputLen--;
|
||||
}
|
||||
} else if (g_inputLen + 1 < sizeof(g_inputLine)) {
|
||||
g_inputLine[g_inputLen++] = (char)ch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
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 raw probe");
|
||||
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 BUS_FLOAT BUS_LOW CMD0_TO CMD0_BAD CMD8_TO CMD8_BAD ACMD41_TO CMD58_TO READY");
|
||||
Serial.println("Input: Enter=blank line, m=mark, ls=list root");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
static uint32_t lastPollMs = 0;
|
||||
static bool haveLastPrinted = false;
|
||||
static RawSnapshot lastPrinted{};
|
||||
static uint32_t lastReadyHeartbeatMs = 0;
|
||||
|
||||
handleSerialInput();
|
||||
|
||||
uint32_t now = millis();
|
||||
if (now - lastPollMs < kPollIntervalMs) {
|
||||
delay(5);
|
||||
return;
|
||||
}
|
||||
|
||||
lastPollMs = now;
|
||||
g_sampleCount++;
|
||||
|
||||
RawSnapshot snap = captureSnapshot();
|
||||
bool changed = (!haveLastPrinted || !sameSnapshot(snap, lastPrinted));
|
||||
if (changed) {
|
||||
printSnapshot(snap);
|
||||
lastPrinted = snap;
|
||||
haveLastPrinted = true;
|
||||
if (snap.state == RawState::READY) {
|
||||
lastReadyHeartbeatMs = now;
|
||||
}
|
||||
} else if (snap.state == RawState::READY && now - lastReadyHeartbeatMs >= kReadyHeartbeatMs) {
|
||||
printReadyHeartbeat();
|
||||
lastReadyHeartbeatMs = now;
|
||||
}
|
||||
showSnapshot(snap);
|
||||
}
|
||||
257
tools/constantTFCard/src/main.cpp
Normal file
257
tools/constantTFCard/src/main.cpp
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
// 20260401 Codex
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <esp_system.h>
|
||||
#include <SPI.h>
|
||||
#include <SD.h>
|
||||
#include <Wire.h>
|
||||
#include <U8g2lib.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 kSdFreqHz = 400000;
|
||||
static const uint32_t kBootSettleMs = 2000;
|
||||
static const uint32_t kSdRailOffMs = 300;
|
||||
static const uint32_t kSdRailOnSettleMs = 1200;
|
||||
static const uint8_t kOutVotesBeforeRailCycle = 10;
|
||||
static const uint32_t kMinRailCycleGapMs = 5000;
|
||||
|
||||
static const uint32_t kDelayedRetryOffsetsMs[] = {
|
||||
1000,
|
||||
3000,
|
||||
7000,
|
||||
15000
|
||||
};
|
||||
|
||||
static XPowersLibInterface* g_pmu = nullptr;
|
||||
static U8G2_SH1106_128X64_NONAME_F_HW_I2C g_oled(U8G2_R0, U8X8_PIN_NONE);
|
||||
static SPIClass g_sdSpi(HSPI);
|
||||
static uint32_t g_bootMs = 0;
|
||||
static size_t g_nextDelayedRetry = 0;
|
||||
static uint32_t g_lastRailCycleMs = 0;
|
||||
static uint8_t g_consecutiveOut = 0;
|
||||
|
||||
static void forceSpiDeselected() {
|
||||
pinMode(tbeam_supreme::sdCs(), OUTPUT);
|
||||
digitalWrite(tbeam_supreme::sdCs(), HIGH);
|
||||
pinMode(tbeam_supreme::imuCs(), OUTPUT);
|
||||
digitalWrite(tbeam_supreme::imuCs(), HIGH);
|
||||
}
|
||||
|
||||
static void oledShowStatus(const char* status, uint32_t sampleCount) {
|
||||
char line1[24];
|
||||
char line2[24];
|
||||
char line3[24];
|
||||
|
||||
snprintf(line1, sizeof(line1), "%s TF CARD", NODE_LABEL);
|
||||
snprintf(line2, sizeof(line2), "%s", status);
|
||||
snprintf(line3, sizeof(line3), "sample %lu", (unsigned long)sampleCount);
|
||||
|
||||
g_oled.clearBuffer();
|
||||
g_oled.setFont(u8g2_font_6x12_tf);
|
||||
g_oled.drawUTF8(0, 14, line1);
|
||||
g_oled.setFont(u8g2_font_logisoso20_tf);
|
||||
g_oled.drawUTF8(0, 42, status);
|
||||
g_oled.setFont(u8g2_font_6x12_tf);
|
||||
g_oled.drawUTF8(0, 62, line3);
|
||||
g_oled.sendBuffer();
|
||||
}
|
||||
|
||||
static void oledShowBootPhase(const char* line2, const char* line3) {
|
||||
char line1[24];
|
||||
|
||||
snprintf(line1, sizeof(line1), "%s TF CARD", NODE_LABEL);
|
||||
|
||||
g_oled.clearBuffer();
|
||||
g_oled.setFont(u8g2_font_6x12_tf);
|
||||
g_oled.drawUTF8(0, 14, line1);
|
||||
if (line2) g_oled.drawUTF8(0, 32, line2);
|
||||
if (line3) g_oled.drawUTF8(0, 50, line3);
|
||||
g_oled.sendBuffer();
|
||||
}
|
||||
|
||||
static bool cardReadable() {
|
||||
SD.end();
|
||||
g_sdSpi.end();
|
||||
delay(2);
|
||||
|
||||
forceSpiDeselected();
|
||||
g_sdSpi.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) {
|
||||
g_sdSpi.transfer(0xFF);
|
||||
}
|
||||
|
||||
if (!SD.begin(tbeam_supreme::sdCs(), g_sdSpi, kSdFreqHz)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SD.cardType() == CARD_NONE) {
|
||||
SD.end();
|
||||
return false;
|
||||
}
|
||||
|
||||
File root = SD.open("/", FILE_READ);
|
||||
bool ok = (bool)root;
|
||||
if (root) {
|
||||
root.close();
|
||||
}
|
||||
|
||||
SD.end();
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool cycleSdRail() {
|
||||
if (!g_pmu) {
|
||||
Serial.println("rail cycle skipped: no PMU");
|
||||
return false;
|
||||
}
|
||||
|
||||
SD.end();
|
||||
g_sdSpi.end();
|
||||
forceSpiDeselected();
|
||||
|
||||
g_pmu->disablePowerOutput(XPOWERS_BLDO1);
|
||||
delay(kSdRailOffMs);
|
||||
g_pmu->setPowerChannelVoltage(XPOWERS_BLDO1, 3300);
|
||||
g_pmu->enablePowerOutput(XPOWERS_BLDO1);
|
||||
delay(kSdRailOnSettleMs);
|
||||
forceSpiDeselected();
|
||||
|
||||
g_lastRailCycleMs = millis();
|
||||
Serial.printf("rail cycle: off=%lu on_settle=%lu\r\n",
|
||||
(unsigned long)kSdRailOffMs,
|
||||
(unsigned long)kSdRailOnSettleMs);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void runDelayedRetry(const char* label) {
|
||||
bool readable = cardReadable();
|
||||
const char* status = readable ? "card IN" : "card OUT";
|
||||
Serial.printf("delayed retry %s -> %s\r\n", label, status);
|
||||
oledShowStatus(status, 0);
|
||||
}
|
||||
|
||||
static void handleSerialCommands() {
|
||||
while (Serial.available() > 0) {
|
||||
int ch = Serial.read();
|
||||
if (ch == 'r' || ch == 'R') {
|
||||
Serial.println("manual command: SD rail reset");
|
||||
oledShowBootPhase("manual SD reset", "re-probing");
|
||||
if (cycleSdRail()) {
|
||||
bool readable = cardReadable();
|
||||
Serial.printf("manual SD reset -> %s\r\n", readable ? "card IN" : "card OUT");
|
||||
oledShowStatus(readable ? "card IN" : "card OUT", 0);
|
||||
}
|
||||
} else if (ch == 'b' || ch == 'B') {
|
||||
Serial.println("manual command: full reboot");
|
||||
Serial.println("restarting now...");
|
||||
oledShowBootPhase("manual reboot", "restarting");
|
||||
delay(250);
|
||||
ESP.restart();
|
||||
} else if (ch == '\r' || ch == '\n') {
|
||||
continue;
|
||||
} else {
|
||||
Serial.printf("commands: r=sd reset, b=reboot (got '%c')\r\n", (char)ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(kSerialDelayMs);
|
||||
|
||||
Wire.begin(OLED_SDA, OLED_SCL);
|
||||
g_oled.begin();
|
||||
g_oled.clearDisplay();
|
||||
oledShowBootPhase("BOOT", "init");
|
||||
|
||||
tbeam_supreme::initPmuForPeripherals(g_pmu, &Serial);
|
||||
forceSpiDeselected();
|
||||
g_bootMs = millis();
|
||||
|
||||
Serial.println();
|
||||
Serial.println("constantTFCard");
|
||||
Serial.printf("Node: %s\r\n", NODE_LABEL);
|
||||
Serial.printf("Polling every %lu ms\r\n", (unsigned long)kPollIntervalMs);
|
||||
Serial.printf("Initial settle delay %lu ms\r\n", (unsigned long)kBootSettleMs);
|
||||
Serial.println("Commands: r=SD rail reset, b=full reboot");
|
||||
|
||||
oledShowBootPhase("BOOT", "settling SD rail");
|
||||
delay(kBootSettleMs);
|
||||
|
||||
runDelayedRetry("after_settle");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
static uint32_t lastPollMs = 0;
|
||||
static uint32_t sampleCount = 0;
|
||||
|
||||
handleSerialCommands();
|
||||
|
||||
uint32_t now = millis();
|
||||
|
||||
if (g_nextDelayedRetry < (sizeof(kDelayedRetryOffsetsMs) / sizeof(kDelayedRetryOffsetsMs[0])) &&
|
||||
now - g_bootMs >= kDelayedRetryOffsetsMs[g_nextDelayedRetry]) {
|
||||
char label[20];
|
||||
snprintf(label, sizeof(label), "t+%lus", (unsigned long)(kDelayedRetryOffsetsMs[g_nextDelayedRetry] / 1000));
|
||||
runDelayedRetry(label);
|
||||
g_nextDelayedRetry++;
|
||||
}
|
||||
|
||||
if (now - lastPollMs < kPollIntervalMs) {
|
||||
delay(5);
|
||||
return;
|
||||
}
|
||||
|
||||
lastPollMs = now;
|
||||
sampleCount++;
|
||||
|
||||
bool readable = cardReadable();
|
||||
if (readable) {
|
||||
g_consecutiveOut = 0;
|
||||
} else if (g_consecutiveOut < 255) {
|
||||
g_consecutiveOut++;
|
||||
}
|
||||
|
||||
if (!readable &&
|
||||
g_consecutiveOut >= kOutVotesBeforeRailCycle &&
|
||||
now - g_lastRailCycleMs >= kMinRailCycleGapMs) {
|
||||
Serial.printf("persistent OUT: %u polls, forcing SD rail cycle\r\n",
|
||||
(unsigned)g_consecutiveOut);
|
||||
oledShowBootPhase("OUT -> rail reset", "re-probing SD");
|
||||
|
||||
if (cycleSdRail()) {
|
||||
bool retryReadable = cardReadable();
|
||||
readable = retryReadable;
|
||||
g_consecutiveOut = retryReadable ? 0 : kOutVotesBeforeRailCycle;
|
||||
Serial.printf("after rail cycle -> %s\r\n", retryReadable ? "card IN" : "card OUT");
|
||||
}
|
||||
}
|
||||
|
||||
const char* status = readable ? "card IN" : "card OUT";
|
||||
|
||||
Serial.println(status);
|
||||
oledShowStatus(status, sampleCount);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue