diff --git a/platformio.ini b/platformio.ini index 2e2d1c1..8c5ccb0 100644 --- a/platformio.ini +++ b/platformio.ini @@ -145,13 +145,14 @@ board_upload.maximum_ram_size = 327680 board_upload.flash_size = 16MB monitor_speed = 115200 -#[env:esp32-N4] -#platform = espressif32 -#framework = arduino -#build_flags = -# -DTARGET_ESP32 -# -DCONFIG_FREERTOS_WATCHDOG_TIMEOUT_S=120 -#board_build.filesystem = littlefs -#board = esp32dev -#board_build.partitions = huge_app.csv -#monitor_speed = 115200 +[env:esp32-N4] +platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip +framework = arduino +build_flags = + -DTARGET_ESP32 + -DCONFIG_FREERTOS_WATCHDOG_TIMEOUT_S=120 +board_build.filesystem = littlefs +board = esp32dev +board_build.partitions = huge_app.csv +monitor_speed = 115200 +lib_ignore = Seeed_GFX diff --git a/src/ble_init.cpp b/src/ble_init.cpp index 12f66c4..b70db6f 100644 --- a/src/ble_init.cpp +++ b/src/ble_init.cpp @@ -93,6 +93,28 @@ void ble_init() { #endif #ifdef TARGET_ESP32 +volatile bool bleRestartAdvertisingPending = false; + +void esp32_restart_ble_advertising(void) { + if (pServer == nullptr) { + bleRestartAdvertisingPending = true; + return; + } + if (pServer->getConnectedCount() > 0) { + bleRestartAdvertisingPending = false; + return; + } + if (epdRefreshInProgress) { + bleRestartAdvertisingPending = true; + return; + } + bleRestartAdvertisingPending = false; + delay(100); + BLEDevice::startAdvertising(); + updatemsdata(); + writeSerial("BLE advertising restarted"); +} + void ble_init_esp32(bool update_manufacturer_data) { writeSerial("=== Initializing ESP32 BLE ==="); String deviceName = "OD" + getChipIdHex(); diff --git a/src/ble_init.h b/src/ble_init.h index 75b5676..f11ccec 100644 --- a/src/ble_init.h +++ b/src/ble_init.h @@ -8,6 +8,8 @@ void ble_nrf_advertising_start(); #ifdef TARGET_ESP32 void ble_init(); void ble_init_esp32(bool update_manufacturer_data = true); +void esp32_restart_ble_advertising(void); +extern volatile bool bleRestartAdvertisingPending; #endif #endif diff --git a/src/config_parser.cpp b/src/config_parser.cpp index 9539d5f..29097a7 100644 --- a/src/config_parser.cpp +++ b/src/config_parser.cpp @@ -1,7 +1,5 @@ #include "config_parser.h" #include "structs.h" - -static_assert(sizeof(struct WifiConfig) == 160, "wifi_config must match config.yaml (32+32+1+95)"); #include "encryption_state.h" #include "encryption.h" #include diff --git a/src/display_service.cpp b/src/display_service.cpp index 387df44..b095714 100644 --- a/src/display_service.cpp +++ b/src/display_service.cpp @@ -10,6 +10,7 @@ #include "communication.h" #include "encryption.h" #include "boot_screen.h" +#include "touch_input.h" #include "uzlib.h" #if defined(TARGET_ESP32) && defined(OPENDISPLAY_SEEED_GFX) #include "display_seeed_gfx.h" @@ -24,6 +25,7 @@ extern "C" { #ifdef TARGET_ESP32 #include +#include "ble_init.h" #include "wifi_service.h" #endif @@ -50,9 +52,15 @@ extern bool directWritePlane2; extern bool directWriteBitplanes; extern bool directWriteCompressed; extern bool directWriteActive; +volatile bool epdRefreshInProgress = false; extern uint8_t* compressedDataBuffer; +#if defined(TARGET_ESP32) +extern uint8_t* dictionaryBuffer; +extern uint8_t* decompressionChunk; +#else extern uint8_t dictionaryBuffer[]; extern uint8_t decompressionChunk[]; +#endif extern uint32_t displayed_etag; @@ -331,19 +339,47 @@ void initDataBuses(){ writeSerial("=== Data Bus Initialization Complete ===", true); } +#ifdef TARGET_ESP32 +static bool s_wire_open_display_ready = false; +static int8_t s_wire_sda_pin = -1; +static int8_t s_wire_scl_pin = -1; +static uint32_t s_wire_clock_hz = 0; +#endif + +void invalidateOpenDisplayWire(void) { +#ifdef TARGET_ESP32 + s_wire_open_display_ready = false; +#endif +} + void initOrRestoreWireForOpenDisplay(void) { #ifdef TARGET_ESP32 if (globalConfig.data_bus_count > 0) { const struct DataBus& bus = globalConfig.data_buses[0]; if (bus.bus_type == 0x01 && bus.pin_1 != 0xFF && bus.pin_2 != 0xFF) { uint32_t hz = bus.bus_speed_hz ? bus.bus_speed_hz : 100000u; - Wire.begin((int)bus.pin_2, (int)bus.pin_1); + int sda = (int)bus.pin_2; + int scl = (int)bus.pin_1; + if (s_wire_open_display_ready && s_wire_sda_pin == sda && s_wire_scl_pin == scl && + s_wire_clock_hz == hz) { + return; + } + Wire.begin(sda, scl); Wire.setClock(hz); + s_wire_sda_pin = (int8_t)sda; + s_wire_scl_pin = (int8_t)scl; + s_wire_clock_hz = hz; + s_wire_open_display_ready = true; return; } } -#endif + if (!s_wire_open_display_ready) { + Wire.begin(); + s_wire_open_display_ready = true; + } +#else Wire.begin(); +#endif } void initio(){ @@ -964,12 +1000,16 @@ void initDisplay(){ if (! (globalConfig.displays[0].transmission_modes & TRANSMISSION_MODE_CLEAR_ON_BOOT)){ writeBootScreenWithQr(); writeSerial("EPD refresh: FULL (boot, Seeed)", true); + touchSuspendForEpdRefresh(); seeed_gfx_full_update(); waitforrefresh(60); seeed_gfx_sleep_after_refresh(); delay(200); + pwrmgm(false); + touchResumeAfterEpdRefresh(); + } else { + pwrmgm(false); } - pwrmgm(false); } else #endif { @@ -986,12 +1026,16 @@ void initDisplay(){ if (! (globalConfig.displays[0].transmission_modes & TRANSMISSION_MODE_CLEAR_ON_BOOT)){ writeBootScreenWithQr(); writeSerial("EPD refresh: FULL (boot)", true); + touchSuspendForEpdRefresh(); bbepRefresh(&bbep, REFRESH_FULL); waitforrefresh(60); bbepSleep(&bbep, 1); delay(200); + pwrmgm(false); + touchResumeAfterEpdRefresh(); + } else { + pwrmgm(false); } - pwrmgm(false); } } else{ @@ -1145,6 +1189,7 @@ void handleDirectWriteCompressedData(uint8_t* data, uint16_t len) { void decompressDirectWriteData() { if (directWriteCompressedReceived == 0) return; + if (!dictionaryBuffer || !decompressionChunk) return; struct uzlib_uncomp d; memset(&d, 0, sizeof(d)); d.source = directWriteCompressedBuffer; @@ -1208,8 +1253,11 @@ void cleanupDirectWriteState(bool refreshDisplay) { } void handleDirectWriteStart(uint8_t* data, uint16_t len) { - if (partialCtx.active) cleanup_partial_write_state(); - if (directWriteActive) cleanupDirectWriteState(false); +if (partialCtx.active) cleanup_partial_write_state(); + if (directWriteActive) { + cleanupDirectWriteState(false); + } + touchSuspendForEpdRefresh(); #if defined(TARGET_ESP32) && defined(OPENDISPLAY_SEEED_GFX) if (seeed_driver_used()) { seeed_gfx_prepare_hardware(); @@ -1232,6 +1280,7 @@ void handleDirectWriteStart(uint8_t* data, uint16_t len) { if (directWriteCompressed) { if (!compressedDataBuffer) { cleanupDirectWriteState(false); + touchResumeAfterEpdRefresh(); uint8_t errorResponse[] = {0xFF, 0xFF}; sendResponse(errorResponse, sizeof(errorResponse)); return; @@ -1245,6 +1294,7 @@ void handleDirectWriteStart(uint8_t* data, uint16_t len) { uint32_t cap = max_compressed_image_rx_bytes(globalConfig.displays[0].transmission_modes); if (compressedDataLen > cap) { cleanupDirectWriteState(false); + touchResumeAfterEpdRefresh(); uint8_t errorResponse[] = {0xFF, 0xFF}; sendResponse(errorResponse, sizeof(errorResponse)); return; @@ -1466,6 +1516,7 @@ void handleDirectWriteEnd(uint8_t* data, uint16_t len) { uint8_t ackResponse[] = {0x00, 0x72}; sendResponse(ackResponse, sizeof(ackResponse)); delay(20); + epdRefreshInProgress = true; bool refreshSuccess = false; uint32_t newEtag = 0; bool hasNewEtag = data != nullptr && len >= 5; @@ -1482,8 +1533,13 @@ void handleDirectWriteEnd(uint8_t* data, uint16_t len) { refreshSuccess = waitforrefresh(60); bbepSleep(&bbep, 1); } + epdRefreshInProgress = false; delay(50); cleanupDirectWriteState(false); + touchResumeAfterEpdRefresh(); +#ifdef TARGET_ESP32 + esp32_restart_ble_advertising(); +#endif if (refreshSuccess) { if (hasNewEtag && newEtag != 0) displayed_etag = newEtag; uint8_t refreshResponse[] = {0x00, 0x73}; diff --git a/src/display_service.h b/src/display_service.h index d478fbe..d94c31e 100644 --- a/src/display_service.h +++ b/src/display_service.h @@ -16,6 +16,8 @@ void initio(); void initDataBuses(); /** Re-apply I2C from data_bus[0] when set; else Wire.begin(). Call before TCON/touch on shared bus. */ void initOrRestoreWireForOpenDisplay(void); +/** Call after Wire.end() so the next touch/sensor access re-inits the bus. */ +void invalidateOpenDisplayWire(void); void scanI2CDevices(); void initSensors(); void initAXP2101(uint8_t busId); @@ -29,6 +31,7 @@ void handleDirectWriteCompressedData(uint8_t* data, uint16_t len); void decompressDirectWriteData(); void cleanupDirectWriteState(bool refreshDisplay); void handleDirectWriteEnd(uint8_t* data, uint16_t len); +extern volatile bool epdRefreshInProgress; void handlePartialWriteStart(uint8_t* data, uint16_t len); int getplane(); int getBitsPerPixel(); diff --git a/src/esp32_ble_callbacks.h b/src/esp32_ble_callbacks.h index a781006..6694271 100644 --- a/src/esp32_ble_callbacks.h +++ b/src/esp32_ble_callbacks.h @@ -8,6 +8,7 @@ #include #include #include +#include "ble_init.h" #ifndef COMMAND_QUEUE_SIZE #define COMMAND_QUEUE_SIZE 5 @@ -29,6 +30,9 @@ extern uint8_t rebootFlag; void updatemsdata(); void cleanupDirectWriteState(bool refreshDisplay); +void touchResumeAfterEpdRefresh(void); +extern volatile bool epdRefreshInProgress; +extern bool directWriteActive; class MyBLEServerCallbacks : public BLEServerCallbacks { void onConnect(BLEServer* pServer) { @@ -37,16 +41,15 @@ class MyBLEServerCallbacks : public BLEServerCallbacks { updatemsdata(); } void onDisconnect(BLEServer* pServer) { + (void)pServer; writeSerial("=== BLE CLIENT DISCONNECTED (ESP32) ==="); - cleanupDirectWriteState(true); - writeSerial("Waiting before restarting advertising..."); - delay(500); - if (pServer->getConnectedCount() == 0) { - BLEDevice::startAdvertising(); - writeSerial("Advertising restarted"); - } else { - writeSerial("not restarting advertising"); + if (epdRefreshInProgress) { + writeSerial("EPD refresh in progress — deferring cleanup/advertising to main loop"); + } else if (directWriteActive) { + cleanupDirectWriteState(true); + touchResumeAfterEpdRefresh(); } + bleRestartAdvertisingPending = true; } }; diff --git a/src/main.cpp b/src/main.cpp index 7d7b0c2..7cb6674 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,6 +8,7 @@ #include "display_service.h" #include "touch_input.h" #include "encryption.h" +#include "ble_init.h" #if defined(TARGET_ESP32) && defined(OPENDISPLAY_LOG_UART) #include @@ -23,7 +24,7 @@ static HardwareSerial LogSerialPort(1); #if defined(TARGET_NRF) static uint8_t s_compressedDataStorage[MAX_COMPRESSED_BUFFER_BYTES]; uint8_t* compressedDataBuffer = s_compressedDataStorage; -#elif defined(TARGET_ESP32) && defined(TARGET_LARGE_MEMORY) && defined(BOARD_HAS_PSRAM) +#elif defined(TARGET_ESP32) uint8_t* compressedDataBuffer = nullptr; #else static uint8_t s_compressedDataStorage[MAX_COMPRESSED_BUFFER_BYTES]; @@ -31,12 +32,20 @@ uint8_t* compressedDataBuffer = s_compressedDataStorage; #endif void allocCompressedDataBuffer(void) { -#if defined(TARGET_ESP32) && defined(TARGET_LARGE_MEMORY) && defined(BOARD_HAS_PSRAM) +#if defined(TARGET_ESP32) if (compressedDataBuffer) return; +#if defined(TARGET_LARGE_MEMORY) && defined(BOARD_HAS_PSRAM) compressedDataBuffer = (uint8_t*)heap_caps_malloc(MAX_COMPRESSED_BUFFER_BYTES, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); +#endif if (!compressedDataBuffer) { compressedDataBuffer = (uint8_t*)heap_caps_malloc(MAX_COMPRESSED_BUFFER_BYTES, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); } + if (!decompressionChunk) { + decompressionChunk = (uint8_t*)heap_caps_malloc(DECOMP_CHUNK_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + } + if (!dictionaryBuffer) { + dictionaryBuffer = (uint8_t*)heap_caps_malloc(MAX_DICT_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + } #endif } @@ -159,6 +168,9 @@ void loop() { bleDrain++; } } + if (bleRestartAdvertisingPending) { + esp32_restart_ble_advertising(); + } if (directWriteActive && directWriteStartTime > 0) { uint32_t directWriteDuration = millis() - directWriteStartTime; if (directWriteDuration > 900000UL) { // 15 minute timeout (upload + refresh window) @@ -174,6 +186,8 @@ void loop() { bool bleActive = (commandQueueTail != commandQueueHead) || (responseQueueTail != responseQueueHead) || (pServer && pServer->getConnectedCount() > 0) || + bleRestartAdvertisingPending || + epdRefreshInProgress || wifiLanSession; if (bleActive) { processButtonEvents(); @@ -323,6 +337,7 @@ void pwrmgm(bool onoff){ writeSerial("Powering down AXP2101 PMIC..."); powerDownAXP2101(); Wire.end(); + invalidateOpenDisplayWire(); pinMode(47, OUTPUT); digitalWrite(47, HIGH); pinMode(48, OUTPUT); @@ -348,6 +363,7 @@ void pwrmgm(bool onoff){ else{ SPI.end(); Wire.end(); + invalidateOpenDisplayWire(); pinMode(globalConfig.displays[0].reset_pin, INPUT); pinMode(globalConfig.displays[0].cs_pin, INPUT); if (globalConfig.displays[0].dc_pin != 0xFF) { @@ -362,6 +378,7 @@ void pwrmgm(bool onoff){ } else { SPI.end(); Wire.end(); + invalidateOpenDisplayWire(); } } if(globalConfig.system_config.pwr_pin != 0xFF){ diff --git a/src/main.h b/src/main.h index 21d19ab..5c21279 100644 --- a/src/main.h +++ b/src/main.h @@ -123,8 +123,13 @@ void bbepWriteData(BBEPDISP *pBBEP, uint8_t *pData, int iLen); extern uint8_t* compressedDataBuffer; void allocCompressedDataBuffer(void); +#ifdef TARGET_ESP32 +uint8_t* decompressionChunk = nullptr; +uint8_t* dictionaryBuffer = nullptr; +#else uint8_t decompressionChunk[DECOMP_CHUNK_SIZE]; uint8_t dictionaryBuffer[MAX_DICT_SIZE]; +#endif uint8_t bleResponseBuffer[94]; uint8_t mloopcounter = 0; uint8_t rebootFlag = 1; // Set to 1 after reboot, cleared to 0 after BLE connection diff --git a/src/touch_input.cpp b/src/touch_input.cpp index 421d699..5ed59cd 100644 --- a/src/touch_input.cpp +++ b/src/touch_input.cpp @@ -13,6 +13,9 @@ extern struct GlobalConfig globalConfig; extern uint8_t dynamicreturndata[11]; +#ifdef TARGET_ESP32 +extern bool directWriteActive; +#endif void updatemsdata(void); void writeSerial(String message, bool newLine = true); @@ -28,6 +31,11 @@ static_assert(sizeof(TouchController) == 32, "TouchController must be 32 bytes f #define GT911_STATUS_BUFFER_READY 0x80u #define GT911_MAX_CONTACTS 5u /* MSD byte0 low nibble: GT911 contact count 1–5 while touching; 6 = released (last x,y in bytes 1–4; high nibble = last track id). 0 = never touched (5-byte block cleared). */ +#define GT911_I2C_RETRIES 3 +#define GT911_I2C_RETRY_DELAY_US 500 +#define TOUCH_I2C_FAIL_BACKOFF_MS 100 +#define TOUCH_PROCESS_MIN_INTERVAL_MS 100 +#define TOUCH_I2C_FAIL_DISABLE_THRESHOLD 5 #ifndef TOUCH_DEBUG #define TOUCH_DEBUG 1 @@ -44,13 +52,69 @@ struct TouchRuntime { uint8_t last_id; uint32_t last_poll_ms; uint32_t last_i2c_warn_ms; + uint32_t last_i2c_fail_ms; uint8_t touch_latched; + uint8_t i2c_fail_streak; + uint8_t disabled; }; static TouchRuntime s_touch_rt[4]; +static uint32_t s_last_touch_process_ms = 0; +static uint8_t s_epd_refresh_suspend = 0; + +static void gt911_drain_wire(void) { + while (Wire.available()) { + (void)Wire.read(); + } +} + +static bool gt911_read_reg_once(uint8_t addr7, uint16_t reg, uint8_t* buf, uint8_t len, bool reg_high_first, bool repeated_start) { + Wire.beginTransmission(addr7); + if (reg_high_first) { + Wire.write((uint8_t)(reg >> 8)); + Wire.write((uint8_t)(reg & 0xFFu)); + } else { + Wire.write((uint8_t)(reg & 0xFFu)); + Wire.write((uint8_t)(reg >> 8)); + } + if (Wire.endTransmission(repeated_start ? false : true) != 0) { + return false; + } + size_t n = Wire.requestFrom((int)addr7, (int)len); + if (n != (size_t)len) { + return false; + } + for (uint8_t i = 0; i < len; i++) { + buf[i] = (uint8_t)Wire.read(); + } + return true; +} static volatile uint8_t s_touch_irq_mask = 0; +static void touch_disable_controller(uint8_t idx, TouchController* tc, TouchRuntime* rt, const char* reason) { + if (rt->disabled) { + return; + } + rt->disabled = 1; + rt->ok = 0; + if (rt->int_irq_attached && tc->int_pin != 0xFF) { + int irq_num = digitalPinToInterrupt(tc->int_pin); + if (irq_num >= 0) { + detachInterrupt(irq_num); + } + rt->int_irq_attached = 0; + s_touch_irq_mask &= (uint8_t)~(1u << idx); + } + writeSerial("Touch[" + String(idx) + "]: disabled (" + String(reason) + ")", true); +} + +void touchSuspendForEpdRefresh(void) { + if (s_epd_refresh_suspend < 255) { + s_epd_refresh_suspend++; + } +} + static void TOUCH_ISR_ATTR touch_isr_0(void) { s_touch_irq_mask |= 1u << 0; } @@ -90,43 +154,42 @@ static void attach_touch_int(uint8_t idx, uint8_t pin) { s_touch_rt[idx].int_irq_attached = 1; } -static const uint32_t TOUCH_I2C_WARN_INTERVAL_MS = 3000; - static bool gt911_write_reg(uint8_t addr7, uint16_t reg, const uint8_t* buf, uint8_t len, bool reg_high_first) { - Wire.beginTransmission(addr7); - if (reg_high_first) { - Wire.write((uint8_t)(reg >> 8)); - Wire.write((uint8_t)(reg & 0xFFu)); - } else { - Wire.write((uint8_t)(reg & 0xFFu)); - Wire.write((uint8_t)(reg >> 8)); - } - for (uint8_t i = 0; i < len; i++) { - Wire.write(buf[i]); + for (uint8_t attempt = 0; attempt < GT911_I2C_RETRIES; attempt++) { + Wire.beginTransmission(addr7); + if (reg_high_first) { + Wire.write((uint8_t)(reg >> 8)); + Wire.write((uint8_t)(reg & 0xFFu)); + } else { + Wire.write((uint8_t)(reg & 0xFFu)); + Wire.write((uint8_t)(reg >> 8)); + } + for (uint8_t i = 0; i < len; i++) { + Wire.write(buf[i]); + } + if (Wire.endTransmission() == 0) { + return true; + } + gt911_drain_wire(); + delayMicroseconds(GT911_I2C_RETRY_DELAY_US); } - return Wire.endTransmission() == 0; + return false; } static bool gt911_read_reg(uint8_t addr7, uint16_t reg, uint8_t* buf, uint8_t len, bool reg_high_first) { - Wire.beginTransmission(addr7); - if (reg_high_first) { - Wire.write((uint8_t)(reg >> 8)); - Wire.write((uint8_t)(reg & 0xFFu)); - } else { - Wire.write((uint8_t)(reg & 0xFFu)); - Wire.write((uint8_t)(reg >> 8)); - } - if (Wire.endTransmission(false) != 0) { - return false; - } - size_t n = Wire.requestFrom((int)addr7, (int)len); - if (n != (size_t)len) { - return false; - } - for (uint8_t i = 0; i < len; i++) { - buf[i] = (uint8_t)Wire.read(); + for (uint8_t attempt = 0; attempt < GT911_I2C_RETRIES; attempt++) { + if (gt911_read_reg_once(addr7, reg, buf, len, reg_high_first, true)) { + return true; + } + gt911_drain_wire(); + delayMicroseconds(GT911_I2C_RETRY_DELAY_US); + if (gt911_read_reg_once(addr7, reg, buf, len, reg_high_first, false)) { + return true; + } + gt911_drain_wire(); + delayMicroseconds(GT911_I2C_RETRY_DELAY_US); } - return true; + return false; } static bool gt911_product_id_match(const uint8_t* id) { @@ -251,6 +314,60 @@ static void gt911_clear_status(uint8_t addr7, bool reg_high_first) { gt911_write_reg(addr7, GT911_REG_STATUS, &z, 1, reg_high_first); } +static bool touch_reinit_gt911(uint8_t idx, TouchController* tc, TouchRuntime* rt) { + if (tc->touch_ic_type != TOUCH_IC_GT911 || !touch_bus_ok(tc)) { + return false; + } + if (tc->touch_data_start_byte > 6u) { + return false; + } + uint8_t addr = gt911_resolve_and_init(tc, rt); + if (addr == 0) { + rt->ok = 0; + return false; + } + rt->addr7 = addr; + rt->ok = 1; + rt->disabled = 0; + rt->i2c_fail_streak = 0; + rt->last_i2c_fail_ms = 0; + rt->last_i2c_warn_ms = 0; + rt->touch_latched = 0; + gt911_clear_status(addr, rt->reg_high_first != 0); + if (tc->int_pin != 0xFF) { + gt911_int_wake_before_irq(tc); + if (!rt->int_irq_attached) { + attach_touch_int(idx, tc->int_pin); + } + } + return true; +} + +void touchResumeAfterEpdRefresh(void) { + if (s_epd_refresh_suspend == 0) { + return; + } + s_epd_refresh_suspend--; + if (s_epd_refresh_suspend != 0) { + return; + } + invalidateOpenDisplayWire(); + initOrRestoreWireForOpenDisplay(); + delay(GT911_POST_RESET_SETTLE_MS); + for (uint8_t i = 0; i < globalConfig.touch_controller_count; i++) { + TouchController* tc = &globalConfig.touch_controllers[i]; + TouchRuntime* rt = &s_touch_rt[i]; + if (tc->touch_ic_type != TOUCH_IC_GT911 || rt->disabled) { + continue; + } + if (touch_reinit_gt911(i, tc, rt)) { + writeSerial("Touch[" + String(i) + "]: reinit OK after EPD @0x" + String(rt->addr7, HEX), true); + } else { + writeSerial("Touch[" + String(i) + "]: reinit failed after EPD refresh", true); + } + } +} + static void apply_touch_map(const TouchController* t, uint16_t* x, uint16_t* y) { if (t->flags & TOUCH_FLAG_SWAP_XY) { uint16_t tmp = *x; @@ -317,36 +434,29 @@ void initTouchInput(void) { writeSerial("Touch[" + String(i) + "]: touch_data_start_byte must be 0–6 (5-byte window)", true); continue; } - uint8_t addr = gt911_resolve_and_init(tc, rt); - if (addr == 0) { + if (!touch_reinit_gt911(i, tc, rt)) { writeSerial("Touch[" + String(i) + "]: init failed", true); continue; } - rt->addr7 = addr; - rt->ok = 1; - gt911_clear_status(addr, rt->reg_high_first != 0); { const bool rh = rt->reg_high_first != 0; uint16_t xres = 0; uint16_t yres = 0; uint8_t inf[12]; - if (gt911_read_reg(addr, GT911_REG_PID, inf, sizeof(inf), rh)) { + if (gt911_read_reg(rt->addr7, GT911_REG_PID, inf, sizeof(inf), rh)) { xres = (uint16_t)inf[6] | ((uint16_t)inf[7] << 8); yres = (uint16_t)inf[8] | ((uint16_t)inf[9] << 8); } - writeSerial("Touch[" + String(i) + "]: GT911 @0x" + String(addr, HEX) + " " + String(rh ? "BE" : "LE") + + writeSerial("Touch[" + String(i) + "]: GT911 @0x" + String(rt->addr7, HEX) + " " + String(rh ? "BE" : "LE") + " " + String(xres) + "x" + String(yres) + (tc->int_pin != 0xFF ? " INT+poll" : " poll"), true); } - if (tc->int_pin != 0xFF) { - gt911_int_wake_before_irq(tc); #if TOUCH_DEBUG + if (tc->int_pin != 0xFF && rt->int_irq_attached) { writeSerial("Touch[" + String(i) + "]: INT GPIO " + String(tc->int_pin) + " FALLING", true); -#endif - attach_touch_int(i, tc->int_pin); - if (!rt->int_irq_attached) { - writeSerial("Touch[" + String(i) + "]: INT attach failed — polling only", true); - } + } else if (tc->int_pin != 0xFF) { + writeSerial("Touch[" + String(i) + "]: INT attach failed — polling only", true); } +#endif rt->last_poll_ms = millis(); } } @@ -365,15 +475,27 @@ bool touch_input_gpio_is_touch_int(uint8_t pin) { } void processTouchInput(void) { + if (globalConfig.touch_controller_count == 0) { + return; + } +#ifdef TARGET_ESP32 + if (directWriteActive || s_epd_refresh_suspend > 0) { + return; + } +#endif uint32_t now = millis(); + if ((uint32_t)(now - s_last_touch_process_ms) < TOUCH_PROCESS_MIN_INTERVAL_MS) { + return; + } + s_last_touch_process_ms = now; for (uint8_t i = 0; i < globalConfig.touch_controller_count; i++) { TouchController* tc = &globalConfig.touch_controllers[i]; TouchRuntime* rt = &s_touch_rt[i]; - if (tc->touch_ic_type != TOUCH_IC_GT911 || !rt->ok) { + if (tc->touch_ic_type != TOUCH_IC_GT911 || !rt->ok || rt->disabled) { continue; } - uint8_t interval = tc->poll_interval_ms ? tc->poll_interval_ms : 25; + uint8_t interval = tc->poll_interval_ms ? tc->poll_interval_ms : TOUCH_PROCESS_MIN_INTERVAL_MS; const bool irq_mode = (rt->int_irq_attached != 0); bool from_irq = false; bool line_low = false; @@ -382,6 +504,10 @@ void processTouchInput(void) { if (irq_mode) { bool edge = (s_touch_irq_mask & (1u << i)) != 0; line_low = (digitalRead(tc->int_pin) == LOW); + if (line_low && rt->i2c_fail_streak > 0 && + (uint32_t)(now - rt->last_i2c_fail_ms) < TOUCH_I2C_FAIL_BACKOFF_MS) { + line_low = false; + } timed_poll = (uint32_t)(now - rt->last_poll_ms) >= interval; want_read = edge || timed_poll || line_low; if (!want_read) { @@ -402,12 +528,19 @@ void processTouchInput(void) { const bool rh = rt->reg_high_first != 0; uint8_t st = 0; if (!gt911_read_reg(rt->addr7, GT911_REG_STATUS, &st, 1, rh)) { - if ((uint32_t)(now - rt->last_i2c_warn_ms) >= TOUCH_I2C_WARN_INTERVAL_MS) { - rt->last_i2c_warn_ms = now; + if (rt->i2c_fail_streak < 255) { + rt->i2c_fail_streak++; + } + rt->last_i2c_fail_ms = now; + if (rt->i2c_fail_streak >= TOUCH_I2C_FAIL_DISABLE_THRESHOLD) { + touch_disable_controller(i, tc, rt, "too many I2C read failures"); + } else if (rt->i2c_fail_streak == 1) { writeSerial("Touch[" + String(i) + "]: I2C read fail (status reg 0x814E, addr 0x" + String(rt->addr7, HEX) + ")", true); } + rt->last_poll_ms = now; continue; } + rt->i2c_fail_streak = 0; if ((st & GT911_STATUS_BUFFER_READY) == 0) { if (!from_irq && !line_low) { @@ -430,9 +563,12 @@ void processTouchInput(void) { if (n > 0) { uint8_t p[8]; if (!gt911_read_reg(rt->addr7, GT911_REG_POINT1, p, 8, rh)) { - if ((uint32_t)(now - rt->last_i2c_warn_ms) >= TOUCH_I2C_WARN_INTERVAL_MS) { - rt->last_i2c_warn_ms = now; - writeSerial("Touch[" + String(i) + "]: I2C read fail (point1 @0x814F, addr 0x" + String(rt->addr7, HEX) + ")", true); + if (rt->i2c_fail_streak < 255) { + rt->i2c_fail_streak++; + } + rt->last_i2c_fail_ms = now; + if (rt->i2c_fail_streak >= TOUCH_I2C_FAIL_DISABLE_THRESHOLD) { + touch_disable_controller(i, tc, rt, "too many I2C read failures"); } continue; } diff --git a/src/touch_input.h b/src/touch_input.h index d4f5729..89388d8 100644 --- a/src/touch_input.h +++ b/src/touch_input.h @@ -6,5 +6,9 @@ void initTouchInput(void); void processTouchInput(void); bool touch_input_gpio_is_touch_int(uint8_t pin); +/** Suspend GT911 polling during EPD refresh (nested calls OK). */ +void touchSuspendForEpdRefresh(void); +/** Resume touch after EPD refresh; re-inits I2C for active controllers. */ +void touchResumeAfterEpdRefresh(void); #endif