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

基于STM32串口中断之缓存区溢出卡死经验分享

[复制链接]
攻城狮Melo 发布时间:2024-5-29 19:53
一个关于STM32串口的问题。他想实现接收无限制字符数量的串口信息。. r/ x( l, E& t! `
* F7 E2 M: m% ]: t
但是他遇到了一个问题,他在主函数中发送循环发送内容(500ms的延时)串口一中断回调函数中如果收到了串口内容,就利用串口发送。
7 i$ t! M0 n( {4 ]" V
, r) d- l6 V1 |+ _5 X0 F
但是他的串口一旦接收到了信息就会导致主循环中的串口发送极快。他的主代码如下
) J* V3 u* z$ @1 Z- a" J$ E2 L  M7 F
  1. unsigned char RecieveBuffer;
    - h0 B3 p0 }6 _( I1 P& r8 h
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
      A7 y. l5 r5 L8 i" @( p3 O" S
  3. {, N2 F1 _9 a) l' H
  4.   UNUSED(huart);3 E- N" E1 b& P7 i, [' I  d
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去
    , [& s: K. X0 \' ^5 r: r
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t" m. T! C' j) C4 l
  7. }
复制代码

# V6 {7 `* A: n6 }' _# b看了他的代码,就这么短短的7行代码,让我汗流浃背。
" C% e! B# Z( t) T' {3 p5 G/ h+ x1 U4 W2 {/ P6 c- R. [
他的发送和接收中有一个参数是8,这意味着当串口接收的寄存器中有8个数据时就会触发中断回调。
9 V+ _( K" ]5 ]
) f: p5 ]8 o6 x( k  N# S
我们需要注意的是,串口寄存器的数据会被存储到缓存区中,这里的缓存区相当于一个队列先进先出,当我们调用HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8);( x& U+ S6 {( H+ p# x: Q1 o
+ S) J" B6 _9 ^
会将RecieveBuffer作为缓存区,将串口接收的信息存入缓存区。
7 X' R8 u1 C& s1 H. {" O* L  A' `  Y6 [8 z$ Q5 h; m
那么让我们来试一下这段代码。6 e( @6 E& a) C- f3 I
1 j) J9 d% z- o, x: n5 o
微信图片_20240529195314.png
- x2 H: b/ d4 S3 ^
' U( I( a9 t3 G' ~2 ]2 f) I) {
可以看到,代码死住了。其实不难理解,我们发送数据的时候,由于缓存区只有一个字符的空间,我们发送八个字符的时候会导致缓存区溢出。所以我朋友的代码可以“正常运行”也是一件非常奇怪的事情。
8 B% m3 N8 I: h$ G5 p" Y4 l. q2 x2 ?% B1 C* m
所以正确的做法应该是:设置和缓存区一样长的读取字符。, g: k* y  b6 u/ x
' Y3 C5 Q- ]/ w% n
  1. unsigned char RecieveBuffer[8];5 f2 F6 C9 S0 C( x8 ~5 i5 _& y1 d
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    8 o, O5 V0 q4 z) \9 Q
  3. {
    , F# Y- G9 v; u/ l% p
  4.   UNUSED(huart);* J1 d9 F' U+ m# u# b4 H
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去$ J4 ?: u' z/ Y/ K
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t  k  @7 Z% ], Z8 {4 y, B% l3 G
  7. }
复制代码
# y/ ~2 P8 e1 h# S
这样子就可以实现每接收8个字符就调用中断一次
7 a& q: ?& v6 N: b9 X- e- T" t& p; v5 Z) s8 J
微信图片_20240529195311.png 3 V+ q7 n8 }3 \2 j; _
$ b! A3 q- h; C& N* a0 T
可以看到,我们正常的发送了8个字符。
4 e2 X  x$ ^: |/ |  N& M0 N/ L$ K; L
但是这样子有一个非常非常致命的缺点!  `6 l' C" k* h7 r

8 t2 ]- _$ W& J) o1 Z6 Q由于我们的代码每8个才会接收一次,所以当我们的发送的数量小于8时,必须发送多次才能会触发一次调用。
2 x% L& D) M! V8 k8 Q7 x  P) i! A7 [2 M
注意,这里的每次我们都是先发送再接收,因为初始化的时候是启用了接收。
; O  X# K! E, `% Z, M8 ~; L, J* j2 ]
$ L5 G' |/ V- j3 X9 @+ V: m. E; G
微信图片_20240529195307.png
" }6 q) k1 n3 Y+ [$ P# C; b
9 q. w' Z( T  }
当我们发送3次“123”时才能让接收的字符>=8,我们把前八个发送了出去,此时缓存区剩下了一个字符是“3”: |6 @+ y* U3 W0 G

) K2 i9 \% j' |
微信图片_20240529195305.png
/ \$ c" R8 Y7 A5 g0 H! L/ E" y8 D

