/* * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "freertos/task.h" #include "esp_lcd_panel_ops.h" #include "esp_lcd_panel_rgb.h" #include "esp_lcd_touch.h" #include "esp_timer.h" #include "esp_log.h" #include "lvgl.h" #include "lvgl_port.h" #include "beep.h" #include "setting.h" static const char *TAG = "lv_port"; static SemaphoreHandle_t lvgl_mux; // LVGL 互斥锁 static TaskHandle_t lvgl_task_handle = NULL; static bool isPressed = false; static bool isReleased = false; #if EXAMPLE_LVGL_PORT_ROTATION_DEGREE != 0 static void *get_next_frame_buffer(esp_lcd_panel_handle_t panel_handle) { static void *next_fb = NULL; static void *fb[2] = {NULL}; if (next_fb == NULL) { ESP_ERROR_CHECK(esp_lcd_rgb_panel_get_frame_buffer(panel_handle, 2, &fb[0], &fb[1])); // 获取双缓冲区 next_fb = fb[1]; } else { next_fb = (next_fb == fb[0]) ? fb[1] : fb[0]; // 切换缓冲区 } return next_fb; } IRAM_ATTR static void rotate_copy_pixel(const uint16_t *from, uint16_t *to, uint16_t x_start, uint16_t y_start, uint16_t x_end, uint16_t y_end, uint16_t w, uint16_t h, uint16_t rotation) { int from_index = 0; int to_index = 0; int to_index_const = 0; switch (rotation) { case 90: to_index_const = (w - x_start - 1) * h; for (int from_y = y_start; from_y < y_end + 1; from_y++) { from_index = from_y * w + x_start; to_index = to_index_const + from_y; for (int from_x = x_start; from_x < x_end + 1; from_x++) { *(to + to_index) = *(from + from_index); from_index += 1; to_index -= h; } } break; case 180: to_index_const = h * w - x_start - 1; for (int from_y = y_start; from_y < y_end + 1; from_y++) { from_index = from_y * w + x_start; to_index = to_index_const - from_y * w; for (int from_x = x_start; from_x < x_end + 1; from_x++) { *(to + to_index) = *(from + from_index); from_index += 1; to_index -= 1; } } break; case 270: to_index_const = (x_start + 1) * h - 1; for (int from_y = y_start; from_y < y_end + 1; from_y++) { from_index = from_y * w + x_start; to_index = to_index_const - from_y; for (int from_x = x_start; from_x < x_end + 1; from_x++) { *(to + to_index) = *(from + from_index); from_index += 1; to_index += h; } } break; default: break; } } #endif /* EXAMPLE_LVGL_PORT_ROTATION_DEGREE */ #if LVGL_PORT_AVOID_TEAR_ENABLE #if LVGL_PORT_DIRECT_MODE #if EXAMPLE_LVGL_PORT_ROTATION_DEGREE != 0 typedef struct { uint16_t inv_p; uint8_t inv_area_joined[LV_INV_BUF_SIZE]; lv_area_t inv_areas[LV_INV_BUF_SIZE]; } lv_port_dirty_area_t; typedef enum { FLUSH_STATUS_PART, FLUSH_STATUS_FULL } lv_port_flush_status_t; typedef enum { FLUSH_PROBE_PART_COPY, FLUSH_PROBE_SKIP_COPY, FLUSH_PROBE_FULL_COPY, } lv_port_flush_probe_t; static lv_port_dirty_area_t dirty_area; static void flush_dirty_save(lv_port_dirty_area_t *dirty_area) { lv_disp_t *disp = _lv_refr_get_disp_refreshing(); // 获取正在刷新的显示设备 dirty_area->inv_p = disp->inv_p; // 保存无效区域的数量 for (int i = 0; i < disp->inv_p; i++) { dirty_area->inv_area_joined[i] = disp->inv_area_joined[i]; // 保存无效区域的合并状态 dirty_area->inv_areas[i] = disp->inv_areas[i]; // 保存无效区域的信息 } } /** * @brief 探测无效区域以进行复制 * * @note 此函数用于避免撕裂效果,仅在 LVGL 直接模式下有效。 * */ static lv_port_flush_probe_t flush_copy_probe(lv_disp_drv_t *drv) { static lv_port_flush_status_t prev_status = FLUSH_STATUS_PART; lv_port_flush_status_t cur_status; lv_port_flush_probe_t probe_result; lv_disp_t *disp_refr = _lv_refr_get_disp_refreshing(); // 获取正在刷新的显示设备 uint32_t flush_ver = 0; uint32_t flush_hor = 0; for (int i = 0; i < disp_refr->inv_p; i++) { if (disp_refr->inv_area_joined[i] == 0) { flush_ver = (disp_refr->inv_areas[i].y2 + 1 - disp_refr->inv_areas[i].y1); // 计算垂直方向的刷新长度 flush_hor = (disp_refr->inv_areas[i].x2 + 1 - disp_refr->inv_areas[i].x1); // 计算水平方向的刷新长度 break; } } /* 检查当前是否为全屏刷新 */ cur_status = ((flush_ver == drv->ver_res) && (flush_hor == drv->hor_res)) ? (FLUSH_STATUS_FULL) : (FLUSH_STATUS_PART); if (prev_status == FLUSH_STATUS_FULL) { if ((cur_status == FLUSH_STATUS_PART)) { probe_result = FLUSH_PROBE_FULL_COPY; // 上次是全屏刷新,本次是部分刷新,需要复制整个屏幕 } else { probe_result = FLUSH_PROBE_SKIP_COPY; // 上次和本次都是全屏刷新,跳过复制 } } else { probe_result = FLUSH_PROBE_PART_COPY; // 上次是部分刷新,本次也是部分刷新,只复制部分区域 } prev_status = cur_status; return probe_result; } static inline void *flush_get_next_buf(void *panel_handle) { return get_next_frame_buffer(panel_handle); // 获取下一个帧缓冲区 } /** * @brief 复制无效区域 * * @note 此函数用于避免撕裂效果,仅在 LVGL 直接模式下有效。 * */ static void flush_dirty_copy(void *dst, void *src, lv_port_dirty_area_t *dirty_area) { lv_coord_t x_start, x_end, y_start, y_end; for (int i = 0; i < dirty_area->inv_p; i++) { /* 刷新未合并的区域 */ if (dirty_area->inv_area_joined[i] == 0) { x_start = dirty_area->inv_areas[i].x1; x_end = dirty_area->inv_areas[i].x2; y_start = dirty_area->inv_areas[i].y1; y_end = dirty_area->inv_areas[i].y2; rotate_copy_pixel(src, dst, x_start, y_start, x_end, y_end, LV_HOR_RES, LV_VER_RES, EXAMPLE_LVGL_PORT_ROTATION_DEGREE); } } } static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map) { esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t)drv->user_data; const int offsetx1 = area->x1; const int offsetx2 = area->x2; const int offsety1 = area->y1; const int offsety2 = area->y2; void *next_fb = NULL; lv_port_flush_probe_t probe_result = FLUSH_PROBE_PART_COPY; lv_disp_t *disp = lv_disp_get_default(); /* 最后一个区域刷新后的操作 */ if (lv_disp_flush_is_last(drv)) { /* 检查是否触发了 `full_refresh` 标志 */ if (drv->full_refresh) { /* 重置标志 */ drv->full_refresh = 0; // 将整个屏幕的 LVGL 缓冲区数据旋转并复制到下一个帧缓冲区 next_fb = flush_get_next_buf(panel_handle); rotate_copy_pixel((uint16_t *)color_map, next_fb, offsetx1, offsety1, offsetx2, offsety2, LV_HOR_RES, LV_VER_RES, EXAMPLE_LVGL_PORT_ROTATION_DEGREE); /* 将当前 RGB 帧缓冲区切换到 `next_fb` */ esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, next_fb); /* 等待当前帧缓冲区传输完成 */ ulTaskNotifyValueClear(NULL, ULONG_MAX); ulTaskNotifyTake(pdTRUE, portMAX_DELAY); /* 异步更新另一个帧缓冲区的无效区域 */ flush_dirty_copy(flush_get_next_buf(panel_handle), color_map, &dirty_area); flush_get_next_buf(panel_handle); } else { /* 探测当前无效区域的复制方法 */ probe_result = flush_copy_probe(drv); if (probe_result == FLUSH_PROBE_FULL_COPY) { /* 保存当前无效区域以供下一个帧缓冲区使用 */ flush_dirty_save(&dirty_area); /* 设置 LVGL 全刷新标志并提前设置刷新准备好 */ drv->full_refresh = 1; disp->rendering_in_progress = false; lv_disp_flush_ready(drv); /* 强制刷新整个屏幕,将递归调用 `flush_callback` */ lv_refr_now(_lv_refr_get_disp_refreshing()); } else { /* 更新当前无效区域以供下一个帧缓冲区使用 */ next_fb = flush_get_next_buf(panel_handle); flush_dirty_save(&dirty_area); flush_dirty_copy(next_fb, color_map, &dirty_area); /* 将当前 RGB 帧缓冲区切换到 `next_fb` */ esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, next_fb); /* 等待当前帧缓冲区传输完成 */ ulTaskNotifyValueClear(NULL, ULONG_MAX); ulTaskNotifyTake(pdTRUE, portMAX_DELAY); if (probe_result == FLUSH_PROBE_PART_COPY) { /* 异步更新另一个帧缓冲区的无效区域 */ flush_dirty_save(&dirty_area); flush_dirty_copy(flush_get_next_buf(panel_handle), color_map, &dirty_area); flush_get_next_buf(panel_handle); } } } } lv_disp_flush_ready(drv); } #endif #elif LVGL_PORT_FULL_REFRESH && LVGL_PORT_LCD_RGB_BUFFER_NUMS == 3 #if EXAMPLE_LVGL_PORT_ROTATION_DEGREE == 0 static void *lvgl_port_rgb_last_buf = NULL; static void *lvgl_port_rgb_next_buf = NULL; static void *lvgl_port_flush_next_buf = NULL; #endif void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map) { esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t)drv->user_data; const int offsetx1 = area->x1; const int offsetx2 = area->x2; const int offsety1 = area->y1; const int offsety2 = area->y2; #if EXAMPLE_LVGL_PORT_ROTATION_DEGREE != 0 void *next_fb = get_next_frame_buffer(panel_handle); /* 将当前 LVGL 缓冲区中的无效区域旋转并复制到下一个 RGB 帧缓冲区 */ rotate_copy_pixel((uint16_t *)color_map, next_fb, offsetx1, offsety1, offsetx2, offsety2, LV_HOR_RES, LV_VER_RES, EXAMPLE_LVGL_PORT_ROTATION_DEGREE); /* 将当前 RGB 帧缓冲区切换到 `next_fb` */ esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, next_fb); #else drv->draw_buf->buf1 = color_map; drv->draw_buf->buf2 = lvgl_port_flush_next_buf; lvgl_port_flush_next_buf = color_map; /* 将当前 RGB 帧缓冲区切换到 `color_map` */ esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, color_map); lvgl_port_rgb_next_buf = color_map; #endif lv_disp_flush_ready(drv); } #endif #else static void flush_callback(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map) { esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t)lv_display_get_user_data(disp); const int offsetx1 = area->x1; const int offsetx2 = area->x2; const int offsety1 = area->y1; const int offsety2 = area->y2; /* 将颜色映射中的数据直接复制到 RGB 帧缓冲区 */ esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, px_map); lv_display_flush_ready(disp); } #endif /* LVGL_PORT_AVOID_TEAR_ENABLE */ static lv_display_t *display_init(esp_lcd_panel_handle_t panel_handle) { assert(panel_handle); ESP_LOGD(TAG, "为 LVGL 缓冲区分配内存"); // 分配 LVGL 使用的绘图缓冲区 void *buf1 = NULL; void *buf2 = NULL; int buffer_size = 0; #if LVGL_PORT_AVOID_TEAR_ENABLE // 为了避免撕裂效果,我们应该至少使用两个帧缓冲区:一个用于 LVGL 渲染,另一个用于 RGB 输出 buffer_size = LVGL_PORT_H_RES * LVGL_PORT_V_RES; #if (LVGL_PORT_LCD_RGB_BUFFER_NUMS == 3) && (EXAMPLE_LVGL_PORT_ROTATION_DEGREE == 0) && LVGL_PORT_FULL_REFRESH // 使用三个缓冲区和全刷新,我们总是有一个缓冲区可用于渲染,消除了等待 RGB 同步信号的需要 ESP_ERROR_CHECK(esp_lcd_rgb_panel_get_frame_buffer(panel_handle, 3, &lvgl_port_rgb_last_buf, &buf1, &buf2)); lvgl_port_rgb_next_buf = lvgl_port_rgb_last_buf; lvgl_port_flush_next_buf = buf2; #elif (LVGL_PORT_LCD_RGB_BUFFER_NUMS == 3) && (EXAMPLE_LVGL_PORT_ROTATION_DEGREE != 0) // 这里我们使用三个帧缓冲区,一个用于 LVGL 渲染,另外两个用于 RGB 驱动(其中一个用于旋转) void *fbs[3]; ESP_ERROR_CHECK(esp_lcd_rgb_panel_get_frame_buffer(panel_handle, 3, &fbs[0], &fbs[1], &fbs[2])); buf1 = fbs[2]; #else ESP_ERROR_CHECK(esp_lcd_rgb_panel_get_frame_buffer(panel_handle, 2, &buf1, &buf2)); #endif #else buffer_size = LVGL_PORT_H_RES * LVGL_PORT_BUFFER_HEIGHT; buf1 = heap_caps_malloc(buffer_size * sizeof(lv_color_t), MALLOC_CAP_SPIRAM); assert(buf1); ESP_LOGI(TAG, "LVGL 缓冲区大小: %dKB", buffer_size * sizeof(lv_color_t) / 1024); #endif // 创建显示设备 lv_display_t *disp = lv_display_create(LVGL_PORT_H_RES, LVGL_PORT_V_RES); if (!disp) { ESP_LOGE(TAG, "创建显示设备失败"); return NULL; } // 设置显示缓冲区 lv_display_set_buffers(disp, buf1, buf2, buffer_size * sizeof(lv_color_t), LV_DISPLAY_RENDER_MODE_PARTIAL); // 设置显示回调 lv_display_set_flush_cb(disp, flush_callback); // 设置用户数据 lv_display_set_user_data(disp, panel_handle); #if LVGL_PORT_FULL_REFRESH lv_display_set_render_mode(disp, LV_DISPLAY_RENDER_MODE_FULL); #elif LVGL_PORT_DIRECT_MODE lv_display_set_render_mode(disp, LV_DISPLAY_RENDER_MODE_DIRECT); #endif return disp; } static void touchpad_read(lv_indev_t *indev, lv_indev_data_t *data) { esp_lcd_touch_handle_t tp = (esp_lcd_touch_handle_t)lv_indev_get_user_data(indev); assert(tp); uint16_t touchpad_x; uint16_t touchpad_y; uint8_t touchpad_cnt = 0; esp_lcd_touch_read_data(tp); bool touchpad_pressed = esp_lcd_touch_get_coordinates(tp, &touchpad_x, &touchpad_y, NULL, &touchpad_cnt, 1); if (touchpad_pressed && touchpad_cnt > 0) { data->point.x = touchpad_x; data->point.y = touchpad_y; data->state = LV_INDEV_STATE_PRESSED; if(!isReleased && !isPressed) { beep(); if(reset_screen_off()) { data->continue_reading = true; return; } isPressed = true; } } else { if(isPressed) { isReleased = false; isPressed = false; } beep_stop(); data->state = LV_INDEV_STATE_RELEASED; } } static lv_indev_t *indev_init(esp_lcd_touch_handle_t tp) { assert(tp); // 创建输入设备 lv_indev_t *indev = lv_indev_create(); if (!indev) { ESP_LOGE(TAG, "创建输入设备失败"); return NULL; } // 设置输入设备属性 lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); lv_indev_set_read_cb(indev, touchpad_read); lv_indev_set_user_data(indev, tp); return indev; } static void tick_increment(void *arg) { /* 告诉 LVGL 已经过去了多少毫秒 */ lv_tick_inc(LVGL_PORT_TICK_PERIOD_MS); } static esp_err_t tick_init(void) { // LVGL 的 Tick 接口(使用 esp_timer 生成 2ms 周期事件) const esp_timer_create_args_t lvgl_tick_timer_args = { .callback = &tick_increment, .name = "LVGL tick"}; esp_timer_handle_t lvgl_tick_timer = NULL; ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer)); return esp_timer_start_periodic(lvgl_tick_timer, LVGL_PORT_TICK_PERIOD_MS * 1000); } static void lvgl_port_task(void *arg) { ESP_LOGD(TAG, "启动 LVGL 任务"); uint32_t task_delay_ms = LVGL_PORT_TASK_MAX_DELAY_MS; while (1) { // 使用 FreeRTOS 的任务切换机制,提高效率 if (lvgl_port_lock(0)) { task_delay_ms = lv_timer_handler(); lvgl_port_unlock(); } if (task_delay_ms > LVGL_PORT_TASK_MAX_DELAY_MS) { task_delay_ms = LVGL_PORT_TASK_MAX_DELAY_MS; } else if (task_delay_ms < LVGL_PORT_TASK_MIN_DELAY_MS) { task_delay_ms = LVGL_PORT_TASK_MIN_DELAY_MS; } // 使用 vTaskDelay 函数,而不是直接使用延时函数,提高效率 vTaskDelay(pdMS_TO_TICKS(task_delay_ms)); } } esp_err_t lvgl_port_init(esp_lcd_panel_handle_t lcd_handle, esp_lcd_touch_handle_t tp_handle) { lv_init(); ESP_ERROR_CHECK(tick_init()); lv_disp_t *disp = display_init(lcd_handle); assert(disp); if (tp_handle) { lv_indev_t *indev = indev_init(tp_handle); assert(indev); #if EXAMPLE_LVGL_PORT_ROTATION_90 esp_lcd_touch_set_swap_xy(tp_handle, true); esp_lcd_touch_set_mirror_y(tp_handle, true); #elif EXAMPLE_LVGL_PORT_ROTATION_180 esp_lcd_touch_set_mirror_x(tp_handle, true); esp_lcd_touch_set_mirror_y(tp_handle, true); #elif EXAMPLE_LVGL_PORT_ROTATION_270 esp_lcd_touch_set_swap_xy(tp_handle, true); esp_lcd_touch_set_mirror_x(tp_handle, true); #endif } lvgl_mux = xSemaphoreCreateRecursiveMutex(); assert(lvgl_mux); ESP_LOGI(TAG, "创建 LVGL 任务"); BaseType_t core_id = (LVGL_PORT_TASK_CORE < 0) ? tskNO_AFFINITY : LVGL_PORT_TASK_CORE; BaseType_t ret = xTaskCreatePinnedToCore(lvgl_port_task, "lvgl", LVGL_PORT_TASK_STACK_SIZE, NULL, LVGL_PORT_TASK_PRIORITY, &lvgl_task_handle, core_id); if (ret != pdPASS) { ESP_LOGE(TAG, "创建 LVGL 任务失败"); return ESP_FAIL; } return ESP_OK; } bool lvgl_port_lock(int timeout_ms) { assert(lvgl_mux && "必须先调用 lvgl_port_init"); const TickType_t timeout_ticks = (timeout_ms < 0) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); return xSemaphoreTakeRecursive(lvgl_mux, timeout_ticks) == pdTRUE; } void lvgl_port_unlock(void) { assert(lvgl_mux && "必须先调用 lvgl_port_init"); xSemaphoreGiveRecursive(lvgl_mux); } bool lvgl_port_notify_rgb_vsync(void) { BaseType_t need_yield = pdFALSE; #if LVGL_PORT_FULL_REFRESH && (LVGL_PORT_LCD_RGB_BUFFER_NUMS == 3) && (EXAMPLE_LVGL_PORT_ROTATION_DEGREE == 0) if (lvgl_port_rgb_next_buf != lvgl_port_rgb_last_buf) { lvgl_port_flush_next_buf = lvgl_port_rgb_last_buf; lvgl_port_rgb_last_buf = lvgl_port_rgb_next_buf; } #elif LVGL_PORT_AVOID_TEAR_ENABLE // 通知当前 RGB 帧缓冲区已传输完毕 xTaskNotifyFromISR(lvgl_task_handle, ULONG_MAX, eNoAction, &need_yield); #endif return (need_yield == pdTRUE); }