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

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

[复制链接]
攻城狮Melo 发布时间:2024-5-29 19:53
一个关于STM32串口的问题。他想实现接收无限制字符数量的串口信息。
- w- z8 M, L& g+ [9 t, }1 x3 }
但是他遇到了一个问题,他在主函数中发送循环发送内容(500ms的延时)串口一中断回调函数中如果收到了串口内容,就利用串口发送。) ^6 P2 Q0 }% h  z

8 W& t8 }) G# p! K$ r( r
但是他的串口一旦接收到了信息就会导致主循环中的串口发送极快。他的主代码如下, p7 s1 u8 v8 K
  1. unsigned char RecieveBuffer;
    0 |- j% `  L# m7 F; f
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    # E, D& A* e8 K2 |: Y( T; k  ?
  3. {
    ; h6 ]7 [% C2 L- a: \
  4.   UNUSED(huart);
    ' y% D# V, Q- V3 a1 k: F8 p9 u
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去6 ^% |( U; Q" f
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t+ J# }# A" i# I: \5 r$ c2 Z! _
  7. }
复制代码
1 i7 [1 d. P4 W- g, ]
看了他的代码,就这么短短的7行代码,让我汗流浃背。
& `* h! \% ]$ s: ?
. S1 t0 j! o- u2 ?2 C2 |% B
他的发送和接收中有一个参数是8,这意味着当串口接收的寄存器中有8个数据时就会触发中断回调。
6 v$ N% e( m+ y) ~9 |) }* L( r) L" f
我们需要注意的是,串口寄存器的数据会被存储到缓存区中,这里的缓存区相当于一个队列先进先出,当我们调用HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8);$ h% o- k  r- i8 T9 v+ h$ U
: _* P: s1 ]: ?) _) Q& D. y
会将RecieveBuffer作为缓存区,将串口接收的信息存入缓存区。
. E, ?8 Y7 {# D% ^- ^7 Y" |6 s0 p( I: d
那么让我们来试一下这段代码。
% E  @2 m  f5 s0 Z- o! t! h+ d% z7 h) ~0 `) y: [  y
微信图片_20240529195314.png 2 E+ l4 S, t' j! X6 \
: l7 u4 e* x, Q0 H) E3 i& Y
可以看到,代码死住了。其实不难理解,我们发送数据的时候,由于缓存区只有一个字符的空间,我们发送八个字符的时候会导致缓存区溢出。所以我朋友的代码可以“正常运行”也是一件非常奇怪的事情。# ^# ^9 k2 D0 B: X. x+ `) D1 Q
! w4 W( g4 E; `/ T1 o' H. B
所以正确的做法应该是:设置和缓存区一样长的读取字符。0 o4 s/ u  g% ?. ?5 S* `
1 ^1 v) e; p2 b. H+ O) z9 l
  1. unsigned char RecieveBuffer[8];
    - q' X+ ^( S1 l
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)& z0 c8 j/ _2 j& c9 P
  3. {
    + g8 i$ A0 f$ a2 T1 Q# @6 U
  4.   UNUSED(huart);
    * r* i" T- s5 @9 M
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去8 ?7 i( ^" N8 [2 E0 m! w1 _! ]
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t3 [- B' _" }# y; `/ i4 F. M+ Z% q
  7. }
复制代码

9 T7 L) X8 V' P9 P这样子就可以实现每接收8个字符就调用中断一次6 ^2 y1 l  |) L, m' U5 k, Y$ E2 P

5 j) ]# S+ c3 b% G0 M
微信图片_20240529195311.png ! c7 Z/ g9 Z9 m
- J, X4 z( n1 v- X( c( S2 y* ^
可以看到,我们正常的发送了8个字符。
2 Q, P2 y5 a& |+ o, `; e2 C. m0 U4 s
+ z( S( q/ F: u. F但是这样子有一个非常非常致命的缺点!6 r9 C& d! }. i4 \* m- t( G) {7 y1 E
+ y9 ~7 {: M8 a8 H
由于我们的代码每8个才会接收一次,所以当我们的发送的数量小于8时,必须发送多次才能会触发一次调用。
' H5 n) |9 k) g# i: V, `& V- D
  v' d4 Y0 M( b& R注意,这里的每次我们都是先发送再接收,因为初始化的时候是启用了接收。
# e+ [; E( d, s1 B! T, @4 W. M  _6 X& D! S3 ^' [7 p" b
微信图片_20240529195307.png
# `9 q+ Z7 H$ N4 X
4 }! z& G% N$ {; ]
当我们发送3次“123”时才能让接收的字符>=8,我们把前八个发送了出去,此时缓存区剩下了一个字符是“3”( |( h6 S" Z# C2 Q( [* p8 ~) Y
6 N( Y0 V, t6 R$ D' @1 Y4 `6 K- ~; m
微信图片_20240529195305.png + C) G2 ^( L: u5 m) |
* `' j; r0 Z8 @/ C
第二次我们发送了三次“123123123”,再次让字符数量>=8,这时候把缓存区的八个“31231231”发送出去再接收后面的“23”,这时候缓存区的数据为:"23 "。
/ g( ~' Y% t+ T/ Z5 T3 W+ \* U' E+ v, {' p$ ]8 l2 M
微信图片_20240529195302.png ) Y% k. ^; p# U" S- _( {$ H

: R% i1 Y' o1 g1 L% m- X+ \' B7 Z最后我们第三次发送“123”的时候,由于23 + 123 + 123 这时候,虽然这里按理来说正好八个,但是实际测下来,当这时候接收的时候系统就会卡死。
, `7 o/ G! e6 ]" }* s) D& l3 Y$ q0 t9 T
所以,实际上这种方法必须保证发送端的数据是完完整整的八个八个发送,多一个少一个都不行。1 U8 f  Q  J- R

  l& M3 Z; E& s; K. u! y' j' d( e

9 a2 Q2 p" o/ I" q5 H解法; f8 ]) V7 k3 f) F& I
事实上最标准的做法是单个字符单个字符处理。定义一个大的缓存区,用来存储接收到的字符并且确立一个结束符来确定数据流的结束。
  1. if((USART_RX_STA&0x8000)==0)//接收未完成' k* ?! @+ i1 t) w. n
  2. {2 \; ]2 o2 [+ B: ~3 t2 d  j
  3.   if(USART_RX_STA&0x4000)//接收到了0x0d
    ; I6 c9 u. ~! x
  4.   {  q/ p9 v7 H) t( T# d4 M, j/ L
  5.     if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
    # ?7 b+ z, M+ w( d" M
  6.     else USART_RX_STA|=0x8000;  //接收完成了
    3 P# S" S3 T* `+ q" O' K
  7.   }! x7 \0 F: V; J" L1 Z  i6 S
  8.   else //还没收到0X0D
    ( b" P. _9 a0 V9 W$ O- W) R1 G
  9.   {  
    + ]$ d" z8 J2 Q5 x
  10.     if(Res==0x0d)USART_RX_STA|=0x4000;2 p. F7 s+ e3 ]) j  c! r
  11.     else
    4 g4 @! u& _4 j# C) ]# p" }
  12.     {* r5 d# T- A( e# t- r. V9 ]+ b
  13.       USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;6 x2 d1 n+ U- v: X- l6 v5 t+ E7 O
  14.       USART_RX_STA++;
    * G) Q6 e1 V( |6 n% l! u1 Y
  15.       if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收    ( c2 t9 ~8 A! c0 _* }
  16.     }     
    - H0 K  `. O9 F8 X, d6 \0 ^, S
  17.   }
    & f- }5 r, g4 L9 t' v0 y% Z  E
  18. }
复制代码
. \; l; o4 m& @  [2 `. x

# q6 H: M' b% R7 p5 Q* k
. e: g, ^) F  G1 r, M$ ^& A
上述是正点原子的官方例程,其中USART_RX_STA是接收到的字符,即是确定一个结束符\r\n来作为一串字符的结尾并且检测长度是否超出长度。
' z$ U+ U$ i  i) S* `! G: }! ^
' F: e4 e* A# h) V( P
这样子的代码容错率非常高,也确定了一个规范,并且避免了缓存区溢出的情况。
( U3 e% P5 M" L2 L+ V& J6 T5 B  x: H

  [- b* b1 M  x) H) ~) z转载自:电路小白% D% ~% F: k& O7 z, b; ^3 X
如有侵权请联系删除* [  I2 ^( S+ E* |' }6 T, s5 \9 _, N
; K1 V, _! p* {, p* [% X6 Y
赞 收藏 评论0 发布时间:2024-5-29 19:53

举报

0个回答

所属标签

相似技术帖

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