STM32 DMA数据转运

单片机学习!


前言

        本文介绍了DMA初始化配置、DMA数据转运的基础内容。


一、DMA配置步骤

初始化步骤:

第一步,RCC开启DMA的时钟。

第二步,参数初始化配置。直接调用DMA_Init,初始化各个参数,包括外设和存储器站点的起始地址、数据宽度、地址是否自增、方向、传输计数器、是否需要自动重装、选择触发源、通道优先级。这些所有的参数,通过一个结构体,就可以配置好了。

第三步,开关控制,调用DMA_Cmd函数,给指定的通道使能,就完成了。

如果选择的是硬件触发,需要在对应的外设调用一下XXX_DMACmd函数,开启一下触发信号的输出;

如果需要DMA的中断,那就调用DMA_ITConfig,开启中断输出。再在NVIC里配置相应的中断通道,然后写中断函数就可以了。

最后,在运行的过程中,如果转运完成,传输计数器清0了,这时再想给传输计数器赋值的话,就DMA失能 -> 写传输计数器 -> DMA使能,这样就行了。

二、详细步骤

2.1 RCC开启DMA的时钟

        第一步,RCC开启DMA的时钟。

代码示例:

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);

        DMA是AHB总线的设备,所以要用AHB开启时钟的函数。

        第一个参数在函数定义中的解释是:

对于互联型设备,参数可以是下面这些值的组合(互联型是STM32F105/107的型号)

  • RCC_AHBPeriph_DMA1
  • RCC_AHBPeriph_DMA2
  • RCC_AHBPeriph_SRAM
  • RCC_AHBPeriph_FLITF
  • RCC_AHBPeriph_CRC
  • RCC_AHBPeriph_OTG_FS    
  • RCC_AHBPeriph_ETH_MAC   
  • RCC_AHBPeriph_ETH_MAC_Tx
  • RCC_AHBPeriph_ETH_MAC_Rx
     

对于其他设备,参数可以是这下面的组合(F103的在其它设备下面的参数表里选)

  • RCC_AHBPeriph_DMA1
  • RCC_AHBPeriph_DMA2
  • RCC_AHBPeriph_SRAM
  • RCC_AHBPeriph_FLITF
  • RCC_AHBPeriph_CRC
  • RCC_AHBPeriph_FSMC
  • RCC_AHBPeriph_SDIO

        这里以STM32F103的芯片来举例,选择RCC_AHBPeriph_DMA1参数。

        第二个参数填ENABLE,开启DMA1的时钟。

2.2 DMA参数初始化配置

        第二步,参数初始化配置。直接调用DMA_Init,初始化各个参数。

        参数包括外设和存储器站点的起始地址、数据宽度、地址是否自增、方向、传输计数器、是否需要自动重装、选择触发源、通道优先级。

        这些所有的参数,通过一个结构体,就可以配置好了。

代码示例:

    DMA_InitTypeDef DMA_InitStructure;
    DMA_InitStructure.DMA_PeripheralBaseAddr=AddrA;//起始地址
    DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;//数据宽度
    DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Enable;//是否自增
    DMA_InitStructure.DMA_MemoryBaseAddr=AddrB;//起始地址
    DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;//数据宽度
    DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;//是否自增
    DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;//传输方向
    DMA_InitStructure.DMA_BufferSize=Size;//传输计数器
    DMA_InitStructure.DMA_Mode=DMA_Mode_Normal;//否使用自动重装
    DMA_InitStructure.DMA_M2M=DMA_M2M_Enable;//选择硬件触发还是软件触发
    DMA_InitStructure.DMA_Priority=DMA_Priority_Medium;//优先级
    DMA_Init(DMA1_Channel1,&DMA_InitStructure);

2.2.1 外设站点的三个参数

外设站点的三个参数:

  1. 起始地址
  2. 数据宽度
  3. 地址是否自增

1. 起始地址
        DMA_PeripheralBaseAddr 起始地址,外设站点的基地址。这在里要写一个32位的地址,比如 0x20000000 这样的地址。对于SRAM的数组,他的地址是编译器分配的,并不是固定的。所以一般不会写绝对地址,而是通过数组名来获取地址。

        在这里就把这个地址提取成整个初始化配置DMA函数的参数,这样在进行初始化配置的时候,想转运哪个数组就把哪个数组的地址传进来就行了。所以这里可以给整个初始化配置DMA函数传入 uint32_t  大小的参数AddrA,AddrA也就是外设的起始地址。然后给结构体DMA_PeripheralBaseAddr 外设起始地址参数这里放AddrA。

示例:

