DMA---直接存储器访问

一.简介

功能:用来搬数据,但是不需要占用 CPU,即在传输数据的时候,CPU 可以干其他的事情,好像是多线程一样。

三种工作方式:

  • 存储器到存储器(SRAM/FLASH);
  • 外设到存储器;
  • 存储器到外设。
  • 分类:
  • DMA1(7个通道);
  • DMA2(5个通道)。
  • 注意:

  • 通道:传输数据的管道;
  • DMA2只存在大容量产品和互联型产品中。
  • 二.框图

  • DMA---直接存储器访问_第1张图片
  • 功能狂徒

1.DMA请求 

外设要想通过 DMA 来传输数据,必须先给 DMA 控制器发送 DMA 请求,DMA 收到请求信号之后,控制器会给外设一个应答信号,当外设应答后且 DMA 控制器收到应答信号之后,就会启动 DMA 的传输,直到传输完毕。

DMA---直接存储器访问_第2张图片

2.通道 

每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。

3.仲裁器

仲裁器管理 DMA 通道请求分为两个阶段。

 

  • 第一阶段属于软件阶段,可以在 DMA_CCRx 寄存器中设置,有 4 个等级:非常高、高、中和低四个优先级;
  • 第二阶段属于硬件阶段,如果两个或以上的 DMA 通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先权越高,比如通道 0 高于通道 1。在大容量产品和互联型产品中,DMA1 控制器拥有高于 DMA2 控制器的优先级。
  •  

三.DMA配置

1.配置方向

外设到存储器 :

外设为源地址,DMA存储器的地址就是目标地址(自定义变量),如ADC采样;

存储器到外设:

外设为目标地址,DMA存储器的地址就是源地址(自定义变量),如串口发送;

存储器到存储器:

需要把DMA_CCR 位 14:MEM2MEM:存储器到存储器模式配置为 1,启动 M2M 模式。

2.配置传输数据大小 

要想数据传输正确,源和目标地址存储的数据宽度还必须一致,串口数据寄存器是 8 位的,所以我们定义的要发送的数据也必须是 8 位。

3.配置传输模式

  • 一次传输:传输一次之后就停止,要想再传输的话,必须关断 DMA 使能后再重新配置后才能继续传输。
  • 循环传输:一次传输完成之后又恢复第一次传输时的配置循环传输,不断的重复。

四.DMA初始化结构体

DMA---直接存储器访问_第3张图片DMA---直接存储器访问_第4张图片

DMA_InitTypeDef结构体

A.DMA_PeripheralBaseAddr:外设地址,设定 DMA_CPAR 寄存器的值;一般设置为外设的数据寄存器地址,如果是存储器到存储器模式则设置为其中一个存储器地址。

B.DMA_Memory0BaseAddr:存储器地址,设定 DMA_CMAR 寄存器值;一般设置为我们自定义存储区的首地址。

C.DMA_DIR:传输方向选择,可选外设到存储器、存储器到外设。它设定DMA_CCR 寄存器的DIR[1:0] 位的值。这里并没有存储器到存储器的方向选择,当使用存储器到存储器时,只需要把其中一个存储器当作外设使用即可。

D.DMA_BufferSize:设定待传输数据数目,初始化设定 DMA_CNDTR 寄存器的值。

E.DMA_PeripheralInc:如果配置为 DMA_PeripheralInc_Enable,使能外设地址自动递增功能,它设定 DMA_CCR 寄存器的 PINC 位的值;一般外设都是只有一个数据寄存器,所以一般不会使能该位。

F. DMA_MemoryInc:如果配置为 DMA_MemoryInc_Enable,使能存储器地址自动递增功能,它设定 DMA_CCR 寄存器的 MINC 位的值;我们自定义的存储区一般都是存放多个数据的,所以要使能存储器地址自动递增功能。

G.DMA_PeripheralDataSize:外设数据宽度,可选字节 (8 位)、半字 (16 位) 和字 (32 位),它设定DMA_CCR 寄存器的 PSIZE[1:0] 位的值。

