LVGL 入门使用教程

一、准备资料

开发板:ESP32-S3
开发环境:VS Code + PlatformIO
串口屏驱动 TFT-eSPI:https://github.com/Bodmer/TFT_eSPI
触摸驱动 Arduino-FT6336U:https://github.com/aselectroworks/Arduino-FT6336U
GUI LVGL:https://github.com/lvgl/lvgl

二、项目搭建

  1. 资源库下载
    这里我使用的驱动都是从 GitHub 上下载,有经验的小伙伴也可以自己写驱动程序

  2. 工程文件
    将下载的 TFT-eSPI、Arduino-FT6336U、LVGL,放在项目的lib文件下,如下图所示:
    LVGL 入门使用教程

  3. 在 c_cpp_properties.json 文件中的 includePath 和 path 中添加资源路径

    "c:/Users/Administrator/Desktop/weather_clock_test/lib/TFT_eSPI",  "c:/Users/Administrator/Desktop/weather_clock_test/lib/lvgl", "c:/Users/Administrator/Desktop/weather_clock_test/lib/lvgl/src",  "c:/Users/Administrator/Desktop/weather_clock_test/lib/Arduino-FT6336U/src", 

    注意:自己的项目路径,我这里只是举例。

三、屏驱动 TFT-eSPI

  1. 配置显示器驱动
    在路径TFT_eSPI/User_Setup.h,中找到User_Setup.h文件,配置显示屏的驱动,不知道怎么使用 TFT-eSPI 的小伙伴可以看我之前的笔记TFT-eSPI入门使用教程

  2. 创建对象

    TFT_eSPI tft = TFT_eSPI() TFT_eSPI tft = TFT_eSPI(320,240)        // 在创建对象的时候设置屏幕尺寸 

    注意:记得加载头文件 #include <TFT_eSPI.h>

  3. TFT-eSPI的初始化程序初始化

    /* ------------ 屏幕背光亮度 ------------*/ /* 配置LED PWM通道属性,PWD通道为 0,频率为1KHz */ ledcSetup(LCD_BL_PWM_CHANNEL, 1000, TFT_BL); /* 配置LED PWM通道属性 */ ledcAttachPin(LCD_BL_PIN, LCD_BL_PWM_CHANNEL); ledcWrite(LCD_BL_PWM_CHANNEL, (int)(1 * 255));  /* 初始化显示驱动 */ tft.init();  /* 旋转角度 0、1、2、3 对应 0 、90度、180度、270 */ tft.setRotation(0); /* 关闭颜色反转 */ tft.invertDisplay(0); 

四. 触摸驱动 Arduino-FT6336U

触摸驱动比较简单,不需要复杂的配置,只需要在初始化的时候传入引脚即可

  1. 触摸引脚的宏定义

    #define I2C_SDA 4 #define I2C_SCL 15 #define RST_N_PIN 5 #define INT_N_PIN 17 
  2. 创建对象

    FT6336U ft6336u(I2C_SDA, I2C_SCL, RST_N_PIN, INT_N_PIN);  FT6336U_TouchPointType tp; 
  3. 初始化

    ft6336u.begin(); 

五、LVGL使用

