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

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

[复制链接]
攻城狮Melo 发布时间:2024-5-29 19:53
一个关于STM32串口的问题。他想实现接收无限制字符数量的串口信息。
) s5 }% @) h5 y) Q, N
# E' C/ P* W  p# i( N4 U
但是他遇到了一个问题,他在主函数中发送循环发送内容(500ms的延时)串口一中断回调函数中如果收到了串口内容,就利用串口发送。
/ w2 v( H% ]( T+ [! Y2 u
7 t+ T% ~3 W6 E  N$ W+ U# W! g
但是他的串口一旦接收到了信息就会导致主循环中的串口发送极快。他的主代码如下
/ |/ [3 i4 G+ n8 v, u  s) b1 v) B
  1. unsigned char RecieveBuffer;
    - F7 @" _6 e) L# @6 Q) a0 P
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    , A5 x7 n/ G" f4 G5 S
  3. {
    8 [9 g' |# ]. ~  Z7 t! G
  4.   UNUSED(huart);7 O& D1 i, _6 X3 v( a; v
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去( X4 V3 K! b& Z5 m  Q, E
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t1 w' h- ^. b# u1 @& e0 I2 S/ s
  7. }
复制代码

- s. d* P; X' ?( w3 u  n5 d# u看了他的代码,就这么短短的7行代码,让我汗流浃背。
% i, u6 f4 E# r! J6 G& _7 r! m- l2 }/ d/ E  n3 E2 @/ A; Q
他的发送和接收中有一个参数是8,这意味着当串口接收的寄存器中有8个数据时就会触发中断回调。' s3 |1 ~9 d4 @+ a  z3 L* W* f
+ [, s' a% t7 z8 n7 n. d
我们需要注意的是,串口寄存器的数据会被存储到缓存区中,这里的缓存区相当于一个队列先进先出,当我们调用HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8);$ T& h- }, l4 P4 D4 k# f: G

. Q8 {5 Z: Y- {0 P
会将RecieveBuffer作为缓存区,将串口接收的信息存入缓存区。
: j$ w' R. w1 q; c5 g" e* F2 ]+ d: T9 t) r  \  R8 d3 g
那么让我们来试一下这段代码。
! r+ k9 H: X+ l4 @! `1 b' _* b  [) s9 p% S. C' j9 [& ~' U5 p& M4 E4 P
微信图片_20240529195314.png 6 i# l1 T2 c& @1 g* d( R! ?+ u1 |

6 u6 E. A& [& Z" d) W; s4 @可以看到,代码死住了。其实不难理解,我们发送数据的时候,由于缓存区只有一个字符的空间,我们发送八个字符的时候会导致缓存区溢出。所以我朋友的代码可以“正常运行”也是一件非常奇怪的事情。$ B8 _; ~1 b6 C% i+ x2 ]

7 `5 f" N& J- k1 s; A, i% L) h所以正确的做法应该是:设置和缓存区一样长的读取字符。; w2 t5 ^. Q+ ^- M! A/ x& {

* d4 _# j" y9 h! C/ ~
  1. unsigned char RecieveBuffer[8];# ?( {3 i5 f3 q" v0 P9 B
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    " U2 x5 l/ }, u
  3. {* d( G. N% h" f" ]/ x9 `
  4.   UNUSED(huart);9 e  B+ x+ `  W5 [
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去
    * G# _# L+ X1 T6 I2 N/ w  R) e' g5 C
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t
    ) V; z, R8 Y- R) z5 U4 Z) J- r
  7. }
复制代码
: z$ L7 r2 K  |1 g/ f4 S) l4 _
这样子就可以实现每接收8个字符就调用中断一次, \1 Y" H! Z) l7 e

3 V+ p! Y; J' P8 q; b
微信图片_20240529195311.png % ^) c# [3 J4 S1 L) M4 T

# Q4 N" R6 \5 e' M. a* V- A" d可以看到,我们正常的发送了8个字符。
0 N% K+ L! t. V5 j1 f* W7 O2 A. M7 h" p
但是这样子有一个非常非常致命的缺点!: v. N4 t$ x$ ]3 K! ~

