3. 硬件驱动适配概述

做嵌入式UI,说白了就是跟显示接口打交道。你选的接口直接决定了帧率上限、内存开销、甚至PCB层数。我这些年调过的屏,从128×64的单色OLED到1080p的MIPI DSI,每种接口都有它的脾气。今天咱们就把SPI、FMC/FSMC、RGB、DPI、MIPI DSI这五个接口掰开揉碎讲清楚。

核心观点:接口选型不是越快越好,而是匹配你的硬件资源。MCU没FMC硬要跑RGB接口,那是给自己挖坑。

嵌入式显示接口层级与带宽对比 SPI (低速控制) FMC/FSMC (并口总线) RGB (并行像素) DPI (串行像素) MIPI DSI (差分串行) SPI ~10 Mbps FMC ~100 Mbps RGB ~400 Mbps DPI ~800 Mbps DSI ~4 Gbps 带宽越高,对MCU/MPU的内存和时钟要求也越高。 TouchGFX建议:QVGA以下用SPI,VGA以上用RGB/DSI。

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)说明
PCLK33.3 MHz像素时钟频率
HBP88行后肩
HFP40行前肩
HSW48行同步脉宽
VBP32场后肩
VFP13场前肩
VSW3场同步脉宽

我曾经犯过的错: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:

对比项RGBDPI
信号线数量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的协议层次:

  1. 物理层:差分信号,D-PHY标准。
  2. 通道管理层:数据分发到各个通道。
  3. 协议层:长包/短包,数据包格式。
  4. 应用层: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,触摸都跟不上。嗯,那就是选型失误。