STM32MP257 SPI调试:CS正常但SCK无输出的排查与修复
2026/8/30 4:04:44 网站建设 项目流程

最近在 STM32MP257F-DK 上调 SPI 通信的时候,遇到一个很典型但不好查的问题:SPI 的 CS 片选信号每次传输都能正常翻转,但逻辑分析仪挂在 SCK 时钟引脚上,从头到尾一条脉冲都没有。更让人头大的是,RCC 里 SPI 时钟明明在跑,ETZPC 防火墙也确认开放了该外设的访问权限。所有常规检查项都是绿的,SCK 就是不工作。

这个现象其实非常有价值。CS 能翻转,说明“发起传输”这个动作本身是成功的——要么是 SPI 控制器进入了传输流程主动拉低了硬件 NSS,要么是某个 GPIO 被软件正确拉低了。可 SCK 作为主模式的核心输出信号完全死寂,问题几乎可以锁定在 SCK 输出链条上的某个特定环节,而不是整个 SPI 外设瘫痪。顺着这个思路往下查,比从头把所有配置推倒重来快得多。下面把我这次在 STM32MP257F-DK 上的完整排查过程和解决方案整理出来,给同样踩坑的朋友一个参考。

1. 先搞懂 STM32MP257F-DK 上 SPI 的“放行链路”

1.1 CS 翻转到底能说明什么

不要急着改代码,先把已有现象的价值榨干。CS 翻转这个事实,在 SPI 系统里至少能证明三件事。

第一,主控端确实在尝试发起传输。无论是对/dev/spidevX.Y发起了一次 read/write,还是裸机代码里对 SPI 的 TX FIFO 写了一笔,CS 电平的变化都说明传输触发逻辑已经走到了“拉低片选”这一步。

第二,CS 引脚的 GPIO 控制器和基础时钟是通的。CS 翻转不是凭空来的,如果 GPIO 的 PCLK 没开、引脚模式配置错误、输出驱动没使能,CS 根本不会动。所以至少这个引脚的底层链路是好的。

第三,如果 CS 用的是 SPI 控制器的硬件 NSS(也就是片选引脚复用到了 SPI 的 NSS 功能上),那说明 SPI 控制器的寄存器写入至少在部分上是生效的,状态机没有被完全锁死。

这点很重要。硬件 NSS 的输出由 SPI 控制器的传输逻辑驱动,它能在 CS 上产生时序,说明控制器内部已经进入了工作状态。可 SCK 完全没动静,这就不太可能是“软件忘了发传输”这种低级问题,而应该往时钟源、引脚复用、权限隔离这些偏硬件链路的方向去找。

1.2 STM32MP2 的 SPI 和普通 MCU 上的 SPI 不是一回事

用过 STM32F4、STM32H7 的朋友对 SPI 的套路很熟:RCC 打开外设时钟,GPIO 配置成复用功能,然后 HAL_SPI_Init 就能干活了。但在 STM32MP257F 上,这套惯性思维会坑人。

STM32MP257F 是双核 Cortex-A35 加单核 Cortex-M33 的异构处理器。SPI 外设可以挂在 A35 侧的 Linux 下,也可以挂在 M33 侧的裸机或 RTOS 下,还可能是两侧共享。为了让这种共享安全可控,SoC 里加了两层普通 MCU 没有的关卡。

第一层是 ETZPC(Extended TrustZone Protection Controller),负责把外设资源分配给 secure 域或 non-secure 域。Linux 通常跑在 non-secure,M33 的 RTOS 或裸机可能跑在 secure。如果 SPI 被 ETZPC 配置成了 secure-only,Linux 侧去访问 SPI 寄存器时,总线事务会被硬件静默拦截。所谓“静默”,就是你写寄存器读回来像是成功了,但实际是影子值,硬件根本不认。

第二层是 RCC 内部的时钟门控和复位控制也被纳入了 TrustZone 管控。某些时钟使能位、复位释放位只允许 secure 域写。如果安全配置没放开,Linux 驱动去clk_prepare_enable时可能得到一个已使能的假象,但硬件时钟树里那个节点终究没通。

这两层拦截有一个共同特点:它们都不影响 GPIO。CS 如果是普通 GPIO,完全不受 ETZPC 和 RCC 安全位的限制,照常翻转。于是就会形成“CS 活得很好,SCK 完全没信号”的割裂现象。这就是为什么光看防火墙开放、时钟在跑,问题依然存在。

1.3 为什么“防火墙打开 + 时钟运行”仍然不够

