简介:

  DMA:Direct Memory Access,直接存储器访问。DMA传输数据从一个地址空间复制到另外一个地址空间。当CPU初始化这个传输动作,传输动作本身就是DMA控制器来实现和完成。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存区。这样的操作并没有让处理器的工作拖延,反而可以重新排程去处理其他的工作。

  DMA传输对高效的嵌入式系统算法和网络是很重要的,DMA的传输无需CPU直接控制传输数据的通路,能使CPU的效率大大提高,DMA是一个非常好的功能,它不仅减轻了CPU的负担还提高了数据传输速率。

  STM32最多有2个DMA控制器(DMA仅存在于大容量产品中),DMA挂载的时钟为AHB总线,其时钟为72Mhz,所以可以实现高速数据搬运。,DMA1有7个通道。DMA2有5个通道。每个通道管理一个或多个来自外设存储器访问的请求,还有一个仲裁起来协调各个DMA请求的优先权。

  

  STM32的DMA有以下一些特征:

  1. 每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过软件来配置。
  2. 在七个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),假如在相等优先权时由硬件决定(请求 0 优先于请求 1,依此类推)
  3. 独立的源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。
  4. 支持循环的缓冲器管理。
  5. 每个通道都有 3 个事件标志(DMA  半传输,DMA 传输完成和 DMA 传输出错),这 3 个事件标志逻辑或成为一个单独的中断请求。
  6. 存储器和存储器间的传输
  7. 外设和存储器,存储器和外设的传输
  8. 闪存、SRAM、外设的 SRAM、APB1 APB2 和 AHB 外设均可作为访问的源和目标。
  9. 可编程的数据传输数目:最大为 65536

库函数下DMA1通道4的配置步骤:

1.使能 DMA 时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);  //使能 DMA 时钟
2.初始化 DMA 通道 4 参数  

  DMA 通道配置参数种类比较繁多,包括内存地址,外设地址,传输数据长度,数据宽度,通道优先级等等。这些参数的配置在库函数中都是在函数 DMA_Init 中完成,下面我们看看函数定义:

void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct)

  函数的第一个参数是指定初始化的 DMA 通道号;第二个参数,跟其他外设一样,同样是通过初始化结构体成员变量值来达到初始化的目的,下面我们来看看 DMA_InitTypeDef 结构体的定义:

   typedef struct
{
uint32_t DMA_PeripheralBaseAddr;
//用来设置DMA的传输外设基地址,比如要进行串口DMA传输,那么外设基地址为串口接受发送数据存储器USART1->DR
uint32_t DMA_MemoryBaseAddr;
//为内存基地址,也就是我们存放DMA传输数据的内存地址
uint32_t DMA_DIR;
/*设置数据传输方向,决定是从外设读取数据到内存还送从内存读取数据发送到外设,也就是外设是源地还是目的地,
这里我们设置为从内存读取数据发送到串口,所以外设自然就是目的地了,所以选择值为 DMA_DIR_PeripheralDST。*/
uint32_t DMA_BufferSize; //设置一次传输数据量的大小
uint32_t DMA_PeripheralInc;
/*设置传输数据的时候外设地址是不变还是递增。如果设置为递增,那么下一次传输的时候地址加 1,
这里因为我们是一直往固定外设地址&USART1->DR发送数据,所以地址不递增,值为 DMA_PeripheralInc_Disable;*/
uint32_t DMA_MemoryInc;
/*设置传输数据时候内存地址是否递增。 这个参数 和DMA_PeripheralInc 意思接近,
只不过针对的是内存。这里我们的场景是将内存中连续存储单元的数据发送到串口,
毫无疑问内存地址是需要递增的,所以值为 DMA_MemoryInc_Enable。*/
uint32_t DMA_PeripheralDataSize;
/*用来设置外设的数据长度是为字节传输(8bits),半字传输(16bits)还是字传输(32bits),
这里我们是8位字节传输,所以值设置为DMA_PeripheralDataSize_Byte。*/
uint32_t DMA_MemoryDataSize;
/*是用来设置内存的数据长度,和第七个参数意思接近,这里我们同样设置为字节传输 DMA_MemoryDataSize_Byte。*/
uint32_t DMA_Mode;
/*用来设置 DMA 模式是否循环采集,也就是说,比如我们要从内存中采集64个字节发送到串口,如果设置为重复采集,
那么它会在 64 个字节采集完成之后继续从内存的第一个地址采集,如此循环。这里我们设置为一次连续采集完成之后不循环。
所以设置值为 DMA_Mode_Normal。在我们下面的实验中,如果设置此参数为循环采集,那么你会看到串口不停的打印数据,
不会中断,大家在实验中可以修改这个参数测试一下。*/
uint32_t DMA_Priority;
/*设置 DMA 通道的优先级,有低,中,高,超高三种模式,这里我们设置优先级别为中级,
所以值为 DMA_Priority_Medium。如果要开启多个通道,那么这个值就非常有意义。*/
uint32_t DMA_M2M;
/*设置是否是存储器到存储器模式传输,这里我们选择DMA_M2M_Disable。*/
}DMA_InitTypeDef;

  实例代码:要配置的有DMA传输通道选择,传输的成员和方向、普通模式还是循环模式等等

