4. 显示控制器驱动:LTDC、DSI、SPI 实战

显示控制器,说白了就是 MCU 和屏幕之间的「翻译官」。你想想看,MCU 里跑的是 RGB 像素数据,屏幕要的是特定时序的电压信号,中间没有驱动层,数据根本过不去。

我最早接触 TouchGFX 时,以为驱动就是填几个寄存器。结果第一次点亮屏幕,花屏了整整三天。后来才明白——显示控制器的适配,核心就三件事:时序对、引脚对、数据格式对。今天咱们就把 LTDC、DSI 和 SPI 这三种最常见的接口,一个一个拆开讲。

核心要点:无论哪种接口,驱动适配的最终目标都是让 TouchGFX 的 FrameBuffer 能正确写入像素数据,并且屏幕能按刷新率稳定显示。

显示控制器接口适配全景 LTDC 接口 RGB 并行 · 高带宽 DSI 接口 串行差分 · 长距离 SPI 接口 低速 · 小尺寸 LTDC 特性 • 直接驱动 RGB 接口屏 • 支持 2~4 层硬件图层 • 需要大量 GPIO 引脚 • 典型:24bit RGB + 同步信号 DSI 特性 • 差分信号,抗干扰强 • 1~4 Lane 可配置 • 支持视频模式/命令模式 • 适合大屏、长排线 SPI 特性 • 引脚少(4~5 根) • 速度受限(~80MHz) • 需内部显存 • 适合小尺寸、低功耗 选择依据:分辨率、帧率、引脚预算、成本

4.1 LTDC 驱动:直接控制 RGB 并行接口

LTDC 是 STM32 上最常见的显示控制器。它直接输出 RGB 数据和同步信号,不需要额外的显示芯片。我个人习惯把 LTDC 的适配分成三步走:时钟配置 → 时序参数 → 引脚映射。

4.1.1 时钟树配置

LTDC 的像素时钟(PCLK)决定了刷新率。公式很简单:

PCLK = H_Total × V_Total × Refresh_Rate

举个例子,480×272 的屏,H_Total=525,V_Total=286,60Hz 刷新率:

PCLK = 525 × 286 × 60 ≈ 9.0 MHz

嗯,这里要注意——PCLK 不能超过 LTDC 时钟源的上限。我在项目中遇到过有人把 PCLK 配到 30MHz,结果屏幕直接黑掉,因为时钟源分频没算对。

经验之谈:用 CubeMX 生成 LTDC 初始化代码时,记得手动检查 PLLSAI 的配置。CubeMX 有时会算出非整数分频,导致 PCLK 偏差超过 5%,屏幕就会闪烁。

4.1.2 时序参数配置

每个 RGB 屏的数据手册都会给一组时序参数。你需要把它们填进 LTDC 的寄存器。我整理了一个对照表:

时序参数 LTDC 寄存器 说明
H_Sync LxCFG1.HSPW 行同步脉冲宽度
H_Back_Porch LxCFG1.HBP 行同步后肩
H_Front_Porch LxCFG1.HFP 行同步前肩
V_Sync LxCFG1.VSPW 帧同步脉冲宽度
V_Back_Porch LxCFG1.VBP 帧同步后肩
V_Front_Porch LxCFG1.VFP 帧同步前肩

我曾经踩过一个坑:某款屏的 H_Back_Porch 是 46 个 PCLK,但我配成了 43。结果屏幕左边有 3 个像素的偏移,整个画面往右挪了一截。后来用逻辑分析仪抓波形才找到问题。

4.1.3 引脚映射与复用

LTDC 需要大量 GPIO,24 位 RGB 加上同步信号,轻松超过 30 个引脚。我的建议是:

  • 优先使用 AF 复用功能,不要手动操作 GPIO 寄存器
  • 检查引脚冲突,LTDC 引脚经常和 FMC、SDIO 共用
  • 注意信号完整性,RGB 线尽量等长,CLK 线单独包地

警告:LTDC 的 DE(数据使能)信号必须正确连接。如果 DE 悬空或接错,屏幕会显示全白或全黑。我见过有人把 DE 接到了 VSYNC 上,折腾了两天才发现。

4.2 DSI 驱动:串行差分的高带宽方案

DSI 是 MIPI 联盟的标准,用差分信号传输像素数据。它比 LTDC 少用很多引脚——4 Lane 的 DSI 只需要 10 根线,却能驱动 1080p 的屏幕。说白了,这是高端嵌入式设备的首选。

4.2.1 DSI 的两种工作模式

DSI 有两种模式,你得根据屏幕类型选:

模式 特点 适用场景
视频模式 实时传输像素流,无需帧缓冲 大屏、高刷新率
命令模式 通过 DCS 命令写入显存 小屏、低功耗

我建议:如果 MCU 内部有足够大的 SRAM 或 SDRAM 做帧缓冲,优先用视频模式。命令模式虽然省电,但每次刷屏都要发命令,TouchGFX 的动画效果会卡顿。

