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)