1、综述 v" ~- N* i' J% u y: `8 [. y
5 ~% a: y9 w$ d; \2 |
DMA(Direct memory access)直接内存访问,被用于内存和内存之间或内存和外设之间的高速数据传输。数据传输可以在没有CPU的干预下快速移动,这样可以保持CPU资源处理其他事情。 5 e# ^4 |$ O# ^
DMA 控制器基于复杂的总线矩阵架构,将功能强大的双 AHB 主总线架构与独立的 FIFO 结合在一起,优化了系统带宽,下图①处,可以看出双 AHB 主总线架构与独立的 FIFO的结构。8 U$ l$ D* U+ p+ Q7 @. g
! k. d7 s4 J& q5 y- X1 H) m) o5 Y0 q3 q1 h) s/ I0 H& J
注意看英文备注: $ r- w4 e ^- V* z6 A2 u k
DMA1控制器AHB外设端口没有像DMA2一样连接到总线矩阵,所以只有DMA2数据流可以执行存储器到存储器的传输。
k2 n* e, N m; O
我们对上图的②处,(DMA1和DMA2结构一样,我们就选择DMA1)详细看
! a/ S* @) R, _
; R" F4 u$ U2 v$ b①每个数据流总共可以有多达 8 个通道(或称请求)。
) q% ~1 c# M$ Q7 ?& q
②DMA1共有8个数据数据流(两个DMA共有16个数据流)。 # B% ]% T; x. U" X% \. `9 [
③每个DMA都有数据流仲裁器,用于处理 DMA 请求间的优先级。
8 K/ N3 u$ Q6 |0 {1 M0 S$ ?' a
④DMA的数据流又有独立的FIFO。
% w6 q5 C# G2 ]) z3 v: D' d
⑤DMA采用双 AHB 主总线架构。
" Q d/ k4 `# _! u. D2 _
备注: 9 x3 J3 Q+ d* U' r" K/ N
①处是选择器,配置完成只能选择一个通道,而③处是仲裁器,也就是说,配置完成,可能8个数据流全部存在,由仲裁器判断优先级。
% f% {, ~5 _. x6 k j! d9 J" Y0 I: S w" G3 s
2、DMA事务
$ e! G3 s* ?( NDMA从传输事务包含一系列的给定数目的数据传输序列。传输的数目可以通过软件编程,8位,16位或32位。 1 e" R1 W4 k5 z/ R' {3 N( C& _! M
每一次DMA传输包含3个操作
' }$ a) p) R s! @! H! S
通过 DMA_SxPAR 或 DMA_SxM0AR 寄存器寻址,从外设数据寄存器或存储器单元中加载数据。 通过 DMA_SxPAR 或 DMA_SxM0AR 寄存器寻址,将加载的数据存储到外设数据寄存器或存储器单元。 ·DMA_SxNDTR 计数器在数据存储结束后递减,该计数器中包含仍需执行的事务数。
~ w! N, v7 C6 s1 H
" |" Q7 J/ a0 v! L+ a, u# R% [4 s! `( e6 A7 Y
在产生事件后,外设会向 DMA 控制器发送请求信号。DMA 控制器根据通道优先级处理该请求。只要 DMA 控制器访问外设, DMA 控制器就会向外设发送确认信号。外设获得 DMA 控制器的确认信号后,便会立即释放其请求。一旦外设使请求失效, DMA 控制器就会释放确认信号。如果有更多请求,外设可以启动下一个事务。
I, S9 q3 E6 X9 w, c6 O$ J8 ?+ |1 H2 ^5 W; O
3、通道选择1 \9 S) ?' t9 k7 K0 S
5 {7 X. d7 P6 ?9 N( d每个数据流可以有8个通道。 4 b5 y8 _0 F5 B2 {
通过上图①可以看出,通道选择仲裁器可以通过DMA_SxCR寄存器的CHSEL[2:0]配置 ( E$ j/ j+ h, T( b5 f% F7 O
DMA的请求可以来自TIM,ADC,SPI等外设。 - S- U) M5 v. r* B0 G% _
DMA1的请求通道 $ v! p8 S; f3 q" }+ X0 m+ t
DMA2的请求通道 6 z- W1 M$ u% }6 C. f
1 I! P1 U. V3 o' u( O. U% o
& x0 n- [# ]! _ u* H* P
4、仲裁器
* Y( T1 L1 O( P* x" f
9 y. _) B( S. {仲裁器为两个 AHB 主端口(存储器和外设端口)提供基于请求优先级的 8 个 DMA 数据流请求管理,并启动外设/存储器访问序列。 ; I, s' [9 a% W5 o! G/ U
优先级管理分为两个阶段 - E7 C+ S6 z! x2 ^5 P, f6 ]
0 ~3 ~6 j4 h. S7 v6 V
0 r5 w. i% v6 b4 I8 f5、DMA数据流' ?4 A* B$ w' `1 Z: ]
# d% _! {) k2 }+ e
8个DMA控制器数据流都能够提供源和目标之间的单向传输链路。
) r4 c2 t$ \) {. M ` D: g
每个数据流配置后都可以执行
8 d: L: n3 @1 @' d2 M
要传输的数据量(多达 65535)可以编程,并与连接到外设 AHB 端口的外设(请求 DMA 传输)的源宽度相关。每个事务完成后,包含要传输的数据项总量的寄存器都会递减。 9 Q0 F3 e' Y# ]9 x( ~+ k" ?
) {3 W g+ d% F% F8 Y
6、源、目标和传输模式4 C0 Z1 N: |2 D J
源地址和目标地址可以在整个4G地址空间,在0x00000000和0xFFFFFFFF之间。
2 P% [/ _2 H+ i7 W
在DNA_SxCR寄存求的DIR[1:0]配置DMA的传输方式。
' ]1 k& N8 M5 N5 p6 {, _! `% l
源地址和目标地址的关系 当数据宽度是半字或字时,外设地址或存储器地址必须是半字或字对齐
7 H& h: `! P! U. ]$ x s/ r% {
6.1、外设到存储器模式 当配置成外设到存储器的DMA传输模式时,两种模式
# L- K5 Q* N9 g( m" a+ A' _0 w 4 ~' a5 i% T( L1 |
传输开始条件,使能数据流(DMA_SxCR 寄存器中的位 EN 置 1),然后外设发出请求,再然后该请求赢得了数据流仲裁,才会开始传输。 4 k4 C2 \. @9 p
传输停止条件,下列满足一条即可
' L a* h x6 h7 ~$ N, P* `
6.2 存储器到外设模式. + b: j/ n" _0 a& [" x! |3 K
FIFO模式 这种模式,只要使能数据流(DMA_SxCR 寄存器中的位 EN 置 1),存储器数据就会传输到FIFO中,发生外设请求,FIFO数据会移出并存储到目标地址。当FIFO小于阈值,存储器的数据会重载FIFO。 8 k0 z+ c6 Y" k8 [
直连模式 使能数据流时,DMA传输存储器的第一个数据到内部FIFO,发生外设请求时,DMA把预装在值发送的目标地址,然后进行下一个数据的传输。预装载的数据大小为 DMA_SxCR 寄存器中 PSIZE 位字段的值。 $ s* A/ L, X" p" @
传输停止条件,下列满足一条即可 4 Y! ^; _7 V' i- p% W" p
存储器到外设模式和外设到存储器模式一样,同样需要对应数据流赢得仲裁,才会启动传输 8 i6 h. ^ i' ^/ [9 b7 A
, w) v6 Y- V1 c% r1 q9 V6.3 存储器到存储器模式 这种模式较为简单,没有外设请求
4 e+ u9 ? O/ @
启动传输 ! ~# R6 _& f+ x; F) C, L
DMA_SxCR 寄存器中的使能位 (EN) 置 1 来使能数据流时,数据就会从源地址传输到FIFO,到达FIFO阈值时,FIFO数据移出到目标地址。
1 d7 i! T8 S4 D1 k
停止传输,下列满足一条即可 当然,同样该数据流需要赢得仲裁。
) u# A# k V2 \' V) e/ { # J' k% z3 q; r% g1 x! C
& q& O8 n8 d' M7 Z. N. D
7、指针递增, ~% `4 L+ J, P
1 z, ^0 A4 ~% e9 @6 W
外设和存储器指针在每次传输后自动向后递增或保持常量,根据DMA_SxCR寄存器的PINC和MINC位。
- I+ P/ A( {: t. j' O4 V
禁止递增模式时非常有用的,当外设源和目标数据是通过单个寄存器访问的。
2 {4 r9 \! @9 b7 u) F
如果使能了递增模式,则根据在 DMA_SxCR 寄存器 PSIZE 或 MSIZE 位中编程的数据宽度,下一次传输的地址将是前一次传输的地址递增 1(对于字节)、 2(对于半字)或 4(对于字)。 $ i. C: _4 P' @% M1 C t- S
为了优化封装操作,可以不管 AHB 外设端口上传输的数据的大小,将外设地址的增量偏移大小固定下来。DMA_SxCR 寄存器中的 PINCOS 位用于将增量偏移大小与外设 AHB 端口或32 位地址(此时地址递增 4)上的数据大小对齐。PINCOS 位仅对 AHB 外设端口有影响。 % a% s9 D: d- N6 p4 M
如果将 PINCOS 位置 1,则不论 PSIZE 值是多少,下一次传输的地址总是前一次传输的地址递增 4(自动与 32 位地址对齐) 。但是, AHB 存储器端口不受此操作影响。 + W6 E" b. o2 I4 q/ g) u) S
如果 AHB 外设端口或 AHB 存储器端口分别请求突发事务,为了满足 AMBA 协议(在固定地址模式下不允许突发事务),则需要将 PINC 或 MINC 位置 1。
. W% p! y/ X1 }6 F e2 O( H0 d3 k1 z. z# Z( {
8、循环模式0 t8 m$ u' ?' b/ L* }$ K& T
& |" o8 F+ s6 L& d' _! ]循环模式可用于处理循环缓冲区和连续数据流(例如 ADC 扫描模式)。可以使用 DMA_SxCR 寄存器中的 CIRC 位使能此特性。 当激活循环模式时,要传输的数据项的数目在数据流配置阶段自动用设置的初始值进行加载,并继续响应 DMA 请求。
; A5 {- B- [# l0 v* K
也就是说,比如我们要从内存中采集 64 个字节发送到串口,如果设置为重复采集,那么它会在 64 个字节采集完成之后继续从内存的第一个地址采集,如此循环。这里我们设置为一次连续采集完成之后不循环。所以设置值为 DMA_Mode_Normal。在我们下面的实验中,如果设置此参数为循环采集,那么你会看到串口不停的打印数据,不会中断。
) ]4 |6 w( h' O' V; T5 \, T% Z; O7 o
* b+ h; Z) E$ `$ r
9、双缓冲模式( z. r* X# ]: u, o8 q8 E
5 C8 e8 {* n9 {5 N# d" ^3 Q& g此模式可用于所有 DMA1 和 DMA2 数据流。
, X/ R2 l3 ^' F
通过将 DMA_SxCR 寄存器中的 DBM 位置 1,即可使能双缓冲区模式。 4 x/ z* Z: K% }0 @- s; E
除了有两个存储器指针之外,双缓冲区数据流的工作方式与常规(单缓冲区)数据流的一样。使能双缓冲区模式时,将自动使能循环模式( DMA_SxCR 中的 CIRC 位的状态是“无关”),并在每次事务结束时交换存储器指针。 2 y& L! Y: h3 ]# f
在此模式下,每次事务结束时, DMA 控制器都从一个存储器目标交换为另一个存储器目标。这样,软件在处理一个存储器区域的同时, DMA 传输还可以填充/使用第二个存储器区域。 + y9 l# e% i/ J; A' W3 c% W
基于DMA双缓冲模式的的特点,应用中必须开辟两个存储区以及存放两个存储区首地址的存储寄存器,DMA_SxM0AR和DMA_SxM1AR。
/ P8 ~) z. v6 e! `; y
1:DMA_SxM0AR:指向存储区0(DMA_Memory_0),单缓冲模式下默认使用该寄存器做存储区指针。 6 Y, w# t- ]/ I& O% m6 h
2:DMA_SxM1AR:指向存储区1(DMA_Memory_1),仅在DMA双缓冲模式下才能使用。 / i8 i! V3 X8 x% J6 C' b: O: I
3:DMA正在访问的当前存储区由DMA_SxCR表示 CT:当前目标 CT = 0:DMA正在访问存储区0,CPU可以访问存储区1。 CT = 1:DMA正在访问存储区1,CPU可以访问存储区0。 + h: u* `4 r: i j
优点:
, z+ q1 y& @+ O4 z8 x5 }3 g' ^
使用DMA双缓冲传输,既可以减少CPU的负荷,又能最大程度地实现DMA数据传输和CPU数据处理互不打扰又互不耽搁,DMA双缓冲模式的循环特性,使用它对存储区的空间容量要求也会大大降低。尤其在大批量数据传送时,你只需开辟两个合适大小的存储区,能满足DMA在切换存储区时的当前新存储区空出来就好,并不一定要开辟多大多深的存储空间,单纯一味地加大双缓冲区的深度并不明显改善数据传输状况。 c7 w- R7 Z( U. z- H. Y& P
/ p6 v; L( m% ?( w: c% r
# D+ e/ A( r K5 l# H- C10、代码
0 Y) m+ E) A- n, j1 r* D$ |) u8 O i
! O% B& k1 { I2 ^. j+ a, K! R2 x: K
4 E' O. P, D3 F- \( \% `* y+ M+ [
标准外设库配置代码:- 3 V) P( G& Z7 T5 G
- /* Configure DMA Stream */
- w; [; m- S- Z, Z6 f1 W - DMA_InitStructure.DMA_Channel = DMA_Channel_0;
$ Z" H6 a) m& e4 S1 e; Q6 @ - DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SRC_Const_Buffer;0 i$ j0 k1 Y* t0 M/ p% f
- DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)DST_Buffer;; n, P' }& k5 P0 g7 b
- DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToMemory;# u) X0 o$ f( ~' X
- DMA_InitStructure.DMA_BufferSize = (uint32_t)32;
# h! N5 {( K( ?' o( y2 Q8 {% p) l - DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;' z" d8 z! }2 c* z& {+ q- `
- DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;; q4 Y5 n# a( i r7 Y
- DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
/ q( H6 M9 p; g r/ _ - DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;1 K. a8 P9 j- ?' \$ A
- DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
$ f- b; i, `5 I: A5 R+ }1 Z( | - DMA_InitStructure.DMA_Priority = DMA_Priority_High;0 o% O6 q9 u2 @' i: C4 _
- DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;.
: s2 y" f7 ~- E% X2 V; S - DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;4 B- N0 f$ ^9 W1 N: A, A9 z* Q
- DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
2 H! \7 @! t0 M, u - DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;: t$ |$ j. \' n! [; A# I% l
- DMA_Init(DMA2_Stream0, &DMA_InitStructure);
复制代码 4 `+ b7 b8 r0 ^) k7 S: |1 P
+ e3 d7 V' n- l
DMA_Channel : 设置 DMA 数据流对应的通道,供每个数据流选择的通道请求多达 8 个,取值有 - 1 d$ M) J4 ~, w/ s* i _
- #define DMA_Channel_0 ((uint32_t)0x00000000)6 x9 t7 ~! ^# r0 A" e* S) r* ]
- #define DMA_Channel_1 ((uint32_t)0x02000000)
& V( f1 `% e! ~* r0 ~5 S6 T - #define DMA_Channel_2 ((uint32_t)0x04000000)
$ G8 a# @* i: U% g8 t1 a7 l - #define DMA_Channel_3 ((uint32_t)0x06000000)
$ F3 C5 W( `, E- }. j - #define DMA_Channel_4 ((uint32_t)0x08000000)1 N& |% D% B! C( R
- #define DMA_Channel_5 ((uint32_t)0x0A000000)
! K) T0 X0 B* D' V# ^6 Y3 X* f5 Y - #define DMA_Channel_6 ((uint32_t)0x0C000000)( J! g9 P+ E, h
- #define DMA_Channel_7 ((uint32_t)0x0E000000)
% N+ |' g% v+ i0 L9 H: Y - DMA_PeripheralBaseAddr :
复制代码 5 `3 K8 b* F5 b: m. L
DMA_PeripheralBaseAddr : DMA 传输的外设基地址,假设进行uart1串口DMA 传输,我们可以按照寄存器的地址偏移直接设置地址:0x40011004,也可以直接使用ST提供库的表示方法:&USART1->DR。
2 {$ o E; Z& w
DMA_Memory0BaseAddr : DMA 传输的内存基地址。 DMA_DIR: 设置数据传输方向,有存储器到存储器,存储器到外设,外设到存储器三种选择,取值有:
3 ^# @+ I; y9 M, p5 a- #define DMA_DIR_PeripheralToMemory ((uint32_t)0x00000000)5 e! S1 a- m( j+ i& M# a
- #define DMA_DIR_MemoryToPeripheral ((uint32_t)0x00000040) + C1 S% `) M% A0 b$ i) r9 u7 q; |- i
- #define DMA_DIR_MemoryToMemory ((uint32_t)0x00000080)
复制代码 . U# ]% B, `/ C$ ]; C: ]
DMA_BufferSize: 设置一次传输数据量的大小 , c) D1 w6 J+ E8 N
DMA_PeripheralInc: 设置传输数据的时候外设地址是不变还是递增,如果设置为递增,那么下一次传输的时候地址加 1,取值有: - 值有:1 d* M% ]3 N8 V% @9 D0 e
/ L9 f' c6 f& Q7 U. _- L4 j/ R" a( X- #define DMA_PeripheralInc_Enable ((uint32_t)0x00000200): e: z# L) ^1 {7 y( T4 s
- #define DMA_PeripheralInc_Disable ((uint32_t)0x00000000)
复制代码 8 v! H$ j6 A) d! S8 M' U$ i
+ N1 o/ H9 L" P& J, Z9 W. s& u+ R, YDMA_MemoryInc: 设置传输数据时 候内存地址 是否递增。这个参数和DMA_PeripheralInc 意思接近,只不过针对的是内存(存储器),取值有:
) M3 ~( f/ ?2 Z* y$ B5 e& }* Z! ^
- / t9 \, P# \: J% V. h0 P/ Y
- #define DMA_MemoryInc_Enable ((uint32_t)0x00000400) # t: S& Z1 b5 Y1 N+ }) @ D. z
- #define DMA_MemoryInc_Disable ((uint32_t)0x00000000)
复制代码
1 Q+ ]. N) v: @8 m( ZDMA_PeripheralDataSize: 设置外设的数据长度是为字节传输(8bits),半字 传 输 (16bits) 还 是 字 传 输 (32bits),取值有:
! z) J8 B: k/ s/ |
- #define DMA_PeripheralDataSize_Byte ((uint32_t)0x00000000)
0 `0 o* h" F$ j" Y( y7 g$ a - #define DMA_PeripheralDataSize_HalfWord ((uint32_t)0x00000800)! h$ J: R( p& Z/ |
- #define DMA_PeripheralDataSize_Word ((uint32_t)0x00001000)
复制代码 ) ^0 w' B$ m& l h
DMA_MemoryDataSize: 用来设置内存的数据长度。 W; j: E! P" N- M6 @$ U8 C1 Z5 r
DMA_Mode: 设置 DMA 模式是否循环采集,取值有: H& N6 p+ ]. n4 Y$ L$ H
- " T3 |% z. f4 j$ x" n, m9 \0 w
- #define DMA_Mode_Normal ((uint32_t)0x00000000) * | \& P6 P. n4 u9 H D
- #define DMA_Mode_Circular ((uint32_t)0x00000100)
复制代码 ( l2 v+ r2 c( ?, j! c m8 S
4 K7 f' `" A. M5 N6 G3 l: r; n- CDMA_Priority: 设置 DMA 通道的优先级,有低,中,高,超高三种模式。就是仲裁器仲裁的时候用的,当多个数据流同时开启,DMA仲裁器优先处理优先级高的数据流,取值有: 0 r# _/ s, }6 M
- #define DMA_Priority_Low ((uint32_t)0x00000000) 8 h- q, `: ~ U# ?: _; f# C
- #define DMA_Priority_Medium ((uint32_t)0x00010000)0 s V* ?9 e1 Z4 J; u: R
- #define DMA_Priority_High ((uint32_t)0x00020000)
9 D5 k' G3 i" n e" |9 @ P - #define DMA_Priority_VeryHigh ((uint32_t)0x00030000)
复制代码
O$ r4 t9 Y4 }2 |* `DMA_FIFOMode: 设置是否开启 FIFO 模式,取值有: - #define DMA_FIFOMode_Disable ((uint32_t)0x00000000)
% m9 S4 h { a2 t - #define DMA_FIFOMode_Enable ((uint32_t)0x00000004)
复制代码 " N8 D8 d0 Z: ~, j' Z8 ]
DMA_FIFOThreshold: 选择 FIFO 阈值,只有上个参数选择使能FIFO,这个参数才有用。取值有:
! s/ `/ `# J* Q! B- e7 i! @
- #define DMA_FIFOThreshold_1QuarterFull ((uint32_t)0x00000000). n$ Y2 z/ A9 i! |& X8 @; i
- #define DMA_FIFOThreshold_HalfFull ((uint32_t)0x00000001)% h! d& l+ P/ I* v
- #define DMA_FIFOThreshold_3QuartersFull ((uint32_t)0x00000002) & r4 B4 h1 N) [$ g9 f" Y7 L1 T( J
- #define DMA_FIFOThreshold_Full ((uint32_t)0x00000003)
复制代码 5 g1 k( m# a# v: R
DMA_MemoryBurst: 配置存储器突发传输配置。可以选择为 4 个节拍的增量突发传输 ,8 个节拍的增量突发传输 , 16 个街拍的增量突发传输以及单次传输。取值有:
a3 I" b+ a7 G* [% X2 @" Q6 @
4 k/ a% {7 E7 g& w9 t$ v& {- #define DMA_MemoryBurst_Single ((uint32_t)0x00000000)
$ I" A9 N2 i3 p2 K. X5 d& M - #define DMA_MemoryBurst_INC4 ((uint32_t)0x00800000)
0 H- f/ r% W% G& N9 ^) c - #define DMA_MemoryBurst_INC8 ((uint32_t)0x01000000)
! j7 J% M& ?6 F6 X, k - #define DMA_MemoryBurst_INC16 ((uint32_t)0x01800000)
复制代码 & J5 f! b6 N: g
DMA_PeripheralBurst: 配置外设突发传输配置。跟前面一个参数DMA_MemoryBurst 作用类似,只不过一个针对的是存储器。取值有 : L. Y1 B% F/ i7 L; O
# |0 O1 p' L# A4 {" w
- #define DMA_PeripheralBurst_Single ((uint32_t)0x00000000)
1 Y% x; Z/ k& g3 v9 s2 N6 {' m - #define DMA_PeripheralBurst_INC4 ((uint32_t)0x00200000) f4 J5 C2 h L9 a @& ?
- #define DMA_PeripheralBurst_INC8 ((uint32_t)0x00400000) 5 [$ y0 P- P5 K7 t, @) f! F
- #define DMA_PeripheralBurst_INC16 ((uint32_t)0x00600000)
复制代码 ) W% }1 Y/ _; U& h$ m: h i' b* k. u5 E
|