Ich implementiere gerade einen I2C-Slave auf einem Attiny85 und bin
dabei auf Ungereimtheiten gestoßen. Es gibt ja dutzende Bibliotheken,
inklusive der im Artikel USI empfohlenen jtronics-Bibliothek, die
letztlich alle auf eine korrigierte Version von AVR312 zurückgehen. Die
Korrektur besteht darin, im Start-Condition-ISR die fallende SCL-Flanke
nach der Start-Condition abzuwarten, damit diese nicht bei den folgenden
16 Flanken mitgezählt wird. In Pseudo-Code sieht das dann so aus:
1
init()
2
{
3
[...]
4
SCL-Pin = output, high;
5
SDA-Pin = input, high;
6
USICR = [...] | (0b10 << USIWM0) | [...];
7
[...]
8
}
9
10
ISR(USI_START_vect)
11
{
12
[...]
13
while(SCL high && SDA low);
14
if(SDA low) // Alternativ "if(SCL low)"
15
{
16
// Keine Stop-Condition
17
Enable Overflow Interrupt; Setze 4-bit-Counter auf 0; etc;
18
}
19
else
20
{
21
// Stop-Condition
22
Setze alles zurück;
23
}
24
Clear interrupts in USISR;
25
}
Dazu habe ich zwei Fragen:
1.) Wozu dient die if-Abfrage im Start-ISR? Schließlich haben wir zuvor
USIWM1:0=0b10 gesetzt und das bedeutet laut Datenblatt:
"The SCL line is held low when a start detector detects a start
condition and the output is enabled. Clearing the Start Condition Flag
(USISIF) releases the line." (Tabelle 15-1 im Attiny85-Datenblatt)
Sprich: Wir (also der ATtiny) halten SCL während der while- und
if-Sachen low! Dementsprechend bricht die while-Schleife sofort beim
ersten Durchlauf ab und das if ist immer wahr. Wozu also also der ganze
Krempel?
2.) Warum würde man in der Initialisierung, selbst für nur eine
Mikrosekunde, die I2C-Pins auf Output-high setzen? Ist das nicht das
große No-no bei einem I2C-Bus? Oder besser gesagt: Warum pfuscht man an
der Pin-Konfiguration herum BEVOR man USICR setzt? Sobald USIWM1:0=0b10
ist, ist ja alles ok, denn dann übernimmt das USI die Pins und sorgt
dafür, dass sie nicht high getrieben werden, egal was in DDRB und PORTB
geschrieben wird.
Das sind m.E. zwei dicke Fehler in praktisch allen
USI-I2C-Slave-Implementierungen, die so im Netz herumgeistern. Das kann
ja wohl nicht sein, ergo muss ich etwas falsch verstanden haben. Kann
mir jemand erklären was das ist?
Weniger wichtig, aber bei der Gelegenheit stellen sich mir noch
weitergehende Fragen zum I2C-Standard:
3.) Wann darf der Master überhaupt eine Stop-Condition senden? Ist es
insbesondere erlaubt, eine Stop-Condition unmittelbar nach einer
Start-Condition zu senden? Ich konnte auf die Schnelle nichts dazu in
der I2C-Spezifikation finden. Es kann aber sein, dass ich es nur
übersehen habe.
4.) Darf ein Slave direkt nach der Start-Condition überhaupt clock
stretching betreiben? Die Spezifikation spricht in Abschnitt 3.1.9 nur
von clock stretching nach einer Byte-Übertragung (erlaubt in standard
und fast mode) und clock stretching nach einer Bit-Übertragung (nur
erlaubt im standard mode). Von clock stretching direkt nach der
Start-Condition ist nicht die Rede.
Der Grund, warum ich mir vorstellen könnte, dass es womöglich
unerwünscht sein sollte, ist folgender: Wenn der Slave sehr schnell ist
(oder der Master langsam), zieht er SCL runter und lässt es wieder los,
noch bevor der Master es low zieht. Für einen dritten Beobachter sieht
das so aus als wäre ein Takt gesendet worden (mit einer Null als Datum,
da SDA low ist). Das stört zwar weder Master noch Slave, aber es
verletzt den "Grundsatz", dass bei einem vernünftigen Protokoll ein
Beobachter am Bus in der Lage sein sollte zu sagen was Sache ist.
Außerdem wird hier nicht eine Low-Periode von SCL "gestretcht", sondern
der Slave erzeugt eine tatsächliche Flanke. Das kann doch nicht gut
sein, oder? Was meint ihr dazu?
Bert 4. schrieb:> und clock stretching nach einer Bit-Übertragung (nur> erlaubt im standard mode).
"On the bit level, a device such as a microcontroller with or without
limited hardware for the I2C-bus, can slow down the bus clock by
extending each clock LOW period. The speed of any master is adapted to
the internal operating rate of this device."
Da steht nichts von "nach" oder "vor" oder "Bit-Übertragung", da steht
"On the bit level ... by extending each clock LOW period". "Each" wird
so gemeint sein, wie es gemeint ist.
Oliver
Bert 4. schrieb:> Wenn der Slave sehr schnell ist> (oder der Master langsam), zieht er SCL runter und lässt es wieder los,> noch bevor der Master es low zieht.
Der Slave zieht nie runter, er darf es nur unten halten.
@Oliver: Nun, eine Start-Condition ist ja keine Bit-Übertragung. Und
"extending clock low period" ist etwas anderes als selbst eine negative
Flanke zu erzeugen und dann low zu halten. Das spräche dafür, dass es
nicht erlaubt ist.
@Michael: Ja, so hätte ich das auch interpretiert. Das hieße dann aber,
dass der USI etwas Verbotenes macht, denn er zieht - wenn ich das
richtig verstehe - SCL auf low sobald der Start-Condition-Interrupt
anspringt. Und anspringen tut er schon bei der fallenden SDA-Flanke,
nicht erst bei der darauf folgenden fallenden SCL-Flanke.
Bert 4. schrieb:> Dementsprechend bricht die while-Schleife sofort beim> ersten Durchlauf ab
Der Start Condition Detector löst bei SDA High-Low aus. SCL darf laut
I²C- Spec aber erst nach mindestens 4us später low gehen. Wenn dein Tiny
hoch getaktet ist, ist der schneller in der ISR.
Oliver
@Oliver: Nicht der ISR triggert das clock stretching, sondern die
Hardware macht das. Es ist also unabhängig vom Takt des ATtiny.
Aber ich glaube ich verstehe jetzt. Wenn man sich Abbildung 15-6
anschaut, dann triggert die fallende SDA-Flanke zwar USISIF, aber clock
hold passiert erst, wenn dann noch eine fallende Flanke auf SCL kommt.
Ok, ich denke damit ist 4.) im Wesentlichen beantwortet. Was mich aber
viel mehr interessieren würde: Hat jemand eine Idee für 1.) und 2.)?
Ich würde das Datenblatt da aber anders interpretieren:
> In addition, the start detector will hold the SCL line low after the master has
forced a negative edge on this line (B). This allows the slave to wake up from
sleep or complete other tasks before setting up the USI Data Register to receive
the address. This is done by clearing the start ondition flag and resetting the
counter.
Liest sich für mich so, als ob das USI schon warten würde, bis SCL auf
low geht, dieses dann aber low halten (erstmal unabhängig vom
Interrupt). Wenn man nun zu schnell in der ISR ist, sollte man dort
warten bis SCL auf low geht.
Edit: ich war mal wieder zu langsam ;-)
Du hast recht und jetzt ergibt das für mich auch mehr Sinn. Damit sind
1.) und 4.) beantwortet. 2.) und 3.) wären noch offen.
Zu 3.): Mir scheint der USI kann beispielsweise nicht damit umgehen,
wenn mitten im Byte eine Stop-Condition kommt. Dazu müsste man im
Hauptprogramm USIPF pollen, richtig? Oder braucht man das nicht zu tun,
weil es einfach verboten ist?
Ich kenn das nur vom "großen" TWI, da gibts den TWI-Status 0x00
> Status 0x00 indicates that a bus error has occurred during a 2-wire> Serial Bus transfer. A bus error occurs when a START or STOP condition> occurs at an illegal position in the format frame.
Wie das USI damit umgeht - keine Ahnung.
Bert 4. schrieb:> Zu 3.): Mir scheint der USI kann beispielsweise nicht damit umgehen,> wenn mitten im Byte eine Stop-Condition kommt. Dazu müsste man im> Hauptprogramm USIPF pollen, richtig? Oder braucht man das nicht zu tun,> weil es einfach verboten ist?
Das Stop braucht man normaler Weise nicht, außer als Multimaster.
Man kriegt irgendwann den nächsten Startinterrupt und gut.
Bert 4. schrieb:> Ist es> insbesondere erlaubt, eine Stop-Condition unmittelbar nach einer> Start-Condition zu senden?
3.10 Note 5:
"A START condition immediately followed by a STOP condition (void
message) is an illegal format. Many devices however are designed to
operate properly under this condition."
Oliver
Bert 4. schrieb:> @Oliver: Nun, eine Start-Condition ist ja keine Bit-Übertragung.
Es gibt den Begriff "Bit-Übertragung" nicht. Der Stadard schreibt "on
bit level", was übersetzt "auf Bit-Ebene" bedeutet. Und da steht
eindeutig, daß jedes SCL LOW vom Slave verlängert werden darf (was die
USI ja schon mit dem ersten LOW macht).
Oliver
Peter D. schrieb:> Das Stop braucht man normaler Weise nicht,Bert 4. schrieb:> Zu 3.): Mir scheint der USI kann beispielsweise nicht damit umgehen,> wenn mitten im Byte eine Stop-Condition kommt. Dazu müsste man im> Hauptprogramm USIPF pollen, richtig? Oder braucht man das nicht zu tun,> weil es einfach verboten ist?
Es ist zwar nicht explizit vorgeschrieben, aber vielleicht doch
sinnvoll, beim Erkennen einer Stop-Bedingung die TWI-Logik intern
zurückzusetzen.
Denn auch wenn es kein normaler Ablauf ist, Murphy und EMV sind
überall...
Oliver
Oliver S. schrieb:> Es ist zwar nicht explizit vorgeschrieben, aber vielleicht doch> sinnvoll, beim Erkennen einer Stop-Bedingung die TWI-Logik intern> zurückzusetzen.
Was aber gar nicht trivial ist. Weil zB ein Slave mitten in der
Übertragung "hängt"
Ich verwende dann eine "bit-banging" i2c reset routine, die zuerst 9
clocks sendet, und dann erst einen reset macht.
Michael R. schrieb:> Was aber gar nicht trivial ist. Weil zB ein Slave mitten in der> Übertragung "hängt"
Als (alleiniger) Master braucht man ja auch kein Stop erkennen, weil man
es nur selber erzeugen kann.
Oliver
Oliver S. schrieb:> 3.10 Note 5:>> "A START condition immediately followed by a STOP condition (void> message) is an illegal format. Many devices however are designed to> operate properly under this condition."
Danke! Das habe ich gesucht.
Peter D. schrieb:> Das Stop braucht man normaler Weise nicht, außer als Multimaster.> Man kriegt irgendwann den nächsten Startinterrupt und gut.
Es gibt schon Gründe, warum man ein Stop erkennen möchte. Zum Beispiel
würde ich den uC gerne schlafen legen und Strom sparen. Selbst wenn der
Master sich vorbildlich verhält, gibt es bei einem Schreibvorgang keine
Möglichkeit festzustellen, wann das Ende erreicht ist. Außer eben per
Stop-Condition.
Oliver S. schrieb:> Michael R. schrieb:>> Was aber gar nicht trivial ist. Weil zB ein Slave mitten in der>> Übertragung "hängt">> Als (alleiniger) Master braucht man ja auch kein Stop erkennen, weil man> es nur selber erzeugen kann.
Ja, mir ging es da um den Slave.
Aber auch der master kann hängen bleiben, weil ein Slave mitten im
Clock-Stretching hängenbleibt.
i2c-reset ist wirklich nicht trivial
Bert 4. schrieb:> Es gibt schon Gründe, warum man ein Stop erkennen möchte. Zum Beispiel> würde ich den uC gerne schlafen legen und Strom sparen. Selbst wenn der> Master sich vorbildlich verhält, gibt es bei einem Schreibvorgang keine> Möglichkeit festzustellen, wann das Ende erreicht ist. Außer eben per> Stop-Condition.
???
Bist du sicher, dass du I2C wirklich verstanden hast? Gedacht war das
mal so:
Der jeweils Empfangende gibt vor, wann ein Schreibvorgang beendet ist.
Nämlich dadurch, das er das letzte, ihn interessierende Datum nicht mehr
ACKed und damit seine Nichtbereitschaft zum Empfang weiterer Daten
anzeigt.
Für einen Slave ist IN DIESEM MOMENT immer die Sache erledigt, egal, ob
er gerade Sender oder Empfänger ist, entweder sendet er halt kein ACK
oder stellt fest, dass vom Master kein ACK kam, in beiden Fällen kann er
ab diesem Moment einfach schlafen gehen. Von allen legalen und illegalen
Ereignissen auf dem Bus ab diesem Moment interessiert einen Slave exakt
nur eins: die nächste Start-Condition, bis dahin kann er alles
ignorieren, denn es kann ihn nicht betreffen.
Bei einem Master ist das auch nur geringfügig komplizierter. Der muss
in dieser Situation entscheiden, was er als nächstes tut. Das können
aber auch nur zwei sinnvolle Sachen sein: Stop (Ende der Kommunikation
mit dem Slave) oder RepeatedStart (neue Kommunikation mit dem gleichen
Slave).
Besonders bescheuerte Slave-Implementierungen nehmen's nun mit der
ACKerei nicht so genau, sondern verlassen sich darauf, dass der Master
weiss, wieviele Bytes sie in einer gegebenen Situation senden oder
empfangen wollen/können. Aber auch dieser Sachverhalt tangiert andere
Slaves am Bus eigentlich überhaupt nicht. Nur der Master muss hier
besser wissen, was er tut.
Fazit: eine Slave-Implementierung ist eigentlich überaus trivial.
Jedenfalls, wenn der Master Clock-Stretching korrekt handeln kann. Und
auch ansonsten hat man eigentlich nur mit Timing-Constraints zu tun. Man
muss dann schlicht "jederzeit" "schnell genug" reagieren können. Asm
rules. Damit kann man das nämlich beweisbar hinreichend schnell
implementieren.
Du beantwortest es dir doch quasi selbst: Nur der Empfangende kann
mittels NACK das Ende einer Übertragung festlegen. Üblicherweise sind
die über I2C gefahrenen Protokolle aber Master-getrieben, im Sinne von
"der Master bestimmt Zeitpunkt, Form und Länge einer Übertragung". Daher
wird bei Schreibvorgängen erst durch das Stop dem Slave klar, das
Schluss ist.
Natürlich kannst du das übergeordnete Protokoll so bauen, dass das
Problem vermieden wird. Zum Beispiel könnte der Master vor jedem
Schreibvorgang die Zahl der zu übertragenden Bytes mitteilen. Im der
Praxis ist es aber so, dass sich gewisse Protokolle durchgesetzt haben.
Soweit ich das sehe, ist das verbreitetste das, wo der Slave ein
Registerfile/EEPROM simuliert. Nahezu alle I2C-Slaves, die mir bisher in
die Finger gekommen sind, arbeiten so. Und dort ist außer dem Stop eben
keine Möglichkeit vorgesehen.
Wenn man ganz streng sein will, wäre es auch ein Missbrauch der
I2C-Spezifikation. Wenn du dir mal Abschnitt 3.1.6 anschaust, dort
werden die Voraussetzungen für ein NACK gelistet. "End of transfer" ist
nur in der Master-Receiver-Situation ein Grund zu NACKen. Die Fälle 1-4
beschreiben allesamt Fehlersitationen oder Nichtverfügbarkeit, aber kein
reguläres Übertragungsende.
Bert 4. schrieb:> Üblicherweise sind> die über I2C gefahrenen Protokolle aber Master-getrieben, im Sinne von> "der Master bestimmt Zeitpunkt, Form und Länge einer Übertragung". Daher> wird bei Schreibvorgängen erst durch das Stop dem Slave klar, das> Schluss ist.
Zeitpunkt ja, Form und Länge beim Datentransfer nein. Der Master muß das
tun, was der Slave erwartet. Klar kann der Master die vom Slave
erwarteten oder vorgebenden Formate und Längen ignorieren, aber sinnvoll
ist das nicht.
Oliver
Ich sage nicht, dass der Master das Protokoll ignorieren soll. Aber im
Rahmen des Protokolls legt der Master diese Dinge fest (bei solchen
Protokollen, die ich als "Master-getrieben" bezeichnet habe).
Wieder als Beispiel das EEPROM: Der Master entscheidet wann Daten
übertragen werden, er entscheidet die Richtung und er entscheidet die
Länge. Wenn der Master schreibt und es geht etwas schief (z.B. er
schreibt über das Speicherende hinaus), kann der Slave NACKen. Aber es
steht ihm nicht zu, unter regulären Bedingungen über das Ende eines
Schreibvorgangs zu entscheiden. Das macht der Master und gibt es per
Stop (oder Restart) bekannt.
Das führt jetzt aber auch alles ein bisschen weit vom Thema weg.
Eigentlich wollte ich nur sagen, dass ich c-haters Aussage für falsch
halte: Lesevorgänge enden üblicherweise mit einem NACK, auch wenn sie
erfolgreich sind. Aber Schreibvorgänge tun dies nicht, außer es tritt
tatsächlich ein Fehler auf. Das sieht (jedenfalls bei strenger Lesart)
die I2C-Spezifikation so vor und die meisten übergeordneten Protokolle
halten sich daran.
Bert 4. schrieb:> Daher> wird bei Schreibvorgängen erst durch das Stop dem Slave klar, das> Schluss ist.
Das läßt sich aber durch den Slave schlecht auswerten, da es keinen
Interrupt erzeugt.
Es ist also einfacher, wenn der Slave die Bytes mitzählt, bis die
erwartete Anzahl erreicht wurde.
Ist ein Paket unvollständig, wird es eben ignoriert, da das nächste
Start den Puffer-Counter zurücksetzt.
Überzählige Bytes werden geNACKt, damit der Master das merkt.
Peter D. schrieb:> Das läßt sich aber durch den Slave schlecht auswerten, da es keinen> Interrupt erzeugt.
Eben. Das ist m.E. ein Designfehler im USI.
> Es ist also einfacher, wenn der Slave die Bytes mitzählt, bis die> erwartete Anzahl erreicht wurde.
Dazu muss der Slave die erwartete Anzahl kennen. Sowas kannst du machen,
wenn du selber ein Protokoll entwirfst. Ansonsten musst du wohl oder
übel USIPF pollen.
Die Idee hatte ich tatsächlich auch schon, habe aber nach einigen Tests
davon abgelassen. Der PCINT feuert so häufig, dass die I2C-Kommunikation
ziemlich stark verlangsamt wird. Dazu kommt, dass man den PCINT sehr
schnell bedienen muss, sonst kommt man schnell in die Situation, dass
beide Pins eine Flanke hatten und man die Reihenfolge nicht mehr sieht.
In meiner konkreten Anwendung ist Polling vergleichsweise einfach,
deshalb mache ich lieber das.
Bert 4. schrieb:> Dazu muss der Slave die erwartete Anzahl kennen. Sowas kannst du machen,> wenn du selber ein Protokoll entwirfst. Ansonsten musst du wohl oder> übel USIPF pollen.
Kannst Du mal ein Protokoll zeigen, welches Stop benötigt?