CIA-Kurs: "Die Geheimnisse des Secret-Service..." (Teil 1) ----------------------------------------
1) Einleitung: --------------
Herzlich willkommen zu unserer neuen Kurs-Serie. Nachdem Sie mein Kollege IVO HERZEG die letzten Monate ja eingehend in die Matierie der Raster-Interrupts eingeführt hat, will ich Ihnen nun ein weiterführendes Thema anbieten: die CIA-Bausteine des C 64. Diese steuern (wie im Raster-IRQ-Kurs schon angedeutet) die übrigen Interrupt- funktionen unseres Rechners und sind für den Kontakt mit der Außenwelt des 64ers verantwortlich. Ohne sie könnten wir ihn in keinster Weise bedienen - die Bedie- nung der Tastatur und des Joysticks oder die Datenspeicherung auf Massenme- dien wie Kassette oder Diskette wäre gar nicht möglich. Sie sehen also, daß in den nächsten Monaten einige ganz inte- ressante Themen auf Sie zukommen werden. Ich möchte mich zunächst einmal um die Interruptprgrammierung kümmern und in fortführenden Folgen Anleitungen zur Bedienung der Ein-/Ausgabeeinheiten des 64ers geben. Wir werden dann den Joy- stick einmal genauer unter die Lupe neh- men und auch den Anschluß einer Maus durchführen, ganz abgesehen von den vielfältigen Möglichkeiten die uns der Userport bietet, um Harware- Erweiterungen zu bedienen. Im übrigen sollte ich noch darauf hin- weisen, daß Sie zum vollen Verständnis dieses Kurses doch schon tiefergreifende Kenntnisse von der Programmierung in Maschinensprache haben sollten, sowie in der Handhabung eines Maschinensprache- Assemblers und eines Speichermonitors. Nichts desto trotz können auch BASIC- Programmierer einiges hier lernen, was eventuell auch von BASIC aus genutzt werden kann, jedoch mit Sicherheit nicht in der komplexen und vielfältigen Art und Weise, wie dies von Maschinensprache aus möglich ist.
2) Die Hardware: ----------------
Zunächst jedoch einmal eine kleine Be- schreibung, mit was für Bausteinen wir es überhaupt zu tun haben. Die beiden CIAs des C64 sind zwei unscheinbare 40- polige Microchips mit vielfältigen Mö- glichkeiten. Man könnte sie quasi als "Manager" unseres Computersystems be- zeichnen, die die Verbindung zwischen den einzelnen Ein- und Ausgabeeinheiten herstellen und deren Zusammenwirken erst richtig möglich machen. Beide CIAs sind baugleich und können somit also problemlos miteinander ver- tauscht werden (was oft bei einer Prü- fung auf Funktionsstörungen schon zu einer eindeutigen Analyse führen kann - trotzdem sei von einer Nachahmung ohne Vorkenntnisse abgeraten). Sie tragen die Bezeichnung MOS 6526 und befinden sich in der Ecke links oben auf der Mutter- platine unseres Rechners. Soviel zur harwaremäßigen Einordnung dieser kleinen Helfer, jedoch möchten wir uns hier ja mit der softwaremäßigen Bedienung befassen, weshalb ich nun also zu den für uns intressanten Fähigkeiten komme. Die CIAs beinhalten jeweils: * Zwei 16-Bit-Timer, mit denen man her- vorragend besonders zeitkritische Pro- gramm-Probleme lösen kann. * Eine 24-Stunden Echtzeituhr, die die Zeit im Gegensatz zur interruptge- steuerten BASIC-Uhr TI$ extrem genau geht. * Zwei freiprogrammierbare Datenports, mit denen auch komplexe Datenübertra- gungen über den Userport möglich wer- den. Wir unterscheiden die beiden CIAs im Folgenden mit CIA1 und CIA2. Sie werden vom Betriebssystem für unterschiedliche Aufgaben genutzt, die nun ebenfalls be- schrieben werden sollen: * CIA1 ist mit der Tastatur und den bei- den Joystickports verbunden und ist somit für die Eingabe über Tastatur, Joysticks, Maus oder Paddles zustän- dig. Desweiteren wird von ihm der Systemin- terrupt gesesteuert, der zyklische Aufgaben, wie das Empfangen von Ta- stencodes oder das Weiterzählen der BASIC-Uhr TI$ erledigt (dazu später mehr). * CIA2 ist für die komplette Daten Ein-/ Ausgabe zuständig. Er steuert den IEC-Bus, mit dem bis zu 4 Disketten- laufwerke und maximal 2 Drucker ange- steuert werden können. Desweiteren ist er am Kasettenport angeschlossen und seine Datenleitungen sind am Userport herausgeführt, wodurch auch PC- Standard-Schnittstellen wie RS-232 (seriell) oder CENTRONICS (parallel) softwaremäßig emuliert werden können. Das Wichtigste, was wir zur Interrupt- programmierung wissen müssen ist, daß der CIA1 mit der IRQ-Leitung und der CIA2 mit der NMI-Leitung des Prozessors verbunden ist. Je nach Aufgabengebiet einer Interruptroutine müssen wir also unterscheiden, von welchem CIA die In- terrupts ausgelöst werden. In der Spei- cherkonfiguation des 64ers sind die bei- den Chips getrennt an zwei verschiedenen Basisadressen eingebunden. Ihre Register sind jedoch aufgrund der Baugleichheit für die gleichen Funktionen zuständig, weshalb wir auch nur EINE Registertabel- le benötigen. Es kommt halt nur drauf an, welchen der beiden Zwillinge wir ansprechen wollen. Die Basisadresse für CIA1 ist $DC00 (=dez. 56320), für CIA2 $DD00 (=dez. 56576). Wollen wir also Timer A (dazu später) von CIA1 mit dem Grundwert 16384 initialisieren, so müs- sen wir die Register 4 und 5 ab $DC00 ($DC04 und $DC05) mit dem LO/HI-Byte von 16384 beschreiben, bei Timer A von CIA2 ebenfalls Register 4 und 5, jedoch dies- mal ab Basisadresse $DD00 ($DD04 und $DD05).
3) Was ist ein Interrupt? -------------------------
Nun zu einigen grundlegenden Informatio- nen zu Interrupts. Ich benuzte dieses Wort die ganze Zeit schon, ohne zu erkl- ären was es überhaupt bedeutet (obwohl Sie sich darin vielleicht schon durch den Raster-IRQ-Kurs auskennen). Interrupt ist englisch und heißt wört- lich übersetzt "Unterbrechung". Der Pro- zessor des C64 besitzt, wie jeder andere Prozessor auch, sogenannte Interrupt- Eingänge. Beim Prozessortyp 6510 (wie er in unserem Rechner Verwendung findet) sind dies insgesamt drei Leitungen, wo- mit er zwischen drei verschiedenen In- terrupts (rein hardwaremäßig - softwa- remäßig sind es sogar noch mehr) unter- scheiden kann. Diese sind IRQ, NMI und RESET. Diese drei Leitungen können nun extern, von anderen Bausteinen, wie zum Beispiel (und vor allem) von den CIAs angesprochen werden um dem Prozessor das Eintreten eines bestimmten Ereignisses zu signalisieren. Der Prozessor bemerkt dies und kann nun durch ganz bestimmte Maßnahmen auf die Bearbeitung eines Ereignisses eingehen. Der Clou an der Sache ist, daß der Pro- zessor so nicht ständig auf das Eintre- ten eines Ereignisses warten muß und deshalb beispielsweise nicht ständig in einer Endlosschleife prüfen muß, ob in irgendeiner Speicherzelle irgendwann einmal ein bestimmter Wert steht, son- dern er bekommt diese Arbeit von den CIAs abgenommen, die ihn schlichtweg nur noch darauf aufmerksam machen, daß er nun seine Achtung etwas anderem schenken sollte - dem Interruptereignis. So kann er also auch gerade mit ganz anderen Dingen beschäftigt sein - nämlich mit der Abarbeitung eines Programmes - und trotzdem zwischendurch ganz gezielten Aufgaben nachgehen. In der Praxis sieht das so aus, daß er seine momentane Arbeit - das Hauptpro- gramm - dann UNTERBRICHT und in ein Job- programm zur Bearbeitung des Interrupts springt. Ich möchte hier zur Verdeutlichung ein- mal ein Beispiel aus dem Alltag bringen. Ich, Uli Basters, sitze hier an meinem Rechner und bin gerade dabei, den ersten Teil des CIA-Kurses zu schreiben. Plötz- lich klingelt das Telefon. Bevor ich aufstehe um zum Telefon zu gehen spei- chere ich schnell noch das bisher ge- schriebene ab und merke mir vor, daß ich nach dem Telefonat unbedingt weiter- schreiben werde. Am anderen Ende ist mein Kollege Ralf Zwanziger, der mir den nächsten Redaktionsschluß durchgibt. Nachdem ich aufgehängt habe erinnere ich mich an mein Vorhaben, gehe wieder zurück zum Rechner, lade den Text wieder ein und setze meine Arbeit fort. Diesen Vorgang kann man sehr gut mit den Tätigkeiten des Prozessors beim Eintre- ten eines Interrupts vergleichen. Eine Interruptleitung signalisiert ihm, daß ein Interruptereignis eingetreten ist (das Telefon klingelt). Schnell merkt er sich noch die wichtigsten Daten, nämlich den Inhalt der Prozessorregister (Akku, X- und Y-Register), den Prozessorstatus (Speicherung des Textes) und den Inhalt des Programmzählers (das Vormerken wei- terzuarbeiten). Danach springt er auf eine Jobroutine, die er für das Eintref- fen eines Interrupts parat hat und ar- beitet diese dann ab (ich führe ein Te- lefonat). Ist er am Ende dieser Routine angelangt, so holt er sich Programmzäh- ler, Status- und Prozessorregister wie- der ins Gedächtnis zurück (Erinnerung weiterzuarbeiten und wiedereinladen des Textes) und setzt seine alte Arbeit wie- der fort. Diesen ganzen Vorgang erledigt er mit einer derart affenartigen Geschwindig- keit, daß wir meinen er würde beides gleichzeitig tun. Das wäre dann auch der nächste Vorteil der ganzen Geschichte. Durch Interrupts ist man also in der Lage mehrere Dinge, ganz unabhängig vo- neinander, scheinbar gleichzeitig zu erledigen - so auch das Betriebssystem, das, während es in der Hauptschleife auf Tasteneingaben wartet, über einen Inter- rupt den Cursor weiterhin blinken läßt. Die Möglichkeiten hier sind sehr vielfältig, wie wir noch bemerken wer- den.
4) Der Timer-Interrupt: -----------------------
Soviel zu den Vorgängen innerhalb unse- res Rechners. Nun möchte ich mich ein wenig mit den Unterschieden zwischen den drei Interruptarten beschäftigen. Wir haben also insgesamt drei verschie- dene Unterbrechungen. Eine davon, näm- lich der IRQ wird Ihnen vielleicht, wenn auch unbewußt, vielleicht schon nur zu gut bekannt sein. Er wird vom CIA1 aus- gelöst und vom Betriebssystem für inter- ne, cyklische Aufgaben verwendet. Des- halb ist er auch ein gutes Beispiel für uns um in dieser Materie einzusteigen, da uns das Betriebssystem schon eine koplette IRQ-Routine zur Verfügung stellt - den System-IRQ. Der System-IRQ nutzt die einfachste und zugleich auch vielseitigste Funktion des CIAs um Interrupts auszulösen - den Ti- merinterrupt. Bevor ich mich jetzt je- doch in unverständlichen Erklärungen verliere erst einmal eine Registerbe- schreibung eines CIA-Registers. Hierzu habe ich Ihnen eine Grafik vorbereitet, die eine Kurzbeschreibung der Register liefert; ausgedruckt sollte sie Ihnen immer parat liegen, da wir sie in Zu- kunft häufiger benutzen werden.
CIA-Kurs Teil 2 Wie man leicht erkennen kann sind zur Timerprogrammierung sechs Register not- wendig:
* TA-LO (Reg.4) * TA-HI (Reg.5) * TB-LO (Reg.6) * TB-HI (Reg.7) * CRA (Reg.14) * CRB (Reg.15)
Desweiteren brauchen wir auch noch das Interrupt-Control-Register (Reg.13). Ohne dieses Register läuft interruptmä- ßig überhaupt nichts mit dem CIA. Der System-IRQ benutzt nun Timer A, der für ihn den Auslöser darstellt. Deshalb können wir die Register TB-LO,TB-HI und CRB vorläufig einmal ausschließen und uns nur den Registern für Timer A zuwen- den. Zunächst möchte ich jedoch den Begriff "Timer" definieren. Ein Timer, so wie er pro CIA ja zweimal vorhanden ist, ist nichts anderes als ein spezielles Zähl- register, das ständig, nach ganz be- stimmten Regeln von einem Maximalwert in Einerschritten heruntergezählt wird. Ist der Timer bei Null angelangt, so wird ein Interrupt ausgelöst. Den schon angesprochenen Maximalwert müssen wir, wie Sie sicher schon vermu- ten in LO/HI-Byte aufgespaltet in TA-LO und TA-HI schreiben. Diese Timerregister haben quasi ein "Doppelleben". Sie be- stehen nämlich zum Einen aus einem Spei- cherregister, dem sogenannten "LATCH" und einem Zählregister oder auch "COUN- TER". Schreiben wir nun einen Wert in die Timerregister, so wird dieser zunächst im Latch abgelegt und gleich- zeitig in den Counter geladen. Starten wir anschließend den Timer, so beginnt der CIA damit, den Counter herabzuzäh- len. Hierzu kann man verschiedene Sig- nalquellen wählen, die ein Herabzählen veranlassen. Diese Signalquellen, können im Control-Register für Timer A festge- legt werden. Jedes der acht Bits in die- sem Register steuert eine ganz bestimmte Funktion. Desweiteren kann von hier auch der Timer gestartet und gestoppt werden, sowie einige bestimmte Zählmodi einge- stellt werden. Zur Erläuterung habe ich Ihnen einmal eine Tabelle erstellt:
Bit0 (START/STOP):
Durch Löschen dieses Bits wird der Timer gestoppt. Setzt man es, so wird begon- nen, den Counter herabzuzählen. Bit1 (PB ON/OFF): Dieses Bit eignet sich besonders für Hardware-Erweiterungen. Ist es gesetzt, so wird beim Unterlauf des Timers ein Signal auf die Portleitung PB6 (Bit 6 an Port B) gelegt. Ist es gelöscht, so wer- den keine Signale ausgegeben.
Bit2 (TOGGLE-PULSE):
Dieses Bit arbeitet nur in Zusammenhang mit "PB ON/OFF". Ist dieses gesetzt, so gelten folgende Bestimmungen für "TOG- GLE-PULSE": * Wenn gelöscht, so werden wie bei "PB- ON/OFF" beschrieben Signale auf die Leitung PB 7 gelegt. Diese sind übri- gens genau einen Prozessortaktzyklus lang. * Wenn gesetzt, so wird der Zustand von PB6 bei jedem Unterlauf des Counters in den jeweils anderen Zusand gekippt, das heißt von Gesetzt auf Gelöscht und umgekehrt. Damit läßt sich also ganz einfach ein Rechtecksignal erzeugen, wobei die Pulsbreite von der Laufzeit des Counters abhängt. Wie Sie sehen, sind die Bits 1 und 2 für ganz spezifische Aufgaben verwendbar und haben leider sehr wenig mit Interrupts zu tun, zumal kein Interruptsignal an den Prozessor gesand wird. Für manche Harwareerweiterungen sind Sie jedoch bestimmt sinnvoll einzusetzen, da man so sehr einfach beliebige Taktfrequenzen für irgendewelche Schaltungen am User- Port des C64 erzeugen kann, da die Lei- tung PB6 an selbigem herausgeführt ist. Hierzu jedoch in einem späteren Kursteil mehr.
Bit3 (ONE-SHOT/CONTINOUS):
Ist dieses Bit gelöscht, so befindet sich der Timer im CONTINOUS-Modus. Das heißt, daß der Counter bis 0 herunter- gezählt, wieder mit dem Wert im Latch geladen wird und von neuem beginnt zu zählen. Bei gesetztem Bit ist der ONE-SHOT-Modus aktiv - es wird bis 0 gezählt und neu geladen, jedoch stoppt der Timer jetzt automatisch, solange bis man ihn wieder mit Bit0 des Controlregisters in Gang setzt. Bit4 (FORCE-LOAD): Wird dieses Bit gesetzt, so wird der Counter, egal ob der Timer im Moment läuft oder nicht, mit dem Wert im Latch neu geladen. Bit5 (IN MODE): Hier wird die Quelle des "Timer- Triggers" festgelegt. Der Timer-Trigger ist die Einrichtung, die den CIA dazu veranlaßt den Counter einmal herunter- zuzählen. Ist dieses Bit gelöscht (was bei uns eigentlich immer der Fall ist), so wird der Systemtakt als Trigger herangezogen (das werden wir im nächsten Abschitt ganz genau behandeln). Ist es gelöscht, so ist die CNT-Leitung des CIA der Trig- ger. Diese ist an den Userport heraus- geführt, so daß somit auch Hardware- Erweiterungen in der Lage sind die In- terrupts im 64er extern zu steuern.
Bit6 (SP-DIRECTION):
Dieses Bit hat etwas mit Register 12 der CIA zu tun (SDR). Dieses Register ist für die serielle Datenübertragung sehr nützlich. Hier kann ein 8-Bit-Wert ge- speichert werden, der zyklisch aus dem Register heraus, an den Pin SP des CIA (mit dem Userport verbunden) gerollt wird, beziehungsweise ein Wert kann über den Pin SP hereingerollt werden (auch dazu in einem späteren Kursteil mehr). Bit6 von CRA steuert nun die Datenrich- tung von SDR. Ist es gelöscht, so ist SDR auf Eingang geschaltet (Bits werden hereingerollt), ist es gesetzt, so wird SDR als Ausgang benutzt (Bits werden herausgerollt).
Bit7 (POWER FREQUENCY):
Dieses Bit wird benötigt um den Trigger- frequenz für die Echtzeituhr des CIAs zu bestimmen. Je nach dem, in welchem Land wir unseren 64er angeschlossen haben, beträgt die Netzfrequenz des Wechsel- stroms aus der Steckdose nämlich 50 oder 60 Hertz (man spricht hier vom sogenann- ten "technischen Strom"). Diese Frequenz wird nun benutzt um die Zeiteinheiten der Echtzeituhr festzustellen. Ist die- ses Bit nun gesetzt, so gilt eine Fre- quenz von 50 Hz als Trigger, ist es gelöscht, eine von 60 Hz. Da wir in der Bundesrepublik Deutschland ja die von 50 Hz haben, sollte bei Uhr-Betrieb dieses Bit also immer gesetzt sein, da andern- falls unsere Uhr schnell "nachgehen" könnte. Na das ist doch schon eine ganze Menge an Information. Doch keine Angst, die Bits 1 und 2, die etwas komplizierter erscheinen mögen, wollen wir vorläufig erst einmal wegfallen lassen. Von ihnen wird, ebenso wie von Bit 6 und 7, in einer späteren Folge dieses Kurses mehr die Rede sein. Nun zum Systemtakt, der -in aller Regel- als Timer-Trigger dient. Er stellt wohl einen der wichtigsten Grundbausteine in unserem (wie auch in jedem anderen) Rechner dar. Ein Rechensystem, so wie es von einem Computer verkörpert wird, braucht schlichtweg IMMER einen Grund- takt, den alle miteinander verknüpften Bausteine benutzen können um synchron miteinander zu arbeiten. Er dient sozu- sagen als "Zeitmaß" für die Geschäfts- welt in einem Rechner. Ein Taktzyklus stellt die elementare Zeiteinheit inner- halb eines Rechner dar, an den sich alle Bausteine halten müssen um mit ihren Signalen nicht zu kollidieren. Solche Takte werden von Quarz-Bausteinen er- zeugt, die, je nach chemischer Zusammen- setzung, eine ganz bestimmte Eigenfre- quenz haben, die extrem genau ist (wie von Quartz-Uhren her ja allgemein be- kannt). Ein Systemtakt ist von Rechner zu Rech- ner verschieden. Beim AMIGA beträgt er zum Beispiel 7.16 MHz (Megahertz), beim ATARI ST 8 MHz, bei PCs mittlerweile zwischen 4.77 und 33 MHz (und mehr). Je höher ein Rechner "getaktet" ist, desto mehr Befehle kann er pro Sekunde abar- beiten und desto schneller ist er; wes- halb die Taktfrequenz häufig auch als ein Maß für die Rechengeschwindigkeit eines Rechners herangezogen wird. Der C64 schneidet hier, aufgrund seiner schon etwas älteren Entwicklung und dem Fakt, daß er halt einfach nur ein Home- computer (der für jeden erschwindlich sein soll) ist, relativ schlecht ab. Mit etwa 1 MHz ist er vergleichsmäßig lang- sam, was jedoch immer noch affenschnell ist! Um genau zu sein sind es 985248.4 Hz (also knapp ein Mhz). So zumindest bei der europäischen Version, die Sie ja alle haben sollten. Die amerikanischen 64er sind sogar noch ein wenig schneller (nämlich 1022727.1 Hz), was für uns je- doch unerheblich ist. Doch was bedeutet diese Zahl nun eigent- lich für uns. Sie bedeutet schlichtweg, daß wir pro Sekunde genau 985248.4 Taktzyklen haben; und die brauchen wir ja als Timer-Trigger. Damit wird es ein- fach, die Dauer zwischen zwei Counter- Unterläufen zu berechnen. Angenommen, Sie wollten (aus welchem Grund auch im- mer), daß jede 1/16-Sekunde ein Inter- rupt ausgelöst würde. Demnach müßten Sie den Systemtakt einfach durch 16 dividie- ren und das Ergebnis in LO/HI-Byte auf- gespalten in die Register TA-LO und TA- HI schreiben. Konkret wäre das:
985248.4 / 16 = 61578.025 (dez.) $F08A (hex.) LO: $8A = 138 HI: $F0 = 240
Schreiben Sie diese beiden Werte nun in die Register 4 und 5 des CIA1, so wird der Systeminterrupt, der ja durch Timer A der CIA1 gesteuert wird, eindeutig verlangsamt (normalerweise tritt er näm- lich jede 1/60-Sekunde auf). Erkennen können Sie dies am langsameren Cursor- blinken, da auch das vom Systeminterrupt erledigt wird. Probieren Sie es doch einfach einmal, also:
POKE 56320+4,138:POKE 56320+5,240 (56320 ist die Basisadresse von CIA1!)
So. Nun wissen Sie also, wie man den Timer A eines CIA programmiert. Da die- ser für IRQs jedoch schon vom Betriebsy- stem benutzt wird, und es da möglicher- weise Timing-Probleme gibt, wenn wir eine eigene IRQ-Routine schreiben wol- len, die zwar parallel zum Betriebsy- stem-IRQ läuft, aber öfter oder weniger oft als dieser auftreten soll, so gibt es für uns auch die Möglichkeit auf Ti- mer B auszuweichen. Dieser ist absolut analog zu bedienen, nur, daß wir die Timerwerte diesmal in die Register 6 und 7 der CIA schreiben müssen (TB-LO und TB-HI). Desweiteren wird er vom Control- Register-Timer-B (CRB) gesteuert - Regi- ster 15 der CIA also. Bis auf kleinere Unterschiede, ist der Aufbau der Regi- ster CRA und CRB identisch. Hier die Unterschiede: 1) Grundsätzlich sind die Funktionen der Bits 0 bis 4 gleich, jedoch mit dem Unterschied, daß sich die Bits 1 und 2 nicht mehr auf PB6 sondern auf PB7 beziehen. 2) Bit 5 und 6 steuern den IN-MODE von Timer B (bei CRA war das NUR Bit 5). Hierzu ergeben sich 4 verschiedene Timer-Trigger-Modi:
Bit 5 6 Funktion -------------------------------------
0 0 Zähle Systemtakt 0 1 Zähle CNT-Flanken 1 0 Zähle Unterläufe von Timer A 1 1 Zähle Unterläufe von Timer A, wenn CNT=1
Somit kann Timer B bei Steuerung durch Hardware bestens eingesetzt werden, da mehr externe Steuermö- glichkeiten (durch CNT) vorhanden sind. 3) Bit 7 steuert die Alarmfunktion der Echtzeituhr. Ist es gesetzt, so wer- den die Werte, die man in die Regi- ster der Echtzeituhr schreibt (dazu auch in einem späteren Kursteil) als Alarmzeit genommen. Ist es gelöscht, so kann die normale Uhrzeit einge- stellt werden. Da die beiden Register CRA und CRB eben- falls eine sehr wichtige Funktion bei den vielseitigsten Anwendungen erfüllen, habe ich Ihnen einmal eine grafische Öbersicht angefertigt:
Das Interrupt-Control-Register (ICR) ----------------------------------------
So. Nun wissen wir also, wie man die Timer der CIAs steuert. Interrupts haben wir nun aber noch lange nicht! Die Timer dienen ja lediglich als Interrupt- Quellen. Wir benötigen noch ein weiteres Register des CIA um ihm zu sagen, daß beim Unterlauf eines Timers auch die IRQ-Leitung (beim CIA1, bzw. NMI-Leitung beim CIA2) des Prozessors zu aktivieren ist, um einen Interrupt zu signalisie- ren. Dieses Register ist Register 13 eines CIAs, das Interrupt Control Regi- ster (ICR). Es ist wohl das wichtigste Register im CIA überhaupt, denn ohne es könnten wir mit ihm überhaupt nichts anfangen! Bevor wir also einen Timer zur Inter- rupterzeugung starten, sollten wir also immer im ICR auch angeben, daß dieser Timer Interrupts erzeugen soll. Darüber- hinaus gibt es auch noch eine ganze Men- ge anderer Interruptquellen, die dieses Register steuert. Ich gebe Ihnen hier einmal eine tabellarische Öbersicht der einzelnen Bits vom ICR. Die Bits müssen gestezt sein, um einen Interrupt aus- zulösen, wenn das entsprechende Ereignis eintritt: Bit0: Löse einen Interrupt aus, wenn Timer A unterläuft. Bit1: Löse einen Interrupt aus, wenn Timer B unterläuft. Bit2: Löse einen Interrupt aus, wenn die Alarmzeit der Echtzeituhr mit der aktuellen Zeit übereinstimmt. Bit3: Löse einen Interrupt aus, wenn das SDR (Serial Data Register) voll, bzw. leer ist (abhängig von der entsprechenden Betriebsart - he- raus- oder hereinrollen). Bit4: Löse einen Interrupt aus, wenn am Pin FLAG (am Userport heraus- geführt) ein Signal anliegt. Bit5: Unbelegt. Bit6: Unbelegt. Bit7: Doppelfunktion (siehe unten). Wie Sie sehen, ist es ganz einfach, be- stimmte Interruptquellen zu wählen. So- gar von externer Hardware können DIREKT Interrupts ausgelöst werden. Nun jedoch noch zu der Sonderfunktion von Bit 7. Man muß bem ICR nämlich un- terscheiden, ob man nun in das Register schreibt, oder ob man es ausliest. Es hat nämlich, wie die Timerregister auch, eine Doppelfunktion. Man unterscheidet zwischen einem Latch-Register, in dem die Interrupt-Maske (INT-MASK) gespei- chert wird und den Interrupt-Daten (INT-DATA). Schreiben wir in das ICR, so wird der geschriebene Wert zwar zwi- schengespeichert, jedoch können wir ihn nicht lesen. Denn wenn wir lesen, gibt uns das ICR augenblickliche Informatio- nen, ob und welches Interruptereignis eingetreten ist, nicht aber, welchen Wert wir vorher hineingeschrieben haben. An diese Doppelfunktion von Registern sollten Sie sich gewöhnen, denn wir wer- den noch öfter damit zu tun haben. Lesen wir nun aus dem ICR Daten aus, so zeigt uns Bit 7 an, ob eines der zuge- lassenen Interruptereignisse eingetreten ist, das heißt, daß Bit 7 immer dann gesetzt ist, wenn mindestens ein Bit von INT-MASK mit einem Bit von INT-DATA übereinstimmt. Dadurch haben wir eine einfache Kontrolle, ob ein Interrupt auch tatsächlich vom CIA ausgelöst wur- de, oder nicht doch von was anderem (wie zum Beispiel vom VIC, der ja die Raster- Interrupts auslöst). Duch eine einfache Abfrage mit dem Assembler-Befehl "BMI" (Branch on MInus), der ja den Zustand von Bit 7 überprüft, können wir schnell feststellen, von wo der Interrupt nun kommt. Schreiben wir in das ICR, so ist Bit 7 nochmal doppeldeutig: Ist es nämlich gelöscht, so wird jedes weitere 1-Bit sein korrespondierendes Maskenbit in INT-MASK löschen. Die ande- ren Bits bleiben unberührt davon. Es wird also quasi ein AND mit dem Wert den wir schreiben und dem Wert der in INT- MASK steht vollzogen. Ist Bit 7 jedoch gesetzt, so wird jedes weitere 1-Bit sein korrespondierendes Masken-Bit setzen. Die anderen bleiben ebenfalls davon unberührt. Diesmal wird also ein OR mit den beiden Werten voll- zogen. Damit können wir also problemlos ganz gezielt Bits im ICR setzen und lö- schen, ohne dabei aus Versehen andere Bits zu verändern. Auch hier will ich Ihnen eine grafische Öbersicht liefern, damit Sie die Bitta- belle vom ICR immer auf Papier parat haben können. Das wars dann mal fürs erste. Ich hoffe, ich habe Ihnen nun mit den (zugegeben) überaus trockenen Grundlagen der Interrupt-Programmierung, nicht das In- teresse am Thema dieses Kurses genommen. Keine Panik, im nächsten Monat werden wir uns dann einmal um die praktische Anwendung kümmern. Ich zeige Ihnen dann einmal den kompletten Ablauf des System- interrupts, auf dem wir dann unsere er- sten Schritte in Sachen Interrupt auf- bauen werden. Bis dahin Servus,
Ihr Uli Basters (ub)
CIA-Kurs: "Die Geheimnisse des Secret Service..." (Teil 2) ----------------------------------------
Herzlich willkommen zur zweiten Runde unseres CIA-Kurses. Nachdem ich Sie letzten Monat ja lange genug mit der trockenen Theorie von der Bedienung der Timer der CIAs gelangweilt habe, will ich jetzt einmal den Grundstein zur Pra- xis legen. Ich möchte mich diesmal mit dem eigentlichen Ablauf eines Interrupts beschäftigen und Ihnen einen solchen auch haarfitzelgenau anhand des System- interrupts erklären.
1) Der Prozessor und ein Interrupt. -----------------------------------
Wie ich schon im letzten Teil erwähnte, wird der Systeminterrupt vom Betriebs system des 64ers zur Erledigung ver- schiedener zyklischer Aufgaben genutzt. Er wird 60 mal in der Sekunde aufgerufen und von Timer A der CIA1 erzeugt. Dem- nach haben wir also einen IRQ (wir erin- nern uns: CIA1 erzeugt Impulse an der IRQ-Leitung des Prozsessors, CIA2 an der NMI-Leitung). Die Timerregister TALO und TAHI beinhalten die Werte 37 und 64. Der Timer zählt also von 16421 (=HI*256+LO) bis 0 herunter und löst dann einen In- terrupt aus. Das ist auch ganz logisch, denn wenn wir ja 60 Interrupts in der Sekunde haben wollen, dann müssen wir ja den Systemtakt durch 60 dividieren. Das Ergebnis hiervon ist 16420.8, aufgerun- det 16421! Ich hoffe Sie verstehen jetzt, warum ich mich zunächst um die Timer selbst gekümmert hatte, da Sie nun auch besseren Einblick in den Systemin- terrupt haben. Wenn Sie einmal ein bis- schen rumprobieren möchten, bitte: Schreiben Sie doch einfach einmal mit- tels POKE einen höheren oder niedrigeren Wert als 64 in TAHI (Reg. 5 von CIA1). Das Ergebnis sehen Sie dann am Cursor- blinken, was ebenfalls vom Systeminter- rupt erledigt wird. Der Cursor sollte nun entweder schneller (bei niedrigerem Wert) oder langsamer (bei höherem Wert) blinken. Übertreiben Sie die Werte je- doch nicht übermäßig, da auch die Tasta- turabfrage vom Systeminterrupt erledigt wird, und Sie so entweder einen Turbo- Cursor haben, mit dem bei eingeschalte- tem Key-Repeat (das ist bei meinem Floppy-Speeder-Betriebssystem nämlich der Fall, und ich bin eben beim Auspro- bieren natürlich prompt wieder darauf hereingefallen...) keine vernünftigen Eingaben gemacht werden können, ebenso wie bei einem ultralangsamen Cursor, wo es noch bis morgen dauern würde, einen rücksetzenden POKE-Befehl einzugeben. Dieser Trick wird übrigens oft benutzt, um Programme schneller zu machen. Je öfter nämlich ein Interrupt pro Sekunde auftritt, desto weniger Zeit hat der Prozessor, das momentan laufende Haupt- programm abzuarbeiten. Setzt man jedoch die Zahl der Interrupts pro Sekunde he- runter, oder schaltet man ihn sogar ganz ab (indem man den Timer einfach anhält, oder mittels des Assemblerbefehls SEI Interrupts ganz sperrt), so läuft das Hauptprogramm logischerweise schneller ab. Dies nur ein Tip am Rande. Was geht nun eigentlich in unserem 64er vor, wenn ein Interrupt abgearbeitet werden soll? Nun, zunächst einmal haben wir da einen Unterlauf von Timer A der CIA1. Diese legt sodann auch gleich ein Signal an die IRQ-Leitung des Prozessors an und markiert die Interruptquelle "Ti- mer A" in ihrem ICR. Folgende Prozesse gehen nun im Prozessor vor: 1) Der Prozessor überprüft nun nach je- dem abgearbeiteten Befehl den Zustand der Unterbrechungsleitungen (IRQ, NMI). Ist eine davon gesetzt, und ist der zugehörige Interrupt auch freige- geben, so beginnt er damit, die Un- terbrechung zu bearbeiten. Hierzu müssen zunächst die wichtigsten Daten
auf den Stapel gerettet werden. Das sind in der hier gezeigten Reihenfol- ge: * HI-BYTE des Programmzählers * LO-BYTE des Programmzählers * Prozessorstatusregister
Der Programmzähler ist ein Prozessor- internes Register, das anzeigt, an welcher Speicheradresse, der nächste zu bearbeitende Befehl liegt. Das Prozessorstatusregister beinhaltet die Flaggen, die dem Prozessor bei Entscheidungen weiterhelfen (ich ver- weise da auf die letzten Kursteile des Assemblerkurses meines Kollegen RALF TRABHARDT). 2) Der Prozessor setzt sich selbst das Interrupt-Flag und verhindert so, daß er durch weitere Interrupts gestört wird. 3) Ganz am Ende des Speichers (in den Adressen $FFFA-$FFFF) sind 6 Bytes für das Interrupt-Handling reser- viert. Dort stehen insgesamt 3 Vekto- ren, die dem Prozessor zeigen, bei welcher Unterbrechung er wohin sprin- gen muß, um einen Interrupt zu bear- beiten. Diese Vektoren heißen im Fachjargon übrigens auch Hardware- Vektoren. Hier einmal eine Aufli- stung:
Interrupt Vektor Adresse -------------------------------------
NMI $FFFA/$FFFB $FE43 (65091) RESET $FFFC/$FFFD $FCE2 (64738) IRQ,BRK $FFFE/$FFFF $FF48 (65352)
Da wir ja einen IRQ behandeln, holt sich der Prozessor jetzt also die Adresse, auf die der Vektor in $FFFE/$FFFF zeigt in den Programmzäh- ler und beginnt so damit eine Jobrou- tine für den IRQ abzuarbeiten. Diese Jobroutine wollen wir uns jetzt einmal genauer ansehen. Vorher jedoch noch eine kleine Erläuterung. Wie Sie ja sehen, wird der Vektor für den IRQ auch als Vektor für BRK-Unterbrechungen be- nutzt. Der BRK-Befehl sollte Ihnen ja vielleicht bekannt sein (wenn Sie sich mit Maschinensprache auskennen). Er hat ansich ja keinen direkten Verwendungs- zweck, jedoch wird er von vielen Moni- tor-Programmen zum Debuggen benutzt. Wie Sie ebenfalls sehen können, hat er glei- che Priorität wie ein IRQ, und man kann Ihn also auch als eigenen Interrupt an- sehen. Vielmehr ist der BRK-Befehl die Unterbrechungsquelle für BRK-Interrupts. Wie man mit ihm umgeht, will ich Ihnen später zeigen. Werfen wir nun jedoch einmal einen Blick auf die Jobroutine ab $FF48 - diese be- findet sich natürlich im Betriebssystem- ROM, weshalb ich Ihnen hier auch einen Auszug daraus liefern möchte (ich habe dies in Form einer Grafik getan, damit ich ausführlichere Kommentare zu den einzelnen Programmschritten geben kann). Bitte laden Sie hierzu den 2. Teil des CIA-Kurses.
CIA-Kurs: "Die Geheimnisse des Secret-Service" (Teil 3) ----------------------------------------
Hallo zusammen zum dritten Teil des CIA-Kurses. Diesen Monat geht's ran an die Bulletten, wir wollen uns endlich einmal um die konkrete Programmierung der CIAs und somit von Interrupts küm- mern! Letztes Mal hatten wit ja den Sy- stem-IRQ behandelt, dessen Funktionsauf- bau wir heute brauchen werden. Ich werde Ihnen anhand eines Beispielprogramms einmal zeigen, wie wir den System-IRQ für uns benutzen können. Also los geht's... Wie Sie nun ja wissen, läuft im C64 im Normalfall ja schon ein IRQ, der Syste- minterrupt nämlich. Er wird 60 Mal pro Sekunde aufgerufen und arbeitet eine Jobroutine im Betriebssystem-ROM ab, die gewisse interne Aufgaben (die wir im letzten Monat ja schon besprochen hat- ten) abarbeitet. Wollen wir einen eige- nen IRQ schreiben, so ist die einfachste Methode hierfür ein "Einklinken" in den System-IRQ. Es hat den Vorteil, daß wir uns (wenn es sich um zyklisch wiederkeh- rende Aufgaben handelt) nicht noch umständlich um das Programmieren eines CIA-Timers kümmern müssen, sondern ein- fach die vorgegebene Timerprogrammierung übernehmen. Sie erinnern sich ja bestimmt noch da- ran, daß beim Auftreten eines IRQ- Ereignisses, der Prozessor über einen Vektor in $FFFE/$FFFF auf eine kleine Jobroutine verzweigt, die feststellt, ob der Interrupt, der aufgetreten ist, ein IRQ- oder ein BRK-Interrupt war. Diese Routine verzweigte dann wiederum über zwei Vektoren im RAM auf verschiedene Jobroutinen für die beiden Interrupts. Diese waren:
* $0314/$0315 (dez. 788/789) für den IRQ * $0316/$0317 (dez. 790/791) für den BRK
Wollen wir also, daß der Prozessor jetzt auf eine eigene Routine verzweigt, dann müssen wir einfach den entsprechenden Vektor hier dahingehend verändern (im Fachjargon spricht man auch von "verbie- gen"), daß er anschließend auf unsere eigene IRQ- bzw. BRK-Routine zeigt. Ich habe Ihnen, wie schon erwähnt, ein- mal ein kleines Beispiel vorbereitet, das dies verdeutlichen soll. Das Problem das ich lösen wollte, war folgendes: Stellen Sie sich vor, Sie programmierten gerade eine Anwendung und Sie wollten, daß Ihr Programm von Zeit zu Zeit Feh- ler- oder Benutzungshinweise auf dem Bildschirm ausgibt. Damit das ganze auch noch optisch gut ins Auge fällt, wäre es angebracht, daß diese Mitteilung längere Zeit aufblinkt, so daß sie dem Benutzer buchstäblich "ins Gesicht springt". Dies ist eine Aufgabe, die sich hervor- ragend über einen IRQ lösen läßt. Es hat sogar zusätzlich noch den Vorteil, daß das Hauptprogramm vollkommen unberührt von dem wäre, was da angezeigt werden soll. Es genügt also, eine Interrupt- Routine zu aktivieren, die dann so ganz nebenher zum Beispiel die Mitteilung "Das Programm rechnet!" ausgibt, wärend das Hauptprogramm tatsächlich gerade mit irgendeiner Berechnung beschäftigt ist. Wollen wir uns ansehen, wie man eine solche Routine nun realisiert. Zunächst einmal brauchen wir natürlich eine eige- ne IRQ-Routine. Sie soll nachher die Nachricht auf dem Bildschirm ausgeben. Ich habe mich da einmal auf die letzte Bildschirmzeile festgelegt. Das ist ein Randbereich, den man gut nutzen kann. Zur eigentlichen Textausgabe brauchen wir zwei kleine Unterroutinen - eine, die den Text schreibt, und eine die ihn wieder löscht, damit wir somit ein Blin- ken erzeugen. Der Einfachheit halber, habe ich mich dazu entschieden, den auszugebenden Text im Bildschirmcode irgendwo im Speicher abzulegen. Dann genügt es nämlich (im Gegensatz zum ASCII-Code), den Text ein- fach in den Bildschirmspeicher einzuko- pieren, was durch eine kleine, aber fei- ne Schleife sehr schnell erledigt wird. Der Aufbau dieser Routine verlangt es mitunter auch, daß das letzte Zeichen des Textes den binären Wert 0 hat. Im Bildschirmcode ist dies der Klammeraffe (" "). Zum Löschen der Mitteilungszeile genügt es, diese mit dem Bildschirmcode für "SPACE" aufzufüllen. Das wäre gleich dem Vorgang, wenn Sie mit dem Cursor in die unterste Zeile des Bildschirms fahren und nun 40 Mal die SPACE-Taste drücken würden. Der Bildschirmcode für das Zei- chen SPACE ist 32 ($20). Hier nun also die beiden Routinen, die diese Aufgaben übernehmen. DOMSG gibt den Bildschirmtext aus, und BLANK löscht die Mitteilungszeile. Zu DOMSG sei noch zu sagen, daß die Anfangsadresse des auszugebenden Textes, vorher schon von der Interrupt-Initialierungsroutine in die beiden Adressen nach dem LDA-Befehl geschrieben wurde. Das Programm hat sich also selbst verändert. Dies ist (für uns) die einfachste und sinnvollste Lö- sung, die man in einem solchen Fall an- wedet. Zu jener Initialisierungsroutine kommen wir später. Ich möchte übrigens darauf hinweisen, daß alle hier erwähn- ten Routinen und Programme mit dem HYPRA-ASS-Assembler aus der Computer- zeitschrift "64'er" erstellt wurden. Leser, die das Eigabeformat und die Be- dienungsweise dieses Assemblers kennen, können sich also glücklich schätzen. Trotzdem werde ich die auftauchenden Assembler-Besonderheiten hier erklären, damit Sie die Programme auch mit jedem anderen Assembler eingeben können. Sie sollten auf jeden Fall wissen, daß in diesem Assembler anstatt absoluter Sprungadressen sogenannte "Labels" be- nutzt werden. Das sind Sprungmarken, die einfacher zu handhaben sind, da man so nur auf einen Namen springen muß, dessen absolute Adresse der Assembler berech- net. Assemblerprogrammier sollten sich aber sowieso mit Labels auskennen, da sie heutzutage in jedem Assembler Verwendung finden.
----------------- DOMSG LDY #00 Y-Reg. als Zeiger initialisieren. LOOP1 LDA $C000,Y Zeichen holen (An- fangsadresse Text plus Y-Offset). BEQ L3 War das letzte Zei- chen gleich 0 (= ), dann ENDE. STA $07C0,Y Ansonsten Zeichen in Bildschirmspecher schreiben. INY Zähler erhöhen. BNE LOOP1 Unbedingter Sprung. ------------------ BLANK LDY #39 Y-Reg. als Zeiger initialisieren (39+1=40 Zeichen fül- len). LDA #32 Bildschirmcode für SPACE in Akku holen. STA $07C0,Y Akku in Bildschirm- speicher entleeren. DEY Zähler erniedrigen. BPL LOOP2 Solange wiederholen, bis 0 unterschritten wird (Y ist dann ne- gativ). RTS Und Tschüß! ------------------
Die Anfangsadresse der Mitteilungszeile (im Folgenden MSG-Zeile; MSG = Message = Mitteilung) ist logischerweise die Adresse des ersten Zeichens in der 25. und letzten Zeile des Bildschirms. Sie errechnet sich aus der Basisadresse des Bildschirmspeichers (normalerweise=1024) plus der Zeilenanzahl-1 multipliziert mit 40. Da unsere Zeilenanzahl 25 ist, lautet die Rechunug für uns:
1024+(25-1)*40=1024+24*40=1984 (=$07C0)
Was der Adresse in unseren beiden Routi- nen entspricht! Desweiteren muß ich noch auf eine Beson- derheit in DOMSG hinweisen. Die Schleife wird erst dann verlassen, wenn das zu- letzt gelesene Zeichen 0 ist (wir Erin- nern uns - das ist die Endmarkierung). Über BEQ springen wir dann auf den RTS- Befehl der BLANK-Routine. Der Branch- Befehl BNE am Ende der Routine ist ein sogenannter unbedingter Sprung. Die hier abgefragte Bedingung ist immer erfüllt, weil das vorher inkrementierte Y- Register nie die 0 erreichen wird, da die maximale Zeichenanzahl ja 40 ist. Mehr wäre unsinnig, denn man sähe diese Zeichen ja gar nicht auf dem Bildschirm. Versierte Assembler-Programmierer kennen diese Art des Springens. Sie hat den Vorteil der Speicherersparnis (ein JMP- Befehl belegt immer 3 Bytes, ein Branch-Befehl, wie BNE nur 2) und ist zusätzlich ganz sinnvoll, wenn man Rou- tinen relokatibel halten möchte. Ver- schiebt man ein Assembler-Programm im Speicher, so ist es nur dann auch an anderer Stelle lauffähig, wenn keine absoluten Zugriffe auf programminterne Adressen stattfinden. Der JMP-Befehl springt ja immer absolut und somit auf die alte Adresse. Der BNE-Befehl jedoch ist relativ adressiert. Er merkt sich nur um wieviele Bytes im Speicher er nach vorne, oder nach hinten springen muß. Wird der Sprungbereich eines Branchbe- fehls nicht überschritten (+127 und -128 Bytes vom Befehl selbst entfernt), so ist der Einsatz von unbedingten Sprüngen sehr sinnvoll (insofern möglich, also wenn man über den Inhalt eines bestimm- ten Prozessorflags eine eindeutige Aus- sage machen kann). Doch zurück zu unserer Message-Ausgabe- Routine. Außer den beiden Routinen zur Textausgabe, brauchen wir auch die IRQ- Routine selbst, die den Aufruf dieser beiden Routinen steuert. Legen wir nun also einmal fest, daß der Message-Text jeweils einmal pro Sekunde blinken soll. Das heißt im Klartext, daß wir zunächst einmal den Text auf den Bildschirm schreiben müssen, wo er eine halbe Sekunde stehen bleibt und ihn an- schließend wieder für eine halbe Sekunde löschen. Dieser Vorgang wiederholt sich nun beliebig oft. Damit das Ganze aber auch irgendwann einmal ein Ende hat, müssen wir nach einer bestimmten Anzahl von Blinkzyklen die Interruptroutine auch wieder abschalten. Da immer alle halbe Sekunde eine der beiden Ausgaben getätigt werden soll, müssen wir so logischerweise alle 30 Interrupts eine solche tätigen (wir wol- len ja den Systeminterrupt benutzen, der wie gesagt 60 Mal pro Sekunde auftritt). Für all diese Funktionen brauchen wir insgeamt 3 verschiedene Zwischenspei- cher: 1) Eine Speicherzelle, die als Inter- ruptzähler dienen soll. Sie zählt 30 Interrupts mit und veranlaßt dann die IRQ-Routine eine der beiden Ausgaben zu tätigen. Dieser Speicherzelle wol- len wir den schlichten Namen COUNTER (="Zähler") geben. 2) Damit die IRQ-Routine auch immer weiß, ob sie nun Text ausgeben, oder Text löschen soll, brauchen wir auch noch eine Speicherzelle, in der ver- merkt ist, welcher Ausgabemodus als nächstes benötigt wird. Ich nenne diese Speicherzelle einmal MSGMODE. 3) Zuletzt brauchen wir noch eine Spei- cherzelle, die mitzählt, wie oft der Text nun geblinkt hat. Ist eine ge- wisse Anzahl erreicht, so kann die IRQ-Routine in eine Routine verzwei- gen, die diese selbst beendet. Diese Adresse habe ich PULSE genannt. Die drei soeben erwähnten Namen werden alle in dem nun folgenden Source-Listing für die IRQ-Routine verwendet, so daß Sie jetzt also über deren Verwendungs- zweck informiert sind. Ich habe diesen Namen natürlich auch schon absolute Adressen zugewiesen. HYPRA-ASS benutzt dafür den Pseudo-Opcode ".EQ" für "is EQual" (="ist gleich"). Schauen Sie sich hierzu auch einmal den Sourcecode zu MSGOUT an (auf der Vorderseite dieser MD als "MSGOUT-ROM.SRC"). Die Zwischenspeicher belegen in der oben angezeigten Reihenfolge die Adressen $FB, $FC, $FD. Diese sind allesamt aus der Zeropage, und (wie die fleißigen Assembler-Programmierer unter Ihnen si- cher wissen) vom Betriebssystem nicht genutzt, weshalb wir sie für unsere Zwecke verwenden können. Jetzt brauchen wir eine Initialisie- rungsroutine, die die neue IRQ-Routine in den System-IRQ einbindet und sie so- mit im System installiert. Das hier ist sie (im Source-Listing auf dieser MD steht sie ganz am Anfang und heißt MS- GOUT):
------------ SEI Alle weiteren IRQs sperren (wegen, des Verbiegens der Vektoren). STA PULSE Im Akku steht die Blinkan- zahl; also merken wir sie uns. STX LOOP1+1 LO-Byte der Anfangsadresse des Textes wird in DOMSG eingesetzt (LOOP1 ist ein Label davon, siehe oben). STY LOOP1+2 Dasselbe mit dem HI-Byte. ------------ LDX #<(IRQ) LO-Byte der Anfangsadresse
der neuen IRQ-Routine laden (siehe unten). LDY #>(IRQ) HI-Byte laden. STX $0314 LO-Byte des Vektors auf unsere Routine ausrichten. STY $0315 HI-Byte des Vektor auf un- sere Routine ausrichten.
------------ LDA #01 Initialisierungswert in Akku laden. STA COUNTER Zählregister damit initia- lisieren. STA MSGMODE Mode-Register damit initia- lisieren. CLI Alle Voreinstellungen getä- tigt; wir können den IRQ wieder freigeben. RTS Und Tschüß! ------------
Die Routine ist in 3 Abschnitte geglie- dert. Im ersten Abschnitt werden zunächst die Aufruf-Parameter gemerkt. Diese sind: * Die Anzahl der Blinkvorgänge steht im Akku. * Die Anfangsadresse des auszugebenden Textes steht in LO/HI-Byte-Darstellung in X- und Y-Register. Der SEI-Befehl am Anfang ist sehr wich- tig. Wir müssen nämlich davon ausgehen, daß gerade dann ein Interrupt auftreten könnte, wenn das LO-Byte der neuen IRQ- Adresse schon gesetzt ist, das HI-Byte jedoch noch nicht. Dann zeigt der Vektor irgendwo in den Speicher hinein. Tritt jetzt ein IRQ auf, dann springt der Pro- zessor in die Pampas und verabschiedet sich meistens danach. Um dem vorzubeu- gen, muß man einfach alle IRQs verhin- dern, was ja mit dem SEI-Befehl erzielt wird. Der Prozessor ignoriert jetzt die Tatsache, daß da die CIA1 gerade einen Interrupt meldet und wir können in Ruhe den Vektor neu setzen. Diese Aufgabe erledigt der zweite Teil unserer Routine. Die Anfangsadresse der neuen IRQ-Routine wird in X- und Y- Register geholt und in die Speichera- dressen unseres Zeigers geschrieben. Im dritten und letzten Teil initialisie- ren wir noch zwei der drei Variablen (PULSE wurde durch das Speichern am An- fang der Routine schon gesetzt). Den COUNTER laden wir mit 1, damit gleich beim nächsten Interrupt eine Ausgabe erfolgt (siehe auch unten). MSGOUT kann zwei verschiedene Zustände haben. Entwe- der es steht dort 0, dann soll bei der nächsten Ausgabe der Text gedruckt wer- den, oder wir haben dort eine 1, dann soll die MSG-Zeile gelöscht werden. Ich initialisiere hier mit 1, damit beim ersten Aufruf die Zeile erst einmal gelöscht wird. Würden wir zuerst den Text schreiben, könnte es uns passieren, daß in den verbleibenden Zeichen (vo- rausgesetzt der Text ist weniger als 40 Zeichen lang) noch alter "Zeichenmüll" im Bildschirmspeicher steht.
Kommen wir nun endlich zur Interrupt- rountine selbst. Hier einmal das Li- sting: ----------------- IRQ DEC COUNTER Zähler herunterzäh- len. BEQ L1 Wenn Zähler=0, dann verzweigen auf Ausga- be. JMP SYSIRQ Sonst springen wir auf den System-IRQ. ----------------- L1 LDA MSGMODE Welcher Ausgabemodus? BNE L2 Ungleich 0, also verzweigen auf "Zeile löschen"! ----------------- INC MSGMODE Schon mal den MSGMODE auf 1 schalten. JSR DOMSG MSG ausgeben. JMP PRP Und Ausgaberoutine
verlassen. ------------------
L2 DEC MSGMODE Schon mal den MSGMODE auf 0 schalten. JSR BLANK Und MSG-Zeile lö- schen. ------------------ DEC PULSE Wenn Zeile gelöscht, ist ein Blinkvorgang abgeschlossen. Also Zähler für Blinks erniedrigen. BPL PRP Wenn noch nicht die 0 unterschritten wurde, Ausgaberoutine ver- lassen. ------------------ LDX #<(SYSIRQ) Wenn ja, dann haben wir oft genug ge- blinkt... LDY #>(SYSIRQ) ...also LO/HI-Byte der Adresse vom Sy- stem-IRQ laden. STX $0314 Und den IRQ-Vektor... STY $0315 ...wieder auf den System-IRQ setzen. JMP SYSIRQ Eigenen IRQ mit Sprung auf System-IRQ beenden. ------------------ PRP LDA #30 Akku laden... STA COUNTER ...und den IRQ-Zähler neu initialisieren. JMP SYSIRQ Auch hier beenden wir den eigenen IRQ mit einem Sprung auf den System-IRQ. ------------------
(Anm. d. Red.: Bitte Laden Sie jetzt den zweiten Teil des CIA-Kurses aus dem Kursmenü!) Hier die Erklärung: Nachdem wir die Initialisierungsroutine von vorhin aufgerufen haben, zeigt der IRQ-Vektor jetzt also auf die Routine "IRQ". Die CIA1 signalisiert nun einen Timerunterlauf in Form eines Signals an den Prozessor. Dieser springt daraufhin auf die Jobroutine ab $FF47, wo die Pro- zessorregister auf den Stapel gerettet werden und über den IRQ-Vektor unsere Routine angesprungen wird. Diese erniedrigt nun also den COUNTER und prüft, ob er schon 0 ist. Das ist der Fall, da wir den COUNTER ja mit 1 initialisiert hatten, und er soeben auf 0 abgezählt wurde. Das Programm verz- weigt deshalb also auf das Label "L1". Dort wird jetzt geprüft, welcher Ausga- bemodus eingestellt ist. Da MSGMODE auf 1 steht gehts jetzt also gleich weiter zu "L2", wo zunächst einmal MSGMODE auf 0 gezählt wird. Durch einen Aufruf von "BLANK" wird die MSG-Zeile gelöscht. Dies heißt für uns auch, daß einmal ge- blinkt wurde. Also müssen wir jetzt den Zähler für die Anzahl der Blinks um 1 erniedrigen. Gehen wir einmal davon aus, daß wir die Initialisierungsroutine mit einer 10 im Akku aufgerufen hatten. So- mit ist der Inhalt von PULSE jetzt, nach dem Herunterzählen 9. Das heißt, daß die 0 noch nicht unterschritten wurde und deshalb wird beim folgenden Branch- Befehl auch gleich auf das Label PRP verzweigt. Dort steht eine kleine Job- routine, die unseren IRQ wieder beendet. Der COUNTER wird hier mit 30 neu geladen und das Programm verzweigt anschließend auf den System-IRQ, der nun regulär ab- gearbeitet wird, und der den Interrupt wieder beendet, indem er die alten Pro- zessorregister zurückholt und den Pro- zessor mittels RTI wieder in das alte Programm, das bearbeitet wurde, als der Interrupt auftrat, zurückschickt. Das Label SYSIRQ beinhaltet also die Sprungadresse des System-IRQs, wie Sie anhand des Source-Codes erkennen können. Ich habe dort nämlich wieder mittels ".EQ" eine Zuweisung an diesen Labelna- men gemacht. Bei dem folgenden IRQ, zählt unsere Rou- tine wieder den COUNTER um 1 herunter. Diesmal jedoch, ist diese Speicherzelle noch nicht 0, weshalb die Routine auch nicht in die Ausgaberoutine ab "L1" verzweigt, sondern gleich auf den Sy- stem-IRQ springt. Dies geht nun 30 In- terrupts lang so weiter, erst dann gibt es wieder eine 0 im COUNTER. Unsere Rou- tine verzweigt jetzt wieder in die Aus- gaberoutine. Dort wird wieder der Ausga- bemodus geprüft, der diesmal jedoch 0, also "Text ausgeben" ist. Dort müssen wir jetzt MSGMODE dann auf 1 hochzählen und dann mittels DOMSG unsere Mitteilung auf dem Bildschirm ausgeben. Anschlie- ßend können wir den Interrupt wieder über den System-IRQ verlassen. Diese Vorgänge werden nun solange wie- derholt, bis PULSE die 0 unterschreitet. Dann nämlich wird nicht auf PRP ver- zweigt, sondern es werden gleich die Befehle hinter dem BPL-Befehl abgearbei- tet. Sie setzen den IRQ-Vektor wieder auf den System-IRQ zurück, so daß also unsere eigene Routine nicht mehr ange- sprungen wird. Ihre Aufgabe ist nun erfüllt. Diesmal brauchen wir das Interrupt-Flag übrigens nicht zu setzen, da innnerhalb eines Interrupts dieses Flag ja schon durch den Prozessor gesetzt wurde (letz- ten Monat hatte ich das ja genauer erklärt). Auch jetzt verzweigen wir wieder auf den System-IRQ um unseren Interrupt zu been- den. Das wäre nun also eine Routine, die in den System-IRQ eingebunden ist. Das Be- triebssystem springt sie direkt an, und sie selbst fährt nach ihrer eigenen Ar- beit gleich mit dem System-IRQ fort. So daß dieser also auch weiterhin arbeitet. Der Vorteil ist schnell ersichtlich. Laden Sie doch einfach einmal das Pro- gramm "MSGOUT.CODE" auf der Vorderseite dieser MD. Es ist ein Assembler- Programm, das mit "SYS 4096*8", oder "SYS 32768" aufgerufen wird. Der MSG- Text "DAS IST EIN IRQ UEBERS BETRIEBS- SYSTEM!" blinkt nun in der untersten Bildschirmzeile. Währenddessen haben wir aber immer noch den Cursor auf dem Bild- schirm, den wir auch weiterhin benutzen können. Würden wir unsere IRQ-Routine nicht über den System-IRQ wieder verlas- sen, wäre das auch nicht der Fall. Da- durch können Sie also während Ihren ei- genen IRQs die Tastatur weiterhin ver- wenden! Kommen wir nun zu einem weiteren Pro- blem. Angenommen, Sie wollten eine Mit- teilung ausgeben, wärend zum Beispiel gerade eine Routine damit beschäftigt ist, im RAM unter dem ROM Daten zu ver- schieben. In dem Fall können Sie ja nicht mehr über den System-IRQ springen, da das Betriebssystem-ROM ja abgeschal- tet wäre. Man kann dies tun, indem man einige Bits im Prozessorport verändert. Dieser wird durch die Speicherzelle $0001 repräsentiert. Dort steht norma- lerweise der Wert 55 (=$37), was für den Prozessor die Speicherkonfiguration: * BASIC-ROM bei $A000-$BFFF eingeschal- tet. * I/O-Bereich bei $D000-$DFFF (wo auch die Register der beiden CIAs liegen) eingeschaltet. * Betriebssystem-ROM bei $E000-$FFFF eingeschaltet. Wollen wir nun auf das RAM unter dem BASIC- und dem Betriebssystem-ROM zu- greifen, so kann man letztere mit dem Schreiben des Wertes 53 (=$35) in den Prozessorport abschalten. Das I/O-ROM, das wir ja noch brauchen (wegen der CIA1), bleibt dabei eingeschaltet. Der System-IRQ ist somit nicht mehr für uns vorhanden und ebenso auch nicht die Jobroutine, die über den IRQ-Vektor $0314/$0315 auf entsprechende IRQ- Routinen springt. In dem Fall müssen wir die Steuerung des Interrupts selbst bewältigen. Das heißt zunächst einmal, daß wir diesmal die Prozessorregister selbst retten müssen (was ja normalerweise die Jobroutine bei $FF47 macht - siehe Teil 2 des CIA- Kurses), und sie auch entsprechend wie- der zurückholen müssen. Als IRQ-Vektor zählt jetzt auch nicht mehr der bei $0314/$0315, sondern wir benutzen den Hardware-Vektor direkt. Da das ROM dort ja abgeschaltet ist, können wir also problemlos die Speicherzellen $FFFE/$FFFF mit einem Vektor auf unsere IRQ-Routine beschreiben. Zur Demonstration habe ich Ihnen unsere MSGOUT-Routine einmal umgeschrieben, so daß sie auch ohne Betriebssystem aus- kommt. Der Source-Code hierzu ist eben- falls auf dieser MD zu finden, unter dem Namen "MSGOUT-RAM.SRC". Im Prinzip brau- chen wir nur ein paar Befehle zu der ROM-Version von MSGOUT hinzuzufügen, um die RAM-Version zu erhalten. Das wich- tigste ist hierbei die Initialisierungs- routine, die ich Ihnen hier nun auffüh- ren möchte: ------------ SEI Interrupts wie immer speren. STA PULSE Blinkzähler merken. STX LOOP1+1 Anfangsadresse des... STY LOOP1+2 ...Textes merken. ------------ LDX #<(IRQ) Anfangsadresse der neuen... LDY #>(IRQ) ...IRQ-Routine laden. STX $FFFE Und den Hardware-Vektor... STY $FFFF ...darauf ausrichten. ------------ LDA #$35 Wert für "ROM aus" laden... STA $01 ...und ab in Prozessorport. ------------ LDA #01 Initialisierungswert laden. STA COUNTER Zähler initialisieren. STA MSGMODE Modus initialisieren. CLI IRQs wieder freigeben. ------------ LOOP3:
LDA $01 Prozessorport laden. CMP #$37 Vergleiche mit "ROM an". BNE LOOP3 Ungleich, also weiter prü- fen. RTS Ansonsten Tschüß! ------------
Viel hat sich hier ja nicht geändert. Den ersten Abschnitt kennen wir ja noch von der alten MSGOUT-Routine. Diesmal müssen wir jedoch noch aus einem zweiten Grund die Interrupts sperren. Indem wir nämlich später noch das Betriebssystem- ROM abschalten, nehmen wir dem Prozessor die Grundlage für IRQs. Zum Einen ver- schwindet somit nämlich der Hardware- Vektor des Betriebssystems, zum Anderen auch alle Jobroutinen für den System- IRQ. Der Prozessor springt dann irgendwo im undefinierten RAM rum und hängt sich dann unweigerlich auf. Also jetzt geht nix mehr ab mit IRQs! Der zweite Abschnitt ist uns auch nicht so unbekannt. Diesmal setzen wir jedoch nicht den IRQ-Vektor $0314/$0315, son- dern den Hardware-Vektor für IRQs bei $FFFE/$FFFF. Das können wir getrost auch bei eingeschaltetem ROM tun (wie das hier der Fall ist), denn die geschriebe- nen Daten landen auf jedem Fall im RAM, da der Prozessor ins ROM ja nicht schreiben kann. Weil er aber irgendwo hin muß mit seinen Daten, schickt er sie automatisch ins RAM. Nur der Lesezugriff kommt aus dem ROM! Um auch dies zu ändern, verändern wir im dritten Abschnitt der Initialisierungs- routine dann auch noch den Prozessorport so, daß BASIC- und Betriebssystem-ROM abgeschaltet werden. Im vierten Abschnitt werden jetzt noch die variablen Register unserer IRQ- Routine initialisiert. Hier hat sich nichts geändert. Wichtig ist nun noch der letzte Ab- schnitt. Wir können nämlich unsere Ini- tialisierungsroutine nicht einfach so verlassen - zumindest nicht in diesem Beispiel. Denn normalerweise, wenn Sie sich im Eingabemodus des 64ers befinden, wird eine Eingabeschleife des BASICs durchlaufen, die ständig auf Eingaben prüft und dann bei entsprechenden BA- SIC-Befehlen, diese aufruft. Wenn Sie also mit SYS unsere IRQ-Routine starten, dann wird die Initialisierngsroutine nach ihrer Arbeit wieder in die BASIC- Eingabeschleife zurückkehren wollen. Die ist jetzt jedoch nicht mehr verfügbar, weil wir ja das BASIC-ROM abgeschaltet haben. Auch hier springt der Prozessor dann mitten ins leere RAM, verläuft sich dort und stürzt vor lauter Kummer ein- fach ab. Da ich die IRQ-Routine nun aber so programmiert habe, daß sie automa- tisch, wenn sie genug geblinkt hat, BA- SIC und Betriebssystem wieder einschal- tet, können wir dies als Kennzeichen dafür nehmen, daß die Grundvoraussetzun- gen für ein Verlassen der Initialisie- rungsroutine wieder gegeben sind. Des- halb also, habe ich eine Warteschleife hier eingebaut, die immer nur prüft, ob die ROMs mittlerweile wieder da sind. Erst wenn dieser Fall eintritt, wird zurückgesprungen! Soviel zur Initialisierung für eine Ar- beit unter dem ROM. Kommen wir nun zur Interrupt-Routine selbst. Auch sie muß leicht modifiziert werden. Auch hier will ich einen kurzen Abriß der hinzu- gefügten Befehle geben:
------------ IRQ PHA Akku retten. TXA X-Reg. in Akku schieben... PHA ...und retten. TYA Y-Reg. in Akku schieben... PHA ...und retten. ------------ (etc...)
So fängt nun die neue IRQ-Routine an. Anschließend folgen genau die Befehle, die auch in MSGOUT-ROM verwedet wurden. Bis auf einen Unterschied: wenn es näm- lich darum geht, den Interrupt wieder abzuschalten, weil wir oft genug ge- blinkt haben, lautet die Abschaltroutine folgendermaßen: ------------ LDA #$37 Alte Speicherkonfiguration STA $01 wieder einschalten. JMP SYSIRQ Und IRQ beenden, ------------ Hier wird einfach das ROM wieder einge- schaltet. Ein Zurückbiegen von Vektoren entfällt, da das ROM ja nun wieder da ist, und von nun an der System-IRQ wie- der treu seine Dienste leistet, so, als wäre nichts geschehen. Nach dieser Änderung des Prozessorports ist auch die Bedingung der Warteschleife der Initialisierungsroutine erfüllt, womit diese sogleich wieder zum guten alten BASIC zurückspringt. Eins muß ich jedoch noch hinzufügen. Wie sie ja noch wissen, verzweigt die ganze Routine ja noch öfter auf den System- IRQ, der dann ja gar nicht da ist! Dem- nach hätte ich diese Verzweigungen, die ich vorhin so leichtfertig übersprungen habe, ja erwähnen müssen! Nun, ich habe dieses Problem anders gelöst. Ich habe nämlich den ".EQ"- Pseudo-Opcode von "HYPRA-ASS", mit dem ich dem Label "SYSIRQ" die Adresse "$EA31" zuwies aus dem Source-Code ent- fernt, und dafür eine eigene SYSIRQ- Routine geschrieben. Der Name entspricht zwar nicht mehr dem, was vorher die Be- deutung war (SYStem-IRQ), aber so ging es halt am einfachsten. Diese neue Routine tut nun nichts ande- res, als den Interrupt ordnungsgemäß zu beenden. Wie wir ja noch aus dem letzten CIA-Kurs wissen, tut dies der System-IRQ am Ende auch. Die entsprechenden Befehle hierzu stehen ab Adresse $EA7E. Genau die habe ich nun in die neue "IRQ- Beenden"-Routine übernommen:
----------------- SYSIRQ LDA $DC0D ICR von CIA1 löschen. PLA Altes Y-Reg. vom Sta- pel in Akku holen... TAY ...und zurück in Y- Reg. schieben. PLA Altes X-Reg. vom Sta- pel in Akku holen... TAX ...und zurück in X- Reg. schieben. PLA Alten Akkuinhalt vom Stapel holen. RTI Und Interrupt beenden. -----------------
Die Bedeutung dieser Befehle sollte Ih- nen ja noch bekannt sein. Zunächst müs- sen wir weitere IRQs durch Löschen des ICR-Registers der CIA1 wider ermöglichen (dadurch werden ja die Interrupt- Quellen-Flags gelöscht, wie wir aus Teil 1 dieses Kurses noch wissen). Dann holen wir uns in umgekehrter Reihenfolge die Prozessorregister wieder vom Stapel run- ter, bevor wir den Interrupt mit RTI beenden. So. Das war's dann mal wieder für diesen Monat. Noch einen Hinweis zu den Pro- grammen bezüglich dieses Kurses: * Die beiden Source-Codes der MSGOUT- Routine können Sie übrigens auch le- sen, wenn sie nicht den HYPRA-ASS be- sitzen. Laden Sie hierzu ein Source- Code-File einfach an den BASIC-Anfang (also mit ",8" am Ende) und geben Sie LIST ein. Jetzt wird der Text zwar nicht automatisch formatiert, so wie HYPRA-ASS das normalerweise tut, aber lesen kann man das ganze schon. Zur Anschauung genügt es zumindest. * Das File "MSGOUT-CODE" beinhaltet bei- de Versionen von MSGOUT. Laden Sie es bitte absolut (also mit ",8,1") und starten Sie die einzelnen Routinen mit: - SYS 32768 für MSGOUT-ROM - SYS 32777 für MSGOUT-RAM Ich will mich jetzt von Ihnen verab- schieden. Nächsten Monat wollen wir uns dann einmal um die Kupplung von Timer A und Timer B einer CIA kümmern und auch noch den BRK-Interrupt behandeln. Bis dahin noch viel Spaß beim Herumprobieren mit IRQs. (ub)
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ü. IRQ-Kurs Teil 4.2 Wollen wir uns nun einmal anschauen, wie unser NMI-Programm aufgebaut ist. Die NMIs werden übrigens über Timer A der CIA2 ausgelöst, der denselben Wert wie der Timer des System-IRQs als Startwert bekommt (das wäre der Wert 16420=$4024). Hier also das Programm:
---------- NMI vorbereiten LDA $7F "NMI-Quellen sperren" laden, STA DD0D und in ICR von CIA2. LDX #$2B Zeiger auf eigene... LDY #$90 ...Routinen laden, STX 0318 und NMI-Vektor... STY 0319 ...setzen. LDX #$24 Timerwert LO-Byte. LDY #$40 Timerwert HI-Byte. STX DD04 In TALO und... STY DD05 ...in TAHI schreiben. LDA #$81 Wert laden... STA DD0D Timer A als Interruptquelle festlegen. STA DD0E Timer A starten. ---------- SID einstellen.
LDA #$0F Wert 15 für volle Lautstärke STA D418 ...ins Lautstärkeregister. LDA #$00 Zählregister für Tonfrequenz STA 02 initialisieren. RTS Und zurück.
---------- NMI-Routine CLI IRQs wieder freigeben. PHA Akku, TXA X-, PHA TYA und Y-Register auf Stapel PHA retten.
LDA #16 Wert für "Dreieckswelle aus" STA D404 ...in SID schreiben LDA 02 Zähler für Frequenz lesen... STA D401 und in Frequenz-HI schreiben INC 02 Zähler um 1 erhöhen. LDA #17 Wert für "Dreieckswelle an" STA D404 ...in SID schreiben.
LDA DD0D NMIs wieder freigeben PLA Akku, TAY X-, PLA TAX und Y-Register wieder vom PLA Stapel zurückholen. RTI Und Interrupt verlassen
---------- Im ersten Teil dieses Listings haben wir die Initialisierungsroutine für unseren NMI. Hier werden zunächst auf die schon beschriebene Art und Weise alle Inter- ruptquellen die von der CIA2 kommen, ge- sperrt. Anschließend wird der NMI-Vektor bei $0318/$0319 auf unsere eigene Routi- ne verbogen (die Routine beginnt bei $9000 im Speicher, weshalb die eigentli- che Interruptroutine bei $902B beginnt). Ist dies getan, müssen wir als nächstes den Timerwert in die Timerregister für Timer A laden (wie bei CIA1 sind dies die Register 4 und 5 - TALO und TAHI). Dies ist der wie oben schon beschriebene Wert $4024. Jetzt müssen wir nur noch den Timer A als NMI-Interruptquelle set- zen und ihn anschließend starten. Dies geschieht in den folgenden 3 Zeilen. Was der Wert $81 für Register 13 (ICR) und 14 (CRA) bedeutet wissen Sie ja schon aus Teil 1 dieses Kurses, als ich Ihnen die Funktionen dieser Register genauer erläutert habe. Zum Abschluß der Initialisierungsroutine müssen wir auch noch den SID darauf vor- bereiten, Sound auszugeben. Dazu haben wir auch noch genug Zeit, da der schon laufende Timer zum nächsten Interrupt noch lange genug zählen wird (ich hätte die SID-Initialisierung auch vorher an- bringen können). Also wird erst einmal die Lautstärke des Soundchips einge- schaltet, sowie den Anfangsfrequenzwert für unseren Soundeffekt in Adresse $02 in der Zeropage geschrieben. Diese Adresse wird als Zählregister be- nutzt, da man auf die Register des SID leider nicht zum Lesen zugreifen kann. Somit sind sie also auch nicht mittels INC hochzählbar. Nun sind alle Voreins- tellungen getätigt, und wir können wie- der zum aufrufenden Programm zurückver- zweigen. Im zweiten Teil des Listings sehen Sie nun die NMi-Routine selbst. Als erstes erlauben wir hier wieder das Auftreten von IRQs (sie erinnern sich, das Be- triebssystem hatte sie ja gesperrt). Nun werden nach der mittlerweile schon alt- bekannten Methode die Prozessorregister auf den Stapel gerettet, was wir bei NMIs ja IMMER von Hand machen müssen, da das Betriebssystem uns diese Arbeit leider nicht abnimmt. Nun kommt der Teil, in dem der nächste Ton gespielt wird. Hierzu wird erst ein- mal die Stimme 1 des SID abgeschaltet. Dies ist notwendig, weil wir keine Hüll- kurve vorher festgelegt hatten, die ei- nen Ton möglicherweise dauerhaft spielen würde. Deshalb befinden sich in den Hüllkurvenregistern die Werte 0, was bedeutet, daß ein Ton nur ganz kurz an- geschlagen wird und gleich wieder ver- stummt. Damit man aber die nächste Fre- quenz nun hört müssen wir die Stimme also erst noch ausschalten. Anschließend wird der Inhalt des Zählregisters $02 in das HI-Byte-Frequenzregister von Stimme 1 geschrieben ($D401), und der Zähler für den nächsten Interrupt um 1 erhöht. Nun schalten wir Stimme 1 wieder an, und zwar mit einer Dreieckswellenform - der Ton wird nun gespielt. Die Arbeit des NMIs ist getan, machen wir uns also daran, den Interrupt zu beenden. Ebenso altbekannt werden also das ICR wieder freigegeben, die Prozes- sorregister zurückgeholt und mittels RTI der NMI beendet. So. Nun wissen Sie also alles wissenwer- te über NMIs. Bis auf einige kleine Aus- nahmen, können Sie diese Interruptart genauso behandeln, wie einen IRQ. Da die CIAs ja baugleich sind, fällt die CIA- gesteuterte Programmierung von NMIs ja ebenso aus, wie beim IRQ. Ein ebenfalls ganz interessantes Anwen- dungsgebiet von NMIs ist die Steuerung von gewiseen Funktionen über einen Druck auf die RESTORE-Taste. Ich habe dies einmal bei einem Apfelmännchenprogramm benutzt. Diese Programme berechnen ja bekanntermaßen Grafiken aus der Mandel- brotmenge, die zwar ganz ansehlich sind, deren Berechnung jedoch oft Stunden, wenn nicht sogar Tage dauern kann. Ich wollte nun eben jenes Programm beschleu- nigen, indem ich den Bildschirm abschal- te. Wie Sie vielleicht wissen, kann durch diese Maßnahme eine Geschwingig- keitssteigerung von 5% erzielt werden, da der VIC bei abgeschaltetem Bildschirm micht mehr auf den Speicher des Compu- ters zugreifen muß, um die Daten für Grafiken, Zeichen, Sprites und ähnliches zu holen. Dadurch stört er den Prozessor nicht mehr beim Zugriff, wodurch dieser schneller arbeiten kann. Das Problem war nun jedoch, daß ich weiterhin sehen wollte, wie weit der Rechner nun mit der Grafikberechnung fortgefahren ist. Mit einer einfachen NMI-Routine war dies möglich. Ohne noch zeitraubend die Ta- statur abzufragen, habe ich einfach ei- nen neuen NMI "eingekoppelt", der nichts anderes tut, als den Bildschirm aus-, bzw. einzuschalten, wenn man die RESTO- RE-Taste drückt. Dieser Trick läßt sich vielfältig anwenden und ist einfach zu programmieren, hier das kleine Programm:
----------------- Initialisierung MAIN LDA #$7F CIA2-NMIs... STA CIA2+13 sperren. LDX #<(NMI) NMI-RAM-Vektor LDY #>(NMI) ...auf eigene STX $0318 ...NMI-Routine STY $0319 ...verbiegen. RTS Tschüß! ----------------- NMI-Routine NMI PHA Akku retten. LDA $D011 Register laden, EOR #16 Bildschirmbit inver- tieren. STA $D011 Und wieder speichern. PLA Akku zurückholen. RTI NMI-Ende. -----------------
Das ist tatsächlich alles! Das Programm ist im "Hypra-Ass"-Quellcode angegeben, dessen Sonderfunktionen ich Ihnen letzen Monat ja schon erklärte. Hier eine Doku- mentation: Im ersten Teil wird zunächst einmal die CIA2 als Interruptquelle gesperrt. Dies ist nicht unbedingt notwendig, da sie sowieso ausgeschaltet sein sollte, je- doch habe ich es hier zur Sicherheit einmal gemacht. Desweiteren wird der NMI-Vektor auf unseren eigenen NMI ver- bogen, und die Initialisierung ist been- det. Nun zum zweiten Teil: Zunächst einmal rette ich hier nur den Akku. X- und Y- Register werden in der NMI-Routine so- wieso nicht benutzt, weshalb wir sie nicht unbedingt auch noch retten müssen. Als nächstes laden wir den Inhalt von Register 17 des VIC ($D011=dez.53265) in den Akku, da mit dem 4. Bit dieses Regi- sters der Bildschirm ein- und ausge- schaltet wird. Der Inhalt dieses Regi- sters wird nun einfach mit dem Wert des 4. Bits geEORt. Dabei wird der Wert des Bits immer invertiert. Ist es 1 (=Bild- schirm an), so wird es nach dem EOR- Befehl 0 (=Bildschirm aus) sein und um- gekehrt. Der neue Wert muß nun nur noch wieder in $D011 zurückwandern, und wir können den NMI beenden. Das Programm finden Sie übrigens auch auf dieser MD unter dem Namen "NMI- SCREENOFF". Es muß absolut (",8,1") ge- laden werden und wird mit SYS 49152 ge- startet. Ab dann können Sie per Tasten- druck auf RESTORE den Bildschirm nach Belieben ein- und ausschalten. Das war es dann man wieder für diesen Monat. Ich wünsche Ihnen noch viel Spaß beim herumexperimentieren mit den NMIs und seien Sie nicht enttäuscht, wenns mal nicht auf Anhieb klappen sollte, denn: Wer noch nie einen Rechnerabsturz erlebt hat, ist kein wahrer Programmie- rer! In diesem Sinne bis nächsten Monat,
Ihr Uli Basters (ub).
CIA-Kurs: "Die Geheimnisse des Secret Service..." (Teil 5) ----------------------------------------
Hallo zusammen, zum 5. Teil dieses Kur- ses. Nachdem Sie nun ja ausgiebig über Interrupts Bescheid wissen, wollen wir uns diesen Monat noch einmal ein wenig intensiver um die CIA-Bausteine ansich kümmern, denen dieser Kurs ja gewidmet ist. Diesmal wollen wir nämlich das Thema der Timerkopplung behandeln. Darunter ver- steht man die Verkettung von Timer A und Timer B einer CIA zu einem großen 32-Bit Timer (je ein Timer verfügt ja über je 16 Bit). Wozu das gut ist, werden Sie spätestens dann gemerkt haben, als Sie einmal einen Timer-Interrupt programmieren wollten, der weniger oft als 15 mal pro Sekunde auftritt. Dann reicht nämlich ein 16- Bit-Timer nicht mehr aus, insofern er Systemtakte zählt, was ja eigentlich die häufigste Anwendung der Timer-Triggerung ist (Triggerung gibt den auslösenden Faktor an, der den Timer dazu veranlaßt, den Wert, den er beinhaltet um 1 zu er- niedrigen - ich erwähnte Timer-Trigger schon zu Anfang dieses Kurses). Sie können in einen 16-Bit-Timer ja ei- nen maximalen Wert von 2↑16-1=65535 la- den. Bei 985248.4 Taktzyklen, die der 64er pro Sekunde bekommt, heißt das al- so, daß der Timer genau 985248.4/65535= 15.03392691 mal pro Sekunde unterlaufen kann, wenn er am langsamsten läuft. Langsamer (oder besser: weniger häufig) geht es nicht. Zu diesem Zweck besteht nun aber auch die Möglichkeit Timer A und Timer B ei- ner CIA zu koppeln. Öber Timer B hatten wir bisher ja wenig gesprochen, da er vom Betriebssystem sowohl in CIA1, als auch in CIA2 nicht benutzt wird. Jedoch ist es ebenso möglich ihn als Timer zu verwenden, wobei analog zu Timer A vor- gegangen wird. Nun jedoch zu jener Kopplung. Es gibt nämlich eine Möglichkeit, mit der wir Timer B anstelle von Systemtakten die Unterläufe von Timer A zählen lassen können. Das heißt also, daß jedesmal, wenn Timer A bei 0 angekommen ist, Timer B um 1 erniedrigt wird. Schaltet man nun einen Interrupt so, daß er dann von ei- ner CIA ausgelöst wird, wenn Timer B unterläuft, so hat man einen vollen 32- Bit-Zähler, mit dem wir schon ganz ande- re Dimensionen in Sachen Häufigkeit von Unterläufen erreichen können. Mit 32 Bit können wir nämlich maximal 2↑32-1= 42949672995 (in Worten: über zweiund- vierzigmilliarden) Werte zählen, was bedeutet, daß wir auch dementsprechend lange Pausen zwischen zwei Timerinter- rupts haben. Mal kurz durchgerechnet sind das alle 42949672955/985248.2 = 4359.273556 Sekunden. Das sind mehr als 72 Minuten, also eine ganze Menge! Die dabei anfallenden Interrupts werden dann von Timer B ausgelöst, weshalb wir dann auch darauf achten müssen, das wir ihn als Interruptquelle im ICR (Register 13) setzen. Kommen wir nun zu einer Anwendung. Ich muß gestehen, viele Möglichkeiten hierzu bieten sich mir nicht, jedoch könnte eine Timerkopplung durchaus zur Lösung des einen oder anderen speziellen Pro- blems nützlich sein. Ich habe mir da eine ganz sinnvolle Anwendung einfallen lassen und Ihnen gleich einmal ein Bei- spielprogramm vorbereitet, anhand dessen ich Ihnen die Timerkopplung erläutern möchte. Es ist ein kleines Programm, das den Takzyklenverbrauch eines anderen Programms stoppen kann. Es heißt EVAL und ist auf dieser MD in zwei Versionen gespeichert. Zum einen habe ich da den ausführbaren Code, den Sie absolut laden müssen (mit ",8,1") und der ab Adresse $9000 (dez.36864) gestartet wird. Hierzu jedoch später mehr. Desweiteren finden Sie auch noch den Quell-Code von EVAL unter dem Namen "EVAL.SRC". Er ist wie immer im Hypra-Ass-Format und kann auch ohne HYPRA-ASS mit ",8" zum Anschauen in den Basicspeicher geladen werden. Doch nun zu EVAL selbst. Zunächst einmal wollen wir uns fragen, was nun genau geleistet werden soll. EVAL soll zunächst einmal ganz einfach die Taktzy- klen zählen, die ein anderes Programm verbraucht. Das ist die Problemstellung. Die Lösung wollen wir -na, Sie werden es nicht glauben- über die Timer einer CIA bewerkstelligen. Ich habe zu diesem Zweck die CIA2 ausgesucht, deren Timer normalerweise, solange die RS232- Schnittstelle des C64 nicht genutzt wird, unbenutzt sind. Zur Ermittlung der verstrichenen Zei- teinheiten, sprich Taktzylen, müssen wir nun einfach nur einen bestimmten Grund- wert in beide Timer laden, sie starten und anschließend das zu prüfende Pro- gramm aufrufen. Dies wollen wir mittels eines "JSR"-Befehls tun. Springt das aufgerufene Programm nun zurück, so müs- sen wir den Timer direkt anhalten und anschließend den in ihm enthaltenen Wert von unserem Anfangswert subtrahieren. Dadurch erhalten wir die Anzahl der Taktztyklen, die verstrichen sind, zwi- schen Start und Stop des Timers. Soviel zum theoretischen Programmablauf von EVAL. Kommen wir nun zu den Timern selbst. Zunächst müssen wir zusehen, daß wir eine richtige Triggerung für Timer A und Timer B wählen. Timer B soll ja die Unterläufe von Timer A zählen und dieser widerum die Systemtakte. Zu diesem Zweck schreiben wir also erst einmal den Wert $81 in das Control-Register von Timer A (=CRA, Reg.14), wie wir das ja auch schon von der Interruptprogrammierung her kennen. Weil bei diesem Wert Bit 5 gelöscht ist zählt Timer A also System- takte. Für Timer B wird das schon schwieriger. Ich hatte Ihnen ja schon einmal bei der Beschreibung der CIA-Register aufgeli- stet, welche Möglichkeiten es hier gibt. Timer B kann nämlich in Gegensatz zu Timer A vier (anstelle von zweien) ver- schiedene Triggerquellen haben. Dies wird von den Bits 5 und 6 gesteuert, deren Kombinationen ich Ihnen noch ein- mal auflisten möchte:
Bit 5 6 Timer B zählt... ----------------------------------------
0 0 Systemtakte. 0 1 steigende CNT-Flanken. 1 0 Unterläufe von Timer A. 1 1 Unterläufe von Timer A, wenn CNT=1 ist.
Für uns kommt da die Kombination "10" in Frage. Bit 5 ist also gesetzt und alle anderen gelöscht. Wie bei Timer A müssen wir jedoch auch Bit 0 setzen, weil wir beim Laden dieses Wertes in das Control- Register von Timer B (CRB, Reg.15) den Timer auch gleich starten wollen. Dem- nach brauchen wir diesmal den Wert $41. Das war dann auch schon alles, was wir zur Timerkopplung brauchen. Timer A zählt nun Systemtakte und löst bei jedem Unterlauf ein Herabzählen von Timer B aus. Einen Interrupt wollen wir diesmal nicht erzeugen, doch könnte man auch durchaus im ICR festlegen, das einer erzeugt werden soll, wenn Timer B dann unterläuft. Somit hätten wir also einen 32-Bit Timer der Systemtakte zählt. Die Reihenfolge der LO/HI-Zählbytes sieht nun folgender- maßen aus:
TimerB-HI TimerB-LO TimerA-HI TimerA-LO
Sie müssen also nicht nur ein High und Lowbytepaar berechnen, sondern gleich zwei. Hier wechselt man dann auch in die nächsthöhere Ebene der "Bits und Bytes". Eine 16-Bit-Zahl bezeichnet man nämlich als ein "Word" (engl.: wörd = Wort) und eine 32-Bit-Binärzahl als ein "Longword" (engl.: Langwort). Um eine Dezimalzahl nun in ein Longword umzuwandeln müssen Sie folgendermaßen vorgehen: 1) Zunächst teilen wir unsere Zahl durch 2↑16 (=65536) und nehmen den Ganzzah- lanteil des Ergebnisses als höherwer- tiges Word. Dieses wird nun wie ge- wohnt in Low- und Highbyte aufgespal- ten. 2) Nun multiplizieren wir das High-Word mit 2↑16 und subrahieren den Wert von unserem Anfangswert. Das Ergebnis was wir hier erhalten ist das niederwer- tige Word, das ebenfalls, wie ge- wohnt, in Low- und Highbyte umgewan- delt wird. Als Beispiel habe ich einmal die Zahl 2000000 in ein Langword umgewandelt:
1 ) 2000000/65536=30.51757813 1a) HI-Word=30 --> LO=30, HI=0 2 ) 2000000-30*65536=33920 2a) LO-Word=33920 --> LO=128, HI=132
Longword: ---------
0 30 132 128 Binär: ------ 00000000 00011110 10000100 10000000
Soviel also hierzu. Nun können wir also schon einmal beliebig lange 32-Bit- Timerwerte berechnen. Für EVAL habe ich übrigens nicht irgendeine Zahl genommen, sondern schlichtweg die größte (also die 42 Milliarden von oben). Länger als 72 Minuten sollte eine zu testende Assem- blerroutine sowieso nicht sein. Kommen wir nun also zu der Programmie- rung von EVAL. Hier möchte ich Ihnen einmal den Anfang des Programms aufli- sten:
------------------- EVAL LDA #$7F Zunächst alle Inter- STA CIA2+13 rupts sperren. LDA #00 Und... STA CIA2+14 Timer A und STA CIA2+15 Timer B anhalten. ------------------- LDA #$FF Nun den Maximalwert STA CIA2+4 in Timer A STA CIA2+5 und STA CIA2+6 in Timer B STA CIA2+7 schreiben. ------------------- LDA #$41 Timer B soll Timer A STA CIA2+15 zählen LDA #$81 Timer A soll System- STA CIA2+14 takte zählen. -------------------
JSR $C000 Testprogramm aufrufen
-------------------
Im ersten Teil dieses Listings sperren wir zunächst einmal alle Interruptquel- len durch Löschen des ICR. Anschließend werden durch das Schreiben von 0 in die Control-Register der Timer selbige ge- stoppt. Diese Maßnahmen sind nur für den Fall gedacht, daß in diesen Timern ganz gegen unsrer Erwartung doch etwas laufen sollte. Zum korrekten Ablauf von EVAL ist es absolut notwenidig, daß die Timer stehen, da sonst die Testwerte verfälscht würden. Anschließend werden die Register TALO, TAHI, TBLO, TBHI mit dem Wert 255 (=$FF) geladen und so auf den Maximalwert 2↑32-1 gesetzt. Im dritten Teil des Listings werden nun die beiden Timer wieder gestartet. Hier- bei MUß Timer B unbedingt VOR Timer A aktiviert werden, da er zum Einen von diesem abhängig ist, und deshalb zuerst aktiv sein sollte (würde Timer A nämlich unterlaufen BEVOR Timer B zählbereit ist, wäre das Testergebnis ebenfalls nicht in Ordnung), und zum Anderen müs- sen wir so spät wie möglich den 32-Bit- Timer starten, damit auch tatsächlich die Taktzyklen der zu messenden Routine gezählt werden und nicht etwa noch eini- ge Taktzyklen die EVAL benötigt. Im letzten Teil wird nun noch die zu testende Routine aufgerufen, die ich hier einmal bei $C000 (dez.49152) ange- siedelt habe. Jetzt ist also unser 32-Bit-Timer aktiv und zählt schön brav von 4294967295 Richtung 0 hinab. Währenddessen läuft unser Testprogramm ab und jeder Taktzy- klus der dabei verstreicht wird mit- gezählt. Wird das Programm dann mittels "RTS" beendet, so kehrt der Rechner wieder in EVAL zurück. Nun müssen wir uns um die weitere Behandlung der Timer kümmern. Damit wieder keine unnötige Rechnerzeit mitgezählt wird stoppen wir also zunächst beide Timer:
------------------- LDA #$80 Wert für "Timer-Stop" STA CIA2+14 Timer A und STA CIA2+15 Timer B anhalten. -------------------
Da der unser 32-Bit-Timer nun also nur während des Ablaufs des zu testenden Programms lief, müsste nun in den Timer- registern logischerweise haargenau die Anzahl der verbrauchten Taktzyklen ste- hen, jedoch in einem invertierten For- mat. Das heißt, daß dadurch, daß der Timer rückwärts lief, die Taktzyklenan- zahl mit folgender Formel berechnet wer- den muß: (2↑32-1)-(Longword in Timerregistern)= tatsächlich verbrauchte Taktzyklen. Na aber holla, das könnte ja schwer wer- den, die 4-Byte Werte voneinander zu subtrahieren! Doch keine Panik, auch das läßt sich ganz einfach lösen. Dadurch nämlich, daß wir beim Start den absolu- ten Maximalwert in die Timer geladen hatten, können wir diese Umrechnung durch schlichtes invertieren der einzel- nen Bytes vornehmen. Dabei wird das so- genannte Einerkomplement unserer Zahl gebildet, was ebenfalls eine Art ist, Bytes voneinander zu subtrahieren. Zum Beweis möchte ich Ihnen hier einmal ein Beipiel geben: Wir hatten den Startwert $FF $FF $FF $FF im Timer stehen. Eine Invertierung die- ses Werts mittels des "EOR"-Befehls ergäbe dann: $00 $00 $00 $00 - mit ande- ren Worten, der Startwert war 0. Angenommen, nun war der Wert, den wir nach Ablauf eines Testprogramms in den Timern vorfanden folgender: $FF $FD $03 $AE. Dies entspricht dem Dezimalwert 4294771630. Subrtrahieren wir diesen Wert von 2↑32-1, so ergibt sich folgendes Ergebnis: 195665. Das wäre also die Anzahl der Taktzyklen, die das aufgerufene Programm verbrauchte. Nun wollen wir doch testweise einfach einmal das Einerkomplement der Ergebnis- zahl bilden (ebenfalls mittels "EOR"):
$FF $FD $03 $AE --> $00 $02 $FC $51.
Dieses Longword umgerechnet ergibt nun aber ebenfalls die Zahl 195665 - "quod erat demonstandum!" Kommen wir nun also zum nächsten Teil von EVAL, dem Retten der Timerwerte und dem gleichzeitigen Umwandeln in die ab- solute Zahl an Taktzyklen:
------------------- LDY #03 Quellzähler initia- lisieren. LDX #00 Zielzähler initiali- sieren. LOOP1 LDA CIA2+4,Y Akku mit dem hinter- sten Timerregister laden EOR #$FF Komplement bilden STA $62,X und umgekehrt si- chern. INX Zielzähler +1. DEY Quellzähler -1. BPL LOOP1 Wenn noch nicht un- tergelaufen, dann wiederholen. -------------------
Diese Schleife nimmt sich nun nacheinan- der die CIA-Register 7,6,5 und 4 vor, bildet ihr Komplement und speichert sie umgekehrt in die Zeropageadressen $62, $63, $64 und $65. Damit hätten wir dann auch gleichzeitig das Problem gelöst, daß die Bytes ja in der Reihenfolge TBHI TBLO TAHI TALO aufeinander folgen müs- sen, damit Sie einen Sinn ergeben. Das hat aber auch noch einen anderen, wei- taus wichtigeren Grund: Ich habe die Adressen $62-$65 nämlich nicht etwa willkülich als Zwischenspei- cher gewählt, sondern in diesen Adressen befinden sich nämlich die Mantissenbytes des Floatingpoint-ACcumualtors (kurz: "FAC") des 64ers. Damit wir unsere 32- Bit-Zahl nämlich auch richtig in dezima- ler Schreibweise ausgeben können, brau- chen wir nämlich den FAC. Der FAC ist ein bestimmter Speicherbereich in der Zeropage der vom Betriebssystem dazu genutzt wird, Fließkommazahlen aufzuneh- men und mit ihnen herumzurechnen. Ebenso gibt es auch eine Routine des Be- triebssystems, mit deren Hilfe wir die 32-Bit-Zahl nachher in Dezimalschreib- weise umwandeln, um sie ausgeben zu kön- nen. Dazu jedoch später. Zunächst einmal hätten wir also die ab- solute Anzahl der vergangenen Taktzy- klen, die zwischen Start und Stop des Timers verstichen sind im FAC stehen. Leider haben wir nun aber doch noch ei- nige Taktzyklen, die durch EVAL ver- braucht wurden, in unserer Rechnung. WO? Werden Sie jetzt fragen, na ganz einfach - schauen wir uns nocheinmal die Zeilen vor und hinter dem Aufruf des Testpro- gramms an:
... LDA #$81 Timer A soll System- STA CIA2+14 takte zählen. ------------------- JSR $C000 Testprgramm aufrufen. -------------------
LDA #$80 Wert für "Timer-Stop" STA CIA2+14 Timer A und STA CIA2+15 Timer B anhalten. ------------------- ... Na? Wissen Sie wo? Natürlich! Ab dem Befehl "STA CIA2+14" war der Timer ak- tiv. Der anschließende JSR-Befehl gehört nun aber noch nicht in die zu testende Routine, wurde aber trotzdem mitgezählt. Ebenso wie die Befehle "LDA #$80" und "STA CIA2+14". Erst dann war der Timer wieder aus. Die hier erwähnten Befehle wurden also alle mitgezählt. Deshalb müssen wir nun noch von unserem Gesam- tergebnis die Anzahl der Takte die sie verbrauchten subtrahieren. Ein JSR- Befehl braucht immer 6 Taktzyklen, das direkte Laden des Akkus 2 und das abso- lute Entleeren des Akkus 4. Demnach ha- ben wir also 6+2+4=12 Taktzyklen zuviel, die nun von der folgenden Subtraktions- routine von dem Gesamtwert subtrahiert werden:
------------------- SEC Carry setzen. SBC #12 Vom Akku (=TALO, in $65) 12 subtrahieren. BCS L1 Wenn kein Unterlauf, dann auf Ende ver- zweigen. DEC $64 Sonst nächsthöheres Byte -1. LDX $64 Prüfen, ob dieses CPX #$FF untergelaufen ist, BNE L1 Nö, also zum Ende. DEC $63 Ja, also nächsthöhe- res Byte -1 LDX $63 Prüfen, ob dieses CPX #$FF untergelaufen ist, BNE L1 Nö, also zum Ende. DEC $62 Sonst, das höchste Byte -1 (ohne Prü- fung, weil es nicht unterlaufen kann!) L1 STA $65 Alles klar, also Akku im niedrigsten Byte sichern. -------------------
Ich erspare mir hier einen Kommentar, weil Sie bestimmt schon wissen, wie man zwei Integerzahlen in Assembler vonei- nander subtrahiert. Ich will nur noch darauf hinweisen, daß am Anfang dieses Programmteils der Inhalt von Speicher- zelle $65 ja immer noch im Akku steht, da die Komplementierungsschleife von vorhin ihn dort noch hat stehen las- sen... So. Nun hätten wir also die tatsächliche Anzahl der Taktzyklen als Longword im FAC stehen. Nun müssen wir sie nur noch ausgeben. Hierzu möchte ich eine Routine des Betriebssystems benutzen, die den Inhalt des FAC in ASCII-Code umwandelt und ihn anschließend ab $0100 ablegt. Nach dem letzten ASCII-Zeichen fügt sie noch ein Nullbyte ein, was später von Bedeutung sein wird. Zunächst haben wir noch ein kleines Pro- blem - die erwähnte Umwandlungsroutine ("FACTOASC" ist übrigens ihr Name), ver- langt nämlich reine Floating-Point- Zahlen im FAC. Diese haben ein eigenes Format, nämlich das MFLPT-Format. Leider entspricht unser 32-Bit-Integer aber noch nicht diesem Format, weshalb wir ihn erst "normieren" müssen. Was dabei passiert, und wie das MFLPT-Format aus- sieht möchte ich hier auslassen, weil es den Rahmen dieses Kurses sprengen würde. Geben Sie sich einfach zufrieden damit, daß der nun folgende Programmabschnitt von EVAL einfach eine 32-Bit-Zahl im FAC normiert:
----------------- LDA #$A0 Exponent initialisie- STA $61 ren. LOOP2 LDA #$62 Höchstes Byte laden, und prüfen, ob höchstes Bit gesetzt. BMI L2 Ja, also fertig! DEC $61 Nein, also die ganze ASL $65 Mantisse um 1 ROL $64 nach ROL $63 links ROL $62 rollen. BCC LOOP2 Wenn Carrybit gelöscht, wiederholen -----------------
L2 ... Sonst weiter... So. Jetzt können wir aber endlich die Taktzyklenanzahl ins ASCII-Format umwan- deln, also rufen wir FACTOASC auf. Die Einsprungsadresse dieser Routine lautet übrigens: $BDDD (dez. 48605). Nun haben wir also den ASCII-Text unse- rer Zahl im Speicher ab $0100 stehen. Jetzt müssen wir ihn nur noch auf den Bildschirm bringen. Dies geschieht mit- tels der Routine STROUT. Sie ist eben- falls eine Betriebssystemroutine und steht ab $AB1E. Als Parameter müssen wir ihr die Anfangsadresse des auszugebenden Textes in LO/HI im Akku und im Y- Register übergeben. Das Textende erkennt sie an einem Nullbyte am Ende des Tex- tes, das FACTOASC ja freundlicherweise schon eingefügt hat. Kommen wir nun also zum letzten Teil von EVAL:
-------------------- L2 JSR FACTOASC Erst FAC in ASCII umwandeln. LDA #<(TXT1) Jetzt geben wir zu- LDY #>(TXT1) nächst den Text "BE- NOETIGTE TAKTZY- KLEN:" JSR TXTOUT aus. LDA #00 Nun kommt die Takt- LDY #01 zyklenanzahl auf den JSR TXTOUT Bildschirm. LDA #13 Zum Schluß Cursor in JMP BSOUT die nächste Zeile setzen. -------------------- TXT1 .TX "BENOETIGTE TAKTZYKLEN:" .BY 0 --------------------
So. Das wars. Die letzten beiden Befehle am Ende des Listings geben nun noch ein "Carriage Return" aus, damit der Cursor nicht am Ende der Taktzyklenzahl stehen- bleibt. Mit dem JMP auf BSOUT (bei $FFD2) beenden wir dann EVAL. Der Rech- ner kehrt direkt von dort in den Einga- bemodus zurück. Damit sind wir dann auch wieder am Ende eines CIA-Kurses angelangt. Ich hoffe, Sie kennen sich jetzt mit der Timerkop- plung aus. Wenn Sie wollen, können Sie ja einmal versuchen, einen Interrupt von einem 32-Bit-Timer auslösen zu lassen, das Know-How sollten Sie aus den vorher- gehenden Kursteilen schon haben. In diesem Sinne möchte ich mich nun bis nächsten Monat von Ihnen verabschieden, wenn wir uns einmal die Echtzeituhren in den CIAs anschauen wollen.
Bis dann, Ihr Uli Basters (ub).
PS: Zum Test von EVAL laden Sie den As- semblercode bitte absolut in den Speicher und sorgen Sie dafür, daß ihr Testprogramm bei Adresse $C000 beginnt. Nun rufen Sie EVAL einfach mit "SYS 36864" auf.
CIA-Kurs: "Die Geheimnisse des Secret Service..." (Teil 6) ----------------------------------------
Willkommen zum 6. Teil unseres CIA- Kurses. Diesmal wollen wir, wie letzten Monat schon versprochen, die Echtzeituh- ren der CIAs programmieren. Hierbei han- delt es sich pro CIA um je eine 24- Stunden-Uhr, die weitaus genauer als die von BASIC bekannte TI$-Uhr gehen. Sie werden nämlich über die Frequenz des Wechselstroms der aus der Steckdose kommt getriggert. Da diese Frequenz in der Regel immer 50 Hertz beträgt (dafür sorgt das Elektrizitätswerk, das ihn in das Stromnetz einspeist), gehen die Uh- ren der CIA so gut wie nie falsch! Zudem haben wir die Möglichkeit, eine Alarmzeit zu programmieren. Stimmt ir- gendwann die aktuelle Uhrzeit mit der angegebenen Alarmzeit überein, so löst die jeweilige CIA einen Interrupt aus. Dieser wird uns im ICR dann auch ge- trennt als Alarm-Interrupt angezeigt. Auch dieses Mal habe ich Ihnen bezüglich unseres Schwerpunktes ein Programm ge- schrieben, daß sich auf dieser MD befin- det. Es heißt "CLOCK" und wird mit RUN gestartet. Desweiteren finden Sie wie immer auch den Source-Code von CLOCK unter dem Namen "CLOCK.SRC" im Hypra- Ass-Format auf dieser Diskette. Mit ei- nem Laden an den BASIC-Anfang (",8") können Sie ihn sich mit LIST anschauen. Kommen wir nun jedoch erst einmal zur Theorie. Wie programmiert man denn einen CIA-Timer? Das gestaltet sich eigentlich als rela- tiv einfach. In den Registern 8-11 einer jeden CIA werden Zehntelsekunden, Sekun- den, Minuten und Stunden abgelegt. Aus diesen Registern kann ebenso die aktuel- le Uhrzeit ausgelesen werden. Dennoch gibt es einige Besonderheiten, die wir beachten müssen. Hier zunächst einmal eine Auflistung der besagten Register mit ihrer Funktion:
Register Funktion ----------------------------------------
8 Zehntelsekunden 9 Sekunden 10 Minuten 11 Stunden
Erfreulicherweise sind die Uhren der CIAs so ausgelegt, daß sie im BCD-Format arbeiten. Dies ist ein spezielles Dar- stellungsformat für Zahlen, das uns die Umwandlung der Ziffern für eine Bild- schirmausgabe erheblich vereinfacht. BCD steht für "Binary Coded Decimal", was übersetzt "Binär kodierte Dezimalzahl" bedeutet. Dieser Code ist unter anderem auch deshalb so vorteilhaft, weil der Prozessor des C64, der 6510, es uns er- laubt, mit diesen Zahlen zu rechnen. Vielleicht kennen Sie ja die Assembler- befehle SED und CLD. Mit ihnen kann man das Dezimal-Flag des Prozessorstatusre- gisters setzen oder löschen, um dem 6510 mitzuteilen, daß man nun mit BCD-Zahlen rechnen möchte. Ich will Ihnen das einmal anhand einiger Beispiele erläutern. Kommen wir zunächst zu dem Zahlenformat selbst. Im BCD- Format wird ein Byte nicht wie sonst anhand seiner gesetzten, oder gelöschten Bits codiert, sondern ein Byte wird in zwei 4-Bit Bereiche aufgepalten. Einen solchen 4-Bit Abschnitt bezeichnet man im Fachjargon als Nibble. Hier einmal eine keine Verdeutlichung:
Nibble1 Nibble2 ---------------
1001 0011 Nibble1 ist hierbei das höherwertige, Nibble2 das niederwertige Nibble eines Bytes. Im BCD-Format stellt nun jedes Nibble eine Zahl zwischen 0 und 9 dar, die dem normalen Binärformat entspricht (deshlb auch "binär kodiert"). Hier ein- mal eine Tabelle mit den Werten für die Ziffern:
Wert Binär -----------
0 0000 1 0001 2 0010 3 0011 4 0100 5 0101 6 0110 7 0111 8 1000 9 1001
Sie sehen, die Werte entsprechen also denselben Werten die sie im Binärformat haben. Die Besonderheit des BCD-Formates ist nun, daß, wie oben schon erwähnt, jedes Nibble eines Bytes eine Dezimal- ziffer kodiert. Die Binärzahl aus dem obigen Beipiel kann also folgendermaßen interpretiert werden:
Binär : 1001 0011 BCD-Format : 9 3 = 93 Dezimal umgewandelt: 147
Das höherwertige Nibble der Zahl hat den Binärwert 9, das niederwertige 3, wes- halb die BCD-Zahl 93 lautet! So kann nun jede Zahl zwischen 0 und 99 kodiert wer- den. Die sonst 256 verschienden Binär- zahlen werden auf 100 Kombinationen re- duziert. Zahlen wie 11001010 gibt es im BCD-Format nicht. Hier wäre die Wertig- keit der Nibbles 12 und 10. Im BCD- Format gibt dies jedoch keinen Sinn, weshalb Bytewerte wie diese wegfallen. In der Informatik spricht man dann von einem "redundanten Code" - man kann mit ihm weniger Elemente kodieren, als es Möglichkeiten gibt. Welchen Vorteil gibt uns nun das BCD- Format in Bezug auf die Timerprogrammie- rung? Nun aufgrund der einzelnen Nibble- codierung können wir sehr leicht die Ziffern der Minuten, Stunden, etc. he- rausfinden. Eine komplizierte Binär- Dezimal-Umwandlung fällt weg. Desweiteren können wir mit dem Prozessor im BCD-Format rechnen. Hierzu setzt man zunächst das Dezimal-Flag mit dem Assem- bler-Befehl SED. Hiernach verhalten sich die Befehle ADC und SBC so, daß sie im- mer BCD-Werte liefern, vorausgesetzt, man addiert auch BCD-Werte miteinander. Hierzu drei kleine Beispiele:
1) $11 + $12 = $23 2) $11 + $0C = $1D 3) $12 + $19 = $31
Ich habe in den Beispielen Hexadezimal- zahlen verwendet, weil mit Ihnen BCD- Zahlen einfacher anzuzeigen sind. Hex- zahlen stellen ja ebenfalls die zwei Nibbles eines Bytes dar, nur daß man alle Möglichkeiten eines Nibbles berück- sichtigt (von 0 bis F). Beachten wir nun, daß Zahlen wie $1C gar keine BCD- Zahlen sind, und verwenden wir sie auch nicht, so können durch das Hexadezi- malsystem sehr einfach BCD-Zahlen darge- stellt werden. Hierbei entspricht die Zahl $12 nicht wie sonst dem Dezimalwert 18, sondern tatsächlich dem Wert 12!!! Das erste und dritte Beispiel soll Ihnen nun verdeutlichen, wie im BCD-Format gerechnet wird. 11+12 ergibt tatsächlich 23. Das wäre nun jedoch nichts neues, da $11+$12 auch $23 ergäbe. Deshalb zeigt Beispiel 3 das Aufreten eines Öberlaufs. Ist der BCD-Modus eingeschaltet, so er- halten wir aus der Addition 12+19 das Ergebnis 31, was auch richtig ist. Bei abgeschaltetem BCD-Modus wäre $12+$19- gleich $2B! Beispiel 2 dient als Gegen- beispiel für eine BCD-Rechung. Weil wir hier mit $0C addierten, was ja keine BCD-Zahl ist, kommt auch keine BCD-Zahl als Ergebnis heraus. Soviel zum BCD-Format. Kommen wir nun zurück zu den Uhren der CIAs. Um die Uhr einer CIA zu Setzen müssen wir also nur BCD-Zahlen in die jeweiligen Register schreiben um die aktuelle Zeit einzu- stellen. Hierbei müssen wir jedoch noch einige Dinge beachten: 1) Beim Setzen der Uhrzeit sollte IMMER zunächst das Register für die Stunden (Reg. 11) beschrieben werden. Wird nämlich auf dieses Register zugegrif- fen, so hält die entsprechende CIA die komplette Uhr an. Dies ist des- halb so wichtig, da die Uhr ja gerade in dem Moment, in dem wir schreiben auf die nächste Stunde umspringen könnte. Würden wir eine 10 in das Stundenregister schreiben und die restlichen Register stünden auf der Zeit ":59:59.9", so würde noch wäh- rend wir die Minuten schreiben die Uhr die volle Stunde erreichen und unsere 10 wäre eine 11, und das ist nicht die aktuelle Uhrzeit! Umgekehrt verhält es sich mit dem Register für die Zehntelsekunden (Reg. 8). Erst, wenn es beschrieben wurde, wird die Uhr wieder in Gang gesetzt. Deshalb müssen wir also im- mer gleich die komplette Uhrzeit set- zen, damit die Uhr auch wirklich läuft! Ahnlich verhält es sich auch beim Lesen. Hier sollten wir ebenfalls IMMER zuerst die Stundenzahl lesen. Dies veranlaßt die CIA zum Zwischens- peichern der Uhrzeit in den 4 Uhrre- gistern zum Zeitpunkt des Zugriffs. Intern läuft die Uhr allerdings wei- ter, sie wird also NICHT angehalten. So können wir immer die richtige Zeit, nämlich die, die zum Zeitpunkt des Zugriffs in der Uhr stand, ausle- sen. Auch hier muß das Zehntelsekun- denregister als letztes ausgelesen werden, damit die tatsächliche Uhr- zeit wieder in die 4 Uhrregister übertragen wird. (Weiter geht's im zweiten Teil...) Zweiter Teil: Die CIA-Uhren sind zwar echte 24h- Uhren, jedoch zählen sie nicht, wie wir es gewohnt sind von 0 bis 23 Uhr, sondern sie arbeiten nach dem ameri- kanischen Zeitsystem, das eine Unter- scheidung von Vor- und Nachmittag berücksichtigt und nur die Stunden von 0 bis 12 kennt. Sie haben das sicher schon einmal bei einer Digi- taluhr beobachtet - sobalt es 12 Uhr mittags ist springt die Anzeige auf "PM" um. Das steht für "post meri- dian" und bedeutet nichts anderes als "nach Mittag". Ebenso erscheint auf dem Display ein "AM" für "ante meri- dian" (="vor Mittag") wenn die Uhr von 11:59 PM auf 12 Uhr nachts um- schaltet. Dieses Verfahren wird eben- so von den CIA-Uhren beutzt. Das 7. Bit des Stundenregisters gibt an, ob es Vor-, oder Nachmittag ist. Ist es gelöscht, so haben wir "AM", ist es gesetzt, so ist "PM". Dies müssen wir also ebenfalls bei der Programmierung berücksichtigen - sowohl beim Lesen, als auch beim Schreiben. CLOCK ist übrigens so ausgelegt, daß es beide Arten der Zeitdarstellung berücksich- tigt. Dazu später mehr. 3) Bei den CIA-Uhren ist mir noch eine kleine Besonderheit aufgefallen, von der ich nicht weiß, ob sie absicht- lich ist und einem Standard ent- spricht, oder ob sie eine Fehlfunk- tion darstellt. Es ist nämlich möglich, wie sollte es auch anders sein, die Stunde 0 Uhr (also 12 Uhr nachts) in das Stunden- register einzutragen. Die CIA liefert dabei keine Fehler sondern übernimmt die Zeit wie sie ist. Sie zählt nun bis 12 Uhr PM und springt dann auf 1 Uhr PM (=13 Uhr) um. Soweit nichts besonderes. Der Witz ist nun, daß nachts um 11:59 PM nicht auf 0:00 AM geschaltet wird, sondern auf 12:00 Uhr AM. Hier scheint also ein kleiner Fehler zu sein (vielleicht begründet durch den internen Aufbau der CIAs), oder haben Sie schon einmal eine Uhr gesehen, die diese Zeit anzeigt? Ich nicht. Deshalb ist CLOCK auch so pro- grammiert, daß es bei 12 Uhr AM nicht 12:00 Uhr sondern 0:00 anzeigt. Dies nur als Hinweis. So. Nun wissen Sie also, wie man die aktuelle Uhrzeit in den 4 Uhrregistern einer CIA unterbringt, und wie man sie dort wieder herausholt. Das ist jedoch noch nicht alles, was man zu einer kor- rekten Uhr-Programmierung braucht. Wir müssen nämlich vor dem Einstellen der Uhr noch 2 Dinge beachten. 1) Zunächst müssen wir der CIA, die un- sere Uhr steuert, mitteilen, mit wel- cher Netzfrequenz der 64er arbeitet, in dem sie drinsteckt. Dies liegt daran, daß in Amerika eine andere Stromnorm benutzt wird, als hier bei uns in Europa. Dort beträgt die Fre- quenz des Wechselstroms aus der Steckdose nämlich 60 Hertz und nicht etwa 50, wie das bei uns üblich ist. Aus diesem Grund kann man der CIA auch mitteilen, welche der beiden Frequenzen nun benutzt wird, damit sie in beiden Stromnetzen richtig arbeitet kann. Diese Funktion legt Bit 7 des CRA-Registers (Reg. 14) fest. Steht es auf 1, so beträgt der Echtzeituhrtrigger 50 Hz, steht es auf 0, so ist er 60 Hz. Weil wir hier in Europa eine Netzfrequenz von 50 Hz haben, müssen wir auch dementspre- chend das 7. Bit von CRA setzen! 2) Beim Einstellen der Uhrzeit verlangt die CIA noch eine weitere Information von uns. Wie ich anfangs ja schon erwähnte, kann der Echtzeituhr auch eine Alarmzeit mitgeteilt werden, zu der ein Interrupt auftreten soll. Diese Alarmzeit wird nun aber in die- selben Register geschrieben, wie die Uhrzeit, also ebenfalls in die Regi- ster 8 bis 11. Bit 7 von CRB (Reg. 15) ist nun dafür zuständig zu unter- scheiden, ob gerade die Alarm-, oder die Uhrzeit gesetzt wird. Steht es auf 1, so setzen wir die Alarmzeit, steht es auf 0 so wird die Uhrzeit geschrieben. Dies müssen wir also berücksichtigen, wenn wir eine der beiden Zeiten einstellen wollen. Dies wäre alles, was Sie zur Programmie- rung der Echtzeituhr wissen müssen. Las- sen Sie mich nun zum praktischen Bei- spiel schreiten und Ihnen die Funktions- weise von CLOCK erklären. Hierzu wollen wir uns erst einmal über- legen, wie CLOCK überhaupt arbeiten soll: 1) Zunächst soll CLOCK in den Systemin- terrupt eingebunden werden, von wo aus es ständig die Ausgabe aktuali- siert. 2) CLOCK soll eine Alarmfunktion bein- halten, die bei erreichen der Alarm- zeit einen Piepton ausgibt. 3) Es soll möglich sein zwischen der 24h- und der AM/PM-Darstellung der Uhrzeit zu wählen. Hierzu habe ich mit die Speicherzelle 3 als Modusre- gister ausgesucht, die normalerweise vom Basic-Befehl USR benutzt wird. Da dieser Befehl jedoch wenig Anwendung findet, kann man bedenkenlos diese Speicherzelle für eigene Zwecke nut- zen. Wenn sie 0 ist, so soll die AM/PM-Darstellung verwendet werden. Ist sie ungleich 0, so wird die 24h- Darstellung gewünscht. Egal, in welchem Modus CLOCK laufen soll, die Eingabe der Uhrzeit soll immer in der 24h-Darstellung gesche- hen. Diese wird in der Form "HHMMSS" angegeben. 4) Zur optischen Aufmachung habe ich die Ziffern 0 bis 9 als Sprites in den Spriteblöcken 33 bis 42 (inklusive) untergebracht. Desweiteren befinden sich den den Blöcken 43 und 44 je ein Sprite für die AM- und PM-Anzeige. Die Uhrzeit wird durch die Sprites des 64ers auf dem Bildschirm ange- zeigt. Kommen wir nun also zum Source-Code Listing von CLOCK. Zunächst wollen wir uns einmal die IRQ-Initialierung an- schauen:
======================================== init lda #$81 Uhr-Trigger auf 50Hz und Timer A starten sta cia1+14 in CRA festlegen lda #<(txt1) LO-Byte Text1 ldy #>(txt1) HI-Byte Text1 ldx #$00 Wert für CRA (=Uhrzeit setzen) jsr setit Unterroutine für Zeit einlesen und setzen aufrufen lda #<(txt2) LO-Byte Text2 ldy #>(txt2) HI-Byte Text2 ldx #$80 Wert für CRA (=Alarm setzen) jsr setit Alarmzeit Lesen und Setzen sei IRQs sperren ldx #<(newirq) ..und die neue ldy #>(newirq) IRQ-Routine stx $0314 im IRQ-Pointer sty $0315 setzen ldx #15 Kopiert Liste mit loop1 lda xtab,x Sprite Koordina- sta v,x ten in die ent- dex sprechenden VIC- bpl loop1 register. ldx #07 Setzt die Farben lda #01 für alle loop2 sta v+39,x 7 Sprites dex auf bpl loop2 "Weiß" lda #$85 Alarm- und Timer- IRQs erlauben sta cia1+13 und im ICR fest- legen lda #15 Hüllkurve für Pie ton... sta sid+5 ...festlegen. lda #$c0 Frequenz von Piepton sta sid+1 festlegen. ldx #<(end+1) CLOCK-Endadr. LO ldy #>(end+1) CLOCK-Endadr. HI stx $2b und BASIC-Anfang sty $2c ...setzen jsr $a642 "NEW" ausführen cli IRQs wieder frei- rts geben und Ende. ========================================
Dies wäre also die Initialisierungsrou- tine für CLOCK. Als Erstes wird der Wert $81 (=bin. 10000001) in CRA geschrieben. Damit setzen wir die Echtzeituhrtrigge- rung auf 50 Hz (Bit 7=1). Gleichzeitig müssen wir aufpassen, daß wir nicht ver- sehens Timer A anhalten, der ja den Sy- stem-IRQ steuert. Deshalb muß also auch Bit 0 gesetzt sein, was für "Timer A starten" steht. Er läuft zwar bereits, jedoch würden wir ihn mit einer 0 anhal- ten. Die 1 ändert also nichts an dem momentanen Zustand von Timer A, sie ver- hindert nur eine Änderung. (Noch weiter geht's im dritten Teil...) Dritter Teil: Als Nächstes wird die absolute Anfangsa- dresse eines Textes, den ich im Speicher abgelegt habe, in LO/HI-Darstellung in A/Y geladen und der Wert 0 in das X- Register. Dies dient der Parameterüber- gabe für die Routine "SETIT", die an- schließend aufgerufen wird. Sie gibt den Text, dessen Anfangsadresse in A/Y steht aus und liest anschließend eine Uhrzeit ein, die sie in den Uhrregistern von CIA1 speichert. Zuvor schreibt sie den übergebenen Wert des X-Registers in CRB und setzt somit den Alarm- oder Uhzeit- Setzen-Modus. Der erste Aufruf von SETIT setzt die aktuelle Uhrzeit, der zweite die Alarmzeit. Anschließend wird der neue Interrupt eingestellt - alle IRQs werden gesperrt und der IRQ-Zeiger des Betriebssysems wird auf unsere neue Interruptroutine mit Namen "NEWIRQ" verbogen. Nun folgen zwei Schleifen, die die Spri- tes, die wir benutzen wollen um die Uhr- zeit auszugeben vorbereiten. Die erste Schleife kopiert eine Liste, die ich etwas weiter hinten im Source-Code abge- legt habe in den Bereich von $D000 bis $D00F. Diese Register des VIC sind für die Koordinaten der Sprites verantwor- lich, die nun entsprechend gesetzt sind. Die zweite Schleife schreibt in die VIC- Register 39 bis 46 den Wert 1, und setzt somit die Farbe für alle Sprites auf Weiß. Nun müssen wir die CIA1 noch darauf vor- bereiten, daß Echtzeituhr-Alarm-IRQs ausgelöst werden sollen. Dies geschieht, indem wir den Wert $85 in das ICR (Reg. 13) von CIA schreiben. Damit lassen wir zum Einen die immer noch gültigen Timer- IRQs zu die von Timer A erzeugt werden zu (für den System-IRQ), zum Anderen haben wir mit dem Wert $85 auch noch das 2. Bit gesetzt, das Alarm-IRQs als In- terruptquelle festlegt. Bit 7 muß wie immer gesetzt sein, damit die übrigen Bits als IRQ-Quelle übernommen werden. Nun müssen wir noch den SID darauf vor- bereiten, einen Piepton auszugeben, wenn ein IRQ auftritt. Deshalb wird das HI- Frequenzregister von Stimme1 (Reg. 1) mit dem Wert $C0 geladen und eine Hüll- kurve in Reg. 5 des SID geschrieben. Register 6, das ja ebenfalls Attribute der Hüllkurve angibt, wird nicht neu beschrieben, da es in der Regel den Wert 0 enthalten sollte, den ich dort auch haben wollte. Zum Abschluß folgen nun noch 5 Befehle, die den Anfang des BASIC-Speichers hö- hersetzen. Dies ist erforderlich, da ich nämlich am normalen Anfang die Sprites und den Code von CLOCK untergebracht habe. Würden Sie nun ein BASIC-Programm eingeben, so würden Sie CLOCK über- schreiben, was nicht sein sollte. In den Adressen $2B und $2C hat das Betriebs- system die Adresse des BASIC-Starts ge- speichert. Wir setzen diese Adressen nun einfach auf die Endadresse von CLOCK und rufen anschließend die Routine bei $A642 auf. Dort steht die BASIC-Routine für den Befehl "NEW". Dies ist notwendig, damit die neue Anfangsadresse des BA- SIC-Speichers auch vom Betriebssystem angenommen wird. Die Initialisierungsroutine gibt nun die IRQs wieder frei und springt zurück. Kommen wir zu der neuen IRQ-Routine, die die Uhr steuert. Sie muß zunächst prü- fen, ob der ausgelöste Interrupt ein Timer-, oder ein Alarm-IRQ war und de- mentsprechend reagieren. Bei Alarm gibt sie einen Piepton aus, bei einem Timer- IRQ wird nur die aktuelle Zeit aus CIA1 ausgelesen und mit Sprites auf dem Bild- schirm dargestellt. Hierbei muß sie un- terscheiden, ob die Zeit nun in 24h- oder AM/PM-Darstellung auf dem Bild- schirm erscheinen soll. Werfen wir doch einfach einmal einen Blick auf diese Routine:
======================================== NEWIRQ ist die IRQ-Routine von CLOCK. Hier wird zunächst einmal die vom Dar- stellungsmodus abhängige Spriteanzahl eingeschaltet. ======================================== newirq lda #$7f Wert für Sprites 0-6 einschalten (für 24h) ldx mode Uhr-Modus prüfen. bne l5 ungleich 0, also 24h lda #$ff Sonst AM/PM, des- halb Wert für "Sprites 0-7 ein- schalten" laden l5 sta v+21 und einschalten === Hier wird geprüft ob ein Alarm-IRQ (Bit2 von ICR=1) Auslöser war. Wenn ja, so wir ein Piepton ausgegeben. === lda cia1+13 ICR auslesen and #$04 Bit2 isolieren beq timeref Wenn =0, dann kei Alarm und weiter lda #15 Sonst Lautstärke sta sid+24 einschalten lda #33 Stimme 1 als "Sä- gezahn" sta sid+4 einschalten ldx #$ff Und warten... loop5 dey (dies... bne loop5 ...ist... dex ...eine... bne loop5 ...Warteschleife) lda #00 Ton gespielt, deshalb sta sid+4 Stimme1 aus sta sid+24 Lautstärke aus ======================================== Dies ist der Teil von NEWIRQ, der die Zeit neu ausgibt. ======================================== timeref ldx #43 Spriteblock "AM" in X laden lda cia1+11 Stunden in Akku bmi l6 Wenn 7.Bit=1 habe wir "PM", dehalb weiter cmp #$12 Akku=12 (in BCD)? bne l2 Nein, also weiter lda #00 12 Uhr AM gibts nicht, deshalb 00 Uhr AM beq l2 Unbedingter Sprun === Hier wird hinverzweigt, wenn PM ist. === l6 inx X-Reg enthält Spriteblock für "AM" - jetzt für "PM" and #$7f Bit7 (=PM) der Stunden löschen ldy mode Modus prüfen beq l2 Wenn AM/PM dann weiter cmp #$12 Akku = 12? beq l3 Ja, dann nicht 12 addieren sed BCD-Mode an clc Weil PM, 12 ad- adc #$12 dieren (für 24h) cld BCD-Mode aus l2 stx sprpoi+7 AM/PM-Sprite schalten
======================================== Nun folgt der Teil von NEWIRQ der die aktuelle Zeit ausgibt. ======================================== l3 jsr getnum Akkuinhalt in Spritepointer um- wandeln stx sprpoi+0 Sprites für Stun- sta sprpoi+1 den setzen
lda cia1+10 Minuten laden jsr getnum ...wandeln stx sprpoi+2 ...und sta sprpoi+3 ...setzen lda cia1+9 Sekunden laden jsr getnum ...wandeln stx sprpoi+4 ...und sta sprpoi+5 ...setzen lda cia1+8 Zehtelsek. laden clc Spritepointer der adc #sprbas Ziffer 0 addiern sta sprpoi+6 und setzen jmp $ea31 IRQ beenden ========================================
Zunächst einmal möchte ich Ihnen die In halte und die Bedeutung verschiedene Labels in diesem Teil des Listings erläu tern: * MODE enthält den Wert 3 und steht fü die Speicherzelle 3, in der wir j festgelegt hatten in welchem Darstel lungsmodus CLOCK arbeiten soll. * SPRPOI enthält den Wert 2040, die Basi sadresse der Spritepointer. In diese Pointern wird angegeben, welcher Spri teblock in einem Sprite dargestell werden soll. * SPRBAS enthät den Wert 33 und steht fü den ersten Spriteblock, den wir verwen den. In ihm steht die Ziffer 0 al Sprite. Addieren wir zu einem Wert i Akku (zwischen 0 und 9) den Wert SPRBA hinzu, so erhalten wir den Zeiger au den Spriteblock mit der entsprechende Ziffer des Wertes, den wir im Akku ste hen hatten. * GETNUM ist eine Routine, die eine BCD Zahl im Akku in die zwei Ziffernwert aufspaltet und SPRBAS zu diesem Wer hinzuaddiert. In X-Register und Akk werden die Spritepointer der beide Ziffern zurückgegeben. (Endgültig Schluß ist erst bei Teil vier...) Vierter Teil: Im ersten Teil von NEWIRQ wird der Dar- stellungsmodus geprüft und, abhängig da von, entweder sieben oder acht Sprite eingeschaltet. Im AM/PM-Modus wird das 8 Sprite nämlich zur Darstellung von A oder PM verwendet. Als Nächstes prüft NEWIRQ, was die Inter ruptquelle des IRQs war. Ist Bit 2 im IC von CIA1 gesetzt, so war es der Alarm IRQ. Wenn dieser der Auslöser war, s wird der voreingestellte Piepton einge schaltet und für die Dauer der folgende Warteschleife gespielt. Anschließend wir er wieder abgeschaltet und NEWIRQ fähr mit der Zeitausgabe fort. Sicherlich is die Tonausgabe mit einer Warteschleif nicht unbedingt die eleganteste Art un Weise dieses Problem zu lösen, jedoc genügt Sie für den Zweck als Beispielpro gramm. Sehen Sie sich doch einfach gefor dert, und versuchen Sie eine eigen Alarm-Routine zu schreiben, die läuft ohne den IRQ zu verzögern. Es folgt nun die Routine TIMEREF, die di Ausgabe der Uhrzeit erledigt. Hierzu müs sen wir zunächst einmal herausfinden, o die Stunden in AM/PM- oder in 24h Darstellung erscheinen soll. Demensprech end muß nämlich dann die Stundenzahl mo difiziert werden. Das geschieht am Anfan von TIMEREF. Das Sprite für AM/PM wir übrigens immer mitgesetzt, selbst wen die 24h-Darstellung gewählt wurde. I diesem Fall ist es jedoch nicht zu sehen weil es am Anfang von NEWIRQ abgeschalte wurde. Wenn das Programm herausfindet, daß di Uhr 12 Uhr AM anzeigt, was ja nach unse rer Definition keine logische Uhrzei ist, so lädt es 0 in den Akku, um späte bei der Ausgabe ein "00" als Stunde er scheinen zu lassen. Wenn es Nachmittag ist, stellt TIMERE zunächst einmal den Spritepointer für da AM/PM-Sprite auf "PM", indem es das X Register um 1 erhöht. Anschließend lösch es das 7. Bit der Stundenzahl, da diese ja nur angibt, daß Nachmittag ist, un später bei der Umwandlung in eine Ziffe stören würde. Wenn die AM/PM-Darstellun aktiv ist, wird direkt zu dieser Umwand lung weiterverzweigt. Andernfalls prüf TIMEREF, ob es nicht gerade 12 Uhr P ist, da bei der 24h-Darstellung in diese Fall ja NICHT der Wert 12 zu der Stunden zahl addiert werden soll. Ist dies nich der Fall, so müssen wir den Wert 12 doc noch zur Stundenzahl hinzuaddieren, wobe wir den BCD-Modus des Prozessors benut zen. Nun ist die Stundenzahl berechne und kann an die Umwandlungsroutine wei tergegeben werden. Der restliche Teil von NEWIRQ erklär sich von selbst. Damit wissen Sie nun, wie man eine CIA Echtzeituhr programmiert. Die übrige Programmteile von CLOCK (die schon erwän ten Routinen SETIT und GETNUM) möchte ic hier aussparen, da sie mit dem Thema un seres Kurses wenig zu tun haben und all gemeingültige Probleme lösen. Nichts de sto trotz können Sie sie sich ja einma im Source-Code-Listing auf dieser MD an schauen. Probieren Sie CLOCK doch einmal aus. Wen Sie es starten so werden Sie zunächs nach Uhr und Alarmzeit gefragt. Anschlie ßend können Sie die Uhr am unteren Bild schirmrand beobachten. Wenn Sie CLOC starten, so müßte die Zeit in der 24h Darstellung erscheinen, da die Speicher stelle 3 in der Regel einen Wert ungleic 0 enthält. Schalten Sie dann doch einfac einmal mit "POKE 3,0" und "POKE 3,1" zwi schen den beiden Modi um. Sie werden se hen, daß CLOCK durch die IRQ Programmierung immer direkt auf Ihre Ein gaben reagiert. So. Das war es dann mal wieder für diese Monat. Sie können ja einmal versuche eine noch komfortablere Uhr zu program mieren - wie es geht wissen Sie jetzt ja Wie wäre es zum Beispiel mit einer Funk tion, die stündlich einen Piepton aus gibt? Oder sogar gleich eine ganze Reih von Intervall-Tönen, wie man es vo Quartz-Weckern her kennt? Ich hoffe, Ihnen damit einige Öbungsanre gungen gegeben zu haben und verabschied mich nun bis nächsten Monat. Dann werde wir uns mit einem der Hauptanwendungsge biete der CIAs beschäftigen: der Ein- un Ausgabe. Bis dahin wünsche ich Ihnen viel Zeit un Piep,
Ihr Uli Basters (ub)
CIA-Kurs: "Die Geheimnisse des Secret Service..." (Teil 7) ----------------------------------------
Hallo und willkommen zum 7. Teil des CIA-Kurses. Diesen Monat wollen wir uns mit der wichtigsten Funktion der CIA- Bausteine beschäftigen, durch die sie erst so richtig leitungsfähig werden. Die beiden kleinen Chips steuern nämlich den kompletten Verkehr mit der Außenwelt des C64. Sei das nun die Bedienung eines Diskettenlaufwerks, eines Druckers, der Tastatur oder sogar die Kommunikation mit eiener Hardware-Erweiterung am User- port, alles geht nur mit den CIAs. Und das sogar relativ einfach. Kommen wir zunächst einmal zu den Grundeinheiten in den CIAs, die die für Ein-/Ausgabe be- stimmt sind, den Portregistern. Jede CIA verfügt nämlich über jeweils zwei frei programmierbare 8-Bit-Ports. Jeder dieser Ports wird von je einem Register der CIA repräsentiert. Die Bits, die Sie dort auslesen, beziehungs- weise hineinschreiben, kommen von, oder erscheinen an den jeweiligen Portleitun- gen der entsprechenden CIA und sind aus ihr herausgeleitet. "Frei programmierbar" heißt, daß diese Ports sowohl zur Ein-, als auch zur Aus- gabe benutzt werden können. Die jeweili- ge Funktion, die man benutzen möchte, kann softwaremäßig festgelegt werden. Und das nicht nur für einen ganzen Port, sondern wir können sogar die einzelnen Bits eines Ports, je nach Bedarf auf Ein- oder Ausgabe schalten. Die Portleitungen erscheinen an den ver- schiedensten Stellen wieder. So sind zum Beispiel die Portbits der CIA2 am User- port zu finden, oder die der CIA1 an den beiden Control Ports für Joysticks, be- ziehungsweise intern an der Tastatur. Sie sehen also, daß die Ports auch mehr- fach benutzt werden. Daher erklärt es sich auch, daß Sie, wenn Sie den Joy- stick in Port1 ein wenig hin und her bewegen, wirre Zeichen auf dem Bild- schirm erscheinen. Das Betriebssystem des C64 glaubt nämlich, die Tastatur würde bedient und gibt die entsprechen- den Zeichen aus. Doch dazu wollen wir später noch einmal kommen. Zunächst will ich Ihnen erst einmal erläutern, wie die Abfrage dieser Geräte funktioniert. Kommen wir also zu der Funktionsweise der Portprogrammierung. Man unterschei- det die Ports einer CIA mit den Bezeich- nungen "Port A" und "Port B". Die Regi- ster dieser Ports finden sich in den Registern 0 und 1 der entsprechenden CIA wieder. Sie heißen Portregister A und B. Zu jedem dieser Ports gibt es nun auch die sogenannten Datenrichtungsregister. Hier wird bestimmt, welches Bit eines Ports auf "Eingabe", und welches auf "Ausgabe" steht. Die Datenrichtungsregi- ster sind in den Registern 2 und 3 einer CIA zu finden. Hier nochmal eine kleine Öbersicht (die Abkürzungen werden wir der Einfachheit ab jetzt immer benut- zen):
Reg. Abk. Name ----------------------------------------
0 PRA Portregister A 1 PRB Portregister B 2 DDRA Datenrichtungsregister Port A 3 DDRB Datenrichtungsregister Port B
Ist ein Bit im Datenrichtungsregister eines Ports gelöscht, so ist das ent- sprechende Bit des Ports auf "Eingang" geschaltet; ist es gesetzt, so steht das Bit des Ports auf "Ausgang". Dem Pro- grammierer sind hier keine Grenzen ge- setzt. Schreibt man einen 8-Bit-Wert in einen Port, dessen Bits unterschiedlich, also in beide Richtungen geschaltet sind (z.B. Bits 0-3 auf "Ausgang"=1, Bits 4-7 auf "Eingang"=0), so werden auch nur die Bits des entsprechenden Portregister an den Ausgang gelegt, die als solcher ge- schatet sind (im Beispiel die Bits 0-3). Die übrigen Bits werden ignoriert, be- ziehungsweise vom Eingang überschrieben. Im Handling mit den Portregister müssen wir übrigens dringend darauf auchten, daß in der CIA ein Inverter eingebaut ist der die Ein- und Ausgangssignale invertiet. Fragen Sie mich nicht warum, für die Probrammierung ist es jedoch unablässig dies zu wissen. So müssen wir beim Lesen eines Daten- ports darauf achten, daß die Eingangs- bits immer in invertierter Schreibweise im Register erscheinen. Ist dabei also ein Port, der auf "Eingang" geschaltet ist, unbelegt, das heißt, daß an ihm kein Signal anlegt, so sind die Bits im Datenportregister auf 1. Liegt eine Spannung an einem der Bits an, so er- scheint es im Portregister als 0! Das ist wichtig zu wissen, da wir den gele- senen Wert dann nämlich erst einmal wie- der invertieren müssen, wenn wir ihn als normalen Zahlwert lesen wollen. Dazu benutzt man dann den EOR-Befehl. Mit EOR #$FF kann man den Inhalt des Akkus in- vertieren. Umgekehrt müssen wir beim Beschreiben der Portregister darauf achten, daß wir das was wir an Eins-Bits erscheinen las- sen wollen umgekehrt schreiben müssen. Wenn Sie also zum Beipsiel die Bitfolge 11110000 am Port A der CIA2 ausgeben wollen (dieser ist am Userport heraus- geführt), dann müssen Sie die Bitfolge 00001111 in das Portregister (Reg.0 von CIA2 = $DD00) schreiben! Kommen wir nun zur Tastaturabfrage. Die Tastatur wird mit Hilfe der Datenports A und B der CIA1 abgefragt. Dies geschieht auf eine besonders pfiffige Art und Wei- se. Da jeder Port über jeweils 8 Bits verfügt, könnte man eigentlich nur 16 Tasten abfragen. Mit diesen 2x8 Bits kann man aber auch eine Matrix bilden, mit der 2↑8=64 Kombinationen abgefragt werden können. Die Abfrage dieser Kombi- nationen ist nun etwas kompliziert. Des- halb gibt es jetzt erst einmal eine Gra- fik mit der Belegung der Matrix. Drucken Sie sie sich am besten aus, oder malen Sie sie ab, da ich sie später bei der Erklärung dringend benötige...
Die Entwickler des C64 haben nun 2 Vor- raussetzungen geschaffen, die eine Ma- tritzenabfrage der Tastatur ermöglichen: 1) Jede einzelne Taste der Tastatur kann man sich als einen Ein/Aus-Schalter vorstellen, der nur solange einen Strom durchschaltet, wie die Taste gedrückt ist. 2) Der Strom, der durchgeschaltet wird kommt - jetzt halten Sie sich fest - von Port A der CIA1. Die Bits dieses Ports sind alle auf "Ausgang" ge- schaltet und gesetzt. Wie Sie aus der obigen Grafik erkennen konnten, sind die Leitungen der einzelnen Bits von Port A in der Waagerechten für je- weils acht Tasten durchgeschleift. Wird nun eine Taste gedrückt, so schaltet sie ein Signal senkrecht durch, wo widerum acht Tasten mitei- nander verbunden sind. Port B dient nun als Eingang für widerum acht sol- cher Signale. Mit Port A sind also die waagerechten, mit Port B die sen- krechten Tasten vesorgt. Öber eine Kreuzpeilung kann man nun genau fest- stellen, welche Taste gerade gedrückt wird. Hierzu eine genaue Bescheibung: Die Tastaturabfrage des Betriebssystems legt nun zunächst an allen Bits von Port A Eins-Signale an. Wird keine Taste gedrückt, so ist auch nichts in Port B zu sehen. Alle seine Bits sind auf Null (das heißt für uns auf 1, weil die Ein- gangsbits ja von der CIA invertiert wer- den). Nun brauchen Sie die Tabelle von eben. Wird jetzt nämlich eine Taste gedrückt, als Beipiel nehme ich mal die Taste "J", so wird das Eins-Signal von Port A, Bit4, an Port B, Bit2 durchgeschaltet. Die Tastaturabfrageroutine erkennt so, daß überhaupt eine Taste gedrückt wurde, da der Inhalt von Port B jetzt ja nicht mehr Null ist. Nun beginnt sie, alle Bits einzeln durchzutesten, indem sie der Reihe nach die Bits von 0 bis 7 von Port A auf Eins setzt und prüft, ob Bit2 von Port B immer noch gesetzt ist. Bei Bit0 ist das nicht der Fall, ebenso bei Bit1, 2, und 3. Wenn sie jetzt Bit4 von Port A auf Eins legt, so erscheint es an Bit2 von Port B wieder. Die Taste wäre lokalisiert! Nun sucht die Abfragerouti- ne sich noch aus einer Tabelle im Be- triebssystem den ensprechenden Tastenco- de heraus, speichert ihn zwischen und schreibt ihn zusätzlich in den Tastatur- puffer, von wo aus die Ausgaberoutine des Betriebssystems das Zeichen auf dem Bildschirm ausgibt. Als Beipiel habe ich Ihnen einmal ein kleines Programm vorbereitet. Es wartet, bis eine der SHIFT-Tasten gedrückt wird und kehrt dann wieder zum aufrufenden Programm zurück. Dabei müssen wir jedoch darauf achten, daß die SHIFT-Tasten bei der Tastatur unterschieden werden in linke und rechte Taste. Unser kleines Progrämmchen muß also zwei Abfragen ma- chen, damit es erkennt, wann wir SHIFT gedrückt haben. Zunächst möchte ich Ih- nen jedoch den Source-Code des Programms hier auflisten. Sie finden ihn auch wie immer auf dieser MD unter dem Namen "WAITSHIFT.SRC". Das lauffähige Assem- blerprogramm heißt "WAITSHIFT.OBJ" und muß absolut (",8,1") geladen werden. Es liegt ab Adresse $C000 (dez. 49152) und muß mit SYS49152 gestartet werden. Nun aber zum Listing:
**************************************** start sei System-IRQ sperren lda #$42 waagerechte Reihen mit SHIFT left und right (Bits 1 und sta cia1+2 auf "Ausgang" scha ten. lda #$00 Alle Bits von Port sta cia1+3 auf "Eingang" scha ten. loop1 lda #$fc Bit 1 auf "Eins" legen sta cia1+0 und ab ins PRA. lda cia1+1 Jetzt PRB prüfen.. cmp #$7f ...wenn SHIFT-LEFT gedrückt, dann ist Bit7 gelöscht! beq end Jau, is so, also Ende. lda #$bf Sonst Bit 6 auf "Eins" setzen sta cia1+0 und wieder ins PRA lda cia1+1 PRB holen... cmp #$ef ...wenn SHIFT-RIGH gedrückt, dann ist Bit4 gelöscht! bne loop1 Is nich, also noch mal von vorne!
end lda #$ff DDRA muß für Sys-I
sta cia1+2 rückgesetzt werden cli System-IRQs wieder frei geben. rts Unn Tschüß! ****************************************
Kommen wir nun zur Dokumentation: Wie Sie aus der Tastentabelle entnehm können, sind die SHIFT-Tasten folgenderm ßen codiert: SHIFT-LEFT wird durch Bi von PRA angesprochen und schaltet zu Bi von PRB durch. Ebenso bekommt SHIFT-RIG sein Signal von Bit6 von PRA und schalt nach Bit4 von PRB durch. Möchten wir n also ganz gezielt diese Tasten abfrage so müssen wir zunächst genau das Bit v PRA setzen, das die entsprechende Tas ansteuert. Dann muß geprüft werden, ob d Bit, das von dieser, und NUR von dies Taste durchgeschaltet wird auch auf 1 is Ist dies der Fall, so war die Tas tatsächlich gedrückt. Zuvor müssen wir jedoch noch ein paar Vo bereitungen treffen. Dies geschieht in d ersten 5 Zeilen von WAITSHIFT. Zunäch müssen wir den System-IRQ sperren, da d ja sonst die Tastatur für uns abfragt, u uns nur stören würde. Da er über CI läuft, genügt es, mit SEI alle IRQs unterbinden. Anschließend müssen die D tenrichtungsregister der beiden Ports f unsere Zwecke ausgerichtet werden. Hier schreiben wir zunächst den Wert $42 DDRA. $42 entspricht dem Binärwe 01000010. Hier sind die Bits 1 und 6 g stetzt, womit wir sie auf Ausgang scha ten. Alle anderen sind Eingang. Dadur legen wir fest, daß Signale nun nur no von den Tasten die in diesen Reihen ( der Tabelle) liegen kommen. In DDRB kom eine 0. Es würde zwar genügen, wenn w nur die Bits 4 und 7 als Eingang schalt ten, jedoch habe ich mich der Einfachhe halber für diese Kombination entschiede da sie nicht zuletzt einfacher und eben effektiv ist. Nun beginnt erst die eigentliche Abfrag Begonnen wird mit der linken SHIFT-Tast Durch den Wert $FC, den wir in PRA schre beb, legen wir Bit2 auf Eins (000000 invertiert ergibt 11111101). Es liegt n ein Pegel dort an, der zu den Tast SHIFT-LEFT, E, S, Z, 4, A, W und 3 durc geschaltet ist. Wird nun eine dieser T sten gedrückt, so erscheint die Eins v Bit2 an einem der 8 Bits von PRB wiede Für die SHIFT-LEFT-Taste wäre das Bit7. wir die Signal-Invertierung beachten mü sen, muß in PRB beim Auslesen also d Wert $7F ($7F=01111111, das ist ein inve tiertes 10000000=$80) stehen. Das wi sogleich durch den CMP-Befehl überprüf War es tatsächlich der Fall, so wird a Ende der Routine verzweigt, wenn nich dann müssen wir nun die rechte SHIFT-Tas abfragen. Dies erfolgt auf demselben Wege wie vo her. Nur legen wir diesmal Bit6 von P auf 1 und untersuchen, ob es an PRB wied erscheint. Ist dies auch nicht der Fal so werden beide Tasten nochmals überprüf solange, bis eine Taste gedrückt wird. Zum Schluß muß WAITSHIFT noch den alt Wert (ALLE Bits als Ausgang) in DD zurückschreiben, damit die Tastaturabfra des System-IRQs auch wieder alle Tast abfragen kann. Nun können wir die IRQs m CLI wieder freigeben und die Routine bee den. Nun wissen Sie also, wie man die Tastat hardwaremäßig abfragt. Das kann sehr hi freich bei der Programmierung von ein Spielsteuerung sein, da diese Art der A frage es einem ermöglicht mehrere Tast gleichzeitig abzufragen. Bei einem Rennw genspiel zum Beispiel, oder bei einer ei fachen Auto-Simulation kann so zum Be spiel eine Taste für die Kupplung des A tos herhalten und eine andere für den Ga der eingelegt werden soll. Nur wenn d Kupplung gedrückt ist, kann der Gang ei gelegt werden. Dadurch bekommt das Spi gewissermaßen einen "seriösen Touch"... Damit soll es dann für diesen Monat gen sein in Sachen Geheimdienst. Nächstea M geht es weiter mit der Ein-/Ausgab Programmierung. Wir wollen uns dann eine Joystickabfrage kümmern und noch e was über ein spezielles Ein-/Ausgab register der CIA erfahen. Bis dahin vi Spaß beim Tastendrücken,
Ihr Uli Baster
CIA-Kurs (Teil 8) "Die Geheimnisse des Secret Service..." ----------------------------------------
Hallo und Willkommen zum 8.Teil unseres Kurses. Diesen Monat soll es weiterge- hen, mit der Ein-/Ausgabe über die CIA- Bausteine. Wir werden eine Joystickab- frage programmieren und darüber hinaus die Funktionsweise von anderen Eingabe- geräten einmal etwas genauer unter die Lupe nehmen. Von der letzten Ausgabe her sollten Sie ja noch wissen, daß jede CIA über zwei Ein-/Ausgabeports verfügt. Diese befin- den sich jeweils in den ersten beiden Registern einer CIA. Desweiteren finden wir zu jedem Port ein Datenrichtungsre- gister (Register 2 und 3 einer CIA), in denen wir jeweils festlegten, in welcher Richtung Daten verarbeitet werden sollen (Eingabe, oder Ausgabe). Dies soll uns nun für die folgenden Themen als Grund- lage dienen. Kommen wir zum ersten Kern- thema, der Joystickabfrage: Wahrscheinlich ist dies für Sie nichts neues mehr; Sie haben sicherlich schon einmal so etwas programmiert, jedoch werden Sie jetzt vielleicht auch die hardwaremäßigen Hintergründe verstehen. Desweiteren will ich Ihnen hiermit auch die Joyports und ihre Verbindungen zu den CIAs und anderen Bausteinen inner- halb unseres "Brotkastens" erläutern. Zunächst einmal können wir CIA2 für die- ses Thema ausklammern, weil nämlich aus- schließlich die CIA1 für die Bedienung der Joyports verantwortlich ist. An je- dem der beiden Ports sind jeweils fünf Leitungen (für die 4 Joystickrichtungen und den Feuerknopf) mit entsprechenden Portleitungen der CIA1 verbunden. Für den Joyport1 sind das die Leitungen PB0-PB4, für Joyport2 die Leitungen PA0-PA4. Desweiteren sind pro Joyport auch noch weitere Signale zu finden, die ich Ihnen in der folgenden Grafik einmal aufführen möchte: Hier Grafik 1... Die eigentliche Joystickabfrage gestal- tet sich nun als sehr einfach. Wir müs- sen lediglich darauf achten, daß die Bits 0-4 eines Ports auf "Eingang" ge- schaltet sind. Da wir in der Regel nichts ausgeben, genügt es also, wenn wir einfach eine 0 (=alle Pins auf "Ein- gang") in das entsprechende Datenrich- tungsregister schreiben. Richtiger wäre natürlich der Wert 224 (=bin.11100000), da wir damit nur die Bits 0-4 als Ein- gang setzen. Die Bits 5-7 sind Ausgang. Wie SIE es nun letztendlich handhaben ist Ihre Sache, nur müssen Sie darauf achten, daß die Werte, die wir in den beiden Fällen lesen verschieden vonei- nander sind (bei Datenrichtungsregister = 0 --> Bits 5-7 immer gesetzt, bei DDR = 224, Bits 5-7 abhängig vom Wert im Datenregister). Betrachtet man also 8- Bit-Werte beim Auslesen, sollte man sich darüber im Klaren sein, welchen Wert die drei unbenutzten Bits haben. Nun brauchen Sie einfach nur einen Wert aus dem entsprechenden Datenregister auszulesen und müssen prüfen, ob, und wenn ja, welche der Bits gelöscht sind. Sie erinnern sich ja vielleicht daran, daß die Signale der Datenports inver- tiert werden, was bedeutet, daß bei kei- ner Joystickbewegung alle Bits auf 1 sind (keine Signale liegen an). Wird der Joystick bewegt, so legt er ein Signal an die entsprechende Portleitung an. Dieses erscheint für uns im Datenregi- ster als ein 0-Bit! Hier möchte ich Ihnen noch einmal eine Öbersicht über die Zuständigkeiten der einzelnen Bits geben, damit Sie auch wissen, welches Bit gelöscht ist, wenn der Joystick in welche Richtung gedrückt wird:
Richtung Joy1 Joy2 ------------------
oben PB0 PA0 unten PB1 PA1 links PB2 PA2 rechts PB3 PA3 Knopf PB4 PA4
Natürlich ist es auch möglich, mehrere Richtungen gleichzeitig abzufragen. Wird der Joystick z.B. nach rechts oben gedrückt, so sind die Portleitungen für "oben" und "rechts" (PB0 und PB3 für Joyport1, bzw. PA0 und PA3 für Joyport2) auf 1, d.h. die Bits 0 und 3 im jeweili- gen Datenregister sind gelöscht! Achten Sie für solche Fälle also immer darauf, daß Ihre Joystickabfrage dynamisch ist und mehrere Richtungen auch erkennen kann. Ich möchte da mit gutem Beispiel vorangehen und habe Ihnen einmal eine Abfrage des Joyport1 als Beipspielpro- gramm programmiert. Sie finden es, wie immer, als Source-Code (mit ".SRC"- Extension) und als ausführbares Maschi- nenprogramm (mit ".OBJ"-Extension) unter dem Namen "JOYTEST" auf dieser MD. Ich habe deshalb den Joyport1 gewählt, weil wir bei ihm das Datenrichtungsregi- ster nicht zu ändern brauchen. Er läuft ja über Port B der CIA1) und wie wir aus dem letzten Kursteil wissen, ist dieser schon vom Betriebssystem her komplett auf "Eingang" geschaltet. Natürlich kön- nen Sie das auch bei Joyport2 tun (er- scheint in Port A der CIA1), jedoch müs- sen Sie dabei berücksichtigen, daß Sie ihn nach der Abfrage wieder auf "Aus- gang" schalten, weil sonst die Tastatur nicht mehr ansprechbar ist! Kommen wir nun aber zu dem Beipspielpro- gramm. Es steht ab $c000 (=dez. 49152) und wird auch dort gestartet (SYS 49152).
**************************************** start lda #01 Zeichenfarbe auf sta 646 "weiß" setzen.
loop1 lda #147 Code für "CLR" laden jsr $ffd2 und ausgeben.
lda $dc01 Datenport B laden. lsr "oben"-Bit in Carry. bcs l1 Gesetzt, also nix "oben". jsr pup Gelöscht, also "oben" ausgeben. l1 lsr "unten"-Bit in Carry. bcs l2 Gesetzt, also nix "un- ten". jsr pdown Gelöscht, also "unten" ausgeben.
l2 lsr "links"-Bit in Carry. bcs l3 Gesetzt, also nix
"links". jsr pleft Gelöscht, also "links" ausgeben. l3 lsr "rechts"-Bit in Carry. bcs l4 Gesetzt, also nix "rechts". jsr prigh Gelöscht, also "rechts" ausgeben. l4 lsr "Knopf"-Bit in Carry. bcs l6 Gesetzt, kein Knopf gedrückt. lda #02 Sonst, Farbe "rot" in Akku. bne l5 Unbedingt verzweigen...
l6 lda #00 Kein Knopf, also Farbe "schwarz" in Akku. l5 sta $d020 und Bildschirmfarbe sta $d021 setzen.
jmp loop1 Schleife wiederholen ****************************************
JOYTEST tut nun nichts anderes, als eine Schleife zu durchlaufen, die zunächst den Bildschirm löscht, dann prüft, wel- che der Joystickrichtungen gedrückt sind und den dazu passenden Text "oben", "un- ten", "links" oder "rechts" ausgibt. Zusätzlich berücksichtigt sie dabei, daß zwei Richtungen gleichzeitig aktiv sein können und gibt auch dementsprechende Texte aus ("oben links", "unten rechts", etc...). Wird der Feuerknopf gedrückt, so erscheint der ganze Bildschirm rot. Beachten Sie bitte, daß auch in diesem Fall alle 8 Joystickrichtungen abgefragt werden! Die Funktionsweise der Routine ist so simpel, daß sie keiner großen Erklärung bedarf. Am Anfang der Schleife wird der Inhalt des Datenports B in den Akku ge- holt und nun Bit für Bit nach rechts herausgeschoben. Dabei gelangen nachei- nander die fünf Joystick-Bits in das Carrybit, von wo aus man prüfen kann, ob die einzelnen Bits nun gesetzt, oder gelöscht sind. Ist eines der Bits gelöscht, so wird eine entsprechende Routine aufgerufen, die einen passenden Text ausgibt ("pup", "pdown", "pleft", "prigh"). Diese Routinen habe ich hier nicht aufgeführt, jedoch können Sie sie sich im Source-Code ja einmal anschauen. Soviel zur Joystickabfrage. Wie Sie je- doch sicherlich in der Grafik von oben gesehen haben, gibt es noch weitere Anschlüsse am Joyport, über die man ge- wisse Geräte betreiben kann. Diese sind zum einen die Paddles und zum anderen der Light-Pen. Von beiden Eingabegeräten haben Sie bestimmt schon einmal gehört. Ich möchte Ihnen nun kurz erläutern, wie sie Funktionieren und wie man sie abfra- gen kann. Ein Paddle, ist ein Eingabegerät, bei dem prinzipiell nur zwei Werte übertra- gen werden, nämlich die X- und die Y- Position eines Grafikcursors. Sicherlich einnern Sie sich noch an diese kleinen "Magic-Tables", wie man sie als Kind oft gehabt hat. Mit Hilfe zweier Drehknöpfe konnte man da auf einen grauen Glas- schirm malen, wobei man mit dem einen Knopf den Zeichenstift nach links und rechts bewegte, mit dem anderen nach oben und unten. So in etwa kann man sich auch Paddles vorstellen, wobei ein Padd- le einem der beiden Drehknöpfe ent- spricht. Grundsätzlich können (oder müs- sen) also ZWEI Paddles an EINEM Joyport angeschlossen werden, wobei diese aufge- teilt werden in X- und Y-Richtungspaddle (Pin 9 und 5 am Joyport). Wie kann man nun aber eine Position von nur einem Pin ablesen? Nun das ist ganz pfiffig: ein Paddle ist nämlich nichts anderes als ein Potentiometer, wie man es aus der Elektronik kennt. Also ein Stufenlos verstellbarer elektrischer Widerstand, der je nach Drehrichtung größer oder kleiner wird. Er wird über das Potentio- meter in den Eingang an Pin 5 oder 9 eingeleitet. Diese Pins sind nun mit dem SID, dem Soundchip des 64ers, verbunden, der über zwei Analog/Digital-Wandler verfügt. Da Widerstand eine analoge Grö- ße ist (er kann unendlich fein aufgelöst werden), muß er zur Verarbeitung mit dem Rechner erst in einen digitalen Wert gewandelt werden, was über jene A/D- Wandler geschieht. Sie haben je eine Auflösung von 8 Bit und legen den digi- talen Wert in den Registern 25 und 26 (Adressen 54297 und 54298) des SID ab. Durch Auslesen der Werte dieser Register erhalten wir einen Digitalwert des Wi- derstandes des Potentiometers, wobei wir 256 verschiedene "Positionen" unter- scheiden können. Zu beachten ist jedoch, daß die A/D-Wandler des SID nur einen bestimmten Bereich abtasten können, näm- lich von 200 Ohm (Wert 0) bis 200000 Ohm (=200 Kiloohm, Wert 255). Zum Lightpen gibt es nicht viel zu sa- gen. Er kann ja ebenfalls am Joyport angeschlossen werden, wobei dies aus- schließlich nur bei Joyport2 der Fall ist. Pin 6 dieses Ports, an dem norma- lerweise der Joystickfeuerknopf hängt, ist für den Lightpen zuständig. Zur Ab- frage eines Lightpens sollte man aber gewisse Grundkenntnisse über den VIC und den Bildschirmaufbau ansich haben. Ein Lightpen ist im Prinzip nichts ande- res, als eine einfache und schlichte Fotozelle, wie man sie im Fachhandel für wenig Geld erstehen kann. Sie ist in der Lage, Licht, das auf sie einfällt zu registrieren und in diesem Fall einen Strom zu erzeugen. Dieser Strom nun wird an Pin 6 von Joyport2 angelegt und ve- ranlaßt somit ein Löschen des Bits 4 vom Datenport A (dasselbe Bit, wie für den Feuerknopf). Dieses Ereignis tritt genau dann ein, wenn der Rasterstrahl des Mo- nitors ganz genau an der Stelle des Bildschirms vorbeifährt, an dem der Lightpen positioniert ist. In dem Fall muß nun ein pfiffiges Programm feststel- len, an genau welcher Position sich der Rasterstrahl nun befindet um die Posi- tion des Lightpens zu ermitteln. Dabei kann einem der VIC helfen, der ein sol- ches Lightpen-Signal als Interruptquelle vorgesehen hat, jedoch müssen Sie berücksichtigen, daß Sie über den VIC lediglich die aktuelle Rasterzeile, nicht aber die Rasterspalte abfragen können. Die muß man kleinlich berechnen, was nur geht, wenn man weiß, wie lange es dauert, bis der Rasterstrahl eine Zeile gezeichnet hat, und vor allen Din- gen wann er damit begonnen hat. Da das alles sehr schnell geht (25 Mal pro Se- kunde läuft der Rasterstrahl über den GESAMTEN Bildschirm), bekommt man meist sehr ungenaue Ergebnisse, was ein hin- und herspringen des Grafikcursors be- wirkt. Obwohl einige Lightpens für den 64er schon auf dem Markt waren, hat sich die- se Eingabeart auf unserem Rechner wohl nie so richtig durchgesetzt. Schade ei- gentlich, aber wenn Sie wollen, können Sie es ja einmal versuchen, das nötige Wissen dazu sollten Sie jetzt ja haben. Wie man mit Raster-Interrupts richtig umgeht, sollte Ihnen mein Kollege Ivo Herzeg, dessen Kurs vor diesem hier lief, hinreichend erklärt haben. In diesem Sinne möchte ich mich nun wie- der von Ihnen verabschieden. Nächsten Monat geht es, ab dann wieder in gewohn- ter Länge, um eine Mausabfrage. Ich habe Ihnen als Leckerbissen ein Programm vor- bereitet, mit dem Sie eine AMIGA-Maus am 64er anschließen und betreiben können. Desweiteren wollen wir uns dann auch noch ein wenig mit dem Userport befas- sen. Bis dahin Servus, Ihr Uli Basters (ub) age. Wie Sie je- doch sicherlich in der Grafik von oben gesehen haben, gibt es noch weitere Anschl
CIA-Kurs (Teil 9) "Die Geheimnisse des Secret Service..." ----------------------------------------
Hallo zusammen zum 9. Teil des CIA- Kurses. Wie schon versprochen, wollen wir uns heute mit einer "echten" Mausab- frage befassen. Die Maus - sicher haben Sie schon vieles von diesem Eingabegerät gehört, durch das die einfache und komfortable Benut- zung eines Computers erst richtig mö- glich wurde. Die "neuen" Rechner wie AMIGA, ATARI ST, oder der MAC werden standardmässig mit diesen kleinen vie- reckigen Kästchen ausgeliefert. Auch das vielgerühmte C64-System GEOS prahlt da- mit, über Maus-Steuerung benutzbar zu sein. Doch hier wollen wir gleich einmal eine Unterscheidung machen: 1) Die Maussteuerung von GEOS ist nichts besonderes. Für GEOS ist die Maus nichts anderes als ein "getarnter" Joystick. Bewegt man sie, so werden absolut dieselben Signale erzeugt, wie der Joystick sie liefert, womit GEOS-Mäuse eigentlich unbrauchbar sind, da sie nicht die Vorteile von "echten" Mäusen bieten. 2) Die "Echten", wie sie an den oben genannten Rechnern zu finden sind, sind nämlich nur deshalb so gut, weil sie jede Bewegung, die die Hand des Benutzers vollzieht absolut naturge- treu wiedergeben. Das heißt im Klar- text, daß wenn die Maus nur um ein paar Millimeter bewegt wird, sich auch der Mauscursor nur um ein paar Pixel über den Bildschirm bewegt; wird die Maus jedoch um mehrere Zen- timeter bewegt, so bewegt sich auch der Mauscursor um eine äquivalent größere Anzahl von Pixeln weiter. Damit wird es also möglich mit Hilfe einer passenden Maus Bilder naturge- treu abzuzeichnen. Desweiteren bewegt sich der Mauscur- sor auch immer mit der Geschwindig- keit, mit der die Hand die Maus be- wegt. Wenn Sie die Maus schnell bewe- gen, so bewegt sich der Mauscursor auch schnell; und ebenso bewegt er sich langsam, wenn die Maus langsam bewegt wird. Eine "falsche" Maus be- wegt sich immer nur so schnell, wie sie abgefragt wird - egal wieviel Meter Sie sie "über den Tisch zie- hen". Uns soll es hier nun um den zweiten Typ von Mäusen gehen. Um eine Abfrage zu programmieren, sollten Sie zunächst ein- mal wissen, wie eine "echte" Maus funk- tioniert. Dazu möchte ich Ihnen nun erklären, wel- che "Hardware" in einer Maus so drins- teckt: Jede Maus verfügt an der Unterseite über eine, aus dem Gehäuse herausschauende, Stahlkugel, die zur besseren Haftung mit einer Gummischicht überzogen ist. Diese Kugel kann man über eine Klappe zu Rei- nigungszwecken entfernen. Sieht man nun in die Ausparung hinein, so erkennt man dort zwei kleine Walzen, auf die die Bewegungen der Maus übertragen werden. Die Walzen stehen in einem rechten Win- ken zueinander, so daß die horizontale und die vertikale Richtung der Maus er- fasst wird. Je nach dem, wie stark die Maus nun in eine Richtung bewegt wird, drehen sich auch die Walzen mehr oder weniger schnell, wobei bei Schrägbewe- gungen die Bewegung durch die Stahlkugel immer in die horizontale und die verti- kale Bewegungsrichtung aufgespalten wird. An jeder der Achsen der beiden Walzen sind nun kleine Lochscheiben angebracht, die sich mit der Walze drehen. An jeder Lochscheibe wird über eine Lichtschranke festgestellt, ob die Maus in einer der beiden Bewegungsachsen bewegt wird. Je- desmal, wenn ein Loch in der Scheibe an der Lichtschranke vorbeifährt, wird ein Impuls an den Rechner gegeben, der so erkennt, daß die Maus in der entspre- chenden Bewegungsachse bewegt wurde. Je höher die Frequenz dieser Impulse ist, desto schneller war die Bewegung. Nun wissen wir also, daß eine Bewegung stattfand, nicht aber, in welche Rich- tung bewegt wurde, weil die Impulse ja in beiden möglichen Richtungen dieselben sind. Um nun die richtige Richtung he- rauszufinden, befindet sich noch eine zweite Lichtschranke an jeder Lochschei- be. Beide Schranken sind um ein halbes Loch voneinander versetzt, so daß immer eine der beiden Schranken vor der ande- ren ein Signal liefert. Je nach dem, welche der Schranken zuerst einen Impuls gibt, wird die Maus in die eine, oder in die andere Richtung bewegt (bei der ho- rizontalen Achse nach links oder rechts, bei der vertikalen Achse nach oben oder unten). Zur besseren Erläuterung hier einmal eine Grafik. Das Signal "H" ist das Sig- nal der ersten Lichtschranke. "H" steht für "Horitontal Pulse" und zeigt dem Rechner, daß überhaupt eine Bewegung in der Horizontalen Achse geschieht. Die zweite Lichtschranke erzeugt das "HQ"- Signal, was für "Horizontal Quadrature Pulse" steht. Durch dieses Signal kann die Bewegungsrichtung festgestellt wer- den. Ich habe mich bei der Grafik auf die horizontale Bewegungsachse beschränkt. Bei der vertikalen Achse ist der Ablauf jedoch derselbe, nur daß hier die Signale "V" und "VQ" heißen. Zunächst habe ich Ihnen den Aufbau der Mausmechanik einmal aufgezeichnet. Sie sehen, wie durch die Versetzung um ein halbes Loch, die zweite Lichtschranke, die das HQ-Signal erzeugt, in dieser Position der Lochscheibe noch keinen Impuls liefert, während die erste Licht- schranke schon aktiv ist. Beachten Sie bitte auch die Oszillator-Darstellung der Signale im unteren Bereich des Bil- des. (Anm. d. Red.: Bitte wählen Sie jetzt den nächsten Kursteil aus dem Menu!)
Soviel also zum Aufbau der Mausmechanik. Nun wollen wir uns einmal überlegen, was wir zu einer Mausabfrage beachten müs- sen. Zunächst einmal wollen wir die ein- zelnen Zustände der Maussignale untersu- chen um herauszufinden, in welche Rich- tung die Maus bewegt wird. Hierbei beschränke ich mich wieder auf die Hori- zontalbewegung, da der Ablauf für die Vertikalbewegung identisch ist. Betrachtet man einmal die Oszillatorkur- ven in der Grafik oben und "übersetzt" man sie in Bitmuster (0 für "low", 1 für "High"), dann erhält man für die einzel- nen Bewegungen folgende Bilder:
Linksbewegung : H - ...0011 0011... HQ - 0110 0110
Rechtsbewegung: H - ...0110 0110... HQ - 0011 0011 Nach jeweils 4 Signalen wiederholt sich das Ganze immer wieder. Wie Sie sehen kommen in beiden Bewegungen die H/HQ- Kombinationen 00,01,10 und 11 vor. Die Unterschiede der beiden Signale, an de- nen wir dann die Richtung erkennen kön- nen sind folgende: 1) Ist die Bedingung H=HQ=0 erfüllt, und sind sind folgenden Signale H=0 und HQ=1, so fand eine Bewegung nach LINKS statt. 2) Ist die Bedingung H=HQ=0 erfüllt, und sind die folgenden Signale H=1 und HQ=0 (also genau umgekehrt), so fand eine Bewegung nach RECHTS statt. 3) Ist die Bedingung H=HQ=1 erfüllt, und sind die folgenden Signale H=1 und HQ=0, so fand eine Bewegung nach LINKS statt. 4) Ist die Bedingung H=HQ=1 erfüllt, und sind die folgenden Signale H=0 und HQ=1, dann fand eine Bewegung nach RECHTS statt. Zur genauen Bestimmung der Richtung müs- sen wir also ZWEI zeitlich voneinander versetzte Signale GLEICHZEITIG auswer- ten! Das kann unter Umständen sehr kom- pliziert werden, da mehrere Vergleiche ineinander verschachtelt werden müssen, um die genaue Richtung herauszufinden. Aus diesem Grund habe ich eine sinnvolle Vereinfachung durchgeführt, die durch folgende Grafik erklärt werden soll: Durch eine Exklusiv-Oder-Verknüpfung der Signale OldH (entspricht dem alten Wert von H, wobei H=HQ war) und H erhält man folgende Aussagetabelle:
OldH H Ergebnis ------------------
0 0 0 0 1 1 1 0 1 1 1 0
Vergleichen wir nun diese Werte mit den oben aufgezählten Richtungsbedingungen, so sieht man, daß das Ergebnis immer dann 0 wird, wenn eine Linksbewegung durchgeführt werden muß, und ebenso daß es immer 1 wird, wenn eine Rechtsbewe- gung durchzuführen ist. Dadurch haben wir uns eine Menge Vergleichsbefehle gespart. Wir müssen jetzt lediglich das Ergebnis der EOR-Operation prüfen, um die Bewegungsrichtung zu bestimmen. Ent- sprechend wird mit dem Vertikal-Signal verfahren. Kommen wir nun zu der fertigen Steuer- routine. Hierbei müssen wir auf vier wichtige Dinge achten: 1) Das Programm zur Mausabfrage MUSS in jedem Fall in der Hauptschleife des Rechners laufen, da sich die Signale der Maus jederzeit ändern können. Eine zyklische Abfrage aus dem IRQ, so wie das z.B. beim Joystick möglich ist, wäre also undenkbar, da sie un- ter Umständen das eine oder andere Signal verpassen würde und somit die Interpretation desselben von unserer Mausabfrage falsch sein könnte. Die Maus würde ruckeln und sich gegebe- nenfalls sogar in die umgekehrte Richtung bewegen. 2) Desweiteren sollte ich Sie darauf aufmerksam machen, daß die Tastatur in der Zeit, in der die Maus ange- schlossen ist nicht zu benutzen ist, da sie IMMER ein Signal sendet (es sei denn V, VQ, H und HQ sind zufäl- lig gerade alle auf 0) und somit die Tastaturabfrage stört (wie wir wissen benutzt diese ja die selben Datenre- gister der CIA wie die Joyports). Wenn Sie also das Beispielprogram "AMIGA-MAUS1" auf dieser MD auspro- bieren wollen, sollten Sie immer da- ran denken, das Programm ZUERST zu laden und mit RUN zu starten und dann erst die Maus einzustecken! 3) Durch die ständige Abfrage müssen wir ebenfalls daran denken, das Maussig- nal zu "entprellen". Das heißt, daß wir prüfen müssen, ob die Maus nun gerade bewegt wird oder nicht. In jedem Fall werden wir nämlich ein und dasselbe Signal mehrmals lesen. Für uns sind jedoch immer nur die Sig- naländerungen von Bedeutung, weshalb wir prüfen müssen, ob das neu gelese- ne Signal nun gleich, oder verschie- den vom letzten Signal ist. Bei Gleichheit wird der Wert einfach ig- noriert und wieder zur Leseschleife zurückverzweigt. 4) Als Letztes will ich Ihnen nun noch die Belegung der Maussignale erkl- ären. Die Maus hat ja, wie der Joy- sick auch, einen 9-poligen Gameport- stecker, dessen Signale sich glückli- cherweise so auf die 9 Pins vertei- len, daß wir sie (fast) alle über die CIA abfragen können. Die Belegung ist hierbei wiefolgt:
Pin Funktion ------------
1 V-Impluse 2 H-Impulse 3 VQ-Impulse 4 HQ-Impulse 5 Knopf 3 (unbenutzt, da nicht vor- handen) 6 Knopf 1 (links) 7 +5 Volt (Betriebsspannung) 8 GND (Masse) 9 Knopf 2 (rechts)
Die Pins 1-4 und 6 der Joyports sind ja, wie Sie aus dem letzten Kursteil noch wissen sollten, mit den Bits 0-4 der Datenports der CIA1 verbunden. Demnach können wir also problemlos die Richtung der Maus abfragen, sowie die linke Maustaste. Die rechte Mau- staste hängt an einem der A/D-Wandler des SID und soll uns deshalb hier nicht interessieren. Der Einfachheit halber habe ich die Abfrage über Joyport2 programmiert, die Signale der Maus erscheinen also im Datenregister A der CIA1 (Reg.0 - Adresse $DC00). Dies ist deshalb ein- facher, weil dieser Port schon von der Tastaturabfrage her auf "Eingang" geschaltet ist. Die benötigten Signale finden wir an folgenden Bitpositionen im Datenregi- ster A:
Bit Signal ----------
0 V 1 H 2 VQ 3 HQ 4 Linke Maustaste
Kommen wir nun zu unserem Beispielpro- gramm "AMIGA-MAUS1", das zusammen mit seinem Quellcode ("AMIGA-MAUS1.SRC") auf dieser MD zu finden ist. Starten Sie es bitte mit RUN und schließen Sie dann eine orginal AMIGA-Maus am Joyport2 an um den Mauspfeil über den Bildschirm zu bewegen. Mit der linken Maustaste wird das Programm abgebrochen. Hier nun die Dokumentation des Pro- gramms:
**************************************** start lda #00 Bildschirm- sta 53280 farben lda #11 auf schwarz/grau sta 53281 setzen. lda #<(text) Text ab Label ldy #>(text) "TEXT" jsr strout ausgeben. lda #01 Spritefarbe auf sta vic+39 weiß setzen. lda #150 Sprite in den sta vic Bildschirm sta vic+1 positionieren. lda #36 Sprite-Pointer sta 2040 setzen. lda #01 Und Sprite 0 sta vic+21 einschalten **************************************** loop2 lda $dc00 Joyport2 lesen eor #$ff und invertieren and #$1f Bits 0-4 isolie- ren cmp #16 Vergleiche mit 1 bcc l1 Wenn kleiner, dann Bewegung rts Sonst wurde Knop gedrückt, also Ende *** l1 ldx #00 Alle stx h Puls- stx hq Register stx v werden stx vq gelöscht lsr V-Bit in V-Reg rol v hineinrollen lsr H-Bit in H-Reg rol h hineinrollen lsr VQ-Bit in VQ-Reg rol vq hineinrollen lsr HQ-Bit in HQ-Reg rol hq hineinrollen **************************************** horizon lda h H mit HQ eor hq verknüpfen cmp hmem und prüfen, ob gleich altem Wer beq vertical Ja, also entprel len sta hmem Sonst merken und cmp #00 prüfen, ob =0 bne l2 Nein, also Bewe- gung ausführen lda h Sonst war H=HQ, deshalb Bitmuste sta oldh in OLDH merken jmp vertical und weiter *** l2 lda oldh OLDH und H verknüfpfen, eor h um die Richtung herauszufinden bne l7 =1, also Rechts! jmp moveleft =0, also Links! l7 jmp moveright **************************************** vertical lda v V mit VQ eor vq verknüpfen cmp vmem und prüfen, ob gleich altem Wer beq loop2 Ja, also entprel len sta vmem Sonst merken und cmp #00 prüfen, ob =0 bne l8 Nein, also Bewe- gung ausführen lda v Sonst war V=VQ, deshalb Bitmuste sta oldv in OLDV merken jmp loop2 und zurück *** l8 lda oldv OLDV und V verknüpfen eor v um die Richtung herauszufinden bne l9 =1, also Hoch! jmp moveup =0, also Runter! l9 jmp movedown ****************************************
Zum besseren Verständnis noch einig Erklärungen zum Source-Code:
1) Die Routinen MOVELEFT, MOVERIGHT, MO VEUP und MOVEDOWN sind Unterroutinen
die das Sprite 0, das den Mauspfei repräsentiert, entprechend der gefor derten Richtung bewegen. Ich habe si hier nicht aufgeführt, da sie nicht mit der CIA zu tun haben. Sie könne Sie sich jedoch im Source-Code vo "AMIGA-MAUS1" ansehen. 2) Die Labels H, HQ, V, VQ, OLDH, OLDV HMEM, und VMEM stehen für Speicherzel len, in denen bestimmte Werte zwi schengespeichert werden. 3) Beim Vergleich, ob H gleich HQ (bzw. gleich VQ) ist habe ich ebenfalls di EOR-Verknüfung benutzt, da so das Ent prellen der Maussignale vereinfach wird. HMEM (bzw. VMEM) ändern sic immer abwechselnd von 0 auf 1 und um gekehrt. 4) Ab dem Label "TEXT" steht der Text der beim Aufruf von "AMIGA-MAUS1" aus gedruckt wird. 5) Beachten Sie bitte, daß die Eingangs werte immer invertiert werden, da de CIA sie uns invertiert im Datenregi ster angibt (sollten Sie noch vo letztem Kursteil her wissen) Desweiteren wird der Wert dann noc AND-Verknüpft um die Bits 0-4 zu iso lieren. Dadurch wird es möglich di Maustaste durch einen einfachen Ver gleich mit dem Wert 16 abzufragen Sonst würde das Programm nämlich auc bei dem Druck auf eine Taste, die di Bits über dem 4. Bit setzt abbrechen! Das war es dann mal wieder für diese Monat. Behalten Sie die AMIGA-Maus, je doch noch bis nächsten Monat. Wir werde uns dann um den Userport kümmern und ein Mausabfrage programmieren, die die Nach teile der oben aufgeführten (nämlich da die Tastatur unbenutzbar ist, und da nicht aus dem Interrupt heraus abgefrag werden kann) verbessert. Bis dahin, wünsche ich ein "funny Mou sing-Around",
Ihr Uli Basters (ub
CIA-Kurs (Teil 10) "Die Geheimnisse des Secret Service..." ----------------------------------------
Herzlich willkommen zum 10. und letzten Teil dieses Kurses. Wir wollen uns heute weiterhin mit der Mausabfrage beschäfti- gen, wobei wir in Zusammenhang mit dem Userport - einem der wichtigsten Themen, wenn es um die CIA geht - die Abfrage von letztem Monat noch verbessern wol- len. Also los... Wie Sie sich bestimmt noch erinnern, hatten wir in der letzten MD eine Maus- abfrage für eine AMIGA-Maus am Joyport programmiert. Hardwaremäßig war das auch am einfachsten, da wir die Maus direkt an den 64er anschließen konnten. Doch es ergaben sich aber auch diverse Nachtei- le: 1) Solange die Maus angeschlossen war, war es uns nicht möglich die Tastatur zu benutzen. 2) Eine Abfrage aus dem Interrupt war nicht möglich, da die Maussignale ständig überwacht und ausgewertet werden mußten. 3) Die rechte Maustaste konnte leider nicht Abgefragt werden. Diese drei Nachteile wollen wir nun ele- gant beseitigen, indem wir die Maus nicht am Joyport, sondern am Userport anschließen. Sicher haben Sie schon ein- mal von diesem Anschluß Gebrauch ge- macht, ist er doch der vielseitigste von allen Anschlüssen am C64. Drucker, Mo- dems, Eprommer, oder Digitizer werden über ihn angeschlossen und bedient - kurzum, ohne ihn wäre der 64er nicht das was er ist! Deshalb soll er uns nun interessieren. Am Userport liegt eine Vielzahl von Sig- nalen und internen Leitungen an, die es uns ermöglichen, direkt in die Hardware unseres Rechners eingreifen zu können. Und gerade weil fast alle Leitungen des Userports mit den beiden CIAs etwas zu tun haben, passt er hervorragend in die- sen Kurs. Kommen wir also zum Aufbau, dieser Un- scheinbaren Schnittstelle, an der Rück- seite unseres "kleinen Brotkastens". Insgesamt 24 Leitungen sind dort heraus- geführt, die alle eine bestimmte Bedeu- tung haben. Hierzu gibt es jetzt erst einmal eine Grafik: (Anm.d.Red.: Bitte wählen Sie jetzt den 2.Teil des CIA-Kurses 10 aus dem Menu.)
Ich möchte Ihnen nun die einzelnen Sig- nale genauer erklären: * Wie Sie sehen ist Masse (GND) viermal am Userport zu finden nämlich an den Pins 1,12,A und N. Auf diese Weise ist sichergestellt, daß man immer an den äußeren Pins den Gegenpol einer Gleichspannung findet. * Die oben genannte Gleichspannung sind wohl in der Regel die +5 Volt an Pin 2, die man als Betriebsspannung für Hardwareerweitungen benutzen kann. Die dort herausgeführte Leitung ist mit maximal 100 mA belastbar. * Pin 3 enthält die RESET-Leitung des Rechners. Durch sie kann der gesamte C64 wieder in den Einschaltzustand zurückversetzt werden. Da das Signal invertiert ist (Strich über dem Wort "RESET"), muß kein Pegel angelegt wer- den (+5V), um den Reset auszulösen, sonden man muß diesen Pin mit Masse verbinden. * Die Leitungen CNT1 (Pin4) und CNT2 (Pin6) sind die CNT-Leitungen von CIA1 und CIA2. Wir haben diese Leitungen schon bei den Timerinterrupts kennen- gelernt, da man sie zur Timersteuerung heranziehen kann. Sie spielen eine große Rolle bei unserer Mausabfrage nacher. * Die Leitungen SP1 (Pin 5) und SP2 (Pin 7) sind die SP-Leitungen von CIA1 und CIA2. Diese Leitungen werden zur inte- raktiven seriellen Datenübertragung benutzt und sollen uns ebenfalls nach- her noch beschäftigen. * Die Leitungen PC2 (Pin 8) und FLAG2 (Pin B) sind negierte Signale (deshalb auch der Strich über den beiden Wor- ten). Sie sind die Handshake-Leitungen von CIA2. Ein "Handshake" ist wichtig, um bei einer Datenübertragung eine Verbindung korrekt aufzubauen. PC2 ist dabei der Handshake-Ausgang, FLAG2 der Handshake-Eingang. * Die Leitung "ATN IN" ist ein hier ebenfalls zu findendes Signal vom IEC-Bus, an dem die Floppy angeschlos- sen ist. Dieses Signal stammt von der Portleitung 3, Port A (PA3) der CIA2. * An den Pins 10 und 11 liegt eine Wech- selspannung von 9 Volt an. Da es sich um Wechselspannung handelt benötigt man natürlich 2 Anschlüsse (Wechsel- spannung wird im Gegensatz zu Gleich- spannung nicht mit Masse am Gegenpol angeschlossen). * Die Pins C-L entsprechen dem gesamten Port B der CIA2. Dadurch wird es mö- glich eine "echte" parallele Datenü- bertragung zu realisieren (CENTRO- NICS), wie sie von vielen Druckern verlangt wird. Gleichzeitig dienen diese Leitungen dem Datenaustausch verschiedenster Art zwischen dem C64 und einer angeschlossenen Hardware. Auch wir werden diese Leitungen später benutzen. * Pin M beinhaltet ebenfalls eine Port- leitung, nämlich das 2. Bit von Port A. Auch diese Leitung kann als I/O- Leitung genutzt werden (z.B. für Steuersignale einer Parallelschnitt- stelle) Soviel also zu den Leitungen am User- port. Wie Sie sehen bedient Haputsäch- lich die CIA2 den Datenverkehr an dieser Schnittstelle. Von CIA1 finden wir nur zwei Leitungen (CNT1 und SP1). Doch ge- rade diese Leitungen, zusammen mit den äquivalenten Leitungen von CIA2 (CNT2 und SP2) spielen bei unserer Mausabfrage später noch eine große Rolle. Kommen wir nun also zu der Abfrage selbst. Wie versprochen, wollen wir ja die drei oben genannten Nachteile unter- binden. Um es einmal langsam anzugehen wollen wir zunächst einmal die Tastatur wieder benutzbar machen. Wie Sie ja nun wissen, sind die Portleitungen der CIA2 am Userport herausgeführt. Diese sind glücklicherweise vom Betriebssystem un- genutzt, weshalb wir an diesen Leitungen getrost Signale einleiten können, ohne dabei den die "normale" Rechnerumgebung zu stören. Da die Tastaturabfrage von CIA1 erledigt wird stören die am User- port angeschlossenen Maussignale sie also in keinster Weise. Zudem können wir haargenau dieselbe Abfrageroutine wie beim letzten Mal benutzen, jedoch mit dem Unterschied, daß sie sich nun die Mausdaten aus Port B von CIA2 holen muß. Wir müssen also lediglich einen Befehl des Programms von letztem Monat ändern, nämlich das ehemalige "LDA $DC00" in ein LDA "$DD01" und schon funktioniert die Abfrage! Zusätzlich brauchen wir jetzt natürlich noch einen Adapterstecker, der jedoch einfach nachzubauen ist. Alles was Sie dazu brauchen ist:
1) Ein Userportstecker 2) Eine Joyportbuchse 3) Etwas Kabel 4) und ein kleines bisschen Erfahrung
mit dem Lötkolben. Bis auf Punkt 4 ist alles für ein paar Mark im Elektronikfachhandel erhältlich. Wenn Sie alles zusammen haben, verbinden Sie bitte Pins der beiden Stecker wie- folgt:
Joyport Userport Signal ----------------------------------------
1 --> C Vertical Pulse 2 --> D Horizontal Pulse 3 --> E Vertical Quadrature 4 --> F Horizontal Quadrature 6 --> H Linker Mausbutton 7 --> 2 Betriebsspannung +5V 8 --> 1 GND 9 --> J Rechter Mausbutton
Die Signalnamen sollten Sie noch aus letztem Kursteil kennen. Achten Sie beim Löten bitte darauf, daß Sie die richtige Symmetrie benutzen. Beim Userport sind die Leitungen näm- lich, wenn man von vorne auf den STECKER schaut genau spiegelverkehrt (Pins 1-12 und A-N von rechts nach links). Schauen Sie hinten auf die Lötpins des Steckers, so stimmt die Belegung wieder, so wie Sie in der obigen Grafik aufgeführt war. Ähnliches gilt für die Joyportbuchse - wenn Sie von vorne auf sie draufsehen ist Pin 1 links oben und Pin 9 rechts unten. Von hinten ist Pin 1 rechts oben und Pin 9 links unten. In der Regel sollten aber beide Stecker auch mit Pin- nummern versehen sein, so daß man keine Fehler machen kann. Achten Sie aber bit- te doch darauf und prüfen Sie vor dem ersten Anschluß nocheinmal alle Pins nach, da Sie sich bei einer falschen Belegung durchaus den Rechner kaputt machen können!!! Wir übernehmen in die- sem Fall keinerlei Ersatzansprüche! Worauf Sie ebenfalls achten sollten ist das sie eine Joyport-BUCHSE benötigen - nicht etwa einen STECKER. Das heißt, daß das Ding, welches Sie sich kaufen genau denen entsprechen muß, die an den Joy- ports des C64 herausschauen! Wenn dann alles geklappt hat können Sie Ihren Adapterstecker ausprobieren. Stek- ken Sie ihn am Userport, bei abgeschal- tetem Rechner, ein (bitte wieder darauf achten, daß Sie ihn nicht verkehrt hi- neinstecken, da sonst derselbe Effekt wie oben auftritt) und schließen Sie eine orginal AMIGA-Maus an der anderen Seite an. Wie Sie sehen, können Sie die Tastatur immer noch ganz normal benut- zen. Laden Sie nun das Programm "AMIGA- MAUS2" von dieser MD und starten Sie es mit RUN. Sie können jetzt den Mauspfeil über den Bildschirm bewegen. Das Pro- gramm kann mit der linken UND - im Ge- gensatz zu unserer alten Abfrage - mit der rechten Maustaste abgebrochen wer- den. Damit hätten wir also schon einmal zwei Nachteile beseitigt, nämlich der, daß die Tastatur unbenutzbar war und der daß die rechte Maustaste nicht abgefragt werden konnte. Letztere hatten wir ja beim Bau des Adaptersteckers mit Pin J des Userports verbunden, womit ihr Sig- nal an der Leitung PB5 von CIA2 anliegt. Diese Leitung entspricht nun dem 5. Bit von PortB der CIA2, womit wir die Taste problemlos abfragen könnten! Falls es Sie interessiert - der Source- Code von "AMIGA-MAUS2" ist ebenfalls auf dieser MD unter dem Namen "AMIGA- MAUS2.SRC" enthalten. Er entspricht je- doch, abesehen von der oben beschiebenen Änderung haargenau dem von "AMIGA-MAUS1" Nun gut - über den Userport haben wir die Maus und die Tastatur vollständig benutzbar gemacht, jedoch ist es schon hinderlich, wenn die Abfrage immer in der Hauptschleife des Rechners läuft. So muß bei einem Programm, das mit der Maus bedient wird, bei jeder einzelnen Routi- ne, die etwas anderes tun soll als die Maus abzufragen, die Abfrage unterbro- chen werden. Und das ist verbunden mit hohem organisatorischem Aufwand. Deshalb wollen wir nun versuchen, eine Abfrage über Interrupts zu programmie- ren. Es IST möglich - ich habe mir da eine pfiffige Routine für Sie ausge- dacht. Wie ich oben schon erwähnte sollen dabei die Leitungen CNT1 und CNT2 am Userport eine wichtige Rolle spielen. Vielleicht erinnern Sie sich ja noch an die ersten Teile des CIA-Kurses, in denen ich Ihnen die Timerprogrammierung erklärte. Damals hatten wir festgestellt, daß die Timer der CIAs Interrupts auslösen können. Bei CIA1 waren das IRQs, bei CIA2 die NMIs. Dabei konnte man verschiedene Ereignisse als Timertrigger einstellen. Im Regel- fall war das der Systemtakt; bei der Timerkopplung war es der Unterlauf von Timer A. Und nun kommts: wir konnten ebenso ein Signal an der CNT-Leitung einer CIA als Timertrigger einstellen. Und genau das ist der Punkt an dem wir ansetzen wollen. Genauer gesagt wird ein Timer, wenn er die CNT-Leitung als Timertrigger pro- grammiert hat, immer dann um 1 herunter- gezählt, wenn an der CNT-Leitung eine steigende Flanke anliegt. Wenn also der Pegel an dieser Leitung gerade von 0 auf 1 umspringt. Danach nicht mehr, solange die Leitung auch auf 1 liegen bleiben sollte. Sie muß erst wieder auf 0 abfal- len, damit der Timer durch eine neue Flanke ein weiteres Mal erniedrigt wer- den kann. Vergleichen wir das einmal mit den Signalen, die uns die Maus liefert (letzten Monat hatte ich das ja genauer erklärt), so stellen wir fest, daß die Maussignale auch immer von 0 auf 1 wech- seln, egal ob es nun das H-, HQ-, V- oder VQ-Signal ist. Wir können also die- se Signale als Timertrigger verwenden, und zwar so, daß der Timer immer alle Flanken der Maus mitzählt. Dadurch kön- nen wir genau erfahren, um wieviele Ein- heiten die Maus bewegt wurde! Das einzi- ge Problem dabei ist die Richtungsbe- stimmung, weil ja in beiden Richtungen dieselben Signale erzeugt werden (beschränkt man sich auf nur EIN Signal, z.B. das H-Signal). Das heißt, daß wir bei JEDER Bewegung, die stattfindet, auch das entsprechende Quadrature-Signal überprüfen müssen um die genaue Richtung bestimmen zu können. Das reine Zählen der Impulse nutzt uns also nichts, den- noch reicht es zum Auslösen eines Inter- rupts bei jeder Bewegung. Wenn wir einen Timer nämlich mit dem Wert 0 initialisieren, genügt ein einzi- ger Impuls von der CNT-Leitung, um einen Interrupt auszulösen. Dieser muß nun feststellen, welche Bewegungsrichtung ausgeführt wurde. Der Witz ist, daß die- se Abfrage sogar noch einfacher ist, als die von der ersten und zweiten Version von AMIGA-MAUS. Weil wir nämlich nicht zwei zeitlich voneinander versetzte Sig- nale überprüfen müssen. Zur besseren Erläuterung will ich Ihnen nocheinmal die Signalfolgen der Bewegungsrichtungen auflisten:
Linksbewegung : H - ...11001100... HQ - 10011001 ↑ ↑ Interrupt Rechtsbewegung: H - ...11001100... HQ - 01100110 ↑ ↑ Interrupt
Gehen wir nun davon aus, daß wir das H-Signal an der CNT-Leitung anliegen haben, und daß einer der Timer der dazu- gehörigen CIA diese Leitung als Trigger hat und zudem mit 0 initialisiert ist, so wird jedesmal, wenn das H-Signal auf 1 springt ein Interrupt ausgelöst, weil der Timer unterläuft. Diese Stellen habe ich in obiger Auflistung markiert ("↑"). Wenn Sie genauer hinsehen, so erkennen Sie, daß das HQ-Signal bei einer Links- bewegung zum Zeitpunkt des Interrupts immer 1, bei einer Rechtsbewegung immer 0 ist. Um nun die Bewegungsrichtung zu bestimmen brauchen wir lediglich das HQ-Signal an einem der Porteingänge von PortB am Userport anzuschließen und bei Auftreten eines Interrupts auszulesen. Ist es 0, so müssen wir den Mauspfeil nach rechts bewegen, ist es 1, so muß der Mauspfeil nach links. Wir brauchen also noch nicht einmal das letzte Signal zur Bestimmung heranzuziehen. Ebenso wird übrigens mit den vertikalen Signa- len verfahren. Hierbei liegt das V- Signal dann an der anderen CNT-Leitung an. Für unser Beispiel habe ich die Be- legungen wievolgt belegt: * V-Signal an CNT1 - löst also IRQs aus (weil an CIA1). * H-Signal an CNT2 - löst also NMIs aus (weil an CIA2). * VQ und HQ wie bei Adapterstecker1 Bei den V-Signalen müssen wir noch etwas beachten: Da der Systeminterrupt über einen Systemtaktgetriggerten Timer A läuft, und wir diesen ja weiterbenutzen möchten, müssen wir die Auswertung des V-Signals über Timer B programmieren. Wenn jetzt ein IRQ auftritt, so müssen wir erst anhand der gesetzten Bits im "Interrupt-Control-Register" (ICR) fest- stellen, welcher Timer der Interrupt- auslöser war. War es Timer A, so wird auf den Systeminterrupt weiterverzweigt, war es Timer B, so wird auf die Abfrage der Vertikalbewegung gesprungen. Ähnlich verhält sich dies bei den H- Signalen. Weil wir die Maustasten ja ebenfalls noch Abfragen wollen, müssen wir den zweiten freien Timer der NMI-CIA zyklische Interrupts auslösen lassen, die in regelmäßigen Abständen die Mau- stasten abfragen. Der Einfachheit halber habe ich die Horizontalbewegungsabfrage ebenfalls über Timer B der CIA2 program- miert, weshalb wir also Timer A zur Ab- frage der Maustasten benutzen wollen. Kommen wir nun zu dem Programm selbst. Hier ist der kommentierte Source-Code:
**************************************** start sei IRQs sperren ldx #<(irq) IRQ-Vektor ldy #>(irq) auf neue stx $0314 IRQ-Routine sty $0315 verbiegen. ldx #<(nmi) NMI-Vektor ldy #>(nmi) auf neue stx $0318 NMI-Routine sty $0319 verbiegen.
ldx #$83 Timer A und B als stx cia1+13 Interruptquelle für stx cia2+13 beide CIAs setzen
lda #00 Timer B von sta cia1+6 CIA1 sta cia1+7 und sta cia2+6 CIA2 mit dem Wert sta cia2+7 0 initialisieren. ldy #$90 Timer A von CIA2 sta cia2+4 initialisieren sty cia2+5 ($9000=27 IRQs/s) lda #$21 Trigger=CNT und "T mer Start" sta cia1+15 in Control-Regist sta cia2+15 für Timer B von CIA1 und CIA2. lda #$81 Timer A von CIA2 sta cia2+14 mit SysTakt als Trigger starten lda #00 Bildschirm- sta 53280 farben lda #11 setzen sta 53281 und lda #<(text) Begrüßungs- ldy #>(text) Text jsr strout ausgeben. lda #01 Sprite 0 sta vic+39 als sta vic+21 Maus- lda #150 pfeil sta vic ini- sta vic+1 tiali- lda #41 sieren. sta 2040 cli IRQs freigeben rts und ENDE. **************************************** nmi pha Alle txa Prozessorregister pha erstmal tya auf Stapel pha retten. cli IRQs freigeben. lda $dd01 Datenport sporadis eor #$ff laden und invertie sta mem merken. lda cia2+13 ICR von CIA2 holen and #$02 Bit 1 isolieren beq buttons Wenn =0, dann war Timer A der NMI- Auslöser, also Knopfabfrage. lda mem Sonst H-Bewegung.. and #$08 Bit für HQ-Signal aus Datenport iso lieren beq moveleft Wenn 0 war ->links bne moveright Wenn 1 war ->rechs buttons lda mem Aus Datenport die and #$30 Bits 4 und 5 (Mau knöpfe) isolieren beq bye Wenn =0, dann war ner gedrückt. and #$20 Sonst Bit5 isolier beq leftone Wenn =0, war der linke gedrückt lda #42 Spritepointer ldx #01 und Timerwert lad l8 sta 2040 und setzen. stx cia1+6 (Timerwert in BEI stx cia2+6 CIAs!) jmp bye NMI beenden. leftone lda #41 Spritepointer und ldx #00 Timerwert für link jmp l8 Taste setzen **************************************** irq lda cia1+13 ICR von CIA1 laden and #$02 Bit 1 isolieren bne ok Wenn =1, dann wars ein Maus-IRQ jmp sysirq Sonst auf SysIRQ springen ok cli IRQs freigeben lda $dd01 Datenport laden und eor #$ff invertieren. and #$04 Bit für VQ-Signal isolieren beq moveup Wenn 0 war -> hoch bne movedown Wenn 1 war -> runter ****************************************
Hier nun noch einige Erläuterungen: 1)Wie Sie sehen, müssen wir bei NMIs den Prozessorregister "von Hand" auf den Stapel retten. Bei NMIs geschieht dies nicht durch eine Routine im Betriebs- system, so wie es bei den IRQs der Fall war. 2)Nachdem eine der beiden Interrupt- routinen aufgerufen wurde, wird so früh wie möglich das IRQ-Flag wieder gelöscht und die IRQs zugelassen. Das ist deshalb so wichtig, weil die IRQs beim Auftreten eines Interrupts (sei das ein IRQ oder ein NMI) gesperrt werde.Befindet sich nun aber der Rech- ner gerade in einem NMI, während die Maus der Vertikalen bewegt wird, so wird kein IRQ ausgelöst, weil dieser ja noch gesperrt ist. Um das doch noch zu ermöglichen müssen wir den IRQ ex- plizit freigeben. Bei NMIs brauchen wir darauf nicht zu achten, weil NMIs ja nicht maskierbar sind.Das heißt,daß ein NMI immer einen NMI unterbrechen kann! 3)Denken Sie bitte nach wie vor daran daß die Daten der Datenports inver- tiert in den CIA-Registern stehen. Wir müssen sie beim Lesen deshalb gleich nochmal invertieren.Das ist gerade bei der Maustastenabfrage sehr wichtig, weshalb der Datenport in jedem Fall erst einmal invertiert wird,bevor eine Entscheidung getroffen wird, woher der NMI überhaupt kam. Bei der Vertikalabfrage, hätte ich den Datenport nicht unbedingt invertieren müssen. Da hier lediglich nur ein eim- ziges Bit abgefragt wird,hätte ich die Branchbefehle zu den Bewegungsroutinen ebenso vertauschen k nnen. Der Voll- ständigkeit halber wird hier der Wert jedoch ebenfalls invertiert. 4)Sicher hat Sie die merkwürdige Maus- button abfrage etwas verwirrt.Ich habe hier noch eine kleine Funktion einge- baut die durch die Art unserer Abfrage ganz einfach zu programmieren wurde. Zunächst einmal wird durch den Druck auf einen der Mausknöpfe der Sprit- pointer zwischen den Spriteblöcken 00 und 42 hin- und hergeschaltet. Dies nur, um Ihnen die Mausbuttonabfrage optisch zu signalisieren. Zusätzlich wird, je nach dem welchen Knopf sie noch drücken, ein anderer Wert in die Timer-Register geschrieben.Drücken Sie die linke Taste,so ist das der Wert 0, wie wir es für die Abfrage ja schon vereinbart hatten. Drücken Sie jedoch die rechte Maustaste, so wird der Wert 1 in die Timer geladen. Durch diesen kleinen, aber effektiven Trick können wir die Mausauflösung halbieren. Da- durch, daß nun 2 Impulse von der Maus kommen müssen, bis ein Interrupt auf- tritt müssen Sie die Maus doppelt wait über den Tisch bewegen, um den Maus- pfeil eine bestimmte Strecke weg zu bewegen.Das kann oftmals kanz nützlich sein, z.B.wenn man genau zeichnen muß. Wenn Sie brigens den Wert 2 die Timer schreiben, so verkleinet sich die Auf- lösung um ein Drittel und so fort... 5)Achten Sie bitte auch auf die Abfragen in den Interruptroutinen, von welchen Quelle der Interrupt kam. Im ICR sind dann n mlich die entsprechenden Bits die auch beim Einstellen der Interrup- quellen benutzt werden gesetzt.So kön- nen wir also unterscheiden, von wo ein Interrupt kam. 6)Die Routinen MOVELEFT, MOVERIGHT,MOVE- UP und MOVEDOWN sind Routinen die den Mauspfeil bewegen und sollen hier nicht näher erläutert werden. Sie kön- nen Sie sich aber gerne im Source- Listing "AM GA-MAUS3.SRC" auf dieser MD anschauen 7)Das Label ENDIRQ enthüllt die Adresse $EA7E. Ab dieser Adresse stehen im Be- triebssystem die Befehle, mit denen jeder Interrupt (auch NMIs) beendet werden.Es werden einfach die Prozesor- register wieder vom Stapel geholt. Um unsere eigenen Interrupts zu be- enden springe ich also der Einfachheit halber gleich diese Adresse an. Natürlich brauchen Sie zum Betrieb der neuen Mausabfrage einen neuen Adapter- stecker. Damit das nicht allzu umständ- lich wird, habe ich die Belegungen im Großen und Ganzen so gelassen wie sie beim erst Adapterstecker waren. Sie müssen lediglich zwei Leitungen umlöten: * Das V-Signal (Pin 1 an der Joyport- buchse) kommt jetzt an CNT1 (=Pin 4 am Userport - vorher Pin C). * Das H-Signal (Pin 2 an der Joyport- buchse) kommt jetzt an CNT2 (=Pin 6 am Userport - vorher Pin D). Schließen Sie den neuen Stecker nun bei abgeschaltetem 64er am Userport an, und stecken Sie eine AMIGA-Maus am anderen Ende ein. Jetzt können Sie das Programm "AMIGA-MAUS3"von dieser MD laden und mit RUN starten. Wie Sie sehen, können Sie nun auch weiterhin Eingaben machen, da der Cursor weiterhin blinkt. Denken Sie nun daran, daß es bei Diskettenzugriffen Probleme geben wird, da die NMIs den Datenverkehr stören(sollten Sie noch aus dem ersten Teilen dieses Kurses wissen). In solchen Fällen ist es ratsam die Ab- frage doch abzuschalten (z.B. indem man die Timer einfach anhält, oder sie als Interruptquellen sperrt). Zum Schluß möchte ich Ihnen noch eine weitere Funktion der CIAs erklären. Nur mit dem Userport erhält sie überhaupt einen Sinn, weshalb ich sie bis jetzt auslassen mußte. Vielleicht erinnern Sie sich noch daran, daß das Register 12 einer CIA das soge- nannte "Serial-Data-Register" ist. Mit diesem Register kann über die SP-Leitung (die ja von beiden CIAs am Userport an- liegt) ein einfacher serieller Datenaus- tausch mit einem anderen Rechner(im ein- fachsten Fall ebenfalls ein 64'er statt- finden. Das kann sehr nützlich sein wenn man z.B. ein Spiel programmieren möchte, bei dem zwei Spieler an jeweils einem eigenen Rechner gegeneinander spielen Wenn das Spielprogramm also Daten über die Tätigkeiten und Bewegungen des ande- ren Spielers benötigt. Über das SD-Register wird dieser Daten- austausch stark vereinfacht und ist fast noch unkomplzierter, als wenn man sich der normal Seriellen Schnittstelle (RS232) des Betriebssystems bedient. Bei der Datenübertragung müssen wir un- terscheiden, ob die SP-Leitung nun auf Ein oder Ausgang geschaltet ist. Dies wird mit Bit 6 des Control-Registers- Timer A (Reg.14) angegeben.Ist es auf 1, so ist SP auf Ausgang, ist es auf 0, so ist SP auf Eingang geschaltet. Je nach Betriebsart verhält sich die Benutzung von SDR wie folgt: * Wenn SP Ausgang ist, so wird ein Wert, der in das SDR geschrieben wird mit der halben Unterlauffrequenz von Timer A in den entsprechenden CIA an SP "herausgesch ben". Das heißt, daß der Wert Bit für Bit, bei jedem Timerunter lauf an SP e scheint. Nach 8 Unter- läufen ist SDR wi der leer und es wird ein Interrupt ausgelöst. Bit3 im ICR zeigt an, daß der Interrupt von dem leeren SDR-Regist kommt. * Wenn SP Eingang ist, so wird mit jeder steigenden Flanke an CNT der entspre- chende CIA der Wert,der gerade anliegt (0 oder 1) in ein internes Schiebere- gister übernommen. Ist dies Mal ge- schehen, so wird der Wert in das SDR übertragen und ebenfalls ein Interrupt ausgelöst.Hier erkennt man ebefalls an Bit 3 im ICR, daß das SDR voll ist und ausgelesen werden kann. Kombiniert man das Ganze jetzt noch mit der Möglichkeit, daß die CIA bei einem Timerunterlauf ein Signal an PB6 anlegen kann,so kann man sehr einfach Daten aus- tauschen. Möchten Sie z.B.Daten an einen anderen C64 senden, so müssen Sie sich ein Kabel bauen, daß die Userporte der beiden Rechner miteinander verbindet. Dabei sollte der SP-Ausgang von Rechnern mit dem SP-Eingang von Rechner 2 verbun- den sein und die Leitung PB6 von Recher1 mit der Leitung CNT von Rechner2 (ob SP1 oder SP2, bzw.CNT1 oder CNT2 hängt davon ab mit welcher CIA sie die Daten über- tragen wollen). Jetzt müssen Sie im Control-Register von Timer A (des sendenden Rechners) noch festlegen, daß das Signal an PB6 bei jedem Timerunterlauf in die jeweils an- dere Lage gekippt werden soll. Das ge- schieht, indem Sie die Bits 1 und 2 dieses Registers auf 1 setzen. Dadurch erscheint nämlich ebenfalls mit der halben Unterlauffrequenz von Timer A(des sendenden Rechners) ein Signal an PB6 und läst somit die Datenübernahme am empfangenden Rechner aus! Das war es dann endgültig mit dem CIA- Kurs. Ich hoffe, daß Sie nun einen bes- seren Einblick in die Funktionen dieser beiden kleinen, aber extrem mächtigen Bausteine innerhalb unseres Rechners haben. Wie Sie sehen lassen sich sehr viele Probleme mit Interrupts leichter lösen (wie die Mausabfrage beweist). Zu- sätzlich können Sie mit dem Userport vielf ltige Har wareerweiterungen leicht und einfach bedienen. Ich freue mich also, wenn es Ihnen ein wenig Spaß gemacht hat und bi n für Kritik und Anregungen zu neuen Kursen immer zu haben (auch ein kleiner Kurs- autor kriegt gerne Leserpost).
Bis auf Weiteres also ein letztes Mal "Servus" Ihr Uli Basters (ub)