
拿到一块带 SPI3 接口的板子外挂 Flash 或者无线模块程序烧进去之后用示波器点 SCK、MOSI发现完全没波形或者只有一个引脚有信号——这种问题我遇到不下十次。SPI3 在 STM32 里用得不少但它和 SPI1/SPI2 有个很大的区别它挂在 APB1 总线上引脚又经常和 JTAG、FSMC 这些功能抢位置所以一旦出现“信号没有送出”的故障排查起来往往比 SPI1 麻烦得多。这篇文章把我平时排查 SPI3 无声信号的完整思路、检查顺序、代码注意点以及几个真实踩坑案例整理出来给正在被 SPI3 折磨的嵌入式开发同好看。1. SPI3接口没信号先定位现象再动手1.1 SPI3在STM32中的位置和容易踩坑的原因先搞清楚 SPI3 这个外设本身。以最常见的 STM32F103 为例SPI3 和 SPI2 挂在 APB1 总线上最高时钟是 36MHz系统主频 72MHz 时 APB1 通常二分频而 SPI1 挂在 APB2 上最高可以到 72MHz。这意味着你算波特率分频的时候基数是 PCLK1 而不是 SYSCLK很多人在这里算错导致 SCK 频率不对进而误判成“没信号”。到了 F4 系列SPI3 同样挂在 APB1 上但 APB1 最高是 42MHz系统 168MHz 时四分频计算方式又不一样。所以在排障之前先确认你的芯片型号、系统主频、APB1 分频系数这三者决定了 SPI3 的时钟来源。再来说引脚。F103 的 SPI3 默认引脚是 PB3SCK、PB4MISO、PB5MOSI、PA15NSS注意这三个引脚在芯片复位后不是普通 IO而是 JTAG 接口的一部分。PB3 是 JTDO、PB4 是 NJTRST、PA15 是 JTDI如果不先把 JTAG 相关功能释放掉GPIO 复用怎么配都白搭。F4 系列虽然没有 JTAG 复用这种历史包袱但 SPI3 的引脚可以通过 AF 映射到不同位置默认也可能不是你想当然的那组引脚不查 Datasheet 或 CubeMX 的 Pinout 视图很容易踩空。1.2 “没有信号送出”的四种典型表现同样一句话“SPI3 没信号”实际现象可以分成好几种每种对应的排查方向完全不同。我先列出来你对照自己的情况完全没波形SCK、MOSI、CS 全部没动静。这种情况大概率是时钟没使能、引脚复用没配好或者代码根本没执行到发送函数。SCK 有波形、MOSI 没波形说明 SPI 外设已经跑起来了但数据没有送到 MOSI 引脚。问题可能出在 MOSI 引脚配置、发送函数流程、DMA 方向配置。SCK 有波形、MOSI 也有波形但 CS 不拉低这是从设备没有被选中的典型问题多半是 NSS 引脚管理方式不对或者 CS 控制代码压根没跑。波形偶尔有、偶尔没有或者发送一段时间后消失这种最隐蔽往往是 NSS 硬件模式导致的 SPI 模式切换、BSY 位卡死、或者初始化顺序被后续代码覆盖。拿到问题第一步不是改代码而是先确认是上面哪一种。因为“SCK 没波形”和“MOSI 没波形”的原因重叠度只有不到一半方向搞错了后面所有排查都会绕远路。2. SPI3初始化代码怎么查时钟、GPIO、模式三个方向2.1 外设时钟APB1没使能SPI3寄存器全是空操作SPI3 无声信号最常见的原因之一就是外设时钟没开。这个听起来很基础但实际踩到的人特别多。原因也很简单很多人是从 SPI1 的例程复制过来改的SPI1 挂在 APB2 上代码里写的是RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE)复制过来后只把 SPI1 改成了 SPI3但时钟总线的宏忘了改到 APB1。标准库正确写法是RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3, ENABLE);HAL 库则是__HAL_RCC_SPI3_CLK_ENABLE();如果时钟没有使能对 SPI3 的所有寄存器操作都是无效的。最典型的特征就是你调用SPI_Cmd(SPI3, ENABLE)之后读SPI3-CR1的 SPE 位仍然是 0说明外设根本没有被激活。这时候示波器上当然什么都测不到因为 SPI 外设压根没有工作时钟。还有一种情况容易被忽略调试器在线仿真时如果程序在配置到一半的时候被复位或者你在初始化 SPI3 之前调用了某些低功耗模式的库函数也会导致 APB1 时钟被关闭。所以排查时不要只看代码有没有写开时钟还要确认运行顺序确实执行到了那一行并且执行完后 RCC 寄存器里的对应位确实置 1 了。2.2 GPIO复用与重映射SPI3的引脚坑比SPI1多在哪时钟开了之后下一步检查 GPIO 复用配置。F103 的 SPI3 默认引脚是 PB3、PB4、PB5、PA15但前面说过 PB3、PB4、PA15 在复位后是 JTAG 功能必须先把 JTAG 释放掉。释放方法RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);注意两件事第一AFIO 时钟必须开不开的话重映射配置写不进去第二GPIO_Remap_SWJ_JTAGDisable会关闭 JTAG但 SWDPA13、PA14不受影响所以用 ST-Link 的 SWD 接口调试完全没问题。F103 的 SPI3 引脚配置标准库代码如下GPIO_InitTypeDef GPIO_InitStructure; // SCK - PB3, MOSI - PB5 配置为复用推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_3 | GPIO_Pin_5; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); // MISO - PB4 配置为浮空输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOB, GPIO_InitStructure);F4 系列用 HAL 库时需要在HAL_SPI_MspInit里配置 AF 映射注意 SPI3 的 AF 编号是GPIO_AF6_SPI3GPIO_InitTypeDef GPIO_InitStruct {0}; __HAL_RCC_SPI3_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); GPIO_InitStruct.Pin GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5; GPIO_InitStruct.Mode GPIO_MODE_AF_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate GPIO_AF6_SPI3; HAL_GPIO_Init(GPIOB, GPIO_InitStruct);很多人在 F4 上忘记设置Alternate字段或者 AF 编号选错结果引脚根本不复用成 SPI 功能自然一点信号都没有。这个字段在 CubeMX 生成的代码里会自动加上但手写代码时特别容易漏。另外提醒一句SCK 和 MOSI 是输出必须配成复用推挽输出MISO 是输入配置成浮空或上拉输入。如果 MISO 配成了复用推挽输出虽然不会直接导致“没有信号送出”但会影响 SPI 读取而且这种错误在调试时非常难发现。还有一个 F103 特有的坑SPI3 支持重映射到 PC10、PC11、PC12。如果你的板子上这些引脚同时接了 FSMC 或者其他外设初始化顺序会决定谁能拿到引脚控制权。我见过 FSMC 先初始化把 PC10 到 PC12 的复用功能抢走了SPI3 重映射之后信号输出不出来的情况。这种情况下要么去掉重映射要么把 SPI3 初始化放到 FSMC 后面并且确认两者没有真正冲突。2.3 SPI模式配置主从、NSS、分频每一个都是雷区时钟和引脚检查完再看 SPI 模式配置。这一层有三个雷区按触发频率排序主从模式配错、NSS 管理方式不对、分频系数算错。先看主从模式。标准库里是SPI_InitStructure.SPI_Mode SPI_Mode_MasterHAL 库是hspi3.Init.Mode SPI_MODE_MASTER。如果这里配成了从模式SPI3 不会自己产生 SCK示波器上当然什么都测不到因为 SCK 是外部输入到芯片的不是芯片输出。这个错误最直接的判断方法就是读SPI3-CR1的 MSTR 位主模式应该是 1从模式是 0。再看 NSS 管理。这是 SPI3 调试中最隐蔽的坑我单独拿出来说。如果你把 NSS 配置成硬件模式SPI_NSS_Hard那么 NSS 引脚的电平会直接控制 SPI 外设的主从状态。NSS 引脚一旦被外部拉低SPI 会自动切换成从模式MSTR位被硬件清零之后你再怎么调用发送函数SCK 都不会输出。为了避免这个问题几乎所有的 SPI 主设备工程都建议使用软件 NSS 管理标准库SPI_InitStructure.SPI_NSS SPI_NSS_Soft;HAL 库hspi3.Init.NSS SPI_NSS_SOFT;使用软件 NSS 后NSS 引脚就只是个普通 GPIO你可以手动控制它来做片选SPI 外设的主从状态完全由软件配置决定不会再被外部电平干扰。这在小信号、长走线、电磁环境复杂的板子上尤其重要。最后是分频系数。SPI 的 SCK 频率计算方式是SCK PCLK1 / (2^(BR 1))其中 BR 是SPI_BaudRatePrescaler的具体值。以 F103 为例系统主频 72MHz 时 APB1 是 36MHzBR 取SPI_BaudRatePrescaler_16即 011SCK 36MHz / 16 2.25MHz。如果你想跑 4.5MHzBR 取 8即 010。这里容易出现的问题是记错 APB1 频率把 72MHz 当成基数去算得到的 SCK 频率比实际高一倍。从设备如果对频率敏感比如某些旧版 W25Q 的 SPI 模式上限是 25MHz一般不至于不响应但如果从设备最高只能跑 1MHz你按 72MHz 基数算出 4.5MHz 以为安全实际上 SCK 跑到了 9MHz从设备完全不识别表现出来就像“没有信号送出”。3. 一步步实测示波器、调试器、代码链路定位SPI3无信号3.1 示波器先测四个点SCK、MOSI、CS、MISO该是什么波形代码层面的检查做得差不多了接下来必须上仪器。逻辑分析仪和示波器二选一但我强烈建议用双通道以上示波器或者四通道逻辑分析仪。原因很简单SPI 调试必须同时看 SCK 和 CS 的关系单通道很难判断时序。先测 SCK。探头接地另一个点接 PB3F103 默认 SPI3 的 SCK触发电平设置在 1.65V 左右触发方式选 Normal 或 Single然后运行发送代码。如果 SCK 完全没有波形说明问题在时钟、引脚复用或 SPI 外设配置层如果有波形下一步测 MOSI。测 MOSI 时有一个细节如果发送的数据全是 0x00MOSI 会一直保持低电平看起来像没信号。所以测试时建议发送 0x55 或者 0xAA这样 MOSI 上会有明显的方波翻转。发送函数的代码可以写成uint8_t spi3_write_read_byte(uint8_t byte) { while (SPI_I2S_GetFlagStatus(SPI3, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI3, byte); while (SPI_I2S_GetFlagStatus(SPI3, SPI_I2S_FLAG_RXNE) RESET); return SPI_I2S_ReceiveData(SPI3); }主循环里调用while (1) { GPIO_ResetBits(GPIOA, GPIO_Pin_4); // CS 拉低 spi3_write_read_byte(0x55); GPIO_SetBits(GPIOA, GPIO_Pin_4); // CS 拉高 delay_ms(10); }通过 CS 的拉低和拉高动作你可以判断程序有没有真正执行到发送逻辑。如果 CS 一直为高说明连发送流程都没走到问题在更前面的调用链上如果 CS 正常拉低但 SCK 无波形问题锁定在 SPI 外设配置层。还有一个容易忽略的测试点引脚速度。如果 GPIO 速度配置过低比如GPIO_Speed_2MHz在 SCK 频率较高时波形边沿会非常缓看起来像三角波接收端可能识别不了。把引脚速度调到 50MHzF4 上是GPIO_SPEED_FREQ_VERY_HIGH大多数情况下都能改善。这个问题的典型表现是示波器上有波形但从设备就是不响应这时候先别怀疑 SPI 协议先用探头看边沿够不够陡。3.2 调试器看两个寄存器CR1和SR直接告诉你问题在哪示波器能告诉你“有没有波形”但没告诉你“为什么没波形”。这时候把调试器挂上直接看 SPI3 的寄存器。打开调试器在 Watch 窗口添加SPI3-CR1和SPI3-SR然后在调用SPI_Cmd(SPI3, ENABLE)之后暂停检查以下位CR1 的 SPEbit 6SPI 使能位。如果为 0说明外设没有使能马上回头看时钟和 SPI_Cmd 是否执行成功。CR1 的 MSTRbit 2主从模式。主模式必须为 1如果为 0可能是配成了从模式或者 NSS 硬件模式被拉低导致硬件自动切换。CR1 的 SSMbit 9和 SSIbit 8软件 NSS 管理。SSM 应为 1软件管理SSI 应为 1主机模式。如果你配的是硬件 NSSSSM 为 0SSI 不受控。SR 的 TXEbit 1发送数据寄存器空。主模式正常工作下发送前 TXE 应为 1表示可以写入数据。如果 TXE 一直为 0说明数据还没被移位寄存器取走可能是 BSY 卡住或 SPI 状态机异常。SR 的 BSYbit 7忙标志。正常空闲时为 0。如果 BSY 一直为 1说明 SPI 认为通信还没结束。这种情况经常出现在 NSS 被拉低或者从设备没有正确回复的时候SPI 外设一直在等待SCK 自然不会输出。通过读寄存器你可以快速区分问题层次如果 SPE 为 0问题在初始化代码如果 MSTR 为 0问题在 NSS 或模式配置如果 SPE 和 MSTR 都正常但 TXE 不为 1问题在发送流程或 BSY 状态。另外调试器还可以强制往SPI3-DR里写数据然后量 MOSI。比如在调试器命令行里执行SPI3-DR 0x55;然后立刻看示波器。如果这时候 MOSI 出了波形说明 SPI 外设和引脚全部正常问题出在你的软件流程上如果还是没有波形问题就在 GPIO 配置或引脚连接上。这一个动作能帮你把排查范围缩小一半。3.3 发送链路拆开验证最简代码排除软件问题寄存器检查完如果还定位不到问题就写一段最简代码把发送链路拆开一段一段验证。不要直接跑完整驱动程序因为驱动里可能嵌套了 DMA、中断、看门狗、RTOS 调度任何一个环节都能把信号吃掉。最简代码的步骤是第一步只初始化 GPIO 和 SPI3 外设然后把 SCK 手动翻转验证引脚本身能不能输出波形GPIO_SetBits(GPIOB, GPIO_Pin_3); delay_ms(10); GPIO_ResetBits(GPIOB, GPIO_Pin_3); delay_ms(10);如果能测到波形翻转说明 PB3 引脚本身没问题问题在复用配置层。如果这样都没有波形检查是不是引脚被其他外设占用或者硬件上根本没引出这个引脚。第二步恢复 SPI 复用配置初始化 SPI3然后直接写发送函数发送一个字节不要加任何其他逻辑。注意发送完成的标准是等待 TXE 和 BSYSPI_I2S_SendData(SPI3, 0x55); while (SPI_I2S_GetFlagStatus(SPI3, SPI_I2S_FLAG_TXE) RESET); while (SPI_I2S_GetFlagStatus(SPI3, SPI_I2S_FLAG_BSY) SET);HAL 库对应的写法uint8_t tx_data 0x55; HAL_SPI_Transmit(hspi3, tx_data, 1, 100);第三步在发送成功的基础上再加 CS 控制和外设的读写逻辑。如果加了 CS 控制之后信号消失说明 CS 引脚和 SPI 的 NSS 功能产生了冲突如果加了外设驱动之后信号消失问题在外设驱动内部和 SPI3 本身无关。这套“由简到繁”的流程能帮你在最短时间内把问题范围压到最小。我见过不少工程师一上来就跑整个应用然后在一个几百行的驱动里翻来覆去找半天最后发现只是初始化顺序被覆盖了。先跑最简代码很多问题一眼就能看到。4. SPI3无声信号真实案例和排查速查表4.1 案例一PB3/PB4被JTAG占住SCK一动不动一块 F103 板子SPI3 外接 W25Q64 FlashSCK 配在 PB3MOSI 配在 PB5。程序烧进去后逻辑分析仪接上SCK 完全没波形CS 有正常的拉低拉高动作。排查过程读SPI3-CR1SPE 为 1MSTR 为 1模式配置正常。量 PB3 引脚电平发现始终为高不是普通 IO 的空闲状态。打开代码搜索GPIO_PinRemapConfig发现整个工程里根本没有释放 JTAG 的调用。在初始化 GPIO 之前加入RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);然后重新烧录SCK 波形正常Flash 读写全部恢复。这个案例的核心教训是F103 的 PB3、PB4、PA15 在复位后不是普通 IO是 JTAG 引脚。如果调试器用的是 ST-Link SWD 模式程序完全能烧进去、能跑但 SPI3 的 SCK 永远复用不到 PB3 上。很多人在这个坑里卡了一晚上就是因为没意识到“程序能跑”和“引脚能复用”是两回事。4.2 案例二NSS硬件模式导致SPI自动变从模式另一块板子SPI3 接一个温湿度传感器现象更诡异刚上电的前几十毫秒 SCK 有波形几秒之后 SCK 没信号了程序跑飞一样。断电重启又恢复正常过几秒又不行。排查过程示波器挂在 SCK 上观察波形消失的瞬间同时用调试器读 CR1。发现波形消失时MSTR位从 1 变成了 0SPI3 自动切到了从模式。查原理图发现 SPI3 的 NSS 引脚PA15悬空但在 PCB 走线上与另一个信号线平行干扰导致 NSS 电平抖动。代码里配置的是SPI_NSS_HardNSS 引脚的电平变化直接控制主从状态。修改配置为SPI_NSS_SoftCS 引脚改用普通 GPIO 手动控制。修改后长时间运行不再复现。这个案例说明硬件 NSS 模式在简单的主从一对一场景里看似没问题但只要 NSS 引脚受到任何干扰SPI 外设就会自作主张切换模式。如果你不需要从设备主动拉低 NSS 来跟主机通信就尽量用软件 NSS把控制权完全握在手里。4.3 案例三DMA方向配错SCK有波形但MOSI永远没数据用 SPI3 驱动一块 LCD 屏SCK 有波形CS 正常但 MOSI 始终是低电平。发送函数用的是 DMA。排查过程发送函数执行正常TXE 标志也有变化。查 DMA 配置发现 DMA 的方向字段配置成了外设到内存Peripheral-to-Memory数据一直是从 SPI3 的 DR 往外搬而不是从内存搬到 DR。MOSI 当然不会有数据。修改 DMA 方向为内存到外设Memory-to-Peripheral同时确认数据宽度8 位和 SPI 的数据帧格式一致。这个案例在 HAL 库里也有对应的坑很多人用HAL_SPI_Transmit_DMA(hspi3, tx_buf, len)时以为只要这个函数执行了 DMA 就一定没问题忘记了 DMA 句柄本身的配置或者在 CubeMX 里 DMA 方向选错。SCK 有波形是因为 SPI 外设本身在正常产生时钟但 MOSI 的数据来源是 DMADMA 没把数据送进来MOSI 自然一直是空闲电平。4.4 SPI3无声信号问题排查速查表最后把排查经验整理成一张速查表贴在你工位上遇到问题直接对着查现象最可能原因快速定位方法解决方案SCK无波形MOSI无波形CS正常SPI3外设时钟未使能PB3/PB4/PA15被JTAG占用GPIO复用未配置读CR1的SPE位量PB3空闲电平查RCC寄存器开启APB1外设时钟执行JTAG释放重映射配置AFIO/GPIO复用SCK有波形MOSI无波形MOSI引脚配置错发送数据函数未执行DMA方向配错量PB5引脚是否翻转断点看发送函数是否进入查DMA类型修正GPIO复用模式检查发送流程改DMA方向为内存到外设SCK和MOSI都有波形CS不拉低CS引脚GPIO未初始化CS控制代码未执行CS与NSS功能冲突量CS引脚电平找CS控制代码断点初始化CS引脚为推挽输出检查代码流程改用软件NSSSCK会出现但波形频率不对BR分频算错误把SYSCLK当PCLK1用示波器测SCK频率确认APB1分频系数按实际PCLK1重新配置BR分频波形存在但接收端不识别GPIO速度太低边沿过缓CPOL/CPHA与从设备不匹配上拉/下拉电阻选择不当看波形边沿是否陡峭核对从设备数据手册提高GPIO速度调整CPOL/CPHA调整上拉/下拉配置发送一段时间后SCK消失NSS硬件模式受干扰MSTR被清零BSY卡死波形消失时读CR1的MSTR位和SR的BSY位改用软件NSS检查NSS引脚电平排查从设备状态我自己踩过几次 SPI3 的坑之后现在每次调 SPI3 都会先做两件事第一在初始化完成后立即读一次SPI3-CR1确认 SPE、MSTR、SSM 三个位都正确第二用示波器量 SCK 的空闲电平。这两步加起来不到两分钟但能筛掉一半以上的问题。另外一个建议画 PCB 的时候把 SPI3 的 SCK、MOSI、MISO、CS 四个信号都留出测试点遇到问题直接拿探头点测试点就行不用飞线更不用拿着镊子去戳芯片引脚。