STM32WL55 LoRa发射端坏包问题排查:时序与电源管理是关键
2026/8/30 9:42:06 网站建设 项目流程

去年我做STM32WL55的点对点LoRa透传时,碰到一件非常奇怪的事:整机放在桌上,距离只有十几米,接收端却时不时解出CRC错误的数据包,偶尔还有内容错位和字节丢失。一开始我以为是射频参数配置问题,翻来覆去调MODULATION_PARAMS和PACKET_PARAMS,问题依旧。后来把示波器和频谱仪都接上,才发现根子根本不在寄存器,而在发射流程的时序和电源管理上。

这篇就把整个排查过程完整写下来。对正在用STM32WL55做LoRa通信、尤其是遇到“发射端明明发了,接收端收到的包却是坏的”这类问题的朋友,应该能省掉好几天的弯路。本文覆盖硬件层面的排查、SX126x内核的寄存器配置链路、固件里的中断/DMA/看门狗隐患,以及一套可以直接照抄的最小验证流程,适合已经跑通基础例程、但一上真实场景就出乱包的开发者。

1. 故障现象与第一轮排查:先把“坏包”定义清楚

1.1 几种“坏包”现象对应的本质差异

排查LoRa通信问题,第一件事不是翻代码,而是把接收端收到的坏包分类。我见过太多人在这一步就直接冲进寄存器堆里盲目试,结果越弄越乱。根据我反复复现的结果,STM32WL55发射端导致的坏包不外乎三类:CRC错误、内容错位、payload字节丢失。

CRC错误通常意味着射频信号是通的,但bit级解调或者同步出了问题,要么是调制参数两端不一致,要么是信噪比和干扰问题。内容错位最常见的原因是显式包头和隐式包头配置不一致,或者payload长度字段和实际写入长度不匹配。字节丢失则基本可以锁定在发射流程被中断、射频启动时序不够、DMA搬了一半数据就被打断这几类原因上。

这三种现象看起来都是“接收到损坏数据包”,但排查方向完全不同。我踩过的坑就是把它们混在一起查,结果越查越乱。拿到问题第一步,建议在接收端做完整的三元组日志:CRC结果、接收长度、payload内容十六进制dump。只有把现象细化了,后面的排查才有方向。

1.2 排查前的工具与基线准备

射频问题不像普通MCU逻辑问题,用printf打点远远不够。我这次排查用到的东西不算多,但没有它们确实寸步难行:一台支持峰值检测的频谱仪、一根近场探头或直接接SMA的射频线缆、至少双通道示波器、稳定的可编程电源。

如果手头没有频谱仪,也可以用带协议分析功能的LoRa测试节点来辅助。我后来买了一对现成的SX1262评估板作为“黄金参考节点”,和STM32WL55做交叉收发,这样能快速区分问题出在发射端还是接收端。

另外强烈建议先建立基线。就是把STM32WL55用最朴素的点对点LoRa工程跑起来,不接RTOS、不开低功耗、不用DMA,直接用阻塞式查询发送和接收,确认这个状态下通信是好的。基线不要用官方例程直接pass掉,而是自己动手搭,因为后面所有怀疑对象都要和这个基线对比。

1.3 前30分钟最常忽略的硬件点:天线、TCXO和射频开关

很多人在软件上折腾了一整天,结果最后发现是板上射频开关的GPIO控制时序不对。STM32WL55这颗芯片内置了sub-GHz radio,但射频前端通常还要外接天线匹配网络和射频开关。如果射频开关的使能脚在TX模式下没有提前拉高,或者拉高时序和SetTx操作不同步,射频前端的TX通路就没有真正建立,发射出去的信号会非常弱甚至完全被反射吸收。

天线匹配的问题更隐蔽。STM32WL55有单端和差分两种射频前端拓扑,我画板时用了单端SMA方案,RF输出脚到SMA座之间的LC匹配网络是按照ST参考设计做的。如果你用的是差分天线,却按照单端的匹配网络抄板,PA输出端阻抗会严重失配,这时发射功率越高,反射损耗越大,接收端反而收到一堆烂包。实测下来,天线失配时频谱仪上能看到明显的杂散和谐波分量,接收端的CRC错误率会随发射功率增大而上升,这是很反直觉的。

