STM32 FSMC/FMC外部存储器接口:从原理到驱动LCD与SRAM实战

STM32 FSMC/FMC外部存储器接口:从原理到驱动LCD与SRAM实战 1. 项目概述为什么FSMC/FMC是STM32连接外部存储器的“高速公路”如果你玩过STM32尤其是那些带屏幕、需要大容量数据存储或者要跑复杂UI的项目那你大概率听说过FSMCFlexible Static Memory Controller或者它的升级版FMCFlexible Memory Controller。这玩意儿在STM32的生态里尤其是在F1、F4、H7这些系列里地位相当关键。简单来说它就是STM32内部CPU核心与外部那些“慢吞吞”的静态存储器比如SRAM、NOR Flash、PSRAM或者液晶屏控制器比如8080/6800接口的TFT屏之间的一座专用高速桥梁。为什么需要这座桥因为STM32内部的SRAM和Flash容量有限当你需要显示一张高分辨率的图片或者运行一个需要大量数据缓存的算法时内部那点内存就捉襟见肘了。这时候你就得外挂一块大容量的SRAM或者一块NOR Flash来存数据。但是这些外部芯片的接口时序和STM32的GPIO直接读写时序对不上如果只用软件模拟速度慢、占用CPU资源还容易出错。FSMC/FMC就是硬件上帮你解决了这个问题的“交通警察”和“协议转换器”它把CPU的读写指令翻译成外部芯片能听懂的、精确到纳秒级别的控制信号像地址线、数据线、片选、读/写使能等让CPU可以像访问内部内存一样直接用指针去访问外部的大容量存储空间。我最早在STM32F103ZE上用FSMC驱动一块4.3寸的TFT屏当时就被它“傻瓜式”的配置和飞快的刷屏速度震撼到了。后来在F429、H750这些更高级的芯片上用FMC挂SDRAM更是体会到了硬件加速带来的性能红利。这个系列我就打算从一个一线开发者的角度把FSMC/FMC从原理到配置再到实际踩坑经验掰开揉碎了讲清楚。无论你是刚接触还是想深入理解其工作机制这篇“保姆级”讲解都希望能让你彻底搞懂它。2. FSMC/FMC核心架构与工作原理拆解要理解FSMC/FMC不能只停留在“配置几个寄存器就能用”的层面。你得先明白它内部是怎么组织、怎么工作的这样后面配置参数时你才知道每一个选项背后的意义出了问题也知道该往哪个方向排查。2.1 内存块Bank与地址映射你的“外设停车场”这是FSMC/FMC最核心的概念之一。你可以把FSMC/FMC控制器想象成一个大型的多层停车场内存块每一层Bank专门停一种类型的车外部存储器。STM32的FSMC/FMC通常将外部存储器的地址空间划分为4个独立的BankBank1~Bank4每个Bank有独立的片选信号NE1~NE4或NEx并且每个Bank可以配置为支持不同的存储器类型。关键点在于地址映射FSMC/FMC会把分配给每个Bank的一段CPU地址空间比如Bank1是0x6000 0000到0x6FFF FFFF通过硬件自动转换成对外部芯片的访问。当你用C语言写*(volatile uint16_t *)(0x60000000) 0x1234;这条语句时CPU认为它只是在向地址0x60000000写数据。但FSMC/FMC硬件会“拦截”这个操作并做以下事情根据地址的高位确定目标Bank例如0x6...开头的地址属于Bank1从而激活对应的片选信号NE1输出低电平。根据地址的低位转换成具体的地址线A0-A25等上的电平信号。根据你是读还是写操作控制NOE读使能或NWE写使能信号。将数据0x1234放到数据线D0-D15等上写操作或者从数据线上读取数据读操作。这个映射关系是固定的由芯片设计决定。例如在STM32F1系列中Bank1通常用于NOR/PSRAM映射到0x6000 0000 - 0x6FFF FFFFBank2和Bank3用于NAND FlashBank4用于PC卡。而在F4/F7/H7的FMC中Bank1又常被细分为4个子Bank对应4个片选用于NOR/SRAM而Bank5~6则专门用于SDRAM。注意这个“停车场”的地址是CPU看到的“虚拟”地址和你外部存储器芯片本身的物理地址是两码事。外部芯片的物理地址范围由它的地址线数量决定比如有20根地址线A0-A19它的寻址范围就是2^20 1MB。FSMC/FMC的地址线会连接到这些引脚上CPU发出的地址值其低位部分会直接呈现在这些地址线上。2.2 支持的存储器类型与接口模式FSMC/FMC之所以“Flexible”灵活就是因为它能适配多种存储器接口。主要支持以下几类SRAM/PSRAM (静态随机存储器)这是最常用的一种。接口简单主要信号就是地址线、双向数据线、写使能NWE、读使能NOE和片选NEx。FSMC/FMC可以非常高效地模拟这种接口的时序。很多液晶屏的控制器如ILI9341, SSD1963使用的8080并行接口其时序与SRAM接口高度相似因此也可以用FSMC/FMC来驱动从而极大减轻CPU负担实现“硬件刷屏”。NOR Flash/OneNAND FlashNOR Flash允许像内存一样随机读取也支持按块擦除和编程。它的接口和SRAM类似但写入编程和擦除操作需要特定的命令序列和较长的等待时间。FSMC/FMC可以通过配置不同的时序模型后面会讲来满足其要求。NAND Flash主要用于大容量数据存储如文件系统。它的接口与SRAM不同采用复用I/O口来传输命令、地址和数据并且需要检查“忙”状态。FSMC/FMC有专门的NAND Flash控制器能处理这些复杂的协议。SDRAM (同步动态随机存储器)这是FMC在F2/F4/F7/H7等系列中相较于FSMC增加的重要功能。SDRAM容量大、成本低但时序极其复杂需要定时刷新、有行列地址分时复用、各种延迟参数CL, tRCD, tRP等。FMC内置的SDRAM控制器自动化了这些繁琐的操作让开发者可以用相对简单的方式使用大容量内存。对于我们大部分应用尤其是驱动液晶屏和扩展SRAM接触最多的就是SRAM/PSRAM模式和8080并口模式。本系列讲解也将重点聚焦于此。2.3 时序模型与外部芯片的“对话节奏”这是配置FSMC/FMC时最需要精细调整的部分直接决定了通信的稳定性和速度。FSMC/FMC为SRAM/PSRAM/NOR Flash提供了几种预定义的时序模型本质上是几组可以独立配置的延时参数用来匹配外部芯片数据手册上的时序要求。以最常用的Mode A/B对应STM32CubeMX中的“Mode1”和“Mode2”为例我们来拆解一次读操作和一次写操作的时序这就像你和外部芯片对话的“节奏协议”。读操作时序以Mode A为例地址建立阶段 (ADDSET)当你给出要读的地址后需要等待一段时间ADDSET个HCLK周期让地址信号在地址线上稳定下来。这对应外部芯片数据手册里的t_{SU}(ADDR)参数。数据建立阶段 (DATAST)地址稳定后你发出读使能信号NOE变低。然后需要再等待一段时间DATAST个HCLK周期外部芯片才会把有效数据放到数据总线上。这个时间必须大于外部芯片的读取访问时间t_{ACC}或输出使能访问时间t_{OE}。数据保持阶段FSMC/FMC在DATAST结束后采样数据然后释放读使能NOE变高。地址还会再保持一段时间ADDHLD通常可配或固定。写操作时序以Mode B为例地址建立阶段 (ADDSET)同上先让地址稳定。数据建立阶段 (DATAST)在写使能NWE变低之前需要先将要写的数据放到数据总线上并保持稳定一段时间DATAST的一部分对应t_{SU}(DATA)。NWE变低后数据需要继续保持一段时间DATAST的另一部分对应t_{PWE}脉冲宽度。写使能后地址保持阶段NWE变高后地址和数据通常还需要保持一小段时间对应t_{H}确保写入可靠。在STM32的库函数或CubeMX配置中你会看到FSMC_NORSRAM_TimingTypeDef这样的结构体里面主要就是配置AddressSetupTime(ADDSET),DataSetupTime(DATAST),BusTurnAroundDuration(BUSTURN用于读写切换的额外延时) 等参数。你的核心任务就是根据你所使用的外部芯片的数据手册查找到上述关键时序参数然后根据FSMC/FMC的时钟频率HCLK计算出对应的周期数填到这些配置项里。实操心得对于常见的SRAM芯片如IS62WV51216或8080接口的LCD其访问时间如55ns, 70ns通常远慢于STM32的HCLK比如72MHz周期约13.9ns。计算DATAST时用(芯片访问时间 / HCLK周期) 1并向上取整是个安全的起点。例如70ns访问时间72MHz HCLK周期13.9ns70/13.9 ≈ 5.04那么DATAST至少配置为6个周期。ADDSET可以从小值如1开始试。原则是在满足外部芯片最低时序要求的前提下参数越小访问速度越快。3. 硬件电路设计与引脚连接详解理解了原理我们就要动手把STM32和外部芯片连起来。这部分是硬件基础连接错误会导致通信彻底失败。3.1 引脚功能定义与复用FSMC/FMC使用了STM32的一组特定GPIO通常是端口A, B, C, D, E, F, G等的高位引脚。这些引脚在启用FSMC/FMC功能后会被复用为以下信号线地址线 (A0-A25)具体数量取决于芯片型号和封装。例如F103ZET6可能支持A0-A25而F103C8T6可能只支持A16-A18用于LCD的RS信号。地址线是输出。数据线 (D0-D15)用于16位数据宽度也支持8位模式。数据线是双向的。片选信号 (NE1/NE2/NE3/NE4 或 NEx)每个Bank一个低电平有效。是输出。读使能 (NOE)低电平有效读操作时输出低电平。是输出。写使能 (NWE)低电平有效写操作时输出低电平。是输出。字节选择 (NBL0, NBL1)在16位模式下用于选择高字节或低字节。可以理解为“掩码”信号。是输出。关键连接示例以16位SRAM (IS62WV51216) 为例假设我们使用Bank1片选NE1对应引脚PD7。FSMC_A0 ~ FSMC_A18 连接 SRAM 的 A0~A18SRAM是512Kx16位地址线需要19根2^19 * 2字节 1MB。FSMC_D0 ~ FSMC_D15 连接 SRAM 的 I/O0~I/O15。FSMC_NE1 连接 SRAM 的 /CE (片选)。FSMC_NOE 连接 SRAM 的 /OE (输出使能)。FSMC_NWE 连接 SRAM 的 /WE (写使能)。FSMC_NBL0, NBL1 可以连接 SRAM 的 UB, LB高/低字节使能如果不需要按字节操作也可以不接。关键连接示例以16位8080并口LCD (ILI9341) 为例这是更常见的用法。LCD的8080接口通常有D0-D15数据线。/CS片选。/RD读使能对应FSMC_NOE。/WR写使能对应FSMC_NWE。RS (或 D/C)命令/数据寄存器选择线。这是关键8080接口通过RS线的高低电平来区分当前写入的是命令还是数据。我们通常将FSMC的一根地址线例如A0连接到LCD的RS引脚。当A00时访问的是命令寄存器地址。当A01时访问的是数据寄存器地址。/RESET复位线用普通GPIO控制即可。BACKLIGHT背光控制用普通GPIO或PWM控制。那么硬件连接如下FSMC_D0~D15 连接 LCD_D0~D15。FSMC_NE1 (或其他NE) 连接 LCD_/CS。FSMC_NOE 连接 LCD_/RD。FSMC_NWE 连接 LCD_/WR。FSMC_A0 连接 LCD_RS。这是8080模式的核心FSMC的其他地址线A1-Ax悬空或接地。这样连接后我们在软件里就可以定义两个宏#define LCD_CMD_ADDR ((uint32_t)0x60000000) // A00 的地址即基地址 #define LCD_DATA_ADDR ((uint32_t)0x60020000) // A01 的地址基地址 (1 (A0的偏移))。A0是第0位地址线偏移1位所以地址2。注意是字节地址16位模式下地址步进为2。向LCD_CMD_ADDR写入就是发送命令向LCD_DATA_ADDR写入就是发送数据。FSMC硬件会自动在写LCD_DATA_ADDR时让A0线输出高电平。3.2 硬件设计注意事项与常见坑点上拉电阻FSMC的数据线是双向的在空闲时应处于高阻态。为了确保电平稳定尤其是总线未被任何设备驱动时建议在数据线D0-D15上添加4.7kΩ - 10kΩ的上拉电阻到VCC。很多开发板为了省事没加在速度不高、布线短的情况下可能没问题但在复杂环境或高速下容易出问题。电源与去耦外部存储器和STM32使用干净、稳定的电源。在每个芯片的电源引脚附近务必放置一个0.1uF的陶瓷电容进行高频去耦并可能还需要一个10uF的钽电容进行低频滤波。这是保证信号完整性的基础。布线等长与阻抗对于高速操作特别是SDRAM地址线、数据线、控制线的走线长度应尽量等长以减少信号偏移。如果是在两层板上做简单应用尽量让走线短而直避免过长的平行走线以减少串扰。引脚冲突FSMC/FMC使用的引脚很多且与某些外设如USB、SDIO的引脚有重叠。在项目规划初期就要在芯片数据手册的“引脚定义”章节和“复用功能”章节确认好避免功能冲突。例如STM32F103ZET6的PD2引脚是FSMC_NE2但同时也是TIM3_ETR和SDIO_CMD如果你同时要用SD卡和FSMC Bank2就得小心。电平匹配确保STM32的IO口电平通常是3.3V与外部芯片的IO电平兼容。大多数现代SRAM和LCD控制器都是3.3V兼容的但老式5V器件可能需要电平转换。4. 软件配置实战从CubeMX到代码解析理论说了这么多现在我们来点实际的。我会以STM32F407驱动一块16位8080接口的ILI9341液晶屏为例使用STM32CubeMX和HAL库展示完整的配置流程和代码解析。4.1 CubeMX图形化配置步骤启用FMC控制器在CubeMX的“Pinout Configuration”标签页左侧找到“Connectivity” - “FMC”。勾选“FMC”使其启用。选择存储类型和Bank在“FMC”配置树下选择“FMC”。在右侧配置窗口点击“Add”添加一个存储器类型。从下拉菜单中选择“Chip Select 1” (对应NE1地址从0x60000000开始)。“Memory type”选择“LCD Interface”。“LCD Register Select”选择“A0”。这就是告诉FMC我们用A0地址线作为LCD的RS信号。“Data”选择“16 bits”。我们的LCD是16位并口。配置时序参数这是最关键的一步。点击刚创建的“Chip Select 1”下方会出现时序配置。Chip Select保持默认或根据需求微调。写操作时序 (Write)Address setup time: 对应ADDSET。对于ILI9341命令/数据的建立时间要求很短比如15ns。我们的HCLK假设为168MHz周期约6ns可以设一个很小的值比如1。Data setup time: 对应DATAST。这是最重要的参数。查看ILI9341数据手册写数据周期时间(t_{WC})最小是66ns。66ns / 6ns ≈ 11个周期。为了保险可以设为12或15。我通常从15开始试。Access mode: 选择“Mode A”或“Mode B”都可以8080接口两者都兼容。通常选“Mode A”。读操作时序 (Read)如果你需要从LCD读数据例如读ID、读GRAM也需要配置。读操作通常比写慢。Data setup time需要更大比如20-25个周期。如果只写不读这里可以随便填但最好也合理配置。GPIO引脚自动分配完成上述配置后CubeMX会自动将相关的GPIO引脚如D0-D15, A0, NE1, NOE, NWE等分配到正确的端口上并设置为复用推挽输出模式。你可以在“Pinout”视图上检查确认。生成代码配置好系统时钟、工程名等后生成代码。4.2 生成的HAL库代码解析与用户层驱动编写CubeMX生成的代码主要初始化了FMC硬件。用户需要在此基础上编写LCD的底层驱动。生成的fmc.c中的关键函数void MX_FMC_Init(void) { FMC_NORSRAM_TimingTypeDef Timing {0}; FMC_NORSRAM_TimingTypeDef ExtTiming {0}; /** Perform the SRAM1 memory initialization sequence */ hsram1.Instance FMC_NORSRAM_DEVICE; hsram1.Extended FMC_NORSRAM_EXTENDED_DEVICE; /* Timing 配置对应之前的图形化设置 */ Timing.AddressSetupTime 1; Timing.AddressHoldTime 0; Timing.DataSetupTime 15; Timing.BusTurnAroundDuration 0; Timing.CLKDivision 0; Timing.DataLatency 0; Timing.AccessMode FMC_ACCESS_MODE_A; /* ExtTiming 是读时序如果配置了的话 */ ExtTiming.AddressSetupTime 1; ExtTiming.AddressHoldTime 0; ExtTiming.DataSetupTime 25; ExtTiming.BusTurnAroundDuration 0; ExtTiming.CLKDivision 0; ExtTiming.DataLatency 0; ExtTiming.AccessMode FMC_ACCESS_MODE_A; if (HAL_SRAM_Init(hsram1, Timing, ExtTiming) ! HAL_OK) { Error_Handler(); } }这个函数初始化了FMC控制器将我们配置的时序参数写入相关寄存器。用户层LCD驱动编写 我们需要定义访问LCD的地址并实现基本的写命令、写数据函数。// lcd.h 或 lcd.c 中定义 // Bank1 (NE1) 的起始地址是 0x60000000 // 我们使用A0作为RSA00是命令A01是数据。 // 在16位数据宽度下地址总线A[25:1]对应FSMC_A[24:0]A0不使用或说被内部用于字节选择。 // 但我们把FSMC的A0引脚连到了LCD_RS。所以要让A0输出0我们访问地址 (0x60000000 (0 1))。 // 要让A0输出1我们访问地址 (0x60000000 (1 1))。因为16位模式下地址对齐是2字节。 // 更通用的计算方式是命令地址 Bank1基址数据地址 Bank1基址 (RS引脚连接的地址线位偏移 1)。 // 假设RS连FSMC_A0则偏移为0数据地址 0x60000000 (1 (0 1)) 0x60000002。 // 假设RS连FSMC_A16则偏移为16数据地址 0x60000000 (1 (16 1)) 0x60020000。 #define LCD_BASE_ADDRESS ((uint32_t)0x60000000) // Bank1, NE1 #define LCD_REG_ADDRESS (*(volatile uint16_t *)(LCD_BASE_ADDRESS | 0x00000000)) // A00 #define LCD_DATA_ADDRESS (*(volatile uint16_t *)(LCD_BASE_ADDRESS | 0x00020000)) // A161如果RS连A16。这里以A16为例更常见。 // 更清晰的宏定义方式假设RS接A16 #define LCD_CMD *(volatile uint16_t *)0x60000000 #define LCD_DATA *(volatile uint16_t *)0x60020000 // 基本操作函数 void LCD_WriteCmd(uint16_t cmd) { LCD_CMD cmd; // 可以加少量延时如果时序配置得比较极限的话 // __NOP(); __NOP(); } void LCD_WriteData(uint16_t data) { LCD_DATA data; } void LCD_WriteReg(uint16_t reg, uint16_t val) { LCD_WriteCmd(reg); LCD_WriteData(val); } // 初始化序列 void LCD_Init(void) { // 硬件复位如果接了RESET引脚 LCD_RST_CLR(); HAL_Delay(100); LCD_RST_SET(); HAL_Delay(100); // 发送初始化命令序列参考ILI9341数据手册 LCD_WriteCmd(0xCF); LCD_WriteData(0x00); LCD_WriteData(0xC1); LCD_WriteData(0x30); // ... 更多初始化命令 LCD_WriteCmd(0x11); // Sleep Out HAL_Delay(120); LCD_WriteCmd(0x29); // Display ON }关键点volatile关键字至关重要。它告诉编译器这个指针指向的内容可能被硬件FMC改变或者每次访问都可能产生副作用比如触发一次FMC操作因此编译器不能对这个地址的读写做任何优化比如合并多次写操作、缓存数据等必须严格按照代码顺序执行每一次访问。4.3 高级应用使用DMA与FMC配合刷屏单纯用CPU写数据到FMC地址来刷屏仍然会占用大量CPU时间。更高效的方式是使用DMA直接存储器访问。你可以将一块内存缓冲区比如存放了图片像素数据的数组的地址配置为DMA的源地址将LCD的数据寄存器地址LCD_DATA_ADDRESS配置为DMA的目标地址。然后启动DMA传输DMA控制器会自动将缓冲区中的数据源源不断地通过FMC总线发送到LCD而CPU在此期间可以解放出来处理其他任务。配置思路配置一个DMA流如DMA2 Stream0方向为存储器到外设MEM2MEM模式其实也可以但更准确是MEM到FMC这个“外设”。外设地址设为(uint32_t)LCD_DATA。存储器地址设为你的像素数据数组地址。数据宽度为半字16位。禁用外设地址增量始终写入同一个LCD数据寄存器使能存储器地址增量。设置传输数据量像素个数。在FMC初始化完成后启动DMA传输。核心代码片段// 假设已定义像素缓冲区 uint16_t lcd_buffer[LCD_WIDTH * LCD_HEIGHT]; // 假设DMA句柄为 hdma_memtomem_dma2_stream0 void LCD_DMA_WriteData(uint16_t *pData, uint32_t Size) { // 停止可能的当前传输 HAL_DMA_Abort(hdma_memtomem_dma2_stream0); // 配置DMA __HAL_DMA_DISABLE(hdma_memtomem_dma2_stream0); hdma_memtomem_dma2_stream0.Instance-PAR (uint32_t)LCD_DATA; // 目标LCD数据地址 hdma_memtomem_dma2_stream0.Instance-M0AR (uint32_t)pData; // 源数据缓冲区 hdma_memtomem_dma2_stream0.Instance-NDTR Size; // 传输数量 // 其他配置数据宽度、地址增量等应在CubeMX或初始化函数中预设好 __HAL_DMA_ENABLE(hdma_memtomem_dma2_stream0); // 等待传输完成或使用中断 while(__HAL_DMA_GET_FLAG(hdma_memtomem_dma2_stream0, DMA_FLAG_TCIF0) RESET); __HAL_DMA_CLEAR_FLAG(hdma_memtomem_dma2_stream0, DMA_FLAG_TCIF0); }结合DMAFMC的性能才能被完全释放实现流畅的动画或视频播放效果。5. 调试技巧与常见问题排查实录即使按照手册配置第一次使用FSMC/FMC也难免遇到问题。下面是我总结的一些常见坑点和调试方法。5.1 现象屏幕白屏、花屏、颜色错乱这是最常见的问题原因多种多样。时序参数不匹配这是首要怀疑对象。症状可能是部分区域显示正常部分花屏或者完全白屏。排查用逻辑分析仪或示波器抓取FSMC的控制信号NOE, NWE, NE和数据信号D0-D15。重点看写使能NWE的宽度t_{PWE}和数据建立时间t_{SU}(DATA)是否满足LCD数据手册的要求。如果没有仪器就逐步增加DataSetupTime和AddressSetupTime比如从15加到202530... 同时减小HCLK频率试试。这是一个试错的过程。心得对于ILI9341DataSetupTime在168MHz下我通常用15-20在72MHz下用8-12。AddressSetupTime用1或2即可。硬件连接错误检查杜邦线或PCB走线。尤其注意数据线D0-D15是否接反或错位。哪怕只错一根颜色也会完全不对。RSA0线是否连接正确且配置对应。如果RS接的是A16但软件里用的是A0的地址偏移那命令和数据就全乱了。电源和地是否稳定。用万用表量一下LCD模块的VCC和GND引脚电压是否在3.3V左右纹波是否过大。初始化序列错误或遗漏不同厂家、不同型号的LCD初始化序列可能不同。确保你使用的初始化代码与你的屏幕型号完全匹配。有些屏幕还需要在初始化前等待足够长的时间比如120ms让内部电源稳定。字节序问题STM32是小端模式Little Endian而有些LCD控制器可能期望的数据字节序不同。如果你发送的16位颜色值如0xF800表示红色显示出来颜色不对可以尝试交换高低字节。在发送数据前用__REV16()宏转换一下或者检查LCD数据手册是否有关于字节序的说明。5.2 现象读写外部SRAM数据错误地址线连接不足如果你外挂的SRAM是1MB19根地址线A0-A18但你只连接了A0-A1516根那么当你访问超过64KB的地址时高位地址无法给出就会访问到错误的物理位置。确保连接的地址线数量覆盖整个你需要访问的地址空间。时序问题同LCDSRAM也有访问时间要求。如果DataSetupTime太短读回来的数据可能不稳定。可以通过编写一个简单的内存测试函数如写-读-比较全地址空间来验证并调整时序。总线竞争如果总线上挂了多个设备比如LCD和SRAM要确保它们的片选NE信号在任何时候只有一个有效。软件上访问不同设备时地址映射不能重叠。变量未用volatile修饰访问FSMC映射地址的指针必须用volatile修饰否则编译器优化可能导致奇怪的错误。5.3 调试工具与方法软件仿真在MDK或IAR中可以查看Memory窗口。输入FSMC Bank的地址如0x60000000如果配置正确你应该能看到该地址区域是可以访问的不是全0或全F。尝试写入一个值再读回来看是否一致。注意软件仿真无法模拟外部芯片的响应所以读回来的值可能不是你写的但至少可以测试总线访问是否被正确触发。硬件调试与打印在初始化FMC后通过串口打印关键寄存器的值如FMC_BCR1, FMC_BTR1等与参考手册对照看配置是否已生效。示波器/逻辑分析仪这是最强大的硬件调试工具。连接几根关键信号线NWE, NOE, NE1, A0, D0运行一个简单的连续写数据循环。你应该能看到周期性的、规整的波形。测量NWE低电平的宽度计算一下是否与你配置的DataSetupTime相符。观察数据线D0-D15上的数据是否与你程序发送的数据一致。简化测试程序写一个最简单的测试程序不要用复杂的LCD驱动库。就循环发送几个固定的命令和数据比如设置开显示看屏幕是否有反应。排除软件其他部分的干扰。5.4 一个典型的排查流程记录我曾经遇到一个项目FMC驱动LCD在低温下偶尔花屏。现象室温下显示正常-10°C实验室环境下运行十几分钟后屏幕局部出现雪花点。初步判断时序临界或电源问题。排查步骤 a.查电源用示波器监测LCD模块的3.3V输入在低温下纹波略有增大但在合理范围内。 b.查时序用逻辑分析仪抓取FMC信号。发现常温下NWE低电平宽度约为100ns配置为15个周期168MHz约90ns。低温下这个宽度略微缩短约95ns但仍在LCD芯片手册要求的最小值66ns之上。 c.深入分析仔细看数据手册发现除了t_{WC}还有一个参数叫t_{WRES}表示写使能撤销后数据需要保持的时间。我之前忽略了它。计算发现我配置的时序在低温下t_{WRES}可能接近临界值。 d.解决将DataSetupTime从15增加到18相当于增加了数据保持时间。同时将AddressSetupTime从1增加到2。重新测试低温下连续运行24小时无花屏。教训配置时序时要关注所有相关的时间参数特别是建立时间(t_{SU})和保持时间(t_{H})而不仅仅是周期时间。并且要留足余量尤其是产品需要工作在宽温范围时。FSMC/FMC是一个强大但需要耐心调试的模块。第一次成功点亮屏幕或者正确读取外部SRAM数据时那种成就感是无与伦比的。希望这篇超详细的讲解能帮你打通任督二脉少走些弯路。在下一讲中我们会深入FMC的SDRAM控制器看看如何驾驭那片容量更大、时序更复杂的“动态”内存。