H.DMA_MemoryDataSize:存储器数据宽度,可选字节 (8 位)、半字 (16 位) 和字 (32 位),它设定DMA_CCR 寄存器的 MSIZE[1:0] 位的值。当外设和存储器之间传数据时,两边的数据宽度应该设置为一致大小。

I.DMA_Mode:DMA 传输模式选择,可选一次传输或者循环传输,它设定 DMA_CCR 寄存器的CIRC 位的值。例程我们的 ADC 采集是持续循环进行的,所以使用循环传输模式。

J. DMA_Priority:软件设置通道的优先级,有 4 个可选优先级分别为非常高、高、中和低,它设定 DMA_CCR 寄存器的 PL[1:0] 位的值。DMA 通道优先级只有在多个 DMA 通道同时使用时才有意义,如果是单个通道,优先级可以随便设置。

K. DMA_M2M:存储器到存储器模式,使用存储器到存储器时用到,设定 DMA_CCR 的位 14 MEN2MEN 即可启动存储器到存储器模式。

五.实验

1.实验一:存储器(flash)到存储器(sram)

实验编程步骤:

  1. 1) 使能 DMA 时钟;
  2. 2) 配置 DMA 数据参数;
  3. 3) 使能 DMA,进行传输;
  4. 4) 等待传输完成,并对源数据和目标地址数据进行比较。
  5.  
  6. 实验目的:
  7. 判断源数据与传输后数据比较结果,相同绿灯点亮,不相同红灯点亮。

dma.c

#include "dma.h"

/* 定义aSRC_Const_Buffer数组作为DMA传输数据源
 * const关键字将aSRC_Const_Buffer数组变量定义为常量类型
 * 表示数据存储在内部的FLASH中
 */
const uint32_t aSRC_Const_Buffer[BUFFER_SIZE]= {
                                    0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
                                    0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20,
                                    0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30,
                                    0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40,
                                    0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50,
                                    0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60,
                                    0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70,
                                    0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80};
/* 定义DMA传输目标存储器
 * 存储在内部的SRAM中																		
 */
uint32_t aDST_Buffer[BUFFER_SIZE];

void DMA_Config(void)
{
	  DMA_InitTypeDef DMA_InitStructure;
	
		// 开启DMA时钟
		RCC_AHBPeriphClockCmd(DMA_CLOCK, ENABLE);
		// 源数据地址
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)aSRC_Const_Buffer;
		// 目标地址
		DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)aDST_Buffer;
		// 方向:外设到存储器(这里的外设是内部的FLASH)	
		DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
		// 传输大小	
		DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE;
		// 外设(内部的FLASH)地址递增	    
		DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
		// 内存地址递增
		DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
		// 外设数据单位	
		DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
		// 内存数据单位
		DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;	 
		// DMA模式,一次或者循环模式
		DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;
		//DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;  
		// 优先级:高	
		DMA_InitStructure.DMA_Priority = DMA_Priority_High;
		// 使能内存到内存的传输
		DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;
		// 配置DMA通道		   
		DMA_Init(DMA_CHANNEL, &DMA_InitStructure);
    //清除DMA数据流传输完成标志位
    DMA_ClearFlag(DMA_FLAG_TC);
		// 使能DMA
		DMA_Cmd(DMA_CHANNEL,ENABLE);
}

/**
  * 判断指定长度的两个数据源是否完全相等,
  * 如果完全相等返回1,只要其中一对数据不相等返回0
  */
uint8_t Buffercmp(const uint32_t* pBuffer, 
                  uint32_t* pBuffer1, uint16_t BufferLength)
{
  /* 数据长度递减 */
  while(BufferLength--)
  {
    /* 判断两个数据源是否对应相等 */
    if(*pBuffer != *pBuffer1)
    {
      /* 对应数据源不相等马上退出函数,并返回0 */
      return 0;
    }
    /* 递增两个数据源的地址指针 */
    pBuffer++;
    pBuffer1++;
  }
  /* 完成判断并且对应数据相对 */
  return 1;  
}

