STM32F4 系列 SDRAM 时序参数实测调优:从数据手册到稳定运行的完整推导
👁 3 阅读 · 2026-08-27 · 嵌入式
STM32F4 系列微控制器在扩展外部 SDRAM 时,时序配置不当会导致数据错乱、系统死机等棘手问题。本文以 W9825G6KH 为例,从数据手册关键参数出发,推导 FMC 控制器所需的时序寄存器值,并结合实测波形与稳定性测试,给出完整的调优流程和代码示例,帮助开发者从理论到实践彻底掌握 SDRAM 时序配置。
# 引言
在嵌入式系统中,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 芯片。记住,时序配置没有万能公式,唯有理解原理并反复验证,才能确保系统的可靠性。