起因,由于cx32l003没有DMA功能,所以采取软模串口空闲中断(定时器实现)。
前言
CX32L003 是一款低成本、高性价比的 ARM Cortex-M0+ 内核 MCU,广泛应用于家电、电机控制、传感器采集等嵌入式场景。由于该芯片没有 DMA 功能,串口接收无法借助硬件搬运数据,因此本文采用「软模串口空闲中断(定时器实现)」方案,在串口空闲时通过信号量唤醒指定线程处理接收到的数据。
在压力测试过程中,串口出现「失灵」现象。通过仿真定位发现,串口接收中断标志位置 1 后一直无法清除,导致中断服务函数被反复进入、系统卡死。本文基于官方 SDK 源码,深入分析 HAL_UART_IRQHandler 中断服务函数的实现细节,定位根因并给出修复方案,供遇到同类问题的开发者参考。
下面是本文的整体流程总览:
1. 问题现象与定位
1.1 软模串口空闲中断方案
由于 CX32L003 没有 DMA 功能,串口接收采用「定时器模拟空闲中断」的方式实现:
- 每次串口空闲时,发送信号量给指定线程处理刚刚接收到的数据。
- 处理数据时,需要经过以下操作:
- 关闭串口中断,终止串口接收。
- 处理数据。
- 打开串口中断,打开串口接收。
软模串口空闲中断的处理流程如下:
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中断服务函数,逐行分析:
- 首先读取
SCON寄存器到isrflags和cr1its。 - 判断接收完成中断条件:
(isrflags & UART_INTSR_RXNE) != RESET且(cr1its & UART_CR1_RXNEIE) != RESET。 - 若条件成立,读取
SBUF清除标志位;否则进入发送中断判断分支。
通过分析确认:关闭中断接收只是关闭了「接收完成中断使能」,并没有关闭「接收完成中断标志位」,而标志位由硬件控制,无法通过软件直接清除,只能通过读取SBUF寄存器来清除。这就是导致死循环的根本原因。
2. 根因分析与修复方案
2.1 官方 SDK 源码分析
通过对官方 SDK 分析,问题定位在CX32L003_HAL_Driver\Src\cx32l003_hal_uart.c的HAL_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,避免发送中断标志位残留导致类似问题。 |
下面是修复前后中断判断逻辑的对比:
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,导致一直进入中断服务,无法退出。
通过对官方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);}