首页 > 其他分享 >DMA直接存储器存取

DMA直接存储器存取

时间:2024-09-11 21:53:55浏览次数:13  
标签:DMA 存储器 ADC 转运 InitStruct 存取 外设

DMA直接存储器存取

DMA简介

DMA(Direct Memory Access)直接存储器存取

DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源

  1. 外设:外设存储器,一般指外设的数据寄存器

  2. 存储器:运行内存SRAM和程序存储器Flash,存储变量、数组和程序代码的地方

其实外设寄存器也是存储器,只是STM32方便区分取得名字,所以DMA本质上都是存储器到存储器的数据转运

12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道) 每个通道都支持软件触发和特定的硬件触发

  1. 软件触发:如果是将数据从存储器转运到存储器,就使用软件触发,因为软件触发不讲究转换时机,只需要以最快速度转运完成

  2. 特定的硬件触发:将外设中的数据转运到存储器中,需要一定的触发时机,例如AD转换完成,才需要通知DMA来转运数据。每个外设触发时走的DMA通道是固定的,所以叫特定的硬件触发

STM32F103C8T6的DMA资源:DMA1(7个通道)

存储器映像

在这里插入图片描述
查看内存时,地址以0x0800开头的数据基本都是编写的C语音代码

地址以0x2000开头的是程序运行过程中产生的变量等

DMA框图

在这里插入图片描述
注意:Flash部分是只读的,如果将Flash存储器作为转运数据的目标地址则会出错

如果需要对Flash进行写入就需要配置Flash阀门控制器

DMA基本结构

在这里插入图片描述
起始地址:决定数据从哪来到哪去

数据宽度:指定每次转运时的位数。

  1. 字节Byte:8位
  2. 半字HalfWord:16位
  3. 字Word:32位

地址是否自增:转运了一次数据后是否要对地址自增,自增就转运下一个地址的数据,不自增就还是转运当前地址的数据。相当于指针自增

方向:是从外设转运数据到存储器还是从存储器转运到外设

传输计数器:用于记录总共要转运几次数据的,是一个自减寄存器,每转运一次就减一,减到0之后就不在转运,并且转运的地址也会回到一开始的地址

自动重装器:当计数器减到0后,恢复计数器的初始值。不使用自动重装时就是单次模式,使用自动重装就是循环模式

M2M:DMA的触发控制。当M2M为1时,软件触发;当M2M为0时硬件触发

  1. 软件触发:以最快的速度连续不断的触发DMA,快速清零计数器完成转换。注意不能和自动重装器一起使用,否则会DMA会一直转运数据,停不下来。适用于存储器到存储器的转运
  2. 硬件触发:与外设有关,需要一定时机来触发。例如ADC转换完成、串口收到数据、定时时间到了

DMA开始转运数据的条件

  1. DMA使能
  2. 传输计数器必须大于0(注意:写传输寄存器的值时,必须要关闭DMA再进行)
  3. 有触发源

DMA硬件请求通道

每个外设的硬件触发控制DMA都有固定对应的DMA通道

通道号越小优先级越高
在这里插入图片描述
总结的表
在这里插入图片描述

数据宽度与对齐

如果目标的数据宽度比源端的数据宽度大,那就在目标数据多出来的空位补0

如果目标的数据宽度比源端的数据宽度小,就会把多出来的高位舍弃

这种操作就和uint8_t、uint16_t、uint32_t的变量互相转换是一样的
在这里插入图片描述

DMA相关库函数

DMA_InitType

