05_【正点原子】DNESP32S3开发板移植LVGL
·
开发板 【正点原子】DNESP32S3开发板

在正点原子 DNESP32S3 开发板上移植 LVGL 图形库。
idf_component.yml
dependencies:
idf: ">=4.4"
lvgl/lvgl: 9.2.0
lvgl_port.c
#include "lvgl_port.h"
#include "esp_timer.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sys/lock.h"
#include "sys/param.h" // 修复:提供 MAX/MIN 宏
#include "unistd.h" // 修复:提供 usleep
#include "driver/spi_master.h"
#include "lcd_driver.h"
#include "assert.h"
static const char *TAG = "lvgl_port";
// LVGL 线程安全互斥锁
static _lock_t lvgl_api_lock;
// 显示设备句柄(内部使用)
static lv_display_t *s_display = NULL;
// 刷新完成回调:通知 LVGL 刷新结束
static bool lvgl_port_flush_ready_cb(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
lv_display_t *disp = (lv_display_t *)user_ctx;
lv_display_flush_ready(disp);
return false;
}
// LVGL 刷新回调:将渲染好的帧缓冲推送到 LCD 面板
static void lvgl_port_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
esp_lcd_panel_handle_t panel_handle = lv_display_get_user_data(disp);
int offsetx1 = area->x1;
int offsetx2 = area->x2;
int offsety1 = area->y1;
int offsety2 = area->y2;
// SPI LCD 为大端,需交换 RGB565 字节序
lv_draw_sw_rgb565_swap(px_map, (offsetx2 + 1 - offsetx1) * (offsety2 + 1 - offsety1));
esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, px_map);
}
// LVGL 节拍递增回调
static void lvgl_port_tick_inc_cb(void *arg)
{
lv_tick_inc(LVGL_TICK_PERIOD_MS);
}
// LVGL 主任务:处理定时器、事件、渲染
static void lvgl_port_task(void *arg)
{
ESP_LOGI(TAG, "Starting LVGL task");
uint32_t time_till_next_ms = 0;
uint32_t min_delay_ms = 1000 / CONFIG_FREERTOS_HZ;
while (1)
{
_lock_acquire(&lvgl_api_lock);
time_till_next_ms = lv_timer_handler();
_lock_release(&lvgl_api_lock);
// 防止任务看门狗超时
time_till_next_ms = MAX(time_till_next_ms, min_delay_ms);
// 防止显示未就绪时空转过快
time_till_next_ms = MIN(time_till_next_ms, LVGL_TASK_MAX_DELAY_MS);
usleep(1000 * time_till_next_ms);
}
}
lv_display_t *lvgl_port_init(esp_lcd_panel_handle_t panel_handle, esp_lcd_panel_io_handle_t io_handle)
{
// 1. 初始化 LVGL 核心
lv_init();
// 2. 创建显示设备
lv_display_t *display = lv_display_create(LCD_H_RES, LCD_V_RES);
s_display = display;
// 3. 分配 DMA 绘制缓冲(双缓冲,局部渲染模式)
size_t draw_buffer_sz = LCD_H_RES * LVGL_DRAW_BUF_LINES * sizeof(lv_color16_t);
void *buf1 = spi_bus_dma_memory_alloc(LCD_HOST, draw_buffer_sz, 0);
assert(buf1);
void *buf2 = spi_bus_dma_memory_alloc(LCD_HOST, draw_buffer_sz, 0);
assert(buf2);
lv_display_set_buffers(display, buf1, buf2, draw_buffer_sz, LV_DISPLAY_RENDER_MODE_PARTIAL);
// 4. 绑定面板句柄与刷新回调
lv_display_set_user_data(display, panel_handle);
lv_display_set_color_format(display, LV_COLOR_FORMAT_RGB565);
lv_display_set_flush_cb(display, lvgl_port_flush_cb);
// 5. 注册 SPI 传输完成回调(修复:直接使用传入的 io_handle)
const esp_lcd_panel_io_callbacks_t cbs = {
.on_color_trans_done = lvgl_port_flush_ready_cb,
};
esp_lcd_panel_io_register_event_callbacks(io_handle, &cbs, display);
return display;
}
void lvgl_port_start(void)
{
// 1. 安装 LVGL 节拍定时器
ESP_LOGI(TAG, "Install LVGL tick timer");
const esp_timer_create_args_t lvgl_tick_timer_args = {
.callback = &lvgl_port_tick_inc_cb,
.name = "lvgl_tick"
};
esp_timer_handle_t lvgl_tick_timer = NULL;
ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer));
ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, LVGL_TICK_PERIOD_MS * 1000));
// 2. 创建 LVGL 处理任务
ESP_LOGI(TAG, "Create LVGL task");
xTaskCreate(lvgl_port_task, "LVGL", LVGL_TASK_STACK_SIZE, NULL, LVGL_TASK_PRIORITY, NULL);
}
void lvgl_port_lock(void)
{
_lock_acquire(&lvgl_api_lock);
}
void lvgl_port_unlock(void)
{
_lock_release(&lvgl_api_lock);
}
main.c
/*
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h>
#include "esp_log.h"
#include "lcd_driver.h"
#include "lvgl_port.h"
#include "lvgl_ui.h"
static const char *TAG = "main";
i2c_obj_t i2c0_master;
void app_main(void)
{
i2c0_master = iic_init(I2C_NUM_0); /* 初始化IIC0 */
xl9555_init(i2c0_master); /* IO扩展芯片初始化 */
// 1. 初始化 LCD 硬件驱动,同时获取面板句柄和IO句柄
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_panel_io_handle_t io_handle = NULL;
ESP_ERROR_CHECK(lcd_driver_init(&panel_handle, &io_handle));
ESP_LOGI(TAG, "LCD driver initialized");
// 2. 初始化 LVGL 移植层(传入面板句柄和IO句柄)
lv_display_t *display = lvgl_port_init(panel_handle, io_handle);
ESP_LOGI(TAG, "LVGL port initialized");
// 3. 启动 LVGL 任务与节拍
lvgl_port_start();
// 4. 初始化 UI 界面(加锁保护线程安全)
ESP_LOGI(TAG, "Initialize LVGL UI interface");
lvgl_port_lock();
lvgl_ui_init(display);
lvgl_port_unlock();
}
总结
本文详细展示了在 ESP32-S3 平台上将 LVGL 图形库移植到 SPI 接口 LCD 屏幕的完整过程。核心工作集中在 lvgl_port.c 文件中,它作为 LVGL 与底层硬件的桥梁,主要实现了以下几个关键功能:
- 显示驱动对接:通过
lvgl_port_flush_cb回调函数,将 LVGL 渲染好的帧缓冲区数据(RGB565格式)交换字节序后,通过esp_lcd_panel_draw_bitmap推送到 LCD 面板。 - 任务与定时器管理:创建了独立的
lvgl_port_task任务来周期性地调用lv_timer_handler(),处理 LVGL 的内部定时器、事件和渲染逻辑。同时,通过 ESP 硬件定时器为 LVGL 提供精确的节拍 (lv_tick_inc)。 - 线程安全与同步:使用
_lock_t互斥锁 (lvgl_api_lock) 保护 LVGL API 的调用,确保在多任务环境下的数据安全。并通过 SPI 传输完成回调 (lvgl_port_flush_ready_cb) 通知 LVGL 一帧数据刷新完成。 - 内存管理:使用
spi_bus_dma_memory_alloc为 LVGL 分配 DMA 可访问的双绘图缓冲区,设置为局部渲染模式以优化性能。
在 main.c 中,应用程序的启动流程清晰明了:
- 初始化 I2C 和 IO 扩展芯片。
- 初始化 LCD 硬件驱动,获取面板和 IO 句柄。
- 调用
lvgl_port_init初始化 LVGL 并绑定显示设备。 - 调用
lvgl_port_start启动 LVGL 任务和节拍定时器。 - 在锁保护下初始化用户界面。
idf_component.yml 则简洁地定义了项目对 ESP-IDF (>=4.4) 和 LVGL (9.2.0) 的依赖关系。
更多推荐
所有评论(0)