[STM32 - 野火] - - - 固件库学习笔记 - - -六.DMA直接存储器存取

1 DMA介绍

1.1 什么是DMA

DMA:Direct Memory Access,直接存储器存取。是一种用于在外围设备和内存之间进行数据传输的机制。通常情况下,中央处理(CPU)负责管理数据的传输,但使用DMA可以让外围设备直接访问系统内存,而无需CPU的干预。

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

两个DMA控制器都能实现从外设向存储器传输数据(P->M)、存储器向外设传输数据(M->P)、存储器向存储器传输数据(M->M)。

P:Peripheral; M:Memory,可以是Flash,也可以是SRAM。

1.2 DMA的优点

  • 1.提高性能: DMA允许外围设备直接与内存进行数据传输,而不需要CPU的介入。这可以显著提高数据传输的效率,释放CPU的资源,使其能够专注于其他任务。

  • 2.降低CPU负载: 由于DMA负责数据传输,CPU无需执行每个数据传输的步骤。这有助于减轻CPU的负载,使其能够处理其他任务。

  • 3.减少延迟: 直接内存访问可以减少数据传输的延迟,因为它不需要等待CPU执行传输操作。

  • 4.支持高速数据传输: 对于需要高速数据传输的设备,如硬盘驱动器或图形卡,DMA是至关重要的,因为它允许快速而有效地移动大量数据。

1.3 DMA工作的一般步骤

  • 1.外围设备请求: 当外围设备需要将数据传输到内存或从内存读取数据时,它会发送一个DMA请求。

  • 2.DMA控制器响应: DMA控制器收到外围设备的请求后,会协调数据传输。DMA控制器与外围设备和内存之间直接进行通信。

  • 3.中断CPU: 为了确保DMA控制器能够获得对内存总线的访问权限,它需要暂时中断CPU。这是为了避免CPU和DMA控制器同时访问内存总线,导致冲突。

  • 4.DMA控制器获得内存总线控制权: 一旦中断CPU,DMA控制器获得对内存总线的控制权,可以直接访问内存。

  • 5.数据传输: DMA控制器开始在外围设备和内存之间传输数据,而无需CPU的干预。这可以是从外围设备到内存的写操作,也可以是从内存到外围设备的读操作。

  • 6.DMA完成通知: 一旦数据传输完成,DMA控制器通常会发送一个中断信号给CPU,以便CPU可以继续执行其他任务。

2 功能框图讲解

2.1 DMA功能框图

DMA功能框图重点理解三个部分:DMA请求、通道与仲裁器。

在这里插入图片描述

1.大容量产品是指闪存存储器容量在256K至512K字节之间的STM32F101xx和STM32F103xx微控制器。
2.互联型产品是指STM32F105xx和STM32F107xx微控制器。

2.2 DMA请求

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

请求可以由双方中的任一方提出,可以由发送方提出,也可以由接收方提出。

2.2.1 DMA1请求映像

在这里插入图片描述

2.2.2 DMA2请求映像

在这里插入图片描述

2.3 通道

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

2.3.1 DMA1通道

在这里插入图片描述

2.3.2 DMA2通道

在这里插入图片描述

2.4 仲裁器

当多个DMA请求一起来时,应该怎么选择?

仲裁器根据通道请求的优先级来启动外设/存储器的访问。

优先级可分为2个阶段进行判断:

  • 1.软件阶段:通过寄存器DMA_CCRx位13:12 PL[1:0]来判断。

在这里插入图片描述
如果软件阶段的优先级一样,再比较硬件优先级。

  • 2.硬件阶段:通道编号小的优先级大,DMA1的优先级高于DMA2的优先级。

3 DMA初始化结构体

// stm32f10x_dma.h 文件

typedef struct
{
  uint32_t DMA_PeripheralBaseAddr; /*!< Specifies the peripheral base address for DMAy Channelx. */

  uint32_t DMA_MemoryBaseAddr;     /*!< Specifies the memory base address for DMAy Channelx. */

  uint32_t DMA_DIR;                /*!< Specifies if the peripheral is the source or destination.
                                        This parameter can be a value of @ref DMA_data_transfer_direction */

  uint32_t DMA_BufferSize;         /*!< Specifies the buffer size, in data unit, of the specified Channel. 
                                        The data unit is equal to the configuration set in DMA_PeripheralDataSize
                                        or DMA_MemoryDataSize members depending in the transfer direction. */

  uint32_t DMA_PeripheralInc;      /*!< Specifies whether the Peripheral address register is incremented or not.
                                        This parameter can be a value of @ref DMA_peripheral_incremented_mode */

  uint32_t DMA_MemoryInc;          /*!< Specifies whether the memory address register is incremented or not.
                                        This parameter can be a value of @ref DMA_memory_incremented_mode */

  uint32_t DMA_PeripheralDataSize; /*!< Specifies the Peripheral data width.
                                        This parameter can be a value of @ref DMA_peripheral_data_size */

  uint32_t DMA_MemoryDataSize;     /*!< Specifies the Memory data width.
                                        This parameter can be a value of @ref DMA_memory_data_size */

  uint32_t DMA_Mode;               /*!< Specifies the operation mode of the DMAy Channelx.
                                        This parameter can be a value of @ref DMA_circular_normal_mode.
                                        @note: The circular buffer mode cannot be used if the memory-to-memory
                                              data transfer is configured on the selected Channel */

  uint32_t DMA_Priority;           /*!< Specifies the software priority for the DMAy Channelx.
                                        This parameter can be a value of @ref DMA_priority_level */

  uint32_t DMA_M2M;                /*!< Specifies if the DMAy Channelx will be used in memory-to-memory transfer.
                                        This parameter can be a value of @ref DMA_memory_to_memory */
}DMA_InitTypeDef;

