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

STM32串口开发之环形缓冲区

[复制链接]
STMCU小助手 发布时间:2021-7-19 14:17
01、缓冲区
1 M5 ^+ Z( }, B4 T2 E/ w缓冲区看名字就知道,是缓冲数据用的。实现缓冲区最简单的办法时,定义多个数组,接收一包数据到数组A,就把接收数据的地址换成数组B,每个数据有个标记字节用于表示这个数组是否收到数据,收到数据是否处理完成。+ g0 M9 L/ e# m  [

6 J4 e+ n; {" Q8 a4 B$ e0 g

% Q6 ]" M. L8 g, K3 P' r上述方案是完全可行的,但有缺点:& n# T- S3 n/ ^1 U0 l8 y: `

( R4 z1 \6 [& Q# J

2 j% V7 b! e* V1 K% e* w  w+ [' ~①缓冲数据组数一定,且有多变量,代码结构不太清晰。1 @5 _) Z/ T( k( T1 \8 D: z
0 I$ b/ |# Z( M1 H0 k8 r7 l: e
8 P6 V% |* N" e5 O: ~
②接收数据长度可能大于数组大小,也可能小于数组大小。不灵活,需要接收数据很长时容易出错,且内存利用率低。$ l& U1 K% c$ `0 s. O4 D( I+ }: j
7 w' J* |3 r4 a1 ?  T: V! m: _, M6 r
/ v5 N3 i5 g- ^, W4 X3 |3 x( Q
解决这个问题的好办法是:环形缓冲区。
4 k$ x0 Z4 b" b5 n/ S9 G" C* R8 C' Z6 ?# x
  Z9 W2 Y3 G$ h! R3 s
环形缓冲区就是一个带“头指针”和“尾指针”的数组。“头指针”指向环形缓冲区中可读的数据,“尾指针”指向环形缓冲区中可写的缓冲空间。通过移动“头指针”和“尾指针”就可以实现缓冲区的数据读取和写入。在通常情况下,应用程序读取环形缓冲区的数据仅仅会影响“头指针”,而串口接收数据仅仅会影响“尾指针”。当串口接收到新的数组,则将数组保存到环形缓冲区中,同时将“尾指针”加1,以保存下一个数据;应用程序在读取数据时,“头指针”加1,以读取下一个数据。当“尾指针”超过数组大小,则“尾指针”重新指向数组的首元素,从而形成“环形缓冲区”!,有效数据区域在“头指针”和“尾指针”之间。如下图
5 E0 p: u( a  }5 J  U' X4 j 1.png * w* X" D, V* l  f
如上面说的,环形缓冲区其实就是一个数组,将其“剪开”,然后“拉直”后如下图
$ D# g# @0 i* W. ?
7 Q% }* P9 C  }1 |7 C) n

1 ~7 C7 b  H/ b* E& Y5 s 2.png
5 ~  s4 G9 F0 F0 E5 u. i8 D, k( b! E+ i- {8 J" n

; I! N8 ~1 ~* w+ v5 e* g环形缓冲区的特性7 z- u6 {0 G$ n0 A3 q# G
* {: b0 C% o- y* V9 {6 w
0 y/ x  I6 }9 y$ r1 i! t: R# p# V
1、先进新出。4 x' L* z  M+ f% _) ~5 {  p; w
0 H# y# _! P! {2 g
" h/ B9 Z# ]9 l1 M
2、当缓冲区被使用完,且又有新的数据需要存储时,丢掉历史最久的数据,保存最新数据。
$ X4 s3 \* s: v) u- N, D4 n5 [
) }. B; ~+ B% i# e" J" p
' \) V" T5 w8 c! @6 L) D+ ^1 \9 w
02、代码实现7 ]; j% A0 }& D& e
环形缓冲区的实现很简单,只需要简单的几个接口即可。
3 `7 B5 r- e/ e; I5 h) c$ P; ]. F- q8 ?0 a

  y+ ~# O* C0 {- z: w0 A首先需要创建一个环形缓冲区# F- z+ t: I" n5 y
  1. <font face="微软雅黑" size="3">#define  RINGBUFF_LEN          (500)     //定义最大接收字节数 500
    3 K6 w- Z( y7 l
  2. #define  RINGBUFF_OK           1     0 o1 t' ?. D5 c- b- V3 V  v
  3. #define  RINGBUFF_ERR          0   : x& ?7 D& ]$ f1 H' Q: m
  4. typedef struct! V" }3 Y- m4 P- J1 _  K, r
  5. {: ^" @3 e' q. T- E9 S1 r
  6.     uint16_t Head;           
    ; `: o0 D; |" @. `
  7.     uint16_t Tail;
      M, r0 @. E: Q
  8.     uint16_t Lenght;
    : K3 G2 X+ t- ?' p9 }9 a
  9.     uint8_t  Ring_data[RINGBUFF_LEN];( a- R; l3 ~& a+ c
  10. }RingBuff_t;
    2 t2 v4 E7 ?8 C% @2 [( m* m
  11. RingBuff_t ringBuff;//创建一个ringBuff的缓冲区, z! {4 d* F8 Q+ @# _
  12. </font>
复制代码
当我们发现环形缓冲区被“冲爆”时,也就是缓冲区满了,但是还有待缓冲的数据时,只需要修改RINGBUFF_LEN的宏定义,增大缓冲区间即可。% U7 S' u- L) J" y& h5 d

, N6 Y; x; d( k! M/ ~

: r# z* m0 a) ]7 W# f$ n环形缓冲区的初始化9 V' U) V/ J4 E2 ]
  1. <font face="微软雅黑" size="3">/**$ N8 x& ]8 `8 E3 ?! G/ h' H
  2. * @brief  RingBuff_Init& S) E4 Q  x+ y- |1 G2 \+ A+ `
  3. * @param  void
    ) G- S) U& |. S2 T$ v
  4. * @return void
    * j- U# ~4 K7 _/ s" {
  5. * @note   初始化环形缓冲区
    4 A  P1 a2 h& f  V6 W
  6. */
    . I8 n3 v& a4 N) C6 K* X* d8 B
  7. void RingBuff_Init(void); z% c' H; y: I6 m
  8. {, E& }, T) w/ y; ]8 N$ o* V5 V
  9.   //初始化相关信息
    6 k8 P  C3 F7 A8 o: M
  10.   ringBuff.Head = 0;
    / _' C& L/ Y& d" V( S3 N
  11.   ringBuff.Tail = 0;
    , G. Y$ C# s* l  L; Q
  12.   ringBuff.Lenght = 0;* i( {3 ~, b  L, ~( H, b! x
  13. }
    5 U! P3 l0 K% r5 L; D
  14. </font>
复制代码
主要是将环形缓冲区的头,尾和长度清零,表示没有任何数据存入。
+ }4 N# y* X+ K2 k% [
0 ^0 ?) v8 ]. G* v: |; t+ u* m

/ ~" u4 f3 O- a4 g环形缓冲区的写入
, _+ C( `# ]+ R$ r6 I1 A) K: u; o4 e; C- k
  1. <font face="微软雅黑" size="3">/**8 i' ~5 B/ g+ ~
  2. * @brief  Write_RingBuff
    : f5 e2 E8 A0 A! `7 w# c
  3. * @param  uint8_t data
    ) ]9 ]0 k/ ^0 F6 [) t3 S  M
  4. * @return FLASE:环形缓冲区已满,写入失败;TRUE:写入成功/ z! ^/ M* c3 g- E- c; X
  5. * @note   往环形缓冲区写入uint8_t类型的数据  P, V+ K- W% `  m0 d
  6. */
    ; R: x. s. }3 J% H
  7. uint8_t Write_RingBuff(uint8_t data)/ s1 c! r  D" }
  8. {; |! p8 |8 V. |$ h. J$ A& J/ g
  9.   if(ringBuff.Lenght >= RINGBUFF_LEN) //判断缓冲区是否已满
    ! w1 c$ X1 S4 ]
  10.   {
    5 R! x! d. L  \. d5 \) @7 S7 _
  11.     return RINGBUFF_ERR;
    9 |+ }1 T/ h5 W0 c* m' k
  12.   }9 K1 C) h/ z9 C0 P, u
  13.   ringBuff.Ring_data[ringBuff.Tail]=data;
      `; q0 u; Q8 c. s2 h, [
  14.   ringBuff.Tail = (ringBuff.Tail+1)%RINGBUFF_LEN;//防止越界非法访问
    & F4 Y- A3 c+ u
  15.   ringBuff.Lenght++;
    7 Z/ B. Z6 a* m
  16.   return RINGBUFF_OK;
    3 r" ?+ U9 N+ Y+ z$ \+ Q
  17. }</font>
