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

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

[复制链接]
嵌了个入式 发布时间:2017-11-21 09:07
资料转自别处,分享一下供大家参考,感谢原创的付出。
在一项目中,使用STM32作为主控,程序运行一段时间后概率出现主循环卡死现象。
% r% }) z+ _$ {) F/ L# j
问题分析如下:
1、程序USART2不停接收并处理串口数据,波特率115200;
2、主循环卡死;
3、USART1中断及TIM2中断响应函数运行正常;(USART1及TIM2中断优先级均比USART2高)
4、出现现象后,拔掉USART2的接收数据线,现象不能回复正常;
5、出现现象后,拔掉后再插入USART2的接收数据线,现象不能回复正常;
6、并未出现HardFault现象;

/ I4 R' W% F2 T( W! l! u5 i" s* h
基于以上4点,可能原因如下:
1、USART2接收中断标志没有清除;
2、堆栈数据溢出,导致程序异常;
        3、USART2中断重入导致异常;
4、USART2中断函数被异常响应;
        5、USART2中断ERR;
5 K, F, y6 q/ s4 C# s
对于以上可能原因一一分析:
1、中断接收标志清楚问题:
(1)USART2接收中断响应函数如下:
[url=][/url]7 O5 g0 V: b& n' N  M& |" T$ C/ K) D
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]; g+ E, }6 @0 p" d% V

