STM32笔记(十)---DMA(直接存储器访问)

DMA(直接存储器访问)

文章目录

    • DMA(直接存储器访问)
        • 一、 DMA简介
        • 二、 DMA功能框图
          • 2-1 DMA请求
          • 2-2 通道
          • 2-3 仲裁器
        • 三、 DMA相关库函数
          • 3-1 配置思路
          • 3-2 DMA 存储器到存储器模式实验
            • 编程要点
          • 3-3 DMA 存储器到外设模式实验
            • 编程要点

一、 DMA简介

DMA(Data Memory Access),直接存储器访问。主要功能是可以把数据从一个地方搬到另外一个地方,而且不占用CPU。通常为以下传输方式:

  • M->M,存储器到存储器,一般指内部存储器(Flash)与外部存储器(SRAM)的传输。
  • P->M, 外设到存储器,一般指外设(Periph)与外部存储器的传输。
  • M->P, 存储器到外设,一般指外部存储器与外设(Periph)的传输。

STM32系列一般分为DMA1、DMA2,两者均可以实现 P->M, M->P,M->M 。

区别:DMA1有7个通道、DMA2有5个通道,同时DMA2在大容量、互联型的芯片上配备。

二、 DMA功能框图

功能框图
STM32笔记(十)---DMA(直接存储器访问)_第1张图片
功能框图可具体分为三块:

  • DMA请求
  • 通道
  • 仲裁器
2-1 DMA请求
  • DMA1请求映射

    STM32笔记(十)---DMA(直接存储器访问)_第2张图片

  • DMA2请求映射

    STM32笔记(十)---DMA(直接存储器访问)_第3张图片

    (ADC3/SDIO/TIM8 的DMA请求只有大容量的单片机才有)

2-2 通道

​ DMA 具有 12 个独立可编程的通道,其中 DMA1 有 7 个通道, DMA2 有 5 个通道,每个通道对应不同的外设的 DMA 请求。虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。

2-3 仲裁器

​ 当发生多个 DMA 通道请求时, 就意味着有先后响应处理的顺序问题,这个就由仲裁器也管理。

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

  • 第一阶段属于软件阶段, 可以在DMA_CCRx 寄存器中设置,有 4 个等级:非常高、高、中和低四个优先级。

  • 第二阶段属于硬件阶段,如果两个或以上的 DMA 通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先权越高, 比如通道 0 高于通道 1。

    在大容量产品和互联型产品中,DMA1 控制器拥有高于 DMA2 控制器的优先级。

三、 DMA相关库函数

3-1 配置思路

​ 使用 DMA,最核心就是配置要传输的数据,包括数据从哪里来,要到哪里去,传输的数据的单位是什么,要传多少数据,是一次传输还是循环传输等等。

​ 先放出DMA初始化结构体:DMA_ InitTypeDef

