(→Dokumentation: update) |
(→ISR mit eigenem Prolog/Epilog: Framepointer, used) |
||
(Eine dazwischenliegende Version desselben Benutzers wird nicht angezeigt) | |||
Zeile 392: | Zeile 392: | ||
Dabei ist darauf zu achten, daß die ISR mit <tt>reti</tt> (return from interrupt) | Dabei ist darauf zu achten, daß die ISR mit <tt>reti</tt> (return from interrupt) | ||
zurückkehrt und evtl. verwendete Register und den Status (<tt>SREG</tt>) sichert. | zurückkehrt und evtl. verwendete Register und den Status (<tt>SREG</tt>) sichert. | ||
+ | |||
+ | Komplexer oder nicht optimierter C-Code, der einen Framepointer braucht, funktioniert nicht mehr weil ohne Prolog der Framepointer nicht initialisiert wird. Die geschieht wenn nicht alle Werte in Registern gehalten werden können und vom Compiler auf dem Stack zwischengespeichert werden. | ||
}} | }} | ||
#include <avr/io.h> | #include <avr/io.h> | ||
Zeile 420: | Zeile 422: | ||
um nicht-implementierte IRQs abzufangen: | um nicht-implementierte IRQs abzufangen: | ||
<pre> | <pre> | ||
− | void __attribute__ ((naked)) | + | void __attribute__ ((naked, used)) |
__vector_default (void) | __vector_default (void) | ||
... | ... | ||
Zeile 705: | Zeile 707: | ||
| [http://gcc.gnu.org/onlinedocs/gcc.pdf pdf] | | [http://gcc.gnu.org/onlinedocs/gcc.pdf pdf] | ||
| [http://gcc.gnu.org/onlinedocs/gcc/AVR-Options.html online] | | [http://gcc.gnu.org/onlinedocs/gcc/AVR-Options.html online] | ||
− | | [http://gcc.gnu.org/gcc- | + | | [http://gcc.gnu.org/gcc-5/changes.html GCC 5] |
|- | |- | ||
! 4.9.x | ! 4.9.x | ||
− | | [http://gcc.gnu.org/onlinedocs/gcc-4.9. | + | | [http://gcc.gnu.org/onlinedocs/gcc-4.9.1/gcc/ HTML] |
− | | [http://gcc.gnu.org/onlinedocs/gcc-4.9. | + | | [http://gcc.gnu.org/onlinedocs/gcc-4.9.1/gcc.pdf pdf] |
− | | [http://gcc.gnu.org/onlinedocs/gcc-4.9. | + | | [http://gcc.gnu.org/onlinedocs/gcc-4.9.1/gcc/AVR-Options.html online] |
− | | [http://gcc.gnu.org/gcc-4. | + | | [http://gcc.gnu.org/gcc-4.9/changes.html GCC 4.9] |
|- | |- | ||
! 4.8.x | ! 4.8.x |
Aktuelle Version vom 14. September 2014, 08:48 Uhr
avr-gcc ist ein freier C-Compiler, mit dem C-Programme zu ausführbaren Programmen übersetzen werden können, die auf Microcontrollern der AVR-Familie lauffähig sind. An Sprachen versteht avr-gcc sowohl C als auch C++. Neben Standard-C bzw. ANSI-C versteht avr-gcc auch GNU-C, das etwas mehr Möglichkeiten und kleinere Spracherweiterungen bietet.
avr-gcc kann auch dazu verwendet werden, um C/C++ Programme nach Assembler zu übersetzen oder um Bibliotheken zu erstellen, die später in unterschiedlichen Projekten verwendet werden können.
Wie bei allen aus der UNIX-Welt kommenden Programmen ist das Kommando-Interface von avr-gcc die Shell bzw. die Kommandozeile, über die Optionen, Parameter, Einstellungen und die Namen der zu übersetzenden Dateien angegeben werden.
Inhaltsverzeichnis
- 1 How to Read
- 2 Benutzer-Schnittstelle
- 3 Unterstützte AVR-Derivate
- 4 Kommandozeilen-Optionen
- 5 C++
- 6 Code-Beispiele
- 7 Includes
- 8 Optimierungen, Tipps & Tricks
- 9 Abkürzungen und Bezeichnungen
- 10 Siehe auch
- 11 Weblinks
- 12 Autor
How to Read
Dieser Artikel bespricht avr-gcc Version 3.x. Er ist kein C-Tutorial und kein AVR-Handbuch – das würde den Umfang des Artikels bei weitem sprengen.
Der Artikel ist ein Handbuch zu avr-gcc. Er bespricht zum Beispiel, wie avr-gcc angenwendet wird und Besonderheiten von avr-gcc-C, die nicht zum Sprachumfang von C gehören. Dazu zählen die Definition von Interrupt Service Routinen (ISRs) oder wie man Daten ins EEPROM legt.
Es wird also besprochen, wie eine ISR zu definieren ist, aber nicht, warum das gegebenenfalls notwendig oder nicht notwendig ist. Warum etwas gemacht wird, ist abhängig von der gestellten Aufgabe, etwa "Initialisiere den UART zur Benutzung mit 9600 Baud". Dafür enthält dieser Artikel zusammen mit dem AVR-Handbuch das Rüstzeug, bietet aber keine Lösungen für konkrete Aufgaben.
Neben diesem Artikel gibt es den Unterartikel Interna von avr-gcc wo Dinge wie die Registerverwendung, Attribute, Builtins und Sections von avr-gcc dargestellt werden. Zudem findet sich dort ein Überblick über die Arbeitsweise von gcc mit den Schritten
- Precompilieren
- Compilieren
- Assemblieren
- Linken
Ein weiterer Unterartikel widmet sich dem Thema Inline-Assembler in avr-gcc.
In den C-Codebeispielen befindet sich das ausführlichere Beispiel "Hallo Welt für AVR (LED blinken)", das nur eine LED blinkt und zeigt, wie ein kleines Projekt mit avr-gcc compiliert werden kann.
Es gibt ein C-Tutorial, das jedoch noch unvollständig und teilweise feherhaft ist (Stand 02/2006). Darüber hinaus gibt es ein C-Tutorial bei www.mikrocontroller.net.
Benutzer-Schnittstelle
Die Benutzer-Schnittstelle von avr-gcc ist – wie für alle Programme, die aus der UNIX-Welt kommen – die Kommandozeile einer Shell, Console bzw. Eingabeaufforderung.
Im einfachsten Fall sieht ein Aufruf von avr-gcc also so aus:
> avr-gcc
Dabei das '>' nicht mittippen, und ein ENTER am Ende der Zeile drücken. Die Antwort bei korrekter Installation ist dann
avr-gcc: no input files
Was bedeutet: das Programm avr-gcc wurde vom Betriebssystem gefunden und konnte/durfte gestartet werden. Dann gibt avr-gcc eine Fehlermeldung aus und beendet die Ausführung, weil er keine Eingabedatei(en) bekommen hat was ja auch stimmt. Soweit ist also alles in Butter.
Um eine C-Datei foo.c mir avr-gcc optimiert zu einem lauffähigen elf-Programm foo.elf für einen ATmega32 zu compileren, würde man angeben
> avr-gcc -Os -mmcu=atmega32 foo.c -o foo.elf
Hat man seine Quellen auf zwei oder mehre Dateien verteilt, geht es analog:
> avr-gcc -Os -mmcu=atmega32 foo.c foo2.c -o foo.elf
Will man nur eine Objekt-Datei erstellen (nur compilieren, nicht linken), dann geht das wie folgt. Das kann günstig sein bei grösseren Projekten, wenn man das Projekt neu erzeugen will, aber nur in einer Quelldatei was geändert hat. Oder wenn das Objekt in einer Bibliothek landen soll.
> avr-gcc -Os -c -mmcu=atmega32 foo.c -o foo.o
Die ausführbare Gesamtdatei foo_all.elf erhält man dann, indem alle Objekte zusammenlinkt:
> avr-gcc -mmcu=atmega32 foo.o foo2.o foo3.o -o foo_all.elf
Um die ausführbare Datei in das oft verwendete Intex-HEX-Format umzuwandeln (einmal fürs Programm, einmal für ein Abbild des EEPROMs) gibt man an:
> avr-objcopy -O ihex -j .text -j .data foo_all.elf foo_all.hex > avr-objcopy -O ihex -j .eeprom --change-section-lma .eeprom=1 foo_all.elf foo_all_eeprom.hex
GCC war immer Kommandozeilen-orientiert und wird es auch immer bleiben, denn das hat gute Gründe:
- ein Compiler ist ein Compiler (und keine grafische Bedienschnittstelle)
- die Plattformabhängigkeit wird auf ein Minimum reduziert
- es gibt die Möglichkeit, avr-gcc per Skript oder make zu starten
- avr-gcc kann durchaus in eine Umgebung integriert werden: in einen Editor oder in eine GUI wie neuere Versionen von AVR-Studio erfolgreich beweisen, etc. Der avr-gcc-Aufruf kann sogar von einem Server-Socket oder einer Web-Application heraus erfolgen, welche ein C-Programm empfängt, es von avr-gcc übersetzen lässt, und das Resultat zurückschickt oder sonst was damit anstellt.
- Lizenzgründe: eine Umgebung, die avr-gcc integriert, kann durchaus proprietär oder nicht quelloffen sein und muss nicht der GPL unterliegen. Wieder ist AVR-Studio ein Beispiel.
Unterstützte AVR-Derivate
Diese Liste der unterstützten Devices kann man anzeigen lassen mit
> avr-gcc --target-help
bzw. ab Version 4.7 mit
> avr-gcc --help=target
Siehe auch "AVR Options" in der GCC Dokumentation.
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. Eine Auflistung aller GCC-Optionen mit Kurzbeschreibung umfasst knapp 1000 Zeilen ohne undokumentierte Optionen, versteht sich.
Allgemeine Optionen für GCC
- --help
- Anzeige der wichtigsten Optionen
- --help -v
- Überschüttet einen mit Optionen
- --target-help
--help=target - Anzeige der wichtigsten maschinenspezifischen Optionen und der unterstützten AVR-Derivate
- -O0
- keine Optimierung - sinnvoll zum debuggen
- -O1
- Optimierung
- -Os
- optimiert für Code-Größe – meist beste Wahl für µCs
- -O2
- stärkere Optimierung für bessere Laufzeit
- -g
- erzeugt Debug-Informationen
- -gdwarf-3 -gstrict-dwarf
- erzeugt Debug-Informationen nachdem DWARF-3 Standard und ohne GNU-spezifische Erweiterungen.
- -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 bzw. *.ii) 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
- -MM
- Für die angegebenen Eingabe-Dateien wird eine Ausgabe erzeugt, die als Makefile-Fragment dienen kann und die Anhängigkeiten (dependencies) der Objekte von den Quellen/Headern beschreibt.
- -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.
- -Wa,<options>
- übergibt Komma-getrennte Liste <options> an den Assembler (avr-as)
- -Wa,-a=<filename>
- Assembler erzeugt ein Listing mit Name <filename>
- -Wp,<options>
- übergibt Komma-getrennte Liste <options> an den Preprozessor
- -Wl,<options>
- übergibt Komma-getrennte Liste <options> an den Linker (avr-ld)
- -Wl,-Map=<filename>
- Linker erzeugt ein Map-File mit Name <filename>
- -Wl,--section-start=<section>=<address>
- Linker legt die Section <section> ab Adresse <address>, z.B: .eeprom=0x810001
- -Wall
- gibt mehr Warnungen, aber immer noch nicht alle
- -std=gnu99
- Sagt dem Compiler, dass er C99 mit GNU-C Erweiterungen akzeptieren soll. Das ist zum Beispiel der Fall, wenn man Embedded-C Code mit __flash verwenden will.
- -std=c89
-ansi - bricht mit einer Fehlermeldung ab, wenn kein ANSI-C (ISO C89) verwendet wurde
- -std=c99
- C99 mit einigen Erweiterungen, die nicht dem C99-Standard widersprechen
- -std=c99 -pedantic
- Bricht mit einer Fehlermeldung ab, wenn kein ISO C99 verwendet wird
Maschinenspezifische Optionen für avr-gcc
Maschinenabhängige Optionen beginnen immer mit -m
- -mmcu=xxx
- Festlegen des Targets (Zielsystem/Controller), für das Code generiert werden soll. Je nach Target muss avr-gcc unterschiedliche Instruktionen verwenden und andere Startup-Dateien (crtxxx.o) einbinden. avr-gcc setzt spezielle Defines, um auch in der Quelle zwischen den Targets unterscheiden zu können, falls das notwendig sein sollte:
#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
- Zwar gibt es für alle AVR-Derivate die avr/io.h, aber die AVR-Familien unterscheiden sich in ihrer Hardware; z.B. darin, wie I/O-Register heissen oder wie Hardware zu initialisieren ist. Diese Abhängigkeit kann man in unterschiedlichen Codestücken aufteilen und wie oben gezeigt bedingt übersetzen. Dadurch hat man Funktionalitäten wie uart_init auf unterschiedlichen Controllern und wahrt den Überblick, weil nicht für jede Controller-Familie eine extra Datei notwendig ist.
- Built-in Makros wie __AVR_ATmega8__ und __AVR_ARCH__ sind ab Version 4.7 im Kapitel "AVR Options" in der GCC Dokumentation erklärt, siehe z.B. "AVR Built-in Macros".
- -mint8
- Datentyp int ist nur 8 Bit breit anstatt 16 Bit. Datentypen mit 64 Bit sind nicht verfügbar. 8-Bit int ist nicht C-Standard konform und wird nicht von der AVR Libc unterstützt (ausser in stdint.h).
- -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
C++
- "C++ is a complex language and an evolving one, and its standard definition (the ISO C++ standard) was only recently completed. As a result, your C++ compiler may occasionally surprise you, even when its behavior is correct."
Zudem sollte der Einsatz von C++ aus Effizienzgründen sehr kritisch betrachtet werden:
- "When programming C++ in space- and runtime-sensitive environments like microcontrollers, extra care should be taken to avoid unwanted side effects of the C++ calling conventions like implied copy constructors that could be called upon function invocation etc. These things could easily add up into a considerable amount of time and program memory wasted. Thus, casual inspection of the generated assembler code (using the -S compiler option) seems to be warranted."
Weiterhin unterliegt der Einsatz von C++ je nach Compiler/Lib-Version bestimmten Einschränkungen:
- Einer kompletten C++ Implementierung fehlt die Unterstützung durch die libstdc++, dadurch fehlen Standardfunktionen, -Klassen und -Templates
- Die Operatoren new und delete sind nicht implementiert, ihre Verwendung führt zu unauflösbaren externen Referenzen (Linker-Fehler)
- Nicht alle Header sind C++-sicher und müssen in extern "C" {...} eingeschlossen werden.
- Exceptions werden nicht unterstützt und müssen via -fno-exceptions abgeschaltet werden, oder der Linker beschwert sich über eine unauflösbare externe Referenz zu __gxx_personality_sj0.
Als Treiber verwendet man wie immer avr-gcc. Standard-Endungen für C++ sind .c++ und .cpp. Bei anderen Endungen teilt man mit -x c++ mit, daß es sich um C++ Dateien handelt, oder ruft avr-c++ direkt auf.
Interrupt-Service-Routinen (ISRs) sind C-Funktionen und werden definiert wie gehabt. Siehe auch Interrupts.
#include <avr/io.h> #include <avr/interrupt.h> #if defined (__cplusplus) extern "C" { #endif /* __cplusplus */ SIGNAL (SIG_NAME) { /* machwas */ } INTERRUPT (SIG_NAME) { /* mach was */ } #if defined (__cplusplus) } #endif /* __cplusplus */
__cplusplus ist ein Standard GCC-Builtin-Define.
Globale Konstruktoren werden in Section .init6 ausgeführt, die Destruktoren in .fini6.
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. Dafür sei auf einschlägige Tutorials/Bücher verwiesen.
Zugriff auf Special Function Registers (SFRs)
Zugiff auf Bytes und Worte
Für den Zugriff auf die SFRs gibt es Defines über den Include
#include <avr/io.h>
Abhängig vom eingestellten Controller werden dann Defines eingebunden, über die auf SFRs wie auf normale Variablen zugegriffen werden kann. Die Namen der Defines sind i.d.R. die gleichen wie im AVR-Manual, also z.b. SREG für das Prozessorstatus-Register SREG:
#include <avr/io.h> ... // SREG lesen uint8_t sreg = SREG; ... // SREG schreiben SREG = sreg;
Für einen Überblick über die eingebundenen Defines kann ein Blick in den Controller-spezifischen Header hilfreich sein. Dieser befindet sich in
- <GCC_HOME>/avr/include/avr/io****.h
z.B. iom32.h für einen ATmega32.
Dieser Zugriff geht auch für 16-Bit Register wie TCNT1 oder ADC, für die eine bestimmte Reihenfolge für den Zugriff auf Low- und High-Teil eingehalten werden muss: avr-gcc generiert die Zugriffe in der richtigen Reihenfolge.
uint16_t tcnt1 = TCNT1;
Zu beachten ist, daß dieser Zugriff nicht atomar erfolgt. Das Lesen/Schreiben mehrbytiger Werte muss vom Compiler in mehrere Byte-Zugriffe zerlegt werden. Zwischen diesen Zugriffen kann ein Interrupt auftreten, wenn Interrupts aktiviert sind. Je nach Programm und welche Aufgaben eine ISR erledigt, kann dies zu Fehlfunktion führen. In dem Fall müssen diese Code-Stücke atomar gemacht werden, damit sie nicht durch einen IRQ unterbrochen werden können!
Zugriff auf einzelne Bits
Zugriff auf Bits geht wie gewohnt mit den Bitoperationen & (and), | (or), ^ (xor) und ~ (not)
Wieder gibt es Defines in den AVR-Headern, mit denen man Masken für den Zugriff erhalten kann, etwa:
/* GIMSK / GICR */ #define INT1 7 #define INT0 6 #define IVSEL 1 #define IVCE 0
Masken ergeben sich durch Schieben von 1 an die richtige Position:
// Ports B_0 und B_1 als Ausgang DDRB |= (1<<PB0) | (1<<PB1);
erzeugt
87 b3 in r24, 0x17 83 60 ori r24, 0x03 87 bb out 0x17, r24
Etwas anders sieht der Code aus, wenn die Bits einzeln gesetzt werden und das Register im bitadressierbaren Bereich liegt (SRAM 0x20 bis 0x3f resp. I/O 0x0 bis 0x1f):
// Ports B_0 und B_1 als Ausgang DDRB |= (1<<PB0); DDRB |= (1<<PB1);
erzeugt
b8 9a sbi 0x17, 0 b9 9a sbi 0x17, 1
Um Bits zu löschen, erzeugt man eine Maske, die an der betreffenden Stelle eine 0 hat:
// Ports B_2 als Eingang DDRB &= ~(1<<PB2);
Auch hier ist zu beachten, daß es Probleme geben kann, wenn nicht atomarer Code erzeugt wird, weil der AVR-Befehlssatz nicht mehr hergibt:
// toggle PORT B_0: wechseln 0 <--> 1 PORTB ^= (1<<PB0);
ergibt
88 b3 in r24, 0x18 ; Wenn hier ein Interrupt auftritt, in dessen ISR PORTB verändert wird, ; dann wird die Änderung durch die letzte Instruktion wieder überschrieben! 91 e0 ldi r25, 0x01 ; dito 89 27 eor r24, r25 ; dito 88 bb out 0x18, r24
Auch das Lesen einzelner Port-Pins geht über das Maskieren von SFRs:
DDRB &= ~(1 << PB2); // PortB.2 als INPUT if (PINB & (1 << PB2)) // PortB.2 ist HIGH else // PortB.2 ist LOW if (!(PINB & (1 << PB2))) // PortB.2 ist LOW else // PortB.2 ist HIGH
Interrupts
Um zu kennzeichnen, daß es sich bei einer Funktion um eine Interrupt Sevice Routine (ISR) handelt, gibt es spezielle Attribute. Diese brauchen nicht explizit hingeschrieben zu werden, ebensowenig wie die genaue Nummer des Interrupt Requests (IRQ). Dafür gibt es Includes aus der AVR Libc und die folgenden Makros.
#include <avr/io.h> #include <avr/interrupt.h> // Eine nichtunterbrechbare Interrupt-Service-Routine ISR (TIMER1_COMPA_vect) { // ISR-Code } // Eine unterbrechbare Interrupt-Service-Routine ISR (TIMER0_OVF_vect, ISR_NOBLOCK) { // ISR-Code }
Dadurch wird die Funktion mit dem richtigen Prolog/Epilog erzeugt, und es wird ein Eintrag in die Interrupt-Vektortabelle gemacht – bei obigem Beispiel also zwei Einträge.
Mit Ausführung einer ISR deaktiviert die AVR-Hardware die Interrupts, so daß die ISR nicht durch andere Interrupt-Anforderungen unterbrochen wird. Beim Verlassen der ISR werden Interrupts wieder automatisch durch die AVR-Hardware aktiviert. Tritt während der ISR ein IRQ auf, wird diese erst nach Beenden des ISR-Codes ausgeführt. Der Interrupt geht also nicht verloren. Dies gilt allerding nicht für Level-getriggerte IRQs wie für manche externen Interrupts oder TWI-Interrupts.
Zwischen zwei ISRs wird zusätzlich mindestens ein Befehl des normalen Programm-Codes abgearbeitet.
Nachschlagen kann man die ISR-Namen im Device-spezifischen Header, die im Installationsverzeichnis liegen:
- <GCC_HOME>/avr/include/avr/ioxxxx.h
Interrupts aktivieren
Damit eine ISR überhaupt zur Ausführung kommt, müssen drei Bedingungen erfüllt sein
- Interrupts müssen global aktiviert sein
- Der entsprechen IRQ muss aktiviert worden sein
- Das zum IRQ gehörende Ereignis muss eintreten
#include <avr/io.h> #include <avr/interrupt.h> ... // enable OutputCompareA Interrupt für Timer1 TIMSK |= (1 << OCIE1A); // disable OutputCompareA Interrupt für Timer1 TIMSK &= ~(1 << OCIE1A); // Interrupts aktivieren sei(); // Interrupts abschalten cli();
Sperrt man eine Code-Sequenz durch Einschachteln in ein cli/sei Paar (man macht das Codestück "atomar", also ununterbrechbar), gehen währenddessen keine Interrupt-Anforderungen verloren. Die entsprechenden IRQ-Flags bleiben gesetzt, und nach dem sei werden die IRQs in der Reihenfolge ihrer Prioritäten abgearbeitet. Ausnahme ist, wenn in einem atomaren Block der selbe IRQ mehrfach auftritt. Der ISR-Code wird dann trotzdem nur einmal ausgeführt.
default Interrupt
Für nicht implementierte Interrupts macht avr-gcc in die Vektortabelle einen Eintrag, der zu __bad_interrupt (definiert im Startup-Code crt*.o) springt und von dort aus weiter zu Adresse 0. Dadurch läuft der AVR wieder von neuem los, wenn ein Interrupt auftritt, zu dem man keine ISR definiert hat allerdings ohne die Hardware zurückzusetzen wie bei einem echten Reset.
Möchte man diesen Fall abfangen, dann geht das über eine globale Funktion namens __vector_default:
#include <avr/interrupt.h> ISR (__vector_default) ...
Damit wird von __bad_interrupt aus nicht nach Adresse 0 gesprungen, sondern weiter zu __vector_default, welches durch ISR() den üblichen ISR-Prolog/Epilog bekommt.
So kann man z.B. eine Meldung ausgeben, eine Warnlampe blinken, in einer Endlosschleife landen, oder über den Watchdog einen richtigen Hardware-Reset auslösen, siehe auch Abschnitt "Reset auslösen".
ISR mit eigenem Prolog/Epilog
Wenn man in einer ISR komplett eigenes Zeug machen will, dann definiert man eine naked Funktion. Mit naked befreit man die Routine vom Standard-Prolog/Epilog.
Dabei ist darauf zu achten, daß die ISR mit reti (return from interrupt) zurückkehrt und evtl. verwendete Register und den Status (SREG) sichert.
Komplexer oder nicht optimierter C-Code, der einen Framepointer braucht, funktioniert nicht mehr weil ohne Prolog der Framepointer nicht initialisiert wird. Die geschieht wenn nicht alle Werte in Registern gehalten werden können und vom Compiler auf dem Stack zwischengespeichert werden.
#include <avr/io.h> #include <avr/interrupt.h> ISR (TIMER0_OVF_vect, ISR_NAKED) { // Port B.6 = 0 // Diese Instruktion verändert nicht das SREG und kein anderes Register // so daß der eigentliche Code nur 1 Befehl lang ist __asm__ __volatile ( "cbi %0, %1" "\n\t" "reti" : : "M" (_SFR_IO_ADDR (PORTB)), "i" (6) ); }
Siehe auch Inline-Assembler in avr-gcc. Die ISR sieht dann so aus:
__vector_9: c6 98 cbi 0x18, 6 18 95 reti
Wiederum kann man als Funktionsname __vector_default nehmen, um nicht-implementierte IRQs abzufangen:
void __attribute__ ((naked, used)) __vector_default (void) ...
SRAM, Flash, EEPROM: Datenablage am Beispiel 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 char str3[] PROGMEM = "Hallo Welt!"; size_t strlen_P (const char *str) { size_t len = 0; while (1) { char c = (char) pgm_read_byte (str); if ('\0' == c) return len; len++; str++; } } void foo (void) { size_t 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[] EEMEM = "Hallo Welt!"; size_t strlen_EE (const char *str) { size_t len = 0; while (1) { char c = (char) eeprom_read_byte (str); if ('\0' == c) return len; len++; str++; } }
Reset auslösen
Falls ein Reset per Software ausgelöst werden soll, dann geht das am besten über den Watchdog. Einfach nur an den Reset-Punkt an Adresse 0 zu springen initialisiert zwar den Controller von neuem, aber es macht keinen wirkliches RESET mit Zurücksetzen der Hardware und allen I/O-Registern.
Durch den Watchdog kann man ein 'richtiges' RESET-Signal erzeugen lassen, so daß die AVR-Hardware genau so initialisiert ist, wie nach einem externen RESET. So kann man z.B. via UART ein RESET-Kommando schicken. Allerdings lässt sich der Watchdog nur minimal auf 15ms einstellen:
#include <avr/wdt.h> #include <avr/interrupt.h> ... cli(); // Interrupts global abschalten wdt_enable (WDTO_15MS); // Watchdog aufziehen auf 15ms while (1); // warten, bis er zubeisst...
Welches Ereignis einen RESET ausgelöst hat, kann man im Register MCUCSR (MCU Control and Status Register) erfahren. Es gibt 4 mögliche RESET-Quellen:
- Power-On Reset
- External Reset
- Brown-Out Reset
- Watchdog Reset
Soll der Inhalt von Variablen einen Reset überleben – eine Variable also nicht initialisiert werden – dann geht das so:
#include <avr/io.h> // status informiert z.B. darüber, ob wir selber den Watchdog ausgelöst haben // oder nicht, oder andere Informationen uint8_t status __attribute__ ((__section__ (".noinit"))); int main (void) { // Wert von MCUSCR merken, möglichst früh im Programm uint8_t mcucsr = MCUCSR; // MCUCSR zurücksetzen MCUCSR = 0; // Watchdog-Reset if (mcuscr & (1 << WDRF)) { // status auswerten } // Power-On Reset: status auf definierten Wert setzen if (mcuscr & (1 << PORF)) { status = 0; } // status auswerten ... }
- An Adresse 0 springen
Falls wirklich zu Adresse 0 gesprungen werden soll – was in einem Bootloader erforderlich sein kann – dann geschieht das mittels einer Funktion reset wie folgt:
extern void reset (void) __attribute__((noreturn)); reset();
reset wird bein Linken mittels -Wl,--defsym=reset=0 auf 0 gesetzt. Weitere Möglichkeit ist, im erzeugten Assembler 0 als Funktionsnamen zu verwenden:
extern void reset (void) __asm__("0") __attribute__((__noreturn__)); reset();
Includes
Die mit
#include <...>
angegebenen Includes werden von avr-gcc in den mit der Option '-I' anegegenen Pfaden gesucht. Dem Compiler bekannt sind die Pfade
<GCC_HOME>/avr/include Standard (stdio.h, ...) <GCC_HOME>/avr/include/avr AVR-spezifisch (avr/io.h, ...) <GCC_HOME>/lib/gcc/avr/<GCC_VERSION>/include Standard, compilerabh. (limits.h, ...)
Gibt man z.B. an
#include <stdio.h>
dann wird automatisch in diesem Verzeichnis nach stdio.h gesucht. In den Verzeichnissen stehen Standard-Includes, die benötigt werden, wenn man libc-Funktionen oder mathematische Funktionen etc. verwendet. AVR-spezifische Dinge stehen im Unterverzeichnis avr, etwa:
#include <avr/io.h>
Als Pfad-Separator wird immer ein / verwendet, auch auf Windows-Betriebssystemen! Also kein \ !
Standard
ctype.h Zeichen-Umwandlungs-Makros und ctype Makros errno.h Symbolische Namen für Fehlercodes stdint, inttypes.h C99 definiert [u]intN_t wenn man genau N [un]signed Bits braucht math.h Mathematische Funktionen: sin, cos, log, gamma, bessel, ... setjmp.h libc unterstützt setjmp() und longjmp(), um direkt in eine andere (nicht-lokale) Funktion zu springen. stdio.h Standard I/O-Funktionen (printf, fscanf, ...) stdlib.h Deklariert grundlegende ISO C Makros und Funktionen sowie einige AVR-spezifische Erweiterungen string.h Stringoperationen auf NULL-terminierten Strings. (strlen, ...) stdarg.h Funktionen mit variabler Argumenanzahl limits.h Min- und Max-Werte von Skalaren (UCHAR_MAX, LONG_MIN, ...)
AVR-spezifisch
Die AVR-spezifischen Includes finden sich wie gesagt im Unterverzeichnis avr. Die meisten dort befindlichen Header wird man nie direkt durch Angabe im C-File erhalten, sondern durch Angabe von
#include <avr/io.h>
Dadurch werden genau die I/O-Header eingebunden, die zum AVR-Modell passen, also z.B. avr/iom8.h für ATmega8 etc. Verantwortlich für die Auswahl des richtigen Sub-Headers ist der Schalter '-mmcu=xxx'.
Obwohl diese Sub-Header nicht explizit angegeben werden müssen, kann ein Blick dorthin hilfreich sein, um die Namen von SFRs oder Signals nachzuschlagen. Diese Header werden im folgenden nicht alle einzeln aufgelistet. Ihre Namen sind immer avr/io*.h.
- für ATmega: avr/iom*.h
- für ATtiny: avr/iotn*.h
avr/boot.h Bootloader Support avr/eeprom.h EEPROM-Routinen avr/interrupt.h sei(), cli(), ISR(), ... avr/io.h RAMEND, ***_vect, SFRs: PORTB, DDRB, PINB, SREG, ..., avr/pgmspace.h Zugriff aufs Flash: Byte lesen, PROGMEM, pgm_read_***, ... avr/sleep.h Power-Safe und Sleep-Modes avr/wdt.h Watchdog util/crc16.h Prüfsumme CRC16 util/delay.h Verzögerungsschleifen für kurze, exakte Verzögerungen util/parity.h Parität util/twi.h I2C
Anwendungs-spezifisch
Eigene Header, die nur innerhalb eigener Projekte gebraucht werden, includet man mit
#include "..."
Auch hier darf man Unterverzeichnisse angeben oder ins übergeordnete Verzeichnis:
#include "../../mein-zeug.h"
Mit der Option -I<path> kann ein Pfad zu den bekannten Include-Pfaden hinzugefügt werden; im obigen Beispiel etwa -I../.. und im Programm dann:
#include "mein-zeug.h"
Optimierungen, Tipps & Tricks
- → Hauptartikel: avr-gcc Optimierungen
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
Code-Beispiele
- Hallo Welt für AVR (LED blinken) - ein erstes Beispiel für avr-gcc
- C-Codebeispiele
- Fallstricke bei der C-Programmierung
Details
Installation (Linux)
Sonstiges
Weblinks
Dokumentation
- Offline
Je nach Distribution wird diese mit offline-Dokumentation als pdf, HTML, etc. ausgeliefert, die dann z.B. in Ordern wie den folgenden befindet:
- <GCC_HOME>/doc/gcc/
- <GCC_HOME>/doc/avr-libc/
- etc.
- Online
- cpp.pdf (470 kByte) - Dokumentation des C-Präprozessors (en)
- AVR Libc: User Manual - Dokumentation zur AVR Libc.
- Binutils: Documentation – Dokumentation der Binutils: Assembler, Linker, ...
- avr-gcc im GCC Wiki – Dokumentation des Application Binary Interface (ABI): Registerverwendung, Calling Conventions, ...
GCC Version | Dokumentation | AVR Options | Release Notes | |
---|---|---|---|---|
Aktuelle Entwicklung | HTML | online | GCC 5 | |
4.9.x | HTML | online | GCC 4.9 | |
4.8.x | HTML | online | GCC 4.8 | |
4.7.x | HTML | online | GCC 4.7 | |
4.6.x | HTML | online | GCC 4.6 | |
4.5.x | HTML | online | GCC 4.5 | |
4.4.x | HTML | online | GCC 4.4 | |
4.3.x | HTML | online | GCC 4.3 | |
3.4.x | HTML | online | GCC 3.4 |
Downloads
- WinAVR-Projekt bei sourceforge.net (en)
- avr-gcc und toolchain als Linux-Paket bei sourceforge.net (en)
- Linuxdistribution_Avr-live-cd
Tipps, Installation
- "Installing the GNU Tool Chain" Hilfe zum Build und Installation von GCC, binutils, etc unter Linux
- Im GCC-Handbuch, siehe Dokumentation.
- www.linuxfocus.org (Artikel) - Tipps zu Build und Installation von avr-gcc, binutils und avr-libc unter Linux
- Rich Neswold: A GNU Development Environment for the AVR Microcontroller
- www.mikrocontroller.net (Foren-Beitrag) - Installation von GCC und Toolchain unter Mac OS X
- www.roboternetz.de (Foren-Beitrag) avrgcc + avrdude installieren
- AVR-Studio & C-Tutorial mit Installationsanleitung
Sonstiges
- Offizielle Homepage von GCC (en)
- GCC in der deutschen Wikipedia
- avr-gcc-Tutorial auf mikrocontroller.net
- Nützliche GCC Runtime-Libary
- Atxmega-C-Tutorial
Autor
--SprinterSB 11:27, 7. Dez 2005 (CET)