3.1 数据从哪里来,存到哪里去?

uint32_t DMA_PeripheralBaseAddr; /* 外设地址 */

uint32_t DMA_MemoryBaseAddr;     /* 存储器地址 */

uint32_t DMA_DIR; 				/* 传输方向 */
  • 外设地址:由DMA_CPARx寄存器配置。

  • 存储器地址:由DMA_CMARx寄存器配置。

  • 传输方向:由DMA_CCRx寄存器位4 DIR决定;如果是 M->M 传输则需要用到DMA_CCRx寄存器位14 MEM2MEM。

假设数据从外设(比如从ADC-DR寄存器)来,就把(ADC-DR寄存器)地址写到DMA_CPARx寄存器中。

假设数据从存储器内部来,就要把地址写到DMA_CMARx寄存器中。

3.2 数据要传多少,传的单位是什么?

uint32_t DMA_BufferSize;         /*!< 传输数目 */

uint32_t DMA_PeripheralInc;      /*!< 外设地址增量模式 */

uint32_t DMA_MemoryInc;          /*!< 存储器地址增量模式 */

uint32_t DMA_PeripheralDataSize; /*!< 外设数据宽度 */

uint32_t DMA_MemoryDataSize;	/*!< 存储器数据宽度 */
  • 传输数目:由DMA_CNDTR寄存器配置。

  • 外设地址增量模式:由DMA_CCRx寄存器位6 PINC配置。

  • 存储器地址增量模式:由DMA_CCRx寄存器位7 MINC配置。

  • 外设数据宽度:DMA_CCRx寄存器位9:8 PSIZE[1:0]配置。

  • 存储器数据宽度:DMA_CCRx寄存器位11:10 MSIZE[1:0]配置。

3.2.1 增量模式

假设现在我们要通过串口发送数据(M->P),我们应该将外设地址增量模式配置为不执行外设地址增量操作、存储器地址增量模式配置为执行存储器地址增量操作。

在这里插入图片描述

3.2.2 数据宽度

  • 如果源端宽度为32位,目标宽度为8位,那么就会丢弃高24位,只存储低8位。

  • 如果源端宽度为8位,目标宽度为32位,那么就会将这8位存储在低8位。

在这里插入图片描述

3.3 什么时候传输结束?

uint32_t DMA_Mode;               /*!< 模式选择 */
  • 模式选择:由DMA_CCRx寄存器位5 CIRC配置。

不循环操作(normal):数据全发送完就不发了。
循环操作(circular):数据全发送完后再从头开始发送,不断发送。

  • 每个 DMA 通道在 DMA 传输过半、传输完成和传输错误时都会有相应的标志位(DMA_ISR),如果使能了该类型的中断后,则会产生中断。

4 DMA相关寄存器

在以下列举的所有寄存器中,所有与通道6和通道7相关的位,对DMA2都不适用,因为DMA2只有5个通道。

4.1 DMA中断状态寄存器(DMA_ISR)

在这里插入图片描述
在这里插入图片描述

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

在这里插入图片描述

4.3 DMA通道x配置寄存器(DMA_CCRx)(x = 1…7)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

4.6 DMA通道x存储器地址寄存器(DMA_CMARx)(x = 1…7)

在这里插入图片描述
在这里插入图片描述

5 存储器到存储器传输

为了使工程更有条理,我们把DMA相关的代码独立分开存储,方便以后移植。在“工程模板”上新建 “bsp_dma_m2m.c” 文件和"bsp_dma_m2m.h" 文件。

5.1 编程要点

把内部FLASH的数据传输到内部的SRAM中。

  • 1.初始化DMA初始化结构体。

  • 2.熟读参考手册DMA章节(非常重要)。

