Forum: Mikrocontroller und Digitale Elektronik AVR: TWI (I²C) Slave mit oder ohne Interrupt?


von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Hallo allerseits,

ich habe seit vielen Monaten eine eigene TWI-Implementierung getrennt 
für Master und Slave für den ATmega328P in Verwendung, die eigentlich 
gut und stabil läuft. Beide waren komplett interrupt-basierend, mit 
einer State Machine im TWI-Interrupt.

letztens habe ich versucht damit ein DOG XL 240 Grafikdisplay 
anzusteuern, hätte zwar funktioniert, ich bin aber draufgekommen dass da 
meine interrupt-Lösung suboptimal ist, u.a. weil sie einen Buffer für 
das ganze Paket benötigt, und dieser Buffer unnötig ist, weil alle Bytes 
"on the fly" berechnet werden können (Font rendering)

Ich habe nun den Master auf eine interrupt-lose Variante umgestellt, und 
zu meiner Überraschung festgestellt dass der Code (sowohl der 
eigentliche i2c-master als auch der "LCD-Treiber") nun besser, 
schneller, hübscher und effizienter ist. Man lernt ja jeden Tag etwas 
neues...

Nun stelle ich mir die Frage, ob eine solche Umstellung nicht auch 
meinem Slave gut täte. Hier sehe ich noch nicht so klar...

Wichtig am Slave ist, dass der möglichst schnell auf einen "Anruf" vom 
Master reagiert und diese beantwortet. Ohne Interrupt bedeutet das, dass 
ich in der main-Schleife recht häufig TWI prüfen müsste, und andere, 
u.U. zeitlich längere Aktionen das stören könnten. Klassischer 
Anwendungsfall für einen Interrupt...

Die eigentliche TWI-Kommunikation lässt sich aber (wie am Master) 
eigentlich eleganter "straight-forward" ohne Interrupt abbilden. Und nun 
bin ich unsicher:

a) Slave komplett ohne Interrupt, mit pollen in der main loop?

b) Slave reagiert nur auf die Adressierung mit sla+rw per Interrupt, 
wickelt die restliche Kommunikation aber ohne interrupts direkt in der 
ISR ab?

c) Slave komplett wie gehabt interrupt-basierend

Hat darüber schon mal jemand nachgedacht, Vor- und Nachteile 
zusammengetragen, implementierungen verglichen, etc?

Über einen entsprechenden Erfahrungsaustausch würde ich mich sehr 
freuen!

lg Michi

von Peter D. (peda)


Lesenswert?

Die Frage ist, ob das Main nur auf I2C wartet oder nebenbei noch was 
anderes tun soll.

Ohne Interrupt kommt man eigentlich nur bei einfachsten Programmen aus.

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Peter Dannegger schrieb:
> Die Frage ist, ob das Main nur auf I2C wartet oder nebenbei noch was
> anderes tun soll.

Das Main am Slave macht noch was anderes, je nach Anwendung. Momentan 
habe ich folgende Fälle:

- Ansteuerung eines Grafik-LCD (damit habe ich ein "Parallelport-LCD" in 
eins mit i2c verwandelt)

- Modbus-Interface für meine Heizung / Wärmepumpe

- Schrittmotor-Steuerung

in allen Fällen ist der Slave auch anderweitig "beschäftigt"

> Ohne Interrupt kommt man eigentlich nur bei einfachsten Programmen aus.
Das ist sicher richtig, nur beim i2c-master hat sich gezeigt dass es 
ohne TWI-Interrupt besser und im Endeffekt einfacher geht

von Dennis H. (t1w2i3s4t5e6r)


Lesenswert?

