STM32-ADC:独立模式和双重模式详解

ADC简介

18个通道:外部信号源就是16个GPIO回。在引脚上直接接模拟信号就行了,不需要侄何额外的电路。引脚就直接能测电压。2个内部信号源是内部温度传感器和内部参考电压。

逐次逼近型ADC:

它是一个独立的8位逐次逼近型ADC芯片,这个ADC0809是一款经典的ADC芯片。现在单片机的性能和集成都有很大的提升,很多单片机内部就有ADC芯片,这样就不用外挂芯片了,引脚可以直接测电压,使用还是非常方便的。

首先左边这里的IN0~IN7,是8路输入通道,通过通道选择开关,选中这一路,输入到这个点进行转换 。下面是地址锁存和译码,就是你想选中那个通道,就把通道号放在这三个脚上 ,然后给一个锁存信号,上面这里对应的通路开关就可以自动拨好了,这部分就相当于一个可必通过模拟信号的数据选择器。因为AD转换是一个很快的过程,你给个开始信号,过几个us就转换完成了,所以说如果你想转换多路信号,只需要一个AD转换器。然后加一个多路选择开,想转换哪路,就先拨一下,选中对应通道,然后再开始转换就行了。

STM32内部的ADC是有18个输入通道的,所以对应这里就是有18路输入的多路开关。

1个外部通道输入的未知编码的电压和一个DAC输出的已知编码的电压。它俩同时输入到电压比较器,进行大小判断。如果DAC输出的电压比较大,我就调小DAC数据,如果DAC输出的电压比较小,我就增大DAC数据,直至DAC输出的电压和外部通道输入的电压相等,这样DAC输入的数据就是外部电压的编码数据了。

ADC框图

左边是ADC的输入通道包括16个GPIO口,IN0~N15;和两个内部的通道,一个是内部温度传感器,另一个是参考电压。总共18个输入通道,然后到达这里,这是一个模拟多路开关,可以指定我们想要选择的通道。右边是多路开关的输出,进入模数转换器,转换结果会直接放在这个数据寄存器,我们读取寄存器就能知道ADC转换的结果了。

注入通道和规则通道

对于普通的ADC,多路开关一般都是只选中某一个通道、开始转换、等待转换完成、取出结果。

在这里它可以同时选中多个,在转换的时候,还分成了两个组,规则通道组和注入通道组。规则组:一次可以最多选中16个通道;注入组:最多可以选中4个通道。规则组虽然可以选中16个通道,但是数据寄存器只能存取一个结果,如果不想之前的结果被覆盖,那在转换完成之后,就要把结果拿走。注入组最多可以选4个通道,但他的数据寄存器有4个,所以他就不用担心数据被覆盖的问题了。

中断触发ADC转换

STM32的ADC,触发ADC开始转换的信号有两种:1、软件触发:就是在程序中手动调用一条代码,就可以启动转换了。2、硬件触发:左下角选中的触发源。这些触发源主要是定时器,有定时器的各个通道。

上面两个是ADC的参考电压,决定了ADC输入电压的范围;下面两个是ADC的供电引脚。一般情况VREF+要接VDDA,VREF-要接VSSA。

ADC的时钟:ADCCLK

ADCCLK来自预分频器,ADC预分频器是来源与RCC的。

ADCCLK最大14MHz,ADC预分频可以选择2、4、6、8分频,选择2分频,36MHz,超出ADCCLK的范围了,所以只能选择6分频或者8分频。

ADC基本结构图

左边是输入通道,16个GPIO口,加两个内部的通道,然后进入AD转换器。

AD转换器里有两个组:

1、规则组:一次可以最多选中16个通道,虽然可以选中16个通道,但是数据寄存器只能存取一个结果,如果不想之前的结果被覆盖,那在转换完成之后,就要把结果拿走。

2、注入组:最多可以选中4个通道,但他的数据寄存器有4个,所以他就不用担心数据被覆盖的问题了。

触发控制:软件触发、硬件触发

RCC的时钟CLOCK:ADC逐次比较的过程就是有这个这个时钟推动的。

输入通道

转换模式

1、单次转换,非扫描模式

选择指定的转换通道,然后我们就可以触发转换,ADC对选定的通道进行模数转换,转换完成之后,转换结果放在数据寄存器里,同时EOC标志位置1,整个转换过程就结束了。

