STM32L152自定义UART协议主从心跳保活稳定性异常排查
问题原因分析
1. 状态机分支的代码逻辑错误
你提供的STATE_CONNECTED分支代码存在缩进/花括号缺失的问题:
if(WaitFullPacket() == TRUE) if (ReceivePacket() == CORRECT && RX_Buffer.buffer[3] == KEEP_ALIVE) t_event_silence = GlobalTick; break;
在C语言中,无花括号的if仅作用于紧跟的第一行代码。实际执行逻辑是:
- 若
WaitFullPacket()为真,进入内层if,仅当心跳包校验正确时才更新t_event_silence - 无论内层
if是否成立,break;都会执行,导致状态机每次进入STATE_CONNECTED分支后立即跳出,无法持续监听UART接收。这会导致心跳包的接收处理被频繁中断,t_event_silence无法及时更新,最终触发TSILENT超时断开。
2. 定时器中断的心跳发送逻辑缺陷
- 未检查连接状态:
GlobalTick == 1的分支会在系统启动后立即发送心跳包,但此时设备可能还未进入STATE_CONNECTED状态,无效数据包会干扰主从机的连接流程。 - 未校验DMA发送状态:直接调用
HAL_UART_Transmit_DMA时,若上一次DMA发送未完成,会覆盖发送缓冲区数据或导致UART硬件冲突,引发丢包或发送错误,进而触发超时断开。
3. 超时判断的时间窗口容错不足
设置TSILENT = TPOLL + 100ms时,时间窗口仅比心跳间隔多100ms,但实际系统中存在以下延迟因素:
- UART接收的帧同步、校验耗时
- 状态机阻塞式接收(
HAL_UART_Receive的10ms超时)导致的处理延迟 - 定时器中断的响应延迟
这些延迟叠加后,可能导致t_event_silence的更新时机晚于TSILENT阈值,触发误断开。而扩大到TPOLL + 1000ms时,足够覆盖所有延迟,因此问题暂时消失。
解决办法
1. 修复状态机分支的逻辑错误
给if语句添加花括号,移除不必要的break,确保状态机可以持续处理STATE_CONNECTED状态下的接收操作:
case STATE_CONNECTED: RX_BufferReset(); HAL_UART_Receive(&huart3, RX_Buffer.buffer, MAX_PKT_BYTES, 10); if(WaitFullPacket() == TRUE) { if (ReceivePacket() == CORRECT) { // 收到任何有效数据包都更新超时标记,不止心跳包 t_event_silence = GlobalTick; } } // 移除break,让状态机循环停留在该分支持续监听接收 // break;
2. 优化定时器中断的心跳发送逻辑
- 移除未进入连接状态时的无效心跳发送
- 添加DMA发送状态校验,避免重复发送冲突
- 仅在连接状态下触发心跳发送:
void TIM3_IRQHandler(void) { HAL_TIM_IRQHandler(&htim3); GlobalTick++; // 仅在连接状态下发送心跳,且检查DMA是否空闲 if((GlobalTick - t_event_poll >= TPOLL) && (state == STATE_CONNECTED)) { if(HAL_UART_GetState(&huart3) != HAL_UART_STATE_BUSY_TX) { TX_BufferReset(); SendPacket(KEEP_ALIVE); HAL_UART_Transmit_DMA(&huart3, TX_Buffer.buffer, TX_Buffer.count); t_event_poll = GlobalTick; } } if((GlobalTick - t_event_silence >= TSILENT) && (state == STATE_CONNECTED)) { state = STATE_DISCONNECTED; ResetTime(); } }
3. 优化UART接收方式(可选但推荐)
将阻塞式的HAL_UART_Receive改为DMA接收+中断回调的非阻塞方式,避免状态机被10ms超时阻塞,提升接收处理的实时性:
- 初始化时启动DMA接收:
HAL_UART_Receive_DMA(&huart3, RX_Buffer.buffer, MAX_PKT_BYTES); - 在
HAL_UART_RxCpltCallback中处理数据包校验和t_event_silence更新
4. 合理设置超时阈值
保持TSILENT为TPOLL + 200~300ms即可覆盖系统延迟,无需扩大到1000ms。同时将超时判断从>改为>=,避免因整数溢出或计数误差导致的误判。
内容的提问来源于stack exchange,提问作者Matteo
相关产品推荐
相关产品推荐