Dann musst du eine komische Implementation vom TWI haben. Werde mal 
etwas konkreter, was denn besser ohne Interrupt läuft? TWI ist halt 
immer mit bisschen Software verbunden, alles in Hardware und das er erst 
einen Interrupt auslöst, wenn Daten da sind das wäre mal schön, klappt 
aber eben nicht. Dort jedesmal ohne Interrupt darauf zu warten, das TWI 
den nächsten Schritt beendet hat klingt für mich nicht wirklich 
effizienter. Was wirklich ne Katastrophe bei TWI ist, ist die 
Fehlererkennung. Keine Ahnung, ob du da auch was in deinem Code dazu 
hast. Wenn es die Fehlererkennung schwierig macht, dann kann ich 
Soft-I²C empfehlen, das geht dort wesentlich einfacher.


Dennis

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Dennis H. schrieb:
> Dann musst du eine komische Implementation vom TWI haben.

ich glaub nicht dass meine (Master-)Implementierung "komisch" ist, sie 
orientiert sich weitestgehend am (weit verbreiteten) Code vom Peter 
Fleury. Die arbeitet nicht mit Interrupts, ich jetzt auch nicht, vorher 
schon mit großer ISR.

Nur damit das klar ist: mit dem master hab ich kein Problem, beim Slave 
stell ich mir die obigen Fragen...

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Daß Interrupt Master langsamer sein soll, höre ich auch zum ersten mal. 
Da muß was falsch implementiert sein. Im Master Interrupt darf nirgends 
gewartet werden!

Mit Slave ohne Interrupt würdest Du den Master massiv ausbremsen, der 
muß dann ständig Däumchen drehen, bis der Slave gnädig das Byte abgeholt 
hat.

von Peter D. (peda)


Lesenswert?

Michael Reinelt schrieb:
> ich glaub nicht dass meine (Master-)Implementierung "komisch" ist, sie
> orientiert sich weitestgehend am (weit verbreiteten) Code vom Peter
> Fleury. Die arbeitet nicht mit Interrupts

Man kann nicht einfach eine Routine ohne Interrupts in einen Interrupt 
rein pappen.
Die muß komplett anders aussehen. Sämtliche Warteabfragen müssen den 
Interrupt verlassen.

von Michael S. (Gast)


Lesenswert?

Hallo Michael Reinelt,

einen TWI-Slave pollend zu konzipieren, halte ich für ziemlich sinnfrei.

Warum ?

Erstens, weil ich Ereignisse, die einen Interrupt auslösen, vorrangig 
via Interrupt behandeln würde.
Schneller und eleganter geht es nicht. Aber das mag ja noch 
Geschackssache sein.

Zweitens, weil es timing-Probleme geben wird.
Wenn der TWI-Takt nur 100 KHz beträgt, dann kommen die Bytes im 
zeitlichen Abstand von ca. 100us herein.
Es muss in entsprechend kleineren Zeitabständen das TWI-Register/Flag 
gepollt werden.

Angenommen, der Controller läuft mit 4 MHz, dann muss das Pollen 
innerhalb von weniger als 400 Takten erfolgen.
Wenn aber Funktionen mehr als 400 Takte benötigen, dann gehen empfangene 
Bytes verloren.

Daher würde ich den Konzept des pollenden TWI-Slaves glatt verwerfen.


Michael S.

von Peter D. (peda)


Lesenswert?

Michael S. schrieb:
> Wenn aber Funktionen mehr als 400 Takte benötigen, dann gehen empfangene
> Bytes verloren.

Beim I2C kann nichts verloren gehen.
Der Slave hält SCL auf low und der Master dreht Däumchen, bis er wieder 
SCL auf high sieht.

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Peter Dannegger schrieb:
> Daß Interrupt Master langsamer sein soll, höre ich auch zum ersten mal.
> Da muß was falsch implementiert sein. Im Master Interrupt darf nirgends
> gewartet werden!

Wird auch nicht...

Peter Dannegger schrieb:
> Man kann nicht einfach eine Routine ohne Interrupts in einen Interrupt
> rein pappen.
Hab ich nicht.
> Die muß komplett anders aussehen. Sämtliche Warteabfragen müssen den
> Interrupt verlassen.
Tun sie...

