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

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

[复制链接]
攻城狮Melo 发布时间:2024-5-29 19:53
一个关于STM32串口的问题。他想实现接收无限制字符数量的串口信息。$ H9 j, {8 A0 V% M+ M/ @8 i
' z5 Y. k8 i5 W7 _7 t
但是他遇到了一个问题,他在主函数中发送循环发送内容(500ms的延时)串口一中断回调函数中如果收到了串口内容,就利用串口发送。
$ I0 L8 Y# Y+ q
+ D) H# ]% p) }6 O& O( ]+ a  N: `, }
但是他的串口一旦接收到了信息就会导致主循环中的串口发送极快。他的主代码如下9 V# s3 i% s6 Y& M1 g1 b& G
  1. unsigned char RecieveBuffer;
    $ E$ l9 O5 e  O/ g, h1 }6 e
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)8 L) D2 E' b- m- s' j+ l9 \" K& X
  3. {+ M) r. \; R6 P4 F
  4.   UNUSED(huart);* h  @$ U+ i2 A5 q7 T6 g) |
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去
    9 O2 k& g8 c2 X1 N
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t
    9 t: y; Z% i7 k: h7 K5 u
  7. }
复制代码

/ s  D! U9 ]0 D! V看了他的代码,就这么短短的7行代码,让我汗流浃背。
% [' a. `' B5 [" H& a. W6 |9 D4 s1 G( Y
他的发送和接收中有一个参数是8,这意味着当串口接收的寄存器中有8个数据时就会触发中断回调。
( p9 A; Y# |8 E9 c1 p% w6 A( e* M0 ?2 f
我们需要注意的是,串口寄存器的数据会被存储到缓存区中,这里的缓存区相当于一个队列先进先出,当我们调用HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8);+ X3 H2 ~" Z, ]' c

) D- u4 }; S5 b! k# ^4 Q& q
会将RecieveBuffer作为缓存区,将串口接收的信息存入缓存区。, U" L6 H0 `( O8 A* c
7 C  F- a% v7 B4 p
那么让我们来试一下这段代码。9 ?+ j" d; |/ H  v# M5 L% d

