Kleine Multitasking-Experimente in einem Mikrocontroller

In einer der vorherigen Anmerkungen hat der Autor versucht zu argumentieren, dass bei der Programmierung des Mikrocontrollers ein einfacher Taskwechsel in Situationen nützlich ist, in denen die Verwendung des Echtzeitbetriebssystems zu viel ist und die umfassende Schleife für alle erforderlichen Aktionen zu gering ist ( Er sagte, genau wie der Graf von La Fer. Genauer gesagt, nicht zu wenig, aber zu verwirrt.


In einem nachfolgenden Hinweis war geplant, den Zugriff auf Ressourcen, die von mehreren Aufgaben gemeinsam genutzt werden, mithilfe von Warteschlangen zu optimieren, die auf Ringpuffern (FIFOs) und einer dafür speziell zugewiesenen separaten Aufgabe basieren. Nachdem wir für verschiedene Aufgaben die Aktionen verteilt haben, die nicht miteinander zusammenhängen, können wir einen sichtbareren Code erwarten. Und wenn wir gleichzeitig etwas Bequemlichkeit und Einfachheit bekommen, warum nicht versuchen?


Offensichtlich ist der Mikrocontroller nicht dafür ausgelegt, irgendeine denkbare Aufgabe des Benutzers zu lösen. Dann wird sich ein solcher Task-Switcher in vielen Situationen möglicherweise als ausreichend erweisen. Kurz gesagt, ein kleines Experiment wird wahrscheinlich nicht schaden. Um nicht unbegründet zu sein, beschloss Ihr bescheidener Diener, etwas zu schreiben und seine Kritzeleien zu testen.


Ich muss sagen, dass bei Mikrocontrollern die Anforderung, mit der Zeit als etwas Wichtiges und Festes zu rechnen, häufiger ist als bei Allzweckcomputern. Das Überschreiten des Rahmens im ersten Fall entspricht einer Inoperabilität, und im zweiten Fall führt dies nur zu einer Verlängerung der Wartezeit, was durchaus akzeptabel ist, wenn die Nerven in Ordnung sind. Es gibt sogar zwei Begriffe: "weiche Echtzeit" und "harte Echtzeit".


Ich möchte Sie daran erinnern, dass wir über Controller mit dem Cortex-M3,4,7-Kern gesprochen haben. Heute ist eine sehr häufige Familie. In den folgenden Beispielen haben wir den Mikrocontroller STM32F303 verwendet, der Teil der Karte STM32F3DISCOVERY ist.


Der Switch ist eine einzelne Assembler-Datei.
Der Assembler hat keine Angst vor dem Autor, weckt aber im Gegenteil die Hoffnung, dass die maximale Geschwindigkeit erreicht wird.


Zunächst war die einfachste Logik der Schaltoperation geplant, die in Abbildung 1 für acht Aufgaben dargestellt ist.



In diesem Schema nehmen Aufgaben ihren Teil der Zeit nacheinander in Anspruch und können nur den Rest ihres Ticks angeben und gegebenenfalls einige ihrer Ticks überspringen. Diese Logik hat sich als gut erwiesen, da die Quantengröße klein gemacht werden kann. Und genau dies ist erforderlich, um eine Aufgabe, für die gerade eine Unterbrechung stattgefunden hat, nicht dringend zu erheben und ihre Priorität zu erhöhen und dann zu senken. Das soeben empfangene Paket wartet leise 200 bis 300 Mikrosekunden, bis seine Aufgabe ihren Tick erhält. Und wenn wir einen Cortex-M7 haben, der mit einer Frequenz von 216 MHz arbeitet, sind 20 Mikrosekunden für einen Tick durchaus sinnvoll, da das Umschalten weniger als eine halbe Mikrosekunde dauert. Und jede Aufgabe aus dem obigen Beispiel wird niemals mehr als 140 Mikrosekunden zu spät sein.


Mit zunehmender Anzahl von Aufgaben kann die Verzögerung des Beginns der Aktivität der erforderlichen Aufgabe jedoch auch bei einer extrem geringen Größe des Zeitquantums nicht mehr angenehm sein. Auf dieser Grundlage und unter Berücksichtigung der Tatsache, dass nur ein kleiner Teil der Aufgaben wirklich harte Echtzeit erfordert, wurde beschlossen, die Logik des Schalters geringfügig zu ändern. Es ist in Abbildung 2 dargestellt.



Jetzt wählen wir nur einen Teil der Aufgaben aus, die ein ganzes Quantum erhalten, und wählen für den Rest nur einen Tick aus, in dem sie sich im Spiel abwechseln. In diesem Fall empfängt die Initialisierungsunterroutine einen Eingabeparameter, nämlich die Positionsnummer, ab der alle Aufgaben in ihren Rechten betroffen sind und einen Tick gemeinsam nutzen. Gleichzeitig blieb das alte Schema verfügbar, dafür reicht es aus, den Parameterwert auf Null oder die Gesamtzahl der Aufgaben zu setzen. Die Umstellungskosten stiegen um nur wenige Assembler-Anweisungen.


Zwei ähnliche Schemata werden verwendet, um den Zugriff auf gemeinsam genutzte Ressourcen zu ermöglichen. Die erste, die in einer früheren Anmerkung erwähnt wurde, verwendet mehrere FIFOs (oder zirkuläre Puffer nach Anzahl der Nachrichtenproduzenten) und eine separate Übereinstimmungsaufgabe. Es wurde für die Kommunikation mit der Außenwelt entwickelt und erfordert keine Erwartungen an Aufgaben, die Nachrichten generieren. Es muss nur sichergestellt werden, dass die Warteschlangen nicht überfüllt sind.


Das zweite Schema verwendet ebenfalls eine separate Aufgabe, um den Zugriff zu ermöglichen, führt jedoch Erwartungen ein, da es die interne Ressource in beide Richtungen verwaltet. Diese Aktionen können nicht an die Zeit gebunden werden. Abbildung 3 zeigt die Komponenten der zweiten Schaltung.



Die Hauptelemente darin sind ein Puffer von Anforderungen entsprechend der Anzahl der gewünschten Aufgaben und eine Zugriffsanzeige. Die Bedienung dieses Entwurfs ist recht einfach. Die Aufgabe auf der linken Seite sendet eine Zugriffsanforderung an einen speziell dafür zugewiesenen Ort (z. B. schreibt Aufgabe 2 1 in Anforderung 2). Aufgabe - Der Dispatcher wählt aus, wen er zulassen möchte, und schreibt die Nummer der ausgewählten Aufgabe in das Auflösungsflag. Die Task, die die Berechtigung erhalten hat, führt ihre Aktionen aus und schreibt das Zeichen für das Ende des Zugriffs auf die Anforderung, den Wert 0xFF. Der Scheduler, der sieht, dass die Anforderung gelöscht wird, setzt das Berechtigungsflag zurück, setzt die vorherige Anforderung zurück und setzt die Anforderung von einer anderen Aufgabe zurück.


Zwei Testprojekte unter IAR und eine Beschreibung der verwendeten STM32F3DISCOVERY-Karte können hier eingesehen werden . Im ersten Projekt hat der ATS303 einfach seine Leistung überprüft und debuggt. Alle auf dieser Platine installierten LEDs waren praktisch. Niemand wurde verletzt.


Im zweiten Entwurf von BTS303 wurden die beiden genannten Optionen für die Ressourcenzuweisung getestet. Darin generieren die Aufgaben 1 und 2 Testnachrichten, die vom Bediener empfangen werden. Um mit dem Bediener zu kommunizieren, musste ich einen Schal mit einem TTL-COM-Anschluss hinzufügen, wie auf dem Foto unten gezeigt.



Der Bediener verwendet einen Terminalemulator. Ich denke, der Leser wird den Autor für die weiche Röhrenfarbe entschuldigen. Es sieht so aus.



Um das gesamte System zu starten, sind vor dem Beheben von Unterbrechungen vorbereitende Schritte im Hauptteil der Null-Task main () erforderlich, die unten dargestellt werden.


void main_start_task_switcher(U8 border); U8 task_run_and_return_task_number((U32)t1_task); U8 task_run_and_return_task_number((U32)t2_task); U8 task_run_and_return_task_number((U32)t3_human_link); U8 task_run_and_return_task_number((U32)t4_human_answer); U8 task_run_and_return_task_number((U32)task_5); U8 task_run_and_return_task_number((U32)task_6); U8 task_run_and_return_task_number((U32)task_7); 

In diesen Zeilen startet der Switch zuerst und dann wiederum die verbleibenden sieben Aufgaben.


Hier ist die Mindestanzahl von Anrufen, die für den Job erforderlich sind.


  void task_wake_up_action(U8 taskNumber); 

Dieser Aufruf wird in einem Interrupt von einem Benutzer-Hardware-Timer verwendet. Herausforderungen aus den Aufgaben selbst sprechen für sich.


  void release_me_and_set_sleep_steps(U32 ticks); U8 get_my_number(void); 

Alle diese Funktionen befinden sich in der Assembler-Switch-Datei. Es gibt mehrere weitere Funktionen, die zum Testen nützlich, aber nicht erforderlich sind.


Im BTS303-Projekt empfängt Aufgabe 3 Bedienerbefehle von außen und sendet ihm die Antworten von Aufgabe 4. Aufgabe 4 empfängt Befehle vom Bediener von Aufgabe 3 und führt sie mit möglichen Antworten aus. Task 3 empfängt auch Nachrichten von Task 1 und 2 und sendet sie über UART an den Terminalemulator (z. B. Putty).


Aufgabe 0 (Haupt) erledigt einige Hilfsarbeiten, überprüft beispielsweise die Anzahl der Wörter, die im gestapelten Bereich jeder Aufgabe nicht betroffen sind. Diese Informationen können vom Bediener angefordert werden, um sich ein Bild von der Verwendung des Stapels zu machen. Zunächst wird für jede Aufgabe ein Stapelbereich von 512 Bytes (128 Wörter) zugewiesen, und es muss (zumindest in der Debugging-Phase) überwacht werden, dass diese Bereiche nicht annähernd überlaufen.


Die Aufgaben 5 und 6 führen Berechnungen für eine gemeinsame Gleitkommavariable durch. Dazu fordern sie ab Aufgabe 7 den Zugriff darauf an.


Es gibt eine weitere zusätzliche Funktion, die in Testprojekten angezeigt wird. Es ist so konzipiert, dass Sie die Aufgabe nicht nach Ablauf der Anzahl der Ticks, sondern nach einer bestimmten Zeit aktivieren können, und es sieht so aus.


  void wake_me_up_after_milliSeconds(U32 timeMS); 

Für die Implementierung ist außerdem ein zusätzlicher Hardware-Timer erforderlich, der auch in Testfällen implementiert wird.


Wie Sie sehen, passt die Liste aller notwendigen Anrufe auf eine Seite.

Source: https://habr.com/ru/post/de461121/


All Articles