STM32F4 FSMC NOR Flash扩展程序存储

在高性能嵌入式系统中,你有没有遇到过这样的窘境:功能越做越多,代码越写越长,结果编译完发现—— Flash爆了!💥 尤其是当你用着STM32F4这种“性能猛兽”,本以为1MB片上Flash够用了,结果UI渲染、通信协议栈、RTOS一加,直接超标。这时候,是换芯片?还是……把程序“搬出去”住?

这就是我们今天要聊的硬核操作: 用FSMC外接NOR Flash,让STM32的程序跑到外部存储器上运行!🚀

别急,这可不是什么黑科技,而是工业级系统里早已成熟的方案。关键就在于—— XIP(eXecute In Place)就地执行 。也就是说,CPU可以直接从NOR Flash取指运行,不用先把代码拷贝到RAM里,既省内存又快!


🧩 为什么选FSMC + NOR Flash?

STM32F4系列有个宝藏外设叫 FSMC(Flexible Static Memory Controller) ,它就像一个“万能翻译官”,能把外部并行存储器(比如SRAM、NOR、NAND)无缝接入CPU的地址空间。

而NOR Flash呢?它和我们常见的SPI Flash不一样,虽然贵一点、体积大一点,但它支持 字节寻址 + 随机读取 + XIP ,特别适合存放需要频繁跳转执行的代码段。相比之下,NAND或SPI Flash通常得先加载到RAM才能运行,多了个搬运过程。

所以,当你的项目面临以下问题时👇
➡️ 固件太大,内部Flash塞不下
➡️ RAM紧张,不想再额外占用空间加载代码
➡️ 要求启动快、运行稳,不能有延迟卡顿

那答案就很明确了: 上FSMC + NOR Flash!


📡 FSMC是怎么工作的?

简单来说,FSMC把外部设备映射成一段连续的内存区域。STM32F4的地址空间中, Bank 1 (0x6000_0000 ~ 0x6FFF_FFFF)就是专为NOR和PSRAM准备的舞台。

这个Bank还细分为4个小分区(Nor/SRAM1~4),每个对应一个片选信号NE1~NE4。我们一般用 NE1 来接NOR Flash,起始地址就是 0x60000000

一旦配置好,你就可以像访问内部Flash一样去读写这个地址——背后全是FSMC帮你搞定地址锁存、数据总线控制、读写时序这些琐事。

📌 关键引脚一览(以IS62WV51216为例)

STM32F4 功能 NOR Flash
PD0~PD15 D0~D15 数据总线
PE0~PE15 A0~A15 地址总线低16位
PF0~PF4 A16~A20 地址高位
PF12 NE1 CE# 片选
PD4 NOE OE# 读使能
PD5 NWE WE# 写使能
PG9(可选) NWAIT WAIT 等待反馈

⚠️ 注意:高位地址(A21以上)由FSMC内部生成,实际引脚数量取决于MCU封装。


⚙️ 配置FSMC其实没那么复杂

很多人一听“寄存器操作”就头大,但其实只要理解几个核心参数,再配合HAL库,几分钟就能搞定。

下面是使用HAL库初始化FSMC连接NOR Flash的关键代码👇

static void FSMC_NOR_Init(void)
{
    FSMC_NORSRAM_TypeDef *device = FSMC_Bank1;
    FSMC_NORSRAM_EXTENDED_TypeDef *ext_device = FSMC_Bank1E;

    // 开启FSMC及GPIO时钟
    __HAL_RCC_FSMC_CLK_ENABLE();

    // 配置BTCR[1]:启用Bank1 Region1,16位数据宽度,NOR模式
    device->BTCR[1] = 
        FSMC_BCR1_MWID_0        |  // 16-bit data bus
        FSMC_BCR1_MTYP_0        |  // NOR Flash type
        FSMC_BCR1_MBKEN;           // Enable memory bank

    // 设置读写时序(BTR)
    device->BTR[1] = 
        (15UL << FSMC_BTR1_ADDSET_Pos)   |  // 地址建立时间:15 HCLK cycles
        (15UL << FSMC_BTR1_DATAST_Pos)   |  // 数据保持时间:15 HCLK
        (3UL  << FSMC_BTR1_BUSTURN_Pos);   // 总线恢复时间

    // 如果使用NWAIT等待信号,开启扩展模式
    device->BTCR[1] |= FSMC_BCR1_EXTMOD;
    device->BWTR[1] = 
        (15UL << FSMC_BWTR1_ADDSET_Pos) |
        (15UL << FSMC_BWTR1_DATAST_Pos);
}

💡 小贴士:假设HCLK=168MHz(周期约5.95ns),ADDSET=15 ≈ 89ns,足以满足大多数NOR Flash的建立时间要求(如IS62WV51216典型值70ns)。如果Flash较慢,还可以启用NWAIT引脚,让Flash主动告诉MCU“等等我”。


🔗 链接脚本才是灵魂所在!

光有硬件配置还不够, 编译器得知道把代码放在哪 。这就轮到 .ld 链接脚本来登场了。

