文章目录
DMA基本原理
DMA(Direct Memory Access)
Direct Memory Access 直接存储器访问
将数据从一个地址空间复制到另一个地址空间
当CPU初始化这个传输动作,传输动作本身是由DMA控制器来实现和完成的
DMA传输方式无需CPU直接传输控制,通过硬件为RAM和IO设备开辟一条直接传输数据的通道,使得CPU效率大大提高
作用:为CPU减负
两个DMA控制器,2个DMA控制器总共有16个数据流。每个数据流有8个通道,每个通道一个仲裁器,用于处理DMA请求间的优先级。
控制器用来管理存储器访问请求
DMA主要特性
① 每个DMA控制器8个数据流 每个数据流多达8个通道
②每个数据流有单独四级32位先进先出存储器缓冲区(FIFO),可用于FIFO模式或直接模式
__FIFO模式:可通过软件将阈值级别选取为FIFO大小的1/4、1/2或3/4
__直接模式:
每个DMA请求会立即启动对存储器的传输。当在直接模式下将DMA请求配置 为以存储器到外设模式传输数据时,DMA仅会将一个数据从存储器预加载到内部FIFO,从而确保一旦外设触发DMA请求时,则立即传输数据
③通过硬件可以将每个数据流配置为
—支持外设到存储器、存储器到外设和存储器到存储器传输的常规通道
—也支持在存储器方双缓冲的双缓冲区通道
④每个数据流都连接专用硬件DMA通道
⑤DMA数据流请求优先级可用编程(非常高、高、中、低)
在软件优先级相同的情况下可以通过硬件决定优先级
⑥每个数据流支持软件触发存储器到存储器的传输
⑦供每个数据流选择的通道请求多达8个。软件配置允许几个外设启动DMA请求
⑧要传输数据项的数目可由DMA控制器或外设管理:
—DMA流控制器:要传输的数据项的数目是1到65535,可用软件编程
—外设流控制器:要传输数据项的数目未知并由源或目标外设控制,这些外设通 过硬件发出传输结束信号
⑨独立的源和目标传输宽度(字节 半子 字) 源和目标的数据宽度不相等时,DMA自动封装/解封必要的传输数据来优化带宽。仅FIFO
⑩对源和目标的增量或非增量寻址
⑪ 支持4、8、16个节拍 的增量突发传输。大小通常为FIFO的一半
⑫每个数据流都支持循环缓冲区管理
⑬5个事件标志(DMA半传输、DMA传输完成、DMA传输错误、DMA FIFO错误、直接模式错误),进行逻辑或运算,从而产生每个数据流的单个中断请求
通道选择
每个数据流关联一个DMA请求 从8个可能的通道请求中选出
DMA请求映射
DMA数据流
8个DMA控制器数据流都能够提供源和目标之间的单向传输链路
每个数据流配置后都可以执行:
常规类型事务:存储器到外设、外设到存储器或存储器到存储器的传输
双缓冲区类型事务:使用存储器的两个存储器指针的双缓冲区传输
传输数据量可以编程,并与连接到外设AHB端口的外设源宽度相关。每个事务完成后,包含传输数据项 的总量的寄存器都会递减
DMA事务
即给定数目的数据传输序列组成
要传输数据项的数目及其宽度可用软件编程
DMA传输包含三项操作:
-----通过DMA_SxPAP或DMA_SxM0AR 寄存器寻址,从外设数据寄存器或存储器单元中加载数据
-----通过DMA_SxPAP或DMA_SxM0AR 寄存器寻址,将加载的数据存储到外设数据寄存器或存储器单元
-----DMA_SxNDTR计数器在数据存储结束后递减,该计数器中包含仍需执行的事务数。
仲裁器
仲裁器为两个AHB主端口(存储器和外设端口)提供基于请求优先级的8个DMA数据流请求管理,并启动外设/存储器访问序列
优先级管理两个阶段
软件:每个数据流优先级都可以在DMA_SxCR 寄存器中配置。四个级别:
非常高 高 中 低
硬件:如果两个请求具有相同的软件优先级,编号低的数据流优先编号高的数据流
指针递增
外设和存储器指针在每次传输后可以自动向后递增或保持常量
循环模式
用于处理循环缓冲区和连续数据流(例如ADC扫描模式)。可使用DMA_SxCR 寄存器中的CIRC位使能此特性
当激活循环模式时,要传输的数据项的数目在数据流配置阶段自动用设置的初始值进行加载,并继续响应DMA请求
单次传输和突发传输
DMA控制器可以产生单次传输或4 8 16 个节拍的增量突发传输
突发大小通过DMA_SxCR寄存器中的MBURST【1:0】和PBURST【1:0】位独立配置
突发大小指示突发中的节拍数,而不是传输的字节数
为确保数据一致性,形成突发的每一组传输都不可分割;在突发传输序列期间,AHB传输会锁定,并且AHB总线矩阵的仲裁器不解除对DMA主总线的授权
根据单次或突发配置的情况,每个DMA请求在AHB外设端口上相应的启动不同数量的传输。
当AHB外设端口被配置为单次传输时,根据DMA_SxCR寄存器PSIZE【1:0】位 的值,每个DMA请求产生一次字节、半字或字的数据传输
当AHB外设端口被配置为突发传输,根据DMA_SxCR寄存器PBURST【1:0】和PSIZE【1:0】位的值,每个DMA请求相应的生成4、8、16个节拍的字节、半字或字的传输。
双缓冲区模式
DBM位置1 使能双缓冲区模式,两个存储器指针,每次事务结束时交换。
每次事务结束时,DMA控制器都从一个存储器目标交换为另一个存储器目标。
则软件在处理一个存储器区域的同时,DMA传输还可以填充使用第二个存储器区域。
DMA中断
每个DMA数据流,可发生中断:
达到半传输 传输完成 传输错误 FIFO错误 直接模式错误
流配置过程
配置DMA数据流x时遵守顺序:
1.使能的数据流,通过重置DMA_SxCR寄存器中的EN位将其禁止,然后读取此位以确认没有正在进行的数据流操作。将此位写为0不会立即生效,所有当前传输都已完成时才会将其写为0,。当读取EN位值为0时,才表示可以配置数据流。因此在开始任何数据流配置之前,需要等待EN位置为0.应将先前的数据块DMA传输中在状态寄存器中置1的所有数据流专用的位置0,然后才可重新使能数据流
2.在DMA_SxPAR寄存器中设置外设端口寄存器地址。外设事件发生后,数据会从此地址移动到外设端口或从外设端口移动到此地址
3.在DMA_ SxMA0R寄存器中设置存储器地址。外设事件发生后,将从此存储器读取数据或将数据写入此存储器
4.在DMA_SxNDTR寄存器中配置要传输的数据项的总数。出现一次外设事件或节拍,该值都会递减
5.使用DMA_SxCR寄存器中的CHSEL【2:0】选择DMA通道
6.外设用作流控制器且支持此功能,将DMA_SxCR寄存器中的PFCTRL位置1
7.使用DMA_SxCR寄存器中的PL【1:0】位配置数据流优先级
8.配置FIFO的使用情况(使能或禁止,发送和接收阈值)
9.配置数据传输方向。外设和存储器增量/固定模式、单独或突发事务、外设和存储器数据宽度、循环模式、双缓冲区模式和传输完成一半或全部完成,和/或DMA_SxCR 寄存器中错误的中断
10.通过将DMA_SxCR寄存器中的EN位置1激活数据流
一旦使能流,即可响应连接到数据流的外设发出的任何DMA请求
实验程序
DMA配置参数
通道 优先级 数据传输方向 存储器/外设 数据宽度 存储器/外设 地址是否增量 循环模式 数据传输量
DMA库函数
设置的结构体参数
typedef struct
{
uint32_t DMA_Channel; //可设置8个通道
uint32_t DMA_PeripheralBaseAddr; //外设基地址
uint32_t DMA_Memory0BaseAddr; //存储器基地址
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_FIFOMode;
uint32_t DMA_FIFOThreshold;
uint32_t DMA_MemoryBurst;
uint32_t DMA_PeripheralBurst;
}DMA_InitTypeDef;
通道
#define DMA_Channel_0 ((uint32_t)0x00000000)
#define DMA_Channel_1 ((uint32_t)0x02000000)
#define DMA_Channel_2 ((uint32_t)0x04000000)
#define DMA_Channel_3 ((uint32_t)0x06000000)
#define DMA_Channel_4 ((uint32_t)0x08000000)
#define DMA_Channel_5 ((uint32_t)0x0A000000)
#define DMA_Channel_6 ((uint32_t)0x0C000000)
#define DMA_Channel_7 ((uint32_t)0x0E000000)
DIR传输方向
#define DMA_DIR_PeripheralToMemory ((uint32_t)0x00000000)
#define DMA_DIR_MemoryToPeripheral ((uint32_t)0x00000040)
#define DMA_DIR_MemoryToMemory ((uint32_t)0x00000080)
设置优先级
:低 中 高 非常高
#define DMA_Priority_Low ((uint32_t)0x00000000)
#define DMA_Priority_Medium ((uint32_t)0x00010000)
#define DMA_Priority_High ((uint32_t)0x00020000)
#define DMA_Priority_VeryHigh ((uint32_t)0x00030000)
判断是否有别的流在运行
FunctionalState DMA_GetCmdStatus(DMA_Stream_TypeDef* DMAy_Streamx);
DMA配置程序过程(串口发送DMA)
① 使能DMA时钟
RCC_AHB1PeriphClockCmd();
②初始化DMA通道参数
DMA_Init();
③ 使能串口DMA发送,串口DMA使能函数
USART_DMACmd();
④查询DMA的EN位,确保数据流就绪,可以配置
DMA_GetCmdStatus();
⑤设置通道当前剩余量
DMA_SetCurrDataCounter();
⑥使能DMA1通道,启动传输
DMA_Cmd();
⑦查询DMA传输状态
DMA_GetFlagStatus();
⑧获取/设置通道当前剩余数据量
DMA_GetCurrDataCounter();
配置函数
void MYDMA_Config (DMA_Stream_TypeDef*DMA_Streamx, u32 chx,u32 par,u32 mar,ul6 ndtr)
DMA_InitTypeDef DMA_InitStructure;
if ((u32) DMA_streamx> (u32) DMA2)//当前stream是属于DMA2还是DAA1,通过基地址的比较来判断
RCC_AHB1PeriphclockCmd (RCC_AHBlPeriph_DMA2,ENABLE);//DMA2时钟使能}else l
RCC_AHBlPeriphclockCmd (RCc_AHBlPeriph_DMA1,ENABLE);//DMAl时钟使能DMA_DeInit (DMA_streamx) ;
while (DMA_Getcmdstatus( DMA_Streamx) != DISABLE){ }//等待DMA可配置
/*配置DMA Stream*/
DMA_Initstructure.DMA_Channel = chx;//通道选择
DMA_Initstructure.DMA_PeripheralBaseAddr = par; //DMA外设地址
DMA_Initstructure.DMz_Memory0BaseAddr = mar ; // DMA存储器0地址
DMA_Initstructure.DMA_DIR = DMA_DIR MemoryToPeripheral;//存储器到外设模式
DMA_Initstructure.DMA_Buffersize = ndtr; //数据传输量
DMA_Initstructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式DMA_Initstructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器增量模式
DMA_Initstructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据长度:e位DMA_Initstructure.DMA_MemoryDataSize = DMA_MemoryDataSize Byte;//存储器数据长度:e位
DMA_Initstructure.DMA Mode = DMA Mode Normal;//使用普通模式
DMA_Initstructure.DMA_Priority = DMA_Priority_Medium;//中等优先级DMA_Initstructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_Initstructure.DMA_FIFoThreshold = DMA_FIFoThreshold_Full;
DMA_Initstructure.DMA_MemoryBurst = DMA MemoryBurst Single;//存储器突发单次传输
DMAInitStructure.DMA PeripheralBurst = DMA PeripheralBurst Single;//外设突发单次传输
DMA_Init (DMA_streamx,&.DMA_Initstructure);//初始化DMA stream
使能数据流
void MYDMA_Enable(DMA_stream_TypeDef *DMA_Streamx,ul6 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 "lcd.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[]={ "ALIENTEKExplorer STM32F4 DMA串口实验";
int main (void)
{
ul6 i;
u8 t=0;
u8 j, mask=0;
float pro=0;//进度
NVIC_PriorityGroupConfig (NVIc_PriorityGroup_2);//设置系统中断优先级分组2
delay_init ( 168);//初始化延时函数
uart_init ( 115200) ; //初始化串口波特率为115200
LED_Init();//初始化LED
LcDInit( ;//LCD初始化
KEYInit(;/按键初始化
MYDNA_Config(DMA2_Stream7, DNA_Channel_4,(u32)& USART1->DR,(u32)SendBuff,SEND_BUF_SIZE);//DMA2,STEAM7,CH4,外设为串口i,存储器为sendBuff,长度为:SEND BUF SIZE.
POINT_COLOR=RED;
LCD_ShowString (30,50,200,16,l6, "Explorer STM32F4");
LCD_ShowString (30,70,200,16,16,"DMA TEST");
LcD_ShowString (30,90,200,16,16,"ATOMRALIENTEK");
LCD_ShowString (30,110,200,16,16, "2021/9/9");
LcD_ShowString (30,130,200,16,16,"KEY0:start");
POINT_COLOR=BLUE;//设置字体为蓝色
//显示提示信息
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++;
}
}
POINT_COLOR=BLUE;//设置字体为蓝色
i=0;
while(l)
t一KEY__Scan (0) ;
if(t==KEYO_PRES)//KEY0按下
{
printf("\r\nDMA DATA: \r\n");
LCD_ShowString (30,150,200,16,16,"Start Transimit. . ..") ;
LCD_ShowString (30,170,200,16,16,"%");//显示百分号
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);//使能串口1的DMA发送
MYDMA_Enable(DMA2_Stream7,SEND_BUF_SIZE);//开始一次DMA传输!等待DMA传输完成
//实际应用中,传输数据期间,可以执行另外的任务
while (l)
{
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);//得到当前还剩余多少个数据
pro=l-pro/ SEND_BUF_SIZE;//得到百分比
pro*=100;
//扩大100倍
LCD_ShowNum (30,170,pro,3,16) ;
}
LCD_ShowNum (30,170,100,3,16) ; //显示100%
LcD_ShowString (30,150,200,16,16,"Transimit Finished!");//提示传送完成}
i++;
delay_ms (10) ;if(i==20)
{
LED0=!LED0;//提示系统正在运行
i=0;
}
}
}