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.
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.
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
(0x5F) | I | T | H | S | V | N | Z | C | SREG |
---|---|---|---|---|---|---|---|---|---|
Zugriff | R/W | R/W | R/W | R/W | R/W | R/W | R/W | R/W | |
Startwert | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Allzweck- Arbeits- register | 7 | 0 | Adresse | |
R0 | 0x00 | |||
R1 | 0x01 | |||
R2 | 0x02 | |||
... | ||||
R13 | 0x0D | |||
R14 | 0x0E | |||
R15 | 0x0F | |||
R16 | 0x10 | |||
R17 | 0x11 | |||
R18 | 0x12 | |||
... | ||||
R26 | 0x1A | X-Register, Low-Byte (XL) | ||
R27 | 0x1B | X-Register, High-Byte (XH) | ||
R28 | 0x1C | Y-Register, Low-Byte (YL) | ||
R29 | 0x1D | Y-Register, High-Byte (YH) | ||
R30 | 0x1E | Z-Register, Low-Byte (ZL) | ||
R31 | 0x1F | Z-Register, High-Byte (ZH) |
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.
15 | XH | XL | 0 | |||
Registerpaar X | 7 | 0 | 7 | 0 | ||
R27 (0x1B) | R26 (0x1A) | |||||
15 | YH | YL | 0 | |||
Registerpaar Y | 7 | 0 | 7 | 0 | ||
R29 (0x1D) | R28 (0x1C) | |||||
15 | ZH | ZL | 0 | |||
Registerpaar Z | 7 | 0 | 7 | 0 | ||
R31 (0x1F) | R30 (0x1E) |
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.
Befehl | Stapelzeiger | Beschreibung |
---|---|---|
PUSH | Vermindert um 1 | Daten werden im Stapel abgelegt |
CALL ICALL RCALL | Vermindert um 2 | Rückkehradresse wird bei Unterprogrammaufruf oder Interrupt im Stapel abgelegt |
POP | Erhöht um 1 | Daten werden vom Stapel abgeholt |
RET RETI | Erhöht um 2 | Rückkehradresse wird vom Stapel abgeholt |
Bit | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | |||||||||
(0x5E) | SP15 | SP14 | SP13 | SP12 | SP11 | SP10 | SP9 | SP8 | SPH | ||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
(0x5D) | SP7 | SP6 | SP5 | SP4 | SP3 | SP2 | SP1 | SP0 | SPL | ||||||||
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |||||||||
Zugriff | R/W | R/W | R/W | R/W | R/W | R/W | R/W | R/W | |||||||||
R/W | R/W | R/W | R/W | R/W | R/W | R/W | R/W | ||||||||||
Startwert | RAMENDRAMEND | RAMEND | RAMEND | RAMEND | RAMEND | RAMEND | RAMEND
| RAMEND | RAMEND | RAMEND | RAMEND | RAMEND | RAMEND | RAMEND | RAMEND
| |
Bei diesen Controllern muss der Stapelzeiger nicht mehr vom Hochlauf-Kode initialisiert werden, im Gegensatz zu älteren AVR-Mikrocontrollern.
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.
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 Assembler | ||
---|---|---|
sei ; Globale Interruptfreigabe setzen sleep ; Schlafmodus starten und auf Interrupt warten ; Hinweis: Erst wird der sleep-Befehl ausgeführt, dann Interrupts bedient |
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.