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

STM32的DMA双缓冲模式详解

[复制链接]
STMCU小助手 发布时间:2023-1-3 18:18
DMA双缓冲是什么?! V: P  [" N! C  {: ]2 M. Y
在使用STM32的DMA时我们通常使用的是普通DMA传输,但STM32自带了一个双缓冲模式设置,这个模式有什么优点呢?( p$ _7 u( `' w, c" n* t
接下来我会在下面的介绍里详细说明:
7 b9 n. b9 z" ^* d7 {
) m" h3 n1 z! O# o$ G0 }. Y/ G; hDMA全称Direct Memory Access,即直接内存访问。
0 \1 E3 m* Z& {& J; {; w普通DMA的使用是在DMA的数据流中进行的,设置好DMA的起点和终点以及传输的数据量即可开启DMA传输。
& b! k9 v& n" I3 w$ a1 c- A! S; N7 R- P! E8 ]- f9 g& T- N
bb2b5bfd30b44de3b44ee1e55e75aca9.png
4 L2 [7 n$ d, Z% P2 |+ o
7 h, O" m8 |- E( N- e6 z
DMA开启后就开始从起点将数据搬运至终点,每搬一次,传输数据量大小就减1,当传输数据量为0时,DMA停止搬运。( j9 a7 G# u# q. R4 Y4 q
( k' }5 V3 i  K! v. B0 k# H
DMA的传输模式如果是普通模式,则当传输数据量为0时,需要程序中手动将传输数据量重新设置满才能开启下一次的DMA数据传输。
: l' u$ E, ^% {" j9 |+ ?( }DMA的传输模式如果是循环模式,则当传输数据量为0时,会自动将传输数据量设置为满的,这样数据就能不断的传输。
* R. x" j, U' V. W7 F1 O9 ]- J: j* G% d- G9 z- ?
412b8ec24c624455a582b052a38c291b.png - e+ o- J: y4 T" A0 u2 R

2 C% ~+ A% k! N那么,双缓冲是什么意思呢?
( T! Z- O# L0 g# L' h. i我们知道,普通DMA的目标数据储存区域只有一个,也就是如果当数据存满后,新的数据又传输过来了,那么旧的数据会被新的数据覆盖。(这就是普通DMA的缺点)
) [1 c4 K/ O$ W5 Y; X: e; y" z" L" W( N$ D+ g, h
1ba7d23fab984cf3a6d86bc2800dee3f.png
5 n; b( x1 v4 z
$ H9 D  p# L; ?9 r
( D+ N) w) Z: @9 w
而双缓冲模式下,我们DMA的目标数据储存区域有两个,也就是双缓冲,3 i) k$ x  N+ r1 O
当一次完整的数据传输结束后(即Counter值从初始值变为0),会自动指向另一个内存区域。1 H/ b3 o) q; |1 M. m# U" q# g/ ]
9 O8 w" c. Q  b9 i% v
5d633c7fc21c43d8abd830b4bc36c772.png . K' {: x8 G) f4 ^  W
/ S5 G0 T: a; Y+ l
如何使用DMA双缓存?( r9 a: C/ d4 h; c
那么我们在程序里怎么使用双缓冲呢?
+ S2 t% _; _2 W- `- H  ^1 J3 G这里我们有两种方案使用双缓冲,一种是利用双缓冲自动跳转内存区域,一种是主动跳转内存区域。
1 j' k, _7 @' }  A, U/ W  v% ~# }+ |1 @0 M
这里我们使用DMA的传输模式是循环模式$ r8 P+ ?: V2 a" I
9 x% L. n" c* t8 X2 b
  1. DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;
    $ e/ o; y1 U- k- N0 B! f8 i1 o
复制代码
4 Z) U0 L$ z  e( f3 g) W) K/ q
首先是第一种:
7 i) Z0 ?& j# ^9 S; B这里我们以大疆RoboMaster的遥控器使用为示例:
3 P4 J6 J% `4 o1 Z' d: W- a首先我们定义好双缓冲使用的两个内存区域
1 u" s: n: v( x7 N' N
  1. uint8_t sbus_rx_buffer[2][18u]; //double sbus rx buffer to save data
      W+ y# m7 y' c
复制代码
1 u) j2 n# j- I& v/ G- v3 M
这里我们设置长度为18,也就是一帧遥控器数据的长度。
" C1 w4 _2 o; y# z0 x" o3 M, I" r( \( u$ d7 e0 Y& e
  1. DMA_InitStruct.DMA_BufferSize = 18;/ v1 Z+ G- [! \" q& T- B
复制代码

* w1 E& R2 g( v- j. Q然后配置好双缓冲的两个内存区域地址2 T: k. r6 S0 q" V
- S% a  D5 r: t- j; P7 [2 M
  1. DMA_InitStruct.DMA_Memory0BaseAddr =  (uint32_t)&sbus_rx_buffer[0][0];
    6 S) a* C; u+ }
复制代码
& Y- f4 O- e, U: s2 ^- Y: k  R
这里配置内存区域2的地址,并把当前DMA指向内存区域1的地址
) r, e: {( ~8 ?3 o3 A( w) D* h0 K2 G* ]% k- J8 F( d! l! ~- t, ?
  1. DMA_DoubleBufferModeConfig(DMA1_Stream1,(uint32_t)&sbus_rx_buffer[1][0],DMA_Memory_0); //first used memory configuration5 j  @' ?/ N4 [! T8 b
复制代码
( D2 H0 j- Z- O
启动DMA双缓冲
" e5 a$ `! y5 T& H/ C4 d
) B! h/ N# V, u  {" N
  1. DMA_DoubleBufferModeCmd(DMA1_Stream1, ENABLE);/ X* u6 U+ Y, U7 T! i' i
