STM32---DMA控制器(DMA)

以STM32F4为例,以下内容,除非特别说明,否则本部分适用于整个 STM32F4xx 系列。

DMA 简介

直接存储器访问 (DMA) 用于在外设与存储器之间以及存储器与存储器之间提供高速数据传
输。可以在无需任何 CPU 操作的情况下通过 DMA 快速移动数据
。这样节省的 CPU 资源可
供其它操作使用。
在这里插入图片描述

DMA 控制器基于复杂的总线矩阵架构,将功能强大的双 AHB 主总线架构与独立的 FIFO 结
合在一起,优化了系统带宽
两个 DMA 控制器总共有 16 个数据流(每个控制器 8 个),每一个 DMA 控制器都用于管理
一个或多个外设的存储器访问请求。每个数据流总共可以有多达 8 个通道(或称请求)。每
个通道都有一个仲裁器,用于处理 DMA 请求间的优先级

DMA 主要特性

DMA 主要特性是:
双 AHB 主总线架构,一个用于存储器访问,另一个用于外设访问
● 仅支持 32 位访问的 AHB 从编程接口
● 每个 DMA 控制器有 8 个数据流,每个数据流有多达 8 个通道(或称请求)
● 每个数据流有单独的四级 32 位先进先出存储器缓冲区 (FIFO),可用于 FIFO 模式或直
接模式:
— FIFO 模式:可通过软件将阈值级别选取为 FIFO 大小的 1/4、1/2 或 3/4
— 直接模式
每个 DMA 请求会立即启动对存储器的传输。当在直接模式(禁止 FIFO)下将 DMA
请求配置为以存储器到外设模式传输数据时,DMA 仅会将一个数据从存储器预加
载到内部 FIFO,从而确保一旦外设触发 DMA 请求时则立即传输数据。
● 通过硬件可以将每个数据流配置为:
— 支持外设到存储器、存储器到外设和存储器到存储器传输的常规通道
— 也支持在存储器方双缓冲的双缓冲区通道
● 8 个数据流中的每一个都连接到专用硬件 DMA 通道(请求)
● DMA 数据流请求之间的优先级可用软件编程(4 个级别:非常高、高、中、低),在软
件优先级相同的情况下可以通过硬件决定优先级(例如,请求 0 的优先级高于请求 1)

● 每个数据流也支持通过软件触发存储器到存储器的传输(仅限 DMA2 控制器)
可供每个数据流选择的通道请求多达 8 个。此选择可由软件配置,允许几个外设启动 DMA
请求
● 要传输的数据项的数目可以由 DMA 控制器或外设管理:
— DMA 流控制器:要传输的数据项的数目是 1 到 65535,可用软件编程
— 外设流控制器:要传输的数据项的数目未知并由源或目标外设控制,这些外设通过
硬件发出传输结束的信号
● 独立的源和目标传输宽度(字节、半字、字):源和目标的数据宽度不相等时,DMA 自动
封装/解封必要的传输数据来优化带宽。这个特性仅在 FIFO 模式下可用
● 对源和目标的增量或非增量寻址
● 支持 4 个、8 个和 16 个节拍的增量突发传输。突发增量的大小可由软件配置,通常等于
外设 FIFO 大小的一半
● 每个数据流都支持循环缓冲区管理
5 个事件标志(DMA 半传输、DMA 传输完成、DMA 传输错误、DMA FIFO 错误、直接
模式错误),进行逻辑或运算,从而产生每个数据流的单个中断请求

DMA 功能说明

在这里插入图片描述
DMA 控制器执行直接存储器传输:因为采用 AHB 主总线,它可以控制 AHB 总线矩阵来启
动 AHB 事务。

它可以执行下列事务:
● 外设到存储器的传输
● 存储器到外设的传输
● 存储器到存储器的传输
在这里插入图片描述

DMA 控制器提供两个 AHB 主端口:AHB 存储器端口 (用于连接存储器)和 AHB 外设端口
(用于连接外设)。但是,要执行存储器到存储器的传输,AHB 外设端口 必须也能访问存
储器。

AHB 从端口用于对 DMA 控制器进行编程(它仅支持 32 位访问)。
有关两个 DMA 控制器的系统实现。
在这里插入图片描述

DMA 事务

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

每个 DMA 传输包含三项操作:
● 通过 DMA_SxPAR 或 DMA_SxM0AR 寄存器寻址,从外设数据寄存器或存储器单元中
加载数据。
● 通过 DMA_SxPAR 或 DMA_SxM0AR 寄存器寻址,将加载的数据存储到外设数据寄存
器或存储器单元。
● DMA_SxNDTR 计数器在数据存储结束后递减,该计数器中包含仍需执行的事务数。

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

通道选择

每个数据流都与一个 DMA 请求相关联,此 DMA 请求可以从 8 个可能的通道请求中选出。
此选择由 DMA_SxCR 寄存器中的 CHSEL[2:0] 位控制

在这里插入图片描述

DMA 请求映射的示例

在这里插入图片描述

在这里插入图片描述

仲裁器

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

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

DMA 数据流

