/* * GT911 capacitive touch controller, CrowPanel 7" Advance. * * The GT911 uses 16-bit register addressing, so every read writes the two * address bytes (MSB first) then does a repeated-start read. * * On the Advance there is no ESP32 GPIO for touch reset or interrupt -- the * STC8H1K28 board controller owns both. backlight_init() must therefore run * first, or the GT911 is still in reset and NACKs every address. */ #include #include "esp_check.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "lvgl.h" #include "display_rgb.h" #include "i2c_bus.h" #include "touch_gt911.h" static const char *TAG = "touch_gt911"; #define TOUCH_I2C_HZ 400000 #define TOUCH_I2C_TIMEOUT_MS 100 #define GT911_ADDR_LOW 0x5D #define GT911_ADDR_HIGH 0x14 #define GT911_REG_PRODUCT_ID 0x8140 #define GT911_REG_STATUS 0x814E #define GT911_REG_TRACK_ID 0x814F /* Coordinate block: x_lo, x_hi, y_lo, y_hi, size_lo, size_hi */ #define GT911_REG_POINT1_XY 0x8150 #define GT911_STATUS_READY 0x80 #define GT911_POINT_COUNT_MASK 0x0F #define GT911_MAX_POINTS 5 static i2c_master_dev_handle_t dev = NULL; static lv_indev_t *indev = NULL; static esp_err_t gt911_read(uint16_t reg, uint8_t *data, size_t len) { const uint8_t addr[2] = { (uint8_t)(reg >> 8), (uint8_t)(reg & 0xFF) }; return i2c_master_transmit_receive(dev, addr, sizeof(addr), data, len, TOUCH_I2C_TIMEOUT_MS); } static esp_err_t gt911_write_u8(uint16_t reg, uint8_t value) { const uint8_t buf[3] = { (uint8_t)(reg >> 8), (uint8_t)(reg & 0xFF), value }; return i2c_master_transmit(dev, buf, sizeof(buf), TOUCH_I2C_TIMEOUT_MS); } /* The status register latches until cleared; leaving it set stops the * controller reporting new coordinates. */ static void gt911_clear_status(void) { const esp_err_t err = gt911_write_u8(GT911_REG_STATUS, 0x00); if (err != ESP_OK) { ESP_LOGW(TAG, "Failed to clear status register: %s", esp_err_to_name(err)); } } static void touch_read_cb(lv_indev_t *drv, lv_indev_data_t *data) { /* LVGL expects the last coordinate to persist through release, otherwise * the release event lands at (0,0) and cancels the gesture. */ static int32_t last_x = 0; static int32_t last_y = 0; static bool was_pressed = false; data->point.x = last_x; data->point.y = last_y; data->state = LV_INDEV_STATE_RELEASED; uint8_t status = 0; if (gt911_read(GT911_REG_STATUS, &status, 1) != ESP_OK || !(status & GT911_STATUS_READY)) { was_pressed = false; return; } const uint8_t points = status & GT911_POINT_COUNT_MASK; if (points == 0 || points > GT911_MAX_POINTS) { was_pressed = false; gt911_clear_status(); return; } /* Only the first contact is used -- the UI has no multi-touch gestures. */ uint8_t buf[4]; if (gt911_read(GT911_REG_POINT1_XY, buf, sizeof(buf)) == ESP_OK) { const uint16_t x = (uint16_t)((buf[1] << 8) | buf[0]); const uint16_t y = (uint16_t)((buf[3] << 8) | buf[2]); if (x < DISPLAY_H_RES && y < DISPLAY_V_RES) { /* Log only the initial contact: the panel reports ~30 samples a * second for as long as a finger stays down. */ if (!was_pressed) { ESP_LOGD(TAG, "touch at (%u, %u)", x, y); } was_pressed = true; last_x = x; last_y = y; data->point.x = x; data->point.y = y; data->state = LV_INDEV_STATE_PRESSED; } else { ESP_LOGW(TAG, "touch out of range: raw (%u, %u)", x, y); } } gt911_clear_status(); } static esp_err_t gt911_try_address(uint8_t addr) { const i2c_device_config_t dev_cfg = { .dev_addr_length = I2C_ADDR_BIT_LEN_7, .device_address = addr, .scl_speed_hz = TOUCH_I2C_HZ, }; ESP_RETURN_ON_ERROR(i2c_master_bus_add_device(i2c_bus_get(), &dev_cfg, &dev), TAG, "failed to add device 0x%02X", addr); uint8_t product_id[4] = {0}; const esp_err_t err = gt911_read(GT911_REG_PRODUCT_ID, product_id, sizeof(product_id)); if (err != ESP_OK) { i2c_master_bus_rm_device(dev); dev = NULL; return err; } ESP_LOGI(TAG, "GT911 found at 0x%02X (product ID \"%.4s\")", addr, (char *)product_id); return ESP_OK; } esp_err_t touch_init(void) { ESP_RETURN_ON_FALSE(i2c_bus_get() != NULL, ESP_ERR_INVALID_STATE, TAG, "i2c_bus_init() must run first"); if (gt911_try_address(GT911_ADDR_LOW) != ESP_OK && gt911_try_address(GT911_ADDR_HIGH) != ESP_OK) { ESP_LOGE(TAG, "No GT911 at 0x%02X or 0x%02X", GT911_ADDR_LOW, GT911_ADDR_HIGH); i2c_bus_scan(TAG); return ESP_ERR_NOT_FOUND; } gt911_clear_status(); indev = lv_indev_create(); ESP_RETURN_ON_FALSE(indev != NULL, ESP_FAIL, TAG, "lv_indev_create failed"); lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); lv_indev_set_read_cb(indev, touch_read_cb); lv_indev_set_display(indev, display_get_default()); ESP_LOGI(TAG, "Touch registered as LVGL pointer input"); return ESP_OK; } lv_indev_t *touch_get_indev(void) { return indev; }