Forum: Mikrocontroller und Digitale Elektronik HOWTO: Dynamische I2C Adressen (per USI)


von Andreas J. (ajbln)


Angehängte Dateien:

Lesenswert?

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
Noch kein Account? Hier anmelden.