7.   Der Kern der AVR-CPU

7.1   Übersicht

In diesem Abschnitt wird die Architektur des AVR-Kerns allgemein beschrieben. Die Hauptfunktion des CPU-Kerns ist, die korrekte Ausführung von Programmen sicherzustellen. Hierfür muss die CPU auf die Speicher zugreifen können, Berechnungen durchführen, die peripheren Funktionen steuern und auf Interrupts reagieren.
Bild 7-1: Blockschaltbild der AVR-CPU-Architektur
Vor dem Hintergrund einer Maximierung der Leistung und der Parallelisierung nutzt der AVR-Controller die Harvard-Architektur mit separaten Speichern und Bussen für Programm und Daten. Der Programmspeicher ist mit einer zweistufigen Pipeline verbunden. D.h., dass während ein Befehl noch abgearbeitet wird, der nächste schon aus dem Programmspeicher „vorausgelesen“ wird. Dies ermöglicht, dass ein Befehl innerhalb eines Taktzyklus bearbeitet wird. Der Programmspeicher besteht aus einem „im-System-programmierbaren“ Flash-Speicher.

Auf alle Register (32 Stück, 8 Bit breit) kann die ALU innerhalb eines Befehles zugreifen. Das heißt, dass während der Abarbeitung eines Befehls zwei Operanden aus den Registern geholt werden können, die dann dem Befehl entsprechend verarbeitet werden und das Ergebnis anschließend wieder in einem Register abgelegt wird. Das ganze geschieht innerhalb nur eines Takt-Zyklus.

Sechs der Register können zu drei 16-Bit-Registern zusammengeschlossen werden, die als Zeiger für den indirekten Speicherzugriff auf den Datenspeicher dienen. Einer der Zeiger (Z) kann auch für die indirekte Adressierung von Speicherzellen im Programmspeicher verwendet werden. Diese Zeiger werden X, Y und Z genannt.

Die ALU unterstützt arithmetische und logische Funktionen, die mit zwei Registern oder mit einem Register und einer Konstante ausgeführt werden können. Befehle, die nur ein Register betreffen, werden natürlich auch in der ALU ausgeführt. Nach der Ausführung arithmetischer Befehle wird das Status-Register aktualisiert und enthält somit weitere Informationen zu den Ergebnissen der Operation.

Der Programmablauf wird durch bedingte und unbedingte Sprünge und Unterprogrammaufrufe unterstützt, die den gesamten zur Verfügung stehenden Programmspeicher adressieren können. Die meisten AVR-Befehle haben ein 16-Bit-Word-Format. Jeder Programmspeicherplatz enthält einen 16- oder 32-Bit-Befehl.

Der Programmspeicher ist in zwei Bereiche unterteilt, dem Urlader-Bereich und den Anwender-Bereich. Beide Bereiche haben eigene Sperrbits, mit denen Schreib- und Lesesperren programmiert werden können. Der SPM-Befehl, der in den Anwender-Bereich schreibt, muss im Boot-Bereich liegen.

Während Interrupt- und Unterprogrammaufrufen wird die Rücksprungadresse des Programzählers PC im Kellerspeicher (Stapelspeicher) gesichert. Der Stapel ist im SRAM realisiert und wird daher in seiner Größe nur durch den zur Verfügung stehenden Platz im SRAM bestimmt. Alle Anwenderprogramme müssen den Stapelzeiger, also den Zeiger auf die nächste freie Stapel-Adresse, im Rahmen der Reset-Routine initialisieren. Der Stapelzeiger ist im I/O-Adressraum hinterlegt und kann sowohl beschrieben als auch gelesen werden. Auf den SRAM kann mit fünf verschiedenen Adressierungsmöglichkeiten zugegriffen werden.

Die flexiblen Interruptmöglichkeiten werden über verschiedene Kontrollregister im I/O-Adressraum und in Verbindung mit einer globalen Interruptfreigabe gehandhabt. Jeder Interrupt hat eine eigene Einsprungadresse. Ferner haben alle Interrupts unterschiedliche Prioritäten. Allgemein gilt, dass die Priorität eines Interrupts umso höher ist, je niedriger seine Einsprungadresse ist.

Der I/O-Adressraum umfasst 64 Adressen, in denen die peripheren Funktionen wie Kontrollregister, SPI und andere I/O-Funktionen hinterlegt sind. Auf den I/O-Adressraum kann direkt zugegriffen werden oder er wird über die Adressen 0x20 bis 0x5F im Datenadressraum adressiert. Ein weiterer, Erweiterter I/O-Adressraum befindet sich im Bereich 0x60 bis 0xFF, bei dem der Zugriff mit mit den ST/STS/STD- und LD/LDS/LDD-Befehlen möglich ist.

7.2   ALU — Arithmetisch-logische Einheit

Die AVR-ALU arbeitet in direkter Verbindung mit den 32 allgemeinen Registern. Innerhalb eines System-Taktes werden die Operationen zwischen zwei Registern in dem Registerspeicher ausgeführt. Die ALU-Operationen können in drei Kategorien unterteilt werden: arithmetische, logische und Bit-bezogene Befehle. Einige Mikrocontroller der AVR-Familie haben einen Hardware-Multiplizierer im arithmetischen Teil der ALU implementiert, mit dem vorzeichenlose, vorzeichenbehaftete und gebrochene Zahlen multipliziert werden können. Siehe Befehlssatz für eine detaillierte Beschreibung.

7.3   Statusregister

Das Status-Register enthält Informationen über das Ergebnis der zuletzt ausgeführten arithemtischen Operation. Diese Informationen können genutzt werden, um alternative Programmabläufe zu gestalten. Zu beachten ist, dass das Status-Register nach jeder Operation der ALU aktualisiert wird, wie in der Befehlsbeschreibung angegeben ist. Dadurch benötigt man in vielen Fällen keine separaten Vergleichsbefehle wodurch schnellere und kompaktere Programme möglich sind.

Das Status-Register wird beim Aufruf einer Interruptroutine nicht automatisch in den Stapel gesichert oder nach dessen Beendigung aus dem Stapel zurückgeholt. Beides muss durch die Interruptroutine sichergestellt werden.

7.3.1   SREG

Bit76543210
(0x5F)ITHSVNZCSREG
ZugriffR/WR/WR/WR/WR/WR/WR/WR/W
Startwert00000000
Das Global Interrupt-Enable-Bit muss gesetzt (auf 1) werden, um alle Interrupts zunächst global freizugeben. Die individuelle Kontrolle der einzelnen Interrupts aus den verschiedenen Interrupt-Quellen erfolgt durch separate Kontroll-Register. Wenn das I-Bit gelöscht ist, sind alle Interrupts unabhängig von den Werten in den anderen Kontroll-Registern gesperrt. Das I-Bit wird beim Auftreten eines Interrupts durch die Hardware gelöscht, so dass weitere Interrupts zunächst nicht ausgeführt werden. Das Bit wird durch einen RETI-Befehl, der eine Interrupt-Routine beendet, automatisch gesetzt, so dass weitere Interrupts wieder ausgeführt werden. Mit den SEI- und CLI-Befehlen kann das I-Bit jederzeit gesetzt und gelöscht werden.

Die Bit-Kopier-Befehle BLD (Bit LoaD) und BST (Bit STore) benutzen das T-Bit als Quelle und Ziel für ihre Operation. Mit dem BST-Befehl kann ein Bit aus einem Register in das T-Bit kopiert werden, mit dem BLD-Befehl wird das T-Bit in ein Bit in einem der Register kopiert.

Das H-Bit signalisiert einen Übertrag vom vierten zum fünften Bit bei einigen arithmetischen Befehlen. Dieses ist nützlich für BCD-Arithmetik. Siehe Befehlsbeschreibung. Das S-Bit ist stets eine Exklusiv-Oder-Verknüpfung zwischen dem Negativ-Flag N und dem Zweierkomplement-Überlaufbit V. Siehe Befehlsbeschreibung. Das Zweierkomplement-Überlaufbit unterstützt die Zweierkomplement-Arithmetik. Siehe Befehlsbeschreibung. Das N-Bit signalisiert ein negatives Ergebnis nach verschiedenen arithmetischen und logischen Befehlen. Siehe Befehlsbeschreibung. Das Z-Bit signalisiert das Ergebnis Null nach verschiedenen arithmetischen und logischen Befehlen. Siehe Befehlsbeschreibung. Das C-Bit signalisiert einen (vorzeichenlosen) Übertrag nach verschiedenen arithmetischen und logischen Befehlen. Siehe Befehlsbeschreibung.

7.4   Allgemeine Register

