Suchen

Auswahl und Einsatz eines Embedded-Betriebssystems für Mikrocontroller

Autor / Redakteur: Frank Benkert * / Sebastian Gerstl

Embedded-Entwicklungsprojekte sehen sich zu Beginn meist immer mit der gleichen, grundlegenden Frage konfrontiert: Mit oder ohne Betriebssystem? Und wenn mit, mit was? Anhand eines konkreten Beispiels wird hier erklärt, welche konkreten Fragen zur Wahl des richtigen Embedded OS zu klären sind.

Firmen zum Thema

Möchte man in seinem Embedded-Projekt ein Betriebssystem auf seinem Mikrocontroller auswählen, sieht man sich oft mit einer Vielzahl von Auswahlmöglichkeiten konfrontiert. Wie kann man sicherstellen, dass das gewählte Betriebssystem auf das "richtige" ist?
Möchte man in seinem Embedded-Projekt ein Betriebssystem auf seinem Mikrocontroller auswählen, sieht man sich oft mit einer Vielzahl von Auswahlmöglichkeiten konfrontiert. Wie kann man sicherstellen, dass das gewählte Betriebssystem auf das "richtige" ist?
(Bild: gemeinfrei / CC0 )

Welches Betriebssystem wähle ich als Entwickler für mein Embedded-Projekt aus? Was eignet sich für den Mikrocontroller, den ich einsetzen möchte, am Besten? Es gibt für diese Fragen natürlich keine allgemeingültige Antwort. Betrachtet man jedoch die Anforderungen an das neue Produkt genauer, so ergibt sich die Antwort oft von selbst. Welche Fakten helfen aber wirklich bei der Entscheidung? Wie erarbeitet man diese Informationen? Welche Fragen muss man stellen? Wer kann diese beantworten?

Im Folgenden soll anhand eines Projektes aus der Praxis aufgezeigt werden, wie dieser Auswahlprozess und die spätere Integration erfolgreich gelingen kann. Als Vorlage dient ein reales Projekt, welches die Neuentwicklung einer Gerätefamilie zur Steuerung von Großmotoren zum Ziel hatte. Als Plattform kam ein ARM Cortex-M7 von ATMEL (automotive Variante) zum Einsatz. Dieser Beitrag soll allerdings als eine allgemeine Grundlage für Embedded-Projekte aller Art dienen. Darum beziehen sich Formulierungen (wo immer möglich) nicht direkt auf das Projekt selbst.

Was ist ein Betriebssystem?

Um die Frage nach dem „Mit oder Ohne“ überhaupt stellen zu können muss klar sein, was die einzelnen Beteiligten unter dem Begriff „Betriebssystem“ verstehen.

Je tiefer ein Entwickler im System arbeitet, desto differenzierter ist seine Sicht auf das „Ding“ zwischen Hardware und Applikation. Unabhängig davon, auf welche Definition man sich letztendlich einigt, ist es äußerst wichtig das gleiche Verständnis von diesem Begriff zu haben.

Im Projekt einigte man sich auf die Formulierung: „Ein Betriebssystem besteht aus einem Task-Scheduler mit angebundener Treiberarchitektur“. Dabei war die Größe oder Vollständigkeit der „Treiberarchitektur“ nicht ausschlaggebend.

Mit oder ohne Betriebssystem?

Die Beantwortung dieser Frage ist viel einfacher als viele meinen. Als Entscheidungsgrundlage dienen nur wenige, wichtige Punkte:

  • Benötigt das Produkt mehrere nebenläufige Ausführungsstränge (Tasks)?
  • Sind diese Tasks aktiv (oder reagieren sie nur auf äußere Ereignisse)?
  • Gibt es zeitkritische Anforderungen an die gegenseitige Abarbeitung?

Sollte mindestens eine dieser Fragen mit „Ja“ beantworten werden, so ist man gut beraten zumindest schon mal einen Scheduler einzusetzen.

Die Frage nach dem aktiv oder reaktiv weist in die Richtung von Interrupts. Ein rein reaktives System kann z.B. mit mehreren Interrupt-Routinen ebenfalls mehr als einen Ausführungsstrang besitzen. Diese sind dann allerdings (normalerweise) sequentiell. Eine Ausnahme bilden Systeme mit „Interrupt-Prioritäten“ und „Nested-Interrupts“. Eine solche Technik kann man bereits als Pseudo-Scheduler bezeichnen.

