Magic Disk 64

home to index to html: MD9101-KURSE-CIA-KURS_TEIL_3-1_:_DIE_GEHEIMNISSE_DES_SECRET-SERVICE_(TEIL_3).html
                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ü!)                              
Valid HTML 4.0 Transitional Valid CSS!