这里我整理了一些 LVGL 的学习资料,需要的可以了解一下 LVGL学习资料

  1. 下载 LVGL
    从GitHub 中下载 或者克隆 LVGL 资源库

  2. 在项目中添加 LVGL 资源库
    将下载的库文件复制到项目的lib路径下,建议将资源的文件名改为 lvgl
    注意:名字不一样时,c_cpp_properties.json文件中添加的路径也会变化

  3. 重命名 lv_conf_template.h 文件

    • lvgl/lv_conf_template.h 文件重命名为 lv_conf.h
    • 将文件中的第一个 #if 0 改为 #if 1
    • 通过配置 LV_COLOR_DEPTH 宏,设置显示屏的颜色深度
  4. 配置 LVGL 的心跳时间
    在计时器或任务重每 x 毫秒调用一次 lv_tick_inc(x) 函数( x 应该在 1 ~ 10 之间)。
    当然使用 Arduino 环境的可以直接配置 lv_conf.h 文件中的宏 LV_TICK_CUSTOM ,达到目的,原理如下图所示:
    LVGL 入门使用教程

  5. LVGL 库的使用
    在需要使用 LVGL 库相关函数的文件中添加 #include <lvgl.h> 头文件即可

  6. 初始化 LVGL 库
    只需要在使用 LVGL 之前 调用 lv_init() 函数即可

  7. 创建绘制缓冲区
    LVGL 将在缓冲区中渲染图像,然后通过显示驱动的函数将图像发送到显示器
    缓冲区大小可以自由设置,但是建议缓冲区最小为屏幕大小的 1/10,程序如下所示:

    /*------------ 通过静态空间创建缓冲区 ------------*/ #define DISP_BUF_SIZE ((240*320)/10) static lv_disp_draw_buf_t draw_buf;            // 绘制缓冲区的内部图形缓冲区 static lv_color_t buf_1[DISP_BUF_SIZE];        // 缓冲区为屏幕大小的1/10  /* 初始化显示缓冲区 */ lv_disp_draw_buf_init(&draw_buf, buf_1, NULL, DISP_BUF_SIZE);  /*------------ 通过堆空间创建缓冲区 ------------*/ #define DISP_BUF_SIZE ((240*320)/10) static lv_disp_draw_buf_t draw_buf;        // 绘制缓冲区的内部图形缓冲区 static lv_color_t *buf1;                   // 缓冲区1 static lv_color_t *buf2;                   // 缓冲区2  buf1 = (lv_color_t*)heap_caps_malloc(DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA); assert(buf1 != NULL);  buf2 = (lv_color_t*)heap_caps_malloc(DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA); assert(buf2 != NULL);  lv_disp_draw_buf_init(&draw_buf, buf1, buf2, DISP_BUF_SIZE); 

    注意:
    必须保证绘制缓冲区的声明周期,方式可以是全局变量、静态空间、堆空间。
    如果是黑白屏创建一个缓冲区即可,也就是 buf2 = NULL ,彩色屏建议创建两个绘制缓冲区

  8. 注册显示驱动
    通过注册的回调函数,将绘制好的图形通过显示屏驱动进行绘制显示。回调函数会在刷新显示的时候调用

    /* 设置LVGL的显示驱动的结构属性 */ static lv_disp_drv_t disp_drv;              // 显示驱动程序的描述符 lv_disp_drv_init(&disp_drv);                // 初始化句柄,确保所有参数都是默认值 disp_drv.hor_res = MY_DISP_HOR_RES;         // 设置显示器的水平分辨率 disp_drv.ver_res = MY_DISP_VER_RES;         // 设置显示器的垂直分辨率 disp_drv.flush_cb = my_disp_flush;          // 显示驱动的回调函数 disp_drv.draw_buf = &draw_buf;              // 将缓冲区分配给显示器 lv_disp_drv_register(&disp_drv);            // 注册驱动  /**  * @brief 显示回调函数,通过此回调函数将绘制空间的图形传递给显示驱动程序  * @param disp 显示驱动程序的描述符  * @param area 图像需要显示的区域  * @param color_p 描绘后的图形  */ void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) {     uint32_t w = (area->x2 - area->x1 + 1);     uint32_t h = (area->y2 - area->y1 + 1);      tft.startWrite();     tft.setAddrWindow(area->x1, area->y1, w, h);     tft.pushColors(&color_p->full, w * h, true);     tft.endWrite();      /* 反馈显示结果*/     lv_disp_flush_ready(disp); } 
  9. 输入设备驱动
    通过注册的回调函数,将触摸获取的坐标值传递给 LVGL 。此回调函数是由 LVGL 的时间管理进行定时调用的,能否通过终端的形式进行获取我目前还不知道,有了解的朋友望告知一下。

    static lv_indev_drv_t indev_drv;                     // 输入驱动程序的描述符 lv_indev_drv_init(&indev_drv);                       // 初始化 indev_drv.type = LV_INDEV_TYPE_POINTER;              // 设置设备类型 indev_drv.read_cb = touch_read;                      // 输入设备的回调函数 lv_indev_drv_register(&indev_drv);                   // 创建输入设备  /**  * @brief 触摸回调函数,通过此回调函数将触摸获取的坐标传递给 LVGL  * @param indev_driver   * @param data 输入设备的数据  */ void touch_read(lv_indev_drv_t * indev_driver, lv_indev_data_t * data) {     tp = ft6336u.scan();      static int16_t last_x = 0;     static int16_t last_y = 0;      /* 判断屏幕是否被按下 */     bool touched = tp.touch_count;     if (touched)     {         last_x = tp.tp[0].x;         last_y = tp.tp[0].y;         data->state = LV_INDEV_STATE_PRESSED;      }     else {         data->state = LV_INDEV_STATE_RELEASED;     }      /* 将获取的坐标传入 LVGL */     data->point.x = last_x;     data->point.y = last_y; } 
  10. 调用 lv_timer_handler()
    在主while(1) 循环或操作系统任务中每隔几毫秒定期调用lv_timer_handler()。它将重绘屏幕、处理输入设备、动画等

