☰
基于STM32的PT100温度采集系统:程序、原理图与标定避坑
2026/10/26 10:39:55 网站建设 项目流程

简介:面向嵌入式初学者与需要完成温度采集课程设计、毕设或工程原型的开发者,这套资料围绕STM32单片机与PT100铂电阻构建完整测温方案,涉及四线制接法、差分放大、ADC采样配置、R-T特性换算及原理图绘制,并给出Keil工程源码与可烧录固件等配套内容。压缩包共277个文件,约18.09MB,以62个C源文件、52个头文件为主,另有编译中间文件、17张调试截图、11份说明文本及PDF、Word文档、Keil工程文件与HEX固件,便于按硬件、程序、文档三部分查阅。已有2099人学习下载,说明该方案在同类课程设计中具有较高参考度。读者可据此理解PT100信号调理与STM32内部ADC采集的衔接方式,参考查找表或多项式拟合完成温度计算,并借助原理图、源码和说明文档快速搭建实验环境、排查采样与显示问题。

1. 为什么 PT100 不能直接接 STM32 的 ADC:0.385Ω/°C 背后的采集门槛

有人把 PT100 两根线直接拧到 STM32 的 ADC 引脚上,室温读数在 25°C 和 31°C 之间来回跳,换一根线又偏了 2°C。这套「基于 STM32 单片机 PT100 铂电阻温度采集系统(程序+原理图+全套资料)」要解决的就是这个门槛:PT100 在 0°C 只有 100Ω,每升高 1°C 电阻只变约 0.385Ω,直接分压后送进 12 位 ADC,分辨率、线阻和噪声都会把温度吃掉。它适合做恒温箱、小型工业测温、实验数据采集的开发者,尤其是手里有 STM32 板子、想把 PT100 从「能读数」做到「能验收」的人。资料把程序、原理图和全套资料放在一起,重点不是炫技,而是把激励、放大、采样、换算和标定串成一条可复现的链路。

2. 硬件链路选型:恒流源、差分放大与 12 位 ADC 的匹配

PT100 前端不是「接个上拉电阻」就完事。先定量程和精度,再选激励方式,最后反推放大倍数和 ADC 码值范围。资料里的原理图如果采用分立方案,重点看恒流源、差分放大和 ADC 输入这三段;如果板上直接放了专用 RTD 前端芯片,程序重点会变成 SPI 配置和故障位读取。

2.1 三种前端方案怎么选:电桥、恒流源和专用芯片

常见方案就三类:电阻电桥加差分放大、恒流源加仪表放大、专用 RTD 前端芯片。电桥成本低,但输出非线性和共模电压都比较明显,适合对精度要求不高的通断判断;恒流源方案线性好,配合三线制或四线制能把线阻影响压下去,是分立电路里最常见的选择;专用芯片把激励、放大、ADC 和断线检测集成到一起,省模拟调试,但成本高,寄存器配置也要花时间。

方案核心思路优点短板适用场景
电桥 + 差分放大PT100 作为桥臂,桥压差放大元件少,成本低非线性、需要稳定桥压、共模高0~100°C、精度 1°C 左右
恒流源 + 仪表放大1mA 恒流流过 PT100,取差分电压线性好,易做三线/四线需要匹配电阻和偏置,模拟调试多工业测温、恒温控制
专用 RTD 前端芯片内部完成激励和 ADC省事,带故障检测成本高,SPI 配置有门槛精度 0.1°C、小批量

这套资料如果走的是分立链路,我一般会先看恒流源是不是 1mA 左右。激励电流太小,信号被运放失调吃掉;太大,PT100 自热又会引入慢漂移。1mA 下 0°C 的功耗只有 0.1mW,100°C 时约 0.14mW,自热通常可以接受。要是原理图上写着 5mA 甚至 10mA,就要重新算自热误差,尤其是探头封装散热差的时候。

2.2 把 0.385mV/°C 放大到 ADC 能吃的范围

