关于STM32 DMA传输的理解

目录

  • 1. DMA是什么
  • 2. CPU如何传输数据
  • 3. DMA的传输过程
  • 4. DMA一些特性
  • 5. DMA各通道对应外设
  • 6. DMA 实例之串口通信
    • 6.1 传输过程的分析
      • 6.1.1 DMA_CCRx寄存器
      • 6.1.2 通道传输数据量
      • 6.1.3 通道传输中断
    • 6.2 通道配置过程
    • 6.3 代码配置过程
      • 6.3.1 部分代码理解

1. DMA是什么

  DMA (Direct Memory Access),直接存储器存取,是一种不经过CPU而直接从内存存取数据的数据交换模式,因而被广泛地使用。早在 8086 的应用中,就已经有 Intel 的 8237 这种典型的 DMA 控制器。而 STM32 的 DMA 则是以类似外设的形式,添加到 Cortex 内核之外的。
  DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路,能使CPU的效率大为提高。
  DMA由硬件实现,从共用系统数据总线的角度看,DMA和CPU是竞争对手的关系,在实现DMA传输时,是由DMA控制器直接掌管总线,在结束DMA传输后,DMA控制器应立即把总线控制权再交回给CPU

系统总线包括:

  • 数据总线:用于CPU与主存储器、CPU与I/O接口之间传送数据
  • 地址总线:用于CPU访问主存储器或外部设备时,传送相关的地址
  • 控制总线:用于传送CPU对主存储器和外部设备的控制信号
    即任何一个部件只要按照标准挂接到总线上,就进入了系统,就可以在CPU统一控制下进行工作

2. CPU如何传输数据

  在DMA出现之前,CPU与外设之间的数据传送方式有程序传送方式、中断传送方式,均由软件实现。CPU是通过系统总线与其他部件连接并进行数据传输。

① 程序控制方式
  1) 无条件传送方式
  也叫做同步传送方式,微机系统中的一些简单的外设,如开关、继电器、数码管、发光二极管等,在它们工作时,可以认为输入设备已随时准备好向CPU提供数据,而输出设备也随时准备好接收CPU送来的数据,这样,在CPU需要同外设交换信息时,就能够用IN或OUT指令直接对这些外设进行输入/输出操作。由于在这种方式下CPU对外设进行输入/输出操作时无需考虑外设的状态,故称之为无条件传送方式。可以简单理解为CPU直接读取某个引脚的状态。
  2) 条件传送
  也叫做查询式传送方式,在开始传送数据前,必须要确认外设是否已经准备好接受数据的状态。
  是指在执行输入指令(IN)或输出指令(OUT)前,要先查询相应设备的状态,当输入设备处于准备好状态、输出设备处于空闲状态时,CPU才执行输入/输出指令与外设交换信息。为此,接口电路中既要有数据端口,还要有状态端口。比如iic传输中,读取数据前需要先等待设备响应后才能读取。

② 中断传送方式
  用查询方式,CPU要不断的查询外设的状态,很浪费时间,CPU工作效率很低。采用中断方式之后,CPU不需要查询外设的状态,而是执行主程序时,当外设数据准备好之后向CPU发出中断申请,因此CPU工作效率大大提高。但是CPU处理中断请求时需要进行断点和现场的保护和恢复,浪费了很多CPU的时间,适合少量数据的传送。

  软件方式实现数据传输的过程:
  在硬件系统中,主要由 CPU(内核)、外设、内存(SRAM)、总线等结构组成,数据经常要在内存与外设之间转移,或从外设 A 转移到外设 B。
  例如:当 CPU 需要处理由 ADC 外设采集回来的数据时,CPU 首先要把数据从 ADC 外设的寄存器读取到内存中(变量),然后进行运算处理,这是一般的处理方法。

关于STM32 DMA传输的理解_第1张图片

  其过程是内核通过 DCode 经过总线矩阵协调,使用 AHB 把外设 ADC 采集的数据读取到内核,暂时存放在内核的内存中(作为临时数据),然后内核通过 DCode 、再通过总线矩阵协调,把数据存放到内存 SRAM 中,最后再由CPU的运算器读取内存中的数据进行处理运算。

   ③ DMA传输
  DMA 正好可以取代上述的工作,其由硬件完成。即由 DMA 控制器的DMA 总线与总线矩阵协调,使用 AHB 把外设 ADC 的数据经由 DMA 通道存放到内存 SRAM,是点到点的数据转移,而不是使用 DMA 的方式还要以内核来作为中转站。在这个数据传输的过程中,不需要内核的全程参与。
  在DMA方式下,CPU连到这些总线上的线处于第三态(高阻状态),而由DMA控制器接管,控制传送的字节数,判断DMA是否结束,以及发出DMA结束信号。

