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

STM32的DMA双缓冲模式详解

[复制链接]
STMCU小助手 发布时间:2023-1-3 18:18
DMA双缓冲是什么?
2 {/ X1 t0 h& E6 [+ e/ X! O" }( {在使用STM32的DMA时我们通常使用的是普通DMA传输,但STM32自带了一个双缓冲模式设置,这个模式有什么优点呢?) K' e/ v: n5 w8 Q$ P
接下来我会在下面的介绍里详细说明:
/ s" q$ Y* X1 R7 b* ?/ F8 s
8 D8 G; D  c2 P& G- a1 a3 n3 aDMA全称Direct Memory Access,即直接内存访问。5 [) R( O9 g; l9 {$ b
普通DMA的使用是在DMA的数据流中进行的,设置好DMA的起点和终点以及传输的数据量即可开启DMA传输。2 o4 `5 s  ^0 I  a8 m1 R, l, m) k
. `5 v) a( P0 G9 J* L+ }
bb2b5bfd30b44de3b44ee1e55e75aca9.png
# j$ Q$ a1 e# C2 c

/ @7 a' Z3 Z- ?% ~% eDMA开启后就开始从起点将数据搬运至终点,每搬一次,传输数据量大小就减1,当传输数据量为0时,DMA停止搬运。
( {6 m' Z: T: E, f, J; I" O+ r( y
DMA的传输模式如果是普通模式,则当传输数据量为0时,需要程序中手动将传输数据量重新设置满才能开启下一次的DMA数据传输。
) C/ l) }" t$ d5 `8 bDMA的传输模式如果是循环模式,则当传输数据量为0时,会自动将传输数据量设置为满的,这样数据就能不断的传输。
( P% o; C; Y, H! D3 ^4 y' {4 X8 a  a5 E9 H" d
412b8ec24c624455a582b052a38c291b.png
- }+ T0 D: d. h
8 h8 k3 \: C2 {; N, V  C) ~* k
那么,双缓冲是什么意思呢?
# ?# _6 L2 f' Y3 g- {! h我们知道,普通DMA的目标数据储存区域只有一个,也就是如果当数据存满后,新的数据又传输过来了,那么旧的数据会被新的数据覆盖。(这就是普通DMA的缺点)# u1 E% Y$ ]) ^! i7 @/ @
) p+ z5 x' ~# }0 S4 j, c$ W
1ba7d23fab984cf3a6d86bc2800dee3f.png
$ C; ]  ?8 L, R& l3 b8 ]5 ^+ u
- H% t9 c2 ^* N
, n$ @1 y0 h8 Z  L$ f" v# C- E
而双缓冲模式下,我们DMA的目标数据储存区域有两个,也就是双缓冲,7 }$ F4 F" K" X# r
当一次完整的数据传输结束后(即Counter值从初始值变为0),会自动指向另一个内存区域。
" W9 f7 L8 K" p  e
. h7 y6 l- a. K+ g
5d633c7fc21c43d8abd830b4bc36c772.png
9 a5 D/ k/ {" V- P8 g* t' x) K- v; L( i$ k+ [+ \/ b- P
如何使用DMA双缓存?
. n4 m5 y; i) H' B- x3 Q' }那么我们在程序里怎么使用双缓冲呢?
( ]' O1 Y1 S& O- k; N7 i4 E这里我们有两种方案使用双缓冲,一种是利用双缓冲自动跳转内存区域,一种是主动跳转内存区域。
4 l" `8 O9 `) d8 ?4 m5 {) y! b
' ^$ i2 t" U' Q! l3 `9 @这里我们使用DMA的传输模式是循环模式
- |3 O) r: w* {( c- `# m) E- w: S; P# F# F; e0 v
  1. DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;+ Z1 v- a3 I5 C& g
复制代码
0 T- O! Z% Z; J% Q7 A) u
首先是第一种:% u3 b3 n" [+ g5 a5 x0 `
这里我们以大疆RoboMaster的遥控器使用为示例:/ K; a4 M. j7 U1 a% U9 S; D/ v
首先我们定义好双缓冲使用的两个内存区域9 ~9 l6 r* b7 i
  1. uint8_t sbus_rx_buffer[2][18u]; //double sbus rx buffer to save data6 h8 P* s3 B4 s4 ~
复制代码

