Magic Disk 64

home to index to text: MD9101-KURSE-CIA-KURS_TEIL_3-1_:_DIE_GEHEIMNISSE_DES_SECRET-SERVICE_(TEIL_3).txt
                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ümmern! Letztes Mal hatten wit ja den System- IRQ behandelt, dessen Funktionsaufbau 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 Systeminterrupt 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 hatten) abarbeitet. Wollen wir einen eigenen 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 wiederkehrende Aufgaben handelt) nicht noch umständlich um das Programmieren eines CIA-Timers kümmern müssen, sondern einfach die vorgegebene Timerprogrammierung übernehmen.
Sie erinnern sich ja bestimmt noch daran, 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 IRQoder 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 " verbiegen"), daß er anschließend auf unsere eigene IRQbzw. BRK-Routine zeigt.
Ich habe Ihnen, wie schon erwähnt, einmal 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 Fehler- 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 hervorragend ü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 eigene 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 Blinken 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 einfach in den Bildschirmspeicher einzukopieren, was durch eine kleine, aber feine 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 Zeichen 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 anwedet. Zu jener Initialisierungsroutine kommen wir später. Ich möchte übrigens darauf hinweisen, daß alle hier erwähnten Routinen und Programme mit dem HYPRA-ASS- Assembler aus der Computerzeitschrift "64' er" erstellt wurden.
Leser, die das Eigabeformat und die Bedienungsweise 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" benutzt werden. Das sind Sprungmarken, die einfacher zu handhaben sind, da man so nur auf einen Namen springen muß, dessen absolute Adresse der Assembler berechnet. 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 Routinen entspricht!
Desweiteren muß ich noch auf eine Besonderheit in DOMSG hinweisen. Die Schleife wird erst dann verlassen, wenn das zuletzt gelesene Zeichen 0 ist ( wir Erinnern 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 Routinen relokatibel halten möchte. Verschiebt 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 Branchbefehls 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 Aussage 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 anschließ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 wollen ja den Systeminterrupt benutzen, der wie gesagt 60 Mal pro Sekunde auftritt) .
Für all diese Funktionen brauchen wir insgeamt 3 verschiedene Zwischenspeicher:
1) Eine Speicherzelle, die als Interruptzä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 wollen 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 vermerkt ist, welcher Ausgabemodus als nächstes benötigt wird. Ich nenne diese Speicherzelle einmal MSGMODE.
3) Zuletzt brauchen wir noch eine Speicherzelle, die mitzählt, wie oft der Text nun geblinkt hat. Ist eine ge- wisse Anzahl erreicht, so kann die IRQ-Routine in eine Routine verzweigen, 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 Verwendungszweck 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 sicher wissen) vom Betriebssystem nicht genutzt, weshalb wir sie für unsere Zwecke verwenden können.
Jetzt brauchen wir eine Initialisierungsroutine, die die neue IRQ-Routine in den System-IRQ einbindet und sie somit 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 unsere 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 gegliedert. 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 Xund Y-Register.
Der SEI-Befehl am Anfang ist sehr wichtig. 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 Prozessor in die Pampas und verabschiedet sich meistens danach. Um dem vorzubeugen, muß man einfach alle IRQs verhindern, 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 Xund Y-Register geholt und in die Speicheradressen unseres Zeigers geschrieben.
Im dritten und letzten Teil initialisieren wir noch zwei der drei Variablen ( PULSE wurde durch das Speichern am Anfang 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. Entweder es steht dort 0, dann soll bei der nächsten Ausgabe der Text gedruckt werden, 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 ( vorausgesetzt 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!