STM32+NTC温度采集实战:从分压电路到代码标定

发布时间:2026/8/31 21:32:16
STM32+NTC温度采集实战:从分压电路到代码标定 简介本资源是一份面向嵌入式初学者与STM32开发者的NTC温度采集实战代码聚焦于利用STM32F103C8T6注描述中为F1038T6实为常见型号F103C8T6笔误资源实际适配主流F103系列实现高精度、低噪声的环境温度测量。项目完整覆盖硬件接口配置ADC分压电路、电阻-温度转换算法含B值公式与查表法基础框架、软件滤波滑动平均及数据处理全流程适用于智能温控、物联网终端、工业传感器节点等典型场景。压缩包共4个文件2个C源文件负责ADC初始化与NTC参数计算2个头文件定义引脚、宏常量与函数接口总大小仅5KB轻量易集成。已有6549人学习下载代码结构清晰、注释详实包含可直接复用的ADC采样调度逻辑、NTC阻值换算模块及温度标定预留接口是掌握STM32模拟信号采集与热敏电阻应用的优质入门范例。 去年做一个热水器控制项目温度采集这块让我折腾到半夜。一开始用数字温度传感器发现探头体积太大塞不进原有模具后来换回NTC热敏电阻方案用STM32的ADC读取再软件换算问题一下全解决了。这篇就把我从电路选型到程序落地的完整过程写出来重点讲NTC温度采集项目里最容易踩的坑分压电阻怎么选、采样时间怎么配、ADC抖动怎么滤、温度值怎么标定。这套方案能解决的问题很明确用几毛钱的NTC热敏电阻加一颗STM32在-20℃到105℃范围内实现±1℃左右精度的温度采集成本低、响应快、探头形状随便定制。适合正在做温控器、热水器、锂电池保护板、冷链运输记录仪或者只是想把温度采集功能加进现有嵌入式项目的朋友参考。我会把计算过程、配置参数、代码示例和实测调试经验都摊开来讲。1. 项目整体设计与方案选型1.1 为什么是NTC而不是DS18B20或PT100很多新手一上来就纠结传感器选型。DS18B20这类数字传感器确实简单单总线直接输出温度值程序几行就能跑通但限制也很明显测温范围一般是-55℃到125℃响应速度偏慢默认12位分辨率下转换时间约750ms探头体积固定不好定制而且一颗原装正品要好几块钱。PT100/PT1000铂电阻精度高、线性好但后续要接调理电路或专用芯片成本翻好几倍用在消费级产品上有点杀鸡用牛刀。NTC热敏电阻的优势在于三个点价格便宜批量采购一颗不到一毛钱体积可以做到非常小贴片0402封装甚至能直接埋在电池内部响应速度快因为热容量小测温滞后比DS18B20明显小。代价就是非线性严重温度换算得自己做数学处理而且一致性不如铂电阻需要校准。做产品设计时需求是“够用就好”还是“必须高精度”这个权衡在项目第一步就要想清楚。1.2 NTC的工作原理阻值变化如何变成温度NTC是负温度系数热敏电阻的缩写它的特性是温度升高时电阻值下降。这个变化不是线性的而是遵循指数关系近似用B值公式描述R_T R_25 × exp(B × (1/T - 1/T_25))其中R_25是25℃时的标称阻值B是材料常数常见值有3435、3950、4200等不同B值对应不同的温度-阻值曲线T是绝对温度单位开尔文。T_25就是298.15K。从公式能看出两件事第一B值越大电阻对温度越敏感高温段变化越剧烈第二整个曲线是指数型的温度越高阻值变化越快。所以工程上一般采用查表法或者直接套用Steinhart-Hart方程来精确换算。Steinhart-Hart方程是更精确的拟合公式1/T A B×ln(R) C×(ln(R))³实际写代码时如果MCU资源充裕建议用B值公式加修正系数如果对精度要求高且Flash空间够直接做一张温度-ADC值对照表用二分查找加线性插值效果最稳。1.3 系统框架从测量点到MCU的完整链路一个完整的NTC温度采集系统链路是NTC探头 → 分压电路 → STM32的ADC引脚 → 软件滤波算法 → 温度换算 → 结果输出串口/OLED/云端。中间最容易出问题的环节是分压电路和ADC采样配置。NTC本身只是一个可变电阻MCU的ADC只能读电压所以必须用固定电阻和NTC组成分压电路把阻值变化转换成电压变化。ADC读到的电压经过计算先还原成NTC当前阻值再通过公式换算成温度。这两个环节的计算公式如果搞反了后面所有温度读数都是错的。2. 硬件电路设计与参数计算2.1 经典分压电路NTC放高端还是低端分压电路有两种接法。第一种是VCC接固定电阻中间节点接ADC下边接NTC到GND。这种接法下温度越高NTC阻值越小节点电压越低ADC读数变小。第二种是VCC接NTC中间接ADC下边接固定电阻到GND温度越高NTC阻值越小节点电压反而上升ADC读数变大。两种接法在原理上等价但工程上推荐第一种固定电阻在上、NTC在下。原因有两个。一是NTC的引线或探头外壳如果碰到金属机箱低端接GND通过NTC到地更安全不容易引入干扰。二是排查故障时更容易判断逻辑温度升高ADC值下降这个趋势比较直观。不过也有人喜欢第二种因为温度升高ADC值升高看起来更“顺”。程序里公式方向改一下就行没有绝对对错关键是全项目统一定义清楚。2.2 上拉电阻选型为什么推荐用10k对10k分压电路中的固定电阻值选择很讲究。原则是让NTC在你关心的温度区间中间值附近分压比尽量接近1/2这样ADC读数的分辨率最高。我常用的搭配是10k NTC配10k固定电阻供电3.3V时25℃下NTC阻值正好是10k分压比1:1模拟量电压约1.65V12位ADC的读数约2048正好在量程中点。选10k对10k还有一个原因这个搭配在-20℃到100℃范围内分压电压能控制在ADC有效测量范围内。用B3950的10k NTC算一下-20℃时阻值约97k节点电压约3.0VADC读数约3725100℃时阻值约680Ω节点电压约0.21VADC读数约260。这样的电压跨度用12位ADC来读分辨率足够不需要额外加运放做电平搬移。要注意的是如果你测的是窄范围比如只关心0~50℃环境温度可以换成一个阻值更接近NTC在目标温度中点阻值的电阻让分压比更优。0~50℃时NTC阻值范围大约从33.9k降到3.9k取固定电阻6.8k或8.2k会更居中。选型公式是R0取NTC在测温范围中点温度时的阻值这样动态范围最大。这个计算逻辑很多教程不讲但实际工程中这才是拉开差距的地方。2.3 自热效应与供电选择NTC是电阻型器件电流流过就会发热这个热量会让NTC自分温度高于被测物体导致读数偏高这就是自热效应。选上拉电阻时不能只考虑ADC量程还要控制流过NTC的电流密度。以10k NTC 10k上拉、3.3V供电为例25℃时回路电流I 3.3V / (10k 10k) 0.165mANTC上的功耗P I² × R 0.165mA² × 10k ≈ 0.27mW。绝大多数NTC的耗散系数在1~3mW/℃之间也就是说这个功耗带来的自热温升不到0.3℃可以忽略。但如果供电电压升到12V或者选了100k NTC配很大的上拉功耗就可能超过几mW自热温升会达到好几度这在精密测量中就是致命的。所以推荐NTC那条支路直接用MCU同源的3.3V供电别用24V或12V系统电压省得额外增加稳压和滤波成本。采样时也可以间歇供电ADC转换前先打开电源稳定几百微秒转换完立刻断开这样能进一步降自热低功耗项目经常这么干。2.4 PCB布线与抗干扰细节硬件上还有一个容易被忽视的环节ADC引脚的滤波电容。NTC分压点的阻抗是固定电阻和NTC的并联值25℃时约5k这个阻抗会让ADC内部采样电容在采样时间内充不够电压。解决办法是在ADC引脚对地加一个0.1μF的电容和引脚阻抗组成低通滤波器既滤高频噪声又能给ADC采样电容提供电荷源。采样时间也要在软件里配合调长后面代码部分再细说。PCB布局上NTC探头如果是外接的走线尽量短并远离大电流回路和开关节点比如MOSFET的D极、电感下方。如果是板载测量环境温度NTC不要放在发热元器件旁边至少和MCU、电源芯片拉开0.5cm以上这是我在一个温控器项目里踩过的坑——NTC放在LDO旁边读数比实际环境温度高5℃。3. STM32端程序实现与代码落地3.1 ADC引脚与通道配置我用的是STM32F103系列HAL库工程里用CubeMX先把ADC配置好。需要设置的参数有分辨率选12位STM32F1固定12位扫描模式关闭单通道不需要连续转换模式打开配合DMA用采样时间设239.5周期外部触发不启用直接软件触发。采样时间为什么一定要拉长前面提到了分压源阻抗约5kADC内部采样电容约几pFRC时间常数在几百纳秒级别如果采样时间太短采样电容电压还没稳定到实际值就开始转换结果会偏低。STM32F1的ADC最大采样周期是239.5周期在72MHz的ADC时钟下约3.3μs对应我实测的情况是无论分压阻抗10k以下都能采准。如果比你当前用的采样时间小比如只有1.5周期或7.5周期读数会明显偏低这是新手最常见的问题之一。GPIO配置上ADC输入引脚要设为模拟输入模式。这里有个小细节STM32F1的ADC引脚如果复用为推挽输出或其他模式会导致读数异常CubeMX里选择Analog模式就不会错。另外VREF引脚接的参考电压必须是干净的3.3V如果板子上直接用开关电源供电且纹波大ADC读数会有周期性的跳变这时建议加一颗TL431或REF3030做基准源能明显改善。3.2 从ADC值还原温度两个核心公式ADC值换算成温度的完整链条分两步。第一步从ADC值算出NTC当前阻值第二步从阻值算出温度。以VCC接10k上拉、下端NTC接地的电路为例也就是温度升高ADC读数变小分压公式是V_adc VCC × R_ntc / (R0 R_ntc)ADC读数与电压成正比ADC / 4095 V_adc / VCC联立可得R_ntc R0 × ADC / (4095 - ADC)注意这里ADC是12位满量程如果参考电压不是3.3V公式里的4095不变因为ADC值和VCC的比值约掉了。但如果VREF和VCC不同就要用ADC/4095 V_adc/VREF重新推导。拿到R_ntc之后用B值公式反推温度。B值公式反解1/T ln(R_ntc / R_25) / B 1 / T_25T(℃) 1 / (ln(R_ntc / 10000.0) / 3950.0 1.0 / 298.15) - 273.15我用的是B3950的10k NTC。如果你的NTC规格书B值不同改成实际值即可差异非常大——B从3435换成3950在50℃时阻值会差出10%以上所以一定不要照抄网上的代码然后抱怨读数不准。3.3 完整代码示例ADC采集到温度输出下面是基于HAL库的完整温度采集代码包括ADC单次读取、滑动平均滤波和温度换算。我直接贴出我项目里的精简版本#include adc.h #include math.h #define NTC_R25 10000.0f #define NTC_B 3950.0f #define ADC_FULL_SCALE 4095.0f #define FILTER_LEN 16 static uint16_t filter_buffer[FILTER_LEN]; static uint8_t filter_index 0; // 读取ADC原始值带简单软件滤波 float get_adc_filtered(void) { uint32_t sum 0; uint8_t i; HAL_ADC_Start(hadc1); for (i 0; i FILTER_LEN; i) { // 等EOC标志也可以用HAL_ADC_PollForConversion while (HAL_ADC_PollForConversion(hadc1, 100) ! HAL_OK); filter_buffer[i] HAL_ADC_GetValue(hadc1); } HAL_ADC_Stop(hadc1); // 先排序去最大最小再平均中序均值滤波 // 简单起见这里直接平均值实际用中值平均效果更好 for (i 0; i FILTER_LEN; i) { sum filter_buffer[i]; } return (float)sum / FILTER_LEN; } // 计算NTC阻值 float calc_ntc_resistance(float adc_value) { if (adc_value 0.0f) adc_value 1.0f; if (adc_value ADC_FULL_SCALE) adc_value ADC_FULL_SCALE - 1.0f; // 电路VCC-10k-ADC_NODE-NTC-GND // Rntc R0 * ADC / (4095 - ADC) return 10000.0f * adc_value / (ADC_FULL_SCALE - adc_value); } // 阻值转温度 float calc_temperature(float rntc) { float temp_k; temp_k 1.0f / (logf(rntc / NTC_R25) / NTC_B 1.0f / 298.15f); return temp_k - 273.15f; } // 使用示例 float read_ntc_temperature(void) { float adc, rntc, temp; adc get_adc_filtered(); rntc calc_ntc_resistance(adc); temp calc_temperature(rntc); return temp; }代码里有几个细节值得说明。ADC_FULL_SCALE - 1这个保护很重要当NTC开路或者温度极高时ADC可能读到4095直接代入公式会除零引发浮点异常。同样ADC读到0说明NTC短路也要做保护。我自己在测试时就遇到过传感器线松动读数满量程的情况温度和电阻都变成inf没有保护机制的话后面显示模块一通乱码。滤波部分我只做了简单的平均值实际生产环境建议用“去极值平均滤波”连续采样16次去掉最大和最小剩下的取平均。这一步能有效滤掉偶发的脉冲干扰同时保留温度变化的真实趋势。代码里用数组存历史值再排序即可缺点是占用几十字节RAM但F103完全不缺这点内存。3.4 查表法与浮点运算的取舍如果MCU没有FPU比如STM32F103是Cortex-M3浮点全靠软件算logf函数会消耗大量CPU时间。一次温度换算大约要花几百微秒如果你的系统1秒钟才采一次温完全无所谓但如果要做实时控制PID调节、保护阈值判断频繁调用浮点log就不太划算了。这时可以用查表法。做法是预先把-20℃到105℃每隔1℃对应的ADC值算出来存成一个const数组运行时用ADC值做二分查找找到所在区间后线性插值。查表法的优势是快、结果稳定、代码好理解缺点是需要占几百字节Flash以及表格只适用于固定的分压电阻和B值换元件得重新生成表。我当时是写了一个Python脚本自动生成这个表把公式、R0、B值、分压电阻都做成参数输出C数组直接拷进工程。如果你也想用查表法建议同样脚本化生成手算不现实。3.5 多通道采集与DMA的应用如果项目里不止一路NTC比如要同时采集进水温度和出水温度可以用ADC的多通道扫描模式DMA。配置上开启扫描模式和DMA循环模式DMA缓冲区长度设为通道数每次转换完成DMA自动把各通道结果搬到内存数组。#define NUM_ADC_CHANNELS 2 uint16_t adc_buf[NUM_ADC_CHANNELS]; // DMA传输完成回调里做处理 void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { // adc_buf[0]对应通道1如进水温度 // adc_buf[1]对应通道2如出水温度 if (hadc-Instance ADC1) { adc_flag 1; } }用DMA的好处是CPU不用一直等待转换完成采集结果自动持续刷新到数组里主循环只读取最新值效率高很多。唯一要注意的是多通道连续采样时通道切换会有串扰解决办法是在扫描序列里给不用的通道留出冷却时间或者干脆加长采样时间。我在两路测温项目里实测只要采样时间设到239.5周期串扰就测不出来了。3.6 程序结构思路最后说下程序整体结构。我习惯把NTC采集封装成独立模块头文件提供NTC_Init()和NTC_ReadTemperature()接口内部把ADC初始化和滤波全部封装好上层串口发送、OLED显示、PID控制只拿最终温度。这个模块化思路让我在不同的项目之间复用代码很方便换个NTC型号只需要改头文件里的三个宏定义。如果你的系统上了FreeRTOS建议NTC采集放在独立任务里1秒采样一次任务内部做20ms间隔的连续采样和滤波避免在中断里做浮点运算。4. 常见问题与排查经验4.1 现象排查速查表这段时间做NTC项目我整理出一份问题排查速查表。你把ADC读数和实际温度对照一下基本能快速锁定问题出在哪一环现象可能原因排查方向ADC始终接近4095NTC开路、探头线断、NTC虚焊用万用表量NTC两端阻值是否正常ADC始终接近0NTC短路、上拉电阻没焊、引脚配错检查PCB焊接量分压点电压读数明显偏低采样时间太短、分压电阻焊错、NTC参数不是B3950查看CubeMX采样周期是否≥239.5读数明显偏高参考电压高于3.3V、NTC贴片烫伤参数漂移量VREF对比规格书读数跳动很大接线接触不良、电源纹波大、没加滤波电容加0.1μF电容检查杜邦线是否松动读数始终不变程序用了上一次缓存、滤波窗口太长、DMA没触发检查DMA回调标志位低温段准高温段不准B值标称与实际偏差大、NTC靠近热源换高精度NTC或做两点标定4.2 精度不够怎么办两点标定法NTC标称阻值和B值存在个体偏差一般±1%的NTC在25℃时阻值可能实际是9.9k到10.1kB值误差也会造成高温段偏离。如果你的项目要求误差在±0.5℃以内直接靠标称参数不现实必须做标定。我用的方法是两点标定。把NTC探头放到冰水混合物中标准0℃等读数稳定后记录ADC值和换算温度再放到沸腾的开水或恒温槽中标准100℃海拔高时沸点会变注意修正再次读取。用这两组数据反推出实际R25和B值代入程序。公式推导不复杂R_0 R0 × ADC_0 / (4095 - ADC_0) 0℃时NTC实际阻值 R_100 R0 × ADC_100 / (4095 - ADC_100) 100℃时NTC实际阻值 B_actual ln(R_0 / R_100) / (1/273.15 - 1/373.15) R25_actual R_0 / exp(B_actual × (1/298.15 - 1/273.15))把算出来的R25_actual和B_actual替换掉代码里的宏温度精度就能从±2℃提升到±0.5℃左右。批量生产时不可能每个探头都标定可以选择同一个批次抽检把抽检平均值写进固件也能提升一致性。4.3 温度响应慢的问题有些朋友做完之后发现温度读数半天才稳定怀疑是滤波太强。其实多数情况是NTC探头本身的热时间常数问题。不同封装的NTC热时间常数差别很大0402贴片约3~5秒玻璃封装约10秒带壳的防水探头可能长达30秒甚至更久。软件滤波再怎么优化也改变不了硬件热滞后。如果需要加快响应选型时看规格书里的热时间常数参数选小封装的如果必须用防水探头尽量选头部做薄一点的或者在安装时用导热硅脂填充缝隙热传导快很多。软件上可以做一阶低通替代大窗口滑动平均比如current 0.8 * last 0.2 * new响应会平滑很多且不滞后太多。4.4 一个容易被忽略的坑ADC输入阻抗与采样保持这个问题在STM32F1上很经典。ADC的等效输入阻抗在采样保持阶段不是无穷大而是几十kHz范围内等效为RC网络。如果分压电路阻抗过高比如NTC在低温时阻值达到100k以上即使采样时间调到最大239.5周期采样电容依然可能充电不充分。解决方案有三种一是用低阻值的NTC10k而不是100k二是ADC引脚并联一个0.1μF电容让电容在采样期间作为电荷储源稳定提供电荷三是用运放做跟随器缓冲阻抗。我实测下来10k NTC 0.1μF电容 239.5周期采样时间在-20℃到100℃全范围误差都在可接受范围内所以不需要额外加运放省了很多成本。最后分享一个实际体会NTC温度采集看起来很简单就是ADC加一条公式但工程落地的过程考验的是对电路、器件、MCU外设三者协同的理解。硬件因素分压比、供电、滤波、布局和软件因素采样时间、滤波、公式、标定是相互牵连的任何一环脱节最终读数都会反馈出来。我踩过的最深刻的坑就是采样时间设置太短导致读数偏低当时死活想不通为什么10k对10k的分压电压实测和理论值差那么多后来用示波器看ADC采样波形才找到原因。如果你也遇到类似的温度读数偏差问题先从电路实测电压开始排查再回头看软件公式这样思路最清晰。本文还有配套的精品资源点击获取