[单片机][cx32][填坑日记] 对串口uart进行压力测试会死机
2026/9/14 11:06:06 网站建设 项目流程

起因,由于cx32l003没有DMA功能,所以采取软模串口空闲中断(定时器实现)。

前言

CX32L003 是一款低成本、高性价比的 ARM Cortex-M0+ 内核 MCU,广泛应用于家电、电机控制、传感器采集等嵌入式场景。由于该芯片没有 DMA 功能,串口接收无法借助硬件搬运数据,因此本文采用「软模串口空闲中断(定时器实现)」方案,在串口空闲时通过信号量唤醒指定线程处理接收到的数据。

在压力测试过程中,串口出现「失灵」现象。通过仿真定位发现,串口接收中断标志位置 1 后一直无法清除,导致中断服务函数被反复进入、系统卡死。本文基于官方 SDK 源码,深入分析 HAL_UART_IRQHandler 中断服务函数的实现细节,定位根因并给出修复方案,供遇到同类问题的开发者参考。

下面是本文的整体流程总览:

CX32L003 无 DMA 功能

采用软模串口空闲中断
(定时器实现)

串口空闲时发送信号量
唤醒线程处理数据

压力测试发现串口失灵

仿真定位:接收中断标志位
无法清除,进入死循环

分析 HAL_UART_IRQHandler
源码,定位根因

修改中断判断逻辑
(&& 改为 ||)

加入 TX 中断判断

解决中断死循环问题

1. 问题现象与定位

1.1 软模串口空闲中断方案

由于 CX32L003 没有 DMA 功能,串口接收采用「定时器模拟空闲中断」的方式实现:

  • 每次串口空闲时,发送信号量给指定线程处理刚刚接收到的数据。
  • 处理数据时,需要经过以下操作:
    1. 关闭串口中断,终止串口接收。
    2. 处理数据。
    3. 打开串口中断,打开串口接收。

软模串口空闲中断的处理流程如下:

串口接收数据

定时器检测到
串口空闲?

发送信号量
唤醒指定线程

关闭串口中断
终止串口接收

处理接收到的数据

打开串口中断
恢复串口接收

1.2 压力测试暴露的问题

经过压力测试,串口发送「失灵」。通过仿真得知,串口接收中断标志位置 1,导致一直进入中断服务,无法退出。

1.3 排查过程

下面详细描述通过仿真定位问题的完整排查步骤,供遇到同类问题的开发者参考。

第一步:挂载仿真器,复现问题

在压力测试环境下,使用 J-Link 仿真器连接 CX32L003,在串口持续收发数据的过程中复现「串口失灵」现象。当串口发送停止响应时,立即暂停程序,观察当前执行位置。

第二步:查看中断标志位

在仿真器的寄存器窗口中,查看串口控制寄存器SCON的值。重点关注以下两个标志位:

  • UART_INTSR_RXNE(接收完成标志位):正常情况下,接收一帧数据后该位置 1,读取数据寄存器SBUF后由硬件自动清零。
  • UART_CR1_RXNEIE(接收完成中断使能位):软件可通过CLEAR_BIT关闭。

通过仿真发现,串口「失灵」时UART_INTSR_RXNE一直为 1,且程序反复进入HAL_UART_IRQHandler中断服务函数,无法退出。

第三步:确认标志位无法清除

在中断服务函数入口处设置断点,单步执行并观察SCON寄存器的变化:

voidHAL_UART_IRQHandler(UART_HandleTypeDef*huart){uint32_tisrflags=READ_REG(huart->Instance->SCON);uint32_tcr1its=READ_REG(huart->Instance->SCON);/* 断点处:观察 isrflags 与 cr1its 的值 */if(((isrflags&UART_INTSR_RXNE)!=RESET)&&((cr1its&UART_CR1_RXNEIE)!=RESET))/* 问题点:使用 && */{/* 读取数据寄存器,清除接收完成标志位 */huart->Instance->SBUF;...}...}

单步执行后发现:当UART_CR1_RXNEIE被软件关闭后,UART_INTSR_RXNE仍然为 1。由于判断条件使用&&,即使标志位置位,只要使能位被关闭,条件就不成立,代码不会执行huart->Instance->SBUF;这一行,标志位永远无法被清除,中断服务函数被反复进入,形成死循环。

第四步:逐步分析 HAL_UART_IRQHandler 源码

打开官方 SDK 中的CX32L003_HAL_Driver\Src\cx32l003_hal_uart.c,定位到HAL_UART_IRQHandler中断服务函数,逐行分析:

  1. 首先读取SCON寄存器到isrflagscr1its
  2. 判断接收完成中断条件:(isrflags & UART_INTSR_RXNE) != RESET(cr1its & UART_CR1_RXNEIE) != RESET
  3. 若条件成立,读取SBUF清除标志位;否则进入发送中断判断分支。

通过分析确认:关闭中断接收只是关闭了「接收完成中断使能」,并没有关闭「接收完成中断标志位」,而标志位由硬件控制,无法通过软件直接清除,只能通过读取SBUF寄存器来清除。这就是导致死循环的根本原因。

2. 根因分析与修复方案

2.1 官方 SDK 源码分析

通过对官方 SDK 分析,问题定位在CX32L003_HAL_Driver\Src\cx32l003_hal_uart.cHAL_UART_IRQHandler中断服务函数中,主要把"&&"改为"||"

2.2 根因

关闭中断接收,只是关闭「接收完成中断使能」,并没有关闭「接收完成中断标志位」,第二个无法关闭,由硬件控制。这就导致标志位一直无法清除,进入死循环。

