☰
MAX31855热电偶测温实战:硬件SPI与软件模拟解析
2026/10/6 8:41:15 网站建设 项目流程

K 型热电偶这个小东西,做测温项目的人基本都绕不开。我最早是想着用仪表放大器放大微伏级信号再接 ADC,结果算完放大倍数还要自己做冷端补偿,麻烦不说,误差还大。后来换用 MAX31855 这颗专用芯片,温度直接以数字量通过 SPI 输出,模拟前端那一大摊子事全被它收进去了。更让我省心的是,这颗芯片既支持单片机自带的硬件 SPI 外设,也可以拿 GPIO 软件模拟时序去读,两条路我都实际调通过,踩了不少坑也积累了一些经验。这篇就围绕 MAX31855 的电路接法、32 位数据帧解析、硬件 SPI 和软件模拟时序两套读取代码展开,适合正在做热电偶测温、3D 打印机热端温度监控、温控仪表这类项目的朋友参考,STM32、Arduino、ESP32/ESP8266 这些平台都通用。

1. MAX31855能干什么:热电偶数字化不是简单接个ADC

1.1 芯片定位与核心价值

热电偶输出的本质是两个不同金属接触产生的热电动势,信号幅度非常小,K 型热电偶典型灵敏度大概 41µV/°C,测个 500°C 也才 20mV 左右。这种信号直接用普通 ADC 采集,一个 LSB 换算下来就是好几度,误差完全没法看。要做得准,前端必须有一级低失调放大器把信号放大上百倍,还得处理冷端温度——因为热电偶测量的是测量端和参考端之间的温差,而参考端就是接线端子所在的位置,这个位置的实际温度必须单独测,两个温度叠加起来才是真实值。这就是常说的"冷端补偿",很多人自己搭电路容易漏掉这一步,或者用手头的 NTC 随便测测就补偿,精度自然上不去。

MAX31855 做的事情,就是把这些麻烦收进一颗 SOIC-8 封装里。芯片内部集成了精密放大器、冷端补偿、ADC 和 SPI 接口,温度结果直接以数字量输出。对做应用层的人来说,等于把整个"模拟前端设计"课题划掉了。芯片后缀对应不同热电偶类型,比如 MAX31855K 配 K 型热电偶,测温范围大概 -200°C 到 +1350°C,分辨率 0.25°C,冷端温度分辨率 0.0625°C。这个精度做常规工业测温、3D 打印机的热床热端监控绰绰有余,而且芯片会定期自我检测热电偶是否开路或短路,异常时用故障位直接告诉 MCU,省去一堆保护电路。

1.2 数据帧格式与温度换算

MAX31855 每次转换输出一个 32 位数据帧,内容分成热电偶温度、故障标志、冷端温度三块,转换周期典型值 100ms,也就是最快 10Hz 左右刷新一次,读得太频繁只会拿到重复数据,这不代表芯片卡住。把这 32 位数据结构拆开看,逐位对应的含义如下:

位段含义
bit31热电偶温度符号位
bit30-18热电偶温度数据(13 位)
bit17保留位,恒为 0
bit16故障总标志,1 表示存在故障
bit15-4冷端温度(12 位无符号)
bit3保留位,恒为 0
bit2SCV,热电偶短路到 VCC
bit1SCG,热电偶短路到 GND
bit0OC,热电偶开路

热电偶温度是 14 位有符号数,bit31 是符号位,bit30-18 是 13 个数据位。解析时把这 14 位取出来,按二进制补码做符号扩展,再乘以 0.25 就是摄氏度。冷端温度是 12 位无符号数,直接乘以 0.0625。举个实际例子:假设原始值里热电偶段是 0b00000011110101,也就是十进制的 245,那么 245×0.25=61.25°C;冷端段读数为 0b010110110100,即 1460,算出来 1460×0.0625=91.25°C。这两个值会随着环境温度变化,冷端温度平时也可以拿来校验芯片周围温度是否异常。

2. 硬件电路设计:参考设计之外还有三个细节

2.1 最小电路连接

MAX31855 的外围电路极其简单,至少比用运放搭的方案少十来个器件。核心信号就 7 个:VCC、GND、SCK、CS、SO、T+、T-。VCC 接 3.3V,GND 接数字地,SCK 接 MCU 的 SPI 时钟引脚,CS 接任意一个 GPIO 作为片选,SO 接 MCU 的 SPI MISO 输入,T+/T- 接热电偶正负极。市面上成品模块板一般把 T+/T- 做成端子或排针,直接拧上热电偶就行,外围器件基本为零。

