Unterrichts- und Lernmaterial für Mikrocontroller
Unterrichts- und Lernmaterial fürMikrocontroller

AVR Assembler - Teil 5

Das Statusregister (SREG)

Es ist ein 8-Bit Register; jedem Bit kommt eine bestimmte Bedeutung zu und gibt Auskunft über den Status der CPU (central processing unit) und der ALU (arithmetic logical unit). Die Bedeutung der einzelnen Bits oder Flags kann im jeweiligen Datenblatt des Prozessors nachgeschlagen werden; beim ATmega8515 unter dem Stichwort Statusregister auf S.10 des Datenblattes.

Ganz allgemein kann man sagen, dass

  • Vergleichsbefehle (CP (compare), CPC (compare with carry),  CPI (compare immediate))
  • arithmetische Anweisungen
  • Schiebebefehle (LSL (logical shift left), LSR (logical shift right), ROL (rotate left), ROR (rotate right))
  • logische Anweisungen

die Flags im Statusregister beeinflussen. Welche Flags durch welche Befehle beeinflusst werden, kann im jeweiligen Datenblatt unter dem Stichwort Instruction Set Summary nachgeschlagen werden. Sprungbefehle greifen vielfach auf die Flags im Statusregister zurück, verändern es selbst aber nicht.

Der Inhalt des Statusregisters wird bei einem Interrupt nicht automatisch gesichert und nach Rückkehr aus der ISR nicht wieder hergestellt; das muss der Programmierer selbst veranlassen. Dazu werden zwei Befehle bereitgestellt:

  • PUSH  legt ein Byte auf dem Stapel ab
  • POP     holt ein Byte vom Stapel zurück

Der Programmzähler (program counter oder PC)

Der Programmzähler zeigt auf die Stelle im Quellcode , die gerade abgearbeitet wird. Wenn das Programm keine Sprung- oder Entscheidungsbefehle enthält (auch keine Interrupts), dann wird Zeile für Zeile des Programms abgearbeitet und der Programmzähler entsprechend laufend erhöht.

Bei einem Unterprogrammaufruf mit RCALL wird

  • der Programmzählerstand um eins erhöht,
  • auf dem Stapel abgelegt,
  • die Einsprungadresse des Unterprogrammes (UP) in den PC geladen.

Die Programmzeilen des UP werden abgearbeitet und am Ende wird die auf dem Stapel abgelegte Rücksprungadresse wieder in den PC geladen und das Programm an der Stelle nach dem UP-Aufruf weiter abgearbeitet.

Der Stack oder Stapelspeicher

Der Stack ist ein bestimmter Speicherbereich im SRAM eines Controllers. In diesem Speicherbereich werden Rücksprungadressen des Programmzählers abgelegt, wenn innerhalb eines auszuführenden Programms Unterprogrammaufrufe oder Interrupts eingebaut sind.

Angesprochen wird er über den sogenannten Stack-Pointer. Dieser zeigt immer auf das Ende des SRAM und kann mit

  • low(RAMEND)
  • high(RAMEND)

initialisiert werden. Wird zum Beispiel ein Interrupt ausgelöst und eine ISR ausgeführt, dann muss vorher die Rücksprungadresse auf dem Stack gesichert werden. Am Ende einer ISR, signalisiert durch den Befehl RETI, wird die Rücksprungadresse vom Stack zurückgeholt und das Programm fortgesetzt (s. Kapitel 4 - timer0 und Interrupt - Übung 3).

Der Stack ist nach dem LIFO-Prinzip (last in, first out) organisiert und lässt sich mit einem Zettelkasten vergleichen. Es kann immer nur die zuletzt abgelegte Rücksprungadresse (der oben liegende Zettel) geladen (dem Kasten entnommen) werden. Und - auch das konnte man in Übung 3 sehen - der Stapelspeicher wächst von der höheren Speicheradresse zur niedrigeren oder von oben nach unten. 

Beispiel

Werden in einem Programm zehn verschiedene Interruptroutinen eines ATmega 8515 eingesetzt (er verfügt über 17 verschiedene Typen), dann benötigt er dafür 20 Byte im SRAM (2 Byte pro Interrupt). Von den ursprünglich zur Verfügung stehenden 512 Byte im SRAM stehen dann  nur noch 492 Byte zur Verfügung. Enthält das Programm darüber hinaus diverse Unterprogrammaufrufe (jeder Aufruf benötigt ebenfalls 2 Byte), dann kann der benötigte Speicherplatz für den Stapelspeicher schnell anwachsen, lässt aber bei 512 Byte SRAM noch genügend Platz für die Zwischenspeicherung von Daten.