dma.h

#ifndef _DMA_H
#define _DMA_H

#include "stm32f10x.h"

// 要发送的数据大小
#define BUFFER_SIZE     32


// 当使用存储器到存储器模式时候,通道可以随便选,没有硬性的规定
#define DMA_CHANNEL     DMA1_Channel6
#define DMA_CLOCK       RCC_AHBPeriph_DMA1

// 传输完成标志
#define DMA_FLAG_TC     DMA1_FLAG_TC6
																		
uint8_t Buffercmp(const uint32_t* pBuffer, uint32_t* pBuffer1, uint16_t BufferLength);
void DMA_Config(void);		

#endif

led,c

#include "led.h"

void led_gpio_init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	RCC_APB2PeriphClockCmd(LED0_GPIO_CLK|LED0_GPIO_CLK|LED0_GPIO_CLK,ENABLE);//注意需要用到的总线
	GPIO_InitStruct.GPIO_Pin = LED0_GPIO_PIN;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(LED0_GPIO_PORT,&GPIO_InitStruct);
	
	GPIO_InitStruct.GPIO_Pin = LED1_GPIO_PIN;
	GPIO_Init(LED1_GPIO_PORT,&GPIO_InitStruct);
	
	GPIO_InitStruct.GPIO_Pin = LED2_GPIO_PIN;
	GPIO_Init(LED2_GPIO_PORT,&GPIO_InitStruct);
	
	GPIO_ResetBits(LED0_GPIO_PORT,LED0_GPIO_PIN);
	GPIO_ResetBits(LED1_GPIO_PORT,LED1_GPIO_PIN);
	GPIO_ResetBits(LED2_GPIO_PORT,LED2_GPIO_PIN);
	
}


led.h

#ifndef _LED_H
#define _LED_H

#include "stm32f10x.h"

#define LED0_GPIO_CLK			RCC_APB2Periph_GPIOB
#define LED0_GPIO_PORT 		GPIOB
#define LED0_GPIO_PIN			GPIO_Pin_5

#define LED1_GPIO_CLK			RCC_APB2Periph_GPIOB
#define LED1_GPIO_PORT 		GPIOB
#define LED1_GPIO_PIN			GPIO_Pin_6

#define LED2_GPIO_CLK			RCC_APB2Periph_GPIOB
#define LED2_GPIO_PORT 		GPIOB
#define LED2_GPIO_PIN			GPIO_Pin_7

/******************************************************************************************/
/* LED端口定义 */
#define LED0(x)   do{ x ? \
                      GPIO_SetBits(LED0_GPIO_PORT, LED0_GPIO_PIN) : \
                      GPIO_ResetBits(LED0_GPIO_PORT, LED0_GPIO_PIN); \
                  }while(0)      /* LED0翻转 */

#define LED1(x)   do{ x ? \
                      GPIO_SetBits(LED1_GPIO_PORT, LED2_GPIO_PIN) : \
                      GPIO_ResetBits(LED1_GPIO_PORT, LED2_GPIO_PIN); \
                  }while(0)      /* LED0翻转 */

#define LED2(x)   do{ x ? \
                      GPIO_SetBits(LED2_GPIO_PORT, LED2_GPIO_PIN) : \
                      GPIO_ResetBits(LED2_GPIO_PORT, LED2_GPIO_PIN); \
                  }while(0)      /* LED0翻转 */

#define digitalToggle(p,i) {p->ODR ^=i;} //输出反转状态

#define LED0_Toggle	digitalToggle(LED0_GPIO_PORT,LED0_GPIO_PIN)
#define LED1_Toggle	digitalToggle(LED1_GPIO_PORT,LED1_GPIO_PIN)
#define LED2_Toggle	digitalToggle(LED2_GPIO_PORT,LED2_GPIO_PIN)

void led_gpio_init(void);


#endif

main.c

#include "stm32f10x.h"
#include "led.h"
#include "dma.h"

