STM32F103——DMA

DMA的基本介绍


        DMA,全称Direct Memory Access,即直接存储器访问。

        DMA传输:在没有CPU的任何干预下,将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。

        我们知道CPU有转移数据、计算、控制程序转移等很多功能,系统运作的核心就是CPU,CPU无时不刻的在处理着大量的事务,比方说数据的复制和存储数据,如果我们把这部分的CPU资源拿出来,让CPU去处理其他的复杂计算事务,是不是能够更好的利用CPU的资源呢?因此:转移数据(尤其是转移大量数据)我们可以不需要CPU参与。比如希望外设A的数据拷贝到外设B,只要给两种外设提供一条数据通路,直接让数据由外设A拷贝外设B  或者  让数据由外设A搬运到存储器(FLASH/RAM)再从存储器(FLASH/RAM)搬运到外设B,这样的话,数据就不用经过CPU的处理,直接从外设A传递到外设B。

        它的作用就是解决大量数据转移过度消耗CPU资源的问题。有了DMA使CPU更专注于更加实用的操作–计算、控制等。


DMA传输参数


        我们知道,数据传输,首先需要的是1 数据的源地址、2 数据传输位置的目标地址 、3 传输宽度,4 传输多少字节,5 传输模式。DMA所需要的核心参数,便是这5个。

        当用户将参数设置好,主要涉及源地址、目标地址、传输数据量这三个,DMA控制器就会启动数据传输,当剩余传输数据量为0时 达到传输终点,结束DMA传输 ,当然,DMA 还有循环传输模式 当到达传输终点时会重新启动DMA传输。也就是说只要剩余传输数据量不是0,而且DMA是启动状态,那么就会发生数据传输。

        数据的源:我们通过串口将数据传输到MCU。我们先在栈中(即RAM)定义一个数组,然后将接收到的数据通过DMA搬运到数组(RAM)中,此时这个串口外设就是数据的源,即数据的来源。而这个数组就是目的数据区(这是外设到内存的传输)

       如果是一个数组的数据传输到另外一个数组,那么就使用DMA通道连接两个数组,将一个数组的数据通过DMA搬运到另外一个数组。那么第一个数组就是数据来源,另外一个数组就是目的数据区。(这是内存到内存的传输)

       目标数据区:FLASH/RAM 存储器中的地址。

        传输宽度:一次传输数据的字节数。分为  字节(8bit)、半字(16bit)、全字节(32bit)

  

注意:数据源和目标数据区设置的传输宽度必须保持一致(数据对齐)

         我们可以看到,如果我们设置数据源的传输宽度为8位(一个字节),存储器的传输宽度也设置为8位(一个字节)。那么数据源传输一个字节数据0xB0给存储器,存储器就存储一个字节数据0x B0。

        

         如果我们设置数据源的传输宽度为8位(一个字节),存储器的传输宽度却设置为16位(2个字节)。那么数据源传输一个字节数据0xB0给存储器,存储器就存储两个字节数据0x 00 B0。

        同理,如果我们设置数据源的传输宽度为8位(一个字节),存储器的传输宽度却设置为32位(4个字节)。那么数据源传输一个字节数据0xB0给存储器,存储器就存储四个字节数据0x 00 00 00 B0。

         如果我们设置数据源的传输宽度为16位(两个字节),存储器的传输宽度却设置为8位(一个字节)。那么数据源传输两个字节数据0x B1 B0给存储器,存储器就存储一个字节数据0x B0。

         如果我们设置数据源的传输宽度为16位(两个字节),存储器的传输宽度却设置为16位(两个字节)。那么数据源传输两个字节数据0x B1 B0给存储器,存储器就存储两个字节数据0x B1 B0。

         同理,可由上述推理得出。

所以,我们使用DMA传输数据的时候,要保证外设和内存的数据传输宽度对齐。

传输模式:

循环的缓冲器管理:就是循环传输模式,从数组的第一个数据搬运,直到搬运完最后一个数据(传输多少字节数据由我们自己设置)。又重新来搬运数组的第一个数据,直到搬运到最后一个数据,以此往复。

正常模式:从数组的第一个数据搬运,直到搬运完最后一个数据。,然后就不再传输数据了。

DMA传输方式


        DMA的作用就是实现数据的直接传输,而去掉了传统数据传输需要CPU寄存器参与的环节,主要涉及四种情况的数据传输,但本质上是一样的,都是从内存的某一区域传输到内存的另一区域(外设的数据寄存器本质上就是内存的一个存储单元)。四种情况的数据传输如下:

外设到内存       (如:UART的数据搬运到FLASH/RAM存储器)(注意:此时需要将DMA中“存储器到存储器”这项设置失能)
内存到外设        (如:FLASH/RAM存储器的数据搬运到UART)
内存到内存          (如:两个数组之间搬运数据或者两个变量之间搬运数据)
外设到外设     (注意:F103在这里不能实现一个外设的数据直接搬运到另一个外设(因为规格书中没有写  外设到外设,如下图),所以此时我们想要实现外设到外设,就让数据由外设A搬运到存储器(或者数组),然后再从存储器(或者数组)搬运到外设B)

  外设的数据到DMA

DMA的数据到FLSAH/RAM

各个通道的DMA1功能

DMA1有7个独立通道,不同的通道,其功能不一样。 

 

 比如通道1,它就只能搬运ADC1、TIM2_CH3、TIM4_CH1这个3个外设的数据。

配置DMA库函数

 

         设置外设,是作为数据的来源,还是作为数据传输的目的地

         设置DMA一共需要传输多少个数据(一个数据是多少字节,由传输数据宽度来决定)

         外设地址递增就是,发送完一个数据,外设地址自动增加,下一次发送数据时,发送的时增加后的地址中的数据。

         外设地址不变就是,一直发送的都是一个地址中的数据。

     

        内存地址递增就是,保存完一个数据,地址自动增加,下一次接收数据的时候,把数据保存到增加后的内存地址。

        内存地址不变就是,一直将数据保存到内存中的同一个地址中。

         设置外设的传输数据宽度,即一次传输多少位数据(字节 半字 全字)

         设置内存的传输数据宽度,即一次接收多少位数据(字节 半字 全字)

         当我们使用的是外设到内存的时候,我们需要将“内存到内存”这项设置为失能

 

【STM32】 DMA原理,步骤超细详解,一文看懂DMA_Z小旋的博客-CSDN博客_dma是什么意思DMA的基本介绍什么是DMA (DMA的基本定义)DMA,全称Direct Memory Access,即直接存储器访问。DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。我们知道CPU有转移数据、计算、控制程序转移等很多功能,系统运作的核心就是CPU,CPU无时不刻的在处理着大量的事务,但有些事情却没有那么重要,比方说数据的...https://blog.csdn.net/as480133937/article/details/104927922?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165594756316782389474485%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=165594756316782389474485&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-104927922-null-null.142%5Ev20%5Econtrol,157%5Ev15%5Enew_3&utm_term=DMA&spm=1018.2226.3001.4187


编程步骤

    #define data_size  30


    uint32_t SRC[data_size]={
        0x11111111,0x12111111,0x11211111,0x11114111,0x11111113,
        0x21111111,0x13111111,0x11311111,0x11115111,0x11111114,
        0x31111111,0x14111111,0x11411111,0x11116111,0x11111115,
        0x41111111,0x15111111,0x11511111,0x11117111,0x11111116,
        0x51111111,0x16111111,0x11611111,0x11118111,0x11111117,
        0x61111111,0x17111111,0x11711111,0x11119111,0x11111118
    };
    
    uint32_t DRC[data_size]={0};
    
    编程步骤:(内存到内存)(将SRC数组的数据通过DMA搬运到DRC数组中去)
        1,打开时钟---DMA1
        2,初始化DMA
            ----外设基地址----SRC(数组)
            ----内存基地址----DRC (数组)
            ----方向----外设作为数据来源 
            ----外设地址是否递增---是
            ----内存地址是否递增---是
            ----外设数据传输的宽度---全字
            ----内存数据传输的宽度---全字 
            ----传输的数目----30
            ----优先级---中等
            ----模式---正常模式
            ----存储器到存储器是否使能---是
        3,使能DMA1通道2
        
        4,验证
            等待传输完成
            清除标志位
            判断SRC和DRC中的数据是否一致

