4. 帧缓冲对齐问题:地址错位引发的花屏

花屏问题里有一种特别隐蔽的情况——画面整体偏移,像是被谁往右推了一把,或者颜色条错位。我早年调试一个 480×272 的工业屏时就遇到过,折腾了两天才发现,罪魁祸首居然是帧缓冲地址没对齐。

说白了,硬件 DMA 在搬运像素数据时,对起始地址有对齐要求。你给它一个没对齐的地址,它搬运的每一行数据都会错位,反映到屏幕上就是花屏。

4.1 为什么需要地址对齐?

TouchGFX 底层依赖 LTDC(LCD 控制器)或 DMA2D 来刷新屏幕。这些硬件模块在读取帧缓冲时,通常以 32 字节或 64 字节为单位突发传输。如果起始地址不是对齐的,硬件会做两件事之一:

  • 自动截断:丢弃不对齐的部分,导致画面左移
  • 错误拼接:把相邻行的数据混在一起,出现颜色错位

你想想看,一个像素占 2 字节(RGB565)或 4 字节(ARGB8888),如果地址没对齐到 32 字节边界,硬件读取的第一个突发可能只拿到半行数据,然后从错误的位置开始拼接——花屏就这么来了。

对齐规则速查

像素格式 每像素字节数 推荐对齐 常见 MCU
RGB565 2 32 字节 STM32F4/F7/H7
ARGB8888 4 64 字节 STM32H7 / i.MX RT
L8(索引色) 1 32 字节 低端屏常用

4.2 对齐问题的典型表现

我在项目中遇到过三种典型的对齐花屏,你可以对照排查:

  1. 整屏右移:画面整体向右偏移几个像素,左侧出现竖条色带。这是最常见的,因为地址没对齐导致第一个突发读取丢失了前几个像素。
  2. 颜色条错位:屏幕出现垂直的彩色条纹,每条纹宽度固定。这是因为硬件把不同行的数据拼接到了同一行。
  3. 闪烁 + 花屏:画面间歇性出现杂点或色块,刷新几次后恢复正常。这种最坑人,因为不是每次都复现,我当初差点以为是硬件虚焊。

注意:对齐问题导致的闪烁和电磁干扰导致的闪烁很像。区分方法很简单——对齐问题在画面静止时也会闪,而电磁干扰通常在刷新瞬间出现。

4.3 如何检查对齐状态?

检查对齐其实就一行代码的事。我个人习惯在帧缓冲初始化后立即打印地址:

// 检查帧缓冲地址是否 32 字节对齐
uint32_t fb_addr = (uint32_t)LCD_FRAME_BUFFER;
if (fb_addr % 32 != 0) {
    printf("错误:帧缓冲地址未对齐!地址 = 0x%08X\n", fb_addr);
    printf("偏移量 = %u 字节\n", fb_addr % 32);
}

如果偏移量不是 0,说明地址没对齐。偏移量是多少,画面就会偏移多少个像素(按像素格式换算)。

举个例子:RGB565 格式下,地址偏移 4 字节,相当于偏移 2 个像素。你会在屏幕左侧看到 2 像素宽的竖条,颜色来自上一行的末尾数据。

4.4 修复方法:强制对齐分配

修复对齐问题,核心思路是让帧缓冲的起始地址满足硬件要求。我常用的方法有三种:

方法一:使用对齐宏(推荐)

在链接脚本或代码中显式对齐:

// 使用 __attribute__ 对齐到 64 字节
#define FRAME_BUFFER_ALIGN __attribute__((aligned(64)))

FRAME_BUFFER_ALIGN
static uint16_t framebuffer[480 * 272];  // RGB565 格式

这种方法最可靠,编译器会保证地址对齐。我在 STM32H743 上一直这么用,从来没出过问题。

方法二:手动对齐分配

如果使用动态分配,需要多分配一些空间,然后手动对齐:

// 分配时多留 64 字节,然后对齐
uint8_t* raw = (uint8_t*)malloc(size + 64);
uint8_t* aligned = (uint8_t*)(((uint32_t)raw + 63) & ~63);

嗯,这里要注意——释放内存时必须释放 raw,而不是 aligned。否则会内存泄漏。

方法三:调整链接脚本

对于使用 SDRAM 的场景,可以在链接脚本中指定帧缓冲段的起始地址:

/* 在 .ld 文件中 */
.framebuffer : {
    . = ALIGN(64);
    *(.framebuffer)
} > SDRAM

然后在代码中:

__attribute__((section(".framebuffer")))
static uint32_t framebuffer[480 * 272 / 2];  // ARGB8888

我的经验:如果你用的是 CubeMX 生成的代码,检查一下 HAL_DSI_Init() 或 HAL_LTDC_Init() 的参数。有些 CubeMX 版本默认不对齐帧缓冲地址,需要手动在配置里勾选 "Frame Buffer Address Alignment"。

4.5 对齐问题的知识图谱

下面这张图梳理了帧缓冲对齐问题的完整链路,从根因到修复,一目了然:

帧缓冲对齐问题知识图谱 硬件 DMA 对齐要求 整屏右移 + 左侧竖条 颜色条错位 / 拼接错误 间歇性闪烁 + 花屏 检查:地址 % 32/64 == 0? __attribute__((aligned)) 手动对齐分配 链接脚本 ALIGN 验证:打印地址 + 目测画面

4.6 避坑指南

我曾经在一个量产项目上吃过亏,这里列几个容易踩的坑:

  • 坑 1:SDRAM 初始化顺序——如果帧缓冲在 SDRAM 中,一定要等 SDRAM 初始化完成后再分配地址。否则地址可能是随机的,对齐检查会通过,但实际数据是乱的。
  • 坑 2:双缓冲切换——使用双缓冲时,两个缓冲区的地址都要对齐。我见过有人只对齐了第一个,第二个没对齐,结果切换时花屏。
  • 坑 3:Cache 一致性——如果 MCU 有 Cache(比如 STM32H7),对齐地址后还要注意 Cache 刷新。不对齐时 Cache 操作可能出错,对齐后反而暴露了 Cache 问题。

快速验证技巧:如果你不确定是不是对齐问题,可以临时把帧缓冲地址强制改成对齐的地址(比如在 SDRAM 末尾找一个对齐位置)。如果花屏消失,那就是对齐问题;如果还在,排查其他原因。

对齐问题说难不难,说简单也不简单。它不像代码逻辑错误那样有明确的报错信息,更多时候需要你凭经验去判断。嗯,记住一点——只要画面出现规律性的偏移或错位,先查对齐,十有八九能命中。