Entwicklungswerkzeuge

mbeddr - Embedded-Entwicklung mit erweiterbarem C

| Autor / Redakteur: Bernd Kolb, Markus Völter * / Franz Graser

Unser Roboter ist ein kleines Polizeiauto, das mit 2 Fahrmotoren sowie einer Pistole ausgerüstet ist. Zur Orientierung kommen ein Kompasssensor, zwei Kollisionssensoren sowie ein Lichtsensor zum Einsatz.
Unser Roboter ist ein kleines Polizeiauto, das mit 2 Fahrmotoren sowie einer Pistole ausgerüstet ist. Zur Orientierung kommen ein Kompasssensor, zwei Kollisionssensoren sowie ein Lichtsensor zum Einsatz. (Bild: itemis)

Was wäre, wenn Programmiersprachen genau so einfach erweiterbar wären wie Programme? Der Artikel zeigt im Kontext eingebetteter Systeme, wie sich mit der Entwicklungsumgebung mbeddr die Programmiersprache C erweitern lässt.

Die Entwicklung eingebetteter Systeme stellt die folgenden Herausforderungen an den Entwickler:

Abstraktionen ohne Laufzeit-Overhead: Dass Abstraktionen bei der Softwareentwicklung von Vorteil sind, wissen wir alle. Gute Abstraktionen führen zu weniger Code, besserer Analysierbarkeit sowie besserer Wartbarkeit. In eingebetteten Systemen müssen derartige Abstraktionen allerdings mit wenig Laufzeitkosten verbunden sein, da die Ressourcen auf den Zielgeräten üblicherweise beschränkt sind.

C ist gefährlich: Die am weitesten verbreitete Programmiersprache für eingebettete Systeme ist C. Allerdings hat C durchaus einige hässliche Ecken, die im Kontext eingebetteter Systeme zu ernsthaften Problemen führen können und deren Verwendung unter bestimmten Umständen daher eingeschränkt werden können sollte. Dazu gehören zum Beispiel Void-Pointer sowie die unkontrollierte Verwendung des Präprozessors.

Programmannotationen: Programme in technischen Domänen benötigen oft zusätzliche Spezifikationen für Typen oder Variablen. Dazu gehören Größenbeschränkungen, physikalische Einheiten, oder Zugriffsbeschränkungen. Diese lassen sich in C nicht sinnvoll an den entsprechenden Typen oder Variablen anbringen und werden nicht mit zum Typchecking mit herangezogen

Verifikation: Eingebettete Software wird häufig in sicherheitskritischen Systemen eingesetzt. Dort muss Software, bevor sie verwendet wird, möglichst umfangreich auf Korrektheit untersucht werden. Die Verifikation von C ist sehr teuer, insbesondere aufgrund ihrer Komplexität und des niedrigen Abstraktionslevels von C.

Prozessunterstützung: Ein Großteil aller eingebetteten Systeme wird im Kontext von Produktlinien entwickelt. Um die damit einhergehende Komplexität in den Griff zu bekommen, ist es unter anderem nötig, die Variabilität der Produkte innerhalb der Produktlinie systematisch zu verwalten und eine stringente Traceability von den Implementierungsartefakten zu den Anforderungen sicherzustellen.

Aufgrund all dieser Anforderungen werden eingebettete Systeme oft mit einer Vielzahl von Werkzeugen entwickelt. Die Integration all dieser Werkzeuge führt allerdings zu weiteren Herausforderungen.

Der Ansatz von mbeddr

Um die oben genannten Herausforderungen in den Griff zu bekommen, geht mbeddr einen neuen Weg: Neben einer IDE (Integrated Development Entironment) für C stellt mbeddr verschiedene Spracherweiterungen für C zur Verfügung und erlaubt es mittels eines SDKs (Software Development Kit) weitere Erweiterungen hinzuzufügen oder die Nutzung existierender Konzepte einzuschränken. Bild 1 zeigt die mbeddr-IDE beim Bearbeiten eines C-Programms mit diversen Erweiterungen.

