一、适用场景
适用场景:人体体温无接触测量、环境温度检测、智能家居(空调/暖风机自动控制)、物联网传感节点、医疗护理设备、智能门禁体温检测、便携式红外测温仪、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/BV1tuWPzKErA/?spm_id_from=333.1387.homepage.video_card.click&vd_source=f7dfe1b14f260b9cc3a146d2dbfd0719

https://www.bilibili.com/video/BV1KVWPzeE8F/?spm_id_from=333.1387.homepage.video_card.click&vd_source=f7dfe1b14f260b9cc3a146d2dbfd0719

https://www.bilibili.com/video/BV14GWPzPEuL/?spm_id_from=333.1387.homepage.video_card.click

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