, O$ m& ^, U) G0 e4 m DMA,全称Direct Memory Access,即直接存储器访问。DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。当CPU初始化这个传输动作,传输动作本身是由DMA控制器来实现和完成的。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场过程,通过硬件为RAM和IO设备开辟一条直接传输数据的通道,使得CPU的效率大大提高。 / l! E) M1 I3 `/ Z6 y9 X) c 主要特点: •DMA上多达7个可独立配置的通道(请求) , |* K! L/ R% n6 s; m) C, D* q •每个通道都连接到专用的硬件DMA请求,软件触发为' U; J* {% W( l" ~# L/ a' G8 I( d& t 4 a/ e0 Q6 e; _3 l' ? 每个频道也都支持。该配置由软件完成。0 `6 L7 C9 f- m) o3 a; @" w# B. G/ X ! K, p3 |$ T6 G2 Z- a M •DMA通道的请求之间的优先级是软件可编程的(4 & F- Q, S# e+ h1 v+ v% r 级别由非常高,高,中,低)或硬件组成(在相等的情况下) (请求1的优先级高于请求2的优先级,依此类推): `* c$ O7 f% o0 S( r( f+ i5 D •独立的源和目标传输大小(字节,半字,字),模拟' t. U' _) p1 O3 Y* ~- w& K* ] # R2 f+ s! E/ J# _/ ~ 包装和拆箱。源/目标地址必须与数据对齐9 F& Q! N+ `9 D7 w% t " d Y1 ]4 L( o8 D! n7 i6 ] 尺寸。 •支持循环缓冲区管理, W- G6 w7 a0 L: u2 a ' P- ~0 l. x3 h4 L* K# h, H •3个事件标志(DMA半传输,DMA传输完成和DMA传输错误)* P9 ^; n$ Y1 ]: r9 j + }2 F# D: R1 H3 n/ e3 \3 F 在每个通道的单个中断请求中进行逻辑或运算2 v. x2 @, z! a! y. F/ J; ~, k& X* T* x2 A •内存到内存的传输 •外围到内存和内存到外围,以及外围到外围 转移 * x1 T$ I( ^/ o! ^7 k8 } •访问闪存,SRAM,APB和AHB外设作为源和目标: ^! V8 L- t @: \+ E ( Z9 ~9 |/ ]) n0 _; f: f8 v! X •可编程的数据传输数量:最多655358 v' C5 T% M+ m* q DMA通道对应的外设情况(F0系列):4 A5 u: f, f& v/ c. G2 w ! s2 J: J' n- e& L! z0 E 很多博文会讲解里面的详细定义,对于每一处寄存器的详细操作指导,所以我就不会去多写了。 # J8 Q5 I2 m5 h3 h( E5 [ 我只想表达,DMA是一种可以快速相关外设数据交互的一种方法,我们学习哪里用它,怎么用它,至于细节学习,大家去网上所搜DMA相信息即可。 ; e( b' `' E Z7 S 之前讲过DMA的数据发送,现在补充上DMA的接收数据部分。/ C, ?# s6 Q$ \' Y6 z, | # k1 v3 m2 L' d. G# j; I 利用DMA接收串口数据的配置,大致分为:1.初始化串口并开启DMAR接收功能,配置DMA的外设到内存的数据接收功能,2.等待串口中断提示,并进行处理数据,3.清空DMA,重新等待数据3 S) E5 Q9 v; x! m4 y# A" G5 H% { ! [2 E) b! o3 n 01 配置串口与DMA) J7 [5 X/ s; W7 S4 |- o7 o 其中USART2->CR3寄存器的第6 bit用来设置DMA的接收配置,此处比较重要我们设置为USART_CR3_DMAR。 USART2->CR1寄存器中开启帧信息接收完成之后的中断,USART_CR1_IDLEIE,这处可以帮助我们节省CPU的不必要开支,开启此处中断类型,我们只需要在每帧信息接收完成之后,usart才触发中断,我们再解析。其余为正常的usart配置。9 Z, g" P4 J: w5 V- b: i% ` " \, {$ D8 Z! U$ K0 s) _. j) d/ M
配置DMA:2 f) ~( m; z: G6 }3 _ V & J8 \4 [: E7 s 首先根据上方的映射表,我们初始化了usart2,而usart2 RX对应的DMA通道为DMA1 channel5,所以我们进行DMA1 ch5通道配置。 f( N3 z$ y; G, \7 Z 第一个为DMA1_Channel5->CPAR寄存器,在下表可知,用来设置对应接收数据的外设的地址,而USART的接收数据的寄存器为RDR,所以寄存器配置为DMA1_Channel5->CPAR = (uint32_t)&(USART2->RDR); 7 R' V, E, E+ M5 R: Z* P' F ! Q' H+ c( f/ @ A6 H+ e 设置需要放数据的指定内存位置,根据指导手册可知CMAR为DMA通道用来放置数据的内存地址,所以此处设置为我们定义好的变量的地址。 ; ^( U: |1 Y9 w) U' |# V& h% I; x , L! b, z" E l3 A8 W 设置单次传送数据量的大小,此处最大可设置为65535byte的数据大小。! L. n; n9 R0 y; P" E! P, x) u9 s 最后开启DMA通道。 ' T, ]9 l, |) j2 R9 j / a* t( i8 d6 o9 B P; { 1 h4 C$ t2 x' f3 B0 |: D6 \
02 等待串口中断,处理数据 6 ?- c+ a6 e0 V9 G- \, {3 u5 m 此时在debug中,在我们定义好的DMA内存变量,地址在RAM初始完成,后续串口数据接收的时候,DMA会直接将数据置于p_data所对应的内存。 然后在中断服务函数我们可以将我们数据进行处理,Uart_Channel_isr[1](USART2->RDR);,这个函数为我自己写的处理函数,中断里面函数都是数据复制,所以我直接在中断执行,一般大家会在中断写个标志,在主循环进行解析。但是DMA接收配合USART_ISR_IDLE标志在STM32平台下并不友好,如果主循环用来解析数据的时候,空闲中断还没有产生,本帧数据还尚未接收完成,但是由于内存数据已经在实时写入,DMA1_Channel5->CNDTR已经有所变化,并早于空闲中断产生,所以主循环就不能用DMA1_Channel5->CNDTR所接收数据长度进行解析了。一般建议,如果解析数据只是单纯的挪移,此时候直接在中断处理即可,对主程序并没有大的阻塞。
03 恢复DMA,清空CNDTR,等待下次数据到来8 K, @3 }/ I T( F7 t 处理完数据之后,及时将DMA标志以及CNDTR清空,否则CNDTR一直不清空,会导致下次接收数据的时候,造成通道的占用,数据使用过之后,就清理掉CNDTR,这样保证每次接收数据的通道有足够的位置。
- _( @4 x8 o, y8 }+ G' j |
【经验分享】STM32F1和STM32F4 区别
【经验分享】STM32F1系列之常用外设说明
【经验分享】STM32介绍
【经验分享】STM32F1x系列——Flash 模拟 EEPROM
【经验分享】STM32F1在MDK下新建标准库函数工程
【经验分享】stm32f1的存储器与复位
【经验分享】STM32F10X-架构
【经验分享】stm32F1 us延时函数
【经验分享】STM32F1之定时器
【经验分享】【stm32】stm32f1代码中core_cm3、system_stm32f10x、stm32f10x_conf、stm32f10x等文件的作用