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

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

[复制链接]
攻城狮Melo 发布时间:2024-5-29 19:53
一个关于STM32串口的问题。他想实现接收无限制字符数量的串口信息。
& k; }: E* p8 o$ v5 P6 y( [- @7 w* ]8 F. w5 q2 Y- t6 y* l
但是他遇到了一个问题,他在主函数中发送循环发送内容(500ms的延时)串口一中断回调函数中如果收到了串口内容,就利用串口发送。
9 i0 m9 O9 z( ~$ t* N
. u+ B5 Z( Z( n8 a
但是他的串口一旦接收到了信息就会导致主循环中的串口发送极快。他的主代码如下
5 ~  f; s. v; F1 V) }+ a/ \! d% W
  1. unsigned char RecieveBuffer;
    1 W& T4 b8 X1 d3 N* \
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    & b0 e( m) f- p- z/ X; n1 g
  3. {, r/ a. R! M  k, I
  4.   UNUSED(huart);
    6 r2 I" o: w  o8 y
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去% H: l" f# e* F& K9 ~; y
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t
    , k1 B. L7 h: c7 C- i( f, Y
  7. }
复制代码

6 l" n8 m9 V7 o. d* p# W/ F看了他的代码,就这么短短的7行代码,让我汗流浃背。3 B! J! S: b5 {; Z' T" t9 A& h

  a& s/ s' e8 n" K" g  a
他的发送和接收中有一个参数是8,这意味着当串口接收的寄存器中有8个数据时就会触发中断回调。: d. L' `  o9 _

$ ^8 K- n. W5 S; s! {
我们需要注意的是,串口寄存器的数据会被存储到缓存区中,这里的缓存区相当于一个队列先进先出,当我们调用HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8);: l) i3 g  E) @$ ^( G7 P+ F
7 Z& s. [( n% W  k" h2 m
会将RecieveBuffer作为缓存区,将串口接收的信息存入缓存区。5 j* y( h) C  o# W3 X+ K2 e

2 T1 B1 z; T% @4 D
那么让我们来试一下这段代码。
( A. P5 I" n/ b; l
- Y5 p4 ?$ L% P) R
微信图片_20240529195314.png
. [+ A4 M3 k  x* ]" W* f- W6 d
' }$ q" H9 x" K4 w
可以看到,代码死住了。其实不难理解,我们发送数据的时候,由于缓存区只有一个字符的空间,我们发送八个字符的时候会导致缓存区溢出。所以我朋友的代码可以“正常运行”也是一件非常奇怪的事情。
& f  ?* l! l0 `. B9 K
* B! Z- H( b7 |所以正确的做法应该是:设置和缓存区一样长的读取字符。
7 r4 z$ T3 ^. [8 u# {) p, D. J6 I& v0 C9 F9 ^  E
  1. unsigned char RecieveBuffer[8];
    # }; i" S! T' Y; Y5 D8 X
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
      V/ p: K5 F4 [3 H* e- X
  3. {
    : b) c8 {) e9 J+ g! D
  4.   UNUSED(huart);: ~  X* a' Z' G/ |/ q
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去& B  \* g7 ?' }; Z/ h
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t
    5 f( q' G- k& ?7 a$ |- m3 y, t
  7. }
复制代码

; L+ b- X, N, ]  S/ m% t7 ~$ a这样子就可以实现每接收8个字符就调用中断一次  L2 _% C/ g1 H, u' ^- o* W4 G

, W% \9 T0 l  J. J. ]0 o) M0 Y+ x, h& ^
微信图片_20240529195311.png 4 o, T! e, K- C

. F" R; [# m( |3 x* ~% m  |可以看到,我们正常的发送了8个字符。
0 K* B( R0 {# F1 A
6 d6 z4 E8 F/ S. \# W但是这样子有一个非常非常致命的缺点!1 c9 L, f4 l1 u2 X* T' U/ M, {# u+ u3 P
+ A1 e! G9 T* O4 K/ E% |& y
由于我们的代码每8个才会接收一次,所以当我们的发送的数量小于8时,必须发送多次才能会触发一次调用。
0 U; p, c3 d  ?* N* L' a, G3 P  Y+ b6 Y4 w" T/ c0 E3 A
注意,这里的每次我们都是先发送再接收,因为初始化的时候是启用了接收。
" L" A" C8 k5 H+ j4 t) c1 W
# [+ j+ a& _' D. ^$ Q. @
微信图片_20240529195307.png 4 [9 a& z& c$ F  E7 t# G1 h

& E& J' V$ f/ ?2 h  G当我们发送3次“123”时才能让接收的字符>=8,我们把前八个发送了出去,此时缓存区剩下了一个字符是“3”
4 P' I- D- B. M: U) z2 J% Z
! Z  C$ `$ x' t$ n1 W
微信图片_20240529195305.png ; h) G5 @1 `& `  A' X1 e! t1 z9 U

0 A0 t& H6 y: u( @6 X4 s# P7 i! [* p第二次我们发送了三次“123123123”,再次让字符数量>=8,这时候把缓存区的八个“31231231”发送出去再接收后面的“23”,这时候缓存区的数据为:"23 "。
6 |( m/ [) \# L5 ~- m* V+ p1 F8 M4 O
微信图片_20240529195302.png
# ~% P1 F. E7 Y. |( a+ }

; P# [% j& U( {, x- q, M最后我们第三次发送“123”的时候,由于23 + 123 + 123 这时候,虽然这里按理来说正好八个,但是实际测下来,当这时候接收的时候系统就会卡死。
% _8 G' A" Y5 N
0 s$ Z5 ^% @( s1 j所以,实际上这种方法必须保证发送端的数据是完完整整的八个八个发送,多一个少一个都不行。
) [) r: j! L. _# j% V5 U$ T- A$ C" w- [* `

# b5 U0 o2 n# s, M解法' F1 e: S. X- Y- _" w
事实上最标准的做法是单个字符单个字符处理。定义一个大的缓存区,用来存储接收到的字符并且确立一个结束符来确定数据流的结束。
  1. if((USART_RX_STA&0x8000)==0)//接收未完成( N, U) L: M8 `! `: H+ U9 L
  2. {- R+ `9 F6 y% J1 O. _  q
  3.   if(USART_RX_STA&0x4000)//接收到了0x0d$ Y& R0 ~: i* u- }
  4.   {
    # V9 Q3 W4 Q9 v2 g. B
  5.     if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始  M/ n" j% {  p( N7 R2 k
  6.     else USART_RX_STA|=0x8000;  //接收完成了 / ]( `' O" o& d7 t% L( B; q" w
  7.   }, Z! Y& T4 @  x7 y8 m8 S
  8.   else //还没收到0X0D
    & z: l- H) G9 P- |" t0 `
  9.   {  0 ?5 b3 j1 O( y1 J) o" I3 X
  10.     if(Res==0x0d)USART_RX_STA|=0x4000;, v4 n0 I& l7 s
  11.     else
    ; }9 v% y. j$ p  ]/ m, o6 ], o
  12.     {' D: k1 e  M* H, r0 U
  13.       USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;0 @8 X- d5 i) s" w4 y" u
  14.       USART_RX_STA++;
    7 z9 @% J/ `$ D' T# p
  15.       if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收   
    1 T; X) f! |# `5 x# R# K1 g; T  `
  16.     }     5 _" p, y9 r, U$ P. g; q
  17.   }& `6 _. X' L2 `, p$ y6 Q! w
  18. }
复制代码

  z2 `0 C. Q& y2 A* X# o6 K2 V8 b& @6 _  V$ Z& i' P9 t
4 d7 x1 {, t( o# Y6 w2 v0 e0 y6 Q, S
上述是正点原子的官方例程,其中USART_RX_STA是接收到的字符,即是确定一个结束符\r\n来作为一串字符的结尾并且检测长度是否超出长度。

; S/ S1 ?* M% }2 _/ ?! b% f+ K8 }4 _5 X. Q' s# x1 K6 a" Q
这样子的代码容错率非常高,也确定了一个规范,并且避免了缓存区溢出的情况。
9 P4 }! {4 E  L5 b
/ y  _5 h8 Q2 T# ]4 o
3 l0 S- M- r8 _4 E/ m, z转载自:电路小白
! M9 k( I  y# p如有侵权请联系删除
; E, q% b9 f5 y7 ~) p
% N! s! C+ r( I# o3 t
收藏 评论0 发布时间:2024-5-29 19:53

举报

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