ich glaub ich hab das etwas mißverständlich formuliert. meine "alte", 
interrupt-basierte master-Implementierung sah selbstverständlich ganz 
anders aus als die "neue" ohne Interrupts, ähnlihc dem Fleury-Code.

nehmen wir ein Beispiel: ich möchte ein Byte aus einem Register von 
einem fiktiven Sensor auslesen.

Die Sequenz am Master ist ja standardisiert:
- START auf den Bus
- SLA+W senden
- Registeradresse senden
- REPEATED START senden
- SLA+R senden
- Byte empfangen
- STOP auf den Bus

Die Sequenz läuft einigermaßen flott ab, ich verschenke also nicht 
extrem viele CPU-Zyklen in der Sequenz. Die könnte ich zwar mit einer 
Interrupt-basierten Implementierung nutzen, nur - wofür? ich brauch das 
Ergebnis um weiterarbeiten zu können, müsste also nachdem ich die 
Interrupt-Variante gestartet habe, sowieso auf irgendein Flag 
"Transmission finished" welches in der ISR gesetzt wird, warten. Für die 
ISR brauch ich auch noch ein paar Status-Variablen, einen Buffer für das 
zu sendende Byte, und die Repeated-Start-Sequenz ist sowieso schwierig 
zu kapseln.

Da ist die Interrupt-lose Variante echt einfacher:
1
TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN); // START senden
2
while (!(TWCR & (1 << TWINT))); // Warten
3
TWDR = slarw; // SLA+W
4
TWCR = (1 << TWINT) | (1 << TWEN); // Warten
5
TWDR = data; // gewünschte Registernummer senden
6
TWCR = (1 << TWINT) | (1 << TWEN); // Warten
7
TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN); // Repeated START senden
8
while (!(TWCR & (1 << TWINT))); // Warten
9
...

(Da fehlt jetzt natürlich die ganze Status-prüfung, soll ja nur das 
prinzip verdeutlichen)

Aber - machen wirs mal andersrum: Peter, ich kenne und schätze deine 
diversen C-Codes über alles. Gibts von dir was zum Thema i2c?

: Bearbeitet durch User
von Michael S. (Gast)


Lesenswert?

Peter Dannegger schrieb:

> Beim I2C kann nichts verloren gehen.
> Der Slave hält SCL auf low und der Master dreht Däumchen, bis er wieder
> SCL auf high sieht.

Wenn es so wäre wie behauptet, dann würde die gesamte TWI-Kommunikation 
gestoppt, wenn ein einzelner Slave (genaugenommen die Software darauf) 
empfangene Bytes nicht abholen würde.
Das würde mich wundern, habe es aber explizit nie nicht getestet.

Abgesehen davon soll es ja Slaves geben, die das Clock-Stretching nicht 
unterstützen ...

Wobei ich übrigens immer von Hardware-TWI ausgehe.


Michael S.

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Michael S. schrieb:
> Wenn es so wäre wie behauptet, dann würde die gesamte TWI-Kommunikation
> gestoppt, wenn ein einzelner Slave (genaugenommen die Software darauf)
> empfangene Bytes nicht abholen würde.

Das ist so. Wobei "nicht abholen" nicht ganz der richtige Ausdruck ist. 
Das ist aber nicht unbedingt eine besodnerheit von I2C, sondern von 
allen Wirde-And-Bussen: Wenn nur ein Teilnehmer den Bus blockiert (indem 
er bei I2C SDA und/oder SCL auf low hält) geht nix mehr.

Michael S. schrieb:
> Abgesehen davon soll es ja Slaves geben, die das Clock-Stretching nicht
> unterstützen ...

Kann man so nicht sagen: Clock-Stretching macht wenn dann nur der Slave 
(wenn er es nicht unterstützt, passiert gar nix) aber der Master muss es 
unterstützen im Sinne von "verstehen".

: Bearbeitet durch User
von c-hater (Gast)


