STM32学习——直接存储器访问(DMA)

DMA(直接存储器访问)

DMA全称Direct Memory Access,即直接存储器访问。用来提供外设和存储器之间,或者存储器和存储器之间的高速数据传输。无须CPU控制,数据可以通过DMA传输,它通过硬件为RAM与I/O设备之间开辟一条直接传送数据的通道,这节省CPU的资源,使CPU效率提高。

一、STM32 DMA主要特性

  1. 12个独立的可配置的通道(请求): DMA1有7个通道, DMA2有5个通道。
  2. 每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过软件来配置。
  3. 在同一个DMA模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),优先权设置相等时由硬件决定(请求0优先于请求1,依此类推) 。
  4. 独立数据源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。
  5. 支持循环的缓冲器管理。
  6. 每个通道都有3个事件标志(DMA半传输、 DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求。
  7. 存储器和存储器间的传输。
  8. 外设和存储器、存储器和外设之间的传输。
  9. 闪存、 SRAM、外设的SRAM、 APB1、 APB2和AHB外设均可作为访问的源和目标。
  10. 可编程的数据传输数目:最大为65535

二、DMA控制器框图

STM32学习——直接存储器访问(DMA)_第1张图片

三、DMA通道请求映像

DMA1控制器从外设(TIMx[x=1、 2、 3、 4]、 ADC1、 SPI1、 SPI/I2S2、 I2Cx[x=1、 2]和USARTx[x=1、 2、 3])产生的7个请求,通过逻辑或输入到DMA1控制器,这意味着同时只能有一个请求有效。外设的DMA请求,可以通过设置相应外设寄存器中的控制位,被独立地开启或关闭。
STM32学习——直接存储器访问(DMA)_第2张图片
STM32学习——直接存储器访问(DMA)_第3张图片

DMA2控制器从外设(TIMx[5、 6、 7、 8]、 ADC3、 SPI/I2S3、 UART4、 DAC通道1、 2和SDIO)产生的5个请求,经逻辑或输入到DMA2控制器,这意味着同时只能有一个请求有效。外设的DMA请求,可以通过设置相应外设寄存器中的DMA控制位,被独立地开启或关闭
STM32学习——直接存储器访问(DMA)_第4张图片
STM32学习——直接存储器访问(DMA)_第5张图片注意:
1. DMA2控制器及相关请求仅存在于大容量产品和互联型产品中。
2. ADC3、SDIO和TIM8的DMA请求只在大容量的产品中存在。

四、DMA仲裁器

仲裁器根据通道请求的优先级来启动外设/存储器的访问。
优先权管理分2个阶段:

● 软件:每个通道的优先权可以在DMA_CCRx寄存器中设置,有4个等级:
[1].最高优先级
[2].高优先级
[3].中等优先级
[4].低优先级

● 硬件:如果2个请求有相同的软件优先级,则较低编号的通道比较高编号的通道有较高的优
先权。举个例子,通道2优先于通道4

注意: 在大容量产品和互联型产品中, DMA1控制器拥有高于DMA2控制器的优先级。

五、DMA中断

每个DMA通道都可以在DMA传输过半、传输完成和传输错误时产生中断。为应用的灵活性考虑,通过设置寄存器的不同位来打开这些中断。
STM32学习——直接存储器访问(DMA)_第6张图片
注意: 在大容量产品中, DMA2通道4和DMA2通道5的中断被映射在同一个中断向量上。在互联型产品中, DMA2通道4和DMA2通道5的中断分别有独立的中断向量。所有其他的DMA通道都有自己的中断向量。

六、DMA核心寄存器简述

STM32学习——直接存储器访问(DMA)_第7张图片
STM32学习——直接存储器访问(DMA)_第8张图片STM32学习——直接存储器访问(DMA)_第9张图片
STM32学习——直接存储器访问(DMA)_第10张图片
STM32学习——直接存储器访问(DMA)_第11张图片

七、DMA通道配置步骤

【1】. 设置外设地址

设置外设地址,通过寄存器DMA_CPARx设置,只需在这个寄存器里面写入外设地址值。

【2】. 设置存储器地址

设置存储器基地址,通过寄存器DMA_CMARx设置,只需在这个寄存器里面写入存储器基地址值。假设把数组sDMA作为存储器,那么在该寄存器写入&sDMA 就行。

【3】. 设置传输数据量

通过寄存器DMA_CNDTRx设置DMA通道数据传输量。该寄存器的数值将在DMA启动后自减,每次DMA传输,都重新向该寄存器写入要传输的数据量。

【4】. 设置通道配置信息

配置信息通过寄存器DMA_CCRx设置。设置存储器和外设传输的数据位宽,设置存储器到外设的存储器地址增量模式,传输的通道优先级等等。

【5】. 使能DMA通道启动传输

完成以上配置后,使能DMA_CCRx最低位开启DMA传输。

八、例程代码

【1】主函数

/******************************************************************************************************************************
本例程功能简述:
1.DMA通道CH4传输,存储器 --> 外设(USART1)
2.Key Up传输19200Byte,Key Down传输15360Byte
4.TFT显示已传输数据、剩余数据、完成比例
5.传输基数据为ASCII为0x20到0x7E
******************************************************************************************************************************/
int main(void)
{
    vSystem_Init();
	vSendData_DealWith();
	
	while(1)
	{
		vKeyBoard_Service_Handle();
	}
}
/******************************************************************************************************************************
*函数名: vSystem_Init
*描  述: 系统初始化
******************************************************************************************************************************/
static void vSystem_Init(void)
{
	SystemInit();
    vSysTick_Init();						
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//Priority Grouping 4 bit
	vLED_GPIO_Config();
	vKEY_GPIO_Config();
	vUART_Init();						
	vTFT_Init();	
	vDMA_Init();
	vTIM_Init();
		
	#if ( USE_SEGGER_RTT_ENABLE > 0 )//Use segger rtt enable
	SEGGER_RTT_Init();
	SEGGER_RTT_printf(0, "STM32F103ZET6 Start Running......\r\n");
	#endif
	
	#if ( USE_KEIL_MACRO_INFO_ENBALE > 0 )//Use Keil macro information enable 
	vKeil_Macro_Information();
	#endif	
}
/******************************************************************************************************************************
*函数名: vSendData_DealWith
*描  述:发送数据处理
******************************************************************************************************************************/
static void vSendData_DealWith(void)
{
	u8 mDat[ASCII_MAX] = {0};
	u8 i = 0;
	
	for(i=0; i<ASCII_MAX; i++)
		mDat[i] = 0x20 + i;
	mDat[94] = 0x0D;
	mDat[95] = 0x0A;
	
	for(i=0; i<SDAT_LEN; i++)
		memcpy(&sDMA[ASCII_MAX * i], mDat, ASCII_MAX);
}
/********************************************************************************************
*函数名:vKeyBoard_Service_Handle
*描  述:键盘服务处理
********************************************************************************************/
void vKeyBoard_Service_Handle(void)
{
	float remDat = 0;//remaining data
	u16   talDat = 0;//total data
	
	
	if(KeyStr.Modes == Idle)
		return;
	
	switch(KeyStr.Modes)
	{
		case KeyUpClick :
		{
			LED2_SET();
			talDat = ASCII_MAX * SDAT_LEN;
		}
		break;
		
		case KeyDownClick :
		{	
			LED3_SET();
			talDat = ASCII_MAX * 160;
		}
		break;
		
		case KeyLeftClick :
		{	
			LED5_CPL();
		}
		break;
		
		case KeyRightClick :
		{
			LED5_CPL();
		}
		break;
		
		default : break;
	}
	
	if(talDat)
	{
		vDMA_Transmit_Enable(DMA1_Channel4, talDat);
		while(1)
		{
			if(DMA_GetFlagStatus(DMA1_FLAG_TC4) != RESET)//Get DMA Status Flag(transfer complete)
			{
				DMA_ClearFlag(DMA1_FLAG_TC4);//Clears DMA flags(transfer complete)
				break; 
			}

			remDat = DMA_GetCurrDataCounter(DMA1_Channel4);//Current remaining data
			vTFT_Show_Number(130, 120, 16, (talDat - remDat), 5, GREEN, BLUE);//Finish Data
			vTFT_Show_Number(130, 140, 16, remDat, 5, GREEN, BLUE);//Residue Data
			vTFT_Show_Number(130, 160, 16, ((1-(remDat/talDat)) * 100), 3, GREEN, BLUE);//Percentage
		}
				
		remDat = DMA_GetCurrDataCounter(DMA1_Channel4);
		vTFT_Show_Number(130, 120, 16, (talDat - remDat), 5, GREEN, BLUE);
		vTFT_Show_Number(130, 140, 16, remDat, 5, GREEN, BLUE);
		vTFT_Show_Number(130, 160, 16, ((1-(remDat/talDat)) * 100), 3, GREEN, BLUE);	
		
				
		if(KeyStr.Modes == KeyUpClick)
			LED2_CLR();
		else if(KeyStr.Modes == KeyDownClick)
			LED3_CLR();
	}
	
	KeyStr.Modes = Idle;
}

【2】DMA配置

/******************************************************************************************************************************
*函数名: vDMA_Config
*描  述:DMA配置
*输  入: chx      : DAM传输通道 
		 perAddr  : 外设基地址
		 memAddr  : 存储器基地址
******************************************************************************************************************************/
static void vDMA_Config(DMA_Channel_TypeDef*  chx, u32 perAddr, u32 memAddr)
{
	DMA_InitTypeDef DMA_InitStructure;
	
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);	//Enable DMA1 clock 
	DMA_DeInit(chx);									//DMA Channel registers reset
	
	DMA_InitStructure.DMA_PeripheralBaseAddr = perAddr;						//Peripheral base address
	DMA_InitStructure.DMA_MemoryBaseAddr 	 = memAddr;						//Memory base address
	DMA_InitStructure.DMA_DIR 			     = DMA_DIR_PeripheralDST;		//DMA transmit direction
	DMA_InitStructure.DMA_BufferSize 		 = 0;							//MDA Channel buffer size
	DMA_InitStructure.DMA_PeripheralInc 	 = DMA_PeripheralInc_Disable;	//Peripheral address is incremented
	DMA_InitStructure.DMA_MemoryInc 		 = DMA_MemoryInc_Enable;		//Memory address is incremented
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;	//Peripheral data width
	DMA_InitStructure.DMA_MemoryDataSize 	 = DMA_MemoryDataSize_Byte;		//Memory data width
	DMA_InitStructure.DMA_Mode 				 = DMA_Mode_Normal;				//DMAy Channe mode
	DMA_InitStructure.DMA_Priority 			 = DMA_Priority_High;			//MDA Channel priority
	DMA_InitStructure.DMA_M2M 				 = DMA_M2M_Disable;				//Memory-to-memory transfer
	DMA_Init(chx, &DMA_InitStructure);										//DMA init
}

