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

STM32串口开发之环形缓冲区

[复制链接]
STMCU小助手 发布时间:2021-7-19 14:17
01、缓冲区1 J- h/ e# ]6 z' T5 W
缓冲区看名字就知道,是缓冲数据用的。实现缓冲区最简单的办法时,定义多个数组,接收一包数据到数组A,就把接收数据的地址换成数组B,每个数据有个标记字节用于表示这个数组是否收到数据,收到数据是否处理完成。
: G8 ]' r0 G7 K, d6 U, H. p! D1 u% w7 ^8 h/ t( f$ ^1 n) u

7 ?% ~; r. R8 E上述方案是完全可行的,但有缺点:
1 c$ I# m" F; T, j" P  k9 ]2 @7 V: D- [! h1 z! ^

0 P! [) l! b3 B9 t  w/ F/ I①缓冲数据组数一定,且有多变量,代码结构不太清晰。7 g) r# P# j1 f% e3 [# N
* R0 D- }$ N9 l0 W# G+ \$ O

0 ~% X6 y# o* @! K②接收数据长度可能大于数组大小,也可能小于数组大小。不灵活,需要接收数据很长时容易出错,且内存利用率低。, G4 r- Z0 V4 Q7 |- _( J4 {
; p5 \3 h) F% b9 c$ _) W: ~

- W3 ~5 {+ i; M% K5 T' z' T$ _解决这个问题的好办法是:环形缓冲区。
* Q4 u4 D% N7 I) d2 V
! J( M" g3 j. K7 {

" }6 \( ~  ^* `& J1 w$ c环形缓冲区就是一个带“头指针”和“尾指针”的数组。“头指针”指向环形缓冲区中可读的数据,“尾指针”指向环形缓冲区中可写的缓冲空间。通过移动“头指针”和“尾指针”就可以实现缓冲区的数据读取和写入。在通常情况下,应用程序读取环形缓冲区的数据仅仅会影响“头指针”,而串口接收数据仅仅会影响“尾指针”。当串口接收到新的数组,则将数组保存到环形缓冲区中,同时将“尾指针”加1,以保存下一个数据;应用程序在读取数据时,“头指针”加1,以读取下一个数据。当“尾指针”超过数组大小,则“尾指针”重新指向数组的首元素,从而形成“环形缓冲区”!,有效数据区域在“头指针”和“尾指针”之间。如下图
$ I3 b8 _2 k) b/ z$ _, Y& O 1.png
: p. A5 F# K5 N如上面说的,环形缓冲区其实就是一个数组,将其“剪开”,然后“拉直”后如下图
0 Q( W4 Y: c0 m- ~
' _8 ^3 v$ W2 m0 }4 q5 y% f0 T; X

  t* r9 \. ?2 v# z( _, g 2.png # s- g* \8 y" M; [5 @$ C. T
/ F2 x( _# H5 m: _% w( J4 V
6 L  [' d) {9 T! v
环形缓冲区的特性( p) o3 z+ S0 X9 d) h

- A* ^2 x$ J! r5 x: v

7 I; M6 X1 ~1 O% i$ o6 ?1、先进新出。
9 S0 h: W! m/ Q% r3 J
' n2 g, A& s' w; c1 n
- D' T6 I9 d$ v) m) `5 {
2、当缓冲区被使用完,且又有新的数据需要存储时,丢掉历史最久的数据,保存最新数据。
! S5 T, F* L) q* e5 e9 N" l" \7 t: R/ G3 ]* u4 L1 @) E0 Z/ P

