一、DMA简介
DMA主要作用于外设和存储器,存储器和存储器之间的数据转运。不需要CPU干预,节省CPU资源。经常性跟ADC一起作用,因为ADC使用扫描模式的时候,需要不停将数据进行转运,不然就会被下一个数据覆盖。
DMA可以直接访问STM32内部的存储器,包括运行内存SRAM、程序存储器Falsh和寄存器等。不然它不能访问也无法完成数据转运的工作。
存储器到存储器之间的触发一般会选择软件触发,外设到存储器的触发会选择硬件触发模式。
DMA特性
DMA (Direct Memory Access)直接存储器存取
其实外设寄存器也是一种存储器,所以这个名字十分科学
STM32内存映像
ROM:掉电不丢失
RAM:掉电丢失
DMA框图
所有的工作就是从某个地方读取内容,放到别的寄存器里
如上图所示,虽然有两个DMA,但是DMA总线只有一条。所以哪一个DMA过就是这个仲裁器根据通道的优先级进行一个确定。
对Flash的工作(比如擦除和写)需要经过Flash接口控制器,一般情况下Flash是只读模式(就是你不能动他,DMA的数据转运到Falsh的地址就会出错)
DMA基本结构图
数据的移动方向,之前就说了可以是外设到存储器,以及存储器到存储器。
要进行数据转运,就要知道转运双方的参数。即双方的起始地址,数据宽度(一次转运按多大的宽度进行,有8位,16位,32位)和地址是否自增(就指针那种感觉,一般来说你的转运的终点肯定得自增,不然你转一次覆盖一次转的意义在哪里)。
M2M给1是软件触发,M2M给0是硬件触发
软件触发的目的是,最快把传输计数器清0,我理解为一种连续触发。
软件触发和循环模式不能同时使用,循环模式是清零后自动重装(那这不就死在这了)
硬件触发源可以选择ADC,串口,定时器等。比如ADC转换完成,串口收到数据,定时时间到,触发DMA进行转运。
DMA要开启有三个条件,首先开关控制必须打开,第二传输计数器不能是0,第三必须有触发源(硬件触发软件触发)。
触发一次,转运一次,传输器自减一次
当传输器为0且无自动重装,无论是否触发DMA都不会再进行转运
如果要再次开启DMA,就要先关闭开关控制,然后写入传输计时器,然后再打开开关控制。
这里比较重要的DMA进行转换(我自己认为很像复制)的参数设定是
外设的起始地址,数据宽度,地址是否自增
存储器的起始地址,数据宽度,地址是否自增
我的理解是,外设和存储器就是一个叫法,就是你存的那个数,和要复制到的位置。
双方数据宽度有可能不太相同,不相同是遵循少的前面补0,多的舍弃高位数据。
地址自增就要看你是把多个地址的数给多个地址,还是把一个地址里的数给多个,还是把多个地址里的给1个,当然最后一种情况就是给的最后一个数成功复制了。
DMA传输一个完成后会产生中断,然后就是表明我传完了,可以干接下来的别的事比如传新的。
外设的DMA请求映像
你要使用某个外设触发源的时候,就必须选择这个触发源对应的通道
如果你要软件触发就无所谓通道了
二、代码
#include "stm32f10x.h"
#include "ADC.h"
u16 AD_value[2];
void Adc_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
//1、开启时钟ADC1的时钟和GPIOA的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE );
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE );
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
//2、配置分频,因为最大不能超过14MHz 72/6=12
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
//3、配置GPIOC和ADC结构体 PC0 PC1 作为模拟通道输入引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_239Cycles5 );
ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 2, ADC_SampleTime_239Cycles5 );
//ADC_DeInit(ADC1); //复位ADC1
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC工作模式:ADC1和ADC2工作在独立模式
ADC_InitStructure.ADC_ScanConvMode = ENABLE; //模数转换工作在单通道模式
//ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //模数转换工作在单次转换模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //模数转换工作在连续转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换由软件而不是外部触发启动
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 2; //顺序进行规则转换的ADC通道的数目
ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器
//可以在这里进行模拟看门狗
//DMA
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; //外设地址
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //16位
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址不自增,一直转运同一个位置的数据
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_value;//转运到数组
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//16位
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器地址自增
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = 2; //两个ADC通道。传2次
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //DMA循环模式
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //1,硬件触发
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1,ENABLE);
ADC_DMACmd(ADC1, ENABLE); //开启DMA触发信号
//4、使能ADC1,(上电)
ADC_Cmd(ADC1, ENABLE);
//5、校准并等待校准结束
ADC_ResetCalibration(ADC1); //使能复位校准
while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束
ADC_StartCalibration(ADC1); //开启AD校准
while(ADC_GetCalibrationStatus(ADC1)); //等待校准结束
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
//防止头文件重复包含
#ifndef __ADC_H
#define __ADC_H
extern u16 AD_value[2];
void Adc_Init();
#endif