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

STM32的DMA双缓冲模式详解

[复制链接]
STMCU小助手 发布时间:2023-1-3 18:18
DMA双缓冲是什么?' x: T8 _% D) b' ~1 L' j/ j
在使用STM32的DMA时我们通常使用的是普通DMA传输,但STM32自带了一个双缓冲模式设置,这个模式有什么优点呢?
4 g8 C; Q, `3 u; d0 a
接下来我会在下面的介绍里详细说明:
! @+ s$ D7 j: e9 ?4 v; Z3 w
- s: W# [6 @$ |, L6 IDMA全称Direct Memory Access,即直接内存访问。
- z: v6 P$ m1 e& W  c, U5 T普通DMA的使用是在DMA的数据流中进行的,设置好DMA的起点和终点以及传输的数据量即可开启DMA传输。
6 p8 a: k5 p' l3 x( S7 u2 A+ V0 q0 Q* n2 P/ ], t" B
bb2b5bfd30b44de3b44ee1e55e75aca9.png
8 {$ u  o9 J, i* _% {2 P2 n
# }8 l: j6 C$ _* f8 ?' T3 ?( M
DMA开启后就开始从起点将数据搬运至终点,每搬一次,传输数据量大小就减1,当传输数据量为0时,DMA停止搬运。
7 _/ U+ k( m0 [/ z! g; B
& p8 T% {$ w8 v+ b# gDMA的传输模式如果是普通模式,则当传输数据量为0时,需要程序中手动将传输数据量重新设置满才能开启下一次的DMA数据传输。- p7 X0 R3 j! H2 K/ N- f0 R; W, y
DMA的传输模式如果是循环模式,则当传输数据量为0时,会自动将传输数据量设置为满的,这样数据就能不断的传输。( }$ V9 A% F  c1 a
( P! F% X- c" A! p
412b8ec24c624455a582b052a38c291b.png * F% f: I- }# E* j4 u8 T% S8 p

7 C) J+ q- }1 S0 X8 ^那么,双缓冲是什么意思呢?5 ?; \" x" P4 R
我们知道,普通DMA的目标数据储存区域只有一个,也就是如果当数据存满后,新的数据又传输过来了,那么旧的数据会被新的数据覆盖。(这就是普通DMA的缺点)5 b, e& I5 E, V) Z4 h

' @+ `- N  |% A# B4 n/ @
1ba7d23fab984cf3a6d86bc2800dee3f.png
: n) ~- u9 y* K: V. S. J" S1 Z7 T3 e+ Z; E, ^" P$ T0 @3 j

