今天我們將學(xué)習(xí)另一種串行通信協(xié)議:I2C(Inter Integrated Circuits)。I2C與SPI相比,I2C只有兩根線,SPI使用四根線,I2C可以有多個主從,而SPI只能有一個主和多個從。因此,在一個項(xiàng)目中有多個微控制器需要成為主控,然后使用 I2C。I2C通信一般用于與陀螺儀、加速度計、氣壓傳感器、LED顯示屏等進(jìn)行通信。
在這個Arduino I2C 教程中,我們將使用兩個 arduino 板之間的 I2C 通信,并使用電位器相互發(fā)送(0 到 127)值。值將顯示在連接到每個 Arduino的16x2 LCD上。在這里,一個 Arduino 將充當(dāng) Master,另一個將充當(dāng) Slave。那么讓我們從I2C通信的介紹開始。
什么是 I2C 通信協(xié)議?
術(shù)語 IIC 代表“ Inter Integrated Circuits ”。在某些地方,它通常表示為 I2C 或 I 平方 C,甚至表示為 2 線接口協(xié)議 (TWI),但它們的含義相同。I2C 是一種同步通信協(xié)議,意思是共享信息的兩個設(shè)備必須共享一個公共時鐘信號。它只有兩根線來共享信息,其中一根用于公雞信號,另一根用于發(fā)送和接收數(shù)據(jù)。
I2C 通信如何工作?
I2C 通信最早是由 Phillips 引入的。如前所述,它有兩條線,這兩條線將跨兩個設(shè)備連接。這里一個設(shè)備稱為 主 設(shè)備,另一個設(shè)備稱為 從設(shè)備。通信應(yīng)該并且將始終發(fā)生在兩個 Master 和 Slave之間。I2C 通信的優(yōu)點(diǎn)是可以將多個從設(shè)備連接到一個主設(shè)備。
完整的通信通過這兩條線進(jìn)行,即串行時鐘(SCL)和串行數(shù)據(jù)(SDA)。
串行時鐘(SCL): 與從機(jī)共享主機(jī)產(chǎn)生的時鐘信號
串行數(shù)據(jù) (SDA): 在主機(jī)和從機(jī)之間發(fā)送數(shù)據(jù)。
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的電壓電平?jīng)]有預(yù)先定義。I2C 通信靈活,即設(shè)備采用 5v 電壓供電,可以使用 5v 進(jìn)行 I2C 通信,3.3v 設(shè)備可以使用 3v 進(jìn)行 I2C 通信。但是,如果兩個運(yùn)行在不同電壓下的設(shè)備需要使用 I2C 進(jìn)行通信怎么辦?5V I2C 總線不能與 3.3V 設(shè)備連接。在這種情況下,電壓轉(zhuǎn)換器用于匹配兩個 I2C 總線之間的電壓電平。
有一些條件構(gòu)成交易。傳輸?shù)某跏蓟瘡?SDA 的下降沿開始,在下圖中定義為“START”條件,其中主機(jī)將 SCL 保持為高電平,同時將 SDA 設(shè)置為低電平。
如下圖所示,
SDA 的下降沿是 START 條件的硬件觸發(fā)。此后,同一總線上的所有設(shè)備都進(jìn)入偵聽模式。
以同樣的方式,SDA 的上升沿停止傳輸,在上圖中顯示為“停止”條件,其中主機(jī)將 SCL 保持為高電平,同時釋放 SDA 變?yōu)楦唠娖健K?SDA 的上升沿停止傳輸。
R/W 位指示后續(xù)字節(jié)的傳輸方向,如果為高表示從機(jī)發(fā)送,如果為低表示主機(jī)將發(fā)送。
每個比特在每個時鐘周期傳輸,因此傳輸一個字節(jié)需要 8 個時鐘周期。在發(fā)送或接收每個字節(jié)后,為 ACK/NACK(確認(rèn)/未確認(rèn))保留第 9 個時鐘周期。該 ACK 位由從機(jī)或主機(jī)根據(jù)情況生成。對于 ACK 位,SDA 在第 9個時鐘周期由主機(jī)或從機(jī)設(shè)置為低。所以它是低的它被認(rèn)為是ACK,否則是NACK。
在哪里使用 I2C 通信?
I2C 通信僅用于 短距離通信。它在一定程度上肯定是可靠的,因?yàn)樗幸粋€同步的時鐘脈沖來讓它變得聰明。該協(xié)議主要用于與傳感器或其他必須向主機(jī)發(fā)送信息的設(shè)備通信。當(dāng)微控制器必須使用最少的電線與許多其他從模塊通信時,它非常方便。如果您正在尋找遠(yuǎn)程通信,您應(yīng)該嘗試 RS232,如果您正在尋找更可靠的通信,您應(yīng)該嘗試 SPI 協(xié)議。
Arduino中的I2C
下圖顯示了 Arduino UNO 中的 I2C 引腳。
在我們開始使用兩個 Arduino 進(jìn)行 I2C 編程之前。我們需要了解Arduino IDE 中使用的Wire 庫。
《 Wire.h 》 庫包含在程序中,用于使用以下函數(shù)進(jìn)行 I2C 通信。
1. Wire.begin(地址):
用途: 該庫用于與 I2C 設(shè)備進(jìn)行通信。這將啟動 Wire 庫并作為主機(jī)或從機(jī)加入 I2C 總線。
地址:7 位從機(jī)地址是可選的,如果未指定地址,它會像這樣 [Wire.begin()] 作為主機(jī)加入總線。
2. Wire.read():
用途:此函數(shù)用于讀取從主設(shè)備或從設(shè)備接收到的字節(jié),或者在調(diào)用 requestFrom()后從從設(shè)備傳輸?shù)街髟O(shè)備,或者從主設(shè)備傳輸?shù)綇脑O(shè)備 。
3.Wire.write():
用途:該函數(shù)用于向從設(shè)備或主設(shè)備寫入數(shù)據(jù)。
從機(jī)到主機(jī):當(dāng)主機(jī)使用Wire.RequestFrom()時,從機(jī)向主機(jī)寫入數(shù)據(jù)。
主到從:對于從主設(shè)備到從設(shè)備的傳輸,在調(diào)用Wire.beginTransmission()和Wire.endTransmission( )之間使用Wire.write()。
Wire.write()可以寫成:
Wire.write(值)
value:作為單個字節(jié)發(fā)送的值。
Wire.write(字符串):
string:作為一系列字節(jié)發(fā)送的字符串。
Wire.write(數(shù)據(jù),長度):
data:以字節(jié)形式發(fā)送的數(shù)據(jù)數(shù)組
長度:要傳輸?shù)淖止?jié)數(shù)。
4. Wire.beginTransmission(地址):
用途:此函數(shù)用于開始向具有給定從地址的 I2C 設(shè)備進(jìn)行傳輸。隨后,使用write()函數(shù)構(gòu)建用于傳輸?shù)淖止?jié)隊列, 然后通過調(diào)用 endTransmission()函數(shù)傳輸它們。發(fā)送設(shè)備的 7 位地址。
5. Wire.endTransmission();
用途:此函數(shù)用于結(jié)束由 beginTransmission()開始的到從設(shè)備的傳輸,并傳輸由Wire.write() 排隊的字節(jié) 。
6. Wire.onRequest();
使用:當(dāng)主設(shè)備使用Wire.requestFrom()從從設(shè)備請求數(shù)據(jù)時,將調(diào)用此函數(shù)。在這里,我們可以包含Wire.write()函數(shù)來向主設(shè)備發(fā)送數(shù)據(jù)。
7. Wire.onReceive();
使用:當(dāng)從設(shè)備接收到來自主設(shè)備的數(shù)據(jù)時調(diào)用此函數(shù)。這里我們可以包含Wire.read(); 函數(shù)讀取從主機(jī)發(fā)送的數(shù)據(jù)。
8. Wire.requestFrom(地址,數(shù)量);
用途:該函數(shù)用于主設(shè)備向從設(shè)備請求字節(jié)。函數(shù)Wire.read()用于讀取從設(shè)備發(fā)送的數(shù)據(jù)。
地址:請求字節(jié)的設(shè)備的 7 位地址
數(shù)量:要請求的字節(jié)數(shù)
所需組件
Arduino Uno(2 號)
16X2液晶顯示模組
10K 電位器 (4-Nos)
面包板
連接電線
電路原理圖
工作說明
這里為了演示Arduino 中的 I2C 通信,我們使用兩個 Arduino UNO,兩個16X2 LCD 顯示器相互連接,并在兩個 arduino 上使用兩個電位器來確定從主機(jī)到從機(jī)和從機(jī)到主機(jī)的發(fā)送值(0 到 127),方法是改變電位器。
我們通過使用電位器將 arduino 引腳 A0 的輸入模擬值從(0 到 5V)獲取,并將它們轉(zhuǎn)換為模擬到數(shù)字值(0 到 1023)。然后這些 ADC 值進(jìn)一??步轉(zhuǎn)換為(0 到 127),因?yàn)槲覀冎荒芡ㄟ^ I2C 通信發(fā)送 7 位數(shù)據(jù)。I2C 通信通過兩個 arduino 的引腳 A4 和 A5 上的兩條線進(jìn)行。
從 Arduino 的 LCD 上的值將通過改變主端的 POT 來改變,反之亦然。
Arduino 中的 I2C 編程
本教程有兩個程序,一個用于主 Arduino,另一個用于從 Arduino。雙方的完整方案在本項(xiàng)目結(jié)束時附有演示視頻。
Arduino大師編程講解
1.首先我們需要包含使用I2C通信功能的Wire庫和使用LCD功能的LCD庫。還為 16x2 LCD 定義 LCD 引腳。在此處了解有關(guān)將 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); //將光標(biāo)設(shè)置在顯示器的第一行
lcd.print("Circuit Digest"); //在 LCD 中打印 CIRCUIT DIGEST
lcd.setCursor(0,1); //將光標(biāo)設(shè)置在顯示器的第二行
lcd.print("I2C 2 ARDUINO");
//在 LCD延遲(5000)
中打印 I2C ARDUINO ;//延遲5秒 lcd.clear(); //清除液晶顯示
3. 在 void loop()
首先我們需要從 Slave 獲取數(shù)據(jù),所以我們使用requestFrom()和 slave 地址 8 并且我們請求一個字節(jié)
Wire.requestFrom(8,1);
使用 Wire.read() 讀取接收到的值
字節(jié) MasterReceive = Wire.read();
接下來,我們需要從連接到引腳 A0 的主 arduino POT 讀取模擬值
int potvalue = 模擬讀取(A0);
我們將該值以一個字節(jié)的形式轉(zhuǎn)換為 0 到 127。
字節(jié) MasterSend = map(potvalue,0,1023,0,127);
接下來我們需要發(fā)送這些轉(zhuǎn)換后的值,所以我們開始使用帶有 8 個地址的從 arduino 進(jìn)行傳輸
Wire.beginTransmission(8);
Wire.write(MasterSend);
Wire.endTransmission();
接下來,我們以 500 微秒的延遲顯示從從 arduino 接收到的值,并且我們不斷地接收并顯示這些值。
lcd.setCursor(0,0); //在 LCD 第一行設(shè)置光標(biāo)
lcd.print(">> Master <<"); //打印 >> Master << at LCD
lcd.setCursor(0,1); //在 LCD 的第二行設(shè)置光標(biāo)
lcd.print("SlaveVal:"); //打印 SlaveVal: in LCD
lcd.print(MasterReceive); //在 LCD 中打印 MasterReceive 從 Slave
Serial.println("Master Received From Slave"); //在串行監(jiān)視器中打印
Serial.println(MasterReceive);
延遲(500);
lcd.clear();
從機(jī)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 請求值時調(diào)用該函數(shù)
Wire.onReceive(receiveEvent);
Wire.onRequest(requestEvent);
接下來我們以 16X2 模式初始化 LCD 顯示模塊并顯示歡迎信息并在五秒鐘后清除。
lcd.開始(16,2);//初始化液晶顯示器
lcd.setCursor(0,0); //將光標(biāo)設(shè)置在顯示器的第一行
lcd.print("Circuit Digest"); //在 LCD 中打印 CIRCUIT DIGEST
lcd.setCursor(0,1); //將光標(biāo)設(shè)置在顯示器的第二行
lcd.print("I2C 2 ARDUINO");
//在 LCD延遲(5000)
中打印 I2C ARDUINO ;//延遲5秒 lcd.clear(); //清除液晶顯示
3.接下來我們有兩個函數(shù),一個是請求事件,一個是接收事件
對于請求事件
當(dāng)主站從從站請求值時,該函數(shù)將被執(zhí)行。此函數(shù)確實(shí)從從 POT 獲取輸入值并將其轉(zhuǎn)換為 7 位并將該值發(fā)送到主控。
void requestEvent()
{
int potvalue = analogRead(A0);
字節(jié) SlaveSend = map(potvalue,0,1023,0,127);
Wire.write(SlaveSend);
}
對于接收事件
當(dāng)主機(jī)向從機(jī)地址(8)的從機(jī)發(fā)送數(shù)據(jù)時,該函數(shù)將被執(zhí)行。此函數(shù)從 master 讀取接收到的值并存儲在byte類型的變量中。
void receiveEvent (int howMany
{
SlaveReceived = Wire.read();
}
4. 在無效循環(huán)()中:
我們在 LCD 顯示模塊中連續(xù)顯示從主機(jī)接收到的值。
無效循環(huán)(無效)
{
lcd.setCursor(0,0);//在 LCD 第一行設(shè)置光標(biāo)
lcd.print(">> Slave <<"); //打印 >> Slave << at LCD
lcd.setCursor(0,1); //在 LCD 的第二行設(shè)置光標(biāo)
lcd.print("MasterVal:"); //打印 MasterVal: in LCD
lcd.print(SlaveReceived); //在 LCD 中打印從 Master 接收到的 SlaveReceived 值
Serial.println("Slave Received From Master:"); //在串行監(jiān)視器中打印
Serial.println(SlaveReceived);
延遲(500);
lcd.clear();
}
通過旋轉(zhuǎn)一側(cè)的電位器,您可以在另一側(cè)的 LCD 上看到不同的值:
所以這就是I2C 通信在 Arduino 中發(fā)生的方式,這里我們使用兩個 Arduino 來演示不僅可以發(fā)送數(shù)據(jù),還可以使用 I2C 通信來接收數(shù)據(jù)。所以現(xiàn)在您可以將任何 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)
無效設(shè)置()
{
lcd.begin(16,2); //初始化液晶顯示器
lcd.setCursor(0,0); //將光標(biāo)設(shè)置在顯示器的第一行
lcd.print("Circuit Digest"); //在 LCD 中打印 CIRCUIT DIGEST
lcd.setCursor(0,1); //將光標(biāo)設(shè)置在顯示器的第二行
lcd.print("I2C 2 ARDUINO");
//在 LCD延遲(5000)中打印 I2C ARDUINO ;//延遲5秒
lcd.clear(); //清除 LCD 顯示
Serial.begin(9600); //以 9600 波特率開始串行通信
Wire.begin(); //在引腳 (A4,A5) 處開始 I2C 通信
}
無效循環(huán)()
{
Wire.requestFrom(8,1); // 從從機(jī) arduino 請求 1 個字節(jié) (8)
byte MasterReceive = Wire.read(); // 從從 arduino 接收一個字節(jié)并存儲在 MasterReceive
int potvalue = analogRead(A0); // 從 POT (0-5V) 字節(jié)中讀取模擬值
MasterSend = map(potvalue,0,1023,0,127); //將數(shù)字值(0到1023)轉(zhuǎn)換為(0到127)
Wire.beginTransmission(8); // 開始傳輸?shù)綇?arduino (8)
Wire.write(MasterSend); // 發(fā)送一個字節(jié)轉(zhuǎn)換后的 POT 值到從
機(jī) Wire.endTransmission(); // 停止傳輸
lcd.setCursor(0,0); //在 LCD 第一行設(shè)置光標(biāo)
lcd.print(">> Master <<"); //打印 >> Master << at LCD
lcd.setCursor(0,1); //在 LCD 的第二行設(shè)置光標(biāo)
lcd.print("SlaveVal:"); //打印 SlaveVal: in LCD
lcd.print(MasterReceive); //在 LCD 中打印 MasterReceive 從 Slave
Serial.println("Master Received From Slave"); //在串行監(jiān)視器中打印
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)
字節(jié) SlaveReceived = 0;
無效設(shè)置()
{
lcd.begin(16,2); //初始化液晶顯示器
lcd.setCursor(0,0); //將光標(biāo)設(shè)置在顯示器的第一行
lcd.print("Circuit Digest"); //在 LCD 中打印 CIRCUIT DIGEST
lcd.setCursor(0,1); //將光標(biāo)設(shè)置在顯示器的第二行
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 的值時的函數(shù)調(diào)用
Wire.onRequest(requestEvent); //Master向Slave請求值時的函數(shù)調(diào)用
}
無效循環(huán)(無效)
{
lcd.setCursor(0,0);//在 LCD 第一行設(shè)置光標(biāo)
lcd.print(">> Slave <<"); //打印 >> Slave << at LCD
lcd.setCursor(0,1); //在 LCD 的第二行設(shè)置光標(biāo)
lcd.print("MasterVal:"); //打印 MasterVal: in LCD
lcd.print(SlaveReceived); //在 LCD 中打印從 Master 接收到的 SlaveReceived 值
Serial.println("Slave Received From Master:"); //在串行監(jiān)視器中打印
Serial.println(SlaveReceived);
延遲(500);
lcd.clear();
}
void receiveEvent (int howMany) //Slave 接收到 master 的值時調(diào)用該函數(shù)
{
SlaveReceived = Wire.read(); //用于讀取從master接收到的值并存儲在變量SlaveReceived中
}
void requestEvent() //當(dāng)Master想要從slave獲取值時調(diào)用這個函數(shù)
{
int potvalue = analogRead(A0); // 從 POT (0-5V) 讀取模擬值
byte SlaveSend = map(potvalue,0,1023,0,127); // 將potvalue數(shù)字值(0到1023)轉(zhuǎn)換為(0到127)
Wire.write(SlaveSend); // 將一個字節(jié)轉(zhuǎn)換后的 POT 值發(fā)送給主控
}
?
評論
查看更多