- 在LVGL中实现可变字体(Variable Font)-第一章
- 在LVGL中实现可变字体(Variable Font)-第二章
- 在LVGL中实现可变字体(Variable Font)-第三章
前言
(2025年11月 重制版说明): 这篇文章的初版我曾发布于第三方平台(简书+Bilibili),并累计获得了50,000+ 次阅读 和大量开发者的反馈。 为了提供更好的阅读体验,我对文章排版和部分内容进行了优化,并将其独家发布在此个人博客
我们在第一章中已经实现了模拟器环境下可变字体字重的设置.
是时候掏出你吃灰已久的ESP32了.
本文会使用PlatformIO创建一个全新的项目,直到显示出现上篇文章末尾的动图为止. 如遇到问题,可参考常见问题内解答.
准备工作
软件准备
为了后续内容顺利进行下去,这里需要你安装好VSCode,并在VSCode上安装PlatformIO插件.
硬件准备
| 名称 | 数量 | 备注 | 图例 |
|---|---|---|---|
| ESP32 开发板 | 1 | \ | ![]() |
| 1.54寸LCD | 1 | 驱动ST7789,分辨率240x240 | ![]() |
| 杜邦线若干 | N | \ | ![]() |
创建项目
使用PlatformIO创建一个名为lvgl_with_freetype的项目
创建完毕后目录结构如下:
.
├── include
│ └── README
├── lib
│ └── README
├── platformio.ini
├── src
│ └── main.cpp
└── test
└── README
点亮屏幕
网上充斥着大量点屏教程, 故本文不做过多赘述.
本次驱动依旧使用TFT_eSPI库, 且本次的屏幕分辨率是240x240, Driver IC是ST7789.
所以需要使用TFT_eSPI里面的Setup24_ST7789.h
同时接线变更为
| ESP32引脚名称 | 液晶屏引脚名称 |
|---|---|
| VCC | VCC |
| GND | GND |
| G23 | SDA |
| G18 | SCL |
| G2 | DC |
| G4 | RES |
| GND | CS |
| VCC | BLK |
对应Setup24_ST7789.h里面内容
##define TFT_MISO 19
##define TFT_MOSI 23
##define TFT_SCLK 18
##define TFT_CS -1
##define TFT_DC 2
##define TFT_RST 4
随便写点内容.测试下屏幕的点亮.
##include <Arduino.h>
##include <TFT_eSPI.h> // Hardware-specific library
##include <SPI.h>
TFT_eSPI tft = TFT_eSPI(); // Invoke custom library
void setup() {
Serial.begin(115200); // Set to a high rate for fast image transfer to a PC
tft.init();
tft.setRotation(0);
tft.fillScreen(TFT_BLACK);
}
void loop() {
tft.print("Ready Perfectly");
}

