<-- 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ösungMan 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 ProblemDass 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 :-(.
<