Tips & Tricks

Funktionen auf Tastendruck

Mit diesem Beitrag zeigen wir Ihnen, wie auf Tastendruck beliebige Funktionen oder Programme auch während eines Programmlaufs gestartet werden können. Egal ob es sich nun um ein Basic- oder Maschinenprogramm handelt.

Dieser Artikel soll Ihnen das Thema »Funktionen« anhand eines Beispiels näherbringen. Das Beispielprogramm können Sie mit etwas Assemblerkenntnis auch für Ihre Programme verwenden. Vorschlag: Der F1-Taste wird eine Funktion zugeordnet. Durch Drücken der F1-Taste soll jederzeit das Directory einer Diskette am Bildschirm gezeigt werden, ohne daß ein im Speicher vorhandenes Programm gelöscht wird. Während das Directory geladen wird, soll der Bildschirmrahmen die Farbe des Hintergrundes annehmen.

Es wurden schon viele solcher Utilities veröffentlicht. Diese Programme werden aber im Regelfall durch Drücken der STOP/RESTORE-Tasten wieder abgeschaltet. Sollen diese Routinen dagegen gewappnet sein, so versagen sie spätestens nach einem Reset. Im Gegensatz zu diesen »Billigmethoden« soll aber unsere Directory-Funktion der F1-Taste auch nach einem Reset erhalten bleiben.

Um derartige Anforderungen zu realisieren, müssen einige Betriebssystemroutinen zumindest angeschnitten werden. Die Wichtigste ist die Interruptroutine des C 64. Die Interruptroutine ist ein computerinternes Programm, das sehr oft angesprochen wird. Befindet sich der Computer im »Ready«-Modus, wird die Interruptroutine beispielsweise alle 1/60 Sekunde durchlaufen. Wird der C 64 in Anspruch genommen, etwa durch Diskettenoperationen, wird dieses Programm jedoch seltener bearbeitet. Der Interrupt erfüllt viele Aufgaben. Er stellt beispielsweise die Uhrzeit (TI,TI$) nach, läßt den Cursor blinken und übernimmt die Tastaturabfrage. Das Interruptprogramm befindet sich ab Adresse $EA31 im Betriebssystem. Der Computer sucht sich diese Adresse über einen Wegweiser. Dieser Wegweiser steht bei Adresse $0314 und $0315 in der Zero-Page. Ein solcher Wegweiser wird im Fachjargon als Vektor bezeichnet. Da sich der Vektor im RAM befindet, kann dieser verändert werden. Das heißt, daß der Interrupt »umgeleitet« werden kann. Doch davon später.

Wie funktioniert eine Modulerkennung?

Eine zweite gewichtige Betriebsroutine ist die Modulerkennung. Diese benötigt man, damit die geplante Funktion der F1-Taste »resetfest« wird und zugleich immun gegen die Tastenkombination STOP/RESTORE. Die Kennungs-Routine überprüft, ob sich ein Steckmodul im Expansion-Port befindet. Und zwar an der Codefolge »cbm80« ab Adresse $8004. Diese ASCII-Codefolge entspricht den Hex-Zahlen $C3,$C2,$CD,$38 und $30. Findet der Computer diese fünf Zahlen ab Adresse $8004, werden sowohl bei einem Reset als auch beim Drücken der Restore-Taste nicht mehr die Standardroutinen angesprochen. Der C 64 springt die Adressen an, deren Vektoren in $8000/8001 und $8002/8003 stehen. Der erste Vektor ist der Resetvektor. Der Resetvektor zeigt auf die Adresse, die nach einem Reset angesprungen wird. Nach STOP/RESTORE holt sich der C 64 die Sprungadresse des NMI-Vektors aus den Speicherzellen $8002 und 8003. Ist kein Modul eingesteckt, ist der Adreßbereich ab $8000 als RAM freigegeben. Wenn man nun ab Adresse $8004 die oben genannte Codefolge ablegt, nimmt der C 64 an, daß sich ein Modul im Expansion-Port befindet. Durch diesen Trick kann der Reset- und NMI-(RESTORE-)Vektor verändert werden.

