电子负载的NTC读数为何突然跳了一度
把查表里的 25°C 节点换成 10kΩ,返回 25.0°C;阻值只减少 0.1Ω,原插值表达式却算出 26.1°C。
这组数字来自固定源码的计算回放,尚未在板子上测温。它说明一个容易被滤波掩盖的问题——表节点可以全对,节点之间仍可能出现明显跳变。加大平均次数,修不好区间起点写错的一度。

图1 旧列只隔离回放原区间表达式。原完整函数另有末端未返回的问题,不能据此断言整机一定显示这些数值。
这次沿着一块电子负载的测温链路,查清阻值单位、插值公式和错误返回,再给出能直接编译运行的单文件修正版。这里解决的是“阻值进来后,查表怎样得到一致的温度”,而不是重新做整台大功率负载。
先沿测温链路找到真正执行的代码
电子负载把输入电能消耗在功率器件上。温度数值一旦被用于界面告警或后续控制,换算中的跳变就会变成阈值判断的噪声。原理图和软件必须对到同一路输入,不能看到一个 NTC 图标就套公式。

图2 同一在线 V3.1 工程的 PCB 顶、底层铜箔与丝印,均在嘉立创 EDA 重新导出。底层保持顶视坐标,未作镜像。本文没有改 PCB,也不凭布局图评价整机可承受功率。
POWER 页上,R35 是接地的 4.7kΩ 电阻;R36、R37 并联后接到 VCC_AMP。分压节点经 R43 送入 U12 同相端,U12 输出回接反相端,输出网络叫 A_TEMP。

图3 同一真实 EDA 母图的测温部分分段放大,连线及原标注保持不变。图上的“3Ω”不足以确认 NTC 型号或标称值,不能把它当作已核实的复刻物料。
网表确认 A_TEMP 接 U14 的 17 脚,MCU 页标为 PA3。软件中,ADC 被配置为 12 位右对齐;ADC_CHANNEL_3 排在普通序列第二项,界面用的是 adc_avg_arr[1]。mdk_v5 工程确实包含 bsp/app.c 和 lv_user/lv_main_page.c,因此这不是从仓库里挑出的闲置示例函数。

图4 PA3、17 脚和 A_TEMP 对应关系。局部图作保真放大;没有补画引脚或改写原始标签。
界面先把 ADC 换算为阻值,再将阻值乘以 10 送给 ntc_temp_get。返回值以 0.1°C 为单位,随后用于温度文本和蓝、橙、红颜色切换。这个调用位置实现的是显示逻辑,不能从颜色变化推断已有温度停载保护。
先把表里的单位对齐
原数组有 191 项,对应 -40°C 到 150°C,每项相隔 1°C。为把数据存进 uint16_t,源码按索引分段缩放,读取时再恢复量级。
| 索引 | 解码规则 | 对应温区 |
|---|---|---|
| 0~65 | 原数组值 ×100 | -40~25°C |
| 66~128 | 原数组值 ×10 | 26~88°C |
| 129~190 | 原数组值 | 89~150°C |
恢复后的阻值单位是 0.1Ω。25°C 的数组值 1000,经解码得到 100000,即 10000Ω;26°C 的值 9574,经解码为 95740,即 9574Ω。别把解码后的 100000 当成 100kΩ。
修正版先把三段量级全部展开为同一单位的 uint32_t 表。191 项占 764 字节,比原 uint16_t 数组多 382 字节;换来的是查表路径不再带着三个缩放分支。是否放进目标芯片 Flash,要看实际链接配置,不能仅凭 const 关键字推断。
多出来的一度藏在区间起点
设当前区间的低温端为 Tᵢ,对应阻值 Rᵢ;下一项温度高 1°C,阻值 Rᵢ₊₁ 更小。区间内的线性插值应从 Tᵢ 起步。
T10 = (i - 40) × 10 + 10 × (Rᵢ - R) / (Rᵢ - Rᵢ₊₁)
原区间表达式却把起点写成 (i - 39) ×10,先多走了 1°C,再加区间里的小数部分。精确命中节点的分支仍使用 (i - 40) ×10,于是节点值和区间值不在同一个起点上。
以 25~26°C 为例,中点阻值是 9787Ω。原表达式返回 26.5°C,正确的线性插值是 25.5°C。再靠近 26°C 节点时,旧表达式可算到 27.0°C;一旦正好命中 9574Ω,又回到 26.0°C。

