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
二、项目搭建
资源库下载
这里我使用的驱动都是从 GitHub 上下载,有经验的小伙伴也可以自己写驱动程序- 屏驱动程序: TFT-eSPI
- 触摸驱动程序: Arduino-FT6336U
- LVGL
工程文件
将下载的 TFT-eSPI、Arduino-FT6336U、LVGL,放在项目的lib文件下,如下图所示:
在 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
配置显示器驱动
在路径TFT_eSPI/User_Setup.h,中找到User_Setup.h文件,配置显示屏的驱动,不知道怎么使用 TFT-eSPI 的小伙伴可以看我之前的笔记TFT-eSPI入门使用教程。创建对象
TFT_eSPI tft = TFT_eSPI()
TFT_eSPI tft = TFT_eSPI(320,240) // 在创建对象的时候设置屏幕尺寸
注意:记得加载头文件
#include <TFT_eSPI.h>
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
触摸驱动比较简单,不需要复杂的配置,只需要在初始化的时候传入引脚即可
触摸引脚的宏定义
#define I2C_SDA 4
#define I2C_SCL 15
#define RST_N_PIN 5
#define INT_N_PIN 17
创建对象
FT6336U ft6336u(I2C_SDA, I2C_SCL, RST_N_PIN, INT_N_PIN);
FT6336U_TouchPointType tp;
初始化
ft6336u.begin();
五、LVGL使用
这里我整理了一些 LVGL 的学习资料,需要的可以了解一下 LVGL学习资料
下载 LVGL
从GitHub 中下载 或者克隆 LVGL 资源库在项目中添加 LVGL 资源库
将下载的库文件复制到项目的lib路径下,建议将资源的文件名改为lvgl
注意:名字不一样时,c_cpp_properties.json文件中添加的路径也会变化重命名 lv_conf_template.h 文件
- 将
lvgl/lv_conf_template.h
文件重命名为lv_conf.h
- 将文件中的第一个
#if 0
改为#if 1
- 通过配置
LV_COLOR_DEPTH
宏,设置显示屏的颜色深度
- 将
配置 LVGL 的心跳时间
在计时器或任务重每x
毫秒调用一次lv_tick_inc(x)
函数(x
应该在 1 ~ 10 之间)。
当然使用 Arduino 环境的可以直接配置lv_conf.h
文件中的宏LV_TICK_CUSTOM
,达到目的,原理如下图所示:
LVGL 库的使用
在需要使用 LVGL 库相关函数的文件中添加#include <lvgl.h>
头文件即可初始化 LVGL 库
只需要在使用 LVGL 之前 调用lv_init()
函数即可创建绘制缓冲区
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);
注意:必须保证
绘制缓冲区
的声明周期,方式可以是全局变量、静态空间、堆空间。注册显示驱动
通过注册的回调函数,将绘制好的图形通过显示屏驱动进行绘制显示。回调函数会在刷新显示的时候调用/* 设置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);
}
输入设备驱动
通过注册的回调函数,将触摸获取的坐标值传递给 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;
}
调用
lv_timer_handler()
在主while(1) 循环或操作系统任务中每隔几毫秒定期调用lv_timer_handler()。它将重绘屏幕、处理输入设备、动画等
六、界面绘制
创建界面
方式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());
创建标签
/**
* @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
}
创建按钮
/**
* @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);
}
八、测试
LVGL 入门使用教程的更多相关文章
- Angular2入门系列教程7-HTTP(一)-使用Angular2自带的http进行网络请求
上一篇:Angular2入门系列教程6-路由(二)-使用多层级路由并在在路由中传递复杂参数 感觉这篇不是很好写,因为涉及到网络请求,如果采用真实的网络请求,这个例子大家拿到手估计还要自己写一个web ...
- Angular2入门系列教程6-路由(二)-使用多层级路由并在在路由中传递复杂参数
上一篇:Angular2入门系列教程5-路由(一)-使用简单的路由并在在路由中传递参数 之前介绍了简单的路由以及传参,这篇文章我们将要学习复杂一些的路由以及传递其他附加参数.一个好的路由系统可以使我们 ...
- Angular2入门系列教程5-路由(一)-使用简单的路由并在在路由中传递参数
上一篇:Angular2入门系列教程-服务 上一篇文章我们将Angular2的数据服务分离出来,学习了Angular2的依赖注入,这篇文章我们将要学习Angualr2的路由 为了编写样式方便,我们这篇 ...
- Angular2入门系列教程4-服务
上一篇文章 Angular2入门系列教程-多个组件,主从关系 在编程中,我们通常会将数据提供单独分离出来,以免在编写程序的过程中反复复制粘贴数据请求的代码 Angular2中提供了依赖注入的概念,使得 ...
- ASP.NET Aries 入门开发教程7:DataGrid的行操作(主键操作区)
前言: 抓紧勤奋,再接再励,预计共10篇来结束这个系列. 上一篇介绍:ASP.NET Aries 入门开发教程6:列表数据表格的格式化处理及行内编辑 本篇介绍主键操作区相关内容. 1:什么时候有默认的 ...
- ASP.NET Aries 入门开发教程6:列表数据表格的格式化处理及行内编辑
前言: 为了赶进度,周末也写文了! 前几篇讲完查询框和工具栏,这节讲表格数据相关的操作. 先看一下列表: 接下来我们有很多事情可以做. 1:格式化 - 键值的翻译 对于“启用”列,已经配置了格式化 # ...
- ASP.NET Aries 入门开发教程4:查询区的下拉配置
背景: 今天去深圳溜达了一天,刚回来,看到首页都是微软大法好,看来离.NET的春天就差3个月了~~ 回到正题,这篇的教程讲解下拉配置. 查询区的下拉配置: 1:查询框怎么配置成下拉? 在配置表头:格式 ...
- 2013 duilib入门简明教程 -- 第一个程序 Hello World(3)
小伙伴们有点迫不及待了么,来看一看Hello World吧: 新建一个空的win32项目,新建一个main.cpp文件,将以下代码复制进去: #include <windows.h> #i ...
- 2013 duilib入门简明教程 -- 部分bug (11)
一.WindowImplBase的bug 在第8个教程[2013 duilib入门简明教程 -- 完整的自绘标题栏(8)]中,可以发现窗口最大化之后有两个问题, 1.最大化按钮的样式 ...
随机推荐
- Golang仿云盘项目-2.2 保留文件元信息
本文来自博客园,作者:Jayvee,转载请注明原文链接:https://www.cnblogs.com/cenjw/p/16459817.html 目录结构 E:\goproj\FileStorage ...
- SVM简要介绍
SVM 支持向量机(SVM),是一个用于解决二分类问题的有监督机器学习模型. 1.SVM的两个优点 更高的速度 在有一定的样本数量支持下(成千上万张),具有比其他模型有更好的效果 2.SVM的工作过程 ...
- 最强人工智能 OpenAI 极简教程
大家好哇,新同学都叫我张北海,老同学都叫我老胡,其实是一个人,只是我特别喜欢章北海这个<三体>中的人物,张是错别字. 上个月安利了一波:机器学习自动补全代(hán)码(shù)神器,然后就 ...
- gerrit系统如何配置访问控制
. 版本:v0.3 作者:河东西望 日期:2022-7-13 . 目录 1 关键概念 2 需求场景 3 配置策略 gerrit系统的上手使用有两个难点: 部署repo仓库. 配置访问控制. 想要上手使 ...
- Java---注解与反射
前言 近期在学习SSM框架的过程中发现在SSM框架中大量用到了反射与注解的知识,要想学好SSM框架,必须将注解与反射熟记于心,尤其是对Java反射机制的理解. 对于我这种记性不好的人来说"基 ...
- MPI简谈
MPI简谈 MPI是分布式内存系统,区别于OpenMP和Pthreads的共享内存系统.MPI是一种基于消息传递的并行编程技术,是如今最为广泛的并行程序开发方法. MPI前世今生 MPI(Messag ...
- Odoo 14 Action URL 生成
from werkzeug.urls import url_encode url = '/web#%s' % url_encode({ 'action': 'hr.plan_wizard_action ...
- linux-0.11分析:boot文件 setup.s 第二篇随笔
boot文件 setup.s 第二篇随笔 参考 [github这个博主的][ https://github.com/sunym1993/flash-linux0.11-talk ] 中断获取光标的位置 ...
- 成为 Apache 贡献者,从提交第一个简单 PR 开始!
开源之路,PR 走起 ! ---全球最大同性交友社区 1 fork 以下实例以 incubator-dolphinscheduler 海豚调度为例进行操作 从远端仓库* https://github. ...
- 【AGC】引导用户购买提升用户留存率
借助AGC的云数据库.云托管.应用内消息.App Linking等服务,您可以给不同价值用户设置不同的优惠套餐活动,引导用户持续购买,增强用户黏性.判断用户价值,发送营销短信,引导用户参与营销活动,提 ...