Komunikace ESP32 I2C: Nastavení pinů, více sběrnicových rozhraní a periferií

04.03.2023 Arduino #esp32 #i2c #programování

ESP32 má dvě rozhraní sbwrnice 2C, které mohou sloužit jako I2C master nebo slave. V tomto tutoriálu je uveden komunikační protokol I2C s ESP32 pomocí Arduino IDE: jak vybrat I2C piny, připojit více I2C zařízení ke stejné sběrnici a jak používat dvě rozhraní I2C sběrnice.


Představení komunikačního protokolu ESP32 I2C

I²C znamená Inter Integrated Ccircuit (vyslovuje se I-squared-C) a je to synchronní, multi-master, multi-slave komunikační protokol. Lze připojit:

  • více slave na jednom masteru: například ESP32 čte ze senzoru BME280 pomocí I2C a zapisuje hodnoty senzoru na I2C OLED displej.
  • více masterů ovládajících stejný slave: například dvě desky ESP32 zapisující data na stejný I2C OLED displej.

Tento protokol se používá mnohokrát s ESP32 ke komunikaci s externími zařízeními, jako jsou senzory a displeje. V níže uvedených příkladech je hlavním čipem ESP32 a externí zařízení jsou podřízenými.

Rozhraní sběrnice ESP32 I2C

ESP32 podporuje I2C komunikaci prostřednictvím svých dvou I2C sběrnicových rozhraní, která mohou sloužit jako I2C master nebo slave, v závislosti na konfiguraci uživatele. Podle datasheetu ESP32 I2C rozhraní ESP32 podporují:

  • Standardní režim (100 kbit/s) 
  • Rychlý režim (400 Kbit/s) 
  • Až 5 MHz, ale omezeno silou vytažení SDA 
  • 7bitový/10bitový režim adresování 
  • Režim duálního adresování. Uživatelé mohou naprogramovat příkazové registry pro ovládání rozhraní I²C, takže mají větší flexibilitu

Připojení I2C zařízení s ESP32

Komunikační protokol I2C využívá ke sdílení informací dva vodiče. Jeden se používá pro hodinový signál ( SCL ) a druhý pro odesílání a příjem dat ( SDA ).

Linky SDA a SCL jsou aktivní nízko, takže by měly být vytaženy pomocí odporů. Typické hodnoty jsou 4,7 kOhm pro 5V zařízení a 2,4 kOhm pro 3,3V zařízení.

Většina senzorů, již mají vestavěné odpory.

Připojení I2C zařízení k ESP32 je normálně stejně jednoduché jako připojení GND k GND, SDA k SDA, SCL k SCL a kladnému napájení k periferii, obvykle 3,3 V (ale záleží na modulu, který používáte).

Zařízení I2C ESP32
SDA SDA (výchozí jeGPIO 21)
SCL SCL (výchozí jeGPIO 22)
GND GND
VCC obvykle 3,3V nebo 5V

Při použití ESP32 s Arduino IDE jsou výchozí I2C piny GPIO 22 (SCL) a GPIO 21 (SDA), ale svůj kód můžete nakonfigurovat tak, aby používal jakékoli jiné piny.

Skenování I2C adres pomocí ESP32

S komunikací I2C má každý slave na sběrnici svou vlastní adresu, hexadecimální číslo, které umožňuje ESP32 komunikovat s každým zařízením.

Adresu I2C lze obvykle nalézt v datovém listu komponenty. Pokud je však obtížné to zjistit, možná budete muset spustit skript skeneru I2C, abyste zjistili adresu I2C.

K nalezení I2C adresy vašich zařízení můžete použít následující program.

#include <Wire.h>

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

Výstupem bude senam adres připojených zařízení prostřednictvím I2C.

Použití různých I2C pinů s ESP32 (změňte výchozí I2C piny)

S ESP32 můžete nastavit téměř jakýkoli pin tak, aby měl I2C schopnosti, stačí to nastavit ve vašem kódu.

