
1. 项目缘起一个被VCC电压“绑架”的温度读数最近在做一个需要精确测温的小项目用的是最常见的NTC热敏电阻。电路也简单一个NTC和一个固定电阻串联分压然后送到MCU的ADC引脚读取电压值再通过公式或者查表换算成温度。按理说这是电子工程师的“Hello World”级应用但我发现测出来的温度值总有点飘忽不定尤其是当系统的供电电压VCC因为电池电量变化或者负载波动而有轻微起伏时温度读数也跟着“跳舞”。这让我意识到我们习以为常的测温电路其实精度严重受制于供电电压的稳定性。如果VCC是理想的5.000V那没问题但现实中它可能是4.95V也可能是5.05V这微小的变化会直接导致分压点电压的绝对误差最终让温度读数产生几度甚至十几度的偏差。这显然无法满足对精度有要求的应用比如恒温控制、电池温度监控或者高精度数据采集。所以这次我们就来彻底解决这个问题聊聊如何从电路设计和软件算法两个层面提高NTC测温的精度并从根本上消除VCC电压波动带来的影响。2. NTC测温基础与精度瓶颈分析在动手改进之前我们得先搞清楚标准电路的问题出在哪里以及精度到底被哪些因素限制住了。2.1 经典分压电路与误差来源最普遍的NTC测温电路如下图所示此处为文字描述将NTC热敏电阻Rt与一个精度较高的参考电阻Rref串联接在VCC和GND之间。两者的连接点即分压点接入MCU的ADC输入引脚。ADC测量的是这个分压点的电压值Vadc。根据分压公式Vadc VCC * [Rref / (Rt Rref)]而我们关心的NTC电阻值Rt可以通过公式反推Rt Rref * (VCC / Vadc - 1)得到Rt后再通过NTC的B值公式或查表法计算出对应的温度T。这个链条上的每一个环节都可能引入误差VCC的绝对误差这是本次要解决的核心问题。公式中VCC作为一个乘数因子直接参与计算。假设VCC标称5V实际为4.95V误差-1%那么计算出的Rt就会产生近1%的误差传递到温度上在25°C附近1%的电阻误差可能导致约0.25°C的温度误差。如果VCC波动更大误差不可接受。ADC的参考电压Vref大多数MCU的ADC参考电压Vref直接取自VCC或与VCC相关。这意味着VCC的波动不仅影响了分压比还影响了ADC的“尺子”本身。即使VCC稳定如果Vref不精准ADC读数也不准。电阻精度与温漂NTC本身有初始精度误差如±1% ±5%B值也有误差。参考电阻Rref的精度和温度系数也会直接影响分压比。ADC自身误差包括偏移误差、增益误差、积分非线性等。这些属于ADC本身的性能参数。自热效应流过NTC的电流会使其自身发热导致测量温度高于环境温度。尤其在低阻值NTC或高VCC电压下更明显。2.2 为什么“查表法”和“计算法”都救不了VCC误差在热词中看到“热敏电阻温度计算查表准还是计算准”的讨论。无论是查表预先将Rt-温度对应关系存入数组还是实时计算使用Steinhart-Hart方程等其前提都是已知准确的Rt值。而我们的Rt值是通过Vadc和VCC计算出来的。如果VCC这个基础数据是错的那么无论后续用多么精确的公式或表格得出的温度都是错的。这就好比用一把刻度不准的尺子去量东西无论你后面用多高级的算法处理测量数据都无法得到物体的真实长度。因此提升精度的首要任务就是获得一把“准尺子”或者找到一种不依赖这把“尺子”绝对精度的测量方法。3. 硬件升级从依赖VCC到依赖基准源要消除VCC影响最根本的硬件思路是让ADC的测量不依赖于VCC的绝对值。这里有几个经典的电路方案。3.1 方案一使用外部精密基准电压源这是提升ADC测量系统精度最直接、最有效的方法。为ADC模块提供一个独立、稳定、精确的电压基准源如REF30303.0V、REF50252.5V并将这个基准电压作为ADC的参考电压Vref。电路连接基准芯片输出一个稳定的电压例如2.5V给MCU的VREF引脚。分压电路NTC和Rref仍然串联但上拉电源不再接变化的VCC而是接这个稳定的基准电压Vref。ADC测量ADC以Vref为参考测量分压点电压。优势一劳永逸ADC的“尺子”Vref和被测电压的“源头”上拉电压是同一个稳定电压。计算Rt的公式变为Rt Rref * (Vref / Vadc - 1)。由于分子分母中的Vref是同一个值它的绝对精度虽然重要但其波动性被完全抵消了。只要Vref稳定VCC哪怕从5V掉到3.3V只要还能维持基准芯片和MCU工作测温精度就不受影响。精度高精密基准源的温度漂移通常很低几个ppm/°C初始精度也很高±0.1%。注意事项与实操成本与功耗增加了一颗芯片和相应的滤波电容。MCU支持需确认你的MCU的ADC是否支持外部参考电压输入以及引脚是否独立。电平匹配确保Vref的值在ADC的输入量程内并且NTC分压后的电压不超过Vref。布线基准电压线要尽量短靠近VREF引脚并做好去耦。3.2 方案二差分测量与比率计法这是一个非常巧妙且高性价比的方法尤其适合那些没有独立VREF引脚或者想节省一颗基准芯片的场景。其核心思想是同时测量NTC分压电压和VCC或一个固定分压比下的VCC通过计算两者的比值来消除VCC。电路连接以测量VCC本身为例通道A测量NTC电压NTC与Rref串联接VCC中点接ADC通道A。通道B测量VCC比例用两个高精度、低温漂的电阻如R1, R2对VCC进行分压分压点接ADC通道B。选择R1和R2使得分压比k R2/(R1R2)为一个方便的比值如0.5、0.25。同步/快速交替采样使用MCU的ADC多通道扫描或双ADC交替采样如热词中提到的stm32 双adc交替采样、adc交替采样功能尽可能接近地同时采集通道A和通道B的电压值记为Vadc_A和Vadc_B。计算原理对于通道BVadc_B VCC * k公式1对于通道AVadc_A VCC * [Rref / (Rt Rref)]公式2我们用公式2除以公式1Vadc_A / Vadc_B [Rref / (Rt Rref)] / k可以看到VCC被完美地约掉了最终推导出Rt Rref * ( k * Vadc_B / Vadc_A - 1)优势彻底消除VCC影响计算中完全不包含VCC只与两个ADC读数的比值、固定电阻Rref、R1、R2有关。对ADC参考电压要求降低因为计算的是比值只要ADC在两个通道测量期间的参考电压是稳定的即使是波动的VCC并且ADC的线性度良好结果就准确。这降低了对Vref绝对精度的依赖。成本低仅需增加两个高精度电阻。实操要点与避坑电阻选型R1、R2的精度和温漂现在直接成为系统精度的关键。建议使用0.1%精度、25ppm/°C温漂的金属膜电阻。采样同步性如果VCC在两次采样间有快速波动会引入误差。因此要利用MCU的同步采样或交替采样模式如STM32的Dual/Triple模式让两次采样的时间间隔极短。如果只能顺序采样则要确保VCC波动频率远低于采样率。ADC误差一致性虽然抵消了VCC但ADC的偏移误差和增益误差如果两个通道不一致仍会影响结果。可以通过校准来减小这部分影响。代码示例思路// 假设使用STM32 HAL库配置了ADC1和ADC2在交替模式下工作分别采样ChA和ChB uint32_t adc_value_A, adc_value_B; float voltage_A, voltage_B; // 假设已根据ADC位数转换为电压值但Vref未知且可能波动 float k 0.5f; // R1R2时的分压比 float Rref 10000.0f; // 10k 参考电阻 // 获取一组同步/交替采样的值 adc_value_A get_ADC1_value(); adc_value_B get_ADC2_value(); // 计算比值注意这里不需要知道绝对电压值 float ratio (float)adc_value_A / (float)adc_value_B; // 计算Rt (VCC已被约去) float Rt Rref * ( (k / ratio) - 1.0f ); // 再利用Rt通过查表或公式计算温度 float temperature calculate_temperature(Rt);3.3 方案三使用集成式ADC芯片如ADS1220对于追求极致精度的应用可以考虑使用外置的高精度、低噪声Σ-Δ型ADC芯片例如TI的ADS1220热词中提及。这类芯片通常内置了高精度、低漂移的基准电压源并支持多种高阻态、低漂移的激励电流源IDAC可以实现更先进的测量方式比如比例测量法或四线制测量从根本上消除引线电阻和电源噪声的影响。以ADS1220比例测量法为例利用芯片内部的IDAC输出一个精确的电流I流过NTC电阻Rt。测量Rt两端的电压V_rt I * Rt。用同一个电流I流过一颗高精度的参考电阻R_ref。测量R_ref两端的电压V_ref I * R_ref。计算比值V_rt / V_ref Rt / R_ref。电流I被约掉结果只与Rt和R_ref的比值有关与激励电流的绝对值无关精度取决于R_ref。这种方式将测量精度转移到了外部高精度参考电阻R_ref和ADC自身的性能上完全与系统电源VCC解耦可以达到非常高的测量精度0.01°C级别。4. 软件赋能滤波、校准与算法补偿硬件方案奠定了高精度的基础但软件算法能进一步打磨结果并应对一些硬件无法完全消除的误差。4.1 ADC采样策略与滤波算法即使硬件上解决了VCC问题ADC采样依然会引入噪声。合理的采样策略和滤波算法至关重要。采样次数与平均单次采样不可靠。通常采集16、32、64甚至更多次然后取算术平均这是最简单有效的抑制随机噪声的方法。注意对于多通道比值法应对每个通道的原始读数分别进行多次采样和平均然后再计算比值。采样时机避开MCU内部或外部的大功率开关动作如PWM变化、电机启停、无线模块发射这些都可能引起电源噪声。数字滤波滑动平均滤波简单易实现但对突发性干扰抑制差。中值滤波对脉冲噪声尖峰有很好的抑制作用。可以先中值滤波再平均。一阶低通滤波软件实现filtered_value α * new_value (1-α) * filtered_value。参数α0α1决定响应速度和平滑度。适合缓慢变化的温度信号。卡尔曼滤波如果系统模型比较明确如温度变化率有物理限制卡尔曼滤波可以提供最优估计。但实现相对复杂。一个实用的复合滤波函数示例C语言#define SAMPLE_SIZE 16 #define MEDIAN_FILTER_SIZE 5 float read_filtered_temperature(void) { uint32_t raw_samples_A[SAMPLE_SIZE]; uint32_t raw_samples_B[SAMPLE_SIZE]; float filtered_ratio; // 1. 采集一组原始数据假设已实现交替采样 for(int i0; iSAMPLE_SIZE; i) { raw_samples_A[i] ADC_GetValue_ChannelA(); raw_samples_B[i] ADC_GetValue_ChannelB(); } // 2. 对每个通道的原始数据分别进行中值滤波可选如果噪声中有尖峰 // median_filter(raw_samples_A, SAMPLE_SIZE, MEDIAN_FILTER_SIZE); // median_filter(raw_samples_B, SAMPLE_SIZE, MEDIAN_FILTER_SIZE); // 3. 计算每个采样时刻的比值然后对比值序列求平均更优 // 或者先对A、B通道分别求平均再计算比值次优但简单 uint32_t sum_A 0, sum_B 0; for(int i0; iSAMPLE_SIZE; i) { sum_A raw_samples_A[i]; sum_B raw_samples_B[i]; } float avg_ratio (float)sum_A / (float)sum_B; // 注意防止除零 // 4. 可选对连续计算出的avg_ratio进行一阶低通滤波 static float last_filtered_ratio 0; float alpha 0.2f; // 滤波系数根据实际调整 filtered_ratio alpha * avg_ratio (1-alpha) * last_filtered_ratio; last_filtered_ratio filtered_ratio; // 5. 使用filtered_ratio计算Rt和温度 float Rt R_REF * ( (K_DIVIDER / filtered_ratio) - 1.0f ); return convert_Rt_to_temperature(Rt); }4.2 系统校准弥补硬件的不完美没有任何硬件是理想的。校准的目的是测量出系统的实际误差并在软件中进行补偿。两点校准法针对ADC和传感器整体低温点将NTC置于一个已知的、稳定的低温环境如冰水混合物0°C记录此时ADC计算出的温度值T_read_low。高温点将NTC置于一个已知的、稳定的高温环境如沸水100°C需考虑海拔修正或使用校准过的恒温槽记录T_read_high。计算斜率和偏移假设真实温度T_real与读取温度T_read是线性关系在NTC的局部范围内近似成立T_real gain * T_read offset。 代入两点0 gain * T_read_low offset100 gain * T_read_high offset解方程得到gain和offset。后续所有读数都用这个公式修正。gain 100.0 / (T_read_high - T_read_low)offset -gain * T_read_low参考电阻校准针对比值法 在比值法电路中关键参数是分压比k和参考电阻Rref。我们可以通过一次测量来校准它们。在已知的、稳定的室温下例如25°C用精度更高的温度计测量环境温度T_cal。根据T_cal利用NTC的B值公式反推出此时NTC电阻的理论值Rt_theory。系统上电测量此时的ADC比值ratio_cal。根据公式Rt_theory Rref * ( k / ratio_cal - 1) 由于Rref和k可能都不准但它们的乘积作为一个整体是我们可以校准的。我们可以定义一个校准因子C Rref * k。 那么公式变为Rt_theory C * (1 / ratio_cal) - Rref。这里有两个未知数C和Rref。 一个更实用的方法是假设Rref是准确的使用高精度电阻只校准k。那么k_calibrated ratio_cal * (Rt_theory / Rref 1)。将计算出的k_calibrated存入Flash替换掉理论值k用于后续所有计算。4.3 温度计算查表与公式的抉择得到准确的Rt后最后一步是转换为温度。查表法优点速度快尤其适合没有浮点单元的MCU。可以通过将Rt值作为索引直接查找对应的温度值或通过相邻点线性插值。缺点占用存储空间。精度取决于表格的密度。如果NTC的R-T特性非线性严重需要更密的表格。实操技巧通常存储的是(电阻值 温度)对。为了提高查找效率和精度可以存储(log(R), 温度)对因为NTC的阻值对数与温度倒数近似成线性关系来自B值公式这样线性插值误差更小。或者只存储关键点的数据中间用线性插值。公式法Steinhart-Hart方程优点精度高存储空间占用极小只需要几个系数。缺点计算涉及对数、指数和浮点运算对MCU计算能力有要求。标准公式1/T A B*ln(R) C*[ln(R)]^3其中T是开尔文温度A B C是NTC厂家提供的或通过三点校准拟合出的系数。简化公式B值公式1/T 1/T0 (1/B) * ln(R/R0)其中T0是参考温度如25°C298.15KR0是T0时的阻值B是B值常数。这个公式在温度范围不大时如0-50°C精度足够计算也更简单。个人建议对于通用性强的产品如果MCU资源允许优先使用Steinhart-Hart公式它更灵活、精度有保障。对于成本极其敏感或主频很低的MCU使用查表插值法。但无论哪种方法前提都是你已经获得了准确的Rt值——这正是我们通过硬件改进和比值法所要保证的。5. 实战案例基于STM32的双ADC比值法测温实现让我们以一个具体的实战案例将前面的理论串联起来。假设我们使用STM32G4系列MCU因其ADC性能较好采用双ADC交替采样模式实现比值法测温。5.1 硬件连接与CubeMX配置硬件连接NTC10kΩ B3950与10kΩ 0.1%精度参考电阻串联中点接PA0ADC1_IN1。两个10kΩ 0.1%精度电阻分压VCC分压比k0.5分压点接PA1ADC2_IN1。ADC的参考电压Vref接VCC因为我们用比值法不依赖其绝对精度。CubeMX关键配置ADC1 ADC2均设置为“Independent mode”。Dual ADC Mode选择“Alternate trigger mode”。这样两个ADC会交替采样。ADC1扫描模式使能连续转换禁用。添加通道1PA0采样时间设为足够长如47.5 cycles。ADC2扫描模式使能连续转换禁用。添加通道1PA1采样时间与ADC1一致。触发源选择一个定时器如TIM1的TRGO事件作为两个ADC的共同触发源。设置一个合适的触发频率如100Hz。DMA为ADC1和ADC2分别配置DMA模式为循环模式将数据搬运到内存中的数组。在交替模式下通常ADC1作为主其DMA传输完成一半和全部时会产生中断我们可以在这个中断里处理一组完整的数据包含ADC1和ADC2的采样值。5.2 软件实现核心代码逻辑// 定义 #define SAMPLE_NUM 32 #define R_REF 10000.0f // 10kΩ #define K_DIVIDER 0.5f // 分压比 #define B_VALUE 3950.0f #define R0 10000.0f // 25°C时的阻值 #define T0 298.15f // 25°C对应的开尔文温度 volatile uint32_t adc1_buffer[SAMPLE_NUM]; volatile uint32_t adc2_buffer[SAMPLE_NUM]; volatile uint8_t dma_half_complete_flag 0; volatile uint8_t dma_full_complete_flag 0; // DMA半传输/全传输中断回调函数 void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc) { if(hadc-Instance ADC1) { dma_half_complete_flag 1; // 前半组数据就绪 } } void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { if(hadc-Instance ADC1) { dma_full_complete_flag 1; // 后半组数据就绪 } } // 获取温度的主函数 float get_temperature_ratio_method(void) { uint32_t sum_adc1 0, sum_adc2 0; float ratio, Rt, temp_k, temp_c; // 等待一组完整数据采集完成例如在定时中断或主循环中检查标志位 while(!dma_half_complete_flag !dma_full_complete_flag) { // 可以加入超时机制 } __disable_irq(); // 短暂关中断安全地读取数据 if(dma_half_complete_flag) { // 处理前半部分数据 for(int i0; iSAMPLE_NUM/2; i) { sum_adc1 adc1_buffer[i]; sum_adc2 adc2_buffer[i]; } dma_half_complete_flag 0; } else if(dma_full_complete_flag) { // 处理后半部分数据 for(int iSAMPLE_NUM/2; iSAMPLE_NUM; i) { sum_adc1 adc1_buffer[i]; sum_adc2 adc2_buffer[i]; } dma_full_complete_flag 0; } __enable_irq(); // 计算平均值和比值 float avg_adc1 (float)sum_adc1 / (SAMPLE_NUM/2.0f); float avg_adc2 (float)sum_adc2 / (SAMPLE_NUM/2.0f); ratio avg_adc1 / avg_adc2; // 核心比值 // 计算Rt (VCC已被消除) Rt R_REF * ( (K_DIVIDER / ratio) - 1.0f ); // 使用B值公式计算温度开尔文 temp_k 1.0f / ( (1.0f/T0) (1.0f/B_VALUE) * logf(Rt/R0) ); temp_c temp_k - 273.15f; // 转换为摄氏度 // 可以在此处加入一阶低通滤波 static float filtered_temp 25.0f; float alpha 0.1f; filtered_temp alpha * temp_c (1-alpha) * filtered_temp; return filtered_temp; }5.3 实测效果与调试心得在实际搭建电路并烧录代码后我进行了对比测试使用传统单通道接法VCC从5.0V调整到4.8V温度读数变化超过1.5°C。使用双ADC比值法VCC在4.7V到5.3V之间变化温度读数波动小于0.1°C。效果立竿见影。调试中遇到的坑与解决ADC采样时间不足最初采样时间设得太短导致采样值不稳定比值波动大。增加采样时间让内部采样电容充分充电后明显改善。经验对于高阻抗源如分压电路ADC采样时间要设得足够长可以尝试从最大值往下降直到找到稳定采样的最短时间。电源噪声干扰当板子上有电机驱动或开关电源工作时即使比值法读数也会有毛刺。解决方法是在NTC分压点并接一个0.1uF的陶瓷电容到地形成低通滤波。注意电容不宜过大否则会影响温度变化的响应速度。电阻自热为了测试精度我使用了10kΩ的NTC和10kΩ的参考电阻在5V供电下流过NTC的电流约0.25mA功耗约0.06mW自热效应可以忽略。但如果使用更大阻值的NTC如100kΩ并降低串联电阻电流会变小但噪声可能增大需要权衡。原则在满足ADC输入阻抗要求和噪声水平的前提下尽量减小流过NTC的电流通常0.1mA以降低自热误差。代码中的浮点运算在无FPU的MCU上频繁进行logf和除法运算比较耗时。如果温度更新率要求不高如1Hz问题不大。如果要求快可以改用查表法或者使用定点数运算库来近似计算对数。经过这一番从硬件到软件的折腾这个测温系统终于摆脱了对VCC电压的依赖即使在电池供电电压逐渐下降的场景下也能提供稳定可靠的高精度温度数据。