Lesenswert?

Michael Reinelt schrieb:

> Die Sequenz läuft einigermaßen flott ab, ich verschenke also nicht
> extrem viele CPU-Zyklen in der Sequenz. Die könnte ich zwar mit einer
> Interrupt-basierten Implementierung nutzen, nur - wofür?

Um all die Sachen zu erledigen, die nix mit der I2C-Kommunikation zu tun 
haben.

Wenn es sowas auf deinem Master nicht gibt, dann ist es halt ein 
SingleTask-Primitivstprogramm, was da läuft. Dafür braucht man dann 
wirklich keine Interrupts.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Michael Reinelt schrieb:
> Kann man so nicht sagen: Clock-Stretching macht wenn dann nur der Slave
> (wenn er es nicht unterstützt, passiert gar nix) aber der Master muss es
> unterstützen im Sinne von "verstehen".

 Nicht aufregen, ich habe auch eine ähnliche Lösung -
 Master ohne Interrupts, aber Slave MUSS mit Interrupts laufen.
 Warum:
 Wenn Master 1 Byte (oder 1 Block) sendet, kann er auch die paar Takte
 bis ACK oder NACK kommt, abwarten - also keine ISR notig. SLAVE muß
 eben innerhalb bestimmter Zeit antworten.
 Dafür muß aber SLAVE so schnell wie möglich antworten, deshalb ist beim
 SLAVE ISR-Empfang die bessere Lösung.

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

c-hater schrieb:
> Um all die Sachen zu erledigen, die nix mit der I2C-Kommunikation zu tun
> haben.
>
> Wenn es sowas auf deinem Master nicht gibt, dann ist es halt ein
> SingleTask-Primitivstprogramm, was da läuft. Dafür braucht man dann
> wirklich keine Interrupts.

Ich überhöre jetzt mal den leicht lächerlich machenden Ton.

Ich probiers mal anders: Die Fleury-Lib für I2C-Master scheint am 
weitesten verbreitet zu sein, auch wenn man hier das Forum durchsucht. 
Wenn das so "primitiv" ist, welche I2c-master-Implementierungen 
verwendets ihr dann? Könnte da mal jemand einen Link posten?

Aber mir gehts ohnehin nciht um den master, den hab ich ja im Griff 
(auch wenns "SingleTask-Primitivstprogramm" ist)

Wie implementierts ihr den Slave? Gibts da auch sowas wie eine 
"Standard-Bibliothek"? (Fleury bietet meines Wissens nach ja keinen 
Slave)

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Michael S. schrieb:
> Wenn es so wäre wie behauptet, dann würde die gesamte TWI-Kommunikation
> gestoppt, wenn ein einzelner Slave (genaugenommen die Software darauf)
> empfangene Bytes nicht abholen würde.
> Das würde mich wundern, habe es aber explizit nie nicht getestet.

 Braucht dich nicht zu wundern, es ist so.
 Low ist Dominant, wenn ein SLAVE die Leitung auf LOW zieht und nicht
 losläßt, ist die I2C Kommunikation zu Ende.

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Marc Vesely schrieb:
> Nicht aufregen, ich habe auch eine ähnliche Lösung -
>  Master ohne Interrupts, aber Slave MUSS mit Interrupts laufen.

Danke, das bestätigt mich.

Ich überlege nur noch, ob es sinnvoll ist, die gesamte Kommunikation per 
Interrupt abzuwickeln, oder nur den ersten "Master ruft an" auf die ISR 
zu legen, und die restliche Kommunikation in der ISR zu machen.

Erscheint mir aber wenig sinnvoll, je mehr ich darüber nachdenke.

