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

STM32的DMA双缓冲模式详解

[复制链接]
STMCU小助手 发布时间:2023-1-3 18:18
DMA双缓冲是什么?
$ C+ o) M9 M- c$ Y5 R6 M' ^# P6 C在使用STM32的DMA时我们通常使用的是普通DMA传输,但STM32自带了一个双缓冲模式设置,这个模式有什么优点呢?
/ b( G. M# W  m, L- y0 ]" C
接下来我会在下面的介绍里详细说明:
; l: B; i! Y# X+ V+ L' R) w$ R& D9 G1 m5 x4 Z
DMA全称Direct Memory Access,即直接内存访问。* ?; H9 Q0 c+ K6 w0 w6 t/ J
普通DMA的使用是在DMA的数据流中进行的,设置好DMA的起点和终点以及传输的数据量即可开启DMA传输。, q* X* L% C) J5 G
. v0 e8 Y) L0 ]1 z8 I
bb2b5bfd30b44de3b44ee1e55e75aca9.png & W7 _- @% ]: Y

; {* c6 B% X" a3 vDMA开启后就开始从起点将数据搬运至终点,每搬一次,传输数据量大小就减1,当传输数据量为0时,DMA停止搬运。5 ~& a- Z: t# _2 O8 \
: N) Q& h, n$ I. @
DMA的传输模式如果是普通模式,则当传输数据量为0时,需要程序中手动将传输数据量重新设置满才能开启下一次的DMA数据传输。
( K( v" J. L" W. O4 eDMA的传输模式如果是循环模式,则当传输数据量为0时,会自动将传输数据量设置为满的,这样数据就能不断的传输。
3 b3 E1 K& g( d& V8 u; `3 b6 v+ ~+ Z$ [- x- f4 |/ d4 ], a
412b8ec24c624455a582b052a38c291b.png
; v' N1 g/ r, r6 |2 ]

% S; D8 f# H4 |6 S: F. j6 K那么,双缓冲是什么意思呢?& {) \/ f0 j; o
我们知道,普通DMA的目标数据储存区域只有一个,也就是如果当数据存满后,新的数据又传输过来了,那么旧的数据会被新的数据覆盖。(这就是普通DMA的缺点)% C& Z% T# A: ]. N* w2 g

) N+ t: X) d( N$ X. Q
1ba7d23fab984cf3a6d86bc2800dee3f.png
5 d- P( o0 a; _/ E4 I  ]( }* H  u# `4 J
) X5 a# i3 x+ S4 Y3 [* O
而双缓冲模式下,我们DMA的目标数据储存区域有两个,也就是双缓冲,
) ]1 \4 N) m& K) [& f1 d, x  C当一次完整的数据传输结束后(即Counter值从初始值变为0),会自动指向另一个内存区域。1 y, I% d* M3 [; I: n* r; p" U

% z1 ~$ O6 i9 _$ O8 X6 O7 _# \
5d633c7fc21c43d8abd830b4bc36c772.png 6 c0 n% u8 Q. e/ ^: ^: \
% Q. {7 t9 L1 e& ]& Q
如何使用DMA双缓存?
$ f8 F( }( N0 T7 [. D! U, n& }那么我们在程序里怎么使用双缓冲呢?
$ A6 ~1 j; Q7 q/ y! C5 X2 p' x" H这里我们有两种方案使用双缓冲,一种是利用双缓冲自动跳转内存区域,一种是主动跳转内存区域。& L) ^$ d, N, o& ~# t$ o

3 |9 K" L* f% k$ Q9 j这里我们使用DMA的传输模式是循环模式) F; J, V6 @- b* {( T! a! l

: M. d0 V  _+ r! l1 ]
  1. DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;1 J& v1 T# z" X# I$ U- r0 i% v; F
复制代码
( u+ d: {  z# O% E: M3 u& r7 s* D
首先是第一种:
3 ~8 u4 a6 o/ L- s" s这里我们以大疆RoboMaster的遥控器使用为示例:
) @, l) K  t0 M( r首先我们定义好双缓冲使用的两个内存区域
6 ^" X/ Z" O3 q( P; ~; ]
  1. uint8_t sbus_rx_buffer[2][18u]; //double sbus rx buffer to save data5 m- [& k3 n# R: X7 V
复制代码
$ m" B, I( y% u# A3 X
这里我们设置长度为18,也就是一帧遥控器数据的长度。
8 b! h/ L' P0 H: G% x7 u2 ?6 u$ K0 Q" g. h" j1 E
  1. DMA_InitStruct.DMA_BufferSize = 18;0 p0 T- K5 R; g( s7 Q$ H1 [
复制代码

+ E3 K  \2 i) B9 x* X* a然后配置好双缓冲的两个内存区域地址3 h  \1 B, N& q; Z! u

4 ?4 `  z( f" ]  B7 s5 K$ i/ w
  1. DMA_InitStruct.DMA_Memory0BaseAddr =  (uint32_t)&sbus_rx_buffer[0][0];
    ( s8 g8 @0 o4 x' l
复制代码

  S+ l* s! k5 C0 l! c这里配置内存区域2的地址,并把当前DMA指向内存区域1的地址
5 t5 \* h+ l$ Q  K% ]6 C
7 u' d9 s. l8 W; t+ y" g
  1. DMA_DoubleBufferModeConfig(DMA1_Stream1,(uint32_t)&sbus_rx_buffer[1][0],DMA_Memory_0); //first used memory configuration
    8 ?; Q# x0 k3 J
复制代码
0 n) u6 x; _. `2 }9 e8 `
启动DMA双缓冲# ?' a) H( {/ y' X) h; D- d7 ^! L6 R
9 e; _: Q. n/ x& E! _
  1. DMA_DoubleBufferModeCmd(DMA1_Stream1, ENABLE);& [$ C- M% q0 p3 O
复制代码
! A  v9 ]8 g  c( F
我们使用DMA时利用串口的空闲中断接收一帧一帧的数据。
1 R$ o4 _3 L! G$ [+ D$ Z0 V7 D5 L在串口中断处理函数中对数据进行处理
! X) k  ?, m& p6 `& K& \* d' E7 F# E$ x$ }
  1. " ?& q- T& T' C' U
  2. uint16_t this_time_rx_len = 0;        //当前剩余数据长度
    - {$ G$ P5 k' \4 {, {# V
  3. ( {" d7 h( |: f% {3 ?( v
  4. if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)        //判断是否为空闲中断3 N3 y3 B8 ~* L  r( E
  5.     {% `% D1 w) q. `% _5 w2 o. f0 m# i
  6.         USART_ReceiveData(USART3);        //清除空闲中断标志位9 X% p5 w9 j1 h
  7. % Z/ F0 z$ p* G7 h9 C5 |
  8.         if(DMA_GetCurrentMemoryTarget(DMA1_Stream1) == DMA_Memory_0)        //获取当前目标内存是否为 DMA_Memory_01 I! k* S5 o6 s' f
  9.         {6 f% l+ B+ a$ ?: U* |: S
  10.             //重新设置DMA
    & L/ a0 a. f8 U  t. m( {
  11.             DMA_Cmd(DMA1_Stream1, DISABLE);
    4 q6 k: a9 y' g
  12.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);        //获取当前剩余数据量; I: y  Y  Q; W1 }, R+ A
  13.                                         3 z4 x0 G7 |) m% j0 \. @
  14.                                          DMA_SetCurrDataCounter(DMA1_Stream1, 18);        //重新设置数据量
    & Z0 j( W3 c. V) u6 ]0 A
  15.                                           
    . l; y$ @4 f! q; [  h" N
  16.                                          DMA_Cmd(DMA1_Stream1, ENABLE);
    ' w. n: Z; \, _. F7 A( Q: O
  17.             if(this_time_rx_len == 18)        //接收成功18个字节长度) y: n; q+ |3 H
  18.             {
    + M$ m) k$ s& W/ B
  19.                 //处理遥控器数据, Z  e1 u& Y5 q" O# t
  20.                 RemoteDataProcess(sbus_rx_buffer[1]);        //Memory_1
    $ y8 O; A3 z4 v+ h
  21.             }+ r- _3 |+ x: f: j+ O5 `  Z5 O
  22. 4 m* x& n7 j  x: t
  23.         }. V6 m! E' D2 b) a- H
  24.         else        //获取当前目标内存是否为 DMA_Memory_14 ]3 V" U1 h0 J( w" E- b7 z0 s; i2 D$ V
  25.         {2 K; F: y5 z$ f
  26.             //重新设置DMA, i- t# P9 r. L4 J
  27.             DMA_Cmd(DMA1_Stream1, DISABLE);- J( e; {1 F, m+ l6 g
  28.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);                //获取当前剩余数据量
    5 }; e$ M5 s8 r( F! F3 d& O

  29. 1 R' y, i  }* n. f2 u8 N
  30.                                                 DMA_SetCurrDataCounter(DMA1_Stream1, 18);        //重新设置数据量8 ?1 h& [2 y7 @6 l; T  S' B- ]5 K
  31. $ p1 J, ]* N8 c1 p0 k
  32.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);
    . F  ]- e, V- H8 S
  33. 1 E: L4 c+ ^3 L& y1 p/ _7 e
  34.             if( this_time_rx_len== 18)        //接收成功18个字节长度
    6 }+ `' @1 c  d: n  r0 _0 E! t
  35.             {
    7 H3 ^' h9 u: g
  36.                 //处理遥控器数据7 R, q# T( ?1 @* ?4 x
  37.                 RemoteDataProcess(sbus_rx_buffer[0]);        //Memory_0' {, r' j3 {. A  {8 [
  38.             }2 q8 _6 H8 v4 y6 Y. O5 M4 a
  39. $ x% c3 m( E) q1 W& J) w7 C
  40.         }: l1 n( U* y8 T3 b# l% E& U
  41.     }
    ( U; b5 l) X, f5 R
复制代码

  }! T- ^: U/ L5 f2 x9 A/ B8 w) [该逻辑在程序复位后,当一帧遥控器数据发送过来后,Memory0区域被填满18个字节,DMA自动指向Memory1,所以第一次进入中断处理函数时,获取内存指向的时候得到的指向是Memory1。
2 o, Z" w6 P* u0 i& s3 V然后判断剩余数据大小是否为18,如果是则说明上一帧数据接收正常,可以对数据进行处理,如果不为18,则说明上一帧数据接收长度不正常,这时候就不对数据进行处理,当下一帧数据发送过来后对数据进行检验,正常后才对数据进行处理。- _" a* L0 a& W9 s# `
7 {/ r, d  ~+ |+ E6 c
然后是第二种:' a& U" s! U- j4 g0 G0 o
这里我们将BufferSize改为比一帧数据长度大的值(比18大),这样可以在一帧数据传输完成后不会因Counter值变0导致DMA指向下一内存区域。0 A7 R4 Z% O2 x( ^$ s, F
3 B& x" f& L, A+ k% F) ]% Z
  1. DMA_InitStruct.DMA_BufferSize = 36;  v  R  J8 B$ {3 N3 ^# M
复制代码
  1. uint16_t this_time_rx_len = 0;        //当前剩余数据长度- N0 b8 W! f" I
  2. 1 |% N1 d  }4 l/ {* V0 u; H
  3. if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)        //判断是否为空闲中断
    9 T+ A" J0 ~1 X$ ~. m
  4.     {
    ( e' y* U( s/ g
  5.         USART_ReceiveData(USART3);        //清除空闲中断标志位2 v" \- y  M! @+ p. v& M' X- t4 L/ y
  6. # s* f: T2 q0 S' _9 Z
  7.         if(DMA_GetCurrentMemoryTarget(DMA1_Stream1) == DMA_Memory_0)        //获取当前目标内存是否为 DMA_Memory_0
    / A! Z% y/ W" |& j
  8.         {) ~* D/ e: T4 E- t# s" w3 K& p
  9.             //重新设置DMA
      Z# S2 ~( d: W) U3 e- {! l
  10.             DMA_Cmd(DMA1_Stream1, DISABLE);
    * N! \3 v( T/ C. Y, R" K
  11.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);        //获取当前剩余数据量  接收18字节,则剩余数据量为36-18=18
    / Z% |* t# d. Q. N3 i4 B5 }
  12.                                        
    3 w0 I( U  y$ h% |5 o* L
  13.                                           DMA_SetCurrDataCounter(DMA1_Stream1, 36);        //重新设置数据量* l6 e2 ]; l  y- I$ R6 E  {
  14.                                                 DMA1_Stream1->CR |= (uint32_t)(DMA_SxCR_CT);  //将DMA指向Memory1
    , @0 [% {! j( f6 `1 t7 v1 {9 f
  15.                                        
    5 z: P9 k/ B" Z; ~- n
  16.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);
    % {: F6 T, n# e6 q
  17.             if(this_time_rx_len == 18)        //接收成功18个字节长度
    ) [: C. Z/ T% @1 G" h4 J' W
  18.             {% c) f& `/ u' t9 I: z4 Z4 S
  19.                 //处理遥控器数据
    , I5 K  I2 `- @  u( F
  20.                 RemoteDataProcess(sbus_rx_buffer[0]);        //Memory_0
    $ y2 t, |. W8 l% X' N) N1 b/ ~
  21.             }- P& I  _# s1 o/ Y* y% \

  22. % l  N- q" m$ Y
  23.         }
    1 w3 [8 r; }! u7 y! f
  24.         else        //获取当前目标内存是否为 DMA_Memory_1
    2 H5 y* j" G+ t) f! c& \" M5 w" w; r
  25.         {& k1 ~* H  ?) p. X1 W/ c" L
  26.             //重新设置DMA5 n- o9 a2 `: f3 q1 ^8 m% ~7 y
  27.             DMA_Cmd(DMA1_Stream1, DISABLE);( v7 X/ _3 n/ r/ ^0 r6 m' e  X
  28.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);                //获取当前剩余数据量 接收18字节,则剩余数据量为36-18=18
      R. U9 o$ d- H4 b

  29. - v7 O0 l+ V; v  v
  30.                                                 DMA_SetCurrDataCounter(DMA1_Stream1, 36);        //重新设置数据量
    4 j% u7 L4 x% _" i8 ^0 ]. \, f
  31.                                                 DMA1_Stream1->CR &= ~(uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0/ ?( x- J: y( G+ ^8 s# {) @* s

  32. # L5 y1 N5 v4 }5 o
  33.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);$ }2 }! T* Y- [

  34. ) ~* V, D- O! z& d
  35.             if( this_time_rx_len == 18)        //接收成功18个字节长度
    ; V; B- |* a2 A1 Q. e
  36.             {
    5 f- \% i2 V. H9 l* {: c# v% H
  37.                 //处理遥控器数据* e, E, o9 E, D9 m" _5 J5 h
  38.                 RemoteDataProcess(sbus_rx_buffer[1]);        //Memory_1
    . z  x* I! d  b( g, s
  39.             }/ V7 Z5 a  H$ F, t
  40. ) n' f3 L2 N' l& H  S
  41.         }
    . d; L$ V* ?" o' I5 W- o
  42.     }. f4 t: r# R3 ?8 Z/ C0 h5 o* F
复制代码

* y; `/ ^2 W; c4 E: v( W9 L7 n$ [两种DMA双缓冲使用方法的区别  M- m  C* B3 W- ]" L* X
第二种方法和第一种方法的区别大同小异,这种方法由于将BufferSize设为比18大的值,也就是当第一帧数据传输完成后,Counter值不会自动填满,且内存区域还是指向Memory0,然后我们将剩余数据量保存下来,再将Counter值填满,接着把DMA指向Memory1,最后通过判断剩余数据量来决定是否对数据进行处理。) [& B6 q/ V6 f4 P
9 i: W# Z7 X* V9 q+ s
可以看到,在第一种方法下,我们并没有使用
- d' F0 _/ k: q5 `4 ]
$ I) O& j8 ]6 K: f0 P: F
  1. DMA1_Stream1->CR &= ~(uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0
    & G5 \3 [1 q. W  R/ Z
复制代码
  1. DMA1_Stream1->CR |= (uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0
复制代码
! B7 ^7 n1 n4 Q0 ?3 A; b

% U6 j5 D; S( e- N9 p% p7 j这是因为当传输数据量和BufferSize大小一致时,完整接收完一帧数据后,DMA会自动指在向下一内存地址,因此在第二种方法中的手动改变内存指向就由DMA自动处理了。
! @. m* s  i; U3 C; i/ w. F————————————————, p  |; n! r$ @" H! |: d6 l! u6 G
版权声明:水木皆Ming
: Y1 N) N' h$ `4 S0 j$ y! v+ R( J* W7 e
/ z7 n1 a0 _  H; X3 c% C+ D0 X! l
收藏 评论1 发布时间:2023-1-3 18:18

举报

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

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

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