1 e) f7 i+ Z0 v' i2 N* e$ Y02、代码实现3 K4 w! O( E  u3 f; i; ?
环形缓冲区的实现很简单,只需要简单的几个接口即可。
* g4 A, r) U- l) S
+ m3 [2 a. |0 \; @3 ~
* X7 N; f/ T5 Q7 i2 f6 k8 D, i; W" h
首先需要创建一个环形缓冲区9 [+ Y% `3 B. D$ p
  1. <font face="微软雅黑" size="3">#define  RINGBUFF_LEN          (500)     //定义最大接收字节数 500
    % j5 o  c- O9 W$ T" p% @  w* S( I9 Y
  2. #define  RINGBUFF_OK           1     
    * H5 [% F4 X0 q( [" ^+ Z& g
  3. #define  RINGBUFF_ERR          0   5 B6 \% o+ ^; A2 s6 w- m# h3 X
  4. typedef struct5 D  E" \- A( F8 A
  5. {! h8 {8 r+ n) |7 |
  6.     uint16_t Head;           " s' B7 K7 K% A9 y* {  Q
  7.     uint16_t Tail;) E$ s: b+ z2 R6 g8 }% C9 s8 g% l- l( O
  8.     uint16_t Lenght;
    0 y( J7 f$ Q( L" C. f& ~
  9.     uint8_t  Ring_data[RINGBUFF_LEN];! d) \# ?. [, D1 T% p. X) Z
  10. }RingBuff_t;
    0 L  {  u( O  r
  11. RingBuff_t ringBuff;//创建一个ringBuff的缓冲区. y' u: a: P$ E7 K" m( i8 r: }+ U
  12. </font>
复制代码
当我们发现环形缓冲区被“冲爆”时,也就是缓冲区满了,但是还有待缓冲的数据时,只需要修改RINGBUFF_LEN的宏定义,增大缓冲区间即可。
. ^" _6 ^3 n- q: h2 B, ?, ?) S7 [% U% R5 E$ ?: \& r

% i6 S' {* c& N$ a$ \环形缓冲区的初始化. A8 B( l3 k; B9 m9 z0 s7 ]0 f
  1. <font face="微软雅黑" size="3">/**: [6 C/ b: U0 n2 [3 h5 x6 x* i" J
  2. * @brief  RingBuff_Init% n1 J' P, M  b! O
  3. * @param  void  C7 Q! |5 X9 j1 h: s0 R0 e7 ~
  4. * @return void
    & a# a- a" Q8 W# N& x, @
  5. * @note   初始化环形缓冲区
    ' C0 F+ z8 q% d; V4 K
  6. */
    2 e7 {# v) K3 y. ~. G
  7. void RingBuff_Init(void)
    : V5 f" i" w+ Z/ t6 q7 u/ w
  8. {, V6 v; y: W+ A* i# o' S  ~6 }( v
  9.   //初始化相关信息! l& _4 l% R) Y, w0 ^  U
  10.   ringBuff.Head = 0;4 `8 M* w7 \8 ]9 s
  11.   ringBuff.Tail = 0;  w. M6 l, K5 E. ]1 I
  12.   ringBuff.Lenght = 0;, ]; y" ^6 @% g
  13. }. Z: ^( C# \7 g
  14. </font>
复制代码
主要是将环形缓冲区的头,尾和长度清零,表示没有任何数据存入。$ |* H) c3 w* n8 O; k
$ M' L' R1 U) \. n

& A" }4 H% L+ {& N1 N3 I0 a( ~环形缓冲区的写入; S- y( }5 y, a

& a  y6 l2 {0 z1 \' a
  1. <font face="微软雅黑" size="3">/**
    4 R0 K6 A# u; s( t" U
  2. * @brief  Write_RingBuff
    - N) ^* p' h, p1 Q2 |6 E0 [" q
  3. * @param  uint8_t data
    * Z  s3 J% y! b; n
  4. * @return FLASE:环形缓冲区已满,写入失败;TRUE:写入成功
    $ _$ Y% u% ^; Y* R" B
  5. * @note   往环形缓冲区写入uint8_t类型的数据- e5 ~% M: s5 k( }! d
  6. */6 b/ U( B  S: z3 s0 d) u
  7. uint8_t Write_RingBuff(uint8_t data), Q- x, e) p. F+ N
  8. {2 k- p& u+ u  C7 t* n
  9.   if(ringBuff.Lenght >= RINGBUFF_LEN) //判断缓冲区是否已满$ k+ ^3 V2 u0 [) T4 @6 I
  10.   {" a) c& q, D9 H
  11.     return RINGBUFF_ERR;
    9 v9 ^; B( {! o4 w$ T3 h2 P/ ^
  12.   }
    $ I' W& c# x8 x7 x; F3 V% s0 e
  13.   ringBuff.Ring_data[ringBuff.Tail]=data;8 N! s- `7 p' I/ ]
  14.   ringBuff.Tail = (ringBuff.Tail+1)%RINGBUFF_LEN;//防止越界非法访问
    4 b- g- n" I3 I& r
  15.   ringBuff.Lenght++;
    ; t: w  B( K; ?/ c% x
  16.   return RINGBUFF_OK;2 @2 ~! m% @$ `6 ]  R
  17. }</font>
复制代码
这个接口是写入一个字节到环形缓冲区。这里注意:大家可以根据自己的实际应用修改为一次缓冲多个字节。并且这个做了缓冲区满时报错且防止非法越界的处理,大家可以自行修改为缓冲区满时覆盖最早的数据。' v' k) E* y0 Y, s% D

2 f' b' ?* T2 m0 p+ l6 _

