Magic Disk 64

home to index to html: KURS-CIA-KURS.html
                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.         
MD9011/MD9011-KURSE-CIA-KURS_TEIL_1-2.koala.png
            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:                  
MD9011/MD9011-KURSE-CIA-KURS_TEIL_1-3.koala.png
  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...          
MD9105/MD9105-KURSE-CIA-KURS_TEIL_7-2.hires.png
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
MD9106/MD9106-KURSE-CIA_TEIL_1.hires.png
            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                                  
MD9108/MD9108-KURSE-CIA_TEIL_1.hires.png
            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!)    
MD9108/MD9108-KURSE-CIA_TEIL_2.hires.png
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.) 
MD9109/MD9109-KURSE-CIA_TEIL_2.hires.png
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) 



Valid HTML 4.0 Transitional Valid CSS!