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

STM32的DMA双缓冲模式详解

[复制链接]
STMCU小助手 发布时间:2023-1-3 18:18
DMA双缓冲是什么?
! z8 F7 [7 B2 }$ z% T+ t/ \" N7 @在使用STM32的DMA时我们通常使用的是普通DMA传输,但STM32自带了一个双缓冲模式设置,这个模式有什么优点呢?5 x" W  d5 h8 C3 a0 J
接下来我会在下面的介绍里详细说明:3 _$ K* C5 F+ B" T
  I- e" ~" a9 T- Z
DMA全称Direct Memory Access,即直接内存访问。% ^7 W5 S& X9 e) i
普通DMA的使用是在DMA的数据流中进行的,设置好DMA的起点和终点以及传输的数据量即可开启DMA传输。
  j2 P0 q' {% R% }* q! F" H! e. w/ |' b+ o( @( J8 x( I
bb2b5bfd30b44de3b44ee1e55e75aca9.png 7 C+ s2 y- v/ D2 l5 _! F
9 x2 \4 }0 x. T! q" Y3 }& R
DMA开启后就开始从起点将数据搬运至终点,每搬一次,传输数据量大小就减1,当传输数据量为0时,DMA停止搬运。
+ Y! l- j+ S8 F8 C6 Y  E& H' _$ m% q5 s3 C% ?0 D( X0 N
DMA的传输模式如果是普通模式,则当传输数据量为0时,需要程序中手动将传输数据量重新设置满才能开启下一次的DMA数据传输。
2 b; w, e+ V; C4 H8 x" O5 ], lDMA的传输模式如果是循环模式,则当传输数据量为0时,会自动将传输数据量设置为满的,这样数据就能不断的传输。0 C& i0 E% O7 I; m7 d5 R1 ^1 ^

0 g0 t1 K4 [! P: F
412b8ec24c624455a582b052a38c291b.png
% }; ^' \/ ^) |! F# }
' b) D9 x: g/ O7 u! ]' r; ~' T
那么,双缓冲是什么意思呢?
7 r+ b+ Q0 w- y+ m5 o: }我们知道,普通DMA的目标数据储存区域只有一个,也就是如果当数据存满后,新的数据又传输过来了,那么旧的数据会被新的数据覆盖。(这就是普通DMA的缺点)
- _6 o+ t) _! k" N, L8 p% |( e4 g$ H$ b4 T0 f2 Y
1ba7d23fab984cf3a6d86bc2800dee3f.png 8 h+ F, `, q& t6 c" n. ^- R; A

' Q0 M* I2 [# j' A

) p0 P( U# a. W* x, ~) o2 k而双缓冲模式下,我们DMA的目标数据储存区域有两个,也就是双缓冲,
  k- z3 ~, n" x# u  `6 u# i当一次完整的数据传输结束后(即Counter值从初始值变为0),会自动指向另一个内存区域。3 L" F5 ~- d# g; I( h. A
/ r8 B0 @6 p( l% K4 c. X
5d633c7fc21c43d8abd830b4bc36c772.png
( T* U, T# `2 ~/ a) ^
+ g/ n7 q4 a% X, q如何使用DMA双缓存?
: h" @+ t& t) m3 c7 U) |' F$ S那么我们在程序里怎么使用双缓冲呢?4 |$ m# C: t2 X, Q7 l% F8 a) b
这里我们有两种方案使用双缓冲,一种是利用双缓冲自动跳转内存区域,一种是主动跳转内存区域。" j/ x) [  ?* _) D2 M+ [

+ u! S5 w) t! L" X  s这里我们使用DMA的传输模式是循环模式
( c) J- J; t+ h5 w  ?* A
3 A, @! X# E7 _4 I9 w2 ]; q$ Y
  1. DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;) C2 d4 N' x  a( O% g- |. T
复制代码
9 ]' l. P  T+ h0 D; r) {& o
首先是第一种:
6 v* w( s( A$ H8 g' H- }4 V( o, a这里我们以大疆RoboMaster的遥控器使用为示例:" g& C# T3 Q0 {  h: o- E2 q
首先我们定义好双缓冲使用的两个内存区域
9 Z  B* l3 `) |: g9 c) ^
  1. uint8_t sbus_rx_buffer[2][18u]; //double sbus rx buffer to save data
    ' J/ C1 Q+ j9 j" o- R
复制代码

5 V2 x) a! `3 j) y) W这里我们设置长度为18,也就是一帧遥控器数据的长度。
3 T  z$ p8 }( r) T5 @& V- ^+ h5 y* ]# e' Q7 ?+ g+ m
  1. DMA_InitStruct.DMA_BufferSize = 18;
    & B% y2 ^- A- Z, p5 q# M
