DMA,全称Direct Memory Access,即直接存储器访问。DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。当CPU初始化这个传输动作,传输动作本身是由DMA控制器来实现和完成的。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场过程,通过硬件为RAM和IO设备开辟一条直接传输数据的通道,使得CPU的效率大大提高。 % R" Q+ B) y; }% e# l 主要特点:0 L5 Y+ m8 X% F+ O0 M( |8 ~ •DMA上多达7个可独立配置的通道(请求)* T, W) G9 [% F; f4 V. d5 z: | ! M) h T; ~7 @1 d •每个通道都连接到专用的硬件DMA请求,软件触发为 , |2 O1 T/ b+ c1 D) G0 `7 S, P# y 每个频道也都支持。该配置由软件完成。/ ^% g. J0 Z5 ?. M% D •DMA通道的请求之间的优先级是软件可编程的(4 级别由非常高,高,中,低)或硬件组成(在相等的情况下) ; V3 v! S2 @; @7 R- A) k% ` (请求1的优先级高于请求2的优先级,依此类推) 3 I3 Z1 Z% f# r9 f# _ •独立的源和目标传输大小(字节,半字,字),模拟9 m& N! R1 c- d9 { u o$ {0 u0 \) T& }$ |+ V- f) Y 包装和拆箱。源/目标地址必须与数据对齐) v2 Y4 W( |: F' u* j# {# C 6 x- D! ]4 E3 ]6 X4 | 尺寸。# e- ]4 u w& o5 Q6 w! Z3 [ " z$ q8 ~: ]9 g! V3 _& o •支持循环缓冲区管理" N- W1 m+ z: _+ S+ S' c- a. ?( G •3个事件标志(DMA半传输,DMA传输完成和DMA传输错误)& _: ^$ E3 s7 b( D" k H# {; w 在每个通道的单个中断请求中进行逻辑或运算 ( r6 Z( S/ V$ O% g; Q •内存到内存的传输5 L2 u' w9 i! `# l* R •外围到内存和内存到外围,以及外围到外围 转移7 K" @ K1 g+ j" o; R- z- O 6 {$ @- t; m* n! X) P9 ? •访问闪存,SRAM,APB和AHB外设作为源和目标 / `# o6 ~ `; G8 b: E1 h* Y4 c3 j, ? •可编程的数据传输数量:最多655356 ^- a3 b3 S* [& ` DMA通道对应的外设情况(F0系列): ; T! q$ {" H3 E: O6 ~ * U/ I' M8 l8 a. x4 v7 b) u 2 M& R% H$ P" L% X) s7 p * v t* Z6 b {0 {6 a% v 很多博文会讲解里面的详细定义,对于每一处寄存器的详细操作指导,所以我就不会去多写了。 3 ~0 D) X# O+ x; b3 i) t' e 我只想表达,DMA是一种可以快速相关外设数据交互的一种方法,我们学习哪里用它,怎么用它,至于细节学习,大家去网上所搜DMA相信息即可。 ( n b9 Q+ O6 f1 O / k) S1 Q, E- Q2 u ]& v0 Y 之前讲过DMA的数据发送,现在补充上DMA的接收数据部分。' V0 K& a2 {) a 利用DMA接收串口数据的配置,大致分为:1.初始化串口并开启DMAR接收功能,配置DMA的外设到内存的数据接收功能,2.等待串口中断提示,并进行处理数据,3.清空DMA,重新等待数据 01 配置串口与DMA, a6 W' A: m1 l. n2 g) A. C+ a* l; G 其中USART2->CR3寄存器的第6 bit用来设置DMA的接收配置,此处比较重要我们设置为USART_CR3_DMAR。 USART2->CR1寄存器中开启帧信息接收完成之后的中断,USART_CR1_IDLEIE,这处可以帮助我们节省CPU的不必要开支,开启此处中断类型,我们只需要在每帧信息接收完成之后,usart才触发中断,我们再解析。其余为正常的usart配置。 0 U7 ~; J1 @1 j5 G3 X4 J
配置DMA: ! `9 v# y: x [: E+ w; k+ p 首先根据上方的映射表,我们初始化了usart2,而usart2 RX对应的DMA通道为DMA1 channel5,所以我们进行DMA1 ch5通道配置。+ K) x* J: h$ m v" h/ i4 y |5 l2 A# n- _ 第一个为DMA1_Channel5->CPAR寄存器,在下表可知,用来设置对应接收数据的外设的地址,而USART的接收数据的寄存器为RDR,所以寄存器配置为DMA1_Channel5->CPAR = (uint32_t)&(USART2->RDR); . O: K+ C# @ v/ H7 W" X% Q 设置需要放数据的指定内存位置,根据指导手册可知CMAR为DMA通道用来放置数据的内存地址,所以此处设置为我们定义好的变量的地址。 1 R1 |0 {4 K# w3 [ e4 @4 R 设置单次传送数据量的大小,此处最大可设置为65535byte的数据大小。; p: `* J* C$ H4 C/ W/ H 最后开启DMA通道。 9 ^( c# D5 r& u, u9 \. i; y* z# n ' k" V& q' \% U! Y) A [
02 等待串口中断,处理数据. s7 C1 u2 b/ i1 Y $ @' B; W3 ~ t# ]2 x 此时在debug中,在我们定义好的DMA内存变量,地址在RAM初始完成,后续串口数据接收的时候,DMA会直接将数据置于p_data所对应的内存。/ ~8 Y: r# Y; T# O0 h5 F 然后在中断服务函数我们可以将我们数据进行处理,Uart_Channel_isr[1](USART2->RDR);,这个函数为我自己写的处理函数,中断里面函数都是数据复制,所以我直接在中断执行,一般大家会在中断写个标志,在主循环进行解析。但是DMA接收配合USART_ISR_IDLE标志在STM32平台下并不友好,如果主循环用来解析数据的时候,空闲中断还没有产生,本帧数据还尚未接收完成,但是由于内存数据已经在实时写入,DMA1_Channel5->CNDTR已经有所变化,并早于空闲中断产生,所以主循环就不能用DMA1_Channel5->CNDTR所接收数据长度进行解析了。一般建议,如果解析数据只是单纯的挪移,此时候直接在中断处理即可,对主程序并没有大的阻塞。/ _5 d9 {/ w- Z + {4 H# i+ R2 T
03 恢复DMA,清空CNDTR,等待下次数据到来 处理完数据之后,及时将DMA标志以及CNDTR清空,否则CNDTR一直不清空,会导致下次接收数据的时候,造成通道的占用,数据使用过之后,就清理掉CNDTR,这样保证每次接收数据的通道有足够的位置。1 h9 H: ~* ~- ?2 S( I+ ^
|
【经验分享】STM32F1 GPIO工作原理
【经验分享】STM32F0xx_DMA收发USART数据配置详细过程
【经验分享】STM32F1和STM32F4 区别
【经验分享】STM32F1系列之常用外设说明
【经验分享】STM32介绍
【经验分享】STM32F1x系列——Flash 模拟 EEPROM
【经验分享】STM32F1在MDK下新建标准库函数工程
【经验分享】stm32f1的存储器与复位
【经验分享】STM32F10X-架构
【经验分享】stm32F1 us延时函数