要玩转ESP32和ST7789屏幕的组合,首先得把硬件环境搭建好。我建议直接从淘宝购买现成的ESP32开发板和ST7789驱动的240x320 TFT屏幕模块,价格通常在50元以内就能搞定全套。这种屏幕一般自带SPI接口,接线非常方便,只需要连接以下6根线:
这里有个坑我踩过:有些便宜的屏幕模块标注的引脚顺序可能和实际不符。第一次使用时最好用万用表测试下VCC和GND,接反了可能会烧毁屏幕。我有个朋友就因此损失了一块屏幕,所以特别提醒大家注意。
软件环境方面,推荐使用Arduino IDE或者PlatformIO。我个人更习惯PlatformIO,因为它对库依赖管理更友好。安装完环境后,需要添加两个关键库:TFT_eSPI和LVGL。在PlatformIO的配置文件中这样添加:
ini复制lib_deps =
bodmer/TFT_eSPI@^2.5.0
lvgl/lvgl@^8.3.0
TFT_eSPI库是连接硬件和高级图形界面的桥梁,它的配置决定了屏幕能否正常工作。首先要在项目目录中找到TFT_eSPI库的User_Setup.h文件,这个文件藏得有点深,通常在.pio/libdeps/你的开发板型号/TFT_eSPI路径下。
关键配置项有这几个:
cpp复制#define ST7789_DRIVER // 指定驱动型号
#define TFT_WIDTH 240 // 屏幕宽度
#define TFT_HEIGHT 320 // 屏幕高度
#define TFT_MOSI 23 // 根据实际接线修改
#define TFT_SCLK 18 // 时钟线引脚
#define TFT_CS 5 // 片选引脚
#define TFT_DC 16 // 数据/命令选择
#define TFT_RST 17 // 复位引脚
#define LOAD_GLCD // 启用基本字体
我遇到过最头疼的问题是颜色显示异常。有次所有显示都变成了反色,调试了半天才发现是屏幕的RGB顺序设置错了。ST7789通常需要这样设置:
cpp复制#define TFT_RGB_ORDER TFT_RGB // 颜色顺序
#define TFT_INVERSION_ON // 启用颜色反转
如果看到显示颜色怪异,可以先尝试注释或取消注释TFT_INVERSION_ON这行。不同批次的屏幕可能需要不同的设置,这点特别容易让人抓狂。
配置好驱动后,建议先用简单的测试程序验证屏幕是否正常工作。TFT_eSPI自带了很多示例,我推荐先运行最基本的图形测试:
cpp复制#include <TFT_eSPI.h>
TFT_eSPI tft = TFT_eSPI();
void setup() {
tft.init();
tft.setRotation(3); // 根据屏幕实际方向调整
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE);
tft.drawString("Hello World!", 50, 100, 4);
}
void loop() {}
这个简单的程序应该会在屏幕中间显示白色文字。如果没显示,先检查以下几点:
旋转方向不对也是个常见问题。setRotation()参数可以是0到3,分别对应不同的旋转角度。我用的屏幕通常需要设置为3才能正常显示。
当基础显示工作正常后,就可以上LVGL了。LVGL是个轻量级嵌入式图形库,能实现类似手机UI的炫酷效果。最新版本的LVGL已经对ESP32有很好的支持。
首先需要在项目中配置LVGL。创建一个lv_conf.h文件,关键配置如下:
cpp复制#define LV_COLOR_DEPTH 16 // 颜色深度
#define LV_HOR_RES_MAX 240 // 水平分辨率
#define LV_VER_RES_MAX 320 // 垂直分辨率
#define LV_TICK_CUSTOM 1 // 使用自定义时钟
#define LV_USE_LOG 1 // 启用日志
然后设置显示驱动接口,这部分需要与TFT_eSPI对接:
cpp复制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((uint16_t *)color_p, w * h, true);
tft.endWrite();
lv_disp_flush_ready(disp);
}
最后是初始化代码:
cpp复制void init_lvgl() {
lv_init();
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[LV_HOR_RES_MAX * 10];
lv_disp_draw_buf_init(&draw_buf, buf, NULL, LV_HOR_RES_MAX * 10);
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = LV_HOR_RES_MAX;
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);
}
现在可以创建真正的图形界面了。LVGL使用类似现代前端框架的组件化设计,我们先从简单的按钮开始:
cpp复制void create_ui() {
lv_obj_t *btn = lv_btn_create(lv_scr_act());
lv_obj_set_size(btn, 100, 50);
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0);
lv_obj_t *label = lv_label_create(btn);
lv_label_set_text(label, "Click me!");
lv_obj_center(label);
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_ALL, NULL);
}
void btn_event_cb(lv_event_t *e) {
lv_event_code_t code = lv_event_get_code(e);
if(code == LV_EVENT_CLICKED) {
static uint8_t cnt = 0;
cnt++;
lv_obj_t *btn = lv_event_get_target(e);
lv_obj_t *label = lv_obj_get_child(btn, 0);
lv_label_set_text_fmt(label, "Clicked: %d", cnt);
}
}
这段代码创建了一个带点击计数的按钮。LVGL的强大之处在于它的动画系统,我们可以轻松添加动画效果:
cpp复制lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_obj_set_y);
lv_anim_set_var(&a, btn);
lv_anim_set_values(&a, 0, 100);
lv_anim_set_time(&a, 500);
lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
lv_anim_set_path_cb(&a, lv_anim_path_bounce);
lv_anim_start(&a);
在ESP32上运行LVGL需要注意性能问题。经过多次实践,我总结出几个关键优化点:
首先是双缓冲配置。前面的例子使用了单缓冲,更高效的方式是使用双缓冲:
cpp复制static lv_color_t buf1[LV_HOR_RES_MAX * 20];
static lv_color_t buf2[LV_HOR_RES_MAX * 20];
lv_disp_draw_buf_init(&draw_buf, buf1, buf2, LV_HOR_RES_MAX * 20);
其次是调整LVGL的刷新率。在lv_conf.h中设置:
cpp复制#define LV_REFR_PERIOD 30 // 刷新周期(ms)
内存管理也很重要。ESP32的RAM有限,建议:
cpp复制#define LV_MEM_SIZE (32 * 1024) // 分配32KB给LVGL
如果项目复杂,可以考虑使用PSRAM(如果开发板支持):
cpp复制#define LV_MEM_CUSTOM 1
void *lv_mem_custom_alloc(size_t size) {
return heap_caps_malloc(size, MALLOC_CAP_SPIRAM);
}
最后是任务调度。建议将LVGL的定时器处理放在单独的RTOS任务中:
cpp复制void lvgl_task(void *pv) {
while(1) {
lv_timer_handler();
vTaskDelay(5 / portTICK_PERIOD_MS);
}
}
xTaskCreate(lvgl_task, "LVGL", 4096, NULL, 2, NULL);
在开发过程中,我遇到过各种奇怪的问题,这里分享几个典型案例:
问题1:屏幕闪烁或撕裂
这通常是刷新率不匹配导致的。解决方法:
问题2:触摸响应延迟
如果使用了触摸屏,可能会遇到延迟问题:
问题3:内存不足崩溃
表现为随机重启或显示异常:
问题4:颜色显示异常
除了前面提到的RGB顺序问题,还可能是:
有个特别隐蔽的bug我花了三天才解决:当WiFi和屏幕同时工作时,显示会出现噪点。最后发现是电源干扰,解决方法是在电源线上加了个100μF的电容。