Interrupt-Kurs "Die Hardware ausgetrickst..." (Teil 1)
Wer kennt sie nicht, die tollen Effekte, die die großen Alchimisten der Programmierkunst aus unserem guten alten " Brotkasten" herauszaubern: mehr als 8 Sprites gleichzeitig auf dem Bildschirm, Sideund Topborderroutinen, die die
Grenzen des Bildschirmrahmens sprengen, sich wellenförmig bewegende Logos, Grafikseiten, die sich in atemberaubender
Geschwindigkeit zusammenfalten, sowie
schillernd bunte 256- farbige Bilder. Man
könnte diese Liste noch bis ins Unendliche weiterführen und sie würde dennoch
die vollständig sein. In der Tat - was
manche Programmierer aus den Silizuimmolekülen der Hardware des C64 herauskitzeln hat schon so manch Einen zum Stau- nen gebracht. Hätte Commodore zur Markteinführung des C64 schon gewusst, was
für eine Wahnsinnsmaschine sie da gebaut
hatten, hätten sich die Entwickler wahrscheinlich selbst an den Kopf gefasst.
All diese Effekte sind nämlich absolut
unbeabsichtigt unserer " Dampfmaschine" der Computerwelt eingepflanzt worden, sondern verdanken ihre Existenz einzig
und allein der unermüdlichen Ausdauer
der Freaks und Coder, die in nächtelanger Herumtüftelei die Software dazu entwickelten, die den C64 weitaus länger
leben ließ, als man je geglaubt hätte.
" Rasterinterrupt" heißt das Zauberwort
das, einem " Sesam öffne Dich" gleich, dem Programmierer das Tor zur wunderbaren Welt der Computer-Effekte aufstößt.
Und um genau diese Effekte soll sich in
diesem Kurs alles drehen. Die nächsten
Monate sollen Sie hier erfahren, wie man
sie programmiert, und wir werden versuchen Ihnen das Grundwissen zu vermitteln, neue Routinen selbst entwickeln zu können.
Im ersten Teil dieses Kurses wollen wir
uns nun zunächst um die Grundlagen kümmern, und den Fragen " Was ist ein Interrupt?"," Wo kommt er her?" und " Was passiert während eines Interrupts?" auf den
Grund gehen. Leider müssen wir Sie auch
darauf hinweisen, daß zur Programmierung
von Interrupts die gute Kenntnis der
Maschinensprache sowie des Befehlssatzes
des 6510- Prozessors ( so wie er im 64 er
seinen Dienst tut) Grundbedingung ist.
Desweiteren sollten Sie einen guten
Speichermonitor, bzw. Disassembler zur
Hand haben, da wir der Einfachheit halber alle Programmbeispiele als direkt
ausführbaren Code der Magic Disk beifügen werden. Ein gutes Hilfsmittel dieser
Art ist z. B. der, mittlerweise sehr weit
verbreitete," SMON" .
1) WAS SIND INTERRUPTS?
Das Wort " Interrupt" kommt aus dem Englischen und bedeutet " Unterbrechung" .
Und nichts anderes tut nun ein Interrupt: er unterbricht den Prozessor bei
seiner momentanen Arbeit. Das tut er
natürlich nicht einfach aus heiterem
Himmel. Vielmehr hat der Programmierer
die Möglichkeit bestimmte Bedingungen
anzugeben, die einen Interrupt auslösen
sollen. Auf diese Weise kann man ganz
genau den Zeitpunkt bestimmen, zu dem
ein Interrupt auftreten soll. Nun wird
der Prozessor jedoch nicht nur einfach
angehalten, wenn er eine Interruptanforderung bekommt. Da das Ganze ja auch
einen Nutzen haben soll, kann natürlich
ein ganz bestimmtes Programm von ihm
abgearbeitet werden, das auf das Interruptereignis reagieren soll.
Bevor wir uns jedoch in diese Dinge
stürzen, wollen wir erst einmal klären, welche Interrupts der 6510 versteht. Er
kennt insgesamt vier Unterbrechungstypen, die von ihm jeweils unterschiedlich behandelt werden. Die Unterscheidung
wird ihm schon durch seinen hardwaremäßigen Aufbau ermöglicht. Er verfügt nämlich über drei Eingangsleitungen, die
die entsprechenden Interrupts bezeichnen
( der vierte Interrupt ist ein besonderer, den wir weiter unten besprechen
werden) . Die Chips um den Prozessor herum sind nun mit diesen Interrupt-Leitungen verbunden, und können ihm so
das Eintreten eines Unterbrechungsereignisses mitteilen. Sie sehen also, daß
Interrupts hardwaremäßig ausgelöst werden. Um nun eine Unterbrechung zu einem
bestimmten Zeitpunkt auftreten lassen zu
können, sollte man sich ebenfalls in der
Programmierung der Hardware auskennen.
Und diese wollen wir in diesem Kurs
natürlich auch ansprechen.
Kommen wir nun jedoch zu den vier Interrupttypen. Der Erste von ihnen ist wohl
Einer der einfachsten. Es handelt sich
um den " RESET", der von keinem externen Chip, sondern vielmehr von einem " externen" Menschen ausgelöst wird. Er dient
dazu, den Computer wieder in einen Normalzustand zu versetzen. Da der Prozessor selbst nicht merkt, wann er Mist
gebaut hat und irgendwo hängt, muß ihm
der Benutzer das durch Drücken eines
RESET-Tasters mitteilen. Hieraufhin
springt er dann automatisch in ein spezielles Programm im Betriebssystem-ROM
und stellt den Einschaltzustand des
Rechners wieder her. Ein Reset-Taster
ist demnach also direkt mit dem Reset-Interrupteingang des Prozessors verbunden.
Der zweite Interrupttyp heißt " NMI" und
kann ausschließlich von CIA-B des C64 ausgelöst werden. Dies ist ein spezieller Chip, der die Kommunikation zwischen
externen Geräten und 6510 ermöglicht.
Desweiteren ist die ' RESTORE'- Taste direkt mit dem NMI-Eingang des Prozessors
verbunden. Drückt man sie, so wird eben- falls ein NMI ausgelöst. Auch hier
springt der 6510 automatisch in eine
spezielle Routine des ROMs und führt ein
NMI-Programm aus. Es prüft, ob zusätzlich auch noch die " RUN/ STOP"- Taste
gedrückt wurde und verzweigt bei positiver Abfrage in die Warmstartroutine des
BASIC-Interpreters.
Der dritte Interrupt ist der wohl am
häufigsten benutzte. Er heißt " IRQ" und
wird von CIA-A, dem Zwilling von CIA-B, sowie dem Videochip ( VIC) ausgelöst.
Gerade weil der VIC diesen Interrupt
bedient, ist dies derjenige, mit dem wir
in diesem Kurs am meisten arbeiten werden.
Der vierte und letzte Interrupt, ist
derjenige unter den vieren, der keine
Leitung zum Prozessor hat. Das liegt
daran, daß er ein softwaremäßiger Interrupt ist, mit dem sich der Prozessor
quasi selbst unterbricht. Er wird aus- gelöst, wenn der 6510 die " BRK"- Anweisung ausführen soll. Demnach heißt
er auch " BRK"- Interrupt. Er ist eine
sehr einfache Unterbrechung, die eigentlich sehr selten verwendet wird, da sie
ja viel bequemer durch ein " JMP" oder
" JSR" ersetzt werden kann. Dennoch kann
er von Nutzen sein, z. B. wenn man einen
Debugger programmiert. Ebenso kann über
den BRK z. B. in einen Speichermonitor
verzweigt werden, der so die Register
oder einen bestimmten Speicherbereich zu
einem bestimmten Punkt im Programm anzeigen kann. Er unterscheidet sich von
den anderen Interrupts lediglich darin, daß er softwaremäßig ausgelöst wird.
2) IRQ UND NMI - EIN UNGLEICHES PAAR Diese beiden Interrupttypen sind für uns
die Interresantesten, da mit Ihnen Unterbrechungsereignisse der Hardware abgefangen werden, die wir ja exzessiv
programmieren werden. Außer, daß sie beide verschiende Quellen haben, unterscheiden Sie sich auch noch in einem
weiteren Punkt: während der NMI IMMER
ausgelöst wird, wenn ein Unterbrechungsereignis eintritt, kann der IRQ " maskiert", bzw." abgeschaltet" werden. Dies
geschieht über den Assemblerbefehl
" SEI", mit dem das Interruptflag des
Statusregisters gesetzt wird. Ist dieses
Flag nun gesetzt, und es tritt ein IRQ-Interruptereignis ein, so ignoriert der
Prozessor schlichtweg das Auftreten dieser Unterbrechung. Dies tut er solange, bis er durch den Asseblerbefehl " CLI" die Instruktion erhält, das Interrupt-Flag wieder zu löschen. Erst dann reagiert er wieder auf eintretende Unterbrechungen. Dies ist ein wichtiger Umstand, den wir auch später bei unseren
IRQ-Routinen beachten müssen.
3) INTERRUPTVEKTOREN - WEGWEISER FÖR DEN PROZESSOR
Was geschieht nun, wenn der 6510 eine
der oben genannten Interruptanforderungen erhält? Er soll also seine momentane Arbeit unterbrechen, um in die
Interrupt-Routine zu springen. Damit er
Nach Beendingung des Interrupts wieder
normal fortfahren kann muß er jetzt einige Schitte durchführen:
1) Ist der auftretende Interrupt ein
BRK, so wird zunächst das dazugehörige Flag im Statusregister gesetzt.
2) Anschließend werden Highund Lowbyte
( in dieser Reihenfolge) des Programmzählers, der die Speicheradresse
des nächsten, abzuarbeitenden Befehls
beinhaltet, auf den Stack geschoben.
Dadurch kann der 6510 beim Zurückkehren aus dem Interrupt wieder die
ursprüngliche Programmadresse ermitteln.
3) Nun wird das Statusregister auf den
Stack geschoben, damit es bei Beendingung des Interrupts wiederherge- stellt werden kann, so daß es denselben Inhalt hat, als bei Auftreten der
Unterbrechung.
4) Zuletzt holt sich der Prozessor aus
den letzten sechs Bytes seines Adressierungsbereiches ( Adressen $ FFFA-$ FFFF) einen von drei Sprungvektoren.
Welchen dieser drei Vektoren er auswählt hängt von der Art des Interrupts ab, der ausgeführt werden soll.
Hierzu eine Liste mit den Vektoren:
Adresse Interrupt Sprungadr.
$FFFA/$FFFB NMI $FE43 $FFFC/$FFFD RESET $FCE2 $FFFE/$FFFF IRQ/BRK $FF48
Da diese Vektoren im ROM liegen, sind
sie schon mit bestimmten Adressen vorbelegt, die die Betriebssystem-Routinen
zur Interruptverwaltung anspringen. Wir
wollen nun einmal einen Blick auf diese
Routinen werfen. Da der RESET keine für uns nützliche Unterbrechung darstellt, wollen wir ihn jetzt und im Folgenden
weglassen. Kommen wir zunächst zu der
Routine für die IRQ/ BRK-Interrupts. Wie
Sie sehen haben diese beiden Typen denselben Sprungvektor, werden also von der
selben Service-Routine bedient. Da diese
jedoch auch einen Unterschied zwischen
den beiden machen soll hat sie einen
speziellen Aufbau. Hier einmal ein ROM-Auszug ab der Adresse $ FF48 :
FF48: PHA ;Akku auf Stapel FF49: TXA ;X in Akku und FF4A: PHA ; auf Stapel FF4B: TYA ;Y in Akku und FF4C: PHA ; auf Stapel FF4D: TSX ;Stackpointer nach FF4E: LDA $0104,X ;Statusreg. holen FF51: AND #$10 ;BRK-Flag ausmas- kieren FF53: BEQ $FF58 ;Nicht gesetzt also überspringen FF55: JMP ($0316) ;Gesetzt, also Öber Vektor $0316/$0317 springen FF58: JMP ($0314) ;Öber Vektor $0314/ $0315 springen
Dieses kleine Programm rettet zunächst
einmal alle drei Register, indem Sie sie
einzeln in den Akku holt und von dort
auf den Stack schiebt ($ FF48-$ FF4 C) .
Dies ist notwendig, da in der Interruptroutine die Register je ebenfalls benutzt werden sollen, und so die ursprünglichen Inhalte beim Verlassen des Interrupts wiederhergestellt werden können
( durch umgekehrtes zurücklesen) . Ab
Adresse $ FF4 D wird nun eine Unterscheidung getroffen, ob ein BRKoder IRQ-Interrupt aufgetreten ist. Ist ersteres
nämlich der Fall, so muß das BRK-Flag im
Statusregister, das bei Auftreten der
Unterbrechung vom Prozessor auf den
Stack geschoben wurde, gesetzt sein.
Durch Zugriff auf den Stack, mit dem
Stackpointer als Index in X, wird nun das Statusregister in den Akku geholt.
Dies ist übrigens die Einzige Methode, mit der das Statusregister als Ganzes
abgefragt werden kann. Natürlich geht
das immer nur aus einem Interrupt heraus. Das BRK-Flag ist nun im 4 . Bit des
Statusregisters untergebracht. Durch
eine UND-Verknüfung mit diesem Bit kann
es aus dem Statusregisterwert isoliert
werden. Ist der Akkuinhalt nun gleich
null, so war das BRK-Flag nicht gesetzt
und es muß daher ein IRQ vorliegen. In
dem Fall wird auf den JMP-Befehl an
Adresse $ FF58 verzweigt. Im anderen Fall
wird der JMP-Befehl an Adresse $ FF55 ausgeführt.
Wie Sie sehen springen diese beiden Befehle über einen indirekten Vektor, der
beim IRQ in den Adressen $0314/$0315, beim BRK in $0316/$0317 liegt. Da diese
Vektoren im RAM liegen, können Sie auch
von uns verändert werden. Das ist also
auch der Punkt, an dem wir unsere Interrupts " Einklinken" werden. Durch die Belegung dieser Vektoren mit der Adresse
unser eigenen Interruptroutine können
wir den Prozessor also zu dieser Routine
umleiten.
Werfen wir nun noch einen Blick auf die
NMI-Routine, die durch den Vektor bei
$ FFFA/$ FFFB angesprungen wird. Sie ist
noch einfacher aufgebaut und besteht
lediglich aus zwei Befehlen:
FE43: SEI ;IRQs sperren FE44: JMP ($0318) ;Öber Vektor $0318/ $0319 springen
Hier wird lediglich der IRQ geperrt und
anschließend über den Vektor in $0318/$0319 gesprungen. Die Akku, Xund Y-Regsiter werden NICHT gerettet. Das muß
unsere Interruptroutine selbst tun.
Zusammenfassend kann man also sagen, daß
beim Auslösen eines Interrupts jeweils
über einen der drei Vektoren, die im Bereich von $0314-$0319 stehen, gesprungen wird. Der angesprungene Vektor ist
dabei interruptabhängig. Hier nochmal
eine Öbersicht der Interruptvektoren im
genannten Bereich:
Adressen Interrupt Zieladr.
$0314/$0315 IRQ $EA31 $0316/$0317 BRK $FE66 $0318/$0319 NMI $FE47
Diese Vektoren werden bei einem Reset
mit Standardwerten vorbelegt. Hierbei
wird dann die jeweilige Standardroutine
des Betriebssystems angesprungen, die
den entsprechenden Interrupt bearbeiten
soll. Möchten wir eigene Interrupts einbinden, so müssen wir lediglich die
Zieladresse der Vektoren auf den Anfang
unserer Routine verbiegen.
4) PROGRAMMBEISPIELE
Um das Prinzip des Einbindens eines eigenen Interrupts kennenzulernen, wollen
wir nun einmal einen eigenen Interrupt
programmieren. Es handelt sich dabei um
den einfachsten von allen, den BRK-Interrupt. Hier einmal ein Programmlisting, daß Sie als ausführbares Programm
auch auf der Rückseite dieser MD unter
dem Namen " BRK-DEMO1" finden. Sie müssen
es absolut ( mit ",8,1") in den Speicher
laden, wo es dann ab Adresse $1000( dez.
4096) abgelegt wird. Gestartet wird es
mit " SYS4096" . Sie können es sich auch
mit Hilfe eines Disassemblers gerne einmal ansehen und verändern:
;*** Hauptprogramm 1000: LDX #$1C ;BRK-Vektor auf 1002: LDY #$10 ; $101C verbiegen. 1004: STX $0316 1007: STY $0317 100A: BRK ;BRK auslösen 100B: NOP ;Füll-NOP 100C: LDA #$0E ;Rahmenfarbe auf dez.14 100E: STA $D020 ; zurücksetzen 1011: LDX #$66 ;Normalen BRK-Vektor 1013: LDY #$FE ; ($FE66) wieder 1015: STX $0316 ; einstellen 1018: STY $0317 101B: RTS ;Und ENDE! ;*** Interruptroutine 101C: INC $D020 ;Rahmenfarbe hochzählen 101F: LDA $DC01 ;Port B lesen 1022: CMP #$EF ;SPACE-Taste gedrückt? 1024: BNE LOOP ;Nein, also Schleife 1026: PLA ;Ja, also Y-Register 1027: TAY 1028: PLA ;X-Register 1029: TAX 102A: PLA ;u.Akku v.Stapel holen 102B: RTI ;Interrupt beenden.
In den Adressen $1000-$100 A setzen wir
zunächst einmal Lowund Highbyte des
BRK-Vektors auf Adresse $101 C, wo unsere
BRK-Routine beginnt. Direkt danach wird
mit Hilfe des BRK-Befehls ein Interrupt ausgelöst, der, wie wir ja mittlerweile
wissen, nach Retten der Prozessorregister über den von uns geänderten BRK-Vektor auf die Routine ab $101 C springt.
Selbige tut nun nichts anderes, als die
Rahmenfarbe des Bildschirms um den Wert
1 hochzuzählen, und anschließend zu vergleichen, ob die ' SPACE'- Taste gedrückt
wurde. Ist dies nicht der Fall, so wird
wieder zum Anfang verzweigt, so daß
ununterbrochen die Rahmenfarbe erhöht
wird, was sich durch ein Farbschillern
bemerkbar macht. Wird die ' SPACE'- Taste
nun endlich gedrückt, so kommen die folgenden Befehle zum Zuge. Hier holen wir
die Prozessorregister, die von der Betriebssystem- Routine in der Reihenfolge
Akku, X, Y auf dem Stack abgelegt wurden, wieder umgekehrt zurück. Das abschließende " RTI" beendet den Interrupt.
Diese Answeisung veranlasst den Prozessor dazu, den alten Programmzähler, sowie das Statusregister wieder vom Stapel
zu holen, und an die Stelle im Hauptpro- gramm zurückzuspringen, an der der BRK
ausgelöst wurde. Dies ist logischerweise
der Befehl direkt nach dem BRK-Kommando.
So sollte man normalerweise denken, jedoch nimmt BRK eine Sonderstellung diesbezüglich ein. Bei IRQs und NMIs wird
tatsächlich der Befehl nach dem zuletzt
bearbeiten wieder ausgeführt, jedoch
wird beim BRK der Offset 2 auf den Programmzähler hinzuaddiert, weshalb nach
Beendigung des Interrupts ein Byte hinter den BRK-Befehl verzweigt wird. Demnach dient der NOP-Befehl, der nach dem
BRK kommt, lediglich dem Angleichen an
die tatsächliche Rücksprungadresse. Er
wird nie ausgeführt, da der Prozessor ja
an Adresse $100 C weiterfährt. Hier nun
setzen wir die Rahmenfarbe wieder auf
das gewohnte Hellblau und geben dem
BRK-Vektor wieder seine Ursprüngliche
Adresse zurück. Würden wir das nicht
tun, so würde beim nächsten BRK-Befehl, der irgendwo ausgeführt wird, automatisch wieder zu unserem Bildschirmflak- kern verzweigt werden. Hier jedoch würde
der Computer unter Umständen nicht mehr
aus dem Interrupt zurückkehren können, weil wir ja nicht wissen, welche Befehle
hinter dem auslösenden BRK standen ( wenn
es nicht der unseres Programmes an
Adresse $100 A war) .
LADEN SIE NUN DEN 2. TEIL DES KURSES !