8 个 DMA 控制器数据流都能够提供源和目标之间的单向传输链路
每个数据流配置后都可以执行:
● 常规类型事务:存储器到外设、外设到存储器或存储器到存储器的传输。
● 双缓冲区类型事务:使用存储器的两个存储器指针的双缓冲区传输(当 DMA 正在进行自/
至缓冲区的读/写操作时,应用程序可以进行至/自其它缓冲区的写/读操作)。
要传输的数据量(多达 65535)可以编程,并与连接到外设 AHB 端口的外设(请求 DMA 传
输)的源宽度相关。每个事务完成后,包含要传输的数据项总量的寄存器都会递减。

DMA 传输通道

每个通道都可以在有固定地址的外设寄存器和存储器地址之间执行DMA传输。DMA传输的数据 量是可编程的,大达到65535。包含要传输的数据项数量的寄存器,在每次传输后递减。

可编程的数据量:
外设和存储器的传输数据量可以通过DMA_CCRx寄存器中的PSIZE和MSIZE位编程。

指针递增模式

根据 DMA_SxCR 寄存器中 PINC 和 MINC 位的状态,外设和存储器指针在每次传输后可以自动向后递增或保持常量。当设置为增量模式时,下一个要传输的地址将是前一个地址加上增量值

通过单个寄存器访问外设源或目标数据时,禁止递增模式十分有用。

如果使能了递增模式,则根据在 DMA_SxCR 寄存器 PSIZE 或 MSIZE 位中编程的数据宽度,下一次传输的地址将是前一次传输的地址递增 1个数据宽度、2个数据宽度或 4个数据宽度。

存储器到存储器模式

DMA通道的操作可以在没有外设请求的情况下进行,这种操作就是存储器到存储器模式。

当设置了DMA_CCRx寄存器中的MEM2MEM位之后,在软件设置了DMA_CCRx寄存器中的EN位启动DMA通道时,DMA传输将马上开始。当DMA_CNDTRx寄存器变为0时,DMA传输结束。存储器到存储器模式不能与循环模式同时使用。

这里要注意仅 DMA2 的外设接口可以访问存储器,所以仅 DMA2 控制器支持存储器到存储器的传输,DMA1 不支持。

存储器到存储器模式不能与循环模式同时使用。

DMA中断

每个DMA通道都可以在DMA传输过半、传输完成和传输错误时产生中断。为应用的灵活性考虑,通过设置寄存器的不同位来打开这些中断。

在这里插入图片描述
使没开启,我们也可以通过查询这些位来获得当前 DMA 传输的状态。这里我们常用的是 TCIFx位,即数据流 x 的 DMA 传输完成与否标志。

可编程的数据传输宽度、对齐方式和数据大小端
当PSIZE和MSIZE不相同时,DMA模块按照下图进行数据对齐。

注意:在大容量产品中, DMA2 通道 4 和 DMA2 通道 5 的中断被映射在同一个中断向量上。在互联型产品 中, DMA2 通道 4 和 DMA2 通道 5 的中断分别有独立的中断向量。所有其他的 DMA 通道都有自己的 中断向量。

DMA的内存占用

在STM32控制器中,芯片采用Cortex-MX架构,总线结构有了很大的优化,DMA占用另外的地址总线,并不会与CPU的系统总线发生冲突。也就是说,DMA的使用不会影响CPU的运行速度

但是要注意:
DMA 控制器和Cortex-M4核共享系统数据总线执行直接存储器数据传输。当CPU和DMA同时访问相同的目标(RAM或外设)时,DMA请求可能会停止 CPU访问系统总线达若干个周期,总线仲裁器执行循环调度,以保证CPU至少可以得到一半的系统总线(存储器或外设)带宽。

DMA配置部分
此部分我们分为DMA寄存器和DMA库函数分别介绍:

DMA寄存器

DMA配置参数包括:通道地址、优先级、数据传输方向、存储器/外设数据宽度、存储器/外设地址是否增量、循环模式、数据传输量。

DMA中断状态寄存器(DMA_ISR)

在这里插入图片描述
我们如果开启了 DMA_ISR 中这些中断,在达到条件后就会跳到中断服务函数里面去,即使 没开启,我们也可以通过查询这些位来获得当前 DMA 传输的状态。这里我们常用的是 TCIFx, 即通道 DMA 传输完成与否的标志。

注意此寄存器为只读寄存器,所以在这些位被置位之后,只 能通过其他的操作来清除

DMA中断标志清除寄存器(DMA_IFCR)

在这里插入图片描述
DMA_IFCR 的各位就是用来清除 DMA_ISR 的对应位的,通过写 0 清除。在 DMA_ISR 被置位后, 我们必须通过向该位寄存器对应的位写入 0 来清除。

DMA通道x配置寄存器(DMA_CCRx)

在这里插入图片描述

该寄存器控制着 DMA 的很多相关 信息,包括数据宽度、外设及存储器的宽度、通道优先级、增量模式、传输方向、中断允许、 使能等都是通过该寄存器来设置的。所以 DMA_CCRx 是 DMA 传输的核心控制寄存器

DMA通道x传输数量寄存器(DMA_CNDTRx)(x = 1…7)

