Commit Graph
4 Commits
Author SHA1 Message Date
Kaj SchittecatandClaude Opus 4.8 3bf56cdd37 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>
2026-07-31 14:50:37 +02:00
Kaj SchittecatandClaude Opus 4.8 ecb506694d touch: P4 antenna — FIX INVERTED POLARITY, internal is HIGH not LOW
b066c37 guessed the antenna mapping from field symptoms and guessed it BACKWARDS,
so "Internal" was selecting the external socket and the boot park pinned the
external connector — the exact hazard the change was meant to remove. Corrected
against LilyGo's own sources, which document this pin explicitly:

  lilygo_device_driver  src/device/t_display_p4/t_display_p4_config.h
    inline constexpr auto kSky13453Vctl = cpp_bus_driver::Xl95x5::Pin::kIo1;
  lilygobox-espidf      main/hal/device/t_display_p4/t_display_p4_device.cpp
    const uint8_t antenna_level = config.antenna == AntennaType::kExternal ? 0 : 1;
    GpioWrite(gpio::xl9535::kSky13453Vctl, antenna_level);
    ... logged as   kExternal ? "RF2" : "RF1"

So HIGH = RF1 = on-board antenna, LOW = RF2 = external socket. INTERNAL_LEVEL
flips false -> true. The one-constant escape hatch written into b066c37 is what
made this a one-line correction.

Two things this settles beyond the polarity. The vendor's AntennaType enum is
literally kInternal/kExternal, so the line IS an antenna select and not a TX/RX
path — the architectural half of the earlier reasoning was right. And LilyGo
preloads VCTL = 1 in their own XL9535 init as their stated safe state, so
booting on the on-board antenna is the vendor's choice too, not just ours.

It also explains the field report properly: the legacy per-TX toggle idled LOW
to receive (= EXTERNAL, where the reporter's antenna is fitted, hence a healthy
+12 dB inbound) and drove HIGH to transmit (= the on-board antenna, hence ~22 dB
down at the repeater). It transmits internal and listens external, which is the
opposite of what a3550d2 claimed. So auto is NOT a PA hazard after all — its
transmit lands on the permanently attached on-board antenna — it is just
guaranteed lopsided. Its confirmation text now says that instead of warning
about damage, and only External keeps the damage warning. UI strings say
"external antenna socket" rather than "MMCX", since the board has two MMCX
connectors and only one of them is the LoRa path.

Verified on hardware (T-Display P4, MAC ...e1:c2:a7): clean boot, only
rst:0x1 (POWERON), antenna park runs, SNTP -> rtc 1785413952.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-30 14:19:48 +02:00
Kaj SchittecatandClaude Opus 4.8 b066c379c0 touch: P4 antenna — internal on every boot, confirm before going external
Reworks the P4 antenna setting so the failure mode it protects against is
impossible by default. Transmitting into an external connector with nothing
fitted is what destroys a PA, so the state you get for free after any power
cycle, crash or OTA has to be the on-board antenna.

  - The on-board antenna is forced at EVERY boot, in two places: the park in
    Xl9535::powerOnSequence() (before the radio object even exists) and a
    re-assert in UITask::begin() in case anything touched IO1 in between.
  - The choice is now session-only and is never written to flash. That is the
    whole point: if "external" could survive a reboot, the safety property is
    gone. touchPrefsGet/SetP4Antenna are removed; the p4_antenna byte stays in
    the struct as reserved so TOUCH_CFG_VER does not have to rewind.
  - Selecting External raises a confirmation naming the MMCX socket and the
    risk. showConfirm has no cancel hook, so the dropdown reverts immediately
    and is only re-selected from the confirm handler — a dismissed dialog
    therefore leaves both the UI and the hardware on internal.
  - The dropdown reads the live xl9535.antennaMode() rather than a stored pref,
    so it can never claim an antenna the hardware is not actually on.

Also renames the modes from the opaque Auto/Pinned A/Pinned B to Internal /
External / Auto (legacy). The reasoning for treating IO1 as an antenna select
rather than a TX/RX path switch is written up on Xl9535.h: a per-transmit
switch must settle within microseconds of the PA ramping, and an I2C expander
write on a bus shared with touch, the RTC and the fuel gauge cannot do that,
while the SX1262 has DIO2 for exactly that job. Which LEVEL is which antenna is
still unconfirmed against LilyGo's schematic, but the SAFE level is known from
the field: the legacy mode sits LOW to receive and every P4 receives fine at
+12 dB, so LOW demonstrably radiates into something connected, while HIGH was
only ever used for transmit and is the suspect. One named constant
(INTERNAL_LEVEL) flips the mapping if the schematic ever says otherwise.

Builds clean on the P4 IDF target plus the V4 and T-Deck S3 envs (the shared
TouchPrefsStore change); compile-verified only, the P4 is off the USB bus.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-30 13:51:15 +02:00
Kaj SchittecatandClaude Opus 4.8 2353c56adc p4: T-Display P4 port — web VNC/remote, time-sync hardening, brightness + sound
LilyGo T-Display P4 (ESP32-P4 + factory ESP-AT C6) reaches POC: LoRa mesh, Wi-Fi
companion (TCP:5000), GPS, SD, battery gauge, chat persistence, and now:

- Web VNC/REMOTE/terminal via a first-byte router on the single ESP-AT listener
  (HTTP verbs -> WS server, '<' frames -> phone companion; both live at once).
  C6Server gains a _begun gate so never-listening instances stay inert.
- VNC freeze fixed: bounded one-CIPSEND-chunk drains under the WS client mutex
  (a whole-band blocking write starved the main loop), fast-drop of stalled
  mirror clients, SEND-OK wait 8s->4s.
- Wi-Fi robustness: CWJAP? miss-streak tolerance, CIPSERVER liveness verify +
  re-arm (a re-association silently killed the listener until reboot).
- Time sync: HTTP-Date fallback when SNTP is blocked; ClockFloorRTC gains a
  MAX_PLAUSIBLE_EPOCH guard (all boards) after a garbage-future hardware RTC
  read (2043) latched the ratchet.
- Brightness: RM69A10 DCS 0x51 driven live (CC slider + persisted pref).
- Sound: ES8311 + NS4150B notification chimes (P4Audio, esp-bsp-derived
  register sequence on Wire1), full HAS_UI_SOUND surface + CC Screen/Sound
  sliders; ccVolumeReleaseCb's hardcoded tanBeep -> uiSoundPreview.
- tdisplay_p4/: IDF project (build.sh, c6_at AT-over-SDIO driver, sdkconfigs,
  fetch-deps.sh vendoring, .gitignore for local payloads).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 19:13:23 +02:00