简介:面向STM32单片机开发者与测温项目初学者,基于STM32F103+MAX31865的铂电阻测温资源,涵盖三线制铂电阻测温的完整原理图与可执行代码,可解决高精度温度采集、SPI通信配置及硬件连接适配等常见问题,适用于工业测温、环境监测、实验室数据采集等场景。压缩包共131个文件,以h头文件、c源文件为主,其中h头文件包含寄存器与接口定义,c源文件实现核心测温逻辑,另有uvprojx工程配置、编译生成的axf/hex烧录文件及辅助脚本等,整体仅2.22MB,目录结构清晰,便于直接打开工程查看或烧录验证。已有780人学习下载。原理图中DRDY报警部分可裁剪,按代码直接接单片机即可;代码内MAX31865.h的参考电阻阻值可按实际改为400,适配不同量程;原理图稍作调整即可支持二线、四线接法,适合快速二次开发与教学实验。 STM32F103 和 MAX31865 这对组合,我做测温项目时用过很多次,工业现场模温控制、设备热保护、实验室数据采集都吃过这套方案的红利。PT100 铂电阻本身线性度好、稳定性高,但信号调理这块要是处理不好,再好的传感器也白搭。MAX31865 这颗芯片刚好把最麻烦的模拟前端、ADC 转换、故障检测全包了,MCU 这边只需要老老实实跑 SPI 就行。这篇文章我把完整原理图设计思路、外部电路参数计算和可执行代码全部摊开讲,从选型理由到实际踩坑,争取一篇让你从零跑通。
1. 整体设计与技术选型思路
1.1 为什么选 MAX31865 而不是自己搭桥式放大电路
很多人一提到铂电阻测温,第一反应是用电桥加仪表放大器再加 ADC。这条路能做,但非常痛苦。电桥输出的差分信号只有毫伏级别,需要高精度仪表放大器,而仪表放大器的温漂、共模抑制比、增益误差全都需要仔细校准。光是一个零点漂移就能让你调半天,更别提生产一致性。
MAX31865 的核心价值在于把 PT100 的激励电流源、可编程增益放大器、15 位 Σ-Δ ADC、偏置电压检测、开路短路故障检测全部集成在一块芯片里。外接一颗精密参考电阻 RREF,芯片就能直接输出数字量。MCU 这边通过 SPI 读取寄存器,算一下电阻值,再查表或者套公式就得温度。
从实际工程角度看,这套方案解决的核心问题是模拟链路不确定性的转移。模拟部分的不确定性全部被芯片内部的高精度电路吸收,输出端只有一根干净的 SPI 数字总线。对于工业模温控制这种需要正负 0.1 摄氏度精度的场景,这种集成方案的可复现性远远优于自己搭分立电路。
1.2 为什么不直接用 ADC 采样分压电阻
也有朋友问:PT100 串个精密电阻分压,ADC 直接采不就完了?理论上能测,但实际精度会非常差。原因有三个。
第一,PT100 的电阻变化率很小,0 到 100 摄氏度的变化量只有约 38.5 欧姆,而线路电阻、接触电阻、ADC 参考电压漂移都会直接叠加到测量结果上。第二,ADC 的分压测量需要稳定的参考电压,而 MCU 内部的参考电压本身受温度影响。第三,这种方案完全无法抑制导线电阻的干扰,尤其当传感器到板卡的距离超过一米,误差会迅速放大。
MAX31865 支持两线、三线、四线铂电阻。工业环境强烈建议用三线制,用恒流源激励抵消导线电阻,这也是模温控制场景里正负 0.1 摄氏度精度能落地的关键。四线制精度更高但需要传感器额外两根引线,成本上去;两线制简单但有导线电阻误差,只适合短距离、低精度场景。
2. 原理图设计要点与硬件细节
2.1 三线制 PT100 接法原理
三线制的本质是让激励电流环路和电压检测环路分开。MAX31865 内部有一个恒流源给铂电阻供电,电流从 FORCE+ 引脚流出,经过 PT100,回到 FORCE- 引脚。同时芯片通过 RTDIN+ 和 RTDIN- 两个高阻输入引脚检测铂电阻两端的电压,这个电压不受电流通路上的导线电阻影响。
三根线里,两根线上流过激励电流,另一根线配合电压检测,形成开尔文连接的简化版。实际接线时,PT100 的三根引线必须是同一规格、同一长度的铜线,才能保证两段导线电阻相等。原理图上我习惯在三个接线端子旁边标注"三线等长",提示 PCBA 加工和现场接线时注意这一点。
要注意的是,MAX31865 数据手册里三线制模式内部会自动扣除一根导线的压降,所以差分电压的计算会比两线制多出一个导线电阻的补偿量。这个补偿原理不需要用户干预,芯片自动完成,但前提是 FORCE+ 和 RTDIN+ 必须短接在传感器同一根线上,FORCE- 和 RTDIN- 短接在另一根线上,否则补偿逻辑会失效。
2.2 MAX31865 外围电路参数计算
MAX31865 外围最关键的元件是参考电阻 RREF。PT100 在 0 摄氏度时电阻为 100 欧姆,在 100 摄氏度时约 138.5 欧姆。RREF 的取值必须大于传感器在整个量程内的最大电阻,否则 ADC 会饱和溢出。
我常用的量程是 -50 到 250 摄氏度,这个区间 PT100 的电阻变化范围大约在 80.3 欧姆到 194.1 欧姆。RREF 选用 430 欧姆的 0.1% 精度、25ppm 温漂的精密电阻,给满量程留出了足够的余量。如果只做 0 到 100 摄氏度的温度控制,RREF 选 390 欧姆也可以,但 430 欧姆是 MAX31865 数据手册给出的标准匹配值,我用下来线性度最好。
RREF 的计算方法很简单:RREF 必须大于量程上限对应的铂电阻阻值,同时不能超过 VBIAS 引脚对地的负载能力。430 欧姆在这颗芯片推荐值范围内,而且市面上很容易买到高精度低温漂的 430 欧姆贴片电阻。除此之外,还需要在靠近芯片电源脚放 0.1uF 和 10uF 的退耦电容,参考电阻的布局要远离发热器件,这点后面细说。
量化分辨率方面的计算我也列一下。MAX31865 内部是 15 位 ADC,满量程对应的数字量是 32768。ADC 读数与电阻的关系是:
RTD = (ADC 读数 / 32768) × RREF
430 欧姆参考电阻下,一个 LSB 对应约 0.0131 欧姆。PT100 在 0 摄氏度附近灵敏度约 0.385 欧姆每摄氏度,换算过来一个 LSB 对应的温度分辨率约 0.034 摄氏度。理论分辨率已经完全够用,实际精度更多取决于 RREF 的精度和 PCB 布线质量。
2.3 原理图设计避坑提醒
原理图层面有几个容易忽略的细节,对精度影响非常大。
第一个是参考电阻的布局。RREF 要尽可能靠近 MAX31865 的 REF+ 和 REF- 引脚,且这两个引脚之间的走线要短而宽。参考电阻下面是禁止铺铜的,尤其不能有数字地铜皮穿过,否则电源噪声会耦合进参考电阻,直接改变 ADC 的基准。我第一版 PCB 就是这里翻过车,参考电阻旁边走过一根 SPI 时钟线,结果温度读数跳了差不多 0.3 摄氏度,后来把走线挪开才稳定下来。
第二个是 SPI 接口的上拉电阻。MAX31865 的 SDO 引脚是开漏输出,必须在外部接一个 10 千欧上拉电阻到 VCC。有些朋友直接用 MCU 的 SPI 硬件片选加外设库,以为软件配置内部上拉就够了,但内部上拉的阻值太大,高速 SPI 下沿不够陡,容易出现读取错位的问题。
第三个是 VBIAS 引脚的处理。很多参考设计里 VBIAS 直接接一个 0.1uF 电容到地,但这个引脚实际上是给传感器提供偏置的,推荐接一个 1 千欧电阻到 FORCE+,具体接法要以选用的电路基础为准。我测试下来,标准接法下测量稳定性更好,但还是以你拿到的芯片手册里的典型应用电路为基准。
3. 固件实现与温度换算
3.1 SPI 底层初始化
MAX31865 的通信接口是标准 SPI,支持的模式是极性和相位都为 1,也就是 SPI_MODE_1。STM32F103 的 SPI1 最大支持 18MHz 时钟,但 MAX31865 的 SPI 时序要求时钟周期不能低于 100ns,换算过来最快 10MHz。为了保证信号完整性,我建议 SPI 时钟初始化到 1MHz 到 5MHz 之间即可,读取频率不高,没必要追求极限速度。
STM32F103 的 SPI 初始化代码我也贴出来,基于标准外设库,方便大家直接改。注意时钟使能要同时打开 GPIOA、GPIOB、GPIO 复用功能时钟。使用硬件 SPI 的好处是代码量少、时序稳定,但 CS 引脚我习惯用软件控制,灵活性更高。
void SPI1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_SetBits(GPIOA, GPIO_Pin_4); SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_Init(SPI1, &SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); }时钟分频这里选了 16 分频,STM32F103 默认系统时钟 72MHz,SPI1 时钟就是 36MHz,16 分频后是 2.25MHz,完全在 MAX31865 的时序限制范围内。如果你用 8 分频,SPI 时钟到 4.5MHz 也能正常工作,但我实测下来 2MHz 左右的时钟抗干扰能力更强,尤其是连接线比较长的时候。
3.2 MAX31865 寄存器操作与温度计算
MAX31865 的寄存器映射很清晰。配置寄存器地址 0x00,写操作后芯片根据配置字决定是否自动转换。我采用连续转换模式,配置寄存器写入 0xD2,也就是启用 VBIAS、启用三线制、启用自动转换、50Hz 和 60Hz 工频滤波都打开。
三个关键数据寄存器是 0x01(RTD MSB)、0x02(RTD LSB)、0x03(高故障阈值 MSB)和 0x04(低故障阈值 LSB)。读取 RTD 电阻值时需要把 0x01 和 0x02 两个字节拼成一个 16 位整数,再右移 1 位得到真正的 15 位 ADC 值。
完整的读数函数我用中断方式实现,读取前拉低 CS,读取后拉高 CS,确保单次事务原子性。要注意的是,MAX31865 的寄存器地址最高位是 R/W 标志位,读寄存器时要把地址最高位置 1,比如读 0x01 寄存器,实际发送的地址字节是 0x81。
下面是我实际项目里跑过的温度采集代码,包含 SPI 读写和电阻到温度的换算。
uint16_t MAX31865_ReadRTD(void) { uint16_t raw = 0; GPIO_ResetBits(GPIOA, GPIO_Pin_4); SPI1_ReadWriteByte(0x01); /* 读 RTD 数据寄存器 MSB */ raw = SPI1_ReadWriteByte(0xFF) << 8; raw |= SPI1_ReadWriteByte(0xFF); GPIO_SetBits(GPIOA, GPIO_Pin_4); /* 去掉第 0 位的故障标志位,得到 15 位 ADC 值 */ raw >>= 1; return raw; } float MAX31865_GetTemp(void) { uint16_t adc = MAX31865_ReadRTD(); float rtd_res = (float)adc * 430.0f / 32768.0f; float temp; /* 0~100 摄氏度范围内使用 Callendar-Van Dusen 简化公式 */ temp = (rtd_res / 100.0f - 1.0f) / 0.00385f; return temp; }这里我用的简化线性公式只在 0 到 100 摄氏度范围内精度尚可,全量程范围误差会变大。正规做法是建一张 PT100 分度表,程序里通过查表加线性插值的方式获取温度。分度表可以按每 10 摄氏度一个点存储,MCU 内部用二分查找找到区间,再线性插值出精确值,这样既能保证精度又不会占用太多 Flash。
3.3 配置寄存器与故障状态处理
MAX31865 的故障状态寄存器地址是 0x07,里面包含开路、短路、超量程等故障标志。每次读取完温度数据后,建议马上去读这个寄存器。如果发现有故障标志置位,温度数据就是无效的,后续控制逻辑必须进入异常处理流程。
比如做工业模温控制的时候,如果 PT100 传感器断线了而程序没察觉,系统会拿一个错误的温度读数去控制加热器,轻则温控失效,重则会触发安全事故。所以我的代码里有个独立函数,专门检查故障状态,故障时把温度输出钳位到一个安全值,同时置位一个全局错误标志供上层逻辑查询。
下面这段是故障检测的代码,寄存器读回来的字节各位与数据手册一致。如果读到任何故障标志,就返回 1:
uint8_t MAX31865_CheckFault(void) { uint8_t fault; GPIO_ResetBits(GPIOA, GPIO_Pin_4); SPI1_ReadWriteByte(0x07 | 0x80); /* 读故障寄存器 */ fault = SPI1_ReadWriteByte(0xFF); GPIO_SetBits(GPIOA, GPIO_Pin_4); if (fault & 0x2C) { /* 0x2C 是常见的故障位组合,包含开路和短路标志 */ return 1; } return 0; }需要注意一点:MAX31865 的故障锁存机制比较特殊,一旦故障发生,故障标志会一直保持,直到你向配置寄存器重新写入相同的配置字才会清除。这其实是芯片的一种保护特性,避免故障状态被自动复位导致系统反复震荡。实际调试时如果一直读出来故障,先检查是不是传感器线没接好,不要急着清标志。
3.4 温度校准方法
校准是整个系统精度落地的最后一步。MAX31865 自带的 ADC 精度很高,但 RREF 的精度误差、PCB 布局引入的寄生电阻、传感器本身的精度等级都会影响最终测量误差。正常情况下出厂校准至少要做零点校准和满量程校准两步。
零点校准最简单:把 PT100 传感器放进冰水混合物里,标准大气压下稳定后读温度值,记录偏差并存入 Flash。满量程校准可以用精密电阻箱模拟一个 100 欧姆、138.5 欧姆的标准电阻,替代 PT100 接入电路,读数对比后计算增益误差。我实际使用下来,经过这两步校准后,整套系统在 0 到 100 摄氏度范围内的误差可以控制在正负 0.1 摄氏度以内。
校准参数建议以结构体的形式保存,底层温度计算函数在读 ADC 后先用校准系数修正电阻值,再换算温度。这样上层业务代码不需要关心校准逻辑,校准只是修改两个浮点数。代码实现如下:
typedef struct { float zero_offset; float gain_error; } CalibParam; float MAX31865_GetCalibTemp(CalibParam *calib) { uint16_t adc = MAX31865_ReadRTD(); float rtd_res = (float)adc * 430.0f / 32768.0f; rtd_res = (rtd_res - calib->zero_offset) * (1.0f + calib->gain_error); return (rtd_res / 100.0f - 1.0f) / 0.00385f; }校准系数要保存到不掉电存储区,比如 STM32F103 内部 Flash 的最后一个扇区,或者外接 EEPROM。每次开机先读取校准参数再进入采集循环,如果没有有效的校准标志位,就使用默认值 0 和 1,保证系统即使没校准也能正常工作,只是精度会差一些。
4. 常见问题与排查经验
4.1 温度读数异常的原因排查
这套系统我前后调试过几十次,把最常见的异常现象和根因整理成了一张速查表,开发时遇到问题直接对照排查,效率很高。
| 异常现象 | 可能原因 | 排查方法 |
|---|---|---|
| 读数一直是 0 | SPI 通信失败或 CS 引脚配置错误 | 用逻辑分析仪抓 SPI 波形,确认 CS 拉低后有时钟信号 |
| 读数跳动超过 0.5 摄氏度 | 参考电阻布局不合理或有干扰源 | 检查 RREF 下方是否有铜皮,传感器线是否与电源线平行走线 |
| 读数明显偏高 | 三线制接线错误 | 确认 FORCE 和 SENSE 引脚与传感器的连接方式与数据手册一致 |
| 读数固定在某一个值不动 | 芯片配置寄存器没写入成功 | 读取配置寄存器回读,确认写入值是否一致 |
| 靠近加热器时读数漂移 | 传感器线受到电磁干扰 | 传感器线使用屏蔽双绞线,屏蔽层单端接地 |
| 故障寄存器总是报开路 | PT100 接线端子虚焊或延长线断线 | 用万用表测量传感器两端电阻,常温下应为 100 欧姆左右 |
| 低温段正常高温段偏差大 | 分度表范围不够或拟合公式不对 | 改用完整分度表查表加插值 |
实际项目里我发现很多所谓的 SPI 通信问题,最后查明都是 GPIO 复用功能没配置对。STM32F103 的 SPI1 默认引脚是 PA5 时钟、PA6 数据输入、PA7 数据输出,这几个引脚必须先配置成复用推挽输出,否则 SPI 外设无法控制引脚电平。
4.2 几个实用的调试小技巧
开发这套系统最痛苦的事情是看着温度数据在串口助手里乱跳,却不知道是硬件问题还是软件问题。我后来养成一个习惯:先固定硬件,再单独测底层。拿到第一版 PCB 后,先把 MAX31865 的 SDO、SCLK、CS 引脚全部引出,用 SPI 调试器直接读芯片寄存器,确认芯片本身能正常工作,再焊接 STM32F103 部分。这样一旦出问题,问题边界非常清晰。
还有一个技巧是软件模拟 SPI 来验证硬件连接。如果 STM32F103 的硬件 SPI 一直没有响应,可以先用 GPIO 模拟 SPI 时序,把 CLK 拉高拉低、数据位逐位翻转。虽然慢,但每一步都能用示波器或者逻辑分析仪验证,定位问题非常快。我那个 2.25MHz 的硬件 SPI 跑不通的板子,最后就是用软件 SPI 确认 CS 拉低时序和时钟极性反了。
关于停机模式还想补充一点。如果系统需要低功耗待机,STM32F103 进入 STOP 模式之前一定要把 MAX31865 的 VBIAS 关掉,也就是重新配置寄存器,把 VBIAS 位置 0。我之前做过一个电池供电的测温记录仪,忘了关 VBIAS,停机模式下整个系统还有接近 5mA 的电流,电池几天就耗干了。后来在进入停机之前调用一次关闭 VBIAS 的配置,待机电流降到了微安级别。
补偿完这些细节,这套测温方案我在三个不同项目里复用过,硬件改版两次,代码几乎没有大动。MAX31865 把模拟链路封装得很完善,STM32F103 只负责 SPI 通信和业务逻辑,开发效率非常高。最后分享一个经验:原理图阶段多花半小时检查 RREF 布局和传感器接线定义,能省下后面至少三天的调板时间。
本文还有配套的精品资源,点击获取