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

STM32串口开发之环形缓冲区

[复制链接]
STMCU小助手 发布时间:2021-7-19 14:17
01、缓冲区
+ Q1 b# X& n* A+ T5 @缓冲区看名字就知道,是缓冲数据用的。实现缓冲区最简单的办法时,定义多个数组,接收一包数据到数组A,就把接收数据的地址换成数组B,每个数据有个标记字节用于表示这个数组是否收到数据,收到数据是否处理完成。% B" K2 L1 ~9 L* ^2 ^8 V+ m

# i* Y+ w4 j: p( W
0 P  f+ T* o" Z8 P1 o* c( }  l
上述方案是完全可行的,但有缺点:8 _4 g, `$ b$ s: D

: X6 @* @, t# \8 Z% k( y
: ?; d7 {1 N, E2 P. _7 i' W1 Q; j4 q5 z
①缓冲数据组数一定,且有多变量,代码结构不太清晰。+ g+ N* o8 P  O* f

8 F/ f/ P3 Y% I1 Z- L* M) C

* U2 S. y5 ~% c9 g②接收数据长度可能大于数组大小,也可能小于数组大小。不灵活,需要接收数据很长时容易出错,且内存利用率低。
" K5 s$ v( Q! s# m3 O+ s0 v, W- B& k5 A$ S  `
6 ?& [7 `' i0 i% X( F& }- X4 E
解决这个问题的好办法是:环形缓冲区。
& H4 w& e) U# l- m' Q, R2 ~0 m) [% g3 c& b, j3 s, ~( p: I. J$ C3 _

& e2 Z7 w6 U% i" `/ G0 X  f环形缓冲区就是一个带“头指针”和“尾指针”的数组。“头指针”指向环形缓冲区中可读的数据,“尾指针”指向环形缓冲区中可写的缓冲空间。通过移动“头指针”和“尾指针”就可以实现缓冲区的数据读取和写入。在通常情况下,应用程序读取环形缓冲区的数据仅仅会影响“头指针”,而串口接收数据仅仅会影响“尾指针”。当串口接收到新的数组,则将数组保存到环形缓冲区中,同时将“尾指针”加1,以保存下一个数据;应用程序在读取数据时,“头指针”加1,以读取下一个数据。当“尾指针”超过数组大小,则“尾指针”重新指向数组的首元素,从而形成“环形缓冲区”!,有效数据区域在“头指针”和“尾指针”之间。如下图
; v% E$ K2 x. c1 K 1.png
& k# Z( j3 v8 Q& E如上面说的,环形缓冲区其实就是一个数组,将其“剪开”,然后“拉直”后如下图
) ?$ H6 `/ \1 A2 i) x$ U! R* c% i. W  y5 L
5 f" f# l0 a) t6 Z7 b
2.png - m" D8 j7 Z: t7 c

9 ?. b5 p: m) i0 B( P0 Y$ I2 }; w+ ^
; D8 I; Z8 u" `7 O$ y* k! S
环形缓冲区的特性
0 }, y! m" [. R9 W+ U) b! E. E- V# W
/ }* m! ~0 d+ C. w
  Y- u1 X; `" i: b9 a3 R
1、先进新出。
  B6 y! ]/ W" Z  c2 C2 j( O1 v4 P
  A) u5 r9 X! P) j, G

; P5 i1 m. k0 p- l2、当缓冲区被使用完,且又有新的数据需要存储时,丢掉历史最久的数据,保存最新数据。
; k& l/ l/ y1 O% _7 _9 I7 M, v4 H
8 `2 q! [4 o/ O- V

2 |% i9 q: j  v3 R/ v02、代码实现
) @7 Y6 J' L9 v: {6 ~& B0 z- c环形缓冲区的实现很简单,只需要简单的几个接口即可。9 R4 V  u- K1 {
) Y5 f" W9 D5 a) W
- T/ T( v, I  H; Z; _( ]
首先需要创建一个环形缓冲区
6 m" b0 L0 {  ~( Z9 E1 j" W/ R
  1. <font face="微软雅黑" size="3">#define  RINGBUFF_LEN          (500)     //定义最大接收字节数 500
    5 r& w: G6 P- ~; H8 I4 v
  2. #define  RINGBUFF_OK           1     
    ) |9 Y( O4 i. ]( [
  3. #define  RINGBUFF_ERR          0   
    0 x0 B. \# R+ R6 A% k' o
  4. typedef struct' K. @  B; b6 M, J9 |3 ^4 W9 @- N
  5. {3 @8 G* R+ v& R& d
  6.     uint16_t Head;           7 t6 A+ h2 u+ P
  7.     uint16_t Tail;
    2 o6 R) \6 m. O6 Y% X- S
  8.     uint16_t Lenght;
    , D$ g( b; ^- g" R* P5 o: J
  9.     uint8_t  Ring_data[RINGBUFF_LEN];
    $ S: ~' T  L4 ^$ x( F0 D
  10. }RingBuff_t;. \  `: p: X4 G! O
  11. RingBuff_t ringBuff;//创建一个ringBuff的缓冲区
    7 Q& g  S) j0 `( Y/ m
  12. </font>
复制代码
当我们发现环形缓冲区被“冲爆”时,也就是缓冲区满了,但是还有待缓冲的数据时,只需要修改RINGBUFF_LEN的宏定义,增大缓冲区间即可。
) m: A$ |+ f  b# N; q: _3 |# V  q, i

+ h2 J3 ?% z: V# C. x' Y7 h6 i环形缓冲区的初始化2 f9 q! Q" ^% m0 v/ _
  1. <font face="微软雅黑" size="3">/**
    - S" L" ?: O. r/ b- S' ?: g
  2. * @brief  RingBuff_Init
    . r' t; H% W- @* I* ^
  3. * @param  void
    1 c  X4 P1 ~$ @. f7 F
  4. * @return void# K1 v) H1 m4 W6 m1 C7 H) Q" _
  5. * @note   初始化环形缓冲区
    1 H% [/ Q6 q8 S4 O4 e0 e4 E
  6. */
    $ s/ `7 ?4 Y0 T. P( p
  7. void RingBuff_Init(void)
    3 o4 h; a3 _( c, H. x& @+ J# a
  8. {
    - g# ?9 I) `- q# A8 m9 @
  9.   //初始化相关信息. R" n/ F) b$ G1 t
  10.   ringBuff.Head = 0;) m5 u# T( Q7 [. x+ a
  11.   ringBuff.Tail = 0;% _) m$ P" J0 O
  12.   ringBuff.Lenght = 0;
    9 w, m+ G6 l% l9 d9 g- `
  13. }3 v6 t+ f- H" a& ]1 Z, y# v% q
  14. </font>
复制代码
主要是将环形缓冲区的头,尾和长度清零,表示没有任何数据存入。. z$ F9 e* n- I- V. g2 T

; a% M# m7 P- M- u. t
; N0 ]& |: y1 J3 J) A# E
环形缓冲区的写入. i* L. v  \2 I5 x( y4 u2 @
: o& |5 p. a- O9 o
  1. <font face="微软雅黑" size="3">/**
    ! R4 g9 A* q! j1 u, D( a9 H# I: p
  2. * @brief  Write_RingBuff
    ; U' L; F8 m: D: D4 J% T
  3. * @param  uint8_t data2 P8 j+ v' M) `; @
  4. * @return FLASE:环形缓冲区已满,写入失败;TRUE:写入成功5 p  q8 ~& L$ j  E
  5. * @note   往环形缓冲区写入uint8_t类型的数据$ J3 U) K! h8 d: R% m, l: D3 ^
  6. */
    ! |  r6 \& u+ ], [, i, N. L- Q3 v7 A
  7. uint8_t Write_RingBuff(uint8_t data)
    % i4 j! u# X" K8 r2 S; H( D) l
  8. {" o2 E% g. Q- h3 e, B; K5 ~
  9.   if(ringBuff.Lenght >= RINGBUFF_LEN) //判断缓冲区是否已满
    - M% E7 A; }$ I+ d- T) q8 V" i
  10.   {' E4 o: z9 ]5 z8 R! `
  11.     return RINGBUFF_ERR;# S1 |( `. L5 y
  12.   }
    : \; D( m( A1 C8 k! f5 Z8 [3 C
  13.   ringBuff.Ring_data[ringBuff.Tail]=data;
    ' p( C1 y2 x- g7 b" D0 a1 j" i
  14.   ringBuff.Tail = (ringBuff.Tail+1)%RINGBUFF_LEN;//防止越界非法访问7 J- t: O2 w2 g" m
  15.   ringBuff.Lenght++;& O: {9 z- z/ ]5 l
  16.   return RINGBUFF_OK;
    " \3 L: v$ w. s9 Q. R9 Q# _) f- v
  17. }</font>
复制代码
这个接口是写入一个字节到环形缓冲区。这里注意:大家可以根据自己的实际应用修改为一次缓冲多个字节。并且这个做了缓冲区满时报错且防止非法越界的处理,大家可以自行修改为缓冲区满时覆盖最早的数据。) m) g# s2 V2 R

