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

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

[复制链接]
攻城狮Melo 发布时间:2024-5-29 19:53
一个关于STM32串口的问题。他想实现接收无限制字符数量的串口信息。
' |7 E4 R3 w9 X' I! i/ r( C/ H
4 L4 m, L) H# I' H; m
但是他遇到了一个问题,他在主函数中发送循环发送内容(500ms的延时)串口一中断回调函数中如果收到了串口内容,就利用串口发送。
8 T+ z2 E9 C5 J' K9 w
' J& C$ s/ T7 Q7 V
但是他的串口一旦接收到了信息就会导致主循环中的串口发送极快。他的主代码如下
7 B+ w# B  F% Z9 ~, Z- j
  1. unsigned char RecieveBuffer;: y- ~+ F4 s" v; K) S, L
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    5 q/ J4 _  D9 w: @
  3. {! ?9 Q8 m- p" }5 i, O" @
  4.   UNUSED(huart);
    ! }& B6 r0 _/ e$ |
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去# J6 g: ?( F- Z: ?# x# R6 x6 h! _
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t
    * T! E* w- O. C& K. b; F: Y
  7. }
复制代码
: N8 [, ^0 e- ~7 t8 s
看了他的代码,就这么短短的7行代码,让我汗流浃背。
$ X7 H1 u( W4 \. c! U# U/ a% R3 n' c' a# T
- }/ T! Z5 ~2 n. V$ _
他的发送和接收中有一个参数是8,这意味着当串口接收的寄存器中有8个数据时就会触发中断回调。
2 ]7 B; n, x4 T& h" s1 J8 w- e/ B* {/ [
我们需要注意的是,串口寄存器的数据会被存储到缓存区中,这里的缓存区相当于一个队列先进先出,当我们调用HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8);) {2 n9 b8 {4 Z8 b

5 L& X. k+ c$ P, m
会将RecieveBuffer作为缓存区,将串口接收的信息存入缓存区。3 o% v( J. Y, ~5 o: d9 X

! @2 \" V" p7 b- ~
那么让我们来试一下这段代码。  f7 U, J3 e# n
, B" r1 H- H2 s# ]$ Z* T( L0 N4 B
微信图片_20240529195314.png 6 l# h" n2 p4 M& j4 ]7 W
. ?1 ^! N5 W+ {% B8 Q0 v: W
可以看到,代码死住了。其实不难理解,我们发送数据的时候,由于缓存区只有一个字符的空间,我们发送八个字符的时候会导致缓存区溢出。所以我朋友的代码可以“正常运行”也是一件非常奇怪的事情。5 B$ \& R. `& _# W0 f5 T
0 C1 y- s( b0 ?% x" j) W
所以正确的做法应该是:设置和缓存区一样长的读取字符。
, f( G" Q, Y; \9 X  }; r* Z0 m2 C' N( T! g
  1. unsigned char RecieveBuffer[8];
    2 ]  h$ M& D4 t/ `) T* g4 z' A: y
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    : Y6 P1 n3 B. S2 d* V7 r; N
  3. {2 l1 ^- A' f9 k2 P( r
  4.   UNUSED(huart);
    ; N! K& R/ ^% R! }" U' @, n
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去
    1 ~# m7 @7 x  x( s! F
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t
    5 H+ p4 a4 l6 h& r7 f! H( [+ `
  7. }
复制代码

$ c5 t( d; c& f; w2 i' _这样子就可以实现每接收8个字符就调用中断一次
% \& G$ [: \, c! }  }. w% m  P* J  W  l6 U9 W  }
微信图片_20240529195311.png 7 u) }7 P* `% `! s( g! c) h
+ g- @! Q9 ]; [4 a( _
可以看到,我们正常的发送了8个字符。7 v1 ^( H( ?; |# ]
' J" z) Z2 C) L7 S
但是这样子有一个非常非常致命的缺点!
. |2 p& x& I2 O/ r, N/ t  f& z% T4 |* G+ X, @
由于我们的代码每8个才会接收一次,所以当我们的发送的数量小于8时,必须发送多次才能会触发一次调用。
3 F: u8 |& _- ]  q" H" R
1 G/ H( u* H7 o2 f# D注意,这里的每次我们都是先发送再接收,因为初始化的时候是启用了接收。
- Y; M. U' x" K8 u" D0 j
4 Q' r/ m3 M3 |3 p5 L/ T7 m
微信图片_20240529195307.png 8 T9 F9 J0 b* {' x* B6 t

, w- w, ?2 x0 m6 K4 A5 M当我们发送3次“123”时才能让接收的字符>=8,我们把前八个发送了出去,此时缓存区剩下了一个字符是“3”
" w5 ]) \  p# J0 v: v. \0 a+ r1 l0 `$ Y  i1 p9 r3 ], n$ P8 P
微信图片_20240529195305.png
4 a0 L5 }( x- R4 E4 Y

8 p# {! T* K8 ~" F第二次我们发送了三次“123123123”,再次让字符数量>=8,这时候把缓存区的八个“31231231”发送出去再接收后面的“23”,这时候缓存区的数据为:"23 "。
2 ?# e4 Y9 P9 Q- }* j* E: H7 _; v
5 m, R9 L% x4 r: w1 R' S" z/ F
微信图片_20240529195302.png 0 j4 j+ v! K0 X. N" @  a
1 {6 |3 D/ R, g: f$ T) O) v
最后我们第三次发送“123”的时候,由于23 + 123 + 123 这时候,虽然这里按理来说正好八个,但是实际测下来,当这时候接收的时候系统就会卡死。
6 Y8 L# h5 r( O; H; ]  P$ a2 ~& A2 ]1 o# h% I; X/ ^0 [0 }
所以,实际上这种方法必须保证发送端的数据是完完整整的八个八个发送,多一个少一个都不行。
5 n7 c6 G6 x! l1 o! r8 t
- }4 S: r3 l$ n) R/ \( ]0 B
' Z. d$ m4 l8 @# N) h; e
解法
! x' f/ V  v  O$ h4 y7 s( I. D- I事实上最标准的做法是单个字符单个字符处理。定义一个大的缓存区,用来存储接收到的字符并且确立一个结束符来确定数据流的结束。
  1. if((USART_RX_STA&0x8000)==0)//接收未完成
    $ ?! v; D' H% D$ D9 @8 X& P; w
  2. {0 P! u4 t4 v7 d: f! R. _
  3.   if(USART_RX_STA&0x4000)//接收到了0x0d. i! g" w: p5 G8 {/ q; C- C
  4.   {
    # |6 {( B# d. R1 E3 b* H# I
  5.     if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
    % @4 @9 @& f, d3 ?3 e5 g( _
  6.     else USART_RX_STA|=0x8000;  //接收完成了
    " `5 n/ H% q- i8 \
  7.   }( b8 _( |0 b% X- Y
  8.   else //还没收到0X0D& e  d3 `1 m$ c. c+ N
  9.   {  
    ' o7 _9 t/ h" w/ s
  10.     if(Res==0x0d)USART_RX_STA|=0x4000;% g" ^2 O1 j% t6 i/ {/ g9 E
  11.     else( Q0 ^8 L; x* G# q, x7 n% O
  12.     {" n$ c: W' L. \; e
  13.       USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;$ [" z1 T0 ?) ?+ g) F
  14.       USART_RX_STA++;4 c2 b# g$ X. p6 j
  15.       if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收    ; Z$ ]$ J) n6 w2 a% m& n  C" e1 X
  16.     }     
    $ A9 }" u9 o3 P7 ?5 x! D
  17.   }+ E$ D3 }1 D: ]6 S
  18. }
复制代码
5 s1 D' S" w' O& }

9 Z  J8 S; \, j& T
8 ^& t4 v) N" X* \- Y2 c) u
上述是正点原子的官方例程,其中USART_RX_STA是接收到的字符,即是确定一个结束符\r\n来作为一串字符的结尾并且检测长度是否超出长度。
, Z6 }% \1 ?2 I9 [5 O
& G5 y7 z4 s; T( N5 ^) w2 ~- k
这样子的代码容错率非常高,也确定了一个规范,并且避免了缓存区溢出的情况。4 b3 b  b& ]$ Z

0 I$ ^8 t# l/ A* K, @6 Q: k9 C  I3 }% \- {: O2 J" O/ l
转载自:电路小白
5 J0 N. H5 @0 b6 a如有侵权请联系删除
$ ~# K: e# C4 W: j
9 ^: r/ V$ Y. w- C
收藏 评论0 发布时间:2024-5-29 19:53

举报

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