news 2026/9/17 3:05:07

STM32读取MAX6675热电偶测温:SPI例程解析与移植要点

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32读取MAX6675热电偶测温:SPI例程解析与移植要点

简介:基于MAX6675与STM32的测温例程,为嵌入式开发者提供一套可直接学习与移植的K型热电偶温度采集实现方案。程序涵盖SPI通信初始化、MAX6675驱动配置、温度数据读取与换算等关键环节,适合正在学习STM32外设驱动或需要快速完成热电偶测温功能的初中级开发者。压缩包整体约2.22MB,共146个文件,包含29个C源码、29个头文件、工程编译生成的十六进制文件与调试映射文件,以及Keil工程配置和备份文件,可据此了解完整工程结构与编译流程。已有806人学习下载,例程代码结构清晰,注释与资源文件便于对照排查硬件连接和SPI时序问题,能有效缩短开发者在温度采集模块上的调试时间。

1. MAX6675_STM32例程:一套SPI测温代码到底解决了什么

如果做K型热电偶测温,很多人第一反应是MAX6675。它把热电偶的微弱毫伏信号放大、冷端补偿、12位ADC全部做进一块芯片里,STM32只要用SPI把结果读回来。标题里这个“MAX6675_STM32例程.zip”听起来就是个代码压缩包,但背后把这类项目的三个痛点都覆盖了:SPI时钟怎么配、16位数据怎么拼接、热电偶断线怎么识别。真正动手时,不少人死在数据始终为0或温度乱跳上,而一份能下到板子上直接改的例程,能省掉半天看手册的时间。无论是做烤箱温度监控、电机保护、还是工业采集模块,这套东西都能作为起点。下面从最常见的F103/F407开发板出发,把例程背后SPI、数据格式和移植细节整个拆开。

2. 从例程反查MAX6675的硬件与SPI时序

2.1 MAX6675内部原理与数据格式

先看数据手册里最关键的输出帧。MAX6675一次转换周期大约220ms到250ms,转换完成后,把结果放在一个16位移位寄存器里,等待外部读走。这16位不是每一位都表示温度,表2-1给出了标准的数据位定义。

表2-1 MAX6675输出数据位(16位,从高位到低位)

位序号名称含义
D15保留始终为0
D14-D3温度数据12位二进制补码,LSB=0.25°C
D2热电偶开路1表示热电偶开路
D1设备ID始终为0
D0三态低阻态,不用

所以温度值不是右移4位,而是右移3位再乘以0.25。很多新手把D0也当成数据位,导致温度偏小或跳动。例程里一旦出现“温度读出来像室温但差很多”的情况,多半是右移位数错了。另外,如果D2为1,温度寄存器内容不可用,避免拿去换算。

芯片内部实际上包含一个斩波放大器和一个冷端补偿二极管,这部分不需要外部电路干预。STM32要做的就是产生16个SCK时钟,并在合适的SPI采样边沿把MISO上的数据锁存回来。下面看STM32侧的硬件连接。

2.2 STM32的接线和SPI参数设定

无论例程是按标准库还是HAL库写的,引脚映射基本一致。以STM32F103的SPI1为例,常用的接法是PA5接SCK、PA6接MISO(SO)、PA4接CS。如果例程里默认用的是SPI2,对应PB13/PB14/PB12。表2-2整理了这两种常见映射。

表2-2 常用STM32 SPI引脚连接

序号信号SPI1引脚SPI2引脚
1VCC3.3V或5V3.3V或5V
2GNDGNDGND
3SCKPA5PB13
4MISOPA6PB14
5CSPA4PB12

注意,MAX6675的SDO是推挽输出,但它在CS为高时是高阻态。如果把MISO引脚复用成SPI功能,STM32的外设会在片选未选中时自动忽略输入,所以通常不需要外部上拉。不过有些低功耗设计会加一个10kΩ上拉到3.3V,防止悬空电平导致SPI误采样,例程里一般不体现,但实际项目值得保留。

SPI参数设定是例程里最值得抄的一段。先看HAL库初始化代码。