typedef struct
{
	uint32_t DMA_PeripheralBaseAddr; // 外设地址
	uint32_t DMA_MemoryBaseAddr; // 存储器地址
	uint32_t DMA_DIR; // 传输方向
	uint32_t DMA_BufferSize; // 传输数目
	uint32_t DMA_PeripheralInc; // 外设地址增量模式
	uint32_t DMA_MemoryInc; // 存储器地址增量模式
	uint32_t DMA_PeripheralDataSize; // 外设数据宽度
	uint32_t DMA_MemoryDataSize; // 存储器数据宽度
	uint32_t DMA_Mode; // 模式选择
	uint32_t DMA_Priority; // 通道优先级
	uint32_t DMA_M2M; // 存储器到存储器模式
} DMA_InitTypeDef;
  • 从哪里来到哪里去

    ​ DMA 传输数据的方向**(对应结构体内容:DMA_DIR:传输方向选择,可选外设到存储器、存储器到外设。它设定DMA_CCR 寄存器的 DIR[1:0]位的值。 这里并没有存储器到存储器的方向选择,当使用存储器到存储器时, 只需要把其中一个存储器当作外设使用即可)**有三个:从外设到存储器,从存储器到外设,从存储器到存储器。 具体的方向由DMA_CCR 寄存器的位 4 DIR 进行配置: 0 表示从外设到存储器, 1 表示从存储器到外设。 这里面涉及到的外设地址由 DMA_CPAR 寄存器配置,存储器地址由 DMA_CMAR 寄存器配置。

    • 外设到存储器
      当我们使用从外设到存储器传输时,以 ADC 采集为例。 DMA 外设寄存器的地址**(对应结构体内容:DMA_PeripheralBaseAddr:外设地址,设定 DMA_CPAR 寄存器的值;一般设置为外设的数据寄存器地址,如果是存储器到存储器模式则设置为其中一个存储器地址)对应的就是 ADC 数据寄存器的地址, DMA 存储器的地址(对应结构体内容:DMA_Memory0BaseAddr:存储器地址,设定 DMA_CMAR 寄存器值;一般设置为我们自定义存储区的首地址)**就是我们自定义的变量(用来接收存储 AD 采集的数据) 的地址。 方向我们设置外设为源地址。
    • 存储器到外设
      当我们使用从存储器到外设传输时,以串口向电脑端发送数据为例。 DMA 外设寄存器的地址对应的就是串口数据寄存器的地址, DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储通过串口发送到电脑的数据) 的地址。方向我们设置外设为目标地址。
    • 存储器到存储器
      当我们使用从存储器到存储器传输时,以内部 FLASH 向内部 SRAM 复制数据为例。DMA 外设寄存器的地址对应的就是内部 FLASH(我们这里把内部 FALSH 当作一个外设来看) 的地址, DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储来自内部 FLASH 的数据)的地址。 方向我们设置外设(即内部 FLASH) 为源地址。 跟上面两个不一样的是,这里需要把 DMA_CCR 位 14: MEM2MEM:存储器到存储器模式配置为 1,启动 M2M 模式。(对应结构体内容:DMA_M2M :存储器到存储器模式 , 使用存储器到存储 器时 用到, 设定DMA_CCR 的位 14 MEN2MEN 即可启动存储器到存储器模式)
  • 要传多少,单位是什么
    当我们配置好数据要从哪里来到哪里去之后,我们还需要知道我们要传输的数据是多少,数据的单位是什么。
    以串口向电脑发送数据为例,我们可以一次性给电脑发送很多数据,具体多少由DMA_CNDTR 寄存器配置,这是一个 32 位的寄存器,一次最多只能传输 65535 个数据**(对应结构体内容:DMA_BufferSize:设定待传输数据数目,初始化设定 DMA_CNDTR 寄存器的值)
    要想数据传输正确,源和目标地址存储的数据宽度还必须一致,串口数据寄存器是 8位的,所以我们定义的要发送的数据也必须是 8 位。外设的数据宽度由 DMA_CCRx 的PSIZE[1:0]配置,可以是 8/16/32 位,存储器的数据宽度由 DMA_CCRx 的 MSIZE[1:0]配置,可以是 8/16/32 位
    (对应结构体内容:DMA_PeripheralDataSize:外设数据宽度,可选字节(8 位)、半字(16 位)和字(32位),它设定 DMA_CCR 寄存器的 PSIZE[1:0]位的值 || DMA_MemoryDataSize:存储器数据宽度,可选字节(8 位)、半字(16 位)和字(32位),它设定 DMA_CCR 寄存器的 MSIZE[1:0]位的值。 当外设和存储器之间传数据时,两边的数据宽度应该设置为一致大小)
    在 DMA 控制器的控制下,数据要想有条不紊的从一个地方搬到另外一个地方,还必须正确设置两边数据指针的增量模式
    (对应结构体内容:DMA_PeripheralInc:如果配置为 DMA_PeripheralInc_Enable,使能外设地址自动递增功能,它设定 DMA_CCR 寄存器的 PINC 位的值;一般外设都是只有一个数据寄存器,所以一般不会使能该位||DMA_MemoryInc:如果配置为 DMA_MemoryInc_Enable,使能存储器地址自动递增功能,它设定 DMA_CCR 寄存器的 MINC 位的值;我们自定义的存储区一般都是存放多个数据的,所以要使能存储器地址自动递增功能)。 外设的地址指针由 DMA_CCRx 的 PINC 配置,存储器的地址指针由 MINC 配置。 以串口向电脑发送数据为例,要发送的数据很多,每发送完一个,那么存储器的地址指针就应该加 1,而串口数据寄存器只有一个,那么外设的地址指针就固定不变。具体的数据指针的增量模式由实际情况决定。 同时,多个DMA通道与不同DMA有仲裁管理的优先级设置(对应结构体内容:DMA_Priority:软件设置通道的优先级,有 4 个可选优先级分别为非常高、高、中和低,它设定 DMA_CCR 寄存器的 PL[1:0]位的值。 DMA 通道优先级只有在多个 DMA 通道同时使用时才有意义,如果是单个通道,优先级可以随便设置)**

  • **什么时候传输完成 **

    ​ 数据什么时候传输完成,我们可以通过查询标志位或者通过中断的方式来鉴别。 每个DMA 通道在 DMA 传输过半、传输完成和传输错误时都会有相应的标志位,如果使能了该类型的中断后,则会产生中断。各个标志位在 DMA 中断状态寄存器DMA_ISR 有详细描述。
    ​ 传输完成还分两种模式,是一次传输还是循环传输,一次传输很好理解,即是传输一次之后就停止, 要想再传输的话,必须关断 DMA 使能后再重新配置后才能继续传输。 循环传输则是一次传输完成之后又恢复第一次传输时的配置循环传输,不断的重复。具体的由 DMA_CCRx 寄存器的 CIRC 循环模式位控制**(对应结构体内容:DMA_Mode: DMA 传输模式选择, 可选一次传输或者循环传输,它设定DMA_CCR 寄存器的 CIRC 位的值。 例程我们的 ADC 采集是持续循环进行的,所以使用循环传输模式)**。