编写程序

方法一:(轮询方法)

void DMA1CH2_Config(uint32_t *src,uint32_t *drc,uint32_t size)
{
     DMA_InitTypeDef  DMA_InitStruct;
    // 1,打开时钟---DMA1
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);

    // 2,初始化DMA
    DMA_InitStruct.DMA_PeripheralBaseAddr    =(uint32_t )src;
    DMA_InitStruct.DMA_MemoryBaseAddr        =(uint32_t )drc;
    DMA_InitStruct.DMA_DIR                   =DMA_DIR_PeripheralSRC;
    DMA_InitStruct.DMA_PeripheralInc         =DMA_PeripheralInc_Enable;
    DMA_InitStruct.DMA_MemoryInc             =DMA_MemoryInc_Enable;
    DMA_InitStruct.DMA_PeripheralDataSize    =DMA_PeripheralDataSize_Word;
    DMA_InitStruct.DMA_MemoryDataSize        =DMA_MemoryDataSize_Word;
    DMA_InitStruct.DMA_BufferSize            =size;
    DMA_InitStruct.DMA_M2M                   =DMA_M2M_Enable;
    DMA_InitStruct.DMA_Mode                  =DMA_Mode_Normal;
    DMA_InitStruct.DMA_Priority              =DMA_Priority_Medium;
    DMA_Init(DMA1_Channel2,&DMA_InitStruct);

    // 3,使能DMA1通道2
    DMA_Cmd(DMA1_Channel2,ENABLE);

}


uint8_t SRC_DRC_compare(uint32_t *src,uint32_t *drc,uint32_t size)        //检查两个数组中的数据是否完全相同
{
    uint8_t ret=0;
    uint8_t i;
    for(i=0;i<size;i++){
        if(src[i] != drc[i]){
            ret=1;
            return ret;
        }
    }
    return ret;
}

#define data_size  30
uint32_t SRC[data_size]={
    0x11111111,0x12111111,0x11211111,0x11114111,0x11111113,
    0x21111111,0x13111111,0x11311111,0x11115111,0x11111114,
    0x31111111,0x14111111,0x11411111,0x11116111,0x11111115,
    0x41111111,0x15111111,0x11511111,0x11117111,0x11111116,
    0x51111111,0x16111111,0x11611111,0x11118111,0x11111117,
    0x61111111,0x17111111,0x11711111,0x11119111,0x11111118
};

uint32_t DRC[data_size]={0};
uint8_t ledflag=0;


int main(void)
{
   
    RCC_ConfigTo72M();//将系统时钟配置成72MHZ 
    Systick_Config(72);

    DMA1CH2_Config(SRC,DRC,data_size);        //一共传输30个数据
    while(SET!=DMA_GetFlagStatus(DMA1_FLAG_TC2))  { ; }        //DMA1通道2传输数据完成时,该标志位置1
    DMA_ClearFlag(DMA1_FLAG_TC2);        //清除DMA1通道2传输数据完成标志位
    if(0==SRC_DRC_compare(SRC,DRC,data_size)){        //传输数据完成,如果两个数组内容相当,则通过串口助手打印dma is ok!
        printf("dma is ok!\n");
    }

    while(1) {;}

}

方法二:(中断方法)

