Aus RN-Wissen.de
Wechseln zu: Navigation, Suche
LiFePO4 Speicher Test

(Weblinks)
(Allgemeine Charakteristika von avr-gcc)
Zeile 7: Zeile 7:
 
;Größe von Pointern: ein Pointer (Zeiger) ist 16 Bit groß
 
;Größe von Pointern: ein Pointer (Zeiger) ist 16 Bit groß
 
;Endianess: <code>avr-gcc</code> implementiert Datentypen als little-endian, d.h. bei Datentypen, die mehrere [[Byte|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.
 
;Endianess: <code>avr-gcc</code> implementiert Datentypen als little-endian, d.h. bei Datentypen, die mehrere [[Byte|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.
 +
 +
=Ablauf der Codegenerierung=
 +
 +
==Übersichts-Grafik==
 +
 +
[[Bild:Avr-gcc-1.png|Zusammenspiel zwischen avr-gcc und binutils]]
  
 
=Kommandozeilen-Optionen=
 
=Kommandozeilen-Optionen=

Version vom 30. November 2005, 19:07 Uhr

Für den GNU C-Compiler GCC ist ein Port für AVR verfügbar. GCC ist ein sehr leistungsfähiger Compiler, und er kann als die wichtigste freie Software überhaupt bezeichnet werden. Immerhin sind das freie Betriebssystem Linux und viele andere Programme – auch gcc und die Toolchains selbst – mit gcc generiert.

Im Gegensatz zu Bascom ist GCC ein reiner Compiler, bringt also keine umfangreiche Bibliothek an Funktionalitäten mit. Jedoch finden sich einige low-level Funktionen zum Lesen aus dem Flash oder Lesen/Schreiben des EEPROMs etc. in der avr-libc.

Allgemeine Charakteristika von avr-gcc

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.

Ablauf der Codegenerierung

Übersichts-Grafik

Zusammenspiel zwischen avr-gcc und binutils

Kommandozeilen-Optionen

Die Codegenerierung bei avr-gcc wird über Kommandozeilen-Optionen gesteuert. Diese legen fest, für welchen Controller Code zu erzeugen ist, wie stark optimiert wird, ob Debug-Informationen erzeugt werden, etc. Die Optionen teilen sich in zwei Gruppen: Optionen, die für alle GCC-Ports verfürgbar sind und maschinenspezifische Optionen, die nur für AVR verfügbar sind.

Aus der Masse an GCC-Optionen kann hier nur ein kleiner Auszug der wichtigsten und am häufigsten verwendeten Optionen vorgestellt werden.

Allgemeine Optionen für GCC

--help
Anzeige der wichtigsten Optionen
--target-help
Anzeige der wichtigsten maschinenspezifischen Optionen
-O0
keine Optimierung
-O1
Optimierung
-Os
optimiert für Code-Größe
-O2
stärkere Optimierung für bessere Laufzeit
-g
erzeugt Debug-Informationen
-c
(pre)compilert und assembliert nur bis zum Objekt (*.o), kein link-Lauf
-S
(pre)compilert nur und erzeugt Assembler-Ausgabe (*.s)
-E
nur Precompilat (*.i) erzeugen, kein Compilieren, kein Assemblieren, kein Linken
-o <filename>
legt den Name der Ausgabedatei fest
-v
zeigt Versionsinformationen an und ist geschwätzig (verbose): Anzeige der aufgerufenen tools
-I<path>
Angabe eines weiteren Include-Pfads, in dem Dateien mit #include <...> gesucht werden
-E -dM <filename>
Anzeige aller Defines
-D<name>
Definiert Makro <name>
-D<name>=<wert>
Definiert Makro <name> zu <wert>
-U<name>
Undefiniert Makro <name>
-save-temps
Temporäre Dateien (*.i, *.s) werden nicht gelöscht. Teilweise fehlerhaft zusammen mit -c
-Wa,<options>
übergibt Komma-getrennte Liste <options> an den Assembler (avr-as)
-Wp,<options>
übergibt Komma-getrennte Liste <options> an den Preprozessor
-Wl,<options>
übergibt Komma-getrennte Liste <options> an den Linker (avr-ld)
-ansi
bricht mit einer Fehlermeldung ab, wenn kein ANSI-C verwendet wurde
-ffreestanding
Das erzeugte Programm läuft nicht in einer Umgebung wie einer Shell. Der Prototyp von main ist
void main (void);

Maschinenspezifische Optionen für avr-gcc

-mmcu=xxx
Festlegen des Targets, für das Code generiert werden soll. Je nach Target werden unterschiedliche Instruktionen verwendet und andere Startup-Dateien (crtxxx.o) eingebunden. Spezielle Defines werden gesetzt, um in der Quelle zwischen den Targets unterscheiden zu können:
#ifdef __AVR_AT90S2313__
/* Code fuer AT90S2313 */
#elif defined (__AVR_ATmega8__) || defined (__AVR_ATmega32__)
/* Code fuer Mega8 und Mega32 */ 
#else
#error Das ist noch nicht implementiert für diesen Controller!
#endif

mcu für AVR Classic, <= 8 kByte    Builtin define
avr2 __AVR_ARCH__ = 2
at90s2313 __AVR_AT90S2313__
at90s2323 __AVR_AT90S2323__
at90s2333 __AVR_AT90S2333__
at90s2343 __AVR_AT90S2343__
attiny22 __AVR_ATtiny22__
attiny26 __AVR_ATtiny26__
at90s4414 __AVR_AT90S4414__
at90s4433 __AVR_AT90S4433__
at90s4434 __AVR_AT90S4434__
at90s8515 __AVR_AT90S8515__
at90c8534 __AVR_AT90C8534__
at90s8535 __AVR_AT90S8535__
at86rf401 __AVR_AT86RF401__
mcu für AVR classic, > 8 kByte    Builtin define
avr3 __AVR_ARCH__ = 3
atmega103 __AVR_ATmega103__
atmega603 __AVR_ATmega603__
at43usb320 __AVR_AT43USB320__
at43usb355 __AVR_AT43USB355__
at76c711 __AVR_AT76C711__
mcu für AVR enhanced, <= 8 kByte    Builtin define
avr4 __AVR_ARCH__ = 4
atmega8 __AVR_ATmega8__
atmega8515 __AVR_ATmega8515__
atmega8535 __AVR_ATmega8535__
mcu für AVR enhanced, > 8 kByte    Builtin define
avr5 __AVR_ARCH__ = 5
atmega16 __AVR_ATmega16__
atmega161 __AVR_ATmega161__
atmega162 __AVR_ATmega162__
atmega163 __AVR_ATmega163__
atmega169 __AVR_ATmega169__
atmega32 __AVR_ATmega32__
atmega323 __AVR_ATmega323__
atmega64 __AVR_ATmega64__
atmega128 __AVR_ATmega128__
at94k __AVR_AT94K__
mcu für AVR, nur Assembler    Builtin define
avr1 __AVR_ARCH__ = 1
at90s1200 __AVR_AT90S1200__
attiny11 __AVR_ATtiny11__
attiny12 __AVR_ATtiny12__
attiny15 __AVR_ATtiny15__
attiny28 __AVR_ATtiny28__
-minit-stack=xxx
Festlegen der Stack-Adresse
-mint8
Datentyp int ist nur 8 Bit breit, anstatt 16 Bit. Datentypen mit 32 Bit wie long sind nicht verfügbar
-mno-interrupts
Ändert den Stackpointer ohne Interrupts zu deaktivieren
-mcall-prologues
Funktions-Prolog und -Epilog werden als Unterroutinen umgesetzt, um die Codegröße zu verkleinern
-mtiny-stack
Nur die unteren 8 Bit des Stackpointers werden verändert
-mno-tablejump
Für ein switch-Statement werden keine Sprungtabellen angelegt
-mshort-calls
Verwendet rjmp/rcall (begrenzte Sprungweite) auf Devices mit mehr als 8 kByte Flash
-msize
Ausgabe der Instruktonslängen im asm-File
-mdeb
(undokumentiert) Ausgabe von Debug-Informationen für GCC-Entwickler
-morder1
(undokumentiert) andere Register-Allokierung
-morder2
(undokumentiert) andere Register-Allokierung

Code-Beispiele

Dieser Abschnitt enthält Code-Schnippsel für avr-gcc. Es werden Besonderheiten besprochen, die für avr-gcc zu beachten sind.

Dieser Abschnitt ist kein Tutorial zur C-Programmierung und keine Einführung in die Programmiersprache C im allgemeinen.

Strings

Die Programmiersprache C kennt selber keine Strings; das einzige, was C bekannt ist, ist der Datentyp char, der ein einzelnes Zeichen repräsentiert.

Darstellung in C

Ein String im Sinne von C ist ein Array von Charactern bzw. ein Zeiger auf den Anfang des Arrays. Die einzelnen Zeichen folgen im Speicher direkt aufeinander und werden in aufsteigenden Adressen gespeichert. Am String-Ende folgt als Abschluss der Character '\0', um das Ende zu kennzeichnen. Dies ist besonders bei der Berechnung des Speicherplatzes für Strings zu berücksichtigen, denn für die 0 muss auch Platz reserviert werden.

Bestimmen der Stringlänge

 /* Bestimmt die Laenge des Strings ohne die abschliessende '\0' zu zaehlen
 unsigned int strlength (const char *str)
 {
   unsigned int len = 0;
   
   while (*str++)
      len++;
   
   return len;
 }

Die Stringlänge kann auch mit der Standard-Funktion strlen bestimmt werden, deren Prototyp sich in string.h befindet:

 #include <string.h>
 size_t strlen (const char*);

String im Flash belassen

Oftmals werden Strings nur zu Ausgabezwecken verwendet und nicht verändert. Verwendet man Sequenzen der Gestalt

 char *str1 = "Hallo Welt!";
 char str2[] = "Hallo Welt!";

dann werden die Strings im SRAM abgelegt. Im Startup-Code werden die Strings vom Flash ins SRAM kopiert und belegen daher sowohl Platz im SRAM als auch im Flash. Wird ein String nicht verändert, braucht er nicht ins SRAM kopiert zu werden. Das spart Platz im knapp bemessenen SRAM. Allerdings muss anders auf den String zugegriffen werden, denn wegen der Harvard-Architektur des AVR-Kerns kann avr-gcc anhand der Adresse nicht unterscheiden, ob diese ins SRAM, ins Flash oder ins EEPROM zeigt.

 #include <avr/pgmspace.h>
 
 const prog_char str3[] = "Hallo Welt!";
 
 unsigned int strlen_P (const prog_char *str)
 {
    unsigned int len = 0;
 
    while (1)
    {
       char c = (char) pgm_read_byte (str);
       if ('\0' == c)
          return len;
       len++;
       str++; 
    }
 }
 
 void foo()
 {
    unsigned int len;
    len = strlen_P (str3);
    len = strlen_P (PSTR("String im Flash"));
 }

String ins EEPROM legen

Dies geht nach dem gleichen Muster, nach dem Strings ins Flash gelegt werden. Der Zugriff wird vergleichsweise langsam, denn der EEPROM ist langsamer als SRAM bzw. Flash.

 #include <avr/eeprom.h>
 
 const char str4[] __attribute__ ((section(".eeprom"))) = "Hallo Welt!";
 
 unsigned int strlen_EE (const char *str)
 {
    unsigned int len = 0;
 
    while (1)
    {
       char c = (char) eeprom_read_byte (str);
       if ('\0' == c)
          return len;
       len++;
       str++; 
    }
 }

Zufall

"Echte" Zufallszahlen zu generieren ist leider nicht möglich, hierzu muss man externe Hardware wie einen Rauschgenerator verwenden. Funktionen wie rand und random basieren auf algebraischen Verfahren, die eine gute Verteilung der gelieferten Werte haben. Werden diese Funktionen mit dem selben Startwert (seed) initialisiert, liefern sie auch immer die gleiche Folge. In diesem Sinne sind die Werte nicht zufällig sondern nur scheinbar zufällig und "wüst umherhüpfend".

Um einen zufälligen Startwert zu erhalten, kann man den uninitialisierten Inhalt des SRAM verwenden, das nach dem power-up keinen definierten Zustand hat.

Startwert (seed) besorgen

Am einfachsten geht dies, indem man eine Variable in die Sektion .noinit lokatiert und den Wert liest:

unsigned long seed __attribute__ ((section (".noinit")));

Etwas bessere Resultate erhält man, wenn man den ganzen Inhalt des nicht verwendetes SRAM zur Bildung der seed heranzieht. Das Symbol __heap_start wird übrigens im standard Linker-Script definiert, RAMEND ist ein Makro aus ioxxxx.h:

#include <avr/io.h>

unsigned short get_seed()
{
   unsigned short seed = 0;
   unsigned short *p = (unsigned short*) (RAMEND+1);
   extern unsigned short __heap_start;
   
   while (p >= &__heap_start + 1)
      seed ^= * (--p);
   
   return seed;
}

Pseudozufall in der avr-libc

In der avr-libc finden sich Funktionen, um Pseudo-Zufallszahlen zu erhalten bzw. um Startwerte für die Algorithmen zu setzen:

#include <stdlib.h>

Prototypen und Defines:

 #define RAND MAX 0x7FFF
 
 int rand (void);
 void srand (unsigned int seed);
 
 long random (void);
 void srandom (unsigned long seed);

Frühe Codeausführung vor main()

Mitunter ist es notwendig, Code unmittelbar nach dem Reset auszuführen, noch bevor man in main() mit der eigentlichen Programmausführung beginnt. Das kann zB zur Bedienung eines Watchdog-Timers erforderlich sein.

Nach einen Reset und vor Aufruf von main werden Initialisierungen ausgeführt wie

  • setzen des Stackpointers
  • Vorbelegung globaler Datenobjekte: Daten ohne Initializer werden zu 0 initialisert (Section .bss). Für Daten mit Initializer (Section .data) werden die Werte aus dem Flash ins SRAM kopiert.
  • Initialisierung von Registern wie R1, in dem bei avr-gcc immer die Konstante 0 gehalten wird.

Im Linker-Script werden Sections von .init0 bis .init9 definiert, die nacheinander abgearbeitet werden. Erst danach wird main betreten. Um Code früh auszuführen, legt man die Funktion in eine dieser Sections:

/* !!! never call this function !!!
void __attribute__ ((naked, section (".init3")))
code_init3 (void)
{
    /* Code */
}

Zu beachten ist dabei

  • Eine so definierte Funktion darf keinesfalls aufgerufen werden!
  • Zuweisungen wie i=0; ergeben vor .init3 inkorrekten Code, da vor Ende von .init2 Register R1 noch nicht mit 0 besetzt ist, avr-gcc aber davon ausgeht, daß es eben diesen Wert enthält.
  • Lokale Variablen müssen in Registern liegen, denn vor Ende von .init2 ist der Stackpointer noch nicht initialisiert. Zudem ist die Funktion naked, hat also insbesondere keinen Prolog, der den Framepointer (Y-Register) setzen könnte, falls er benötigt wird.
  • Gegebenenfalls ist daher die Verwendung von inline-Assembler angezeigt oder die Implementierung in einem eigenen Assembler-Modul, das dazu gelinkt wird. Der erzeugte Code ist im List-File zu überfrüfen.
  • Werden mehrere Funktionen in die gleiche init-Section gelegt, ist die Reihenfolge ihrer Ausführung nicht spezifiziert und i.a. nicht die gleiche wie in der Quelle.

Unbenutzte init-Sections haben die Nummern 0, 1, 3 und 5 bis 8. Die verbleibenden werden vom Startup-Code verwendet:

.init2
Initialisieren von R1 mit 0 und setzen des Stackpointers
.init4
Kopieren der Daten vom Flash ins SRAM (.data) und löschen von .bss
.init9
Sprung zu main

Abkürzungen und Bezeichnungen

GCC
GNU Compiler Collection
gcc
GNU C-Compiler
GPR
General Purpose Register
ISR
Interrupt Service Routine
IRQ
Interrupt Request
Prolog/Epilog
Code am Anfang/Ende jeder Funktionen/ISR, der dazu dient, verwendete Register zu sichern, den Stack-Frame für lokale Variablen anzulegen (falls benötigt), Stackpointer zu setzen, zurück zu springen (ret, reti), etc.
SFR
Special Function Register
Target
Zielsystem, in unserem Falle avr

Siehe auch

Weblinks


LiFePO4 Speicher Test