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

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

[复制链接]
攻城狮Melo 发布时间:2024-5-29 19:53
一个关于STM32串口的问题。他想实现接收无限制字符数量的串口信息。' ]6 K# }! p+ }# a
' c8 u7 V. Z" m' T$ m0 |
但是他遇到了一个问题,他在主函数中发送循环发送内容(500ms的延时)串口一中断回调函数中如果收到了串口内容,就利用串口发送。! x/ o, ]* J7 P, n) f7 i0 J5 p/ x& q
! }) m. N3 K9 T8 o$ f! Z, D, k& t
但是他的串口一旦接收到了信息就会导致主循环中的串口发送极快。他的主代码如下9 Y. B+ w, b8 v. `: ?0 W5 _* I1 p
  1. unsigned char RecieveBuffer;
    7 ]' }1 v! m4 n
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); F( |( `: Y1 Z4 g, L/ B  l
  3. {) Y* A6 `* W1 l, e
  4.   UNUSED(huart);
    ' o+ y- Z" o) W1 [& s4 [, T6 x
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去
    3 o5 {  T4 N) T! a. b) z: z
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t
    : O* F- l' o3 ?1 f# X# R
  7. }
复制代码
! \7 G' Z" J: J. U/ x
看了他的代码,就这么短短的7行代码,让我汗流浃背。. i, M! n7 h! \7 o: m6 n; J' q
. c4 Y- F8 z3 D6 E. g
他的发送和接收中有一个参数是8,这意味着当串口接收的寄存器中有8个数据时就会触发中断回调。7 x. f  L$ i: K2 e0 c% {, c$ G
: G0 L3 `$ V- `& Y- o( I' x
我们需要注意的是,串口寄存器的数据会被存储到缓存区中,这里的缓存区相当于一个队列先进先出,当我们调用HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8);
5 F  z/ B/ X" U0 D" f7 o
& M( P6 i* O  X8 j* I% Y9 `
会将RecieveBuffer作为缓存区,将串口接收的信息存入缓存区。
" \7 h  I5 P# e  D
% ~0 a% ?- K+ z5 s* ^" F( d
那么让我们来试一下这段代码。
) E3 Q- d1 |- H5 Y( r! ~( L: t; _# j
微信图片_20240529195314.png . q. O! ~" _7 v* S

. `4 i$ f, H6 k可以看到,代码死住了。其实不难理解,我们发送数据的时候,由于缓存区只有一个字符的空间,我们发送八个字符的时候会导致缓存区溢出。所以我朋友的代码可以“正常运行”也是一件非常奇怪的事情。
9 k9 ?% M8 y5 w5 [/ P
8 }$ {+ F, l9 l  {所以正确的做法应该是:设置和缓存区一样长的读取字符。3 W; t8 m; S6 _" C  g' n

% {, T) w! D1 G" `4 w$ B
  1. unsigned char RecieveBuffer[8];
    + L3 x8 S, }$ z/ }) V: Q, k: S
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    3 b6 k7 j9 `1 R! j+ ^% E
  3. {/ {1 l6 v1 e2 N( l/ [
  4.   UNUSED(huart);
    9 l: W: f! g3 g3 @
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去% H3 |  ^$ m; C$ K; W8 `
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t
    & }% O8 p% e* W5 [
  7. }
复制代码

: O. X% O5 ^* r% o这样子就可以实现每接收8个字符就调用中断一次
8 E# q# Q! B4 Z( V
2 h" a7 W, _: }
微信图片_20240529195311.png
$ z* J; a5 R' j- m: T3 V3 n
, x8 x0 K5 V. g. Z, r: X: z
可以看到,我们正常的发送了8个字符。& N: P5 q+ |/ A3 R2 O

5 s8 ]' m" u! r$ e$ K7 s但是这样子有一个非常非常致命的缺点!
* V6 Q# {2 D- r3 d
8 J# `4 B# L; [9 d由于我们的代码每8个才会接收一次,所以当我们的发送的数量小于8时,必须发送多次才能会触发一次调用。
9 S$ C, i# R3 f7 L8 f
+ r5 y- ^" U6 Z注意,这里的每次我们都是先发送再接收,因为初始化的时候是启用了接收。8 x% G7 M& Y" _/ T
/ ]" f8 c8 n* l2 B+ e1 C3 w
微信图片_20240529195307.png ; |  u2 v5 F. _0 J  f/ S

