一、准备资料

开发板: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文件下,如下图所示:

  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 ,达到目的,原理如下图所示:

  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);

    注意:必须保证绘制缓冲区的声明周期,方式可以是全局变量、静态空间、堆空间。

  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); // 对象居中显示
    }
    }
    }

七、测试程序

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);
}

八、测试

最新文章

  1. C3p0连接池配置
  2. HTTP和HTTPS
  3. 多线程更新UITableView时容易导致的问题
  4. Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project LogTest: Compilation failure -&gt; [Help 1]
  5. Vim自动补全神器–YouCompleteMe
  6. BZOJ3309 : DZY Loves Math
  7. hdu 1829 A Bug&#39;s Life(分组并查集(偏移量))
  8. Java字符判断
  9. Java笔记——面向接口编程(DAO模式)
  10. .net抓取网页信息 - Jumony框架使用1
  11. 如何调整iMindMap打印设置
  12. mysql 的replace 和replace in to
  13. angular $watch 一个变量的变化
  14. 【转】Linux C下非特定波特率的配置和使用
  15. Filedset
  16. 06-TypeScript中的表达式
  17. 三级菜单(低端版VS高端版)
  18. 3、python内置类型(0529)
  19. Leetcode 980. 不同路径 III
  20. swift NSdata 转换 nsstring

热门文章

  1. 配置svn,httpd启动报错 Job for httpd.service failed because the control process exited with error code. See &quot;systemctl status httpd.service&quot; and &quot;journalctl -xe&quot; for details.
  2. Modeling Conversation Structure and Temporal Dynamics for Jointly Predicting Rumor Stance and Veracity(ACL-19)
  3. nexus org.sonatype.nexus.bootstrap.jetty.JettyServer - Start failed
  4. 2022-07-15 第六组 润土 Java03数据结构学习笔记
  5. day02 Java_变量
  6. 聊聊 C++ 中的几种智能指针 (上)
  7. .NET 使用自带 DI 批量注入服务(Service)和 后台服务(BackgroundService)
  8. 分享一个WPF 实现 Windows 软件快捷小工具
  9. md 中超链接的解析问题:解析`this.$set()`,`$`前要加空格或转义符 `\`
  10. 国产钡铼分布式IO模块如何与西门子PLC Profinet通讯