今天我们将学习另一种串行通信协议:I2C(Inter Integrated Circuits)。I2C与SPI相比,I2C只有两根线,SPI使用四根线,I2C可以有多个主从,而SPI只能有一个主和多个从。因此,在一个项目中有多个微控制器需要成为主控,然后使用 I2C。I2C通信一般用于与陀螺仪、加速度计、气压传感器、LED显示屏等进行通信。
在这个Arduino I2C 教程中,我们将使用两个 arduino 板之间的 I2C 通信,并使用电位器相互发送(0 到 127)值。值将显示在连接到每个 Arduino的16x2 LCD上。在这里,一个 Arduino 将充当 Master,另一个将充当 Slave。那么让我们从I2C通信的介绍开始。
什么是 I2C 通信协议?
术语 IIC 代表“ Inter Integrated Circuits ”。在某些地方,它通常表示为 I2C 或 I 平方 C,甚至表示为 2 线接口协议 (TWI),但它们的含义相同。I2C 是一种同步通信协议,意思是共享信息的两个设备必须共享一个公共时钟信号。它只有两根线来共享信息,其中一根用于公鸡信号,另一根用于发送和接收数据。
I2C 通信如何工作?
I2C 通信最早是由 Phillips 引入的。如前所述,它有两条线,这两条线将跨两个设备连接。这里一个设备称为 主 设备,另一个设备称为 从设备。通信应该并且将始终发生在两个 Master 和 Slave之间。I2C 通信的优点是可以将多个从设备连接到一个主设备。
完整的通信通过这两条线进行,即串行时钟(SCL)和串行数据(SDA)。
串行时钟(SCL): 与从机共享主机产生的时钟信号
串行数据 (SDA): 在主机和从机之间发送数据。
At any given TIme only the master will be able to iniTIate the communicaTIon. Since there is more than one slave in the bus, the master has to refer to each slave using a different address. When addressed only the slave with that parTIcular address will reply back with the information while the others keep quit. This way we can use the same bus to communicate with multiple devices.
I2C的电压电平没有预先定义。I2C 通信灵活,即设备采用 5v 电压供电,可以使用 5v 进行 I2C 通信,3.3v 设备可以使用 3v 进行 I2C 通信。但是,如果两个运行在不同电压下的设备需要使用 I2C 进行通信怎么办?5V I2C 总线不能与 3.3V 设备连接。在这种情况下,电压转换器用于匹配两个 I2C 总线之间的电压电平。
有一些条件构成交易。传输的初始化从 SDA 的下降沿开始,在下图中定义为“START”条件,其中主机将 SCL 保持为高电平,同时将 SDA 设置为低电平。
如下图所示,
SDA 的下降沿是 START 条件的硬件触发。此后,同一总线上的所有设备都进入侦听模式。
以同样的方式,SDA 的上升沿停止传输,在上图中显示为“停止”条件,其中主机将 SCL 保持为高电平,同时释放 SDA 变为高电平。所以 SDA 的上升沿停止传输。
R/W 位指示后续字节的传输方向,如果为高表示从机发送,如果为低表示主机将发送。
每个比特在每个时钟周期传输,因此传输一个字节需要 8 个时钟周期。在发送或接收每个字节后,为 ACK/NACK(确认/未确认)保留第 9 个时钟周期。该 ACK 位由从机或主机根据情况生成。对于 ACK 位,SDA 在第 9个时钟周期由主机或从机设置为低。所以它是低的它被认为是ACK,否则是NACK。
在哪里使用 I2C 通信?
I2C 通信仅用于 短距离通信。它在一定程度上肯定是可靠的,因为它有一个同步的时钟脉冲来让它变得聪明。该协议主要用于与传感器或其他必须向主机发送信息的设备通信。当微控制器必须使用最少的电线与许多其他从模块通信时,它非常方便。如果您正在寻找远程通信,您应该尝试 RS232,如果您正在寻找更可靠的通信,您应该尝试 SPI 协议。
Arduino中的I2C
下图显示了 Arduino UNO 中的 I2C 引脚。
在我们开始使用两个 Arduino 进行 I2C 编程之前。我们需要了解Arduino IDE 中使用的Wire 库。
《 Wire.h 》 库包含在程序中,用于使用以下函数进行 I2C 通信。
1. Wire.begin(地址):
用途: 该库用于与 I2C 设备进行通信。这将启动 Wire 库并作为主机或从机加入 I2C 总线。
地址:7 位从机地址是可选的,如果未指定地址,它会像这样 [Wire.begin()] 作为主机加入总线。
2. Wire.read():
用途:此函数用于读取从主设备或从设备接收到的字节,或者在调用 requestFrom()后从从设备传输到主设备,或者从主设备传输到从设备 。
3.Wire.write():
用途:该函数用于向从设备或主设备写入数据。
从机到主机:当主机使用Wire.RequestFrom()时,从机向主机写入数据。
主到从:对于从主设备到从设备的传输,在调用Wire.beginTransmission()和Wire.endTransmission( )之间使用Wire.write()。
Wire.write()可以写成:
Wire.write(值)
value:作为单个字节发送的值。
Wire.write(字符串):
string:作为一系列字节发送的字符串。
Wire.write(数据,长度):
data:以字节形式发送的数据数组
长度:要传输的字节数。
4. Wire.beginTransmission(地址):
用途:此函数用于开始向具有给定从地址的 I2C 设备进行传输。随后,使用write()函数构建用于传输的字节队列, 然后通过调用 endTransmission()函数传输它们。发送设备的 7 位地址。
5. Wire.endTransmission();
用途:此函数用于结束由 beginTransmission()开始的到从设备的传输,并传输由Wire.write() 排队的字节 。
6. Wire.onRequest();
使用:当主设备使用Wire.requestFrom()从从设备请求数据时,将调用此函数。在这里,我们可以包含Wire.write()函数来向主设备发送数据。
7. Wire.onReceive();
使用:当从设备接收到来自主设备的数据时调用此函数。这里我们可以包含Wire.read(); 函数读取从主机发送的数据。
8. Wire.requestFrom(地址,数量);
用途:该函数用于主设备向从设备请求字节。函数Wire.read()用于读取从设备发送的数据。
地址:请求字节的设备的 7 位地址
数量:要请求的字节数
所需组件
Arduino Uno(2 号)
16X2液晶显示模组
10K 电位器 (4-Nos)
面包板
连接电线
电路原理图
工作说明
这里为了演示Arduino 中的 I2C 通信,我们使用两个 Arduino UNO,两个16X2 LCD 显示器相互连接,并在两个 arduino 上使用两个电位器来确定从主机到从机和从机到主机的发送值(0 到 127),方法是改变电位器。
我们通过使用电位器将 arduino 引脚 A0 的输入模拟值从(0 到 5V)获取,并将它们转换为模拟到数字值(0 到 1023)。然后这些 ADC 值进一步转换为(0 到 127),因为我们只能通过 I2C 通信发送 7 位数据。I2C 通信通过两个 arduino 的引脚 A4 和 A5 上的两条线进行。
从 Arduino 的 LCD 上的值将通过改变主端的 POT 来改变,反之亦然。
Arduino 中的 I2C 编程
本教程有两个程序,一个用于主 Arduino,另一个用于从 Arduino。双方的完整方案在本项目结束时附有演示视频。
Arduino大师编程讲解
1.首先我们需要包含使用I2C通信功能的Wire库和使用LCD功能的LCD库。还为 16x2 LCD 定义 LCD 引脚。在此处了解有关将 LCD 与 Arduino 连接的更多信息。
#include
#include
LiquidCrystal lcd(2, 7, 8, 9, 10, 11);
2. 在 void setup()
我们以波特率 9600 开始串行通信。
序列号.开始(9600);
接下来我们在引脚 (A4,A5) 处开始 I2C 通信
Wire.begin(); //在引脚 (A4,A5) 处开始 I2C 通信
接下来我们以 16X2 模式初始化 LCD 显示模块并显示欢迎信息并在五秒钟后清除。
lcd.开始(16,2);//初始化液晶显示器
lcd.setCursor(0,0); //将光标设置在显示器的第一行
lcd.print("Circuit Digest"); //在 LCD 中打印 CIRCUIT DIGEST
lcd.setCursor(0,1); //将光标设置在显示器的第二行
lcd.print("I2C 2 ARDUINO");
//在 LCD延迟(5000)
中打印 I2C ARDUINO ;//延迟5秒 lcd.clear(); //清除液晶显示
3. 在 void loop()
首先我们需要从 Slave 获取数据,所以我们使用requestFrom()和 slave 地址 8 并且我们请求一个字节
Wire.requestFrom(8,1);
使用 Wire.read() 读取接收到的值
字节 MasterReceive = Wire.read();
接下来,我们需要从连接到引脚 A0 的主 arduino POT 读取模拟值
int potvalue = 模拟读取(A0);
我们将该值以一个字节的形式转换为 0 到 127。
字节 MasterSend = map(potvalue,0,1023,0,127);
接下来我们需要发送这些转换后的值,所以我们开始使用带有 8 个地址的从 arduino 进行传输
Wire.beginTransmission(8);
Wire.write(MasterSend);
Wire.endTransmission();
接下来,我们以 500 微秒的延迟显示从从 arduino 接收到的值,并且我们不断地接收并显示这些值。
lcd.setCursor(0,0); //在 LCD 第一行设置光标
lcd.print(">> Master <<"); //打印 >> Master << at LCD
lcd.setCursor(0,1); //在 LCD 的第二行设置光标
lcd.print("SlaveVal:"); //打印 SlaveVal: in LCD
lcd.print(MasterReceive); //在 LCD 中打印 MasterReceive 从 Slave
Serial.println("Master Received From Slave"); //在串行监视器中打印
Serial.println(MasterReceive);
延迟(500);
lcd.clear();
从机Arduino编程讲解
1.和master一样,首先我们需要包含使用I2C通信功能的Wire库和使用LCD功能的LCD库。还为 16x2 LCD 定义 LCD 引脚。
#include
#include
LiquidCrystal lcd(2, 7, 8, 9, 10, 11);
2. 在 void setup()
我们以波特率 9600 开始串行通信。
序列号.开始(9600);
接下来我们在引脚 (A4, A5) 上启动 I2C 通信,从地址为 8。这里指定从地址很重要。
Wire.begin(8);
接下来我们需要在 Slave 接收到 master 的值以及 Master 从 Slave 请求值时调用该函数
Wire.onReceive(receiveEvent);
Wire.onRequest(requestEvent);
接下来我们以 16X2 模式初始化 LCD 显示模块并显示欢迎信息并在五秒钟后清除。
lcd.开始(16,2);//初始化液晶显示器
lcd.setCursor(0,0); //将光标设置在显示器的第一行
lcd.print("Circuit Digest"); //在 LCD 中打印 CIRCUIT DIGEST
lcd.setCursor(0,1); //将光标设置在显示器的第二行
lcd.print("I2C 2 ARDUINO");
//在 LCD延迟(5000)
中打印 I2C ARDUINO ;//延迟5秒 lcd.clear(); //清除液晶显示
3.接下来我们有两个函数,一个是请求事件,一个是接收事件
对于请求事件
当主站从从站请求值时,该函数将被执行。此函数确实从从 POT 获取输入值并将其转换为 7 位并将该值发送到主控。
void requestEvent()
{
int potvalue = analogRead(A0);
字节 SlaveSend = map(potvalue,0,1023,0,127);
Wire.write(SlaveSend);
}
对于接收事件
当主机向从机地址(8)的从机发送数据时,该函数将被执行。此函数从 master 读取接收到的值并存储在byte类型的变量中。
void receiveEvent (int howMany
{
SlaveReceived = Wire.read();
}
4. 在无效循环()中:
我们在 LCD 显示模块中连续显示从主机接收到的值。
无效循环(无效)
{
lcd.setCursor(0,0);//在 LCD 第一行设置光标
lcd.print(">> Slave <<"); //打印 >> Slave << at LCD
lcd.setCursor(0,1); //在 LCD 的第二行设置光标
lcd.print("MasterVal:"); //打印 MasterVal: in LCD
lcd.print(SlaveReceived); //在 LCD 中打印从 Master 接收到的 SlaveReceived 值
Serial.println("Slave Received From Master:"); //在串行监视器中打印
Serial.println(SlaveReceived);
延迟(500);
lcd.clear();
}
通过旋转一侧的电位器,您可以在另一侧的 LCD 上看到不同的值:
所以这就是I2C 通信在 Arduino 中发生的方式,这里我们使用两个 Arduino 来演示不仅可以发送数据,还可以使用 I2C 通信来接收数据。所以现在您可以将任何 I2C 传感器连接到 Arduino。//I2C MASTER CODE
//两个Arduino之间的I2C通信
//Circuit Digest
//Pramoth.T
#include
#include
LiquidCrystal lcd(2, 7, 8, 9, 10, 11); //定义 LCD 模块引脚 (RS,EN,D4,D5,D6,D7)
无效设置()
{
lcd.begin(16,2); //初始化液晶显示器
lcd.setCursor(0,0); //将光标设置在显示器的第一行
lcd.print("Circuit Digest"); //在 LCD 中打印 CIRCUIT DIGEST
lcd.setCursor(0,1); //将光标设置在显示器的第二行
lcd.print("I2C 2 ARDUINO");
//在 LCD延迟(5000)中打印 I2C ARDUINO ;//延迟5秒
lcd.clear(); //清除 LCD 显示
Serial.begin(9600); //以 9600 波特率开始串行通信
Wire.begin(); //在引脚 (A4,A5) 处开始 I2C 通信
}
无效循环()
{
Wire.requestFrom(8,1); // 从从机 arduino 请求 1 个字节 (8)
byte MasterReceive = Wire.read(); // 从从 arduino 接收一个字节并存储在 MasterReceive
int potvalue = analogRead(A0); // 从 POT (0-5V) 字节中读取模拟值
MasterSend = map(potvalue,0,1023,0,127); //将数字值(0到1023)转换为(0到127)
Wire.beginTransmission(8); // 开始传输到从 arduino (8)
Wire.write(MasterSend); // 发送一个字节转换后的 POT 值到从
机 Wire.endTransmission(); // 停止传输
lcd.setCursor(0,0); //在 LCD 第一行设置光标
lcd.print(">> Master <<"); //打印 >> Master << at LCD
lcd.setCursor(0,1); //在 LCD 的第二行设置光标
lcd.print("SlaveVal:"); //打印 SlaveVal: in LCD
lcd.print(MasterReceive); //在 LCD 中打印 MasterReceive 从 Slave
Serial.println("Master Received From Slave"); //在串行监视器中打印
Serial.println(MasterReceive);
延迟(500);
lcd.clear();
}
从 Arduino 编程
//I2C SLAVE CODE
//两个Arduino之间的I2C通信
//CircuitDigest
//Pramoth.T
#include
#include
LiquidCrystal lcd(2, 7, 8, 9, 10, 11); //定义 LCD 模块引脚 (RS,EN,D4,D5,D6,D7)
字节 SlaveReceived = 0;
无效设置()
{
lcd.begin(16,2); //初始化液晶显示器
lcd.setCursor(0,0); //将光标设置在显示器的第一行
lcd.print("Circuit Digest"); //在 LCD 中打印 CIRCUIT DIGEST
lcd.setCursor(0,1); //将光标设置在显示器的第二行
lcd.print("I2C 2 ARDUINO");
//在 LCD延迟(5000)中打印 I2C ARDUINO ;//延迟5秒
lcd.clear(); //清除 LCD 显示
Serial.begin(9600); //以 9600 波特率开始串行通信
Wire.begin(8); //开始I2C通信,从地址为8在引脚(A4,A5)
Wire.onReceive(receiveEvent); //Slave 接收到 master 的值时的函数调用
Wire.onRequest(requestEvent); //Master向Slave请求值时的函数调用
}
无效循环(无效)
{
lcd.setCursor(0,0);//在 LCD 第一行设置光标
lcd.print(">> Slave <<"); //打印 >> Slave << at LCD
lcd.setCursor(0,1); //在 LCD 的第二行设置光标
lcd.print("MasterVal:"); //打印 MasterVal: in LCD
lcd.print(SlaveReceived); //在 LCD 中打印从 Master 接收到的 SlaveReceived 值
Serial.println("Slave Received From Master:"); //在串行监视器中打印
Serial.println(SlaveReceived);
延迟(500);
lcd.clear();
}
void receiveEvent (int howMany) //Slave 接收到 master 的值时调用该函数
{
SlaveReceived = Wire.read(); //用于读取从master接收到的值并存储在变量SlaveReceived中
}
void requestEvent() //当Master想要从slave获取值时调用这个函数
{
int potvalue = analogRead(A0); // 从 POT (0-5V) 读取模拟值
byte SlaveSend = map(potvalue,0,1023,0,127); // 将potvalue数字值(0到1023)转换为(0到127)
Wire.write(SlaveSend); // 将一个字节转换后的 POT 值发送给主控
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)