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

STM32的DMA双缓冲模式详解

[复制链接]
STMCU小助手 发布时间:2023-1-3 18:18
DMA双缓冲是什么?
5 r; `6 x- G. V2 ~# b在使用STM32的DMA时我们通常使用的是普通DMA传输,但STM32自带了一个双缓冲模式设置,这个模式有什么优点呢?
, ~% f5 y2 _7 T- E1 n  `- \0 H
接下来我会在下面的介绍里详细说明:
$ J* k$ t+ Y0 d$ p" {& E
" P' g0 r! r6 H/ `' s  n* dDMA全称Direct Memory Access,即直接内存访问。
( j0 T4 `) e& h' m& L  w普通DMA的使用是在DMA的数据流中进行的,设置好DMA的起点和终点以及传输的数据量即可开启DMA传输。
- E7 l! f8 V. M6 H6 Q* H  l0 ^- [+ _8 ]6 v- C8 K- X
bb2b5bfd30b44de3b44ee1e55e75aca9.png
" P5 R7 x$ S2 T4 e( ?8 |  Y

3 o/ o7 C. D* P0 B! G2 ~. ~DMA开启后就开始从起点将数据搬运至终点,每搬一次,传输数据量大小就减1,当传输数据量为0时,DMA停止搬运。. z2 N8 x. g7 s  \$ C

- N6 D, D0 F4 |. ADMA的传输模式如果是普通模式,则当传输数据量为0时,需要程序中手动将传输数据量重新设置满才能开启下一次的DMA数据传输。+ I/ }$ x$ T9 `) v+ o" z9 s+ E
DMA的传输模式如果是循环模式,则当传输数据量为0时,会自动将传输数据量设置为满的,这样数据就能不断的传输。$ R7 [9 k0 n3 l( ]- g. f

! B* W% @7 J6 l- \' N9 J, C9 m
412b8ec24c624455a582b052a38c291b.png + g" D* ]$ [+ S8 I
* }% ~7 w/ `0 Y! s
那么,双缓冲是什么意思呢?
- T  ~9 Z: b6 O2 b; @* Y我们知道,普通DMA的目标数据储存区域只有一个,也就是如果当数据存满后,新的数据又传输过来了,那么旧的数据会被新的数据覆盖。(这就是普通DMA的缺点)! U8 O2 W) J4 w# E* r
5 {+ n  u/ M- p% c  i' y
1ba7d23fab984cf3a6d86bc2800dee3f.png # \- |" ^1 `3 c. N
3 P* \' Q9 B8 {
% v% j  Y  |) S3 ~9 C
而双缓冲模式下,我们DMA的目标数据储存区域有两个,也就是双缓冲,3 V3 w1 u! f) ~; }+ V1 s
当一次完整的数据传输结束后(即Counter值从初始值变为0),会自动指向另一个内存区域。
1 W% A) a) O# E) Q! O8 \% v6 x2 q) n3 Q" _
5d633c7fc21c43d8abd830b4bc36c772.png " Q1 `0 F- t, F

. ?% H2 c* F" U, e如何使用DMA双缓存?& _/ v+ G8 V0 a
那么我们在程序里怎么使用双缓冲呢?$ F, `5 U% E. P# a2 e' w
这里我们有两种方案使用双缓冲,一种是利用双缓冲自动跳转内存区域,一种是主动跳转内存区域。
; ?$ f% e7 z$ S/ }8 l9 B, h8 t  ?- V: j: \. l- V( i
这里我们使用DMA的传输模式是循环模式6 F. P! Y( ~% ^3 Z1 |/ f
! j3 L1 M/ E2 L' N+ u" {. v
  1. DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;4 G% w5 e. r1 ]& y7 V& C; F
复制代码

5 w( T% W/ A% g0 I- m/ H首先是第一种:
3 A; C4 D; m, v% w" u$ p6 M这里我们以大疆RoboMaster的遥控器使用为示例:0 I& r/ z/ e- f4 m
首先我们定义好双缓冲使用的两个内存区域$ ^& o3 C$ b; v+ \
  1. uint8_t sbus_rx_buffer[2][18u]; //double sbus rx buffer to save data
    9 r5 u3 }7 k* l) b
复制代码
$ ~) U+ K8 w! C8 H/ N  F
这里我们设置长度为18,也就是一帧遥控器数据的长度。" k0 n+ H% J( K5 u$ ]

2 {2 W% J9 J+ `' |3 y/ j
  1. DMA_InitStruct.DMA_BufferSize = 18;
      b- U; J0 ?7 \' v2 b" y
