4、显示驱动移植实战:基于SPI接口的LCD驱动移植、基于RGB接口的LCD驱动移植、基于MIPI DSI接口的驱动移植、驱动性能调优。
显示驱动移植,说白了就是让GUI框架和你的屏幕“对上话”。
我这些年经手过的屏幕接口,少说也有十几种。从最简单的SPI小屏,到高端的MIPI DSI大屏,每种接口都有自己的脾气。今天咱们就把这三种主流接口的移植套路,掰开揉碎了讲清楚。
核心认知:不管接口多复杂,驱动移植的本质就三件事——初始化时序、数据传输通道、帧缓冲管理。抓住这三点,万变不离其宗。
4.1 基于SPI接口的LCD驱动移植
SPI接口的屏幕,最常见的就是那些1.3寸、2.4寸的小彩屏。驱动芯片通常是ST7735、ILI9341之类的。
我个人习惯把SPI驱动移植拆成四步走:
- 硬件初始化——配置GPIO(CS、DC、RST、BL)、SPI时钟和模式。
- 驱动芯片初始化序列——发送一堆寄存器配置命令。
- 实现画点函数——这是GUI最底层的调用。
- 实现区域填充——优化刷屏性能的关键。
我的经验:SPI时钟不是越高越好。有些便宜的屏幕,SPI时钟超过20MHz就容易丢数据。我建议先跑10MHz,稳定了再往上提。
画点函数的实现,核心就是设置坐标窗口,然后写像素数据。以ILI9341为例:
// 设置坐标窗口
void LCD_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
LCD_WriteCmd(0x2A); // 列地址
LCD_WriteData(x0 >> 8); LCD_WriteData(x0 & 0xFF);
LCD_WriteData(x1 >> 8); LCD_WriteData(x1 & 0xFF);
LCD_WriteCmd(0x2B); // 行地址
LCD_WriteData(y0 >> 8); LCD_WriteData(y0 & 0xFF);
LCD_WriteData(y1 >> 8); LCD_WriteData(y1 & 0xFF);
LCD_WriteCmd(0x2C); // 写内存
}
// 画一个像素点
void LCD_DrawPixel(uint16_t x, uint16_t y, uint16_t color) {
LCD_SetWindow(x, y, x, y);
LCD_WriteData(color >> 8);
LCD_WriteData(color & 0xFF);
}
注意:SPI传输数据时,DC引脚的电平决定了传输的是命令还是数据。很多新手在这里翻车——发命令时DC没拉低,屏幕直接不响应。
4.2 基于RGB接口的LCD驱动移植
RGB接口的屏幕,常见于4.3寸、7寸的中大屏。驱动芯片像SSD1963、RA8875这些。
RGB接口和SPI最大的区别是什么?它没有“写命令”这一说。RGB接口只管往GRAM里填像素数据,控制命令走的是I2C或SPI。
移植要点:
- 时序匹配——VSYNC、HSYNC、DCLK、DE这些信号,必须和屏幕数据手册严格对齐。
- GRAM映射——搞清楚屏幕的GRAM起始地址和扫描方向。
- 帧缓冲——RGB接口通常需要外部SRAM或内部大RAM做帧缓冲。
我曾经在一个项目里,因为HSYNC的极性搞反了,屏幕一直花屏。查了两天才发现是数据手册里写的是“负极性”,我默认用了正极性。嗯,这种坑踩过一次就记住了。
| 参数 | 典型值 | 说明 |
|---|---|---|
| VSYNC 周期 | 60 Hz | 帧率,人眼不闪烁的最低要求 |
| HSYNC 周期 | 行同步脉冲宽度 | 通常为几个DCLK周期 |
| DCLK 频率 | 5~33 MHz | 取决于分辨率和色深 |
| HBP / HFP | 背/前肩 | 行消隐区,必须配置正确 |
关键公式:DCLK = (H_Total × V_Total × Refresh_Rate)。比如800×480@60Hz,H_Total=928,V_Total=525,算出来DCLK≈29.2MHz。
4.3 基于MIPI DSI接口的驱动移植
MIPI DSI接口,这是目前手机和平板上最主流的屏幕接口。它用差分信号传输,抗干扰能力强,带宽高。
说实话,MIPI DSI的移植比前两种复杂得多。因为它涉及协议层——DCS命令、包格式、LP/HS模式切换。
移植步骤:
- PHY层配置——设置差分电压、摆率、时钟频率。
- D-PHY初始化——进入HS模式,建立数据通道。
- DCS命令发送——通过短包或长包发送初始化序列。
- 视频模式或命令模式选择——视频模式适合动态画面,命令模式适合静态界面。
避坑指南:我曾经在MIPI DSI的LP模式下发送DCS命令时,忘记检查“总线翻转”状态,导致命令一直发不出去。后来加了超时重试机制才搞定。
一个典型的MIPI DSI初始化序列片段:
// 发送DCS软复位命令
mipi_dsi_dcs_soft_reset(dsi_dev);
mdelay(120);
// 设置像素格式为24位RGB
mipi_dsi_dcs_set_pixel_format(dsi_dev, 0x77);
mdelay(10);
// 退出睡眠模式
mipi_dsi_dcs_exit_sleep_mode(dsi_dev);
mdelay(120);
// 开启显示
mipi_dsi_dcs_set_display_on(dsi_dev);
mdelay(50);
重要:MIPI DSI的时序要求非常严格。从LP切换到HS模式时,如果T_LPX(低功耗传输时间)不满足规范,屏幕可能直接黑屏。务必用示波器抓波形确认。
4.4 驱动性能调优
驱动写好了,能点亮屏幕了。但你会发现——刷屏慢得像PPT。这时候就需要调优了。
我总结了三板斧:
- DMA传输——别让CPU傻等SPI或RGB接口传数据。用DMA把CPU解放出来。
- 双缓冲——一个缓冲区在显示,另一个在后台绘制。切换时只改指针,不搬数据。
- 局部刷新——只更新变化区域。GUI框架通常有脏矩形机制,别全屏刷新。
举个例子,SPI接口用DMA传输后,刷屏速度能从15fps提升到45fps。你想想看,这体验差距有多大。
调优前后对比:
| 优化项 | 优化前 | 优化后 | 提升幅度 |
|---|---|---|---|
| SPI 刷屏(320×240) | 15 fps | 45 fps | 3x |
| RGB 刷屏(800×480) | 30 fps | 55 fps | 1.8x |
| MIPI DSI 刷屏(1080p) | 40 fps | 60 fps | 1.5x |
还有一个容易被忽略的点——缓存对齐。很多MCU的DMA要求源地址和目的地址按4字节对齐。如果不对齐,DMA会触发异常或者传输错误。我建议在定义帧缓冲数组时,加上对齐属性:
// GCC 对齐属性
uint16_t frame_buffer[320 * 240] __attribute__((aligned(4)));
// ARMCC 对齐属性
__align(4) uint16_t frame_buffer[320 * 240];
嗯,到这里,三种接口的移植和调优就讲完了。每种接口都有自己的适用场景和坑点。SPI适合低成本小屏,RGB适合中端工控屏,MIPI DSI适合高性能消费电子屏。选对接口,写好驱动,调好性能,你的GUI才能跑得流畅又稳定。