这里必须强调一个红线:MAX31855 正常工作电压范围是 3.0V~3.6V,绝对不能接 5V,接上去芯片很快报废。我第一次画板子时图省事从系统的 5V 母线取电,结果上电后芯片明显发烫,查了手册才反应过来。如果手头是 5V 单片机系统,比如老式 Arduino UNO,还要考虑 MCU 的 SPI 输出电平问题,SCK/CS 的高电平最高能到 5V,直接怼到 MAX31855 输入引脚上有超压风险,稳妥做法是加逻辑电平转换电路,或者用分压电阻把控制信号降一降。反过来看,MAX31855 的 SO 输出是 3.3V 电平,接 5V 单片机时,3.3V 高电平对大多数 5V 器件的 VIH 阈值(约 0.6×VCC=3V)刚好擦线通过,短距离测试问题不大,但量产或要求稳定就得用双向电平转换器。

2.2 电源滤波与地线布局

虽然数字接口部分对电源噪声不算特别敏感,但芯片内部毕竟集成了高增益模拟放大器,电源纹波会直接影响最终测温精度。我的经验是在 VCC 引脚旁边放一个 0.1µF 陶瓷电容,如果成本允许再并联一个 1µF 电容效果更稳,电容要尽量贴着芯片电源脚放,走线别拉太长。另外一个容易忽略的点是冷端补偿对芯片摆放位置非常敏感。MAX31855 的冷端补偿原理是用芯片自己周围的温度代表热电偶接线端的温度,所以芯片位置的选取直接决定测量准确度。

注意:MAX31855 尽量放在热电偶接线端子旁边,远离 MOS 管、LDO、变压器这类发热源,也不要放在 PCB 正中央被四面热量捂起来。

我做过一个 3D 打印机配件板,把 MAX31855 放在 MOS 管旁边,结果热床一开启,温度读数平白高了好幾度。后来把芯片挪到板子边缘,两个发热源中间留出空隙,误差才恢复正常,这个教训我一直记着。另外,如果机箱内空气不流通,局部温升也会导致冷端偏大,必要时给芯片位置开个通风缝。

2.3 上拉电阻与电平匹配

很多 SPI 器件手册建议在 MISO 上加个上拉电阻,MAX31855 可以不加,因为它的 SO 是推挽输出,不是开漏。加上拉也不会坏,就是纯属多余。SCK 和 CS 是输入引脚,如果 MCU 引脚在系统刚上电时处于高阻态,驱动不够强,可能让芯片在初始化前收到随机时钟脉冲,但这不影响最终读数。为了规范控制时序,CS 和 SCK 都用 GPIO 推挽输出,不需要额外上拉。

还有一个多设备共线的问题容易踩坑。多个 SPI 设备共用同一根 MISO 总线时,MAX31855 的 SO 是三态输出,CS 拉高后就会释放总线,所以挂在同一 MISO 上很安全。但要注意:如果使用硬件 SPI 的硬件 NSS 自动片选功能,反而容易出问题。因为有些 MCU 的硬件 NSS 会在每个字节传输后自动回高,而 MAX31855 要求 CS 在整个 32 位传输期间一直保持低电平,这样就会导致只读到一部分数据。下一节我会细讲为什么推荐软件控制 CS。

3. 硬件SPI方式:从CubeMX配置到数据解析

3.1 为什么要用硬件SPI:省CPU还可靠

硬件 SPI 的好处是时序由外设时钟分频产生,稳定、可预测,CPU 只需要把数据寄存器里的字节取走。MAX31855 是一个只读从设备,通信模式是 SPI Mode 0(CPOL=0,CPHA=0),MSB 在前。Mode 0 的含义是 SCK 空闲为低电平,数据在上升沿被采样。MAX31855 的 SO 数据在 SCK 下降沿之后更新,正好在上升沿被主机稳定采到,两边配合起来非常顺畅。

