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

STM32串口开发之环形缓冲区

[复制链接]
STMCU小助手 发布时间:2021-7-19 14:17
01、缓冲区
+ W+ J) x" ~- I7 p0 [: @& j, Z缓冲区看名字就知道,是缓冲数据用的。实现缓冲区最简单的办法时,定义多个数组,接收一包数据到数组A,就把接收数据的地址换成数组B,每个数据有个标记字节用于表示这个数组是否收到数据,收到数据是否处理完成。5 |" J5 i( r, T7 T6 [2 \

& h5 K! }# p4 |; \0 ~

  `. b) Z/ `' W4 q  c, y上述方案是完全可行的,但有缺点:
$ `0 A' M, ^/ ~4 o  l) F& j3 u5 f; o) W% K6 ~5 G$ h
$ P0 R5 e- u8 o
①缓冲数据组数一定,且有多变量,代码结构不太清晰。- t  W6 M! w! Y1 u

- S' K  L5 _* f2 B2 g& `1 n9 J

$ s& f& o! f+ I2 _②接收数据长度可能大于数组大小,也可能小于数组大小。不灵活,需要接收数据很长时容易出错,且内存利用率低。
8 U6 n3 K5 [* w3 O7 T4 r. V$ F; e3 }1 z. ]; m- O# J% k& V/ d" A+ I

6 j& W# ~% z0 H) A2 v. u& X解决这个问题的好办法是:环形缓冲区。
+ J( x* z! O% E4 T  P+ ]2 Q
3 |! H4 k' Z8 i: J4 M* x4 e

1 `6 O* t# Q& z6 O8 E- ~环形缓冲区就是一个带“头指针”和“尾指针”的数组。“头指针”指向环形缓冲区中可读的数据,“尾指针”指向环形缓冲区中可写的缓冲空间。通过移动“头指针”和“尾指针”就可以实现缓冲区的数据读取和写入。在通常情况下,应用程序读取环形缓冲区的数据仅仅会影响“头指针”,而串口接收数据仅仅会影响“尾指针”。当串口接收到新的数组,则将数组保存到环形缓冲区中,同时将“尾指针”加1,以保存下一个数据;应用程序在读取数据时,“头指针”加1,以读取下一个数据。当“尾指针”超过数组大小,则“尾指针”重新指向数组的首元素,从而形成“环形缓冲区”!,有效数据区域在“头指针”和“尾指针”之间。如下图; \( A0 J) g' G: Q) |
1.png : X& N3 S- s7 t1 _" G+ \2 e
如上面说的,环形缓冲区其实就是一个数组,将其“剪开”,然后“拉直”后如下图
5 {0 u0 |! D. R  a: y7 d! e1 ~% a6 Y, W) n) F

5 S! D0 A. n  k0 r5 W 2.png " R6 x6 w: A1 G$ M* t$ |8 I

3 ]+ n2 l1 @3 Y4 d  e

& U; S  T! ?- Q+ ~环形缓冲区的特性
. @1 X- {7 u* d! @/ R. ^
& s* r8 b! k' m3 I% x& o$ ^+ v. L
  S; z! {# v4 \- D$ Z# s
1、先进新出。
. n8 D! F7 Q4 O% H
+ m3 a' l2 J6 n' r. Y, i

( b! ^7 w& h9 H! T" L3 F: t( j2、当缓冲区被使用完,且又有新的数据需要存储时,丢掉历史最久的数据,保存最新数据。
) J; p! ]/ c- |5 F% c' X  f7 n7 Q: j. F5 _' F
+ U, y9 k) z& D
02、代码实现
: {, i  |# c9 Z# c  [环形缓冲区的实现很简单,只需要简单的几个接口即可。
2 {/ g2 s9 A( b1 w! x. n2 n4 W" ?4 I) [9 k+ ~$ Z7 Y
8 q" C5 D+ }, o9 y1 _, I
首先需要创建一个环形缓冲区! B1 K: ]5 U  k4 e0 k& i
  1. <font face="微软雅黑" size="3">#define  RINGBUFF_LEN          (500)     //定义最大接收字节数 500
    6 V) Y6 Y; T! s. b6 p' k' n* I
  2. #define  RINGBUFF_OK           1     1 ?$ m1 D/ u$ ^- j
  3. #define  RINGBUFF_ERR          0   
      ~( J6 Q8 a: m1 x& ]
  4. typedef struct' M6 V6 j8 H# e* C3 N( P3 X
  5. {, j- `4 o- ]: p8 {. i+ C
  6.     uint16_t Head;           6 O! `& ^& p. c
  7.     uint16_t Tail;6 F/ N7 r! z4 f* e- g
  8.     uint16_t Lenght;
    ( n$ D. B* R" ]1 G- \+ u- c" ]$ j$ J1 H
  9.     uint8_t  Ring_data[RINGBUFF_LEN];
    ! f5 i9 N& h; D. Q' E& l
  10. }RingBuff_t;/ R# ~7 ^* S3 s( A" `5 L, Z
  11. RingBuff_t ringBuff;//创建一个ringBuff的缓冲区; Y; {# U* p" C' C
  12. </font>
复制代码
当我们发现环形缓冲区被“冲爆”时,也就是缓冲区满了,但是还有待缓冲的数据时,只需要修改RINGBUFF_LEN的宏定义,增大缓冲区间即可。! v$ h) O# V: h) A/ T3 f