图5 由 C 回归程序实际输出的 CSV 绘图。横轴向右阻值减小;橙线是旧区间表达式的隔离回放,青线是修正模块。阶梯来自 0.1°C 整数量化,不是采样波形。
还有一个比偏移更基础的问题。原 ntc_tab_get 的最后分支只覆盖 i<190,偏偏 ntc_temp_get 开头就读取索引 190。这个索引没有返回值,不能假定它会自动返回 0,也不能拿某次编译器恰巧产生的值当作有效温度边界。本次提取该函数做静态编译,MSVC 给出了 C4715“不是所有控制路径都返回值”警告。没有执行这个存在未定义返回行为的完整旧函数。
让节点、区间和边界遵守同一套规则
新模块只接收已经确认单位的等效阻值,先检查输入范围,再在相邻两项之间插值。节点也走相同区间规则,只有最后一个节点单独收尾,避免访问表外项。
返回值和温度输出分开。越界阻值返回 NTC_OUT_OF_RANGE,并保持输出参数不变;空指针返回 NTC_INVALID_ARGUMENT。调用者必须先看状态,失败时显示无效标记,不能继续把保留的旧温度拿去参与控制判断。
整数除法采用最近 0.1°C 舍入,半格向较暖方向取整。中间乘法使用 uint64_t;温度起点先转成有符号类型,负温区也按同一公式处理。
下面三段按顺序放进同一个 ntc_lookup_demo.c,就是此次验证的完整最小例子。阻值表沿用被审查源码的数据,转换逻辑和错误合同已经重写。
统一阻值单位与状态定义
/* SPDX-License-Identifier: Apache-2.0
* NTC resistance data derived from lovelessing/electronic-load,
* commit 64ffd842ad84ff5712408358e9466ea54d1637dd, bsp/app.c.
* Independent lookup, error contract and demonstration added 2026-10-11.
* Units: r10 = 0.1 ohm; t10 = 0.1 degree Celsius.
* No claim of physical sensor calibration or load-protection behavior.
*/
#include <stdint.h>
#include <stddef.h>
#ifndef NTC_NO_DEMO
#include <stdio.h>
#endif
enum ntc_status {
NTC_OK = 0,
NTC_INVALID_ARGUMENT,
NTC_OUT_OF_RANGE
};
/* Index 0 is -40 C; index 190 is 150 C. */
static const uint32_t ntc_r10[191] = {
3366000u, 3150000u, 2950000u, 2764000u, 2590000u, 2428000u, 2278000u, 2138000u,
2006000u, 1884000u, 1770000u, 1664000u, 1565000u, 1473000u, 1385000u, 1304000u,
1229000u, 1158000u, 1091000u, 1029000u, 971200u, 916600u, 865400u, 817200u,
772200u, 729800u, 690000u, 652600u, 617600u, 584600u, 553400u, 524200u,
496600u, 470800u, 446400u, 423400u, 401600u, 381200u, 362000u, 343800u,
326600u, 310400u, 295000u, 280600u, 266800u, 254000u, 241800u, 230200u,
219200u, 208800u, 199000u, 189700u, 180900u, 172600u, 164600u, 157100u,
150000u, 143200u, 136800u, 130700u, 124900u, 119400u, 114200u, 109200u,
104500u, 100000u, 95740u, 91660u, 87780u, 84080u, 80580u, 77220u,
74040u, 70980u, 68080u, 65320u, 62680u, 60160u, 57760u, 55460u,
53260u, 51180u, 49180u, 47260u, 45440u, 43680u, 42020u, 40420u,
38880u, 37420u, 36020u, 34680u, 33400u, 32160u, 30980u, 29860u,
28780u, 27740u, 26740u, 25800u, 24880u, 24000u, 23160u, 22340u,
21580u, 20820u, 20120u, 19420u, 18760u, 18130u, 17510u, 16930u,
16370u, 15820u, 15300u, 14800u, 14320u, 13850u, 13410u, 12980u,
12560u, 12160u, 11780u, 11410u, 11050u, 10710u, 10380u, 10060u,
9750u, 9452u, 9164u, 8888u, 8620u, 8364u, 8114u, 7874u,
7642u, 7418u, 7202u, 6994u, 6792u, 6596u, 6408u, 6226u,
6050u, 5880u, 5714u, 5556u, 5402u, 5252u, 5108u, 4968u,
4832u, 4702u, 4574u, 4452u, 4334u, 4218u, 4106u, 3998u,
3894u, 3792u, 3694u, 3598u, 3506u, 3416u, 3328u, 3244u,
3162u, 3082u, 3006u, 2930u, 2858u, 2788u, 2720u, 2652u,
2588u, 2526u, 2464u, 2406u, 2348u, 2292u, 2238u, 2184u,
2134u, 2084u, 2036u, 1988u, 1942u, 1897u, 1854u,
};
转换函数
/* On failure, *out_t10 is unchanged. Nearest 0.1 C, ties toward warmer. */
enum ntc_status ntc_from_r10(uint32_t r10, int16_t *out_t10)
{
size_t i;
if (out_t10 == NULL) return NTC_INVALID_ARGUMENT;
if (r10 > ntc_r10[0] || r10 < ntc_r10[190])
return NTC_OUT_OF_RANGE;
if (r10 == ntc_r10[190]) {
*out_t10 = 1500;
return NTC_OK;
}
for (i = 0; i < 190; ++i) {
uint32_t hi = ntc_r10[i];
uint32_t lo = ntc_r10[i + 1];
if (r10 <= hi && r10 >= lo) {
uint32_t width = hi - lo;
uint64_t numerator = (uint64_t)(hi - r10) * 10u;
int32_t base = ((int32_t)i - 40) * 10;
int32_t fraction = (int32_t)((numerator + width / 2u) / width);
*out_t10 = (int16_t)(base + fraction);
return NTC_OK;
}
}
return NTC_OUT_OF_RANGE;
}
主机回放入口
#ifndef NTC_NO_DEMO
int main(void)
{
const uint32_t inputs[] = {100000u, 99999u, 97870u, 95741u,
95740u, 1854u, 1853u, 3366000u};
size_t i;
for (i = 0; i < sizeof inputs / sizeof inputs[0]; ++i) {
int16_t t10 = 1234;
enum ntc_status status = ntc_from_r10(inputs[i], &t10);
if (status == NTC_OK)
printf("r10=%lu -> t10=%d\n", (unsigned long)inputs[i], (int)t10);
else
printf("r10=%lu -> OUT_OF_RANGE (output unchanged)\n",
(unsigned long)inputs[i]);
}
return 0;
}
#endif
在 Visual Studio 的 x64 Native Tools 命令行编译运行:
cl /nologo /TC /std:c11 /W4 /WX /O2 ntc_lookup_demo.c /Fe:ntc_demo.exe
ntc_demo.exe
前五个输入的 t10 依次为 250、250、255、260、260。最后一个高温节点 1854 对应 1500;1853 已超出表的范围,应返回失败状态,而不是伪装成 150.0°C。
验证专挑节点附近和越界输入
只测 25°C 或只测几个整数温度,很容易把这类问题漏掉。此次除了 191 个节点,还扫过表内全部 3,364,147 个 0.1Ω 整数输入,检查返回成功、单调性,以及与独立浮点分段线性计算的差值。

