STM32 DMA机制详解:工作原理与操作指南
DMA简介
DMA:Data Memory Access,直接存储器访问。主要功能是可以把数据从一个地方搬到另外一个地方而且不占用CPU。
DMA1:有7个通道,可以实现P->M,M->P,M->M
DMA2∶有5个通道,可以实现P->M,M->P,M->M
这里的通道 可以理解为传输数据的一种管道。要注意的是DMA2只存在于大容量产品和互联型产品中。
DMA功能框图
1-DMA请求
如果外设要想通过DMA来传输数据,必须先给DMA控制器发送DMA请求,DMA收到请求信 号之后,控制器会给外设一个应答信号,当外设应答后且DMA控制器收到应答信号之后,就会 启动DMA的传输,直到传输完毕。
2-通道
不同的DMA控 制器的通道对应着不同的外设请求,这决定了我们在软件编程上该怎么设置
DMA1请求映像表
DMA2请求映像表
M->M 时全部通道都可以使用
其中ADC3、SDIO和TIM8的DMA请求只在大容量产品中存在,这个在具体项目时要注意。
每个通道对 应不同的外设的DMA请求。虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一 个,不能同时接收多个
3-仲裁器
当发生多个DMA通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器也管理。
仲裁器管理DMA通道请求分为两个阶段。
第一阶段属于软件阶段
可以在DMA_CCRx寄存器 中设置,有4个等级:非常高、高、中和低四个优先级。
第二阶段属于硬件阶段
如果两个或以 上的DMA通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先权越高, 比如通道0高于通道1。
在大容量产品和互联型产品中,DMA1控制器拥有高于DMA2 控制器的 优先级。
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_DIR_PeripheralDST 外设是目的地 即: M->P
DMA_DIR_PeripheralSRC 外设是源 即: P->M
前面三个成员决定了要从哪里来到那里去
可编程的数据传输宽度、对齐方式和数据大小端
总结:小方大 将小的放在头部 大放小 要头不要尾
实验
实验1-M to M:
FLASH to SRAM,把内部FLASH的数据传输到内部的SRAM。
编程要点
1-在FLASH中定义好要传输的数据,在SRAM中定义好用来接收FLASH数据的变量
定义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];
2-初始化DMA,主要是配置DMA初始化结构体。
DMA挂载到AHB总线
void dma_mtm_config(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, 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_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
// 使能内存到内存的传输
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;
DMA_Init(DMA1_Channel6,&DMA_InitStructure);
DMA_Cmd(DMA1_Channel6, ENABLE);
}
在编写M-M的时候可以将Flash看作外设
3-编写比较函数。
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;
}
判断指定长度的两个数据源是否完全相等,如果完全相等返回1,只要其中一对数据不相等返回0
4-编写main函数。
int main(void)
{
uint8_t sta;
led_init();
led_B_ON();
delay(700);
led_OFF();
dma_mtm_config();
while(DMA_GetFlagStatus(DMA1_FLAG_TC6)==0);
DMA_ClearFlag(DMA1_FLAG_TC6);
sta = Buffercmp(aSRC_Const_Buffer,aDST_Buffer, BUFFER_SIZE);
if(sta==0)
led_R_ON();
else
led_G_ON();
while(1)
{
}
}
实验代码
main.c代码
#include "stm32f10x.h"
#include "led.h"
#include "bsp_systick.h"
#include "bsp_dma_mtm.h"
extern const uint32_t aSRC_Const_Buffer[BUFFER_SIZE];
extern uint32_t aDST_Buffer[BUFFER_SIZE];
int main(void)
{
uint8_t sta;
led_init();
led_B_ON();
delay(700);
led_OFF();
dma_mtm_config();
while(DMA_GetFlagStatus(DMA1_FLAG_TC6)==0);
DMA_ClearFlag(DMA1_FLAG_TC6);
sta = Buffercmp(aSRC_Const_Buffer,aDST_Buffer, BUFFER_SIZE);
if(sta==0)
led_R_ON();
else
led_G_ON();
while(1)
{
}
}
bsp_dma_mtm.h代码
#ifndef _BSP_DMA_MTM_H
#define _BSP_DMA_MTM_H
#include "stm32f10x.h"
#define BUFFER_SIZE 32
void dma_mtm_config(void);
uint8_t Buffercmp(const uint32_t* pBuffer,
uint32_t* pBuffer1, uint16_t BufferLength);
#endif
bsp_dma_mtm.c代码
#include "bsp_dma_mtm.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_mtm_config(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, 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_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
// 使能内存到内存的传输
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;
DMA_Init(DMA1_Channel6,&DMA_InitStructure);
DMA_Cmd(DMA1_Channel6, 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;
}
实验2-M to P:
SRAM to 串口,同时LED灯闪烁,演示DMA传数据不需要占用CPU。
bsp_dma_mtp.h代码
#ifndef _BSP_DMA_MTP_H
#define _BSP_DMA_MTP_H
#include "stm32f10x.h"
#include <stdio.h>
//串口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 //debug
#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 SENDDATA_SIZE 5000
void dma_mtp_config(void);
void USART_Config(void);
#endif
bsp_dma_mtp.c代码
#include "bsp_dma_mtp.h"
uint8_t senddata[SENDDATA_SIZE];
void dma_mtp_config(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_InitStructure.DMA_PeripheralBaseAddr = (USART1_BASE+0x04);
// 目标地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)senddata;
// 方向:外设到存储器(这里的外设是内部的FLASH)
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
// 传输大小
DMA_InitStructure.DMA_BufferSize = SENDDATA_SIZE;//5000
// 外设(内部的FLASH)地址递增
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_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
// 使能内存到内存的传输
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel4,&DMA_InitStructure);
DMA_Cmd(DMA1_Channel4, ENABLE);
}
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);
// 清除发送完成标志
//USART_ClearFlag(USART1, USART_FLAG_TC);
}
int fputc(int ch, FILE *f)
{
/* 发送一个字节数据到串口 */
USART_SendData(DEBUG_USARTx, (uint8_t) ch);
/* 等待发送完毕 */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);
return (ch);
}
main.c代码
#include "stm32f10x.h"
#include "led.h"
#include "bsp_systick.h"
#include "bsp_dma_mtp.h"
extern uint8_t senddata[SENDDATA_SIZE];
int main(void)
{
uint32_t i;
led_init();
USART_Config();
dma_mtp_config();
for(i=0;i<SENDDATA_SIZE;i++)
{
senddata[i]='H';
}
USART_DMACmd(USART1,USART_DMAReq_Tx, ENABLE);
while(1)
{
printf("测试");
led_R_ON();
delay(500);
led_OFF();
delay(1500);
}
}
作者:嵌R式小Z