STM32 驱动 TFT 屏 + LVGL 实战:从裸刷花屏到真彩 UI,内存和刷新是两个坎

📅 2026/8/20 13:24:44
STM32 驱动 TFT 屏 + LVGL 实战:从裸刷花屏到真彩 UI,内存和刷新是两个坎
做温控器面板那会儿先用数码管加按键凑合后来想上真彩 TFT 画个温度曲线加触摸按钮。裸操 ST7789SPI 一个像素一个像素往里灌屏闪得跟迪厅灯球似的手指戳上去半秒才反应、还经常点歪。后来把 LVGL 搬上来才明白画界面和刷像素是两码事。这一篇讲怎么把屏点亮、把 GUI 跑顺以及两个最要命的坎内存和刷新。原理人话版TFT 屏背后有个驱动芯片ST7789、ILI9341、SSD1309 这类它自己内部有一块 GRAM显示 RAM。MCU 不用去管每一行怎么扫只要通过 SPI 或 8080 并行口把颜色写进 GRAM驱动芯片自己负责把像素刷到玻璃上。颜色用 RGB565一个像素 2 字节红占 5 位、绿占 6 位、蓝占 5 位。比 RGB888 省三分之一带宽肉眼看够用。显存你这边要维护的那块 buffer大小等于屏宽乘屏高乘 2 字节。算笔账240 乘 240 的小屏是 115200 字节约 112KB320 乘 240 是 153600约 150KB480 乘 272 是 261120约 255KB。STM32F103 总共才 20KB SRAMF407 也才 128KB整屏根本塞不下。这就是第一道坎显存放哪。LVGL 是个开源 GUI 库。你建对象标签、按钮、滑块、图表它在你给的一块 buffer 上画你定时调lv_timer_handler()让它处理动画和重绘画完的脏区域通过flush_cb回调交给你搬到屏上。你不用关心像素坐标写 UI 像搭积木。CubeMX 怎么配以 SPI 接口 ST7789 1.3 寸 240x240 为例屏走 SPI前面讲过的那套全双工、软件片选。除了 SCK/MOSI/MISOST7789 还要两根控制脚DC数据/命令选择DC0 发命令DC1 发数据。普通 GPIO如 PA2。RES复位拉低复位屏普通 GPIO如 PA3。BL背光拉高屏才亮普通 GPIO如 PA1。有些模块背光直接接 3.3V 常亮。CubeMX 里SPI1 配Full-Duplex Master。ST7789 用 Mode 0CPOL0, CPHA0对照模块手册确认。NSS 选SoftwareCS 脚当普通 GPIO 用屏一般固定选通CS 接地也行。DC/RES/BL 各设一个GPIO_Output。SPI 时钟分频先/8跑通线短再提。不需要 LTDC那是 RGB 并行大屏用的小屏 SPI 足够。代码点亮屏再把 LVGL 喂起来先写最底层发命令、发数据靠 DC 脚区分#defineDC_PINGPIO_PIN_2#defineDC_PORTGPIOA#defineRST_PINGPIO_PIN_3#defineRST_PORTGPIOA#defineBL_PINGPIO_PIN_1#defineBL_PORTGPIOAstaticvoidlcd_write_cmd(uint8_tcmd){HAL_GPIO_WritePin(DC_PORT,DC_PIN,GPIO_PIN_RESET);// DC0 命令HAL_SPI_Transmit(hspi1,cmd,1,100);}staticvoidlcd_write_data(uint8_t*d,uint16_tlen){HAL_GPIO_WritePin(DC_PORT,DC_PIN,GPIO_PIN_SET);// DC1 数据HAL_SPI_Transmit(hspi1,d,len,100);}屏上电先拉复位、发厂家给的初始化序列一堆 write_cmd 加 write_data从模块资料抄voidlcd_init(void){HAL_GPIO_WritePin(RST_PORT,RST_PIN,GPIO_PIN_RESET);HAL_Delay(10);HAL_GPIO_WritePin(RST_PORT,RST_PIN,GPIO_PIN_SET);HAL_Delay(120);lcd_write_cmd(0x11);HAL_Delay(120);// Sleep outlcd_write_cmd(0x3A);uint8_tp[]{0x55};lcd_write_data(p,1);// 16bit RGB565// 其余按模块手册初始化序列补齐lcd_write_cmd(0x29);// Display onHAL_GPIO_WritePin(BL_PORT,BL_PIN,GPIO_PIN_SET);// 开背光}LVGL 这边核心是三样buffer、flush 回调、心跳。#defineTFT_W240#defineTFT_H240// 整屏放不下就开部分缓冲例如 10 行staticlv_color_tdisp_buf[TFT_W*10];// 240*10*2 4800 字节省内存voidmy_flush_cb(lv_disp_drv_t*drv,constlv_area_t*area,lv_color_t*color_p){// 把 color_p 指向的脏区域通过 SPI 搬到屏的 GRAMlcd_set_window(area-x1,area-y1,area-x2,area-y2);// 设写入窗口uint32_tsize(area-x2-area-x11)*(area-y2-area-y11);HAL_GPIO_WritePin(DC_PORT,DC_PIN,GPIO_PIN_SET);HAL_SPI_Transmit(hspi1,(uint8_t*)color_p,size*2,100);// 每个像素 2 字节lv_disp_flush_ready(drv);// 必须通知 LVGL 刷完了否则它不再派活}intmain(void){HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_SPI1_Init();lcd_init();lv_init();staticlv_disp_draw_buf_tbuf;lv_disp_draw_buf_init(buf,disp_buf,NULL,TFT_W*10);staticlv_disp_drv_tdrv;lv_disp_drv_init(drv);drv.flush_cbmy_flush_cb;drv.draw_bufbuf;drv.hor_resTFT_W;drv.ver_resTFT_H;lv_disp_drv_register(drv);// 一个简单的 UI标签 按钮lv_obj_t*labellv_label_create(lv_scr_act());lv_label_set_text(label,Temp: 26.4 C);lv_obj_align(label,LV_ALIGN_CENTER,0,-20);lv_obj_t*btnlv_btn_create(lv_scr_act());lv_obj_align(btn,LV_ALIGN_CENTER,0,30);lv_obj_t*bllv_label_create(btn);lv_label_set_text(bl,Tap me);while(1){lv_tick_inc(5);// 每 5ms 喂一次心跳用 SysTick 或定时器更准lv_timer_handler();// 必须周期调用界面才动、按钮才有反应HAL_Delay(5);}}要点lv_timer_handler()必须周期调用推荐 1 到 5ms 一次界面才动flush_cb刷完必须调lv_disp_flush_ready否则 LVGL 以为你还在忙、不再派新帧。10 个真实翻车点1. 显存放不下整屏 buffer 开在栈上爆 RAM。头号坎。240 乘 240 整屏 112KB你lv_color_t full[TFT_W*TFT_H]开在 main 里栈直接炸 HardFault。小 RAM 芯片用 LVGL 的部分缓冲开 10 行、反复刷或外挂 SRAM或用 RGB 接口大屏自带显存。先算账整屏字节 宽乘高乘 2。2. DC 脚搞反。DC0 是命令、DC1 是数据你写反了屏把颜色当命令、把命令当颜色初始化全乱、白屏。每发命令前 DC0、发数据前 DC1焊死。3. SPI 模式不对。ST7789 多数要 Mode 0CPOL0 CPHA0少数模块 Mode 3。你按默认 Mode 3 配命令收错、屏不认。翻模块手册 SPI Mode 那行和前面 SPI 那套一样照配。4. 刷新率上不去、动画花屏。SPI 只有几百 k 到几 MHz整屏 112KB 逐个字节刷一帧要好几十毫秒动画卡成 PPT。提速三招用 DMA 搬不占 CPU、提高 SPI 时钟线长有限别超、用部分缓冲只刷脏区。5. lv_timer_handler 没周期调用。你只在初始化调一次界面永远不动、按钮点了没反应。必须在主循环或定时器中断里周期调间隔 1 到 5ms。忘了它LVGL 等于没启动。6. flush_cb 里忘了 lv_disp_flush_ready。LVGL 派给你一帧你搬完屏没通知它它以为还在忙再也不派新活界面冻住。刷完最后一行立刻lv_disp_flush_ready(drv)。7. 颜色格式、字节序不对。屏要 RGB565你发 RGB888 或高低字节反小端大端颜色怪、发红发绿。初始化里0x3A命令设0x55选 16 位且确认你的lv_color_t和屏的 RGB565 字节序一致。8. 栈溢出。flush_cb 或 timer 回调里干重活解析图片、算曲线栈爆 HardFault。回调里只做搬屏这件轻活重活丢主循环或任务里。9. 触摸没接、没校准点了没反应。屏亮了但手指戳没反应电阻屏要 XPT2046 加校准点四点对齐电容屏走 I2C 中断。LVGL 的 indev 回调把坐标喂给它坐标反了就点左上跑到右下。10. 背光没开或常亮烧屏。BL 脚没拉高屏全黑你以为初始化失败常亮又费电、OLED 类有烧屏风险。用 PWM 调背光亮度不用时关。完整例程骨架intmain(void){HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_SPI1_Init();lcd_init();lv_init();// buffer drv 注册见上build_ui();// 建标签/按钮/图表while(1){lv_tick_inc(5);lv_timer_handler();HAL_Delay(5);}}适合带真彩界面的嵌入式产品温控面板、仪器显示、手持设备、需要按钮/滑块/曲线这类交互的终端。不适合纯字符显示数码管或 0.96 寸 OLED 更省也不适合 SRAM 极小又不想外挂显存的芯片那种只能把整屏缓冲放外部 SRAM或用单色屏。铁律三条显存大小先算账宽乘高乘 2放不下用部分缓冲、DC 脚命令与数据严格区分、lv_timer_handler 与 flush_cb 里的 lv_disp_flush_ready 必须都在。把这三条焊死TFT 加 LVGL 基本一次跑通。屏不是刷像素的是喂 GUI 的。你管界面LVGL 管像素分工清楚了真彩 UI 才顺。