资讯详情

资讯详情

STM32 CubeMX LTDC驱动RGB屏:SDRAM+DMA2D

1. RGB屏不是驱动出来的是喂出来的手里有一块 800×480 的 5 寸 TFT 模组40pin 的 FPC 排线一半是 R0-R7、G0-G7、B0-B7 这 24 根数据线另一半是 PCLK、HSYNC、VSYNC、DE 四根同步信号外加背光和触摸。很多人第一次把它接到 STM32 上CubeMX 里勾了 LTDC、配了引脚、生成了代码烧进去之后屏幕只有背光全白什么都没有。这不是代码没跑起来是喂的方式不对。RGB 屏跟 SPI 屏、8080 并口屏完全不是一个物种。SPI 屏内部自带显存和控制器你只要发指令、发数据它自己负责刷新8080 并口屏也一样有 ILI9341、ST7789 这类驱动 IC 顶在前面你写一个像素它就存一个。而裸 RGB 屏本质上是一块没有大脑的玻璃——它内部只有源极驱动和栅极驱动没有任何显存也没有时序发生器。它要求主控每隔一个像素时钟就把当前像素的颜色值摆到 24 根数据线上同时按严格的节拍给出 HSYNC、VSYNC、DE。也就是说主控必须像一个不知疲倦的搬运工一行 800 个像素、一帧 480 行每秒 60 次一次都不能停。这件事在 STM32 上有个专门的硬件来做LTDCLCD-TFT Display Controller。它挂在 AHB 总线上能自己从 SDRAM 里读显存、自己产生时序、自己把数据推到引脚上CPU 全程不参与。这也是本文要拆的核心用 STM32CubeMX 把 LTDC、FMC外扩 SDRAM、DMA2D 这三块拼起来让一块裸 RGB 屏稳定跑出 60Hz 的画面。适合谁看手上已经有 F4/F7/H7 系列带 LTDC 的板子、屏和排线都买好了、但在 CubeMX 里点完之后画面不对的人也适合还没上手、想先搞清楚这套东西到底怎么串起来的人。全流程我会按实际动手顺序走一遍包括那些 CubeMX 图形界面里根本不会提示、但少写一行就黑屏的细节。2. 时序参数整篇文章最不能拍脑袋的地方2.1 把一行一帧拆成四个阶段RGB 屏的时序说白了就是一行里除了有效像素还得留出空白时间给电子束或者说给驱动 IC回位。这个空白期分四段水平和垂直各两段HSYNC 宽度HSW行同步脉冲持续的像素时钟数告诉屏这一行结束了准备下一行。HBPHorizontal Back Porch行同步之后到第一个有效像素之间的空闲。有效宽度Active Width真正显示的像素数800。HFPHorizontal Front Porch最后一个有效像素到下一个 HSYNC 之间的空闲。垂直方向同理VSW、VBP、Active Height480、VFP。真正决定像素时钟的是这四个数加起来的总和HTotal HSW HBP ActiveW HFP VTotal VSW VBP ActiveH VFP PixelClock HTotal × VTotal × RefreshRate拿一块常见的 800×480 屏举例规格书给的典型值是 HSW48、HBP88、HFP40、VSW3、VBP32、VFP13。那么HTotal 48 88 800 40 976 VTotal 3 32 480 13 528 PixelClock 976 × 528 × 60 ≈ 30.9 MHz这 30.9MHz 就是你要在 CubeMX 的 LTDC 时钟配置里凑出来的目标值。这里最容易犯的错是直接拿 800×480×60 23MHz 去配结果画面能出但整体偏左偏上、右边和下边有黑边或者干脆撕裂。因为屏是按 HTotal×VTotal 来算节拍的你少算了消隐区它收到的像素就比它期待的少。2.2 从像素时钟反推 CubeMX 的时钟树CubeMX 里 LTDC 的时钟源叫 LCD-TFT clock它来自 PLLSAIF4/F7或 PLL3H7。以 F429 为例路径是 HSE → PLLSAI → PLLSAIDIVR → LTDC。PLLSAIDIVR 只能取 2、4、8、16 这四个分频值所以你不能精确得到 30.9MHz只能取最接近的。假设 PLLSAI 的 VCO 输出是 192MHz选 DIVR4得到 48MHzDIVR8得到 24MHz。30.9 落在中间两个都不理想。这时候要反过去调 PLLSAI 的 N、M、P 参数让 VCO 输出变成比如 123.6MHz 再除 4 —— 但 VCO 本身有范围限制一般 100~432MHz不是随便填。我的做法是先在纸上列出可选组合挑误差最小的。比如 VCO192MHz、DIVR8 → 24MHz比目标低 22%屏可能还能点亮但刷新率掉到 46Hz 左右会有肉眼可见的闪VCO248MHz、DIVR8 → 31MHz误差 0.3%这个就很好。CubeMX 的时钟树页面里选中 LTDC 后右下角会显示当前实际频率改一个参数看一眼几秒钟就能试出来。提示屏的规格书通常会写典型值 33.3MHz范围 25~40MHz。这个范围内都能点亮不是必须卡死在标称值。刷新率偏低会闪偏高可能让某些屏的驱动 IC 发热或者出现竖条纹尽量往中间靠。垂直方向还有一层实际刷新率 PixelClock / (HTotal × VTotal)。你配完时钟之后务必用这个式子回算一遍。如果算出来是 48Hz 而不是 60Hz说明时钟没配到位屏上会看到明显的滚动条纹——那是刷新率和环境光频率拍频的结果。2.3 显存带宽与 SDRAM 这笔账LTDC 是无脑读显存的硬件它每个像素时钟读一次不管你 CPU 在干什么。800×480、RGB5652 字节/像素、60Hz一秒钟要读800 × 480 × 2 × 60 ≈ 46 MB/s如果开双缓冲翻倍到 92MB/s。再加上 CPU 往显存里画图、DMA2D 做填充和混合实际占用会更高。这就是为什么内部 SRAM 根本不够用。800×480×2 768KB 的单帧缓冲F429 总共才 256KB SRAMH743 有 1MB 但也要留给栈、堆和其他缓冲。所以必须外扩 SDRAM而且必须算清楚带宽够不够。一颗 16 位位宽、32MB 容量、166MHz 时钟的 SDRAM理论带宽是 16bit × 166MHz × 2DDR 664MB/s。听起来绰绰有余但那是理想值实际要扣除刷新、预充电、行列切换的开销能跑到 60%~70% 就不错了。460MB/s 对 92MB/s 的显存读取余量是够的但前提是你不能让 CPU 和 LTDC 抢同一块 bank也不能频繁打断 SDRAM 的连续读写。我踩过的一个坑把显存放在 SDRAM 的 bank0把代码里的大数组也放在 bank0结果一动数组画面就抖。后来把显存放 bank1、其他数据放 bank0抖动立刻消失。SDRAM 的多 bank 结构就是为了让不同主设备交替访问时能并行用好了能省很多事。2.4 屏的规格书要重点看哪几页买屏的时候商家一般会给一份 PDF几十页大部分是机械尺寸和光学参数你真正要抄的只有三处位置要抄的内容用途Electrical Characteristics 章节DCLK 频率范围、典型值反推 PLL 分频Timing 章节的表格HSW/HBP/HFP/VSW/VBP/VFP填 LTDC 时序Interface 章节数据位序R0 是 MSB 还是 LSB、DE 模式还是 HV 模式决定接线和极性第三点特别容易被忽略。有的屏 R0 是最低位有的屏 R0 是最高位接反了画面颜色会整体偏色但不一定花屏很容易被误判成颜色格式配错了。还有的屏只支持 DE 模式靠 DE 信号界定有效区HSYNC/VSYNC 可以不接有的必须用 HV 模式。CubeMX 里 LTDC 的 Hardware Synchronization 选项要跟屏对应上选错了就是黑屏。3. CubeMX 配置的动手顺序3.1 先配时钟树别急着点 LTDC很多人打开 CubeMX 第一件事就是去 Connectivity 里找 LTDC勾上然后发现引脚冲突、时钟报红。正确的顺序是自底向上在 System Core → RCC 里把 HSE 设成 Crystal/Ceramic Resonator频率填板子上的晶振值一般是 8MHz 或 25MHz。打开 Clock Configuration 页面先把 SYSCLK 拉到芯片允许的最高频率。找到 FMC 的时钟源H7 是 HCLK/2 或 PLL2F4 是 HCLK/2确认它能给出 SDRAM 需要的频率。最后才去配 LTDC 的 PLL用上一节算出来的目标值。顺序反过来会很难受你先配了 LTDC再去拉高主频PLL 参数一变LTDC 频率跟着飘又得重算。FMC 的时钟要单独说一句。SDRAM 的时序参数TRP、TRCD、TRAS 等都是以纳秒为单位的而你填进 CubeMX 的是时钟周期数所以要先把频率定死再换算。比如 TRCD 15nsFMC 时钟 100MHz周期 10ns填 2 就够如果时钟是 90MHz周期 11.1ns15/11.1 1.35必须向上取整填 2。所有时序参数都要向上取整取小了一定不稳。3.2 FMC/SDRAM 控制器的参数怎么填CubeMX 里 FMC 的 SDRAM 配置页有一长串参数看着吓人其实分三类第一类是几何参数直接照 SDRAM 手册抄行地址位数、列地址位数、bank 数、数据位宽。以常见的 32MB、16 位颗粒为例通常是 13 位行、9 位列、4 个 bank。第二类是时序参数按上一节的方法从纳秒换算成周期数参数含义典型 ns100MHz 下填几TMRD加载模式寄存器到激活的延迟2 tCK2TXSR退出自刷新时间707TRAS行激活到预充电425TRC行循环周期606TWR写恢复时间152TRP预充电周期152TRCD行到列延迟152第三类是刷新参数COUNT 值 刷新周期 × FMC 时钟频率 / 行数。比如 64ms 刷新周期、8192 行、100MHz64000000ns / 100ns / 8192 ≈ 78填 78 或者 80 都行宁大勿小。填完之后CubeMX 生成的MX_FMC_Init()里会有一串FMC_SDRAM_Timing结构体赋值。这里有个隐蔽的坑CubeMX 只生成初始化代码不生成发命令的代码。SDRAM 上电后必须按顺序发一串命令时钟配置使能 → 上电延时 → 预充电全部 → 连续 8 次自动刷新 → 加载模式寄存器 → 设置刷新计数器。这段代码你必须在MX_FMC_Init()末尾手动补上。/* SDRAM 上电初始化序列手写在 MX_FMC_Init 末尾 */ FMC_SDRAM_CommandTypeDef cmd {0}; cmd.CommandMode FMC_SDRAM_CMD_CLK_ENABLE; cmd.CommandTarget FMC_SDRAM_CMD_TARGET_BANK1; cmd.AutoRefreshNumber 1; cmd.ModeRegisterDefinition 0; HAL_SDRAM_SendCommand(hsdram1, cmd, 0xFFFF); HAL_Delay(1); cmd.CommandMode FMC_SDRAM_CMD_PALL; HAL_SDRAM_SendCommand(hsdram1, cmd, 0xFFFF); cmd.CommandMode FMC_SDRAM_CMD_AUTOREFRESH_MODE; cmd.AutoRefreshNumber 8; HAL_SDRAM_SendCommand(hsdram1, cmd, 0xFFFF); cmd.CommandMode FMC_SDRAM_CMD_LOAD_MODE; cmd.ModeRegisterDefinition 0x220; /* CAS latency 3, burst length 1 */ HAL_SDRAM_SendCommand(hsdram1, cmd, 0xFFFF); HAL_SDRAM_ProgramRefreshRate(hsdram1, 78);ModeRegisterDefinition这个值最容易写错。它的位定义是bit0-2 是 burst lengthbit3 是 burst typebit4-6 是 CAS latency其余位保留。CAS3 对应 0x30burst length1 对应 0加起来是 0x30但很多手册里还会要求写 bit9write burst mode等所以常见值是 0x220 或 0x230。这个值必须查你这颗颗粒的规格书抄错一位就点不亮。补完这段写个简单的读写测试往 SDRAM 起始地址写 0xAA、读回来比对再写 0x55、读回来比对。两个都过说明 SDRAM 通了可以往下走。跳步直接配 LTDC后面出问题你根本分不清是显存坏还是时序错。3.3 LTDC 全局参数与层的填写CubeMX 的 LTDC 配置分两块Global Parameters和Layer Parameters。全局部分填的就是上一节算出来的时序Horizontal Synchronization Width 48Horizontal Back Porch 88Active Width 800Horizontal Front Porch 40Vertical Synchronization Height 3Vertical Back Porch 32Active Height 480Vertical Front Porch 13Horizontal Vertical Synchronization Polarity一般选 Active LowDepolarityDE、Pixel Clock 默认就行极性问题是个高发故障点。HSYNC/VSYNC 选 Active High 还是 Low取决于屏的规格书里同步脉冲是高电平有效还是低电平有效。选反了的典型现象是画面整体偏移、出现横向或纵向的粗黑条。不确定就两个都试一遍改动只在这一个下拉框试错成本很低。Layer 部分填的是这一层画在屏幕的哪个位置、多大、什么格式Window X0/Y0/X1/Y1左上角坐标和右下角坐标注意 X1/Y1 是包含在窗口内的所以 800 宽就是 X00、X1799Pixel FormatRGB565 最常用省显存要 24 位真彩就选 RGB888显存占用翻 1.5 倍Alpha层不透明就填 255Blending Factor不透明时随便选要用叠加的时候再细调窗口坐标写错是黑屏的常见原因。X1 必须 ≥ X0Y1 必须 ≥ Y0且不能超过 Active Width/Height。CubeMX 不会帮你校验填个 X1800 就越界了——LTDC 会认为整个窗口无效一个像素都不输出。3.4 GPIO 复用与引脚分配LTDC 的引脚非常多16 根或 24 根数据线、4 根同步信号、1 根时钟加起来 21~29 个。CubeMX 的引脚图里一勾 LTDC它会自动把可用引脚标绿但自动分配的引脚不一定和你的硬件对得上。我的建议是先看原理图把屏的排线定义列成一张表然后在 CubeMX 的 Pinout 视图里逐个手动点选。点选的时候注意看引脚旁边的复用编号LTDC 在不同引脚上可能对应 AF9、AF14 等不同值选错复用号等于接错线。常见的 800×480 模组是 24 位RGB888但如果你只需要 16 位色可以只接 R3-R7、G2-G7、B3-B7 这 16 根低位不接。这时候两级都要对齐屏那边低位悬空或者接地CubeMX 里 Layer 的 Pixel Format 也要选 RGB565。反过来说如果屏是 24 位接线你选 RGB565会看到画面颜色层次明显不够、渐变出现色带——这是格式不匹配不是屏的问题。提示LTDC 的引脚分布很散画 PCB 的时候要先规划好走线。数据线之间最好等长PCLK 尽量走短否则高频下会出现像素错位。已经画完板子的情况下如果频率跑高了出问题可以先降像素时钟到 20MHz 左右测一遍确认是信号完整性问题还是配置问题。3.5 生成代码后必须手写的几段CubeMX 生成的MX_LTDC_Init()只是把参数写进寄存器它不会帮你指定显存地址。层地址默认是 0也就是没有显存所以上电必然是黑屏或者一堆随机噪点。你必须手动调用/* 显存起始地址强制转成 uint32_tSDRAM 基址 偏移 */ #define LCD_FRAME_BUFFER ((uint32_t)0xC0000000) /* 设置第一层的帧缓冲地址 */ HAL_LTDC_SetAddress(hltdc, LCD_FRAME_BUFFER, 0); /* 打开 LTDC */ HAL_LTDC_Enable(hltdc);HAL_LTDC_SetAddress的第三个参数是层号0 表示第一层。如果你开了两层做叠加第二层要单独设地址。再往下是往显存里填东西。最朴素的做法是一个双重循环void LCD_Clear(uint16_t color) { uint16_t *p (uint16_t *)LCD_FRAME_BUFFER; uint32_t n 800 * 480; while (n--) { *p color; } }这段代码能跑但清一次屏要几百毫秒因为 CPU 在逐字节访问外部 SDRAM每次写都要走 FMC 总线效率很低。这就是为什么下一节要引入 DMA2D——不是可选项是必需品。4. 让画面跑顺DMA2D、双缓冲与 Cache4.1 DMA2D 到底省了什么DMA2D 是个专门做 2D 图形搬运的硬件它能干三件事纯色填充、内存到内存的块拷贝可带格式转换、带 Alpha 混合的双层合成。这三件事 LTDC 自己不会做CPU 做又太慢所以中间加了这个加速器。用 DMA2D 做纯色填充代码从上面那个 while 循环变成void LCD_Fill_DMA2D(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint16_t color) { DMA2D-CR 0x00000000UL | (1 9); /* 寄存器到存储器模式 */ DMA2D-OMAR LCD_FRAME_BUFFER (y * 800 x) * 2; DMA2D-OOR 800 - w; /* 行偏移跳过窗口外的像素 */ DMA2D-NLR (w 16) | h; /* 宽高 */ DMA2D-OPFCCR DMA2D_OUTPUT_RGB565; DMA2D-OCOLR color; DMA2D-CR | DMA2D_CR_START; while (DMA2D-CR DMA2D_CR_START); }OOROutput Offset Register这个参数是精髓。它告诉 DMA2D每写完一行指针要多跳几个像素。因为你只填屏幕中间一块 200×100 的区域写完这 200 个像素之后下一行要跳过右边 600 个像素才能接上正确的地址。填 0 的话填充区域会变成一条斜的、跨行的色带。实测下来同样清全屏CPU 循环大约 300msDMA2D 大约 8ms差了近 40 倍。做动画或者刷 UI 的时候这个差距是能用和不能用的区别。DMA2D 还有一个隐藏功能带格式转换的拷贝。你可以把存在 Flash 里的 RGB888 图片直接搬到显存里转成 RGB565 输出一行代码搞定省掉中间缓冲。4.2 双缓冲与撕裂单缓冲的问题在于LTDC 正在读显存第 200 行的时候你 CPU 把第 200 行改了屏幕上就会看到上半部分是旧画面、下半部分是新画面中间一条明显的断层——这就是撕裂tearing。解决办法是双缓冲准备两块显存LTDC 读 A 的时候 CPU 往 B 里画画完在垂直消隐期间把 LTDC 的地址切到 B。切换动作要快而且必须在 VBlank 里做否则切换那一刻也会撕裂。LTDC 提供了两个中断LTDC_IT_LI行中断和LTDC_IT_RR寄存器重载完成。实践中最省事的做法是void HAL_LTDC_LineEventCallback(LTDC_HandleTypeDef *hltdc) { /* 只在最后一行触发接近 VBlank 时刻 */ if (__HAL_LTDC_GET_FLAG(hltdc, LTDC_FLAG_LI)) { __HAL_LTDC_CLEAR_FLAG(hltdc, LTDC_FLAG_LI); HAL_LTDC_Reload(hltdc, LTDC_RELOAD_VERTICAL_BLANKING); } }LTDC_RELOAD_VERTICAL_BLANKING的意思是等你把当前这一帧画完在下一帧开始前再重载寄存器。这样地址切换发生在消隐期人眼看不到。代价是显存翻倍800×480×2×2 1.5MB。SDRAM 有 32MB这点空间不算什么但带宽压力上去了前面算过92MB/s。如果 SDRAM 跑得吃紧可以退一步用单缓冲 局部刷新只重画变化区域能省不少带宽。4.3 Cache 一致性这个坑H7 用户必看F4 系列没有 D-Cache写完显存就完事。但 H7 有 D-CacheCPU 写显存的时候可能只写进了 Cache没落到 SDRAMLTDC 是直接读 SDRAM 的它读到的还是旧数据。表现出来就是清屏代码明明跑了屏幕纹丝不动。两种解法。第一种是每次画完调一次SCB_CleanDCache()简单但慢。第二种是用 MPU 把显存区域配成 Non-cacheable 或者 Write-throughMPU_Region_InitTypeDef MPU_Init {0}; HAL_MPU_Disable(); MPU_Init.Enable MPU_REGION_ENABLE; MPU_Init.Number MPU_REGION_NUMBER0; MPU_Init.BaseAddress 0xC0000000; /* SDRAM 基址 */ MPU_Init.Size MPU_REGION_SIZE_32MB; MPU_Init.SubRegionDisable 0x00; MPU_Init.TypeExtField MPU_TEX_LEVEL1; MPU_Init.AccessPermission MPU_REGION_FULL_ACCESS; MPU_Init.DisableExec MPU_INSTRUCTION_ACCESS_DISABLE; MPU_Init.IsShareable MPU_ACCESS_SHAREABLE; MPU_Init.IsCacheable MPU_ACCESS_NOT_CACHEABLE; MPU_Init.IsBufferable MPU_ACCESS_BUFFERABLE; HAL_MPU_ConfigRegion(MPU_Init); HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);这段代码必须在 LTDC 初始化之前调用而且要在 SDRAM 初始化之后。顺序错了会直接 HardFault。我个人的偏好是 Write-through 模式读走 Cache、写同时落到内存。这样 CPU 读显存比如做碰撞检测、读像素还是快的写也不会丢。Non-cacheable 最稳但读写都慢看具体场景取舍。5. 上电之后的各种不对怎么查5.1 白屏、黑屏、背光亮但无图像这是最高频的问题按概率从高到低排第一位显存地址没设。检查有没有调HAL_LTDC_SetAddress。CubeMX 生成的代码里层地址默认是 0很多人以为它会自动指向某处。没有地址LTDC 读出来全是 0屏幕是黑的RGB565 的 0 是纯黑。第二位SDRAM 没初始化成功。前面说的上电命令序列没补或者 CAS latency 填错。验证方法在 main 里写一个测试函数往 SDRAM 写几个值再读回来用调试器看变量。读不回来就别往下走了先把 FMC 弄通。第三位背光没开。这个听起来很蠢但真的常见。背光是独立的 LED 驱动电路一般是 PWM 控制或者一个使能脚拉高。LTDC 输出的是图像信号和背光完全无关。图像正常但背光没开屏幕就是全黑背光开了但 LTDC 没输出屏幕是均匀的白色或灰色。看屏幕是黑还是灰就能区分是哪一类问题。第四位极性配反。HSYNC/VSYNC/DE/PCLK 四个极性16 种组合虽然大部分屏是固定的那一种但就是有例外。规格书里找 Timing 图看同步信号是下降沿还是上升沿有效。第五位PCLK 频率超出范围。太高了屏的驱动 IC 跟不上会花屏或黑屏太低了刷新率不够会闪。量一下 PCLK 引脚的波形用示波器看频率对不对得上你算的值。5.2 颜色不对红蓝互换或者整体偏色红蓝互换是最典型的接线问题。RGB888 的 24 根线里R 和 B 的位序接反了。改法有两种改硬件重新打板或者飞线或者改软件配 LTDC 层的 Pixel Format如果支持 BGR 格式就直接选不支持就手动调换数据。整体偏绿或偏紫一般是某个颜色通道的高位丢了。比如 G 通道的 G7 没接上绿色最大值只能到一半画面会整体发暗发红。用纯色测试LCD_Clear(0xF800)应该是纯红如果显示出来是暗红或者偏橙就去查 R 通道。渐变出现明显色带是位深不匹配。屏是 24 位接线你配了 RGB565只有 5-6-5 共 16 位有效数据剩下的低位补 0颜色过渡自然断成台阶。这时候要么改配置为 RGB888要么接受这个效果——很多 UI 场景下 16 位色完全够用只是在纯色渐变上会露怯。5.3 画面抖动、撕裂、局部闪烁抖动分两种。一种是整屏在垂直方向轻微上下浮动那是 VSYNC 极性或者刷新率和环境光拍频把像素时钟微调一点或者换一个 DIVR 值试试。另一种是局部区域抖动通常是 SDRAM 带宽不够CPU 在和 LTDC 抢总线试试把显存换到另一个 bank或者降低 CPU 对 SDRAM 的访问频率。撕裂就是前面说的单缓冲问题上双缓冲或者用 VBlank 重载解决。闪烁要区分是背光闪还是图像闪。背光闪就查 PWM 频率低于 200Hz 人眼就能感觉出来一般调到 1kHz 以上图像闪就看刷新率算对没有48Hz 和 60Hz 在白色背景下的观感差别很明显。5.4 一张表把排查路径固化下来现象最可能的原因快速验证方法处理方式全黑背光不亮背光使能没拉高量背光驱动脚电压拉高使能或调 PWM全白无图像LTDC 没使能或层地址为 0调试器看 LTDC_LxCFBAR 寄存器调 SetAddress Enable花屏随机彩点SDRAM 时序不对或没初始化写读测试补上电序列校 CAS画面偏左/偏上右侧黑边HBP/HFP 填反或 Total 算错重新核对规格书时序修正 Porch 值红蓝互换RGB 线序接反清纯红屏看颜色改格式或改线序上下滚动条纹刷新率不是 60Hz用 PixelClock/(HTotal×VTotal) 回算重配 PLL 分频局部撕裂单缓冲边画边显示观察撕裂线位置是否固定上双缓冲 VBlank 重载画面动一下就抖CPU 与 LTDC 抢 SDRAM把显存挪到独立 bank分离 bank 或降 CPU 访问H7 上写完不变D-Cache 没同步关 Cache 再试配 MPU 或手动 Clean提示调试 LTDC 的时候把HAL_LTDC_SetAddress之后立刻填一个纯色比如纯红 0xF800不要放图片、不要放字库。纯色能出来说明时序、地址、使能三件事都对了剩下的才是画图逻辑的问题。这个最小可验证单元的思路能省掉大量瞎猜的时间。5.5 几个只有踩过才知道的细节LTDC 的层窗口不能超过 Active 尺寸超了不会报错就是那个区域不显示。而且 X1/Y1 是闭区间800 宽的屏最大只能填 799。SDRAM 的刷新计数要在上电序列最后设提前设会被后面的命令覆盖掉表现为跑几分钟之后画面开始随机出错——那是电容漏电导致数据丢失。FMC 的 GPIO 速度等级要设成 Very High。默认的 Low 在 100MHz 下上升沿太缓SDRAM 读写会偶发失败。CubeMX 里在 GPIO Settings 里逐个改或者生成代码后在HAL_GPIO_Init里统一改成GPIO_SPEED_FREQ_VERY_HIGH。屏的排线越长能跑的时钟越低。40pin 的 FPC 超过 10cm30MHz 以上就可能有像素错位。这种情况要么降频要么在数据线上串 22Ω 电阻做阻抗匹配。CubeMX 重新生成代码会覆盖手动改的内容。所以所有手写的初始化SDRAM 上电序列、MPU 配置、显存地址设置都要放在/* USER CODE BEGIN */和/* USER CODE END */之间或者单独写成函数在 main 里调用。我一般会在Core/Src下新建一个bsp_lcd.c把屏相关的全放进去CubeMX 怎么生成都不影响。第一帧出来之前有一段几百毫秒的黑屏是正常的因为 SDRAM 初始化、LTDC 配置、显存清零都要时间。但如果超过 2 秒还黑就是有问题别以为是启动慢。最后分享一个我在实际项目里养成的习惯每次改 LTDC 配置只改一个参数。改了时序又改了格式又改了地址出问题就完全不知道是哪一项导致的。改一项、烧一次、看一眼虽然慢但总时长比反复试错要短。这套东西配通一次之后后面换屏、换分辨率都是十分钟的事——时序参数重算一遍时钟树微调一下其余代码一行不用动。真正的门槛不在代码量在于把屏是没脑子的玻璃、主控必须不停喂数据这件事想明白。想明白了CubeMX 里那几十个输入框每一个填什么、为什么这么填就都有答案了。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →