STM32的ADC实验的原理简介

STM32的ADC实验的原理简介

ADC简介

Analog-to-Digital Converter的缩写。指模/数转换器或者模拟/数字转换器。是指将连续变量的模拟信号转换为离散的数字信号的器件。

STM32的ADC的配置

 

战舰V3开发板的ADC通道与引脚的对应关系

 

模拟量输入的范围(ADC正常工作的范围)

STM32F103ZET6芯片中的电源部分(VREF与VSSA):

 

我们看到Vref-连接到了GND,而VREF+通过跳线帽连接到了VDD(VDD连接到了开发板的3.3V供电电源上),因此我们的DAC通道正常工作的模拟量输入范围为(2.4V,3.3V)。

ADC工作逻辑图解析(以ADC通道1为例)

 

 

ADC中断的三种类型

 

注入通道与规则通道的区别

STM32 将 ADC 的转换分为 2 个通道组:规则通道组和注入通道组。规则通道相当于你正常运行的程序,而注入通道呢,就相当于中断。在你程序正常执行的时候,中断是可以打断你的执行的。同这个类似,注入通道的转换可以打断规则通道的转换, 在注入通道被转换完成之后,规则通道才得以继续转换。

假如你在家里的院子内放了5个温度探头,室内放了3个温度探头;你需要时刻监视室外温度即可,但偶尔你想看看室内的温度;因此你可以使用规则通道组 循环扫描室外的5个探头并显示AD转换结果,当你想看室内温度时,通过一个按钮启动注入转换组(3个室内探头)并暂时显示室内温度,当你放开这个按钮后, 系统又会回到规则通道组继续检测室外温度。

从系统设计上,测量并显示室内温度的过程中断了测量并显示室外温度的过程,但程序设计上可以在初始化阶段分别设置好不同的转换组,系统运行中不必再 变更循环转换的配置,从而达到两个任务互不干扰和快速切换的结果。可以设想一下,如果没有规则组和注入组的划分,当你按下按钮后,需要从新配置DAC循环扫描的通道,然后在施放按钮后需再次配置DAC循环扫描的通道。

当你按下按钮后,势必DAC通道的扫描优先级发生改变,此时我们如果没有注入通道,那我们需要重新分配DAC通道的优先级,这样的话势必会出现较大的延迟这是我们不想看到的,我们想要的是:当我们按下按钮就好像进入中断一样,直接跳转至另一路DAC通道进行扫描,这样既不用调整DAC通道扫描优先级又可以将拥有不同作用的DAC通道清晰的区分开来。

注入通道的触发方式

触发注入

注意:在选择注入通道个数的时候一定要事先考虑外部触发事件可能持续的时间,触发事件的持续时间必须长于所有参与DAC转换的注入通道转换的时间,这样才可以保证所有注入通道都可以被使用。

自动注入

CONT位的功能:通过CR2中的CONT来决定是单次转换还是连续转换,如果我们不对CONT进行设置,那么会进行多次“单次DAC转换”,并不会进行“连续DAC转换”,我们知道单次DAC转换很费时间(主要花费在DAC设备的准备阶段),更何况是多次“单次的DAC”,这样不如“连续DAC转换”效率高。

注意:一定要禁止外部事件触发DAC注入通道转换,因为“自动”就说明DAC通道有特定的转换时序,这是系统默认的不可改变的!

ADC中断的三种类型

模拟看门狗复位中断

这里的模拟看门狗并不是我们前面章节提及过的窗口看门狗(WWDG),这里的模拟看门狗的工作也是复位MCU,但是这里触发复位的条件不同,不再是针对于程序跑飞而是DAC通道的模拟输入量。

如果被ADC转换的模拟电压低于低阀值或高于高阀值,AWD模拟看门狗状态位被设置。阀值位 于ADC_HTR和ADC_LTR寄存器的最低12个有效位中。通过设置ADC_CR1寄存器的AWDIE位 以允许产生相应中断。(注意这里的HTR,LTR阈值在寄存器中不会采用我们要求的对齐方式进行对齐)

通过配置ADC_CR1寄存器,模拟看门狗可以作用于1个或多个通道:

其中模拟看门狗作用的通道选择如下:

注入通道转换结束中断&规则通道转换结束中断

这两种中断从名字就可以得知,当注入通道结束触发“注入通道结束中断”,当规则通道转换结束触发“规则通道转换结束中断”。

