前言
最近的一个项目需要用到红外温度传感器,便想着能不能在Hi3861上开发这个外设,经过摸索了一阵子,终于把红外温度传感器简单的开起来了,后面还会追加对传感器的使用。
本次使用开发板:HiSpark_WiFi_IoT
红外温度传感器
介绍
红外温度传感器应用广泛,在疫情期间可做体温测量,在汽车上可做空调控制、防雾应用,在家庭中可做家电温度控制等等。
MLX90614是一款红外非接触温度计(下文多以gy906简称)。TO-39金属封装里同时集成了红外感应热电堆探测器芯片和信号处理专用集成芯片。用以阻碍可见光和近红外光辐射的光学滤波器(可传播长波)集成在封装内提供对环境和日光的免疫。需注意的是封装内部的温差会影响温度计测量的精度,需要尽量将传感器和环境隔离。
协议
查阅文档时,发现这个红外温度传感器是使用SMBus通信协议或者PWM(默认SMBus输出格式),其中SMBus协议需要IO口设置为开漏输出,在Hi3861的IO口模式选择中并未发现有开漏输出选项,因此暂时不选择SMBus协议
通过摸索了一大阵子得知,SMbus 规格是由 I2C 简化而来。两条信号线为 SMBCLK 和 SMBDATA. 这和 I2C 上的 Clock(SCL) 和 Data(SDA) 是一样的。SMBus的读取数据格式与I2C协议大差不差,差别只在于电平认定。
两者协议的电平认定虽有不同,但也有部分交集。
I2C协议中有两者电平认定:相对认定和绝对认定,其中的相对认定是依据Vdd的电压来决定,高电平为0.7 Vdd,低电平低于0.3 Vdd即可。
再看一看红外温度传感器上的SMBus协议(如下图),高电平需要高于Vdd-0.1,和I2C协议符合。而低电平最大只能0.6V,这里就只能希望Hi3861的I2C能将低电平拉得到0.6V以下了。但巧合了,经过验证Hi3861可以使用!
准备
本次使用到的是MLX90614BCC版本,该型号的供电是3V,注意不要不要插到5V的供电。!
接线:
VIN->3V3,GND->GND,SLK->SLK,SDA->SDA
既然已经使用到了I2C,那么便需要知道该设备的地址,查阅了挺久的,发现了该设置的默认地址为0X00或者是0XB4,其中0x00是单个I2C设备连接时默认都能访问到。并且可以修改EEPROM,对其进行修改I2C地址。如果不小心修改了地址,又忘记了地址,便可以把传感器单独插上去,读0X0E的数值便可以得到设备的地址。
传感器将温度数据存在RAM里面的Tojb1,而ram里面Tobj1的地址为0x07,则得知读取温度的指令则为0x07,向该设备发送0x07,便可读取到温度数据
代码编写
IO口初始化
老步骤,第一步先IO口初始化
流程:
GPIO口初始化(开发板背后有标注GPIO口:13,14)
IO口复用成I2C
I2C口初始化(从hi_io.h里面的GPIO口管脚功能列举可得知I2C口)
#define GPIO_SDA 13
#define GPIO_SCL 14
#define GY906_I2C 0
void gy906_init(void)
{
IoTGpioInit(GPIO_SDA);
IoTGpioInit(GPIO_SCL);
hi_io_set_func(GPIO_SDA, HI_IO_FUNC_GPIO_13_I2C0_SDA);
hi_io_set_func(GPIO_SCL, HI_IO_FUNC_GPIO_14_I2C0_SCL);
hi_i2c_init(GY906_I2C, 400000);
}
主函数及线程
查询相关文档,gy906反馈的信息有三个,第一个是低位数据,第二个是高位数据,第三个是校验位.将高位数据和低位数据拼接,再通过公式计算即可得出温度数据.
流程:
- 初始化I2c传输数据
- 接收传感器反馈回来的数据
- 判断数据的正确性
- 对数据做拼接计算,即可得出温度数据
#define GY906_I2C 0
#define GY906_addr 0x00 // 0x00 or 0xB4
//获取温度数值
void gy906_get(void)
{
hi_i2c_data gy_data = {0}; //I2C数据结构
uint8_t recv_data[3] = {0}; //接收数据缓存
uint8_t send_data[1] = {0x07}; //读取温度的指令
//初始化gy_data
gy_data.send_buf = send_data;
gy_data.send_len = 1;
gy_data.receive_buf = recv_data;
gy_data.receive_len = 3;
//I2C读写操作
hi_i2c_writeread(GY906_I2C, (GY906_addr << 1) | 0x01, &gy_data);
//进行校验位判断
uint8_t addr[6] = {0, 0, 0, 0x01, 0x07, 0x00};
addr[1] = recv_data[1]; //高位
addr[2] = recv_data[0]; //低位
uint8_t PY_CRC1 = PEC_Calculation(addr);
//如果校验位正确
if (PY_CRC1 == recv_data[2])
{
printf("temperture correct!\n");
}
//如果校验位错误,返回全部数值
else
{
printf("temperature false!\n");
printf("recv_data:low:%x high:%x CRC:%x\n", recv_data[0], recv_data[1], recv_data[2]);
printf("CRC1:%x\n", PY_CRC1);
}
//打印温度 T= (DataH:DataL)*0.02-273.15
float temperature_f = (((float)((recv_data[1] << 8) | recv_data[0])) * 2 - 27315) / 100;
printf("temperature:%.2f\n", temperature_f);
}
//主函数
void gy906_demo(void)
{
//IO口初始化
gy906_init();
while (1)
{
gy906_get();
sleep(1);
}
}
//线程创建
void gy_demo(void)
{
osThreadAttr_t attr;
attr.name = "gy_demo";
attr.attr_bits = 0U;
attr.cb_mem = NULL;
attr.cb_size = 0U;
attr.stack_mem = NULL;
attr.stack_size = 10240;
attr.priority = 25;
if (osThreadNew((osThreadFunc_t)gy906_demo, NULL, &attr) == NULL)
{
printf("[gy_demo] Falied to create gy_demo!\n");
}
}
SYS_RUN(gy_demo);
校验算法
算法的代码如下(部分介绍打在注释上):
uint8_t PEC_Calculation(uint8_t pec[])
{
uint8_t crc[6]; //存放多项式
uint8_t BitPosition = 47; //存放所有数据最高位,6*8=48 最高位就是47位
uint8_t shift;
uint8_t i;
uint8_t j;
uint8_t temp;
do
{
/*初始化 0x00 00 00 00 01 07*/
crc[5] = 0;
crc[4] = 0;
crc[3] = 0;
crc[2] = 0;
crc[1] = 0x01;
crc[0] = 0x07;
//设置最大位位位置为47,记录位位置
BitPosition = 47;
/*Set shift position at 0*/
shift = 0;
/*查找pec[5]开始的传输消息中的第一个“1”*/
i = 5;
j = 0;
//0x80 -> 1000 0000,一位一位地找第一个"1"
while ((pec[i] & (0x80 >> j)) == 0 && i > 0)
{
//位位置-1
BitPosition--;
if (j < 7)
{
j++;
}
else
{
j = 0x00;
i--;
}
} /*End of while*/
/*记录位位置*/
shift = BitPosition - 8;
/*校验操作 */
//循环次数为:shift
while (shift)
{
for (i = 5; i < 0xFF; i--) //i<0xff即是i>0,遍历crc数组
{
//以下操作即是把一个八位的数据整体向左移一位,最高位移到最低位,例:1001 1000 ->0011 0001
if ((crc[i - 1] & 0x80) && (i > 0))
{
temp = 1;
}
else
{
temp = 0;
}
crc[i] <<= 1; //左移一位
crc[i] += temp;
} /*End of for*/
shift--;
} /*End of while*/
//将已经操作过的位 置0
for (i = 0; i <= 5; i++)
{
pec[i] ^= crc[i];
} /*End of for*/
} while (BitPosition > 8); /*循环操作*/
return pec[0];
}
编译脚本BUILD.gn
static_library("my_gy906_demo") {
sources = [
"gy906.c",
]
include_dirs = [
"//utils/native/lite/include",
"//kernel/liteos_m/components/cmsis/2.0",
"//base/iot_hardware/peripheral/interfaces/kits",
"//device/soc/hisilicon/hi3861v100/sdk_liteos/include",
]
}
效果
附件链接:https://ost.51cto.com/resource/2302
https://ost.51cto.com/#bkwz