2.12 MLX90614红外无接触测温传感器
MLX90614 系列模块是一组通用的红外测温模块。在出厂前该模块已进行校验及线性化,具有非接触、体积小、精度高,成本低等优点。被测目标温度和环境温度能通过单通道输出,并有两种输出接口,适合于汽车空调、室内暖气、家用电器、手持设备以及医疗设备应用等。测温方式可分为接触式和非接触式,接触式测温只能测量被测物体与测温传感器达到热平衡后的温度,所以响应时间长,且极易受环境温度的影响;而红外测温是根据被测物体的红外辐射能量来确定物体的温度,不与被测物体接触,具有影响动被测物体温度分布场,温度分辨率高、响应速度快、测温范围广、不受测温上限的限制、稳定性好等特点,所以我们选择mlx90614来作为红外测温模块。
单片机与mlx90614红外测温模块之间通信的方式是“类IIC”通信,意思就是通信方式跟IIC通信方式很像但又不是IIC,它有另外一个名字叫做SMBus。SMBus (System Management Bus)是 1995 年由 intel 公司提出的一种高效同步串行总线,SMBus 只有两根信号线:双向数据线和时钟信号线,容许 CPU 与各种外围接口器件以串行方式进行通信、交换信息,即可以提高传输速度也可以减小器件的资源占用,另外即使在没有SMBus 接口的单片机上也可利用软件进行模拟。
2.12.1 模块来源
采购链接:GY-906 MLX90614ESF BAA BCC DCI IR红外测温传感器模块温度采集
资料下载链接:https://pan.baidu.com/s/1AsEBvVCiNAvTKqTeGSA60w
提取码:g06n
2.12.2 规格参数
工作电压:4.5~5.5V
工作电流:1.3~2.5mA
工作电流:1.3~2.5mA
2.12.3 移植过程
我们的目标是在梁山派GD32F470上能够测量物体温度和环境温度的功能。首先要获取资料,查看数据手册应如何实现,再移植至我们的工程。
2.12.3.1 查看资料
MLX90614中有两个存储器,分别为EEPROM和RAM。
- MLX90614中共有32个字长为16位的EEPROM存储单元,其地址为000H—01FH。EEPROM中所有的寄存器都是可以通过SMBus进行读取,但只有部分寄存器是可以进行改写的(地址为0x00, 0x01, 0x02, 0x03, 0x04, 0x05*,0x0E, 0x0F, 0x19)。可改写部分如下图所示。因在出厂前模块已进行校验及线性化,所以我们直接使用默认参数,不需要修改。
名称 | 功能说明 | 地址 |
---|---|---|
Tomax | 测量物体温度上限设定 | 00H |
Tomin | 测量物体温度下限设定 | 01H |
PWMCTRL | 测量物体温度下限设定 | 02H |
Ta range | PWM控制 | 03H |
Emissivity correction conefficient | 环境温度范围设定 | 04H |
Config Register1 | 发射率校准系数:0.1-1 | 05H |
SMBus address(LSByte only) | 配置寄存器 | 0EH |
- MLX90614中总共有32个17位的RAM存储单元,用户不能通过RAM来写入数据,只能读取RAM中的部分存储单元读取16位存储数据。其采集的环境温度数据保存在地址06H存储单元中,采集的被测物体温度数据存储在07H存储单元中。因此运用存储在RAM地址中的数据,通过公式的计算,可以得到环境温度Ta及被测物体温度数据To。
时序说明
需要注意的是数据的低8位在前面,高8位在后。
器件地址(Slave Address)在数据手册中有说明,默认器件地址为0X5A;
命令(Command)是根据要控制的是RAM还是eeprom来决定一个字节中的BIT7BIT5。剩余的BIT4BIT0由要操控的地址决定。
例如,我要读取RAM的Ta温度数据,则命令组成见下表。其中RAM地址为000x_xxxx,Ta温度数据地址为0x06=0000_0110,只取低5位则为xxx0_0110。
PEC是一个多项式为X8+X2+X1+1的CRC-8校验数据。
在数据手册中举了两个例子。其中0xB4为器件地址左移一位后的值。
得到温度的原始数据后,根据数据手册的说明进行换算即可得到温度。
以上是手册中举了一个例子,如果读取到的温度数据是0X3AF7,其10进制为15095,将10进制数 除以50或者乘以0.02得到301.9,再减去273.15即可得到实际温度。
温度 = 温度原始数据 * 0.02 - 273.15
该温度换算公式对To和Ta都适用。
2.12.3.2 引脚选择
测温传感器 | 立创·梁山派 |
---|---|
VIN | 5V |
GND | GND |
SCL | PB9 |
SDA | PB8 |
2.12.3.3 移植至工程
移植步骤中的导入.c和.h文件与上一节相同,只是将.c和.h文件更改为bsp_mlx90614.c与bsp_mlx90614.h。见2.2.3.3 移植至工程。这里不再过多讲述。移植完成后面修改相关代码。
在文件bsp_mlx90614.c中,编写如下代码。
/********************************************************************************
* 文 件 名: bsp_mlx90614.c
* 版 本 号: 初版
* 修改作者: LC
* 修改日期: 2023年04月19日
* 功能介绍:
******************************************************************************
* 注意事项:
*********************************************************************************/
#include "bsp_mlx90614.h"
#include "bsp_usart.h"
#include "stdio.h"
#include "systick.h"
/******************************************************************
* 函 数 名 称:MLX90614_GPIO_Init
* 函 数 说 明:MLX90614的引脚初始化
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:MLX90614是5V,而且立创·梁山派开发板的引脚输出是3.3V,
* 故设置引脚模式时,必须设置为开漏模式
******************************************************************/
void MLX90614_GPIO_Init(void)
{
/* 使能时钟 */
rcu_periph_clock_enable(RCU_SCL);
rcu_periph_clock_enable(RCU_SDA);
/* 配置SCL为输出模式 */
gpio_mode_set(PORT_SCL,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,GPIO_SCL);
/* 配置为推挽输出 50MHZ */
gpio_output_options_set(PORT_SCL,GPIO_OTYPE_OD,GPIO_OSPEED_50MHZ,GPIO_SCL);
/* 配置SDA为输出模式 */
gpio_mode_set(PORT_SDA,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,GPIO_SDA);
/* 配置为推挽输出 50MHZ */
gpio_output_options_set(PORT_SDA,GPIO_OTYPE_OD,GPIO_OSPEED_50MHZ,GPIO_SDA);
}
/******************************************************************
* 函 数 名 称:IIC_Start
* 函 数 说 明:IIC起始时序
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void IIC_Start(void)
{
SDA_OUT();
SDA(1);
delay_us(5);
SCL(1);
delay_us(5);
SDA(0);
delay_us(5);
SCL(0);
delay_us(5);
}
/******************************************************************
* 函 数 名 称:IIC_Stop
* 函 数 说 明:IIC停止信号
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void IIC_Stop(void)
{
SDA_OUT();
SCL(0);
SDA(0);
SCL(1);
delay_us(5);
SDA(1);
delay_us(5);
}
/******************************************************************
* 函 数 名 称:IIC_Send_Ack
* 函 数 说 明:主机发送应答或者非应答信号
* 函 数 形 参:0发送应答 1发送非应答
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void IIC_Send_Ack(unsigned char ack)
{
SDA_OUT();
SCL(0);
SDA(0);
delay_us(5);
if(!ack) SDA(0);
else SDA(1);
SCL(1);
delay_us(5);
SCL(0);
SDA(1);
}
/******************************************************************
* 函 数 名 称:I2C_WaitAck
* 函 数 说 明:等待从机应答
* 函 数 形 参:无
* 函 数 返 回:0有应答 1超时无应答
* 作 者:LC
* 备 注:无
******************************************************************/
unsigned char I2C_WaitAck(void)
{
char ack = 0;
unsigned char ack_flag = 10;
SCL(0);
SDA(1);
SDA_IN();
delay_us(5);
SCL(1);
delay_us(5);
while( (SDA_GET()==1) && ( ack_flag ) )
{
ack_flag--;
delay_us(5);
}
if( ack_flag <= 0 )
{
IIC_Stop();
return 1;
}
else
{
SCL(0);
SDA_OUT();
}
return ack;
}
/******************************************************************
* 函 数 名 称:Send_Byte
* 函 数 说 明:写入一个字节
* 函 数 形 参:dat要写人的数据
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void Send_Byte(uint8_t dat)
{
int i = 0;
SDA_OUT();
SCL(0);//拉低时钟开始数据传输
for( i = 0; i < 8; i++ )
{
SDA( (dat & 0x80) >> 7 );
__nop();
SCL(1);
delay_us(5);
SCL(0);
delay_us(5);
dat<<=1;
}
}
/******************************************************************
* 函 数 名 称:Read_Byte
* 函 数 说 明:IIC读时序
* 函 数 形 参:无
* 函 数 返 回:读到的数据
* 作 者:LC
* 备 注:无
******************************************************************/
unsigned char Read_Byte(void)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
SCL(0);
delay_us(5);
SCL(1);
delay_us(5);
receive<<=1;
if( SDA_GET() )
{
receive|=1;
}
delay_us(5);
}
SCL(0);
return receive;
}
/******************************************************************
* 函 数 名 称:PEC_Calculation
* 函 数 说 明:PEC校验
* 函 数 形 参:pec要校验的数据地址 len校验的长度
* 函 数 返 回:校验后的值
* 作 者:LC
* 备 注:无
******************************************************************/
static unsigned char PEC_Calculation(unsigned char *dat , unsigned char len)
{
unsigned char i;
unsigned char crc=0;
while( len-- )
{
crc ^= *dat++;
for( i=0 ; i<8 ; i++ )
{
if( crc&0x80 )
{
crc = (crc<<1)^0x07;
}
else
{
crc = (crc<<1);
}
}
}
return crc;
}
/************************************************************
* 函数名称:MLX90615_Read
* 函数说明:读取MLX90615的温度
* 型 参:SlaveAddr = 器件地址 RegAddr = 要操作的寄存器地址
* 返 回 值:温度值
* 备 注: SlaveAddr = 0X5A默认器件地址
* RegAddr = 0X07读取被测量物体温度
* RegAddr = 0X06读取环境温度
*************************************************************/
#define CRC_VERIFY_ENABLE 1
float MLX90614_Read(unsigned char SlaveAddr, unsigned char RegAddr)
{
int i = 0;
unsigned char buff[3]={0}; //保存温度高低位与校验码
unsigned char arr[6]={0}; //校验数据使用
uint16_t temp = 0; //高低位整合数据保存
float T=0.0; //换算出的实际温度
IIC_Start();
Send_Byte((SlaveAddr<<1)|0);//写命令
I2C_WaitAck(); //等待响应
Send_Byte(RegAddr);//写入要操作的寄存器地址
I2C_WaitAck();
do{
delay_1ms(1);
IIC_Start(); //重新开始IIC
Send_Byte((SlaveAddr<<1)|1); //读命令
}while( I2C_WaitAck() );
buff[0] = Read_Byte(); //保存温度数据的低8位
IIC_Send_Ack(0); //主机发送应答
buff[1] = Read_Byte(); //保存温度数据的高8位
IIC_Send_Ack(0); //主机发送应答
buff[2] = Read_Byte(); //保存校验码
IIC_Send_Ack(1); //主机发送应答
IIC_Stop(); //停止时序
//使用校验
#if CRC_VERIFY_ENABLE
arr[0] = (SlaveAddr<<1); //器件地址+写
arr[1] = RegAddr; //命令
arr[2] = (SlaveAddr<<1)+1; //器件地址+读
arr[3] = buff[0]; //数据低8位
arr[4] = buff[1]; //数据高8位
if( PEC_Calculation(arr, 5) == buff[2] )//如果校验正确
{
temp = (short)(buff[1]<<8) | buff[0];//整合高低位
T = (temp * 0.02) - 273.15 ; //带入公式换算出实际温度
}
else
{
printf("ERROR CODE 4\r\n");
}
#endif
//不使用校验
#if !CRC_VERIFY_ENABLE
temp = (uint16_t)(buff[1]<<8) | buff[0];
T = (temp*0.02)-273.15 ;
#endif
return T;
}
在文件bsp_mlx90614.h中,编写如下代码。
#ifndef _BSP_MLX90614_H_
#define _BSP_MLX90614_H_
#include "gd32f4xx.h"
//端口移植
#define RCU_SDA RCU_GPIOB
#define PORT_SDA GPIOB
#define GPIO_SDA GPIO_PIN_8
#define RCU_SCL RCU_GPIOB
#define PORT_SCL GPIOB
#define GPIO_SCL GPIO_PIN_9
//设置SDA输出模式
#define SDA_OUT() gpio_mode_set(PORT_SDA,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,GPIO_SDA)
//设置SDA输入模式
#define SDA_IN() gpio_mode_set(PORT_SDA,GPIO_MODE_INPUT,GPIO_PUPD_PULLUP,GPIO_SDA)
//获取SDA引脚的电平变化
#define SDA_GET() gpio_input_bit_get(PORT_SDA,GPIO_SDA)
//SDA与SCL输出
#define SDA(x) gpio_bit_write(PORT_SDA,GPIO_SDA, (x?SET:RESET))
#define SCL(x) gpio_bit_write(PORT_SCL,GPIO_SCL, (x?SET:RESET))
void MLX90614_GPIO_Init(void);
float MLX90614_Read(unsigned char SlaveAddr, unsigned char RegAddr);
#endif
2.12.4 移植验证
在自己工程中的main主函数中,编写如下。
/********************************************************************************
* 文 件 名: main.c
* 版 本 号: 初版
* 修改作者: LC
* 修改日期: 2022年04月19日
* 功能介绍:
******************************************************************************
* 注意事项:
*********************************************************************************/
#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "sys.h"
#include "bsp_usart.h"
#include "bsp_mlx90614.h"
/************************************************
函数名称 : main
功 能 : 主函数
参 数 : 无
返 回 值 : 无
作 者 : LC
*************************************************/
int main(void)
{
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
systick_config(); // 滴答定时器初始化
sart_gpio_config(9600U); // 串口0初始化
MLX90614_GPIO_Init();
printf("start\r\n");
while(1)
{
printf("temperature = %.2f\r\n", MLX90614_Read(0X5A, 0X07) );
delay_1ms(1000);
}
}
移植现象:测量手心温度为36℃左右。
移植成功示例,见文件2.12.4-1 。
文件2.12.4-1