" v6 ^3 A9 H9 [( p; u0 F( |* m, f: W/ S' _# [; y
0 X, j4 D4 I9 KDMA,全称Direct Memory Access,即直接存储器访问。DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。当CPU初始化这个传输动作,传输动作本身是由DMA控制器来实现和完成的。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场过程,通过硬件为RAM和IO设备开辟一条直接传输数据的通道,使得CPU的效率大大提高。8 V- o: w i* }9 Z- z
1 t+ t. y$ R' J0 i主要特点:
/ ^5 I2 g+ G! s6 c- V3 W; g•DMA上多达7个可独立配置的通道(请求)
' ^4 O8 P; v! v: R; ~& b5 C) G/ }2 G$ Q! G
•每个通道都连接到专用的硬件DMA请求,软件触发为6 n0 R( ~- V& x8 X) g% N
+ g8 n9 Y+ j* _0 J. t每个频道也都支持。该配置由软件完成。
: j- I( ?3 s( |+ |; t
: `% a. ^5 \( G6 R, X•DMA通道的请求之间的优先级是软件可编程的(4
7 M/ N2 C* p- X$ A6 ?, b- C1 m
- o% z6 U: R h v* n3 f% u, Q9 M( ]级别由非常高,高,中,低)或硬件组成(在相等的情况下)
( T* X( g' T } ?+ a7 H
! K8 N$ Z7 A+ i(请求1的优先级高于请求2的优先级,依此类推)
/ {8 r' c5 ?' X0 ~7 ^( E
- H$ w( N3 `; G. k% [/ I# ~•独立的源和目标传输大小(字节,半字,字),模拟3 G0 k2 c4 K- D* K, ~7 P
: q# `9 m/ b+ f' j, J' a包装和拆箱。源/目标地址必须与数据对齐
$ @2 v4 {8 q' Q+ n: i! H* R- @$ Y2 [. r, A- a/ o/ N! a4 K7 D* ?/ X* W
尺寸。' W1 Z9 a0 k! _( ^ s/ x8 u* {
3 w0 h! G- W. V% h•支持循环缓冲区管理5 _( K; T7 a3 C" A: R" _7 }
% e1 R+ E. R1 ~3 o•3个事件标志(DMA半传输,DMA传输完成和DMA传输错误)7 J4 g0 h( @( K- a+ b' U
' s! g" d$ U) R5 h" G
在每个通道的单个中断请求中进行逻辑或运算
. n m4 a* V; J" F6 |* k3 P/ X$ ^1 k c! x T
•内存到内存的传输# ?& n: u5 m3 g# ^- I" E% m
4 n9 I, k$ O9 C7 U! v2 I' F. b7 f•外围到内存和内存到外围,以及外围到外围
7 `: p9 Z$ o# t) U- j0 Y$ T, j, v6 m" c* ?
转移
' N$ H- f5 {3 x) L0 y$ R4 W9 } }* i: i8 q0 P) V, B" }
•访问闪存,SRAM,APB和AHB外设作为源和目标
# t" t: e* p* S! O0 g B# D" B/ I. K! i0 Q5 _8 `' n" f
•可编程的数据传输数量:最多65535
- M" Y% A' c) ]. _' F3 x- S+ q, l$ l* g6 p; t) s
DMA通道对应的外设情况(F0系列):6 q$ ]; W% |$ `& q* n2 p
2 S k/ x/ A* i4 R
/ G/ G+ T% T) A; V
0 b9 N: z6 K* J) f/ }2 f
% t" h; n5 t; `4 F- A) K; x* m& e" X) E( u, H
0 o% r5 a7 W7 m0 ?
/ ?3 }7 Y0 k5 i+ x' F. V+ ?
很多博文会讲解里面的详细定义,对于每一处寄存器的详细操作指导,所以我就不会去多写了。8 m' f8 Y* q# u: L2 ?1 _
, } {5 ]# y& y. T6 b" c 我只想表达,DMA是一种可以快速相关外设数据交互的一种方法,我们学习哪里用它,怎么用它,至于细节学习,大家去网上所搜DMA相信息即可。
6 u3 J2 O/ v3 N' O$ E u& q7 I. P' p1 d: \
之前讲过DMA的数据发送,现在补充上DMA的接收数据部分。- D, d4 ~# E5 O3 Z
2 y M' T! B3 ]; a
利用DMA接收串口数据的配置,大致分为:1.初始化串口并开启DMAR接收功能,配置DMA的外设到内存的数据接收功能,2.等待串口中断提示,并进行处理数据,3.清空DMA,重新等待数据. H9 E7 {( f7 {& q
' n: d3 K0 m1 v# ]
01 配置串口与DMA
8 J- _2 y, I \# }2 _/ h P 其中USART2->CR3寄存器的第6 bit用来设置DMA的接收配置,此处比较重要我们设置为USART_CR3_DMAR。 USART2->CR1寄存器中开启帧信息接收完成之后的中断,USART_CR1_IDLEIE,这处可以帮助我们节省CPU的不必要开支,开启此处中断类型,我们只需要在每帧信息接收完成之后,usart才触发中断,我们再解析。其余为正常的usart配置。
6 n- `7 h( k" s" e7 U" u& L. R
* z' K! u- q6 R+ W$ t8 Q. n, E! O, }! W. I6 N7 Q; \! A
6 w& d) `/ @+ C, `5 G D4 M; ^6 ~- GPIOD->MODER |= GPIO_MODER_MODER5_1| //USART2_TX3 y, k! |) n- u$ t; E7 g" }1 u J
- GPIO_MODER_MODER6_1; //USART2_RX 0 N, q7 i5 o/ p, C3 w1 B
- /* set baudrate */
) f1 l$ S( Y: s# A3 S - USART2->BRR = USART_Baudrate;//115200/ Q" e" b! E. I9 R0 p
- USART2->CR3 |= USART_CR3_DMAT|USART_CR3_DMAR;//USART_CR3_OVRDIS;// 不需要覆写* j, w( y) O4 e5 q3 H. [
-
2 n0 a2 d y1 C ?' x$ { - /*enable usart2 and enable tx*/0 r2 U9 O6 X/ g/ H
- USART2->CR1 |= USART_CR1_UE | USART_CR1_TE | USART_CR1_RE | USART_CR1_IDLEIE;
复制代码 6 z1 t4 T6 Y- `3 ^/ Q
配置DMA:) G' q" z- p( y" n# t
5 \' g' V3 u0 {3 d! `% E. ? 首先根据上方的映射表,我们初始化了usart2,而usart2 RX对应的DMA通道为DMA1 channel5,所以我们进行DMA1 ch5通道配置。7 d* c% H; x% f% B2 T5 `
7 [5 N% c( s2 l! p: D* Q
第一个为DMA1_Channel5->CPAR寄存器,在下表可知,用来设置对应接收数据的外设的地址,而USART的接收数据的寄存器为RDR,所以寄存器配置为DMA1_Channel5->CPAR = (uint32_t)&(USART2->RDR);5 ]$ D! A# W' D5 E
4 i2 a- m1 h8 k+ m
. U9 o7 R' d Q1 D
& p/ v) ~( E: R4 ?+ p8 n9 I* V1 X$ h; W! G7 K
7 m* e, t. s- N2 B2 `, x: b+ `+ T 设置需要放数据的指定内存位置,根据指导手册可知CMAR为DMA通道用来放置数据的内存地址,所以此处设置为我们定义好的变量的地址。
. B3 e7 U1 E* j1 z% k, b( ~ m6 w1 y. a( p ?& i" w
8 K1 d: ^4 g: \: a" r7 J) H$ a) N/ A9 B+ _4 I
设置单次传送数据量的大小,此处最大可设置为65535byte的数据大小。
" O( C* y3 ?$ G# S. |. O: X; U4 I! ^( y1 c
; S# _ U5 {5 W- a+ F' C3 b" m
5 A8 @& J, t7 d/ Q最后开启DMA通道。/ h, S4 u6 E# Q
; N/ U2 \% w! b& L% f
/ f# }. R$ Y3 [ J' |
8 x& Z9 F& d9 k [9 o+ e7 \0 o0 O3 |& y6 `
# O2 ~6 c7 L/ ~ K2 R3 R* B
* n1 X8 O0 n5 L4 W1 O' R$ B- void USART2_DMA_Recive(uint8_t *p_data,uint16_t length)
! v6 F( H; g5 p* h: m. T% F& i - {
! W$ U( ]* o f% n8 r- Y - DMA1_Channel5->CPAR = (uint32_t)&(USART2->RDR); //Peripheral address
2 f- ^& Q0 K3 x - DMA1_Channel5->CMAR = (uint32_t)p_data; //memory address! q! _/ Q2 a0 X- _
- DMA1_Channel5->CNDTR = length; //Set the length. j, Y% h j) } p4 M/ B
- DMA1_Channel5->CCR |= DMA_CCR_MINC | DMA_CCR_EN;
4 q0 T& a2 O2 w1 [+ x9 C2 C - }1 U/ s' A0 J$ s; x& s' w/ f7 M% A
复制代码 : D% i" g8 m8 }' |2 ]# t9 q
02 等待串口中断,处理数据 S: G8 E+ I% h" d8 G2 x; H
; `' l- [, t0 }9 T! |
' `5 _5 `( \1 ?/ f+ A6 y x: H3 i- J. a @! u% S8 q
此时在debug中,在我们定义好的DMA内存变量,地址在RAM初始完成,后续串口数据接收的时候,DMA会直接将数据置于p_data所对应的内存。7 k4 \/ G; v5 c$ E' J1 A% _
% C# g5 O5 ~7 ]; B. `1 V
然后在中断服务函数我们可以将我们数据进行处理,Uart_Channel_isr[1](USART2->RDR);,这个函数为我自己写的处理函数,中断里面函数都是数据复制,所以我直接在中断执行,一般大家会在中断写个标志,在主循环进行解析。但是DMA接收配合USART_ISR_IDLE标志在STM32平台下并不友好,如果主循环用来解析数据的时候,空闲中断还没有产生,本帧数据还尚未接收完成,但是由于内存数据已经在实时写入,DMA1_Channel5->CNDTR已经有所变化,并早于空闲中断产生,所以主循环就不能用DMA1_Channel5->CNDTR所接收数据长度进行解析了。一般建议,如果解析数据只是单纯的挪移,此时候直接在中断处理即可,对主程序并没有大的阻塞。
* R0 G) Q' {& W6 T6 ?3 I
9 j8 g/ j( w# D6 n- void USART2_IRQHandler(void)
, i/ @# ^5 q" p( S+ s - {
; G2 [7 u# e/ h- Q5 @9 M/ Q - if ((USART2->ISR & USART_ISR_ORE) == USART_ISR_ORE)
8 @) m1 t# [; M6 `4 ]- }- c - {
) j# I8 B. b5 _- L, a1 _ - USART2->ICR |= USART_ICR_ORECF;
7 G" u) d; Y5 I( q1 ? - }* y* |. I& t% e( q
3 f" }! H8 y; c2 _/ T- if ((USART2->ISR & USART_ISR_IDLE) == USART_ISR_IDLE) //The new frame data receive . T: a- j: N; ]' P" G2 ^
- {
4 s9 W" G# a! k7 V - USART2->ICR |= USART_ICR_IDLECF;
5 `% T7 L2 u3 K: a, N5 }8 C - Uart_Channel_isr[1](USART2->RDR);/*读取解析数据*/
, t! L7 y @- O! U7 q - }
, n( L* P# e4 n# Y - }
复制代码 * v+ E1 D" z2 N1 d& E+ W# A- b
03 恢复DMA,清空CNDTR,等待下次数据到来
* S5 L3 z) z9 t' H8 r. l H" n$ k 处理完数据之后,及时将DMA标志以及CNDTR清空,否则CNDTR一直不清空,会导致下次接收数据的时候,造成通道的占用,数据使用过之后,就清理掉CNDTR,这样保证每次接收数据的通道有足够的位置。
8 O% X. `! c4 q7 j; ]- static void Usart2_Dma_Reload(uint16_t length)/*清空数据*/
7 f% m+ b8 t$ k8 J; D+ e- Q - {
; n8 ]( Z0 L8 F! f9 Q - DMA1_Channel5->CCR &= ~(DMA_CCR_EN); //disable the dma & W& P# b$ U: B+ E8 P
- DMA1_Channel5->CNDTR = length; //Set the length2 `( C8 ~4 i# T9 H" \7 a8 m: U6 g
- DMA1_Channel5->CCR |= DMA_CCR_EN; //enable the dma
: }7 b0 l* k. v; A2 I% M - }2 i) p' u0 s, R& r0 b
复制代码 9 {5 O; t5 M2 Q, ~. Z9 r: T; ^
1 k( ^& }" G! Y. U& o5 l |