复制代码
5 w* n) Y0 s% i8 b/ u
然后配置好双缓冲的两个内存区域地址  m$ [' Y& [& V$ A
" G! n3 x; P; p% @% m" p
  1. DMA_InitStruct.DMA_Memory0BaseAddr =  (uint32_t)&sbus_rx_buffer[0][0];
    5 M" w) [6 |! \% E+ u$ f/ R( M# ^3 Q
复制代码

( h$ s% z. J" c' _这里配置内存区域2的地址,并把当前DMA指向内存区域1的地址- k  s# J$ x; E

6 m* F8 E  q* K* p! p- x
  1. DMA_DoubleBufferModeConfig(DMA1_Stream1,(uint32_t)&sbus_rx_buffer[1][0],DMA_Memory_0); //first used memory configuration
    ' h6 E" y2 T  |$ N4 S
复制代码
* {3 T3 N8 h* Y" x6 L4 @3 B
启动DMA双缓冲
& W4 x9 S% |8 n0 H& a9 z+ X
9 d6 v/ l$ f' m. i" K- \. [
  1. DMA_DoubleBufferModeCmd(DMA1_Stream1, ENABLE);
      Z9 ~" ?8 o9 V" u4 R
复制代码
0 D0 C$ [4 S$ t- K; N8 F% q
我们使用DMA时利用串口的空闲中断接收一帧一帧的数据。7 H. W2 J$ N9 O7 L! d
在串口中断处理函数中对数据进行处理
. u* Q( I6 O  ]9 t! N! f9 r: r
) ~6 C0 X1 h# D/ O) y' K4 ]6 r

  1. + L! p+ y% C, O- d; S1 z1 b9 J
  2. uint16_t this_time_rx_len = 0;        //当前剩余数据长度5 ^0 [( _( m- R8 u

  3. 6 x6 v8 h' ~! j7 e; {! l
  4. if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)        //判断是否为空闲中断
    . E7 i7 g: p( H, l# |5 P: z. ^
  5.     {
    8 e/ |) n$ O; ^
  6.         USART_ReceiveData(USART3);        //清除空闲中断标志位* e$ J9 c9 y5 a2 K
  7. 5 I( M0 {4 N' N# r
  8.         if(DMA_GetCurrentMemoryTarget(DMA1_Stream1) == DMA_Memory_0)        //获取当前目标内存是否为 DMA_Memory_0. }4 }' O( L9 h8 ]1 L
  9.         {, x! b! R4 D' b( S6 I9 V
  10.             //重新设置DMA; H, J0 I( D: B, {
  11.             DMA_Cmd(DMA1_Stream1, DISABLE);
    6 z! K8 [+ t$ Q
  12.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);        //获取当前剩余数据量
    6 F+ {) Y! l, ]" T8 m
  13.                                        
    0 V5 @* p* D% l
  14.                                          DMA_SetCurrDataCounter(DMA1_Stream1, 18);        //重新设置数据量& |9 \: ^- J3 U1 h( o. ^
  15.                                           0 Y8 N- }6 c' v. U+ @- ]
  16.                                          DMA_Cmd(DMA1_Stream1, ENABLE);
    # R7 O. P! U8 k
  17.             if(this_time_rx_len == 18)        //接收成功18个字节长度
    % ~4 ]* H3 Y$ l% X# a; Z+ X0 W
  18.             {
    ) {3 Y) ?/ Z% i# p7 w% _. V
  19.                 //处理遥控器数据
    2 g2 ~" L+ ]5 y' ]5 X: o& `5 _
  20.                 RemoteDataProcess(sbus_rx_buffer[1]);        //Memory_1
    2 y1 P( V: w* a7 V3 K7 `
  21.             }' Q% @: T) F$ m% U
  22. 8 x$ E: |5 N- c7 D8 \
  23.         }6 g% w( N: H2 i6 `
  24.         else        //获取当前目标内存是否为 DMA_Memory_1
    - H: M. O- N/ |( v" e; U
  25.         {
    : N7 \$ H! O+ ^# L/ t# f* w
  26.             //重新设置DMA4 U- b; i9 n! G0 D. Z' b. S
  27.             DMA_Cmd(DMA1_Stream1, DISABLE);
    ! x- @- M6 C1 S, U( F' A7 _8 |$ x
  28.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);                //获取当前剩余数据量
    1 i  j1 b( O, {" k
  29. 9 M" \# @- u& B. N
  30.                                                 DMA_SetCurrDataCounter(DMA1_Stream1, 18);        //重新设置数据量4 y+ t# J8 u4 E, N$ ?
  31. + C& Z/ u0 F) y; ~1 b5 p
  32.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);* d2 M; W9 s# t# C9 L

  33. , P3 p7 r* |1 V$ {$ L( ^
  34.             if( this_time_rx_len== 18)        //接收成功18个字节长度
    1 N# A' W9 m. b) X
  35.             {
    9 _$ L  U. P: L7 f; L8 a1 K
  36.                 //处理遥控器数据& Y( w7 g' R( t; r( |
  37.                 RemoteDataProcess(sbus_rx_buffer[0]);        //Memory_0: ~( Z* A6 ]& a
  38.             }
    & f8 x4 ~* l3 H5 s/ R

  39. 1 e5 F1 e$ x5 Q. c% @* L
  40.         }! G! f/ G' {% R1 s4 s. Z9 z" q
  41.     }- P& h$ o: ?$ x# k7 P3 ^: `; b
复制代码
5 X# x6 `+ K0 d3 p8 S. I) Y) m
该逻辑在程序复位后,当一帧遥控器数据发送过来后,Memory0区域被填满18个字节,DMA自动指向Memory1,所以第一次进入中断处理函数时,获取内存指向的时候得到的指向是Memory1。
4 D2 r+ I! \( e$ D/ ?+ q然后判断剩余数据大小是否为18,如果是则说明上一帧数据接收正常,可以对数据进行处理,如果不为18,则说明上一帧数据接收长度不正常,这时候就不对数据进行处理,当下一帧数据发送过来后对数据进行检验,正常后才对数据进行处理。
% k: E3 z- V6 ]& h5 f
6 p* Z! {' A3 W) q' A然后是第二种:7 j* }9 t) V* N; o0 s3 K! Z
这里我们将BufferSize改为比一帧数据长度大的值(比18大),这样可以在一帧数据传输完成后不会因Counter值变0导致DMA指向下一内存区域。; k2 F0 M, f# C; W7 ^

) W& Y; x( _! K. C) _
  1. DMA_InitStruct.DMA_BufferSize = 36;
    6 E+ U& H! w' _: K, ^3 W) E; x
复制代码
  1. uint16_t this_time_rx_len = 0;        //当前剩余数据长度% c  u5 C! N) w. J& L) [5 _2 `2 H& J0 N

  2.   ^# g! E5 Z- K# ~
  3. if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)        //判断是否为空闲中断& [" B* x8 ^/ ~2 [5 x+ l* P& x  u
  4.     {  z1 i* F1 d0 Q& K4 y1 Y5 s' g
  5.         USART_ReceiveData(USART3);        //清除空闲中断标志位, c3 `- Y' k3 V0 d& {

  6. $ Q; Z$ o+ O0 s3 h' o
  7.         if(DMA_GetCurrentMemoryTarget(DMA1_Stream1) == DMA_Memory_0)        //获取当前目标内存是否为 DMA_Memory_09 j- \9 `1 B7 i/ n
  8.         {
    + l* i2 x% a$ F8 V8 q/ ~6 A
  9.             //重新设置DMA: U2 Y: J& D! o
  10.             DMA_Cmd(DMA1_Stream1, DISABLE);# T/ e" ^6 o  t3 W" t- m/ U
  11.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);        //获取当前剩余数据量  接收18字节,则剩余数据量为36-18=18) X, z' y2 {/ p7 g- }' A; J6 N
  12.                                         & A2 L. c! S1 k. V0 L/ y/ ]5 ~, j) O
  13.                                           DMA_SetCurrDataCounter(DMA1_Stream1, 36);        //重新设置数据量
    7 J2 L( Z4 u! W. h. \0 L
  14.                                                 DMA1_Stream1->CR |= (uint32_t)(DMA_SxCR_CT);  //将DMA指向Memory1
    2 a& n* E5 \' X
  15.                                        
    / a" h. L9 e: R' d+ x' f. i, b) p
  16.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);* {. S/ r. M/ p& g4 V
  17.             if(this_time_rx_len == 18)        //接收成功18个字节长度, Z# g8 @/ B. G9 m8 q# ]
  18.             {
    $ F( i  g+ ^- ~% Z# H% B, |
  19.                 //处理遥控器数据
    ( C, L- s4 p! S  s0 l$ x" X
  20.                 RemoteDataProcess(sbus_rx_buffer[0]);        //Memory_0( g8 p+ r$ i# f7 o9 C
  21.             }
    $ v& d' o; y1 X) n

  22. & A% p7 _' m9 p1 G# x+ I/ j
  23.         }1 G) s7 L9 }( h3 {. l6 K- }" h6 e8 q
  24.         else        //获取当前目标内存是否为 DMA_Memory_1
    $ j# k8 s7 o7 \) X0 h
  25.         {
    & j" p/ _% f+ H- f4 _: ^
  26.             //重新设置DMA) L& \& c9 E3 B( m1 G0 ^; c
  27.             DMA_Cmd(DMA1_Stream1, DISABLE);
    5 n: u7 w9 [) h  e
  28.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);                //获取当前剩余数据量 接收18字节,则剩余数据量为36-18=187 F& s; ?1 A5 f0 i; A, K0 q
  29. # U- {# W* C$ A$ n- u
  30.                                                 DMA_SetCurrDataCounter(DMA1_Stream1, 36);        //重新设置数据量
    1 W  b7 U8 ~5 m+ g
  31.                                                 DMA1_Stream1->CR &= ~(uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0# ^1 {* b0 z8 U% z2 ?* @) T* I

  32. , p1 {) U  t- j7 G- K, {! R8 S
  33.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);
    / z( `+ y- o9 _9 g6 E) X9 b2 b! ]

  34. & _: z0 ?+ u/ E( Q
  35.             if( this_time_rx_len == 18)        //接收成功18个字节长度
    4 t( O0 J* ?$ e" ~) y+ t; A8 c. A: B
  36.             {
    ; H/ D1 L( h1 T; O8 t
  37.                 //处理遥控器数据4 l) U+ ^3 g' y2 l& F0 x8 b
  38.                 RemoteDataProcess(sbus_rx_buffer[1]);        //Memory_1' O7 y8 e4 j# O  B4 c, r, C
  39.             }
    ' t2 X, @. q! b! }* Y9 h
  40. / B' K/ S  u) L2 v% @+ D
  41.         }. \( z8 f! B/ M. P/ x8 r
  42.     }
    ! t. ~; u! H- h8 D* D1 W6 r  G
复制代码

0 X- |$ R; h$ j7 c% m两种DMA双缓冲使用方法的区别
6 P( A5 t  L: w% r/ q( j/ Z0 W第二种方法和第一种方法的区别大同小异,这种方法由于将BufferSize设为比18大的值,也就是当第一帧数据传输完成后,Counter值不会自动填满,且内存区域还是指向Memory0,然后我们将剩余数据量保存下来,再将Counter值填满,接着把DMA指向Memory1,最后通过判断剩余数据量来决定是否对数据进行处理。4 o1 S% ~' t9 A  n& z9 i$ L' a7 N

& d7 @5 q4 m% o: F& Q; D: n9 X可以看到,在第一种方法下,我们并没有使用# x% g  J8 S* n
1 H) ]# m: D0 M( z5 B' H' c
  1. DMA1_Stream1->CR &= ~(uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0' N3 k' E) W% [3 _+ U# O+ }% I$ S
复制代码
  1. DMA1_Stream1->CR |= (uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0
复制代码
7 f* T) G) \, @, g: N; V
! w5 v8 O' K3 y  Q
这是因为当传输数据量和BufferSize大小一致时,完整接收完一帧数据后,DMA会自动指在向下一内存地址,因此在第二种方法中的手动改变内存指向就由DMA自动处理了。' Y% J, O! B; k  h7 q- A
————————————————$ n2 ]1 Z1 S+ F8 W0 S  t9 M3 P
版权声明:水木皆Ming
! l' \, r' [( K. |( p3 z5 }; Z: P/ h8 T  ^
  C9 {4 C8 Z2 K# j
收藏 评论1 发布时间:2023-1-3 18:18

举报

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

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

所属标签

相似技术帖

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版