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

STM32进阶之串口环形缓冲区实现  

[复制链接]
xiaojie0513 发布时间:2018-6-4 09:49
本帖最后由 xiaojie0513 于 2018-6-4 09:55 编辑
8 B5 c/ e7 U( z  {! q
9 ^! Q7 e" K6 A队列的概念% ~; W& \, o: v3 }2 ~
在此之前,我们来回顾一下队列的基本概念:# j/ l1 Y. |8 U- R' }+ N9 N# Y# l
  队列 (Queue):是一种先进先出(First In First Out ,简称 FIFO)的线性表,只允许在一端插入(入队),在另一端进行删除(出队)。
: t9 M2 F$ r' G2 g
  I) t! U+ _/ e& I0 l! ^7 |# n/ _
队列的特点
类似售票排队窗口,先到的人看到能先买到票,然后先走,后来的人只能后买到票

1 K) v7 _" j8 p- a0 g: Q队列的常见两种形式" o# s8 N5 l: p4 C
/ r  k9 ~; u4 J6 \" O" j
普通队列* I- n" T" f  f/ `# Z3 q
5 O2 w# I& E( P3 V; u, P
  在计算机中,每个信息都是存储在存储单元中的,比喻一下吧,上图的一些小正方形格子就是一个个存储单元,你可以理解为常见的数组,存放我们一个个的信息。' q6 O) i# X: K; Z

1 S) p" k, _3 V& ^4 |   当有大量数据的时候,我们不能存储所有的数据,那么计算机处理数据的时候,只能先处理先来的,那么处理完后呢,就会把数据释放掉,再处理下一个。那么,已经处理的数据的内存就会被浪费掉。因为后来的数据只能往后排队,如过要将剩余的数据都往前移动一次,那么效率就会低下了,肯定不现实,所以,环形队列就出现了。
2 [6 ^6 P# q$ O! M; |环形队列" T) w1 |/ p! z4 _" z
  它的队列就是一个环,它避免了普通队列的缺点,就是有点难理解而已,其实它就是一个队列,一样有队列头,队列尾,一样是先进先出(FIFO)。我们采用顺时针的方式来对队列进行排序。6 e) B5 d. x/ Q# g
) x  y( Y1 k& k2 C2 i0 [5 u
队列头 (Head) :允许进行删除的一端称为队首。
# G* G! M4 \( `4 \; s1 y* L队列尾 (Tail) :允许进行插入的一端称为队尾。
. ^: F2 S( S' P3 A. o4 e# `' V+ }9 Z% W
  环形队列的实现:在计算机中,也是没有环形的内存的,只不过是我们将顺序的内存处理过,让某一段内存形成环形,使他们首尾相连,简单来说,这其实就是一个数组,只不过有两个指针,一个指向列队头,一个指向列队尾。指向列队头的指针(Head)是缓冲区可读的数据,指向列队尾的指针(Tail)是缓冲区可写的数据,通过移动这两个指针(Head) &(Tail)即可对缓冲区的数据进行读写操作了,直到缓冲区已满(头尾相接),将数据处理完,可以释放掉数据,又可以进行存储新的数据了。9 y+ ^2 ]1 P$ \6 {6 i3 @9 D

/ J( g0 [& \( \" F& X0 Y 实现的原理:初始化的时候,列队头与列队尾都指向0,当有数据存储的时候,数据存储在‘0’的地址空间,列队尾指向下一个可以存储数据的地方‘1’,再有数据来的时候,存储数据到地址‘1’,然后队列尾指向下一个地址‘2’。当数据要进行处理的时候,肯定是先处理‘0’空间的数据,也就是列队头的数据,处理完了数据,‘0’地址空间的数据进行释放掉,列队头指向下一个可以处理数据的地址‘1’。从而实现整个环形缓冲区的数据读写。
# z: t3 s- o3 y. a$ N! z& z# O* }* O2 x3 k) I7 V0 i7 ~& j+ @9 l4 g
  看图,队列头就是指向已经存储的数据,并且这个数据是待处理的。下一个CPU处理的数据就是1;而队列尾则指向可以进行写数据的地址。当1处理了,就会把1释放掉。并且把队列头指向2。当写入了一个数据6,那么队列尾的指针就会指向下一个可以写的地址。
1 }6 U$ [4 i' B
( Q8 g) _% G: f. @, X, m6 _" q4 P
如果你懂了环形队列,那就跟着歌曲来一步步用代码实现吧:
从队列到串口缓冲区的实现, Z' O9 ?2 C5 e; ^1 |
  串口环形缓冲区收发:在很多入门级教程中,我们知道的串口收发都是:接收一个数据,触发中断,然后把数据发回来。这种处理方式是没有缓冲的,当数量太大的时候,亦或者当数据接收太快的时候,我们来不及处理已经收到的数据,那么,当再次收到数据的时候,就会将之前还未处理的数据覆盖掉。那么就会出现丢包的现象了,对我们的程序是一个致命的创伤。6 i9 g6 n/ E3 {; c" l, O

5 B8 u6 S9 v" M& ]/ P
  x6 f9 X( H% ?+ ^7 p) V$ ?! c  那么如何避免这种情况的发生呢,很显然,上面说的一些队列的特性很容易帮我们实现我们需要的情况。将接受的数据缓存一下,让处理的速度有些许缓冲,使得处理的速度赶得上接收的速度,上面又已经分析了普通队列与环形队列的优劣了,那么我们肯定是用环形队列来进行实现了。下面就是代码的实现:# r/ y1 Z/ H/ f8 S% X' i. s
' _% M8 F2 D; Y: f
①定义一个结构体:1 {, X- `6 H% r3 j2 G/ Y3 `% q# k
1typedef struct6 U. |) g* @4 b  z
2{
) E, Z- _4 l8 ?& z# I" S4 x' `
3    u16 Head;             _$ Y: K. g1 X, d
4    u16 Tail;
9 v- ~0 c% j2 y7 F" J, D: Y9 I* z# x5    u16 Lenght;% c  w1 ~/ Z8 _0 ^
6    u8 Ring_Buff[RINGBUFF_LEN];
: Z, j- {$ R7 z4 d7}RingBuff_t;6 T% y% _6 X" I: R8 e7 W! I
8RingBuff_t ringBuff;//创建一个ringBuff的缓冲区: X+ @% p" n+ u: n, v: ~

8 j" v. w" j& w4 m②初始化结构体相关信息:使得我们的环形缓冲区是头尾相连的,并且里面没有数据,也就是空的队列。
5 _1 s" b+ _  u4 p: C- ]! a5 r( d 1/**8 s% @0 ?6 I! R9 E# B
2* @brief  RingBuff_Init- a7 M9 T6 k+ [3 D
3* @param  void
5 a3 @' W8 v0 q  r0 w/ ~ 4* @return void2 S. B" G6 E4 o2 X5 c$ W
5* @author 杰杰& \' }$ j, @6 l
6* @date   20186 U- _/ M$ l7 X6 w* _$ C* g* z: s
7* @version v1.0
8 j2 ?( l$ u+ _4 K" j' I 8* @note   初始化环形缓冲区
( U# Q# o# `8 Y$ S2 q7 u# Q 9*/

5 v% q4 l, D* C; ?" o10void RingBuff_Init(void)
1 n9 _. V) K  p, @; C11
{
+ a+ |- _- j' E) U# L! r12   //初始化相关信息- B/ g5 V- b; b) V8 `9 Z, w
13   ringBuff.Head = 0;- U6 b8 u# j* x! A
14   ringBuff.Tail = 0;
' I9 X) l" y% V; {/ _15   ringBuff.Lenght = 0;
% x6 P7 W" f* O/ m0 Q; X' K8 x16}+ M4 N  c2 Y7 X) v/ J/ A9 ^* d1 q
初始化效果如下:. B+ R5 [% ^2 ~$ [" j) r1 R+ u6 b

+ a+ ^: W# A0 m7 M3 H- _% F/ z
+ ?# o7 @( G8 s. n! J9 Z, |" o2 c' s" v写入环形缓冲区的代码实现: 1/**
* ?7 O$ W# ?' c( Q 2* @brief  Write_RingBuff2 u2 b, ]- b8 [! E6 p4 R
3* @param  u8 data
& W  |" b* c% E1 b( b9 a  v9 o 4* @return FLASE:环形缓冲区已满,写入失败;TRUE:写入成功, z0 N; g" ^) b/ E% n% Z
5* @author 杰杰# O0 D9 x& O; m% t! T
6* @date   2018" e- `( n5 _  A. B
7* @version v1.0/ Y0 X# Z+ P' t# \0 q9 D
8* @note   往环形缓冲区写入u8类型的数据6 |. }$ P$ x& U2 x: |
9*/
/ O# X3 i2 {3 I4 [* |+ L
10u8 Write_RingBuff(u8 data)
8 ?1 Y7 W. R- o6 N11{
% h$ o% H$ b7 O' U5 ^! b2 E4 b/ n! S12   if(ringBuff.Lenght >= RINGBUFF_LEN) //判断缓冲区是否已满
* _% k8 ^# B7 n! I7 T- u13    {
/ H; w* T, x+ w4 n* H  g/ A3 x: d6 J14      return FLASE;, o1 u7 w4 I, k0 U! I, b+ d
15    }
4 j: `% U2 S) A' {: `- {; L16    ringBuff.Ring_Buff[ringBuff.Tail]=data;
' K) v. _* u6 Z  R% D$ r0 }" K: o17//    ringBuff.Tail++;% C# h* J# z, `+ P. A
18    ringBuff.Tail = (ringBuff.Tail+1)%RINGBUFF_LEN;//防止越界非法访问3 i+ O+ @1 T  j+ z/ \* c8 F% M
19    ringBuff.Lenght++;
6 x8 D! I/ A$ b4 p5 R" O" H& t# @20    return TRUE;3 K) T0 l  C! K0 _$ G
21}* G  U0 J5 }. f# Z" P3 L
读取缓冲区的数据的代码实现: 1/**, n/ x5 n. L6 ]; i
2* @brief  Read_RingBuff/ z4 f1 m& J- o* r) m/ ^/ m6 J; H, c+ @
3* @param  u8 *rData,用于保存读取的数据. K$ L2 V0 J! B* U1 M
4* @return FLASE:环形缓冲区没有数据,读取失败;TRUE:读取成功9 Q9 ^, }, p" k. j) v
5* @author 杰杰3 x3 w: F9 w; t! Z) t' U' o  J7 f: F
6* @date   2018
) ~% C; L5 f2 a: U 7* @version v1.0
  w+ V: d" n2 R3 y- F9 h5 A) f 8* @note   从环形缓冲区读取一个u8类型的数据1 _2 ?* k& Z1 Z2 o
9*/
& K# i6 c" e, G. v/ J8 O1 A
10u8 Read_RingBuff(u8 *rData). d. |/ j% @6 i/ s+ n' H. o* a
11{
& U+ {& g# j# g% X12   if(ringBuff.Lenght == 0)//判断非空5 ]2 K: S2 l: f2 h+ D2 {! s
13    {
, R8 L" U5 h/ d/ X, t+ B14       return FLASE;
) _# q. Y; y2 l. Z2 K7 V7 x5 b8 g15    }- }) `$ A/ Z" i& g
16   *rData = ringBuff.Ring_Buff[ringBuff.Head];//先进先出FIFO,从缓冲区头出+ X! y, ~! S$ [
17//   ringBuff.Head++;* G  \! ], X, k, m
18   ringBuff.Head = (ringBuff.Head+1)%RINGBUFF_LEN;//防止越界非法访问6 C5 V. I  G% ?' j  U0 z2 N! J
19   ringBuff.Lenght--;
3 V$ Z" _2 }+ O) t+ P. O% `$ K20   return TRUE;% |# ?3 e( X  K4 ]. `2 w
21}对于读写操作需要注意的地方有两个:
4 o3 b/ ?* W; s* A2 G3 D, Q+ K" ^1:判断队列是否为空或者满,如果空的话,是不允许读取数据的,返回FLASE。如果是满的话,也是不允许写入数据的,避免将已有数据覆盖掉。那么如果处理的速度赶不上接收的速度,可以适当增大缓冲区的大小,用空间换取时间。5 M- w3 Z/ Z2 r  e' Q
2:防止指针越界非法访问,程序有说明,需要使用者对整个缓冲区的大小进行把握。
  s, Z* h2 A) ^0 c那么在串口接收函数中:
4 Y+ ^6 v4 R2 T" G6 V( F. \8 M+ Q
! K; W' a3 Z$ ~& e* U# u$ n2 q1void USART1_IRQHandler(void)   
) c& E% \$ N, r2
{% @3 j% K6 J# A" q) R
3   if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断4 v. D# A4 m1 B. l7 g0 ?
4                   {  ?3 _' k) z. R, J( N4 J$ Y
5           USART_ClearITPendingBit(USART1,USART_IT_RXNE);       //清楚标志位/ x; g0 P4 L+ g9 l6 U
6           Write_RingBuff(USART_ReceiveData(USART1));      //读取接收到的数据& h" \: f8 g  i5 z' M, _& }6 g
7       }* C3 n* x- p' _8 }- y/ Y. u) a
8}
1 c# i0 @4 r( c; ?5 X; }测试效果9 T/ G; ^- d7 [- d6 M
测试数据没有发生丢包现象9 |. y% K3 C! L+ z
补充  对于现在的阶段,杰杰我本人写代码也慢慢学会规范了。所有的代码片段均使用了可读性很强的,还有可移植性也很强的。我使用了宏定义来决定是否开启环形缓冲区的方式来收发数据,移植到大家的代码并不会有其他副作用,只需要开启宏定义即可使用了。5 e2 Y  H+ P4 `/ o1 \
1#define USER_RINGBUFF  1  //使用环形缓冲区形式接收数据1 C. o$ j! Z  K( m
2#if  USER_RINGBUFF  O3 n+ m# ]: k/ N/ Y6 b& _$ [& r6 T
3/**如果使用环形缓冲形式接收串口数据***/
8 e  h5 b) }2 }2 R 4#define  RINGBUFF_LEN          200     //定义最大接收字节数 2002 ?5 A8 R7 s6 Y5 g8 w
5#define  FLASE   1
6 f1 A* F" }9 \) N- s; L 6#define  TRUE    0 3 t/ M8 W6 }/ @) X1 E% k0 ?
7void RingBuff_Init(void);( u, {, Z+ B+ m  s
8u8 Write_RingBuff(u8 data);
6 `2 E& \5 W4 M 9u8 Read_RingBuff(u8 *rData);
1 A6 s" o* r3 A9 u1 X  u3 h10#endif/ _4 k& F5 W1 {5 f6 e- p) T% U+ q

5 M8 m0 |5 m" |3 r2 G    当然,我们完全可以用空闲中断与DMA传输,效率更高,但是某些单片机没有空闲中断与DMA,那么这种环形缓冲区的作用就很大了,并且移植简便。
7 M, s2 t+ ~0 v; a
说明:文章部分截图来源慕课网james_yuan老师的课程
小编:CK
往期精彩回顾
创客:
创客飞梦空间是开源公众号
, o5 d+ M8 \6 i8 _8 F) v7 k欢迎大家分享出去
) G$ R* h4 h, Z3 z% s; N" r8 A也欢迎大家投稿1 P) g+ z7 }0 [$ `2 Q9 g% [

2 U9 v9 n+ S- U/ H6 h" g5 ]8 ]) a' B0 z: v1 O6 [  |( W1 t3 X0 |

/ X* I# Q7 V! p/ I

实验4 串口实验.zip

下载

2.62 MB, 下载次数: 937

评分

参与人数 1 ST金币 +3 收起 理由
努力的人 + 3 加油,欢迎继续分享

查看全部评分

收藏 8 评论112 发布时间:2018-6-4 09:49

举报

112个回答
roguebear2012 回答时间:2018-6-7 21:11:46
给lz看一个移植的linux的串口fifo 用在stm32上的。
. y, g, G: [8 f8 q! [#include <kfifo.h>
" U. ~+ @; `4 j% U) s7 a5 c1 G+ h- u+ k; _7 M' I* `( Z
9 X7 @* z+ P' u5 j0 Y. \  g! }
5 z2 X0 E' D6 M$ y+ a2 g: i: k

' H* X+ |5 v' ]. f4 Tint __kfifo_init(struct __kfifo *fifo, void *buffer, unsigned int size), n0 ~) ^& Y! O  O1 e1 Z& S; X
{( R- x7 r: U% L1 p; e  m+ q
    fifo->in = 0;
" m: z% ?6 B- n) U- L0 h. c    fifo->out = 0;
3 t; O' M$ [# v' W    fifo->mask = 0;
8 H! P2 s# b1 H3 p# l/ ^    fifo->data = buffer;' G  f+ \& q3 _: z
# f/ _* y7 s+ p
    if (size < 2) {
/ _) |* f. P1 T, f        return -1;
: Q- f8 \6 ^: m4 c" p    }) ], @  ~& P" A, V+ L7 Z3 Q/ u
; b% o3 i& |( S, N2 P
    if ((size & (size-1)) != 0)
' L9 S+ K6 z8 b. G+ A    {! T2 e3 W! u' e7 t( i
        return -2;            /*** Must be a power of 2 (2,4,8,16,32,64,128,256,512,...) ***/
! Y. e) L* h% M5 Q% r7 o    }
, E' k* n$ Y; b3 ]6 J: @. z! f5 T, y5 u' ^: a
    fifo->mask = size - 1;
2 Z. B$ l8 N# k7 A! ^* m5 R5 z" {
    return 0;
* @' ~5 ~; @# O- {+ s; b8 d}8 M. y3 K  f- V) }2 B

9 O! S$ {2 M2 }. }& d  C' I* B- o* M

1 n* p$ J, G' u- f" a9 z' {. ^- r( s7 o! |" M
+ @& j9 b* y$ y7 E
unsigned int __kfifo_push_char(struct __kfifo *fifo,  const char *p)
8 c" B# e& F3 a+ ]8 h  X% r# R' e: H{+ Q* l! B: a* d( @

  {8 H: q' d+ ?: @/ C& R7 z    if(( ( fifo->in - fifo->out ) & ( ~fifo->mask ) ) == 0 )9 l, X! C+ r, B) }0 B/ x% k
    {
# f, O4 C3 ?1 r8 D$ D* |        *( (char*)fifo->data + (fifo->in++ & fifo->mask) ) = *p;& ?; F+ D" O: D' u
, t% N! F! v, M0 Z: |! N3 u8 [7 H0 D
        return 1;5 X! w1 x, V6 R1 l. L
    }
% e( y+ z/ q! O6 E$ _6 @! d/ Z" Q, `& G& ?5 z; _
    return 0;4 ~  ], i4 P9 P9 o6 m1 L
}: ]) q: o# Y! G/ {6 l7 U) F

5 X; Q% L& J- [: Q& o3 }3 F# w8 F' l3 L3 o9 s

4 K3 D! [/ y  z6 S9 Y; o' V* S  D( F7 ?! o9 {
- o  ~0 W* t$ R& u
unsigned int __kfifo_pop_char(struct __kfifo *fifo, char* p )
( H* m" m, H9 z6 d0 T4 R$ T{- d. s- }  a/ @5 p8 m! f
    if( fifo->in != fifo->out )% _3 Z: A) g1 @' q# O  Q
    {$ F+ y1 [! T+ [" E  Y! v
        *p =  *(  (char*)fifo->data + (fifo->out++ &  fifo->mask) ) ;
1 i2 c0 A* [* s8 x$ H3 g; ]' ]' U
) M( Y( {( f, Y        return 1;1 `  X: e! Z0 d
    }
2 U; {' |+ g  Z2 s$ w/ v2 r/ E: q* E
    return 0;, I) g% A: C5 h
}6 z2 m3 {, g7 _6 \, r4 R9 f5 v) k1 U
+ d" n: y: a  d9 t

; z) `/ J( [) C% k& x# @8 K( k, F0 e; \( n4 [6 m$ T
hi201803 回答时间:2018-6-14 09:52:46
本帖最后由 hi201803 于 2018-6-14 10:03 编辑
* q1 o! B: h# J; Y- r
$ R: R0 V2 l1 C6 ?( x/ s& X, f% ?ok,  了解了解7 L6 V; d/ t) Y) P
=========================================$ {0 _$ ~; I8 r3 t  L; \
看了源代码,
! K- L4 J; \; Z+ I, {6 b
* j. h6 |% ^* A2 v9 t4 }' o. b串口接收与发送缓冲区FIFO 的实现 , 在 keil 51 里面 有一个简单例子, 非常好. 适用于单一资源提供者与资源消费者, 简单的代码里面就解决了资源提供者与消费者之间协调的问题.  可以去看看.6 {" k7 G3 ?' z4 y5 I8 X+ s! T; g4 J

- W. I+ B+ T2 Z8 K$ E) k% F+ W1 C4 f
七哥 回答时间:2018-6-4 12:50:10
本帖最后由 toofree 于 2018-6-4 16:42 编辑 " l0 {' f/ e: y
xiaojie0513 发表于 2018-6-4 11:09
. Y% O1 ~0 R7 \  k7 d1 x9 X: d数据结构是个好东西,我还得去学

! N1 B4 I/ I: }& K& s$ b我也没正规学过数据结构,是考3级数据库时,数据结构是必须的。
% V. E3 d& w9 {  C" M* ~一个暑假自学完一本数据结构课本,做完一本题库。3 C9 z1 W  P( [; f, H

2 t4 ?! Z) _1 G% N最近让破总给我买了几本书,C++课本、习题、C++数据结构。
MrJiu 回答时间:2018-6-4 09:54:39
还不错。。。其实呢,搞懂数据结构这本书后,这些都是小意思,难点高的是树什么的!!!
电子星辰 回答时间:2018-6-4 09:54:41
有意思,先看看
xiaojie0513 回答时间:2018-6-4 09:56:03
MrJiu 发表于 2018-6-4 09:54/ ~. |+ y. m; n; f
还不错。。。其实呢,搞懂数据结构这本书后,这些都是小意思,难点高的是树什么的!!! ...

$ ^4 X& C3 _% Y! N" h9 ~嘻嘻嘻我瞎搞的
xiaojie0513 回答时间:2018-6-4 09:56:14
电子星辰 发表于 2018-6-4 09:54
* e) Y& H! J, k3 J, o有意思,先看看
2 |) m. `- O. k
勿忘心安110 回答时间:2018-6-4 10:01:12
看看再说
七哥 回答时间:2018-6-4 10:09:15
本帖最后由 toofree 于 2018-6-4 10:14 编辑
0 I/ K) C, H9 ~! V8 J; ]) r9 O; c# K) Z$ D
新上任的版主们最近都很活跃
xiaojie0513 回答时间:2018-6-4 10:15:23
toofree 发表于 2018-6-4 10:09
8 N) b( m  V3 G" o# R* f" R新上任的版主们最近都很活跃
# m) l& |" s( |4 y2 e
是吗是吗
七哥 回答时间:2018-6-4 10:24:14
MrJiu 发表于 2018-6-4 09:54$ b1 Z' X# F+ k
还不错。。。其实呢,搞懂数据结构这本书后,这些都是小意思,难点高的是树什么的!!! ...
# A" d9 n# d3 j. P
自从考完试再没摸过,就记得个“二叉树”名字了
hunyuanqi 回答时间:2018-6-4 11:02:21
看看,学习学习!!!!!!!!!!!!!!!!!
xiaojie0513 回答时间:2018-6-4 11:09:47
toofree 发表于 2018-6-4 10:24  u# Z& F/ E0 H
自从考完试再没摸过,就记得个“二叉树”名字了

/ r4 h4 Y: L; I/ `5 M$ f数据结构是个好东西,我还得去学
kunchen 回答时间:2018-6-4 11:55:53
MrJiu 回答时间:2018-6-4 13:42:11
toofree 发表于 2018-6-4 10:249 U: c* `. t$ f% v% f! Y3 E+ m+ C# p
自从考完试再没摸过,就记得个“二叉树”名字了

8 k( X( e# X3 M数据结构还是用处很大的!!!
yqsqqq 回答时间:2018-6-4 14:01:11
........................\

所属标签

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