在 STM32MP2 上,“防火墙打开”不是一个全局开关。以 SPI2 为例,你需要在多个配置层面同时放行,它才能真正属于当前运行域。

  • Linux 设备树中的访问控制属性,比如access-controllers = <&etzpc 对应ID>
  • OP-TEE 编译时内置的设备树。OP-TEE 启动时会用自己的安全策略覆盖外设分配,如果你只改了 Linux DTS 而没有同步 OP-TEE 的配置,启动后 SPI 照样被锁在 secure 域。
  • RCC 中对应的时钟使能位是否被安全域锁定。
  • 如果有运行时重新配置的脚本或者 U-Boot 环境变量覆盖了原有设置,那还要把这一层也考虑进来。

同样,“时钟在跑”也要看是哪一级在跑。SPI 的 APB 接口时钟只是用于寄存器读写,SCK 的产生依赖的是 SPI 内核时钟(Kernel Clock)。如果内核时钟没有被正确选择、PLL 没有锁定、分频链路配置错误,寄存器读着正常,SCK 也照样出不来。

2. SCK 输出链条:从控制器内部到引脚

2.1 主模式是 SCK 输出的大前提

SCK 只有在 SPI 配置为主模式时才会由控制器主动驱动。如果 CR1 或 CFG 寄存器里的 MSTR 位是 0,SPI 工作于从模式,SCK 引脚作为输入存在,自然不会有任何时钟输出。

这个原因听着很蠢,但真的经常发生。裸机初始化时 HAL 结构体里的 Mode 字段写成了SPI_MODE_SLAVE,或者初始化函数返回错误后配置被回滚,都会造成这个结果。在 Linux 设备树里,如果 SPI 控制器的状态不是okay,或者驱动 probe 失败,SPI 可能根本没有被正确初始化,但 CS 如果被一个独立的 GPIO 控制,它依然会翻转。

判断方法很简单。如果是裸机,读一下 SPI 控制寄存器,确认 MSTR 位是否为 1。如果是 Linux,先确认/dev/spidevX.Y设备节点是否存在,以及/sys/class/spi_master/下有没有对应的 master。节点不存在,说明驱动就没绑定成功,后面的一切都无从谈起。

2.2 内核时钟和 APB 接口时钟,两个都要活着

SPI 外设其实有两个时钟域。一个是接口时钟,也叫 PCLK,用于访问 SPI 的寄存器;另一个是内核时钟,也叫 KCLK,用于驱动 SCK 产生、移位寄存器等功能。

这两个时钟在某些 MCU 上绑在一起,一个使能就都通,但在 STM32MP2 这样复杂的时钟树上,它们可能来自不同的 PLL 或分频器。如果只开了接口时钟,寄存器读起来完全正常,但 SCK 生成逻辑没有任何时钟源,一个脉冲都出不来。

在 HAL 代码里,除了__HAL_RCC_SPI2_CLK_ENABLE()使能接口时钟外,还需要检查时钟选择寄存器的配置,比如__HAL_RCC_SPI2_CONFIG()选择正确的内核时钟源。有些移植过来的参考代码会漏掉这一句,或者把时钟源选到了一个 PLL 输出无效的分支上。

Linux 下确认这个状态很直观。在目标板上执行:

cat /sys/kernel/debug/clk/clk_summary | grep spi

如果spi2_k这一行显示enable_count为 0,或者频率为 0,那么这个时钟链路就没通。正常工作的 SPI 节点,enable_count至少是 1,频率应该落在合理范围内。

2.3 ETZPC 的“部分放行”才是最隐蔽的坑

ETZPC 的保护粒度可以精确到单个外设实例。ST 的参考实现里,每个外设节点在设备树中通过一个 ID 关联到 ETZPC 的配置项。这个机制本身不复杂,复杂的是它会跟 OP-TEE 的启动策略交互。

很多时候你改了 Linux 设备树,觉得防火墙已经打开了。但 OP-TEE 启动时读的是它自己内置的 device tree 和安全策略,不会去读 Linux 那份 DTS。如果 OP-TEE 里的配置还是老的,启动时就会把你的新设置覆盖掉。于是你看到的现象就是:设备树明明改成了 non-secure,编译也通过了,但运行时 SPI 寄存器就是写不进去。

这种“配置看起来对,实际运行不对”的问题,最有效的验证手段是读 ETZPC 寄存器实际值,而不是看源代码。在 Linux 下用 devmem 读取对应寄存器,换算一下安全位。如果显示 secure,那就是 OP-TEE 或 U-Boot 在启动时锁死了外设,必须去改那部分配置。

2.4 引脚复用:AF 编号错了就是全错

