MLX90614非接触式红外测温传感器
基于STM32读取MLX90614非接触式红外测温传感器(附源码 & 原理讲解)
前言
在嵌入式开发中,非接触式测温是一个非常经典的应用场景。MLX90614 是一款集成了红外热电堆传感器和信号处理芯片的高精度测温模块,通信方式为 SMBus(兼容 I2C),非常适合配合 STM32 使用。
本文将详细介绍如何使用 STM32F103 标准库,通过模拟 I2C 时序驱动 MLX90614 传感器,并将测得的环境温度和物体温度显示在 OLED 屏幕上。
1. 硬件准备
-
主控芯片:STM32F103C8T6 (最小系统板)
-
传感器:MLX90614-DCI 或 BAA (GY-906模块)
-
显示屏:0.96寸 OLED (I2C接口)
-
下载器:ST-Link 或 DAP-Link
-
软件环境:Keil MDK5
硬件接线示意
| MLX90614 (GY-906) | STM32F103 | 说明 |
| VIN | 3.3V / 5V | 根据模块型号供电,建议3.3V |
| GND | GND | 共地 |
| SCL | PB10 | 模拟I2C时钟线 (可自定义) |
| SDA | PB11 | 模拟I2C数据线 (可自定义) |
注意:MLX90614 的 SMBus 协议要求总线空闲时为高电平,因此 SCL 和 SDA 建议接 4.7kΩ 上拉电阻(大多数 GY-906 模块内部已集成上拉,直接接 IO 即可)。
2. MLX90614 协议与原理
MLX90614 内部包含两个主要的存储区域:
-
EEPROM:存储配置参数(发射率等)。
-
RAM:存储只读的测量数据。
我们要读取的温度数据在 RAM 中:
-
环境温度 (Ta):RAM 地址
0x06 -
物体温度 (Tobj1):RAM 地址
0x07
通信协议 (SMBus)
虽然官方文档称其为 SMBus,但在物理层面上它与 I2C 非常相似。唯一的区别在于时序要求更严格,且读取数据时通常不需要“ACK”信号结束,或者使用特定的 PEC(数据包错误校验)。在 STM32 上,使用 软件模拟 I2C (Software I2C) 是最稳定、移植性最好的方案。
读取温度的公式:
$$ ext{Temp} = ( ext{Data} imes 0.02) - 273.15$$
其中 Data 是从寄存器读取出的 16位 原始数据。
3. 代码实现
工程结构如下(参考上传的文件结构):
-
Hardware/MyI2C.c: 底层 I2C 模拟时序 -
Hardware/MLX90614.c: 传感器驱动层 -
User/main.c: 主逻辑
3.1 底层 I2C 驱动 (MyI2C.c)
为了保证稳定性,我们使用 GPIO 翻转来模拟 I2C 时序。
#include "stm32f10x.h"
#include "Delay.h"
/* 引脚定义,根据实际修改 */
#define SCL_PORT GPIOB
#define SCL_PIN GPIO_Pin_10
#define SDA_PORT GPIOB
#define SDA_PIN GPIO_Pin_11
#define I2C_SCL_High() GPIO_SetBits(SCL_PORT, SCL_PIN)
#define I2C_SCL_Low() GPIO_ResetBits(SCL_PORT, SCL_PIN)
#define I2C_SDA_High() GPIO_SetBits(SDA_PORT, SDA_PIN)
#define I2C_SDA_Low() GPIO_ResetBits(SDA_PORT, SDA_PIN)
#define I2C_SDA_Read() GPIO_ReadInputDataBit(SDA_PORT, SDA_PIN)
void MyI2C_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; // 开漏输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = SCL_PIN | SDA_PIN;
GPIO_Init(SCL_PORT, &GPIO_InitStructure);
I2C_SCL_High();
I2C_SDA_High();
}
void MyI2C_Start(void)
{
I2C_SDA_High();
I2C_SCL_High();
Delay_us(4); // 延时保证时序
I2C_SDA_Low();
Delay_us(4);
I2C_SCL_Low();
}
void MyI2C_Stop(void)
{
I2C_SDA_Low();
I2C_SCL_High();
Delay_us(4);
I2C_SDA_High();
Delay_us(4);
}
void MyI2C_SendByte(uint8_t Byte)
{
uint8_t i;
for(i=0; i<8; i++)
{
if(Byte & (0x80 >> i))
I2C_SDA_High();
else
I2C_SDA_Low();
Delay_us(2);
I2C_SCL_High();
Delay_us(4);
I2C_SCL_Low();
Delay_us(2);
}
}
uint8_t MyI2C_ReceiveByte(void)
{
uint8_t i, Byte = 0x00;
I2C_SDA_High(); // 释放总线,准备读取
for(i=0; i<8; i++)
{
I2C_SCL_High();
Delay_us(4);
if(I2C_SDA_Read())
{
Byte |= (0x80 >> i);
}
I2C_SCL_Low();
Delay_us(2);
}
return Byte;
}
void MyI2C_SendAck(uint8_t AckBit)
{
if(AckBit)
I2C_SDA_High();
else
I2C_SDA_Low();
Delay_us(2);
I2C_SCL_High();
Delay_us(4);
I2C_SCL_Low();
Delay_us(2);
}
3.2 MLX90614 驱动层 (MLX90614.c)
这里实现了 SMBus 的读取协议:Start -> Slave Address(W) -> Command -> Repeated Start -> Slave Address(R) -> Read Low Byte -> Read High Byte -> PEC(Ignore) -> Stop.
#include "MLX90614.h"
#include "MyI2C.h"
#include "Delay.h"
#define MLX90614_ADDRESS 0x00 // 默认通用地址,或者 0x5A<<1
/*
* 读取MLX90614的数据
* Address: RAM地址 (0x06=环境, 0x07=物体)
* 返回值: 转换后的温度值(float)
*/
float MLX90614_ReadTemp(uint8_t Address)
{
uint8_t DataL, DataH, PEC;
uint16_t Data;
float Temp;
MyI2C_Start();
MyI2C_SendByte(0xB4); // 发送从机地址+写位 (0x5A << 1 | 0) = 0xB4
MyI2C_ReceiveAck(); // 这里的Ack处理可以简化,MLX通常会应答
MyI2C_SendByte(Address); // 发送寄存器地址
MyI2C_ReceiveAck();
// Repeated Start (重复起始条件)
MyI2C_Start();
MyI2C_SendByte(0xB5); // 发送从机地址+读位 (0x5A << 1 | 1) = 0xB5
MyI2C_ReceiveAck();
DataL = MyI2C_ReceiveByte(); // 读取低8位
MyI2C_SendAck(0); // 给应答 (ACK)
DataH = MyI2C_ReceiveByte(); // 读取高8位
MyI2C_SendAck(0); // 给应答 (ACK)
PEC = MyI2C_ReceiveByte(); // 读取校验位 (可选)
MyI2C_SendAck(1); // 给非应答 (NACK),结束传输
MyI2C_Stop();
// 数据合成
Data = (DataH << 8) | DataL;
// 温度计算公式: T = Data * 0.02 - 273.15
Temp = (float)Data * 0.02 - 273.15;
return Temp;
}
void MLX90614_Init(void)
{
MyI2C_Init();
}
3.3 主函数 (main.c)
在主循环中不断读取并显示。建议不要读取过快,MLX90614 的刷新率通常在 Hz 级别。
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
#include "MLX90614.h"
#include // 用于sprintf
int main(void)
{
/* 模块初始化 */
OLED_Init();
MLX90614_Init();
/* 变量定义 */
float Temp_Obj; // 物体温度
float Temp_Amb; // 环境温度
char String[16];
OLED_ShowString(1, 1, "MLX90614 Test");
while (1)
{
/* 读取数据 */
Temp_Amb = MLX90614_ReadTemp(0x06); // 读取环境温度
Temp_Obj = MLX90614_ReadTemp(0x07); // 读取物体温度
/* 串口打印或OLED显示 */
// 显示物体温度
sprintf(String, "Obj: %.2f C", Temp_Obj);
OLED_ShowString(2, 1, String);
// 显示环境温度
sprintf(String, "Amb: %.2f C", Temp_Amb);
OLED_ShowString(3, 1, String);
Delay_ms(200); // 延时刷新
}
}

4. 常见问题排查 (FAQ)
-
读出的温度一直是 1000 多度或者 -273度?
-
原因:I2C 通信失败,读取到的
Data全为0xFF或0x00。 -
解决:检查接线是否松动;检查 SDA/SCL 是否接反;确保 SCL/SDA 初始化为开漏输出模式且有上拉电阻。
-
-
数据跳动很大?
-
原因:电源纹波大或者传感器视场角(FOV)内有干扰。
-
解决:在传感器 VCC 和 GND 之间加一个 0.1uF 的滤波电容。
-
-
为什么地址是 0x00?
-
MLX90614 出厂默认地址是 0x5A,但在 SMBus 协议中,使用地址 0x00 可以作为“广播地址”访问总线上的任意设备(前提是总线上只有一个设备)。为了代码通用性,如果不修改地址,可以使用 0x00 进行读写。
-
5. 总结
MLX90614 是一款非常易用的红外测温模块。通过本文的 STM32 软件 I2C 实现,我们可以轻松获取温度数据。相比于模拟电压输出的传感器,数字接口的 MLX90614 抗干扰能力更强,精度更高。