Flußdiagramm zum Interruptprogramm

Da unser Programm wegen dieses Tricks mitten im Basic-Speicher steht, muß es vor dem Überschreiben durch lange Basic-Programme oder Stringvariablen gesichert werden. Dies erreicht man, wenn man den Zeiger für den Stringspeicher $33-$34 und den Zeiger für die Speichergrenze auf $8000 setzt.

Doch nun zum Programm selbst. Nach dem Eingeben des Programmes mit Hilfe des MSE kann es mit SYS 32819 oder mit einem Reset (SYS 64738) initialisiert werden. Dabei wird der Interrupt auf die Adresse $8046 umgeleitet und das Programm vor dem Überschreiben geschützt. Solange der Interruptvektor verändert wird, muß der Interrupt gesperrt werden. Das heißt, daß während dieser Zeit die Interruptroutine nicht angesprungen werden darf. Ein Interrupt muß auch verhindert werden, wenn gerade ein Interruptprogramm stattfindet. Die Katze soll sich schließlich nicht in den eigenen Schwanz beißen. Um einen Interrupt zu sperren, gibt es einen speziellen Assemblerbefehl, den SEI (Set Interrupt Flag). Ist das IRQ-Flag (IRQ, Interrupt Request) gesetzt, läßt sich der 6502-Prozessor nicht mehr durch einen Interrupt unterbrechen, um ein anderes Programm auszuführen. Um den Interruptvektor auf unser Beispielprogramm zu richten, muß in Speicherzelle $0314 das Lowbyte ($46) und in $0315 das Highbyte ($80) unserer Programmadresse ($8046) geschrieben werden. Danach kann das Interruptflag wieder gelöscht werden. Der Assemblerbefehl dazu lautet CLI (Clear Interrupt Flag). Wird nun ein Interrupt ausgelöst (von den CIAs), springt der Prozessor zur Adresse $8046. In unserem Beispiel setzt dort die Abfrage der F1-Taste ein. Ist sie gedrückt oder nicht? Eine Antwort darauf liefert die Adresse $CB in der Zero-Page. Denn der Inhalt von $CB gibt Auskunft darüber, welche Taste zuletzt gedrückt wurde. War es die F1-Taste, steht dort eine »4«.

Den Code einer jeden Taste können Sie mit der folgenden Basic-Anweisung leicht ermitteln:
FOR 1=1 TO 10000 : PRINT PEEK(203) : NEXT I

Drücken Sie danach die gewünschte Taste, erscheint die zugehörige Zahl am Bildschirm.

Das Beispielprogramm reagiert nun folgendermaßen: Ist dieF1-TaSte nicht gedrückt, also der Inhalt der Speicherzelle ungleich 4, wird in Speicherzelle $8009 der Wert $FF geschrieben und die Adresse $EA31 angesprungen. Wie schon erwähnt, steht ab $EA31 die normale IRQ-Routine des C 64. Warum in Speicherzelle $FF geschrieben wird, soll später erklärt werden.

Wurde die F1-Taste gedrückt, wird das Beispielprogramm bearbeitet. Dabei verändert der Bildschirm seine Rahmenfarbe und das Unterprogramm »Directory« ab Adresse $8074 wird abgearbeitet. Ab Adresse $8074 kann jedes beliebige Unterprogramm stehen. Voraussetzung ist nur, daß dieses mit RTS abgeschlossen wird. In diesem Beispiel ist es eben das Zeigen eines Diskettendirectories.

