☰
STC15单片机驱动MLX90614实现红外测温与LCD1602显示
2026/9/25 23:21:03 网站建设 项目流程

简介:这是一份基于STC15F2K60S2单片机实现Mlx90614红外测温并用LCD1602显示的完整工程,主要面向51单片机初学者与嵌入式测温应用开发者,用于学习非接触式温度采集、I2C总线通信及液晶显示控制。压缩包共17个文件,以C源码和头文件为主,涵盖传感器驱动、I2C底层读写、LCD1602显示驱动、延时函数与主程序模块,同时附带Keil工程配置、说明文档及LCD指令集/ASCII表参考图片,整体仅1.33MB,内容紧凑且便于直接编译调试。该项目已有324人学习,代码将底层驱动与上层逻辑分离,模块化程度较高,便于逐段理解。通过阅读和修改这些文件,读者可以掌握Mlx90614的I2C读取温度流程、LCD1602字符显示初始化与写入方式,以及单片机定时延时和多模块协同工作的编程思路,对课程设计、毕业设计或入门实践都具有参考价值。

1. 非接触测温枪拆开之后:STC15、MLX90614和LCD1602是怎么协作的

把一支出厂价几十元的红外测温枪拆开,里面的核心往往就是这三样东西:一颗51内核单片机、一个MLX90614红外热电堆传感器、一块LCD1602字符屏。用STC15F2K60S2读取MLX90614的环境温度和物体表面温度,再通过LCD1602显示出来,整个过程不依赖RTOS、不依赖串口上位机,纯裸机C代码就能跑通。这个组合是嵌入式入门里少有的“高信息密度”项目:既有I2C/SMBus从机协议,又有并行LCD时序,还涉及数据手册寄存器解析和整数量化换算。非常适合刚做完LED流水灯、想往传感器驱动方向深入的人,也适合做课程设计时需要一个能现场演示的非接触式测温方案。

2. MLX90614的I2C读温度原理与LCD1602并行接口设计

2.1 MLX90614内部测量链路:热电堆、参考端与数字输出

MLX90614的敏感核心是红外热电堆。物体辐射的红外能量照射到芯片内的热电堆上,在热结和冷结之间产生温差电压;与此同时芯片内部还有一个环境温度传感器测量冷结温度。两路信号经过Sigma-Delta ADC转换、DSP运算后,最终得到两个我们需要的东西:环境温度TA和物体温度TOBJ。这个换算过程在传感器内部完成,单片机不需要关心红外辐射的物理公式,只要通过SMBus读取对应RAM地址即可。

SMBus是I2C的一种变种,MLX90614默认7位从机地址是0x5A,写操作地址0xB4,读操作地址0xB5。温度结果以16位无符号数存于RAM中,单位是0.02℃/LSB,数据范围从-40℃到+125℃,分辨率约0.02℃。读取时要特别注意:MLX90614的读写时序与EEPROM类I2C不同,读RAM必须用“重复起始条件”的方式,先发命令地址再重新发读地址,而不是一次写地址后直接连续读。

2.2 STC15没有硬件SMBus时,怎样用IO口模拟I2C

STC15F2K60S2属于增强型51单片机,但它并没有在片上集成I2C/SMBus外设。所以在项目中我们直接用两个普通IO口模拟I2C协议。与AT89C52不同的是,STC15的IO口默认为准双向口,但也可以配置为推挽输出,这会对I2C时序的边沿速率产生明显影响。建议将SCL、SDA设置为推挽输出,同时在外部加上4.7kΩ上拉电阻,这样既保证上升沿足够快,又保留开漏特性。

模拟I2C的四个基础动作是起始、停止、写字节、读字节。起始条件定义是SCL为高电平时,SDA产生一个下降沿;停止条件则是SCL为高电平时,SDA产生一个上升沿。数据位在SCL高电平期间必须保持稳定,所以标准写字节流程应当是:先把SDA设为目标电平,再拉高SCL,此时从机采样;然后拉低SCL,再切换下一位。