void MyDMA_Init(uint32_t AddrA)
{
    DMA_InitStructure.DMA_PeripheralBaseAddr=AddrA;//起始地址
    ......
}

这样外设站点的地址就完成了。

2. 数据宽度
        DMA_PeripheralDataSize数据宽度,函数定义中介绍是,指定数据宽度的参数可以是以下值:

  • DMA_PeripheralDataSize_Byte            Byte,字节,就是uint8_t ; 
  • DMA_PeripheralDataSize_HalfWord    HalfWord,半字,就是uint16_t ;
  • DMA_PeripheralDataSize_Word           Word,字,就是uint32_t 。

        这里需要以字节的方式传输,所以就填入DMA_PeripheralDataSize_Byte。

3. 地址是否自增
        DMA_PeripheralInc 地址是否自增。函数定义中解释是,指定外设地址是自增或者不是。

参数取值:

  • DMA_PeripheralInc_Enable  自增
  • DMA_PeripheralInc_Disable   不自增

        数组之间的转运,地址需要自增,所以这里选择DMA_PeripheralInc_Enable。


这样外设站点的参数就配置好了:

    DMA_InitStructure.DMA_PeripheralBaseAddr=AddrA;//起始地址
    DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;//数据宽度
    DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Enable;//是否自增

2.2.2 存储器站点的三个参数

存储器站点的三个参数:

  1. 起始地址
  2. 数据宽度
  3. 地址是否自增

1. 起始地址

        DMA_MemoryBaseAddr 起始地址,存储器站点的基地址。和外设站点一样,也把这个地址提取成整个初始化配置DMA函数的参数。

        所以这里可以给整个初始化配置DMA函数传入 uint32_t  大小的参数AddrB。AddrB也就是存储器的起始地址。然后给结构体 DMA_MemoryBaseAddr 存储器起始地址参数这里放AddrB。

示例:

void MyDMA_Init(uint32_t AddrA,uint32_t AddrB)
{
    ......
    DMA_InitStructure.DMA_MemoryBaseAddr=AddrB;//起始地址
    ......

}

这样存储器站点的地址就完成了。

2. 数据宽度
        DMA_MemoryDataSize 数据宽度,和外设站点一样也选择Byte参数,以字节传输。

  • DMA_MemoryDataSize_Byte           Byte,字节,就是uint8_t ;
  • DMA_MemoryDataSize_HalfWord   HalfWord,半字,就是uint16_t ;
  • DMA_MemoryDataSize_Word          Word,字,就是uint32_t 。

3. 地址是否自增

        DMA_MemoryInc 地址是否自增,和外设站点一样也选择地址自增。

  • DMA_MemoryInc_Enable   自增
  • DMA_MemoryInc_Disable   不自增

这样存储器站点的参数就配置好了:

    DMA_InitStructure.DMA_MemoryBaseAddr=AddrB;//起始地址
    DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;//数据宽度
    DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;//是否自增

2.2.3 传输方向

        DMA_DIR 传输方向,函数定义解释是,指定外设站点是源端还是目的地。

  • DMA_DIR_PeripheralDST  外设站点作为DST,destination,目的地,外设站点作为目的地,其实就是传输方向是存储器站点到外设站点。  
  • DMA_DIR_PeripheralSRC  外设站点作为SRC,source,源头,外设站点作为源头,也就是传输方向是外设站点到存储器站点。

        函数设计将DataA放在外设站点,DataB放在存储器站点。传输方向就是外设站点到存储器站点,所以这里选择 DMA_DIR_PeripheralSRC参数,外设站点作为数据源。

代码示例:

    DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;//传输方向

2.2.4 传输计数器

        DMA_BufferSize 缓存区大小,其实就是传输计数器。

        DMA_BufferSize 在函数定义解释是,以数据单元指定缓存区大小,数据单元等于外设数据宽度或者存储器数据宽度,数据宽度取决于传输方向。

        以数据单元指定缓存区大小,就是说需要传送几个数据单元,这个数据单元等于传输源站点的DataSize,简单理解就是,DMA_BufferSize就是传输计数器,指定传输几次。

        可以查看DMA_Init函数的源码,DMA_BufferSize参数其实就是直接赋值给了传输计数器的寄存器,它的取值是0~65535.

        所以这里可以把DMA_BufferSize参数也提取到初始化配置DMA函数的参数里来,给整个初始化配置DMA函数传入 uint16_t  大小的参数Size,Size也就是传输计数器指定传输的次数,然后把Size给到结构体DMA_BufferSize参数这里。

示例:

void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t Size)
{
    ......
    DMA_InitStructure.DMA_BufferSize=Size;//传输计数器
    ......
}

这样传输次数就完成了。

