当前位置: 首页 > article >正文

DMA直接内存访问,STM32实现高速数据传输使用配置

1、DMA运用场景

        随着智能化、信息化的不断推进,嵌入式设备的数据处理量也呈现指数级增加,因此对于巨大的数据量处理的情况时,必须采取其它的方式去替CPU减负,以保证嵌入式设备性能。例如SD卡存储器和音视频、网络高速通信等其它情景使用时,如果仅靠CPU去处理,将会消耗大量的系统资源,并且可能不能满足设备实时性的要求,对于嵌入式等一众资源受限的设备中,这是致命的。因此有必要采取一种特殊的方式,使得在执行大量数据处理过程中,CPU依然去执行正常的嵌入式系统任务。

        在嵌入式系统中,常用DMA去解决这一问题。DMA(Direct Memory Access),直接存储器访问)是一种特殊的硬件功能,用于数据传输而不需要CPU的干预DMA主要用于高速数据传输,可以提高系统性能和效率

        ①数据传输:DMA可以在外设和存储器之间进行高速数据传输,例如将数据从外设(如传感器、音频设备、网络接口等)直接传输到存储器中,或者从存储器中直接传输到外设中。这样可以减少CPU的负载,并提高数据传输的速度和效率。

        ②音视频处理:在多媒体应用中,DMA可以用于将音频、视频等数据从外设传输到存储器中进行处理,或者从存储器传输到外设进行播放。通过使用DMA,系统可以实现高质量的音视频数据传输和处理,同时降低对CPU的负担。

        ③存储器处理:DMA可以用于将存储器中的数据备份到外部存储设备(如硬盘、闪存等),或从外部存储设备中恢复数据到存储器中。这样可以提高数据备份和恢复的速度,并降低对CPU的负载。

        ④高速通信:DMA可以用于在嵌入式系统中实现高速通信,例如通过网络接口卡(如以太网)、串行接口(如UART、SPI)等传输数据。DMA可以在数据传输时,绕过CPU直接在外设和存储器之间进行传输,提高数据传输的速度和效率。

2、STM32 DMA基础

在STM32F4xx官方参考手册文档中,有对DMA控制器的讲解说明,内容十分的丰富,在这一部分中将其中较为核心基础的内容进行了梳理。

直接存储器访问 (DMA) 用于在外设与存储器之间以及存储器与存储器之间提供高速数据传输。可以在无需任何 CPU 操作的情况下通过 DMA 快速移动数据。这样节省的 CPU 资源可供其它操作使用。

DMA搬运的三种模式:

①、内存--->内存

②、内存--->外设

③、外设--->内存

DMA1:外设--->内存,内存--->外设

DMA2:外设--->内存,内存--->外设,内存--->内存

DMA2比DMA1多了一个内存到内存的处理功能,因此,如果需要实现内存到内存的DMA搬运模式,必须使用DMA2。

流:是数据传输的一条链路,每个DMA控制器有8条独立的数据流,每次传输的最大数据量为65535,如果数据单位为字的话,可以一次传输256KB。

通道:每个数据流有8个通道选择,每个通道对应不同的DMA请求。

同一个数据流只能使用一个通道,同一个DMA控制器可以使用多个数据流。

仲裁器:仲裁器为两个 AHB 主端口(存储器和外设端口)提供基于请求优先级的 8 个 DMA 数据流请求管理,并启动外设/存储器访问序列。

仲裁器优先级管理分为软件优先级管理和硬件优先级管理。多个数据流到来时,仲裁器会分为两个阶段进行仲裁,第一个阶段为软件优先级管理,在其编程时设置数据流的优先级第二个阶段为硬件阶段,由数据流的硬件编号决定。

FIFO:源和目标之间的一个数据中转站。FIFO模式下,可以将要传输的多个数据(或字节)累计存储在FIFO缓冲器中,然后在FIFO缓冲器中设置存储阈值,当到达阈值时,FIFO会自动把所有存储的数据一次性的发送到目标地址。

一个FIFO为4个字的大小,每个数据流有4字的FIFO,DMA配置为存储器---存储器模式时,FIFO由硬件开启,软件控制无效。且DMA配置为存储器到存储器模式时,不能设置为循环传输。

如图所示可知,DMA1、DMA2控制器挂载在AHB1总线下

由STM32F4xx官方参考手册可知,对于STM32F407系列,其嵌入式SRAM的起始映射的地址0x2000 0000开始。

由STM32F4xx官方参考手册可知,对于STM32F407系列,其嵌入式FLASH的起始映射的地址0x0800 0000开始。

3、STM32 编程实现DMA

在这一部分的讲解梳理中,将DMA配置为了内存--->内存模式,如果是要配置为内存--->外设外设--->内存,修改 DMA结构体的DMA_InitStructure.DMA_DIR参数即可其它参数的配置的思路大致相同。

//STM32F407中const修饰的全局存储到FLASH中
const uint32_t src_const_buf[32] = {
                                 0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
                                 0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20,
                                 0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30,
                                 0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40,
                                 0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50,
                                 0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60,
                                 0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70,
                                 0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80
                              };
uint32_t dst_buf[32] = {0};

因为DMA配置结构体中,需要填入外设/内存的地址信息,所以通过打印数组的内存地址,查看数据的存储位置,以确定外设/内存的地址。由上述STM32F4xx的官方参考手册嵌入式FLASH和SRAM部分说明和打印出来的内存地址可知,const修饰的全局变量被存储于嵌入式FLASH中。

DMA数据传输测试,DMA内存到内存模式代码实现效果如下图所示,通过打印的数据也可确定,DMA高速存储成功且数据无误。

