Forum: Mikrocontroller und Digitale Elektronik I2C von Polling zum Interrupt


von Uwe (Gast)


Lesenswert?

Hallo zusammen,

ich habe lange Zeit I2C im Polling Mode benutzt, also bei einem Read 
oder Write war nach Aufruf der Funktion der Datentransfer abgearbeitet.

Nun möchte ich den I2C-Bus im Interrupt Mode verwenden. Das ganze klappt 
auch schon, ich habe aber noch eine grundsätzliche Frage. Ich Starte mit 
dem Aufruf einer Read oder Write Funktion nur die I2C Statemachine, der 
eigentliche Datentransfer wird im Hintergrund ausgeführt. Nach Beendung 
wird eine Callback Funktion aufgerufen.

Im Moment starte ich zum Beispiel ein Read und warte danach 20ms, damit 
die Statemachine abgearbeit werden kann. Das ist aber nicht Sinn der 
Sache, wie bekomme ich meine I2C mit der Callback Routine verheiratet?

von Karl H. (kbuchegg)


Lesenswert?

Uwe schrieb:

> Im Moment starte ich zum Beispiel ein Read und warte danach 20ms, damit
> die Statemachine abgearbeit werden kann. Das ist aber nicht Sinn der
> Sache, wie bekomme ich meine I2C mit der Callback Routine verheiratet?

Indem du deine Statmachine ganz zum Schluss zb die Callback Routine 
aufrufen lässt oder aber wenn du keinen Funktionaufruf aus der ISR 
heraus haben willst, dann setzt die Statemachine ganz zum Schluss eine 
globale Flag-Variable auf 'Übertragung FERTIG' und du pollst in der 
Hauptschleife auf diese Variable.

Jetzt magst du dich fragen, wozu dann überhaupt Interrupt, wenn ich 
sowieso pollen muss?
Du hast eine bessere Organisationsform, bei der du einfacher andere 
Dinge erledigen kannst, während die I2C Übertragung im Hintergrund 
abgewickelt wird. Der Trick bei der Programmierung, bei der ein µC 
scheinbar viele Dinge mehr oder weniger gleichzeitig erledigen kann, 
besteht darin, dass die einzelnen Tätigkeiten auf kleine Zeithappen 
heruntergebrochen werden und nirgends viel Zeit in einem Rutsch drauf 
geht. So auch hier.

: Bearbeitet durch User
von Bernd K. (prof7bit)


Lesenswert?

Zum Glück gibts ja auf diesem Planeten nur eine einzige Sorte 
Microcontroller und der Code den Du bereits geschrieben hast kann sich 
auch jeder schon denken. Ansonsten wüsste nämlich jetzt niemand womit Du 
Dich da überhaupt beschäftigst und keiner könnte irgendwelche Antworten 
geben.

von Uwe (Gast)


Lesenswert?

@Karl-Heinz,

gut hab ich soweit verstanden. Aber wenn ich z.b. mehrere Write Aufrufe 
habe z.B.:

I2C_Write(Address_A, Value_1);
I2C_Write(Address_B, Value_2);
I2C_Write(Address_C, Value_3);

da ist mir noch nicht klar wie ich die Wartezeiten hinbekomme.

@Bernd
Es geht mir doch nur um das Prinzip.

von holger (Gast)


Lesenswert?

>Aber wenn ich z.b. mehrere Write Aufrufe
>habe z.B.:
>
>I2C_Write(Address_A, Value_1);
>I2C_Write(Address_B, Value_2);
>I2C_Write(Address_C, Value_3);
>
>da ist mir noch nicht klar wie ich die Wartezeiten hinbekomme.

Indem du jedem Write zwei States gibst. Einen wo der gestartet
wird und einen wo das Ende festgestellt wird.

von Bernd K. (prof7bit)


Lesenswert?

Uwe schrieb:
> Es geht mir doch nur um das Prinzip.

Wie auch immer die Hardware aussehen mag, ich bin sicher sie bietet die 
Möglichkeit einen Interrupt auszulösen wenn ein Byte samt ack/nack 
komplett geschrieben/gelesen worden ist. In dem Interrupt dann 
entscheidest Du welcher Zustand als nächstes folgen soll und was dafür 
nun zu tun ist.

von c-hater (Gast)


Lesenswert?

Uwe schrieb:

> Im Moment starte ich zum Beispiel ein Read und warte danach 20ms, damit
> die Statemachine abgearbeit werden kann. Das ist aber nicht Sinn der
> Sache, wie bekomme ich meine I2C mit der Callback Routine verheiratet?

Du lernst einfach als erstes, ereignisorientiert zu denken und zu 
programmieren. Das ist eine grundsätzliche Abkehr von einfachen 
Kontrollstrukturen über das Gesamtproblem hin zu den Strukturen von 
state machines. Und damit ein durchaus großes Problem für 
Programmieranfänger, denen das auch heute erstaunlicherweise nicht 
wirklich vermittelt wird.

Denn das ist eigentlich die unabdingbare Vorstufe zum Verständnis der 
Abläufe und damit zur Programmierung von Interrupts (oder auch 
multithreading).

Also: Am Anfang machst du es wie bei den C-lern üblich: Du setzt in ISRs 
nur irgendein Flag und pollst in main() dieses Flag. Später dann die 
Flags von mehreren ISRs und du verarbeitest vielleicht sogar die Daten, 
die diese ISRs liefern (oder brauchen).

Im letzten Schritt der Selbstaufschlauung erkennst du dann vielleicht 
irgendwann, daß dieses Konzept komplette Grütze ist, wenn's an's 
Eingemachte geht, also die ganze Sache an die Grenze des Machbaren 
gelangt. Genau nur deswegen gibt es Interrupts nämlich: Weil es komplett 
kontraproduktiver Schwachsinn ist, den Kram generell in main() zu 
serialisieren. Wäre das irgendwie sinnvoll oder nützlich, bräuchte es 
nämlich das ganze Konzept der Hardware-Interrupts einfach nicht zu 
geben. Es gibt sie aber und zwar aus sehr guten Gründen...

Laß dir also von keinem dieser unwissenden C-ler Regeln einreden wie 
etwa "Interrupts müssen immer kurz sein" oder "alles Wesentliche sollte 
in main() passieren". Das ist nur das, was die umsetzen können, ohne 
in's Schwitzen zu geraten, weil ihnen die verwendete Sprache die volle 
Kontrolle über das Timing verwehrt und/oder sie Interrupts nur sehr 
ineffizient nutzen kann.

Allerdings: Sobald du dich wirklich mit multithreading (oder der 
wesentlich härteren Form derselben Sache in Form von miteinander 
interagierenden nativen ISRs) beschaftigst, wirst du nicht darum herum 
kommen, dich mit den Problemen der Synchronisation nebenläufiger 
Software zu beschäftigen, sonst kommt ziemlich sicher nur Grütze raus. 
Natürlich vollkommen sprachunabhängig. Man kann das durchaus auch in C 
sauber lösen. Nur ist das Ergebnis halt in aller Regel wesentlich 
weniger effizient als in Assembler...

von Bernd K. (prof7bit)


Lesenswert?

c-hater schrieb:
> Nur ist das Ergebnis halt in aller Regel wesentlich
> weniger effizient als in Assembler...

Kein Mensch der noch ganz bei Sinnen ist schreibt eine komplette 
Anwendung in Assembler. Bestenfalls mal ne kurze Interrupt-Routine, aber 
auch dann nur wenns wirklich gar nicht anders geht, oder man macht 
vielleicht mal alle Jubeljahre mal eine winzige Änderungen am (bereits 
vorhandenen) Startup-code oder vielleicht mal nen kleinen 3-Zeiler um 
schmutzige Tricks mit dem Stackpointer zu veranstalten. Das wars dann 
aber schon.

von Karl H. (kbuchegg)


Lesenswert?

Uwe schrieb:
> @Karl-Heinz,
>
> gut hab ich soweit verstanden. Aber wenn ich z.b. mehrere Write Aufrufe
> habe z.B.:
>
> I2C_Write(Address_A, Value_1);
> I2C_Write(Address_B, Value_2);
> I2C_Write(Address_C, Value_3);
>
> da ist mir noch nicht klar wie ich die Wartezeiten hinbekomme.

Indem du die auszugebenden Werte in einer Warteschlange (Queue) parkst 
und die Statemaschine so umbaust, dass sie nicht nur einzelne Bytes 
abarbeiten kann sondern eine komplette Sequenz aus der Warteschlange.