8 P' ?& _+ I# g/ B0 g( C7 I第二次我们发送了三次“123123123”,再次让字符数量>=8,这时候把缓存区的八个“31231231”发送出去再接收后面的“23”,这时候缓存区的数据为:"23 "。2 D- f7 m7 M5 }# N5 n6 T4 B. N

  j$ F( _( e2 T0 A
微信图片_20240529195302.png ! l6 A& u; R5 _6 _, A! R$ P) p
! p# q% O1 q& C4 c- l
最后我们第三次发送“123”的时候,由于23 + 123 + 123 这时候,虽然这里按理来说正好八个,但是实际测下来,当这时候接收的时候系统就会卡死。
( F& N4 D8 H3 [3 U* X1 b
8 l- m: l! w3 D# F所以,实际上这种方法必须保证发送端的数据是完完整整的八个八个发送,多一个少一个都不行。
# {* c# [0 \# h8 J5 C% y, P( w' o1 l/ w# G
2 m! y% b- E; x7 A
解法( Z" S8 i5 Y; v! d- U' s0 S
事实上最标准的做法是单个字符单个字符处理。定义一个大的缓存区,用来存储接收到的字符并且确立一个结束符来确定数据流的结束。
  1. if((USART_RX_STA&0x8000)==0)//接收未完成3 ?& p# @' W+ j0 `4 K+ y8 {: P$ i7 U, [4 W
  2. {
    : t& g% M3 m7 \
  3.   if(USART_RX_STA&0x4000)//接收到了0x0d6 X$ o5 f5 B+ q7 D# D" Y
  4.   {
    $ _: A1 R6 \9 k6 w4 V- H; `! }
  5.     if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始, e0 ^) p: c- V) n# i# d0 d" O! p
  6.     else USART_RX_STA|=0x8000;  //接收完成了
    / @* p: {$ P5 _( o5 W
  7.   }9 `) q. V( J9 s+ \! d
  8.   else //还没收到0X0D  d7 f, A! h& k) }$ |  c
  9.   {  
    & o3 Z$ Y* y) s
  10.     if(Res==0x0d)USART_RX_STA|=0x4000;
    ; n3 s4 f" w6 F  ]" R. E
  11.     else
    7 G9 Y8 ^( e7 n5 b
  12.     {
    7 O! }! G8 c: C0 B1 C# j
  13.       USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;  [6 S6 O+ j2 E8 ?
  14.       USART_RX_STA++;4 p8 [" D) ~$ d/ e7 {/ S( O
  15.       if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收   
    7 P4 n4 n7 x. p2 g( ]
  16.     }     ) M" \! s/ R% U0 \
  17.   }
    & X3 x( B1 z6 `
  18. }
复制代码
/ L) @; g8 L% n" H: O+ v$ @

3 q/ R. Y  h) d, u

7 r* V6 K! |" y6 I4 S4 D8 K上述是正点原子的官方例程,其中USART_RX_STA是接收到的字符,即是确定一个结束符\r\n来作为一串字符的结尾并且检测长度是否超出长度。

* s& z3 }' M0 \5 S* j0 G- {5 ^$ t- t7 }' I% l; O7 K
这样子的代码容错率非常高,也确定了一个规范,并且避免了缓存区溢出的情况。
" D' ]/ g+ t9 i, u7 C- {( I
) z" s5 \/ s  P2 W5 B; A% S4 H' ~9 Y% B6 \+ }* n7 P: a+ T* G
转载自:电路小白3 Z- u9 F* j$ d( l% S( }0 m
如有侵权请联系删除
2 V5 x# |- h3 y3 t, |, q9 T
1 b* Q: A: `  g" p) v
赞 收藏 评论0 发布时间:2024-5-29 19:53

举报

0个回答

所属标签

相似技术帖

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