你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32串口接收大量数据导致死机的解决办法

[复制链接]
嵌了个入式 发布时间:2017-11-21 09:07
资料转自别处,分享一下供大家参考,感谢原创的付出。
在一项目中,使用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接收中断响应函数如下:
[url=][/url]
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 }[url=][/url]

(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数据丢弃。
修改如下:

[url=][/url]
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 }[url=][/url]

总结:
1、看文档!看文档!还是看文档!(重要的事情要说3遍)
2、库函数用的时候,也要注意其实现,稍有不慎就可能用错。
3、注意USART_GetFlagStatus与USART_GetITStatus的区别,还有中断响应机制。
4、任意时候都要考虑出错处理。

1 收藏 3 评论2 发布时间:2017-11-21 09:07

举报

2个回答
五哥1 回答时间:2017-11-22 12:23:21
1、看文档!看文档!还是看文档!(重要的事情要说3遍)
2、库函数用的时候,也要注意其实现,稍有不慎就可能用错。
3、注意USART_GetFlagStatus与USART_GetITStatus的区别,还有中断响应机制。
4、任意时候都要考虑出错处理。

总结的非常好,支持。
zlk1214 回答时间:2020-4-19 23:50:13
其实说到底,就是Overrun(USART_SR_ORE)在作怪。
串口接收函数HAL_UART_Receive_IT调用后,会有三种结果:

第一种:只调用HAL_UART_RxCpltCallback回调函数
第二种:HAL_UART_RxCpltCallback和HAL_UART_ErrorCallback回调函数都调用
第三种:只调用HAL_UART_ErrorCallback回调函数

长时间(半小时以上)发送大量串口数据时,单片机会因为来不及处理串口数据出现Overrun的错误,此时USART_SR_ORE位为1。
到了一定时候,当USART_SR寄存器的USART_SR_RXNE为0,且USART_SR_ORE位为1时,就会出现第三种情况,只有HAL_UART_ErrorCallback这个函数会被调用,而且USART_SR_ORE位始终不能自动清除,串口就不能继续接收数据,USART_SR_RXNE位一直为0,这样就产生了死循环。
函数调用HAL_UART_Receive_IT开始接收数据,而因为ORE=1,马上产生串口中断,进入HAL_UART_ErrorCallback,然后接收自动中止(注意看UART_EndRxTransfer函数,里面只是简单地中止接收)。RXNE位一直为0,永远进不了HAL_UART_RxCpltCallback。而HAL库函数又没有清除ORE位的功能,所以即便再次调用HAL_UART_Receive_IT也是同样的结果。

手册里面可以看到,ORE位是只读位,只能用特殊的方法清除:

Bit 3 ORE: Overrun error
This bit is set by hardware when the word currently being received in the shift register is
ready to be transferred into the RDR register while RXNE=1. An interrupt is generated if
RXNEIE=1 in the USART_CR1 register. It is cleared by a software sequence (an read to the
USART_SR register followed by a read to the USART_DR register).
0: No Overrun error
1: Overrun error is detected
Note: When this bit is set, the RDR register content will not be lost but the shift register will be
overwritten. An interrupt is generated on ORE flag in case of Multi Buffer
communication if the EIE bit is set.

清除ORE位的方法是,先读USART_SR寄存器,再读USART_DR寄存器。只要清除了ORE位,就可以打破这种死循环的状态。
HAL库里面有一个__HAL_UART_FLUSH_DRREGISTER宏可以用来读DR寄存器:
#define __HAL_UART_FLUSH_DRREGISTER(__HANDLE__) ((__HANDLE__)->Instance->DR)
所以,我们只要在ORE错误产生时,读一下DR寄存器,就可以解决这个bug,退出这种死循环。

    void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
    {
      if (HAL_UART_GetError(huart) & HAL_UART_ERROR_ORE)
        __HAL_UART_FLUSH_DRREGISTER(huart);
    }

另外,调试的时候要特别注意,不要把Keil的USART寄存器窗口打开了。因为Keil在读USART1->DR寄存器的时候,会导致USART_SR_RXNE位被清除,程序就可能收不到串口数据。

