ESP32 I2C通訊詳細說明以及應用程序講解

ESP32具有兩個I2C總線接口,可用作I2C主站或從站。在本教程中,我們將使用Arduino IDE查看與ESP32的I2C通信協議:如何選擇I2C引腳,將多個I2C設備連接到同一總線以及如何使用兩個I2C總線接口。

在本教程中,我們將介紹以下概念:

  • 將I2C設備與ESP32連接
  • 使用ESP32掃描I2C地址
  • ESP32使用不同的I2C引腳(更改默認I2C引腳)
  • ESP32使用兩個I2C總線接口
  • 我們將使用Arduino IDE對ESP32進行編程,因此在繼續本教程之前,您應該在Arduino IDE中安裝ESP32插件。
  • 具有多個I2C器件的ESP32
  • 1. 相同的總線,不同的地址

    2. 相同的地址·

    ESP32 I2C通訊協議介紹

    I²C是一個同步,多主,多從通信協議。您可以連接:

  • 一個主設備有多個從設備:例如,您的ESP32使用I2C從BME280傳感器讀取數據,並將傳感器讀數寫入I2C OLED顯示屏。
  • 多個主設備控制同一個從設備:例如,兩個ESP32板將數據寫入同一I2C OLED顯示器。
  • 我們在ESP32中多次使用此協議與外部設備(例如傳感器和顯示器)進行通信。在這種情況下,ESP32是主芯片,外部設備是從芯片。

    ESP32 I2C總線接口

    ESP32通過其兩個I2C總線接口支持I2C通信,這兩個接口可以用作I2C主設備或從設備,具體取決於用戶的配置。根據ESP32數據表,ESP32的I2C接口支持:

  • 標準模式(100 Kbit / s)
  • 快速模式(400 Kbit / s)
  • 高達5 MHz,但受到SDA上拉強度的限制
  • 7位/ 10位尋址模式
  • 雙尋址模式。用戶可以對命令寄存器進行編程以控制I²C接口,從而具有更大的靈活性
  • 將I2C設備與ESP32連接

    I2C通信協議使用兩條線共享信息。一個用於時鐘信號(SCL),另一個用於發送和接收數據(SDA)。

    注意:在許多分支板上,SDA線也可能標記為SDI,SCL線也可能標記為SCK。

    ESP32 I2C通訊詳細說明以及應用程序講解

    SDA和SCL線為低電平有效,因此應使用電阻將其上拉。對於5V器件,典型值為4.7k Ohm;對於3.3V器件,典型值為2.4k Ohm。

    我們在項目中使用的大多數傳感器都是已內置電阻的分線板。因此,通常,當您處理此類電子元件時,您無需為此擔心。

    將I2C器件連接到ESP32通常很簡單,只需將GND連接到GND,將SDA連接到SDA,將SCL連接到SCL並將正電源連接到外圍設備通常為3.3V(但這取決於您使用的模塊)。

    將ESP32與Arduino IDE結合使用時,默認的I2C引腳為GPIO 22(SCL)和GPIO 21(SDA),但您可以將代碼配置為使用任何其他引腳。


    通過I2C通信,總線上的每個從站都有其自己的地址,此十六進制數允許ESP32與每個設備通信。

    I2C地址通常可以在組件的數據表中找到。但是,如果很難查明,則可能需要運行I2C查找程序以查明I2C地址。

    您可以使用以下程序找到設備的I2C地址。

    #include

    void setup() {

    Wire.begin();

    Serial.begin(115200);

    Serial.println("\nI2C Scanner");

    }

    void loop() {

    byte error, address;

    int nDevices;

    Serial.println("Scanning...");

    nDevices = 0;

    for(address = 1; address < 127; address++ ) {

    Wire.beginTransmission(address);

    error = Wire.endTransmission();

    if (error == 0) {

    Serial.print("I2C device found at address 0x");

    if (address<16) {

    Serial.print("0");

    }

    Serial.println(address,HEX);

    nDevices++;

    }

    else if (error==4) {

    Serial.print("Unknow error at address 0x");

    if (address<16) {

    Serial.print("0");

    }

    Serial.println(address,HEX);

    }

    }

    if (nDevices == 0) {

    Serial.println("No I2C devices found\n");

    }

    else {

    Serial.println("done\n");

    }

    delay(5000);

    }

    您會在串口監視器中看到類似的內容。此特定示例適用於。

    ESP32 I2C通訊詳細說明以及應用程序講解

    ESP32使用不同的I2C引腳(更改默認I2C引腳)

    使用ESP32,您幾乎可以將任何引腳設置為具有I2C功能,您只需要在代碼中進行設置即可。

    將ESP32與Arduino IDE配合使用時,請使用Wire.h庫與使用I2C的設備進行通信。使用此庫,您可以按以下方式初始化I2C:

    Wire.begin(I2C_SDA, I2C_SCL);

    因此,您只需要在I2C_SDA和I2C_SCL變量上設置所需的SDA和SCL GPIO 。

    但是,如果您使用庫與這些傳感器進行通信,則這可能無法正常工作,選擇其他引腳可能會有些棘手。發生這種情況是因為,如果在初始化庫時不傳遞自己的Wire實例,這些庫可能會覆蓋您的引腳。

    在這種情況下,您需要仔細查看.cpp庫文件,並瞭解如何傳遞自己的TwoWire參數。

    例如,如果您仔細看一下,您會發現可以將自己的TwoWire傳遞給begin()方法。

    ESP32 I2C通訊詳細說明以及應用程序講解

    因此,使用其他引腳(例如,GPIO 33作為SDA和GPIO 32作為SCL)從BME280讀取示例程序如下。

    #include

    #include

    #include

    #define I2C_SDA 33

    #define I2C_SCL 32

    #define SEALEVELPRESSURE_HPA (1013.25)

    TwoWire I2CBME = TwoWire(0);

    Adafruit_BME280 bme;

    unsigned long delayTime;

    void setup() {

    Serial.begin(115200);

    Serial.println(F("BME280 test"));

    I2CBME.begin(I2C_SDA, I2C_SCL, 100000);

    bool status;

    status = bme.begin(0x76, &I2CBME);

    if (!status) {

    Serial.println("Could not find a valid BME280 sensor, check wiring!");

    while (1);

    }

    Serial.println("-- Default Test --");

    delayTime = 1000;

    Serial.println();

    }

    void loop() {

    printValues();

    delay(delayTime);

    }

    void printValues() {

    Serial.print("Temperature = ");

    Serial.print(bme.readTemperature());

    Serial.println(" *C");

    Serial.print("Pressure = ");

    Serial.print(bme.readPressure() / 100.0F);

    Serial.println(" hPa");

    Serial.print("Approx. Altitude = ");

    Serial.print(bme.readAltitude(SEALEVELPRESSURE_HPA));

    Serial.println(" m");

    Serial.print("Humidity = ");

    Serial.print(bme.readHumidity());

    Serial.println(" %");

    Serial.println();

    }


    讓我們看一下使用其他I2C引腳的相關部分。

    首先,在I2C_SDA和I2C_SCL變量上定義新的I2C引腳。在這種情況下,我們使用的是GPIO 33和GPIO 32。

    #define I2C_SDA 33

    #define I2C_SCL 32

    創建一個新的TwoWire實例。在這種情況下,它稱為I2CBME。這只是創建一個I2C總線。

    TwoWire I2CBME = TwoWire(0);

    在setup()中,使用您先前定義的引腳初始化I2C通信。第三個參數是時鐘頻率。

    I2CBME.begin(I2C_SDA, I2C_SCL, 400000);

    最後,用您的傳感器地址和TwoWire對象初始化一個BME280對象。

    status = bme.begin(0x76, &I2CBME);

    之後,您可以對bme對象使用常規方法來請求溫度,溼度和壓力。

    注意:如果您使用的庫在其文件中使用諸如wire.begin()之類的語句,則可能需要註釋該行,以便可以使用自己的引腳。

    具有多個I2C器件的ESP32

    如前所述,每個I2C設備都有其自己的地址,因此可能在同一總線上具有多個I2C設備。

    多個I2C設備(相同的總線,不同的地址)

    當我們有多個具有不同地址的設備時,如何設置它們很簡單:

  • 將兩個外設都連接到ESP32的SCL和SDA線路;
  • 在代碼中,通過其地址引用每個外圍設備;
  • 看下面的示例,該示例從BME280傳感器(通過I2C)獲取傳感器讀數,並將結果顯示在I2C OLED顯示器上。

    #include

    #include

    #include

    #include

    #include

    #define SCREEN_WIDTH 128

    #define SCREEN_HEIGHT 64

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

    Adafruit_BME280 bme;

    void setup() {

    Serial.begin(115200);

    if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {

    Serial.println(F("SSD1306 allocation failed"));

    for(;;);

    }

    bool status = bme.begin(0x76);

    if (!status) {

    Serial.println("Could not find a valid BME280 sensor, check wiring!");

    while (1);

    }

    delay(2000);

    display.clearDisplay();

    display.setTextColor(WHITE);

    }

    void loop() {

    display.clearDisplay();

    display.setTextSize(1);

    display.setCursor(0,0);

    display.print("Temperature: ");

    display.setTextSize(2);

    display.setCursor(0,10);

    display.print(String(bme.readTemperature()));

    display.print(" ");

    display.setTextSize(1);

    display.cp437(true);

    display.write(167);

    display.setTextSize(2);

    display.print("C");

    display.setTextSize(1);

    display.setCursor(0, 35);

    display.print("Humidity: ");

    display.setTextSize(2);

    display.setCursor(0, 45);

    display.print(String(bme.readHumidity()));

    display.print(" %");

    display.display();

    delay(1000);

    }


    由於OLED和BME280具有不同的地址,因此我們可以使用相同的SDA和SCL線,而不會出現任何問題。OLED顯示地址為0x3C,BME280地址為0x76。

    if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {

    Serial.println(F("SSD1306 allocation failed"));

    for(;;);

    }

    bool status = bme.begin(0x76);

    if (!status) {

    Serial.println("Could not find a valid BME280 sensor, check wiring!");

    while (1);

    }

    ESP32 I2C通訊詳細說明以及應用程序講解

    多個I2C設備(相同地址)

    但是,如果您有多個具有相同地址的外設怎麼辦?例如,多個OLED顯示器或多個BME280傳感器?有幾種解決方案。

  • 更改設備的I2C地址;
  • 使用I2C多路複用器。
  • 更改I2C地址

    許多分線板都可以選擇根據其電路更改I2C地址。例如,看下面的OLED顯示器。

    ESP32 I2C通訊詳細說明以及應用程序講解


    通過將電阻器放在一側或另一側,可以選擇不同的I2C地址。其他組件也會發生這種情況。

    使用I2C多路複用器

    但是,在前面的示例中,這僅允許您在同一總線上具有兩個I2C顯示器:一個具有0x3C地址,另一個具有0x3D地址。

    此外,有時更改I2C地址並非易事。因此,為了在同一I2C總線中擁有多個具有相同地址的設備,您可以使用I2C多路複用器,如TCA9548A,它允許您與多達8個具有相同地址的設備進行通信。

    ESP32 I2C通訊詳細說明以及應用程序講解

    ESP32使用兩個I2C總線接口

    要使用ESP32的兩個I2C總線接口,您需要創建兩個TwoWire實例。

    TwoWire I2Cone = TwoWire(0);

    TwoWire I2Ctwo = TwoWire(1)

    然後,以所需的頻率在所需的引腳上初始化I2C通信。

    void setup() {

    I2Cone.begin(SDA_1, SCL_1, freq1);

    I2Ctwo.begin(SDA_2, SCL_2, freq2);

    }


    然後,您可以使用Wire.h庫中的方法與I2C總線接口進行交互。

    一個更簡單的替代方法是使用預定義的Wire()和Wire1()對象。Wire()。begin()使用默認引腳和默認頻率在第一條I2C總線上創建I2C通信。對於Wire1.begin(),您應該傳遞所需的SDA和SCL引腳以及頻率。

    setup(){

    Wire.begin();

    Wire1.begin(SDA_2, SCL_2, freq);

    }

    這種方法允許您使用兩條I2C總線,其中一條使用默認參數。

    為了更好地瞭解其工作原理,我們來看一個簡單的示例,該示例從兩個BME280傳感器讀取溫度,溼度和壓力。

    ESP32 I2C通訊詳細說明以及應用程序講解

    每個傳感器都連接到不同的I2C總線。

    · I2C總線1:使用GPIO 27(SDA)和GPIO 26(SCL);

    · I2C總線2:使用GPIO 33(SDA)和GPIO 32(SCL);

    #include

    #include

    #include

    #define SDA_1 27

    #define SCL_1 26

    #define SDA_2 33

    #define SCL_2 32

    TwoWire I2Cone = TwoWire(0);

    TwoWire I2Ctwo = TwoWire(1);

    Adafruit_BME280 bme1;

    Adafruit_BME280 bme2;

    void setup() {

    Serial.begin(115200);

    Serial.println(F("BME280 test"));

    I2Cone.begin(SDA_1, SCL_1, 100000);

    I2Ctwo.begin(SDA_2, SCL_2, 100000);

    bool status1 = bme1.begin(0x76, &I2Cone);

    if (!status1) {

    Serial.println("Could not find a valid BME280_1 sensor, check wiring!");

    while (1);

    }

    bool status2 = bme2.begin(0x76, &I2Ctwo);

    if (!status2) {

    Serial.println("Could not find a valid BME280_2 sensor, check wiring!");

    while (1);

    }

    Serial.println();

    }

    void loop() {

    Serial.print("Temperature from BME1= ");

    Serial.print(bme1.readTemperature());

    Serial.println(" *C");

    Serial.print("Humidity from BME1 = ");

    Serial.print(bme1.readHumidity());

    Serial.println(" %");

    Serial.print("Pressure from BME1 = ");

    Serial.print(bme1.readPressure() / 100.0F);

    Serial.println(" hPa");

    Serial.println("--------------------");

    Serial.print("Temperature from BME2 = ");

    Serial.print(bme2.readTemperature());

    Serial.println(" *C");

    Serial.print("Humidity from BME2 = ");

    Serial.print(bme2.readHumidity());

    Serial.println(" %");

    Serial.print("Pressure from BME2 = ");

    Serial.print(bme2.readPressure() / 100.0F);

    Serial.println(" hPa");

    Serial.println("--------------------");

    delay(5000);

    }

    讓我們看一下使用兩個I2C總線接口的相關部分。

    定義要使用的SDA和SCL引腳:

    #define SDA_1 27

    #define SCL_1 26

    #define SDA_2 33

    #define SCL_2 32

    創建兩個TwoWire對象(兩個I2C總線接口):

    TwoWire I2Cone = TwoWire(0);

    TwoWire I2Ctwo = TwoWire(1);

    創建Adafruit_BME280庫的兩個實例以與您的傳感器進行交互:bme1和bme2。

    Adafruit_BME280 bme1;

    Adafruit_BME280 bme2;

    在定義的引腳和頻率上初始化I2C通信:

    I2Cone.begin(SDA_1, SCL_1, 100000);

    I2Ctwo.begin(SDA_2, SCL_2, 100000);

    然後,應使用正確的地址和I2C總線初始化bme1和bme2對象。bme1使用I2Cone:

    bool status = bme1.begin(0x76, &I2Cone);

    而bme2用途I2Ctwo:

    bool status1 = bme2.begin(0x76, &I2Ctwo);

    現在,您可以在bme1和bme2對象上使用Adafruit_BME280庫中的方法來讀取溫度,溼度和壓力。

    另一種選擇

    為了簡單起見,可以使用預定義的Wire()和Wire1()對象:

    · Wire():在默認引腳GPIO 21(SDA)和GPIO 22(SCL)上創建I2C總線

    · Wire1(SDA_2,SCL_2,freq):以所需的頻率在定義的SDA_2和SCL_2引腳上創建I2C總線。


    這是相同的示例,但是使用此方法。現在,您的一個傳感器使用默認引腳,另一個傳感器使用GPIO 32和GPIO 33。

    #include

    #include

    #include

    #define SDA_2 33

    #define SCL_2 32

    Adafruit_BME280 bme1;

    Adafruit_BME280 bme2;

    void setup() {

    Serial.begin(115200);

    Serial.println(F("BME280 test"));

    Wire.begin();

    Wire1.begin(SDA_2, SCL_2);

    bool status1 = bme1.begin(0x76);

    if (!status1) {

    Serial.println("Could not find a valid BME280_1 sensor, check wiring!");

    while (1);

    }

    bool status2 = bme2.begin(0x76, &Wire1);

    if (!status2) {

    Serial.println("Could not find a valid BME280_2 sensor, check wiring!");

    while (1);

    }

    Serial.println();

    }

    void loop() {

    Serial.print("Temperature from BME1= ");

    Serial.print(bme1.readTemperature());

    Serial.println(" *C");

    Serial.print("Humidity from BME1 = ");

    Serial.print(bme1.readHumidity());

    Serial.println(" %");

    Serial.print("Pressure from BME1 = ");

    Serial.print(bme1.readPressure() / 100.0F);

    Serial.println(" hPa");

    Serial.println("--------------------")

    Serial.print("Temperature from BME2 = ");

    Serial.print(bme2.readTemperature());

    Serial.println(" *C");

    Serial.print("Humidity from BME2 = ");

    Serial.print(bme2.readHumidity());

    Serial.println(" %");

    Serial.print("Pressure from BME2 = ");

    Serial.print(bme2.readPressure() / 100.0F);

    Serial.println(" hPa");

    Serial.println("--------------------");

    delay(5000);

    }

    您應該在串口監視器上同時獲得兩個傳感器的讀數。

    ESP32 I2C通訊詳細說明以及應用程序講解

    在本教程中,您瞭解了有關ESP32的I2C通信協議的更多信息。希望對您有幫助。


    分享到:


    相關文章: