Interrupts

Für diesen Beitrag wurde das CBM prg Studio verwendet.
weitersagen ...
Tweet about this on TwitterShare on FacebookShare on Google+Share on LinkedIn

CBM prg StudioSTOP! Unterbrechung!

Damit wir bei unserem Spiel ‚L.O.V.E.‚ mit dem Schritt ‚Landung 002‚ weitermachen können. Gibt es hier eine erste kleine Einführung zum Thema Interrupts. Die Unterbrechungen machen das Programmiererleben erst so richtig schön 😉 und nur durch sie könnt ihr dem C64 all die schönen Effekte entlocken, die ihr aus den Spielen und besonders aus Demos kennt.

Dieser Beitrag behandelt allgemeine Grundlagen und wir programmieren unseren ersten einfachen IRQ. Im nächsten Beitrag schauen wir uns dann die NMIs an und vergleichen diese nochmal mit den IRQs. Anschließend kommen wir dann endlich zum Rasterzeileninterrupt (wohl der wichtigste IRQ für Spiele und Demos).

 

Was sind Interrupts?

Computer (zumindest Systeme, die nur eine CPU mit einem Kern besitzen, wie der C64) können nur eine Aufgabe zur Zeit bewältigen. Wir haben aber den Eindruck, sie würden mehrere Sachen gleichzeitig erledigen. Da diese Aufgaben so schnell ausgeführt werden, sieht es so aus, als geschehen sie gleichzeitig. So blinkt z. B. der Cursor unablässig, die Tastatur wird abgefragt und die interne Uhr des C64 läuft auch mehr oder weniger korrekt 😉 .

Genau hier kommen jetzt die Interrupts zu ihrem Einsatz. Treten bestimmte Ereignisse ein, die man teilweise auch selbst definieren kann, dann unterbricht der C64 das aktuelle Programm, springt zur Adresse des Interrupts, arbeitet den Interrupt ab und kehrt schließlich zum ursprünglichen Programm zurück. So gibt es z. B. einen Interrupt, der dafür sorgt, dass der Cursor blinkt, die Tastatur abgefragt und die Uhr weitergestellt wird.

Dabei müssen wir zwei Arten von Interrupts (Unterbrechungen) unterscheiden:

  • IRQ (InterruptReQuest): Diese stellen eine Anfrage für einen Interrupt dar. Diese Anfrage kann von uns mit  SEI und CLI  abgelehnt oder erlaubt werden. Warum man Interrupts verhindern möchte kann verschiedene Gründe haben, habt ihr z. B. einen sehr ‚friggeligen‚ Rasterzeileninterrupt programmiert, würde ein anderer Interrupt evtl. euer Timing zerstören. Einen weiteren Grund sehen wir gleich im Beispielprogramm.
     
  • NMI (Non Maskable Interrupt): Diese Interrupts werden immer ausgeführt und lassen sich nicht ablehnen (unterdrücken). Diese Interrupts verwendet man z. B. häufig für die Datenübertragung. Damit dort alles reibungslos läuft, wird über einen NMI alles im Takt gehalten. NMIs werden im nächsten Beitrag etwas ausführlicher behandelt.

 

Führen wir uns Interrupts mal an Hand eines Beispiels aus dem Alltag vor Augen. Mir hat die Erklärung aus dem 64‘er Assemblerkurs bzw. aus ‚Assembler ist keine Alchemie‚ sehr gefallen, also versuche ich es mit einer etwas modernisierten Version:

  • Stellt euch vor, ihr schaut gerade eins meiner Videos auf You-Tube (dies steht für das Hauptprogramm), dann klingelt euer Handy.
     

    • Ihr unterbrecht das Schauen des Videos und geht ans Handy (ein ‘normaler‚ Interrupt), ihr wollt gerade von dem Video berichten, da tönt euer Rauchmelder im Flur.
       

      • Den könnt ihr natürlich nicht ignorieren und unterbrecht (dies stellt einen NMI da) jetzt auch euer Handygespräch, um nach dem Rechten zu sehen.
         
    • Da es sich zum Glück um einen Fehlalarm gehandelt hat, setzt ihr das Handygespräch, nach deaktivieren des Alarms, fort und berichtet endlich von dem Video. Sobald das Gespräch (der ‘normale‚ Interrupt) beendet ist kehrt ihr zum You-Tube Video zurück.
       
  • Ihr wollt euch nun endlich das Video in Ruhe anschauen (hier sind wir wieder im Hauptprogramm) und nehmt euch vor, euch von nichts mehr unterbrechen zulassen.
     
  • Kurz darauf bekommt ihr über Skype eine Einladung zum Retro-Chat (eine ‘normale‚ Interruptanfrage). Da ihr aber das Video anschauen wollt, ignoriert ihr die Chat-Einladung einfach (s. SEI / CLI bei den Mnemonics).
     
  • Würde sich der Rauchmelder jetzt nochmal melden, schaut ihr natürlich wieder nach, den Alarm kann man ja nicht ignorieren (dies wäre wieder ein NMI).

 

