(30)STM32——DMA笔记

目录

学习目标

成果展示 

介绍

框图

特性

通道选择

请求映射

数据流

事务

仲裁器

指针递增

循环模式

双缓冲区模式

DMA中断 

流配置过程

配置

代码 

总结 


学习目标

        本节我们来学习有关DMA的知识,这部分知识在51中是没有接触的,也算是一个新的概念,简而言之,DMA就是一个不需要CPU的传输方式。好了,接下来我们就开始介绍有关DMA的知识吧!

成果展示 

         传输速度较快,视频也看不太清,就发一张截图了,一同发送了200条这样的数据。

介绍

        DMA,全称为:Direct Memory Access,即直接存储器访问。DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路,能为CPU减负,使 CPU 的效率大为提高。

        STM32F4 最多有 2 个 DMA 控制器(DMA1 和 DMA2),共 16 个数据流(每个控制器 8 个),每一个 DMA 控制器都用于管理一个或多个外设的存储器访问请求。每个数据流总共可以有多达 8 个通道(或称请求)。每个数据流通道都有一个仲裁器,用于处理 DMA 请求间的优先级。

框图

        这就是有关DMA的流程框图,一个8个数据流,每个数据流8个通道,还有仲裁器。其中的FIFO应该是先进先出队列,我们在此不做过多介绍。 

特性

  • 双 AHB 主总线架构,一个用于存储器访问,另一个用于外设访问 
  • 仅支持 32 位访问的 AHB 从编程接口
  • 每个 DMA 控制器有 8 个数据流,每个数据流有多达 8 个通道(或称请求)
  • 每个数据流有单独的四级 32 位先进先出存储器缓冲区(FIFO),可用于 FIFO 模式或直接模式。
  • 通过硬件可以将每个数据流配置为:
    • 1,支持外设到存储器、存储器到外设和存储器到存储器传输的常规通道 。
    • 2,支持在存储器方双缓冲的双缓冲区通道
  • 8 个数据流中的每一个都连接到专用硬件 DMA 通道(请求)
  • DMA 数据流请求之间的优先级可用软件编程(4 个级别:非常高、高、中、低),在软件优先级相同的情况下可以通过硬件决定优先级(例如,请求 0 的优先级高于请求 1)
  • 每个数据流也支持通过软件触发存储器到存储器的传输(仅限 DMA2 控制器)
  • 可供每个数据流选择的通道请求多达 8 个。此选择可由软件配置,允许几个外设启动 DMA 请求
  • 要传输的数据项的数目可以由 DMA 控制器或外设管理:
    • 1,DMA 流控制器:要传输的数据项的数目是 1 到 65535,可用软件编程 。
    • 2,外设流控制器:要传输的数据项的数目未知并由源或目标外设控制,这些外设通过硬 件发出传输结束的信号
  • 独立的源和目标传输宽度(字节、半字、字):源和目标的数据宽度不相等时,DMA 自动封装/解封必要的传输数据来优化带宽(这个有点类似于Java里的缓冲流)。这个特性仅在 FIFO 模式下可用。
  • 对源和目标的增量或非增量寻址
  • 支持 4 个、8 个和 16 个节拍的增量突发传输。突发增量的大小可由软件配置,通常等 于外设 FIFO 大小的一半
  • 每个数据流都支持循环缓冲区管理
  • 5 个事件标志(DMA 半传输、DMA 传输完成、DMA 传输错误、DMA FIFO 错误、直接模式错误),进行逻辑或运算,从而产生每个数据流的单个中断请求

通道选择

        这个是通道选择,具体是哪个寄存器连接到数据流,是通过CHSEL寄存器来控制的。

 

请求映射

        这就是一一对应关系,不同的数据流和通道对应不同的外设。 

 

