Experience Embedded

Professionelle Schulungen, Beratung und Projektunterstützung

Embedded Software as an Integrated Product

Author: Giancarlo Parodi, Renesas Electronics Europe

Beitrag - Embedded Software Engineering Kongress 2015

 

Bei der Auswahl kommerzieller Software wird erwartet, dass diese vom Zulieferer qualifiziert und nach kommerziellen Standards getestet wird und kompatibel mit der Ziel-Mikrocontroller-Plattform ist. Dieser Vortrag wird in ein Konzept eines integrierten Softwarepakets einführen, dass ein Echtzeitbetriebssystem, optimierte Stacks, Applikationsanwendungssoftware, Treiber und Konfigurationspakete für Hardware-Plattformen beinhaltet. All diese wurden nach dem IEC/ISO/IEEE-12207 Standard entwickelt, integriert und getestet.

Die Komplexität der heutigen vernetzten, eingebetteten Systeme ist im Vergleich zur Vergangenheit um ein vielfaches gewachsen. Die Entwickler müssen viel Zeit in die Bereitstellung von Basisfunktionalitäten investieren: Geräte-Treiber und "Middleware" konzipieren, ein Echtzeitbetriebssystem integrieren und womöglich auch Cloud-basierte Anwendungen in Anspruch nehmen. Eine gut entwickelte Software-Architektur ist ein Schlüsselfaktor, um eine reibungslose Entwicklung und Test-Phase zu ermöglichen, insbesondere wenn mehrere miteinander verbundene Module und Protokoll-Stacks benötigt werden.

Der Mikrocontroller-Hersteller stellt Software zur Verfügung, oft in Form einer "Application Note" oder eines "Beispiels". Der Entwickler kann diese kostenlos erhalten und für ein Produkt dieses Herstellers verwenden. Der Hersteller übernimmt aber keine Verantwortung für die Eigenschaften und das Verhalten dieser Software, was in dem Copyright-Text in der Regel explizit erwähnt wird. Die Wortwahl "provided as-is" ist ein typischer Satz, der die Verantwortung für Entwicklung und Test der Software ausschließlich auf die Schulter des Anwenders zurückschiebt.

Sehr oft stellen solche Softwaremodule eine einfache Funktionalität dar und sind deswegen für ein simples Testen einer einzelne Peripherie wohl hilfreich; sie sind aber nicht geeignet, um reibungslos in einer komplexeren Applikation integriert zu werden. Dazu kommt noch, dass eventuelle komplexere "Middleware"-Protokolle weder im vollen Umfang noch als geprüfte Software geliefert werden. Damit steigt das Risiko, dass es Inkompatibilitäten bei der Integration von Softwarepaketen aus unterschiedlichen Quellen gibt und die Evaluierung bzw. das Testen somit noch mehr Zeit in Anspruch nimmt.

Andere Schwierigkeiten bereitet dann der Fall, dass eines dieser Softwaremodule von dem Software-Hersteller erneuert wird. Dieses wird nicht gegenüber anderen Software-Paketen verifiziert, und das gesamte System muss deswegen neu getestet werden, sobald eine der Komponenten gegen eine neuere Version ausgetauscht wird oder einzelne Korrekturen der fehleranfälligen Software mit einem sogenannten „Software-Patch“ vorgenommen werden müssen.

Die kommerziellen Hindernisse sind auch relativ hoch, weil es in einem solchen Szenario schwierig wird, dass die Software-Hersteller eventuelle Probleme an ihren Modulen nachvollziehen können oder wollen, sobald diese in eine komplexere kundespezifische Applikation integriert werden.

Welche Möglichkeiten gibt es also für den Entwickler, die ihm das Leben erleichtern können? Es gibt unterschiedliche Aspekte im Zusammenhang mit Werkzeugen, Systemen und Software-Architekturen, die helfen können, die Entwicklungszeit zu reduzieren und den Optimierungs- und Testaufwand zu minimieren und dabei zu einer Applikation führen, die für die Zukunft einfacher zu verwalten ist.

