Zugriff auf Register
Die AVR-Controller verfügen über eine Vielzahl von Registern. Die meisten davon sind sogenannte Schreib-/Leseregister. Das heißt, das Programm kann die Inhalte der Register sowohl auslesen als auch beschreiben.
Register haben einen besonderen Stellenwert bei den AVR Controllern. Sie dienen dem Zugriff auf die Ports und die Schnittstellen des Controllers. Wir unterscheiden zwischen 8-Bit und 16-Bit Registern. Vorerst behandeln wir die 8-Bit Register.
Einzelne Register sind bei allen AVRs vorhanden, andere wiederum nur bei bestimmten Typen. So sind beispielsweise die Register, welche für den Zugriff auf den UART notwendig sind, selbstverständlich nur bei denjenigen Modellen vorhanden, welche über einen integrierten Hardware UART bzw. USART verfügen.
Die Namen der Register sind in den Headerdateien zu den entsprechenden AVR-Typen definiert. Dazu muss man den Namen der controllerspezifischen Headerdatei nicht kennen. Es reicht aus, die allgemeine Headerdatei avr/io.h einzubinden:
Ist im Makefile der MCU-Typ z. B. mit dem Inhalt atmega8 definiert (und wird somit per -mmcu=atmega8 an den Compiler übergeben), wird beim Einlesen der io.h-Datei implizit ("automatisch") auch die iom8.h-Datei mit den Register-Definitionen für den ATmega8 eingelesen.
Intern wird diese "Automatik" wie folgt realisiert: Der Controllertyp wird dem Compiler als Parameter übergeben (vgl. avr-gcc -c -mmcu=atmega16 [...] im Einführungsbeispiel). Wird ein Makefile nach der WinAVR/mfile-Vorlage verwendet, setzt man die Variable MCU, der Inhalt dieser Variable wird dann an passender Stelle für die Compilerparameter verwendet. Der Compiler definiert intern eine dem mmcu-Parameter zugeordnete "Variable" (genauer: ein Makro) mit dem Namen des Controllers, vorangestelltem __AVR_ und angehängten Unterstrichen (z. B. wird bei -mmcu=atmega16 das Makro __AVR_ATmega16__ definiert). Beim Einbinden der Header-Datei avr/io.h wird geprüft, ob das jeweilige Makro definiert ist und die zum Controller passende Definitionsdatei eingelesen. Zur Veranschaulichung einige Ausschnitte aus einem Makefile:
[...]
# MCU Type ("name") setzen:
MCU = atmega16
[...]
[...]
## Verwendung des Inhalts von MCU (hier atmega16) fuer die
## Compiler- und Assembler-Parameter
ALL_CFLAGS = -mmcu=$(MCU) -I. $(CFLAGS) $(GENDEPFLAGS)
ALL_CPPFLAGS = -mmcu=$(MCU) -I. -x c++ $(CPPFLAGS) $(GENDEPFLAGS)
ALL_ASFLAGS = -mmcu=$(MCU) -I. -x assembler-with-cpp $(ASFLAGS)
[...]
[...]
## Aufruf des Compilers:
## mit den Parametern ($(ALL_CFLAGS) ist -mmcu=$(MCU)[...] = -mmcu=atmega16[...]
$(OBJDIR)/%.o : %.c
@echo
@echo $(MSG_COMPILING) $<
$(CC) -c $(ALL_CFLAGS) $< -o $@
[...]
Da --mmcu=atmega16 übergeben wurde, wird __AVR_ATmega16__ definiert und kann in avr/io.h zur Fallunterscheidung genutzt werden:
// avr/io.h
// (bei WinAVR-Standardinstallation in C:WinAVRavrincludeavr)
[...]
#if defined (__AVR_AT94K__)
# include <avr/ioat94k.h>
// [...]
#elif defined (__AVR_ATmega16__)
// da __AVR_ATmega16__ definiert ist, wird avr/iom16.h eingebunden:
# include <avr/iom16.h>
// [...]
#else
# if !defined(__COMPILING_AVR_LIBC__)
# warning "device type not defined"
# endif
#endif
|
Die Beispiele in den folgenden Abschnitten demonstrieren den Zugriff auf Register anhand der Register für I/O-Ports (PORTx, DDRx, PINx), die Vorgehensweise ist jedoch für alle Register (z. B. die des UART, ADC, SPI) analog.