默认情况下,所有代码都放内部Flash(0x08000000)。我们要做的,是把 .text 段挪到 0x60000000 开始的外部区域。

/* stm32f4_nor_flash.ld */
MEMORY
{
    FLASH_ISR  (rx) : ORIGIN = 0x08000000, LENGTH = 16K     /* 中断向量保留 */
    FLASH_CODE (rx) : ORIGIN = 0x60000000, LENGTH = 8M-16K  /* 外部主程序区 */
    RAM        (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}

SECTIONS
{
    .isr_vector :
    {
        KEEP(*(.isr_vector))
    } > FLASH_ISR

    .text :
    {
        *(.text*)
        *(.rodata*)
    } > FLASH_CODE AT> FLASH_ISR

    .ARM.exidx :
    {
        *(.ARM.exidx*)
    } > FLASH_CODE

    .init_array :
    {
        PROVIDE_HIDDEN (__init_array_start = .);
        KEEP (*(SORT(.init_array.*)))
        KEEP (*(.init_array))
        PROVIDE_HIDDEN (__init_array_end = .);
    } > FLASH_CODE

    .data : { ... } > RAM AT> FLASH_CODE
    .bss  : { ... } > RAM
}

🎯 关键点解析:
- 中断向量表必须留在内部Flash !因为复位后CPU只会从0x08000000开始取SP和PC。
- .text 段被重定向到外部Flash,但加载域仍可在内部(AT> FLASH_ISR),首次运行前需通过Bootloader烧录过去。
- .data 段依然加载到RAM,只是初始值从外部Flash拷贝。


🚀 启动流程怎么安排?

MCU上电后,默认从内部Flash启动。因此我们需要一个 两阶段启动机制

  1. 第一阶段(Bootloader)
    运行在内部Flash的小程序,负责:
    - 初始化系统时钟
    - 配置FSMC控制器
    - 检查外部NOR Flash中的应用程序是否有效
    - 跳转至外部Flash入口地址

  2. 第二阶段(主应用)
    存放在NOR Flash中的真正业务逻辑,地址从 0x60001000 开始。

跳转代码示例:

// 假设主程序Reset_Handler地址存在0x60001004
uint32_t *app_entry = (uint32_t*)0x60001004;
uint32_t app_stack = *app_entry;        // 获取MSP初始值
__set_MSP(app_stack);                   // 切换主堆栈
((void(*)(void))(app_entry[1]))();      // 跳转到Reset Handler

✅ 成功跳转后,后续所有函数调用都会直接从NOR Flash执行,真正做到“XIP”。


🏭 实际应用场景有哪些?

1. 工业HMI人机界面
  • UI资源丰富(图标、字体、动画)
  • 使用LVGL等图形库,代码轻松突破1MB
  • 外部NOR存放GUI逻辑 + 图片索引,内部Flash只留核心驱动
2. 车载T-Box模块
  • 需运行FreeRTOS + TCP/IP + TLS + FOTA升级协议
  • 双固件镜像设计:一个在内部,一个在外扩Flash
  • 升级失败自动回滚,永不“变砖”🚫🧱
3. 医疗设备备份系统
  • 主程序跑在内部Flash
  • 备份固件预存于NOR Flash
  • 异常重启时切换至备用系统,保障设备可用性

✅ 最佳实践 & ❌ 常见坑点

推荐做法 ✅ 避免事项 ❌
选用工业级宽温NOR Flash(-40°C~+85°C) 不要把中断向量表完全移出内部Flash
每颗IC旁加0.1μF陶瓷滤波电容 忽略电源去耦,导致信号抖动
地址/数据线尽量等长布线 高频干扰环境下未做屏蔽处理
启用NWAIT引脚提升兼容性 在FSMC未初始化前访问0x60000000区域
使用带ECC校验的Flash增强可靠性 写操作中途断电,可能损坏扇区

🔧 调试建议
- 使用J-Link等高端调试器,支持对外部Flash进行在线下载和单步调试
- 在IDE(如STM32CubeIDE)中正确设置Flash算法文件,避免烧录失败


💭 写在最后:这条路还走得通吗?

你可能会问:“现在QSPI Flash这么流行,为啥还要搞复杂的FSMC+NOR?”

问得好!👏
确实,像 QSPI + Octal-SPI NOR 凭借引脚少、成本低、密度高,在很多消费类产品中已成主流。尤其是STM32H7系列支持HyperBus,速度甚至超过传统FSMC。

但别忘了, FSMC的优势在于确定性与时序可控性 。它是纯并行接口,没有协议开销,随机访问延迟极低,特别适合对实时性要求高的工业场景。

换句话说:
- 想省钱、引脚紧张 → 选QSPI
- 要极致稳定、高速响应 → FSMC+NOR仍是王者 👑

而且,掌握这套技术,不仅能应对复杂项目需求,更能深入理解 存储器映射、总线架构、启动流程 这些底层机制,是嵌入式工程师进阶的必经之路。


所以,下次当你的STM32又“Flash不够用了”,别急着换芯片,试试给它加块NOR Flash吧!💡
毕竟,真正的高手,都是会“扩容”的 😉。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