这里,我们会有疑问:如果ADC采用多次“单词ADC转换”和单次“连续的ADC转换”,触发中断机制会有所不同吗?

答案是:不会的。我们看如下图所示:

 

无论是“连续ADC转换”还是“单次ADC转换”,没转换完一次必定置位一次中断标志位。

ADC通道的四种不同的工作模式

单次ADC模式

单次转换模式下,ADC只执行一次转换。该模式既可通过设置ADC_CR2寄存器的ADON位(只适用于规则通道)启动也可通过外部触发启动(适用于规则通道或注入通道),这时CONT位为0。

说白了就是DAC操作的两种触发模式:事件/外部操作。

连续DAC模式

在连续转换模式中,当前面ADC转换一结束马上就启动另一次转换。此模式可通过外部触发启动或通过设置ADC_CR2寄存器上的ADON位启动,此时CONT位是1。

扫描ADC模式

此模式用来扫描一组模拟通道,不同于连续ADC模式只连续扫描特定的ADC通道。

扫描模式可通过设置ADC_CR1寄存器的SCAN位来选择。一旦这个位被设置,ADC扫描所有被ADC_SQRX寄存器(对规则通道)或ADC_JSQR(对注入通道)选中的所有通道。在每个组的每个通道上执行单次转换。在每个转换结束时,同一组的下一个通道被自动转换。

如果设置了CONT位,转换不会在选择组的最后一个通道上停止,而是再次从选择组的第一个通道继续转换。

如果设置了DMA位,在每次EOC后,DMA控制器把规则组通道的转换数据传输到SRAM中。而注入通道转换的数据总是存储在ADC_JDRx寄存器中。

总之,只有扫描完该扫描的所有DAC通道采取触发中断和事件,单次完成DAC操作不会触发任何操作。

间断ADC执行模式

这里一定要注意:

① 千万不可以将“自动注入模式”与“间断ADC转换模式”一起配置,这是因为“自动注入”模式只需要一次触发事件就可以自动依次执行完“规则通道->注入通道”,而间断模式下需要不断的触发事件才可以保证DAC操作执行下去。

② 子组的数量一定要小于等于8。

间断ADC执行模式较ADC扫描模式的不同点

STM32 的ADC拥有连续扫描模式,也有间断模式,间断模式较扫描模式需要更多的触发事件才能完成所有的通道转换操作,在实际工程应用中,可以利用间断模式实现一些特殊应用。

ADC中常见的问题说明

为什么单次ADC扫描模式花费时间比较多?

如下图所示,ADC在开始精确转换前需要一个稳定时间tSTAB。在开始ADC转换和14个时钟周期后,EOC标志被设置,16位ADC数据寄存器包含转换的结果。

 

我们这里采用的是14MHz的工作频率,DAC转换时间为1.5个时钟周期,除了DAC转换外,MCU还需要进行数据的处理等其他操作,因此从第一次成功的DAC转换到下一次开始转换DAC的时间间隔为1.5+12.5个时钟周期,如果按照14MHz的工作频率来求解的话,DAC转换周期为1us(我这里只求解了DAC转换的周期,没有算DAC转换前的准备工作,因此如果单次DAC转换时间消耗>1us)。

配置一切正常,但是ADC转换不准确

DAC的时钟频率不可以超过14MHz,也就是说AHB时钟72MHz的分频系数必须大于等于6才可以使得DAC正常的运行。其实,DAC转换频率也是DAC设备性能的反应,当频率太高,功耗增加,发热增加,DAC设备的稳定性变差,容易造成死机。

STM32的ADC最大转换率为1HZ,也就是转换时间为 1us(在 ADCCLK=14M,采样周期为 1.5 个 ADC 时钟下得到),不要让 ADC 的时钟超过 14M,否则将导致结果准确度下降。否则将导致结果准确度下降。

注入通道的选择与顺序

我们看说明可以得知:ADC_JSQR[21:0] = 10 00011 00011 00111 00010意味着“注入通道长度为3,4th通道(第一个DAC转换通道),3th通道(第二个DAC转换通道),2th通道(第三个DAC转换通达)”。

如果外部电压波动较大,我们应该如何操作?

注:这个校准是自动校准不需要人为操作。

什么是复位缺省值?

简单的说就是复位之后的值,就是也就是默认值!

其实下列函数都是复位缺省值函数(De的含义就是default默认的意思),作用都是恢复对应设备的初始值(默认值):

