Raspberry Pico对接RS485 Board时随运行时长增加出现数据包重叠故障
问题背景
- 基于Raspberry Pico搭建RS485总线数据记录仪,功能为嗅探总线数据并存储到SD卡,基础功能可正常运行,但存在随运行时长增加,错误数据读取频率逐步升高的故障。
- 标准数据包固定长度80字节,固定以
0x02为帧头、0x03为帧尾,正常数据包示例:
[2, 140, 130, 128, 137, 128, 0, 0, 0, 128, 130, 135, 130, 173, 178, 128, 178, 128, 178, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 194, 188, 128, 230, 129, 184, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 56, 3]
- 故障表现为数据包重叠:异常包中会在未收满前序包时出现新的合法
0x02帧头,新包以0x03正常结尾,截断前序未完成接收的数据包,异常数据包示例:
[2, 140, 128, 128, 137, 128, 0, 0, 0, 160, 136, 157, 136, 173, 167, 129, 181, 129, 181, 129, 128, 133, 128, 129, 128, 128, 128, 175, 155, 147, 160, 128, 128, 128, 128, 221, 255, 249, 255, 133, 188, 128, 230, 129, 192, 128, 192, 128, 128, 128, 152, 0, 2, 140, 128, 128, 137, 128, 0, 0, 0, 168, 136, 165, 136, 173, 167, 129, 181, 129, 181, 129, 128, 133, 128, 129, 128, 128, 128, 153, 156, 253, 159, 128, 128, 249, 255, 242, 255, 149, 128, 133, 188, 128, 230, 129, 192, 128, 192, 128, 128, 128, 152, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 67, 3]
- 当前使用MicroPython编写UART读取逻辑,核心逻辑为逐字节读取,等待
0x02帧头后持续缓存字节,直到检测到0x03帧尾即判定单包接收完成,现有实现代码:
uart1 = UART(0, baudrate=38400, tx=Pin(0), rx=Pin(1)) led = Pin(25, Pin.OUT) while True: while uart1.any() > 0: data_raw = uart1.read(1) if data_raw == b'\x02': line = bytearray() led.toggle() line += data_raw R_EOF = False while not R_EOF: data_raw = uart1.read(1) if data_raw == b'\x03': line += data_raw R_EOF = True led.toggle() elif data_raw == None: pass else: line += data_raw
- 初步怀疑硬件焊接不良,但该假设无法解释故障频率随运行时长增加逐步升高的特征,需排查故障根因并给出解决方案。
故障根因分析
- 现有UART读取逻辑采用阻塞设计是核心诱因:内层收包的
while not R_EOF循环无退出条件兜底,一旦进入收包流程,未读到0x03就会持续循环,未做长度校验、超时控制,也未处理接收过程中出现新帧头的异常场景。 - Raspberry Pico UART硬件接收缓冲区默认仅128字节,当收包逻辑阻塞、SD卡写入等操作占用CPU时,缓冲区中未及时读取的字节会被新到的数据覆盖,产生丢字节问题。一旦帧尾
0x03丢失,程序会持续卡在收包状态,将后续多个数据包的内容全部读入同一缓存,直到碰到下一个0x03才退出,直接表现为观测到的数据包重叠现象。 - 故障频率随运行时长升高的特征与SD卡写入特性直接相关:随运行时间增加,SD卡存储的文件体积不断增大,每次写入的寻址、写入耗时逐步上升,UART缓冲区溢出丢字节的概率同步升高,故障出现频率随之上涨。硬件焊接不良导致的故障通常表现为随机、与运行时长无相关性的丢包,与该特征不符,可基本排除。
修复方案
- 去掉阻塞式的内层收包循环,改用状态机逐字节处理,所有读取逻辑都在最外层非阻塞循环中运行,避免收包阶段卡死
- 增加固定长度校验:已知标准包固定80字节,收到帧头后计数接收字节数,达到80字节时校验最后一个字节是否为
0x03,符合则判定为有效包,否则直接丢弃当前缓存重置状态 - 增加接收超时机制:收到帧头后启动计时器,超过单包最大传输时间(38400波特率下80字节传输耗时约20ms,超时阈值可设为50ms)还没收到合法帧尾,直接重置接收状态
- 收包过程中如果检测到新的
0x02帧头,直接丢弃之前未接收完成的残包,从新帧头开始重新缓存 - 扩大UART接收缓冲区,初始化UART时增加
rxbuf=256参数将软件缓冲区扩到256字节,降低溢出概率 - 解耦UART读取与SD卡写入逻辑:单独开辟内存队列,收到有效包先存入队列,主循环优先处理UART读取,空闲时再批量把队列里的数据写入SD卡,避免SD写入操作阻塞UART读取导致丢包
修复后参考实现代码:
from machine import UART, Pin import time # 初始化UART,扩大接收缓冲区 uart1 = UART(0, baudrate=38400, tx=Pin(0), rx=Pin(1), rxbuf=256) led = Pin(25, Pin.OUT) # 接收参数配置 PACKET_LEN = 80 RX_TIMEOUT = 50 # 单位:ms recv_buff = bytearray() recv_start_tick = 0 save_queue = [] while True: # 非阻塞读取所有可用字节 while uart1.any() > 0: data_raw = uart1.read(1) if not data_raw: continue byte = data_raw[0] # 检测到帧头,重置接收状态 if byte == 0x02: recv_buff = bytearray() recv_buff.append(byte) recv_start_tick = time.ticks_ms() led.toggle() continue # 处于收包状态时处理后续字节 if len(recv_buff) > 0: recv_buff.append(byte) # 达到固定长度校验帧尾 if len(recv_buff) == PACKET_LEN: if byte == 0x03: # 有效包入队,等待写入SD卡 save_queue.append(bytes(recv_buff)) led.toggle() # 无论包是否有效,重置接收状态 recv_buff = bytearray() # 超时重置,避免卡死 elif time.ticks_diff(time.ticks_ms(), recv_start_tick) > RX_TIMEOUT: recv_buff = bytearray() # 空闲时批量写入SD卡,单次写入不超过队列中10个包,避免长时间阻塞 if len(save_queue) > 0 and uart1.any() == 0: # 此处补充SD卡写入逻辑,将save_queue中前N个包写入文件后移除对应元素 pass
内容的提问来源于stack exchange,提问作者NicoCaldo
相关产品推荐
相关产品推荐

