MLX90614红外温度传感器使用指南:关键寄存器详解与STM32F1单片机软件IIC通信协议实践。
1 前言
本躯在使用MLX90614时,问商家要器件手册,他给我发一个毛用没有的示例程序,寄存器呢?客服说不知道您说的什么东西呢亲亲。
在CSDN上查这个器件,全是会员专享不说,给的内容还牛头不对马嘴。
本躯一气之下,决定亲自写文章,帮助后面的使用者快速学习并简单使用这个器件。
本文章代码运行环境:Keil;平台:STM32F103C8T6(注意:本文章代码与我自己测试的完整代码有部分改动,并且改动后的代码并没有测试,所以可能会出现细微的错误,如有错误请指正)
本文章主要内容:GY906硬件基础了解,器件连接,器件选型,STM32F1软件IIC,寄存器种类和配置。
2 MLX90614(GY906)红外传感器硬件
2.1 MLX90614(GY906)(以下全部称为GY906)器件连接
MLX90614支持IIC、UART等协议,一般我们使用更方便的IIC协议。
将你的IIC连接到MCU的IIC,VDD接3.3V,GND接地。
需要注意的是,GY906内部并没有集成上拉电阻,所以SDA和SCL都应该通过串联电阻连接到VCC。这里我推荐4.7KΩ电阻。
GY906的VDD按照器件手册上来说,可以接3-5V电源电压,但由于会出现较大的线性误差,推荐接3.3V电源。
图2-1 典型连接原理图
图2-2 误差与VDD的线性关系图,来源:MLX90614器件手册
如果需要更高的精度,可以采用以下滤波公式:T_C=T-(VDD-3)*0.6
2.2 GY906器件选型
相信大家在选择BAA、DCC等一堆器件时不清楚该选哪个,问客服又问不明白,先看一个图。
图2-3 不同型号的峰值和宽度域 来源:MLX90614器件手册
我们注意到除了xBA,其他型号宽度域是逐渐减小的,宽度域可以简单理解为视域,宽度域越大,红外检测的视野范围越广,但同时视野距离越近。
因此,想要满足长检测距离,应使用宽度域小的型号,比如xCC,甚至于xCI。
前面的x是什么意思呢,是测量精度,市面上常见的有B和D型,B型满足一般需求,D型满足医用级需求,比如红外测温。
在本文中,我们选用DCC型号,大约10CM测距,医用级精度。
2.3 GY906硬件简介和寄存器
GY906内部自动采集环境温度和目标物体温度,内置算法自动滤波。
通过FIR滤波器滤波、增益补偿和偏移补偿,IIR进一步滤波最后计算出最终温度数据。
用户在使用GY906时,使用到的寄存器很少,以下是主要寄存器和说明:
从机地址 | 说明 | – | 数据格式 | 功能 |
---|---|---|---|---|
0x5A | 单片机使用从机地址时应左移一位 | 8位 | IIC从机地址 | |
寄存器地址 | 名称 | 读写权限 | 数据格式 | 关键功能 |
0x06 | RAM_TA | 只读 | 16位(开尔文) | 环境温度 |
0x07 | RAM_TOBJ1 | 只读 | 16位(开尔文) | 物体温度(主传感器) |
0x08 | RAM_TOBJ2 | 只读 | 16位(开尔文) | 物体温度(副传感器,仅存在于双区域型号中) |
0x04 | EEPROM_EMISSIVITY | 读写 | 16位(0~65535) | 设置发射率(0.0~1.0) |
0x05 | EEPROM_CONFIG | 读写 | 16位(位控制) | 配置滤波、SMBus 地址等 |
0x2E | EEPROM_SMBUS_ADDR | 读写 | 16位(低7位) |
修改 I2C 地址 |
2.3.1 温度数据寄存器
RAM_TA、RAM_TOBJ1、RAM_TOBJ2(DCC型号不存在)可以直接读取环境温度和物体温度,一共发送24位数据,先发送低八位数据,再发送高八位数据,最后发送PEC校验数据,每八位数据发送过来之后会发送一个ACK,所以我们只需要读取到两次ACK,就可以把全部的16位温度数据读取完毕。如果想使用PEC校验,只需要再读取一次ACK,读取到的PEC数据如果全部为0,则数据无误。
图2-4 接收数据格式 来源:MLX90614器件手册
写入数据与接收数据同理
图2-5 写入数据格式 来源:MLX90614器件手册
2.3.2 EEPROM_EMISSIVITY发射率设置寄存器
根据被测量物体发射率的不同,我们需要设置不同的发射率,比如如果测量人体,发射率应设置为0.96-0.98。
直接向发射率寄存器写入我们的发射率×65535,就可以设定发射率。(如0.98×65535)
2.3.3 EEPROM_CONFIG配置寄存器
在配置寄存器中,我们可以写入数据来控制寄存器是否上电复位(谨慎使用),是否开启强数字滤波(建议开启),和设置SMBus从机地址(一般默认为0x5A)。
位 | 名称 | 功能 | 默认值 |
---|---|---|---|
15 | POR(上电复位) | 1 = 执行复位,0 = 正常 | 0 |
14 | IIR_FILTER | 数字滤波强度(0=弱,1=强) | 0 |
13:8 | 保留 | 必须保持 0 |
0 |
7:0 | SMBus 地址 | 设置 SMBus 从机地址(0x00~0x7F) | 0x5A |
2.3.4 EEPROM_SMBUS_ADDR SMBus从机地址寄存器
可以直接读取从机地址或改变从机地址,仅低7位有效。
3 STM32F1软件IIC
由于网上大把软件IIC,在此我不在赘述,直接给出代码(才不是因为我懒)。至于我为什么使用软件IIC,而不使用STM32F1单片机自带的硬件IIC,我只能说硬件IIC谁用谁知道,一个标志位能把你程序直接卡死。
3.1 Delay.c
#include "stm32f10x.h"
void Delay_us(uint32_t xus)
{
SysTick->LOAD = 72 * xus;
SysTick->VAL = 0x00;
SysTick->CTRL = 0x00000005;
while(!(SysTick->CTRL & 0x00010000));
SysTick->CTRL = 0x00000004;
}
void Delay_ms(uint32_t xms)
{
while(xms--)
{
Delay_us(1000);
}
}
void Delay_s(uint32_t xs)
{
while(xs--)
{
Delay_ms(1000);
}
}
3.2 Delay.h
#ifndef __DELAY_H
#define __DELAY_H
void Delay_us(uint32_t us);
void Delay_ms(uint32_t ms);
void Delay_s(uint32_t s);
#endif
3.3 MyIIC.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
//软件模拟I2C
//B10口为SCL;B11为SDA;可自行重新配置
void MyI2C_W_SCL(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);
Delay_us(10);
}
void MyI2C_W_SDA(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);
Delay_us(10);
}
uint8_t MyI2C_R_SDA(void)
{
uint8_t BitValue;
BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);
Delay_us(10);
return BitValue;
}
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_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);
}
void MyI2C_Start(void)
{
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
void MyI2C_Stop(void)
{
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
void MyI2C_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i ++)
{
MyI2C_W_SDA(Byte & (0x80 >> i));
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
}
uint8_t MyI2C_ReceiveByte(void)
{
uint8_t i, Byte = 0x00;
MyI2C_W_SDA(1);
for (i = 0; i < 8; i ++)
{
MyI2C_W_SCL(1);
if (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);}
MyI2C_W_SCL(0);
}
return Byte;
}
void MyI2C_SendAck(uint8_t AckBit)
{
MyI2C_W_SDA(AckBit);
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
uint8_t MyI2C_ReceiveAck(void)
{
uint8_t AckBit;
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
AckBit = MyI2C_R_SDA();
MyI2C_W_SCL(0);
return AckBit;
}
3.4 MyIIC.h
#ifndef __MYI2C_H
#define __MYI2C_H
void MyI2C_Init(void);
void MyI2C_Start(void);
void MyI2C_Stop(void);
void MyI2C_SendByte(uint8_t Byte);
uint8_t MyI2C_ReceiveByte(void);
void MyI2C_SendAck(uint8_t AckBit);
uint8_t MyI2C_ReceiveAck(void);
#endif
4 STM32F1通过软件IIC读取MLX90614温度数据
4.1 MLX90614.c
#include "MyIIC.h"
#include "MLX90614_Address.h"
float a_temp=0; //环境温度
float o_temp=0; //物体温度
uint32_t MLX90614_ReadReg(uint8_t RegAddress)
{
uint8_t Data[2]={0};
uint16_t Data_all=0;
MyI2C_Start();
MyI2C_SendByte(MLX90614_ADDRESS);
MyI2C_ReceiveAck();
MyI2C_SendByte(RegAddress);
MyI2C_ReceiveAck();
MyI2C_Start();
MyI2C_SendByte(MLX90614_ADDRESS | 0x01);
MyI2C_ReceiveAck();
Data[0] = MyI2C_ReceiveByte();
MyI2C_ReceiveAck();
Data[1] = MyI2C_ReceiveByte();
MyI2C_SendAck(1);
MyI2C_Stop();
Data_all=(Data[1]<<8)+Data[0];
return Data_all;
}
void MLX90614_Init(void)
{
MyI2C_Init();
}
void MLX90614_TO(void)
{
int i=MLX90614_ReadReg(MLX90614_RAM_TOBJ1);
i=(i*2)-27315;
o_temp=i*0.01;
}
void MLX90614_TA(void)
{
int i=MLX90614_ReadReg(MLX90614_RAM_TA);
i=(i*2)-27315;
a_temp=i*0.01;
}
4.2 MLX90614.h
#ifndef __MLX90614_H
#define __MLX90614_H
#define float a_temp; //环境温度
#define float o_temp; //物体温度
uint32_t MLX90614_ReadReg(uint8_t RegAddress);
void MLX90614_Init(void);
void MLX90614_TO(void);
void MLX90614_TA(void);
#endif
4.3 MLX90614_Address.h
#ifndef __MLX90614_Address_H
#define __MLX90614_Address_H
#define MLX90614_ADDRESS 0x5A<<1
#define MLX90614_RAM_TA 0x06
#define MLX90614_RAM_TOBJ1 0x07
#define MLX90614_RAM_TOBJ2 0x08
#define MLX90614_EEPROM_EMISSIVITY 0x04
#define MLX90614_EEPROM_CONFIG 0x05
#define MLX90614_EEPROM_SMBUS_ADDR 0x2E
#endif
5 主程序示例
#include "stm32f10x.h"
#include "Delay.h"
#include "MyI2C.h"
#include "MLX90614.h"
#include "MLX90614_Address.h"
int main(void)
{
MLX90614_Init();
Delay_ms(20);
float ambient_temperature=0; //环境温度储存于此
float object_temperature=0; //物体温度储存于此
while (1)
{
MLX90614_TA(); //读取环境温度并储存到a_temp
Delay_ms(20);
MLX90614_TO(); //读取物体温度并储存到o_temp
Delay_ms(20);
ambient_temperature=a_temp;
object_temperature=o_temp;
}
}
6 结语
由此,就能简单得到环境温度和物体温度。
当然,很多内容我都没写,比如写入数据的代码,比如配置发射率和强数字滤波的代码。
再比如说,这样对着额头测体温依然是不准的,因为额头的温度实际上并不是人体核心温度。环境的温度、汗液的散热、血管的收缩和扩张,甚至于此时这个人的血压和心率,都非常影响额头温度和核心温度的差值。当然,这并不是完全无法解决,有兴趣的小伙伴可以自己试试。
并且我还删掉了大部分的注释。软件不难看懂,希望大家能好好学习基础知识,并且自己加上属于自己的注释,当然,如果能修改代码变成自己的代码习惯就更好了。
万事开头难,有了基础打底,剩下的进阶内容大家自己加油。
本躯水平有限,若有错误恳请斧正。
作者:史上最甜的躯