首页 / 资讯中心 / 文章详情

SPI调试实战:MISO线上20ns毛刺的定位与解决

SPI调试实战:MISO线上20ns毛刺的定位与解决 ★ FEATURED ARTICLE
做了这么多年嵌入式SPI调试里最烦遇到的一种情况就是总线上明明没有数据传输示波器却抓到了来历不明的窄脉冲。我前段时间就碰到一个特别典型的案例——主控和从机通信完全正常该读的数据都能读出来但在MISO线上却总能抓到大约20ns宽的毛刺脉冲而且是在从机完全没有被编程为发送的状态下出现的。这种问题最坑的地方在于它不会让你的系统立刻崩溃但会埋下偶发性通信错误的种子在批量产线上或者现场环境中突然爆发。这篇文章就把这个现象的排查过程、背后的电路原理、以及最终的解决方案完整拆开讲。如果你也在调SPI尤其是多从机共享一条MISO总线这篇文章应该能帮你省掉不少弯路。1. 先搞清楚这个现象是什么20ns窄脉冲到底意味着什么1.1 复现现场示波器上的20ns毛刺长什么样先交代一下测试环境。主控用的是一块常见的STM32F103SPI1作为主机总线上挂着两片从机一片SPI接口的ADC一片SPI Flash。通信速率是2MHz也就是SCLK周期500ns数据位宽250ns。我拿示波器在主机侧MISO引脚上观测探头是200MHz带宽、1GS/s采样率触发方式设成上升沿触发触发电平放在1.65V。现象是这样的正常SPI通信时MISO线上的数据波形是干净的高低电平都很稳定。但把示波器设置成“脉冲宽度触发”专门抓低于30ns的窄脉冲时能在MISO线上抓到一堆宽度大约20ns的尖刺幅值能冲到3.3V高电平。最蹊跷的是这些毛刺的出现和NSS片选信号、SCLK时钟信号都没有固定的相位关系完全是随机的。换句话说通信是正常的但总线上不该有的噪声一直在那里。1.2 为什么20ns脉冲值得警惕很多人看到这种毛刺会不以为然觉得反正通信正常何必较真。但20ns这个宽度在SPI体系里其实是个很敏感的量级。以2MHz的SCLK为例一个时钟周期是500ns数据采样点通常在中点附近也就是每个bit的250ns附近。20ns大约是半个bit时间的8%。如果毛刺恰好出现在主机采样窗口附近导致采样点读到跳变沿就会产生一个错误的bit。而且20ns这个宽度比大多数MCU内部输入引脚的数字滤波时间常数要短但又比一些高速GPIO的毛刺过滤阈值长所以它有机会穿过引脚滤波器进入内部逻辑成为真正的干扰源。更麻烦的是多从机场景。SPI总线的MISO是一条共享线所有从机的MISO输出都是三态门并联在一起的。正常情况下没有被片选选中的从机必须把它自己的MISO输出置为高阻态只让被选中的那一个从机驱动总线。这时候如果有一个从机在“未发送”状态下向MISO线上注入一个20ns的脉冲就等同于往一个正在被驱动的信号线上叠加噪声。一旦这个脉冲的幅度和位置都凑巧被主机采到那字节就是错的而且这种错误通常没有规律极难复现和定位。2. 从SPI协议和物理层原理排查MISO在“不发送”时到底应该是什么状态2.1 SPI从机输出级的三态逻辑与“未被编程为发送”的真实含义要理解这个毛刺首先得把SPI从机MISO引脚在“不发送”时的真实状态搞清楚。SPI的MISO线全称是Master In Slave Out也就是主机接收、从机发送的数据线。由于SPI总线上可以挂多个从机所有从机的MISO输出端必须设计成三态门——高电平、低电平、高阻态。高阻态就是这个引脚既不输出高也不输出低对总线呈现极高的阻抗相当于和总线“断开”。从机什么时候该把MISO变成高阻态两种典型情况第一从机的NSS片选引脚没有被拉低也就是这个从机根本没被主机选中它自然不能去驱动MISO线第二从机已经被片选选中了但主机这一帧发出的命令是“只写不读”的类型比如SPI Flash的写使能命令从机在这一帧里不需要回任何数据那么它的MISO输出缓冲也必须关闭保持高阻。“未被编程为发送”在寄存器层面意味着什么以常见的MCU从机为例SPI外设里通常有几个关键控制位SPESPI使能、MSTR主机/从机模式选择、BIDIMODE双向数据模式使能、BIDIOE双向模式下输出使能。此外还有片选信号经过内部逻辑产生的“发送允许门控”。任何一个环节的状态组合不对都可能让MISO引脚在应该高阻的时候被错误地驱动起来。哪怕这个错误驱动只持续了几十个纳秒在示波器上就表现为一个窄脉冲。2.2 为什么会出现20ns的“幽灵脉冲”几大根因方向排查到这里我已经把可能的原因分成几类。这些判断不是凭空来的而是结合了SPI物理层原理和实际调试经验得出的方向性结论。根因方向一GPIO复用配置与SPI外设状态不一致。这是最常见的坑。很多MCU的MISO引脚在作为SPI功能使用时需要先把GPIO配置成复用功能AF模式但具体是复用推挽输出还是复用开漏输出、是否使能上下拉每一家芯片的参考手册要求都不一样。如果GPIO被配成了普通推挽输出但SPI外设又没有正确使能它的输出缓冲引脚就可能处于一种“既不是高阻也不是确定的输出”的中间状态在外界干扰或内部状态的触发下产生短暂翻转。根因方向二片选信号边沿与内部三态控制信号之间的竞争。从机的三态输出控制通常由NSS片选信号和内部状态机共同产生。如果片选信号本身的边沿不够陡或者从机内部对片选信号的处理存在组合逻辑路径长度差异就可能造成一个极短的窗口期——在这期间输出缓冲被短暂使能然后在逻辑稳定后又恢复到高阻。这个窗口如果只有20ns左右在示波器上就是一个地地道道的毛刺。根因方向三电气层面的串扰和耦合。MISO是一条高阻线尤其是从机没有驱动它的时候线路上没有任何有源器件在维持电平。这时候如果PCB上MISO走线和SCLK或MOSI走线平行且距离较近SCLK每次翻转都会通过寄生电容向MISO注入电荷在MISO上感应出电压跳变。SCLK的边沿时间如果是5ns左右感应产生的脉冲宽度天然就是十几到二十几纳秒这个量级。这与标题里“20ns Pulses”这个数字惊人地吻合。根因方向四示波器测量方法本身引入的假象。这可能是最不“技术”但最常见的原因。示波器探头的地线夹如果用了那条又长又细的鳄鱼夹线它和探头尖端之间就形成了一个很大的环路天线周围的电磁干扰开关电源、电机驱动、甚至隔壁桌的手机充电器都会在这个环路里感应出电压叠加到测量波形上。这种干扰脉冲的宽度完全取决于干扰源的频率和幅度20ns是完全可能的。3. 现场排查实操从现象到根因的五步定位法3.1 第一步排除测量假象确认真实信号排查这种事第一动作永远是“先怀疑自己的测量设备”而不是一上来就怀疑芯片或电路。我在调试过程中吃过不少亏换了三次芯片都没解决的“幽灵中断”最后发现是探头地线太长。具体做法是把示波器探头上那条鳄鱼夹地线摘掉换成弹簧地线尖就是探头上自带的一个小弹簧圈直接压到MISO引脚旁边最近的地焊盘上。这样可以最大限度减小测量环路的面积。然后再把示波器的带宽限制分别设成全带宽和20MHz低通分别测一次看毛刺有没有变化。另外要做的是把采样率提到2GS/s以上因为1GS/s采样率下一个20ns的脉冲只有大约20个采样点勉强能看清如果采样率不够毛刺的宽度和幅值都会被严重失真影响判断。我用示波器的“脉冲宽度触发”功能专门设置触发条件为“脉冲宽度30ns”这样可以把杂乱的波形过滤掉只显示窄毛刺。如果换了测量方法之后毛刺完全消失那这个问题就到此为止是测量假象不是电路问题。3.2 第二步最小系统隔离验证脉冲来源在哪个从机确定毛刺是真实存在的之后下一步是使用排除法定位脉冲到底从哪来。先把SPI总线上的所有从机都断开MISO线保持悬空或者用一个固定的上拉/下拉电阻拉到确定电平。这时候再测MISO线上的波形如果毛刺还在那说明问题出在主机内部或者MISO走线本身的耦合上如果毛刺消失说明毛刺来自某个从机。然后一片一片地把从机接回去。我的排查结果是这样的只接SPI Flash时MISO线上非常干净一个毛刺都没有只接那片SPI ADC时毛刺就出现了。这下目标锁定了问题就是那一片ADC的MISO引脚在未发送状态下向总线注入了噪声。这里提醒一句做这个测试时从机的电源一定要正常不要用那种“反正只是看看波形”的心态随便飞几根线。因为毛刺的幅度和宽度受供电电压、去耦电容影响很大供电不稳会导致误判。3.3 第三步静态配置检查逐项核对寄存器与GPIO状态锁定了问题芯片之后开始翻它的寄存器配置。我用的是一片常见的12位SPI ADC它本身只是个纯从机没有复杂的状态系统。按理说它的MISO输出完全由NSS和SCLK控制只要NSS为高MISO就应该高阻。但毛刺的出现说明它的输出缓冲在某些条件下被短暂使能了。核对清单如下从机的MISO引脚是否被配置成了正确的复用功能有没有被意外配置成普通推挽输出SPI外设的SPE位是否在通信结束后被正确关闭如果SPE一直开着从机内部逻辑会持续对SCLK和NSS采样任何一个边沿都可能在内部产生短暂的输出使能脉冲。从机的NSS引脚配置如果从机的NSS内部有上拉电阻并且这个上拉电阻值很大而主机侧没有专门驱动NSS比如NSS是悬空的那NSS电平可能处于不确定状态偶尔抖动一下就触发了从机的内部状态机。主机侧的SPI寄存器MSTR位是否为1BIDIMODE是否被意外置1在BIDIMODE下MISO引脚可能会被复用为MOSI线的输出从而被主机驱动产生毛刺。时钟配置从机SPI外设的时钟是否被关闭如果时钟没关但SPE没有开部分芯片的内部逻辑仍会跑产生意外的翻转。逐项核对后我发现问题并不是出在从机侧而是主机侧。这块STM32F103的SPI1外设MISO对应的引脚PA6被我配置成了GPIO_MODE_AF_PP复用推挽输出这在STM32的SPI主机场景下是标准的配置方式因为从机视角看PA6是它的MISO输出方向。但问题在于我初始化GPIO的顺序是在SPI外设使能之前而SPI外设没有使能的时候PA6作为复用推挽输出它的输出缓冲处于“无信号驱动”状态引脚电平完全处于不确定。虽然最终SPI使能后一切正常但在瞬间或者在某些寄存器重新配置的时刻PA6就可能吐出一个极短的脉冲。3.4 第四步时序与信号完整性排查如果静态配置检查没有发现问题那就需要从物理层找原因了。这时可以用近场探头沿着MISO走线扫一遍找一个信号最强的地方。如果没有近场探头也可以用一段同轴线剥出芯线绕成一个直径几毫米的小线圈接到示波器上效果也还行。同时要检查PCB走线。MISO和SCLK、MOSI是否平行走了很长的距离两线之间的间距是多少有没有地线做隔离我这次排查中实测发现虽然问题产生的原因是GPIO配置但20ns这个宽度的脉冲能通过耦合从走线中传播出来和MISO走线末端的高阻状态有很大关系——高阻线对耦合电荷几乎没有泄放路径信号即使很弱也会呈现为明显的电压变化。走线的耦合电容可以粗略估算平行走线每厘米大约0.1到0.3pF。如果MISO线长5cm和SCLK平行SCLK边沿时间5ns翻转3.3V那么通过耦合电容注入MISO的电流大约是IC×dV/dt。算下来C取1pFdV3.3Vdt5nsI≈0.66mA。如果MISO对地阻抗是10kΩ高阻态时的等效阻抗那么感应电压大约6.6V的峰值——当然这是理想化计算实际会低很多但足以说明几伏的干扰脉冲完全可能通过这种耦合产生。所以PCB布线层面也要一并处理。3.5 第五步软件动态控制验证最后一步是做一个对照实验验证毛刺是不是由SPI外设的配置状态直接产生的。方法很简单在软件里把PA6强制配置成普通GPIO输入模式下拉使能然后跑同样的SPI通信流程。这时候如果MISO线上毛刺消失说明问题确实出在SPI复用状态的配置顺序或SPI外设使能时机上如果毛刺还在那就说明问题在硬件耦合。我做了这个实验结果毛刺完全消失。这就确认了根因MISO引脚在SPI外设未使能期间被配置成复用推挽输出导致引脚处于不确定驱动状态在SPI外设使能切换的瞬间产生了一个约20ns的输出脉冲。这个脉冲作为共模干扰又通过MISO走线的寄生参数传播到了观测点。4. 解决问题软件配置修正、硬件改善与预防性设计4.1 软件方案把从机GPIO配置成该浮空就浮空这次排查最终落在软件修复上。核心原则就是在SPI外设没有正式使能之前任何和SPI功能相关的引脚都不应当处于“有源驱动”状态尤其是像MISO这种按协议要求应当三态的输出线。我最终改成了这样的初始化顺序和配置// 1. 先把MISO引脚配置成普通输入带上拉防止悬空 GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_6; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); // 2. 配置SPI外设参数 hspi1.Instance SPI1; hspi1.Init.Mode SPI_MODE_MASTER; hspi1.Init.Direction SPI_DIRECTION_2LINES; hspi1.Init.DataSize SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity SPI_POLARITY_LOW; hspi1.Init.CLKPhase SPI_PHASE_1EDGE; hspi1.Init.NSS SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_16; hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; HAL_SPI_Init(hspi1); // 3. SPI外设使能后再把MISO引脚切换成复用功能 GPIO_InitStruct.Pin GPIO_PIN_6; GPIO_InitStruct.Mode GPIO_MODE_AF_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Alternate GPIO_AF5_SPI1; HAL_GPIO_Init(GPIOA, GPIO_InitStruct);关键点是先以普通输入模式接管引脚等SPI外设初始化完成并处于稳定状态后再把引脚切换成复用推挽输出。这样从引脚通电到SPI真正可用的整个过程中MISO线都处于确定的高阻/输入状态不会产生输出脉冲。另外要注意SPI外设的使能和关闭时机。如果SPI在通信完成后需要被禁用比如为了省电那么在禁用SPE位之前先把MISO引脚切回普通输入模式然后再清SPE顺序反了同样会出现短暂的配置翻转窗口。我自己遇到过的类似问题还有在调试器暂停halt的状态下MCU的GPIO输出状态会被调试器冻结如果此时正好停在SPI通信的中间MISO线可能被冻结在驱动状态松开暂停的瞬间就会产生一个脉冲注入总线。这些细节在批量产线测试时特别容易出现值得重视。4.2 硬件方案上下拉电阻、引脚隔离与PCB处理即使软件修好了我也强烈建议在硬件上做一些加固。因为MISO作为一条高阻线它对各种耦合干扰天生敏感软件只能保证“从机不主动捣乱”但无法保证“总线不被外部耦合干扰”。最常用的加固手段是在MISO线上加一个下拉电阻或者上拉电阻具体加哪种取决于你SPI空闲时希望MISO保持什么电平。SPI协议本身没有定义空闲电平它由CPOL参数决定。建议的做法是如果你的SPI配置是CPOL0空闲低电平MISO线上加下拉电阻让没人驱动时总线保持低电平这样即使有耦合干扰幅度也会被电阻压低。如果你的SPI配置是CPOL1空闲高电平MISO线上加上拉电阻保持高电平。阻值选择常见用10kΩ我这次用的是4.7kΩ。理由很简单10kΩ对高阻线上的耦合电荷泄放速度太慢4.7kΩ的泄放能力更强对20ns量级脉冲的抑制也更明显。当然阻值也不是越小越好过小会增加从机驱动MISO时的功耗负担还会和从机输出阻抗分压影响高电平的摆幅。对3.3V系统和CMOS输入2.2kΩ到10kΩ之间都是安全区间按需选择即可。另外可以在MISO线上串联一个22Ω的小电阻放在主机侧靠近MCU引脚的位置。这个电阻有两个作用一是抑制信号反射二是和MISO对地电容一起构成一个低通滤波网络衰减高频毛刺。串阻不能太大否则会拉慢信号沿影响高速SPI通信。22Ω到47Ω是这个场景下常见的取值。PCB层面的改善同样重要。上一节提到的耦合路径分析已经说明MISO和SCLK/MOSI平行走线是产生窄脉冲的重要物理条件。在PCB改版时尽量让MISO走线远离SCLK和MOSI中间用地线隔离。如果实在隔离不了至少要把平行长度控制在1cm以内。多层板的话保证MISO走线的参考地平面完整不要跨越分割区。还有一个容易忽略的地方MISO走线末端如果直接接到排针或者连接器那这段裸露的引脚就是一个天线。很多产品在最终测试时发现MISO毛刺消失但一接上排线就出现就是因为排线的寄生电容和天线效应。对这种情况在连接器侧加一个小电容到地10pF到22pF能有效衰减高频噪声但要注意电容值会影响SPI信号上升时间高速通信时不能加太大。4.3 不同MCU平台的坑从STM32到STC8H、ESP32S3的差异提醒这个问题的排查思路是通用的但具体到不同MCU平台坑的位置各不相同。我在多个平台上都遇到过类似的“20ns脉冲”问题简单梳理一下平台差异STM32系列MISO引脚必须配置为GPIO_MODE_AF_PP但很多人不知道的是在SPI外设初始化完成之前这个配置会让引脚处于“输出缓冲无输入信号”的状态也就是引脚电平不确定。STM32的参考手册里其实写了建议在SPI配置好之前相关引脚应当保持默认的复位状态通常是浮空输入。真正可靠的做法是像上文那样分两步切换。STC8H系列STC8H的SPI引脚功能和GPIO复用关系不太一样它的SPI功能和PWM、串口等功能共用一组引脚需要通过P_SW1/P_SW2这些端口切换寄存器来选择功能映射。如果只配置了SPI控制寄存器SPCTL但没有正确配置端口映射MISO引脚可能还处于普通GPIO模式既可能有输出也可能高阻状态很不确定。此外STC8H内部有可配置的上拉/下拉电阻有些型号默认使能了内部上拉会导致MISO空闲电平偏高更容易受到干扰。ESP32S3系列ESP32S3的GPIO矩阵非常灵活任何引脚都可以映射到SPI外设的任意信号。但正因为灵活配置错误的方式也更多。常见的坑包括GPIO输出使能GPIO_ENABLE_REG没有正确配置导致引脚虽然映射到了SPI信号但输出缓冲一直被使能总线被引脚持续驱动或者输入的IE位没有打开导致引脚读不到数据。ESP32S3在初始化SPI时需要同时关注GPIO矩阵寄存器和SPI外设寄存器两者状态不匹配最容易产生窄脉冲。HC32L072系列国产小华半导体的MCU它的GPIO复用功能配置分为两个开关一个是GPIO的AF功能选择一个是外设功能选择类似“外设总开关”。很多工程师只配了GPIO的AF漏掉了外设功能开关导致引脚状态在“普通IO”和“复用功能”之间来回试探出现短暂输出异常。HC32L072的低功耗模式切换也很容易触发这种问题在进入低功耗前要特别注意把SPI引脚恢复成输入状态。不同平台的芯片手册差异很大但定位问题的思路完全一致确认MISO输出缓冲的使能条件是什么、在什么软件状态下它会从“高阻”变成“驱动”或者从“驱动”变成“高阻”以及这个切换过程是否会产生中间态脉冲。把这个逻辑理清楚平台再变也不怕。5. 常见问题速查表与几点实操经验5.1 一张表速查20ns MISO毛刺问题把这次排查中遇到的各种情况和对应的处理方向整理成表方便以后遇到类似问题时快速对照现象特征优先怀疑方向快速验证方法解决方向MISO毛刺不定期出现与SCLK无固定相位关系测量假象换弹簧地线尖、全带宽/20MHz带宽对比测改善测量方法毛刺只在接上某一片从机时出现该从机的MISO输出缓冲被误使能断开其他从机单独接它复测检查该从机的SPE、BIDIOE、NSS配置毛刺在SPI通信开始/结束瞬间集中出现GPIO复用配置与SPI外设使能时机不一致初始化顺序改成“先输入后复用”调整软件初始化顺序MISO线上毛刺幅度随SCLK翻转而增大走线耦合或地回流问题近场探头扫PCB、检查MISO与SCLK平行长度PCB改版加地隔离、MISO加下拉/上拉电阻毛刺和NSS下降沿严格同步从机内部片选竞争状态用示波器逻辑触发同时测NSS和MISO从机片选信号加滤波、从机寄存器配置检查毛刺在从机进入低功耗模式后加剧低功耗模式下引脚状态恢复检查低功耗模式下GPIO和外设的禁用配置低功耗前将MISO切回输入并禁用SPI外设时钟5.2 排查与抑制的实战经验总结最后分享几条实打实的经验。第一示波器的“脉冲宽度触发”是这个场景下最强大的工具。以前我排查毛刺都是靠运气去“撞”波形用普通边沿触发根本抓不到那些偶发的窄脉冲。后来发现用脉冲宽度触发设置“脉宽小于某个值”示波器会像捕鼠夹一样自动逮住所有符合条件的事件一次不用等太久就能确认毛刺是否存在、是否变化、频率如何。第二排查这种问题千万别急着改代码或者换芯片。先用30分钟把测量做严谨把“真信号”和“假信号”彻底分开能省下后面好几个小时的无效劳动。我见过太多工程师一上来就改软件、换芯片、加电容最后发现问题其实是探头地线太长。第三MISO线的下拉/上拉电阻不是可有可无的。很多人做开发板的时候图省事不给MISO线加任何偏置电阻这在实验室环境下一般也能工作但一旦走到现场、接上真实线缆高阻态下MISO线对干扰的暴露程度会几何级放大。建议所有涉及SPI的设计MISO线上都预留一个电阻位0欧姆默认不贴调试时遇到类似问题再补上成本极低收益很大。第四多从机共享MISO时每个从机的MISO引脚都必须确认在未选中时处于高阻状态。这个看似简单的要求在批量产线上经常出问题有些从机芯片的MISO输出使能默认是“随时都开着的”只是通过片选来选通数据有些芯片的MISO输出使能由SPI模式寄存器的某个位控制复位默认值是“输出使能”需要软件在初始化时显式关闭。这些细节不看芯片手册真的很容易踩坑。5.3 再补充一个排查技巧用逻辑分析仪配合排除如果手头没有高端示波器可以用逻辑分析仪来辅助排查但方法和示波器不一样。逻辑分析仪看不到模拟幅值只能看到数字0/1所以它的作用不是精确测量脉冲宽度而是判断毛刺出现的时机和频率。把逻辑分析仪的采样率设高同时采MISO、NSS、SCLK三根线然后长时间记录波形再用软件搜索MISO上所有小于一个特定宽度的脉冲统计它们的分布规律——是集中在NSS下降沿附近、还是和SCLK上升沿同步、还是完全随机。这个统计规律能帮你把“内部逻辑问题”和“外部耦合问题”区分开。这次排查中我发现毛刺既不在NSS下降沿附近也不和SCLK同步完全是随机分布这个特征就已经强烈指向“GPIO配置状态不稳定”而不是“片选竞争”了。这次调试最终解决之后我把这套排查流程总结成了固定动作先确认测量无假象再做最小系统隔离然后查软件配置的每一个状态位最后才动硬件。按照这个顺序走下来那种“看起来玄学”的毛刺问题绝大多数都能在半天之内找到根因并解决。希望这篇文章对你有帮助下次再看到20ns的窄脉冲至少知道该从哪个方向下手了。
阅读完成 · 觉得有帮助?
咨询建站