Die meisten Artikel, die ich finden kann und die im Wesentlichen "I2C für Dummies auf XXX-Mikrocontrollern" sind, schlagen vor, die CPU zu blockieren, während auf Bestätigungsereignisse gewartet wird. Zum Beispiel , (Kommentare und Beschreibung sind auf Russisch, aber das spielt eigentlich keine Rolle). Hier ist das Beispiel für "Blockieren", von dem ich spreche:
I2C_GenerateSTART(HMC5883L_I2C, ENABLE);
/* wait for confirmation */
while (!I2C_CheckEvent(HMC5883L_I2C, I2C_EVENT_MASTER_MODE_SELECT));
Diese while
Schleifen sind im Wesentlichen nach jeder Operation vorhanden. Abschließend meine Frage: Wie kann ich I2C implementieren, ohne die MCU mit diesen While-Schleifen zu "hängen"? Auf einer sehr hohen Ebene verstehe ich, dass ich Interrupts anstelle von Whiles verwenden muss, aber ich kann mir kein Codebeispiel einfallen lassen. Kannst du mir bitte dabei helfen?
Sicher.
Ich denke, die ersten beiden Optionen sind wahrscheinlich Ihre besseren Wetten. Vieles hängt jedoch davon ab, wie sehr Sie von Bibliothekscode abhängig sind, der von anderen geschrieben wurde. Es ist durchaus möglich, dass ihr Bibliothekscode nicht dafür ausgelegt ist, in Komponenten der oberen und unteren Ebene aufgeteilt zu werden. Und es ist auch gut möglich, dass Sie sie nicht basierend auf Timer-Ereignissen oder anderen auf Zustandsmaschinen basierenden Ereignissen aufrufen können.
Ich neige dazu anzunehmen, dass ich meinen eigenen Code schreiben muss, um die Arbeit zu erledigen, wenn mir die Art und Weise, wie die Bibliothek die Arbeit erledigt, nicht gefällt (nur Busy-Wait-Schleifen). Das bedeutet, dass ich frei bin, jede der oben genannten Methoden zu verwenden.
Aber Sie müssen sich den Bibliothekscode genau ansehen und sehen, ob er bereits über Funktionen verfügt, mit denen Sie das geschäftige Warteverhalten vermeiden können. Es ist möglich, dass die Bibliothek etwas anderes unterstützt. Das ist also der erste Ort, an dem Sie nachsehen können, nur für den Fall. Wenn nicht, spielen Sie mit der Idee, die vorhandenen Funktionen entweder als Teil einer Aufteilung des oberen / unteren Treibers oder als einen von einer Zustandsmaschine gesteuerten Prozess zu verwenden. Es ist möglich, dass Sie das auch herausfinden können.
Mir fallen jetzt keine anderen Vorschläge auf die Schnelle ein.
Beispiele gibt es in den STM32Cube
Bibliotheken. Holen Sie sich das für Ihre Controller-Familie geeignete (z. B. STM32CubeF4
oder STM32CubeL1
) und suchen Sie Examples/I2C/I2C_TwoBoards_ComDMA
im Projects
Unterverzeichnis.
Nun, der Grund ist einfach: Das Blockieren ist einfach und auf den ersten Blick scheint es zu funktionieren. Wehe dir, wenn du in der Zwischenzeit etwas anderes machen willst.
Ohne auf viele Einzelheiten einzugehen, da ich den STM32 nicht kenne, können Sie dieses Problem im Allgemeinen auf zwei Arten lösen, je nach Ihren Anforderungen.
I2C_GenerateSTART(HMC5883L_I2C, ENABLE);
/* wait for confirmation */
while (!I2C_CheckEvent(HMC5883L_I2C, I2C_EVENT_MASTER_MODE_SELECT));
Entweder Sie implementieren ein Timeout für alle Ihre while
Schleifen. Das heisst:
I2C_GenerateSTART(HMC5883L_I2C, ENABLE);
/* wait for confirmation */
static unsigned long start = now();
while (!I2C_CheckEvent(HMC5883L_I2C, I2C_EVENT_MASTER_MODE_SELECT) && now()-start < TIMEOUT);
if (now()-start >= TIMEOUT) { return ERROR_TIMEOUT; }
(Das ist natürlich Pseudocode, Sie verstehen schon. Fühlen Sie sich frei, ihn nach Bedarf zu optimieren oder an Ihre Codierungspräferenzen anzupassen.)
Sie müssen die Rückgabecodes überprüfen, wenn Sie den Stapel nach oben bewegen, und die richtige Stelle auswählen, an der Sie Ihre Timeout-Behandlung durchführen. Beachten Sie, dass es hilfreich ist, auch eine globale Variable i2c_timeout_occured=1
oder was auch immer zu setzen, damit Sie weitere I2C-Aufrufe schnell abbrechen können, ohne zu viele Argumente herumreichen zu müssen.
Diese Änderung ist hoffentlich ziemlich schmerzlos.
Wenn Sie stattdessen wirklich andere Verarbeitungen durchführen müssen, während Sie auf dieses Ereignis warten, müssen Sie die innere While-Schleife vollständig entfernen. Das machst du so:
void main_loop() {
do_i2c_stuff(); // must never block
do_other_stuff();
...
}
// Must never block. Assuming all I2C_... functions do not block either.
void do_i2c_stuff() {
static int state=...;
if (state==0) {
I2C_GenerateSTART(HMC5883L_I2C, ENABLE);
state=1;
} else if (state==1) {
if (I2C_CheckEvent(HMC5883L_I2C, I2C_EVENT_MASTER_MODE_SELECT))
state=2;
} else ...
}
Es ist nicht unbedingt sehr kompliziert, abhängig von Ihrer anderen Logik. Sie können viel mit dem richtigen Einrücken/Kommentieren/Formatieren tun, damit Sie nicht den Überblick darüber verlieren, was Sie programmieren.
Die Art und Weise, wie dies funktioniert, besteht darin, eine Zustandsmaschine zu erstellen . Wenn Sie sich Ihren ursprünglichen Code ansehen, sieht er so aus:
non-blocking code
while (!nonblocking_function_call1());
non-blocking code
while (!nonblocking_function_call2());
Um das in eine Zustandsmaschine umzuwandeln, haben Sie jeweils einen Zustand:
state 0: non-blocking code
state 1: nonblocking_function_call1()
state 2: non-blocking code
state 3: nonblocking_function_call2()
Dann rufen Sie, wie im obigen Beispiel gezeigt, diesen Code in einer Endlosschleife (Ihrer Hauptschleife) auf und führen nur den Code aus, der Ihrem aktuellen Zustand entspricht (getrackt in einer statischen Variablen state
). Der nicht blockierende Code ist trivial, er ist unverändert. Der Blockierungscode wird durch eine Variante ersetzt, die nicht blockiert, sondern erst aktualisiert, state
wenn sie fertig ist.
Beachten Sie, dass die einzelnen while
Schleifen verschwunden sind; Sie haben sie durch die Tatsache ersetzt, dass Sie sowieso Ihre Hauptschleife auf oberster Ebene haben, die Ihre Zustandsmaschine wiederholt aufruft.
Diese Lösung kann schmerzhaft sein, wenn Sie viel Legacy-Code haben, da Sie nicht einfach die innerste Blockierungsfunktion anpassen können, wie in der ersten Lösung. Es glänzt, wenn Sie anfangen, frischen Code zu schreiben und diesen Weg von Anfang an gehen. Kombinieren Sie es mit vielen anderen Dingen, die ein µC tun könnte (z. B. auf Tastendrücke warten usw.); Wenn Sie sich daran gewöhnen, es die ganze Zeit so zu machen, erhalten Sie beliebige Multitasking-Fähigkeiten kostenlos.
Ehrlich gesagt würde ich für so etwas (dh unendliches Blockieren einfach loswerden) versuchen, mich von Interrupts fernzuhalten, es sei denn, Sie haben extreme Timing-Anforderungen. Interrupts machen es kompliziert, schnell, Sie haben vielleicht sowieso nicht genug davon, und es läuft sowieso auf einen ziemlich ähnlichen Code hinaus, da Sie innerhalb des Interrupts nicht viel mehr tun wollen, als einige Flags zu setzen.
while
. Ich habe eine Frage zu Ihrem letzten Codebeispiel - ich bin mir nicht sicher, was Sie genau vorschlagen. Das Problem ist - kehrt nach einiger Zeit I2C_CheckEvent
zurück true
und blockiert nicht, was bedeutet, dass es im Allgemeinen nicht ausreicht, es einmal aufzurufen, was Sie (so wie ich es verstanden habe) vorschlagen. Können Sie das bitte ein wenig erläutern?while
. Der erste hat sie noch, fügt aber ein Timeout hinzu. Der zweite entfernt sie vollständig (vorausgesetzt, Sie haben eine Schleife der obersten Ebene, die sowieso in einer Dauerschleife ausgeführt wird). Ich habe, wie gewünscht, eine Erklärung hinzugefügt. i2C_CheckEvent wird oft aufgerufen, aber Ihre Methode kehrt unabhängig vom Ergebnis sofort zurück, sodass auch die anderen Teile Ihres Programms Zeit zum Ausführen haben.Hier ist eine Liste von Interrupt-Anforderung auf einem STM32 verfügbar. Da ich den genauen Chip, den Sie verwenden, nicht kenne, wird empfohlen, in Ihrem Referenzhandbuch nachzusehen, ob es einen Unterschied gibt, aber ich bezweifle, dass es einen geben wird.
Um bei Ihrem Beispielcode zu bleiben, müssen Sie, wenn eine nicht blockierende Version benötigt wird, das Ereignis Startbit gesendet (Master)ITEVFEN
mit dem Steuerbit aktivieren und das SB
Ereignisflag in der ISR überprüfen.
Betrachtet man den Auszug von @BenceKaulics aus einem Datenblatt, könnte der Pseudo-Code für eine Interrupt-Service-Routine (ISR) so aussehen:
i2c_event_isr() {
switch( i2c_event ) {
case master_start_bit_sent:
send_address(...);
break;
case master_address_sent:
case data_byte_finished:
if ( has_more_data() ) {
send_next_data_byte();
} else {
send_stop_condition();
}
break;
...
}
}
JimmyB
scttnlsn