复制代码
0 O5 a& q/ u1 Z  g
然后配置好双缓冲的两个内存区域地址
& w- d9 {$ p% B( O1 q" i1 y1 [
) g8 h1 Y; M1 ]0 Q5 V7 n
  1. DMA_InitStruct.DMA_Memory0BaseAddr =  (uint32_t)&sbus_rx_buffer[0][0];
    8 c+ a! D  ^) S3 t5 j, T
复制代码

. g2 h. m2 K2 ^% s8 y+ d这里配置内存区域2的地址,并把当前DMA指向内存区域1的地址6 H6 o1 X# j4 p# l# ^% }9 V) V$ m% }
4 {* D0 F  D5 k  P5 S6 a
  1. DMA_DoubleBufferModeConfig(DMA1_Stream1,(uint32_t)&sbus_rx_buffer[1][0],DMA_Memory_0); //first used memory configuration$ x/ e) Z% M- ?0 b: w
复制代码
" z6 o) b& |# L4 [% p: ]
启动DMA双缓冲
9 F* J3 z' \/ s5 R) d2 l: ]; {0 J* M
  1. DMA_DoubleBufferModeCmd(DMA1_Stream1, ENABLE);
    ! b; S- u1 Q# e; A
复制代码

" z- u5 u; E, Y9 I我们使用DMA时利用串口的空闲中断接收一帧一帧的数据。6 L+ h7 `2 u7 r& c$ v. J* u
在串口中断处理函数中对数据进行处理$ D; v& g0 Y/ `

/ h1 ^2 I$ p3 ]2 w: Y

  1. 6 |4 H5 z& P, t# i
  2. uint16_t this_time_rx_len = 0;        //当前剩余数据长度
    5 S/ ^5 \) |) j7 b7 o/ Z2 m
  3. / E  \/ [- y+ o) ~
  4. if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)        //判断是否为空闲中断
    4 w: l0 b/ b3 D
  5.     {6 `0 E% l/ B! i4 f2 [* s
  6.         USART_ReceiveData(USART3);        //清除空闲中断标志位+ i2 M' F. z; N* t6 E" t

  7. " a2 V, a4 {- \) d
  8.         if(DMA_GetCurrentMemoryTarget(DMA1_Stream1) == DMA_Memory_0)        //获取当前目标内存是否为 DMA_Memory_00 ^: }4 S4 p! k- R) ]
  9.         {
    / W1 y; ^4 u2 e
  10.             //重新设置DMA
    - f5 [; W& k9 ?2 ~( o  c' m
  11.             DMA_Cmd(DMA1_Stream1, DISABLE);9 s0 |- H2 A& D1 q+ M
  12.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);        //获取当前剩余数据量. Q4 H$ S5 B' T# h% Z9 M- i6 E0 k
  13.                                        
    + e4 {. R2 A* e  A7 l' t
  14.                                          DMA_SetCurrDataCounter(DMA1_Stream1, 18);        //重新设置数据量. k( U, p8 C. ~/ j7 j+ `9 L
  15.                                           4 S6 h. \5 @& \1 b+ _( _: M6 B
  16.                                          DMA_Cmd(DMA1_Stream1, ENABLE);( P+ ^# l7 t3 [" K3 J
  17.             if(this_time_rx_len == 18)        //接收成功18个字节长度' c# a( i/ J, G! [$ g( n
  18.             {
    # ]* d, \  j( w. K& E3 z7 V. }
  19.                 //处理遥控器数据
    7 m" C3 u' h, g; X, R/ K; t/ k
  20.                 RemoteDataProcess(sbus_rx_buffer[1]);        //Memory_1: y5 j8 O1 v* c% t) z
  21.             }
      L# h5 G) q' L

  22. ' ?1 i  T, Q+ p# a. h( P
  23.         }2 X: d0 `, q) _( ~2 v+ @
  24.         else        //获取当前目标内存是否为 DMA_Memory_1/ y8 e9 U% N- P  S
  25.         {
    0 k  O# D+ v# B+ n
  26.             //重新设置DMA
    3 G7 T1 f; R6 U6 r& G( h
  27.             DMA_Cmd(DMA1_Stream1, DISABLE);
    7 j1 u: J2 p/ C6 S- [; j
  28.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);                //获取当前剩余数据量
    ! {0 _! }  _1 x0 T/ ^

  29. + X1 |/ [# g9 p9 M3 n7 u. h
  30.                                                 DMA_SetCurrDataCounter(DMA1_Stream1, 18);        //重新设置数据量. u: T. u, H3 \0 u& ]# H" ?) @

  31. 1 D4 \$ q' @5 F8 E+ v% P
  32.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);
    . x" y5 f/ @7 j% Y3 |) a

  33. / z2 ~; Y) t( F8 B% }1 w' `
  34.             if( this_time_rx_len== 18)        //接收成功18个字节长度' `& O+ e- F) r" v
  35.             {' t/ e  C4 F# n% ]
  36.                 //处理遥控器数据
    3 `# F+ d- x" Z3 n6 G3 O1 Z
  37.                 RemoteDataProcess(sbus_rx_buffer[0]);        //Memory_0" y4 R' p* s+ l5 W
  38.             }1 N* z3 v2 N( l2 d/ a

  39. . Q& Q# C; l" r1 w+ J/ \& X
  40.         }
    8 ]& ^3 @+ `- R/ Q+ x8 t( |. Y, C: m
  41.     }4 B- u. z) f8 R5 `
复制代码

) g& a: Z0 f* I% A4 q4 [该逻辑在程序复位后,当一帧遥控器数据发送过来后,Memory0区域被填满18个字节,DMA自动指向Memory1,所以第一次进入中断处理函数时,获取内存指向的时候得到的指向是Memory1。
! F9 m. \: S0 h. u4 O& o然后判断剩余数据大小是否为18,如果是则说明上一帧数据接收正常,可以对数据进行处理,如果不为18,则说明上一帧数据接收长度不正常,这时候就不对数据进行处理,当下一帧数据发送过来后对数据进行检验,正常后才对数据进行处理。
, k' w. ^+ d3 H+ V1 C7 d/ K% P- X9 B9 }" {
然后是第二种:, ~: n# ~. W" z, o/ s
这里我们将BufferSize改为比一帧数据长度大的值(比18大),这样可以在一帧数据传输完成后不会因Counter值变0导致DMA指向下一内存区域。. [  Z& n- C. D
& P9 B- l% Q$ S* C
  1. DMA_InitStruct.DMA_BufferSize = 36;$ i. r7 u; V9 J& y2 {; s$ l* D
复制代码
  1. uint16_t this_time_rx_len = 0;        //当前剩余数据长度) E4 v( C7 q! X, u! r7 p

  2. ' t" p) |4 j# `5 B2 v
  3. if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)        //判断是否为空闲中断
    5 w+ S7 R7 }3 `/ F$ n7 f; P
  4.     {. [  y2 b3 `' x( t
  5.         USART_ReceiveData(USART3);        //清除空闲中断标志位
    ) [0 a: }$ a2 T
  6. 4 D5 H8 A8 d* K  a
  7.         if(DMA_GetCurrentMemoryTarget(DMA1_Stream1) == DMA_Memory_0)        //获取当前目标内存是否为 DMA_Memory_0+ w1 o+ C& S$ ?$ A) R" w+ M! e
  8.         {' m8 o- l0 s* `% x: Y4 Y8 O2 G
  9.             //重新设置DMA
    2 W  B7 f2 e! t* R! I# b+ ?' u8 N
  10.             DMA_Cmd(DMA1_Stream1, DISABLE);
    " u( {2 w& e& d8 D; t
  11.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);        //获取当前剩余数据量  接收18字节,则剩余数据量为36-18=18
    , f; I5 p2 h% r, m# d* f/ [
  12.                                        
    . m7 k) R2 i7 u. |
  13.                                           DMA_SetCurrDataCounter(DMA1_Stream1, 36);        //重新设置数据量; p: e# z; L. I+ n+ q
  14.                                                 DMA1_Stream1->CR |= (uint32_t)(DMA_SxCR_CT);  //将DMA指向Memory18 k. n" {4 x+ X4 D2 E8 {, n1 O- E
  15.                                         ( p) w/ w. b' m  J
  16.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);# {9 a$ U! J; J+ f5 O" c
  17.             if(this_time_rx_len == 18)        //接收成功18个字节长度' A  l! f" }, w3 p9 z- V  j3 T' D
  18.             {
      w. P* c" l% _
  19.                 //处理遥控器数据) y! n' I; `# j: N
  20.                 RemoteDataProcess(sbus_rx_buffer[0]);        //Memory_0( I+ Y5 w# O% x) Z
  21.             }
    # c7 J% d4 ?; E/ M6 u7 \+ W9 t! j6 ^
  22. " [( o7 m- a$ }5 H! g0 D
  23.         }
    4 `0 j/ L% h; ^5 ?
  24.         else        //获取当前目标内存是否为 DMA_Memory_1
    8 O4 f& k; Y4 e, {9 S) R
  25.         {
    # U& ?/ T- T! S  I; q$ z0 k
  26.             //重新设置DMA
    3 [+ K6 q9 z0 ~# Y
  27.             DMA_Cmd(DMA1_Stream1, DISABLE);
    - b  G' L1 h2 y7 C
  28.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);                //获取当前剩余数据量 接收18字节,则剩余数据量为36-18=180 V6 T7 n0 f' I7 b6 i! l2 z
  29. : D: h6 z5 t, S1 T) N
  30.                                                 DMA_SetCurrDataCounter(DMA1_Stream1, 36);        //重新设置数据量$ H7 Z/ d  V' v, Y+ m& f
  31.                                                 DMA1_Stream1->CR &= ~(uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0
    $ W8 d9 L  |/ n9 o. n/ D
  32. - ?4 z7 N2 k1 F8 N; d- ~
  33.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);
    , ~, _" y! f/ k" Q/ Z" E% M5 h. y

  34. ! v' K1 M( e5 t8 O7 `
  35.             if( this_time_rx_len == 18)        //接收成功18个字节长度
    0 t- U6 i% n6 e" W3 i9 W
  36.             {
    ! _% p9 R+ z8 U0 R- x; y" ~
  37.                 //处理遥控器数据! z" E! A0 Y  n  h3 v  x. C- R. N
  38.                 RemoteDataProcess(sbus_rx_buffer[1]);        //Memory_1
    ) u: a1 d$ v* M" l; t% b
  39.             }% n; _, z' R& G5 \  t
  40. % m- y" {0 g0 o/ k
  41.         }
    / K. W6 I6 D- C3 U* y  E
  42.     }# h0 v* A$ w# |- Q- @" Z
