热敏电阻NTC、PTC
Chapter1 热敏电阻NTC、PTC
原文链接:https://blog.csdn.net/qq_54564988/article/details/130693394
前言
NTC、PTC是什么?对于没有接触过NTC、PTC或刚接触过NTC、PTC的人来说,NTC、PTC是什么也不知道,当然,了解NTC、PTC概念也是比较简单的,但是当你去搜索资料,看到许许多多的懵懵懂懂的专业名词时,以及看到一些硬件,可能会出现些许傻眼,毕竟没接触过,满脑问号。对于初学者或急于敢项目的软件工程师,尽快初步了解,学习到基本原理,代码跑出正确的数据就是最好的。毕竟学习是渐进的,不可一蹴而就就能深入其原理。
NTC是负温度系数的简写,全称是Negative temperature coefficient.意思是随着温度的升高,电阻值呈现下降趋势。常用作温度传感器。这里有一个式子表示负温度系数的电阻值:
RT=R0*exp(B (1/T-1/T0))
RT为周围温度为T (K) 时的电阻值,R0是周围温度为T0 (K) 时的电阻值,注意这里的温度是开尔文温度。B为B常数. 请记住这个B常数,它也是材料常数,一般在25摄氏度下测得。B值和电阻的温度系数正相关,也就是B值越大,电阻的温度系数越高。而温度系数是指每增加1℃,电阻值的变化率。也就是说,B值越大,电阻值的变化随着温度的增加越多,灵敏度越高.
一、NTC和PTC是什么?
NTC、PTC都是热敏电阻,是特殊的电阻,可以随着温度的变化阻值,也可以说是一种传感器。
区别是NTC就是负温度系数热敏电阻,PTC就是正温度系数热敏电阻。
正温度系数热敏电阻(PTC):电阻值随着温度的升高而增大;
负温度系数热敏电阻(NTC):电阻值随着温度的升高而减小;
二、NTC和PTC的用途
1.NTC的用途:
用于温度检测,一般是测温型NTC
用于浪涌抑制,一般是功率型的NTC
2.PTC的用途有:
保护电路中,如过温保护,过流保护
启动电路中
三、B值
B值:材料常数,是用来表示NTC在工作温度范围内阻值随温度变化幅度的参数,与材料的成分和烧结工艺有关。B值通常数值(3435K、3950K)。
B值越大表明阻值随温度的升高降低得越快,B值越小则相反。
B值在本文章没有用到,只是为了了解。求温度也可以用温度系数B值求值法,也可以称开尔文温度算法。
四、R25
R25:25℃时NTC本体的电阻值。
五、原理分析
以NTC为例,一般原理图如下:
原理分析:
ADC功能是用来采集电压的。
R1和R2是串联电路,通过串联电阻的分压公式,有:
R=R1+R2;
由I=U/R=U/(R1+R2)则:
U1=IR1=U(R1/(R1+R2))
U2=IR2=U(R2/(R1+R2))
我们用到的则是U2=IR2=U(R2/(R1+R2))这个就行了。
又由ADC采集的数据转换成电压就是U2的电压,所以
U(R2/(R1+R2))=ADC/1024*U
这里1024是我用的单片机的ADC的分辨率为10位,即1024
这里我们知道 U=3.3v 也就是图中的VCC,R1的值是10k,R2是NTC所以暂时不知道它的值。U是可以抵掉的。
最终公式为:R2=ADC*R1/1024-ADC
即R2=ADC*10000/1024-ADC
得到R2的电阻值后我们就可以通过对比阻值表来得到温度了。阻值对照表一般购买后商家都会给。
接下来上代码,这里采用NTC的查表方式进行温度转换。这个代码只要把你的ADC值加上去,就可以用了。
const unsigned int temp_tab[]={
119520,113300,107450,101930,96730,91830,87210,82850,78730,74850,//-30到-21,
71180,67710,64430,61330,58400,55620,53000,50510,48160,45930,//-20到-11,
43810,41810,39910,38110,36400,34770,33230,31770,30380,29050,//-10到-1,
27800,26600,25460,24380,23350,22370,21440,20550,19700,18900,18130,//0-10,
17390,16690,16020,15390,14780,14200,13640,13110,12610,12120,//11-20,
11660,11220,10790,10390,10000,9630,9270,8930,8610,8300,//21-30,
8000,7710,7430,7170,6920,6670,6440,6220,6000,5800,//31-40,
5600,5410,5230,5050,4880,4720,4570,4420,4270,4130,//49-50,
4000,3870,3750,3630,3510,3400,3300,3190,3090,3000,//51-60,
2910,2820,2730,2650,2570,2490,2420,2350,2280,2210,//61-70,
2150,2090,2030,1970,1910,1860,1800,1750,1700,1660,//71-80,
1610,1570,1520,1480,1440,1400,1370,1330,1290,1260,//81-90
1230,1190,1160,1130,1100,1070,1050,1020,990,//91-99,
};
short ADC;//获取ntc的ADC值
short NTC_R;//ntc的电阻值
#define R1 10000
void get_temp()
{
short temp;
short cnt;
ADC= adc_get_value(ADC_CH_0);//获取ADC值
printf("-----------ADC:%d \n\n",ADC);
NTC_R=ADC*R1/(1024-ADC);
cnt = 0;
temp = -30;
do{
if(temp_tab[cnt] < NTC_R){ //表值小于计算出来的电阻值,退出 得出温度
break;
}
++temp;
}while(++cnt < sizeof(temp_tab)/4);//循环表的大小,即次数
printf("NTC_R:%d temp:%d \n\n",NTC_R,temp);
}
Chapter2 手把手教你使用热敏电阻NTC,产品级精度±0.1℃以内,简单明了,内附源码详解,方便移植
原文链接:https://blog.csdn.net/FutureStudio1994/article/details/112189049
一、背景
前一段疫情期间,就考虑到用NTC来做测温功能,写在这里记录自己的成长历程,也分享出去供大家参考!
NTC(Negative Temperature Coefficient)是指随温度上升电阻呈指数关系减小、具有负温度系数的热敏电阻现象和材料。(与此相反的有PTC)
与温度相关的大部分开发均可使用NTC,现在市面上的电子体温计多数用到的就是NTC,额温枪内部热电堆传感器就包含了一个NTC来做采集环境温度的功能,市面上不少智能手环为了降低成本也采用NTC,更不必论温控加热水壶,环境温度检测仪等等。NTC在生活中的应用不胜枚举。那么我们这次就来使用NTC测温度,这里用了黑体恒温水槽来检验精度。实验表明精度可以达到±0.1℃以内。
二、实验原理
本次实验采用了国产的BLE芯片,具体型号这里不公布,看官佬爷只需关心实现原理即可,与实验相关的ADC是10bit精度,无论是使用哪一款MCU都可以参考本例程。
首先必须大胆的明白热敏电阻就是电阻。所以它符合电阻的物理特性。
这里P上接线图:
通过上图,我们可以知道电阻NTC所分得的电压Vntc即为:Vntc = Vcc * ( Rntc / (Rntc + Rm) ),于是有了Vntc去换算出ADCntc是轻而易举的。只是换算的时候需要关注MCU内部ADC的参考电压是多少?
因为我这里使用的是10bit的ADC,该ADC引脚内部参考电压为3.6V,因此ADCntc = ( Vntc / 3.6 ) * 1024。
①:Vntc = Vcc * ( Rntc / (Rntc + Rm) )
②:ADCntc = ( Vntc / 3.6 ) * 1024
这里需要知道的是:ADCntc是ADC引脚采集到的数值,Vcc和Rm是已知量,只有Rntc是未知量。
故由式①②求得Rntc,然后拿着Rntc去查表,查什么表呢?
三、表的制作
这里附上产品规格,这个RT表是开发NTC过程中一定要使用的,问厂家或者客服索要。不同NTC的数据是存在差异的。
通过表格可以知道我选用的NTC的规格:R(37℃)= 30Kohm,也就是讲当温度在37℃的时候,该热敏电阻NTC阻值在30K,故而分压电阻也选为30K,以提高精度。
需要仔细阅读表格发现其中的规律,这里的规律就是
1.温度范围为0 - 60℃;
2.温度从0到 60℃,总共有251个数据,按照次序编一个序号从0到250,该序号将会作为数组的下标;
3.温度范围在[32℃,42℃]内,序号每增加1,温度就增加0.05℃;
温度范围在[0℃,32℃]和[42℃,60℃],序号每增加1,温度就增加1℃;
四、代码的实现
1.制作表格
我们要做的把上面的表格数据复制到编译器中,做成一维数组。
/*************************************************
NTC的R值数据表
表的数值随序号的增加而减小
*************************************************/
#define NTCTABNum 251
static float NTCTAB[NTCTABNum]={
163.3,155.2,147.5,140.3,133.4,127.0,120.9,115.1,109.6,104.4,99.48,94.83,90.42,86.24,82.28,
78.52,74.96,71.57,68.36,65.31,62.41,59.66,57.04,54.56,52.19,49.94,47.80,45.76,43.82,41.98,
40.22,38.54,36.94,36.86,36.79,36.71,36.63,36.56,36.48,36.40,36.32,36.25,36.17,36.10,36.02,
35.94,35.87,35.79,35.72,35.64,35.57,35.49,35.42,35.35,35.27,35.20,35.12,35.05,34.98,34.90,
34.83,34.76,34.69,34.61,34.54,34.47,34.40,34.32,34.25,34.18,34.11,34.04,33.97,33.90,33.83,
33.76,33.68,33.61,33.54,33.48,33.41,33.34,33.27,33.20,33.13,33.06,32.99,32.92,32.85,32.79,
32.72,32.65,32.58,32.51,32.45,32.38,32.31,32.25,32.18,32.11,32.05,31.98,31.91,31.85,31.78,
31.72,31.65,31.59,31.52,31.46,31.39,31.33,31.26,31.20,31.13,31.07,31.00,30.94,30.88,30.81,
30.75,30.69,30.62,30.56,30.50,30.43,30.37,30.31,30.25,30.19,30.12,30.06,30.00,29.94,29.88,
29.82,29.76,29.69,29.63,29.57,29.51,29.45,29.39,29.33,29.27,29.21,29.15,29.09,29.03,28.97,
28.91,28.86,28.80,28.74,28.68,28.62,28.56,28.50,28.45,28.39,28.33,28.27,28.22,28.16,28.10,
28.04,27.99,27.93,27.87,27.82,27.76,27.70,27.65,27.59,27.54,27.48,27.42,27.37,27.31,27.26,
27.20,27.15,27.09,27.04,26.98,26.93,26.87,26.82,26.77,26.71,26.66,26.60,26.55,26.50,26.44,
26.39,26.34,26.28,26.23,26.18,26.13,26.07,26.02,25.97,25.92,25.86,25.81,25.76,25.71,25.66,
25.61,25.55,25.50,25.45,25.40,25.35,25.30,25.25,25.20,25.15,25.10,25.05,25.00,24.95,24.90,
24.85,24.80,24.75,24.70,24.65,24.60,24.55,24.50,23.54,22.63,21.76,20.92,20.12,19.35,18.62,
17.92,17.25,16.61,15.99,15.40,14.84,14.30,13.78,13.28,12.80,12.34};
/*
接下来的处理就是围绕着计算出来的Rntc去查表格。
*/
2.写查表函数
/*================================================================================
*Function Name :LookupTable
*Description :查表函数
*parameter :1.*p :表头,即表的首地址
* 2.tableNum :表格的元素的个数
* 3.data :该变量在这里传入的是当前温度下NTC的阻值
*Return :当前NTC阻值对应在表中的位置
================================================================================*/
//这里提供两种较易理解的查表方法
#if 1
//第一种方法
uint8_t LookupTable(float *p , uint8_t tableNum , float data)
{
uint16_t begin = 0;
uint16_t end = 0;
uint16_t middle = 0;
uint8_t i = 0;
end = tableNum-1;
if(data >= p[begin]) return begin;
else if(data <= p[end]) return end;
while(begin < end)
{
middle = (begin+end)/2;
if(data == p[middle]) break;
if(data < p[middle] && data > p[middle+1]) break;
if(data > p[middle]) end = middle ;
else begin = middle ;
if(i++ > tableNum) break;
}
if(begin > end) return 0;
return middle;
}
#else
//第二种方法
uint8_t LookupTable(float *p,uint8_t tableNum,float data)
{
uint8_t i,index = 0;
for(i=0;i<(tableNum-1);i++)
{
if((data<p[i]) && (data>p[i+1]))
index = i;
}
return index;
}
#endif
3.获取AD值或R值
/*================================================================================
*Function Name :GetADCAverage/GetRkohmAverage
*Description :获取多次采样的平均值
*parameter :无
*Return :平均的AD值
================================================================================*/
/* 这里附上伪代码,只走一个思路,每个parameter都有自己的想法
* Get_Single_ADC_Value(); 是针对不同MCU的ADC单次采集接口函数
*/
float GetADCAverage(void)
{
/*times是样本采样次数
* adc_average 是均值
*/
for(t=0;t<times;t++)
{
temp_val += Get_Single_ADC_Value();
}
adc_average = temp_val/times;
return adc_average;
}
float GetRkohmAverage(void)
{
/*
①:Vntc = Vcc * ( Rntc / (Rntc + Rm) )
②:ADCntc = ( Vntc / 3.6 ) * 1024
由公式①②得出Rntc的表达式,
其中ADCntc = GetADCAverage();
可以求出Rntc,拿着这个值去查表即可!
*/
}
4.获取温度粗值
/*================================================================================
*Function Name :GetRoughTemperature
*Description :由序号转化得出温度粗值
*parameter :serialNum :表的序号值
*Return :roughTemp :温度粗值
================================================================================*/
float GetRoughTemperature(uint8_t serialNum)
{
float roughTemp = 0;
if(serialNum <= 32) roughTemp = serialNum;
else if(serialNum >= 232) roughTemp = serialNum - 190;
else roughTemp = 0.05 * (serialNum - 32) + 32;
/* eg:132-32=100 100*0.05=5 5+32=37 */
return roughTemp;
}
/*该函数是观察RT表的规律得出的*/
5.获取温度精值
/*================================================================================
*Function Name :GetAccuraryTemperature
*Description :由温度粗值得到温度精值
*parameter :readRKohm :读取到的电阻值
*Return :accuraryTemp :温度精值
================================================================================*/
/*== 可以精确计算到±0.1℃ ,例如36.57℃ ==*/
float GetAccuraryTemperature(float readRKohm) //这里的返回值数据是要拿出去显示出来的
{
float t0 = 0;
float temp = 0;
float accuraryTemp = 0;
uint8_t serialNum = 0; //查表得到的 AD值 或 R值 所在的位置
if((readRKohm <= NTCTAB[0]) && (readRKohm > NTCTAB[NTCTABNum-1]))
{
serialNum = LookupTable(NTCTAB,NTCTABNum,readRKohm);
t0 = GetRoughTemperature(serialNum);
/*== 温度范围在32℃ -- 42℃ ==*/
if((readRKohm <= NTCTAB[32]) && (readRKohm > NTCTAB[232]))
temp = 0.05*(readRKohm-NTCTAB[serialNum])/(NTCTAB[serialNum+1]-NTCTAB[serialNum])+t0;
/*== 温度范围在0℃ -- 32℃ 以及 42℃ -- 60℃ ==*/
else
temp = 1*(readRKohm-NTCTAB[serialNum])/(NTCTAB[serialNum+1]-NTCTAB[serialNum])+t0;
}
accuraryTemp = temp;
return accuraryTemp;
}
/****************************************************************
三个点,在坐标上的顺序依次为(X1,Y1),(X,Y),(X2,Y2)
已知(X1,Y1),(X2,Y2),求(X,Y)
两点式:(X-X1)/(Y-Y1) = (X2-X1)/(Y2-Y1)
则:X = [(X2-X1)/(Y2-Y1 )]* (Y-Y1) + X1
由于已知(X1,Y1),(X2,Y2)为相邻两温度点 X2-X1 = 0.05
故:X = [0.05/(Y2-Y1 )]* (Y-Y1) + X1
或者X = 0.05 * (Y-Y1) / (Y2-Y1 ) + X1
其中X对应温度值 Y对应R值 这样可以把精度从RT表上的0.05提高到0.01
下图中的(Xi,Yi)就是这里描述的(X,Y);
****************************************************************/
6.温度数值送显
/*================================================================================
*Function Name :GetDisplayTempValue
*Description :送显的温度数值
*parameter :accuraryTemp :读取到的温度精值
*Return :temp :温度精值*100
================================================================================*/
uint32_t GetDisplayTempValue(float accuraryTemp)
{
uint32_t temp = 0;
temp = GetAccuraryTemperature(accuraryTemp)*100;
return temp;
}
/****************************************************************
作用:我这里是拿着数据显示到OLED屏幕上的,设计上是要显示到小数点后两位的,
eg:36.57℃, 例如:exempli gratia → eg
而采集到的也是小数点后两位,为了方便处理显示函数这里将温度值乘以100,
拿着3657去取整取余分别将每一位显示出来,温度值在上一个函数(第5步)已经实现,这里只是为了送显;
输入参数:float readRKohm这个参变量将代表 GetADCAverage(); 或者 GetRkohmAverage();
****************************************************************/
创建变量TempValue作为求得的目标温度值
TempValue= GetDisplayTempValue(GetAccuraryTemperature(GetRkohmAverage()));
这里调用的是GetRkohmAverage();故而查表的表格是NTC的RT表格;
或者
TempValue = GetDisplayTempValue(GetAccuraryTemperature(GetADCAverage()));
这里调用的是GetADCAverage();故而查表的表格是NTC的ADC表格;
本文中只设计了RT表没有制作对应的AD表,可以用Excel表格将RT表换算得出对应的AD表,原理是一样的。
到此温度值就已经得到了,精度至少可以保证在±0.3以内,为什么这么讲,
我们要明白影响温度精度的要素有什么?
1.Vcc:取决于LDO,可以用四位半以上万用表测试一下电压等;
2.30K:取决于精密电阻,一般选用1‰,不同的NTC配置不同的大小的电阻;
3.NTC:NTC也是电阻,故NTC的精度和表格的精度是有误差的;一般情况下采购是分等级的;
4.MCU:批量生产的时候,可能会发现芯片是有差的,每一个芯片的ADC采集到的数值是不一样的,原因很多,其中影响较大的是芯片设计的时候内部的参考电压是否是稳定的;
5.计算过程中的误差,比如浮点型转整型,求取平均值的时候的误差;
等等诸多因素
其中影响最大的就是第4条,芯片之间的差异;批量生产的时候需要注意,如果存在这个问题那么设计电路的时候也许就要换个思路了,后面会讲如何改变消除这种影响,抛开第四条因素之外(如果你选用的芯片没有第四条这个问题),其他的只要不是很差劲,精度一般可以做到±0.1以内;
五、消除芯片ADC误差的影响,在上述四的基础上进行代码的扩展实现(另一个方法:采用电压基准供电)
要明白一点,这个误差是来自于不同芯片之间内部的参考电压不一致,为了排除掉这个误差,可以用下图的设计,避开掉内部的参考电压对数据结果的影响。
那么根据新的电路设计,使用双ADC,为了方便阅读,我们在这里先约定:
1.AN1引脚的ADC值命名为MaxADC;
2.AN2引脚的ADC值命名为MinADC;
3.精密电阻依然是30K,命名为Rm;
4.NTC的电阻值命名为Rntc,程序中为:RKohmValue 见后面程序;
求取的Rntc = Rm*MinADC/(MaxADC-MinADC);
这样无论是MaxADC还是MinADC都是以该芯片的参考电压为比例得到的数值;分式就抵消掉了这部分的影响,其实仔细思考会发现抵消掉的还有Vcc的影响,毕竟随着电池使用,电量降低,其实LDO出来的数值也是会有影响的,不仅如此,LDO的精度也受制于选择的型号及品牌,不过没关系这部分在这里也将抵消掉!
为了进一步提高精度,要在多次采样之后均值的处理上做一些改变!
为什么要这么做,上图
这里只是我在Excel上随机写了100组数据,实际上我们采集到的ADC的数值离散分布也是如此的,那么我们最好能够摒弃掉上下两个绿色框内的数据,限制幅度,其实样本大了以后会发现,中位值是最接近真值的。
那么我们要怎么处理这些离散数据呢?
获取一定样本的数据,放在一维数组中,对该数值的元素进行从小到大排序,取中间一定数量的元素求和取平均值,但是因为冒泡排序是比较耗费资源的,再求和取平均势必影响出值速度,因此这里我取中位值作为有效值去计算NTC的电阻值!
我将其称为限幅滤波,或者是中位值滤波!
7.滤波
/*======================以下是对数据进行滤波处理============================*/
/*说明:代码中使用了malloc和free,用malloc来申请空间自身是有弊端的,它会将空间分成很多个碎片,
但在本实验中没有太大影响,*/
/*================================================================================
*Function Name :GetMaxADCValue
*Description :获取供电端ADC的数值
*parameter :无
*Return :MaxADCFilterValue
================================================================================*/
float GetMaxADCValue(void)
{
/*== 变量定义 ==*/
float MaxADCFilterValue = 0;
uint32_t *MaxADCArray;//数组首元素的地址
uint32_t i,j,m=0;
uint32_t times = 501;//样本大小
/*== 获得样本数据 ==*/
MaxADCArray = (uint32_t *)malloc(times);
for(m=0;m<times;m++)
{
MaxADCArray[m] = Get_MaxADC_Single_ADC_Value();
}
/*== 样本数据从小到大排列 ==*/
for (j=0;j<times-1;j++)
{
for (i=0;i<times-1-j;i++)
{
if (MaxADCArray[i] > MaxADCArray[i+1])
{
MaxADCArray[i] ^= MaxADCArray[i+1];
MaxADCArray[i+1] ^= MaxADCArray[i];
MaxADCArray[i] ^= MaxADCArray[i+1];
}
}
}
/*== 滤除远离目标值的无效值 ==*/
//这里只取了排序之后的中间的值作为有效值,也就是中位值
MaxADCFilterValue = MaxADCArray[250];
free(MaxADCArray);
return MaxADCFilterValue;
}
/*================================================================================
*Function Name :GetMinADCValue
*Description :获取NTC端ADC的数值
*parameter :无
*Return :MinADCFilterValue
================================================================================*/
float GetMinADCValue(void)
{
/*== 变量定义 ==*/
float MinADCFilterValue = 0;
uint32_t *MinADCArray;//数组首元素的地址
uint32_t i,j,m=0;
uint32_t times = 801; //样本大小
/*== 获得样本数据 ==*/
MinADCArray = (uint32_t *)malloc(times);
for(m=0;m<times;m++)
{
MinADCArray[m] = Get_MinADC_Single_ADC_Value();
}
/*== 样本数据从小到大排列 ==*/
for (j=0;j<times-1;j++)
{
for (i=0;i<times-1-j;i++)
{
if (MinADCArray[i] > MinADCArray[i+1])
{
MinADCArray[i] ^= MinADCArray[i+1];
MinADCArray[i+1] ^= MinADCArray[i];
MinADCArray[i] ^= MinADCArray[i+1];
}
}
}
/*== 滤除远离目标值的无效值 ==*/
//这里只取了排序之后的中间的值作为有效值,也就是中位值
MinADCFilterValue = MinADCArray[400];
free(MinADCArray);
return MinADCFilterValue;
}
8.获取NTC阻值
/*================================================================================
*Function Name :GetRKohmValve
*Description :获取当前温度下NTC阻值
*parameter :无
*Return :NTC的阻值
================================================================================*/
float GetRKohmValve(void)
{
float RKohmValue = 0;
float MaxADC,MinADC = 0;
MaxADC = GetMaxADCValue();
MinADC = GetMinADCValue();
RKohmValue = 30*MinADC/(MaxADC-MinADC);
return RKohmValue;
}
创建变量TempValue作为求得的目标温度值
TempValue= GetDisplayTempValue(GetAccuraryTemperature(GetRKohmValve()));
这里调用的是GetRKohmValve();故而查表的表格是NTC的RT表格;
最后,设计上如果对功耗有要求,在第二种设计的基础上可以用一个单独的IO口作为供电端Vcc,
使用的时候拉高,不用的时候拉低,这样可以降低功耗!
Chapter3 stm32通过NTC采集温度,二分法查表,精度0.1℃
NTC是指负温度系数的电阻器,电阻值会随着温度上升而减少,我们可以利用该特性,对温度进行采集和计算。
下面是NTC的规格和温度阻值表
标称阻值:10kΩ @ 25℃
精度公差:±1%
B值:3435K at 25/85℃
B值公差:±1%
我用的是查表法,所以在计算中不使用B值,B值直接忽略,只关心精度和温度阻值表。
我们先看看电路
STM32的ADC是12位的,我们根据电路图可以得到公式
我们把温度阻值表代入公式,可借助EXCEL计算,可以得到温度对应的ADC值大小,然后定义数组,用于查表计算温度。
我选取的温度范围是-20℃~120℃,生成下列数组。
#ifndef _NTC_H
#define _NTC_H
#include "user_define.h"
#define NTC_HIGH_PRECISION 1//1=0.1℃ 0=1℃
#define NUM 141
//-20℃~120℃
static sc16 NTC3435_10K[NUM]=
{
3581,3559,3535,3511,3486,3461,3435,3408,3381,3353,
3324,3295,3266,3234,3203,3171,3138,3105,3072,3037,
3003, //0
2968,2932,2896,2859,2823,2785,2748,2710,2672,2633,
2595,2556,2517,2478,2438,2399,2360,2321,2281,2242,
2203,2164,2125,2086,2048,2009,1971,1933,1896,1858,
1822,1785,1749,1713,1678,1643,1896,1896,1540,1507,
1475,1436,1411,1380,1349,1319,1289,1260,1231,1203,
1176,1149,1122,1096,1070,1045,1021,997,974,951,
928,906,885,864,843,823,803,784,766,747,
729,712,695,678,662,646,631,616,601,587,
573,559,546,533,520,508,496,484,473,462,
451,440,430,420,410,400,391,382,373,365,
356,348,340,332,325,317,310,303,296,290,
283,277,271,265,259,253,247,242,237,231,
};
extern s16 Read_NTC_Temperature(sc16 *list,u16 rADC,s16 BaseValue);
#endif
二分法查表,可以用循环的方法或者递归函数查找,我采用的是循环的方法,查找出ADC在列表中的位置,就可以知道对应的索引号,进而计算温度值,代码如下:
//输入参数:ADC值表 ADC值
//返回值:查表后的索引号
u16 NTC_Lookup(sc16 *list,u16 data)
{
u16 middle=0;
u16 indexL=0;
u16 indexR=NUM-1;
if(data>=*(list+0))
return 0;
if(data<=*(list+NUM-1))
return NUM-1;
while((indexR-indexL)>1)
{
middle=(indexL+indexR)>>1;
if(data==*(list+middle))
return middle;
else if(data>*(list+middle))
indexR=middle;
else if(data<*(list+middle))
indexL=middle;
}
return indexL;
}
//例如我们采集到的ADC值是3000,经过二分法查找到的索引号是20,我们就可以知道温度在NTC3435_10K[20](0℃)和NTC3435_10K[21](1℃)之间。
二分法查找的值是表中的索引号,我们还需要根据索引号和ADC值进一步计算温度值(精度0.1℃),代码如下:
//输入参数:ADC表 采集的ADC值 ADC表的起始温度值(-20℃=-200)
//返回值:温度值 单位0.1℃ 例如返回值是100,对应的就是100*0.1℃=10℃。
s16 Read_NTC_Temperature(sc16 *list,u16 rADC,s16 BaseValue)
{
u16 index=0;
u16 deta=0;
u16 t=0;
s16 result=0;
if(rADC>=list[0])
return BaseValue;
if(rADC<=*(list+NUM-1))
{
result=((NUM-1)*10+BaseValue);
return result;
}
index=NTC_Lookup(list,rADC);
#if NTC_HIGH_PRECISION
deta=list[index]-list[index+1];
t=10*(list[index]-rADC)/deta;
#endif
result=(BaseValue+index*10+t);
return result;
}
main.c代码
int main()
{
int temp;//
temp=Read_NTC_Temperature(NTC3435_10K,3010,-200);//温度ADC表 ADC值 ADC表的起始温度(-20℃)
printf("ntc = %d\n",temp);
return 0;
}
我输入ADC值3010,输出值为-3,也就是-3*0.1℃=-0.3℃,
我用ADC换算成电阻值再去推算温度也是-0.3℃,算法正确,大家放心使用。
Chapter4 Simulink查表法实现NTC温度计算模型
原文链接:https://blog.csdn.net/weixin_42665184/article/details/134141109
前言
在实际项目中需要对NTC对某些区域进行温度采样和做一些系统层面的保护等等,比如过温降载,过温保护,这时就需要对NTC或者其他的温度传感器进行采样,计算实时温度。而NTC的数据表提供的温度和阻值的对应关系点数太少,计算的温度误差比较大。这里记录下所使用的方法,方便日后回顾。
把NTC数据导入到excel
如下图是开发板上的一个NTC电路。MCU采样NTC_Temp的AD值并进行温度的计算
拟合NTC温度曲线
输入以下代码
Coef1=polyfit(ADC_Table_T,Tem_Table_T,5);
z1=polyval(Coef1,ADC_Table_T);
plot(ADC_Table_T,Tem_Table_T,'r*',ADC_Table_T,z1,'b')
第一句的意思是把ADC数组作为输入,把温度作为输出,拟合成一个5次多项式的形式,并获取其系数。
第二句就是把ADC数组作为输入,用拟合得到的系数方程计算得到温度Z1
第三句就是比较拟合出来的曲线和数组曲线作对比
查表实现温度计算
把计算公式输入到excel里,和之前的数据做对比,这里设置了从-40-125度一共191个点。可根据实际需要进行适当调整,精度越高占用空间越多,合理选择就可以。
Chapter5 【STC32G应用】NTC测温还在用查表法?
原文链接:https://blog.csdn.net/lunzilx/article/details/131888957
前言
测温是单片机经常应用的一项功能,记得早期在学校用DS18B20这种单总线传感器,后面还有温湿度一体的传感器。后面到了一些应用领域,尤其是养殖和种植行业,NTC电阻这种方式还是更多一点。一个是价格相对便宜,再一个应用领域里基本都是分布式,传感器与控制器之间可能间距百米,NTC电阻就更有优势一点。
一、NTC测温原理
NTC其实就是一个随温度变化的电阻,有正温度系数和负温度系数,以前51在做NTC测温的时候,通常会选择查表法来推算温度。查表法也就是把各个温度对应的NTC阻值组成一个数组,单片机计算出NTC阻值后,在数组中进行查找,找到对应的温度值。这种方式的优点是速度快,缺点就是误差比较大,尤其是数组数量越小,误差越大,数组数量多的话,又占用存储空间。
其实NTC还有另外一种测试的方式,就是通过公式进行推算。
NTC电阻有一个参数,B值。B值与NTC电阻的制作材料有关系。
NTC热敏电阻B值公式为:B=T1T2 Ln(RT1/RT2)/(T2-T1)
其中的B:NTC热敏电阻的B值,由厂家提供;RT1、RT2:热敏电阻在温度分别为T1、T2时的电阻值; T1、T2:绝对温标。
之前51查表法用的多,也有一个原因是通过公式来计算效率比较低,但是现在STC32G出来以后,计算量来说就并不是问题了。
二、NTC测温函数
代码如下(示例):
const float Rp=10000.0; //100K
const float T2 = (273.15+25.0);//T2
const float B0 = 3950.0;//B
const float B1 = 3435.0;//B
const float Ka = 273.15;
float Get_Temp(float Rt,float Bx)
{
float temp;
//like this R=5000, T2=273.15+25,B=3470, RT=5000*EXP(3470*(1/T1-1/(273.15+25)),
temp = Rt/Rp;
temp = log(temp);//ln(Rt/Rp)
temp/=Bx;//ln(Rt/Rp)/B
temp+=(1/T2);
temp = 1/(temp);
temp-=Ka;
return temp;
}
总结
通过公式来计算NTC阻值,相对来说更加精确。但是计算前和计算后一般还需要进行一些滤波操作,来让温度值更加平滑。
Chapter6 NTC查表法,采用二分法
原文链接:https://blog.csdn.net/xuechengchang/article/details/89399125
做温控器,传感器采用NTC热敏电阻,前几年做的代码,为了省事方便,直接采用查询方法,从头到尾查询一边,一个200个元素的一维数组,例如NTC_ADC_TAB[200],最多要查询200次!方法很笨!
对于强迫症的工程师来说,这是个打心眼里别扭的事情,就像穿西装,打领带这么别扭!
最近项目不忙,对温控器的代码做了优化重构,顺便把温度查表函数做了优化更改,采用二分法,效率显著提升,废话不多,SHOW THE CODE:
以下代码采用编程平台:IAR FOR STM8
硬件平台:stm8l152
ADC分辨率:12位
已经在产品上验证并批量出货!PS:做一个靠谱的网友!
/*********************** 宏定义 *********************************/
#define NTC_ADC_MAX 201
/**************** NTC热敏电阻转换成ADC的值 ***********************/
const uint16_t NTC_ADC_Tab[NTC_ADC_MAX]={
2559 ,2555 ,2551 ,2546 ,2542 ,2538 ,2534 ,2529 ,2525 ,2521 ,// 25.0~25.9 ℃
2517 ,2513 ,2508 ,2504 ,2500 ,2496 ,2491 ,2487 ,2483 ,2479 ,// 26.0~26.9 ℃
2474 ,2470 ,2466 ,2461 ,2457 ,2453 ,2449 ,2444 ,2440 ,2436 ,// 27.0~27.9 ℃
2432 ,2427 ,2423 ,2419 ,2415 ,2410 ,2406 ,2402 ,2397 ,2393 ,// 28.0~28.9 ℃
2389 ,2385 ,2380 ,2376 ,2372 ,2368 ,2363 ,2359 ,2355 ,2350 ,// 29.0~29.9 ℃
2346 ,2342 ,2338 ,2333 ,2329 ,2325 ,2320 ,2316 ,2312 ,2308 ,// 30.0~30.9 ℃
2303 ,2299 ,2295 ,2291 ,2286 ,2282 ,2278 ,2273 ,2269 ,2265 ,// 31.0~31.9 ℃
2261 ,2256 ,2252 ,2248 ,2243 ,2239 ,2235 ,2231 ,2226 ,2222 ,// 32.0~32.9 ℃
2218 ,2214 ,2209 ,2205 ,2201 ,2196 ,2192 ,2188 ,2184 ,2179 ,// 33.0~33.9 ℃
2175 ,2171 ,2167 ,2162 ,2158 ,2154 ,2150 ,2145 ,2141 ,2137 ,// 34.0~34.9 ℃
2133 ,2128 ,2124 ,2120 ,2116 ,2111 ,2107 ,2103 ,2099 ,2095 ,// 35.0~35.9 ℃
2090 ,2086 ,2082 ,2078 ,2073 ,2069 ,2065 ,2061 ,2057 ,2052 ,// 36.0~36.9 ℃
2048 ,2044 ,2040 ,2036 ,2031 ,2027 ,2023 ,2019 ,2015 ,2010 ,// 37.0~37.9 ℃
2006 ,2002 ,1998 ,1994 ,1990 ,1985 ,1981 ,1977 ,1973 ,1969 ,// 38.0~38.9 ℃
1965 ,1960 ,1956 ,1952 ,1948 ,1944 ,1940 ,1936 ,1932 ,1927 ,// 39.0~39.9 ℃
1923 ,1919 ,1915 ,1911 ,1907 ,1903 ,1899 ,1895 ,1890 ,1886 ,// 40.0~40.9 ℃
1882 ,1878 ,1874 ,1870 ,1866 ,1862 ,1858 ,1854 ,1850 ,1846 ,// 41.0~41.9 ℃
1842 ,1838 ,1833 ,1829 ,1825 ,1821 ,1817 ,1813 ,1809 ,1805 ,// 42.0~42.9 ℃
1801 ,1797 ,1793 ,1789 ,1785 ,1781 ,1777 ,1773 ,1769 ,1765 ,// 43.0~43.9 ℃
1761 ,1757 ,1753 ,1750 ,1746 ,1742 ,1738 ,1734 ,1730 ,1726 ,// 44.0~44.9 ℃
1722 ,// 45 ℃
};
/**
* @brief 二分法查表法,得出单路温度值
* @param pos
* @retval TempSingleValue
*/
uint16_t get_single_temp(uint8_t pos)
{
uint16_t End = NTC_ADC_MAX - 1;// 数组下标最后一个数
uint16_t Front = 0;// 数组第一个数
uint8_t half = 0;
uint16_t TempSingleADC = 0;// 单次转换的ADC值
uint16_t TempSingleValue = 0;// 单次换算成的温度值,用来函数返回值;
ADC_Channel_TypeDef ADC_Channel_X = ADC_Channel_0;
switch(pos)
{
case 0:
ADC_Channel_X = ADC_Channel_0;
break;
case 1:
ADC_Channel_X = ADC_Channel_1;
break;
case 2:
ADC_Channel_X = ADC_Channel_2;
break;
default :
break;
}
/* Enable ADC1 clock */
CLK_PeripheralClockConfig(CLK_Peripheral_ADC1, ENABLE);
/* Initialize and configure ADC1 */
ADC_Init(ADC1, ADC_ConversionMode_Continuous, ADC_Resolution_12Bit, ADC_Prescaler_2);
/* ADC channel used for IDD measurement */
ADC_SamplingTimeConfig(ADC1, ADC_Group_FastChannels, ADC_SamplingTime_192Cycles);
/* Enable ADC1 */
ADC_Cmd(ADC1, ENABLE);
Delay(500);
/* Disable SchmittTrigger for ADC_Channel, to save power */
ADC_SchmittTriggerConfig(ADC1, ADC_Channel_X, DISABLE);
/* Enable ADC1 Channel used for LAD measurement */
ADC_ChannelCmd(ADC1, ADC_Channel_X, ENABLE);
Delay(500);
ADC_SoftwareStartConv (ADC1);//开启软件转换
while(!ADC_GetFlagStatus (ADC1,ADC_FLAG_EOC));//等待转换结束
ADC_ClearFlag (ADC1,ADC_FLAG_EOC);//清除相关标识
TempSingleADC = ADC_GetConversionValue (ADC1);//获取转换值;
//TempSingleADC = 1722;// 调试二分法查表用的临时赋值,调试代码用;
/* DeInitialize ADC1 */
ADC_DeInit(ADC1);
/* Disable ADC1 clock */
CLK_PeripheralClockConfig(CLK_Peripheral_ADC1, DISABLE);
ADC_ChannelCmd(ADC1, ADC_Channel_X, DISABLE);
/**********二分法查表求温度值*********/
if((TempSingleADC <= NTC_ADC_Tab[0])&&(TempSingleADC >= NTC_ADC_Tab[NTC_ADC_MAX-1]))
{
for(half=100;End-Front != 1;)
{
if(TempSingleADC > NTC_ADC_Tab[half])
{
End = half;
half = (End + Front)/2;
}
else if(TempSingleADC < NTC_ADC_Tab[half])
{
Front = half;
half = (Front + End)/2;
}
else// 正好等于表格中的值
{
TempSingleValue = 2500+half*10+(NTC_ADC_Tab[half]-TempSingleADC)*10/(NTC_ADC_Tab[half]-NTC_ADC_Tab[half+1]);
break;
}
}
if(End-Front == 1)// 在表格两个值之间的数
{
TempSingleValue = 2500+half*10+(NTC_ADC_Tab[half]-TempSingleADC)*10/(NTC_ADC_Tab[half]-NTC_ADC_Tab[half+1]);
}
}
else
{
TempSingleValue = 0; // 温度超出数组范围,就返回0度
}
return TempSingleValue;
}
Chapter7 STM32 ADC NTC热敏电阻二分(折半)查表法实现测温功能
原文链接:https://blog.csdn.net/lnniyunlong99/article/details/105802834
本文主要描述 - STM32 ADC NTC热敏电阻二分(折半)查表法测温功能的思路和代码实现
NTC的相关属性:R25=10K±3% B25/50=4100K±3% 10K上拉
STM32 ADC实现NTC测温的电路示意图如下:
STM32的ADC分辨率为12位,模数转换的范围 0~ 4095(0x000~0xFFF)
针对以上描述的NTC属性以及电路,对应的温度和测量的数字量的关系表:
static const uint16 R10K_TAB[] = { //R25=10K±3% B25/50=4100K±3% 10K上拉
3738,3719,3698,3677,3655,3631,3607,3582,3556,3530, //-20℃ ... -11℃
3502,3473,3443,3412,3381,3348,3314,3280,3244,3208, //-10℃ ... -1℃
3170,3132,3093,3053,3012,2970,2928,2885,2842,2797, // 0℃ ... 9℃
2752,2707,2661,2615,2568,2522,2474,2427,2379,2332, // 10℃ ... 19℃
2284,2237,2189,2142,2095,2048,2001,1954,1908,1863, // 20℃ ... 29℃
1818,1773,1729,1685,1642,1600,1558,1517,1477,1437, // 30℃ ... 39℃
1398,1360,1323,1286,1250,1215,1180,1147,1114,1082, // 40℃ ... 49℃
1051,1020, 990, 961, 933, 905, 878, 852, 827, 802, // 50℃ ... 59℃
778, 755, 732, 710, 688, 668, 647, 628, 609, 590, // 60℃ ... 69℃
572, 555, 538, 522, 506, 491, 476, 461, 447, 434, // 70℃ ... 79℃
421, 408, 395, 384, 372, 361, 350, 340, 330, 320, // 80℃ ... 89℃
311, 302, 293, 284, 276, 268, 261, 253, 246, 239, // 90℃ ... 99℃
233, 226, 220, 214, 209, 203 //100℃ ... 105℃
};
将数据表的数据放到Excel中查看曲线的形状
从图中可以看出,NTC的温度与数字量的关系比较接近一元线性关系,数字量越大,温度值越低,负相关。那么将两度之间的小数度数几乎可以认为就是线性的,下面介绍计算小数精度的方法按照线性处理。
在数据表中给出的数据,是整数温度对应的数字量,本次实验测量的精度为0.1℃,测量的数字量值很可能是在两个整数度中间,如ADC采样的数字量为 0x80C,十进制是2060,对应在数据表的2048(25℃)和2095(24℃)中间,计算方式按照线性处理如下:
关于二分(折半)查找的方法,肯定是要比逐次对比效率要高很多,至于效率高多少就不分析了,这里我按照二分查找的思路来进行。由于采样的ADC数字量可能在表中查找不到,但是可以查找出在哪个范围内,就如我上述举例的情况,采样是 2060,在2048和2095之间,至于怎么实现二分查找,参考一下下面的代码。
函数方法实现的思路,在第一图电路示意图中如果没有接NTC传感器,相当于是开路,ADC采样的值相当于电源电压;如果将NTC的两个端子用导线短接,ADC采样的值相当于GND;考虑到极限的情况温度低于-20℃,采样的数字量值不在数据表范围内,即大于3738;同样,如果实际测量温度高于105度,采样的数字量值不在数据表范围内,即小于203;其他的情况就属于正常可以查表的数字量。开路测量电源电压时,可能会稍低于0XFFF,我这里留出一些余量0X00F;短路测GND的电压数字量时,可能会稍微高于0X000,也留出余量0X00F。
用图来表示
下面是具体的代码实现:(代码中小数部分,通过*10的倍率来表示,即247代表24.7℃)
z_hardware_adc.c
#ifndef __Z_HARDWARE_ADC_H
#define __Z_HARDWARE_ADC_H
#include "z_hardware_adc.h"
#endif
#define SHORT_CIRCUIT_THRESHOLD 15
#define OPEN_CIRCUIT_THRESHOLD 4080
static const u16 R10K_TAB[] = { //R25=10K±3% B25/50=4100K±3% 10K??
3738,3719,3698,3677,3655,3631,3607,3582,3556,3530, //-20? ... -11?
3502,3473,3443,3412,3381,3348,3314,3280,3244,3208, //-10? ... -1?
3170,3132,3093,3053,3012,2970,2928,2885,2842,2797, // 0? ... 9?
2752,2707,2661,2615,2568,2522,2474,2427,2379,2332, // 10? ... 19?
2284,2237,2189,2142,2095,2048,2001,1954,1908,1863, // 20? ... 29?
1818,1773,1729,1685,1642,1600,1558,1517,1477,1437, // 30? ... 39?
1398,1360,1323,1286,1250,1215,1180,1147,1114,1082, // 40? ... 49?
1051,1020, 990, 961, 933, 905, 878, 852, 827, 802, // 50? ... 59?
778, 755, 732, 710, 688, 668, 647, 628, 609, 590, // 60? ... 69?
572, 555, 538, 522, 506, 491, 476, 461, 447, 434, // 70? ... 79?
421, 408, 395, 384, 372, 361, 350, 340, 330, 320, // 80? ... 89?
311, 302, 293, 284, 276, 268, 261, 253, 246, 239, // 90? ... 99?
233, 226, 220, 214, 209, 203 //100? ... 105?
};
void init_adc(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
ErrorStatus HSEStartUpStatus;
RCC_DeInit();
RCC_HSEConfig(RCC_HSE_ON);
HSEStartUpStatus = RCC_WaitForHSEStartUp();
if(HSEStartUpStatus == SUCCESS)
{
RCC_HCLKConfig(RCC_SYSCLK_Div1);
RCC_PCLK2Config(RCC_HCLK_Div1);
RCC_PCLK1Config(RCC_HCLK_Div2);
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
RCC_PLLCmd(ENABLE);
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
while(RCC_GetSYSCLKSource() != 0x08);
}
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;//PC4
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_DeInit(ADC1);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
u16 func_get_adc_valve_ch7(void)
{
ADC_RegularChannelConfig(ADC1, ADC_Channel_7, 1, ADC_SampleTime_55Cycles5);
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
return ADC_GetConversionValue(ADC1);
}
u8 func_get_ntc_temp(u16 value_adc, s16* value_temp)
{
u8 index_l, index_r;
u8 r10k_tab_size = 126;
s32 temp = 0;
if(value_adc <= SHORT_CIRCUIT_THRESHOLD)
{
return 1;
}
else if(value_adc >= OPEN_CIRCUIT_THRESHOLD)
{
return 2;
}
else if(value_adc > R10K_TAB[0])
{
return 3;
}
else if(value_adc < R10K_TAB[r10k_tab_size - 1])
{
return 4;
}
index_l = 0;
index_r = r10k_tab_size - 1;
for(;index_r - index_l > 1;)
{
if((value_adc <= R10K_TAB[index_l]) && (value_adc > R10K_TAB[(index_r + index_l)%2 == 0 ? (index_r + index_l)/2 : (index_r + index_l)/2 + 1]))
{
index_r = (index_r + index_l) % 2 == 0 ? (index_r + index_l)/2 : (index_r + index_l)/2 + 1;
}
else
{
index_l = (index_r + index_l)/2;
}
}
if(R10K_TAB[index_l] == value_adc)
{
temp = (((s16)index_l) - 20)*10;//rate *10
}
else if(R10K_TAB[index_r] == value_adc)
{
temp = (((s16)index_r) - 20)*10;//rate *10
}
else
{
if(R10K_TAB[index_l] - R10K_TAB[index_r] == 0)
{
temp = (((s16)index_l) - 20)*10;//rate *10
}
else
{
temp = (((s16)index_l) - 20)*10 + ((R10K_TAB[index_l] - value_adc)*100 + 5)/10/(R10K_TAB[index_l] - R10K_TAB[index_r]);
}
}
*value_temp = temp;
return 0;
}
z_hardware_adc.h
#ifndef __STM32F10X_H
#define __STM32F10X_H
#include "stm32f10x.h"
#endif
void init_adc(void);
u16 func_get_adc_valve_ch7(void);
u8 func_get_ntc_temp(u16 value_adc, s16* value_temp);
测试的主函数代码:
#ifndef __STM32F10X_H
#define __STM32F10X_H
#include "stm32f10x.h"
#endif
#ifndef __Z_UTIL_TIME_H
#define __Z_UTIL_TIME_H
#include "z_util_time.h"
#endif
#ifndef __Z_HARDWARE_USART2_H
#define __Z_HARDWARE_USART2_H
#include "z_hardware_usart2.h"
#endif
#ifndef __Z_HARDWARE_ADC_H
#define __Z_HARDWARE_ADC_H
#include "z_hardware_adc.h"
#endif
#ifndef __Z_HARDWARE_LED_H
#define __Z_HARDWARE_LED_H
#include "z_hardware_led.h"
#endif
int main()
{
u8 buf[8];
u16 val;
s16 value_temp;
u8 res;
init_adc();
init_hardware_usart2_dma(9600);
init_led();
val = func_get_adc_valve_ch7();
for(;;)
{
val = func_get_adc_valve_ch7();
res = func_get_ntc_temp(val, &value_temp);
if(res == 0)
{
buf[0] = value_temp >> 8;
buf[1] = value_temp;
func_usart2_dma_send_bytes(buf, 2);
}
func_led1_on();
delay_ms(1000);
func_led1_off();
delay_ms(1000);
}
}
测试的效果,通过串口将16进制值打印出来如下:
Chapter8 个人实验,R25=10k, B3950,公式直接计算法(也可以采用二分查找法)
开发板:正点原子或者神舟号STM32F4开发板,PA5引脚作为ADC输入,VREF=VCC=3.3V
NTC: R25=10k, B3950
计算公式:
测试结果:
代码:
/**
****************************************************************************************************
* @file main.c
* @author 正点原子团队(ALIENTEK)
* @version V1.0
* @date 2021-10-18
* @brief 单通道ADC采集 实验
* @license Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
****************************************************************************************************
* @attention
*
* 实验平台:正点原子 探索者 F407开发板
* 在线视频:www.yuanzige.com
* 技术论坛:www.openedv.com
* 公司网址:www.alientek.com
* 购买地址:openedv.taobao.com
*
****************************************************************************************************
*/
#include "stdio.h"
#include "math.h"
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/ADC/adc.h"
uint16_t adcx;
float temp;
float Rntc = 10000.0;
const float Rp=10000.0; //10K
const float T2 = (273.15+25.0);//T2
const float B0 = 3950.0;//B
const float B1 = 3435.0;//B
const float Ka = 273.15;
float Get_Temp(float Rt,float Bx)
{
float temp;
//like this R=5000, T2=273.15+25,B=3470, RT=5000*EXP(3470*(1/T1-1/(273.15+25)),
temp = Rt/Rp;
temp = log(temp);//ln(Rt/Rp)
temp/=Bx;//ln(Rt/Rp)/B
temp+=(1/T2);
temp = 1/(temp);
temp-=Ka;
return temp;
}
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
delay_init(168); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
adc_init(); /* 初始化ADC */
while (1)
{
adcx = adc_get_result_average(ADC_ADCX_CHY, 10); /* 获取通道5的转换值,10次取平均 */
Rntc = (float)10000.0*adcx/(4096-adcx);
temp = Get_Temp(Rntc, B0);
printf("ADC:%d, Rntc:%f, temp:%.2f\n", adcx, Rntc, temp);
LED0_TOGGLE();
delay_ms(500);
}
}