【嵌入式协议外设篇】-MLX90614 红外测温模块
本文介绍了MLX90614红外测温模块的应用与实现方法。该模块适用于无接触体温测量、环境监测、智能家居控制等场景,通过检测物体红外辐射实现温度测量。文章详细说明了硬件连接方式(VCC、GND、SCL、SDA接口)和STM32的I²C通信实现,提供了标准库和HAL库两种编程示例。核心代码包括SMBus通信协议实现、温度数据读取及CRC校验等关键函数,最终将原始数据转换为摄氏度输出。该方案可作为嵌入式
一、适用场景
适用场景:人体体温无接触测量、环境温度检测、智能家居(空调/暖风机自动控制)、物联网传感节点、医疗护理设备、智能门禁体温检测、便携式红外测温仪、DIY 小项目(热感应玩具 / 智能风扇 / 热源跟踪小车)等。MLX90614 是学习 I²C 通信、温度采集、浮点数据解析的经典入门外设。

二、器材清单
MLX90614 红外测温模块 ×1
STM32F103VET6 ×1
若干杜邦线(母对母/公对公,根据引脚)
5V/3.3V电源
三、工作原理(要点)
物体表面会辐射红外线,MLX90614 接收后通过热电堆感应,结合内部算法计算出目标温度(To)与模块自身温度(Ta)。
四、接线示意
VCC → +5V
GND → GND
标准库
PB6 →SCL
PB7 →SDA
HAL库
PB8 →SCL
PB9 →SDA
五、示例代码
//标准库
extern char utemp;
char nowtemp[10];
int count=0;//记录人数
float valuenow;//记录温度
int main (void)
{
SMBus_Init(); //SMBus初始化
initUart(); //串口初始化
pwm_init(); //pwm初始化
initGPIO(); //gpio初始化
SysTick_Config(72*1000);
TIM_SetCompare1(TIM3, 500); //将舵机角度设置0度
TIM_SetCompare2(TIM3, 500); //将舵机角度设置0度
while(1)
{
SysTick_Delay_ms(1000); //1s延时,减慢温度的检测速度
valuenow =SMBus_ReadTemp(); //检测温度
sprintf(nowtemp,"%f\n",valuenow);
Usart_SendString(USART1,nowtemp);
while(!USART_GetFlagStatus(USART1,USART_FLAG_TXE));
}
return 0;
}
#include"MLX90614.h"
#include"stm32f10x.h"
void SMBus_Delay(u16 time)
{
u16 i, j;
for (i=0; i<4; i++)
{
for (j=0; j<time; j++);
}
}
void SMBus_StartBit(void)//
{
SMBUS_SDA_H(); //给SDA高电平
SMBus_Delay(5); //延时,给SDA稳定一段时间
SMBUS_SCK_H(); //给SCK高电平
SMBus_Delay(5); //延时,给SCK稳定一段时间
SMBUS_SDA_L(); //给SDA低电平
SMBus_Delay(5); //延时,给SDA稳定一段时间,在SCK为高电平时,检测到SDA由高电平到低电平的状态翻转表示通信开始(下降沿)
SMBUS_SCK_L(); //给SCK低电平
SMBus_Delay(5); //延时,给SCK稳定一段时间
}
void SMBus_StopBit(void)
{
SMBUS_SCK_L(); //给SCK低电平
SMBus_Delay(5); //延时,给SCK稳定一段时间
SMBUS_SDA_L(); //给SDA低电平
SMBus_Delay(5); //延时,给SDA稳定一段时间
SMBUS_SCK_H(); //给SCK高电平
SMBus_Delay(5); //延时,给SCK稳定一段时间,在SCK为高电平时,检测到SDA由高电平到低电平的状态翻转表示通信开始(下降沿)
SMBUS_SDA_H(); //给SDA高电平
SMBus_Delay(5); //延时,给SDA稳定一段时间
}
void SMBus_SendBit(u8 bit_out)
{
if(bit_out==0) //判断发送的数据是1还是0,从而选择拉高还是拉低SDA的电平
{
SMBUS_SDA_L();
}
else
{
SMBUS_SDA_H();
}
SMBus_Delay(2); //延时,给SDA稳定一段时间
SMBUS_SCK_H(); //给SCK高电平
SMBus_Delay(6); //延时,给SCK稳定一段时间
SMBUS_SCK_L(); //给SCK低电平,防止下次调用干扰
SMBus_Delay(3); //延时,给SCK稳定一段时间
return;
}
u8 SMBus_ReceiveBit(void)
{
u8 Ack_bit;
SMBUS_SDA_H(); //给SDA高电平,引脚靠外部电阻上拉,当作输入
SMBus_Delay(2); //延时,给SDA稳定一段时间
SMBUS_SCK_H(); //给SCK高电平
SMBus_Delay(5); //延时,给SCK稳定一段时间
if (SMBUS_SDA_PIN()) //读取此时SDA的电平,从而选择ACK
{
Ack_bit=1;
}
else
{
Ack_bit=0;
}
SMBUS_SCK_L(); //给SCK低电平,防止下次调用干扰
SMBus_Delay(3); //延时,给SCK稳定一段时间
return Ack_bit;
}
u8 SMBus_SendByte(u8 Tx_buffer)
{
u8 Bit_counter;
u8 Ack_bit;
u8 bit_out;
for(Bit_counter=8; Bit_counter; Bit_counter--) //一个字节占八位,因此循环八次输出
{
if (Tx_buffer&0x80) //判断当前最高位是否为1,是的话将最高位的标志位设置为1,反之设置为0
{
bit_out=1;
}
else
{
bit_out=0;
}
SMBus_SendBit(bit_out); //把最高位的标志位发送出去
Tx_buffer<<=1; //移一位,把上一个最高位的下一位设置为当前最高位,循环8次,每次都发最高位,就可把一个字节发出去了
}
Ack_bit=SMBus_ReceiveBit(); //获取从机的应答状态
return Ack_bit;
}
u8 SMBus_ReceiveByte(u8 ack_nack)
{
u8 RX_buffer;
u8 Bit_Counter;
for(Bit_Counter=8; Bit_Counter; Bit_Counter--) //一个字节占八位,因此循环八次输出
{
if(SMBus_ReceiveBit()) //读取SDA的电平来判断当前的二进制值
{
RX_buffer <<= 1; //左移一位,将最低为空出来
RX_buffer |=0x01; //如果Ack_bit=1,把收到应答信号1与0000 0001 进行或运算,确保为1
}
else
{
RX_buffer <<= 1; //左移一位,将最低为空出来
RX_buffer &=0xfe; //如果Ack_bit=1,把收到应答信号0与1111 1110 进行与运算,确保为0
}
}
SMBus_SendBit(ack_nack); //发送主机的应答状态
return RX_buffer;
}
void SMBus_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SMBUS_PORT, ENABLE);
GPIO_InitStructure.GPIO_Pin = SMBUS_SCK | SMBUS_SDA;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(SMBUS_PORT, &GPIO_InitStructure);
SMBUS_SCK_H();
SMBUS_SDA_H();
}
u8 PEC_Calculation(u8 pec[])
{
u8 crc[6]; //存放多项式
u8 BitPosition=47; //存放所有数据最高位,6*8=48 最高位就是47位
u8 shift;
u8 i;
u8 j;
u8 temp;
do
{
crc[5]=0; //多项式模式值0x00 00 00 00 01 07
crc[4]=0;
crc[3]=0;
crc[2]=0;
crc[1]=0x01;
crc[0]=0x07;
BitPosition=47; //将最大位位置设置为 47,6个元素,一个元素占8位,一共48位,再减去1位为47
shift=0; //将移位位置设置为 0
i=5;
j=0;
while((pec[i]&(0x80>>j))==0 && i>0) //求出多项式的1,将1的位置记录到BitPosition中
{
BitPosition--;
if(j<7) //对一个元素循环7次找到
{
j++;
}
else //在同一个元素循环7次都没找到1的话就换下一个元素继续找
{
j=0x00;
i--;
}
}
shift=BitPosition-8; //获取模式值的移位值
while(shift) //将多项式变为二进制
{
for(i=5; i<0xFF; i--)
{
if((crc[i-1]&0x80) && (i>0))
{
temp=1;
}
else
{
temp=0;
}
crc[i]<<=1;
crc[i]+=temp;
}
shift--;
}
for(i=0; i<=5; i++) //pec和crc之间进行异或
{
pec[i] ^=crc[i];
}
}while(BitPosition>8);
return pec[0];
}
u16 SMBus_ReadMemory(u8 slaveAddress, u8 command)
{
u16 data; //存放温度的变量,由DataH和DataL共同组成
u8 Pec; //PEC字节存储
u8 DataL=0; //存放温度的低八位
u8 DataH=0; //存放温度的高八位
u8 arr[6]; //已发送字节的缓冲区
u8 PecReg; //计算的PEC字节存储
u8 ErrorCounter; //与MLX90614通信的尝试次数
ErrorCounter=0x00;
slaveAddress <<= 1; //1-7位表示从机地址 从机地址左移一位,把读写位空出来
do
{
repeat:
SMBus_StopBit(); //发送停止位,防止上次的干扰
--ErrorCounter;
if(!ErrorCounter) //如果超过255次还没成功进行通信则认为通信失败,直接跳出循环
{
break;
}
SMBus_StartBit(); //发送起始位
if(SMBus_SendByte(slaveAddress)) //发送从机地址,由于上面进行了最低为的偏移,此时的最低位为0,表示接下来写命令
{
goto repeat; //失败,再次重复通讯
}
if(SMBus_SendByte(command)) //发送,温度数据所在的地址
{
goto repeat; //失败,再次重复通讯
}
SMBus_StartBit(); //再次发送起始位
if(SMBus_SendByte(slaveAddress+1)) //发送从机地址+1,此时的最低位为1,表示接下来读数据
{
goto repeat; //失败,再次重复通讯
}
DataL = SMBus_ReceiveByte(ACK); //读取低八位数据,主机必须发送ACK
DataH = SMBus_ReceiveByte(ACK); //读取高八位数据,主机必须发送ACK
Pec = SMBus_ReceiveByte(NACK); //读取PEC字节,主机必须发送 NACK
SMBus_StopBit(); //发送停止位
arr[5] = slaveAddress;
arr[4] = command;
arr[3] = slaveAddress+1;
arr[2] = DataL;
arr[1] = DataH;
arr[0] = 0;
PecReg=PEC_Calculation(arr); //进行CRC数据校验,获取PEC字节存储
}while(PecReg != Pec); //如果接收和计算的CRC相等,则退出
data = (DataH<<8) | DataL; //整合温度的高八位和低八位
return data;
}
float SMBus_ReadTemp(void)
{
float temp;
temp = SMBus_ReadMemory(SA, RAM_ACCESS|RAM_TOBJ1)*0.02-273.15;
return temp;
}
//HAL库
SMBus_Init();
/* USER CODE END 2 */
/* USER CODE BEGIN 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
valuenow =SMBus_ReadTemp(); //检测温度
sprintf(show,"%.3f\r\n",valuenow);
HAL_UART_Transmit(&huart1,show,strlen(show),HAL_MAX_DELAY);
HAL_Delay(1000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
#include "MLX90614.h"
void SMBus_Delay(u16 time)
{
u16 i, j;
for (i=0; i<4; i++)
{
for (j=0; j<time; j++);
}
}
void SMBus_StartBit(void)//
{
SMBUS_SDA_H(); //给SDA高电平
SMBus_Delay(5); //延时,给SDA稳定一段时间
SMBUS_SCK_H(); //给SCK高电平
SMBus_Delay(5); //延时,给SCK稳定一段时间
SMBUS_SDA_L(); //给SDA低电平
SMBus_Delay(5); //延时,给SDA稳定一段时间,在SCK为高电平时,检测到SDA由高电平到低电平的状态翻转表示通信开始(下降沿)
SMBUS_SCK_L(); //给SCK低电平
SMBus_Delay(5); //延时,给SCK稳定一段时间
}
void SMBus_StopBit(void)
{
SMBUS_SCK_L(); //给SCK低电平
SMBus_Delay(5); //延时,给SCK稳定一段时间
SMBUS_SDA_L(); //给SDA低电平
SMBus_Delay(5); //延时,给SDA稳定一段时间
SMBUS_SCK_H(); //给SCK高电平
SMBus_Delay(5); //延时,给SCK稳定一段时间,在SCK为高电平时,检测到SDA由高电平到低电平的状态翻转表示通信开始(下降沿)
SMBUS_SDA_H(); //给SDA高电平
SMBus_Delay(5); //延时,给SDA稳定一段时间
}
void SMBus_SendBit(u8 bit_out)
{
if(bit_out==0) //判断发送的数据是1还是0,从而选择拉高还是拉低SDA的电平
{
SMBUS_SDA_L();
}
else
{
SMBUS_SDA_H();
}
SMBus_Delay(2); //延时,给SDA稳定一段时间
SMBUS_SCK_H(); //给SCK高电平
SMBus_Delay(6); //延时,给SCK稳定一段时间
SMBUS_SCK_L(); //给SCK低电平,防止下次调用干扰
SMBus_Delay(3); //延时,给SCK稳定一段时间
return;
}
u8 SMBus_ReceiveBit(void)
{
u8 Ack_bit;
SMBUS_SDA_H(); //给SDA高电平,引脚靠外部电阻上拉,当作输入
SMBus_Delay(2); //延时,给SDA稳定一段时间
SMBUS_SCK_H(); //给SCK高电平
SMBus_Delay(5); //延时,给SCK稳定一段时间
if (SMBUS_SDA_PIN()) //读取此时SDA的电平,从而选择ACK
{
Ack_bit=1;
}
else
{
Ack_bit=0;
}
SMBUS_SCK_L(); //给SCK低电平,防止下次调用干扰
SMBus_Delay(3); //延时,给SCK稳定一段时间
return Ack_bit;
}
u8 SMBus_SendByte(u8 Tx_buffer)
{
u8 Bit_counter;
u8 Ack_bit;
u8 bit_out;
for(Bit_counter=8; Bit_counter; Bit_counter--) //一个字节占八位,因此循环八次输出
{
if (Tx_buffer&0x80) //判断当前最高位是否为1,是的话将最高位的标志位设置为1,反之设置为0
{
bit_out=1;
}
else
{
bit_out=0;
}
SMBus_SendBit(bit_out); //把最高位的标志位发送出去
Tx_buffer<<=1; //移一位,把上一个最高位的下一位设置为当前最高位,循环8次,每次都发最高位,就可把一个字节发出去了
}
Ack_bit=SMBus_ReceiveBit(); //获取从机的应答状态
return Ack_bit;
}
u8 SMBus_ReceiveByte(u8 ack_nack)
{
u8 RX_buffer;
u8 Bit_Counter;
for(Bit_Counter=8; Bit_Counter; Bit_Counter--) //一个字节占八位,因此循环八次输出
{
if(SMBus_ReceiveBit()) //读取SDA的电平来判断当前的二进制值
{
RX_buffer <<= 1; //左移一位,将最低为空出来
RX_buffer |=0x01; //如果Ack_bit=1,把收到应答信号1与0000 0001 进行或运算,确保为1
}
else
{
RX_buffer <<= 1; //左移一位,将最低为空出来
RX_buffer &=0xfe; //如果Ack_bit=1,把收到应答信号0与1111 1110 进行与运算,确保为0
}
}
SMBus_SendBit(ack_nack); //发送主机的应答状态
return RX_buffer;
}
void SMBus_Init()
{
// 替换时钟使能
__HAL_RCC_GPIOB_CLK_ENABLE(); // 替代RCC_APB2PeriphClockCmd
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 配置SCL引脚(PB8)
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; // 开漏输出模式
GPIO_InitStruct.Pull = GPIO_NOPULL; // 新增的HAL库参数
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 对应50MHz
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// 配置SDA引脚(PB9)
GPIO_InitStruct.Pin = GPIO_PIN_9;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// 初始化引脚电平保持原样
SMBUS_SCK_H();
SMBUS_SDA_H();
}
u8 PEC_Calculation(u8 pec[])
{
u8 crc[6]; //存放多项式
u8 BitPosition=47; //存放所有数据最高位,6*8=48 最高位就是47位
u8 shift;
u8 i;
u8 j;
u8 temp;
do
{
crc[5]=0; //多项式模式值0x00 00 00 00 01 07
crc[4]=0;
crc[3]=0;
crc[2]=0;
crc[1]=0x01;
crc[0]=0x07;
BitPosition=47; //将最大位位置设置为 47,6个元素,一个元素占8位,一共48位,再减去1位为47
shift=0; //将移位位置设置为 0
i=5;
j=0;
while((pec[i]&(0x80>>j))==0 && i>0) //求出多项式的1,将1的位置记录到BitPosition中
{
BitPosition--;
if(j<7) //对一个元素循环7次找到
{
j++;
}
else //在同一个元素循环7次都没找到1的话就换下一个元素继续找
{
j=0x00;
i--;
}
}
shift=BitPosition-8; //获取模式值的移位值
while(shift) //将多项式变为二进制
{
for(i=5; i<0xFF; i--)
{
if((crc[i-1]&0x80) && (i>0))
{
temp=1;
}
else
{
temp=0;
}
crc[i]<<=1;
crc[i]+=temp;
}
shift--;
}
for(i=0; i<=5; i++) //pec和crc之间进行异或
{
pec[i] ^=crc[i];
}
}while(BitPosition>8);
return pec[0];
}
u16 SMBus_ReadMemory(u8 slaveAddress, u8 command)
{
u16 data; //存放温度的变量,由DataH和DataL共同组成
u8 Pec; //PEC字节存储
u8 DataL=0; //存放温度的低八位
u8 DataH=0; //存放温度的高八位
u8 arr[6]; //已发送字节的缓冲区
u8 PecReg; //计算的PEC字节存储
u8 ErrorCounter; //与MLX90614通信的尝试次数
ErrorCounter=0x00;
slaveAddress <<= 1; //1-7位表示从机地址 从机地址左移一位,把读写位空出来
do
{
repeat:
SMBus_StopBit(); //发送停止位,防止上次的干扰
--ErrorCounter;
if(!ErrorCounter) //如果超过255次还没成功进行通信则认为通信失败,直接跳出循环
{
break;
}
SMBus_StartBit(); //发送起始位
if(SMBus_SendByte(slaveAddress)) //发送从机地址,由于上面进行了最低为的偏移,此时的最低位为0,表示接下来写命令
{
goto repeat; //失败,再次重复通讯
}
if(SMBus_SendByte(command)) //发送,温度数据所在的地址
{
goto repeat; //失败,再次重复通讯
}
SMBus_StartBit(); //再次发送起始位
if(SMBus_SendByte(slaveAddress+1)) //发送从机地址+1,此时的最低位为1,表示接下来读数据
{
goto repeat; //失败,再次重复通讯
}
DataL = SMBus_ReceiveByte(ACK); //读取低八位数据,主机必须发送ACK
DataH = SMBus_ReceiveByte(ACK); //读取高八位数据,主机必须发送ACK
Pec = SMBus_ReceiveByte(NACK); //读取PEC字节,主机必须发送 NACK
SMBus_StopBit(); //发送停止位
arr[5] = slaveAddress;
arr[4] = command;
arr[3] = slaveAddress+1;
arr[2] = DataL;
arr[1] = DataH;
arr[0] = 0;
PecReg=PEC_Calculation(arr); //进行CRC数据校验,获取PEC字节存储
}while(PecReg != Pec); //如果接收和计算的CRC相等,则退出
data = (DataH<<8) | DataL; //整合温度的高八位和低八位
return data;
}
float SMBus_ReadTemp(void)
{
float temp;
temp = SMBus_ReadMemory(SA, RAM_ACCESS|RAM_TOBJ1)*0.02-273.15;
return temp;
}
六、讲解视频
https://www.bilibili.com/video/BV14GWPzPEuL/?spm_id_from=333.1387.homepage.video_card.click
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)