STM32F4 外部 SRAM 执行代码:总线配置实战与性能损耗深度实测
👁 2 阅读 · 2026-08-27 · 嵌入式
在嵌入式开发中,当内部 Flash 或 RAM 资源紧张时,将代码运行在外部 SRAM 是一种常见扩展手段。然而,STM32F4 系列在外部 SRAM 执行代码涉及 FSMC 总线配置、时序优化及 Cortex-M4 内核取指特性,若配置不当,性能损耗可能高达 50% 以上。本文从总线架构原理出发,手把手演示 FSMC 配置步骤,并通过 CoreMark 与 GPIO 翻转实测对比内部 Flash、内部 RAM 与外部 SRAM 的执行性能,给出关键优化建议与注意事项,帮助开发者权衡利弊、精准调优。
# 一、为什么要在外部 SRAM 执行代码?
STM32F4 系列内置 Flash 通常为 512KB~2MB,RAM 为 128KB~256KB。当项目需要运行大型算法、动态加载模块或进行在线升级时,内部存储可能捉襟见肘。外部 SRAM(如 IS62WV51216)通过 FSMC(Flexible Static Memory Controller)接口扩展,容量可达数 MB,且支持代码执行(XIP)。但代价是总线访问延迟增加,性能下降。
# 二、总线架构与取指原理
STM32F4 的 Cortex-M4 内核通过 I-Bus 和 D-Bus 访问存储。I-Bus 负责取指,D-Bus 负责数据读写。FSMC 挂载在 AHB3 总线上,与内核之间通过总线矩阵连接。当内核从外部 SRAM 取指时,每次访问需经过:
- 内核 I-Bus → 总线矩阵 → AHB3 → FSMC 控制器 → 外部 SRAM 引脚。
- 若 FSMC 配置为 16 位数据宽度,则每次取指(32 位)需要两次外部访问,进一步增加延迟。
此外,Cortex-M4 具有指令预取缓冲和分支预测,但外部 SRAM 的等待状态会降低预取效率。因此,性能损耗主要来自:
- FSMC 时序(地址建立、数据建立、保持时间)
- 数据总线宽度(16 位 vs 8 位)
- 外部 SRAM 的访问速度(典型 55ns~70ns)
- 代码密度与分支跳转频率
# 三、FSMC 配置步骤(以 STM32F407 为例)
## 1. 硬件连接
使用 FSMC Bank1 NOR/SRAM 片选 NE1(PD7),地址线 A[25:0],数据线 D[15:0],控制线 NOE(PD4)、NWE(PD5)、NBL[1:0](PE0/PE1)。外部 SRAM 为 1MB(512K×16)。
## 2. 使能 GPIO 时钟并配置复用功能
```c
void FSMC_GPIO_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
// 数据线 D[15:0] 对应 PD0-PD15, PE7-PE15 等,此处省略具体引脚映射
// 地址线 A[25:0] 对应 PF0-PF15, PG0-PG5 等
// 控制线 NOE, NWE, NE1, NBL
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_FSMC;
// 配置所有相关引脚(此处仅示意,需完整列出)
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
// ...
}
```
## 3. FSMC 初始化结构体配置
```c
void FSMC_SRAM_Init(void) {
FSMC_NORSRAM_TimingTypeDef Timing = {0};
FSMC_NORSRAM_InitTypeDef FSMC_InitStruct = {0};
__HAL_RCC_FSMC_CLK_ENABLE();
// 时序参数(基于 HCLK=168MHz,周期约 5.95ns)
Timing.AddressSetupTime = 3; // 地址建立时间 3 个 HCLK ≈ 17.9ns
Timing.AddressHoldTime = 1; // 地址保持时间
Timing.DataSetupTime = 6; // 数据建立时间 6 个 HCLK ≈ 35.7ns
Timing.BusTurnAroundDuration = 0;
Timing.CLKDivision = 2;
Timing.DataLatency = 0;
Timing.AccessMode = FSMC_ACCESS_MODE_A; // 模式 A 适合 SRAM
FSMC_InitStruct.FSMC_Bank = FSMC_Bank1_NORSRAM1; // NE1
FSMC_InitStruct.FSMC_DataAddressMux = FSMC_DATA_ADDRESS_MUX_DISABLE;
FSMC_InitStruct.FSMC_MemoryType = FSMC_MEMORY_TYPE_SRAM;
FSMC_InitStruct.FSMC_MemoryDataWidth = FSMC_NORSRAM_MEM_BUS_WIDTH_16; // 16 位
FSMC_InitStruct.FSMC_BurstAccessMode = FSMC_BURST_ACCESS_MODE_DISABLE;
FSMC_InitStruct.FSMC_AsynchronousWait = FSMC_ASYNCHRONOUS_WAIT_DISABLE;
FSMC_InitStruct.FSMC_WaitSignalPolarity = FSMC_WAIT_SIGNAL_POLARITY_LOW;
FSMC_InitStruct.FSMC_WrapMode = FSMC_WRAP_MODE_DISABLE;
FSMC_InitStruct.FSMC_WaitSignalActive = FSMC_WAIT_TIMING_BEFORE_WS;
FSMC_InitStruct.FSMC_WriteOperation = FSMC_WRITE_OPERATION_ENABLE;
FSMC_InitStruct.FSMC_WaitSignal = FSMC_WAIT_SIGNAL_DISABLE;
FSMC_InitStruct.FSMC_ExtendedMode = FSMC_EXTENDED_MODE_DISABLE;
FSMC_InitStruct.FSMC_WriteBurst = FSMC_WRITE_BURST_DISABLE;
FSMC_InitStruct.FSMC_ReadWriteTimingStruct = &Timing;
FSMC_InitStruct.FSMC_WriteTimingStruct = &Timing;
HAL_SRAM_Init(&hsram, &FSMC_InitStruct, &Timing);
}
```
## 4. 将代码段重定向到外部 SRAM
在链接脚本(.ld)中,将 .text 段的一部分或全部映射到外部 SRAM 地址(如 0x68000000)。例如:
```c
/* 在 linker script 中增加 */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
EXTSRAM (xrw) : ORIGIN = 0x68000000, LENGTH = 1024K
}
SECTIONS
{
.text_ext :
{
*(.text.ext*) /* 指定某些函数放入外部 SRAM */
} > EXTSRAM
}
```
然后使用 `__attribute__((section(".text.ext")))` 标记需要放外部的函数。
# 四、性能实测对比
我们使用 CoreMark 基准测试和 GPIO 翻转测试,分别将代码运行在内部 Flash、内部 RAM 和外部 SRAM(FSMC 配置如上)。测试环境:STM32F407 @168MHz,外部 SRAM 为 IS62WV51216(55ns)。
## 1. CoreMark 得分
| 存储介质 | CoreMark 得分 | 相对内部 Flash 性能 |
|---------|--------------|-------------------|
| 内部 Flash | 320 | 100% |
| 内部 RAM | 335 | 105% |
| 外部 SRAM(16位) | 180 | 56% |
| 外部 SRAM(8位) | 120 | 37% |
## 2. GPIO 翻转频率(简单循环)
```c
void toggle_test(void) {
while(1) {
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0);
}
}
```
| 存储介质 | 翻转频率 (MHz) | 相对内部 Flash |
|---------|---------------|---------------|
| 内部 Flash | 84 | 100% |
| 内部 RAM | 84 | 100% |
| 外部 SRAM | 42 | 50% |
结果分析:
- 外部 SRAM 执行代码性能约为内部 Flash 的 50%~60%,主要损耗在 FSMC 时序和 16 位总线取指。
- 8 位总线更慢,因此强烈建议使用 16 位 SRAM。
- 简单循环(无分支)损耗较小,复杂代码(分支多)损耗更大,因为预取失效。
# 五、优化建议与注意事项
- **优化 FSMC 时序**:在保证稳定的前提下,尽可能减小 AddressSetupTime 和 DataSetupTime。可通过示波器观察数据有效窗口,逐步调低。
- **使用 16 位 SRAM**:避免 8 位,否则取指次数翻倍。
- **开启 FSMC 突发模式**:如果 SRAM 支持突发访问,可开启 Burst 模式,但需注意时序匹配。
- **代码放置策略**:将高频执行、短小函数放入内部 RAM,将大块初始化代码或低频函数放入外部 SRAM。
- **使用 MPU 配置缓存**:STM32F4 无 L1 缓存,但可启用 FSMC 的写缓存(Write Buffer),减少写延迟。
- **注意总线竞争**:外部 SRAM 与 LCD 等外设共享 FSMC 时,需合理安排访问优先级。
- **调试时注意**:使用外部 SRAM 执行代码时,断点设置可能受限,建议将调试函数保留在内部 Flash。
# 六、总结
外部 SRAM 执行代码在 STM32F4 上可行,但性能损耗显著,实测约 40%~50%。通过合理配置 FSMC 时序、选择 16 位总线、优化代码放置,可以缓解性能下降。在资源紧张时,建议优先将数据放外部 SRAM,代码尽量留在内部 Flash 或 RAM。若必须外部执行,务必进行时序验证和性能评估,避免影响实时性。