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

相关文章

SpringBoot分段处理List集合多线程批量插入数据方式

《SpringBoot分段处理List集合多线程批量插入数据方式》文章介绍如何处理大数据量List批量插入数据库的优化方案:通过拆分List并分配独立线程处理,结合Spring线程池与异步方法提升效率... 目录项目场景解决方案1.实体类2.Mapper3.spring容器注入线程池bejsan对象4.创建

PHP轻松处理千万行数据的方法详解

《PHP轻松处理千万行数据的方法详解》说到处理大数据集,PHP通常不是第一个想到的语言,但如果你曾经需要处理数百万行数据而不让服务器崩溃或内存耗尽,你就会知道PHP用对了工具有多强大,下面小编就... 目录问题的本质php 中的数据流处理:为什么必不可少生成器:内存高效的迭代方式流量控制:避免系统过载一次性

C#实现千万数据秒级导入的代码

《C#实现千万数据秒级导入的代码》在实际开发中excel导入很常见,现代社会中很容易遇到大数据处理业务,所以本文我就给大家分享一下千万数据秒级导入怎么实现,文中有详细的代码示例供大家参考,需要的朋友可... 目录前言一、数据存储二、处理逻辑优化前代码处理逻辑优化后的代码总结前言在实际开发中excel导入很

MyBatis-plus处理存储json数据过程

《MyBatis-plus处理存储json数据过程》文章介绍MyBatis-Plus3.4.21处理对象与集合的差异:对象可用内置Handler配合autoResultMap,集合需自定义处理器继承F... 目录1、如果是对象2、如果需要转换的是List集合总结对象和集合分两种情况处理,目前我用的MP的版本

GSON框架下将百度天气JSON数据转JavaBean

《GSON框架下将百度天气JSON数据转JavaBean》这篇文章主要为大家详细介绍了如何在GSON框架下实现将百度天气JSON数据转JavaBean,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录前言一、百度天气jsON1、请求参数2、返回参数3、属性映射二、GSON属性映射实战1、类对象映

C# LiteDB处理时间序列数据的高性能解决方案

《C#LiteDB处理时间序列数据的高性能解决方案》LiteDB作为.NET生态下的轻量级嵌入式NoSQL数据库,一直是时间序列处理的优选方案,本文将为大家大家简单介绍一下LiteDB处理时间序列数... 目录为什么选择LiteDB处理时间序列数据第一章:LiteDB时间序列数据模型设计1.1 核心设计原则

Java+AI驱动实现PDF文件数据提取与解析

《Java+AI驱动实现PDF文件数据提取与解析》本文将和大家分享一套基于AI的体检报告智能评估方案,详细介绍从PDF上传、内容提取到AI分析、数据存储的全流程自动化实现方法,感兴趣的可以了解下... 目录一、核心流程:从上传到评估的完整链路二、第一步:解析 PDF,提取体检报告内容1. 引入依赖2. 封装

MySQL中查询和展示LONGBLOB类型数据的技巧总结

《MySQL中查询和展示LONGBLOB类型数据的技巧总结》在MySQL中LONGBLOB是一种二进制大对象(BLOB)数据类型,用于存储大量的二进制数据,:本文主要介绍MySQL中查询和展示LO... 目录前言1. 查询 LONGBLOB 数据的大小2. 查询并展示 LONGBLOB 数据2.1 转换为十

使用SpringBoot+InfluxDB实现高效数据存储与查询

《使用SpringBoot+InfluxDB实现高效数据存储与查询》InfluxDB是一个开源的时间序列数据库,特别适合处理带有时间戳的监控数据、指标数据等,下面详细介绍如何在SpringBoot项目... 目录1、项目介绍2、 InfluxDB 介绍3、Spring Boot 配置 InfluxDB4、I

Java整合Protocol Buffers实现高效数据序列化实践

《Java整合ProtocolBuffers实现高效数据序列化实践》ProtocolBuffers是Google开发的一种语言中立、平台中立、可扩展的结构化数据序列化机制,类似于XML但更小、更快... 目录一、Protocol Buffers简介1.1 什么是Protocol Buffers1.2 Pro