目录
本节我们来学习有关DMA的知识,这部分知识在51中是没有接触的,也算是一个新的概念,简而言之,DMA就是一个不需要CPU的传输方式。好了,接下来我们就开始介绍有关DMA的知识吧!
传输速度较快,视频也看不太清,就发一张截图了,一同发送了200条这样的数据。
DMA,全称为:Direct Memory Access,即直接存储器访问。DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路,能为CPU减负,使 CPU 的效率大为提高。 STM32F4 最多有 2 个 DMA 控制器(DMA1 和 DMA2),共 16 个数据流(每个控制器 8 个),每一个 DMA 控制器都用于管理一个或多个外设的存储器访问请求。每个数据流总共可以有多达 8 个通道(或称请求)。每个数据流通道都有一个仲裁器,用于处理 DMA 请求间的优先级。
这就是有关DMA的流程框图,一个8个数据流,每个数据流8个通道,还有仲裁器。其中的FIFO应该是先进先出队列,我们在此不做过多介绍。
这个是通道选择,具体是哪个寄存器连接到数据流,是通过CHSEL寄存器来控制的。
这就是一一对应关系,不同的数据流和通道对应不同的外设。
8 个 DMA 控制器数据流都能够提供源和目标之间的单向传输链路。 每个数据流配置后都可以执行:
要传输的数据量(多达 65535)可以编程,并与连接到外设 AHB 端口的外设(请求 DMA 传输)的源宽度相关。每个事务完成后,包含要传输的数据项总量的寄存器都会递减。
DMA 事务由给定数目的数据传输序列组成。要传输的数据项的数目及其宽度(8 位、16 位 或 32 位)可用软件编程。 每个 DMA 传输包含三项操作:
在产生事件后,外设会向 DMA 控制器发送请求信号。DMA 控制器根据通道优先级处理该请求。只要 DMA 控制器访问外设,DMA 控制器就会向外设发送确认信号。外设获得 DMA 控制器的确认信号后,便会立即释放其请求。一旦外设使请求失效,DMA 控制器就会释放确认信号。如果有更多请求,外设可以启动下一个事务。
仲裁器为两个 AHB 主端口(存储器和外设端口)提供基于请求优先级的 8 个 DMA 数据流请求管理,并启动外设/存储器访问序列。 优先级管理分为两个阶段:
其实有点类似于中断的优先级的概念。
其实比较好理解,就是发送数据时指针自动递增,就能发送一条完整的数据流了。
就是是否需要循环发送数据。
我们可以理解为,两个数据流同时操作,比如DMA1在传输的时候,去填充DMA2的数据,等DMA1完成后,DMA2就接上DMA1的数据流继续发送。
对于每个 DMA 数据流,可在发生以下事件时产生中断:
可以使用单独的中断使能位以实现灵活性。
我们在代码中详细介绍一下整个的过程。
1、使能 DMA2 时钟,并等待数据流可配置。
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2 时钟使能
while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){}//等待 DMA 可配置
2、初始化 DMA2 数据流 7,包括配置通道,外设地址,存储器地址,传输数据量等。
void DMA_Init(DMA_Stream_TypeDef* DMAy_Streamx, DMA_InitTypeDef* DMA_InitStruct);
3、使能串口 1 的 DMA 发送。
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口 1 的 DMA 发送
4、使能 DMA2 数据流 7,启动传输。
void DMA_Cmd(DMA_Stream_TypeDef* DMAy_Streamx, FunctionalState NewState)
DMA_Cmd (DMA2_Stream7,ENABLE);
5、查询 DMA 传输状态。
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG)
DMA_GetFlagStatus(DMA2_Stream7,DMA_FLAG_TCIF7);
uint16_t DMA_GetCurrDataCounter(DMA_Stream_TypeDef* DMAy_Streamx);
DMA_GetCurrDataCounter(DMA1_Channel4);
void DMA_SetCurrDataCounter(DMA_Stream_TypeDef* DMAy_Streamx, uint16_t Counter);
#include "dma.h"
#include "delay.h"
//DMAx的各通道配置
//chx:DMA通道选择,@ref DMA_channel DMA_Channel_0~DMA_Channel_7
//par:外设地址
//mar:存储器地址
//ndtr:数据传输量
void MYDMA_Config(DMA_Stream_TypeDef *DMA_Streamx,u32 chx,u32 par,u32 mar,u16 ndtr)
{
DMA_InitTypeDef DMA_InitStructure;
if((u32)DMA_Streamx>(u32)DMA2)//得到当前stream是属于DMA2还是DMA1,数据流大于DMA2的地址
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能
}else
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);//DMA1时钟使能
}
DMA_DeInit(DMA_Streamx);
while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){}//等待DMA可配置
/* 配置 DMA Stream */
DMA_InitStructure.DMA_BufferSize = ndtr;//数据传输量
DMA_InitStructure.DMA_Channel = chx;//通道选择
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;//存储器到外设模式
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;// 不使用FIFO
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;//设置FIFO范围,在此可以不设
DMA_InitStructure.DMA_Memory0BaseAddr = mar;//DMA 存储器0地址
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_MemoryDataSize =DMA_MemoryDataSize_Byte;//存储器数据长度:8位
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器增量模式
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 使用普通模式
DMA_InitStructure.DMA_PeripheralBaseAddr = par;//DMA外设地址
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//存储器突发单次传输
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据长度:8位
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Low; //低优先级
DMA_Init(DMA_Streamx, &DMA_InitStructure);//初始化DMA Stream
}
//开启一次DMA传输
//DMA_Streamx:DMA数据流,DMA1_Stream0~7/DMA2_Stream0~7
//ndtr:数据传输量
void MYDMA_Enable(DMA_Stream_TypeDef *DMA_Streamx,u16 ndtr)
{
DMA_Cmd(DMA_Streamx, DISABLE); //关闭DMA传输
while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){} //确保DMA可以被设置
DMA_SetCurrDataCounter(DMA_Streamx,ndtr); //数据传输量
DMA_Cmd(DMA_Streamx, ENABLE); //开启DMA传输
}
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "dma.h"
#define SEND_BUF_SIZE 8200 //发送数据长度,最好等于sizeof(TEXT_TO_SEND)+2的整数倍.
u8 SendBuff[SEND_BUF_SIZE]; //发送数据缓冲区
const u8 TEXT_TO_SEND[]={"ALIENTEK Explorer STM32F4 DMA 串口实验"};
int main(void)
{
u16 i;
u8 t=0;
u8 j,mask=0;
u8 pro=0;//进度
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
delay_init(168); //初始化延时函数
uart_init(115200); //初始化串口波特率为115200
LED_Init(); //初始化LED
KEY_Init(); //按键初始化
MYDMA_Config(DMA2_Stream7,DMA_Channel_4,(u32)&USART1->DR,(u32)SendBuff,SEND_BUF_SIZE);//DMA2,STEAM7,CH4,外设为串口1,存储器为SendBuff,长度为:SEND_BUF_SIZE.
//显示提示信息
j=sizeof(TEXT_TO_SEND);
for(i=0;i<SEND_BUF_SIZE;i++)//填充ASCII字符集数据
{
if(t>=j)//加入换行符
{
if(mask)
{
SendBuff[i]=0x0a;
t=0;
}else
{
SendBuff[i]=0x0d;
mask++;
}
}else//复制TEXT_TO_SEND语句
{
mask=0;
SendBuff[i]=TEXT_TO_SEND[t];
t++;
}
}
i=0;
while(1)
{
t=KEY_Scan(0);
if(t==KEY0_PRES) //KEY0按下
{
printf("\r\nDMA DATA:\r\n");
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA发送
MYDMA_Enable(DMA2_Stream7,SEND_BUF_SIZE); //开始一次DMA传输!
//等待DMA传输完成,此时我们来做另外一些事,点灯
//实际应用中,传输数据期间,可以执行另外的任务
while(1)
{
if(DMA_GetFlagStatus(DMA2_Stream7,DMA_FLAG_TCIF7)!=RESET)//等待DMA2_Steam7传输完成
{
DMA_ClearFlag(DMA2_Stream7,DMA_FLAG_TCIF7);//清除DMA2_Steam7传输完成标志
break;
}
pro=DMA_GetCurrDataCounter(DMA2_Stream7);//得到当前还剩余多少个数据
printf("%d",pro);
}
printf ("Transimit Finished!");//提示传送完成
}
i++;
delay_ms(10);
if(i==200)
{
LED0=!LED0;//提示系统正在运行
i=0;
}
}
}
本节MDA的知识点就介绍到这了,因为我自己也不是特别了解,以后也会慢慢研究,谢谢大家观看了。