void DMA1CH2_Config(uint32_t *src,uint32_t *drc,uint32_t size)
{
     DMA_InitTypeDef  DMA_InitStruct;
     NVIC_InitTypeDef NVIC_InitStruct;
    // 1,打开时钟---DMA1
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);

    // 2,初始化DMA
    DMA_InitStruct.DMA_PeripheralBaseAddr    =(uint32_t )src;
    DMA_InitStruct.DMA_MemoryBaseAddr        =(uint32_t )drc;
    DMA_InitStruct.DMA_DIR                   =DMA_DIR_PeripheralSRC;
    DMA_InitStruct.DMA_PeripheralInc         =DMA_PeripheralInc_Enable;
    DMA_InitStruct.DMA_MemoryInc             =DMA_MemoryInc_Enable;
    DMA_InitStruct.DMA_PeripheralDataSize    =DMA_PeripheralDataSize_Word;
    DMA_InitStruct.DMA_MemoryDataSize        =DMA_MemoryDataSize_Word;
    DMA_InitStruct.DMA_BufferSize            =size;
    DMA_InitStruct.DMA_M2M                   =DMA_M2M_Enable;
    DMA_InitStruct.DMA_Mode                  =DMA_Mode_Normal;
    DMA_InitStruct.DMA_Priority              =DMA_Priority_Medium;
    DMA_Init(DMA1_Channel2,&DMA_InitStruct);

    //打开DMA中断
    DMA_ITConfig(DMA1_Channel2, DMA_IT_TC, ENABLE);
    
    //配置中断优先级
    NVIC_InitStruct.NVIC_IRQChannel =DMA1_Channel2_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelCmd =ENABLE;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority =0;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority        =3;
    NVIC_Init(&NVIC_InitStruct);
    
    // 3,使能DMA1通道2
    DMA_Cmd(DMA1_Channel2,ENABLE);

}


uint8_t SRC_DRC_compare(uint32_t *src,uint32_t *drc,uint32_t size)
{
    uint8_t ret=0;
    uint8_t i;
    for(i=0;i<size;i++){
        if(src[i]!=drc[i]){
            ret=1;
            return ret;
        }
    }
    return ret;
}

//DMA中断服务函数

extern uint8_t dmaflag;

void DMA1_Channel2_IRQHandler(void)
{
    if(SET==DMA_GetITStatus(DMA1_IT_TC2)){        /*当DMA传输数据完成后就会触发DMA中断,并且该中断标志位会置1。注意:除了中断的时候需要CPU,在整个DMA传输数据的过程中,CPU都不会去干预*/
        DMA_ClearITPendingBit(DMA1_IT_TC2);
        dmaflag=1;
    }
}

#define data_size 30


uint32_t SRC[data_size]={
    0x11111111,0x12111111,0x11211111,0x11114111,0x11111113,
    0x21111111,0x13111111,0x11311111,0x11115111,0x11111114,
    0x31111111,0x14111111,0x11411111,0x11116111,0x11111115,
    0x41111111,0x15111111,0x11511111,0x11117111,0x11111116,
    0x51111111,0x16111111,0x11611111,0x11118111,0x11111117,
    0x61111111,0x17111111,0x11711111,0x11119111,0x11111118
};

uint32_t DRC[data_size]={0};
uint8_t dmaflag=0;

int main(void)
{
 
    RCC_ConfigTo72M();//将系统时钟配置成72MHZ 
    Systick_Config(72);
    // 4,如果是程序的第一个中断,需要设置优先级分钟
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    DMA1CH2_Config(SRC,DRC,data_size);

    while(1){
        
        if(1== dmaflag){

            dmaflag = 0;
            if(0==SRC_DRC_compare(SRC,DRC,data_size)){
                printf("dma is ok!\n");
                
            }
        }
    }
    

}

        轮询的方法与中断的方法区别在于,轮询需要CPU一直循环的去检测标志位。而中断的方法不占用CPU资源。所以,我们常用中断的方法去使用DMA。

  • 6
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值