/** **************************************************************************************************** * @file lvgl_port.c * @author HiFi Player Project * @version V1.0 * @date 2026-01-01 * @brief LVGL 8.x 移植层 * * 移植要点: * 1. 绘图缓冲区必须放在内部 RAM,并带 DMA 能力(SPI DMA 不能直接读 PSRAM)。 * 2. flush_cb 中把 LVGL 的小端 RGB565 数据做字节交换,再交给 spilcd_flush()。 * 3. spilcd_flush() 是阻塞的(等 SPI 传输完成回调),因此这里调用后可以直接 * lv_disp_flush_ready(),实现简单可靠。 **************************************************************************************************** */ #include "lvgl_port.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_heap_caps.h" #include "esp_log.h" #include "esp_timer.h" #include "lvgl.h" #include "spilcd.h" #define TAG "lvgl_port" #define DISP_HOR_RES SPI_LCD_H_RES /* 320 */ #define DISP_VER_RES SPI_LCD_V_RES /* 240 */ #define DRAW_BUF_LINES 40 /* 单缓冲区高度(40 行约 25KB) */ #define LVGL_TASK_STACK (8 * 1024) #define LVGL_TASK_PRIO 4 static lv_disp_draw_buf_t s_draw_buf; static lv_disp_drv_t s_disp_drv; static lv_color_t *s_draw_buf_mem = NULL; static esp_timer_handle_t s_tick_timer = NULL; /** * @brief LVGL 刷新回调:把绘制好的区域送到 SPI LCD */ static void disp_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_p) { int32_t width = lv_area_get_width(area); int32_t height = lv_area_get_height(area); uint32_t pixels = (uint32_t)width * (uint32_t)height; uint16_t *buf = (uint16_t *)color_p; /* LVGL 内部是 RGB565 小端,ST7789 要求高字节在前,这里原地交换 */ for (uint32_t i = 0; i < pixels; i++) { buf[i] = (uint16_t)((buf[i] << 8) | (buf[i] >> 8)); } /* x2 / y2 使用“不包含”的坐标 */ spilcd_flush((uint16_t)area->x1, (uint16_t)area->y1, (uint16_t)(area->x2 + 1), (uint16_t)(area->y2 + 1), buf); lv_disp_flush_ready(drv); } /** * @brief LVGL 1ms 时基 */ static void lv_tick_cb(void *arg) { (void)arg; lv_tick_inc(1); } esp_err_t lvgl_port_init(void) { lv_init(); /* ---------------- 绘图缓冲区(内部 DMA RAM) ---------------- */ size_t pixel_count = (size_t)DISP_HOR_RES * DRAW_BUF_LINES; s_draw_buf_mem = (lv_color_t *)heap_caps_malloc(pixel_count * sizeof(lv_color_t), MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); if (s_draw_buf_mem == NULL) { ESP_LOGE(TAG, "no memory for LVGL draw buffer"); return ESP_ERR_NO_MEM; } lv_disp_draw_buf_init(&s_draw_buf, s_draw_buf_mem, NULL, pixel_count); /* ---------------- 注册显示设备 ---------------- */ lv_disp_drv_init(&s_disp_drv); s_disp_drv.hor_res = DISP_HOR_RES; s_disp_drv.ver_res = DISP_VER_RES; s_disp_drv.draw_buf = &s_draw_buf; s_disp_drv.flush_cb = disp_flush_cb; lv_disp_drv_register(&s_disp_drv); /* ---------------- 1ms 时基 ---------------- */ const esp_timer_create_args_t timer_args = { .callback = lv_tick_cb, .name = "lv_tick", }; ESP_ERROR_CHECK(esp_timer_create(&timer_args, &s_tick_timer)); ESP_ERROR_CHECK(esp_timer_start_periodic(s_tick_timer, 1000)); /* 1000us */ ESP_LOGI(TAG, "LVGL %d.%d.%d ready, draw buffer %u px", LVGL_VERSION_MAJOR, LVGL_VERSION_MINOR, LVGL_VERSION_PATCH, (unsigned)pixel_count); return ESP_OK; } static void lvgl_task(void *arg) { (void)arg; while (1) { lv_timer_handler(); vTaskDelay(pdMS_TO_TICKS(5)); } } void lvgl_port_start(void) { if (xTaskCreate(lvgl_task, "lvgl", LVGL_TASK_STACK, NULL, LVGL_TASK_PRIO, NULL) != pdPASS) { ESP_LOGE(TAG, "create lvgl task failed"); } }