Magic Disk 64

home to index to text: MD9311-KURSE-IRQ-KURS_1.1.txt
             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 ! 

Valid HTML 4.0 Transitional Valid CSS!