void DMA_Configuration(void)
{
DMA_InitTypeDef DMA_InitStructure;
DMA_DeInit(DMA1_Channel4);//串口1的DMA传输通道是通道4
DMA_InitStructure.DMA_PeripheralBaseAddr = &USART1->DR; //DMA 外设 ADC 基地址
DMA_InitStructure.DMA_MemoryBaseAddr = cmar; //DMA 内存基地址 ,DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //从内存读取发送到外设
DMA_InitStructure.DMA_BufferSize = 64; //DMA 通道的 DMA 缓存的大小 DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;//传输大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //8 位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 8 位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常缓存模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA 通道 x 拥有中优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //非内存到内存传输
DMA_Init(DMA_CHx, &DMA_InitStructure); //根据指定的参数初始化
} 

3.使能DMA发送 

  进行 DMA 配置之后,我们就要开启串口的 DMA 发送功能,使用的函数是: 

USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);

  如果是要使能串口 DMA 接受,那么第二个参数修改为 USART_DMAReq_Rx 即可。

4.使能 DMA1 通道 4,启动传输

  使能串口 DMA 发送之后,我们接着就要使能 DMA 传输通道:

DMA_Cmd(DMA_CHx, ENABLE);

  通过以上 3 步设置,我们就可以启动一次 USART1 的 DMA 传输了

5.查询 DMA 传输状态

  在 DMA 传输过程中,我们要查询 DMA 传输通道的状态,使用的函数是:

FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG)

  比如我们要查询 DMA 通道 4 传输是否完成,方法是:

DMA_GetFlagStatus(DMA2_FLAG_TC4);

  这里还有一个比较重要的函数就是获取当前剩余数据量大小的函数:

uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx)

  比如我们要获取 DMA 通道 4 还有多少个数据没有传输,方法是:

DMA_GetCurrDataCounter(DMA1_Channel4); 

总例程:

DMA_InitTypeDef DMA_InitStructure;

