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

【经验分享】STM32串口开发之环形缓冲区

[复制链接]
STMCU小助手 发布时间:2022-4-16 20:00
01、简介
. E: f7 v4 ^( J/ g  m在之前的文章《stm32 串口详解》中,我们讲解了串口的基本应用,使用串口中断接收数据,串口中断发送回包(一般可以使用非中断形式发送回包,在数据接收不频繁的应用中。串口接收中断保证串口数据及时响应,使用非中断方式发送回包即可)。
  D" I! r4 v& R2 O& d8 a
7 `  A% r' x" d/ `8 z& o除了上述两种场景,还有一种应用场景:串口接收数据长度位置,频率未知,不要求实时处理的场景。如果采用上述方案,接收一帧数据立即处理,那么在处理的时候来的数据包就“丢失”了。这个时候就需要缓冲队列来解决这个问题。. v' Z" N2 [! |* `

; [- \5 y0 Q' H" l02、缓冲区
  @- i2 G4 r) E% G, f* N缓冲区看名字就知道,是缓冲数据用的。实现缓冲区最简单的办法时,定义多个数组,接收一包数据到数组A,就把接收数据的地址换成数组B,每个数据有个标记字节用于表示这个数组是否收到数据,收到数据是否处理完成。) L! X( J8 G# w% ~/ U5 c! [' T/ j
& {6 o- @4 {% }; Z  j
上述方案是完全可行的,但有缺点:3 V% U" ~; l$ j+ e5 O3 Q& O

& ~* y& o! l. S& y7 Y5 M①缓冲数据组数一定,且有多变量,代码结构不太清晰。
5 T& c4 ?/ N0 }! x: p
& q( I' ^4 M+ f, \. r6 v7 v) Y②接收数据长度可能大于数组大小,也可能小于数组大小。不灵活,需要接收数据很长时容易出错,且内存利用率低。
& j. `% I5 A% |5 k- I; Y
9 ?6 u8 R4 m' y! Y9 Y/ v3 a解决这个问题的好办法是:环形缓冲区。3 w! b: r) @$ D+ ?& t$ A
$ Z1 y3 e# U2 e" w4 P
环形缓冲区就是一个带“头指针”和“尾指针”的数组。“头指针”指向环形缓冲区中可读的数据,“尾指针”指向环形缓冲区中可写的缓冲空间。通过移动“头指针”和“尾指针”就可以实现缓冲区的数据读取和写入。在通常情况下,应用程序读取环形缓冲区的数据仅仅会影响“头指针”,而串口接收数据仅仅会影响“尾指针”。当串口接收到新的数组,则将数组保存到环形缓冲区中,同时将“尾指针”加1,以保存下一个数据;应用程序在读取数据时,“头指针”加1,以读取下一个数据。当“尾指针”超过数组大小,则“尾指针”重新指向数组的首元素,从而形成“环形缓冲区”!,有效数据区域在“头指针”和“尾指针”之间。如下图- x2 g9 _. i7 q( m

. k  B* G' [+ D 094c97348810f8c363b8ac02673c96fb.jpg
: O( X/ s8 I% [# c0 f% K1 r/ b$ |5 ~- B6 ]* b8 r
如上面说的,环形缓冲区其实就是一个数组,将其“剪开”,然后“拉直”后如下图! T- m" t( l% k  V' c) w/ F8 @+ O8 z
% `" |+ C4 W- y0 b5 r4 ]6 E1 [- P
480cd34c507ef913ca5bd548d6d9df87.jpg
/ R# {& R# _$ x2 t
6 Z7 ~( ^6 p* T  x/ H+ J  s环形缓冲区的特性
: d/ o  T" z' @  D8 n& b- E
  {; _2 R& x- b8 n0 z) Y' |1、先进新出。
1 m$ U3 ?; l/ _2 x/ v2 T8 X$ q  ?, z1 U2 m" h$ u) }0 y! k
2、当缓冲区被使用完,且又有新的数据需要存储时,丢掉历史最久的数据,保存最新数据。* s5 Y% l9 Q' P+ P5 Z
/ i( ]4 B7 y/ k
03、代码实现
) i! {9 ]. U1 e- t环形缓冲区的实现很简单,只需要简单的几个接口即可。
8 z- j- _+ l* g, _. N1 T% I8 I- W
; t$ ~! F- d- g! i8 H首先需要创建一个环形缓冲区
* T/ @' y& k1 T3 U# {9 n4 r- B( ~' |1 K! I$ Z# K+ b
  1. #define  RINGBUFF_LEN          (500)     //定义最大接收字节数 500! Q6 ^7 ^& ~; l/ F8 f9 q" K
  2. #define  RINGBUFF_OK           1     
    : B. E1 \1 C# c
  3. #define  RINGBUFF_ERR          0   
    9 B) F; h1 q) X0 r/ J( E
  4. typedef struct$ S# @/ z* c0 x6 b, A
  5. {
    & X3 K2 J2 y8 C7 N
  6.     uint16_t Head;           ! p8 ~( \* B7 j* l. a; J
  7.     uint16_t Tail;
    6 U2 x! F$ |$ O6 k+ |2 J
  8.     uint16_t Lenght;" C& j9 h2 {6 W
  9.     uint8_t  Ring_data[RINGBUFF_LEN];* m  Y- S6 p8 ?: Q' a
  10. }RingBuff_t;
    9 s/ ]8 H1 l/ L
  11. RingBuff_t ringBuff;//创建一个ringBuff的缓冲区
复制代码
) [; P9 T0 W+ x( O
当我们发现环形缓冲区被“冲爆”时,也就是缓冲区满了,但是还有待缓冲的数据时,只需要修改RINGBUFF_LEN的宏定义,增大缓冲区间即可。
5 S# |& \/ P: F! z# F* S% a  {7 N; w
环形缓冲区的初始化7 J: G* o9 d3 p, Z4 q$ v0 V

- U8 E" k( I! m; r& v

  1. 3 ?# ~/ _) h1 v. s
  2. /**
      ]6 a, G- m) I7 r& V# _' K
  3. * @brief  RingBuff_Init
    9 |0 q9 u+ [# B# j; D. W
  4. * @param  void4 v  l1 q. y- L
  5. * @return void
    ! _) Q/ E# x, ?9 R4 i
  6. * @note   初始化环形缓冲区) P0 H- d7 `& y% {
  7. */
    8 ~7 X4 m6 v+ Q. M
  8. void RingBuff_Init(void). b: R6 [6 K, e
  9. {
    # g: b$ j: _- j& s2 h
  10.   //初始化相关信息2 B/ P: l* e! H; r
  11.   ringBuff.Head = 0;
    - p8 S( z5 w% ^& b
  12.   ringBuff.Tail = 0;2 i3 W& w" |8 D. H7 z, d
  13.   ringBuff.Lenght = 0;# ]4 v0 r5 H! {* e: [" X
  14. }
复制代码
0 ]# z! i. a- R/ X; U+ F+ F9 q
主要是将环形缓冲区的头,尾和长度清零,表示没有任何数据存入。
  |1 H$ L9 @8 b  m/ P/ t
- w3 `* X; q- \. ~- y环形缓冲区的写入8 W8 ^1 K, V- M% C$ h# k+ Y; [

: y. z9 k  ]. O
  1. /**$ M4 M7 B% s  w$ K. a/ ~  }. D
  2. * @brief  Write_RingBuff. v9 G! Y+ U* [+ p4 G4 ^) e
  3. * @param  uint8_t data. w4 v# i  N* K/ K
  4. * @return FLASE:环形缓冲区已满,写入失败;TRUE:写入成功
    5 ?9 m  y9 I! g8 O+ m
  5. * @note   往环形缓冲区写入uint8_t类型的数据# V' |! P4 C. H+ Q4 c5 w8 U  H" I
  6. */9 o& m! s& q' m' ?4 I' d
  7. uint8_t Write_RingBuff(uint8_t data)! y9 r4 @" ^: t9 S
  8. {
    + Y1 E6 G/ M7 m; t9 a
  9.   if(ringBuff.Lenght >= RINGBUFF_LEN) //判断缓冲区是否已满
    # c3 a9 T4 i: p+ I3 m
  10.   {
    2 m8 A- Y% J% n. q9 c
  11.     return RINGBUFF_ERR;/ Y. n2 L& h. o8 T
  12.   }" C2 w, |7 O, ]% Z7 S* S' m
  13.   ringBuff.Ring_data[ringBuff.Tail]=data;  C2 _2 @* W  G% b8 T- e3 T
  14.   ringBuff.Tail = (ringBuff.Tail+1)%RINGBUFF_LEN;//防止越界非法访问
    : _4 t7 Q7 f" @8 K1 `2 C4 s
  15.   ringBuff.Lenght++;- G5 {- _9 {' G4 P5 ]) _
  16.   return RINGBUFF_OK;
    8 n+ C2 `. \% x7 K! o4 V1 J
  17. }
复制代码

% X7 T+ e* c6 n/ @* }( A
7 l  `2 v$ Q& y) [这个接口是写入一个字节到环形缓冲区。这里注意:大家可以根据自己的实际应用修改为一次缓冲多个字节。并且这个做了缓冲区满时报错且防止非法越界的处理,大家可以自行修改为缓冲区满时覆盖最早的数据。
: S/ ~$ h: @# \# M3 q/ D& E2 t2 y7 |0 o# _# r
环形缓冲区的读取9 i0 m" i% h! q( C& K2 }

2 x) x4 N- x- H7 y( c  N8 Q
  1. /**7 b: t8 Z' |4 @9 M
  2. * @brief  Read_RingBuff
    * M" S; \  H4 C. J
  3. * @param  uint8_t *rData,用于保存读取的数据, J5 I3 W1 `: B: D$ S* \8 d
  4. * @return FLASE:环形缓冲区没有数据,读取失败;TRUE:读取成功, U9 y# c. i6 i& t3 v2 o. v1 z
  5. * @note   从环形缓冲区读取一个u8类型的数据
    $ J" T0 g/ z) I2 e7 @
  6. */
    5 E: ]7 s9 P; f5 E, A! ^
  7. uint8_t Read_RingBuff(uint8_t *rData)
    2 J- M, ]6 R& x+ T& z# h
  8. {  Q# D& d* o' c9 r( {
  9.   if(ringBuff.Lenght == 0)//判断非空
    3 w# L  F+ M- G& h
  10.   {
    - T( H. _/ T6 B1 e0 B7 K4 H7 S9 E
  11.     return RINGBUFF_ERR;
    ' {" E( d2 J2 u* T8 o
  12.   }! C! @1 t. m% P- N1 B, g
  13.   *rData = ringBuff.Ring_data[ringBuff.Head];//先进先出FIFO,从缓冲区头出
    ( ^, ~# j; O2 c$ F- |
  14.   ringBuff.Head = (ringBuff.Head+1)%RINGBUFF_LEN;//防止越界非法访问
    , M6 w2 C* W% ]% F
  15.   ringBuff.Lenght--;
    ! ]* D* ^# N. }. q# f' b
  16.   return RINGBUFF_OK;* O/ [: s6 E, I' W, |0 K& F
  17. }1 y! W- P+ F4 f& I
复制代码

6 l6 Y9 b. V+ P) g; C1 Z. G$ ]$ @读取的话也很简单,同样是读取一个字节,大家可以自行修改为读取多个字节。& R9 J2 _7 ^  s6 w
, i6 n! c" P: `
04、验证# X8 _2 a7 ?% P, v& b- W/ Y
光说不练假把式,下面我们就来验证上面的代码可行性。# s! K4 l6 C' h7 K) A

3 p" T, v% Y& c  K( B" p串口中断函数中缓冲数据; ?8 ~; J4 H# m

+ K: }8 n. |) C
  1. void USART1_IRQHandler(void)
    * c9 |& g( r0 r6 L1 r
  2. {
    : x8 c& o- a# J! ]' q& e& h
  3.   if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE))2 `4 c5 I+ u4 W- \" c: K6 e5 d4 W
  4.   {6 M% [1 S+ d# ?% Q8 }3 o7 Z
  5.     Write_RingBuff(USART_ReceiveData(USART1));
    1 d  D8 S, w, S; Q& V3 @
  6.     USART_ClearFlag(USART1, USART_FLAG_RXNE);/ I5 {9 Y. E# ^: `7 \
  7.   }
    ' ^/ |# e8 O6 e4 @
  8. }