Interrupts: Übersicht Programm, IRQ, NMI
Übersicht Programm, IRQ, NMI

 

Wer  sorgt nun für die Interrupts?
Wenn wir die Interruptquellen des C64 mal grob einteilen, so kommen wir auf deren vier:

  1. RESET: Der RESET stellt einen besonderen Interrupt dar. Dieser wird nicht vom C64 bzw. einem seiner Bausteine ausgelöst. Der RESET ist ein Interrupt, den wir als User von außen auslösen. Der 6510 führt einen RESET aus, wenn Pin-40 auf LOW gezogen wird.
     
  2. NMI: Ein NMI (None Maskable Interrupt) kann einzig vom CIA-2 ausgelöst werden, er wird daher auch manchmal NMI-CIA genannt. Dieser ist dazu direkt mit mit Pin-4 des 6510 verbunden. Ein NMI läßt sich, wie oben bereits erwähnt, nicht mit  SEICLI unterdrücken! Ein weiterer ‚Auslöser‚ für einen NMI ist die <RESTORE>-Taste, auch diese ist direkt mit dem NMI-Eingang der CPU verbunden.
     
  3. IRQ: Eine ‘standard‚ Interruptanfrage (InterruptReQuest) kann von verschiedenen Bausteinen ausgelöst werden, z. B. dem CIA-1 (über den Timer) oder dem VIC-II (für einen Rasterzeileninterrupt). Dazu sind die Bausteinen mit dem IRQ-Eingang des 6510 an Pin-2 verbunden. IRQs lassen sich mit  SEICLI unterdrücken.
     
  4. BRK: Auch der BRK-Befehl stellt einen besonderen Interrupt dar. Hierbei handelt es sich um einen sog. Software-Interrupt. Trifft die CPU auf ein BRK unterbricht sie sich quasi selbst. Der Einsatzbereich ist relativ klein, am Häufigsten wird ein Break bei Assemblern (z. B. Turbo Assembler oder SMON) auf dem C64 verwendet. Treffen diese auf ein  BRK, so unterbrechen sie das aktuelle Programm und zeigen die Prozessorregister an.

 

Was passiert bei einem IRQ eigentlich?

  1. Sobald ein Interrupt auftritt, unterbricht die CPU das aktuelle Programm, nach dem der aktive Befehl verarbeitet wurde.
    Handelt es sich bei der Quelle um einen RESET, dann geht es direkt bei Punkt 6 weiter.
  2. Die Adresse des nächsten Befehls wird auf dem Stack abgelegt, erst das MSB, dann LSB. Denkt dran, dass der Stack, von oben nach unten gefüllt wird und somit die Adresse wieder im gewohnten LSB/MSB-Format im Speicher liegt.
  3. Handelt es sich beim Auslöser um den BRK-Befehl, dann wird das B-Flag gesetzt.
  4. Danach landet das Statusregister (inkl. ggf. gesetzem B-Flag) ebenfalls auf dem Stack.
  5. Jetzt werden noch weitere Interrupts durch Setzen des I-Flags verhindert.
  6. Die CPU springt zu der laut Hardware-Vektoren zugehörigen Speicherstelle. Abhängig von der jeweiligen Interruptquelle gibt es unterschiedliche Einsprungadressen.
    Bei einem RESET ist hier Schluß, schließlich startet der Rechner alles neu.
  7. Trifft der Prozessor auf ein RTI (ReTrun from Interrupt), dann wird zunächst das Statusregister vom Stack geholt. Da das I-Flag erst nach der Ablage auf dem Stack gesetzt wurde, wird es so auch automatisch wieder gelöscht. Anschließend wird die Adresse des nächsten Befehls (s. Punkt 2) vom Stack geholt und das Programm dort fortgesetzt.

 

