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

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

[复制链接]
嵌了个入式 发布时间:2017-11-21 09:07
资料转自别处,分享一下供大家参考,感谢原创的付出。
在一项目中,使用STM32作为主控,程序运行一段时间后概率出现主循环卡死现象。

0 y5 V7 M( D1 g& v7 r! h% O! z: I
问题分析如下:
1、程序USART2不停接收并处理串口数据,波特率115200;
2、主循环卡死;
3、USART1中断及TIM2中断响应函数运行正常;(USART1及TIM2中断优先级均比USART2高)
4、出现现象后,拔掉USART2的接收数据线,现象不能回复正常;
5、出现现象后,拔掉后再插入USART2的接收数据线,现象不能回复正常;
6、并未出现HardFault现象;

! Y- B- r, e0 h$ _. B
基于以上4点,可能原因如下:
1、USART2接收中断标志没有清除;
2、堆栈数据溢出,导致程序异常;
        3、USART2中断重入导致异常;
4、USART2中断函数被异常响应;
        5、USART2中断ERR;

+ l/ {4 |0 Y7 `- ~" Q
对于以上可能原因一一分析:
1、中断接收标志清楚问题:
(1)USART2接收中断响应函数如下:
[url=][/url]8 }% v9 g0 A% T/ _8 ?+ z$ l
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]* G9 `% ^) |% I' n7 l) O

) E# ]; u" b# X* W, C5 ?
(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 v& H* x$ X) @3 {  s' O
2、堆栈数据溢出,导致程序异常;
(1)使用2倍栈空间,问题存在,概率不会降低;
(2)使用0.5倍栈空间,问题存在,概率不会提高;
(3)使用0.25倍栈空间,程序运行进入HardFault;
结论:与堆栈无关。

" l/ H2 _8 _9 ?5 m+ Q2 _
3、USART2中断重入导致异常;
(1)使用标志法,确认出现问题时,中断响应函数没有重入;
结论:中断响应函数没有重入。

; s+ v; F& O; S$ ~& u4 T* c9 k
4、USART2中断函数被异常响应;
(1)USART2中断函数可以被正常调用,只是不停进入中断响应函数,卡死主循环;
(2)检查程序Map,没发现与中断响应函数地址相同的函数;
(3)检查中断向量表,没发现异常;
结论:中断函数没有被异常调用;
3 P+ ]; m; x8 [1 x- ^. H
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,也没法恢复正常?
9 r# G  t/ o# k  }% [/ c
带着以上几个疑问,查看了参考手册,才恍然大悟!如下:
(1)打开RXNEIE,默认会同时打开RXNE和ORE中断。
(2)必须第一时间清零RXNE,如没及时清零,下一帧数据过来时就会产生Overrun error!
(3)错误就是ORE导致的
出现错误时,读了RXNE=0,出错应该是上图打勾的情况,如下

0 @, h4 T6 T8 M
(4)如文档说明,要清除ORE中断需要按顺序读取USART_SR和USART_DR寄存器!
那就是说USART_ClearFlag清掉所有Flag后,还必须读一遍USART_DR寄存器!
      经过测试出现问题后依次读读取USART_SR和USART_DR,程序回复正常!
& K) r8 c; l) U. Z6 N. N& u; P
(5)那还有一个问题,为什么USART_GetITStatus读不到ORE中断标志?
读USART_GetITStatus函数就知道了,只有CR3的EIE置1且SR的ORE置1,读出来USART_GetITStatus(USART2,  USART_IT_ORE)  才是 SET。
见CR3的EIE位说明。
* m0 v$ q, }' e+ [; v
解决办法,出现通过接收时,通过USART_GetFlagStatus读取ORE,若不为RESET,则读取DR数据丢弃。
修改如下:

! L  ?5 d( l! o[url=][/url]
' n, {4 X5 ~9 i' ]4 p 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]; g9 S2 w* h& U1 I

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

; p% J0 |# A/ A' W& \
1 收藏 3 评论2 发布时间:2017-11-21 09:07

举报

2个回答
五哥1 回答时间:2017-11-22 12:23:21
1、看文档!看文档!还是看文档!(重要的事情要说3遍)8 A0 P5 W* G6 a9 A7 j7 ?
2、库函数用的时候,也要注意其实现,稍有不慎就可能用错。
9 o$ _  B1 {9 S) e8 S9 ^) F2 w/ z3、注意USART_GetFlagStatus与USART_GetITStatus的区别,还有中断响应机制。' T! s! K4 N0 T6 K  L0 f" \
4、任意时候都要考虑出错处理。1 k; k* _3 p) @  g# V: ]) J

