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

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

[复制链接]
攻城狮Melo 发布时间:2024-5-29 19:53
一个关于STM32串口的问题。他想实现接收无限制字符数量的串口信息。
! ]* K# g2 _( e& |0 f# N0 W8 K$ W. B! h: p! H
但是他遇到了一个问题,他在主函数中发送循环发送内容(500ms的延时)串口一中断回调函数中如果收到了串口内容,就利用串口发送。
( y: R3 e3 ^  b9 G- d& K) j" }+ U) U
但是他的串口一旦接收到了信息就会导致主循环中的串口发送极快。他的主代码如下2 y: G9 V! g  t) ?+ {
  1. unsigned char RecieveBuffer;9 c( g6 |5 S! O; J
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); q/ r7 e$ \: ^1 x% N
  3. {
    , o0 a3 l: z! c% l
  4.   UNUSED(huart);
    6 f6 x8 H. E4 n1 P" V9 h* F
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去
    $ c$ v% d* a0 ]: x4 L
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t, }2 ]# s6 u  f6 f* ~1 n3 Y  |& x$ Y
  7. }
复制代码

1 _  A8 e2 S  ?6 [: [# Y0 s& E! o5 U看了他的代码,就这么短短的7行代码,让我汗流浃背。2 Y2 M  ?/ ^0 U4 n/ W! U
: l# ^( B: H" T- _- H- W) @: m
他的发送和接收中有一个参数是8,这意味着当串口接收的寄存器中有8个数据时就会触发中断回调。/ ~" o- O, ^' u
- A, n: ?1 ]* j/ B. J
我们需要注意的是,串口寄存器的数据会被存储到缓存区中,这里的缓存区相当于一个队列先进先出,当我们调用HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8);
& T5 t; r* c% J2 G" S
  e& Q4 Q0 [; w0 v& d
会将RecieveBuffer作为缓存区,将串口接收的信息存入缓存区。$ t4 E# E* j+ X" o6 _/ U9 L) ]  K9 J

% H% E- L1 v) W7 t& r
那么让我们来试一下这段代码。
8 r1 K2 Z* F9 W/ t8 ^/ f
) `8 O8 K% U# S
微信图片_20240529195314.png
' ^9 L( c: \1 b+ R* m, v( Z

4 X+ M- e8 n( v; \可以看到,代码死住了。其实不难理解,我们发送数据的时候,由于缓存区只有一个字符的空间,我们发送八个字符的时候会导致缓存区溢出。所以我朋友的代码可以“正常运行”也是一件非常奇怪的事情。
8 `3 }3 c3 ~2 S, `& f7 Q3 D/ _
/ T/ p) U) Z. A# w) B所以正确的做法应该是:设置和缓存区一样长的读取字符。
! n' W6 [" f  z% f7 V* o2 O* i9 O- u, b; _2 v# ~
  1. unsigned char RecieveBuffer[8];
    7 N" ~3 K+ J; c+ Y! p6 F
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    . R" S, V* K: Y: Q, h' c  }+ t9 j
  3. {
    4 F4 U  s' r" J! m; u; z4 o" Q
  4.   UNUSED(huart);# W* ^. P, a8 b6 E7 q0 b
  5.   HAL_UART_Transmit(&huart1,&RecieveBuffer,8 ,100); // 将接收到的数据再通过串口发送出去  P' b" n: d4 M3 Q0 z4 k
  6.   HAL_UART_Receive_IT(&huart1,&RecieveBuffer,8); //使能接收中断,RecieveBuffer的类型是uint8_t, \* Q; y# P, J
  7. }
复制代码

! h1 B; O/ a8 d. k这样子就可以实现每接收8个字符就调用中断一次
( k" V. H+ [4 `& `5 J1 A
, Z2 ?& u* X/ L3 z) l
微信图片_20240529195311.png
1 D4 z+ j4 l# m5 i; r+ k
; Y% ^8 Q; A2 d& n! a6 p; o2 U( j
可以看到,我们正常的发送了8个字符。
) L" ]0 E$ v9 j% _7 _  e/ t. D( Y- V* E2 C& x
但是这样子有一个非常非常致命的缺点!
# v3 @4 N2 u& y+ W% _, A* J" b0 o+ s5 u
由于我们的代码每8个才会接收一次,所以当我们的发送的数量小于8时,必须发送多次才能会触发一次调用。
! c: S, m2 i* x( @; a: D8 |  I/ |! l* ?# j: z8 p
注意,这里的每次我们都是先发送再接收,因为初始化的时候是启用了接收。" E, c9 k0 i: U! I# J

" C5 m' L# q$ B2 p
微信图片_20240529195307.png " R6 p9 ]7 L7 c( m# r+ {# [0 v

7 `3 f) U) e( l8 h当我们发送3次“123”时才能让接收的字符>=8,我们把前八个发送了出去,此时缓存区剩下了一个字符是“3”# Q8 |6 ]& |$ s

8 @. }9 x& S: b% g% g, }0 I1 L! `
微信图片_20240529195305.png
1 D: R$ P. F7 L7 v( \0 X; @* q

0 w7 H4 G/ `" \第二次我们发送了三次“123123123”,再次让字符数量>=8,这时候把缓存区的八个“31231231”发送出去再接收后面的“23”,这时候缓存区的数据为:"23 "。. H  k  _$ {6 U4 K& Q

) N0 `% y  K" b0 I+ n$ f- @* u. O
微信图片_20240529195302.png
4 n% d( v  P5 n, D8 }) x
+ w; g. m* v5 ]
最后我们第三次发送“123”的时候,由于23 + 123 + 123 这时候,虽然这里按理来说正好八个,但是实际测下来,当这时候接收的时候系统就会卡死。
+ M2 `! ?' p" X4 i( }9 R/ C# a1 t2 d$ P( N$ u$ r# Q+ }: S
所以,实际上这种方法必须保证发送端的数据是完完整整的八个八个发送,多一个少一个都不行。% h1 ?: E9 f2 f9 \1 {4 K
! O" H4 v2 Z! I* j& n
3 U6 g! B3 d. I" X# g4 T' _
解法. N6 J" ?7 q  r# c6 t, c7 W
事实上最标准的做法是单个字符单个字符处理。定义一个大的缓存区,用来存储接收到的字符并且确立一个结束符来确定数据流的结束。
  1. if((USART_RX_STA&0x8000)==0)//接收未完成0 p6 i/ [1 b6 T7 s
  2. {
    " l1 |3 K/ o) o: w1 Q, h. g
  3.   if(USART_RX_STA&0x4000)//接收到了0x0d
    . Z& a5 A- u! U$ t" g( C% ?' Y
  4.   {
    6 c5 a; {% l0 B) l. ~- v5 H* }6 V
  5.     if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始- p6 g) d; m* V$ Z$ \
  6.     else USART_RX_STA|=0x8000;  //接收完成了 3 E' j/ M- z% H/ c. J
  7.   }" M9 ?8 ^9 t$ R7 f' a
  8.   else //还没收到0X0D
    2 m' J6 ]6 [/ e
  9.   {  . q- s8 ?9 s  [
  10.     if(Res==0x0d)USART_RX_STA|=0x4000;1 k4 F5 O* ?$ [% h2 o3 x
  11.     else
    : `" h& D5 R2 ]" J0 a, O: Q
  12.     {
    . t+ H# b8 n9 L) _; f/ a! x
  13.       USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
    - T+ N2 u. `) k- W- \  _' _& g' L
  14.       USART_RX_STA++;5 b) `1 P0 Q" r, R) p
  15.       if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收    + |. q5 ^7 P6 I  V
  16.     }     1 f5 H- M, _% T+ v
  17.   }
    ; r. ?  [5 g# m" O+ M+ v8 [3 R$ g
  18. }
复制代码
: Z' ~2 }3 }* T! s' r

1 J2 j+ ^6 L8 [) m
& W' Z" l% v: o; ?! w2 x
上述是正点原子的官方例程,其中USART_RX_STA是接收到的字符,即是确定一个结束符\r\n来作为一串字符的结尾并且检测长度是否超出长度。
/ `! w0 j2 }) \  v7 e5 ]6 p

1 H  g- C, @/ ]2 c4 X- B& @) B9 L这样子的代码容错率非常高,也确定了一个规范,并且避免了缓存区溢出的情况。
  m8 z( K* d5 Y/ W! ]
! o5 J2 Z# D2 g9 S/ Y6 |- b7 }
/ H7 {1 N& l4 n+ b: o8 \转载自:电路小白2 J7 q' }  f0 Y+ m! w% s9 [: H+ _& M; V
如有侵权请联系删除
6 ]4 v. @7 E+ x) j9 {3 l) T5 i- F" u( |$ z7 E3 G3 j8 s8 F
收藏 评论0 发布时间:2024-5-29 19:53

举报

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