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
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 | PWM 控制 | 02H |
Ta range | 环境温度范围设定 | 03H |
Emissivity correction conefficient | 发射率校准系数:0.1-1 | 04H |
Config Register1 | 配置寄存器 | 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 来决定一个字节中的 BIT7~BIT5。剩余的 BIT4~BIT0 由要操控的地址决定。
例如,我要读取 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 即可得到实际温度。
该温度换算公式对To和Ta都适用。
2.12.3.2 引脚选择
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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
在文件 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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
移植现象:测量手心温度为 36℃ 左右。
移植成功示例,见文件 2.12.4-1 。
文件 2.12.4-1 移植成功示例