Sollte man sich trotz eines „Ja“ gegen einen Scheduler entscheiden, so wird das Projekt mit absoluter Sicherheit neben dem eigentlichen Produkt zusätzlich einen Scheduler als Output liefern.

Um die Frage nach der Notwendigkeit einer Treiberarchitektur zu beantworten gibt es hingegen tatsächlich nur einen einzigen Punkt:

  • Gibt es Hardware, welche der Applikation gegenüber abstrahiert werden soll(te)?

Ein „Ja“ bedeutet, dass mindestens ein Treiber benötigt wird.

Zusammen mit der Antwort auf die Frage nach dem Scheduler lässt sich nun sehr leicht die grundlegendste Frage nach dem „Mit oder Ohne“ beantworten.

Im vorliegenden Projekt entschied man sich für das „Mit“. Deswegen behandeln wir in der Folge: Welche Fragen muss ich mir als Entwickler stellen, wenn ich ein geeignetes Betriebssystem für mein Embedded-Projekt finden will?

Anforderungen an das Betriebssystem

Um später eine Auswahl zwischen den hunderten am Markt vertretenen Betriebssystemen treffen zu können, ist es wichtig die Anforderungen in einer Art Matrix zusammenzutragen.

Die folgende Liste soll einen Einstieg in die Fragestellung geben. Je nach Produkt muss diese natürlich erweitert, verändert oder gekürzt werden.

Wird der Prozessor-Kern unterstützt?

Prozessoren unterstützen Betriebssysteme mittlerweile mit einer Vielzahl von integrierten Funktionen. Was früher im Scheduler „von Hand“ implementiert werden musste, wird heute über wenige Assemblerbefehle direkt vom Prozessor erledigt. Von Automatismen zur Taskumschaltung über Stackmanagement bis hin zur Prüfung von Berechtigungen sind viele Funktionen mittlerweile in die Prozessoren integriert. Dafür ist es jedoch notwendig, dass der Kern des Scheduler diese Besonderheiten kennt und ansteuert. Nur so ist es möglich die optimale Leistung aus dem Prozessor zu holen.

Unterstützt der Scheduler die notwendigen Scheduler-Strategien?

Aus der Entwicklung von embedded Produkten quasi nicht mehr wegzudenken ist ein Echtzeit-Scheduler. Dieser hat dafür zu sorgen, dass Deadlines eingehalten werden und die Vorhersagbarkeit gewährleistet bleibt. Für dessen Umsetzung ist zunächst lediglich eine strikt prioritätsgetriebene Umsetzung ausreichend. Doch was ist, wenn mehrere Tasks auf der gleichen Priorität arbeiten müssen. Welche von diesen wird dann bevorzugt?

Hierfür gibt es zusätzliche Strategien wie „Round Robin“ oder „First In, First Out“.

Auch sind Scheduler erhältlich, welche Fairness auf die eine oder andere Art garantieren. Allerdings werden diese nur selten in embedded Systemen gefordert.

Welche Granularität muss der Scheduler bieten?

Ein embedded System muss teilweise im Mikrosekunden-Raster Entscheidungen treffen oder Aktionen triggern können. Abhängig von der angestrebten Systemarchitektur ist es wichtig zu fragen, ob z.B. eine externe Nachricht alle 2 Millisekunden verarbeitet werden kann oder ein Aktorwert innerhalb einer Deadline von 300 Mikrosekunden neu beschrieben wird. Hat man dann einen Scheduler mit festem 20ms Raster, so ist das zwar schnell, aber unbrauchbar.

Werden Synchronisation und Signalisierung unterstützt?

Sobald mehrere Tasks innerhalb eines Systems laufen, bleibt es nicht aus, dass diese sich Ressourcen teilen müssen. Um den Zugriff auf diese Ressourcen zu synchronisieren sind atomare Mechanismen erforderlich.

Üblicherweise sind diese in Form von „Counting Semaphores“ abgebildet, auf denen wiederum verschiedene andere Synchronisationsmechanismen aufgebaut werden können.

Benötigt das Projekt Interprozess-Kommunikation (IPC)?

