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

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

[复制链接]
嵌了个入式 发布时间:2017-11-21 09:07
资料转自别处,分享一下供大家参考,感谢原创的付出。
在一项目中,使用STM32作为主控,程序运行一段时间后概率出现主循环卡死现象。
0 j  O' u/ q1 ]/ Y; M. S
问题分析如下:
1、程序USART2不停接收并处理串口数据,波特率115200;
2、主循环卡死;
3、USART1中断及TIM2中断响应函数运行正常;(USART1及TIM2中断优先级均比USART2高)
4、出现现象后,拔掉USART2的接收数据线,现象不能回复正常;
5、出现现象后,拔掉后再插入USART2的接收数据线,现象不能回复正常;
6、并未出现HardFault现象;
$ }7 F4 s, s# d" B  E0 F  i/ C# e
基于以上4点,可能原因如下:
1、USART2接收中断标志没有清除;
2、堆栈数据溢出,导致程序异常;
        3、USART2中断重入导致异常;
4、USART2中断函数被异常响应;
        5、USART2中断ERR;
" h5 v" `% d3 j" b. {6 B
对于以上可能原因一一分析:
1、中断接收标志清楚问题:
(1)USART2接收中断响应函数如下:
[url=][/url]3 Y* @6 D* O4 p9 n
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]
5 K' Q4 P) d% E  y& M# t

0 l$ W3 L/ i. o
(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中断标志无关。
8 F, \/ L* P0 i+ \5 i. t
2、堆栈数据溢出,导致程序异常;
(1)使用2倍栈空间,问题存在,概率不会降低;
(2)使用0.5倍栈空间,问题存在,概率不会提高;
(3)使用0.25倍栈空间,程序运行进入HardFault;
结论:与堆栈无关。
: Y4 H# N5 v3 u5 ^( x/ p3 J
3、USART2中断重入导致异常;
(1)使用标志法,确认出现问题时,中断响应函数没有重入;
结论:中断响应函数没有重入。

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

' B+ y6 L! \* `  ~$ r' f5 Z7 `
(4)如文档说明,要清除ORE中断需要按顺序读取USART_SR和USART_DR寄存器!
那就是说USART_ClearFlag清掉所有Flag后,还必须读一遍USART_DR寄存器!
      经过测试出现问题后依次读读取USART_SR和USART_DR,程序回复正常!

. e, w- G# L% a: S: E( w6 C+ i
(5)那还有一个问题,为什么USART_GetITStatus读不到ORE中断标志?
读USART_GetITStatus函数就知道了,只有CR3的EIE置1且SR的ORE置1,读出来USART_GetITStatus(USART2,  USART_IT_ORE)  才是 SET。
见CR3的EIE位说明。

& p7 z4 k0 d  I# W& W6 R  Q! j
解决办法,出现通过接收时,通过USART_GetFlagStatus读取ORE,若不为RESET,则读取DR数据丢弃。
修改如下:
- q  w% n. z! F0 }; |* R
[url=][/url]
9 ]! s$ |" ^2 \: E 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]3 f+ D9 |! T% |) A& i
- c6 Y  h& `! c0 ~) F0 o% Y1 n" g
总结:
1、看文档!看文档!还是看文档!(重要的事情要说3遍)
2、库函数用的时候,也要注意其实现,稍有不慎就可能用错。
3、注意USART_GetFlagStatus与USART_GetITStatus的区别,还有中断响应机制。
4、任意时候都要考虑出错处理。
9 b9 D* v4 D# d& x3 A
1 收藏 3 评论2 发布时间:2017-11-21 09:07

举报

2个回答
五哥1 回答时间:2017-11-22 12:23:21
1、看文档!看文档!还是看文档!(重要的事情要说3遍)
! ]4 Q# d' r1 P* f! v2、库函数用的时候,也要注意其实现,稍有不慎就可能用错。
, ?: e$ e7 d8 n) ^- D4 m3、注意USART_GetFlagStatus与USART_GetITStatus的区别,还有中断响应机制。1 z7 k6 |/ n  W. G& \
4、任意时候都要考虑出错处理。
0 z, y: o$ x5 L3 Z+ _4 K
( G, V+ v* D. p0 L& `3 S总结的非常好,支持。
zlk1214 回答时间:2020-4-19 23:50:13
其实说到底,就是Overrun(USART_SR_ORE)在作怪。
. ]$ O- v! M4 f' I+ r  D* N串口接收函数HAL_UART_Receive_IT调用后,会有三种结果:+ A# I9 ^" ?9 K4 y) R