8 F7 W3 q& Q& g! j当我们发送3次“123”时才能让接收的字符>=8,我们把前八个发送了出去,此时缓存区剩下了一个字符是“3”0 m9 U' l$ K# A* q
( L# S" w$ x: u
微信图片_20240529195305.png
1 _: r: o1 v" ]7 E

9 z) W% M: h3 k+ V: i, q第二次我们发送了三次“123123123”,再次让字符数量>=8,这时候把缓存区的八个“31231231”发送出去再接收后面的“23”,这时候缓存区的数据为:"23 "。
# H$ p! a! v$ D+ w/ m- R, b5 R5 ^7 b2 O: I" L$ f  E
微信图片_20240529195302.png
9 I4 Q1 J2 s' i9 D: S
; I$ R# w& F+ J- \- ?& f' L+ v
最后我们第三次发送“123”的时候,由于23 + 123 + 123 这时候,虽然这里按理来说正好八个,但是实际测下来,当这时候接收的时候系统就会卡死。$ O- z2 }  h* k
2 e4 V# E8 L9 T3 l' {
所以,实际上这种方法必须保证发送端的数据是完完整整的八个八个发送,多一个少一个都不行。5 m# o+ ~; H1 t. {0 j( h5 P
2 x0 I4 \+ j  l

0 B2 u) H7 X$ H6 d/ T' x8 h解法
3 g4 X" |0 ~0 u; n, M事实上最标准的做法是单个字符单个字符处理。定义一个大的缓存区,用来存储接收到的字符并且确立一个结束符来确定数据流的结束。
  1. if((USART_RX_STA&0x8000)==0)//接收未完成
    / D* j5 N' D9 E6 l; D4 {9 I
  2. {# m9 i- A" Y$ K1 R
  3.   if(USART_RX_STA&0x4000)//接收到了0x0d
    ) {; `$ b4 |% l- Y
  4.   {
    / i2 C& N# ]9 ~; [
  5.     if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
    . v) @0 p$ h, ~  n3 o
  6.     else USART_RX_STA|=0x8000;  //接收完成了
    7 j$ W' U! c  D
  7.   }5 R/ J5 l0 d, r6 F
  8.   else //还没收到0X0D
    ; T% w3 y% [5 E- [5 l
  9.   {  ( b. r3 j9 o3 d5 t" c9 D
  10.     if(Res==0x0d)USART_RX_STA|=0x4000;
    2 Z" g1 s, ?' a5 O; B5 |7 H
  11.     else' L# i. B1 d: e- H' o  m" d5 |! L
  12.     {3 i* S0 m- q# x( g1 t
  13.       USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;3 j4 d/ k! o' o# R( R& \
  14.       USART_RX_STA++;
    + A; K7 s  a7 U% E
  15.       if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收    2 ~) {( P: g, H- Z0 o2 u" N
  16.     }     
    . v: x; W* S9 r5 }% |0 x
  17.   }
    1 b2 w+ E" i' f# V4 f% t2 m& l, }
  18. }
复制代码

; N  y0 D8 p8 G% C7 G' s5 b, g
( K  a$ k3 T9 E* \& X8 K
9 M, |# ?2 Q! i/ F/ M# l; L
上述是正点原子的官方例程,其中USART_RX_STA是接收到的字符,即是确定一个结束符\r\n来作为一串字符的结尾并且检测长度是否超出长度。

, `7 z; l- V6 L* ?  R
! s4 j- p$ d, g" @+ M这样子的代码容错率非常高,也确定了一个规范,并且避免了缓存区溢出的情况。2 i$ c( b; n) z0 G8 e; M
4 V3 [8 y6 ?9 m9 M6 Q2 S9 \

3 V- _* ?% a+ m" ^% _9 `) W, q转载自:电路小白$ u; j! a, ~/ f. Z& u
如有侵权请联系删除
/ N2 h! A9 B, e3 Y& c  W
. T+ X  i. k. p" ?: J+ ]9 f
收藏 评论0 发布时间:2024-5-29 19:53

举报

0个回答

所属标签

相似技术帖

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