DMA
一、简介
直接存储器存取(Direct Memory Access, DMA),可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省CPU资源。F407具有两个通用双端口DMA,每个DMA有8个流,每个流最多8个通道(请求),每个通道都支持软件触发和特定的硬件触发。双AHB总线结构,一个专用于存储器访问,一个专用于外设访问。为外设配备了专用FIFO,旨在提供最大外围带宽。一句话总结DMA:协助CPU完成数据转运,原数据不会消失。
存储器映像
- ROM
- 程序存储器Flash
主闪存,存储C语言编译后的程序代码,程序被下载到此处,MCU一般也是从这里开始运行的 - 系统存储器
存储BootLoader,用于串口下载,存储介质也是Flash,类似PC中的BIOS系统 - 选项字节
存储一些独立于程序代码的配置参数,一般是MCU的参数、读写保护、看门狗等配置
- 程序存储器Flash
- RAM
- 运行内存SRAM
存储运行过程中的临时变量,相当于PC主机的内存条 - 外设寄存器
存储各个外设的配置参数,即初始化各个外设最终所读写的东西,存储介质其实也是SRAM,寄存器是连接软件和硬件的桥梁 - 内核外设寄存器
存储内核各个外设的配置参数,像NVIC、SysTick等
- 运行内存SRAM
DMA请求得根据存储器设备是否支持读写进行配置。只有DMA2支持M2M模式。
- 在选择数据流时,如果需要高优先级的数据传输可选择更低的数据流编号;如果需要大数据量传输,则可以选择更高编号的数据流,提高数据传输效率
- 在选择通道时,通常情况下建议选择DMA2通道0或1,这两个通道支持循环模式并且具有高优先级
外设和存储器两个数据站点都有三个参数
- 起始地址
决定数据从哪来,到哪去 - 数据宽度
指定依次转运要按多大得数据宽度来进行,可以选择Byte
、HalfWord
和Word
。32位系统中一个字为4个字节,数据转运时还得注意对齐,不够补0,多了就舍弃 - 地址是否自增
指定一次转运完成后,下一次转运是否要把地址移动到下一个位置去,寄存器地址不会自增,存储器地址会自增
传输计数器是一个自减计数器,用于指定转运次数,减到0之后,会恢复到起始地址位置。自动重装器是可以循环为传输计数器装填计数次数,类似ADC的连续模式。M2M是选择软件触发还是硬件触发,软件触发一般适用于存储器到存储器的转运、硬件触发一般适用于外设寄存器的转运。写传输计数器时,必须先关闭DMA再进行。
STM32F4系列DMA1请求映像
STM32F4系列DMA2请求映像
每个通道都有一个数据选择器,可以选择硬件触发或软件触发。每个通道的硬件触发源都是不同的,应结合参考手册进行配置,软件触发则通道可以任意选择。
FIFO
必须关闭Direct Mode,才能使用FIFO。当下游模块无法及时处理上流模块输出的数据时,此时需要用FIFO暂存数据,防止数据丢失。每一个流有着一个独立的4字大小的FIFO,通过软件配置阈值。
二、实验案例
- 数据转运+DMA
将SRAM中数据DataA转运到另一个数组DataB中参数配置:起始地址即为数组地址,数据宽度保持一致,地址需要自增,传输计数器次数为7,不需要自动重装,采用软件触发。由于是软件触发,通道和流可以任意选择。自动重装和软件触发不能同时使用。
STM32F4系列DMA配置代码如下
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
DMA_InitTypeDef DMA_InitStructure;
//DMA_InitStructure成员非常多,根据手册进行配置
DMA_InitStructure.DMA_BufferSize = Size;//缓存区大小,即传输计数器指定传输计次
DMA_InitStructure.DMA_Channel = DMA_Channel_1;//DMA数据流通道选择,通道0和1具有高优先级
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToMemory;//传输方向,这里是SRAM中两个数组内容转换,M2M模式
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;//使用M2M模式时,FIFO硬件开启,软件控制不了
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;//FIFO阈值和存储器突发大小必须配合使用,是内存突发传输数据量的整数倍
DMA_InitStructure.DMA_Memory0BaseAddr = AddrB;//目的地地址
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//单次模式,突发规定的是指针每次增加的单位宽度是多少,将指针自增由指令控制封装为硬件突发模式进行
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//存储器数据宽度,这里选择字节的方式传输
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//使能自增
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//一次传输,软件触发下的M2M模式不能使用循环传输(自动重装)
DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;//数据源地址
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//单次模式
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设寄存器数据宽度,这里选择字节的方式传输
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;//使能自增
DMA_InitStructure.DMA_Priority = DMA_Priority_High;//选择高优先级
DMA_Init(DMA2_Stream1, &DMA_InitStructure);//由于是软件触发,低编号优先级高,这里给流1
DMA_Cmd(DMA2_Stream1, ENABLE);
}
主函数main代码如下
uint8_t DataA[] = {0x11, 0x21, 0x31, 0x41};
uint8_t DataB[] = {0, 0, 0, 0};
int main()
{
OLED_Init();
for(uint8_t i = 0; i < 4; i++) OLED_ShowHexNum(1, 1 + 3 * i, DataA[i], 2);
for(uint8_t i = 0; i < 4; i++) OLED_ShowHexNum(2, 1 + 3 * i, DataB[i], 2);
MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);
for(uint8_t i = 0; i < 4; i++) OLED_ShowHexNum(3, 1 + 3 * i, DataA[i], 2);
for(uint8_t i = 0; i < 4; i++) OLED_ShowHexNum(4, 1 + 3 * i, DataB[i], 2);
while(1)
{
}
}
- ADC扫描模式+DMA
ADC7个通道依次进行AD转换,转换结果存放在ADC_DR数据寄存器中,因此需要做的就是在每个单独的通道转换完成后进行一个DMA数据转运,并且目的地址进行自增。DMA通过ADC硬件触发,计数器是否自动重装取决于ADC的配置,DMA转运时机需要和ADC单个通道转换完成同步,ADC通道转换完会产生DMA请求,用以触发DMA转运。
这里是ADC外设寄存器到存储器的数据转运,因此可以使用DMA1,DMA代码配置如下
uint16_t AD_Value[2];//将外设数据转运到SRAM数组中
void ADC_MYInit()
{
//开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
//配置GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//ADC规则通道配置,多通道要设置次序
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_3Cycles);//规则通道0,次序1,采样时间为56个采样周期
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_3Cycles);//规则通道0,次序1,采样时间为56个采样周期
//通用ADC配置
ADC_CommonInitTypeDef ADC_CommonInitStructure;
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_1;//使用DMA模式1
ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;//独立ADC模式
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div6;//6分频,72MHz/6 = 12MHz
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;//采样延时
ADC_CommonInit(&ADC_CommonInitStructure);
//配置ADC1,使用扫描模式
ADC_InitTypeDef ADC_InitStructure;
ADC_StructInit(&ADC_InitStructure);
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//连续转换
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据右对齐
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;//禁止触发检测,使用软件触发
ADC_InitStructure.ADC_NbrOfConversion = 2;//所用2个通道数量,仅在扫描模式下使用
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;//采用12位分辨率
ADC_InitStructure.ADC_ScanConvMode = ENABLE;//扫描模式
ADC_Init(ADC1, &ADC_InitStructure);
//DMA配置
DMA_InitTypeDef DMA_InitStructure;
// DMA_StructInit(&DMA_InitStructure);
DMA_InitStructure.DMA_BufferSize = 2;//缓存区大小,即传输计数器指定传输计次
DMA_InitStructure.DMA_Channel = DMA_Channel_0;//ADC1外设只能选择通道0
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//外设寄存器到存储器
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)AD_Value;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//与DMA_PeripheralBurst相同
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//同样也是半个字
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器是连续的,需要自增
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//循环模式
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;//ADC1数据寄存器地址
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//突发规定的是指针单次增加的单位宽度,将指针自增由指令控制封装为硬件突发模式
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//DR低16位的数据,则外设数据宽度为半个字
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设寄存器不是连续的,不自增
DMA_InitStructure.DMA_Priority = DMA_Priority_High;//这里选择高优先级
DMA_Init(DMA2_Stream0, &DMA_InitStructure);//ADC1外设只能选择DMA2的Channel0和Stream0
DMA_Cmd(DMA2_Stream0, ENABLE);
ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE);
ADC_DMACmd(ADC1, ENABLE);//开启ADC到DMA的输出
//F4系列不需要校准,内部自动校准
ADC_Cmd(ADC1, ENABLE);//硬件触发DMA
ADC_SoftwareStartConv(ADC1);//软件触发
}