数据流

        8 个 DMA 控制器数据流都能够提供源和目标之间的单向传输链路。 每个数据流配置后都可以执行:

  • 常规类型事务:存储器到外设、外设到存储器或存储器到存储器的传输。
  • 双缓冲区类型事务:使用存储器的两个存储器指针的双缓冲区传输(当 DMA 正在进行自/ 至缓冲区的读/写操作时,应用程序可以进行至/自其它缓冲区的写/读操作)。

        要传输的数据量(多达 65535)可以编程,并与连接到外设 AHB 端口的外设(请求 DMA 传输)的源宽度相关。每个事务完成后,包含要传输的数据项总量的寄存器都会递减。

事务

        DMA 事务由给定数目的数据传输序列组成。要传输的数据项的数目及其宽度(8 位、16 位 或 32 位)可用软件编程。 每个 DMA 传输包含三项操作:

  • 通过 DMA_SxPAR 或 DMA_SxM0AR 寄存器寻址,从外设数据寄存器或存储器单元中加载数据。
  • 通过 DMA_SxPAR 或 DMA_SxM0AR 寄存器寻址,将加载的数据存储到外设数据寄存 器或存储器单元。
  • DMA_SxNDTR 计数器在数据存储结束后递减,该计数器中包含仍需执行的事务数。

        在产生事件后,外设会向 DMA 控制器发送请求信号。DMA 控制器根据通道优先级处理该请求。只要 DMA 控制器访问外设,DMA 控制器就会向外设发送确认信号。外设获得 DMA 控制器的确认信号后,便会立即释放其请求。一旦外设使请求失效,DMA 控制器就会释放确认信号。如果有更多请求,外设可以启动下一个事务。

仲裁器

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

  • 软件:每个数据流优先级都可以在 DMA_SxCR 寄存器中配置。分为四个级别:非常高优先级、高优先级、中优先级、低优先级。
  • 硬件:如果两个请求具有相同的软件优先级,则编号低的数据流优先于编号高的数据 流。例如,数据流 2 的优先级高于数据流 4。

 其实有点类似于中断的优先级的概念。

指针递增

        其实比较好理解,就是发送数据时指针自动递增,就能发送一条完整的数据流了。

循环模式

        就是是否需要循环发送数据。

双缓冲区模式

        我们可以理解为,两个数据流同时操作,比如DMA1在传输的时候,去填充DMA2的数据,等DMA1完成后,DMA2就接上DMA1的数据流继续发送。

DMA中断 

对于每个 DMA 数据流,可在发生以下事件时产生中断:

  • 达到半传输
  • 传输完成
  • 传输错误
  • FIFO 错误(上溢、下溢或 FIFO 级别错误)
  • 直接模式错误

可以使用单独的中断使能位以实现灵活性。

流配置过程

        我们在代码中详细介绍一下整个的过程。 

  1. 如果使能了数据流,通过重置 DMA_SxCR 寄存器中的 EN 位将其禁止,然后读取此位 以确认没有正在进行的数据流操作。将此位写为 0 不会立即生效,因为实际上只有所有 当前传输都已完成时才会将其写为 0。当所读取 EN 位的值为 0 时,才表示可以配置数 据流。因此在开始任何数据流配置之前,需要等待 EN 位置 0。应将先前的数据块 DMA 传输中在状态寄存器(DMA_LISR 和 DMA_HISR)中置 1 的所有数据流专用的位置 0, 然后才可重新使能数据流。
  2. 在 DMA_SxPAR 寄存器中设置外设端口寄存器地址。外设事件发生后,数据会从此地址 移动到外设端口或从外设端口移动到此地址。
  3. 在 DMA_SxMA0R 寄存器(在双缓冲区模式的情况下还有 DMA_SxMA1R 寄存器)中设 置存储器地址。外设事件发生后,将从此存储器读取数据或将数据写入此存储器。
  4. 在 DMA_SxNDTR 寄存器中配置要传输的数据项的总数。每出现一次外设事件或每出现 一个节拍的突发传输,该值都会递减。
  5. 使用 DMA_SxCR 寄存器中的 CHSEL[2:0] 选择 DMA 通道(请求)。
  6. 如果外设用作流控制器而且支持此功能,请将 DMA_SxCR 寄存器中的 PFCTRL 位置 1。
  7. 使用 DMA_SxCR 寄存器中的 PL[1:0] 位配置数据流优先级。
  8. 配置 FIFO 的使用情况(使能或禁止,发送和接收阈值)。
  9. 配置数据传输方向、外设和存储器增量 / 固定模式、单独或突发事务、外设和存储器数 据宽度、循环模式、双缓冲区模式和传输完成一半和/或全部完成,和/或 DMA_SxCR 寄存器中错误的中断。
  10. 通过将 DMA_SxCR 寄存器中的 EN 位置 1 激活数据流。 一旦使能了流,即可响应连接到数据流的外设发出的任何 DMA 请求。 一旦在 AHB 目标端口上传输了一半数据,传输一半标志 (HTIF) 便会置 1,如果传输一半中 断使能位 (HTIE) 置 1,还会生成中断。传输结束时,传输完成标志 (TCIF) 便会置 1,如果 传输完成中断使能位 (TCIE) 置 1,还会生成中断。

配置

1、使能 DMA2 时钟,并等待数据流可配置。

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2 时钟使能
while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){}//等待 DMA 可配置

2、初始化 DMA2 数据流 7,包括配置通道,外设地址,存储器地址,传输数据量等。

void DMA_Init(DMA_Stream_TypeDef* DMAy_Streamx, DMA_InitTypeDef* DMA_InitStruct);

3、使能串口 1 的 DMA 发送。

USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口 1 的 DMA 发送 

4、使能 DMA2 数据流 7,启动传输。

void DMA_Cmd(DMA_Stream_TypeDef* DMAy_Streamx, FunctionalState NewState)
DMA_Cmd (DMA2_Stream7,ENABLE);

5、查询 DMA 传输状态。

FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG)
DMA_GetFlagStatus(DMA2_Stream7,DMA_FLAG_TCIF7);
uint16_t DMA_GetCurrDataCounter(DMA_Stream_TypeDef* DMAy_Streamx);
DMA_GetCurrDataCounter(DMA1_Channel4);
void DMA_SetCurrDataCounter(DMA_Stream_TypeDef* DMAy_Streamx, uint16_t Counter);

代码 

#include "dma.h"																	   	  
#include "delay.h"		 
 
//DMAx的各通道配置
//chx:DMA通道选择,@ref DMA_channel DMA_Channel_0~DMA_Channel_7
//par:外设地址
//mar:存储器地址
//ndtr:数据传输量  
void MYDMA_Config(DMA_Stream_TypeDef *DMA_Streamx,u32 chx,u32 par,u32 mar,u16 ndtr)
{ 
 
	DMA_InitTypeDef  DMA_InitStructure;
	
	if((u32)DMA_Streamx>(u32)DMA2)//得到当前stream是属于DMA2还是DMA1,数据流大于DMA2的地址
	{
	  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能 
		
	}else 
	{
	  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);//DMA1时钟使能 
	}
 
	 DMA_DeInit(DMA_Streamx);
	while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){}//等待DMA可配置 
		

	
  /* 配置 DMA Stream */
		DMA_InitStructure.DMA_BufferSize = ndtr;//数据传输量
		DMA_InitStructure.DMA_Channel = chx;//通道选择
		DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;//存储器到外设模式
		DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;// 不使用FIFO
		DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;//设置FIFO范围,在此可以不设
		DMA_InitStructure.DMA_Memory0BaseAddr = mar;//DMA 存储器0地址
		DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
		DMA_InitStructure.DMA_MemoryDataSize =DMA_MemoryDataSize_Byte;//存储器数据长度:8位
		DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器增量模式
		DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 使用普通模式
		DMA_InitStructure.DMA_PeripheralBaseAddr = par;//DMA外设地址
		DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//存储器突发单次传输
		DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据长度:8位
		DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式
		DMA_InitStructure.DMA_Priority = DMA_Priority_Low;	//低优先级
		DMA_Init(DMA_Streamx, &DMA_InitStructure);//初始化DMA Stream
	

} 
//开启一次DMA传输
//DMA_Streamx:DMA数据流,DMA1_Stream0~7/DMA2_Stream0~7 
//ndtr:数据传输量  
void MYDMA_Enable(DMA_Stream_TypeDef *DMA_Streamx,u16 ndtr)
{
 
	DMA_Cmd(DMA_Streamx, DISABLE);                      //关闭DMA传输 
	
	while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){}	//确保DMA可以被设置  
		
	DMA_SetCurrDataCounter(DMA_Streamx,ndtr);          //数据传输量  
 
	DMA_Cmd(DMA_Streamx, ENABLE);                      //开启DMA传输 
}	  

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"  
#include "dma.h"