Im weiteren Verlauf dieses Artikels zeigen wir, wie die oben genannten Herausforderungen durch Spracherweiterungen gelöst bzw. entschärft werden können. Dies tun wir anhand eines auf Lego Mindstorms basierenden Beispiels (Bild 2). Mit Hilfe von Mindstorms können kleine autonome Roboter gebaut werden. An den Mindstorm können bis zu 3 Aktuatoren und 4 Sensoren angeschlossen werden. Normalerweise wird Mindstorms mit Hilfe einer grafischen, auf Blockdiagrammen basierenden Sprache programmiert. Mit Hilfe des nxtOSEK-Projekts ist jedoch auch eine C-basierte Programmierung möglich. Wie aus dem Namen des Projekts erkennbar, wird hierbei das Echtzeit-Betriebssystem OSEK verwendet.

Spracherweiterung und MPS

Unter Spracherweiterung versteht man das modulare Hinzufügen neuer Sprachkonzepte zu existierenden Sprachen. Die Betonung dabei liegt auf modular: die Basissprache, in unserem Fall C, darf nicht invasiv verändert werden, um neue Sprachmodule hinzuzufügen. Außerdem sollten sich verschiedene unabhängig voneinander entwickelte Spracherweiterungen nicht gegenseitig stören. Dies hat insbesondere zur Folge, dass die Komposition von unabhängig entwickelten Syntaxbeschreibungen (Grammatiken) nicht zu ungültigen Syntaxbeschreibungen führen darf.

Neue, typischerweise abstraktere oder domänenspezifische Sprachkonstrukte werden mittels Transformationen auf existierende Sprachkonstrukte zurückgeführt. Dies können entweder Konstrukte anderer, bereits existierender Erweiterungen sein oder Konstrukte der Basissprache C. Nachdem alle Erweiterungen auf die Basissprache zurückgeführt wurden, wird das dadurch entstandene Programm mit einem regulären Compiler übersetzt. MPS hat sich als hervorragende Basis für diese Art der modularen Spracherweiterung herausgestellt. Dies ist vor allem der Tatsache zu verdanken, dass MPS ein projizierender Editor ist.

Das bedeutet, dass keine Grammatik oder Parser zum Einsatz kommen. Stattdessen führt eine Änderung an einem Programm im Editor direkt zur Änderung des abstrakten Syntaxbaums. Des weiteren sind projizierende Editoren nicht auf textartige Syntaxformen beschränkt. Tabellarische, mathematische, oder (zukünftig) grafische Notationen lassen sich ohne konzeptionellen Bruch mit textuellen Notationen integrieren. Auch diese Eigenschaft nutzen wir in mbeddr.

Die zentrale Idee von mbeddr ist, dass sich Anwender eigene Erweiterungen bauen, passend für die entsprechende Domäne. Nach einer Einarbeitung in MPS lassen sich solche Erweiterungen unserer Erfahrung nach innerhalb von Stunden oder Tagen entwickeln. Nichtsdestotrotz umfasst mbeddr bereits eine Reihe von Erweiterungen, die allgemein genug gehalten sind, dass sie in vielen Embedded-Projekten sinnvoll eingesetzt werden können. In Folgenden zeigen wir exemplarisch einige dieser Erweiterungen anhand des Roboter-Beispiels.

Hello, Robot!

Wie in jeder neuen Umgebung beginnen wir mit einem „Hello World“-Programm. Nachdem wir die Header-Dateien zum Zugriff auf die Sensoren und Aktuatoren des Roboters in MPS importiert haben, beginnen wir mit der Implementierung einer C-Funktion, die auf dem Display unseres Roboters eine Nachricht ausgibt:

