DMA(Data Memory Access),直接存储器访问。主要功能是可以把数据从一个地方搬到另外一个地方,而且不占用CPU。通常为以下传输方式:
STM32系列一般分为DMA1、DMA2,两者均可以实现 P->M, M->P,M->M 。
区别:DMA1有7个通道、DMA2有5个通道,同时DMA2在大容量、互联型的芯片上配备。
DMA 具有 12 个独立可编程的通道,其中 DMA1 有 7 个通道, DMA2 有 5 个通道,每个通道对应不同的外设的 DMA 请求。虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。
当发生多个 DMA 通道请求时, 就意味着有先后响应处理的顺序问题,这个就由仲裁器也管理。
仲裁器管理 DMA 通道请求分为两个阶段。
第一阶段属于软件阶段, 可以在DMA_CCRx 寄存器中设置,有 4 个等级:非常高、高、中和低四个优先级。
第二阶段属于硬件阶段,如果两个或以上的 DMA 通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先权越高, 比如通道 0 高于通道 1。
在大容量产品和互联型产品中,DMA1 控制器拥有高于 DMA2 控制器的优先级。
使用 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 寄存器配置。
要传多少,单位是什么
当我们配置好数据要从哪里来到哪里去之后,我们还需要知道我们要传输的数据是多少,数据的单位是什么。
以串口向电脑发送数据为例,我们可以一次性给电脑发送很多数据,具体多少由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 采集是持续循环进行的,所以使用循环传输模式)**。
‘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)
{
}
}
‘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();
}
}