17:ADC模数转换器

1、ADC的简介

ADC就是一个模数转换器,将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁。说的直观一点就是一个电压表,用于测量电压的片上外设。其中转换电压的范围0~3.3v

在这里插入图片描述
其中这些传感器在有外界的刺激的情况下,将会改变自身电阻的大小(如下图N1电阻),进而改变输出电压的大小。而电压的大小通过模拟输出口AO口输出。单片机可以通过ADC获取外部模块的电压值,进而做出一些动作。
在这里插入图片描述
那么单片机是怎样通过ADC进行对外部电压的测量并转换为数字量保存在内存中喃?

答案:比较器将ADC的结果寄存器里面的数据(DAC转换)和模拟信号进行比较,
①若ADC的结果寄存器里面的数据<模拟信号,则增加ADC的结果寄存器里面的数据。
②若ADC的结果寄存器里面的数据>模拟信号,则减小ADC的结果寄存器里面的数据。
③若ADC的结果寄存器里面的数据=模拟信号,则将数据保存到DMA中。

STM32F103C8T6有2个ADC,假如ADC的结果寄存器是2位,那么分辨率是多少喃?如下图
在这里插入图片描述
若结果寄存器是4位喃?是8位喃?

在这里插入图片描述

2、逐次逼近型ADC

我们先来了解一下ADC的工作原理,如下图所示:
在这里插入图片描述

如图:当闭合采样开关,模拟信号输入,对电容充电。当充满电后,断开采样开关,比较器通过比较来给结果寄存器增加数据/减少数据,直到和模拟信号的值相差到规定的数值以内,然后将数据保存并转换。如下图为模拟信号为2.21v。

在这里插入图片描述
则结果寄存器的数据最后为1010。

3、采样时间和转换时间

采样时间就是对电容充满电所需要的时间,而转换时间就是通过比较,确定结果寄存器里面的数值的时间。
而ADC挂载在APB2总线上,但是ADC的特性规定,ADC的时钟<14MHz。所以APB2时钟出来后还要被分频才能够给ADC通过时钟源。

在这里插入图片描述
在这里插入图片描述

  • 转换时间 = 12.5的时钟周期。

在这里插入图片描述

  • 采样时间 = 电容充电的时间
    电容充电的时间越长,采样的数据就越精确,误差就越小。
    在这里插入图片描述但是ADC的精度本来就是有限的,只要我们采样的误差<1/4*ADC分辨率即可。
    在这里插入图片描述电容充电的时间由电路中的电阻决定的,电阻越大,电流越小,充电时间越长,采样时间越长。
    在这里插入图片描述
    假设ADC的时钟源频率为14MH在,则
    在这里插入图片描述但是在编程中采样时间是规定了8个挡位的,所以我们只能选择和采样时间相近的那个挡位。挡位>采样时间,数据越精确。挡位<采样时间,数据变化的越快

问:最理想的情况下ADC每砂最多执行多少次转换

在这里插入图片描述
所以:理想情况下1us转换1次数据,则1s转换1000000次。

4、STM32中ADC模块

STM32F103C8T6单片机中有2个ADC模块:ADC1、ADC2,结果寄存器都为12位。也就是将3.3v的电压分为了4095份。且每个ADC片上外设都有10外部测量通道和2个内部信号源。我们可以通过外部通道测量外部模块的电压
在这里插入图片描述
这么多的通道是怎样管理的喃?
答案是:通过规则组(常规序列)和注入组(注入序列)进行管理

在这里插入图片描述

+如上图所示:规则组和注入组就像是一个盒子(用来存放需要测量的物件),ADC就像是一个工人。
① 规则组:规则组里面最多能对16个通道采集转换(盒子里面最多能放16个物件等待被测量),而存放采集转换的数据的盒子只有一个。所以只有等第一个的数据被DMA挪走后,第二个数据才能放入盒子里面
② 注入组:注入组里面最多能对4个通道采集转换,而又有4个盒子专门存放数据。
③触发控制:让ADC开始采集转换的信号(让工人开始测量),如下图所示,有定时器启动和软件启动。而软件启动就是就是给寄存器置1
在这里插入图片描述

在这里插入图片描述