Nach so viel Theorie ist jetzt entscheidend, welche Auswirkungen all das auf die zukünftige Programmstruktur eines Assemblerprogrammes haben soll. Halten wir fest:

  • Bei Unterprogrammaufrufen oder Interrupts werden das Statusregister und die Universalregister nicht automatisch gesichert. Das muss der Programmierer selbst übernehmen.
  • Der Stack muss zum Programmstart initialisiert werden.

In den folgenden Übungen wird die neue Programmstruktur konsequent aufgebaut.

Übung 1 - Taster und LED

Die zugrunde liegende Schaltung in dieser Übung ist für uns nicht neu; sie besteht aus einem Taster T0 und einer LED. Beide Bauteile befinden sich auf dem STK200 Board, können aber auch separat auf einem Steckbrett nach Schaltskizze aufgebaut werden.

Taster und LED
Aufgaben
  • Entwickle ein Programm, das LED0 oder eine beliebige Anzahl von LEDs aufleuchten lässt, sobald Taster T0 gedrückt wird.
  • Das Programm soll strukturiert sein und die Abschnitte:

Programmkopf,

Hauptprogramm,

Unterprogramme (optional) ausweisen.

Material
  • STK200 Board
  • Programmer
  • Steckernetzteil 5V, 1,5A
  • USB-Kabel
  • alternativ zum STK200 Board:
  • Steckbrett
  • 1x Widerstand, 470 Ohm
  • 1x Widerstand, 10 kOhm
  • 1x LED
  • 1x Taster

Schaltskizze

Abbildung 1 - Der Taster liegt an PD.0, die LED an PB.0 des ATmega8515. Taster T0 und LED0 sind "low active".

Vorbemerkungen zum Programmaufbau

Beginnen wir mit dem Programmkopf. Was man dort alles hineinpackt ist in Teilbereichen Geschmachssache. Bei mir besteht der Programmkopf aus

  • Informationen darüber, was das vorliegende Programm macht und gibt Auskunft über den benutzten Prozessor, die Taktung und anderes.
  • Direktiven, die einzelnen Registern "sprechende Namen" zuordnen.
  • allen oder einigen ausgewählten Interrupthandlern des benutzten Prozessors (wenn man gleich alle Interrupthandler eines Prozessors angibt, erspart man sich später  Schreibarbeit).
  • der Beendigung aller nicht benutzten Interrupts.

Wie das praktisch umgesetzt wird, zeigt das Programm zu dieser Übung. Der Programmkopf kann zukünftig in jedes neues Assemblerprogramm kopiert werden und muss im Einzelfall nur noch um weitere Interrupthandler und/oder Variablennamen erweitert werden, so wie es die Programmanforderung vorsieht.

Das Programm

Abbildung 2 - Im vorliegenden Programm werden alle acht LEDs der Bargrafanzeige angesprochen. Sobald Taster T0 gedrückt wird, gehen einzelne LEDs an. Wird der Taster losgelassen, gehen die LEDs aus.

Wie arbeitet das Programm?

Der Programmkopf endet mit dem Befehl RETI. Alle bis dort getroffenen Festsetzungen gelten zukünftig für alle weiteren Programme und werden ggf. bei anderen Voraussetzungen angepasst bzw. ergänzt.

Bei den Interrupthandlern habe ich nur den Timer0 berücksichtigt; es lassen sich natürlich auch alle anderen Interrupt-Handler hier auflisten. Das ist Geschmackssache und muss jeder für sich entscheiden.

Im RESET-Block wird standardmäßig der Stackpointer eingerichtet und werden die Ein- und Ausgänge festgelegt.

Im MAIN-Block wird hier die Ansteuerung der LEDs bei Tastendruck an T0 definiert. Die beiden Befehle

  • IN hreg_1, PIND
  • ANDI hreg_1, $01

überprüfen, ob Taster T0 gedrückt wurde. Bei nicht gedrücktem Taster liegt am Eingang PIND.0 eine 1, die über den Befehl IN im hreg_1 abgelegt wird. Die logische UND Verknüpfung ergibt nur bei gleichen Eingangsgrößen HIGH am Ausgang ein HIGH oder 1. Das Z-Flag im Statusregister wird gelöscht oder LOW.

