
1. 为什么NTC热敏电阻配STM32是个好组合温度测量这件事看起来简单做起来坑不少。我最早接触温度采集是在一个工业控制项目里当时用DS18B20一线总线精度还行但响应速度慢而且多点组网时时序要求苛刻线一长就容易出问题。后来换到NTC热敏电阻加STM32的方案才发现这个组合在成本和灵活性上优势明显——一颗NTC几毛钱STM32的ADC又是片内自带整体BOM成本极低而且采样速度可以做到毫秒级适合需要快速响应的场景。NTC是Negative Temperature Coefficient的缩写意思是负温度系数热敏电阻温度越高阻值越低。它的阻值变化不是线性的而是一条指数曲线这也是为什么很多人觉得NTC“不好用”——直接读ADC值换算温度误差能大到离谱。但只要你掌握了正确的换算方法NTC的精度可以做到±0.1℃甚至更高完全能满足大多数工业和消费类场景的需求。STM32这边几乎全系列都带12位ADC像F103、F407、G0、G4、H7这些常用型号ADC采样率从1Msps到5Msps不等分辨率也有16位可选。用STM32的ADC去采集NTC分压后的电压再通过算法换算成温度整个链路非常清晰。我实测下来用STM32F103加一颗10kΩ B值3950的NTC在0到100℃范围内经过校准后精度可以稳定在±0.3℃以内如果加一点滤波和线性化处理还能更好。这篇文章适合谁看如果你正在做基于STM32的温度采集项目或者毕业设计里需要测温模块又或者你想搞清楚ADC采样和NTC换算的完整链路那这篇内容就是为你准备的。我会从硬件电路设计讲到代码实现再到校准和排错把我在实际项目中踩过的坑和总结的技巧都摊开来讲。代码部分我会给出完整的C语言实现基于HAL库可以直接移植到你的工程里。2. 硬件电路设计与关键参数计算2.1 NTC分压电路的基本结构NTC测温最常用的电路就是分压法一个NTC和一个固定电阻串联中间节点接到STM32的ADC引脚。NTC接在VCC和ADC引脚之间固定电阻接在ADC引脚和GND之间这样温度升高时NTC阻值减小ADC引脚电压升高。反过来接也可以但逻辑会反过来我习惯用NTC上拉的方式因为这样温度越高ADC读数越大符合直觉。这里有个关键点固定电阻的阻值选择。很多人随便拿一个10kΩ就往上焊结果发现低温段分辨率很差。固定电阻的阻值应该选在你要测量的温度范围中点对应的NTC阻值附近。比如你测0到100℃中点50℃时NTC阻值大概是3.6kΩ以10kΩ B3950为例那固定电阻选3.3kΩ或3.6kΩ会比10kΩ好很多。为什么因为分压电路在固定电阻和NTC阻值相等时电压对阻值的变化率最大也就是灵敏度最高。我一般会先确定测量范围然后查NTC的R-T表找到中点温度对应的阻值再选最接近的标准电阻值。如果你手头只有10kΩ的电阻那测量范围最好设在20到80℃之间这样中点50℃时NTC阻值3.6kΩ和10kΩ差距不算太大勉强能用但低温段精度会明显下降。2.2 参考电压的选择与噪声抑制STM32的ADC参考电压通常用VDDA也就是3.3V。但3.3V这个电源往往同时给MCU和其他外设供电纹波和噪声不小。如果你对精度要求高建议用独立的基准电压芯片比如REF3033输出3.0V或3.3V噪声能低一个数量级。我试过在F103上用VDDA直接做参考ADC读数跳动大概在±3个LSB换成外部基准后跳动降到±1个LSB以内。另外ADC引脚前面一定要加RC低通滤波。NTC的响应时间通常在几秒到几十秒所以滤波截止频率可以设得很低比如10Hz甚至1Hz。我一般用1kΩ电阻加1μF电容截止频率约160Hz能滤掉大部分高频噪声又不会影响NTC本身的响应速度。如果你测的是快速变化的温度比如水流温度那电容可以减小到0.1μF。还有一点容易被忽略NTC的引线。如果NTC离STM32比较远比如超过10厘米引线上的分布电容和电感会引入干扰。这时候建议用屏蔽线或者把NTC的模拟部分和数字部分在PCB上分开布局模拟地单点接地。我在一个电机控制项目里NTC引线只有5厘米但电机一启动ADC读数就乱跳后来在NTC两端并了一个0.1μF电容问题就解决了。2.3 自热效应与激励电流NTC是有自热效应的——电流流过NTC会产生热量导致测量值偏高。这个误差在测量精度要求高的时候必须考虑。NTC的功耗应该控制在0.1mW以下最好在0.01mW量级。怎么算假设NTC阻值10kΩ流过的电流是I功耗PI²R。如果P要小于0.01mW那I要小于约31.6μA。分压电路里电流由VCC和总阻值决定3.3V除以10k10k165μA功耗约0.27mW偏大了。解决办法有两个一是增大固定电阻和NTC的阻值比如都用100kΩ电流降到16.5μA功耗就下来了二是用间歇供电只在采样时给NTC通电采样完就断电。我一般用第一种方法因为简单可靠。但阻值大了之后ADC输入阻抗的影响就不可忽略。STM32的ADC输入阻抗在采样时间足够长的情况下可以认为是无穷大但采样时间短的时候等效输入阻抗会下降。所以用100kΩ级别时ADC采样时间要设长一些比如239.5个周期确保采样电容充分充电。3. NTC温度换算的数学原理与代码实现3.1 从ADC值到电阻值的换算STM32的ADC是12位的读数范围0到4095对应电压0到VDDA。假设NTC上拉固定电阻下拉ADC读数记为adc_val则ADC引脚电压V_adc adc_val / 4095 * VDDA。根据分压公式V_adc VDDA * R_fixed / (R_ntc R_fixed)整理得R_ntc R_fixed * (VDDA / V_adc - 1) R_fixed * (4095 / adc_val - 1)。这个公式很简单但有个坑adc_val不能为0否则除零。实际电路中如果NTC短路或ADC引脚接地adc_val会是0。所以代码里要加保护adc_val小于某个阈值时直接返回错误或上限温度。我一般设adc_val最小为10对应R_ntc约409倍R_fixed已经远超正常范围了。另外VDDA的实际值可能不是精确的3.3V比如3.28V或3.32V。如果你用VDDA做参考那VDDA的误差会直接传递到结果里。所以要么用外部基准要么在代码里用实测的VDDA值。我通常会在板子上留一个测试点用万用表量一下VDDA然后把实际值写进代码。3.2 Steinhart-Hart方程与B值公式NTC的阻值和温度关系最准确的描述是Steinhart-Hart方程1/T A Bln(R) Cln(R)³其中T是开尔文温度R是阻值A、B、C是常数。这个方程精度最高但需要三个系数而且计算量大在单片机上跑有点吃力。更常用的是B值公式1/T 1/T0 (1/B) * ln(R/R0)其中T0是参考温度通常25℃298.15KR0是T0时的阻值比如10kΩB是B值比如3950。这个公式只需要两个参数计算量小在0到100℃范围内精度足够。我实测过B值公式和Steinhart-Hart方程在0到100℃内的最大偏差约0.2℃对大多数应用足够了。B值公式的代码实现要注意ln函数在math.h里但STM32的HAL库默认可能没开浮点运算。F103没有FPU浮点运算是软件模拟速度慢但能用。如果你用的是F4或G4有硬件FPU那浮点运算就很快。我一般会把B值公式里的常数预先算好减少运行时计算量。比如1/T0和1/B都是常数可以提前算出来。3.3 完整代码实现与注释下面是我在实际项目中用的代码基于STM32 HAL库ADC用DMA采样多次平均后换算温度。代码里包含了ADC读取、电阻计算、温度换算和简单的滤波。#include main.h #include math.h #define R_FIXED 10000.0f // 固定电阻阻值单位欧姆 #define R0 10000.0f // NTC在25℃时的阻值 #define B_VALUE 3950.0f // NTC的B值 #define T0 298.15f // 25℃对应的开尔文温度 #define ADC_MAX 4095.0f // 12位ADC最大值 #define VDDA 3.3f // 参考电压 extern ADC_HandleTypeDef hadc1; // 读取ADC并换算成NTC阻值 float Get_NTC_Resistance(void) { uint32_t adc_sum 0; uint16_t adc_val 0; float v_adc, r_ntc; // 采样16次取平均降低噪声 for (int i 0; i 16; i) { HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 10); adc_sum HAL_ADC_GetValue(hadc1); } adc_val adc_sum / 16; // 防止除零和异常值 if (adc_val 10) adc_val 10; if (adc_val 4085) adc_val 4085; // 计算NTC阻值 r_ntc R_FIXED * (ADC_MAX / (float)adc_val - 1.0f); return r_ntc; } // 根据NTC阻值计算温度返回摄氏度 float Get_Temperature(void) { float r_ntc Get_NTC_Resistance(); float ln_r logf(r_ntc / R0); float inv_t 1.0f / T0 ln_r / B_VALUE; float temp_k 1.0f / inv_t; float temp_c temp_k - 273.15f; return temp_c; }这段代码里采样16次取平均是最简单的滤波能有效抑制随机噪声。如果你需要更平滑的曲线可以用一阶低通滤波new_val alpha * old_val (1-alpha) * new_valalpha取0.1到0.3之间。我一般用0.2响应速度和平滑度比较平衡。还有一点logf函数在math.h里链接时需要加-lm。在Keil里需要在工程设置里勾选Use MicroLIB或者手动添加math库。如果你用的是GCC在Makefile里加-lm就行。4. 精度提升与校准实战4.1 两点校准法的具体操作NTC的标称B值和R0都有公差通常±1%到±5%直接套公式会有几度的误差。要提升精度必须校准。最实用的方法是两点校准在冰水混合物0℃和沸水100℃注意气压修正中各测一次记录ADC读数然后反推实际的R0和B值。具体操作先把NTC放进冰水混合物里等读数稳定大概2到3分钟记录ADC值adc_0和对应的阻值R_0_actual。再放进沸水里记录adc_100和R_100_actual。然后根据B值公式反推B_actual ln(R_0_actual / R_100_actual) / (1/T_0 - 1/T_100)其中T_0273.15KT_100373.15K。R0_actual就是R_0_actual。把这两个值写回代码精度能提升到±0.1℃以内。我实测过一颗标称10kΩ B3950的NTC未校准前在50℃时误差约1.5℃两点校准后误差降到0.2℃。这个提升非常明显而且操作不复杂值得花时间做。4.2 分段线性插值法如果你觉得浮点运算太慢或者MCU没有FPU可以用分段线性插值。把NTC的R-T表按温度分成若干段每段用线性近似。比如每10℃一段在0到100℃内分10段每段存两个端点阻值和温度运行时先查表找到所在段再线性插值。这样计算量小精度也能做到±0.5℃以内。分段线性插值的代码实现定义一个结构体数组每个元素包含阻值下限、阻值上限、温度下限、温度上限。运行时遍历数组找到R_ntc所在的区间然后temp temp_low (R_ntc - R_low) * (temp_high - temp_low) / (R_high - R_low)。这个方法在8位单片机上都能跑STM32更是绰绰有余。4.3 常见误差来源与对策NTC测温的误差来源主要有几个NTC本身的公差、固定电阻的公差、VDDA的误差、ADC的量化误差、自热效应、引线电阻。NTC和固定电阻建议用1%精度的VDDA用外部基准ADC量化误差可以通过过采样和平均降低自热效应通过增大阻值或间歇供电解决引线电阻在引线短的时候可以忽略。还有一个容易被忽略的ADC的偏移误差和增益误差。STM32的ADC有出厂校准值可以在初始化时调用HAL_ADCEx_Calibration_Start()进行校准。我试过校准前后ADC读数在零点附近能差2到3个LSB对低温段测量影响不小。所以每次上电后先做一次ADC校准是个好习惯。5. 常见问题排查与实操避坑指南5.1 ADC读数跳动大怎么办ADC读数跳动是最常见的问题。先检查硬件VDDA和VSSA之间有没有加去耦电容我一般加100nF和10μF并联靠近MCU引脚。ADC引脚前面有没有RC滤波没有的话加1kΩ加1μF。NTC引线有没有屏蔽没有的话尽量缩短或者用双绞线。软件上增加采样次数取平均是最直接的办法。我一般采16次如果还跳就采64次。但采样次数太多会影响响应速度所以要权衡。另外ADC的采样时间要设够特别是用高阻值NTC时。STM32F103的ADC采样时间可选1.5到239.5个周期我一般用71.5或239.5确保采样电容充满。还有一个坑如果ADC引脚同时被其他外设复用比如用作GPIO输出那读数肯定乱。检查一下GPIO初始化确保ADC引脚配置为模拟输入模式不要上下拉。5.2 温度值跳变或明显错误温度值跳变通常是因为ADC值在临界点附近波动导致换算出的阻值跳变。比如adc_val在2047和2048之间跳阻值就会跳几十欧姆温度跳零点几度。解决办法是加迟滞或者用滑动平均滤波。我一般用滑动窗口窗口大小8每次新值进来就替换最旧的值然后取平均。这样曲线很平滑响应也够快。如果温度值明显错误比如室温显示100℃先检查分压电路有没有焊反。NTC和固定电阻的位置搞反了温度就会反着来。再检查B值和R0有没有写错10kΩ的NTC写成100kΩ温度会差很多。还有logf的参数不能为负或零如果R_ntc算出来是负数说明ADC值有问题检查adc_val是否超范围。5.3 常见问题速查表现象可能原因排查方法解决措施ADC读数始终为0NTC短路或ADC引脚接地万用表测ADC引脚电压检查NTC和焊接ADC读数始终为4095NTC断路或ADC引脚接VCC万用表测ADC引脚电压检查NTC和焊接读数跳动±10以上电源噪声或滤波不足示波器看VDDA纹波加RC滤波和去耦电容温度偏高几度自热效应或VDDA偏高测NTC电流和VDDA增大阻值或间歇供电温度偏低几度引线电阻或VDDA偏低测引线电阻和VDDA用三线制或校准低温段精度差固定电阻选大了查R-T表换小阻值固定电阻高温段精度差固定电阻选小了查R-T表换大阻值固定电阻5.4 实操心得与避坑技巧第一个心得NTC的焊接温度不能太高烙铁停留时间不能太长。NTC的环氧封装耐温有限长时间高温焊接会导致阻值漂移。我一般用300℃烙铁每个焊点不超过3秒焊完等冷却再测阻值。第二个心得如果测量范围跨零度以下B值公式的精度会下降因为NTC在低温段的非线性更强。这时候建议用Steinhart-Hart方程或者分段校准。我在一个冷链项目里测-20到0℃用B值公式误差到了1℃换成Steinhart-Hart后降到0.3℃。第三个心得代码里加一个温度合理性检查。如果算出来的温度超出NTC的物理范围比如-50到150℃之外直接返回错误值避免后续逻辑出错。我在一个加热控制项目里NTC脱落导致ADC读数满量程温度算出来500℃加热器一直全功率运行差点出事。后来加了范围检查超过150℃就报故障并关闭加热。第四个心得如果多个NTC共用一路ADC用模拟开关切换时切换后要等一段时间再采样让RC滤波电容充电稳定。我一般等1ms采样时间设239.5周期这样切换通道后的第一组数据才可靠。6. 从单点测温到多点组网的扩展思路单点测温跑通之后很容易扩展到多点。STM32的ADC有多通道配合DMA可以自动扫描多个通道把每个NTC的ADC值存到数组里然后逐个换算温度。我用F103的ADC1扫描4个通道DMA循环模式采样率1kHzCPU几乎不参与效率很高。如果NTC数量超过ADC通道数可以用模拟开关比如CD40518选1。STM32用3个GPIO控制通道选择ADC只采一路。切换通道后延时1ms再采样避免通道间串扰。我做过16路NTC采集用两片CD4051扫描周期100ms温度刷新率10Hz完全够用。再往上如果要做分布式测温可以用RS485总线每个节点一个STM32加一个NTC主机轮询。RS485抗干扰能力强传输距离可达1200米适合工业现场。每个节点的代码和单点测温一样只是加了Modbus协议。我做过一个粮仓测温项目32个节点每个节点4路NTC总共128个测温点用RS485组网运行两年多没出过问题。代码层面多点采集的关键是DMA缓冲区和通道顺序的对应关系。STM32的ADC扫描模式按通道号从小到大依次转换DMA按顺序搬运。所以你要确保DMA缓冲区的索引和通道号对应。我一般定义一个结构体数组每个元素包含通道号、NTC参数、温度值初始化时按通道号排序这样DMA搬完直接对应。最后分享一个调试技巧如果多点采集时某个通道读数异常先单独测那个通道排除硬件问题。如果硬件没问题检查DMA缓冲区有没有越界或者通道顺序有没有搞错。我遇到过一次DMA缓冲区大小设成了4但实际扫描了5个通道结果最后一个通道的数据覆盖了第一个通道查了半天才发现是数组大小写错了。这种低级错误在实际项目中很常见写代码时多留个心眼。