规则组的转换模式:
①单次转换非扫描模式
在这里插入图片描述
在此模式下,规则组只有第一个盒子里面的通道才有效(即序列1),我们可以在序列1的盒子里面放入我们需要转换的通道,然后给规则组一个触发信号,开始采集转换,转换完成后数据保存在数据寄存器里面,同时给ECO置1。需要进行第二次转换,那么需要在启动一次触发信号,开启转换。

②连续转换非扫描模式
在这里插入图片描述
和单次转换非扫描模式不同的是,给规则组一个触发信号,在第一次转换完成后立马进入第二次转换,不断的进行下去。单片机需要获取数据,只需要读取数据寄存器里面的数据即可

③单次转换扫描模式

在这里插入图片描述
在规则组里面的多个盒子里面放入我们需要转换的通道,然后告诉ADC有多少个盒子,转换完成后依次存放在数据寄存器里面。ECO置1,需要第二次转换则需要启动触发信号。

④连续转换扫描模式
在这里插入图片描述

数据对齐:
ADC转换后的数据是12位的,而数据寄存器是16位的,所以数据寄存器有4个空出来的位置补零。一般的情况下使用右对齐即可
在这里插入图片描述

数据校准
校准都是固定的,只需要在初始化后加上校准代码即可,不用理解。
在这里插入图片描述

5、编程案列

与之相关的标准库编程接口:
在这里插入图片描述

5.1、AD单通道

ADC.c文件的代码如下;

/*
	ADC单通道实验,通过通道1(PA0)对自身输出电压进行采集,通过电位器改变电压变化,最终在OLED上面显示出来
*/
#include "stm32f10x.h"                  // Device header

void AD_Init(void)
{
	//1.开启时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	
	//2.对ADC时钟进行分频
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);//72MHZ/6 = 12MHz
	
	//3.对通道1(PA0)进行配置
	GPIO_InitTypeDef GPIOInitStruct;
	GPIOInitStruct.GPIO_Mode = GPIO_Mode_AIN;//模拟输入模式,ADC的专属模式
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_0;

	GPIO_Init(GPIOA,&GPIOInitStruct);
	
	//4.对ADC规则组进行配置
	ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);//ADC几,通道,序列,采样时间
	
	//5.初始化ADC
	ADC_InitTypeDef ADC_InitStruct;
	ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;//工作模式:独立模式/双ADC模式,这里的独立模式
	ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//ADC触发选择,选择软件触发
	ADC_InitStruct.ADC_ContinuousConvMode = DISABLE;//连续/单次模式,这里选择单次
	ADC_InitStruct.ADC_ScanConvMode = DISABLE;//扫描/非扫描,这里选择非扫描
	ADC_InitStruct.ADC_NbrOfChannel = 1;//在扫描模式下的盒子数目
	ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;//数据对齐,右对齐
	
	ADC_Init(ADC1,&ADC_InitStruct);
	
	//6.开启ADC电源
	ADC_Cmd(ADC1,ENABLE);
	
	//7.校准
	ADC_ResetCalibration(ADC1);//将校准复位,给CR2_RSTCAL置1,进行复位
	while(ADC_GetResetCalibrationStatus(ADC1) == SET);//复位完成,硬件置0
	ADC_StartCalibration(ADC1);//开始校准,给CR2_CAL置1
	while(ADC_GetCalibrationStatus(ADC1) == SET);//校准完成,硬件置0
	
}

uint16_t AD_GetValue(void)//获取数据寄存器里面的数据
{
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发
	while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);//等待ECO置1,代表转换完成
	return ADC_GetConversionValue(ADC1);//获取ADCx->DR里面的数据,ECO自动清除
}

【注】如果使用连续非扫描模式那么ADC.c文件的代码如下:

#include "stm32f10x.h"                  // Device header