# w# s2 E2 E- f6 c; z  V环形缓冲区的读取
- o9 K6 M/ B/ z
- u2 v% c4 D" C4 c! @; o0 }0 m% s
  1. <font face="微软雅黑" size="3">
    9 Z( {9 i! Q$ ?* [6 d) U
  2. /**& H8 H% k, N% r
  3. * @brief  Read_RingBuff7 v0 D  q* u. F7 ?/ J1 S+ j
  4. * @param  uint8_t *rData,用于保存读取的数据3 n: B" {. ^7 u/ h- |% w8 P
  5. * @return FLASE:环形缓冲区没有数据,读取失败;TRUE:读取成功
    , e3 R9 [5 P2 l% m3 ^/ U" [
  6. * @note   从环形缓冲区读取一个u8类型的数据1 H" i0 C; i6 D
  7. */
    # M; ]; H6 _6 A: G: a3 n
  8. uint8_t Read_RingBuff(uint8_t *rData)
    : F# t* G# J( X/ E  w
  9. {8 ^$ T* P' v  d8 t5 Q
  10.   if(ringBuff.Lenght == 0)//判断非空2 E: }3 ~. t4 a$ w
  11.   {
    . H* d8 }3 \/ F$ c) L5 \; z+ Y
  12.     return RINGBUFF_ERR;
    1 d  ~0 O" \! b& m
  13.   }
    8 L4 z+ `9 q' v. b9 l
  14.   *rData = ringBuff.Ring_data[ringBuff.Head];//先进先出FIFO,从缓冲区头出; P/ D) ?; J8 E$ [% i7 [
  15.   ringBuff.Head = (ringBuff.Head+1)%RINGBUFF_LEN;//防止越界非法访问
    + B/ T8 x' c( `+ N( ]
  16.   ringBuff.Lenght--;, |( L6 `4 S$ r- `9 l/ D
  17.   return RINGBUFF_OK;' B% M, W; r$ Z& g: B. m! v
  18. }8 T( @$ o+ \& D5 g+ h' M' R+ j
  19. </font>
复制代码
读取的话也很简单,同样是读取一个字节,大家可以自行修改为读取多个字节。
8 Z! A8 p  x1 I7 k& H. f- o1 A
; }/ y- q9 y- J2 k
2 J2 z! c  a6 U- y5 ?5 g
02、验证# ^0 Q! W; \. g2 ~; {( ^3 R
光说不练假把式,下面我们就来验证上面的代码可行性。) [2 _& Q8 }5 {8 O* \5 ^3 ?

" c9 W7 Y! V1 m; e! S. {

% N5 n$ C: ^& k/ j1 Y串口中断函数中缓冲数据; g, |1 C& p2 Q- Y( B; o- e" g
8 Z$ k# q) M* d3 M3 W# v
  1. <font face="微软雅黑" size="3">void USART1_IRQHandler(void): H7 J( i5 x# w
  2. {
    . n% W7 d1 U. E: V" Q
  3.   if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE))
      R# u3 o9 n( r, R0 T3 Y& ]
  4.   {
    : n" |, V9 I% ^  j/ j) d
  5.     Write_RingBuff(USART_ReceiveData(USART1));
    " K# R+ T( i" ]" Q
  6.     USART_ClearFlag(USART1, USART_FLAG_RXNE);
    8 H, w  \. z1 }* Q
  7.   }
    & o$ W& r: a2 `# N+ a
  8. }</font>
复制代码
在主循环中,读取缓冲区的数据,然后发送出去,因为是简单的demo,添加了延时模拟CPU处理其他任务。
2 Q1 P$ m, F* G1 W7 j4 Q" ?& ^- G
  1. <font face="微软雅黑" size="3">
    - S6 c( n/ Z  F1 K1 o: _( }
  2. while (1): ]; s' g4 t, A& \. S' j
  3.   {
    # u! O6 ]/ f" w- q2 z
  4.     if(Read_RingBuff(&data))            //从环形缓冲区中读取数据( p: M8 A! b: D' t% J# r
  5.     {" t1 K# E1 {9 r: Y
  6.       USART_SendData(USART1, data);( g4 ~5 N7 l+ @
  7.     }
    " \% v7 \0 M" v+ ]
  8.     SysCtlDelay(1*(SystemCoreClock/3000));
    # h3 n6 X4 [) f/ T
  9.   }
    & c! i" `( b( B' V/ I
  10. </font>
复制代码
验证,间隔100ms发送数据。
* x: ?: K' ~5 d+ M0 G
+ ^- U) \  ~$ _6 ~* E8 B- H
, d# [$ ~! g, u, ?/ C) q9 l5 r, O

# g1 g& ]! u' c2 A' W
/ M4 j$ Y1 j) k
; m/ U  K5 `+ g) z4 s
结果显示没有出现丢包问题。如果你的应用场景串口通信速率快,数据量大或处理速度慢导致丢包,建议增大RINGBUFF_LEN的宏定义,增大缓冲区间即可。0 I, m1 N6 S7 n2 [* U0 X( E

5 m# F3 ?* ~+ l# _+ x. f3 {

- y4 j# G1 [2 T- X* d6 |. j+ JKeil和IAR的工程文件下载地址:* o2 G' {- U- c
[url=https://github.com/strongercjd/STM32F207VCT6]https://github.com/strongercjd/STM32F207VCT6
0 [9 x, z0 x* U! S! C+ W[/url]! y% Y8 p+ p) e' V  ]7 z1 K

  o& S) S- i& G7 R, J; Z7 Z3 K6 @$ {4 X0 [% v7 I1 w4 \
赞 收藏 评论0 发布时间:2021-7-19 14:17

举报

0个回答

所属标签

相似技术帖

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