另外要注意TCXO。STM32WL55的射频内核需要一个稳定的32MHz参考时钟,如果你的板子用的是外置TCXO而不是普通晶振,TCXO本身的启动稳定时间必须留够。SX126x系列的驱动里通常有针对TCXO的SetTCXOMode配置,设置了专用供电脚和启动时间,但如果你的板子实际用的是无源晶振,这个寄存器的配置会白白增加启动等待;反过来,如果用了TCXO但没有配置启动等待,射频内核可能会在时钟还没稳定时就开始发前导码,接收端经常表现为“能听到信号但同步不上”。

1.4 用寄存器状态机锁死问题范围

排查到这一步,我强烈建议把问题范围锁到射频内核的状态机上。SX126x系列(STM32WL55内置的radio就是这个内核)的状态机其实非常清晰:STDBY_RC -> STDBY_XOSC -> FS -> TX/RX。每次命令切换后,BUSY引脚会拉高一段时间,表示射频内核正在处理命令,这期间不能发送任何新的命令。

一个很容易犯的错误是在SetTx后立刻就去读写状态寄存器,或者在BUSY期间连续调用SetStandby、SetRfFrequency等操作,造成命令丢失或状态错乱。STM32WL55的驱动里虽然有BUSY等待,但如果你绕过了HAL层直接操作SPI寄存器,就必须在每条命令之间等BUSY释放。

我排查时用逻辑分析仪把SPI的CS、SCK、MOSI和BUSY全部抓下来,发现有一处代码在射频内核还在BUSY时连续发了SetStandby和SetPacketType两条命令,导致第二条命令被丢掉,packet type始终停在旧值上。这种问题非常隐蔽,因为你以为配置了LoRa模式,实际上内核还在FSK模式,发射出去的数据包当然在接收端一塌糊涂。

2. 波形定案:TX截断、PA启动时序与timeout陷阱

2.1 频谱仪上看到的异常形态和示波器对照

软件配置反复核对无果后,我把射频输出端接到频谱仪上,用单次扫描触发抓发射瞬间的频谱包络。正常的LoRa发射包络应该是一个平滑的梯形:功率从底噪快速上升到设定值,保持一段时间,然后平滑下降,整个突发长度应该和payload长度、symbol rate匹配。

实测抓到的包络却是前段正常,后段突然掉功率,像被一刀切掉一样。用示波器同时抓射频开关的TX使能脚和PA电源引脚,发现发射过程中射频开关的控制脚有过一次极短暂抖动,导致PA输出被切断了一瞬间,发出去的数据包在接收端表现为CRC错误或长度正确但内容烂掉。

这类问题在普通MCU逻辑测试里根本不会浮现,只有叠加到射频前端才会变成“corrupted packets”。所以排查LoRa发射问题,不能只盯寄存器,要跳出来看物理层信号。

2.2 tx_timeout的真正含义与一个常见误用

SX126x系列的SetTx命令带一个timeout参数,单位是15.625微秒。timeout设为0表示无限超时,射频进入TX后除非TX_DONE中断或调用SetStandby,否则一直处于发射状态。我在工程里看到过非常经典的误用:有人把timeout设成一个很小的值,比如100,也就是大约1.56毫秒,认为“这样如果发送失败能快速回收射频内核”。

这个思路放在SX126x上会出大问题。LoRa是一个低速率调制,SF7、带宽125kHz时单symbol时长大约1.024毫秒,一个包含8个symbol前导码、两个同步字symbol和payload的典型包,整包空中时间至少几十毫秒。如果你把timeout设在几毫秒内,射频内核会在数据还没发完时自动退出TX状态,把信号硬生生截断,接收端拿到的就是残缺包。

