【工具使用】STM32CubeMX-FMC配置-实现SDRAM读写

一、概述

    无论是新手还是大佬,基于STM32单片机的开发,使用STM32CubeMX都是可以极大提升开发效率的,并且其界面化的开发,也大大降低了新手对STM32单片机的开发门槛。

    本文主要讲述STM32芯片FMC功能的配置及其相关知识。

二、软件说明

    STM32CubeMX是ST官方出的一款针对ST的MCU/MPU跨平台的图形化工具,支持在Linux、MacOS、Window系统下开发,其对接的底层接口是HAL库,另外习惯于寄存器开发的同学们,也可以使用LL库。STM32CubeMX除了集成MCU/MPU的硬件抽象层,另外还集成了像RTOS,文件系统,USB,网络,显示,嵌入式AI等中间件,这样开发者就能够很轻松的完成MCU/MPU的底层驱动的配置,留出更多精力开发上层功能逻辑,能够更进一步提高了嵌入式开发效率。

    演示版本 6.9.0

三、FMC功能简介

3.1 FMC概述

    FMC(Flexible Memory Controller,灵活存储控制器)是意法半导体在中高端 STM32 系列中搭载的并行外部存储控制器,是经典 FSMC 外设的升级版本。它在完全兼容静态存储接口的基础上,新增了动态 SDRAM 控制器,是 STM32 扩展片外存储、驱动高速并行外设的核心外设。

3.2 FMC模块框图

3.2.1 存储控制器

    FMC 内部集成了三个完全独立的存储控制器,共享地址 / 数据总线,但控制逻辑与时序完全独立,可同时挂载不同类型的外部设备。

1、NOR/PSRAM memory controller(NOR/PSRAM 存储器控制器)

    负责管理静态类存储设备,支持 SRAM、PSRAM、NOR Flash、并口 LCD 等设备,兼容异步 / 同步访问模式,是 FMC 向下兼容经典 FSMC 的核心部分。

2、NAND Flash memory controller(NAND 闪存控制器)

    专门管理 NAND Flash,自带硬件 ECC 校验引擎,支持 8/16 位位宽的 NAND 颗粒,拥有独立的片选与就绪中断信号,负责大容量数据存储的访问控制。

3、SDRAM controller(SDRAM 控制器)

    这是 FMC 相比经典 FSMC 新增的核心控制器,专门管理动态 SDRAM 颗粒,硬件自动完成刷新、行 / 列地址选通、低功耗模式控制,是 STM32 扩展大容量运行内存的核心。

3.2.2 对外信号接口

    所有对外引脚按功能与归属分为多组,地址 / 数据线为全局共享资源,控制信号按设备类型独立分组,通过片选信号区分目标设备。

1、静态存储类信号(NOR/PSRAM/SRAM 共用)

  • Shared signals(全共享信号)
    FMC_A25:0:26 根地址线,支持单 Bank 最大 64MB 字节寻址;
    FMC_D31:0:32 根双向数据线,可灵活配置为 8/16/32 位位宽。
    这组总线被 NOR/PSRAM/SRAM/NAND 共用,通过片选信号切换目标设备。
  • NOR / SRAM shared signals
    FMC_NBL3:0:4 根字节使能信号,对应 32 位数据的 4 个字节,实现字节 / 半字 / 字粒度的精准读写。
  • NOR / PSRAM / SRAM shared signals
    FMC_NE4:1:4 根片选信号,对应静态存储的 4 个子 Bank,同一时刻仅一个片选有效;
    FMC_NOE:读使能信号,低电平有效,控制外部设备输出数据;
    FMC_NWE:写使能信号,低电平有效,控制外部设备锁存数据;
    FMC_NWAIT:等待输入信号,慢速外部设备可拉低该信号,让 FMC 自动插入等待周期,适配低速器件。
    NOR/PSRAM signals(专用)
    FMC_NL (or NADV):地址锁存 / 地址有效信号,用于同步模式或地址复用场景;
    FMC_CLK:同步模式时钟,用于同步 NOR/PSRAM 的突发传输。

2、NAND 专用信号

FMC_NCE:NAND Flash 片选信号;

FMC_INT:NAND 中断输入,一般连接 NAND 的就绪 / 忙引脚,用于通知 CPU 操作完成。

3、SDRAM 专用信号

FMC_SDCLK:SDRAM 同步工作时钟,所有 SDRAM 操作同步于此时钟;

