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

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

[复制链接]
攻城狮Melo 发布时间:2024-5-29 19:53
一个关于STM32串口的问题。他想实现接收无限制字符数量的串口信息。$ V. R& D( @+ ~5 i0 W
" U( E4 u! m& _5 Z, L
但是他遇到了一个问题,他在主函数中发送循环发送内容(500ms的延时)串口一中断回调函数中如果收到了串口内容,就利用串口发送。( P7 \: m8 r. C$ f2 w! O. t8 Z

4 l9 K1 |$ k7 {5 n9 m
但是他的串口一旦接收到了信息就会导致主循环中的串口发送极快。他的主代码如下# @/ X( s0 L  P+ y( g% g  }0 `* p
  1. unsigned char RecieveBuffer;
    : v* Q8 h; a" Q- J
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)) [& {0 ^. F1 P' g" B
  3. {) @+ g! ~+ J6 F6 J
  4.   UNUSED(huart);
    3 B0 M7 _- k+ G: m, @" L3 w
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去
    % h4 }  v: S; y; v/ O, X
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t
    1 v4 l% H" l# B# \: n5 J3 C! w1 B* o; A
  7. }
复制代码
$ C! ]# N" O$ H, _1 D2 N0 E! A5 j5 w
看了他的代码,就这么短短的7行代码,让我汗流浃背。1 O, x" U0 v4 e9 F, V1 V
, A) h. P$ \: O5 t  K! f
他的发送和接收中有一个参数是8,这意味着当串口接收的寄存器中有8个数据时就会触发中断回调。
7 w3 M9 f4 ]" s& C+ Q2 ^1 Q8 j4 C, ]- _6 p1 y) x6 B  _
我们需要注意的是,串口寄存器的数据会被存储到缓存区中,这里的缓存区相当于一个队列先进先出,当我们调用HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8);
4 I* ~% M+ n2 G8 B, [. `0 t5 Y8 q7 t( n: I% D. {9 J
会将RecieveBuffer作为缓存区,将串口接收的信息存入缓存区。) J5 o9 e6 w& @' Y# A4 E( w+ \: R3 q
- U& T2 c5 O$ s$ u( R, E# F5 l1 F
那么让我们来试一下这段代码。
4 l0 q8 j+ L6 B( w% @7 l; f4 G' {; W5 }
微信图片_20240529195314.png
" Z) p( u* A1 C# X6 S  ]
$ b; v3 J+ n& D7 M+ A- Y; {- W
可以看到,代码死住了。其实不难理解,我们发送数据的时候,由于缓存区只有一个字符的空间,我们发送八个字符的时候会导致缓存区溢出。所以我朋友的代码可以“正常运行”也是一件非常奇怪的事情。) J8 n/ F8 `/ K' p/ b
% t8 ]9 @# @- }% G5 ]# t
所以正确的做法应该是:设置和缓存区一样长的读取字符。( r$ d' ?3 s2 h& u9 a
( L! W4 \! ^4 l8 _" `
  1. unsigned char RecieveBuffer[8];4 h& a9 q/ }. b* R8 Q
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)0 |( `! U5 ^  |2 _* T! \0 w
  3. {
    . V5 B" V. g# M& ~: V
  4.   UNUSED(huart);2 d! e' w; @+ u/ t7 a) U, V
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去. I* N, h5 o; H) o  H3 J
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t; m7 H  X3 X  @1 I" G' R: ^
  7. }
复制代码
5 N! ?0 L3 e- W# S/ c6 z( s, O# G
这样子就可以实现每接收8个字符就调用中断一次4 C  V9 z* ?2 M  z0 w' {! u
+ v1 m& [7 p; k6 a* ?
微信图片_20240529195311.png
3 L9 s/ t0 ^: s1 X( b) c
- ?, r  ~' }# Q; W4 V# o/ |: O( X
可以看到,我们正常的发送了8个字符。
( e; V. C5 _0 L
% v3 B, Z- ~# _$ D; T6 g, {但是这样子有一个非常非常致命的缺点!% N8 B3 a2 z6 _; a1 k
% A6 O, U# E7 |: M
由于我们的代码每8个才会接收一次,所以当我们的发送的数量小于8时,必须发送多次才能会触发一次调用。
6 c; i" R& G5 J' E+ f( E& h2 A  Y+ i
: d, i  G+ H0 p5 n2 [3 q0 N注意,这里的每次我们都是先发送再接收,因为初始化的时候是启用了接收。
7 k( O5 s' P% r
# p8 G/ F# _: o! W! V# x& V
微信图片_20240529195307.png
/ u3 ?5 b- I: P3 ^+ O
. D( R; t! _# O( y4 Q$ l7 o2 M
当我们发送3次“123”时才能让接收的字符>=8,我们把前八个发送了出去,此时缓存区剩下了一个字符是“3”  L# f9 M+ M) I  V! x5 e

, Y- T6 Z% F8 R5 D" k' R
微信图片_20240529195305.png 5 S: ~0 X' M9 h
2 g- |( B1 `9 F6 q- R
第二次我们发送了三次“123123123”,再次让字符数量>=8,这时候把缓存区的八个“31231231”发送出去再接收后面的“23”,这时候缓存区的数据为:"23 "。# B! h! b: G2 g5 z7 o

( Z8 Y1 r4 M0 |
微信图片_20240529195302.png # E# |$ p9 r4 z$ ^9 r' g; U
6 I. P0 ?* y! g/ K0 s; T
最后我们第三次发送“123”的时候,由于23 + 123 + 123 这时候,虽然这里按理来说正好八个,但是实际测下来,当这时候接收的时候系统就会卡死。
7 h9 s7 Y5 `1 p# ]7 D9 ^$ |* @$ L! j; k+ o1 y+ _& G8 m, i+ L
所以,实际上这种方法必须保证发送端的数据是完完整整的八个八个发送,多一个少一个都不行。) Q1 t+ b! p5 O! q

" u5 \8 y, P6 M7 l- N& e

( z( i+ s8 c/ m$ c, n; V- ?解法
0 u: T" i- \! j  e; h- V事实上最标准的做法是单个字符单个字符处理。定义一个大的缓存区,用来存储接收到的字符并且确立一个结束符来确定数据流的结束。
  1. if((USART_RX_STA&0x8000)==0)//接收未完成* w, s' B% d) `* P
  2. {
    3 A% J, |1 Q0 n- V6 z, n
  3.   if(USART_RX_STA&0x4000)//接收到了0x0d' N) j  ]; _7 V
  4.   {
    & m7 ~0 j" x6 ]0 l9 U% x
  5.     if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始- W! ~5 ^- n# F& }$ p3 r
  6.     else USART_RX_STA|=0x8000;  //接收完成了 ( C) a$ v2 M1 j! _
  7.   }
    " j6 \. A$ t/ f; u
  8.   else //还没收到0X0D( f- ?  Y. I/ ?- t! v
  9.   {  * F4 J$ [* B" F
  10.     if(Res==0x0d)USART_RX_STA|=0x4000;
    ; B, g+ z3 m7 Z& K' b$ \+ ?
  11.     else
    ( Q# ^  }1 s/ U+ `% o5 l5 \# k9 N3 v
  12.     {
    5 C1 V" u0 L; a/ b4 v6 Q) D
  13.       USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
    4 `/ U* U0 b+ J1 Q* k+ G# i
  14.       USART_RX_STA++;9 }3 y$ L% L7 S# K
  15.       if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收   
    & q5 t* B/ L  V3 u" H" ?3 U
  16.     }     ; [: N# y: w7 P
  17.   }
    * P- V) q/ n7 c
  18. }
复制代码
* I! v/ h2 F- L4 W" m

# t, @. U% a0 u8 x1 q

+ m# y* l4 ^5 q: I- f" `上述是正点原子的官方例程,其中USART_RX_STA是接收到的字符,即是确定一个结束符\r\n来作为一串字符的结尾并且检测长度是否超出长度。
( _- t) h0 e; Y1 T

2 l- S% t; U' |3 _8 W/ D/ S这样子的代码容错率非常高,也确定了一个规范,并且避免了缓存区溢出的情况。
4 A' V$ e  `, i4 D- V7 ~
% b* R3 K6 n2 E
# @: v9 m5 x0 f' H转载自:电路小白9 o% e! a8 p. g# A7 A! f$ n$ ]
如有侵权请联系删除+ u- r7 Q. ]& }! a5 K: }
# q$ N5 p' v$ F. @7 }  T4 y
赞 收藏 评论0 发布时间:2024-5-29 19:53

举报

0个回答

所属标签

相似技术帖

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