引言

STM32F4 系列(如 STM32F407/429)内置 FMC(Flexible Memory Controller),可无缝连接外部 SDRAM,常用于 LCD 帧缓冲、音频缓存或大容量数据暂存。然而,SDRAM 的时序参数(如 TRCD、TRP、TRC 等)若配置不当,轻则性能低下,重则系统随机崩溃。本文以常见的 IS42S16400J(16M×16bit)为例,从 Datasheet 参数到 FMC 寄存器配置,给出完整的推导与实测调优方法。

1. SDRAM 核心时序参数解析

SDRAM 的时序由多个延迟参数决定,STM32F4 的 FMC 控制器通过 FMC_SDRAMTimingInitTypeDef 结构体配置。关键参数如下:

  • TRCD(RAS to CAS Delay):行地址选通到列地址选通的延迟,决定行激活后多久可发送读/写命令。
  • TRP(Row Precharge Delay):预充电命令到下一次行激活的最小间隔。
  • TRC(Row Cycle Time):两次行激活之间的最小间隔,通常 TRC = TRCD + TRP + 额外余量。
  • TRAS(Active to Precharge Delay):行激活到预充电的最小时间,影响刷新效率。
  • TWR(Write Recovery Time):写命令到预充电的最小间隔,防止数据未写入完成。

这些参数在 SDRAM 芯片的 Datasheet 中通常以 ns 为单位给出,而 FMC 配置需要转换为 STM32 的时钟周期数(HCLK 周期)。

2. 从 Datasheet 到 FMC 配置的推导

2.1 获取芯片参数

以 IS42S16400J 为例,其典型时序参数(-7 速度等级)如下:

| 参数 | 最小值 (ns) | 说明 | |------|------------|------| | TRCD | 20 | RAS 到 CAS 延迟 | | TRP | 20 | 预充电周期 | | TRC | 63 | 行周期 | | TRAS | 42 | 行激活到预充电 | | TWR | 14 | 写恢复时间 |

2.2 计算时钟周期数

STM32F4 的 FMC 使用 HCLK 作为时钟源。假设系统主频为 168MHz,则 HCLK = 168MHz,周期 T = 1/168MHz ≈ 5.95ns。

每个时序参数对应的周期数 N 计算公式为:

N = ceil(参数时间 / T) + 1  // 加 1 是为了补偿 FMC 内部的同步延迟

以 TRCD 为例:

TRCD_cycles = ceil(20ns / 5.95ns) + 1 = ceil(3.36) + 1 = 4 + 1 = 5

同理可得:

  • TRP = 5 周期
  • TRC = ceil(63/5.95) + 1 = 11 + 1 = 12 周期
  • TRAS = ceil(42/5.95) + 1 = 8 + 1 = 9 周期
  • TWR = ceil(14/5.95) + 1 = 3 + 1 = 4 周期

2.3 配置 FMC 寄存器

在 STM32 HAL 库中,配置代码如下:

FMC_SDRAM_TimingTypeDef Timing = {0};

Timing.LoadToActiveDelay    = 2;   // tMRD,模式寄存器加载到激活,通常固定为 2
Timing.ExitSelfRefreshDelay = 7;   // tXSR,退出自刷新时间,需根据芯片手册
Timing.SelfRefreshTime      = 5;   // tRAS,自刷新时行激活时间
Timing.RowCycleDelay        = 12;  // TRC
Timing.WriteRecoveryTime    = 4;   // TWR
Timing.RPDelay              = 5;   // TRP
Timing.RCDDelay             = 5;   // TRCD

// 初始化 SDRAM 控制器
SDRAM_InitTypeDef SdramInit = {0};
SdramInit.SDBank             = FMC_SDRAM_BANK1;
SdramInit.ColumnBitsNumber   = FMC_SDRAM_COLUMN_BITS_NUM_8;
SdramInit.RowBitsNumber      = FMC_SDRAM_ROW_BITS_NUM_12;
SdramInit.MemoryDataWidth    = FMC_SDRAM_MEM_BUS_WIDTH_16;
SdramInit.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4;
SdramInit.CASLatency        = FMC_SDRAM_CAS_LATENCY_3;
SdramInit.WriteProtection    = FMC_SDRAM_WRITE_PROTECTION_DISABLE;
SdramInit.SDClockPeriod     = FMC_SDRAM_CLOCK_PERIOD_2; // HCLK/2 = 84MHz
SdramInit.ReadBurst          = FMC_SDRAM_RBURST_DISABLE;
SdramInit.ReadPipeDelay      = FMC_SDRAM_RPIPE_DELAY_1;

HAL_SDRAM_Init(&hsdram, &SdramInit, &Timing);

3. 实测调优:从理论到稳定

3.1 编写内存测试函数

理论值只是起点,实际 PCB 布线、温度、电压波动都会影响稳定性。编写一个遍历写读测试函数,检测数据线和地址线的连通性:

uint8_t SDRAM_Test(uint32_t baseAddr, uint32_t size) {
    uint32_t *p = (uint32_t *)baseAddr;
    uint32_t i;
    // 写入递增模式
    for (i = 0; i < size/4; i++) {
        p[i] = i + 0x12345678;
    }
    // 读取并比较
    for (i = 0; i < size/4; i++) {
        if (p[i] != (i + 0x12345678)) {
            return 1; // 错误
        }
    }
    return 0;
}

3.2 边界扫描与参数调整

  • 降低时钟频率:若测试失败,首先将 SDClockPeriod 从 2(HCLK/2)改为 3(HCLK/3),降低 SDRAM 工作频率,观察是否稳定。
  • 增加余量:将 TRCD、TRP 等参数各加 1-2 个周期,重新测试。通常 TRC 和 TRAS 对稳定性影响最大。
  • CAS Latency:若芯片支持,可尝试降低 CAS 延迟(从 3 改为 2),但需确认芯片规格。

3.3 使用示波器验证

用示波器测量 SDRAM 的 DQS 和 CLK 信号,观察建立/保持时间是否满足要求。理想情况下,数据有效窗口应占整个时钟周期的 60% 以上。若发现毛刺,需检查 PCB 走线长度匹配。

4. 完整代码示例

以下是一个完整的初始化与测试流程(基于 STM32CubeMX 生成的代码):

#include "stm32f4xx_hal.h"

SDRAM_HandleTypeDef hsdram;

void SDRAM_Init(void) {
    // 假设 GPIO 和时钟已在 CubeMX 中配置
    FMC_SDRAM_TimingTypeDef Timing = {0};
    SDRAM_InitTypeDef SdramInit = {0};

    // 时序参数(基于 168MHz HCLK)
    Timing.LoadToActiveDelay    = 2;
    Timing.ExitSelfRefreshDelay = 7;
    Timing.SelfRefreshTime      = 5;
    Timing.RowCycleDelay        = 12;
    Timing.WriteRecoveryTime    = 4;
    Timing.RPDelay              = 5;
    Timing.RCDDelay             = 5;

    SdramInit.SDBank             = FMC_SDRAM_BANK1;
    SdramInit.ColumnBitsNumber   = FMC_SDRAM_COLUMN_BITS_NUM_8;
    SdramInit.RowBitsNumber      = FMC_SDRAM_ROW_BITS_NUM_12;
    SdramInit.MemoryDataWidth    = FMC_SDRAM_MEM_BUS_WIDTH_16;
    SdramInit.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4;
    SdramInit.CASLatency        = FMC_SDRAM_CAS_LATENCY_3;
    SdramInit.WriteProtection    = FMC_SDRAM_WRITE_PROTECTION_DISABLE;
    SdramInit.SDClockPeriod     = FMC_SDRAM_CLOCK_PERIOD_2;
    SdramInit.ReadBurst          = FMC_SDRAM_RBURST_DISABLE;
    SdramInit.ReadPipeDelay      = FMC_SDRAM_RPIPE_DELAY_1;

    if (HAL_SDRAM_Init(&hsdram, &SdramInit, &Timing) != HAL_OK) {
        Error_Handler();
    }

    // 发送模式寄存器设置命令
    uint32_t modeReg = 0x23; // 设置 CAS=3, 突发长度=1
    HAL_SDRAM_SendCommand(&hsdram, FMC_SDRAM_CMD_MODE, FMC_SDRAM_MODE_REGISTER, modeReg, 1000);
}

int main(void) {
    HAL_Init();
    SystemClock_Config(); // 配置 168MHz
    SDRAM_Init();

    // 测试 1MB 区域
    if (SDRAM_Test(0xC0000000, 1024*1024) == 0) {
        // 成功
    } else {
        // 失败,调整时序参数
    }

    while (1);
}

5. 注意事项与常见问题

  • 刷新率:SDRAM 需要周期性刷新(通常 64ms 内 8192 次),FMC 会自动处理,但需确保 RefreshRate 寄存器设置正确。在 HAL 中通过 HAL_SDRAM_ProgramRefreshRate 设置,计算公式为:刷新周期 = (刷新时间 / 行数) / 时钟周期
  • PCB 布线:SDRAM 数据线、地址线、控制线长度差应控制在 ±1mm 内,否则高速时会出现数据偏移。
  • 温度漂移:高温下时序参数会恶化,建议在最高工作温度下进行压力测试(如长时间运行内存测试)。
  • 不要盲目调大:增加时序周期会降低性能,应在稳定性和速度间取得平衡。

结语

通过从 Datasheet 参数到 FMC 寄存器的精确推导,再结合实测调优,可以确保 STM32F4 外接 SDRAM 的稳定运行。本文提供的方法不仅适用于 IS42S16400J,也适用于其他常见 SDRAM 芯片(如 W9825G6KH)。记住:理论计算是基础,实测验证是关键,两者结合才能打造可靠的产品。