拓冰建站拓冰建站
首页 / 资讯中心 / 正文

STM32F103内置ADC实现PT100温度采集完整方案

简介面向嵌入式初学者和需要快速验证 PT100 测温方案的开发者STM32F103 PT100 温度测量代码专注 ADC 采样与电压-温度换算适合正在搭建恒流源测温电路时直接参考。压缩包为 rar 格式仅 4KB包含 4 个源文件其中 Adc.h/Adc.c 负责 STM32F103 模拟输入通道的初始化、采样时间设置以及转换结果读取pt100.h/pt100.c 则基于 PT100 的电阻温度特性将 ADC 数值换算为实际温度代码结构简洁便于移植。已有 5540 人学习/下载反映出这套例程在入门级 STM32 项目中具有较高参考价值。通过这组代码可以了解参考电压、分辨率、DMA/中断读取等 ADC 关键配置也能看到恒流源测量场景下电压转换和温度标定的实现思路。对首次接触 STM32 ADC 的开发者而言它能够帮助建立从模拟前端到温度输出的完整链路认识配合 PT100 分度表与自己的恒流源电路即可验证不同温度点实际调试中也能减少重复踩坑。 手头这个老项目其实挺典型的用 STM32F103 做一路温度采集传感器选 PT100成本卡得死不给用外部专用 ADC也不给用 MAX31865 这类数字转换芯片。最后方案落在 STM32F103 内置 ADC 上——用恒流源把 PT100 的电阻变化变成电压变化ADC 采样后软件再把电压换算成温度。整个链路从硬件到软件今天拆开讲一遍全是可以直接抄的配置和代码。这个方案只依赖单片机内置的 12 位 ADC成本低、电路简单适合 -50℃ 到 200℃ 的常规工业测温场景。如果你正在做温度测量相关的小项目或者面试前想弄明白ADC 采集后到底怎么换算温度这篇都能给你一个完整的参考。我会把 ADC 采样参数、电阻换算公式、查表法、标定方法都过一遍也会聊聊那些文档里不写但实际开发一定会踩的坑。1. 整体方案设计一条完整的测温信号链1.1 从电阻到温度的链路拆解温度测量这条信号链其实分四段第一段是物理量到电阻量PT100 的阻值随温度近似线性变化0℃ 时正好 100Ω第二段是电阻量到电压量需要给 PT100 加激励电流让阻值变化体现在电压变化上第三段是电压量到数字量STM32F103 的 12 位 ADC 把模拟电压变成 0~4095 的数字码第四段是数字量到温度值软件做算法换算。每一段都会引入误差PT100 本身有 ±0.1% 或 ±0.3% 的精度等级差别激励电流的稳定性直接影响电压准确性ADC 的非线性和参考电压温漂会带来量化误差软件算法如果只做粗暴的线性映射在温度区间两端会明显偏离真实值。所以做这个项目不能只看单片机代码前端电路和算法得一起设计。我常用的思路是误差预算假设目标精度是 ±1℃那么先把量程内 PT100 的电阻变化折算出来看信号电压变化有多大再判断 12 位 ADC 的量化分辨率够不够不够的话前端就要加放大器或者调整激励方案。1.2 前端电路选型恒流源是这个场景的最优解PT100 的激励方案常见有三种固定电阻分压、恒流源激励、惠斯通电桥。我直接说结论分压电路最简单但线性差电桥适合差分放大场景但电阻配对麻烦恒流源激励在成本和精度之间平衡最好。恒流源的思路是给 PT100 通一个恒定电流 I典型值 1mA那么 PT100 两端的电压 V I × Rpt温度变化直接反映为电压变化而且电压与电阻成正比软件换算时不需要做二次线性化。固定电阻分压的 Vout 公式是 V Vcc × Rpt / (R_fixed Rpt)这个式子本身带一个非线性项即使后面查表也需要更多插值点才能保证精度。我在项目里选的是 TL431 三极管搭建的 1mA 恒流源。相比运放方案TL431 方案便宜且稳定温漂系数能做到 50ppm 级别对 1℃ 的精度目标来说足够了。后面我会把电路参数和计算过程完整列出来。2. 硬件电路设计恒流源与信号调理2.1 1mA 恒流源电路原理与计算恒流源电路核心是一个基准电压源和一个采样电阻。TL431 的参考端稳定在 2.495V我把它接成一个稳压源然后用三极管扩流发射极电阻两端的电压被钳位在基准电压所以流过发射极的电流恒定。设计时选择发射极电阻来设定电流大小。一个标准的电路结构是TL431 阴极通过电阻接到电源负极参考端接在采样电阻的输出端PNP 三极管的基极由 TL431 的阴极驱动发射极接采样电阻集电极输出电流给 PT100。反馈环路会不断调整三极管基极电流使采样电阻上电压始终保持 2.495V从而稳定输出电流。计算过程设定 I 1mA采样电阻 R_sense 2.495V / 1mA 2.495kΩ实际取精密电阻 2.49kΩ实际电流约为 1.002mA这个误差可以通过软件标定消除。为什么选 1mA 而不是大电流因为 PT100 有自热效应电流越大功耗越高100Ω 电阻在 1mA 下功耗是 0.1mW温升可忽略如果用 10mA功耗 10mW会造成零点几摄氏度的温升精度就丢了。需要说明这是基于常见实践的经典电路实际项目里我用的就是这个结构。如果你手头没有 PNP 三极管也可以用运放比如 LM358 NPN 三极管替代原理一样只是极性反一下。2.2 三线制接法消除引线电阻误差PT100 传感器本身只有两三百毫安的电流等级但安装距离一远导线电阻就不能忽略。常见的 0.5mm² 铜线每米电阻约 0.037Ω对应大约 0.096℃/m 的误差距离一长就失控了。三线制接法的思路是两根线分别走电源和采样电压第三根线连接到传感器另一端由 ADC 的地参考端引出。核心是让激励电流的引线和电压采样的引线分离这样导线上的压降不会叠加到采样电压上。具体来说恒流源的电流流过一根引线进入 PT100从另一根引线回流而第三根线接到 ADC 的地端电压测量端和电流回路端分开了。实际接线时要注意三根导线的电阻要尽量一致因为补偿效果依赖于对称性。工业上用的三线制 PT100 探头内部已经接好了三根同规格引线接线端子处注意区分颜色即可。如果现场干扰大可以在 ADC 采样端并联一个 100nF 电容做低通滤波但要注意采样稳定时间后续 ADC 配置里要留足采样时间。2.3 信号放大方案与增益计算STM32F103 的 ADC 参考电压是 3.3V12 位分辨率对应 LSB 约 0.806mV。而 1mA 激励下 PT100 在 -50℃ 到 200℃ 范围内的电压变化是 80.31mV 到 175.86mV满量程电压差只有 95mV 左右直接接 ADC 的话温度分辨率大概在 0.7℃/LSB 左右虽然勉强但富余量不大。如果要做到 ±0.5℃ 的稳定度前端最好加一级放大。我用的方案是两级先测 PT100 电压然后通过同相放大器放大 10 倍。运放电源用单电源 3.3V问题来了——输入信号本身是 80mV 到 176mV放大 10 倍后是 0.8V 到 1.76V在 0V 到 3.3V 范围内可以正常工作。但要留意如果温度范围拉到 0℃ 以下电阻低于 100Ω信号电压更小导致放大后的电压偏低需要检查运放输入输出是否还在共模范围内。计算增益公式G R_feedback / R_input_gain 1我取 R_input_gain 1kΩR_feedback 9kΩ则 G 10。输出送至 STM32 的 PC0 引脚ADC_IN10。没有加偏置是因为这个量程范围内信号都在 3.3V 以内不需要把信号抬到中间。3. STM32F103 ADC 采样配置详解3.1 内置 ADC 的基本特性与工作模式STM32F103 的 ADC 是 12 位逐次逼近型SAR最快时钟 14MHz 时约 1MHz 采样率有规则组、注入组和模拟看门狗支持 DMA。做单通道温度测量我一般用规则组 软件触发 多通道轮询或者规则组 定时器触发 DMA。两种方式都能用但 DMA 方式特别适合还要同时做其他任务的场景CPU 不用一直等转换完成。ADC 的参考电压 VREF 默认接 VDDA3.3V如果项目对精度要求高建议单独用高精度基准源比如 REF3030 提供 3.0V 基准接到 VREF这样 ADC 的增益误差可以从几十 LSB 降到几个 LSB。我这个项目用的板子是把 VREF 直接接 VDDA所以在软件标定阶段把这一部分系统误差也一起标掉了。12 位 ADC 的换算公式ADC_Value V_in / V_REF × 4095。这个公式大家都会写但实际项目中两个关键点容易被忽略一是采样时间必须足够长PT100 信号源阻抗不低加上分压网络等效阻抗可能有几 kΩ如果采用采样时间太短采样电容没充满数值会整体偏低二是 ADC 时钟频率会影响采样精度ADC_CLK 超过 14MHz 时虽然仍能工作但精度会下降。3.2 HAL 库代码ADC 初始化与多通道采样下面给出我用 HAL 库写的 ADC 初始化代码配置 PC0 为模拟输入单通道规则组软件触发。static void MX_ADC1_Init(void) { ADC_ChannelConfTypeDef sConfig {0}; hadc1.Instance ADC1; hadc1.Init.ScanConvMode ADC_SCAN_DISABLE; // 单通道模式 hadc1.Init.ContinuousConvMode DISABLE; // 手动触发 hadc1.Init.DiscontinuousConvMode DISABLE; hadc1.Init.ExternalTrigConv ADC_SOFTWARE_START; // 软件触发 hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion 1; HAL_ADC_Init(hadc1); sConfig.Channel ADC_CHANNEL_10; // PC0 对应 ADC_IN10 sConfig.Rank ADC_REGULAR_RANK_1; sConfig.SamplingTime ADC_SAMPLETIME_239CYCLES_5; // 长采样时间 HAL_ADC_ConfigChannel(hadc1, sConfig); }重点解释采样时间。ADC_SAMPLETIME_239CYCLES_5 表示采样阶段持续 239.5 个 ADC 时钟周期加上 12.5 个周期的逐次逼近时间一共 252 个周期。假设 ADC 时钟 12MHz一次转换耗时约 21μs。这个时间比快速配置比如 1.5 周期采样慢很多但换来的是采样电容稳定时间充足。我实测过同一个电路用 1.5 周期和 239.5 周期前者 ADC 读数比后者低 3~8 个 LSB误差相当于零点几摄氏度所以做高精度测温采样时间别省。单次读取函数uint16_t adc_read_once(void) { uint16_t value 0; HAL_ADC_Start(hadc1); if (HAL_ADC_PollForConversion(hadc1, 100) HAL_OK) { value HAL_ADC_GetValue(hadc1); } HAL_ADC_Stop(hadc1); return value; }如果还要采集其他电压比如外部基准可以改成 ScanConvMode ENABLE 加 NbrOfConversion 2但要注意多通道轮询时需要在每次转换后切换通道。单通道测温场景显然不需要。3.3 软件滤波让 ADC 数值稳定下来ADC 的原始值直接拿来算温度得到的温度曲线会明显抖动尤其是恒流源由低功率器件供电、基准电压有纹波时。实测一个没有滤波的 ADC 读数波动范围可能达到 ±10 个 LSB对应温度波动超过 ±2℃。这个必须处理。我的做法是三级滤波组合先做中值滤波剔除野值再做滑动平均平滑最后对温度结果做一阶低通压制残余高频抖动。中值滤波窗口取 5滑动平均窗口取 16一阶低通系数取 0.2~0.4根据现场动态响应需求调整。#define MEDIAN_WINDOW 5 #define MOVING_WINDOW 16 uint16_t adc_median_move_avg(uint16_t new_sample) { static uint16_t history[MOVING_WINDOW]; static uint8_t idx 0; static uint16_t buf[MEDIAN_WINDOW]; static uint8_t buf_idx 0; uint32_t sum 0; uint8_t i, j; uint16_t temp; // 中值滤波 buf[buf_idx] new_sample; buf_idx (buf_idx 1) % MEDIAN_WINDOW; temp buf[0]; for (i 0; i MEDIAN_WINDOW - 1; i) for (j i 1; j MEDIAN_WINDOW; j) if (buf[j] buf[i]) { temp buf[i]; buf[i] buf[j]; buf[j] temp; } uint16_t median buf[MEDIAN_WINDOW / 2]; // 滑动平均 history[idx] median; idx (idx 1) % MOVING_WINDOW; for (i 0; i MOVING_WINDOW; i) sum history[i]; return (uint16_t)(sum / MOVING_WINDOW); }这个滤波函数在 20ms 左右输出一个稳定值每次 ADC 转换 滤波耗时约 30μs但触发间隔取 100ms 更合理保证系统有余量。需要注意的是中值滤波对持续毛刺无效如果现场有强干扰导致连续多个野值还是要想办法从硬件上解决比如加强滤波电容、优化 PCB 布局。4. 电压到温度的换算从 ADC 码到真实温度的完整实现4.1 两步换算ADC 码先还原成电阻值拿到稳定的 ADC 码之后第一个动作是还原成 PT100 的电阻值。这一步是整个换算的基础做错后面全错。根据前面硬件设计ADC 输入电压 V_in G × I × Rpt其中 G 是放大器增益10 倍I 是激励电流约 1mA。所以反推Rpt ADC_Value / 4095 × V_REF / (G × I)代码实现float adc_to_resistance(uint16_t adc_value, float vref, float gain, float i_exc) { float v_in (float)adc_value / 4095.0f * vref; return v_in / (gain * i_exc); }注意这里用了设计值 vref3.3、gain10、i_exc0.001。实际器件肯定有偏差直接算出来的电阻值可能偏大或偏小 1%~2%对应温度误差好几度。所以必须加标定系数修正。标定方法我会在 4.4 节专门讲。4.2 查表法 线性插值工程中最稳的选择把电阻换成温度主流有两种思路查表法和公式法。查表法简单直观不容易算错代码量也小。我提供一个 PT100 分度表片段覆盖 -50℃ 到 200℃ 每隔 10℃ 的电阻标称值依据 IEC 60751α0.00385。温度(℃)电阻(Ω)温度(℃)电阻(Ω)-5080.3180130.90-4084.2790134.71-3088.22100138.51-2092.16110142.29-1096.09120146.070100.00130149.8310103.90140153.5820107.79150157.3330111.67160161.0540115.54170164.7750119.40180168.4860123.24190172.1770127.08200175.86表格里的数据我按 IEC 标准公式精算过直接可用于工程。实际查表时用线性插值公式是在两个相邻表节点之间假设电阻与温度为线性关系按比例计算温度。typedef struct { float temp; float res; } pt100_table_t; const pt100_table_t pt100_table[] { {-50.0f, 80.31f}, {-40.0f, 84.27f}, {-30.0f, 88.22f}, {-20.0f, 92.16f}, {-10.0f, 96.09f}, { 0.0f, 100.00f}, { 10.0f, 103.90f}, { 20.0f, 107.79f}, { 30.0f, 111.67f}, { 40.0f, 115.54f}, { 50.0f, 119.40f}, { 60.0f, 123.24f}, { 70.0f, 127.08f}, { 80.0f, 130.90f}, { 90.0f, 134.71f}, {100.0f, 138.51f}, {110.0f, 142.29f}, {120.0f, 146.07f}, {130.0f, 149.83f}, {140.0f, 153.58f}, {150.0f, 157.33f}, {160.0f, 161.05f}, {170.0f, 164.77f}, {180.0f, 168.48f}, {190.0f, 172.17f}, {200.0f, 175.86f} }; float pt100_res_to_temp(float res) { uint8_t n sizeof(pt100_table) / sizeof(pt100_table[0]); if (res pt100_table[0].res) return pt100_table[0].temp; if (res pt100_table[n-1].res) return pt100_table[n-1].temp; for (uint8_t i 0; i n - 1; i) { if (res pt100_table[i].res res pt100_table[i1].res) { float ratio (res - pt100_table[i].res) / (pt100_table[i1].res - pt100_table[i].res); return pt100_table[i].temp ratio * (pt100_table[i1].temp - pt100_table[i].temp); } } return pt100_table[n-1].temp; }插值步长 10℃ 的情况下线性插值带来的最大误差大约在 0.02℃ 量级因为 PT100 的非线性度在 0~200℃ 范围内占满量程的比例很小对绝大多数项目足够。如果想要更小误差可以把表加密到 5℃ 步长代码基本不用改。4.3 公式法高精度场景的替代方案如果不想维护大数组或者需求要求更高的内插精度比如要多点标定可以用 IEC 60751 标准公式。0℃ 以上用二次方程Rt R0 × (1 A×t B×t²)其中 A 3.9083×10⁻³B -5.775×10⁻⁷。反解温度时这是一个一元二次方程B×t² A×t (1 - Rt/R0) 0取正根t (-A sqrt(A² - 4B×(1 - Rt/R0))) / (2B)代码#define PT100_R0 100.0f #define PT100_A 3.9083e-3f #define PT100_B -5.775e-7f float pt100_res_to_temp_formula(float res) { float c 1.0f - res / PT100_R0; float disc PT100_A * PT100_A - 4.0f * PT100_B * c; if (disc 0) return -273.15f; return (-PT100_A sqrtf(disc)) / (2.0f * PT100_B); }0℃ 以下还有第三项 C×(t-100)×t³公式更长我在实际项目里如果温度范围低于零下仍然用查表法因为 0℃ 以下的公式更复杂且很容易写错。查表法的最大好处就是怎么都不容易错。4.4 系统标定提高绝对精度最有效的手段代码写得再好硬件器件有离散误差照样不准。TL431 基准的初始误差、采样电阻的精度、运放的失调电压、ADC 的增益误差都会叠加进最终结果。我在项目里用了两点标定来修正系统误差。具体操作把 PT100 探头放到 0℃ 环境冰水混合物和 100℃ 环境沸水中记录 ADC 读数。然后假设实际温度与 ADC 读数满足线性关系T_real k × ADC_cal b求出 k 和 b。之后所有 ADC 值都代入这个线性模型换算。注意这个线性修正要把前面的电阻计算一起打包最简单的方式是对最终温度做线性修正。typedef struct { float slope; float offset; } linear_cal_t; linear_cal_t temp_calibration { .slope 100.0f / (adc_at_100c - adc_at_0c), .offset -100.0f * adc_at_0c / (adc_at_100c - adc_at_0c) }; float get_calibrated_temp(float adc_val) { return temp_calibration.slope * adc_val temp_calibration.offset; }如果校准环境不理想找不到沸水可以拿一个高精度水银温度计或者数字温度计做参照在室温附近取两个点也行但线性修正的覆盖范围会变小。用这个两点标定之后我实测整个 0~100℃ 区间的最大误差能压到 ±0.5℃ 以内这已经是仪表级精度了。5. 常见问题与排查技巧实录5.1 现象、原因、对策速查表现象可能原因排查与对策ADC 数值持续跳动 ±15 LSB电源纹波大、接地不合理在 VDDA/VREF 引脚加 100nF10μF 电容ADC 采样时间加大软件滤波温度读数整体偏高/偏低恒流源电流偏差、增益电阻精度差测量采样电阻实际阻值软件里改 I_exc 或 G 参数做两点标定读数随线长变化明显引线电阻未消除改用三线制接法信号线远离电机/电源线温度变化时读数滞后较大软件滤波过于激进降低一阶低通系数或减小滑动窗口低温段读数明显非线性单点标定不够使用查表法 多点标定或公式法5.2 几个容易被忽略的细节第一恒流源的上电时间。TL431 基准源从通电到稳定输出需要几百毫秒甚至更久如果系统上电后立即读 ADC前几个采样值会是乱的。我的做法是在初始化函数里先延时 500ms 再进行第一次采样。第二ADC 初始化时连续转换模式的问题。如果开了连续转换程序可能在一次转换还没完成就再次启动导致数据错乱。用软件触发时记得在每次读取完成后调用 HAL_ADC_Stop确保状态机复位。第三运放输出阻抗对 ADC 采样电容的影响。STM32 的 ADC 采样电容大约几 pF如果运放带载能力弱电压会在采样瞬间被拉低。实测用 LM358 直接驱动 ADC 时ADC 数值比用 OPA2188 低 2~5 个 LSB。低阻抗输出是 ADC 驱动的基本要求。第四如果温度探头离 MCU 板超过 1 米信号线最好用屏蔽双绞线屏蔽层单端接地。我在一个现场就遇到过因为信号线靠近变频器导致 ADC 读数周期性跳变的问题当时只靠软件滤波根本压不住最后是换了屏蔽线才解决的。6. 经验总结与扩展建议这个 STM32F103 PT100 ADC 的方案最大价值在于用最简的硬件实现了仪表级温度测量核心就是误差预算 查表法 标定三个动作。做这个项目我最大的体会是精度不是靠某一个环节的极致而是每个环节都控制在合理范围内——恒流源电流要稳采样时间要够滤波要适度标定要到位缺一个最终显示的温度都会差口气。最后分享一个小技巧如果你后面想把这个方案扩展到多路 PT100 测温不用重新画板子MCU 里 ADC 引脚多的是每路用一个运放通道和一个恒流源通道即可。但要注意多路采样时 ADC 的采样时间要重新计算因为切换到新通道后采样电容需要时间充电到新电压建议每路采样之间加几十微秒的稳定时间。用 DMA 触发多通道规则组扫描是比轮询更高效的做法CPU 几乎零负担。这套方法我后来复制到了 -50℃~150℃ 的冷库监控项目里效果一样稳。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门