前言
在数字通信各种协议中,相对Ethernet, USB, SATA, PCI-Express等传输速度达数百上千兆字节每秒的总线,I2C和SPI常称为小协议。但是,我们不能忘记的是各种总线的用途是什么。“大”协议是用于系统外的整个系统之间通信的,“小”协议是用于系统内各芯片间的通信,没有迹象表明“大”协议有必要取代“小”协议。I2C和SPI的存在和流行体现了“够用就好”的哲学。
一、I2C总线定义
I2C (‘intel’ -Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。在主从通信中,可以有多个I2C总线器件同时接到I2C总线上,通过地址来识别通信对象。
I2C总线是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据。在CPU与被控IC之间、IC与IC之间进行双向传送,最高传送速率100kbps。各种被控制电路均并联在这条总线上,但就像电话机一样只有拨通各自的号码才能工作,所以每个电路和模块都有唯一的地址,在信息的传输过程中,I2C总线上并接的每一模块电路既可以是主控器(或被控器),又可以是发送器(或接收器),这取决于它所要完成的功能。
二、Arduino 单片机Wire Library
I2C是非常普通的通信方式,Arduino单片机集成了I2C库,可以直接调用几个函数实现I2C通信。这个库允许您与I2C / TWI 设备进行通信。在Arduino板上的R3布局(1.0 pinout), SDA(数据线)和SCL(时钟线)都在靠近AREF引脚的针头上。Arduino Due有两个I2C /TWI接口SDA1和SCL1靠近AREF pin,另外一个在20和21上。详情参考这里
注:当连接SDA/SCL引脚时上拉电阻需要被连接,mega2560的上拉电阻在20-21引脚上。(没另外设置,调试成功,原因暂且未知?)
Arduino的I2C库是Wire.h
常用库函数如下:
begin()
requestFrom()
beginTransmission()
endTransmission()
write()
available()
read()
SetClock()
onReceive()
onRequest()
Wire.beginTransmission(Addr)
Wire.write(data)
Wire.endTransmission(void)
Wire.requestFrom(Addr, num)
Wire.available()
Wire.read()
示例程序
引脚接线:
1 VCC ------VCC
2 GND ------ GND
3 SCL ------ 21(SCL)或者SCL1
4 SDA ------20(SDA)或者SDA1
测试一:
#include <Wire.h>
void setup() {
// put your setup code here, to run once:
Wire.begin(); // join i2c bus (address optional for master)master因为不需要地址,Wire.begin()就可以了。
Serial.begin(9600); // start serial for output
}
uint16_t result;
float temp;
void loop() {
// put your main code here, to run repeatedly:
Wire.beginTransmission(0x5A);//发送起始信号和I2C总线地址
Wire.write(0x07); // 只发送一次数据,地址自动加一
Wire.endTransmission(false); // 停止信号
Wire.requestFrom(0x5A, 3);//向已知地址slave获取连续3个数据,这时候需要注意,数据只是存起来了,并没有真正返回
result = Wire.read(); //Receive DATA
result |= Wire.read() << 8; //Receive DATA
uint8_t pec = Wire.read();
temp = result*0.02-273.15;//
Serial.print(temp);
Serial.println();
// delay(500)
}
Q:Arduino如何读取I2C总线上连接设备的地址?
1、将一个I2C器件连接至arduino的SDA、SCL引脚(UNO R3 连接A4、A5)
2、将如下代码烧入arduino,打开串口界面即可得到I2C设备的地址。
#include <Wire.h>
void setup()
{
Wire.begin();
Serial.begin(9600);
Serial.println("nI2C Scanner");
}
void loop()
{
byte error, address;
int nDevices;
Serial.println("Scanning...");
nDevices = 0;
for(address = 1; address < 127; address++ )
{
Wire.beginTransmission(address);
error = Wire.endTransmission();
if (error == 0)
{
Serial.print("I2C device found at address 0x");
if (address<16)
Serial.print("0");
Serial.print(address,HEX);
Serial.println(" !");
nDevices++;
}
else if (error==4)
{
Serial.print("Unknow error at address 0x");
if (address<16)
Serial.print("0");
Serial.println(address,HEX);
}
}
if (nDevices == 0)
Serial.println("No I2C devices foundn");
else
Serial.println("donen");
delay(5000); // wait 5 seconds for next scan
}
Arduino主从机之间的双向I2C通讯实验
我现在采用老版本Arduino-0018编程,老版本I2C通讯的发送数据命令是send(),接受数据命令是receive(),最新版本Arduino 1.0的发送数据的命令是write(),接受数据的命令是read(),这一点要注意啊,看清楚自己下载应用的是什么版本的Arduino编程软件。Arduino程序下载地址:点击这里下载
I2C实验说明: 主机向从机循环发送字符串" light is "和字节 x,x 为 1 或 0,从机接收后,把数据显示在它的串口监视器中,如上图。然后当主机通知从机向它上传数据时,会把 x 值再上传回主机,然后赋值给变量c。当主机程序判断 c 为 1,则点亮主机数字引脚13相连的LED,否则熄灭LED。
通过这个实验把主从机之间的双向通讯都诠释出来了。
测试二:
Arduino 主机 程序如下:
/*主机向从机循环发送字符串"light is "和字节x,x为1或0从机接收到主机发来的数据后,当主机通知从机向它上传数据时
会把x值再上传回主机,然后赋值给变量c。
当主机程序判断c为1,则点亮LED,否则熄灭LED。*/
#include <Wire.h> //声明I2C库文件
#define LED 13
byte x = 0; //变量x决定LED的亮灭 这里byte就是unsigned char
//初始化
void setup()
{
Serial.begin(9600);
Wire.begin(); // 加入 i2c 总线,作为主机
pinMode(LED,OUTPUT);//设置数字端口13为输出
}
//主程序
void loop()
{
Wire.beginTransmission(4); //**Write**--**Sender**---发送数据到设备号为4的从机
Wire.write("light is "); // 发送字符串"light is "
Wire.write(x); // 发送变量x中的一个字节
Wire.endTransmission(); // 停止发送
x++; //变量x加1
if(x==2) //如果变量x的值为2,则把x值转为0
x=0;
delay(1000); //延时1s
Wire.requestFrom(4, 1); //通知4号从机上传1个字节
while(Wire.available()>0) // **Read**--**Receiver**-----当主机接收到从机数据时
{
byte c = Wire.read(); //接收一个字节赋值给c
Serial.println(c);
if(c==1) //判断c为1,则点亮LED,否则熄灭LED。
{
digitalWrite(LED,LOW);
}
else
{
digitalWrite(LED,HIGH);
}
}
delay(1000); //延时1s
}
Arduino 从机 程序如下:
/*循环接收主机发送来的数据包,同时显示在串口监视器上
把数据包的最后一个字节,再上传回主机
*/
#include <Wire.h> //声明I2C库文件
int x; //变量x值决定主机的LED是否点亮
//初始化
void setup()
{
Wire.begin(4); // 加入 i2c 总线,设置从机地址为 #4
Wire.onReceive(receiveEvent); //注册接收到主机字符的事件
Wire.onRequest(requestEvent); // 注册主机通知从机上传数据的事件
Serial.begin(9600); //设置串口波特率
}
//主程序
void loop()
{
delay(100); //延时
}
void receiveEvent(int howMany) //** Read**--**Receiver**--------当从机接收到主机字符,执行该事件
{
while( Wire.available()>1) // 循环执行,直到数据包只剩下最后一个字符
{
char c = Wire.read(); // 作为字符接收字节
Serial.print(c); // 把字符打印到串口监视器中
}
//接收主机发送的数据包中的最后一个字节
x = Wire.read(); // 作为整数接收字节
Serial.println("");
Serial.println(x); //把整数打印到串口监视器中,并回车
}
//**Write** --**Sender** --------当主机通知从机上传数据,执行该事件
void requestEvent()
{
//把接收主机发送的数据包中的最后一个字节再上传给主机
Wire.write( x); // 响应主机的通知,向主机发送一个字节数据
}
参考资料(文章/视频)
- 很清晰的解读I2C协议
- Arduino之间的I2C通讯
- Arduino主从机之间的I2C通信之MLX90614红外测温
- 关于MLX90614红外测温模块SMBus 协议的学习笔记
- UART SPI IIC的详解及三者的区别和联系
- 什么是BootLoader?
- Arduino 烧录BootLoader
- 什么是I2C?
- Arduino CH02-12 I2C通訊 主端讀取從端傳送
- Arduino通过I2C(PCF8574T)驱动1602LCD