─ 转换数据被储存在16位ADC_DR寄存器中

─ EOC(转换结束)标志被设置

─ 如果设置了EOCIE,则产生中断。

2、连续转换,非扫描模式

首先,他还是非扫描模式,所以菜单列表就只用第一个,然后他与单次转换的不同的是,它在第一次转换之后不会停止,而是立刻开始下一轮的转换,然后一直持续。

3、单次转换,扫描模式

这个模式也是单次转换,所以每触发一次,转换结束后,就会停下来,下次转换就得再触发才能开始。然后它是扫描模式,这就会用到这个菜单列表了,这里每个位置是通道几可以任意指定,并且也是可以重复的。

16个通道位置用不完的情况,只用前几个,那就需要再给一个通道数目的参数(几个通道)。比如说这里指定的通道数目为7,那就只看前7个位置,然后每次触发之后,他就一次对这前7个位置进行AD转换,转换结果都放在数据寄存器里。为了防止数据被覆盖,就需要用DMA及时将数据挪走。7个通道转化完成之后,产生EOC信号,转换结束。

4、连续转换,扫描模式

就是在单次转换,扫描模式的基础上,一次转换完之后,立刻开始下一轮的转换。

数据对齐

STM32F103C8T6这个ADC是12位的,它的转换结果就是一个12位的数据,但是数据寄存器是16位的,所以就存在一个数据对齐的问题。

数据右对齐:12位的数据向右靠,高位多出来的几位就补0

数据左对齐:低位多出来的几位补0

我们一般使用的是右对齐,这样读取的16位寄存器,直接就是转换结果。如果是左对齐的话,结果比实际结果大,左移4位,相当于把结果乘16了。

转换时间

校准

示例工程

1、AD单通道实现

#include "stm32f10x.h"                  // Device header

/**
  * 函    数:AD初始化
  * 参    数:无
  * 返 回 值:无
  */
void AD_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);	//开启ADC1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
	
	/*设置ADC时钟*/
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);						//选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
	
	/*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_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);		//规则组序列1的位置,配置为通道0
	
	/*ADC初始化*/
	ADC_InitTypeDef ADC_InitStructure;						//定义结构体变量
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;		//模式,选择独立模式,即单独使用ADC1
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	//数据对齐,选择右对齐
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//外部触发,使用软件触发,不需要外部触发
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;		//连续转换,失能,每转换一次规则组序列后停止
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;			//扫描模式,失能,只转换规则组的序列1这一个位置
	ADC_InitStructure.ADC_NbrOfChannel = 1;					//通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是1
	ADC_Init(ADC1, &ADC_InitStructure);						//将结构体变量交给ADC_Init,配置ADC1
	
	/*ADC使能*/
	ADC_Cmd(ADC1, ENABLE);									//使能ADC1,ADC开始运行
	
	/*ADC校准*/
	ADC_ResetCalibration(ADC1);								//固定流程,内部有电路会自动执行校准
	while (ADC_GetResetCalibrationStatus(ADC1) == SET);
	ADC_StartCalibration(ADC1);
	while (ADC_GetCalibrationStatus(ADC1) == SET);
}

/**
  * 函    数:获取AD转换的值
  * 参    数:无
  * 返 回 值:AD转换的值,范围:0~4095
  */
uint16_t AD_GetValue(void)
{
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);					//软件触发AD转换一次
	while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);	//等待EOC标志位,即等待AD转换结束
	return ADC_GetConversionValue(ADC1);					//读数据寄存器,得到AD转换的结果
}
#ifndef __AD_H
#define __AD_H

void AD_Init(void);
uint16_t AD_GetValue(void);

#endif
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"

uint16_t ADValue;			//定义AD值变量
float Voltage;				//定义电压变量

int main(void)
{
	/*模块初始化*/
	OLED_Init();			//OLED初始化
	AD_Init();				//AD初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "ADValue:");
	OLED_ShowString(2, 1, "Voltage:0.00V");
	
	while (1)
	{
		ADValue = AD_GetValue();					//获取AD转换的值
		Voltage = (float)ADValue / 4095 * 3.3;		//将AD值线性变换到0~3.3的范围,表示电压
		
		OLED_ShowNum(1, 9, ADValue, 4);				//显示AD值
		OLED_ShowNum(2, 9, Voltage, 1);				//显示电压值的整数部分
		OLED_ShowNum(2, 11, (uint16_t)(Voltage * 100) % 100, 2);	//显示电压值的小数部分
		
		Delay_ms(100);			//延时100ms,手动增加一些转换的间隔时间
	}
}