#define SEND_BUF_SIZE 8200	//发送数据长度,最好等于sizeof(TEXT_TO_SEND)+2的整数倍.

u8 SendBuff[SEND_BUF_SIZE];	//发送数据缓冲区
const u8 TEXT_TO_SEND[]={"ALIENTEK Explorer STM32F4 DMA 串口实验"};	 

  
int main(void)
{ 
	u16 i;
	u8 t=0;
	u8 j,mask=0;
	u8 pro=0;//进度
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
	delay_init(168);     //初始化延时函数
	uart_init(115200);	//初始化串口波特率为115200
	LED_Init();					//初始化LED  
	KEY_Init(); 				//按键初始化 
 	MYDMA_Config(DMA2_Stream7,DMA_Channel_4,(u32)&USART1->DR,(u32)SendBuff,SEND_BUF_SIZE);//DMA2,STEAM7,CH4,外设为串口1,存储器为SendBuff,长度为:SEND_BUF_SIZE.     	 
 //显示提示信息	
	j=sizeof(TEXT_TO_SEND);	   
	for(i=0;i<SEND_BUF_SIZE;i++)//填充ASCII字符集数据
    {
		if(t>=j)//加入换行符
		{
			if(mask)
			{
				SendBuff[i]=0x0a;
				t=0;
			}else 
			{
				SendBuff[i]=0x0d;
				mask++;
			}	
		}else//复制TEXT_TO_SEND语句
		{
			mask=0;
			SendBuff[i]=TEXT_TO_SEND[t];
			t++;
		}   	   
    }		 	  
	i=0;
	while(1)
	{
		t=KEY_Scan(0);
		if(t==KEY0_PRES)  //KEY0按下
		{
			printf("\r\nDMA DATA:\r\n"); 	         
      USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);  //使能串口1的DMA发送     
			MYDMA_Enable(DMA2_Stream7,SEND_BUF_SIZE);     //开始一次DMA传输!	  
		    //等待DMA传输完成,此时我们来做另外一些事,点灯
		    //实际应用中,传输数据期间,可以执行另外的任务
	 
		    while(1)
		    {
				if(DMA_GetFlagStatus(DMA2_Stream7,DMA_FLAG_TCIF7)!=RESET)//等待DMA2_Steam7传输完成
				{ 
					DMA_ClearFlag(DMA2_Stream7,DMA_FLAG_TCIF7);//清除DMA2_Steam7传输完成标志
					break; 
		        }
				pro=DMA_GetCurrDataCounter(DMA2_Stream7);//得到当前还剩余多少个数据 
				printf("%d",pro); 
		    }			     
			printf ("Transimit Finished!");//提示传送完成
		}
		i++;
		delay_ms(10);
		if(i==200)
		{
			LED0=!LED0;//提示系统正在运行	
			i=0;
		}		   
	}		    
}

总结 

        本节MDA的知识点就介绍到这了,因为我自己也不是特别了解,以后也会慢慢研究,谢谢大家观看了。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值