Das Bild in diesem Beitrag stammt von Thomas von Pixabay.

Wie kann man mehrere I2C-Geräte mit der gleichen Geräteadresse an eine MCU anschließen? Meistens wird empfohlen, Hardware-Lösungen zu verwenden. Hier werden wir uns ansehen, wie man das Problem mit der FlexWire-Bibliothek per Software lösen kann.

Motivation

Die meisten I2C-Geräte haben eine feste Geräteadresse. Du kannst also nur ein solches Gerät an deinen I2C-Bus anschließen. Wenn du mehr als ein I2C-Gerät desselben Typs anschließen möchtest, hast du folgende Möglichkeiten:

  1. verwende einen Hardware-I2C-Multiplexer wie TCA9548A oder PCA9546;
  2. verwende verschiedene I2C-Schnittstellen auf deiner MCU, wenn dies unterstützt wird;
  3. verwende verschiedene Software-I2C-Kommunikationsklassen-Instanzen;
  4. verwende eine Software-I2C-Klasse und schalte auf verschiedene SDA-Leitungen um.

Hardware-Lösungen

Das Hinzufügen eines I2C-Multiplexers, wie in diesem Adafruit-Tutorial beschrieben, ist konzeptionell die einfachste Lösung. Allerdings brauchst du natürlich zusätzliche Hardware, was man vielleicht vermeiden möchte.

Wenn die MCU mehr als eine I2C-Schnittstelle unterstützt, wie es beim ESP32 der Fall ist, dann kannst du die Sensoren an verschiedene I2C-Busse anschließen, wie in diesem Random-Nerd-Tutorial beschrieben. Das funktioniert aber nur, wenn du eine solche MCU hast und die Anzahl der I2C-Geräte nicht höher ist als die Anzahl der unterstützten Busse (normalerweise 2).

Software-Lösungen

In den meisten Fällen, wenn wir I2C-Geräte mit identischen Geräteadressen verbinden wollen, gibt es einige ungenutzte GPIOs. Diese können genutzt werden, um mehrere I2C-Busse zu implementieren, die von Bit-Banging-I2C-Bibliotheken wie SoftWire, SoftwareWire, SlowSoftWire oder FlexWire angesteuert werden können. Diese Bibliotheken unterstützen alle mehrere Instanzen der jeweiligen Klassen mit unterschiedlichen Datenpins.

Wenn du die I2C-Interaktion selbst programmierst oder bereit bist, die Bibliothek, die das I2C-Protokoll für das jeweilige Gerät implementiert, zu ändern, kannst du jede der oben genannten Bibliotheken verwenden. Wenn du die Gerätebibliothek unverändert verwenden und die Wire-Bibliothek durch eine Software-I2C-Bibliothek ersetzen willst, stößt du auf die in meinem vorherigen Blogbeitrag beschriebenen Probleme. Die FlexWire-Bibliothek ist die einzige Bibliothek, die den Ersatz der Wire-Bibliothek projektbezogen ermöglicht. Du fügst diese Bibliothek einfach als erste in deinen Sketch ein, danach wird die ursprüngliche Wire-Bibliothek ignoriert.

Zur Veranschaulichung wird hier gezeigt, wie du zwei HUT21D Luftfeuchtigkeits- und Temperatursensoren an einen Arduino UNO anschließt. Das Anschließen der Sensor-Breakout-Boards ist ganz einfach. Der einzige Punkt, den du beachten solltest, ist, dass die Platinen eine 3,3V-Versorgung benötigen und die Open-Drain-Datenleitungen auch auf diese Weise versorgt werden sollten.

Mit der Gerätebibliothek SparkFunHUT21D kann man den folgenden Sketch schreiben, der beide Geräte initialisiert und dann in einer Schleife beide Sensoren ausliest. Der Sketch kann leicht erweitert werden, um mehr Sensoren auszulesen.

#include <FlexWire.h>
#include <SparkFunHTU21D.h>

#define MAXSENSOR 2

// I2C-Pins
uint8_t sdapin[MAXSENSOR] = { 2, 4 };
uint8_t sclpin[MAXSENSOR] = { 3, 5 };

// Array von Flexwire-Instanzen
FlexWire wire[MAXSENSOR] = { {sdapin[0], sclpin[0]}, {sdapin[1], sclpin[1]} }; 

// Erstelle ein Array von Instanzen der HTU21D-Klasse
HTU21D htu[MAXSENSOR];

void setup()
{
  Serial.begin(9600);
  Serial.println(F("Multi-I2C Beispiel mit HTU21D"));
  for (uint8_t i=0; i < MAXSENSOR; i ) htu[i].begin(wire[i]);
}

void loop()
{
  for (uint8_t i=0; i < MAXSENSOR; i ) {
    Serial.print(F("Sensor "));
    Serial.print(i 1);
    Serial.print(F(": "));
    Serial.print(htu[i].readTemperature(), 1);
    Serial.println("C");
  }
  Serial.println();
  delay(1000);
}

In den Arrays sdapin und sclpin werden die GPIO-Pins gespeichert, die für die verschiedenen I2C-Busse verwendet werden. Die Werte werden wiederum für die Initialisierung des wire-Arrays verwendet, das alle FlexWire-Instanzen enthält. Wir hätten die Werte für die Initialisierung der sdapin- und sclpin-Arrays auch direkt in die Initialisierungsanweisung für das wire-Array einfügen können, aber weiter unten wird klar, warum wir diesen indirekten Weg gewählt haben. Schließlich enthält das htu-Array alle HTU21D-Instanzen.

