<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>
"); //-->

博客專(zhuān)欄

EEPW首頁(yè) > 博客 > 老宇哥帶你玩轉 ESP32:07 I2C協(xié)議,看這一篇就夠了

老宇哥帶你玩轉 ESP32:07 I2C協(xié)議,看這一篇就夠了

發(fā)布人:芯片之大家 時(shí)間:2023-07-04 來(lái)源:工程師 發(fā)布文章

今天我們來(lái)玩兒I2C。

I2C概述

I2C全稱(chēng)是Inter-Integrated Circuit,是飛利浦半導體公司(06年遷移到NXP了)在1982年發(fā)明的,是使用非常廣泛的一種通信協(xié)議,很多傳感器、存儲芯片、OLED等,都是在使用I2C。標準輸出模式下能達到100kbps的傳輸速率,快速模式下能達到400kbps的傳輸速率,高速模式下能達到3.4Mbps,超高速下最快能達到5Mbps。

與UART一樣,IIC僅用兩條線(xiàn)在設備間通信:

image.png


SCL -- 時(shí)鐘信號

SDA -- 數據信號

I2C主機與從機之間共享時(shí)鐘信號,時(shí)鐘始終由主機控制,總線(xiàn)下面可以?huà)於鄠€(gè)設備,是一種同步,多主,多從,半雙工的通信協(xié)議,下面我們簡(jiǎn)單介紹一下通信原理:

image.png


默認情況下,兩條線(xiàn)都被上拉,SCL=1,SDA=1。

啟動(dòng)與停止信號:

通信開(kāi)始,要先發(fā)開(kāi)啟動(dòng)信號,結束的時(shí)候,要發(fā)送結束信號。

開(kāi)始信號由主設備發(fā)出啟動(dòng),具體為在SCL高電平期間,SDA從高電平切換到低電平;

停止信號由主設備發(fā)出結束,具體為在SCL高電平期間,SDA從低電平切換到高電平;

image.png


當然,在傳輸過(guò)程中,有時(shí)候需要更改數據方向,重新傳輸等,我們沒(méi)必要發(fā)停止信號,直接重新發(fā)啟動(dòng)信號啟動(dòng)即可。

image.png


地址字節

我們的總線(xiàn)上可能掛很多從設備,在我們主設備發(fā)送了啟動(dòng)信號之后,總線(xiàn)上的從設備就都被“喚醒”了,等著(zhù)主設備發(fā)送地址寵幸。所以這里有一個(gè)從機地址的概念,從機地址以8位字節發(fā)送的,MSB在前,最后一位表示接下來(lái)讀或寫(xiě),所以高7位構成了從機地址,也可以看出,同一個(gè)總線(xiàn)上,可以尋址128個(gè)從設備。

一旦從設備的地址匹配,就繼續讀取最后一位,低電平代表寫(xiě)入,高電平代表讀取。其它從設備就忽略后面的數據。

ACK與NACK

在每個(gè)字節傳輸之后,接收設備發(fā)送一個(gè)應答信號,確認或者不確認,接收設備通過(guò)在SCL高電平期間,將SDA拉低生成一個(gè)確認信號ACK,拉高生成一個(gè)不確認信號NACK,這里ACK主要用于表示字節正確傳輸了,NACK表示數據傳輸有錯誤,需要從新發(fā)送。應答信號主設備,從設備都可以產(chǎn)生,比如,主設備從從設備讀取最后一個(gè)字節的數據后,就要發(fā)送NACK結束傳輸。

image.png


數據信號

數據以8位字節格式傳輸,高字節在前,傳輸的字節數量沒(méi)有限制,但是每個(gè)字節后面必須要有一個(gè)數據接收方產(chǎn)生的應答信號。傳輸過(guò)程中,SCL為低的時(shí)候,SDA數據可以改變,SCL為高的時(shí)候,SDA的數據必須穩定。

image.png


命令字節

當寫(xiě)入或讀取從設備中特定寄存器時(shí),主機首先要向已尋址的從機寫(xiě)入寄存器地址,其實(shí)也是一個(gè)數據字節,我們這里稱(chēng)之為命令字節。

寫(xiě)入設備

主設備在發(fā)出啟動(dòng)信號之后,緊著(zhù)著(zhù)發(fā)送要操作從設備的地址,最后一位為低電平表示接下來(lái)寫(xiě)入數據,然后在時(shí)鐘信號下一位一位的寫(xiě)入數據,在從設備發(fā)出ACK應答之后,發(fā)送結束信號結束通信。

image.png


讀取數據

主設備在發(fā)出啟動(dòng)信號之后,緊著(zhù)著(zhù)發(fā)送要操作從設備的地址,最后一位為高電平表示接下來(lái)讀取數據,然后接管SDA數據線(xiàn)并在時(shí)鐘的控制下向主設備發(fā)送數據,主設備同樣要在每個(gè)字節接收完畢的時(shí)候發(fā)送ACK響應,當主設備不想接收的時(shí)候,就在最后一個(gè)字節接收后發(fā)送NACK響應,然后恢復對總線(xiàn)的控制并發(fā)送結束信號。

SCL的控制權始終在主機這里。

image.png