anyway: Marc, darfst du deinen Slave-Code zeigen? Es gibt vergleichswese 
wenig Slave-Implementierungen da draußen, und ich lerne und vergleiche 
gerne bestehenden, funktionierenden, bewährten Code.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Michael Reinelt schrieb:
> Wie implementierts ihr den Slave? Gibts da auch sowas wie eine
> "Standard-Bibliothek"? (Fleury bietet meines Wissens nach ja keinen
> Slave)

 Bei der Kommunikation ohne Interrupt prüfst du TWSR nach der Aktion
 auf Status. Mit Interrupt ist alles dasselbe wie früher, nur am Anfang
 der ISR prüfst du TWSR auf Status, damit du überhaupt weisst, warum du
 in ISR gelandet bist.
 Es sind, glaube ich 9 Zustände fur SLAVE als Empfänger und 5 Zustände
 für SLAVE als Sender möglich und alle sind im DaBla gut erklärt.
 Wenn Status <> OK fur ausgeführte Aktion, FehlerFlag setzen, I2C-Bus
 so schnell wie möglich freigeben und weiter mit Fehlerbearbeitung.
 Wenn Status = OK, nächsten Schritt ausführen usw.

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Marc Vesely schrieb:
> Low ist Dominant, wenn ein SLAVE die Leitung auf LOW zieht und nicht
>  losläßt, ist die I2C Kommunikation zu Ende.

Wobei man im Idealfall diese Situation vom Master aus noch versuchen 
kann zu bereinigen: TWI abschalten, händisch neun Clock-Impulse 
rausschreiben, und hoffen dass sich der Slave wieder erholt. Wenn nicht, 
ist schicht im Schacht. Da hilft nichtmal ein Reboot des Masters (per 
Watchdog o.ä.)

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Marc Vesely schrieb:
> Bei der Kommunikation ohne Interrupt prüfst du TWSR nach der Aktion
>  auf Status. Mit Interrupt ist alles dasselbe wie früher, nur am Anfang
>  der ISR prüfst du TWSR auf Status, damit du überhaupt weisst, warum du
>  in ISR gelandet bist.
>  Es sind, glaube ich 9 Zustände fur SLAVE als Empfänger und 5 Zustände
>  für SLAVE als Sender möglich und alle sind im DaBla gut erklärt.
>  Wenn Status <> OK fur ausgeführte Aktion, FehlerFlag setzen, I2C-Bus
>  so schnell wie möglich freigeben und weiter mit Fehlerbearbeitung.
>  Wenn Status = OK, nächsten Schritt ausführen usw.

Ja, genau so ist mein momentaner Slave ausgeführt. Allerdings sind es 
schon etwas mehr als 9 Zustände (viele davon kann man aber 
zusammenfassen)

Trotzdem würd ich gerne mal die eine oder andere "fremde" 
Implementierung sehen.

Wenn das nicht möglich ist, poste ich gerne meine Variante, und hoffe 
auf kritische aber konstruktive Kommentare

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Michael Reinelt schrieb:
> anyway: Marc, darfst du deinen Slave-Code zeigen? Es gibt vergleichswese

 Assembler.
 Aber glaube mir, du brauchst keinen fremden Code, nur TWSR am
 Anfang der ISR-Routine prüfen...
 Gerade rumgestöbert, kann es nicht finden, aber im C wäre das
 etwas ähnliches:
 switch(TWSR & 0xF8) {
   case 0x60 :  /* Own SLA+W has been received; ACK has been returned */
     Action_1 ;  /* Flag setzen, Zeiger erhöhen... */
     break ;
   case 0xA8 :  /* Own SLA+R has been received; ACK has been returned */
     Action_2 ;
     break ;
  usw...

 am Anfang der ISR-Routine und das ist schon die komplette Umstellung.

von Michael R. (Firma: Brainit GmbH) (fisa)


Angehängte Dateien:

Lesenswert?

Marc Vesely schrieb:
>  Aber glaube mir, du brauchst keinen fremden Code, nur TWSR am
>  Anfang der ISR-Routine prüfen...

Ja, aber da gibts sicher viel (Fehler-) Potential.

Anbei meine momentane Implementierung, bitte um Kritik

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.