Bei gedrücktem Taster (logisch 0) wird dieser Wert mit logisch 1 AND-verknüpft; das Ergebnis ist immer logisch LOW oder 0; gleichzeitig wird das Z-Flag im Statusregister gesetzt oder ist HIGH.

Der BRNE-Befehl überprüft nun das Z-Flag und verzweigt nur dann, wenn es gelöscht (LOW) ist; mit anderen Worten, wenn der Taster T0 nicht gedrückt wurde.

Wie oben bereits besprochen, wurde bei dem Sprung ins Unterprogramm das Statusregister vorher "gerettet". Dies erfolgt über die beiden Befehle

  • IN status, SREG  ;kopiert den Inhalt von SREG ins Register status (R20)
  • PUSH status        ;legt den Inhalt von status auf dem Stack ab

Am Ende des Unterprogramms werden die Daten das Statusregister vom Stack zurückgelesen.

  • POP status              ;liest die zuletzt abgelegten Daten zurück ins Register status (R20)
  • OUT SREG, status  ;schreibt die Daten zurück ins SREG

Was wo im Speicher während des Programmablaufs passiert, lässt sich mit Hilfe des Debuggers genauer untersuchen.

Damit ist die Struktur der Assemblerprogramme für die Zukunft festgelegt.

Was man wissen sollte ...

Boolsche Daten

Die Bits in einem Byte lassen sich mit ihren Nullen und Einsen als eine binäre Zahl interpretieren. Mit acht Bit lassen sich dann alle positiven ganzen Zahlen zwischen 0 und 255 darstellen.

Ordnet man den einzelnen Bits Wahrheitswerte in Form von TRUE und FALSE oder 1 und 0 zu, dann sprechen wir von sogenannten boolschen Daten.

Boolsche Daten lassen sich mit boolschen Operatoren wie AND, OR, NAND, NOR, EXOR und NOT softwaremäßig verknüpfen. Die Ergebnisse der Verknüpfung boolscher Daten mit den genannten Operatoren werden jeweils in einer Wahrheitstabelle zusammengefasst (siehe Grundlagen / Logik-Gatter / Kapitel "Wahrheitstafeln erstellen").

Logische Operatoren wirken immer auf das ganze Byte. Soll ein logischer Operator nur auf bestimmte Bits innerhalb eines Bytes wirken, benutzt man dazu spezielle Bitmasken oder kurz gesagt, man maskiert das Byte.

 

Bits maskieren

Mit dem Operator AND lassen sich Bits in einem Byte maskieren und auf Null setzen.Die Maske muss an den zu maskierenden Stellen Null und an allen anderen Stellen eine 1 haben.

 

Beispiel

Das obere Nibble des Bytes 0b1011 0011 soll maskiert (auf Null gesetzt) werden. Die AND-Maske muss dann 0b0000 1111 sein. Überall, wo in der Maske eine Null steht, wird das Ergebnis auf Null gesetzt, überall wo eine 1 steht, wird das Ergebnis des Bytes übernommen. Das Ergebnis lautet damit: 0b0000 0011.

 

Bits löschen oder auf Null ziehen

Mit dem AND-Operator lassen sich sehr einfach und gezielt einzelne Bits in einem Register löschen oder auf LOW ziehen. Dazu muss in der Maske an der betreffenden Stelle nur eine Null eingetragen werden und alle anderen Stellen auf 1 oder HIGH gesetzt sein.

 

Beispiel

Die Maske 0b1101 1111 = $DF setzt Bit 5 in einem Byte auf Null.

 

Bits umschalten (toggeln)

Mit dem XOR-Operator lassen sich einzelne Bits oder alle Bits in einem Byte umschalten: aus HIGH (1) wird LOW (0) und umgekehrt. An den Stellen, an denen die XOR-Maske eine 1 ausweist, werden die Bits umgeschaltet, dort wo eine 0 steht, passiert gar nichts.

 

Beispiel

Die XOR-Maske 0b1111 1111 = $FF angewendet auf das Byte 0b10101010 ergibt 0b01010101.

 

Bits setzen

Mit dem OR-Operator lassen sich gezielt einzelne Bits in einem Byte setzen. Dazu muss in der OR-Maske an der betreffenden Stelle eine 1 und der Rest 0 gesetzt werden.

 

Beispiel

Die OR-Maske 0b0010 0000 = $20 setzt im Byte 0b0001 0110 das 5. Bit auf 1. Nach der Maskierung hat das Byte die Darstellung 0b0011 0110.

Zusammenfassung

Bit n in einem Byte auf 0 setzen/löschen

n AND 0