Angefangen bei der Toollandschaft - die meistverwendete ist immer noch die "C" Programmiersprache. Es gibt unterschiedliche Versionen von diesem Standard; die Empfehlung ist, mindestens Compiler zu verwenden, die den ANSI C99 Standard unterstützen. Das bringt einige Vorteile, wie zum Beispiel das Benutzen von <stdint.h> und <stdbool.h>, "Designated Initializers", die Möglichkeit, Programm und Variablendeklarationen zu mischen usw. Diese sind hilfreich, um sicherzustellen, dass alle Komponenten einer Struktur einen gewissen Wert zugewiesen bekommen können, ohne dabei eine strikte Reihenfolge berücksichtigen zu müssen. Oder dass Variablen im Programm erst dann definiert sind, wenn sie benötigt werden. Die Benutzung von Aufzählungstypen ist auch zu empfehlen, um dem Compiler eine bessere Möglichkeiten zu geben, dass Funktionsparameter zur Kompilierungszeit überprüft werden können und die Applikation durch diese mnemonischen Symbole für den Entwickler verständlicher wird.

Eine Applikation für eingebettete Systeme verlangt oft nach Abweichungen, Optimierungen oder Erweiterungen des Programmierungsstandards. Diese werden in der Regel von jedem Compilerhersteller zur Verfügung gestellt, deren Einsatz sollte aber wenn möglich vermieden oder minimiert werden, um die damit verbundenen Abhängigkeiten zu reduzieren. Manchmal ist der Einsatz solcher Erweiterungen nicht komplett mit anderen Entwicklungsstandards wie MISRA zu vereinbaren und sollten deswegen immer dokumentiert werden, insbesondere wenn eine Zertifizierung des Gerätes erforderlich ist.

Die Dokumentation der Software ist auch ein wichtiger Aspekt, welcher leider oft vernachlässigt wird, insbesondere in den Anfangsstadien eines Projektes. Programme wie "doxygen" generieren aus den in der Software eingebetteten Kommentaren die Dokumentation automatisch. Dies kann eine große Hilfe sein, um die Dokumentation immer konsistent mit der Software zu halten und den Aufwand der Generierung der Dokumentation zu reduzieren.

Programmierungstechniken nach dem "Agile" Prinzip stehen heutzutage an der Basis der modernen Entwicklung einer Applikation. In einem traditionelleren Verfahren würden unterschiedliche Phasen nacheinander stattfinden - beginnend mit der Definition einer Spezifikation und dann dem Definieren des Softwarekonzepts, gefolgt von der Implementierung der Funktionalität, dem Testen der Anwendung und schließlich am Ende des Prozesses dem Einführen einer Instandhaltungsphase. In einem solchen Konzept ist jede Phase mehr oder weniger abgeschlossen, bevor die nächste anfangen kann.

Im Gegensatz dazu sehen die Prinzipien eines "agilen" Verfahrens vor, dass funktionsfähige Softwaremodule binnen Tagen oder Wochen anstelle von Monaten bereitgestellt werden. Um dies zu ermöglichen, sind eine enge Kooperation zwischen der Anforderungsphase und der Implementierungsphase und eine schnelle Anpassung an stets wechselnde Vorgaben erforderlich. Dazu verwenden die Entwickler tägliche kurze Sitzungen, um den Fortschritt des Projektes zusammenzufassen und eventuelle Probleme in der Implementierung aufzubringen.

Ein "agiler" Prozess sieht vor, erst einmal funktionelle Module zu identifizieren. Dann werden diese in kürzere "Handlungsstränge" aufgebrochen und nach deren Komplexität ausgewertet. Diese werden wiederum in noch kürzere Aufgaben zerlegt, und jede Aufgabe wird dann einem Entwickler zugewiesen. Die vorgesehene Zeit, um diese Aufgabe ("Sprint") zu bewältigen, ist in der Regel eher kurz, zum Beispiel zwei Wochen. Am Ende dieser Zeit ist ein Entwicklungszyklus abgeschlossen, der Entwickler liefert sein spezifischen Softwaremodul, und der Prozess wiederholt sich (siehe Abbildung "Agile Development Workflow", PDF).

