Files
wadamesh/include/SdFastClock.h
T
Christopher Van HooseandClaude Fable 5 844d8c3357 V4-R8 perf pass + M9 UI polish (prefs v49/v50)
Heltec V4-R8 — "are we getting full performance?" audit. Compute side was already
at spec; six gaps closed (record: variants/heltec_v4/R8_AUDIT.md "Perf pass"):
- FEM LNA defaulted OFF on a KCT8103L board: prefs v49 defaults it ON on the R8
  (one-time flip of existing installs, no new field). Toggle stays in Radio & Mesh.
- Display SPI 40 -> 80 MHz (LGFX_SPI_WRITE_HZ; -D to 40000000 steps back).
- Async DMA band flush (LGFXDisplay::flushBandRGB565): swap into a 12 KB internal
  DMA buffer, one no-convert DMA per band, lv_disp_flush_ready at once so LVGL
  renders band N+1 while band N drains; lv_disp_flush_is_last closes the frame
  transaction so the shared micro-SD gets the bus between frames. Sync fallback.
- Wi-Fi modem power save is a pref (wifi_ps; toggle in Wi-Fi settings, live once
  associated). Default ON everywhere (unchanged), OFF on the R8.
- micro-SD 4 -> 20 MHz after the proven mount, read-verified (probe-file head
  captured at 4 MHz and byte-compared after the raise; falls back). Wired at all
  three mount sites; no-op without SD_SPI_FAST_HZ. Note: SDFS::begin returns true
  if already mounted, so every clock change goes through SD.end() first.
- R8 env boots at ESP32_CPU_FREQ=240 (setup() no longer runs at the V4's 80 MHz).
- Settings -> About "Perf:" row on the R8 (no serial console on that board):
  "CPU 240 · TFT 80 MHz DMA · SD 20 MHz · LNA on" is the all-green reading.

ThinkNode M9 UI:
- Spectrum page root gets NAV_SKIP_FLAG: nothing on it is actionable, so the
  d-pad no longer highlights the chart / scale box; Back closes it as before.
- Map "Show tile z/x/y" line defaults OFF (prefs v50, one-time flip; toggle kept).
- The tab bar is bypassed entirely on the M9: the accent "glow" indicator lived on
  the screen, not in the bar, so hiding the zero-height bar left it glowing over
  the content's bottom edge. No bar styling / gestures / key hints / indicator
  are built on the M9 now.

Cross-compiled: R8, V4, M9, T-Deck. Flashed + booted on the user's R8 and M9.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 13:51:11 -04:00

95 lines
3.6 KiB
C

#pragma once
// Post-mount micro-SD operating-clock raise (perf pass 2026-08-20; Heltec V4-R8 first).
//
// Every SPI-SD board mounts the card with a conservative ladder that tops out at 4 MHz,
// and SD.begin()'s clock is the operating clock for the whole session — so the card that
// is the PRIMARY store on the R8 (contacts, chat history, sync log, file manager) ran at
// ~400 KB/s on traces that carry the display at 80 MHz. Standard SD bring-up is "initialise
// slow, then raise": once the 4 MHz mount has proved the card, re-begin at SD_SPI_FAST_HZ
// and READ-VERIFY it — a probe file's first 512 bytes are read at the proven clock first
// and compared byte-for-byte after the raise (SPI-mode SD has no data CRC by default, so a
// bare "mount succeeded" would not catch a marginal clock). Any mismatch or failure drops
// straight back to the clock that just worked. Boards that do not define SD_SPI_FAST_HZ
// (or set it <= 4 MHz) get a no-op — nothing changes for the T-Deck/M9/Pager ladders.
//
// Returns the operating clock after the call (cur_hz if the raise was refused or failed),
// or 0 if the card could not be re-mounted at all (the caller treats that as unmounted).
#include <Arduino.h>
#if defined(ESP32)
#include <SD.h>
#include <SPI.h>
#include <string.h>
#ifndef SD_SPI_FAST_HZ
#define SD_SPI_FAST_HZ 0
#endif
static inline uint32_t sdTryFastClock(uint8_t cs_pin, SPIClass& spi, uint32_t cur_hz, const char* tag) {
#if SD_SPI_FAST_HZ > 4000000
if (cur_hz == 0 || cur_hz >= (uint32_t)SD_SPI_FAST_HZ) return cur_hz;
// 1) Pick a probe file and capture its head at the proven clock.
char probe_path[96] = {0};
uint8_t ref[512];
size_t ref_len = 0;
{
File root = SD.open("/");
if (root && root.isDirectory()) {
for (int i = 0; i < 16; ++i) {
File e = root.openNextFile();
if (!e) break;
if (!e.isDirectory() && e.size() > 0 && e.path() && strlen(e.path()) < sizeof probe_path) {
strlcpy(probe_path, e.path(), sizeof probe_path);
ref_len = e.size() < sizeof ref ? e.size() : sizeof ref;
if (e.read(ref, ref_len) != ref_len) { probe_path[0] = 0; ref_len = 0; }
e.close();
break;
}
e.close();
}
root.close();
}
}
// 2) Re-begin at the fast clock.
SD.end();
delay(20);
bool ok = SD.begin(cs_pin, spi, SD_SPI_FAST_HZ, "/sd", 6) && SD.cardType() != CARD_NONE;
// 3) Verify: directory walk always; byte-compare of the probe head when we have one.
if (ok) {
File root = SD.open("/");
ok = root && root.isDirectory();
if (ok) { File e = root.openNextFile(); if (e) e.close(); root.close(); }
}
if (ok && probe_path[0]) {
uint8_t now[512];
File p = SD.open(probe_path, FILE_READ);
ok = p && p.read(now, ref_len) == ref_len && memcmp(now, ref, ref_len) == 0;
if (p) p.close();
}
if (ok) {
Serial.printf("[%s] SD clock %lu -> %lu Hz (%s)\n", tag, (unsigned long)cur_hz,
(unsigned long)SD_SPI_FAST_HZ, probe_path[0] ? "read-verified" : "dir-verified, no probe file");
return (uint32_t)SD_SPI_FAST_HZ;
}
// 4) Fall back to the clock that just worked.
Serial.printf("[%s] SD %lu Hz verify FAILED; back to %lu Hz\n", tag,
(unsigned long)SD_SPI_FAST_HZ, (unsigned long)cur_hz);
for (int attempt = 0; attempt < 2; ++attempt) {
SD.end();
delay(attempt == 0 ? 60 : 150);
if (SD.begin(cs_pin, spi, cur_hz, "/sd", 6) && SD.cardType() != CARD_NONE) return cur_hz;
}
Serial.printf("[%s] SD remount at %lu Hz failed\n", tag, (unsigned long)cur_hz);
return 0;
#else
(void)cs_pin; (void)spi; (void)tag;
return cur_hz;
#endif
}
#endif // ESP32