
刚把一块带GD32F303的板子调通用的是EXMC总线外扩了一颗16位并口PSRAM中间踩了复用地址/数据总线、同步突发时序的不少坑。顺手把FMC/FSMC/EXMC这套外部总线控制器在NOR Flash和PSRAM接口上的门道捋了一遍尤其是“异步-复用-不突发”和“同步-复用-突发”这两组模式写出来给做嵌入式硬件和底层驱动的人参考。很多初学者拿到STM32的FMC、GD32的EXMC第一反应是“这玩意儿不就是用来接TFT屏幕的吗”这其实把它的能力看小了。FMC/FSMC/EXMC本质是一个可编程时序的外部存储器控制器除了屏幕还能接NOR Flash、SRAM、PSRAM、SDRAM甚至某些FPGA的双口RAM。今天就把这个控制器在NOR Flash和PSRAM上的两种典型用法拆开讲透从原理到寄存器再到实测参数一次说清楚。1. 内容整体设计与思路拆解1.1 FMC、FSMC、EXMC到底是不是同一个东西先说结论它们是同一类外设的不同叫法寄存器结构基本同源但不同型号芯片的能力有差异。FSMC全称是Flexible Static Memory Controller最早出现在STM32F1系列解决了当时单片机外部扩展SRAM、NOR、LCD的问题。它支持NOR/PSRAM、SRAM、NAND三组控制器片选最多可以接4个NOR/PSRAM/SRAM设备。到了STM32F4/F7/H7名字改成了FMCFlexible Memory Controller在FSMC的基础上增加了SDRAM控制功能而且对NOR/PSRAM部分的时序参数设置做了一些扩展和调整。本质上你以前在FSMC上写的参数在FMC上依然能看懂只是寄存器地址和位段有小幅改动。EXMC是GD32兆易创新对同一类外设的命名全称External Memory Controller。因为GD32在Cortex-M4内核GD32F303/F407等上复刻了ST的外设体系所以EXMC的用法和STM32的FMC/FSMC高度相似寄存器配置思路几乎可以平移。但也务必注意寄存器位段名称、库函数接口和等待状态、时钟分频的计算方式不完全一致用的时候要对着各自的手册核对。这套控制器解决了什么实际问题单片机内部Flash和SRAM有限当你的代码量超过128KB或者需要大缓冲区做图像处理、音频缓冲、掉电数据存储时就必须外扩存储芯片。而外扩存储芯片的总线协议五花八门有NOR的、有PSRAM的、有SRAM的甚至还有SDRAM的如果每个都用GPIO模拟时序CPU会被拖死代码也写不优雅。FMC/FSMC/EXMC统一解决了上面这些芯片的时序问题你只需要配置好对应Bank的时序参数然后像访问内部RAM一样直接对某个地址段读写剩下的时序产生、总线切换、等待状态处理全由硬件完成。1.2 为什么我要关注“复用”“突发”这些模式很多项目里外扩NOR Flash用的是最简单的“地址线数据线分离”接法比如STM32F103接一个256KB的SRAMA0到A17接地址D0到D15接数据搭配NEx、NOE、NWE直接读写。这种模式叫异步非复用模式时序简单驱动起来容易但有一个问题引脚占用多、布线面积大、高频率下信号质量难保证。所以后来并行存储芯片开始往“减少引脚”方向走典型的就是“地址/数据复用”接口。这种芯片把地址总线和数据总线共用同一组引脚一般16位或32位通过ALE/NADV信号来区分当前总线上是地址还是数据。这样外部存储芯片的引脚数可以大幅减少PCB面积也相应减小。但复用总线天然带来一个代价每次访问需要在一个总线周期内先送地址再切到数据阶段相当于多一个地址锁存阶段。如果每次都只访问一个地址性能肯定比非复用慢。于是就有了“同步突发”模式第一个周期送地址后续连续地址的数据在时钟沿下一个接一个地出来不再重复送地址。这就是标题里“异步-复用-不突发”和“同步-复用-突发”两组模式的核心区别。4注意上面这段话中“4”是多余字符应删掉1. 内容整体设计与思路拆解1.1 FMC、FSMC、EXMC到底是什么为什么一个项目里三个名字都出现先统一认识FMC、FSMC、EXMC本质上是同一类外设就是“外部存储器控制器”只是在不同厂商、不同系列芯片里的叫法不一样。FSMC出现在ST早期的STM32F1和STM32F4上全称是“灵活的静态存储器控制器”支持NOR Flash、PSRAM、SRAM、NAND Flash但不支持SDRAM。它内部有独立的Bank区域每个Bank对应不同的片选引脚和地址段。FMC是ST后期在STM32F4、F7、H7系列上使用的名字全称是“灵活存储器控制器”在FSMC的基础上补充了SDRAM控制器同时对NOR/PSRAM模式下的时序配置、数据宽度配置做了一些调整。注意F4系列有些型号叫FMC有些叫FSMC要具体看型号。EXMC则是国产GD32兆易创新对“外部存储器控制器”的命名功能上对齐ST的FSMC/FMC但寄存器布局、库函数接口略有差异。GD32F303系列用的就是EXMC驱动方式跟STM32F4的FMC很像。这套控制器解决了什么问题很简单让MCU能像访问内部SRAM一样访问外部存储器不需要用GPIO模拟时序去读外部Flash或者扩展SRAM。你只要把地址发给FMC数据就自动出现在数据总线上或者自动写入外部芯片剩下的硬件时序、读写信号、地址锁存全部由外设控制器完成。真正让新人头疼的不是它怎么用而是它的模式和接口类型太多了。同一个外部地址空间可以接SRAM、NOR Flash、PSRAM、SDRAM、LCD控制器时序参数还各不相同模式还分为异步、同步、复用、非复用、突发、非突发排列组合起来很容易让人绕晕。1.2 “异步-复用-不突发”和“同步-复用-突发”是什么可以把这两个模式理解成外部存储接口的两种“工作方式”它们决定了几件关键的事地址和数据是分开传还是共用一组线数据读取是跟着时钟边沿走还是靠电平建立时间一次连续读取能不能持续输出数据。“异步”意味着总线上没有外部时钟信号控制器靠内部时序比如地址建立时间、数据建立时间、片选有效时间来控制读写过程。MCU发出片选和写使能信号然后等足够的建立时间再从数据线上取数据。整个过程依靠电平信号而不是时钟边沿。“复用”指的是数据和地址共用同一组引脚。典型情况下引脚总数可以减少但由于地址和数据分时复用所以每次访问需要先锁存地址再切换为数据传输。控制器引脚如FMC_A[25:0]是地址线FMC_D[15:0]是数据线非复用模式下两者各管各复用模式下某个状态由NADV信号控制下总线上的值是地址另一个状态下总线上的值是数据。“不突发”表示每次访问只完成一个地址的数据读写不会自动连续读取后续地址。而“突发”模式下只要给定起始地址后续地址通过内部计数器自动递增总线在时钟沿逐个输出连续数据适合填充显存、批量拷贝代码、读取连续数据表等场景。而“同步”模式则要求外部存储器支持时钟信号如FMC_CLK在时钟的上升沿或下降沿采样数据配合突发模式可以做到一个时钟周期读取一个数据效率远高于异步模式。为什么标题里把“异步-复用-不突发”放在前面因为这是大多数带“地址/数据复用接口”的NOR Flash和PSRAM最基础的工作方式。很多芯片不支持同步突发或者即使支持也需要额外的初始化配置。异步复用是门槛最低、兼容性最好的模式。反过来“同步-复用-突发”是性能更高的方案主要用于支持Synch Burst同步突发的PSRAM和部分NOR Flash。它需要额外接FMC_CLK、等待状态引脚等但换来的是持续读取时几乎满速率的数据吞吐。2. 核心细节解析与实操要点2.1 NOR Flash接口方式从“地址/数据分离”到“复用”常见的并口NOR Flash有两种接口一种是SRAM-like接口地址线和数据线分离占用引脚多但接口逻辑简单。另一种是地址/数据复用接口常见于较新的高密度NOR Flash比如Intel的StrataFlash、Spansion的GL系列等。引脚数少但也带来了接口时序上更高的复杂度。对于复用接口的NOR Flash它内部本身有地址锁存NADV引脚的下降沿用来锁存地址之后地址线释放给数据线使用。因此FMC在写地址之后需要插入一个地址锁存期再由FMC_NL输出锁存使能信号控制地址锁存器把地址稳定住。这里有一个很容易忽略的细节FMC_NL信号只是控制器输出的地址锁存使能外部还需要一颗锁存器比如74LVC373或74LVC573把地址锁存到NOR Flash的地址引脚上。如果省掉这颗锁存器FMC_NL没有任何意义地址也传不进Flash。当然如果你的NOR Flash本身支持复用口自带地址锁存那就不需要外部锁存器但时序依然要按复用模式配。还有个常见误区很多人以为FMC/FSMC只能接“非复用”存储器因为数据手册上SRAM接口地址线和数据线是分开的。这是狭义理解。FMC的NOR/PSRAM控制器其实明确支持“地址/数据复用”模式典型场景就是接80xx/68xx总线接口的LCD控制器和复用接口的NOR Flash、PSRAM。关键是BCR寄存器的MUXEN位要置1。我调过一块IS61WV12816非复用SRAM和一块S29GL256复用接口NOR两者在FMC驱动代码上一个MUXEN位的差别其他完全一样。但一旦搞错数据读出来全是乱的而且是“看似能读、一校验就崩”的那种乱。2.2 PSRAM和普通SRAM/DRAM的差异为什么它需要专用时序PSRAM全称Pseudo SRAM也叫CellularRAM内部本质是DRAM但带有刷新逻辑对外接口看起来像SRAM。它省掉了外部刷新电路兼容SRAM接口同时成本低于真正的SRAM容量可以做得比较大。但PSRAM也有两类一类是异步SRAM兼容接口另一类是同步突发接口。异步型PSRAM采用正常的SRAM读写时序芯片内部自主刷新外部不需要关心刷新逻辑。同步突发型PSRAM则工作在时钟模式下支持突发读取时序参数和SDRAM有点像但控制逻辑要简单不少。FMC/FSMC/EXMC对这两种PSRAM的支持是不同的。异步PSRAM直接当作NOR Flash/SRAM异步模式配时序即可同步突发PSRAM则需要使用同步突发模式并且要设置好时钟分频、等待状态、突发长度等参数。PSRAM和DRAM的区别在于DRAM需要外部控制器定期刷新时序有RAS、CAS、预充电等阶段PSRAM虽然内部是DRAM结构但控制器上看不到这些过程依然是片选、读写使能、地址数据这套SRAM式接口。这也是为什么它叫伪SRAM。实际项目中如果只是扩展几百KB到几MB的数据缓冲区PSRAM比SDRAM省心太多——不需要SDRAM初始化流程不需要刷新定时器接口简单代码量小。缺点是容量到不了SDRAM那么大性能也低于SDRAM。2.3 同步突发模式需要哪些关键引脚和参数同步突发模式除了标准地址、数据、片选、读使能、写使能之外还多了两个信号FMC_CLK同步时钟输出和FMC_NWAIT等待状态输入。FMC_CLK是控制器输出给外部存储器的同步时钟所有地址、数据、控制信号的采样都以它为准。频率由HCLK分频而来根据PSRAM/NOR的最高工作频率和FMC的时钟树来确定。比如HCLK是168MHzPSRAM最高工作频率是104MHz那么CLKDIV至少得是2也就是FMC_CLK约84MHz留出一点余量。FMC_NWAIT是外部存储器用来通知控制器“我还没准备好请等待”的信号。等待状态在同步突发中非常关键因为PSRAM的读延时也就是从地址锁存到第一个数据输出通常大于一个时钟周期有的芯片是1个等待周期有的是2个。如果外部存储器需要更多时间它会把NWAIT拉低FMC检测到后会插入额外等待周期。实际调试中NWAIT上下拉很容易翻车。如果外部存储器不接入NWAIT引脚必须根据芯片手册明确它是高有效还是低有效并在FMC配置里把NWAIT配置为禁用否则读取会随机卡死。曾经遇到一块PSRAM数据手册写的是WAIT有效为低但硬件上没有接这个引脚FMC配置里却开了NWAIT等待结果跑一会儿就hardfault。突发长度也要配置它决定了每次突发传输的节拍数。常见的有4、8和连续模式。连续模式适合批量拷贝场景连续地址的数据可以无限读下去只要不发新的地址。实际配置时还要注意FMC的“突发访问区域”设置不是所有地址都能突发访问FMC内部专门有一块区域用于同步突发访问比如Bank1的区3、区4映射地址范围不同访问方式也不同。3. 实操过程与核心环节实现3.1 从零配置FMC/EXMC驱动异步复用NOR Flash这里以GD32F303的EXMC为例因为它的名字是EXMC但配置思路可以直接平移回STM32的FMC。先把EXMC初始化拆成几个环节GPIO配置、EXMC配置、等待时序计算。GPIO配置相对简单把对应的数据线、地址线、控制线全部配置为复用功能即可。注意不同型号芯片的复用功能映射关系不一样GD32F303和STM32F4的AF号不同。调试时常见的“踩坑”是GPIO时钟没开全FMC没反应读写全返回0xFF。EXMC配置的核心参数如下数据宽度8位、16位存储器类型NOR Flash、PSRAM、SRAM复用模式MUXEN置1启用地址/数据复用异步还是同步ASYNCWAIT、SYNCMODE等时序参数地址建立时间ADDSET、地址保持时间ADDHOLD、数据建立时间DATAST对于异步复用NOR Flash典型配置如下以HCLK120MHz周期约8.3ns为例地址建立时间ADDSET建议8~15个HCLK因为复用模式下要先输出地址还要等外部锁存器稳定。锁存器从输入到输出的传播延时大约是3~7ns加上地址总线沿传播时间通常留10个HCLK左右比较稳。地址保持时间ADDHOLD建议4~8个HCLK主要是让外部锁存器锁存住地址。数据建立时间DATAST这是读数据的关键参数。NOR Flash的读周期时间一般在70ns~90ns之间换算成HCLK周期约9~12个HCLK。这个时间如果太短数据还没稳定就被控制器采样读出来的值就会随机跳动。代码上使用寄存器配置而不是库函数因为寄存器更直观。以GD32的EXMC_BANK0_NORSRAM为例/* EXMC bank1 region1 配置 */ EXMC_BANK0_BCR 0x00001014 | EXMC_BCR_MUXEN | EXMC_BCR_MWID_16BITS; /* 异步复用、16位数据宽度 */ EXMC_BANK0_BTR (10 0) | /* ADDSET10 */ (5 8) | /* ADDHOLD5 */ (15 16); /* DATAST15 */这里EXMC_BCR_MUXEN置1是启用复用模式。注意BCR寄存器里还有个EXTMOD位如果NOR Flash的读时序和写时序差别很大可以把EXTMOD置1然后额外配置BWTTR寄存器。3.2 同步突发PSRAM的配置与调试要点同步突发PSRAM比异步NOR麻烦不少因为要配置时钟分频、等待状态、突发长度。以一块8MB同步突发PSRAM例如AP Memory的APS256XXXN或ISSI的IS66WVS8M8BLL为例假设HCLK168MHzPSRAM最高支持104MHz则CLKDIV2FMC_CLK84MHz。初始化步骤配置GPIO把FMC_CLK、NWAIT、NOE、NWE、NE、NL、地址线、数据线全部配置为复用功能。配置EXMC时钟分频CLKDIV。配置EXMC为同步模式启用突发模式设置突发长度。设置等待状态如果PSRAM支持NWAIT就把NWAIT对应的等待使能打开。设置地址建立时间、数据建立时间等。寄存器配置示例STM32F4的FMC为例FMC_Bank1-BTCR[0] FMC_BCR1_MUXEN | FMC_BCR1_MTYP_0 | FMC_BCR1_MWID_0 | FMC_BCR1_BURSTEN | FMC_BCR1_WAITPOL; /* 复用、NOR Flash/PSRAM类型、16位数据宽度、突发使能、等待极性 */ FMC_Bank1-BTCR[1] (1 12) | /* CLKDIV2 */ (8 0) | /* ADDSET8 */ (10 16); /* DATAST10 */ FMC_Bank1E-BWTR[0] ...; /* 写时序 */实际调试中发现同步模式的地址建立时间和数据建立时间不再用那么多HCLK周期因为有了时钟时序窗口小很多。ADDSET给1~4个时钟DATAST给4~8个时钟是常见值具体要以芯片数据手册的时序图为准。异步模式里数据建立时间可以往大了配只要不超时读得慢一点也没关系稳定优先。但同步模式里数据建立时间太长会导致控制器每次等待很久突发读的吞吐优势就被吃掉了所以需要卡在一个合理的平衡点。3.3 为什么“同步”一定要和“突发”配合才有效单独用同步但不用突发其实意义不大。因为同步模式的每一个数据都是在时钟沿采样的如果每次都只读一个地址还是要重复送地址、锁存、读数据三个阶段性能提升有限但引脚和配置复杂度却上去了。而一旦进入突发模式整个访问流程变成送一个起始地址选通片选然后按定义的突发长度依次输出数据直到突发长度耗尽或者被NWAIT打断。地址在内部自动递增。这在什么场景下最有用显存填充、屏幕刷新、代码搬移、查表计算这些都是连续地址批量读取写入的典型场景。比如从NOR Flash中拷贝一段初始化数据到SRAM或者连续读取音频帧数据用突发模式能显著提高吞吐。实际测试中我用非突发异步模式读PSRAM随机读大概只有5~10MB/s而同步突发连续读能到50~80MB/s视FMC_CLK频率和等待状态而定。对一个需要处理图像数据的系统来说这个差距是决定性的。但要注意同步突发模式里如果访问地址不连续控制器会自动回退到非突发访问模式性能会有断崖式下降。所以如果关心的是随机访问性能同步突发不是好选择。异步模式虽然总带宽低但随机读写延迟稳定CPU直接操作时不产生额外损耗。4. 常见问题与排查技巧实录4.1 读取数据全是0xFF或0x00片选都没拉低这种问题十有八九是时钟或GPIO没配置好。先检查对应片选引脚有没有输出拿示波器看NEx信号如果没有低电平脉冲说明FMC控制器根本没被触发可能原因GPIO复用功能没配置NEx引脚还是普通GPIOEXMC时钟没使能访问地址不对根本不在FMC映射范围内FSMC/FMC/EXMC的NOR/PSRAM支持在固定的地址空间比如Bank1的NE1对应0x60000000NE2对应0x64000000。如果直接对0x60000000地址操作但配置的是NE2片选那也白搭。4.2 复用模式下数据错乱怀疑是时序问题如果数据不是全0xFF而是“有点反应但不正确”大概率是复用模式下的地址建立时间不够地址还没稳定就被NADV锁存或者锁存器还没输出有效地址就开始读数据。解决思路是先加大ADDSET和ADDHOLD不要一上来就追求速度。ADDSET加到15个HCLK、ADDHOLD加到8个HCLK能稳定读到正确值以后再逐步递减到合适值。实际调试中我一般先用大时序参数把功能跑通再用示波器看数据窗口计算出真实的建立时间余量最后定一个安全的参数组合。还有一个容易犯的错数据总线的上下拉、PCB布线长度不匹配会导致数据线在不同时序下读到不同的值这时靠软件排查很痛苦只能通过调整DATAST来控制采样点位置找到最稳的窗口。4.3 PSRAM同步突发模式进不去始终跑到异步排查时先确认BCR里BURSTEN位有没有置1以及CLKDIV有没有配置。如果CLKDIV0FMC_CLK直接等于HCLK很多PSRAM不支持那么高的时钟芯片会忽略信号或者工作异常。接着检查NWAIT。如果外部PSRAM不支持NWAIT而FMC配置里开了等待极性外部拉不低等待信号FMC会一直等待表现为卡死。解决方法是先禁用NWAIT或者把等待极性配置成相反值让FMC认为不需要等待。再有就是地址区域。同步突发模式只能在FMC特有的同步访问区域使用如果用普通读函数访问普通bank里的地址控制器可能自动用异步模式即使配了突发也没效果。4.4 为什么NOR Flash写入那么慢和FSMC/FMC有没有关系这个坑很多人会误判。FMC/FSMC控制器只管总线时序但NOR Flash本身的写操作流程不止是发地址和写数据还有“编程前擦除、查询状态、写命令序列”这些Flash内部协议。比如对NOR Flash写入前要发写命令写完要查询状态寄存器确认Flash内部编程完成才能写下一个地址。所以即使你把FMC时序调到很快NOR Flash的编程速度瓶颈在Flash芯片内部编程时间而不是总线时序。你做随机单字节写入几十毫秒都是正常的。这种情况下FMC的时序参数反而不需要激进稳定即可因为Flash内部早就把总线的速度拖慢了。4.5 示波器实测经验观察FMC/EXMC波形时的关键点调外部总线示波器是必需品。重点观察以下几组信号NEx片选信号是否有干净的下降沿和上升沿毛刺多不多NADV/NL信号能否在地址阶段产生清晰的锁存脉冲数据线信号在NOE上升沿之前是否已经稳定FMC_CLK时钟是否稳定占空比接近50%实际测试中数据信号的脾气最大。如果数据线在片选释放时还没有建立完成或者经常在采样点附近跳变优先检查布局和走线不要盲目怀疑寄存器配置。外部存储器接口对PCB布线长度一致性有要求数据线和地址线尽量做等长处理。5. 写在最后的一点实战心得把FMC/FSMC/EXMC这套东西彻底弄明白最大的收获不是会配几个寄存器而是对“CPU怎么跟外部存储打交道”有了直观认识。过去总觉得存储器协议很神秘其实拆开看就是一个又一个时序窗口什么时候送地址、什么时候等数据、什么时候释放总线每一步都有据可查。从我个人的项目经验来看FMC/FSMC/EXMC的调试难点不在“驱动代码本身”而在“如何根据芯片手册推导合理的时序参数”。大多数时候芯片手册给的时序图和数据手册里有明确的建立时间、保持时间、访问周期等数值计算出对应的HCLK计数再去配寄存器几乎是零风险方案。反而是“看着别人的例程照抄”最容易出问题因为主频不同、存储器型号不同、布线质量不同参数几乎必然要调整。另外一个小技巧调试阶段可以故意把一个功能拆分到两个模式下比如异步读配置、同步突发读数据分别在示波器上观察波形确定两种模式都稳定后再合并代码。这样出问题时能快速定位是跳变模式导致的兼容性问题还是原本配置就有错。这套控制器后续能扩展的方向也不少可以用它接并口LCD做双缓冲显示、外扩SDRAM跑简单GUI、接FPGA做高速数据交换甚至接两块NOR Flash做A/B升级分区。我后面打算写一篇EXMC接SDRAM的详细驱动分析到时候再把刷新时序和Bank切换的细节一并补上。