一、为什么要在外部 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 时钟并配置复用功能
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 初始化结构体配置
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)。例如:
/* 在 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 翻转频率(简单循环)
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。若必须外部执行,务必进行时序验证和性能评估,避免影响实时性。