FMC_SDNWE:SDRAM 写使能信号;

FMC_SDCKE1:0:2 根时钟使能信号,对应 2 个 SDRAM 设备,用于控制 SDRAM 进入 / 退出低功耗自刷新模式;

FMC_SDNE1:0:2 根 SDRAM 片选信号,对应 2 个独立的 SDRAM Bank;

FMC_NRAS:行地址选通信号,用于锁存行地址;

FMC_NCAS:列地址选通信号,用于锁存列地址。

四、FMC配置

4.1 外围接口及配置

    使用FMC驱动SDRAM还是比较简单的,只需要在CubeMX里配置好SDRAM的功能,然后初始化时发送SDRAM芯片的配置指令后,即可正常使用RAM内存的读写操作。查看开发板的原理图,这里用的是2片W9825G6KH的SDRAM芯片。根据原理图接口配置单片机的GPIO口。

4.2 FMC功能配置

4.2.1 FMC Mode and Configuration(上半部分)

    Mode 模式选择区是 FMC 支持的全部存储设备类型,当前展开配置的是 SDRAM 1(对应 FMC 的第 1 路 SDRAM 控制器,片选 SDNE0,地址空间起始地址 0xC0000000)。

  • Clock and chip enable:SDCKE0+SDNE0

启用 SDRAM 第 0 路的时钟使能引脚(SDCKE0)和片选引脚(SDNE0)。选择该项后,CubeMX 会自动将对应 GPIO 配置为 FMC 复用功能,硬件上对应外接的第一片 SDRAM。

  • Internal bank number:4 banks

SDRAM 芯片内部的逻辑 Bank 数量。主流 SDRAM 颗粒内部均为 4 个逻辑 Bank,通过BA1:0引脚选择,是 SDRAM 的标准架构,绝大多数场景都选 4 banks。

  • Address:13 bits

SDRAM 的行地址线位数,对应 13 根行地址引脚FMC_A12:0。13 位行地址是中大容量 SDRAM(如 256Mbit、512Mbit)的标准配置,决定了 SDRAM 单行的存储容量。

  • Data:32 bits

SDRAM 数据总线位宽为 32 位。可以是单颗 32 位 SDRAM 颗粒,也可以是两颗 16 位颗粒并联组成 32 位位宽;位宽越大,单位时间的读写带宽越高。

  • Byte enable:32-bit byte enable

启用 32 位对应的 4 路字节使能信号FMC_NBL3:0,支持按字节、半字(16 位)、字(32 位)粒度独立读写,无需每次都读写完整 32 位数据。

4.2.2 Configuration(下半部分)

    Configuration 详细配置区分为SDRAM 控制参数和SDRAM 时序参数两大类,所有参数必须严格对照实际 SDRAM 芯片的数据手册计算填写,否则会出现读写错误、死机等问题。

1、SDRAM control(核心控制参数)

配置 SDRAM 的基础工作模式与寻址规则。

  • Bank:SDRAM bank 1

对应上方选择的 SDRAM 1 通道,固定为第 1 路 SDRAM 片选。

  • Number of column address bits:9 bits

列地址位数,对应 SDRAM 列地址线的数量。9 位列地址是主流配置,结合 13 位行地址 + 2 位内部 Bank 地址,可计算 SDRAM 总容量:总容量 = 2^行地址 × 2^列地址 × 内部Bank数 × 数据位宽。

  • Number of row address bits:13 bits

与上方 Mode 区的 Address 参数对应,行地址位数,二者保持一致即可。

  • CAS latency:2 memory clock cycles

CAS 潜伏期(Column Address Strobe Latency):发送列地址读命令后,数据输出需要等待的 SDRAM 时钟周期数。

这是 SDRAM 的核心参数,常见可选 2 或 3:时钟频率低选 2,频率高时为保证稳定性选 3,需以 SDRAM 手册标称值为准。

  • Write protection:Disabled

写保护功能:关闭状态,允许对 SDRAM 进行读写操作。正常应用场景都保持关闭,仅当需要保护 SDRAM 数据不被误写时才开启。

  • SDRAM common clock:2 HCLK clock cycles

SDRAM 工作时钟分频:SDRAM 时钟 = 内核 HCLK / 2。

例如 STM32F7 的 HCLK 为 216MHz 时,SDRAM 时钟SDCLK = 108MHz。这是最常用的分频配置,也可根据需求选择其他分频比。

  • SDRAM common burst read:Enabled

