
搞嵌入式这些年的朋友几乎都遇到过这么一幕调一个 SPI3 接口的外设代码写得头头是道初始化没报错逻辑也貌似没毛病结果示波器一夹上去引脚上安安静静一个脉冲都没有。尤其坑的是这种“没有信号送出”的故障往往不是大写的一句报错而是整块板子都表现得像“没这回事”但外设就是不工作。今天这篇文章我就从一次典型的 SPI3 调试经历聊起把这类问题的定位路径、排查顺序、实测方法和寄存器级验证手段一次讲透。适合刚接触嵌入式 SPI 通信的初学者也适合被“外设不响应”折磨过几次的中级开发者参考。既然是 SPI3 接口没有信号送出第一反应当然是把测量点、测量工具和“没有信号”的定义先捋清楚。很多时候我们以为的“没信号”实际可能只是看错了引脚或者用了错误的触发方式。别笑这种低级错误在时间紧张的时候最容易犯我自己就栽过一回。1. 现象复现与定位思路先确认“没有信号”到底指什么1.1 从一次SPI3通信失败说起当时的情况是这样的新做的一批板子主控通过 SPI3 挂了一个 SPI Flash用来存运行参数和日志。上电之后固件初始化正常读写日志模块却一直报超时。按常规套路我先检查了代码流程SPI3 的 GPIO 初始化、SPI3 外设初始化、片选控制、读写函数翻了几遍都没看出明显问题函数返回值也都是正常的。排到这一步我拿出示波器准备直接看波形结果发现 SPI3 的 SCK 脚和 MOSI 脚完全没有波形。事情到这里就有点意思了。明明 HAL_SPI_Init 返回了 HAL_OK底层状态寄存器也没有报错为什么引脚上一个时钟脉冲都看不到这里先抛出我的判断SPI3 没有信号送出十有八九不是 SPI 协议本身的问题而是“外设信号根本没有连接到引脚”或者“外设虽然使能了但通信流程还没走到发送这一步”。定位思路应该按照“从概率最高到概率最低”的顺序来而不是一上来就怀疑协议时序。1.2 把“没有信号”定义清楚再动手排查的第一步是把“没有信号”这个描述精确化。建议直接先量四根线SCK、MOSI、MISO、CS。不要只看一根线就下结论。因为不同故障模式下这四根线的表现差异很大SCK 无波形、MOSI 无波形通常是 SPI 外设时钟、GPIO 复用配置或 SPE 使能位的问题主控侧根本没有发起通信。SCK 有波形、MOSI 无波形说明主控已经在跑协议但发送数据没有进入数据寄存器或者输出引脚配置错误。SCK 和 MOSI 都有波形但 MISO 无信号这就不是“没有信号送出”了而是“信号送出了但从机没回应”问题大概率在从机侧。我那次遇到的情况属于第一种——SCK 和 MOSI 都没动静。只要把问题定义到这一层后面的排查方向就清晰很多先查主控侧配置再查硬件连接而不是一上来就去怀疑 Flash 芯片坏了。2. 按概率从高到低排查CLOCK、GPIO、SPI配置、代码逻辑2.1 最先怀疑时钟SPI3挂在哪条总线上使能了没有在绝大多数主流 MCU 上SPI3 并不是默认上电就工作的外设。比如在 STM32F1 系列里SPI3 挂在 APB1 总线上对应时钟使能位是 RCC_APB1ENR 里的 SPI3EN在 STM32F4 系列里SPI3 同样挂在 APB1 上但 APB1 的最高频率和分频系数与 F1 不同。很多初学者会犯一个错误把 SPI1 的时钟使能方式套到 SPI3 上在 RCC_APB2ENR 里翻半天找不到 SPI3EN于是 SPI3 的外设时钟其实从未被打开。这时候程序的表现往往很有迷惑性调用 SPI 初始化函数仍然返回正常因为很多初始化函数的第一步就是操作 CR1/CR2 寄存器而 GPIO 配置也独立于 SPI 外设时钟。只要 GPIO 的时钟开了代码就不会报错但 SPI 外设本身处于“断电”状态引脚自然不可能有波形。排查这一项最快的方法是直接读时钟使能寄存器确认对应位真的置 1 了而不是只看初始化函数的返回值。2.2 GPIO映射与复用选错AF号信号根本到不了引脚如果说时钟没开是“外设没醒”那 GPIO 复用配置错了就是“外设醒了但门走错了”。SPI3 的引脚在多数芯片上都有不止一组映射比如在 STM32F1 的大容量型号上SPI3 的 SCK/MISO/MOSI 可以映射到 PB3/PB4/PB5也可以映射到 PC10/PC11/PC12在 STM32F4 上则通常使用 PB3/PB4/PB5复用功能编号是 AF6。如果你在初始化 GPIO 时把 AF 编号写成了 AF5或者用了默认的普通推挽输出模式那 SPI3 外设即使内部已经产生波形信号也到不了引脚。这里有个特别隐蔽的坑尤其是在 STM32F1 这类芯片上PB3、PB4 和 PA15 默认是 JTAG 调试引脚如果代码里没有禁用 JTAG 功能这几个引脚根本不会作为普通 GPIO 或复用功能输出。你明明配置了 PB3 作为 SPI3_SCK但 JTAG 功能还占着这个引脚波形自然出不来。我当时调试的那块板子就是把 SPI3 放在了 PB3/PB4/PB5 上折腾了半天才发现是 JTAG 占用问题。解决办法是在 GPIO 初始化前加上一行 AFIO 的重映射配置把 SWJ_CFG 设为“JTAG-DP DisabledSW-DP Enabled”或者在图形化配置工具里勾选禁用 JTAG 选项。2.3 SPI配置边界主从模式、极性和波特率一个都不能将就排除了时钟和 GPIO 之后就要检查 SPI3 外设自身的配置。最容易出问题的主要有三项主从模式选择、CPOL/CPHA 极性和相位、波特率分频系数。先看主从模式。SPI3 如果被错误配置成了从模式那它只能被动等待外部时钟自己不会主动输出 SCK 波形。这种“没有信号送出”的现象和主模式下完全一样但解决方法截然不同。确认方法很简单读 CR1 寄存器的 MSTR 位或者看初始化函数里传入的参数是否为主模式。再看 CPOL/CPHA。这两个参数决定了 SCK 空闲电平和采样边沿。如果 SPI 主机和从机之间的极性和相位不匹配通信可能完全失败但 SCK 波形其实是有的。不过在某些芯片组合下双方对极性的理解不同会导致从机接收数据时始终采到错误电平整条链路表现为“没有数据送出去”。所以排查到这一步要把 CPOL/CPHA 和从机数据手册认真对一遍别想当然。最后是波特率。SPI 的波特率由总线时钟和分频系数决定比如在 STM32F1 上SPI3 是 APB1 外设APB1 最高 36MHz分频系数从 2 到 256。我曾经遇到过一种情况SPI3 的 SCK 波形确实有但频率算错了远高于从机支持的极限导致从机完全无法响应。这时候用示波器看波形脉冲是存在的但抓不到有效通信。如果你看到 SCK 有波形却“没有信号送出”的错觉建议优先把波特率降到最低一档试试比如 APB1/256然后再逐渐往上调。2.4 代码执行逻辑SPE使能、片选顺序和发送函数在软件层面还有几个隐蔽的“逻辑坑”能导致 SPI3 没有信号送出。第一个坑是 SPE 使能位。很多初始化函数只负责配置 SPI 模式、极性、分频等参数并不会自动把 SPE 位置 1。比如 HAL 库的 HAL_SPI_Init 只做配置初始化真正的通信要等调用 HAL_SPI_Transmit/Receive 时才会把 SPE 置位。如果你只是初始化完之后在外部循环里用某种方式直接写数据寄存器而没有触发发送函数那么 SPE 仍然为 0SCK 不会输出任何时钟脉冲。这个现象很常见尤其在复杂的代码框架里很容易漏掉真正的“发起发送”这一步。第二个坑是片选顺序。如果是软件控制 NSS通常需要先在发送前把 CS 拉低发送完成后拉高。如果 CS 保持高电平从机处于未选中状态即使主机照发不误从机也不会回应。但这并不会导致 SCK 或 MOSI 没有波形所以它更适合归入“信号送出了但通信失败”一类而不是“没有信号送出”。不过排查的时候要留意区分。第三个坑是代码中等待标志位不当。发送一个字节时主机需要等待 TXE 标志置位然后写入数据写完还要等 BSY 位清零表示移位寄存器中的最后一个位已经送出。如果代码陷入了某个错误的等待循环比如一直在等待一个永远不会置位的标志位那么程序看起来“卡死”了自然也不会有后续的 SPI3 信号输出。这种问题可以通过调试器单步执行来确认如果卡在某个 while 循环里出不来那多半就是标志位等待条件写错了。3. 用示波器和寄存器回读锁死问题实测方法与最小验证程序3.1 测量顺序先SCK后MOSI逐级缩小范围前面讲了原理层面的排查项现在说实测方法。接线这块有个实用建议示波器探头的地线夹子一定要用最短的地线弹簧不要拖一根长鳄鱼夹线。SPI 信号虽然不算高频但长地线引入的振铃和噪声足以让本来就微弱的信号看起来“没有波形”。如果你用的是逻辑分析仪同样要注意采样率至少是信号频率的 4 倍以上否则抓到的是“假死波形”。测量的顺序我一律推荐先夹 SCK看看有没有时钟脉冲再夹 MOSI看有没有数据最后夹 MISO看从机回没回。先测 SCK 的好处在于它能一锤定音地区分“主控侧到底有没有发起通信”。如果 SCK 完全平直那基本可以确定问题出在主控的时钟、GPIO 复用或代码执行的早期阶段如果 SCK 有脉冲但 MOSI 没数据那问题范围就缩小到了发送地址或数据寄存器这一层。3.2 寄存器回读法比HAL返回值更可信很多人习惯了 HAL 库的“返回 HAL_OK”就以为万事大吉但调试这种静默故障我更推荐直接读寄存器眼见为实。这里列几个关键的寄存器点时钟使能寄存器确认 SPI3 的时钟使能位为 1。GPIO 复用寄存器在 STM32 系列上读 GPIOx-AFR 确认对应引脚的 AF 编号是否正确在 F1 系列上则读 GPIOx-CRL/CRH 确认模式是否为复用推挽输出。SPI3 的 CR1 寄存器确认 SPE 位为 1MSTR 位为 1主模式以及分频系数是否符合预期。SPI3 的 SR 寄存器检查 TXE、RXNE、BSY 位的实时状态。如果 TXE 一直为 0说明上一次数据还没有发完发送链路有阻塞如果 BSY 一直为 1说明移位寄存器还在忙。这组寄存器回读法比“有没有报错”更可靠因为很多底层错误不会直接通过 HAL 函数返回而寄存器状态会诚实反映外设的真实工作情况。调试时我通常开一个 Watch 窗口把以上寄存器都加进去单步执行发送函数观察每一步对应的状态变化。3.3 一个能快速复现的最小发送示例排查时需要尽快建立一个“最小验证链路”。这里给你一个非常精简的 SPI3 发送示例以 STM32F1 系列寄存器操作为例重点看流程具体寄存器位定义以你的芯片手册为准// 1. 使能 SPI3 时钟APB1 RCC-APB1ENR | RCC_APB1ENR_SPI3EN; // 2. 使能 GPIOC 时钟并把 PC10/PC11/PC12 配置为复用功能根据实际引脚调整 RCC-APB2ENR | RCC_APB2ENR_IOPCEN | RCC_APB2ENR_AFIOEN; GPIOC-CRH ~(0xFFFF 8); // 清 PC10/PC11/PC12 配置 GPIOC-CRH | (0xB333 8); // 复用推挽输出/输入速度 50MHz // 3. 如果使用 PB3/PB4/PB5 这类默认 JTAG 引脚先禁用 JTAG // AFIO-MAPR | AFIO_MAPR_SWJ_CFG_1; // 4. 配置 SPI3主模式、软件 NSS、8位数据、极性相位按从机要求 SPI3-CR1 SPI_CR1_MSTR | SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_BR_2; // 16分频 SPI3-CR1 | SPI_CR1_SPE; // 5. 发送一个字节 0xAA SPI3-DR 0xAA; while (!(SPI3-SR SPI_SR_TXE)); // 等待发送缓冲空 while (SPI3-SR SPI_SR_BSY); // 等待移位寄存器空闲这段代码的意义不在于生产环境使用而在于快速验证“SPI3 到底能不能把信号送出去”。如果这段代码执行完示波器上 SCK 还是没有波那就可以笃定问题是时钟、GPIO 或外设配置这类基础环节如果 SCK 有波了再把功能代码一点一点加回去。这种“先排除基础配置、再往业务逻辑走”的顺序是最省时间的。4. 典型问题速查表从“SCK无波形”到“从机不回应”4.1 症状—原因—对策对照速查我把实际调试中遇到过的 SPI3 无声场景整理成了一张速查表方便以后直接对照症状可能原因排查/对策SCK/MOSI 全部无波形SPI3 外设时钟未使能核对 RCC APB1 中 SPI3EN直接读寄存器确认置1SCK/MOSI 全部无波形GPIO 复用配置错误AF 号不对核对数据手册引脚映射表F1 检查 CRL/CRHF4 检查 AFRSCK/MOSI 全部无波形引脚占用了 JTAG 调试功能关闭 JTAG启用 SWD或改用非 JTAG 引脚SCK/MOSI 全部无波形初始化后 SPE 位未置1确认是否调用了真正的发送函数或手动 CR1 置位 SPESCK 有波形MOSI 无波形发送地址/数据寄存器逻辑未触发单步跟踪发送函数检查 TXE 标志和 DR 写入SCK 有波形通信失败CPOL/CPHA 极性与从机不匹配对照从机手册逐一核对用逻辑分析仪对比时序SCK 有波形通信失败波特率超从机上限把分频系数调到最大逐级降低观察是否恢复SCK/MOSI 有波形MISO 无信号从机未上电/未初始化/CS 未选中先检查从机供电和复位确认 CS 拉低时序这张表虽然针对 SPI3但里面大部分排查项对其他 SPI 外设同样适用。核心逻辑就是先看主控有没有发再看引脚有没有通最后看从机有没有回。4.2 我踩过最深的三个坑第一个坑是 JTAG 引脚占用。当时用 STM32F103ZET6SPI3 引到了 PB3/PB4/PB5。理论上这么配置没错但实测没有任何输出。最后翻参考手册才意识到PB3、PB4 和 PA15 默认是 JTAG 功能引脚必须先把 AFIO 的 SWJ_CFG 改掉释放这些引脚才能当 SPI3 用。这个坑在项目后期特别伤因为调试器也要用 SWD改配置的时候要小心别把自己的下载通道关了建议只禁用 JTAG、保留 SWD。第二个坑是 HAL_Init 之后的 SPE 状态。代码里调用了 HAL_SPI_Init也配置了 GPIO但一直没有数据波形。后来单步跟踪发现HAL_SPI_Init 只是写入配置寄存器SPE 仍然为 0必须等到调用 HAL_SPI_Transmit 时才真正打开通信。如果你在初始化之后、发送之前去测波形自然什么都测不到。这个失败经历给我的教训是测量 SPI3 波形一定从“发送函数被调用之后”再观察不要从“初始化完成之后”开始。第三个坑是 APB1 分频与波特率的“想当然”。我以为 SPI3 挂 APB1 上APB1 是 36MHz就用了一个很小的分频系数觉得应该没问题。结果从机 Flash 的最高频率只有 33MHz加上 PCB 走线和杂散电容实际信号质量已经差到从机完全无法识别。表现出来就是主机在发从机没反应。后来把分频调到 8 分频甚至 16 分频通信立刻恢复正常。调试 SPI3 这类老牌接口低波特率永远是第一优先级的“安全选项”。5. 排查思路沉淀两个让我少加班的通用习惯5.1 先建立“最小可信链路”再谈功能多次和 SPI3 这样的静默故障打交道之后我养成了一个习惯不管业务逻辑多复杂打样阶段一定会先写一个几行的最小发送函数配上最基础的主模式、软件 NSS、低波特率配置先把 SCK 和 MOSI 的波形测出来。这里的关键词是“最小”和“可信”。什么叫可信就是波形不仅存在而且能稳定重复触发SCK 频率和配置值一致MOSI 能按预期字节翻转。只有这个链路可信了后续再加上片选控制、从机初始化、中断、DMA 等功能才有意义。否则上面叠了多少层逻辑排查时就要往下拆多少层时间成本完全不一样。很多看起来像 SPI3 协议问题的 bug最后都翻车在底层基础配置上这就是最小可信链路的价值。5.2 用反向验证法区分主控侧和从机侧问题第二个习惯和“反向验证”有关。当 SPI3 的主控侧已经确认有波形但通信还是失败的时候我会做一个反向实验把主控配置改成回环模式或者直接把 MOSI 和 MISO 短接让主控自发自收。如果回环模式下发送的数据能被正确收回来说明 SPI3 外设、GPIO、引脚焊接和主控侧链路都是通的问题基本锁定在从机侧如果回环模式都失败那问题还是在主控侧配置甚至可能是硬件板级问题。这个方法的原理很简单SPI 全双工通信中MOSI 的数据在从机不响应的静默状态下是不会自动跑到 MISO 上的。手动短接 MOSI 和 MISO 后主机就能“自己听到自己在说什么”相当于绕过从机做了一次完整的链路验证。这个方法尤其适合排查“SCK/MOSI 有波形但 MISO 没回应”的疑难杂症也适合新板子贴片回来之后的首轮验证。最后再分享一个小细节现在每次调 SPI3 之前我都会先拿示波器确认“SCK 有没有波形”这个最基础的事实再去看代码和协议。这不是技术含量很高的事情但能省掉后面 90% 的无效排查。希望这篇经验文章能帮你少走一段弯路。