1. 问题现象与影响
1.1 表面现象:Slave 永远等不到 NSS 被拉低
最近在 STM32MP257F_EV1 上调试一个外接 FPGA 的通信链路,FPGA 作为 SPI Master,板子上的 SPI3 作为 Slave。SPI3 的 NSS 引脚按照原理图走的是 PB1,于是我把 PB1 配成 SPI3_NSS,中断里等 FPGA 把 NSS 拉低后开始收发。
结果一跑起来,FPGA 那边明明已经正常输出时钟和数据,SPI3 这边却毫无反应。逻辑分析仪挂在 PB1 上,波形很清楚:主机确实把 NSS 拉低了,时钟也来了,但从机完全没有进入接收状态。更让人抓狂的是,读寄存器看到的 NSS 电平状态始终是 1,好像 PB1 根本没连到 SPI3 内部。
这种问题最难受的地方在于:硬件看着没问题,软件代码也翻了无数遍,最后折腾一圈才发现,问题根本不在 SPI 外设本身,而是 PB1 这个引脚在系统初始化阶段就没有成功被 SPI3 驱动“claim”下来。
1.2 这类问题的影响范围
不光是 STM32MP257F_EV1 这一块板子,所有跑 Linux 的 STM32MP 系列芯片都会遇到类似情况。只要涉及引脚复用、pinctrl、设备树,就存在“引脚被谁抢走”的问题。尤其是这种从机模式,NSS 引脚必须老老实实进 SPI 控制器,一旦被其他设备或者 GPIO 子系统占用,从机功能就会静默失效,而且不会有特别明显的报错。
影响范围不仅仅是 NSS 这一个脚。SCK、MISO、MOSI 如果被别的节点占走,现象会更早爆发。但 NSS 这个脚比较特殊,它有时候会以 GPIO 方式复用,导致问题更隐蔽。所以我建议所有做 STM32MP2 系列外设开发的工程师,都看一遍这篇文章里的排查思路,省得到时候被“假硬件故障”坑一整天。
2. SPI 从机和 NSS 的基本功
2.1 从机是怎么被“选中”的
SPI 通信里,Master 和 Slave 之间的关系很像开会:Master 是主持人,SCK 是会议室里的时钟铃,NSS 就是会议邀请。只有收到邀请的人(NSS 拉低),才有资格在总线上发言。
从机的内部逻辑很简单:NSS 有效电平到来之前,它内部的所有收发状态机都处于 idle,SCK 上无论来多少时钟都不会理会。NSS 有效的那一瞬间,从机被激活,开始按 SCK 边沿采样 MOSI 或输出 MISO 数据。如果你的 NSS 根本没被正确映射到 SPI 外设,那即使外部电平变化,从机内部也根本看不到。
所以“NSS 无法 claim”这句话,翻译成人话就是:SPI 控制器并没有获得对 PB1 这个引脚的访问权。它既不能读取 NSS 的高低压,也不能控制 NSS 的去抖和边沿检测。
2.2 硬件 NSS 和软件 NSS 的区别
STM32 的 SPI 从机模式支持两种 NSS 管理方式。
第一种是硬件 NSS,也就是 NSS 信号由外部输入到专用引脚,SPI 外设直接读取这个引脚的电平。好处是响应快,不需要软件参与,特别适合一主一从、对时序敏感的场景。坏处就是引脚必须被正确复用,而且外部信号质量要过关。
第二种是软件 NSS,通过配置 SPI_CFG2 寄存器里的 SSOE、NSSP 等位,让从机不依赖外部引脚,而是由软件在合适的时机“假装”收到选中信号。这种方式省掉一个引脚,但时序上会有一定延迟,而且主从双方必须约定好通信窗口。
我在调试的时候优先用的是硬件 NSS,因为 FPGA 作为主机时 NSS 波形非常干净,延迟完全可控。如果硬件 NSS 在引脚申请上出了岔子,切换到软件 NSS 也只能算“能跑”,并不能真正解决根因。
2.3 NSS claim 究竟 claim 了什么
在 Linux 系统里,一个引脚从启动到能被某个外设使用,要走完“pin control”这条路。NSS 引脚也不例外。claim 这个动作,实际上是 Linux pinctrl 子系统在帮 SPI 驱动向 pinmux 模块“登记入住”。
一旦 claim 失败,SPI3 驱动仍然会加载,但 Pin 的 ownership 不在它手里。外设寄存器和物理引脚之间处于“断线”状态。这种失败往往不会导致 SPI3 设备节点不出现,只是功能完全不正常。
简单类比:你订了酒店房间,但门禁卡没有发到你手里,你既进不了房间,也看不到房间状态。表面看一切正常,实际上资源根本不可用。
3. STM32MP257F_EV1 板卡上的 SPI3/PB1 地盘
3.1 从原理图到 pinmux 的必经之路
在 STM32MP257F_EV1 上,SPI3 和 PB1 的关系并不像单片机那么容易看明白。第一件事永远是查原理图,确认 PB1 在板级上到底连到哪里。
我在这块板子上踩过很多次坑,发现不少 EV1 板上的某些引脚会同时引到多个外设,比如一边接扩展座,一边接按键或者指示灯。如果你只盯着芯片手册看 PB1 的 SPI3_NSS 复用编号,而不去确认板级上有没有其他器件占用它,后面一定会出问题。
确认完原理图后,需要做第二件事:看芯片数据手册里 PB1 的 Alternate Function 表格。STM32MP257F 的引脚复用非常多,同一个 PB1 可以复用为 GPIO、SPI3_NSS、某个定时器通道等,选错 AF 值会导致信号完全乱掉。这个 AF 值不能靠猜,必须查手册。
3.2 pinctrl 的 claim 机制
STM32MP 系列跑 Linux,引脚管理统一走 pinctrl 子系统。设备树中每个外设节点通过pinctrl-0指定需要的引脚组,内核启动时,由 pinctrl 驱动把这些引脚从“空闲状态”分配给对应设备。
关键点来了:pinctrl 的分配是排他性的。同一个引脚同一时刻只能被一个设备 claim。SPI3 节点想用 PB1,它得首先通过 pinctrl 框架将自己声明为 PB1 的 owner。
如果同一个 PB1 已经被其他设备的 pin controller 服务节点声明了,SPI3 这边的请求就会失败。而这个失败通常只是在内核 log 里默默出现一行,用户空间程序完全无感。SPI3 驱动可能继续注册成功,但 NSS 引脚实际上停留在“别人家”的状态,永远不会把外部电平送回 SPI 外设。
3.3 PB1 最容易踩的冲突
PB1 这块板子上的一个高频冲突来源是 LED 或者按键。很多开发板为了快速演示,喜欢把默认状态设置为 GPIO 控制 LED 或者读取按键,而 ST 官方设备树里经常会出现gpio-leds、gpio-keys这类节点。
如果那棵设备树里给某个按键或 LED 用了gpios = <&gpiof 5 ...>,但你的系统镜像里又启用了另一个节点用<&gpiob 1 ...>,两者正好指到同一个引脚位置,claim 失败就来了。
另外还有一种冲突是 SPI3 节点自己写了多个 pinctrl state。比如 default 里写了一个“master 模式”的引脚组,sleep 里却要切成“slave 模式”的引脚组,两边都引用了 PB1。一旦 state 切换顺序没有做好,也会出现“当前 state 没有获取到 PB1”的尴尬结果。
4. 排查过程实录
4.1 从 dmesg 里挖第一手线索
遇到这个问题后,第一步不是改代码,而是打开串口终端,抓启动日志。我当时执行了:
dmesg | grep -i -E "spi3|pinctrl|PB1|gpio"日志里出现了类似这样的信息:
pinctrl core: request pin 123 for 5000f000.spi failed pinctrl core: could not request pin 123 on device 50002000.pinctrl这里的5000f000.spi就是 SPI3 控制器,123是芯片内部的引脚编号,具体数值取决于实际 BSP,不一定和 GPIO 号一致。这条日志直接告诉我:SPI3 想申请 PB1 时失败了。
如果日志里找不到,还可以看pinmux相关的完整输出:
dmesg | grep -i pinmux多数情况下,失败原因会连带指出当前占到 PB1 的是哪个节点。如果日志没有写明,那就需要去 pinctrl 的 debugfs 里手动查。
4.2 进入 pinctrl debugfs 确认占用者
挂载 debugfs 后,可以查看系统中所有 pin 的状态:
mount -t debugfs none /sys/kernel/debug cat /sys/kernel/debug/pinctrl/50002000.pinctrl/pins输出里每一行都会标注该 pin 当前被哪个设备占用。重点关注 PB1 这一行,如果 owner 显示的是gpio-leds或者gpio-keys,那问题就实锤了。
还可以看 pinmux 的映射状态:
cat /sys/kernel/debug/pinctrl/50002000.pinctrl/pinmux-pins这会显示该引脚当前处于哪个 function 下、被哪个设备使用。如果 owner 不是spi3,说明 NSS 引脚根本没有归属到 SPI3。
4.3 用 GPIO 调试确认电平通路
在进一步改设备树之前,我习惯先用 GPIO 子系统确认物理引脚没有坏。把 PB1 临时在设备树里解开所有冲突,然后拉成普通 GPIO 输入,读取电平:
echo 97 > /sys/class/gpio/export cat /sys/class/gpio/gpio97/value这里的编号只是示例,实际值要根据板卡的 GPIO 控制器地址和引脚号换算。注意这一步只是为了验证外部电路能不能把电平送到引脚,不能直接验证 SPI 功能。如果电平能正常读到 0 和 1,说明板级电路没问题。
4.4 用逻辑分析仪看物理波形
软件层面确认之后,还要从物理线上排除问题。把逻辑分析仪探头夹到 PB1 上,让 FPGA 主机运行一次拉低 NSS 的操作。
如果逻辑分析仪能看到明显的下降沿,说明外部硬件没问题,问题还是在芯片内部的 pin mux / pinctrl 上。如果看不到下降沿,就要查连接线、焊接、或者跳线帽。
我当时看到的现象是:逻辑分析仪有下降沿,但 SPI3 寄存器里的 NSS 状态位纹丝不动。结合 dmesg 里的失败日志,基本可以断定是 pinctrl 层的 claim 失败。
5. 根因分析与修复方案
5.1 根因一:PB1 被其他设备占用
在我这单问题里,最终定位到设备树中一个gpio-keys节点,它把 PB1 配置成了按键输入,用于检测一个物理按键。这个节点在默认设备树里是开启的,SPI3 再去申请同一根引脚时,pinctrl 直接返回-EBUSY。
解决方式很简单:把gpio-keys节点里与 PB1 相关的配置删除,或者直接在根节点里把该按键子节点status = "disabled"。
修改前先确认这个按键确实没有实际用途。如果按键没接任何外部电路,禁用它是完全安全的。
修改后重新编译设备树,烧写启动,再看 dmesg 就没有 claim 失败日志了。
5.2 根因二:AF 配置成了主模式而不是输入模式
还有一种常见情况是设备树里用了错误的 pinctrl 配置,比如把 NSS 引脚配置成了 SPI3 主模式下的输出引脚,而不是从机模式下的输入引脚。
从机模式下,NSS 是输入信号。pinctrl 节点里必须把它配置成输入模式,并且使能内部上拉/下拉或者保持浮空,取决于具体外部电路。
错误示例:
&spi3 { pinctrl-0 = <&spi3_slave_nss_output>; };正确示例:
&spi3 { pinctrl-0 = <&spi3_slave_nss_input>; };“output”和“input”的区别看似很小,但会导致 NSS 引脚被驱动到固定电平,外部主机的拉低动作根本不起作用。
5.3 根因三:NSS 电气连接悬空
在从机模式下,如果外部 Master 没有连接,或者 NSS 线路上没有上下拉,NSS 引脚会悬空。SPI 外设内部虽然有施密特触发器,但悬空状态下输入很容易受到干扰,表现为 NSS 状态随机翻转,或者始终无法稳定 claim。
这种情况下,需要检查原理图。通常从机 NSS 在空闲时应保证为高电平,建议在外部加一个 10kΩ 上拉电阻到 3.3V。这样主机没有驱动时,NSS 稳定在高电平,从机不会被误选。
不过这个根因在“Fails to claim”里不是最常见的原因,做排障时还是要先看 dmesg,再查电气。
5.4 修复后的设备树写法
以 Linux 下把 SPI3 配成从机模式为例,设备树里至少需要以下几个元素:
&spi3 { pinctrl-names = "default", "sleep"; pinctrl-0 = <&spi3_slave_nss_input>; pinctrl-1 = <&spi3_slave_nss_sleep>; spi-slave; slave@0 { compatible = "st,stm32-spi-slave-dummy"; reg = <0>; spi-max-frequency = <10000000>; }; };spi-slave;这行是关键,它告诉 SPI 控制器驱动,这个外设不是作为 Master 使用,而是作为 Slave 使用。如果没有这一行,驱动会尝试以 Master 方式初始化,NSS 引脚的状态反馈逻辑完全不同。
slave@0子节点可以理解成一个“虚拟从设备设备”,用来让用户空间通过 spidev 或特定协议访问从机数据。具体 compatible 取决于你的 BSP 里有没有自带 slave 测试驱动。
5.5 软件 NSS 方案作为兜底
如果 pinctrl 冲突实在来不及解决,比如硬件上必须保留某个功能在 PB1 上,可以临时切换到软件 NSS。
软件 NSS 模式下,NSS 引脚不参与 SPI3 的选中逻辑。从机在使能接收之前,由软件直接触发内部选中。这个方案适合测试验证,但不太适合有严格时序要求的真实通信。
在软件 NSS 下,设备树里可以省略 NSS 的 pinctrl,直接把管脚留空,或者只配置 SCK/MISO/MOSI 三个脚。同时需要在驱动或应用层里做好“什么时候开始通信”的约定。
我个人的建议是:软件 NSS 只作为临时兜底,不要把这种方案带到产品里。因为 SPI 从机本身依赖外部时序,软件介入选中逻辑会引入不确定延迟,调试起来非常痛苦。
6. 验证与踩坑经验
6.1 验证从机是否真正被“选中”
修好设备树后,不能只看 dmesg 没报错就收工。要让 FPGA 主机实际发起一次通信,同时用示波器或逻辑分析仪抓 PB1、SCK、MISO、MOSI 的波形。
关键判据是:PB1 拉低后,SPI3 状态寄存器中 NSS 标志位应该立刻跟着变化。如果能在示波器上看到 SCK 边沿和 MISO 输出紧跟 NSS 拉低,说明从机真的被选中了。
另外可以尝试在从机端设置一个超长数据包,故意让主机连续读写,看看是否有数据被正确接收和发送。先用短数据包,等链路稳定后再加大包长。
6.2 值得记住的排查命令
整理几个我觉得在 STM32MP2 系列上排查引脚问题特别有用的命令:
# 查看 SPI 设备是否注册成功 ls /dev/spidev* # 查看 SPI 控制器状态 cat /sys/bus/platform/devices/*spi*/of_node/name # 查看 pinctrl 状态 cat /sys/kernel/debug/pinctrl/*/pinmux-pins # 查看 pin 的 owner cat /sys/kernel/debug/pinctrl/*/pins # 实时抓 pin 状态 cat /sys/kernel/debug/gpio当你遇到“外设加载了,但功能不对”的情况,优先用 debugfs 查 owner,不要一开始就怀疑寄存器配置。
6.3 几条个人心得
在高性能 MPU 上做 SPI 从机,第一原则是先确认引脚所有权,再谈外设配置。
第二原则是别迷信自动生成代码。CubeMX 生成的是底层初始化,但在 Linux 环境下,设备树里的 pinctrl 才是真正的“全局仲裁者”。两边必须保持一致,否则就会出现“初始化里配了 AF,但设备树根本没分配给 SPI3”的冲突。
第三原则是善用示波器和逻辑分析仪。这种问题靠肉眼是看不出来的,SPI 从机只有在 NSS 被正确拉低后才会响应,一切判断都要建立在真实的物理波形上,而不是只看软件寄存器。
这次调完 SPI3 Slave 模式后,我把设备树里所有与 PB1 相关的节点都重新过了一遍,顺便把其他外设的引脚占用表也梳理了一遍。踩过的坑记录下来,下次遇到类似问题能省一大半时间。