1、什么是DMA2 {! L. D+ G% G& E/ |4 Z$ F
DMA全称是Direct Memory Access,即是直接存储器访问。 DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路,能使CPU的效率大为提高。
: ?4 n w1 e' Q8 g6 G: C5 @1 Z2 @* }& f
2、DMA特性$ O4 T% A& _+ n
● STM32F1有12个独立的可配置的通道(请求):DMA1有7个通道,DMA2有5个通道
. J6 y; e2 M2 E% S1 @, d
! o; n% W0 \! Y● 每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。可通过软件来配置。
4 b: V( r4 M& d2 _, P% ?5 S) d% F D% l% Q, R Y+ o1 V
● 在同一个DMA模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、, k3 K1 D4 V+ Q+ I1 S2 h, I* e
5 P$ O+ f$ s! d) [9 }中等和低),优先权设置相等时由硬件决定(请求0优先于请求1,依此类推) 。
4 L# p; Z2 m$ e$ {3 j/ |7 K
/ g5 t) a, v3 Y) h0 o, x7 v● 独立数据源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。! {2 N/ e2 F3 ^9 {0 |' b- t
5 n$ }2 @3 M5 y
● 支持循环的缓冲器管理
# I9 `4 d! K6 C6 w5 M, `" Q
# W+ ]8 \! P% F& u● 每个通道都有3个事件标志(DMA半传输、DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求。6 O, q; Z$ H7 U3 ?; [
0 y% y7 N1 W& ^● 存储器和存储器间的传输
: v) w8 T0 ?# [/ M' f, V& {
" x, e( D! ?* v0 H( q9 ?& u0 F● 外设和存储器、存储器和外设之间的传输6 t4 ~ W* q- V+ ?+ _) k6 V% |
8 y. V$ |$ K/ W6 m● 闪存、SRAM、外设的SRAM、APB1、APB2和AHB外设均可作为访问的源和目标。% _" q) B% ^2 D& @( N0 \
/ j9 ]! c8 E) M; y3 R' O7 i) Y
● 可编程的数据传输数目:最大为65535; \9 |9 X3 f9 f9 x2 o
2 d4 D5 P- s" `9 H
3、请求映射表
- r9 {$ f! C" f" S8 \: k% A' V两个DMA控制器有12个通道(DMA1有7个通道,DMA2有5个通道),每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个DMA请求的优先权。' M% s5 H5 i" B
/ c* ~, o) c; B3 u
3.1、DMA1请求映射表
9 h) h2 W* Z- ? E/ Y0 A* O$ X r3 m0 W5 h
; n% n# O' _+ V' \9 q
) i; ~4 k/ S- I: ^1 B' Q% ^! e) @
3.2、DMA2请求映射表
& J( M: a! @& b ^" {3 Q
3 H% h" }& `6 E: I
' H; w- \7 ]7 X8 L7 {- {7 ^
z g6 p Y4 i# {! }) x4、软件实现
`- n1 K7 k+ D我这里使用USART3和DMA来实现。通过上表可以知道,USART3_TX和SART3_RX分别对应的是DMA1的通道2和通道3,首先就是对USART3和DMA进行初始化# c6 l* r/ B6 ?
2 e3 K j# S) m4.1 串口的初始化! a1 b: S; E: o Y; c W6 y
串口有单独讲解,可自行搜索参考,这里不再讲解。
% V1 t7 ~# d4 t" b) v) ~) T- e+ s) ~, R- o6 }
- void uart3_init(u32 bound)" F; y; `" O' H( R/ G: ~
- {
9 |# u2 T- ^) I7 k9 k, W. b: U - //GPIO端口设置8 g! I- r9 ]9 G7 P, ?/ P7 u( G
- GPIO_InitTypeDef GPIO_InitStructure;
, {5 Y1 w a- H# y: K- x" f7 ]$ H - USART_InitTypeDef USART_InitStructure;
, N! v3 E; Y* ~3 \7 u, v) j/ h% a* T - NVIC_InitTypeDef NVIC_InitStructure;. _0 i% m, Q3 d( d0 }
- //使能USART3,GPIOB时钟
4 L! o7 Y2 n8 h - RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
* P$ z- y# y+ u( [ - % r5 S* P, N9 E4 c$ u
- //USART3_TX GPIOB10
3 S" P2 F! V3 }/ Q - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //P010
) g; J* w4 O' ]3 X% p9 f& t, e - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
9 A) E. h5 J- ~- X" x - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出8 t+ O4 F$ ~6 X% a
- GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB10
/ U: m! M" Y! i2 e5 r0 m: x# t - 0 c9 R4 m k/ _7 l, L# A5 \
- //USART3_RX GPIOB11初始化
0 _1 c' J/ P# {% s0 u) d# d) Y( ?8 ? - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;//PB11: B" m+ D6 @/ h, [. f9 C6 c
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
! l0 q3 H: ]3 v- `1 y: M( U - GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB11 $ K2 T! L1 }3 f
`3 {! o; } M- P3 L- //Usart3 NVIC 配置2 c& P: H {' @9 f
- NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
5 c$ q; e& `1 Z) T T - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级38 B: H" _; A5 J' E4 Y: `
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3
% B! `! w+ }7 z. I - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
- v& N4 `. B4 e" f, J - NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器& g- n3 \/ F! f$ B7 A8 R
- 3 x8 o9 \' a/ B. X( I3 P; ~) f
- //USART 初始化设置
% c2 Z, d6 e. R; R7 U5 q0 ? - USART_InitStructure.USART_BaudRate = bound;//串口波特率% k) ?1 B5 r" X$ Q. }$ a* c
- //字长为8位数据格式
. P; r; @* z: U. E1 o) [. x& A - USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
& A2 H0 v+ R3 L) `# Q/ v: E1 X - USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位* i; H& J7 W* u1 @9 k9 ?
- USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制/ x h4 y1 ]' G8 J( G9 A
- USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
6 ]7 x. e* V# {2 Q - USART_Init(USART3, &USART_InitStructure); //初始化串口31 A. k1 S$ q3 _) m J7 U
- USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);//开启串口接受中断3 u5 s; s! G9 Z
- USART_Cmd(USART3, ENABLE); //使能串口3
+ L& l9 E; Z6 ^4 R& c/ t0 @1 V( U - }
复制代码
4 }( a- t! R* W2 ]0 ?" \& |/ ^ f4.2 DMA初始化
- o2 ]# ^" k Z$ Y) n# Z1)使能 DMA 时钟6 _6 p+ k% _2 S8 ^( o: S2 k b- z
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能 DMA 时钟
复制代码 2 A% G! n& d0 [1 j3 q
2)初始化 DMA 通道 4 参数/ y. W7 K9 S- z7 O3 a' M
- void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct)
复制代码
' x, v0 ^! n1 n; o+ P! m! Q+ ~7 K' X跟其他外设一样,同样是通过初始化结构体成员变量值来达到初始化的目的,DMA_InitTypeDef 结构体的定义如下:
" R; g, H) O( l {" E/ z/ v$ F5 R6 a
5 K; H/ }" @, y1 g" J: ]- typedef struct
8 a7 n. d) N2 q- n, c" d+ x - {
* ^3 [$ A5 |8 H, n1 q1 x - //来设置 DMA 传输的外设基地址,比如串口3,表示方法为 &USART1->DR,也就是外设基地址为串口3的接受发送数据存储器DR的地址! E m" \9 _ }- k$ T0 w( N
- uint32_t DMA_PeripheralBaseAddr;
3 C0 I/ z8 x" J) H - //内存基地址,也就是我们存放DMA传输数据的内存地址
+ Q* O8 S% v+ i% `2 [: ]) d6 l' z) N/ \" } - uint32_t DMA_MemoryBaseAddr;
, o- ~! {1 m- R% P - //置数据传输方向,可以是外设读取数据到内存,也可以是从内存读取数据发送到外设
4 r5 z/ d/ \2 W+ b# J/ n) D - uint32_t DMA_DIR;
- D; B* L: H% K6 |9 m - uint32_t DMA_BufferSize;//设置一次传输数据量的大小,也就是缓存大小
9 l+ @" O1 |5 t, C - uint32_t DMA_PeripheralInc;//设置传输数据的时候外设地址是不变还是递增
2 b% f. T; T2 ~( Z' y4 m! S - uint32_t DMA_MemoryInc;//传输数据时候内存地址是否递增
/ m7 W" A% T- K+ K+ [ - uint32_t DMA_PeripheralDataSize;//设置外设的数据宽度,8bit,16bit,还是36bit ) U' L4 f: T+ n4 i, z
- uint32_t DMA_MemoryDataSize;//设置内存的数据长度) _# V' r$ Q) y
- uint32_t DMA_Mode;//设置DMA模式是否循环采集 ' ^5 S( f$ h; H+ E) B
- uint32_t DMA_Priority;//设置DMA通道的优先级 o% G6 o6 E9 v/ |8 [
- uint32_t DMA_M2M;//设置是否是存储器到存储器模式传输 1 ]" A3 I y7 o' C' t- p
- }DMA_InitTypeDef;
复制代码
9 f# r7 j! L" l+ L所以,使用DMA发送的初始化代码如下:
! p; B) Y# z& X& c8 \7 T
$ N8 W e# y' \9 i" j- void MYDMA_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr). u& F. I ~ r0 `+ @; F
- {
7 p1 h1 F6 f9 ?! L8 i - RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA传输
# @1 a0 ] v) |2 X - DMA_DeInit(DMA_CHx); //将DMA的通道1寄存器重设为缺省值# V) v- q6 \* `' |
- DMA_InitStructure.DMA_PeripheralBaseAddr = cpar; //DMA外设基地址: a: A) c' }6 m' L
- DMA_InitStructure.DMA_MemoryBaseAddr = cmar; //DMA内存基地址
- \2 K$ [( w+ h5 s" t& y - DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //数据传输方向,从内存读取发送到外设
+ }5 G. `" Q; h9 Z( `4 g' G3 G - DMA_InitStructure.DMA_BufferSize = cndtr; //DMA通道的DMA缓存的大小- t& ~8 c0 {" O# b
- DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变; r7 N9 P7 _4 S2 i
- DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增8 V; \. ^5 ^0 ] ~4 Z9 s f
- DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为8位
7 g( t8 z7 k4 Y, P3 |+ c - DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位
+ ~! @$ O" o5 n6 d A, q! y! E - DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常模式) h% Y: R+ \7 F2 P- F" r7 F2 ~
- DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级
0 F$ E6 L" r. p - DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
: x7 ?& K4 t; f4 t4 p3 S - DMA_Init(DMA_CHx, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数初始化DMA的通道所标识的寄存器
8 i7 K9 V( ?! F$ Q - }% S' K+ T, ? o4 f+ w6 w) V5 J. K5 A" Z
8 J( H3 ^5 v5 B( X- //因为使用的是正常模式,所以在每次使用DMS传输时都需要使能一下DMA+ H( V( w& A, M, m
- //开启一次DMA传输$ n7 _4 \$ H8 E; p7 C
- void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx)
1 P% l6 I) b( s# \: t - { 0 S# C0 ]" M8 G) o5 G
- DMA_Cmd(DMA_CHx, DISABLE ); //关闭USART1 TX DMA1 所指示的通道 & e$ U, t: z8 s2 M( R2 O
- DMA_SetCurrDataCounter(DMA_CHx,DMA1_MEM_LEN);//DMA通道的DMA缓存的大小
+ b G% {7 Z7 A9 A3 i( o - DMA_Cmd(DMA_CHx, ENABLE); //使能USART1 TX DMA1 所指示的通道
) ^* l9 @% ]! S% v - }
复制代码
8 i. ]* ~' S+ {$ a% l/ G* e7 \因为是使用的串口,所以还要开启串口的DMA发送功能* x' |' K2 X3 {) f
& w- s' H2 H0 R: YUSART_DMACmd(USART3,USART_DMAReq_Tx,ENABLE);//开启串口的DMA发送功能
- f- Z! Y9 A& a0 }8 _' p7 {总结:也就是MCU内部有一条总线,专门用来提供给DMA使用,如果不使用DMA,串口有数据来,CPU要从串口的数据寄存器把数据搬运到内存(也就是我们定义的buff),如果使用了DMA,数据就可以直接从串口的数据寄存器到内存,也就不关CPU的事了。9 C( x1 z! e" ]2 w1 z3 W
/ a2 j$ Y+ o7 m/ c. E4.3 主函数
! x! G0 o- f M0 F, ~主函数如下
0 t; E: X5 ~. y- //发送数据长度,最好等于sizeof(TEXT_TO_SEND)+2的整数倍.. ?% [; a6 |4 `2 W* g
- #define SEND_BUF_SIZE 8200
7 q6 ?+ _' ]! j! R6 J3 Z+ q - u8 SendBuff[SEND_BUF_SIZE]; //发送数据缓冲区/ m1 z8 x" L% o/ E. J5 `1 _
- const u8 TEXT_TO_SEND[]={"STM32F1 DMA 串口实验"};
: X6 @& h) }7 b4 a# J: b - 1 K$ \1 Y8 Z( F! G2 _( T7 D
- int main(void)
O" q7 C9 f# H8 I! f( n7 P6 V - {
; f. c1 T8 n& J+ h, L- b7 {- w ^* | - float pro=0;//进度
8 d/ G& m! p9 G1 W - delay_init(); //延时函数初始化
( y+ O* N0 P6 T8 {% F - //设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
" k$ N2 T2 k: ^7 k) c
, [' _' t' ]6 Y& Q5 v+ G- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); # x% Q: }& @$ y) a' Y
- uart_init(115200);//串口初始化为115200; c2 {' x* P- ?
- MYDMA_Config(DMA1_Channel2,(u32)&USART3->DR,(u32)SendBuff,SEND_BUF_SIZE);//DMA1通道2,外设为串口3,存储器为SendBuff,长度SEND_BUF_SIZE.! {3 B7 D0 w. E: N
-
2 a- g) G% i' |* k - while(1). b0 L( ` v& R! W
- { 1 Y+ d" J8 q' w6 [3 ^* ^
- printf("\r\nDMA DATA:\r\n"); ; F( p/ a" P0 G, h. Z: M, q, H" {; @
- USART_DMACmd(USART3,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA发送 # j( n6 t* S1 X8 z+ Y4 Z( }- n* F
- MYDMA_Enable(DMA1_Channel4);//开始一次DMA传输! 7 Y! q+ N* y: Y6 ]0 T: W" I8 V
- //等待DMA传输完成,此时我们来做另外一些事,点灯
7 K* [0 Q2 d0 x8 ^ - //实际应用中,传输数据期间,可以执行另外的任务3 Y2 z2 z+ l) q( b# l
- while(1)
: z' `" n6 N1 w# R - {
) O: P6 O2 ]( `) |8 [* H - if(DMA_GetFlagStatus(DMA1_FLAG_TC4)!=RESET) //判断通道4传输完成3 I2 K, Y( l( l7 o4 {
- {& j1 F' s6 }- A4 S7 Q
- DMA_ClearFlag(DMA1_FLAG_TC4);//清除通道4传输完成标志
. Z" `) v9 z+ I( z - break;
9 Z9 C- s9 k2 l: ]" d6 i; i% O# [ - }9 _3 ^. _ q: l# Z
- pro=DMA_GetCurrDataCounter(DMA1_Channel4);//得到当前还剩余多少个数据
5 e' [# |' a# ^ - pro=1-pro/SEND_BUF_SIZE;//得到百分比
t6 u0 M& R8 R# h - pro*=100; //扩大100倍" _$ c/ N& ?* u/ F& E! y
- }
) R' H6 R9 c7 a8 J* _ - dealy_ms(500); E p+ h: v2 {7 f# W5 F
- }
: t3 Q3 R, V9 i& k5 M" q- ~( f7 M" R - }
复制代码
) B8 u- G; A# l W3 K8 N
) O# \0 C& U( y! T B& N
. |* Y; H% y9 x# P0 |1 M |