参考程序:
  1. #include <FreeRTOS.h>
  2. #include <semphr.h>
  3. #include <stdio.h>
  4. #include <stm32f2xx.h>
  5. #include <task.h>
  6. #include "common.h"

  7. extern UART_HandleTypeDef huart1;
  8. RTC_HandleTypeDef hrtc;
  9. static SemaphoreHandle_t uart_sem;

  10. static void main_task(void *arg)
  11. {
  12.   RTC_DateTypeDef date;
  13.   RTC_TimeTypeDef time;
  14.   
  15.   hrtc.Instance = RTC;
  16.   while (1)
  17.   {
  18.     HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BCD);
  19.     HAL_RTC_GetDate(&hrtc, &date, RTC_FORMAT_BCD);
  20.     printf_s("ticks=%u, time=20%02x-%x-%x %02x:%02x:%02x\n", HAL_GetTick(), date.Year, date.Month, date.Date, time.Hours, time.Minutes, time.Seconds);
  21.     printf_s("rxstate=%#x, RXNEIE=%d\n", huart1.RxState, __HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_RXNE) != RESET);
  22.     vTaskDelay(pdMS_TO_TICKS(10));
  23.   }
  24. }

  25. static void uart_task(void *arg)
  26. {
  27.   uint8_t data;
  28.   BaseType_t bret;
  29.   HAL_StatusTypeDef status;
  30.   
  31.   uart_sem = xSemaphoreCreateBinary();
  32.   HAL_NVIC_SetPriority(USART1_IRQn, 1, 0);
  33.   HAL_NVIC_EnableIRQ(USART1_IRQn);
  34.   HAL_UART_Receive_IT(&huart1, &data, 1);
  35.   while (1)
  36.   {
  37.     bret = xSemaphoreTake(uart_sem, 100);
  38.     if (bret == pdTRUE)
  39.     {
  40.       if (data == 'a')
  41.         printf_s("Received: %c!\n", data);
  42.     }
  43.    
  44.     if (huart1.RxState == HAL_UART_STATE_READY)
  45.     {
  46.       printf_lock();
  47.       status = HAL_UART_Receive_IT(&huart1, &data, 1);
  48.       configASSERT(status == HAL_OK);
  49.       printf_unlock();
  50.     }
  51.   }
  52. }

  53. int main(void)
  54. {
  55.   HAL_Init();
  56.   
  57.   clock_init();
  58.   usart_init(115200);
  59.   printf("STM32F217VE USART1\n");
  60.   printf("SystemCoreClock=%u\n", SystemCoreClock);
  61.   
  62.   xTaskCreate_s(main_task, NULL, 2 * configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL);
  63.   xTaskCreate_s(uart_task, NULL, 2 * configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL);
  64.   vTaskStartScheduler();
  65.   return 0;
  66. }

  67. void USART1_IRQHandler(void)
  68. {
  69.   HAL_UART_IRQHandler(&huart1);
  70. }

  71. void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
  72. {
  73.   if (HAL_UART_GetError(huart) & HAL_UART_ERROR_ORE)
  74.     __HAL_UART_FLUSH_DRREGISTER(huart);
  75. }

  76. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
  77. {
  78.   BaseType_t woken = pdFALSE;
  79.   
  80.   if (huart == &huart1)
  81.     xSemaphoreGiveFromISR(uart_sem, &woken);
  82.   portYIELD_FROM_ISR(woken);
  83. }
复制代码

程序的里面有两个任务,一个任务不停地在用printf打印,往串口输出数据,另一个任务专门负责接收串口字符。发送和接收函数用printf_mutex这个互斥量来保护(printf_lock和printf_unlock)。接收串口字符时等待xSemaphoreTake信号量,若xSemaphoreTake信号量有信号,说明串口收到了字符。然后判断接收是否已停止,若huart1.RxState == HAL_UART_STATE_READY,说明接收已停止,调用HAL_UART_Receive_IT函数重新开始接收。
在HAL_UART_RxCpltCallback回调函数中使能信号量唤醒接收线程。在HAL_UART_ErrorCallback函数中处理Overrun错误(ORE),及时读取DR寄存器清除ORE位,防止陷入死循环。

特别注意,while(1)里面的if (huart1.RxState == HAL_UART_STATE_READY)不能放到if (bret == pdTRUE)内,因为调用HAL_UART_Receive_IT后有可能只有HAL_UART_ErrorCallback回调函数被调用,这个时候是没有唤醒信号量的,线程就不能够进入if (bret == pdTRUE)里面,调用HAL_UART_Receive_IT恢复数据接收。
————————————————
版权声明:本文为CSDN博主「巨大八爪鱼」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:http://blog.csdn.net/ZLK1214/article/details/105624510

所属标签

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版