PT100 的正温特性可以先用线性式估算:R(T) ≈ 100 × (1 + 0.00385 × T)。按 -50~200°C 算,电阻范围约 80.31~175.86Ω。选一个 100Ω 的基准电阻 Rref,让同一个 1mA 电流流过 PT100 和 Rref,取两者压差,差分电压就是 I × (Rpt100 - Rref),范围约 -19.69~75.86mV。这个信号直接进 12 位 ADC 肯定不够看,必须放大并加偏置。

参数取值说明
激励电流 I1mA自热小,信号够用
基准电阻 Rref100Ω,0.01%,低温漂对应 0°C 的 PT100
差分电压范围-19.69~75.86mV按 -50~200°C 估算
放大倍数 G20输出 -0.394~1.517V
偏置 Vbias0.4V把负压抬到 ADC 正输入范围
ADC 输入范围0.006~1.917V3.3V 参考下留足余量
ADC 分辨率3.3V / 4095 ≈ 0.806mV12 位
输入等效温度0.806mV / (0.385mV/°C × 20) ≈ 0.105°C单次采样理论值

计算顺序不要乱:先定量程,再选激励电流,接着选 Rref 和增益,最后算偏置并校验 ADC 上下轨。上面这组参数里,200°C 时输出约 1.917V,离 3.3V 还有 1.38V 余量;-50°C 时输出约 0.006V,贴着 0V 但还没进死区。如果量程只要 0~150°C,增益可以提到 25 左右;如果坚持用 50 倍,200°C 时输出会超过 3.3V,高温段直接饱和。这个账不提前算,后面调代码就是玄学。

2.3 原理图落地时先检查哪几个节点

拿到原理图,不要先看单片机引脚分配,先看模拟前端这几个节点。第一,恒流源的负载范围要覆盖 PT100 的 80~176Ω 加上线阻,断线时输出不能把运放顶到电源轨。第二,差分放大的四个电阻或仪表放大器的增益电阻要匹配,0.1% 起步,否则共模抑制上不去。第三,ADC 输入前要有 1kΩ 加 100nF 的 RC,截止频率约 1.6kHz,既能滤高频噪声,又不会拖慢采样建立时间。

第四,模拟地和数字地要单点汇合,继电器、显示屏、串口芯片的电流不要从采样地线上走。第五,供电要干净,ADC 参考最好用外部基准,别直接拿单片机 VDDA 当基准,屏幕一亮纹波就上来了。第六,三线制不是接三根线就自动补偿,需要两条匹配的电流路径或电桥结构;如果只有一个恒流源,三线制只能算部分补偿,线阻还是会进结果。

提示:ADC 输入不要贴着 0V 和 3.3V 轨用,留 50mV 以上余量,否则运放输出饱和后软件滤波也救不回来。

3. 程序框架拆解:从 ADC 采样到 PT100 温度换算的代码落地

程序不要写成「读一次 ADC 就显示」的循环。温度探头本身有热惯性,但电源噪声和继电器动作是快的,采样、滤波、换算、输出要分层。资料里的程序如果是 HAL 库风格,常见结构是定时器触发 ADC、DMA 搬数、中值滤波加滑动平均、再走电阻和温度换算,最后组帧上传。

3.1 ADC 采样与滤波:DMA、中值滤波和滑动平均

先定采样率。温度显示刷新 2Hz 足够,但 ADC 采样可以做到 100Hz,用多余的采样点换稳定性。DMA 开双缓冲,半满和全满中断各处理一半,主循环只取滤波后的值。中值滤波取 5 点,专门打掉继电器吸合那种单点尖峰;滑动平均取 16 点,把随机噪声压下去。

#define ADC_N_MED 5 #define ADC_N_AVG 16 static uint16_t adc_med_buf[ADC_N_MED]; static uint16_t adc_avg_buf[ADC_N_AVG]; static uint8_t med_idx = 0, avg_idx = 0; /* 5 点中值滤波:复制后插入排序,取中间值 */ uint16_t median5(uint16_t *buf, uint16_t new_val) { uint16_t tmp[ADC_N_MED]; buf[med_idx] = new_val; med_idx = (med_idx + 1) % ADC_N_MED; for (int i = 0; i < ADC_N_MED; i++) tmp[i] = buf[i]; for (int i = 1; i < ADC_N_MED; i++) { uint16_t key = tmp[i]; int j = i - 1; while (j >= 0 && tmp[j] > key) { tmp[j + 1] = tmp[j]; j--; } tmp[j + 1] = key; } return tmp[ADC_N_MED / 2]; } /* 16 点滑动平均:先填满,再滚动 */ uint16_t moving_average16(uint16_t new_val) { static uint32_t sum = 0; static uint8_t filled = 0; sum -= adc_avg_buf[avg_idx]; adc_avg_buf[avg_idx] = new_val; sum += new_val; avg_idx = (avg_idx + 1) % ADC_N_AVG; if (filled < ADC_N_AVG) filled++; return (uint16_t)(sum / filled); }

这段代码里,median5的窗口是 5 个采样点,按 100Hz 算就是 50ms,能压住窄脉冲但不会把真实温度变化抹平。moving_average16的窗口是 160ms,输出更新率仍然是 100Hz,但等效带宽降下来了。sum用 32 位,16 个 12 位码值相加不会溢出。实际工程里可以把中值滤波放在 DMA 中断里做轻量处理,滑动平均放主循环,避免中断里做太多浮点运算。

3.2 电压到电阻:差分放大公式怎么写进代码

ADC 码值先转电压,再按差分放大关系反推 PT100 电阻。公式是 Vout = Vbias + G × I × (Rpt100 - Rref),反解得到 Rpt100 = Rref + (Vout - Vbias) / (G × I)。代码里把 VREF、G、I、Rref、Vbias 都做成宏或配置结构,标定时只改参数,不改公式。

#define ADC_FULL_SCALE 4095.0f #define VREF_VOLT 3.300f #define GAIN 20.0f #define I_EXC_A 0.001f #define RREF_OHM 100.0f #define VBIAS_VOLT 0.400f float adc_to_voltage(uint16_t raw) { return (float)raw * VREF_VOLT / ADC_FULL_SCALE; } float voltage_to_resistance(float vout) { /* 先减偏置,再除增益和激励电流,最后加基准电阻 */ return RREF_OHM + (vout - VBIAS_VOLT) / (GAIN * I_EXC_A); }

ADC_FULL_SCALE用 4095 还是 4096 取决于标定方式,12 位 ADC 常用 4095 做端点映射,但最终以两点标定为准。VREF_VOLT不是随便填 3.3,最好用万用表实测基准电压,再写进参数。GAIN和I_EXC_A的误差会直接乘到电阻上,所以这两个值要么用精密电阻算,要么靠标定修正。VBIAS_VOLT是差分放大器输出的零点偏置,单电源电路里通常由基准分压产生,实测值比标称值可靠。

如果原理图用的是专用 RTD 前端芯片,这一节可以跳过,程序直接读芯片的电阻寄存器;但后面温度换算和标定思路仍然适用,只是把「ADC 码值到电阻」换成「寄存器值到电阻」。

3.3 电阻到温度:Callendar-Van Dusen 正温公式与负温迭代

PT100 的电阻温度关系不是一条直线。正温段用 Callendar-Van Dusen 方程,C 项为 0;负温段要把 C 项带上,解析解不好直接写,常用牛顿迭代。代码里先判断电阻是否大于等于 100Ω,正温走二次方程,负温走迭代,迭代 4~5 次就够。