2.3 解决方案

解决思路:只要有接收标志位,就读取缓冲区。

  • 使用SET_BIT(huart->Instance->SCON, (UART_SCON_REN))使能串口接收。
  • 使用CLEAR_BIT(huart->Instance->SCON, (UART_SCON_REN))关闭串口接收。
  • HAL_UART_IRQHandler中断服务函数中加入 TX 中断判断:
elseif(((isrflags&UART_INTSR_TI)!=RESET)){__HAL_UART_CLEAR_FLAG(huart,UART_FLAG_TC);}

下面是修复前后HAL_UART_IRQHandler中断服务函数的完整代码对比:

修改前(使用&&,存在死循环风险):

voidHAL_UART_IRQHandler(UART_HandleTypeDef*huart){uint32_tisrflags=READ_REG(huart->Instance->SCON);uint32_tcr1its=READ_REG(huart->Instance->SCON);/* 接收中断处理:只有接收完成中断使能 且 接收完成标志位置位 才进入 */if(((isrflags&UART_INTSR_RXNE)!=RESET)&&((cr1its&UART_CR1_RXNEIE)!=RESET))/* 问题点:使用 && */{/* 读取数据寄存器,清除接收完成标志位 */huart->Instance->SBUF;/* 处理接收到的数据 */...}/* 发送中断处理 */elseif(((isrflags&UART_INTSR_TI)!=RESET)){__HAL_UART_CLEAR_FLAG(huart,UART_FLAG_TC);}}

修改后(使用||,修复死循环问题):

voidHAL_UART_IRQHandler(UART_HandleTypeDef*huart){uint32_tisrflags=READ_REG(huart->Instance->SCON);uint32_tcr1its=READ_REG(huart->Instance->SCON);/* 接收中断处理:只要接收完成标志位置位 或 接收中断使能,就读取缓冲区 */if(((isrflags&UART_INTSR_RXNE)!=RESET)||((cr1its&UART_CR1_RXNEIE)!=RESET))/* 修复点:改为 || */{/* 只要有接收标志位,就读取缓冲区,确保标志位被清除 */huart->Instance->SBUF;/* 处理接收到的数据 */...}/* 发送中断处理 */elseif(((isrflags&UART_INTSR_TI)!=RESET)){__HAL_UART_CLEAR_FLAG(huart,UART_FLAG_TC);}}

修改说明:

位置修改前修改后作用
接收中断判断条件&&(接收完成中断使能接收完成标志位置位)||(接收完成中断使能接收完成标志位置位)关闭中断接收时,只是关闭「接收完成中断使能」,并没有关闭「接收完成中断标志位」(该位由硬件控制,无法软件清除)。使用&&时,一旦使能位被关闭而标志位仍为 1,条件永远不成立,标志位无法通过读取缓冲区清除,导致中断死循环。改为||后,只要标志位置位就读取缓冲区,从而清除标志位,退出中断。
发送中断处理加入else if (((isrflags & UART_INTSR_TI) != RESET))分支处理发送完成中断,清除发送完成标志位UART_FLAG_TC,避免发送中断标志位残留导致类似问题。

下面是修复前后中断判断逻辑的对比:

修改前(使用 &&)

使能位被关闭,
标志位仍为 1

修改后(使用 ||)

接收完成中断使能
或 接收完成标志位置位

只要有接收标志位
就读取缓冲区

清除标志位
退出中断

接收完成中断使能
且 接收完成标志位置位

读取缓冲区
清除标志位

条件永远不成立
标志位无法清除

中断死循环
系统卡死

根因:关闭中断接收只关闭使能位
标志位由硬件控制无法软件清除

3. 总结

本文从 CX32L003 无 DMA 的硬件限制出发,介绍了软模串口空闲中断的实现方案,并针对压力测试中出现的串口「失灵」问题,深入分析了HAL_UART_IRQHandler中断服务函数的实现,定位到「接收完成中断标志位无法清除」这一根因,最终通过修改中断判断逻辑并加入 TX 中断处理,解决了中断死循环问题。希望本文能为使用 CX32L003 或其他无 DMA 特性 MCU 的开发者提供参考。

参考资料

  • CX32L003 官方 SDK:CX32L003_HAL_Driver\Src\cx32l003_hal_uart.c
  • CX32L003 数据手册与参考手册
  • ARM Cortex-M0+ 内核编程手册

每次串口空闲时,发送信号量给指定线程处理刚刚接收到数据。
处理数据时,需要经过以下操作:

  1. 关闭串口中断,终止串口接收
  2. 处理数据
  3. 打开串口中断,打开串口接收

经过压力测试,发送串口"失灵"了.通过仿真得知,串口接收中断标志位置1,导致一直进入中断服务,无法退出。
通过对官方sdk分析。

CX32L003_HAL_Driver\Src\cx32l003_hal_uart.c

HAL_UART_IRQHandler 中断服务函数

主要把 “&&” 改 “||”.

原因:关闭中断接收,只是关闭[接收完成中断使能],并没有关闭[接收完成中断标志位],第二个无法关闭,硬件控制。这就导致标志位一直无法清除,进入死循环。


解决:只要有接收标志位,就读取缓冲区。

SET_BIT(huart->Instance->SCON, (UART_SCON_REN));

CLEAR_BIT(huart->Instance->SCON, (UART_SCON_REN));


HAL_UART_IRQHandler 中断服务函数加入TX中断判断

elseif(((isrflags&UART_INTSR_TI)!=RESET)){__HAL_UART_CLEAR_FLAG(huart,UART_FLAG_TC);}

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

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

立即咨询