ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于STM32和MAX31865的三线制PT100高精度温度采集方案

基于STM32和MAX31865的三线制PT100高精度温度采集方案 简介一份基于STM32的MAX31865热电偶放大器驱动源码面向嵌入式开发者适用于工业温控、数据采集终端、实验室仪器等需要高精度热电偶测温的场景。MAX31865支持K、J、T等多种热电偶内置冷端补偿配合STM32的SPI接口即可读取温度数据。包体共6个文件主要是.c和.h源码分为MAX31865驱动层与stm32_spi底层接口层压缩包仅6KB轻量且便于整体移植。代码覆盖GPIO与SPI初始化、寄存器读写、CS片选管理、温度数据解析等关键环节并给出冷端补偿与错误处理的基础框架可直接嵌入现有工程后按硬件修改。驱动文件命名直观包含max31865.c/h和MAX31865drv.c/h两套实现便于对照理解SPI通信和芯片寄存器操作也可按模块选用。该资源已有1114人学习下载适合正在调试热电偶模块、需要参考STM32 SPI驱动写法或希望缩短测温方案开发周期的开发者。 最近把手上的温度采集模块重新整理了一遍核心方案是MAX31865 PT100 三线制 STM32没用外部放大器和 ADC直接用 MAX31865 这颗专用 RTD 转换芯片。这套组合在 0~200°C 范围内做到 0.1°C 级别的重复性没有任何问题而且驱动写起来比想象中简单关键是搞清楚几个寄存器和接线细节。如果你正准备调模温机、恒温槽或者实验室测温板这篇内容可以直接当参考。先交代一下背景我用的 MCU 是 STM32F103C8T6开发环境是 Keil 5 HAL 库SPI 选用 SPI1PA5/PA6/PA7。传感器是国产三线制 PT100探头导线长约 1 米。文章会从选型接线开始一直讲到驱动代码、实测数据和排错尽量把坑都踩一遍给你看。1. 选型与接线为什么是 MAX31865 三线制 PT1001.1 MAX31865 这颗芯片到底解决了什么问题测 PT100 最原始的办法是自己搭恒流源给传感器供电然后用差分 ADC 采集电压再软件算温度。这个方案看着简单实际做起来让人头疼恒流源的温漂、ADC 的参考电压精度、引线电阻带来的误差任何一个环节没处理好温度数据就会飘。MAX31865 把这些问题全部打包解决了。它内部集成了可编程增益放大器、15 位 ADC、偏置电压源、故障检测电路还支持 2 线、3 线、4 线制 PT100/PT1000。芯片直接通过 SPI 上报 15 位数字量MCU 只需要读寄存器再换算温度。它的测量分辨率能做到 0.03125°C 左右以 PT100 430Ω 参考电阻计算这个精度用来做工业模温控制、实验室测量都绰绰有余。另外它内置了 RTD 开路、短路检测和过压欠压检测接错线、探头断线都能通过寄存器读出来这对现场维护是实打实的省心功能。不用自己再去设计断线检测电路了。1.2 三线制接线FORCE 和 SENSE 必须分清很多人第一次用 MAX31865 会直接在传感器两端接两根线这在短距离、常温环境没问题但导线一长误差就上来了。PT100 的引线电阻大约每米 0.1~0.2Ω看线径对应温度误差就是 0.3~0.5°C这对高精度应用完全不能接受。三线制的原理是用一根额外的 SENSE 线去感知 FORCE 线上的压降从而抵消引线电阻。MAX31865 的引脚接线方式是——PT100 的一端接RTDIN另一端的两根线分别接RTDIN-和REFIN-。参考电阻 RREF 接在 REFIN 和 REFIN- 之间。三线制接法就是为了让 REFIN- 引脚参与感测芯片内部会利用这个引脚的电压来自动补偿引线电阻。实话说我第一次画板子的时候就把接线搞反了把两根线都接到了 RTDIN- 上导致温度读数偏高约 20°C查了半天才发现是 REFIN- 悬空了。所以这里强调一下三线制中 RTDIN- 和 REFIN- 各接一根线不能共用同一条走线直接短接两者要在 PT100 探头根部汇合。1.3 RREF 和滤波参数怎么选RREF 是整个温度精度的基石。PT100 标准搭配是 430Ω 精密电阻温度测到 850°C 时 PT100 阻值约 390Ω430Ω 的满量程刚好覆盖。如果你用的是 PT1000RREF 要换成 4300Ω。RREF 的精度直接影响温度换算结果我选的是 0.1% 精度、温漂 5ppm/°C 的金属膜电阻。这里算一笔账如果 RREF 偏差 1Ω在 0°C 附近造成的温度误差大约是 1/(430) × 32768 × 0.034 ≈ 2.6°C。所以别图便宜随便找个电阻必须用好料。滤波位在配置寄存器 bit40 代表 50Hz 抑制1 代表 60Hz 抑制。国内电网是 50Hz所以配置成 0。如果这个位设置和当地电网频率不匹配读到的数据会带上明显的工频纹波看起来就是温度值小幅度周期性跳变。2. STM32 端 SPI 工程搭建2.1 引脚分配和 SPI 模式我的接线方案如下表所示你看完可以直接抄STM32 引脚连接对象说明PA5MAX31865 SCLKSPI1 时钟PA6MAX31865 SDOSPI1 MISO芯片输出PA7MAX31865 SDISPI1 MOSI芯片输入PA4MAX31865 CS软件控制片选PB0MAX31865 DRDY数据就绪可以悬空或接 EXTIMAX31865 支持 SPI Mode 1CPOL0CPHA1和 Mode 3CPOL1CPHA1我用的是 Mode 1。CS 千万不要用 STM32 的硬件 NSS那个引脚号固定不方便而且 MAX31865 的片选逻辑比较简单用普通 GPIO 拉低拉高就足够了。有时候有人会把 SPI 复用引脚选到 PB3/PB4/PB5这里提醒一下PB3、PB4 默认是 JTAG 引脚如果你不关闭 JTAG 的话这俩脚是输出不了波形的。在 GPIO 初始化之前最好加上一句__HAL_AFIO_REMAP_SWJ_NOJTAG()否则你会在示波器上看到 SCK 完全不动作白白浪费半天时间。2.2 HAL 库 SPI 配置我用 CubeMX 生成基础工程SPI1 的参数配置如下void MX_SPI1_Init(void) { hspi1.Instance SPI1; hspi1.Init.Mode SPI_MODE_MASTER; hspi1.Init.Direction SPI_DIRECTION_2LINES; hspi1.Init.DataSize SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity SPI_POLARITY_LOW; hspi1.Init.CLKPhase SPI_PHASE_2EDGE; hspi1.Init.NSS SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_16; hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; HAL_SPI_Init(hspi1); }使用 72MHz 系统时钟时SPI 预分频 16 分频就是 4.5MHz对于 MAX31865 来说是安全的。它的 SPI 时钟上限大约 10MHz你如果用分频 89MHz也问题不大但 4.5MHz 更稳妥读取一次温度也就几十微秒完全不会成为性能瓶颈。CS 引脚我用宏定义控制#define MAX31865_CS_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET) #define MAX31865_CS_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET)2.3 硬件 SPI 还是软件模拟如果你的项目里 SPI1 已经被别的设备占了或者引脚不够用用 GPIO 模拟 SPI 也完全可以。MAX31865 是典型的从设备模拟时序的要求是片选拉低、按位发送 8 位地址、按位读取 8 位数据、片选拉高。唯一注意的就是时钟频率不要太高模拟 SPI 的话用HAL_Delay(1)级别的延时或者空循环延时都没问题。我个人还是建议用硬件 SPI因为 HAL 库的HAL_SPI_TransmitReceive用起来太方便了后面读取 RTD 值只需要一轮收发就完成了。软件模拟不是不行但代码量和出错概率都会上去。3. 驱动代码核心实现3.1 寄存器读写基础MAX31865 的寄存器地址很简单读指令是寄存器地址高位置 10x80 | reg写指令是寄存器地址保持 bit70。核心用到这几个寄存器地址寄存器名称说明0x00配置寄存器偏置、滤波、线制、转换模式0x01RTD MSBRTD 转换值高 8 位0x02RTD LSBRTD 转换值低 7 位 故障标志位0x03/0x04高故障阈值默认 0x7FFF满量程0x05/0x06低故障阈值默认 0x00000x07故障状态寄存器各类故障标志基于这个表写驱动先从最基础的读写函数开始。注意每次传输完成后要把 CS 拉高MAX31865 没有片选保持的概念你一直拉着低电平反而可能把数据线搞乱。uint8_t max31865_read8(uint8_t reg) { uint8_t cmd 0x80 | reg; uint8_t dummy 0x00; uint8_t rx 0x00; MAX31865_CS_LOW(); HAL_SPI_TransmitReceive(hspi1, cmd, dummy, 1, 10); HAL_SPI_TransmitReceive(hspi1, dummy, rx, 1, 10); MAX31865_CS_HIGH(); return rx; } void max31865_write8(uint8_t reg, uint8_t val) { uint8_t tx[2] { reg 0x7F, val }; MAX31865_CS_LOW(); HAL_SPI_Transmit(hspi1, tx, 2, 10); MAX31865_CS_HIGH(); }3.2 初始化配置初始化要做两件事配置芯片工作模式把高/低故障阈值设为默认满量和零值。配置寄存器我给出的值是 0xC8二进制是 11001000各位含义是bit71VBIAS 偏置开启读取 RTD 前必须打开bit61FAULTCLR清除上一次遗留的故障标志bit4050Hz 滤波bit31三线制模式bit10自动转换模式bit00正常工作不关断void max31865_init(void) { max31865_write8(0x00, 0xC8); max31865_write8(0x03, 0xFF); max31865_write8(0x04, 0xFF); max31865_write8(0x05, 0x00); max31865_write8(0x06, 0x00); }这里有个细节FAULTCLR位不是一直写 1 的它在写入后会自动清除。所以每次重新初始化后我先用 0xC8 把故障清掉后面读到故障状态就是当前真实发生的故障。3.3 读取 RTD 值和故障检测读取 RTD 值需要连续读 0x01 和 0x02 两个寄存器MAX31865 支持在 CS 拉低期间连续读取所以我可以一次发三个字节完成整个操作。uint16_t max31865_read_rtd(uint8_t *fault_flag) { uint8_t cmd 0x80 | 0x01; uint8_t tx[3] { cmd, 0x00, 0x00 }; uint8_t rx[3] { 0x00, 0x00, 0x00 }; uint16_t raw 0; MAX31865_CS_LOW(); HAL_SPI_TransmitReceive(hspi1, tx, rx, 3, 20); MAX31865_CS_HIGH(); raw ((uint16_t)rx[1] 8) | rx[2]; *fault_flag 0; // rx[2] 的最低位是 FAULT 标志位为 1 表示存在故障 if (rx[2] 0x01) { *fault_flag max31865_read8(0x07); } // 真正的 RTD 值是 15 位LSB 最低位是故障标志 return raw 1; }细心的朋友会发现rx[0]读取的是发送 cmd 时从机返回的配置寄存器值我们并不关心。rx[1]是 RTD MSBrx[2]是 RTD LSB。把 MSB 和 LSB 拼接后再右移一位因为rx[2]的最低位是故障标志要剔除掉。3.4 电阻到温度的换算函数这一步是驱动里最需要讲清楚的地方。芯片给出的原始 ADC 值需要先换算成电阻值公式是RTD电阻 原始ADC值 × RREF / 32768因为 MAX31865 的 ADC 是 15 位满量程 32767而满量程对应的电阻值就是 RREF。这里的数学逻辑很简单ADC 值占满量程的比例乘以参考电阻就是当前电阻。拿到电阻值之后按照 IEC 60751 标准PT100 在 0°C 以上满足 Callendar-Van Dusen 公式R(t) R0 × (1 A×t B×t²) A 3.9083e-3 B -5.775e-7 R0 100Ω反推温度需要用二次方程求根公式直接代入即可float max31865_rtd_resistance(uint16_t adc) { return (float)adc * 430.0f / 32768.0f; } float max31865_temperature(float rtd) { const float R0 100.0f; const float A 3.9083e-3f; const float B -5.775e-7f; if (rtd R0) { float disc sqrtf(R0 * R0 * A * A - 4.0f * R0 * B * (R0 - rtd)); return (-R0 * A disc) / (2.0f * R0 * B); } else { // 0°C 以下用二分法在 [-200, 0] 区间求解 float lo -200.0f, hi 0.0f, mid 0.0f; for (int i 0; i 40; i) { mid (lo hi) / 2.0f; float r_cal R0 * (1.0f A * mid B * mid * mid); if (r_cal rtd) lo mid; else hi mid; } return mid; } }rtd R0判断的是 0°C 以上的情况。0°C 以下如果还想用二次方程算误差会偏大所以我直接用二分法在 -200~0°C 区间内求解。40 次迭代足够收敛到小数点后很多位了单片机上跑一次也就几微秒不用担心性能。3.5 主循环轮询读取主循环里我做了 200ms 轮询用串口把 ADC 原始值、电阻值和温度值一起打印出来调试阶段信息全一点没坏处。while (1) { uint8_t fault 0; uint16_t adc max31865_read_rtd(fault); float rtd max31865_rtd_resistance(adc); float temp max31865_temperature(rtd); printf(adc%u, rtd%.3f ohm, temp%.2f C, fault0x%02X\r\n, adc, rtd, temp, fault); HAL_Delay(200); }注意自动转换模式下芯片大约每 66ms 完成一次转换所以轮询间隔尽量大于这个时间。如果你用 1-shot 单次转换模式读取前要先写配置寄存器触发转换再等待转换完成标志。4. 实测数据与精度分析4.1 用精密电阻箱模拟 PT100 的标定结果我没有直接拿恒温槽做严苛标定但手里刚好有个 0.05% 精度的电阻箱用它模拟 PT100 在不同温度下的标准阻值这样可以直接验证驱动算法。实测几个关键点如下电阻箱设定阻值对应标准温度驱动读到的温度100.00Ω0.00°C0.03°C119.40Ω50.00°C49.96°C138.51Ω100.00°C100.02°C157.33Ω150.00°C150.04°C175.86Ω200.00°C200.06°C实测读到的温度偏差都在 0.06°C 以内这个误差主要来自 RREF 电阻本身的精度和 ADC 量化误差对大多数应用完全够用。驱动里的换算算法本身没有引入明显误差。如果想让精度再上一个台阶可以做两点一是用更高精度的 0.05% RREF二是在固件里做两点校准——比如用恒温槽测 0°C 和 100°C 两个点然后做线性增益修正。工业模温控制要做到正负 0.1°C通常需要保留这一步校准单纯靠芯片本身达到这个指标会有点勉强。4.2 真实 PT100 探头的动态表现接上真实 PT100 探头后我把探头放进冰水混合物里读数稳定在 0.2°C 上下波动放到室温 25°C 环境下读数稳定在 24.9~25.1°C。这个波动范围可以说是三线制 MAX31865 的正常表现了。影响波动的因素包括探头的自热效应、电源纹波、PCB 布局的参考地噪声等。MAX31865 内部转换本身非常稳定如果你看到超过 0.5°C 的跳动大概率是外围问题而不是芯片问题。5. 常见问题与排错记录5.1 读到的 RTD 值全是 0xFFFF 或者全 0这是最常见的问题基本发生在第一次上电调试。读回 0xFFFF满码大概率是 RTD 开路也就是探头没接好或者导线断了。这时候读一下故障状态寄存器 0x07如果 bit3 为 1就说明检测到了 RTD 开路。还有一种情况是 FAULT 标志一直存在读取函数里已经把故障状态返回了串口打印出来一看就知道。读回 0 的情况通常是 SDO/MISO 信号没通或者是 SPI 模式配置不对。用示波器抓一下 PA6 的波形看芯片是否真的有数据输出。如果 PA6 一直低电平检查一下芯片供电和焊接。5.2 温度数据跳变超过 1°C 级别如果稳定读数在某个值附近来回跳 1°C 以上先看电源纹波。MAX31865 的供电建议用 LDO 单独给模拟电源不要直接挂在电机驱动或继电器电源上。其次是滤波位设置确认配置寄存器 bit4 跟电网频率一致国内用 50Hz 就把 bit4 写成 0。另外还有一个隐藏很深的坑SPI 读取频率不要超过转换完成频率。自动转换模式下你使劲读读到的其实是上一次的转换结果数据看起来就是一条平台跳变线。解决办法就是轮询间隔放大到 100ms 以上或者干脆用 DRDY 引脚做外部中断触发读取。5.3 三线制接线导致的温度整体偏高这个问题我在文章开头提到过这里再强调一次三线制必须让 RTDIN- 和 REFIN- 各接一根 PT100 引线。如果你只接了两根线一端接 RTDIN另一端接 RTDIN-REFIN- 悬空那么芯片会把 FORCE 线上的引线电阻全部算进去实测会高出好几度甚至十几度。判断方法很简单用万用表量 RTDIN- 和 REFIN- 之间的阻值如果是接近 0Ω 说明两根线被短接到了一起正确的接法应该是一个 PT100 引线电阻的阻值几欧姆以内。5.4 使用 PB3/PB4 复用引脚时没波形如果你把 SPI 映射到 PB3/PB4/PB5而且系统时钟用的是 SWD 调试下载那么必须先禁用 JTAG否则 PB3/PB4 不会正常输出。在MX_GPIO_Init()里加一句__HAL_AFIO_REMAP_SWJ_NOJTAG();加上之后再初始化 GPIO 复用功能就能正常输出波形了。这个问题在标准库工程里尤其常见HAL 库如果用 CubeMX 生成则需要在 Pinout 里手动配置。最后再分享一个小经验MAX31865 的 CS 控制必须干净利落建议在每次 SPI 通信前把 CS 拉低通信结束后立刻拉高中间不要插入其他 GPIO 操作。如果 CS 的拉低拉高时序里混入了延时或者中断处理偶尔会读到错误的帧数据出现个别异常跳点。把读取函数放到临界区或者保证 CS 操作连续性比堆数据滤波还有效。本文还有配套的精品资源点击获取
返回列表