Stellen Sie sich jetzt vor, es würde kein Directory gelesen, sondern irgendeine Funktion aufgerufen, die einen sehr kleinen Zeitbedarf hat. Eventuell eine Einstellung von Bildschirmfarben, wo verfügbare Farben durch Drücken der F1-Taste gezeigt werden. Alle 1/60 Sekunde würde dann die Farbe wechseln. Unmöglich! Deshalb ist in die Tastenabfrage noch ein Trick eingebaut, der das verhindert. Dazu wird nach dem Drücken von F1 ein Flag gesetzt (Speicherzelle $8009 auf $00) und erst dann die Funktion aufgerufen. Ist beim nächsten Interrupt $8009 immer noch »0«, so heißt das, daß die F1-Taste in der Zwischenzeit noch nicht losgelassen wurde. Die Funktion soll dann nicht nochmal ausgeführt, sondern mit der originalen IRQ-Routine fortgefahren werden.

Wäre die F1-Taste in der Zwischenzeit losgelassen worden, hätte in der Speicherzelle $CB eine Änderung stattgefunden. Unsere IRQ-Routine hätte das erkannt und in $8009 den Hex-Wert $FF geschrieben.

Damit die Funktions-Belegung derF1-Taste nicht durch einen Reset zerstört werden kann, wurde das Prinzip der Modulerkennung genutzt. Der Resetvektor zeigt auf die Adresse $800A. Ab dieser Adresse wurde einfach der Anfang des Originalresets nachgebildet. Nachdem der Arbeitsspeicher neu initialisiert und alle Vektoren (auch Interruptvektor) mit ihren Standardwerten belegt wurden, springt »unser« Reset nach $8033 und initialisiert wieder die F1-Taste. Anschließend wird der Prozessor wieder auf seinen gewohnten Weg geschickt ($FCFB).

Unempfindlich gegen Reset

Der NMI-Vektor wurde auf die Adresse $801F »verbogen«. Ab dieser Adresse ist der Original-NMI nachgebaut Bis auf den Sprung an die Adresse $FD15, der weggelassen wurde. Dort wird nämlich die Interruptadresse korrigiert und die F1-Taste abgeschaltet, was die ganzen Anstrengungen zunichte machen würde.

Flußdiagramm zur Initialisierung

Das Beispielprogramm kann natürlich beliebig erweitert oder geändert werden. Es wäre zum Beispiel möglich, die Buchstabentasten über die CTRL-Taste mit einer vierten Funktion zu belegen. Ähnlich SHIFT/CBM. Ein anderer Einsatz wäre der Aufruf einer Hardcopy. Versuchen Sie einfach mal, eine Hardcopyroutine über ????? aufzurufen, statt die Directory-Funktion. Sie müssen dazu nur den JSR $8074-Befehl ab Adresse $806A so ändern, daß die von Ihnen gewählte Routine abgearbeitet wird. Der Befehl lautet dann vielleicht JSR $C000. Haben Sie keine Angst vor Maschinensprache! Experimentieren Sie doch einfach mit diesem Programm. Eine einfache Übung: Stellen Sie mit der F1-Taste die Farben ein.

