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

STM32的DMA双缓冲模式详解

[复制链接]
STMCU小助手 发布时间:2023-1-3 18:18
DMA双缓冲是什么?. U3 ~$ Z( a  O
在使用STM32的DMA时我们通常使用的是普通DMA传输,但STM32自带了一个双缓冲模式设置,这个模式有什么优点呢?& k2 P9 v, a7 r1 Z/ c
接下来我会在下面的介绍里详细说明:1 f# _; q7 ~' v) ^  X8 @  |4 W
9 W! _7 [# w( D- b4 L
DMA全称Direct Memory Access,即直接内存访问。
" G8 y: y" _  V. ?- j$ q普通DMA的使用是在DMA的数据流中进行的,设置好DMA的起点和终点以及传输的数据量即可开启DMA传输。
5 u3 `# u& N; u* t2 a; d; B' }. z# ^7 S! O! W' b/ v1 i
bb2b5bfd30b44de3b44ee1e55e75aca9.png ( J4 G9 u: U: \- \- D9 ?
. j, Z- m& [. B- ~
DMA开启后就开始从起点将数据搬运至终点,每搬一次,传输数据量大小就减1,当传输数据量为0时,DMA停止搬运。1 }5 {. E# v! m
& \- V1 j6 j" b
DMA的传输模式如果是普通模式,则当传输数据量为0时,需要程序中手动将传输数据量重新设置满才能开启下一次的DMA数据传输。; K( X# r& x; ~
DMA的传输模式如果是循环模式,则当传输数据量为0时,会自动将传输数据量设置为满的,这样数据就能不断的传输。! Y# D$ ], |: p# Y
& u; e: k; ~6 P% |+ ]1 v
412b8ec24c624455a582b052a38c291b.png
9 t; C( u$ J- e8 P0 j/ [4 f( K

2 I% d8 }* H" y/ i; [, G; k) k那么,双缓冲是什么意思呢?9 Q' S' Q7 A, ]3 [" i6 U% _. I, e+ y
我们知道,普通DMA的目标数据储存区域只有一个,也就是如果当数据存满后,新的数据又传输过来了,那么旧的数据会被新的数据覆盖。(这就是普通DMA的缺点)1 }& r0 E) b9 Z2 l* B2 m$ |

# K" ~$ }" `5 P7 P& b7 s7 `$ X6 x
1ba7d23fab984cf3a6d86bc2800dee3f.png
8 i- B7 A/ \( {0 K
; @- N* P, `9 z0 a# m

, i) ]* P1 h+ l/ S  v而双缓冲模式下,我们DMA的目标数据储存区域有两个,也就是双缓冲,$ b5 d4 }; ~! j. o
当一次完整的数据传输结束后(即Counter值从初始值变为0),会自动指向另一个内存区域。
0 r. A5 {6 N5 k0 e/ H/ S' P0 P% O. x, A9 G$ }1 h, t
5d633c7fc21c43d8abd830b4bc36c772.png 7 V) Y) I2 Y$ @0 q' o
: I* q5 M( C- q6 U* `( A# I6 R
如何使用DMA双缓存?+ ]! s) m2 i( w1 x; i- H
那么我们在程序里怎么使用双缓冲呢?) B  z/ P" _! h
这里我们有两种方案使用双缓冲,一种是利用双缓冲自动跳转内存区域,一种是主动跳转内存区域。
4 k; e' |( p6 v( j* Y1 ~# D- g0 X0 d3 c, E* h( {& o3 T
这里我们使用DMA的传输模式是循环模式
5 G! J, |( s7 u6 F6 L% m( R* \' H
  1. DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;/ j6 t& ]6 k- Y
复制代码
/ w) C3 t2 W  L: P
首先是第一种:7 D8 M/ X: V# p! {2 X% K
这里我们以大疆RoboMaster的遥控器使用为示例:
; B3 n) S2 ?- Z% x3 h! k( `- }首先我们定义好双缓冲使用的两个内存区域
1 L" W! X+ e8 F: u+ C1 q6 K
  1. uint8_t sbus_rx_buffer[2][18u]; //double sbus rx buffer to save data$ I2 `3 ?+ c8 _4 }$ V
复制代码

0 b  M7 {7 V% x* u这里我们设置长度为18,也就是一帧遥控器数据的长度。3 n5 i8 k% d# h6 @

, p* W" g4 U0 D% V- i/ b2 k
  1. DMA_InitStruct.DMA_BufferSize = 18;
    / h# \1 y; Y7 k- R& W9 o  ?3 Q, ^
复制代码
! O; [% n1 L& O2 c
然后配置好双缓冲的两个内存区域地址) g* ?" g, d& J  c+ U! e

+ P: W! E3 c5 k
  1. DMA_InitStruct.DMA_Memory0BaseAddr =  (uint32_t)&sbus_rx_buffer[0][0];4 Y6 }% |! r$ s- d5 a" \9 r; J7 |1 q
复制代码

0 [8 Q' q/ ^+ Z" y这里配置内存区域2的地址,并把当前DMA指向内存区域1的地址
( L; c" T- C% E$ A1 B2 E9 d+ B/ w: a( B1 L% s
  1. DMA_DoubleBufferModeConfig(DMA1_Stream1,(uint32_t)&sbus_rx_buffer[1][0],DMA_Memory_0); //first used memory configuration
    % u/ y' C$ e( s/ ]- |
复制代码
. L/ ]/ P3 I# b* ]3 [% X2 R8 v4 |4 }
启动DMA双缓冲& I7 a) c; ^$ c$ g: P$ _. o

) M" p# k4 M0 ^
  1. DMA_DoubleBufferModeCmd(DMA1_Stream1, ENABLE);
    ; m2 B" `8 O6 G8 J& q
复制代码

