ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32MP257 SPI3从机NSS失效排查:ETZPC外设所有权问题详解

STM32MP257 SPI3从机NSS失效排查:ETZPC外设所有权问题详解 1. 问题现场SPI3从机“装死”NSS拉了等于没拉先交代一下我当时的处境。板子是STM32MP257F_EV1跑的工程在Cortex-M33裸机侧需要把SPI3配成从机模式NSS引脚用PB1。主控端是一个SPI主机正常工作时会周期性拉低CS片选然后发几个字节的数据过来。按说从机这边只要NSS上出现下降沿SPI外设就应该立刻进入被选中状态接收FIFO开始攒数据状态寄存器里对应标志位也会跳起来。可我实测的结果是主机那边CS波形明明已经拉下去了PB1上也测到了干净的低电平从机这边却毫无反应。读状态寄存器没有任何数据接收标志FIFO始终是空的BUSY位也一直没亮过。换句话讲NSS pin根本没有claim住这次片选事件。我最初怀疑是硬件连接问题毕竟“波形到了但外设不认”这种故障第一反应都是查线。我用示波器反复量了PB1到主机CS输出的连线通断正常电平幅值正常上升沿下降沿也都不拖泥带水。又把SPI3的SCK、MOSI、MISO三条线全部检查了一遍确认和主机侧是同一组信号。硬件看起来没有任何毛病问题一定出在芯片内部配置上。然后我花了大半天时间把SPI3所有相关寄存器从头到尾捋了一遍。IOMUX引脚复用选择AF功能没问题GPIOB端口时钟打开了SPI3外设时钟在RCC里也置位了SPI_CR1的MSTR位清0SPI使能位SPE置1从机模式方向配置正确。每个位我都是对着参考手册逐项核对过的逻辑上完全说得通但外设就是不干活。这种感觉最折磨人——你明明按标准答案填的卷子老师却给你判了零分。后来我才意识到在STM32MP257F这种MPU平台上外设调试的思路和传统MCU有个很大的区别不是“你写了寄存器就该生效”而是“你先得确认这个外设归你管”。这套平台里有Cortex-A35和Cortex-M33两个核心还有一整套总线资源隔离机制。如果某个外设被默认分配给了A35侧的Linux系统那M33这边对它做任何配置都是白费力气。这个问题就是我这篇文章要讲的根因。2. 从机模式下NSS claim的底层机制先搞懂它2.1 NSS在从机模式下的角色输入检测不是输出控制很多人一提到NSS引脚脑子里跳出来的第一个画面是主机用它做片选输出这没错。但放到从机模式下NSS的角色就完全变了——它必须作为一个输入引脚接收来自主机的片选信号外部主机拉低它的那一瞬间从机内部就要产生一个事件把自己切换到“被选中”的状态。这一点必须从根上想明白。SPI通信本身是主从架构主机掌握时钟和通信节奏从机则是被动的它靠什么知道“现在该轮到我应答了”靠的就是NSS引脚的边沿和电平状态。在硬件NSS模式下外部主机把NSS拉低从机SPI外设的状态机就认为一次传输开始后续SCK上的每个时钟脉冲都会被当成有效数据接收。一旦NSS拉高从机就结束本次会话等待下一次下降沿。所以“NSS claim”这件事本质上是两个信号层面的事件第一NSS引脚上必须出现从高到低的电平跳变第二SPI外设内部必须识别到这个跳变并翻转自己的状态机。如果引脚上的波形很好、电平干净但外设内部状态机没反应那问题就出在“外设内心”而不是引脚上。2.2 SPI_CR1和SPI_CFG1中几个决定NSS命运的位我调试SPI的时候发现真正决定NSS能不能正常claim的往往集中在几个不太起眼的控制位上。以STM32MP257F这套SPI外设为例最需要关注的有这么几个MSTR位SPI_CR1主从模式选择从机模式必须为0。这个大家都在意一般不会错。SSOE位SPI_CR1从机选择输出使能。这个位的含义要仔细体会。在从机模式下SSOE一旦被置1NSS引脚就不再是纯输入了外设会尝试用内部控制信号去管理这个引脚此时外部主机的片选信号根本无法真正触达从机内部逻辑。换句话说这个位如果配错了外部主机把NSS拉得再低从机也“看不见”。SSI位SPI_CR1内部从机选择。它和SSOE配合使用当SSOE1时NSS状态由SSI决定只有当SSOE0时NSS才能作为外部输入。这一点特别容易被忽略。MSSE位SPI_CFG1主从选择输入使能。在老一些的STM32上这个字段可能不叫这个名字但功能是类似的——它控制NSS引脚是否真的被用作从机选择输入。我当时检查配置的时候MSTR、SPE这些显眼的位置全都对唯独SSOE和MSSE这两个容易被忽视的字段我起初只是扫了一眼没太较真。后来仔细一看配置里SSOE确实是0MSSE也置了位这让我很困惑因为从纸面配置来看从机的NSS输入路径应该是通的。2.3 什么才叫“claim”状态寄存器怎么看出来如果你也在调试类似问题建议先用状态寄存器来量化“到底有没有claim”。STM32MP257F的SPI状态寄存器里有几个位值得重点关注BUSY位SPI通信忙标志。从机被NSS选中且接收或发送过程中BUSY会置1。如果主机拉了CS且发了SCK但BUSY始终为0说明从机SPI状态机根本没启动。RXP位接收FIFO非空标志。从机收到数据后RXP会被硬件置1如果FIFO里有数据但你没读也可以从RXNE/RXP看出来。EOT位传输结束标志。NSS拉高或者SPI禁用时EOT可能置位表示一次传输结束。我的判断方法是用调试器实时去读SPI_SR寄存器。主机每拉一次CS、发一组数据我就看一次状态。如果RXP和BUSY都不动说明从机SPI外设完全没感知到外部活动。这时候基本可以锁定期望不是电气问题就是外设内部控制链路断了。2.4 软件NSS与硬件NSS很多人在这里栽跟头再来聊一个容易混淆的点。SPI的NSS管理分为软件模式和硬件模式体现在寄存器配置上就是SSOE和SSI的组合关系硬件NSS模式SSOE0NSS引脚作为输入受外部电平控制。从机模式下这是最常用的配置外部主机的CS信号可以正常触发从机选中。软件NSS模式SSOE1NSS引脚脱离外部控制从机是否被选中完全由内部SSI位决定。在这种模式下外部CS信号拉不拉低都没意义从机只认SSI位的值。有很多人在配置从机模式时直接把主机那边的一套配置抄过来SSOE置1然后发现外部CS无效就是这个原因。如果某些情况下你确实想用软件NSS那就得手动控制SSI位来模拟片选时序。但在常规主从通信场景里硬件NSS是最省心、最可靠的选择。3. 排查全链路从“读寄存器”开始发现所有值都不对劲3.1 第一步读回SPI3的寄存器竟然读出一串0硬件连接没问题配置看起来也对那我就开始做寄存器级别的读回验证。调试器连上去直接读SPI3的SPI_CR1、SPI_CFG1、SPI_SR。按理说这些寄存器里应该能看到我配置进去的位。结果让我有点懵SPI_SR里该置位的位确实置位了比如SPE使能看起来SPI3的寄存器不是全0功能配置基本符合预期。但问题在于SPI_SR里跟状态相关的位比如BUSY、RXP、EOT一直没有任何变化。主机那边每来一次片选和数据传输这边状态寄存器纹丝不动。这说明SPI外设的时钟域是活的寄存器能读能写但接收路径完全没有工作。这里我要特别强调一个经验寄存器能写能读不代表外设功能就正常。很多时候总线访问和外设逻辑是两回事。你写进去的每个控制位外设可能会“笑纳”但并不代表这些控制位已经对内部逻辑生效了。尤其在外设被隔离或时钟异常的场景下这种“半死状态”特别常见。3.2 第二步查RCC时钟发现SPI3时钟没真正使能既然SPI外设寄存器的状态在动那下一步是查时钟。SPI3的时钟由RCC控制在STM32MP257F这种芯片上外设时钟的管理比传统MCU复杂得多既要看RCC里对应的时钟使能位还要看这个时钟位是否可写。我去读了RCC里SPI3对应的时钟使能位你猜怎么着那个位的状态始终不对。我明明在初始化代码里把SPI3时钟打开了但读回来却无法确认它处于使能状态。反复写了几次寄存器值都没有按照预期翻转。这是一个非常关键的信号。在传统MCU上RCC寄存器位写了一般都会马上反映在寄存器里但在MPU平台上RCC的访问也受到安全隔离控制。如果当前上下文没有权限操作这个时钟位写入动作会被总线直接忽略表现出来就是“写不进去”。时钟没使能那后面SPI外设再怎么说也是空中楼阁。3.3 第三步在MPU上外设所有权才是“隐藏关卡”直到这一步我才把思路从“SPI配置”转向“资源所有权”。STM32MP257F是异构双核架构Cortex-A35上跑的是OpenSTLinuxCortex-M33上跑的是裸机或RTOS工程。这两个核心共享一大堆外设但外设本身却需要通过ETZPCExtended TrustZone Protection Controller来分配归属。ETZPC的作用简单理解就是它决定某个外设由哪个上下文安全/非安全、A35侧还是M33侧来访问控制。如果一个外设被分配给A35侧那M33侧发起的所有访问要么被忽略要么被拦截。这不是配置错误而是系统级的安全隔离策略。这个机制我记得是STM32MP系列从第一代MP1开始就有的设计。在MP157上我就遇到过类似问题——某个UART在Linux启动后就被A7侧接管M4侧怎么配置都没有输出。到了MP257上这个机制不仅保留了而且隔离粒度更细。SPI3这种常用接口默认情况下很可能就是分配给Linux侧的。我在M33代码里的所有努力都像是在“隔空操作”一个不属于自己的外设。当时我在调试器里反复确认SPI3控制寄存器的每一个位已经确信配置完全正确却没想到问题的根源根本不在SPI配置本身而在更上一层的“这个外设到底听谁的”这个问题上。如果你也在调STM32MP系列一定记住寄存器看着能读能写不代表这个外设真的归你管。3.4 第四步ETZPC配置核对果然踩中了有了第三步的推断我马上去查ETZPC寄存器。STM32MP257F的参考手册里有一张ETZPC外设分配表里面详细列出了每个外设对应哪个DECPROT位组。SPI3在表格里对应的是一组4位的配置字段用来指定它的访问权限归属。我读了一下ETZPC里SPI3对应的配置位结果证实了我的怀疑SPI3被分配给了非安全上下文也就是Cortex-A35侧。M33侧即便是在安全世界运行也需要通过ETZPC正确配置之后才能真正对SPI3做完整的控制。这个配置不解决SPI3在M33这边就是一个“看得见但摸不着”的影子外设。这里我顺便说下STM32MP2上ETZPC位组的含义。每个外设通常有4位控制字段其中既有“安全/非安全”的区分也有“是否允许非安全访问”的权限位。对于SPI3这种被Linux占用的外设如果你希望M33完全接管正确做法是把SPI3从A35的域里“拿出来”交给M33所在的安全域或非安全域同时调整A35侧的设备树让Linux不要再去碰它。4. 修复方案把SPI3从Linux侧释放给M33并重新验证4.1 方案A在M33启动阶段正确配置ETZPC如果你和我一样M33工程是裸机或者基于主流的RTOS那可以在M33启动早期通过ETZPC把SPI3的所有权重新分配过来。具体操作分两步。第一步确认SPI3在ETZPC中的地址和外设编号。以STM32MP257的参考手册为准每个外设对应固定的DECPROT寄存器及位段SPI3是哪一组手册里写得清清楚楚。第二步往对应的DECPROT位段写入允许M33访问的配置值。需要注意ETZPC本身可能有锁定寄存器如果SPI3的控制位在系统启动时已经被锁定那运行期修改不会生效。这时候你需要在启动代码的非常早期阶段在可能的锁定操作之前完成设置。如果启动加载器或ROM代码已经做了一些默认分配你就得从启动链头上去改ETZPC的初始化配置。我当时是在M33的SystemInit阶段增加了ETZPC的配置代码核心逻辑就是找到SPI3对应的DECPROT寄存器把它的工作模式调成“允许Cortex-M33访问”。这一步做完之后再去读RCC的SPI3时钟使能位就能正常置位了寄存器反应完全不一样。4.2 方案B修改设备树让Linux让出SPI3如果你手上用的是OpenSTLinux自定义M33固件的组合还应该从Linux侧做配合。默认的设备树里SPI3大概率是被开启的Linux启动时会初始化它并且把对应的引脚复用占上。这个状态下M33怎么操作都不可能拿到完整的所有权。正确做法是在设备树里把SPI3节点状态改为禁用同时把PB1对应的pinctrl占用也删掉。Linux启动后不会初始化SPI3引脚也会被释放出来这样M33侧才有机会完整接管。设备树修改类似这样spi3 { status disabled; }; pinctrl_spi3 { status disabled; };当然具体节点名和pinctrl定义要根据你实际使用的内核版本和设备树源文件来调整。改完之后重新编译设备树刷到板子上再验证一下。如果SPI3在Linux那边还挂在了某个驱动上光改status可能不够还需要保证相关的驱动不再probe。总线上的设备节点如果引用了SPI3也要一并处理否则Linux启动阶段可能报错或者仍然尝试访问。4.3 验证NSS claim恢复正常完整测试记录两种方案实施完毕后我在M33工程里重新启动SPI3从机配置并用调试器进行验证。结果是立竿见影的SPI3的RCC时钟使能位正常置位读回值为1。SPI_SR寄存器中主机拉低CS后BUSY位立即置1。主机发送0x5A从机接收FIFO里马上就出现了0x5ARXP标志正常置位。主机拉高CS后BUSY清零EOT标志位置位一次完整传输结束。我还特意用示波器同时抓了PB1、SCK、MOSI三路信号PB1的下降沿和SPI_SR中BUSY位置位的时间点完全同步这说明从机外设已经能正确识别外部片选事件了。NSS claim不再是问题从机通信链路完全打通。这里有一个很有价值的调试小技巧验证从机NSS是否正常工作不一定要接真实主机。你可以用另一路GPIO手动模拟片选信号——先把PB1拉到低电平然后在SCK上手动敲几个脉冲再去读SPI_SR和接收FIFO。如果这样从机都能正确收数据说明NSS claim和接收路径都通了。这个方法在实验室里排查问题非常高效。5. 一次调通之后MPU平台外设调试的几道“隐形门槛”5.1 所有权检查清单不只SPIUART/I2C/GPIO同理这次SPI3的踩坑经历让我把MPU平台外设调试的思路彻底刷新了一遍。以前在单核MCU上写好配置就能跑几乎不用考虑“这个外设是不是我的”这种问题。但在STM32MP257F这种异构MPU上“外设所有权”是必须排在第一位检查的事项。一个重要经验如果你在M33侧的代码里写寄存器无论怎么配置读回状态都没有变化或者配置本身就无法持久化首先要去查ETZPC分配表确认这个外设是否在该上下文的访问域内。这个道理不仅适用于SPIUART、I2C、GPIO、定时器所有共享外设都一样。我自己后来整理了一个排查清单遇到类似问题按顺序过一遍省下很多无用功外设在ETZPC中属于哪个上下文当前代码运行在哪个上下文对应外设的RCC时钟使能位是否可写、是否真的置位成功外设寄存器的关键控制位读回值和预期是否一致引脚对应的GPIO bank和AF功能配置是否真的生效外设中断是否到达了正确的中断控制器和处理器核心这套清单在MP1上帮我解决过UART无输出的问题在MP2上又帮我解决了这次SPI3从机的问题。每次看似复杂的玄学故障追到根上基本都是所有权的坑。5.2 GPIO锁、复用冲突与第二路由除了ETZPCMPU平台上还有一个隐蔽的坑引脚复用冲突。当两个核心都想用同一个引脚时某个核心配置的AF可能会被另一个核心覆盖。M33这边把PB1配成SPI3_NSS的AF如果Linux侧把同一个引脚配成了GPIO输出等到Linux启动之后引脚功能就可能被Linux侧的配置冲掉。所以调试NSS claim问题时我不仅检查了SPI寄存器还反复确认了PB1当前的引脚状态。确认GPIOB口对应的AF寄存器确实是SPI3_NSS功能并且没有被其他上下文改写。在MPU平台上这种“两个核心抢一个引脚”的情况虽然不多但一旦发生现象往往就是“配置明明对的就是死活不工作”。另外STM32MP257的GPIO控制器还有个特性GPIO配置寄存器本身也可能有安全属性决定某个bank的配置寄存器由哪个上下文访问。如果GPIOB被设为仅允许非安全访问而M33运行在安全状态那你设置的AF配置也可能无效。所以当SPI配置看起来完全正确却始终不工作时不要只盯着SPI寄存器GPIO那侧的配置权限也要一并排查。5.3 我还会保留的习惯最小复现程序寄存器级可观测性经过这次调试我给自己立了两个规矩。第一个规矩是在MPU平台上调试外设永远先写一个最小复现程序。不要在你的完整工程里去找问题因为完整工程里既有M33的RTOS调度又有A35侧Linux的并发干扰变量太多了。单独拉一个M33工程只做一件事配置SPI3从机、等待NSS、接收数据、写测试标志。这样排除干扰问题定位会快很多。第二个规矩是保持寄存器级可观测性。不管用调试器还是串口打印都要能实时看到SPI_SR、RCC状态、ETZPC配置这些关键信息。很多问题不看寄存器是永远猜不出来的。“现象正常但实际不正常”和“现象不正常但寄存器很诚实”这两种情况在MPU调试中特别常见。寄存器不会骗你它比你的直觉可靠得多。另外如果你用的IDE支持外设寄存器视图一定要用起来。在调试运行时直接观察SPI3相关寄存器组的实时变化比我当初反反复复打印日志的效率高太多了。尤其是BUSY位、RXP位这种实时状态位在寄存器视图里用肉眼观察变化判断“到底有没有claim”会非常直观。
返回列表