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

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

[复制链接]
攻城狮Melo 发布时间:2024-5-29 19:53
一个关于STM32串口的问题。他想实现接收无限制字符数量的串口信息。, J4 q$ t6 a+ P* n( O
% ~. ]: ?  p9 g+ m  _
但是他遇到了一个问题,他在主函数中发送循环发送内容(500ms的延时)串口一中断回调函数中如果收到了串口内容,就利用串口发送。' w  g0 @, N6 T' [9 |  K* y* T
4 ]+ ]8 d" G$ A0 z/ c: V
但是他的串口一旦接收到了信息就会导致主循环中的串口发送极快。他的主代码如下
; j- j7 c2 Z& p3 l- h+ E  _
  1. unsigned char RecieveBuffer;
    - T6 m" b0 b2 ~  B* W
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)+ T  O, N+ X/ r" f3 C0 {2 |  u
  3. {- h1 i/ H$ J' [( W/ p
  4.   UNUSED(huart);+ O4 \" B3 ^& E1 Q
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去% `! Y# K; b/ p) R( `+ a' \  Q
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t
    % l7 w* B8 D: p: t2 g6 g$ q9 V) G5 a
  7. }
复制代码

$ S+ Z- g8 {4 _# c8 G& ^看了他的代码,就这么短短的7行代码,让我汗流浃背。
! o2 X# l6 Y, [+ b
; \' Y. q5 {! [; l' e' N. J
他的发送和接收中有一个参数是8,这意味着当串口接收的寄存器中有8个数据时就会触发中断回调。
  }; \5 V: ]* K# M  H7 b
. @$ {: t/ f( K) b
我们需要注意的是,串口寄存器的数据会被存储到缓存区中,这里的缓存区相当于一个队列先进先出,当我们调用HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8);
! R$ H1 b$ X, @! w; E* f0 a) I! z: {! u' @
会将RecieveBuffer作为缓存区,将串口接收的信息存入缓存区。! n. L2 A9 g0 }: A6 s% x
. j: g8 L" G, D  `2 `/ t
那么让我们来试一下这段代码。. A# T3 F/ t3 i, F% n; m+ c
) E$ P0 h* r1 `, w+ C
微信图片_20240529195314.png , }" ^" c; w$ a3 {4 m
. l4 ?- n: j; d4 ]2 y* v, U3 u
可以看到,代码死住了。其实不难理解,我们发送数据的时候,由于缓存区只有一个字符的空间,我们发送八个字符的时候会导致缓存区溢出。所以我朋友的代码可以“正常运行”也是一件非常奇怪的事情。7 n4 [, C' }' Q8 k

' Z% [( |- X# B1 n+ `- }所以正确的做法应该是:设置和缓存区一样长的读取字符。2 @) v! m' z( t) z8 D: M# F4 z7 B

5 k3 N8 T  V$ o4 a6 S! W& B; a2 X- ?
  1. unsigned char RecieveBuffer[8];
      i2 C, o4 d) a4 M, J
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    . d' T/ S1 n# J; G4 z8 F' o
  3. {' E. _  P: K  E' I3 x
  4.   UNUSED(huart);6 G& \, P4 \6 ^$ G0 R4 l
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去1 Y  V- [( N5 Z. T
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t
    7 v5 ^/ m5 d9 @: }) Z5 B
  7. }
复制代码
3 z/ Z2 b4 z/ @. W* Q" @( ~* l6 h& I
这样子就可以实现每接收8个字符就调用中断一次
7 J7 P! n. w/ l3 ~: }
  Z4 }, p9 d8 f: D  H3 a) F5 ~3 R
微信图片_20240529195311.png ; h9 l% J$ N3 Z% i; Y/ u( M1 u3 C

# Z5 l! z; Z1 s+ q; F可以看到,我们正常的发送了8个字符。
8 n9 f4 T3 M( u3 m% F- D% g: G) b. ^0 v! L
但是这样子有一个非常非常致命的缺点!0 z3 A6 k4 v; K' F
: O5 k! d+ h0 G' z
由于我们的代码每8个才会接收一次,所以当我们的发送的数量小于8时,必须发送多次才能会触发一次调用。6 D8 @% n2 c# m% _2 G/ U) l

3 ]7 Q- K" J0 x. I( E7 H注意,这里的每次我们都是先发送再接收,因为初始化的时候是启用了接收。* k/ S! ^) p, {- f# A! h( p; \
7 s5 F) V6 s9 L( n. k, Y) ]
微信图片_20240529195307.png - q( n! }+ s/ D1 w6 k
, ?- O4 f: z" S# ^" V3 f
当我们发送3次“123”时才能让接收的字符>=8,我们把前八个发送了出去,此时缓存区剩下了一个字符是“3”* ~* \; j* D3 ?- Z: H

. ^* {- u5 ^5 t. l4 i
微信图片_20240529195305.png
. P6 n2 k5 @! j! V( y) E" s
, T/ G" ^% }1 Q9 h4 k0 Y' Q
第二次我们发送了三次“123123123”,再次让字符数量>=8,这时候把缓存区的八个“31231231”发送出去再接收后面的“23”,这时候缓存区的数据为:"23 "。
! w; b- O: V; _, {. s/ U9 m$ n: w+ y4 }
微信图片_20240529195302.png + }! a3 H; U0 a/ m7 ]

: v; P1 o1 I5 U- v% z: M3 n最后我们第三次发送“123”的时候,由于23 + 123 + 123 这时候,虽然这里按理来说正好八个,但是实际测下来,当这时候接收的时候系统就会卡死。, y! }% A% X5 X! B# i
, Z$ n! }: O0 T% V& K/ i
所以,实际上这种方法必须保证发送端的数据是完完整整的八个八个发送,多一个少一个都不行。
1 j' ]3 P7 c/ v+ I5 w; F, |% J. D+ Q& }$ C
- @  S/ |# P4 M, b* }7 {# A
解法$ j1 X4 G# O) u$ ^! R, B
事实上最标准的做法是单个字符单个字符处理。定义一个大的缓存区,用来存储接收到的字符并且确立一个结束符来确定数据流的结束。
  1. if((USART_RX_STA&0x8000)==0)//接收未完成
    % @. p4 @0 \0 D; f3 U
  2. {' D* j1 ?" w9 n$ ^4 Y8 v* y
  3.   if(USART_RX_STA&0x4000)//接收到了0x0d; o0 o+ m, _. V4 c, |7 ~& f* ]
  4.   {) ], V0 ~0 f3 v) [, l# r
  5.     if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
    6 V" ]. j' Q# _) W# b
  6.     else USART_RX_STA|=0x8000;  //接收完成了
    ' }9 m" e) L  c$ z; \6 U8 Z
  7.   }
    / j: T" H5 T- Y* N( H7 }
  8.   else //还没收到0X0D
    ) B0 W1 Q' @; W- p* E8 v, J
  9.   {  % [0 l1 L' ~! x! G7 |$ v) @6 v
  10.     if(Res==0x0d)USART_RX_STA|=0x4000;
    + l% j  q9 q& x( b% o: W
  11.     else
    3 ?! B6 J) b0 p" }$ Y: n4 ~
  12.     {
    5 u, q7 w4 [& ~- S; c
  13.       USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;& X, o- x! c" m8 X) B; T
  14.       USART_RX_STA++;- f! X- l1 h+ z- @% Y
  15.       if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收    " h" t1 D5 r" O3 T
  16.     }     + y0 p5 _9 Y( l! |# t
  17.   }
    2 X0 z% ^0 S5 J& z
  18. }
复制代码
5 O$ l) K2 n  ?
. T3 b8 a( P0 K$ [6 j+ h$ \

4 z) w& w! H! g9 F上述是正点原子的官方例程,其中USART_RX_STA是接收到的字符,即是确定一个结束符\r\n来作为一串字符的结尾并且检测长度是否超出长度。

& |8 n7 U% }. a; ^6 r5 T) h( I5 l% R( X' O
这样子的代码容错率非常高,也确定了一个规范,并且避免了缓存区溢出的情况。6 V# g/ a& M! X
& k% ]1 V# r" `. ^) G" i

! ?  z6 }5 v& U, R  H$ m4 H: F转载自:电路小白: X; f+ I. J( }8 b
如有侵权请联系删除8 A5 Q1 E2 I9 w2 o" Q$ X, J7 N

- R& [% r9 K, G7 F
赞 收藏 评论0 发布时间:2024-5-29 19:53

举报

0个回答

所属标签

相似技术帖

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