7 @# R3 S$ t- b$ R! F/ {我们使用DMA时利用串口的空闲中断接收一帧一帧的数据。
/ s* M( b. w8 u' y  c: E在串口中断处理函数中对数据进行处理
# O! B) Y: s( Y8 ]8 R# @/ |+ c: Z8 O9 H  {' y
  1. 9 {, p. Z" |  L/ N0 |: a# B
  2. uint16_t this_time_rx_len = 0;        //当前剩余数据长度
    4 w# r: l5 F# V2 d' C2 d
  3. 5 G7 J$ d" e* Y/ `
  4. if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)        //判断是否为空闲中断* q) F; ?( P8 `7 U
  5.     {1 I: ]9 {1 \5 k8 E
  6.         USART_ReceiveData(USART3);        //清除空闲中断标志位1 v$ R3 K# X1 }2 S7 s2 G) M+ e9 g
  7. ( A/ M/ A( j8 d; U
  8.         if(DMA_GetCurrentMemoryTarget(DMA1_Stream1) == DMA_Memory_0)        //获取当前目标内存是否为 DMA_Memory_0' O, m$ V8 p( g3 x
  9.         {
    9 D6 L! B: `$ [# s! A' h
  10.             //重新设置DMA
    ! i$ A2 [, i% i' M5 F9 `. k
  11.             DMA_Cmd(DMA1_Stream1, DISABLE);
    6 x! s) a: B7 v: M( A
  12.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);        //获取当前剩余数据量4 k3 T- b/ i  j
  13.                                         % V% P3 {! u# v& w' y
  14.                                          DMA_SetCurrDataCounter(DMA1_Stream1, 18);        //重新设置数据量# a* J0 Z( v  T3 v2 A* P: M
  15.                                           
    9 K4 H# M5 q2 W; d6 O
  16.                                          DMA_Cmd(DMA1_Stream1, ENABLE);
    ( b) N5 G. Z  S
  17.             if(this_time_rx_len == 18)        //接收成功18个字节长度; ~( A) B% G. _
  18.             {  V2 J1 I. f6 ?% p$ b9 F
  19.                 //处理遥控器数据' W: K; I) i3 {6 ~2 {. n- m; j
  20.                 RemoteDataProcess(sbus_rx_buffer[1]);        //Memory_1( S4 q8 E8 V" l" X+ d
  21.             }
    8 P* @- b  y; M$ {* L0 j

  22. " a$ J) q" @  |6 \
  23.         }$ S! j- J. B6 b2 W, |- m6 @/ ?
  24.         else        //获取当前目标内存是否为 DMA_Memory_1
    # f% M8 M, x& v  [
  25.         {
    7 u* Z) p& o1 B+ J
  26.             //重新设置DMA
    " h- F' M* A( J0 k
  27.             DMA_Cmd(DMA1_Stream1, DISABLE);0 R/ V9 f$ D/ A
  28.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);                //获取当前剩余数据量- H0 g! U" y: [0 t% t3 `
  29. $ }3 ?* \+ [* y! ]# _# j
  30.                                                 DMA_SetCurrDataCounter(DMA1_Stream1, 18);        //重新设置数据量) U. ^5 y- u) \) A& V8 M

  31. 6 {3 ?/ S& c) ]9 ?
  32.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);/ B% f4 A' q4 A5 w- n. [9 v6 w

  33. 5 M6 M: k6 n# z0 {$ Z4 Z4 {
  34.             if( this_time_rx_len== 18)        //接收成功18个字节长度
    , W% H  h7 b( j( e
  35.             {7 P& F8 Q+ J( I
  36.                 //处理遥控器数据
    " G% I6 [% s% Z' g, g8 B! y1 U
  37.                 RemoteDataProcess(sbus_rx_buffer[0]);        //Memory_05 A1 \: V" ^: R" e1 ]* B" [) a* i
  38.             }4 q2 ^7 \3 s$ ^0 a$ @% H. M
  39. 4 {; F+ B4 v5 J( z5 _# z6 l* t
  40.         }3 c) @2 |+ Q" x) i- u$ m
  41.     }3 D; h9 ~" e5 B! ^( j4 i/ J9 H  I
复制代码
  V" i& H* S* A& {4 c# `
该逻辑在程序复位后,当一帧遥控器数据发送过来后,Memory0区域被填满18个字节,DMA自动指向Memory1,所以第一次进入中断处理函数时,获取内存指向的时候得到的指向是Memory1。
0 c" z6 c2 I- A8 c2 G7 o1 i9 x然后判断剩余数据大小是否为18,如果是则说明上一帧数据接收正常,可以对数据进行处理,如果不为18,则说明上一帧数据接收长度不正常,这时候就不对数据进行处理,当下一帧数据发送过来后对数据进行检验,正常后才对数据进行处理。
  T6 n: {/ k: @8 Q8 T7 J1 V* y) d/ V9 C
然后是第二种:
9 ?3 X$ ]: {4 D1 [. M这里我们将BufferSize改为比一帧数据长度大的值(比18大),这样可以在一帧数据传输完成后不会因Counter值变0导致DMA指向下一内存区域。: I# U4 ^) s2 w) ?3 K' Z' X3 m
# ~8 j3 C2 j+ k5 ~' Z9 K
  1. DMA_InitStruct.DMA_BufferSize = 36;$ I3 }- t, ^# o! O! Z2 ?; T4 n
复制代码
  1. uint16_t this_time_rx_len = 0;        //当前剩余数据长度* a' L. l+ D- S5 G8 y
  2. : Y! H$ S8 Q  T. H# {+ V/ a
  3. if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)        //判断是否为空闲中断
    ; ~$ Z; }" X! K+ P' j- |2 Q- |2 x
  4.     {6 y; ]) |- {' \$ M
  5.         USART_ReceiveData(USART3);        //清除空闲中断标志位
    8 _8 v& I8 I3 F/ ^: s1 f. Y

  6. 5 Q% q- C' u3 L' E
  7.         if(DMA_GetCurrentMemoryTarget(DMA1_Stream1) == DMA_Memory_0)        //获取当前目标内存是否为 DMA_Memory_0
    , v2 b2 l* i. a: a% I) X2 ]5 f
  8.         {
    # ^& p6 `% G9 P4 {- I* ?3 H
  9.             //重新设置DMA
    , _4 Z, }$ U: v" m1 L% u. q4 b
  10.             DMA_Cmd(DMA1_Stream1, DISABLE);
    5 d% R7 ^$ x) C1 b
  11.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);        //获取当前剩余数据量  接收18字节,则剩余数据量为36-18=18& K! f! C" }. n) T4 v& O
  12.                                        
    ; u* ]( W, x" C( o' ~; _3 @
  13.                                           DMA_SetCurrDataCounter(DMA1_Stream1, 36);        //重新设置数据量- {) `  K, @3 ^% o0 X
  14.                                                 DMA1_Stream1->CR |= (uint32_t)(DMA_SxCR_CT);  //将DMA指向Memory1& t6 l" k2 z" Z
  15.                                        
    : }7 a7 s  J; z7 f. J, g+ x3 Q
  16.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);0 B+ x5 G. t) K+ y+ l
  17.             if(this_time_rx_len == 18)        //接收成功18个字节长度
    5 z( ?- |- m5 K: v9 O
  18.             {
    . e+ h4 ~9 C- \, I4 b4 z7 j, P
  19.                 //处理遥控器数据
    4 k- h' d& d: i% D; Y6 d1 W
  20.                 RemoteDataProcess(sbus_rx_buffer[0]);        //Memory_08 Q5 M) q- {& Q6 }( C- T
  21.             }3 A5 M% u" A: o9 H4 H) s9 s! y

  22. - n- z' A" C3 Z& [0 D
  23.         }
    1 x1 U( \% T3 Y7 J$ @3 ^3 d
  24.         else        //获取当前目标内存是否为 DMA_Memory_1
    . c! s" ~+ b# S. A; M/ X
  25.         {
    : _: S# r2 |* e0 o8 i
  26.             //重新设置DMA
    : @: E* Y- X% N: |# I
  27.             DMA_Cmd(DMA1_Stream1, DISABLE);; u0 E: K! y& \  t4 r
  28.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);                //获取当前剩余数据量 接收18字节,则剩余数据量为36-18=189 K: m' u/ R; r3 S( i! Q
  29. % G: k  Z/ c4 R
  30.                                                 DMA_SetCurrDataCounter(DMA1_Stream1, 36);        //重新设置数据量; M; [: v+ c; q7 f6 T
  31.                                                 DMA1_Stream1->CR &= ~(uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0, ?* Y5 o$ j+ z6 v2 H8 T& t9 d& S
  32. . t3 [, M8 q6 a, Q
  33.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);: l8 w+ G4 m- M: m( }
  34. . |3 ~4 c; H8 P6 S) q
  35.             if( this_time_rx_len == 18)        //接收成功18个字节长度( H) E4 m+ y3 S7 i$ |1 p4 _
  36.             {
    4 I( G  H% U, }! S8 @
  37.                 //处理遥控器数据) d% g5 x, ~/ ~, D9 P4 c+ e; v+ f
  38.                 RemoteDataProcess(sbus_rx_buffer[1]);        //Memory_1
    & G8 S7 q. h- m' M7 C% c- K  W1 L
  39.             }' v9 l6 M! N+ B& S/ m' k

  40. ) d4 Y. C  g$ Y- U; X) ]2 r1 B5 d2 g
  41.         }
    2 P$ P, w! A) _- t/ M3 a5 i5 U
  42.     }
    1 ~# h8 V% s* T% O. i* m
