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
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
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
- #define RINGBUFF_LEN (500) //定义最大接收字节数 500
' |2 m/ D" N+ p) G# X9 V - #define RINGBUFF_OK 1 7 d, l$ t! Y- \3 J
- #define RINGBUFF_ERR 0
1 Y- E3 n. e! g5 @' V! u - typedef struct
- n6 j. h5 j% ]; b - {( e8 z" S$ b; |$ C0 ~7 d
- uint16_t Head;
& {1 h$ `- m; H' o - uint16_t Tail; C& @8 T, g3 b4 Z' W& L
- uint16_t Lenght;5 D/ [- b+ a3 f: s, D6 w1 t
- uint8_t Ring_data[RINGBUFF_LEN];8 B$ }1 u0 s# o( j$ H# H! n2 `
- }RingBuff_t;
. U8 }. s" w+ d0 D. J, O - 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 ?
- C2 Y2 Y% a% `- /**
: {8 }% }7 h- i W+ C) n9 d8 j - * @brief RingBuff_Init
5 }, F2 Z7 m7 s; Q0 @9 y9 D - * @param void# M' C5 L0 X4 t0 i. t& }7 c
- * @return void
$ m$ U% g+ `5 J& F# D, V - * @note 初始化环形缓冲区
& i: Z( J8 Y" Z4 l9 w- j7 [6 Q - */
" {! @ \$ }- F! g4 _; w5 D/ _( y - void RingBuff_Init(void)
: Q# b% l# C* Z& n - {+ Q+ _' W/ H' H% M' {; K# I( J
- //初始化相关信息
% l, m; H& G: W" H4 s/ q - ringBuff.Head = 0;
' e6 @! o2 M$ w" Q" r2 | - ringBuff.Tail = 0;: r0 N( I. T# i. n& |
- ringBuff.Lenght = 0;
0 ?# n s. d. | - }
复制代码
, 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- /**
( G C3 F* z6 S: @; Z0 T( G U - * @brief Write_RingBuff. }6 k. j& P) q) y
- * @param uint8_t data/ u; T q4 x" g8 p: l
- * @return FLASE:环形缓冲区已满,写入失败;TRUE:写入成功
8 L! s! X$ x# K( r, @1 f9 V4 } - * @note 往环形缓冲区写入uint8_t类型的数据
- Y3 _% ~2 ?( [( z - */0 q6 O5 i" H" l2 M
- uint8_t Write_RingBuff(uint8_t data) k4 [7 e0 K! Z; z- Z4 H, E
- {
0 Q# [5 O# `, n4 y- \5 \. y6 C- ` - if(ringBuff.Lenght >= RINGBUFF_LEN) //判断缓冲区是否已满, K+ n! k; c- V7 A4 b
- {% v/ z, \/ |+ A" H7 ]1 P, c
- return RINGBUFF_ERR;" f1 d- n! S% i t- C; V. Z8 @
- }
0 N' \2 g; `$ t5 `' u - ringBuff.Ring_data[ringBuff.Tail]=data;
7 d/ y# h0 J& M# a) j4 K - ringBuff.Tail = (ringBuff.Tail+1)%RINGBUFF_LEN;//防止越界非法访问
- ]& L* s# W' f) H7 N: X v - ringBuff.Lenght++;; z' R. K8 ` G% F' ~
- return RINGBUFF_OK;
5 L w' E6 \7 X" s+ P6 [" j - }
复制代码 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- /**
( L5 E1 g5 z, u7 A - * @brief Read_RingBuff1 [! q# S: f/ Q; D: C0 _
- * @param uint8_t *rData,用于保存读取的数据' Y+ X; f. i; c: o7 m
- * @return FLASE:环形缓冲区没有数据,读取失败;TRUE:读取成功5 w4 ~. \* T1 w8 U Z/ n
- * @note 从环形缓冲区读取一个u8类型的数据3 a5 d( G: c# O
- */
& S% d, C" x- b8 q - uint8_t Read_RingBuff(uint8_t *rData)
7 G) q- u. u3 ^- v - {
7 s- L: t5 u' Z# K- s - if(ringBuff.Lenght == 0)//判断非空/ Z/ w5 T8 r$ l4 H1 u+ ?2 L
- {+ x) _; {2 P% R/ }* `
- return RINGBUFF_ERR;
2 s$ W" ^8 K) y0 x7 R. j - }" F3 {6 k6 a7 ~' ]
- *rData = ringBuff.Ring_data[ringBuff.Head];//先进先出FIFO,从缓冲区头出4 ?7 E7 n7 K& A O! I" L4 K
- ringBuff.Head = (ringBuff.Head+1)%RINGBUFF_LEN;//防止越界非法访问
5 I v9 {. K9 w! [ - ringBuff.Lenght--;- e% J, H8 p: A' ^3 c) f8 J
- return RINGBUFF_OK;: V% x: \% N7 [/ o% N, q! V
- }
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
- void USART1_IRQHandler(void)
4 k2 d/ J& q5 e0 b9 V2 X- v! m2 e. Z - {
, S* p9 B! o3 ?! _ - if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE))
" p; ^( s8 O6 Q( [% e3 P - {) ?; w# X' g" Z7 p: ?: [9 m* ]
- Write_RingBuff(USART_ReceiveData(USART1));
3 V- g. G" J% F/ h2 P1 F - USART_ClearFlag(USART1, USART_FLAG_RXNE);
4 e! M7 }; W3 j; K0 C& i - }
. O7 q! W; g* i1 C9 M - }
复制代码 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- while (1)- w Z: M6 w! n; V4 m* A# u
- {
; R" k/ Z7 F( ~: k! M5 M - if(Read_RingBuff(&data)) //从环形缓冲区中读取数据
% l9 P1 N# I' b) Z - {
7 R, Y* v& x" L - USART_SendData(USART1, data);
# {1 U: ` t# a* {$ Q - }1 s( p$ b) q1 M- v5 C2 x
- SysCtlDelay(1*(SystemCoreClock/3000));
0 r0 L% B1 {4 e" N( J9 V+ n - }
复制代码
9 @- A- w" C* S( D9 r验证,间隔100ms发送数据。: b2 W0 ~0 u7 U- \: v+ n) R, j
" u5 ]) `, P" z2 c3 y
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: @ |