第1章:NTC热敏电阻驱动——分压电路设计、Steinhart-Hart方程与查表法温度计算、ADC采样与滤波

各位同学,欢迎来到《按摩仪加热模块温控策略实战课程》。

我是你们的老朋友,一个在嵌入式温控领域摸爬滚打了十几年的工程师。今天咱们开始第一讲,也是整个温控系统最基础、最核心的一环——如何让单片机准确地知道当前温度是多少

说白了,你算法再牛,PID调得再花哨,如果温度都测不准,那一切都是白搭。我当年刚入行时就吃过这个亏,一个项目折腾了两个月,最后发现是NTC分压电阻选错了,温度偏差十几度……嗯,从那以后,我对前端采样电路就格外上心。

1.1 分压电路设计:别小看这两个电阻

NTC热敏电阻,全称是负温度系数热敏电阻。温度越高,电阻值越小。这个特性大家应该都清楚。

但单片机不能直接测电阻,它只能测电压。所以我们需要一个分压电路,把电阻的变化转换成电压的变化。

最简单的电路长这样:一个固定电阻R1(也叫上拉电阻或偏置电阻)和NTC串联,中间抽头接到ADC引脚。VCC接在R1另一端,NTC另一端接地。

这里有个关键问题:R1选多大?

我个人习惯,R1的取值要等于NTC在目标测温范围中点时的阻值。比如你的按摩仪工作温度是25℃到45℃,那NTC在35℃时的阻值大约是10kΩ,R1就选10kΩ。为什么?

因为这样分压点电压刚好在VCC的一半左右,ADC的测量分辨率利用率最高。你想想看,如果R1选得太大或太小,电压变化范围会压缩在ADC量程的一小段里,有效分辨率就浪费了。

经验公式:

R1 ≈ R_NTC(T_mid),其中T_mid是目标测温范围的中点温度。

另外,R1的精度要选1%或更高的。我见过有人用5%的普通电阻,结果每个产品都要单独校准,量产时头都大了。R1的温漂也要注意,最好用金属膜电阻,温漂50ppm以内。

避坑指南:

我曾经在一个项目中,为了省成本用了碳膜电阻做分压。结果夏天和冬天的温度读数差了3℃多。后来换成金属膜电阻,问题立刻解决。所以,分压电阻的温漂真的不能忽视。

1.2 Steinhart-Hart方程:精确计算的数学武器

分压电路测出了电压,接下来就是从电压反推温度

NTC的电阻-温度关系不是线性的。早期有人用简单的B值公式近似,但精度有限。真正专业的做法是用Steinhart-Hart方程

这个方程长这样:

1/T = A + B * ln(R) + C * [ln(R)]³

其中T是开尔文温度,R是NTC当前阻值,A、B、C是三个系数,由NTC厂家提供,或者通过三点标定自己算出来。

为什么用这个方程?因为它能在很宽的温度范围内保持高精度,误差通常小于0.1℃。而B值公式在温度范围两端偏差会比较大。

实际使用时,我们先把ADC采样到的电压值换算成NTC的阻值:

R_ntc = R1 * (V_adc / (V_ref - V_adc))

然后代入Steinhart-Hart方程,解出T。

不过要注意,这个方程在单片机里直接算比较费劲,因为有自然对数和三次方运算。如果你的MCU没有硬件浮点单元,计算一次可能要几十毫秒。

我的建议:

如果MCU性能足够,直接用浮点运算最省事。如果性能紧张,可以预先算好一个查找表,运行时用查表加线性插值。下面会讲。

1.3 查表法:又快又准的工程方案

查表法,说白了就是用空间换时间

我们把温度范围和对应的ADC值预先算好,存成一个数组。运行时直接查表,或者查表后做线性插值。

比如,你的测温范围是0℃到100℃,每1℃存一个点,那只需要101个表项。每个表项存一个uint16_t的ADC值,总共才202字节,几乎不占什么资源。

查表法的核心是表怎么生成。我一般用Python或Excel,先根据Steinhart-Hart方程算出每个温度对应的NTC阻值,再根据分压电路算出ADC值,最后生成C语言数组。

代码示例:

// 温度查找表,单位:0.1℃
// ADC参考电压3.3V,12位ADC,R1=10kΩ
const uint16_t temp_table[] = {
    4095,  // 0℃
    4020,  // 1℃
    3940,  // 2℃
    // ... 中间省略 ...
    120,   // 99℃
    100    // 100℃
};

// 查表函数,返回温度值,单位0.1℃
int16_t get_temperature(uint16_t adc_value) {
    // 边界检查
    if (adc_value >= temp_table[0]) return 0;
    if (adc_value <= temp_table[100]) return 1000;
    
    // 二分查找
    uint8_t low = 0, high = 100, mid;
    while (high - low > 1) {
        mid = (low + high) / 2;
        if (temp_table[mid] > adc_value)
            low = mid;
        else
            high = mid;
    }
    
    // 线性插值
    int16_t temp = low * 10;
    temp += (int32_t)(adc_value - temp_table[low]) * 10 / 
            (temp_table[low] - temp_table[high]);
    return temp;
}

这个代码里用了二分查找,最坏情况只需要7次比较就能定位到区间,然后做一次线性插值。速度非常快。

查表法 vs 直接计算:

对比项 Steinhart-Hart直接计算 查表法+插值
计算速度 慢(几十ms) 快(几μs)
精度 高(0.01℃级) 取决于表密度(0.1℃级)
代码量 需要存储表
适用场景 高精度、宽范围 嵌入式实时控制

我个人在按摩仪项目中,用的就是查表法。因为按摩仪的温度范围窄(25℃~45℃),而且对实时性要求高,查表法完全够用。

1.4 ADC采样与滤波:别让噪声毁了你的数据

ADC采样,看起来简单,但坑很多。

首先,采样时间要足够。MCU的ADC内部有个采样电容,需要时间充电。如果采样时间太短,电压还没稳定就开始转换,结果会偏小。

我一般把采样时间设为最大,或者至少10μs以上。具体看MCU手册,别偷懒。

其次,滤波是必须的。NTC信号本身就有噪声,再加上电源纹波、电磁干扰,原始ADC值跳来跳去很正常。

常用的滤波方法有几种:

  • 均值滤波:连续采N次,取平均。简单有效,但会引入延迟。
  • 中值滤波:采3~5次,取中间值。能有效去除毛刺噪声。
  • 一阶低通滤波:y[n] = α * x[n] + (1-α) * y[n-1]。α越小,滤波越强,但响应越慢。
  • 滑动平均滤波:维护一个固定长度的队列,每次采新值就入队,同时出队一个旧值,然后求平均。

我在按摩仪项目里用的是滑动平均滤波,队列长度8。为什么?因为按摩仪的加热速度不快,8次采样带来的延迟完全可以接受,而且滤波效果很好,温度读数非常稳定。

一个小技巧:

ADC采样和加热控制不要同时进行。加热时PWM开关会产生很大的电磁干扰,会影响ADC精度。我习惯在PWM关断的瞬间采样,或者错开采样和加热的时间窗口。

最后,别忘了参考电压的稳定性。如果Vref波动,ADC结果也会跟着波动。我建议用内部基准电压,或者外部高精度基准芯片。如果实在要用VCC做参考,那VCC一定要稳,最好加LDO。

又一个坑:

我曾经在一个产品中,ADC参考电压直接接了电池。结果电池电压从4.2V降到3.3V,温度读数漂了5℃。后来加了颗3.3V的LDO专门给ADC供电,问题解决。所以,ADC的参考电压一定要独立、稳定。

小结

这一章我们讲了NTC驱动的基础三件套:

  1. 分压电路:R1选在测温范围中点,精度和温漂要重视。
  2. 温度计算:Steinhart-Hart方程精度高,查表法速度快,根据场景选。
  3. ADC采样与滤波:采样时间要够,滤波要到位,参考电压要稳。

下一章,我们会讲PID控制算法的原理和实现。到时候你会发现,今天学的温度测量精度,直接决定了PID控制的效果。所以,这一章的内容一定要吃透。

好了,今天就到这里。有什么问题,欢迎在课程群里交流。我是你们的工程师朋友,咱们下章见。