DMA简介
DMA(Direct Memory Access)直接存储器存取
DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源。
DMA的作用就是解决大量数据转移过度消耗CPU资源的问题,有了DMA得CPU可以更加专注的实用的的操作——计算、控制等。外围设备可以通过DMA控制器直接访问内存,与此同时,CPU可以继续执行程序。DMA传输期间,DMA控制器接管了总线的控制权。在DMA传输结束后,DMA控制器将总线的控制权交给CPU。通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路,能使 CPU 的效率大为提高。(CPU控制DMA开启、关闭)
附:存储器映像
STM32F407的DMA
STM32的两个 DMA 控制器总共有 16 个数据流(每个控制器 8 个通道),每一个 DMA 控制器都用于管理 一个或多个外设的存储器访问请求。每个数据流总共可以有多达 8 个通道(或称请求)。每个通道都有一个仲裁器,用于处理 DMA 请求间的优先级。
每个通道都支持软件触发(存储器到存储器)和特定的硬件触发(存储器到外设,触发一次转运一次)
DMA 的数据传输方向方式共有四种:
1.外设到内存
2.内存到外设
3.内存到内存
4.外设到外设(保留模式,基本不用)
DMA控制器提供两个 AHB 主端口:AHB 存储器端口(用于连接存储器)和 AHB 外设端口 (用于连接外设)。但是,要执行存储器到存储器的传输,AHB 外设端口必须也能访问存储器。
但需要注意的是,只有DMA2能够实现内存到内存的传输。
DMA基本结构
DMA请求
如果外设要想通过 DMA 来传输数据,必须先给 DMA 控制器发送 DMA 请求, DMA 控制器根据通道优先级处理该请求。控制器会给外设一个应答信号,当外设 应答后且 DMA 控制器收到应答信号之后,就会启动 DMA 的传输,直到 传输完毕。
数据宽度与对齐
数据转运+DMA
ADC扫描模式+DMA
通道选择
DMA1和DMA2均有8个数据流,每个数据流都与一个 DMA 请求相关联,此 DMA 请求可以从 8 个可能的通道请求中选出。 每个通道都有一个仲裁器,用于处理DMA 请求间的优先级。此选择由 DMA_SxCR 寄存器中的 CHSEL[2:0] 位控制。
下面为DMA1和DMA2的请求映射关系:
仲裁器
仲裁器为两个 AHB 主端口(存储器和外设端口)提供基于请求优先级的 8 个 DMA 数据流请 求管理,并启动外设/存储器访问序列。
优先级管理分为软件和硬件,软件部分由数据流的优先级(DMA_SxCR寄存器种)决定,硬件部分由流的编号决定,机制类似中断硬件编号。
FIFO直接模式和阈值突发模式
1.直接模式(禁止FIFO):不使用FIFO 的阈值级别控制。每完成一次从外设到FIFO 的数据传输后,相应的数据立即就会移出并存储到目标中。
注:存储器到存储器传输时不得使用直接模式
2.FIFO阈值突发模式:在此模式下,每次产生外设请求.数据流都会启动数据源到FIFO的传输。达到FIFO的阈值级别时,FIFO的内容移出并存储到目标中。
FIFO用于在源数据传输到目标之前临时存储这些数据。每个数据流都有一个独立的(总容量16字节)FIFO,FIFO临时存储数据最多为16字节,FIFO的存储阈值级别可由软件配置为1/4(4字节)、1/2(8字节)、3/4(12字节)或满(16字节)。
DMA传输模式
DMA传输方向使用 DMA_SxCR 寄存器中的 DIR[1:0] 位进行配置,有三种可能的传输方向:存储器到外设、外设到存储器或存储器到存储器。
以存储器到外设模式为例,框图如下:
-
在此模式下数据流会立即启动传输,从数据源完全填充FIFO。每次发生外设请求,FIFO 的内容都会移出并存储到目标中。当 FIFO 的级别小于或等于预定义的阈值级别时,将使用存储器中的数据完全重载 FIFO。
-
如果传输完成或外设请求停止传输或软件关闭通道,传输会立即停止。
-
直接模式下,使能了数据流,DMA 便会预装载第一个数据,将其传输到内部 FIFO。一旦外设请 求数据传输,DMA 便会将预装载的值传输到配置的目标。每次传输都会将数据先装载至内部FIFO。
-
预装载的数据大小为 DMA_SxCR 寄存器中 PSIZE 位字段的值。
-
数据流在经过仲裁后才可以访问源或目标端口
指针递增
外设和存储器指针在每次传输后可以自动向后递增或保持常量。
如果使能了递增模式,则根据在 DMA_SxCR 寄存器 PSIZE 或 MSIZE 位中编程的数据宽度,下一次传输的地址将是前一次传输的地址递增 1(对于字节)、2(对于半字)或 4(对于字)。
单个寄存器访问外设源或目标数据时,禁止递增模式
普通模式与循环模式
普通模式:DMA搬运了设定长度的数据后,CNDTR清0,会产生中断标志,然后DMA就停止工作了,如果再有数据也不接收了。需关断 DMA 使能后再重新配置后才能继续传输。
循环模式: DMA搬运了设定长度的数据后,CNDTR清0,会产生中断标志,如果再有数据,DMA会循环保存到内存中,覆盖前面的数据。在CNDTR=0时DMA会自动装载初始化时的配置,CNDTR重置为初始值。
代码
1.DMA.c
#include "DMA.h"
#include "stm32f4xx.h"
uint8_t data[4] = {0x01,0x02,0x03,0x04};
u16 MyDMA_size;
void dma_usart_init()
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);
DMA_InitStructure.DMA_Channel = DMA_Channel_4; //串口对应的通道4
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR; //串口地址
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)data; //内存地址
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; //内存到外设
DMA_InitStructure.DMA_BufferSize = 4; //传输数据大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设非增量模式
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存增量模式
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设数据长度1字节
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //内存数据长度1字节
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //普通模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //中等优先级
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable; //不使用FIFO
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull; //满缓冲
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; //内存单节拍存储
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; //外设单节拍存储
//DMA_DeInit(DMA2_Stream7);//复位,清除FIFO缓冲区里的数据
DMA_Init(DMA2_Stream7, &DMA_InitStructure);
DMA_Cmd(DMA2_Stream7, ENABLE);
}
void dma_usart_start()
{
//1.去使能关闭传输
DMA_Cmd(DMA2_Stream7, DISABLE);
//2.确保可以正常工作
while(DMA_GetCmdStatus(DMA2_Stream7) == ENABLE)
//3.开始数据传输
DMA_SetCurrDataCounter(DMA2_Stream7, MyDMA_size);
//4.使能
DMA_Cmd(DMA2_Stream7, ENABLE);
//判断传输是否完成
if(DMA_GetFlagStatus(DMA2_Stream7, DMA_FLAG_TCIF7) == SET)
{
DMA_ClearFlag(DMA2_Stream7, DMA_FLAG_TCIF7);//清空标志位
}
}
2.DMA.h
#ifndef _DMA_H
#define _DMA_H
#include "stm32f4xx.h"
void dma_usart_init(void);
void dma_usart_start(void);
#endif
3.main.c
int main(void)
{
uint8_t data[8] = {1,2,3,4,5,6,7,8};
uint8_t databuf[8];
SysTick_Init();
USART_Init_Config();
dma_usart_init();
USART_DMACmd(USART1,USART_DMAReq_Tx, ENABLE);
while(1)
{
dma_usart_start();
//printf("\r\n");
Delay_ms(1000);
}
}