fix: full TCON re-init on first SEEED render after deep-sleep wake#2
Merged
davelee98 merged 2 commits intoJul 8, 2026
Merged
Conversation
On the ED103TC2/IT8951 SEEED_GFX path, deep-sleep wake is a full reboot that power-cycles the panel/TCON rail, but the post-wake direct-write seam only called EPaper::wake() -- a no-op after reboot (g_seeed_epaper is plain RAM, so _sleep resets to false) that also only issues tconWake() and never runs hostTconInit() (VCOM / panel dimensions / I80CPCR packed-pixel mode). The first post-wake image push therefore landed on an un-initialized TCON, risking a garbled/blank refresh or a TCON-busy stall. Track init state in a plain-RAM (NOT RTC_DATA_ATTR) file-static flag that is false after every reset -- including deep-sleep wake, which is exactly when a full begin() is required -- and run the full seeed_gfx_epaper_begin() on the first render each boot, keeping the cheap wake() for subsequent same-session writes. Also restores the correct gray-mode sprite on 4-gray panels post-wake. The non-SEEED bb_epaper path re-inits unconditionally and is unaffected. This is independent of the prvCopyDataToQueue BLE panic in minimalSetup(). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wkc4weAfTh36iPGHawXAxi
An image push logged 6-8 lines per 0x71 data frame across the receive callback, command/response queue drain, imageDataWritten, and sendResponse -- ~1 MB of UART text for a 1.3 MB image. At 115200 baud writeSerial blocks once the TX buffer fills, throttling the transfer itself. Replace the per-frame spam with a compact stream log: the first frame in full, a 5%-step percentage meter thereafter, and a completion summary with the final frame, total chunk count, elapsed time, and KB/s throughput. New file-static logging state + helpers in display_service.cpp (imageWriteLogReset/Start/Chunk/Progress/Finish) drive the meter; three exported predicates (imageWriteLogQuietCmd/Ack/Frame) let communication.cpp, main.cpp's queue drain, and the esp32_ble_callbacks receive path suppress their per-frame lines mid-stream while keeping chunk 1, errors, and NACKs visible. Applies to both direct-write and partial-write paths; the meter tracks decompressed progress on compressed uploads. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
davelee98
marked this pull request as ready for review
July 8, 2026 23:48
davelee98
added a commit
that referenced
this pull request
Jul 18, 2026
…dit #1-3,6) The Bluedroid->NimBLE migration left heavyweight, state-mutating work running inline inside the ESP32 server callbacks on the NimBLE host task, racing the Arduino loop() task. Make the callbacks flag-only and service the flags from loop(), mirroring the existing bleRestartAdvertisingPending pattern. - onDisconnect: EPD session teardown (bbepSleep/delay/SPI.end + rail cut), partial + pipe cleanup, and resetPipeWriteState() ran on the host task while loop() streamed SPI via bbepWriteData and processed pipe frames. The only guard (epdRefreshInProgress) covered the refresh phase, not streaming. Now sets bleDisconnectCleanupPending; serviced by serviceBleDisconnectCleanup() in loop() (preserving the epdRefreshInProgress deferral), run before the advertising restart. (#1 critical, #2 high, OpenDisplay#6 medium) - onConnect: updatemsdata() polled I2C and mutated the shared advertisement vector that loop() also drives on its 60s cadence -> heap corruption. Now sets msdUpdatePending; loop() calls updatemsdata(). (#3 high) - updatemsdata(): drop the connected-branch that rebuilt *advertisementData but never pushed it via setAdvertisementData() -- dead work, no behavior change. All ESP32-guarded; nRF/Bluefruit path untouched. Builds clean on esp32-s3-N16R8, esp32-c3-N16, nrf52840custom. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
davelee98
added a commit
that referenced
this pull request
Jul 19, 2026
…Display#107) (OpenDisplay#108) * fix(ble): defer NimBLE onConnect/onDisconnect work to loop() (race audit #1-3,6) The Bluedroid->NimBLE migration left heavyweight, state-mutating work running inline inside the ESP32 server callbacks on the NimBLE host task, racing the Arduino loop() task. Make the callbacks flag-only and service the flags from loop(), mirroring the existing bleRestartAdvertisingPending pattern. - onDisconnect: EPD session teardown (bbepSleep/delay/SPI.end + rail cut), partial + pipe cleanup, and resetPipeWriteState() ran on the host task while loop() streamed SPI via bbepWriteData and processed pipe frames. The only guard (epdRefreshInProgress) covered the refresh phase, not streaming. Now sets bleDisconnectCleanupPending; serviced by serviceBleDisconnectCleanup() in loop() (preserving the epdRefreshInProgress deferral), run before the advertising restart. (#1 critical, #2 high, OpenDisplay#6 medium) - onConnect: updatemsdata() polled I2C and mutated the shared advertisement vector that loop() also drives on its 60s cadence -> heap corruption. Now sets msdUpdatePending; loop() calls updatemsdata(). (#3 high) - updatemsdata(): drop the connected-branch that rebuilt *advertisementData but never pushed it via setAdvertisementData() -- dead work, no behavior change. All ESP32-guarded; nRF/Bluefruit path untouched. Builds clean on esp32-s3-N16R8, esp32-c3-N16, nrf52840custom. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Replace protocol magic numbers with canonical named constants Follow-up to the header vendoring (3f09f6d): sweep the remaining hand-coded protocol literals across the BLE command handlers and use the constants from the vendored opendisplay_protocol.h. Value-for-value, no wire-behavior change; all CI envs build clean. - encryption.cpp: auth-status frames -> RESP_ACK/RESP_AUTHENTICATE + AUTH_STATUS_*; CCM envelope sizes -> BLE_CMD_HEADER_SIZE/ ENCRYPTION_NONCE_SIZE/ENCRYPTION_TAG_SIZE (handshake nonces left as-is). - communication.cpp: handler response codes -> RESP_* (incl. RESP_AUTH_REQUIRED); config limits -> CONFIG_CHUNK_SIZE*/ MAX_CONFIG_CHUNKS/MAX_RESPONSE_DATA_SIZE; envelope sizes. - device_control.cpp / buzzer_control.cpp: LED, deep-sleep, and buzzer ack/nack frames -> RESP_*. - display_service.cpp: direct-write/refresh frames -> RESP_DIRECT_WRITE_*; pipe-start errors -> OD_ERR_PIPE_START_*; deleted the local ERR_* shadow copies of OD_ERR_PARTIAL_* in favor of the canonical names. Pipe/partial-write opcode-echo bytes (0x76/0x80/0x81/0x82), the pipe data-phase error codes, and the START response-flags bit stay raw with TODO(protocol) markers: the canonical header defines no RESP_* mirror or data-phase error namespace for them yet. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On the SEEED_GFX (ED103TC2 / IT8951 TCON) display path, deep-sleep wake is a full ESP32 reboot that power-cycles the panel/TCON rail (
src/main.cpp"Panel rail is cut";pwrmgm(true)= pwr_pin HIGH +delay(800); AXP2101 boards fully power-cycle the PMIC). The IT8951 therefore loses all register state.The post-wake render seam
seeed_gfx_direct_write_reset()only calledg_seeed_epaper.wake(), which:g_seeed_epaperis a plain static, so its_sleepflag is reconstructed tofalseandwake()hitsif (!_sleep) return;, andwake()only issuestconWake()— it never runshostTconInit()(VCOM, panel dimensions,I80CPCRpacked-pixel mode), which lives ininit()/begin().So the first image push after a deep-sleep wake lands on an un-initialized TCON → likely garbled/blank first refresh or a TCON-busy stall (15 s watchdog). The non-SEEED bb_epaper path is immune (it re-inits unconditionally every direct write).
Fix
Track whether the SEEED driver has been fully initialized since the current boot with a plain-RAM (deliberately not
RTC_DATA_ATTR) file-static flag — false after every reset, including deep-sleep wake, which is exactly when a fullbegin()is required. On the first render each boot, run the fullseeed_gfx_epaper_begin()(doeshostTconInit()+ correct gray-mode sprite alloc); keep the cheapwake()for subsequent same-session writes.wake().wake().Single-file change:
src/display_seeed_gfx.cpp(3 hunks).Notes / testing
reset-reason-logging(the reset-logging diagnostic branch); PR targets that branch.esp32-s3-*OPENDISPLAY_SEEED_GFXenv, and (2) on an ED103TC2 unit (ideally 4-gray), reproduce the pre-fix garbled/stalled first post-wake refresh, then confirm it renders correctly after the fix and cold-boot/normal writes are unchanged.prvCopyDataToQueueBLE panic inminimalSetup()— that fires before any display code and affects bb_epaper units too.🤖 Generated with Claude Code
https://claude.ai/code/session_01Wkc4weAfTh36iPGHawXAxi