
![]() / V' P0 n0 h4 j+ \& W6 k; N DMA,全称Direct Memory Access,即直接存储器访问。DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。当CPU初始化这个传输动作,传输动作本身是由DMA控制器来实现和完成的。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场过程,通过硬件为RAM和IO设备开辟一条直接传输数据的通道,使得CPU的效率大大提高。3 m3 {- p+ R6 X/ O' Z1 a 主要特点: •DMA上多达7个可独立配置的通道(请求)% Q2 t* X- D' y5 A8 C- Q- X •每个通道都连接到专用的硬件DMA请求,软件触发为7 ]$ m2 l8 W; J% I+ d5 U 0 i9 v R( p4 R/ |# ? 每个频道也都支持。该配置由软件完成。5 M0 n% ~7 Q9 J% n ; T0 D B; g3 W& d •DMA通道的请求之间的优先级是软件可编程的(4 级别由非常高,高,中,低)或硬件组成(在相等的情况下) : N0 q# m$ Q: Y% w. |1 k (请求1的优先级高于请求2的优先级,依此类推) 7 b" m5 e" b) t6 I! O •独立的源和目标传输大小(字节,半字,字),模拟, e; D4 \! R; f0 }4 Q8 k9 e, S& U 7 S8 W5 Z! J5 L$ q( o _% D& s1 s 包装和拆箱。源/目标地址必须与数据对齐" _4 l8 @$ h/ k- G1 K+ j+ [ 尺寸。 •支持循环缓冲区管理5 n1 L! U4 j, B8 \& @) i * F) `7 W' h2 B( d •3个事件标志(DMA半传输,DMA传输完成和DMA传输错误) - U+ I+ \1 D2 @" y 在每个通道的单个中断请求中进行逻辑或运算 •内存到内存的传输 •外围到内存和内存到外围,以及外围到外围! D/ ]2 w( s* y# [# m- s 转移 •访问闪存,SRAM,APB和AHB外设作为源和目标 3 Z8 Z \ w. x d •可编程的数据传输数量:最多655357 b: u1 D- {+ C, c' F + g& K% n) Y( t+ ^9 q: g# ?2 z* j DMA通道对应的外设情况(F0系列):( A: w9 d" q- n2 P! Q# f 6 w& v7 c' t" h4 u ![]() ![]() 8 O) }6 C1 l _2 j4 k 很多博文会讲解里面的详细定义,对于每一处寄存器的详细操作指导,所以我就不会去多写了。 我只想表达,DMA是一种可以快速相关外设数据交互的一种方法,我们学习哪里用它,怎么用它,至于细节学习,大家去网上所搜DMA相信息即可。 5 Q/ w1 I. r5 C$ n% M 之前讲过DMA的数据发送,现在补充上DMA的接收数据部分。% s) U4 }$ v& y8 U5 e9 v . @) X: L- K- v 利用DMA接收串口数据的配置,大致分为:1.初始化串口并开启DMAR接收功能,配置DMA的外设到内存的数据接收功能,2.等待串口中断提示,并进行处理数据,3.清空DMA,重新等待数据- q! J8 \3 l$ t" s 01 配置串口与DMA 其中USART2->CR3寄存器的第6 bit用来设置DMA的接收配置,此处比较重要我们设置为USART_CR3_DMAR。 USART2->CR1寄存器中开启帧信息接收完成之后的中断,USART_CR1_IDLEIE,这处可以帮助我们节省CPU的不必要开支,开启此处中断类型,我们只需要在每帧信息接收完成之后,usart才触发中断,我们再解析。其余为正常的usart配置。 ![]()
配置DMA:) B% V4 h$ L L5 T# m6 f % q3 o( }9 Z' U; z6 h+ x' L 首先根据上方的映射表,我们初始化了usart2,而usart2 RX对应的DMA通道为DMA1 channel5,所以我们进行DMA1 ch5通道配置。, w; X% _9 ]8 l @8 c 第一个为DMA1_Channel5->CPAR寄存器,在下表可知,用来设置对应接收数据的外设的地址,而USART的接收数据的寄存器为RDR,所以寄存器配置为DMA1_Channel5->CPAR = (uint32_t)&(USART2->RDR); ' W1 L6 f' z4 H" W ![]() ^" r% I1 X5 o6 E 9 b* I. A* P# b: h2 Z' _* J " N* N9 ~- P, V 设置需要放数据的指定内存位置,根据指导手册可知CMAR为DMA通道用来放置数据的内存地址,所以此处设置为我们定义好的变量的地址。 : Q# K4 ~8 S+ K( r, q ![]() - k9 l( O) ^* _" A) c 设置单次传送数据量的大小,此处最大可设置为65535byte的数据大小。4 D* s$ n. \; X! r* ^. C7 R8 G5 N ![]() " U& H, R# g% t 最后开启DMA通道。7 E( M' o) |' ?( T/ @ ![]() 2 [# k- Z" d5 T7 X ![]() ![]() 9 D; M1 m5 D9 |
02 等待串口中断,处理数据 # O0 N7 `9 W7 k2 [, @ ![]() / J6 e$ f: D- q% c9 Y9 j9 W 此时在debug中,在我们定义好的DMA内存变量,地址在RAM初始完成,后续串口数据接收的时候,DMA会直接将数据置于p_data所对应的内存。 9 |4 q! M1 E! U 然后在中断服务函数我们可以将我们数据进行处理,Uart_Channel_isr[1](USART2->RDR);,这个函数为我自己写的处理函数,中断里面函数都是数据复制,所以我直接在中断执行,一般大家会在中断写个标志,在主循环进行解析。但是DMA接收配合USART_ISR_IDLE标志在STM32平台下并不友好,如果主循环用来解析数据的时候,空闲中断还没有产生,本帧数据还尚未接收完成,但是由于内存数据已经在实时写入,DMA1_Channel5->CNDTR已经有所变化,并早于空闲中断产生,所以主循环就不能用DMA1_Channel5->CNDTR所接收数据长度进行解析了。一般建议,如果解析数据只是单纯的挪移,此时候直接在中断处理即可,对主程序并没有大的阻塞。
03 恢复DMA,清空CNDTR,等待下次数据到来* J1 t1 x$ J; E. A 处理完数据之后,及时将DMA标志以及CNDTR清空,否则CNDTR一直不清空,会导致下次接收数据的时候,造成通道的占用,数据使用过之后,就清理掉CNDTR,这样保证每次接收数据的通道有足够的位置。
|
【经验分享】STM32F1 GPIO工作原理
【经验分享】STM32F0xx_DMA收发USART数据配置详细过程
【经验分享】STM32F1和STM32F4 区别
【经验分享】STM32F1系列之常用外设说明
【经验分享】STM32介绍
【经验分享】STM32F1x系列——Flash 模拟 EEPROM
【经验分享】STM32F1在MDK下新建标准库函数工程
【经验分享】stm32f1的存储器与复位
【经验分享】STM32F10X-架构
【经验分享】stm32F1 us延时函数