mirror of
https://github.com/ALLFATHER-BV/wadamesh.git
synced 2026-10-02 12:48:26 +00:00
p4: #167 resolved -- SD writes flash the AMOLED; hot store moved to internal LittleFS
Root cause, established by on-device elimination over one long session: SD-card WRITE bursts electrically disturb the T-Display P4's AMOLED -- the whole-screen blue flash of #167. The display stack itself measured clean at every layer while flashes were visible: framebuffer content (flush tracer: every band black), DSI bridge underrun interrupt (never fired), DSI host protocol errors incl. the host payload-buffer underflow (never latched), DCS/brightness traffic and XL9535 expander writes (silent), disp on/off (never). The flash survived every digital derate -- 4->1-bit SD bus, 20->10 MHz SD clock, 1000->750 Mbps DSI lane rate -- on every card tried, at any brightness (including near-zero, i.e. the panel is being upset at the hardware level, not showing content), yet vanished the moment the card was removed or left unwritten. The card's rail is switched off the same 3V3 domain as the panel (XL9535 SD_EN), so its program-current spikes reach the panel and no firmware setting can filter them. Streaming tile writes never flashed; rapid open-write-close store bursts always did. Meck-P4 not flashing proves nothing about the board: it never writes the SD. Fix: the hot store (contacts/channels/identity/blobs/prefs-file/chat history) lives on the INTERNAL 'storage' partition; the SD keeps bulk, gentle writers (map tiles, backups). The internal partition is mounted as LittleFS, not FAT: the P4's FAT layer has documented broken metadata and the v3 migration proved its writes are no better (a 102 KB contacts3 "copied" onto it read back as 0 B; on LittleFS the same copy reads back byte-exact and loads all 689 contacts). esp_littlefs looks partitions up by label with SUBTYPE_ANY, so the ex-FAT partition converts in place on first boot (formatOnFail) -- no partition-table change, OTA-safe. One-time v4 migration (lessons v1-v3 encoded): substance-gated (only from a card whose contacts3 beats the internal one -- a fresh test card once donated a blank store), whitelist-only copy (v2 filled the partition with junk before contacts3 landed), both historical card layouts probed (root vs /meshcomod), marker written only after the copy, card never written (it remains the rollback). Also in this change, kept as opt-in diagnostics (dormant without their env gates): WADA_P4_POKE_TRACE (DCS/expander/disp-on-off tracing + a DSI host error-register poller), WADA_SD_KHZ / WADA_SD_1BIT (SD primary-rung derates), WADA_DSI_MBPS (lane-rate override; default back at the vendor 1000 -- the 750 experiment changed nothing and is documented in the driver). Verified on device: no flash on the contact-churn trigger with the store internal (the previously 100%-reproducing trigger), 689/689 contacts after migration, tiles still on SD. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c99997ea9a
commit
3bf56cdd37
@@ -41853,6 +41853,12 @@ static bool uiDataFsReady() {
|
||||
// T-Deck's /meshcomod root; fall back to FFat only when no card is present. Both are mounted at
|
||||
// boot in main.cpp (g_sd_ok / g_fs_ok). (The P4 used to fall into the #else SPIFFS branch below —
|
||||
// no SPIFFS partition exists there, so history was never persisted and vanished on every reboot.)
|
||||
// #167: hot UI data (chat history) lives on the INTERNAL LittleFS -- SD write bursts
|
||||
// electrically disturb this board's AMOLED, and the P4's internal FAT layer is broken
|
||||
// (see the storage note in tdisplay_p4/main/main.cpp). SD = degraded fallback only;
|
||||
// tiles keep using the SD via their own selector.
|
||||
extern bool g_fs_ok;
|
||||
if (g_fs_ok) { s_ui_data_fs = &LittleFS; s_ui_data_root[0] = '\0'; return true; }
|
||||
extern bool g_sd_ok;
|
||||
if (g_sd_ok) {
|
||||
SD_MMC.mkdir("/meshcomod");
|
||||
@@ -41860,8 +41866,6 @@ static bool uiDataFsReady() {
|
||||
strncpy(s_ui_data_root, "/meshcomod", sizeof s_ui_data_root - 1);
|
||||
return true;
|
||||
}
|
||||
extern bool g_fs_ok;
|
||||
if (g_fs_ok) { s_ui_data_fs = &FFat; s_ui_data_root[0] = '\0'; return true; }
|
||||
#elif defined(HAS_TDECK_GT911)
|
||||
// T-Deck: the SD card is the persistent user-data store (large, removable,
|
||||
// survives a reflash) and is where chat history already lives. Prefer it; only
|
||||
|
||||
@@ -111,6 +111,32 @@ endforeach()
|
||||
# One-off C6-reflash helper: build with `C6_FLASH_HELPER=1 ./build.sh build` to boot a P4 image that
|
||||
# never touches the C6 (no C6_EN reset pulse, no AT worker), so the C6 can be held in ROM download mode
|
||||
# and reflashed over its own USB. Harmless when the env var is unset (normal builds are unaffected).
|
||||
# #167 SD-coupling experiments: WADA_SD_KHZ=<khz> / WADA_SD_1BIT=1 derate the primary SD mount
|
||||
# (clock and/or bus width) to isolate + mitigate the SD-activity screen flash.
|
||||
if(DEFINED ENV{WADA_SD_KHZ})
|
||||
idf_build_set_property(COMPILE_OPTIONS "-DTDP4_SD_KHZ=$ENV{WADA_SD_KHZ}" APPEND)
|
||||
message(STATUS "wadamesh: SD primary rung clock = $ENV{WADA_SD_KHZ} kHz")
|
||||
endif()
|
||||
if(DEFINED ENV{WADA_SD_1BIT})
|
||||
idf_build_set_property(COMPILE_OPTIONS "-DTDP4_SD_1BIT=true" APPEND)
|
||||
message(STATUS "wadamesh: SD primary rung = 1-bit bus")
|
||||
endif()
|
||||
|
||||
# #167: WADA_DSI_MBPS=<n> overrides the RM69A10 DSI lane bit rate (default 750 -- see the
|
||||
# RM_BITRATE note in RM69A10Display.cpp; 1000 = the LilyGo/Meck value with the thin eye).
|
||||
if(DEFINED ENV{WADA_DSI_MBPS})
|
||||
idf_build_set_property(COMPILE_OPTIONS "-DRM_BITRATE_MBPS=$ENV{WADA_DSI_MBPS}" APPEND)
|
||||
message(STATUS "wadamesh: DSI lane rate = $ENV{WADA_DSI_MBPS} Mbps")
|
||||
endif()
|
||||
|
||||
# Companion diagnostic (#167): `WADA_P4_POKE_TRACE=1 ./build.sh build` logs every runtime poke at
|
||||
# the panel or the XL9535 expander (DCS brightness writes, disp on/off, expander bit writes) so
|
||||
# visible flashes can be correlated against them on the console.
|
||||
if(DEFINED ENV{WADA_P4_POKE_TRACE})
|
||||
idf_build_set_property(COMPILE_OPTIONS "-DTDP4_POKE_TRACE=1" APPEND)
|
||||
message(STATUS "wadamesh: POKE TRACE enabled (diagnostic build, do not ship)")
|
||||
endif()
|
||||
|
||||
# Diagnostic build for the whole-screen colour flash (#167): `WADA_P4_FLUSH_TRACE=1 ./build.sh build`
|
||||
# logs every large, mostly-single-colour band pushed to the panel, with its RGB565 value. If the
|
||||
# screen flashes and nothing logs, the cause is below LVGL and the UI is the wrong place to look.
|
||||
|
||||
+102
-26
@@ -14,7 +14,12 @@
|
||||
#include "esp_heap_caps.h"
|
||||
#include "UITask.h"
|
||||
#include "target.h" // board, radio_driver, rtc_clock, display, sensors, radio_init()
|
||||
#include <FFat.h> // internal 'storage' FAT partition (no-card fallback)
|
||||
#include <LittleFS.h> // internal 'storage' partition, mounted as LittleFS (label lookup is
|
||||
// subtype-agnostic, so the ex-FAT partition converts in place -- no
|
||||
// partition-table change, OTA-safe). The P4's FAT-on-flash layer has
|
||||
// documented broken metadata (exists/size/f_getfree lie) and v3 of the
|
||||
// #167 migration proved even its WRITES cannot be trusted; LittleFS is
|
||||
// the same battle-proven backend every S3 board uses internally.
|
||||
#include <SD_MMC.h> // microSD on SDMMC slot 0 (primary store)
|
||||
#include "esp_partition.h"
|
||||
#include <WiFi.h>
|
||||
@@ -72,8 +77,8 @@ volatile int g_boot_phase = 0;
|
||||
extern "C" void set_boot_phase(int phase) { g_boot_phase = phase; }
|
||||
|
||||
// App globals — src/main.cpp declares these; we own them here (that file is excluded from the build).
|
||||
DataStore store(FFat, rtc_clock);
|
||||
bool g_fs_ok = false; // FFat('storage') mounted (extern — UITask file browser)
|
||||
DataStore store(LittleFS, rtc_clock);
|
||||
bool g_fs_ok = false; // internal 'storage' (LittleFS) mounted (extern — UITask file browser)
|
||||
bool g_sd_ok = false; // microSD mounted (extern)
|
||||
MultiTransportCompanionInterface serial_interface;
|
||||
StdRNG fast_rng;
|
||||
@@ -99,9 +104,11 @@ static void hostedConnectC6() {
|
||||
|
||||
// Recursive FS→FS copy for the one-time SD adoption migration (FFat store → SD /meshcomod).
|
||||
// Skips files that already exist at the destination, so a partial earlier run just completes.
|
||||
static void migrateFsCopyFile(fs::FS &src, const char *sp, fs::FS &dst, const char *dp) {
|
||||
File probe = dst.open(dp, FILE_READ);
|
||||
if (probe) { probe.close(); return; }
|
||||
static void migrateFsCopyFile(fs::FS &src, const char *sp, fs::FS &dst, const char *dp, bool overwrite = false) {
|
||||
if (!overwrite) {
|
||||
File probe = dst.open(dp, FILE_READ);
|
||||
if (probe) { probe.close(); return; }
|
||||
}
|
||||
File in = src.open(sp, FILE_READ);
|
||||
if (!in) return;
|
||||
File out = dst.open(dp, FILE_WRITE);
|
||||
@@ -112,7 +119,7 @@ static void migrateFsCopyFile(fs::FS &src, const char *sp, fs::FS &dst, const ch
|
||||
out.close(); in.close();
|
||||
printf("[storage] migrated %s\n", sp);
|
||||
}
|
||||
static void migrateFsDir(fs::FS &src, const char *sdir, fs::FS &dst, const char *ddir) {
|
||||
static void migrateFsDir(fs::FS &src, const char *sdir, fs::FS &dst, const char *ddir, bool overwrite = false) {
|
||||
File d = src.open(sdir);
|
||||
if (!d || !d.isDirectory()) { if (d) d.close(); return; }
|
||||
dst.mkdir(ddir);
|
||||
@@ -126,8 +133,8 @@ static void migrateFsDir(fs::FS &src, const char *sdir, fs::FS &dst, const char
|
||||
snprintf(dp, sizeof dp, "%s/%s", ddir, leaf);
|
||||
bool is_dir = f.isDirectory();
|
||||
f.close();
|
||||
if (is_dir) migrateFsDir(src, sp, dst, dp);
|
||||
else migrateFsCopyFile(src, sp, dst, dp);
|
||||
if (is_dir) migrateFsDir(src, sp, dst, dp, overwrite);
|
||||
else migrateFsCopyFile(src, sp, dst, dp, overwrite);
|
||||
}
|
||||
d.close();
|
||||
}
|
||||
@@ -164,9 +171,12 @@ static void wadameshSetup() {
|
||||
DisplayDriver* disp = &display;
|
||||
|
||||
// 6. Storage — microSD (SDMMC slot 0) primary, internal FFat 'storage' as the no-card fallback.
|
||||
g_fs_ok = FFat.begin(true, "/ffat", 10, "storage");
|
||||
printf("[storage] FFat(storage) = %s\n", g_fs_ok ? "OK" : "FAILED");
|
||||
if (g_fs_ok) { FFat.mkdir("/identity"); FFat.mkdir("/bl"); }
|
||||
// formatOnFail=true: the first boot of this build finds FAT data in the partition, fails the
|
||||
// littlefs mount, and formats it — the deliberate one-way conversion (store contents come back
|
||||
// from the SD card via the v4 migration below).
|
||||
g_fs_ok = LittleFS.begin(true, "/lfs", 10, "storage");
|
||||
printf("[storage] LittleFS(storage) = %s\n", g_fs_ok ? "OK" : "FAILED");
|
||||
if (g_fs_ok) { LittleFS.mkdir("/identity"); LittleFS.mkdir("/bl"); }
|
||||
// The slot's VDD is gated by the XL9535's SD_EN (IO15), ACTIVE-LOW — powerOnSequence() drives it
|
||||
// low. (Root-caused 2026-07-15 with a GPIO pad sweep: SD_EN high = all six SD pads clamped LOW
|
||||
// through the unpowered card's ESD diodes -> 0x107/0x109 on every mount; SD_EN low = pads high,
|
||||
@@ -180,9 +190,24 @@ static void wadameshSetup() {
|
||||
for (int p : {44, 39, 40, 41, 42}) gpio_pullup_en((gpio_num_t)p);
|
||||
// Mount ladder: 4-bit@20 MHz normally succeeds first try now that the slot is powered; the
|
||||
// fallbacks stay for marginal cards. (1-bit skips D1..D3; 5 MHz derates signal margin.)
|
||||
//
|
||||
// #167: SD activity IS the whole-screen flash. Proven by elimination on-device: identical
|
||||
// firmware with the card removed never flashes, while every reported trigger (message arrives,
|
||||
// opening an unread chat, contact add/delete, boot pre-splash) ends in an SD write burst or the
|
||||
// mount probe. The display stack itself measured clean throughout (framebuffer content, bridge
|
||||
// underrun, host protocol errors, DCS traffic, expander writes -- all silent during visible
|
||||
// flashes; DSI config + panel init identical to the flash-free Meck-P4). Remaining coupling is
|
||||
// electrical: the 4-bit 20 MHz SDMMC bus and/or the card's write-current spikes on the shared
|
||||
// rail. TDP4_SD_KHZ / TDP4_SD_1BIT (build-time) tune the first mount rung to test/derate.
|
||||
{
|
||||
#ifndef TDP4_SD_KHZ
|
||||
#define TDP4_SD_KHZ SDMMC_FREQ_DEFAULT // 20 MHz
|
||||
#endif
|
||||
#ifndef TDP4_SD_1BIT
|
||||
#define TDP4_SD_1BIT false
|
||||
#endif
|
||||
struct { bool onebit; int khz; const char *tag; } tries[] = {
|
||||
{ false, SDMMC_FREQ_DEFAULT, "4-bit 20MHz" },
|
||||
{ TDP4_SD_1BIT, TDP4_SD_KHZ, "primary (TDP4_SD_KHZ)" },
|
||||
{ false, 5000, "4-bit 5MHz" },
|
||||
{ true, SDMMC_FREQ_DEFAULT, "1-bit 20MHz" },
|
||||
{ true, 5000, "1-bit 5MHz" },
|
||||
@@ -197,20 +222,71 @@ static void wadameshSetup() {
|
||||
}
|
||||
printf("[storage] SD_MMC = %s\n", g_sd_ok ? "OK" : "no card");
|
||||
if (g_sd_ok) {
|
||||
SD_MMC.mkdir("/meshcomod"); SD_MMC.mkdir("/meshcomod/identity"); SD_MMC.mkdir("/meshcomod/bl");
|
||||
// One-time ADOPTION MIGRATION (the beta_36 lesson: never flip a storage root without migrating).
|
||||
// This device has been running with the store on FFat while the SD init was broken — if the card
|
||||
// has no identity yet but FFat does, copy the whole FFat store across BEFORE switching the root,
|
||||
// or the node would boot with a fresh identity and "lose" contacts/prefs/history.
|
||||
File sp = SD_MMC.open("/meshcomod/identity/_main.id", FILE_READ);
|
||||
bool sd_has_id = (bool)sp; if (sp) sp.close();
|
||||
File fp = FFat.open("/identity/_main.id", FILE_READ);
|
||||
bool ff_has_id = (bool)fp; if (fp) fp.close();
|
||||
if (!sd_has_id && ff_has_id) {
|
||||
printf("[storage] first SD adoption -> migrating FFat store to SD /meshcomod\n");
|
||||
migrateFsDir(FFat, "/", SD_MMC, "/meshcomod");
|
||||
// #167 RESOLUTION -- the hot store lives on INTERNAL FFat; the SD carries only bulk, gentle
|
||||
// writers (map tiles, backups). Root cause, established by on-device elimination: SD-card
|
||||
// WRITE bursts electrically disturb the AMOLED (whole-screen blue flash). The display stack
|
||||
// measured clean throughout -- framebuffer content, DSI bridge underrun, DSI host protocol
|
||||
// errors, DCS traffic, expander writes: all silent during visible flashes -- and the flash
|
||||
// survived every digital derate (4->1-bit bus, 20->10 MHz SD clock, 1000->750 Mbps DSI lane
|
||||
// rate) on EVERY card tried, at any brightness, yet vanished the moment the card was removed
|
||||
// or left unwritten. The card's supply rail is shared with the panel (XL9535 SD_EN switches
|
||||
// it off the same 3V3 domain), so its program-current spikes reach the panel and firmware
|
||||
// cannot filter them -- it can only stop hammering the card. Streaming tile writes never
|
||||
// flashed, so the SD keeps those. This is also why the flash-free Meck build proves nothing
|
||||
// about the board: it keeps its store internal and never writes the SD at all.
|
||||
//
|
||||
// One-time REVERSE migration: earlier P4 builds kept the live store on SD /meshcomod, so if
|
||||
// the card carries an identity and this build hasn't migrated yet, copy the store back to
|
||||
// FFat, OVERWRITING FFat's stale pre-adoption copies (the SD is the current truth -- booting
|
||||
// cardless on the old FFat set showed long-deleted contacts). SD data is left in place,
|
||||
// untouched, as a rollback.
|
||||
// v4 migration -- one lesson per predecessor: v1 trusted whichever card was in and copied a
|
||||
// blank store; v2 copied the whole SD tree and filled the partition before contacts3 landed;
|
||||
// v3 did format+whitelist correctly and STILL read back empty -- because the P4's FAT-on-flash
|
||||
// driver itself is broken (its stat layer lying was already documented; v3 proved writes are
|
||||
// no better). v4 = the same clean-slate whitelist copy, onto LittleFS. Card is never written.
|
||||
File mk = LittleFS.open("/.hot_store_on_lfs_v4", FILE_READ);
|
||||
bool migrated = (bool)mk; if (mk) mk.close();
|
||||
if (!migrated) {
|
||||
File sp = SD_MMC.open("/meshcomod/identity/_main.id", FILE_READ);
|
||||
bool sd_has_id = (bool)sp; if (sp) sp.close();
|
||||
auto fsize = [](fs::FS &f, const char *path) -> size_t {
|
||||
File h = f.open(path, FILE_READ); size_t n = h ? h.size() : 0; if (h) h.close(); return n;
|
||||
};
|
||||
size_t sd_meshcomod = fsize(SD_MMC, "/meshcomod/contacts3");
|
||||
size_t sd_root = fsize(SD_MMC, "/contacts3");
|
||||
size_t lf_contacts = fsize(LittleFS, "/contacts3");
|
||||
size_t sd_contacts = sd_meshcomod > sd_root ? sd_meshcomod : sd_root;
|
||||
if (sd_has_id && sd_contacts > lf_contacts) {
|
||||
printf("[storage] #167 v4: store SD -> LittleFS (contacts3 /meshcomod=%uB root=%uB, lfs had %uB)\n",
|
||||
(unsigned)sd_meshcomod, (unsigned)sd_root, (unsigned)lf_contacts);
|
||||
LittleFS.mkdir("/identity"); LittleFS.mkdir("/bl");
|
||||
const char *sub = sd_meshcomod >= sd_root ? "/meshcomod" : ""; // live layout root on the card
|
||||
char sp2[64];
|
||||
migrateFsDir(SD_MMC, "/meshcomod/identity", LittleFS, "/identity", true);
|
||||
migrateFsDir(SD_MMC, "/meshcomod/bl", LittleFS, "/bl", true);
|
||||
static const char *k_files[] = { "/contacts3", "/channels2", "/adv_blobs", "/new_prefs",
|
||||
"/new_prefs.tmp", "/regions2", "/ui_chat_history_v1.bin" };
|
||||
for (const char *nm : k_files) {
|
||||
snprintf(sp2, sizeof sp2, "%s%s", sub, nm);
|
||||
File probe = SD_MMC.open(sp2, FILE_READ); bool have = (bool)probe; if (probe) probe.close();
|
||||
if (!have && sub[0]) { snprintf(sp2, sizeof sp2, "%s", nm); }
|
||||
else if (!have) { snprintf(sp2, sizeof sp2, "/meshcomod%s", nm); }
|
||||
migrateFsCopyFile(SD_MMC, sp2, LittleFS, nm, true);
|
||||
}
|
||||
printf("[storage] #167 v4: done -- lfs used %u / %u KB, contacts3 readback %uB\n",
|
||||
(unsigned)(LittleFS.usedBytes() / 1024), (unsigned)(LittleFS.totalBytes() / 1024),
|
||||
(unsigned)fsize(LittleFS, "/contacts3"));
|
||||
File w = LittleFS.open("/.hot_store_on_lfs_v4", FILE_WRITE);
|
||||
if (w) { w.write((const uint8_t*)"1", 1); w.close(); }
|
||||
} else {
|
||||
printf("[storage] #167 v4: no migration (sd_id=%d sd=%uB lfs=%uB) -- LittleFS is the store\n",
|
||||
(int)sd_has_id, (unsigned)sd_contacts, (unsigned)lf_contacts);
|
||||
File w = LittleFS.open("/.hot_store_on_lfs_v4", FILE_WRITE);
|
||||
if (w) { w.write((const uint8_t*)"1", 1); w.close(); }
|
||||
}
|
||||
}
|
||||
store.useSdMmcStorage();
|
||||
// Deliberately NOT calling store.useSdMmcStorage(): FFat stays the DataStore root.
|
||||
}
|
||||
store.begin();
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
#include "esp_lcd_panel_io.h" // esp_lcd_panel_io_tx_param (runtime brightness 0x51)
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
#include "esp_lcd_rm69a10.h"
|
||||
#if defined(TDP4_POKE_TRACE)
|
||||
#include "soc/mipi_dsi_host_struct.h" // MIPI_DSI_HOST — raw host error status (poke-trace poller)
|
||||
#include "freertos/task.h"
|
||||
#endif
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
|
||||
@@ -20,6 +24,22 @@
|
||||
// the frame buffer as horizontal black/garbage bands, worst during transitions (many rapid
|
||||
// flushes). This binary semaphore makes each flush synchronous: draw_bitmap → wait for done.
|
||||
static SemaphoreHandle_t s_flush_sem = nullptr;
|
||||
#if defined(TDP4_POKE_TRACE)
|
||||
// #167 hunt, round 3: the esp_lcd driver watches ONLY the bridge-FIFO underrun (proven silent
|
||||
// during visible flashes). The DSI HOST has its own never-read error latches: int_st0 = the
|
||||
// panel acking packets WITH ERRORS, int_st1 = ECC/CRC/timeout/payload-write errors INCLUDING
|
||||
// dpi_buff_pld_under (the host payload buffer underflowing -- a different FIFO than the bridge).
|
||||
// Both are read-clear, so a 25 ms poll gives per-event capture. If these latch when the screen
|
||||
// flashes, we have a hardware flash-detector and can bisect subsystems without eyes on the panel.
|
||||
static void dsiErrPollTask(void*) {
|
||||
for (;;) {
|
||||
const uint32_t st0 = MIPI_DSI_HOST.int_st0.val; // RC
|
||||
const uint32_t st1 = MIPI_DSI_HOST.int_st1.val; // RC
|
||||
if (st0 || st1) printf("[DSIERR] %lu st0=%08X st1=%08X\n", (unsigned long)millis(), (unsigned)st0, (unsigned)st1);
|
||||
vTaskDelay(pdMS_TO_TICKS(25));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
static bool IRAM_ATTR rm69a10TransDone(esp_lcd_panel_handle_t, esp_lcd_dpi_panel_event_data_t*, void*) {
|
||||
BaseType_t hpw = pdFALSE;
|
||||
if (s_flush_sem) xSemaphoreGiveFromISR(s_flush_sem, &hpw);
|
||||
@@ -38,7 +58,19 @@ static bool IRAM_ATTR rm69a10TransDone(esp_lcd_panel_handle_t, esp_lcd_dpi_panel
|
||||
#endif
|
||||
#define RM_DPI_MHZ 60
|
||||
#define RM_LANES 2
|
||||
#define RM_BITRATE 1000 // Mbps per lane — TODO(device): confirm vs LilyGo RM69A10 define
|
||||
// Lane bit rate. LilyGo/Meck ship 1000 Mbps (confirmed against both sources 2026-07-31), and the
|
||||
// panel needs only ~550 Mbps of it at DPI 60 MHz / RGB565 / 2 lanes -- the rest is eye margin.
|
||||
// #167 made that margin matter: SD-card write bursts load the P4's on-chip LDO4 and disturb the
|
||||
// sibling LDO3 that powers the DSI PHY, and at a 1 ns unit interval that wobble was enough to
|
||||
// glitch the link -- the panel loses a frame to a whole-screen blue flash (proven by elimination:
|
||||
// no flash with the card out or unwritten; framebuffer/underrun/host-error/DCS all measured clean;
|
||||
// identical timings+init to the flash-free Meck build). A 750 Mbps derate was tried against #167 and changed nothing
|
||||
// (the disturbance reaches the panel around the link, not through it), so this stays at the
|
||||
// vendor value. Overridable per build: -DRM_BITRATE_MBPS=<n>.
|
||||
#ifndef RM_BITRATE_MBPS
|
||||
#define RM_BITRATE_MBPS 1000
|
||||
#endif
|
||||
#define RM_BITRATE RM_BITRATE_MBPS
|
||||
#define RM_HSYNC 50
|
||||
#define RM_HBP 150
|
||||
#define RM_HFP 50
|
||||
@@ -139,6 +171,9 @@ bool RM69A10Display::begin() {
|
||||
cbs.on_color_trans_done = rm69a10TransDone;
|
||||
esp_lcd_dpi_panel_register_event_callbacks(_panel, &cbs, nullptr);
|
||||
|
||||
#if defined(TDP4_POKE_TRACE)
|
||||
xTaskCreatePinnedToCore(dsiErrPollTask, "dsierr", 3072, nullptr, 3, nullptr, 1);
|
||||
#endif
|
||||
Serial.printf("[RM69A10] up %dx%d\n", RM_W, RM_H);
|
||||
return true;
|
||||
}
|
||||
@@ -221,10 +256,20 @@ void RM69A10Display::writePixelsRGB565(int x, int y, int w, int h, const uint16_
|
||||
|
||||
void RM69A10Display::setBrightness(uint8_t b) {
|
||||
if (!_dbi_io) return;
|
||||
#if defined(TDP4_POKE_TRACE)
|
||||
// #167 hunt: DCS 0x51 is a low-power command injected into the LIVE video stream -- if the
|
||||
// panel glitches a frame per command, these lines will pace the visible flashes exactly.
|
||||
printf("[BRI] %lu val=%u\n", (unsigned long)millis(), b);
|
||||
#endif
|
||||
esp_lcd_panel_io_tx_param(_dbi_io, 0x51, &b, 1); // DCS SET_DISPLAY_BRIGHTNESS
|
||||
}
|
||||
|
||||
#if defined(TDP4_POKE_TRACE)
|
||||
void RM69A10Display::turnOn() { printf("[DSP] %lu on\n", (unsigned long)millis()); if (_panel) esp_lcd_panel_disp_on_off(_panel, true); _on = true; }
|
||||
void RM69A10Display::turnOff() { printf("[DSP] %lu off\n", (unsigned long)millis()); if (_panel) esp_lcd_panel_disp_on_off(_panel, false); _on = false; }
|
||||
#else
|
||||
void RM69A10Display::turnOn() { if (_panel) esp_lcd_panel_disp_on_off(_panel, true); _on = true; }
|
||||
void RM69A10Display::turnOff() { if (_panel) esp_lcd_panel_disp_on_off(_panel, false); _on = false; }
|
||||
#endif
|
||||
|
||||
#endif // HAS_TDISPLAY_P4
|
||||
|
||||
@@ -51,6 +51,11 @@ void Xl9535::write(uint8_t io, bool high) {
|
||||
uint8_t port, bit; split(io, port, bit);
|
||||
if (high) _out[port] |= (1 << bit);
|
||||
else _out[port] &= ~(1 << bit);
|
||||
#if defined(TDP4_POKE_TRACE)
|
||||
// #167 hunt: any expander write while the panel streams is a glitch suspect (this is an
|
||||
// UNLOCKED shadow RMW -- a cross-task race can transiently drop SCREEN_RST or a rail bit).
|
||||
printf("[XLW] %lu io=%u v=%d out=%02X%02X\n", (unsigned long)millis(), io, (int)high, _out[1], _out[0]);
|
||||
#endif
|
||||
writeReg(REG_OUT0 + port, _out[port]);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user