简介:面向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 | 符号位,恒为0 |
| bit14 ~ bit3 | 温度数据(12位),范围0 ~ 4095 |
| bit2 | 热电偶开路检测位,为1表示断线 |
| bit1 | 恒为0 |
| bit0 | 三态,通常为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引脚 | 说明 |
|---|---|---|
| VCC | 3.3V或5V | 手册支持3.3~5.5V,但建议统一用3.3V供电,和MCU电平一致 |
| GND | GND | 共地,必须接 |
| SCK | PA5(SPI1_SCK) | SPI时钟 |
| CS | PA4(任意GPIO) | 片选信号,软件控制 |
| SO | PA6(SPI1_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,也就是CPOL=0、CPHA=0。这个必须严格配好,否则读出来的数据要么全零,要么乱跳。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 + b,k和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平台上移植起来都很快。
本文还有配套的精品资源,点击获取