DMA(Direct Memory Access 直接内存访问)指的是STM32中的一个外设。它可以在无需CPU介入的情况下,实现外设和存储器之间或存储器与存储器之间的数据传输,这里的存储器指的是SRAM或者是Flash。在进行一些大批量的,或者是周期性重复的数据转移工作时,通常都会使用到DMA,这使得CPU可以腾出时间完成其他更具意义的任务,从而提高处理效率,在这点上,DMA和GPU的存在意义类似,都是用于处理专门的需求或者数据而存在的
STM32的DMA的控制器包含了两个DMA:DMA1和DMA2,其中DMA1拥有7个通道(可以理解为数据传输通道),DMA2拥有5各通道。DMA2在大容量类型或互联网类型STM32产品上才会搭载
STM32上的DMA外设有以下特点:
STM32的DMA外设同其他外设一样,也是独立于Cortex内核的存在,其可以大致分为三部分:请求,通道,仲裁器,功能框图如下:
其他外设如果需要使用DMA功能,需要向DMA发送请求,DMA收到请求后,会根据情况给发起请求的外设一个应答信号,只有当请求的外设收到应答信号时,才会启动DMA传输功能
通道指代的是数据传输通道,DMA1有7条通道,DMA2有5条通道,12条通道都可以独立编程控制,并且每条通道对应了不同的外设,具体对应如下图:
可以看到,每个通道可以对应多个外设。但是同一时间,一个通道只能被一个外设使用,需要多个外设同时使用同一个通道时,开发者需要注意规划外设DMA通道时序,以确保通道带宽得到有效且正确的分配
DMA传输的实现机制以及拓展思考
DMA传输实际上还是需要同Cortex内核共享系统数据总线来实现的,这就意味着在DMA传输进行时,CPU依旧是需要等待的,既然需要等待那么如何提高访问效率呢。为此,STM开发者是这么设计的,当DMA外设和内核同时访问同一个设备时,DMA外设会暂停内核访问总线的若干个周期,同时总线总裁器执行循环调度,保证内核能至少获得总线一半的带宽,
类似于多个外设同时使用一个通道的场景,DMA传输在同一时刻只能对一个通道进行数据传输操作,当DMA控制器同时接收到来自多个通道的数据传输请求时,会通过优先级进行仲裁,优先级分为两种:软件优先级和硬件优先级
DMA外设的数据传输配置,主要是需要对数据的元数据(即描述数据用的数据)进行配置:包括传输方向,数据量,传输模式
DMA传输数据的方向分为三种:外设到存储器,存储器到外设,存储器到存储器。三种方向主要通过以下寄存器中的位来配置:
具体举例三种情况:
数据量的配置包括了,数据量大小,单个数据的宽度,以及数据指针增量模式。主要通过以下寄存器中的位来配置:
传输模式分为两种:单次传输和循环传输。顾名思义,单次传输进行一次传输,而循环传输会一直不间断的进行单次传输,每轮单次传输完成后,都会重新按照初始配置重新进行传输。传输模式由DMA_CCRx(x = 1…7)寄存器的CIRC位控制,CIRC位置1时,执行循环传输操作,CIRC位清0时,执行单次传输操作
对于两种传输的传输状态,包括传输过半,传输完成,传输出错都有相应的标志位去查询,同时也可以启用中断,通过中断去对特定指定状态执行对应操作
以下内容基于STM32F103VET野火指南者开发板,分别实现两个功能:
会使用到开发板的LED灯模块以及USB串口模块(实际上是USART模块+CH340G芯片),原理图分别如下:
首先,需要解析标准库提供的DMA初始化结构体,以及相关操作函数:
/**
* @brief DMA Init structure definition
*/
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;
其中结构体成员的含义分别如下:
此功能的实现思路为:
分别建立DMA相关板级支持文件bsp_dma.h,bsp_dma.c,内容分别如下:
#ifndef __BSP_DMA_H__
#define __BSP_DMA_H__
#include
#define DMA_CHANNEL DMA1_Channel1
#define DMA_CLK RCC_AHBPeriph_DMA1
#define DMA_FLAG_TC DMA1_FLAG_TC1
#define DMA_BUFFER_SIZE 32
extern const uint32_t aSRC_Const_Buffer[DMA_BUFFER_SIZE];
extern uint32_t aDST_Buffer[DMA_BUFFER_SIZE];
void DMA_Config(void);
uint8_t BufferCompare(const uint32_t *pSRCBuffer, uint32_t *pDSTBuffer, uint16_t BufferLength);
#endif
#include "bsp_dma.h"
#include "stm32f10x.h"
#include "stm32f10x_dma.h"
#include "stm32f10x_rcc.h"
#include
// The "const" keyword for a global variable will cause it to be storged in ROM(Flash)
const uint32_t aSRC_Const_Buffer[DMA_BUFFER_SIZE] =
{
0x01020304, 0x05060708, 0x090A0B0C, 0x0D0E0F10,
0x11120304, 0x05160718, 0x090A1B0C, 0x0D0E0F10,
0x21220304, 0x05260728, 0x090A2B0C, 0x0D0E0F20,
0x31320304, 0x05360738, 0x090A3B0C, 0x0D0E0F30,
0x41420304, 0x05460748, 0x090A4B0C, 0x0D0E0F40,
0x51520304, 0x05560758, 0x090A5B0C, 0x0D0E0F50,
0x61620304, 0x05660768, 0x090A6B0C, 0x0D0E0F60,
0x71720304, 0x05760778, 0x090A7B0C, 0x0D0E0F70
};
// Without the "const" keyword, a global variable will be storged in SRAM
uint32_t aDST_Buffer[DMA_BUFFER_SIZE];
void DMA_Config(void)
{
// DMA
DMA_InitTypeDef DMA_InitStructure;
// DMA Clk enable
RCC_AHBPeriphClockCmd(DMA_CLK, ENABLE);
// DMA param config
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) aSRC_Const_Buffer;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) aDST_Buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = DMA_BUFFER_SIZE;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
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_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable; // Enable M2M
DMA_Init(DMA_CHANNEL, &DMA_InitStructure);
// DMA enable
DMA_Cmd(DMA_CHANNEL, ENABLE);
}
uint8_t BufferCompare(const uint32_t *pSRCBuffer, uint32_t *pDSTBuffer, uint16_t BufferLength)
{
for (int i=0; i<BufferLength; ++i)
{
if (*(pSRCBuffer+i) == *(pDSTBuffer+i))
{
continue;
}
else
{
return 1;
}
}
return 0;
}
通过板级支持包代码实现需求:
#include "userapp.h"
#include "bsp_dma.h"
#include "bsp_clkconfig.h"
#include "bsp_led.h"
#include "stm32f10x.h"
#include "stm32f10x_dma.h"
#include
int userapp(void)
{
LED_GPIO_Init();
Delay(3000000);
LED_PURPLE;
DMA_Config();
// Wait until DMA transfer complete
while (DMA_GetFlagStatus(DMA_FLAG_TC) == RESET);
Delay(3000000);
// Compare buffer and show result by different LED color
if (BufferCompare(aSRC_Const_Buffer, aDST_Buffer, BUFFER_SIZE) == 0)
{
LED_GREEN;
}
else
{
LED_RED;
}
while (1)
{
}
}
此功能的实现思路为和存储器间的样例没有很大不同,但是要注意以下几点:
分别建立DMA相关板级支持文件bsp_dma_uart.h,bsp_dma_uart.c,内容分别如下:
#ifndef __BSP_DMA_UART_H__
#define __BSP_DMA_UART_H__
#include "stm32f10x_usart.h"
#define UART_DR_ADDR (USART1_BASE + 0x04)
// DMA
#define UART_DMA_CLK RCC_AHBPeriph_DMA1
#define UART_DMA_CHANNEL DMA1_Channel4 // channel 4 support only
#define UART_DMA_BUFFER_SIZE 64
extern uint8_t aSRC_Buffer[UART_DMA_BUFFER_SIZE];
void UART_DMA_Config(void);
#endif
#include "bsp_dma_uart.h"
#include "bsp_uart.h"
#include "stm32f10x.h"
#include "stm32f10x_dma.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_usart.h"
#include
uint8_t aSRC_Buffer[UART_DMA_BUFFER_SIZE] = "HeLLo WoRld !!! This is a demo of DMA/UART\n";
void UART_DMA_Config(void)
{
// DMA
DMA_InitTypeDef DMA_InitStructure;
// DMA Clk enable
RCC_AHBPeriphClockCmd(UART_DMA_CLK, ENABLE);
// DMA param config
DMA_InitStructure.DMA_PeripheralBaseAddr = UART_DR_ADDR;
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)aSRC_Buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = UART_DMA_BUFFER_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_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(UART_DMA_CHANNEL, &DMA_InitStructure);
// DMA enable
DMA_Cmd (UART_DMA_CHANNEL,ENABLE);
// DMA UART TX enable
USART_DMACmd(USARTx, USART_DMAReq_Tx, ENABLE);
}
通过板级支持包代码实现需求:
#include "userapp.h"
#include "bsp_clkconfig.h"
#include "bsp_uart.h"
#include "bsp_dma_uart.h"
#include "bsp_led.h"
#include "stm32f10x.h"
#include "stm32f10x_dma.h"
#include "stm32f10x_usart.h"
#include
int userapp(void)
{
LED_GPIO_Init();
LED_PURPLE;
USART_Config();
Delay(5000000);
UART_DMA_Config();
while (1)
{
LED_GREEN;
Delay(5000000);
LED_OFF;
Delay(5000000);
}
}
示例代码已上传Github:Sinuxtm32