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

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

[复制链接]
攻城狮Melo 发布时间:2024-5-29 19:53
一个关于STM32串口的问题。他想实现接收无限制字符数量的串口信息。8 s" {4 _; u% p1 q! q7 e
; j; H* ^! o. V+ b0 K+ b7 l
但是他遇到了一个问题,他在主函数中发送循环发送内容(500ms的延时)串口一中断回调函数中如果收到了串口内容,就利用串口发送。
1 i: O; y9 k7 y; l. {# R8 l. A" K
  Q5 ~& c' _) n- E5 L; O
但是他的串口一旦接收到了信息就会导致主循环中的串口发送极快。他的主代码如下
3 b6 B4 `1 M, Q& y, @, F; i
  1. unsigned char RecieveBuffer;
    $ A2 o, T3 q. n* ?3 _
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)) {# D  E- C/ q0 E3 d/ _8 b
  3. {" C* h+ ~8 l; s# t6 D
  4.   UNUSED(huart);
    ! m- \, ^! g. }
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去0 E/ v, _* j. W  e! B
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t
    0 q! q" \+ X' B  p8 V3 T. Q
  7. }
复制代码

( g* Y% z% D( y7 b4 {看了他的代码,就这么短短的7行代码,让我汗流浃背。
5 q9 j7 L5 _5 L- ]1 _2 Z/ N  h& [, U8 m$ ~* H( |1 U
他的发送和接收中有一个参数是8,这意味着当串口接收的寄存器中有8个数据时就会触发中断回调。
. H1 b4 p( p0 l) ~
4 d( V  |4 v0 O+ U7 V
我们需要注意的是,串口寄存器的数据会被存储到缓存区中,这里的缓存区相当于一个队列先进先出,当我们调用HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8);
% M1 A5 U4 r0 o- o6 `4 T/ L$ N) Q- ]/ u# {8 L2 V% I2 e
会将RecieveBuffer作为缓存区,将串口接收的信息存入缓存区。0 b. L( w. C% E, L  l/ J% P1 X7 v
6 X0 i* G4 x' ~6 [# V% |4 n
那么让我们来试一下这段代码。
$ d0 _, z# B! I2 o7 H  T9 H. s% [' `; o
微信图片_20240529195314.png
. P4 g% [0 u( ~' F

8 v% W6 S: ~9 h+ }- x: b可以看到,代码死住了。其实不难理解,我们发送数据的时候,由于缓存区只有一个字符的空间,我们发送八个字符的时候会导致缓存区溢出。所以我朋友的代码可以“正常运行”也是一件非常奇怪的事情。
" k- R  t. r+ \9 z, ?/ t9 S* b8 H6 Q. t( k7 Y( Y  d* V
所以正确的做法应该是:设置和缓存区一样长的读取字符。
( ]5 \# a" Y5 r0 h% P; E( O# N! s9 E
  1. unsigned char RecieveBuffer[8];# L! ?( _4 K4 ^+ A1 d4 s
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    $ W8 G9 ~6 h3 W4 I$ n. B4 y( v
  3. {( {* V6 A: ~1 I) \, x
  4.   UNUSED(huart);
    5 F* s% {1 U5 p7 s, J/ c6 _: }
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去
    " S2 X0 l" U- k, a5 {5 J* W- |% G/ J
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t
    ' a. U: I& I$ R! r* Q
  7. }
复制代码

/ E0 u5 Y7 J, ?( `) W. n8 @/ C# }这样子就可以实现每接收8个字符就调用中断一次' G6 V# T% F6 j7 u1 p
  M9 a1 }3 T# \9 |1 F
微信图片_20240529195311.png / k8 u! v0 v4 }8 S& a: O- {8 Q
' n2 J! }8 O% u# J
可以看到,我们正常的发送了8个字符。
1 J+ N0 k6 v/ z% x
; e  w- d6 _$ {3 i: h但是这样子有一个非常非常致命的缺点!$ \& c) G" F* V2 v; Z, T+ G
9 h: w0 H3 E4 I5 i6 C
由于我们的代码每8个才会接收一次,所以当我们的发送的数量小于8时,必须发送多次才能会触发一次调用。
/ ?1 ~5 ]. G! K/ T" [  T5 D4 P2 R
注意,这里的每次我们都是先发送再接收,因为初始化的时候是启用了接收。
- y; M/ _$ t0 j4 p! d% A$ G* w, J7 K1 z
微信图片_20240529195307.png $ d8 [$ Z, {/ M: y6 g
: n5 p6 e. [. p8 k% B2 ?3 u
当我们发送3次“123”时才能让接收的字符>=8,我们把前八个发送了出去,此时缓存区剩下了一个字符是“3”! G; d$ Z: X. F/ w/ o7 W) q0 l
$ V5 n. t8 i' M9 V2 C3 v
微信图片_20240529195305.png
0 A# e$ X% C5 `3 \( r( ?

8 S' i! y% k2 C/ {& e8 r第二次我们发送了三次“123123123”,再次让字符数量>=8,这时候把缓存区的八个“31231231”发送出去再接收后面的“23”,这时候缓存区的数据为:"23 "。% m9 u# {8 Y& j. W) z" B
5 N2 y/ z4 _5 ?! \" s+ K$ Q
微信图片_20240529195302.png ( K- T& q0 r$ |* R! V) `- Y- \& I: m
- m5 s2 `0 @1 S! b- R2 S
最后我们第三次发送“123”的时候,由于23 + 123 + 123 这时候,虽然这里按理来说正好八个,但是实际测下来,当这时候接收的时候系统就会卡死。
. V+ S4 R) U" B8 l$ R
# y* u" N& E, n" f+ Q' Z7 b所以,实际上这种方法必须保证发送端的数据是完完整整的八个八个发送,多一个少一个都不行。
$ _5 J5 l9 |6 E  C- {
* C0 D$ h4 S: O
( @- U$ L8 i8 P1 d. W
解法: V7 U2 p! k& t8 D
事实上最标准的做法是单个字符单个字符处理。定义一个大的缓存区,用来存储接收到的字符并且确立一个结束符来确定数据流的结束。
  1. if((USART_RX_STA&0x8000)==0)//接收未完成
    7 N& x. K( e; c9 r
  2. {
    7 A; a) E$ V* j' c$ `1 o: q# G
  3.   if(USART_RX_STA&0x4000)//接收到了0x0d
    , E7 v) G/ c7 J. [4 [& g$ L4 ^2 n
  4.   {
    ) v1 P% A- I( O  t! G0 K. @1 i$ o
  5.     if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始4 j: Y; G0 s- [( p- E& Q
  6.     else USART_RX_STA|=0x8000;  //接收完成了 ! F8 ^4 U9 M0 X$ d# P
  7.   }* n( ?: s! A2 a8 a: F
  8.   else //还没收到0X0D
    , C& n& a( E$ f1 t( D
  9.   {  # e4 f# {; Q, _4 R$ ]+ `! F0 P  n
  10.     if(Res==0x0d)USART_RX_STA|=0x4000;
    7 G7 b2 S0 S$ {, p
  11.     else
    1 U: @& a9 \  y& ]) p
  12.     {8 W2 S4 G+ ?* A' J1 D+ d( g
  13.       USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;( K/ b3 T3 @8 F( i
  14.       USART_RX_STA++;9 V4 U+ ^; @: E" T2 F% P- L
  15.       if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收   
    " t  @* P+ N, I/ R
  16.     }     
    0 Q3 E# }* a" d3 x
  17.   }
    % p. S/ S1 S+ J$ ]% |/ n
  18. }
复制代码

, T) i( A' u9 d3 V, x
: P3 a, z  \6 n% W! p" g6 T

" k1 t( D$ @) B上述是正点原子的官方例程,其中USART_RX_STA是接收到的字符,即是确定一个结束符\r\n来作为一串字符的结尾并且检测长度是否超出长度。
) W- {, g, I  Z) _
; A5 I. o3 w+ x+ j
这样子的代码容错率非常高,也确定了一个规范,并且避免了缓存区溢出的情况。$ N; z: N" `* C1 l  S: e
& u" N% n9 `' z0 c. ]- z7 ]
0 B: m( i, w1 y! u7 \
转载自:电路小白
: i& k' b4 p, s, M) G+ i8 ]* @$ u如有侵权请联系删除; Q2 g) z! M9 s0 W  s5 y( ?
9 @6 d( Q8 z2 [" ]1 g$ U
收藏 评论0 发布时间:2024-5-29 19:53

举报

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