3-2 DMA 存储器到存储器模式实验
  • 存储器到存储器模式可以实现数据在两个内存的快速拷贝。定义一个静态的源数据, 存放在内部 FLASH,然后使用 DMA 传输把源数据拷贝到目标地址上(内部 SRAM) ,最后对比源数据和目标地址的数据,看看是否传输准确。
编程要点
  1. 使能 DMA 时钟;
  2. 配置 DMA 数据参数;
  3. 使能 DMA,进行传输;
  4. 等待传输完成,并对源数据和目标地址数据进行比较。

‘bsp-dma.h’

#ifndef __BSP_DMA_H__
#define __BSP_DMA_H__

#include "stm32f10x.h"

#define BUFFER_SIZE 			32
#define DMA_M2M_CLK				RCC_AHBPeriph_DMA1
#define DMA_M2M_CHANNEL			DMA1_Channel6
#define DMA_M2M_FLAG_TC			DMA1_FLAG_TC6

uint8_t Buffercmp(const uint32_t* pBuffer,uint32_t* pBuffer1, uint16_t BufferLength);
void DMA_M2M_Config(void);

‘bsp-dma.c’

#include "bsp-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];
/**
  * DMA_M2M配置
  */		
void DMA_M2M_Config(void)
{
	DMA_InitTypeDef DMA_InitStructure;
	
	RCC_AHBPeriphClockCmd(DMA_M2M_CLK, ENABLE);								//使能AHB时钟
	
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)aSRC_Const_Buffer;	//外设(Flash)地址
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)aDST_Buffer;			//存储器地址
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;						//外设Flash作为发送源
	DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE;							//发送数据数目
	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_InitStructure.DMA_Mode = DMA_Mode_Normal;							//模式选择正常(不进行循环)
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;						//通道优先级设置为高
	DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;								//存储器到存储器模式开启
	
	DMA_Init(DMA_M2M_CHANNEL, &DMA_InitStructure);							//DMA通道初始化
	DMA_ClearFlag(DMA_M2M_FLAG_TC);											//发送完成位先进行清楚操作
	DMA_Cmd(DMA_M2M_CHANNEL, ENABLE);										//DMA使能
}

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

‘main.c’

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