typedef struct
{
  uint32_t DMA_PeripheralBaseAddr; /*!< 指定要进行DMA转运的外设站点地址 */
  uint32_t DMA_MemoryBaseAddr;     /*!< 指定要进行DMA转运的存储器站点地址 */
  uint32_t DMA_DIR;                /*!< 指定外设站点是目标端还是发送端 */
  uint32_t DMA_BufferSize;         /*!< 传输计数器的初值,也就是要转运的次数 */
  uint32_t DMA_PeripheralInc;      /*!< DMA每转运一次外设站点的地址是否自增 */
  uint32_t DMA_MemoryInc;          /*!< DMA每转运一次存储器站点的地址是否自增 */
  uint32_t DMA_PeripheralDataSize; /*!< 外设站点的数据宽度 */
  uint32_t DMA_MemoryDataSize;     /*!< 存储器站点的数据宽度 */
  uint32_t DMA_Mode;               /*!< DMA的工作模式,是否启用自动重装器 */
  uint32_t DMA_Priority;           /*!< 指定通道的优先级 */
  uint32_t DMA_M2M;                /*!< 软件触发还是硬件触发 */
}DMA_InitTypeDef;

/* DMA_PeripheralBaseAddr */
外设站点地址。注意要将数据类型强转为:uint32_t

/* DMA_MemoryBaseAddr */
存储器站点地址。注意要将数据类型强转为:uint32_t

/* DMA_DIR */
DMA_DIR_PeripheralDST	// 外设站点作为目的地
DMA_DIR_PeripheralSRC	// 外设站点作为数据源

/* DMA_BufferSize */
0~65535		// 要转运的次数

/* DMA_PeripheralInc */
DMA_PeripheralInc_Enable	// 每次转运后外设站点的地址自增
DMA_PeripheralInc_Disable	// 每次转运后外设站点的地址不自增

/* DMA_MemoryInc */
DMA_MemoryInc_Enable	// 每次转运后存储器站点的地址自增
DMA_MemoryInc_Disable	// 每次转运后存储器站点的地址不自增

/* DMA_PeripheralDataSize */
DMA_PeripheralDataSize_Byte			// 外设站点的数据宽度为8位
DMA_PeripheralDataSize_HalfWord		// 外设站点的数据宽度为16位
DMA_PeripheralDataSize_Word			// 外设站点的数据宽度为32位

/* DMA_MemoryDataSize */
DMA_MemoryDataSize_Byte			// 存储器站点的数据宽度为8位
DMA_MemoryDataSize_HalfWord		// 存储器站点的数据宽度为16位
DMA_MemoryDataSize_Word			// 存储器站点的数据宽度为32位

/* DMA_Mode */
DMA_Mode_Circular	// 启用自动重装器,循环模式。注意该模式不能与软件触发一起使用
DMA_Mode_Normal		// 不启用自动重装器,单次模式

/* DMA_Priority */
DMA_Priority_VeryHigh	// 很高的优先级
DMA_Priority_High		// 高的优先级
DMA_Priority_Medium		// 一般优先级
DMA_Priority_Low		// 低的优先级

/* DMA_M2M */
DMA_M2M_Enable		// 软件触发。注意该触发方式不能与循环模式一起使用
DMA_M2M_Disable		// 硬件触发

函数

// 重置DMA配置
void DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx);

// 使用DMA_InitTypeDef结构体初始化DMA
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);

// 使用默认值填充DMA_InitTypeDef的属性
void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct);

// 开启和关闭DMA通道
void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);

// 启用或禁用DMA通道的中断
void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);

// 单独设置传输计数器的值
void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber); 

// 获取传输计数器当前的值
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);

// 检测DMA通道中指定的标志位
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);

// 清除DMA通道中指定的标志位
void DMA_ClearFlag(uint32_t DMAy_FLAG);

// 检测DMA通道的是否发生中断
ITStatus DMA_GetITStatus(uint32_t DMAy_IT);

// 清除DMA通道的中断标志位
void DMA_ClearITPendingBit(uint32_t DMAy_IT);

案例

DMA转运存储器数据

用到的函数

// 使用DMA_InitTypeDef结构体初始化DMA
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);

// 开启和关闭DMA通道
void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);

// 单独设置传输计数器的值
void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber); 

// 检测DMA通道中指定的标志位
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);

// 清除DMA通道中指定的标志位
void DMA_ClearFlag(uint32_t DMAy_FLAG);

接线图

在这里插入图片描述

示例代码

#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "Delay.h"

/**
  * @brief  初始化DMA1的通道1.软件触发、不使用自动重装器,单次模式、外设端作为目标端、使用通道1
  * @param  Peripheral: 外设端的地址
  * @param  Memory: 存储器端的地址
  * @param  BufferSize: 转运的次数,写入传输计数器的初始值
  * @retval 无
  */
void MyDMA_Init(uint32_t Peripheral, uint32_t Memory, uint8_t BufferSize)
{
	// 开启属于AHB的MDA1的时钟
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	
	// 初始化DMA1的通道1。因为是软件触发,所以DMA的通道可以随便选
	DMA_InitTypeDef DMA_InitStruct;
	DMA_InitStruct.DMA_MemoryBaseAddr = Memory;								// 外设端的地址
	DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;			// 外设端的数据宽度:8位
	DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;					// 外设端每转运一次地址自增一次
	DMA_InitStruct.DMA_PeripheralBaseAddr = Peripheral;						// 存储器端的地址
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;	// 存储器端的数据宽度:8位
	DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Enable;			// 存储器端每转运一次地址自增一次
	DMA_InitStruct.DMA_BufferSize = BufferSize;								// 转运次数
	DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralDST;							// 外设端作为目的端
	DMA_InitStruct.DMA_M2M = DMA_M2M_Enable;								// 软件触发
	DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;								// 不开启自动重装器,单次模式
	DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;						// DMA优先级
	DMA_Init(DMA1_Channel1, &DMA_InitStruct);								// 配置到DMA1的通道1
}

/**
  * @brief  根据DMA初始化的配置开始转运数据
  * @param  无
  * @retval 无
  */
void MyDMA_Transfer()
{
	// 开启DMA,开始转运数据
	DMA_Cmd(DMA1_Channel1, ENABLE);
	// 检测DMA通道的标志位。TC1表示通道1的转运完成标志位,返回SET说明数据全部转运完成
	while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
	// 清除转运完成标志位
	DMA_ClearFlag(DMA1_FLAG_TC1);
	// DMA失能
	DMA_Cmd(DMA1_Channel1, DISABLE);
	// 重新写入传输计数器的值。注意,必须在DMA失能后再写入该寄存器
	DMA_SetCurrDataCounter(DMA1_Channel1, 4);
}

int main()
{
	uint8_t DataA[4] = {0x01, 0x02, 0x03, 0x04};
	uint8_t DataB[4] = {0, 0, 0, 0};
	
	// 初始化DMA1的通道1
	MyDMA_Init((uint32_t)DataB, (uint32_t)DataA, 4);
	
	OLED_Init();
	while(1)
	{
		// 显示DataA和DataB
		for(uint8_t i = 0;i < 4;i++)
		{
			OLED_ShowString(i + 1, 1, "A[ ]:");
			OLED_ShowNum(i + 1, 3, i, 1);
			OLED_ShowNum(i + 1, 6, DataA[i], 2);
			
			OLED_ShowString(i + 1, 9, "B[ ]:");
			OLED_ShowNum(i + 1, 11, i, 1);
			OLED_ShowNum(i + 1, 14, DataB[i], 2);
		}
		
		Delay_s(1);
		
		// DataA中的所有数据自增
		for(uint8_t i = 0;i < 4;i++)
		{
			DataA[i]++;
		}
		
		for(uint8_t i = 0;i < 4;i++)
		{
			OLED_ShowString(i + 1, 1, "A[ ]:");
			OLED_ShowNum(i + 1, 3, i, 1);
			OLED_ShowNum(i + 1, 6, DataA[i], 2);
			
			OLED_ShowString(i + 1, 9, "B[ ]:");
			OLED_ShowNum(i + 1, 11, i, 1);
			OLED_ShowNum(i + 1, 14, DataB[i], 2);
		}
		
		Delay_s(1);
		
		// DMA转运数据
		MyDMA_Transfer();
	}
}

DMA与ADC多通道的扫描模式配合

接线图

在这里插入图片描述

示例代码

#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "Delay.h"

/**
  * @brief  初始化ADC1所用的DMA通道
  * @param  Peripheral: 外设端的地址
  * @param  Memory: 存储器端的地址
  * @param  BufferSize: 转运的次数,写入传输计数器的初始值
  * @retval 无
  */
void MyDMA_Init(uint32_t Peripheral, uint32_t Memory, uint8_t BufferSize)
{
	// 开启DMA1的时钟
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	
	// 初始化DMA
	DMA_InitTypeDef DMA_InitStruct;
	DMA_InitStruct.DMA_BufferSize = BufferSize;									// 转运次数
	DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;								// 外设端为数据源
	DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;									// 硬件触发
	DMA_InitStruct.DMA_MemoryBaseAddr = Memory;									// 存储端地址
	DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;			// 存储端数据宽度
	DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;						// 存储端地址自增
	DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;								// 循环模式。使用自动重装器
	DMA_InitStruct.DMA_PeripheralBaseAddr = Peripheral;							// 外设端地址
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;	// 外设端数据宽度
	DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;				// 外设端地址不自增
	DMA_InitStruct.DMA_Priority = DMA_Priority_High;							// 高优先级
	DMA_Init(DMA1_Channel1, &DMA_InitStruct);
	
	// 启用DMA1的通道1。根据DMA硬件请求通道可知ADC1的DMA通道是DMA1
	DMA_Cmd(DMA1_Channel1, ENABLE); 
}

/**
  * @brief  初始化ADC1的0~3通道
  * @param  无
  * @retval 无
  */
void MyADC_Init()
{
	// 开启ADC1和GPIOA的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	// 对APB2的时钟频率进行6分频后作为ADC的时钟频率
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	
	// 初始化PA0、PA1、PA2、PA3为模拟输入
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	// 初始化ADC1
	ADC_InitTypeDef ADC_InitStruct;
	ADC_InitStruct.ADC_ContinuousConvMode = ENABLE;						// ADC连续转换模式
	ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;					// 数据右对齐
	ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	// 使用软件触发
	ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;						// ADC1独立工作模式
	ADC_InitStruct.ADC_NbrOfChannel = 4;								// 扫描模式下每一轮转运的次数
	ADC_InitStruct.ADC_ScanConvMode = ENABLE;							// 扫描模式
	ADC_Init(ADC1, &ADC_InitStruct);
	
	// ADC1开始工作
	ADC_Cmd(ADC1, ENABLE);
	
	// 根据手册要求ADC开始工作后进行校准
	ADC_ResetCalibration(ADC1);
	while(ADC_GetResetCalibrationStatus(ADC1) == SET);
	ADC_StartCalibration(ADC1);
	while(ADC_GetCalibrationStatus(ADC1) == SET);
	
	// 软件触发ADC1的AD转换
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);
	
	// 配置规则组的通道
	ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);		// 将ADC1的通道0放到规则组的序列1上
	ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);		// 将ADC1的通道1放到规则组的序列2上
	ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);		// 将ADC1的通道2放到规则组的序列3上
	ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);		// 将ADC1的通道3放到规则组的序列4上
	
	// 开启ADC1的DMA通道
	ADC_DMACmd(ADC1, ENABLE);
}

// 该数组用于存放ADC转换完成的数据,由DMA搬运。索引0对应通道0,以此类推
uint16_t AOData[4];

int main()
{
	MyADC_Init();
	// ADC规则组数据转换完成后将数据放到叫DR16位数据寄存器中,可以在手册中了解
	MyDMA_Init((uint32_t)&ADC1->DR, (uint32_t)AOData, 4);
	OLED_Init();
	while(1)
	{
		for(uint8_t i = 0; i < 4; i++)
		{
			OLED_ShowNum(i + 1, 1, AOData[i], 5);
		}
	}
}

标签:DMA,存储器,ADC,转运,InitStruct,存取,外设
From: https://blog.csdn.net/2401_83614570/article/details/142143790

相关文章

  • 零基础国产GD32单片机编程入门(十六)DMA详解及ADC-DMA方式采集含源码
    文章目录一.概要二.GD32F103C8T6单片机DMA外设特点三.GD32单片机DMA内部结构图四.DMA各通道请求五.GD32F103C8T6单片机ADC-DMA采集例程六.工程源代码下载七.小结一.概要基本概念:DMA是DirectMemoryAccess的首字母缩写,是一种完全由硬件执行数据交换的工作方式。DM......
  • DMA的超通俗讲解(巨TM详细)(再不懂回家放牛种地吧)
    注意注意,本文只用"国粹"来讲解DMA的原理,请大家准备好。作者:你你你,那个谁,坐下,听我说两句(叫我装逼王!)观众:装逼王!装逼王!装逼王!装逼王!(还有人在挥手!还有摇旗的)好,我先现在开始⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇......
  • 八、3 DMA数据转运(代码)
    (1)DMA函数介绍(2)DMA是AHB总线的设备,要用AHB开启时钟若将DataA放在外设站点,DataB放在存储器站点,传输方向就是外设站点—>存储器站点DMA转运的三个条件:1)传输计数器大于0(传一个大于0的Size);2)触发源有触发信号(触发源为软件触发,一直有触发信号);3)DMA使能转运一次,传输......
  • DMA
    DMAOverviewDMA对应的地址空间是DRAM,而不是MMIO。现代DMA和64位地址:现代系统中,许多设备和DMA控制器可以支持64位地址,这意味着它们能够访问整个物理内存空间,而不再受限于16MB的限制。在这种情况下,BIOS只需要确保为需要的设备分配内存,而不必局限于低地址空间。BIOS......
  • 信息学奥赛初赛天天练-85-NOIP2014普及组-基础题4-链表、随机存取、顺序存取、二分查
    信息学奥赛初赛天天练-85-NOIP2014普及组-基础题4-链表、随机存取、顺序存取、二分查找、二分比较、循环结构、图领奖PDF文档公众号回复关键字:202409071NOIP2014普及组基础题49下列选项中不属于图像格式的是()AJPEG格式BTXT格式CGIF格式DPNG格式1......
  • 信息学奥赛初赛天天练-84-NOIP2014普及组-基础题3-总线、存储器、邮件协议、二叉树、
    信息学奥赛初赛天天练-84-NOIP2014普及组-基础题3-总线、存储器、邮件协议、二叉树、满二叉树、顶点的度、无向图、有向图PDF文档公众号回复关键字:202409061NOIP2014普及组基础题36CPU、存储器、I/O设备是通过()连接起来的A接口B总线C控制线D系统文......
  • DMA的巨详细配置步骤(还不懂回家种田去)
    首先介绍我的DMA使用场景“电阻ADC转化”。从外界读取ADC的值,然后让DMA进行搬运至自己定义的数组当中,之后读取这个数据就可以了。先介绍一个我的ADC结构体,也就是句柄(到时候对ADC的所有控制,就是操作这个句柄)/***@briefADChandleStructuredefinition*/type......
  • 两个月冲刺软考——存储器件有哪些?数据输入输出技术;计算后缀表达式(配合例题讲解+分析
    1.段页式存储管理根据段号、页号、页内地址进行计算“话术”:最多可有……个段,每个段最大允许有……个页,页的大小为……2.已知内存容量256K,求若用16K*4bit的存储器芯片构成该内存共需要多少片?16K*4bit的存储芯片,意味着每个芯片有16K个存储单元,每个存储单元可以存储4位(bit)的......
  • DMA——STM32F407ZGT6
    DMA简介DMA(DirectMemoryAccess)直接存储器存取DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源。DMA的作用就是解决大量数据转移过度消耗CPU资源的问题,有了DMA得CPU可以更加专注的实用的的操作——计算、控制等。外围设备可以通过......
  • 解决podman: ERRO[0000] running newuidmap: write to uid_map failed: Invalid argum
    报错ERRO[0000]running/usr/bin/newuidmap27115520100011100000655366553710000065537:newuidmap:writetouid_mapfailed:InvalidargumentError:cannotsetupnamespaceusing"/usr/bin/newuidmap":shouldhavesetuidorhavefilecapssetu......