Die Anordnung der Register ist für die Befehlsausführung der AVR-RISC-Controller optimiert. Um die erforderliche Leistung und Flexibilität zu erreichen, werden folgende Ein-/Ausgabemöglichkeiten bei der Abarbeitung von Befehlen durch die Register-Anordnung unterstützt: Folgendes Bild zeigt die Struktur der 32 allgemeinen Register in der CPU.
Bild 7-2: Allgemeine Register
Allzweck-
Arbeits-
register
70Adresse
R00x00
R10x01
R20x02
...
R130x0D
R140x0E
R150x0F
R160x10
R170x11
R180x12
...
R260x1AX-Register, Low-Byte (XL)
R270x1BX-Register, High-Byte (XH)
R280x1CY-Register, Low-Byte (YL)
R290x1DY-Register, High-Byte (YH)
R300x1EZ-Register, Low-Byte (ZL)
R310x1FZ-Register, High-Byte (ZH)
Die meisten Befehle, die die Register verwenden, können direkt auf diese zugreifen und werden in einem Takt ausgeführt.

Wie in der obigen Abbildung zu sehen, repräsentiert jedes Register auch eine Adresse im Datenspeicher; somit sind alle Register auch im unteren Bereich des Datenspeichers abgebildet. Obwohl die Register nicht physikalisch im SRAM realisiert sind, wird durch diese Organisation des Speichers eine große Flexibilität hinsichtlich des Zugriffs auf die Register erreicht, da diese auch über X, Y und Z indiziert werden können.

7.4.1   Zeigerregister X, Y und Z

Die Register R26 bis R31 haben neben ihren allgemeinen Funktionen noch zusätzliche Funktionen. Diese Register bilden 16-Bit-Zeiger, die für die indirekte Adressierung des Datenspeichers verwendet werden. Die drei Zeiger-Register sind wie folgt definiert:
Bild 7-3: Zeigerregister X, Y und Z
15XHXL0
Registerpaar X7070
R27 (0x1B)R26 (0x1A)
15YHYL0
Registerpaar Y7070
R29 (0x1D)R28 (0x1C)
15ZHZL0
Registerpaar Z7070
R31 (0x1F)R30 (0x1E)
In den verschiedenen Adressierungsarten arbeiten diese Adresszeiger mit festem Versatz, mit automatischem Inkrement oder mit automatischem Dekrement (siehe Befehlsbeschreibung).

7.5   Stapelzeiger

Der Stapel (Stack) wird hauptsächlich benutzt, um temporäre oder lokale Daten zu speichern und um Rücksprungadressen bei der Ausführung von Interruptrouinen und Unterprogrammen zu sichern. Der Stapelzeiger zeigt immer auf die nächste freie Speicherstelle im Stapel. Der Stapel ist so aufgebaut, dass der von hohen Speicheradressen hin zu niedrigen wächst. Das bedeutet, dass beim Sichern eines Datenbytes in den Stapel (PUSH-Befehl) der Stapelzeiger dekrementiert wird.

Der Stapelzeiger zeigt auf eine Speicherstelle im SRAM, diese muss durch das Programm zunächst bestimmt werden, bevor ein erstes Unterprogramm ausgeführt wird oder die Interrupts freigegeben werden. Der Initialisierunswert des Stapelzeigers entspricht der letzten RAM-Adresse. Der Zeiger muss stets größer als der RAM-Anfang sein, siehe Tabelle 8-3.

Siehe folgende Tabelle für Details.

Tabelle 7-1: Befehle mit Stapelzeiger
BefehlStapelzeigerBeschreibung
PUSHVermindert um 1Daten werden im Stapel abgelegt
CALL
ICALL
RCALL
Vermindert um 2Rückkehradresse wird bei Unterprogrammaufruf oder Interrupt im Stapel abgelegt
POPErhöht um 1Daten werden vom Stapel abgeholt
RET
RETI
Erhöht um 2Rückkehradresse wird vom Stapel abgeholt
Der Stapelzeiger besteht aus zwei 8-Bit-Registern im I/O-Adressraum. Die Anzahl der Bits, die wirklich für die Adressierung verwendet werden, hängt von der Größe des zu adressierenden Speicherbereiches ab. In einigen AVR-Controllern ist der Speicherbereich so klein, dass nur das Low-Byte des Stapelzeigers verwendet wird. In diesen Fällen ist das High-Byte gar nicht implementiert.

7.5.1   SPH und SPL

Bit15141312111098
(0x5E)SP15SP14SP13SP12SP11SP10SP9SP8SPH
(0x5D)SP7SP6SP5SP4SP3SP2SP1SP0SPL
Bit76543210
ZugriffR/WR/WR/WR/WR/WR/WR/WR/W
R/WR/WR/WR/WR/WR/WR/WR/W
StartwertRAMENDRAMENDRAMENDRAMENDRAMENDRAMENDRAMENDRAMEND
RAMENDRAMENDRAMENDRAMENDRAMENDRAMENDRAMENDRAMEND
Bei diesen Controllern muss der Stapelzeiger nicht mehr vom Hochlauf-Kode initialisiert werden, im Gegensatz zu älteren AVR-Mikrocontrollern.

7.6   Zeitverhalten

Nachfolgend wird das allgemeine Konzept der Zugriffszeiten bei der Ausführung von Befehlen beschrieben. Die AVR-CPU wird durch den CPU-Takt clkCPU getaktet, der direkt aus der ausgewählten Taktquelle abgeleitet wird, ohne vorher geteilt zu werden.

Die nachfolgende Abbildung zeigt das parallele Auslesen und Ausführung von Befehlen, was durch die Harvard-Architektur und den Direktzugriff auf die Register ermöglicht wird. Aufgrund dieses Konzeptes kann ein Durchsatz von 1 MIPS pro MHz erreicht werden.

Bild 7-4: Zeitablauf beim Befehl lesen und ausführen
Das Timing bei der Abarbeitung eines Befehles zeigt folgende Abbildung. In einem Takt-Zyklus werden bis zu zwei Register gelesen, die Operation in der ALU ausgeführt und das Ergebnis zurück in ein Register geschrieben.
Bild 7-5: Zeitablauf ALU-Operation

7.7   Reset- und Interruptverhalten

Der ATmega8 bietet eine Vielzahl verschiedener Interruptquellen. Diese Interruptquellen haben ebenso wie der Reset-Vektor separate Einsprungadressen im Programmspeicher. Alle Interrupts können separat freigegeben werden, indem sie mit einer logischen 1 in den dazugehörigen Registern beschrieben werden. Außerdem müssen alle Interrupts durch Setzen des Globalen Interrupt-Freigabe-Bits im Status-Register freigegeben sein. Abhängig vom Wert des Programmzähler (PC) werden Interrupts automatsich gesperrt, wenn die Lock-Bits BLB02 oder BLB12 programmiert sind. Dadurch kann eine höhere Softwaresicherheit erreicht werden. Siehe Programmieren des Speichers.

Die unteren Adressen im Programmspeicher sind als Reset- und Interrupt-Vektoren voreingestellt. Die vollständige Liste der Einsprungadressen und der Interrupt-Prioritäten sind bei Interrupts beschrieben. Allgemein gilt, dass die niedrigsten Einsprungadressen die höchste Priorität haben. Somit hat der Reset mit der Einsprungadresse 0x0000 die höchste Priorität. Die nächsthöhere Priorität hat der externe Interrupt 0. Die Interrupt-Vektoren können an den Beginn des Boot-Sektors verschoben werden, indem das Interrupt-Vektor-Auswahl-Bit (IVSEL) im Mikrocontroller-Steuerregister (MCUCR) gesetzt wird. Siehe Interrupts für weitere Infos. Der RESET-Vektor kann ebenfalls zum Anfang des Urlader-Bereichs umgesetzt werden, dies durch Programmieren der BOOTRST-Fuse. Siehe Urlader-Unterstützung.

Wenn ein Interrupt auftritt, wird das Global Interrupt Freigabe Bit (I-Bit) automatisch gelöscht, so dass keine weiteren Interrupts akzeptiert werden. Wenn das I-Bit durch die Software wieder auf 1 gesetzt wird, können weitere Interrupts die laufende Interrupt-Routine unterbrechen. Das I-Bit wird durch das Beenden einer Interrupt-Routine (RETI-Befehl) automatisch wieder gesetzt.

Man unterscheidet grundsätzlich zwei Arten von Interrupts. Die erste wird durch ein Ereignis getriggert und setzt dadurch ein Interrupt-Flag. In diesem Fall wird der Programmzähler mit dem dazugehörigen Interrupt-Vektor geladen um die Interrupt-Routine auszuführen. Gleichzeitig wird das dazugehörige Interrupt-Flag durch die Hardware wieder gelöscht. Das Interrupt-Flag kann aber auch dadurch gelöscht werden, indem eine 1 an die Position des Flags geschrieben wird. Wenn ein Interrupt auftritt, während der dazugehörige Interrupt gesperrt ist, wird das Interrupt-Flag trotzdem gesetzt. Es speichert demnach so lange an das aufgetrete Ereignis, bis der Interrupt freigegeben wird oder das Flag durch die Software gelöscht wird. Das gleiche gilt für Interrupts, die auftreten, während alle Interrupts global gesperrt sind. In diesem Fall werden nach der globalen Freigabe alle zwischenzeitlich aufgetretenen Interrupts entsprechend ihrer Priorität abgearbeitet.

Die zweite Art von Interrupts hat nur für die Dauer des Auftretens ihres auslösenden Ereignisses Gültigkeit. Diese Interrupts haben nicht notwendigerweise ein Interrupt-Flag. Somit wird ein Interrupt nicht ausgeführt, wenn das Ereignis wieder verschwindet, bevor der dazugehörige Interrupt freigegeben wird.

Wenn eine Interrupt-Routine beendet wird, wird das unterbrochene Programm wieder aufgerufen und mindestens ein weiterer Befehl des Programms ausgeführt, bevor ein wartender Interrupt abgearbeitet wird.

Das Status-Register wird beim Aufruf der Interrupt-Routine nicht automatsich gesichert, daher muss dies durch die Software sichergestellt werden.

Wenn der CLI-Befehl verwendet wird, um die Interrupts zu sperren, dann wirkt diese Sperre sofort, so dass keine weiteren Interrupts ausgeführt werden, auch dann nicht, wenn der Interrupt zeitgleich mit dem CLI-Befehl auftritt. Das folgende Beispiel zeigt, wie Interrupts während des Schreibens des EEPROMs vermieden werden.

Beispiel in Assembler
	in	r16, SREG	; SREG retten
	cli			; Interrupts für die zeitlich limitierte Sequenz sperren
	sbi	EECR, EEMPE	; EEPROM schreiben starten
	sbi	EECR, EEPE
	out	SREG, r16	; SREG wiederherstellen (I-Bit)
Beispiel in C
	char cSREG;
	cSREG = SREG;		/* SREG retten */
	_CLI();			/* Interrupts für die zeitlich limitierte Sequenz sperren */
	EECR |= (1<<EEMPE);	/* EEPROM schreiben starten */
	EECR |= (1<<EEPE);
	SREG = cSREG;		/* SREG wiederherstellen (I-Bit) */
Wenn der SEI-Befehl ausgeführt wird, um die Interrupts global freizugeben, dann wird auf jeden Fall erst noch der Befehl nach dem SEI-Befehl ausgeführt, bevor ein wartender Interrupt behandelt wird.
Beispiel in Assembler
	sei			; Globale Interruptfreigabe setzen
	sleep			; Schlafmodus starten und auf Interrupt warten
	; Hinweis: Erst wird der sleep-Befehl ausgeführt, dann Interrupts bedient
Beispiel in C
	__enable_interrupt();	/* Globale Interruptfreigabe setzen */
	__sleep();		/* Schlafmodus starten und auf Interrupt warten */
	/* Hinweis: Erst wird der sleep-Befehl ausgeführt, dann Interrupts bedient */

7.7.1   Interrupt-Reaktionszeit

Die Reaktionszeit für die AVR-Interruptausführung beträgt mindestens 4 Takte. Nach diesen 4 Takten ist der Interruptvektor in den Programmzähler (PC) geladen, der vorherige Inhalt des Programmzählers auf dem Stapel abgelegt, und der Befehl auf dem Interruptvektor kommt zur Abarbeitung. Der Vektor ist normalerweise ein Sprung zur Interruptroutine, welcher weitere 3 Takte benötigt. Tritt die Interruptanforderung während der Ausführung eines Mehrzyklen-Befehls auf, wird der Befehl komplett ausgeführt, bevor die Anforderung bedient wird. Befindet sich der Controller im Schlafmodus, dauert die Reaktion auf die Interruptanforderung 4 weitere Takte länger. Zuzüglich kommen Verzögerungen durch die verschiedenen Hochlaufzeiten des ausgewählten Schlafmodus'.

Die Rückkehr von der Interruptroutine zum unterbrochenen Programm dauert 4 Takte. Während der 4 Takte wird der Programmzähler (PC; 2 Bytes) vom Stapel geholt, der Stapelzeiger um 2 erhöht und das I-Bit in SREG gesetzt.