版权声明:本文为博主原创文章,未经博主容许不得转载。 https://blog.csdn.net/zouleideboke/article/details/75112224
ADC简介:git
ADC(Analog-to-Digital Converter,模/ 数转换器)。也就是将模拟信号转换为数字信号进行处理,在存储或传输时,模数转换器几乎必不可少。编程
STM32在片上集成的ADC外设很是强大,我使用的奋斗开发板是STM32F103VET6,属于加强型的CPU,它有18个通道,可测量16个外部和2个内部信号源。各通道的A/D转换能够单次,连续,扫描或间断模式执行,ADC的结果能够左对齐或右对齐方式存储在16位数据寄存器中。ide


ADC工做过程分析:函数
咱们以ADC规则通道转换过程来分析,如上图,全部的器件都是围绕中间的模拟至数字转换器部分展开的。它的左端VREF+,VREF- 等ADC参考电压,ADCx_IN0 ~ ADCx_IN15为ADC的输入信号通道,即某些GPIO引脚。输入信号通过这些通道被送到ADC器件,ADC器件须要收到触发信号才开始进行转换,如EXTI外部触发,定时器触发,也可使用软件触发。ADC部件接受到触发信号后,在ADCCLK时钟的驱动下对输入通道的信号进行采样,并进行模数转换,其中ADCCLK是来自ADC预分频器。优化
ADC部件转换后的数值被保存到一个16位的规则通道数据寄存器(或注入通道数据寄存器)中,咱们能够经过CPU指令或DMA把它读到内存(变量),模数转换以后,能够出发DMA请求或者触发ADC转换结束事件,若是配置了模拟看门狗,而且采集的电压大于阈值,会触发看门狗中断。ui
其实对于ADC采样,软件编程主要就是ADC的配置,固然我是基于DMA方式的,因此DMA的配置也是关键!话很少说看代码!es5
主函数:main.cspa
-
-
-
-
-
extern __IO uint16_t ADC_ConvertedValue;
-
float ADC_ConvertedValueLocal;
-
void Delay(__IO uint32_t nCount)
-
-
for(;nCount !=0;nCount--);
-
-
-
-
-
-
printf("******This is a ADC test******\n");
-
-
-
-
ADC_ConvertedValueLocal =(
float) ADC_ConvertedValue/4096*3.3;
-
printf("The current AD value =0x%04X\n",ADC_ConvertedValue);
-
printf("The current AD value =%f V\n",ADC_ConvertedValueLocal);
-
-
-
-
-
注意ADC_ConvertedValueLocal保存了由转换值计算出来的电压值,计算公式是:实际电压值=ADC转换值 x LSB ,这里因为个人板子VREF+接的参考电压为3.3V,因此LSB=3.3/4096,STM32的ADC的精度为12位。.net
ADC与DMA配置:adc.c3d
-
-
volatile uint16_t ADC_ConvertedValue;
-
-
-
GPIO_InitTypeDef GPIO_InitStructure;
-
ADC_InitTypeDef ADC_InitStructure;
-
DMA_InitTypeDef DMA_InitStructure;
-
-
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
-
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC|RCC_APB2Periph_ADC1,ENABLE);
-
-
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;
-
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;
-
GPIO_Init(GPIOC,&GPIO_InitStructure);
-
DMA_DeInit(DMA1_Channel1);
-
DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;
//ADC地址
-
DMA_InitStructure.DMA_MemoryBaseAddr = (
uint32_t)&ADC_ConvertedValue; //内存地址
-
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
//方向(从外设到内存)
-
DMA_InitStructure.DMA_BufferSize =
1; //传输内容的大小
-
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
//外设地址固定
-
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
//内存地址固定
-
DMA_InitStructure.DMA_PeripheralDataSize =
-
DMA_PeripheralDataSize_HalfWord ;
//外设数据单位
-
DMA_InitStructure.DMA_MemoryDataSize =
-
DMA_MemoryDataSize_HalfWord ;
//内存数据单位
-
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular ;
//DMA模式:循环传输
-
DMA_InitStructure.DMA_Priority = DMA_Priority_High ;
//优先级:高
-
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
//禁止内存到内存的传输
-
-
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
//配置DMA1的4通道
-
DMA_Cmd(DMA1_Channel1,ENABLE);
-
-
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
//独立ADC模式
-
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
//禁止扫描方式
-
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
//开启连续转换模式
-
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
//不使用外部触发转换
-
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
//采集数据右对齐
-
ADC_InitStructure.ADC_NbrOfChannel =
1; //要转换的通道数目
-
ADC_Init(ADC1, &ADC_InitStructure);
-
-
RCC_ADCCLKConfig(RCC_PCLK2_Div8);
//配置ADC时钟,为PCLK2的8分频,即9Hz
-
ADC_RegularChannelConfig(ADC1, ADC_Channel_11,
1, ADC_SampleTime_55Cycles5);//配置ADC1通道11为55.5个采样周期
-
-
-
-
-
ADC_ResetCalibration(ADC1);
//复位校准寄存器
-
while(ADC_GetResetCalibrationStatus(ADC1));//等待校准寄存器复位完成
-
-
ADC_StartCalibration(ADC1);
//ADC校准
-
while(ADC_GetCalibrationStatus(ADC1));//等待校准完成
-
-
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
//因为没有采用外部触发,因此使用软件触发ADC转换
-
ADC配置仍是比较简单的,毕竟只配置了单通道,仍是分析一下吧!这里我是把ADC1的通道11使用的GPIO引脚PC1配置成模拟输入模式,在做为ADC的输入时,必须使用模拟输入。对于ADC通道,每一个ADC通道对应一个GPIO引脚端口,GPIO的引脚在设为模拟输入模式后可用于模拟电压的输入。STM32F103VET6有三个ADC,这三个ADC公用16个外部通道。
DMA的总体配置为:使用DMA1的通道1,数据从ADC外设的数据寄存器(ADC1_DR_Address)转移到内存(ADC_ConvertedValue变量),内存外设地址都固定,每次传输的大小为半字(16位),使用DMA循环传输模式。
DMA传输的外设地址,也就是ADC1的地址为0x40012400+0x4c,这个地址可查STM32 datasheet得到,如图;


