示例代码

#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);
}
  1. 接收LVGL传递的矩形区域(area)和像素数据(color_p)
  2. 通过draw16bitRGBBitmap将RGB565数据写入显存
  3. 必须调用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定时器完成处理定时回调,分发用户输入事件的回调。