第12课【DMA 直接数据访问】直接数据访问 效率 数据总线冲突 通道 仲裁器

news/2024/11/30 0:33:56/

目录

  • 简介
  • 功能框图
    • 请求
    • 通道
    • 仲裁器
  • 使用配置
    • 传输方向
    • 数据量
    • 传输模式
  • 实例分析
    • 存储器间传输
    • 存储器到外设

简介

DMA(Direct Memory Access 直接内存访问)指的是STM32中的一个外设。它可以在无需CPU介入的情况下,实现外设和存储器之间或存储器与存储器之间的数据传输,这里的存储器指的是SRAM或者是Flash。在进行一些大批量的,或者是周期性重复的数据转移工作时,通常都会使用到DMA,这使得CPU可以腾出时间完成其他更具意义的任务,从而提高处理效率,在这点上,DMA和GPU的存在意义类似,都是用于处理专门的需求或者数据而存在的

STM32的DMA的控制器包含了两个DMA:DMA1和DMA2,其中DMA1拥有7个通道(可以理解为数据传输通道),DMA2拥有5各通道。DMA2在大容量类型或互联网类型STM32产品上才会搭载

STM32上的DMA外设有以下特点:

  • 12个通道(DMA1有7个,DMA2有5个)都可独立配置
  • 低、中、高、最高四种通道优先级可选
  • SRAM,Flash,AHB/APB1/APB2总线上的基本所有外设都支持DMA

功能框图

STM32的DMA外设同其他外设一样,也是独立于Cortex内核的存在,其可以大致分为三部分:请求,通道,仲裁器,功能框图如下:

在这里插入图片描述

请求

其他外设如果需要使用DMA功能,需要向DMA发送请求,DMA收到请求后,会根据情况给发起请求的外设一个应答信号,只有当请求的外设收到应答信号时,才会启动DMA传输功能

通道

通道指代的是数据传输通道,DMA1有7条通道,DMA2有5条通道,12条通道都可以独立编程控制,并且每条通道对应了不同的外设,具体对应如下图:

  • DMA1通道对应外设:
    在这里插入图片描述
  • DMA2通道对应外设:
    在这里插入图片描述

可以看到,每个通道可以对应多个外设。但是同一时间,一个通道只能被一个外设使用,需要多个外设同时使用同一个通道时,开发者需要注意规划外设DMA通道时序,以确保通道带宽得到有效且正确的分配

DMA传输的实现机制以及拓展思考
DMA传输实际上还是需要同Cortex内核共享系统数据总线来实现的,这就意味着在DMA传输进行时,CPU依旧是需要等待的,既然需要等待那么如何提高访问效率呢。为此,STM开发者是这么设计的,当DMA外设和内核同时访问同一个设备时,DMA外设会暂停内核访问总线的若干个周期,同时总线总裁器执行循环调度,保证内核能至少获得总线一半的带宽,

仲裁器

类似于多个外设同时使用一个通道的场景,DMA传输在同一时刻只能对一个通道进行数据传输操作,当DMA控制器同时接收到来自多个通道的数据传输请求时,会通过优先级进行仲裁,优先级分为两种:软件优先级和硬件优先级

  • 软件优先级:可以通过DMA_CCR寄存器对单独某个通道进行优先级配置,分别有四种:低,中,高,最高。高优先级通道的数据传输请求会被优先处理
  • 硬件优先级:如果某两个通道的软件优先级配置一致,那么会根据通道固定的硬件优先级进行比较,硬件优先级数字越小,优先级越高

使用配置

DMA外设的数据传输配置,主要是需要对数据的元数据(即描述数据用的数据)进行配置:包括传输方向,数据量,传输模式

传输方向

DMA传输数据的方向分为三种:外设到存储器,存储器到外设,存储器到存储器。三种方向主要通过以下寄存器中的位来配置:

  • DMA_CCR寄存器的DIR位:配置DMA数据的传输方向,DIR位置1时,从存储器读取数据,写入到外设,即存储器->外设;DIR位清0时,从外设读取数据,写入到存储器中,即外设->存储器;当使用存储器到存储器模式时,还需要将寄存器中的MEM2MEM位置1。
  • DMA_CPARx(x = 1~7)寄存器:配置外设的地址
  • DMA_CMARx(x = 1…7)寄存器:配置存储器的地址