void AD_Init(void)
{
	//1.开启时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	
	//2.对ADC时钟进行分频
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);//72MHZ/6 = 12MHz
	
	//3.对通道1(PA0)进行配置
	GPIO_InitTypeDef GPIOInitStruct;
	GPIOInitStruct.GPIO_Mode = GPIO_Mode_AIN;//模拟输入模式,ADC的专属模式
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_0;

	GPIO_Init(GPIOA,&GPIOInitStruct);
	
	//4.对ADC规则组进行配置
	ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);//ADC几,通道,序列,采样时间
	
	//5.初始化ADC
	ADC_InitTypeDef ADC_InitStruct;
	ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;//工作模式:独立模式/双ADC模式,这里的独立模式
	ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//ADC触发选择,选择软件触发
	ADC_InitStruct.ADC_ContinuousConvMode = ENABLE;//连续/单次模式,这里选择单次
	ADC_InitStruct.ADC_ScanConvMode = DISABLE;//扫描/非扫描,这里选择非扫描
	ADC_InitStruct.ADC_NbrOfChannel = 1;//在扫描模式下的盒子数目
	ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;//数据对齐,右对齐
	
	ADC_Init(ADC1,&ADC_InitStruct);
	
	//6.开启ADC电源
	ADC_Cmd(ADC1,ENABLE);
	
	//7.校准
	ADC_ResetCalibration(ADC1);//将校准复位,给CR2_RSTCAL置1,进行复位
	while(ADC_GetResetCalibrationStatus(ADC1) == SET);//复位完成,硬件置0
	ADC_StartCalibration(ADC1);//开始校准,给CR2_CAL置1
	while(ADC_GetCalibrationStatus(ADC1) == SET);//校准完成,硬件置0
	
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发
	while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);//等待ECO置1,代表转换完成
	
}

uint16_t AD_GetValue(void)//获取数据寄存器里面的数据
{
//	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发
//	while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);//等待ECO置1,代表转换完成
	return ADC_GetConversionValue(ADC1);//获取ADCx->DR里面的数据,ECO自动清除
}

主程序文件的代码如下:

/*
	ADC单通道的简单使用
*/

#include "stm32f10x.h"                 
#include "OLED.h"
#include "ADC.h"
#include "Delay.h"

uint16_t Value;
float Voltage;				//定义电压变量

int main(void)
{
	AD_Init();
	OLED_Init();
	OLED_Clear();
	OLED_ShowString(1,1,"Value:");
	OLED_ShowString(2,1,"Voltage:0.00V");
	while(1)
	{
		Value = AD_GetValue();//获取数据
		Voltage = (Value * 3.3) / 4095;//转换为电压值
		
		OLED_ShowNum(1,7,Value,4);//显示数据
		OLED_ShowNum(2,9,Voltage,1);//显示电压值的整数部分
		OLED_ShowNum(2,11,(uint16_t)(Voltage*100) % 100,2);//显示电压值的小数部分
		Delay_ms(100);
	}
}

5.2、AD多通道

我们还没有学习DMA,使用如果不使用DMA对多通道转换的数据进行挪动的话,那么会出现数据的覆盖。我们通过单次转换非扫描模式模拟单次扫描模式。就是在第一次转换完成后,将第一序列的盒子里面的通道通过手动修改。
ADC.c文件的代码如下:

#include "stm32f10x.h"                  // Device header

void AD_Init(void)
{
	//1.开启时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	
	//2.对ADC时钟进行分频
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);//72MHZ/6 = 12MHz
	
	//3.对通道1(PA0)进行配置
	GPIO_InitTypeDef GPIOInitStruct;
	GPIOInitStruct.GPIO_Mode = GPIO_Mode_AIN;//模拟输入模式,ADC的专属模式
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_0 |GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;

	GPIO_Init(GPIOA,&GPIOInitStruct);
	
	//4.对ADC规则组进行配置
//	ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);//ADC几,通道,序列,采样时间
	
	//5.初始化ADC
	ADC_InitTypeDef ADC_InitStruct;
	ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;//工作模式:独立模式/双ADC模式,这里的独立模式
	ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//ADC触发选择,选择软件触发
	ADC_InitStruct.ADC_ContinuousConvMode = DISABLE;//连续/单次模式,这里选择单次
	ADC_InitStruct.ADC_ScanConvMode = DISABLE;//扫描/非扫描,这里选择非扫描
	ADC_InitStruct.ADC_NbrOfChannel = 1;//在扫描模式下的盒子数目
	ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;//数据对齐,右对齐
	
	ADC_Init(ADC1,&ADC_InitStruct);
	
	//6.开启ADC电源
	ADC_Cmd(ADC1,ENABLE);
	
	//7.校准
	ADC_ResetCalibration(ADC1);//将校准复位,给CR2_RSTCAL置1,进行复位
	while(ADC_GetResetCalibrationStatus(ADC1) == SET);//复位完成,硬件置0
	ADC_StartCalibration(ADC1);//开始校准,给CR2_CAL置1
	while(ADC_GetCalibrationStatus(ADC1) == SET);//校准完成,硬件置0
}

