
Guten Tag! Ich möchte über die Architektur eingebetteter Anwendungen sprechen. Leider gibt es nur sehr wenige Bücher zu diesem Thema, und da in letzter Zeit das Interesse an Embedded und IoT zunimmt, möchte ich diesem Thema Aufmerksamkeit schenken. In diesem Artikel möchte ich eine der möglichen Optionen für das Design solcher Anwendungen beschreiben.
Dies ist eine umstrittene Frage! Deshalb bieten sie an, ihre Vision in den Kommentaren zu teilen!
Zunächst werden wir den Bereich bestimmen: Im Rahmen dieses Artikels meinen wir unter eingebetteter Entwicklung Softwareentwicklung für Mikrocontroller (im Folgenden MK, zum Beispiel STM32) in der Sprache C / Asm.
Projekte für MK-basierte Systeme können bedingt in Projekte unterteilt werden
, die kein Multitasking
erfordern und
erfordern . Die Lösungen des ersten Typs sind normalerweise nicht sehr komplex (aus struktureller Sicht). Beispielsweise erfordert ein einfaches Projekt, bei dem Daten vom Sensor gelesen und auf dem Bildschirm angezeigt werden müssen, kein Multitasking. Es reicht aus, die sequentielle Ausführung der oben genannten Operationen zu implementieren.

Wenn die Anwendung komplexer ist: In diesem Rahmen müssen Daten sowohl von digitalen als auch von analogen Sensoren gelesen werden. Speichern Sie die erhaltenen Werte im Speicher (z. B. auf einer SD-Karte), pflegen Sie die Benutzeroberfläche (Display + Tastatur) und ermöglichen Sie den Zugriff auf Daten über eine digitale Schnittstelle (z. B. RS-485 / Modbus oder Ethernet / TCP / IP) und reagieren so schnell wie möglich auf bestimmte Ereignisse im System (Drücken von Notruftasten usw.). In diesem Fall ist es schwierig, auf Multitasking zu verzichten. Es gibt zwei Möglichkeiten, um das Multitasking-Problem zu lösen: Sie können es selbst implementieren oder ein Betriebssystem verwenden (im Folgenden als Betriebssystem bezeichnet). Heute ist FreeRTOS eines der beliebtesten Echtzeitbetriebssysteme für eingebettete Systeme.
Stellen wir uns vor, wie die Architektur einer „komplexen“ eingebetteten Anwendung aussehen soll, die eine relativ große Anzahl heterogener Vorgänge ausführt. Ich gebe zu, dass es möglich ist, eine noch kompliziertere Option vorzuschlagen, bei der Probleme der Tonverarbeitung, Kryptographie usw. gelöst werden, aber wir werden uns mit der oben beschriebenen Option befassen.
Wir setzen die Aufgabe klarer, auch wenn es im Rahmen unserer Anwendung notwendig ist:
- Lesen Sie Daten von Sensoren am RS-485 / Modbus.
- Lesen Sie Daten von Sensoren am I2C-Bus.
- Lesen Sie Daten von digitalen Eingängen.
- Steuerrelaisausgang.
- Benutzeroberfläche pflegen (Display + Tastatur).
- Ermöglichen Sie den Zugriff auf Daten über RS-485 / Modbus.
- Speichern Sie Daten auf externen Medien.
Weil Wir müssen eine ausreichend große Anzahl verschiedener Unteraufgaben implementieren, wir werden das Echtzeit-Betriebssystem (zum Beispiel das bereits erwähnte FreeRTOS) als Basis verwenden. Threads im Betriebssystem werden manchmal als Aufgaben bezeichnet - ähnlich wie bei FreeRTOS. Ich möchte Sie sofort warnen: Der Artikel enthält keinen Quellcode. Es ist der architektonische Aspekt dieses Problems, der von Interesse ist.
Wenn wir die Aufgabe analysieren, können wir sehen, dass verschiedene Komponenten des Systems dieselben Daten verwenden. Beispiel: Daten von Sensoren müssen abgerufen, auf einem Bildschirm angezeigt, auf ein Medium geschrieben und externen Systemen zum Lesen bereitgestellt werden. Dies legt nahe, dass eine Art Echtzeitdatenbank (RTDB) erforderlich ist, um die relevantesten Daten für verschiedene Subsysteme zu speichern und bereitzustellen.
Im System ausgeführte Aufgaben (Lesen von Daten, Schreiben, Anzeigen usw.) können unterschiedliche Anforderungen an die Häufigkeit ihres Anrufs stellen. Es macht keinen Sinn, die Daten auf dem Display mit einer Frequenz von 1 Mal pro 100 ms zu aktualisieren, weil Dies ist für eine Person nicht kritisch, aber es ist häufig erforderlich, Daten von Sensoren zu lesen (insbesondere, wenn Kontrollmaßnahmen für sie erforderlich sind) (obwohl dies je nach TK möglicherweise nicht möglich ist). Ein weiterer wichtiger Punkt betrifft die Lösung des Problems des Zugriffs auf dieselben Daten zum Lesen und Schreiben. Beispiel: Ein Stream, der Sensoren abfragt, schreibt die empfangenen Werte in die RTDB, und in diesem Moment liest der Stream, der für die Aktualisierung der Informationen auf dem Display verantwortlich ist, diese. Hier helfen uns die Synchronisationsmechanismen, die das Betriebssystem bietet.
Beginnen wir mit dem Entwurf der Architektur unserer Anwendung!
Echtzeitdatenbank

