#include "include/touch_calibrate.h" #include #include #include #include #include "esp_err.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "lvgl.h" #include "nvs.h" #include "nvs_flash.h" #include "include/f_init-lvgl.h" #include "include/touch.h" static const char *TAG = "TOUCH_CAL"; #define TOUCH_CAL_NVS_NAMESPACE "touch8048" #define TOUCH_CAL_NVS_KEY "cal_v2" #define TOUCH_CAL_MAGIC 0x54433832UL /* "TC82" */ #define TOUCH_CAL_TARGET_SIZE 34 #define TOUCH_CAL_MARGIN 40 #define TOUCH_CAL_RELEASE_MS 5000 #define TOUCH_CAL_FLAG_SWAP_XY (1U << 0) #define TOUCH_CAL_FLAG_INVERT_X (1U << 1) #define TOUCH_CAL_FLAG_INVERT_Y (1U << 2) #define TOUCH_CAL_FLAGS_VALID (TOUCH_CAL_FLAG_SWAP_XY | TOUCH_CAL_FLAG_INVERT_X | TOUCH_CAL_FLAG_INVERT_Y) typedef struct { uint32_t magic; uint16_t x_min; uint16_t x_max; uint16_t y_min; uint16_t y_max; uint8_t flags; uint8_t reserved[3]; } touch_cal_nvs_t; typedef struct { int16_t x; int16_t y; const char *name; } touch_cal_target_t; enum { TOUCH_CAL_TOP_LEFT = 0, TOUCH_CAL_TOP_RIGHT, TOUCH_CAL_BOTTOM_RIGHT, TOUCH_CAL_BOTTOM_LEFT, TOUCH_CAL_TARGET_COUNT, }; static const touch_cal_target_t s_targets[TOUCH_CAL_TARGET_COUNT] = { [TOUCH_CAL_TOP_LEFT] = { .x = TOUCH_CAL_MARGIN, .y = TOUCH_CAL_MARGIN, .name = "superior esquerdo", }, [TOUCH_CAL_TOP_RIGHT] = { .x = TOUCH_H_RES - TOUCH_CAL_MARGIN - 1, .y = TOUCH_CAL_MARGIN, .name = "superior direito", }, [TOUCH_CAL_BOTTOM_RIGHT] = { .x = TOUCH_H_RES - TOUCH_CAL_MARGIN - 1, .y = TOUCH_V_RES - TOUCH_CAL_MARGIN - 1, .name = "inferior direito", }, [TOUCH_CAL_BOTTOM_LEFT] = { .x = TOUCH_CAL_MARGIN, .y = TOUCH_V_RES - TOUCH_CAL_MARGIN - 1, .name = "inferior esquerdo", }, }; static esp_err_t touch_cal_nvs_open(nvs_open_mode_t mode, nvs_handle_t *handle) { esp_err_t err = nvs_open(TOUCH_CAL_NVS_NAMESPACE, mode, handle); if (err == ESP_ERR_NVS_NOT_INITIALIZED) { err = nvs_flash_init(); if (err != ESP_OK) { return err; } err = nvs_open(TOUCH_CAL_NVS_NAMESPACE, mode, handle); } return err; } static bool touch_cal_data_valid(const touch_cal_nvs_t *cal) { if (cal == NULL || cal->magic != TOUCH_CAL_MAGIC) { return false; } if (cal->x_min >= cal->x_max || cal->y_min >= cal->y_max) { return false; } if (cal->x_max > 16384 || cal->y_max > 16384) { return false; } return (cal->flags & ~TOUCH_CAL_FLAGS_VALID) == 0; } static esp_err_t touch_cal_load(touch_cal_nvs_t *cal) { if (cal == NULL) { return ESP_ERR_INVALID_ARG; } nvs_handle_t nvs; esp_err_t err = touch_cal_nvs_open(NVS_READONLY, &nvs); if (err != ESP_OK) { return err; } size_t size = sizeof(*cal); err = nvs_get_blob(nvs, TOUCH_CAL_NVS_KEY, cal, &size); nvs_close(nvs); if (err != ESP_OK) { return err; } if (size != sizeof(*cal)) { return ESP_ERR_INVALID_SIZE; } return touch_cal_data_valid(cal) ? ESP_OK : ESP_ERR_INVALID_ARG; } static esp_err_t touch_cal_save(const touch_cal_nvs_t *cal) { if (!touch_cal_data_valid(cal)) { return ESP_ERR_INVALID_ARG; } nvs_handle_t nvs; esp_err_t err = touch_cal_nvs_open(NVS_READWRITE, &nvs); if (err != ESP_OK) { return err; } err = nvs_set_blob(nvs, TOUCH_CAL_NVS_KEY, cal, sizeof(*cal)); if (err == ESP_OK) { err = nvs_commit(nvs); } nvs_close(nvs); return err; } static void touch_cal_apply(const touch_cal_nvs_t *cal) { touch_set_transform( (cal->flags & TOUCH_CAL_FLAG_SWAP_XY) != 0, (cal->flags & TOUCH_CAL_FLAG_INVERT_X) != 0, (cal->flags & TOUCH_CAL_FLAG_INVERT_Y) != 0 ); touch_set_calibration(cal->x_min, cal->x_max, cal->y_min, cal->y_max); } bool f_touch_calibrate_is_done(void) { touch_cal_nvs_t cal = {0}; return touch_cal_load(&cal) == ESP_OK; } esp_err_t f_touch_calibrate_erase(void) { nvs_handle_t nvs; esp_err_t err = touch_cal_nvs_open(NVS_READWRITE, &nvs); if (err != ESP_OK) { return err; } err = nvs_erase_key(nvs, TOUCH_CAL_NVS_KEY); if (err == ESP_ERR_NVS_NOT_FOUND) { err = ESP_OK; } if (err == ESP_OK) { err = nvs_commit(nvs); } nvs_close(nvs); ESP_LOGI(TAG, "Touch calibration erased from NVS."); return err; } static void touch_cal_draw_screen(int target_index) { lock(); lv_obj_t *screen = lv_scr_act(); lv_obj_clean(screen); lv_obj_set_style_bg_color(screen, lv_color_hex(0x000000), 0); lv_obj_t *title = lv_label_create(screen); lv_label_set_text(title, "Calibracao do touch"); lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0); lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 12); char message[96]; snprintf( message, sizeof(message), "Toque uma vez no ponto %s (%d/%d)", s_targets[target_index].name, target_index + 1, TOUCH_CAL_TARGET_COUNT ); lv_obj_t *instruction = lv_label_create(screen); lv_label_set_text(instruction, message); lv_obj_set_style_text_color(instruction, lv_color_hex(0xFFFFFF), 0); lv_obj_align(instruction, LV_ALIGN_BOTTOM_MID, 0, -12); for (int i = 0; i < TOUCH_CAL_TARGET_COUNT; i++) { lv_obj_t *target = lv_obj_create(screen); lv_obj_set_size(target, TOUCH_CAL_TARGET_SIZE, TOUCH_CAL_TARGET_SIZE); lv_obj_set_pos( target, s_targets[i].x - (TOUCH_CAL_TARGET_SIZE / 2), s_targets[i].y - (TOUCH_CAL_TARGET_SIZE / 2) ); lv_obj_clear_flag(target, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_style_radius(target, LV_RADIUS_CIRCLE, 0); lv_obj_set_style_border_width(target, 2, 0); bool active = i == target_index; lv_obj_set_style_bg_color( target, lv_color_hex(active ? 0x00CC44 : 0x282828), 0 ); lv_obj_set_style_border_color( target, lv_color_hex(active ? 0xFFFFFF : 0x606060), 0 ); } lv_obj_invalidate(screen); unlock(); /* Permite que a task do LVGL apresente o novo alvo antes da leitura. */ vTaskDelay(pdMS_TO_TICKS(180)); } static void touch_cal_draw_done_screen(void) { lock(); lv_obj_t *screen = lv_scr_act(); lv_obj_clean(screen); lv_obj_set_style_bg_color(screen, lv_color_hex(0x000000), 0); lv_obj_t *label = lv_label_create(screen); lv_label_set_text(label, "Touch calibrado com sucesso"); lv_obj_set_style_text_color(label, lv_color_hex(0x00DD55), 0); lv_obj_align(label, LV_ALIGN_CENTER, 0, 0); lv_obj_invalidate(screen); unlock(); } static void touch_cal_wait_release(void) { TickType_t start = xTaskGetTickCount(); while (touch_is_pressed()) { if ((xTaskGetTickCount() - start) >= pdMS_TO_TICKS(TOUCH_CAL_RELEASE_MS)) { ESP_LOGW(TAG, "Release wait timeout; continuing calibration."); break; } vTaskDelay(pdMS_TO_TICKS(20)); } vTaskDelay(pdMS_TO_TICKS(180)); } static void touch_cal_wait_raw_press(uint16_t *raw_x, uint16_t *raw_y) { while (!touch_read_raw(raw_x, raw_y)) { vTaskDelay(pdMS_TO_TICKS(15)); } } static int32_t touch_cal_axis_value( const uint16_t raw_x[TOUCH_CAL_TARGET_COUNT], const uint16_t raw_y[TOUCH_CAL_TARGET_COUNT], bool swap_xy, int index, bool horizontal_axis ) { if (horizontal_axis) { return swap_xy ? raw_y[index] : raw_x[index]; } return swap_xy ? raw_x[index] : raw_y[index]; } static uint16_t touch_cal_clamp_coordinate(int32_t value) { if (value < 0) { return 0; } if (value > 16384) { return 16384; } return (uint16_t)value; } static esp_err_t touch_cal_calculate( const uint16_t raw_x[TOUCH_CAL_TARGET_COUNT], const uint16_t raw_y[TOUCH_CAL_TARGET_COUNT], touch_cal_nvs_t *cal ) { int32_t horizontal_x = abs((int)raw_x[TOUCH_CAL_TOP_RIGHT] - (int)raw_x[TOUCH_CAL_TOP_LEFT]) + abs((int)raw_x[TOUCH_CAL_BOTTOM_RIGHT] - (int)raw_x[TOUCH_CAL_BOTTOM_LEFT]); int32_t horizontal_y = abs((int)raw_y[TOUCH_CAL_TOP_RIGHT] - (int)raw_y[TOUCH_CAL_TOP_LEFT]) + abs((int)raw_y[TOUCH_CAL_BOTTOM_RIGHT] - (int)raw_y[TOUCH_CAL_BOTTOM_LEFT]); int32_t vertical_x = abs((int)raw_x[TOUCH_CAL_BOTTOM_LEFT] - (int)raw_x[TOUCH_CAL_TOP_LEFT]) + abs((int)raw_x[TOUCH_CAL_BOTTOM_RIGHT] - (int)raw_x[TOUCH_CAL_TOP_RIGHT]); int32_t vertical_y = abs((int)raw_y[TOUCH_CAL_BOTTOM_LEFT] - (int)raw_y[TOUCH_CAL_TOP_LEFT]) + abs((int)raw_y[TOUCH_CAL_BOTTOM_RIGHT] - (int)raw_y[TOUCH_CAL_TOP_RIGHT]); bool swap_xy = (horizontal_y + vertical_x) > (horizontal_x + vertical_y); int32_t left = ( touch_cal_axis_value(raw_x, raw_y, swap_xy, TOUCH_CAL_TOP_LEFT, true) + touch_cal_axis_value(raw_x, raw_y, swap_xy, TOUCH_CAL_BOTTOM_LEFT, true) ) / 2; int32_t right = ( touch_cal_axis_value(raw_x, raw_y, swap_xy, TOUCH_CAL_TOP_RIGHT, true) + touch_cal_axis_value(raw_x, raw_y, swap_xy, TOUCH_CAL_BOTTOM_RIGHT, true) ) / 2; int32_t top = ( touch_cal_axis_value(raw_x, raw_y, swap_xy, TOUCH_CAL_TOP_LEFT, false) + touch_cal_axis_value(raw_x, raw_y, swap_xy, TOUCH_CAL_TOP_RIGHT, false) ) / 2; int32_t bottom = ( touch_cal_axis_value(raw_x, raw_y, swap_xy, TOUCH_CAL_BOTTOM_LEFT, false) + touch_cal_axis_value(raw_x, raw_y, swap_xy, TOUCH_CAL_BOTTOM_RIGHT, false) ) / 2; int32_t x_delta = right - left; int32_t y_delta = bottom - top; if (abs((int)x_delta) < (TOUCH_H_RES / 4) || abs((int)y_delta) < (TOUCH_V_RES / 4)) { ESP_LOGE(TAG, "Calibration points are too close or out of order."); return ESP_ERR_INVALID_RESPONSE; } const int32_t x_screen_span = (TOUCH_H_RES - 1) - (2 * TOUCH_CAL_MARGIN); const int32_t y_screen_span = (TOUCH_V_RES - 1) - (2 * TOUCH_CAL_MARGIN); /* Extrapola os alvos internos para os pixels 0 e maximo da tela. */ int32_t x_at_zero = left - ((x_delta * TOUCH_CAL_MARGIN) / x_screen_span); int32_t x_at_max = right + ((x_delta * TOUCH_CAL_MARGIN) / x_screen_span); int32_t y_at_zero = top - ((y_delta * TOUCH_CAL_MARGIN) / y_screen_span); int32_t y_at_max = bottom + ((y_delta * TOUCH_CAL_MARGIN) / y_screen_span); bool invert_x = x_at_max < x_at_zero; bool invert_y = y_at_max < y_at_zero; cal->magic = TOUCH_CAL_MAGIC; cal->x_min = touch_cal_clamp_coordinate(invert_x ? x_at_max : x_at_zero); cal->x_max = touch_cal_clamp_coordinate(invert_x ? x_at_zero : x_at_max); cal->y_min = touch_cal_clamp_coordinate(invert_y ? y_at_max : y_at_zero); cal->y_max = touch_cal_clamp_coordinate(invert_y ? y_at_zero : y_at_max); cal->flags = (swap_xy ? TOUCH_CAL_FLAG_SWAP_XY : 0) | (invert_x ? TOUCH_CAL_FLAG_INVERT_X : 0) | (invert_y ? TOUCH_CAL_FLAG_INVERT_Y : 0); cal->reserved[0] = 0; cal->reserved[1] = 0; cal->reserved[2] = 0; return touch_cal_data_valid(cal) ? ESP_OK : ESP_ERR_INVALID_ARG; } esp_err_t f_touch_calibrate_run(void) { if (f_lvgl_get_display() == NULL) { return ESP_ERR_INVALID_STATE; } ESP_LOGW(TAG, "Starting four-point capacitive touch calibration."); uint16_t raw_x[TOUCH_CAL_TARGET_COUNT] = {0}; uint16_t raw_y[TOUCH_CAL_TARGET_COUNT] = {0}; touch_cal_wait_release(); for (int target = 0; target < TOUCH_CAL_TARGET_COUNT; target++) { touch_cal_draw_screen(target); touch_cal_wait_raw_press(&raw_x[target], &raw_y[target]); ESP_LOGI( TAG, "Point %d/%d: raw_x=%u raw_y=%u", target + 1, TOUCH_CAL_TARGET_COUNT, raw_x[target], raw_y[target] ); touch_cal_wait_release(); } touch_cal_nvs_t cal = {0}; esp_err_t err = touch_cal_calculate(raw_x, raw_y, &cal); if (err != ESP_OK) { return err; } ESP_LOGI( TAG, "Calculated: x=%u..%u y=%u..%u swap=%d invert_x=%d invert_y=%d", cal.x_min, cal.x_max, cal.y_min, cal.y_max, (cal.flags & TOUCH_CAL_FLAG_SWAP_XY) != 0, (cal.flags & TOUCH_CAL_FLAG_INVERT_X) != 0, (cal.flags & TOUCH_CAL_FLAG_INVERT_Y) != 0 ); err = touch_cal_save(&cal); if (err != ESP_OK) { ESP_LOGE(TAG, "Could not save calibration: %s", esp_err_to_name(err)); return err; } touch_cal_apply(&cal); touch_cal_draw_done_screen(); vTaskDelay(pdMS_TO_TICKS(900)); ESP_LOGI(TAG, "Touch calibration saved and applied."); return ESP_OK; } esp_err_t f_touch_calibrate_check_or_run(void) { touch_cal_nvs_t cal = {0}; esp_err_t err = touch_cal_load(&cal); if (err == ESP_OK) { ESP_LOGI(TAG, "Loading capacitive touch calibration from NVS."); touch_cal_apply(&cal); return ESP_OK; } ESP_LOGW(TAG, "No valid calibration found; opening calibration screen."); return f_touch_calibrate_run(); }