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

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

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

3 q/ X+ i4 A" l# [
问题分析如下:
1、程序USART2不停接收并处理串口数据,波特率115200;
2、主循环卡死;
3、USART1中断及TIM2中断响应函数运行正常;(USART1及TIM2中断优先级均比USART2高)
4、出现现象后,拔掉USART2的接收数据线,现象不能回复正常;
5、出现现象后,拔掉后再插入USART2的接收数据线,现象不能回复正常;
6、并未出现HardFault现象;
% l# j+ u$ x: w; k3 c2 h' d
基于以上4点,可能原因如下:
1、USART2接收中断标志没有清除;
2、堆栈数据溢出,导致程序异常;
        3、USART2中断重入导致异常;
4、USART2中断函数被异常响应;
        5、USART2中断ERR;

8 W# \" ]& |( J  b# I4 [$ y$ ]3 L
对于以上可能原因一一分析:
1、中断接收标志清楚问题:
(1)USART2接收中断响应函数如下:
[url=][/url]4 G$ x( N8 V  r
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]
/ a9 o" Q: m" ?7 ^! `5 T- Z2 V) R
2 L$ c& ]; h, {' D4 R
(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中断标志无关。
; G0 K0 D; E4 C/ G/ z& p$ v
2、堆栈数据溢出,导致程序异常;
(1)使用2倍栈空间,问题存在,概率不会降低;
(2)使用0.5倍栈空间,问题存在,概率不会提高;
(3)使用0.25倍栈空间,程序运行进入HardFault;
结论:与堆栈无关。

- ^3 p% t& }1 t# e# x
3、USART2中断重入导致异常;
(1)使用标志法,确认出现问题时,中断响应函数没有重入;
结论:中断响应函数没有重入。

1 m5 s+ t: `4 }
4、USART2中断函数被异常响应;
(1)USART2中断函数可以被正常调用,只是不停进入中断响应函数,卡死主循环;
(2)检查程序Map,没发现与中断响应函数地址相同的函数;
(3)检查中断向量表,没发现异常;
结论:中断函数没有被异常调用;
5 Z) R: f: J4 B' J$ g# i/ I
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# f. S0 r8 d& M
带着以上几个疑问,查看了参考手册,才恍然大悟!如下:
(1)打开RXNEIE,默认会同时打开RXNE和ORE中断。
(2)必须第一时间清零RXNE,如没及时清零,下一帧数据过来时就会产生Overrun error!
(3)错误就是ORE导致的
出现错误时,读了RXNE=0,出错应该是上图打勾的情况,如下

3 n8 r6 \1 X: M
(4)如文档说明,要清除ORE中断需要按顺序读取USART_SR和USART_DR寄存器!
那就是说USART_ClearFlag清掉所有Flag后,还必须读一遍USART_DR寄存器!
      经过测试出现问题后依次读读取USART_SR和USART_DR,程序回复正常!

0 j* G; M+ j0 t* l7 m& S2 i
(5)那还有一个问题,为什么USART_GetITStatus读不到ORE中断标志?
读USART_GetITStatus函数就知道了,只有CR3的EIE置1且SR的ORE置1,读出来USART_GetITStatus(USART2,  USART_IT_ORE)  才是 SET。
见CR3的EIE位说明。

