引言
在嵌入式系统中,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 结构体配置:
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 并测试读写。
#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 走线、温度、电压都会影响时序。推荐以下调优步骤:
- 基础测试:先用上述配置运行简单的读写测试,如全地址写读、随机地址写读。
- 边界扫描:逐步增大或减小关键时序参数(如 TRCD、TRP),观察错误率变化。
- 压力测试:长时间运行(如 24 小时)并循环读写,确保无偶发错误。
- 使用示波器:测量 SDRAM 时钟、数据线波形,检查建立/保持时间是否满足。
常见问题及对策:
- 数据错误:优先增加 TRCD 和 TRP 值。
- 刷新失败:调整刷新率,确保小于 64ms/行。
- 初始化失败:检查模式寄存器设置,CAS 延迟必须与硬件匹配。
6. 注意事项
- 不同 SDRAM 芯片的时序参数差异较大,务必查阅实际数据手册。
- FMC 时钟频率越高,时序余量越小,建议先使用较低频率(如 90MHz)验证。
- 模式寄存器中的 CAS 延迟必须与 FMC 的
ReadPipeDelay匹配,否则读取数据会错位。 - 刷新率设置不能过大或过小,过大会导致刷新频繁占用带宽,过小则数据丢失。
- 在调试时,建议使用内存映射方式访问 SDRAM,便于观察变量。
结语
通过从数据手册提取参数、推导时序寄存器值,并结合实测调优,我们可以让 STM32F4 稳定运行外部 SDRAM。本文提供的流程和代码可作为模板,适用于大多数 SDRAM 芯片。记住,时序配置没有万能公式,唯有理解原理并反复验证,才能确保系统的可靠性。