资讯动态

STM32H7 QSPI驱动W25Q128实战:从配置到性能优化

发布时间:2026/9/27 20:39:42 来源:尧图企业网站定制
1. 为什么W25Q128在STM32H7上值得认真对待W25Q128这颗Flash芯片搞嵌入式的基本都见过。128Mbit容量、SPI接口、便宜、好买、资料全很多人拿它存个字库、放个图片、记点日志就完事了。但如果你手里正好有一块STM32H7的板子还只用普通SPI模式去读写它那说实话有点浪费。STM32H7系列带QSPI控制器配合W25Q128的Quad模式理论带宽能拉到普通SPI的四倍左右。这个提升不是纸面数字在实际项目里比如你要做GUI刷图、音频采样缓存、或者跑个轻量文件系统差距非常明显。我最早接触这个组合是在一个工业HMI项目上屏幕刷新时要从Flash读大量图片数据普通SPI模式下刷一屏有明显撕裂感换成QSPI之后流畅度直接上了一个台阶。后来陆续在几个数据采集项目里也用了这套方案踩了不少坑也总结了一些经验。这篇文章就把W25Q128在STM32H7上跑QSPI的完整思路、关键配置、实操步骤和避坑经验都梳理一遍。适合谁看如果你已经会用STM32的普通SPI读写W25Q128想进一步榨干硬件性能那这篇正好。如果你刚接触QSPI也没关系我会从最基本的原理讲起把每个关键寄存器配置的逻辑都说清楚。前提是你对STM32的HAL库或者寄存器操作有基本概念C语言要能看懂。注意本文所有内容基于公开的芯片数据手册和常见工程实践具体参数以你手头芯片的最新手册为准。2. QSPI和普通SPI到底差在哪2.1 从四根线说起普通SPI大家都很熟四根线SCK、MOSI、MISO、CS。数据一位一位地传全双工。W25Q128支持标准SPI模式最高时钟频率大概在80MHz到104MHz之间具体看型号后缀和电压。算一下80MHz下理论带宽是10MB/s实际因为命令开销、等待周期能跑到5MB/s就算不错了。QSPI的核心变化在于数据线。它把MOSI拆成IO0到IO3四根双向数据线SCK和CS保留。在Quad模式下每个时钟周期可以传4位数据而不是1位。同样是80MHz时钟理论带宽直接变成40MB/s。当然实际也达不到理论值但跑到20MB/s以上是很正常的。这里有个概念要区分清楚QSPI是STM32H7外设的名字Quad SPI是W25Q128的工作模式。STM32H7的QSPI控制器支持单线、双线、四线模式也支持内存映射模式。W25Q128这边你需要发特定命令让它进入Quad模式之后才能用四根线传数据。2.2 STM32H7的QSPI外设有什么特别的STM32H7的QSPI控制器和F4、F7上的有点不一样。它支持双闪存模式可以同时接两颗Flash支持内存映射模式映射后可以直接用指针读Flash数据像读内部Flash一样方便还有独立的FIFO和DMA请求线。最关键的是内存映射模式。配置好之后W25Q128的地址空间会映射到STM32H7的0x90000000起始的区域。你定义一个指针指向这个地址直接解引用就能读数据CPU不需要干预传输过程。这对执行代码或者快速读大量数据来说太方便了。但内存映射模式只支持读写操作还是得走间接模式。而且进入内存映射模式之前必须先配置好Flash的Quad使能位否则映射过去读出来的数据全是错的。2.3 四倍速是怎么算出来的很多人说四倍速其实是个粗略说法。准确讲在相同SCK频率下Quad模式每个周期传4位标准SPI每个周期传1位理论上是4倍。但实际有效带宽还受几个因素影响命令阶段的开销每次传输都要发命令、地址、模式位这些在Quad模式下也可以优化等待周期Flash内部有读取延迟需要配置Dummy CyclesDMA效率如果不用DMACPU搬运数据也会成为瓶颈总线仲裁STM32H7的总线矩阵很复杂QSPI的优先级需要合理配置所以实际测下来读性能提升通常在3到3.5倍之间写性能提升更少一些因为写操作本身有页编程时间限制。但即便如此这个提升也足够让很多应用场景发生质变。3. 硬件连接与引脚配置要点3.1 引脚映射不是随便选的STM32H7的QSPI引脚是复用在特定GPIO上的不是任意引脚都能用。以常见的STM32H743为例QSPI的引脚分布在PORTB和PORTF上信号引脚复用功能QSPI_CLKPB2AF9QSPI_CSPB6AF10QSPI_IO0PF8AF10QSPI_IO1PF9AF10QSPI_IO2PF7AF9QSPI_IO3PF6AF9注意IO2和IO3的复用功能编号和IO0、IO1不一样这个在配置GPIO的时候很容易搞错。我见过有人把IO2配成AF10结果Quad模式下读数据高四位全是0查了半天才发现是复用功能选错了。另外PB2作为QSPI_CLK它的复用功能是AF9不是AF10。这些细节在STM32H7的参考手册GPIO复用表里都能查到但表格很大容易看串行。建议配置的时候直接对着CubeMX生成的代码检查一遍。3.2 上拉电阻和走线W25Q128在Quad模式下IO0到IO3都是双向的。当Flash没有驱动这些线的时候它们处于高阻态。如果这时候STM32H7的对应引脚也处于输入模式且没有上拉线路上就会有浮空风险可能导致误触发或者功耗增加。常见做法是在IO0到IO3上各加一个10K到47K的上拉电阻到VCC。CS线也建议加上拉保证在空闲时Flash不被选中。SCK线一般不需要上拉但如果你走线比较长可以考虑加一个串联电阻来抑制振铃。走线方面QSPI的时钟频率可以跑到100MHz以上这时候PCB走线就不能太随意了。几条数据线尽量等长误差控制在几个毫米以内。SCK线要远离其他高速信号避免串扰。如果板子空间允许QSPI的走线最好走在同一层参考平面完整。实操心得我在一个项目里因为QSPI走线跨了分割地平面导致高速读取时偶尔出现位错误。后来在Flash电源引脚旁边加了0.1uF和1uF的退耦电容问题才解决。退耦电容一定要靠近Flash的VCC引脚越近越好。3.3 电源和电压匹配W25Q128的供电范围是2.7V到3.6VSTM32H7的IO电压一般是3.3V两者可以直接对接。但要注意如果你用的是W25Q128JV系列它的IO电压和核心电压是分开的VCC和VIO都要接。有些封装把这两个引脚合并了有些没有看具体型号。STM32H7的QSPI引脚输出能力有限如果走线很长或者挂了多个从设备可能需要加缓冲器。但一般单颗Flash、走线在10厘米以内直接连就行。4. CubeMX配置与底层驱动搭建4.1 时钟树配置QSPI的时钟来源是PLL1Q或者PLL2R具体取决于你的时钟树设计。以STM32H743为例假设系统时钟跑到400MHzPLL1Q可以配置到200MHz然后QSPI控制器内部再分频。W25Q128在Quad模式下的最高时钟频率普通型号是104MHz但那是理想条件下的。实际跑的时候受PCB走线、电源质量、温度影响能稳定跑到80MHz到100MHz就不错了。我一般先配置到80MHz跑通了再往上试。CubeMX里QSPI的时钟分频系数是1到256实际时钟等于输入时钟除以分频系数。比如输入200MHz分频系数设为2QSPI时钟就是100MHz。但注意这个100MHz是SCK的输出频率在Quad模式下每个周期传4位所以数据速率是400Mbit/s也就是50MB/s。当然实际达不到因为有命令开销和等待周期。4.2 QSPI参数配置CubeMX里QSPI的配置项比较多我挑几个关键的讲Clock Prescaler分频系数决定了SCK频率。先设大一点跑通再减小。FIFO ThresholdFIFO阈值影响中断和DMA请求的触发时机。一般设为4或者8看你的传输粒度。Sample Shifting采样偏移有None和Half Cycle两个选项。如果高速下读数据不稳定可以试试Half Cycle让采样点往后移半个周期。Flash SizeFlash容量W25Q128是128Mbit也就是16MB。这个值影响内存映射模式的地址范围。CS High Time片选高电平保持时间单位是时钟周期。W25Q128要求CS拉高后至少保持50ns按100MHz算就是5个周期。设成8比较保险。Flash ID这个不是必填的但填上之后CubeMX会自动帮你计算一些参数。4.3 命令配置表QSPI控制器需要你告诉它读、写、擦除分别用什么命令地址是几位数据是几位Dummy Cycles要几个。W25Q128的Quad读命令是0xEBQuad页编程是0x32扇区擦除是0x20。以Quad读为例配置如下参数值说明Instruction0xEBQuad I/O Fast ReadAddress24位3字节地址Alternate Bytes0不用Dummy Cycles680MHz下需要6个Data4线Quad模式Instruction Mode1线命令阶段还是单线Dummy Cycles的数量和SCK频率有关。W25Q128手册里有个表格80MHz下Quad读需要6个Dummy Cycles100MHz下需要8个。这个值设少了会读到错误数据设多了会浪费带宽。我一般按手册推荐值加1来设留点余量。4.4 GPIO初始化代码CubeMX生成的GPIO初始化代码大概长这样GPIO_InitStruct.Pin GPIO_PIN_2|GPIO_PIN_6; GPIO_InitStruct.Mode GPIO_MODE_AF_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate GPIO_AF9_QSPI; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); GPIO_InitStruct.Pin GPIO_PIN_6|GPIO_PIN_7; GPIO_InitStruct.Alternate GPIO_AF10_QSPI; HAL_GPIO_Init(GPIOF, GPIO_InitStruct); GPIO_InitStruct.Pin GPIO_PIN_8|GPIO_PIN_9; GPIO_InitStruct.Alternate GPIO_AF10_QSPI; HAL_GPIO_Init(GPIOF, GPIO_InitStruct);注意PB6同时出现在两个配置里因为CS是PB6但它的复用功能是AF10而PB2是AF9。这段代码里我把PB6和PB2分开配置了实际写的时候要仔细核对。常见坑CubeMX有时候会把QSPI的GPIO配置生成得比较乱特别是当你同时用了其他复用功能的时候。建议生成代码后手动检查一遍每个引脚的AF编号别嫌麻烦。5. W25Q128的Quad模式使能流程5.1 上电后的默认状态W25Q128上电后默认是标准SPI模式IO0到IO3中只有IO0和IO1参与数据传输IO2和IO3是高阻态。要进入Quad模式需要先发命令设置状态寄存器2的QE位。这个QE位是Non-Volatile的也就是说设置一次之后下次上电还是Quad模式。但如果你换了一颗新的Flash或者Flash被擦除过QE位可能是0。所以稳妥的做法是每次初始化的时候都检查并设置一次。5.2 写使能加写状态寄存器设置QE位的流程是发Write Enable命令0x06发Write Status Register 2命令0x31发一个字节bit1设为1等待写完成用HAL库的代码大概是这样QSPI_CommandTypeDef cmd; // 写使能 cmd.Instruction 0x06; cmd.InstructionMode QSPI_INSTRUCTION_1_LINE; cmd.AddressMode QSPI_ADDRESS_NONE; cmd.DataMode QSPI_DATA_NONE; cmd.DummyCycles 0; HAL_QSPI_Command(hqspi, cmd, HAL_QPSI_TIMEOUT_DEFAULT_VALUE); // 写状态寄存器2 cmd.Instruction 0x31; cmd.InstructionMode QSPI_INSTRUCTION_1_LINE; cmd.AddressMode QSPI_ADDRESS_NONE; cmd.DataMode QSPI_DATA_1_LINE; cmd.NbData 1; uint8_t reg2 0x02; // QE位置1 HAL_QSPI_Command(hqspi, cmd, HAL_QPSI_TIMEOUT_DEFAULT_VALUE); HAL_QSPI_Transmit(hqspi, reg2, HAL_QPSI_TIMEOUT_DEFAULT_VALUE);写完之后要等一会儿让Flash内部完成写入。可以读状态寄存器1的bit0BUSY位来判断是否完成。5.3 验证Quad模式是否生效设置完QE位之后可以发一个Quad读命令试试。如果读出来的数据正确说明Quad模式已经生效。如果读出来全是0xFF或者0x00那可能是QE位没设上或者Dummy Cycles不对。我一般会先读Flash的JEDEC ID命令0x9F来确认通信正常然后再试Quad读。JEDEC ID是固定的W25Q128应该是0xEF4018。如果这个都读不对那说明基础SPI通信就有问题得先查硬件。实操心得有些批次的W25Q128在出厂时QE位就是1有些是0。不要假设每次都检查。另外如果你在代码里频繁切换单线和四线模式记得每次切换后都要重新配置QSPI控制器因为STM32H7的QSPI外设不会自动记住这些状态。6. 内存映射模式配置与使用6.1 进入内存映射模式内存映射模式是STM32H7 QSPI最实用的功能之一。配置好之后W25Q128的整个16MB空间会映射到0x90000000开始的地址。你可以直接定义指针来读数据#define QSPI_BASE_ADDR 0x90000000 uint8_t *flash_ptr (uint8_t *)QSPI_BASE_ADDR; uint8_t data flash_ptr[0x1000]; // 读偏移0x1000处的数据进入内存映射模式的代码QSPI_CommandTypeDef cmd; QSPI_MemoryMappedTypeDef mem_mapped; cmd.Instruction 0xEB; // Quad读 cmd.InstructionMode QSPI_INSTRUCTION_1_LINE; cmd.AddressMode QSPI_ADDRESS_4_LINES; cmd.AddressSize QSPI_ADDRESS_24_BITS; cmd.AlternateByteMode QSPI_ALTERNATE_BYTES_NONE; cmd.DummyCycles 6; cmd.DataMode QSPI_DATA_4_LINES; cmd.NbData 0; cmd.DdrMode QSPI_DDR_MODE_DISABLE; cmd.SIOOMode QSPI_SIOO_INST_EVERY_CMD; mem_mapped.TimeOutActivation QSPI_TIMEOUT_COUNTER_DISABLE; mem_mapped.TimeOutPeriod 0; HAL_QSPI_MemoryMapped(hqspi, cmd, mem_mapped);配置好之后CPU访问0x90000000区域时QSPI控制器会自动发起读操作不需要软件干预。这对执行代码或者快速读大量数据来说非常方便。6.2 内存映射模式下的性能优化内存映射模式虽然方便但默认配置下性能不一定最优。有几个地方可以调Cache配置STM32H7有L1 Cache如果QSPI映射区域配置成Cacheable重复读同一块数据时可以直接从Cache取速度极快。但要注意Cache一致性问题如果Flash内容被外部修改了Cache里的数据可能不是最新的。对于只读的Flash来说这个问题不大。预取使能QSPI控制器支持预取可以在CPU还没请求下一块数据的时候提前读。这个功能在顺序读场景下很有用能显著提升有效带宽。DMA配合虽然内存映射模式下CPU可以直接读但如果要搬运大量数据到其他内存区域用DMA更高效。STM32H7的MDMA可以配置成从QSPI映射区域搬到SRAM完全不占CPU。我实测过在400MHz的STM32H743上从QSPI映射区域用DMA搬16KB数据到SRAM耗时大概在30微秒左右算下来有效带宽超过500MB/s。当然这是有Cache命中的情况如果Cache没命中带宽会降到50MB/s左右。6.3 退出内存映射模式内存映射模式不是永久的你可以随时退出回到间接模式。退出之后0x90000000区域就不再映射到Flash了访问会出错。退出内存映射模式的方法是调用HAL_QSPI_Abort或者重新配置QSPI控制器。我一般会在需要写Flash之前退出映射模式写完再重新进入。注意内存映射模式下不能直接写Flash。W25Q128的写操作需要发命令、发地址、发数据这些在映射模式下是做不到的。所以写操作必须走间接模式。7. 读写性能实测与对比7.1 测试方法为了量化QSPI带来的提升我设计了一个简单的测试从Flash的0地址开始连续读1MB数据到SRAM分别用标准SPI模式和Quad模式跑记录耗时。测试平台是STM32H743IIT6系统时钟400MHzQSPI时钟80MHz。标准SPI模式下SCK也是80MHz。测试代码用DWT计数器计时精度到CPU周期。7.2 实测数据模式数据量耗时有效带宽标准SPI1MB210ms4.76MB/sQuad SPI1MB62ms16.1MB/sQuad SPI DMA1MB58ms17.2MB/sQuad SPI 内存映射1MB55ms18.2MB/s从数据看Quad模式比标准SPI快了大约3.4倍。加上DMA和内存映射之后提升到3.8倍左右。虽然没有到理论上的4倍但考虑到命令开销和等待周期这个结果已经很不错了。写性能方面因为W25Q128的页编程时间固定典型值0.7ms最大3ms所以Quad模式对写性能的提升主要体现在数据传输阶段。对于大量小数据写入提升不明显对于连续大块写入提升大概在2倍左右。7.3 影响性能的关键因素Dummy Cycles这个参数对读性能影响很大。设多了浪费时钟周期设少了读数据出错。80MHz下6个Dummy Cycles是手册推荐值我实测下来6个和7个都能稳定工作但6个的带宽高一点。FIFO阈值FIFO阈值设得太小会导致频繁中断设得太大又可能溢出。我一般设成8配合DMA使用。总线优先级STM32H7的总线矩阵里QSPI的优先级可以配置。如果QSPI和其他高带宽外设比如LTDC、DMA2D同时工作需要合理分配优先级避免QSPI被饿死。Cache策略内存映射模式下Cache命中率直接影响读性能。对于顺序读预取能显著提高命中率对于随机读Cache效果有限。实操心得我在一个GUI项目里把图片数据放在QSPI Flash里用内存映射模式直接读。刚开始没配Cache刷图很慢。后来把QSPI映射区域配成Write-Through模式刷图速度直接翻倍。但要注意Write-Through模式下写操作会直接写穿到Flash而Flash写很慢所以只适合只读区域。8. 常见问题与排查技巧8.1 读出来全是0xFF这是最常见的问题。可能的原因有QE位没设置成功Flash还在单线模式Dummy Cycles设少了采样点落在无效数据区GPIO复用功能配错了IO2或IO3没配成QSPI功能Flash的CS没拉低或者拉低时间不够电源电压不够Flash没正常工作排查顺序先读JEDEC ID确认基础SPI通信正常再读状态寄存器2确认QE位是1然后检查GPIO配置最后用示波器看波形。8.2 高速下数据偶尔出错低速能跑高速就出错一般是信号完整性问题。可以尝试降低SCK频率看是否稳定在IO0到IO3上加小电容10pF到22pF到地滤除高频噪声调整Sample Shifting改成Half Cycle增加CS High Time检查电源退耦电容是否足够我在一个项目里遇到过100MHz下每读几MB就错一个字节的情况后来把SCK降到80MHz就完全稳定了。所以不要盲目追求最高频率稳定第一。8.3 内存映射模式读数据不对内存映射模式配置对了但读出来的数据和预期不符。可能的原因进入映射模式之前没有正确配置Quad使能映射的地址范围超过了Flash实际容量Cache一致性问题读到了旧数据映射模式配置里的Dummy Cycles和间接模式不一致解决方法先用间接模式读一块数据作为基准再用映射模式读同一块数据对比是否一致。如果不一致逐步排查配置。8.4 写操作失败W25Q128的写操作有几个限制写入地址必须按页对齐256字节一页写入数据不能跨页写之前必须先擦除擦除单位是扇区4KB每次写之前都要发Write Enable命令如果写失败先检查这些条件是否满足。另外写操作完成后要等待BUSY位清零不能立即发下一条命令。8.5 常见问题速查表现象可能原因解决方法读全0xFFQE位未设检查状态寄存器2读全0x00CS未拉低检查CS引脚配置高速出错信号完整性降频、加电容、调采样映射读错Dummy Cycles不对与间接模式对比写失败未擦除或未写使能检查写流程偶尔死机总线冲突调整QSPI优先级9. 进阶技巧与项目经验9.1 双闪存模式STM32H7的QSPI支持双闪存模式可以同时接两颗W25Q128容量翻倍到32MB带宽也能翻倍。配置上需要把两颗Flash的CS分别接到QSPI的CS和另一个GPIO上然后配置QSPI控制器为双闪存模式。双闪存模式下两颗Flash的地址空间是交替映射的。比如0x90000000对应Flash1的0地址0x90000001对应Flash2的0地址以此类推。读的时候两颗Flash并行工作带宽翻倍。这个模式适合需要大容量高带宽的场景比如视频缓存、大型字库。但硬件设计上要注意两颗Flash的走线等长否则并行读写时可能出错。9.2 配合DMA和DMAMUXSTM32H7的DMAMUX可以把QSPI的DMA请求路由到任意DMA通道。配合双缓冲技术可以实现QSPI读和CPU处理并行。比如你在做音频播放DMA从QSPI读下一段音频数据到缓冲区A同时CPU处理缓冲区B的数据两个缓冲区交替使用实现无缝播放。配置DMAMUX的时候要注意QSPI的DMA请求线是固定的需要通过DMAMUX的请求发生器来路由。具体配置在参考手册的DMAMUX章节有详细说明。9.3 在QSPI Flash上跑代码STM32H7支持从外部QSPI Flash执行代码这叫XIPExecute In Place。配置好内存映射模式后把代码链接到0x90000000区域CPU就可以直接从Flash取指令执行。但要注意QSPI Flash的读取速度比内部Flash慢而且有Cache才能跑得流畅。对于性能敏感的代码还是放到内部RAM或者ITCM里跑比较好。XIP适合放一些不常执行的代码比如配置函数、字符串常量。我在一个项目里把UI资源文件放在QSPI Flash代码放在内部Flash通过内存映射读资源效果很好。既节省了内部Flash空间又保证了代码执行速度。9.4 低功耗考虑W25Q128支持深度掉电模式电流可以降到1微安以下。在电池供电的设备里不访问Flash的时候可以发命令让它进入掉电模式需要的时候再唤醒。但要注意掉电模式下Flash不响应任何命令唤醒需要一定时间。而且STM32H7的QSPI控制器在掉电模式下也要相应配置避免产生不必要的总线活动。实操心得我在一个低功耗数据记录仪项目里用QSPI Flash存数据平时Flash处于掉电模式MCU定时唤醒写一批数据然后继续睡。整体功耗控制在50微安以下用一节纽扣电池能跑好几个月。关键是要确保写数据之前Flash已经完全唤醒否则会写失败。10. 完整初始化代码参考把前面讲的串起来一个完整的QSPI初始化流程大概是这样的// 1. 配置GPIO MX_GPIO_Init(); // 2. 配置QSPI外设 hqspi.Instance QUADSPI; hqspi.Init.ClockPrescaler 2; // 200MHz/2 100MHz hqspi.Init.FifoThreshold 8; hqspi.Init.SampleShifting QSPI_SAMPLE_SHIFTING_HALFCYCLE; hqspi.Init.FlashSize 23; // 2^23 8MB? 不对W25Q128是16MB // 更正FlashSize 24, 2^24 16MB hqspi.Init.ChipSelectHighTime QSPI_CS_HIGH_TIME_8_CYCLE; hqspi.Init.ClockMode QSPI_CLOCK_MODE_0; hqspi.Init.FlashID QSPI_FLASH_ID_1; hqspi.Init.DualFlash QSPI_DUALFLASH_DISABLE; HAL_QSPI_Init(hqspi); // 3. 复位Flash QSPI_CommandTypeDef cmd; cmd.Instruction 0x66; // Enable Reset cmd.InstructionMode QSPI_INSTRUCTION_1_LINE; cmd.AddressMode QSPI_ADDRESS_NONE; cmd.DataMode QSPI_DATA_NONE; cmd.DummyCycles 0; HAL_QSPI_Command(hqspi, cmd, HAL_QPSI_TIMEOUT_DEFAULT_VALUE); cmd.Instruction 0x99; // Reset HAL_QSPI_Command(hqspi, cmd, HAL_QPSI_TIMEOUT_DEFAULT_VALUE); HAL_Delay(1); // 4. 设置QE位 cmd.Instruction 0x06; // Write Enable HAL_QSPI_Command(hqspi, cmd, HAL_QPSI_TIMEOUT_DEFAULT_VALUE); cmd.Instruction 0x31; // Write Status Register 2 cmd.DataMode QSPI_DATA_1_LINE; cmd.NbData 1; uint8_t qe 0x02; HAL_QSPI_Command(hqspi, cmd, HAL_QPSI_TIMEOUT_DEFAULT_VALUE); HAL_QSPI_Transmit(hqspi, qe, HAL_QPSI_TIMEOUT_DEFAULT_VALUE); // 5. 等待写完成 do { cmd.Instruction 0x05; // Read Status Register 1 cmd.DataMode QSPI_DATA_1_LINE; cmd.NbData 1; uint8_t status; HAL_QSPI_Command(hqspi, cmd, HAL_QPSI_TIMEOUT_DEFAULT_VALUE); HAL_QSPI_Receive(hqspi, status, HAL_QPSI_TIMEOUT_DEFAULT_VALUE); if ((status 0x01) 0) break; } while(1); // 6. 进入内存映射模式 QSPI_MemoryMappedTypeDef mem_mapped; cmd.Instruction 0xEB; cmd.AddressMode QSPI_ADDRESS_4_LINES; cmd.AddressSize QSPI_ADDRESS_24_BITS; cmd.DataMode QSPI_DATA_4_LINES; cmd.DummyCycles 6; cmd.NbData 0; mem_mapped.TimeOutActivation QSPI_TIMEOUT_COUNTER_DISABLE; HAL_QSPI_MemoryMapped(hqspi, cmd, mem_mapped);这段代码里有个地方要注意FlashSize的计算。W25Q128是128Mbit等于16MB2的24次方是16MB所以FlashSize应该设为24。我上面写23是错的后来更正了。这个参数影响内存映射的地址范围设错了会导致访问越界。另外复位Flash的步骤不是必须的但建议加上。有些Flash在上电后可能处于不确定状态复位一下更稳妥。11. 我踩过的几个坑第一个坑是GPIO复用功能配错。前面提过IO2和IO3的AF编号和IO0、IO1不一样。我当时没仔细看手册全配成AF10结果Quad模式下读数据高四位全是0。查了两天才发现是这个问题。第二个坑是Dummy Cycles设少了。80MHz下我设了4个低速能读高速就出错。后来查手册发现80MHz需要6个改成6之后稳定了。这个参数不能凭感觉一定要查手册。第三个坑是内存映射模式下Cache一致性问题。我在一个项目里通过QSPI写入了新数据然后立即用映射模式读读出来还是旧数据。后来发现是Cache没刷新。解决方法是在写操作之后调用SCB_InvalidateDCache_by_Addr刷新对应的Cache行。第四个坑是电源退耦不足。Flash在高速读写时电流波动比较大如果退耦电容不够电源上会有噪声导致读写错误。我在Flash的VCC引脚旁边加了0.1uF和1uF的电容之后问题就解决了。第五个坑是CS High Time设得太小。W25Q128要求CS拉高后至少保持50ns我一开始设了1个周期100MHz下才10ns不够。改成8个周期之后就没再出过问题。这些坑说起来都是细节但每一个都可能导致项目卡壳好几天。希望看到这篇文章的人能少走点弯路。12. 性能还能再压榨吗如果你已经把QSPI跑到100MHzDMA和内存映射都配好了还想再快可以考虑几个方向提高SCK频率W25Q128的极限是104MHz但实际能不能跑到取决于你的PCB和电源。我试过跑到120MHz大部分芯片能工作但个别批次会出错。如果产品要过认证建议留足余量。用双闪存模式两颗Flash并行带宽翻倍。但硬件成本增加PCB面积也增加。优化Cache策略把QSPI映射区域配成Cacheable并且合理设置预取能显著提高顺序读性能。减少命令开销在内存映射模式下每次读都要发命令和地址。如果读的是连续地址QSPI控制器会自动合并传输减少开销。但如果读的地址跳跃很大开销就上去了。用DDR模式STM32H7的QSPI支持DDR模式在时钟的上升沿和下降沿都采样数据理论带宽再翻倍。但W25Q128不支持DDR所以这个用不上。如果你用的是支持DDR的Flash可以试试。实际项目里我一般把SCK定在80MHz到100MHz之间配合DMA和内存映射性能已经足够应付大多数场景了。再往上压榨收益递减风险递增不太划算。13. 几个实用的小技巧快速验证Quad模式是否生效读JEDEC ID用单线模式读Device ID用Quad模式对比结果。如果Quad模式下读出来不对说明QE位没设上。用示波器看波形如果条件允许用示波器看SCK和IO0的波形。正常工作时IO0上应该有明显的四线传输特征。如果IO0一直是高电平或者低电平说明Quad模式没生效。写操作前先擦除W25Q128只能把1写成0不能把0写成1。所以写之前必须先擦除擦除后所有位都是1。这个和EEPROM不一样别搞混了。注意扇区边界擦除的最小单位是4KB扇区写入不能跨页256字节。如果你要写的数据跨越了页边界需要分多次写。保留一份黄金配置调通之后把CubeMX的配置文件或者初始化代码保存一份。下次做新项目直接复制省得重新踩坑。定期读状态寄存器在长时间连续读写的时候定期读一下状态寄存器确认BUSY位和WEL位状态正常。如果发现异常及时处理。温度影响W25Q128的读写速度受温度影响。高温下可能需要降低SCK频率才能稳定工作。如果你的产品要在宽温范围内工作建议留足余量。批次差异不同批次的W25Q128在性能上可能有差异。我遇到过同一型号不同批次的芯片一批能跑100MHz另一批只能跑80MHz。所以量产的时候要留余量不能按实验室的最优值来设计。这些技巧都是实际项目中积累的书本上不一定有。希望能帮到正在折腾QSPI的你。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