复制代码
/ C6 H) |( v3 ]
我们使用DMA时利用串口的空闲中断接收一帧一帧的数据。
, ~: H4 l$ {+ h( y5 {$ L* M在串口中断处理函数中对数据进行处理
: p% ]$ F1 y( c/ L# S
  u( u5 j1 K8 k; w2 C, F: G
  1. : ~0 [( o; ]. T
  2. uint16_t this_time_rx_len = 0;        //当前剩余数据长度
    : Z( }* x! k+ u7 U6 r( Y! D( m
  3. : u" L0 F- f, z6 ]1 S
  4. if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)        //判断是否为空闲中断
    : y# D& \8 O, m+ u
  5.     {# B$ ]. C2 n# a- f- \1 `/ F
  6.         USART_ReceiveData(USART3);        //清除空闲中断标志位& b$ O" _- s' r+ B4 m6 j+ F$ B1 v

  7. " _5 u$ ]3 o, b# {4 X, z  `
  8.         if(DMA_GetCurrentMemoryTarget(DMA1_Stream1) == DMA_Memory_0)        //获取当前目标内存是否为 DMA_Memory_0
    3 h; U! f8 J. K5 h) n7 W6 g
  9.         {( {, S& {1 i( R/ E! X
  10.             //重新设置DMA
    , v+ L5 }3 j# P) _1 N  y
  11.             DMA_Cmd(DMA1_Stream1, DISABLE);
    5 S& o/ e9 D- c. v; ?
  12.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);        //获取当前剩余数据量, M2 @/ w3 t3 Z  z* z& ?: D% y
  13.                                        
    + I! K7 B) o, w2 A  L5 k* o
  14.                                          DMA_SetCurrDataCounter(DMA1_Stream1, 18);        //重新设置数据量
    7 i' {4 \- }9 k1 V, ^
  15.                                           ; g% Q; ~/ _6 U+ E% z% |
  16.                                          DMA_Cmd(DMA1_Stream1, ENABLE);
    ! m3 n1 Q- J) t5 R
  17.             if(this_time_rx_len == 18)        //接收成功18个字节长度# _$ F  c2 I2 ~5 k; [
  18.             {, N% \* \2 c: j% ^7 m" l0 r' E
  19.                 //处理遥控器数据
    2 ~; Q& }" P+ C# y
  20.                 RemoteDataProcess(sbus_rx_buffer[1]);        //Memory_1
    * Z( x. }6 P  K% ]" Y
  21.             }2 ]! |" _( G* D; T2 [

  22. ( C3 A3 A) r9 ^( E
  23.         }8 X  q7 k, D! n. q9 X8 G* t. |. B
  24.         else        //获取当前目标内存是否为 DMA_Memory_1
    8 b3 r9 M6 D7 q
  25.         {
    / v; M" X) e8 h3 ?% H4 u9 o
  26.             //重新设置DMA" h* v; S( M7 |3 e  ^
  27.             DMA_Cmd(DMA1_Stream1, DISABLE);
    1 @5 t9 E; F3 _& {* m+ s
  28.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);                //获取当前剩余数据量5 V7 |) M) F  k4 I3 {" H

  29. 4 D9 A2 L) P. @" F4 g3 ?
  30.                                                 DMA_SetCurrDataCounter(DMA1_Stream1, 18);        //重新设置数据量/ |. p) o# N2 N% t* J3 N

  31. ! Z: C. M/ W" G* p7 j1 r
  32.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);
    ) f, N1 e' q3 T" I3 O- l' D+ A3 h

  33. + V6 Z8 E  U/ z% [* e
  34.             if( this_time_rx_len== 18)        //接收成功18个字节长度' p  l* e, Q( W- Q8 ~: }2 b
  35.             {
    & J% j  e8 C6 {3 g% n) @
  36.                 //处理遥控器数据) y; g9 b; e5 z& k$ y, D& ]
  37.                 RemoteDataProcess(sbus_rx_buffer[0]);        //Memory_0
    / J+ l, I& G1 P+ b0 |% v
  38.             }
    * W5 ^- e! |& X  j5 x3 G! ], C

  39. 1 Z: g: y: \# q7 C6 i+ I
  40.         }
    ! s8 C& a3 ~# b! q3 y* c( x
  41.     }
    ) x; ?6 Z$ g: M; i
