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

STM32的DMA双缓冲模式详解

[复制链接]
STMCU小助手 发布时间:2023-1-3 18:18
DMA双缓冲是什么?
+ U- n( j: B/ m, A3 ^5 k在使用STM32的DMA时我们通常使用的是普通DMA传输,但STM32自带了一个双缓冲模式设置,这个模式有什么优点呢?+ M" S+ {1 V* K
接下来我会在下面的介绍里详细说明:
- X! f7 q5 i. K9 p- O  y3 M9 V1 G& a; l1 B8 {
DMA全称Direct Memory Access,即直接内存访问。+ \2 E7 J2 {9 D4 x) t
普通DMA的使用是在DMA的数据流中进行的,设置好DMA的起点和终点以及传输的数据量即可开启DMA传输。
' ]$ c6 u% R2 L+ y7 G2 M, q, C% f, _& P7 U7 [( j& W5 Q6 T' X
bb2b5bfd30b44de3b44ee1e55e75aca9.png * t  ^" s6 w8 K# d6 B$ v

  G3 `, N" ?# x, A9 H2 KDMA开启后就开始从起点将数据搬运至终点,每搬一次,传输数据量大小就减1,当传输数据量为0时,DMA停止搬运。* J$ r' J1 `3 ]7 G% F3 O& ]
4 \% ]5 w8 O+ k' m* L7 ]; }
DMA的传输模式如果是普通模式,则当传输数据量为0时,需要程序中手动将传输数据量重新设置满才能开启下一次的DMA数据传输。
: N4 p9 ^: f7 O" h& w; }DMA的传输模式如果是循环模式,则当传输数据量为0时,会自动将传输数据量设置为满的,这样数据就能不断的传输。2 }' f* [- C. V" h
" a' ]2 B. n% E8 W/ r" T
412b8ec24c624455a582b052a38c291b.png
5 B5 u/ R; S' m  L' \
/ r: ^- d2 D/ E1 D
那么,双缓冲是什么意思呢?
6 e" A4 j6 D( w) Z& b: n$ r, ]我们知道,普通DMA的目标数据储存区域只有一个,也就是如果当数据存满后,新的数据又传输过来了,那么旧的数据会被新的数据覆盖。(这就是普通DMA的缺点)/ j5 o  K0 a9 G. Q) Y8 F
4 \) B( ]8 s6 q, X
1ba7d23fab984cf3a6d86bc2800dee3f.png
/ [- t- h$ E& p1 s- y4 F& n
+ A1 w1 q7 p4 K/ a
+ v) N6 E& G/ ^5 o7 i. p
而双缓冲模式下,我们DMA的目标数据储存区域有两个,也就是双缓冲,
1 C& F% R, @" S( n* Z9 b- Q当一次完整的数据传输结束后(即Counter值从初始值变为0),会自动指向另一个内存区域。
: I( \+ N  v* P# i% e; \
& ?* M% q6 z0 ]; [% c  @, u' P6 v
5d633c7fc21c43d8abd830b4bc36c772.png
+ Z3 C2 ~/ I- i
5 G- }) P, c* i+ v: i/ w# f0 l! d如何使用DMA双缓存?
: d! s% [+ O  R- w那么我们在程序里怎么使用双缓冲呢?
. o0 @" t% [1 u6 \这里我们有两种方案使用双缓冲,一种是利用双缓冲自动跳转内存区域,一种是主动跳转内存区域。* v2 _1 }! }# R5 V# a1 x
7 l- O% A4 M! a6 G1 y, H
这里我们使用DMA的传输模式是循环模式
! B0 D2 J/ j7 N* T5 G  Y3 R2 S/ C3 h+ f1 u/ \6 F/ L  v4 O
  1. DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;) h, z/ |- F# P% _* k& K) `) o
复制代码
9 F; g" O) s& @2 A  J
首先是第一种:
2 Z8 L5 G) J" _" d# M/ S  G3 I这里我们以大疆RoboMaster的遥控器使用为示例:
$ G; c- X3 U& k. `) D+ ~  }0 ^3 v0 |( \首先我们定义好双缓冲使用的两个内存区域; K) a5 F, o6 U' B* {8 n2 S1 o
  1. uint8_t sbus_rx_buffer[2][18u]; //double sbus rx buffer to save data
    3 `: }: {5 x: S5 N" L% P
复制代码
& o' y- j! J  I% P7 J, X) L6 B8 p. j
这里我们设置长度为18,也就是一帧遥控器数据的长度。
# o$ n* O8 B( n- V) k9 D
# |. b( {9 s1 {+ `( J4 O
  1. DMA_InitStruct.DMA_BufferSize = 18;" Q8 o. Q/ X8 I) C! Z
复制代码
2 d% R7 k5 d- }' F8 r3 E- Q6 a7 A
然后配置好双缓冲的两个内存区域地址' x! O: P5 W2 N. ^% U

