
简介基于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保留始终为0D14-D3温度数据12位二进制补码LSB0.25°CD2热电偶开路1表示热电偶开路D1设备ID始终为0D0三态低阻态不用所以温度值不是右移4位而是右移3位再乘以0.25。很多新手把D0也当成数据位导致温度偏小或跳动。例程里一旦出现“温度读出来像室温但差很多”的情况多半是右移位数错了。另外如果D2为1温度寄存器内容不可用避免拿去换算。芯片内部实际上包含一个斩波放大器和一个冷端补偿二极管这部分不需要外部电路干预。STM32要做的就是产生16个SCK时钟并在合适的SPI采样边沿把MISO上的数据锁存回来。下面看STM32侧的硬件连接。2.2 STM32的接线和SPI参数设定无论例程是按标准库还是HAL库写的引脚映射基本一致。以STM32F103的SPI1为例常用的接法是PA5接SCK、PA6接MISOSO、PA4接CS。如果例程里默认用的是SPI2对应PB13/PB14/PB12。表2-2整理了这两种常见映射。表2-2 常用STM32 SPI引脚连接序号信号SPI1引脚SPI2引脚1VCC3.3V或5V3.3V或5V2GNDGNDGND3SCKPA5PB134MISOPA6PB145CSPA4PB12注意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; // CPOL0, 空闲低 hspi1.Init.CLKPhase SPI_PHASE_2EDGE; // CPHA1, 第二个边沿采样 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); }这段代码里有几个参数需要说明。CPOL0、CPHA1这一组合对应SPI Mode 1是MAX6675数据手册推荐的做法。如果例程里看到CPOL0、CPHA0也不要马上改先看MISO数据是否在SCK上升沿附近稳定。BaudRatePrescaler32是把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到D3D3是最低有效位所以右移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°C0x0FF80000 1111 1111 10000510.25°C0x00040000 0000 0000 01001开路3.3 例程里几种SPI读取变体在市面上各种例程包中MAX6675的读取不外乎三种写法。第一种是上面这种HAL库的TransmitReceive第二种用的是标准库直接操作SPI_I2S_SendData和SPI_I2S_ReceiveData一次发两个字节第三种会自己写GPIO模拟SPI把SCK拉高拉低手动移位。前两种性能足够第三种只在硬件SPI被别的设备占用时使用。如果你的例程是用标准库写的核心代码通常像这样/* 标准库读取MAX6675SPI1配置为模式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最高72MHzSPI2挂在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设备。本文还有配套的精品资源点击获取