Werden Informationen in mehreren Tasks verarbeitet, so kann es notwendig werden Nachrichten zwischen den Systemteilen auszutauschen. Dies kann z.B. über Pipes oder Queues geschehen. Sofern das Produkt diese Problemstellung aufweist sollte ein Betriebssystemkandidat auch gegen diese Anforderung geprüft werden.

Beinhaltet das System Unterstützung für MMUs?

Immer mehr embedded Systeme sind mit „Memory Management Units“ (MMUs) ausgestattet. Je nach Größe des Systems bergen diese MMUs unterschiedlich viele Funktionen. Vom einfachen Cache-Management (welcher Speicherbereich wird durch den CPU-Cache abgebildet) über Zugriffsteuerung bis hin zu komplexen Mappingaufgaben innerhalb eines virtuellen Adressraums. Je nach Anforderung des Produktes und Gegebenheiten der Hardware stellt dies eine hilfreiche Funktion des Betriebssystems dar.

Beinhaltet das System Unterstützung für Speichermanagement?

Die Standard-Schnittstellen zur dynamischen Speicherallokation sind jedem Programmierer gut bekannt: „malloc“ und „free“. Hinter diesem sehr einfachen Interface verbergen sich jedoch komplexe Strategien um z.B. eine Fragmentierung des Speichers zu reduzieren. Falls das Produkt eine solche Funktion benötigt ist es wichtig im Betriebssystem bereits erprobte und getestete Mechanismen hierfür vorzufinden.

Unterstützung für Peripherie und Schnittstellen

Auch wenn die Anbindung von Peripherie oft als Referenzimplementation bereits durch die Hersteller zur Verfügung gestellt wird, so spart es dennoch viel Arbeit, wenn ein ähnlicher Baustein oder die geforderte Schnittstelle bereits als fertiger Treiber für genau dieses Betriebssystem existiert. Bevorzugt natürlich bereits fertig „abgehangen“ und getestet. Zumindest aber mit einem Satz von Testfunktionen, welche auf der eigenen Hardware abgearbeitet werden können.

Unterstützung IDE und Debugging.

Ob ein Betriebssystem mit seiner eigenen „full featured IDE“ geliefert wird, in die bereits der Compiler integriert ist oder ob man die Komponenten selbst zusammenstellen kann ist unerheblich. Wichtig bei der Bewertung dieses Punktes ist, dass ein geschlossener Kreislauf vom Programmieren in einem komfortablen Editor über die Kompilation und das Aufbringen des Kompilats auf das Target bis hin zum Debuggen auf Zeilenebene möglich ist. Letzteres lässt sich natürlich bei manchen Produkten nur durch Emulatoren erreichen.

Diese Möglichkeiten sollten im Zweifel getestet werden, bevor man sich für eine Lösung entscheidet.

Liegt der Quellcode des Betriebssystems vor?

Auf embedded Systemen ist die Verzahnung zwischen Applikation, Treibern und Scheduler naturgemäß sehr eng. Das bedingt oft, dass während des Debuggings fremde Funktionen des besseren Verständnisses wegen durchlaufen werden wollen. Viele kommerziellen Anbieter gewähren deshalb unter bestimmten vertraglichen Bedingungen einen begrenzten Einblick in ihre Sourcecodes um dieses Vorgehen zu unterstützen.

Bei Open Source-Systemen stellt sich diese Frage naturgemäß nicht.

Welcher Lizenz unterliegt das Betriebssystem?

Eine Frage, die nicht nur die Techniker, sondern auch die Produktverantwortlichen umtreibt. So viele Lizenzmodelle es in der Open Source Welt gibt, so viele gibt es auch in der Welt der kommerziellen Produkte. Was welche Lizenz genau bedeutet muss am jeweiligen Produkt detailliert geprüft werden. Z.B. ist eine kommerzielle Lizenz pro Entwicklungsstandort bei verteilten Teams ebenso schädlich, wie eine Lizenz, welche die Nennung des Autors im Handbuch bei einem Produkt ohne Kundendokumentation verlangt.

Support und Community

Oft vernachlässigt, aber trotzdem wichtig ist die Frage nach der Community. Selbst der kommerzielle Support des Herstellers kann eine rege Diskussionsgruppe im Internet nur schwer ersetzen. Ein Ort, an dem unbürokratisch Fragen beantwortet, Codeschnipsel gepostet oder Fehler gemeldet werden ist oft mehr wert als ein Supportvertrag. Seit einiger Zeit gehen auch kommerzielle Anbieter dazu über Mailinglisten zu hosten in denen sowohl Anwender, wie auch Supportmitarbeiter sich gegenseitig austauschen können.

Neben dieser allgemein gehaltenen Liste spielten im realen Projekt noch viele weitere Aspekte eine Rolle. So z.B. die Unterstützung für die Schnittstellen I2C, SPI, QSPI und USB, sowie Dateisysteme und Feldbusse. Weiterhin wurden in die Bewertung weiche Faktoren wie Zukunftssicherheit, Lizenzrisiko, Testbarkeit und Einarbeitungsaufwand mit einbezogen.

Der Auswahlprozess für ein geeignetes Embedded-Betriebssystem

Für den Auswahlprozess sollte man sich auf einige wenige aussichtsreiche Kandidaten begrenzen. Als Hilfe zur Vorauswahl können neben (leider oft veralteten) Listen aus dem Internet und Informationen der Anbieter z.B. auch Erfahrungen von beteiligten Entwicklern dienen.

Der Auswahlprozess selbst ist nicht so trivial, wie man glauben könnte, weshalb es gut ist so wenige Alternativen wie möglich zu sichten. Es empfiehlt sich maximal fünf Bewerber aufzunehmen.

Anders als vermutet besteht der Prozess nicht einfach aus dem Ankreuzen von Features, sondern vorrangig aus Recherche in vorhandener Dokumentation, im Internet und Anfragen an den Support. Ob das in der Hitliste genannte Feature XY tatsächlich das geforderte X und Y zusammen ist oder ob der Hersteller darunter nur eine Teilmenge versteht ist oft nur schwer nachvollziehbar und muss aufwändig recherchiert werden.

Für den Auswahlprozess sollte eine vernünftige Deadline vorgegeben werden. Man kann sich bei der Analyse von Features schnell verzetteln. Auch empfiehlt es sich als Antworten neben „Ja“ und „Nein“ auch „sehr wahrscheinlich“ oder „zu XX Prozent“ zuzulassen um Zeit zu sparen.

Hat man die Bewertungsmatrix annähernd vollständig, so kommen abhängig von der Beantwortung der Feature-Fragen noch weitere Größen hinzu:

  • Welcher Aufwand entsteht durch eine unzureichende / nicht vorhandene Unterstützung eines Features.
  • Welches Risiko birgt die Unsicherheit, ob ein Feature (nur teilweise) unterstützt wird?
  • Welches Risiko birgt ein fehlender Support bzw. eine fehlende oder nur sehr kleine Community?

Nach einem mehrwöchigen Auswahlverfahren in dem viele Aspekte nicht nur per Dokumentation und Supportanfragen, sondern auch per Tests evaluiert werden mussten entschied sich das Projekt für das Open Source Betriebssystem NuttX.

Bei NuttX handelt es sich um ein Echtzeit-Betriebssystem mit einer POSIX-kompatiblen Treiber- und Schnittstellenarchitektur unter BSD-Lizenz. Es wurde erstmals 2007 von Gregory Nutt veröffentlicht und wird seither aktiv unter seiner Führung weiterentwickelt. Da NuttX unter BSD-Lizenz steht muss kein Nutzer (außer in seinem Handbuch) bekannt geben, dass er NuttX nutzt. So war es lange Zeit sehr still um das System.

Wenige Veröffentlichungen verwiesen auf das System, wie z.B. das Open-Hardware-Projekt PX4 - eine (Quadrocoper-) Autopilot-Hardware in Zusammenarbeit mit der ETH Zürich.

Die Anzahl der unterstützten Prozessortypen aber zeigt, dass sehr wohl viel mehr Interesse bestand als die öffentliche Wahrnehmung vermuten ließ. Aktuell umfasst diese Liste von ARM bis ZiLOG Z80 über 150 Prozessorfamilien. Zuzüglich einer Linux/Cygwin Simulationsplattform.

Die Entscheidung für NuttX fiel im Projekt denkbar knapp, da zum damaligen Zeitpunkt noch nicht alle vom Projekt benötigten Funktionen und Features integriert waren. Mittlerweile sind sie es – durch Upstreaming der Sourcecodes. Mittlerweile bekennen sich zahlreiche Firmen öffentlich dazu, NuttX einzusetzen.