3. DMA的传输过程

  一个完整的DMA传输过程必须经过DMA请求、DMA响应、DMA传输、DMA结束4个步骤。
① 请求
  CPU对DMA控制器初始化,并向I/O接口发出操作命令,I/O接口提出DMA请求。

DMA中断和普通中断的区别
  两者最大的不同表现为对CPU的干扰程度不同:DMA 传送方式的优先级高于程序中断,两者的区别主要表现在对CPU的干扰程度不同。中断请求不但使CPU停下来,而且要CPU执行中断服务程序为中断请求服务,这个请求包括了对断点和现场的处理以及CPU与外设的传送,所以CPU付出了很多的代价;DMA请求仅仅使CPU暂停一下,不需要对断点和现场的处理,并且是由DMA控制外设与主存之间的数据传送,无需CPU的干预,DMA只是借用了一点CPU的时间而已。
  还有一个区别就是,CPU对这两个请求的响应时间不同,对中断请求一般都在执行完一条指令的时钟周期末尾响应,而对DMA的请求,由于考虑它的高效性,CPU在每条指令执行的各个阶段之中都可以让给DMA使用,是立即响应。

② 响应
  DMA控制器对DMA请求判别优先级及屏蔽,向总线裁决逻辑提出总线请求。当CPU执行完当前总线周期即可释放总线控制权。此时,总线裁决逻辑输出总线应答,表示DMA已经响应,通过DMA控制器通知I/O接口开始DMA传输。
③ 传输
  DMA控制器获得总线控制权后,CPU即刻挂起或只执行内部操作,由DMA控制器输出读写命令,直接控制RAM与I/O接口进行DMA传输。
  在DMA控制器的控制下,在存储器和外部设备之间直接进行数据传送,在传送过程中不需要中央处理器的参与。开始时需提供要传送的数据的起始位置和数据长度。
④ 结束
  当完成规定的成批数据传送后,DMA控制器即释放总线控制权,并向I/O接口发出结束信号。当I/O接口收到结束信号后,一方面停止I/O设备的工作,另一方面向CPU提出中断请求,使CPU从不介入的状态解脱,并执行一段检查本次DMA传输操作正确性的代码。最后,带着本次操作结果及状态继续执行原来的程序。

  由此可见,DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为RAM与I/O设备开辟一条直接传送数据的通路,使CPU的效率大为提高。

4. DMA一些特性

  STM32 最多有 2 个 DMA 控制器(DMA2 仅存在大容量产品中),DMA1 有 7 个通道。DMA2 有 5个通道。每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁起来协调各个 DMA 请求的优先权。

关于STM32 DMA传输的理解_第2张图片

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

5. DMA各通道对应外设

DMA1

关于STM32 DMA传输的理解_第3张图片

DMA2

关于STM32 DMA传输的理解_第4张图片

6. DMA 实例之串口通信

  以 DMA 的方式使用串口发送数据,实际上是利用 DMA 把数据(数组)从内存转移到外设(串口)。我们知道外设工作的时候,除了转移数据,实质是不需要内核干预的,而数据转移的工作现在交给了 DMA,所以在串口发送数据的时候,内核同时还可以进行其它操作,例如点亮 LED灯。使用的是串口 1 的 DMA1 传送,也就是要用到通道 4。

6.1 传输过程的分析

  在发生一个事件后,外设向DMA控制器发送一个请求信号。DMA控制器根据通道的优先权处理请求。当DMA控制器开始访问发出请求的外设时,DMA控制器立即发送给它一个应答信号。当从DMA控制器得到应答信号时,外设立即释放它的请求。一旦外设释放了这个请求,DMA控制器同时撤销应答信号。如果有更多的请求时,外设可以启动下一个周期。总之,每次DMA传送由3个操作组成:

  • 从外设数据寄存器或者从当前外设/存储器地址寄存器指示的存储器地址取数据,第一次传输时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元。
  • 存数据到外设数据寄存器或者当前外设/存储器地址寄存器指示的存储器地址,第一次传输时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元。
  • 执行一次DMA_CNDTRx寄存器的递减操作,该寄存器包含未完成的操作数目。

6.1.1 DMA_CCRx寄存器

① 由位4[DIR]设置数据传输方向,该位由软件设置和清除。
  0:从外设读  1:从存储器读

② 由位13:12[PL]设置通道优先级
  软件方面:00:低  01:中  10:高  11:最高
  硬件方面:如果2个请求有相同的软件优先级,则较低编号的通道比较高编号的通道有较高的优先权。举个例子,通道2优先于通道4。 注意: 在大容量产品和互联型/产品中DMA1控制器拥有高FDMA2控制器的优先级

