简介这份资源是面向嵌入式初学者与STM32开发者的MAX6675测温驱动测试例程基于STM32F103与K型热电偶专用芯片MAX6675搭建重点解决SPI接口配置、冷端补偿读取与温度数据解析等实际问题适合用于温度采集类课程设计、实验验证或工业测温模块的原型开发。压缩包共96个文件约308KB以39个h头文件与38个c源文件为主体另含8个s汇编启动文件、Keil工程文件uvprojx、uvoptx、JLinkSettings.ini调试配置、hex烧录文件及readme说明覆盖CMSIS、标准外设库、Board与User等目录工程结构完整可直接编译。目前已有3917人学习下载热度较高。读者可从中获得SPI时序配置、GPIO初始化、MAX6675命令序列与0.5°C分辨率温度换算的完整实现并参考主循环调用与中断处理思路快速完成测温功能移植与二次开发。1. 为什么一个 8 块钱的模块能让整条温控链路翻车STM32 驱动 MAX6675 测温的测试例程看起来是嵌入式入门里最没门槛的一类活一个 K 型热电偶、一片 SOP-8 的转换芯片、三根 SPI 线接上就能读温度。但真把它放进回流焊炉、3D 打印机热端、小型恒温箱这类场景你会发现读数跳、冷端补偿偏、断线检测失灵甚至偶发地把 25℃ 报成 1024℃。问题往往不在 MAX6675 本身而在于驱动层把「SPI 读 16 位」当成了全部忽略了时序、冷端、噪声和故障位。这篇笔记面向正在用 STM32 做温度采集的工程师如果你手上是 F103、F407、G0、L4 这类常见型号想用最少的代码把 MAX6675 跑通并且希望读数稳定到 ±2℃ 以内、能识别热电偶开路那下面的内容可以直接抄。我会先讲清 MAX6675 到底吐出了什么再给出一份可编译的测试例程最后把我在实际项目里踩过的坑逐条摊开。整套方案不依赖任何第三方库纯 HAL 加寄存器操作换芯片型号时改引脚和 SPI 句柄即可。2. MAX6675 到底吐出了什么从 16 位时序到冷端补偿2.1 它不是标准 SPI 从机别拿硬件 NSS 硬套MAX6675 的接口常被写成「SPI 兼容」但严格说它只支持 SPI 模式 0CPOL0CPHA0而且没有片选输入只有 SCK、SO、CS 三根线。CS 拉低后芯片在 SCK 的下降沿把数据放到 SO 上主控在上升沿采样。一个完整读取周期是 16 个时钟前 14 位是数据后 2 位是状态。很多人直接用 STM32 的硬件 SPI 配成主机、软件控制 CS这没问题。但如果你把 NSS 设成硬件输出或者用了 SPI 的 TI 模式时序就会错位读出来的值会整体偏移。我一般会这样配SPI 模式 0、8 位数据帧、MSB 先出、预分频到 1MHz 以下。MAX6675 的 SCK 最高 4MHz但实际在长线或杜邦线连接时超过 2MHz 就容易出现位翻转所以测试例程里我默认用 1MHz 左右。2.2 16 位里的有效位、故障位和那个「假 1024」一次读回来的 16 位数据结构是这样的位段含义说明D15固定 0保留D14~D312 位温度数据分辨率 0.25℃二进制补码形式D2热电偶开路标志1 表示热电偶断开D1固定 0器件 IDD0固定 0保留温度换算公式是温度 (raw 3) * 0.25。注意这里 raw 是 16 位原始值右移 3 位后得到 12 位有符号数。如果热电偶开路D2 会置 1此时温度字段可能是任意值常见的是全 1换算出来就是 1023.75℃也就是很多人说的「假 1024」。冷端补偿是 MAX6675 内部完成的它测的是芯片自身引脚处的温度不是环境温度。所以芯片焊在板子上、靠近发热源时冷端补偿会偏。我见过一个案例MAX6675 放在 DC-DC 旁边环境 25℃芯片自身 40℃读出来的热电偶温度比实际低了近 15℃。解决办法要么把芯片挪远要么在软件里加一个偏移校准。2.3 最小测试例程CubeMX 配置加三行核心代码下面这份例程基于 STM32F103C8T6SPI1 主机PA5 作 SCKPA6 作 MISOPA4 作软件 CS。CubeMX 里 SPI1 配成 Full-Duplex Master、Prescaler 872MHz/89MHz 再分频到约 1.1MHz、CPOL Low、CPHA 1Edge、MSB First、CRC 关闭。生成代码后在 main.c 里加一个读取函数。// max6675.h #ifndef MAX6675_H #define MAX6675_H #include main.h #define MAX6675_CS_PORT GPIOA #define MAX6675_CS_PIN GPIO_PIN_4 float MAX6675_ReadTemperature(void); uint8_t MAX6675_IsThermocoupleOpen(void); #endif// max6675.c #include max6675.h extern SPI_HandleTypeDef hspi1; // 拉低 CS读 2 字节再拉高 CS static uint16_t MAX6675_ReadRaw(void) { uint8_t tx[2] {0xFF, 0xFF}; uint8_t rx[2] {0}; HAL_GPIO_WritePin(MAX6675_CS_PORT, MAX6675_CS_PIN, GPIO_PIN_RESET); // MAX6675 要求 CS 下降沿后至少等待 100ns这里用空指令兜底 __NOP(); __NOP(); HAL_SPI_TransmitReceive(hspi1, tx, rx, 2, 100); HAL_GPIO_WritePin(MAX6675_CS_PORT, MAX6675_CS_PIN, GPIO_PIN_SET); return ((uint16_t)rx[0] 8) | rx[1]; } float MAX6675_ReadTemperature(void) { uint16_t raw MAX6675_ReadRaw(); // D2 为 1 表示热电偶开路直接返回错误码 if (raw 0x0004) { return -999.0f; } // 取 D14~D3右移 3 位再乘 0.25 int16_t temp_raw (int16_t)(raw 3); return temp_raw * 0.25f; } uint8_t MAX6675_IsThermocoupleOpen(void) { uint16_t raw MAX6675_ReadRaw(); return (raw 0x0004) ? 1 : 0; }逻辑说明MAX6675_ReadRaw负责拉低 CS、发两个 0xFF 产生时钟、收回两个字节。这里用HAL_SPI_TransmitReceive而不是HAL_SPI_Receive是因为主机必须提供 SCK只收不发在 HAL 里会阻塞。CS 拉低后的两个__NOP()是给 MAX6675 的建立时间虽然手册写 100ns但在 72MHz 下两个空指令约 28ns实际测试够用如果主频更高建议改成for循环或HAL_Delay微秒级延时。参数说明SPI 预分频决定了 SCK 频率。F103 的 SPI1 挂在 APB2 上72MHzPrescaler 8 得到 9MHz但 MAX6675 在 9MHz 下可能误码所以我在 CubeMX 里实际选的是 Prescaler 16得到 4.5MHz再配合杜邦线也能稳定。如果你用 PCB 走线可以放到 4MHz 以内。HAL_SPI_TransmitReceive的超时设 100ms正常读取在微秒级完成超时只会在硬件故障时触发。2.4 冷端补偿的软件校准一个偏移量解决 80% 的偏差MAX6675 内部冷端补偿的精度典型值是 ±2℃但这是芯片结温与环境一致时的指标。实际板子上芯片自身发热、周围热源辐射都会让冷端偏离。我一般会在系统稳定后把热电偶放在已知温度点比如冰水混合物 0℃ 或室温计对比读一组值算平均偏差然后在代码里加一个float cold_junction_offset。// 校准示例在 25℃ 室温下热电偶也处于 25℃读回 23.5℃ // 则 offset 25.0 - 23.5 1.5 float g_cold_offset 1.5f; float MAX6675_ReadTemperatureCalibrated(void) { float t MAX6675_ReadTemperature(); if (t -100.0f) return t; // 开路错误码不补偿 return t g_cold_offset; }这个偏移量不是万能的它只在冷端温度相对稳定时有效。如果环境温度变化超过 10℃建议用外部 NTC 或 DS18B20 单独测冷端再在软件里做查表补偿。但多数恒温场景下一个固定偏移就能把误差压到 ±1.5℃ 以内。3. 把例程跑进真实系统采样周期、滤波和断线检测3.1 采样周期不是越快越好MAX6675 有 220ms 转换时间MAX6675 内部每 220ms 完成一次转换如果你每 10ms 读一次会读到重复值而且频繁拉低 CS 可能打断内部转换。我一般把读取任务放在 250ms 或 500ms 的定时器里用HAL_TIM_PeriodElapsedCallback触发。这样既不会丢数据也不会因为 SPI 占用过久影响其他外设。// 在定时器回调里置标志主循环处理 volatile uint8_t g_max6675_tick 0; void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2) { g_max6675_tick 1; } } // 主循环 float g_current_temp 0.0f; uint8_t g_tc_open 0; while (1) { if (g_max6675_tick) { g_max6675_tick 0; uint16_t raw MAX6675_ReadRaw(); g_tc_open (raw 0x0004) ? 1 : 0; if (!g_tc_open) { g_current_temp ((int16_t)(raw 3)) * 0.25f g_cold_offset; } } // 其他任务 }这里把读取和换算放在主循环定时器只置标志避免在中断里做 SPI 传输。SPI 传输本身可以放在中断里但 HAL 的阻塞式传输在中断上下文里容易和主循环的 SPI 操作冲突所以标志位法更稳。3.2 滑动平均加中值滤波把 ±3℃ 的跳动压到 ±0.5℃原始读数在热电偶线较长、附近有电机或继电器时会出现单点跳变。我常用的是「中值 滑动平均」两级滤波先取 5 个连续样本去掉最大最小剩下 3 个求平均再把结果放进一个长度 8 的滑动窗口求平均。这样既能滤掉脉冲噪声又不会让响应变得太迟钝。#define MEDIAN_N 5 #define AVG_N 8 static float median_buf[MEDIAN_N]; static uint8_t median_idx 0; static float avg_buf[AVG_N]; static uint8_t avg_idx 0; static uint8_t avg_count 0; float MAX6675_Filter(float new_val) { // 中值滤波 median_buf[median_idx] new_val; median_idx (median_idx 1) % MEDIAN_N; float tmp[MEDIAN_N]; for (int i 0; i MEDIAN_N; i) tmp[i] median_buf[i]; // 简单冒泡排序 for (int i 0; i MEDIAN_N - 1; i) { for (int j 0; j MEDIAN_N - 1 - i; j) { if (tmp[j] tmp[j1]) { float t tmp[j]; tmp[j] tmp[j1]; tmp[j1] t; } } } float med tmp[MEDIAN_N / 2]; // 滑动平均 avg_buf[avg_idx] med; avg_idx (avg_idx 1) % AVG_N; if (avg_count AVG_N) avg_count; float sum 0; for (int i 0; i avg_count; i) sum avg_buf[i]; return sum / avg_count; }参数说明MEDIAN_N取 5 是折中取 3 滤不干净取 7 会让响应延迟超过 1.5 秒。AVG_N取 8在 250ms 采样周期下窗口覆盖 2 秒对恒温场景足够。如果你做的是快速升温检测可以把AVG_N降到 4但跳动会变大。3.3 断线检测不能只看 D2还要加超量程判断D2 位能检测热电偶开路但有些故障比如热电偶短路到地、线间短路不会置 D2而是让读数跑到量程边缘。MAX6675 的量程是 0℃ 到 1023.75℃但实际 K 型热电偶在 0℃ 以下也能测只是芯片输出会是 0。我一般会加两条判断读数持续等于 0 或持续大于 1000℃都视为故障。uint8_t MAX6675_CheckFault(float temp, uint8_t open_flag) { if (open_flag) return 1; // 开路 if (temp 0.0f) return 2; // 低于量程或短路 if (temp 1000.0f) return 3; // 超量程 return 0; // 正常 }在恒温箱项目里这条逻辑帮我抓到过一次热电偶接线端子松动D2 没置位但读数在 0℃ 和 800℃ 之间跳最后用超量程判断锁定了故障。4. 避坑与排查MAX6675 测试例程里最容易翻车的 5 个点4.1 读数一直是 0 或 1023.75现象上电后串口打印温度恒为 0.00 或 1023.75热电偶插不插都一样。原因1023.75 对应 raw 全 1通常是 MISO 没接、CS 没拉低、或者 SPI 模式配错导致采样不到有效位。读 0 则可能是 SCK 没输出或者 CS 一直高电平。解决先用示波器看 CS 和 SCK确认 CS 在读取期间拉低、SCK 有 16 个脉冲。如果没有示波器把 SPI 速率降到 100kHz 以下用万用表测 MISO 在 CS 拉低后是否有电平变化。另外检查 CubeMX 里 SPI 的 CPOL/CPHA 是不是 0/0很多人默认配成 Mode 3 就会读全 1。4.2 温度比实际低 10℃ 以上现象热电偶放在室温 25℃ 环境读出来只有 15℃ 左右。原因MAX6675 的冷端补偿测的是芯片引脚温度如果芯片靠近 LDO、MCU 或功率器件自身温度高于环境补偿就会过度导致读数偏低。解决把 MAX6675 远离热源至少 1cm或者用铜箔/开槽隔离。软件上按 2.4 节做偏移校准。如果环境温度变化大建议换 MAX31855它支持外部冷端补偿但那是另一个话题。4.3 读数每隔几秒跳一次幅度超过 5℃现象温度曲线整体平稳但每隔 2~3 秒出现一个尖峰。原因SPI 总线上有其他从机CS 切换时产生毛刺或者热电偶线太长引入了工频干扰。解决给 MAX6675 的 CS 加 100nF 到地SCK 和 MISO 串 33Ω 电阻。软件上启用 3.2 节的中值滤波。如果尖峰仍然存在检查热电偶的屏蔽层是否单点接地屏蔽层另一端悬空。4.4 用 HAL_SPI_Receive 读不到数据现象调用HAL_SPI_Receive后一直超时或者读回全 0。原因STM32 的 SPI 主机在只接收模式下SCK 不会自动产生需要发送 dummy 数据来提供时钟。HAL_SPI_Receive在主机模式下会尝试产生时钟但某些 HAL 版本对 2 字节接收的处理有问题。解决统一用HAL_SPI_TransmitReceive发0xFF收数据。这是最稳的做法也是我在所有 MAX6675 例程里固定使用的接口。4.5 多片 MAX6675 共用 SPI 时互相干扰现象两片 MAX6675 接在同一条 SPI 总线上单独读都正常同时读时其中一片数据错乱。原因CS 切换时前一片的 SO 引脚没有及时释放或者两片 CS 同时被拉低。解决确保任意时刻只有一片 CS 为低CS 拉高后加 1 微秒延时再拉低另一片。如果 SPI 速率高于 2MHz把速率降下来。更彻底的做法是每片 MAX6675 独立 CSSO 线上加二极管隔离但一般场景用软件时序控制就够了。5. 进阶技巧用 DMA 和定时器触发把 CPU 占用降到 1% 以下如果你把 MAX6675 放进一个多任务系统比如同时跑 PID 控温、串口屏刷新和按键扫描轮询式读取会占用不少 CPU 时间。虽然 250ms 一次不算频繁但每次HAL_SPI_TransmitReceive是阻塞的在 1MHz 下 2 字节大约 16 微秒加上函数调用开销实际可能到 50 微秒。这看起来不多但如果 SPI 总线上还挂了其他高速设备阻塞式传输会拖累整体响应。我后来改成定时器触发 DMA 接收。思路是TIM2 每 250ms 触发一次在中断里拉低 CS启动 SPI 的 DMA 接收DMA 完成中断里拉高 CS 并解析数据。这样 CPU 只在 DMA 完成中断里处理一次耗时不到 5 微秒。// 使用 DMA 的读取流程 static uint8_t g_spi_rx[2]; static uint8_t g_spi_tx[2] {0xFF, 0xFF}; void MAX6675_StartDMARead(void) { HAL_GPIO_WritePin(MAX6675_CS_PORT, MAX6675_CS_PIN, GPIO_PIN_RESET); HAL_SPI_TransmitReceive_DMA(hspi1, g_spi_tx, g_spi_rx, 2); } // DMA 完成回调 void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi-Instance SPI1) { HAL_GPIO_WritePin(MAX6675_CS_PORT, MAX6675_CS_PIN, GPIO_PIN_SET); uint16_t raw ((uint16_t)g_spi_rx[0] 8) | g_spi_rx[1]; // 解析 raw置标志给主循环 g_max6675_raw raw; g_max6675_ready 1; } }注意 DMA 模式下CS 的拉低和拉高必须严格对应传输开始和结束。如果 CS 拉高太早最后一个字节会丢拉高太晚会干扰下一次转换。我一般把拉高放在 DMA 完成回调的第一行实测在 1MHz 下没问题。如果 SPI 速率高于 4MHz建议用 SPI 的 NSS 脉冲模式但 MAX6675 不支持标准 NSS所以还是软件控制最稳。另一个技巧是给 MAX6675 的电源加 RC 滤波。我在一个电机控制板上遇到过 SPI 读数随机跳变最后发现是电机换向时电源纹波耦合到了 MAX6675 的 VCC。在 VCC 和 GND 之间并一个 10μF 钽电容加 100nF 陶瓷电容跳变就消失了。这个经验让我后来在所有模拟/数字混合板上都默认给传感器电源加两级滤波。最后说一个我自己的习惯每次调试 MAX6675我都会先用一个已知温度源比如校准过的恒温水浴跑 30 分钟记录原始 raw 值和换算值画一条误差曲线。如果误差在 ±2℃ 内且没有跳变才进入下一步。这个习惯帮我省掉了很多后期返工。希望帮到你。本文还有配套的精品资源点击获取