Eine gewöhnliche Struktur, die den erforderlichen Satz von Feldern oder ein Array enthält, kann als solche Basis dienen. Für den Zugriff auf "RTDB" verwenden wir die API, mit der wir Daten aus der Datenbank schreiben und lesen können. Die Datenzugriffssynchronisation innerhalb der API-Funktionen kann auf den vom Betriebssystem bereitgestellten Mutexen basieren (oder einen anderen Mechanismus verwenden).

Arbeiten mit Sensoren an den Reifen
Die Arbeit mit Sensoren umfasst Folgendes:
- Daten lesen;
- Datenverarbeitung (falls erforderlich), einschließlich:
- Validierungsprüfung;
- Skalierung
- Filtern
- Validierung gültiger Werte;
- Aufzeichnung empfangener Daten in RTDB.
All diese Arbeiten können in einer Aufgabe erledigt werden.

"Port" - der wahre Hafen von MK;
"Protokolltreiber" - Protokolltreiber (z. B. Modbus). Für einen solchen Treiber ist es ratsam, die Benutzeroberfläche zu erstellen und durchzuarbeiten. Im Rahmen einer solchen Schnittstelle ist es möglich, die Kontrolle des Zugriffs auf die Ressource über Mutexe zu implementieren, wie dies für „RTDB“ geschehen ist. Einige Entwickler schlagen vor, dies auf Portebene zu tun, um sicherzustellen, dass niemand anderes etwas auf diesen Port schreibt, während wir unsere Modbus-Pakete über diesen Port übertragen.
"Sensor Reader" - eine Aufgabe (Task), die Sensoren abfragt, die empfangenen Informationen aufräumt und in "RTDB" schreibt.
"RTDB" ist die oben im entsprechenden Abschnitt beschriebene Echtzeitdatenbank.
Die Aufschrift „Pr: 1“ über der Aufgabe bedeutet Priorität. Unter dem Strich kann jede Aufgabe Priorität haben, wenn zwei Aufgaben, die auf die Prozessorzeit warten, unterschiedliche Prioritäten haben. Die Ressource erhält die Aufgabe mit der höheren Priorität. Wenn die Aufgaben dieselbe Priorität haben, wird die Aufgabe mit der längeren Wartezeit gestartet.
Arbeiten Sie mit diskreten Eingängen
Im Allgemeinen kann die Arbeit mit digitalen Eingängen genauso organisiert werden wie mit digitalen Sensoren. Es kann jedoch erforderlich sein, schnell auf Änderungen im Status der Eingänge zu reagieren. Schließen Sie beispielsweise auf Knopfdruck den Relaisausgang so schnell wie möglich. In diesem Fall ist es besser, den folgenden Ansatz zu verwenden: Um den Relaisausgang zu verarbeiten, erstellen wir eine spezielle separate Aufgabe mit einer höheren Priorität als die anderen. In dieser Aufgabe befindet sich ein Semaphor, das erfasst werden soll. Ein Interrupt wird ausgelöst, um einen bestimmten Digitaleingang auszulösen, bei dem das oben erwähnte Semaphor zurückgesetzt wird. Weil Die Interrupt-Priorität ist maximal, dann wird die damit verbundene Funktion fast sofort ausgeführt. In unserem Fall wird das Semaphor zurückgesetzt, und danach ist die nächste Aufgabe in der Ausführungswarteschlange diejenige, innerhalb derer das Relais gesteuert wird (weil es hat) Die Priorität ist höher als bei anderen Aufgaben und die Sperre beim Warten auf das Semaphor wird aufgehoben.
So kann das Schema dieses Subsystems aussehen.