复制代码
# ?, ~# ~' T) b; x: S7 Q% I
该逻辑在程序复位后,当一帧遥控器数据发送过来后,Memory0区域被填满18个字节,DMA自动指向Memory1,所以第一次进入中断处理函数时,获取内存指向的时候得到的指向是Memory1。
7 J; d0 m. L# B& O" a8 f然后判断剩余数据大小是否为18,如果是则说明上一帧数据接收正常,可以对数据进行处理,如果不为18,则说明上一帧数据接收长度不正常,这时候就不对数据进行处理,当下一帧数据发送过来后对数据进行检验,正常后才对数据进行处理。
/ K1 X( d' n% i# A8 J$ R
1 H3 b0 I" Z$ _+ r然后是第二种:" Y5 \& w# V5 l% d1 B& W
这里我们将BufferSize改为比一帧数据长度大的值(比18大),这样可以在一帧数据传输完成后不会因Counter值变0导致DMA指向下一内存区域。  `% `$ i% y+ {+ S# {2 D/ Q6 w
0 S' a9 l# d- ^2 ?' u
  1. DMA_InitStruct.DMA_BufferSize = 36;5 V2 a: m2 b7 [( J1 [& B: a# ?
复制代码
  1. uint16_t this_time_rx_len = 0;        //当前剩余数据长度, Z1 c1 ?( g! g, v
  2. % O( w% _6 i# |7 P, B* @
  3. if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)        //判断是否为空闲中断0 v+ s4 E) X" O
  4.     {
    ) t$ }# Y- w( F8 g
  5.         USART_ReceiveData(USART3);        //清除空闲中断标志位
    $ L/ X/ i1 K( T' o& L: I' I6 F! g
  6. 3 k% g/ |4 m- s6 t, x" k0 Q8 ~4 h
  7.         if(DMA_GetCurrentMemoryTarget(DMA1_Stream1) == DMA_Memory_0)        //获取当前目标内存是否为 DMA_Memory_0* O: {3 p+ c. e! b& B7 ?5 ~1 e# a
  8.         {: T5 ^; k; L- t: h
  9.             //重新设置DMA
    2 w7 D. p( F% u) s" J' m
  10.             DMA_Cmd(DMA1_Stream1, DISABLE);
    + I5 W! {: R/ f/ W3 G
  11.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);        //获取当前剩余数据量  接收18字节,则剩余数据量为36-18=18# |4 d" B# ?; k& R" q
  12.                                        
    & Z7 }' e1 f9 b9 N, Z& m
  13.                                           DMA_SetCurrDataCounter(DMA1_Stream1, 36);        //重新设置数据量
    . }- ?3 Z7 ]8 |+ l% a0 u. n. l- ], }
  14.                                                 DMA1_Stream1->CR |= (uint32_t)(DMA_SxCR_CT);  //将DMA指向Memory1
    / u# g2 @2 |  ?; y
  15.                                        
    ' n( y6 Q' ]  P. A
  16.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);
    7 c$ p3 c- d0 c" u9 ^7 S
  17.             if(this_time_rx_len == 18)        //接收成功18个字节长度! q2 t9 r( S2 m3 Y7 Q- {' |
  18.             {
    3 _2 \9 h; H* B8 e* D6 [! Y
  19.                 //处理遥控器数据; G8 T' G3 t0 J/ i
  20.                 RemoteDataProcess(sbus_rx_buffer[0]);        //Memory_0
    3 C) q: A. `# u* @* ?
  21.             }
    . M" g/ ^( ?$ x6 v
  22. " E( g: k# @2 M
  23.         }
    . X6 {4 G" O, t/ X/ Z2 ?3 t
  24.         else        //获取当前目标内存是否为 DMA_Memory_1
    - N/ o' q. o; l# A
  25.         {
    ) L$ d4 Q, o4 E# H- ?6 B+ V4 |
  26.             //重新设置DMA
    8 V4 K  N! u4 m* j% a: `( W+ \
  27.             DMA_Cmd(DMA1_Stream1, DISABLE);4 T1 ]' r- R' H
  28.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);                //获取当前剩余数据量 接收18字节,则剩余数据量为36-18=18
    9 |9 W' O& s- w. h9 N$ D9 n/ R! P8 }
  29. " K- y9 ^  i9 r: P) w! S
  30.                                                 DMA_SetCurrDataCounter(DMA1_Stream1, 36);        //重新设置数据量
    $ K3 r! {. \; R; i! f/ M
  31.                                                 DMA1_Stream1->CR &= ~(uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0
    ' |. T+ Q$ V" F$ r# m2 m! Y

  32. 5 y" J1 A8 \4 g4 ^2 O4 }
  33.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);# M( x8 m8 ^2 t: {" b
  34. . d& h  x4 ~/ E% w* |' e. U# l% U
  35.             if( this_time_rx_len == 18)        //接收成功18个字节长度1 O9 W; c( \" s5 R% s
  36.             {
    * ^4 b, H/ n( I. Z1 X+ J
  37.                 //处理遥控器数据/ S( z- W, T2 L# k# `
  38.                 RemoteDataProcess(sbus_rx_buffer[1]);        //Memory_1
    2 o- J# [9 b5 a+ ]3 d& ^5 P5 X
  39.             }6 Y, C, H! h6 z, o" S7 |5 n

  40. ) v" C7 X  ]" G+ N
  41.         }
    ! _5 a% F1 U0 _# A6 q
  42.     }6 w6 H+ |7 g1 m1 n4 i% ~9 i