. c: e- ?1 H0 v6 b, U( X
微信图片_20240529195314.png ( s) L8 s6 T3 F8 }5 M5 [( t
6 J% k3 l/ c; `, m5 E  j
可以看到,代码死住了。其实不难理解,我们发送数据的时候,由于缓存区只有一个字符的空间,我们发送八个字符的时候会导致缓存区溢出。所以我朋友的代码可以“正常运行”也是一件非常奇怪的事情。4 o/ n, X% h, @( w" H4 S, m/ l4 k; u
6 x3 F7 ]. o# ?" f/ B; f( f# ^9 X6 @
所以正确的做法应该是:设置和缓存区一样长的读取字符。
1 p6 U, A4 H* G9 B! L3 X' n* [$ Z5 V! U% @" }: ^
  1. unsigned char RecieveBuffer[8];1 y$ F- _4 n7 `% s$ P+ y( T
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    " v2 b) T6 m6 j0 X" E2 f4 ^
  3. {
    ) E  t  D- F. g9 K2 ?$ Y. q, T
  4.   UNUSED(huart);
    - R' S* s( `. ?  e8 s8 r
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去$ G  n* V2 y% |
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t
    0 \& R# h$ t2 N& s. `# r3 g: w! h. T
  7. }
复制代码

! N5 G) ^  c  V/ _: c/ d这样子就可以实现每接收8个字符就调用中断一次
' b' {1 H5 i  d5 w# t- y0 D* x) b9 N  f, p1 d" p5 i& A
微信图片_20240529195311.png . T) z6 |, |+ p, N* a1 A

$ y2 J6 r5 n6 _, K可以看到,我们正常的发送了8个字符。- p0 ^3 j5 g! e1 ]
& x8 V9 O: b9 B; @4 U7 c( o# N- @
但是这样子有一个非常非常致命的缺点!1 e+ b$ L7 l+ |! j0 u
0 S9 N2 K4 h2 i" Z8 H7 O2 Z
由于我们的代码每8个才会接收一次,所以当我们的发送的数量小于8时,必须发送多次才能会触发一次调用。% M# L% D9 D$ Y( E( {2 j

( b$ x1 W# t  \1 ?' X# p' l' s注意,这里的每次我们都是先发送再接收,因为初始化的时候是启用了接收。
% A/ t0 a* `8 E$ g' E
% @% C0 y8 i3 m& o
微信图片_20240529195307.png 4 R, |. v/ g/ o2 l) f" Q; |
7 `$ }- O! ?! I* O! V* e) y
当我们发送3次“123”时才能让接收的字符>=8,我们把前八个发送了出去,此时缓存区剩下了一个字符是“3”  d! @1 b; P+ V. L  u
6 g+ y+ }" w8 ^, X5 `. w- D6 V
微信图片_20240529195305.png
8 \; ?; q) R; l% Q2 v

  [% j7 F( U/ a+ e/ p& R7 \第二次我们发送了三次“123123123”,再次让字符数量>=8,这时候把缓存区的八个“31231231”发送出去再接收后面的“23”,这时候缓存区的数据为:"23 "。2 ^# c0 J" O& X7 F3 E$ }" U

+ @  |5 j% L4 }9 `# p' _5 J( Z) u
微信图片_20240529195302.png
5 h9 e* J! P) m) u5 ]

- E- W9 \' _2 E, R; m) J最后我们第三次发送“123”的时候,由于23 + 123 + 123 这时候,虽然这里按理来说正好八个,但是实际测下来,当这时候接收的时候系统就会卡死。, _! y3 I* K7 K/ t
1 O7 C% z+ j" S4 l: ^5 q
所以,实际上这种方法必须保证发送端的数据是完完整整的八个八个发送,多一个少一个都不行。
* O$ D1 w/ b$ R# X( G, C
3 L0 E6 \8 Q0 E: `: @) B
5 o0 p7 K0 z2 |% X1 j# K% ^9 G
解法
! H1 h3 e' N- Z- u7 ~; ~6 O事实上最标准的做法是单个字符单个字符处理。定义一个大的缓存区,用来存储接收到的字符并且确立一个结束符来确定数据流的结束。
  1. if((USART_RX_STA&0x8000)==0)//接收未完成) }9 I( P8 U& P
  2. {
    - ]& U# f1 l  x
  3.   if(USART_RX_STA&0x4000)//接收到了0x0d  |& m# M9 H5 [: c8 i# D
  4.   {& s1 g0 I5 T# e4 m
  5.     if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
    4 u6 v5 e1 Q: k& V# r0 e6 Q
  6.     else USART_RX_STA|=0x8000;  //接收完成了
      u" P+ S4 d1 Q1 j: T, ]6 g
  7.   }
    # @1 a& M& Q' q8 x8 \: d
  8.   else //还没收到0X0D7 O# ]' Z( t. _! l7 b5 q& [
  9.   {  . V+ q& ]2 t, }9 R# U* v. H
  10.     if(Res==0x0d)USART_RX_STA|=0x4000;
    * \' c' `. o% M/ O+ o
  11.     else; d3 L+ B- v" u5 y. R
  12.     {% n# Z  C2 G5 X- O
  13.       USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
    ; {, F( \7 I% `
  14.       USART_RX_STA++;
    $ G8 R# Q% p, l" k4 j6 h
  15.       if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收   
    8 e; W9 i3 e' M; Z
  16.     }     
    # `- t- f5 l1 Q0 `% w% X2 Z  `
  17.   }
    $ r9 s. H6 ~; [* J; p1 S) Z& z+ L5 j
  18. }
复制代码
  _+ ^$ x: Z+ d" ~5 K

9 v6 E% _4 o# {+ A% Q. _
( m. z: @2 W  G9 M; x! K, Y) X
上述是正点原子的官方例程,其中USART_RX_STA是接收到的字符,即是确定一个结束符\r\n来作为一串字符的结尾并且检测长度是否超出长度。
% R2 l) W" ?# B! k- p- ^/ S- \# b
. m  B$ `% \4 L: v: A0 {8 f
这样子的代码容错率非常高,也确定了一个规范,并且避免了缓存区溢出的情况。
8 Q9 P7 e/ F4 U1 t- q% s  ?4 q
% l" D0 \" |( t& U9 Q
0 f/ d8 o( E# V" v2 i2 X9 v1 N转载自:电路小白3 C- q  I/ J" c; B8 \
如有侵权请联系删除- f  Y8 N6 j# |" N+ T
$ u+ u  q) D2 Z- X( J5 Z8 a6 k
赞 收藏 评论0 发布时间:2024-5-29 19:53

举报

0个回答

所属标签

相似技术帖

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