六、界面绘制

  1. 创建界面

    • 方式1
      创建一个空的界面

      lv_obj_t *view_test = lv_btn_create(NULL); 

      注意:新的界面在显示的时候需要通过加载函数,将界面加载到显示器上

      lv_scr_load(view_test); 
    • 方式2:
      在当前活动界面上创建界面,创建完成后会自动加载到显示器上

      lv_obj_t * text_t = lv_btn_create(lv_scr_act()); 
  2. 创建标签

    /**  * @brief 创建一个标签  */ 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 } 
  3. 创建按钮

    /**  * @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_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);                                                   // 对象居中显示         }     }     } 
  4. LGVL 的API
    因为官网并没有详细的 API 文档,所以想找查找详细的 API 只能通过每个组件的头文件进行查看

七、测试程序

main.cpp

#include <Arduino.h> #include <lvgl.h> #include <TFT_eSPI.h> #include <FT6336U.h>  /*------------ 触摸引脚 ------------*/ #define I2C_SDA 4 #define I2C_SCL 15 #define RST_N_PIN 5 #define INT_N_PIN 17  /*------------ 背光通道 ------------*/ #define LCD_BL_PIN 6				// PWD 的 IO 引脚 #define LCD_BL_PWM_CHANNEL 0		// Channel  通道, 0 ~ 16,高速通道(0 ~ 7)由80MHz时钟驱动,低速通道(8 ~ 15)由 1MHz 时钟驱动  /*------------ LVGL ------------*/ #define MY_DISP_HOR_RES 240                                     // 显示屏的宽像素 #define MY_DISP_VER_RES 320                                     // 显示屏的高像素 #define DISP_BUF_SIZE ((MY_DISP_HOR_RES*MY_DISP_VER_RES)/10)  static lv_disp_draw_buf_t draw_buf;                             // 绘制缓冲区的内部图形缓冲区 static lv_color_t buf_1[DISP_BUF_SIZE];                         // 缓冲区为屏幕大小的1/10 static lv_color_t *buf1;                                        // 缓冲区为屏幕大小的1/10 static lv_color_t *buf2;                                        // 缓冲区为屏幕大小的1/10  /*------------ 显示驱动对象 ------------*/ TFT_eSPI tft = TFT_eSPI();  /*------------ 触摸驱动对象 ------------*/ FT6336U ft6336u(I2C_SDA, I2C_SCL, RST_N_PIN, INT_N_PIN);  FT6336U_TouchPointType tp;  /*------------ 测试界面对象 ------------*/ lv_obj_t *label;   /**  * @brief 触摸回调函数,通过此回调函数将触摸获取的坐标传递给 LVGL  * @param indev_driver   * @param data 输入设备的数据  */ void touch_read(lv_indev_drv_t * indev_driver, lv_indev_data_t * data) {     tp = ft6336u.scan();      static int16_t last_x = 0;     static int16_t last_y = 0;      /* 判断屏幕是否被按下 */     bool touched = tp.touch_count;     if (touched)     {         last_x = tp.tp[0].x;         last_y = tp.tp[0].y;         data->state = LV_INDEV_STATE_PRESSED;      }     else {         data->state = LV_INDEV_STATE_RELEASED;     }      /* 将获取的坐标传入 LVGL */     data->point.x = last_x;     data->point.y = last_y; }  /**  * @brief 显示回调函数,通过此回调函数将绘制空间的图形传递给显示驱动程序  * @param disp 显示驱动程序的描述符  * @param area 图像需要显示的区域  * @param color_p 描绘后的图形  */ void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) {     uint32_t w = (area->x2 - area->x1 + 1);     uint32_t h = (area->y2 - area->y1 + 1);      tft.startWrite();     tft.setAddrWindow(area->x1, area->y1, w, h);     tft.pushColors(&color_p->full, w * h, true);     tft.endWrite();      /* 反馈显示结果*/     lv_disp_flush_ready(disp); }  /**  * @brief 初始化显示屏驱动  */ void disp_drv_init(){     /* ------------ 屏幕背光亮度 ------------*/     /* 配置LED PWM通道属性,PWD通道为 0,频率为1KHz */     ledcSetup(LCD_BL_PWM_CHANNEL, 1000, TFT_BL); 	/* 配置LED PWM通道属性 */     ledcAttachPin(LCD_BL_PIN, LCD_BL_PWM_CHANNEL);     ledcWrite(LCD_BL_PWM_CHANNEL, (int)(1 * 255));      /* 初始化显示驱动 */     tft.init();      /* 旋转角度 0、1、2、3 对应 0 、90度、180度、270 */     tft.setRotation(0);     /* 关闭颜色反转 */     tft.invertDisplay(0); }  /**  * @brief 初始化触摸驱动  */ void touch_drv_init(){     ft6336u.begin(); }  void lvgl_init(){     /*------------- 初始化LVGL库 -------------*/     lv_init();      /* 初始化显示缓冲区 */     // lv_disp_draw_buf_init(&draw_buf, buf_1, NULL, DISP_BUF_SIZE);      /*------------- 创建图形绘制缓冲区 -------------*/     buf1 = (lv_color_t*)heap_caps_malloc(DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA);     assert(buf1 != NULL);      buf2 = (lv_color_t*)heap_caps_malloc(DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA);     assert(buf2 != NULL);      lv_disp_draw_buf_init(&draw_buf, buf1, buf2, DISP_BUF_SIZE);       /*------------- 设置LVGL的显示设备 -------------*/     static lv_disp_drv_t disp_drv;              // 显示驱动程序的描述符     lv_disp_drv_init(&disp_drv);                // 初始化句柄,确保所有参数都是默认值     disp_drv.hor_res = MY_DISP_HOR_RES;             // 设置显示器的水平分辨率     disp_drv.ver_res = MY_DISP_VER_RES;            // 设置显示器的垂直分辨率     disp_drv.flush_cb = my_disp_flush;          // 显示驱动的回调函数     disp_drv.draw_buf = &draw_buf;              // 将缓冲区分配给显示器     lv_disp_drv_register(&disp_drv);            // 注册驱动      /*------------- 设置LVGL的输入设备 -------------*/     // static lv_indev_t *indev_cor;     static lv_indev_drv_t indev_drv;                     // 输入驱动程序的描述符     lv_indev_drv_init(&indev_drv);                       // 初始化     indev_drv.type = LV_INDEV_TYPE_POINTER;              // 设置设备类型     indev_drv.read_cb = touch_read;                      // 输入设备的回调函数     lv_indev_drv_register(&indev_drv);                   // 创建输入设备 }  /**  * @brief 创建标签  */ void lvgl_lable_test(){     /* 创建一个标签 */     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_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);                                                   // 对象居中显示         }     }     }  void setup() { 	Serial.begin(115200);     Serial.println("mian.cpp-> 程序初始化......");      /* 初始化显示驱动 */ 	disp_drv_init();      /* 初始化触摸驱动 */     touch_drv_init();  	/* lvgl 初始化 */     lvgl_init();      /* 加载标签 */     lvgl_lable_test();     /* 加载按钮 */     lvgl_button_test(); }  void loop() {     lv_timer_handler(); 	delay(5); }  

八、测试

LVGL 入门使用教程

发表评论

评论已关闭。

相关文章