一、DMA简介:
DMA,全称为:Direct Memory Access,即直接存储器访问。
DMA传输将数据从一个地址复制空间复制到另外一个地址空间,当CPU初始化这个传输动作之后,传输动作本身是由DMA控制器来实现和完成的。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路,能使 CPU 的效率大为提高。
STM32F4 最多有 2 个 DMA 控制器(DMA1 和 DMA2),共 16 个数据流(每个控制器 8 个),每一个 DMA 控制器都用于管理一个或多个外设的存储器访问请求。每个数据流总共可以有多达 8个通道(或称请求)。每个数据流通道都有一个仲裁器,用于处理 DMA请求间的优先级。
1.1 DMA框图
从上图中可以看出,每个DMA有8个数据流,即REQ_STREAM0~REQ_STREAM7,每个数据流有8个通道,即REQ_STRx_CH0~REQ_STRx_CH7,虽然每个数据流有8个通道,但是同一时刻,只有一个通道可以连接到数据流。DMA可以执行下列事务:
- 外设到存储器的传输
- 存储器到外设的传输
- 存储器到存储器的传输
1.2 DMA主要特性:
STM32F4 的DMA 有以下一些特性:
- 每个 DMA 控制器有8 个数据流,每个数据流有多达 8 个通道(或称请求)
- 每个数据流有单独的四级 32 位先进先出存储器缓冲区(FIFO),可用于 FIFO 模式或直接模式
- FIFO模式:可通过软件将阈值级别选取为FIFO大小的1/4、1/2、或3/4。
- 直接模式:每个DMA请求会立即启动对存储器的传输。
- 通过硬件可以将每个数据流配置为:
- 支持外设到存储器、存储器到外设和存储器到存储器传输的常规通道
- 支持在存储器方双缓冲的双缓冲区通道
- 8 个数据流中的每一个都连接到专用硬件 DMA 通道(请求)
- DMA 数据流请求之间的优先级可用软件编程(4 个级别:非常高、高、中、低),在软件优先级相同的情况下可以通过硬件决定优先级(例如,请求 0 的优先级高于请求 1)
- 每个数据流也支持通过软件触发存储器到存储器的传输(仅限 DMA2 控制器)
- 可供每个数据流选择的通道请求多达 8 个。此选择可由软件配置,允许几个外设启动DMA 请求
- 要传输的数据项的数目可以由 DMA 控制器或外设管理:
- DMA 流控制器:要传输的数据项的数目是 1 到 65535,可用软件编程
- 外设流控制器:要传输的数据项的数目未知并由源或目标外设控制,这些外设通过硬件发出传输结束的信号
- 独立的源和目标传输宽度(字节、半字、字):源和目标的数据宽度不相等时,DMA自动封装/解封必要的传输数据来优化带宽(显然要FIFO配合)。这个特性仅在 FIFO 模式下可用。
- 对源和目标的增量或非增量寻址
- 支持 4 个、8 个和 16 个节拍的增量突发传输。突发增量的大小可由软件配置,通常等于外设 FIFO 大小的一半
- 每个数据流都支持循环缓冲区管理
- 5 个事件标志(DMA 半传输、DMA 传输完成、DMA 传输错误、DMA FIFO 错误、直接模式错误),进行逻辑或运算,从而产生每个数据流的单个中断请求
二 DMA配置
2.1 通道选择
DMA中,每个数据流对应有8个通道,通道的选择是通过DMA_SxCR 寄存器来控制的,如下图所示:
从上图可以看出,DMA_SxCR 控制数据流到底使用哪一个通道,每个数据流有 8 个通道可供选择,每次只能选择其中一个通道进行 DMA 传输。
2.2 DMA请求映射:
DMA1各数据流的通道映射如下:
DMA2各数据流的通道映射如下:
2.3 DMA数据流
DMA的8个数据流能够提供源和目标之间的单向传输链路。
每个数据流在配置之后都可以执行:
- 常规类型事务:存储器到外设、外设到存储器、存储器到存储器的传输
- 双缓冲区类型事务:使用存储器的两个存储器指针的双缓冲传输。
要传输的数据量(多达65535)可以编程,并与连接到AHB端口的外设源宽度相关。每个事务完成之后,包含要传输的数据项总量的寄存器都会递减。
2.4 DMA事务:
DMA事务由给定数目的数据传输序列组成。要传输的数据项的数目及其宽度(8位、16位或32位)可用软件编程。
每个DMA传输包含三个操作:
- 通过DMA_SxPAR或DMA_SxM0AR寄存器寻址,从外设数据寄存器或存储单元中加载数据。
- 通过DMA_SxPAR或DMA_SxM0AR寄存器寻址,从加载的数据存储到外设数据寄存器或存储单元。
- DMA_SxNDTR计数器在数据存储结束后递减,该计数器中包含仍需执行的事务数。
传输方向使用 DMA_SxCR 寄存器中的 DIR[1:0] 位进行配置,有三种可能的传输方向:存储器到外设、外设到存储器或存储器到存储器。下表介绍了相应的源和目标地址:
2.5 仲裁器
仲裁器为两个 AHB 主端口(存储器和外设端口)提供基于请求优先级的 8 个 DMA 数据流请求管理,并启动外设/存储器访问序列。
优先级管理分为两个阶段:
● 软件:每个数据流优先级都可以在 DMA_SxCR 寄存器中配置。分为四个级别:
— 非常高优先级
— 高优先级
— 中优先级
— 低优先级
● 硬件:如果两个请求具有相同的软件优先级,则编号低的数据流优先于编号高的数据流。例如,数据流 2 的优先级高于数据流 4。
2.5 指针递增
根据 DMA_SxCR 寄存器中 PINC 和 MINC 位的状态,外设和存储器指针在每次传输后可以自动向后递增或保持常量。
2.6 循环模式
循环模式可用于处理循环缓冲区和连续数据流(例如 ADC 扫描模式)。可以使用DMA_SxCR寄存器中的 CIRC 位使能此特性。
当激活循环模式时,要传输的数据项的数目在数据流配置阶段自动用设置的初始值进行加载,并继续响应 DMA 请求。
2.7 可编程数据宽度、封装/解封、字节序
要传输的数据项数目(即数据量)必须在使能数据流之前编程到 DMA_SxNDTR(要传输数据项数目位, NDT)中,当流控制器是外设且 DMA_SxCR 中的 PFCTRL 位置为 1 时除外。
当使用内部 FIFO 时,源和目标数据的数据宽度可以通过 DMA_SxCR 寄存器的 PSIZE 和MSIZE 位(可以是 8、16 或32 位)编程。
2.8 DMA中断
对于每个DMA数据流,可在发生以下事件时产生中断:
- 达到半传输
- 传输完成
- 传输错误
- FIFO错误(上溢、下溢或FIFO级别错误)
- 直接模式错误
可以使用单独的中断使能位以实现灵活性。如下表:
三、DMA配置过程
配置DMA数据流x(0~7)应该遵循以下顺序:
- 如果使能了数据流,通过重置DMA_SxCR寄存器中的EN位,将其禁止,然后读取此位以确认没有正在进行的数据流操作。将此位写为0不会立即生效,因为实际上只有所有当前传输都以完成时才会将其写为0。当所读取EN位的值为0时,才表示可以配置数据流。因此在开始任何数据流配置之前,需要等待EN位置为0。
- 在DMA_SxPAR寄存器中设置外设端口寄存器地址。外设事件发生后,数据会从此地址移动到外设端口或从外设端口移动到此地址。
- 在DMA_SxMA0R寄存器中设置存储器地址。外设事件发生之后,将从此存储器读取数据或将数据写入此存储器。
- 配置数据传输方向,外设和存储器增量/固定模式、单独或突发事务、外设和寄存器数据宽度、循环模式、双缓冲区模式和传输一半或全部完成或DMA_SxCR寄存器中错误的中断。
- 在DMA_SxNDTR寄存器中配置要传输的数据总量。每出现一次外设事件或每出现一个节拍的突发传输(突发传输指非8位的传输,如16位、32位等,由编程确立),该值都会递减。
- 使用DMA_SxCR寄存器中的CHESL[2:0]选择DMA通道。
- 如果外设作流控制器,而且支持此功能,将DMA_SxCR寄存器中的PFCTRL位置1。
- 使用DMA_SxCR寄存器中的PL[1:0]位配置数据流的优先级。
- 配置FIFO;
- 通过将DMA_SxCR寄存器中的EN位置1激活数据流。
一旦使能了流,即可响应连接到数据流的外设发出的任何DMA请求。并且会触发相应的中断。
//DCMI DMA配置
//DMA_Memory0BaseAddr:存储器地址 将要存储摄像头数据的内存地址(也可以是外设地址)
//DMA_BufferSize:存储器长度 0~65535
//DMA_MemoryDataSize:存储器位宽
//DMA_MemoryDataSize:存储器位宽 @defgroup DMA_memory_data_size :DMA_MemoryDataSize_Byte/DMA_MemoryDataSize_HalfWord/DMA_MemoryDataSize_Word
//DMA_MemoryInc:存储器增长方式 @defgroup DMA_memory_incremented_mode /** @defgroup DMA_memory_incremented_mode : DMA_MemoryInc_Enable/DMA_MemoryInc_Disable
void DCMI_DMA_Init(u32 DMA_Memory0BaseAddr,u16 DMA_BufferSize,u32 DMA_MemoryDataSize,u32 DMA_MemoryInc)
{ DMA_InitTypeDef DMA_InitStructure;// 1.使能DMA2时钟,并等待数据流可配置RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE); //DMA2时钟使能 DMA_DeInit(DMA2_Stream1);while (DMA_GetCmdStatus(DMA2_Stream1) != DISABLE){} //等待DMA2_Stream1可配置 /*** * 配置 DMA Stream **/// 通道1 DCMI通道 DMA_InitStructure.DMA_Channel = DMA_Channel_1; // 外设地址为:DCMI->DRDMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&DCMI->DR;// DMA 存储器0地址DMA_InitStructure.DMA_Memory0BaseAddr = DMA_Memory0BaseAddr;// 数据传输方向,此处为外设到存储器模式DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;// 数据传输量 DMA_InitStructure.DMA_BufferSize = DMA_BufferSize;// 外设非增量模式DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;// 存储器增量模式DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc;// 外设数据长度:32位DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;// 存储器数据长度 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize;// 使用循环模式 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;// 高优先级DMA_InitStructure.DMA_Priority = DMA_Priority_High;// FIFO模式DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;// 使用全FIFO DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;// 外设突发单次传输DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;// 存储器突发单次传输DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;DMA_Init(DMA2_Stream1, &DMA_InitStructure);//初始化DMA Stream
}