【3】DMA传输使能

/******************************************************************************************************************************
*函数名: vDMA_Transmit_Enable
*描  述:DMA传输使能
*输  入: chx      : DAM传输通道 
		 dataSize : 传输字节数
******************************************************************************************************************************/
void vDMA_Transmit_Enable(DMA_Channel_TypeDef*  chx, u16 dataSize)
{
	USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);//Enable USART1 DMA
	
	DMA_Cmd(chx, DISABLE);					//DMA disable
	DMA_SetCurrDataCounter(chx, dataSize);	//Current DMA Channel transfer Counter
	DMA_Cmd(chx, ENABLE);					//DMA enable
}

【4】DMA初始化

/******************************************************************************************************************************
函数名: vDMA_Init
描  述:DMA初始化
******************************************************************************************************************************/
u8 sDMA[ASCII_MAX * SDAT_LEN] = {0};
void vDMA_Init(void)
{
	vDMA_Config(DMA1_Channel4, ((u32)(&USART1->DR)), (u32)sDMA);
}

【5】DMA头文件

#define SDAT_LEN											200
#define ASCII_MAX											96

void vDMA_Transmit_Enable(DMA_Channel_TypeDef*  chx, u16 dataSize);	//vDMA_Transmit_Enable
void vDMA_Init(void);												//vDMA_Init

extern u8 sDMA[ASCII_MAX * SDAT_LEN];

你可能感兴趣的:(STM32)