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

基于STM32的DMA存储器到外设经验分享

[复制链接]
攻城狮Melo 发布时间:2023-3-18 15:57
DMA简介
: t4 Z6 n, _) J" }6 r" wDMA(Direct Memory Access)——直接存储器存取,就像其名称一样,DMA的主要作用是搬数据,DMA可以把数据从存储器搬到外设、从外设搬到存储器、从存储器搬到存储器。DMA的特殊之处就是搬运数据不需要占用CPU,DMA控制器包含了DMA1和DMA2,其中DMA1由7个通道,DMA2有5个通道。
" n3 K/ h3 q& h& d
. D0 @% i+ r! V+ V4 A. M! {; {$ L
9 t9 f  T' {7 z3 x' X+ e8 [' [5 ^9 ^
DMA框图
  j" K) p8 `) ~6 s& W+ ]了解外设先要理解其工作框图:5 C) u- E  I% b4 h) n2 r
+ f% [- z. X$ M3 z2 S
7 I! y2 h* E, Q. F& K# O1 r) G
功能框图主要分为三部分:' g) _: n; q- P+ D
1.DMA请求- G2 [5 i& \# o# \& F
外设如果想要通过DMA传输数据,必先给DMA控制器发送DMA请求,DMA收到请求信号之后会传回给外设一个应答信号,当外设应答后且DMA控制器收到应答信号后,就会启动DMA的传输,直至传输完毕。但DMA有2个DMA控制器,15条通道,不同的通道对应着不同的外设请求,所以必须对通道和外设请求进行一一对口,各个通道对应的外设如下:
) Y( b9 y1 n8 e& _7 t& a5 X( L' G, L9 \5 C& _, }" g

. q# Q# \2 G* t; n. p2 b' ]2.通道% y* ~$ a1 C" P7 {/ U
要注意的是DMA共有12个独立可编程的通道,DMA1有7个、DMA2有5个,每个通道对应着不同的外设的请求,但同一时间只能接收一个。
2 F( n) W' V" O% p1 _% k6 L" C) c, P3.仲裁器7 R3 y8 F1 |* k+ J0 i; {
当多个通道同时请求时,就要处理先后响应的问题,就像中断里的优先级分组一样。仲裁器管理DMA通道请求时主要依据俩点:9 E) t. ?+ |6 K! c- h
第一判断:在DMA_CCRx 寄存器中设置有 4 个等级:非常高、高、中和低,先根据此优先级判断响应的先后,如果优先级都一样,进行第二判断。! X; W& V$ G4 [) i* ?0 a1 H, I
第二判断:比较通道的编号,编号越低优先权越高。
* Q+ n# ~( j' _, |5 q3 v/ ^6 P+ H" \; l/ k2 Q, z3 A: Y

3 a, @6 o; ~5 [: B( bDMA传输数据分析2 n' x3 W. y% c
使用DMA,核心技术就是配置数据的传输,主要分为三点:
( [- O, s% J$ L7 n0 F0 G3 Z1.传输的方向
8 N1 L8 J$ u# Y2.传输的数量
) X) C# D3 ]$ A2 d3.传输的模式* k$ Z+ _* u4 D8 O* E! l! g& S
1 _+ ?7 ~* F8 a8 Z4 E
3 h7 F" t1 e2 w, w: ]4 u! u
1.传输的方向
- e. r4 y! m& W# v; J2 TDMA有三种数据传输方向:6 m1 D" F, M6 y" {. U4 B
一:存储器——>外设1 R- C, M# @% V9 ]
当我们使用从存储器到外设传输时,以串口向电脑端发送数据为例。 DMA 外设寄存器的地址对应的就是串口数据寄存器的地址, DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储通过串口发送到电脑的数据)的地址。方向我们设置外设为目* M. u7 w# s/ e- d. C7 b
标地址。
7 s, N7 {% R2 D* h. o7 H0 y" c, B二:外设——>存储器: \: r; _6 ]4 E( q/ _( t
当我们使用从外设到存储器传输时,以 ADC 采集为例。 DMA 外设寄存器的地址对应的就是 ADC 数据寄存器的地址, DMA 存储器的地址就是我们自定义的变量(用来接收存储 AD 采集的数据)的地址。方向我们设置外设为源地址。% U: n7 v  S0 l* _! x. W
三:存储器——>存储器
) G- a4 L, p! R: y8 h当我们使用从存储器到存储器传输时,以内部 FLASH 向内部 SRAM 复制数据为例。DMA 外设寄存器的地址对应的就是内部 FLASH(我们这里把内部 FALSH 当作一个外设来看)的地址, DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储来自内部 FLASH 的数据)的地址。方向我们设置外设(即内部 FLASH)为源地址。跟上面两个不一样的是,这里需要把 DMA_CCR 位 14: MEM2MEM:存储器到存储器模式配
( N4 ^0 Y5 F. `置为 1,启动 M2M 模式。
7 }( d: g$ \6 f( W
0 G$ E! l. q# J( K* m0 [

, R* i( i5 X5 @. z1 ^2.传输的数量
9 X5 ?; H+ z3 D# I5 w以串口向电脑发送数据为例,我们可以一次性给电脑发送很多数据,具体多少由DMA_CNDTR 配置,这是一个 32 位的寄存器,一次最多只能传输 65535 个数据。而且源和目标的数据宽度必须一致,数据宽度可以设置为8/16/32位。
0 i" R  m/ o2 Q% g除此之外,还要设置源和目标俩边数据指针的增量模式 ,即传输完一个数据后数据指针的移动模式,是加一?还是不变?以串口向电脑发送数据为例,要发送的数据很多,每发送完一个,那么存储器的地址指针就应该加 1,而串口数据寄存器只有一个,那么外设的地址指针就固定不变。. O* d- {, m1 R+ ^- C' k: s$ V

8 L3 N" D$ A5 h/ {/ H8 U' M
; Y1 S' @1 }* p7 @9 f
3.传输的模式
2 H2 G/ I7 Z* V- n+ i' @数据传输的情况可以通过查询标志位或者通过中断来鉴别,每个DMA通道在DMA传输过半、传输完成、传输错误时都会有相应的标志位,如果使能相关的中断后还会产生中断。一次数据传输完成后还分俩种模式:是一次传输还是循环传输。
" l& l  U* F: t( y一次传输: 传输一次后就停止,要想再传的话,必须关闭DMA使能后重新配置后方能继续传输。
9 e+ T( |6 F) k7 J" x, x循环传输: 一次传输完成后又恢复第一次传输时的配置循环传输,不断重复。! t$ o) M! g  x$ }. i! e" r. l; r

( ~5 M$ V& e4 C2 x$ {6 e$ D

- V, Y4 A6 t, I代码部分
% l- u5 _) y/ z- w3 L6 `5 P" }* _, s编程要点:  @  ^5 V4 F* B$ Z" U0 U  `
1.配置USART通信功能0 m" @6 ?  S: G/ z, p. E* V
2.设置DMA工作参数
8 \8 @( |! {, z" A! G0 H3.使能DMA
9 K8 v0 ^& o$ g
3 X# i* o. D. ?: C& ]

0 u9 L; X5 ^# l2 MDMA初始化结构体
/ G5 Q9 @( m/ n9 K+ ?, h2 J3 L固件库编程中对一个外设的操作主要通过配置外设的初始化结构体来完成,虽然最终操作的是寄存器,但相比较寄存器,还是采用库函数比较方便。
8 t2 i# z  K3 Y4 [( e+ d7 ~; i# G& e% S  k, q7 I
$ r7 C0 `! O6 T$ |5 |! w2 i* r
typedef struct
! \- R, `" M4 k8 n3 P/ z* Z {: z& S" }0 z) D" i% \- n
uint32_t DMA_PeripheralBaseAddr; // 外设地址
2 Y; P' u% @; _7 [0 v% B% X uint32_t DMA_MemoryBaseAddr; // 存储器地址3 b6 F+ L0 s0 j& o
uint32_t DMA_DIR; // 传输方向0 b7 G9 n" Y( Q% w- \: \9 h" V$ N' E
uint32_t DMA_BufferSize; // 传输数目
, T, x9 x0 n# w% G& z5 K5 W" R uint32_t DMA_PeripheralInc; // 外设地址增量模式
, E7 e4 Y1 K0 z, R4 n5 y3 G# r uint32_t DMA_MemoryInc; // 存储器地址增量模式
. i: ~5 U# g! ?4 S. H- |( ~- ~ uint32_t DMA_PeripheralDataSize; // 外设数据宽度
3 _& ?7 V+ W3 a uint32_t DMA_MemoryDataSize; // 存储器数据宽度
/ _" L$ O; Z) c! \, g( h2 Q) D uint32_t DMA_Mode; // 模式选择" d$ K+ T( j, p7 u( ]8 C! r* k6 i
uint32_t DMA_Priority; // 通道优先级  h7 ^. C. d! p; R) H
uint32_t DMA_M2M; // 存储器到存储器模式6 @1 }- h1 `. x; G( X  a. v4 K
} DMA_InitTypeDef;
# ^7 x0 B" S! F& T7 R7 B) v1
4 P2 m+ {' h; r% ?8 z4 O; u2
7 Y! B5 z$ H* s3
) C9 O) Y( P+ G: S( g4
4 b' b6 [' t3 C7 L1 \0 ^6 B6 l5- e( T& @" g% S+ F9 y  V
6; \  p9 c5 g9 z9 C. M6 D
79 k( H8 T- }* z! i
8
& e) p/ u  p1 F' S1 z; U9
8 d7 S6 B  [+ w6 b, K10
5 ?- A: r# Y8 n: n1 V11
' n# y) P' c# X. m129 F* k0 X- u& J, V  }
134 V4 t) t; z! {, p5 ^
14
& e8 \4 j5 ~9 c3 y下面利用《零死角玩转 STM32F103》的话介绍一下结构体成员:
3 ]* W6 o9 A7 ]2 r1.DMA_PeripheralBaseAddr:外设地址,设定 DMA_CPAR 寄存器的值;一般设置为外设的数据寄存器地址,如果是存储器到存储器模式则设置为其中一个存储器地址。. p5 I+ L: M2 ?0 p
3 r& b% t  y) I6 b  C0 @. ]
5 U; Y1 u, \4 J2 @7 J* C
== 2.DMA_Memory0BaseAddr == : 存储器地址,设定 DMA_CMAR 寄存器值;一般设置为我们自定义存储区的首地址。零死角玩转 STM32F103—MINI
% W$ k* l4 f: G! y& l1 c2 K
, z& p. m3 [6 }! o* r
; d7 `$ h2 a! g
3.DMA_DIR:传输方向选择,可选外设到存储器、存储器到外设。它设定' u1 _, D$ p# q9 w3 Q
DMA_CCR 寄存器的 DIR[1:0]位的值。这里并没有存储器到存储器的方向选择,- l0 g% u7 f5 X' p2 Z$ [6 ]* r
当使用存储器到存储器时,只需要把其中一个存储器当作外设使用即可。& k7 V& s9 W3 ]  F. ^1 y

% n, l2 j* Z: I2 ?) D

9 S1 C( }1 J: N& C+ U4. DMA_BufferSize:设定待传输数据数目,初始化设定 DMA_CNDTR 寄存器的值。
; E$ \0 w) Q7 i+ k% A- A: t& B
+ F" {" P0 V% W5 R; g
5. DMA_PeripheralInc:如果配置为 DMA_PeripheralInc_Enable,使能外设地址自动递增功能,它设定 DMA_CCR 寄存器的 PINC 位的值;一般外设都是只有一个数据寄存器,所以一般不会使能该位。
" @$ O4 B/ j3 v0 K
3 H3 W  W7 L& g. O

/ H8 N4 j9 p$ d; k. j6. DMA_MemoryInc:如果配置为 DMA_MemoryInc_Enable,使能存储器地址自动递增功能,它设定 DMA_CCR 寄存器的 MINC 位的值;我们自定义的存储区一般都是存放多个数据的,所以要使能存储器地址自动递增功能。
! I( H7 m# E$ s/ l; q. S0 y0 i- ~$ x& u
3 m; z! m4 p5 a( [4 v
7.DMA_PeripheralDataSize:外设数据宽度,可选字节(8 位)、半字(16 位)和字(32位),它设定 DMA_CCR 寄存器的 PSIZE[1:0]位的值。
0 {: s  l3 w$ H) Y) R# J& t
4 U, q$ l7 Q: ^

! ~" V7 |( V! X, T7 E* Z' O: F8. DMA_MemoryDataSize:存储器数据宽度,可选字节(8 位)、半字(16 位)和字(32位),它设定 DMA_CCR 寄存器的 MSIZE[1:0]位的值。当外设和存储器之间传数据时,两边的数据宽度应该设置为一致大小。6 b/ l: V; {/ \
. m4 s( o3 n8 m

$ ~0 N- I- {' N$ Q; s& j9. DMA_Mode: DMA 传输模式选择,可选一次传输或者循环传输,它设定1 N/ Y0 Q+ _. c9 Z' a# e
DMA_CCR 寄存器的 CIRC 位的值。例程我们的 ADC 采集是持续循环进行的,所
6 J0 \! Z, k6 o; |5 U3 _以使用循环传输模式。
- R7 W7 Z3 a  f: P( M+ r
9 h% j3 B( C: X6 R4 \' }- q: @

; Q$ _7 h8 [, ~  M10.DMA_Priority:软件设置通道的优先级,有 4 个可选优先级分别为非常高、高、中和低,它设定 DMA_CCR 寄存器的 PL[1:0]位的值。 DMA 通道优先级只有在多个 DMA 通道同时使用时才有意义,如果是单个通道,优先级可以随便设置。+ |6 L" R& p+ X/ ?) W' [" {
: w  y- ?/ c+ O% J  t4 h0 x
& B1 `5 O! i+ K8 r
==11. DMA_M2M == :存 储器 到存 储器 模式 ,使 用存储 器到 存储 器时 用到, 设定DMA_CCR 的位 14 MEN2MEN 即可启动存储器到存储器模式。
5 z. R5 U  d4 b3 e" c9 A+ W3 k) U  ]" {5 ^' Z4 z. s

4 x; o, [9 J$ QUSART配置函数
# n5 j& ]2 ?  f: v8 L9 e3 ~6 T此代码结合串口通信章节内容理解更佳,此处不多于讲解。USART配置详解
- n7 R( u3 f  D0 H# `( Q% O( D* |# }
1 g% |& B- L/ e# P1 L1 |; ?6 t' y
void DEBUG_UART_Config(void)
' X! H  O, j' n3 \3 ]1 o. G{9 L' o0 ~* L" d- n
        GPIO_InitTypeDef GPIO_InitStructure;
  M3 A' v' C0 S        USART_InitTypeDef USART_InitStructure;( ^. y. \# b" e% A( Q
        ! D! ~2 L4 }* A$ W6 y
        /* 第一步:初始化GPIO */$ ?  n3 M4 W2 ~. M
                // 打开串口GPIO的时钟8 R1 z8 v! w( i7 K' d
        DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
) I8 h8 v0 i7 N$ O                // 将USART Tx的GPIO配置为推挽复用模式2 I: y0 E! {1 x) |$ l
        GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
! Y$ V; t- u' u+ B  S4 S        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;1 W- G2 Y: a  L5 X9 H! }/ I: ]3 }" Y
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
9 V1 A! D% v! L. t- \        GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);9 c9 H1 o/ P0 {/ C4 T  q
9 O3 W8 Z: {1 @1 w1 h
) K, g' ?7 g+ W# n9 l; [
  // 将USART Rx的GPIO配置为浮空输入模式
3 L  m4 S- O1 N. V9 A: }" [/ M. Y        GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;8 T% X8 w. O" p* j  I; e+ r
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
& c+ G. k% N$ x& `, x        GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);        * Y0 m* h  F# d
       
9 Y/ o+ I) X. T        /* 第二步:配置串口的初始化结构体 */
( Q$ [# q6 D' {% I5 M                // 打开串口外设的时钟
1 j' ?  t, ^0 r3 t        DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);0 n" g: ?2 D/ J( F+ u( @8 W
        // 配置串口的工作参数0 n9 D, L" H: t( q- x, w% j
        // 配置波特率
% e  g4 l7 i" I  j7 `0 \7 w7 n        USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
4 W6 Z! z0 f2 Y  g8 Z        // 配置 针数据字长
' w* b) X2 S/ l3 p3 y1 G. v6 i1 ?        USART_InitStructure.USART_WordLength = USART_WordLength_8b;
/ V* M3 T0 q6 A# V0 a        // 配置停止位, Y  x' c% z  A: ]6 L; p
        USART_InitStructure.USART_StopBits = USART_StopBits_1;5 O2 c' R& x: p- j, E. S
        // 配置校验位
# i* L0 T# B. t- M8 C        USART_InitStructure.USART_Parity = USART_Parity_No ;
2 W6 v2 X4 i- M' w! |        // 配置硬件流控制
) t1 T6 Q/ u, X" j. u        USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
5 P9 q7 {- M2 Z+ O; `! r        // 配置工作模式,收发一起4 K, i' s% ]3 k( o* t) t; m% O
        USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
9 \3 d3 \& F! k        // 完成串口的初始化配置$ c  E* ?: e3 B6 j
        USART_Init(DEBUG_USARTx, &USART_InitStructure);4 M: i8 a$ D- Q2 y) \7 H2 X1 {' c# L

) i; _, g2 c( n) {, I* q

& x! g$ q# l% _& |, ~/ n* n4 m& Y( l        /* 第三步:使能串口 */       
6 a7 [7 P4 o# a, l                // 使能串口
7 K5 n" P  c# ~& D: c        USART_Cmd(DEBUG_USARTx, ENABLE);       
% s* i8 Z: G8 n: }6 G- }8 R}! ~9 @" b# ?1 E6 {+ L

* |6 e( x( j7 K1 K
. x1 L7 n% d; a
$ ~8 z* G1 @! q4 Y  ]8 g

+ B5 R) c0 u/ g/ d0 z' H1% O0 X) M  q& p/ o
2: }: H; \* `3 G+ ^' B4 V3 x" O
3+ R4 a: n0 V, k/ s1 h1 d
4" U+ z" k3 A1 F
5$ L2 z, \9 _6 [  @$ t
6
1 J, g& M+ Z7 N6 J' t+ w* W7# h) Y& C4 ?+ @/ E6 M9 }
8& a+ M% Q- x) Q6 ~7 a% e, b0 A
9. T2 ^& n, y* y3 A/ m) n: D: V
10
6 G# B/ R& E4 ]. A8 K11
, x8 x- x) M4 L0 K123 y& }# G# ?+ l$ @
135 l/ Y" K) t6 ]- k+ O
14& b- P2 Z" k4 ?# l- Y5 `
15
6 m/ K7 p* x( e16
* o4 R2 K' Q2 `4 Q17
4 I$ ~! t/ b2 C; f6 C& l2 f. ]& c18
# Z2 L& j- a8 ?& }1 k8 [8 U( Z8 U19+ m5 H# {+ U  j2 M, L4 V8 X9 ?( y
20
1 X+ n2 _2 i& R5 [- w. c215 D+ t/ l8 O) u, J- _
22
8 Y* N  N; t# s* P. f234 i9 F' G) _7 E1 Z3 N
24. F0 i+ ]3 v1 f3 O- {# Q" U+ M
25; X  G& J7 F  I" d4 s
26
5 a! V* m0 H) W27
: D# J& k7 Y, u. `: X28) Z' [) b5 m: g8 r, ?( W
293 y0 H+ p, u* @; a5 \/ k
30
0 _6 V- R+ C* V" j317 {7 `* x( p' a1 \1 q  K1 q+ c9 _/ }
322 P6 v- b& B/ h' ^5 w+ B/ \! E
33
5 |1 Y4 C: @" y34
7 }" N9 J8 k- n% Q0 C' x35! m6 k# V9 P. Z/ s
36( R' |+ `- `* c9 e- }$ k  w/ |# U
37
- @) }5 V8 w4 z8 w# g38
% N4 V' t/ M/ [9 ~& w3 |398 L  z4 i* x+ Z4 \
40  h3 h4 K7 K) k8 K3 I; _
41
8 B% T# I, m+ S) T42
' V/ H# B3 o/ {43
( @0 L+ l. T# h, |6 g  J0 G) QDMA配置函数
' w; m* `& m* c1 H* O% B; H: ~3 L此函数主要是打开DMA外设时钟、配置DMA初始化结构体、使能DMA通道
; s" u+ l% T! ~6 [- o" {$ h————————————————& x3 _/ e4 e+ _' t$ x$ b7 e
版权声明:Aspirant-GQ
" x8 Q6 m2 ~; b' ^4 s7 I) o! l/ v如有侵权请联系删除' W$ `4 \+ e! o6 H( c1 k) m

, o$ o. t6 C0 N" \$ v$ W! \: k6 l
& r. ]: E7 U. K( Q2 H% H7 E
收藏 评论0 发布时间:2023-3-18 15:57

举报

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