<-- zurück

Leider klappt der Beispiel-Code der Applikation Note "AVR312: Using the USI module as a I2C slave" nicht immer. Das herauszufinden hat mich einige Abende gekostet.

Das Problem
Der Start Condition Interrupt wird ausgelöst, wenn bei einem Signalpegel High auf der SCL-Leitung SDA von High auf Low wechselt.
SCL geht dann kurze Zeit später auf Low. Der SCL-Zustand wird in der Start-Condition-Interrupt-Service-Routine durch eine Schleife abgefragt. Es geht erst dann weiter,  wenn SCL auch auf Low liegt.
Nun kann es aber sein, dass auf Grund irgendeines Fehlers nicht SCL auf Low geht, sondern statt dessen SDA zurück auf High. Dies ist zwar im Standard nicht vorgehesen. Aber wenn ein Slave sich verpätet einschaltet und während dieser Zeit SDA kurzfristig auf Low zieht, kann das schon einmal passieren. Wenn man nun ausschließlich SCL testet, ergibt sich eine Endlosschleife - zumindest eine sehr lange Schleife, schließlich sind die Interrupts in der ISR gesperrt. Es geht erst weiter, wenn SCL aus irgend einem anderen Gruns auf Low geht. Aber dann ist es aus mit der Synchronisation.

In der Application Note wird dieser Umstand berücksichtigt. Allerding etwas (zu) trickreich. Die oben beschriebene Situation SDA Low -> High bei SCL auf High entspricht einer Stopp-Bedingung. Zur Erkennung der Stopp-Bedingung gibt es eine Harware-Einheit, die man über USIPF (Stop Condition Flag) abfragen kann. Das macht folgender Code in der Application Note:
while ((PIN_USI & (1<<PORT_USI_SCL)) & !(USISR & (1<<USIPF)));  
// Wait for SCL to go low to ensure the "Start Condition" has completed.
// If a Stop condition arises then leave the interrupt to prevent waiting forever.
Das klappt häufig, aber leider nicht immer! Dazu muss man folgende Situation betrachten:
Der Master führt eine Übertragung durch. Wenn er ordentlich programmiert ist, beendet er diese indem er eine Stopp-Bedingung auf dem Bus erzeugt. Das Erkennen der Stopp-Bedingung hat im Code des Slaves aber keinerlei Auswirkungen. Dies ist auch schwierig, weil die Stopp-Bedingung im Gegensatz zur Startbedingung keinen Interrupt auslöst.
Die nächste Übertragung beginnt dann wieder mit einer Start-Bedingung. Beim Eintritt in die ISR ist jetzt aber auf Grund der vorhergehenden Stopp-Bedingung USIPF gesetzt! Das bewirkt, dass die Warteschleife sofort verlassen wird. Es wird also gar nicht gewartet, bis SCL auf Low geht, sondern das USI wird unmittelbar zum Einlesen des Adress-Bytes vorbereitet. Ist der I²C-Master entsprechend langsam (oder der Slave schnell), zählt die nun kommende fallende SCL-Flanke der Startbedingung bereits als erste Flanke für das Adress-Byte (Hinwies: der AVR zählt sowohl steigende als auch fallende Flanken). Damit ist die Synchronisation zwischen Master und Slave verloren.

Die Lösung
Man muss auf die Abfrage von USIPF verzichten:
#define USI_MASK ((1<<PORT_USI_SCL) | (1<<PORT_USI_SDA))
while ( (tmpSignalState = (PIN_USI & USI_MASK)) == (1<<PORT_USI_SCL)); // solange SDA low und SCL high ist
if (tmpSignalState == USI_MASK)  // unerwartete Stopp-Bedingung erkannt
  {...
  }
else
  { ...
  }

Weiteres Problem
Dass in einer nicht unterbrechbaren ISR eine Schleife programmiert ist, ist grottenschlechter Programmierstil. Bei 20 MHz Prozessortaktfrequenz und einer 100 kHz Standard I²C-Frequenz wartet man im Zweifelsfall knapp 200 Takte :-(.

<