简介面向STM32开发者和嵌入式初学者这是一份基于STM32F103驱动MAX6675测温芯片的完整例程解决K型热电偶的SPI通信读取与温度解析问题。工程共96个文件压缩包仅308KB以38个C源文件和39个头文件为主体覆盖SPI、GPIO、定时器、串口等驱动代码另含8个汇编启动文件与Keil工程配置并配有可直接烧录的hex文件。例程不仅实现MAX6675的0.5°C分辨率、±0.5°C精度温度读取还对冷端补偿、温度符号位与数据拼接做了清晰处理同时集成按键、蜂鸣器、LED等外设便于扩展温度告警或显示功能。已有3899人学习适合快速入门MAX6675驱动及SPI外设开发源码结构规范可借鉴其中的分层设计和中断管理思路。 做嵌入式测温方案时肯定绕不开K型热电偶搭配MAX6675这套组合。STM32通过SPI读取MAX6675的驱动是整个测试例程里最基础也最容易出问题的一环。这篇东西适合谁看正好在调STM32的SPI驱动、发现读到的温度不对或者第一次接触热电偶模块不知道从哪下手的工程师。我会把这次跑通测试例程的过程完整记录下来包括底层原理、接线、代码、调试踩坑全是一线实操的东西。1. 关于MAX6675你不能忽略的几个底层事实1.1 它不是简单把模拟信号转成数字的ADC很多人第一次拿到MAX6675模块以为它就是个普通ADC把热电偶的电压读进来然后换算成温度。实际用下来会发现如果不懂它内部干了什么调试时很容易被“读回来数值乱跳”“温度差得离谱”这类问题搞到怀疑人生。K型热电偶输出的电压信号非常微弱大约只有41微伏/摄氏度而且是非线性的。你让MCU的ADC直接去采这个信号先不说分辨率够不够光是冷端温度的变化就能让测量结果漂出好几度。MAX6675存在的意义就是把这些脏活累活都干了它内部集成了高精度仪表放大器、冷端补偿电路、ADC转换器和SPI接口逻辑。MCU要做的只是通过SPI把16位数据读回来然后做一次简单的移位和乘法温度值就出来了。1.2 冷端补偿到底补偿的是什么热电偶有两个结点测量端和参考端。你测的是两个结点间的温差电动势如果参考端也就是接线端子那边温度不稳定读出来的数据就会跟着漂。MAX6675内部有一个温度传感器贴在芯片附近专门检测冷端温度然后自动把补偿量叠加到测量结果里。这带来的一个实际问题是MAX6675的冷端补偿只能在一定的环境温度范围内保证精度。如果板子本身发热严重或者你把模块贴在发热元件旁边冷端补偿就会失准测出来的温度偏大。所以布板和使用时尽量让芯片远离发热源这个细节经常被忽略。1.3 数据格式其实是16位里藏了12位温度MAX6675每次转换完成后会输出一个16位的数据帧其中真正有用的温度信息只有12位温度分辨率为0.25摄氏度。数据位的分布情况我整理在下面位序号含义bit15符号位恒为0bit14 ~ bit3温度数据12位范围0 ~ 4095bit2热电偶开路检测位为1表示断线bit1恒为0bit0三态通常为0读回16位原始数据后先判断bit2是不是1如果是1说明热电偶没接好或者线断了程序里要做对应的错误处理。如果正常把原始值右移3位再乘以0.25就是实际的摄氏温度。测量范围是0到1023.75摄氏度所以它不适合测零下温度。2. 硬件连接与SPI引脚配置2.1 引脚接线对照MAX6675模块一般就5个引脚VCC、GND、SCK、CS、SO。和STM32的接法非常直接MAX6675引脚STM32引脚说明VCC3.3V或5V手册支持3.3~5.5V但建议统一用3.3V供电和MCU电平一致GNDGND共地必须接SCKPA5SPI1_SCKSPI时钟CSPA4任意GPIO片选信号软件控制SOPA6SPI1_MISO数据输出注意MAX6675只有一个数据引脚SO没有SI因为它是单向输出设备MCU不需要往它那边写数据。也就是说这个SPI连接其实是半双工的主设备只需要提供时钟数据从SO引脚上来。2.2 硬件上最容易踩的两个坑第一个坑是电源。很多廉价的MAX6675模块板上没有加去耦电容如果你用杜邦线从开发板那里引3.3V过来电源纹波一大会直接影响内部ADC的转换精度。我习惯在模块的VCC和GND之间就近焊一个0.1微法的陶瓷电容实测数据跳动会明显变小。第二个坑是接线长度和走向。SPI时钟和热电偶信号线尽量不要绑在一起走尤其热电偶那两根线很长的时候高速开关的SCK信号会通过寄生电容耦合到毫伏级的热电偶信号上导致读数上下乱跳。我这边遇到过SPI线绕着热电偶线走了一圈温度读数直接漂了十几度的情况把两根线分开之后立刻恢复正常。2.3 SPI模式配置不能错MAX6675的时序要求是SCK空闲时为低电平数据在SCK上升沿被采样对应SPI Mode 0也就是CPOL0、CPHA0。这个必须严格配好否则读出来的数据要么全零要么乱跳。SPI时钟频率手册上允许最高4.3MHz但实际用1MHz左右最稳转换速度本身只有220毫秒一次SPI跑再快也快不过这个没必要追求高波特率。我用标准库做硬件SPI初始化的时候SPI参数的配置是这样写的SPI_InitTypeDef SPI_InitStructure; SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode SPI_Mode_Master; SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_32; SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB;有的人会用SPI_DataSize_16b来做一次收发16位这也能工作但要注意字节序问题。我个人习惯用8位模式读两个字节再手动拼接逻辑更直观排查问题也容易。3. 最小复用型驱动代码实现3.1 读取原始数据的核心函数驱动逻辑其实很简单拉低CS通过SPI读取两个字节再拉高CS。这里有个细节虽然MAX6675不需要主机发送数据但要产生SPI时钟主机必须往发送寄存器里写东西。写什么不重要通常写0即可。uint16_t MAX6675_ReadRaw(void) { uint16_t temp_data 0; uint8_t buf[2] {0}; GPIO_ResetBits(GPIOA, GPIO_Pin_4); // CS拉低 delay_us(1); SPI_I2S_SendData(SPI1, 0x00); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) RESET); buf[0] SPI_I2S_ReceiveData(SPI1); SPI_I2S_SendData(SPI1, 0x00); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) RESET); buf[1] SPI_I2S_ReceiveData(SPI1); GPIO_SetBits(GPIOA, GPIO_Pin_4); // CS拉高 temp_data (uint16_t)((buf[0] 8) | buf[1]); return temp_data; }CS拉低后为什么要延时1微秒因为MAX6675手册要求CS拉低到第一个SCK上升沿之间至少保持一小段时间给芯片一个准备输出的时间。虽然实测不加这个延时大多数时候也能读到数据但在时序边界条件下偶尔会读丢一个bit导致数值突变还是加上稳妥。3.2 原始数据到实际温度值的换算拿到16位原始值之后处理逻辑按照前面说的数据格式来float MAX6675_GetTemperature(void) { uint16_t raw MAX6675_ReadRaw(); if (raw 0x04) { // bit2为1表示热电偶开路 return -999.0f; } raw 3; return (float)raw * 0.25f; }这个函数里返回-999.0f作为错误码调用方可以根据这个特殊值做断线报警。乘0.25这一步是因为MAX6675的量化步进就是0.25摄氏度4096分之一对应满量程简单说就是把12位二进制数换算成真实温度。需要注意的是如果SPI时钟线上有干扰导致读到错误数据偶尔会出现一个异常跳变的温度值。我做过一个简单的数字滤波器连续读三次取中间值作为最终结果能有效滤掉单次偶发错误。这个方法虽然简单但在这类低速传感器上非常好用。3.3 软件模拟SPI作为兜底方案有时候硬件SPI引脚被其他外设占用了或者想移植到其他MCU上我建议保留一套软件模拟SPI的代码。MAX6675时序不复杂用GPIO直接翻转即可uint8_t MAX6675_SoftReadByte(void) { uint8_t i; uint8_t data 0; for (i 0; i 8; i) { data 1; GPIO_SetBits(GPIOA, GPIO_Pin_5); // SCK拉高 delay_us(1); if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) SET) { data | 0x01; } GPIO_ResetBits(GPIOA, GPIO_Pin_5); // SCK拉低 delay_us(1); } return data; }软件模拟的好处是引脚随便选不受SPI外设通道限制。坏处是CPU会占用一些延时时间但对于220毫秒才转换一次的MAX6675来说完全够用。很多国产MCU平台上的移植我都是直接用这套逻辑跑通的。4. 实测中必踩的坑与排查链路4.1 温度一直不变或数值异常的完整排查思路我调试这类驱动时的习惯是先不看温度值先看原始数据。加一个串口打印原始16进制数据printf(RAW: 0x%04X\r\n, raw);通过原始值就能把问题一分为三原始值一直是0x0000大概率SPI时钟没通或者CS控制有问题用示波器量SCK引脚有没有波形量CS拉低瞬间有没有下降沿。原始值一直是0xFFFF大概率SO引脚没接对或者MISO配置成了推挽输出应该配成浮空输入或上拉输入。原始值有变化但温度不对看看bit2是不是为1如果是1检查热电偶接线如果不是检查是否忘了右移或乘0.25。4.2 热电偶正负极接反的真实反应K型热电偶的红线对应正极蓝线或者白线对应负极。接反了会出现一个很有意思的现象温度读数不随温度升高而升高反而往下降。这是因为电势方向反了而MAX6675只能测正温度所以读到的数据会从0开始往上跳但和实际温度毫无对应关系。排查这个问题的方法很简单用手捏住热电偶的探头温度应该快速上升。如果读数下降或者变乱赶紧检查两根线的接线顺序。4.3 读取频率和转换时间的坑MAX6675大约每220毫秒完成一次转换。如果MCU在50毫秒的循环任务里频繁读取会发现读回来的数据长时间不变甚至隔几次跳一个值。这不是驱动写错了而是芯片根本还没完成新的转换你读到的还是上一次的结果。正确的做法是读取间隔大于220毫秒。我一般把读取任务放在定时器里每250毫秒读一次。如果你用RTOS在任务里加一个vTaskDelay(250 / portTICK_PERIOD_MS)也可以。想进一步确认转换完成没有可以对比两次读取的raw值如果连续读到的值相同说明转换数据没更新这是软件层面判断转换状态的最简单办法。4.4 CS引脚的控制细节有些例程会把CS直接接地让MAX6675始终处于片选状态。这样做的确能读到数据但有个隐患每次读取的开头和结束都没有明确的片选边界芯片内部的状态机有时候会乱。我踩过这个坑表现为上电后第一次读取正常后面数据开始错位就是CS一直拉低导致的。建议每次读取都用GPIO控制CS读完立刻拉高给芯片一个明确的停止信号。MAX6675在CS拉高后会开始新一轮的转换这样下一轮读到的数据才是稳定的新值。5. 精度实测与校准心得5.1 用冰水和沸水做两个参考点驱动跑通之后建议找两个稳定温度源校准一下。0摄氏度参考点用冰水混合物把热电偶探头插进去等读数稳定后记录偏差100摄氏度参考点用沸水同样记录偏差。我在室温约25摄氏度的环境下实测冰水读出来的温度通常在0.2到0.5摄氏度之间浮动沸水在99.2到100.5摄氏度之间整体表现符合手册上说的满量程精度。如果你发现偏差是线性的比如0度时高0.3度100度时高2.8度最简单的校准是做一个两点线性修正temp_real temp_read * k bk和b由两个参考点推算。不需要动硬件程序里改一下就行。5.2 数据抖动怎么压下去排除了接线和电源问题之后如果数据还有轻微抖动大概率是热电偶引线拾取了环境电磁干扰。解决办法有几个方向第一热电偶线换成K型专用的双绞线或者屏蔽线屏蔽层单端接地。第二SPI线缩短MAX6675模块尽量靠近STM32板子。第三在温度数据上做软件平滑比如简单的一阶低通滤波滤波系数取0.2左右即filtered filtered * 0.8 current * 0.2这样既能压低高频抖动又不会明显拖慢温度响应。实测下来我用了一次取中值的滤波之后温度数值的抖动幅度从正负1摄氏度左右降到了正负0.25摄氏度以内也就是稳定在量化步进附近。5.3 长期稳定性的观察曾经让这个测温系统连续跑了48小时观察漂移情况。结果显示在环境温度恒定的室内长时间读数基本稳定在正负0.5摄氏度以内。但如果环境温度有波动比如空调开关导致室温变化两三度MAX6675内部的冷端补偿会反应过来读数会有相应的小幅漂移。这个局限是芯片本身决定的应用在高精度场合时还是要考虑通过外部冷端补偿或者选用更高端的转换芯片来规避。我自己后续接项目的时候凡是要求测温精度在正负1摄氏度以上的场景都会评估一下要不要换带外部冷端补偿的方案。如果只是做加热器温控、电机外壳温度监测这类对精度要求不高的场景MAX6675这种低成本方案完全够用而且SPI驱动方式在各种MCU平台上移植起来都很快。本文还有配套的精品资源点击获取
