# 一、为什么要在外部 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。若必须外部执行,务必进行时序验证和性能评估,避免影响实时性。