复制代码
这个接口是写入一个字节到环形缓冲区。这里注意:大家可以根据自己的实际应用修改为一次缓冲多个字节。并且这个做了缓冲区满时报错且防止非法越界的处理,大家可以自行修改为缓冲区满时覆盖最早的数据。( ?: z3 M" ^1 a8 i0 U
2 H& g9 X2 t5 _: `  V

: N4 w8 b3 v7 q  F! T环形缓冲区的读取/ N0 l5 O& K+ l6 P+ c- `3 R
# b9 c' v. y5 X8 G
  1. <font face="微软雅黑" size="3">
    + {: u; Q4 E/ `
  2. /**
    2 b6 O8 j) K% A( n/ k7 z' p
  3. * @brief  Read_RingBuff% B- V- X4 K! A$ i  n
  4. * @param  uint8_t *rData,用于保存读取的数据
    # F" Q6 A+ x! g+ d8 {
  5. * @return FLASE:环形缓冲区没有数据,读取失败;TRUE:读取成功
    % b+ F7 X& M/ K
  6. * @note   从环形缓冲区读取一个u8类型的数据
    + \3 ?& c' }3 k+ ^; I8 {' Y
  7. */
    ' y& a( Q$ R1 M" U5 D' g
  8. uint8_t Read_RingBuff(uint8_t *rData)7 O! L9 W" B& {/ R: G8 I$ m
  9. {! h" }! U  |/ v+ D
  10.   if(ringBuff.Lenght == 0)//判断非空3 o" A8 D% y4 M
  11.   {& t6 O9 M2 o/ Q3 g
  12.     return RINGBUFF_ERR;: ?6 o4 Y8 g7 V9 M+ H, `
  13.   }( O! {: a: s2 x& I2 Q: q
  14.   *rData = ringBuff.Ring_data[ringBuff.Head];//先进先出FIFO,从缓冲区头出
    ( l. O0 j: y6 U
  15.   ringBuff.Head = (ringBuff.Head+1)%RINGBUFF_LEN;//防止越界非法访问
    % ?/ Z: M1 i% @/ S) Y+ S# c
  16.   ringBuff.Lenght--;
    - i6 t% ^2 B# h0 m
  17.   return RINGBUFF_OK;
    ) m" m" x* `0 x6 n- \2 [
  18. }
    . g, L9 R3 t9 @/ `
  19. </font>
复制代码
读取的话也很简单,同样是读取一个字节,大家可以自行修改为读取多个字节。( N4 T, e: K9 `

& k( I2 y+ a$ Y5 s) t) @1 b

5 b1 i) e4 k  j  h' z02、验证" Q; W* U8 ]# r
光说不练假把式,下面我们就来验证上面的代码可行性。3 n: P/ a! p9 Z; L3 b
+ r$ T1 u7 t, I8 n' K  g
7 ~+ d8 e1 V* E/ p  y- K- [: c
串口中断函数中缓冲数据9 ~; d( N7 @) M. d! h

/ d  [' s4 B. m  }
  1. <font face="微软雅黑" size="3">void USART1_IRQHandler(void)
    - H' P. x# O$ X3 K
  2. {% I5 H' i& a8 A1 b# B% O% M
  3.   if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE))' i( o$ V$ g) w2 T: U9 X
  4.   {, E0 P$ Q+ ]+ l6 X, P
  5.     Write_RingBuff(USART_ReceiveData(USART1));3 s4 l! D  M' n( m$ d8 ?- t
  6.     USART_ClearFlag(USART1, USART_FLAG_RXNE);, a' u4 t3 M# Y8 I) ^" g( u( ^
  7.   }
    : m  d9 _$ m* |* R, h. f$ y" k" t% z' |
  8. }</font>
复制代码
在主循环中,读取缓冲区的数据,然后发送出去,因为是简单的demo,添加了延时模拟CPU处理其他任务。
2 q! H- x2 P: o* W  {0 b% ]; G  U' u+ `
  1. <font face="微软雅黑" size="3">1 s$ D" V& t& |. N/ H' ?+ R
  2. while (1), m/ q9 e' U) f" D
  3.   {
    ; v' C$ r+ K' |- a) m7 X
  4.     if(Read_RingBuff(&data))            //从环形缓冲区中读取数据
    2 U( Y5 C  j2 I" @/ Z
  5.     {. _$ c( }. z0 l/ b
  6.       USART_SendData(USART1, data);# p, S+ Z0 n2 I- d/ w
  7.     }- B2 l. `$ n, n. [
  8.     SysCtlDelay(1*(SystemCoreClock/3000));
    $ A1 C' u7 M6 b# {6 Z- R
  9.   }
    $ L1 R" R8 `7 E1 A$ D5 }
  10. </font>
复制代码
验证,间隔100ms发送数据。
4 ^, X) K+ i& w$ }: n3 `' ^2 j1 Y' L" z4 [+ x: U' f

* L9 j( ^0 `4 y7 i! h4 J
8 N  U/ O; v1 F$ i& ?( `) ~- x5 t
- d( O& R% R* k- A0 n; z5 Z
  V7 ]7 Z  s6 ?; ?. O
结果显示没有出现丢包问题。如果你的应用场景串口通信速率快,数据量大或处理速度慢导致丢包,建议增大RINGBUFF_LEN的宏定义,增大缓冲区间即可。
2 `6 X' X* S/ p; [$ K' j1 R- n  B
8 S  ^( A4 |, U, l8 p

! ^; D  z$ i' T% c9 e5 I* P* zKeil和IAR的工程文件下载地址:- ~: S5 ~, D) h" y( g
[url=https://github.com/strongercjd/STM32F207VCT6]https://github.com/strongercjd/STM32F207VCT6
) R5 c5 i6 m4 A' \4 R[/url]8 ~' T8 ]- @+ z/ C" b0 O

9 e# R+ ^. E0 c! Q4 L( q8 h! Z6 [5 g8 M# s; I6 p% o1 J
收藏 评论0 发布时间:2021-7-19 14:17

举报

0个回答

所属标签

相似技术帖

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版