SPI 的 SCK 引脚必须配置为复用功能(Alternate Function),而且 AF 编号必须完全匹配该 SoC 的复用表。注意,CS 可以用普通 GPIO,但 SCK 一般不能,因为它需要由 SPI 外设直接驱动输出时钟。

这里就出现一个非常常见的错误:复制了某一个 SPI 实例的 pinctrl 配置,但忘了改 AF 编号。或者 SCK 引脚被配置成了输出模式但没选 AF,而是普通的 GPIO 输出。这种配置下,CS 如果由另一个 GPIO 驱动,它照常翻转,而 SCK 引脚因为没有被正确映射到 SPI 外设,只能是静默状态。

在 Linux 下排查 pinctrl,可以用这个命令:

cat /sys/kernel/debug/pinctrl/pinctrl-handles

正常绑定 SPI 后,SCK 引脚应该处于alt function状态,并且显示的 AF 编号与 datasheet 一致。如果显示的是gpio状态,说明 pinctrl 配置没有生效。

2.5 异步时钟模式下的分频问题

STM32MP2 的 SPI 支持异步时钟模式,SCK 频率由 SPI 内核时钟和配置寄存器里的分频系数共同决定。如果分频系数配置异常,比如内核时钟被分到极低频率,示波器上会看到一条“看似没有”的平线,但实际延长捕获时间后,可能能看到极慢的时钟脉冲。

这个坑很容易被忽略。很多人把示波器时基设在微秒级,SCK 实际只有几 kHz 甚至几百 Hz,一个脉冲都看不到,自然判断为“没有时钟”。所以排查 SCK 是否真的没有输出之前,先把示波器时基拉长到毫秒级,或者用逻辑分析仪长时间采样,然后再下结论。

另外,在异步模式下,SPI 的分频系数不是随便给的。STM32 系列 SPI 的波特率分频寄存器是 3 位,对应 2、4、8、16、32、64、128、256 分频。如果你配置的分频值让 SCK 频率超出了从机支持的极限,即便波形存在,通信也完全异常。这种问题一般不会表现为“SCK 完全平”,但容易被误判为链路问题。

3. 完整排查过程实录

3.1 第一步:确认 CS 到底是硬件片选还是软件 GPIO

我先检查了板级 DTS 中的 cs-gpios 定义。如果节点里有cs-gpios属性,那 CS 就是普通 GPIO 控制的软件片选;如果没有,而 SPI 配置了SPI_NSS_HARD,那 CS 才是 SPI 控制器输出的硬件片选。

这一步非常关键,直接决定排查方向。如果 CS 是硬件 NSS,那 SPI 控制器已经进入了传输流程,问题偏向时钟链路。如果 CS 是 GPIO 软件片选,那 CS 翻转只能证明 GPIO 是好的,与 SPI 是否真正工作无关。问题可能更大范围地存在于 SPI 初始化失败或者外设权限被锁。

我的这次案例里,CS 被设计成了 GPIO 软件片选,所以“CS 翻转”这个现象其实只能指出 GPIO 链路正常,无法证明 SPI 已经发起传输。排查范围一下子拓宽了:SPI 控制器可能根本没动。

3.2 第二步:读寄存器,验证 MSTR 和 SPE 的真实状态

在 Linux 下用 devmem 读取 API 也没有问题,但首先得知道 SPI 外设的基地址和相关寄存器偏移。更稳妥的方式是通过调试器连接 JTAG 直接读,但 devmem 在目标板上更快。

我先把 SPI 外设配置成大致的预期状态,然后读取配置寄存器,确认 MSTR 是否为 1、SPE 是否为 1、TX 相关位是否配置正确。如果读回来的值跟我写入的不一致,尤其是那些关键位写 1 读 0,那基本可以断定寄存器访问被硬件拦截了。这次的检查结果很微妙——MSTR 和 SPE 都读到了 1,说明寄存器访问本身没有被 ETZPC 拦住,SPI 控制器确实处于主模式且已使能。

既然控制器的核心状态是正确的,那问题就不在权限层,而更可能在内核时钟源、分频链路或引脚复用。

3.3 第三步:检查 SPI 内核时钟的使能和频率

这个步骤是这次排查的分水岭。在 Linux 里执行:

cat /sys/kernel/debug/clk/clk_summary | grep -i spi2

一开始我注意到 SPI2 的接口时钟和内核时钟都在 Clk Summary 列表里,enable_count 为 1,频率也有显示。看起来的确“时钟在跑”。但随后我仔细观察发现,这个 enable_count 是 Linux clk 框架计数,它只能说明 Linux 在时钟树层面成功调用了clk_enable,并不能完全替代硬件寄存器层面的确认。

