一、准备材料
- 开发板:esp32s3
- idf版本:4.4.2
- lvgl:8.3.3
注意:lvgl不要选择master分支,编译失败时不好确定问题。
二、创建idf项目
方式一
通过 VSCode 创建项目
-
在命令面板中搜索 esp-idf new,开始创建项目
-
项目配置信息
-
选择需要的模板,也可以选择想要的案例
注意: 需要VSCode 中安装 idf 环境的可以看我之前的笔记VSCode 中安装 esp-idf
方式二
通过命令创建项目
idf.py create-project
方式三
直接在路径 %ESP-IDF%espressifframeworksesp-idf-v4.4.2examples
中拷贝自己需要的使用案例
三、添加LVGL库
-
下载lvgl8.3.3
GitHub:https://github.com/lvgl/lvgl.git -
将lvgl库添加到项目的 components 文件中,如下图所示
** 注意,如果觉得文件比较多的话,可以删除不用的文件,如下图所示:
-
将
lv_conf_template.h
重命名为lv_conf_.h
,并将文件中的#if 0
改为#if 1
-
设置开发板为esp32s3
-
编译
** 注意:**如果出现编译错误时,检查一下自己是否下载成了master分支
四、添加显示驱动
-
下载lvgl_esp32_drivers
GitHub:https://github.com/lvgl/lvgl_esp32_drivers -
使用命令
idf.py menuconfig
打开图形配置界面
-
进入 Component config → LVGL ESP Drivers → LVGL TFT Display controller 配置显示驱动信息
-
SPI引脚配置
-
进入 Component config → LVGL configguration 配置LVGL信息
-
按s键进行保存,完成后按Q退出
-
配置屏幕信息和SPI通道数量
编译后会产生以下错误,如果所示
未定义显示器的像素宽度和高度
未定义开发板 SPI 的通道数量
只需要在文件
lvgl_helpers.h
中添加以下定义即可,如图所示#define SPI_HOST_MAX 3 // 开发板 SPI 通道数量 #define LV_HOR_RES_MAX 240 // 显示器水平像素 #define LV_VER_RES_MAX 320 // 显示器垂直像素
-
编译通过后,在 main.c 文件中添加程序,程序后面附上
-
SPI 信道错误
运行后不断重启,并出现图示中的问题,值需要在文件 `` 中将SPI信道改为自动模式
SPI_DMA_CH_AUTO
即可,如下图所示:
到此屏幕就能正常显示了。
五、添加触摸驱动
-
使用命令
idf.py menuconfig
打开图形配置界面
-
进入 Component config → LVGL ESP Drivers → LVGL Touch controller 打开触摸驱动
-
返回上一级,选择I2C通道
-
进入 Component config → I2C Port Settings 配置I2C引脚
六、main.c文件
#include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_freertos_hooks.h" #include "freertos/semphr.h" #include "esp_system.h" #include "driver/gpio.h" /* Littlevgl specific */ #ifdef LV_LVGL_H_INCLUDE_SIMPLE #include "lvgl.h" #else #include "lvgl/lvgl.h" #endif #include "lvgl_helpers.h" /********************* * DEFINES *********************/ #define TAG "main" #define LV_TICK_PERIOD_MS 1 /********************** * STATIC PROTOTYPES **********************/ static void lv_tick_task(void *arg); static void guiTask(void *pvParameter); static void create_demo_application(void); /********************** * APPLICATION MAIN **********************/ void app_main() { /* 使用任务创建图形时,必须使用固定任务创建 * 注意:不使用Wi-Fi或蓝牙时,可以将gui任务固定到核心0 */ xTaskCreatePinnedToCore(guiTask, "gui", 4096*2, NULL, 0, NULL, 1); } /* Creates a semaphore to handle concurrent call to lvgl stuff * If you wish to call *any* lvgl function from other threads/tasks * you should lock on the very same semaphore! */ SemaphoreHandle_t xGuiSemaphore; static void guiTask(void *pvParameter) { (void) pvParameter; xGuiSemaphore = xSemaphoreCreateMutex(); lv_init(); /* 初始化驱动程序使用的SPI或I2C总线 */ lvgl_driver_init(); /* 创建堆内存,用过图形缓冲区buf1 */ lv_color_t* buf1 = heap_caps_malloc(DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA); assert(buf1 != NULL); /* 不使用单色显示器时使用双缓冲 */ #ifndef CONFIG_LV_TFT_DISPLAY_MONOCHROME lv_color_t* buf2 = heap_caps_malloc(DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA); assert(buf2 != NULL); #else static lv_color_t *buf2 = NULL; #endif static lv_disp_draw_buf_t disp_buf; uint32_t size_in_px = DISP_BUF_SIZE; #if defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_IL3820 || defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_JD79653A || defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_UC8151D || defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_SSD1306 /* Actual size in pixels, not bytes. */ size_in_px *= 8; #endif /* Initialize the working buffer depending on the selected display. * NOTE: buf2 == NULL when using monochrome displays. */ lv_disp_draw_buf_init(&disp_buf, buf1, buf2, size_in_px); lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); disp_drv.flush_cb = disp_driver_flush; /* When using a monochrome display we need to register the callbacks: * - rounder_cb * - set_px_cb */ #ifdef CONFIG_LV_TFT_DISPLAY_MONOCHROME disp_drv.rounder_cb = disp_driver_rounder; disp_drv.set_px_cb = disp_driver_set_px; #endif disp_drv.draw_buf = &disp_buf; lv_disp_drv_register(&disp_drv); /* Register an input device when enabled on the menuconfig */ #if CONFIG_LV_TOUCH_CONTROLLER != TOUCH_CONTROLLER_NONE lv_indev_drv_t indev_drv; lv_indev_drv_init(&indev_drv); indev_drv.read_cb = touch_driver_read; indev_drv.type = LV_INDEV_TYPE_POINTER; lv_indev_drv_register(&indev_drv); #endif /* Create and start a periodic timer interrupt to call lv_tick_inc */ const esp_timer_create_args_t periodic_timer_args = { .callback = &lv_tick_task, .name = "periodic_gui" }; esp_timer_handle_t periodic_timer; ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer)); ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer, LV_TICK_PERIOD_MS * 1000)); /* Create the demo application */ create_demo_application(); while (1) { /* Delay 1 tick (assumes FreeRTOS tick is 10ms */ vTaskDelay(pdMS_TO_TICKS(10)); /* Try to take the semaphore, call lvgl related function on success */ if (pdTRUE == xSemaphoreTake(xGuiSemaphore, portMAX_DELAY)) { lv_task_handler(); xSemaphoreGive(xGuiSemaphore); } } /* A task should NEVER return */ free(buf1); #ifndef CONFIG_LV_TFT_DISPLAY_MONOCHROME free(buf2); #endif vTaskDelete(NULL); } /** * @brief 创建标签 */ void lvgl_lable_test(){ /* 创建一个标签 */ lv_obj_t* label = lv_label_create(lv_scr_act()); if (NULL != label) { // lv_obj_set_x(label, 90); // 设置控件的X坐标 // lv_obj_set_y(label, 100); // 设置控件的Y坐标 // lv_obj_set_size(label, 60, 20); // 设置控件大小 lv_label_set_text(label, "Counter"); // 初始显示 0 // lv_obj_center(label); // 居中显示 lv_obj_align(label, LV_ALIGN_CENTER, 0, -50); // 居中显示后,向上偏移50 } } /** * @brief 按钮事件回调函数 */ static void btn_event_callback(lv_event_t* event) { static uint32_t counter = 1; lv_obj_t* btn = lv_event_get_target(event); //获取事件对象 if (btn != NULL) { lv_obj_t* btn_parent = lv_obj_get_parent(btn); lv_obj_t* label = lv_obj_get_child(btn_parent, 0); lv_label_set_text_fmt(label, "%d", counter); //设置显示内容 lv_obj_align(label, LV_ALIGN_CENTER, 0, -50); // 居中显示后,向上偏移50 counter++; } } /** * @brief 创建按钮 */ void lvgl_button_test(){ /* 在当前界面中创建一个按钮 */ lv_obj_t* btn = lv_btn_create(lv_scr_act()); // 创建Button对象 if (btn != NULL) { lv_obj_set_size(btn, 80, 20); // 设置对象宽度和高度 // lv_obj_set_pos(btn, 90, 200); // 设置按钮的X和Y坐标 lv_obj_add_event_cb(btn, btn_event_callback, LV_EVENT_CLICKED, NULL); // 给对象添加CLICK事件和事件处理回调函数 lv_obj_align(btn, LV_ALIGN_CENTER, 0, 50); // 居中显示后,向下偏移50 lv_obj_t* btn_label = lv_label_create(btn); // 基于Button对象创建Label对象 if (btn_label != NULL) { lv_label_set_text(btn_label, "button"); // 设置显示内容 lv_obj_center(btn_label); // 对象居中显示 } } } static void create_demo_application(void) { /* 加载标签 */ lvgl_lable_test(); /* 加载按钮 */ lvgl_button_test(); } static void lv_tick_task(void *arg) { (void) arg; lv_tick_inc(LV_TICK_PERIOD_MS); }