当前位置:首页 > 显示光电 > 显示光电
[导读]撕裂不是屏的问题,是“显示控制器在读数、CPU在写数”撞在同一块显存上。LCD控制器按行扫描前缓冲,CPU中途改了下半屏,屏上就出现“上一帧上半+下一帧下半”的缝。双缓冲的本质是空间换稳定:两块帧缓冲,一块给控制器读,一块给CPU画,画完再在安全时机换指针。


撕裂不是屏的问题,是“显示控制器在读数、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。



本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