一、DMA介绍
DMA叫做直接存储器存取,DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源。
DMA具有12个独立可配置的通道:其中 DMA1(7个通道), DMA2(5个通道)每个通道都支持软件触发和特定的硬件触发,DMA2 只存在于大容量产品和互联型产品中。
二、DMA功能框图
DMA功能框图主要由3部分组成
第一部分是DMA请求:
外设要想通过 DMA 来传输数据,必须先给 DMA 控制器发送 DMA 请求,DMA请求针对于不同的外设所申请的DMA通道也是不一样的,在实际使用中根据DMA请求映像表来申请。DMA 收到请求信号之后,控制器会给外设一个应答信号,当外设应答后且 DMA 控制器收到应答信号之后,就会启动 DMA 的传输,直到传输完毕。
第二部分是通道:
DMA 具有 12 个独立可编程的通道,其中 DMA1 有 7 个通道, DMA2 有 5 个通道,每个通道对应不同的外设的 DMA 请求。虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个
第三部分是仲裁器:
当发生多个 DMA 通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器来管理。仲裁器管理 DMA 通道请求分为两个阶段。第一阶段属于软件阶段,可以在 DMA_CCRx 寄存器中设置,有 4 个等级:非常高、高、中和低四个优先级。第二阶段属于硬件阶段,如果两个或以
上的 DMA 通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先权越高,
比如通道 0 高于通道 1。在大容量产品和互联型产品中, DMA1 控制器的优先级高于 DMA2 控制器。
三、DMA配置流程
DMA 数据配置主要分3部。
1.选择DMA的传输方向,传输方向主要有从外设到存储器,从存储器到外设,从存储器到存储器这3种。存储器到外设主要由存储器传输数据给串口。存储器到存储器传输主要指的是FLASH中的值传输到SRAM中。外设到存储器主要由ADC采集数据传输给存储器。
2.选择要传输的长度和单位。其中传输一次长度的最大值为65535个数据。源地址和目标地址的数据宽度必须保持一致。同时还得设置源地址和目标地址是否需要变化。
3.数据什么时候传输完成,我们可以通过查询标志位或者通过中断的方式来鉴别。每个 DMA 通道
在 DMA 传输过半、传输完成和传输错误时都会有相应的标志位,如果使能了该类型的中断后,
则会产生中断。传输完成还分两种模式,是一次传输还是循环传输,一次传输很好理解,即是传输一次之后就停止,要想再传输的话,必须关断 DMA 使能后再重新配置后才能继续传输。循环传输则是一次传输完成之后又恢复第一次传输时的配置循环传输,不断的重复。
四、存储器(FLASH)传输数据到存储器(SRAM)
DMA_MTM头文件
#ifndef __DMA_MTM_H
#define __DMA_MTM_H
#define SIZE 5
#define RCC_AHBPeriph_DMAX RCC_AHBPeriph_DMA1
#define DMAy_Channelx DMA1_Channel1
void DMA_MTM_Init(void);
uint8_t CMP(const uint32_t* A1,uint32_t* A2,uint8_t lenght);
#endif
DMA_MTM源文件
#include "stm32f10x.h"
#include "dma_mtm.h"
const uint32_t AddrA[SIZE]={0x11,0x22,0x33,0x44,0x55};//Flash
uint32_t AddrB[SIZE];//SRAM
void DMA_MTM_Init(void)
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMAX,ENABLE);
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)AddrA;//外设存储器地址
DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;//外设存储器数据大小
DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Enable;//外设存储器是否递增
DMA_InitStructure.DMA_MemoryBaseAddr=(uint32_t)AddrB;//存储器地址
DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;//存储器数据大小
DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;//存储器是否递增
DMA_InitStructure.DMA_Mode=DMA_Mode_Normal;//工作模式为单次传输
DMA_InitStructure.DMA_BufferSize=SIZE;//传输的数据大小
DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;//指定外设递增为源地址
DMA_InitStructure.DMA_M2M=DMA_M2M_Enable;//开启存储器到存储器模式
DMA_InitStructure.DMA_Priority=DMA_Priority_High;//DMA通道的软件优先级
DMA_Init(DMAy_Channelx,&DMA_InitStructure);
DMA_ClearFlag(DMA1_FLAG_TC1);
DMA_Cmd(DMAy_Channelx,ENABLE);//开启DMA
}
uint8_t CMP(const uint32_t* A1,uint32_t* A2,uint8_t lenght)//循环比较2个数组的值
{
while(lenght--)
{
if(*A1++==*A2++)
{
return 1;
}
else
{
return 0;
}
}
}
mian函数
#include "stm32f10x.h" // Device header
#include "led.h"
#include "DMA_MTM.h"
extern const uint32_t AddrA[SIZE];//Flash
extern uint32_t AddrB[SIZE];//SRAM
int main(void)
{
uint8_t flag;
LED_Init();
DMA_MTM_Init();
while(DMA_GetFlagStatus(DMA1_FLAG_TC1)==RESET);//等待DMA传输数据完成
while(1)
{
flag=CMP(AddrA,AddrB,5);
if(flag==1)
{
LED_OFF();
}
else
{
LED_ON();
}
}
}
五、存储器(SRAM)传输数据到外设(串口)
DMA_MTP头文件
#ifndef __DMA_MTP_H
#define __DMA_MTP_H
#include "stdio.h"
//RCC
#define RCC_APB2Periph_GPIOX RCC_APB2Periph_GPIOA
#define RCC_APB2Periph_USARTX RCC_APB2Periph_USART1
//GPIO
#define USART_GPIO GPIOA
#define USART_GPIO_TX GPIO_Pin_9
#define USART_GPIO_RX GPIO_Pin_10
//USART
#define USARTX USART1
//DMA
#define USART1_ADDR (USART1_BASE+0x04)
#define SIZE 20
#define RCC_AHBPeriph_DMAX RCC_AHBPeriph_DMA1
#define DMAy_Channelx DMA1_Channel4
void DMA_MTP_Init(void);
void Serial_Init(void);
void SerialByte(uint16_t Date);
void SerialDoubleByte(uint16_t Date);
void SerialArray(uint16_t* Date,uint16_t lenght);
void SerialString(uint8_t* Str);
#endif
DMA_MTP源文件
#include "stm32f10x.h"
#include "DMA_MTP.h"
#include <stdarg.h>
extern uint32_t ADDRB[SIZE];
void Serial_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOX,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USARTX,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//选择工作模式为复用推挽输出模式
GPIO_InitStructure.GPIO_Pin=USART_GPIO_TX;//选择对应的引脚
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;//配置工作频率
GPIO_Init(USART_GPIO,&GPIO_InitStructure);//初始化结构体
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;//选择工作模式为复用推挽输出模式
GPIO_InitStructure.GPIO_Pin=USART_GPIO_RX;//选择对应的引脚
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;//配置工作频率
GPIO_Init(USART_GPIO,&GPIO_InitStructure);//初始化结构体
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate=9600;//波特率设置
USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;//不开启硬件流
USART_InitStructure.USART_Mode=USART_Mode_Tx | USART_Mode_Rx;//串口工作模式选择发送与接收
USART_InitStructure.USART_Parity=USART_Parity_No;//不设置校验位
USART_InitStructure.USART_StopBits=USART_StopBits_1;//设置1个停止位
USART_InitStructure.USART_WordLength=USART_WordLength_8b;//设置数据长度为8位
USART_Init(USARTX,&USART_InitStructure);
// USART_ITConfig(USARTX,USART_IT_RXNE,ENABLE);//启动串口中断
//
// NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//
// NVIC_InitTypeDef NVIC_InitStructure;
// NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn;
// NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
// NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
// NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
// NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USARTX,ENABLE);//启用指定的串口
}
void SerialByte(uint16_t Date)//发送一个字节
{
USART_SendData(USARTX,Date);
while(USART_GetFlagStatus(USARTX,USART_FLAG_TXE)==RESET);//判断发送标志位状态
}
void SerialDoubleByte(uint16_t Date)//发送2个字节
{
uint8_t DateH,DateL;
DateH=(Date &0xFF00)>>8;
DateL=(Date & 0xFF);
USART_SendData(USARTX,DateH);
while(USART_GetFlagStatus(USARTX,USART_FLAG_TXE)==RESET);
USART_SendData(USARTX,DateL);
while(USART_GetFlagStatus(USARTX,USART_FLAG_TXE)==RESET);
}
void SerialArray(uint16_t* Date,uint16_t lenght)//发送数组
{
uint8_t i;
for(i=0;i<lenght;i++)
{
SerialByte(Date[i]);
}
}
void SerialString(uint8_t* Str)//发送字符串
{
while(*Str!='\0')
{
SerialByte(*Str++);
}
}
int fputc(int ch,FILE *f)//重写print
{
USART_SendData(USARTX,(uint16_t)ch);
while(USART_GetFlagStatus(USARTX,USART_FLAG_TXE)==RESET);
return ch;
}
int fgetc(FILE *f)//重写getchar
{
while(USART_GetFlagStatus(USARTX,USART_FLAG_RXNE)==RESET);
return (uint16_t)USART_ReceiveData(USARTX);
}
void DMA_MTP_Init(void)
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMAX,ENABLE);
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)USART1_ADDR;//外设存储器地址
DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Word;//外设存储器数据大小
DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable ;//外设存储器地址不递增
DMA_InitStructure.DMA_MemoryBaseAddr=(uint32_t)ADDRB;//存储器地址
DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Word;//存储器数据大小
DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;//存储器地址递增
DMA_InitStructure.DMA_Mode=DMA_Mode_Normal;//工作模式为单次传输
DMA_InitStructure.DMA_BufferSize=SIZE;//传输的数据大小
DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralDST;//指定外设递增为目的地地址
DMA_InitStructure.DMA_M2M=DMA_M2M_Disable ;//关闭存储器到存储器模式
DMA_InitStructure.DMA_Priority=DMA_Priority_High;//DMA通道的软件优先级
DMA_Init(DMAy_Channelx,&DMA_InitStructure);
DMA_ClearFlag(DMA1_FLAG_TC1);//清除传输完成标志位
DMA_Cmd(DMAy_Channelx,ENABLE);//开启DMA
}
main函数
#include "stm32f10x.h" // Device header
#include "led.h"
#include "DMA_MTP.h"
#include "delay.h"
uint32_t ADDRB[SIZE];
int main(void)
{
uint8_t i=0;
for(i=0;i<SIZE;i++)
{
ADDRB[i]='A';
}
LED_Init();
Serial_Init();
DMA_MTP_Init();
USART_DMACmd(USARTX,USART_DMAReq_Tx,ENABLE);//开启串口发送DMA请求
while(1)
{
LED_ON();
Delay(0xFFFFF);
LED_OFF();
Delay(0xFFFFF);
}
}