Software-Design Wiederverwendung über verschiedene Core-Architekturen hinweg

Niemand möchte für jedes neue Projekt die gleichen Algorithmen und Funktionen immer wieder neu schreiben. Neue integrierte Entwicklungsumgebungen und Software-Frameworks können dabei helfen, Software-Designs sogar über verschiedene Core-Architekturen hinweg wiederzuverwenden.

Software spielt für den Erfolg eines Endprodukts eine immer größere Rolle, da die Datenverarbeitungsleistung von Embedded Systemen immer weiter steigt. Eine enge Integration der Software-Entwicklungsumgebung verbessert die Programmiereffizienz, da eine Analyse des Codes bereits vor der Kompilierung möglich ist. Programmierer, die Desktop- oder Server-fokussierte Software entwickeln, nutzen den Vorteil integrierter Entwicklungsumgebungen (IDE), die zunehmend effizienter werden und die Produktivität erhöhen.

Frameworks sind heute im Desktop-Bereich alltäglich und nehmen Pro-grammierern die Last, für jedes neue Projekt ständig die gleichen Algorithmen und Funktionen schreiben zu müssen. Um die Vorteile dieses Frameworks richtig nutzen zu können, hat es allerdings einige Zeit gedauert. Der Ansatz für Frameworks in der Embedded-Entwicklung etabliert sich gerade erst.

Neue IDEs wie Atmels »Studio 6« bieten heute nicht nur Core-Language-Syntax-Support, sondern auch verbesserte kontextsensitive Hinweise auf Parameter und Funktionen innerhalb eines Frameworks (Bild 1). MCU-Anbieter integrieren immer mehr Funktionen in ihre Plattformen. Embedded-Entwickler müssen daher produktiver werden, was nur durch eine häufigere Wiederverwendung von Code möglich ist.

Das Marketing verlangt eine immer schnellere Entwicklung, da dies direkt mit dem Unternehmenserfolg zusammenhängt. Wechselt man zu einem anderen Mikrocontroller, muss auch ein neues Support-Code-Archiv und eine neue Dokumentation heruntergeladen sowie eine neue API erlernt werden. Code, der ansonsten identisch ist, ist neu zu schreiben, da sich das API der verschiedenen MCUs geändert hat. Dies kann in einem Marktumfeld problematisch sein, in dem schnelles Skalieren und Anpassen erforderlich sind, um Anforderungsänderungen zu bedienen. Für Embedded-Entwickler wird somit ein umfassendes, plattform-unabhängiges Software-Framework immer wichtiger.

Ein Beispiel für einen solchen Ansatz ist Atmels Software-Framework (ASF). Zusammen mit der IDE »Studio 6« ist damit ein Top-down-Designansatz für die Embedded-Systementwicklung möglich, der das gesamte IP und Know-how des Unternehmens nutzt (Bild 2). Wichtige Codeteile müssen dann nicht noch einmal für jeden Port neu geschrieben werden, wenn man eine andere MCU-Variante oder eine andere Architektur verwendet.

Das ASF bietet eine reine Schnittstelle zwischen dem Anwenderprogramm-Code und den Software-Stacks, sodass die Anwendung auf verschiedenen Embedded-Target-MCUs laufen kann. Das ASF stellt alles Erforderliche für die Applikation sowie das Hardwaredesign bereit und ist sofort einsatzfertig. Die Funktionen und Codebeispiele sind hinsichtlich der Codegröße für jede Zielarchitektur optimiert und arbeiten mit verschiedenen ANSI-C-Compilern. Der ASF-Code wurde von Experten bei Atmel zudem architekturoptimiert, was nicht nur hohe Leistungsfähigkeit, sondern auch einen geringen Stromverbrauch garantieren soll. Alle Funktionen nutzen Amels MCU-Leistungsmerkmale wie Stromsparmodi und das Peripheral-Event-System.

Target-spezifische Details wegabstrahiert

Chip-spezifische Merkmale in den Protokoll-Stacks und Funktionen maximieren die Portabilität des Codes auf Applikationsebene. Die Funktionen werden über eine gängige API implementiert, die Target-spezifische Details wegabstrahiert. Entwickler können damit einen auf einem Atmel-Baustein entwickelten Code auf einem anderen Atmel-Target praktisch unverändert kompilieren.

Die API verwendet ein intuitives Format, in dem Bausteine programmiert werden und dabei Funktionsaufrufe verwenden, die den folgenden Na

Diese Architektur unterstützt auch komplexe Protokolle wie USB. Es gibt vier verschiedene Ebenen: Komponen-te, Service, Peripherie und Board. Die Komponenten- und Service-Module werden direkt von der Anwendung aufgerufen, es sei denn, direkter Zugriff auf Low-Level-Device-Funktionen ist erforderlich, die durch die Peripherie- oder Board-Ebenen bereitgestellt werden.

Die Service-Ebene bietet anwendungsorientierte Software-Stacks wie USB-Class-Treiber, Dateisysteme, architekturoptimierte DSP-Funktionen und Grafikbibliotheken. Diese Ebene nutzt auch die Hardwarefunktionen der MCU. Zu den Komponenten zählen High-Level-Treiber, um MCU- und Board-Level-Peripherie intuitiv und direkt anzusteuern (z.B. Display, Sensoren und Funkschnittstellen).