Bereits hier sind einige kleinere Änderungen im Vergleich zu normalem C-Code zu erkennen. Zum einen wird die Funktion ecrobot_device_initialize in einem Module angelegt (Bild 3). Module sind Namespaces und dienen zur Sichtbarkeitskontrolle. Insofern sind sie ein Ersatz für Header-Files. Wie im Beispiel zu sehen ist, werden die Funktionen auch direkt implementiert. Eine Trennung der Deklaration eines Funktionsprototypen und der Implementierung ist in mbeddr nicht notwendig. Funktionen können als exported markiert werden.

So kann der Nutzer die Sichtbarkeit außerhalb eines Modules steuern. Nur für exportierte Funktionen wird deren Prototyp im generierten Header erstellt. Andernfalls landet dieser in derselben C-Datei wie die Funktion selbst. Außerdem ist im Beispielcode zu sehen, dass ein anderes Modul importiert wird. Diese und andere Abweichungen erleichtern dem Programmierer die Arbeit und adaptieren bewährte Konzepte aus anderen Sprachen, soweit diese keinen Laufzeit-Overhead mit sich bringen. mbeddr verzichtet vollständig auf den Einsatz des Präprozessors und ersetzt ihn durch geeignete Spracherweiterungen.

Komponenten- und Unit-Testing, Logging

Mit der zuvor gezeigten Implementierung haben wir uns an die API des Mindstorms gebunden. Ein Test ohne die entsprechende Hardware ist nun nicht mehr ohne Weiteres möglich. Um solche engen Kopplungen zu vermeiden, hat mbeddr Komponenten als Sprachkonstrukte eingeführt. Diese erlauben es, die Interfacebeschreibung von deren Implementierung zu trennen und bei Bedarf unterschiedliche Implementierungen zu verwenden (Bild 4).

Die Komponente spezifiziert, welche Interfaces von ihr implementiert (provides EcRobot robot) werden und welche anderen Interfaces zur Implementierung benötigt werden. Dabei ist wichtig, dass auch bei den benötigten Interfaces (requires ...) nicht direkt auf eine andere, die Implementierung bereitstellende, Komponente verwiesen wird, um die direkte Kopplung zu vermeiden.

Für Interface-Operationen können Vor- und Nachbedingungen angegeben werden. Diese erlauben es, Erwartungen an alle Implementierungen zu beschreiben und können mit in den C-Code kompiliert werden. Bei einem Fehlschlag wird zur Laufzeit eine entsprechende Fehlermeldung ausgelöst. Mit Vorbedingungen lassen sich die Erwartungen an Funktionsparameter beschreiben. Nachbedingungen können zusätzlich verwendet werden, um das Ergebnis der Funktion zu überprüfen.

Für die Überprüfung steht die volle Mächtigkeit von C-Expressions zur Verfügung. Auch Protokollzustandsmaschinen zur Beschreibung der gültigen Aufruf-Reihenfolge von Operationen werden unterstützt.

In der Zukunft wird mbeddr mit Hilfe von Modelverifikationsmethoden auch in der Lage sein, Teile der pre- & post-Conditions sowie Protokollzustandsmaschinen statisch zu prüfen.

Im Gegensatz zu C++ erlauben mbeddr-Komponenten keine dynamische Erzeugung und müssen bei Programmstart initialisiert werden. Dies geschieht durch das initialize-Statement. In diesem Statement wird die Instanziierung und Verdrahtung der Komponenten vorgenommen: Für jedes Interface, das von einer Komponente als required markiert wurde, muss eine Komponente mit dem zugehörigen provided-Port angegeben werden, der die Implementierung dafür zur Verfügung stellt.

Nun ist es möglich, die Funktionalität von ecrobot_device_initialize unabhängig von der Hardware zu testen, da die Implementierung der benötigten Schnittstellen einfach ausgetauscht werden kann.