我平时用 STM32 平台,用 STM32CubeMX 配置 SPI 很快。选一个 SPI 外设,比如 SPI1,模式配成 Transmit/Receive Master,数据宽度 8bit,时钟极性选 Low,时钟相位选 1Edge,也就是标准的 Mode 0。预分频器选多少其实无所谓,我一般把 SCK 配到几百 kHz 到 1MHz 就够了,因为数据帧才 32 位,100ms 才更新一次,完全不存在性能瓶颈。这里重点说一下片选:CubeMX 里如果勾选了 Hardware NSS,STM32 会自动控制 CS 引脚,但 MAX31855 要求 CS 在整个 32 位传输期间保持低电平,硬件 NSS 往往做不到这一点。所以强烈建议把 CS 配置成普通 GPIO 输出,由软件在传输开始前拉低、读完 4 个字节再拉高,这也是日常工程里"软件片选"比"硬件片选"更常用的原因。

3.2 STM32 HAL库读取代码

初始化代码用 CubeMX 生成后基本不用改,关键就是别启用硬件 NSS,把 CS 引脚配成 GPIO_MODE_OUTPUT_PP。读取 32 位数据帧的 HAL 代码如下:

#define CS_PORT GPIOA #define CS_PIN GPIO_PIN_4 uint8_t spi_rx_buf[4]; uint32_t max31855_read_hwspi(void) { uint32_t raw = 0; HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_RESET); // 等待一小段 CS 建立时间,稳妥起见留 10us delay_us(10); // 一次接收 4 个字节,对应 32 个 SCK 周期 // HAL_SPI_Receive 会同时发送 0xFF 来产生时钟,对只读设备无影响 HAL_SPI_Receive(&hspi1, spi_rx_buf, 4, 100); HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_SET); raw = ((uint32_t)spi_rx_buf[0] << 24) | ((uint32_t)spi_rx_buf[1] << 16) | ((uint32_t)spi_rx_buf[2] << 8) | ((uint32_t)spi_rx_buf[3]); return raw; }

MAX31855 不需要 MOSI 数据,MOSI 引脚可以不接,接上也不影响。HAL_SPI_Receive 在接收时会把发送数据寄存器写成 0xFF,利用它产生时钟,这对只读设备来说完全没有副作用。

3.3 解析函数与温度换算代码

拿到 raw 之后进入解析步骤,把 32 位结构拆开。我强烈建议把解析逻辑单独写成一个函数,这样后面切换硬件 SPI 和软件模拟时序时,上层代码一行都不用改。下面是我常用的解析代码:

typedef struct { float tc_temp; // 热电偶温度,单位 ℃ float cj_temp; // 冷端温度,单位 ℃ uint8_t fault; // 故障总标志 uint8_t fault_oc; // 热电偶开路 uint8_t fault_scg; // 热电偶短路到 GND uint8_t fault_scv; // 热电偶短路到 VCC } max31855_data_t; int max31855_parse(uint32_t raw, max31855_data_t *out) { out->fault_scv = (raw >> 2) & 0x01; out->fault_scg = (raw >> 1) & 0x01; out->fault_oc = (raw >> 0) & 0x01; out->fault = (raw >> 16) & 0x01; if (out->fault) { // 故障状态下温度数据无效,直接返回错误 return -1; } // 热电偶温度:bit31 为符号位,bit30-18 为数据位,共 14 位有符号数 int16_t tc_raw = (int16_t)((raw >> 18) & 0x3FFF); if (tc_raw & 0x2000) { tc_raw |= 0xC000; // 14 位补码符号扩展到 16 位 } out->tc_temp = tc_raw * 0.25f; // 冷端温度:bit15-4,12 位无符号数 uint16_t cj_raw = (raw >> 4) & 0x0FFF; out->cj_temp = cj_raw * 0.0625f; return 0; }

跑起来之后你会发现,环境温度稳定时连续读出的数值几乎不变,因为转换周期是 100ms 级别,短时间内多次读只是在读同一个转换结果。我习惯在主循环里每 200~500ms 读一次并打印,串口输出类似TC=123.50 C, CJ=24.31 C,观察数值稳定性很直观。

4. 软件模拟时序:GPIO手搓SPI的核心逻辑

4.1 什么时候必须用软模拟

既然硬件 SPI 又快又省事,为什么还要写软件模拟时序?我实际碰到的场景有这么几种。第一种是引脚不够用,比如 STM32 的 SPI1 引脚已经被其他外设复用了,改板又来不及。第二种是目标平台没有硬件 SPI,或者硬件 SPI 使用起来很别扭,比如某些 ESP8266 模块的硬件 SPI 要和 Flash 操作、引脚复用纠缠,很多老手干脆放弃硬件外设。第三种是在 Proteus 这类仿真软件里做验证,有时候仿真模型对硬件 SPI 支持不完整,软件模拟反而更直观,也方便初学者理解 SPI 的本质。

MAX31855 这类低速传感器非常适合软件模拟。它的数据率本来就不高,转换周期 100ms,数据帧只有 32 位,哪怕每个 bit 折腾几微秒,整帧读下来也就几百微秒,完全不影响系统实时性。你要用软件模拟去刷 TFT 屏幕那种高速外设,CPU 占用率肯定会很尴尬,但读 MAX31855 完全无所谓。

4.2 软件模拟SPI的时序实现

软件模拟 SPI 的关键是按 Mode 0 的节奏走:CS 拉低,之后每个 bit 先拉低 SCK、读取 SO 引脚、再拉高 SCK。MAX31855 的 SO 在 SCK 下降沿后更新数据,所以在 SCK 低电平期间读到的位是稳定可靠的。整个 32 位帧读完后把 CS 拉高。下面这段代码我直接写在 GPIO 宏可移植的工程里,换平台只需改引脚定义:

#define MAX31855_CS_PIN GPIO_PIN_4 #define MAX31855_SCK_PIN GPIO_PIN_5 #define MAX31855_MISO_PIN GPIO_PIN_6 #define MAX31855_PORT GPIOA #define CS_LOW() HAL_GPIO_WritePin(MAX31855_PORT, MAX31855_CS_PIN, GPIO_PIN_RESET) #define CS_HIGH() HAL_GPIO_WritePin(MAX31855_PORT, MAX31855_CS_PIN, GPIO_PIN_SET) #define SCK_LOW() HAL_GPIO_WritePin(MAX31855_PORT, MAX31855_SCK_PIN, GPIO_PIN_RESET) #define SCK_HIGH() HAL_GPIO_WritePin(MAX31855_PORT, MAX31855_SCK_PIN, GPIO_PIN_SET) #define MISO_READ() HAL_GPIO_ReadPin(MAX31855_PORT, MAX31855_MISO_PIN) void soft_delay_us(uint32_t us) { // 简单循环延时,具体循环次数按主频调整 for (volatile uint32_t i = 0; i < us * 12; i++) { __NOP(); } } uint32_t max31855_read_soft(void) { uint32_t raw = 0; CS_LOW(); soft_delay_us(5); for (uint8_t i = 0; i < 32; i++) { SCK_LOW(); soft_delay_us(1); raw = (raw << 1) | (MISO_READ() ? 1 : 0); SCK_HIGH(); soft_delay_us(1); } CS_HIGH(); return raw; }

这里有个细节值得单独说:第 1 个 bit,也就是符号位,在 CS 拉低后很快就会出现在 SO 引脚上,所以读第一个位之前先等了几微秒。之后的每个 bit 在 SCK 下降沿后更新,因此循环里先拉低 SCK、等待信号稳定、读位、再拉高 SCK,这个顺序不能反。如果你把读位放在 SCK 拉高之后,很容易读到下一个 bit 或者边缘不稳定值,数据就乱了。

4.3 两种读法的对比与选择建议

维度硬件 SPI软件模拟 SPI
CPU 占用极低,外设自动移位每个 bit 都要 CPU 参与,但 32 位也就几百微秒
时序稳定性硬件时钟保证,非常稳定依赖延时函数和中断抢占,极端情况下可能抖动
引脚要求只能用 SPI 外设映射的引脚任意 GPIO 都行,灵活
代码可移植性依赖平台 SPI 库宏定义改一改就能换 MCU
调试难度配置项多,出错时不好定位简单直接,一个逻辑分析仪就能抓清楚

我的实际建议是主选硬件 SPI,软件模拟留着做备用和移植兜底。反正解析函数是同一个,两个读函数返回的 raw 格式完全一样,切换时只需要把调用的函数换一下。这套设计让我从 STM32 换到 ESP8266 的时候省了大量时间,这也是我坚持把底层读取和上层解析分开的原因。

5. 常见问题与排查技巧实录

5.1 CS 拉低后读到全 0 或全 1

这类问题见得最多。出现全 0 通常是 CS、SCK、MISO 接线错误,或者读取代码里 CS 根本没有拉低,MCU 没和芯片进入通信状态。出现全 1 往往是因为 MISO 引脚被配置成了输出而不是输入,或者 SO 引脚悬空被外部环境拉高。遇到这种情况先别急着怀疑芯片,拿起万用表量一下各引脚电压:VCC 是不是 3.3V,CS 在传输期间是不是低电平,SO 在 CS 拉低后有没有变化。手头有逻辑分析仪或示波器就更直接,抓一下 SCK 和 SO 的波形,数据帧对不对一目了然。

我排查过一次全 1 的问题,最后发现是 STM32CubeMX 里把 MISO 引脚误配成了 GPIO 输出,电平被外部上拉电阻拉高,读回来全是 1。改回复用 SPI 输入功能就好了,这种低级错误最容易白耗一两个小时。

5.2 故障位一直置 1:其实芯片在保护你

MAX31855 自带故障检测是件好事。如果发现 bit16 总为 1,去查 bit0 到 bit2:bit0 是开路故障,最常见原因是热电偶没接或者接线断了;bit1 是短路到 GND;bit2 是短路到 VCC。故障状态下温度寄存器数据是无效的,有些代码不判断故障位直接换算,会得到一个离谱的读数。我的做法是读取后先解析故障位,再决定温度能不能用,串口打印时把故障标志也带出来,比如FAULT: SCV SCG,定位问题非常方便。

还有一个容易踩的坑:热电偶本身虽然不分正负极,但 T+ 和 T- 接反会导致温度显示负值或明显错误。K 型热电偶的引线一般有颜色标识,红色线通常是负极,接反了读数会在 -200°C 附近跳动。测量之前先核对极性,能省很多排查时间。

5.3 温度读数跳变或整体偏差

读数跳变基本是干扰问题。热电偶信号线从测量端到芯片之间如果走线过长,很容易感应交流噪声,尤其是靠近电机、加热丝这类强干扰源时更明显。解决方案是把热电偶线双绞,或者用屏蔽线且让屏蔽层单端接地,芯片附近不要走大电流回路。如果固件里已经排除了接线干扰,还可以在软件上加一个简单滑动平均滤波,比如连续采 8 次,去掉最大最小再取平均,效果立竿见影。

整体偏差则要从冷端补偿入手。MAX31855 的冷端温度代表的是芯片自己周围的温度,如果布局让芯片靠近发热元件,或者机箱内空气不流通造成局部温升,冷端测量偏大,最终热电偶温度也会跟着偏。解决办法是调整布局,让 MAX31855 贴近热电偶端子,和发热源保持距离。晶圆厂的数据手册里也提过一种校准思路:用冰水混合物和沸水两个标准温度点做两点校准,把系统偏差记下来在软件里修正,这是工程上最常用的办法。

5.4 移植到 ESP8266 等平台时要注意什么

ESP8266 和 STM32 不一样,它的硬件 SPI 在某些 SDK 版本里和多路复用、Flash 操作存在冲突,很多老手干脆直接用软件模拟。ESP8266 的 GPIO 输出是 3.3V 推挽,读入引脚用 INPUT 模式就行,软件模拟的循环代码基本照搬,只是延时改用delayMicroseconds()。注意一点:ESP8266 模组丝印上的引脚编号和芯片内部的 GPIO 编号不是一回事,NodeMCU 的 D1、D2 与 GPIO5、GPIO4 的对应关系要查清楚,接错线是最常见的低级问题。

另外一个经验是,如果用 ESP8266 的硬件 SPI,要注意它内部 Flash 通信占用的引脚,以及 SDK 初始化时对 SPI 引脚的重映射。我之前把 MAX31855 接在 ESP8266 上做远程温度上报,整个工程完全没碰硬件 SPI 外设,软件模拟跑得很稳,数据上报 24 小时没有出现过一次丢帧。

这套 MAX31855 的驱动从我第一次画板到现在,前前后后改过四五版,最深的体会是先把 SPI 协议本身吃透,再谈用哪个外设。硬件 SPI 和软件模拟说到底只是达成同一份时序的两种手段,解析层完全共用,切换成本几乎为零。如果你也在做测温项目,建议一开始就把两套读函数都写上,后面换芯片、换平台、调传感器都会省很多事。最后分享一个小技巧:初次上电不要急着看绝对温度,先用手捏住热电偶测量端,观察读数有没有快速上升,这一步能一口气验证电路、通信和解析三个环节是否都正常。

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

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

立即咨询