Der Code in der Komponentenebene wurde mit Schwerpunkt auf die Funktionen geschrieben, die ein Anwender für jede On-Chip-Peripherie-komponente benötigt. Ruft die Anwendung eine Peripherie auf, und zwar in der Art, dass sie nicht direkt über die Komponentenebene unterstützt wird, kann der Anwender den bestehenden Quellcode einfach ändern oder eine zusätzliche Funktion zur API hinzufügen.

Die Komponenten- und Service-Module kommunizieren mit Low-Level-Treibern in der Peripherie-Ebene, die über Hardwareschnittstellen eine Steuerung auf Registerebene bietet. Anwendungen können diese Treiber auch direkt aufrufen, um eine enge Hardware-Integration zu vereinfachen, was die Portierbarkeit zwischen den verschiedenen MCUs des Herstellers maximiert.

Das Board-Modul bietet die Hardware-Ansicht der MCU in der Ziel-umgebung. Der Board-Code abstrahiert die Module oberhalb des Boards von den physikalischen Verbindungen und Initialisierungsfunktionen, welche die I/Os und externen Komponenten steuern. Der Board-Code identifiziert auch, welche Board-Funktionen in den Modulen zur Verfügung stehen, die weiter oben im Software-Stack angesiedelt sind.

Die Board-Definition bietet eine bequeme Möglichkeit, digitale und analoge Peripherie an jeden I/O-Pin zuzuweisen. Eine Anwendung wie eine Audio-Schnittstelle für einen PC kann einen USB-Port, ADC- und DAC-Kanäle, einen SPI-Port und verschiedene universelle digitale I/O-Kanäle erfordern, die auf der Leiterplatte mit Tasten und LEDs verbunden sind.

Eine LED, die zum Beispiel anzeigt, ob sich die MCU im Sleep-Modus befindet oder aktiv ist, kann GPIO4 benannt sein. Anstatt sich diesen Namen zu merken, kann der Entwickler den logischeren Namen »ACTIVITY_LED« der Bitmaske zuweisen, die zum Zugriff der spezifischen Bits innerhalb des eigentlichen I/O-Port-Registers dient, das den LED-Zustand steuert. Nach der Definition kann diese Konstante in der gesamten Anwendung zum Einsatz kommen, um Zugriff von I/O-Funktionsaufrufen auf die LED zu ermöglichen.

Ein Code für mehrere Architekturen

Um sicherzustellen, dass die Modul-APIs genutzt werden, verwendet der vom ASF bereitgestellte Code Standardtechniken zur Initialisierung und für andere Verwaltungsaufgaben. Dies verringert die Lernzeiten für neue Funktionen, da Programmierer die API-Aufrufe in ähnlicher Weise bei bekannten Modulen anwenden können, sobald sie eine neue Funktion hinzufügen sollen.

Ein Modulstart und -stopp wird normalerweise mittels module_start(…)- und module_stop(…)-API-Aufrufen ausgeführt. Beim Einsatz eines ADC-Moduls kann der Programmierer Funktionen wie adc_start(…) und adc_stop(…) verwenden. Die Funktionsaufrufe und Parameter bleiben über alle MCUs von Atmel hinweg gleich. Dazu zählen die Produktreihen »AVR UC3«, »megaAVR«, »AVR XMEGA« und »SAM Cortex-M«.

Das ASF nutzt die IDE und die Struktur des C-Codes, um maximale Anwendungskompatibilität zu garantieren - selbst über Architekturen hinweg. Damit ist eine gemeinsame Code-Entwicklung für 8-Bit- und 32-Bit-Targets möglich, auch wenn verschiedene Compiler verwendet werden. So lassen sich Unterschiede zwischen Compilern und die Art und Weise, wie sie Informationen in den Quelldateien interpretieren, in architekturspezifische Header-Dateien mit aufnehmen. Damit kann das ASF die GCC- und IAR-Compiler sowohl für die 8-Bit- und 32-Bit-AVR- als auch für die ARM-basierten MCUs unterstützen.

Auf ähnliche Weise werden Unterschiede zwischen der Peripherie der verschiedenen Atmel-MCUs in den Header und die C-Quelldateien aufgenommen, und zwar mithilfe von Regeln, welche die ASF-Entwickler erstellt haben, um maximale Gemeinsamkeit und Portabilität zu garantieren. Bei der Ausarbeitung des ASF wandten die Softwareentwickler verschiedene Regeln und Techniken an, die sicherstellen, dass gemeinsamer Anwendungscode nicht zu ändern ist, wenn die Ziel-MCU ausgetauscht wird (Bild 3).

Diese Gemeinsamkeit soll eine einfache Skalierung garantieren, sobald sich die Marktanforderungen ändern. Auch der Übergang von der Prototypen- in die Fertigungsphase wird rationalisiert. Entwickler wählen oft einen flexibleren und hochleistungsfähigeren Baustein für das Prototyping. Damit ist sichergestellt, dass sie genügend Spielraum für den Anwendungscode und mögliche Projektänderungen haben.

Nähert sich das Projekt der Fertigstellung, kann sich herausstellen, dass ein kostengünstigerer Baustein für die Volumenfertigung ausreicht - einer, der sogar eine andere Core-Architektur aufweist. Mit diesem Framework-basierten Ansatz soll es nun äußerst einfach sein, ein anderes Target schnell zu integrieren, was den Marketing-Forderungen nach mehr Entwicklungsflexibilität entspricht.

Über den Autor:

Jörg Bertholdt ist Director Marketing für MCU Tools und Software bei Atmel.