Neben der schnellen Reaktion auf die Änderung des Status eines bestimmten Eingangs können Sie zusätzlich die Aufgabe „DI-Leser“ festlegen, um den Status diskreter Eingänge zu lesen. Diese Aufgabe kann entweder unabhängig sein oder vom Timer aufgerufen werden.
Die Arbeit des „Interrupt-Handlers“ und des „Relais-Controllers“ in Form von Diagrammen wird nachfolgend dargestellt.

Daten auf externe Medien schreiben
Das Schreiben von Daten auf ein externes Medium ist ideologisch dem Lesen von Daten von digitalen Sensoren sehr ähnlich, nur die Bewegung von Daten erfolgt in die entgegengesetzte Richtung.

Wir lesen von „RTDB“ und schreiben über den „Store-Treiber“ auf ein externes Medium - es kann sich um eine SD-Karte, ein USB-Flash-Laufwerk oder etwas anderes handeln. Vergessen Sie auch hier nicht, Mutex-Wrapper (oder andere Tools zum Organisieren des Zugriffs auf die Ressource) in die Schnittstellenfunktionen aufzunehmen!
Bereitstellung des Zugriffs auf Echtzeitdaten
Ein wichtiger Punkt ist die Bereitstellung von Daten aus „RTDB“ für externe Systeme. Es können fast alle Schnittstellen und Protokolle sein. Im Gegensatz zu einer Reihe von betrachteten Subsystemen besteht der Hauptunterschied darin, dass einige der in Automatisierungssystemen weit verbreiteten Protokolle spezielle Anforderungen an die Antwortzeit auf die Anforderung stellen. Wenn die Antwort nicht innerhalb einer bestimmten Zeit eintrifft, wird davon ausgegangen, dass es keine gibt Kommunikation, auch wenn er (die Antwort) nach einer Weile kommen wird. Und seitdem Der Zugriff auf "RTDB" in unserem Beispiel kann vorübergehend blockiert werden (durch Mutex). Es ist erforderlich, das externe Master-Gerät (Master ist ein Gerät, das versucht, Daten von unserem zu lesen) vor einer solchen Blockierung zu schützen. Es ist auch erwägenswert, den Schutz des Geräts selbst vor der Tatsache zu berücksichtigen, dass der Master es mit hoher Frequenz abfragt, wodurch der Betrieb des Systems durch ständiges Lesen von "RTDB" behindert wird. Eine Lösung besteht darin, einen Zwischenpuffer zu verwenden.

Der "Datenaktualisierer" liest Daten aus "RTDB" mit einer bestimmten Frequenz und addiert das, was er im "Protokollcache" gelesen hat, von dem der "Protokollhandler" Daten aufnimmt. In diesem Fall besteht das Problem des Blockierens auf Protokoll-Cache-Ebene. Um dieses Problem zu beheben, können Sie einen weiteren Cache erstellen, in dem der „Protokoll-Handler“ Daten speichert, falls diese nicht aus dem blockierten „Protokoll-Cache“ gelesen werden konnten. Sie können zusätzlich:
- "Protokollhandler" zu einer höheren Priorität machen;
- Erhöhen Sie die Lesezeit von "RTDB" für "Data Updater" (was eine mittelmäßige Lösung ist).
Arbeiten Sie mit der Benutzeroberfläche
Das Arbeiten mit der Benutzeroberfläche umfasst das Aktualisieren der Daten auf dem Bildschirm und das Arbeiten mit der Tastatur. Die Architektur dieses Subsystems kann folgendermaßen aussehen.

Der UI-Mitarbeiter ist dafür verantwortlich, Tastenanschläge zu lesen, Daten aus „RTDB“ zu entnehmen und die Anzeige zu aktualisieren, die der Benutzer sieht.
Allgemeine Systemstruktur
Schauen Sie sich jetzt an, was am Ende passiert ist.

Um die Last auszugleichen, können Sie zusätzliche Caches festlegen, wie wir es in dem Subsystem getan haben, das für den Zugriff auf diese externen Systeme verantwortlich ist. Einige Aufgaben der Datenübertragung können mithilfe von Warteschlangen gelöst werden, da sie normalerweise von Echtzeitbetriebssystemen unterstützt werden (sicher in FreeRTOS).
Das ist alles, ich hoffe es war interessant.
PSAls Literatur würde ich "Making Embedded Systems: Entwurfsmuster für großartige Software" Elecia White und Artikel von
Andrey Kournits "FreeRTOS - ein Betriebssystem für Mikrocontroller" empfehlen.