ADC内部通道的使用
1、内部温度的测量
已知STM32F103C8有10个外部通道和2个内部通道,10个外部通道连接着外部引脚。而2个内部通道连接着特定功能的内部模块。如下图所示:通道16连接着温度传感,所以可以通过通道16来测量单片机内部的温度。通道17连接着一个1.2v恒定不变的电压,可以通过通道17测量出实际的参考电压。
温度传感器在内部和ADC1_IN16输入通道相连接,此通道把传感器输出的电压转换成数字值。温度传感器模拟输入推荐采样时间是17.1μs,若ADC的工作频率为12MHz,则时钟周期T = (1/12)(us),则17.1us = 17.1 * 12(T) = 205.2T,所以采样时间t = 205.2个时钟周期。因此在程序设置中采样时间应 > 205.2T。这样采样到的温度才较为准确。
①ADC.c文件的代码如下:
#include "ADC.h"
/**
* ADC1初始化函数
*/
void ADC1_Init(void)
{
/* 1、使能GPIO和ADC时钟 */
// RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //使能ADC1时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //对ADC时钟源分频
// /* 2、对PA0(ADC1的通道0引脚)进行引脚配置 */
// GPIO_InitTypeDef GPIO_InitStructure;
// GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //PA0 作为模拟通道输入引脚
// GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入引脚
// GPIO_Init(GPIOA, &GPIO_InitStructure);
/* 3、配置ADC1工作模式 */
ADC_InitTypeDef ADC_InitStructure;
ADC_DeInit(ADC1); //将外设ADC1的全部寄存器重设为缺省值
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC1和ADC2工作在独立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //非扫描(单通道)模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //连续模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换由软件而不是外部触发启动
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC1数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; //转换的ADC通道的数目,只有PA0
ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器
/* 4、配置规则组*/
ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 1, ADC_SampleTime_239Cycles5); //ADC1,ADC通道,盒子序列1,
//采样时间为239.5周期 > 205.2T
//此步是将通道16放入序列1的盒子里面
ADC_Cmd(ADC1, ENABLE); //使能ADC1
ADC_TempSensorVrefintCmd(ENABLE); //唤醒关电模式下的温度传感器,此步非常重要,温度传感器唤醒才会工作
/* 5、ADC校准 */
ADC_ResetCalibration(ADC1); //使能复位校准
while (ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束
ADC_StartCalibration(ADC1); //开启AD校准
while (ADC_GetCalibrationStatus(ADC1)); //等待校准结束
/* 6、软件触发 */
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //启动ADC转换
}
/**
* 获取ADC中的结果寄存器的值
*/
uint16_t Get_ADCResult(void)
{
while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); //等待转换完成标志位置1
return ADC_GetConversionValue(ADC1); //获取ADCx->DR里面的数据,ECO自动清除
//【注意】返回的数据是12位的二进制数
}
②主函数main.c文件的代码如下:
#include "UART.h"
#include "ADC.h"
int main(void)
{
uint16_t Data = 0;
float ADC_VSENSE = 0;
float Temp = 0;
UART1_Init();
ADC1_Init();
printf("ADC温度测量\r\n");
while(1)
{
Data = Get_ADCResult();
printf("结果寄存器数值为:%d\r\n",Data);
ADC_VSENSE = Data * 3.3 / 4095; //将二进制计数为电压电压值
printf("内部电压为:%0.2fV\r\n",ADC_VSENSE);
Temp = (1.43 - ADC_VSENSE)/0.0043 + 25; //将电压转换为温度
printf("内部温度为:%0.2f度\r\n",Temp);
Delay_ms(1000);
}
}
2、参考的电压的校准
内部通道17是用于确定参考电压的实际数值。在之前的代码中参考电压都是使用的是3.3v,因为参考电压引脚VREF连接着内部供电电压VDDA,而VDDA连接着VDD,而VDD是3.3v。但是在实际的使用中,VDDA的会随着使用的片上外设增加而减小,所以参考电压也会改变,而不一直是3.3v。
可以通过内部通道17测量出实际的参考电压的值。内部通道17连接着内部参照电压VREFINT(1.2v),VREFINT(1.2v)是恒定不变的,不会随着单片机的使用而改变。所以可以通过以下公式得出实际参考电压VREF的值。
①ADC.c文件的代码如下:
#include "ADC.h"
/**
* ADC1初始化函数
*/
void ADC1_Init(void)
{
/* 1、使能GPIO和ADC时钟 */
// RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //使能ADC1时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //对ADC时钟源分频
/* 使用内部通道,无需引脚配置 */
// /* 2、对PA0(ADC1的通道0引脚)进行引脚配置 */
// GPIO_InitTypeDef GPIO_InitStructure;
// GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //PA0 作为模拟通道输入引脚
// GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入引脚
// GPIO_Init(GPIOA, &GPIO_InitStructure);
/* 3、配置ADC1工作模式 */
ADC_InitTypeDef ADC_InitStructure;
ADC_DeInit(ADC1); //将外设ADC1的全部寄存器重设为缺省值
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC1和ADC2工作在独立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //非扫描(单通道)模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //非连续模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换由软件而不是外部触发启动
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC1数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; //转换的ADC通道的数目
ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器
ADC_Cmd(ADC1, ENABLE); //使能ADC1
/* 5、ADC校准 */
ADC_ResetCalibration(ADC1); //使能复位校准
while (ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束
ADC_StartCalibration(ADC1); //开启AD校准
while (ADC_GetCalibrationStatus(ADC1)); //等待校准结束
}
/**
* 获取ADC中的结果寄存器的值
*/
uint16_t Get_ADCResult(void)
{
/* 配置规则组 */
ADC_RegularChannelConfig(ADC1, ADC_Channel_17, 1, ADC_SampleTime_55Cycles5); //ADC1,ADC通道,盒子序列1,采样时间为55.5周期
/* 软件触发 */
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //启动ADC转换
while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); //等待转换完成标志位置1
return ADC_GetConversionValue(ADC1); //获取ADCx->DR里面的数据,ECO自动清除
//【注意】返回的数据是12位的二进制数
}
②主函数main.c文件的代码如下:
#include "stm32f10x.h"
#include "Delay.h"
#include "UART.h"
#include "ADC.h"
int main(void)
{
uint16_t Data = 0;
float ADC_VREF = 0;
UART1_Init();
ADC1_Init();
printf("ADC实际参考电压\r\n");
while(1)
{
Data = Get_ADCResult();
printf("结果寄存器数值为:%d\r\n",Data);
ADC_VREF = 1.2 * 4095 / Data; //求出参考电压实际值
printf("参考电压实际值:%0.2f\r\n",ADC_VREF);
Delay_ms(1000);
}
}
如图所示:此时的参考电压是3.18v,而不是3.3v。
3、使用校准后的参考电压测量内部温度值
①ADC.c文件的代码如下:
#include "ADC.h"
/**
* ADC1初始化函数
*/
void ADC1_Init(void)
{
/* 1、使能GPIO和ADC时钟 */
// RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //使能ADC1时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //对ADC时钟源分频
/* 使用内部通道,无需引脚配置 */
// /* 2、对PA0(ADC1的通道0引脚)进行引脚配置 */
// GPIO_InitTypeDef GPIO_InitStructure;
// GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //PA0 作为模拟通道输入引脚
// GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入引脚
// GPIO_Init(GPIOA, &GPIO_InitStructure);
/* 3、配置ADC1工作模式 */
ADC_InitTypeDef ADC_InitStructure;
ADC_DeInit(ADC1); //将外设ADC1的全部寄存器重设为缺省值
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC1和ADC2工作在独立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //非扫描(单通道)模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //非连续模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换由软件而不是外部触发启动
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC1数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; //转换的ADC通道的数目
ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器
ADC_Cmd(ADC1, ENABLE); //使能ADC1
ADC_TempSensorVrefintCmd(ENABLE); //唤醒关电模式下的温度传感器,这一步很重要,唤醒传感器才会工作。
/* 5、ADC校准 */
ADC_ResetCalibration(ADC1); //使能复位校准
while (ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束
ADC_StartCalibration(ADC1); //开启AD校准
while (ADC_GetCalibrationStatus(ADC1)); //等待校准结束
}
/**
* 获取ADC中的结果寄存器的值
*/
uint16_t Get_ADCResult(uint8_t ADC_Channel)
{
/* 配置规则组 */
ADC_RegularChannelConfig(ADC1, ADC_Channel, 1, ADC_SampleTime_239Cycles5); //ADC1,ADC通道,盒子序列1,采样时间为239.5周期
/* 软件触发 */
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //启动ADC转换
while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); //等待转换完成标志位置1
return ADC_GetConversionValue(ADC1); //获取ADCx->DR里面的数据,ECO自动清除
//【注意】返回的数据是12位的二进制数
}
②主函数main.c文件的代码如下:
#include "stm32f10x.h"
#include "Delay.h"
#include "UART.h"
#include "ADC.h"
int main(void)
{
uint16_t Data = 0;
float ADC_VREF = 0;
float ADC_VSENSE = 0;
float Temp = 0;
UART1_Init();
ADC1_Init();
while(1)
{
Data = Get_ADCResult(ADC_Channel_17);
ADC_VREF = 1.2 * 4095 / Data; //求出参考电压实际值
printf("参考电压实际值:%0.2fv\r\n",ADC_VREF);
Data = Get_ADCResult(ADC_Channel_16);
ADC_VSENSE = ADC_VREF / 4095 * Data; //使用校准的参考电压求温度传感器电压
printf("内部温度电压为:%0.2fv\r\n",ADC_VSENSE);
Temp = (1.43 - ADC_VSENSE)/0.0043 + 25; //将电压转换为温度
printf("内部温度为:%0.2f度\r\n",Temp);
Delay_ms(1000);
}
}
综上:若没有使用校准的参考电压时:即3.3v,测量出的温度传感器的内部电压为1.45v,计算出的温度为21度。而使用了校准的参考电压,即3.25v,测量出的温度传感器的内部电压为1.43v,算出的温度为25度。