导语
I²C,即Inter-Integrated Circuit,是目前嵌入式系统和单片机用于连接外部设备最常用的串行通信总线协议之一。I²C技术由Philips半导体公司(现NXP半导体公司)在1980年代开发,使用多主从架构,通常用于低速IC外设与处理器或单片机之间的短距离板内通讯。
从传感器到EEPROM,从时钟模块到LED显示屏,许多功能的实现都依赖处理器或单片机与外围设备之间的通讯。熟练掌握I²C的使用可以让我们更快更好地完成硬件开发与调试。本教程将使用I²C总线实现Arduino Micro及温度传感器DS1621之间的通讯,将温度传感器DS1621对环境温度的测量结果在串口中显示出来。
I²C介绍
上世纪70年代后期,Philips公司的半导体部门(现在的NXP)需要简化并标准化其产品中各种集成电路之间的数据交换,于是研发出了I²C总线。I²C总线只需要两根线——一根数据线(SDA, Serial DAta),一根时钟线(SCL, Serial CLock)就可以实现集成电路中的串行通信。
最初,I²C的传输速率被限制在了100 kbit/s。这个传输速率已经可以应付大多数情况下的需要。为了实现更高的传输速度,新的标准诞生了。首先诞生的是Fast Mode,传输速度可以达到400 kbit/s。随后,Fast Mode Plus标准把传输速度提升到了1 Mbit/s。1998年的2.0版新增了把速率提升到了3.4 Mbit/s的高速模式High Speed,并为了节省能源而减少了电压及电流的需求。目前最新的标准是2014年第6版。
I²C连接原理图如上图所示。它有如下几个特点:
- 仅需两根线双向通信。一根时钟线,一根数据线。
- 同步时序传输。I²C不需要像RS232连接一样事先确定传输速率。I²C总线由主机Master产生的时钟讯号作为时序进行通讯。
- 外设的连接方式是主从式。每个外设都有地址,而且它们的地址在总线上都是独一无二的。
- I²C总线上可以有多个主机
I²C和Arduino
每一个Arduino板都可以作为主机进行I²C通信。在Arduino Uno上,用于I²C的两个脚分别是A4 – SDA 和 A5 – SCL。而在Arduino Micro上则是 2 脚(SDA) 3脚 (SCL)。
只有一个I²C设备连接到Arduino时,通常情况下是不需要上拉电阻的,因为Arduino的单片机芯片ATmega328已经在内部集成了上拉电阻。但如果有很多设备同时连接到了Arduino上,就需要在SDA和SCL脚上放置两个10kΩ的上拉电阻。如果你的datasheet上对上拉电阻的阻值有更明确的要求,请遵循datasheet上的建议。
I²C总线上主从机之间最大传输距离大约是1米。每个设备可以按任意顺序连接在总线上。在本教程中,Arduino是主机,会给从机DS1621发送命令并读取信息。
在Arduino上使用I²C总线,需要调用Wire库。
温度传感器DS1621
DS1621是Maxim Integrated公司出品的温度传感器,提供DIP 8封装,非常适合我们在面包板上进行快速prototyping。
从功能模块图中我们可以看出,DS1621整合了从温度采集,模数转换到数据处理的一系列功能。我们可以从它内部寄存器中直接读取温度信息。我们甚至可以设置温度上下限,当DS1621检测到温度超过设置的上下限时,在Tout管脚发出报警信号。
管脚连接
将Arduino Micro和DS1621连接起来非常简单。
- 电源:将Arduino Micro的5V电源脚与DS1621的8脚VDD相连,将Arduino Micro的GND脚与DS1621的4脚GND相连
- I²C:将Arduino Micro的2脚SDA与DS1621的1脚SDA相连,将Arduino Micro的3脚SCL与DS1621的2脚SCL相连
- 地址:DS1621的5/6/7脚是地址脚。参阅温度传感器的Datasheet我们知道,当A0 = A1 = A2 =0时,DS1621的地址是100 1000 = 48Hex
Arduino与DS1621通讯
发送数据
首先,主机Arduino要依据从机在总线上的地址与从机建立联系:
Wire.beginTransmission
(
0x48
); //
A0=A1=A2=0时
DS1621的地址
然后,向从机发送数据或命令:
Wire.write
(
0xEE
); //
0xEE是DS1621开始温度数据转换的命令
最后,命令结束以后断开与该从机的联系(以和其他设备通讯)
Wire.endTransmission
(
);//
结束传输
如果想要修改DS1621中寄存器的值,需要首先向寄存器发送修改寄存器值的命令,然后在向该寄存器写入值:
Wire.beginTransmission
(
0x48
); //
开始传输
Wire.write(0xA1); // 选定TH寄存器,以设定温度报警
Wire.write(0x20); // 0x20即为10进制的32
Wire.write(0x80); // 0.5°C,所以报警温度设定为32.5°C
Wire.endTransmission
(
);//
结束传输
Wire.beginTransmission
(
0x48
); // 开始传输
Wire.write(0xA2); // 选定TL寄存器,以设定解除温度报警
Wire.write(0x1E); // 0x1E即为10进制的30
Wire.write(0x80); // 0.5°C,所以解除报警的温度设定为30.5°C
Wire.endTransmission
(
);// 结束传输
读取数据
首先,主机Arduino要依据从机在总线上的地址与从机建立联系:
Wire.beginTransmission
(
0x48
); //
A0=A1=A2=0时
DS1621的地址
然后,向从机发送索取数据的命令:
Wire.write
(
0xAA
); //
0xAA是向DS1621读取温度的命令
最后,命令结束以后断开与该从机的联系(以和其他设备通讯)
Wire.endTransmission
(false
);//
结束传输,如果读取错误则重启
发送完上述命令后,DS1621已经准备好向主机Arduino Micro发送温度数据了,所以Arduino要做好读取工作:
Wire.requestFrom(ADDRESS_DS1621, 2); // 读取两个字节 if (2 <= Wire.available()) { temperatureMSB = Wire.read(); // 先读MSB temperatureLSB = Wire.read(); // 再读LSB } temperature = (float) temperatureMSB; if (temperatureLSB & 0x80) temperature += 0.5; // 是否有小数0.5°C if (temperatureMSB & 0x80) temperature -= 256; // 温度是否为负
最后,在串口中显示出来
Serial.print("Temperature = "); Serial.println(temperature);
完整的代码如下:
#include <Wire.h> #define A0_DS1621 0 #define A1_DS1621 0 #define A2_DS1621 0 #define ADDRESS_DS1621 (0x48 | A2_DS1621<<2 | A1_DS1621<<1 | A0_DS1621) #define ONESHOT 0 // 连续温度转换 #define POL 1 // Tout模式为Active High #define NVB 0 // bit NVB, non utilisé ici #define TLF 0 // bit TLF, non utilisé ici #define THF 0 // bit THF, non utilisé ici /* 参数寄存器初设 */ #define REGISTER_CONFIG (THF<<6 | TLF<<5 | NVB<<4 | POL<<1 | ONESHOT) #define DONE_MASK 0x80 // Masque pour bit DONE /* Commandes du DS1621 */ #define READ_TEMPERATURE 0xAA #define ACCESS_CONFIG 0xAC #define START_CONVERT 0xEE #define STOP_CONVERT 0x22 #define ACCESS_TH 0xA1 #define ACCESS_TL 0xA2 byte endConversion = 0; byte temperatureMSB = 0; byte temperatureLSB = 0; byte THLSB = 0x80; byte THMSB = 0x20; byte TLLSB = 0x80; byte TLMSB = 0x1E; float temperature; void setup() { Serial.begin(9600); // Initialisation Terminal Série Wire.begin(); // Initialisation I2C /* DS1621初设 */ // Wire.beginTransmission(ADDRESS_DS1621); // Wire.write(STOP_CONVERT); // Wire.endTransmission(); /* 设置高低温寄存器DS1621 */ Wire.beginTransmission(ADDRESS_DS1621); Wire.write(ACCESS_TH); // 进入高温寄存器 Wire.write(THMSB); Wire.write(THLSB); // 设置报警温度为32.5°C Wire.endTransmission(); Wire.beginTransmission(ADDRESS_DS1621); Wire.write(ACCESS_TL); // 进入低温寄存器 Wire.write(TLMSB); Wire.write(TLLSB); // 设置解除温度报警的温度为30.5°C Wire.endTransmission(); /* DS1621寄存器初设 */ Wire.beginTransmission(ADDRESS_DS1621); Wire.write(ACCESS_CONFIG); // 进入参数寄存器 Wire.write(REGISTER_CONFIG); // 写入参数 Wire.endTransmission(); } void loop() { /* 开始温度转换 */ Wire.beginTransmission(ADDRESS_DS1621); Wire.write(START_CONVERT); Wire.endTransmission(); /* 逐次转换时(One Shot = 0) */ // do { // Wire.beginTransmission(ADDRESS_DS1621); // Wire.write(ACCESS_CONFIG); // Wire.endTransmission(false); // Condition RESTART // Wire.requestFrom(ADDRESS_DS1621, 1); // Un octet est requis // if (1 <= Wire.available()) endConversion = Wire.read() & DONE_MASK; // } while (!endConversion); /* 读取温度*/ Wire.beginTransmission(ADDRESS_DS1621); Wire.write(READ_TEMPERATURE); Wire.endTransmission(false); // 失败则重启 Wire.requestFrom(ADDRESS_DS1621, 2); // 读取两个字节 if (2 <= Wire.available()) { temperatureMSB = Wire.read(); // temperatureLSB = Wire.read(); // } /* 计算及显示温度 */ temperature = (float) temperatureMSB; if (temperatureLSB & 0x80) temperature += 0.5; // if (temperatureMSB & 0x80) temperature -= 256; // Serial.print("Temperature = "); Serial.print(temperature); Serial.println(" degres Celcius"); delay(100); // 每过100ms测量一次温度 }