Die drei Hardware Interrupt-Vektoren des 6510 findet ihr am Ende des Speichers, in den Adressen $FFFA$FFFF.

Da wir vier Interrupt-Quellen (IRQ, NMI, BRK und RESET), aber nur drei Vektoren haben, teilen sich IRQ und BRK einen Vektor. Man kann (muss) diese durch Prüfen des B-Flags unterscheiden.

Für uns ist der IRQ-Vektor am wichtigsten, schauen wir uns doch einfach mal an, was dort genau geschieht.

Zunächst kontrollieren wir unter $FFFE / $FFFF, wo das System hinspringt. Dazu starten wir einfach WinVICE und rufen dann mit  ALT-M den Monitor (falls ihr mit diesem wenig Erfahrung habt, unter ‚Ein kleiner ‚Crack‚‚ wurde etwas mehr damit gemacht) auf. Im Monitor schauen wir uns im Memory-Fenster die letzten beiden BYTES des Speichers an:

IRQ_002

Wie wir sehen zeigt der IRQ-Vektor auf die Adresse $FF48, wie sie auch oben in der Übersicht steht. Das heißt also, wenn ein IRQ auftritt, springt der C64 zu dieser Adresse. Werfen wir nun einen Blick auf die Routine, die wir unter $FF48 finden. Dazu verwenden wir das Disassambly-Fenster des VICE-Monitors:

IRQ_003

Zunächst werden Akku, X- und Y-Register auf dem Stack abgelegt. Als nächstes muss geprüft werden, ob die Quelle ein IRQ oder ein BRK war. Dazu greift die Routine direkt auf den Stack zu! Wir benutzen bisher nur die Push- und Pull-Befehle des 6510, um auf den Stack zuzugreifen. Aber da es sich im Prinzip um einen ganz normalen Speicherbereich handelt (Page 1 von $0100 bis $01FF), können wir auch direkt darauf zugreifen.

Gehen wir mal davon aus, dass der Stackpointer vor der Unterbrechung auf $F6 gezeigt hat. Wenn nun ein Interrupt ausgelöst wird, dann landen zunächst ja das MSB und dann das LSB auf dem Stack. Wie ihr wisst, wird der Stack von ‚oben‚ $01FF nach ‚unten‚ $0100 gefüllt, daher müsst ihr die folgende Liste von unten  nach oben lesen.

Wollen wir nun das Statusregister vom Stack in den Akku holen, dann müssen wir zur Adresse $0104 den aktuellen Stackpointer addieren. Nachdem Akku, X- und Y-Register auf dem Stack abgelegt wurden, zeigt der SP auf einen um vier höheren Wert, daher wird nicht $0100 als Basis genommen. Hier zeigt der SP also auf $F0 addieren wir nun $0104, dann landen wir bei $01F4 und dort finden wir das gespeicherte Statusregister. Dann wird per  AND geprüft ob das B-Flag (4. Bit) gesetzt ist. Falls nicht wird zum indirekten Sprung über den IRQ-Vektor  $0314 verzweigt, sonst geht es weiter mit dem indirekten Sprung über den BRK-Vektor $0316.
Wie jetzt, schon wieder Vektoren? Ja, wir müssen die ROM (oder Hardware) Vektoren ( $FFFA- $FFFF) und die RAM-Vektoren unterscheiden. Unter ‚Ein Modul erstellen‚ haben wir schon zwei dieser Vektoren benutzt, für die Interrupts sind folgende Vektoren im RAM wichtig. Finden können wir diese RAM-Vektoren auf Page 3.

Also springen die letzten beiden JMP-Befehle aus der IRQ-Routine, je nach Quelle zur Adresse, die an $0314 (IRQ) oder $0316 (BRK) zufinden ist. Die Zieladressen, wie sie nach einem RESET zufinden sind, stehen ganz rechts.

Da diese Vektoren im RAM liegen erlauben sie es uns nun, die Interrupts ‚anzuzapfen‚ und für unsere Zwecke zu verwenden. Das RAM können wir schließlich überschreiben.
Der Vollständigkeithalber sei erwähnt, dass es auch Möglich ist direkt die Vektoren $FFFA bis $FFFF zu ‚verändern‚. Dazu müssen wir nur das ROM aus- und das RAM einblenden. Da uns so aber auch die Kernalfunktionen fehlen würden, verzichten wir erstmal darauf.

 

