目录
一丶ADC介绍
二丶ADC工作原理及管脚分布
三丶代码部分详解
(一)库函数介绍
(二)代码部分整合
ADC模块中文名为模拟/数字转换器,是12位逐次逼近型的模拟数字转换器,一般用于数值的采样 可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁。学习过stm32后我们知道,stm32是数字电路,。数字电路没有多少伏,多少度的概念,而通常的传感器模块,输出的都是模拟量。
比如我要使用热敏传感器测量温度,那么需要将传感器模块的模拟量,转换成STM32可以“看懂的数字量”,所以想要读取温度的数值,就需要用到ADC模数转换器来实现了,实现过程简单来说就是ADC读取引脚上的电压,通过转换,储存到DR寄存器里。(本次使用的是STM32C8T6)
AD转换原理如图
(1)ADC的输入通道,包括IN0-IN15,共16GPIO口,和两个内部通道,内部温度传感器,内部参考电压
(2)共18个输入通道,将数据传输到数模转换器,模拟多路开关可选择我们想要的通道
(3)数模转换器使用逐次逼近法将模拟量转换为数字量
(4)转换后分为两个组:规则组和注入组,存放准换结果。
规则组:可以传输16位数据,但规则组寄存器只有一位,所以如果转运不及时会出现数据覆盖的问题,前面的数据还没有读,后面的数据就上来了,这时候就需要用到DMA,DMA可以将ADC寄存器的数据暂时挪到DMA寄存器中,需要的时候就会被取出(本次代码不涉及DMA,如需了解可看下篇文章)本次代码使用的是规则组
注入组:注入组一次可以传输4位数据,则不需要考虑数据覆盖的问题
(5)我们知道,ADC可以由软件触发,也可以由硬件触发,软件触发顾名思义,在程序中手动调动代码即可实现。而硬件触发,就是框图内所包含的触发源。靠上的是规则组,靠下的是注入组
(6)EOC是规则组准换完成信号,JEOC是注入组转换完成信号
总结如图
鉴于ADC库中函数繁多,本次只介绍现象代码会使用到的部分和少量拓展
1丶复位函数ADC_DeInit
void ADC_DeInit(ADC_TypeDef* ADCx);
该函数作用为将外设ADCx的全部寄存器重设为缺省值,说的简单点就是复位,恢复“出厂设置
2丶初始化函数ADC_Init
工作模式:本次采用单ADC模式
数据对其方式:ADC转换是12位,故存储在寄存器中是12位的数据,但数据寄存器是16位的,存储时空位补零,所以就出现了数据对其问题。一半来说,我们都是采用右对齐的模式。那么左对齐有什么用呢,加入你只想读一个大概的数据,不想要这么高的分辨率,就可以使用左对齐,取前八位,就舍弃了后面的数据,减小了分辨率。
ADC触发方式:选择内部软件触发
转换模式:单次转换模式为转换一次给到寄存器数据后停止。连续转换模式转换一次后开始下一轮转换,一直持续下去,不需要手动开始下一次转换
扫描模式:多通道进行
通道数选择:只选用一个通道
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //独立工作模式,即单ADC模式
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据右对齐
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //内部软件触发
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //单次转换模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //非扫描模式
ADC_InitStructure.ADC_NbrOfChannel = 1; //只使用一个通道
ADC_Init(ADC1, &ADC_InitStructure);
3丶 ADC_Cmd使能ADC
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);
4丶 ADC_DMACmd是否使用DMA
void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);
5丶ADC_ITConfig是否使用中断
void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);
6丶根据手册规定,需要进行ADC校准
void ADC_ResetCalibration(ADC_TypeDef* ADCx); //复位校准
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx); //复位校准状态
void ADC_StartCalibration(ADC_TypeDef* ADCx); //开始校准
FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx); //校准状态
7丶ADC_SoftwareStartConvCmd软件触发ADC
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
8丶时钟分频
手册规定ADC最大14mhz,所以选择6分频或8分频
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
结合上文总结一下ADC初始化流程
(1)开启APB2时钟和ADC时钟
(2)ADC时钟分频,选择六分频或八分频
(3)初始化GPIO 注:这里GPIO模式要使用GPIO_Mode_AIN,ADC专用模式
(4)ADC及ADC通道选择
(5)初始化ADC
(6)使能ADC,给ADC上电
(7)校准ADC
ADC部分
#include "stm32f10x.h" // Device header
void AD_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
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);
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;
ADC_InitStructure.ADC_NbrOfChannel=1;
ADC_InitStructure.ADC_ScanConvMode=DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;
ADC_Init(ADC1,&ADC_InitStructure);
ADC_Cmd(ADC1,ENABLE);
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1)==SET);
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1)==SET);
}
uint16_t AD_GetValue(void)
{
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==RESET);
return ADC_GetConversionValue(ADC1);
}
主函数部分
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "ADC.h"
#include "LED.h"
uint16_t Temp;
#define T25 298.15
#define B 3380
float Vlue;
double myLn(double a)
{
int N = 15;
int k=0,nk=0;
double x=0.0,xx=0.0,y=0.0;
x=(a-1)/(a+1);
xx = x*x;
nk = 2*N+1;
y = 1.0/nk;
for(k = N;k>0;k--)
{
nk = nk -2;
y = 1.0/nk+xx*y;
}
return 2.0*x*y;
}
float Get_Temperaturn(void )
{
float r_f = 0.0,temp_f = 0.0;
Temp = AD_GetValue();
Vlue = (float)Temp/4095*3.3;
r_f = (Vlue*10000)/(3.3-Vlue);
temp_f = 1/((myLn(r_f/10000))/B + 1/T25 ) - 273.15;
return temp_f;
}
int main(void)
{
OLED_Init();
AD_Init();
OLED_ShowString(1, 1, "Temperature:");
LED1_ON();
while (1)
{
Temp = AD_GetValue();
OLED_ShowNum(2, 3, Get_Temperaturn(), 2);
OLED_ShowString(2,6,"C");
Delay_ms(100);
if(Get_Temperaturn()>28)
{
OLED_ShowString(3,1,"warn");
}
if(Get_Temperaturn()<28)
{
OLED_ShowString(3,1,"safe");
}
}
}
关于温度的转换
Rt = R 乘 EXP(B 乘 (1/T1-1/T2))
对上面的公式解释如下:
Rt 是热敏电阻在T1温度下的阻值;
R是热敏电阻在T2常温下的标称阻值;
B值是热敏电阻的重要参数;
EXP是e的n次方;
这里T1和T2指的是K度即开尔文温度,K度=273.15(绝对温度)+摄氏度;
根据串联分压,知道总电压VCC 3.3v,热敏电阻的电压V2是adc采集后经过转换得到的,也是已知,
所以R1的电压就是VCC - V2 ,然后根据R1电阻10K,可以求得电路的电流 I ,所以热敏电阻的
电阻 就可以用电流电压比值,于是得到Rt。
参数R 和 B值都是热敏电阻的参数,根据自己买的器件决定,我的就是10k,3380。可以问卖家,
也可以自己网上查型号。
这里还要注意,T2的单位是开尔文,所以室温25摄氏度的开尔文是273.15+25=298.15.
就只剩下T1是未知数,一元一次方程,带进去一算就欧克。
AD视频