(Christian Quirin Spitzner/hm)
PROGRAMM : F-TASTE_DIR    8000 80F1
-----------------------------------
8000 : 0A 80 1F 80 C3 C2 CD 38   1C
8008 : 30 FF 8E 16 D0 20 A3 FD   37
8010 : 20 50 FD 20 B7 E4 20 15   29
8018 : FD 20 33 80 4C FB FC 20   DB
8020 : BC F6 20 E1 FF D0 09 20   86
8028 : A3 FD 20 18 E5 6C 02 A0   E0
8030 : 4C 72 FE 78 A2 46 A0 80   64
8038 : 8E 14 03 8C 15 03 58 A9   41
8040 : 80 85 34 85 38 60 A5 CB   F5
8048 : C9 04 F0 08 A9 FF 8D 09   33
8050 : 80 4C 31 EA AD 09 80 C9   59
8058 : FF D0 F6 A9 00 8D 09 80   44
8060 : AD 20 D0 48 AD 21 D0 8D   9D
8068 : 20 D0 20 74 80 68 8D 20   49
8070 : D0 4C 31 EA A9 01 20 C3   BB
8078 : FF A9 24 8D F0 03 A9 30   35
8080 : 8D F1 03 A9 01 A2 08 A0   82
8088 : 00 20 BA FF A9 02 A2 F0   5E
8090 : A0 03 20 BD FF 20 C0 FF   76
8098 : A9 40 20 90 FF A2 01 20   D5
80A0 : C6 FF 20 90 FF 20 CF FF   C0
80A8 : 20 CF FF 20 CF FF 20 CF   D1
80B0 : FF C9 00 F0 31 A9 01 85   21
80B8 : CC A2 01 20 C6 FF 20 CF   A6
80C0 : FF A8 20 CF FF 48 98 AA   0F
80C8 : 68 20 CD BD A9 20 20 D2   2D
80D0 : FF 20 CF FF C9 00 D0 08   C3
80D8 : A9 0D 20 D2 FF B8 50 CB   09
80E0 : 20 D2 FF B8 50 EB A9 01   8D
80E8 : 20 C3 FF 20 CC FF 60 A5   87
80F0 : 61                        A5
Listing zum Directory auf Tastendruck
8000  0a 80                  ; reset-vektor
8002  37 80                  ; nmi-vektor
8004  c3 c2 cd 38 30         ; cbm80 (bewirkt 'modul-start')
8009  ff
-----------------------------  reset
800a  8e 16 d0   stx $d016
800d  20 a3 fd   jsr $fda3
8010  20 50 fd   jsr $fd50   ; arbeitsspeicher initialisieren
8013  20 b7 e4   jsr $e4b7
8016  20 15 fd   jsr $fd15   ; hardware und i/o vektoren setzen/holen
8019  20 33 80   jsr $8033   ; initialisierung der f1-taste
801c  4c fb fc   jmp $fcfb   ; zurueck zum original-reset
-----------------------------  nmi
801f  20 bc f6   jsr $f6bc
8022  20 e1 ff   jsr $ffe1
8025  d0 09      bne $8030   ; nicht gedrueckt, dann $8030
8027  20 a3 fd   jsr $fda3   ; i/o initialisieren
802a  20 18 e5   jsr $e518   ; bildschirm loeschen (i/o initialisieren)
802d  6c 02 a0   jmp ($a002) ; zum basic-warmstart
8030  4c 72 fe   jmp $fe72   ; weiter im original nmi
-----------------------------  initialisierung der f1-taste
8033  78         sei         ; setzen des interrupt-dissable-bit
8034  a2 46      ldx #$46
8036  a0 80      ldy #$80
8038  8e 14 03   stx $0314   ; interrupt auf adresse
803b  8c 15 03   sty $0315   ;   $8046 setzen
803e  58         cli         ; loeschen des interrupt-dissable-bit
803f  a9 80      lda #$80    ; programm vor ueberschreiben sichern
8041  85 34      sta $34     ; zeiger fuer stringspeicher auf $8000
8043  85 38      sta $38     ; zeiger speichergrenze auf $8000
8045  60         rts
-----------------------------  erweiterung der interrupts
8046  a5 cb      lda $cb     ; aktuelle taste holen
8048  c9 04      cmp #$04    ; = f1-taste ?
804a  f0 08      beq $8054   ; ja, dann $8054
804c  a9 ff      lda #$ff    ; flag fuer
804e  8d 09 80   sta $8009   ; f1-taste loeschen
8051  4c 31 ea   jmp $ea31   ; zurueck zum interrupt
8054  ad 09 80   lda $8009   ; f1-taste schon laenger gedrueckt ?
8057  c9 ff      cmp #$ff
8059  d0 f6      bne $8051   ; ja, dann zurueck zum interrupt
805b  a9 00      lda #$00    ; setzt flag
805d  8d 09 80   sta $8009   ; fuer gedrueckte f1-taste
8060  ad 20 d0   lda $d020   ; rahmenfarbe lesen
8063  48         pha         ; farbe auf stapel legen
8064  ad 21 d0   lda $d021   ; hintergrundfarbe lesen
8067  8d 20 d0   sta $d020   ; rahmen angleichen;
806a  20 74 80   jsr $8074   ; beliebige funktion (z.b. directory)
806d  68         pla         ; rahmenfarbe von stapel holen
806e  8d 20 d0   sta $d020   ; rahmenfarbe in den originalzustand
8071  4c 31 ea   jmp $ea31   ; zurueck zum interrupt
-------------------------------directory (hier kann beliebige funktion
                                          ausgefuehrt werden.)