4.2.2 DSI 初始化流程

DSI 的初始化比 LTDC 复杂,因为它涉及 PHY 层和协议层。我的标准流程是:

  1. 配置 DSI 时钟:设置 PLL 输出频率,确保满足 Lane 速率
  2. 初始化 DSI 主机:设置 Lane 数、极性、Escape 时钟
  3. 启动 DSI PHY:等待 PHY 锁定,检查状态寄存器
  4. 发送 DCS 命令:初始化屏幕(Sleep Out、Display On 等)
  5. 配置视频模式时序:与 LTDC 类似,但参数通过 DSI 封装

关键代码片段:DSI 启动 PHY 的等待循环,很多人会漏掉超时处理。

// 等待 DSI PHY 锁定
uint32_t timeout = 1000000;
while (!(DSI->ISR & DSI_ISR_PLLLS)) {
    if (--timeout == 0) {
        // 错误处理:PHY 未锁定
        break;
    }
}

4.2.3 与 TouchGFX 的集成

TouchGFX 通过 TouchGFXHAL 类与显示驱动交互。对于 DSI,你需要重写 flushFrameBuffer() 方法:

void TouchGFXHAL::flushFrameBuffer(const Rect& rect) {
    // 计算要刷新的像素区域
    uint16_t* fb = getFrameBuffer();
    uint32_t offset = rect.y * LCD_WIDTH + rect.x;
    
    // 通过 DSI 写入像素数据
    DSI->DCWR = (uint32_t)&fb[offset];
    DSI->DCCR = rect.width * rect.height * 2;  // RGB565 格式
}

嗯,这里要注意——DSI 的写入地址必须 4 字节对齐。我遇到过因为地址没对齐,导致 DMA 传输卡死的情况。

4.3 SPI 驱动:小尺寸屏的经典方案

SPI 接口的屏幕成本低、引脚少,在 3.5 寸以下的市场里非常常见。但它的缺点也很明显——速度慢。SPI 时钟一般跑到 40~80MHz,刷 320×240 的屏,全屏刷新大概要 10ms,做动画会吃力。

4.3.1 SPI 驱动架构

SPI 屏通常带一个内部控制器(比如 ILI9341、ST7789),MCU 通过 SPI 命令操作它的显存。TouchGFX 的适配思路是:

  • 使用 SPI DMA:不要用轮询方式发数据,否则 CPU 占用率会飙到 90%
  • 实现局部刷新:只更新变化区域,减少 SPI 传输量
  • 利用屏幕的窗口命令:设置 CASET/RASET 限定刷新区域

我的习惯:SPI 驱动里加一个 spi_dma_transfer_done 回调,用信号量通知 TouchGFX 可以继续刷下一帧。这样能避免帧撕裂。

4.3.2 代码实现示例

下面是一个简化的 SPI 刷屏函数,配合 TouchGFX 的 flushFrameBuffer:

void LCD_IO_WriteMultipleData(uint16_t* data, uint32_t count) {
    // 设置屏幕窗口(假设只刷新脏矩形)
    LCD_IO_WriteCmd(0x2A);  // CASET
    LCD_IO_WriteData(rect.x >> 8);
    LCD_IO_WriteData(rect.x & 0xFF);
    LCD_IO_WriteData((rect.x + rect.width - 1) >> 8);
    LCD_IO_WriteData((rect.x + rect.width - 1) & 0xFF);
    
    LCD_IO_WriteCmd(0x2B);  // RASET
    // ... 类似设置 y 范围
    
    // 写像素数据
    LCD_IO_WriteCmd(0x2C);  // RAMWR
    HAL_SPI_Transmit_DMA(&hspi1, (uint8_t*)data, count * 2);
}

避坑指南:SPI 屏的 DCX(数据/命令选择)引脚必须在发送每个字节前设置正确。我曾经在 DMA 传输中忘了切换 DCX,结果屏幕把像素数据当成了命令,画面直接花掉。

4.4 三种接口的对比与选型建议

最后,我根据自己的项目经验,给出一份选型参考:

维度 LTDC DSI SPI
引脚数 30+ 6~12 4~5
最大分辨率 1024×768 1920×1080 480×320
帧率(典型) 60fps 60fps 30~40fps
硬件图层 支持 支持 不支持
驱动复杂度 中 高 低
成本 中 高 低

我个人建议:如果你的项目引脚充裕、分辨率在 800×480 以下,LTDC 是最稳的选择。DSI 适合高端产品,但调试起来确实费时间。SPI 嘛,做小尺寸原型验证最快,但别指望它跑复杂的动画。

好了,这一章的内容就到这儿。三种接口的驱动适配,说白了就是「时序配好、数据传对、中断管住」。你动手写代码时,记得先用逻辑分析仪抓一下波形,确认时序对不对——这一步能省掉你后面至少一半的调试时间。