u16 DMA1_MEM_LEN;//保存DMA每次数据传送的长度
//DMA1的各通道配置
//这里的传输形式是固定的,这点要根据不同的情况来修改
//从存储器->外设模式/8位数据宽度/存储器增量模式
//DMA_CHx:DMA通道CHx
//cpar:外设地址
//cmar:存储器地址
//cndtr:数据传输量 void MYDMA_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA传输
DMA_DeInit(DMA_CHx); //将DMA的通道1寄存器重设为缺省值
DMA1_MEM_LEN=cndtr; DMA_InitStructure.DMA_PeripheralBaseAddr = cpar; //DMA外设ADC基地址
DMA_InitStructure.DMA_MemoryBaseAddr = cmar; //DMA内存基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //数据传输方向,从内存读取发送到外设
DMA_InitStructure.DMA_BufferSize = cndtr; //DMA通道的DMA缓存的大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常缓存模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
DMA_Init(DMA_CHx, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数初始化DMA的通道USART1_Tx_DMA_Channel所标识的寄存器
} //开启一次DMA传输
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx)
{
DMA_Cmd(DMA_CHx, DISABLE ); //关闭USART1 TX DMA1 所指示的通道
DMA_SetCurrDataCounter(DMA1_Channel4,DMA1_MEM_LEN);//DMA通道的DMA缓存的大小
DMA_Cmd(DMA_CHx, ENABLE); //使能USART1 TX DMA1 所指示的通道
} u8 SendBuff[5200];
MYDMA_Config(DMA1_Channel4,(u32)&USART1->DR,(u32)SendBuff,5168);//DMA1通道4,外设为串口1,存储器为SendBuff,长度5168. USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA发送 MYDMA_Enable(DMA1_Channel4);//开始一次DMA传输!
if(DMA_GetFlagStatus(DMA1_FLAG_TC4)!=RESET) //判断通道4传输完成
{
DMA_ClearFlag(DMA1_FLAG_TC4);//清除通道4传输完成标志
break;
}
pro=DMA_GetCurrDataCounter(DMA1_Channel4);

  补充:

1、DMA的配置
要配置的有DMA传输通道选择,传输的成员和方向、普通模式还是循环模式等等。
void DMA_Configuration(void)
{
    DMA_InitTypeDef DMA_InitStructure;
    //DMA设置:
    //设置DMA源:内存地址&串口数据寄存器地址
    //方向:内存-->外设
    //每次传输位:8bit
    //传输大小DMA_BufferSize=SENDBUFF_SIZE
    //地址自增模式:外设地址不增,内存地址自增1
    //DMA模式:一次传输,非循环
    //优先级:中
    DMA_DeInit(DMA1_Channel4);//串口1的DMA传输通道是通道4
    DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base;
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//外设作为DMA的目的端
    DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;//传输大小
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址不增加
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//内存地址自增1
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    //DMA_Mode_Normal(只传送一次), DMA_Mode_Circular (不停地传送)
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//(DMA传送优先级为中等)
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel4, &DMA_InitStructure);
}
注:
1、传输通道:通过查表,串口1的发送对应的是DMA的通道4,所以此处选择通道4.
2、DMA传输方式:
(1) DMA_Mode_Normal,正常模式,当一次DMA数据传输完后,停止DMA传送,对于上例而言,就是DMA_PeripheralDataSize_Byte个字节的传送完成后,就停止传送。
(2) DMA_Mode_Circular
循环模式,当传输完一次后,重新接着传送,永不停息。
2、外设的DMA方式设置
将串口1设置成DMA模式:
每一个外设都有一个类似以下的一个DMA调用函数:xxx_DMACmd();
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);//发送就为USART_DMAReq_Tx;读取就为USART_DMAReq_Rx
3、待传输数据的定义和初始化
#define SENDBUFF_SIZE   10240
vu8 SendBuff[SENDBUFF_SIZE];
    for(i=0;i<sendbuff_size;i++)
    {
        SendBuff[i] = i%10+'0';
    }
4、开始DMA传输(使能对应的DMA通道)
DMA_Cmd(DMA1_Channel4, ENABLE);
5、DMA传输的完成
 while(DMA_GetFlagStatus(DMA1_FLAG_TC4) == RESET)
 {
       LED_1_REV;      //LED翻转
       Delay();        //浪费时间
 }
当传输完成后,就会跳出上面的死循环。
当然,使用串口作为外设的时候,还需要对串口进行初始化。

