STM32串口中断卡死主循环问题分析

本文针对STM32主控程序中USART2接收中断导致主循环卡死的问题进行详细分析,揭示了问题根源在于Overrun Error,并提供了解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在一项目中,使用STM32作为主控,程序运行一段时间后概率出现主循环卡死现象。


问题分析如下:

1、程序USART2不停接收并处理串口数据,波特率115200;

2、主循环卡死;

3、USART1中断及TIM2中断响应函数运行正常;(USART1及TIM2中断优先级均比USART2高)

4、出现现象后,拔掉USART2的接收数据线,现象不能回复正常;

5、出现现象后,拔掉后再插入USART2的接收数据线,现象不能回复正常;

6、并未出现HardFault现象;


基于以上4点,可能原因如下:

1、USART2接收中断标志没有清除;

2、堆栈数据溢出,导致程序异常;

        3、USART2中断重入导致异常;

4、USART2中断函数被异常响应;

        5、USART2中断ERR;

对于以上可能原因一一分析:

1、中断接收标志清楚问题:

(1)USART2接收中断响应函数如下:

[cpp]  view plain  copy
  1. void USART2_Istr(void)  
  2. {    
  3.     if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)  
  4.     {     
  5.         USART_ClearFlag(USART2, USART_FLAG_RXNE);  
  6.         USART_ClearITPendingBit(USART2, USART_IT_RXNE);  
  7.         Data = USART_ReceiveData(USART2);  
  8.         //Process Data  
  9.     }  
  10. }  

(2)出现现象后,通过Usart1中断获取到如下信息:

a. USART_GetITStatus(USART2,  USART_IT_RXNE)  == RESET

b. USART_GetFlagStatus(USART2,  USART_FLAG_RXNE)  == RESET

c. 执行USART_ClearFlag(USART2, USART_FLAG_RXNE)及 USART_ClearITPendingBit(USART2, USART_IT_RXNE)后无法恢复正常;

 结论:与USART2 RXNE中断标志无关。


2、堆栈数据溢出,导致程序异常;

(1)使用2倍栈空间,问题存在,概率不会降低;

(2)使用0.5倍栈空间,问题存在,概率不会提高;

(3)使用0.25倍栈空间,程序运行进入HardFault;

结论:与堆栈无关。


3、USART2中断重入导致异常;

(1)使用标志法,确认出现问题时,中断响应函数没有重入;

结论:中断响应函数没有重入。


4、USART2中断函数被异常响应;

(1)USART2中断函数可以被正常调用,只是不停进入中断响应函数,卡死主循环;

(2)检查程序Map,没发现与中断响应函数地址相同的函数;

(3)检查中断向量表,没发现异常;

结论:中断函数没有被异常调用;


5、USART2中断ERR;

(1)关闭USART2中断,主循环恢复正常;

(2)启动USART2中断,主循环卡死;

(3)获取到DR=0x0000;

(4)USART_GetITStatus取到:RXNE=0,PE=0,TXE=0,TC=0,IDLE=0,LBD=0,CTS=0,ERR=0,ORE=0,NE=0,FE=0;

(5)通过USART_ClearITPendingBit清除CTS,LBD,TXE,TC,RXNE,IDLE,ORE,NE,FE,PE均无法恢复正常;

(6)通过USART_GetFlagStatus:

  a.第一次:CTS=0,LBD=0,TXE=1,TC=1,RXNE=0,IDLE=1,ORE=1,NE=0,FE=0,PE=0

b.第二次:CTS=0,LBD=0,TXE=1,TC=1,RXNE=0,IDLE=0,ORE=0,NE=0,FE=0,PE=0

c.第三次:CTS=0,LBD=0,TXE=1,TC=1,RXNE=0,IDLE=0,ORE=0,NE=0,FE=0,PE=0

(7)通过USART_ClearFlag清除CTS,LBD,TXE,TC,RXNE,IDLE,ORE,NE,FE,PE均无法恢复正常;

分析:

(1)为什么通过USART_GetITStatus获取了所有中断标志,均为RESET(TC、TXE中断没开),还会进中断?

(2)为什么通过USART_ClearITPendingBit清除了所有中断标志,还会进入中断?

(3)为什么关闭USART2中断后再次启动它还会进入卡死状态?

(4)为什么通过USART_GetFlagStatus第一次和第二次读的不一样?而且USART_ClearFlag清掉所有Flag,也没法恢复正常?


带着以上几个疑问,查看了参考手册,才恍然大悟!如下:

(1)打开RXNEIE,默认会同时打开RXNE和ORE中断。


(2)必须第一时间清零RXNE,如没及时清零,下一帧数据过来时就会产生Overrun error!


(3)错误就是ORE导致的

出现错误时,读了RXNE=0,出错应该是上图打勾的情况,如下


(4)如文档说明,要清除ORE中断需要按顺序读取USART_SR和USART_DR寄存器!

那就是说USART_ClearFlag清掉所有Flag后,还必须读一遍USART_DR寄存器!

      经过测试出现问题后依次读读取USART_SR和USART_DR,程序回复正常!


(5)那还有一个问题,为什么USART_GetITStatus读不到ORE中断标志?

读USART_GetITStatus函数就知道了,只有CR3的EIE置1且SR的ORE置1,读出来USART_GetITStatus(USART2,  USART_IT_ORE)  才是 SET。