. }/ V9 f" g: j' n4 J第一种:只调用HAL_UART_RxCpltCallback回调函数
0 o. X7 x/ z' H' N, z- T第二种:HAL_UART_RxCpltCallback和HAL_UART_ErrorCallback回调函数都调用$ O6 [2 L  R! h' v+ p) x
第三种:只调用HAL_UART_ErrorCallback回调函数
( Q. T; o( a: x( X( v  |! b$ T/ x3 c4 }& T
长时间(半小时以上)发送大量串口数据时,单片机会因为来不及处理串口数据出现Overrun的错误,此时USART_SR_ORE位为1。4 G3 q, {8 R4 ?' B. g, M! ]
到了一定时候,当USART_SR寄存器的USART_SR_RXNE为0,且USART_SR_ORE位为1时,就会出现第三种情况,只有HAL_UART_ErrorCallback这个函数会被调用,而且USART_SR_ORE位始终不能自动清除,串口就不能继续接收数据,USART_SR_RXNE位一直为0,这样就产生了死循环。1 {# n9 O" ^: M! m* U& V
函数调用HAL_UART_Receive_IT开始接收数据,而因为ORE=1,马上产生串口中断,进入HAL_UART_ErrorCallback,然后接收自动中止(注意看UART_EndRxTransfer函数,里面只是简单地中止接收)。RXNE位一直为0,永远进不了HAL_UART_RxCpltCallback。而HAL库函数又没有清除ORE位的功能,所以即便再次调用HAL_UART_Receive_IT也是同样的结果。5 n3 g3 V2 T+ j( d
# K$ X" R* y; S, f9 Y# n, {; C* Y
手册里面可以看到,ORE位是只读位,只能用特殊的方法清除:- i7 H2 B; }+ o4 g/ l" V5 L: Z( c
* |* s; o1 d7 H/ p7 g
Bit 3 ORE: Overrun error# p2 s. j7 ]1 v# B3 v* K
This bit is set by hardware when the word currently being received in the shift register is
! Z8 o; \8 }, t$ v: I7 Tready to be transferred into the RDR register while RXNE=1. An interrupt is generated if
% E' b3 L. l' Z) IRXNEIE=1 in the USART_CR1 register. It is cleared by a software sequence (an read to the
3 P' }! U) F* Y/ _6 d5 CUSART_SR register followed by a read to the USART_DR register).( G4 @$ r8 z9 r7 _
0: No Overrun error
+ W% Y1 `+ a9 L  r' l+ E/ x; T1: Overrun error is detected$ l  d. E3 c6 g0 N* h: b
Note: When this bit is set, the RDR register content will not be lost but the shift register will be0 f) ~7 [$ U3 x( I
overwritten. An interrupt is generated on ORE flag in case of Multi Buffer% P4 v0 A/ [* R% ]* Y) y- d
communication if the EIE bit is set.
6 H& `3 ^8 d% [* N2 r  `
# q! |0 S' M0 l" }( I清除ORE位的方法是,先读USART_SR寄存器,再读USART_DR寄存器。只要清除了ORE位,就可以打破这种死循环的状态。
( P# J) a3 b. g2 k' ]! I/ rHAL库里面有一个__HAL_UART_FLUSH_DRREGISTER宏可以用来读DR寄存器:" o+ ]0 u; x: V0 L
#define __HAL_UART_FLUSH_DRREGISTER(__HANDLE__) ((__HANDLE__)->Instance->DR)' |# o; f% x$ {. j
所以,我们只要在ORE错误产生时,读一下DR寄存器,就可以解决这个bug,退出这种死循环。0 q( R4 X& z% s6 e/ I, \- }; B9 d; |

- C# c- p7 J% }7 K    void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)6 \1 J  S! t! n) V+ [
    {1 j3 S8 c! a' Z6 \0 ~8 Q
      if (HAL_UART_GetError(huart) & HAL_UART_ERROR_ORE)* g$ I: ^1 b# A
        __HAL_UART_FLUSH_DRREGISTER(huart);* B3 g# E& B9 {) P6 D9 O
    }
# `8 K6 P" m7 i9 `* D' L1 H: v
" G1 N+ m0 A1 a( x( p3 N) O; H( \8 v另外,调试的时候要特别注意,不要把Keil的USART寄存器窗口打开了。因为Keil在读USART1->DR寄存器的时候,会导致USART_SR_RXNE位被清除,程序就可能收不到串口数据。* G9 E5 H. p( Z# r" t! q* ?

2 m8 o, }$ p" h) e9 G参考程序:& I- Q+ G$ ^5 b$ H3 m, y
  1. #include <FreeRTOS.h>
    2 z4 A2 O1 h5 Q- F. y
  2. #include <semphr.h>
    ! Y- x1 l% V+ U6 L! |3 v# n
  3. #include <stdio.h>; Q* t8 M- V, W5 H. ^5 i
  4. #include <stm32f2xx.h>8 g1 h' o' {/ ~1 D! K: R, S7 n
  5. #include <task.h>3 _  }/ @: A7 ]# f  ^# ~
  6. #include "common.h"6 p& Q# a7 d; q4 q$ `5 F& m
  7. / P- g8 J& c( e4 I% v
  8. extern UART_HandleTypeDef huart1;3 b* ]( J+ l2 u( {) r9 {8 W
  9. RTC_HandleTypeDef hrtc;; v5 Z2 x& k) }( v7 P  G' Z& o8 A0 J. L" a
  10. static SemaphoreHandle_t uart_sem;
    7 b# j! F/ M( G" I. p- N  T. l* M
  11. 7 U" y4 P! |( V! j4 @7 w
  12. static void main_task(void *arg)
    ; c$ Y4 |5 d) T
  13. {
    5 b) h' H+ s) E0 t7 A4 K7 J
  14.   RTC_DateTypeDef date;
    ( p7 t( k9 E; P6 e  [0 N
  15.   RTC_TimeTypeDef time;
    + {8 f& O  r' I- p; e! t; Q
  16.   
    & `/ ?7 ~* r/ z2 N9 D
  17.   hrtc.Instance = RTC;" s4 ~1 o* i$ H6 }8 s( K
  18.   while (1)
    8 H) @0 I  n7 V4 f
  19.   {
      O3 Q* H: k0 g) _) f. e
  20.     HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BCD);+ j% F) X0 g5 \2 b
  21.     HAL_RTC_GetDate(&hrtc, &date, RTC_FORMAT_BCD);
    ! j" u& j. o" Z( Y! i
  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);
    ; O6 j! p( Z8 D2 U
  23.     printf_s("rxstate=%#x, RXNEIE=%d\n", huart1.RxState, __HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_RXNE) != RESET);
    , k( r0 ?0 {) K0 A4 g1 S3 ^  m: M
  24.     vTaskDelay(pdMS_TO_TICKS(10));: ^5 t: ]  U) Z- v7 Q# t
  25.   }2 A1 B' [  @+ d9 d+ n: G
  26. }8 ~6 P( D) X7 A* @# N+ u% t
  27. 8 M, c1 E1 R) e$ E& y
  28. static void uart_task(void *arg)
      ^5 y6 p8 k' y: y- @1 f- K9 @
  29. {
    ( ]9 t+ [2 M! I
  30.   uint8_t data;4 I1 a$ g3 k, f7 E) a
  31.   BaseType_t bret;
    $ z, v" e; X$ a5 I" }
  32.   HAL_StatusTypeDef status;5 k$ s; Y" j5 G0 \) R
  33.   8 B, A! S8 l: S
  34.   uart_sem = xSemaphoreCreateBinary();
    . O( X4 E+ T3 r& @$ x9 g: d9 }
  35.   HAL_NVIC_SetPriority(USART1_IRQn, 1, 0);; H1 [& L0 k+ o9 V
  36.   HAL_NVIC_EnableIRQ(USART1_IRQn);
    * R, J+ q  ^; Q! ]! a, G
  37.   HAL_UART_Receive_IT(&huart1, &data, 1);" A) c; B* t6 B$ }; w
  38.   while (1)
    7 G/ ^" Q& f3 `1 U! l
  39.   {, i! N0 R/ Z/ j0 M: }8 |
  40.     bret = xSemaphoreTake(uart_sem, 100);; Y  e3 M# |$ c* b
  41.     if (bret == pdTRUE)
    6 [; B! N( b6 O6 |$ m. p
  42.     {
    ; h8 x5 x2 m% z' j
  43.       if (data == 'a')
    " y1 I; A3 Z, M, z: D9 A% A
  44.         printf_s("Received: %c!\n", data);0 A  w! D9 x$ A) |4 s
  45.     }
    & A5 h5 K0 f; N) E+ R
  46.    
    8 X2 S6 s$ u4 a" f1 F# t& X9 Y4 k
  47.     if (huart1.RxState == HAL_UART_STATE_READY)- ?0 W0 V1 E) T+ s# k7 b* a! C
  48.     {
    1 }3 a/ t4 A+ r
  49.       printf_lock();
    ) \0 n0 B9 w- t3 W# M4 j3 _
  50.       status = HAL_UART_Receive_IT(&huart1, &data, 1);/ Y( ]# H. \6 K! D
  51.       configASSERT(status == HAL_OK);
    $ Z% J6 ?/ b  o1 Y; m0 |1 w
  52.       printf_unlock();
    9 b+ `6 d$ o0 ?, P
  53.     }
    # Y9 U' ?% n, T* r. m  f' a- {; {
  54.   }
    " S) l8 R2 h+ I! A0 E
  55. }
    & {$ B5 k9 N5 ]' j$ y) _5 D1 f. P
  56. . w/ ]3 H# t2 r% m# c, I# a2 {, i
  57. int main(void)% v# r7 O/ Y7 Q5 c3 F+ v1 R0 V
  58. {
    ( s  E' c* p" F% }
  59.   HAL_Init();
    + Z" d3 ^2 i" i& o) c) L  f
  60.   
    ; a% Z( h" U8 Y# Z
  61.   clock_init();" z6 h# R% `' l. ~* b# y
  62.   usart_init(115200);
    / F4 B! P+ I9 p, F# z
  63.   printf("STM32F217VE USART1\n");- I5 e- E4 C2 Y9 h7 l2 k
  64.   printf("SystemCoreClock=%u\n", SystemCoreClock);
    6 \. C7 r5 }8 a! q+ F: z
  65.   
    # _  ~; j% J% _1 j
  66.   xTaskCreate_s(main_task, NULL, 2 * configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL);
    : B: G% e7 {/ a0 i2 e$ _. s  \
  67.   xTaskCreate_s(uart_task, NULL, 2 * configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL);
    ) }6 Q% t2 X4 s4 V' Y5 k
  68.   vTaskStartScheduler();
    ' ^7 {  C7 F+ K# X4 g
  69.   return 0;. S  s( c% B1 x: M; z+ C6 f
  70. }* g1 N6 p$ z$ G: I
  71. 3 w" t! |1 a3 d0 v' i/ C4 U, i8 x/ a
  72. void USART1_IRQHandler(void); V1 Q1 u3 V1 P
  73. {
      m8 ~. \9 _, X9 s* N  L
  74.   HAL_UART_IRQHandler(&huart1);
    9 B! K* [8 P! }5 s# _5 l$ L
  75. }. j- r, F( m7 H7 P9 T2 K/ O, p
  76. 5 V; y1 A* w* u8 S. M" g" y6 v
  77. void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
    # r9 ]8 W; I7 S) x+ _
  78. {7 I. C1 V2 }5 j/ F# D3 M
  79.   if (HAL_UART_GetError(huart) & HAL_UART_ERROR_ORE)2 B0 e+ O* Y  @+ z- q/ f; J
  80.     __HAL_UART_FLUSH_DRREGISTER(huart);9 @5 \& ]( q$ ]- K7 R' H
  81. }
    4 A1 A- ?) m1 r2 i
  82. 0 I+ C9 o9 X( i) ~
  83. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    9 u& h; P8 t" I% R  F
  84. {& s' [+ D- }: y* M; ~" M+ T9 m
  85.   BaseType_t woken = pdFALSE;
    : z2 [' g% v+ w2 V3 l% J' C0 W
  86.   
    9 _# W+ C8 M( D% r+ G* [% @, r0 g
  87.   if (huart == &huart1)" o8 U" N5 u0 l, W" z& v
  88.     xSemaphoreGiveFromISR(uart_sem, &woken);) [6 K0 H# h5 K0 j
  89.   portYIELD_FROM_ISR(woken);
    8 O; O  F! F; x. D& X8 o% h
  90. }
复制代码

0 j; Z4 K' z0 w程序的里面有两个任务,一个任务不停地在用printf打印,往串口输出数据,另一个任务专门负责接收串口字符。发送和接收函数用printf_mutex这个互斥量来保护(printf_lock和printf_unlock)。接收串口字符时等待xSemaphoreTake信号量,若xSemaphoreTake信号量有信号,说明串口收到了字符。然后判断接收是否已停止,若huart1.RxState == HAL_UART_STATE_READY,说明接收已停止,调用HAL_UART_Receive_IT函数重新开始接收。3 ~0 m% j" {8 I  X6 w8 j6 b
在HAL_UART_RxCpltCallback回调函数中使能信号量唤醒接收线程。在HAL_UART_ErrorCallback函数中处理Overrun错误(ORE),及时读取DR寄存器清除ORE位,防止陷入死循环。& @) G) w9 T0 U$ K2 x
* G4 c6 l' b" Z( F& [
特别注意,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 ~* |1 h& X  I* x
————————————————& r7 d: i9 d" X1 K7 x- G. B, R  @
版权声明:本文为CSDN博主「巨大八爪鱼」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
2 q  D! \8 s/ `6 W1 Z原文链接:http://blog.csdn.net/ZLK1214/article/details/105624510

所属标签

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