简介:针对STM32F103嵌入式开发者的四路MAX6675温度采集工程,以SPI方式读取K型热电偶数据,实现多点温度监测,并通过LCD1602实时显示、UART串口打印输出,适合学习传感器采集、SPI通信和外设驱动开发的入门与进阶参考。压缩包约7.65MB,共216个文件,以C源文件、H头文件、Keil工程文件(uvprojx)、编译生成文件(o/crf/axf/hex/map)等为主,既保留可读源码也包含可直接烧录的hex文件,便于直接验证与二次修改。已有1143人学习使用。项目代码覆盖MAX6675四路采集配置、LCD1602显示驱动、串口打印逻辑、系统时钟与定时器初始化等模块,目录结构清晰,能帮助读者快速理解从传感器数据获取到人机交互显示和串口输出的完整流程。对于需要搭建多点测温装置或熟悉STM32标准外设库的开发者,这套资料具有直接参考价值。 手里同时要测四路K型热电偶的温度,并且需要实时显示到LCD、发到串口上位机,这个需求在我之前的一个小项目里正好出现过。当时直接把STM32F103和一排MAX6675模块搭起来,用SPI轮询四路通道,结果一次就点亮了液晶屏,数据也稳定地出现在串口调试助手里。今天就把这套方案从硬件到代码完整整理出来,项目工程里包含了驱动源码、接线说明,直接在标准外设库工程上改一改就能复用。
这类项目的典型应用场景很明确:多路热风枪温度监控、3D打印机热床与喷头温度采集、锂电池充放电温度记录、工业加热设备多点巡检等。凡是“需要同时看几个热电偶温度、又要留一份数据给电脑处理”的场合,这套配置基本都能用。适合有一定STM32基础、想把传感器真正跑起来的人参考。
1. 项目概述与方案选型
1.1 为什么选择MAX6675做温度采集
K型热电偶本身输出的是微伏级电压信号,完全不能直接接单片机ADC。MAX6675这颗芯片内部集成了信号放大、冷端补偿和ADC转换,把热电偶的电压信号变成数字量,再通过SPI接口发给主控。这个“一站式”特性让它在单片机项目里非常流行,省掉了运放调理电路和一大堆校准工作。
MAX6675的实际测量范围是0到1024摄氏度,分辨率为0.25摄氏度。对绝大多数加热设备监控场景来说,这个精度够用。它内部还带了冷端补偿,也就是说室温变化不会导致测量值漂移,这在多路测量时特别重要——如果冷端补偿做不好,四路读出来的温度会跟实际环境温度一起变动,容易误判。
1.2 为什么选择STM32F103作为主控
STM32F103主频72MHz,SPI、USART、GPIO这些资源非常充足,处理四路MAX6675的轮询采集完全不在话下。这项目用的是大家比较熟悉的标准外设库(StdPeriph),不是HAL,代码看起来直白,网上资料也多,新手对着改也能上手。
选择F103而不是51单片机,核心原因有两个。第一,F103的GPIO翻转速度快,软件模拟SPI读MAX6675的时候,时序余量充足,不像51那样要抠机器周期。第二,F103的USART带硬件缓冲,串口打印大量温度数据时不丢数据,而且后续想加FreeRTOS、文件系统、PID控制,这个平台都撑得住。
1.3 四路采集的拓展思路
四路MAX6675共享同一个SPI总线,利用各自独立的CS片选脚区分设备。读取第一路时,把CS1拉低,其他三路CS保持高电平;读完后拉高CS1,再拉低CS2去读第二路。这是最典型的SPI多设备挂载方式,不增加SPI外设,只多占几个GPIO引脚。
这样一来,扩展路数非常方便。今天做四路,明天想改成六路、八路,只需要复制一份读取代码、多接一个模块、多占一个CS引脚就行,主控逻辑完全不用动。
2. 硬件连接与关键时序
2.1 接线方法(完整对照表)
MAX6675标准的模块有5个引脚:VCC、GND、SCK、SO(也叫MISO)、CS。模块上的T+和T-是接热电偶的,T+接K型热电偶的正极(通常是红色线),T-接负极(另一色线)。
我实际使用的引脚分配如下表,建议直接照抄:
| MAX6675模块 | STM32F103引脚 | 说明 |
|---|---|---|
| VCC(所有模块) | 3.3V | 统一用3.3V供电 |
| GND(所有模块) | GND | 共地必须接牢 |
| SCK(所有模块并联) | PA5 | SPI时钟线共用 |
| SO(所有模块并联) | PA6 | 数据输出线共用 |
| CS1 | PA4 | 第一路片选 |
| CS2 | PA7 | 第二路片选 |
| CS3 | PB0 | 第三路片选 |
| CS4 | PB1 | 第四路片选 |
LCD1602和串口的接线也需要说明一下。LCD1602采用4线模式,能省4个引脚:
| LCD1602引脚 | STM32F103引脚 |
|---|---|
| RS | PB10 |
| RW | PB11 |
| E | PB12 |
| D4 | PB13 |
| D5 | PB14 |
| D6 | PB15 |
| D7 | PB8 |
| VCC | 5V或者3.3V(模块不同而异) |
| GND | GND |
串口这里用的是USART1,PA9做TX、PA10做RX,接一个USB转TTL模块就能在电脑上看到数据。
2.2 MAX6675的SPI读写时序
MAX6675的SPI时序很有特点。读数据的流程是:先把CS拉低,此时SO引脚输出第一位数(其实是无效位),然后给16个SCK脉冲,在每个脉冲的上升沿读取SO引脚上的电平,就得到了16位数据。
时序上有个硬性要求:CS拉低之后至少要等100ns才能开始第一个SCK脉冲。整个16位数据读完后,CS拉高。这个拉低的持续时间也不能太短,否则转换结果可能还没准备好。另外,MAX6675完成一次温度转换大约需要220ms,也就是说两次连续读取之间的间隔不能小于220ms,否则读到的还是旧数据。
16位数据里真正有用的部分,看这张表就清楚了:
| 位 | 含义 |
|---|---|
| D15 | 恒为0,无意义 |
| D14~D3 | 12位温度数据 |
| D2 | 热电偶开路标志,1表示断线 |
| D1~D0 | 恒为0,无意义 |
温度值计算很简单:把D14~D3这12位数据当成一个整数,乘以0.25就是实际温度。比如读出16位数据是0x0190,右移3位得到十进制50,50乘以0.25等于12.5摄氏度。如果D2位是1,说明热电偶没接好,此时温度数据无效。
2.3 硬件设计上容易踩的坑
第一个坑是供电电压。很多MAX6675模块上有稳压电路,5V也能用,但SO引脚输出的高电平是跟随供电电压的。如果模块用5V供电,SO引脚输出5V高电平,直接接在STM32F103的PA6上,长期运行有可能损伤引脚。所以稳妥的做法是整个系统统一3.3V给MAX6675供电,避免电平不匹配。
第二个坑是热电偶方向接反。K型热电偶接反不会烧芯片,但读出来的温度是负值或者明显偏低。如果四路里有某一路读数异常,优先检查热电偶是否接反。
第三个坑是模块的SO引脚。MAX6675的SO引脚在CS为高电平时输出高阻态,所以四路SO并联不会互相干扰,不需要额外加上拉电阻。但模块上如果有板载指示灯的话,不同模块之间可能会因为LED电流导致电源纹波,严重时会让温度读数跳动,这时在模块供电端加一个100uF电解电容和0.1uF瓷片电容去耦,问题通常能解决。
3. 软件实现:STM32F103库函数版
3.1 引脚初始化与串口配置
整个工程的初始化顺序是:先配置系统时钟(默认72MHz),然后配置GPIO、USART1、LCD1602,最后进入主循环。GPIO配置这里有一个重要细节:PA4、PA5、PA6、PA7这四个引脚默认是SPI1的NSS、SCK、MISO、MOSI功能,但因为我们用软件模拟SPI,不需要开启SPI外设,只用普通推挽输出和上拉输入模式就行。
PA5作为SCK,配置成50MHz推挽输出;PA6作为SO,配置成上拉输入;PA4、PA7、PB0、PB1作为CS,配置成推挽输出。LCD引脚全部配置成推挽输出,RS、RW、E、D4~D7都是输出模式。
USART1配置成115200波特率、8位数据、1位停止位、无校验。这里提醒一句,串口发数据的时候要注意换行符的问题。单独发\n有时候在部分串口助手里显示不换行,最稳妥的做法是发\r\n组合,这样无论在SSCOM还是其他调试工具里都能正常换行。
3.2 MAX6675读取与数据解析
读取MAX6675的核心代码,用软件模拟SPI其实很直观。上代码:
// 读取一路MAX6675数据,返回16位原始值 uint16_t MAX6675_Read(GPIO_TypeDef* CS_PORT, uint16_t CS_PIN) { uint16_t raw = 0; uint8_t i; // CS拉低,等待至少100ns GPIO_ResetBits(CS_PORT, CS_PIN); Delay_Us(1); for(i = 0; i < 16; i++) { // SCK拉高,在上升沿读取SO数据 GPIO_SetBits(GPIOA, GPIO_Pin_5); Delay_Us(1); raw <<= 1; if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) == Bit_SET) { raw |= 0x01; } // SCK拉低 GPIO_ResetBits(GPIOA, GPIO_Pin_5); Delay_Us(1); } // CS拉高,结束读取 GPIO_SetBits(CS_PORT, CS_PIN); return raw; } // 把16位原始数据转换成浮点温度值 float MAX6675_ToCelsius(uint16_t raw) { if(raw & 0x04) // 热电偶开路 { return -999.0f; // 用-999表示断线 } return (float)((raw >> 3) * 0.25); }这段代码里两个细节值得注意。第一是每次SCK拉高和拉低之间加1微秒延时,这个时间远大于MAX6675的时序要求,能保证在上升沿时数据已经稳定。第二是读取前先左移再判断MISO电平,这样最高位D15先进来,移位顺序正好对上MAX6675的MSB先行输出。
这里有人会问,为什么不直接用芯片自带的硬件SPI?我实测下来,软件模拟SPI在这个场景里反而更实用。MAX6675的SPI时序非常简单,不需要高速传输,软件模拟可以精确控制每个时序间隔,而且四路CS轮询的逻辑本来就要操作GPIO,软件模拟写起来更顺手。实际上在寄存器操作级别,F103主频72MHz,GPIO翻转速度足够快,读一路16位数据耗时不到1毫秒,四路全读也就几毫秒,完全不影响系统实时性。
3.3 LCD1602显示与串口上报
LCD1602的驱动代码,我使用的是4位模式,关键点是初始化时序。LCD1602上电后必须等40ms以上,然后按“03、03、03、02”的顺序发初始化命令,再设置“显示开、光标关、不闪烁”等参数。4位模式下,每发一个字节要先发高4位再发低4位,中间通过E引脚的下降沿锁存数据。
显示四路温度时,我把它分成两行:
char line1[17]; char line2[17]; sprintf(line1, "T1:%4.1f T2:%4.1f", temp[0], temp[1]); sprintf(line2, "T3:%4.1f T4:%4.1f", temp[2], temp[3]); if(temp[0] < -100 || temp[1] < -100) { sprintf(line1, "T1: OPEN T2: OPEN"); } if(temp[2] < -100 || temp[3] < -100) { sprintf(line2, "T3: OPEN T4: OPEN"); } LCD_ShowString(0, 0, line1); LCD_ShowString(0, 1, line2);这样第一行显示T1和T2的温度,第二行显示T3和T4的温度,每路温度保留一位小数。一旦某一路上报了负的999(也就是断线标志),LCD对应位置直接显示OPEN,比显示乱码或者0.0友好得多。
串口打印这里,我推荐一个上位机解析友好的格式。如果每次都打印“CH1:25.50℃ CH2:26.00℃”这种中文加单位的长文本,上位机处理起来麻烦,中文编码在串口助手之间也不一致。更实用的做法是打印纯数字,用逗号分隔:
printf("%.2f,%.2f,%.2f,%.2f\r\n", temp[0], temp[1], temp[2], temp[3]);配合上位机用Python的split(',')或者EXCEL导入,都能轻松解析成表格,做趋势记录很方便。如果你用的串口助手没有CSV解析功能,想调试时看个直观效果,可以临时改成“CH1:25.50 CH2:26.00”这样的格式。
3.4 主循环与转换周期调度
主循环的逻辑非常直白:
while(1) { // 依次读取四路MAX6675 raw[0] = MAX6675_Read(GPIOA, GPIO_Pin_4); raw[1] = MAX6675_Read(GPIOA, GPIO_Pin_7); raw[2] = MAX6675_Read(GPIOB, GPIO_Pin_0); raw[3] = MAX6675_Read(GPIOB, GPIO_Pin_1); // 转换成温度 for(i = 0; i < 4; i++) { temp[i] = MAX6675_ToCelsius(raw[i]); } // 刷新LCD显示 LCD_UpdateDisplay(temp); // 串口打印 printf("%.2f,%.2f,%.2f,%.2f\r\n", temp[0], temp[1], temp[2], temp[3]); // 等待下一个转换周期 Delay_Ms(250); }每次循环间隔250ms,比MAX6675的220ms转换周期略长,保证每次读到的都是最新转换结果。四路全部读取只占几毫秒,剩下的时间都让给LCD刷新和串口发送,整体刷新率大概在4Hz左右,看温度变化完全够用。
这里有一个容易忽略的点:不要在读取MAX6675之后马上再读第二次,一定要留有至少220ms的时间间隔。有的新手发现数据不变,以为是传感器坏了,其实是读取频率太快,读到的全是上一次的缓存结果。
4. 常见问题与排查技巧
4.1 温度数据异常怎么定位
温度读出来一直是0.00,这是最常见的故障。排查优先级是:先看CS引脚对应的GPIO是否配置成输出模式,再看SCK引脚和SO引脚有没有接反,最后用万用表量一下MAX6675模块的VCC和GND是不是真的3.3V。我遇到过一例,就是模块供电线太细,压降太大,模块实际工作电压只有2.8V,导致SPI通信不稳定。
温度读出来忽高忽低,跳变超过1摄氏度,通常是电源问题或热电偶接触不良。K型热电偶的探头和引线连接处是焊接的,经常弯折容易虚焊。可以用万用表测热电偶两端电阻,正常值在几欧姆到几十欧姆之间,如果电阻无穷大说明内部断线。另外热电偶线材要尽量远离交流电线和继电器之类的强干扰源,否则感应出来的噪声会被MAX6675完整放大。
温度读出来是负值,最常见的原因就是热电偶正负极接反。K型热电偶在高温下接反,读数不是负温度,而是在0℃附近徘徊,这个特征比较迷惑人。判断方法很简单:用手捏住热电偶探头,温度读数如果升高说明接线正确,如果下降说明接反了。
4.2 LCD和串口不工作怎么办
LCD1602显示方块或者没反应,90%的情况是初始化时序不对。注意LCD1602上电后必须延时至少40ms才能发第一条命令,如果上电后立即初始化,LCD内部还没准备好,命令就丢失了。另外检查一下VO引脚,也就是对比度调节脚,通常要接一个10K电位器到GND,阻值调到中间,否则液晶显示可能淡得看不见。
串口收不到数据,优先级最高的排查项是USB转TTL模块的TXD和RXD有没有接反。STM32的TX要接模块的RX,STM32的RX接模块的TX。我见过很多次,两个板子的TX接TX,一头一头发出去谁也收不到。第二个排查项是波特率,确认串口助手里的波特率和代码里配置的115200一致。第三是看PA9和PA10有没有被其他功能占用,比如有些开发板用PA9接了LED或者按键,需要断开。
4.3 如何让四路采集更稳定
四路MAX6675一起工作时,如果某一路偶尔读到错误数据,大概率是片选切换太仓促。代码里每次CS拉低之后、开始读数据之前,加一个微秒级的延时,让MAX6675的SO引脚有时间从高阻态切换到数据输出状态,这个问题就会消失。
我还在代码里加了一组软件滤波,每次连续读取三遍,取中间值,执行效果很好。MAX6675本身是12位分辨率,噪声本身不大,但在电磁环境复杂的工业现场,偶尔跳变一个LSB也很正常,用中值滤波能消除绝大多数偶发异常。
5. 后续扩展与低功耗思路
5.1 从轮询到DMA
如果后续想提升系统的效率,可以考虑把软件模拟SPI改成硬件SPI + DMA。但前面也说了,MAX6675本身转换周期就有220ms,四路轮询也就几毫秒完成,普通场景下DMA带不来实际体验提升。真正需要DMA的场景是:主控同时还要跑屏显、通信、PID控制等高占用的任务,不想让SPI读取阻塞主循环。
改DMA的思路是,把四路MAX6675的CS都用GPIO控制,SPI的SCK和MISO用硬件SPI1,每次读取时用SPI_I2S_DMACmd开启接收DMA,DMA收到16位数据后自动触发中断,在中断里读结果。这样主循环完全不用等待,靠中断驱动数据刷新。
5.2 低功耗场景优化
如果你的项目需要电池供电,可以参考STM32F103的停机模式。思路是:主循环读一次温度、刷一次屏、发一次串口,然后立刻进入STOP模式,用定时器唤醒,再重复以上流程。MAX6675转换期间其实不需要主控参与,所以可以在MCU睡着的220ms里等它转换完,醒过来直接读数据。
实际测试下来,这种“读温度-睡觉-醒-读温度”的节奏,待机电流能从正常的40mA降到5mA左右。当然,LCD1602和MAX6675本身的耗电占了大头,真正要低功耗还得给传感器单独做电源开关,这里就不展开说了。
最后分享一个我做这套项目时的小心得:给每个MAX6675模块贴上通道编号标签,尤其是在机箱里走线的时候,四根热电偶混在一起,不贴标签就只能一路一路拔了试,非常费时间。工程代码里我在结构体里也存了通道号和片选脚,后续加通道只需要往数组里追加就行,这也是多路采集项目最该提前规划好的部分。
本文还有配套的精品资源,点击获取