CIA-Kurs: "Die Geheimnisse des Secret-Service" (Teil 4) ----------------------------------------
Herzlich Willkommen zum 4. Teil unseres CIA-Kurses. Diesen Monat möchte ich dann doch vorgreifen und Ihnen zunächst ein- mal die NMI-Interrupts erklären. Dann können wir nämlich anhand eines einfa- chen Beispiels auch eine sehr nützliche Anwendungsweise von Timerkopplung behan- deln. Mit dem IRQ kennen Sie sich mittlerweile ja gut aus. Wir haben diese Interruptart in Zusammenhang mit dem Systeminterrupt ja schon eingehendst kennengelernt und auch schon eigene IRQ-Routinen geschrie- ben, die sowohl eigenständig, als auch im System-IRQ eingebunden arbeiteten. Kommen wir nun also auch zu der anderen für uns wichtigen Interruptart, dem NMI. Zunächst: Was ist der Unterschied zwi- schen einem IRQ und einem NMI? Da haben wir zum einen schon einmal den Unter- schied, daß beide Interruptarten von jeweils einem CIA angesteuert werden. Das hatte ich Ihnen ja schon zu einem früheren Zeitpunkt erläutert. CIA2 löst also NMIs aus, CIA1 IRQs. Doch es gibt da noch einen weitgehendst wichtigeren Punkt, in dem sich der NMI vom IRQ unterscheidet. Ich hatte Ihnen damals bei der Erklärung der Hardware- verbindungen der CIAs und des Prozessors untereinander ja schon erklärt, daß jede der CIAs nicht nur verschiedenartige Interrupts auslöst, sondern daß vielmehr der Prozessor über zwei verschiedene Eingänge verfügt, an denen der jeweilige Interrupt ausgelöst werden kann. Das bedeutet aber auch, daß er einen Unter- schied zwischen beiden Interruptarten macht, und das ist ganz wichtig für uns zu wissen! IRQ ist die Abkürzung für "Interrupt- ReQuest", was soviel bedeutet, wie "An- frage auf eine Unterbrechung". Das Wort "Anfrage" möchte ich hier ganz deutlich herausstellen, denn wie Sie mittlerweile ja ebenfalls wissen sollten, können wir den Prozessor durch den SEI-Befehl da- hingehend manipulieren, daß er Signale am IRQ-Eingang ignoriert. Im Fachjargon sagt man auch, man kann einen Interrupt "maskieren" - durch Setzen des Inter- ruptflags können wir also softwaremäßig IRQs sperren und das ist dann auch der Punkt, bei dem der Unterschied zum NMI in Erscheinung tritt. "NMI" ist nämlich ebenfalls eine Abkürzung und steht für "Non-Maskable-Interrupt", was mit "Nicht-maskierbare-Unterbrechung" den Nagel auf den Kopf trifft. Und schon hätten wir das Kind im Brunnen. NMIs sind softwaremäßig nicht sperrbar und das kann enorme Vorteile gegenüber dem IRQ haben! Hier einmal ein einfaches Beispiel: die Routinen des Betriebssystems müssen in der Regel aus dem einen oder anderen Grund von Zeit zu Zeit IRQs verhindern. Zum Einen aus Zeitersparnis und somit zur Geschwindigkeitssteigerung, zum An- deren bei komplizierten Synchronisa- tionsvorgängen mit der Peripherie des Computers, wobei auftretende Interrupts Zeitwerte verfälschen und somit stören könnten, benutzt das Betriebssystem nun ebenfalls den SEI-Befehl. Die Folge des Ganzen wird schnell klar: soll der IRQ nun ganz zeitkritische Arbeiten erldedi- gen, so kommt er schnell aus dem Takt und ist somit oft viel zu ungenau. Glän- zendes Beispiel ist die BASIC-Uhr TI$. Sie wird nämlich über den System-IRQ gesteuert, der ja normalerweise 60 Mal pro Sekunde auftritt. Rein theoretisch braucht die Routine für TI$ also nur bis 60 zu zählen, um zu wissen, daß jetzt eine Sekunde verstrichen ist. Praktisch sieht es aber so aus, daß zum Beispiel die Ein-/Ausgaberoutinen des Betriebssy- stems oft den IRQ unterbinden. Es genügt also, ein längeres Programm von Diskette zu laden um die TI$-Uhr extrem zu brem- sen, so daß sie die eine oder andere Sekunde nachgeht. Aus den Sekunden wer- den Minuten, je mehr man lädt und ir- gendwann kann man die Zeitwerte der Uhr komplett vergessen: dadurch, daß IRQs zwischendurch nicht mehr auftreten kön- nen, aber die Zeit weiterhin unerbitt- lich verstreicht, zählt die TI$-Routine zwar weiterhin 60 IRQs, diese jedoch dauerten länger als eine Sekunde. NMIs hingegen werden IMMER bearbeitet, sobald sie auftreten. Sogar dann, wenn sich der Prozessor gerade innerhalb ei- nes IRQs befindet. Er rettet dann ein- fach die Daten des IRQs (Programmzeiger, Prozessorstatus etc.) und bearbeitet den NMI. Umgekehrt jedoch, kann kein IRQ während eines NMIs auftreten, da der Prozessor ja dann das Interruptflag ja schon von selbst gesetzt hat (sie erin- nern sich...). Es sei denn wir lassen dies ausdrücklich zu, indem wir inner- halb der NMI-Routine das Flag durch CLI wieder löschen. Sie sehen also, man muß immer Unter- schiede machen, wofür ein Interrupt be- nötigt wird. Einfache Probleme lassen sich schnell mit dem IRQ bewältigen (und das ist bei den meisten der Fall), da er bei Bedarf auch sehr einfach abgeschal- tet werden kann. Bei zeitkritischen Pro- blemen benutzt man besser einen NMI. Er funktioniert genau und zuverlässig, wobei man allerdings in Kauf nehmen muß, daß man diesen nicht so einfach wieder verhindern kann. Das ist nämlich der Grund warum man bei der Programmierung eines NMIs mehr Auf- wand hat. Für ihn existiert, ebenso wie für den IRQ, auch ein Vektor, der verän- dert werden muß, wenn man die NMIs auf eigene Interruptroutinen umleiten will. Wir hatten ja letzten Monat schon ge- lernt, daß man dabei sichergehen muß, daß während dieser Veränderung in gar keinem Fall ein Interrupt ausgelöst wer- den darf, da so schon während das LO- Byte, jedoch noch nicht das HI-Byte des Vektors verändert ist, der Rechner un- kontrolliert in die Pampas springen könnte, was so unangenehme Folgen hätte, wie zum Beispiel einen Rechnerabsturz. Aus diesem Grund müssen wir zusehen, daß alle eventuell in Frage kommenden NMI- Quellen so geschaltet sind, daß sie kei- nen Interrupt auslösen, während wir den NMI-Vektor verändern. Im Normalfall ist dieses Problem eigent- lich relativ einfach zu handhaben, denn das Betriebssystem benutzt den Timer-NMI ausschließlich nur bei Betrieb der RS232-Schnittstelle, also bei der se- riellen Datenübertragung per Modem. In aller Regel können wir diesen Fall je- doch ausklammern und davon ausgehen, daß alle Funktionen der CIA2, die den NMI betreffen, funktionslos ihr Dasein fri- sten. Nur im Falle einer eigenen Benut- zung sollten wir uns immer im Klaren darüber sein, was für eine Aufgabe der NMI gerade behandelt und wie sie ge- steuert wird. Im Regelfall genügt es jedoch, einfach alle Bits des ICR- Registers der CIA2 zu löschen, so daß von dort keine Interrupts mehr an den Prozessor gelangen. Dies geschieht durch ein Schreiben des Wertes 127 (=$7F) in selbiges Register ($DD0D = dez. 56589). Eine weitere Besonderheit des NMIs ist, daß die RESTORE-Taste hardwaremäßig DI- REKT an die NMI-Leitung des Prozessors angeschlossen ist, daß also auch von dort Interrupts ausgelöst werden können. Dieses macht sich das Betriebssystem zunutze, denn bei einem Druck auf RUN/- STOP-RESTORE, was den C64 ja wieder in einen einigermaßen definierten Zustand zurückbringt, wird immer ein NMI aus- gelöst. Was nun allerdings wirklich da- bei geschieht und wie es mit Sprungvek- toren für NMIs aussieht, wollen wir uns jetzt einmal näher anschauen. Dazu ist wieder einmal eine kleine Reise in die tieferen Gefilde des Betriebssy- stems angesagt. Beginnen wir mit den elementaren Grundvoraussetzungen: * Zunächst also wird ein NMI ausgelöst, indem der Benutzer auf die RESTORE- Taste drückt. * Der Prozessor hält seine momentane Arbeit jetzt unverzüglich an, rettet wie bei jedem Interrupt die wichtig- sten Daten auf den Stapel (das hatten wir ja schon), setzt das Interrupt- flag, so daß ihn keine IRQs mehr stö- ren können und macht sich daran, wie- der in einem eigenen Vektor für NMIs, am Ende seines Adressbereichs nachzu- sachauen, wo er jetzt weiterfahren soll. Dieser Vektor liegt bei $FFFA/$FFFB und zeigt auf eine Jobrou- tine des Betriebssystems bei $FE43. Schauen wir uns einmal an, was dort so läuft:
---------------- NMI-Anspringen
FE43 SEI IRQs sperren. FE44 JMP ($0318) Öber NMI-Vektor sprin- gen. ---------------- Da hätten wir auch schon den angespro- chenen NMI-Vektor, der für uns veränder- bar ist. Er belegt die Speicherstellen $0318/$0319 (dez. 792/793) und zeigt normalerweise auf die Adresse gleich hinter der soeben aufgelisteten Routine, auf $FE47. Was übrigens anzumerken ist, ist die Tatsache, daß wir beim Einbinden von eigenen NMIs in den System-NMI darauf achten müssen, daß wir auch die Prozes- sorregister quasi "von Hand" auf den Stapel retten müssen. Die IRQ- Vorbereitungsroutine hatte dies ja noch VOR dem Sprung über den RAM-Vektor ge- macht, weshalb wir uns nicht mehr darum kümmern mußten. Beim NMI macht das Be- triebssystem das erst NACH dem Sprung über den Vektor, in der nun folgenden Routine:
---------------- NMI vorbereiten. FE47 PHA Akku auf Stapel. FE48 TXA X nach Akku... FE49 PHA ...und auf Stapel. FE4A TYA Y nach Akku... FE4B PHA ...und auf Stapel. FE4C LDY #$7F Wert laden... FE4E STA $DD0D ...und damit alle NMI-Quellen von der CIA2 kommend sperren. ---------------- Auf RS232-Betrieb prü- fen. FE51 LDY $DD0D Interruptquellen- Anzeige aus ICR lesen und somit löschen um weitere NMIs freizuge- ben. FE54 BMI $FE72 Wenn die RS232- Schnittstelle aktiv ist, verzweigen. ----------------
Anmerkung: Mit dem letzten Befehl wurde abgefragt, ob eines der Interruptquel- lenbits des ICR gesetzt ist. Da das Be- triebssystem ja CIA2-gesteuerte NMIs nur dann benutzt, wenn die RS232 Schnitt- stelle läuft, genügt es, nur zu prüfen, ob der NMI überhaupt von der CIA2 kommt. In diesem Fall ist Bit 7 des ICR auf 1. Wenn das nicht der Fall ist, dann kann der Auslöser nur die RESTORE-Taste gewe- sen sein, und es wird wiefolgt fortge- fahren:
---------------- Auf ROM-Modul prüfen FE56 JSR FD02 Prüft ob ein ROM-Modul im Expansions-Port steckt. FE59 BNE $FE5E Wenn nein, dann ist das Zero-Flag gelöscht und wir überspringen den folgenden Befehl. FE5B JMP ($8002) Ja, wir haben ein Mo- dul, also springen wir auf den Modul-NMI (siehe unten). ---------------- Prüfen, ob R-S/RESTORE FE5E JSR $F6BC Flag für STOP-Taste in der Zeropage ($91 = dez. 145) berechnen und setzen. FE61 JSR $FFE1 STOP-Taste abfragen. FE64 BNE $FE72 Wenn nicht gedrückt verzweigen, um den NMI zu beenden. ---------------- R-S/RESTORE ausführen FE66 JSR $FD15 Standard-Vektoren für Interrupts und Ein-/ Ausgabevektoren ini- tialisieren. FE69 JSR FDA3 Ein-/Ausgabebausteine initialisieren. FE6C JSR E518 Bildschirm löschen. FE6F JMP ($A002) BASIC-Warmstart ausführen. ----------------
Der Teil von $FE47-$FE59 rettet nun zunächst einmal die drei Prozessorre- gister auf den Stapel. Desweiteren wer- den alle Interruptquellen die der CIA2 geben könnte, gesperrt. Dann, von $FE51-$FE55 wird das ICR der CIA2 ausgelesen und somit für neue In- terrupts freigeben (ist im Moment zwar nicht möglich, da wir ja die Interrupts vorher sperrten, wird aber für die RS232-Behandlung gebraucht!). Gleichzei- tig wird geprüft, ob der Befehl von der CIA2 kam, und wenn ja zur RS232- Unterroutine verzweigt. Im nun folgenden Teil von $FE56-$FE5D wird geprüft, ob ein ROM-Modul im Expan- sionsport steckt. Wie so etwas funktio- niert, will ich Ihnen nächsten Monat erklären. Wenn ein Modul da ist, dann wird auf einen moduleigenen NMI verz- weigt, andernfalls wissen wir nun endgültig, daß der Benutzer wahrschein- lich den Computer zurücksetzen will, und wir können in den folgenden Teil verwei- gen. Dieser geht von $FE5E-$FE65 und prüft nach, ob die RUN/STOP-Taste gleichzeitig auch noch gedrückt ist. Hierzu wird eine Unterroutine ab $F6BC benutzt, die di- rekt die Tastatur abfragt und in Spei- cherzelle $91 der Zeropage anzeigt, ob die RUN/STOP-Taste gedrückt ist. Steht dort eine 0, so war dies der Fall. An- dernfalls verzweigt das Programm nun doch in die RS232-Routine. Ehrlich ge- sagt, weiß ich nicht warum dies so ist, denn es läge näher, den NMI direkt zu beenden, aber die Wege des C64- Betriebssystems sind manchmal halt auch unergründlich... Jetzt sind wir aber endlich im letzten Teil angelangt. R/S-RESTORE wurde gedrückt, was heißt, daß wir einen "Mi- ni-Reset" ausführen sollen. Es werden nun drei Unterroutinen aufgerufen, die die wichtigsten Voreinstellungen im Sy- stem vornehmen, nämlich das Zurücksetzen der Sprungvektoren von (inclusive) $0314-$333 (Routine ab $FD15), das Rück- setzen des Grafikchips (VIC) und des Soundchips (SID), sowie der CIAs (Routi- ne ab $FDA3) und das Löschen des Bild- schirms (Routine ab $E518). Zum Schluß wird dann mit einem indirekten Sprung auf den NMI-BASIC-Warmstart in den "READY"-Modus des BASICs verzweigt. Hierbei wird der NMI nicht wie üblich mit RTI beendet, sondern die Warmstart- routine stetzt einfach den Stackpointer wieder zurück und springt dann in die BASIC-Eingabe-Warteschleife. Soviel zum System-NMI. Wir werden in den nächsten Kursteilen auch noch auf die RS232-Schnittstelle und deren Bedienung, sowie auf die ROM-Modul-Behandlung näher eingehen, weshalb ich diese Themen dies- mal aussparen möchte. Kommen wir nun zu der Programmierung von NMIs. Die Anwendungsbereiche für diese Interruptart sind vielfältig. Sie läßt sich gut bei zeitkritischen Problemen einsetzen, wie zum Beispiel das zykli- sche Lesen von Daten, in einer fest vor- geschriebenen Geschwindigkeit (Digitizer und Scanner arbeiten oft mit NMIs). Am eindrucksvollsten ist aber bestimmt das Abspielen von Musik über den NMI. Viele Sound-Editoren benutzen ja schon häufig die Möglichkeit, ein Musikstück via In- terrupt spielen zu lassen, jedoch gehen diese meist über den IRQ. Ich habe Ihnen einmal ein Beispielpro- gramm auf dieser MD mit abgespeichert. Es heißt "NMI/IRQ-DEMO" und beinhaltet drei kleine Unterprogramme. Das Programm tut nichts anderes, als einen Interrupt zu initialisieren, der ständig einen Ton über Stimme 1 des SID spielt. Hierbei wird bei jedem Aufruf das HI-Byte der Tonfrequenz um 1 erhöht. Das Ergebnis ist ein ganz lustiger Soundeffekt, der nicht unähnlich dem Geräusch ist, das entsteht, wenn Scotty die Besatzung der "Enterprise" rumbeamt. Eigentliche Aufgabe dieses Beispielpro- gramms ist nun aber, Ihnen den Unter- schied zwischen IRQ und NMI zu verdeut- lichen. Deshalb gibt es zwei Möglichkei- ten, es aufzurufen. Zum Einen können Sie es mit "SYS 4096*9" starten. Dann ini- tialisieren Sie einen NMI. Der Ton wird ständig über den NMI ausgegeben. Nun bietet sich zusätzlich noch die Möglich- keit, daß Sie durch "SYS 4096*9+3" eine kleine Unterroutine aufrufen, die alle IRQs sperrt. Zu erkennen ist dies daran, daß nach dem Aufruf der Cursor nicht mehr blinkt. Trotzdem aber hören Sie weiterhin den Soundeffekt - dies also als Beweis, daß der NMI unabhängig vom IRQ arbeitet. Die zweite Möglichkeit den Effekt zu starten ist die mit "SYS 4096*9+6". Sie initialisieren dann einen IRQ, der je- doch genau dasselbe tut wie der NMI zu- vor. Sie können nun nocheinmal mit "SYS 4096*9+3" die IRQs sperren, und schon hören Sie nichts mehr. Als Beispiel zu den Problematiken, die- sich mit dem IRQ und dem Betriebssystem ergeben, empfehle ich Ihnen, während der IRQ läuft einmal ein Programm von Dis- kette zu laden. Sie werden merken, daß die Tonausgabe zwischenzeitlich desöfte- ren stockt. Wenn das passiert, dann hat gerade wieder einmal eine Routine des Betriebssystems den IRQ mittels SEI ab- geschaltet. Leider können Sie dieses Problem nicht mit einem laufenden NMI untersuchen. Der stört nämlich dann die anfangs schon erwähnten Synchronisa- tionsvorgänge, die beim Laden benötigt werden, wobei der 64er nur Mist an- stellt. Probieren können Sie es einmal. Manchmal hat man Glück, machmal nicht. Bitte laden Sie nun den zweiten Teil des IRQ-Kurses aus dem Kurs-Menü.