恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

STM32F103驱动MAX31865实现PT100铂电阻高精度测温详解

  • 首页
  • 资讯中心
  • /
  • STM32F103驱动MAX31865实现PT100铂电阻高精度测温详解

相关资讯

superlance 的监控案例 2026/9/1 7:15:30
Android17车载音频进阶之AudioReach总体架构(一百六十七) 2026/9/1 7:10:29
计算机毕业设计之基于AES的用户教学资源推荐系统 2026/9/1 7:10:29

最新资讯

旋变解码实战:STM32+AD2S1210硬件配置与SPI数据读取
跑通Demo的核心挑战:环境配置、分层排查与调试思路
微服务开发实战复盘:Spring Cloud Alibaba技术栈与团队协作方法论
STM32F107 + LAN8720A + LwIP 实现 Modbus TCP 从站全解析
基于Quartus II的FPGA简易时钟设计:Verilog分频与数码管显示实现
ai时代上站记录

今日推荐

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

本周热门

备战数据库管理工程师校招:索引、事务、备份恢复核心考点解析
数字电路时序基石:深入理解建立时间与保持时间
蓝桥杯国赛超声波测距机:从单片机原理到嵌入式系统实战

本月精选

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

STM32F103驱动MAX31865实现PT100铂电阻高精度测温详解

发布时间:2026/9/1 7:15:30
STM32F103驱动MAX31865实现PT100铂电阻高精度测温详解 简介针对STM32F103与MAX31865组合的铂电阻测温需求这套资源提供了可直接运行的Keil工程代码与配套原理图面向单片机开发者和仪表类项目初学者可帮助快速搭建三线制PT100测温电路。内容涵盖三线铂电阻测温完整程序设计DRDY报警引脚按代码直接连接单片机原理图中相关部分可精简代码内MAX31865.h中的参考电阻阻值支持按实际硬件修改为400Ω原理图稍作调整后也能用于二线、四线接法工程灵活性较强。压缩包共131个文件、2.22MB以34个h与33个c源码文件为核心附有uvprojx/uvoptx工程配置、hex/axf烧录文件及map/lst编译信息方便直接打开工程编译或烧录到开发板验证。已有780人学习下载对想掌握SPI温度采集、铂电阻非线性处理及报警逻辑的读者可省去文档摸索时间对照代码与原理图即可完成硬件连接、参数调整与软件调试。 去年帮朋友改一套液体循环控温设备的探头原设计用的是NTC热敏电阻加运放放大温度一超过60℃读数就开始飘换了好几个批次传感器一致性也差。后来整套改成PT100铂电阻加MAX31865方案硬件和代码全部重新做实测下来在0~150℃区间内稳定度做到正负0.1℃以内校准之后整机精度才算真正立住了。今天把整套设计思路、原理图要点和可执行代码整理出来分原理、硬件、软件、校准、调优五块讲适合正在做工业测温、环境监测或者想把手上的STM32F103项目从模拟测温升级成数字测温的朋友直接参考。1. 为什么是MAX31865铂电阻测温的选型逻辑1.1 铂电阻测温的原理与工程难点铂电阻PT100/PT1000的基本原理很简单铂的电阻值随温度近似线性变化0℃时PT100电阻正好100Ω100℃时约138.51Ω温度越高阻值越大。相比NTC它的优势是长期稳定性好、重复性高、线性度相对好所以工业现场凡是要求精度和可靠性的测温点基本都绕不开铂电阻。但工程实现上有几个坎。第一铂电阻的电阻变化量很小PT100在0~100℃只变化38.5Ω如果要分辨0.1℃至少得分辨0.0385Ω这对测量电路的ADC精度和基准稳定性要求很高。第二传感器到电路板之间必然有导线导线电阻如果串进测量回路直接变成测温误差。两线制接法下1m的普通铜线可能就有0.1~0.2Ω相当于0.25~0.5℃的误差根本没法用。第三要给铂电阻供电就会有电流流过电流在传感器上产生的焦耳热会抬高自身温度这叫自热效应电流越大误差越大。这几个坎叠加起来就决定了测温电路不能是简单的电阻分压加ADC读取必须在激励源、导线补偿、信号调理上做文章。1.2 MAX31865、ADS1220、运放方案三选一我做这个项目之前先比了一圈方案列个表给大家参考方案精度潜力线阻补偿数字接口成本上手难度MCU ADC 恒流源 仪表运放一般需自己设计无低高ADS122024位ADC高支持三线/四线SPI中中高MAX31865专用RTD芯片高支持两线/三线/四线SPI中低ADS1220是通用高精度ADC灵活度大可以做热电偶、电桥等多种测量但配置项多滤波和参考源都要自己调。MAX31865是TI/ADI这类厂商专门为RTD设计的芯片内置了精密激励电流源、16位Delta-Sigma ADC、开短路检测和故障阈值比较器外围只需要一个基准电阻加几个电容就能跑起来。它把给RTD供电、测量、补偿、诊断这些事全封装好了对STM32开发者来说少写几百行驱动代码。我最终选MAX31865核心原因是这个芯片的激励源和测量基准是同一个架构理论上能做到比例式测量电源电压波动对精度的影响很小。而且它自带三线制补偿能直接解决前面说的导线电阻问题不用自己搭恒流源电路。2. 硬件搭建原理图里真正影响精度的几个细节2.1 MAX31865外围电路与基准电阻计算MAX31865的外围电路非常精简核心就四个部分电源去耦、基准电阻RREF、滤波电阻RFILTER、滤波电容。先看RREF的计算。MAX31865内部把RREF作为ADC的参考电阻测得的电压比例直接反映了RTD电阻与RREF的比值。公式是R_RTD (ADC_CODE / 32768) × RREFRREF的值决定了满量程测量范围。用PT100时典型选430Ω为什么是这个值因为PT100在0℃时100Ω在850℃时约390.5Ω取430Ω能让整个量程正好落在ADC的约90%处既留有余量又不浪费分辨率。如果做低温场景只需测到200℃选350Ω或400Ω也能提高低温段的分辨率但通用场景直接430Ω最省事。注意很多现成模块上印着4.3kΩ的RREF那是给PT1000用的接PT100时如果直接买模块要看清楚否则读出来的温度会大将近10倍。RFILTER通常取100Ω串在RTD和芯片引脚之间配合100pF电容组成低通滤波主要滤掉工业现场的高频干扰。电容要靠近芯片引脚放置走线尽量短别放在传感器那头。2.2 三线制接法最常见的坑在这里三线制接线是MAX31865使用中翻车率最高的地方我见过好几个朋友把FORCE-悬空或者直接接地结果温度读数稳定但整体偏高几度问题就出在线路补偿没生效。三线制的正确接法是这样PT100一端引一根线接到FORCE另一端引两根线分别接到RTDIN-和FORCE-。在PT100本体那一端RTDIN-和FORCE-这两根线是短接在一起的但它们在芯片端是独立引脚。测量时FORCE提供激励电流FORCE-是电流回流路径RTDIN和RTDIN-则是高阻电压采样端几乎不流过电流。这样设计的好处是电流回路上产生的导线压降不会进入电压采样回路而两根采样线如果等长等材质其压降在差分测量中可以互相抵消。所以三根线的线径、长度尽量一致这是三线制能否发挥作用的关键。原理图里还要注意MAX31865的DRDY引脚是开漏输出板子上需要加上拉电阻典型4.7kΩ或10kΩ否则读不到电平变化。STM32F103的GPIO虽然可以配置内部上拉但建议外部加一个更稳妥毕竟这个引脚是接收中断和查询用的。2.3 最小系统与PCB布局要点STM32F103部分用最小系统就够8MHz晶振、复位电路、BOOT0下拉、3.3V去耦电容。晶振尽量远离MAX31865的模拟引脚避免高频时钟干扰测温信号。电源方面MAX31865的供电建议单独加一个10Ω电阻加10uF电容组成的RC滤波或者直接用一个低噪声LDO供电。温度测量精度和电源噪声的关系很大如果MCU和MAX31865共用一个开关电源纹波会直接耦合到测量结果里。我实测过同一块板子上开关电源供电和LDO供电读数跳动差了将近0.05℃。PCB布局上信号线走线顺序传感器连接器进来后先经过滤波电容然后进MAX31865再去STM32。中间不要穿过PWM驱动线、继电器控制线这类强干扰源。走线尽量短粗尤其是FORCE和FORCE-这两条激励线如果板内走线过长板内铜箔电阻也会影响精度。3. STM32F103驱动MAX31865寄存器级SPI实现3.1 SPI时序与引脚分配MAX31865的接口是标准SPI但它有几个细节和普通SPI器件不太一样。第一SPI模式要选CPOL1、CPHA1也就是SCLK空闲为高电平数据在第二个边沿采样这个和很多Flash芯片、SD卡默认的模式不同初学时容易在这里卡住。第二寄存器地址只有7位最高位用来区分读操作1和写操作0。我用的引脚分配如下功能STM32引脚说明SCKPA5SPI1时钟MISOPA6SPI1数据输入MOSIPA7SPI1数据输出CSPB0软件控制片选DRDYPB1转换完成标志开漏需上拉CS用普通GPIO控制不用SPI的硬件NSS因为MAX31865要求CS在每次读写事务之间拉高拉低软件控制更灵活。3.2 驱动代码初始化、读写寄存器先给STM32标准库版本HAL库的读者可以照着逻辑移植核心时序不变。/* max31865.h */ #ifndef __MAX31865_H #define __MAX31865_H #include stm32f10x.h #define MAX31865_CS_HIGH() GPIO_SetBits(GPIOB, GPIO_Pin_0) #define MAX31865_CS_LOW() GPIO_ResetBits(GPIOB, GPIO_Pin_0) #define MAX31865_DRDY_READ() GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) #define RREF_VALUE 430.0f // 基准电阻 #define RTD_NOMINAL 100.0f // PT100 0℃标称值 #define REG_CONFIG 0x00 #define REG_RTD_MSB 0x01 #define REG_RTD_LSB 0x02 #define REG_HI_FAULT_MSB 0x03 #define REG_LO_FAULT_MSB 0x05 #define REG_FAULT_STATUS 0x07 void MAX31865_Init(void); uint8_t MAX31865_Read8(uint8_t reg); void MAX31865_Write8(uint8_t reg, uint8_t value); uint16_t MAX31865_Read16(uint8_t reg); float MAX31865_ReadTemperature(void); #endif/* max31865.c */ #include max31865.h #include delay.h static uint8_t SPI_TransferByte(uint8_t byte) { while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendByte(SPI1, byte); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) RESET); return SPI_I2S_ReceiveByte(SPI1); } static void MAX31865_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE); /* SCKPA5, MOSIPA7 */ GPIO_InitStructure.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); /* MISOPA6 */ GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); /* CSPB0 输出 */ GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); /* DRDYPB1 输入内部上拉 */ GPIO_InitStructure.GPIO_Pin GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_Init(GPIOB, GPIO_InitStructure); MAX31865_CS_HIGH(); } static void MAX31865_SPI_Config(void) { SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); 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); } uint8_t MAX31865_Read8(uint8_t reg) { uint8_t val; MAX31865_CS_LOW(); SPI_TransferByte(0x80 | reg); val SPI_TransferByte(0xFF); MAX31865_CS_HIGH(); return val; } void MAX31865_Write8(uint8_t reg, uint8_t value) { MAX31865_CS_LOW(); SPI_TransferByte(reg); SPI_TransferByte(value); MAX31865_CS_HIGH(); } uint16_t MAX31865_Read16(uint8_t reg) { uint16_t hi, lo; MAX31865_CS_LOW(); SPI_TransferByte(0x80 | reg); hi SPI_TransferByte(0xFF); lo SPI_TransferByte(0xFF); MAX31865_CS_HIGH(); return (uint16_t)((hi 8) | lo); } void MAX31865_Write16(uint8_t reg, uint16_t value) { MAX31865_CS_LOW(); SPI_TransferByte(reg); SPI_TransferByte((value 8) 0xFF); SPI_TransferByte(value 0xFF); MAX31865_CS_HIGH(); } void MAX31865_Init(void) { MAX31865_GPIO_Config(); MAX31865_SPI_Config(); /* 配置寄存器0xABVBIAS1自动转换模式三线制50/60Hz滤波 */ MAX31865_Write8(REG_CONFIG, 0xAB); /* 等待偏置电压建立 */ delay_ms(10); /* 清故障锁存 */ MAX31865_Read8(REG_FAULT_STATUS); MAX31865_Write8(REG_CONFIG, 0xAF); /* FAULT位写1清除故障 */ MAX31865_Write8(REG_CONFIG, 0xAB); /* 设置故障阈值高300Ω低80Ω超出则认为传感器异常 */ MAX31865_Write16(REG_HI_FAULT_MSB, (uint16_t)((300.0f / RREF_VALUE) * 32768.0f) 1); MAX31865_Write16(REG_LO_FAULT_MSB, (uint16_t)((80.0f / RREF_VALUE) * 32768.0f) 1); }配置寄存器0xAB这行值得多说一句0xAB二进制是10101011其中bit71打开VBIAS偏置bit6-501设置自动转换模式bit31选择三线制bit1-011选择50/60Hz数字滤波。如果用的是两线制传感器bit3要改成0。3.3 读取流程与故障检测MAX31865的转换结果存在0x01高字节和0x02低字节两个寄存器里但有个关键细节实际有效数据只有15位最低位固定是0。所以读出16位后要右移一位再参与电阻计算新手经常在这里直接拿0x01寄存器的高8位去算结果偏了一倍。DRDY引脚在转换过程中是低电平转换完成变高电平。读取前先等待DRDY为高确保数据是新的。完整读取函数float MAX31865_ReadTemperature(void) { uint16_t raw; uint16_t adc_code; float resistance; float temperature; uint8_t fault; /* 读取故障寄存器 */ fault MAX31865_Read8(REG_FAULT_STATUS); if (fault 0x0F) { /* 低4位有故障RTD开路/短路/基准异常等返回错误值 */ return -999.0f; } /* 等待转换完成 */ while (MAX31865_DRDY_READ() BIT_RESET); raw MAX31865_Read16(REG_RTD_MSB); adc_code raw 1; /* 15位有效数据 */ resistance ((float)adc_code / 32768.0f) * RREF_VALUE; /* 电阻转温度见下一章 */ temperature PT100_ResistanceToTemperature(resistance); return temperature; }故障寄存器0x07的低4位分别是RTD开路、RTD短路、基准电压过高、基准电压过低出现任何一个都要优先处理不能继续用测量结果。我建议在正式产品里把故障标志传到上位机或本地显示别只返回一个-999否则现场调试时很难判断是传感器断了还是线没接好。4. 从原始码值到真实温度换算、查表与线性化4.1 电阻值还原与CVD方程MAX31865给我们的原始值是ADC码值第一步要还原成电阻。这个公式前面出现过R_RTD (ADC_CODE / 32768) × RREF还原出电阻之后再反查温度。PT100的温度-电阻关系用IEC60751标准里的Callendar-Van Dusen方程描述0℃以上是二次多项式R(t) R0 × (1 A×t B×t²)其中R0100A3.9083×10⁻³B-5.775×10⁻⁷。0℃以下是四次多项式要额外加C项。0℃以上的场景可以直接套一元二次求根公式反解t代码很短。但0℃以下或者想要覆盖-200~850℃全量程直接解高次方程很麻烦工程上普遍的做法是查表加线性插值。查表法的好处是温度范围可以任意裁剪表里的点越密精度越高代价只是占一点Flash。4.2 查表插值实现我用的是50℃间隔的IEC60751标准表从-200℃到850℃一共22个点占Flash空间很小线性插值后全程误差可以控制在0.1℃以内。为什么50℃间隔就够因为铂电阻的曲线接近线性在相邻两个50℃点之间做线性近似偏差远小于0.1℃。如果追求更高精度可以把步长缩到20℃几十个点也完全放得下。static const float pt100_table[][2] { {-200.0f, 18.52f}, {-150.0f, 39.72f}, {-100.0f, 60.26f}, {-50.0f, 80.31f}, {0.0f, 100.00f}, {50.0f, 119.40f}, {100.0f, 138.51f}, {150.0f, 157.33f}, {200.0f, 175.86f}, {250.0f, 194.10f}, {300.0f, 212.05f}, {350.0f, 229.72f}, {400.0f, 247.09f}, {450.0f, 264.18f}, {500.0f, 280.98f}, {550.0f, 297.49f}, {600.0f, 313.71f}, {650.0f, 329.64f}, {700.0f, 345.28f}, {750.0f, 360.63f}, {800.0f, 375.70f}, {850.0f, 390.48f} }; float PT100_ResistanceToTemperature(float resistance) { uint8_t lo 0; uint8_t hi sizeof(pt100_table) / sizeof(pt100_table[0]) - 1; uint8_t mid; float temp; if (resistance pt100_table[0][1]) return pt100_table[0][0]; if (resistance pt100_table[hi][1]) return pt100_table[hi][0]; while (hi - lo 1) { mid (lo hi) / 2; if (pt100_table[mid][1] resistance) lo mid; else hi mid; } temp pt100_table[lo][0] (resistance - pt100_table[lo][1]) / (pt100_table[hi][1] - pt100_table[lo][1]) * (pt100_table[hi][0] - pt100_table[lo][0]); return temp; }二分查找加线性插值最坏情况下循环5次MCU负载可以忽略。4.3 校验与边界处理查表函数在上下界之外直接返回端点值这样不会算出异常大的数但上位机需要根据这个端点值判断传感器是否超出量程。更严谨的做法是返回一个特殊值由调用方统一处理。关于阈值寄存器的设置我建议不要设得太宽那样故障检测形同虚设。以我的项目为例测温范围是0~150℃就把高阈值设在160℃对应的电阻约161.05Ω低阈值设在-10℃对应的电阻约96.09Ω一旦传感器断线、短路线缆异常故障位能第一时间置起来。阈值寄存器的写入格式同样是15位有效数据左移一位这一段在初始化代码里已经示范过了。5. 实测调优正负0.1摄氏度的距离有多远5.1 噪声来源与数据平滑把硬件和代码跑通后我先把PT100探头放进冰水混合物里做0℃基准测试第一个版本读数是0.08℃到-0.06℃之间跳算下来峰峰值约0.14℃已经接近正负0.1℃的指标但还不够稳。逐项排查后发现噪声主要来自三个方面。第一个是电源。前面说过MAX31865和MCU如果共用一个普通LDO还行但共用开关电源时读数跳动能到0.2℃以上。我在MAX31865的VDD引脚前加了一级RC本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号