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

相关文章

Python在二进制文件中进行数据搜索的实战指南

《Python在二进制文件中进行数据搜索的实战指南》在二进制文件中搜索特定数据是编程中常见的任务,尤其在日志分析、程序调试和二进制数据处理中尤为重要,下面我们就来看看如何使用Python实现这一功能吧... 目录简介1. 二进制文件搜索概述2. python二进制模式文件读取(rb)2.1 二进制模式与文本

C#实现将XML数据自动化地写入Excel文件

《C#实现将XML数据自动化地写入Excel文件》在现代企业级应用中,数据处理与报表生成是核心环节,本文将深入探讨如何利用C#和一款优秀的库,将XML数据自动化地写入Excel文件,有需要的小伙伴可以... 目录理解XML数据结构与Excel的对应关系引入高效工具:使用Spire.XLS for .NETC

MySQL数据目录迁移的完整过程

《MySQL数据目录迁移的完整过程》文章详细介绍了将MySQL数据目录迁移到新硬盘的整个过程,包括新硬盘挂载、创建新的数据目录、迁移数据(推荐使用两遍rsync方案)、修改MySQL配置文件和重启验证... 目录1,新硬盘挂载(如果有的话)2,创建新的 mysql 数据目录3,迁移 MySQL 数据(推荐两

Python数据验证神器Pydantic库的使用和实践中的避坑指南

《Python数据验证神器Pydantic库的使用和实践中的避坑指南》Pydantic是一个用于数据验证和设置的库,可以显著简化API接口开发,文章通过一个实际案例,展示了Pydantic如何在生产环... 目录1️⃣ 崩溃时刻:当你的API接口又双叒崩了!2️⃣ 神兵天降:3行代码解决验证难题3️⃣ 深度

MySQL快速复制一张表的四种核心方法(包括表结构和数据)

《MySQL快速复制一张表的四种核心方法(包括表结构和数据)》本文详细介绍了四种复制MySQL表(结构+数据)的方法,并对每种方法进行了对比分析,适用于不同场景和数据量的复制需求,特别是针对超大表(1... 目录一、mysql 复制表(结构+数据)的 4 种核心方法(面试结构化回答)方法 1:CREATE

详解C++ 存储二进制数据容器的几种方法

《详解C++存储二进制数据容器的几种方法》本文主要介绍了详解C++存储二进制数据容器,包括std::vector、std::array、std::string、std::bitset和std::ve... 目录1.std::vector<uint8_t>(最常用)特点:适用场景:示例:2.std::arra

MySQL中的DELETE删除数据及注意事项

《MySQL中的DELETE删除数据及注意事项》MySQL的DELETE语句是数据库操作中不可或缺的一部分,通过合理使用索引、批量删除、避免全表删除、使用TRUNCATE、使用ORDERBY和LIMI... 目录1. 基本语法单表删除2. 高级用法使用子查询删除删除多表3. 性能优化策略使用索引批量删除避免

MySQL 数据库进阶之SQL 数据操作与子查询操作大全

《MySQL数据库进阶之SQL数据操作与子查询操作大全》本文详细介绍了SQL中的子查询、数据添加(INSERT)、数据修改(UPDATE)和数据删除(DELETE、TRUNCATE、DROP)操作... 目录一、子查询:嵌套在查询中的查询1.1 子查询的基本语法1.2 子查询的实战示例二、数据添加:INSE

Linux服务器数据盘移除并重新挂载的全过程

《Linux服务器数据盘移除并重新挂载的全过程》:本文主要介绍在Linux服务器上移除并重新挂载数据盘的整个过程,分为三大步:卸载文件系统、分离磁盘和重新挂载,每一步都有详细的步骤和注意事项,确保... 目录引言第一步:卸载文件系统第二步:分离磁盘第三步:重新挂载引言在 linux 服务器上移除并重新挂p

使用MyBatis TypeHandler实现数据加密与解密的具体方案

《使用MyBatisTypeHandler实现数据加密与解密的具体方案》在我们日常的开发工作中,经常会遇到一些敏感数据需要存储,比如用户的手机号、身份证号、银行卡号等,为了保障数据安全,我们通常会对... 目录1. 核心概念:什么是 TypeHandler?2. 实战场景3. 代码实现步骤步骤 1:定义 E