Dank dieser kurzen Iterationen wird es möglich, sowohl die Zeit für die Fertigstellung des Projektes abzuschätzen, als auch während der Entwicklungsphase die Geschwindigkeit des Projektfortschrittes zu messen. Das ist sehr wichtig, um Verzögerungen rechtzeitig zu erkennen und die damit verbundenen Probleme so frühzeitig wie möglich zu beheben. In solchen Fällen kann ein Projektmanagement-Tool wie "Jira" von Atlassian Software eine solide Infrastruktur bereitstellen, um die Entwicklung nach dem "agilen" Verfahren zu ermöglichen.

Ein oft auftretendes Problem ist auch, dass dem Entwickler am Anfang eines Projektes noch keine geeignete Hardware-Plattform zur Verfügung steht, die Software-Entwicklung aber trotzdem frühestmöglich starten soll. In solchen Fällen kommen dem Entwickler andere Tools wie "CMock" zu Hilfe. Damit ist es möglich, Module auf funktionaler Ebene zu simulieren und zu testen. Ein sogenanntes "Mock Objekt" kann das Verhalten einer Hardware-Interaktion mittels einer softwarebasierten Variante darstellen und dabei diese Funktion bereitstellen.

Sobald die echte Hardware-Plattform verfügbar ist, kann der Entwickler mühelos diese simulierte Version mit der echten austauschen. Selbstverständlich muss diese validiert werden, doch auf funktionaler Ebene konnte das Modul dank dem "CMock Objekt" bereits getestet werden. Somit darf sich die Verifizierung des Verhaltens der Software auf eine reine Hardwareproblematik fokussieren.

"CMock" ist ein natürlicher Begleiter von "Unity", einem C-Programm, das für das einzelne Testen von Softwaremodulen konzipiert ist. Die beiden lassen sich sehr gut zusammen verwenden und sind fundamentale Säulen einer "testgesteuerten Entwicklung" (TDD, Test-Driven Development; siehe Abbildung "Developing Without Hardware", PDF).

Nach diesem Vorgang werden die Tests für die Überprüfung der Funktionen eines Softwaremoduls vor der Implementierung dieses Moduls festgestellt und festgehalten. Dies ermöglicht, dass die Spezifikation überprüft wird, um sicherzustellen, dass diese konform mit den Anforderungen ist. Die Implementierung des Moduls wird damit die einfachste sein, die die definierten Tests erfolgreich übersteht.

Die hauptsächliche Schwierigkeit besteht darin, einen Satz an Tests zu generieren, welcher die unterschiedlichen Funktionen des Moduls von allen Seiten in Anspruch nimmt und dieses unabhängig von der Implementierung prüft. Der Vorteil dabei ist, dass die Implementierung tendenziell minimal gehalten werden kann und somit eine einfachere Lösung des Problems beinhält. Die zukünftige Verwaltung der Software wird somit dank dieser effizienteren Vorgehensweise vereinfacht. Gleichzeitig dient die stets wachsende Anzahl an Testvorgängen, die bei jeder Änderung automatisch durchgeführt werden können, dazu, dass eventuelle Probleme oder unerwünschte Nebeneffekte, die nach einer Änderung in der Implementierung unbewusst eingeführt wurden, sehr schnell in dem automatischen Testvorgang identifiziert werden.

Innerhalb einer größere Gruppe stellt sich dann die Frage, wie die Kollaboration unter Entwicklern gestaltet werden kann.  Dazu gibt es zum Glück professionelle Tools wie "Bitbucket" von Atlassian oder "Mercurial". Diese können sich als graphische Benutzeroberfläche mit anderen Tools wie dem bekannten "GIT" verbinden und die Versionsverwaltung von Dateien vereinfachen.