Um das Testen weiter zu unterstützen, hat mbeddr test cases als First-Class Citizen eingeführt. Innerhalb von test case stehen assert und fail-Statements zur Verfügung. Eine test-Expression erlaubt das Ausführen von mehreren test cases. Als Ergebnis liefert sie die Anzahl der fehlgeschlagenen Tests. Neben test cases bietet mbeddr für viele weitere Spracherweiterungen Testunterstützung, so z. B. Stubs und Mocks für Komponenten.

Das Logging von Nachrichten ist in eingebetteten Systemen nicht einfach, da abhängig vom Zielgerät keine Konsole zur Verfügung steht, sondern die Nachrichten in einem Fehlerspeicher abgelegt werden müssen. Außerdem sollten deaktivierte Log-Nachrichten keinen Laufzeit-Overhead mit sich bringen. mbeddr kommt mit Sprachunterstützung für Logging, die durch anpassbare Generatoren für verschiedenste Ausgabeziele genutzt werden kann. Zunächst werden hierzu die Nachrichten mit dem zugehörigen Log-Level deklariert. Nachrichten können optional parametrisiert werden.

Danach können diese Fehlermeldungen mit Hilfe des report-Statement ausgelöst werden. Fehlermeldungen können zentral deaktiviert werden und tauchen damit nicht im generierten Code auf. In unserem Beispiel werden Log-Nachrichten für die Ausgabe des Textes in der Debug- Implementierung verwendet. Auch die Übersetzung der pre- und post-Conditions sowie Protokollzustandsmaschinen nutzt das report-Statement, um über Fehler zu informieren. (Bild 5)

Zustandsautomaten und Model Checking

Das Verhalten vieler eingebetteter Systeme ist zustandsgetrieben — so auch das unseres Roboters. Dieser soll sich nach Erhalt eines Einsatzbefehls auf den Weg machen und nach Verdächtigen Ausschau halten. Wurde ein solcher entdeckt, soll er diesen verhaften und zurück zur Basisstation fahren. Ein Einsatz kann jedoch zu jedem Zeitpunkt abgebrochen werden.

Zur Modellierung eines solches Verhaltens bieten sich Zustandsautomaten an. Um diese effizient implementieren zu können, bietet mbeddr ein spezielles Sprachkonstrukt für Zustandsautomaten. Bild 6 zeigt, wie das Konstrukt in unserem Beispiel verwendet wird, um das Verhalten des Roboters zu beschreiben.

Zustandsautomaten in mbeddr können mehrfach instanziiert werden. Dies kann zum Beispiel dazu verwendet werden, um dieselbe Implementierung der Zustandsmaschine mehrfach für die Ansteuerung unterschiedlicher Motoren zu verwenden. Es ist möglich Automaten in Komponenten einzubetten und damit das Verhalten dieser zu modellieren. Neben der textuellen Repräsentation wird mbeddr ab etwa Mitte 2013 auch eine graphische Notation für Zustandsmaschinen anbieten. Ab diesem Zeitpunkt wird MPS neben textuellen, mathematischen und tabellarischen Notationen auch Graphik erlauben.

Ein großer Vorteil der Verwendung von Zustandsmaschinen liegt in ihrer guten Verifizierbarkeit. Mittels Model Checking lassen sich unterschiedliche Eigenschaften von Zustandsmaschinen mathematisch beweisen. Für Zustandsmaschinen, die als verifiable markiert sind, überprüft mbeddr beispielsweise automatisch, dass alle Zustände erreichbar sind sowie dass alle Transitionen deterministisch sind. Benutzer können eigene zu beweisende Eigenschaften festlegen. Für fehlgeschlagene Checks bekommt der Nutzer ein Gegenbeispiel, anhand dessen er nachvollziehen kann, unter welchen Umständen eine gewisse Annahme nicht zutrifft (siehe voriges Bild). Ziel von mbeddr ist es dabei, nicht selbst die mathematischen Beweise zu führen. Vielmehr kommen renommierte kostenfreie Tools wie beispielsweise NuSMV zum Einsatz. mbeddr schafft es jedoch, die mächtigen Werkzeuge für Entwickler zugänglich zu machen und die Analyseergebnisse auf verständliche Weise dem Anwender zu präsentieren und ermöglicht damit für viele Entwickler neue Möglichkeiten bei der Qualifizierung von Embedded-Software.

Physikalische Einheiten

Die Verwendung physikalischer Einheiten als Teil von Typen und Literalen vermeidet eine ganze Klasse von Fehlern. Die entsprechende mbeddr-Erweiterung bettet physikalische Einheiten direkt in das C-Typsystem ein und ist insbesondere in der Lage, mit den Einheiten zu rechnen. Die Benutzer der entsprechenden Spracherweiterung können eigene abgeleitete Einheiten definieren sowie Konvertierungsregeln zwischen den Einheiten beschreiben.

Mittels Spracherweiterung lassen sich auch weitere primitive Einheiten definieren, die nicht zwangsläufig etwas mit den vordefinierten SI-Einheiten zu tun haben müssen. In unserem Beispiel ist im Bild 7 zu erkennen, dass die Operationsparameter mit physikalischen Einheiten annotiert wurden. In der Implementierung wurde eine mit einer Zeit-Einheit annotierte Variable durch eine mit einer Distanz-Einheit annotierte Variable geteilt. Das Ergebnis sollte einer Geschwindigkeits-Variable zugewiesen werden.

mbeddr hat das erfolgreich als Typfehler erkannt. Die driveForward-Operation des Interfaces erfordert beim Aufruf als ersten Parameter die Einheit mm. Beim Aufruf verwenden wir den Unit-Cast-Operator, um 15m in mm zu konvertieren. Die Konvertierungsregeln wurden im UnitContainer im unteren Teil des folgenden Bildes hinterlegt. Dort wurde auch mps (meters per second) als neue Einheit definiert.

Produktlinienvariablilität

Die zentrale Herausforderung im Kontext von Softwareproduktlinien besteht in der skalierbarer Umsetzung der Variabilität der einzelnen Produkte innerhalb der Produktlinie.

In unserem Roboter-Beispiel haben wir eine sehr einfache Produktlinie. Wir benötigen zwei Versionen unserer Software: eine zum Test sowie eine weitere, die auf der echten Hardware ausgeführt werden kann.

In C werden Varianten typischerweise mit Hilfe des Präprozessors erzeugt. Er entfernt einzelne Textabschnitte, wenn das betreffende #define nicht vorhanden ist. In mbeddr steht ein besserer Ansatz zur Verfügung, der nicht auf Textebene, sondern auf Strukturebene arbeitet. Beliebige Programmelemente lassen sich mit sogenannten Presence Conditions versehen. Dies sind Boole'sche Ausdrücke über Konfigurationsschalter. Falls während der Generierung ein solcher Ausdruck zu false evaluiert, werden das betreffende Element und alle seine Kinder aus dem Modell entfernt und damit nicht kompiliert.

Dieses Zurechtschneiden des Programmbaumes lässt sich nicht nur im Rahmen der Generierung durchführen, sondern auch im Editor selbst: Programme lassen sich in jeder beliebigen Variante betrachten und bearbeiten. (Bild 8)

Verfolgung der Anforderungen (Requirements Tracing)

Neben der eigentlichen Implementierung spielen in vielen Projekten die Erfassung Nachverfolgung von Anforderungen eine wichtige Rolle. Auch hierfür bietet mbeddr Unterstützung. Nachdem die Anforderungen in mbeddr erfasst oder importiert wurden können Traces an beliebigen Programmteilen angebracht werden. Diese Traces werden direkt am jeweiligen Programmelement angezeigt.

