尧图精选

Linux内核下W25Q128 SPI NOR Flash调试实战与避坑指南

🕒 发布时间:2026/10/1 1:20:19 📁 来源:尧图网络
干过嵌入式Linux开发的朋友十有八九都跟SPI NOR Flash打过交道。W25Q128这颗芯片更是出镜率极高128Mbit容量、SPI接口、四个引脚就能搞定一个根文件系统或者U-Boot环境变量区性价比在8MB-16MB这个区间里几乎没有对手。但越是常见的芯片调起来越容易翻车——我见过不少人在内核里把驱动选项勾上了、设备树也写了断电重启后却死活读不到Flash或者读到ID却擦写失败最后折腾半天发现是某个细节没注意到。这篇文章就围绕Linux kernel环境下调试W25Q128这条主线把从硬件确认、内核配置、设备树编写到驱动框架拆解、读写验证、波形分析这一整套流程完整走一遍。内容适合正在做平台bringup的工程师也适合刚接触内核驱动、想搞懂SPI NOR Flash完整工作路径的学生。我会把实际调试中那些手册里写不清楚、文档里找不到的坑一并讲透。1. 调试W25Q128之前先把这几件事搞清楚1.1 为什么W25Q128是调试SPI NOR的首选芯片W25Q128来自华邦Winbond容量128Mbit换算过来就是16MB。它支持标准SPI、Dual SPI和Quad SPI三种访问模式标准模式下时钟最高可以跑到133MHz快速读指令。具体到内核调试阶段我们关心的核心信息就几个JEDEC ID是0xEF 0x40 0x18页大小256字节扇区大小4KB块大小64KB支持SFDPSerial Flash Discoverable Parameters协议。这颗芯片在Linux内核的drivers/mtd/spi-nor/目录下有非常成熟的支持基本属于“插上就能识别”的类型。但也正因为支持太成熟一旦板子上没跑起来反而要怀疑是不是自己的配置问题。它的兼容型号也很多比如GD25Q128兆易创新、XM25QH128C复旦微等寄存器指令集几乎完全兼容这给选型留了很大余地。1.2 硬件连接与原理图检查清单调试的第一步不是写代码而是对照原理图把SPI引脚确认清楚。W25Q128标准接线是六根线CLK时钟、DI数据输入MOSI、DO数据输出MISO、CS#片选、WP#写保护、HOLD#保持。这里有两个极其容易踩的坑。第一WP#和HOLD#不能悬空。很多开发板的原理图为了省事把WP#和HOLD#直接悬空结果内核读写Flash一切正常但执行擦除操作时芯片始终返回0x80状态错误。原因很简单WP#悬空时电平不确定一旦被拉低状态寄存器里的块保护位BP3-BP0就生效了芯片拒绝任何擦写指令。正确的做法是把WP#和HOLD#上拉到VCC或者用GPIO控制。第二片选引脚的GPIO极性。如果片选是用GPIO模拟的设备树里cs-gpios属性必须声明正确的标志。SPI片选是低有效所以GPIO标志是GPIO_ACTIVE_LOW。写成高有效的话内核访问Flash时会看到片选永远拉不高、也拉不低总线上的设备响应逻辑完全错乱。1.3 内核配置少了这几个选项驱动根本不工作Linux内核里SPI NOR Flash的驱动路径经过了几次重构。老版本4.x早期用的是m25p80驱动新版本4.18以后统一走spi-nor框架再配合spi-mem接口层。不管哪个版本内核配置里这几个选项都必须开启CONFIG_MTDy CONFIG_MTD_SPI_NORy CONFIG_SPIy CONFIG_SPI_MASTERy如果用的不是mtdblock而是直接挂根文件系统还需要根据文件系统类型开启对应的支持。比如用JFFS2就开CONFIG_JFFS2_FS用UBIFS开CONFIG_UBIFS_FS。这里有个细节很多人忽略老内核里CONFIG_MTD_M25P80这个选项独立存在新内核里它被合并进CONFIG_MTD_SPI_NOR了。如果你在旧内核上照着新教程配选项会发现根本找不到对应的配置项。所以调试前先确认内核版本别把网上搜到的配置方法直接照抄。另外设备树里必须写对compatible属性。当前内核推荐写法是jedec,spi-nor这个compatible是万能兼容串无论是W25Q128还是GD25Q128只要符合JEDEC规范内核都会尝试通过读取SFDP和JEDEC ID来识别芯片ecspi1 { pinctrl-names default; pinctrl-0 pinctrl_ecspi1; cs-gpios gpio4 9 GPIO_ACTIVE_LOW; status okay; w25q128: flash0 { compatible jedec,spi-nor; reg 0; spi-max-frequency 40000000; spi-tx-bus-width 1; spi-rx-bus-width 1; #address-cells 1; #size-cells 1; }; };spi-max-frequency这个参数直接影响稳定性我调试时习惯先从25MHz起跳确认完全稳定再往上提。原因后面章节展开讲。2. SPI协议里最容易翻车的四个细节2.1 CPOL和CPHA决定了能不能通SPI通信的四种模式本质是时钟极性和相位排列组合。CPOL决定空闲时时钟电平CPHA决定数据采样沿。W25Q128支持Mode 0CPOL0CPHA0和Mode 3CPOL1CPHA1。实际调试中Mode 0用得最多绝大多数SPI控制器默认也是Mode 0。但问题出在设备树或驱动里显式指定模式时比如你在设备树里写了spi-cpol或spi-cpha属性而控制器驱动实现又不够严谨就可能产生模式错配。现象是读ID读出来的值每个字节都带移位或者像0x00这种全零数据因为采样点落在了翻转沿上。另外要注意spi-mem框架在发起传输前会自动根据flash的mode requirements做适配声明为jedec,spi-nor的芯片默认在Mode 0或Mode 3下都能工作。反倒是某些自研的SPI控制器驱动没有正确处理mode位。2.2 硬件片选与软件片选谁更可靠SPI片选分为硬件片选控制器自动拉CS和软件片选GPIO控制CS。调试W25Q128这种低频外设两条路都走得通但优先级不同。硬件片选的优点是响应快控制器在总线事务开始前自动拉低CS结束后自动拉高不需要CPU干预。缺点是对控制器驱动的要求高如果在一次spi_message里包含多个spi_transfer而驱动在每段transfer之间错误地拉了CSFlash就会认为这是两次独立操作传输直接断掉。尤其是一些继承了旧DMA路径的驱动容易出现片选毛刺。软件片选则相对“无脑但可靠”。设备树里用cs-gpios声明GPIO之后spi_controller会在每次传输前后通过GPIO操作来模拟CS行为事务边界控制得非常好。缺点是CS动作由CPU执行高频下会有几微秒的延迟但对于W25Q128这种读ID、页编程级别的操作完全感知不到差别。我个人的建议读数据量小的控制通路用硬件片选涉及大数据块读写、又对时序抖动敏感的场景用软件片选更稳。前提是整个SPI控制器驱动对两种模式都支持这个在板级调试阶段就要确认清楚。2.3 时钟频率上限怎么定W25Q128的手册上写着快速读0x0B指令最高133MHz但那是四线快读模式下的理论极限。内核调试阶段我们通常先跑标准读0x03指令这个指令模式下最高只能到50MHz。设备树里的spi-max-frequency只是声明芯片能承受的上限实际跑多快还取决于SPI控制器本身的时钟树分频。比如某平台SPI根时钟80MHz分频系数2就是40MHz分频系数3是26.7MHz。很多平台在设备树里写着133MHz内核实际算出的波特率却是向下取整到最近的可配置值。调试时我建议按这个顺序来先设25MHz确认通路再用50MHz验证标准读最后如果实际场景需要大吞吐量再检查PCB走线质量、上拉电阻阻值尝试上100MHz的Quad模式。千万别一上来就怼高频SPI协议本身没握手机制频率过高时数据直接错而且错得毫无规律。2.4 DMA到底要不要用热搜词里“spi需要两个dma吗”是个高频问题说明很多人对SPI DMA通道配置有困惑。结论是W25Q128这类NOR Flash的调试阶段先别急着开DMA确认功能正确后再考虑性能优化。SPI是全双工总线理论上同时有发送和接收两条数据线所以如果要用DMA确实需要TX DMA和RX DMA两个通道一个负责把数据从内存搬到SPI TX FIFO一个负责把SPI RX FIFO的数据搬到内存。开DMA之前必须确认两件事DMA的snoop/缓存策略配置正确以及spi_transfer里的tx_buf、rx_buf都满足DMA对齐要求。否则数据缓存不一致会导致发送内容少几个字节或者接收缓冲区里出现陈旧缓存数据。我在一个Cortex-A7平台上就遇到过开了DMA后读Flash ID偶尔成功偶尔失败同一包命令发出去回包的第一字节是上一次传输的残留内容。排查下来是DMA描述符的缓冲去使能配置不对CPU cache没有invalidate。这种问题在功能验证阶段会浪费大量时间。所以先把DMA关掉、用PIO模式跑通再开DMA优化这是最务实的路径。3. Linux内核SPI NOR驱动框架从设备树到MTD设备3.1 三层结构控制器驱动、spi-mem层、spi-nor层要理解调试步骤先看清内核里这条数据链路长什么样。整个SPI NOR的支持分三层SPI控制器驱动位于drivers/spi/目录负责具体芯片的寄存器操作比如i.MX平台的spi-imx.c。对外提供spi_controller抽象内核通过它收发SPI数据。spi-mem层位于drivers/spi/spi-mem.c。它是SPI控制器和存储设备之间的适配层把read、write、erase这些操作翻译成SPI协议里的命令序列。它的存在让同一个flash驱动能跑在不同控制器上不用关心底层时序实现。spi-nor层位于drivers/mtd/spi-nor/这是Flash逻辑核心。管理JEDEC ID识别、SFDP解析、四字节地址模式、状态寄存器操作、各种erase粒度。这一层向上注册成MTD设备。调试时看到的内核日志输出基本来自三层里的任意一层。比如控制器传输超时会报在spi层Flash状态寄存器检查失败会报在spi-nor层MTD分区创建失败会报在MTD层。学会从日志关键字定位到具体层级排查效率能翻倍。3.2 设备树节点里的隐藏信息设备树并不仅仅是把芯片挂在总线上那么简单。reg 0表示片选序号对应控制器下的第几个CS引脚。spi-max-frequency是给控制器驱动用的频率上限。spi-tx-bus-width和spi-rx-bus-width是声明四线模式的关键想启用Quad读需要把这俩改成4并且芯片必须支持对应的读指令。这里有个容易踩的坑只改spi-tx-bus-width而不改spi-rx-bus-width或者只改设备树不确认内核是否开启CONFIG_MTD_SPI_NOR的四线支持Quad模式是不会自动生效的。而且有些闪存厂商在芯片出厂时要手动发指令进入Quad Enable状态比如W25Q128需要往状态寄存器2的QE位写1。这个操作在Linux内核里由spi_nor框架内部处理但前提是内核能正确识别到芯片支持Quad模式。如果SFDP解析失败默认只走标准SPI四线性能优化就成了空谈。另一个细节是m25p,fast-read属性。老内核里这个属性用来启用Fast Read指令新内核里判断标准变了如果没有显式配置0x03标准读会被优先使用。这使得同样设备树在升级内核后读性能出现明显差异排查起来相当隐蔽。3.3 内核启动日志怎么确认驱动挂载成功板子启动后第一步看dmesg | grep spi。正常的日志应该类似spi-master spi0: spi0.0 registered spi-nor spi0.0: w25q128 (16384 Kbytes)第一行来自spi控制器驱动表示总线上的0号设备注册成功。第二行来自spi-nor框架w25q128是识别出的芯片名16384 Kbytes是容量。如果只看到第一行没有第二行说明spi-mem层发起的读ID命令没有得到正确响应问题可能出在硬件连接、设备树compatible配置或控制器驱动上。确认注册成功后再看分区表是否生效。cat /proc/mtd会列出所有MTD分区dev: size erasesize name mtd0: 01000000 00001000 spi.flash如果分区表和预期不符检查设备树里flash节点下的partitions子节点或者检查内核开启了CONFIG_MTD_CMDLINE_PARTS、CONFIG_MTD_OF_PARTS。这俩选项没开设备树里写了分区也不会生效。4. 调试手段从内核日志到示波器的一整套组合拳4.1 打开内核的调试开关调试SPI相关问题时建议开启这几个内核配置能提供大量细节日志CONFIG_SPI_DEBUGy CONFIG_MTD_DEBUGy CONFIG_DYNAMIC_DEBUGy其中CONFIG_DYNAMIC_DEBUG尤其有用它允许你在运行时动态打开某个文件的打印。比如只想看spi-mem层的传输细节就可以echo -n file drivers/spi/spi-mem.c p /sys/kernel/debug/dynamic_debug/control这个技巧能省掉反复编译内核的时间我调驱动时几乎必用。注意要提前挂载debugfsmount -t debugfs none /sys/kernel/debug。另外查看SPI控制器注册了哪些设备和参数可以看/sys/bus/spi/devices/目录。每个设备目录下的of_node链接指向设备树节点而statistics文件里记录了传输次数、错误次数、超时次数对判断总线健康状况很有参考价值。4.2 用MTD工具验证读写驱动挂载成功后就要验证实际读写功能。最常用的是一套mtd-utils工具包里的mtd_debug和flashcp。先擦除4KB扇区mtd_debug erase /dev/mtd0 0 0x1000再写入一段数据mtd_debug write /dev/mtd0 0 0x1000 data.bin最后读回来比对mtd_debug read /dev/mtd0 0 0x1000 dump.bin cmp data.bin dump.bin注意mtd_debug的offset和length必须对齐erase block size否则返回EINVAL。很多新手在这里吃亏写入时用了任意字节偏移结果擦除失败。4KB扇区大小可以通过cat /proc/mtd里的erasesize列看到W25Q128一般是0x10004KB。如果想模拟更真实的文件系统操作可以用flashcpflashcp -v data.bin /dev/mtd0flashcp会自动先擦后写还会校验比手动三条命令省事。但注意它不保留分区表逻辑只做裸分区操作。4.3 用逻辑分析仪看SPI波形当软件层面看起来一切正常但读写结果依然不对时就该上逻辑分析仪了。SPI总线是四根线全都能测到除了MISO、MOSI、CLK、CS之外建议把WP#和HOLD#也一起挂上因为这两个信号的电平决定了芯片的写入权限。判断时序是否正常的核心是看CS在每帧传输前后是否干净拉低拉高以及CLK的上升沿与数据线的采样关系。可以用一个非常简单的读JEDEC ID命令来验证时序主机发0x9F芯片返回3字节ID。逻辑分析仪上应该能看到CS拉低CLK输出8个时钟拍子MOSI上出现0x9F然后继续24个时钟拍子MISO上依次出现0xEF 0x40 0x18。如果MISO上没数据可能的原因有DI/DO接反、WP#/HOLD#电平异常、芯片根本没供电。如果MISO有数据但值不对优先怀疑CPOL/CPHA配置。4.4 常见错误码的含义与定位思路调试中常见的错误码和排查方向我整理成了一张表方便快速对照错误/现象可能原因排查思路spi-nor spi0.0: unreachable 0xef4018芯片ID识别失败检查设备树compatible、SPI模式、硬件接线spi_master spi0: I/O Error控制器传输失败检查DMA配置、时钟分频、CS GPIO映射擦除操作超时状态寄存器WIP位一直不置0查WP#引脚电平、分区是否被FUSE保护写入后读回全0xFF擦写命令未真正执行查状态寄存器、HOLD/WP引脚、bus-width配置读回数据错位SPI模式不匹配用逻辑分析仪确认采样沿4.5 用SPI环回测试验证控制器通路如果硬件、设备树、驱动都查过还是不确定问题出在flash芯片还是控制器总线可以用一个物理层的技巧验证SPI控制器本身把MOSI和MISO短接然后从用户态发起一个简单的spidev读写操作全双工模式下从MISO读到的数据应该和MOSI发送的完全一致。要使用这种方法需要在设备树里额外加一个spidev节点ecspi1 { spidev1 { compatible rohm,dh2228fv; /* 任意spidev兼容串 */ reg 1; spi-max-frequency 1000000; }; };然后在内核里开CONFIG_SPI_SPIDEV编译完后在用户态用spidev_test工具测试spidev_test -D /dev/spidev0.1 -v如果自测数据一致说明控制器底层收发没问题问题锁定在flash芯片侧。5. 实测中踩过的坑与完整排查链路5.1 读ID正常但擦除失败从状态寄存器入手一次在某平台调试W25Q128内核日志显示芯片识别成功cat /proc/mtd也能看到分区写数据也没报错唯独mtd_debug erase命令执行后读回来的数据还是全0xFF而且命令超时。排查链路如下第一步先排除软件问题。换个环境用flash_erase再擦一次现象依旧。用mtd_debug info /dev/mtd0看擦除块大小确认是0x1000参数没错。第二步怀疑芯片状态寄存器有保护。通过mtd_debug没法直接读状态寄存器我写了一个小的内核模块或直接用devmem工具读取SPI控制器寄存器手动发起0x05读状态寄存器1命令。软件模拟出来的结果是0x3C二进制为00111100其中BP0-BP3位全是1说明芯片被设置为全部扇区写保护。第三步查硬件原理。翻看板子原理图发现WP#引脚直接连到地没有做上拉。这个接法等于芯片永远处在写保护使能状态。解决方法要么硬件上把WP#改接到VCC要么在内核驱动每次操作前获取状态寄存器、清除BP位。对于产品化方案肯定优先改硬件。这个案例说明Flash芯片识别正常不代表擦写权限正常状态寄存器的检查必须纳入基础调试流程。后期我在新板卡调试时会先跑一遍mtd_debug erase再读回验证作为快速验证芯片写能力的环境冒烟测试项。5.2 写数据后读回来全是0xFF从时序找根因另一个案例是芯片能读ID、能擦除、写操作也不报错但写入任意数据后读回的内容永远是0xFF。这个问题比全盘写保护更隐蔽因为芯片没有报告错误只是数据根本没写进去。排查过程用逻辑分析仪抓写命令。观察到主机发完页编程0x02命令和256字节数据后CS按预期拉高但紧接着主机没有发“读状态寄存器”的轮询命令而是直接认为编程完成然后开始发擦除命令。进一步分析发现flash的页编程需要时间常见值是0.4ms到3ms控制器必须在发完数据后不断轮询状态寄存器的WIP位等WIP变0后才算编程完成。问题定位到spi-nor驱动的write流程在一个老旧BSP上FIFO的预取逻辑和DMA的中断回调配合不好导致轮询状态寄存器的请求被错误地合并到上一次传输的尾部芯片收到的实际命令序列是“页编程 读状态”连着执行读状态这一拍返回的其实是编程结束后的真值但驱动程序误判状态。解决思路是做数据保持时间补偿、或者关闭FIFO预取启用更保守的传输方式。最终调稳后读写校验跑了几百遍没再出错。这类问题在PIO模式下几乎不会出现又在用DMA的平台上一定要保留怀疑空间。5.3 系统启动挂载根文件系统失败最后一个坑是开在把根文件系统直接放在W25Q128上选用JFFS2。系统能识别MTD分区但启动时内核报VFS: Unable to mount root fs。排查链路先确认分区名和根设备参数。root/dev/mtdblock3这种写法在内核新版本里经常失效因为mtdblock和mtdblock_ro在驱动层面不再等同于mtd设备。实际上启动参数应该用rootmtd:分区名配合CONFIG_MTD_BLOCK、CONFIG_MTD_BLOCK_RO开启。还有一种方式是直接root31:03用主设备号:次设备号指定。确认内核命令行无误后再看文件系统镜像本身。JFFS2镜像的制作要和MTD分区的eraseblock大小匹配如果制作工具用了错误的页大小刷进去后挂载时系统读取超级块失败就会报类似jffs2: Node with stale CRC的错误。重新用mkfs.jffs2 --eraseblock4KiB -p制作镜像后问题解决。这类问题的核心教训是SPI NOR的根文件系统挂载链路涉及bootloader传参、内核MTD子系统、文件系统的对齐参数任何一环的错误表现都是挂载失败但根因可能相去甚远。建议用排除法先把参数逐一对齐。6. 一点收尾心得把W25Q128在Linux kernel下调通这件事前期准备占到六成功夫。硬件上确认WP#、HOLD#的上拉电平确认片选极性和SPI模式软件上确认内核配置选项、设备树compatible和频率参数再配合一个逻辑分析仪做波形兜底多半的问题都能在两小时内定位到根因。反而是那些最容易忽略的小细节——状态寄存器保护位、DMA缓存一致性问题、Quad模式的QE位才是真正容易卡住人的地方。调试工具和技巧都分享完了最后分享一个我自己的操作习惯每到一个新平台我做的第一件事不是调驱动逻辑而是先用spidev环回测试把控制器底层的收发完整性验证一遍再挂上Flash芯片跑一组标准的“读ID→擦除→写入→读回”测试。这组流程走完了整个SPI链路的基本盘就稳了剩下的都是细节优化。这个习惯帮我省下了大量排查时间希望对你也一样有用。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →