Allgemeiner Zugriff auf Register

Die AVR-Controller verfügen über eine Vielzahl von Registern. Die meisten davon sind sogenannte Schreib-/Leseregister. Das heisst, das Programm kann die Inhalte der Register auslesen und beschreiben.
Einige Register haben spezielle Funktionen, andere wiederum könne für allgemeine Zwecke (Speichern von Datenwerten) verwendet werden.

Einzelne Register sind bei allen AVR's 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 UART verfügen.

Die Namen der Register sind in den Headerdateien zu den entsprechenden AVR-Typen definiert.
Wenn im Makefile der MCU-Typ definiert ist so bindet das System automatisch die richtige Headerdatei ein.
Man kann der MCU-Typ aber selbstverständlich auch noch in der C-Quelldatei definieren, wenn man Freude daran hat.

I/O-Register

Die I/O-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 mal die 8-Bit Register.

Hinweis: Die folgenden Funktionen erwarten als Argument für das jeweilige Portregister konstante Werte. Am besten verwenden wir die entsprechenden defines aus der Headerdatei <io.h>. Wenn die Portadresse über eine 8-Bit Variable übergeben quittiert der Inline Assembler dies jeweils mit 2 Fehlermeldungen folgender Form:

<Dateiname>:<Zeilennummer>: warning: asm operand 0 probably doesn't match constraints
<Dateiname>:<Zeilennummer>: warning: asm operand 1 probably doesn't match constraints

Offensichtlich läuft das Programm so auch tatsächlich nicht korrekt ab. Wenn wir also Ports über Variablen ansprechen wollen müssen wir auf die Low Level-Funktion __mmio ausweichen.

Lesen eines I/O-Registers

Der gesamte Inhalt eines Registers kann mit dem Befehl

inp(<register>);

ausgelesen werden.

Lesen eines Bits

Die AVR-Bibliothek stellt auch Funktionen zur Abfrage eines einzelnen Bits eines Registers zur Verfügung:

bit_is_set (<port>, <pin>);

Die Funktion bit_is_set prüft, ob ein Bit gesetzt ist. Wenn das Bit gesetzt ist wird ein Wert ungleich 0 zurückgegeben. Genau genommen ist es die Wertigkeit des abgefragten Bits, also 1 für Bit0, 2 für Bit1, 4 für Bit2 etc.

bit_is_clear (<port>, <pin>);

Die Funktion bit_is_clear prüft, ob ein Bit gelöscht ist. Wenn das Bit gelöscht ist, also auf 0 ist, wird ein Wert ungleich 0 zurückgegeben.

Schreiben eines I/O-Registers

Zum Beschreiben eines I/O-Registers verwendet man allgemein den Befehl

outp (<wert>, <register>);

Als Wert muss die Bitmaske mit den Werten aller Pins angegeben werden.

Schreiben eines Bits

Auch für das Setzen bzw. Löschen eines einzelnen Bits eines I/O-Registers stellt die AVR-Bibliothek entsprechende Funktionen zur Verfügung.

sbi (<register>, <bitnummer>);

Die Funktion sbi setzt ein beliebiges Bit eines Registers, das heisst, es wird der logische Wert 1 in das Bit geschrieben. Die anderen Bits des Ports werden nicht verändert.
Das niederwertigste Bit hat die Bitnummer 0.

cbi (<register>, <bitnummer>);

Die Funktion cbi löscht ein beliebiges Bit eines Registers, das heisst, es wird der logische Wert 0 in das Bit geschrieben. Die anderen Bits des Ports werden nicht verändert.
Das niederwertigste Bit hat die Bitnummer 0.

Warten auf einen bestimmten Zustand

Es gibt in der Bibliothek sogar Funktionen, die Warten, bis ein bestimmter Zustand auf einem Bit erreicht ist.
Es ist allerdings normalerweise eine eher unschöne Programmiertechnik.

loop_until_bit_is_set(<register>, <bitnummer>);

Die Funktion loop_until_bit_is_set wartet in einer Schleife, bis das definierte Bit gesetzt ist. Wenn das Bit beim Aufruf der Funktion bereits gesetzt ist wird die Funktion sofort wieder verlassen.
Das niederwertigste Bit hat die Bitnummer 0.

loop_until_bit_is_clear(<register>, <bitnummer>);

Die Funktion loop_until_bit_is_clear wartet in einer Schleife, bis das definierte Bit gelöscht ist. Wenn das Bit beim Aufruf der Funktion bereits gelöscht ist wird die Funktion sofort wieder verlassen.
Das niederwertigste Bit hat die Bitnummer 0.

Speicherbezogener Portzugriff

In der Regel sind die weiter oben erwähnten Funktionen zu bevorzugen. Es kann aber auch sein, dass wird mal eine Stufe tiefer einsteigen müssen. Dann verwenden wir für den Portzugriff das Synonym __mmio.

__mmio(<port>)

Diese Funktion dient dem speicherbasierten Zugriff auf die Ports (Memory Mapped I/O).
Die Funktion kann sowohl zum Lesen als auch zum Schreiben eines Ports verwendet werden.

Um ein einzelnes Bit in einem Port zu setzen bzw. zu löschen kann also ein der folgenden Befehlszeilen verwendet werden:

__mmio (<port>) = __mmio (<port>) | (1 << <bitnummer>)   // Setzt ein Bit
__mmio (<port>) = __mmio (<port>) & ~(1 << <bitnummer>)   // Löscht ein Bit

Die anderen Bits des Ports bleiben unverändert.