DMA的原理

一、介绍

DMA(Direct Memory Access)是一种允许设备直接与内存进行数据交换的技术,无需‌CPU干预。DMA的主要功能是提供在‌外设和存储器之间或者存储器和存储器之间的高速数据传输。比如使用ADC进行数据采集,可以直接将数据存入存储器中,而无须CPU干预,节省了CPU的资源

二、基本结构与原理

工作原理

  1. 初始化阶段‌:当系统启动时,DMA控制器会初始化并配置好相关寄存器,这些寄存器用于定义DMA传输的数据量、传输方向、传输速率等参数。
  2. 请求传输‌:外设准备好数据后,会向DMA控制器发送一个传输请求。
  3. CPU响应请求‌:CPU收到DMA请求信号后,会让出总线控制权,允许DMA控制器接管总线控制权。
  4. 数据传输‌:DMA控制器从外设读取数据,并将其存储在内部缓冲区中,然后传输到系统内存中的目标地址。
  5. 传输完成‌:数据传输完成后,DMA控制器会释放总线控制权,并通知CPU。

下图为stm32中DMA的结构,两个DMA控制器有12个通道(DMA1有7个通道, DMA2有5个通道),每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个DMA请求的优先权。

上方的框图,我们可以看到STM32内核,存储器,外设及DMA的连接,这些硬件最终通过各种各样的线连接到总线矩阵中,硬件结构之间的数据转移都经过总线矩阵的协调,使各个外设和谐的使用总线来传输数据,

三、DMA传输方式

DMA的作用就是实现数据的直接传输,而去掉了传统数据传输需要CPU寄存器参与的环节,主要涉及四种情况的数据传输,但本质上是一样的,都是从内存的某一区域传输到内存的另一区域(外设的数据寄存器本质上就是内存的一个存储单元)。四种情况的数据传输如下:

  • 外设到内存
  • 内存到外设
  • 内存到内存
  • 外设到外设

数据传输阶段:

  • 源地址(Source Address)‌:指定数据传输的起始位置。
  • 目标地址(Destination Address)‌:指定数据传输的目的位置。
  • 数据长度(Data Length)‌:定义需要传输的数据大小。
  • 控制信息(Control Information)‌:包括传输模式、中断使能等参数。

数据传输阶段‌:

  • 外设发起传输请求。
  • DMA控制器暂停CPU访问,并通过请求信号获取总线控制权。
  • DMA控制器从外设读取数据,并存储在内部缓冲区。
  • 数据从内部缓冲区传输到系统内存中的目标地址。
  • 传输完成后,DMA控制器释放总线控制权,并通知CPU。

当用户将参数设置好,主要涉及源地址、目标地址、传输数据量这三个,DMA控制器就会启动数据传输,当剩余传输数据量为0时, 达到传输终点,结束DMA传输 。当然,DMA 还有循环传输模式,当到达传输终点时会重新启动DMA传输。 也就是说只要剩余传输数据量不是0,而且DMA是启动状态,那么就会发生数据传输。

当CPU和DMA同时访问相同的目标(RAM或外设)时, DMA请求会暂停CPU访问系统总线达若干个周期,总线仲裁器执行循环调度,以保证CPU至少可以得到一半的系统总线(存储器或外设)带宽
 

四、源码实例

实现从外设的数据到存储器的转运,并显示在LED显示屏上

#include "stm32f10x.h"                  // Device header

uint16_t MyDMA_Size;					//定义全局变量,用于记住Init函数的Size,供Transfer函数使用

/**
  * 函    数:DMA初始化
  * 参    数:AddrA 原数组的首地址
  * 参    数:AddrB 目的数组的首地址
  * 参    数:Size 转运的数据大小(转运次数)
  * 返 回 值:无
  */
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
	MyDMA_Size = Size;					//将Size写入到全局变量,记住参数Size
	
	/*开启时钟*/
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);						//开启DMA的时钟
	
	/*DMA初始化*/
	DMA_InitTypeDef DMA_InitStructure;										//定义结构体变量
	DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;						//外设基地址,给定形参AddrA
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;	//外设数据宽度,选择字节
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;			//外设地址自增,选择使能
	DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;							//存储器基地址,给定形参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_Init,配置DMA1的通道1
	
	/*DMA使能*/
	DMA_Cmd(DMA1_Channel1, DISABLE);	//这里先不给使能,初始化后不会立刻工作,等后续调用Transfer后,再开始
}

/**
  * 函    数:启动DMA数据转运
  * 参    数:无
  * 返 回 值:无
  */