Um den Kreis der Tool-Landschaft zu schließen: Es gibt zusätzliche Möglichkeiten, die man erfolgreich einsetzen kann. Programme wie "TeamCity" sind für eine kontinuierliche Integration von Softwaremodulen sehr gut geeignet und unterstützen ein automatisiertes Testverfahren, das bei jeder gespeicherten Änderung in der Software eingeleitet wird. Des Weiteren unterstützt "Scons" die Generierung von mehreren Versionen einer Software für unterschiedliche Compiler-Umgebungen oder Hardware-Plattformen. Das Tool "LDRA" kann eingesetzt werden, um eine statische Programmanalyse durchzuführen und die Konsistenz mit anderen Entwicklungsstandards und Regeln wie von MISRA-C spezifiziert zu überprüfen. Dasselbe Tool kann auch eine dynamische Analyse durchführen, um die Abdeckung der Implementierung bei jedem Test-Aufruf zu prüfen und dabei sicherzustellen, dass die gesamten Funktionen in Anspruch genommen werden und es keine Teile im Softwaremodul gibt, die nicht benutzt werden.

Alle solche Programme sind die fundamentalen Bausteine, die relevant sind, um ein automatisiertes Testen und die dazugehörige Berichtserstellung zu ermöglichen, mit dem Ziel, den Entwickler von diesen Tätigkeiten zu befreien und ihm mehr Zeit einzuräumen, seiner Hauptverantwortung nachgehen zu können: eine effiziente und gut konzipierte Software zu schreiben.

Wie könnte ein Vorgang aussehen, mit dem eine gute und effiziente Softwarearchitektur definiert wird? Ein modulares Konzept ist eine natürliche Konsequenz des Bedarfs, ein komplexes Problem in einen Satz von einfacheren Elementen zu partitionieren. Ein Element der Software kann man gut mit einem Modul und dessen Schnittstelle identifizieren. Damit kann man die Abhängigkeiten der Module reduzieren, den Aufwand für die Pflege der Software geringer halten und das Einzeltesten der Module ermöglichen.

Der IEC/ISO/IEEE-12207 Standard ("Systems and software engineering - Software life cycle processes") ist an dieser Stelle eine große Hilfe, um den Entwicklungsfluss zu definieren, da es die Feststellung einer Spezifikation der Anforderungen, der Gestalt und des Test-Verfahrens verlangt.

Wie kann man in der Praxis ein "Modul" definieren? Ein Modul ist ein Basis-Baustein, der mit dem Konzept von Bereitstellen und Benötigen von Diensten zusammenhängt. Damit ist es möglich, eine hierarchische und flexible Lösungen zusammensetzen. Einige Module könnten keine Dienste von untergeordneten Modulen benötigen, werden aber immer Dienste an übergeordnete Module bereitstellen; übergeordnete Module könnten mehrere Dienste von untergeordneten Modulen kombinieren und benutzen. Sobald ein Modul indentifiziert wird, ergibt sich das Problem, dass die Applikation für einen bestimmten Zweck unterschiedliche Module verwenden könnte, die eine ähnliche Funktionalität anbieten. Wie könnte man unterschiedliche Implementierungen in der Software-Architektur unterstützen? (s. Abbildungen auf Seite 6, PDF).

Die Nachteile dabei sind, dass oft sowohl die Programmier-Schnittstelle (API) als auch die Implementierung nicht gleich sind. Es besteht die Gefahr, dass in dem Programm während der Änderungen Fehler eingeführt werden.

Eine zweite Option könnte beide Module unterstützen (s. Abbildung auf Seite 7, PDF). Dies ist aber mit mehr Aufwand verbunden, weil die Änderungen an jeder Stelle, in der das Modul eingesetzt wird, eingeführt werden müssen. Die Funktionsparameter werden höchstwahrscheinlich unterschiedlich sein, und ein Leistungsrückgang ist aufgrund der konditionalen Ausführung der Funktionsaufrufe zu erwarten. Eine elegantere Lösung bietet das Einführen einer "Schnittstelle". Diese regelt "vertraglich", welche Optionen und Funktionen ein gewisses Modul bereitstellt (s. Abbildung auf Seite 8, PDF).

Um dieses Modul zu verwenden, muss die Applikation eine "Instanz" davon generieren. Eine Instanz ist ein Objekt, das alles einkapselt, was für die Benutzung des Moduls notwendig ist: eine Referenz zu seiner Kontroll-Struktur (ähnlich wie eine "this" oder "self" Referenz, die in anderen Programmiersprachen vorhanden ist), eine Konfigurationsstruktur und ein Satz an Funktionsaufrufen (APIs), die die Schnittstelle unterstützt, um die Dienste des Modus in Anspruch zu nehmen.