突发读使能:开启后,连续读取同一行内的连续地址时,无需重复发送行地址,仅需列地址即可连续输出数据,大幅提升连续读取的带宽。正常应用都建议开启。

  • SDRAM common read pipe delay:0 HCLK clock cycle

读流水线延迟:FMC 读取到 SDRAM 数据后,内部延迟多少个 HCLK 周期再送到系统总线。

0 延迟性能最高,当时钟频率过高、数据建立时间不足时,可设为 1 周期提升稳定性。

2、SDRAM timing in memory clock cycles(时序参数)

所有参数单位均为SDRAM 时钟(SDCLK)周期,数值需要将 SDRAM 手册的时间参数(ns)除以 SDCLK 周期,向上取整得到。

  • Load mode register to active delay:2

对应 SDRAM 参数 tMRD:加载模式寄存器命令 → 激活命令的最小间隔。主流 SDRAM 固定为 2 个时钟周期,几乎无需修改。

  • Exit self-refresh delay:8

对应 SDRAM 参数 tXSR:退出自刷新模式 → 第一个有效访问命令的最小间隔。由 SDRAM 的自刷新恢复时间决定,是时序中较大的参数,需按手册计算。

  • Self-refresh time:6

对应 SDRAM 参数 tRAS(行激活保持时间):行激活命令 → 行预充电命令的最小间隔。

注意:参数名容易误解,它不是自刷新的时长,而是一行被激活后必须保持的最小时间,才能执行预充电。

  • SDRAM common row cycle delay:6

对应 SDRAM 参数 tRC(行周期):两次激活同一 Bank 行地址的最小间隔。满足 tRC = tRAS + tRP 的关系,是 SDRAM 连续换行访问的最小周期。

  • Write recovery time:4

对应 SDRAM 参数 tWR(写恢复时间):写操作结束 → 行预充电命令的最小间隔。保证写数据可靠写入存储单元后才能预充电,避免数据丢失。

  • SDRAM common row precharge delay:2

对应 SDRAM 参数 tRP(行预充电时间):行预充电命令 → 下一次行激活命令的最小间隔。预充电是关闭当前行、准备打开新行的操作,需要一定时间完成。

  • Row to column delay:2

对应 SDRAM 参数 tRCD(行到列延迟):行激活命令 → 列读写命令的最小间隔。打开一行后,需要等待行地址稳定,才能发送列地址进行读写。

4.3 接口说明

HAL_SDRAM_SendCommand 是 STM32 HAL 库中 FMC SDRAM 控制器的核心命令发送接口,负责向外部 SDRAM 芯片下发各类控制命令(时钟使能、预充电、自动刷新、加载模式寄存器、进入自刷新等),同时通过软件状态机维护 SDRAM 外设的工作状态,是 SDRAM 初始化、低功耗控制的底层通用入口。它属于阻塞式调用:函数会等待命令执行完成(或超时)后才返回,不支持异步非阻塞操作。

HAL_SDRAM_ProgramRefreshRate 是 HAL 库中用于配置 FMC SDRAM 自动刷新周期的专用接口。它的核心作用是设置 FMC 内部刷新定时器的重装值,让控制器自动、周期性地向 SDRAM 发送自动刷新命令,保证 SDRAM 内部电容数据不丢失,是 SDRAM 正常稳定工作的必要配置。相比发送单次命令的 HAL_SDRAM_SendCommand,这个函数配置的是后台自动运行的周期性机制,配置完成后无需 CPU 干预,FMC 硬件会自动完成刷新。

HAL_SDRAM_SetAutoRefreshNumber 是 HAL 库中用于配置 FMC SDRAM 控制器单次软件自动刷新的连续命令次数的接口。它直接修改 FMC 硬件寄存器的对应位域,决定了当软件触发自动刷新命令时,控制器会连续向 SDRAM 芯片发送多少个 AUTO REFRESH 命令。该函数属于纯配置接口,只修改参数、不触发任何实际刷新动作;在常规 SDRAM 初始化流程中很少单独调用,通常都和自动刷新命令合并配置。

4.4 代码实现

4.4.1 SDRAM初始化及配置

    查看W9825G6KH芯片资料,该芯片上电需要实现一段初始化。时钟使能->延时200us->预充电->配置自动刷新->配置模式寄存器->配置自动刷新周期。

  • 模式寄存器

    先查看数据手册看数据位的定义如下,根据CubeMX的配置,进行如下配置。

