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

STM32串口开发之环形缓冲区

[复制链接]
STMCU小助手 发布时间:2021-7-19 14:17
01、缓冲区% v; y( H* J& t0 [$ l  W0 ~
缓冲区看名字就知道,是缓冲数据用的。实现缓冲区最简单的办法时,定义多个数组,接收一包数据到数组A,就把接收数据的地址换成数组B,每个数据有个标记字节用于表示这个数组是否收到数据,收到数据是否处理完成。
0 m) V$ X1 {) |3 S: p$ h' }) L; E) y
$ b9 X/ n) d' V- [' c' Y% c
9 c. a- a  ?' @8 ]6 e8 Z
上述方案是完全可行的,但有缺点:) [4 U$ k! o: T

* I" p  o6 b1 `9 u. W! f
( n0 ]" H$ X" H6 {) O; Z& p
①缓冲数据组数一定,且有多变量,代码结构不太清晰。
( M3 i/ Y: f- f! g% T8 T' D0 z/ U8 W9 _: M# P% q
0 v2 O* A' Q) J! V0 R5 b* K7 p
②接收数据长度可能大于数组大小,也可能小于数组大小。不灵活,需要接收数据很长时容易出错,且内存利用率低。
" C0 `/ b* z7 z  b5 V- p
9 F6 p; T. @  V5 o) i2 X, N: |
: \0 T" j3 _: i  R0 m
解决这个问题的好办法是:环形缓冲区。6 I7 @# B. V7 _. ~
$ T2 Y$ K8 T: v( A& e

4 d6 |1 l' a: z+ [4 a' {: y8 n环形缓冲区就是一个带“头指针”和“尾指针”的数组。“头指针”指向环形缓冲区中可读的数据,“尾指针”指向环形缓冲区中可写的缓冲空间。通过移动“头指针”和“尾指针”就可以实现缓冲区的数据读取和写入。在通常情况下,应用程序读取环形缓冲区的数据仅仅会影响“头指针”,而串口接收数据仅仅会影响“尾指针”。当串口接收到新的数组,则将数组保存到环形缓冲区中,同时将“尾指针”加1,以保存下一个数据;应用程序在读取数据时,“头指针”加1,以读取下一个数据。当“尾指针”超过数组大小,则“尾指针”重新指向数组的首元素,从而形成“环形缓冲区”!,有效数据区域在“头指针”和“尾指针”之间。如下图
0 j  n7 S4 u2 l% _5 d. Z+ c2 K6 D 1.png 9 \  g% V6 W% T6 a$ g
如上面说的,环形缓冲区其实就是一个数组,将其“剪开”,然后“拉直”后如下图) l8 o9 U' L: ^
& a. W3 O2 }+ S* [6 r

0 O& P  ?8 S$ \! y# ^ 2.png
3 G4 }- c% @" k* Y1 t
% M5 f0 ?9 K; ^7 m$ U' M6 h1 ~; O7 T

5 o$ N/ y7 y  r1 a8 O环形缓冲区的特性2 B" D2 s* j0 l9 W- g- E( [! N: d$ _
( n& U- q* X- W0 r( H
! M' ~4 c# i" b& S: E  L- [* ~2 K
1、先进新出。
# p4 s2 G- q* r
- r6 d2 e1 G; w7 }# `7 M

' X, L" w0 I% k- d' i  j* n( x2、当缓冲区被使用完,且又有新的数据需要存储时,丢掉历史最久的数据,保存最新数据。
5 O  W# k$ {% L4 W# z3 P$ t8 ^/ W- m# A  f% v1 T9 {9 n) V( J! g

% N! u6 o2 T8 l# [8 W! X* Z/ k  _' s02、代码实现3 W" a3 ^, x$ n. D  a! \
环形缓冲区的实现很简单,只需要简单的几个接口即可。4 j% @+ N' ?- c1 S! _  S+ E: j

( ^$ H+ ~& L! Y' |! t3 h& x% ]

* o0 ], c+ c# n( R" v7 ~% K首先需要创建一个环形缓冲区+ z$ T4 a% N* Q, h
  1. <font face="微软雅黑" size="3">#define  RINGBUFF_LEN          (500)     //定义最大接收字节数 500
    6 l" M1 Q7 _$ }! g
  2. #define  RINGBUFF_OK           1     
    5 l3 w3 N- Q' |. n8 f( E
  3. #define  RINGBUFF_ERR          0   
    ! k6 O2 e  S' q0 \
  4. typedef struct
    7 y8 ]. x! J7 m3 z( P
  5. {
    ( {9 d# Y$ W1 x. ^7 b
  6.     uint16_t Head;           9 I5 M9 v6 r0 [9 L% J1 E! u
  7.     uint16_t Tail;
    , F" y. h, j5 J
  8.     uint16_t Lenght;6 Y0 _1 f7 a7 x# T
  9.     uint8_t  Ring_data[RINGBUFF_LEN];0 ?  T& X# I" B! h7 h: B  @8 D" U; @
  10. }RingBuff_t;
    . J$ N4 A  k* e2 ~$ F3 R
  11. RingBuff_t ringBuff;//创建一个ringBuff的缓冲区
    0 F. h9 F* J1 b/ d& z, T3 B
  12. </font>
复制代码
当我们发现环形缓冲区被“冲爆”时,也就是缓冲区满了,但是还有待缓冲的数据时,只需要修改RINGBUFF_LEN的宏定义,增大缓冲区间即可。
2 \5 d; a+ R( |5 d( D" P# f$ j% o
: x; O/ _1 H! D' O
$ V% a# w  N8 @/ x
环形缓冲区的初始化
" L+ g" {) ]4 ~
  1. <font face="微软雅黑" size="3">/**" E% J9 v3 }8 j* m6 d
  2. * @brief  RingBuff_Init
    5 e8 i/ n( t. h
  3. * @param  void
    % u+ ?% \3 P/ E' \* ~9 W2 Y
  4. * @return void. a2 f5 b& u3 i2 `9 L; C
  5. * @note   初始化环形缓冲区
    + k+ ^% I; @/ ^5 L) x2 `* \* L+ M! X
  6. */3 o+ m) [) v' w
  7. void RingBuff_Init(void)
    $ L$ k+ [2 C' \) j2 j  q
  8. {
      X4 \) a% O: c# ^, C
  9.   //初始化相关信息
    % g3 T% ~+ t, g2 u# Y" e  D$ m/ \
  10.   ringBuff.Head = 0;" Q8 Y/ ?+ i' v# A4 j
  11.   ringBuff.Tail = 0;
    / U0 v% Q( q0 `8 @
  12.   ringBuff.Lenght = 0;3 f# q. `* I4 D0 m6 v  i; B# E
  13. }$ \& u: m; k$ ~. X" D
  14. </font>
复制代码
主要是将环形缓冲区的头,尾和长度清零,表示没有任何数据存入。& o+ _- i$ i6 Q) S

9 V, L: H8 F2 {2 `9 U5 y

$ J4 J& a, J6 D: n: C1 k: ~0 m环形缓冲区的写入
8 G# r7 c; ^+ m  o: D1 D# o* y2 a
4 s  \" C5 |( K: ~
  1. <font face="微软雅黑" size="3">/**/ O5 j8 A" Z2 x2 k( k0 T
  2. * @brief  Write_RingBuff
    7 E! U9 i% a1 e1 ]* g% W# I
  3. * @param  uint8_t data
    & A: ?3 u) t* P4 f. `
  4. * @return FLASE:环形缓冲区已满,写入失败;TRUE:写入成功2 A4 X/ F/ h: J$ O, A1 V6 R& ~
  5. * @note   往环形缓冲区写入uint8_t类型的数据
    ! z6 z% T0 Z6 r) \! i- o
  6. */4 E* h' w& l  H; D/ b7 \% W0 D
  7. uint8_t Write_RingBuff(uint8_t data)
    6 \# ?$ C  U7 D8 c0 j# N
  8. {
    ( M$ Q1 w* M) @* v$ a$ v1 ]- l
  9.   if(ringBuff.Lenght >= RINGBUFF_LEN) //判断缓冲区是否已满$ _# W3 _9 x8 Z! [* d. _
  10.   {  O2 O! @4 z, n" a' x
  11.     return RINGBUFF_ERR;
    1 T5 B+ n. J- L' `! F
  12.   }
    $ |# L' m; [- f
  13.   ringBuff.Ring_data[ringBuff.Tail]=data;0 T* U" ^1 t9 _9 }: x# R' z+ \
  14.   ringBuff.Tail = (ringBuff.Tail+1)%RINGBUFF_LEN;//防止越界非法访问
    2 o5 E' m3 q; W7 u# o6 F6 p
  15.   ringBuff.Lenght++;
    ! C. f+ q0 s4 I; U
  16.   return RINGBUFF_OK;. @4 o% _8 ^$ `$ J( B2 k8 O
  17. }</font>
复制代码
这个接口是写入一个字节到环形缓冲区。这里注意:大家可以根据自己的实际应用修改为一次缓冲多个字节。并且这个做了缓冲区满时报错且防止非法越界的处理,大家可以自行修改为缓冲区满时覆盖最早的数据。2 X2 u4 K  W, E& X3 Z/ j
7 {- ]3 c: B) `' L8 U6 H6 O
, k. K- j7 E3 k, R4 r
环形缓冲区的读取0 R2 g# f9 B8 {; ?3 `5 A+ b! A( N, T

1 `0 P1 H/ v6 p: }, F' I9 y
  1. <font face="微软雅黑" size="3">
    0 p6 D9 E( q! t$ a3 y
  2. /**
    8 C4 x: @+ M+ A  Y
  3. * @brief  Read_RingBuff; i- P4 v" Z: T% v0 r6 |
  4. * @param  uint8_t *rData,用于保存读取的数据
    ; `$ r7 d2 A$ Q/ l: k
  5. * @return FLASE:环形缓冲区没有数据,读取失败;TRUE:读取成功
    7 I6 O) K2 p! c
  6. * @note   从环形缓冲区读取一个u8类型的数据
    ) W. n0 e+ N* ^8 {
  7. */
    * s- l8 b5 b+ ^- g5 f0 \
  8. uint8_t Read_RingBuff(uint8_t *rData)7 ^3 M" j) G, I9 h# S$ f
  9. {
    . C1 T* ~7 G9 J1 y
  10.   if(ringBuff.Lenght == 0)//判断非空5 J# H- B7 l* j% w6 a
  11.   {
    8 {: a! ?9 e6 [/ t  h
  12.     return RINGBUFF_ERR;) A4 u1 X: B9 R4 ~, u
  13.   }! k" t9 ?5 o$ n* ?/ {
  14.   *rData = ringBuff.Ring_data[ringBuff.Head];//先进先出FIFO,从缓冲区头出. f, \* W; E6 S) `8 H/ v
  15.   ringBuff.Head = (ringBuff.Head+1)%RINGBUFF_LEN;//防止越界非法访问& _$ S7 j7 i% u. T+ G
  16.   ringBuff.Lenght--;
    * X8 H) k, Y' a
  17.   return RINGBUFF_OK;
    6 d! B$ M- {' m8 L. T
  18. }
    / F( j8 u6 {- C; R# x% J
  19. </font>
复制代码
读取的话也很简单,同样是读取一个字节,大家可以自行修改为读取多个字节。6 ^& j/ U6 c8 _* q& l( c* Z

1 z; `, _/ E' V1 }: C6 ~

, |# [) m; Y- a6 j02、验证
' k, _" ]& H: L光说不练假把式,下面我们就来验证上面的代码可行性。
0 p9 M' X  h, C
! ?* \; t; x3 W, ]7 ]' q

! D# h- N( A) s串口中断函数中缓冲数据
# s& U* H4 j3 p, X% Q
) P# Q0 Y" i- h; ?
  1. <font face="微软雅黑" size="3">void USART1_IRQHandler(void)! b7 x! O2 p; x! F; {
  2. {
    % C! ^5 u+ w: ^7 `" r( O
  3.   if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE))
    2 `( \% q7 C% @7 x
  4.   {
    $ R8 v6 @6 u% @! _0 S  A/ h* N
  5.     Write_RingBuff(USART_ReceiveData(USART1));
    # ]$ ], F! n6 C+ {! R! z: x
  6.     USART_ClearFlag(USART1, USART_FLAG_RXNE);
    ( t2 E! u) d- H; ]0 i
  7.   }
    4 z+ ?4 F; a9 H& n
  8. }</font>
复制代码
在主循环中,读取缓冲区的数据,然后发送出去,因为是简单的demo,添加了延时模拟CPU处理其他任务。3 t: x; v# J- ]; G" Z4 Z* q
  1. <font face="微软雅黑" size="3">" t# h6 B( s, m$ f! L
  2. while (1)
      D5 ?( d8 ~+ x5 S' C, M. P
  3.   {
    5 S" R. M& M9 X$ M/ ^" G
  4.     if(Read_RingBuff(&data))            //从环形缓冲区中读取数据) d8 V2 R: G) t
  5.     {
    3 G( t5 P6 J0 q- H, E% _& g
  6.       USART_SendData(USART1, data);
    ; n' z* m: I+ J8 O* w
  7.     }
      z4 C: W1 Q0 B( m- T3 [! S1 R
  8.     SysCtlDelay(1*(SystemCoreClock/3000));
    1 U9 w$ a& K! a# w& o! a
  9.   }
    ; z- S& @% |* F  {
  10. </font>
复制代码
验证,间隔100ms发送数据。
$ U5 |5 ^( t$ p
1 a. \: _, Y, T) @) n
$ B/ U( \; Z( T7 v) n" I8 ?
- c+ B9 w. p* n# N

; \! u. _1 S! T; r& m7 C+ {9 K

' Z2 ^, S) H: d) O4 ~) w结果显示没有出现丢包问题。如果你的应用场景串口通信速率快,数据量大或处理速度慢导致丢包,建议增大RINGBUFF_LEN的宏定义,增大缓冲区间即可。- K! A7 Z1 S- ?3 [7 [

- F& X0 V- l! \: J
0 ]& K9 c! j4 S4 t1 j/ X; r
Keil和IAR的工程文件下载地址:
% Y4 |" G+ B2 Q0 d$ I[url=https://github.com/strongercjd/STM32F207VCT6]https://github.com/strongercjd/STM32F207VCT6
. W. O1 {9 E# w5 D* Y9 o( Y2 K[/url]: E* C) I% D3 K- T+ `" {
# N* R  ]" N/ ?. p; j

3 t0 O7 u- c% g+ [' N
收藏 评论0 发布时间:2021-7-19 14:17

举报

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