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

STM32的DMA双缓冲模式详解

[复制链接]
STMCU小助手 发布时间:2023-1-3 18:18
DMA双缓冲是什么?; n  H: }5 E, k5 Y
在使用STM32的DMA时我们通常使用的是普通DMA传输,但STM32自带了一个双缓冲模式设置,这个模式有什么优点呢?- a! e/ W0 k$ R6 y" T
接下来我会在下面的介绍里详细说明:7 g* F1 g% _; j( [( Y; K

8 Q) O' o+ x* U7 \, b# P/ JDMA全称Direct Memory Access,即直接内存访问。- Y: f! U0 K/ ^6 p
普通DMA的使用是在DMA的数据流中进行的,设置好DMA的起点和终点以及传输的数据量即可开启DMA传输。' {/ M7 f$ n  y2 @, e! p  L& }* y

6 y% a0 ^. ]& X3 w7 s" O" ^
bb2b5bfd30b44de3b44ee1e55e75aca9.png " b1 P9 J* g5 l
! v, M- W: v* q
DMA开启后就开始从起点将数据搬运至终点,每搬一次,传输数据量大小就减1,当传输数据量为0时,DMA停止搬运。
" p  ^( [- \& z  b
* v* y+ ^% Y" c) |( T/ @& }: |DMA的传输模式如果是普通模式,则当传输数据量为0时,需要程序中手动将传输数据量重新设置满才能开启下一次的DMA数据传输。
( D# _( h) L  F* _: F3 j8 O1 u. [DMA的传输模式如果是循环模式,则当传输数据量为0时,会自动将传输数据量设置为满的,这样数据就能不断的传输。0 `, i3 g1 s9 Y
8 L) G0 w( \* W* f
412b8ec24c624455a582b052a38c291b.png
0 t5 [& C" h+ L2 W5 n3 m0 ~5 w: T8 [

" Q2 Y+ j! [# B# g' ~- D% G那么,双缓冲是什么意思呢?! ^  n+ h; z- W/ Z
我们知道,普通DMA的目标数据储存区域只有一个,也就是如果当数据存满后,新的数据又传输过来了,那么旧的数据会被新的数据覆盖。(这就是普通DMA的缺点)
* W( Y  O$ ]5 |7 f8 v  {
6 g  O; ]) {5 g5 R3 H# f
1ba7d23fab984cf3a6d86bc2800dee3f.png - t$ t) _7 E# b& s9 Y( x0 y/ p

7 o' q4 i" K2 q. S% n) r; F
" f, w8 h9 X" m. R; E
而双缓冲模式下,我们DMA的目标数据储存区域有两个,也就是双缓冲,
! Q% T% N" Z9 U) ], U当一次完整的数据传输结束后(即Counter值从初始值变为0),会自动指向另一个内存区域。1 @/ `% o4 A6 ^6 m# h: `4 u0 N

- ?" _; E+ f! }0 q6 u
5d633c7fc21c43d8abd830b4bc36c772.png , D, r6 I5 ?. y* I8 Q% E
, {7 E9 |9 |" \8 P; I+ r, k
如何使用DMA双缓存?" g; d; t0 `1 B4 C8 ?5 D
那么我们在程序里怎么使用双缓冲呢?
* {+ ~! L* U: L/ A/ @7 D' M6 N这里我们有两种方案使用双缓冲,一种是利用双缓冲自动跳转内存区域,一种是主动跳转内存区域。& E: }4 ^' F% E+ ?, p1 S" X

5 f# g2 ]; G6 d这里我们使用DMA的传输模式是循环模式
- i8 _" t' {, P) ~: D4 A. j6 e; o9 c" ]9 G7 g5 D
  1. DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;
    ( u/ `1 e- i8 b
复制代码
5 g: L' m% F$ j8 b+ V5 s0 }
首先是第一种:0 Q. j  u, ^3 a- h! R2 _2 D1 y, D
这里我们以大疆RoboMaster的遥控器使用为示例:5 a0 X2 p  k( `3 H
首先我们定义好双缓冲使用的两个内存区域
/ w$ g8 W, p& k3 t; ~5 C
  1. uint8_t sbus_rx_buffer[2][18u]; //double sbus rx buffer to save data+ }, R/ [1 V+ b
复制代码
) b& W3 U# E' n* X; @9 x* @
这里我们设置长度为18,也就是一帧遥控器数据的长度。/ J; Y' X( `! ]/ r5 c5 y

7 _/ M4 z1 x  B% V5 [$ C
  1. DMA_InitStruct.DMA_BufferSize = 18;
    ' X$ Q3 R6 I" w7 G4 U
复制代码

) l8 R4 a  k  [" [然后配置好双缓冲的两个内存区域地址( |# m7 x0 m# M1 I) d' g- c
8 E; f  h! v8 K
  1. DMA_InitStruct.DMA_Memory0BaseAddr =  (uint32_t)&sbus_rx_buffer[0][0];. i- o$ e& _- _' Q
复制代码
- l  {# l3 u: K  v  O
这里配置内存区域2的地址,并把当前DMA指向内存区域1的地址. R% r# T9 e: T( @, ~, |1 |
/ ?9 I2 e3 N- `& E% e, C4 T
  1. DMA_DoubleBufferModeConfig(DMA1_Stream1,(uint32_t)&sbus_rx_buffer[1][0],DMA_Memory_0); //first used memory configuration5 Z* p1 P- h& b: p* e5 b3 ~2 m
复制代码

9 `' m' c" z5 Z3 p' j启动DMA双缓冲
2 O1 I# S8 ?, y" j' s, \2 M7 m9 A) R# ]+ _5 I4 P9 [
  1. DMA_DoubleBufferModeCmd(DMA1_Stream1, ENABLE);+ m$ L) A  l; l7 x- t
复制代码
- _, m' v, ~  @! H, T: A2 Q
我们使用DMA时利用串口的空闲中断接收一帧一帧的数据。3 x4 ~9 |5 L# n! T4 l6 i  k- U
在串口中断处理函数中对数据进行处理
7 T* U' O" C8 |* r# A3 S; C" A- o  I6 @3 `* Q
  1. % F7 ?7 \2 L, u. J0 Q
  2. uint16_t this_time_rx_len = 0;        //当前剩余数据长度1 u2 x+ a9 i) U1 _5 V6 m

  3. , ?1 B: S; Q8 W$ P  e& c5 P5 {
  4. if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)        //判断是否为空闲中断
    ' M; ]) D+ K0 y) _& T# [" r5 F2 b( d
  5.     {4 ]/ w1 F# ^% N, {6 h! |% @
  6.         USART_ReceiveData(USART3);        //清除空闲中断标志位
    & [( ]- g8 y$ @: k

  7. ( H0 d3 N6 V9 |  A7 [; S) t# l
  8.         if(DMA_GetCurrentMemoryTarget(DMA1_Stream1) == DMA_Memory_0)        //获取当前目标内存是否为 DMA_Memory_0( k) d2 N: o+ c6 v1 i
  9.         {9 v1 m# s  {: g7 H: w- F
  10.             //重新设置DMA
    8 C/ X: v: w/ h: h" f
  11.             DMA_Cmd(DMA1_Stream1, DISABLE);
    1 p2 u, x4 ~. x2 _+ b1 S
  12.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);        //获取当前剩余数据量- Y8 l; ^. f* o' R7 C+ f$ n
  13.                                        
    ) M! I/ {$ Y* s
  14.                                          DMA_SetCurrDataCounter(DMA1_Stream1, 18);        //重新设置数据量: ^0 a9 t$ ^" y4 r
  15.                                           + H9 o, W/ q' h; w7 C% K% K
  16.                                          DMA_Cmd(DMA1_Stream1, ENABLE);
    * I# T. m# L; I) G9 L
  17.             if(this_time_rx_len == 18)        //接收成功18个字节长度
    " t; [% g; G9 |7 v# y, ~
  18.             {2 i1 V2 F* F7 t- y
  19.                 //处理遥控器数据( a! b; m5 O4 B: Q; f% q- K2 X4 e
  20.                 RemoteDataProcess(sbus_rx_buffer[1]);        //Memory_1
    1 p. n. V; K9 I- Q4 f
  21.             }
    & \7 O6 S$ }7 W* K' W3 N. n5 s, C

  22. 1 j. M% \- }. U
  23.         }6 S) ~9 h' k  s4 G! ^+ |
  24.         else        //获取当前目标内存是否为 DMA_Memory_1, A& j' `9 _" R- s
  25.         {
      K) x% x8 a5 N7 K: I5 u
  26.             //重新设置DMA4 a$ Z7 }0 M* |  E" T4 Z4 M
  27.             DMA_Cmd(DMA1_Stream1, DISABLE);
    9 A1 @- g- H( E( B1 m0 m
  28.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);                //获取当前剩余数据量
    * r% @* ]% o* m& O3 V& ~

  29. 5 W1 W8 I' j/ R' ~- x
  30.                                                 DMA_SetCurrDataCounter(DMA1_Stream1, 18);        //重新设置数据量7 B. U1 }2 }- a2 s1 L0 _) {

  31. / ]  D1 F/ k: P( I% v) u1 Y
  32.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);
    8 L8 p: \, T" q' I8 v
  33. ' A. [$ g6 j1 g6 O" |; c0 A
  34.             if( this_time_rx_len== 18)        //接收成功18个字节长度
    . B' q; t& q% W
  35.             {
    * u0 Z1 T& [6 d/ k8 H6 |% o+ s* c
  36.                 //处理遥控器数据1 M/ h$ A& I: K% |9 h! l1 s4 W
  37.                 RemoteDataProcess(sbus_rx_buffer[0]);        //Memory_0; Y, }! x/ I0 L1 }
  38.             }* M! I4 A1 t3 y

  39. $ H  z5 D# v# P. h; J! a- i- B; E
  40.         }
    , k9 j5 s' Q4 O* Q. q
  41.     }
    7 Z: L4 P3 I! H- p& I* }# G: _* R
