接前一篇文章:PY32F系列MCU在串口接收时跑飞的根因分析(1)
二、问题分析
4. 实验确认
上一回在分析排查板子接收串口错误数据跑飞的问题时,定位到了是串口中断处理程序中else分支代码所导致的问题。再来回顾一下。完整的串口接收中断处理代码(示例代码)如下:
cpp
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);
}
问题出在default中的else分支中,重点怀疑是memset一行代码导致的问题。

为了验证就是是否是这一行代码引发的问题,将这一行注释掉。编译、烧录,再次运行,仍然发送错误数据以及正确数据,这次不会出现跑飞的情况了,最多就是先发送错误帧的情况下,接下来发送的一帧正确数据也会被"带坏",但再接下来就正常了。这就是说,确实如之前猜测,问题出在了memset这一行。
但正如上一回所讲的,memset函数是系统标准库代码,笔者也没有用错,也不存在数组溢出的问题。为什么会导致程序跑飞呢?
三、根因分析
这个问题确实比较令人费解。没办法,只能求助于AI工具了。笔者之前在"PY32F系列MCU在OTA时App区概率性跑不起来的根因分析"系列文章中曾经提到过Cursor,当时用它来正向定位解决了OTA遇到的跑飞问题。当时在那个系列文章中曾经提到,后续使用Cursor工具又解决了另一个棘手问题(PY32F系列MCU在OTA时App区概率性跑不起来的根因分析(6)-CSDN博客),

就是本系列的这个问题了。
Cursor给出的根因分析如下:
串口中断里这条memset会把中断拖过一个字节的时间,USART溢出之后中断就再也不退出。memset本身没有把内存写坏。
由于工程开了MicroLIB,因此memset这一句编出来是 __aeabi_memclr,给uart1_rx_buf清 256字节。链接信息里这个函数栈占用是0,只是一段字节循环。串口波特率是1500000(1.5M),一个字节大约6.7us。PY32F040的USART没有FIFO,清256字节远长于这个间隔,后面的字节进不了数据寄存器,硬件置上溢出标志ORE。
中断函数末尾又调用了HAL_UART_IRQHandler()。ORE已经置位、并且RXNE中断是开着的时候,这个函数读完状态就return,没有按"先读SR、再读DR"把ORE清掉。ORE在RXNE中断开启时会一直产生中断请求,所以一返回又马上再进USART1_IRQHandler。主循环再也得不到执行,表现(表象)就是跑飞。没有memset这行时,收一个字节的处理时间短于字节间隔,ORE起不来,所以只有加上它才出现。
四、最终解决
解决方案很简单,就是注释掉memset这一行就可以了。
因为中断里也不需要清这256字节。uart1_rx_cnt = 0之后,下一帧会从下标0重新覆盖。主循环处理完命令后本来就会memset这块缓冲。
五、总结与感悟
说实在的,如果说之前那个OTA问题笔者还能够自己想明白(虽然比AI时间长得多,但最终能够根据结果反推,进而想清楚),那么这个问题就让笔者看到了自己这二十几年在MCU层面的不足了。以我的功力,能够定位到memset这一行,也能够通过将其封掉来解决问题,但把这个问题想得如此透彻,以目前的功力来说无论如何做不到。还是要将基础再打牢一些,甚至得"二次学艺",才能弥补这种理论基础上的"先天不足"了。
笔者因为这两次败于AI,确实一度感到沮丧甚至心灰意冷。但后来使用Cursor分析了基于SoC的Linux源码,对于这一块Cursor表现得比较弱,远不如MCU领域所展现出来的专业和精深。看来短时间(几年)之内程序员们还不至于没饭吃,只要他们努力提高自身修为,还是能够与AI一较高下的。