1 s- |4 ]. b @9 s
) o# [; v$ Y( z2 i/ e0 A3 o. i( P! {
DMA,全称Direct Memory Access,即直接存储器访问。DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。当CPU初始化这个传输动作,传输动作本身是由DMA控制器来实现和完成的。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场过程,通过硬件为RAM和IO设备开辟一条直接传输数据的通道,使得CPU的效率大大提高。
, V! X% m; T! `* m, u6 Z9 [3 f
5 n! F( N0 z" j4 ?* Y. {. V主要特点:3 v$ V8 m. \- l4 M- A6 E' D
•DMA上多达7个可独立配置的通道(请求)" M4 }6 ]* w$ B( r) ^5 `2 P0 W
: r t6 Q) M3 K1 Y A2 A! p. h9 M. G•每个通道都连接到专用的硬件DMA请求,软件触发为
+ W1 G/ v9 k2 J M4 n% D9 ~3 ~) s: |* I' @7 g5 B
每个频道也都支持。该配置由软件完成。
! Y7 z, P8 j% J4 H% z9 L* t+ \1 R2 y* p9 }8 H4 g
•DMA通道的请求之间的优先级是软件可编程的(4
) L3 K) p$ E- X) s, {$ J
8 \: c" T: S9 G级别由非常高,高,中,低)或硬件组成(在相等的情况下)" ~' s8 `) r2 F" _5 m
6 x x% J4 T r& _; u* A9 K! X(请求1的优先级高于请求2的优先级,依此类推)
: A, p c. M& w9 H7 V) Y
( Y: J7 `8 Z& `•独立的源和目标传输大小(字节,半字,字),模拟
0 u+ }2 R' S/ [/ B+ K
" o9 o7 l4 E2 t! }6 B包装和拆箱。源/目标地址必须与数据对齐
( _$ h7 M" f0 ?2 m1 q
; G3 L' Z$ B C) r$ z1 V6 [+ ?( D尺寸。
# I8 h# X9 Z# z0 a4 Z* I2 O2 Z) M) ]# t1 H4 M7 S/ m& |5 `1 s0 {
•支持循环缓冲区管理
4 g; T2 v0 p1 A2 P: U7 P, c7 d9 f) T' v' o
•3个事件标志(DMA半传输,DMA传输完成和DMA传输错误)' K" s1 P/ q$ p2 A
& c6 K8 ]# m9 [4 K8 y/ i- { H在每个通道的单个中断请求中进行逻辑或运算
2 i9 X; H: y$ u# |: W* q
. a6 k$ Z; n9 l( w•内存到内存的传输
7 p7 J% n: X5 Y" l7 }
5 }' N, F; w1 U# m! k" l•外围到内存和内存到外围,以及外围到外围
! D* R& h/ }# {
, r1 t. Y7 @! y$ j- P转移( [1 C( A0 F# [2 B8 W4 p) t6 H# D
V" ^7 U; T N$ b5 ^•访问闪存,SRAM,APB和AHB外设作为源和目标
# {& E" A# a. g3 F6 `, Z, Q1 f8 K1 g" a O- w5 M) a2 ^# W
•可编程的数据传输数量:最多65535, T3 e! k: |+ h, X1 |7 q- M* W
- D4 Q& \/ ~0 T4 t( _
DMA通道对应的外设情况(F0系列):
+ {+ T. ^% F" m7 s. U% ~9 O9 y: p6 S Q$ [$ N9 A# m
' [' j% P7 E Q/ v& w5 |
1 F$ r" O |* P! m5 ^. u
4 ^& H; T6 \1 D4 \ v
0 J% _& q- V/ i* [) n# C
) l$ \" _/ [& C+ P( v4 F
8 G" f4 t. l7 F. G8 S- A* q# H6 h, \ 很多博文会讲解里面的详细定义,对于每一处寄存器的详细操作指导,所以我就不会去多写了。5 K9 e6 L5 H8 i3 h8 Z6 R
5 K, |; W6 @' \* i6 T 我只想表达,DMA是一种可以快速相关外设数据交互的一种方法,我们学习哪里用它,怎么用它,至于细节学习,大家去网上所搜DMA相信息即可。
# f% t I4 @3 Y5 k, P* ^3 i4 S4 f/ N5 E
之前讲过DMA的数据发送,现在补充上DMA的接收数据部分。' n* s5 ]9 |5 h. N" l
( I/ e; u* G; p* G( E
利用DMA接收串口数据的配置,大致分为:1.初始化串口并开启DMAR接收功能,配置DMA的外设到内存的数据接收功能,2.等待串口中断提示,并进行处理数据,3.清空DMA,重新等待数据' `) v! b5 F7 o: Z3 e
( k; W1 k5 ^6 z
01 配置串口与DMA
. Z& W. c9 Q2 C. P; F 其中USART2->CR3寄存器的第6 bit用来设置DMA的接收配置,此处比较重要我们设置为USART_CR3_DMAR。 USART2->CR1寄存器中开启帧信息接收完成之后的中断,USART_CR1_IDLEIE,这处可以帮助我们节省CPU的不必要开支,开启此处中断类型,我们只需要在每帧信息接收完成之后,usart才触发中断,我们再解析。其余为正常的usart配置。5 p# u( L! S$ e* }
7 B+ d/ H1 g1 y, @5 g# y9 Y
/ ~- [, R ]) i. e( O
7 f5 L4 C% d5 e/ A v- GPIOD->MODER |= GPIO_MODER_MODER5_1| //USART2_TX/ n1 z$ `/ L4 ~- M- V8 h& z
- GPIO_MODER_MODER6_1; //USART2_RX
' e! g! s# ?- r$ H2 `- [; V5 H - /* set baudrate */& V/ s! _& N8 _. {" Y; h
- USART2->BRR = USART_Baudrate;//115200' [$ b3 L3 [# H, _% v5 N
- USART2->CR3 |= USART_CR3_DMAT|USART_CR3_DMAR;//USART_CR3_OVRDIS;// 不需要覆写
: l% ^' G+ Y+ j% ] - , F% u& v; U4 S& q0 e
- /*enable usart2 and enable tx*/% m/ P5 P7 ^0 v7 S( k! g- {
- USART2->CR1 |= USART_CR1_UE | USART_CR1_TE | USART_CR1_RE | USART_CR1_IDLEIE;
复制代码 / \5 X( ^: c! c' F7 S! _ N
配置DMA:
2 l# M/ ~8 O2 C7 I- N" R; P4 q; x/ n6 }+ F: n
首先根据上方的映射表,我们初始化了usart2,而usart2 RX对应的DMA通道为DMA1 channel5,所以我们进行DMA1 ch5通道配置。, w! K" L8 a' Y! u9 r& }
8 _3 q1 W& Q3 w: c/ b
第一个为DMA1_Channel5->CPAR寄存器,在下表可知,用来设置对应接收数据的外设的地址,而USART的接收数据的寄存器为RDR,所以寄存器配置为DMA1_Channel5->CPAR = (uint32_t)&(USART2->RDR);
5 X2 w) m6 L- D0 u( ?4 s& d$ \; E
& j X8 b0 I% w! R5 B* c7 S, _( k) ^# P2 k+ s2 H7 y' ~
3 E; h5 P0 L% y, \( J( k0 @6 G% G. z$ r: M1 u
设置需要放数据的指定内存位置,根据指导手册可知CMAR为DMA通道用来放置数据的内存地址,所以此处设置为我们定义好的变量的地址。
B2 R6 \( f6 Y+ U+ p8 j6 f3 F4 T8 w* s: o+ m% g- l% Z: `0 r5 d) B$ o9 K4 D: c
3 N6 a6 g$ r0 R* P {3 c
2 v% W4 R- _- u# C 设置单次传送数据量的大小,此处最大可设置为65535byte的数据大小。- h5 k3 ?" M" U/ z3 o, ^1 f5 e' Z
/ E5 J& Q) g- ~( ?
- W9 E0 _6 v2 d( J% |9 r' R6 \1 }0 G4 F B7 u5 S" X. @4 g
最后开启DMA通道。5 Q8 z; q3 W7 ?8 S
- q( R" w# I% r: M" l
, {( D& E4 x1 Q$ M) p
4 |2 l3 j8 E. g0 V4 X9 _4 ^- ~
" w4 ~! g' A5 Q- z3 c a+ W( a V8 C( @* A ]+ B% j4 @ b! c2 I
! B1 R, `; Z! `9 y- void USART2_DMA_Recive(uint8_t *p_data,uint16_t length)! p! e1 g1 M$ C7 Y
- {
9 G u$ x" m) {% T5 {( s - DMA1_Channel5->CPAR = (uint32_t)&(USART2->RDR); //Peripheral address
, H9 t) Z% O+ z$ T* X( M9 ?' r - DMA1_Channel5->CMAR = (uint32_t)p_data; //memory address
" ^0 `4 D! ?' a - DMA1_Channel5->CNDTR = length; //Set the length
* e6 j5 R8 m& n/ G - DMA1_Channel5->CCR |= DMA_CCR_MINC | DMA_CCR_EN;
' f) ?, d( g- T& S0 T O2 }8 c - }; g+ }, l1 d! f; t1 u; t7 R
复制代码
1 e( d: g' k+ ]: m. } {3 y02 等待串口中断,处理数据
7 P, v! ]1 a# U6 o, M9 W/ z- `, R
! {; f6 D& T2 J# Z% e6 w4 N' \% I+ u6 a' j
! v0 a8 _6 U- I: V- I5 d% { 此时在debug中,在我们定义好的DMA内存变量,地址在RAM初始完成,后续串口数据接收的时候,DMA会直接将数据置于p_data所对应的内存。* h- v/ g% Z i- p9 W
. R- ?% J* u- v 然后在中断服务函数我们可以将我们数据进行处理,Uart_Channel_isr[1](USART2->RDR);,这个函数为我自己写的处理函数,中断里面函数都是数据复制,所以我直接在中断执行,一般大家会在中断写个标志,在主循环进行解析。但是DMA接收配合USART_ISR_IDLE标志在STM32平台下并不友好,如果主循环用来解析数据的时候,空闲中断还没有产生,本帧数据还尚未接收完成,但是由于内存数据已经在实时写入,DMA1_Channel5->CNDTR已经有所变化,并早于空闲中断产生,所以主循环就不能用DMA1_Channel5->CNDTR所接收数据长度进行解析了。一般建议,如果解析数据只是单纯的挪移,此时候直接在中断处理即可,对主程序并没有大的阻塞。
+ U, f# d* R( u
0 a8 Z" j1 C, S1 W$ f0 {- void USART2_IRQHandler(void)& p& B6 W1 H% B4 k& d
- {2 t; H1 L+ C* l6 Q: ^5 I: n5 U
- if ((USART2->ISR & USART_ISR_ORE) == USART_ISR_ORE)
* ]$ t* H3 v# v3 q1 }9 ~ - { 3 a$ D7 b/ ]9 k/ Y! @6 i* O4 [4 j) _, j
- USART2->ICR |= USART_ICR_ORECF;
( J/ H" b2 q& E% L- _3 [$ c: T - }8 q7 q6 e) }( s/ L) ~
- + p9 V- ]1 g0 V
- if ((USART2->ISR & USART_ISR_IDLE) == USART_ISR_IDLE) //The new frame data receive : p6 T$ h! S: H- Y$ B7 s
- {
5 A1 M) n p1 h1 t, w" g" z8 N1 n - USART2->ICR |= USART_ICR_IDLECF; 8 t6 Y! O2 R4 A/ H# M+ C7 m9 C
- Uart_Channel_isr[1](USART2->RDR);/*读取解析数据*/
" q! s9 [+ o& F% _1 Q6 m0 ?9 y - } $ i% c9 Z+ ~& K# J! r1 P
- }
复制代码
: y+ z& E2 H( r3 Y$ d+ i& D03 恢复DMA,清空CNDTR,等待下次数据到来
( P7 D; D3 k; f* `2 Q' r! F$ h9 v% L 处理完数据之后,及时将DMA标志以及CNDTR清空,否则CNDTR一直不清空,会导致下次接收数据的时候,造成通道的占用,数据使用过之后,就清理掉CNDTR,这样保证每次接收数据的通道有足够的位置。9 T0 K' R+ ?5 J: t" H0 d+ l1 Q0 E6 Z
- static void Usart2_Dma_Reload(uint16_t length)/*清空数据*/
+ _9 z6 [' l' y7 V, @! D0 p% ]8 F - {
6 ?! m* }' t+ N# N - DMA1_Channel5->CCR &= ~(DMA_CCR_EN); //disable the dma
2 d" n6 i- W0 G/ n - DMA1_Channel5->CNDTR = length; //Set the length
5 A0 B5 T3 r6 P, ~. I - DMA1_Channel5->CCR |= DMA_CCR_EN; //enable the dma; M) }7 J) h- d$ ?4 G) K) }
- }5 h0 E& Z2 d0 S1 h
复制代码 7 f0 E1 i3 U# \6 M. g
0 }1 m) b* m7 N- A
|