复制代码

- i# Z; k( j% ?/ r6 G. ^两种DMA双缓冲使用方法的区别
' P; q) C- A' u第二种方法和第一种方法的区别大同小异,这种方法由于将BufferSize设为比18大的值,也就是当第一帧数据传输完成后,Counter值不会自动填满,且内存区域还是指向Memory0,然后我们将剩余数据量保存下来,再将Counter值填满,接着把DMA指向Memory1,最后通过判断剩余数据量来决定是否对数据进行处理。
+ M7 c  B" \8 r( e+ i0 i5 V' o( g2 _
可以看到,在第一种方法下,我们并没有使用& g" o, u1 p5 F$ c  Z6 ^" P

, A( y+ z+ B1 L) d
  1. DMA1_Stream1->CR &= ~(uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0
    ' t- |* Z! Z: m* c- |! w
复制代码
  1. DMA1_Stream1->CR |= (uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0
复制代码

* Q' l$ v: U7 l* W
/ W3 q9 b: [/ p" ?这是因为当传输数据量和BufferSize大小一致时,完整接收完一帧数据后,DMA会自动指在向下一内存地址,因此在第二种方法中的手动改变内存指向就由DMA自动处理了。
9 [/ ?3 a( e1 Y6 C————————————————
. Q, e; Z7 D% A/ b7 X版权声明:水木皆Ming. t! g" x5 X8 W6 ^& `/ {
# E0 P, g" y1 J- E" j5 C& K( X
: i! A9 ]  d6 j2 q
收藏 评论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 手机版