1、DMA简介        

        DMA,即Direct Memory Access,直接存储器存取。DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源。

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

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

2、DMA主要特性

        (1)12个独立的可配置的通道(请求):DMA1有7个通道,DMA2有5个通道。

        (2)每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过软件来配置。

        (3)在同一个DMA模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),优先权设置相等时由硬件决定(请求0优先于请求1,依此类推)。

        (4)独立数据源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。

        (5)源和目标地址必须按数据传输宽度对齐。

        (6)支持循环的缓冲器管理。

        (7)每个通道都有3个事件标志(DMA半传输、DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求。

        (8)存储器和存储器间的传输。

        (9)外设和存储器、存储器和外设之间的传输。

        (10)闪存、SRAM、外设的SRAM、APB1、APB2和AHB外设均可作为访问的源和目标。

        (11)可编程的数据传输数目:最大为65535。

3、DMA框图

        注:(1)DMA2仅存在于大容量产品和互联型产品。

               (2)SPI/2S3、UART4、TIM5、TIM6、TIM7和DAC的DMA请求仅存在于大容量产品和互联型产品。

               (3)ADC3、SDIO和TIM8的DMA请求仅存在于大容量产品。

        不同的外设有着对应的DMA通道,DMA1参考如下:

4、DMA请求处理

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

        总之,每次DMA传送由3个操作组成:

        取数据:从外设数据寄存器或者从当前外设I存储器地址寄存器指示的存储器地址取数据,第一次传输时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元。

        村数据:存数据到外设数据寄存器或者当前外设/存储器地址寄存器指示的存储器地址,第一次传输时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元。

        执行一次DMA_CNDTRx寄存器的递减操作,该寄存器包含未完成的操作数目。

        在通道配置时,要设置外设和存储器各自的起始地址,数据宽度、地址是否自增。

5、DMA通道配置过程

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

        (1)在DMA_CPARx寄存器中设置外设寄存器的地址。发生外设数据传输请求时,这个地址将是数据传输的源或目标。

        (2)在DMA_CMARx寄存器中设置数据存储器的地址。发生外设数据传输请求时,传输的数据将从这个地址读出或写入这个地址。

        (3)在DMA_CNDTRx寄存器中设置要传输的数据量。在每个数据传输后,这个数值递减。

        (4)在DMA_CCRx寄存器的PL[1:0]位中设置通道的优先级。

        (5)在DMA_CCRx寄存器中设置数据传输的方向、循环模式、外设和存储器的增量模式、外设和存储器的数据宽度、传输一半产生中断或传输完成产生中断。

        (6)设置DMA_CCRx寄存器的ENABLE位,启动该通道。

        一旦启动了DMA通道,它既可响应连到该通道上的外设的DMA请求。

        当传输一半的数据后,半传输标志(HTIF)被置1,当设置了允许半传输中断位(HTIE)时,将产生一个中断请求。在数据传输结束后,传输完成标志(TCIF)被置1,当设置了允许传输完成中断位(TCIE)时,将产生一个中断请求。

5.1 循环模式

        循环模式用于处理循环缓冲区和连续的数据传输(如ADC的扫描模式)。在DMA_CCRx寄存器中的CIRC位用于开启这一功能。当启动了循环模式,数据传输的数目变为0时,将会自动地被恢复成配置通道时设置的初值,DMA操作将会继续进行。

5.2 存储器到存储器模式

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

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

6、DMA编程示例

//MyDMA_init.c
#include "stm32f10x.h"                  // Device header
uint16_t MyDMA_Size;

//这里DMA模块不涉及外部电路,所以可以在system中建立
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
	MyDMA_Size = Size;
	
	//第一步,RCC开启DMA的时钟
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	
	//第二步,直接调用DMA_Init,初始化这里的各个参数
	DMA_InitTypeDef DMA_InitStructure;
	DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;	//外设站点的起始地址
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;	//外设站点的数据宽度
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;			//外设站点是否自增
	DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;		//存储器站点的起始地址
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;		//存储器站点的数据宽度
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;				//存储器站点是否自增
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;					//Direction,传输方向
	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使能
	DMA_Cmd(DMA1_Channel1, ENABLE);
	
}

//调用这个函数就再次启动一次DMA转运
void MyDMA_Transfer(void)
{
	//给传输计数器赋值,必须要先让DMA失能
	DMA_Cmd(DMA1_Channel1, DISABLE);
	//给传输计数器赋值
	DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);
	DMA_Cmd(DMA1_Channel1, ENABLE);
	//检查DMA1的转运完成通道标志位,实现等待转运完成的效果
	while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
	//标志位置1后,要清除标志位,这个标志位要手动清除
	DMA_ClearFlag(DMA1_FLAG_TC1);
}
//main.c

//源端数组
uint8_t DataA[] = {0x01, 0x02, 0x03, 0x04};
//目标数组
uint8_t DataB[] = {0, 0, 0, 0};

int main(void)
{
    MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);

    while (1)
	{
		DataA[0] ++;
		DataA[1] ++;
		DataA[2] ++;
		DataA[3] ++;

        Delay_ms(1000);
		
		MyDMA_Transfer();
    }
}

作者:ant-small

物联沃分享整理
物联沃-IOTWORD物联网 » STM32 DMA传输技术详解

发表回复