传感器_高精度热敏电阻测量温度算法_有序浮点型数据使用二分法查询最接近的值
引言
最近项目上做了一个利用热敏电阻测量温度的功能。热敏电阻的阻值可以通过ADC采集电压测量得到。NTC 热敏电阻的阻值和温度之间有一个关系。
R
t
=
R
n
⋅
e
B
(
1
T
−
1
T
n
)
{R_t} = {R_n} \cdot {e^{B\left( {\frac{1}{T} - \frac{1}{{{T_n}}}} \right)}}
Rt=Rn⋅eB(T1−Tn1)
T = B ⋅ T n T n ⋅ ln ( R t R n ) + B − 273.15 T = \frac{{B \cdot {T_n}}}{{{T_n} \cdot \ln \left( {\frac{{{R_t}}}{{{R_n}}}} \right) + B}} - 273.15 T=Tn⋅ln(RnRt)+BB⋅Tn−273.15
T n {T_n} Tn 和 R n {R_n} Rn是标称温度和标称阻值, 比如 T n = 273.15 + 25 {T_n} = 273.15 + 25 Tn=273.15+25开氏度,对应阻值是 R n = 10 k Ω {R_n} = 10k\Omega Rn=10kΩ 。每种型号的NTC热敏电阻应该都会给出 B B B参数,很典型参数配置就是 B = 3950 B=3950 B=3950, R n = 10 k Ω {R_n} = 10k\Omega Rn=10kΩ , T n = 273.15 + 25 {T_n} = 273.15 + 25 Tn=273.15+25开氏度。 那对于上述公式,已知热敏电阻阻值 R t {R_t} Rt就能求解温度 T T T了。
下面给出3点我不用上面公式的原因:
1、计算公式中取对数的操作,调用数学库可能效率并不高
2、甚至在某些环境下根本无法调用数学库
3、同时,公式计算数据未必准确
4、作者可能就是任性,他就是不用公式计算,他非要另辟蹊径(开个小玩笑)。
因此,本文使用另一种高效的方式来实现根据热敏电阻阻值测量温度的方法。从后文中读者还可以看到,本文提到的数据
正文
本算法的思路是,我在程序中先存一个温度阻值对应表,然后根据测量得到的
R
t
R_t
Rt,去查询表中与
R
t
R_t
Rt最接近的那个阻值,然后就能给出对应阻值下的温度值。下表是某热敏电阻规格书给出的温度和标称阻值的对应表。
但存在一个问题,假设考虑的温度范围是 [ − 50 , 50 ] [-50,50] [−50,50]。那么程序中表的长度就是 101 101 101。如果每次得到Rt都要去遍历这个表,那么最多就会遍历 101 101 101次,这反而违背了我们要提升程序效率的初衷。
由于温度阻值表是个有序序列,因此本文考虑采用二分法去寻找最接近的阻值。 假设我们的测量温度范围有 2 7 = 128 2^7 = 128 27=128个温度,那么二分查找法最多也就会递归 7 7 7次。
不过在实现二分查找算法之前,先使用MatLAB用插值方法把精度为 ± 5 ℃ ±5℃ ±5℃的温度阻值表,扩充到 ± 1 ℃ ±1℃ ±1℃。(主要是为了把代码记下来,如果下回再用到我好用)
% 热敏电阻型号:NCP15XH103F03RC
clear all;
close all;
clc;
T = [
-40
-35
-30
-25
-20
-15
-10
-5
0
5
10
15
20
25
30
35
40
45
50
55
60
65
70
75
80
85 ];
R = [
197.390
149.390
114.340
88.381
68.915
54.166
42.889
34.196
27.445
22.165
18.010
14.720
12.099
10.000
8.309
6.939
5.824
4.911
4.160
3.539
3.024
2.593
2.233
1.929
1.673
1.455 ];
% 插值数据
T1 = -40:1:85;
R1 = interp1(T,R,T1,'spline'); % ▲ 👈看这里
% 标准公式计算数据
B = 3380; % 我们用那个热敏电阻型号是B值是3380
Rn = 10;
Tn = 298.15;
T3 = T1+273.15;
T3 = T3';
R3 = Rn * exp(B*((1./T3) - (1/Tn)));
% ▲ 👆 看你这个[1./T3],很关键
figure(1);
plot(T, R,'B-^','LineWidth',2.0); hold on;grid on;
plot(T1, R1,'r:','LineWidth',2.0); hold on;
plot(T1, R3,'k-','LineWidth',2.0); hold on;
legend('原始数据','插值后数据','公式计算的数据');
xlabel('温度/℃');
ylabel('阻值/kΩ');
title('温度-阻值曲线');
para_path = 'NCP15XH103F03RC温敏电阻特性.txt';
fp = fopen(para_path, 'w');
for i = 1:126/5
fprintf(fp,'\t%f,\t%f,\t%f,\t%f,\t%f,\t\\ \r',R1((i-1)*5+1),R1((i-1)*5+2),R1((i-1)*5+3),R1((i-1)*5+4),R1((i-1)*5+5));
end
fprintf(fp,'\t%f \n',R1(126));
fclose(fp);
open('NCP15XH103F03RC温敏电阻特性.txt');
下图给出一个运行结果
原本的数据,温度的步长是5℃,比如-20℃,-15℃,没有中间的数据,而图中的-17℃则是通过插值的方式得到的。
**注意:**黑色的线就是使用引言给出的标准公式计算得到,低温的差别还是蛮大的。不过如果你应用场景:1、对测量精度真的没有什么要求;2、能调用数学库使用公式计算。你也可以用公式计算。
下面给出 基于c语言的 有序浮点型数据使用二分法查询最接近的值方法
#include "user_adc.h"
float temperature = 0;
float Vtemp = 0;// 温敏电阻ADC电压
float Rtemp = 0; // 温敏电阻阻值
float Rpullup = 33; // 温敏电阻上拉阻值
#define Max_R_TEMP 197.390000
#define Min_R_TEMP 1.455000
// -40℃ ~ +85℃
float RtempList[126] = {
197.390000, 186.554967, 176.370622, 166.803794, 157.821311, \
149.390000, 141.476689, 134.048206, 127.071378, 120.513033, \
114.340000, 108.521406, 103.035579, 97.863150, 92.984747, \
88.381000, 84.033424, 79.927085, 76.047933, 72.381921, \
68.915000, 65.633849, 62.528057, 59.587940, 56.803816, \
54.166000, 51.665260, 49.294168, 47.045745, 44.913015, \
42.889000, 40.967047, 39.141800, 37.408231, 35.761307, \
34.196000, 32.707497, 31.291863, 29.945380, 28.664332, \
27.445000, 26.283836, 25.177963, 24.124672, 23.121254, \
22.165000, 21.253310, 20.384020, 19.555075, 18.764420, \
18.010000, 17.289844, 16.602317, 15.945868, 15.318946, \
14.720000, 14.147546, 13.600369, 13.077318, 12.577245, \
12.099000, 11.641475, 11.203721, 10.784830, 10.383892, \
10.000000, 9.632282, 9.280011, 8.942500, 8.619059, \
8.309000, 8.011663, 7.726506, 7.453018, 7.190686, \
6.939000, 6.697460, 6.465622, 6.243053, 6.029323, \
5.824000, 5.626673, 5.437015, 5.254721, 5.079485, \
4.911000, 4.748968, 4.593117, 4.443182, 4.298898, \
4.160000, 4.026239, 3.897430, 3.773400, 3.653981, \
3.539000, 3.428282, 3.321628, 3.218833, 3.119692, \
3.024000, 2.931576, 2.842330, 2.756196, 2.673108, \
2.593000, 2.515791, 2.441341, 2.369496, 2.300101, \
2.233000, 2.168060, 2.105225, 2.044460, 1.985730, \
1.929000, 1.874226, 1.821328, 1.770217, 1.720804, \
1.673000, 1.626716, 1.581863, 1.538352, 1.496094, \
1.455000 };
// ▲ 👇看这里
int binaryfind(float arr[], int min, int max, float key)
{
int mid = ( min + max ) / 2; //数组中间值的下标
float diff = 0;
float diff1 = 0;
float diff2 = 0;
if(key > Max_R_TEMP || key < Min_R_TEMP){
return -1;
}
if( key <= arr[mid] && key >= arr[mid+1] ){ // 递归停止条件
// 注意:我们的数组的降序的
diff1 = key - arr[mid];
if(diff1<0) diff1 = -diff1;
diff2 = key - arr[mid+1];
if(diff2<0) diff2 = -diff2;
if(diff1 < diff2){
return mid;
}else{
return mid+1;
}
}else{
if(key > arr[mid]){ // 如果key在数组左边部分,就继续调用binary函数,并且把max 移位到mid - 1处
return binaryfind(arr,min, mid,key);
}
if(key < arr[mid]){ //如果key在数组右边部分,继续调用binary函数,并且把min移位到mid + 1处{
return binaryfind(arr,mid, max,key);
}
}
return -1;
}
int temperature_cnt = 0;
float temperature_temp1 = 0.0;
float temperature_temp2 = 0.0;
// ADC 温度单次采样模式 配置
void get_temperature_adc(void)
{
int TempIndex;
Adc_CfgSglChannel(AdcExInputCH16);
Adc_SGL_Start();
ADC_FLAG = ADC_WAITING;
while(ADC_FLAG == ADC_WAITING){
if(TRUE == Adc_GetIrqStatus(AdcMskIrqSgl))
{
Adc_ClrIrqStatus(AdcMskIrqSgl); // 清除中断标志位
u32AdcRestultTemperature = Adc_GetSglResult();
Vtemp = (float)u32AdcRestultTemperature*0.0006103515625;
Rtemp = Rpullup*Vtemp / (3.3-Vtemp);
TempIndex = binaryfind(RtempList, 0, 125, Rtemp); // ▲ 👈看这里
if(TempIndex == -1){
temperature_temp1 = 1000.0;
}else{
temperature_temp1 = (float)TempIndex - 40; // ▲ 👈看这里
}
temperature_temp2 += temperature_temp1*0.1;
temperature_cnt++;
if(temperature_cnt == 10){
temperature_cnt = 0;
temperature = temperature_temp2;
temperature_temp2 = 0;
// 温度测量异常判断
if(temperature >= 200){
temperature_error_flag = 1;
}else{
temperature_error_flag = 0;
}
}
// temperature = u32AdcRestultTemperature ;
ADC_FLAG = ADC_COMPLETED;
Adc_SGL_Stop(); // ADC 单次转换停止
}
}
}
感谢您的阅读,欢迎留言讨论、收藏、点赞、分享。