显示驱动编程避坑:解决SPI屏幕刷新时出现的花屏、残影问题
SPI小屏花屏、残影基本不是“屏坏了”,而是命令/数据时序、GRAM窗口、缓冲同步三处没接对。花屏是数据写错位置或写串了;残影是旧像素没被覆盖,或屏还在扫描时你就改了GRAM。
一、DC/CS时序错=花屏头号原因
SPI LCD只有四根线时,屏靠DC判断“当前字节是命令还是数据”。DC在传输途中跳变,ST7789这类控制器会把数据当命令、命令当数据,初始化直接乱掉。
规则很简单:
发命令:CS拉低 → DC拉低 → 发字节
发数据:CS保持低 → DC拉高 → 连续发
DC切换必须在SPI空闲期完成,别在SCK跳动时翻
一次连续刷屏不要每字节都切CS,开始拉低、结束拉高即可
void lcd_write_cmd(uint8_t c){
LCD_CS_LOW();
LCD_DC_CMD(); // DC=0
spi_tx(&c,1);
}
void lcd_write_data(uint8_t *p, int len){
LCD_DC_DATA(); // DC=1,CS别动
spi_tx(p,len);
}
void lcd_end(void){ LCD_CS_HIGH(); }
上电瞬间DC别悬空,加10k下拉;RST拉低≥10ms再释放,等120ms再发Sleep Out→Display On,否则初始化不完整。
二、地址窗口设错=错位花屏
往GRAM写像素前必须先设窗口,否则写入指针按屏内部默认范围走,坐标一偏就是斜条纹。
void lcd_set_window(uint16_t x0,uint16_t y0,uint16_t x1,uint16_t y1){
lcd_write_cmd(0x2A); // CASET
lcd_write_data((uint8_t[]){
x0>>8,x0&0xFF,x1>>8,x1&0xFF},4);
lcd_write_cmd(0x2B); // RASET
lcd_write_data((uint8_t[]){
y0>>8,y0&0xFF,y1>>8,y1&0xFF},4);
lcd_write_cmd(0x2C); // RAMWR
}
坑点:
结束坐标是闭区间,128宽屏是0~127不是0~128
MADCTL方向换了,窗口坐标也要跟着换
RGB/BGR寄存器配错→颜色错乱不算花屏也算“伪花屏”
三、DMA刷屏:完成前别碰缓冲
用DMA搬帧数据,CPU才能脱手。但最典型的花屏是——DMA没发完,主循环已经开始改下一帧缓冲,甚至CS提前拉高截断传输。
static uint8_t fb_a[FB_SIZE], fb_b[FB_SIZE];
static uint8_t *front = fb_a, *back = fb_b;
static volatile uint8_t dma_busy = 0;
void lcd_flush(void){
while(dma_busy);
uint8_t *t = front; front = back; back = t; // 换指针
dma_busy = 1;
lcd_set_window(0,0,W-1,H-1);
LCD_DC_DATA(); LCD_CS_LOW();
HAL_SPI_Transmit_DMA(&hspi, front, FB_SIZE);
}
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef*h){
if(h->Instance==SPI1){
LCD_CS_HIGH(); // 只有这里能放CS
dma_busy = 0;
}
}
带Cache的内核还要在DMA前Clean缓冲:
SCB_CleanDCache_by_Addr(front, FB_SIZE);
单缓冲直接写GRAM=半旧半新撕裂;双缓冲才是正解。
四、残影:不是清屏命令没用,是区域没覆盖
残影常见三种:
旧文字没擦:只在新内容位置画,没把旧矩形填背景色
伪清屏:发了填充色但没重设0x2A/0x2B窗口,只刷了部分GRAM
电荷残留:下电不按序关显示/背光,液晶恢复不全
正确局部刷新:
void lcd_clear_rect(uint16_t x,uint16_t y,uint16_t w,uint16_t h,uint16_t bg){
lcd_set_window(x,y,x+w-1,y+h-1);
for(int i=0;i<w*h;i++) lcd_write_pixel(bg);
}
动哪里清哪里,别整屏狂刷——SPI带宽本来就紧。
字体透明、叠加图层时更容易留尾巴:每次重画前先把脏矩形填底再画新内容。
五、TE同步(带TE脚的屏才用)
屏内部扫描GRAM和MCU写GRAM撞上,就会出现一条水平撕裂线。带TE引脚的控制器发0x35开TE,MCU等TE中断再启动DMA写GRAM。
void TE_IRQHandler(void){
if(need_flush) lcd_flush();
}
注意:TE只保证“起点错开”,整帧传输时间超过安全窗仍会追尾,所以高分辨率屏别盲目整屏刷,改局部窗口。
六、排错顺序
偶发花屏先降SPI时钟:降了就好=信号完整性/超频;不变=DC或窗口逻辑错。
白屏/颜色错:DC、MADCTL、RGB/BGR
斜条纹:CASET/RASET终点算错
半幅新旧:单缓冲、DMA没等完成
切换留影:脏矩形没清、旧区域边界算小
长期残影:VCOM、上下电时序、5V误供3.3V屏
电源上VCC/GND并100nF+10μF,SCK/MOSI别超10cm飞线,CS上拉,DC下拉,复位用GPIO别只靠RC。
七、落地原则
SPI LCD稳定刷屏就三条底线:
DC/CS切换干净、地址窗口每次重设、DMA完成前缓冲不动。
花屏查时序和窗口,残影查覆盖区和上下电;带TE就等TE,不带TE就用双缓冲+局部刷新。别在中断里做绘图,别在DMA没完就swap,别把清屏当成“发个颜色指令”就完事——GRAM指针和矩形区域都得自己管。





