4. 显示控制器驱动移植与LTDC配置

好,到了这一章,我们终于要跟硬件正面打交道了。

前面几章我们搭好了工程框架,配好了时钟和内存。但说实话,那些都是热身。真正的硬骨头,是让屏幕亮起来。而屏幕亮起来的关键,就是LTDC——LCD-TFT显示控制器。

我个人习惯把LTDC比作一个“快递分拣中心”。它从内存里把图像数据取出来,按照你设定的格式和时序,一包一包地发给屏幕。你配置得好,画面就流畅;配不好,要么花屏,要么干脆黑屏。

4.1 LTDC到底在干什么?

先别急着写代码。我们得搞清楚LTDC的工作流程。

你看这张图,就明白了。

LTDC 数据流与配置核心 帧缓冲 SDRAM 中的图像数据 DMA2D 图形加速/格式转换 LTDC 控制器 时序生成 + 数据打包 显示接口 RGB 引脚输出 LCD 面板 像素点亮 LTDC 核心配置参数 • 水平同步宽度 (HSW) / 垂直同步高度 (VSH) • 水平后沿 (HBP) / 垂直后沿 (VBP) • 有效宽度 (Active Width) / 有效高度 (Active Height) • 像素时钟极性 / 同步信号极性

数据从帧缓冲(SDRAM)出发,经过DMA2D加速(可选),送到LTDC。LTDC根据你配的时序参数,把数据转换成RGB信号,通过引脚发给LCD面板。

嗯,这里要注意:LTDC本身不存储图像数据,它只是一个搬运工。所以帧缓冲的地址、像素格式、层配置,一个都不能错。

4.2 时序参数:从数据手册里抠数字

移植LTDC最烦人的一步,就是配时序参数。为什么?因为每个屏幕的时序都不一样。

我记得第一次做这个的时候,拿着一个4.3寸屏的数据手册,翻来覆去找了半小时才找全参数。后来我总结了一个规律:你只需要关注数据手册里的“Timing Characteristics”表格。

以最常见的480x272分辨率屏幕为例,你需要提取以下参数:

参数 符号 典型值 说明
水平同步宽度 HSW 41 行同步脉冲宽度(像素时钟数)
水平后沿 HBP 2 行同步结束到有效数据开始
水平前沿 HFP 2 有效数据结束到下一个行同步
垂直同步宽度 VSW 10 帧同步脉冲宽度(行数)
垂直后沿 VBP 2 帧同步结束到有效数据开始
垂直前沿 VFP 2 有效数据结束到下一个帧同步
我的小技巧: 如果数据手册里只给了“最小/典型/最大”值,我一般直接用典型值。如果典型值没有,就用最小值加一个安全余量。千万别用最大值,否则屏幕刷新率可能不够。

4.3 像素时钟的计算

时序参数配好了,像素时钟(Pixel Clock)怎么算?

公式其实很简单:

Pixel Clock = (HSW + HBP + Active Width + HFP) × (VSW + VBP + Active Height + VFP) × 刷新率

举个例子,480x272的屏幕,刷新率60Hz:

一行总像素 = 41 + 2 + 480 + 2 = 525
一帧总行数 = 10 + 2 + 272 + 2 = 286
Pixel Clock = 525 × 286 × 60 ≈ 9.0 MHz

嗯,这里要注意:你配的LTDC时钟源(比如PLLSAI)必须能整除出这个频率。如果除不尽,屏幕会出现水波纹或者闪烁。

我曾经踩过的坑: 有一次我配的像素时钟是9.1MHz,屏幕也能亮,但画面有轻微的横纹滚动。查了两天才发现是时钟分频没算对,导致帧率不是精确的60Hz。后来我改用PLLSAI的精确分频,问题就解决了。

4.4 LTDC层配置:图层叠加与颜色格式

LTDC支持最多2个图层(Layer0和Layer1)。你可以把它们叠在一起,实现类似“背景+前景”的效果。

我个人习惯把Layer0作为背景层,Layer1作为UI层。这样TouchGFX只操作Layer1,背景层可以放一个静态图片或者纯色,减少刷新开销。

配置图层时,你需要指定:

  • 帧缓冲地址: 必须是32位对齐的地址
  • 像素格式: 常见的有RGB565、RGB888、ARGB8888
  • 窗口位置: 可以只显示帧缓冲的一部分
  • Alpha混合: 图层透明度

看一段实际的初始化代码:

static void LTDC_Layer_Init(void)
{
    LTDC_LayerCfgTypeDef layer_cfg;

    // Layer0 配置:背景层
    layer_cfg.WindowX0 = 0;
    layer_cfg.WindowY0 = 0;
    layer_cfg.WindowX1 = 480;
    layer_cfg.WindowY1 = 272;
    layer_cfg.PixelFormat = LTDC_PIXEL_FORMAT_RGB565;
    layer_cfg.FBStartAdress = (uint32_t)&background_buffer;
    layer_cfg.Alpha = 255;
    layer_cfg.Alpha0 = 0;
    layer_cfg.BlendingFactor1 = LTDC_BLENDING_FACTOR1_PAxCA;
    layer_cfg.BlendingFactor2 = LTDC_BLENDING_FACTOR2_PAxCA;
    layer_cfg.ImageWidth = 480;
    layer_cfg.ImageHeight = 272;
    HAL_LTDC_ConfigLayer(&hltdc, &layer_cfg, 0);

    // Layer1 配置:UI层
    layer_cfg.FBStartAdress = (uint32_t)ui_buffer;
    layer_cfg.Alpha = 255;
    HAL_LTDC_ConfigLayer(&hltdc, &layer_cfg, 1);
}
关键点: 如果你使用RGB565格式,每个像素占2字节。480x272的分辨率,一帧需要480×272×2 ≈ 261KB。如果你的SDRAM只有8MB,可以放30帧的缓冲。但TouchGFX一般只需要双缓冲,也就是2帧就够了。

4.5 引脚映射与GPIO复用

LTDC的RGB信号是通过GPIO引脚输出的。不同芯片的引脚映射不一样,你得对照原理图和数据手册来配。

以STM32F429为例,LTDC的引脚分布如下:

信号 引脚 说明
LCD_CLK PG7 像素时钟
LCD_HSYNC PI12 行同步
LCD_VSYNC PI13 帧同步
LCD_DE PK7 数据使能
LCD_R[7:0] PJ0-PJ7 红色数据
LCD_G[7:0] PK0-PK7 绿色数据
LCD_B[7:0] PJ8-PJ15 蓝色数据

配置GPIO时,记得把复用功能设为AF14(LTDC)。我见过有人配成了AF13,结果屏幕死活不亮,查了半天才发现是复用功能选错了。

4.6 中断与同步:别让屏幕撕裂

屏幕撕裂(Tearing)是什么?就是画面一半是上一帧,一半是当前帧。看起来像撕开了一样。

为什么会这样?因为你在LTDC正在读取帧缓冲的时候,往里面写了新数据。

解决办法有两个:

  • 使用垂直同步中断: 在VSYNC中断里切换缓冲
  • 使用双缓冲: 一个缓冲被LTDC读取,另一个缓冲被CPU写入,VSYNC时交换

TouchGFX默认使用双缓冲+VSYNC同步。你需要使能LTDC的Line中断或者VSYNC中断:

HAL_NVIC_SetPriority(LTDC_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(LTDC_IRQn);

// 在中断服务函数中
void LTDC_IRQHandler(void)
{
    if(__HAL_LTDC_GET_FLAG(&hltdc, LTDC_FLAG_VSYNC))
    {
        // 通知TouchGFX切换缓冲
        touchgfx::HAL::getInstance()->signalVSync();
        __HAL_LTDC_CLEAR_FLAG(&hltdc, LTDC_FLAG_VSYNC);
    }
}
我的建议: 中断优先级不要设得太高,否则会影响其他实时任务。我一般设到5或者6,够用就行。

4.7 调试技巧:黑屏了怎么办?

说实话,LTDC移植第一次就能点亮屏幕的概率,不高。我做了这么多年,也经常遇到黑屏。

如果你遇到黑屏,按这个顺序排查:

  1. 检查时钟: PLLSAI有没有使能?LTDC时钟有没有输出?用示波器量LCD_CLK引脚
  2. 检查复位: LTDC外设有没有被复位?有些芯片默认是关闭的
  3. 检查引脚: GPIO复用功能对不对?有没有被其他外设占用?
  4. 检查时序: HSW、HBP这些参数有没有配反?我曾经把HSW和HBP写反了,屏幕闪得厉害
  5. 检查帧缓冲地址: 地址对不对?SDRAM有没有初始化成功?
我曾经遇到的一个奇葩问题: 屏幕能亮,但颜色完全不对——红色变成了蓝色。查了半天,发现是RGB引脚顺序接反了。硬件工程师把R[0]接到了B[0]上。嗯,这种问题只能改硬件或者改软件映射。

4.8 本章小结

LTDC移植说白了就是三件事:配时序、配图层、配引脚。时序参数从数据手册里抠,图层配置注意地址对齐,引脚映射别搞错复用功能。

如果你按照这个流程走下来,屏幕应该能亮了。下一章我们会把TouchGFX的渲染引擎挂到LTDC上,让UI真正跑起来。

嗯,先到这。去试试吧,有问题随时翻回来看看。