具体举例三种情况:

  • 外设到存储器:例如通过DMA外设将串口外设的数据传输到SRAM中,首先要配置DMA_CPARx
    寄存器中的外设地址,也就是串口外设的数据寄存器地址,之后再配置DMA_CMARx
    寄存器中的存储器地址,即代码中的临时缓存变量地址,临时变量会存储于SRAM中,最后将DMA_CCR寄存器中的DIR位清0,表示传输方向为:外设->存储器
  • 存储器到外设:与上一个例子类似,只不过传输方向需要改为存储器->外设,需要将DMA_CCR寄存器中的DIR位置1
  • 存储器到存储器:存储器到存储器同以上两个例子有些许不同,例如将Flash的数据传输到SRAM(Flash中的数据只读),那么可以将Flash的访问地址当作外设地址,配置到DMA_CPARx寄存器中;将临时变量的地址当作存储器地址,配置到DMA_CMARx寄存器中;而此模式下传输方向仅支持配置为Flash到SRAM,DMA_CCR寄存器的DIR位无需进行配置;同时需要将DMA_CCR寄存器中的MEM2MEM位置

数据量

数据量的配置包括了,数据量大小,单个数据的宽度,以及数据指针增量模式。主要通过以下寄存器中的位来配置:

  • DMA_CNDTRx(x = 1…7)寄存器:用于显示剩余待传输的数据的量。最小值为0,此时无论如何都不会发生数据传输,最大值为65535(无符号32位寄存器),最多一次能传输65536Bytes的数据。每传输1Byte数据,此寄存器会自减1。如果此寄存器的值自减到0,那么会根据是否有配置重载数据,进行寄存器值重新装载
  • DMA_CCRx(x = 1…7)寄存器的PSIZE位[1:0]/MSIZE位[1:0]:用于配置外设/存储器数据宽度。PSIZE位[1:0]配置外设数据宽度,MSIZE位[1:0]配置存储器数据宽度,可以是8/16/32位。但是要确保两者的数据宽度是一致的,才能进行数据传输
  • DMA_CCRx(x = 1…7)寄存器的PINC位/MINC位:用于配置数据传输时的数据指针增量模式,外设的地址指针由DMA_CCRx的PINC位配置,PINC位置1时,每完成一次数据传输,外设的数据指针会自增1,PINC位清0时,每完成一次数据传输,外设的数据指针保持不变,存储器的地址指针由MINC位配置,配置方法和PINC位类似。是否需要启用指针增量模式,由开发者来决定

传输模式

传输模式分为两种:单次传输和循环传输。顾名思义,单次传输进行一次传输,而循环传输会一直不间断的进行单次传输,每轮单次传输完成后,都会重新按照初始配置重新进行传输。传输模式由DMA_CCRx(x = 1…7)寄存器的CIRC位控制,CIRC位置1时,执行循环传输操作,CIRC位清0时,执行单次传输操作

对于两种传输的传输状态,包括传输过半,传输完成,传输出错都有相应的标志位去查询,同时也可以启用中断,通过中断去对特定指定状态执行对应操作

实例分析

以下内容基于STM32F103VET野火指南者开发板,分别实现两个功能:

  • (存储器间传输)通过DMA将Flash数据复制到SRAM中,对比两份数据,查看DMA传输是否成功
  • (存储器到外设)通过DMA将SRAM数据复制到串口外设的数据寄存器中,并循环发送到PC端,查看是否准确发送。同时主程序中运行LED闪烁实验,以验证DMA传输不影响CPU进行其他活动

会使用到开发板的LED灯模块以及USB串口模块(实际上是USART模块+CH340G芯片),原理图分别如下:

  • LED灯模块:
    在这里插入图片描述
  • CH340G芯片:
    在这里插入图片描述

首先,需要解析标准库提供的DMA初始化结构体,以及相关操作函数:

/** * @brief  DMA Init structure definition*/typedef struct
{uint32_t DMA_PeripheralBaseAddr; /*!< Specifies the peripheral base address for DMAy Channelx. */uint32_t DMA_MemoryBaseAddr;     /*!< Specifies the memory base address for DMAy Channelx. */uint32_t DMA_DIR;                /*!< Specifies if the peripheral is the source or destination.This parameter can be a value of @ref DMA_data_transfer_direction */uint32_t DMA_BufferSize;         /*!< Specifies the buffer size, in data unit, of the specified Channel. The data unit is equal to the configuration set in DMA_PeripheralDataSizeor DMA_MemoryDataSize members depending in the transfer direction. */uint32_t DMA_PeripheralInc;      /*!< Specifies whether the Peripheral address register is incremented or not.This parameter can be a value of @ref DMA_peripheral_incremented_mode */uint32_t DMA_MemoryInc;          /*!< Specifies whether the memory address register is incremented or not.This parameter can be a value of @ref DMA_memory_incremented_mode */uint32_t DMA_PeripheralDataSize; /*!< Specifies the Peripheral data width.This parameter can be a value of @ref DMA_peripheral_data_size */uint32_t DMA_MemoryDataSize;     /*!< Specifies the Memory data width.This parameter can be a value of @ref DMA_memory_data_size */uint32_t DMA_Mode;               /*!< Specifies the operation mode of the DMAy Channelx.This parameter can be a value of @ref DMA_circular_normal_mode.@note: The circular buffer mode cannot be used if the memory-to-memorydata transfer is configured on the selected Channel */uint32_t DMA_Priority;           /*!< Specifies the software priority for the DMAy Channelx.This parameter can be a value of @ref DMA_priority_level */uint32_t DMA_M2M;                /*!< Specifies if the DMAy Channelx will be used in memory-to-memory transfer.This parameter can be a value of @ref DMA_memory_to_memory */
}DMA_InitTypeDef;

其中结构体成员的含义分别如下:

  • DMA_PeripheralBaseAddr:外设地址
  • DMA_MemoryBaseAddr:存储器地址
  • DMA_DIR:数据传输方向。可以是外设->存储器,也可以是存储器->外设
  • DMA_BufferSize:传输数据量的大小。最小为0Byte,最大为65536Bytes
  • DMA_PeripheralInc:外设地址的增量模式。由开发者选择是否使用
  • DMA_MemoryInc:存储器地址的增量模式。由开发者选择是否使用
  • DMA_PeripheralDataSize:外设地址可以获取到的单次传输的数据大小。可以是8/16/32bits
  • DMA_MemoryDataSize:存储器地址可以获取到的单次传输的数据大小。可以是8/16/32bits
  • DMA_Mode:传输模式。分为单次传输和循环传输
  • DMA_Priority:通道优先级。分为低、中、高、最高四种
  • DMA_M2M:是否开启存储器到存储器模式。只有使用存储器间传输时会开启

存储器间传输

此功能的实现思路为:

  • 初始化Flash中的源数据,以及SRAM中的目标数据
  • 初始化LED灯用GPIO口
  • 初始化DMA传输参数
  • 启动DMA传输
  • 对比源数据与目标数据,将结果通过LED灯颜色展示出来

分别建立DMA相关板级支持文件bsp_dma.h,bsp_dma.c,内容分别如下:

  • bsp_dma.h:
#ifndef __BSP_DMA_H__
#define __BSP_DMA_H__#include <stdint.h>#define DMA_CHANNEL DMA1_Channel1
#define DMA_CLK     RCC_AHBPeriph_DMA1
#define DMA_FLAG_TC DMA1_FLAG_TC1#define DMA_BUFFER_SIZE 32extern const uint32_t aSRC_Const_Buffer[DMA_BUFFER_SIZE];
extern uint32_t aDST_Buffer[DMA_BUFFER_SIZE];void DMA_Config(void);
uint8_t BufferCompare(const uint32_t *pSRCBuffer, uint32_t *pDSTBuffer, uint16_t BufferLength);#endif
  • bsp_dma.c:
