嵌入式显示双缓冲编程:彻底消除画面刷新时的撕裂感
撕裂不是屏的问题,是“显示控制器在读数、CPU在写数”撞在同一块显存上。LCD控制器按行扫描前缓冲,CPU中途改了下半屏,屏上就出现“上一帧上半+下一帧下半”的缝。双缓冲的本质是空间换稳定:两块帧缓冲,一块给控制器读,一块给CPU画,画完再在安全时机换指针。
一、单缓冲为什么必撕
uint16_t fb[W*H]; // 唯一显存
draw_ui(fb); // CPU改
lcd_refresh(fb); // 控制器同时读
CPU写Line150时,控制器可能正扫Line80,两条总线访问同一块RAM,扫描线走过的位置就是新旧混合。局部刷新、动画、滑动必出撕裂。
二、双缓冲结构
static uint16_t fb_front[W*H];
static uint16_t fb_back[W*H];
uint16_t *disp = fb_front; // 控制器读
uint16_t *work = fb_back; // CPU画
规则:
所有GUI绘制只写work
控制器/DMA只从disp取数
一帧画完才交换指针
交换时机不对,双缓冲也白搭
三、RGB/LTDC屏:VSYNC里换基址
带LCD控制器的平台(STM32 LTDC、Linux DRM)控制器持续从帧基址搬像素,切换点是垂直消隐期,此时没有有效行在传,换地址安全。
void LCD_IRQHandler(void){
if(LCD->ISR & LCD_ISR_EOF){ // 帧结束/VSYNC
LCD->ISR = LCD_ISR_EOF;
if(swap_pending){
LCD->LAYERS[0].BFCR = (uint32_t)work; // 改层基址
uint16_t *t = disp; disp = work; work = t;
swap_pending = 0;
}
}
}
void flush_frame(void){
draw_ui(work);
swap_pending = 1; // 等下一帧消隐再切
}
注意是改控制器寄存器里的帧基址,不是memcpy两块缓冲。LTDC/DRM硬件双缓冲就是切BFCR/FB地址。
四、SPI屏:等DMA完成再换
SPI TFT没有LCD控制器自刷,屏GRAM靠MCU主动推。这时候“垂直同步”不存在,安全点是DMA Tx Complete。
static uint8_t *active_fb = fb_a;
static uint8_t *render_fb = fb_b;
static volatile uint8_t dma_busy = 0;
void lcd_flush(void){
while(dma_busy); // 上一帧没发完不抢
uint8_t *t = active_fb; active_fb = render_fb; render_fb = t;
dma_busy = 1;
lcd_set_window(0,0,W-1,H-1);
LCD_DC_DATA(); LCD_CS_LOW();
HAL_SPI_Transmit_DMA(&hspi, active_fb, W*H*2);
}
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef*h){
if(h->Instance==SPI1){
LCD_CS_HIGH(); // 只有这里能放CS
dma_busy = 0;
}
}
CS提前拉高=传输截断;flush里提前swap=花屏。
五、LVGL里的双缓冲
LVGL的draw buffer不等于帧缓冲,它是“渲染临时块”。要真双缓冲得:
buf1/buf2都开成整屏大小
render mode用DIRECT或FULL
flush_cb里只把控制器帧基址指向color_p
传输完成调lv_display_flush_ready
void my_flush(lv_display_t*d, const lv_area_t*a, uint8_t*px){
lcd_set_frame_addr(px); // 改LTDC基址
wait_vsync(); // 或等DMA完成
lv_display_flush_ready(d);
}
单缓冲模式LVGL会等flush_ready再画下一笔;双缓冲才能渲染和发送并行。
六、Cache一致性
M7带D-Cache平台,DMA读显存前CPU写的可能还在Cache里没回RAM:
SCB_CleanDCache_by_Addr(work, W*H*2); // 写回
DMA写屏再CPU读(少见)就Invalidate。不处理偶发花屏最难查。
七、为什么“双缓冲了还撕”
最常见三种:
交换时机不对:帧中间直接改基址,等于没缓冲
SPI屏在flush里立即swap:DMA还在发旧front
中断里继续改work:以为换了,其实DMA正读那块
单缓冲当双缓冲:只开一个buf传给LVGL,还是原地画
局部刷新窗口算错:脏矩形覆盖到正在发的区域
正确边界:交换只发生在VSYNC ISR或DMA完成回调里,而且只换指针/基址,不拷贝、不绘制。
八、RAM不够怎么办
240×320 RGB565一帧150KB,双缓冲300KB,F103片内扛不住。
折中方案:
只做局部双缓冲(脏矩形两块小buf)
或单缓冲+DMA,接受静态界面不撕、动画偶尔重刷
或外扩SRAM/PSRAM放第二帧
SPI小屏别硬上整屏双缓冲,局部刷新更划算
九、落地顺序
确认显示架构:LTDC/DRM还是SPI GRAM
开前后两块帧缓冲
绘制只写back
LTDC在EOF中断改基址;SPI在TxCplt换指针
CS/DC时序别在DMA中途动
带Cache平台Clean
LVGL整屏buf+fllush_ready
逻辑分析仪/TE脚验证换帧边界
双缓冲解决撕裂的前提不是“有两块内存”,而是交换发生在显示控制器不看那块内存的时刻。RGB屏等VSYNC,SPI屏等DMA完成,LVGL等flush_ready——时机对了,撕裂立刻没;时机错了,双缓冲只是多占RAM。





