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

STM32的DMA双缓冲模式详解

[复制链接]
STMCU小助手 发布时间:2023-1-3 18:18
DMA双缓冲是什么?) g4 d; J! A" i
在使用STM32的DMA时我们通常使用的是普通DMA传输,但STM32自带了一个双缓冲模式设置,这个模式有什么优点呢?
6 I% r/ e: Y+ Y$ n" l! e
接下来我会在下面的介绍里详细说明:
8 _$ h# W" Q: f
! |0 a  f' k+ i! vDMA全称Direct Memory Access,即直接内存访问。
" v9 h1 v0 M5 ]5 W2 x, W0 Q4 ^普通DMA的使用是在DMA的数据流中进行的,设置好DMA的起点和终点以及传输的数据量即可开启DMA传输。
5 R2 b1 J0 A& S) V& Z+ F
) Z: Y! b! S) p
bb2b5bfd30b44de3b44ee1e55e75aca9.png % ^: v' h+ x: {: ^5 t$ Q
- K, o# Y! C& w8 v0 @
DMA开启后就开始从起点将数据搬运至终点,每搬一次,传输数据量大小就减1,当传输数据量为0时,DMA停止搬运。
: z/ @: l8 V$ \7 J$ [7 V8 T; F1 g* Q5 C
DMA的传输模式如果是普通模式,则当传输数据量为0时,需要程序中手动将传输数据量重新设置满才能开启下一次的DMA数据传输。
3 N% c' d6 y% E0 U' d4 X+ X8 o8 `$ ]' w5 qDMA的传输模式如果是循环模式,则当传输数据量为0时,会自动将传输数据量设置为满的,这样数据就能不断的传输。( I5 Y1 Z3 o4 k  E" h3 a% V% G
+ M/ ^, [/ h/ g2 c: g$ a; P/ L
412b8ec24c624455a582b052a38c291b.png ) h; k) f, h; g0 L. u) ]
% j( X1 g5 f6 ]% G4 l  Y) q
那么,双缓冲是什么意思呢?
  I. A* }5 B5 c. O0 @我们知道,普通DMA的目标数据储存区域只有一个,也就是如果当数据存满后,新的数据又传输过来了,那么旧的数据会被新的数据覆盖。(这就是普通DMA的缺点)
$ `9 L  d/ z% l  {" t' T9 q" ~0 G# Q" E- A0 ~/ o
1ba7d23fab984cf3a6d86bc2800dee3f.png
, P. ]1 \% I+ @9 Q* @. K3 p
6 v9 n1 C0 Y: q8 G3 H1 A) S
6 m" f6 m$ I1 r' M/ {3 u
而双缓冲模式下,我们DMA的目标数据储存区域有两个,也就是双缓冲,
, [0 F  L6 V! @- f当一次完整的数据传输结束后(即Counter值从初始值变为0),会自动指向另一个内存区域。
3 |! a, \, P  j8 z6 A
3 u7 [0 m6 c3 ]7 [9 }
5d633c7fc21c43d8abd830b4bc36c772.png
: K! f" x9 ^5 W7 t( ]$ E6 a8 U2 L9 J. d7 Y
如何使用DMA双缓存?
( P+ W1 P! c. f4 d那么我们在程序里怎么使用双缓冲呢?7 Z6 S1 }$ X, _* c
这里我们有两种方案使用双缓冲,一种是利用双缓冲自动跳转内存区域,一种是主动跳转内存区域。  P( z/ e( w4 v; P3 o
* i$ C' N3 o& h7 z& k$ B
这里我们使用DMA的传输模式是循环模式8 G( q8 l0 N2 X3 [& N: U9 x/ ^

2 q( R7 r. O3 T7 N1 v3 x2 C3 k8 k
  1. DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;
    $ ^( N7 A4 R; E, ]! L
复制代码

4 i2 K: k/ ~! s6 R/ I首先是第一种:1 F( K4 t, L0 z# |1 Y* ]
这里我们以大疆RoboMaster的遥控器使用为示例:+ g0 `4 {0 j8 c% k5 Y
首先我们定义好双缓冲使用的两个内存区域* E5 X1 g! w  ~6 I* R
  1. uint8_t sbus_rx_buffer[2][18u]; //double sbus rx buffer to save data
    % `2 J9 D, A8 a! M# d- K
复制代码

+ P, s( G( M; Z! p  P9 ]这里我们设置长度为18,也就是一帧遥控器数据的长度。" r9 @3 z. h) M

( @+ ]$ c2 j+ Z7 E+ h) D" N9 @' K
  1. DMA_InitStruct.DMA_BufferSize = 18;; o7 L- K# H. O, t8 a+ T- k
复制代码

. A& j/ r# W" a( n4 g7 ]' J3 v! }然后配置好双缓冲的两个内存区域地址
* Z7 ]7 |  `+ ^3 X
) [9 t% i# a0 M  L, ?( l3 |; P
  1. DMA_InitStruct.DMA_Memory0BaseAddr =  (uint32_t)&sbus_rx_buffer[0][0];
    ( l# j2 r: e1 J
复制代码
4 v9 B; R$ m# p' H7 s
这里配置内存区域2的地址,并把当前DMA指向内存区域1的地址# `7 i% |' Y1 s# V
' ^9 x: ?7 p! v
  1. DMA_DoubleBufferModeConfig(DMA1_Stream1,(uint32_t)&sbus_rx_buffer[1][0],DMA_Memory_0); //first used memory configuration
    - ?6 A6 k. K+ o* S# w. T! r. v
复制代码
! H5 |) Q+ W. [
启动DMA双缓冲$ _; T" Z. F2 A/ p: l4 Y( Q& C
/ n! F: y. ^4 R5 o8 g
  1. DMA_DoubleBufferModeCmd(DMA1_Stream1, ENABLE);1 f$ Z+ K$ R& S) Z0 Y9 N  B
复制代码
/ [* J9 c9 r& [/ _, S7 n6 S
我们使用DMA时利用串口的空闲中断接收一帧一帧的数据。
3 H* _; ?  w4 S% x, @$ a% T在串口中断处理函数中对数据进行处理
+ Z! |3 k3 a/ G$ {! l0 ^
) I& z& M7 n' h0 N$ d
  1. 1 f9 P! F( W9 x) g9 J
  2. uint16_t this_time_rx_len = 0;        //当前剩余数据长度
    % g$ v/ w* ?5 m2 Y
  3. 7 t% f) k) `) L. D, s8 q
  4. if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)        //判断是否为空闲中断
    5 y" ]9 p/ j8 F0 ]
  5.     {
    ' a: a/ j5 Y$ l4 c. r
  6.         USART_ReceiveData(USART3);        //清除空闲中断标志位
    $ f# i5 R  Q& A" _
  7. 3 j5 I" Q7 ^5 f4 W% {
  8.         if(DMA_GetCurrentMemoryTarget(DMA1_Stream1) == DMA_Memory_0)        //获取当前目标内存是否为 DMA_Memory_0
    $ P0 s  v2 o/ a: i6 S/ r) R
  9.         {
    # V2 L+ j$ x) n, e1 O; M
  10.             //重新设置DMA
    0 `# a, o. o% y" S
  11.             DMA_Cmd(DMA1_Stream1, DISABLE);
    6 G* |; s  H" |3 H. W& n
  12.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);        //获取当前剩余数据量% Y6 U  N9 \. {# ?+ b# L+ U
  13.                                         ' @, G- W& c# f
  14.                                          DMA_SetCurrDataCounter(DMA1_Stream1, 18);        //重新设置数据量& Q/ K" w6 [% U6 x, J3 E8 S
  15.                                           
    2 W, g& _! j9 S- P, d  H
  16.                                          DMA_Cmd(DMA1_Stream1, ENABLE);
    0 n! O3 y6 ^& F; c1 L* [
  17.             if(this_time_rx_len == 18)        //接收成功18个字节长度
    : B+ \2 P& \, r7 R' p
  18.             {
    % _4 [' R$ \( l8 r3 x2 \0 c8 q
  19.                 //处理遥控器数据; C( A. O' G* d. e2 Q
  20.                 RemoteDataProcess(sbus_rx_buffer[1]);        //Memory_1
    2 r7 \! _$ T+ E$ r( r9 C4 ~
  21.             }
    + A+ F$ m$ }/ j# O# |( `0 N5 M
  22. : c+ r' _9 R% b! c
  23.         }
    ; k; D: D: n" x! ~% C' Q
  24.         else        //获取当前目标内存是否为 DMA_Memory_1. g6 l  W1 ~1 d$ G( O# E
  25.         {' T9 |+ ?$ U" F( W2 W& K; Z
  26.             //重新设置DMA
    & g9 k% {! Z4 H
  27.             DMA_Cmd(DMA1_Stream1, DISABLE);3 j2 t. j$ m# N6 r  @
  28.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);                //获取当前剩余数据量7 a7 s' O9 m8 C- F
  29. ; Y7 {. l/ \1 z8 e/ ^2 o
  30.                                                 DMA_SetCurrDataCounter(DMA1_Stream1, 18);        //重新设置数据量
    * T1 ~. n# |. K7 Y# v
  31. " A& M  W: i% ]
  32.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);& L4 {- b7 r  T7 ^2 L

  33. 5 Z& G( w. l5 h6 H( e
  34.             if( this_time_rx_len== 18)        //接收成功18个字节长度
    7 O/ k; I9 @% T' M. O  g2 l* u9 @
  35.             {
    ! I- B0 I  h# {, [" S+ s- l7 ^
  36.                 //处理遥控器数据* g2 u/ h) g; T& ^
  37.                 RemoteDataProcess(sbus_rx_buffer[0]);        //Memory_0' K( Z& ~# V% v, h3 ~
  38.             }% J; F5 U4 t6 k6 c6 e, s
  39. + i) y! O& ~9 ^6 s3 v8 [  |. g0 z
  40.         }
    ' @1 @9 \' Y) v8 S3 d- y
  41.     }" N" |- x6 u) m4 N
复制代码
0 W- H) z1 r* O( l0 i
该逻辑在程序复位后,当一帧遥控器数据发送过来后,Memory0区域被填满18个字节,DMA自动指向Memory1,所以第一次进入中断处理函数时,获取内存指向的时候得到的指向是Memory1。
+ _) H/ F% C+ I$ J6 z然后判断剩余数据大小是否为18,如果是则说明上一帧数据接收正常,可以对数据进行处理,如果不为18,则说明上一帧数据接收长度不正常,这时候就不对数据进行处理,当下一帧数据发送过来后对数据进行检验,正常后才对数据进行处理。, {1 j( Y" i. Z& C0 {

5 w  C# Q1 P/ }& E. b/ \8 h然后是第二种:; Z4 e9 Q9 F% X; E8 B- T' ~/ |
这里我们将BufferSize改为比一帧数据长度大的值(比18大),这样可以在一帧数据传输完成后不会因Counter值变0导致DMA指向下一内存区域。
8 K9 ~; U' Z( N- U# y' L
, p) ~* m! O! W2 F! i) X0 E* z
  1. DMA_InitStruct.DMA_BufferSize = 36;
    ( f3 S1 V& k( g8 |1 [+ a
复制代码
  1. uint16_t this_time_rx_len = 0;        //当前剩余数据长度
    3 N! s- e, ~$ I% _3 S4 ?

  2. - ~* Y5 Y# [. S
  3. if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)        //判断是否为空闲中断
    6 ~4 k4 h$ g" G6 q. s3 t
  4.     {
    & l5 y% q: e6 M0 F8 ^+ {
  5.         USART_ReceiveData(USART3);        //清除空闲中断标志位) \& v% U+ a8 r* i; A% N0 F

  6. + o8 q" H2 K' ?9 y! E
  7.         if(DMA_GetCurrentMemoryTarget(DMA1_Stream1) == DMA_Memory_0)        //获取当前目标内存是否为 DMA_Memory_0
    9 H! s9 X9 [) q4 t* E2 ], }! r
  8.         {
    - j; G+ U! u1 M' [( `
  9.             //重新设置DMA
    4 C$ P5 [/ b; i& H/ H5 T4 V
  10.             DMA_Cmd(DMA1_Stream1, DISABLE);
    2 ?7 l* M* X& _& m
  11.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);        //获取当前剩余数据量  接收18字节,则剩余数据量为36-18=18
    9 |- P# F" Q2 p/ }/ i
  12.                                         + O! T6 P& o/ S, E6 o
  13.                                           DMA_SetCurrDataCounter(DMA1_Stream1, 36);        //重新设置数据量' Z5 y- Z. \  \
  14.                                                 DMA1_Stream1->CR |= (uint32_t)(DMA_SxCR_CT);  //将DMA指向Memory1
    , z: p1 ]# I( H+ f
  15.                                        
    + H& E* i  Q0 Q" U, w7 o% Q% G; e8 H
  16.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);
    1 Z' b# z* x: P, H4 W
  17.             if(this_time_rx_len == 18)        //接收成功18个字节长度/ b: i: Q5 B7 G7 V* M/ e
  18.             {( H  {# Z3 [- x% E0 w& D& K, A
  19.                 //处理遥控器数据
    ! q' q8 z( G! X* v. {8 L6 ~
  20.                 RemoteDataProcess(sbus_rx_buffer[0]);        //Memory_0
    2 a# c+ |1 Z9 N- g9 H% B
  21.             }2 K9 n3 {3 G" }! D
  22. ) A) @7 p1 |5 y/ }8 Q$ s9 j* z
  23.         }4 J2 ]; p4 }4 y, c' y7 c
  24.         else        //获取当前目标内存是否为 DMA_Memory_1% M2 ~. L! A9 F# ]
  25.         {
    1 a9 {3 ]3 x3 n8 j
  26.             //重新设置DMA
    , y' ~/ H# T! ^8 P, f
  27.             DMA_Cmd(DMA1_Stream1, DISABLE);
      `9 F. H, i# M* u
  28.                                           this_time_rx_len = DMA_GetCurrDataCounter(DMA1_Stream1);                //获取当前剩余数据量 接收18字节,则剩余数据量为36-18=18
    ' C; q2 i# N6 y' O& ?. d
  29. : o1 I& ^+ W* s5 m7 u
  30.                                                 DMA_SetCurrDataCounter(DMA1_Stream1, 36);        //重新设置数据量; }# N( B& o8 }3 g" I0 H
  31.                                                 DMA1_Stream1->CR &= ~(uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0
    / |) y& \- n' T

  32. & u# A7 V# G) R5 q
  33.                                                 DMA_Cmd(DMA1_Stream1, ENABLE);
    3 l' }" d3 D3 u

  34. 4 }2 J' A5 G$ f1 I1 V" g6 H' S
  35.             if( this_time_rx_len == 18)        //接收成功18个字节长度* G, o& ^: w  J% C6 W; E3 b
  36.             {; `2 H$ W4 s0 e: r# o
  37.                 //处理遥控器数据1 j, S8 P- \  F4 `: M# Z
  38.                 RemoteDataProcess(sbus_rx_buffer[1]);        //Memory_1
    ( l- p4 w/ P) D5 O4 c; G0 m; l
  39.             }
    " \  ]8 Y* B  @

  40. . V* J. }+ T5 Q5 |: R
  41.         }
      p( m% D( t+ h. g
  42.     }* R- ~( U5 d2 `; }) o
复制代码
! F- \7 r- R/ l: g
两种DMA双缓冲使用方法的区别
% d  O  M1 s; K/ `2 L, ?; h% S  ]第二种方法和第一种方法的区别大同小异,这种方法由于将BufferSize设为比18大的值,也就是当第一帧数据传输完成后,Counter值不会自动填满,且内存区域还是指向Memory0,然后我们将剩余数据量保存下来,再将Counter值填满,接着把DMA指向Memory1,最后通过判断剩余数据量来决定是否对数据进行处理。4 Y: E; _* _* y8 Z! O- W

: n- F, |. i" x7 z! R7 A$ `; T可以看到,在第一种方法下,我们并没有使用  }) }. |( |: E" g! _% O

# Z, A  u( G% q2 ]7 X% b2 O
  1. DMA1_Stream1->CR &= ~(uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0
    4 n3 ?. Z, z6 x" J2 d, p$ T
复制代码
  1. DMA1_Stream1->CR |= (uint32_t)(DMA_SxCR_CT);  将DMA指向Memory0
复制代码

+ E8 w/ u% V- o3 s" U2 c6 J$ Y6 ]) {, g! a  p' }0 o9 A6 K
这是因为当传输数据量和BufferSize大小一致时,完整接收完一帧数据后,DMA会自动指在向下一内存地址,因此在第二种方法中的手动改变内存指向就由DMA自动处理了。# V* @( W3 |! G$ g5 m: v" Y
————————————————1 u  T: A& B  }* H9 O; s
版权声明:水木皆Ming9 H# q( P  |' l! j9 G- e: {2 @

% w+ ?8 I9 D" r% |+ s) e! A8 J* t2 M1 ~5 X
赞 收藏 评论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 手机版