简介:面向STM32H7系列开发者,资源基于STM32H750完成7英寸1024×600 RGB LCD屏驱动,工程覆盖LTDC控制器配置、HAL库初始化、触摸屏坐标解析等核心环节,适合作为显示与交互项目的工程模板或学习范例。压缩包共201个文件,其中104个.h头文件与91个.c源文件构成驱动主体,另含Keil工程配置、汇编启动文件、HEX固件等,整体1.61MB,目录结构清晰,便于按模块检索与快速移植。目前已有993人学习下载。源码中可看到完整的LTDC层配置流程,包括像素时钟、同步时序、颜色格式与帧缓冲管理;触摸屏驱动保留底层通信与坐标校准流程,能帮助开发者理解RGB屏初始化与触摸IC配合的全链路实现。实际使用时,可直接编译烧录验证显示效果,再按项目需求调整屏参或裁剪驱动,大幅缩短从零调试到稳定显示的周期。
1. STM32H750 驱动 7 寸 1024×600 RGB 屏:先绕过 FSMC 那条弯路
之前用 FSMC 刷 480×272 并行屏很顺手,换成 7 寸 1024×600 的 RGB 接口 TFT 才发现问题:FSMC 本质是“CPU 主动读写外设”的总线,满足不了 51MHz 像素时钟下每帧 600 行连续数据的推送要求。真正该承担这件事的是 LTDC(LCD-TFT Display Controller),它相当于芯片内部的显示扫描引擎,自动把显存里的像素按固定时序送到屏幕,CPU 只需要更新显存内容。这套基于 STM32H750 的资源,就是用 HAL 库把 LTDC 完整驱动起来,点亮 1024×600 的 7 寸 RGB 屏,并额外接入触摸屏。适合从串口屏、并行屏转 RGB 大屏的嵌入式工程师,也适合做 HMI 人机界面但不想给系统增加额外视频芯片的团队。
2. LTDC 时序参数计算与像素时钟树设计
2.1 屏幕数据手册里的水平/垂直时序怎么看
RGB 接口屏的数据手册里,都会给出一组水平时序和垂直时序参数。这里以项目里 7 寸 1024×600 面板常见的规格为例:
| 参数 | 典型值 | 单位 |
|---|---|---|
| 水平同步信号 HSYNC | 1 | 像素时钟 |
| 水平后肩 HBP | 160 | 像素时钟 |
| 水平有效区域 | 1024 | 像素时钟 |
| 水平前肩 HFP | 160 | 像素时钟 |
| 垂直同步信号 VSYNC | 3 | 行 |
| 垂直后肩 VBP | 23 | 行 |
| 垂直有效区域 | 600 | 行 |
| 垂直前肩 VFP | 12 | 行 |
这些数值不是固定的,不同厂家的 7 寸屏会有 10~12 行、150~170 像素的差别。拿到屏之后第一步就是找到数据手册里的Timing Characteristics表,把这六个数抄下来,后面所有配置都以它为依据。如果屏幕是从某宝买的组装屏,厂商不给完整手册,可以找同规格面板(比如 M700 系列)的数据手册作参考,先点亮再微调。
2.2 反推像素时钟 PCLK,避免刷新率“肉眼可见”
LTDC 的像素时钟 PCLK 不是随便定的,它和刷新率直接相关。一行总的像素周期是:
TotalWidth = 1024 + 1 + 160 + 160 = 1345一帧总行数是:
TotalHeight = 600 + 3 + 23 + 12 = 638刷新率公式:
refresh = PCLK / (TotalWidth × TotalHeight)如果目标刷新率是 60Hz,PCLK 就应该在:
60 × 1345 × 638 ≈ 51.5MHz很多第一次接触 RGB 屏的人在这里犯的第一个错误:用默认的 25MHz PCLK,结果刷新率只有 29Hz,屏幕明显闪烁,还以为是背光问题。RGB 屏刷新率低于 45Hz 时,视觉上会有持续的闪烁感,这种情况调 LTDC 时序参数是没有任何效果的,问题出在时钟频率不够。
2.3 PLL3Q 分频计算与 CubeMX 时钟树配置
STM32H750 的 LTDC 像素时钟一般从 PLL3Q 引出。外部晶振经过 PLL3 倍频,再经过 Q 分频得到 LTDC 时钟,最后经过 LTDC 内部的 PCDivider 分频得到最终 PCLK。常见晶振配置下的 PLL3 参数组合如下:
| 配置项 | 8MHz 晶振 | 25MHz 晶振 | 计算说明 |
|---|---|---|---|
| PLL3M | 1 | 5 | 外部时钟先降频到 4~5MHz |
| PLL3N | 102 | 102 | VCO = 输入频率 × PLL3N |
| PLL3Q | 16 | 10 | 给 LTDC 的分频系数 |
| 输出频率 | 51.0MHz | 51.0MHz | 8/1×102/16 与 25/5×102/10 |
在 CubeMX 的 Clock Configuration 界面里,找到LCD-TFT对应的时钟源,选择PLL3Q,填入上述参数后它会自动算出 51MHz。需要注意 PLL3 的 VCO 频率有一个有效范围,CubeMX 会在你输入数据后实时校验,显示红色表示超出允许区间,需要调整 PLL3M 和 PLL3N 的组合。常见错误是照搬另一块板子的 PLL 参数,结果因为外部晶振不同,VCO 直接超限,工程都编译不过去。
2.4 别忘了背光是 PWM 驱动的,不能只靠 LTDC
RGB 屏能显示的前提是背光亮起来。项目里 7 寸屏的背光通常是 LED 串并联结构,由一颗背光升压芯片驱动,亮度调节引脚一般接到 STM32 的某个定时器 PWM 通道上。这就是为什么工程里除了 LTDC,还会初始化一个定时器,输出占空比可调的 PWM 信号。
注意:如果 LTDC 参数全对、时序也对,但屏幕只亮白屏或完全不亮,先量背光引脚电压。背光 PWM 占空比为 0 或 pin 悬空时,屏幕只是亮不显示,这个故障和 LTDC 一点关系都没有。
3. CubeMX 工程搭建与 40pin RGB 接口引脚分配
3.1 新建工程时应选通的外设
在 STM32CubeMX 里新建 STM32H750 工程,除了常规的 RCC(HSE 外部晶振)、SYS(Debug Serial Wire),还需要打开下面几个外设:
- LTDC:LCD-TFT Display Controller,配置为 1 个 Layer,用于显示画面。
- FMC:外部 SDRAM 控制器,显存放在外部 SDRAM 里。
- I2C1 或 I2C2:用于触摸屏控制 IC 通信,常见触控 IC 是 GT911、FT5x06。
- TIM 通道:用于背光 PWM 调光,通常是 TIM2/TIM3 的某个通道。
这里最容易漏的是 FMC。H750 芯片内部可用的 SRAM 只有 128KB 左右,而 1024×600 分辨率的 RGB565 一帧就需要约 1.2MB,RGB888 更是接近 2.5MB。不接外部 SDRAM,LTDC 根本找不到地方放帧缓冲,所以外部 SDRAM 在这个项目里不是可选件,而是必选件。
3.2 LTDC 参数在 CubeMX 界面里的填法
CubeMX 的 LTDC 配置界面里,参数名和屏幕数据手册完全对应。像素时钟频率直接填 51000000,其余字段按下表填写:
| CubeMX 字段名 | 对应含义 | 填入值 |
|---|---|---|
| Horizontal Sync | HSYNC 脉宽 | 1 |
| Vertical Sync | VSYNC 脉宽 | 3 |
| Accumulated HBP | HSYNC + HBP | 161 |
| Accumulated VBP | VSYNC + VBP | 26 |
| Accumulated Active Width | 上述 + 水平有效宽度 | 1185 |
| Accumulated Active Height | 上述 + 垂直有效高度 | 626 |
| Total Width | 一行总像素数 | 1345 |
| Total Height | 一帧总行数 | 638 |
生成代码后,HAL 库会在HAL_LTDC_Init中将这些值寄存器化,内部会做减一处理,所以无需自己再手工减一。如果某字段填错,常见现象是画面左右偏移、上下滚动或者整屏斜条纹,此时优先复查 Accumulated 系列数值。
3.3 40pin RGB 接口的信号映射
7 寸 RGB 屏大多采用 40pin 或 50pin 的 FPC 接口,信号线分为数据、同步、时钟、电源四组。40pin 接口典型定义如下:
| 信号分组 | 引脚信号 | 说明 |
|---|---|---|
| 像素数据 | LTDC_B[7:0] | 蓝色数据 8 位 |
| 像素数据 | LTDC_G[7:0] | 绿色数据 8 位 |
| 像素数据 | LTDC_R[7:0] | 红色数据 8 位 |
| 同步 | LTDC_HSYNC | 行同步信号 |
| 同步 | LTDC_VSYNC | 帧同步信号 |
| 同步 | LTDC_DE | 数据有效标志 |
| 时钟 | LTDC_DOTCLK | 像素时钟 |
| 电源 | VDD/GND | 3.3V 与地 |
| 背光 | LED+ / LED- | 背光电源 |
| 触摸 | SDA / SCL | I2C 信号 |
需要注意的是,40pin 接口的定义在不同屏厂之间存在差异,有些屏把蓝色和红色数据线位置互换,或者 DE 信号不引出。接错数据线会导致画面偏色、颜色通道错乱,这种情况在 LTDC 配置里是查不到的,只能对照数据手册逐根核对引脚顺序。
3.4 FMC-SDRAM 配置与“片外 app 卡死”问题
外部 SDRAM 在 CubeMX 的 FMC 配置界面里完成,选择 16 位数据宽度,地址映射到 Bank1。典型初始化参数:列地址 8 位、行地址 12 位、CAS 延迟 2、写恢复 2。生成代码时需要补一段 SDRAM 初始化序列,先发送MODE_REGISTER_WRITE,设置 Burst Length 为 1,然后等待刷新稳定。
这一步也是网上经常有人反馈stm32h750 片外app 卡死的实际原因:SDRAM 时序配置过紧,初始化勉强通过,但频繁访问显存时发生位错误或死锁。排查手段很简单,初始化完成后对整块 SDRAM 写递增数、再读回来比对,连续跑几轮不出错再往下做显示。
3.5 触摸屏 I2C 引脚分配
触摸屏控制 IC 通常挂在一组 I2C 总线上,GT911 的 7 位地址一般是 0x5D 或 0x14,取决于 INT 引脚电平。CubeMX 里直接启用 I2C1,速率设为 400kHz,引脚会自动映射到预设的 GPIO 上。需要注意 I2C 上拉电阻:GT911 模组通常本身已经带 4.7kΩ 上拉,如果板子上又焊了上拉电阻,两个上拉并联会降低总阻值,I2C 波形变差,触摸偶尔失灵就是这个原因。
4. HAL 库 LTDC 初始化代码逐段拆解与点亮排错
4.1 LTDC 初始化结构体与寄存器取值说明
CubeMX 生成的MX_LTDC_Init是点亮屏幕的核心函数,代码中每个字段都和时序计算章节的数值对应:
LTDC_HandleTypeDef hltdc; LTDC_LayerCfgTypeDef layer_cfg; static void MX_LTDC_Init(void) { hltdc.Instance = LTDC; /* 以下数值来自 1024x600 面板规格书,单位是像素 */ hltdc.Init.HorizontalSync = 0; /* HSYNC 脉宽 1,寄存器 0 起算 */ hltdc.Init.VerticalSync = 2; /* VSYNC 脉宽 3,寄存器减 1 */ hltdc.Init.AccumulatedHBP = 160; /* HSYNC + HBP - 1 = 1 + 160 - 1 */ hltdc.Init.AccumulatedVBP = 25; /* VSYNC + VBP - 1 = 3 + 23 - 1 */ hltdc.Init.AccumulatedActiveW = 1184; /* HSYNC + HBP + 1024 - 1 */ hltdc.Init.AccumulatedActiveH = 625; /* VSYNC + VBP + 600 - 1 */ hltdc.Init.TotalWidth = 1344; /* 活动行结束 + HFP - 1 */ hltdc.Init.TotalHeigh = 637; /* 活动帧结束 + VFP - 1 */ hltdc.Init.HSPolarity = LTDC_HSPOLARITY_AL; /* 低有效 */ hltdc.Init.VSPolarity = LTDC_VSPOLARITY_AL; /* 低有效 */ hltdc.Init.DEPolarity = LTDC_DEPOLARITY_AL; /* DE 低有效 */ hltdc.Init.PCPolarity = LTDC_PCPOLARITY_IPC;/* 下降沿采样 */ if (HAL_LTDC_Init(&hltdc) != HAL_OK) { Error_Handler(); } }这段代码里最容易看糊涂的是为什么 TotalWidth 填的不是 1345 而是 1344。原因是 LTDC 寄存器全部从 0 开始计数,HAL 库的结构体字段直接对应寄存器原始值。CubeMX 界面上按自然数填写,生成代码时自动减一。如果从别处抄的代码直接搬过来用,却没有检查数值是“自然值”还是“寄存器值”,屏幕很容易出现横向错位或撕裂。
极性问题也需要留意。同一个屏幕放在不同驱动板上,HSPolarity 可能要求低有效或高有效,这取决于屏的 TCON 电路。我一般在第一版固件里把这四个极性全部配置成 AL(低有效),如果画面显示异常但颜色对,再用调试器逐个翻转试。
4.2 Layer 1 窗口配置与帧缓冲地址挂载
LTDC 初始化完成后,必须配置至少一个 Layer 才能看到图像。Layer 1 的配置代码如下:
static void LCD_Layer1_Config(uint32_t fb_addr) { layer_cfg.WindowX0 = 0; layer_cfg.WindowY0 = 0; layer_cfg.WindowX1 = 1024; /* 窗口右边界 */ layer_cfg.WindowY1 = 600; /* 窗口下边界 */ layer_cfg.PixelFormat = LTDC_PIXEL_FORMAT_RGB565; layer_cfg.FBStartAdress = fb_addr; layer_cfg.Alpha = 255; /* 全不透明 */ layer_cfg.Alpha0 = 0; layer_cfg.BlendingFactor1 = LTDC_BLENDING_FACTOR1_CA; layer_cfg.BlendingFactor2 = LTDC_BLENDING_FACTOR2_CA; layer_cfg.ImageWidth = 1024; layer_cfg.ImageHeight = 600; layer_cfg.ColorKeying = 0; if (HAL_LTDC_ConfigLayer(&hltdc, &layer_cfg, 1) != HAL_OK) { Error_Handler(); } }WindowX0/Y0和WindowX1/Y1定义了层窗口在屏幕上的显示位置,图像分辨率必须与窗口大小一致,否则超出部分会被裁剪。PixelFormat选择 RGB565 还是 RGB888 要看屏幕接口和显存容量,RGB565 显存减半,但在显示渐变图像时会有轻微色阶断层,这就是常说的色带问题。FBStartAdress填的是 SDRAM 中帧缓冲区的首地址,LTDC 会从这个地址连续读取像素数据。如果这个地址没有对齐到 4 字节,某些芯片上会出现肉眼可见的竖条纹或整个画面偏移。
4.3 点亮后出现白屏、斜纹、闪烁的排错顺序
屏没有点亮或者显示异常时,不要盲目改寄存器。我习惯按下面的顺序排查:
- 检查背光电压和 PWM:背光不亮时,LTDC 一切正常也看不到画面。
- 用示波器测量 DOTCLK 引脚,确认像素时钟接近 51MHz。如果没有时钟波形,说明 PLL3Q 没有配置好或 LTDC 时钟源选错。
- 检查 DE、HSYNC、VSYNC 波形:这三个信号在 RGB 接口下必须存在,如果 DE 始终为低,多半是时序配置中累计值填错。
- 确认像素时钟极性和同步信号极性:PCLK 采样沿反了,画面会整体偏色或出现轻微拖影。
- 检查帧缓冲地址是否属于已初始化的 SDRAM 映射区间,以及该区域是否被 MPU 配置成了不可缓存。
白屏但背光亮,通常是 LTDC 没有输出有效数据;斜纹或整屏滚动,通常是一行总像素和帧总行数与实际不符;画面闪烁但颜色正确,优先考虑刷新率是否低于 45Hz。
4.4 把图片和中文显示出来,RGB 取模到底怎么做
很多人点亮屏幕后第一件想做的是显示图片或中文,但卡在取模工具上。RGB565 的每个像素占 2 字节,红色取高 5 位、绿色取高 6 位、蓝色取高 5 位,然后按小端序存储。用 Python 脚本从 PNG 转数组是最快捷的做法:
from PIL import Image img = Image.open("menu_bg.png").convert("RGB") w, h = img.size out = [] for y in range(h): for x in range(w): r, g, b = img.getpixel((x, y)) val = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3) out.append(f"0x{val:04X}") with open("image_data.c", "w", encoding="utf-8") as f: f.write(f"const uint16_t image_data[{w}*{h}] = {{\n") for i in range(0, len(out), 16): f.write(" " + ", ".join(out[i:i+16]) + ",\n") f.write("};\n")这张生成的image_data数组可以直接用 DMA2D 或 memcpy 拷贝到帧缓冲中。需要注意:(r & 0xF8)取的是红通道高 5 位,(g & 0xFC)取绿色高 6 位,(b >> 3)取蓝色高 5 位。如果取错位,图片颜色会偏紫或偏绿,整体对比度明显不对。中文显示也是同理,只是字库取模时每个像素只有 0 或 1 两个状态,显示时把两种颜色映射成 RGB565 写入帧缓冲即可。
5. 触摸屏坐标读取、校准与双缓冲刷新下的防撕裂实现
5.1 GT911 触摸 IC 的 I2C 坐标读取
GT911 是 7 寸电容触摸屏上使用率最高的控制 IC。它的坐标数据放在内部寄存器中,CPU 通过 I2C 直接读取:
#define GT911_I2C_ADDR 0x5D #define GT911_POINT_BUF_ADDR 0x814E int8_t Touch_ReadPoint(uint16_t *x, uint16_t *y) { uint8_t buf[6]; if (HAL_I2C_Mem_Read(&hi2c1, GT911_I2C_ADDR << 1, GT911_POINT_BUF_ADDR, I2C_MEMADD_SIZE_16BIT, buf, 6, 20) != HAL_OK) { return -1; } if ((buf[0] & 0x80) == 0) { return 0; } *x = buf[1] | ((buf[2] & 0x0F) << 8); *y = buf[3] | ((buf[4] & 0x0F) << 8); return buf[0] & 0x07; }buf[0]的最高位表示是否有触点按下,低 3 位是触点数量。buf[1]和buf[2]组成 12 位 X 坐标,buf[3]和buf[4]组成 12 位 Y 坐标。GT911 的坐标单位是 0 到 1023,和屏的物理分辨率没有直接对应关系,必须做一次线性映射。
5.2 从触摸坐标到屏幕坐标的线性映射
如果你的触摸模组是标准全贴合组装,映射时用两点校准。读取触摸屏左上角和右下角两个点的原始坐标,然后:
uint16_t lcd_x = (touch_x - x_min) * 1024 / (x_max - x_min); uint16_t lcd_y = (touch_y - y_min) * 600 / (y_max - y_min);x_min/x_max和y_min/y_max从两点校准中计算得到。校准一般放在出厂测试阶段,将校准数据保存到外置 Flash 或 STM32 内部 Flash 的用户区,下次开机直接读取,不必每次上电校准。
5.3 双缓冲刷新时如何避免画面上下撕裂
LTDC 在扫描到屏幕某一固定行时会产生LineEven中断,利用这个中断可以在垂直消隐期切换帧缓冲地址:
static uint32_t fb_front; static uint32_t fb_back; void HAL_LTDC_LineEven_IRQHandler(void) { uint32_t tmp = fb_front; fb_front = fb_back; fb_back = tmp; HAL_LTDC_SetAddress(&hltdc, fb_front, 1); }写入画面时先写fb_back,等 LTDC 切换到该缓冲后再写入另一块。这样避免了在屏幕扫描过程中切换地址导致的上下半屏撕裂。另外要特别注意 H750 的 D-Cache 与外部 SDRAM 之间的一致性,CPU 写完帧缓冲后要调用SCB_CleanDCache_by_Addr清对应范围,否则屏幕可能出现局部脏块。这个清理动作频率越高越保险,但会消耗 CPU 时间,建议界定更新区域只清理变化部分。
本文还有配套的精品资源,点击获取