要特别注意ADC转换时间配置,因为ADC时钟频率越高,转换速度越快,那是否是就把ADC的时钟频率设的越大越好呢?其实否则,根据ADC时钟图可知,ADC时钟有上限值,即不能超过14MHz,如图:

这里ADC预分频器的输入为高速外设时钟(PCLK2),使用RCC_ADCCLKConfig()库函数来设置ADC预分频的分频值,PCLK2经常使用时钟为72MHz,而ADCCLK必须小于14MHz,因此这里ADCCLK为PCLK2的6分频,即12MHz,而个人程序中只是随便设为8分频,9MHz,若但愿ADC以最高频率14MHz运行,能够把PCLK2设置为56MHz,而后再4分频获得ACCLK。
ADC的转换时间不只与ADC的时钟有关,还与采样周期有关。每一个ADC通道能够设置为不一样的采样周期。STM32的ADC采样时间计算公式为:
T=采样周期+12.5个周期
公式中的采样周期就是函数中配置的 ADC_SampleTime,然后边加上的12.5个周期为固定值,则ADC1通道11的转换时间为T=(55.5+12.5) x 1/9=7.56us。
补充:在adc.c文件中定义了ADC_ConvertedValue变量,要注意这个变量是由关键字volatile修饰的,volatile的做用是让编译器不要去优化这个变量,这样每次用到这个变量时都要回到相应变量的内存中去取值,而若是不使用volatile进行修饰的话,ADC_ConvertedValue变量在被访问的时候可能会直接从CPU的寄存器中取出(由于以前该变量被访问过,也就是说以前就从内存中取出ADC_ConvertedValue的值保存到某个CPU寄存器中),之因此直接从寄存器中去取值而不去内存中取值,这是编译器优化代码的结果(访问CPU寄存器比访问内存快得多)。这里的CPU寄存器指R0,R1等CPU通用寄存器,用于CPU运算及暂存数据,不是指外设中的寄存器。
由于ADC_ConvertedValue这个变量值随时都会被DMA控制器改变的,因此用volatile来修饰它,确保每次读取到的都是实时ADC转换值。
adc.h:
-
-
-
-
#include "stm32f10x_dma.h"
-
#include "stm32f10x_adc.h"
-
#define ADC1_DR_Address ((uint32_t)0x4001244c);
-
-
-
-
效果图:


因为个人开发板没有滑动变阻器,因此我就将电压的输入端接入通用IO口的3V引脚。如图:
