今天我們將學習另一種串行通信協議: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 值發送給主控
}
?
評論
查看更多