ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于STM32的NTC热敏电阻测温电路设计与实现

基于STM32的NTC热敏电阻测温电路设计与实现 手头要做一块基于单片机的汽车冷却液温度测量电路我第一反应不是选数字温度传感器而是拿了一颗NTC热敏电阻。原因很直白便宜、皮实、能扛到一百多度还不需要像DS18B20那样去折腾总线时序只要一颗分压电阻把阻值变化变成电压变化再用STM32F042的ADC读回来温度测量就完成了。这篇文章就把我这次完整做完的NTC测温电路、计算方法和实际踩过的坑整理出来配套例程基于STM32F042的HAL工程也顺带说清楚NTC热敏电阻测温电路到底该怎么设计、ADC读到的原始值该如何换算成温度。1. 先把NTC这枚“电阻”说清楚1.1 “NTC输出什么信号”——这个常见疑问的正确答案很多刚接触NTC的朋友会问一句NTC热敏电阻输出什么信号说实话NTC本身根本不会“输出信号”。它就是一个阻值随温度变化的普通电阻和贴片电阻、直插电阻本质上没有区别只是它的阻值对温度特别敏感。NTC是Negative Temperature Coefficient的缩写意思是负温度系数温度升高阻值下降温度降低阻值上升。所以当你把NTC当成一个“传感器”来看的时候你拿到的不是一个电压、不是一串数字、更不是PWM而是两个引脚之间的直流电阻。想把它变成单片机ADC能读的模拟电压就必须在外面额外搭一个分压电路。你可以简单理解成NTC负责把温度变成电阻分压电路负责把电阻变成电压STM32F042的ADC负责把电压变成数字量。这颗“哑元件”没有协议、没有地址、不需要初始化但它要求的恰恰是外围模拟电路设计的基本功。1.2 决定性的两个参数R25和B值选NTC第一眼看两个参数R25和B值。R25在25℃时测到的阻值。市面上最常见的是10kΩ也有100kΩ、50kΩ、5kΩ的。R25决定了整个测温电路的工作点。B值又叫材料常数单位是开尔文K它描述阻值随温度变化的剧烈程度。常见B值有3380K、3435K、3950K、4100K等。B值越大温度变化引起的阻值变化越明显灵敏度也越高。这两个参数都标在NTC的丝印或数据手册里。比如我常用的“103AT-11”这颗意思就是10×10^3 Ω 10kΩA表示B值范围后面的11代表封装或者系列。不同厂家对丝印命名不完全一样最稳妥的办法还是查规格书上面会同时给出B值的测试区间比如B25/50表示25℃到50℃之间测得的B值。NTC的阻值和温度之间的关系在工程上一般用简化B值公式近似R_T R_25 × e^(B × (1/T - 1/298.15))其中T是热力学温度单位K298.15就是25℃对应的开尔文温度。这个公式不是严格物理推导出来的而是对实际NTC曲线在某个温度范围内做的指数拟合但胜在简单实用在-20℃到100℃这个日常区间里已经够用。真要追求高精度就得用更复杂的Steinhart-Hart方程后面细说。1.3 为什么说NTC的“线性度”永远没法和PT100比第一次拿万用表量NTC的人会很快发现温度从25℃升到30℃阻值可能掉了差不多2kΩ从75℃升到80℃阻值只掉了几百欧。这是因为NTC的阻值曲线是指数形的不是线性的。所以在方案设计的一开始就要有心理准备后面软件换算温度时绝不能用“电压除以灵敏度”这种线性思路。也正是因为这种非线性分压电路和ADC参数的选择就变得比想象中更重要。同样的ADC位数在低温段可能一个字的误差只对应零点几度在高温段可能一个字就对应好几度。下面我们就从电路开始把这条链路一步步走通。2. 分压采样电路设计一个电阻就能把温度变成电压2.1 上拉还是下拉先决定NTC放哪边经典NTC测温电路就是一个分压器一只固定电阻加一只NTC串联接到电源和地之间中间抽头进ADC。关键问题是固定电阻放上面还是下面。如果NTC放在下面、固定电阻接电源那热端电压Vout VCC × R_NTC / (R_NTC R_B)。温度升高时NTC阻值下降Vout下降ADC读数下降。 如果NTC放在上面、固定电阻接地那么Vout VCC × R_B / (R_NTC R_B)温度升高时Vout上升。两种接法都能用选哪种取决于你的ADC参考电压和故障保护习惯。我这次用的是下面接地型NTC一端接3.3V另一端接ADC采样点采样点再通过一只10kΩ固定电阻接地。这样做的好处是如果NTC意外断路采样点被固定电阻拉到0VADC读0程序会报“传感器断路”如果短路采样点直接3.3V读满量程报“传感器短路”。故障特征非常清楚。2.2 固定电阻阻值怎么选别想当然拿R25去凑很多人想都不想就把固定电阻选成和R25一样大也就是10kΩ配10kΩ这在25℃附近灵敏度确实不错因为在NTC阻值等于固定电阻时分压中点电压变化率最大。但如果你要测量的温度区间是0℃到100℃比如汽车冷却液温度10kΩ配10kΩ就不一定是最优解。固定电阻选大好还是选小好取决于你想要高温段还是低温段的分辨率想低温分辨率高比如测冰箱、冷库固定电阻可以比R25小一些让低温段NTC阻值很大时输出电压接近满量程斜率更陡想高温分辨率高比如测发动机冷却液、电机线包固定电阻可以比R25大一些让高温段NTC阻值变小时输出电压不至于降得太低ADC低端有效位数更多。实际拿B3435、R2510kΩ的NTC配R_B10kΩ用前面B值公式算几个典型温度点结果如下温度NTC阻值采样点电压(3.3V)ADC原始值(12bit)0℃约28.7kΩ约2.45V约303725℃10kΩ1.65V204860℃约2.98kΩ约0.77V约96180℃约1.66kΩ约0.47V约583100℃约0.99kΩ约0.30V约368可以看到0℃时ADC原始值3037100℃时368跨度接近2670个字按12位ADC的4096满量程看可用范围已经吃了70%。这个配置在0~100℃内算是比较均衡的。如果应用场景是常年80~100℃的高温段把固定电阻换成47kΩ左右高温段分辨率还能再提一点代价是低温段会早早进入满量程区。没有十全十美的配置只有适合你温度范围的配置动手前先拉一张表算明白比什么都强。2.3 自发热、电源参考和并联电容NTC流过电流就会发热发热会让它自己骗自己。NTC规格书里一般会标“耗散常数”单位是mW/℃比如1mW/℃意味着自身消耗1mW功率温度会升高约1℃。分压网络里电流越大自热误差越明显。用10kΩ固定电阻配10kΩ NTC、3.3V供电时常温下回路电流约165μANTC上的功耗约1.65V×0.165mA0.27mW。这种量级通常只带来不到0.3℃的偏差大部分场合可以接受。如果你用1kΩ的固定电阻电流直接放大十倍NTC自身功耗好几mW误差可能达到两三摄氏度那就不能不管了。电源方面分压电路最好直接接ADC的参考电压源。STM32F042的VDDA通常就是3.3VADC以VDDA作为参考电压所以NTC分压网络从VDDA取电压最合适不要图省事从某个带负载压降的3.3V LDO输出口直接取。采样点对地并联一只100nF电容能滤掉大部分高频干扰如果现场环境特别恶劣可以再加一个RC低通比如100Ω串联1μF电容但注意串联电阻不要太大否则会给ADC的内部采样电容带来额外充电时间压力反过来要求你提高ADC采样时间。3. 吃透STM32F042的ADC引脚、参考电压与采样时间3.1 STM32F042的ADC外设并不复杂STM32F042是Cortex-M0内核主频最高48MHz资源不算豪华但麻雀虽小五脏俱全。它内部集成了一路12位逐次逼近型ADC有多路外部通道和内部通道测量温度这种慢变信号绰绰有余。外部通道通过模拟多路开关复用到一个ADC上你同一时刻只能采一路但可以通过软件切换依次扫描好几个NTC。用CubeMX配置时只需要做几件事把PA0配置为ADC模拟输入对应ADC_IN0选择ADC的时钟分频保证ADCCLK不超过14MHz选择12位分辨率、右对齐采样时间选最长或接近最长。ADC时钟和PCLK的关系要注意。如果我系统时钟配到48MHzAPB2也是48MHz那ADC预分频器就得选4得到12MHz在14MHz限制以内。如果分频选224MHz直接超规格ADC转换结果会不稳定这是新手特别容易忽略的。CubeMX里还有一个选项叫“Continuing Conversion Mode”即连续转换模式。因为NTC温度变化非常慢我不需要连续转换每次软件触发一次转换、读一个值就够了甚至可以用定时器触发固定频率采样然后做软件平均更稳。3.2 采样时间为什么不能设得太短ADC采样一个模拟量过程可以粗略拆成两步先是采样开关闭合内部采样电容充电到和外部电压一致然后采样开关断开逐次逼近寄存器开始一位一位比较。采样电容电容值一般在几pF到十几pF之间如果外部信号源内阻很大充电时间就会变长。NTC分压网络的等效输出阻抗不是0尤其在低温段NTC阻值几十千欧分压点等效内阻可能大得吓人。等效内阻怎么估算NTC在0℃时约28.7kΩ并联固定电阻10kΩ等效源阻抗约28.7×10/(28.710)7.4kΩ。这个值已经明显高于ADC手册里建议的低阻抗源范围。如果采样时间还按最短的1.5周期来采样电容可能还没充满电就被切断测出来的电压会偏低而且这种误差和温度强相关低温段偏得更厉害最后算出来的温度曲线就是歪的。解决方法是把采样时间设为最长。STM32F042ADC支持最长239.5个ADC时钟周期的采样时间在12MHz时钟下接近20μs。对NTC这种信号源阻抗接近10kΩ的场合这个采样时间足够让内部采样电容稳稳充电。20μs采一次温度慢吗完全不影响因为温度本身的响应时间少说也要几百毫秒把换算时间成本花在采样稳定上非常划算。3.3 一个最容易踩的性能误区有人说STM32F042的ADC在12MHz下最大采样率能到1Msps刻意去追求快速连续采样。这对测量NTC完全没意义。NTC测温瓶颈从来不是ADC速度而是传感器自身的热时间常数。你每秒采100万个点采到的其实还是同一个温度附近的噪声。真正该做的是降低采样率、延长采样时间、增加软件平均把有效分辨率用在刀刃上。还有一种情况是同时接好几个NTC比如测冷却液的进出口温度。这时可以每次切换通道前先让ADC对地放电或者至少在通道切换后等待一小段时间再开始采样。STM32F042可以关闭连续模式每次软件触发只转换一个通道读完后程序再去切下一个通道天然避免了通道间串扰。4. 从ADC原始值逆推温度公式法和查表法4.1 原始值先换算成NTC阻值这是第一个关键跳变无论你用哪种温度计算方法第一步都一样把ADC原始值转回NTC的实际阻值。对于NTC在下、固定电阻R_B接地的分压结构采样点电压Vout ADC值 / 4095 × VREFNTC阻值R_NTC Vout × R_B / (VREF - Vout)很多教程喜欢把这两个公式合并成一步直接算温度但工程上我强烈建议保留中间变量R_NTC。原因很实际你调试时可以把R_NTC打印出来和万用表实际测量值对一下立刻就能判断是电路问题还是软件问题。如果直接把ADC变成一个“看似合理”的温度出错了根本不知道是哪一环引入的。如果分压结构反过来NTC在上面、固定电阻R_B接地公式就变成R_NTC R_B × (VREF - Vout) / Vout方向反了公式千万别套错。我见过有人在代码里把两个公式搞混最后温度显示成负一百多度查了半天才发现是Vout的位置写反了。4.2 用B值公式直接算温度代码最少B值公式反解温度T 1 / ( 1/T0 (1/B) × ln(R_NTC / R25) )对应代码非常短#define VREF 3.3f #define ADC_FULL 4095.0f #define R_BIAS 10000.0f /* 固定分压电阻 */ #define R25 10000.0f /* NTC在25℃的阻值 */ #define B_CONST 3435.0f /* NTC材料常数 */ #define T0_K 298.15f /* 25℃对应的开尔文温度 */ float adc_to_ntc_resist(uint32_t raw) { float vin (float)raw / ADC_FULL * VREF; float rntc vin * R_BIAS / (VREF - vin); return rntc; } float ntc_resist_to_temp_c(float rntc) { float t_k 1.0f / (1.0f / T0_K (1.0f / B_CONST) * logf(rntc / R25)); return t_k - 273.15f; }如果NTC在分压电路的上方记得把adc_to_ntc_resist里的公式换成R_NTC R_BIAS * (VREF - vin) / vin。这里有个性能问题需要说清楚。STM32F042的Cortex-M0核心没有硬件浮点单元上面的代码里用了float和logf函数。一次logf计算在软件浮点下可能会耗掉不少CPU周期但温度测量频率低就算每秒测10次也毫无压力。如果硬要在弱单片机上做高频运算或者工程里完全没有浮点库那就走下面的查表插值路线。4.3 查表插值法Cortex-M0上更顺手的做法查表的思路非常朴素把要用的温度范围按每1℃或每5℃取一个点提前算好对应的NTC阻值存成一张常量表然后在运行时找到测量阻值落在哪两个表项之间做线性插值。好处有三个不需要浮点、不需要log函数、响应速度极快。缺点是表本身占Flash空间而且你要保证查表区间和实际环境温度匹配。一张从-20℃到120℃、步长1℃的表总共141个点如果存uint16_t阻值也就两百多字节对STM32F042的几十KB Flash来说完全无所谓。把阻值转成温度时直接查表typedef struct { int16_t temp_x10; /* 温度值单位0.1℃ */ uint16_t resist; /* 对应NTC阻值单位Ω */ } ntc_temp_entry_t; static const ntc_temp_entry_t ntc_table[] { { -200, 154300 }, { -100, 91410 }, /* 中间按1℃间隔填表 */ { 1200, 6420 }, }; #define NTC_TABLE_CNT (sizeof(ntc_table) / sizeof(ntc_table[0])) float ntc_resist_to_temp_lookup(float rntc) { if (rntc ntc_table[0].resist) { return ntc_table[0].temp_x10 / 10.0f; } if (rntc ntc_table[NTC_TABLE_CNT - 1].resist) { return ntc_table[NTC_TABLE_CNT - 1].temp_x10 / 10.0f; } for (uint16_t i 0; i NTC_TABLE_CNT - 1; i) { if (rntc ntc_table[i].resist rntc ntc_table[i 1].resist) { float t0 ntc_table[i].temp_x10 / 10.0f; float t1 ntc_table[i 1].temp_x10 / 10.0f; return t0 (rntc - ntc_table[i].resist) * (t1 - t0) / (float)(ntc_table[i 1].resist - ntc_table[i].resist); } } return 0.0f; }这张表怎么生成最好用官方工具或者Excel。很多NTC厂家会提供B值对应的电阻温度表直接用就行。如果你想自己算也可以按B值公式拉一张Excel表然后把mΩ或者Ω的整数部分导成数组。关键提醒表里的阻值数据一定要用你手上那颗NTC的实际参数去生成不同B值对应的表差异很大不要拿别人的表硬套。4.4 一个完整数字例子验证整个链路假设刚才的10kΩ/3435分压电路ADC读到原始值2280那Vout 2280 / 4095 × 3.3 ≈ 1.837VR_NTC 1.837 × 10000 / (3.3 - 1.837) ≈ 12560Ω。查表或B值公式反推12560Ω对应的温度大约在18℃附近。这个过程对应ADC值、NTC阻值、温度三者可以互相换算我调试时会把三个值一起在串口打印出来对照着看一眼就能定位问题。5. 完整例程与集成从CubeMX到串口输出5.1 CubeMX的关键配置用STM32CubeMX生成STM32F042工程我习惯这样配置RCC选择HSI或外接晶振按需配置时钟树把SYSCLK配到48MHz确保ADC时钟不超过14MHzPA0配置为ADC_IN0不用开上拉下拉因为分压电路已经提供了明确电平USART1或USART2作为调试串口输出温度值添加一个1ms滴答定时器作为软件定时基准或者直接用HAL_Delay。ADC初始化代码HAL会自动生成核心参数如下hadc.Instance ADC1; hadc.Init.ClockPrescaler ADC_CLOCK_SYNC_PCLK_DIV4; hadc.Init.Resolution ADC_RESOLUTION_12B; hadc.Init.DataAlign ADC_DATAALIGN_RIGHT; hadc.Init.ContinuousConvMode DISABLE; hadc.Init.DiscontinuousConvMode DISABLE; hadc.Init.ExternalTrigConv ADC_SOFTWARE_START; hadc.Init.EOCSelection ADC_EOC_SINGLE_CONV;CubeMX默认生成的采样时间不一定是最长的如果你选了ADC_IN0后发现采样时间只有1.5周期务必手动改掉sConfig.Channel ADC_CHANNEL_0; sConfig.Rank ADC_REGULAR_RANK_1; sConfig.SamplingTime ADC_SAMPLINGTIME_239CYCLES_5;5.2 读取ADC并做软件平均NTC信号源阻抗高单次采样结果会带一些随机噪声最简单的处理就是多次采样取平均。我用的比较多的是连续采16次取中位值平均或者直接去掉最大最小后求平均代码如下#define ADC_AVG_CNT 16 uint32_t adc_read_average(void) { uint32_t sum 0; uint32_t max 0; uint32_t min 4095; for (uint16_t i 0; i ADC_AVG_CNT; i) { HAL_ADC_Start(hadc); if (HAL_ADC_PollForConversion(hadc, 100) HAL_OK) { uint32_t val HAL_ADC_GetValue(hadc); sum val; if (val max) max val; if (val min) min val; } HAL_ADC_Stop(hadc); } sum - max min; return sum / (ADC_AVG_CNT - 2); }这样平均后的结果比直接读一次稳定得多。16次采样每次ADC转换时间在20μs左右总耗时不到半毫秒对测温系统来说可以忽略不计。5.3 主循环里的完整流程主循环逻辑很简单读平均ADC值转电阻转温度格式化输出然后延时几百毫秒再采下一次。温度是慢变量没必要紧赶慢赶。int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_ADC1_Init(); MX_USART1_UART_Init(); while (1) { uint32_t raw adc_read_average(); float rntc adc_to_ntc_resist(raw); float temp_c ntc_resist_to_temp_c(rntc); char buf[64]; snprintf(buf, sizeof(buf), ADC%lu R%.1f T%.2f C\r\n, (unsigned long)raw, rntc, temp_c); HAL_UART_Transmit(huart1, (uint8_t *)buf, strlen(buf), 100); HAL_Delay(500); } }如果你用的工程开了MBED或者上面的printf重定向有问题直接用HAL_UART_Transmit发送字符串最稳。NTC测温不需要什么实时操作系统也不需要DMA裸机排队采集完全够用。5.4 串口打印浮点的注意事项Cortex-M0工程里要把float通过printf打出来默认的MicroLib可能做不到完整浮点输出。我一般不开MicroLib而是使用C标准库并在串口重写fputc或者干脆用snprintf来格式化然后走HAL_UART_Transmit发送。如果你用IAR勾选“Printf formatter”为Full否则float打印出来是空的或者固定0.00这个问题很容易让人误以为温度计算函数写错了。6. 实测校准心得比公式更重要的工程细节6.1 噪声和跳字从硬件到软件逐个排除我第一次把电路搭好后串口里温度值总在稳定值附近上下跳0.3℃左右排查思路是这样先看电源。NTC分压电路直接接VDDA但面包板供电来自USB转串口模块的3.3V纹波比较大而且面包板的长跳线本身就是天线于是先在采样点对地并联100nF电容乱跳幅度立刻小了不少。再看采样时间。最初CubeMX生成的采样时间是13.5周期改用239.5周期后每次转换值稳定很多因为内部采样电容有足够时间完全充电。最后是软件平均。做了16次去极值平均之后最终显示温度基本能稳定在±0.1℃以内。这三层下来硬件噪声和软件随机误差基本都被压制住了。6.2 自加热误差的现场表现与对策有次我把NTC直接放在绝缘胶带缠好的电机外壳上测出来的温度比红外点温枪高2℃多。一开始怀疑是NTC位置不对后来量了分压电路的回路电流才发现固定电阻被我为了“提高灵敏度”换成了1kΩ常温下NTC功耗接近2.7mW自热升温自然明显。NTC自加热误差在中高温段尤其危险因为高温段NTC阻值小回路电流更大自热更严重。对策有三条尽量提高固定电阻阻值限制回路电流增大NTC和被测对象的接触面积让热量导走软件里做修正但修正量会随温度变化不如从电路源头压低功耗。6.3 校准不用复杂单点和两点校准就够了公式算出来的温度实际总会和标准温度计有几度偏差主要来源是固定电阻R_B的实际阻值和标称值有容差比如1%就是1%NTC自己的R25和B值有批次公差VREF和3.3V标称值不完全一致ADC读到的满量程有偏差。工程上做单点校准最简单把NTC和标准温度计放在同一个恒温环境里比如冰水混合物0℃读取ADC算出来的R_NTC误差把这个误差作为增益修正系数写进程序整个量程都会改善。更讲究一点做两点校准比如0℃和60℃各取一个点然后用两点线性修正去校正最终温度结果。NTC这种元件不要指望出厂就完全一致批量产品必须要校准。一个很实用的做法把固定电阻换成0.1%精密电阻VREF如果要求更高就选带外部VREF引脚的MCU型号并加基准源。但对于绝大多数消费级和车载级应用用1%的电阻加上单点校准完全能把误差控制在±1℃以内没必要为了追求极限精度把BOM成本抬上去。6.4 汽车冷却液温度测量场景中的几个“额外功课”回到我开头说的汽车冷却液温度测量。这个场景和桌面上搭个面包板完全两码事至少要多考虑几件事NTC要选带密封外壳的品种防止冷却液腐蚀和泄漏外壳热阻会影响响应速度但可靠性优先线束可能很长采样点离MCU很远需要在传感器侧就近做RC滤波否则线束拾取的EMI会让ADC跳动非常夸张电源域可能是12V电瓶经过LDO降到3.3VLDO在冷启动和发电机波动时输出电压会有起伏而ADC以VDDA为参考VDDA不稳测温结果必然跟着抖传感器失效模式要定义清楚断路、短路、温度超上限、温度跳变过快分别对应什么故障码都要在软件里做好。这些任务是NTC测温“电路设计”的完整一环。单纯把ADC读数换算成温度只是万里长征第一步真正决定产品靠不靠谱的其实是分压电路、滤波电路、电源处理和软件异常分支一起构成的系统设计。搞完这个项目让我最深刻的一点是NTC测温看着简单但从阻值到电压再到数字量每一环都有误差来源。不要迷信公式算出来的温度更不要一上来就堆算法先把硬件分压、采样时间和滤波处理干净再考虑校准温度值自然就准了。后面有空我打算再把多点NTC扫描和故障自诊断一起整理出来如果你也在STM32F042或者其他Cortex-M0单片机上做类似的NTC项目欢迎按这个套路先把电路跑通再回来一起聊里面那些细节坑。
返回列表