3. 硬件驱动适配概述
做嵌入式UI,说白了就是跟显示接口打交道。你选的接口直接决定了帧率上限、内存开销、甚至PCB层数。我这些年调过的屏,从128×64的单色OLED到1080p的MIPI DSI,每种接口都有它的脾气。今天咱们就把SPI、FMC/FSMC、RGB、DPI、MIPI DSI这五个接口掰开揉碎讲清楚。
核心观点:接口选型不是越快越好,而是匹配你的硬件资源。MCU没FMC硬要跑RGB接口,那是给自己挖坑。
3.1 SPI接口——小屏的命根子
SPI,串行外设接口。四根线:SCK、MOSI、MISO、CS。驱动小尺寸LCD(128×64、240×240)绰绰有余。我最早用STM32F103驱动1.44寸TFT,就是走的SPI,帧率大概15fps,显示个菜单、图标完全够用。
为什么TouchGFX能用SPI? 因为TouchGFX的帧缓冲可以很小——你想想看,320×240分辨率、16位色,帧缓冲才150KB。SPI时钟跑到36MHz,刷一屏大概8ms,加上刷新率60Hz,实际体验很流畅。
我的习惯:SPI驱动LCD时,把DC(数据/命令)脚也挂在GPIO上,别省。有些屏支持9位SPI模式,但兼容性不好,我吃过亏。
SPI驱动LCD的关键配置:
- 时钟极性(CPOL)和相位(CPHA):多数LCD用Mode 0或Mode 3,看数据手册。
- 数据长度:8位是标准,但有些屏支持16位一次传一个像素。
- DMA传输:必须开。CPU轮询刷屏,CPU占用率直接飙到80%以上。
// SPI初始化示例(STM32 HAL库)
SPI_HandleTypeDef hspi2;
hspi2.Instance = SPI2;
hspi2.Init.Mode = SPI_MODE_MASTER;
hspi2.Init.Direction = SPI_DIRECTION_2LINES;
hspi2.Init.DataSize = SPI_DATASIZE_8BIT;
hspi2.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi2.Init.NSS = SPI_NSS_SOFT;
hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;
HAL_SPI_Init(&hspi2);
我曾经踩过的坑:SPI的CS脚在DMA传输完成后不能立刻拉高,要等最后一个SCK边沿结束。否则最后一字节会丢失,屏幕底部出现一条黑线。解决办法:在DMA传输完成回调里加一个微秒级延时再拉高CS。
3.2 FMC/FSMC接口——并口老将
FMC(Flexible Memory Controller)是STM32F4/H7系列上的并口控制器。FSMC是前身,功能差不多。说白了,就是把LCD当成SRAM来访问——你往某个地址写数据,LCD自动把数据打到像素上。
为什么快? 因为地址线和数据线是独立的。16位数据线,一个写周期就能传一个像素。我测过STM32F429搭配FMC驱动480×272的屏,刷满一屏只要3ms,60fps毫无压力。
FMC驱动LCD的典型接线:
| FMC信号 | LCD引脚 | 说明 |
|---|---|---|
| NE1 (片选) | CS | 片选,低电平有效 |
| NWR (写使能) | WR | 写信号 |
| NRD (读使能) | RD | 读信号(一般不用) |
| A16 (地址线) | DC | 数据/命令选择 |
| D[0:15] | DB[0:15] | 16位数据总线 |
地址映射技巧:把A16接到DC脚,那么地址0x60000000写就是命令,0x60010000写就是数据。代码里只需要两个宏:
#define LCD_CMD_ADDR ((uint32_t)0x60000000)
#define LCD_DATA_ADDR ((uint32_t)0x60010000)
#define LCD_WR_CMD(cmd) (*(volatile uint16_t *)LCD_CMD_ADDR = (cmd))
#define LCD_WR_DATA(data) (*(volatile uint16_t *)LCD_DATA_ADDR = (data))
注意:FMC的时序参数(建立时间、保持时间)必须根据LCD数据手册配置。太快了数据采样不到,太慢了浪费带宽。我一般先按手册典型值设,再用逻辑分析仪抓波形微调。
3.3 RGB接口——真正的并行像素
RGB接口跟FMC不一样。FMC是总线协议,RGB是直接输出像素时钟、行同步、场同步和像素数据。没有协议开销,每个时钟周期传一个像素。
RGB接口的信号:
- PCLK:像素时钟,每个上升沿传一个像素。
- HSYNC:行同步,每行开始前拉低。
- VSYNC:场同步,每帧开始前拉低。
- DE:数据使能,有效期间像素数据有效。
- R[0:7]、G[0:7]、B[0:7]:24位RGB数据线。
为什么RGB接口需要LTDC? 因为MCU没有专门的RGB控制器,需要LTDC(LCD-TFT Display Controller)外设来生成时序。STM32F429的LTDC可以支持到800×480分辨率。我做过一个项目,用LTDC驱动7寸屏,帧率稳定在60fps,效果非常棒。
RGB接口的时序参数:
| 参数 | 典型值(800×480@60Hz) | 说明 |
|---|---|---|
| PCLK | 33.3 MHz | 像素时钟频率 |
| HBP | 88 | 行后肩 |
| HFP | 40 | 行前肩 |
| HSW | 48 | 行同步脉宽 |
| VBP | 32 | 场后肩 |
| VFP | 13 | 场前肩 |
| VSW | 3 | 场同步脉宽 |
我曾经犯过的错:LTDC的时钟配置错了,导致屏幕闪烁。原因是PCLK不是随便设的,必须满足:PCLK = (H_total × V_total × 刷新率)。算出来33.3MHz,我设成30MHz,结果刷新率掉到54Hz,肉眼可见闪烁。
3.4 DPI接口——串行化的RGB
DPI(Display Pixel Interface)其实就是把RGB接口的并行数据串行化。信号线从24根减少到4根(数据+时钟),但带宽不变。说白了,就是RGB接口的LVDS版本。
DPI vs RGB:
| 对比项 | RGB | DPI |
|---|---|---|
| 信号线数量 | 28根(24数据+4控制) | 4根(2数据+1时钟+1使能) |
| 抗干扰能力 | 弱(并行线间串扰) | 强(差分信号) |
| 传输距离 | 短(<10cm) | 中等(<30cm) |
| 典型应用 | 小尺寸屏(<7寸) | 中尺寸屏(7-15寸) |
DPI接口在工业平板和医疗设备上很常见。我调过一款10.1寸的DPI屏,分辨率1280×800,用i.MX6ULL驱动,效果很稳定。注意DPI的时钟频率很高,PCB走线要等长,差分对间距要控制好。
3.5 MIPI DSI接口——移动设备的王者
MIPI DSI(Display Serial Interface)是当前智能手机和平板的主流接口。它用差分信号传输,一对差分线可以跑到1Gbps以上。DSI有1-4个数据通道,每个通道都是差分对。
DSI的两种传输模式:
- Command Mode:类似SPI/FMC,有帧缓冲,通过命令更新显示内容。适合MCU。
- Video Mode:类似RGB接口,实时刷新,没有帧缓冲。适合MPU。
DSI的协议层次:
- 物理层:差分信号,D-PHY标准。
- 通道管理层:数据分发到各个通道。
- 协议层:长包/短包,数据包格式。
- 应用层:DCS命令,设置亮度、对比度等。
我的经验:DSI的初始化序列非常关键。很多屏的初始化代码有上百行,包括PLL配置、时序参数、Gamma校正等。我建议把初始化序列放在一个数组里,用状态机逐条发送,方便调试。
// DSI初始化序列示例(伪代码)
const uint8_t dsi_init_seq[] = {
0x11, 0x00, // Exit Sleep Mode
0x29, 0x00, // Display On
0xB0, 0x01, 0x00, // Set Page Address
0xC0, 0x03, 0x28, 0x80, 0x00, // Set PLL
// ... 更多命令
};
void DSI_SendSequence(const uint8_t *seq) {
while (*seq != 0xFF) {
uint8_t cmd = *seq++;
uint8_t len = *seq++;
DSI_WriteCommand(cmd, seq, len);
seq += len;
}
}
DSI带宽计算:假设4通道DSI,每通道1Gbps,有效带宽约4Gbps。对于1920×1080@60fps、24位色,需要带宽 = 1920×1080×60×24 ≈ 2.98Gbps。DSI完全够用,还有余量。
3.6 接口选型决策树
说了这么多,到底怎么选?我总结了一个简单的决策逻辑:
- 分辨率 ≤ 320×240:SPI接口,成本低,MCU资源够。
- 分辨率 480×272 ~ 800×480:FMC或RGB接口。有FMC外设就用FMC,没有就用RGB+LTDC。
- 分辨率 1024×600 ~ 1920×1080:MIPI DSI或DPI。DSI优先,因为线少、抗干扰好。
- 需要长距离传输(>30cm):DPI或DSI,差分信号抗干扰强。
你想想看,选对了接口,后面驱动适配就顺了。选错了,后面全是坑。我见过有人用SPI驱动800×480的屏,帧率只有5fps,触摸都跟不上。嗯,那就是选型失误。