一、概述
无论是新手还是大佬,基于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的基础使用。