1 `; y- o( N8 c3 U5 L" f! K由于我们的代码每8个才会接收一次,所以当我们的发送的数量小于8时,必须发送多次才能会触发一次调用。9 D: U* Q+ h0 b% ?, ~0 X, P* X' v& U

, D# v5 T) B1 ?: \% M5 h# X' R注意,这里的每次我们都是先发送再接收,因为初始化的时候是启用了接收。6 z- X6 ]9 t! s! w
; C! K& X; r; H8 _. P% Z
微信图片_20240529195307.png 3 G& G- E# b# w3 L' ~

$ ~! j/ o! U8 D: s8 l当我们发送3次“123”时才能让接收的字符>=8,我们把前八个发送了出去,此时缓存区剩下了一个字符是“3”' D( @' d3 ], f
7 o! D& C; g9 c& k$ z0 l" D+ D* v
微信图片_20240529195305.png 0 r' t# V) g8 K# W, J' f4 ]  }
+ i6 Q  X; t, d7 [0 U
第二次我们发送了三次“123123123”,再次让字符数量>=8,这时候把缓存区的八个“31231231”发送出去再接收后面的“23”,这时候缓存区的数据为:"23 "。
7 a- w& \* M7 e. _; T  q0 h  `/ [& ]% P! E- z. M
微信图片_20240529195302.png
: P! w! D6 b' X% d/ [
! \; P! _4 R# ^6 i8 j
最后我们第三次发送“123”的时候,由于23 + 123 + 123 这时候,虽然这里按理来说正好八个,但是实际测下来,当这时候接收的时候系统就会卡死。
* e% B( B1 F# Y$ C( X& }8 P" D
1 U7 o8 k5 G" E所以,实际上这种方法必须保证发送端的数据是完完整整的八个八个发送,多一个少一个都不行。
/ L, _; A8 U- _7 C0 D, ^0 F! x  g
% V$ l' k, f7 Y% ]8 b  e
2 G7 {# y6 ?& J& S( z5 o/ a/ a5 N
解法4 b! a6 S- K. }( D  z! y3 h
事实上最标准的做法是单个字符单个字符处理。定义一个大的缓存区,用来存储接收到的字符并且确立一个结束符来确定数据流的结束。
  1. if((USART_RX_STA&0x8000)==0)//接收未完成" s1 @" O: s0 y3 V% v/ C
  2. {
    0 i/ V, Z6 ~0 r0 [8 r
  3.   if(USART_RX_STA&0x4000)//接收到了0x0d6 ~9 T$ B% S  i: T9 o
  4.   {8 B4 f/ k$ C+ H/ K
  5.     if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始5 T3 e" E& [3 {* g$ N0 G' R$ v& B; N
  6.     else USART_RX_STA|=0x8000;  //接收完成了 ; k& @/ ]. C# w' f9 ?
  7.   }
    . R+ B1 \- j& P( k% Z% m
  8.   else //还没收到0X0D
    % E, H8 N8 s) G, U' f* A
  9.   {  8 k  k( V6 b4 `1 |' s
  10.     if(Res==0x0d)USART_RX_STA|=0x4000;* S  [' }! o: u4 X' c7 j8 W
  11.     else) V, b6 q# C" Y0 I4 q! v0 l
  12.     {
    8 f3 Q) o" V  T
  13.       USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;9 e0 R2 r) O. f6 g, f
  14.       USART_RX_STA++;
    . M! f# n; J# W( d% D8 o
  15.       if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收    ! m5 C& ^1 ?' q4 T4 i& A' |
  16.     }     
    8 D  ]) r& E/ U1 |% |0 w3 J
  17.   }
    3 `( m% j  Q4 \+ G; v% @; P% D3 j
  18. }
复制代码
: _" o# j3 R1 b7 n6 c% s4 l
* m! h+ _; d) U* G+ p& y

9 P" p9 f0 q" R: P* T上述是正点原子的官方例程,其中USART_RX_STA是接收到的字符,即是确定一个结束符\r\n来作为一串字符的结尾并且检测长度是否超出长度。
2 f. |0 m1 l' j; }) K4 H! b9 B) U

5 |& G4 e2 L6 k# S这样子的代码容错率非常高,也确定了一个规范,并且避免了缓存区溢出的情况。
8 i$ V3 p* t& D7 j9 e& C  \. h! s" L
7 y# G& ~4 t, s+ v
转载自:电路小白+ P$ V1 T( `7 O3 \5 y1 W
如有侵权请联系删除+ [& |) c% \1 Y3 t& J' H5 t
0 x7 p* K, R- l/ c& c
收藏 评论0 发布时间:2024-5-29 19:53

举报

0个回答
关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版