第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驱动的基础三件套:
- 分压电路:R1选在测温范围中点,精度和温漂要重视。
- 温度计算:Steinhart-Hart方程精度高,查表法速度快,根据场景选。
- ADC采样与滤波:采样时间要够,滤波要到位,参考电压要稳。
下一章,我们会讲PID控制算法的原理和实现。到时候你会发现,今天学的温度测量精度,直接决定了PID控制的效果。所以,这一章的内容一定要吃透。
好了,今天就到这里。有什么问题,欢迎在课程群里交流。我是你们的工程师朋友,咱们下章见。