; q9 s0 @- a6 C4 s这里我们设置长度为18,也就是一帧遥控器数据的长度。
' J: d5 g+ M4 t# b* k3 w2 y' T% v% z, m* e5 T  K
  1. DMA_InitStruct.DMA_BufferSize = 18;# E% B, f/ [% U" w
复制代码
* ?) g3 S4 |" j3 {
然后配置好双缓冲的两个内存区域地址, H4 r! t  L( Y
6 N7 k4 P8 b1 g* S
  1. DMA_InitStruct.DMA_Memory0BaseAddr =  (uint32_t)&sbus_rx_buffer[0][0];$ [' _1 O; F% |+ f; h& |' X$ G
复制代码

! L: O+ P/ w2 B* d这里配置内存区域2的地址,并把当前DMA指向内存区域1的地址! ^( H* G2 N$ C* S! R8 ?, k

, T& v( q& P2 W$ @( l& R
  1. DMA_DoubleBufferModeConfig(DMA1_Stream1,(uint32_t)&sbus_rx_buffer[1][0],DMA_Memory_0); //first used memory configuration
    , m) `4 U6 Z4 `- C% x% y3 q: N9 n
复制代码
' c; R, p7 q, J: T5 ~
启动DMA双缓冲5 m  N! X# Y. E  Z- Q9 O

" k% N9 S- A2 }1 h& L
  1. DMA_DoubleBufferModeCmd(DMA1_Stream1, ENABLE);
    5 g$ Q6 _) X4 A$ S9 z
复制代码

" v" J' F6 ?5 J' _9 C6 L/ U我们使用DMA时利用串口的空闲中断接收一帧一帧的数据。
4 Z, o( W+ i/ C# e; l  b9 k* ]5 ?在串口中断处理函数中对数据进行处理
, W2 W4 E/ }. f' e& ^
$ E) V+ d/ Q2 i) P
  1. + U  [: L3 O: q+ L# U+ P
  2. uint16_t this_time_rx_len = 0;        //当前剩余数据长度( P. l9 a; P: J$ h! s! E3 E+ W

  3. 5 n/ ~5 r. B# _
  4. if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)        //判断是否为空闲中断
    & U, v, D1 z- |2 M' ]! v
  5.     {
    2 H" X% I. p2 }% r. i7 o
  6.         USART_ReceiveData(USART3);        //清除空闲中断标志位
    1 y4 W) {% D5 D  s. T9 v6 q7 O$ |

  7. ' j9 L8 {* z' ~
  8.         if(DMA_GetCurrentMemoryTarget(DMA1_Stream1) == DMA_Memory_0)        //获取当前目标内存是否为 DMA_Memory_0
    3 U0 |7 U  Y- R9 j) Y  g1 y! z
  9.         {0 G' M. r& q4 p/ R
  10.             //重新设置DMA
    ( z" r/ p4 m3 j. z( j: X. d, X- x
  11.             DMA_Cmd(DMA1_Stream1, DISABLE);+ g, O! G/ A! Z
  12.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);        //获取当前剩余数据量. Z) n' Z7 G7 r; \7 H
  13.                                        
    4 ~) v# K3 Q* X' P; l  L  T
  14.                                          DMA_SetCurrDataCounter(DMA1_Stream1, 18);        //重新设置数据量2 s3 r0 y( b  L5 M# `7 j
  15.                                           . B' _* y6 [! ]& ]8 c% ^  o
  16.                                          DMA_Cmd(DMA1_Stream1, ENABLE);7 }6 m" I! J3 {& g: q
  17.             if(this_time_rx_len == 18)        //接收成功18个字节长度
    ' a- T5 d0 N8 G$ a$ U) I2 F- Z
  18.             {5 D! V$ Y  }* j, u6 F% K' n
  19.                 //处理遥控器数据$ p% |& o) g: o( ~
  20.                 RemoteDataProcess(sbus_rx_buffer[1]);        //Memory_1- n; ^: d+ E, Z/ n. Q2 E" P  u
  21.             }
    * X5 J& `$ K: u4 K. s+ p9 |
  22. 6 c. s4 n3 [5 K( {: p
  23.         }* @- L" X- N5 ?
  24.         else        //获取当前目标内存是否为 DMA_Memory_1
    2 L8 j' X' N, r  ]" `
  25.         {4 z8 \7 U! g  m( G% e& Z
  26.             //重新设置DMA
    2 E4 P5 A- Z1 d
  27.             DMA_Cmd(DMA1_Stream1, DISABLE);
    - o9 y9 C$ c3 J$ W2 k; j
  28.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);                //获取当前剩余数据量
    " S3 }) f/ W' c

  29. " ^, j" h; h  X6 x% x4 o; W, G
  30.                                                 DMA_SetCurrDataCounter(DMA1_Stream1, 18);        //重新设置数据量
      y- \9 n, ?& ]5 p( I3 W* @2 g" b
  31. , _3 N4 ^% @" C: U2 [9 b( ]
  32.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);3 e1 i# T6 P- B8 _4 \  ^) G3 w
  33. - b' p. k, C# q" }: t6 Q
  34.             if( this_time_rx_len== 18)        //接收成功18个字节长度6 G3 S8 _7 ^" _0 r/ K
  35.             {
    , C0 {2 `9 u& Z6 Y0 \
  36.                 //处理遥控器数据
    * x5 W, @, `; s6 J2 z
  37.                 RemoteDataProcess(sbus_rx_buffer[0]);        //Memory_0
    # Y( J( ~4 v3 K+ x' P
  38.             }
    - [; x! [, D. @* I# [& {2 {4 ?' a4 W2 L

  39. : j* s, S+ t  {8 ~
  40.         }2 O& C) H3 i1 l: |9 p1 Y8 I
  41.     }! L! a; S+ V" l# r$ k& ]6 A
复制代码

/ I: Q* f/ M) F# t7 K该逻辑在程序复位后,当一帧遥控器数据发送过来后,Memory0区域被填满18个字节,DMA自动指向Memory1,所以第一次进入中断处理函数时,获取内存指向的时候得到的指向是Memory1。$ l9 i% k2 o3 L' T4 [) M7 ?
然后判断剩余数据大小是否为18,如果是则说明上一帧数据接收正常,可以对数据进行处理,如果不为18,则说明上一帧数据接收长度不正常,这时候就不对数据进行处理,当下一帧数据发送过来后对数据进行检验,正常后才对数据进行处理。
2 J1 A* m8 L3 ]8 E7 ^8 r& s. b/ W* B0 h% u5 @% k
然后是第二种:
3 {1 |7 ~# Q- `/ Z9 F0 R这里我们将BufferSize改为比一帧数据长度大的值(比18大),这样可以在一帧数据传输完成后不会因Counter值变0导致DMA指向下一内存区域。
5 O! E6 _0 B, r8 w! T, \) m, p3 t3 H+ z
  1. DMA_InitStruct.DMA_BufferSize = 36;
    2 {! K9 i8 ~1 `( ~
复制代码
  1. uint16_t this_time_rx_len = 0;        //当前剩余数据长度
    - [2 R0 C. j" J( q% o2 l0 A- P/ ~. @1 t

  2. 2 m( W- O0 K* Z9 x5 t* f  ~
  3. if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)        //判断是否为空闲中断
    % a' @, y. i) `/ X# [+ r8 N
  4.     {+ w1 G4 a5 @, V" \  I! x
  5.         USART_ReceiveData(USART3);        //清除空闲中断标志位& j3 i' ]$ `2 {9 ~# b

  6. ( d, z; D; m& C4 ^4 N( A
  7.         if(DMA_GetCurrentMemoryTarget(DMA1_Stream1) == DMA_Memory_0)        //获取当前目标内存是否为 DMA_Memory_0
    & \( x) m3 P. f- {- v
  8.         {) ^: b( m9 L1 G$ b  e
  9.             //重新设置DMA5 F$ |% V0 m$ @) _+ @
  10.             DMA_Cmd(DMA1_Stream1, DISABLE);( Y( g8 @; }, e- o
  11.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);        //获取当前剩余数据量  接收18字节,则剩余数据量为36-18=18
    7 z" v6 K: S0 K
  12.                                        
    ) z7 e/ f4 H, Q! j0 R5 I
  13.                                           DMA_SetCurrDataCounter(DMA1_Stream1, 36);        //重新设置数据量
    ; ?! S6 t% e+ t" c1 I
  14.                                                 DMA1_Stream1->CR |= (uint32_t)(DMA_SxCR_CT);  //将DMA指向Memory1. @$ M! w3 `' ?& @# f
  15.                                         * x& M5 Y% h! e# C+ b+ R
  16.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);
    " E$ L2 p) f$ o* D
  17.             if(this_time_rx_len == 18)        //接收成功18个字节长度: c- p' d& w/ C) H+ x3 l. z
  18.             {1 I! H: g% B3 c% a& C- I, U
  19.                 //处理遥控器数据
    $ B" j/ v8 `7 m% |. z' }
  20.                 RemoteDataProcess(sbus_rx_buffer[0]);        //Memory_0
    % Y; \* e( e* V" c5 U# F; [$ ~
  21.             }/ v6 n' G  l6 z

  22. - R$ Y) e+ W/ r5 f) |) M( H% D% G
  23.         }
    7 W$ |$ y9 d/ @( A- [7 x
  24.         else        //获取当前目标内存是否为 DMA_Memory_1" N2 i1 ?/ x) E8 C8 a: Q4 A) l
  25.         {& S, l8 b9 \1 T! N$ ]
  26.             //重新设置DMA6 b5 ~1 w( S; r( M+ H
  27.             DMA_Cmd(DMA1_Stream1, DISABLE);
    / f( ~. q( K& Y, i' j
  28.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);                //获取当前剩余数据量 接收18字节,则剩余数据量为36-18=18# O" _, J6 J4 I$ C8 ?8 I7 J& s
  29. 6 `2 r- a. ~5 e) I) V/ m% Z6 b
  30.                                                 DMA_SetCurrDataCounter(DMA1_Stream1, 36);        //重新设置数据量% ~2 @! G# y. ?5 E7 Z0 [
  31.                                                 DMA1_Stream1->CR &= ~(uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0. v: ?4 P0 @* o7 ?/ ]( C
  32. 5 W! }* _6 K* V  G! z$ w
  33.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);: v6 A/ R) R3 o% Y" ^2 C* K6 I. F
  34. $ U, y1 b) @* p- H8 f5 e/ s
  35.             if( this_time_rx_len == 18)        //接收成功18个字节长度4 y5 s4 k1 |5 |
  36.             {* y" }( q% [8 H' S' k% W
  37.                 //处理遥控器数据
    . w% {9 u( H7 _, R% A: F* m5 C, k
  38.                 RemoteDataProcess(sbus_rx_buffer[1]);        //Memory_1. B+ R* w/ S5 v1 X' k4 d5 n; n9 _
  39.             }
    0 A; N' `. s) F; z3 d

  40. . `$ D* K6 J/ ^4 G/ `
  41.         }
    8 H8 ^8 {, s4 p
  42.     }
    ; x9 p( B& p( N$ F
复制代码

4 p  ~5 ]: q4 N8 Q! j两种DMA双缓冲使用方法的区别
( R7 |. U: ~6 p3 j7 k第二种方法和第一种方法的区别大同小异,这种方法由于将BufferSize设为比18大的值,也就是当第一帧数据传输完成后,Counter值不会自动填满,且内存区域还是指向Memory0,然后我们将剩余数据量保存下来,再将Counter值填满,接着把DMA指向Memory1,最后通过判断剩余数据量来决定是否对数据进行处理。. J  U6 J# B, Y% y

5 j; o% j" m' V( p+ g' V4 B8 @! U可以看到,在第一种方法下,我们并没有使用
0 U# u- `, T9 F; n
; A& W2 F4 L; k2 J$ O' l- P* O& F
  1. DMA1_Stream1->CR &= ~(uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0
    ! e+ C) r% d% r; K
复制代码
  1. DMA1_Stream1->CR |= (uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0
复制代码

9 G' D1 j, a2 ~% h( T- m1 y- i7 V
" Z  b4 Q$ R# L0 z这是因为当传输数据量和BufferSize大小一致时,完整接收完一帧数据后,DMA会自动指在向下一内存地址,因此在第二种方法中的手动改变内存指向就由DMA自动处理了。
' v5 u4 N# V4 t, W' N————————————————5 r# {6 K9 P, U3 m' _6 |
版权声明:水木皆Ming, h# X5 y% {; `$ `1 M5 ^/ d5 |

8 S0 `) o; b3 d+ L; L6 ^
! H1 N: G$ K3 u( }0 b8 z
赞 收藏 评论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 手机版