在这里插入图片描述

这个寄存器控制 DMA 通道 x 的每次 传输所要传输的数据量。其设置范围为 0~65535。并且该寄存器的值会随着传输的进行而减少, 当该寄存器的值为 0 的时候就代表此次数据传输已经全部发送完成了。所以可以通过这个寄存 器的值来知道当前 DMA 传输的进度

DMA通道x外设地址寄存器(DMA_CPARx)(x = 1…7)

在这里插入图片描述
该寄存器用来存储 STM32 外设的地 址,比如我们使用串口 1,那么该寄存器必须写入 0x40013804(其实就是&USART1_DR)。如果使 用其他外设,就修改成相应外设的地址就行了。

DMA通道x配置寄存器(DMA_CMARx)

在这里插入图片描述

DMA寄存器配置流程

通道配置过程 下面是配置DMA通道x的过程(x代表通道号):

在DMA_CPARx寄存器中设置外设寄存器的地址。发生外设数据传输请求时,这个地址将 是数据传输的源或目标。
在DMA_CMARx寄存器中设置数据存储器的地址。发生外设数据传输请求时,传输的数 据将从这个地址读出或写入这个地址。
在DMA_CNDTRx寄存器中设置要传输的数据量。在每个数据传输后,这个数值递减。
在DMA_CCRx寄存器的PL[1:0]位中设置通道的优先级。
在DMA_CCRx寄存器中设置数据传输的方向、循环模式、外设和存储器的增量模式、外 设和存储器的数据宽度、传输一半产生中断或传输完成产生中断。
设置DMA_CCRx寄存器的ENABLE位,启动该通道。

一旦启动了DMA通道,它既可响应连到该通道上的外设的DMA请求。 当传输一半的数据后,半传输标志(HTIF)被置1,当设置了允许半传输中断位(HTIE)时,将产生 一个中断请求。在数据传输结束后,传输完成标志(TCIF)被置1,当设置了允许传输完成中断位 (TCIE)时,将产生一个中断请求。

DMA库函数配置过程:
使能DMA时钟:RCC_AHBPeriphClockCmd();
初始化DMA通道:DMA_Init();
//设置通道;传输地址;传输方向;传输数据的数目;传输数据宽度;传输模式;优先级;是否开启存储器到存储器。
使能外设DMA;
以串口为例:使能串口DMA发送,串口DMA使能函数。调用函数:USART_DMACmd();
使能DMA通道传输;函数:DMA_Cmd();
查询DMA传输状态。函数:DMA_GetFlagStatus();
获取当前剩余数据量大小 函数: DMA_GetCurrDataCounter(DMA1_Channel4);

UART DMA传输

DMA就是一个搬运工,可以将数据从一个位置搬运到另一个位置。
以UART为例,如果要接收数据,会触发UART中断,然后CPU介入,在中断中通过CPU将UART输入寄存器的值读出来,存放到内存中;
而DMA方式,产生UART中断后,DMA直接参与,把UART输入寄存器的值搬运到内存中,CPU只需要在去检查内存的值就好了,这样提高了CPU的效率。

DMA代码配置

DMA初始化配置

void dma_init()
{
    
    
 
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);

/*DMA配置*/

DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base;//串口数据寄存器地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)SendBuff; //内存地址(要传输的变量的指针)
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //方向(从内存到外设)
DMA_InitStructure.DMA_BufferSize = 500; //传输内容的大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址不增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址自增
DMA_InitStructure.DMA_PeripheralDataSize =
DMA_PeripheralDataSize_Byte ; //外设数据单位
DMA_InitStructure.DMA_MemoryDataSize =
DMA_MemoryDataSize_Byte ; //内存数据单位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ; //DMA模式:一次传输,循环
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium ; //优先级:高
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //禁止内存到内存的传输
 
DMA_Init(DMA1_Channel4, &DMA_InitStructure); //配置DMA1的4通道
DMA_Cmd(DMA1_Channel4,ENABLE);
DMA_SetCurrDataCounter(DMA_CH4,DMA1_MEM_LEN);//DMA通道的DMA缓存的大小
DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE);//配置DMA发送完成后产生中断
 
}

//DMA中断


void DMA1_Channel4_IRQHandler(void)
{
    
    
	if(DMA_GetFlagStatus(DMA1_FLAG_TC4)==SET)
	{
    
    
	
		DMA_ClearFlag(DMA1_FLAG_TC4);
	}
}

main函数

#define SEND_BUF_SIZE 500	//发送数据长度,最好等于sizeof(TEXT_TO_SEND)+2的整数倍.
 
u8 SendBuff[SEND_BUF_SIZE];	//发送数据缓冲区
const u8 TEXT_TO_SEND[]={
    
    "STM32F1 DMA 串口实验"};
 uint16_t i;
int main(void)
{
    
    	   
	uart_init(115200);	 	//串口初始化为115200
		
	for(i=0;i<500;i++)
	{
    
    
	SendBuff[i] =0xaf;
	}	
	USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);  //使能串口dma传输 
	
	while(1)
	;
}

猜你喜欢

转载自blog.csdn.net/weixin_43491077/article/details/109688420