示例代码
#include <Arduino_GFX_Library.h>
#include <lvgl.h>
#include <Ticker.h>
//定时器
Ticker lvglTicker;
// 定时器回调函数
void lvglTickCallback() {
lv_tick_inc(1); // 每毫秒增加一次
}
// ESP12E 引脚定义
#define TFT_CS 15 // GPIO15
#define TFT_DC 4 // GPIO4
#define TFT_RST 2 // GPIO2
#define TFT_BL 5 // GPIO5(背光控制)
// 使用硬件SPI
Arduino_DataBus *bus = new Arduino_ESP8266SPI(TFT_DC, TFT_CS);
Arduino_GFX *gfx = new Arduino_ILI9488_18bit(bus, TFT_RST); // 使用18位模
/* LVGL配置 */
#define LV_HOR_RES_MAX 480 // 水平分辨率
#define LV_VER_RES_MAX 320 // 垂直分辨率
#define LV_BUF_SIZE (LV_HOR_RES_MAX * 5) // 缓冲区大小(行数)
static lv_disp_draw_buf_t draw_buf; // LVGL绘制缓冲区
static lv_color_t buf1[LV_BUF_SIZE]; // 第一缓冲区
static lv_color_t buf2[LV_BUF_SIZE]; // 第二缓冲区(双缓冲)
void initGFX(){
// 复位显示屏
pinMode(TFT_RST, OUTPUT);
digitalWrite(TFT_RST, LOW);
delay(100);
digitalWrite(TFT_RST, HIGH);
delay(200);
// 初始化背光
pinMode(TFT_BL, OUTPUT);
digitalWrite(TFT_BL, HIGH); // 保持常亮
// 初始化显示屏
if (!gfx->begin(27000000)) { // 27MHz SPI速度
Serial.println("Display init failed!");
while(1); // 停止执行
}
// 设置横屏模式
gfx->setRotation(1); // 1表示90度旋转,通常是横屏
Serial.println("Display init success!");
}
/* 显示刷新回调函数 */
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);
//绘图
//开始绘制以area->x1, area->y1为起点,颜色为color_p(这里包含了x1,y1,w,h的所有颜色值),终点为w,h的矩形
gfx->draw16bitRGBBitmap(
area->x1, // x起始位置
area->y1, // y起始位置
(uint16_t *)color_p, // 数据指针
w, // 宽度
h // 高度
);
lv_disp_flush_ready(disp); // 通知LVGL刷新完成
}
void lvgl_init(){
lv_init();
lv_disp_draw_buf_init(&draw_buf, buf1, buf2, LV_BUF_SIZE);
/* 注册显示驱动 */
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = LV_HOR_RES_MAX;//设置LVGL display的分辨率
disp_drv.ver_res = LV_VER_RES_MAX;
disp_drv.flush_cb = my_disp_flush; // 注册刷新回调
disp_drv.draw_buf = &draw_buf;
lv_disp_drv_register(&disp_drv);
}
lv_obj_t *label;
void setup(void) {
initGFX();
// testGFX();
lvgl_init();
// 启动定时器,每1ms调用一次
lvglTicker.attach_ms(1, lvglTickCallback);
label = lv_label_create(lv_scr_act());
}
void loop()
{
static char buffer[32];
sprintf(buffer, "Hello World! counter = %d", counter);
lv_label_set_text(label, buffer);
counter++;
lv_timer_handler();
Serial.printf("Counter: %d\n", counter);
}
1.初始化显示器。
GFX初始化主要是为了给lvgl提供一个绘图函数,lvgl需要一个绘图函数。这时候lvgl会接管调用显示驱动。也就是我们主动调用绘图函数在屏幕上打点画图:(交给lvgl去做了)
//GFX 自己在屏幕上绘图
gfx->fillScreen(BLACK);//将屏幕设置成黑色
gfx->setTextColor(WHITE, BLACK);//字体颜色白色,背景黑色
gfx->setTextSize(2, 2);//字体大小
gfx->setCursor(30, 140);//所在位置
gfx->println("ILI9488 3.5\"");//开始打印字体在屏幕上
可以给lvgl提供一个打点函数,也可以提供一个画矩形的函数
void Arduino_GFX::writePixel(int16_t x, int16_t y, uint16_t color)
void Arduino_GFX::draw16bitRGBBitmap(int16_t x, int16_t y,
uint16_t *bitmap, int16_t w, int16_t h)
2.设置lvgl绘制缓冲区
2.1. 为什么需要设置绘制缓冲区?
LVGL是一个嵌入式图形库,通常运行在资源有限的微控制器上。它采用了一种部分刷新的策略,即每次只刷新屏幕的一部分,而不是整个屏幕(全屏刷新会消耗大量资源且速度慢)。
为了高效地管理图形绘制,LVGL使用一个或多个缓冲区(称为绘制缓冲区)来暂存即将绘制到屏幕上的像素数据。这样,图形绘制操作可以在内存中完成,然后一次性将数据发送到显示设备,提高效率。
如果没有缓冲区,每次绘制操作(比如画一个点)都可能直接操作显示设备(通过接口如SPI、并行接口等),这样效率极低,因为每次操作都会有通信开销。
2.2.缓冲区的作用
临时画布:存储即将渲染到屏幕的像素数据。
图形加速:在内存中高效完成复杂绘制(如透明度混合、渐变)。
同步控制:协调绘制与屏幕刷新时序,确保画面稳定。
工作原理:LVGL在buf1渲染时,GPU同时从buf2读取数据到屏幕,交替使用避免视觉撕裂
缓冲区大小:480*5=2400像素,每次渲染5行(平衡性能与内存)
static lv_disp_draw_buf_t draw_buf; // LVGL绘制缓冲区
static lv_color_t buf1[LV_BUF_SIZE]; // 第一缓冲区
static lv_color_t buf2[LV_BUF_SIZE]; // 第二缓冲区(双缓冲)
3.lvgl初始化
void lvgl_init(){
lv_init(); // 核心库初始化
lv_disp_draw_buf_init(&draw_buf, buf1, buf2, LV_BUF_SIZE); // 双缓冲配置
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv); // 驱动结构体初始化
disp_drv.hor_res = 480; // 设置水平分辨率
disp_drv.ver_res = 320; // 设置垂直分辨率
disp_drv.flush_cb = my_disp_flush; // 注册刷新回调
disp_drv.draw_buf = &draw_buf; // 绑定缓冲区
lv_disp_drv_register(&disp_drv); // 注册驱动
}
| 组件 | 作用 | 代码示例 |
|---|---|---|
| lv_init() | 初始化LVGL内核数据结构(内存管理/定时器等) | lv_init() |
| 绘制缓冲区 | 双缓冲机制防止撕裂 | lv_disp_draw_buf_init(&draw_buf, buf1, buf2, 2400) |
| 显示驱动结构体 | 承载显示设备的配置参数 | lv_disp_drv_t disp_drv |
| 分辨率设置 | 定义物理屏幕尺寸 | disp_drv.hor_res = 480 |
| 刷新回调注册 | 连接LVGL渲染引擎与硬件驱动程序 | disp_drv.flush_cb = my_disp_flush |
| 驱动注册 | 将配置注入LVGL系统 | lv_disp_drv_register(&disp_drv) |
4.回调函数
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) {
// 1. 计算刷新区域尺寸
uint32_t w = area->x2 - area->x1 + 1;
uint32_t h = area->y2 - area->y1 + 1;
// 2. 调用硬件加速绘图
//开始绘制以area->x1, area->y1为起点,颜色为color_p(这里包含了x1,y1,w,h的所有颜色值),终点为w,h的矩形
gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)color_p, w, h);
// 3. 通知LVGL刷新完成
lv_disp_flush_ready(disp);
}
- 接收LVGL传递的矩形区域(
area)和像素数据(color_p) - 通过
draw16bitRGBBitmap将RGB565数据写入显存 - 必须调用
lv_disp_flush_ready()解除LVGL阻塞
简单来说就是,lvgl要开始画图了。画的区域是x,y,w,h这个区域,颜色值为color。然后传递给GFX硬件绘图驱动去绘图。接着绘图完成之后调用lv_disp_flush_ready()解除LVGL阻塞。
5.*手动给lvgl定时器时钟。更新这个内部时钟计数器
#include <Ticker.h>
Ticker lvglTicker;
// 定时器回调函数
void lvglTickCallback() {
lv_tick_inc(1); // 每毫秒增加一次
}
// 启动定时器,每1ms调用一次
lvglTicker.attach_ms(1, lvglTickCallback);
lv_tick_inc(1);
这个必须要的,如果不给lvgl更新时钟计数器,lvgl所有工作都不能正常运行。
/手动提供LVGL tick //可以使用其他时钟源在#define LV_TICK_CUSTOM 0中开启
lv_timer_handler()
LVGL调度函数,发现需要重绘,安排刷新,发现LVGL定时器完成处理定时回调,分发用户输入事件的回调。