5.2 步骤

  • 1.在FLASH中定义好要传输的数据,在SRAM中定义好用来接收FLASH数据的变量。

  • 2.初始化DMA,主要是配置DMA初始化结构体。

  • 3.编写比较函数。

  • 4.编写main函数。

// bsp_dma_m2m.c 文件
#include "bsp_dma_m2m.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_m2m_config(void)
{
	DMA_InitTypeDef DMA_InitStruct;
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	
	DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)aSRC_Const_Buffer;
	DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)aDST_Buffer;
	DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;
	
	DMA_InitStruct.DMA_BufferSize = BUFFER_SIZE;
	DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
	DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
	DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
	
	DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;
	DMA_InitStruct.DMA_Priority = DMA_Priority_High;
	DMA_InitStruct.DMA_M2M = DMA_M2M_Enable;
	
	DMA_Init(DMA1_Channel1, &DMA_InitStruct);
	
	DMA_Cmd(DMA1_Channel1, 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;  
}
// bsp_dma_m2m.h 文件
#ifndef __BSP_DMA_M2M_H
#define __BSP_DMA_M2M_H

#include "stm32f10x.h"

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

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

#endif /* __BSP_DMA_M2M_H */ 
// main.c 文件
#include "bsp_led.h"
#include "stm32f10x.h"
#include "bsp_dma_m2m.h"

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

int main(void)
{
	
	LED_G_GPIO_Config();
	LED_B_GPIO_Config();
	LED_R_GPIO_Config();
	
	dma_m2m_config();
	LED_B(1);
	
	while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
	LED_B(0);
	
	if(Buffercmp(aSRC_Const_Buffer, aDST_Buffer, BUFFER_SIZE)) 
	{
		LED_G(1);
	}
	else 
	{
		LED_R(1);
	}
	
	while(1)
	{
		
	}
	
}

6 存储器到外设传输

为了使工程更有条理,我们把DMA相关的代码独立分开存储,方便以后移植。在“工程模板”上新建 “bsp_dma_m2p.c” 文件和"bsp_dma_m2p.h" 文件。

6.1 编程要点

SRAM到串口。

  • 1.初始化串口。

  • 2.配置DMA初始化结构体。

  • 3.编写main函数。

// bsp_dma_m2p.c 文件
#include "bsp_dma_m2p.h"

uint8_t SENDBUFF[SENDBUFF_SIZE];

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

	// 打开串口GPIO的时钟
	RCC_APB2PeriphClockCmd(DEBUG_USART_GPIO_CLK, ENABLE);
	
	// 打开串口外设的时钟
	RCC_APB2PeriphClockCmd(DEBUG_USART_CLK, ENABLE);
	RCC_APB1PeriphClockCmd(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);
	
//	// 串口中断优先级配置
//	NVIC_Configuration();
//	
//	// 使能串口接收中断
//	USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);	
	
	// 使能串口
	USART_Cmd(DEBUG_USARTx, ENABLE);	    
}

void dma_m2p_config(void)
{
	DMA_InitTypeDef DMA_InitStruct;
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	
	DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)(USART1_BASE+0x04);
	DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)SENDBUFF;
	DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralDST;
	
	DMA_InitStruct.DMA_BufferSize = SENDBUFF_SIZE;
	DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
	DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
	
	DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;
	DMA_InitStruct.DMA_Priority = DMA_Priority_High;
	DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;
	
	DMA_Init(DMA1_Channel4, &DMA_InitStruct);
	
	DMA_Cmd(DMA1_Channel4, ENABLE);
}

// bsp_dma_m2p.h 文件
#ifndef __BSP_DMA_M2P_H
#define __BSP_DMA_M2P_H

#include "stm32f10x.h"

// 要发送的数据大小
#define SENDBUFF_SIZE    5000

// 串口1-USART1
#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  DEBUG_USART_IRQ                USART1_IRQn
#define  DEBUG_USART_IRQHandler         USART1_IRQHandler

																
void USART_Config(void);
void dma_m2p_config(void);

#endif /* __BSP_DMA_M2M_P */ 
// main.c 文件
#include "stm32f10x.h"

#include "bsp_led.h"
#include "bsp_dma_m2m.h"
#include "bsp_dma_m2p.h"

#include <string.h>

extern uint8_t SENDBUFF[SENDBUFF_SIZE];

int main(void)
{
	
	LED_G_GPIO_Config();
	LED_B_GPIO_Config();
	LED_R_GPIO_Config();
	
	USART_Config();
	
	memset(SENDBUFF, 0, sizeof(SENDBUFF));

	
	dma_m2p_config();
	
	USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Tx, ENABLE);
	
	while(1)
	{
		
	}
	
}

在这里插入图片描述

以上为单片机复位后通过串口助手看到的数据。

6.3 M->M与M->P的区别

  • M->M一旦使能就立刻进行数据传输,但是M->P不一样,需要外设给DMA发送一个请求。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值