位域 配置值 选择原因
突发长度(A0~A2) 000(长度 = 1) FMC 单次访问对应一次读写,避免突发模式导致多余数据
突发类型(A3) 0(顺序突发) STM32 FMC 仅支持顺序突发
CAS 潜伏期(A4~A6) 010(2 周期) 和你 CubeMX 配置的 CAS=2 严格一致
测试模式(A7) 0 标准工作模式,禁止进入测试模式
保留位(A8/A10~A12) 全 0 手册强制要求
写模式(A9) 1(单字节写) 避免单次写操作触发突发写入,适配 FMC 的字节写逻辑

模式寄存器值 = (1 << 9) | (2 << 4) = 0x200 + 0x20 = 0x00000220

  • 自动刷新周期计算

    STM32 FMC 的刷新计数器公式为:

刷新计数值 = (单行刷新间隔时间 × SDRAM时钟频率) - 20

    其中减 20 是 STM32 官方推荐的裕量,避免总线延迟导致刷新不及时。

    另外手册要求64ms 内必须完成全部 8192 行的刷新(如下图),即每行的刷新间隔为 64ms / 8192 = 7.8125μs。当前SDCLK=100MHz,所以

7.8125μs × 100MHz - 20 = 781.25 - 20 ≈ 761

4.4.2 相关接口代码实现

sdram_drv.c实现

c 复制代码
/**
  ******************************************************************************
  * @file           : sdram_drv.c
  * @brief          : W9825G6KH SDRAM 芯片上电初始化实现
  * @note           : CubeMX 已配置好 FMC 寄存器 (MX_FMC_Init),
  *                   本文件只负责发送 SDRAM 芯片的初始化命令序列
  ******************************************************************************
  */

#include "sdram_drv.h"

/* SDRAM 命令超时时间 (ms) */
#define SDRAM_CMD_TIMEOUT       1000U

/**
  * @brief  SDRAM 芯片上电初始化命令序列
  * @note   参考 W9825G6KH Datasheet 初始化流程:
  *         1. 上电等待 200us (硬件已满足)
  *         2. Precharge All Banks (PALL)  - 所有 Bank 进入 IDLE
  *         3. Auto Refresh x 8            - 刷新所有行
  *         4. Load Mode Register          - 设置 CAS Latency / Burst Length
  *         5. Set Refresh Rate            - 配置自动刷新周期
  * @retval HAL_OK / HAL_ERROR
  */
HAL_StatusTypeDef SDRAM_Init(void)
{
    FMC_SDRAM_CommandTypeDef Command = {0};
    uint32_t tick;

    /* ---- Step 1: 时钟使能 (给 SDRAM 芯片提供 SDCLK) ---- */
    Command.CommandMode             = FMC_SDRAM_CMD_CLK_ENABLE;
    Command.CommandTarget           = FMC_SDRAM_CMD_TARGET_BANK1;
    Command.AutoRefreshNumber       = 1;
    Command.ModeRegisterDefinition  = 0;
    if (HAL_SDRAM_SendCommand(&hsdram1, &Command, SDRAM_CMD_TIMEOUT) != HAL_OK)
        return HAL_ERROR;

    /* 等待至少 200us (上电稳定 + tRP) */
    HAL_Delay(1);

    /* ---- Step 2: 预充电所有 Bank (PALL) ---- */
    Command.CommandMode             = FMC_SDRAM_CMD_PALL;
    Command.CommandTarget           = FMC_SDRAM_CMD_TARGET_BANK1;
    Command.AutoRefreshNumber       = 1;
    Command.ModeRegisterDefinition  = 0;
    if (HAL_SDRAM_SendCommand(&hsdram1, &Command, SDRAM_CMD_TIMEOUT) != HAL_OK)
        return HAL_ERROR;

    /* ---- Step 3: 自动刷新 8 次 ---- */
    Command.CommandMode             = FMC_SDRAM_CMD_AUTOREFRESH_MODE;
    Command.CommandTarget           = FMC_SDRAM_CMD_TARGET_BANK1;
    Command.AutoRefreshNumber       = 8;
    Command.ModeRegisterDefinition  = 0;
    if (HAL_SDRAM_SendCommand(&hsdram1, &Command, SDRAM_CMD_TIMEOUT) != HAL_OK)
        return HAL_ERROR;

    /* ---- Step 4: 加载模式寄存器 (CL=2, BL=1) ---- */
    Command.CommandMode             = FMC_SDRAM_CMD_LOAD_MODE;
    Command.CommandTarget           = FMC_SDRAM_CMD_TARGET_BANK1;
    Command.AutoRefreshNumber       = 1;
    Command.ModeRegisterDefinition  = SDRAM_MODE_REG_VAL;
    if (HAL_SDRAM_SendCommand(&hsdram1, &Command, SDRAM_CMD_TIMEOUT) != HAL_OK)
        return HAL_ERROR;

    /* ---- Step 5: 设置自动刷新周期 ---- */
    if (HAL_SDRAM_ProgramRefreshRate(&hsdram1, SDRAM_REFRESH_COUNT) != HAL_OK)
        return HAL_ERROR;

    /* ---- Step 6: 等待 tMRD (Mode Register Set 生效时间, >= 2 tCK) ---- */
    tick = HAL_GetTick();
    while ((HAL_GetTick() - tick) < 1U) { /* 1ms >> tMRD */ }

    return HAL_OK;
}

