Moderne Mikrocontroller haben eine ziemlich hohe Leistung, und dies gibt vielen Programmierern die Möglichkeit, ungefähr so zu denken: - „Es ist in Ordnung, wenn 1-5% der Leistung für die Wartung des Betriebssystems verwendet werden. Aber mein Code wird leicht zu debuggen und explizit sein! “ Diese Überlegungen werden durch eine große Menge nichtflüchtigen Speichers (Flash) zum Speichern des Betriebssystemcodes und eines Arbeitsspeichers (RAM / SRAM) zum Zuweisen eines eigenen Stapels für jede Aufgabe unterstützt. In den meisten Fällen ist diese Idee jedoch falsch. Und in diesem Artikel werde ich Ihnen sagen, warum.
Über die Projekte, mit denen ich arbeite
In meiner Praxis muss ich oft mit einem „Designer“ arbeiten. Ich habe diesen Ansatz in meinem vorherigen Artikel über die
Verwendung von C ++ in Mikrocontrollern ausführlich beschrieben. Dann habe ich nicht das Wichtigste gesagt. Die meisten "Blöcke" dieses "Konstruktors" sind irgendwie an ein Echtzeitbetriebssystem gebunden. Die meisten „Blöcke“ haben ihren eigenen Ablauf (Aufgabe in Bezug auf das verwendete FreeRTOS-Echtzeitbetriebssystem). Und dass das Projekt im Durchschnitt etwa 10-15 Aufgaben hat. Manchmal erreicht dieser Wert 35-40.
Wo so viel?
Hier ist eine kurze Liste der Aufgaben, die
in jedem Projekt auftreten:
- ADC-Wartung (jedes Modul wird von seinem eigenen Ablauf gewartet);
- wdt-Wartung (wenn das Betriebssystem abstürzt, wird es von der Task nicht zurückgesetzt und das Gerät wird neu gestartet);
- Arbeiten mit Einstellungsseiten (ein separater Stream steuert die Arbeit mit dem Flash-Speicher);
- Aufrechterhaltung des Interaktionsprotokolls mit der Außenwelt (stromabwärts der Schnittstelle. Zum Beispiel uart);
Dann gibt es bereits spezifische Dinge für jedes Gerät, wie zum Beispiel einen Strom für die Wartung von Thermistoren (Empfangen von Daten aus dem ADC-Messstrom und Konvertieren dieser Daten in Temperatur), Abfragen der externen Peripheriegeräte und so weiter.
Scheinbare Einfachheit
Trotz der Tatsache, dass das Projekt viele Aufgaben enthält, ist jede davon in einem Objekt der entsprechenden Klasse "versteckt" (denken Sie daran, dass sich der Konstruktor in C ++ befindet, dies kann jedoch auch in C nachgeahmt werden, indem "Programmieren in C in einem objektorientierten Stil" verwendet wird. Es ist
jedoch besser, dies nicht zu tun notwendig ). Da die Objekte dieses „Konstruktors“ global sind und FreeRTOS 9 in Projekten verwendet wird, wodurch die Erstellung eigener Entitäten in vom Benutzer zugewiesenen Puffern unterstützt wird, kann die Speichernutzung zum Zeitpunkt der Verknüpfung gesteuert werden. Unter dem Gesichtspunkt der Überwachung von Speicherlecks ist also alles mehr oder weniger normal. Es gibt jedoch folgende Nuancen:
- Es muss klar sein, wie viel ein Stapel für jeden Thread benötigt wird. Dabei:
- kritische Fälle müssen berücksichtigt werden (z. B. Verschachtelung mit einem bestimmten Verhalten);
- Wenn Funktionen aus Standardbibliotheken verwendet werden, wissen Sie auch, wie sie angeordnet sind, oder haben zumindest eine Vorstellung davon, wie viel sie den Stapel verbrauchen werden.
Abgesehen von dieser Tatsache scheint die Verwendung des Betriebssystems nur die Logik des Codes zu verbessern und ihn klarer zu machen.
Missbrauch der Betriebssystemfunktionalität
Die Hauptprobleme beginnen in dem Moment, in dem Sie vergessen, was Sie speziell für den Mikrocontroller schreiben. Das Betriebssystem erhebt seine Kosten für die Arbeit mit eigenen Entitäten (wie Semaphoren, Mutexen, Warteschlangen). Hier ist ein Beispiel einer
UART-Klasse zum Implementieren einer Terminalfunktion . In dem Interrupt wird ein Byte empfangen. Wenn es den Bereich durch gültige Eingabezeichen überschreitet, wird es mit den entsprechenden Ersetzungen zur Warteschlange hinzugefügt (z. B. '\ n' ändert sich in die Sequenz "\ n \ r"). Dies wurde durchgeführt, um den Port für das Senden zu sichern (da der Port nicht nur als Terminal fungieren kann. Protokolldaten können auch über ihn gesendet werden). Dies stellt einerseits sicher, dass die Antwort so schnell wie möglich gesendet wird und das Senden von Daten mit höherer Priorität nicht beeinträchtigt (während Daten mit höherer Priorität gesendet werden, sammeln sie sich im Puffer an, wodurch DMA zum Senden der Antwort verwendet werden kann). Ab diesem Moment geraten Sie jedoch auf eine rutschige Strecke. Anstatt einen Haufen durch die Warteschlange zu schreiben, könnte man die Unterbrechung einfach korrekt in einem nicht leeren Puffer konfigurieren, der derzeit nicht auf dem UART arbeitet und wenn der DMA endet. Dieser Ansatz erfordert ein klares Verständnis der Funktionsweise von Peripheriegeräten. Es reduziert jedoch die Kosten auf ein absolutes Minimum, wodurch die Notwendigkeit einer solchen Lösung gleich Null wird.
Ignorieren der Hardwarefunktionalität des Mikrocontrollers
In meiner Praxis traf ich ein Projekt mit 18 Betriebssystem-Software-Timern, die auf dieselbe Frequenz eingestellt waren. Zur gleichen Zeit gab es ungefähr 10 Zeitgeber im Mikrocontroller, von denen nur systische verwendet wurden. Um den Scheduler des Betriebssystems zu takten. Diese Entscheidung wurde durch den fehlenden Wunsch erklärt, "mit der Hardware" des Mikrocontrollers herumzuspielen. Gleichzeitig wurden dem Stapel etwa 10 kb für die vom Software-Timer aufgerufene Funktion zugewiesen. Tatsächlich wurde ungefähr 1 kb verwendet (kurz). Dies lag an der "Mehrdeutigkeit dessen, was in den genannten Bibliotheken geschieht".
In diesem Fall war es möglich, TIM6 sicher auszuwählen (im Fall der Verwendung von stm32f4), was einen Interrupt mit einer bestimmten Frequenz erzeugen und darin einfach die erforderlichen Funktionen aufrufen würde.
Verwenden einer Endlosschleife anstelle einer Zustandsmaschine
Als separate Spalte würde ich die Unfähigkeit einiger Programmierer herausgreifen, kompakte Finite-State-Maschinen zu schreiben, und stattdessen einen Stream erstellen, in dem es eine Endlosschleife gibt, die ihre Arbeit beginnt, indem sie etwas aus der Warteschlange holt. Interessanterweise wird in
diesem Artikel beschrieben , wie kompakte Finite-State-Maschinen mithilfe der Sprache selbst erstellt werden.
Ignorieren des "Hardware-Schedulers"
Viele 32-Bit-Mikrocontroller verfügen über einen durchdachten Interrupt-Controller mit einem anpassbaren Prioritätssystem. Im Fall von stm32f4 hat es den Namen NVIC und kann Interrupt-Prioritäten mit 16 Ebenen festlegen (ohne Berücksichtigung von Unterebenen).
Die meisten Anwendungen unter FreeRTOS, mit denen ich mich befassen musste, konnten als Zustandsmaschinen geschrieben werden, die in Interrupts mit korrekt konfigurierten Prioritäten aufgerufen wurden. Und falls der Prozessor zur "normalen Ausführung" zurückkehrt, gehen Sie in den "Ruhezustand". In diesem Fall wäre es nicht erforderlich, den Zugriff auf die meisten Ressourcen (Variablen und andere) zu blockieren. Anwendungen würden eine zusätzliche Abstraktionsebene verlieren. Und in diesem Fall - alles andere als kostenlos. Dieser Ansatz erfordert jedoch eine sorgfältige Architekturplanung für jedes Projekt. In Projekten "Designer" - alle Interrupts haben eine Priorität und werden tatsächlich benötigt, um die Daten zu "filtern". Stellen Sie dann die Reste in die Warteschlange, von wo aus der Objektstrom der entsprechenden Klasse sie nimmt.
Zusammenfassung
In diesem Artikel habe ich über die grundlegenden Probleme gesprochen, mit denen Sie bei der Verwendung des Betriebssystems in Projekten für Mikrocontroller konfrontiert sind, und auch häufige Fälle der Verwendung des Betriebssystems untersucht, bei denen dies hätte vermieden werden können, ohne die Lesbarkeit und Logik des Codes zu verlieren.