NTC热敏电阻是一种负温度系数电阻,随着温度升高阻值会下降。这种特性使其成为温度测量的理想选择,但同时也带来了测量上的挑战。在实际项目中,我经常遇到新手开发者对NTC工作原理理解不透彻导致测量误差的问题。
NTC的阻值-温度关系遵循Steinhart-Hart方程,这个方程描述了半导体材料的电阻与温度之间的非线性关系。不过在实际工程应用中,我们更常用的是查表法,因为直接解这个方程需要复杂的计算,对单片机资源消耗较大。
硬件电路设计上,通常采用分压电路将NTC阻值变化转换为电压变化。我常用的电路是将NTC与一个固定电阻串联,中间节点接入STM32的ADC引脚。这里有个关键点:固定电阻的阻值最好选择与NTC在测量范围内的中间值相近,这样可以获得最佳的电压变化灵敏度。
ADC配置是NTC温度采集的核心环节。以STM32G030C8T6为例,我通常会采用DMA方式采集ADC数据,这样可以减轻CPU负担。在CubeMX中配置时,需要注意以下几个关键参数:
c复制// ADC初始化代码示例
void MX_ADC1_Init(void)
{
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.ScanConvMode = DISABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 1;
hadc1.Init.DMAContinuousRequests = ENABLE;
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
}
ADC采集到的是电压值,需要转换为电阻值,再通过查表得到温度。这里有个常见误区:很多开发者会忽略参考电压的精度问题。在实际项目中,我发现STM32的内部参考电压可能有±5%的偏差,这会直接影响测量精度。
电阻值计算公式如下:
c复制float adc_value = (float)ADC_Value[0] * VREF / 4095.0f;
float ntc_resistance = R_REF * (VREF - adc_value) / adc_value;
查表算法的实现要点:
c复制// 改进后的查表函数
float LookupTemperature(float resistance, const float* table, int size)
{
int low = 0, high = size - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (table[mid] == resistance) {
return (float)mid;
}
if (table[mid] < resistance) {
high = mid - 1;
} else {
low = mid + 1;
}
}
// 线性插值
float ratio = (resistance - table[high]) / (table[low] - table[high]);
return (float)high + ratio;
}
原始数据往往存在噪声,滤波是必不可少的环节。除了常见的均值滤波,我还尝试过以下几种滤波算法:
这里重点介绍我在实际项目中最常用的复合滤波方案:
c复制#define SAMPLE_SIZE 20
#define OUTLIER_THRESHOLD 0.5f
float FilteredTemperature(void)
{
static float samples[SAMPLE_SIZE];
static int index = 0;
float sum = 0, avg, variance = 0;
// 获取新样本
samples[index] = GetTemperature();
index = (index + 1) % SAMPLE_SIZE;
// 计算平均值
for(int i=0; i<SAMPLE_SIZE; i++) {
sum += samples[i];
}
avg = sum / SAMPLE_SIZE;
// 计算方差
for(int i=0; i<SAMPLE_SIZE; i++) {
variance += (samples[i] - avg) * (samples[i] - avg);
}
variance /= SAMPLE_SIZE;
// 剔除异常值后重新计算
if(variance > OUTLIER_THRESHOLD) {
sum = 0;
int count = 0;
for(int i=0; i<SAMPLE_SIZE; i++) {
if(fabs(samples[i] - avg) < OUTLIER_THRESHOLD*2) {
sum += samples[i];
count++;
}
}
if(count > SAMPLE_SIZE/2) {
avg = sum / count;
}
}
return avg;
}
在实际项目中踩过不少坑,这里分享几个典型问题及解决方案:
问题1:温度跳变严重
问题2:低温段测量不准
问题3:长时间运行后数据漂移
问题4:不同批次NTC参数不一致
经过多个项目的积累,我总结出以下几点优化经验:
c复制// 定点数实现示例
#define FIXED_POINT_SHIFT 8
int16_t FixedPointLookup(uint16_t resistance, const uint16_t* table, int size)
{
int low = 0, high = size - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (table[mid] == resistance) {
return mid << FIXED_POINT_SHIFT;
}
if (table[mid] < resistance) {
high = mid - 1;
} else {
low = mid + 1;
}
}
// 线性插值
int32_t ratio = ((int32_t)(resistance - table[high]) << FIXED_POINT_SHIFT) /
(table[low] - table[high]);
return (high << FIXED_POINT_SHIFT) + ratio;
}
最近完成的一个智能温控器项目,要求温度测量精度达到±0.5℃。经过多次试验,最终采用的方案是:
这个方案在-20℃到80℃范围内实现了±0.3℃的测量精度,完全满足项目要求。关键点在于:
在调试过程中发现,NTC的安装方式也会影响测量结果。如果NTC与被测物体接触不良,或者受到空气流动影响,都会导致测量误差。最终我们采用导热硅胶固定NTC,并用泡沫塑料包裹减少空气对流影响。