简介:面向嵌入式软硬件开发者和电子工程学习者,这份C语言驱动代码围绕MAX6675与MAX31855两款热电偶驱动芯片,解决K型及多种热电偶的温度采集与驱动实现问题。压缩包内仅1个C文件,整体大小592B,集中展示了SPI与单线接口的初始化、通信读写函数、温度数据解析以及错误检测等核心环节。目前已有3228人学习下载。通过研读这份精简源码,可以快速掌握MAX6675的SPI读取流程与MAX31855的单线通信配置要点,理解芯片内部冷端补偿和A/D转换后输出的14位温度数据格式,并可直接移植到自己的项目中,减少查阅数据手册和反复调试的时间。资源虽小,但覆盖了从接口配置到温度获取的完整通路,对需要快速上手热电偶测温方案的开发者是一份小巧实用的参考范例。 去年接了个小项目,要给一台小机器加装温度监测,选型时图省事直接上了MAX6675加K型热电偶的组合——毕竟网上例程铺天盖地,模块几块钱一个,拿来就能用。结果板子打样回来一测,温度读数在正常值和明显偏差值之间来回跳,排查了整整一个下午,最后发现根子出在SPI读取时序和杜邦线的干扰上。后来把方案换成MAX31855,顺手把两个芯片的驱动整合成了一份通用代码。这篇就把MAX6675和MAX31855的驱动代码、数据位解析思路,以及数据手册里不会写的实际坑都讲透。适用对象是正在做K型热电偶测温的嵌入式开发者,不管你是刚接触SPI还是已经调过几块板子,都能从里面拿点东西走。
1. 选型决定驱动写法:MAX6675与MAX31855的核心差异
很多人看到这两颗芯片长得差不多、接法也差不多,就想当然地认为驱动可以完全通用。实际上它们的数据格式和功能边界有明显区别,驱动代码的解析逻辑也必须跟着变。
1.1 数据格式和测量范围
先看最关键的温度数据位宽。MAX6675输出12位无符号温度数据,分辨率为0.25°C/LSB,测量范围只有0°C到+1023.75°C。注意这个“无符号”很关键:它天生不支持负温度,环境温度低于0°C时,内部冷端补偿会失效,读数直接不准。MAX31855则是14位带符号温度数据,同样0.25°C/LSB,理论上讲可以覆盖-2048°C到+2047.75°C,实际范围受限于热电偶类型,以K型为例大约是-200°C到+1350°C。如果你要测零下温度,或者需要更宽的量程,MAX31855是必须的选择。
另一个直接影响驱动代码的点是输出数据的宽度。MAX6675每次通过SPI读回16位,高12位是有效温度,低4位里有状态位;MAX31855每次读回32位,高位是温度,中间是冷端温度,低位是故障标志。这就意味着驱动里读数据的字节数、拼接方式、移位处理全都不一样。所以写驱动之前先确认自己用的是哪颗芯片,别拿着MAX6675的例程去读MAX31855,读回来的数据用肉眼根本看不出规律。
1.2 热电偶类型、故障检测与硬件兼容性
MAX6675只支持K型热电偶,MAX31855则有多个子型号,分别对应K、J、N、T、S、R、E、B型热电偶,买料的时候要根据后缀选对型号,比如MAX31855K就是K型专用版。这种设计上的差异也解释了为什么MAX31855的驱动要多做一层热电偶类型判断,而MAX6675完全不需要。
故障检测方面两者差距更明显。MAX6675只有一路热电偶开路检测位,短路、反接都检测不出来;MAX31855把低3位做成了故障标志,分别对应开路、短路到VCC、短路到GND。实际生产中这个差别很重要——热电偶的偶丝很细,弯折几次就可能内部断开,MAX6675虽然能报开路,但遇到短路故障时会给出一个看起来正常的错误温度,容易把设备带跑偏。
硬件引脚上,SO-8封装的两颗芯片在SO、CS、SCK这三个信号脚上是兼容的,这也是很多产品能直接替换的原因。但驱动不能直接换,因为数据位定义和读取长度都变了。
| 对比项 | MAX6675 | MAX31855 |
|---|---|---|
| 适用热电偶 | 仅K型 | K、J、N、T、S、R、E、B型 |
| 温度分辨率 | 12位,0.25°C/LSB | 14位,0.25°C/LSB |
| 测量范围 | 0~+1023.75°C | 视型号,K型约-200~+1350°C |
| 冷端补偿 | 内部补偿,值不可读 | 内部补偿,冷端温度可读出 |
| 故障检测 | 仅开路 | 开路、短路到VCC、短路到GND |
| SPI读取长度 | 16位 | 32位 |
2. SPI读取链路拆解:时序、字节序和位定义
驱动代码的核心是SPI读取和位解析。这部分搞清楚了,代码本身其实就是简单的移位运算。
2.1 时钟模式与片选时序
MAX6675和MAX31855都支持SPI Mode 0,即CPOL=0、CPHA=0。实测下来这个模式最稳,我也见过有人用Mode 1也能读,但既然数据手册推荐Mode 0,就没有必要在时序上冒险。
片选的控制比SPI模式更容易被忽略。读数据前把CS拉低,然后开始发时钟,读完立刻拉高。CS拉低到第一个SCK上升沿之间要留一点余量,MAX6675规格是至少100ns,用STM32的GPIO翻转速度一般没问题,但如果用的是软件SPI,GPIO翻转加上指令周期通常也够。需要注意的是两次读取之间必须间隔大于内部转换周期,MAX6675和MAX31855的典型转换时间都是100ms左右。如果你读的频率太快,读到的实际上是上一次转换的结果,表现就是温度值好几个周期不变,然后忽然跳一下。
字节序方面,两颗芯片都是MSB先出。配置SPI时务必确认是MSB First,如果配置成了LSB First,读回来的字节顺序会反过来,温度数据会完全乱套。这个坑我踩过一次,排查了很久才发现是CubeMX默认设置被改掉了。
2.2 MAX6675的16位数据解析
MAX6675读回来的是16位数据,按bit排列如下:
- bit15:恒为0,因为是单极性测量,不支持负温
- bit14~bit3:12位温度值,0.25°C/LSB
- bit2:热电偶开路标志,1表示开路
- bit1:恒为0
- bit0:恒为0
解析逻辑就是先检查bit2,如果为1直接报故障,不需要解析温度。如果正常,把原始数据右移3位再取低12位,然后乘0.25就得到实际温度。由于bit15恒为0,右移之后不会污染温度数据,所以代码里直接(raw >> 3) & 0x0FFF就行。
2.3 MAX31855的32位数据解析与冷端温度
MAX31855读回来的是32位数据,位定义如下:
- bit31:符号位
- bit30~bit18:14位温度值,二进制补码,0.25°C/LSB
- bit17~bit4:12位冷端温度,二进制补码,0.25°C/LSB
- bit3:保留位,恒为0
- bit2:热电偶短路到VCC
- bit1:热电偶短路到GND
- bit0:热电偶开路
温度解析时,先把(raw >> 18) & 0x3FFF取到低14位,然后做符号扩展。二进制补码的第13位就是符号位,判断0x2000是否置位,如果置位就把它扩展成16位有符号数。冷端温度同理,取(raw >> 4) & 0x0FFF,再检查第11位做符号扩展。最后都乘0.25。
这里特别提醒一句:当低3位故障标志的任意一位为1时,整个32位数据里的温度字段和冷端字段都无效,不能拿来计算。很多人没注意这一点,在开路状态下读到一个看似正常的温度值,还以为设备是好的。
3. 一套驱动吃两代芯片:可复用的HAL库实现
既然两颗芯片的物理连接兼容、读取时序一致,只是数据格式不同,那驱动就可以设计成一套代码、两个解析分支。这样做的好处是换料时不用改上层调用逻辑,只需要改一个枚举值。
3.1 抽象设备句柄与API设计
我用一个结构体把SPI句柄、片选引脚和芯片类型包起来,上层调用只跟这个结构体打交道。这样项目里即使是多路测温,也可以定义多个设备实例,互不干扰。
typedef enum { TC_CHIP_MAX6675 = 0, TC_CHIP_MAX31855 } tc_chip_t; typedef struct { SPI_HandleTypeDef *hspi; GPIO_TypeDef *cs_port; uint16_t cs_pin; tc_chip_t chip; } tc_dev_t; typedef enum { TC_OK = 0, TC_ERR_OPEN, TC_ERR_SHORT_VCC, TC_ERR_SHORT_GND } tc_fault_t; typedef struct { float temperature; // 热电偶测量端温度,摄氏度 float cold_junction; // 冷端温度,仅MAX31855有效 tc_fault_t fault; // 故障码 uint8_t raw_valid; // 数据有效性,1有效,0故障 } tc_sample_t; tc_sample_t tc_read_sample(tc_dev_t *dev);API只有一个tc_read_sample,返回一个结构体,里面包含温度值、冷端值和故障码。这样调用方拿到结果后不用关心底层是哪颗芯片,统一判断raw_valid和fault就行。
3.2 底层SPI读取与数据拼接
底层的SPI读取,MAX6675读2字节,MAX31855读4字节。这里统一用8位模式收发,收到后手动拼成16位或32位。不推荐直接配置成16位或32位数据长度,因为有些MCU对非对齐访问和DMA有额外限制,8位模式最稳。
static uint16_t tc_spi_read16(tc_dev_t *dev) { uint8_t tx[2] = {0x00, 0x00}; uint8_t rx[2] = {0x00, 0x00}; HAL_GPIO_WritePin(dev->cs_port, dev->cs_pin, GPIO_PIN_RESET); HAL_SPI_TransmitReceive(dev->hspi, tx, rx, 2, HAL_MAX_DELAY); HAL_GPIO_WritePin(dev->cs_port, dev->cs_pin, GPIO_PIN_SET); return ((uint16_t)rx[0] << 8) | rx[1]; } static uint32_t tc_spi_read32(tc_dev_t *dev) { uint8_t tx[4] = {0x00, 0x00, 0x00, 0x00}; uint8_t rx[4] = {0x00, 0x00, 0x00, <p> <a href="https://download.csdn.net/download/u010210711/12484642" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>