Při použití ESP32 s Arduino IDE použijte knihovnu Wire.h pro komunikaci se zařízeními pomocí I2C. S touto knihovnou inicializujete I2C následovně:

Wire.begin(I2C_SDA, I2C_SCL);

Stačí tedy nastavit požadované SDA a SCL GPIO na I2C_SDA a I2C_SCL proměnné.

Pokud však ke komunikaci s těmito senzory používáte knihovny, nemusí to fungovat a výběr jiných pinů může být trochu složitější. To se děje proto, že tyto knihovny mohou přepsat vaše piny, pokud nepředáte své vlastníDrátinstance při inicializaci knihovny.

V těchto případech se musíte blíže podívat na soubory knihovny .cpp a zjistit, jak předat své vlastníTwoWire parametry.

Pokud se například blíže podíváte na knihovnu Adafruit BME280, zjistíte, že můžete předat svou vlastní metodu TwoWire.beign().

Příklad programu pro čtení z BME280 například pomocí jiných pinů GPIO 33jako SDA a GPIO 32 jako SCL je následující.

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

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

  // default settings
  // (you can also pass in a Wire library object like &Wire2)
  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");

  // Convert temperature to Fahrenheit
  /*Serial.print("Temperature = ");
  Serial.print(1.8 * bme.readTemperature() + 32);
  Serial.println(" *F");*/

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

Pojďme se podívat na příslušné části pro použití dalších I2C pinů.

Nejprve definujte proměnné pro nové I2C piny na I2C_SDA a I2C_SCL. V tomto případě používáme GPIO 33 a GPIO 32.

#define I2C_SDA 33
#define I2C_SCL 32

Vytvoř novou instanci TwoWire. V tomto případě je to tzv I2CBME. Jednoduše tak vznikne I2C sběrnice.

TwoWire I2CBME = TwoWire(0);

V setup(), inicializujte I2C komunikaci s piny, které jste definovali dříve. Třetím parametrem je hodinová frekvence.

I2CBME.begin(I2C_SDA, I2C_SCL, 400000);

Nakonec inicializujte objekt BME280 s adresou vašeho senzoru a objekt TwoWire.

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

Poté můžete na svém počítači používat obvyklé metody bme objekt požadovat teplotu, vlhkost a tlak.

ESP32 s více I2C zařízeními

Jak jsme již zmínili, každé I2C zařízení má svou vlastní adresu, takže je možné mít více I2C zařízení na stejné sběrnici.

Více I2C zařízení (stejná sběrnice, různé adresy)

Když máme více zařízení s různými adresami, je triviální, jak je nastavit:

  • připojte obě periferie k linkám ESP32 SCL a SDA;
  • v kódu odkazujte na každou periferii její adresou;

Podívejte se na následující příklad, který získává hodnoty senzoru ze senzoru BME280 (přes I2C) a zobrazuje výsledky na I2C OLED displeji.

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.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);

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 temperature
  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 humidity
  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);
}

Protože OLED a BME280 mají různé adresy, můžeme bez problémů používat stejné linky SDA a SCL. Adresa OLED displeje je 0x3C a adresa BME280 je 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);
}

Více I2C zařízení (stejná adresa)

Ale co když máte více periferních zařízení se stejnou adresou? Například více OLED displejů nebo více senzorů BME280? Existuje několik řešení.

  • změnit I2C adresu zařízení;
  • použijte I2C multiplexer.

Změna adresy I2C

Mnoho breakout desek má možnost změnit I2C adresu v závislosti na jejich obvodech. Například, že pohled na následující OLED displej.

Umístěním odporu na jednu nebo druhou stranu můžete vybrat různé adresy I2C. To se děje i u jiných komponent.

Použití I2C multiplexeru

V tomto předchozím příkladu vám to však umožňuje mít pouze dva displeje I2C na stejné sběrnici: jeden s adresou 0x3C a druhý s adresou 0x3D.

Navíc někdy není triviální změnit I2C adresu. Takže, abyste měli více zařízení se stejnou adresou na stejné I2C sběrnici, můžete použít I2C multiplexer jako TCA9548A, který vám umožní komunikovat až s 8 zařízeními se stejnou adresou.