sdram_drv.h实现

c 复制代码
/**
  ******************************************************************************
  * @file           : sdram_drv.h
  * @brief          : W9825G6KH SDRAM 芯片驱动头文件
  * @note           : 2片 W9825G6KH 级联, 32bit 数据总线, 总容量 64MB
  *                   FMC Bank1 映射地址: 0xC0000000
  *                   SDCLK = HCLK/2 = 100MHz, CAS Latency = 2
  ******************************************************************************
  */
#ifndef __SDRAM_DRV_H
#define __SDRAM_DRV_H

#ifdef __cplusplus
extern "C" {
#endif

#include "stm32h7xx_hal.h"

/* ======================== SDRAM 地址映射 ======================== */
#define SDRAM_BASE_ADDR         0xC0000000UL    /* FMC SDRAM Bank1 基地址 */
#define SDRAM_SIZE              0x04000000UL    /* 64MB                  */

/* ======================== 模式寄存器 ======================== */
/* CAS Latency=2, Burst Length=1, Sequential, Standard Operation
 * A9=1 (Single Write Mode), 确保写操作永远单字, 不受突发长度影响
 * A6-A4=010(CL2), A2-A0=000(BL1)
 */
#define SDRAM_MODE_REG_VAL      0x0220U

/* ======================== 刷新周期 ======================== */
/* COUNT = (64ms / 8192) * 100MHz - 20 = 761 */
#define SDRAM_REFRESH_COUNT     761U

/* ======================== 对外接口 ======================== */

extern SDRAM_HandleTypeDef hsdram1;

/**
  * @brief  SDRAM 芯片上电初始化 (发送命令序列)
  * @note   必须在 MX_FMC_Init() 之后调用
  *         MX_FMC_Init 已配置好 FMC 寄存器 (时钟/时序/位宽等)
  *         本函数发送芯片级初始化命令:
  *           CLK_ENABLE -> PALL -> AutoRefresh x8 -> LoadMode -> SetRefreshRate
  * @retval HAL_OK / HAL_ERROR
  */
HAL_StatusTypeDef SDRAM_Init(void);

#ifdef __cplusplus
}
#endif

#endif /* __SDRAM_DRV_H */

4.4.3 读写应用

c 复制代码
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2026 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "sdram_drv.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

SDRAM_HandleTypeDef hsdram1;

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_FMC_Init(void);
/* USER CODE BEGIN PFP */
static void SDRAM_Test(void);
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_FMC_Init();
  /* USER CODE BEGIN 2 */

  /* ---- SDRAM 芯片初始化 + 读写测试 ---- */
  SDRAM_Test();

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}



/* USER CODE BEGIN 4 */

/**
  * @brief  SDRAM 初始化 + 读写测试
  */
