Inhaltsverzeichnis
Compiler
Das, was letzlich ein Micro-Controller tut, wird ausschließlich durch das bestimmt, was wir in den Program-Flash reinladen. Üblicherweise ist das eine "*.HEX" Datei, die in etwas seltsamer Form doch nur ein 1:1 Bild der Bits und Bytes ist, wie sie dann im Speicher stehen. Und das sind feststehende Daten (wie z.B. Umrechnungs-Tabellen), und das übersetzte Programm.
Varianten der Programmentwicklung
"Echte" Compiler
Das sind zum Beispiel Assembler, Bascom, GCC. Die verhalten sich genau so wie in der Darstellung und erzeugen mehr oder weniger direkt den Maschinen-code für einen ganz konkreten Controller. "FastAVR", den ich aber mangels besserer Kenntnis hier nur erwähnen will, ist wohl eher ein Programm-Generator.
Ablauf der Codegenerierung mit GCC (z.B. avr-gcc)
Die Code-Erzeugung durch avr-gcc geschieht in mehreren, voneinander relativ unabhängigen Schritten. Die einzelnen Schritte sind für den Anwender nicht immer direkt erkennbar. Für ein besseres Verständnis der Code-Generierung und zur Einordnung von Fehlermeldungen ist eine Kenntnis der Schritte jedoch hilfreich.
- Precompilerung
- Zunächst werden alle Preprozessor-Direktiven aufgelöst. Dazu gehören Direktiven wie
#include
,#define
,#if
,#ifdef
, etc. Der Vorgang des Precompilierens besteht also nur aus reinem Textersatz (z.B. Auflösen von Makros). - Compilierung
- In diesem Schritt geschieht der eigentliche Compilier-Vorgang:
avr-gcc
übersetzt die reine, precompilierte C-Quelle (*.i) in Assembler (*.s). - Assemblierung
- Der Assembler (
avr-as
) übersetzt den Assembler-Code (*.s) in das AVR-eigene Objektformat elf32-avr (*.o). - Linken und Lokatieren
- Der Linker (
avr-ld
) bindet die angegebenen Objekte (*.o) zusammen und löst externe Referenzen auf. Der Linker entscheidet anhand der Beschreibung im Linker-Script, in welchen Speicheradressen und Sektionen die Daten landen. Module aus Bibliotheken (*.a) werden hinzugebunden und die elf32-avr Ausgabedatei (üblicherweise *.elf) erzeugt. - Umwandeln des Objekt-Formats
- Linker und Assembler erzeugen ihre Ausgabe im Objektformat elf32-avr. Wird ein anderes Objektformat wie Intel-HEX (*.hex), binary (*.bin) oder srec (*.srec) benötigt, kann
avr-objcopy
dazu verwendet werden, um diese zu erstellem. Der Inhalt einzelner Sections kann gezielt umkopiert oder ausgeblendet werden, so daß Dateien erstellt werden können, die nur den Inhalt des Flashs (Section.text
oder des EEPROMs (Section.eeprom
) repräsentieren. Durch das Umwandeln in ein anderes Objektformat gehen üblicherweise Informationen wie Debug-Informationen verloren.
Ohne die Angabe spezieller Optionen werden die oben erwähnten Zwischenformate nur als temporäre Dateien angelegt und nach Beenden des gcc-Laufs wieder gelöscht, so daß die Aufgliederung in die Unterschritte nicht auffällt. In diesem Falls müssen Assembler und Linker/Locator auch nicht explizit aufgerufen werden, sondern die Aufrufe erfolgen implizit durch gcc. Ausnahme ist avr-objcopy
, welches immer explizit aufgerufen werden muss wenn man z.B. eine HEX-Datei haben möchte.
Program-Generator
Es gibt auch die Gruppe der "Programm-Generatoren". Die erstellen als Ergebnis einen Source-Code eine anderen Sprache, den man dann nochmals übersetzen muß. Bei einigen Micro-Controller-Compilern ist es auch so, daß die als Zwischen- oder Nebenprodukt Assemblercode herstellen, den man noch überarbeiten könnte, wenn man das will. Die Tücke dabei ist, daß das aber nicht umgekehrt geht, d.h. bei jeder Änderung der ursprünglichen Source muß man solche Anpassungen nochmal machen.
Virtual Machines
Compiler dieser Kategorie (JAVA) erzeugen ebenfalls Maschinencode, aber für eine Maschine, die es eigentlich garnicht gibt. Auf dem Zielsystem (Prozessor, Controller) läuft dann ein Programm, daß diesen Code zum Laufzeitpunkt auf den realen Maschinencode umsetzt. Der Gedanke ist ja nicht schlecht: Ich kann für alle möglichen Rechner EINE Applikation entwickeln, daß dann ohne weiteres auf jedem x-beliebigen Computer ablaufen kann, für den es dieses "Umsetz"-Programm gibt.
"Basic" Controller
(PICAXE) Auch hier wird in zwei Teilen kompiliert /übersetzt: Erstmal ein Basic-Dialekt in eine Art "Token"-Format (kann man als "Kürzel" bezeichnen). Dieses Format wird dann auf den Controller geladen und hier läuft dann als "Firmware" ein Interpreter, der diese Token dann für den RISC-Controller aufbereitet.
Source Code
Recht grob, aber doch, besteht jedes Programm für jeden Compiler (mit unterschiedlicher Gewichtung) aus drei Teilen:
Definition des Zielsystems
Man muß dem Compiler sagen, für welchen Controller er den Code generieren soll. Meistens durch "Include" (GCC) oder "$Regfile= xxx " (Bascom). Die Absicht ist, daß man den restlichen Source-Code nicht ändern muß, wenn man einen anderen Controller verwenden will. Das ist aber schon recht theoretisch, denn so standardisiert, wie es bei den PCs ist (durch das Betriebssystem), sind die Microcontroller beileibe nicht.
Definition der Daten
Hier wird definiert, mit welchen Daten das Programm arbeiten soll. Beim Assembler kann man das auch bleibenlassen, der ist da nicht so heikel, im anderen Extrem, bei den ++ Objekt-orientierten Sprachen, muß hier bereits recht detailliert beschrieben werden, was man braucht und wie man mit diesen Daten im restlichen Programm denn umgehen will.
- Einzelfelder: eines oder mehrere Byte, die zusammen eine bestimmten Datentyp bilden
- Arrays: das sind ein- oder mehrdimensionale Tabellen, wobei die Elemente Einzelfelder, aber auch Strukturen sein können
- Strukturen: Zusammenfassung von Einzelfeldern oder anderen Strukturen zu zusammenhängenden Einheiten
Befehle
Das eigentliche Programm in Form von mehr oder weniger symbolischen und/oder abstrakten Anweisungen. Auch hier gibt es Unterscheidungen
- Anweisungen
Variable = ausdruck
- Verzweigung
GOTO CALL /GOSUB ... RETURN
- Vergleiche
IF (Bedingung) THEN ... ELSE .... END IF
- Auswahl Befehle
SELECT CASE ..... END SELECT
- Wiederholungs-Schleifen
Wichtige Unterscheidung ist, ob die Schleifenbedingung VOR oder NACH den enthaltenen Anweisungen geprüft wird
DO .. WHILE
WHILE ... WEND FOR .. NEXT