# 引言 在嵌入式图形界面开发中,STM32F4 凭借其强大的 Cortex-M4 内核和丰富的外设,常被用于驱动 TFT-LCD 或 OLED 屏。当分辨率提升至 480x272 甚至 800x480 时,内部 SRAM 容量捉襟见肘,外部 SDRAM 成为帧缓冲的必然选择。然而,在 168MHz 主频下,CPU、DMA 和 LCD 控制器(LTDC 或 FSMC)同时访问 SDRAM 时,总线仲裁延迟会显著影响系统实时性,导致画面撕裂、卡顿甚至数据错误。本文基于实际测试,剖析延迟来源并给出可落地的优化方案。 # 一、总线架构与仲裁机制 STM32F4 采用 AHB 总线矩阵,连接 CPU、DMA1/DMA2、以太网 MAC 等主设备到存储控制器(如 FMC)。当多个主设备同时请求访问 SDRAM 时,总线矩阵的仲裁器根据固定优先级(默认 CPU 最高)或可编程优先级进行裁决。延迟主要由两部分组成: - **仲裁等待**:主设备等待总线授权的时间,取决于当前总线占用者。 - **SDRAM 刷新与预充电**:SDRAM 的周期性刷新(如 64ms 内 8192 次)和行切换(tRP、tRCD)会阻塞访问。 实测中,当 CPU 连续执行内存拷贝指令时,DMA 的 SDRAM 访问延迟可高达 120ns(约 20 个 CPU 周期),而 LCD 控制器通过 LTDC 的突发读取延迟则相对稳定,但若与刷新周期重叠,会出现 200ns 以上的峰值延迟。 # 二、实测环境与方法 - **硬件**:STM32F429ZI(主频 168MHz),外部 SDRAM(W9825G6KH,容量 32MB,时钟 84MHz)。 - **软件**:STM32CubeIDE 1.15,HAL 库,裸机测试。 - **测量方法**:在 GPIO 引脚上输出高电平标记访问开始,通过逻辑分析仪记录时间戳。分别测量以下场景: 1. CPU 连续读 SDRAM(循环读取 4KB)。 2. DMA2 从 SDRAM 搬运数据到内部 SRAM(突发 16 字节)。 3. CPU 与 DMA 同时访问 SDRAM(模拟帧缓冲更新)。 测试代码片段(测量 CPU 读延迟): ```c volatile uint32_t *sdram_base = (uint32_t *)0xD0000000; volatile uint32_t data; // 开启 GPIO 高电平 GPIOE->BSRRL = GPIO_PIN_1; for (int i = 0; i < 1024; i++) { data = sdram_base[i]; // 直接读 } GPIOE->BSRRH = GPIO_PIN_1; ``` # 三、延迟实测数据与分析 | 场景 | 平均延迟 (ns) | 最大延迟 (ns) | 说明 | |------|---------------|---------------|------| | CPU 独占读 | 28 | 45 | 受 SDRAM 行切换影响 | | DMA 独占读 | 35 | 60 | 突发模式效率较高 | | CPU+DMA 并发 | 85 | 210 | 仲裁竞争明显 | | 并发且遇刷新 | 120 | 320 | 刷新周期叠加 | 分析: - 并发时延迟增加约 3 倍,主要因为仲裁器需等待当前访问完成,且 SDRAM 控制器无法并行处理多个请求。 - 最大延迟出现在刷新期间,因为刷新操作优先级最高,且持续约 60ns。 - CPU 的连续访问会占用大量总线带宽,导致 DMA 饥饿。 # 四、优化策略 ## 1. 调整 DMA 优先级与突发模式 将 DMA2 的优先级设为最高(高于 CPU),并配置为 16 字节突发模式。这能减少 DMA 的等待时间,但需注意避免 CPU 长时间被阻塞。 ```c DMA_HandleTypeDef hdma; hdma.Init.Priority = DMA_PRIORITY_HIGH; // 或 VERY_HIGH hdma.Init.Mode = DMA_NORMAL; hdma.Init.BurstMode = DMA_BURST_16; // 突发 16 字节 ``` ## 2. 优化 SDRAM 时序参数 在 FMC 配置中,适当放宽 tRCD、tRP 等参数可减少行切换延迟,但需根据 SDRAM 数据手册谨慎调整。实测中,将 tRCD 从 6 周期降至 5 周期(仍满足规格)后,平均延迟降低 10%。 ```c FMC_SDRAM_TimingTypeDef timing; timing.LoadToActiveDelay = 2; timing.ExitSelfRefreshDelay = 7; timing.SelfRefreshTime = 4; timing.RowCycleDelay = 7; // 原为 8 timing.WriteRecoveryTime = 2; timing.RPDelay = 2; timing.RCDDelay = 5; // 原为 6 ``` ## 3. 内存布局重映射 将帧缓冲放置在 SDRAM 的固定区域,并确保行大小对齐(如 1024 字节),避免跨行访问。同时,将关键数据(如 DMA 描述符)放在内部 SRAM,减少对 SDRAM 的竞争。 ```c // 链接脚本中定义 SDRAM 区域 MEMORY { SDRAM (rwx) : ORIGIN = 0xD0000000, LENGTH = 32M } // 帧缓冲地址对齐 uint8_t framebuffer[480*272*2] __attribute__((section(".sdram"), aligned(1024))); ``` ## 4. 使用 LTDC 的专用 SDRAM 接口 若使用 LTDC,可启用其专用 FIFO 和突发传输,减少对总线的占用。同时,将 LTDC 的优先级设为低于 DMA 但高于 CPU,平衡实时性。 # 五、完整示例:DMA 搬运帧缓冲 以下代码演示了如何配置 DMA2 从 SDRAM 读取一帧数据到内部 SRAM,并触发中断。 ```c #include "stm32f4xx_hal.h" void SDRAM_To_SRAM_DMA(void) { // 假设 hdma 已初始化,源地址为 SDRAM 帧缓冲,目的为内部 SRAM hdma.Init.Direction = DMA_MEMORY_TO_MEMORY; hdma.Init.PeriphInc = DMA_PINC_ENABLE; hdma.Init.MemInc = DMA_MINC_ENABLE; hdma.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; hdma.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; hdma.Init.Mode = DMA_NORMAL; hdma.Init.Priority = DMA_PRIORITY_VERY_HIGH; HAL_DMA_Init(&hdma); // 启动传输,长度为 480*272*2/4 字 HAL_DMA_Start(&hdma, (uint32_t)0xD0000000, (uint32_t)internal_buf, 480*272*2/4); // 等待传输完成(可加中断) HAL_DMA_PollForTransfer(&hdma, HAL_DMA_FULL_TRANSFER, HAL_MAX_DELAY); } ``` # 六、注意事项 - **刷新周期**:SDRAM 刷新不可关闭,但可通过调整刷新率(如从 64ms 改为 128ms)减少冲突,需确保数据保持时间。 - **缓存一致性**:若启用 D-Cache,需在 DMA 传输前后执行 `SCB_CleanDCache()` 和 `SCB_InvalidateDCache()`,否则会读到脏数据。 - **优先级权衡**:提高 DMA 优先级可能影响 CPU 实时性,在中断密集场景需测试。 - **硬件版本**:不同 STM32F4 型号(如 F429 vs F407)的 FMC 时序略有差异,需参考对应数据手册。 # 结语 通过实测,我们明确了 STM32F4 在 168MHz 下 SDRAM 帧缓冲的总线仲裁延迟主要源于并发访问和刷新周期。调整 DMA 优先级、优化 SDRAM 时序、合理布局内存,可显著降低延迟,提升图形刷新流畅度。建议开发者根据实际应用场景,结合逻辑分析仪和示波器进行针对性调优。