Einen IRQ ‚anzapfen‚
Wie eingangs bereits erwähnt, liefert der CIA-1 einen IRQ beim Unterlauf von Timer A (also wenn dieser heruntergezählt wurde). Dieser IRQ wird 60 mal in der Sekunde ausgelöst und vom System z. B. zum Weiterstellen der Uhr, abfragen der Tastatur und blinken des Cursors verwendet.
Wir haben nun die Möglichkeit, uns in diesen Interrupt einzuklinken, um unseren eigenen Code auszuführen. So könnten wir z. B. Musik abspielen lassen, während wir nebenbei BASIC programmieren. Das klappt nur, da (wie eben gesehen) beim Interrupt nicht direkt ins ROM gesprungen wird, sondern über die RAM-Vektoren verzeigt wird.

Wir werden jetzt einfach mal den Rahmen blinken lassen, während wir nebenbei ganz normal mit dem BASIC weiterarbeiten.

Zu Beginn legen wir drei Konstanten fest und lassen dann unsere BASIC-Startzeile folgen. Diesmal ist die aber nicht die gewohnte. Damit wir das BASIC problemlos verwenden können, wird unser Programm jetzt an der Adresse $C000 beginnen. Außerdem sorgen wir mit einem abschließenden NEW dafür, dass wir ein ‘sauberes‚ BASIC haben und direkt mit dem Programmieren beginnen können.

Wenn wir jetzt die Adresse unserer IRQ-Routine an $0314/15 ablegen wollen, dann sollten wir zunächst dafür sorgen, dass kein Interrupt auftritt. Stellt euch nur mal vor, wir haben gerade unser LSB in $0314 abgelegt und dann tritt der IRQ auf. Da nun eine ungewollte Adresse im IRQ-Vektor steht, wird sich der C64 sehr wahrscheinlich ‚aufhängen‚. Also sperren wir mit  sei die Interrupts, dann speichern wir die Adresse unserer IRQ-Funktion an $0314/15 und erlauben, vor der Rückkehr zum BASIC, die Interrupts wieder.

Unsere IRQ-Routine irq: erhöht einfach mal wieder die Rahmenfarbe. Damit der C64 nichts von unserem ‚Zwischenfunken‚ merkt und normal weiterarbeitet, springen wir anschließend einfach zur ursprünglichen Kernal-Funktion nach $EA31.

Startet das Programm und ihr könnt nun z. B. ein BASIC-Programm, begleitet vom blinkenden Rahmen, schreiben. Unsere IRQ-Routine wird nun 60 mal in der Sekunde aufgerufen.

Interrupts: Der Rahmen blinkt unablässig, während wir im BASIC arbeiten.
Der Rahmen blinkt unablässig, während wir im BASIC arbeiten.

 

Probleme könnt ihr natürlich bekommen, wenn euer IRQ-Code länger läuft, als ihr eigentlich Zeit habt. Also wenn ihr z. B. obigen IRQ ‚anzapft‚ und eure IRQ-Funktion binnen einer sechzigstel Sekunde nicht beendet ist, dann tritt schon der nächste IRQ auf, bevor ihr mit eurem fertig seid.


 

Das wars erstmal mit der Einführung in die Interrupt-Programmierung.

Als nächstes folgt dann der NMI. Später werden uns die Interrupts noch weiter beschäftigen, besonders die vom VIC-II und da natürlich der Rasterzeileninterrupt.

Auf BRK und RESET möchte ich nicht gesondert eingehen, da deren Einsatz doch sehr speziell ist. Bei ‚Ein Modul erstellen‚ wird der RESET (allerdings nicht direkt der Interrupt) verwendet. Dort seht ihr auch, wie ihr euer Programm ‚RESET fest‚ machen könnt.


Schrott!!Naja...Geht so...Ganz gut...SUPER! (4 Bewertungen | Ø 5,00 von 5 | 100,00%)

Loading...


 

<<< zurück | weiter >>>

weitersagen ...
Tweet about this on TwitterShare on FacebookShare on Google+Share on LinkedIn

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Protected by WP Anti Spam