# STM32F4 系列 SDRAM 运行内存的总线冲突与性能调优 ## 一、为什么 SDRAM 会引发总线冲突? STM32F4 内部 SRAM 位于 CPU 核心总线(如 I-Bus、D-Bus)上,访问延迟极低(零等待)。而外部 SDRAM 挂在 FMC(Flexible Memory Controller)上,通过 AHB 总线矩阵与 CPU、DMA 等主设备相连。当 CPU 频繁访问 SDRAM 时,FMC 需要占用 AHB 总线,同时 SDRAM 自身的刷新(Refresh)操作也会抢占总线周期,导致冲突。 **核心冲突点:** - **CPU 与 DMA 竞争**:DMA 传输(如 ADC 采样、UART 接收)与 CPU 执行代码(从 SDRAM 取指)同时访问 FMC,总线矩阵按优先级仲裁,造成等待。 - **SDRAM 刷新开销**:SDRAM 必须周期性刷新(如 64ms 刷新 4096 行),刷新期间 FMC 暂停所有访问,若刷新配置不当,会显著增加延迟。 - **FMC 数据线宽度**:若配置为 16 位数据总线,一次 32 位访问需拆分为两次,增加总线占用时间。 ## 二、硬件与 FMC 配置优化 ### 1. 正确设置 FMC 时序参数 SDRAM 时序参数(如 TRCD、TRP、TRC)直接影响访问速度。以 W9825G6KH 为例(133MHz 时钟): ```c // FMC SDRAM 初始化结构体(以 72MHz FMC 时钟为例) FMC_SDRAM_TimingTypeDef Timing = {0}; Timing.LoadToActiveDelay = 2; // tRSC: 加载模式寄存器到激活 Timing.ExitSelfRefreshDelay = 7; // tXS: 退出自刷新 Timing.SelfRefreshTime = 4; // tSR: 自刷新时间 Timing.RowCycleDelay = 7; // tRC: 行周期 Timing.WriteRecoveryTime = 2; // tWR: 写恢复 Timing.RPDelay = 2; // tRP: 预充电 Timing.RCDDelay = 2; // tRCD: 行到列延迟 // 使能 SDRAM 时钟 __HAL_RCC_FMC_CLK_ENABLE(); // 初始化 FMC SDRAM 控制器(Bank1,16位数据线) // 注意:若使用 32 位数据线,需确保 SDRAM 芯片支持并正确连接 ``` **要点:** - 时序参数需根据 SDRAM 数据手册和 FMC 时钟频率计算,过紧会导致数据错误,过松则浪费性能。 - 建议使用 `HAL_SDRAM_Init()` 后,通过 `HAL_SDRAM_SendCommand()` 发送模式寄存器配置(如 CAS=3,突发长度=1)。 ### 2. 数据总线宽度选择 - **16 位总线**:节省引脚,但 32 位访问需两次传输,适合对容量要求高、性能要求中等的场景。 - **32 位总线**:匹配 CPU 字长,单次访问 32 位,但需更多引脚(如 F4 系列 100 脚以上封装)。 **推荐**:若性能优先,使用 32 位模式,并确保 SDRAM 芯片支持(如两片 16 位并联)。 ## 三、软件层面的性能调优 ### 1. 利用 Cache 减少访问次数 STM32F4 内置 4KB I-Cache 和 4KB D-Cache(需启用)。将 SDRAM 区域配置为 Cacheable,可缓存常用数据,减少 FMC 访问频率。 ```c // 启用 D-Cache 和 I-Cache SCB_EnableICache(); SCB_EnableDCache(); // 将 SDRAM 区域设置为 Cacheable(MPU 配置) MPU_Region_InitTypeDef MPU_InitStruct = {0}; MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0xC0000000; // SDRAM 起始地址 MPU_InitStruct.Size = MPU_REGION_SIZE_8MB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; // 可缓存 MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE; MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); HAL_MPU_Enable(MPU_CONTROL_PRIVILEGED_DEFAULT); ``` **注意**:D-Cache 启用后,DMA 访问 SDRAM 时需注意缓存一致性。若 DMA 写入 SDRAM,CPU 读取前需 `SCB_CleanDCache()` 或 `SCB_InvalidateDCache()`。 ### 2. 使用 DMA 分流 CPU 负载 对于大数据块传输(如 LCD 帧缓冲、文件系统读写),使用 DMA 而非 CPU 逐字拷贝,可释放 CPU 并减少总线占用时间。 ```c // 示例:使用 DMA2 从 SDRAM 传输数据到 LCD DMA_HandleTypeDef hdma; hdma.Instance = DMA2_Stream0; hdma.Init.Channel = DMA_CHANNEL_0; 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; HAL_DMA_Init(&hdma); // 启动传输(源地址为 SDRAM,目的地址为外设) HAL_DMA_Start(&hdma, (uint32_t)sdram_buffer, (uint32_t)lcd_buffer, size); ``` **要点:** - 配置 DMA 优先级为高,确保实时性。 - 若同时有多个 DMA 流,合理分配优先级(如音频 > 显示 > 存储)。 ### 3. 调整 FMC 调度策略 FMC 支持写入突发(Write Burst)和读取突发(Read Burst),启用后可连续访问多个地址,减少总线切换开销。 ```c // 在 FMC SDRAM 初始化时设置突发模式 sdramHandle.Init.SDBank = FMC_SDRAM_BANK1; sdramHandle.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_8; sdramHandle.Init.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_12; sdramHandle.Init.MemoryDataWidth = FMC_SDRAM_MEM_BUS_WIDTH_32; sdramHandle.Init.BurstReadMode = FMC_SDRAM_BURST_READ_ENABLE; // 启用突发读 sdramHandle.Init.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE; ``` **注意**:突发长度需与 SDRAM 模式寄存器一致(通常为 1 或 8),且 CPU 访问地址需对齐。 ### 4. 避免频繁的 SDRAM 刷新冲突 SDRAM 刷新是自动的,但可通过调整刷新率(`RefreshRate`)来平衡。若刷新过于频繁,会占用大量总线时间;若过少,数据可能丢失。 ```c // 设置刷新计数(以 72MHz 为例,64ms 刷新 4096 行) sdramHandle.Init.RefreshRate = 1386; // 计算公式:刷新周期 = (刷新时间 * 时钟频率) / 行数 - 20 ``` **经验值**:在 72MHz 下,`RefreshRate` 通常为 1386 左右,可根据实际稳定性微调。 ## 四、完整代码示例:SDRAM 初始化和性能测试 ```c #include "stm32f4xx_hal.h" SDRAM_HandleTypeDef hsdram1; void SDRAM_Init(void) { // 1. 配置 FMC 时钟和引脚(略,参考 HAL 示例) // 2. 初始化 SDRAM 控制器 hsdram1.Instance = FMC_SDRAM_BANK1; hsdram1.Init.SDBank = FMC_SDRAM_BANK1; hsdram1.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_8; hsdram1.Init.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_12; hsdram1.Init.MemoryDataWidth = FMC_SDRAM_MEM_BUS_WIDTH_32; hsdram1.Init.BurstReadMode = FMC_SDRAM_BURST_READ_ENABLE; hsdram1.Init.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE; hsdram1.Init.RefreshRate = 1386; FMC_SDRAM_TimingTypeDef Timing = {0}; Timing.LoadToActiveDelay = 2; Timing.ExitSelfRefreshDelay = 7; Timing.SelfRefreshTime = 4; Timing.RowCycleDelay = 7; Timing.WriteRecoveryTime = 2; Timing.RPDelay = 2; Timing.RCDDelay = 2; if (HAL_SDRAM_Init(&hsdram1, &Timing) != HAL_OK) { Error_Handler(); } // 3. 发送模式寄存器命令(CAS=3,突发长度=1) FMC_SDRAM_CommandTypeDef Command = {0}; Command.CommandMode = FMC_SDRAM_CMD_LOAD_MODE; Command.ModeRegisterDefinition = 0x230; // CAS=3, 突发长度=1 Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1; Command.AutoRefreshNumber = 1; Command.ModeRegisterDefinition = 0x230; HAL_SDRAM_SendCommand(&hsdram1, &Command, 100); // 4. 发送预充电命令 Command.CommandMode = FMC_SDRAM_CMD_PRECHARGE_ALL; HAL_SDRAM_SendCommand(&hsdram1, &Command, 100); // 5. 自动刷新 Command.CommandMode = FMC_SDRAM_CMD_AUTOREFRESH; Command.AutoRefreshNumber = 8; HAL_SDRAM_SendCommand(&hsdram1, &Command, 100); } // 性能测试:比较 SDRAM 和内部 SRAM 的读写速度 void PerformanceTest(void) { uint32_t *sdram_ptr = (uint32_t*)0xC0000000; uint32_t *sram_ptr = (uint32_t*)0x20000000; volatile uint32_t sum = 0; // 写入测试(10000 次) uint32_t start = HAL_GetTick(); for (int i = 0; i < 10000; i++) sdram_ptr[i] = i; uint32_t sdram_write_time = HAL_GetTick() - start; start = HAL_GetTick(); for (int i = 0; i < 10000; i++) sram_ptr[i] = i; uint32_t sram_write_time = HAL_GetTick() - start; // 读取测试 start = HAL_GetTick(); for (int i = 0; i < 10000; i++) sum += sdram_ptr[i]; uint32_t sdram_read_time = HAL_GetTick() - start; start = HAL_GetTick(); for (int i = 0; i < 10000; i++) sum += sram_ptr[i]; uint32_t sram_read_time = HAL_GetTick() - start; printf("SDRAM Write: %d ms, SRAM Write: %d ms\r\n", sdram_write_time, sram_write_time); printf("SDRAM Read: %d ms, SRAM Read: %d ms\r\n", sdram_read_time, sram_read_time); } ``` ## 五、注意事项与常见陷阱 - **缓存一致性**:启用 D-Cache 后,DMA 与 CPU 共享 SDRAM 时,必须手动维护缓存(`SCB_CleanDCache` / `SCB_InvalidateDCache`),否则数据错乱。 - **内存对齐**:SDRAM 访问建议 32 位对齐,避免非对齐访问导致总线多次传输。 - **初始化顺序**:必须先初始化 SDRAM 控制器,再发送模式寄存器命令,否则 SDRAM 无法正常工作。 - **刷新率调整**:若系统时钟变化(如降频),需重新计算 `RefreshRate`,否则数据丢失。 - **调试技巧**:使用逻辑分析仪观察 FMC 总线波形,确认时序是否满足 SDRAM 要求。 ## 六、总结 通过合理配置 FMC 时序、启用 Cache、使用 DMA 分流以及调整刷新策略,可以显著降低 STM32F4 使用 SDRAM 时的总线冲突,提升系统整体性能。实际项目中,建议结合具体应用场景(如 LCD 显示、音频缓冲)进行针对性优化,并利用性能测试代码量化效果。