uint16_t AD_GetValue(uint8_t ADC_Channel)//获取数据寄存器里面的数据,指定通道
{
	ADC_RegularChannelConfig(ADC1,ADC_Channel,1,ADC_SampleTime_55Cycles5);//ADC几,通道,序列,采样时间
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发
	while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);//等待ECO置1,代表转换完成
	return ADC_GetConversionValue(ADC1);//获取ADCx->DR里面的数据,ECO自动清除
}

主程序文件的代码如下:

/*
	ADC单通道的简单使用
*/

#include "stm32f10x.h"                 
#include "OLED.h"
#include "ADC.h"
#include "Delay.h"

uint16_t Value1,Value2,Value3,Value4;

int main(void)
{
	AD_Init();
	OLED_Init();
	OLED_Clear();
	
	OLED_ShowString(1,1,"Value1:");
	OLED_ShowString(2,1,"Value2:");
	OLED_ShowString(3,1,"Value3:");
	OLED_ShowString(4,1,"Value4:");
	
	while(1)
	{
		Value1 = AD_GetValue(ADC_Channel_0);//获取通道1数据,手动修改第一序列的通道
		Value2 = AD_GetValue(ADC_Channel_1);//获取通道2数据
		Value3 = AD_GetValue(ADC_Channel_2);//获取通道3数据
		Value4 = AD_GetValue(ADC_Channel_3);//获取通道4数据
		
		OLED_ShowNum(1,8,Value1,4);
		OLED_ShowNum(2,8,Value2,4);
		OLED_ShowNum(3,8,Value3,4);
		OLED_ShowNum(4,8,Value4,4);

		Delay_ms(100);
	}
}

【注】此方法不能在连续非扫描情况下模拟连续扫描模式。
因为:连续非扫描模式下,只需要一次的触发信号,如果模拟连续扫描模式,则需要手动修改通道,而修改通道后没有触发信号来进行触发。

#include "stm32f10x.h"                  // Device header

void AD_Init(void)
{
	//1.开启时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	
	//2.对ADC时钟进行分频
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);//72MHZ/6 = 12MHz
	
	//3.对通道1(PA0)进行配置
	GPIO_InitTypeDef GPIOInitStruct;
	GPIOInitStruct.GPIO_Mode = GPIO_Mode_AIN;//模拟输入模式,ADC的专属模式
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_0 |GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;

	GPIO_Init(GPIOA,&GPIOInitStruct);
	
	//4.对ADC规则组进行配置
//	ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);//ADC几,通道,序列,采样时间
	
	//5.初始化ADC
	ADC_InitTypeDef ADC_InitStruct;
	ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;//工作模式:独立模式/双ADC模式,这里的独立模式
	ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//ADC触发选择,选择软件触发
	ADC_InitStruct.ADC_ContinuousConvMode = ENABLE;//连续/单次模式,这里选择单次
	ADC_InitStruct.ADC_ScanConvMode = DISABLE;//扫描/非扫描,这里选择非扫描
	ADC_InitStruct.ADC_NbrOfChannel = 1;//在扫描模式下的盒子数目
	ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;//数据对齐,右对齐
	
	ADC_Init(ADC1,&ADC_InitStruct);
	
	//6.开启ADC电源
	ADC_Cmd(ADC1,ENABLE);
	
	//7.校准
	ADC_ResetCalibration(ADC1);//将校准复位,给CR2_RSTCAL置1,进行复位
	while(ADC_GetResetCalibrationStatus(ADC1) == SET);//复位完成,硬件置0
	ADC_StartCalibration(ADC1);//开始校准,给CR2_CAL置1
	while(ADC_GetCalibrationStatus(ADC1) == SET);//校准完成,硬件置0
	
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发
	while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);//等待ECO置1,代表转换完成
}

uint16_t AD_GetValue(uint8_t ADC_Channel)//获取数据寄存器里面的数据
{
	ADC_RegularChannelConfig(ADC1,ADC_Channel,1,ADC_SampleTime_55Cycles5);//ADC几,通道,序列,采样时间
//	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发
//	while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);//等待ECO置1,代表转换完成
	return ADC_GetConversionValue(ADC1);//获取ADCx->DR里面的数据,ECO自动清除
}

上面的代码为连续非扫描情况下模拟连续扫描模式的ADC.c文件的代码,需要手动修改通道的在软件触发之后,这是不允许的,因为等规则组里面的配置完整后,在给触发信号这样正确。

  • 14
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值