以下文章来源于:公_众_号开源电子网 读取更多技术文章,请扫码关注
关注公众号,后台回复:DMA
(免费领取项目文件)
DMA的三种传输方式,你GET到了吗? 前言DMA,详称“Direct Memory access”,直接寄存器访问,被亲切的称为“数据搬运工”。DMA的传输是不需要CPU去控制的,CPU只需要对DMA进行配置即可。所以,在进行大量数据传输的时候,我们首先想到的是能不能用DMA帮我们完成数据传输工作,CPU就可以去干别的事情。
DMA传输简单来说,就是要确定三要素:源地址(从哪里提取数据),目标地址(数据传输到哪里去)还有传输的数据量(传多少数据)。
而这里的源地址和目标地址就可以是“外设”或者“内存”。“外设”可以理解成外设的数据寄存器(XX_DR),而“内存”就可以理解成大数组。
DMA的传输方式就分为三种“内存到外设”、“外设到内存”以及“内存到内存”,本文着重说明使用,细节的东西,可以查看正点原子教程《DMA实验》。
DMA配置步骤简单来说: 1、初始化DMA时钟(具体用到DMA1还是DMA2,查一下参考手册就知道了)。 2、配置好DMA相关工作参数,通过HAL_DMA_Init函数进行初始化。 3、通过外设提供的DMA函数接口进行数据的发送,例如串口1的DMA发送即使用HAL_UART_Transmit_DMA函数即可。 4、开始DMA传输后,肯定需要查询当前当前的DMA传输状态了,这时候就可以使用__HAL_DMA_GET_FLAG函数。 5、使用HAL提供的函数接口,大多数在函数内部已经使能了某些中断位,所以想要配置中断,需要设置NVIC相关和编写中断服务函数xxx_IRQHandler。
接下来就介绍这三种传输方式,都有相对应的例子提供。
内存到外设这里我们以串口1发送使用DMA为例子。具体情景:DMA控制器把数组里的数据传输到串口1的数据寄存器中。DMA初始化相关代码如下:
当然事先,串口1肯定是需要配置好的。在初始化函数这里并没有明确源地址和目标地址相关信息,其实HAL库提供的USART-DMA发送函数中形参就需要三要素。
而我们也是使用这个接口启动DMA传输。启动DMA传输的另一种方式可以直接通过操作寄存器去实现,这种方式将在“内存到内存”部分介绍。
启动DMA传输后,通过__HAL_DMA_GET_FLAG去查询当前DMA传输情况,假如传输完成,那么就需要清除传输完成标记并且停止串口的DMA功能。当然对于DMA传输来说,很多时候想获取当前数据量,就可以通过查询DMA_CNDTR寄存器查询或者用HAL库提供的接口__HAL_DMA_GET_COUNTER。
具体实现如下图:
在HAL_UART_Tramsit_DMA函数中已经默认USART_DR寄存器作为目标寄存器,那么传参中的SendBuff就是要传输的大数组,而SEND_BUF_SIZE就是传输的数据量了。
外设到内存这里我们以ADC1使用DMA为例子。具体情景:DMA控制器将ADC转换后的数据传输到我们定义好的大数组。DMA初始化相关代码如下:
这部分DMA代码与前面的串口相关的DMA代码有点差别,这里使能了DMA1通道1的中断,也就是当满足了条件,例如DMA传输完成,程序就会跳进中断服务函数,通过HAL库提供的一个公共处理中断接口函数HAL_DMA_IRQHandler,最终会进入ADC转换完成的回调函数接口里,这里我们通过一个全局变量去标记是否已经完成ADC的转换,进而在main函数中做完成的数据处理。
而ADC初始化函数存在一点小变动,即使能连续转换,对ADC初始化结构体变量成员ContinuousConvMode赋值ENABLE。具体代码如下:
回调函数这里也列出来,主要就是进行GPIO初始化还有ADC时钟配置。
进行ADC的DMA传输使用的是HAL_ADC_Start_DMA函数接口,具体函数说明可以看下面。
在while循环中的操作,就如下图。
上图中g_adc_dma_buf即ADC完成转换后,DMA会把数据传输到该数组中。 通过对全局变量g_adc_dma_sta得值进行判断(该值会在ADC转换完成中断回调函数中进行置1),转换完成即可对数据进行计算。
内存到内存 DMA的所有通道都支持内存到内存的传输方式,这里指的是F1,其他系列就不一样了,可以查看对应手册说明。具体情景:DMA控制器将源地址中存放的数据传输到我们定义好的大数组。DMA初始化相关代码如下:
这里我们是在初始化函数中直接用了HAL_DMA_Start函数接口去确定DMA传输的三要素,只不过传输数据量为0。这时候,我们就可以通过上面提到的另一种方式去传输数据,即通过DMA_CNDTR寄存器,对该寄存器赋值后,开始DMA传输,寄存器中的数值会递减。这里我们可以看看寄存器说明。
利用寄存器的特性,所以我们就可以自定义一个函数,如下图,完成DMA数据传输。
在while循环中的操作,就如下图。
内存到内存的传输方式比较简单,跟前面的逻辑差不多。通常来说,很少用到这种传输方式,用得比较多的是前面两种方式。