mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik ATtiny USI als I2C slave: Start-Detection-Problematik


Autor: Bert 4. (b42)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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:
init()
{
   [...]
   SCL-Pin = output, high;
   SDA-Pin = input, high;
   USICR = [...] | (0b10 << USIWM0) | [...];
   [...]
}

ISR(USI_START_vect)
{
   [...]
   while(SCL high && SDA low);
   if(SDA low) // Alternativ "if(SCL low)"
   {
      // Keine Stop-Condition
      Enable Overflow Interrupt; Setze 4-bit-Counter auf 0; etc;
   }
   else
   {
      // Stop-Condition
      Setze alles zurück;
   }
   Clear interrupts in USISR;
}

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?

Autor: Bert 4. (b42)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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?

Autor: Oliver S. (oliverso)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Michael R. (fisa)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Bert 4. (b42)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@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.

: Bearbeitet durch User
Autor: Oliver S. (oliverso)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Bert 4. (b42)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@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.)?

Autor: Michael R. (fisa)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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 ;-)

: Bearbeitet durch User
Autor: Bert 4. (b42)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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?

: Bearbeitet durch User
Autor: Michael R. (fisa)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Peter D. (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Oliver S. (oliverso)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Oliver S. (oliverso)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Oliver S. (oliverso)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

: Bearbeitet durch User
Autor: Michael R. (fisa)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Oliver S. (oliverso)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Bert 4. (b42)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Michael R. (fisa)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: c-hater (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Bert 4. (b42)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Oliver S. (oliverso)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Bert 4. (b42)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Peter D. (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Bert 4. (b42)
Datum:

Bewertung
2 lesenswert
nicht lesenswert
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.

Autor: Michael R. (fisa)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Bert 4. schrieb:
> Ansonsten musst du wohl oder
> übel USIPF pollen.

oder vielleicht (parallel) einen Pin Change Interrupt nutzen?

Autor: Bert 4. (b42)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Peter D. (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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?

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.