Inhaltsverzeichnis
Ablauf der Codegenerierung
Die Code-Erzeugung durch avr-gcc geschieht in mehreren, voneinander unabhängigen Schritten. Diese Schritte sind für den Anwender nicht immer erkennbar, und es auch nicht unbedingt notwendig, sie zu kennen. Für ein besseres Verständnis der Code-Generierung und zur Einordnung von Fehlermeldungen und Warnungen ist eine Kenntnis aber durchaus hilfreich.
Übersichts-Grafik
Schritte der Codegenerierung
Ohne die Angabe spezieller Optionen legt avr-gcc die Zwischenformate nur als temporäre Dateien an, und nach Beenden des Compilers werden diese wieder gelöscht. Dadurch fällt die Aufgliederung in Unterschritte nicht auf. In diesem Falle müssen Assembler und Linker/Locator auch nicht extra aufgerufen werden, sondern die Aufrufe werden durch avr-gcc verwaltet.
- Precompileren
- Alle Preprozessor-Direktiven werden aufgelöst. Dazu gehören Direktiven wie
#include <avr/io.h> #include "meinzeug.h" #define MAKRONAME ERSATZTEXT #if !defined(__AVR__) #error einen Fehler ausgeben und abbrechen #else /* Alles klar, wir koennen loslegen mit C-Code fuer AVR */ #endif MAKRONAME
- Precompilieren besteht also nur aus reinem Textersatz: Auflösen von Makros, kopieren von anderen Dateien in die Quelle, etc.
- Compilieren
- In diesem Schritt geschieht der eigentliche Compilier-Vorgang: avr-gcc übersetzt die reine, precompilierte C-Quelle (*.i): Die Quelle wird auf Syntax-Fehler geprüft, es werden Optimierungen gemacht, und das übersetzte C-Programm als Assembler-Datei in (*.s) gespeichert.
- Assemblieren
- Der Assembler (avr-as) übersetzt den Assembler-Code (*.s) in das AVR-eigene Objektformat elf32-avr (*.o). Das Objekt enthält schon Maschinen-Code. Zusätzlich gibt es aber noch Lücken, die erst später gefüllt werden und Debug-Informationen und ganz viel anderes Zeug.
- 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 Sections die Daten landen: er lokatiert (von location, locate (en)). Module aus Bibliotheken (*.a) werden hinzugebunden (z.B. printf) und die elf32-avr Ausgabedatei (üblicherweise *.elf) erzeugt.
- Umwandeln ins gewünschte Objekt-Format
- Ohne Angabe spezieller Optionen erzeugen Linker und Assembler 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 erstellen. 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.
- Es ist auch möglich, den Linker mit der Optione --oformat=... zu starten, damit er direkt das gewünschte Ausgabeformat erzeugt. Diese Option lässt man von avr-gcc an den Linker weiterreichen, etwa wenn man direkt eine Intel-HEX-Datei erstellen will und keine elf-Datei braucht, wie sie z.b. beim Debuggen benötigt wird:
> avr-gcc ... -Wl,--oformat=ihex
Dabei ist dafür zu sorgen, daß keine Debug-Informationen etc. in der hex-Datei landen! Generell ist es vorzuziehen, die hex-Datei aus einer elf-Datei zu erzeugen.
Allgemeine Charakteristika von avr-gcc
- Groß- und Kleinschreibung
- C unterscheidet generell zwischen Groß- und Kleinschreibung, sowohl bei Variablen- und Funktionsnamen, bei Sprungmarken als auch bei Makros, und je nach Betriebssystem auch bei Pfad- und Dateinamen/Dateierweiterungen.
- Größe des Typs int
- Der Standard-Typ int ist 16 Bit groß
- Größe von Pointern
- Ein Pointer (Zeiger) ist 16 Bit groß
- Endianess
- avr-gcc implementiert Datentypen als little-endian, d.h. bei Datentypen, die mehrere Bytes groß sind, wird das niederwertigste Byte an der niedrigsten Adresse gespeichert. Dies gilt auch für Adressen und deren Ablage auf dem Stack sowie die Ablage von Werten, die mehrere Register belegen.
- size_t
- size_t ist unsigned und immer 16 Bit groß, unabhängig davon , ob mit -mint8 übersetzt wird oder nicht.
Binäre Konstanten
Einige Versionen von avr-gcc ermöglichen die Verwendung binärer Konstanten für Ganzzahl-Werte:
unsigned char value = 0b00000010;
Davon sollte man absehen, denn zum einen hat man schnell eine 0 zu wenig oder zu viel getippselt, es ist kein Standard-C und man hat die leserlichere Alternative
unsigned char value = (1<<1);
Registerverwendung
avr-gcc verwendet die Prozessor-Register GPRs auf eine definierte Art und Weise. Wie die Register verwendet werden, muss man wissen, wenn man Assembler-Funktionen schreibt, die mit durch avr-gcc übersetztem C-Code zusammenpassen sollen. Der "reinen" C-Programmierer muss sich keine Gedanken um die Register-Verwendung machen.
- R0
- ein temporäres Register, in dem man rumwutzen darf
- R1
- enthält immer den Wert 0
- R1 R17, R28, R29
- allgemeine Register, die durch einen Funktionsaufruf nicht verändert bzw wieder auf den ursprünglichen Wert restauriert werden
- R0, R18 R27, R30, R31
- können durch Funktionsaufrufe verändert werden, ohne restauriert zu werden
- Framepointer
- R28 R29 (Y-Reg) enthält den Framepointer, sofern benötigt
- Argument-Register
- Die Register R8 bis R25 finden Verwendung zur Übergabe von Funktionsparametern. Für den Ort, an dem das Argument übergeben wird, gilt:
- Die Große in Bytes wird zur nächsten geraden Zahl aufgerundet, falls die Argumentgröße ungerade ist.
- Ist in den verbleibenden Übergaberegistern kein Platz mehr oder ist das Argument namenlos (varargs), wird es im Memory übergeben und die Adresse als implizites erstes Argument.
- Der Registerort fängt mit 26 an.
- Von dem Registerort wird die berechete Größe abgezogen und (falls Platz) das Argument in diesen Registern übergeben. Ist das erste Argument z.B. ein long, dann erfolgt die Übergabe in den Registern R22, R23, R24 und R25 (LSB zuerst). Danach wird die gerundete Größe des Arguments vom Registerort abgezogen, dieser also auf 22 gesetzt. Ist das nächste Argument ein char, wird dessen Größe auf 2 aufgerundet und vom Ort abgezogen. Dieses Argument wird also in R20 übergeben und der Ort auf 20 gesetzt, etc.
- Return-Register
- Ein Return-Wert wird in den gleichen Registern zurückgegeben, die auch für ein gleichgrosses erstes Funktionsargument genommen würden. Liefert eine Funktion ein long, dann erfolgt die Rückgabe also in den Registern R22-R25 (LSB zuerst). Bei einem short sind es die Register R24 und R25.