做温度采集项目的时候很多人一开始会选DS18B20或者NTC热敏电阻图个省事。可一旦涉及到工业现场、高精度测量或者长距离传输PT100铂电阻几乎是绕不开的选择。但这玩意儿不像数字传感器那样直接读温度它本质上是一个阻值随温度变化的电阻想把电阻值准确转成温度前端调理电路就得花点心思。MAX31865就是专门干这个事的芯片把模拟前端、ADC、故障检测全部集成在一起MCU只需要通过SPI读寄存器就行。这项目就是用STM32CubeMX配合HAL库让STM32通过硬件SPI去读MAX31865的转换结果同时把MAX31865的DRDY引脚接到外部中断上转换完成自动通知MCU来取数。相比轮询方式这套方案CPU占用很低主循环可以腾出手干别的活非常适合温度采集只是其中一个模块、系统还要同时处理显示、通信、控制逻辑的场景。文章会从硬件设计、CubeMX配置、驱动代码到温度换算、故障排查全部过一遍新手照着做也能跑起来。1. 项目整体设计思路拆解1.1 为什么用MAX31865而不是自己搭电阻分压电路PT100测温的原理其实就一句话PT100的阻值随温度近似线性变化0℃时是100Ω温度每升高1℃大约增加0.385Ω。所以测温本质上是测电阻。听起来简单但实际工程里有两个坑一是PT100的阻值变化量太小100℃时也才138.5Ω左右从0℃到100℃只变化38.5Ω如果直接用电阻分压然后送ADC稍微有点引线电阻、参考电压波动测量误差就大到离谱二是PT100的引脚线缆本身有电阻尤其是长距离布线时线阻会造成很大偏差。自己搭恒流源、仪表放大器、高精度ADC不是不能做但调试周期长、校准麻烦、温漂难控还要处理故障检测做产品基本不划算。MAX31865就是把这一整套模拟链路都做了芯片内部集成了精密恒流源给PT100提供激励电流同时把PT100上的压降和参考电阻上的压降送入一个高精度16位ADC实际15位有效数据加符号位自动算出RTD电阻与参考电阻的比值。MCU拿到ADC码值之后乘以参考电阻值就能还原PT100的电阻值再通过查表或公式换算成温度。省掉了大量外围电路而且芯片内置开路、短路检测工业场景下这个功能非常实用。1.2 硬件SPI和软件模拟SPI怎么选很多老工程师习惯用GPIO软件模拟SPI因为不受引脚复用限制随便挑几个IO口就行。但软件模拟SPI有一个明显的问题时序是靠延时或者空循环来凑的CPU被占得死死的时序精度也不如硬件外设稳定。尤其是在系统里还有中断、RTOS、LCD刷新、无线通信等任务的时候软件SPI的时序非常容易被打断导致数据错位。MAX31865本身时钟频率可以跑到5MHz通信速度要求不高但稳定性要求很高数据一旦错位读出来的温度就是乱跳。硬件SPI的优势就在于时钟的产生、数据的移位全由外设硬件完成CPU只需要往寄存器里丢数据、等完成标志整个传输过程不占用CPU时间。用CubeMX配置也简单选择Full-Duplex Master模式设置好时钟极性和相位剩下的交给我们。另外硬件SPI不需要额外写延时函数代码更干净出问题的概率也小得多。这个项目使用硬件SPI是最稳妥的选择。1.3 外部中断DRDY的意义MAX31865有一个DRDY引脚数据就绪输出开漏当一次温度转换完成时DRDY会被拉低直到MCU读取RTD数据寄存器后才自动恢复高电平。如果不用这个引脚MCU就得不停地给MAX31865发命令查询转换状态或者干脆死等一段固定时间再去读前者浪费CPU后者效率低还可能读早了。把DRDY接到STM32的外部中断引脚配置为下降沿触发芯片转换完成会立刻通知MCUMCU在中断回调里只置一个标志位主循环看到标志再去执行SPI读取。这样温度采集几乎是零延迟而且处理器不用空转等待。2. 硬件电路设计与关键参数2.1 MAX31865核心工作原理MAX31865内部主要由恒流源、模拟开关、ADC、控制寄存器和SPI接口组成。芯片通过恒流源给PT100和参考电阻Rref都施加相同的激励电流然后分别测量PT100两端电压和Rref两端电压。由于电流相同PT100电阻值等于参考电阻乘以两者电压之比。ADC输出的码值其实就是这个比例关系的数字表达。具体换算关系是Rrtd ADC_CODE × Rref ÷ 32768。为什么是32768因为MAX31865内部ADC的有效分辨率是15位满量程对应2^15 32768也就是说当ADC码值达到32768时RTD电阻值等于参考电阻值。这样只需要一个高精度参考电阻PT100的测量精度就完全交给MAX31865和Rref了MCU这边做乘除法非常简单。2.2 PT100的接线方式与跳线配置MAX31865支持2线、3线、4线三种PT100接法区别在于如何消除引线电阻的影响。2线制最简单但引线电阻直接串进测量回路误差最大适合短线近距离、精度要求不高的场合。3线制是工业现场最常用的两根线分别走恒流源的正负端第三根线用来抵消引线电阻精度已经能满足大多数需求。4线制精度最高用独立的感测线将引线电阻的影响基本降为零适合实验室或高精度计量场景。在代码层面只需要修改MAX31865配置寄存器的第4位3-Wire位和硬件接线对应就行。4线或2线制时该位写03线制时该位写1。有一点必须注意如果硬件接的是4线却把配置寄存器写成了3线模式读数会明显偏大偏差可能有好几欧姆在0℃附近会显示成几十度的误差。之前我就在这上面栽过跟头排查很久才发现是配置位搞错了。2.3 参考电阻Rref的选取参考电阻的精度直接决定测量精度这可以说是MAX31865电路设计里最关键的器件。根据MAX31865数据手册Rref的大小决定了ADC的满量程范围。ADC满量程对应的RTD电阻值等于Rref所以Rref必须大于PT100在最大量程时的电阻值否则会超量程。常用做法是需要测-200℃到850℃全范围的话PT100在850℃时约390.5ΩRref推荐选430Ω留出大约10%的余量。如果只测0℃到200℃的窄范围可以适当减小Rref来提升分辨率比如选200Ω或250Ω但大多数场合430Ω是最省心的。Rref的温漂指标要注意建议选温度系数25ppm/℃以内、精度0.1%的金属膜电阻或精密电阻。不少买来的MAX31865模块上直接焊的就是430Ω 0.1%电阻如果自己画板子这块不能省。同样的PT100本身的精度等级也分A级/B级/C级A级误差±0.15℃B级±0.3℃想要整机精度高探头和参考电阻都得把关。2.4 实际接线清单这里以STM32F103C8T6和常见MAX31865模块为例列一份接线参考。MAX31865模块通常有8个引脚VIN、GND、SCK、SDI对应MOSI、SDO对应MISO、CS、DRDY外加PT100的接线端子。MAX31865引脚STM32引脚说明VIN3.3V芯片供电模块上一般有稳压GNDGND共地SCKPA5SPI1_SCK时钟SDI/MOSIPA7SPI1_MOSI主机发送接芯片数据输入SDO/MISOPA6SPI1_MISO主机接收接芯片数据输出CSPB0用普通GPIO做片选软件控制DRDYPA0外部中断输入下降沿触发PT100探头的接线要看是几线制的。用4线探头接法PT100一端两根线接FORCE和SENSE另一端两根线接FORCE-和SENSE-模块上通常标了RTD和RTD-两对端子。如果是3线探头模块上三个端子接两根线加一根补偿线具体看模块丝印。3. STM32CubeMX工程配置全流程3.1 基础工程配置打开STM32CubeMX选择MCU型号STM32F103C8T6如果用的是其他型号操作逻辑一样。首先在System Core里配置RCC选择Crystal/Ceramic Resonator启用外部高速晶振。然后在SYS里选择Serial Wire保留SWD调试口不然下载一次程序后第二次就连接不上了。时钟树里把HCLK设到72MHzAPB1为36MHzAPB2为72MHz后面算SPI波特率要用。接着在Connectivity里找到SPI1Mode选择Full-Duplex Master。Parameter Settings里各项参数这么设Data Size选8BitsFirst Bit选MSB FirstPrescaler选32分频这样SPI时钟就是72MHz ÷ 32 2.25MHz在MAX31865允许的5MHz以内。Boot位把SPI_NSS设为Disable因为这里用软件片选。时钟极性这块MAX31865支持SPI Mode 1CPOLLowCPHA2 Edge和Mode 3CPOLHighCPHA2 Edge实际项目里我用Mode 1比较多也就是CPOL Low、CPHA 2 Edge。如果你的板子数据一直不对可以试着切到Mode 3两种模式MAX31865都支持但个别模块的电路设计可能对时序有影响。3.2 GPIO引脚配置片选与外部中断硬件片选还是软件片选这问题我直接给结论用软件片选。MAX31865的CS是低电平有效硬件NSS模式下虽然能由SPI外设自动拉低拉高但很多STM32系列的NSS硬控制逻辑和HAL库配合起来并没有想象中那么省心尤其是通信结束时机不好把握容易造成CS释放过早导致最后一两个字节读出来是错的。软件片选就是普通GPIO通信前拉低通信完拉高完全可控代码就多两行但稳定性提升很多。把PB0配置为GPIO_Output初始电平设为High因为CS是高电平表示未选中。在引脚上单击PB0选择GPIO_Output即可。然后在GPIO设置里把Maximum output speed选为High防止边沿太慢。DRDY引脚接PA0。在System Core里找到GPIO把PA0配置为External Interrupt Mode with Falling edge trigger detection同时GPIO Pull-up/Pull-down选Pull-up。因为MAX31865的DRDY输出是开漏需要外部上拉才能输出高电平虽然很多模块板上已经自带上拉电阻了但STM32内部上拉再开一道更保险对电平稳定性有好处。然后在NVIC Settings里把EXTI line0 interrupt的Enable打勾优先级根据项目实际情况给温度采集实时性要求没那么苛刻我给的是中等优先级避免和通信中断抢资源。3.3 串口调试配置调试阶段强烈建议开一路串口把温度值打出来看。在Connectivity里选择USART1Mode选Asynchronous波特率1152008位数据无校验1位停止位。PA9是TXPA10是RX。CubeMX会自动把串口初始化和GPIO配置都生成好。串口主要用于观察温度数据、打印故障状态寄存器内容排查问题的时候作用非常大。所有配置完成后在Project Manager里设置工程名称和路径Toolchain选择MDK-ARMCode Generator里勾选Generated peripheral initialization as a pair of .c/.h files这样每个外设独立成文件方便阅读。然后点击GENERATE CODE生成工程用Keil打开。4. 驱动代码实现与温度计算4.1 MAX31865寄存器地图写代码之前先把寄存器搞清楚。MAX31865的寄存器不多最常用的就这几个配置寄存器地址0x00读写地址规则是bit71表示写bit70表示读RTD数据寄存器地址0x01MSB和0x02LSB故障状态寄存器地址0x07。配置寄存器的位定义需要注意bit7是VBIAS置1开启偏置电流源bit6是转换模式置1为自动转换模式适合我们这种用外部中断通知方式bit5是单次转换触发位bit4是3线制选择1表示3线0表示2线或4线bit3是故障状态清除位写1清故障bit2是50Hz/60Hz工频滤波选择国内用50Hz就写1。所以常见的配置值4线制自动转换就是0xC2偏置开自动转换50Hz滤波3线制自动转换是0xD2。这里强调一点VBIAS和自动转换模式这两个位必须都置1否则DRDY不会工作。很多人初始化后发现中断不触发检查寄存器才发现VBIAS没开电流源都没启动芯片当然不动。4.2 SPI读写封装HAL库的SPI读写推荐用HAL_SPI_TransmitReceive一个函数同时完成发送和接收。读操作流程是先拉低CS发送寄存器地址紧接着读一个字节然后拉高CS。写操作类似发送地址时把bit7置1后面跟要写入的数据。uint8_t MAX31865_ReadReg(uint8_t reg) { uint8_t tx_data reg 0x7F; // bit7 0读操作 uint8_t rx_data 0; HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); HAL_SPI_TransmitReceive(hspi1, tx_data, rx_data, 1, 100); HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); return rx_data; } void MAX31865_WriteReg(uint8_t reg, uint8_t data) { uint8_t tx_data[2] { (reg | 0x80), data }; // bit7 1写操作 HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); HAL_SPI_Transmit(hspi1, tx_data, 2, 100); HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); }完整的读RTD数据函数要把0x01和0x02两个寄存器连续读出来再拼成16位数据。uint16_t MAX31865_ReadRTD(void) { uint16_t raw 0; uint8_t msb MAX31865_ReadReg(0x01); uint8_t lsb MAX31865_ReadReg(0x02); raw ((uint16_t)msb 8) | lsb; // 低字节的bit0是故障标志位参与计算前要把它清零 raw 1; return raw; }为什么要右移一位MAX31865数据手册很明确RTD寄存器低字节最低位不是数据而是错误标志位实际有效的ADC数据是15位。所以把16位数据右移一位得到真正15位ADC码值。如果忘了这个操作算出来的电阻值会直接翻倍温度完全不对。这个坑在论坛里被反复问特别值得拿出来讲。4.3 外部中断回调处理DRDY中断进来不要在中断服务函数里直接做SPI通信。SPI通信本身虽然快但在中断里做耗时操作会阻塞其他中断还可能遇到SPI的HAL库等待超时机制导致不可预期的问题。标准做法是中断里置一个标志位主循环轮询标志位等置位后再去读数据。volatile uint8_t max31865_data_ready 0; void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin DRDY_Pin) { max31865_data_ready 1; } }主循环里这样处理while (1) { if (max31865_data_ready) { max31865_data_ready 0; uint16_t adc_code MAX31865_ReadRTD(); float resistance (float)adc_code * 430.0f / 32768.0f; float temperature ResistanceToTemperature(resistance); printf(ADC:%d Resistance:%.2f ohm Temperature:%.2f C\r\n, adc_code, resistance, temperature); } }这里要注意volatile关键字不能省因为max31865_data_ready在中断和主循环之间共享没有volatile的话编译器可能把它优化到寄存器里导致主循环永远看不到变化。串口重定向printf的具体实现各个工程不同最简单是重写fputc函数或者在代码里直接用HAL_UART_Transmit封装一个自己的日志函数。4.4 温度换算从电阻到温度拿到PT100电阻值还得换算成温度。两种常见方案查表法和公式法。查表法把IEC 60751标准表存到数组里用二分查找找到区间再线性插值精度高且速度快适合单片机资源有限的情况。公式法用的是Callendar-Van Dusen方程0℃以上温度t满足Rt R0 × (1 A×t B×t²) 0℃以下温度t满足Rt R0 × (1 A×t B×t² C×(t-100)×t³)其中R0取100系数A 3.9083×10⁻³B -5.775×10⁻⁷C -4.183×10⁻¹²。这个方程反解比较麻烦工程上常直接套现成公式。这里给一个不用查表、精度够用的近似反解适合0℃到300℃范围float ResistanceToTemperature(float resistance) { // 适合0~300℃的近似计算精度约±0.3℃ float t (resistance - 100.0f) / 0.385f; return t; }这个线性近似在0℃附近误差很小到100℃大约偏差不到0.5℃300℃时偏差能达到几度。要求更高的话就直接用查表法。给一个简化版的思想把0到300℃每10℃一档的PT100电阻值做成表总共31个点查表加线性插值算下来的误差在0.1℃以内比单纯线性近似好很多。温度(℃)PT100电阻(Ω)0100.0010103.9020107.7925109.7330111.6740115.5450119.4060123.24100138.51查表法的实现不复杂遍历找到temp[i] ≤ R temp[i1]的区间按比例插值。实际项目里我用的是这个方案既不需要解高次方程也没有浮点大运算负担大家可以参考这个套路。5. 常见问题与排查技巧实录5.1 读回来的数据全是0x0000或0xFFFF这是SPI通信最典型的现象原因通常是接线错误、片选没拉低、或者SPI模式和时序不对。先检查MISO和MOSI有没有接反这个错误太常见了模块上标注的SDI接STM32的MOSISDO接STM32的MISO一旦接反读回来的数据就是全0或全FF。其次检查CS引脚拉低时序用示波器或者逻辑分析仪看CS低电平期间有没有正常的时钟波形。最后确认SPI模式切换到Mode 3试试个别模块因为上拉电阻和时序余量的关系Mode 1下数据刚好采到边沿上。还有一个隐藏问题如果MAX31865模块是5V供电的而STM32是3.3V电平SPI引脚之间的电平不匹配也会导致通信失败。此时模块上的SCK、SDI、CS得上拉或者加电平转换SDO输出如果是5V还得用电阻分压。买模块时看清楚规格最好选3.3V供电的版本。5.2 温度读数跳动剧烈偏差很大如果数据能读出来但温度在明显跳变首先怀疑参考电阻Rref的精度和温漂。Rref误差2%温度就误差大约2%这不是软件能校准回来的。其次检查接线3线制如果少接一根线或4线制有两根线短路电阻值会明显偏大或偏小。还有一个比较容易忽略的点MAX31865配置寄存器里的3线制位必须和硬件接线一致不一致时读数会跑偏很多。建议在串口里同时打印ADC码值和算出来的电阻值对照PT100分度表看当前环境温度对应电阻应该是多少。比如室温25℃时电阻应该在109.73Ω附近如果读到的是120Ω甚至更高先检查配置位和线制。如果电阻值偏低几欧姆多半是引线电阻补偿没做好2线制在大电流下线路压降会直接计入测量值。5.3 DRDY外部中断不触发或者进入死循环DRDY引脚不拉低有几个原因一是VBIAS没开芯片的恒流源没工作转换流程根本不会启动二是配置寄存器里自动转换位没写对只开了VBIAS但没开自动转换三是DRDY引脚开漏输出外部没有上拉电阻电平变化被拉死。还有一种情况是初始化写入配置寄存器后第一次转换需要一点时间建议初始化之后先延时50ms到100ms再等待DRDY不要一上电就盼着中断进来。如果中断频繁触发甚至卡死在中断里大概率是中断回调里直接做了SPI读取而SPI的等待超时时间设得太长或者中断优先级配置不当导致重复进入。把中断回调精简成只置标志位、主循环处理所有通信操作问题基本能消掉。5.4 常见问题速查表现象可能原因排查方向数据全0 / 全FFMOSI/MISO接反、SPI模式不对检查接线、切Mode 3电阻值整体偏大线制配置位错误、引线电阻补偿不足核对3-Wire位、检查接线温度跳变剧烈Rref精度差、供电不稳换0.1%电阻、加滤波电容DRDY不触发VBIAS未开启、自动转换位未置1检查配置寄存器值读数始终为0RTD开路、接线松动读故障寄存器0x07数据偶尔错一位SPI时钟过高、线缆过长降SPI分频、缩短引线MAX31865的故障状态寄存器0x07在排查时非常好用每一位对应一种故障。bit2是RTD开路bit3是RTD短路bit4是参考电阻开路把它的值打印出来很多硬件问题一眼就能定位。我每次调试第一步就是打印这个寄存器省下大量盲目排查的时间。6. 一点实操心得和调试建议这个项目做下来最核心的经验其实就三条配置寄存器一定要按数据手册逐位核对DRDY中断处理保持轻量参考电阻不要贪便宜。只要这三条没问题整条链路跑起来非常稳定。调试顺序上我建议先把4线制接法跑通因为4线制不需要考虑引线电阻补偿数据最直观。验证方法很简单用万用表实测PT100探头电阻和串口打印的电阻值对比误差在0.1Ω以内说明链路完全正常。然后再根据实际场景切换到3线制确认读数变化是否符合预期。初始化后延时100ms再去读故障寄存器能提前暴露出不少硬件问题。最后还有一个容易被忽略的小地方MAX31865的数据手册里提到RTD数据寄存器读取之后DRDY引脚会自动拉高。如果读操作过程中CS控制时序不对DRDY可能不会正确复位导致中断状态混乱。所以CS引脚的操作一定要包住完整的SPI读写过程不能提前释放。这个细节我在实际项目中踩过一次当时主循环里处理速度太快CS刚拉低就立刻拉高数据能读出来但中断标志位始终不干净最后对比时序才定位到问题。希望这篇写下来能帮大家少走这些弯路。
