我需要编写一个函数,它使用一个查找表来测量温度传感器模拟输入的ADC值,并通过“插值”-线性近似找到给定ADC值的温度。
我已经创建了一个函数并为它编写了一些测试用例,我想知道你们是否有什么可以改进代码的建议,因为这应该是一个嵌入式uc,可能是stm32。
我正在张贴我的代码,并附上我的C文件,它将编译和运行。
如果您有任何改进意见/建议,请告诉我。
我还想了解一下从uint32到float的转换,如果这是一种有效的编码方式的话。
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#define TEMP_ADC_TABLE_SIZE 15
typedef struct
{
int8_t temp;
uint16_t ADC;
}Temp_ADC_t;
const Temp_ADC_t temp_ADC[TEMP_ADC_TABLE_SIZE] =
{
{-40,880}, {-30,750},
{-20,680}, {-10,595},
{0,500}, {10,450},
{20,410}, {30,396},
{40,390}, {50,386},
{60,375}, {70,360},
{80,340}, {90,325},
{100,310}
};
// This function finds the indices between which the input reading lies.
// It uses an algorithm that doesn't need to loop through all the values in the
// table but instead it keeps dividing the table in two half until it finds
// the indices between which the value is or the exact index.
//
// index_low, index_high, are set to the indices if a value is between sample
// points, otherwise if there is an exact match then index_mid is set.
//
// Returns 0 on error, 1 if indices found, 2 if exact index is found.
uint8_t find_indices(uint16_t ADC_reading,
const Temp_ADC_t table[],
int8_t dir,
uint16_t* index_low,
uint16_t* index_high,
uint16_t* index_mid,
uint16_t table_size)
{
uint8_t found = 0;
uint16_t mid, low, high;
low = 0;
high = table_size - 1;
if((table != NULL) && (table_size > 0) && (index_low != NULL) &&
(index_mid != NULL) && (index_high != NULL))
{
while(found == 0)
{
mid = (low + high) / 2;
if(table[mid].ADC == ADC_reading)
{
// exact match
found = 2;
}
else if(table[mid].ADC < ADC_reading)
{
if(table[mid + dir].ADC == ADC_reading)
{
// exact match
found = 2;
mid = mid + dir;
}
else if(table[mid + dir].ADC > ADC_reading)
{
// found the two indices
found = 1;
low = (dir == 1)? mid : (mid + dir);
high = (dir == 1)? (mid + dir) : mid;
}
else if(table[mid + dir].ADC < ADC_reading)
{
low = (dir == 1)? (mid + dir) : low;
high = (dir == 1) ? high : (mid + dir);
}
}
else if(table[mid].ADC > ADC_reading)
{
if(table[mid - dir].ADC == ADC_reading)
{
// exact match
found = 2;
mid = mid - dir;
}
else if(table[mid - dir].ADC < ADC_reading)
{
// found the two indices
found = 1;
low = (dir == 1)? (mid - dir) : mid;
high = (dir == 1)? mid : (mid - dir);
}
else if(table[mid - dir].ADC > ADC_reading)
{
low = (dir == 1)? low : (mid - dir);
high = (dir == 1) ? (mid - dir) : high;
}
}
}
*index_low = low;
*index_high = high;
*index_mid = mid;
}
return found;
}
// This function uses the lookup table provided as an input argument to find the
// temperature for a ADC value using linear approximation.
//
// Temperature value is set using the temp pointer.
//
// Return 0 if an error occured, 1 if an approximate result is calculate, 2
// if the sample value match is found.
uint8_t lookup_temp(uint16_t ADC_reading, const Temp_ADC_t table[],
uint16_t table_size ,int8_t* temp)
{
uint16_t mid, low, high;
int8_t dir;
uint8_t return_code = 1;
float gradient, offset;
low = 0;
high = table_size - 1;
if((table != NULL) && (temp != NULL) && (table_size > 0))
{
// Check if ADC_reading is out of bound and find if values are
// increasing or decreasing along the table.
if(table[low].ADC < table[high].ADC)
{
if(table[low].ADC > ADC_reading)
{
return_code = 0;
}
else if(table[high].ADC < ADC_reading)
{
return_code = 0;
}
dir = 1;
}
else
{
if(table[low].ADC < ADC_reading)
{
return_code = 0;
}
else if(table[high].ADC > ADC_reading)
{
return_code = 0;
}
dir = -1;
}
}
else
{
return_code = 0;
}
// determine the temperature by interpolating
if(return_code > 0)
{
return_code = find_indices(ADC_reading, table, dir, &low, &high, &mid,
table_size);
if(return_code == 2)
{
*temp = table[mid].temp;
}
else if(return_code == 1)
{
gradient = ((float)(table[high].temp - table[low].temp)) /
((float)(table[high].ADC - table[low].ADC));
offset = (float)table[low].temp - gradient * table[low].ADC;
*temp = (int8_t)(gradient * ADC_reading + offset);
}
}
return return_code;
}
int main(int argc, char *argv[])
{
int8_t temp = 0;
uint8_t x = 0;
uint16_t u = 0;
uint8_t return_code = 0;
uint8_t i;
//Print Table
printf("Lookup Table:\n");
for(i = 0; i < TEMP_ADC_TABLE_SIZE; i++)
{
printf("%d,%d\n", temp_ADC[i].temp, temp_ADC[i].ADC);
}
// Test case 1
printf("Test case 1: Find the temperature for ADC Reading of 317\n");
printf("Temperature should be 95 Return Code should be 1\n");
return_code = lookup_temp(317, temp_ADC, TEMP_ADC_TABLE_SIZE, &temp);
printf("Temperature: %d C\n", temp);
printf("Return code: %d\n\n", return_code);
// Test case 2
printf("Test case 2: Find the temperature for ADC Reading of 595 (sample value)\n");
printf("Temperature should be -10, Return Code should be 2\n");
return_code = lookup_temp(595, temp_ADC, TEMP_ADC_TABLE_SIZE, &temp);
printf("Temperature: %d C\n", temp);
printf("Return code: %d\n\n", return_code);
// Test case 3
printf("Test case 3: Find the temperature for ADC Reading of 900 (out of bound - lower)\n");
printf("Return Code should be 0\n");
return_code = lookup_temp(900, temp_ADC, TEMP_ADC_TABLE_SIZE, &temp);
printf("Return code: %d\n\n", return_code);
// Test case 4
printf("Test case 4: Find the temperature for ADC Reading of 300 (out of bound - Upper)\n");
printf("Return Code should be 0\n");
return_code = lookup_temp(300, temp_ADC, TEMP_ADC_TABLE_SIZE, &temp);
printf("Return code: %d\n\n", return_code);
// Test case 5
printf("Test case 5: NULL pointer (Table pointer) handling\n");
printf("Return Code should be 0\n");
return_code = lookup_temp(595, NULL, TEMP_ADC_TABLE_SIZE, &temp);
printf("Return code: %d\n\n", return_code);
// Test case 6
printf("Test case 6: NULL pointer (temperature result pointer) handling\n");
printf("Return Code should be 0\n");
return_code = lookup_temp(595, temp_ADC, TEMP_ADC_TABLE_SIZE, NULL);
printf("Return code: %d\n", return_code);
// Test case 7
printf("Test case 7: Find the temperature for ADC Reading of 620\n");
printf("Temperature should be -14 Return Code should be 1\n");
return_code = lookup_temp(630, temp_ADC, TEMP_ADC_TABLE_SIZE, &temp);
printf("Temperature: %d C\n", temp);
printf("Return code: %d\n\n", return_code);
// Test case 8
printf("Test case 8: Find the temperature for ADC Reading of 880 (First table element test)\n");
printf("Temperature should be -40 Return Code should be 2\n");
return_code = lookup_temp(880, temp_ADC, TEMP_ADC_TABLE_SIZE, &temp);
printf("Temperature: %d C\n", temp);
printf("Return code: %d\n\n", return_code);
// Test case 9
printf("Test case 9: Find the temperature for ADC Reading of 310 (Last table element test)\n");
printf("Temperature should be 100 Return Code should be 2\n");
return_code = lookup_temp(310, temp_ADC, TEMP_ADC_TABLE_SIZE, &temp);
printf("Temperature: %d C\n", temp);
printf("Return code: %d\n\n", return_code);
printf("Press ENTER to continue...\n");
getchar();
return 0;
}
最佳答案
我通常离线计算查找表,运行时代码可以归结为:
温度=表[dac_值];
特别是如果要嵌入,你不需要浮点,往往不需要它。预先计算表也解决了这个问题。
预计算也解决了有一个高效算法的问题,你可以像你想的那样草率和缓慢,你只需要很少做这个计算。没有任何算法能够在运行时与查找表竞争。只要你有足够的空间去看桌子,这是双赢的。例如,如果8位DAC的PROM中没有256个位置,则可能有128个位置,并且可以进行一些实时插值:
//todo为max dac_value和max dac_value-1添加特殊情况,或使表129项变深
if(dac_值&1)
{
温度=(表[(DAC值>>1)+0]+表[(DAC值>>1)+1])>>1;
}
其他的
{
温度=表[DAC_值>>1];
}
我经常发现被喂进去的桌子会变的。你的计算可能是一成不变的,但这种计算是用经过校准的设备来实现的。您已经做了正确的事情,检查数据是否在正确的一般方向上(相对于dac增加而减少,或者相对于dac值增加而增加),更重要的是检查除以零。尽管是硬编码表,但要养成习惯,期望它会变为不同的硬编码表,希望不必每次都更改插值代码。
我也相信原始的dac值是这里最重要的值,计算出的温度可以随时发生。即使转换成某种味道的度数是用石头做的,显示或存储原始DAC值和计算的温度也是一个好主意。您始终可以从DAC值重新计算温度,但不能始终从计算值准确地再现原始DAC值。这取决于你的建筑自然,如果这是一个公共使用的恒温器在他们的家里,他们不想有一些十六进制值的显示器。但是,如果这是任何一种测试或工程环境,您在其中收集数据,以便以后分析或验证某个产品是好是坏,随身携带DAC值可能是一件好事。如果提供给您表格的工程师声称是最终表格,然后更改表格,则只需要一两次。现在,您必须返回到使用不正确表的所有日志,使用上一个表计算回DAC值,并使用新表重新计算临时值,然后编写一个新的日志文件。如果您在那里有原始的dac值,并且每个人都接受了dac值方面的培训,并且温度只是一个参考,那么您可能不必为每个新的校准表修复旧的日志值。最坏的情况是日志文件中只有温度,无法确定该日志文件使用了哪个cal表,日志文件无效,被测单元成为风险项等。