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

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

[复制链接]
攻城狮Melo 发布时间:2024-5-29 19:53
一个关于STM32串口的问题。他想实现接收无限制字符数量的串口信息。# k: s" e/ g: o( A+ M+ O

$ F0 Z( y: e6 _; i7 I: R& a
但是他遇到了一个问题,他在主函数中发送循环发送内容(500ms的延时)串口一中断回调函数中如果收到了串口内容,就利用串口发送。2 q4 h8 x& c+ n- }- E* H

: Q9 K$ [% m% {; L
但是他的串口一旦接收到了信息就会导致主循环中的串口发送极快。他的主代码如下0 |% `) @- j! s2 O. N
  1. unsigned char RecieveBuffer;* m/ `. Z/ Y2 O* N) Y( y
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    9 w# o' ]$ F0 ^! |
  3. {% s, Q, G8 |8 {, R) J
  4.   UNUSED(huart);
    9 V: F8 B. I7 \' M
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去
    3 G+ [9 F/ Z1 q" S5 f: Z; }
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t/ Y/ c( p7 \9 Q4 g, P6 N
  7. }
复制代码
% J+ O9 O' k+ Q) g" N
看了他的代码,就这么短短的7行代码,让我汗流浃背。% c+ x& ?! u+ ]: S8 G. F
- l) `) C3 Y2 n8 V
他的发送和接收中有一个参数是8,这意味着当串口接收的寄存器中有8个数据时就会触发中断回调。2 }8 H: V5 n% [8 K! W: ~* F

% Y8 K% p( O1 V
我们需要注意的是,串口寄存器的数据会被存储到缓存区中,这里的缓存区相当于一个队列先进先出,当我们调用HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8);6 M- c) b5 T3 U5 x; K1 T7 l$ e7 z

4 S3 }8 q* C# J. l( k8 l
会将RecieveBuffer作为缓存区,将串口接收的信息存入缓存区。
: b( J' ?5 u$ A- y
% D9 V3 r2 A1 m  M2 D' ^9 T& F- }
那么让我们来试一下这段代码。
0 h" h; N: L6 h  G$ Q! W0 H. Y! q- a! Z; ^1 F/ @! g
微信图片_20240529195314.png $ i, w" o6 d9 R2 k5 M
4 ?. U# R1 L/ y% N  s' y
可以看到,代码死住了。其实不难理解,我们发送数据的时候,由于缓存区只有一个字符的空间,我们发送八个字符的时候会导致缓存区溢出。所以我朋友的代码可以“正常运行”也是一件非常奇怪的事情。
; Y, P# G/ }2 w8 w  _
' Z8 K: m6 o; i+ W所以正确的做法应该是:设置和缓存区一样长的读取字符。
; C9 `6 T  O) p1 I  B' k8 F) l8 M' |- R9 D" N( P, e
  1. unsigned char RecieveBuffer[8];% k# Z+ J! N0 P* s2 c6 O6 q
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    : j5 \! u: l& z% |; |1 y$ s6 U; Y
  3. {
    ( E1 }( b& b6 h8 C! k4 |
  4.   UNUSED(huart);1 e7 _  o" Q. L  Y. g0 ~' p4 x
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去
    / K: Y! j9 u1 W  W4 K
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t
    " k+ h/ O& d8 e# U8 i0 ?% Z
  7. }
复制代码
0 r4 Q) w- J( R# [& B
这样子就可以实现每接收8个字符就调用中断一次
% k* P- L7 J  h% {0 W8 C- K5 V" w: g$ A0 @
微信图片_20240529195311.png
# U/ I1 O+ q( t

: \9 X2 V: S0 L2 M% K4 J# b/ B可以看到,我们正常的发送了8个字符。0 |' j4 u) ?  c
; y6 ^8 Q( x7 z
但是这样子有一个非常非常致命的缺点!
0 L2 Y% q* {0 ]3 q6 I$ i) C* J4 F$ G6 I* I; e# X
由于我们的代码每8个才会接收一次,所以当我们的发送的数量小于8时,必须发送多次才能会触发一次调用。
# L2 }: L/ I! {5 b: l$ g4 c7 V4 K7 l# G4 d, O0 [: L! p$ W" w
注意,这里的每次我们都是先发送再接收,因为初始化的时候是启用了接收。
! {+ j* o6 r4 Y8 ]# S. `7 J4 o8 u3 f" K6 D7 F3 {
微信图片_20240529195307.png
- j* N* J4 @1 v, l

3 p+ f, J  U# r6 b" Z! R当我们发送3次“123”时才能让接收的字符>=8,我们把前八个发送了出去,此时缓存区剩下了一个字符是“3”# z; m, @! h/ F1 H: g
/ F- V% o6 [5 Q8 _2 H# M0 l
微信图片_20240529195305.png
$ N! ~" b( d8 I# v( Z

& W( o0 d% y) H第二次我们发送了三次“123123123”,再次让字符数量>=8,这时候把缓存区的八个“31231231”发送出去再接收后面的“23”,这时候缓存区的数据为:"23 "。
, n8 t* a9 ^' H$ Q3 i* q1 Z; h6 S! `; {+ u& N
微信图片_20240529195302.png % L& }0 ?7 s4 ]: t6 @0 g6 U

5 {) K0 G' ~# e( c最后我们第三次发送“123”的时候,由于23 + 123 + 123 这时候,虽然这里按理来说正好八个,但是实际测下来,当这时候接收的时候系统就会卡死。
, e( e/ H6 D% w6 i# f1 T6 i7 a* T8 A( x6 B& N
所以,实际上这种方法必须保证发送端的数据是完完整整的八个八个发送,多一个少一个都不行。: u8 W6 E) n9 p( n9 x# i
1 V- i, ]: B/ O" o7 }- [  G
& w' D1 V- r3 v% b. U
解法. e$ t7 Y% o, v
事实上最标准的做法是单个字符单个字符处理。定义一个大的缓存区,用来存储接收到的字符并且确立一个结束符来确定数据流的结束。
  1. if((USART_RX_STA&0x8000)==0)//接收未完成5 l! ?. p- G' p6 U, M( w- W
  2. {- m$ o5 O: M- \6 q! s; i
  3.   if(USART_RX_STA&0x4000)//接收到了0x0d
    # N- b; ]* ]# l* @: M- B. e
  4.   {% w4 I  i1 c4 I
  5.     if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
    & U; Y+ C( t: E" a4 }
  6.     else USART_RX_STA|=0x8000;  //接收完成了 1 w1 N; F# ~3 g$ v/ Y2 ^/ U9 V
  7.   }
    ; @& ^$ ?7 o3 I$ B7 J! P
  8.   else //还没收到0X0D( `& O+ |: s" l8 Y' P- T
  9.   {  
    ( h) q8 h# z# z/ n. Q: h' o. d
  10.     if(Res==0x0d)USART_RX_STA|=0x4000;5 G- Z0 v0 w2 z7 L2 a3 x
  11.     else
    2 \8 s5 {# Y, \; }" ]8 P
  12.     {) [2 h( f. J8 \. r
  13.       USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;* d) X) G( t4 J1 u  V
  14.       USART_RX_STA++;0 r* g4 Z$ A0 i  W6 l2 P3 S( J
  15.       if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收   
    5 i4 _' V. ~0 q% D
  16.     }     
    4 ?1 D9 z3 @! B! V) t
  17.   }
    / \4 v9 B; [: S3 r7 j
  18. }
复制代码
: I" d0 g" ]! L# G- t' ~( q

4 i2 J' }8 @. s3 c9 {
6 |0 g' b$ F' G# a+ G" B. H
上述是正点原子的官方例程,其中USART_RX_STA是接收到的字符,即是确定一个结束符\r\n来作为一串字符的结尾并且检测长度是否超出长度。
/ \$ R7 L( B; _

  ?; V4 q; g  `1 }7 o& Q这样子的代码容错率非常高,也确定了一个规范,并且避免了缓存区溢出的情况。9 l1 _/ R7 R0 K7 k: g# B
% n) d) A% m& ?% P7 ^

+ T, x3 Q4 o4 z+ F转载自:电路小白
* n2 H6 F9 V) ^/ U如有侵权请联系删除1 n" ?/ w$ \4 ]: I9 Q' k
3 T9 [! I5 Q# _; Q
收藏 评论0 发布时间:2024-5-29 19:53

举报

0个回答

所属标签

相似技术帖

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