ESP32 pomocí dvou I2C sběrnicových rozhraní

Chcete-li použít dvě rozhraní sběrnice I2C ESP32, musíte vytvořit dvě instance TwoWire.

TwoWire I2Cone = TwoWire(0);
TwoWire I2Ctwo = TwoWire(1)

Poté inicializujte I2C komunikaci na požadovaných pinech s definovanou frekvencí.

void setup() {
  I2Cone.begin(SDA_1, SCL_1, freq1);
  I2Ctwo.begin(SDA_2, SCL_2, freq2); 
}

Poté můžete použít metody zWire.hknihovny pro interakci s rozhraními sběrnice I2C.

Jednodušší alternativou je použití předdefinovaných objektů Wire() a Wire1().Wire().begin() vytvoří I2C komunikaci na první I2C sběrnici pomocí výchozích pinů a výchozí frekvence. Pro Wire1.begin() měli byste předat požadované piny SDA a SCL a také frekvenci.

setup(){
  Wire.begin(); //uses default SDA and SCL and 100000HZ freq
  Wire1.begin(SDA_2, SCL_2, freq);
}

Tato metoda umožňuje použít dvě I2C sběrnice, jedna z nich používá výchozí parametry.

Abychom lépe pochopili, jak to funguje, podíváme se na jednoduchý příklad, který čte teplotu, vlhkost a tlak ze dvou senzorů BME280.

Každý senzor je připojen k jiné I2C sběrnici.

  • I2C Bus 1: použití GPIO 27 (SDA) aGPIO 26 (SCL);
  • I2C Bus 2: použití GPIO 33 (SDA) aGPIO 32 (SCL);

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

#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() { 
  // Read from bme1
  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("--------------------");

  // Read from bme2
  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);
}

Podívejme se na příslušné části pro použití dvou rozhraní sběrnice I2C.

Definujte piny SDA a SCL, které chcete použít:

#define SDA_1 27
#define SCL_1 26

#define SDA_2 33
#define SCL_2 32

Vytvořte dva objekty TwoWire (dvě rozhraní sběrnice I2C):

TwoWire I2Cone = TwoWire(0);
TwoWire I2Ctwo = TwoWire(1);

Vytvořte dvě instance knihovny Adafruit_BME280 pro interakci s vašimi senzory: bme1 a bme2.

Adafruit_BME280 bme1;
Adafruit_BME280 bme2;

Inicializujte I2C komunikaci na definovaných pinech a frekvenci:

I2Cone.begin(SDA_1, SCL_1, 100000); 
I2Ctwo.begin(SDA_2, SCL_2, 100000);

Poté byste měli inicializovat objekty bme1 a bme2 se správnou adresou sběrnice I2C sběrnicí. bme1 používá I2Cone:

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

A bme2 používá I2Ctwo:

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

Nyní můžete použít metody zAdafruit_BME280knihovna na vašembme1abme2objektů ke čtení teploty, vlhkosti a tlaku.

Další alternativa

Pro jednoduchost můžete použít předdefinovanéDrát()aWire1()objekty:

  • Wire(): vytvoří I2C sběrnici na výchozích pinech GPIO 21 (SDA) a GPIO 22 (SCL)
  • Wire1(SDA_2, SCL_2, frekv.): vytvoří I2C sběrnici na definovaném SDA_2 a SCL_2 piny s požadovanou frekvencí.

Zde je stejný příklad, ale s použitím této metody. Nyní jeden z vašich senzorů používá výchozí piny a druhý používá GPIO 32 a GPIO 33.

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

#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() { 
  // Read from bme1
  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("--------------------");

  // Read from bme2
  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);
}

Literatura:

ESP32 I2C Communication: Set Pins, Multiple Bus Interfaces and Peripherals (Arduino IDE). Random Nerd Tutorials [online]. USA: RNT, 2022 [cit. 2022-08-19]. Dostupné z: https://randomnerdtutorials.com/esp32-i2c-communication-arduino-ide/