6 w9 u! C6 a  W6 @( r而双缓冲模式下,我们DMA的目标数据储存区域有两个,也就是双缓冲,; \! V4 V7 I* E/ s
当一次完整的数据传输结束后(即Counter值从初始值变为0),会自动指向另一个内存区域。
0 v3 x9 `( }0 H
8 C2 t" w) t4 U' }/ f
5d633c7fc21c43d8abd830b4bc36c772.png 8 c2 {  f; F/ P7 i( Q4 Z9 z7 [( ~
# F) s) c! p) o# j* I) U) J" m
如何使用DMA双缓存?6 z9 J- ~/ y, M* x5 l
那么我们在程序里怎么使用双缓冲呢?& I. V' S4 z' O
这里我们有两种方案使用双缓冲,一种是利用双缓冲自动跳转内存区域,一种是主动跳转内存区域。
: ?; g' @" x" J- ^5 k, [  D! i: h; x3 K2 S5 \, Q' d
这里我们使用DMA的传输模式是循环模式% ]- X, |' x9 E8 V, `( t
. F- n. e- S5 U2 @) P( r, q8 N
  1. DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;
    % ?& k: x  \1 a
复制代码
* q- \* t! u# v* C8 C$ u3 V
首先是第一种:5 _8 r1 Y& S% ~
这里我们以大疆RoboMaster的遥控器使用为示例:" G, e) @# |3 V. Q" W
首先我们定义好双缓冲使用的两个内存区域
, b# B" Y, ~1 e
  1. uint8_t sbus_rx_buffer[2][18u]; //double sbus rx buffer to save data' T+ }) A+ C/ u) ?3 G
复制代码
: g( w, n: e7 m1 t. R& a( \
这里我们设置长度为18,也就是一帧遥控器数据的长度。5 N2 S) P! q: @7 f4 i0 `% f1 N
% a) [7 w  z/ Z3 @6 }5 k
  1. DMA_InitStruct.DMA_BufferSize = 18;
    , ?; q. }( Q* l# ~' ~# W
复制代码
" }! s; n7 ^& a
然后配置好双缓冲的两个内存区域地址
. V# Q. y) s8 ?2 K4 _% l5 [/ @7 w4 G; J0 u. P! \! P
  1. DMA_InitStruct.DMA_Memory0BaseAddr =  (uint32_t)&sbus_rx_buffer[0][0];) {+ l) P" J5 t5 s8 e+ W4 q9 ]
复制代码

$ @- `$ ~" t( j& B9 c6 f: n这里配置内存区域2的地址,并把当前DMA指向内存区域1的地址8 L  a, i# Q8 [+ u; [# d/ r
5 B3 ~0 T4 V3 W" g
  1. DMA_DoubleBufferModeConfig(DMA1_Stream1,(uint32_t)&sbus_rx_buffer[1][0],DMA_Memory_0); //first used memory configuration
    ; ]+ v1 T" t  |& _& K
复制代码
' f' Z6 v- ?, K4 b" J: V5 m7 q( k3 v
启动DMA双缓冲
' a% J/ `7 p. j+ ~7 J" Y2 }5 k+ B5 Z3 b- b: W
  1. DMA_DoubleBufferModeCmd(DMA1_Stream1, ENABLE);2 ^0 `, E* J1 z$ B
复制代码

( Y4 ?) [# @4 D我们使用DMA时利用串口的空闲中断接收一帧一帧的数据。: E6 v( [5 S: G" l
在串口中断处理函数中对数据进行处理
" u3 h# |# u# c) v' h" P: i8 k% q" q" o% C, \, C$ D  P

  1. 4 K  P( ]5 m1 A5 N& `
  2. uint16_t this_time_rx_len = 0;        //当前剩余数据长度" J* H, s9 N8 s# I  ~' a; T
  3. 0 `5 e3 ^5 F1 v
  4. if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)        //判断是否为空闲中断( D9 o+ M; T2 O5 O6 d
  5.     {
    ) o$ T' }% z& |' t9 P+ j' j, ?1 y; [
  6.         USART_ReceiveData(USART3);        //清除空闲中断标志位
    % |6 j7 o: _" w1 ^7 Y8 @4 I

  7. ' z* J4 |3 O4 Q; ^
  8.         if(DMA_GetCurrentMemoryTarget(DMA1_Stream1) == DMA_Memory_0)        //获取当前目标内存是否为 DMA_Memory_0* s: g; U7 Z1 C7 D7 |
  9.         {
    1 v* C2 N8 h- H1 B( p9 ~. w
  10.             //重新设置DMA
    + [; m7 q4 S# u% m; F7 `8 N; ]
  11.             DMA_Cmd(DMA1_Stream1, DISABLE);
    5 Q' @! `5 H  e7 M( g6 k
  12.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);        //获取当前剩余数据量
    9 @& S! R; s5 n& I# l
  13.                                         % s& i' }. V( I8 O# X' q' X. u  B
  14.                                          DMA_SetCurrDataCounter(DMA1_Stream1, 18);        //重新设置数据量
    * s5 B: k5 r6 W/ ?
  15.                                           0 d# p  X6 j8 P6 \
  16.                                          DMA_Cmd(DMA1_Stream1, ENABLE);
    1 ?# g( ~$ j* J5 L
  17.             if(this_time_rx_len == 18)        //接收成功18个字节长度
    9 ?5 l* c& \- L6 R+ s9 T
  18.             {
    # `8 @- }+ t: ~5 M6 {
  19.                 //处理遥控器数据
    - k: n: [' X5 ~
  20.                 RemoteDataProcess(sbus_rx_buffer[1]);        //Memory_1. F% e- B9 W0 e( G  ^0 `5 }
  21.             }
      J/ X5 H! k- R# f4 S, i% W# C
  22. : `4 w  p$ ?5 z2 O% C
  23.         }* e# c- }. N  H& y
  24.         else        //获取当前目标内存是否为 DMA_Memory_1
    2 E. g+ Z6 n; Q6 i7 F* ~6 B
  25.         {
    - f  r! x! o; x1 A: J9 S9 Z
  26.             //重新设置DMA# v* X% `# D5 M6 l! `
  27.             DMA_Cmd(DMA1_Stream1, DISABLE);
    3 }5 F5 z" U/ T, _' a% I
  28.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);                //获取当前剩余数据量1 M; }$ F5 a0 d4 D( y
  29. 0 g6 x. h9 ^4 z/ W8 Z' [
  30.                                                 DMA_SetCurrDataCounter(DMA1_Stream1, 18);        //重新设置数据量6 x1 Q( x, ~- F9 Q# A
  31. 0 H( A0 u' h) e8 f) O% E( ^/ J8 p) M
  32.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);4 z9 \* m  Q! v& m; ~9 q# l8 Z5 X
  33. 2 i  e( ~- z; D
  34.             if( this_time_rx_len== 18)        //接收成功18个字节长度
    5 c. H8 b$ x; N9 q- M5 R$ W
  35.             {
    5 m6 O- G  O* Y& X7 r, o
  36.                 //处理遥控器数据
    & N4 t0 b9 S; I: ?: N
  37.                 RemoteDataProcess(sbus_rx_buffer[0]);        //Memory_0
    # @0 S. V( q) A' k2 g: P6 p1 n
  38.             }# {' F: ]7 h! ]& Z( G5 M3 l7 p' N
  39. 5 @6 J3 N- M2 C( E: a
  40.         }
    ! B$ t9 V+ c6 ^
  41.     }
    7 B) @8 z3 b7 v  K# p0 ~7 v
