#include "include/touch.h" #include "include/touch_calibrate.h" #include #include #include #include "driver/gpio.h" #include "driver/i2c.h" #include "esp_err.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "lvgl.h" static const char *TAG = "GT911_TOUCH"; /* * Ligacoes ESP32-8048S043C, touch capacitivo: * * CTP_SDA -> GPIO19 * CTP_SCL -> GPIO20 * CTP_RST -> GPIO38 * * O CTP_INT nao vem ligado ao ESP32 nesta placa sem modificacao de hardware. * Por isso o driver usa polling e nunca trata o GPIO38 como interrupcao. */ #define TOUCH_I2C_PORT I2C_NUM_0 #define TOUCH_I2C_SDA_GPIO GPIO_NUM_19 #define TOUCH_I2C_SCL_GPIO GPIO_NUM_20 #define TOUCH_RESET_GPIO GPIO_NUM_38 #define TOUCH_I2C_CLOCK_HZ 400000 #define TOUCH_I2C_TIMEOUT_MS 50 #define GT911_ADDR_5D 0x5D #define GT911_ADDR_14 0x14 #define GT911_REG_PRODUCT_ID 0x8140 #define GT911_REG_STATUS 0x814E #define GT911_STATUS_READY 0x80 #define GT911_STATUS_POINTS 0x0F #define GT911_MAX_POINTS 5 /* Orientacao usada caso a resolucao informada pelo GT911 nao seja conclusiva. */ #define TOUCH_DEFAULT_SWAP_XY 0 #define TOUCH_DEFAULT_INVERT_X 0 #define TOUCH_DEFAULT_INVERT_Y 0 typedef enum { GT911_EVENT_NONE = 0, GT911_EVENT_POINT, GT911_EVENT_RELEASE, } gt911_event_t; static uint8_t s_gt911_addr = 0; static uint16_t s_gt911_x_res = TOUCH_H_RES; static uint16_t s_gt911_y_res = TOUCH_V_RES; static lv_indev_t *s_touch_indev = NULL; static int16_t s_last_x = 0; static int16_t s_last_y = 0; static bool s_pressed = false; /* Limites das coordenadas depois da eventual troca X/Y. */ static uint16_t s_cal_x_min = 0; static uint16_t s_cal_x_max = TOUCH_H_RES - 1; static uint16_t s_cal_y_min = 0; static uint16_t s_cal_y_max = TOUCH_V_RES - 1; static bool s_swap_xy = TOUCH_DEFAULT_SWAP_XY; static bool s_invert_x = TOUCH_DEFAULT_INVERT_X; static bool s_invert_y = TOUCH_DEFAULT_INVERT_Y; static esp_err_t gt911_read(uint16_t reg, uint8_t *data, size_t len) { uint8_t reg_buf[2] = { (uint8_t)(reg >> 8), (uint8_t)(reg & 0xFF), }; return i2c_master_write_read_device( TOUCH_I2C_PORT, s_gt911_addr, reg_buf, sizeof(reg_buf), data, len, pdMS_TO_TICKS(TOUCH_I2C_TIMEOUT_MS) ); } static esp_err_t gt911_write_u8(uint16_t reg, uint8_t value) { uint8_t data[3] = { (uint8_t)(reg >> 8), (uint8_t)(reg & 0xFF), value, }; return i2c_master_write_to_device( TOUCH_I2C_PORT, s_gt911_addr, data, sizeof(data), pdMS_TO_TICKS(TOUCH_I2C_TIMEOUT_MS) ); } static esp_err_t gt911_prepare_reset_pin(void) { gpio_config_t reset_conf = { .pin_bit_mask = 1ULL << TOUCH_RESET_GPIO, .mode = GPIO_MODE_OUTPUT, .pull_up_en = GPIO_PULLUP_DISABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE, .intr_type = GPIO_INTR_DISABLE, }; esp_err_t err = gpio_config(&reset_conf); if (err != ESP_OK) { return err; } return gpio_set_level(TOUCH_RESET_GPIO, 1); } static esp_err_t gt911_hardware_reset(void) { ESP_LOGW(TAG, "GT911 did not answer; applying reset on GPIO%d.", TOUCH_RESET_GPIO); esp_err_t err = gpio_set_level(TOUCH_RESET_GPIO, 0); if (err != ESP_OK) { return err; } vTaskDelay(pdMS_TO_TICKS(12)); err = gpio_set_level(TOUCH_RESET_GPIO, 1); if (err != ESP_OK) { return err; } vTaskDelay(pdMS_TO_TICKS(60)); return ESP_OK; } static bool gt911_probe_addr(uint8_t addr) { uint8_t reg_buf[2] = { (uint8_t)(GT911_REG_PRODUCT_ID >> 8), (uint8_t)(GT911_REG_PRODUCT_ID & 0xFF), }; uint8_t product_id[4] = {0}; esp_err_t err = i2c_master_write_read_device( TOUCH_I2C_PORT, addr, reg_buf, sizeof(reg_buf), product_id, sizeof(product_id), pdMS_TO_TICKS(TOUCH_I2C_TIMEOUT_MS) ); if (err != ESP_OK) { return false; } s_gt911_addr = addr; ESP_LOGI( TAG, "GT911 detected at 0x%02X, product id: %c%c%c%c", addr, product_id[0], product_id[1], product_id[2], product_id[3] ); return true; } static bool gt911_probe(void) { return gt911_probe_addr(GT911_ADDR_5D) || gt911_probe_addr(GT911_ADDR_14); } static void gt911_read_resolution(void) { /* * 0x8140..0x8149: product id, firmware e resolucoes X/Y atualmente * usadas pelo controlador. Esta leitura nao altera a configuracao de fabrica. */ uint8_t info[10] = {0}; esp_err_t err = gt911_read(GT911_REG_PRODUCT_ID, info, sizeof(info)); if (err != ESP_OK) { ESP_LOGW(TAG, "Could not read GT911 resolution: %s", esp_err_to_name(err)); return; } uint16_t x_res = (uint16_t)info[6] | ((uint16_t)info[7] << 8); uint16_t y_res = (uint16_t)info[8] | ((uint16_t)info[9] << 8); if (x_res > 1 && x_res <= 16384) { s_gt911_x_res = x_res; } if (y_res > 1 && y_res <= 16384) { s_gt911_y_res = y_res; } ESP_LOGI(TAG, "GT911 coordinate resolution: %ux%u", s_gt911_x_res, s_gt911_y_res); } void touch_set_transform(bool swap_xy, bool invert_x, bool invert_y) { s_swap_xy = swap_xy; s_invert_x = invert_x; s_invert_y = invert_y; ESP_LOGI( TAG, "Coordinate transform: swap_xy=%d invert_x=%d invert_y=%d", s_swap_xy, s_invert_x, s_invert_y ); } void touch_set_calibration(uint16_t x_min, uint16_t x_max, uint16_t y_min, uint16_t y_max) { if (x_min >= x_max || y_min >= y_max) { ESP_LOGW(TAG, "Ignoring invalid touch calibration limits."); return; } s_cal_x_min = x_min; s_cal_x_max = x_max; s_cal_y_min = y_min; s_cal_y_max = y_max; ESP_LOGI( TAG, "Calibration applied: x=%u..%u y=%u..%u", s_cal_x_min, s_cal_x_max, s_cal_y_min, s_cal_y_max ); } static void touch_configure_default_mapping(void) { bool swap_xy = TOUCH_DEFAULT_SWAP_XY; /* Detecta automaticamente controladores configurados em 480x800. */ if (s_gt911_x_res == TOUCH_V_RES && s_gt911_y_res == TOUCH_H_RES) { swap_xy = true; } else if (s_gt911_x_res == TOUCH_H_RES && s_gt911_y_res == TOUCH_V_RES) { swap_xy = false; } touch_set_transform( swap_xy, TOUCH_DEFAULT_INVERT_X, TOUCH_DEFAULT_INVERT_Y ); uint16_t x_max = swap_xy ? (s_gt911_y_res - 1) : (s_gt911_x_res - 1); uint16_t y_max = swap_xy ? (s_gt911_x_res - 1) : (s_gt911_y_res - 1); touch_set_calibration(0, x_max, 0, y_max); } static gt911_event_t gt911_read_event(uint16_t *raw_x, uint16_t *raw_y) { /* Status (1 byte) seguido pelo primeiro ponto (8 bytes). */ uint8_t data[9] = {0}; esp_err_t err = gt911_read(GT911_REG_STATUS, data, sizeof(data)); if (err != ESP_OK) { ESP_LOGW(TAG, "GT911 read error: %s", esp_err_to_name(err)); return GT911_EVENT_NONE; } uint8_t status = data[0]; if ((status & GT911_STATUS_READY) == 0) { return GT911_EVENT_NONE; } uint8_t points = status & GT911_STATUS_POINTS; if (points == 0 || points > GT911_MAX_POINTS) { (void)gt911_write_u8(GT911_REG_STATUS, 0); s_pressed = false; return GT911_EVENT_RELEASE; } uint16_t x = (uint16_t)data[2] | ((uint16_t)data[3] << 8); uint16_t y = (uint16_t)data[4] | ((uint16_t)data[5] << 8); if (raw_x != NULL) { *raw_x = x; } if (raw_y != NULL) { *raw_y = y; } (void)gt911_write_u8(GT911_REG_STATUS, 0); s_pressed = true; return GT911_EVENT_POINT; } static int32_t map_range_clamped( int32_t value, int32_t in_min, int32_t in_max, int32_t out_max ) { if (in_max <= in_min) { return 0; } if (value < in_min) value = in_min; if (value > in_max) value = in_max; return ((value - in_min) * out_max) / (in_max - in_min); } static bool touch_transform_point(uint16_t raw_x, uint16_t raw_y, int16_t *screen_x, int16_t *screen_y) { if (screen_x == NULL || screen_y == NULL) { return false; } int32_t axis_x = s_swap_xy ? raw_y : raw_x; int32_t axis_y = s_swap_xy ? raw_x : raw_y; int32_t x = map_range_clamped(axis_x, s_cal_x_min, s_cal_x_max, TOUCH_H_RES - 1); int32_t y = map_range_clamped(axis_y, s_cal_y_min, s_cal_y_max, TOUCH_V_RES - 1); if (s_invert_x) { x = (TOUCH_H_RES - 1) - x; } if (s_invert_y) { y = (TOUCH_V_RES - 1) - y; } *screen_x = (int16_t)x; *screen_y = (int16_t)y; return true; } bool touch_is_pressed(void) { if (s_gt911_addr == 0) { return false; } (void)gt911_read_event(NULL, NULL); return s_pressed; } bool touch_read_raw(uint16_t *raw_x, uint16_t *raw_y) { if (s_gt911_addr == 0) { return false; } uint16_t x = 0; uint16_t y = 0; if (gt911_read_event(&x, &y) != GT911_EVENT_POINT) { return false; } if (raw_x != NULL) { *raw_x = x; } if (raw_y != NULL) { *raw_y = y; } return true; } static void lvgl_touch_read_cb(lv_indev_drv_t *drv, lv_indev_data_t *data) { (void)drv; uint16_t raw_x = 0; uint16_t raw_y = 0; gt911_event_t event = gt911_read_event(&raw_x, &raw_y); if (event == GT911_EVENT_POINT) { int16_t x = 0; int16_t y = 0; if (touch_transform_point(raw_x, raw_y, &x, &y)) { s_last_x = x; s_last_y = y; } } data->state = s_pressed ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL; data->point.x = s_last_x; data->point.y = s_last_y; } esp_err_t touch_init(void) { if (s_touch_indev != NULL) { ESP_LOGW(TAG, "Touch already initialized."); return ESP_OK; } ESP_LOGI(TAG, "Initializing GT911 capacitive touch..."); esp_err_t err = gt911_prepare_reset_pin(); if (err != ESP_OK) { ESP_LOGE(TAG, "Could not configure GT911 reset pin: %s", esp_err_to_name(err)); return err; } i2c_config_t i2c_conf = { .mode = I2C_MODE_MASTER, .sda_io_num = TOUCH_I2C_SDA_GPIO, .scl_io_num = TOUCH_I2C_SCL_GPIO, .sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_pullup_en = GPIO_PULLUP_ENABLE, .master.clk_speed = TOUCH_I2C_CLOCK_HZ, .clk_flags = 0, }; err = i2c_param_config(TOUCH_I2C_PORT, &i2c_conf); if (err != ESP_OK) { ESP_LOGE(TAG, "i2c_param_config failed: %s", esp_err_to_name(err)); return err; } err = i2c_driver_install(TOUCH_I2C_PORT, I2C_MODE_MASTER, 0, 0, 0); if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { ESP_LOGE(TAG, "i2c_driver_install failed: %s", esp_err_to_name(err)); return err; } /* Mantem o caminho que ja funciona; so reseta se o primeiro probe falhar. */ if (!gt911_probe()) { err = gt911_hardware_reset(); if (err != ESP_OK) { ESP_LOGE(TAG, "GT911 reset failed: %s", esp_err_to_name(err)); return err; } if (!gt911_probe()) { ESP_LOGE(TAG, "GT911 not found at 0x%02X or 0x%02X.", GT911_ADDR_5D, GT911_ADDR_14); return ESP_ERR_NOT_FOUND; } } gt911_read_resolution(); touch_configure_default_mapping(); /* * Executado antes de registrar o input no LVGL para que a rotina de * calibracao seja a unica consumidora dos eventos do GT911. */ err = f_touch_calibrate_check_or_run(); if (err != ESP_OK) { ESP_LOGW(TAG, "Touch calibration was not applied: %s", esp_err_to_name(err)); } static lv_indev_drv_t indev_drv; lv_indev_drv_init(&indev_drv); indev_drv.type = LV_INDEV_TYPE_POINTER; indev_drv.read_cb = lvgl_touch_read_cb; s_touch_indev = lv_indev_drv_register(&indev_drv); if (s_touch_indev == NULL) { ESP_LOGE(TAG, "Failed to register input device in LVGL."); return ESP_FAIL; } ESP_LOGI(TAG, "GT911 touch initialized successfully."); return ESP_OK; }