( ?# t* j$ y0 W7 ?/ S

0 i5 \) O4 u3 |. \1 e" {环形缓冲区的读取
3 v3 P3 ?, w2 N! l' z
. c+ g) n9 j5 E: }
  1. <font face="微软雅黑" size="3">) w+ N, A/ C0 _* w
  2. /**
    * R! ?) o1 C8 I; W/ \; V8 z
  3. * @brief  Read_RingBuff
    ; ]2 B* s! {, E
  4. * @param  uint8_t *rData,用于保存读取的数据, ~. j1 Y5 D, M7 l8 ?- E6 Z* `$ P
  5. * @return FLASE:环形缓冲区没有数据,读取失败;TRUE:读取成功& y; z4 ~& G6 @3 H6 n0 B$ s0 g
  6. * @note   从环形缓冲区读取一个u8类型的数据
    ; o) i3 Y6 H$ L  \& t! V9 @. I
  7. */" b6 q$ m  P% ?- o" v1 Z
  8. uint8_t Read_RingBuff(uint8_t *rData)
    + x0 R0 a- B4 R! z' b, u
  9. {
    ; M4 k) d/ a  K0 R/ S/ B
  10.   if(ringBuff.Lenght == 0)//判断非空
    ( L1 s' ]" q8 e7 e
  11.   {2 c# m/ a4 o9 G. f; v3 I
  12.     return RINGBUFF_ERR;
    - F  ^0 v/ D2 h8 f7 }2 N5 n
  13.   }$ H7 v# s! o" ?2 ?
  14.   *rData = ringBuff.Ring_data[ringBuff.Head];//先进先出FIFO,从缓冲区头出- Y3 J3 t; @+ c& D4 c3 c
  15.   ringBuff.Head = (ringBuff.Head+1)%RINGBUFF_LEN;//防止越界非法访问
    7 _+ `" G* L7 C1 I4 C0 r5 D8 E
  16.   ringBuff.Lenght--;0 s4 f  _* @: z3 s; ^* u
  17.   return RINGBUFF_OK;/ R" o' R$ a! m5 A+ r
  18. }
    5 \, f; p* w! F  O/ ~$ o6 W
  19. </font>
复制代码
读取的话也很简单,同样是读取一个字节,大家可以自行修改为读取多个字节。% p# i3 |* b( D0 P2 A6 f" H$ d9 U. q
" h% q- E3 K' [. H4 |5 L9 t
# _; C! t! E$ c9 c* g
02、验证
1 w4 x# X% J% |! J8 j* V$ b. [; E光说不练假把式,下面我们就来验证上面的代码可行性。
7 @4 B0 |5 E' l# S# x4 {) I1 e! O6 h  Q# y( U: M

/ x* _2 @% J9 @* z$ i串口中断函数中缓冲数据
4 F# n4 X$ w# R5 t* R. S) O6 G7 l! u) B8 j& R* v( v
  1. <font face="微软雅黑" size="3">void USART1_IRQHandler(void)% r4 }& e5 H9 V( j$ Q
  2. {
    3 w" E1 _1 }" k  h
  3.   if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE))
    9 n8 x# o1 m( M3 n9 a$ ~  J/ |# J% @
  4.   {$ O; s5 z8 N- P6 u: s& `* I
  5.     Write_RingBuff(USART_ReceiveData(USART1));
    7 Y# `+ n3 y& `+ G
  6.     USART_ClearFlag(USART1, USART_FLAG_RXNE);
    # H4 g" r; K4 [6 v' Y6 r! ~
  7.   }
    - d2 D, p; w: ?  _) d; @& S3 j% h, b: j% c
  8. }</font>
复制代码
在主循环中,读取缓冲区的数据,然后发送出去,因为是简单的demo,添加了延时模拟CPU处理其他任务。
7 o0 i8 N& x9 e2 g/ W& \+ ]* k
  1. <font face="微软雅黑" size="3">* r: u3 B  G# ?' O) M/ Y9 N( z2 c& z
  2. while (1)
    3 V; v2 A) [- v' V
  3.   {3 w1 I4 Z. l9 Y. P
  4.     if(Read_RingBuff(&data))            //从环形缓冲区中读取数据! |1 [6 l( R* H7 J. Y- o
  5.     {8 m( y: \* s/ Z
  6.       USART_SendData(USART1, data);9 @5 X& J+ n2 `+ g; B$ y+ Z
  7.     }$ z' I  _4 Y1 E6 h+ L
  8.     SysCtlDelay(1*(SystemCoreClock/3000));0 \# y. {& V/ O6 Y- @% c, q
  9.   }' }) l9 F  O4 h) i: Z+ @9 g; @
  10. </font>
复制代码
验证,间隔100ms发送数据。& b0 }5 A5 Q# A0 Q! F4 n

$ `2 ?2 T+ m3 }2 @$ X. h
9 I& ?- B0 L! K" |6 q
: f" t9 y, ~/ D! v: b8 M- }
( F6 ~2 c( [; v# Q

" I4 q8 M: v9 J/ g" N& B' W结果显示没有出现丢包问题。如果你的应用场景串口通信速率快,数据量大或处理速度慢导致丢包,建议增大RINGBUFF_LEN的宏定义,增大缓冲区间即可。
( G9 v. i8 k% T, c
. P, S/ v0 U; `% V
1 T& e# _  p  P3 U& M, P
Keil和IAR的工程文件下载地址:2 J4 ]/ E2 ^* c! U+ \
[url=https://github.com/strongercjd/STM32F207VCT6]https://github.com/strongercjd/STM32F207VCT61 @/ Y1 {2 q) j
[/url]& u% `5 V+ u7 V/ m9 E

- j( |( X0 t, T2 M1 Z; g
: ^. _: B3 s% _' N) ]
收藏 评论0 发布时间:2021-7-19 14:17

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版