/* STM32F103 HAL库 SPI1初始化 */ SPI_HandleTypeDef hspi1; void MX_SPI1_Init(void) { hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; // STM32做主机 hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; // 逐字节读取 hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // CPOL=0, 空闲低 hspi1.Init.CLKPhase = SPI_PHASE_2EDGE; // CPHA=1, 第二个边沿采样 hspi1.Init.NSS = SPI_NSS_SOFT; // 手动控制CS hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32; // 按SPI1最高36MHz算约1.125MHz hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; // 高位先出 HAL_SPI_Init(&hspi1); }

这段代码里有几个参数需要说明。CPOL=0、CPHA=1这一组合对应SPI Mode 1,是MAX6675数据手册推荐的做法。如果例程里看到CPOL=0、CPHA=0,也不要马上改,先看MISO数据是否在SCK上升沿附近稳定。BaudRatePrescaler=32是把SPI1时钟36MHz分频到1.125MHz,远低于数据手册给出的4.3MHz上限,实际读取250ms一次的数据根本不需要更快,分频大一点还能减少噪声干扰。

一个常见的反直觉点是NSS。MAX6675有真实CS引脚,但STM32的硬件NSS功能会与SPI片选信号互相干扰。例程里普遍把NSS配成软件管理,然后把CS当普通GPIO操作。这样做的好处是读一个温度可以随时拉低CS、再拉高,不依赖SPI事件标志。如果例程用NSS_HARDWARE,会出现CS拉低时间不对,读回来的数据错位。

3. 例程核心代码讲解:读取、换算与异常位

3.1 用HAL库写一个最小读取函数

例程再怎么封装,最终回到读取16位数据这一步。由于SPI数据宽度按8位配置,需要发两条假写命令把数据时钟打出来。

/* 读取MAX6675一次温度值,返回原始16位ADC码 */ uint16_t MAX6675_ReadRaw(void) { uint8_t rx[2] = {0, 0}; uint8_t tx[2] = {0, 0}; // 写什么无所谓,用来产生SCK uint16_t raw = 0; HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); HAL_SPI_TransmitReceive(&hspi1, tx, rx, 2, 100); HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); raw = ((uint16_t)rx[0] << 8) | rx[1]; return raw; }

这条函数的核心是HAL_SPI_TransmitReceive。tx数组写0也可以,因为SPI全双工通信时SCK由主机产生,只要主机在发数据,MISO就在往外移数据。拉低CS之后必须等待至少100ns再开始打时钟,这个时间在HAL库的片选切换代码中几乎不会如此精确;如果总线太长或线缆有分布电容,建议在写CS之后加一个__NOP()循环或延迟1us,避免第一拍采样到不稳定电平。

3.2 温度换算和热电偶开路检测

有了raw后,要按第2章表2-1进行位处理。直接写进一个函数,方便例程直呼返回浮点温度。

/* 解析MAX6675原始数据,返回温度值;-9999表示热电偶开路 */ float MAX6675_ReadTemp(void) { uint16_t raw = MAX6675_ReadRaw(); float temp; if (raw & 0x0004) { return -9999.0f; // 热电偶未接或断线 } raw >>= 3; // 删除D2-D0,留下12位温度位 temp = (float)raw * 0.25f; // 每个LSB代表0.25°C return temp; }

这里的掩码判断和右移3位是例程中最重要的两行。如果你看到别人右移4位,是因为把12位数据误当成按16位对齐。MAX6675的温度位是从D14到D3,D3是最低有效位,所以右移3位才能让D3落到bit0。

注意事项有两点。第一,热电偶开路时,D2位会被置1,但MAX6675的SDO在开路后输出可能表现为持续高电平,导致读回0xFFFF,这时要优先判定D2。第二,MAX6675不支持负温度下的线性输出,虽然数据手册标称-200°C到+700°C的宽容限,但0°C以下建议改用其他冷端补偿更好的方案,例程里一般也不会处理负温度。表3-1列几个关键位组合的解读。

表3-1 常见原始值与解析结果

原始值(Hex)二进制D15-D0D2状态温度值
0x019A0000 0001 1001 1010051.25°C
0x0FF80000 1111 1111 10000510.25°C
0x00040000 0000 0000 01001开路

3.3 例程里几种SPI读取变体

在市面上各种例程包中,MAX6675的读取不外乎三种写法。第一种是上面这种HAL库的TransmitReceive;第二种用的是标准库,直接操作SPI_I2S_SendData和SPI_I2S_ReceiveData,一次发两个字节;第三种会自己写GPIO模拟SPI,把SCK拉高拉低手动移位。前两种性能足够,第三种只在硬件SPI被别的设备占用时使用。

如果你的例程是用标准库写的,核心代码通常像这样:

/* 标准库读取MAX6675,SPI1配置为模式0或模式1 */ uint16_t MAX6675_Read(void) { uint16_t value = 0; CS_LOW(); SPI_I2S_SendData(SPI1, 0); // 发第1个假字节 while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET); value = SPI_I2S_ReceiveData(SPI1); value <<= 8; SPI_I2S_SendData(SPI1, 0); // 发第2个假字节 while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET); value |= SPI_I2S_ReceiveData(SPI1); CS_HIGH(); return value; }

标准库写法容易忽略一个坑:发送后必须等待RXNE置位再取数据,否则读到的可能是上一帧的残留值。HAL库内部已经处理这个等待,但标准库例程里经常有人漏掉while等待,导致数据整体错位一个字节。给不同MCU移植时,尤其注意SPI外设的RXNE和BSY标志。这些可见的“反直觉”点就是例程能帮你避雷的地方。

4. 把例程移植到自己的STM32项目

4.1 从标准库例程迁到HAL库/LL库

很多老旧例程压缩包里是标准库写的。而现在新开项目多数用STM32CubeMX生成HAL库工程,所以直接复制原文件会遇到SPI外设句柄、GPIO函数、Delay函数不兼容的问题。

我一般会先把原例程里的MAX6675驱动文件拆成三部分:底层SPI发送接收、CS控制、温度换算。底层SPI发送接收替换成对应HAL接口,CS控制替换成HAL_GPIO_WritePin,温度换算几乎不用改。这份ZIP例程如果包含bsp_max6675.c和bsp_max6675.h,那么里面一定有以上三个功能块。定义下面这个适配层结构,能把不同库的差异隔离出来。

/* 适配层:把SPI发送接收和CS控制统一封起来 */ typedef struct { void (*cs_low)(void); void (*cs_high)(void); void (*spi_transfer)(uint8_t *tx, uint8_t *rx, uint16_t len); } max6675_port_t; max6675_port_t port; port.cs_low = MAX6675_CS_LOW; port.cs_high = MAX6675_CS_HIGH; port.spi_transfer = MAX6675_SPI_Transfer; float temp = max6675_read_temp(&port);

这种写法不是例程原有结构,但做产品时我会这么套。好处是以后换SPI2、换引脚甚至换芯片平台,只需要改四个函数,业务层不用动。如果你只是跑通例程,可以跳过,直接在bsp文件里替换。

4.2 修改SPI引脚和时钟,适配不同开发板

例程默认跑在某个开发板上,你的板子很可能不适用。在CubeMX里打开工程,改SPI引脚或重新映射SPI时,有三个地方必须同步检查。

第一,时钟树里SPI的分频系数。F103的SPI1挂在APB2(最高72MHz),SPI2挂在APB1(最高36MHz)。同一份代码里BaudRatePrescaler设为32,在SPI1上约1.125MHz,在SPI2上也是1.125MHz,但二者预算余量不同。如果改用SPI2且把分频设成2,则9MHz,已经超过MAX6675的4.3MHz上限,所以不宜只看例程里的固定分频值,应结合外设总线时钟计算。

第二,CS引脚如果只做软件GPIO,要注意推挽输出的开漏模式。MAX6675的CS是低电平使能,所以GPIO输出寄存器的初始值要置为高,防止上电瞬间拉低片选,造成一次误转换。例程里一般在GPIO初始化时把CS置为高,移植时别漏。

第三,如果你的开发板引脚不够,可以把CS接到任意GPIO,甚至用TIM PWM或DMA控制,但读取逻辑里CS拉低的时序窗口必须覆盖整个16个SCK周期。最常见的错误是CS拉低后执行了几行计算再发SPI,影响不大,但如果CS拉低后立即发SPI且中间有OS延迟,容易出现读回数据全1,因为MAX6675尚未把数据推到移位寄存器输出端。

/* 换到SPI2时,例程里的相应修改 */ #define MAX6675_CS_PORT GPIOB #define MAX6675_CS_PIN GPIO_PIN_12 #define MAX6675_SPI hspi2 HAL_GPIO_WritePin(MAX6675_CS_PORT, MAX6675_CS_PIN, GPIO_PIN_SET); // CS默认高

4.3 多通道或更高刷新率怎么调整

一个MAX6675只能接一支热电偶。需要测多路温度时,常见方案是每个通道一个MAX6675,它们共享SCK,但每片MAX6675都有独立SDO,所以MISO不能直接并联。多数例程会改成多个CS分时选择,把各芯片的SDO都接到同一个MISO引脚上,利用CS高时的三态输出完成总线复用。

这时读取函数要增加CS选择参数:

uint16_t MAX6675_ReadRaw_CS(GPIO_TypeDef *cs_port, uint16_t cs_pin) { uint8_t tx[2] = {0, 0}; uint8_t rx[2] = {0, 0}; uint16_t raw = 0; HAL_GPIO_WritePin(cs_port, cs_pin, GPIO_PIN_RESET); // 选中目标 HAL_SPI_TransmitReceive(&hspi1, tx, rx, 2, 100); HAL_GPIO_WritePin(cs_port, cs_pin, GPIO_PIN_SET); // 释放总线 raw = ((uint16_t)rx[0] << 8) | rx[1]; return raw; }

多路复用后要注意三个事:每个通道转换时间是异步的,读取时读到的是各自最近一次转换结果;如果某一路热电偶开路,该芯片的SDO可能持续为高,占住MISO电平,所以程序上要增加异常标记;最后是采样周期,别在循环里把四个CS快速轮询一遍就结束,每个通道的转换需要250ms,建议启一个定时器,每250ms循环读一遍四路,或每路独立定时读取。

刷新率不仅是SPI读取频率,也受MAX6675内部转换周期限制。MAX6675每秒约可输出4个有效采样。如果你在例程里硬把读取间隔压到100ms,读到的值是旧值,看起来像温度不变。这种“例程没问题,但速度上不去”的现象,很多人都遇到过。

5. 几招验证例程是否正常的小工具手法

5.1 用逻辑分析仪看CS/SCK/MISO时序

拿到例程第一步不是直接接热电偶,而是先接一个逻辑分析仪,把CS、SCK、MISO三根线抓出来。MAX6675正常读取时,CS先拉低,SCK出现16个脉冲,MISO在SCK边沿附近稳定。如果看到CS拉低但SCK没有动作,说明SPI没有使能外设时钟或中断占用;如果MISO一直为高,检查接线和是否选择了SPI_MODE_1。

逻辑分析仪还能帮你确认最后温度值对应的二进制。例子中读回0x019A时,展开16位后应与表3-1中的二进制完全一致。若错位,往往是把SPI数据大小从8位配成了16位,导致读回顺序颠倒。

5.2 用串口打印观察温度跳变

例程包里一般都有串口打印代码。没接线时,通过ST-Link的虚拟串口把温度值以printf输出。如果打印的是很大数字或-9999,先确认D2位是否置位,也就是热电偶是否接好。K型热电偶的极性不能接反,红色线正极接MAX6675的T+,黄色线接T-,反过来会显示负温度。

观察跳变还有个技巧:把手捏住热电偶接头,看温度是否往37度方向接近。这个实验能同时检验冷端补偿是否工作。如果显示固定在约0.25的倍数且毫无变化,可能是数据位偏移,检查代码里的右移3位是否丢了或配置成了右移4位。

5.3 常见读取失败快速定位

最后给一个排查顺序表,适合遇到例程跑不通时逐条对照。

表5-1 MAX6675例程常见故障定位

现象大概率原因检查方法
温度一直是0CS未拉低或SPI分频过高导致无时钟测SCK引脚波形
温度跳变且偏小右移位数错打印原始hex值
-9999或开路热电偶未接/接反/D2位被误判检查热电偶极性和接头
读回0xFFFFSPI总线无设备响应或模式错误用逻辑分析仪看MISO电平

这几招如果能跑通,例程基本就算吃透了。后续在量产板上去掉测试代码时,别把CS的初始高电平去掉,否则上电瞬间会产生一次虚假片选,可能干扰其他SPI设备。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 3:04:08

SiC/IGBT双脉冲动态测试仪技术解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 3:02:11

Delphi ERP源码实战:编译、数据库连接与系统对接全解析

简介&#xff1a;这是一份Delphi开发的大型企业ERP管理系统完整源码包&#xff0c;面向需要学习传统客户端/服务器架构开发的程序员、用于毕业设计的学生以及正在搭建小型企业信息化系统的小团队。资源包共2906个文件&#xff0c;大小约18.07MB&#xff0c;核心代码以432个pas单…

作者头像 李华
网站建设 2026/9/17 3:02:10

桌面端CRM实战:DeskcommCRM从架构设计到MVP落地全解析

1. 项目缘起&#xff1a;为什么会有 DeskcommCRM 这个项目1.1 先聊聊我对这类系统的真实感受做销售和客户服务的人应该都有这种感觉&#xff1a;客户资料、跟进记录、通话内容、报价单一堆东西散落在 Excel、聊天软件、邮箱和脑子里&#xff0c;真正需要找一条半年前的沟通线索…

作者头像 李华
网站建设 2026/9/17 3:01:49

MySQL vs DuckDB:10亿行数据下OLAP查询性能实测与选型指南

1. 对比的起点&#xff1a;一次真实业务慢查询引发的选型思考大概半年前&#xff0c;我手里一条业务线的用户行为分析报表开始频繁超时。单表记录数刚过 6 亿&#xff0c;每天凌晨的定时任务要跑将近四十分钟&#xff0c;业务方早上八点打开后台&#xff0c;看到的数据经常还是…

作者头像 李华