复制代码
( D" M  n6 F9 a$ `
该逻辑在程序复位后,当一帧遥控器数据发送过来后,Memory0区域被填满18个字节,DMA自动指向Memory1,所以第一次进入中断处理函数时,获取内存指向的时候得到的指向是Memory1。2 x6 k- c% v4 u, N1 ^
然后判断剩余数据大小是否为18,如果是则说明上一帧数据接收正常,可以对数据进行处理,如果不为18,则说明上一帧数据接收长度不正常,这时候就不对数据进行处理,当下一帧数据发送过来后对数据进行检验,正常后才对数据进行处理。
1 E6 d, g: I$ r- ~; z" o8 F5 V# h7 a5 q6 {" x
然后是第二种:
" i6 Z: |+ d+ m5 M; S4 m$ J这里我们将BufferSize改为比一帧数据长度大的值(比18大),这样可以在一帧数据传输完成后不会因Counter值变0导致DMA指向下一内存区域。! s% K" E* V. G) ~" D  u

! T3 r  V/ p1 l+ C" z, U3 v
  1. DMA_InitStruct.DMA_BufferSize = 36;! z+ }0 k" o0 ~+ k$ T/ g
复制代码
  1. uint16_t this_time_rx_len = 0;        //当前剩余数据长度) C% L6 ^$ T3 Q

  2. 1 r4 `  R) _. A7 d6 J2 @" J& N4 E% H8 c
  3. if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)        //判断是否为空闲中断
    , s0 H& o# F9 V8 `8 S9 w
  4.     {3 Q' C, P! U/ z, ^, o3 d4 n
  5.         USART_ReceiveData(USART3);        //清除空闲中断标志位8 I# x5 K  |/ S0 e& l1 `
  6. : q0 h' V; _) A7 [! n+ |, V
  7.         if(DMA_GetCurrentMemoryTarget(DMA1_Stream1) == DMA_Memory_0)        //获取当前目标内存是否为 DMA_Memory_0$ F' L; t* B& |8 \
  8.         {9 t4 y2 \$ N9 L8 Z+ p0 O2 i0 s
  9.             //重新设置DMA+ ]' U5 X8 }% c( X( I! H
  10.             DMA_Cmd(DMA1_Stream1, DISABLE);; Y5 Z2 x( S& E; ~% l/ ?/ I! s
  11.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);        //获取当前剩余数据量  接收18字节,则剩余数据量为36-18=18& T+ \4 k4 @6 e) ]; k7 w. x
  12.                                         % P1 ?9 J; Q' c  D/ p& z% f9 d
  13.                                           DMA_SetCurrDataCounter(DMA1_Stream1, 36);        //重新设置数据量
    & ~* u$ i* f, K" z9 n2 u) t, g" {( K
  14.                                                 DMA1_Stream1->CR |= (uint32_t)(DMA_SxCR_CT);  //将DMA指向Memory1# @, [# ~# n) c! M5 \- \
  15.                                         ' G- l4 J% n& i* {( z2 \& W, D
  16.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);
    $ |" H; f8 B# U, X* ^% h
  17.             if(this_time_rx_len == 18)        //接收成功18个字节长度7 n( V1 |/ e0 K! p2 p4 @
  18.             {
    . _% p/ r. o) x" y( ]
  19.                 //处理遥控器数据, ^  ~0 ?6 V; V
  20.                 RemoteDataProcess(sbus_rx_buffer[0]);        //Memory_0
    ( h: O4 e- T+ Q2 G
  21.             }4 f  G  G& o' s' O( U8 F

  22.   d$ x. ]1 J3 r4 D% Q
  23.         }( M1 \* d9 C2 Q2 [
  24.         else        //获取当前目标内存是否为 DMA_Memory_1- Q) A; x5 n% |( O& H; h
  25.         {5 {" n  y& h$ f( ]
  26.             //重新设置DMA2 H! g9 [  F& l6 L4 b
  27.             DMA_Cmd(DMA1_Stream1, DISABLE);
    ( Z) J8 M- \' v6 b. ?$ O
  28.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);                //获取当前剩余数据量 接收18字节,则剩余数据量为36-18=18
    8 p2 o7 ~6 c$ l- P8 `$ O6 Y9 Y/ z/ G

  29. 0 y' b2 z# j' A( m# |( D
  30.                                                 DMA_SetCurrDataCounter(DMA1_Stream1, 36);        //重新设置数据量% [, H5 x) X+ |4 X2 y7 S
  31.                                                 DMA1_Stream1->CR &= ~(uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0! I  o$ }9 u! _
  32. 3 g7 g( Z/ c0 |  _
  33.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);
    : Y& |8 w/ B  l  c5 b

  34. 7 m/ _9 A  ^' _' H* y' Q* E
  35.             if( this_time_rx_len == 18)        //接收成功18个字节长度- p$ X0 \, [7 r3 R: \( {( U
  36.             {% c* t7 P0 q: i9 W0 A
  37.                 //处理遥控器数据- O2 d% Z( o+ u+ w, M" P
  38.                 RemoteDataProcess(sbus_rx_buffer[1]);        //Memory_1' ]- A# s$ l9 z9 g# y  a
  39.             }' _$ l* O8 u2 k9 a, `, j2 i
  40. , [. I; G$ T  M/ C7 Q0 L
  41.         }
    ! A# m# K2 ]+ i: h3 V
  42.     }2 M2 Y! g! j$ e* V3 B/ W
复制代码

! m8 o# J% R$ k4 D, @9 |: ]两种DMA双缓冲使用方法的区别
" Q$ I7 j# H, U. G7 |' C. \第二种方法和第一种方法的区别大同小异,这种方法由于将BufferSize设为比18大的值,也就是当第一帧数据传输完成后,Counter值不会自动填满,且内存区域还是指向Memory0,然后我们将剩余数据量保存下来,再将Counter值填满,接着把DMA指向Memory1,最后通过判断剩余数据量来决定是否对数据进行处理。2 B/ O5 I  J/ u: g7 P8 O7 h

8 W+ k8 n- a7 B0 n可以看到,在第一种方法下,我们并没有使用5 S% m. ~2 c! e$ b
3 W% i) F  U2 C6 t# D6 U  F
  1. DMA1_Stream1->CR &= ~(uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0% |6 g/ H5 ~2 {! m/ F
复制代码
  1. DMA1_Stream1->CR |= (uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0
复制代码

. l4 z% O. i7 T  B
! N  O# w" @! Z- h( o! J这是因为当传输数据量和BufferSize大小一致时,完整接收完一帧数据后,DMA会自动指在向下一内存地址,因此在第二种方法中的手动改变内存指向就由DMA自动处理了。& c9 p7 G) d# I; n# U, D
————————————————
# d9 Q: [" h! ^% v5 ~- ]版权声明:水木皆Ming
5 O3 E: U$ _5 j; X2 Y0 i6 r- G- B3 L
' N" D9 c( a7 }8 v3 {/ q& j; ?
收藏 评论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 手机版