③ 由位11:10[MSIZE]设置存储器数据宽度
  00:8位(字节)  01:16位(半字)  10:32位(字)  11:保留

④ 由位9:8[PSIZE]设置外设数据宽度
  00:8位(字节)  01:16位(半字)  10:32位(字)  11:保留

指针增量
当设置为增量模式时,下一个要传输的地址将是前一个地址加上增量值,增量值取决与所选的数据宽度为1、2或4。第一个传输的地址是存放在DMA_CPARx/DMA_CMARx寄存器中地址。在传输过程中,这些寄存器保持它们初始的数值,软件不能改变和读出当前正在传输的地址(它在内部的当前外设/存储器地址寄存器中)。

⑤ 由位7[MINC]设置存储器地址增量模式
  0:不执行存储器地址增量模式操作1:执行存储器地址增量模式操作

⑥ 由位6[PINC]设置外设地址增量模式
  0:不执行外设地址增量模式操作1:执行外设地址增量模式操作

⑦ 由位5[CIRC]设置循环模式
  0:不执行循环模式操作1:执行循环模式操作

6.1.2 通道传输数据量

关于STM32 DMA传输的理解_第5张图片

6.1.3 通道传输中断

关于STM32 DMA传输的理解_第6张图片

6.2 通道配置过程

  • DMA_CPARx寄存器中设置外设寄存器的地址。发生外设数据传输请求时,这个地址将是数据传输的源或目标。
  • DMA_CMARx寄存器中设置数据存储器的地址。发生外设数据传输请求时,传输的数据将从这个地址读出或写入这个地址。
  • 在DMA_ CNDTRx寄存器中设置要传输的数据量。在每个数据传输后,这个数值递减。
  • 在DMA_CCRx寄存器的PL[1:0]位中设置通道的优先级。
  • 在DMA_CCRx寄存器中设置数据传输的方向、循环模式、外设和存储器的增量模式、外设和存储器的数据宽度、传输一半产生中断或传输完成产生中断。
  • 设置DMA_CCRx寄存器的ENABLE位,启动该通道。
  • 一旦启动了DMA通道,它既可响应连到该通道上的外设的DMA请求。
  • 当传输一半的数据后,半传输标志(HTIF)被置1,当设置了允许半传输中断位(HTIE)时,将产生一个中断请求。在数据传输结束后,传输完成标志(TCIF)被置1,当设置了允许传输完成中断位(TCIE)时,将产生一个中断请求。

6.3 代码配置过程

  将利用外部按键 KEY0 来控制 DMA 的传送,每按一次 KEY0,DMA 就传送一次数据到USART1,然后在串口助手上显示进度等信息,同时在DMA数据传输过程进行点亮LED的操作。
  
dma.c文件

#include "dma.h"
DMA_InitTypeDef DMA_InitStructure;
u16 DMA1_MEM_LEN;//保存 DMA 每次数据传送的长度 
//DMA1 的各通道配置
//这里的传输形式是固定的,这点要根据不同的情况来修改
//从存储器->外设模式 /8位数据宽度 /存储器增量模式
//DMA_CHx:DMA 通道 CHx
//cpar:外设地址
//cmar:存储器地址
//cndtr:数据传输量
void MYDMA_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
{
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能 DMA 时钟
	DMA_DeInit(DMA_CHx); //将 DMA 的通道 1 寄存器重设为缺省值
	DMA1_MEM_LEN = cndtr;
	DMA_InitStructure.DMA_PeripheralBaseAddr = cpar; //DMA 外设 ADC 基地址
	DMA_InitStructure.DMA_MemoryBaseAddr = cmar; //DMA 内存基地址
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //数据传输方向内存到外设
	DMA_InitStructure.DMA_BufferSize = cndtr; //DMA 通道的 DMA 缓存的大小
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址不变
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//内存地址寄存器递增
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为 8 位
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度//为 8 位
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常缓存模式
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DM 通道拥有中优先级
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //非内存到内存传输
	DMA_Init(DMA_CHx, &DMA_InitStructure); //初始化 DMA 的通道
} 

//开启一次 DMA 传输
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx)
{ 
	DMA_Cmd(DMA_CHx, DISABLE); //关闭 USART1 TX DMA1 所指示的通道 
	DMA_SetCurrDataCounter(DMA1_Channel4,DMA1_MEM_LEN);//设置 DMA 缓存的大小
	DMA_Cmd(DMA_CHx, ENABLE); //使能 USART1 TX DMA1 所指示的通道
}

  
main.c文件

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

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