, N! A" g! T8 P  m
  1. DMA_InitStruct.DMA_Memory0BaseAddr =  (uint32_t)&sbus_rx_buffer[0][0];
    , @  Z% i% i+ \: h) `* h
复制代码
9 ]0 ^* M% S1 G
这里配置内存区域2的地址,并把当前DMA指向内存区域1的地址
& @+ v3 p, V0 o. x
  J7 {/ u' b! F( d: n4 o
  1. DMA_DoubleBufferModeConfig(DMA1_Stream1,(uint32_t)&sbus_rx_buffer[1][0],DMA_Memory_0); //first used memory configuration. w4 \$ N+ |% Y, S# L  q
复制代码
' u" ?8 n0 e# \5 r  ~6 A
启动DMA双缓冲
9 ^1 i. ^) r  P0 L- W6 }& q/ `3 X% X& b
  1. DMA_DoubleBufferModeCmd(DMA1_Stream1, ENABLE);
    ! S1 m2 Y' G$ P1 M. m; }! C8 [$ ^
复制代码
9 ^0 e3 _, ?& n8 I2 Z6 Z" m
我们使用DMA时利用串口的空闲中断接收一帧一帧的数据。
: _9 H/ |! A5 {' H/ F7 G0 J在串口中断处理函数中对数据进行处理7 S; ^( o, f7 V" O: W# v) i

; e" T$ n4 j% d, T8 ~/ B! f

  1. 3 y) V, |  k: E$ Q* |
  2. uint16_t this_time_rx_len = 0;        //当前剩余数据长度; h7 ~& ]' C! C* v+ x

  3. 5 q2 {* Q1 \9 f7 U  R5 `
  4. if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)        //判断是否为空闲中断0 R+ I% A1 n" P; C) I# z& W
  5.     {& b: f8 [3 I  n9 r& W8 O6 n6 w: G; \. L
  6.         USART_ReceiveData(USART3);        //清除空闲中断标志位
    5 D/ `3 q& ?: {% X

  7. 7 `. R9 j* r2 o  W, l9 A
  8.         if(DMA_GetCurrentMemoryTarget(DMA1_Stream1) == DMA_Memory_0)        //获取当前目标内存是否为 DMA_Memory_05 u3 P8 q* _" e- L( L3 I4 g
  9.         {
    1 I  R& h! U, O( b  m  Z' Q" ^& F
  10.             //重新设置DMA( `$ \0 Q2 }1 k/ y& N
  11.             DMA_Cmd(DMA1_Stream1, DISABLE);8 u1 f6 m% }8 x/ J3 Z2 R6 M
  12.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);        //获取当前剩余数据量* b6 M( Y& `4 A( H) t, B$ k
  13.                                        
    $ U- z+ B- h( V
  14.                                          DMA_SetCurrDataCounter(DMA1_Stream1, 18);        //重新设置数据量
    ' F: C( U( p/ a. T) S  i1 G
  15.                                           3 }+ U% t: H5 b4 _& N; y$ w7 ?
  16.                                          DMA_Cmd(DMA1_Stream1, ENABLE);
    2 q& a1 \$ c$ x& K3 e
  17.             if(this_time_rx_len == 18)        //接收成功18个字节长度7 q& J0 \! U0 m% }9 ]+ }5 i. W
  18.             {8 h) L/ C) S: K( a0 g- X
  19.                 //处理遥控器数据2 F% V  d# j9 ?
  20.                 RemoteDataProcess(sbus_rx_buffer[1]);        //Memory_1$ c5 X( r* p# k7 q& W  F
  21.             }% g. Q- }  K- q
  22. ! s, ~9 O0 ]5 A: c
  23.         }
    / ~! @3 b- G" J: B+ N
  24.         else        //获取当前目标内存是否为 DMA_Memory_1
    1 F( E/ X( z! x, O
  25.         {
    $ C6 j2 J8 G6 {2 |$ v; B) w
  26.             //重新设置DMA
    / ^) V' B7 L, L; {, z) p
  27.             DMA_Cmd(DMA1_Stream1, DISABLE);
    4 Z$ K5 q. |' p) Y) M. T
  28.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);                //获取当前剩余数据量8 P4 F1 {5 i& u

  29. 1 y' M  ^- q1 e: E2 J  A) L4 x, I
  30.                                                 DMA_SetCurrDataCounter(DMA1_Stream1, 18);        //重新设置数据量
      r- |/ Z& ~/ t  T1 V, L! U9 n! _- c
  31. 2 E6 \  E- }/ \: u- t! y/ ^0 C
  32.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);9 L4 d  P$ z, a' F
  33. + ~# B, N. q5 B4 s4 r+ r  E  H3 ~
  34.             if( this_time_rx_len== 18)        //接收成功18个字节长度
    , R2 M( T; |4 H! g+ [- ?
  35.             {: k# p2 x' I9 F5 W
  36.                 //处理遥控器数据7 u6 S& X9 Q4 a' o8 h+ w$ a5 N
  37.                 RemoteDataProcess(sbus_rx_buffer[0]);        //Memory_0
    ! T$ L* o5 U/ c$ o8 @3 Y4 ~
  38.             }' q- ^, {1 ^% c  j4 @
  39. - D' g2 O# f6 }* [9 u
  40.         }
    2 W: o4 Z  n. I9 Z
  41.     }
    , l* d8 y# l, T" Z; @" K1 `