正确做法是:要么显式设一个覆盖整包发送时间的较大timeout(比如0xFFFFFF,约4095秒,实际不会超时),要么直接用0。我后来的做法是设0,然后靠DIO1上的TX_DONE中断来确认发送完成,再用一个超时看门狗兜底,防止射频内核异常卡死。

2.3 PA ramp-up和TCXO稳定时间不够导致包头发早了

SX126x内核从STDBY切换到TX,中间需要经过FS状态完成频率合成器和PA的启动。STM32WL55的驱动里有SetTxParams接口,其中rampTime参数控制PA功率从0升到目标功率的时间,可选值是3.5us、20us、62us、1.7ms等。

rampTime设得太短,PA功率还没稳定就开始打数据,包头的几个bit会被压扁或产生频偏,接收端经常表现为“能抓到信号但payload的CRC解错”。rampTime设得太长又没有实际意义,纯粹浪费时间。工程上我通常用62us或200us,既能保证PA稳定,又不会影响整包空中时间。

还有一个容易忽略的点:如果射频内核配置了TCXO供电,每次从STDBY_RC唤醒回STDBY_XOSC,都需要等待TCXO启动完成。SX126x驱动里有一个参数专门配置TCXO启动时间,单位是微秒,我习惯设成2000us甚至3000us,宁可多等也不能抢跑。曾经有一次我只设了500us,结果TCXO还没锁稳,频率偏差高达几十kHz,接收端误码率飙升。

2.4 一个常被漏掉的寄存器:DIO1映射与TX_DONE中断

STM32WL55的DIO1引脚可以映射多种射频中断事件,包括TX_DONE、RX_DONE、PREAMBLE_DETECTED、SYNCWORD_VALID、HEADER_VALID、CRC_ERROR等等。很多人写代码时只在配置里使能了TX_DONE,却忘了把DIO1重新映射到正确的状态机上。

我做低功耗模式时踩过一次更深的坑:在发送之前把系统切到了低功耗状态,DIO1中断被系统中断控制器挂起,CPU来不及在射频发完包之前响应,导致发送流程认为射频一直忙,启动了重发逻辑。结果射频内核那边TX_DONE事件已经发生,但主控这边还在准备重发,相邻两个包在时间上交错,接收端看到的是一堆错位数据。

如果你在发射的同时还开启了接收中断或者SPI DMA中断,一定要检查中断优先级是否合理。LoRa的symbol速率很低,尤其是SF10、SF11时,synchronization和payload之间的时间窗口其实很宽,但射频内核的BUSY/状态机中断不允许延迟响应,否则就会丢掉关键状态切换窗口。

3. 固件管线的隐性杀手:DMA、radio busy和看门狗

3.1 缓冲区与DMA的读写时机

STM32WL55的radio访问payload,是通过SPI往射频内核的buffer区写数据。这个过程看起来很简单,但如果你启用了DMA搬运,DMA完成中断、SPI总线和射频内核busy三种事件交织在一起,任何一个时序不对都会丢数据。

我遇到过一种非常隐蔽的丢字节情况:DMA往radio buffer搬运payload的过程中,CPU在另一个中断里调用了Radio.Sleep(),导致SPI传了一半就被暂停,射频内核的buffer里只有半个包的数据。发送端认为发送完成,接收端解出来payload长度对不上,CRC乱掉。后来我加了一个互斥锁,确保DMA搬运期间任何射频命令都不能进入,问题才消失。

还要留意STM32WL55的buffer基址。SX126x内核内置256字节的buffer,你可以通过SetBufferBaseAddress分别设置TX和RX的基址。如果两个基址重叠,或者基址偏移设置错误,写入的payload可能被协议头覆盖,发出去的就是“看起来长度对了但内容全错”的包。我第一次移植驱动时就把TX基址设成了0x80、RX基址设成了0x00,结果发射之前写buffer时覆盖了接收缓冲区,导致同时收发时数据互相污染。

3.2 radio busy处理不当引发的命令丢失

