资讯动态

NTC10K温度计全链路设计与误差控制

发布时间:2026/10/26 21:21:18 来源:尧图企业网站定制
简介面向嵌入式入门与电子测量学习者的 NTC10K 温度计实践资料围绕负温度系数热敏电阻的阻值—温度换算展开适用于单片机 A/D 采样、分压电路与温度显示类小项目参考。压缩包共 17 个文件约 71KB以 C 源文件、头文件、HEX 固件、Keil 工程及编译中间文件为主既包含可烧录验证的固件也保留工程配置与构建记录便于了解 STC12 系列单片机的开发组织方式。目前已有 2066 人学习说明其在 NTC 测温入门场景中具有一定参考价值。包内代码涉及 A/D 转换初始化、采样电压到电阻值的换算、Steinhart-Hart 公式或查表求温度、串口发送等关键环节并配有带隙基准、LED 显示等相关头文件可帮助读者梳理从传感器采样到温度输出的完整实现思路适合对照学习热敏电阻非线性补偿与嵌入式程序结构。1. NTC10K温度计一颗几毛钱的电阻凭什么撑起整条测温链路NTC10K温度计这个词写在采购单上是“10K热敏电阻”落在原理图上是一个分压节点到了固件里就是几行 log 运算再到产线上就成了一份一致性报告。它便宜、免冷端补偿、外围只要一颗电阻从热水器、储能柜、充电桩到恒温培养箱凡是 0100℃ 这一段需要测温度的地方基本都能看到它。但它也是最容易被“能出数就行”糊弄过去的器件同一套电路有人能做到 ±0.1℃有人换一批料就偏 1.5℃还找不到原因。差别不在电阻本身而在于有没有把 B 值公差、上拉电阻取值、参考电压漂移、自热这几个参数当成一个整体去算。下面按选型、电路、算法、固件、排查、标定的顺序把这条链路一段段拆开讲。2. 分压电阻与 B 值把 NTC10K 温度计的误差预算算在第几档NTC 的难点从来不是“接上去读电压”而是“读出来的电压对应多少度、这个数能信到什么程度”。一颗标称 10K25℃、B 值 3950 的热敏电阻实际 B 值可能在 39103990 之间浮动R25 也可能在 ±1% 以内偏移。这两个公差都不大可它们对应的温度误差曲线完全不重叠弄不清楚的话后面做多少滤波、多少校准都是白费。2.1 R25 和 B 值公差分别落在误差预算的哪一格先把最常用的 Beta 方程摆出来它是后面一切计算的地基R(T) R25 · exp( B · (1/T − 1/T0) )其中 T0 298.15K25℃T 用开尔文。反解过来就是固件里真正跑的那一行T 1 / ( 1/T0 ln(R/R25) / B ) − 273.15这个式子里R25 是曲线在 25℃ 的锚点B 是曲线的弯曲程度。两者的作用区间不一样R25 偏差会让整条曲线在纵轴上平移全温区都有误差B 偏差在 T T0 处恰好被 (1/T − 1/T0) 0 消掉25℃ 附近完全看不出来温度越往两端走误差越大。这一点非常关键它直接决定了标定策略。把具体数字代进去假设器件实际 R25 比标称高 1%、或者实际 B 比标称高 1%固件仍按标称值算得到的误差是这样分布的器件偏差0℃ 处误差25℃ 处误差50℃ 处误差100℃ 处误差R25 偏 1%−0.20℃−0.22℃−0.26℃−0.39℃B 偏 1%−0.23℃0.00℃0.27℃0.91℃看完这张表前面说的“普通做法”就有了依据。只在室温下用一支标准温度计对一次把偏移量扣掉这一刀砍掉的是 R25 误差B 值误差原封不动留在那里。等到设备跑到 90℃ 的工况读数偏 0.81℃再回头查就非常难查因为室温下它一切正常。所以 B 值不在 25℃ 标定必须另找一个远离 25℃ 的点通常取 0℃ 附近或 80℃ 以上把两个点都拉进来才能同时定出 R25 和 B。一个容易被忽略的细节是B 值本身也是温度的函数厂家给的 3950 通常是 25/50℃ 两点之间的平均值或者 25/85℃ 的平均值。如果主测温区在 0℃ 以下用 25/50℃ 的 B 值外推会明显偏。稳妥做法是向供应商索取 B25/50 和 B25/85 两个值或者直接索取 R-T 分度表用表反拟合。要不到表就自己标定。提示模数转换器满量程利用率低于 30% 时先别急着怀疑热敏电阻多半是上拉电阻选错了温区或者参考电压被别的负载拉动了。2.2 上拉电阻取 10K 还是 4.7K灵敏度峰值出现在 R1 Rntc 的温区分压电路输出电压 Vout Vref · Rntc / (Rntc R1)对 Rntc 求导得到 dVout/dRntc Vref · R1 / (Rntc R1)²这个导数在 R1 Rntc 时取到最大值。换句话说上拉电阻取多大就等于你选定了哪个温度点分辨率最高R1 10K 时峰值在 25℃R1 4.7K 时峰值大约在 40℃R1 3.3K 时峰值在 50℃ 出头。25℃ 附近热敏电阻的相对灵敏度是 −B/T² ≈ −4.45%/℃折算成绝对量是 −445Ω/℃。再乘上分压的传递系数就能算出每摄氏度对应多少毫伏上拉 R1−20℃ 输出0℃ 输出25℃ 输出100℃ 输出25℃ 灵敏度4.7K3.159V2.895V2.245V0.427V31.9 mV/℃10K3.014V2.543V1.650V0.215V36.7 mV/℃20K2.774V2.068V1.100V0.111V32.6 mV/℃表里的电压按 Vref 3.3V 计算。对比三行能看出两件事第一10K 上拉在 25℃ 的灵敏度最高但 −20℃ 时输出已经顶到 3.014V离电源轨只剩 0.29V冷端稍微再低一点模数转换器就饱和了第二20K 上拉把 25℃ 的电压压到 1.1V冷端宽裕了可 100℃ 只剩 0.111V12 位转换器在 3.3V 满量程下每个 LSB 是 0.806mV换算过去只剩 0.3 个码值高温段的分辨率几乎全丢。12 位、Vref3.3V 时0.806mV/LSB 配合 36.7mV/℃ 的灵敏度理论分辨率是 0.022℃。这是个理论上限实际受噪声、抖动、量化误差影响能做到 0.050.1℃ 就已经是很干净的设计。反过来说如果实测分辨率只有 0.5℃说明信号链上游就已经损失掉了精度靠软件平滑无法补救。选型的判断顺序我一般是这样走先定主测温区。冷链、室温、人体、电池包取 050℃10K 上拉。主测温区上移到 60110℃功率器件、电机绕组、出水口上拉降到 3.3K4.7K把峰值挪到高温端。需要覆盖 −30℃ 的冷端上拉升到 20K47K或者干脆换 100K25℃ 的型号让整条曲线在满量程中间摊开。温区跨度超过 120℃ 又要全段 0.1℃别用一颗热敏电阻硬扛分成两档量程、或者在高温段换成另一种敏感元件。还有一条常被忽略的约束上拉电阻的精度直接进误差预算。1% 的电阻已经算及格0.1% 的薄膜电阻25ppm/℃才配得上 0.1℃ 的设计目标。用普通 5% 碳膜电阻光它的公差就能贡献 0.3℃ 以上的偏差后面怎么调都不对。2.3 自热误差0.27mW 到底把读数抬高了多少热敏电阻靠自身发热来测量电阻值因此只要给电流它就会热起来读数也随之偏离真实温度。误差大小等于功耗除以耗散系数ΔT P / D。环氧封装的小珠在静止空气里 D 通常在 25 mW/℃ 之间浸在水里或贴了导热胶能到 10 mW/℃ 以上。按 3 mW/℃ 估算供电与拓扑25℃ 时电流自身功耗静止空气 ΔT水中/灌封 ΔT3.3V 10K 上拉0.165 mA0.27 mW0.09℃0.03℃5V 10K 上拉0.250 mA0.63 mW0.21℃0.06℃5V 4.7K 上拉0.338 mA1.14 mW0.38℃0.11℃3.3V 系统配 10K 上拉0.09℃ 的自热误差在这种精度下完全可以接受这也是它成为默认搭配的原因之一。但换成 5V 供电配 4.7K 上拉就接近 0.4℃这个量级已经和 B 值公差带来的误差同样大了再叠加起来整套设计就不再满足 0.1℃ 的要求。需要更低保真度的场合常见做法是间歇激励采样前 1ms 把上拉电阻的供电打开转换完成后立刻拉低占空比做到 5% 以下自热误差就按比例降到 0.005℃ 量级。代价是需要一个能耐受开关的驱动脚以及注意上电后 RC 建立时间要算进采样窗口。占空比压得太低采样窗口短了RC 没建立完反而引入新的误差我一般把建立时间取到 5 倍时间常数以上再回头核一次总误差。3. 从 ADC 码值到摄氏度Beta 方程、三系数拟合与查表插值拿到电压或码值之后怎么变成摄氏度是第二个分岔口。三条路各有各的适用场合浮点 Beta 方程写起来最快三系数拟合精度最高但需要标定查表加插值最省算力、在只有整数运算能力的小单片机上最稳。3.1 Beta 方程够不够用先算清楚它在你这个温区留了多少残差Beta 方程只有一个参数 B它假设 ln(R) 和 1/T 是严格的线性关系。真实器件的这条线是微微弯曲的所以 Beta 方程在标定点附近最准离得越远残差越大。下面这段脚本可以直接跑用三个标准点拟出三系数模型再和 Beta 方程对比残差import math R25, B, T0 10000.0, 3950.0, 298.15 def r_of_t(t_c, r25R25, bB): 由温度反算电阻用来构造验证数据 t t_c 273.15 return r25 * math.exp(b * (1.0 / t - 1.0 / T0)) def t_of_r_beta(r, r25R25, bB): Beta 方程反解温度 return 1.0 / (1.0 / T0 math.log(r / r25) / b) - 273.15 def solve_sh(points): 三点标定解 Steinhart-Hart 三系数 A B*lnR C*(lnR)^3 import numpy as np m np.array([[1.0, math.log(r), math.log(r) ** 3] for r, _ in points]) y np.array([1.0 / (t 273.15) for _, t in points]) return np.linalg.solve(m, y) # 返回 (A, B, C) def t_of_r_sh(r, coef): a, b, c coef ln math.log(r) return 1.0 / (a b * ln c * ln ** 3) - 273.15 # 用 0℃ / 25℃ / 100℃ 三个点做标定 cal [(r_of_t(0), 0.0), (r_of_t(25), 25.0), (r_of_t(100), 100.0)] coef solve_sh(cal) for t_true in (-20, 0, 10, 25, 40, 60, 80, 100): r r_of_t(t_true) e_beta t_of_r_beta(r) - t_true e_sh t_of_r_sh(r, coef) - t_true print(f{t_true:6.1f}℃ Beta残差 {e_beta:.3f} SH残差 {e_sh:.3f})这段脚本的逻辑是先用一个已知的 R-T 关系造出“真实器件”再用两种模型去反解温度差值就是模型残差。三系数模型在三个标定点上的残差恒为 0中间段的残差取决于真实曲线偏离模型的程度Beta 方程只有一个自由度被 25℃ 一点吃掉之后剩下的误差会全部堆到温区两端。实际跑下来Beta 方程的残差在 100℃ 附近常常是零点几度量级而三系数模型能压到 0.05℃ 以内。参数上有两点要注意。一是拟合用的点要覆盖你真正要用的温区用 −40/25/125 三个点拟出来的系数在 0100℃ 段未必最优。二是三系数模型在 ln(R) 的立方项上很敏感如果标定点本身有 0.2℃ 的测量误差外推到温区外可能反而比 Beta 方程差所以三系数标定必须用足够准的标准源用冰水浴和沸水自己凑出来的两个点还不如老老实实用 Beta 方程配一个实测 B 值。3.2 查表加插值1℃ 步长的表能把误差压到多少在没有硬件浮点、甚至没有 32 位乘法器的平台上每次采样跑一遍 log 是奢侈的。常见做法是预先把码值到温度的映射做成表运行时只做一次整数除法。表的构造有两种索引方式。按温度等间隔建表、反查码值查表时要二分按码值等间隔建表、直接索引查表是 O(1)。后者更适合固件因为采样频率高、响应时间要求严。import math R1, R25, B, T0 10000.0, 10000.0, 3950.0, 298.15 VREF, ADC_MAX 3.3, 4095 def code_of_t(t_c): 给定温度算出 12 位 ADC 的理论码值 t t_c 273.15 r R25 * math.exp(B * (1.0 / t - 1.0 / T0)) v VREF * r / (r R1) return round(v / VREF * ADC_MAX) # 比率法VREF 约掉 # 以 1℃ 为步长从 -40℃ 建到 125℃表项为「温度 × 100」的整数 table [] for t10 in range(-400, 1251, 10): # 步进 0.1℃ 生成再抽样成 1℃ pass lut [(code_of_t(t), t * 100) for t in range(-40, 126)] # 实际固件里只保留 code 一列按码值升序排列即可 lut.sort(keylambda x: x[0]) print(lut[:3], ..., lut[-3:])生成的表逆序排列温度越高热敏电阻越小码值越小。固件里做一次线性插值找到 code 落在哪两个表项之间用 (code − c0) / (c1 − c0) 的比例在两个温度值之间做线性插值全程整数运算。1℃ 步长的表在 25℃ 附近两点之间的非线性残差通常在 0.02℃ 以内跑到温区两端因为曲线曲率变大残差会涨到 0.05℃ 左右。如果要求更高把步长缩到 0.5℃ 或者对插值结果加一个二次修正项。表的大小也要算一笔账。−40125℃ 按 1℃ 步长是 166 项每项一个 16 位码值加一个 16 位温度值一共 664 字节对绝大多数单片机都不算什么。但如果按 0.1℃ 步长建表就变成 6640 字节小容量芯片上要掂量一下。折中方案是按 1℃ 建表加插值插值带来的那点误差比 ADC 本身的噪声小一个量级没必要为它浪费 Flash。3.3 定点实现没有浮点也能算到 0.01℃现场经常遇到只有整数指令的 8 位机硬件乘法都靠软件模拟这时候 Beta 方程要换成定点近似。思路是把 ln(R/R25) 拆成“整数部分查表 小数部分多项式”。R/R25 落在 1/1616 之间时取二进制最高位的位置作为整数部分 k尾数 m ∈ [1, 2)ln(m) 用一段 5 阶多项式近似在 [1,2) 上误差可以做到 1e-5 以内对应温度误差远小于 0.01℃。/* Q15 定点ln(m)m 为 Q15 格式、范围 [1.0, 2.0) */ static int32_t ln_q15(uint16_t m_q15) { /* 5 阶 minimax 多项式系数按 Q15 缩放实际使用时需按平台重新拟合 */ const int32_t c1 30103, c2 -14427, c3 9612, c4 -7211, c5 5780; int32_t x (int32_t)m_q15 - 32768; /* 映射到 [0, 1)Q15 */ int32_t x2 (x * x) 15; int32_t x3 (x2 * x) 15; int32_t x4 (x3 * x) 15; int32_t x5 (x4 * x) 15; return (c1 * x 15) (c2 * x2 15) (c3 * x3 15) (c4 * x4 15) (c5 * x5 15); }这段代码只解决了 ln 的尾数部分整数部分 k 直接来自前导零计数两者相加得到完整的 ln(R/R25)乘上 ln2 的 Q15 常数 22713。系数不是通用常数换平台、换编译器都要用最小二乘重新拟一遍再用一组已知点回验误差超过 2 个 LSB 就别用了。参数上最关键的是 Q 格式的选择中间量用 Q15乘法结果右移 15 位要保证中间结果不溢出 int32x2 * x这一步的最大值约为 32768³必须用 int64 承接再截断这一点在 8 位机上尤其容易翻车。走定点这条路的收益不只是省算力。浮点库会带来几 KB 的代码膨胀和不确定的执行时间而定点插值 查表是固定周期中断里跑也不会抖动。代价是维护成本系数一改所有回归测试都得重跑一遍。4. 固件链路比率法采样、时序安排与断线检测从电路进到固件最容易出错的地方往往不是算法而是采样。同样的电路和同样的公式采样时机差一点读数就会周期性跳变参考电压没处理好整批板子在不同电源下读数不一致。这一层做扎实了后面的滤波和标定才有意义。4.1 比率法让参考电压的漂移不出现在结果里模数转换器的输出本质上是 Vin / Vref × 满量程。如果分压电路的上端和转换器的参考端接的是同一个电源那么 Vref 在分子分母里各出现一次直接约掉code 4095 × [Vref·Rntc/(RntcR1)] / Vref 4095 × Rntc / (Rntc R1)也就是说电源从 3.30V 漂到 3.15V码值一点不变。这就是比率法它的前提是“同源”而不是“稳源”。很多设计花大力气加基准芯片却没注意分压电路还挂在另一个电源上那就白费了。/* 比率法ADC 参考 分压电源Vref 自动约掉 * code 为 12 位转换结果返回值 Q12 定点比例 Rntc/(RntcR1) */ #define ADC_MAX 4095u #define R1_OHM 10000u uint16_t ntc_ratio_q12(uint16_t code) { if (code ADC_MAX) return 0xFFFu; /* 贴顶视为开路 */ return (uint16_t)(((uint32_t)code 12) / ADC_MAX); } /* 由比例反算电阻Q12 中间量最后右移回欧姆 */ uint32_t ntc_resistance_ohm(uint16_t ratio_q12) { uint32_t den 4096u - ratio_q12; if (den 8u) return 0xFFFFFFFFu; /* 分母过小判为短路 */ return (uint32_t)(((uint64_t)R1_OHM * ratio_q12) / den); }这里的关键是den的保护判断。比例接近 4096 时说明 NTC 阻值远大于上拉电阻对应极低温度或者干脆就是断线分母趋零时整数除法会直接溢出。设一个 8/4096 的下限既挡住了异常又不影响 −40℃ 的正常量程−40℃ 时 Rntc ≈ 197K比例约 0.95den ≈ 200远大于 8。如果转换器用的是内部基准比如 1.2V 带隙和分压电源不同源就必须额外占用一路通道去测分压电源本身再在两路结果之间做除法。这会引入两路采样不同步的问题采样间隔内的电源波动消不掉所以能同源就不要走这条路。4.2 采样时序工频抑制、过采样与中值滤波的先后顺序热敏电阻的信号带宽极低热时间常数动辄几秒到几十秒但它受到的干扰却是高频的。工频 50Hz 及其谐波通过电源和地耦合进来表现为读数上的周期性跳动。处理方法是在模拟侧做 RC 低通在数字侧让采样窗口覆盖整数个工频周期。具体做法转换时间设为 20ms 的整数分之一或者用 Σ-Δ 型转换器直接把输出速率设到 20ms/周期50Hz或 16.67ms60Hz。软件过采样取 16 次累加再右移 4 位等效于多出 2 位分辨率代价是响应时间变成原来的 16 倍。对于热时间常数 10s 的探头增加 300ms 完全无所谓。滤波的顺序有讲究我一般按“先中值、后平均”来/* 5 点中值 8 点滑动平均两级串联 * 中值先干掉偶发的尖峰静电、继电器动作平均再压随机噪声 */ #define MED_N 5 #define AVG_N 8 static uint16_t med_buf[MED_N]; static uint32_t avg_sum; static uint16_t avg_buf[AVG_N]; static uint8_t med_idx, avg_idx; uint16_t ntc_filter(uint16_t raw) { /* 第一级中值 */ med_buf[med_idx] raw; med_idx (med_idx 1) % MED_N; uint16_t tmp[MED_N]; for (uint8_t i 0; i MED_N; i) tmp[i] med_buf[i]; for (uint8_t i 1; i MED_N; i) { /* 插入排序N 小够用 */ uint16_t k tmp[i]; int8_t j i - 1; while (j 0 tmp[j] k) { tmp[j 1] tmp[j]; j--; } tmp[j 1] k; } uint16_t med tmp[MED_N / 2]; /* 第二级滑动平均 */ avg_sum - avg_buf[avg_idx]; avg_buf[avg_idx] med; avg_sum med; avg_idx (avg_idx 1) % AVG_N; return (uint16_t)(avg_sum / AVG_N); }中值放在前面是因为尖峰一旦进入平均值需要 8 个采样周期才能完全排出去而中值一个周期就能剔掉。滤波深度也不是越大越好AVG_N 8 配合 20ms 采样等效时间常数约 160ms相对于探头本身的热惯性可以忽略但如果采样周期是 1s8 点平均就带来 8s 的滞后控制系统里会明显感到“温度追不上”。还有一条容易漏的上电后前 N 个采样必须丢弃。滤波缓冲区初值是 0前几轮输出会被拉低控制系统如果在上电瞬间读到这个值可能触发误保护。4.3 断线与短路贴轨读数怎么和真实低温区分开热敏电阻断线时分压节点被上拉电阻拉到电源轨码值接近 4095看起来和 −40℃ 的读数一模一样。短路时码值接近 0看起来又像 150℃。如果只按温度做上下限判断断线会被当成本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