u8 SendBuff[SEND_BUF_SIZE];	//发送数据缓冲区
const u8 TEXT_TO_SEND[]={"测试:WarShip STM32F1 DMA 串口实验"};
int main(void)
{	 
	u16 i;
	u8 t=0;
	u8 j,mask=0;
	float pro=0;//进度

	delay_init();	    	 //延时函数初始化	  
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
	uart_init(115200);	 	//串口初始化为115200
	LED_Init();		  		//初始化与LED连接的硬件接口
	KEY_Init();				//按键初始化		 	
 	MYDMA_Config(DMA1_Channel4,(u32)&USART1->DR,(u32)SendBuff,SEND_BUF_SIZE);//DMA1通道4,外设为串口1,存储器为SendBuff,长度SEND_BUF_SIZE.

	//显示提示信息	
	j = sizeof(TEXT_TO_SEND);
	for(i=0;i<SEND_BUF_SIZE;i++)//填充数据到SendBuff
  {
		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按下
		{
		  USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA发送      
			MYDMA_Enable(DMA1_Channel4);//开始一次DMA传输!	  
		    //等待DMA传输完成,此时我们来做另外一些事,点灯
		    //实际应用中,传输数据期间,可以执行另外的任务
		  while(1)
		  {
				if(DMA_GetFlagStatus(DMA1_FLAG_TC4)!=RESET)	//判断通道4传输完成
				{
					DMA_ClearFlag(DMA1_FLAG_TC4);//清除通道4传输完成标志
					break; 
				}
				pro=DMA_GetCurrDataCounter(DMA1_Channel4);//得到当前还剩余多少个数据
				pro=1-pro/SEND_BUF_SIZE;//得到百分比	  
				pro*=100;      //扩大100倍
				LED1 = 0;
				//printf("正在传输……\t%.2f%%\r\n",pro);
		  }
			LED1 = 1;
			//printf("传输完成!\t%.2f%%\r\n",100.00);			
		}
		i++;
		delay_ms(10);
		if(i==20)
		{
			LED0=!LED0;//提示系统正在运行	
			i=0;
		}		   
	}
}

运行结果

关于STM32 DMA传输的理解_第7张图片

6.3.1 部分代码理解

  将存储器中的数组发送至串口,因此设置外设为非增量模式,而存储器为增量模式,因此存储器中发送数据后,指针增量会自动增加。

typedef struct
{
	uint32_t DMA_PeripheralBaseAddr; //外设基地址
	uint32_t DMA_MemoryBaseAddr; //存储器基地址
	uint32_t DMA_DIR; //数据传输方向
	uint32_t DMA_BufferSize; //通道传输数据量
	uint32_t DMA_Peripherallnc;//外设增量模式
	uint32_t DMA_MemoryInc; //存储器增量模式
	uint32_t DMA_PeripheralDataSize; //外设数据宽度
	uint32_t DMA_MemoryDataSize; //存储器数据宽度
	uint32_t DMA_Mode; //模式:是否循环
	uint32_t DMA_Priority; //优先级
	uint32_tDMA_M2M; //是否存储器到存储器方式
}DMA_ InitTypeDef;


//本实例中使用的是非循环模式
#define DMA_Mode_Circular                  ((uint32_t)0x00000020)
#define DMA_Mode_Normal                    ((uint32_t)0x00000000)
#define IS_DMA_MODE(MODE) (((MODE) == DMA_Mode_Circular) || ((MODE) == DMA_Mode_Normal))

//当通道配置为非循环模式时,传输结束后(即传输计数变为0)将不再产生DMA操作
//要开始新的DMA传输,需要在关闭DMA通道的情况下,在DMA_CNDTRx寄存器中重新写入传输数目。
//因此有main函数里以下两行
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA发送      
MYDMA_Enable(DMA1_Channel4);//开始一次DMA传输!(重新写入传输数目)	

  执行MYDMA_Enable(DMA1_Channel4);之后,由DMA控制系统总线进行数据传输,此时CPU会继续执行接下来的代码,即在DMA数据传输的过程进行读取数据的剩余量、点亮LED等操作。




引用参考:
正点原子教程
https://blog.csdn.net/zhejfl/article/details/82555634
https://www.cnblogs.com/cposture/p/4278801.html
https://baike.baidu.com/item/DMA/2385376?fr=aladdin


  谢谢观看,如有错误还望指正!
  (疑问:DMA方式下,CPU是被挂起不工作,还是还能执行其他代码?)

你可能感兴趣的:(STM32学习,嵌入式,STM32)