图6 实际主机回归结果。测试覆盖整张表,失败数为 0;异常用例包括两端之外、UINT32_MAX 和空指针。日志、回放程序和 CSV 与正文代码一致。
最大差值 0.05°C 只描述“同一张表做分段线性插值,再舍入成 0.1°C”的数值量化差。它没有包含 NTC 本体误差、ADC 误差、分压电阻偏差、热接触和表本身的拟合误差,因此绝不能写成整机精度 ±0.05°C。
接回板子前,还要核实三件事
先核实传感器与表是否匹配。 当前图中有两颗并联器件。分压得到的是等效阻值;如果它们温度不同,单个数值无法分别还原两个温度。即便都是 10kΩ,两颗并联在同温时也是 5kΩ,不能直接拿单颗 10kΩ 的表解释。此次没有确认实物型号、B 值和安装状态,所以保留原表用于软件回放,不宣称它已完成传感器标定。
再核实激励电源与 ADC 基准。 原界面公式写死了 3.3V 和 4.7kΩ。分压公式中的电源能否约掉,取决于激励电源和 ADC 基准是否满足对应比例关系;这里的 VCC_AMP 与 MCU 基准不是一个可随意混用的名字。不能因为代码写了 3.3,就认定硬件比例已经正确。
最后处理原始码与显示状态。 原公式会除以 ADC 码,零码要在除法前拒绝;非有限阻值不能直接转成无符号整数。负温度显示也不能把带负号的余数直接拼成小数。本文模块把输入边界放在阻值这一层,上述 ADC 接入与 UI 改动尚未做板级验证,需要在实际驱动层单独处理。附件中的 main 是主机回放入口,不是可直接烧入 AT32 的整机固件。
查表的可靠性,最终落在几个很小的约定上——单位统一、区间起点正确、所有路径都有返回、失败不冒充温度。滤波和校准都应建立在这些约定已经成立的基础上。
来源、许可与验证范围
取材项目为 lovelessing 的《智能大功率电子负载》,项目地址 https://oshwhub.com/lovelessing/intelligent-high-power-electroni 。硬件及本次 EDA 取图按项目标示的 GPL 3.0 使用;在线板名电子负载_V3.1,POWER 页更新日期 2025-04-02,MCU 页更新日期 2025-03-20。原理图母图以 4x 导出,PCB 顶底层母图为 3006×4096px;正文作裁剪、排版和保真缩放,没有改写原图元件与数值。
软件来源 https://gitee.com/lovelessing/electronic-load ,固定提交 64ffd842ad84ff5712408358e9466ea54d1637dd。仓库根 LICENSE 为 Apache-2.0,与硬件许可分开保留。阻值表源自 bsp/app.c;参考 bsp/io.c、lv_user/lv_main_page.c 及 mdk_v5/led_toggle.uvprojx 的调用与构建关系。返回路径警告依据 Microsoft Learn 的 C4715 说明 https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-1-c4715 。
本文独立分析文字按 CC BY 4.0 开放;工程图及代码仍分别遵循 GPL 3.0 和 Apache-2.0。
本文独立完成公式推导、统一单位的转换模块、明确错误返回和主机回归。完整可编译例子已在正文给出;DF 版另附 ntc-lookup-v1.zip,含对应源码、测试、日志、CSV、构建说明、LICENSE 和 NOTICE,不包含上游整套固件或第三方库。未做整机烧录、实体温升测试、传感器标定或温度停载保护验证。
技术交流,欢迎关注微信公众号:美男子玩编程。



