见过不少 SPI 调试的怪事,但 CS 线能正常拉低、SCK 却纹丝不动,依然是我觉得最有迷惑性的故障之一。最近在 STM32MP257F-DK 上又遇到一次,而且是在防火墙已经开了、时钟也确认在跑的情况下,单片机里读状态一切正常,示波器却看不到时钟脉冲。这种问题如果在普通 MCU 上,多半是初始化顺序或寄存器配置漏了位;但到了 STM32MP2 这种 Cortex-A35 加 Cortex-M33 的异构多核平台,原因更多也更隐蔽。这篇文章就把这次排查的完整思路、踩过的坑和一套可以直接照做的检查流程整理出来,希望能帮到被类似现象卡住的人。
MP257F-DK 这块板子并不是普通意义上跑裸机代码的 MCU。芯片内部有双核 Cortex-A35 和单核 Cortex-M33,外设通过资源隔离框架 RIF 来划分访问权限,典型的安全属性和非安全属性、CID 过滤、总线主设备过滤,都会影响一个外设到底能不能被正确操作。标题里那句 firewall open 听起来好像问题不大,但在 STM32MP2 上,“打开防火墙”往往不是一个开关,而是一组属性的组合配置。更别提 SPI 这类外设,它同时依赖总线接口时钟、内核时钟、引脚复用和控制器自身状态,任何一个环节不到位,都会出现“寄存器看起来在工作、引脚却没有输出”的现象。下面我就从实际调试的顺序出发,把每个值得检查的点都过一遍。
1. 现象放在多核平台上,就不只是 SPI 控制器的事
1.1 问题现象还原
先说现象。我这边通过调试器把 SPI 初始化代码执行完,GPIO 配置里 CS 为普通推挽输出,SCK、MOSI、MISO 都配成对应的复用功能。初始化完成后,人为把 CS 拉低,用示波器量 CS 引脚,电平确实从高拉低,说明引脚控制链路是通的。但量 SCK 引脚时,任何时刻都看不到脉冲,要么一直保持低电平,要么一直保持高电平,仅仅在 CS 拉低期间一动不动。
为了排除是我对触发点理解错了,我量了整个通信窗口。CS 下降沿之后等了几十微秒,SCK 没有任何波形;CS 上升沿之后也没有。如果把示波器时基拉到几百微秒甚至毫秒级,依然看不到任何翻转。这说明不是“时钟频率太高没抓到”,而是 SCK 引脚真的没有时钟活动。我当时甚至怀疑是测试点接触不良,换了一根探头、换到板子背面引脚再量,结果还是一样。
这种“CS 正常但 SCK 完全无输出”的现象,放在普通 STM32 MCU 上,我会先怀疑 GPIO 复用没配好,或者 SPI 外设没使能,再或者主从模式配置错了。但在 MP257F-DK 上,这几个怀疑点都要再加一层多核访问控制的判断,因为即使代码跑起来了,外设也可能根本没有接收到你想写入的配置。这种“代码执行成功、外设没反应”的割裂感,在普通 MCU 上很少见,在带安全隔离的 MPU 上却是家常便饭。
1.2 寄存器读到了,不代表引脚能输出
多核平台上最容易出现的误导,是调试器连接上去后,我能读到 SPI 的寄存器,CR1 里的 SPE 位是 1,SR 里的 TXE 标志也能置位,看起来外设工作正常。但寄存器能访问只说明 APB 总线接口时钟是通的、访问权限没有被总线层完全挡住。SPI 内部要真正产生 SCK,还需要两个独立条件:一是 SPI 内核时钟(用于产生波特率)已正确供给,二是 SCK 引脚本身被正确的复用控制器驱动。
尤其要小心访问被部分屏蔽的情况。MP2 的 RIF 有安全属性和 CID 过滤,如果某个代码运行时对外设寄存器只能读不能写,读出的寄存器值可能仍然是复位默认值或上次配置值,看起来就像配置过了。这种情况下,外设内部状态机可能压根没启动,但寄存器视图骗过了你。我的体会是:在这种平台上,寄存器只能作为辅助证据,不能作为“外设正常”的充分条件。所以下面的排查,每一层都要直接确认物理结果,不能只看寄存器。
1.3 排查方向总览
我这次排查基本按五层推进:第一层确认 SPI 的时钟输入,第二层确认 RIF 访问控制,第三层确认 GPIO 复用,第四层确认 SPI 控制器配置,第五层确认当前处理器主域和软件栈的干扰因素。下面每层都会给出具体的检查方法和典型现象。如果你也遇到 SCK 不动、CS 却正常的问题,建议按同样的顺序做,而不是先从控制器寄存器开始猜。因为顺序错了,很容易在某一层反复绕圈,最后发现根因在完全不同的位置。
2. SPI 的“时钟”到底指什么:PCLK 和内核时钟别混着算
2.1 一个外设、两个时钟输入
SPI 外设并不只有一路时钟。很多嵌入式工程师习惯了“打开外设时钟”这个动作,认为只要 RCC 对应位置 1,外设就有电、就能跑。实际上大多数 MCU 的 SPI 外设至少有两路时钟输入:
- 总线接口时钟 PCLK:来自 APB 总线,负责寄存器读写、中断逻辑、状态标志。这一路如果不开启,SPI 的寄存器根本读不到,写不进去,程序可能直接 HardFault 或读到全 0。
- SPI 内核时钟 Kernel Clock:负责波特率发生器、移位寄存器、时钟输出逻辑,决定 SCK 频率。这一路由 RCC 中的专用选择器和分频器控制,可以来自不同的 PLL、HSE、CSI 等时钟源。
标题里说 “clocks running”,很多人以为指的就是这两路都确认过了。但我见到的大量案例是:只确认了 PCLK 使能,没确认 SPI 内核时钟选到了真正有效的时钟源,或者内核时钟分频器被配成了 0,导致 SCK 频率计算不出来。寄存器照样能读写,可 SPI 不会产生任何时钟脉冲。就好比你给一台发动机通了电,油路却没接,仪表盘当然能亮,但轮子就是不动。
2.2 内核时钟选错或没分频时会发生什么
STM32MP257F-DK 的 RCC 比普通 MCU 复杂很多。SPI 的 kernel clock 不会自动跟随 PCLK,它通常有一个时钟源选择位域和一个分频位域,需要你把系统里某个 PLL 的输出、HSE、CSI 或者其他时钟源接到 SPI 上。如果这个选择器复位后的默认值是“无时钟”或“已关闭的 PLL”,那无论怎么改 APB 使能位,SCK 都不会动。
这里有个更隐蔽的情况是“异步时钟模式”分频。SPI 内核时钟里有时会有额外的分频逻辑或门控逻辑,如果你配置的分频值为 0,某些实现可能直接关闭时钟输出而不是按最大值分频。文档里写的是 Divide 0 保留或不可用,但驱动代码没做防御,实际寄存器写入了 0,结果就是没有 SCK。遇到 SCK 完全无输出的情况,我都会先查一遍 kernel clock 选择器、分频器和门控使能位,把这三个位域全部读出来,而不是只看 RCC 里有没有打开 SPI 对应位。这种细节在 MCU 上几乎不用关心,在 MP2 上却经常决定成败。
2.3 怎么确认时钟配置真的落到寄存器里
我的做法是:在 SPI 使能之前,先用调试器读回 RCC 中 SPI 相关寄存器,确认四件事:
- APB 总线时钟使能位为 1。
- SPI 内核时钟源选择位域不是 0,且指向的 PLL/HSE/CSI 确实已在运行。
- 内核时钟分频位域是一个非 0 的合理值,目标 SCK