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

STM32串口开发之环形缓冲区

[复制链接]
STMCU小助手 发布时间:2021-7-19 14:17
01、缓冲区
0 o+ x3 I" R) P, n0 K2 y1 h  b缓冲区看名字就知道,是缓冲数据用的。实现缓冲区最简单的办法时,定义多个数组,接收一包数据到数组A,就把接收数据的地址换成数组B,每个数据有个标记字节用于表示这个数组是否收到数据,收到数据是否处理完成。. B) d5 m1 }9 E
- s4 y2 `* Q# H8 y7 Y
) ^  B; g0 @! R! ~, q3 @
上述方案是完全可行的,但有缺点:
7 [+ u( d& ]& |4 z+ N% b0 }0 f% V" L& J/ p& k/ C

+ S% _9 U+ f2 B. j  s, z①缓冲数据组数一定,且有多变量,代码结构不太清晰。
9 S, d8 I- D- F4 H
% M8 L1 @+ j: \! _% _3 V

8 x# r7 {( S9 q②接收数据长度可能大于数组大小,也可能小于数组大小。不灵活,需要接收数据很长时容易出错,且内存利用率低。
: t. T8 v9 C4 F4 K5 r! {$ r8 j' l% p: ?1 @

1 N6 p3 |; G5 H8 T- F. v解决这个问题的好办法是:环形缓冲区。
4 v% v4 l) n( z8 Z' E- x* I
! m% f7 l$ H: _# `# T
  U2 N+ r8 `. z+ v- [
环形缓冲区就是一个带“头指针”和“尾指针”的数组。“头指针”指向环形缓冲区中可读的数据,“尾指针”指向环形缓冲区中可写的缓冲空间。通过移动“头指针”和“尾指针”就可以实现缓冲区的数据读取和写入。在通常情况下,应用程序读取环形缓冲区的数据仅仅会影响“头指针”,而串口接收数据仅仅会影响“尾指针”。当串口接收到新的数组,则将数组保存到环形缓冲区中,同时将“尾指针”加1,以保存下一个数据;应用程序在读取数据时,“头指针”加1,以读取下一个数据。当“尾指针”超过数组大小,则“尾指针”重新指向数组的首元素,从而形成“环形缓冲区”!,有效数据区域在“头指针”和“尾指针”之间。如下图
9 P# }+ d+ _1 Z- m2 S' | 1.png / O3 f6 |1 p# a; X
如上面说的,环形缓冲区其实就是一个数组,将其“剪开”,然后“拉直”后如下图
' F) S( }/ S! b& ^3 c+ h! N0 k' ?5 P# n- G+ d5 u8 i3 j) V

4 l  p3 t# J" ] 2.png
9 B; g6 l0 n! p& L3 E
. X1 D: Z, R' v4 U" R. i! }4 K0 M
) v; x+ ?( J6 u1 I8 V; v
环形缓冲区的特性
) P7 }! _1 |2 \! j
1 E* e: k* \- g# h" X! Z6 b. z  a

( D- d3 V! J! N) t9 ~! t* T8 I0 `1、先进新出。
, D3 _6 G( x# i$ u7 G8 Q8 M& g* V
- [+ A2 X/ ]# ]

5 c0 [, F+ E( w3 Y& i2、当缓冲区被使用完,且又有新的数据需要存储时,丢掉历史最久的数据,保存最新数据。
% ?% [6 y6 O# [9 J# o, o3 x& W  r( o
, S& d7 p+ s2 S- M5 }* X
02、代码实现  F" H: f* k0 ~3 ]  ?0 X  w! H
环形缓冲区的实现很简单,只需要简单的几个接口即可。. q! P2 k+ J3 Y6 l
1 {; d( g3 _' P$ u

8 M) [7 b* K9 T9 I6 W首先需要创建一个环形缓冲区6 Q3 M( e3 B. D5 [6 N1 S( C# t
  1. <font face="微软雅黑" size="3">#define  RINGBUFF_LEN          (500)     //定义最大接收字节数 500
    7 g+ x0 a" ~% P% y
  2. #define  RINGBUFF_OK           1     ' |; j" a1 g6 e- i5 J( `! l  b
  3. #define  RINGBUFF_ERR          0   $ c5 Y: ~( T1 u. I0 C+ g
  4. typedef struct
    4 @6 I: |2 f) f6 [, N: e. x
  5. {
    9 ~  n- j% b" g( L7 ?; ^
  6.     uint16_t Head;           " F: Z, K5 f/ d6 l+ e9 y
  7.     uint16_t Tail;
    & w; b/ J; B9 o7 j+ O
  8.     uint16_t Lenght;
    : ~% e0 X0 G4 n. m+ P  S+ T
  9.     uint8_t  Ring_data[RINGBUFF_LEN];" I/ Q' U. u/ x* H3 `/ u! b) |, f+ [
  10. }RingBuff_t;
    # f5 L6 t5 a; U+ ^. y) m
  11. RingBuff_t ringBuff;//创建一个ringBuff的缓冲区
    * N0 u7 h* a, l. l) U0 T
  12. </font>
复制代码
当我们发现环形缓冲区被“冲爆”时,也就是缓冲区满了,但是还有待缓冲的数据时,只需要修改RINGBUFF_LEN的宏定义,增大缓冲区间即可。- o& y) _# l0 h4 X. l
- d0 G5 m% s$ v" C6 h/ a3 A% f! z

1 S* o. `+ i- e# r) k! t+ W环形缓冲区的初始化1 h8 ~5 N3 P. ^8 g' f  x, q
  1. <font face="微软雅黑" size="3">/**( E# u( x) r: T) r9 o
  2. * @brief  RingBuff_Init- y$ U- K! r) T
  3. * @param  void
    9 U: I. a3 ~6 z4 S/ l2 K* P
  4. * @return void9 u; }, L+ F, |  n$ N4 j
  5. * @note   初始化环形缓冲区
    ( q& T* s/ n9 @8 v8 v6 {
  6. */
    * l3 H. \0 ?- S
  7. void RingBuff_Init(void)* s# K0 i; g# J  C6 s) j! q' T+ e
  8. {
    : R1 D9 B, R% [# k7 b' K/ ~  G% j
  9.   //初始化相关信息
    9 R0 Y8 F0 P8 n! z) w$ f+ J" K
  10.   ringBuff.Head = 0;
    ' B' x  U$ I, C) t, X
  11.   ringBuff.Tail = 0;2 |# I, O+ o" n5 p2 F! e
  12.   ringBuff.Lenght = 0;
    % y& P6 p( r. o" `4 j- R$ r
  13. }
    ! S: Y1 q: W4 m1 C, A4 X& R: x! X! _/ q# d
  14. </font>
复制代码
主要是将环形缓冲区的头,尾和长度清零,表示没有任何数据存入。
/ v; G( o6 h" P8 @8 _  B0 _& Q
0 P/ D; S, G: l4 ]* P

: |7 B1 u2 Q+ Z3 `* v8 q- ^环形缓冲区的写入
4 E* t3 ~2 r! [3 e
# g7 S9 L. Y# d) p6 o- b
  1. <font face="微软雅黑" size="3">/**
    7 T- x  c6 e2 \) x; A: V; x- H
  2. * @brief  Write_RingBuff
    ; r* G5 w9 ^7 o. ?( M1 V
  3. * @param  uint8_t data
    , U; y8 M0 m* m. S
  4. * @return FLASE:环形缓冲区已满,写入失败;TRUE:写入成功/ `/ w- ~2 A: K" y4 ~( A  X
  5. * @note   往环形缓冲区写入uint8_t类型的数据
    + Y/ m) _6 o! e0 A' K% b7 S
  6. */" b" ^$ ]) W! j! P
  7. uint8_t Write_RingBuff(uint8_t data)' T! q' }6 X: N0 O  }9 Y5 Z
  8. {" r; C2 q! f8 ~1 Z/ B9 g
  9.   if(ringBuff.Lenght >= RINGBUFF_LEN) //判断缓冲区是否已满
    + Z+ y/ q1 ?: D8 c- W- a
  10.   {: v* `' e  V& Y$ M6 T2 I4 J# o
  11.     return RINGBUFF_ERR;
    ; z2 p: r6 F7 X& I( j
  12.   }
    * V2 O3 O  ?3 X1 B4 k
  13.   ringBuff.Ring_data[ringBuff.Tail]=data;
    1 b) n2 a" ]. m
  14.   ringBuff.Tail = (ringBuff.Tail+1)%RINGBUFF_LEN;//防止越界非法访问8 F1 K* m, @# ?, Q' E# A
  15.   ringBuff.Lenght++;: F6 b" y0 y: O. N. d; p! o
  16.   return RINGBUFF_OK;- v  M. O! j# b. H7 Y5 O( J$ F" B( {
  17. }</font>
复制代码
这个接口是写入一个字节到环形缓冲区。这里注意:大家可以根据自己的实际应用修改为一次缓冲多个字节。并且这个做了缓冲区满时报错且防止非法越界的处理,大家可以自行修改为缓冲区满时覆盖最早的数据。! H8 e* B  X; F4 T# D; q/ L* d
& a4 Z* ]+ x( N2 {6 |  P
- F8 X+ T$ L: m& q3 Y2 [- H( E
环形缓冲区的读取/ c4 S3 d0 d  ^

- b: d" z2 x% [2 s5 I" w
  1. <font face="微软雅黑" size="3">
    / n1 [, [! u% w$ I
  2. /**
    / a+ v9 W- q; v* g# `
  3. * @brief  Read_RingBuff! z2 s0 v4 o2 ~( O
  4. * @param  uint8_t *rData,用于保存读取的数据: ~+ }' A& K; y; _$ \5 G
  5. * @return FLASE:环形缓冲区没有数据,读取失败;TRUE:读取成功/ }& {5 d" y& X
  6. * @note   从环形缓冲区读取一个u8类型的数据
    ' Q( f5 q* Y  ]6 P1 C
  7. */: i& t/ T2 p& ?1 a: ?
  8. uint8_t Read_RingBuff(uint8_t *rData)
    . q4 A- r8 c/ ~: _) Q
  9. {
    6 ^5 P* u# C" d6 r$ u
  10.   if(ringBuff.Lenght == 0)//判断非空
    ( [  e- g% @0 [. o5 e4 I
  11.   {( \9 x2 v, y. q3 W
  12.     return RINGBUFF_ERR;4 q2 \* o6 c) ~6 o6 y
  13.   }6 G5 O6 d# d. o5 F8 d. M
  14.   *rData = ringBuff.Ring_data[ringBuff.Head];//先进先出FIFO,从缓冲区头出
    6 I  o6 [, i% K' u: e# b
  15.   ringBuff.Head = (ringBuff.Head+1)%RINGBUFF_LEN;//防止越界非法访问9 `6 y2 X$ |7 d6 e
  16.   ringBuff.Lenght--;
      |4 q8 i- A7 g' L$ B3 B( u
  17.   return RINGBUFF_OK;
    0 I4 S9 D* n% b9 Q4 f% P
  18. }+ ?3 n% n  }5 x! ?" e4 {7 r0 z# |
  19. </font>
复制代码
读取的话也很简单,同样是读取一个字节,大家可以自行修改为读取多个字节。. H* v! @, _# Y& c5 m, [
6 Z9 r. R2 D& n
5 e  v, |) ]- S  a; V
02、验证
  T* k8 c9 I, D0 h光说不练假把式,下面我们就来验证上面的代码可行性。
- {8 g- h" C0 c( H8 Z/ ?) H* p7 V4 @0 _$ J( g- V: _

: d# V" o6 p- J% z+ T: S串口中断函数中缓冲数据
/ y) m4 |8 U( ^  Q9 V0 X& k' @8 ~% a8 U2 m( e
  1. <font face="微软雅黑" size="3">void USART1_IRQHandler(void)
    7 c$ J! h5 ^& f% G" W# T' g9 b0 Z( E% n
  2. {+ W# j! m% q& ]  `2 h8 S5 ~
  3.   if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE))
    0 t  b7 R, q6 B' j/ o! R
  4.   {
    $ a' n, W) x. Q- k
  5.     Write_RingBuff(USART_ReceiveData(USART1));; b* k; I, i% l: D; I
  6.     USART_ClearFlag(USART1, USART_FLAG_RXNE);
    6 Y5 G0 J& |6 t# |; N  d
  7.   }
    % u# r3 L$ l/ M9 ^2 E
  8. }</font>
复制代码
在主循环中,读取缓冲区的数据,然后发送出去,因为是简单的demo,添加了延时模拟CPU处理其他任务。
4 D0 q1 w( a  p, i# e9 }% l* y
  1. <font face="微软雅黑" size="3">
    1 {* d9 I" U1 r3 w; p& k
  2. while (1)
    8 X9 u; t# p$ t4 t1 L. h7 h3 S
  3.   {
    # m+ h4 l: b) O* x1 |
  4.     if(Read_RingBuff(&data))            //从环形缓冲区中读取数据$ [2 f& b% F4 F$ F+ {
  5.     {0 P4 w- }' ^& J
  6.       USART_SendData(USART1, data);
    2 i" ^- V: {: R+ A8 B
  7.     }
    5 S: j: g" I9 D# r. L
  8.     SysCtlDelay(1*(SystemCoreClock/3000));
    8 a6 v9 x7 \" p0 S
  9.   }
    + z2 b- M# c7 H  K, h5 {: K: b
  10. </font>
复制代码
验证,间隔100ms发送数据。- O9 G  p5 S- a/ _

) E" T2 k' g5 Z; H1 ?( Y, Q
* r2 p+ ~6 `6 b1 O" _7 T. G( L& c

, v  Q4 d- W/ O4 T5 H. z
* h. p- |0 a7 j- @5 {

( r' ~! @+ t2 I/ s+ s5 q: d* G结果显示没有出现丢包问题。如果你的应用场景串口通信速率快,数据量大或处理速度慢导致丢包,建议增大RINGBUFF_LEN的宏定义,增大缓冲区间即可。
6 V8 E1 z% M( ?& R/ j
& p& x. Z: E! m" n: [

! b) b, C5 ~- e5 [Keil和IAR的工程文件下载地址:3 E5 |% p' S: D, A( s# h5 q
[url=https://github.com/strongercjd/STM32F207VCT6]https://github.com/strongercjd/STM32F207VCT64 |# s& k" u) ~, F' }6 t" F1 y1 ]
[/url]) R( b$ N3 A  d! x8 c* `5 m
  Z  {" T8 q6 t: n
' u' W) ^* ~3 A2 j4 k- @" Y5 s
赞 收藏 评论0 发布时间:2021-7-19 14:17

举报

0个回答

所属标签

相似技术帖

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