schließt Bit n von einer Aktion aus n AND 1 oder n OR 0 oder n XOR 0

Bit n in einem Byte auf 1 setzen

n OR 1

Bit n in einem Byte toggeln/invertieren

n XOR 1

Das Maskieren, Setzen, Löschen und Toggeln von Bits wird in Übung 2 praktisch untersucht.

Übung 2 - Logische Verknüpfungen

In dieser Übung machen wir uns mit den logischen Instruktionen vertraut. Die Bargrafanzeige auf dem STK-200 dient als Anzeige der logischen Ergebnisszustände. Insgesamt sind vier verschiedene Aufgaben zu lösen.

Logische Verknüpfungen
Material
  • STK-200 Board
  • Programmer
  • USB-Verbindungskabel
  • 5V/1,5A Steckernetzteil
Aufgaben
  • Ausgangspunkt: im Grundzustand leuchten die LEDs 1, 2, 4 und 7 der Bargrafanzeige. Dies soll dem Byte 0b1001 0110 entsprechen. Achtung: die LEDs sind active low.
  1. Schreibe ein Programm avr_teil5_2.asm, das folgende Aufgaben erledigt:
  • Im Grundzustand leuchten die LEDs 1, 2, 4 und 7.
  • Maskiere das Ausgabebyte für die LEDs so, dass nur das obere Nibble (Bit 4 - 7) ausgegeben wird, wenn Taster T0 gedrückt wird. Das heißt, die LEDs 1 und 2 werden gelöscht, 4 und 7 bleiben an.
  1. Schreibe bei gleichem Ausgangspunkt drei weitere Programme, die folgende Aufgaben erledigen:
  2. Maskiere das Ausgabebyte so, dass nur der Wert des 2. Bits ausgegeben wird, wenn Taster T0 gedrückt wird.
  3. Maskiere das Ausgabebyte so, dass das dritte Bit zusätzlich gesetzt wird, wenn Taster T0 gedrückt wird.
  4. Maskiere das Ausgabebyte so, dass das obere Nibble getoggelt wird, wenn Taster T0 gedrückt wird und der Rest sich nicht verändert.

Bevor es ans Programmieren geht, sind zunächst ein paar Dinge im Datenblatt des Prozessors ATmega8515 nachzuschlagen. Beantworte zunächst die folgenden Fragen:

  • Welche logischen Instruktionen kann der ATmega8515 verarbeiten?
  • Worin unterscheiden sich die Befehle?
  • Welche Statusregister werden von diesen Befehle verändert?

Die Antworten findest du am Ende dieses Kapitels.

Das Programm avr_teil5_2.asm zu Teilaufgabe 1

Abbildung 3 - Sobald Taster T0 gedrückt wird, werden die LEDs im unteren Nibbel ausgeschaltet.

Ausgabe auf der Bargrafanzeige

Abbildung 4 - Das Board wurde um 90° im Uhrzeigersinn gedreht. Das MSB befindet sich links, das LSB ganz rechts.

Das Programm avr_teil5_3.asm für die zweite Teilaufgabe

Bit2 wird AND-maskiert; dazu muss eine Programmzeile im Hauptprogramm geändert werden, der Rest bleibt gleich.

Abbildung 5 - LDI hreg_1, $04 ist die geänderte Programmzeile für diese Teilaufgabe.

Ausgabe auf der Bargrafanzeige

Abbildung 6 - Wird Taster T0 gedrückt, leuchtet nur LED2 auf.

Das Programm avr_teil5_4.asm für die dritte Teilaufgabe

Bit3 muss OR-maskiert werden; dazu werden zwei Programmzeilen im Hauptprogramm geändert.

Abbildung 7 - LDI hreg_1, $08 und ORI hreg_1, leds sind die geänderten Programmzeilen.

Ausgabe auf der Bargrafanzeige

Abbildung 8 - Wird T0 gedrückt, leuchtet zusätzlich LED3 auf.

Das Programm avr_teil5_5.asm für die vierte Teilaufgabe

Das Byte wird EOR-maskiert. Insgesamt werden drei Programmzeilen geändert/ausgetauscht.

Ausgabe auf der Bargrafanzeige

Abbildung 9 - Wird T0 gedrückt, toggelt das obere Nibble der Bargrafanzeige, das unter Nibble bleibt fix.

Wie arbeiten die Programm avr_teil5_2-5.asm?

Der Programmkopf und das Reset-Modul wurden bereits früher besprochen. Änderungen wurden dort nicht vorgenommen. Kommen wir also zum Hauptprogramm.

