☰
PY32F系列MCU在串口接收时跑飞的根因分析(1)
2026/10/11 6:54:47 网站建设 项目流程

一、问题现象

1. 概述

测试(其实是硬件开发)人员在测试的时候偶然发现了一个问题:使用串口调试助手发数据(十六进制方式),一旦中间有一个数据少了一个0,则会引发程序跑飞。

2. 详情

(1)复现步骤

PC通过USB转串口工具连接板子,打开串口调试助手软件,选择“HEX发送”,以十六进制发送数据。在发送数据框中故意删除一个0。如下图所示:

  • 正常数据

  • 人为错误数据

可以看到,原本是发送01,结果少了一个0,直接发1。

(2)期望结果

板子中的程序对于错误数据能够过滤,不受干扰。

(3)实际结果

板子中的程序直接跑飞。

二、问题分析

1. 初步分析

先要看一下,认为修改数据之后,发出来的数据是什么。

经过检查,正常发送的的数据是0x55 0xAA 0x00 0x10 0x04 0x02 0x00 0x01 0x00 0x00 0xCC,长度为11个字节;而错误的数据是0x55 0xAA 0x00 0x10 0x04 0x02 0x00 0x10 0x00 0x0C,长度为10个字节。

也就是说,板子在收到正常11个字节的数据时没有问题,而收到10个字节的错误数据就会导致程序跑飞了。

2. 进一步跟踪

进一步从宏观现象观察发现,当发送错误数据时,第一次程序只是没有回应,代码并未跑飞(通过指示灯以及按键功能确定)。再次发送数据(无论是正确的还是错误的)才会导致程序跑飞(指示灯不闪,按键也失效)。而且是每次必现。

3. 深入分析

带着观察到的问题现象,深入代码,尝试找到与现象对应的代码段及缺陷。

既然问题是与串口收发数据相关的,那么无疑与值对应的是串口接收数据代码。示例代码如下:

void USART1_IRQHandler(void) { __IO uint8_t data; static uint16_t length = 0; if (__HAL_UART_GET_IT_SOURCE(&Uart1Handle, UART_IT_RXNE) != RESET) { data = Uart1Handle.Instance->DR; if (uart1_rx_cnt < (UART_RX_BUFFER_SIZE - 1)) { switch (uart1_rx_cnt) { case 0: if (data == 0x55) //frame header 1 uart1_rx_buf[uart1_rx_cnt++] = data; else uart1_rx_cnt = 0; break; case 1: if (data == 0xAA) //frame header 2 uart1_rx_buf[uart1_rx_cnt++] = data; else uart1_rx_cnt = 0; break; case 2: uart1_rx_buf[uart1_rx_cnt++] = data; break; case 3: …… break; case 4: …… break; default: uart1_rx_buf[uart1_rx_cnt] = data; if (uart1_rx_cnt >= 3 + length + 2 + 2 - 1) { if (uart1_rx_buf[uart1_rx_cnt] == 0xCC) { uart1_rx_cnt++; recv_cmd1 = 1; } else { uart1_rx_cnt = 0; memset((uint8_t *)uart1_rx_buf, 0x00, sizeof(uart1_rx_buf)); } break; } uart1_rx_cnt++; break; } } else { uart1_rx_cnt = 0; } __HAL_UART_CLEAR_FLAG(&Uart1Handle, UART_IT_RXNE); } HAL_UART_IRQHandler(&Uart1Handle); }

笔者使用的是经典的串口接收处理程序。通过双帧头0x55和0xAA进行数据过滤筛除,确保每次收到的必定是0x55 0xAA开头的数据。单帧尾0xCC以及数据长度用于确认一帧结束,从而得到完整的一帧数据。

这样就能够和观测到的宏观现象初步对应起来了:

  • 当正常发送数据时,帧头、帧尾和数据长度都对,这样就认为收到了完成的一帧,置位标志位进行处理。
  • 当发送错误数据时,帧头对、数据长度也对,但是发送的数据少了一个字节,导致没有认为收完了完整的一帧,还在继续等待接收数据。注意此时程序还能够正常运行;而当再次发送数据时,第1个字节(0x55)导致了接收数据长度满足了要求,从而进入到了帧数据处理。代码片段如下:
if (uart1_rx_cnt >= 3 + length + 2 + 2 - 1) { if (uart1_rx_buf[uart1_rx_cnt] == 0xCC) { uart1_rx_cnt++; recv_cmd1 = 1; } else { uart1_rx_cnt = 0; memset((uint8_t *)uart1_rx_buf, 0x00, sizeof(uart1_rx_buf)); } break; }

由于帧尾并不是0xCC,因此会走到以上代码中的else分支。经过了else分支之后,代码就跑飞了。

else分支中的代码很简单,一共就两行。第1行将uart1_rx_cnt设置为0应该不会产生这么大影响,那么问题极有可能就是出在了memset这一行代码上。但是memset是系统标准库代码,笔者也没有用错,也不存在数组溢出的问题。为什么会导致程序跑飞呢?

请看下回。

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

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

立即咨询