void GPIO_DeInit(GPIO_TypeDef* GPIOx)

将GPIO端口复位

void GPIO_AFIODeInit(void)

将复用功能(重映射事件控制和 EXTI 设置)重设为默认值

void ADC_DeInit(ADC_TypeDef* ADCx)

将外设 ADCx 的全部寄存器重设为缺省值

定时器触发,软件触发,外部触发何时用?

如果想周期性的采集信号,那就用定时器触发;如果想不定时任意时刻的采集信号,那就用软件触发;如果想让外部信号在特定情况下被采集,那就用外部触发。

我们看引脚图的时候有些懵,例如:什么是ADC123_IN1?

在STM32中ADC有些通道是重合的,也就是说ADC1和ADC2的某些通道是重合的,应用时要注意。

比如:图中的PB1含义就是ADC1和ADC2的通道9。又比如PA0分别为ADC1、ADC2和ADC3的通道0。

ADC的参考电压是什么?

我们看到:VREF-与VREF+分别连接GND与VDDA,代表着ADC的输入范围为[0V,3.3V],也就是说ADC的参考电压已经默认设置为STM32开发板的电源电压(3.3V)。

ADC转换的外部触发事件("ADC1&ADC2"与"ADC3"略有不同)

转换可以由外部事件触发(例如定时器捕获,EXTI线)。如果设置了EXTTRIG控制位,则外部事件就能够触发转换。EXTSEL[2:0]和JEXTSEL2:0]控制位允许应用程序选择8个可能的事件中的某一个,可以触发规则和注入组的采样。

注意: 当外部触发信号被选为ADC规则或注入转换时,只有它的上升沿可以启动转换。

注释2的意思是:JEXTSEL=110时对应着两个外部中断事件,因此我们用另一个位(DAC1对应ADC1_ETRGINJ和DAC2对应ADC2_ETRGINJ_REMAP)来确定“那个时间触发DAC转换”。

ADC相应寄存器说明

ADC控制寄存器(ADC_CR1和ADC_CR2)

ADC_CR1的SCAN 位,该位用于设置扫描模式,由软件设置和清除,如果设置为1 ,则使用扫描模式,如果为 0,则关闭扫描模式。在扫描模式下,由 ADC_SQRx或ADC_JSQRx寄存器选中的通道被转换。如果设置了 EOCIE 或JEOCIE,只在最后一个通道转换完毕后才会产生EOC 或JEOC 中断。ADC_CR1[19:16]用于设置 ADC的操作模式

ADC_CR2的ADCON 位用于开关AD转换器。而CONT 位用于设置是否进行连续转换,我们使用单次转换,所以CONT 位必须为0。CAL 和RSTCAL 用于AD校准。ALIGN用于设置数据对齐,我们使用右对齐,该位设置为0 。

EXTSEL[2:0]用于选择启动规则转换组转换的外部事件,详细的设置关系如下:

这里使用的是软件触发(SWSTART ),所以设置这3 个位为111 。ADC_CR2 的SWSTART 位用于开始规则通道的转换,我们每次转换(单次转换模式下)都需要向该位写 1 。AWDEN 为用于使能温度传感器和Vrefint 。

ADC采样事件寄存器(ADC_SMPR1 和ADC_SMPR2 )

这两个寄存器用于设置通道0~17的采样时间,每个通道占用 3 个位。

ADC_SMPR2 的各位描述如下:

对于每个要转换的通道,采样时间建议尽量长一点,以获得较高的准确度,但是这样会降低ADC的转换速率。ADC的转换时间可以由下式计算:

Tcovn= 采样时间+12.5 个周期

其中:Tcovn 为总转换时间,采样时间是根据每个通道的SMP位的设置来决定的。例如,当ADCCLK=14Mhz 的时候,并设置 1.5个周期的采样时间,则得到:Tcovn=1.5+12.5=14 个周期=1us 。

ADC规则序列寄存器(ADC_SQR1~3)

L[3:0] 用于存储规则序列的长度,我们这里只用了 1 个,所以设置这几个位的值为 0 。其他的SQ13~16 则存储了规则序列中第13~16 个通道的编号(0~17)。另外两个规则序列寄存器同ADC_SQR1大同小异,我们这里就不再介绍了,要说明一点的是:我们选择的是单次转换,所以只有一个通道在规则序列里面,这个序列就是SQ0 ,通过ADC_SQR3的最低5 位设置。

