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

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

[复制链接]
攻城狮Melo 发布时间:2024-5-29 19:53
一个关于STM32串口的问题。他想实现接收无限制字符数量的串口信息。
6 q! g, Z! F- }/ u" a, @# }  d
: s" X9 J0 ^& C! w
但是他遇到了一个问题,他在主函数中发送循环发送内容(500ms的延时)串口一中断回调函数中如果收到了串口内容,就利用串口发送。; z& M8 i9 _- I0 O

& u: D1 w" F* y
但是他的串口一旦接收到了信息就会导致主循环中的串口发送极快。他的主代码如下7 m. h# y: H% p* e  g
  1. unsigned char RecieveBuffer;
    % n9 H& H8 v" }  b* f) Q
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart). a1 V& G' z/ S4 u6 p5 A* ?( O
  3. {0 s, ^" v  ~  d; `+ k0 z4 c
  4.   UNUSED(huart);" \/ h% j6 |! W
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去
    / j. l2 e1 e. D3 W0 c
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t
    * ?3 E+ ?6 H6 k% C" j0 D. o* g" j9 I- o: d
  7. }
复制代码
. R# B3 a0 g" }" T
看了他的代码,就这么短短的7行代码,让我汗流浃背。  c6 G  L5 [4 m1 {( U6 l1 @7 f! b
# N4 k: F# a; J# w9 {9 j3 H
他的发送和接收中有一个参数是8,这意味着当串口接收的寄存器中有8个数据时就会触发中断回调。% {- N: d0 I/ z" d( v4 S

4 @  [9 g' V) x' N$ S
我们需要注意的是,串口寄存器的数据会被存储到缓存区中,这里的缓存区相当于一个队列先进先出,当我们调用HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8);
9 F- \6 }9 H7 A" v8 n: }1 w1 ~4 S- ^. r& H5 V
会将RecieveBuffer作为缓存区,将串口接收的信息存入缓存区。
% I: @- Q! U# g7 y, _, V
# v  I  J* }) m: x  _
那么让我们来试一下这段代码。
' E( j/ l3 x" ~" @8 `
3 x8 x& j5 ~0 J7 f) i9 u
微信图片_20240529195314.png
: M: v8 p+ ]7 T" }  |' v& r
1 {- C) _2 \( f+ s+ b* P9 q
可以看到,代码死住了。其实不难理解,我们发送数据的时候,由于缓存区只有一个字符的空间,我们发送八个字符的时候会导致缓存区溢出。所以我朋友的代码可以“正常运行”也是一件非常奇怪的事情。
# E% Z* U- z7 n$ h6 L9 |7 Y# b, `) i% T! n" J. ^. {
所以正确的做法应该是:设置和缓存区一样长的读取字符。
' `" K* Q, ]; k( n. D$ }
3 T& }% v8 N6 g4 @( l2 z% O
  1. unsigned char RecieveBuffer[8];: ]  z0 B( T* e, c- f. y
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)& U( B9 z& M+ s- M# O2 v1 s! n" o
  3. {; S8 E8 A  u" @; a4 Z0 G9 _4 O
  4.   UNUSED(huart);; V5 M3 S% W* G4 {) `% D
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去; q$ f7 t( V" m3 t' |4 e# Y
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t5 i1 S; t! k; ^* b: A# ^' \
  7. }
复制代码
6 c# f- a( f8 g- s/ K( X
这样子就可以实现每接收8个字符就调用中断一次
" k0 I1 }6 Z& v% z) Y
, ]; ]6 Y1 h! {- }/ e
微信图片_20240529195311.png
( H; V0 \" |. E+ f; t- r
# ?; \7 T' @4 j( U3 T1 p( s
可以看到,我们正常的发送了8个字符。4 }$ I5 c* B5 Q3 e: }
9 H; i6 @3 E5 I& f
但是这样子有一个非常非常致命的缺点!
1 {$ ]! F( P/ ]" s  {( n- X
5 x, h0 p& G: p由于我们的代码每8个才会接收一次,所以当我们的发送的数量小于8时,必须发送多次才能会触发一次调用。( K* @. t3 E. \

& J" X; _# N, d1 [: [3 y: W6 u注意,这里的每次我们都是先发送再接收,因为初始化的时候是启用了接收。$ W. E: }) i3 v5 p! K1 X

8 Z* d& u* f$ U  q" h- F& H* p
微信图片_20240529195307.png 9 r3 ^- g! K& {; v3 j
/ O- n4 H) G; \2 H" H$ V9 O
当我们发送3次“123”时才能让接收的字符>=8,我们把前八个发送了出去,此时缓存区剩下了一个字符是“3”9 \% `( Q3 H( e' F/ a/ H# t8 E

! z+ Y8 L- R5 }7 R7 J
微信图片_20240529195305.png
6 I3 P% L" ?( |+ C2 _. G  K% @

0 I2 ?! W! J$ z! G" n7 r' F6 @! X第二次我们发送了三次“123123123”,再次让字符数量>=8,这时候把缓存区的八个“31231231”发送出去再接收后面的“23”,这时候缓存区的数据为:"23 "。
, n$ f. p; j) r/ E" f: [+ O* ^, Q* B8 W" w4 a6 S
微信图片_20240529195302.png 6 J3 s& t1 L" F; e: g
! F: f: d1 U5 S: O4 V! i
最后我们第三次发送“123”的时候,由于23 + 123 + 123 这时候,虽然这里按理来说正好八个,但是实际测下来,当这时候接收的时候系统就会卡死。# R! n; n" a( {4 b3 L. |! @
$ O5 G" ^. f& @2 S. |* y% B" v2 D
所以,实际上这种方法必须保证发送端的数据是完完整整的八个八个发送,多一个少一个都不行。
( ]$ x* k% z# X$ v: U  M
: r& O6 ~5 ^* o+ l8 A

& S- _0 h! d1 @, |  b解法: X4 ^' d2 A3 F" w6 W
事实上最标准的做法是单个字符单个字符处理。定义一个大的缓存区,用来存储接收到的字符并且确立一个结束符来确定数据流的结束。
  1. if((USART_RX_STA&0x8000)==0)//接收未完成( F( R3 C; }* Y& n7 h
  2. {
    * k$ X3 i4 l0 w+ R' F
  3.   if(USART_RX_STA&0x4000)//接收到了0x0d6 t8 M6 K9 j8 v5 d  Z2 V8 j! O9 o
  4.   {1 E2 j; u" K! i& S  `. b
  5.     if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始0 q# {& D! _) U0 @1 l1 o
  6.     else USART_RX_STA|=0x8000;  //接收完成了 : n2 w5 V. U5 x$ }
  7.   }
    2 n+ e( R- e5 c* Y) K
  8.   else //还没收到0X0D5 O- w6 y0 y3 j" V: @! @% a, ~
  9.   {  
    $ K9 R& u4 Q, Q* _4 E. }8 I1 ~
  10.     if(Res==0x0d)USART_RX_STA|=0x4000;
    * I4 w7 n! i/ [+ ^
  11.     else
    3 c. _. ?# S. v7 f' G/ @' |
  12.     {' ]0 h- y$ g. K3 j2 x* G3 p
  13.       USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
    * P) Z  v7 @2 W
  14.       USART_RX_STA++;
    - M" l) Q$ R9 Y5 D% p3 m1 O
  15.       if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收    - B# v5 `6 _7 U+ [
  16.     }     - N/ m; C5 |/ {+ }/ Z3 N/ C/ L  s
  17.   }: K2 I. m1 a: b- D
  18. }
复制代码

# u& ~' E  k1 i. k$ @4 d: y$ O1 g4 o
- a& f  a, ~2 c/ F5 I0 X8 L
上述是正点原子的官方例程,其中USART_RX_STA是接收到的字符,即是确定一个结束符\r\n来作为一串字符的结尾并且检测长度是否超出长度。
0 ~5 `) @* v" W

6 D  M/ K: C" q! \! E这样子的代码容错率非常高,也确定了一个规范,并且避免了缓存区溢出的情况。# o6 `; ?. S. N  F' S+ a

$ t  {4 |" x( l% c+ U2 k  t- r! k/ K" d- |2 Q- ]& h
转载自:电路小白
$ G& a8 K& ~3 T如有侵权请联系删除
; |% t' ]9 r' n2 y# @1 S4 L5 B+ s! t. \$ ^" w
收藏 评论0 发布时间:2024-5-29 19:53

举报

0个回答

所属标签

相似技术帖

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