友情提示:380元/半年,儿童学编程,就上码丁实验室。
我是潘,曾经是个工程师。这是 “Arduino 公开课” 系列的入门教程。前面课程介绍了I2C工作原理,以及怎样向设备发送数据,并通过小屏幕来显示出来,这节课将介绍如何通过 I2C 链接多个设备,接收并发送数据。有任何疑问请在评论区提出,我会逐一回答。
这节课要设计一个能够显示当前温度和光照度的装置,这种装置在温室培植中应用很广泛。我们要用到 LM75 温度传感器、 BH1750 光照度传感器和 1602 OLED 显示屏。它们都是基于 I2C 协议,这意味着3个设备只要2根数据线,就能全部链接起来:
由于设备较多,所以我们需要2个 5 ~ 10KΩ 上拉电阻。1602 OLED 前面已经介绍过了,现在我们先介绍 LM75 温度传感器。这个传感器由 NXP 设计,安森美、TI 等厂家也有生产,外围电路非常小,只需一个电容就能工作,芯片尺寸只有 4X5mm(1/3指甲头),精度为 0.5 ºC,除了日常测温度外,也适合安装在各种设备中,作为温控原件。
根据 LM75 的 Datasheet,其作为 slave,地址是 7-bits,1001A2A1A0,我手里的模块 A2\A1\A0 都接地,所以地址就是1001000。继续查看手册,温度的格式为 16bits,最左边 D15 为 MSB,若MSB 为 1,表示为负。D15 ~ D8 为为温度的整数部分,D7~D0 合计 8bits ,但只有 D7 (LSB)起作用,用于记录 0.5度。
我们从简单的入手,先获取温度正整数部分:
1
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
|
/*
作者:Ardui.Co
效果:I2C 连接 1602 LCD 和 LM75 显示温度
版本:1.0
更新时间:2017年4月18日
*/
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0×27,16,2);//声明I2C地址和点阵的规格为16字符和2行
intLM75address=0×48;//LM75 I2C地址
voidsetup()
{
Wire.begin();// 初始化I2C
lcd.begin();// 初始化LCD
}
voidloop()
{
Wire.beginTransmission(LM75address);
Wire.requestFrom(LM75address,1);//从 LM75 地址获取1字节数据
inttemp=Wire.read();//读取LM75返回的第1个字节
lcd.setCursor(0,0);//光标移动到第2行第1个字符
lcd.write(“temp is:”)
lcd.setCursor(8,0);//光标移动到第2行第9个字符
lcd.write(temp);
Wire.endTransmission();
delay(500);
}
|
为了显示负数和小数位,我们需要分两部分获取 LM75 的数据,为了更好理解以及方便后面增加其他传感器,我们将 LM75 单独出一个函数来操作:
1
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
|
/*
作者:Ardui.Co
效果:I2C 连接 1602 LCD 和 LM75 显示温度
版本:1.0
更新时间:2017年4月18日
*/
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0×27,16,2);//声明I2C地址和点阵的规格为16字符和2行
intLM75address=0×48;//LM75 I2C地址
voidsetup()
{
Wire.begin();// 初始化I2C
lcd.begin();//初始化LCD
}
voidloop()
{
lcd.setCursor(0,0);//光标移动到第2行第1个字符
lcd.print(“temp is:”);
lcd.setCursor(8,0);//光标移动到第2行第9个字符
lcd.print(LM75());
delay(500);
}
doubleLM75()// LM75 操作函数
{
Wire.beginTransmission(LM75address);
Wire.requestFrom(LM75address,2);
charpart1=Wire.read();//以char类型来读取第1个LM75返回的字节
intpart2=Wire.read();//以int类型读取第2个LM75返回的字节
part2&=B10000000;
part2>>=7;
Wire.endTransmission();
returnpart1+part2 *0.5;
}
|
其实,这里用到一个类型转换的技巧,由于 char 只能显示 8bits 的数据,超出部分,以十进制方式显示是 “-”,所以将part1的类型由 int 改为 char,111111111 前8位为 11111111,就显示 -1 了。LM75() 定义为 double 是为保留返回值的小数点,如果换成 int 小数点就会被去掉。参考一下 Datasheet 就很容易理解这点:
现在再看看 BH1750 光亮度传感器。该传感器内置16bit ADC,可以测量 1~65535 lux 的亮度,以120ms 的速度采样时,精度可以达到 0.5 lux,如果20ms采样,精度为 4 lux。
怎么判断黑夜还是白昼?可以参考下面的光亮度数据:
晚上: 0.001-0.02
月夜: 0.02-0.3
多云室内: 5-50
适合阅读的亮度: 50-60
多云室外: 50-500
晴天室内: 100-1000
夏天中午光照下: 10∧6
(单位:lux)
根据 BH1750 的 DataSheet,BH1750 是可以设置测量模式的,方式是通过 I2C 写入下表对应的数据:
比如,要把它的采样设置为低速(120ms)、高分辨率(1lux)模式,即 “0001 0000”,换算为16进制,即 0×10,对应的 I2C 操作为:
1
|
Wire.write(0×10);
|
为了省电,我们可以用单次模式,比如 0010_0001,即 0×21。
写入数据后,BH1750 开始反馈数据,一共 2byte,我们紧接着 2 个变量来接收,使用 while() 循环比较便捷。根据 Datasheet 这2个byte分别为:
深灰色部分是操作byte,刚才讲过以写入的方式操作,第2个、第3个就是 BH1750 返回的数据。计算方式 DataSheet 已经写的很清楚,第16位2的15次方,第15位2的14次方,如此类推。各位数相加后除以 1.2,就能得出结果。高低byte相加时,我们要用到移位符 “<<”,具体程序如下:
1
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
|
/*
作者:Ardui.Co
效果:I2C 连接 1602 LCD 和 LM75 显示温度
版本:1.0
更新时间:2017年4月18日
*/
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <math.h>
LiquidCrystal_I2C lcd(0×27,16,2);//声明I2C地址和点阵的规格为16字符和2行
intBH1750address=0×23;//BH1750 I2C地址
intLM75address=0×48;//LM75 I2C地址
voidsetup()
{
Wire.begin();// 初始化I2C
lcd.begin();//初始化LCD
}
voidloop()
{
lcd.setCursor(0,0);
lcd.print(“temp is:”);
lcd.setCursor(8,0);
lcd.print(LM75());
lcd.setCursor(0,1);
lcd.print(“Lux is:”);
lcd.setCursor(7,1);
lcd.print(BH1750());
delay(500);
}
doubleLM75()
{
Wire.beginTransmission(LM75address);
Wire.requestFrom(LM75address,2);
charpart1=Wire.read();
intpart2=Wire.read();
part2&=B10000000;
part2>>=7;
Wire.endTransmission();
returnpart1+part2 *0.5;
}
doubleBH1750()//BH1750 操作函数
{
inti=0;
bytebuff[0];
Wire.beginTransmission(BH1750address);
Wire.write(0×21);//根据 Datasheet 设置为单次获取模式,采样时间为120ms,高精度
delay(200);//确保采样成功,起码延时200ms
Wire.requestFrom(BH1750address,2);
while(Wire.available())
{
buff[i]=Wire.read(); // 循环读取 BH1750 返回数据,一共3个byte
i++;
}
if(i==2)return((buff[0]<<8)|buff[1])/1.2;//当 i == 2 时,由于没有数据,所以 While 循环已经跳出来了
Wire.endTransmission();
}
|
顺便提一下,常见 I2C 传感器模块,内置了上拉电阻,所以这里不需要再另外接。