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

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

[复制链接]
STMCU小助手 发布时间:2022-4-16 20:00
01、简介
4 P6 @  `5 c" i# ^  q$ D) N6 b在之前的文章《stm32 串口详解》中,我们讲解了串口的基本应用,使用串口中断接收数据,串口中断发送回包(一般可以使用非中断形式发送回包,在数据接收不频繁的应用中。串口接收中断保证串口数据及时响应,使用非中断方式发送回包即可)。
- k% I8 D% r6 q7 V" f3 v/ X; [8 v' v" B: C7 X4 E! ?+ A
除了上述两种场景,还有一种应用场景:串口接收数据长度位置,频率未知,不要求实时处理的场景。如果采用上述方案,接收一帧数据立即处理,那么在处理的时候来的数据包就“丢失”了。这个时候就需要缓冲队列来解决这个问题。  h+ b" r) o. ]4 ?. U; N( v% f- J

' N) V. G/ y% K" t02、缓冲区
) P& R" I4 |% O2 |& U- o4 O缓冲区看名字就知道,是缓冲数据用的。实现缓冲区最简单的办法时,定义多个数组,接收一包数据到数组A,就把接收数据的地址换成数组B,每个数据有个标记字节用于表示这个数组是否收到数据,收到数据是否处理完成。' Y1 c. q+ t! X
4 n( u2 r5 i% @  ]' w( r$ ]1 ]
上述方案是完全可行的,但有缺点:
/ ], I7 Q% u( e5 }. }( d2 s4 p
3 d2 h0 I& F0 @! w! r0 s①缓冲数据组数一定,且有多变量,代码结构不太清晰。- K1 ?2 R; s1 k% j) \

/ R: i% R2 \% V0 D  p②接收数据长度可能大于数组大小,也可能小于数组大小。不灵活,需要接收数据很长时容易出错,且内存利用率低。
" J; e9 I6 ]* K% b- e+ [: o2 ^$ I3 j
解决这个问题的好办法是:环形缓冲区。' F# ^6 H5 J3 a. E

' h5 k0 N6 _5 ?4 P$ s: o5 n环形缓冲区就是一个带“头指针”和“尾指针”的数组。“头指针”指向环形缓冲区中可读的数据,“尾指针”指向环形缓冲区中可写的缓冲空间。通过移动“头指针”和“尾指针”就可以实现缓冲区的数据读取和写入。在通常情况下,应用程序读取环形缓冲区的数据仅仅会影响“头指针”,而串口接收数据仅仅会影响“尾指针”。当串口接收到新的数组,则将数组保存到环形缓冲区中,同时将“尾指针”加1,以保存下一个数据;应用程序在读取数据时,“头指针”加1,以读取下一个数据。当“尾指针”超过数组大小,则“尾指针”重新指向数组的首元素,从而形成“环形缓冲区”!,有效数据区域在“头指针”和“尾指针”之间。如下图
& V$ G+ L* _. ~, v
( K5 Y0 k3 f" M* n. R9 F& I6 S 094c97348810f8c363b8ac02673c96fb.jpg
4 |. d1 E* n1 b1 _' q- m* w
4 j% ~, u* W) M( q  ~如上面说的,环形缓冲区其实就是一个数组,将其“剪开”,然后“拉直”后如下图
" I/ X; s4 k; l) U* r6 y
" a" [6 H& a( u* @: K" |7 x 480cd34c507ef913ca5bd548d6d9df87.jpg % X* B+ X/ P3 q# n
. A# b$ a  Z9 S/ Z: J: [
环形缓冲区的特性
: W' x8 z" A: j& G2 H# K& b; e& {% {* }" B( X
1、先进新出。1 N2 l5 ~. }& E( n- q

% a5 I( _% g0 y, [  j5 m" X2、当缓冲区被使用完,且又有新的数据需要存储时,丢掉历史最久的数据,保存最新数据。+ q# F& A' D+ d% x6 t' w
' U+ `: X/ b. h5 `1 P% B
03、代码实现
+ V. y; P5 F8 M$ w7 h6 C- s3 D环形缓冲区的实现很简单,只需要简单的几个接口即可。
5 V  B+ S! m  a5 y) C6 H% h: I9 \; k) @$ U* j% j) c0 {
首先需要创建一个环形缓冲区
  ^6 V5 H, B/ V3 b0 s
7 m+ F) v5 x9 p$ m6 k, X5 K
  1. #define  RINGBUFF_LEN          (500)     //定义最大接收字节数 500/ D6 A  F+ |0 m) f
  2. #define  RINGBUFF_OK           1     ; X1 G+ M  L+ s6 B
  3. #define  RINGBUFF_ERR          0   ! }' d. O6 y2 I1 S2 T
  4. typedef struct
    1 v. y: ^; `8 g7 _" ?2 c0 S' B
  5. {
    5 s+ N% E9 Y, S
  6.     uint16_t Head;           
      E+ z$ U. s$ ?( J
  7.     uint16_t Tail;# M$ [2 O" ?; C: r
  8.     uint16_t Lenght;) E: R0 g3 x8 I7 N+ }
  9.     uint8_t  Ring_data[RINGBUFF_LEN];
    ! C+ ^0 @. j8 ~  `! R  X& ~
  10. }RingBuff_t;2 l4 H5 A+ i% v  L+ t3 e2 M$ }& X
  11. RingBuff_t ringBuff;//创建一个ringBuff的缓冲区
复制代码

" u0 B5 Z' F- W2 V. p% ?5 W. `4 j! n3 p当我们发现环形缓冲区被“冲爆”时,也就是缓冲区满了,但是还有待缓冲的数据时,只需要修改RINGBUFF_LEN的宏定义,增大缓冲区间即可。& V/ ], C2 ^6 {) l8 M, G$ U
: K: V( r. t- F7 \! |  r' J
环形缓冲区的初始化' K& s; D( l- K1 X
* E: B& F7 w. x( ^& o
  1. . y( v; Q( H4 P8 Q1 I& p- K
  2. /**
    * ^% q/ {/ h3 L; ?
  3. * @brief  RingBuff_Init
    / h+ K7 j) M4 H4 M! n
  4. * @param  void1 c1 x4 N; P; [; T" D
  5. * @return void
    3 N9 c8 m! S! _/ |! j+ U, N% ?
  6. * @note   初始化环形缓冲区
    ! l+ a0 u4 g! @9 F
  7. *// B7 q7 ?+ \- s
  8. void RingBuff_Init(void)
    8 U# `! T$ x  d. {
  9. {
    + S8 @6 h1 p# w! c1 Q% ]6 p
  10.   //初始化相关信息
    $ y5 k5 ]& }# f$ I! n4 u
  11.   ringBuff.Head = 0;
    6 c# _; t; ]4 V' j5 l7 I
  12.   ringBuff.Tail = 0;
    3 w5 H8 P# S: n1 }; i. L0 C
  13.   ringBuff.Lenght = 0;( G; Y- {1 a) I" Z& j
  14. }
复制代码

7 e7 b0 z7 r! p2 d, ~3 H3 p主要是将环形缓冲区的头,尾和长度清零,表示没有任何数据存入。
- E! J. e3 h7 v2 x* K4 G. `; D3 k+ }1 J" R$ }
环形缓冲区的写入' [+ x# u0 d6 q3 Y  e

" K6 I9 {2 A( a
  1. /**: A5 Z% D- r! s! }$ O
  2. * @brief  Write_RingBuff# z: r& ?1 l9 U" B# W
  3. * @param  uint8_t data* K+ B$ z0 ]7 C! u( i$ G+ _. Z
  4. * @return FLASE:环形缓冲区已满,写入失败;TRUE:写入成功
    4 n% V# u0 q8 ?0 ?0 `% s& R3 k
  5. * @note   往环形缓冲区写入uint8_t类型的数据% Z; d" N) X( q( m0 M7 Z/ j
  6. */2 p: B6 W# e( u
  7. uint8_t Write_RingBuff(uint8_t data)6 ^  j& M4 o( m# o2 ]
  8. {
    8 ?3 m# D# G& N+ _# k
  9.   if(ringBuff.Lenght >= RINGBUFF_LEN) //判断缓冲区是否已满! T  }. N. j3 T; p+ ~( Z: S$ W
  10.   {
    0 x6 Q% `9 c7 U- r- ~; G
  11.     return RINGBUFF_ERR;
    * @+ w7 j; |5 L( L( n2 B9 `
  12.   }! N: W4 Q3 H; ^) P+ s) R/ Y
  13.   ringBuff.Ring_data[ringBuff.Tail]=data;
    , y% P6 }3 E! M' z
  14.   ringBuff.Tail = (ringBuff.Tail+1)%RINGBUFF_LEN;//防止越界非法访问0 v  f8 c: e; m0 u' z3 I( o/ {
  15.   ringBuff.Lenght++;
    8 \4 |- L% Y$ I1 A7 Q- k
  16.   return RINGBUFF_OK;6 L# _4 P( {7 A8 }& B! R
  17. }
复制代码

3 ^$ k( D6 K& _
  X9 z: F/ F9 f4 q这个接口是写入一个字节到环形缓冲区。这里注意:大家可以根据自己的实际应用修改为一次缓冲多个字节。并且这个做了缓冲区满时报错且防止非法越界的处理,大家可以自行修改为缓冲区满时覆盖最早的数据。
7 H5 h8 n* }$ K3 Y& G8 T3 c; V& i6 k  o. y/ r# O' M) ~
环形缓冲区的读取; R6 j& v% {% b. r8 ]/ j
# K- i# W7 t& y  u: o0 J
  1. /**; h. a' G; ]9 K3 ]
  2. * @brief  Read_RingBuff
    - N: r" W5 T7 C
  3. * @param  uint8_t *rData,用于保存读取的数据
    - {$ s4 R* Y# X0 k) f+ G2 L, X( t" u# y
  4. * @return FLASE:环形缓冲区没有数据,读取失败;TRUE:读取成功
    $ ]. {* E2 O$ p" R& K! M' n; S
  5. * @note   从环形缓冲区读取一个u8类型的数据
    2 C- {- F- X- f( E
  6. */
    9 ?1 r8 q9 X9 E: N1 k( u
  7. uint8_t Read_RingBuff(uint8_t *rData)
    9 _+ E% e8 [$ ?' d5 ^
  8. {
    0 _& V; X' b. J8 ?& a+ D
  9.   if(ringBuff.Lenght == 0)//判断非空
    - f) C& K2 f. W: ?3 S# V; P
  10.   {
    5 m6 }7 h4 P& A1 t
  11.     return RINGBUFF_ERR;
    % \* ?7 j) k0 R& c' h7 n
  12.   }
    ; w" \0 P; Q. A: z
  13.   *rData = ringBuff.Ring_data[ringBuff.Head];//先进先出FIFO,从缓冲区头出
    $ z) w1 ]/ T) Q" I' H
  14.   ringBuff.Head = (ringBuff.Head+1)%RINGBUFF_LEN;//防止越界非法访问
    6 v4 K7 ?0 _$ p+ L
  15.   ringBuff.Lenght--;
    " U, U- W4 D# \& H0 Q( k
  16.   return RINGBUFF_OK;
    ! A" u0 t- r6 F3 D' |! @
  17. }
    - T# p4 ]$ w4 Y  z0 n