Ja, in einem gewissen Sinne hat c-hater schon recht. Jetzt gehts ans 
eingemachte. Aber die Sache mit 'nur Assembler ist das wahre' ist 
kompletter Blödsinn. Gerade wenn es um Datenstrukturen geht, als um 
höhere Konzepte als einfach nur Bytes in Register schieben, wirst du mit 
Assembler alt.

: Bearbeitet durch User
von c-hater (Gast)


Lesenswert?

Karl H. schrieb:

> Ja, in einem gewissen Sinne hat c-hater schon recht. Jetzt gehts ans
> eingemachte. Aber die Sache mit 'nur Assembler ist das wahre' ist
> kompletter Blödsinn.

Ähem, wo habe ich das denn geschrieben?

Mich deucht, ich hätte vielmehr geschrieben:

> Natürlich vollkommen sprachunabhängig. Man kann das durchaus auch in C
> sauber lösen. Nur ist das Ergebnis halt in aller Regel wesentlich
> weniger effizient als in Assembler...

Und dazu stehe ich.

Wir können das gerne bei jedem beliebigen realen Problem vergleichen. Im 
allerbesten Fall (sehr primitive Sachen) wirst du mit C gerade so 
Gleichstand erreichen können. Im Normalfall wirst du aber (je nach 
Compilerqualität) schlechter bis ziemlich lausig schlechter abschneiden. 
Besonders schlimm finde ich: Ich bin mir absolut sicher, daß du nur zu 
gut selber weißt, daß es so ist!

Warum also immer wieder die ewig gleichen erbärmlichen Lügen? Was zum 
Teufel bringt dir das?

von Karl H. (kbuchegg)


Lesenswert?

c-hater schrieb:

> Wir können das gerne bei jedem beliebigen realen Problem vergleichen. Im
> allerbesten Fall (sehr primitive Sachen) wirst du mit C gerade so
> Gleichstand erreichen können. Im Normalfall wirst du aber (je nach
> Compilerqualität) schlechter bis ziemlich lausig schlechter abschneiden.
> Besonders schlimm finde ich: Ich bin mir absolut sicher, daß du nur zu
> gut selber weißt, daß es so ist!

Wir können das gerne an einem ansprechend komplexen Problem klären, wer 
schneller ein entsprechendes Programm lauffähig hat.
Aber bitte kein Wischi-Waschi Blink LED Beispiel. Moby hat ja Zeitnot 
vorgeschoben, was ich (augenzwinker) überhaupt nicht verstehen kann. 
Wenn ich Sonntag nachmittags 3 Stunden investieren kann, dann wird er es 
ja wohl auch können. Wie, der kann in 3 Stunden in Assembler nichts 
vernünftiges auf die Beinde stellen? Komisch, ich kann das in C 
durchaus.

> Warum also immer wieder die ewig gleichen erbärmlichen Lügen? Was zum
> Teufel bringt dir das?

Weil dieser Kram keinen mehr wirklich interessiert.
Ein Programm muss nur schnell genug sein. Nicht mehr. In der realen 
Praxis ist es in 95% aller Fälle völlig unerheblich, ob der Compiler 
beim Aufruf einer ISR noch das Sichern und Wiederherstellen von 5 
Registern einbaut oder nicht. Die paar Takte mehr stören, bis auf ein 
paar Ausnahmefälle, nicht weiter.
Und das weisst du auch zur Genüge.
Du versuchst hier ständig deine mangelnden C-Fähigkeiten als Ursache für 
Probleme vorzuschieben, die nichts mit der Sprachwahl zu tun haben. Ich 
stimme dir zu, dass das Problem des TO in der Strukturierung der 
Software zu lösen ist. Die Frage ob Assembler oder C ist dafür 
allerdings überhaupt nicht relevant. Du hast dieses Thema hier 
eingebracht, nicht ich. Ich will das nur nicht so im Raum stehen lassen. 
Auf der anderen Seite würde ich gerne mal ein hocheffizentes Programm 
für meine Problemstellungen laufen lassen, um zu sehen wie viel mir 
das tatsächlich bringt. Schliesslich will ich auch etwas dafür haben, 
wenn schon die Entwicklungszeit um einen Faktor von mindestens 10 
ansteigt. Ich seh zwar noch nicht, was mir das bringen könnte, denn die 
C Versionen sind allesamt schnell genug und lassen sich zu einem 
Bruchteil der Kosten einer Assemblerversion produzieren, aber ich lass 
mich auch überzeugen.