In der Setup-Prozedur werden alle begin-Methoden für die verschiedenen HTU21D-Instanzen aufgerufen. In der Schleifenprozedur rufen wir die readTemperature-Methode für alle HTU21D-Instanzen auf, die im htu-Array gespeichert sind.

Gemeinsame Nutzung der SCL-Leitung

Die obige Lösung funktioniert perfekt. Wenn wir jedoch zu wenig GPIOs haben, stellt sich die Frage, ob man die Anzahl der benötigten GPIOs reduzieren kann. Tatsächlich scheint es durchaus möglich zu sein, die SCL-Leitung gemeinsam zu nutzen. Der Grund dafür ist, dass ein I2C-Gerät nur dann reagiert, wenn eine sogenannte Startbedingung auf dem Bus vorliegt und die Geräteadresse danach gesendet wird. Wenn die Datenleitung inaktiv ist, während ein Taktsignal anliegt, gibt es weder eine Startbedingung noch wird eine gültige Adresse auf dem Bus übertragen, und das I2C-Gerät bleibt inaktiv. Mit anderen Worten: Wir können eine gemeinsame SCL-Leitung für alle I2C-Busse verwenden, vorausgesetzt, nur ein Bus ist zu jedem Zeitpunkt aktiv. Letzteres ist bei unserem Aufbau garantiert.

Die gemeinsame Nutzung der SCL-Leitung lässt sich leicht bewerkstelligen, indem wir in der obigen Schaltung nur eine Verbindung vom UNO-Board von D3 (weiße Linie) zu den beiden SCL-Pins der Sensor-Breakout-Boards herstellen. In dem Sketch müssen wir einfach zwei Leitungen wie folgt ändern:

...

// Das sind die Pins, die wir für die I2C-Busse verwenden werden
...
const uint8_t sclpin = 3;

// Array von Flexwire-Instanzen
FlexWire wire[MAXSENSOR] = { {sdapin[0], sclpin}, {sdapin[1], sclpin} }; 

...

Was man allerdings bei der gemeinsamen Nutzung der SCL-Leitung beachten sollte ist, dass in diesem Fall die (meist auf den Sensorplatinen vorhandenen) Pull-Up-Widerstände parallel geschaltet werden. Da I2C-Bausteine nur bis zu 3mA auf den Datenleitungen vertragen, sollte man bei Pull-Ups von 10 kΩ nicht mehr als 5 solcher I2C-Bausteine an einer gemeinsamen SCL-Leitung betreiben.

Umschalten der SDA-Leitung

Abschließend kann man sich fragen, warum wir eine FlexWire-Instanz pro I2C-Bus haben. Da zu jedem Zeitpunkt nur ein Bus aktiv sein kann und es außer dem aktiven SDA-Pin keine Zustandsinformationen gibt, sollte eine FlexWire-Instanz ausreichen. Wenn wir uns ansehen, wie ein Hardware-Multiplexer verwendet wird, bei dem wir einfach eine Wire-Instanz verwenden und mit verschiedenen I2C-Geräten verbinden, scheint es tatsächlich möglich zu sein, mit nur einer FlexWire-Instanzen zu arbeiten. Wir müssen nur die SDA-Leitung umschalten. Dadurch werden mindestens 32 Byte RAM für den Eingangspuffer pro FlexWire-Instanz eingespart. Ich habe der FlexWire-Klasse die Methode setPins hinzugefügt, mit der die SDA- und SCL-Pins gesetzt werden. Dies ist kompatibel mit der ESP32-Implementierung der Wire-Bibliothek.

Und wir brauchen in diesem Fall natürlich nur eine HTU21D-Instanz. Der Sketch muss also wie folgt geändert werden.

...
// Flexwire-Instanz
FlexWire Draht;
// HTUD21-Instanz
HTUD21 htu;
...
void setup()
{
  ...
  for (uint8_t i=0; i < MAXSENSOR; i ) {
    Wire.setsda(sdapin[i], sclpin);
    htu.begin();
  }
}
void loop()
{
  for (uint8_t i=0; i < MAXSENSOR; i ) {
    ...
    Wire.setPins(sdapin[i], sclpin);
    Serial.print(htu.readTemperature(), 1);
    ...
  }
  ...
}

Zusammenfassung

Wenn du mehrere I2C-Geräte mit identischen Geräteadressen an deine MCU anschließen musst und keine zusätzliche Hardware kaufen möchtest, du aber ein paar GPIOs übrig hast und die Kommunikationsgeschwindigkeit nicht von entscheidender Bedeutung ist, kannst du die FlexWire-Bibliothek verwenden, um einen Software-I2C-Multiplexer zu implementieren. Das Umschalten zwischen verschiedenen I2C-Bussen ist genauso einfach wie bei einem Hardware-Multiplexer. Außerdem musst du die Gerätebibliothek nicht ändern, denn die FlexWire-Bibliothek ist ein Drop-in-Ersatz für den Master-Mode-Teil der Wire-Bibliothek.

Views: 8