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

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

[复制链接]
STMCU小助手 发布时间:2022-4-16 20:00
01、简介
2 N7 x! n1 k$ C! l7 a在之前的文章《stm32 串口详解》中,我们讲解了串口的基本应用,使用串口中断接收数据,串口中断发送回包(一般可以使用非中断形式发送回包,在数据接收不频繁的应用中。串口接收中断保证串口数据及时响应,使用非中断方式发送回包即可)。, r+ A: H- ]' \  y: O& O" r
& a2 R6 `  ^6 A! P
除了上述两种场景,还有一种应用场景:串口接收数据长度位置,频率未知,不要求实时处理的场景。如果采用上述方案,接收一帧数据立即处理,那么在处理的时候来的数据包就“丢失”了。这个时候就需要缓冲队列来解决这个问题。
0 W, ~: {2 [( p, J0 u# D
+ w- V  U5 m3 p7 n02、缓冲区& ?6 A/ {2 \- O
缓冲区看名字就知道,是缓冲数据用的。实现缓冲区最简单的办法时,定义多个数组,接收一包数据到数组A,就把接收数据的地址换成数组B,每个数据有个标记字节用于表示这个数组是否收到数据,收到数据是否处理完成。
8 f6 S4 G, b  E! q. P0 g9 F/ |
, T, y% T+ e: [+ v上述方案是完全可行的,但有缺点:
$ v0 j4 R3 {8 k2 _9 A; J# f$ {  ^4 P0 [% l
①缓冲数据组数一定,且有多变量,代码结构不太清晰。
# t! U! f( \* {. W8 c+ x! v' H+ J2 p0 x
②接收数据长度可能大于数组大小,也可能小于数组大小。不灵活,需要接收数据很长时容易出错,且内存利用率低。$ b" L2 Y, X) M/ S- p# _6 O

, z) W& V6 }  ~9 F解决这个问题的好办法是:环形缓冲区。
, S7 V: E! Q: Z
! o* ~$ G$ F+ l& [环形缓冲区就是一个带“头指针”和“尾指针”的数组。“头指针”指向环形缓冲区中可读的数据,“尾指针”指向环形缓冲区中可写的缓冲空间。通过移动“头指针”和“尾指针”就可以实现缓冲区的数据读取和写入。在通常情况下,应用程序读取环形缓冲区的数据仅仅会影响“头指针”,而串口接收数据仅仅会影响“尾指针”。当串口接收到新的数组,则将数组保存到环形缓冲区中,同时将“尾指针”加1,以保存下一个数据;应用程序在读取数据时,“头指针”加1,以读取下一个数据。当“尾指针”超过数组大小,则“尾指针”重新指向数组的首元素,从而形成“环形缓冲区”!,有效数据区域在“头指针”和“尾指针”之间。如下图7 a% N! S' E. ~3 |% Y# G; ~9 Y3 c
9 w0 {; W" [5 }: h( a
094c97348810f8c363b8ac02673c96fb.jpg 4 C8 I/ e/ z2 N2 ~8 L

9 a5 C; w) k; p) y4 e4 T如上面说的,环形缓冲区其实就是一个数组,将其“剪开”,然后“拉直”后如下图+ h2 Q! i$ m" b, R: ]# O
& P+ P+ Y% G% L
480cd34c507ef913ca5bd548d6d9df87.jpg 3 i# ^8 s* Z/ z* o

8 H4 ?# Y0 M7 `环形缓冲区的特性+ @0 k1 {& w  x3 C3 |) W
" L9 h, c  [# ]# g
1、先进新出。
+ ?- @9 \2 N) ]# x! z
8 k  f8 ^7 M# U2 c" F- x5 E2、当缓冲区被使用完,且又有新的数据需要存储时,丢掉历史最久的数据,保存最新数据。. A& h) X5 L4 }
2 r! d9 _" m+ d( Y+ z3 E/ ?
03、代码实现
; O; P7 U, x* G6 f, u环形缓冲区的实现很简单,只需要简单的几个接口即可。
1 s, m. }* R- I% H- m/ G/ t
4 Q4 ]3 ?# B3 W3 I( H0 L首先需要创建一个环形缓冲区
, m6 M9 Q4 e# g% m3 X6 S3 ?1 o8 Q8 g5 O+ g% v1 |: e
  1. #define  RINGBUFF_LEN          (500)     //定义最大接收字节数 500
    ' |2 m/ D" N+ p) G# X9 V
  2. #define  RINGBUFF_OK           1     7 d, l$ t! Y- \3 J
  3. #define  RINGBUFF_ERR          0   
    1 Y- E3 n. e! g5 @' V! u
  4. typedef struct
    - n6 j. h5 j% ]; b
  5. {( e8 z" S$ b; |$ C0 ~7 d
  6.     uint16_t Head;           
    & {1 h$ `- m; H' o
  7.     uint16_t Tail;  C& @8 T, g3 b4 Z' W& L
  8.     uint16_t Lenght;5 D/ [- b+ a3 f: s, D6 w1 t
  9.     uint8_t  Ring_data[RINGBUFF_LEN];8 B$ }1 u0 s# o( j$ H# H! n2 `
  10. }RingBuff_t;
    . U8 }. s" w+ d0 D. J, O
  11. RingBuff_t ringBuff;//创建一个ringBuff的缓冲区
复制代码

4 n! b# K& r" x9 `# V/ [当我们发现环形缓冲区被“冲爆”时,也就是缓冲区满了,但是还有待缓冲的数据时,只需要修改RINGBUFF_LEN的宏定义,增大缓冲区间即可。4 ]3 ~7 F8 {) I( @" m" ~  U# I' R

+ D1 V0 `/ R! s# B6 S- a" j8 n环形缓冲区的初始化; r7 T  h+ U8 v) ~
6 b9 [* [4 O+ z, K7 ?

  1. - C2 Y2 Y% a% `
  2. /**
    : {8 }% }7 h- i  W+ C) n9 d8 j
  3. * @brief  RingBuff_Init
    5 }, F2 Z7 m7 s; Q0 @9 y9 D
  4. * @param  void# M' C5 L0 X4 t0 i. t& }7 c
  5. * @return void
    $ m$ U% g+ `5 J& F# D, V
  6. * @note   初始化环形缓冲区
    & i: Z( J8 Y" Z4 l9 w- j7 [6 Q
  7. */
    " {! @  \$ }- F! g4 _; w5 D/ _( y
  8. void RingBuff_Init(void)
    : Q# b% l# C* Z& n
  9. {+ Q+ _' W/ H' H% M' {; K# I( J
  10.   //初始化相关信息
    % l, m; H& G: W" H4 s/ q
  11.   ringBuff.Head = 0;
    ' e6 @! o2 M$ w" Q" r2 |
  12.   ringBuff.Tail = 0;: r0 N( I. T# i. n& |
  13.   ringBuff.Lenght = 0;
    0 ?# n  s. d. |
  14. }
复制代码

, C8 O: T$ p% F) c% Y  p主要是将环形缓冲区的头,尾和长度清零,表示没有任何数据存入。
2 t8 A1 P- \1 t4 x# n! W8 S* k9 n3 C1 B4 ^/ Y
环形缓冲区的写入
- m, J7 K. \5 i: A( _/ {+ H! z$ v8 w
1 m2 J* a) [/ ]0 E6 W( u3 D
  1. /**
    ( G  C3 F* z6 S: @; Z0 T( G  U
  2. * @brief  Write_RingBuff. }6 k. j& P) q) y
  3. * @param  uint8_t data/ u; T  q4 x" g8 p: l
  4. * @return FLASE:环形缓冲区已满,写入失败;TRUE:写入成功
    8 L! s! X$ x# K( r, @1 f9 V4 }
  5. * @note   往环形缓冲区写入uint8_t类型的数据
    - Y3 _% ~2 ?( [( z
  6. */0 q6 O5 i" H" l2 M
  7. uint8_t Write_RingBuff(uint8_t data)  k4 [7 e0 K! Z; z- Z4 H, E
  8. {
    0 Q# [5 O# `, n4 y- \5 \. y6 C- `
  9.   if(ringBuff.Lenght >= RINGBUFF_LEN) //判断缓冲区是否已满, K+ n! k; c- V7 A4 b
  10.   {% v/ z, \/ |+ A" H7 ]1 P, c
  11.     return RINGBUFF_ERR;" f1 d- n! S% i  t- C; V. Z8 @
  12.   }
    0 N' \2 g; `$ t5 `' u
  13.   ringBuff.Ring_data[ringBuff.Tail]=data;
    7 d/ y# h0 J& M# a) j4 K
  14.   ringBuff.Tail = (ringBuff.Tail+1)%RINGBUFF_LEN;//防止越界非法访问
    - ]& L* s# W' f) H7 N: X  v
  15.   ringBuff.Lenght++;; z' R. K8 `  G% F' ~
  16.   return RINGBUFF_OK;
    5 L  w' E6 \7 X" s+ P6 [" j
  17. }
复制代码
5 `$ e4 v% X# u1 Q7 s; F
7 k7 b4 r. T- _  }! v& ~& [, T, r3 V
这个接口是写入一个字节到环形缓冲区。这里注意:大家可以根据自己的实际应用修改为一次缓冲多个字节。并且这个做了缓冲区满时报错且防止非法越界的处理,大家可以自行修改为缓冲区满时覆盖最早的数据。$ |" ?- ?  n, n( l3 d& Y/ }

1 M6 X" V* w' M0 w( l9 w环形缓冲区的读取; W9 l$ |+ H- \' i; e' P

7 n- V+ V$ Z4 i) ~; k2 J
  1. /**
    ( L5 E1 g5 z, u7 A
  2. * @brief  Read_RingBuff1 [! q# S: f/ Q; D: C0 _
  3. * @param  uint8_t *rData,用于保存读取的数据' Y+ X; f. i; c: o7 m
  4. * @return FLASE:环形缓冲区没有数据,读取失败;TRUE:读取成功5 w4 ~. \* T1 w8 U  Z/ n
  5. * @note   从环形缓冲区读取一个u8类型的数据3 a5 d( G: c# O
  6. */
    & S% d, C" x- b8 q
  7. uint8_t Read_RingBuff(uint8_t *rData)
    7 G) q- u. u3 ^- v
  8. {
    7 s- L: t5 u' Z# K- s
  9.   if(ringBuff.Lenght == 0)//判断非空/ Z/ w5 T8 r$ l4 H1 u+ ?2 L
  10.   {+ x) _; {2 P% R/ }* `
  11.     return RINGBUFF_ERR;
    2 s$ W" ^8 K) y0 x7 R. j
  12.   }" F3 {6 k6 a7 ~' ]
  13.   *rData = ringBuff.Ring_data[ringBuff.Head];//先进先出FIFO,从缓冲区头出4 ?7 E7 n7 K& A  O! I" L4 K
  14.   ringBuff.Head = (ringBuff.Head+1)%RINGBUFF_LEN;//防止越界非法访问
    5 I  v9 {. K9 w! [
  15.   ringBuff.Lenght--;- e% J, H8 p: A' ^3 c) f8 J
  16.   return RINGBUFF_OK;: V% x: \% N7 [/ o% N, q! V
  17. }
    1 T; V$ n% c: X) v
复制代码
- d) D7 w0 a) |4 Q+ D+ h! f2 Y
读取的话也很简单,同样是读取一个字节,大家可以自行修改为读取多个字节。3 Z; ^- ^2 C$ B2 H( N

, f; U: Q' }$ I! [0 u04、验证
  ?, v4 s& H7 A! \* ~2 |光说不练假把式,下面我们就来验证上面的代码可行性。3 d) T$ I( F( A- n% z  p/ \
2 U, Z* [+ O8 ]9 ^2 p( Q* w1 ?
串口中断函数中缓冲数据6 u1 S1 Q, d" s; x# y% f9 y0 @9 T, d
% U7 i5 A8 B8 e4 v% |4 [6 J1 \2 Y
  1. void USART1_IRQHandler(void)
    4 k2 d/ J& q5 e0 b9 V2 X- v! m2 e. Z
  2. {
    , S* p9 B! o3 ?! _
  3.   if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE))
    " p; ^( s8 O6 Q( [% e3 P
  4.   {) ?; w# X' g" Z7 p: ?: [9 m* ]
  5.     Write_RingBuff(USART_ReceiveData(USART1));
    3 V- g. G" J% F/ h2 P1 F
  6.     USART_ClearFlag(USART1, USART_FLAG_RXNE);
    4 e! M7 }; W3 j; K0 C& i
  7.   }
    . O7 q! W; g* i1 C9 M
  8. }
复制代码
2 T& G& y  y4 q
在主循环中,读取缓冲区的数据,然后发送出去,因为是简单的demo,添加了延时模拟CPU处理其他任务。1 H& o/ C& p" D5 k6 X
' L: p* n0 H3 S% P* P+ s

% R. e( z, q% N3 A
  1. while (1)- w  Z: M6 w! n; V4 m* A# u
  2.   {
    ; R" k/ Z7 F( ~: k! M5 M
  3.     if(Read_RingBuff(&data))            //从环形缓冲区中读取数据
    % l9 P1 N# I' b) Z
  4.     {
    7 R, Y* v& x" L
  5.       USART_SendData(USART1, data);
    # {1 U: `  t# a* {$ Q
  6.     }1 s( p$ b) q1 M- v5 C2 x
  7.     SysCtlDelay(1*(SystemCoreClock/3000));
    0 r0 L% B1 {4 e" N( J9 V+ n
  8.   }
复制代码

9 @- A- w" C* S( D9 r验证,间隔100ms发送数据。: b2 W0 ~0 u7 U- \: v+ n) R, j

" u5 ]) `, P" z2 c3 y b42b6eca089cec31e877ee76445704cc.png 4 g+ z' }, S3 ^
* Y$ j8 M  B% J, v
结果显示没有出现丢包问题。如果你的应用场景串口通信速率快,数据量大或处理速度慢导致丢包,建议增大RINGBUFF_LEN的宏定义,增大缓冲区间即可。
9 O3 I. w" K' i, d$ d4 m5 w
1 H6 |+ U+ v0 u
; _7 D* I1 e: @
赞 收藏 评论0 发布时间:2022-4-16 20:00

举报

0个回答

所属标签

相似技术帖

官网相关资源

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