Die Konfigurationsstruktur enthält normalerweise die gemeinsamen Parameter, die für diesen Typ von Modul relevant sind (als Beispiel für ein I2C Modul: eine I2C Kanalnummer, die maximale Übertragungsgeschwindigkeit, die Bus-Adresse und die Adressentiefe; s. Abbildung auf Seite 8, PDF).

Für Module, die sich direkt mit der Hardware unterhalten, gibt es den zusätzlichen Bedarf, eine Funktion zu definieren, die aufgerufen wird, wenn die Hardware der Applikation ein Ereignis mitteilen muss (eine "Callback"-Funktion), wenn sich zum Beispiel eine Änderungen in dem Zustand der Peripherie ergibt oder bei Verfügbarkeit von neuen Daten.

Diese Funktion wird im Zusammenhang mit einen Interrupt-Ereignis aufgerufen und ist in der Lage, einen Kontext bereitzustellen, um den Auslöser dieses Ereignisses zu identifizieren (s. Abbildung auf Seite 8, PDF).

Die Definition der APIs innerhalb der Schnittstelle ermöglicht ein einfaches Austauschen des Moduls, indem man es an eine unterschiedliche Instanz / Implementierung zuweist. Somit ist es möglich, mit dem Modul mittels einer einheitlichen Schnittstelle zu interagieren (s. Abbildungen auf Seite 9, PDF).

Jegliche Änderung benötigt nur das Austauschen der Referenz zu der aktuellen Instanz. Damit kann man leicht neue zukünftige Implementierungen unterstützen, mittels der Abstrahierungsschicht, die die API-Definition ermöglicht (s. Abbildung auf Seite 9, PDF).

Jede Sonderfunktion, die einzigartig für ein bestimmten Modul ist, jenseits der gemeinsamen Funktionen für den Modultyp, kann in einen Erweiterungsfeld innerhalb der Struktur untergebracht werden; dieser kann in dem Kontext von dem Modul während seiner Initialisierung verwendet werden (s. Abbildungen auf Seite 10, PDF).

Alle diese Ansätze können dazu beitragen, eine robuste, modulare, gut getestete Grundlage für eine eingebettete Software zu entwickeln. Aus der Perspektive des Entwicklers hat dieser Aufwand bis hier aber einen eher überschaubaren Teil der gesamte Anwendung adressiert, wahrscheinlich fokussiert auf Treiberebene (s. Abbildung auf Seite 10, PDF).

Die Applikation besteht aber aus mehreren Teilen und beinhaltet eine Software für die hardwarenahe Konfiguration des Boards, vermutlich ein Echtzeit-Betriebssystem (RTOS), komplexe Software-Stacks für USB- oder Ethernet-Funktionalität und ggf. einige funktionsbezogene Module (Framework), die die Basis-Blöcke kombinieren, um der Applikation komplexere Funktionen bereitzustellen.

Das Problem, welches der Entwickler lösen muss, ist aber immer noch erheblich. Er muss mehrere Module weiter integrieren, um die Konfiguration des Boards und die Applikationsdienste zu definieren, diese als Einheiten testen, die Module dann in einem RTOS unterbringen und diese erneut in der RTOS-Umgebung testen (mittels Jtag-basiertem Debugging und Tracetools). Für Graphik-Applikationen muss der Entwickler erst einmal das Aussehen der Anwendung auf dem PC umsetzen und diese mithilfe eine Graphik-Bibliothek im System unterbringen. Wenn Kommunikationsschnittstellen benötigt werden, könnte die Integration von USB- oder Ethernet-Stacks zusätzlich Zeit verbrauchen. Dazu müssen dann diese Applikationsschichten auf höhere Ebene gegeneinander getestet werden.