复制代码
5 F( E* X. r8 t% i
该逻辑在程序复位后,当一帧遥控器数据发送过来后,Memory0区域被填满18个字节,DMA自动指向Memory1,所以第一次进入中断处理函数时,获取内存指向的时候得到的指向是Memory1。
( L! u  s6 @3 H4 h4 |6 O/ f( ]然后判断剩余数据大小是否为18,如果是则说明上一帧数据接收正常,可以对数据进行处理,如果不为18,则说明上一帧数据接收长度不正常,这时候就不对数据进行处理,当下一帧数据发送过来后对数据进行检验,正常后才对数据进行处理。
6 r4 W5 Q9 O2 l( g1 a& F, \$ X, L* P  h9 V- o
然后是第二种:
( l! X4 U+ y7 D& |% F, G4 j1 J& b这里我们将BufferSize改为比一帧数据长度大的值(比18大),这样可以在一帧数据传输完成后不会因Counter值变0导致DMA指向下一内存区域。
' O2 s4 m- ^9 n0 b
9 y# g/ D7 \/ B4 D$ D$ z
  1. DMA_InitStruct.DMA_BufferSize = 36;
    + e* I% X( I7 s, z  N$ ]: {
复制代码
  1. uint16_t this_time_rx_len = 0;        //当前剩余数据长度
    0 @- o5 c! D# D0 l: J- f
  2. 7 ^7 B5 H+ A" l% Q) Y# g; y
  3. if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)        //判断是否为空闲中断
    ' U8 y/ W+ M8 V
  4.     {( {; K1 X4 J+ b# h; D5 z% C
  5.         USART_ReceiveData(USART3);        //清除空闲中断标志位6 ?, s+ _7 u' K& ?% O

  6. 6 D: j( @" H3 H9 N0 Z' z. l
  7.         if(DMA_GetCurrentMemoryTarget(DMA1_Stream1) == DMA_Memory_0)        //获取当前目标内存是否为 DMA_Memory_0
    8 r8 {  i( Z" `, Z& K& @% U& ~
  8.         {/ R* Q4 {/ q+ ]
  9.             //重新设置DMA
    * F$ F: o& W4 _
  10.             DMA_Cmd(DMA1_Stream1, DISABLE);7 o" W9 j' L6 l% ]7 j
  11.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);        //获取当前剩余数据量  接收18字节,则剩余数据量为36-18=18
    ) V& J+ w# N( B$ b: d( u" V; ^
  12.                                         5 Y2 O) R' _# a2 j6 i8 d0 E/ w# U
  13.                                           DMA_SetCurrDataCounter(DMA1_Stream1, 36);        //重新设置数据量
    # R& T+ y/ S4 o& _( A# j; |
  14.                                                 DMA1_Stream1->CR |= (uint32_t)(DMA_SxCR_CT);  //将DMA指向Memory17 A2 E& v; e) P. C/ a
  15.                                        
    8 S- U. [4 c& C2 o
  16.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);, [! J' z8 }1 w. j$ d: f
  17.             if(this_time_rx_len == 18)        //接收成功18个字节长度4 `* y- }2 S* p
  18.             {
    1 I6 ^* \! s3 ^5 \  y
  19.                 //处理遥控器数据
    $ [/ S' q5 e8 W7 W) _
  20.                 RemoteDataProcess(sbus_rx_buffer[0]);        //Memory_0
    9 i1 j$ V! N( y% a0 i
  21.             }
    & c! H, `8 s2 g; U0 \6 G. G* e* u& q
  22. ( M* A# w8 G& \0 k/ ~: k" \4 O
  23.         }
    # k, U& ]9 j* k: @% U4 g1 m
  24.         else        //获取当前目标内存是否为 DMA_Memory_19 B8 n$ {" G6 D
  25.         {+ z, D# L: K) B
  26.             //重新设置DMA0 i0 j% V0 j* G7 k  j% f
  27.             DMA_Cmd(DMA1_Stream1, DISABLE);- X8 h6 _: V/ |* p, a
  28.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);                //获取当前剩余数据量 接收18字节,则剩余数据量为36-18=18
    , f% E! N' F# {* d. L
  29. , R0 y7 R5 L; V( k
  30.                                                 DMA_SetCurrDataCounter(DMA1_Stream1, 36);        //重新设置数据量& u2 ~; F7 u" V+ {
  31.                                                 DMA1_Stream1->CR &= ~(uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0
    - {5 E1 ^, ^: y. k  o3 D, X5 h6 n2 K5 k
  32. 7 c) p. l& H* Y1 t$ g. j: c) V# K9 E8 E: I
  33.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);
    ! k8 a$ W, Y! G" j) Q
  34. 5 |$ J5 b& \4 f6 C
  35.             if( this_time_rx_len == 18)        //接收成功18个字节长度
    4 ]: g, R! [/ V1 X/ p$ \; G. P
  36.             {
    9 U6 T* ^& W6 |1 l5 I% J6 b- x
  37.                 //处理遥控器数据
    ' X4 u; c3 q$ M$ M
  38.                 RemoteDataProcess(sbus_rx_buffer[1]);        //Memory_1
    ( S$ ^/ w8 ]# ^$ X6 e0 e/ v3 F
  39.             }0 A# a2 c$ h; l  Q# N' V

  40. 4 P- h/ [: P4 p: v& u
  41.         }
    ; G6 X+ P! d4 p- S
  42.     }
    % Y8 h. G2 b5 g
复制代码
: x0 E' U7 k, |, V* Y2 J% N; y4 V
两种DMA双缓冲使用方法的区别& ?/ l0 L9 i+ _
第二种方法和第一种方法的区别大同小异,这种方法由于将BufferSize设为比18大的值,也就是当第一帧数据传输完成后,Counter值不会自动填满,且内存区域还是指向Memory0,然后我们将剩余数据量保存下来,再将Counter值填满,接着把DMA指向Memory1,最后通过判断剩余数据量来决定是否对数据进行处理。
( H1 _) F" Y" Y$ t1 w1 N
2 L/ d+ C  W6 Y: _( m可以看到,在第一种方法下,我们并没有使用
8 E) f; b" E2 m9 M$ ]6 H. G( C# g9 n( F
  1. DMA1_Stream1->CR &= ~(uint32_t)(DMA_SxCR_CT);  将DMA指向Memory03 `0 @9 D+ [4 e9 H& {
复制代码
  1. DMA1_Stream1->CR |= (uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0
复制代码

. h/ C. A% v" _9 t  u) ~3 o% `. G3 A- G5 X* r9 K4 r2 F" B. x0 g
这是因为当传输数据量和BufferSize大小一致时,完整接收完一帧数据后,DMA会自动指在向下一内存地址,因此在第二种方法中的手动改变内存指向就由DMA自动处理了。
" K( i% K+ Q+ C" o————————————————
) x' e" N# t$ r版权声明:水木皆Ming2 ^- a! a0 e% D! \7 i( A

& n6 A# v1 f# G$ ?6 T" u9 {3 G8 W% [8 w/ m
收藏 评论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 手机版