实现DMA存储器到存储器,高速数据传输模式的参考代码Demo如下:

#include "stm32f4xx.h"
#include "stm32f4xx_dma.h"
#include <stdio.h>

//const修饰的全局存储到FLASH中
const uint32_t src_const_buf[32] = {
                                 0xAAAAAAAA,0xBBBBBBBB,0xCCCCCCCC,0xDDDDDDDD,
                                 0xEEEEEEEE,0xFFFFFFFF,0x10000000,0x11111111,
                                 0x22222222,0x33333333,0x44444444,0x55555555,
                                 0x66666666,0x77777777,0x88888888,0x99999999,
                                 0x10000000,0x10000000,0x10000000,0x10000000,
                                 0x11111111,0x11111111,0x11111111,0x11111111,
                                 0x22222222,0x22222222,0x22222222,0x22222222,
                                 0x33333333,0x33333333,0x33333333,0x33333333
                              };
uint32_t dst_buf[32] = {0};

void DMA_Config(void);
int8_t buf_cmp(uint32_t *pbuf1, uint32_t *pbuf2, int len);

int main(void)
{
    NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
    USART1_Init(115200);
    printf("starting...\r\n");
    printf("const addr:%X\r\n", (uint32_t)src_const_buf);
    printf("addr:%X\r\n", (uint32_t)dst_buf);     
    DMA_Config();
    while(DMA_GetCmdStatus(DMA2_Stream0) != DISABLE);    //传输完成后,DMA会进行复位
    if(buf_cmp((uint32_t *)src_const_buf, dst_buf, 32)==0)
    {
        
        printf("DMA传输完成,且数据无误...\r\n");
        printf("src:\r\n");
        for(int i = 0; i<32; i++)
        {
            printf("%0X\t", src_const_buf[i]);
        }
        printf("\r\n");
        printf("dst:\r\n");
        for(int i = 0; i<32; i++)
        {
            printf("%0X\t", dst_buf[i]);
        }
        printf("\r\n");
        return 0;
    }else{
        printf("DMA数据传输故障...\r\n");    
        return -1;
    }
    
}

void DMA_Config(void)
{
    DMA_InitTypeDef DMA_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    volatile uint32_t  Timeout = 10000;
        
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);//内存到内存的传送,使用DMA2
    DMA_DeInit(DMA2_Stream0);    //初始化DMA的寄存器到复位状态
        
    while(DMA_GetCmdStatus(DMA2_Stream0) != DISABLE);    //确保DMA复位完成

    
    //配置DMA流
    DMA_InitStructure.DMA_Channel = DMA_Channel_0;    //启用DMA通道0
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)src_const_buf; //FLASH中的数据地址
    DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)dst_buf;
//SRAM的数据地址
    DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToMemory;    //存储器到存储器模式
    DMA_InitStructure.DMA_BufferSize = (uint32_t)32;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;    //FLASH地址自增使能
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;            //SRAM地址自增
            //SRAM地址自增使能
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;         
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
    
    DMA_Init(DMA2_Stream0, &DMA_InitStructure);    //DMA初始化
    
    DMA_ITConfig(DMA2_Stream0, DMA_IT_TC, ENABLE);
    DMA_Cmd(DMA2_Stream0, ENABLE);        //DMA使能
        
    while(DMA_GetCmdStatus(DMA2_Stream0) != ENABLE && (Timeout-->0));
    
    if(Timeout == 0)
    {
        while(1);
    }
    
    NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}


int8_t buf_cmp(uint32_t *pbuf1, uint32_t *pbuf2, int len)
{
    int cnt = 0;
    for(int i=0; i<len; i++)
    {
        if(pbuf1[i] != pbuf2[i])
        {
            return -1;
        }
    }
    return 0;
}

void DMA2_Stream0_IRQHandler(void)
{    
    //DMA2通道0数据流传输完成中断 
    if(DMA_GetITStatus(DMA2_Stream0, DMA_IT_TCIF0) == SET)
    {
        //清除DMA传输完成中断标志位
        DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TCIF0);
        //在此可根据项目需求增加DMA处理完时的操作
    }

}


http://www.kler.cn/a/232413.html

相关文章:

  • MySQL-初识数据库
  • 【Linux网络编程】简单的UDP网络程序
  • 【eNSP】路由基础与路由来源——静态路由实验
  • MDK 5 各个历史版本下载地址
  • 122、java的LambdaQueryWapper的条件拼接实现数据sql中and (column1 =1 or column1 is null)
  • 如何在Mac上切换到JDK 17开发环境
  • 挖矿系列:细说Python、conda 和 pip 之间的关系
  • 配置ARM交叉编译工具的通用步骤
  • Redis篇之redis是单线程
  • ArcGIS Pro 按照字段进行融合或拆分
  • golang压缩与解压缩文件
  • Sqli靶场23-->30
  • Spring AI - 使用向量数据库实现检索式AI对话
  • Akamai 如何揪出微软 RPC 服务中的漏洞
  • 零基础学Python(9)— 流程控制语句(下)
  • Python爬虫 Beautiful Soup库详解#4
  • JavaScript valueOf() 方法详解
  • Oracle的权限
  • 在工业制造方面,如何更好地实现数字化转型?
  • 蓝桥杯刷题day08——完全日期
  • [力扣 Hot100]Day26 环形链表 II
  • TCP的连接和断开详解
  • 【LeetCode每日一题】525连续数组 303区域和检索(前缀和的基本概念和3个简单案例)
  • 使用Linux docker方式快速安装Plik并结合内网穿透实现公网访问
  • leetcode69 x 的平方根
  • Antd+React+react-resizable实现表格拖拽功能