#include "bsp_dma.h"
#include "stm32f10x.h"
#include "stm32f10x_dma.h"
#include "stm32f10x_rcc.h"
#include <stdint.h>// The "const" keyword for a global variable will cause it to be storged in ROM(Flash)
const uint32_t aSRC_Const_Buffer[DMA_BUFFER_SIZE] = 
{0x01020304, 0x05060708, 0x090A0B0C, 0x0D0E0F10, 0x11120304, 0x05160718, 0x090A1B0C, 0x0D0E0F10, 0x21220304, 0x05260728, 0x090A2B0C, 0x0D0E0F20, 0x31320304, 0x05360738, 0x090A3B0C, 0x0D0E0F30, 0x41420304, 0x05460748, 0x090A4B0C, 0x0D0E0F40, 0x51520304, 0x05560758, 0x090A5B0C, 0x0D0E0F50, 0x61620304, 0x05660768, 0x090A6B0C, 0x0D0E0F60, 0x71720304, 0x05760778, 0x090A7B0C, 0x0D0E0F70
};// Without the "const" keyword, a global variable will be storged in SRAM
uint32_t aDST_Buffer[DMA_BUFFER_SIZE];void DMA_Config(void)
{// DMADMA_InitTypeDef DMA_InitStructure;// DMA Clk enableRCC_AHBPeriphClockCmd(DMA_CLK, ENABLE);// DMA param configDMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) aSRC_Const_Buffer;DMA_InitStructure.DMA_MemoryBaseAddr     = (uint32_t) aDST_Buffer;DMA_InitStructure.DMA_DIR                = DMA_DIR_PeripheralSRC;DMA_InitStructure.DMA_BufferSize         = DMA_BUFFER_SIZE;DMA_InitStructure.DMA_PeripheralInc      = DMA_PeripheralInc_Enable;DMA_InitStructure.DMA_MemoryInc          = DMA_MemoryInc_Enable;DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;DMA_InitStructure.DMA_MemoryDataSize     = DMA_MemoryDataSize_Byte;DMA_InitStructure.DMA_Mode               = DMA_Mode_Normal;DMA_InitStructure.DMA_Priority           = DMA_Priority_High;DMA_InitStructure.DMA_M2M                = DMA_M2M_Enable; // Enable M2MDMA_Init(DMA_CHANNEL, &DMA_InitStructure);// DMA enableDMA_Cmd(DMA_CHANNEL, ENABLE);
}uint8_t BufferCompare(const uint32_t *pSRCBuffer, uint32_t *pDSTBuffer, uint16_t BufferLength)
{for (int i=0; i<BufferLength; ++i){if (*(pSRCBuffer+i) == *(pDSTBuffer+i)){continue;}else {return 1;}}return 0;
}

通过板级支持包代码实现需求:

#include "userapp.h"
#include "bsp_dma.h"
#include "bsp_clkconfig.h"
#include "bsp_led.h"
#include "stm32f10x.h"
#include "stm32f10x_dma.h"
#include <stdint.h>int userapp(void)
{LED_GPIO_Init();Delay(3000000);    LED_PURPLE;DMA_Config();// Wait until DMA transfer complete while (DMA_GetFlagStatus(DMA_FLAG_TC) == RESET);Delay(3000000);    // Compare buffer and show result by different LED colorif (BufferCompare(aSRC_Const_Buffer, aDST_Buffer, BUFFER_SIZE) == 0){LED_GREEN;}else {LED_RED;}while (1){}
}

存储器到外设

此功能的实现思路为和存储器间的样例没有很大不同,但是要注意以下几点:

  • 需要初始化串口用GPIO口以及串口模块
  • 不需要开启DMA传输的M2M模式
  • 将Flash当作外设来处理
  • 需要额外配置USART的DMA控制发送功能,此功能仅DMA 通道4支持

分别建立DMA相关板级支持文件bsp_dma_uart.h,bsp_dma_uart.c,内容分别如下:

  • bsp_dma_uart.h:
#ifndef __BSP_DMA_UART_H__
#define __BSP_DMA_UART_H__#include "stm32f10x_usart.h"#define UART_DR_ADDR         (USART1_BASE + 0x04)// DMA
#define UART_DMA_CLK              RCC_AHBPeriph_DMA1
#define UART_DMA_CHANNEL          DMA1_Channel4 // channel 4 support only
#define UART_DMA_BUFFER_SIZE      64extern uint8_t aSRC_Buffer[UART_DMA_BUFFER_SIZE];void UART_DMA_Config(void);#endif
  • bsp_dma_uart.c:
#include "bsp_dma_uart.h"
#include "bsp_uart.h"
#include "stm32f10x.h"
#include "stm32f10x_dma.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_usart.h"
#include <stdint.h>uint8_t aSRC_Buffer[UART_DMA_BUFFER_SIZE] = "HeLLo WoRld !!! This is a demo of DMA/UART\n";void UART_DMA_Config(void)
{// DMADMA_InitTypeDef DMA_InitStructure;// DMA Clk enableRCC_AHBPeriphClockCmd(UART_DMA_CLK, ENABLE);// DMA param configDMA_InitStructure.DMA_PeripheralBaseAddr = UART_DR_ADDR;DMA_InitStructure.DMA_MemoryBaseAddr     = (u32)aSRC_Buffer;DMA_InitStructure.DMA_DIR                = DMA_DIR_PeripheralDST;DMA_InitStructure.DMA_BufferSize         = UART_DMA_BUFFER_SIZE;DMA_InitStructure.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;DMA_InitStructure.DMA_MemoryInc          = DMA_MemoryInc_Enable;DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;DMA_InitStructure.DMA_MemoryDataSize     = DMA_MemoryDataSize_Byte;DMA_InitStructure.DMA_Mode               = DMA_Mode_Circular;DMA_InitStructure.DMA_Priority           = DMA_Priority_Medium;DMA_InitStructure.DMA_M2M                = DMA_M2M_Disable;DMA_Init(UART_DMA_CHANNEL, &DMA_InitStructure);// DMA enableDMA_Cmd (UART_DMA_CHANNEL,ENABLE);// DMA UART TX enableUSART_DMACmd(USARTx, USART_DMAReq_Tx, ENABLE);
}