: Bearbeitet durch User
von Uwe (Gast)


Lesenswert?

Hallo nochmal,

bitte keinen C/Assembler Krieg anfangen. Ich will keine Rakete zum Mond 
schicken sondern nur einen Helligkeitssensor auslesen und werde in C 
programmieren.


Also ich habe immer noch folgenden Aufruf z.B.
1
void Sensor_Init(void)
2
{
3
I2C_Write(Address_A, Value_1);
4
I2C_Write(Address_B, Value_2);
5
I2C_Write(Address_C, Value_3);
6
}
7
8
//wird immer nach einem fertigen Write automatisch aufgerufen
9
void Write_Complete_Callback(void)
10
{
11
//???
12
}
13
14
void main(void)
15
{
16
Sensor_Init();
17
while(1)
18
{}
19
}

Die Statemachine muss also in die  Write_Complete_Callback funktion, 
oder?

von Rainer B. (katastrophenheinz)


Lesenswert?

Grob skizziert könnte das so ausehen,
das ganze Drumherum fehlt natürlich.
1
unsigned short statenum;
2
unsigned char goto_next_state=0;
3
4
void SensorInit(void) 
5
{
6
   statenum = 0;
7
   goto_next_state = 1;
8
}
9
10
void SensorWrite(void)
11
{
12
switch ( statenum ) {
13
   case 0:
14
  I2C_Write(Address_A, Value_1);
15
  break;
16
   case 1:
17
  I2C_Write(Address_B, Value_2);
18
  break;
19
   case 2:
20
  I2C_Write(Address_C, Value_3);
21
  break;  
22
   case 3:
23
  PUTS("Done\r\n");
24
  break;
25
   default:
26
  // Illegal State
27
}
28
29
//wird immer nach einem fertigen Write automatisch aufgerufen
30
void Write_Complete_Callback(void)
31
{
32
  goto_next_state = 1;
33
  statenum++;
34
}
35
36
void main(void)
37
{
38
  Sensor_Init();
39
  while(1) 
40
  {
41
    if ( goto_next_state ) {
42
  goto_next_state = 0;
43
  SensorWrite();
44
    }
45
  }
46
}

von Uwe (Gast)


Lesenswert?

Hallo Rainer,

super jetzt hab ich es verstanden.

von W.S. (Gast)


Lesenswert?

Uwe schrieb:
> Im Moment starte ich zum Beispiel ein Read und warte danach 20ms, damit
> die Statemachine abgearbeit werden kann. Das ist aber nicht Sinn der
> Sache, wie bekomme ich meine I2C mit der Callback Routine verheiratet?

Du hast damit ein prinzipielles Problem, ich kenne das bis zum Abwinken: 
Bei eigentlich allen I2C-Anwendungen ist es so, daß man einen Transfer 
anstoßen und dann warten muß bis er fertig ist, weil man für den 
Fortgang des momentanen Prozesses die Ergebnisse einfach braucht.

Man kommt da ganz einfach nicht darum herum, auf das I2C-Busgeschäft und 
dessen Ende zu warten.

Die logische Antwort wäre, eben ein RTOS zu verwenden, damit andere 
Prozesse die Rechenzeit zwischendurch haben können. Aber in sehr vielen 
Fällen ist ein RTOS einfach daneben weil zu überdimensioniert, zu viel 
Overhead.

Also bleibt dann doch nur übrig, irgendwie ereignisorientiert zu 
programmieren. Also Transfer interruptgesteuert, bei Ende einen 
"Erfolgs-Event" generieren, parallel einen Timeout-Counter (per 
Systemtick) laufen lassen, der bei Zeitüberschreitung dann einen 
"Mißerfolgs-Event" generiert.

Das ist alles recht verwinkelt und ein bissel kompliziert, also frag 
dich lieber, ob das alles nötig ist oder ob dein bisheriges Polling für 
die Anwendung völlig ausreichend ist.

W.S.

von Ali K. (teddy50)


Lesenswert?

Karl H. schrieb:
> Jetzt magst du dich fragen, wozu dann überhaupt Interrupt, wenn ich
> sowieso pollen muss?
> Du hast eine bessere Organisationsform, bei der du einfacher andere
> Dinge erledigen kannst, während die I2C Übertragung im Hintergrund
> abgewickelt wird. Der Trick bei der Programmierung, bei der ein µC
> scheinbar viele Dinge mehr oder weniger gleichzeitig erledigen kann,
> besteht darin, dass die einzelnen Tätigkeiten auf kleine Zeithappen
> heruntergebrochen werden und nirgends viel Zeit in einem Rutsch drauf
> geht. So auch hier.

Die Frage ist warum?
Jeder Thread/Task bekommt einen Zeitslot und in dieser Zeit muss du 
dafür sorgen, dass eine I2C-Kommunikation, ob Read oder Write 
abgeschlossen ist.
I2C im Interrupt-Mode macht nur als I2C-Slave sinn, als Master aus 
meiner Sicht weniger.
Alles in kleine Häppchen herunterzubrechen bringt nur Spaghetti-Code und 
Unvorhersagbarkeit.

: Bearbeitet durch User
von Monk (roehrmond)


Lesenswert?

Uwe schrieb:
> da ist mir noch nicht klar wie ich die Wartezeiten hinbekomme.

Ali K. schrieb:
> Die Frage ist warum?

Nee, 8 Jahre später fragt sich das niemand mehr. Ich frage mich, warum 
du den knallroten Hinweis über dem Eingabefeld missachtet hast?:

"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."

Dazu kommt, dass ich deine Antwort für falsch halte. Es macht auch für 
den Master durchaus Sinn. I²C Transaktionen können komplett in ISR 
abgewickelt werden, wenn man dazu Puffer verwendet. Das Hauptprogramm 
müsste nur die "Message" in den Puffer (oder eine Warteschlange) legen 
und später die "Response" auswerten, nachdem sie komplett empfangen 
wurde.

: Bearbeitet durch User
von Ali K. (teddy50)


Lesenswert?

Steve van de Grens schrieb:
> Uwe schrieb:
>> da ist mir noch nicht klar wie ich die Wartezeiten hinbekomme.
>
> Ali K. schrieb:
>> Die Frage ist warum?
>
> Nee, 8 Jahre später fragt sich das niemand mehr. Ich frage mich, warum
> du den knallroten Hinweis über dem Eingabefeld missachtet hast?:
>
> "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."
>
> Dazu kommt, dass ich deine Antwort für falsch halte. Es macht auch für
> den Master durchaus Sinn. I²C Transaktionen können komplett in ISR
> abgewickelt werden, wenn man dazu Puffer verwendet. Das Hauptprogramm
> müsste nur die "Message" in den Puffer (oder eine Warteschlange) legen
> und später die "Response" auswerten, nachdem sie komplett empfangen
> wurde.

Dh, der Master muss sowieso warten , bis irgendwann alles gesendet und 
empfangen wurde.
Und dieses irgendwann ist nicht erlaubt.

von Monk (roehrmond)


Lesenswert?

Ali K. schrieb:
> Dh, der Master muss sowieso warten , bis irgendwann alles gesendet und
> empfangen wurde.

Stimmt. Es macht aber schon für die Struktur des Programmes einen 
erheblichen Unterschied, ob er auf eine ganze Antwort wartet, oder ob er 
(z.B.) 4 mal auf das Senden einzelner Bytes und danach 12 mal den 
Empfang einzelner Bytes wartet.

Die Abhandlung von Timeouts kann man unabhängig davon im 
Zustandsautomaten implementieren. Egal ob man nun auf ein einzelnes Byte 
oder eine komplette Response wartet.

Das raus und rein schieben in Puffer macht man üblicherweise in einer 
ISR, wenn das nicht bereits die Hardware ganz alleine kann (STM32 können 
das).

: Bearbeitet durch User
von C-hater (c-hater)


Lesenswert?

Ali K. schrieb:

> Dh, der Master muss sowieso warten , bis irgendwann alles gesendet und
> empfangen wurde.

Nein, er muss eben nicht warten. Er delegiert einfach die Arbeit an 
eine interruptgetriebene Statemachine, die die Arbeit nebenbei erledigt. 
Was er dann nur noch machen muss (zumindest bei Reads zwingend): immer 
mal wieder checken, ob und mit welchem Ergebnis das I2C-Subsystem die 
Aufgabe abgeschlossen hat.

Und selbst diese Poll-Aufgabe kann man noch durch einen Callback 
ersetzen. Der dann aber natürlich im Kontext eines Interrupts ausgeführt 
wird und dementsprechend darin limitiert ist, was darin gefahrlos getan 
werden kann.

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.