于是用 devmem 手动读取 RCC 中 SPI2 内核时钟选择的分频控制位,结果发现时钟源选择被写到了一个 PLL 输出异常的分支。这就解释了为什么 Clk Summary 显示频率存在,但实际 SCK 完全无输出——因为那个 PLL 分支在硬件上根本没有有效的时钟信号。

3.4 第四步:回头检查 pinctrl 的 AF 配置

虽然前面已经定位到时钟链路,但 pinctrl 还是要确认,否则修完时钟还得返工。这次重新核对了 SCK 引脚在设备树 pinctrl 节点里的 AF 编号,发现果然是复制了另一个 SPI 实例的节点,AF 编号对不上,导致 SCK 引脚没有连到 SPI 外设上。

这解释了为什么现象具有迷惑性。SCK 的 pinctrl 是错的,但设备树编译不报错,因为引脚定义本身是合法的,只是 AF 功能不对。而 CS 用的是 GPIO 功能,完全不受影响。

3.5 第五步:硬件链路验证

软件配置基本确认后,我又用示波器表笔直接点在 STM32MP257F-DK 的 SPI 扩展接口上,确认 PCB 走线没有断开、扩展板上没有其他器件把 SCK 拉低。一些开发板的扩展接口会有跳线或默认的负载电阻,如果跳线帽没插好,SCK 信号根本到不了你测量点,但你以为测的是 SCK。这一步虽然简单,但偶尔就是会出现这种低级问题。

4. 定位后的修复方案与代码调整

4.1 Linux 设备树:时钟源、pinctrl、访问控制一个都不能少

修复的第一步是纠正 SPI2 节点里的内核时钟源配置。以设备树为例,SPI 节点应该显式指定内核时钟,并确保时钟源有效:

&spi2 { pinctrl-names = "default", "sleep"; pinctrl-0 = <&spi2_pins_a>; pinctrl-1 = <&spi2_sleep_pins_a>; access-controllers = <&etzpc ETZPC_SPI2_ID>; clocks = <&rcc SPI2_K>, <&rcc SPI2_PCLK>; clock-names = "kclk", "pclk"; status = "okay"; spidev@0 { compatible = "rohm,dh2228fv"; reg = <0>; spi-max-frequency = <1000000>; }; };

注意几个容易错的点。assigned-clock-rates如果没配,内核会使用设备树里默认的频率,但这个默认频率可能依赖一个不存在的 PLL 配置。最好在时钟控制器节点里确认 SPI2_K 的时钟源,比如:

&rcc { assigned-clocks = <&rcc SPI2_K>; assigned-clock-parents = <&rcc PLL2_P>; assigned-clock-rates = <100000000>; };

这样 spi2 内核时钟就明确绑到了 PLL2_P,并且频率被强制设定为 100 MHz,后续 SPI 分频配置就有了一个已知的基准。

pinctrl 节点也同步修正。以spi2_pins_a为例,确保 SCK/MOSI/MISO 都使用了正确的 AF 编号:

spi2_pins_a: spi2-0 { pins1 { pinmux = <STM32PINMUX('I', 1, AF5)>, /* SPI2_SCK */ <STM32PINMUX('I', 2, AF5)>, /* SPI2_MOSI */ <STM32PINMUX('I', 3, AF5)>; /* SPI2_MISO */ bias-disable; drive-push-pull; slew-rate = <1>; }; };

修改 DTS 后,重新编译并烧录设备树。需要特别提醒的是,不要只替换 Linux 的 DTB。如果板卡使用 OP-TEE,必须同步确认 OP-TEE 的 DTS 中 SPI2 的访问控制也被设置为 non-secure,否则启动后外设权限被覆盖,改成什么都没用。

4.2 裸机 M33 侧:HAL 库初始化的正确顺序

如果你是在 M33 核上跑裸机或 RTOS,初始化 SPI 时的HAL流程如下:

/* 1. 使能 SPI2 接口时钟 */ __HAL_RCC_SPI2_CLK_ENABLE(); /* 2. 选择 SPI2 内核时钟源,这一步最容易被忽略 */ __HAL_RCC_SPI2_CONFIG(RCC_SPI2CLKSOURCE_PLL2P); /* 3. 配置 GPIO 复用 */ __HAL_RCC_GPIOI_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = GPIO_AF5_SPI2; HAL_GPIO_Init(GPIOI, &GPIO_InitStruct); /* 4. 配置 SPI 外设 */ hspi2.Instance = SPI2; hspi2.Init.Mode = SPI_MODE_MASTER; hspi2.Init.Direction = SPI_DIRECTION_2LINES; hspi2.Init.DataSize = SPI_DATASIZE_8BIT

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询