赞
踩
目录
本节我们来学习有关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应该是先进先出队列,我们在此不做过多介绍。
- 双 AHB 主总线架构,一个用于存储器访问,另一个用于外设访问
- 仅支持 32 位访问的 AHB 从编程接口
- 每个 DMA 控制器有 8 个数据流,每个数据流有多达 8 个通道(或称请求)
- 每个数据流有单独的四级 32 位先进先出存储器缓冲区(FIFO),可用于 FIFO 模式或直接模式。
- 通过硬件可以将每个数据流配置为:
- 1,支持外设到存储器、存储器到外设和存储器到存储器传输的常规通道 。
- 2,支持在存储器方双缓冲的双缓冲区通道
- 8 个数据流中的每一个都连接到专用硬件 DMA 通道(请求)
- DMA 数据流请求之间的优先级可用软件编程(4 个级别:非常高、高、中、低),在软件优先级相同的情况下可以通过硬件决定优先级(例如,请求 0 的优先级高于请求 1)
- 每个数据流也支持通过软件触发存储器到存储器的传输(仅限 DMA2 控制器)
- 可供每个数据流选择的通道请求多达 8 个。此选择可由软件配置,允许几个外设启动 DMA 请求
- 要传输的数据项的数目可以由 DMA 控制器或外设管理:
- 1,DMA 流控制器:要传输的数据项的数目是 1 到 65535,可用软件编程 。
- 2,外设流控制器:要传输的数据项的数目未知并由源或目标外设控制,这些外设通过硬 件发出传输结束的信号
- 独立的源和目标传输宽度(字节、半字、字):源和目标的数据宽度不相等时,DMA 自动封装/解封必要的传输数据来优化带宽(这个有点类似于Java里的缓冲流)。这个特性仅在 FIFO 模式下可用。
- 对源和目标的增量或非增量寻址
- 支持 4 个、8 个和 16 个节拍的增量突发传输。突发增量的大小可由软件配置,通常等 于外设 FIFO 大小的一半
- 每个数据流都支持循环缓冲区管理
- 5 个事件标志(DMA 半传输、DMA 传输完成、DMA 传输错误、DMA FIFO 错误、直接模式错误),进行逻辑或运算,从而产生每个数据流的单个中断请求
这个是通道选择,具体是哪个寄存器连接到数据流,是通过CHSEL寄存器来控制的。
这就是一一对应关系,不同的数据流和通道对应不同的外设。
8 个 DMA 控制器数据流都能够提供源和目标之间的单向传输链路。 每个数据流配置后都可以执行:
- 常规类型事务:存储器到外设、外设到存储器或存储器到存储器的传输。
- 双缓冲区类型事务:使用存储器的两个存储器指针的双缓冲区传输(当 DMA 正在进行自/ 至缓冲区的读/写操作时,应用程序可以进行至/自其它缓冲区的写/读操作)。
要传输的数据量(多达 65535)可以编程,并与连接到外设 AHB 端口的外设(请求 DMA 传输)的源宽度相关。每个事务完成后,包含要传输的数据项总量的寄存器都会递减。
DMA 事务由给定数目的数据传输序列组成。要传输的数据项的数目及其宽度(8 位、16 位 或 32 位)可用软件编程。 每个 DMA 传输包含三项操作:
- 通过 DMA_SxPAR 或 DMA_SxM0AR 寄存器寻址,从外设数据寄存器或存储器单元中加载数据。
- 通过 DMA_SxPAR 或 DMA_SxM0AR 寄存器寻址,将加载的数据存储到外设数据寄存 器或存储器单元。
- DMA_SxNDTR 计数器在数据存储结束后递减,该计数器中包含仍需执行的事务数。
在产生事件后,外设会向 DMA 控制器发送请求信号。DMA 控制器根据通道优先级处理该请求。只要 DMA 控制器访问外设,DMA 控制器就会向外设发送确认信号。外设获得 DMA 控制器的确认信号后,便会立即释放其请求。一旦外设使请求失效,DMA 控制器就会释放确认信号。如果有更多请求,外设可以启动下一个事务。
仲裁器为两个 AHB 主端口(存储器和外设端口)提供基于请求优先级的 8 个 DMA 数据流请求管理,并启动外设/存储器访问序列。 优先级管理分为两个阶段:
- 软件:每个数据流优先级都可以在 DMA_SxCR 寄存器中配置。分为四个级别:非常高优先级、高优先级、中优先级、低优先级。
- 硬件:如果两个请求具有相同的软件优先级,则编号低的数据流优先于编号高的数据 流。例如,数据流 2 的优先级高于数据流 4。
其实有点类似于中断的优先级的概念。
其实比较好理解,就是发送数据时指针自动递增,就能发送一条完整的数据流了。
就是是否需要循环发送数据。
我们可以理解为,两个数据流同时操作,比如DMA1在传输的时候,去填充DMA2的数据,等DMA1完成后,DMA2就接上DMA1的数据流继续发送。
对于每个 DMA 数据流,可在发生以下事件时产生中断:
- 达到半传输
- 传输完成
- 传输错误
- FIFO 错误(上溢、下溢或 FIFO 级别错误)
- 直接模式错误
可以使用单独的中断使能位以实现灵活性。
我们在代码中详细介绍一下整个的过程。
- 如果使能了数据流,通过重置 DMA_SxCR 寄存器中的 EN 位将其禁止,然后读取此位 以确认没有正在进行的数据流操作。将此位写为 0 不会立即生效,因为实际上只有所有 当前传输都已完成时才会将其写为 0。当所读取 EN 位的值为 0 时,才表示可以配置数 据流。因此在开始任何数据流配置之前,需要等待 EN 位置 0。应将先前的数据块 DMA 传输中在状态寄存器(DMA_LISR 和 DMA_HISR)中置 1 的所有数据流专用的位置 0, 然后才可重新使能数据流。
- 在 DMA_SxPAR 寄存器中设置外设端口寄存器地址。外设事件发生后,数据会从此地址 移动到外设端口或从外设端口移动到此地址。
- 在 DMA_SxMA0R 寄存器(在双缓冲区模式的情况下还有 DMA_SxMA1R 寄存器)中设 置存储器地址。外设事件发生后,将从此存储器读取数据或将数据写入此存储器。
- 在 DMA_SxNDTR 寄存器中配置要传输的数据项的总数。每出现一次外设事件或每出现 一个节拍的突发传输,该值都会递减。
- 使用 DMA_SxCR 寄存器中的 CHSEL[2:0] 选择 DMA 通道(请求)。
- 如果外设用作流控制器而且支持此功能,请将 DMA_SxCR 寄存器中的 PFCTRL 位置 1。
- 使用 DMA_SxCR 寄存器中的 PL[1:0] 位配置数据流优先级。
- 配置 FIFO 的使用情况(使能或禁止,发送和接收阈值)。
- 配置数据传输方向、外设和存储器增量 / 固定模式、单独或突发事务、外设和存储器数 据宽度、循环模式、双缓冲区模式和传输完成一半和/或全部完成,和/或 DMA_SxCR 寄存器中错误的中断。
- 通过将 DMA_SxCR 寄存器中的 EN 位置 1 激活数据流。 一旦使能了流,即可响应连接到数据流的外设发出的任何 DMA 请求。 一旦在 AHB 目标端口上传输了一半数据,传输一半标志 (HTIF) 便会置 1,如果传输一半中 断使能位 (HTIE) 置 1,还会生成中断。传输结束时,传输完成标志 (TCIF) 便会置 1,如果 传输完成中断使能位 (TCIE) 置 1,还会生成中断。
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的知识点就介绍到这了,因为我自己也不是特别了解,以后也会慢慢研究,谢谢大家观看了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。