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

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

[复制链接]
攻城狮Melo 发布时间:2024-5-29 19:53
一个关于STM32串口的问题。他想实现接收无限制字符数量的串口信息。9 H  B& B. l( l# P
9 j& P  U- {- K- j
但是他遇到了一个问题,他在主函数中发送循环发送内容(500ms的延时)串口一中断回调函数中如果收到了串口内容,就利用串口发送。# V" z- x" x& s9 O% y3 k+ p: S# F
# d4 n3 P& G- a5 c% A0 R1 P/ b4 r1 m
但是他的串口一旦接收到了信息就会导致主循环中的串口发送极快。他的主代码如下7 J  w8 L; w3 `' _4 H
  1. unsigned char RecieveBuffer;
    * t6 w% a7 o; q; G7 R
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    5 x( O, i$ H1 H; W
  3. {
    1 j# @: W+ x0 P5 P% C9 f
  4.   UNUSED(huart);
    1 `3 C0 k4 U" M; o+ f  S% }
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去* H: p, ]/ r9 y
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t1 ^) H2 Z# G$ q* \/ X
  7. }
复制代码

" B, U6 R& w! n/ w7 v6 z看了他的代码,就这么短短的7行代码,让我汗流浃背。8 W7 T- Y1 P( p8 [. I+ e. F/ Z9 @
  W2 q/ {4 [$ f3 o/ I
他的发送和接收中有一个参数是8,这意味着当串口接收的寄存器中有8个数据时就会触发中断回调。1 k+ e( t+ b% t
3 [" o, Q0 j% w" v. w0 N! z* b: T$ [
我们需要注意的是,串口寄存器的数据会被存储到缓存区中,这里的缓存区相当于一个队列先进先出,当我们调用HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8);% j: H( l1 q" L6 T( f
  ^! H9 S, P. i) |1 G2 `+ Q; \
会将RecieveBuffer作为缓存区,将串口接收的信息存入缓存区。: Q+ ^/ ^. ]- o2 @6 S" \

" u6 F2 D. \/ @+ H* E
那么让我们来试一下这段代码。
9 o8 L6 i8 z/ _; j# m8 Q' O: O! F! W8 p& J) p8 Q" g
微信图片_20240529195314.png % ?' [! q! m! m) p: w

, i* O8 i  i- D2 x- u可以看到,代码死住了。其实不难理解,我们发送数据的时候,由于缓存区只有一个字符的空间,我们发送八个字符的时候会导致缓存区溢出。所以我朋友的代码可以“正常运行”也是一件非常奇怪的事情。
- s# j" v8 |" s6 a) e9 V1 X8 ?* L2 I& B5 p* n9 |
所以正确的做法应该是:设置和缓存区一样长的读取字符。
; V, R% L; \4 q$ t
9 U5 [3 F' G. N$ H! o' _8 a
  1. unsigned char RecieveBuffer[8];% f( @7 Q/ J7 D6 X& B9 P/ h
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    ! R' f2 J2 Y6 A% x2 a- |- Y: N7 e
  3. {
    / |0 F3 I, ]2 L8 |7 O) C7 t2 D
  4.   UNUSED(huart);; l4 x  q9 E6 O
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去/ S, L5 E! S7 D& r+ L: x
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t
    8 R2 i- B% D% D
  7. }
复制代码

0 U) _2 [) ~1 O/ f0 j3 C这样子就可以实现每接收8个字符就调用中断一次
7 Z# k) j% k- f0 o0 p" x; P
% J( [$ p' U* ?
微信图片_20240529195311.png
( ?) f0 U1 M, ]  U

% S1 v! d+ a+ V8 T: Y可以看到,我们正常的发送了8个字符。# c( p" T- O0 Y7 T5 h0 T& ^

. z# C5 v4 T  I0 j2 P7 b4 |但是这样子有一个非常非常致命的缺点!9 M  ^5 [5 V/ e$ S
( ?  a7 e# r( A6 j2 ^3 Z
由于我们的代码每8个才会接收一次,所以当我们的发送的数量小于8时,必须发送多次才能会触发一次调用。
- n5 v6 ]  U3 e4 k2 G
& Q& s# v- W' d: [$ R注意,这里的每次我们都是先发送再接收,因为初始化的时候是启用了接收。" N! V9 @7 G7 `. T6 t0 ?
9 q* |, g8 ~) M! w, \2 ~4 b! ?1 V
微信图片_20240529195307.png
4 B8 e7 h' d4 r  t6 F

8 I* u! O7 h% f; }4 I8 P: B当我们发送3次“123”时才能让接收的字符>=8,我们把前八个发送了出去,此时缓存区剩下了一个字符是“3”
% |" Z( m& J  Q8 A+ {+ U; T: u/ Q% k( }0 Y
微信图片_20240529195305.png
0 Z9 k% T8 a* a2 _! M& y
: C. F( O% E/ P( @3 v
第二次我们发送了三次“123123123”,再次让字符数量>=8,这时候把缓存区的八个“31231231”发送出去再接收后面的“23”,这时候缓存区的数据为:"23 "。' i$ F* I2 b( [# v! Z
5 X3 X  u* B) ~7 I$ @' Y, U
微信图片_20240529195302.png
* C* Y' p- x- z. s9 t# O

! z1 G+ @4 O4 ~- f2 `0 u最后我们第三次发送“123”的时候,由于23 + 123 + 123 这时候,虽然这里按理来说正好八个,但是实际测下来,当这时候接收的时候系统就会卡死。/ K/ i; c, y9 E( q7 m! @7 V

5 p. M% z) h& f0 z, C% x8 f: a3 r所以,实际上这种方法必须保证发送端的数据是完完整整的八个八个发送,多一个少一个都不行。0 s  h" y; x8 t1 Y0 J: M9 p- z
; @/ x, ~7 q3 ~1 H$ O# w! L/ q0 Y8 Y
6 S, `+ T; n* y) k5 g% a3 @2 Q
解法
$ \2 u. o" h: }9 S事实上最标准的做法是单个字符单个字符处理。定义一个大的缓存区,用来存储接收到的字符并且确立一个结束符来确定数据流的结束。
  1. if((USART_RX_STA&0x8000)==0)//接收未完成4 T& z) k5 K8 Y
  2. {1 j% N0 G. h+ H
  3.   if(USART_RX_STA&0x4000)//接收到了0x0d
    1 n  g/ u9 G+ a2 R5 l4 j) N
  4.   {
    4 k& ^( h1 o6 {0 a0 i2 Q
  5.     if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始9 p* ^  D' W7 ?. F8 l( J$ s
  6.     else USART_RX_STA|=0x8000;  //接收完成了 % P# n4 w( j! U6 p2 c# e0 ~+ c
  7.   }
    8 }" R9 f  u: c9 \" J
  8.   else //还没收到0X0D
    " h% J  X. U" V1 \) p$ P1 ]
  9.   {  
    7 f) D3 i3 w% Y4 k) _
  10.     if(Res==0x0d)USART_RX_STA|=0x4000;
    ( z2 Z8 R2 o. q3 V
  11.     else
    : i9 R: D* I3 y$ H
  12.     {
    4 x+ k2 t$ S$ `' s- [6 n
  13.       USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;3 r' q7 i2 L4 Y& f& _2 g
  14.       USART_RX_STA++;5 i. c  f0 k, G+ C2 M  i3 r
  15.       if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收    $ T! z- o; h, w1 }1 R  f8 E
  16.     }     
    ! @3 t; Q: l2 X! n
  17.   }
    9 T; l( M" \7 \; L9 @8 v- A& r" I
  18. }
复制代码

$ f0 V6 B( t& n( N! a, X$ E
9 n) b! ]1 k: m
! _+ \$ O8 o, ~
上述是正点原子的官方例程,其中USART_RX_STA是接收到的字符,即是确定一个结束符\r\n来作为一串字符的结尾并且检测长度是否超出长度。
# m$ C. B2 ~5 t# _1 I5 \$ E

3 W& g8 V2 c5 I* q1 V/ x这样子的代码容错率非常高,也确定了一个规范,并且避免了缓存区溢出的情况。
: _$ r1 Q, c; u6 r
: Z+ i# H2 i1 L) K: p8 k& ]
# y4 c# m" E7 Y  m. G* U2 T$ l转载自:电路小白" U  c0 D5 N5 C$ q8 t% J  G, S
如有侵权请联系删除* ]% }8 n; P2 b/ Q) D: {) z! Y
( u8 M- @5 r! u4 }) g8 ?# p
收藏 评论0 发布时间:2024-5-29 19:53

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32Cube扩展软件包
意法半导体边缘AI套件
ST - 理想汽车豪华SUV案例
ST意法半导体智能家居案例
STM32 ARM Cortex 32位微控制器
关注我们
st-img 微信公众号
st-img 手机版