SX126x内核的BUSY信号是排查时的关键观测点。每条SPI命令操作完成后,射频内核都需要花时间处理内部状态迁移,这个过程会拉高BUSY。驱动里一般会做一个while等待BUSY释放,但如果你在中断上下文里调用射频API,或者等待循环被高优先级中断打断,BUSY等待就可能超时,命令被强行丢弃。

我自己的代码里曾经用了一个很朴素的等待方式:

while (Radio.Busy) { /* 空转 */ }

结果系统里有一个PWM中断,频率是10kHz,中断服务函数不算长但足以影响空转循环的及时性。射频内核在忙于处理命令时,如果SPI主控这边没有持续检测BUSY,命令冲突的概率就会增加。后来我把这条空转循环改成了带超时的等待,并且把无线电相关API全部放到非中断上下文执行,用消息队列把射频事件串行化,问题率明显下降。

uint32_t tick = HAL_GetTick(); while (Radio.Busy) { if (HAL_GetTick() - tick > 10) { return RADIO_TIMEOUT; // 超时返回,避免死等 } }

这个改动虽然看起来是防御性编程,但对于整包CRC错误率的影响非常明显。射频内核和MCU主频不在一个量级上,MCU如果一直抢占SPI事务,命令交错几乎是必然的。

3.3 中断优先级导致TX中途被打断

STM32WL55的射频发送过程一旦真正开始,硬件层面会把数据按一定的速率从buffer调制到空中,这个过程不需要CPU参与。但CPU仍需要处理预发射和发射完成两个阶段,如果预发射阶段被打断,射频内核可能停在FS或者TX_RAMP状态,空中信号会被拉长或截断。

有一次我把一个高频率的外设中断优先级设得比DIO1更高,而这个外设中断服务函数里又有微秒级的延时操作。发射开始时DIO1还没触发,但SPI正在搬运配置命令,高优先级中断进来后,SPI传输被暂停了十几个微秒。射频内核在等待SPI配置的窗口里超时,直接退回了STDBY状态。程序看起来是“执行了Radio.Send()”,实际空中什么都没发,接收端自然是什么都收不到。

一个实用的经验是:所有涉及射频的SPI操作、DIO1中断处理,都放在RTOS的高优先级任务里,或者放在临界区内,同时把DIO1中断优先级设为尽可能高。发送完成后不要立刻做复杂处理,先把射频状态机和结果记录下来,再慢慢做业务逻辑。

3.4 看门狗复位造成半包发射的实证

这类问题是最难排查的,因为它只在特定供电条件下出现,概率又低,一旦出现就是“偶发坏包”。我最终定位到看门狗的时候,人已经在示波器前蹲了两天。

现象是这样的:设备在充满电的锂电池下长时间测试,一切正常;换上一块老化严重的电池后,接收端开始有零星坏包。用示波器抓电源轨,发现发射瞬间电流瞬间拉高,劣化电池的内阻大,电压跌落超过几百毫伏,MCU的复位监视器触发了一次短暂的复位,射频内核在复位过程中把发射中断了,空中就发了一个不完整的前导码和半个payload。

但固件看起来没重启,因为看门狗复位后系统的状态恢复很快,等喂狗逻辑重新执行时,你已经看不到复位痕迹。我是加了复位原因寄存器日志才确认:MCU的RCC_FLAG_IWDGRST被置位了。这种问题光调射频参数没用,必须解决发射瞬间的电源跌落,要么加大电容,要么分时放电降低瞬时电流,或者把看门狗超时时间调得更宽,避免在射频发射窗口里触发复位。

4. 一套可以直接抄的LoRa发射链路排查流程

4.1 最小验证工程配置清单

排查这类问题,我不建议在完整业务代码里来回改,而是建一个最小验证工程,只做一件事:周期性发送固定payload,接收端打印CRC、RSSI和payload内容。

我常用的最小配置如下:

  • 频点:470MHz或868MHz频段,先用一个干净的ISM频点,避开Wi-Fi和4G干扰
  • 调制参数:SF7,带宽125kHz,编码率4/5
  • 包参数:显式包头,CRC on,payload固定8字节,内容用0x00~0x07这种带位置信息的伪随机序列
  • 发射功率:先设置成14dBm,不启用High Power PA,排除PA配置问题
  • 发射间隔:1秒一次,留足接收和打印时间
  • 接收模式:接收端用连续接收,每收到一包打印一次结果

这个配置的好处是每个symbol ~1ms,整包空中时间约20ms,既不会因为RF速率太高导致干扰敏感,也不会因为速率太低把简单问题复杂化。如果在这个状态下仍然有坏包,那基本可以断定是硬件或者固件底层的时序问题,而不是payload业务层面的问题。

4.2 参数核对表:照着逐项核对

这里分享一份我自己打印出来贴在工位上的参数核对表。每次排查LoRa坏包,先对照表格逐项确认,能避免重复踩坑。

检查项正确状态异常后果
Packet TypeLoRa (0x01)若误配为FSK,收发完全乱套
RF Frequency收发两端一致频偏过大会持续CRC错误
SF/BW/CR收发两端一致解调失败或误码率升高
显式/隐式包头收发两端一致payload错位、长度错误
CRC使能收发两端一致接收端无法校验完整性
前导码长度收发两端一致(至少8 symbol)短前导码在低速率下易丢失
IQ极性点对点默认Normal反向会导致完全解不出来
TX基址/RX基址不重叠缓冲区互相覆盖
TxParams功率值匹配PA配置功率过高或过低导致信号劣化
rampTime至少62us包头发射不稳定
TX timeout0或足够大包被截断
TCXO启动时间足够大频率未稳导致误码

每一次排查我都建议把这份表格打印出来,一项项打钩。不要凭记忆,因为LoRa的配置组合太多,任何一项不一致都会导致“corrupted packets”现象。

4.3 双节点收发验证方法:空包、伪随机序列和多速率

单一节点自测很难定位问题是出在发射端还是接收端。我强烈建议准备至少两个节点:一个STM32WL55被测节点,一个SX1262参考节点或另一个STM32WL55。交叉验证分三轮:

第一轮,被测节点发射,参考节点接收。如果参考节点也收到坏包,说明发射端确实有问题。第二轮,参考节点发射,被测节点接收。如果坏包消失,说明问题在发射链路;坏包依旧,则要怀疑被测节点的接收链路或天线。第三轮,两个被测节点互相发,通常能复现出特定速率下的坏包概率。

payload内容不要用全0或全1,因为这类固定pattern在LoRa的bit流里非常容易被误判。建议用伪随机序列,比如LFSR生成一个8字节payload,接收端同步生成同一个序列做校验,能更精确地判断是哪个字节发生了翻转。

多速率测试也很重要。SF7、SF8、SF9、SF10各跑一遍,观察坏包率和速率之间的关系。如果坏包率随SF变大而升高,多半是时钟稳定度或者接收端灵敏度问题;如果SF7坏反而是坏包率高,则要怀疑码间干扰和PA非线性。

4.4 射频前端与电源域专项测试

参数全部核对无误后,坏包还在,就要开始动硬件。我用三个专项测试来缩小范围:

电源测试:用示波器探头接在STM32WL55的VDD和PA供电脚旁边,把探头放到20mV/div,抓发射瞬间的电压跌落和纹波。如果幅度超过100mV,基本可以确定电源有问题。LoRa发射瞬间电流很大,SX126x在14dBm时的PA电流大约20mA左右,但STM32WL55整机在射频发射时电流可以冲到几十毫安。电源走线太细或者去耦电容不够,会导致瞬间跌落。

天线匹配测试:有条件的话用VNA看S11,没条件就用频谱仪看发射频谱。正常发射频谱应该是一个干净的LoRa突发,带宽和设定一致。如果看到旁边有异常杂散或者频谱塌陷,匹配网络一定有问题。

射频开关时序测试:用示波器同时抓射频开关的控制GPIO和DIO1的TX_DONE信号,确认TX期间射频开关一直处于选通状态。如果射频开关控制脚在发送中间被别的逻辑拉低,就会复现我在2.1节描述的那种“包络被切掉”的现象。

5. 三次真实复现与最终修复对比

5.1 复现A:CRC off与显式包头长度不匹配导致的“内容损坏”

第一次复现,坏包特征是CRC错误率约20%,但payload长度几乎都是8字节,偶尔出现长度7或9。对照排查表,我发现自己把收发两端的CRC配置设反了:发射端CRC off,接收端CRC on。发射端发出去的包没有CRC字段,接收端却按“有CRC”去解析,把最后几个payload字节当成CRC吃掉,解出来的内容自然错乱。

这个问题的根子是我在初始化代码里,发射路径写的是SetPacketParams(CRC_OFF),接收路径写的是SetPacketParams(CRC_ON),本意是接收端强制校验,但发射端又没开CRC,等于两边格式不匹配。修正方法是收发两端CRC模式保持一致,要么都开,要么都关。LoRa本身自带CRC能力,建议都开。

5.2 复现B:TX timeout设为100的截断性发射

第二次复现是最让我无语的一次。代码审查时发现,上一任同事在Radio.Send()里设置TX timeout时,为了“避免阻塞太长时间”,直接把timeout写成了100。前面分析过,这个数值对应约1.56ms,在SF7、125kHz带宽下,一个symbol就有1.024ms,整包10字节在显式包头下需要大约14个symbol以上,也就是14ms左右。射频内核会在发送中途自动退出,空中信号被截断。

示波器抓到的频谱包络是典型的截断形状,接收端有时能收到payload的前半部分加上一堆异常数据,CRC每次都挂。我把timeout改成0并依赖DIO1中断判断发送完成后,坏包率直接归零。

5.3 复现C:电源毛刺造成的瞬时失锁

第三次复现出现在换用老化电池之后。坏包不是固定概率,而是和电池电量相关,电量充足时没有,电量下降后逐渐增多。用示波器抓电源轨,发射瞬间VDD跌落了约450mV,明显超过规格。

我做的修复是在PA供电和VDD之间各加了一颗10uF低ESR陶瓷电容,并把射频发射过程中的瞬时电流峰值通过软件做了削峰处理——在发送前的50us内先把射频内核唤醒、把PA电源稳定好,而不是在发送瞬间同时唤醒和抬功率。经过这两步,发射瞬间的电压跌落降到了100mV以内,坏包消失。

5.4 修复后的验证数据

修复完成后我做了连续72小时老化测试,每分钟发送60包,总共25.9万包,接收端只出现2包CRC错误(这两包还是因为同频干扰,RSSI非常低)。整体误包率从初始的约8%降到了0.001%以下。

我还把同样的发射固件换到了另一块没有射频开关、直接走SMA的测试板上做交叉验证,坏包数同样为0,这说明问题确实是发射链路时序和电源管理,而非LoRa协议本身。

最后的工程体会

抛开寄存器配置,这次排查给我的最大收获是:LoRa通信里,“发射端发出去”不等于“空中数据包是完整的”。从STM32WL55的SPI写buffer,到射频内核内部FS状态机的切换,再到PA功率建立、TCXO频率稳定、射频开关选通,任何一个环节的时序偏差,最终都会在接收端表现为一个corrupted packet。与其盯着接收端疯狂重传,不如回发射端看物理波形。

另外强烈建议每次画板时,在射频开关控制脚、PA电源脚、DIO1这几个关键测试点上预留0欧电阻断点或测试焊盘。我在这次排查中来回飞线飞了很多次,如果最初就预留了测试点,至少能省半天时间。对于所有正在调STM32WL55的人,别急着怀疑芯片本身,先怀疑你的电源、时序和射频前端——这颗芯片的LoRa内核本身是很稳的。

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

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

立即咨询