复制代码
0 f. N9 u5 ~! [  w
两种DMA双缓冲使用方法的区别* b( j( N5 R6 {& B3 D% C( F& T
第二种方法和第一种方法的区别大同小异,这种方法由于将BufferSize设为比18大的值,也就是当第一帧数据传输完成后,Counter值不会自动填满,且内存区域还是指向Memory0,然后我们将剩余数据量保存下来,再将Counter值填满,接着把DMA指向Memory1,最后通过判断剩余数据量来决定是否对数据进行处理。
9 I. t; Y, s3 c+ W& v& o8 J
8 {( ], {' F- ]- o0 Q5 r4 N: c6 A可以看到,在第一种方法下,我们并没有使用; J$ ^/ o- q( |( ]

/ N% U; @! W/ _
  1. DMA1_Stream1->CR &= ~(uint32_t)(DMA_SxCR_CT);  将DMA指向Memory02 E3 Q* t0 J0 f$ ]8 q+ O3 i
复制代码
  1. DMA1_Stream1->CR |= (uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0
复制代码
' E8 e% U! ?! \

; h( u& ^! I0 c. J" D2 |这是因为当传输数据量和BufferSize大小一致时,完整接收完一帧数据后,DMA会自动指在向下一内存地址,因此在第二种方法中的手动改变内存指向就由DMA自动处理了。3 y9 B1 Z: D) d" M" O4 O
————————————————# ~* D- {# V+ v4 i
版权声明:水木皆Ming
* C3 M, k4 L0 L2 Y: M3 Z0 Q1 |& C+ |$ l
4 S8 }! w5 d9 {) Y. l+ R
收藏 评论1 发布时间:2023-1-3 18:18

举报

1个回答
YK1 回答时间:4 天前

请问一下,使用DMA双缓冲区进行数据的发送的时候,出现了重复发送数据的情况,请问该怎么解决呢?感觉主要是剩余文件数量达不到BUFFER_SIZE要求,带来的重复发送数据。

关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版