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