今天我們來玩兒I2C。
I2C概述
I2C全稱是Inter-Integrated Circuit,是飛利浦半導體公司(06年遷移到NXP了)在1982年發明的,是使用非常廣泛的一種通信協議,很多傳感器、存儲芯片、OLED等,都是在使用I2C。標準輸出模式下能達到100kbps的傳輸速率,快速模式下能達到400kbps的傳輸速率,高速模式下能達到3.4Mbps,超高速下最快能達到5Mbps。
SDA-- 數據信號
I2C主機與從機之間共享時鐘信號,時鐘始終由主機控制,總線下面可以掛多個設備,是一種同步,多主,多從,半雙工的通信協議,下面我們簡單介紹一下通信原理:
默認情況下,兩條線都被上拉,SCL=1,SDA=1。
啟動與停止信號:
通信開始,要先發開啟動信號,結束的時候,要發送結束信號。
開始信號由主設備發出啟動,具體為在SCL高電平期間,SDA從高電平切換到低電平;
停止信號由主設備發出結束,具體為在SCL高電平期間,SDA從低電平切換到高電平;
當然,在傳輸過程中,有時候需要更改數據方向,重新傳輸等,我們沒必要發停止信號,直接重新發啟動信號啟動即可。
地址字節
我們的總線上可能掛很多從設備,在我們主設備發送了啟動信號之后,總線上的從設備就都被“喚醒”了,等著主設備發送地址寵幸。所以這里有一個從機地址的概念,從機地址以8位字節發送的,MSB在前,最后一位表示接下來讀或寫,所以高7位構成了從機地址,也可以看出,同一個總線上,可以尋址128個從設備。
一旦從設備的地址匹配,就繼續讀取最后一位,低電平代表寫入,高電平代表讀取。其它從設備就忽略后面的數據。
ACK與NACK
在每個字節傳輸之后,接收設備發送一個應答信號,確認或者不確認,接收設備通過在SCL高電平期間,將SDA拉低生成一個確認信號ACK,拉高生成一個不確認信號NACK,這里ACK主要用于表示字節正確傳輸了,NACK表示數據傳輸有錯誤,需要從新發送。應答信號主設備,從設備都可以產生,比如,主設備從從設備讀取最后一個字節的數據后,就要發送NACK結束傳輸。
數據信號
數據以8位字節格式傳輸,高字節在前,傳輸的字節數量沒有限制,但是每個字節后面必須要有一個數據接收方產生的應答信號。傳輸過程中,SCL為低的時候,SDA數據可以改變,SCL為高的時候,SDA的數據必須穩定。
命令字節
當寫入或讀取從設備中特定寄存器時,主機首先要向已尋址的從機寫入寄存器地址,其實也是一個數據字節,我們這里稱之為命令字節。
寫入設備
主設備在發出啟動信號之后,緊著著發送要操作從設備的地址,最后一位為低電平表示接下來寫入數據,然后在時鐘信號下一位一位的寫入數據,在從設備發出ACK應答之后,發送結束信號結束通信。
讀取數據
主設備在發出啟動信號之后,緊著著發送要操作從設備的地址,最后一位為高電平表示接下來讀取數據,然后接管SDA數據線并在時鐘的控制下向主設備發送數據,主設備同樣要在每個字節接收完畢的時候發送ACK響應,當主設備不想接收的時候,就在最后一個字節接收后發送NACK響應,然后恢復對總線的控制并發送結束信號。
SCL的控制權始終在主機這里。
當然,實際還要很多組合傳輸協議,這里由于篇幅問題就不展開說了,基本上大同小異,我們根據不同設備的數據手冊來傳輸就可以啦。I2C還有很多特性,快速命令,仲裁,多主控等等,普通的應用接觸不到,感興趣的小伙伴自行研究下。
硬件
ESP32有2個硬件I2C總線接口,接口可以配置為主機或從機模式,支持如下特性:
- 標準模式 (100 Kbit/s)
- 快速模式 (400 Kbit/s)
- 高達 5 MHz,但受 SDA 上拉強度的限制
- 7位/10位尋址模式
- 雙尋址模式,用戶可以通過編程命令寄存器來控制 I2C 接口,讓他們有更大的靈活性
SDA與SCL是低電平有效的,所以我們應該在兩根數據線上用電阻上拉,IO內部也是開漏輸出的,一般5V系統接4.7K上拉,3.3V系統接2.4K上拉即可。ESP32上,SDA默認連接GPIO21,SCL默認連接GPIO22,當然,我們可以在代碼中配置到任何引腳。
軟件
啟動I2C
啟動Wire庫并作為主機或者從機加入總線,這個函數調用一次即可,參數為7位從機地址,不帶參數就以主機的形式加入總線。
Wire.begin();
Wire.begin(address)
主設備從從設備請求字節
由主設備向從設備請求字節,之后用available()和read()函數讀取字節,第三個參數位為stop,在請求后會發送停止消息,釋放I2C總線,否則總線就不會被釋放。
Wire.requestFrom(address, quantity);
Wire.requestFrom(address, quantity, stop);
給指定地址的從設備傳輸數據
給指定地址的從設備傳輸數據,之后調用write()函數排隊傳輸字節,要通過endTransmission()結束傳輸。
Wire.beginTransmission(address)
endTransmission()有以下幾個返回結果:
- 0:成功
- 1:數據太長,無法放入發送緩沖區
- 2:在發送地址時收到 NACK
- 3:在發送數據時收到 NACK
- 4:其他錯誤
寫數據
向從設備寫入數據,在調用 beginTransmission() 和 endTransmission() 之間。
Wire.write(value)
Wire.write(string)
Wire.write(data, length)
舉個例子
#include < Wire.h >
byte val = 0;
void setup()
{
Wire.begin(); // join i2c bus
}
void loop()
{
Wire.beginTransmission(44); // transmit to device #44 (0x2c)
// device address is specified in datasheet
Wire.write(val); // sends value byte
Wire.endTransmission(); // stop transmitting
val++; // increment value
if(val == 64) // if reached 64th position (max)
{
val = 0; // start over from lowest value
}
delay(500);
}
讀數據
調用requestFrom()后從從設備讀取數據。
Wire.read()
舉個例子
#include < Wire.h >
void setup()
{
Wire.begin(); // join i2c bus (address optional for master)
Serial.begin(9600); // start serial for output
}
void loop()
{
Wire.requestFrom(2, 6); // request 6 bytes from slave device #2
while(Wire.available()) // slave may send less than requested
{
char c = Wire.read(); // receive a byte as character
Serial.print(c); // print the character
}
delay(500);
}
還有其它一些函數,例如修改時鐘頻率等等,大家用到的時候自行了解一下。
完整程序
這里我們用一個例子來演示一下,I2C啟動之后,我們開始掃描總線上存在的設備,并通過串口打印結果出來,我在I2C下面接了一個OLED的設備。
#include "Wire.h"
void setup(){
Serial.begin(115200);
Serial.println();
Serial.println("Scanning for I2C Devices ...");
Serial.print("rn");
int I2CDevices = 0;
byte address;
Wire.begin();
for (address = 1; address < 127; address++)
{
Wire.beginTransmission(address);
if (Wire.endTransmission() == 0)
{
Serial.print("Found I2C Device: ");
Serial.print(" (0x");
if (address < 16)
{
Serial.print("0");
}
Serial.print(address, HEX);
Serial.println(")");
I2CDevices++;
}
}
if (I2CDevices == 0)
{
Serial.println("沒有發現I2C設備!n");
}
else
{
Serial.print("發現了");
Serial.print(I2CDevices);
Serial.println("個I2C設備!n");
}
}
void loop(){
}
Wire.endTransmission()返回0,代表這個地址通信成功,我們就認為總線上存在這個地址的設備。
I2C OLED
I2C只是個通信協議,具體的還是要結合實物來演示,比如一些傳感器或者屏幕,這里我們用I2C協議的0.96寸OLED屏幕來演示下:
OLED使用SSD1306控制芯片,所以我們需要下載一個庫SSD1306,另外還需要配合圖形庫GFX操作,代碼中,我們先包含對應頭文件,然后創建一個Adafruit_SSD1306對象,第三個參數是用的I2C對象。
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
初始化時候用display.begin(SSD1306_SWITCHCAPVCC, 0x3C)初始化顯示對象,傳入地址,然后就可以自由簡單的顯示我們想要顯示的數據了。
關于Adafruit_GFX庫,非常強大的一個圖形庫,我們后面單獨講解具體的原理,這里先了解一下即可。
完整程序
#include < Wire.h >
#include < Adafruit_GFX.h >
#include < Adafruit_SSD1306.h >
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
void setup() {
Serial.begin(115200);
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;);
}
delay(1000);
display.display();
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(0,0);
display.print("CHIPHOME");
display.display();
display.setCursor(0,8);
display.print("12345678");
display.display();
delay(1000);
}
void loop() {
}
SSD1306示例代碼演示:
感謝大家,關于ESP32的學習,希望大家Enjoy!
-
傳感器
+關注
關注
2551文章
51207瀏覽量
754535 -
通信協議
+關注
關注
28文章
894瀏覽量
40334 -
總線
+關注
關注
10文章
2891瀏覽量
88162 -
I2C
+關注
關注
28文章
1490瀏覽量
123983 -
ESP32
+關注
關注
18文章
971瀏覽量
17357
發布評論請先 登錄
相關推薦
評論