学习某一个东西,我们首先要了解这个东西的定义是什么,用来干什么的,怎么用,用的过程中有什么注意事项,这些都OK了,那么我们就算是基本掌握他了。
0 前言
ADC,Analog-to-Digital Converter的缩写,指模/数转换器或者模拟/数字转换器。
模拟信号转换为数字信号,一般分为4个步骤进行,即采样、保持、量化和编码。前2个步骤在采样——保持电路中完成,后两步骤则在ADC中完成。ADC是把经过与标准量比较处理后的模拟量转换成以二进制数值表示的离散信号的转换器。
DMA,全称Direct Memory Access,即直接存储器访问。
DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。当CPU初始化这个传输动作,传输动作本身是由DMA控制器来实现和完成的。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场过程,通过硬件为RAM和IO设备开辟一条直接传输数据的通道,使得CPU的效率大大提高。
这里为什么将ADC和DMA放在一起来讲呢,因为通常使用到ADC就会使用到DMA来将ADC转换的值直接通过DMA传输到缓存中去,减少了CPU的参与,提高程序运行的效率。
1.ADC和DMA
1.1概述
1.1.1 ADC概述
在STM32F10X中对ADC的描述
12位ADC是一种逐次逼近型模拟数字转换器。它有多达18个通道,可测量16个外部和2个内部信号源。各通道的A/D转换可以单次、连续、扫描或间断模式执行。ADC的结果可以左对齐或右对齐方式存储在16位数据寄存器中。
ADC的功能框架图如下:
从框架图可以看出,ADC不仅支持GPIO口输入和外部中断触发转换还支持定时器触发转换,在转换结束,注入转换结束,发生模拟看门狗事件的时候会产生中断或者DMA请求。除此之外ADC不仅可以使用单通道采集还可以使用双重通道采集来提高精度,采样的间隔也可以通过通道编程,ADC转换时间:最大转换速率 1us(最大转换速度为1MHz,在ADCCLK=14M,采样周期为1.5个ADC时钟下得到)
1.1.2 DMA概述
在STM32F10X中对DMA的描述
直接存储器存取(DMA)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作。两个DMA控制器有12个通道(DMA1有7个通道,DMA2有5个通道),每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个DMA请求的优先权。
DMA的原理:
DMA 与cortex m3 共享数据总线,通常用到外设与存储器,当CPU和DMA同时访问相同目标时,(RAM或外设),MDA会请求暂停CPU访问系统总线达若干个周期,总线仲裁器执行循环调度,以保证CPU至少可以得到一半的系统总线(存储器或外设)带宽,所以不用担心DMA霸占资源。
DMA的特点:
将CPU从数据传输中解放出来,实现数据传输的独立性
直接通过DMA实现外部内存与主存之间的数据传输
DMA的数据传输:
DMA支持数据从外设到存储器传输,存储器到外设传输,存储器到存储器传输,支持循环传输和单次传输
1)从外设到存储器
以ADC采集为例,DMA外部寄存器地址对应ADC数据寄存器地址,DMA存储器地址是我们自定义的变量的地址。方向设置为源地址。
2)存储器到外设
存储器到外设传输以串口向电脑端发送为例,DMA 外设寄存器的地址对应的就是串口数据寄存器的地址,DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储通过串口发送到电脑的数据)的地址。方向我们设置外设为目标地址。
3)存储器到存储器
当我们使用从存储器到存储器传输时,以内部 FLASH 向内部 SRAM 复制数据为例。
DMA外设寄存器的地址对应的就是内部 FLASH(我们这里把内部 FALSH 当作一个外设来看)的地址,DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储来自内部 FLASH 的数据)的地址。方向我们设置外设(即内部 FLASH)为源地址。跟上面两个不一样的是,这里需要把
4)传输模式
传输完成还分两种模式,是一次传输还是循环传输,一次传输很好理解,即是传输一次之后就停止,要想再传输的话,必须关断 DMA 使能后再重新配置后才能继续传输。循环传输则是一次传输完成之后又恢复第一次传输时的配置循环传输,不断的重复。具体的由 DMA_CCR寄存器的 CIRC 循环模式位控制。
DMA的功能框图
从上图可以看出DMA的数据传输流程:
1.外设对DMA控制器发出请求。
2.DMA控制器收到请求,触发DMA工作。
3.DMA控制器从外设获取数据存储到DMA通道中
4.DMA控制器的DMA总线与总线矩阵协调,使用数据总线把外设的数据经由DMA通道存放到SRAM中,这个数据的传输过程中,完全不需要内核的参与,也就是不需要CPU的参与
2.使用示例
2.1ADC和DMA配置
使用固件库函数配置ADC两个通道采集并用DMA传输数据
.c文件内容
#include "ADC_DMA.h"
#define ADC1_DR_Address ((uint32_t)0x4001244C)
#define M 2
volatile uint16_t adc_value[2]; //DMA缓存
/**
* @brief 配置ADC的GPIO口
* @param None
* @retval None
*/
static void adc1_gpio_config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
/**
* @brief 配置ADC和DMA的相关寄存器
* @param None
* @retval None
*/
static void adc1_dma_config(void)
{
ADC_InitTypeDef ADC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
/* DMA1 channel1 configuration ----------------------------------------------*/
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(ADC1->DR);
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&adc_value[0];
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //外设到SRAM
DMA_InitStructure.DMA_BufferSize = M;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_disable; //外设地址不递增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//半字
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 循环传送
DMA_InitStructure.DMA_Priority = DMA_Priority_High; //高优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
/* Enable DMA1 channel1 */
DMA_Cmd(DMA1_Channel1, ENABLE);
/* ADC1 configuration ------------------------------------------------------*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
ADC_DeInit(ADC1);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//正常模式
ADC_InitStructure.ADC_ScanConvMode = ENABLE; //工作在多通道
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 连续转换使能
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //软件控制
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //右对齐
ADC_InitStructure.ADC_NbrOfChannel = 2;//两个通道
ADC_Init(ADC1, &ADC_InitStructure);
ADC_RegularChannelConfig(ADC1, ADC_Channel_1,1, ADC_SampleTime_239Cycles5); //温度传感器1
ADC_RegularChannelConfig(ADC1, ADC_Channel_2,2, ADC_SampleTime_239Cycles5); //温度传感器2
/* Enable ADC1 DMA */
ADC_DMACmd(ADC1, ENABLE);//允许DMA请求
/* Enable ADC1 */
ADC_Cmd(ADC1, ENABLE);
/* Enable ADC1 reset calibration register */
ADC_ResetCalibration(ADC1);
/* Check the end of ADC1 reset calibration register */
while(ADC_GetResetCalibrationStatus(ADC1));
/* Start ADC1 calibration */
ADC_StartCalibration(ADC1);
/* Check the end of ADC1 calibration */
while(ADC_GetCalibrationStatus(ADC1));
/* Start ADC1 Software Conversion */
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //启动ADC
}
/**
* @brief 初始化ADC和DMA
* @param None
* @retval None
*/
void adc1_init(void)
{
// rcc_config();
adc1_gpio_config();
adc1_dma_config();
}
/**
* @brief 从DMA缓存中读取ADC采集过来的数据
* @param times :次数 ADCchannel_numb :ADC通道
* @retval None
*/
uint32_t Get_Adc_Average(uint8_t times,uint8_t ADCchannel_numb) //平均滤波
{
u32 temp_val=0;
uint8_t t;
if(ADCchannel_numb==0) //第一个温度传感器的
{
for(t=0;t<times;t++)
{
temp_val += adc_value[0];
}
return temp_val/times;
}
else //第二个温度传感器
{
for(t=0;t<times;t++)
{
temp_val += adc_value[1];
}
return temp_val/times;
}
}
.h文件内容
#ifndef __ADC_DMA_H
#define __ADC_DMA_H
extern volatile uint16_t adc_value[2];
void adc1_init(void);
uint32_t Get_Adc_Average(uint8_t times,uint8_t ADCchannel_numb) //平均滤波
#endif
3.问题总结
1.ADC的分辨率,可选的分辨率有12位、10位、8位和6位。分辨率越高,AD转换数据精度越高,转换时间也越长;分辨率越低,AD转换数据精度越低,转换时间也越短。默认是12位的分辨率,数据左对齐需要除以16。
2.ADC时钟是有PCLK2分频而来,分频系数决定ADC时钟频率,可选的分频系数为2、4、6和8。ADC最大时钟配置为36MHz。
3.ADC的采样周期跟与采样的目标相关,采集有频率的目标需要目标频率设置采样周期,采样周期越长,精度越高,但是时间越长。
4.使用ADC_RegularChannelConfig函数可以配置通道采集的先后顺序,配合DMA的缓存数组可以确定哪一个通道存放在哪一个内存。
5.DMA_BufferSize 表示的是有几个缓存,如果DMA_PeripheralDataSize 和DMA_MemoryDataSize都设置的是半字,那1个DMA_BufferSize 就表示1个半字缓存,注意 DMA_PeripheralDataSize 和DMA_MemoryDataSize的配置必须一致。
6.扫描模式用于多通道采集时,一轮采集为根据通道设定的顺序依次采集每个开启的通道。但要注意的是每个通道采集到的值都是存放在寄存器ADCx-> DR中,而只有当一轮采集完即所有设定的通道都采集完后采集完成标志才会被置位,所以不使用DMA传输方式的时候会出现采集到的数据被覆盖的现象。
aSize 和DMA_MemoryDataSize都设置的是半字,那1个DMA_BufferSize 就表示1个半字缓存,注意 DMA_PeripheralDataSize 和DMA_MemoryDataSize的配置必须一致。
6.扫描模式用于多通道采集时,一轮采集为根据通道设定的顺序依次采集每个开启的通道。但要注意的是每个通道采集到的值都是存放在寄存器ADCx-> DR中,而只有当一轮采集完即所有设定的通道都采集完后采集完成标志才会被置位,所以不使用DMA传输方式的时候会出现采集到的数据被覆盖的现象。
7.规则通道组中可以安排最多16个通道,而注入通道组可以安排最多4个通道。规则组是程序按照配置规则转换,注入组是根据外部触发中断来转换,在执行规则通道组扫描转换时,如有例外处理则可启用注入通道组的转换。