7 ^# E% m( l! @3 k, Z" l9 A
(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中断标志无关。
/ ^% x  `4 `6 {7 p8 g% H! V
2、堆栈数据溢出,导致程序异常;
(1)使用2倍栈空间,问题存在,概率不会降低;
(2)使用0.5倍栈空间,问题存在,概率不会提高;
(3)使用0.25倍栈空间,程序运行进入HardFault;
结论:与堆栈无关。

% M2 x+ G% B7 ?# S7 _: D
3、USART2中断重入导致异常;
(1)使用标志法,确认出现问题时,中断响应函数没有重入;
结论:中断响应函数没有重入。

6 B) g( b5 ?$ ]8 o
4、USART2中断函数被异常响应;
(1)USART2中断函数可以被正常调用,只是不停进入中断响应函数,卡死主循环;
(2)检查程序Map,没发现与中断响应函数地址相同的函数;
(3)检查中断向量表,没发现异常;
结论:中断函数没有被异常调用;
  m. C" n$ ~0 r% P" X# _
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,也没法恢复正常?

7 u5 i1 u- k4 h# R
带着以上几个疑问,查看了参考手册,才恍然大悟!如下:
(1)打开RXNEIE,默认会同时打开RXNE和ORE中断。
(2)必须第一时间清零RXNE,如没及时清零,下一帧数据过来时就会产生Overrun error!
(3)错误就是ORE导致的
出现错误时,读了RXNE=0,出错应该是上图打勾的情况,如下
8 L$ Y, I* g# R3 Z0 H& P* j
(4)如文档说明,要清除ORE中断需要按顺序读取USART_SR和USART_DR寄存器!
那就是说USART_ClearFlag清掉所有Flag后,还必须读一遍USART_DR寄存器!
      经过测试出现问题后依次读读取USART_SR和USART_DR,程序回复正常!

1 H& ~3 x( s2 E# o) l, x
(5)那还有一个问题,为什么USART_GetITStatus读不到ORE中断标志?
读USART_GetITStatus函数就知道了,只有CR3的EIE置1且SR的ORE置1,读出来USART_GetITStatus(USART2,  USART_IT_ORE)  才是 SET。
见CR3的EIE位说明。

0 o/ A/ R; R' ?* i" B: Z
解决办法,出现通过接收时,通过USART_GetFlagStatus读取ORE,若不为RESET,则读取DR数据丢弃。
修改如下:

5 E0 x- X2 J0 p4 T[url=][/url]1 ?* X! P: L" y8 |0 k& a
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]
8 @. G) {: J- E# I/ ]# h8 j
' t1 T" m6 b" h# E2 i" J
总结:
1、看文档!看文档!还是看文档!(重要的事情要说3遍)
2、库函数用的时候,也要注意其实现,稍有不慎就可能用错。
3、注意USART_GetFlagStatus与USART_GetITStatus的区别,还有中断响应机制。
4、任意时候都要考虑出错处理。

/ r( h& A! u8 M4 [2 H
赞 1 收藏 3 评论2 发布时间:2017-11-21 09:07

举报

2个回答
五哥1 回答时间:2017-11-22 12:23:21
1、看文档!看文档!还是看文档!(重要的事情要说3遍)- S9 C8 |* _, I7 i; G6 _0 O
2、库函数用的时候,也要注意其实现,稍有不慎就可能用错。
/ ]$ U# r' Y) `, H3、注意USART_GetFlagStatus与USART_GetITStatus的区别,还有中断响应机制。+ h2 a9 ]4 C& c! ~
4、任意时候都要考虑出错处理。
3 S. n1 u  `$ ?; q# O( }3 R% E4 E0 T+ R8 P
总结的非常好,支持。
zlk1214 回答时间:2020-4-19 23:50:13
其实说到底,就是Overrun(USART_SR_ORE)在作怪。
" D' }' T/ I& t+ s9 I3 X( J- F串口接收函数HAL_UART_Receive_IT调用后,会有三种结果:3 y( E- ]: A; T# S% }; \
. p9 k( T8 V" B8 s0 U2 w
第一种:只调用HAL_UART_RxCpltCallback回调函数
9 \( A: Z7 B: s/ m第二种:HAL_UART_RxCpltCallback和HAL_UART_ErrorCallback回调函数都调用
1 z: ?& z- i6 J; \/ c! C# q第三种:只调用HAL_UART_ErrorCallback回调函数
$ {. {5 w# m) B4 G# p3 s
  E( J* d4 e4 ~" D长时间(半小时以上)发送大量串口数据时,单片机会因为来不及处理串口数据出现Overrun的错误,此时USART_SR_ORE位为1。3 L+ [3 u$ Z$ k) I1 g- l
到了一定时候,当USART_SR寄存器的USART_SR_RXNE为0,且USART_SR_ORE位为1时,就会出现第三种情况,只有HAL_UART_ErrorCallback这个函数会被调用,而且USART_SR_ORE位始终不能自动清除,串口就不能继续接收数据,USART_SR_RXNE位一直为0,这样就产生了死循环。9 q; c; |+ r& a0 q: J
函数调用HAL_UART_Receive_IT开始接收数据,而因为ORE=1,马上产生串口中断,进入HAL_UART_ErrorCallback,然后接收自动中止(注意看UART_EndRxTransfer函数,里面只是简单地中止接收)。RXNE位一直为0,永远进不了HAL_UART_RxCpltCallback。而HAL库函数又没有清除ORE位的功能,所以即便再次调用HAL_UART_Receive_IT也是同样的结果。' L$ Y5 Z- G; v7 o$ h
! c' u! v  e, Z+ f
手册里面可以看到,ORE位是只读位,只能用特殊的方法清除:
! R; y. H( p( {
4 c1 F/ u7 r9 T5 R5 X) `* y9 s* jBit 3 ORE: Overrun error* K& M# }3 _" ^- S
This bit is set by hardware when the word currently being received in the shift register is
& n8 m$ _; R' E3 ~; Mready to be transferred into the RDR register while RXNE=1. An interrupt is generated if/ a6 c, ?7 d* @3 A# h
RXNEIE=1 in the USART_CR1 register. It is cleared by a software sequence (an read to the
9 |7 R" k( C+ C3 d+ t4 d8 L  Z) tUSART_SR register followed by a read to the USART_DR register).
/ }& ]6 K9 a# J8 N! Y7 b+ ^, d: k0: No Overrun error
/ j/ U. r! s- |) c5 U1: Overrun error is detected1 Z1 `) K& V! q
Note: When this bit is set, the RDR register content will not be lost but the shift register will be
9 A; h9 W; w' A; d$ ]8 Q. i, k7 D6 Loverwritten. An interrupt is generated on ORE flag in case of Multi Buffer
8 n' n1 j$ d) p) l$ ~) ?communication if the EIE bit is set.4 B$ O. P4 I6 V8 G8 K# A

* x& f" U3 @5 j清除ORE位的方法是,先读USART_SR寄存器,再读USART_DR寄存器。只要清除了ORE位,就可以打破这种死循环的状态。
" _. @: ~2 \6 s' THAL库里面有一个__HAL_UART_FLUSH_DRREGISTER宏可以用来读DR寄存器:
" ^/ S2 N( ~/ _7 e" o5 E#define __HAL_UART_FLUSH_DRREGISTER(__HANDLE__) ((__HANDLE__)->Instance->DR)* |* u6 z0 Y3 \2 E5 Z  O1 e. X- d( K1 f  ^
所以,我们只要在ORE错误产生时,读一下DR寄存器,就可以解决这个bug,退出这种死循环。
2 _  p* I! E. S1 I. H7 H6 o$ [8 I- \5 K; a( q$ E4 E
    void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)& n: E: A9 K% a0 D/ p! x
    {
- F- J9 _1 q$ x6 {3 W; Q      if (HAL_UART_GetError(huart) & HAL_UART_ERROR_ORE), l8 D( H1 ]5 @* ~' `
        __HAL_UART_FLUSH_DRREGISTER(huart);9 b3 y$ Q" \- M; T
    }; S/ `; e) ]6 i' M  ?* O+ \) ?) k) C

. [& z  u% B! P, x, e! Y( M$ {& D另外,调试的时候要特别注意,不要把Keil的USART寄存器窗口打开了。因为Keil在读USART1->DR寄存器的时候,会导致USART_SR_RXNE位被清除,程序就可能收不到串口数据。" E+ Z; G" ^1 m5 b+ ^! s

+ H2 M; Q2 G: b. z3 A% J1 W参考程序:& B# k* p3 C! P$ M
  1. #include <FreeRTOS.h>
    ( D, \$ H  P$ s* T
  2. #include <semphr.h>
    ! f) X  X+ V7 v& e% {4 p1 w' {
  3. #include <stdio.h>+ c8 K) F4 b, A6 ?. t
  4. #include <stm32f2xx.h>
    * _! ~. r7 g( d# _
  5. #include <task.h>, M3 X# ]+ u4 |; ~
  6. #include "common.h"
    & f* }* \! J5 X6 I: L

  7. - L) M9 n- N( o2 a' l
  8. extern UART_HandleTypeDef huart1;
    + D' t* x1 t* o5 \# I7 H2 W
  9. RTC_HandleTypeDef hrtc;
    * z" `" F9 K  h: f
  10. static SemaphoreHandle_t uart_sem;; E. f5 X- q4 b/ t9 ?
  11. ; L' i- J+ C8 F; H$ h! x8 t  e
  12. static void main_task(void *arg)
    7 o; S3 K/ I7 o2 p$ D( q# s
  13. {* n; ^2 {9 E1 D# s: V
  14.   RTC_DateTypeDef date;/ F$ n6 H! [" Q1 k
  15.   RTC_TimeTypeDef time;* W* a! w! }4 ^: ?5 f" O
  16.   
    ; I7 R% {! a- |
  17.   hrtc.Instance = RTC;
    3 r: u" z- \5 i. q
  18.   while (1)9 P2 }  y. x& T1 c* o
  19.   {
    ! s) [. J% L$ t8 U
  20.     HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BCD);
    , H+ d4 b! O9 z8 @! q
  21.     HAL_RTC_GetDate(&hrtc, &date, RTC_FORMAT_BCD);, D( T# n  x# F
  22.     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);
    ! n' j! n% y' \
  23.     printf_s("rxstate=%#x, RXNEIE=%d\n", huart1.RxState, __HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_RXNE) != RESET);
    1 I' ^& @. I" I( T4 r
  24.     vTaskDelay(pdMS_TO_TICKS(10));* m8 }/ L. u7 r; C2 S
  25.   }2 b+ y3 r0 e% a2 y
  26. }
    ) K5 t) {  e9 g$ d
  27. ! R0 P) s6 n, U( m  z: M
  28. static void uart_task(void *arg)
    . u( R/ f* h7 a3 O6 O
  29. {
    8 h, `! m4 M; e( U. @( R
  30.   uint8_t data;
    ( Q8 ]3 D' v+ f
  31.   BaseType_t bret;
    9 @* T' c7 q# i" a+ Z+ P
  32.   HAL_StatusTypeDef status;
      U6 C/ n4 I. Y9 y+ z2 l3 [$ m
  33.   
      [+ q9 T+ l" D
  34.   uart_sem = xSemaphoreCreateBinary();' g' T0 O) X% ^8 j; o  a* N  b! q
  35.   HAL_NVIC_SetPriority(USART1_IRQn, 1, 0);
    0 t" V, H. N4 T( D' H
  36.   HAL_NVIC_EnableIRQ(USART1_IRQn);6 L+ d. H' ?1 ?9 v* D
  37.   HAL_UART_Receive_IT(&huart1, &data, 1);
    3 |4 t: t+ d+ Z( O1 q7 X
  38.   while (1)
    . E  e- l5 P' V5 Z
  39.   {* ~+ m2 o6 m% m; d0 Q( ^
  40.     bret = xSemaphoreTake(uart_sem, 100);' U4 o. [" m! o$ P
  41.     if (bret == pdTRUE)) r( E; w# l/ p$ a( m" Y
  42.     {# J, J, e) h+ C% ~+ `
  43.       if (data == 'a')" V# ]7 U' e+ `( g) e! P; I
  44.         printf_s("Received: %c!\n", data);
    6 U: h/ V* E5 q" j: j7 Z+ Y
  45.     }
    ' \" H/ c5 [" v% u7 u6 ^7 [
  46.    
    " \: A) [/ n6 B8 \
  47.     if (huart1.RxState == HAL_UART_STATE_READY)  d5 U6 f( F4 A
  48.     {% I" v; t* Q, `7 R4 w) S
  49.       printf_lock();
    ( E$ F( S. A2 I7 `' X
  50.       status = HAL_UART_Receive_IT(&huart1, &data, 1);
    3 ]5 m. M7 F! V5 F) C% t
  51.       configASSERT(status == HAL_OK);5 j# @1 \8 q. D4 B1 l/ k6 n  _3 G
  52.       printf_unlock();
    % E; T  C2 E, V% l; G2 H+ w0 x
  53.     }
    7 J) l/ j5 b/ D8 H1 M
  54.   }
    9 ^( c% H2 B  l) C7 H1 p" x: X1 A
  55. }$ L4 @( b, j' E& p: L5 p# i

  56. ( t/ V; v& P& I: q& o
  57. int main(void)8 G' Q8 H5 \' i- N$ l
  58. {6 b2 R3 v5 d+ P2 _
  59.   HAL_Init();
    ; ~2 W  y3 ]% Q6 P" Z
  60.   
    7 X1 f# L5 W" c% g# c3 w. c
  61.   clock_init();8 Z0 J0 i1 V  N* h3 c5 V6 }0 ?
  62.   usart_init(115200);
    ' Q& m8 z, z* F' G5 R& z
  63.   printf("STM32F217VE USART1\n");
    3 f2 j/ n* U( t$ O3 P
  64.   printf("SystemCoreClock=%u\n", SystemCoreClock);
    % t% v! r8 G  W) x2 v3 {" e; w
  65.   
    6 q3 i& J$ s+ O, f" H+ X
  66.   xTaskCreate_s(main_task, NULL, 2 * configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL);
    3 f1 q; D4 N, l  P: A
  67.   xTaskCreate_s(uart_task, NULL, 2 * configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL);
    2 D7 l2 \1 X5 ^, I
  68.   vTaskStartScheduler();
    * k& [# C0 F( [+ S
  69.   return 0;1 D4 f6 e; C$ @5 g# g+ ?$ c
  70. }
    + i$ {% m# `6 v3 s# o) a1 c
  71. : j& Y( P; e; j$ Z: U
  72. void USART1_IRQHandler(void)6 q8 C: P# \1 _5 f
  73. {
    , p; ~; ?6 K8 E2 [5 y9 e9 O# s" k
  74.   HAL_UART_IRQHandler(&huart1);  B9 w, u" \! k" z2 h
  75. }1 }+ w3 {8 I" W9 I' P4 T( |

  76. 2 n* V+ V+ n7 e% N+ B' S& B
  77. void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)! N' ~) Z+ i& t# h+ a% Z2 e
  78. {
      g7 o  d) d5 p3 y
  79.   if (HAL_UART_GetError(huart) & HAL_UART_ERROR_ORE)
    5 x2 M1 T# f* A- ^9 Z7 |) W$ m
  80.     __HAL_UART_FLUSH_DRREGISTER(huart);! H; q9 U! l1 w$ {$ a/ z0 V9 b
  81. }
    8 H5 a' ?. o1 Z) w/ D- v0 S/ p. h
  82. , e! b! b% M1 H1 M
  83. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    # v, b5 x4 V; x
  84. {  y' b! Y3 ^5 @
  85.   BaseType_t woken = pdFALSE;' J+ N1 R: p) ~, S* }; u
  86.   : U6 p' L' i$ q! U
  87.   if (huart == &huart1)
    8 D% a7 a- ~% G! r
  88.     xSemaphoreGiveFromISR(uart_sem, &woken);
    2 B- `( E2 a4 x/ \
  89.   portYIELD_FROM_ISR(woken);' }+ j0 w, t5 A+ ~5 X. _8 ~# J' I
  90. }
复制代码

# ^+ d( h. p  @, `; y( z5 `程序的里面有两个任务,一个任务不停地在用printf打印,往串口输出数据,另一个任务专门负责接收串口字符。发送和接收函数用printf_mutex这个互斥量来保护(printf_lock和printf_unlock)。接收串口字符时等待xSemaphoreTake信号量,若xSemaphoreTake信号量有信号,说明串口收到了字符。然后判断接收是否已停止,若huart1.RxState == HAL_UART_STATE_READY,说明接收已停止,调用HAL_UART_Receive_IT函数重新开始接收。" R; g5 ~0 b0 b7 f& v8 ~1 k
在HAL_UART_RxCpltCallback回调函数中使能信号量唤醒接收线程。在HAL_UART_ErrorCallback函数中处理Overrun错误(ORE),及时读取DR寄存器清除ORE位,防止陷入死循环。
- I. N& g: m5 z/ P' B8 A
3 B$ s% q6 m* a) W特别注意,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恢复数据接收。
6 l! Z5 p1 n6 o" u5 Q& ?; z6 V————————————————( z) I) x6 N  G4 m  e7 N, W3 W, P
版权声明:本文为CSDN博主「巨大八爪鱼」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。7 ^' q7 S5 E! h
原文链接:http://blog.csdn.net/ZLK1214/article/details/105624510

所属标签

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版