ADC规则数据寄存器(ADC_DR)

这里要提醒一点的是,该寄存器的数据可以通过ADC_CR2 的ALIGN位设置左对齐还是右对齐。在读取数据的时候要注意。

ADC状态寄存器(ADC_SR )

这里我们要用到的是EOC 位,我们通过判断该位来决定是否此次规则通道的AD转换已经完成,如果完成我们就从ADC_DR 中读取转换结果,否则等待转换完成。

ADC注入通道数据偏移寄存器x (ADC_JOFRx)(x=1..4)

当转换注入通道时,这些位定义了用于从原始转换数据中减去的数值。转换的结果可以在

ADC_JDRx寄存器中读出。

ADC采样时间寄存器 2(ADC_SMPR2)和ADC采样时间寄存器 1(ADC_SMPR1)

用于独立地选择每个通道的采样时间。在采样周期中通道选择位必须保持不变。

ADC转换的逻辑关系图

ADC固件库函数功能和用法简介

函数名称

函数功能

void ADC_DeInit(ADC_TypeDef* ADCx)

恢复ADCx的全部寄存器为默认值

void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct)

自定义初始化ADCx

void ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct)

将指定结构体初始化默认值

void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState)

ADCx设备使能

void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState)

在打开ADCx总中断通道后,配置具体的ADCx中断通道

void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState)

使能或者失能指定的 ADC 的软件转换启动功能

void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime)

设置指定 ADC 的规则组通道,设置它们的转化顺序和采样时间

uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx)

返回最近一次 ADCx 规则组的转换结果

void ADC_ResetCalibration(ADC_TypeDef* ADCx)

将ADCx的校准寄存器复位为默认值

FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx)

是否初始化校准寄存器(针对于CR2寄存器的RSTCAL位)

void ADC_StartCalibration(ADC_TypeDef* ADCx);

开始校准

FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx)

是否校准完成(针对于CR2寄存器的CAL位)

ADC操作代码解析(配置ADC1_IN0)

APB2总线上的外设时钟使能

RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1|RCC_APB2Periph_GPIOA, ENABLE); // 使能APB2总线上的GPIOA与ADC1时钟 

GPIO引脚配置

GPIO_InitTypeDef GPIO_InitStructure;  
  
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;  
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;  
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  
GPIO_Init(GPIOA, &GPIO_InitStructure); // 配置PA0为模拟通道输入模式   

   这里参考如下资料:

ADC/DAC对应的GPIO引脚属性配置:

ADC1_CH0对应的引脚位置:

复位ADC1外设,然后设置ADC时钟分频因子

ADC_DeInit(ADC1); // 复位ADC1,将ADC1用默认属性进行初始化  
  
RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 设置ADCx的分频因子为6  

初始化ADC1外围设备

ADC_InitTypeDef ADC_InitStructrue;    
  
DC_InitStructrue.ADC_ContinuousConvMode = DISABLE; // 失能连续转换模式,即使能单次转换模式  
ADC_InitStructrue.ADC_DataAlign = ADC_DataAlign_Left; // 左对齐  
ADC_InitStructrue.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 无外部事件触发  
ADC_InitStructrue.ADC_Mode = ADC_Mode_Independent; // 独立模式  
ADC_InitStructrue.ADC_NbrOfChannel = 1; // ADC组长度为1==单次ADC转换  
ADC_InitStructrue.ADC_ScanConvMode = DISABLE; // 扫描模式失能  
ADC_Init(ADC1, &ADC_InitStructrue); // 初始化ADC1  

ADC1外围设备使能

ADC_Cmd(ADC1,ENABLE); // 使能ADC1  

ADC1校准功能配置

ADC_ResetCalibration(ADC1); // ADC1校准复位  
  
while(ADC_GetResetCalibrationStatus(ADC1)); // 等待ADC1复位完成  
  
ADC_StartCalibration(ADC1); // ADC1校准开始  
  
while(ADC_GetCalibrationStatus(ADC1)); // 等待ADC1校准初始化完成  

注:先将ADC1校准功能恢复到默认值,然后再初始化ADC1。

所用到的寄存器为ADC控制寄存器 2(ADC_CR2)的如下两个位:

配置读取ADC1单次转换的值的函数