int main(void)
 {
 
	uint8_t TransferStatus;		// 定义存放比较结果变量 
	LED_GPIO_Config();			// LED 端口初始化 	
	LED_PURPLE;					// 设置 RGB 彩色灯为紫色
	Delay(0xFFFFFF);			// 简单延时函数 
	DMA_Config();				// DMA 传输配置 
	while (DMA_GetFlagStatus(DMA_FLAG_TC)==RESET) 
	{
		//等待 DMA 传输完成
	}
	//比较源数据与传输后数据
	TransferStatus=Buffercmp(aSRC_Const_Buffer, aDST_Buffer, BUFFER_SIZE);
	//判断源数据与传输后数据比较结果
	if (TransferStatus==0)
	{
	//源数据与传输后数据不相等时 RGB 彩色灯显示红色
		LED_RED;
	}
	else
	{
	//源数据与传输后数据相等时 RGB 彩色灯显示蓝色
		LED_BLUE;
	}
	while (1)
	{
	}
}
3-3 DMA 存储器到外设模式实验
  • 定义一个数据变量,存于 SRAM 中,然后通过 DMA 的方式传输到串口的数据寄存器,然后通过串口把这些数据发送到电脑的上位机显示出来。
编程要点
  1. 配置 USART 通信功能;
  2. 设置串口 DMA 工作参数;
  3. 使能 DMA;
  4. DMA 传输同时 CPU 可以运行其他任务。

‘bsp-dma.h’

#ifndef __BSP_DMA_H__
#define __BSP_DMA_H__

#include "stm32f10x.h"
		
// 串口工作参数宏定义
#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

#define	 DMA_USART_TX_CLK				RCC_AHBPeriph_DMA1
#define  DMA_USART_TX_CHANNEL           DMA1_Channel4
#define  DMA_USART_TX_FLAG_TC           DMA1_FLAG_TC4
#define  USART_DR_ADDRESS        		(USART1_BASE+0x04)
#define  SENDBUFF_SIZE            		5000

void USART_Config(void);
void DMA_USARTx_Config(void);

‘bsp-dma.c’

#include "bsp-dma.h"

void USART_Config(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);	    										
}

// Memory -> P (USART->DR)
void DMA_USARTx_Config(void)
{
	DMA_InitTypeDef DMA_InitStruct;
	
	RCC_AHBPeriphClockCmd(DMA_USART_TX_CLK, ENABLE);						//使能DMA1时钟
	
	DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)USART_DR_ADDRESS;		//外设为USART_DR寄存器
	DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)SendBuff;					//存储器地址为定义数组变量地址	
	DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralDST;							//方向为外设接收端
	DMA_InitStruct.DMA_BufferSize = SENDBUFF_SIZE;							//数据数量为SENDBUFF_SIZE:5000
	DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;			//外设地址只有一个USART,不增加
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;	//发送数据大小为存储器发送端定义的一个字节(8位)
	DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;					//存储器为数组,需增量地址
	DMA_InitStruct.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte;		//数据一个字节大小
	DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;								//不进行循环
	DMA_InitStruct.DMA_Priority = DMA_Priority_VeryHigh;					//超高优先级
	DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;								//不开启存储器与存储器模式
	
	DMA_Init(DMA_USART_TX_CHANNEL, &DMA_InitStruct);
	DMA_ClearFlag(DMA_USART_TX_FLAG_TC);
	DMA_Cmd(DMA_USART_TX_CHANNEL, ENABLE);
}

‘main.c’

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

extern uint8_t SendBuff[SENDBUFF_SIZE];

int main(void)
{
	uint16_t i;
	LED_GPIO_Config();
	USART_Config();
	DMA_USARTx_Config();
	
	  /*填充将要发送的数据*/
	for(i=0;i<SENDBUFF_SIZE;i++)
	{
		SendBuff[i]	 = 'P';
	}
	/* USART1 向 DMA发出TX请求 */
	USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Tx, ENABLE);
	/*为演示DMA持续运行而CPU还能处理其它事情*/
	while(1)
	{
		LED_Fire();
	}
}

你可能感兴趣的:(STM32笔记(十)---DMA(直接存储器访问))