2、AD多通道实现

#include "stm32f10x.h"                  // Device header

/**
  * 函    数:AD初始化
  * 参    数:无
  * 返 回 值:无
  */
void AD_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);	//开启ADC1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
	
	/*设置ADC时钟*/
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);						//选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA0、PA1、PA2和PA3引脚初始化为模拟输入
	
	/*不在此处配置规则组序列,而是在每次AD转换前配置,这样可以灵活更改AD转换的通道*/
	
	/*ADC初始化*/
	ADC_InitTypeDef ADC_InitStructure;						//定义结构体变量
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;		//模式,选择独立模式,即单独使用ADC1
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	//数据对齐,选择右对齐
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//外部触发,使用软件触发,不需要外部触发
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;		//连续转换,失能,每转换一次规则组序列后停止
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;			//扫描模式,失能,只转换规则组的序列1这一个位置
	ADC_InitStructure.ADC_NbrOfChannel = 1;					//通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是1
	ADC_Init(ADC1, &ADC_InitStructure);						//将结构体变量交给ADC_Init,配置ADC1
	
	/*ADC使能*/
	ADC_Cmd(ADC1, ENABLE);									//使能ADC1,ADC开始运行
	
	/*ADC校准*/
	ADC_ResetCalibration(ADC1);								//固定流程,内部有电路会自动执行校准
	while (ADC_GetResetCalibrationStatus(ADC1) == SET);
	ADC_StartCalibration(ADC1);
	while (ADC_GetCalibrationStatus(ADC1) == SET);
}

/**
  * 函    数:获取AD转换的值
  * 参    数:ADC_Channel 指定AD转换的通道,范围:ADC_Channel_x,其中x可以是0/1/2/3
  * 返 回 值:AD转换的值,范围:0~4095
  */
uint16_t AD_GetValue(uint8_t ADC_Channel)
{
	ADC_RegularChannelConfig(ADC1, ADC_Channel, 1, ADC_SampleTime_55Cycles5);	//在每次转换前,根据函数形参灵活更改规则组的通道1
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);					//软件触发AD转换一次
	while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);	//等待EOC标志位,即等待AD转换结束
	return ADC_GetConversionValue(ADC1);					//读数据寄存器,得到AD转换的结果
}
#ifndef __AD_H
#define __AD_H

void AD_Init(void);
uint16_t AD_GetValue(uint8_t ADC_Channel);

#endif
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"

uint16_t AD0, AD1, AD2, AD3;	//定义AD值变量

int main(void)
{
	/*模块初始化*/
	OLED_Init();				//OLED初始化
	AD_Init();					//AD初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "AD0:");
	OLED_ShowString(2, 1, "AD1:");
	OLED_ShowString(3, 1, "AD2:");
	OLED_ShowString(4, 1, "AD3:");
	
	while (1)
	{
		AD0 = AD_GetValue(ADC_Channel_0);		//单次启动ADC,转换通道0
		AD1 = AD_GetValue(ADC_Channel_1);		//单次启动ADC,转换通道1
		AD2 = AD_GetValue(ADC_Channel_2);		//单次启动ADC,转换通道2
		AD3 = AD_GetValue(ADC_Channel_3);		//单次启动ADC,转换通道3
		
		OLED_ShowNum(1, 5, AD0, 4);				//显示通道0的转换结果AD0
		OLED_ShowNum(2, 5, AD1, 4);				//显示通道1的转换结果AD1
		OLED_ShowNum(3, 5, AD2, 4);				//显示通道2的转换结果AD2
		OLED_ShowNum(4, 5, AD3, 4);				//显示通道3的转换结果AD3
		
		Delay_ms(100);			//延时100ms,手动增加一些转换的间隔时间
	}
}

作者:松松win

物联沃分享整理
物联沃-IOTWORD物联网 » STM32-ADC:独立模式和双重模式详解

发表评论