9 ~" D' C5 p1 F7 F/ e6 v0 ]& a总结的非常好,支持。
zlk1214 回答时间:2020-4-19 23:50:13
其实说到底,就是Overrun(USART_SR_ORE)在作怪。& T- |6 s3 q4 x5 ~* Q' ], U
串口接收函数HAL_UART_Receive_IT调用后,会有三种结果:2 t% S  P7 D2 [4 ]% t& z& G* }
- }1 b3 V! Q0 B% [" o7 f
第一种:只调用HAL_UART_RxCpltCallback回调函数
: G5 E: q% j  C( u" A第二种:HAL_UART_RxCpltCallback和HAL_UART_ErrorCallback回调函数都调用
! f: v4 K. x) S2 y4 X& T第三种:只调用HAL_UART_ErrorCallback回调函数7 M. M4 \. n  z8 `$ N! A# A
. R( u1 ^& A& B% }) c# u7 x- z, u7 j
长时间(半小时以上)发送大量串口数据时,单片机会因为来不及处理串口数据出现Overrun的错误,此时USART_SR_ORE位为1。
% m5 v# [1 X; [到了一定时候,当USART_SR寄存器的USART_SR_RXNE为0,且USART_SR_ORE位为1时,就会出现第三种情况,只有HAL_UART_ErrorCallback这个函数会被调用,而且USART_SR_ORE位始终不能自动清除,串口就不能继续接收数据,USART_SR_RXNE位一直为0,这样就产生了死循环。2 T, D& R3 Q. `- R' ]1 ]8 I8 r
函数调用HAL_UART_Receive_IT开始接收数据,而因为ORE=1,马上产生串口中断,进入HAL_UART_ErrorCallback,然后接收自动中止(注意看UART_EndRxTransfer函数,里面只是简单地中止接收)。RXNE位一直为0,永远进不了HAL_UART_RxCpltCallback。而HAL库函数又没有清除ORE位的功能,所以即便再次调用HAL_UART_Receive_IT也是同样的结果。- Y$ ^! l+ M/ j2 V7 A

! g7 l9 s/ p  J1 i: V  a% z; U手册里面可以看到,ORE位是只读位,只能用特殊的方法清除:4 b6 ~( N2 q  s

. X2 ?, j9 J1 {2 EBit 3 ORE: Overrun error
0 J  t, Z0 g: u4 ?+ n  QThis bit is set by hardware when the word currently being received in the shift register is
9 P/ w+ g9 P  ?; k6 Z3 q6 u* }ready to be transferred into the RDR register while RXNE=1. An interrupt is generated if
% y: k% x- D* y" B' W3 J( d5 sRXNEIE=1 in the USART_CR1 register. It is cleared by a software sequence (an read to the
) X$ h7 a* z  M* s' c* Z1 AUSART_SR register followed by a read to the USART_DR register).' m+ q7 O: Q! R$ ~! ^5 d; Z
0: No Overrun error: J! u3 H/ [7 k/ f3 @# `2 _; r6 S
1: Overrun error is detected0 c4 V% V+ {" d5 K+ |4 g" O$ S
Note: When this bit is set, the RDR register content will not be lost but the shift register will be" W- ?' E; @0 C0 U
overwritten. An interrupt is generated on ORE flag in case of Multi Buffer
+ z+ z; }1 ~/ n: z  [communication if the EIE bit is set.
) @1 @0 c! z6 E* }7 P5 K8 T" ]* V) S2 \+ s* H* r
清除ORE位的方法是,先读USART_SR寄存器,再读USART_DR寄存器。只要清除了ORE位,就可以打破这种死循环的状态。! t+ p1 o1 P3 e! ]- w% n0 w
HAL库里面有一个__HAL_UART_FLUSH_DRREGISTER宏可以用来读DR寄存器:
; i* y1 c( D% K#define __HAL_UART_FLUSH_DRREGISTER(__HANDLE__) ((__HANDLE__)->Instance->DR)  e) M# ]: w. P3 Y* H9 f
所以,我们只要在ORE错误产生时,读一下DR寄存器,就可以解决这个bug,退出这种死循环。+ l! @4 \0 |# v; n# f2 f8 ?
2 t* Q1 ^' r- Z% B
    void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
! ~, L* b0 t& q. D    {% q$ Z. S" F3 {) G6 G
      if (HAL_UART_GetError(huart) & HAL_UART_ERROR_ORE)
$ }: O# y7 r0 b0 K        __HAL_UART_FLUSH_DRREGISTER(huart);
9 X$ O! G5 m+ Z    }! a3 N, l3 X: z( I
5 g$ n5 i9 x. B# f
另外,调试的时候要特别注意,不要把Keil的USART寄存器窗口打开了。因为Keil在读USART1->DR寄存器的时候,会导致USART_SR_RXNE位被清除,程序就可能收不到串口数据。
( t+ e3 w' w+ m3 R  E9 K4 Q* T1 ~+ f
! }" r( c7 Z( E; q  Y参考程序:7 Z/ G' W, R7 C% w
  1. #include <FreeRTOS.h>5 _1 k. |, j$ N$ c; z  M3 [7 j: B' c. ?. ~4 F
  2. #include <semphr.h>
    8 ^* V* }9 F' P4 A
  3. #include <stdio.h>
    % ]& B- Y; s# `# e; t  o0 S
  4. #include <stm32f2xx.h>
      s- f, p0 {% S. {! ~
  5. #include <task.h>' L" E7 q, y5 e3 ~; U" e9 @( e
  6. #include "common.h"  ?8 f6 |% Q+ @! Y' K) J. D

  7. 4 |. `7 L2 A# Z) m$ F2 n) [8 s
  8. extern UART_HandleTypeDef huart1;
    ; H" ?" Y- W2 r  k1 s- L- }- W
  9. RTC_HandleTypeDef hrtc;
    1 _7 x' F  H, e5 k) O' _+ W: s5 j* @
  10. static SemaphoreHandle_t uart_sem;
    * f6 i3 \- [) s8 i# ^
  11. 1 A4 b" k2 \7 |- z% F$ A- T3 u1 f
  12. static void main_task(void *arg)
    " V. x1 V- j- h7 H9 R
  13. {
    " {( U9 a+ l: k  q3 y
  14.   RTC_DateTypeDef date;9 R0 \% @3 |" N  }
  15.   RTC_TimeTypeDef time;9 t6 ?( C5 F' _
  16.   
    1 U+ c; c, K9 ~; t2 a3 Y; V( c
  17.   hrtc.Instance = RTC;
    1 O) e8 G2 {, O  N0 G9 l6 d" B
  18.   while (1)# t4 N8 z8 j6 Z& `9 C
  19.   {
    - x# J0 U0 t- P3 g+ |/ s9 Z. z
  20.     HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BCD);
    $ w) |1 q' ~% O$ y( f
  21.     HAL_RTC_GetDate(&hrtc, &date, RTC_FORMAT_BCD);
    ( m+ q. w4 {' o
  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);) K+ C9 v2 h' R4 m9 h
  23.     printf_s("rxstate=%#x, RXNEIE=%d\n", huart1.RxState, __HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_RXNE) != RESET);& G5 d9 n+ I4 T3 Y
  24.     vTaskDelay(pdMS_TO_TICKS(10));6 W8 n! u; q* A/ \0 [+ o
  25.   }
    - w9 P1 I! I) v! U0 e) z4 t
  26. }
    6 y+ H; p4 y4 k, X. D9 n
  27. ! K9 @! g9 c- _. T
  28. static void uart_task(void *arg)
    ( X- f9 [" n( A: E. \. Z
  29. {
    / r/ Q* e0 @7 n$ k
  30.   uint8_t data;
    0 S7 m2 a$ k7 I
  31.   BaseType_t bret;
    . C9 z# A3 K2 h) E6 ^( {
  32.   HAL_StatusTypeDef status;) \- U* g1 ?, {# z5 j& D) c5 c
  33.   
    3 G6 Z" E+ ?4 B' Q
  34.   uart_sem = xSemaphoreCreateBinary();
    , `* s8 {  L' b! z2 _, }
  35.   HAL_NVIC_SetPriority(USART1_IRQn, 1, 0);3 R; h% M- z2 {
  36.   HAL_NVIC_EnableIRQ(USART1_IRQn);  O; K1 F  v. w4 p; j
  37.   HAL_UART_Receive_IT(&huart1, &data, 1);2 n. v2 \" ]2 A* u9 i/ a
  38.   while (1)
    ! A- _4 ~; b$ I' M+ [
  39.   {
    % P/ y" R% E% w' q6 p% u$ t# m4 a
  40.     bret = xSemaphoreTake(uart_sem, 100);
    $ ]2 p- x* h2 \5 V
  41.     if (bret == pdTRUE)+ Z7 U2 `0 h; @+ p5 l
  42.     {
    8 y! d' ?* U9 u- W5 H
  43.       if (data == 'a')8 w8 a, e, _6 o. @. b& _
  44.         printf_s("Received: %c!\n", data);
    ; G9 P- p& c  Q" M2 `' p
  45.     }8 N* y0 N! t; F, ]! b# c5 o8 y6 ?
  46.    
    4 L7 V) y2 q' j" h
  47.     if (huart1.RxState == HAL_UART_STATE_READY)/ S. K) l8 |2 R3 D
  48.     {
    & z1 w% u6 H! A  X$ l$ o
  49.       printf_lock();
    . S* ?; k1 P  E8 v/ k
  50.       status = HAL_UART_Receive_IT(&huart1, &data, 1);
    * i6 L/ R3 d9 [0 o7 f, T: x
  51.       configASSERT(status == HAL_OK);
    , L5 E! f' a$ G: V5 c9 ]) l. m
  52.       printf_unlock();
    4 W- ?/ }2 W5 o$ K
  53.     }
    4 }+ ~8 m/ H6 o8 J9 t8 Z3 C" k" g4 V
  54.   }
    / ~5 `4 u8 _  W& J* p
  55. }; H( e  L& v. M- ~9 M# m8 C* H# V" C
  56. % c& ?7 F8 B/ d$ p
  57. int main(void)
    + L. D+ O$ B" C, Z' C, J
  58. {
    ! l5 T( I2 A0 o5 [
  59.   HAL_Init();
    ! u' v: N# ?9 B- _$ W, L' K
  60.   
    6 d9 r( r/ A+ V9 p1 Z, |
  61.   clock_init();
    & G7 a. a; ~& _% F/ d
  62.   usart_init(115200);
    + l" \" d: a: i  ^& K/ B
  63.   printf("STM32F217VE USART1\n");0 D: L) x0 ]% n
  64.   printf("SystemCoreClock=%u\n", SystemCoreClock);* j: d2 V1 O! r! ~% O! S
  65.   % v, C* D$ ?1 X/ w, S
  66.   xTaskCreate_s(main_task, NULL, 2 * configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL);6 k3 P  O  \9 l+ |# c
  67.   xTaskCreate_s(uart_task, NULL, 2 * configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL);
    / D: ]* P" J" H$ F- i
  68.   vTaskStartScheduler();
    1 J  @5 F$ F" y0 p5 Y, a
  69.   return 0;
    7 t) t: q2 W/ E6 i8 d
  70. }
    2 j6 v5 l/ g/ |/ Y8 b

  71. ( I* x" e7 a: n2 A* t
  72. void USART1_IRQHandler(void)2 x& f# D4 {5 s+ R
  73. {% d/ o% i  Y( O( Z( G3 n& w
  74.   HAL_UART_IRQHandler(&huart1);
    , g2 g5 T) h) [7 @& z
  75. }
    . E9 \! V4 @1 {5 s* D
  76. " d+ j$ @$ l0 Q* L5 ]
  77. void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
    % {  A% U! b* r) p
  78. {
    6 f& d9 e& P: w
  79.   if (HAL_UART_GetError(huart) & HAL_UART_ERROR_ORE); [: w4 g5 [7 z
  80.     __HAL_UART_FLUSH_DRREGISTER(huart);
    + U: }4 M$ l, t  ~* U
  81. }
    0 [0 b1 q2 y; x8 }
  82. # O$ X7 l" P' ?" \4 N  |
  83. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)4 x+ S& n: H. t* D- D$ v) d* u* |+ g
  84. {. j; G. W* U3 \9 G
  85.   BaseType_t woken = pdFALSE;
    / B; e/ _3 n2 Q: M: a; i" B
  86.   
    : I: e4 t/ {5 D9 ~
  87.   if (huart == &huart1)& d  n/ Y- B4 G0 r& X
  88.     xSemaphoreGiveFromISR(uart_sem, &woken);
    ! d6 L! Z2 X. r0 P5 Q1 @/ k$ Q1 l, \
  89.   portYIELD_FROM_ISR(woken);9 q- n8 W$ n, M5 k- m
  90. }
复制代码

# n, D) s$ r  e程序的里面有两个任务,一个任务不停地在用printf打印,往串口输出数据,另一个任务专门负责接收串口字符。发送和接收函数用printf_mutex这个互斥量来保护(printf_lock和printf_unlock)。接收串口字符时等待xSemaphoreTake信号量,若xSemaphoreTake信号量有信号,说明串口收到了字符。然后判断接收是否已停止,若huart1.RxState == HAL_UART_STATE_READY,说明接收已停止,调用HAL_UART_Receive_IT函数重新开始接收。( F% S9 Q( Z- u, N  M. g
在HAL_UART_RxCpltCallback回调函数中使能信号量唤醒接收线程。在HAL_UART_ErrorCallback函数中处理Overrun错误(ORE),及时读取DR寄存器清除ORE位,防止陷入死循环。5 i! \4 T9 o, D1 C  e+ P
" y, T$ M: g* ?$ t
特别注意,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恢复数据接收。% j; W, n$ c  v9 U: M! l
————————————————
; e* g' z% i5 |0 P: Z版权声明:本文为CSDN博主「巨大八爪鱼」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
2 ?& N5 o5 q2 o$ f8 [! A0 P/ X原文链接:http://blog.csdn.net/ZLK1214/article/details/105624510

所属标签

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