复制代码

: E! L  ]5 [  r两种DMA双缓冲使用方法的区别: p% J% R, B) {" Q6 L: `; ~( ^
第二种方法和第一种方法的区别大同小异,这种方法由于将BufferSize设为比18大的值,也就是当第一帧数据传输完成后,Counter值不会自动填满,且内存区域还是指向Memory0,然后我们将剩余数据量保存下来,再将Counter值填满,接着把DMA指向Memory1,最后通过判断剩余数据量来决定是否对数据进行处理。2 [$ a: X: ?; F
9 w4 b" W0 g, ^$ \, A
可以看到,在第一种方法下,我们并没有使用
: [9 N& c- y- s9 G! q4 \& z2 b; \
( l0 u$ t$ I( e
  1. DMA1_Stream1->CR &= ~(uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0, L/ {: R! v2 F3 E) t0 s# r3 G$ ]) A
复制代码
  1. DMA1_Stream1->CR |= (uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0
复制代码

# Z& M( F/ {) |; f
/ c" ?% b7 V8 b这是因为当传输数据量和BufferSize大小一致时,完整接收完一帧数据后,DMA会自动指在向下一内存地址,因此在第二种方法中的手动改变内存指向就由DMA自动处理了。
. Q, T( v/ z7 N4 \————————————————" V% E. J5 t! }2 w3 u/ c
版权声明:水木皆Ming
1 [# I1 E& \% N8 x9 r; ~0 B# d4 a2 J

' h6 T. U/ |" j! Z" m0 {: M
收藏 评论1 发布时间:2023-1-3 18:18

举报

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

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

关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版