static void SDRAM_Test(void)
{
    volatile uint32_t *pSdram = (volatile uint32_t *)SDRAM_BASE_ADDR;
    uint32_t i, val;

    /* ---- 1. 芯片上电初始化 ---- */
    if (SDRAM_Init() != HAL_OK)
        Error_Handler();

    /* ---- 2. 数据总线 Walking-1 测试 ---- */
    for (i = 0; i < 32; i++) {
        val = 1UL << i;
        pSdram[0] = val;
        if (pSdram[0] != val) Error_Handler();
    }

    /* ---- 3. 数据总线 Walking-0 测试 ---- */
    for (i = 0; i < 32; i++) {
        val = ~(1UL << i);
        pSdram[0] = val;
        if (pSdram[0] != val) Error_Handler();
    }

    /* ---- 4. 地址总线 + 数据校验 (前 64KB) ---- */
    {
        uint32_t cnt = 16384;  /* 64KB / 4 = 16K words */
        for (i = 0; i < cnt; i++)
            pSdram[i] = (i << 16) | (i & 0xFFFF);
        for (i = 0; i < cnt; i++) {
            uint32_t expect = (i << 16) | (i & 0xFFFF);
            if (pSdram[i] != expect) Error_Handler();
        }
    }

    /* ---- 5. 全 0 / 全 1 测试 ---- */
    {
        uint32_t cnt = 4096;
        for (i = 0; i < cnt; i++) pSdram[i] = 0x00000000;
        for (i = 0; i < cnt; i++) if (pSdram[i] != 0x00000000) Error_Handler();

        for (i = 0; i < cnt; i++) pSdram[i] = 0xFFFFFFFF;
        for (i = 0; i < cnt; i++) if (pSdram[i] != 0xFFFFFFFF) Error_Handler();
    }

    /* ---- 6. 棋盘格测试 ---- */
    {
        uint32_t cnt = 4096;
        for (i = 0; i < cnt; i++) pSdram[i] = 0x55555555;
        for (i = 0; i < cnt; i++) if (pSdram[i] != 0x55555555) Error_Handler();

        for (i = 0; i < cnt; i++) pSdram[i] = 0xAAAAAAAA;
        for (i = 0; i < cnt; i++) if (pSdram[i] != 0xAAAAAAAA) Error_Handler();
    }

    /* ---- 7. 偏移地址 32bit 读写 ---- */
    {
        uint32_t base = SDRAM_BASE_ADDR + 0x100000;  /* 偏移 1MB */
        uint32_t rval;

        *(volatile uint32_t *)base = 0xAAAAAAAA;
        rval = *(volatile uint32_t *)base;
        if (rval != 0xAAAAAAAA) Error_Handler();

        *(volatile uint32_t *)base = 0x55555555;
        rval = *(volatile uint32_t *)base;
        if (rval != 0x55555555) Error_Handler();

        *(volatile uint32_t *)base = 0x12345678;
        rval = *(volatile uint32_t *)base;
        if (rval != 0x12345678) Error_Handler();

        for (volatile int d = 0; d < 1000; d++) { __NOP(); }

        *(volatile uint32_t *)base = 0x87654321;
        for (volatile int d = 0; d < 1000; d++) { __NOP(); }

        rval = *(volatile uint32_t *)base;
        if (rval != 0x87654321) Error_Handler();
    }

    /* ---- 8. 块读写测试 (4KB, 偏移 2MB) ---- */
    {
        static uint32_t buf[1024];    /* static 避免栈溢出 */
        uint32_t offset = 0x200000;   /* 偏移 2MB */

        for (i = 0; i < 1024; i++) buf[i] = i * 0x11111111UL;
        for (i = 0; i < 1024; i++) pSdram[offset / 4 + i] = buf[i];
        for (i = 0; i < 1024; i++) buf[i] = pSdram[offset / 4 + i];
        for (i = 0; i < 1024; i++) {
            if (buf[i] != i * 0x11111111UL) Error_Handler();
        }
    }

    /* ---- 9. 64MB 全空间抽检 ---- */
    {
        uint32_t totalWords = SDRAM_SIZE / 4;        /* 16M words */
        uint32_t step       = totalWords / 256;       /* 抽 256 个点 */

        for (i = 0; i < 256; i++)
            pSdram[i * step] = i * step;
        for (i = 0; i < 256; i++)
            if (pSdram[i * step] != i * step) Error_Handler();
    }
}

/* USER CODE END 4 */
  • 效果演示

    通过内存窗口查看0xC0000000地址,可以正常的读写数据,且测试代码过程没有进入异常死循环,说明SDRAM工作正常。

五、注意事项

1、配置核心在于控制时序,一定要根据芯片手册进行配置,一些时间间隔的配置,如果对性能要求不是很高的情况下,可适当调长时间,以获得更稳定的读写状态。

六、相关链接

对于刚入门的小伙伴可以先看下STM32CubeMX的基础使用及Keil的基础使用。

【工具使用】STM32CubeMX-基础使用篇

【工具使用】Keil5软件使用-基础使用篇