sbit I2C_SCL = P2^0; sbit I2C_SDA = P2^1; void I2C_Delay(void) { unsigned char i = 10; while (i--); } void I2C_Start(void) { I2C_SDA = 1; I2C_SCL = 1; I2C_Delay(); I2C_SDA = 0; // SCL高电平期间SDA拉低,产生起始条件 I2C_Delay(); I2C_SCL = 0; }

这里的I2C_Delay用来控制SCL半周期。MLX90614的SMBus模式最高通信速率通常按100kHz考虑,也就是SCL高电平和低电平时间不能短于4.7μs。在STC15F2K60S2默认11.0592MHz主频下,一个空循环大约能提供几微秒的延时,具体值可以通过示波器测量SCL调整。如果SCL太快,传感器可能不回ACK,或者读回的数据随机跳变。

2.3 LCD1602为什么不能直接挂在同一条I2C总线上

很多人会看到LCD1602也有I2C转接板,就误以为LCD1602本身是I2C设备。实际项目里使用的LCD1602模块是并行接口:8根数据线加RS、RW、E三个控制线。I2C转接板是把一块PCF8574挂在I2C总线上,再由PCF8574的8个IO口去驱动LCD1602。而这个原工程文件里的LCD1602.c和LCD1602.h是直接操作P0口或P2口驱动的,走的不是I2C。

这在硬件连接上是个关键判断:MLX90614占用SCL、SDA两根线,LCD1602占用一组并行IO口。如果盲目给LCD1602加I2C转接板,反而会让项目从“一个I2C设备加并行显示”变成“两个I2C设备”,此时还要处理地址冲突和PCF8574驱动,复杂度更高。对于这个项目,保持原设计是最直接的做法:MLX90614用软I2C读到温度值,LCD1602用并行方式显示字符。

2.4 硬件接线与引脚分配

下面是一组在实验板上比较容易实现的接线方式,其中LCD1602数据线选择P0口,便于直接并联:

模块引脚连接目标说明
MLX90614VIN3.3V/5V 电源传感器供电范围3.3V~5V,5V供电时SDA上拉不可省略
MLX90614SDAP2.1软件模拟SDA,外部接4.7kΩ上拉到VCC
MLX90614SCLP2.0软件模拟SCL,外部接4.7kΩ上拉到VCC
LCD1602VSSGND电源地
LCD1602VDD5VLCD背光/逻辑电源
LCD1602VO10kΩ电位器中点调节对比度
LCD1602RSP1.00写命令,1写数据
LCD1602RWP1.10写,1读,本工程强制为0
LCD1602EP1.2使能脉冲
LCD1602D0~D7P0.0~P0.78位并行数据

如果实验板上的LCD1602是5V供电,而MLX90614传感器模块是3.3V供电,不建议直接把两个电源接到同一路3.3V,否则LCD背光电流会把传感器电源拉出纹波。更稳妥的做法是:LCD1602用5V供电,MLX90614用板载3.3V供电,I2C上拉电阻接到传感器侧的3.3V,此时SCL/SDA高电平约3.3V,STC15也能正常识别。

3. 模块化驱动实现:从寄存器读取到LCD显示

3.1 工程文件拆分逻辑

这个项目的代码结构非常典型:main.c负责初始化与主循环,Mlx90614.c负责读温度寄存器,LCD1602.c负责屏幕显示,I2C.c提供底层通信,Delay.c统一管理延时。头文件里除了寄存器定义,还会把IO口引脚定义集中起来,方便换板子时快速改线。

STC15F2K60S2.H是STC官方头文件,里面包含了SFR位定义、中断向量号和常用寄存器结构。和标准reg52.h相比,STC15的头文件多了AUXR、P0M0/P0M1等IO模式寄存器。使用这个头文件后,可以直接操作P2、P0等端口,但要注意如果单片机的IO口被配置成高阻输入模式,LCD数据口会读取异常。

3.2 I2C底层:读单字节函数

MLX90614读取RAM时,需要先发送从机写地址和寄存器地址,然后重复起始,再发送从机读地址。下面的代码只读一个RAM寄存器,返回值就是16位原始温度数据:

unsigned int Mlx90614_ReadRam(unsigned char reg_addr) { unsigned char low = 0, high = 0; I2C_Start(); I2C_WriteByte(0xB4); // 从机写地址:0x5A左移一位,末尾写标志0 I2C_WriteByte(reg_addr); // RAM寄存器地址,例如0x07是TOBJ1 I2C_Start(); // 重复起始条件,这是SMBus读的关键 I2C_WriteByte(0xB5); // 从机读地址:0x5A左移一位,末尾读标志1 low = I2C_ReadByte(); // 第一字节是温度低字节,主机返回ACK I2C_Ack(0); // 第二字节是温度高字节,主机返回NACK表示读结束 high = I2C_ReadByte(); I2C_Stop(); return ((unsigned int)high << 8) | low; }

代码里的I2C_WriteByte返回ACK状态,但很多简化驱动会忽略它。实际调试时建议保留返回值,如果从机没有应答,立刻停止后续处理。I2C_Ack(0)这一句决定了SMBus读时序的完整性:读第二个字节前,主机需要给从机一个非应答信号,从机才会停止发送。如果这里漏掉NACK,MLX90614会认为主机还要继续读,导致I2C总线时序错乱。

3.3 把原始温度值换算成摄氏度

MLX90614的RAM寄存器0x07存放TOBJ1温度,分辨率0.02℃/LSB。比如读回0x4E20,也就是十进制20000,乘以0.02后是400.00K,减去273.15K,得到126.85℃。浮点运算在51上不是不能用,只是Keil C51的浮点库会多占几KB Flash,主频低时计算也偏慢。项目里更常用的做法是全部用整数计算:

int Mlx90614_GetObjectTempC10(void) { unsigned int raw = Mlx90614_ReadRam(0x07); int temp_c10; temp_c10 = (int)((raw * 2 - 27315) / 10); return temp_c10; // 返回温度*10,避免浮点数 }

这里temp_c10的单位是0.1℃,显示时拆成整数部分和小数部分即可。raw * 2 / 10 - 2731.5被改写成(raw * 2 - 27315) / 10,避免了浮点运算。如果环境温度在零下,raw对应的开尔文值小于27315时,整数除法在C51里会向零截断,显示逻辑里最好单独处理负号,否则零下温度会变成不准确的数值。

3.4 LCD1602并行驱动与主循环

LCD1602初始化时,需要按数据手册发送一组命令:0x38设置8位双行显示模式,0x0C关闭光标显示,0x01清屏,0x06写入后地址自动加一。下面是写命令函数:

void LCD1602_WriteCmd(unsigned char cmd) { LCD_RS = 0; // RS=0表示写命令 LCD_RW = 0; LCD_DATA = cmd; LCD_EN = 1; // 使能脉冲,高电平有效 Delay10us(); LCD_EN = 0; Delay10us(); }

LCD1602的E引脚是一个宽度至少几百纳秒的使能脉冲。单片机在E为高电平时把数据锁存进LCD控制器,然后E拉低。很多新手只写LCD_EN = 1后立刻置0,速度太快时LCD控制器来不及采样,表现就是第一行有字、第二行乱码,或者字符偏移。常规做法是每次操作后加一个微秒级延时,初始化之后再加几个毫秒的等待时间。

主循环内容很直接:循环读取温度,拼接字符串,更新LCD显示。

void main(void) { int temp_c10; char buf[16]; LCD1602_Init(); LCD1602_Clear(); while (1) { temp_c10 = Mlx90614_GetObjectTempC10(); sprintf(buf, "Obj:%d.%d C", temp_c10 / 10, temp_c10 % 10); LCD1602_SetCursor(0, 0); LCD1602_WriteString(buf); DelayMs(100); } }

这里的temp_c10 / 10是整数部分,temp_c10 % 10是小数部分。如果温度是负值,比如-12.3℃,C语言的整数除法和取余结果会把负号单独放在整数部分,输出变成“-12.-3”,所以生产级代码里要单独做符号处理。DelayMs(100)控制刷新频率,MLX90614的测量响应大约是几十毫秒,100ms刷新既能避免LCD频繁闪烁,也不会让人觉得数值卡顿。

4. 移植到STC15时的时序坑与参数调整

4.1 时钟差异导致延时不对

STC15F2K60S2是1T增强型内核,执行一条普通指令的机器周期是1个时钟周期,而传统AT89C52是12T,12个时钟周期才是1个机器周期。如果沿用AT89C52时代的DelayMs代码,在STC15上实际延时会缩小到原来的约1/12。LCD1602的E使能脉冲和I2C的SCL时序都会受到影响。

解决方法是先用STC-ISP软件把主频明确配置为11.0592MHz,并在Delay.c里用_nop_()或定时器做延时校准。对于I2C延时,建议直接用示波器看SCL波形,目标频率80kHz左右即可,不需要精确到标准100kHz。

void I2C_Delay(void) { unsigned char i = 12; while (i--) { _nop_(); // STC15 1T模式下,单个_nop_约0.09us _nop_(); } }

这段代码在11.0592MHz下大约能提供2μs左右的延时,加上while循环本身的跳转开销,SCL半周期大约能落到5μs附近。如果读回的温度值在高频振荡中跳变,就把i从12改成16或20,别急着怀疑传感器损坏。

4.2 MLX90614的SMBus时序边界

MLX90614的数据手册里明确写了SMBus接口,虽然兼容I2C,但规约上对最小SCL低电平时间有要求,常见标准是4.7μs。问题在于STC15的IO推挽输出能力很强,SDA下降沿非常陡峭,加上PCB走线或杜邦线的寄生电容,过冲可能让从机误判。

实测中常见的错误是:杜邦线超过20cm时,SCL出现明显振铃,MLX90614偶尔返回0xFFFF,或者连续两次读取结果差几十度。处理办法有三个:一是把SDA、SCL的上拉电阻从4.7kΩ换成10kΩ,减小灌电流;二是在SCL与GND之间并联一个几十pF的小电容,把边沿放缓;三是让I2C_Delay加大到15μs级别。不要忘记,MLX90614模块上如果已经带了上拉电阻,外部再接4.7kΩ会造成并联后阻值偏低,反而让信号更难看。

4.3 LCD1602的对比度与电源噪声

LCD1602的VO引脚需要接一个电位器到GND,典型值为10kΩ。调整电位器使屏幕上出现清晰但不“全黑”的字符块。如果VO悬空,LCD1602很可能不显示任何内容;如果VO直接接地,对比度最高,屏幕上会出现两行方块,字符反而看不清。

另一个容易忽略的问题是LCD1602的背光电流。STC15F2K60S2的工作电压可以到5V,但MLX90614传感器如果和LCD背光共用一个5V电源,LCD背光每次切换字符时的电流波动会被传感器捕捉到,表现为温度小数点后一位在0.1~0.2℃之间跳动。建议传感器电源单独用3.3V稳压,主芯片和LCD用5V;如果只有一个5V电源,可以在MLX90614的VIN前串联一个10Ω电阻,再并联10μF电容,做一个简单RC滤波。

4.4 寄存器地址与PEC校验

寄存器地址名称含义典型值
0x06TA环境温度出厂校准值
0x07TOBJ1物体温度第一通道0x04D1对应100.00℃
0x08TOBJ2物体温度第二通道与0x07相同,取决于物体温度范围
0x0EEmissivity发射率(EEPROM)0x04D1对应1.00
0x00Configuration配置寄存器含扩展温度范围位

上面表格中,0x07和0x08都是热像传感器输出的物体温度数据。默认量程下两者一致;如果把配置寄存器的扩展温度模式打开,0x07可以测量更低或更高温度,0x08则保留原量程。读取时优先读0x07,除非硬件规格书说明需要使用TOBJ2。

SMBus数据包还包含一个PEC校验字节,也就是CRC-8。简化驱动不读PEC也能工作,但工业环境里建议启用。方法是在读完高字节后继续读第三字节,然后通过查表计算CRC,与第三字节比较。这个项目是课程设计或原型验证阶段,先不读PEC不影响功能,只要知道“读取值偶尔跳变可能与PEC被忽略有关”即可。

4.5 故障现象定位

  • 读回0xFFFF:I2C通信中断,检查SCL/SDA是否接反,地址是否正确。
  • 读回0x0000:传感器上电时间不足,或供电电压过低,MLX90614需要约50ms稳定时间。
  • 环境温度恒定,物体温度始终比实际低:镜头前方有塑料遮挡,红外辐射被衰减。
  • LCD1602显示乱码:数据线接错、E脉冲时序太快,或P0口没有接上拉电阻。
  • 高温物体移开后温度值回落很慢:这是MLX90614的响应特性,阅读数据手册中的响应时间参数后结合软件滤波处理。

5. 从温度值到稳定显示的进阶处理

5.1 用滑动滤波消除手持抖动的影响

MLX90614测量的是视场角内所有物体的平均辐射温度。手持测量时,手部抖动会导致视场忽而对准额头、忽而对准背景,读数跳动幅度可能达到1℃以上。最简单的办法是在主循环里做连续多次采样,去掉最高最低值后取平均。

#define SAMPLE_COUNT 8 unsigned long sum = 0; unsigned char i; unsigned int raw; unsigned int max_raw = 0, min_raw = 0xFFFF; for (i = 0; i < SAMPLE_COUNT; i++) { raw = Mlx90614_ReadRam(0x07); if (raw > max_raw) max_raw = raw; if (raw < min_raw) min_raw = raw; sum += raw; DelayMs(20); } sum = sum - max_raw - min_raw; raw = sum / (SAMPLE_COUNT - 2);

这种做法相当于中值平均滤波,能有效剔除一次由干扰造成的异常跳变。注意SAMPLE_COUNT - 2在极端情况下是去掉一个最大值和一个最小值后的有效样本数,如果只有两个样本则会出现除零,实际使用应保证样本数不少于4。

5.2 用整数值判断超温报警

很多测温枪场景需要高于某个阈值就点亮LED或驱动蜂鸣器。这类判断不要用浮点数比较,直接用temp_c10和阈值比较更干净:

#define TEMP_ALERT_C10 375 // 37.5℃ if (temp_c10 >= TEMP_ALERT_C10) { ALARM_PIN = 0; // 低电平驱动蜂鸣器 } else { ALARM_PIN = 1; }

这里的375表示37.5℃。因为temp_c10是温度乘以10后的整数,判断逻辑不会因为编译器的浮点舍入方式不同而产生边界差异。同时要注意,MLX90614读到的物体温度受发射率影响,人体皮肤发射率约0.98,如果直接测金属表面,发射率可能低于0.5,测到的温度会明显偏低。

5.3 发射率补偿与单点校准

MLX90614的EEPROM中有一个发射率寄存器,默认值对应发射率1.00。真实物体发射率小于1时,传感器输出的TOBJ温度会低于真实温度。标准做法是把发射率寄存器改写为目标物体的发射率,但EEPROM写操作有寿命限制,不建议在每次上电时都写。

更稳妥的方案是在软件层做单点线性校准:用一个接触式温度探针同时测量目标物体,记录当前传感器读数和真实温度,得到偏差值offset。读取后直接加上这个偏差:

int temp_c10_calibrated; temp_c10_calibrated = temp_c10 + 15; // 假设实测偏低了1.5℃

这种校准方式只适用于固定测量目标,比如芯片表面或者恒定距离下的体温检测。如果测量距离、目标材质频繁变化,单一偏移量无法覆盖所有场景,此时就有必要改动发射率寄存器,或者增加一个近距离传感器用来判断目标是否进入有效测量范围。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询