當然,實(shí)際還要很多組合傳輸協(xié)議,這里由于篇幅問(wèn)題就不展開(kāi)說(shuō)了,基本上大同小異,我們根據不同設備的數據手冊來(lái)傳輸就可以啦。I2C還有很多特性,快速命令,仲裁,多主控等等,普通的應用接觸不到,感興趣的小伙伴自行研究下。

硬件

ESP32有2個(gè)硬件I2C總線(xiàn)接口,接口可以配置為主機或從機模式,支持如下特性:

  • 標準模式 (100 Kbit/s)

  • 快速模式 (400 Kbit/s)

  • 高達 5 MHz,但受 SDA 上拉強度的限制

  • 7位/10位尋址模式

  • 雙尋址模式,用戶(hù)可以通過(guò)編程命令寄存器來(lái)控制 I2C 接口,讓他們有更大的靈活性

SDA與SCL是低電平有效的,所以我們應該在兩根數據線(xiàn)上用電阻上拉,IO內部也是開(kāi)漏輸出的,一般5V系統接4.7K上拉,3.3V系統接2.4K上拉即可。ESP32上,SDA默認連接GPIO21,SCL默認連接GPIO22,當然,我們可以在代碼中配置到任何引腳。

image.png


軟件

啟動(dòng)I2C

啟動(dòng)Wire庫并作為主機或者從機加入總線(xiàn),這個(gè)函數調用一次即可,參數為7位從機地址,不帶參數就以主機的形式加入總線(xiàn)。

Wire.begin();Wire.begin(address)

主設備從從設備請求字節

由主設備向從設備請求字節,之后用available()和read()函數讀取字節,第三個(gè)參數位為stop,在請求后會(huì )發(fā)送停止消息,釋放I2C總線(xiàn),否則總線(xiàn)就不會(huì )被釋放。

Wire.requestFrom(address, quantity);Wire.requestFrom(address, quantity, stop);

給指定地址的從設備傳輸數據

給指定地址的從設備傳輸數據,之后調用write()函數排隊傳輸字節,要通過(guò)endTransmission()結束傳輸。

Wire.beginTransmission(address)

endTransmission()有以下幾個(gè)返回結果:

  • 0:成功

  • 1:數據太長(cháng),無(wú)法放入發(fā)送緩沖區

  • 2:在發(fā)送地址時(shí)收到 NACK

  • 3:在發(fā)送數據時(shí)收到 NACK

  • 4:其他錯誤

寫(xiě)數據

向從設備寫(xiě)入數據,在調用 beginTransmission() 和 endTransmission() 之間。

Wire.write(value)
Wire.write(string)
Wire.write(data, length)

舉個(gè)例子

#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()

舉個(gè)例子

#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);
}

還有其它一些函數,例如修改時(shí)鐘頻率等等,大家用到的時(shí)候自行了解一下。

完整程序

這里我們用一個(gè)例子來(lái)演示一下,I2C啟動(dòng)之后,我們開(kāi)始掃描總線(xiàn)上存在的設備,并通過(guò)串口打印結果出來(lái),我在I2C下面接了一個(gè)OLED的設備。

#include "Wire.h"void setup(){
  Serial.begin(115200); 
  Serial.println();
  Serial.println("Scanning for I2C Devices ...");
  Serial.print("\r\n");  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("沒(méi)有發(fā)現I2C設備!\n");
  }  else
  {   
    Serial.print("發(fā)現了");
    Serial.print(I2CDevices);
    Serial.println("個(gè)I2C設備!\n");  
  }  
}

void loop(){
}

Wire.endTransmission()返回0,代表這個(gè)地址通信成功,我們就認為總線(xiàn)上存在這個(gè)地址的設備。

image.png


I2C OLED

I2C只是個(gè)通信協(xié)議,具體的還是要結合實(shí)物來(lái)演示,比如一些傳感器或者屏幕,這里我們用I2C協(xié)議的0.96寸OLED屏幕來(lái)演示下:

image.png


OLED使用SSD1306控制芯片,所以我們需要下載一個(gè)庫SSD1306,另外還需要配合圖形庫GFX操作,代碼中,我們先包含對應頭文件,然后創(chuàng )建一個(gè)Adafruit_SSD1306對象,第三個(gè)參數是用的I2C對象。

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

初始化時(shí)候用display.begin(SSD1306_SWITCHCAPVCC, 0x3C)初始化顯示對象,傳入地址,然后就可以自由簡(jiǎn)單的顯示我們想要顯示的數據了。

關(guān)于A(yíng)dafruit_GFX庫,非常強大的一個(gè)圖形庫,我們后面單獨講解具體的原理,這里先了解一下即可。

完整程序

#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 pixelsAdafruit_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示例代碼演示:

image.png


*博客內容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀(guān)點(diǎn),如有侵權請聯(lián)系工作人員刪除。



關(guān)鍵詞: 協(xié)議

相關(guān)推薦

技術(shù)專(zhuān)區

關(guān)閉
国产精品自在自线亚洲|国产精品无圣光一区二区|国产日产欧洲无码视频|久久久一本精品99久久K精品66|欧美人与动牲交片免费播放
<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>