正点原子 发表于 2022-3-2 11:41:46

DMA的三种传输方式,你GET到了吗?

以下文章来源于:公众号:开源电子网,读取更多技术文章,请扫码关注



DMA的三种传输方式,你GET到了吗?

前言

       DMA,详称“DirectMemoryaccess”,直接寄存器访问,被亲切的称为“数据搬运工”。DMA的传输是不需要CPU去控制的,CPU只需要对DMA进行配置即可。所以,在进行大量数据传输的时候,我们首先想到的是能不能用DMA帮我们完成数据传输工作,CPU就可以去干别的事情。

       DMA传输简单来说,就是要确定三要素:源地址(从哪里提取数据),目标地址(数据传输到哪里去)还有传输的数据量(传多少数据)。

       而这里的源地址和目标地址就可以是“外设”或者“内存”。“外设”可以理解成外设的数据寄存器(XX_DR),而“内存”就可以理解成大数组。

       DMA的传输方式就分为三种“内存到外设”、“外设到内存”以及“内存到内存”,本文着重说明使用,细节的东西,可以查看正点原子教程《DMA实验》。

      (点击关注上方公众号,回复“DMA”免费获取项目文件哦.还有千讲免费视频等你拿)

       DMA配置步骤简单来说:
      ① 初始化DMA时钟(具体用到DMA1还是DMA2,查一下参考手册就知道了)。
      ② 配置好DMA相关工作参数,通过HAL_DMA_Init函数进行初始化。
      ③ 通过外设提供的DMA函数接口进行数据的发送,例如串口1的DMA发送即使用HAL_UART_Transmit_DMA函数即可。
      ④ 开始DMA传输后,肯定需要查询当前当前的DMA传输状态了,这时候就可以使用__HAL_DMA_GET_FLAG函数。
      ⑤ 使用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循环中的操作,就如下图。

         

      内存到内存的传输方式比较简单,跟前面的逻辑差不多。通常来说,很少用到这种传输方式,用得比较多的是前面两种方式。

页: [1]
查看完整版本: DMA的三种传输方式,你GET到了吗?