
5 w* e! {% w! k( Z* c: D![]() 0 I3 K0 ?; z% ~( }% J$ W DMA,全称Direct Memory Access,即直接存储器访问。DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。当CPU初始化这个传输动作,传输动作本身是由DMA控制器来实现和完成的。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场过程,通过硬件为RAM和IO设备开辟一条直接传输数据的通道,使得CPU的效率大大提高。 : `- p1 p8 Q) ]/ v" y- H. S$ ]* Q 主要特点:9 Y. ?6 D( W, P$ F •DMA上多达7个可独立配置的通道(请求); c" z% h2 g9 U! G) }% F% N; S •每个通道都连接到专用的硬件DMA请求,软件触发为 3 y) e$ X6 \# _4 Z) b* l 每个频道也都支持。该配置由软件完成。. J( u2 `* y S' c% q3 r5 j4 L •DMA通道的请求之间的优先级是软件可编程的(43 A! m4 q- _4 B 4 M0 Y$ c8 \6 i- i4 _ 级别由非常高,高,中,低)或硬件组成(在相等的情况下)) h' P3 V0 [4 e7 R1 p 5 A2 V$ t$ B6 G! \ (请求1的优先级高于请求2的优先级,依此类推)" t9 z1 N+ C; R2 J. l/ ` 2 B3 M- _4 h$ E! H •独立的源和目标传输大小(字节,半字,字),模拟 包装和拆箱。源/目标地址必须与数据对齐 尺寸。8 T. }( L9 ^3 g9 i( H0 ^# B. g •支持循环缓冲区管理3 j* C7 ? G4 d4 o% Z0 v # l8 S5 X2 g, b3 w •3个事件标志(DMA半传输,DMA传输完成和DMA传输错误) 在每个通道的单个中断请求中进行逻辑或运算 •内存到内存的传输 •外围到内存和内存到外围,以及外围到外围 转移; I y' r4 a% i9 [% k! l ( `2 i5 Q- p1 w9 y! F0 f •访问闪存,SRAM,APB和AHB外设作为源和目标 * W$ d/ E/ o- [5 o4 ]3 h# T: r •可编程的数据传输数量:最多65535 ; F' V! O3 ?: S; N) w; D/ ? DMA通道对应的外设情况(F0系列): ) ]7 _7 u! C) V& E5 I% h* }0 D5 ] ![]() 1 U9 s7 `) x" [- n# f& x% {9 @ ![]() : C+ Y. z/ ?+ e+ c% T+ ~ 很多博文会讲解里面的详细定义,对于每一处寄存器的详细操作指导,所以我就不会去多写了。( x$ ? k; ]) ~5 E8 t6 r. ~; M 我只想表达,DMA是一种可以快速相关外设数据交互的一种方法,我们学习哪里用它,怎么用它,至于细节学习,大家去网上所搜DMA相信息即可。 . F% K3 q- U4 O" P x 之前讲过DMA的数据发送,现在补充上DMA的接收数据部分。' D- p6 B5 \' q2 i: R2 A6 K2 M3 S+ E # a0 _2 u) D. q0 S) V5 j1 Z4 o 利用DMA接收串口数据的配置,大致分为:1.初始化串口并开启DMAR接收功能,配置DMA的外设到内存的数据接收功能,2.等待串口中断提示,并进行处理数据,3.清空DMA,重新等待数据 T4 T; ^" B T% j 01 配置串口与DMA 其中USART2->CR3寄存器的第6 bit用来设置DMA的接收配置,此处比较重要我们设置为USART_CR3_DMAR。 USART2->CR1寄存器中开启帧信息接收完成之后的中断,USART_CR1_IDLEIE,这处可以帮助我们节省CPU的不必要开支,开启此处中断类型,我们只需要在每帧信息接收完成之后,usart才触发中断,我们再解析。其余为正常的usart配置。 2 g1 x4 B5 h/ \% ^6 U, y0 @1 Q9 _ ![]() 3 W i; b4 S% b* a) O& Y! B
配置DMA:3 R0 U+ y' i0 H: R* o9 W7 Y- [7 w 首先根据上方的映射表,我们初始化了usart2,而usart2 RX对应的DMA通道为DMA1 channel5,所以我们进行DMA1 ch5通道配置。: q* W6 h) G* f K) K0 P" v4 V' w 第一个为DMA1_Channel5->CPAR寄存器,在下表可知,用来设置对应接收数据的外设的地址,而USART的接收数据的寄存器为RDR,所以寄存器配置为DMA1_Channel5->CPAR = (uint32_t)&(USART2->RDR); ![]() 6 S% t, A7 K( W6 y, E$ D ( z5 i$ |; d: x& c$ F2 @ 0 @. n6 D3 u( }: O" o9 E% [ 设置需要放数据的指定内存位置,根据指导手册可知CMAR为DMA通道用来放置数据的内存地址,所以此处设置为我们定义好的变量的地址。, p4 u( }5 B, r- N# `3 E9 z4 Q ![]() 设置单次传送数据量的大小,此处最大可设置为65535byte的数据大小。 M7 B" [. o# x- s* `% B ![]() 7 h+ @9 d& c/ C; |3 V; a 最后开启DMA通道。& B8 v f9 S7 t# q# o0 P! I ![]() ![]() ![]() `8 g+ v& g7 u# e# L
02 等待串口中断,处理数据2 M1 `+ y% b1 N, c( H ![]() R+ p+ X4 q7 Z7 V( B$ ?, j: j4 R 此时在debug中,在我们定义好的DMA内存变量,地址在RAM初始完成,后续串口数据接收的时候,DMA会直接将数据置于p_data所对应的内存。 3 Q! |' k! }/ t2 C$ c 然后在中断服务函数我们可以将我们数据进行处理,Uart_Channel_isr[1](USART2->RDR);,这个函数为我自己写的处理函数,中断里面函数都是数据复制,所以我直接在中断执行,一般大家会在中断写个标志,在主循环进行解析。但是DMA接收配合USART_ISR_IDLE标志在STM32平台下并不友好,如果主循环用来解析数据的时候,空闲中断还没有产生,本帧数据还尚未接收完成,但是由于内存数据已经在实时写入,DMA1_Channel5->CNDTR已经有所变化,并早于空闲中断产生,所以主循环就不能用DMA1_Channel5->CNDTR所接收数据长度进行解析了。一般建议,如果解析数据只是单纯的挪移,此时候直接在中断处理即可,对主程序并没有大的阻塞。$ X5 m& [7 s1 t- [ c) ?( C
03 恢复DMA,清空CNDTR,等待下次数据到来' c! E4 s! r6 b8 @4 L; w 处理完数据之后,及时将DMA标志以及CNDTR清空,否则CNDTR一直不清空,会导致下次接收数据的时候,造成通道的占用,数据使用过之后,就清理掉CNDTR,这样保证每次接收数据的通道有足够的位置。# A# b; _% z) J
|
【经验分享】STM32F1 GPIO工作原理
【经验分享】STM32F0xx_DMA收发USART数据配置详细过程
【经验分享】STM32F1和STM32F4 区别
【经验分享】STM32F1系列之常用外设说明
【经验分享】STM32介绍
【经验分享】STM32F1x系列——Flash 模拟 EEPROM
【经验分享】STM32F1在MDK下新建标准库函数工程
【经验分享】stm32f1的存储器与复位
【经验分享】STM32F10X-架构
【经验分享】stm32F1 us延时函数