通过板级支持包代码实现需求:

#include "userapp.h"
#include "bsp_clkconfig.h"
#include "bsp_uart.h"
#include "bsp_dma_uart.h"
#include "bsp_led.h"
#include "stm32f10x.h"
#include "stm32f10x_dma.h"
#include "stm32f10x_usart.h"
#include <stdint.h>int userapp(void)
{LED_GPIO_Init();LED_PURPLE;USART_Config();Delay(5000000);UART_DMA_Config();while (1){LED_GREEN;Delay(5000000);LED_OFF;Delay(5000000);}
}

示例代码已上传Github:Sinuxtm32


http://www.ppmy.cn/news/94657.html

相关文章

异常检测论文1

本文仅作为个人阅读文献&#xff0c;做笔记记录。 <> \usepackage[dvipsnames]{xcolor} 一、摘要部分&#xff1a; 我们发现&#xff0c;现有的数据集偏向于局部结构异常&#xff0c;如划痕、凹痕或污染。特别是&#xff0c;它们缺乏违反逻辑约束形式的异常&#xff0…

python 在pdf指定位置添加公章或者照片

python 在pdf指定位置添加公章或者照片 -- coding: utf-8 -- import fitz import os from django.conf import settings from PIL import Image as pilImage author ‘JayChen’ class ProcessPDF: def init(self, pdf_path, pdf_name, pdf_out_path, pdf_out_name, seal,…

【蓝桥杯选拔赛真题57】Scratch计数游戏 少儿编程scratch图形化编程 蓝桥杯选拔赛真题讲解

目录 scratch计数游戏 一、题目要求 编程实现 二、案例分析 1、角色分析

MIT6824——lab4(实现一个分片kv存储)的一些实现,问题,和思考

Part A 分片控制器 1. 整体思路 和lab3A一样&#xff0c;shardctler也是一个服务&#xff0c;由客户端调用。这个服务建立在raft集群上&#xff0c;保证容错。 shardctler也应该保证线性一致性和重复请求的问题&#xff0c;因此也需要记录clientid和messageid。 shardctler保…

Python数据分析案例28——西雅图交通事故预测(不平衡样本处理)

本次案例适合机器学习数据科学方向的同学。 引言(废话集) 交通事故是一个严重的公共安全问题&#xff0c;在全球范围内每年都有成千上万的人死于交通事故。随着交通运输的发展和城市化进程的加速&#xff0c;交通事故已成为制约城市发展和人民幸福的主要因素之一。因此&#x…

STM32F4_I2C(从机EEPROM/MPU-6050)协议详解

目录 1. I2C是什么 2. I2C物理层介绍 3. I2C协议层介绍 3.1 I2C基本读写过程 3.1.1 通讯复合格式 3.2 通讯的起始和停止信号 3.3 数据有效性 3.4 地址及数据方向 3.5 响应 4. STM32的I2C特性及架构 4.1 I2C架构剖析 5. I2C通讯过程 5.1 主发送器 5.2 主接收器 6…

中间件篇2:中间件交付云原生之Operator

为什么我们需要Operator? 编写Operator其目的是将部署从文档化转为代码化,从人工部署转为自动化部署,即“部署即代码”,但还不是纯粹的部署即代码,因为基础设施依然需要手动去申请。 例如部署网络接入器(我司基于百度开源BFE二次开发的七层流量代理网关),我们需要先部…

攫取 RGB图像 和 PCM音频 数据

一、获取源码 1. 下载地址 Github: https://github.com/Gaaagaa/MediaGrabber 2. 编译提醒 这个测试程序&#xff0c;是使用 QtCreator 写的 Qt 界面程序&#xff0c;调用我封装好的 vlc_mgrabber_t 类实现了一个简单的播放器。MFC的我也写过相应的测试程序&#xff0c;这里…