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

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

[复制链接]
攻城狮Melo 发布时间:2024-5-29 19:53
一个关于STM32串口的问题。他想实现接收无限制字符数量的串口信息。
( h: c, P1 ?0 v  F6 _$ Q( }! M% p, g
但是他遇到了一个问题,他在主函数中发送循环发送内容(500ms的延时)串口一中断回调函数中如果收到了串口内容,就利用串口发送。* Z/ j' U( G: O; l, b- P; u

0 G1 T, s0 a, `3 q0 \5 y
但是他的串口一旦接收到了信息就会导致主循环中的串口发送极快。他的主代码如下
( C1 @" G/ o+ s; C
  1. unsigned char RecieveBuffer;
    4 G8 |6 B; G' e/ O' Y* O0 h
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart), I; q% i  o* E8 s; _5 C" `6 a
  3. {' O: {+ V, X* G
  4.   UNUSED(huart);2 U% c, p9 ]7 d$ r
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去9 u! X& A3 p$ o  G# @5 k
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t
    $ \6 k  U" A- z5 t$ W; c
  7. }
复制代码
/ Q! n9 F* I" o: Q0 d
看了他的代码,就这么短短的7行代码,让我汗流浃背。
4 L7 p4 F8 G/ E- J4 D  R: _# p- X; v6 I( p6 d
他的发送和接收中有一个参数是8,这意味着当串口接收的寄存器中有8个数据时就会触发中断回调。; f8 R/ {" Z' H/ G6 P0 T$ @

0 c+ e7 J$ k0 H+ G
我们需要注意的是,串口寄存器的数据会被存储到缓存区中,这里的缓存区相当于一个队列先进先出,当我们调用HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8);( @  p" S  ]1 G

& q# o& O9 O6 B. d
会将RecieveBuffer作为缓存区,将串口接收的信息存入缓存区。
7 M; @$ o8 ^" [% l" D4 ?' n
) w( l6 r; L, s
那么让我们来试一下这段代码。
/ z/ x% Q/ |) h) N) b! [/ t- m2 j+ N' ^
微信图片_20240529195314.png
8 I, Q; M% @, R7 I: \$ p1 d! d* x7 Z
1 Y4 }6 R4 [5 [1 G
可以看到,代码死住了。其实不难理解,我们发送数据的时候,由于缓存区只有一个字符的空间,我们发送八个字符的时候会导致缓存区溢出。所以我朋友的代码可以“正常运行”也是一件非常奇怪的事情。
! [4 w; N# x% K$ H( w
1 I) P* X( N# k! b" z. ?所以正确的做法应该是:设置和缓存区一样长的读取字符。
* H: m& O; P8 m! |5 c9 A- Z8 J9 n
' \6 X' O& T. ~" v5 [$ y7 m+ Y
  1. unsigned char RecieveBuffer[8];
    * i3 `# R. K) R7 d/ f) k
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    . E5 @& U) i* d! I' X
  3. {
    ) z9 y4 g0 H# h. I$ z) t! ]
  4.   UNUSED(huart);5 ^( {" b! }4 f
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去
    & {6 a, H3 W- D9 s+ B
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t" I9 A+ g  X9 Q: w
  7. }
复制代码

- K# U0 \& @; E1 ?0 M& ~, t1 |这样子就可以实现每接收8个字符就调用中断一次2 i3 e, r2 ?% `) ^" X
' {/ ^  l9 A( r
微信图片_20240529195311.png % @/ n# ~: N" P" u, J% Q. q

( J2 F3 X, D3 L可以看到,我们正常的发送了8个字符。
4 J# R% J. `  B( x* U3 i! k- ~( ?" O, n. ?. j9 x, q) P5 d6 S8 u; p
但是这样子有一个非常非常致命的缺点!7 Z) E. s( o* ^& ^0 ~" X
! X* X2 {6 a5 A/ K
由于我们的代码每8个才会接收一次,所以当我们的发送的数量小于8时,必须发送多次才能会触发一次调用。3 z( [7 p0 H' b1 d3 b, e3 ^" i$ Y2 d
9 G1 o6 l' J8 Z# U/ {, {* o
注意,这里的每次我们都是先发送再接收,因为初始化的时候是启用了接收。
- _9 i' L& l" I" @: z$ z$ B4 ]) I" q7 Y; Y* D' g* @
微信图片_20240529195307.png
2 w8 w+ e" H9 F' |& B7 `# b

) Z, `& h% K0 B+ x+ Y& I/ R. h9 ~8 B当我们发送3次“123”时才能让接收的字符>=8,我们把前八个发送了出去,此时缓存区剩下了一个字符是“3”
# @5 z' z: m2 s% e" c9 E% z3 ]% F- W, K1 n# T9 E! {
微信图片_20240529195305.png 3 T1 R; G* u/ S6 v
  d6 d1 Y5 t# D7 a$ |8 `
第二次我们发送了三次“123123123”,再次让字符数量>=8,这时候把缓存区的八个“31231231”发送出去再接收后面的“23”,这时候缓存区的数据为:"23 "。' A' [" v( q: w" y" i) B* s% ~% }

2 w- b# ^; g" |6 S3 H
微信图片_20240529195302.png
! N& I" R$ L" t  @/ S6 A

+ `& m( c2 @! G; V0 O: o最后我们第三次发送“123”的时候,由于23 + 123 + 123 这时候,虽然这里按理来说正好八个,但是实际测下来,当这时候接收的时候系统就会卡死。
5 r: Y% Q. M8 \( N1 [4 D- Y' _# \  w3 N1 D* n/ m& @
所以,实际上这种方法必须保证发送端的数据是完完整整的八个八个发送,多一个少一个都不行。- i2 k' D0 t* [- F

# R% W2 L1 o0 S2 v

1 ]* }) c7 v* ?1 e: T解法
: F2 c2 C: C/ H, i5 \2 n6 Z事实上最标准的做法是单个字符单个字符处理。定义一个大的缓存区,用来存储接收到的字符并且确立一个结束符来确定数据流的结束。
  1. if((USART_RX_STA&0x8000)==0)//接收未完成2 p, F6 v4 Y9 |' s, E
  2. {
    1 E' T/ ~5 [9 K* ~
  3.   if(USART_RX_STA&0x4000)//接收到了0x0d/ a2 l4 ]; T9 ~. h3 \  W3 R
  4.   {1 O0 R! U7 Z" Y. I9 S7 m) x7 M
  5.     if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始  Y; d# d) _, h+ ?0 f) v8 L/ u, ]
  6.     else USART_RX_STA|=0x8000;  //接收完成了
      K/ g  j, [  F, Q3 l6 n# P$ C
  7.   }
    2 A$ J  I( e5 K' ~# Q% i& \8 s
  8.   else //还没收到0X0D+ z! L; n6 u8 {0 j& I8 v2 {
  9.   {  
    ' ~0 G/ t$ s/ H: p, H  |. j
  10.     if(Res==0x0d)USART_RX_STA|=0x4000;4 ~+ S+ a% \/ b- J$ r' Z0 P
  11.     else4 |4 _; [/ x% D3 t* z
  12.     {
    5 f3 A& _7 X* B6 _* ^% q& g0 n
  13.       USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;  b$ c, T9 t% p; S0 c
  14.       USART_RX_STA++;2 M9 [1 y2 }8 r9 F& B) ?
  15.       if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收    , p) h. d. v  D8 g3 h& [
  16.     }     
    9 @4 e+ `/ k4 p  x, v0 u7 ?+ d: ~5 w
  17.   }
    / B: @1 {7 }9 H' |. E
  18. }
复制代码
" Z- ]8 W3 w! \: L# Z: a' V

' w) s1 L& n) @* \- V# a$ R
' j. O& E6 o/ O+ k: a' D; p
上述是正点原子的官方例程,其中USART_RX_STA是接收到的字符,即是确定一个结束符\r\n来作为一串字符的结尾并且检测长度是否超出长度。

+ @. U6 [% b8 ~/ I$ v
; l) w/ b$ v0 n3 ]$ ~这样子的代码容错率非常高,也确定了一个规范,并且避免了缓存区溢出的情况。
# p" M& v0 O0 n( U; p3 Z8 d& ^) k
( d0 R: {. `7 H+ k* O2 C' L
转载自:电路小白
1 |# ], e; {) z3 G" {如有侵权请联系删除: ~8 [- {  `( M! j/ w- j, S$ W3 D
4 I  K* E" ]" c! B9 r+ I
收藏 评论0 发布时间:2024-5-29 19:53

举报

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