ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32+MAX6675+K型热电偶高温测温方案详解:原理、接线与代码

STM32+MAX6675+K型热电偶高温测温方案详解:原理、接线与代码 先说个很现实的场景做STM32项目时十次里有八次要碰温度测量测环境温度用DS18B20很方便可一旦测的是加热台表面、电机外壳、烤箱内部这种超过一百度的地方DS18B20就彻底没戏了。工作温度上限125℃贴着极限用随时可能翻车。这时候K型热电偶才是正解测温范围能到1000℃以上探头就两根线便宜又皮实。但热电偶输出的是微伏级的微弱电压STM32内置ADC根本直接采不动中间必须加一颗MAX6675专用芯片做信号放大、冷端补偿和数字化。这篇文章就把STM32F103C8T6最小系统板 MAX6675模块 K型热电偶这套最经典的测温组合讲透从原理、硬件接线到软件代码一次说清楚。光贴代码没意思我尽量把每个决定背后的原因也讲明白包括为什么选MAX6675不选其他方案、为什么SPI要配成这个模式、为什么片选要软件控制而不是用硬件NSS。文末还会附上硬件SPI和模拟SPI两版可直接抄的代码不管引脚怎么安排都能自己改来用。1. 这套测温方案到底解决什么问题1.1 为什么是K型热电偶而不是DS18B20先聊需求。很多温度采集场景要求很简单但很硬性能测高温、探头要耐用、成本要低。比如测电烙铁头温度工作温度常在350℃上下测加热台可能到400℃测热风枪出风温度五六百度也常见。DS18B20这种半导体温度传感器到125℃就到顶了别说测烙铁头贴在加热台边上都可能损坏。K型热电偶是工业上最普及的测温元件之一两根不同材质的金属丝镍铬-镍硅在一端焊接在一起就构成了测温探头。它的测温范围很宽标准K型热电偶理论上可以覆盖-200℃到1300℃左右成本又极低一根探头几块钱到十几块钱。不过要注意本文用的MAX6675芯片只支持0℃到1023.75℃所以真要去测一千多度还得换MAX31855芯片这个后面细说。1.2 为什么中间要加一颗MAX6675热电偶的原理是“塞贝克效应”两种不同金属接触后如果两端温度不一样回路里就会产生一个微小的热电动势。K型热电偶的灵敏度大约是41μV/℃也就是说温度变化1℃只有几十微伏的电压变化。STM32F103的ADC参考电压是3.3V12位分辨率下每个LSB对应约0.8mV。拿这个去采几十微伏的信号量化误差直接吞掉信号测出来全是噪声。MAX6675这颗芯片集成了低噪声放大器、冷端补偿、12位ADC和SPI数字接口。它把热电偶的微弱信号放大并数字化再把冷端温度补偿进去最后通过SPI接口直接输出一个代表绝对温度的12位数据。对外只需要三根线SCK、MISO、CS内部做了大部分脏活累活对新手极其友好。这也是为什么这套组合成了STM32测温入门的标准答案。1.3 物料清单与选型注意整套方案需要的硬件很少新手照着买就行STM32F103C8T6最小系统板一块蓝色Pill板最常见国产兼容型号如GD32F103C8T6也可以直接用MAX6675模块一块淘宝几块钱模块上一般已经焊好芯片、去耦电容和K型热电偶接线端子K型热电偶探头一根建议买带把手的插头式或者螺丝端子款方便接线ST-Link或DAP-Link下载器一个用来烧录程序USB转TTL模块一个用于串口打印温度值杜邦线若干、面包板一块。这里有个选型提示MAX6675模块有两种常见封装一种是裸芯片加排针一种是带K型插座甚至带OLED屏幕的合体板。新手建议先买最简单的裸模块避免合体板把引脚定义搞复杂。K型热电偶探头也分裸线、铠装、插头式室内测试买最便宜的裸露探头就行耐温范围足够。2. 原理讲透热电偶、MAX6675和SPI时序2.1 K型热电偶的毫伏级信号热电偶测的不是“温度”而是“温差”。它的两根金属丝在测温端焊接在一起这个端点叫作“热端”也就是实际要测温度的地方。而另一端接到MAX6675模块的接线端子上这个端点叫作“冷端”也就是参考端。塞贝克效应告诉我们热端和冷端温度不同两条不同金属之间就会产生电动势温差越大电压越高。K型热电偶的输出电压和温度大致呈线性关系大约每度41μV。如果热端是500℃冷端是25℃差值475℃对应的电压大约是19.5mV就这么点电压对放大器的要求很高。而且冷端温度本身会随着环境变化如果不在芯片层面做补偿测温结果一定飘。这就要靠MAX6675了。2.2 MAX6675内部做了什么MAX6675内部大致有这几部分一个低噪声仪表放大器、一个冷端补偿测温二极管、一个12位ADC以及一组SPI接口逻辑。热电偶的热电动势经过放大器放大后送入ADC转换成数字量。与此同时芯片内部的二极管测量出芯片附近的环境温度也就是冷端温度。最后在芯片内部把冷端温度和热电偶的温差电压合成为一个“绝对温度”数字值输出。因为这个补偿是在芯片内部自动完成的SPI接口读出来的温度已经是热端的实际温度不需要自己在软件里做差分计算。模块板上通常还有一个贴片电容做电源去耦所以使用时一般不用再额外加电容。这也解释了为什么MAX6675模块在淘宝上长得几乎一模一样——芯片内部集成度太高外围电路非常简化。2.3 16位数据格式与温度换算MAX6675读回来的数据是16位的但有效信息只占其中一部分。具体格式如下位名称含义15D15无效位通常为014~3D14~D312位温度数据MSB在前无符号数2D2热电偶开路标志1表示开路1D1器件ID通常为00D0三态位通常为0温度数据的换算公式很直接把读回的16位数据右移3位得到12位有效数值再乘以0.25就是摄氏度。提示MAX6675分辨率是0.25℃也就是说温度永远以0.25℃为步进递增。看到25.25℃这种数据是正常的不是噪声。还要注意D2位。如果K型热电偶没接好或者中间断线了D2会被置1。代码里必须对这个位做判断不能无视它直接去换算温度否则断线时会读到乱七八糟的大数。2.4 SPI时序要点为什么选Mode 1MAX6675的SPI时序和常规SPI从机略有区别。片选CS拉低后芯片开始把数据送到MISO线上。之后每来一个SCK时钟脉冲就会移出下一位数据MSB在前。数据在SCK下降沿更新在SCK上升沿时稳定可读。这里就引出一个容易踩坑的点SPI有四种模式MAX6675应该用哪种按数据手册的时序关系来看最稳妥的配置是CPOL0、CPHA1也就是SPI Mode 1。具体含义是SCK空闲时为低电平主机在第二个边沿也就是上升沿采样数据。此时MISO上的数据已经经过下降沿更新并稳定读到的每一位都可靠。如果你用CubeMX配置硬件SPI参数上就是Clock Polarity选择LowClock Phase选择2 Edge。网上有些教程用Mode 0也能读出温度大多是因为MAX6675在CS拉低后MISO上已经输出了第一位时序侥幸没出问题但这种方法在部分批次芯片或拉长杜邦线后容易读到错误数据不推荐。3. 硬件接线照着接就能跑3.1 最小系统板与MAX6675模块的引脚对照这套方案的接线很简单核心就是SPI三根线加电源。以下是我使用的默认引脚分配同时也兼容CubeMX的默认配置STM32F103C8T6引脚功能MAX6675模块引脚3.3V电源正极VCCGND电源地GNDPA5SPI1_SCKSCKPA6SPI1_MISOSO数据输出PA4软件片选CS注意看这里没有用到MOSI。MAX6675是只读从机数据方向只有芯片输出给主机主机不需要发送任何命令字只需要提供SCK时钟。硬件SPI配置成Full-Duplex Master时MOSI引脚会空出来不接就好了。如果选模拟SPI版本PA5、PA6、PA4这几个引脚可以随意改。我自己调试时经常把片选换到PB12或者PA3这些闲置脚模拟SPI最大优势就是引脚灵活不受硬件外设复用限制。3.2 供电、共地与去耦的注意事项MAX6675芯片的供电范围是3.0V到5.5V而STM32F103C8T6最小系统板上的3.3V LDO输出电流一般足够带几个模块。稳妥的做法是给MAX6675模块供3.3V这样它的MISO高电平也是3.3V和STM32引脚电平完全兼容不需要额外转换。网上有些帖子习惯给模块供5V说测量更准但这样一来MISO高电平会接近5V长期直接接到STM32引脚上是有超压风险的。F103的引脚虽然内部有保护二极管但工程上没必要赌这个直接用3.3V供电最安全。共地问题也要强调。MAX6675模块和STM32必须共地否则SPI信号没有参考基准测出来全是乱码。用杜邦线连接时3.3V和GND两对线尽量靠近减少回路面积。特别是用模拟SPI的时候如果地线太长SCK和MISO之间容易耦合噪声。3.3 硬件片选与软件片选怎么选很多人在STM32的SPI外设里看到NSS引脚会纠结要不要启用硬件片选。我的建议很明确MAX6675这种单从机场景别用硬件NSS直接用普通GPIO做软件片选。原因有两条。第一硬件NSS由SPI外设自动控制拉低拉高的时机和MAX6675要求的CS时序不一定完全匹配尤其在不同库版本下行为还不一样排查起来很头疼。第二硬件NSS通常绑定固定引脚一旦引脚被其他功能占用就很被动。软件片选只需要在读取前拉低CS、读取后拉高CS逻辑清晰模拟SPI和硬件SPI可以用同一套片选代码以后板子上加其他SPI从机也好扩展。实践中我见到有人图省事把MAX6675的CS直接接地让它一直处于选中状态。这样在部分芯片上也能读到数据但不推荐因为CS拉高是芯片内部逻辑复位的必要条件长时间保持低电平可能导致状态异常。每次读温度前拉低、读完后拉高这个动作不要省。4. 双版本代码实现硬件SPI与模拟SPI4.1 CubeMX配置硬件SPI的关键参数先讲硬件SPI版本。打开STM32CubeMX选芯片STM32F103C8T6按下面几步配置SYS选择Debug为Serial Wire避免烧录口被占用RCC选择HSE外部晶振也可以用内部RC只不过精度要求不高的测温场景无所谓SPI1工作模式选Full-Duplex Master参数里Clock Polarity选LowClock Phase选2 EdgeData Size选8 BitsFirst Bit选MSB FirstBaud Rate Prescaler选32USART1开异步模式波特率115200用来打印温度在GPIO选项卡里把PA4配置为GPIO_Output初始输出高电平作为MAX6675片选。关于预分频值STM32F103的系统时钟一般是72MHz72MHz除以32等于2.25MHz低于MAX6675手册允许的4.3MHz最大SCK频率余量足够。有些人喜欢拉到4.5MHz极限其实没必要SPI时序稳定性比那点速度重要得多。配置完成后生成工程然后你会看到默认引脚PA5是SPI1_SCKPA6是SPI1_MISOPA7是SPI1_MOSI。MOSI空着不管PA4作为软件CS使用。4.2 硬件SPI版读取代码硬件SPI的核心思路是拉低CS然后通过SPI外设产生16个时钟脉冲在发时钟的同时从MISO接收16位数据收完拉高CS。由于配置的是8位数据长度需要做两次传输再把两个字节拼成一个16位整数。#include main.h extern SPI_HandleTypeDef hspi1; #define MAX6675_CS_LOW() HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET) #define MAX6675_CS_HIGH() HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET) uint16_t MAX6675_Read_Raw_HWSPI(void) { uint8_t tx_data[2] {0xFF, 0xFF}; uint8_t rx_data[2] {0, 0}; uint16_t raw 0; MAX6675_CS_LOW(); // 主机发送0xFF只是为了提供SCK时钟这里真正关心的是rx_data收到的内容 HAL_SPI_TransmitReceive(hspi1, tx_data, rx_data, 2, 100); MAX6675_CS_HIGH(); raw (rx_data[0] 8) | rx_data[1]; return raw; } float MAX6675_Read_Temp_HWSPI(void) { uint16_t raw MAX6675_Read_Raw_HWSPI(); // bit2为热电偶开路标志 if (raw 0x0004) { return -999.0f; } // 右移3位得到12位有效数据乘以0.25换算为摄氏度 return (float)((raw 3) 0x0FFF) * 0.25f; }有几个细节值得展开。第一主机发送0xFF是为了产生SCK时钟脉冲在Master模式下这是标准操作不要被“接收”两个字迷惑SPI的收和发永远是成对发生的。第二拼字节时(rx_data[0] 8) | rx_data[1]因为配置的是MSB First数据手册定义的位序也是MSB First所以直接拼没问题。如果你手欠把First Bit改成了LSB First这个拼接逻辑就全乱了。第三raw 0x0004判断的是D2位这个标志必须检查。有读者可能问能不能用16位数据长度一次收完可以在实际工程中把SPI的Data Size改成16 Bits然后HAL_SPI_TransmitReceive时用uint16_t缓冲即可。但很多新手在16位传输时容易遇到字节序翻转问题所以这里先用8位拼法最直观也最稳。4.3 模拟SPI版GPIO配置与读取代码模拟SPI的出发点很简单不使用STM32的SPI外设而是用GPIO手动翻转SCK让时序完全由代码控制。这样引脚可以任意指定哪怕PA5、PA6被其他功能占用了换一组引脚改几行宏定义就行。CubeMX阶段把PA4、PA5设置为GPIO_OutputPA6设置为GPIO_Input其他都不用配。PA6可以选上拉输入因为SPI空闲时MISO是高阻还是低电平不好说上拉能避免悬空误判。#include main.h #define CS_PORT GPIOA #define CS_PIN GPIO_PIN_4 #define SCK_PORT GPIOA #define SCK_PIN GPIO_PIN_5 #define MISO_PORT GPIOA #define MISO_PIN GPIO_PIN_6 #define MAX6675_CS_LOW() HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_RESET) #define MAX6675_CS_HIGH() HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_SET) #define MAX6675_SCK_LOW() HAL_GPIO_WritePin(SCK_PORT, SCK_PIN, GPIO_PIN_RESET) #define MAX6675_SCK_HIGH() HAL_GPIO_WritePin(SCK_PORT, SCK_PIN, GPIO_PIN_SET) #define MAX6675_MISO_READ() HAL_GPIO_ReadPin(MISO_PORT, MISO_PIN) static void MAX6675_Delay(void) { volatile uint32_t i; for (i 0; i 10; i) { __NOP(); } } uint16_t MAX6675_Read_Raw_SoftSPI(void) { uint16_t raw 0; uint8_t i; MAX6675_CS_LOW(); MAX6675_Delay(); for (i 0; i 16; i) { raw 1; MAX6675_SCK_HIGH(); MAX6675_Delay(); if (MAX6675_MISO_READ()) { raw | 0x0001; } MAX6675_SCK_LOW(); MAX6675_Delay(); } MAX6675_CS_HIGH(); MAX6675_Delay(); return raw; } float MAX6675_Read_Temp_SoftSPI(void) { uint16_t raw MAX6675_Read_Raw_SoftSPI(); if (raw 0x0004) { return -999.0f; } return (float)((raw 3) 0x0FFF) * 0.25f; }这段代码的逻辑和硬件SPI时序是对应的CS拉低后芯片把D15放到MISO上SCK拉高后读取一个位SCK拉低触发芯片更新下一位如此循环16次就拿到了完整16位数据。所以代码里“先SCK高、再读取”并不是随意写的它正好对应了SPI Mode 1的采样相位。那个空转延时函数MAX6675_Delay不要删。STM32F103跑到72MHz时GPIO翻转只要几个时钟周期空转一小段时间可以把SCK半周期拉到微秒级别。MAX6675只限制了最高SCK频率不限制最低频率所以慢一点完全没问题反而更稳。4.4 串口打印与主循环无论用哪个版本主循环逻辑都一样读温度、判断开路、打印、延时。这里给出示范代码重点是把printf重定向到串口。int fputc(int ch, FILE *f) { while (!(USART1-SR USART_SR_TXE)); USART1-DR (ch 0x1FF); return ch; } int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_SPI1_Init(); MX_USART1_UART_Init(); float temp 0.0f; while (1) { temp MAX6675_Read_Temp_SoftSPI(); // 或改为 MAX6675_Read_Temp_HWSPI() if (temp -999.0f) { printf(Thermocouple open circuit!\r\n); } else { printf(Temperature: %.2f C\r\n, temp); } // MAX6675转换周期典型值170ms读取间隔建议不小于200ms HAL_Delay(250); } }这里有个容易被忽略的耗时问题。MAX6675不是连续输出的它内部每转换一次需要大约170ms然后结果被锁存到输出寄存器里。如果你主循环跑得非常快比如每10ms读一次读到的很可能是同一个转换结果表现为温度不变。所以代码里延时250ms是合理的既不会错过新数据又给串口打印留了从容的时间。在实际调试中如果你发现温度刷新很慢先别怀疑代码检查一下读取频率是不是比170ms还快。串口打印时建议用%.2f保留两位小数。MAX6675分辨率是0.25℃所以打印结果通常是25.25、25.50这种属于正常现象。如果你看到25.24999这种浮点数尾巴那是浮点运算的误差不影响使用。5. 踩坑实录常见问题与排查技巧5.1 读数一直为0的排查清单我见过最多的新手问题就是代码跑了但串口打印的温度永远是0.00。这个问题九成出在接线或片选上排查顺序建议这样现象可能原因解决方案读写全0模块没供电或供电不足用万用表量VCC对GND电压确认在3.0V以上读写全0模块和单片机没共地检查GND是否连到同一参考地读写全0CS一直为高模块没被选中确认代码里CS低电平拉低动作执行了读写全0SCK频率超过4.3MHz在CubeMX里调大分频系数SCK降到2MHz左右读写全0SPI极性和相位配置不对改为CPOLLow、CPHA2 Edge即Mode 1偶尔读到0MISO接错引脚确认PA6接的是模块的SO或MISO引脚不是SDO如果接线和代码都检查过了还是全0可以用示波器或者逻辑分析仪抓一下SCK和CS。没有仪器的话写一个最简单的GPIO翻转程序把SCK引脚拉高拉低然后用万用表量电压确认引脚本身没损坏。这一步能快速区分是硬件问题还是代码问题。5.2 温度跳变、偏差大的常见原因读数不是0但温度忽高忽低或者明显偏离实际这通常是信号完整性和探头安装的问题。温度跳变的几个典型来源第一杜邦线太长。我曾经用30厘米长的杜邦线连接MAX6675模块SPI时钟频率还维持2.25MHz结果读到的数据隔几秒跳一次。缩短到10厘米以内后问题消失。对模拟SPI来说可以把延时调大但更根本的解决方法是缩短引线。第二供电纹波大。STM32F103最小系统板上的3.3V通常来自板载LDO如果此时又给OLED、WiFi模块、舵机等从同一路3.3V取电纹波会变大影响MAX6675内部ADC参考电压。遇到温度跳变可以单独用一颗LDO或AMS1117给传感器供电。第三K型热电偶极性接反。K型热电偶红色线是正极接MAX6675模块的“”端子另一根线一般是黄色或蓝色接“-”端子。接反了不会报错但读数会随温度升高而下降甚至显示负温度。如果发现温度走势完全反着来优先怀疑这两根线接反了。第四探头固定方式有问题。如果测的是金属表面温度探头没有压紧、中间有空气隙热传导不好读数会比实际低很多。这时候在探头触点涂少量导热硅脂再用机械方式压紧误差能明显下降。5.3 RTOS场景下的注意事项如果你已经在STM32F103C8T6上移植了FreeRTOS想把MAX6675读取放到一个独立任务里有几个坑需要提前避开。模拟SPI版本对任务抢占非常敏感。虽然MAX6675允许很慢的时钟但如果读取16位的过程中被更高优先级任务打断CS低电平时间会被拉长。从时序图看只要CS还在低电平芯片内部移位寄存器就不会复位理论上恢复后还能继续读但一旦被打断的位置发生在SCK拉高期间读取位就可能错乱。稳妥做法是在读取函数里挂一个互斥信号量或者把读取任务优先级设低只让短任务抢占它。如果多个外设共用同一个硬件SPI总线比如MAX6675和OLED都挂在SPI1上那么片选管理就必须严格操作哪个设备就拉低哪个设备的CS操作完立刻拉高。千万不要让两个从机的CS同时为低。HAL库本身不会替你管这个需要自己在代码层面加互斥。另外F103的硬件SPI在RTOS下被中断打断也不会丢字节因为SPI硬件时序由外设产生只会多等一个字节的时间不会像模拟SPI那样被扯乱。所以RTOS项目里优先用硬件SPI版本代码稳定性会好很多。5.4 模块级替换与后续扩展方向MAX6675的替代芯片主要是MAX31855。两者引脚不兼容数据格式也不同MAX31855的测温范围更宽能覆盖负温和超过1024℃的高温同时还输出K型热电偶故障标志和冷端温度。如果你要测的温度超过1000℃或者需要测负温建议直接上MAX31855。它的SPI时序和MAX6675相似只是数据位定义不同换芯片后改一下解析函数就行。还有一个常见的“普通芯片能不能换成国产兼容芯片”问题。STM32F103C8T6的国产替代型号比如GD32F103C8T6管脚定义、CubeMX工程基本完全兼容SPI外设的寄存器结构也很接近。实际测试中同一份代码不需要改动就能驱动MAX6675。要注意的是GD32在上电后的SPI初始时序略有差异如果遇到第一次读温度总为0的问题可以在初始化完成后多读一次丢弃或者加50ms延时后再进入主循环。后面想扩展功能的话可以走几个方向加一块SSD1306 OLED显示实时温度或者接ESP8266模块把温度数据上传到云平台做远程监控。有人问过ESP8266能不能直接连MAX6675理论上有SPI硬件并可以用GPIO模拟但ESP8266在跑WiFi协议栈时对时序要求高的操作容易受影响不如用STM32读取MAX6675再通过串口把温度发给ESP8266分工明确各干各的拿手活。最后再分享一个小经验。K型热电偶的信号是毫伏级MAX6675芯片的冷端补偿基准就在芯片附近所以模块的接线端子尽量远离强热源。很多人把模块直接放在加热台旁边结果冷端温度漂移读数跟着飘。模块和探头的连接处离开热源一段距离让接线端子处在室温环境里测温稳定性会好很多。做工业现场测试时给MAX6675模块扣一个塑料外壳挡辐射热也能明显减少热干扰。
返回列表