* i* ]$ ^, L! e, v: b

9 i! f* j$ y9 N环形缓冲区的初始化
6 l$ r" [% V$ {! v6 l- f, f' [
  1. <font face="微软雅黑" size="3">/**/ e: A6 b; c& n( ~3 J, J
  2. * @brief  RingBuff_Init+ Q& z; i8 i- e1 D+ |# Y
  3. * @param  void) I; |0 t- r' W, K! W& C; w1 a8 p
  4. * @return void4 t- @5 ?7 ^* m" f4 k
  5. * @note   初始化环形缓冲区# ]2 R0 X, [" Y! n+ B
  6. */( t4 X$ k- s- g, Q2 `7 Y7 T2 E9 t5 x
  7. void RingBuff_Init(void)
    & \  W, a. r; {" _1 f
  8. {
    # V' z  m( h4 }5 x' \
  9.   //初始化相关信息
      q# D! e5 o8 K7 w1 Q, `0 |
  10.   ringBuff.Head = 0;
    4 n3 B2 y, Y. ?5 s4 Y5 F; l
  11.   ringBuff.Tail = 0;! H& h1 t7 Y3 _$ L/ g/ o) C
  12.   ringBuff.Lenght = 0;; P3 [9 @- Q1 r" X
  13. }/ x: y) {6 P' W) L; y
  14. </font>
复制代码
主要是将环形缓冲区的头,尾和长度清零,表示没有任何数据存入。5 e8 P- b% M# G& ]4 V" l0 o
5 Y; Z% U* R5 X4 S* y! ~: c

4 {  }8 E! N# c+ k  A& J: o# p环形缓冲区的写入
! J" \2 m9 X& P# l; o" D; {4 |; G6 K
  1. <font face="微软雅黑" size="3">/**
    6 F1 I2 Z0 g+ F9 T4 i% H. o
  2. * @brief  Write_RingBuff# D# N8 Q3 O% ~- v+ G" }
  3. * @param  uint8_t data
    * H  i- r% h8 |) A4 u( y* k' b
  4. * @return FLASE:环形缓冲区已满,写入失败;TRUE:写入成功
    . y2 b9 ~: [1 T3 N& ~9 f, F" I- `
  5. * @note   往环形缓冲区写入uint8_t类型的数据6 [% W2 V9 d- }! ^1 L
  6. */
    - f) x: H7 J1 _) K& K
  7. uint8_t Write_RingBuff(uint8_t data)
    " i" D; A9 Y4 Y& ?- S4 l3 u2 p0 z
  8. {1 Y0 Q) f* ~- @* s, S2 o' ?$ ?
  9.   if(ringBuff.Lenght >= RINGBUFF_LEN) //判断缓冲区是否已满1 |- M4 M, k6 Q8 w7 n7 e' S
  10.   {
    " R0 k8 J$ \4 h+ [$ h
  11.     return RINGBUFF_ERR;
    1 C5 g+ K% A* ~$ p
  12.   }6 f3 X( l2 B6 ~* U% C
  13.   ringBuff.Ring_data[ringBuff.Tail]=data;9 L/ Q' s! o! ^/ x
  14.   ringBuff.Tail = (ringBuff.Tail+1)%RINGBUFF_LEN;//防止越界非法访问
    , H2 `5 Q8 T) }& m/ [8 v4 I- c) A
  15.   ringBuff.Lenght++;
    ' y  M8 l& h2 T7 O
  16.   return RINGBUFF_OK;
    ! J% U3 ]) g' |3 z' W' C7 z1 r
  17. }</font>
复制代码
这个接口是写入一个字节到环形缓冲区。这里注意:大家可以根据自己的实际应用修改为一次缓冲多个字节。并且这个做了缓冲区满时报错且防止非法越界的处理,大家可以自行修改为缓冲区满时覆盖最早的数据。* n. R2 {1 G7 g- H' x) u* B0 H

& h9 r$ Y, z( }- @1 C
# B7 z0 k* R2 H( E2 L9 n
环形缓冲区的读取
7 b  c4 r. P7 ^. W
% I3 R9 Z% f1 ]( k& e
  1. <font face="微软雅黑" size="3">/ N& h4 O- o0 N: H* D
  2. /**: F3 A% M% l4 Y) u
  3. * @brief  Read_RingBuff
    * o; W  I5 l- H  E& M9 v5 a/ v5 w/ X
  4. * @param  uint8_t *rData,用于保存读取的数据
    0 c( j, A' Q% R  Q
  5. * @return FLASE:环形缓冲区没有数据,读取失败;TRUE:读取成功1 o  Q2 k) m& G- ~5 U4 x! `8 I% ^
  6. * @note   从环形缓冲区读取一个u8类型的数据  I* u) t& P% R+ c6 q! T
  7. */. P- Y1 H" s5 `: F4 y# a4 y" y
  8. uint8_t Read_RingBuff(uint8_t *rData)% X/ ^; |; F3 q/ |3 m, m
  9. {  N+ W$ ^# Y5 }" j3 i
  10.   if(ringBuff.Lenght == 0)//判断非空  H0 U7 j) a7 R
  11.   {; `. Z4 N! t' D9 g- U+ v
  12.     return RINGBUFF_ERR;
    1 k+ F& f2 E1 p4 d2 k% U
  13.   }- h# k- T' V- V6 U: ^, V8 j
  14.   *rData = ringBuff.Ring_data[ringBuff.Head];//先进先出FIFO,从缓冲区头出
    ; L& V0 D5 b2 ?3 a0 f  F
  15.   ringBuff.Head = (ringBuff.Head+1)%RINGBUFF_LEN;//防止越界非法访问
    , o4 b" F# K, B! c
  16.   ringBuff.Lenght--;
    1 I  X. X# x4 c
  17.   return RINGBUFF_OK;. `5 p6 K4 h0 w! Y  c' }- n
  18. }& Z9 @& R3 E: f% t5 \2 j/ [, Q
  19. </font>
复制代码
读取的话也很简单,同样是读取一个字节,大家可以自行修改为读取多个字节。" z) D% @$ ^5 p" X3 b; E. Q( ^

$ a5 z6 B$ u. ^; N% r% C

: c2 |0 P' N+ n% M, u* t02、验证0 v& k0 i; W6 F+ ^7 \
光说不练假把式,下面我们就来验证上面的代码可行性。" y$ w& |' W$ t" |9 A

: Y' `' w7 {5 L# B
7 h  g7 k9 R, u! w1 H6 X( }
串口中断函数中缓冲数据
! V5 s' k- b5 ~0 A$ [# p1 ?6 [" L* U- @  h) u( M- u
  1. <font face="微软雅黑" size="3">void USART1_IRQHandler(void)
      c. z6 C- m. c9 h: I$ ^
  2. {
    . c6 H* m6 F. j) k, c% T
  3.   if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE))
    7 }8 l7 D' _% L$ \% y6 ~' i
  4.   {
    % {+ S+ e$ n1 L$ j
  5.     Write_RingBuff(USART_ReceiveData(USART1));
    4 z/ t* u9 z, W5 B7 Q  y' S
  6.     USART_ClearFlag(USART1, USART_FLAG_RXNE);
    * Z6 k" X9 i1 \3 P5 q: y
  7.   }
    ! D0 ]; ]& ^  w$ V2 n+ \8 E
  8. }</font>