STM32(11)——DMA的更多相关文章

  1. Netruon 理解(11):使用 NAT 将 Linux network namespace 连接外网

    学习 Neutron 系列文章: (1)Neutron 所实现的虚拟化网络 (2)Neutron OpenvSwitch + VLAN 虚拟网络 (3)Neutron OpenvSwitch + GR ...

  2. 基于MVC4+EasyUI的Web开发框架经验总结(11)--使用Bundles处理简化页面代码

    在Web开发的时候,我们很多时候,需要引用很多CSS文件.JS文件,随着使用更多的插件或者独立样式文件,可能我们的Web界面代码会越来越臃肿,看起来也很累赘,在MVC里面提供了一个Bundle的对象, ...

  3. Python的平凡之路(11)

    一. rabbitmq 1 进程Queue:  父进程与子进程进行交互,或者同属于同一父进程下多个子进程进行交互 2 队列通信:   send1.py #!/usr/bin/env python#Au ...

  4. spring 第一篇(1-1):让java开发变得更简单(下)转

    spring 第一篇(1-1):让java开发变得更简单(下) 这个波主虽然只发了几篇,但是写的很好 上面一篇文章写的很好,其中提及到了Spring的jdbcTemplate,templet方式我之前 ...

  5. Web 在线文件管理器学习笔记与总结(11)获取文件夹信息 (12)返回上一级操作

    (11)获取文件夹信息 文件夹没有修改操作. index.php: <?php require 'dir.func.php'; require 'file.func.php'; require ...

  6. MS CRM 2011的自定义和开发(11)——插件(plugin)开发(三)

    http://www.cnblogs.com/StoneGarden/archive/2012/02/06/2340661.html MS CRM 2011的自定义和开发(11)——插件(plugin ...

  7. MS CRM 2011的自定义和开发(11)——插件(plugin)开发(四)

    http://www.cnblogs.com/StoneGarden/archive/2012/02/08/2343294.html MS CRM 2011的自定义和开发(11)——插件(plugin ...

  8. MS CRM 2011的自定义和开发(11)——插件(plugin)开发(一)

    http://www.cnblogs.com/StoneGarden/archive/2012/02/02/2336147.html MS CRM 2011的自定义和开发(11)——插件(plugin ...

  9. MS CRM 2011的自定义和开发(11)——插件(plugin)开发(二)

    http://www.cnblogs.com/StoneGarden/archive/2012/02/06/2339490.html MS CRM 2011的自定义和开发(11)——插件(plugin ...

随机推荐

  1. Studying TCP's Congestion Window using NS

    Studying TCP's Congestion Window using NS How to obtain TCP's CWND value The most important value th ...

  2. M-wordL-图

    典型的需要使用图模型 将start 和 end 以及字典一同构建成图,然后探究从start到end的最短路径

  3. Python学习---远程执行命令

    原则:发送一个接受一个 原理:发送执行命令的大小给客户端,客户端根据接受的大小判断是否全部接收了服务器sendall()发送的全部 利用send发送的全部数据都是bytes类型的,需要进行字符编码的转 ...

  4. C/C++内存泄露检测

    以下测试基于的gcc版本: gcc (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4Copyright (C) 2013 Free Software Foundation, In ...

  5. Centos如何通过yum安装php7

      执行如下命令安装epel yum -y install epel-release   更换rpm源,请根据自己的centos版本选择相应的rpm源进行安装 Centos 5.X: rpm -Uvh ...

  6. Intellij idea用快捷键自动生成序列化id

    ntellij idea用快捷键自动生成序列化id 类继承了Serializable接口之后,使用alt+enter快捷键自动创建序列化id 进入setting→inspections→seriali ...

  7. zt 设计模式六大原则(3):依赖倒置原则

    下面说法对不对? 父类将算法(逻辑)封装起来,子类实现细节:这个就叫DIP(依赖倒置:Dependency Inversion Principles),模板模式就是这个原则的实现.如果在父类中加一个t ...

  8. jemter 使用if控制器,选择需要的内容

    背景:需要根据人员传入的变量,来选择运行的环境,调用不同的参数,进行拼接,使用到if控制器 取到的数据,调用的就是test1的数据

  9. 如何访问tomcat所在服务器的其他盘符的资源。

    <Host appBase="webapps" autoDeploy="true" name="localhost" unpackWA ...

  10. 理解JavaScript的this对象

    1.概述 this对象是在运行时基于函数的执行环境绑定的,this总是返回一个对象,简单说,就是返回属性或方法"当前"所在的对象.在全局函数中,this等于window,而当函数作 ...