Support und Extra-Features

Dies mag kein entscheidender Faktor sein, aber im vorliegenden Fall haben einige nette Extras bei der Entscheidung für die Wahl des Betriebssystems geholfen. NuttX bietet neben vielen „normalen“ Features aber auch Highlights, welche es aus der Masse der embedded Systeme hervorstechen lassen.

C++11 Support

Während der rudimentäre C++-Support bereits seit mehreren Jahren über die µClibC++ gegeben war wurde 2016 die C++11 kompatible „libc++“ aus dem LLVM Projekt integriert.

„Ist wie Linux“

Die Programmierung von NuttX unterscheidet sich kaum von der von Linux. Von „pthreads“ über „everything is a file“ innerhalb eines virtuellen Dateisystems bis zu einer Shell, welche über die serielle Schnittstelle erreichbar ist.

Diagnosebefehle wie „free“ oder „ps“ können gleichermaßen aktiviert werden wie „dd“ oder „hexdump“.

Das bedeutet, dass der Einarbeitungsaufwand für Programmierer von (normalerweise größeren) embedded Linux Systemen sehr gering ist. Auch können bestehende Sourcecodes von eigenen Funktionsbibliotheken fast nahtlos nach NuttX übernommen werden.

Tickless Scheduler

Standard-Scheduler nutzen den zyklischen Takt eines Timer-Bausteins um ihre Scheduler-Abarbeitung zu bedienen. Dabei wird bei jedem Tick eine Funktion angesprungen, welche die Verwaltung der Tasks erledigt. Dadurch tritt ab einer bestimmten Frequenz (Tick-Geschwindigkeit) eine sehr hohe Interruptlast auf, welche gerade embedded Systeme stark beeinträchtigt. NuttX bietet hier das Feature „Tickless Scheduler“, bei welchem der Scheduler errechnet, wann er die nächste Aktion ausführen muss und programmiert den Timer-Baustein (quasi als dynamischen Wecker) auf diesen Zeitpunkt. Dadurch sind höhere Genauigkeiten bei niedriger Frequenz und damit niedriger Interruptlast möglich.

Dynamisches Composite USB Device

NuttX bietet nicht nur Host-USB-Treiber, sondern auch Device-USB an. Ein recht neues Feature ist das zur Laufzeit konfigurierbare Composite USB Device. Mit diesem ist es z.B. möglich innerhalb eines Composite-Devices zusätzliche serielle Schnittstellen für Maintenance-Mode oder Debugging zu aktivieren, welche in den Standardeinstellungen nicht aktiv sind.

Visual Studio mit VisualGDB als IDE

Da NuttX Open Source ist kann die Taskverwaltung komplett eingesehen werden. Konfigurierbare Debugger-Plugins können entsprechend die Taskstrukturen und Stackframes auslesen und ermöglichen so ein komfortables Debuggen.

Im Projekt kam ein Atmel Cortex-M7 Prozessor zum Einsatz. Die Anbindung an den Hardware-Debugger erfolgte über die SWD-Schnittstelle. Die Aufbereitung der Information wurde über ein Debugger-Plugin umgesetzt, sodass die Anzeige in Visual Studio vom Debuggen eines lokalen (Windows-)Prozesses nicht zu unterscheiden war.

Fazit

Die Auswahl eines Betriebssystems für einen Mikrocontroller ist ein aufwändiger Prozess und sollte nicht unterschätzt werden. Für Projekte, welche über Monate oder Jahre laufen ist es unabdingbar eine nicht unerhebliche Zeit in die Evaluation verschiedener Systeme zu investieren.

Der Autor

Der Autor: Frank Benkert, FRB Computersysteme GmbH.
Der Autor: Frank Benkert, FRB Computersysteme GmbH.
(Bild: FRB Computersysteme GmbH)

* Frank Benkert ist seit 1992 als Systementwickler tätig. Sein Werdegang begann mit der Steuerung von Sondermaschinen; damals noch unter div. DOS-Derivaten. Für Automobilzulieferer entwickelte er embedded Datenbanken für Navigationssysteme. Seit über zehn Jahren ist er Software-Architekt für Steuerungen und Steuerungskomponenten im Großmotoren-Bau. Sein Schwerpunkt liegt auf Hardwarenähe und Implementierung von Betriebssystemen und -treibern.

(ID:45416846)