ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

PY32F系列MCU在串口接收时跑飞的根因分析(1)

PY32F系列MCU在串口接收时跑飞的根因分析(1) 一、问题现象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是系统标准库代码笔者也没有用错也不存在数组溢出的问题。为什么会导致程序跑飞呢请看下回。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进