Wird der Taster nicht gedrückt, dann verzweigt das Programm zum Unterprogramm an der Adresse T0_offen. Das Statusregister wird gesichert und mit

  • LDI hreg_1, leds

werden die LEDs in der Bargrafanzeige angesprochen, die später leuchten sollen; es sind LED1, LED2, LED4 und LED7. Steht die 1 für LED AN, dann sähe das Binärmuster so aus: 0b1001 0110. Dieser Binärzahl entspricht die Hexadezimalzahl $96. Über die Direktive .EQU wurde genau dieser Wert der Variablen leds im Programmkopf zugewiesen.

Mit der Instruktion

  • COM hreg_1

wird das Komplement des Registerinhalts von hreg_1 gebildet. Aus Einsen werden Nullen und umgekehrt. Dieser Befehl muss sein, da die LEDs low-active sind und die 1 bei unseren bisherigen Betrachtungen für eine eingeschaltete LED stand.

Überträgte man die Bitfolge von hreg_1 jetzt an das Ausgangsregister PORTB, dann leuchten die entsprechenden LEDs 7, 4, 2 und 1.

Aus dem Unterprogramm wird anschließend mit RJMP zurückgesprungen in das Hauptprogramm main.

 

Wird der Taster T0 gedrückt, dann macht das Programm weiter bei der Instruktion

  • LDI hreg_1, $F0

Die Binärzahl 0b1111 0000 (Bitmaske) wird in das Register hreg_1 geladen und mit dem Inhalt von leds über

  • ANDI hreg_1, leds

logisch-AND maskiert. Das Ergebnis wird in hreg_1 abgelegt.

Abbildung 10 - hreg_1 und leds werden logisch-AND miteinander verknüpft. Das Ergebnis steht in hreg_1.

Die folgende Instruktion COM berücksichtigt, dass die LEDs low-active sind und bildet das Komplement zur Binärzahl 0b1001 0000; das ist 0b0110 1111. Dieses Byte wird an den Ausgangsport PORTB übergeben. Überall dort, wo eine 0 steht, leuchtet die entsprechende LED, dort wo eine 1 steht, ist sie aus.

Zweite Teilaufgabe

Das zweite Bit muss logisch-AND maskiert werden.

  • LDI hreg_1, $04
  • ANDI hreg_1, leds

Alles Weitere ist bekannt.

 

Dritte Teilaufgabe

Das dritte Bit muss logisch-OR maskiert werden, weil es zusätzlich zu den bereits bestehenden Bits gesetzt werden soll. Das wird erledigt durch

  • LDI hreg_1, $08
  • ORI hreg_1, leds

 

Vierte Teilaufgabe

Das obere Nibbel soll bei Tastendruck getoggelt und das untere beibehalten werden. Das Byte muss logisch-EXOR maskiert werden. Das erledigt der Befehl EOR, der nur über zwei Register angesprochen werden kann. Deshalb wird ein kleiner Umweg im Programm genommen. Die Maske und das Byte leds werden vorher in je ein Hilfsregister abgelegt und anschließend als Argument für die Instruktion EOR benutzt.

  • LDI hreg_1, $F0
  • LDI hreg_2, leds
  • EOR hreg_1, hreg_2

Antworten zu den Fragen aus Übung 2

1. Es werden fünf logische Instruktionen bereitgestellt: AND, ANDI, OR, ORI und EOR.

2. Die Befehle unterscheiden sich in der Art, welche Daten miteinander verknüpft werden können. Die Instruktionen AND, OR und EOR führen logische Verknüpfungen von zwei Registerinhalten durch, während die Instruktionen ANDI und ORI den Registerinhalt mit einer Konstanten logisch verknüpfen.

3. Es werden drei Statusregister beeinflusst: Z, N und V.

Kommentar schreiben

Kommentare

  • olaf (Sonntag, 10. Dezember 2017 08:35)

    wow...die mit Abstand beste Site, die ich bisher zu diesem Thema gesehen habe :-))
    Vielen Dank dafür. Hilft super beim Erklären!!

  • David Eisenblätter (Sonntag, 11. Februar 2018 13:38)

    Sehr schön erklärt, und zufällig heute, wo ich nochmal nach einer detailierten Gedankenstütze diesbezüglich gesucht habe!

    Vielen vielen Dank!

    David.

Bitte geben Sie den Code ein
* Pflichtfelder
Druckversion Druckversion | Sitemap
© Reinhard Rahner - Gettorf