extern const uint32_t aSRC_Const_Buffer[BUFFER_SIZE];
extern uint32_t aDST_Buffer[BUFFER_SIZE];

#define SOFT_DELAY Delay(0x0FFFFF);
void Delay(__IO u32 nCount); 

int main()
{
	/* 定义存放比较结果变量 */
  uint8_t TransferStatus;
	
	led_gpio_init();
	
	LED0(1);
	
	/* 简单延时函数 */
  Delay(0xFFFFFF);  
	
	LED0(0);
	 /* DMA传输配置 */
  DMA_Config(); 
	
	/* 等待DMA传输完成 */
  while(DMA_GetFlagStatus(DMA_FLAG_TC)==RESET)
  {
    
  } 
	
	/* 比较源数据与传输后数据 */
  TransferStatus=Buffercmp(aSRC_Const_Buffer, aDST_Buffer, BUFFER_SIZE);
  
  /* 判断源数据与传输后数据比较结果*/
  if(TransferStatus==0)  
  {
    /* 源数据与传输后数据不相等时RGB彩色灯显示红色 */
    LED1(1);
  }
  else
  { 
    /* 源数据与传输后数据相等时RGB彩色灯显示绿色 */
    LED2(1);
  }
	
	while(1)
	{

		
	}
}


void Delay(__IO uint32_t nCount)	 //简单的延时函数
{
	for(; nCount != 0; nCount--);
}

2.实验二:存储器到外设

实验编程步骤:

 

  • 1) 配置 USART 通信功能;
  • 2) 设置串口 DMA 工作参数;
  • 3) 使能 DMA;
  • 4) DMA 传输同时 CPU 可以运行其他任务。

usart.c

#include  "usart.h"

/**
  * @brief  USART GPIO 配置,工作参数配置
  * @param  无
  * @retval 无
  */
void Usart_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;

	// 打开串口GPIO的时钟
	DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
	
	// 打开串口外设的时钟
	DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);

	// 将USART Tx的GPIO配置为推挽复用模式
	GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);

  // 将USART Rx的GPIO配置为浮空输入模式
	GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
	
	// 配置串口的工作参数
	// 配置波特率
	USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
	// 配置 针数据字长
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	// 配置停止位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	// 配置校验位
	USART_InitStructure.USART_Parity = USART_Parity_No ;
	// 配置硬件流控制
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	// 配置工作模式,收发一起
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	// 完成串口的初始化配置
	USART_Init(DEBUG_USARTx, &USART_InitStructure);	
	
	// 使能串口
	USART_Cmd(DEBUG_USARTx, ENABLE);	    
}


/*****************  发送一个字节 **********************/
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
{
	/* 发送一个字节数据到USART */
	USART_SendData(pUSARTx,ch);
		
	/* 等待发送数据寄存器为空 */
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);	
}

/****************** 发送8位的数组 ************************/
void Usart_SendArray( USART_TypeDef * pUSARTx, uint8_t *array, uint16_t num)
{
  uint8_t i;
	
	for(i=0; i>8;
	/* 取出低八位 */
	temp_l = ch&0XFF;
	
	/* 发送高八位 */
	USART_SendData(pUSARTx,temp_h);	
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
	
	/* 发送低八位 */
	USART_SendData(pUSARTx,temp_l);	
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);	
}

///重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
		/* 发送一个字节数据到串口 */
		USART_SendData(DEBUG_USARTx, (uint8_t) ch);
		
		/* 等待发送完毕 */
		while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);		
	
		return (ch);
}

///重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{
		/* 等待串口输入数据 */
		while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);

		return (int)USART_ReceiveData(DEBUG_USARTx);
}

usart.h

#ifndef _USART_H
#define _USART_H

#include "stm32f10x.h"
#include 

// 串口工作参数宏定义
#define  DEBUG_USARTx                   USART1
#define  DEBUG_USART_CLK                RCC_APB2Periph_USART1
#define  DEBUG_USART_APBxClkCmd         RCC_APB2PeriphClockCmd
#define  DEBUG_USART_BAUDRATE           115200