// 开始一次ADC1转换  
u16 GetADC1Value()  
{  
    ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_1Cycles5); // 设置ADC1通道的转换优先级和转换周期  
      
    ADC_SoftwareStartConvCmd(ADC1,ENABLE); // 外部事件触发一次规则转换通道  
      
    while(!(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==SET)); // 等待ADC1转换完成  
      
    return ADC_GetConversionValue(ADC1);  
}  

对于单次ADC转换,我们通常将对于单个ADC转换通道的配置放于专属函数内,这样使得程序更加有条理。

注意:对于这种要有一定时间执行的外设,一定要用位标志读取函数去查看外设的任务是否已经执行完毕,例如:ADC转换需要一定时间,我们一定要确保ADC转换结束后,我们才可以进行后续操作!

配置多次“单次ADC转换”结果的取平均的函数

// 开始5次ADC1转换并返回平均值  
u16 GetADC1AverageValue()  
{  
    u8 i = 5;  
    u16 temp = 0;  
      
    while(i<5)  
    {  
        temp += GetADC1Value();  
        delay_ms(5);  
        i++;  
    }  
    return temp/5;  
}  

注:这里是循环进行5次“ADC1_CH0的单次ADC转换”,并且对于这5次的结果取平均。

ADC_CH1单次ADC转换配置示例

Adc.h

#ifndef _ADC_H  
#define _ADC_H  
  
#include "sys.h"  
  
void ADC_InitConfig();  
u16 GetADC1Value();  
u16 GetADC1AverageValue();  
  
#endif  

Adc.c

#include "adc.h"  
#include "delay.h"  
#include "stm32f10x.h"  
  
void ADC_InitConfig()  
{  
    GPIO_InitTypeDef GPIO_InitStructure;  
    ADC_InitTypeDef ADC_InitStructrue;  
      
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1|RCC_APB2Periph_GPIOA, ENABLE); // 使能APB2总线上的GPIOA与ADC1时钟  
      
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;  
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;  
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  
    GPIO_Init(GPIOA, &GPIO_InitStructure); // 配置PA0为模拟通道输入模式  
      
    ADC_DeInit(ADC1); // 复位ADC1,将ADC1用默认属性进行初始化  
      
    RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 设置ADCx的分频因子为6  
      
    ADC_InitStructrue.ADC_ContinuousConvMode = DISABLE; // 失能连续转换模式,即使能单次转换模式  
    ADC_InitStructrue.ADC_DataAlign = ADC_DataAlign_Left; // 左对齐  
    ADC_InitStructrue.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 无外部事件触发  
    ADC_InitStructrue.ADC_Mode = ADC_Mode_Independent; // 独立模式  
    ADC_InitStructrue.ADC_NbrOfChannel = 1; // ADC组长度为1==单次ADC转换  
    ADC_InitStructrue.ADC_ScanConvMode = DISABLE; // 扫描模式失能  
    ADC_Init(ADC1, &ADC_InitStructrue); // 初始化ADC1  
      
    ADC_Cmd(ADC1,ENABLE); // 使能ADC1  
      
    ADC_ResetCalibration(ADC1); // ADC1校准复位  
      
    while(ADC_GetResetCalibrationStatus(ADC1)); // 等待ADC1复位完成  
      
    ADC_StartCalibration(ADC1); // ADC1校准开始  
      
    while(ADC_GetCalibrationStatus(ADC1)); // 等待ADC1校准初始化完成  
      
    delay_init();  
}  
// 开始一次ADC1转换  
u16 GetADC1Value()  
{  
    ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_1Cycles5); // 设置ADC1通道的转换优先级和转换周期  
      
    ADC_SoftwareStartConvCmd(ADC1,ENABLE); // 外部事件触发一次规则转换通道  
      
    while(!(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==SET)); // 等待ADC1转换完成  
      
    return ADC_GetConversionValue(ADC1);  
}  
// 开始5次ADC1转换并返回平均值  
u16 GetADC1AverageValue()  
{  
    u8 i = 5;  
    u16 temp = 0;  
      
    while(i<5)  
    {  
        temp += GetADC1Value();  
        delay_ms(5);  
        i++;  
    }  
    return temp/5;  
} 

 Main.c

#include "adc.h"  
#include "usart.h"  
#include "stm32f10x.h"  
  
int main()  
{  
    ADC_InitConfig();  
    uart_init(115200);  
      
    while(1)  
    {  
        printf("Data:%d\n",GetADC1AverageValue());  // 串口1打印信息
    }  
}  

 

猜你喜欢

转载自blog.csdn.net/weixin_45590473/article/details/108668718