复制代码

/ t8 g5 }4 ]# G. z$ |' B在主循环中,读取缓冲区的数据,然后发送出去,因为是简单的demo,添加了延时模拟CPU处理其他任务。% J& H: T7 X+ Z1 l& E* E$ i( w
. z6 ]& v+ I' J5 L/ F
* t* h4 W0 V, ?- z
  1. while (1)/ ^% b% `7 |5 r$ d
  2.   {# A8 R' \+ j/ y* S- @
  3.     if(Read_RingBuff(&data))            //从环形缓冲区中读取数据, E3 ?; L5 C8 V' H
  4.     {
    3 B8 `  y) p5 d& v3 d' n" ]
  5.       USART_SendData(USART1, data);' a% h- e; f9 s, v
  6.     }
    - |! r8 Z) Q2 A4 `9 A7 o
  7.     SysCtlDelay(1*(SystemCoreClock/3000));
    % Z6 P( V0 y* C; B9 Q0 K! v3 N
  8.   }
复制代码

* q* \5 h5 y3 K3 Z! u5 u8 j验证,间隔100ms发送数据。7 k+ P$ h# N" Z$ s; G0 b; T
- L* M4 }5 [8 `6 V* \8 B: I  V
b42b6eca089cec31e877ee76445704cc.png 7 ]* T6 f/ _0 c8 K

. E* W& u- `1 S+ k结果显示没有出现丢包问题。如果你的应用场景串口通信速率快,数据量大或处理速度慢导致丢包,建议增大RINGBUFF_LEN的宏定义,增大缓冲区间即可。, x/ R1 B" w: S" w7 |5 d

' L# f' C( S. I4 S% ^
& j7 J* Q, z& w% x) d/ }8 j! F
收藏 评论0 发布时间:2022-4-16 20:00

举报

0个回答

所属标签

相似分享

官网相关资源

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