# 引言 在嵌入式系统中,SDRAM 常被用作大容量数据缓冲或 LCD 显存。STM32F4 的 FMC(Flexible Memory Controller)支持 SDRAM,但时序参数配置复杂,稍有不慎便会导致随机性错误。本文将从数据手册出发,逐步推导时序寄存器值,并通过实测验证,最终实现稳定运行。 # 1. SDRAM 时序基础 SDRAM 的访问时序由多个延迟参数决定,核心包括: - **tRCD**(RAS to CAS Delay):行激活到列命令的延迟。 - **tRP**(Row Precharge Time):预充电时间。 - **tWR**(Write Recovery Time):写恢复时间。 - **tRC**(Row Cycle Time):行周期时间。 - **tRAS**(Active to Precharge Time):行激活到预充电的最小时间。 STM32F4 的 FMC 通过以下寄存器配置时序: - `FMC_SDRTR`(刷新定时器) - `FMC_SDCR1/2`(控制寄存器,含列选择、突发长度等) - `FMC_SDTR1/2`(时序寄存器,含 TMRD、TXSR、TRAS、TRC、TWR、TRP、TRCD) # 2. 数据手册关键参数提取 以常见的 32MB SDRAM W9825G6KH 为例,其数据手册(-75 速度等级)给出: - 时钟频率最高 133MHz,我们使用 90MHz(STM32F429 的 FMC 时钟)。 - 关键时序(单位 ns): - tRCD = 20ns - tRP = 20ns - tWR = 14ns(或 2 个时钟周期) - tRC = 63ns - tRAS = 42ns - tXSR(自刷新退出到命令) = 70ns - tMRD(模式寄存器加载到命令) = 2 个时钟周期 注意:不同速度等级数值不同,务必以实际芯片手册为准。 # 3. 时序寄存器值推导 FMC 时序寄存器中的值以时钟周期为单位,计算公式为: - 周期数 = ceil(时间 / 时钟周期) 时钟周期 = 1 / 90MHz ≈ 11.11ns。 ## 3.1 计算各参数 - **TMRD**(模式寄存器加载延迟):tMRD = 2 周期,直接取 2。 - **TXSR**(自刷新退出时间):tXSR = 70ns → 70/11.11 ≈ 6.3 → 向上取整为 7。 - **TRAS**:tRAS = 42ns → 42/11.11 ≈ 3.78 → 取 4。 - **TRC**:tRC = 63ns → 63/11.11 ≈ 5.67 → 取 6。 - **TWR**:tWR = 14ns → 14/11.11 ≈ 1.26 → 取 2(注意:TWR 最小为 2 个周期)。 - **TRP**:tRP = 20ns → 20/11.11 ≈ 1.8 → 取 2。 - **TRCD**:tRCD = 20ns → 取 2。 ## 3.2 写入寄存器 在 STM32F4 的 HAL 库中,通过 `FMC_SDRAM_TimingTypeDef` 结构体配置: ```c FMC_SDRAM_TimingTypeDef Timing = {0}; Timing.LoadToActiveDelay = 2; // TMRD Timing.ExitSelfRefreshDelay = 7; // TXSR Timing.SelfRefreshTime = 4; // TRAS(实际为 SelfRefreshTime,但对应 TRAS) Timing.RowCycleDelay = 6; // TRC Timing.WriteRecoveryTime = 2; // TWR Timing.RPDelay = 2; // TRP Timing.RCDDelay = 2; // TRCD ``` 注意:HAL 库中 `SelfRefreshTime` 对应 TRAS,`RowCycleDelay` 对应 TRC。 # 4. 完整初始化代码示例 以下代码基于 STM32F429 和 HAL 库,初始化 FMC SDRAM 并测试读写。 ```c #include "stm32f4xx_hal.h" // SDRAM 基地址(Bank2) #define SDRAM_BANK_ADDR 0xD0000000 #define SDRAM_BANK_SIZE (32 * 1024 * 1024) FMC_SDRAM_HandleTypeDef hsdram1; void SDRAM_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; // 使能 GPIO 时钟 __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOD_CLK_ENABLE(); __HAL_RCC_GPIOE_CLK_ENABLE(); __HAL_RCC_GPIOF_CLK_ENABLE(); __HAL_RCC_GPIOG_CLK_ENABLE(); __HAL_RCC_GPIOH_CLK_ENABLE(); // 配置 FMC 引脚(此处省略具体引脚映射,根据原理图配置) // ... // FMC 时钟使能 __HAL_RCC_FMC_CLK_ENABLE(); // SDRAM 控制器配置 hsdram1.Instance = FMC_SDRAM_DEVICE; hsdram1.Init.SDBank = FMC_SDRAM_BANK2; 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_16; hsdram1.Init.SDClockPeriod = FMC_SDRAM_CLOCK_PERIOD_2; hsdram1.Init.ReadBurst = FMC_SDRAM_RBURST_ENABLE; hsdram1.Init.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_1; // 时序参数(根据推导) FMC_SDRAM_TimingTypeDef Timing = {0}; Timing.LoadToActiveDelay = 2; Timing.ExitSelfRefreshDelay = 7; Timing.SelfRefreshTime = 4; Timing.RowCycleDelay = 6; Timing.WriteRecoveryTime = 2; Timing.RPDelay = 2; Timing.RCDDelay = 2; if (HAL_SDRAM_Init(&hsdram1, &Timing) != HAL_OK) { Error_Handler(); } // 发送初始化命令序列 FMC_SDRAM_CommandTypeDef Command = {0}; // 时钟使能 Command.CommandMode = FMC_SDRAM_CMD_CLK_ENABLE; Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2; Command.AutoRefreshNumber = 1; Command.ModeRegisterDefinition = 0; HAL_SDRAM_SendCommand(&hsdram1, &Command, 1000); // 预充电全部 Command.CommandMode = FMC_SDRAM_CMD_PALL; Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2; Command.AutoRefreshNumber = 1; Command.ModeRegisterDefinition = 0; HAL_SDRAM_SendCommand(&hsdram1, &Command, 1000); // 自动刷新(两次) Command.CommandMode = FMC_SDRAM_CMD_AUTOREFRESH_MODE; Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2; Command.AutoRefreshNumber = 2; Command.ModeRegisterDefinition = 0; HAL_SDRAM_SendCommand(&hsdram1, &Command, 1000); // 加载模式寄存器(设置突发长度、CAS 延迟等) // 模式寄存器值:CAS=3,突发长度=1(或8),顺序突发 uint32_t mode_reg = (3 << 4) | (0 << 7) | (0 << 9); // CAS=3, BL=1 Command.CommandMode = FMC_SDRAM_CMD_LOAD_MODE; Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2; Command.AutoRefreshNumber = 1; Command.ModeRegisterDefinition = mode_reg; HAL_SDRAM_SendCommand(&hsdram1, &Command, 1000); // 设置刷新率(典型值 64ms / 4096 行 = 15.625us,取 15us) // 刷新计数 = (15us * 90MHz) - 20 = 1330 HAL_SDRAM_ProgramRefreshRate(&hsdram1, 1330); } // 测试函数:写入并读取 32 位数据 void SDRAM_Test(void) { uint32_t *sdram = (uint32_t *)SDRAM_BANK_ADDR; uint32_t errors = 0; // 写入 0xDEADBEEF for (uint32_t i = 0; i < SDRAM_BANK_SIZE/4; i++) { sdram[i] = 0xDEADBEEF; } // 读取并校验 for (uint32_t i = 0; i < SDRAM_BANK_SIZE/4; i++) { if (sdram[i] != 0xDEADBEEF) { errors++; } } if (errors == 0) { // 成功 } else { // 失败,需调整时序 } } ``` # 5. 实测调优与验证 理论推导只是起点,实际 PCB 走线、温度、电压都会影响时序。推荐以下调优步骤: 1. **基础测试**:先用上述配置运行简单的读写测试,如全地址写读、随机地址写读。 2. **边界扫描**:逐步增大或减小关键时序参数(如 TRCD、TRP),观察错误率变化。 3. **压力测试**:长时间运行(如 24 小时)并循环读写,确保无偶发错误。 4. **使用示波器**:测量 SDRAM 时钟、数据线波形,检查建立/保持时间是否满足。 常见问题及对策: - **数据错误**:优先增加 TRCD 和 TRP 值。 - **刷新失败**:调整刷新率,确保小于 64ms/行。 - **初始化失败**:检查模式寄存器设置,CAS 延迟必须与硬件匹配。 # 6. 注意事项 - 不同 SDRAM 芯片的时序参数差异较大,务必查阅实际数据手册。 - FMC 时钟频率越高,时序余量越小,建议先使用较低频率(如 90MHz)验证。 - 模式寄存器中的 CAS 延迟必须与 FMC 的 `ReadPipeDelay` 匹配,否则读取数据会错位。 - 刷新率设置不能过大或过小,过大会导致刷新频繁占用带宽,过小则数据丢失。 - 在调试时,建议使用内存映射方式访问 SDRAM,便于观察变量。 # 结语 通过从数据手册提取参数、推导时序寄存器值,并结合实测调优,我们可以让 STM32F4 稳定运行外部 SDRAM。本文提供的流程和代码可作为模板,适用于大多数 SDRAM 芯片。记住,时序配置没有万能公式,唯有理解原理并反复验证,才能确保系统的可靠性。