#include <math.h> #define R0_OHM 100.0f #define CVD_A 3.9083e-3f #define CVD_B -5.775e-7f #define CVD_C -4.183e-12f float resistance_to_temp_c(float r) { if (r >= R0_OHM) { /* 正温:R = R0(1 + A*T + B*T^2),取正根 */ float disc = CVD_A * CVD_A - 4.0f * CVD_B * (1.0f - r / R0_OHM); return (-CVD_A + sqrtf(disc)) / (2.0f * CVD_B); } else { /* 负温:牛顿迭代解 R = R0[1 + A*T + B*T^2 + C*(T-100)*T^3] */ float t = (r / R0_OHM - 1.0f) / CVD_A; for (int i = 0; i < 5; i++) { float t2 = t * t; float t3 = t2 * t; float f = R0_OHM * (1.0f + CVD_A * t + CVD_B * t2 + CVD_C * (t - 100.0f) * t3) - r; float df = R0_OHM * (CVD_A + 2.0f * CVD_B * t + CVD_C * (4.0f * t3 - 300.0f * t2)); t -= f / df; } return t; } }

正温段的判别条件是r >= 100.0f,对应 0°C 以上。disc是二次方程判别式,正常参数下不会为负;如果为负,说明电阻值已经超出 PT100 合理范围,程序要报故障而不是继续算。负温迭代的初值用线性式给,收敛很快,5 次迭代在 Cortex-M 上开销可以接受。系数 A、B、C 是 PT100 的标准系数,不要随手改;如果探头是 PT1000,R0 和系数体系要整体换掉。

3.4 串口帧与报警输出:把温度变成可用的数据

温度算出来只是内部变量,要上传就得定帧格式。常见做法是帧头 0xAA 0x55、类型、4 字节浮点温度、2 字节 CRC,小端发送。报警输出加回差,比如上限 80°C 报警、降到 78°C 才解除,避免继电器在阈值附近反复吸合。

typedef struct { uint8_t head[2]; uint8_t type; float temp; uint16_t crc; } TempFrame; static uint16_t crc16_modbus(const uint8_t *data, uint16_t len) { uint16_t crc = 0xFFFF; while (len--) { crc ^= *data++; for (int i = 0; i < 8; i++) crc = (crc & 1) ? (crc >> 1) ^ 0xA001 : (crc >> 1); } return crc; } void send_temp_frame(float temp) { TempFrame f; f.head[0] = 0xAA; f.head[1] = 0x55; f.type = 0x01; f.temp = temp; f.crc = crc16_modbus((uint8_t *)&f, sizeof(f) - 2); uart_send_bytes((uint8_t *)&f, sizeof(f)); } /* 报警回差:上限 80°C,回差 2°C */ void alarm_task(float temp, uint8_t *alarm_on) { if (!(*alarm_on) && temp > 80.0f) *alarm_on = 1; if ( (*alarm_on) && temp < 78.0f) *alarm_on = 0; }

帧结构里temp直接用 float,调试方便,但跨平台时要确认双方字节序。CRC 放在最后两字节,接收端按同样结构校验。alarm_task只处理回差逻辑,继电器驱动和硬件互锁要另做,尤其是加热场景,软件失效时不能只靠一个 GPIO 电平。串口上传周期建议 200~500ms,太快会把显示和报警任务挤掉,太慢又看不出阶跃响应。

4. 标定与误差控制:两点标定、线阻补偿和滤波参数怎么设

PT100 采集系统装完板子,不标定就敢写精度,基本是给自己挖坑。标定不是把代码里的 100Ω 改成实测值就完事,要把「ADC 码值到电阻」和「电阻到温度」分开看。前者靠两点标定锁增益和偏移,后者靠标准系数和线制补偿。顺序反了,后面调滤波只会越调越乱。

4.1 两点标定:用 100Ω 和 138.5Ω 把增益偏移锁死

最稳的标定不是拿冰水和沸水,而是用精密电阻箱替代 PT100。冰水受气压和冰水比例影响,沸水受海拔影响,实验室里看着是 0°C 和 100°C,实际可能差零点几度。电阻箱直接给 100.00Ω 和 138.51Ω,对应 0°C 和 100°C,重复性好,也不用等温度平衡。

标定点电阻箱输出理论温度记录内容
低点100.00Ω0°CADC 原始码值 20 次平均
高点138.51Ω100°CADC 原始码值 20 次平均

标定公式按电阻线性化写:R = k × raw + b。低点 raw1 对应 100Ω,高点 raw2 对应 138.51Ω,k = (138.51 - 100) / (raw2 - raw1),b = 100 - k × raw1。把 k、b 写进 Flash,上电先读标定参数,读不到就用默认值并报未标定。

typedef struct { float k; float b; uint32_t magic; } CalibParam; float raw_to_resistance(uint16_t raw, const CalibParam *c) { return c->k * (float)raw + c->b; } void calib_two_point(uint16_t raw_at_100, uint16_t raw_at_13851, CalibParam *c) { c->k = (138.51f - 100.0f) / ((float)raw_at_13851 - (float)raw_at_100); c->b = 100.0f - c->k * (float)raw_at_100; c->magic = 0x50433130; /* 标定参数有效标志 */ }

这段代码只标定电阻链路,温度换算仍然走标准公式。raw_at_100和raw_at_13851要先做中值加平均,不能拿单次采样。magic用来判断 Flash 里是不是有效参数,避免第一次上电读到全 0xFF 算出离谱的 k。标定后至少复测 50Ω、150Ω 两个中间点,看非线性误差,别只盯两个端点。

4.2 三线制与四线制:线阻到底吃掉了多少温度

PT100 每 0.385Ω 对应 1°C,0.1Ω 线阻就是约 0.26°C。两线制把两根引线电阻全算进探头电阻,探头线越长、线越细,误差越大。三线制用三条线,两条电流路径匹配时可以把引线电阻抵消掉大部分,但匹配不好仍会剩误差。四线制把电流回路和电压测量回路分开,线阻基本不进结果,是精度最高的接法。

线制接线复杂度线阻影响适用
两线制最低全部计入,0.1Ω 约 0.26°C短距离、低精度
三线制中等匹配后大部分抵消工业现场常见
四线制最高基本消除高精度、实验室

原理图如果是三线制,要检查两条引线是不是接到匹配的电流源或差分输入端。只把第三根线接地,不叫三线补偿。四线制则要确认激励电流从一对线走,差分放大从另一对线取压,ADC 前端的高阻抗不能把电流引到测量线上。探头屏蔽层单端接地,别两头都接,否则地环流会变成低频漂移。

4.3 滤波与采样率:显示 2Hz 但采样 100Hz 的参数搭配

采样 100Hz、显示 2Hz 是个好搭配:前端有足够点数做滤波,人眼又不需要看 100Hz 的跳动。中值 5 点加滑动平均 16 点,等效延迟大约 80~100ms,对温度这种慢变量完全够。如果还嫌跳,再串一个一阶 IIR,alpha 取 0.05~0.1,别一上来就 0.01,否则恒温箱到了设定值还在慢慢爬。

float iir_lowpass(float x, float *y, float alpha) { /* alpha 越小越平滑,滞后越大;0.1 对应约 10 个采样周期的时间常数 */ *y += alpha * (x - *y); return *y; }

alpha取 0.1 时,按 100Hz 采样算,时间常数约 0.1s,90% 响应约 0.23s;取 0.02 时时间常数约 0.5s,响应明显变慢。恒温控制里滤波太狠会让 PID 以为温度没变化,输出继续加,等滤波值上来已经超调。我一般让显示滤波和控温滤波分开,显示可以重一点,控温用轻量中值加短平均。

注意:1mA 连续激励下自热很小,但如果原理图把电流提到 5mA,PT100 功耗约 2.5mW,探头封装散热差时可能带来零点几度的自热漂移,标定时要等热平衡。

5. 避坑与排查:PT100 采集翻车的 4 个现场记录

PT100 前端的坑很少是单一原因,往往是电源、地、线制和代码参数一起作用。下面这四类现象在调试现场出现频率最高,按「现象 → 原因 → 解决」记,比只看波形截图有用。

5.1 读数跳变:电源纹波和地线把 12 位 ADC 打回 8 位

现象:探头放在恒温槽里,温度读数在 24.3~26.8°C 之间跳,手碰板子或屏幕亮起时跳得更厉害,万用表

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询