
K 型热电偶这个小东西做测温项目的人基本都绕不开。我最早是想着用仪表放大器放大微伏级信号再接 ADC结果算完放大倍数还要自己做冷端补偿麻烦不说误差还大。后来换用 MAX31855 这颗专用芯片温度直接以数字量通过 SPI 输出模拟前端那一大摊子事全被它收进去了。更让我省心的是这颗芯片既支持单片机自带的硬件 SPI 外设也可以拿 GPIO 软件模拟时序去读两条路我都实际调通过踩了不少坑也积累了一些经验。这篇就围绕 MAX31855 的电路接法、32 位数据帧解析、硬件 SPI 和软件模拟时序两套读取代码展开适合正在做热电偶测温、3D 打印机热端温度监控、温控仪表这类项目的朋友参考STM32、Arduino、ESP32/ESP8266 这些平台都通用。1. MAX31855能干什么热电偶数字化不是简单接个ADC1.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保留位恒为 0bit16故障总标志1 表示存在故障bit15-4冷端温度12 位无符号bit3保留位恒为 0bit2SCV热电偶短路到 VCCbit1SCG热电偶短路到 GNDbit0OC热电偶开路热电偶温度是 14 位有符号数bit31 是符号位bit30-18 是 13 个数据位。解析时把这 14 位取出来按二进制补码做符号扩展再乘以 0.25 就是摄氏度。冷端温度是 12 位无符号数直接乘以 0.0625。举个实际例子假设原始值里热电偶段是 0b00000011110101也就是十进制的 245那么 245×0.2561.25°C冷端段读数为 0b010110110100即 1460算出来 1460×0.062591.25°C。这两个值会随着环境温度变化冷端温度平时也可以拿来校验芯片周围温度是否异常。2. 硬件电路设计参考设计之外还有三个细节2.1 最小电路连接MAX31855 的外围电路极其简单至少比用运放搭的方案少十来个器件。核心信号就 7 个VCC、GND、SCK、CS、SO、T、T-。VCC 接 3.3VGND 接数字地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×VCC3V刚好擦线通过短距离测试问题不大但量产或要求稳定就得用双向电平转换器。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 0CPOL0CPHA0MSB 在前。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 NSSSTM32 会自动控制 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-412 位无符号数 uint16_t cj_raw (raw 4) 0x0FFF; out-cj_temp cj_raw * 0.0625f; return 0; }跑起来之后你会发现环境温度稳定时连续读出的数值几乎不变因为转换周期是 100ms 级别短时间内多次读只是在读同一个转换结果。我习惯在主循环里每 200~500ms 读一次并打印串口输出类似TC123.50 C, CJ24.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软件模拟 SPICPU 占用极低外设自动移位每个 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.3VCS 在传输期间是不是低电平SO 在 CS 拉低后有没有变化。手头有逻辑分析仪或示波器就更直接抓一下 SCK 和 SO 的波形数据帧对不对一目了然。我排查过一次全 1 的问题最后发现是 STM32CubeMX 里把 MISO 引脚误配成了 GPIO 输出电平被外部上拉电阻拉高读回来全是 1。改回复用 SPI 输入功能就好了这种低级错误最容易白耗一两个小时。5.2 故障位一直置 1其实芯片在保护你MAX31855 自带故障检测是件好事。如果发现 bit16 总为 1去查 bit0 到 bit2bit0 是开路故障最常见原因是热电偶没接或者接线断了bit1 是短路到 GNDbit2 是短路到 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 和软件模拟说到底只是达成同一份时序的两种手段解析层完全共用切换成本几乎为零。如果你也在做测温项目建议一开始就把两套读函数都写上后面换芯片、换平台、调传感器都会省很多事。最后分享一个小技巧初次上电不要急着看绝对温度先用手捏住热电偶测量端观察读数有没有快速上升这一步能一口气验证电路、通信和解析三个环节是否都正常。