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

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

[复制链接]
攻城狮Melo 发布时间:2024-5-29 19:53
一个关于STM32串口的问题。他想实现接收无限制字符数量的串口信息。
. T) N. N+ \1 ~3 u0 z2 ^
) N3 D. `; r* {) U) g
但是他遇到了一个问题,他在主函数中发送循环发送内容(500ms的延时)串口一中断回调函数中如果收到了串口内容,就利用串口发送。
: ?6 \" m4 u! V2 s# |3 B% y$ L2 F+ u6 `( j0 ~. L
但是他的串口一旦接收到了信息就会导致主循环中的串口发送极快。他的主代码如下
( [; Q6 ~% {1 j& U5 w3 R
  1. unsigned char RecieveBuffer;
    1 N+ K& c# C! }1 X7 q
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    9 Y/ M/ t- |' S- i6 @  r' o
  3. {& n8 m7 d- t) o. A4 L
  4.   UNUSED(huart);% a+ ?9 D; l% z# q0 e2 c+ V2 G
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去( [) @; i8 `/ ?% l# d, F
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t
    . g' Z: z# s" U( e9 E6 n, J7 ~* N
  7. }
复制代码
+ l2 ~! i) C" i  ~
看了他的代码,就这么短短的7行代码,让我汗流浃背。- h, O8 b5 d, n/ j0 N$ m
2 @+ _( F' u3 W- V1 b
他的发送和接收中有一个参数是8,这意味着当串口接收的寄存器中有8个数据时就会触发中断回调。% q# B" y+ M8 R) T9 G

. H- c  I& a, _1 O, D
我们需要注意的是,串口寄存器的数据会被存储到缓存区中,这里的缓存区相当于一个队列先进先出,当我们调用HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8);6 S) U$ d/ ?  _8 Q& ~( C
$ p9 U+ E9 x- ?# g5 W, X  w& `3 e
会将RecieveBuffer作为缓存区,将串口接收的信息存入缓存区。
- W' ^+ _8 f. R4 ]) b/ t% ^) C+ }( N1 ?) {5 {, I* ]5 F8 C! ]
那么让我们来试一下这段代码。0 Z8 d" X. s1 y6 T

& P( f$ t3 L4 v5 ^
微信图片_20240529195314.png / v/ E! `8 V% ]7 q

: {1 ]) R: F6 P5 I$ g可以看到,代码死住了。其实不难理解,我们发送数据的时候,由于缓存区只有一个字符的空间,我们发送八个字符的时候会导致缓存区溢出。所以我朋友的代码可以“正常运行”也是一件非常奇怪的事情。% ~  v1 ?4 S6 D) j; v
% U- F' e/ c% `: O% e& _+ C
所以正确的做法应该是:设置和缓存区一样长的读取字符。
& o2 q) b/ K; h0 U. R  f
" N- J$ j" x  f
  1. unsigned char RecieveBuffer[8];1 _# b7 L, e% O6 @  _  t! l3 z" M+ U
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)9 A7 I) ?! n: A; _: I/ r. H, U8 G/ s
  3. {* h6 j8 C) H7 E6 G9 k3 v9 s
  4.   UNUSED(huart);
    . L% P# }0 {, m: z+ y6 e
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去
    $ G. J- X* ?5 p+ D" P3 q; z& `. U
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t& R- `6 N; k; f+ z" U7 Y
  7. }
复制代码
, p/ X" P( W6 E- v+ t
这样子就可以实现每接收8个字符就调用中断一次* E4 V8 X$ n1 K2 i6 C) e9 `; V" M& |
6 r9 e' P* e5 N; V0 \6 }
微信图片_20240529195311.png
4 z$ t* i* q9 {  H% `; b4 N) k

5 c# a0 x  |5 |" p& s7 {可以看到,我们正常的发送了8个字符。* n. l* s, {" k( T2 \# M9 h

# ?& [7 T7 w3 C. I  }但是这样子有一个非常非常致命的缺点!
+ j9 b% G+ _' N! z! d1 W& P2 V3 j- T  }. p' u
由于我们的代码每8个才会接收一次,所以当我们的发送的数量小于8时,必须发送多次才能会触发一次调用。, [; o/ P2 H  |( x0 ?
5 }" Y) z9 E: T; M5 M4 O, _: Z
注意,这里的每次我们都是先发送再接收,因为初始化的时候是启用了接收。! g" k4 `- Q  x3 j5 u# D
/ e6 ^7 G& z) U- D  x% S
微信图片_20240529195307.png 8 b4 S! F: L- C. v7 u" @7 J, @

5 U. D" j2 A/ X0 r当我们发送3次“123”时才能让接收的字符>=8,我们把前八个发送了出去,此时缓存区剩下了一个字符是“3”1 W) j2 y0 R7 B" K) W
4 D8 F4 W( ?/ D2 W  {; Y- ~- @
微信图片_20240529195305.png
; ]" M$ F* q! f! Z5 K% B, o

4 Z( |- v! a- ^9 ~9 U第二次我们发送了三次“123123123”,再次让字符数量>=8,这时候把缓存区的八个“31231231”发送出去再接收后面的“23”,这时候缓存区的数据为:"23 "。7 w% N3 b/ C9 z. `5 [
% g, y% C* k* \8 @  R
微信图片_20240529195302.png
( W/ F& A( q: Z4 @

3 u' D& K7 m, m) z0 R* ?最后我们第三次发送“123”的时候,由于23 + 123 + 123 这时候,虽然这里按理来说正好八个,但是实际测下来,当这时候接收的时候系统就会卡死。; J3 x6 [& [5 n

% Z' C! N8 _$ t7 m6 Z! S2 X所以,实际上这种方法必须保证发送端的数据是完完整整的八个八个发送,多一个少一个都不行。
: b3 c, p& k( {; K) x* e
+ m; E$ k) ^( N6 ~8 B& [

) F& p# j9 w  V2 d9 T/ G  R解法
) Y6 _/ M) |' Z事实上最标准的做法是单个字符单个字符处理。定义一个大的缓存区,用来存储接收到的字符并且确立一个结束符来确定数据流的结束。
  1. if((USART_RX_STA&0x8000)==0)//接收未完成
    3 S6 k8 L9 c0 G
  2. {
    $ v) W* \( M0 X# F4 P# c
  3.   if(USART_RX_STA&0x4000)//接收到了0x0d' _! \0 }+ |4 }/ K6 }
  4.   {# o: _+ [7 p( c
  5.     if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始3 W9 W9 b/ Y3 K  w
  6.     else USART_RX_STA|=0x8000;  //接收完成了
    - h% \* O0 `3 n; m6 j( {
  7.   }7 \4 f" l, S# h3 T+ U( u5 P
  8.   else //还没收到0X0D$ a% |0 d3 P& G, m9 U, Z- o
  9.   {  9 g9 s. Y4 @  d. Q$ C7 n
  10.     if(Res==0x0d)USART_RX_STA|=0x4000;
    $ c+ u7 e5 `& ~6 j" p( K) m& |6 h
  11.     else) R$ R9 `2 d* H+ l& j6 [$ u# ]2 W
  12.     {% g0 w( z' |) t$ S9 m3 h
  13.       USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
    8 P4 n- j4 k: @% y9 }
  14.       USART_RX_STA++;9 f& K/ v3 J% {, U
  15.       if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收    " P; H1 n( C$ u
  16.     }     
    1 i0 ]9 K" Z4 n  S  m( R3 s
  17.   }6 O' ^+ u, q3 d6 ?6 E
  18. }
复制代码

0 {/ `6 X5 J5 d) _) y& f: ^5 d% V: @8 w4 C

' g% Q/ Q6 K! |: w7 _+ _上述是正点原子的官方例程,其中USART_RX_STA是接收到的字符,即是确定一个结束符\r\n来作为一串字符的结尾并且检测长度是否超出长度。

# C/ {1 C# n1 P! r
5 ]1 U- Y' ]% [! S1 ?' a这样子的代码容错率非常高,也确定了一个规范,并且避免了缓存区溢出的情况。( `8 X) r, N- ]# o4 {

' v, W  i2 u9 T1 ]+ S- B. g4 h4 e, l
0 B5 N  |8 Y. x% d转载自:电路小白
6 `5 P" S" @5 p. K9 g8 ~如有侵权请联系删除
) t6 W* }0 o+ i% w1 M7 X6 J7 t8 t5 O4 |
收藏 评论0 发布时间:2024-5-29 19:53

举报

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