见CR3的EIE位说明。




解决办法,出现通过接收时,通过USART_GetFlagStatus读取ORE,若不为RESET,则读取DR数据丢弃。

修改如下:

[cpp]  view plain  copy
  1. void USART2_NewIstr(void)  
  2. {    
  3.     if (USART_GetFlagStatus(USART2, USART_FLAG_PE) != RESET)  
  4.    {  
  5.        USART_ReceiveData(USART2);  
  6.      USART_ClearFlag(USART2, USART_FLAG_PE);  
  7.    }  
  8.       
  9.    if (USART_GetFlagStatus(USART2, USART_FLAG_ORE) != RESET)  
  10.    {  
  11.        USART_ReceiveData(USART2);  
  12.      USART_ClearFlag(USART2, USART_FLAG_ORE);  
  13.    }  
  14.       
  15.     if (USART_GetFlagStatus(USART2, USART_FLAG_FE) != RESET)  
  16.    {  
  17.        USART_ReceiveData(USART2);  
  18.       USART_ClearFlag(USART2, USART_FLAG_FE);  
  19.    }  
  20.       
  21.     if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)  
  22.     {     
  23.         USART_ClearFlag(USART2, USART_FLAG_RXNE);  
  24.         USART_ClearITPendingBit(USART2, USART_IT_RXNE);  
  25.         Data = USART_ReceiveData(USART2);  
  26.     }  
  27. }  

 总结: 
 

1、看文档!看文档!还是看文档!(重要的事情要说3遍)

2、库函数用的时候,也要注意其实现,稍有不慎就可能用错。

3、注意USART_GetFlagStatus与USART_GetITStatus的区别,还有中断响应机制。

4、任意时候都要考虑出错处理。


查了一下 ,也有人遇到了相同的情况,可参考:

http://blog.youkuaiyun.com/love_maomao/article/details/8234039

### RT-Thread 中通过串口接收数据 在 RT-Thread 实时操作系统中,可以通过多种方式实现串口数据的接收。通常采用的方式有轮询模式和中断模式两种。 #### 轮询模式下的串口接收 对于简单的应用场景,可以使用轮询模式来读取串口中的数据。这种方式简单易懂,但在高负载环境下效率较低。下面是一个基于轮询模式的例子: ```c #include <rtthread.h> #include <rtdevice.h> #define UART_NAME "uart1" int main(void) { struct serial_device *serial; /* 查找指定名称的串口设备 */ serial = (struct serial_device *)rt_device_find(UART_NAME); if (!serial) return -1; char ch; while (1) { /* 从串口中读取单个字符 */ rt_size_t recv_len = rt_device_read(serial, -1, &ch, 1); if(recv_len != 0){ /* 如果成功接收到数据,则打印该字符 */ rt_kprintf("%c", ch); } /* 添加适当延时防止占用过多CPU资源 */ rt_thread_mdelay(10); } return 0; } ``` 此方法适用于低速率传输场景,在每次循环迭代期间都会尝试检查是否有新的输入到来[^3]。 #### 中断驱动型DMA接收 更高效的做法是在RT-Thread里配置好DMA控制器用于自动搬运接收到的数据至内存缓冲区内,并利用中断机制通知应用程序有关新到达的信息包。这种方法特别适合于高速率通信场合下保持稳定性能表现。具体来说就是初始化完成后启动DMA通道监听特定事件的发生;一旦检测到有效载荷则触发相应的ISR(Interrupt Service Routine),进而唤醒处于休眠状态的任务去处理这些刚传入的内容片段[^1]。 以下是关于如何设置这种工作流程的一个简化版本示例代码: ```c static void uart_rx_irq_handler(struct rt_serial_device* dev) { /* 获取当前实例关联的具体硬件描述符指针 */ USART_TypeDef* usart_base_addr = (USART_TypeDef*)dev->parent.user_data; /* 清除标志位并准备传递实际接收到的有效字节数量给上层应用逻辑 */ __HAL_UART_CLEAR_FLAG(usart_base_addr, UART_FLAG_IDLE); } void init_uart_with_dma_and_interrupt() { struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; struct serial_device *serial_dev; /* 初始化串口参数 */ config.baud_rate = BAUD_RATE_115200; config.data_bits = DATA_BITS_8; config.stop_bits = STOP_BITS_1; config.parity = PARITY_NONE; config.mode = MODE_RX | MODE_TX; /* 找到对应的串口设备对象 */ serial_dev = (struct serial_device *)rt_device_find("uart1"); if(!serial_dev) { // 错误处理... } /* 设置波特率及其他属性 */ rt_hw_serial_config(serial_dev, &config); /* 注册接收中断服务程序 */ rt_hw_serial_register_isr(serial_dev, uart_rx_irq_handler); /* 开启DMA接收功能 */ HAL_UART_Receive_DMA((UART_HandleTypeDef*)(serial_dev->parent.user_data), rx_buffer, BUFFER_SIZE); } ``` 上述代码展示了怎样创建一个专门负责响应外部刺激而执行某些预定义动作的功能模块——即所谓的“中断服务子程序”。每当发生IDLE线空闲中断时就会调用`uart_rx_irq_handler()`函数清除相关标记并向主线程发送信号表明已有待处理的新消息存在[^2]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值