2.2.5 是否使用自动重装

        DMA_Mode 传输模式,其实就是指定传输计数器是否使用自动重装。

        DMA_Mode 在函数定义中的解释是,指定操作方式有对应参数取值列表。

        配置 DMA_Mode 参数还有一个注意事项,函数定义中写的是循环模式,也就是自动重装,不能应用在存储器到存储器的情况下。也就是之前博文说的,自动重装和软件触发不能同时使用,如果同时使用,DMA就会连续触发,永远也不会停下来。

  • DMA_Mode_Circular  循环模式,就是传输计数器自动重装  
  • DMA_Mode_Normal  正常模式,就是传输计数器不自动重装,自减到0后停下来。

        这里转运数组是存储器到存储器的传输,转运一次停下来就行了。DMA_Mode 的配置选择正常模式DMA_Mode_Normal。

代码示例:

    DMA_InitStructure.DMA_Mode=DMA_Mode_Normal;//否使用自动重装

2.2.6 选择触发源

        DMA_M2M 选择是否存储到存储器,其实就是选择硬件触发还是软件触发。

        DMA_M2M 在函数定义中的解释是,DMA是否应用于存储器到存储器的转运模式,存储器到存储器的转运模式就是软件触发。

  • DMA_M2M_Enable    使用软件触发。
  • DMA_M2M_Disable   不使用软件触发,也就是使用硬件触发。

        这里转运数组,所以选择 DMA_M2M_Enable 使用软件触发。

代码示例:

    DMA_InitStructure.DMA_M2M=DMA_M2M_Enable;//选择触发源

2.2.7 通道优先级

        DMA_Priority 优先级,按照参数要求,给一个优先级。

        DMA_Priority 在函数定义中的解释是,指定通道的软件优先级。

  • DMA_Priority_VeryHigh    非常高  
  • DMA_Priority_High     高
  • DMA_Priority_Medium    中等   
  • DMA_Priority_Low     低

        如果有多个通道,可以指定一下优先级,确保紧急的转运有更高的优先级。这里只有一个通道,那优先级可以任意配置。

代码示例:

     DMA_InitStructure.DMA_Priority=DMA_Priority_Medium;//优先级

2.2.8 DMA_Init

DMA_Init 函数参数配置:

        第一个参数:DMAy_Channelx,y可以是1或2,用来选择是哪个DMA;对于DMA1,x可以是1~7,或者对于DMA2,x可以是1~5.x用来选择是哪一个通道。

        DMA_Init函数的第一个参数,既选择了是哪个DMA,也选择了是DMA的哪个通道。

        DMAy_Channelx,这里y写为1,选择DMA1;x选择通道,这里因为是存储器到存储器的转运,用的是软件触发,所以通道可以任意选择。这里x给1,通道1.

        第二个参数的位置放 DMA_InitStructure 结构体的地址,这样就是把结构体指定的参数,配置到DMA1的通道1里面去。

代码示例:

DMA_Init(DMA1_Channel1,&DMA_InitStructure);


        以上DMA的参数就配置完成了。参数虽然比较多,但是对照框图来理解的话,就比较清晰。

        到目前为止,DMA还不能工作。

        DMA转运有三个条件:

  • 第一个条件,传输计数器大于0;
  • 第二个条件,触发源有触发信号;
  • 第三个条件,DMA使能。

三个条件缺一不可,

  1. 目前如果传一个大于0的数给Size的话,第一个条件满足。
  2. 触发源为软件触发,所以一直都有触发信号,第二个条件满足。
  3. 最后一个条件,DMA还没有使能,第三个条件不满足。

        所以到目前为止DMA还不会工作。如果想在初始化之后就立刻工作的话,可以在最后加上DMA_Cmd函数。

2.3 开关控制

        第三步,开关控制,调用DMA_Cmd函数,给指定的通道使能,就完成了。    

代码示例:

    DMA_Cmd(DMA1_Channel1,ENABLE);

        使能DMA之后,三个条件满足,DMA就会进行数据转运了。转运一次,传输计数器自减一次,当传输计数器减到0之后,转运完成。转运完成的同时第一个条件就不满足了,转运停止。这样就完成了一次数组之间的数据转运。

2.4 DMA初始化配置代码

代码示例:


void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t Size)//函数初始化DMA
{

	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);

	DMA_InitTypeDef DMA_InitStructure;
	DMA_InitStructure.DMA_PeripheralBaseAddr=AddrA;//起始地址
	DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;//数据宽度
	DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Enable;//是否自增
	DMA_InitStructure.DMA_MemoryBaseAddr=AddrB;//起始地址
	DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;//数据宽度
	DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;//是否自增
	DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;//传输方向
	DMA_InitStructure.DMA_BufferSize=Size;//传输计数器
	DMA_InitStructure.DMA_Mode=DMA_Mode_Normal;//是否使用自动重装
	DMA_InitStructure.DMA_M2M=DMA_M2M_Enable;//选择触发源
	DMA_InitStructure.DMA_Priority=DMA_Priority_Medium;//优先级
	DMA_Init(DMA1_Channel1,&DMA_InitStructure);

	DMA_Cmd(DMA1_Channel1,ENABLE);
}

对应结构图:

三、DMA转运启动函数设计

        设计一个函数 MyDMA_Transfer,调用一次这个函数,就再启动一次DMA转运。

3.1 传输计数器赋值

        再启动一次DMA转运,就需要重新给传输计数器赋值。

重新给传输计数器赋值必须要

  1. 先给DMA失能;
  2. 然后给传输计数器赋值;
  3. 最后再给DMA使能。

代码示例:

void MyDMA_Transfer(void)
{
    DMA_Cmd(DMA1_Channel1,DISABLE);
    DMA_SetCurrDataCounter(DMA1_Channel1,MyDMA_Size);
    DMA_Cmd(DMA1_Channel1,ENABLE);

    ......

}

1. 调用DMA_Cmd函数,使DMA失能。

  • 第一个参数还是选择DMA1的通道1;
  • 第二个参数给DISABLE。

2. 然后就可以给传输计数器赋值了,调用DMA_SetCurrDataCounter函数。

  • 第一个参数,选择DMA和通道。
  • 第二个参数,指定要给传输计数器写入的值。

        传输计数器写入的值需要从DMA初始化配置的函数中获取一下Size参数。但是Size参数在MyDMA_Init初始化配置函数中,这里需要的传输计数器写入的值位于MyDMA_Transfer函数中,不能直接传递过来。

        解决办法:可以在代码块最前面定义一个全局变量MyDMA_Size,初始化的时候把Size往这个全局变量里也存一份,之后在这个函数块里,就可以使用全局变量的MyDMA_Size了。这样就可以重新给传输计数器赋值了。

示例:

uint16_t MyDMA_Size;

void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t Size)//函数初始化DMA
{
	MyDMA_Size = Size;

	......

	DMA_InitStructure.DMA_BufferSize=Size;//传输计数器
	
    ......
}

void MyDMA_Transfer(void)
{
    ......

    DMA_SetCurrDataCounter(DMA1_Channel1,MyDMA_Size);

    ......

}

3. 最后,再次调用DMA_Cmd函数,给DMA使能。

  • 第一个参数还是选择DMA1的通道1;
  • 第二个参数给ENABLE。

        完成以上三步,DMA传输的3个条件又重新满足了。DMA就会再次开始转运。

3.2 标志位查看/清除

        转运开始之后还需要做一个工作,就是等待转运完成,可以通过查看标志位来确定转运是否完成。

        因为转运也是要花一些时间的,等待转运完成调用 DMA_GetFlagStatus函数可以查看标志位。

        DMA_GetFlagStatus函数中总共四种标志位,以DMA1的通道1举例,其它所有的通道,都是这4种标志位。

  • DMA1_FLAG_GL1:全局标志位
  • DMA1_FLAG_TC1:转运完成标志位
  • DMA1_FLAG_HT1:转运过半标志位
  • DMA1_FLAG_TE1:转运错误标志位

代码示例:

    while(DMA_GetFlagStatus(DMA1_FLAG_TC1)==RESET);
    DMA_ClearFlag(DMA1_FLAG_TC1);

        这里需要检查DMA1通道1转换完成的标志位,所以选择DMA1_FLAG_TC1参数。

        转运完成之后,标志位会置1,所以需要加一个while循环,等待这个标志位==RESET,如果没有完成,就一直循环等待,这样就实现了等待转运完成的效果了。

        标志位置1之后,不要忘记清除标志位,这个标志位需要手动清除。调用 DMA_ClearFlag函数。

3.3 DMA转运启动函数代码

代码示例:

void MyDMA_Transfer(void)
{
    DMA_Cmd(DMA1_Channel1,DISABLE);
    DMA_SetCurrDataCounter(DMA1_Channel1,MyDMA_Size);
    DMA_Cmd(DMA1_Channel1,ENABLE);
    while(DMA_GetFlagStatus(DMA1_FLAG_TC1)==RESET);
    DMA_ClearFlag(DMA1_FLAG_TC1)
}


总结

        以上就是今天要讲的内容,本文仅仅简单介绍了DMA初始化配置以及一些配置代码的细节。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值