复制代码
: n: o/ X6 H0 {. y: R* X, H
该逻辑在程序复位后,当一帧遥控器数据发送过来后,Memory0区域被填满18个字节,DMA自动指向Memory1,所以第一次进入中断处理函数时,获取内存指向的时候得到的指向是Memory1。
; @& s. k8 P* F" L  [然后判断剩余数据大小是否为18,如果是则说明上一帧数据接收正常,可以对数据进行处理,如果不为18,则说明上一帧数据接收长度不正常,这时候就不对数据进行处理,当下一帧数据发送过来后对数据进行检验,正常后才对数据进行处理。
. }4 d% |9 a' C6 O- p2 [. U$ Q( P3 U, C0 J( Z3 z1 h4 R
然后是第二种:* i1 W) p  ?) |! U; G# C0 a
这里我们将BufferSize改为比一帧数据长度大的值(比18大),这样可以在一帧数据传输完成后不会因Counter值变0导致DMA指向下一内存区域。6 N- C% l9 {  D( y1 S

' F) S1 s2 i3 e2 _  u4 V
  1. DMA_InitStruct.DMA_BufferSize = 36;0 M% J4 b" Z# ~, r# T; m
复制代码
  1. uint16_t this_time_rx_len = 0;        //当前剩余数据长度
    ' R4 G# X2 u& t' D. l9 v* R- H
  2. * T0 s# g1 i0 F* Q
  3. if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)        //判断是否为空闲中断7 o1 a6 j. {4 Q' t( c
  4.     {/ I- [& {1 m+ V! u7 I$ ^
  5.         USART_ReceiveData(USART3);        //清除空闲中断标志位
    ' _5 ^* p; A2 G, }

  6. 7 S' s1 [! I# |% @
  7.         if(DMA_GetCurrentMemoryTarget(DMA1_Stream1) == DMA_Memory_0)        //获取当前目标内存是否为 DMA_Memory_0: j6 n1 j0 U# B9 G) d3 D2 U
  8.         {
    0 M) ]; ^, h) _. z! ~0 d
  9.             //重新设置DMA4 X) k4 a7 w. V: a2 t
  10.             DMA_Cmd(DMA1_Stream1, DISABLE);. Y* A- H7 m$ z( \4 ^  M% B
  11.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);        //获取当前剩余数据量  接收18字节,则剩余数据量为36-18=18
    6 l# k% y( g- Z3 X$ G: Z
  12.                                        
    * l! B# c; I- V: N2 L8 J/ [
  13.                                           DMA_SetCurrDataCounter(DMA1_Stream1, 36);        //重新设置数据量9 a. Q# `: D8 q; c0 w
  14.                                                 DMA1_Stream1->CR |= (uint32_t)(DMA_SxCR_CT);  //将DMA指向Memory1
    4 [: F" }1 |4 }% t1 ^( N3 {
  15.                                        
    0 J+ t; e8 r5 b3 G% z  i
  16.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);
    & w; p9 e! }, G. R; j) y) i2 t
  17.             if(this_time_rx_len == 18)        //接收成功18个字节长度
    4 a: b/ i* F/ G
  18.             {
    ! [1 k0 K7 x8 L
  19.                 //处理遥控器数据+ m% u4 O. d& T; G8 C
  20.                 RemoteDataProcess(sbus_rx_buffer[0]);        //Memory_0
    . g5 C9 L5 h0 [/ P4 a- a
  21.             }. j) }5 A" v  i3 i7 s2 q

  22. 8 u# q, T2 y4 G; O( v
  23.         }
    - A6 e0 m* d7 l8 u# z2 w3 K1 p) P( t
  24.         else        //获取当前目标内存是否为 DMA_Memory_11 h& E, n8 R1 R! [
  25.         {5 Q6 I6 F% D- i. h
  26.             //重新设置DMA
    $ u% M& E1 K! \5 N: b& _
  27.             DMA_Cmd(DMA1_Stream1, DISABLE);* b+ D0 A; m$ r& V+ q2 C
  28.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);                //获取当前剩余数据量 接收18字节,则剩余数据量为36-18=18
    1 j8 n4 b' b, k9 z* P( @
  29. 8 t& Q4 s! y* |3 Y. X& Q2 B
  30.                                                 DMA_SetCurrDataCounter(DMA1_Stream1, 36);        //重新设置数据量/ |: M, W8 [, d
  31.                                                 DMA1_Stream1->CR &= ~(uint32_t)(DMA_SxCR_CT);  将DMA指向Memory03 X+ U- y) E, y' X0 H$ T% y

  32. + f7 a6 j& s+ i/ v: W+ u- G
  33.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);
    $ I" A( r$ ^8 i. I: ~2 Q

  34. + W# q1 e% V) U( g3 N; o* X3 `; o% X
  35.             if( this_time_rx_len == 18)        //接收成功18个字节长度3 [! U4 }' q( A- N3 N; L3 F& P
  36.             {* R$ I5 F* o: X) \
  37.                 //处理遥控器数据) s+ `$ C3 @% ^) @' U+ y
  38.                 RemoteDataProcess(sbus_rx_buffer[1]);        //Memory_1- U  V. N. f9 X8 Z' K2 v  A! w
  39.             }3 f, N: e( X5 z: a. t9 m& Y0 W

  40. - s3 A/ g# T$ Z* Y/ F( M
  41.         }3 ?' x" |# s0 n
  42.     }
    8 t/ |. r( l7 G+ q" |" q" v0 w
复制代码
- @" t  Y4 ^, H* b$ D
两种DMA双缓冲使用方法的区别* `+ d/ R* H! N! @- S
第二种方法和第一种方法的区别大同小异,这种方法由于将BufferSize设为比18大的值,也就是当第一帧数据传输完成后,Counter值不会自动填满,且内存区域还是指向Memory0,然后我们将剩余数据量保存下来,再将Counter值填满,接着把DMA指向Memory1,最后通过判断剩余数据量来决定是否对数据进行处理。
3 D4 X/ n/ q% m' x+ i6 U+ a: ]8 ~, h$ r9 ?- `
可以看到,在第一种方法下,我们并没有使用7 U/ q8 [- |! J( o

- B6 i5 C' O0 g& w9 v% t
  1. DMA1_Stream1->CR &= ~(uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0* I( M: w) c6 Q
复制代码
  1. DMA1_Stream1->CR |= (uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0
复制代码

8 J/ z1 Z9 W9 m& R0 i" _5 a
& V. Y4 J5 Z6 {( N' Z! g) }这是因为当传输数据量和BufferSize大小一致时,完整接收完一帧数据后,DMA会自动指在向下一内存地址,因此在第二种方法中的手动改变内存指向就由DMA自动处理了。; Y7 @" g' D1 v0 r3 S! ?
————————————————5 c' W; j- q+ p1 M
版权声明:水木皆Ming
- v4 O' z& v$ d% K0 T6 H
2 a! U/ }: x7 }& @3 ]5 B
0 |+ r, a/ k( s& [
收藏 评论1 发布时间:2023-1-3 18:18

举报

1个回答
YK1 回答时间:2024-9-23 20:58:50

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

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32Cube扩展软件包
意法半导体边缘AI套件
ST - 理想汽车豪华SUV案例
ST意法半导体智能家居案例
STM32 ARM Cortex 32位微控制器
关注我们
st-img 微信公众号
st-img 手机版