复制代码
0 i0 U' i5 v# M. {
读取的话也很简单,同样是读取一个字节,大家可以自行修改为读取多个字节。
. N! k& U. P4 g+ T" y4 `. |) Q! b5 B" V9 L( G3 |) \5 r6 r7 H
04、验证
7 x) ~+ X- c1 R2 F! O7 x光说不练假把式,下面我们就来验证上面的代码可行性。' L/ y/ C9 F& u9 |9 m8 u+ X+ ^0 x
0 b# n7 n  ]/ d9 Q
串口中断函数中缓冲数据
! D1 o4 U9 r" z4 `. ~  x0 a+ [2 {" I' m
  1. void USART1_IRQHandler(void)4 b& b8 G  m$ v  ^2 @1 x4 ]6 M' P
  2. {, u+ z7 V* s9 \5 D6 o& q$ S* a
  3.   if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE))9 ^1 i4 h: J6 ~+ R# j: M1 h* E
  4.   {
    + L9 _% k" l2 E5 e
  5.     Write_RingBuff(USART_ReceiveData(USART1));
    * e. R" C" I0 F1 t' a, _# K8 x
  6.     USART_ClearFlag(USART1, USART_FLAG_RXNE);
    ! b- Z9 x; u" o  I$ N; a
  7.   }  k/ d. u) n( Z( c
  8. }
复制代码

* ~" f& x" [  i% x3 ~$ F* x% [在主循环中,读取缓冲区的数据,然后发送出去,因为是简单的demo,添加了延时模拟CPU处理其他任务。
% [) p; P# f$ N, R9 b' e. l+ V2 a. b& z, y& m0 c: `, [, t! {
3 ~" F. ^2 m- m; p$ ^2 h8 ?
  1. while (1)
    & a1 w; P/ Z( z! p/ H" j
  2.   {* v2 O+ i2 I- v1 E7 D& ]5 j
  3.     if(Read_RingBuff(&data))            //从环形缓冲区中读取数据. }# P/ ~7 v- W# T
  4.     {7 d# h& W4 W1 v
  5.       USART_SendData(USART1, data);  n% Q" F8 S$ \0 F5 ?4 v* k
  6.     }
    ) k4 S. w7 n# E8 Y
  7.     SysCtlDelay(1*(SystemCoreClock/3000));* u8 \( x0 W  d7 t1 {
  8.   }
复制代码
: J- A# k# \& l
验证,间隔100ms发送数据。* X) T+ x2 v' j" V% t

$ @) A- a- y, C3 ^. d4 R6 [! p b42b6eca089cec31e877ee76445704cc.png
- e& Z5 u# ^" j1 [) e% G9 ]& p0 D6 I7 a
结果显示没有出现丢包问题。如果你的应用场景串口通信速率快,数据量大或处理速度慢导致丢包,建议增大RINGBUFF_LEN的宏定义,增大缓冲区间即可。8 Z- E+ x' ?2 E* w1 B+ L

' B  ~2 k" z( Q: l1 q
' X# K& X. N7 V' e% L8 e4 p( g
赞 收藏 评论0 发布时间:2022-4-16 20:00

举报

0个回答

所属标签

相似技术帖

官网相关资源

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