8074  a9 01      lda #$01
8076  20 c3 ff   jsr $ffc3   ; close 1
8079  a9 24      lda #$24
807b  8d f0 03   sta $03f0
807e  a9 30      lda #$30
8080  8d f1 03   sta $03f1
8083  a9 01      lda #$01
8085  a2 08      ldx #$08
8087  a0 00      ldy #$00
8089  20 ba ff   jsr $ffba   ; fileparameter (1,8,0) setzen
808c  a9 02      lda #$02
808e  a2 f0      ldx #$f0
8090  a0 03      ldy #$03
8092  20 bd ff   jsr $ffbd   ; filenamenparameter (2,5,13)
8095  20 c0 ff   jsr $ffc0   ; open
8098  a9 40      lda #$40
809a  20 90 ff   jsr $ff90   ; status setzen  st=64
809d  a2 01      ldx #$01
809f  20 c6 ff   jsr $ffc6   ; eingabegeraet = 1 setzen
80a2  20 90 ff   jsr $ff90   ; status setzen
80a5  20 cf ff   jsr $ffcf   ; basin (zeichen holen)
80a8  20 cf ff   jsr $ffcf   ; basin (zeichen holen)
80ab  20 cf ff   jsr $ffcf   ; basin (zeichen holen)
80ae  20 cf ff   jsr $ffcf   ; basin (zeichen holen)
80b1  c9 00      cmp #$00    ; zeichen = 0 ?
80b3  f0 31      beq $80e6   ; ja, dann ende
80b5  a9 01      lda #$01
80b7  85 cc      sta $cc     ; cursorblinken ausschalten
80b9  a2 01      ldx #$01
80bb  20 c6 ff   jsr $ffc6   ; chkin eingabegeraet = 1 setzen
80be  20 cf ff   jsr $ffcf
80c1  a8         tay         ; akku ins y-register
80c2  20 cf ff   jsr $ffcf   ; basin (zeichen holen)
80c5  48         pha         ; zahl auf stapel legen
80c6  98         tya         ; y->akku
80c7  aa         tax         ; akku->x
80c8  68         pla         ; zahl vom stapel holen
80c9  20 cd bd   jsr $bdcd   ; dezimalzahl ausgeben
80cc  a9 20      lda #$20
80ce  20 d2 ff   jsr $ffd2   ; leerzeichen ausgeben
80d1  20 cf ff   jsr $ffcf   ; basin (zeichen holen)
80d4  c9 00      cmp #$00    ; zeichen = 0 ?
80d6  d0 08      bne $80e0   ; nein, dann zur zeichenausgabe
80d8  a9 0d      lda #$0d
80da  20 d2 ff   jsr $ffd2   ; zeilenvorschub ausgeben
80dd  b8         clv
80de  50 cb      bvc $80ab
80e0  20 d2 ff   jsr $ffd2   ; zeichenausgabe
80e3  b8         clv
80e4  50 eb      bvc $80d1   ; naechstes zeichen holen ($80d1)
80e6  a9 01      lda #$01
80e8  20 c3 ff   jsr $ffc3   ; close 1
80eb  20 cc ff   jsr $ffcc   ; clrch
80ee  60         rts         ; zurueck zum interrupt
Das Quellisting zum Directory auf Tastendruck
PDF Diesen Artikel als PDF herunterladen
Mastodon Diesen Artikel auf Mastodon teilen
← Vorheriger ArtikelNächster Artikel →