i.MX RT嵌入式开发:实现Flash边读边写(RWW)的双方案详解
1. 项目概述与RWW技术背景在嵌入式系统开发尤其是基于i.MX RT这类高性能微控制器的项目中我们常常面临一个经典的矛盾固件代码需要从外部Flash中高速、稳定地执行同时应用又需要频繁地向同一片Flash写入或更新数据比如记录日志、保存配置参数或进行OTA升级。传统Flash的操作特性决定了在执行写或擦除命令时整个存储阵列会进入“忙”状态此时任何读取请求包括CPU取指都会被阻塞。对于i.MX RT这类没有内部Flash、极度依赖外部Quad SPI Flash运行代码的芯片来说这意味着在写入数据的几百微秒甚至几十毫秒内整个系统会“卡住”中断无法响应实时性荡然无存。这就是“边读边写”Read-While-Write RWW技术要解决的核心痛点。RWW并非一个遥不可及的概念它直接关系到你的设备能否在后台安静地保存数据的同时前台还能流畅地处理用户交互或实时控制任务。想象一个智能家居网关它需要一边响应手机APP的即时控制指令从Flash取指执行一边将传感器数据打包写入Flash存储。没有RWW写入数据的那一瞬间控制指令就可能丢失或延迟用户体验大打折扣。因此理解并实现RWW是从“功能实现”迈向“产品化可靠设计”的关键一步。实现RWW硬件是基础。其核心思路可以归结为两条主流路径一是使用“天生丽质”的专用RWW Flash芯片这类芯片内部通过多存储区Multi-Bank的物理隔离设计允许一个Bank执行写操作时另一个Bank照常提供读取服务二是采用“人多力量大”的策略利用i.MX RT的FlexSPI接口支持多设备连接的特性通过连接两颗独立的通用Flash芯片一颗专用于运行代码另一颗专用于数据存储从物理上规避了访问冲突。本文将聚焦于i.MX RT平台为你深入拆解这两种方案的硬件设计要点、软件驱动实现以及在实际工程中必须绕开的那些“坑”。2. RWW实现方案深度对比与选型考量在动手画原理图或写代码之前选择一个适合你项目的RWW实现方案至关重要。这不仅仅是一个技术选择题更是一个涉及成本、复杂度、供应链和长期维护的综合考量。2.1 方案一使用专用RWW Flash芯片如MX25UW12845G这种方案可以理解为“一步到位”的解决方案。芯片制造商如旺宏Macronix、华邦Winbond等提供了原生支持RWW功能的Flash产品。以文档中提到的MX25UW12845G为例它是一款128Mb的八线OctalSPI Flash其内部被划分为多个独立的存储区Bank。例如可以将Bank 0固定用于存放固件代码而Bank 1到Bank 3则用于数据存储。当CPU从Bank 0读取指令时即使主控正在向Bank 1写入数据两个操作也可以并行不悖因为它们在物理上是不同的存储阵列。优势分析设计简洁硬件连接与单颗通用八线Flash完全相同无需增加额外的Flash芯片和片选线路节省PCB面积和布线复杂度。软件透明性高在理想情况下你几乎可以像操作普通Flash一样操作它RWW功能由芯片内部硬件保证驱动程序无需处理复杂的总线仲裁和冲突避免逻辑只需在访问不同Bank时遵循芯片数据手册的特定命令序列即可。性能可预期由于是芯片原生支持读写并行操作的时序和稳定性由芯片规格保证性能表现更确定。劣势与挑战成本较高专用RWW Flash通常比同容量的通用Flash价格更高对于成本敏感型项目可能形成压力。型号选择有限并非所有Flash型号都支持RWW这可能会限制你在容量、电压、封装等方面的选择余地带来供应链风险。驱动需适配虽然相对简单但仍需在底层驱动中实现对多Bank架构和RWW命令集的支持不能直接套用最通用的QSPI驱动。2.2 方案二使用双通用Flash芯片模拟实现这是文档中重点介绍的也是更具普适性和灵活性的方案。其核心思想是利用i.MX RT的FlexSPI控制器支持多个片选Chip Select的特性连接两颗物理上独立的Flash芯片。一颗作为主FlashFlash A1存放并运行固件代码另一颗作为从FlashFlash A2或Flash B1专门用于数据存储。当需要向数据Flash写入时操作仅针对该芯片而代码Flash始终保持可读状态从而模拟出RWW的效果。优势分析成本优势明显你可以自由选择两颗最便宜、最易采购的通用Quad SPI Flash总成本可能低于一颗专用RWW Flash。灵活性极强两颗Flash的型号、容量甚至可以不同。例如代码Flash选用高速型号数据Flash选用大容量型号。硬件设计上也有端口A双设备或端口A/B各一设备两种连接方式适应不同的板级布局。技术通用该方案不依赖于特定Flash型号你积累的通用QSPI驱动和调试经验可以完全复用降低了学习成本和维护难度。劣势与挑战硬件设计复杂需要多占用一个Flash芯片的PCB空间和布线特别是当使用同一FlexSPI端口如Port A连接两颗Flash时数据线D0-D3是共享的必须仔细处理片选CS信号和可能的写保护WP、保持HOLD信号避免冲突。软件复杂度增加这是本方案的核心挑战。由于共享同一个FlexSPI接口当控制器向数据Flash发送写命令时必须通过总线仲裁机制暂时挂起对代码Flash的读取访问即AHB命令。这需要精心设计驱动确保在极短的关键时序窗口内如发送命令和数据的几微秒内关闭中断防止任何意外的Flash访问导致硬件错误。驱动开发中需要深入理解FlexSPI的LUT查找表、IP命令、AHB命令以及总线仲裁机制。选型决策建议对于大多数中小型项目尤其是对成本敏感、且Flash读写操作不是极端频繁的应用双Flash方案往往是更务实的选择。它利用了i.MX RT的硬件灵活性用一定的软件复杂性换来了更低的BOM成本和供应链弹性。而对于那些对实时性要求极为严苛、系统复杂度已经很高、希望最大限度简化软件负担的高端应用专用RWW Flash方案则提供了更优雅的解决方案。在决策时务必结合项目的量产规模、硬件成本预算、软件团队的技术储备以及产品的长期演进路线进行综合判断。3. 双Flash方案硬件设计与引脚配置详解选定双Flash方案后硬件设计是第一个关键步骤。i.MX RT的FlexSPI接口非常灵活支持多种连接拓扑这里我们详细解析两种最常用的连接方式及其设计要点。3.1 连接拓扑同一端口 vs. 不同端口方式一两颗Flash连接至同一FlexSPI端口如Port A这是最节省引脚资源的连接方式。如图2所示两颗Quad SPI Flash共享同一组数据线FLEXSPI_A_DATA0-3、时钟线FLEXSPI_A_SCLK和部分控制线。它们通过不同的片选信号Chip Select进行区分。通常Flash A1使用默认的片选CS0对应芯片引脚PCS0Flash A2使用片选CS1对应芯片引脚PCS1。优点引脚占用少布线相对简单两颗Flash可以紧挨着放置。缺点两颗Flash必须使用完全相同的时序参数如时钟频率、采样模式等因为FlexSPI端口A的时序配置寄存器如DLLACR是端口全局的。如果两颗Flash型号或批次不同导致最佳时序有差异系统可能只能在保守的较低的频率下稳定运行。方式二两颗Flash分别连接至FlexSPI的不同端口如Port A和Port B如图1中连接Flash A1和Flash B1的方案。这种方式下两颗Flash拥有各自独立的数据线、时钟线和片选线。优点最大的灵活性。端口A和端口B有各自独立的时序配置寄存器DLLACR和DLLBCR可以为两颗型号、速度各异的Flash分别配置最优化的时序充分发挥各自性能。例如代码Flash可采用更高频率的Octal模式数据Flash使用标准的Quad模式。缺点占用芯片引脚资源最多PCB布线复杂度增加特别是高速信号线增多对布局布线要求更高。设计建议在板级空间和引脚资源允许的情况下优先考虑使用不同端口的连接方式。这为未来升级Flash型号、优化性能提供了更大的余地。如果必须使用同一端口务必在物料选型时选择两颗时序特性高度一致的Flash芯片并在量产前进行充分的交叉测试。3.2 关键引脚配置与启动设备指定无论采用哪种连接方式都必须明确指定哪一颗Flash作为启动设备。i.MX RT芯片上电后Boot ROM会读取特定的 GPIO通常是某个拨码开关或eFuse设置来确定从哪个外部存储器启动。对于FlexSPI启动ROM默认会去访问Port A CS0所连接的设备。因此启动FlashFlash A1必须连接到FlexSPI的Port A并使用片选信号CS0。其对应的数据线、时钟线必须连接到芯片指定的启动引脚上这些引脚通常是固定的不能随意映射。例如在RT1060上FLEXSPI_A_DATA0-3、SCLK、CS0分别对应固定的引脚如GPIO_SD_B1_xx系列。设计时必须查阅芯片数据手册的“Chapter 10: External Memory Interfaces”和参考板的原理图确保这些引脚连接正确无误。数据FlashFlash A2或Flash B1其片选引脚可以灵活配置。如果使用Port A CS1需要注意其默认映射的引脚如RT1060上是GPIO_SD_B0_00。如果你希望将这个片选信号映射到其他GPIO上以方便布线是完全可以的但这需要在软件初始化阶段在Boot ROM配置完FlexSPI基础功能后立即重新配置相应的IOMUX引脚复用和GPIO设置。图3的引脚分配图展示了这种灵活性。硬件设计检查清单[ ] 确认启动FlashFlash A1的CS、CLK、DATA线连接到了芯片指定的启动引脚。[ ] 为两颗Flash的电源VCC、地GND添加足够的去耦电容通常每个电源引脚一个100nF MLCC靠近芯片放置。[ ] 如果Flash支持将/WP写保护和/HOLD保持引脚通过电阻上拉到VCC避免意外进入保护状态。[ ] 检查FlexSPI时钟线SCLK的走线确保其长度匹配并远离噪声源必要时进行阻抗控制。[ ] 对于使用同一端口的两颗Flash确认它们共用数据线时的走线拓扑合理避免信号反射。4. 双Flash方案软件驱动实现与核心代码剖析硬件准备就绪后软件驱动是实现RWW功能的大脑。整个过程可以分为初始化配置、LUT查找表定制、以及关键的读写操作与总线仲裁处理三大块。4.1 初始化流程与双设备配置i.MX RT的启动过程由Boot ROM主导。ROM会从启动Flash的固定位置读取一个叫做“FlexSPI配置块”的数据结构并根据其中的参数来初始化FlexSPI控制器。要让ROM识别并初始化第二颗Flash我们必须修改这个配置块。关键步骤修改FlexSPI配置块在SDK中这个配置块通常定义在一个独立的文件里如evkmimxrt1060_flexspi_nor_config.c。你需要找到描述Flash属性的结构体例如qspiflash_config。在其中添加或修改关于第二颗Flash大小的字段。正如文档代码片段所示.sflashA2Size 8u * 1024u * 1024u, // 定义连接到Port A CS1的Flash大小为8MB这个信息会告诉ROM除了默认的启动设备A1外总线上还有另一个设备A2存在ROM会在初始化时一并配置好它。重映射片选引脚可选如果你没有使用默认的PCS1引脚如GPIO_SD_B0_00就需要在用户代码的早期在访问A2之前重新配置引脚功能。首先禁用默认引脚的上拉/下拉等功能然后将其配置为通用的GPIO输入模式避免冲突最后将你选用的新引脚配置为FlexSPI的PCS1功能。// 示例禁用默认PCS1引脚 IOMUXC_SetPinMux(IOMUXC_GPIO_SD_B0_00_GPIO3_IO15, 0U); IOMUXC_SetPinConfig(IOMUXC_GPIO_SD_B0_00_GPIO3_IO15, 0x10B0u); // 配置新的引脚为FLEXSPI_A_PCS1 IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B0_11_FLEXSPI_A_PCS1, 0U); IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B0_11_FLEXSPI_A_PCS1, 0x10F9u);软件复位与LUT更新Boot ROM配置的LUT可能只包含了最基本的读取指令。为了实现擦除、编程等操作我们需要更新LUT。这是一个极其关键的步骤操作不当会导致系统崩溃。文档流程图图4明确指出在更新LUT之前必须通过设置FLEXSPI_MCR0[SWRESET]位对FlexSPI控制器进行软件复位。更重要的是执行LUT更新操作的这段代码本身绝对不能位于正在被更新的Flash通常是代码Flash中。因为更新LUT本身也是一条IP命令执行时会暂停AHB访问。如果更新代码在Flash里执行到一半被暂停就会导致取指失败引发硬故障HardFault。解决方案将更新LUT的函数以及后续所有直接操作FlexSPI寄存器的底层驱动函数链接到内部SRAM如DTCM或OCRAM中执行。在链接器脚本.ld文件中为这些函数指定一个位于RAM的段section并在函数声明时使用__attribute__((section(.ram_code)))GCC或__ramfuncIAR关键字。4.2 LUT定制与总线仲裁机制解析LUT是FlexSPI控制器的“指令集”它将一个操作如读、写、擦除分解为一系列具体的序列发送命令码、发送地址、读数据、写数据等。每个序列由1到8个指令组成每个指令定义了在FlexSPI总线上执行的具体动作。实现RWW的关键在于理解IP命令与AHB命令的仲裁AHB命令这是CPU通过总线如取指、数据加载发起的对Flash的访问是“被动”和“实时”的。IP命令这是我们通过写FlexSPI的IPCRx、IPCRx等寄存器主动发起的命令如擦除0x20、页编程0x02等是“主动”的。当FlexSPI控制器正在执行一个IP命令比如向数据Flash发送页编程命令时如果有新的AHB命令比如CPU要从代码Flash取指到达总线仲裁器会暂停这个AHB命令直到当前的IP命令序列执行完毕FlexSPI接口回到空闲状态然后再继续执行被暂停的AHB命令。这个“暂停-继续”的过程对CPU来说是透明的只要IP命令执行时间足够短就不会影响系统实时性。软件设计要点封装原子操作将发送IP命令如擦除、编程的代码封装成函数并在函数开始处关闭全局中断在函数结束前恢复中断。如图5流程图所示。这是因为在发送命令和数据的极短时间内文档实测约6us我们必须防止任何中断处理程序在此期间访问Flash从而触发未被处理的AHB访问冲突。使用DMA传输数据对于页编程操作需要传输大量数据如256字节。如果使用CPU循环写数据到FlexSPI的TFDR寄存器期间CPU被占用且任何中断都可能引发问题。更优的方案是使用DMA直接存储器访问来搬运数据。配置DMA通道将内存中的数据缓冲区自动搬运到FlexSPI的发送FIFO。这样CPU在启动DMA后就可以去做其他事情虽然中断仍被关闭但可以快速退出临界区数据传输由DMA硬件完成效率更高也更安全。精确控制关键时序窗口通过测量如图6所示可以用一个GPIO引脚拉高/拉低来标记时间窗口我们确认了从发出编程命令到数据传输完成的总时间。这个时间窗口就是必须关闭中断的“临界区”长度。优化这个时间如提高时钟频率、优化LUT序列能直接减少系统中断延迟。4.3 缓存与预取机制的协同与测试i.MX RT的缓存ICACHE/DCACHE和Flash预取缓冲区能极大提升代码执行效率但它们会“掩盖”一些问题。在RWW场景下如果代码正在从缓存或预取缓冲区读取那么即使底层Flash访问被IP命令短暂阻塞CPU也可能感知不到因为数据已经提前取好了。这带来了一个隐患你的RWW驱动在开启缓存时测试一切正常但一旦禁用缓存比如在低功耗模式下或某些极端调试场景系统就可能因为真实的Flash访问冲突而崩溃。因此完备的RWW测试必须包括禁用缓存的场景。在SDK示例代码中通常会包含类似以下的测试代码// 禁用缓存和预取 SCB_DisableICache(); SCB_DisableDCache(); FLEXSPI_EnablePrefetch(...); // 禁用预取 // 在此状态下执行密集的代码运行如循环执行某段代码和并发的数据Flash写入操作 // 观察系统是否稳定是否产生硬故障只有在这种“最坏情况”下测试通过才能证明你的RWW实现是真正健壮的。5. 专用RWW Flash方案软件适配要点如果你选择了像MX25UW12845G这样的专用RWW Flash软件层面的工作会相对简化但仍有几个关键点需要注意。识别与初始化首先你的驱动需要能够识别这款芯片。通常通过读取Flash的JEDEC ID来实现。确认ID后需要发送特定的命令序列来启用其RWW或称为Multi-Bank功能。具体命令请查阅该Flash的数据手册通常会有一个“Bank Register”或“Configuration Register”需要配置。Bank地址规划你需要明确规划哪个Bank存放代码哪个Bank存放数据。例如将0x0000_0000到0x00FF_FFFF的地址空间Bank 0映射为代码区将0x0100_0000开始的地址空间Bank 1映射为数据区。这个映射关系需要在链接器脚本中体现确保编译器将代码段和数据段分配到正确的物理地址。跨Bank操作当需要擦写数据Bank时驱动代码应位于代码Bank中。操作流程与普通Flash擦写类似但目标地址指向数据Bank。由于是原生RWW支持在向数据Bank发送页编程命令时CPU可以持续从代码Bank取指理论上不需要关闭全局中断。但出于绝对谨慎的考虑在发送命令序列的极短时间内微秒级关闭中断仍然是一个好习惯可以避免任何潜在的时序风险。FlexSPI配置MX25UW12845G是八线OctalFlash需要将FlexSPI配置为工作在“组合模式”Combined Mode即同时使用Port A和Port B的数据线来达到8位数据宽度。这需要在FlexSPI配置块中设置相应的标志位如flexspi_config.flashType kFlexSpiDeviceType_Octal并正确连接所有8根数据线。6. 实战调试技巧与常见问题排查理论最终要服务于实践。在调试RWW功能时你可能会遇到以下典型问题这里提供排查思路。问题一系统在更新LUT或执行Flash写操作时发生硬故障HardFault。排查这是最经典的问题。99%的原因是你的驱动函数特别是LUT更新函数、Flash擦写函数没有被正确链接到RAM中执行。检查你的链接器脚本确认为这些函数分配的段如.ram_code的加载地址Load Address和运行地址VMA都指向了SRAM区域。在map文件中查看这些函数的实际链接地址确认它们不在Flash地址范围内。技巧可以在函数入口处打印一个全局变量的地址或者直接通过调试器查看该函数的反汇编代码所在的地址快速判断其是否在RAM中。问题二开启RWW后系统运行不稳定偶尔死机或数据错误。排查1时序问题。检查两颗Flash双Flash方案的时序配置是否与其数据手册要求匹配。特别是建立时间tDS、保持时间tDH和输出延迟tOD。对于同一端口连接两颗Flash的情况时序配置必须满足两颗芯片中要求更严格的那个。使用示波器测量SCLK、CS和数据线的波形确保信号质量良好无过冲、振铃或时序违例。排查2中断临界区过长。用GPIO引脚和逻辑分析仪测量从关闭中断到重新打开中断的实际时间。确保这个时间远小于你系统中最紧急的中断响应时间要求。优化方法包括使用DMA传输数据优化LUT序列减少不必要的等待周期dummy cycles如果Flash支持使用更快的四线或八线编程模式。排查3缓存一致性问题。如果你在数据Flash上使用了内存映射XIP方式来读取数据并且CPU缓存是开启的那么在数据Flash被写入新内容后对应地址的缓存内容是旧的。后续CPU读取时可能直接命中缓存得到错误数据。需要在每次成功写入数据Flash后对相应的缓存行执行无效化Invalidate操作。SCB_InvalidateDCache_by_Addr()函数可以完成这个任务。问题三双Flash方案中只能正确访问启动Flash无法访问第二颗Flash。排查1片选信号。首先用示波器确认在对第二颗Flash进行操作时其片选信号CS是否被正确拉低。如果没有检查引脚配置是否正确是否被其他功能复用。排查2配置块。确认在FlexSPI配置块中正确设置了第二颗Flash的容量sflashA2Size。这个值必须与实际Flash容量一致否则FlexSPI控制器无法正确计算地址范围。排查3地址映射。理解FlexSPI的地址映射规则。对于Port A CS0的设备通常映射到0x6000_0000开始。对于Port A CS1的设备其地址是紧接着CS0设备容量之后的。例如CS0设备容量为16MB那么CS1设备的起始地址就是0x6000_0000 16MB 0x6100_0000。访问第二颗Flash时必须使用正确的映射地址。问题四使用专用RWW Flash时写入数据Bank期间从代码Bank取指偶尔出错。排查虽然芯片支持RWW但某些操作可能仍有限制。仔细阅读数据手册中关于“同时操作”Concurrent Operations的章节。有些Flash在数据Bank执行“页编程”时允许代码Bank读取但在执行“扇区擦除”这种更耗时的操作时可能会短暂禁止所有读取。确保你的驱动遵循了这些限制或者在执行此类操作时短暂地将关键中断服务程序复制到RAM中运行。调试工具箱建议逻辑分析仪必备工具。连接FlexSPI的SCLK、CS、数据线可以清晰地看到命令序列、地址和数据传输过程是分析通信协议和时序问题的利器。调试器JTAG/SWD用于单步调试RAM中的驱动函数查看和修改寄存器分析HardFault原因。GPIO调试法在代码关键位置如进入/退出临界区、开始/结束DMA传输控制GPIO引脚电平用示波器测量时间是量化性能瓶颈和验证逻辑流程的最直接方法。实现RWW功能尤其是双Flash方案确实需要开发者对硬件接口和底层驱动有更深的理解。但一旦攻克它将为你的i.MX RT应用带来质的提升使其能够胜任更复杂、实时性要求更高的任务。从成本可控的双Flash方案入手逐步吃透FlexSPI的仲裁机制和LUT编程是掌握这项技术的一条有效路径。在实际项目中建议先基于官方SDK中的相关示例通常位于SDK_xxx\boards\xxx\driver_examples\flexspi\nor_rww进行修改和测试它能提供一个坚实可靠的起点帮你避开许多初级的陷阱。