STM32 FSMC/FMC外部存储器控制器原理与应用详解 1. 从“为什么需要FSMC/FMC”说起如果你刚开始接触STM32尤其是那些带屏幕、带SRAM、带NAND Flash的“大”项目你可能会被一个叫FSMC或者FMC的外设搞得一头雾水。数据手册上那一大堆寄存器、时序图看起来复杂得让人想放弃。别急今天我们就来把这个东西彻底掰开揉碎了讲清楚让你从“这是什么鬼”变成“原来这么回事”。首先FSMC和FMC本质上是一回事都是STM32用来连接外部存储器的控制器。FSMC是“Flexible Static Memory Controller”的缩写主要用在STM32F1、F4等系列上。而FMC是“Flexible Memory Controller”可以看作是FSMC的升级版功能更强支持的内存类型更多主要用在STM32F7、H7等高性能系列上。为了方便我们下文统一用FSMC来指代这个功能模块但在原理上是相通的。那么STM32内部不是有Flash和SRAM吗为什么还要外接原因很简单不够用或者不够快。比如你要驱动一个800x480的RGB屏一帧图像的数据量就接近1.5MBSTM32内部的SRAM通常只有几十到几百KB根本放不下。再比如你想跑一个复杂的图形界面或者文件系统需要大容量的存储内部Flash也捉襟见肘。这时候你就需要外挂一片SRAM来当“显存”或“数据池”或者外挂一片NOR Flash、NAND Flash、SDRAM来扩展存储。问题来了怎么连接最笨的办法就是用普通的GPIO口模拟8080并行总线的时序一个字节一个字节地去“怼”数据。我早期做项目就这么干过代码写起来冗长速度慢受限于CPU操作IO的速度而且极度占用CPU资源屏幕刷一下CPU啥也别干了。FSMC就是为了解决这个问题而生的。它的核心价值就是把访问外部存储器这个“体力活”交给一个专用的硬件控制器来完成。你只需要配置好这个控制器的参数告诉它外接的是什么芯片、速度多快然后就可以像访问内部内存一样用一个指针直接读写外部芯片了。CPU发出一条读/写指令FSMC硬件会自动产生符合该芯片时序要求的控制信号片选、读/写、地址、数据线整个过程无需CPU干预效率极高。简单来说FSMC就是一个“协议转换器”和“信号发生器”。它把CPU发出的内部总线访问比如通过AHB总线转换成符合外部芯片通信协议如8080并行接口、SRAM接口、NOR Flash接口的波形。这就像你CPU想去一个说外语的国家外部芯片FSMC就是一个随身翻译协议转换兼司机信号驱动你只需要说出目的地剩下的沟通和路线它全包了。2. FSMC的“地址映射”魔法把外部芯片变成内存的一部分这是理解FSMC最核心、也最巧妙的一点。STM32的CPU寻址空间是4GB32位地址线。这片巨大的“土地”被划分成了不同的区域用于映射不同的东西内部Flash、内部SRAM、外设寄存器等等。FSMC控制器独占了一大块连续的地址空间通常是0x6000 0000到0x9FFF FFFF具体起始和大小因型号而异需查数据手册。FSMC内部又把这大片地址空间分成了好几个“Bank”存储块常见的有4个Bank1-Bank4。每个Bank对应着一组独立的外部设备拥有独立的片选信号NE1, NE2, NE3, NE4 或 NEx。更重要的是每个Bank还可以通过配置支持不同类型的内存比如Bank1通常用于NOR Flash/PSRAMBank2和Bank3可用于NAND FlashBank4可用于PC卡等。对于最常用的LCD屏8080接口和SRAM我们通常使用支持NOR/PSRAM的Bank比如Bank1。那么这个“映射”具体是怎么发生的呢假设我们使用Bank1并将其起始地址配置为0x6000 0000。当我们把一片16位宽度的SRAM芯片连接到FSMC的Bank1上后神奇的事情发生了你访问单片机内存地址0x6000 0000实际上就是在访问这片外部SRAM芯片的第一个存储单元。举个例子你执行一句C语言*(volatile uint16_t *)(0x60000000) 0x1234;CPU试图向地址0x6000 0000写入数据0x1234。内存管理单元发现这个地址落在FSMC管理的地址范围内于是把这个写请求转交给FSMC控制器。FSMC控制器一看这个地址属于Bank1的区域于是它拉低对应Bank1的片选信号NE1假设我们用的是NE1选中我们的SRAM芯片。根据配置的内存宽度16位在地址线A[25:0]具体用了多少根取决于芯片容量上输出地址0x0因为0x6000 0000是Bank1的基址相对偏移为0。拉低写使能信号NWE。将数据0x1234放到16位数据线D[15:0]上。等待预先配置好的“写建立时间”、“写保持时间”结束。拉高NWE和NE1完成一次写操作。整个过程你只用了一条指针赋值语句FSMC硬件就自动完成了一整套复杂的并行总线时序读操作也是同理。这样一来对外部存储器的操作在编程层面就简化成了对一段特定内存地址的读写极其方便高效。驱动LCD屏时我们只需要把LCD的数据/命令寄存器地址映射到FSMC的某个地址然后向这个地址写数据或命令即可LCD驱动芯片会通过8080接口识别并执行。2.1 关键配置存储器类型与数据宽度在配置FSMC时你必须明确告诉它两件事你接的是什么类型的芯片以及数据总线是8位还是16位存储器类型主要分为SRAM/NOR、PSRAM、NOR Flash等。不同类型的芯片其读写时序的特征不同。例如NOR Flash通常需要较长的地址建立时间。FSMC通过“存储器类型”配置来选择内置的、最接近该类型芯片的默认时序模型作为我们进一步调整的基础。数据宽度这是最容易出错的地方之一。它指的是FSMC控制器与外部芯片之间数据总线的位宽可以是8位或16位。这个选择直接影响硬件连接和地址生成。8位宽度使用数据线D[7:0]。当你访问一个32位变量如uint32_t时FSMC会自动将其拆成4个连续的8位访问分4次完成传输。此时地址线A[0]通常用于区分数据/命令在8080接口中而A[1]对应外部存储器的地址线A0。16位宽度使用数据线D[15:0]。访问16位变量uint16_t一次完成。访问8位变量uint8_t时FSMC也能处理但需要特别注意字节对齐可能会涉及高/低字节选择信号NBL。此时地址线A[0]不起寻址作用因为16位宽时最小寻址单元是2字节外部存储器的地址线A0应连接到FSMC的A[1]。这一点是硬件设计时的关键接错了就无法正确寻址。注意很多新手在画PCB或连接液晶屏时容易忽略数据宽度对地址线连接的影响。如果你的LCD是16位8080接口那么LCD的地址线A0通常标记为RS命令/数据选择引脚应该接到STM32的FSMC_A[1]上而不是FSMC_A[0]。这是一个经典的“坑”。3. FSMC时序模型深度拆解不仅仅是几个时间参数FSMC之所以“Flexible”灵活就在于它允许我们精细地控制访问外部芯片的时序以适应不同速度、不同型号的芯片。它的时序模型可以理解为对一个读或写操作周期的“分解动作”。我们以SRAM/NOR Flash Bank的时序配置寄存器FSMC_BTR和FSMC_BWTR分别对应读时序和写时序为例来讲解。每个时序寄存器都包含以下几个关键参数单位是HCLK时钟周期ADDSET: 地址建立时间。在片选信号有效后地址信号需要保持稳定的时间才能发出读/写命令。对应外部芯片数据手册里的t_SU(ADDR)。ADDHLD: 地址保持时间。在读写信号无效后地址信号还需要保持稳定的时间。这个参数在某些模式下可能不需要。DATAST: 数据建立时间。对于读操作是从读使能有效到FSMC采样数据线之前必须等待的时间确保外部芯片的数据已经稳定输出。对于写操作是写使能有效后数据需要保持稳定的时间。对应读周期的t_SU(DATA)或写周期的t_WP。BUSTURN: 总线周转时间。主要用于在两个不同的读操作之间或者读操作之后紧跟写操作时插入一个空闲周期防止总线冲突。对于连续访问同一设备的操作通常可以设为0。这些参数怎么来的答案一定来自外部芯片的数据手册Datasheet。绝对不能拍脑袋随便填。我们以一个常见的16位SRAM芯片IS62WV51216为例看看如何从数据手册映射到FSMC配置。假设我们芯片的关键时序参数如下在特定电压和温度下t_RC(读周期时间): 最小55nst_AA(地址访问时间): 最大55nst_WC(写周期时间): 最小55nst_PWE(写使能脉冲宽度): 最小45nst_SU(ADDR)(地址建立到写使能有效): 最小0nst_HD(ADDR)(写使能无效后地址保持): 最小10ns我们的STM32系统时钟HCLK为72MHz则一个HCLK周期约为13.89ns。配置读时序核心是满足t_AA。t_AA是地址有效后数据稳定输出的最大时间。FSMC的读周期总时间 (ADDSET DATAST 1) * HCLK周期。其中DATAST必须覆盖t_AA。假设我们设置ADDSET 1约13.89ns那么DATAST需要满足(1 DATAST 1) * 13.89ns 55ns。计算可得DATAST至少需要55/13.89 - 2 ≈ 1.96取整为2。所以DATAST可以设为2或3以留有余量。因此读时序可以配置为ADDSET1,DATAST3。总读周期时间约为(131)*13.8969.45ns大于芯片要求的55ns满足条件。配置写时序核心是满足t_PWE写使能脉冲宽度和t_HD(ADDR)。在FSMC的“模式A”或“模式2”写时序中DATAST参数决定了写使能信号低电平的宽度。我们需要DATAST * HCLK周期 t_PWE。45ns / 13.89ns ≈ 3.24所以DATAST至少为4。同时ADDSET需要满足t_SU(ADDR)本例为0最小可为0和t_HD(ADDR)。t_HD(ADDR)要求写使能无效后地址至少保持10ns。FSMC的时序保证在写使能无效后地址还会保持ADDSET个周期。所以ADDSET * 13.89ns 10nsADDSET至少为1。因此写时序可以配置为ADDSET1,DATAST4。实操心得在实际项目中尤其是驱动TFT液晶屏时屏驱动IC的时序要求往往比SRAM更严格而且对读写速度有要求影响刷屏率。我的经验是先根据数据手册计算一个保守值然后在实际硬件上测试。可以通过逐步减小DATAST和ADDSET参数直到屏幕显示出现雪花、错点或不稳定然后再回调1-2个周期作为最终值。这样能在稳定性和速度之间取得最佳平衡。另外如果屏幕初始化或读写一直失败首先应该用逻辑分析仪或示波器抓取FSMC引脚的实际波形对照数据手册的时序图逐一检查这是最直接的调试手段。4. 硬件连接实战指南与常见陷阱理解了原理和时序最终要落到硬件连接上。这里以STM32F407驱动一款16位8080并行接口的ILI9341 TFT屏为例说明FSMC的典型连接方式和必须避开的“坑”。引脚分配数据线 D[15:0] 连接STM32的FSMC_D[15:0]到LCD的D[15:0]。务必注意如果LCD是16位接口必须连接全部16根不能只接8根否则颜色显示会错乱高8位缺失。地址线 A[1] 连接STM32的FSMC_A[1]到LCD的RS寄存器选择引脚。如前所述16位模式下A[0]不用于寻址LCD的A0/RS引脚接A[1]。控制线片选 NE1 连接STM32的FSMC_NE1可能是PG9具体查芯片数据手册到LCD的CS#片选引脚。写使能 NWE 连接STM32的FSMC_NWE到LCD的WR#写使能引脚。读使能 NOE 连接STM32的FSMC_NOE到LCD的RD#读使能引脚。复位和背光 这些引脚通常用普通GPIO控制不占用FSMC资源。配置步骤以HAL库为例开启时钟 使能FSMC和对应GPIO端口的时钟。配置GPIO 将所有用到的FSMC数据线、地址线、控制线引脚模式设置为复用推挽输出速度设置为High或Very High以提供足够的驱动能力。配置FSMC 填充SRAM_HandleTypeDef结构体。Instance 选择FSMC Bank例如FSMC_NORSRAM_DEVICE或FSMC_NORSRAM_EXTENDED_DEVICE。InitMemoryTypeFSMC_MEMORY_TYPE_SRAM对于8080接口的LCD通常配置为SRAM模式虽然它不是SRAM但时序类似。DataAddressMuxFSMC_DATA_ADDRESS_MUX_DISABLE数据和地址线不复用。MemoryDataWidthFSMC_NORSRAM_MEM_BUS_WIDTH_1616位宽度。BurstAccessModeFSMC_BURST_ACCESS_MODE_DISABLE8080接口一般不支持突发模式。WaitSignalPolarityFSMC_WAIT_SIGNAL_POLARITY_LOW。WrapModeFSMC_WRAP_MODE_DISABLE。WaitSignalActiveFSMC_WAIT_TIMING_BEFORE_WS。WriteOperationFSMC_WRITE_OPERATION_ENABLE必须开启写。WaitSignalFSMC_WAIT_SIGNAL_DISABLE。ExtendedModeFSMC_EXTENDED_MODE_ENABLE重要启用扩展模式才能分别配置读时序和写时序。AsynchronousWaitFSMC_ASYNCHRONOUS_WAIT_DISABLE。WriteBurstFSMC_WRITE_BURST_DISABLE。Timing和ExtendedTiming 分别填入我们前面计算好的读时序和写时序参数结构体。初始化 调用HAL_SRAM_Init()函数。定义访问地址 根据连接Bank1的起始地址是0x60000000我们使用NE1且RS接在A[1]上。那么LCD的命令寄存器地址0x60000000A[1]0即RS0。LCD的数据寄存器地址0x60000000 2A[1]1即RS1。因为16位总线地址偏移2对应A[1]的跳变。 在代码中我们可以这样定义#define LCD_CMD_ADDR ((uint32_t)0x60000000)#define LCD_DATA_ADDR ((uint32_t)0x60000002)然后通过指针进行读写*(__IO uint16_t *)LCD_CMD_ADDR 0x2A; // 发送命令常见陷阱与排查屏幕全白或全黑无任何显示 首先检查背光是否开启复位时序是否正确。然后用逻辑分析仪检查FSMC的CS、WR、RD、A[1]、D[15:0]是否有波形。如果完全没有波形检查FSMC时钟和GPIO配置是否正确WriteOperation是否使能。屏幕有显示但错乱、花屏 这是最典型的问题。首要怀疑对象是时序参数。可能是DATAST或ADDSET设置得太小不满足LCD驱动IC的建立保持时间。其次是数据/命令地址错误确认LCD_CMD_ADDR和LCD_DATA_ADDR的计算是否正确特别是A[1]的连接。最后检查数据线连接是否虚焊、错位D0-D15顺序。读写速度慢 在确保稳定的前提下可以尝试优化减小DATAST和ADDSET参数。另外检查是否开启了DMA。对于刷屏这种大数据量操作应该使用FSMC配合DMA将显存数据直接搬运到LCD_DATA_ADDR可以极大解放CPU。使用SDRAM时更复杂 SDRAM如IS42S16400J需要初始化序列预充电、模式寄存器设置、自动刷新等其FSMC配置FMC_SDRAM与SRAM不同涉及刷新率、延迟、突发长度等更多参数。调试SDRAM时建议先从官方例程的配置开始并务必仔细核对原理图连接特别是时钟、行列地址线、Bank选择线的连接。FSMC/FMC是一个功能强大但稍显复杂的模块初次接触会觉得寄存器繁多。但只要你理解了其“地址映射”的核心思想掌握了时序参数的计算方法并细心完成硬件连接它就会成为你项目中最得力的助手轻松驱动起各种外部存储器为你的STM32应用打开一片新天地。在下一部分我们将深入FSMC的扩展模式、不同存储器的配置差异以及如何利用DMA与FSMC配合实现高效数据传输。