void MyDMA_Transfer(void)
{
	DMA_Cmd(DMA1_Channel1, DISABLE);					//DMA失能,在写入传输计数器之前,需要DMA暂停工作
	DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);	//写入传输计数器,指定将要转运的次数
	DMA_Cmd(DMA1_Channel1, ENABLE);						//DMA使能,开始工作
	
	while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);	//等待DMA工作完成
	DMA_ClearFlag(DMA1_FLAG_TC1);						//清除工作完成标志位
}
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"

uint8_t DataA[] = {0x01, 0x02, 0x03, 0x04};				//定义测试数组DataA,为数据源
uint8_t DataB[] = {0, 0, 0, 0};							//定义测试数组DataB,为数据目的地

int main(void)
{
	/*模块初始化*/
	OLED_Init();				//OLED初始化
	
	MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);	//DMA初始化,把源数组和目的数组的地址传入
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "DataA");
	OLED_ShowString(3, 1, "DataB");
	
	/*显示数组的首地址*/
	OLED_ShowHexNum(1, 8, (uint32_t)DataA, 8);
	OLED_ShowHexNum(3, 8, (uint32_t)DataB, 8);
		
	while (1)
	{
		DataA[0] ++;		//变换测试数据
		DataA[1] ++;
		DataA[2] ++;
		DataA[3] ++;
		
		OLED_ShowHexNum(2, 1, DataA[0], 2);		//显示数组DataA
		OLED_ShowHexNum(2, 4, DataA[1], 2);
		OLED_ShowHexNum(2, 7, DataA[2], 2);
		OLED_ShowHexNum(2, 10, DataA[3], 2);
		OLED_ShowHexNum(4, 1, DataB[0], 2);		//显示数组DataB
		OLED_ShowHexNum(4, 4, DataB[1], 2);
		OLED_ShowHexNum(4, 7, DataB[2], 2);
		OLED_ShowHexNum(4, 10, DataB[3], 2);
		
		Delay_ms(1000);		//延时1s,观察转运前的现象
		
		MyDMA_Transfer();	//使用DMA转运数组,从DataA转运到DataB
		
		OLED_ShowHexNum(2, 1, DataA[0], 2);		//显示数组DataA
		OLED_ShowHexNum(2, 4, DataA[1], 2);
		OLED_ShowHexNum(2, 7, DataA[2], 2);
		OLED_ShowHexNum(2, 10, DataA[3], 2);
		OLED_ShowHexNum(4, 1, DataB[0], 2);		//显示数组DataB
		OLED_ShowHexNum(4, 4, DataB[1], 2);
		OLED_ShowHexNum(4, 7, DataB[2], 2);
		OLED_ShowHexNum(4, 10, DataB[3], 2);

		Delay_ms(1000);		//延时1s,观察转运后的现象
	}
}

五、总结

DMA传输方式虽然高效,但是,只是减轻了CPU的工作负担;系统总线仍然被占用。特别是在传输大容量文件时,CPU的占用率可能不到10%,但是用户会觉得运行部分程序时系统变得相当的缓慢。主要原因就是在运行这些应用程序(特别是一些大型软件),操作系统也需要从系统总线传输大量数据;故造成过长的等待时间。

参考:

关于DMA的介绍和在不同设备上的使用和介绍_qq64f6f10b54d8e的技术博客_51CTO博客

百度安全验证

MSP432DMA是指在MSP432P401R单片机上使用DMA(Direct Memory Access,直接内存访问)进行数据传输的原理DMA是一种数据传输方式,它可以在不经过CPU的干预下,直接在外设和内存之间进行数据传输。这样可以减轻CPU的负担,提高数据传输的效率。 在MSP432P401R单片机上,使用DMA进行ADC(Analog-to-Digital Converter,模数转换器)数据传输的原理如下: 1. 首先,需要启动DMA通道,并使能ADC转换。 2. 然后,通过软件请求DMA传输,这将开始DMA的数据传输。 3. 在DMA中断服务函数中,可以进行一些操作,比如点亮LED灯等。 4. 设置DMA的通道控制寄存器,指定数据传输的参数,比如数据大小、源地址和目的地址等。 5. 使用DMA的PingPong模式,可以在两个缓冲区之间切换,实现连续的数据传输。 6. 最后,通过DMA的软件传输请求,启动DMA通道进行数据传输。 关于MSP432DMA的更多详细信息和使用方法,可以参考引用\[2\]中提供的链接,该链接提供了关于MSP432P401R单片机使用DMA进行ADC数据传输的教程和示例代码。 #### 引用[.reference_title] - *1* *2* *3* [【电赛】MSP432P401R——ADC+DMA+串口打印](https://blog.csdn.net/m0_64886697/article/details/131741301)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值