ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

QSPI接口NAND Flash驱动移植实战:从选型到性能调优

QSPI接口NAND Flash驱动移植实战:从选型到性能调优 简介面向STM32F446嵌入式开发者的QSPI NAND闪存驱动实例基于四线SPI接口实现与多种SPI NAND器件的通信实测可完成读、写、擦除、识别等基本操作。QSPI相比传统SPI具有更高数据吞吐适用于音频缓存、图像存储、日志记录等对容量和速度有一定要求的场景。压缩包共274个文件包含127个H头文件、103个C源码文件、24个汇编文件以及uvprojx、uvoptx等Keil工程配置和PDF说明整体1.89MB方便直接导入编译。这套示例代码完整展示了QSPI的时钟配置、GPIO复用、传输模式选择等初始化细节驱动层覆盖设备识别、页面写入、块擦除、数据读取及坏块检测流程并针对不同厂商NAND的时序差异设计了通用命令结构。目前已有1137人学习适合具有一定STM32基础、希望深入掌握外部存储扩展的开发者参考使用。 这两年做嵌入式存储NAND Flash的价格一路往下掉4Gbit的国产片子几块钱就能拿到而同样容量的NOR Flash可能贵出好几倍。但很多人在STM32F系列上挂NAND时会卡在接口选型上——普通SPI速率上不去做固件升级和日志存储太慢eMMC又得额外接SDIO或专用控制器硬件复杂度明显增加。QSPI刚好卡在中间这一档四根数据线一根时钟一个片选用很少的IO就能把吞吐拉起来整体成本和布线难度都比并口NAND或者eMMC方案低一个量级。这篇文章适合谁一类是正在做低成本大容量存储方案的嵌入式工程师另一类是手里有GD32或者其他带QSPI外设MCU、想试试SPI NAND的玩家。我会从方案选型开始讲清楚QSPI和NAND这两样东西为什么适合凑在一起再给出一套可以直接抄的驱动移植流程最后把我实际踩过的坑、调过的性能参数一并整理出来。这篇东西不涉及复杂的文件系统层重点放在底层驱动和硬件配合上读完你至少能自己把一片带QSPI的NAND跑起来。1. 方案选型为什么QSPI NAND是低成本大容量的最优解1.1 NAND和NOR的本质区别NAND和NOR虽然都叫Flash但设计目标完全不同。NOR的随机读取速度优秀支持按字节读、甚至直接在Flash里执行代码所以MCU内嵌的小容量代码存储基本都用NOR。NAND则把容量和成本放在第一位结构上更接近块设备读写必须按页、擦除必须按块进行。对嵌入式场景来说最直观的差异就是同容量下NAND的价格可能只有NOR的几分之一但同时带来了坏块、ECC、写寿命这些绕不开的问题。我用一个表格把这个对比讲清楚表格里的数字是常见参数范围不同厂家片子会有差异对比项NOR FlashNAND Flash单位容量价格高低约为NOR的1/5到1/10最大容量常见到128Mbit再往上贵得离谱常见1Gbit到8Gbit上探更大随机读支持按字节读很适合XIP不支持读必须先整页载入写入方式按字节/按页编程按页编程页大小常见2KB/4KB擦除扇区擦除最小4KB左右块擦除常见128KB/256KB坏块几乎没有出厂即有坏块运行中还会产生ECC需求一般不强制必须做1bit或4bit根据制程决定接口SPI/QSPI/并口SPI/QSPI/并行/ONFI等STN32F系列里STM32F7、H7等型号内置了完整的QUADSPI外设GD32H7也做了兼容设计。这一点很关键如果你手里只有STM32F4是不支持QSPI的必须走FSMC并口NAND或者用普通SPI慢慢磨。所以选型第一步先查MCU的手册确认外设名是不是QUADSPI或者QSPI。1.2 QSPI外设的“提速”原理QSPI本质上是对传统SPI的扩展。普通SPI靠MOSI和MISO两根线一根一根传比特QSPI把数据线扩到四根分别在D0、D1、D2、D3上走数据。更聪明的是QSPI允许命令阶段、地址阶段、数据阶段分别配置不同的IO宽度。比如你可以用单线发送命令字再用四线传输大量数据这样既保持了兼容性又不浪费带宽。在STM32的QUADSPI里这个机制被包装成了比较灵活的寄存器控制。外设支持SDR和DDR两种模式DDR模式下时钟上下沿都能采样理论上直接把数据带宽再翻一倍。对于NAND而言典型页大小是4KB读一页数据的时间主要花在传输阶段QSPI把原本单线传一页的时间压缩到四分之一体感差别非常明显。1.3 什么时候别用这个组合如果应用要求上电后直接从Flash执行代码比如“XIP启动”QSPI NAND基本不适合。NAND出厂的坏块和后期磨损决定了你不能把它当作线性存储直接做指令寻址读出的数据还要经ECC校验执行代码会有很大的稳定性隐患。这种场景老老实实选NOR。另外如果你的应用是高频小量随机写比如每秒写几十个字节的配置参数NAND的页编程和块擦除机制会非常痛苦。擦一块256KB可能只是为了改一个字节还面临重复擦写磨损这种场景用EEPROM或NOR更合适。QSPI NAND最适合的是大块连续数据的顺序读写固件升级包、录音文件、图片缓存、IoT日志、黑匣子数据记录这类数据一写就是几KB甚至几MBNAND的页结构反而成了优势。2. 硬件连接与芯片选型国产NAND的“平替”思路2.1 QSPI引脚对应关系STM32的QUADSPI通常复用在一组固定IO上以常见的100Pin封装的F7/H7系列为例信号常用复用引脚说明QSPI_CLKPB2时钟最高频率取决于分频和芯片规格QSPI_BK1_CSPB6/PB11Bank1片选片选电平极性可配QSPI_BK1_IO0PD11/PE7/PA0等数据线0同时也是标准SPI的MOSIQSPI_BK1_IO1PD12/PE8/PA1等数据线1同时也是MISOQSPI_BK1_IO2PD13/PE9/PA2等数据线2QSPI独有QSPI_BK1_IO3PD14/PE10/PA3等数据线3QSPI独有具体引脚的复用关系必须查你所用型号的Datasheet和CubeMX不同封装、不同型号都会影响可用的映射。硬件连接上D0-D3在上拉电阻方面建议参考芯片手册一般需要4.7k到10k的上拉保证上电瞬间所有数据线处于确定电平防止NAND误判指令。2.2 国产高性价比NAND芯片推荐“国产便宜的NAND”这个话题在渠道里已经有不少稳定供货的型号了。我实际用过的方向大概可以分成几类兆易创新GD5F系列比如GD5F4GQ4RC4Gbit容量3.3V供电指令集和主流SPI NAND通用性好技术支持资料也相对齐全是我目前最常用的一颗。东芯半导体D35Q系列2Gbit/4Gbit都有主打性价比我在小批量项目里试过D35QHA兼容性OK。各类兼容W25N01GV的国产SPI NAND华邦的W25N01GV是1Gbit的经典器件现在国产替代型号很多价格比原厂便宜不少买的时候注意核对指令手册虽然大框架一致但细节命令字节偶尔有区别。选型时我特别提醒三件事。第一电压等级很多NAND有1.8V和3.3V两种型号引脚不兼容电压搞错直接烧片子。第二看“最大频率”参数不能只看标题数字要结合你MCU的QSPI时钟树能否正好落到这个频率。第三确认片子的地址模式是3字节还是4字节。容量超过2Gbit一般需要开启4字节地址这会直接让你的首个读ID命令和后续页读写命令都不一样。2.3 SD NAND、eMMC与裸NAND的取舍搜索里有人问“国产便宜的SD NAND芯片”我也说说这个东西。SD NAND本质上是在NAND晶圆外面封了一个SD控制器对外以SDIO协议通信软件上可以直接搭文件系统。它的好处是省心坏块管理和ECC都由控制器代劳但代价是协议复杂、引脚多、实际成本比裸NAND高而且SD协议本身是为存储卡设计的在MCU环境里不如裸NAND灵活。eMMC也是一样的思路内部整合了控制器使用门槛低但引脚多、ball封装难布线适合需要大容量且PCB空间宽裕的场景。QSPI裸NAND夹在中间比普通SPI NAND快得多比eMMC简单得多比SD NAND便宜。在固件升级、日志存储、数据记录这类不需要复杂文件系统、我们可以直接做坏块表管理的应用里它是最平衡的方案。3. 驱动移植实战从CubeMX到第一个ID读出来3.1 CubeMX里的QUADSPI配置我以STM32H750 GD5F4GQ4RC为例子第一步是在CubeMX里使能QUADSPI外设。关键配置项如下Mode选择Quad SPI启用Bank1。Clock PrescalerQSPI时钟来源于AHB一般先把分频调大比如先分到25MHz验证基本通信稳定后再拉到更高的66MHz或80MHz。Flash Size这个容易把人绕晕。QUADSPI的Flash Size寄存器填的是“芯片位宽减1”以bit为单位。比如4Gbit的NAND容量是2^32 bitsFlash Size就填31。FIFO Threshold一般保持默认如果收发大数据建议设置成16字节。Sample Shift这个参数影响接收数据时的采样点默认可能不满足时序要求如果读出来的数据不稳定可以尝试切换采样沿这是QSPI调试里最常用的优化点之一。配置完成后生成工程先别急着写应用代码。QUADSPI在HAL库里属于比较复杂的类别初始化结构体里的ClockPrescaler、FifoThreshold、FlashSize、SampleShifting这几个字段任何一处不对都会导致通信异常所以务必对照CubeMX生成值和实际芯片参数确认一遍。3.2 底层读写接口封装SPI NAND的命令机制我第一次用的时候觉得和普通NOR差异很大。普通SPI NOR可以用一条0x03命令连续读任意地址程序员几乎感觉不到内部结构。NAND不是这样读一页数据必须分两步先发送页读取命令把整页数据从存储阵列搬到NAND内部缓存再通过缓存读取命令把数据搬到MCU。写数据也要先往缓存里灌数据再执行真正的写入命令。以GD5F4GQ4RC为例读一个页的完整流程是uint8_t nand_read_page(uint32_t addr, uint8_t *buf, uint32_t len) { QSPI_Command_TypeDef cmd {0}; uint8_t status; // 第一步发送页读取命令0x13把页数据加载到NAND内部缓存 cmd.Instruction 0x13; cmd.InstructionMode QSPI_INSTRUCTION_1_LINE; cmd.AddressMode QSPI_ADDRESS_1_LINE; cmd.AddressSize QSPI_ADDRESS_24_BITS; cmd.Address addr; cmd.DummyCycles 0; cmd.DataMode QSPI_DATA_NONE; HAL_QSPI_Command(hqspi, cmd, HAL_QSPI_TIMEOUT_DEFAULT_VALUE); // 等待内部操作完成 if (nand_wait_ready() ! 0) { return 0xFF; } // 第二步用四线缓存读取命令0x6B把数据传到MCU cmd.Instruction 0x6B; cmd.AddressMode QSPI_ADDRESS_1_LINE; cmd.Address 0; cmd.DataMode QSPI_DATA_4_LINES; cmd.NbData len; HAL_QSPI_Command(hqspi, cmd, HAL_QSPI_TIMEOUT_DEFAULT_VALUE); HAL_QSPI_Receive(hqspi, buf, HAL_QSPI_TIMEOUT_DEFAULT_VALUE); return 0; }这个函数就是整个QSPI NAND驱动的核心骨架。理解它之后写页和擦除的命令结构都类似只是命令字节不同0x02是Program Load把数据灌进缓存0x10是Program Execute把缓存数据真正写入阵列0x20或0xD8是块擦除。3.3 状态寄存器轮询与擦写时序NAND执行一次编程或擦除需要几十微秒甚至几毫秒的时间MCU必须通过状态寄存器判断操作何时完成。常用的就是0x05命令读取状态寄存器1其中bit0是OIPOperation In Progress位为1表示正在忙。uint8_t nand_wait_ready(void) { QSPI_Command_TypeDef cmd {0}; uint8_t status 0xFF; uint32_t timeout 100000; cmd.Instruction 0x05; cmd.InstructionMode QSPI_INSTRUCTION_1_LINE; cmd.AddressMode QSPI_ADDRESS_NONE; cmd.DummyCycles 0; cmd.DataMode QSPI_DATA_1_LINE; cmd.NbData 1; while (timeout--) { HAL_QSPI_Command(hqspi, cmd, HAL_QSPI_TIMEOUT_DEFAULT_VALUE); HAL_QSPI_Receive(hqspi, status, HAL_QSPI_TIMEOUT_DEFAULT_VALUE); if ((status 0x01) 0) return 0; } return 0xFF; }注意在发送写使能命令0x06之后、每次Program或Erase之前一定要确认WEL位状态寄存器bit1已经置1。如果WEL没有生效后续写入和擦除命令会被NAND直接忽略连错误都不报这是新手最容易莫名其妙的问题之一。我自己的排查顺序一般是先查0x06有没有发送、再查片选极性是否正常、最后查供电电压和上电时序。3.4 手写驱动时最容易漏掉的细节第一NAND上电后需要等一小段时间才能接受命令典型值是1ms到3ms代码里在QSPI初始化后最好做一次延时。第二几乎所有命令发送前都要确认上一次操作没有进行中否则命令被丢弃。第三NAND的地址不是连续的字节地址而是由列地址和行地址组成的页大小4KB的芯片低几位是列地址高位才是行地址。如果你直接拿“页号×页大小”的线性地址去发命令后面的数据布局会和实际存储排列不一致这在调试时非常隐蔽。4. 坏块管理与ECCNAND能不能稳定用全靠这两件事4.1 坏块策略不是做好人是不得不做NAND出厂时就有坏块且每个芯片的坏块位置都不同这是制造工艺决定的。单片机项目里最常见的做法是“坏块表 块跳过”。生产阶段驱动遍历所有块读取每个块第一个页的Spare区第一个字节若该字节不是0xFF就标记为坏块。运行时文件系统层或FAL层分配块时跳过坏块。如果项目没有文件系统那就要在一层简单的“逻辑块地址映射”里做坏块重映射保证应用只和逻辑块号打交道物理坏块对上层透明。GD5F4GQ4RC这类SPI NAND每个块包含128个页每个页除了4KB数据还有128字节Spare区。坏块标记的位置很固定就是在块内第一页的Spare区偏移0处。所以通过一次扫描就能建立块级坏块表。4.2 ECC方案硬件没有就纯软件算NAND在制程缩小后可靠性明显下降读出来的数据偶尔会出现1bit甚至多位错误。ECC就是用来纠正这些错误的。嵌入式MCU的QUADSPI外设本身不做ECC所以你需要如果芯片有硬件BCH或ECC引擎比如部分GD32H7/SD NAND控制器直接在驱动里启用。如果没有硬件ECC就选择软件算法。1bit错误可以用Hamming码4bit以上错误基本得用BCH。纯软件BCH在MCU上计算开销比较大但只要你算过延时在写图片、音频这类可以容忍稍微慢点的场景里也能接受。至少做一个“读回校验”写页完成后把数据读回比对发现不一致就重新写或者标记坏块。这个方案不能纠正运行时比特翻转但对掉电导致写入失败这类问题非常有效。我自己现在的做法是数据量小、对速度不敏感时用软件Hamming把每256字节算出12字节校验码存进Spare区数据量大时改用带硬件ECC的SD NAND或者挂一颗带BCH控制器的方案。别想着“我的应用对数据错误不敏感”就跳过ECCNAND用久了随机比特位翻转一定会出现。4.3 掉电安全与磨损均衡嵌入式设备随时可能断电而NAND的页编程分“缓存加载”和“编程执行”两个阶段如果在第二阶段断电这个页的数据可能变成非0xFF也非写入数据的“脏状态”。应对策略是写操作先写到一个“临时块”写完并校验成功后再通过更新映射表方式把数据“上线”。这个做法本质是把掉电窗口压缩到最小。磨损均衡针对的是那些高频写入的数据比如日志文件。同一个逻辑块反复擦写可能几千次就废了而其他块还在“闲置”。简单的办法是在块映射表里记录每个物理块的擦除次数分配时优先挑次数少的块。完整实现有一定工作量但如果你只是做固件升级写入频率低可以先不管磨损均衡如果你用来存日志一天写入几十次就一定要做最基本的冷热数据分离。5. 实测踩坑记录与性能调优5.1 常见问题排查速查表我整理了一份我自己调试时遇到过的问题清单按出现频率排的现象可能原因排查方法读ID全0xFF片选极性配置反了或者CS没拉低检查CubeMX里ChipSelectPolarity配置读ID全0时钟分频过高数据线没上拉示波器查CLK和D0脚检查上拉电阻写数据后读回全是0xFF没有发写使能0x06或WEL没置位打印状态寄存器1确认bit11读写中途卡死没有等待NAND忙状态完成检查nand_wait_ready的轮询逻辑数据错位列地址/行地址拼接错误对照芯片手册确认页内偏移的地址范围某些块突然读写错误使用了坏块没有做坏块表扫描一遍Spare区把坏块加入表高时钟下通信不稳定Sample Shift采样点偏移逐档调整采样移位测误码率其中读ID这一步我会单独强调如果ID都读不出来后面全是空谈。先降低时钟到最低确认硬件通路没问题再逐步提速这是解决QSPI调试问题的最稳妥路径不要上来就跑高频通信不稳定时很难定位是时序问题还是接线问题。5.2 性能实测与优化方向以GD5F4GQ4RC在66MHz四线模式下为例实测读一个4KB页总线传输时间大约在130微秒左右算上NAND内部加载时间和命令开销整体读一页在300到500微秒。擦除一个块需要1到3毫秒写一页需要0.5到1.5毫秒。这个速度已经可以满足绝大多数嵌入式存储需求如果传一个1MB的固件包读出来基本在100毫秒级别。想进一步优化的话主要有三招。第一开DMAHAL_QSPI_Receive在阻塞模式下会占用CPUDMA模式下大块数据传输时CPU可以干别的活。第二用双Bank交错写QUADSPI有Bank1和Bank2让两个NAND片选交替工作把擦除时间隐藏到传输时间里。第三合理调大时钟和采用DDR模式但前提是NAND芯片支持对应的命令和时序DDR模式下信号质量对PCB布线更敏感不能盲目追求快。5.3 关于“魔百盒”这类应用的一点补充网上搜索里总能看到“魔百盒CM201-2”、“hi3798mv300固件”这些词这些电视盒子板子上其实就是大容量的NAND Flash。很多人想自己备份固件、修砖或者刷机底层原理和使用STM32F的QSPI读写NAND是相通的。如果你只是单次备份完全可以用一片带QSPI的STM32/GD32开发板飞线接出盒子的NAND引脚按本文的驱动流程把整片读出来保存成固件文件。但这属于救砖场景盒子的BootLoader和分区结构各不相同驱动层跑通只是第一步后续还要面对分区解析和校验不是小白五分钟能搞定的事操作前务必做好数据备份。涉及的具体盒子和SoC资料建议直接去对应固件社区查那里面有大把前人踩平的坑。最后再分享一个小技巧拿到一颗新NAND先不要急着写完整驱动。用逻辑分析仪抓一下上电后的默认引脚电平然后手动发一个0x9F命令读ID。ID读通了说明命令通路没问题后面全部都是在这个基础上加地址、加数据的事。我自己每换一颗芯片都习惯先跑一遍这个“最小验证”半小时能排除八成硬件问题省下的调试时间非常可观。本文还有配套的精品资源点击获取
返回列表