移植LVGL
屏幕点亮以后,就可以开始移植LVGL了.
当前时间为2021.12.19,GitHub上LVGL最新版本是8.1.1-dev
使用命令
git clone https://github.com/lvgl/lvgl.git
获取LVGL后将其复制到lib文件夹下.此时文件目录为
.
├── include
│ └── README
├── lib
│ └── README
│ └── lvgl
│ └── TFT_eSPI
├── platformio.ini
├── src
│ └── main.cpp
└── test
└── README
platformio.ini文件内容
[env:pico32]
platform = espressif32
board = pico32
framework = arduino
monitor_speed = 115200
lib_extra_dirs =
lib/TFT_eSPI
lib/lvgl
修改LVGL配置文件
创建LVGL的配置文件,找到lvgl文件夹内的lv_conf_templat.h,复制一份lv_conf_templat.h并重命名为lv_conf.h,然后打开lv_conf.h
为了使配置文件内容生效,找到第15行(其他版本的lvgl行数可能不在这里,需要自行寻找)
##if 0 /*Set it to "1" to enable content*/
改为
##if 1 /*Set it to "1" to enable content*/
找到第30行,修改颜色顺序
##define LV_COLOR_16_SWAP 0
改为
##define LV_COLOR_16_SWAP 1
找到第49行,启用自定义内存管理
##define LV_MEM_CUSTOM 0
修改为
##define LV_MEM_CUSTOM 1
找到第88行,设置自定义周期函数
##define LV_TICK_CUSTOM 0
修改为
##define LV_TICK_CUSTOM 1
找到第174行,启用LVGL日志功能
##define LV_USE_LOG 1
修改为
##define LV_USE_LOG 1
对接LVGL和TFT_eSPI
按照目录
.
├── include
│ └── README
├── lib
│ └── README
│ └── lvgl
│ └── TFT_eSPI
├── platformio.ini
├── src
│ └── main.cpp
│ └── Port
│ └── lv_port_disp.cpp
│ └── lv_port_disp.h
└── test
└── README
创建lv_port_disp.cpp和lv_port_disp.h
##ifndef LV*PORT_DISP_H*
##define LV*PORT_DISP_H*
##include "TFT_eSPI.h"
##include "lvgl.h"
##define DISP_HOR_RES 240
##define DISP_VER_RES 240
##define DISP_BUF_SIZE (DISP_HOR_RES\*DISP_VER_RES/4)
extern TaskHandle_t handleTaskLvgl;
void Port_Init();
void lv_port_disp_init(TFT_eSPI\* scr);
##endif
##include "lv_port_disp.h"
// 用于初始化完毕后启用LVGL显示的TaskHandle_t
TaskHandle_t handleTaskLvgl;
// lvgl显示驱动
static lv_disp_drv_t disp_drv;
// lvgl更新任务
void TaskLvglUpdate(void\* parameter) {
// 阻塞在此处,直到xTaskNotifyGive
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
for (;;) {
lv_task_handler();
delay(5);
}
}
/\*\*
- @brief 显示初始化
- @param 无
- @retval 无
\*/
void Port_Init() {
static TFT_eSPI screen;
/_ 屏幕初始化 _/
screen.begin();
screen.initDMA(true);
screen.setRotation(0);
screen.fillScreen(TFT_BLACK);
/_ lvgl初始化 _/
lv_init();
lv_port_disp_init(&screen);
printf("lvInitDone\n");
// 在核心2上执行LVGL
xTaskCreatePinnedToCore(TaskLvglUpdate, "LvglThread", 20480, nullptr,
configMAX_PRIORITIES, &handleTaskLvgl, 1);
}
/\*\*
- @brief 自定义打印函数
- @param 无
- @retval 无
*/
void my_print(lv_log_level_t level, const char *file, uint32_t line,
const char *fun, const char *dsc) {
Serial.printf("%s@%d %s->%s\r\n", file, line, fun, dsc);
Serial.flush();
}
/\*\*
- @brief 屏幕刷新回调函数
- @param disp:屏幕驱动地址
- @param area:刷新区域
- @param color_p:刷新缓冲区地址
- @retval 无
*/
static void disp_flush_cb(lv_disp_drv_t *disp, const lv*area_t *area,
lv_color_t _color_p) {
TFT_eSPI \_screen = (TFT_eSPI _)disp->user_data;
int32_t w = (area->x2 - area->x1 + 1);
int32_t h = (area->y2 - area->y1 + 1);
screen->startWrite();
screen->setAddrWindow(area->x1, area->y1, w, h);
screen->pushPixelsDMA((uint16*t *)(&color*p->full), w * h);
screen->endWrite();
lv_disp_flush_ready(disp);
}
/\*\*
- @brief 屏幕初始化
- @param 无
- @retval 无
_/
void lv_port_disp_init(TFT_eSPI_ scr) {
lv*log_register_print_cb(reinterpret_cast<lv_log_print_g_cb_t>(
my_print)); /* register print function for debugging _/
DMA_ATTR static lv_color_t \_lv_disp_buf =
static_cast<lv_color_t _>(heap_caps_malloc(
DISP_BUF_SIZE \* sizeof(lv_color_t), MALLOC_CAP_DMA));
static lv_disp_draw_buf_t disp_buf;
lv_disp_draw_buf_init(&disp_buf, lv_disp_buf, nullptr,
DISP_BUF_SIZE);
/_Initialize the display_/
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = DISP_HOR_RES;
disp_drv.ver_res = DISP_VER_RES;
disp_drv.flush_cb = disp_flush_cb;
disp_drv.draw_buf = &disp_buf;
disp_drv.user_data = scr;
lv_disp_drv_register(&disp_drv);
}
再写个简单例子测试下LVGL能不能运行
##include <Arduino.h>
##include "./Port/lv_port_disp.h"
void setup() {
Serial.begin(115200); // Set to a high rate for fast image transfer to a PC
Port_Init();
lv_obj_t \*label = lv_label_create(lv_scr_act());
lv_label_set_text(label, "Toou.\nAnata wa watashi no masuta ka?");
// 一切就绪, 启动LVGL任务
xTaskNotifyGive(handleTaskLvgl);
}
void loop() {
}
Toou.Anata wa watashi no masuta ka?

施工中,待更新
常见问题
Q:点亮屏幕时候,编译器报找不到TFT_eSPI
A:检查TFT_eSPI是否集成 将TFT_eSPI放置在lib文件夹内,并向platformio.ini文件末尾添加
lib_extra_dirs = lib/TFT_eSPI
Q:在移植LVGL时候,屏幕颜色异常
A:可能与lv_conf.h文件内#define LV_COLOR_16_SWAP 0有关 可以尝试将此处的0改成1,或1改回0
环境:
Espressif 32 (3.4.0) > ESP32 Pico Kit
framework-arduinoespressif32 3.10006.210326 (1.0.6)
tool-esptoolpy 1.30100.210531 (3.1.0)
toolchain-xtensa32 2.50200.97 (5.2.0)
<lvgl> 8.1.1-dev
<TFT_eSPI> 2.3.89
esptool.py v3.1


