Hier eine kleine Anleitung, wie man dynamisch I2C Adressen vergeben kann. Was heißt eigentlich dynamisch? Bei I2C haben die Slaves normalerweise feste Adressen. Oder bei den meisten ICs eben Jumper, um diese festzulegen. Nun möchte man aber manchmal vielleicht mehrere Mikrocontroller mit einem Bus vernetzen und sie mit einem Master kommunizieren lassen. Und das, ohne a) extra Adress-Jumper auf der Platine vorzusehen (um mögliche Fehlbenutzungen zu vermeiden und/oder Platz zu sparen) oder b) mehrere Softwareversionen auszuliefern, die sich nur anhand der I2C-Adresse unterscheiden. Ziel ist es also, eine Software zu schreiben, die es ermöglicht, dass sich die Slaves mit Hilfe eines Masters eine im Bus eindeutige I2C-Adresse aushandeln. In meinem Beispiel ist es übrigens eine skalierbare Aquariumsbeleuchtung: ein Mastercontroller (ATmega) steuert bis zu 8 LED-Controller (ATtiny) an. Da ich I2C als Busprotokoll vorgesehen habe, lässt sich dafür natürlich die im I2C-Protokoll definierte Kollisionserkennung (arbitration) dafür verwenden. Definiert ist diese eigentlich nur für den Multimaster-Betrieb, lässt sich aber natürlich genauso auch für den Singlemaster/Multislave-Betrieb anwenden. Die Kollisionserkennung funktioniert so, dass mehrer Sender, die über die selbe Adresse angesprochen wurden, natürlich gleichzeitig ihre Daten senden. Aber nach dem Senden ihres Bits auf der SDA-Leitung prüfen, ob das Bit auch tatsächlich so auf dem Bus angekommen ist. Sendet also ein Slave ein High und ein anderer zeitgleich ein Low, erkennt (durch die stärkere Wirkung von Low) der Slave mit dem High, dass ein Low anliegt und beendet die Übertragung umgehend. Nun brauchen wir aber noch ein paar Zutaten für die dynamische Adressvergabe: 1) Eine für alle Slaves einheitliche I2C-Adresse zum Aushandeln der dedizierten Slave-Adresse mit dem Master. 2) Eine eindeutige „ID“, die jeder Slave an den Master schickt, um von ihm eine neue Adresse zu erhalten. Der Algorithmus funktioniert pseudomäßig so: GCA = General Call Address, also die Slave-Adresse, auf der alle Slaves horchen GET ID = Registerwert für „gebt mir eure ID“ SET Address = Registerwert für „der Slave mit der ID x bekommt die Adresse y“ Master: [GCA (w)], [GET ID], [I2C-Restart], [GCA (r)] Die (noch nicht durchnummerierten) Slaves antworten mit: [ID], wobei nur einer am Ende übrig bleibt (der mit den meisten Low’s am Anfang). Der Master sendet dann: [I2C-Restart], [GAC (w)][SET Address][ID][Address][Stop] Alle Slaves lesen mit und der Slave mit der passenden ID übernimmt seine neue eindeutige Adresse. Der Master kontrolliert dies, in dem er den Slave mit dieser Adresse anpingt. Der Master muss das dann natürlich solange wiederholen, bis er alle Slaves gefunden hat, also kein Slave mehr eine ID sendet. Als „ID“ bietet sich eine Zufallszahl an, eine „echte“ natürlich. Idealerweise über das Rauschen eines ADC-Pins. Man kann natürlich auch irgendeine eingebrannte ID von einem Chip nehmen (wie bei einem DS18B20 z.B.). Für eine etwas bessere zufällige Bitverteilung sollte man jedes Zufallsbit für seine Zufallszahl aus dem niederwertigsten Bit einer einzelnen ADC-Berechnung nehmen. Sprich für 32 Bit (wie in meinem Falle) benötige man 32 ADC-Berechnungen. Jetzt kommen wir zu einer Hürde, die ich zumindest bei den ATtiny’s erstmal überwinden musste: ATtiny’s haben als I2C-Hardware-Support das sogenannte USI. Und „eigentlich“ sogar eine Kollisionserkennung im USISR Register: Das Bit USIDC wird demnach high, wenn die Hardware feststellt, dass SDA nicht so ist wie gesetzt (also - s.o. – dass ein High gesendet, aber ein Low detektiert wurde, der umgekehrte Fall existiert ja nicht). Nun sendet man per USI aber normalerweise ein ganzes Byte. Und hier liegt das Problem! USIDC funktioniert nur für ein Bit, und zwar nur für das, was gerade gesendet wurde. Sendet man ein Byte, würde die Erkennung nur auf dem niederwertigsten Bit angewendet werden. Das kann man algorithmisch übrigens auch über den Master lösen, ich wähle aber den Weg über die Kollisionserkennung. Die Lösung des Problems ist: die ID bitweise und nicht byteweise zu übertragen, was den USI-Teil geringfügig komplizierter macht. Aber sehr gut funktioniert. Nimmt man den Beispielcode für USI Slave (App Note AVR312) muss dafür die Statemachine erweitert werden und eben das Byte bitweise übertragen werden. Sorry, dass ich hier keinen Beispielcode mit angebe, sondern nur den Weg beschreibe. Das liegt daran, dass meine Implementierung sauberes C++ 11 ist und fast nichts mehr mit dem Original zu tun hat. Ich habe noch ein Bild von dem Versuchsaufbau angehängt: Links sind zwei LED-Controller-Boards, auf dem Breadboard ist der Master. Über Blinkcodes teilen die Slaves ihre ID mit und der Master, wieviele Slaves er gefunden hat. Welcher Slave die Adresse "1" und welcher "2" hat kann sich nach jedem Reset ändern, erkannt werden aber immer beide (Master blinkt immer "2").
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.