Da diese Traces das Programm unübersichtlich machen können und nicht ständig benötigt werden, ist es möglich, deren Anzeige ein- und auszuschalten. Bei einem Kopier-, Lösch- oder Änderungsvorgang werden jedoch selbst bei abgeschalteter Anzeige die Traces entsprechend mit verändert. Dies ist ein wesentlicher Vorteil im Vergleich zu heutigen, meist recht fragilen Ansätzen.

mbeddr bietet die Möglichkeit der Navigation von Traces in beide Richtungen. So wird beispielsweise eine Suche angeboten, mit deren Hilfe es möglich ist, alle Stellen zu finden, die zur Erfüllung eines Requirements beitragen. Natürlich kann auch von dort wieder zurück zur Anforderung navigiert werden. Traces können außerdem typisiert werden. Somit ist beispielsweise eine Unterscheidung zwischen einem Trace zu Testzwecken oder einem Trace zur eigentlichen Implementierung möglich. (Bild 9)

Domänenspezifische Erweiterung – OSEK-Unterstützung

Wie eingangs erwähnt, benutzen wir OSEK als Betriebssystem für unseren Roboter. Um Tasks, Events und weitere Betriebssystemresourcen zu allokieren, müssen diese in einem speziellen Format in einer .oil-Datei vorab bekannt gemacht werden. Im C-Code gibt es zahlreiche Verweise auf Namen, die in dieser Datei vergeben wurden. Um späteren Compilerfehlern vorzubeugen, die durch diese Art von Namenskonventionen entstehen können, haben wir uns für dieses Beispiel entschlossen, den OSEK-Support in mbeddr einzubauen. Dafür wurde die oil-Sprache in mbeddr eingebaut. Diese erlaubt es, den Inhalt von oil-Dateien in mbeddr zu erfassen.

Danach wurden der C-Basissprache neue Sprachkonstrukte hinzugefügt, um bspw. einen OSEK-Task zu implementieren. Hierbei wird direkt die Taskbeschreibung im .oil-File referenziert. Durch die entsprechenden Änderungen wird nun nicht nur Tippfehlern vorgebeugt. Durch das Einführen von Task, Event und Resource als eigener Typ (und nicht der Benutzung des Präprozessors wie sonst in C üblich) werden nun auch Typfehler beim falschen Aufruf von APIs erkannt. Der Nutzer bekommt somit direkt in der IDE Informationen über mögliche Fehler. Der Bau einer derartigen Spracherweiterung lässt sich mit etwas Erfahrung im Umgang in MPS innerhalb einer halben Stunde umsetzen.

Aktueller Stand

mbeddr wird im Rahmen eines KMU Innovativ-Projektes des BMBF unter Beteiligung von itemis, ForTISS und Sick entwickelt. Der Großteil von mbeddr ist Open Source. Es kommt die Eclipse Public License zum Einsatz, die eine gewerbliche Verwendung des Codes erlaubt.

Wir sind derzeit dabei, erste Industrieprojekte mit mbeddr umzusetzen. Dabei haben wir unter anderem viel Arbeit in das Importieren existierender Header-Files gesteckt, so dass eine Integration mit existierenden Bibliotheken leicht möglich ist.

Weitere Informationen zu mbeddr sowie Download-Packages und das Repository finden sich unter http://mbeddr.com sowie dem von dort verlinkten github-Repository.

* Bernd Kolb ist als Softwarearchitekt und Trainer für die itemis AG in Stuttgart tätig. Seine Schwerpunkte sind Eclipse, OSGi und Modellierung.

* Markus Völter arbeitet als freiberuflicher Berater und Coach für die itemis AG in Stuttgart. Seine Schwerpunkte liegen auf Architektur, modellgetriebener Softwareentwicklung und domänenspezifischen Sprachen sowie Produktlinien-Engineering..

Kommentar zu diesem Artikel abgeben

Schreiben Sie uns hier Ihre Meinung ...
(nicht registrierter User)

Kommentar abschicken

 

Copyright © 2017 - Vogel Business Media