STM32 DMA数据转运

2024-06-17 07:36
文章标签 数据 stm32 dma 转运

本文主要是介绍STM32 DMA数据转运,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

单片机学习!

目录

文章目录

前言

一、DMA配置步骤

二、详细步骤

2.1 RCC开启DMA的时钟

2.2 DMA参数初始化配置

2.2.1 外设站点的三个参数

2.2.2 存储器站点的三个参数

2.2.3 传输方向

2.2.4 传输计数器

2.2.5 是否使用自动重装

2.2.6 选择触发源

2.2.7 通道优先级

2.2.8 DMA_Init

2.3 开关控制

2.4 DMA初始化配置代码

三、DMA转运启动函数设计

3.1 传输计数器赋值

3.2 标志位查看/清除

3.3 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初始化配置以及一些配置代码的细节。

这篇关于STM32 DMA数据转运的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1068825

相关文章

使用MongoDB进行数据存储的操作流程

《使用MongoDB进行数据存储的操作流程》在现代应用开发中,数据存储是一个至关重要的部分,随着数据量的增大和复杂性的增加,传统的关系型数据库有时难以应对高并发和大数据量的处理需求,MongoDB作为... 目录什么是MongoDB?MongoDB的优势使用MongoDB进行数据存储1. 安装MongoDB

Python MySQL如何通过Binlog获取变更记录恢复数据

《PythonMySQL如何通过Binlog获取变更记录恢复数据》本文介绍了如何使用Python和pymysqlreplication库通过MySQL的二进制日志(Binlog)获取数据库的变更记录... 目录python mysql通过Binlog获取变更记录恢复数据1.安装pymysqlreplicat

Linux使用dd命令来复制和转换数据的操作方法

《Linux使用dd命令来复制和转换数据的操作方法》Linux中的dd命令是一个功能强大的数据复制和转换实用程序,它以较低级别运行,通常用于创建可启动的USB驱动器、克隆磁盘和生成随机数据等任务,本文... 目录简介功能和能力语法常用选项示例用法基础用法创建可启动www.chinasem.cn的 USB 驱动

Oracle数据库使用 listagg去重删除重复数据的方法汇总

《Oracle数据库使用listagg去重删除重复数据的方法汇总》文章介绍了在Oracle数据库中使用LISTAGG和XMLAGG函数进行字符串聚合并去重的方法,包括去重聚合、使用XML解析和CLO... 目录案例表第一种:使用wm_concat() + distinct去重聚合第二种:使用listagg,

Python实现将实体类列表数据导出到Excel文件

《Python实现将实体类列表数据导出到Excel文件》在数据处理和报告生成中,将实体类的列表数据导出到Excel文件是一项常见任务,Python提供了多种库来实现这一目标,下面就来跟随小编一起学习一... 目录一、环境准备二、定义实体类三、创建实体类列表四、将实体类列表转换为DataFrame五、导出Da

Python实现数据清洗的18种方法

《Python实现数据清洗的18种方法》本文主要介绍了Python实现数据清洗的18种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录1. 去除字符串两边空格2. 转换数据类型3. 大小写转换4. 移除列表中的重复元素5. 快速统

Python数据处理之导入导出Excel数据方式

《Python数据处理之导入导出Excel数据方式》Python是Excel数据处理的绝佳工具,通过Pandas和Openpyxl等库可以实现数据的导入、导出和自动化处理,从基础的数据读取和清洗到复杂... 目录python导入导出Excel数据开启数据之旅:为什么Python是Excel数据处理的最佳拍档

在Pandas中进行数据重命名的方法示例

《在Pandas中进行数据重命名的方法示例》Pandas作为Python中最流行的数据处理库,提供了强大的数据操作功能,其中数据重命名是常见且基础的操作之一,本文将通过简洁明了的讲解和丰富的代码示例,... 目录一、引言二、Pandas rename方法简介三、列名重命名3.1 使用字典进行列名重命名3.编

Python使用Pandas库将Excel数据叠加生成新DataFrame的操作指南

《Python使用Pandas库将Excel数据叠加生成新DataFrame的操作指南》在日常数据处理工作中,我们经常需要将不同Excel文档中的数据整合到一个新的DataFrame中,以便进行进一步... 目录一、准备工作二、读取Excel文件三、数据叠加四、处理重复数据(可选)五、保存新DataFram

使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)

《使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)》在现代软件开发中,处理JSON数据是一项非常常见的任务,无论是从API接口获取数据,还是将数据存储为JSON格式,解析... 目录1. 背景介绍1.1 jsON简介1.2 实际案例2. 准备工作2.1 环境搭建2.1.1 添加