Insgesamt erfordert es, um die Entwicklung der Software endlich auf Applikationsebene zu bringen, viel Zeit und Ressourcen. Zusätzliche Kosten, die erst einmal nicht direkt zu erkennen sind, entstehen durch Training, technische Unterstützung, Verwaltung, Optimierung und Integration der Software.

Um dem Entwickler entgegenzukommen und die Gesamtkosten eines Projektes zu senken, stellt Renesas ein revolutionäres Konzept einer Softwareplattform namens Synergy vor. Die gelieferte Software heißt Synergy Software Package (SSP).

Dieses Software-Paket enthält viele Module, die alle notwendigen Funktionalitäten bereitstellen: Peripherietreiber für die hardwarenahe Programmierung, Applikationsframeworks, das bekannte und robuste RTOS ThreadX, die dazu passenden Middleware-Stacks (NetX, USBx) für Konnektivität über USB und Ethernet, FileX, um FAT-kompatible Dateisysteme zu benutzen, GuiX und ein PC-Tool namens GuiX Studio, um graphische Applikationen zu realisieren, und das PC-basierte TraceX-Tool, um das Laufzeitverhalten einer  RTOS-Applikation mittels einer nicht-invasiven Trace-Applikation zu testen.

Jedes dieser Module kann in einer Endapplikation ohne Änderungen verwendet werden. Sie wurden alle nach den modernen Industriestandardverfahren, die im Laufe dieses Artikels erwähnt wurden, entwickelt oder integriert. Das SSP kann für eine komplett neue Familie von Mikrocontrollern verwendet werden, welche mittels einer kompatiblen API-Schnittstelle für alle Derivate verfügbar sind.

Diese Software wird von Renesas als Produkt zur Verfügung gestellt. Deswegen verpflichtet sich Renesas, neue Versionen und Verbesserungen in der Software für die Zukunft vorzunehmen und diese für die Synergy Plattform zu verwalten und zu pflegen (s. Abbildung auf Seite 11, PDF).

Diese Module werden von Renesas qualifiziert und getestet und sind auf Basis der zugehörigen Software-Datenblätter und Benutzerhandbücher gewährleistet. Als alleiniger Ansprechpartner übernimmt Renesas die Verantwortung für die Funktionalität und Qualität der Software (s. Abbildung auf Seite 11, PDF).

Für die Entwicklung der Software hat Renesas Industriestandards, Tools und Prozesse verwendet (IEC/ISO/IEEE-12207), um eine hohe Qualität zu gewährleisten. Zu den Prozeduren gehören Projektmanagement, Konfigurationsmanagement, Kodierungsstandards, Test und Qualitätssicherung sowie kontinuierliche Integration.

Zusätzlich werden dem Anwender die Dokumente und Informationen zur Verfügung gestellt, um die Spezifikation und die Qualität des SSP-Softwarepakets zu validieren, falls gewünscht, Testdaten inklusive.  Dank dieses revolutionären Softwareansatzes kann der Softwareingenieur die Entwicklungs- und Testteit drastisch reduzieren.

Damit kann er mehr Zeit darauf verwenden, innovative Produkte zu entwickeln und sich auf das konzentrieren, was für den Erfolg des Projektes am relevantesten ist: Mehrwert auf der Applikationsebene zu leisten.

Referenzen

Renesas Synergy
Atlassian Jira
Atlassian BitBucket
Cmock
Unity
Mercurial

 

Beitrag als PDF downloaden

 


Implementierung - unsere Trainings & Coachings

Wollen Sie sich auf den aktuellen Stand der Technik bringen?

Dann informieren Sie sich hier zu Schulungen/ Seminaren/ Trainings/ Workshops und individuellen Coachings von MircoConsult zum Thema Implementierung /Embedded- und Echtzeit-Softwareentwicklung.

 

Training & Coaching zu den weiteren Themen unseren Portfolios finden Sie hier.


Implementierung - Fachwissen

Wertvolles Fachwissen zum Thema Implementierung/ Embedded- und Echtzeit-Softwareentwicklung steht hier für Sie zum kostenfreien Download bereit.

Zu den Fachinformationen

 
Fachwissen zu weiteren Themen unseren Portfolios finden Sie hier.