ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

STM32 NTC测温实战:从ADC采样到温度换算的完整实现

STM32 NTC测温实战:从ADC采样到温度换算的完整实现 简介这是一份专为STM32微控制器设计的NTC热敏电阻驱动库版本2.0.1面向嵌入式开发者与物联网项目解决温度采集、阻值换算与滤波处理等常见问题。压缩包共16个文件含4个h头文件、3个cpp实现、4个ino示例及library.properties、README.md、LICENSE等文档/配置资源整体约46KB目录结构简洁。代码针对STM32做过优化兼顾紧凑性与计算效率并提供SmoothThermistor、AverageThermistor等多类滤波算法可有效抑制ADC采样噪声提高温度读数稳定性。示例涵盖STM32、串口打印、平滑/平均测量等场景文档与API说明同步更新方便直接移植到真实项目。目前已有168人学习下载适合从入门到进阶的开发者在此基础上快速构建可靠、低功耗的温度监测方案。1. 用 STM32 做 NTC 测温先弄懂 NTC_Thermistor 这个库在解决什么NTC 热敏电阻是嵌入式测温里性价比最高的选择一个 0603 封装的热敏电阻加一个普通贴片电阻物料成本不到五毛钱就能在 STM32 项目里把 -20°C 到 100°C 范围内的温度读回来。但真正做过一版的人都知道NTC 到 ADC、再到温度的链路里最容易出问题的并不是 ADC 本体的 12 位分辨率而是 B 值公式、分压电阻和参考电压这三件事。标题里 NTC_Thermistor-2.0.1 这类驱动库核心就是把“ADC 原始值 → NTC 电阻值 → 摄氏温度”的换算和滤波封装成一层让做 STM32 的工程师不用每次重新推导一遍公式。下面我会用一个最小驱动把这条路走通再讲清楚参数怎么设、坑在哪里。2. 从 NTC 到 STM32 内部 ADC温度换算的数学原理与分压电路选型2.1 NTC 的电阻-温度特性与 B 值公式NTC 全称负温度系数热敏电阻温度升高时电阻值指数下降。标准近似公式是R_T R25 * exp(B * (1/T - 1/T25))其中 R25 是 25°C 时的标称阻值T 和 T25 都是开尔文温度B 是材料常数。这个公式做工程估算足够了实际 NTC 在宽温度范围下会和多项式查表有偏差但 B 值公式的优点是不需要存表一个 float 运算就能得到连续温度。在 STM32 上做选型时我一般会先把这条曲线算出来确认在目标测量区间内电阻变化是否适合分压。下面这段代码可以快速算出一个 NTC 在给定温度下的期望电阻值用来挑分压电阻#include math.h float ntc_resistance_at(float temp_c, float r25, float b) { float t temp_c 273.15f; float t25 298.15f; return r25 * expf(b * (1.0f / t - 1.0f / t25)); }这段代码直接实现了 B 值公式。注意 expf 在 Keil 和 GCC 下都是标准库函数但使用时要确保已经链接了 libm。对于 -20°C、25°C、80°C 三个点分别算一下阻值能很快看出分压电路的输出范围是否落在 ADC 的可用区。B 值的另一个问题是它的定义条件。常见规格书写的是 B25/85也就是用 25°C 和 85°C 两个点拟合出来的值也有的写 B25/50。同一个 NTC用不同 B 值代入公式中间温度点的误差可能到 1°C 以上。所以参数表里必须写清楚是哪个区间的 B 值不能只写一个数字。2.2 分压电路与 ADC 采样值的数学映射STM32 的 ADC 不能直接测电阻需要一个分压电路。常见做法是把 NTC 接在电源端采样电阻接地采样点从两者之间引出。设 VCC 是供电电压R_ntc 是 NTC 当前阻值R_load 是接地电阻则采样点电压为Vsam VCC * R_load / (R_ntc R_load)温度升高时 R_ntc 减小Vsam 升高ADC 读数随之变大这样软件处理起来更直观。如果把这个公式反过来就能从 ADC 读数反推 R_ntcfloat ntc_resistance_from_adc(uint32_t adc, float r_load, float vref, float vcc) { if (adc 0 || adc 4095) return 0.0f; float vsam (float)adc * vref / 4095.0f; return r_load * (vcc / vsam - 1.0f); }这里有一个容易踩的坑很多教程默认 vref 等于 vcc直接用 4095/adc 计算但这在 STM32 上不严谨。如果你把 VREF 接到了外部基准或者启用了内部 1.2V 基准vref 和 vcc 就是两个值必须按上面代码里的写法分开传。adc 为 0 或 4095 时直接返回 0是为了让上层判断开路和短路状态而不是把这个异常包装成一条假温度。2.3 采样电阻 R_load 的选型最大值出现在 R_load 等于 R_ntc 的位置从 Vsam 对 R_ntc 的偏导可以证明当 R_ntc 等于 R_load 时电压对电阻的变化率最大也就是这个温度点附近 ADC 分辨率最高。所以 R_load 应当选在测量范围中间温度所对应的 NTC 阻值附近。常见配置是 10k NTC 搭配 10k 采样电阻这样 25°C 附近分辨率最好。如果你要测的是 60°C 到 100°C 的电机绕组温度NTC 在这个区间阻值可能只有 1k 到 3k那么 R_load 选 2.2k 或 3k 更合理。下面是不同 R_load 对 ADC 输出的影响示意NTC 取 10k 25°C、B3950温度R_ntcR_load10kR_load3.3kR_load1k25°C10kΩ1.65V1.22V0.75V60°C2.6kΩ0.82V1.10V0.92V100°C0.7kΩ0.30V0.61V0.78V以上是 3.3V 供电下的近似值。可以看到 R_load 越小高温段的 ADC 值越分散但低温段分辨率会变差反之亦然。选型没有银弹只能按你实际需要保护的温区来定。还要注意 NTC 的自热效应。流过 NTC 的电流越大传感器自身发热越明显。典型 NTC 的耗散系数在 1mW/K 左右3.3V 供电、10k 串联时电流约 0.165mA自热功率约 0.27mW误差小于 0.5°C可以接受。如果为了抗干扰把采样电阻改成 1k电流会到 1mA 以上自热误差就需要在系统层面校正。3. STM32 工程中集成 NTC 温度换算最小驱动与公式法实现3.1 用结构体封装 NTC 参数在 STM32 项目里我不建议把 B 值、R25 这些参数散落在 main.c 中而是封装成一个只描述传感器属性的结构体。NTC_Thermistor-2.0.1 这类库的常见做法也是这样做的参数集中换传感器时只改一处后续校准偏移也放进去。typedef struct { float b; /* B25/85 材料常数 */ float r25; /* 25°C 标称阻值 */ float r_load; /* 分压采样电阻 */ float offset; /* 校准偏移单位 °C */ } ntc_cfg_t; const ntc_cfg_t ntc_10k_b3950 { .b 3950.0f, .r25 10000.0f, .r_load 10000.0f, .offset 0.0f };结构体命名直接体现传感器型号比如 ntc_10k_b3950、ntc_100k_b4200这样在项目里多人协作时代码评审的人一眼就能看出用的是哪个型号。offset 字段是后面做两点校准用的初始默认 0。这种封装还有一个好处如果一块板上同时存在 NTC 和数字温度传感器温度换算函数可以统一接口上层逻辑不需要关心底层是用模拟量还是 I2C 读回来的。3.2 从 NTC 电阻到摄氏温度B 值公式与查表法的取舍拿到电阻后用 B 值公式反推温度T 1 / ( 1/T25 (1/B) * ln(R/R25) ) - 273.15在 STM32F103 这类主频 72MHz 的 MCU 上一次 logf 运算大约几十微秒对非实时温度采集完全够用。但如果你的系统每秒要轮询几十个通道而且不允许使用浮点库查表法才是更稳的选择。下面是一个同时支持公式法和查表法的最小实现。先定义换算函数原型float ntc_resistance_to_celsius(float r_ntc, const ntc_cfg_t *cfg) { if (r_ntc 0.0f) return -273.15f; float t25 298.15f; float k 1.0f / t25 (1.0f / cfg-b) * logf(r_ntc / cfg-r25); return (1.0f / k - 273.15f) cfg-offset; } float ntc_adc_to_celsius(uint32_t adc, const ntc_cfg_t *cfg, float vref, float vcc) { float r_ntc ntc_resistance_from_adc(adc, cfg-r_load, vref, vcc); if (r_ntc 0.0f) return -273.15f; return ntc_resistance_to_celsius(r_ntc, cfg); }这里把 ADC 到电阻、电阻到温度分成了两个函数中间保留 r_ntc 这个量后续如果要接精密电阻校准时可以直接复用。注意当 r_ntc 为 0 时返回 -273.15f这个值约等于绝对零度在 UI 层应当过滤掉避免把开路误显示成正常低温。查表法的实现本质是相同思路把 ADC 值按步进预先算好温度存放在 const 数组里运行时用二分查找。对于 12 位 ADC一张 4096 个元素的表也只有 8KB完全可以放进 Flash。3.3 在 Keil 与 STM32CubeIDE 中组织驱动文件实际工程里我会把上面的函数单独放到 ntc_thermistor.c / ntc_thermistor.h与 HAL 层的 ADC 代码分开。头文件只暴露配置结构体和三个 API#ifndef NTC_THERMISTOR_H #define NTC_THERMISTOR_H #include stdint.h typedef struct { float b; float r25; float r_load; float offset; } ntc_cfg_t; float ntc_adc_to_celsius(uint32_t adc, const ntc_cfg_t *cfg, float vref, float vcc); float ntc_resistance_to_celsius(float r_ntc, const ntc_cfg_t *cfg); #endif这样做的好处是驱动层不依赖 HAL哪怕你从 STM32 HAL 换成 LL 库或者换到 APM32、GD32 这类兼容芯片文件基本不用改。Keil 工程里只需要把 ntc_thermistor.c 添加进项目C99 和数学库默认开启即可。头文件里没有 include stm32f1xx_hal.h这是因为 NTC 换算函数不需要访问寄存器。这个隔离带来一个额外好处单元测试可以直接在 PC 上编译不用接硬件。对应几个函数的使用场景可以简单归纳成下表函数输入输出典型调用场景ntc_adc_to_celsiusADC 原始值、配置结构体摄氏温度主循环中查询单路温度ntc_resistance_to_celsiusNTC 电阻值摄氏温度校准、精密电阻标定ntc_resistance_from_adcADC 原始值NTC 电阻值信号链调试观察电阻波动4. NTC_Thermistor 关键参数设置B 值、R25、采样电阻与滤波的配合4.1 三个必调参数B 值、R25 和采样电阻很多第一次用 NTC 的 STM32 开发者会问“为什么我测出来温度总是偏高/偏低”答案基本都在这三个参数上。R25 是 NTC 在 25°C 的阻值决定整个分压电路的工作点。它可以从规格书拿到但注意规格书标的是标称值实际阻值有 ±1% 到 ±5% 的离散所以高精度应用一般都要做单点校准。B 值影响曲线斜率。B 值越大曲线越陡温度每变化 1°C 阻值变化越明显灵敏度越高但线性区间也就越窄。选择时应该根据你关心的温度区间用规格书里的 B25/85 而不是随意找的近似值。采样电阻 R_load 在上面已经讲过分压逻辑这里要补充的是它同样有精度问题。我建议至少用 1% 精度的电阻如果做量产产品最好用 0.1% 低温漂电阻否则 R_load 的误差会直接叠加到温度换算里。下面是一张常见 NTC 选型参数表可以直接作为初始配置参考NTC 型号R25B25/85采样电阻合适测温区间10k 343510kΩ3435K10kΩ-20°C ~ 60°C10k 395010kΩ3950K10kΩ0°C ~ 100°C100k 4200100kΩ4200K100kΩ-40°C ~ 120°C如果你用的是第 3 章的结构体只需要替换这一份配置即可不需要改代码逻辑。4.2 滤波策略滑动平均与一阶低通NTC 本身是较大的阻性传感器抗干扰能力不差但 STM32 ADC 的采样电容、地线噪声和电源纹波仍然会让读数出现 ±1 个 LSB 的抖动对应温度可能在 ±0.2°C 左右。如果你的温控系统对突变敏感滤波是必须的。最常见的是滑动平均窗口选 8 或 16NTC 信号变化很慢窗口不会带来明显延迟#define NTC_AVG_FILTER_SIZE 8 uint32_t ntc_avg_filter(uint32_t new_sample, uint32_t *buf, uint32_t *sum, uint32_t size) { uint32_t oldest buf[0]; memmove(buf, buf 1, (size - 1) * sizeof(uint32_t)); buf[size - 1] new_sample; *sum *sum new_sample - oldest; return *sum / size; }这段代码维护一个环形队列的累计和每次更新只做一次减法一次加法除法是最后的平均值计算。注意 memmove 只处理 8 个元素开销很小。如果窗口需要更大考虑使用环形索引避免反复搬移。一阶低通滤波的代码更轻比较适合放在中断里static float filtered_volt 0.0f; float ntc_lowpass(float new_sample, float alpha) { filtered_volt alpha * new_sample (1.0f - alpha) * filtered_volt; return filtered_volt; }alpha 的取值需要权衡响应速度。alpha 越大跟踪越快但抑制噪声能力弱alpha 越小读数越平滑但启动时要更长的时间才能收敛。常见取 0.2 到 0.4。无论哪种滤波都要在 ADC 原始值上做不要在已经算出温度后再做因为非线性映射会把滤波后的误差不对称地反映到温度上。4.3 两点校准修正系统误差的落地方法分压电阻精度、VREF 误差、NTC 本身的离散都会造成最终温度读取与真实温度之间的固定偏差。这部分误差用软件校准完全可以消除。两点校准的流程是让系统在已知的两个温度点稳定后记录下软件计算出的温度值然后计算偏差。例如用冰水混合物和沸腾水作为两个参考点但沸点会受气压影响量产时更推荐用高精度温度计或者恒温槽。从两个校准点反推 B 值和 R25 是更系统化的做法。已知 T1 时电阻 R1T2 时电阻 R2则实际有效 B 值可以用下面的公式计算float ntc_calibrate_b(float r1, float t1_c, float r2, float t2_c) { float t1 t1_c 273.15f; float t2 t2_c 273.15f; return logf(r1 / r2) / (1.0f / t1 - 1.0f / t2); }注意这里 logf 参数是 r1/r2不是 r2/r1。算出 B 之后再用其中一个点的数据反推 R25float ntc_calibrate_r25(float r1, float t1_c, float b) { float t1 t1_c 273.15f; float t25 298.15f; return r1 / expf(b * (1.0f / t1 - 1.0f / t25)); }这两个函数写好后可以直接在串口命令里触发校准把算出来的 B 和 R25 写回配置结构体。需要强调的是校准必须在同一条 ADC 信号链上进行不要在 PC 上单独算好再手工填。5. 多路 NTC 采集定时器触发 ADC DMA 的 STM32 实现5.1 为什么轮询不适合多路温度采集当一个 STM32 项目里只有一路 NTC 时主循环里直接调用 HAL_ADC_Start HAL_ADC_PollForConversion 没问题。但做多路采集比如电池 pack 的 12 个电芯温度、电机控制器三相绕组的三个温度轮询就有两个明显问题一是每路都要等 ADC 转换完成主循环被阻塞二是 ADC 触发时刻不定采样电容充电时间不可控会引入周期性的读数抖动。NTC 是慢信号但 STM32 的 ADC 是快速外设正确的做法是让定时器以固定频率触发一组规则组转换转换完成后 DMA 把结果搬到内存主循环只做滤波和换算。5.2 定时器触发多通道采样的配置要点在 CubeMX 里先把定时器的 Update 事件配置为 ADC 触发源。以 STM32F103 为例ADC1 可以接受 TIM2_TRGO 等触发选择时要注意通道映射是否支持。接着把 ADC 的扫描模式开启选择你要用的通道比如 IN0、IN1、IN2采样周期尽量调大。NTC 源阻抗通常有几 kΩST 手册建议外部阻抗大于 10kΩ 时采样周期至少 28.5 个周期实际我会直接调到最大 480.5 cycles来减少采样电容充电时间不足带来的误差。DMA 设置为循环模式数据宽度 half-word内存地址递增外设地址固定为 ADC1-DR。这样才能保证每次采样结果按顺序填入指定的数组。下面是一个最小配置描述关键注册代码在 main 中写成这样ADC_HandleTypeDef hadc1; DMA_HandleTypeDef hdma_adc1; TIM_HandleTypeDef htim2; void MX_ADC1_Init(void) { hadc1.Instance ADC1; hadc1.Init.ScanConvMode ADC_SCAN_ENABLE; hadc1.Init.ContinuousConvMode DISABLE; hadc1.Init.DiscontinuousConvMode DISABLE; hadc1.Init.ExternalTrigConv ADC_EXTERNALTRIGCONV_T2_TRGO; hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion 4; HAL_ADC_Init(hadc1); }这是 HAL 库的标准写法注意 ContinuousConvMode 必须为 DISABLE否则定时器触发信号就不起作用了。ExternalTrigConv 的值是 F103 系列特有的其他 STM32 型号可能是 ADC_SOFTWARE_START 或不同的 trigger 枚举。定时器配置的核心是触发频率。以 100Hz 采样率每个周期转换 4 路为例TIM2 的 Period 可以按下面的公式计算/* 假设 ADC 时钟为 12MHz分频 71则计数频率 1MHz */ htim2.Init.Period 9999; /* 1MHz / 10000 100Hz */ htim2.Init.Prescaler 71; htim2.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; HAL_TIM_Base_Init(htim2);这个参数里 Prescaler 和 Period 的数值没有固定值关键是保证触发周期高于 ADC 完成全部通道转换所需的时间否则下一次触发到来时上一次还没转换完数据序会错乱。根据不同的触发频率定时器和采样周期组合可以按下面的表来预估触发频率定时器计数频率Period4通道转换预估时间是否合理10Hz1MHz99999约 164us富裕100Hz1MHz9999约 164us富裕1kHz1MHz999约 164us仍可运行但留给滤波的时间变短5.3 DMA 双缓冲半传输与全传输中断配合DMA 循环模式会不停覆盖缓冲区如果主循环读的温度和 DMA 写的正好撞在同一时刻会出现半组新数据、半组旧数据的坏帧。NTC 信号慢偶尔坏一次也能被滤波吞掉但更稳的做法是双缓冲。DMA 缓冲区开成两片每片存放一次完整转换的结果。DMA 传输总长度设为通道数的两倍这样前半区写完触发一次半传输中断后半区写完触发一次全传输中断。中断回调里不搬运数据只切换当前可读的半区索引#define NTC_CHANNEL_COUNT 4 uint16_t adc_dma_buf[2][NTC_CHANNEL_COUNT]; volatile uint8_t ntc_read_index 0; volatile uint8_t dma_buf_ready 0; void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc-Instance ADC1) { ntc_read_index 1; dma_buf_ready 1; } } void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc-Instance ADC1) { ntc_read_index 0; dma_buf_ready 1; } }主循环里可以这么读if (dma_buf_ready) { for (int i 0; i NTC_CHANNEL_COUNT; i) { uint32_t adc adc_dma_buf[ntc_read_index][i]; float temp ntc_adc_to_celsius(adc, ntc_cfg, 3.3f, 3.3f); /* 做滤波或存到环形日志 */ } dma_buf_ready 0; }dma_buf_ready 标志位在读完一组后清零。如果下一次 DMA 中断到来时主循环还没读完新的 dma_buf_ready 不会丢失最多是跳过一组数据不会读到撕裂数据。温度换算和滤波放在主循环定时器中断只做标志切换这样中断服务时间可以控制在很短。6. 验证温度采样精度的三个技巧精密电阻替代法、串口监视和软件补偿6.1 用精密电阻替代 NTC 验证信号链没有恒温槽时最简单的验证方法是把 NTC 从电路上摘掉焊一个 0.1% 精度、10kΩ 的精密电阻到原来的位置。因为此时分压电路输出正好是 VCC 的一半软件理论上应该读出 25.0°C。如果读出来是 24.5°C说明问题不在 NTC而在采样电阻、VREF、ADC 偏移这一类系统级误差。再换用 2.6kΩ 的电阻模拟 60°C如果读数与理论值偏差超过 1°C优先检查的是 VREF 和 VCC 是否匹配。这个替代法可以帮你把传感器本体和电路误差分离开是一个非常高效的排错手段。6.2 串口同时输出原始 ADC 值和换算温度只打印温度结果会掩盖很多问题。把 ADC 原始值、换算的电阻值、最终温度一起按 CSV 格式输出用串口工具抓到 PC 里画在图表上很容易看出是周期性跳变还是随机噪声。常见格式如下printf(%lu,%lu,%.2f,%.2f\r\n, tick_ms, adc_raw, resistance, temperature_c);其中 tick_ms 用 STM32 的 SysTick 累加器得到方便回看采样时序。如果 adc_raw 出现毛刺优先检查地线如果电阻值整体偏移优先检查分压电阻精度。6.3 软件补偿采样周期对自热的影响NTC 自热在连续采样时会产生一个稳定的正偏差。比如 3.3V 供电、10k 串联时自热功率接近 0.3mW某些封装下温升 0.5°C。这个误差无法通过滤波消除但可以在固件里做线性补偿先在工作温区记录自热偏移然后在 offset 里减去这个值。补偿系数不要用常数而是根据 ADC 读数推算功率因为 NTC 阻值变化时自热功率也在变化。把这三个技巧写成一个自检函数每次上电后先读一个已知电阻再进入正常采样相当于给每个 STM32 设备做一次出厂自检比在产线上人工看串口可靠得多。本文还有配套的精品资源点击获取
返回列表