// USART GPIO 引脚宏定义
#define  DEBUG_USART_GPIO_CLK           (RCC_APB2Periph_GPIOA)
#define  DEBUG_USART_GPIO_APBxClkCmd    RCC_APB2PeriphClockCmd
    
#define  DEBUG_USART_TX_GPIO_PORT       GPIOA   
#define  DEBUG_USART_TX_GPIO_PIN        GPIO_Pin_9
#define  DEBUG_USART_RX_GPIO_PORT       GPIOA
#define  DEBUG_USART_RX_GPIO_PIN        GPIO_Pin_10

void Usart_Init(void);



#endif

dma.c

#include "dma.h"

uint8_t SendBuff[SENDBUFF_SIZE];

/**
  * @brief  USARTx TX DMA 配置,内存到外设(USART1->DR)
  * @param  无
  * @retval 无
  */
void Dma_Init(void)
{
		DMA_InitTypeDef DMA_InitStructure;
	
		// 开启DMA时钟
		RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
		// 设置DMA源地址:串口数据寄存器地址*/
    DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;
		// 内存地址(要传输的变量的指针)
		DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff;
		// 方向:从内存到外设	
		DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
		// 传输大小	
		DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;
		// 外设地址不增	    
		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模式,一次或者循环模式
		DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;
		//DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;	
		// 优先级:中	
		DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; 
		// 禁止内存到内存的传输
		DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
		// 配置DMA通道		   
		DMA_Init(USART_TX_DMA_CHANNEL, &DMA_InitStructure);		
		// 使能DMA
		DMA_Cmd (USART_TX_DMA_CHANNEL,ENABLE);
}


dma.h

#ifndef _DMA_H
#define _DMA_H

#include "stm32f10x.h"

// 串口对应的DMA请求通道
#define  USART_TX_DMA_CHANNEL     DMA1_Channel4
// 外设寄存器地址
#define  USART_DR_ADDRESS        (USART1_BASE+0x04)
// 一次发送的数据量
#define  SENDBUFF_SIZE            5000

void Dma_Init(void);

#endif

led.c

#include "led.h"

void led_gpio_init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	RCC_APB2PeriphClockCmd(LED0_GPIO_CLK,ENABLE);//注意需要用到的总线
	GPIO_InitStruct.GPIO_Pin = LED0_GPIO_PIN;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(LED0_GPIO_PORT,&GPIO_InitStruct);
	GPIO_ResetBits(LED0_GPIO_PORT,LED0_GPIO_PIN);
	
}


led.h

#ifndef _LED_H
#define _LED_H

#include "stm32f10x.h"

#define LED0_GPIO_CLK			RCC_APB2Periph_GPIOB
#define LED0_GPIO_PORT 		GPIOB
#define LED0_GPIO_PIN			GPIO_Pin_5

/******************************************************************************************/
/* LED端口定义 */
#define LED0(x)   do{ x ? \
                      GPIO_SetBits(LED0_GPIO_PORT, LED0_GPIO_PIN) : \
                      GPIO_ResetBits(LED0_GPIO_PORT, LED0_GPIO_PIN); \
                  }while(0)      /* LED0翻转 */

#define digitalToggle(p,i) {p->ODR ^=i;} //输出反转状态

#define LED0_Toggle	digitalToggle(LED0_GPIO_PORT,LED0_GPIO_PIN)

void led_gpio_init(void);


#endif

main.c

#include "stm32f10x.h"
#include "led.h"
#include "dma.h"
#include "usart.h"


extern uint8_t SendBuff[SENDBUFF_SIZE];
static void Delay(__IO u32 nCount); 



int main()
{
	uint16_t i;
	
	Usart_Init();
	Dma_Init();
	led_gpio_init();
	
	/*填充将要发送的数据*/
  for(i=0;i

 

有关外设到存储器的实验我们就先不做了,留到ADC章节我们再讲。

 

你可能感兴趣的:(STM32(固件库),单片机,stm32)