5 N. O. b- \5 l/ k
解决办法,出现通过接收时,通过USART_GetFlagStatus读取ORE,若不为RESET,则读取DR数据丢弃。
修改如下:
3 ?; _0 D, e5 G9 e* b  F( [
[url=][/url]+ i+ `  K4 p/ ]0 w  m: c
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], e! i# h3 M& N9 }3 r* z7 F) }" N8 K' }

1 u* A$ Q5 |5 I; U' o
总结:
1、看文档!看文档!还是看文档!(重要的事情要说3遍)
2、库函数用的时候,也要注意其实现,稍有不慎就可能用错。
3、注意USART_GetFlagStatus与USART_GetITStatus的区别,还有中断响应机制。
4、任意时候都要考虑出错处理。
  i: X. |" v* Z8 a3 z3 F3 M; d
赞 1 收藏 3 评论2 发布时间:2017-11-21 09:07

举报

2个回答
五哥1 回答时间:2017-11-22 12:23:21
1、看文档!看文档!还是看文档!(重要的事情要说3遍)
, [- `5 t( k, {7 y- ^2 u! `2、库函数用的时候,也要注意其实现,稍有不慎就可能用错。
8 c( ~1 M8 ?! @0 D2 E3、注意USART_GetFlagStatus与USART_GetITStatus的区别,还有中断响应机制。/ k$ h7 y8 @$ K5 N3 Y" X, o
4、任意时候都要考虑出错处理。
) j8 Z( w9 d: r9 ^; r5 p5 R" s! Y8 v  I+ \0 y
总结的非常好,支持。
zlk1214 回答时间:2020-4-19 23:50:13
其实说到底,就是Overrun(USART_SR_ORE)在作怪。+ G; p! X" J2 F
串口接收函数HAL_UART_Receive_IT调用后,会有三种结果:0 w7 e& Q2 z1 w  ?

$ r( M7 p* P3 d6 R  I# U' M第一种:只调用HAL_UART_RxCpltCallback回调函数: d5 Z( Q9 N1 J4 K/ c1 a' F
第二种:HAL_UART_RxCpltCallback和HAL_UART_ErrorCallback回调函数都调用+ B  V$ u5 O2 b  D
第三种:只调用HAL_UART_ErrorCallback回调函数" Y. |. h7 U# o

- t9 ?% A. S4 ^6 ~  |长时间(半小时以上)发送大量串口数据时,单片机会因为来不及处理串口数据出现Overrun的错误,此时USART_SR_ORE位为1。' N9 k9 e) ]0 n
到了一定时候,当USART_SR寄存器的USART_SR_RXNE为0,且USART_SR_ORE位为1时,就会出现第三种情况,只有HAL_UART_ErrorCallback这个函数会被调用,而且USART_SR_ORE位始终不能自动清除,串口就不能继续接收数据,USART_SR_RXNE位一直为0,这样就产生了死循环。
  w6 ?) d; E/ k  l函数调用HAL_UART_Receive_IT开始接收数据,而因为ORE=1,马上产生串口中断,进入HAL_UART_ErrorCallback,然后接收自动中止(注意看UART_EndRxTransfer函数,里面只是简单地中止接收)。RXNE位一直为0,永远进不了HAL_UART_RxCpltCallback。而HAL库函数又没有清除ORE位的功能,所以即便再次调用HAL_UART_Receive_IT也是同样的结果。5 y5 C: w; _* F7 Z: l; y; \9 S
9 ]' w" y. n6 M" _/ J
手册里面可以看到,ORE位是只读位,只能用特殊的方法清除:
* l/ G& F2 w( J
) A1 D7 q- K2 SBit 3 ORE: Overrun error
% B% C) z9 O* ]5 t: x3 tThis bit is set by hardware when the word currently being received in the shift register is3 \9 y) T7 |3 z( \* l3 f$ Q/ \9 k
ready to be transferred into the RDR register while RXNE=1. An interrupt is generated if9 E( ]) d/ I3 M' z- p
RXNEIE=1 in the USART_CR1 register. It is cleared by a software sequence (an read to the
* q* t: C9 y; \% w/ p  k& U/ c' h9 YUSART_SR register followed by a read to the USART_DR register).
/ G/ A! i# V4 h" w1 m0: No Overrun error
. r& T9 g/ j8 ^  g. U- ~' ^1: Overrun error is detected
2 F! O; u' p/ @, g3 dNote: When this bit is set, the RDR register content will not be lost but the shift register will be
5 N5 T5 ]4 X: O! N  E# Foverwritten. An interrupt is generated on ORE flag in case of Multi Buffer7 }  ]) D/ ~5 U$ \. {
communication if the EIE bit is set.* D( C# n% o0 ^+ _+ ~" Z! V

' c' W) j! R- O- U* y* `清除ORE位的方法是,先读USART_SR寄存器,再读USART_DR寄存器。只要清除了ORE位,就可以打破这种死循环的状态。
+ e, h4 a- p5 J2 }HAL库里面有一个__HAL_UART_FLUSH_DRREGISTER宏可以用来读DR寄存器:' O0 o# c6 B) ^( @8 x# _$ w
#define __HAL_UART_FLUSH_DRREGISTER(__HANDLE__) ((__HANDLE__)->Instance->DR)
- j( n. N$ b) q" V所以,我们只要在ORE错误产生时,读一下DR寄存器,就可以解决这个bug,退出这种死循环。
' N1 n5 h3 _$ r. @: G/ z4 x' A8 N3 C3 Y8 [3 b% y$ }/ R" \8 ]
    void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)6 z* a8 E0 d+ k. f% G$ K: z3 @
    {
7 Y! \9 X' s9 L* J9 G5 L: }* X: @0 k# w      if (HAL_UART_GetError(huart) & HAL_UART_ERROR_ORE)0 L* I$ B  w$ }) \5 Y" t
        __HAL_UART_FLUSH_DRREGISTER(huart);
2 Q) m6 `4 G+ `& S. w3 U# X    }4 {0 S5 R: W9 K3 @0 Q4 ]. x
/ b* |4 [( \5 \6 K. ?* `+ P
另外,调试的时候要特别注意,不要把Keil的USART寄存器窗口打开了。因为Keil在读USART1->DR寄存器的时候,会导致USART_SR_RXNE位被清除,程序就可能收不到串口数据。
/ h, |$ W2 k7 |- U! @" @
! p+ r- Z" h1 `  b1 W6 N; D4 O# l参考程序:
. n' X+ Q3 L( y! ]! W+ @. \8 `
  1. #include <FreeRTOS.h>
    6 p* X3 \2 `* H; E& H* I
  2. #include <semphr.h>
    4 L" y' _% h& a$ i. r2 Y8 `8 @- W
  3. #include <stdio.h>7 q& D9 ?3 e% A9 n% z3 k# M
  4. #include <stm32f2xx.h>
    / V& n4 W+ p$ t* ], e
  5. #include <task.h>4 g8 ^6 e5 F/ @2 C" n
  6. #include "common.h"
    1 N) D) A% h- X" p# v
  7. 7 m. D, E& r( x6 U& h& t" O" E# k1 i
  8. extern UART_HandleTypeDef huart1;
    ! ?* }8 U, u6 }5 Q
  9. RTC_HandleTypeDef hrtc;+ V* }7 P  d6 B* T+ |
  10. static SemaphoreHandle_t uart_sem;  q! |' F$ D% A; _' J. u  @

  11. $ C+ k7 F( g1 F% i  b- I8 [2 j+ X
  12. static void main_task(void *arg)
    ! v4 j) \9 b# S# _3 a: N6 S
  13. {
    / d$ D+ w5 |, |# Q1 o, ^
  14.   RTC_DateTypeDef date;+ p, a- e6 W, y
  15.   RTC_TimeTypeDef time;) t- ?$ W/ t0 e, a1 m6 H
  16.   
    - M) e/ S( n5 p
  17.   hrtc.Instance = RTC;7 I0 d4 @; h$ d4 L. S0 L9 E- F+ {
  18.   while (1)
    3 C2 W+ F6 O6 Y
  19.   {) ]4 a0 S& ?& G* V1 z5 m+ R1 z
  20.     HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BCD);" K6 B2 M$ ?1 Y
  21.     HAL_RTC_GetDate(&hrtc, &date, RTC_FORMAT_BCD);
    1 C6 c% h; U  k
  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);
    ! D- \4 d# n8 i- g: w+ j
  23.     printf_s("rxstate=%#x, RXNEIE=%d\n", huart1.RxState, __HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_RXNE) != RESET);
    ! ]+ ]  K( r* r& Z
  24.     vTaskDelay(pdMS_TO_TICKS(10));
    # x& r" R+ d. }& E6 N. v$ e7 @
  25.   }1 R6 Q% E6 A/ V1 V$ ^% a5 k8 Z
  26. }
      s% ?5 E. T% i1 V4 E& b
  27. % N0 o7 X* F3 x
  28. static void uart_task(void *arg)% E. l+ a& K2 R
  29. {# D# T* W/ {3 C
  30.   uint8_t data;$ ~  }: x: X8 `; ~
  31.   BaseType_t bret;
      S4 s9 E8 p- U
  32.   HAL_StatusTypeDef status;
    " B# a6 R  j! q6 i
  33.   2 ]! b1 ]' m. C+ ]2 h' ^! K
  34.   uart_sem = xSemaphoreCreateBinary();8 d# R9 l2 ?1 y* ~9 o" l: C
  35.   HAL_NVIC_SetPriority(USART1_IRQn, 1, 0);5 T, k7 s2 e9 _1 [8 C, v
  36.   HAL_NVIC_EnableIRQ(USART1_IRQn);' M8 w" \5 |! g! p
  37.   HAL_UART_Receive_IT(&huart1, &data, 1);' o# e# u/ S! a; D% [( ]1 q8 Z
  38.   while (1)
    7 ~4 Y* O/ |9 ^3 B) Z
  39.   {' R3 f/ H* D: T/ L
  40.     bret = xSemaphoreTake(uart_sem, 100);" [" R' m; C$ y; y
  41.     if (bret == pdTRUE)* S4 Q( P- W" S8 t; N& f
  42.     {
    : S$ J5 F2 r: }
  43.       if (data == 'a')9 H$ d; g9 j2 B" {
  44.         printf_s("Received: %c!\n", data);' E8 T1 R. }% g" W
  45.     }3 r% b+ A  w" o
  46.     : u5 W7 h6 g. A; a; R* z
  47.     if (huart1.RxState == HAL_UART_STATE_READY); w4 ]- o4 ^5 x& |) x, ~
  48.     {
    5 p5 [% w) j+ c$ ^
  49.       printf_lock();
    , r$ a+ }) q3 E/ q8 p- \
  50.       status = HAL_UART_Receive_IT(&huart1, &data, 1);
    ) M. Z( s$ b, M) _7 w1 U
  51.       configASSERT(status == HAL_OK);3 H" J6 k# l+ Z0 G& O
  52.       printf_unlock();
    2 C* Q4 R! k: n- ?
  53.     }0 E# v3 \6 {3 Q( |
  54.   }7 q& V7 @  B3 V
  55. }) B! O4 }; e6 _3 c

  56. / |+ M+ q9 [3 w# _- p
  57. int main(void)/ ]6 C, p( ?* [5 c/ K) A& j3 g  t0 w
  58. {7 S0 ?4 m: s  [7 \4 ~' t9 W
  59.   HAL_Init();6 Q2 y7 A4 x; A% H! s
  60.   9 c/ d3 N/ U; C  o
  61.   clock_init();. z1 b" Z8 q7 d
  62.   usart_init(115200);
    ( i& q( n2 _7 z4 P# f* x. T6 b+ S9 d
  63.   printf("STM32F217VE USART1\n");! B8 x$ f4 M' l1 A  ~0 `  T' T6 v
  64.   printf("SystemCoreClock=%u\n", SystemCoreClock);
    4 E+ V! I; h; \5 F/ h- q( Q
  65.   
    9 O3 m) U( U3 S2 s( q$ v! t0 M. S
  66.   xTaskCreate_s(main_task, NULL, 2 * configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL);% D, W) N( |9 C* J0 ]7 k$ o* c
  67.   xTaskCreate_s(uart_task, NULL, 2 * configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL);4 W3 a' \) ~; T" m
  68.   vTaskStartScheduler();
    7 A+ Q, e- \* J9 V1 z! `+ ?% W
  69.   return 0;
    8 z2 C/ X, k. F3 {$ h% B
  70. }
    ' U0 z* V( Y+ c, T1 `

  71. 1 `. i0 K3 k& S4 y8 X$ k2 d
  72. void USART1_IRQHandler(void)
    5 I5 G* p- l2 y; e
  73. {
    0 k/ @2 M2 d# C2 ?$ ]+ S- r9 q
  74.   HAL_UART_IRQHandler(&huart1);  ]3 g  G1 B  R# H6 c9 C/ m7 a
  75. }# D( u; T6 |, e; F& W

  76. + R: S2 Y5 u1 L
  77. void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
    ; ]- ^/ Z$ j4 \7 G
  78. {' n  \% P$ M' q+ i9 i' R
  79.   if (HAL_UART_GetError(huart) & HAL_UART_ERROR_ORE)
    ( K! c; }3 z) }$ k1 l- F
  80.     __HAL_UART_FLUSH_DRREGISTER(huart);. q* |4 a# u; l6 O1 R4 M
  81. }% z- X' B) m% g# Y, f

  82. 4 h/ S9 z' k" Z3 |: R, S
  83. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)" C' W: v1 d- B% K$ j+ [
  84. {
    2 S8 R% J+ I  e, g
  85.   BaseType_t woken = pdFALSE;2 _5 P" w6 i8 K3 z
  86.   0 F1 z6 g9 N1 e% P% X" n
  87.   if (huart == &huart1)3 j  O4 C$ q/ Q+ A
  88.     xSemaphoreGiveFromISR(uart_sem, &woken);5 q6 s# J% [( C$ M2 B. O5 |
  89.   portYIELD_FROM_ISR(woken);
    $ P- x! F/ D8 ~, y* l
  90. }
复制代码

" I: @* ?! Q  e6 s! ^程序的里面有两个任务,一个任务不停地在用printf打印,往串口输出数据,另一个任务专门负责接收串口字符。发送和接收函数用printf_mutex这个互斥量来保护(printf_lock和printf_unlock)。接收串口字符时等待xSemaphoreTake信号量,若xSemaphoreTake信号量有信号,说明串口收到了字符。然后判断接收是否已停止,若huart1.RxState == HAL_UART_STATE_READY,说明接收已停止,调用HAL_UART_Receive_IT函数重新开始接收。
4 w: l6 {5 u! U4 b% `- y' M在HAL_UART_RxCpltCallback回调函数中使能信号量唤醒接收线程。在HAL_UART_ErrorCallback函数中处理Overrun错误(ORE),及时读取DR寄存器清除ORE位,防止陷入死循环。
0 {2 z9 |. D6 \6 w, n
6 c1 W5 @' B* g; s/ G' U& 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恢复数据接收。
/ m4 r- Y3 H) h9 K————————————————# C4 E( D- t$ T
版权声明:本文为CSDN博主「巨大八爪鱼」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。& x$ k5 X" I$ d# [# O. o& j
原文链接:http://blog.csdn.net/ZLK1214/article/details/105624510
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版