一直想要做一个可以控制温度,检测温度的简单的嵌入式设备,在做的过程中,也是有很多的体会与感悟,在电路和小模块的使用和制作上以及知识点的学习上,感觉网上能查到的、能让我这种初学者易学的内容不算多,所以想着写一写方便初学者开发简单的嵌入式设备学习的内容,写的比较细,希望能帮到需要的人。
一、NTC(热敏电阻)
首先想要做一个测温控温的设备先要了解有哪些可以进行检测温度的东西,我手上拿到的是一个NTC(热面电阻)。热敏电阻大家都了解是随着温度的变化,阻值就发生变化的一个敏感元件,NTC又是什么呢?可以看一看百度的的介绍是做直接的:
热敏电阻是一种传感器电阻,其电阻值随着温度的变化而改变。按照温度系数不同分为正温度系数热敏电阻(PTC thermistor,即 Positive Temperature Coefficient thermistor)和负温度系数热敏电阻(NTC thermistor,即 Negative Temperature Coefficient thermistor)。正温度系数热敏电阻器的电阻值随温度的升高而增大,负温度系数热敏电阻器的电阻值随温度的升高而减小,它们同属于半导体器件。
现在我了解了NTC就是随着温度升高而电阻值降低的热敏电阻,那问题又来了,我怎样才能知道热敏电阻的阻值呢?
1、ADC
首先我们要了解ADC是什么:ADC,全称模数转换器(Analog-to-Digital Converter),模拟数字转换器即A/D转换器,ADC的作用就是将连续变化的模拟信号转换为离散的数字信号。通常的模数转换器是将一个输入电压信号转换为一个输出的数字信号。由于数字信号本身不具有实际意义,仅仅表示一个相对大小。故任何一个模数转换器都需要一个参考模拟量作为转换的标准,比较常见的参考标准为最大的可转换信号大小。而输出的数字量则表示输入信号相对于参考信号的大小 。
说白了就是,用数字来代替当前的电压值,经过这些敏感元件在生产制作时的测试,会告诉我们从敏感元件中读取的数据怎样转换成需要的电压值。
首先怎样把NTC内的ADC值读取出来:
ADC.c文件:
#include "stm32f10x.h" // Device header
#include "Delay.h"
/**
* 函 数: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_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA0引脚初始化为模拟输入
/*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_Channel(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转换的结果
}
//多取几次,取平均值
uint16_t Get_Adc_Average(uint8_t times,uint8_t ADC_Channel)
{
uint32_t temp_val = 0;
for(uint8_t t = 0;t < times;t++)
{
temp_val += AD_GetValue_Channel(ADC_Channel);
Delay_ms(5);
}
return temp_val / times;
}
ADC.h文件
#ifndef __AD_H
#define __AD_H
void AD_Init(void);
uint16_t AD_GetValue_Channel(uint8_t ADC_Channel);
uint16_t Get_Adc_Average(uint8_t times,uint8_t ADC_Channel);
#endif
注释很细,通过至两个文件的代码即可读出ADC的值(这里是开了三个通道读取三个NTC的ADC值,并且为了减小误差进行的读多次取平均值,这段代码只要是使用ADC一般都可以直接用)。
2、电压电阻值的转换
有了ADC值,那我想要NTC两端的电压值应当怎样做呢?
我用的NTC热敏电阻的数据是线性的,直接对应0欧姆对应0伏,通过计算机课得到实际的电压值:
//------------------------------获取输出电压(伏特)-----------------------------------
double Get_Vout(uint16_t ADValue)
{
return ((double)ADValue / 4095 * 3.3); //将AD值线性变换到0~3.3的范围,表示电压
}
接下来只需要通过固定的公式就能算出NTC当前的电阻值了(需要给NTC连接上电阻进行辅助计算,计算公式及原理以及简单的电路连接网上一抓一大把,简单给大家附上我的)
//-----------------------------------获取电阻值(欧姆)---------------------------------
double Get_Resistance(uint16_t adc_value)
{
double resistance; // 电阻值
resistance = (Get_Vout(adc_value) / (Vin - Get_Vout(adc_value))) * R1; //求出NTC当前的电阻
return resistance; // 返回电阻值
}
#define Vin 3.3 //系统电压
#define R1 100000 //分压电阻
(注意上面函数中需要知道你接的上电阻的阻值R1,才能计算电压值,本文中接的是100K,也就是R1 = 100000,Vin为当前输入进来的电压值,单片机IO口一般都是3.3V,也就是Vin = 3.3;结合上面的求电压值函数Get_Vout(adc_value)即可进行运算)
3、温度值的计算
到此时电阻值我们也得到了,最后搞清楚怎么通过电阻值获取到对应的温度就彻底完成了测温的功能。
在网上我大概看了一下,基本上有两种获取NTC温度值的方法,第一种是通过NTC电阻值与温度值的关系,进行各种各样的计算来得到当前电阻值所对应的温度是多少。(第一种很好用,但是我没太搞懂句的关系和计算,而且调用对数计算比较麻烦,以我的脑袋瓜儿需要慢慢捋清楚)第二种是相对来说比较简单的方式,查表法,咱们就简单说一下这个查表法。
思路:
查表法,顾名思义就是,有一个表格,之个表格里面有NTC的电阻值和温度值,我们只需要获取到ADC模拟量的值转换成电阻值,再在表格中查找当前的电阻值所对应的温度值是多少就可以了。
做法:
首先我们需要搜索一下有没有这样的表格,一搜索,发现确实有很多现成的表格,但是内容都不太一样,那我们应该选哪一种表格呢?这就要问一下我们买的NTC的店家了,我的店家告诉我我买的NTC是100K 3950的,这是什么意思呢?我不管,我先查,一查,这种型号的NTC温度表基本上差不多,为什么有小小的数字区别呢?查了一下基本上就是存在的计算误差而已,我当前环境不需要温度多么的准确,能用就行。简单给大家找了一个,并且把表个写成了数组,大家可以直接拿去用(也可以去网上搜):
NTC.h文件:
#ifndef __NTC_H
#define __NTC_H
typedef struct
{
uint16_t ADValue; //定义AD值变量
uint32_t Resistance; //定义电阻值变量
float Voltage; //定义电压变量
float Temperature; //定义温度变量
}Channel_Value;
//阻值温度表(KΩ)
float Temp_Resistance_Tab_2[150] =
{
332.0384,
306.235,
291.2999,
277.1804,
263.8276,
251.1955,
239.2413,
227.9249,
217.2087,
207.0576,
197.4389,
188.3216,
179.6769,
171.4779,
163.699,
156.3166,
149.3083,
142.6533,
136.3317,
130.3252,
124.6165,
119.1891,
114.0277,
109.118,
104.4464,
100, //25度时,电阻值为100K,这就是前面说的NTC型号参数
95.7669,
91.7358,
87.8959,
84.2373,
80.7504,
77.4264,
74.2568,
71.2336,
68.3494,
65.5971,
62.970,
60.4618,
58.0664,
55.7784,
53.5923,
51.5031,
49.506,
47.5966,
45.7705,
44.0238,
42.3525,
40.7531,
39.2222,
37.7564,
36.3527,
35.0082,
33.7201,
32.4858,
31.3027,
30.1686,
29.0811,
28.0381,
27.0376,
26.0776,
25.1564,
24.2722,
23.4233,
22.6082,
21.8254,
21.0734,
20.3509,
19.6565,
18.9892,
18.3477,
17.7308,
17.1376,
16.5671,
16.0181,
15.4899,
14.9816,
14.4923,
14.0213,
13.5677,
13.1309,
12.7102,
12.3048,
11.9142,
11.5378,
11.175,
10.8253,
10.488,
10.1628,
9.8491,
9.5465,
9.2546,
8.9729,
8.701,
8.4385,
8.1851,
7.9404,
7.7042,
7.476,
7.2555,
7.0425,
6.8367,
6.6378,
6.4455,
6.2597,
6.080,
5.9062,
5.7381,
5.5756,
5.4183,
5.2661,
5.1189,
4.9764,
4.8384,
4.7049,
4.5756,
4.4504,
4.3292,
4.2117,
4.098,
3.9878,
3.881,
3.7775,
3.6772,
3.5799,
3.4857,
3.3943,
3.3057,
3.2197,
3.1363,
3.0555,
2.977,
2.9009,
2.827,
2.7553,
2.6857,
2.6182,
2.5526,
2.4889,
2.4271,
2.3671,
2.3087,
2.2521,
2.197,
2.1435,
2.0916,
2.0411,
1.992,
1.9443,
1.8979,
1.8528,
};
#endif
(上面是写了一个结构体用来存储一个NTC的ADC、电压值、电阻值和温度;下面的长数组是0度~149度所对应的电阻值,如果温度值不够用可以去往上搜索;注意单位为KΩ,记得计算时的转换,后面代码我会写清楚)
4、二分法查表
现在有了表格,也能够自己看出当前的温度是多少了,那我们怎样在STM32的程序中将温度计算出来并展示在屏幕上呢?
使用最多的还是进行二分法查表,他的原理简单来说就是,我们拿到电阻值之后,就把温度表分成两半,循环比对当前阻值是在前半段还是后半段,然后对应好后在进行分段查找(具体步骤写进了函数里,简单一看琢磨一下就能搞懂):
//--------------------------获取温度值(从温度数组表中查找获取)-------------------------
double Get_Temperature(double resistance,float Temp_Resistance_Tab[])
{
uint8_t End = 150 - 1;
uint8_t Front = 0;
uint8_t half = 0;
uint32_t Temp_Resistance = resistance;
Temp_Resistance /= 1000;
float TempValue = 0;
if((Temp_Resistance < Temp_Resistance_Tab[0]) && (Temp_Resistance > Temp_Resistance_Tab[149]))
{
for(half = 74;End - Front != 1;)
{
if(Temp_Resistance < Temp_Resistance_Tab[half]) //如果小于中间值,往后查
{
Front = half;
half = (Front + End) / 2;
}
else if(Temp_Resistance > Temp_Resistance_Tab[half]) //如果大于中间值,往前查
{
End = half;
half = (Front + End) / 2;
}
else
{
TempValue = half; //正好等于表格中的值
break;
}
}
if(End - Front == 1)
{
// //精确到个位数
// if(Temp_Resistance - Temp_Resistance_Tab[End] > Temp_Resistance_Tab[Front] - Temp_Resistance)
// {
// TempValue = half;
//
// }
// else
// {
// TempValue = half + 1;
// }
//精确到小数点后一位
float t = (Temp_Resistance_Tab[Front] - Temp_Resistance) / ((Temp_Resistance_Tab[Front] - Temp_Resistance_Tab[End]) / 10) / 10;
TempValue = half + t;
}
}
else //超出范围
{
if(Temp_Resistance >= Temp_Resistance_Tab[0]) //温度为零下
{
TempValue = 0;
}
else if(Temp_Resistance <= Temp_Resistance_Tab[149]) //温度超过150摄氏度
{
TempValue = 150;
}
}
return TempValue;
}
(以上函数为查表函数,参数为当前的NTC电阻值,和表格的数组名,返回值为精确到小数点后一位的温度值,注释部分为精确到各位数的温度值)
现在所有我们想要的数值都知道了,就可以在OLED屏幕上、电脑上或者其他的什么设备上进行检测温度了。
现在先记录这么多,废话比较多,就希望我们这种初学者能够真正的看懂,后续很快会把测温、加热器的控温代码以及电路和笔记全都整理出来免费发出,一是自己总结自己看,而是有需要的兄弟能够得到帮助(如有讲的不对的地方欢迎大家指出留言)。