复制代码
在主循环中,读取缓冲区的数据,然后发送出去,因为是简单的demo,添加了延时模拟CPU处理其他任务。
7 X5 h: S- Z% v. S( U
  1. <font face="微软雅黑" size="3">- m. V2 Y/ H  q5 X% P( E+ p+ R
  2. while (1)% e# ?; a6 E, c' P
  3.   {
    + f% ?7 s" u  K& {2 T
  4.     if(Read_RingBuff(&data))            //从环形缓冲区中读取数据
    / {  s- t' E. w/ c- K( u7 Z
  5.     {
    # z+ g/ Z! q( j7 O/ W+ p. b. z
  6.       USART_SendData(USART1, data);
    / g  h( n: K& e( a+ u' d
  7.     }
    $ C, w3 r7 ~% ?
  8.     SysCtlDelay(1*(SystemCoreClock/3000));7 d, c" j% f0 M! I$ L: d
  9.   }) N4 w; l+ d) F6 V8 S
  10. </font>
复制代码
验证,间隔100ms发送数据。
) A3 R( v- w, h- g* i  l
, r& K/ f4 q' m( B9 ^
; @3 h0 T5 y5 K1 }( X5 x! e

  H0 g  [, i' R/ D, @3 k9 K$ {0 B- E1 e- I+ @, D

% r+ h5 m+ [8 |  l结果显示没有出现丢包问题。如果你的应用场景串口通信速率快,数据量大或处理速度慢导致丢包,建议增大RINGBUFF_LEN的宏定义,增大缓冲区间即可。
6 F. A' A- ]9 k$ a/ n; J/ M, U) T. J( i
; w+ l8 L3 ]! V( n5 M# x& s
- C2 k6 k/ e6 ]' \5 j. J) S' \
Keil和IAR的工程文件下载地址:0 K9 j( h8 Y$ W: i* ^% D
[url=https://github.com/strongercjd/STM32F207VCT6]https://github.com/strongercjd/STM32F207VCT6" ?9 N* v, P3 G% S' Z' J
[/url]
+ }; o" A$ v6 a" ?
, }) V3 N6 i) X" o$ h$ x& j1 p7 G
( k+ R- @. S3 c# E0 k8 S  X* M, c
收藏 评论0 发布时间:2021-7-19 14:17

举报

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