The following article was printed in the special issue 3/88 of the magazine „DVM".
An interesting article about interrupts on the JOYCE.

Immer im Bild

- interruptgesteuerte Anzeigen auf dem JOYCE

Wenn man bestimmte Daten immer im Blickfeld haben will, kommt man um eine interruptgesteuerte Anzeige nicht herum. Im folgenden Artikel werden neben zwei praktischen Erweiterungen für Turbo Pascal auch einige Einsichten in das Innenleben des JOYCE vorgestellt.

Wer kennt sie nicht, diese Anzeigen in irgendeiner Ecke des Bildschirms, die munter vor sich hinlaufen und sich vom übrigen Geschehen auf der Mattscheibe in keiner Weise beeinflussen lassen. Das Zauberwort heißt interruptgesteuerte Anzeigen. Interrupts sind normalerweise regelmäßige Unterbrechungen eines Anwenderprogramms, um auf oder unterhalb der Ebene des Betriebssystems bestimmte Aufgaben durchzuführen, wie beispielsweise Tastatur oder Schnittstellen abzufragen. (Nicht zu verwechseln mit den Software-Interrupts unter MSDOS, die eher 'willkürlichen' Unterprogrammaufrufen entsprechen.). Hier ist von Hardware-Interrupts die Rede, die lebenswichtig für das Funktionieren des Rechners sind.

Sie werden meist mehrere hundertmal in der Sekunde von der Hardware erzeugt und unterbrechen das gerade laufende Programm, um in eine Interrupt Service Routine (ISR) zu springen, in der vitale Aufgaben unabhängig vom Anwenderprogramm erledigt werden. Hängt man nun eigene kleine Programme zusätzlich in diese ISR, werden sie 'praktisch nebenbei' miterledigt. Diese recht einfache Art von 'Multitasking', die bei anderen Betriebssystemen, z.B. DOS PLUS zum Standard gehört, ist recht gut dazu geeignet, bestimmte Zustände des Systems dauernd am Bildschirm anzuzeigen. Sehr beliebt ist hier beispielsweise die Anzeige der Uhrzeit oder der Zustand einer I/O-Adresse. Auch der JOYCE hat nun die Ehre, den Anwender mit solchen Anzeigen zu verwöhnen. Im folgenden werden zwei Include-Files vorgestellt, die unter Turbo Pascal die kontinuierliche Anzeige der Uhrzeit oder einer Portadresse erlauben.

Moderne Zeiten...

TIME.INC stellt alle für die Interruptanzeige der Uhrzeit nötigen Funktionen zur Verfügung. Es sind dies im einzelnen:
procedure Clock_Init:
Hiermit wird der notwendige Maschinenspracheteil für die Anzeige initialisiert, die Anzeige selbst jedoch noch nicht gestartet.

procedure Clock_XY (x,y:byte):
Diese Prozedur ermöglicht ein Verschieben der Anzeige auf dem Bildschirm durch Angabe der Spalte (0 < x < 90) und Zeile (0 < y < 32) in Textkoordinaten bezogen auf den linken oberen Rand. Wird diese Prozedur nicht aufgerufen, erfolgt die Anzeige rechts oben ab (80/0). Wenn der Bildschirm nach dem Einschalten der Anzeige stabil bleibt, kann die Anzeige überall erfolgen. Wird jedoch gescrollt, ist die Verwendung von Zeile 0 empfohlen!

procedure Clock_On:
Schaltet die Anzeige durch Verbiegen der ISR an. Die Anzeige erfolgt im Format 'hh:mm:ss' an der durch Clock_XY() spezifizierten Stelle. Die Anzeige wird jedoch (aus später noch darzustellenden Gründen) nur etwa zweimal pro Sekunde aufgefrischt, beim Scrollen des Bildschirms kann sie also kurz verschwinden. Für die Zeit der aktivierten Anzeige sind andere ISR-Erweiterungen wie beispielsweise Maus-Treiber abgeschaltet!

procedure Clock_Off:
Diese Prozedur schaltet die Anzeige ab und stellt die ursprüngliche ISR wieder her. Dieser Befehl sollte unbedingt vor dem Programmende verwendet werden. Ansonsten bleibt zwar die Anzeige bestehen, irgendwann wird jedoch die ab $F500 stehende ISR-Erweiterung überschrieben, und das System stürzt ab!

procedure Set_Clock (h,m:byte):
Hiermit kann die Uhrzeit unter Verwendung der BDOS Funktion 104 gestellt werden, wobei Stunden und Minuten als Parameter zu übergeben sind. Die vorliegende Form hat jedoch einen kleinen Nachteil: sie überschreibt das Systemdatum, da es für diese Anwendung nicht relevant ist. Falls Sie mit Timestamps auf Ihren Disketten arbeiten, sollten Sie auf Abhilfe sinnen. (Z.B. erst mit BDOS 105 den aktuellen Zustand einlesen und dann nur die Zeit ändern.)

function TimeString:
Liefert die aktuelle Uhrzeit als Sring der Länge 8 (vordefiniert als Strg_8) im Anzeigeformat. Diese Funktion kann zur Übernahme der Zeit in die Anwendung oder zur 'Eventbearbeitung' herangezogen werden. (Beispiel hierfür in der Demo...)

function BCD()
ist eine Hilfsfunktion zur Umwandlung eines Bytes in das zur systeminternen Darstellung der Uhrzeit verwendete BCD-Format.

TIMEDEMO.PAS
ist eine kleine Demonstration der Befehlserweiterung, mit der einige Uhrzeiten zur Anzeige gebracht werden. (Beachten Sie die Reihenfolge der Befehle bei der Initialisierung.) Abschließend erfolgt eine Demonstration des 'Multitaskings': es laufen zwei Anzeigen gleichzeitig. Einmal die Uhrzeit, gleichzeitig auch eine Zählschleife. Diese Zählung beginnt um 23:59:00 und endet um 00:00:00, was durch die Abfrage von TimeString erreicht wird. Bei der Verwendung von TimeString ist zu beachten, daß zwischen der Initialisierung durch Clock_On und der erstmaligen Zuweisung an eine Variable eine gewisse Zeit vergehen muß, bis der String einen gültigen Wert hat. Erreicht wird dies in der Demo durch 'delay(200)'.

Anzumerken ist noch, daß Sie bei der Verwendung von TIME.INC die Compilerendadresse niemals größer als $F4FF setzen dürfen (der Standardwert ist sowieso kleiner), da sonst die ISR-Erweiterung überschrieben wird und das System abstürzt. Aus diesem Grund darf auch der Run-Modus nicht verwendet werden, sondern es muß auf Diskette compiliert werden. (Hier kann sich für die Programmentwicklung als nützlich erweisen, ein Include-File mit Dummy-Prozeduren zu erstellen, die nur aus Prozedurkopf und leeren 'begin end;'-Anweisungen bestehen und Testläufe erst mit diesen Files durchzuführen, um Fehler in anderen Programmteilen zu lokalisieren.) Bemerkenswert ist auch noch, daß die Anzeige alle paar Sekunden ins Stocken gerät. Der Grund hierfür konnte bislang noch nicht herausgefunden werden, das Vergnügen an der neuen Anzeige dürfte dadurch aber auch nicht allzusehr getrübt werden.

In den System-Topf geguckt

Ähnlich aufgebaut ist PORTVIEW.INC, nur daß hier nicht die Uhrzeit, sondern der Zustand einer Portadresse zur binären Anzeige gebracht wird. Diese Verwendung wird jedoch erst in Verbindung mit Hardwareerweiterungen interessant, da der JOYCE ansonsten relativ wenig 'Anschauliches' zu bieten hat. Zu den Funktionen:

Display_Init, Display_On, Display_Off entsprechen den Prozeduren Clock_* in TIME.INC.
procedure Set_Parameters (x,y,port:byte):

Hiermit werden die Anzeigekoordinaten und der anzuzeigende Port ausgewählt. Defaultwerte sind hier (80,0,$FD), was den Druckerstatus rechts oben zur Anzeige bringt. Die Anzeige erfolgt binär mit '0' und '1' für den Zustand des jeweiligen Bits, das niederwertigste Bit steht rechts. Neu sind Slow_Display und Quick_Display, mit denen die Anzeigeräte eingestellt werden kann. Normalerweise erfolgt die Auffrischung der Anzeige zweimal pro Sekunde, was für sich schnell verändernde Zustände jedoch zu langsam ist.

Wird Quick_Display aufgerufen, steigt die Auffrischungsrate auf mehrere hundertmal pro Sekunde. Dies verlangsamt jedoch den Rechner erheblich und bringt das interne Timing durcheinander, was bei Zugriffen auf Diskette zum Systemabsturz führt. Daher sollte vor Diskettenzugriff auf alle Fälle mit Slow_Display die Anzeigerate verlangsamt werden.

Das Programm PORTDEMO.PAS zeigt die Verwendung der neuen Befehle und stellt den Druckerstatus und den Baudratezähler der seriellen Schnittstelle dar. (Experimentieren Sie mal mit verschiedenen Baud-Raten und beobachten den Zähler dabei - im Quick_Display-Modus...)
Übrigens können Sie nicht sowohl die Uhrzeit als auch einen Port gleichzeitig anzeigen, da sie den gleichen Speicherbereich verwenden. Sie können jedoch beide Erweiterungen einbinden und bei Bedarf durch Neuinitialisierung umschalten. Mit der in JOYCE Sonderheft 2 vorgestellten Grafikerweiterung arbeiten beide Teile jedoch problemlos zusammen.

Hinter den Kulissen

Schauen wir uns nun die beiden Assemblerlistings TIMEINC.MAC und PORTVIEW.MAC an, die den MCode der External-Routinen enthalten. Sie sind bei beiden Erweiterungen gleich aufgebaut und bestehen aus folgenden Teilen:

1. Den Teilen zum Verbiegen des Interruptvektors, über den die neue Erweiterung in die bestehende ISR ein- und wieder ausgebunden wird.

2. Dem Kopf der ISR-Erweiterung (-Neue Routine-).
In ihm werden die Register gerettet und geprüft, ob ein Anzeigenupdate an der Zeit ist. Erreicht wird dies durch einen Zähler, der bei jedem Interrupt erhöht wird. Ist der Zähler 0 (alle 255 Interrupts), wird ein Update vorgenommen, andernfalls wird die ISR-Erweiterung verlassen (JP NZ,exit), die Register wiederhergestellt und die Standard-ISR angesprungen.

Der Zähler ist notwendig, da zur eigentlichen Anzeige relativ viel Rechenzeit benötigt wird, die natürlich von der Anwenderzeit abgeht und das interne Timing stört. Dies macht sich im Absturz des Floppy-Controllers bemerkbar, der sich abmeldet, wenn er nach einer gewissen Zeit keine Rückmeldung von der CPU bekommt. (Quick_Display aus PORTVIEW.INC greift an dieser Stelle ein und setzt statt JP NZ ein JP Z,exit was dazu führt, daß bei jedem Interrupt ein Update erfolgt, nur nicht beim Nulldurchgang des Zählers.)

Ist ein Update nötig, so wird die aktuelle Memory Map gerettet, der Bildschirmspeicher eingeblendet und die Anzeigeroutine aufgerufen. Nach der Anzeige werden Memory Map und Register wieder hergestellt und die Standard-ISR aufgerufen.

3. Der eigentlichen Anzeigeroutine. Sie basiert im wesentlichen auf Matthias Uphoffs SuperScript aus PC International 6/87 und schreibt die anzuzeigenden Werte direkt in den Bildschirmspeicher, wobei sich bei den einzelnen Anwendungen kleine Unterschiede ergeben. In TIMEINC.MAC wird die BCD-Uhrzeit aus dem System Control Block (SCB) ab $FBF6 ausgelesen und in einen ASCII-String der Form 'hh:mm:ss:' gebracht.

Der letzte Doppelpunkt wird nicht angezeigt, wegen der einfacheren Schleifenprogrammierung jedoch miterzeugt. Dieser String wird nun Zeichen für Zeichen unter Verwendung der Originalmatrix in den Bildschirmspeicher kopiert. (Details hierzu im o.g. Beitrag von M. Uphoff.)

Bei der Portanzeige entfällt die Berechnung der Adresse des Zeichens im Matrix-RAM, da für die Darstellung Sonderzeichen aus einer eigenen Matrix (ONMAT, OFFMAT) verwendet werden. Je nach Zustand des auszugebenden Bits wird die entsprechende Matrix in den Bildschirmspeicher kopiert.

Interna

Soweit um programmiertechnischen Hintergrund der Anzeige, jetzt noch einige Einblicke in das Innenleben des JOYCE, die für das Thema interessant und maßgeblich sind. Da wären zunächst einmal die Interrupts. Der Z80 Prozessor des JOYCE wird im Interruptmode 1 betrieben, das heißt, die Interruptsignale, die mehrere hundertmal in der Sekunde vom Gate Array erzeugt werden, führen zu einem RST 38. An dieser Stelle steht in der TPA ein Sprung nach $FDA1. Dort wird auf die Systembank umgeschaltet und ein Unterprogramm bei $FDCB aufgerufen.

Dies initialisiert einen RST 38-Vektor in der Systembank, wobei ein in $FEA7 enthaltener Wert als Sprungadresse verwendet wird. Hier steht also praktisch der Zeiger auf der ISR.

Wird dieser Zeiger auf eine andere Routine verbogen und am Ende dieser Routine der alte Wert angesprungen, hängt die neue Routine mit in der ISR, so wie bei den Anzeigen geschehen. (Das Listing 'Wege zum Interrupt' zeigt nochmals die maßgeblichen Stellen im Betriebssystem in disassemblierter Form.)

Listing: Wege zum Interrupt
---------------------------


DDT/Z ÄH&TÜ

> l 38
0038  JP  FDA1        <- RST 38-Vektor in TPA

>>l fda1
FDA1  LD   (FEA3),SP  <- ISR Einsprung in COMMON
FDA5  LD   SP,FF4A
FDA8  PUSH AF
FDA9  LD   A,80
FDAB  OUT  (F0),A     <- Block 0 einblenden
FDAD  CALL FDCB       <- Vektor initialisieren
FDB0  LD   A,84
FDB2  OUT  (F0),A     <- TPA wiederherstellen
FDB4  POP  AF
FDB5  LD   SP,(FEA3)
FDB9  EI
FDBA  RET             <- ISR Ende in COMMON

>>l fdcb
FDCB  PUSH HL
FDCC  LD   HL,(FEA7)  <- RST 38-Vektor in Block 0
FDCF  LD   (0039),HL  <- initialisieren
FDD2  RST  38         <- und anspringen

>>d fea7
FEA7  40 1E 00 00     <- Standard ISR-Vektor
Abb. 2: Listing: Wege zum Interrupt

Der Normalwert des Zeigers ist für CP/M v1.4 $1E40, er kann je nach Systemerweiterungen verändert sein, ein Maustreiber würde ihn beispielsweise auf seine eigene Routine verbiegen. Um Kollisionen mit solchen Erweiterungen zu vermeiden, wurde der Ur-Vektor $1E40 zum Ansprung der ISR verwendet, der tatsächliche Zeiger wird in OLDINT zwischengespeichert und erst beim Ausschalten der Anzeige wieder hergestellt. Im Prinzip ist es möglich, den abschließenden JP 1E40 durch einen Sprung auf den Vektor in OLDINT zu ersetzen, dies kann allerdings zu Komplikationen führen.

Weiterhin ist bei der Programmierung eigener ISR-Teile folgendes zu beachten: Register, die sich in der Erweiterung verändern, müssen gerettet und wieder hergestellt werden. Das gleiche gilt für die Memory Map. Sie besteht zum Beginn der ISR aus den Blöcken 0,5,6,7 (das heißt, die ersten 16 KByte der TPA wurden durch die ersten 16 KByte der Systembank ersetzt. Diese Belegung ist, sofern sie verändert wird, wieder zu restaurieren.

Die Standard-ISR wird noch einige weitere Änderungen vornehmen, bringt diese jedoch selbständig wieder in Ordnung. Die Standard BDOS-, BIOS- und XBIOS-Aufrufe dürfen nicht (!) in den ISR-Erweiterungen verwendet werden!

Noch einige Worte zur Memory Map und zum Bankswitching, da inzwischen genauere Informationen vorliegen und es im Zusammenhang mit dem XBIOS-Artikel im vorletzten Sonderheft zu einigen Mißverständnissen gekommen ist.

Es muß unterschieden werden zwischen 'Bank' und 'Block'. Eine Bank besteht aus 64 KByte, die in Blöcke zu 16 KByte unterteilt sind. Die Systembank (CP/M Bank 0) besteht daher aus den Blöcken 0,1,3,7, die TPA (CP/M Bank 1) wird durch die Blöcke 4,5,6,7 gebildet. Je nach Bedarf können in diese Bänke andere Speicherbereiche (z.B. Screen Environment, RAM-Disk) blockweise eingeblendet werden. Daraus ergeben sich zwar auch wieder Bänke, die jedoch keine offiziellen CP/M-Bänke mehr sind.

Hier nochmal die Regel: 1 Bank = 64 KByte = 4 Blöcke zu 16 KByte.

Offizielle CP/M-Bänke: 0:Systembank = Blocks 0,1,3,7; 1:TPA = Blocks 4,5,6,7.

Das Bank- (oder besser Block-) Switching erfolgt auf dem JOYCE über die I/O-Adressen $F0-$F3, wobei jede Adresse einen 16 K-Block in den Adreßraum der CPU einblendet. $F0 ist für $0000-$3FFF zuständig, $F1 für $4000-$7FFF, $F2 für $8000-$BFFF, $F3 für $C000-$FFFF.

Um nun einen Block in einen bestimmten Adreßraum einzublenden, muß an den entsprechenden Port die Blocknummer + $80 ausgegeben werden. Um z.B. den ersten Block der RAM-Disk (Block Nr. 9) in die TPA ab $8000 einzublenden, muß folgendes ausgeführt werden: LD A,89 und OUT (F2),A. Das Listing 'RAM-Disk Analyzer' (Abb. 1) zeigt zur Veranschaulichung eine Sitzung mit DDTZ, in der ein Programm zum Kopieren eines Teils der RAM Disk in die TPA erstellt und gestartet wird, nachdem das Programm als TEST.RAM auf der RAM Disk gespeichert wurde.

Listing: RAM-Disk Analyzer
--------------------------

DDT/Z ÄH&TÜ

> l 100
0100  DI
0101  LD   A,89
0103  OUT  (F2),A
0105  LD   BC,4000
0108  LD   DE,4000
010B  LD   HL,8000
010E  LDIR
0110  LD   A,80
0112  OUT  (F2),A
0114  EI

>>b 115
> b
  0115  :0001

>>fm:test.ram
> w 100 115

> g 100
      E  A =86 BC =0000 DE =8000 HL =C000 SP=CC00 PC=0115      NOP
         A'=00 BC'=0000 DE'=0000 HL'=0000 IX=0000 IY=0000 I=00

> d 4000
4000   89 E5 E5 E5  E5 E5 E5 E5  E5 E5 E5 E5  E5 E5 E5 E5  .eeeeeeeeeeeeeee
4010   E5 E5 E5 E5  E5 E5 E5 E5  E5 E5 E5 E5  E5 E5 EE E5  eeeeeeeeeeeeeeee
4020   00 54 45 53  54 20 20 20  20 52 41 4D  00 00 00 01  .TEST    RAM....
4030   02 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
4040   E5 E5 E5 E5  ES E5 E5 E5  E5 E5 E5 E5  E5 E5 E5 B5  eeeeeeeeeeeeeeee

>>d 4800
4800   F3 3E 89 D3  F2 01 00 40  11 00 40 21  00 80 ED B0  s>.Sr..§..§!..m0
4810   3E 86 D3 F2  FB 00 00 00  00 00 00 00  00 00 00 00  >.Srä...........
4820   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................

>>l 4800
4800  DI
4801  LD   A,89
4803  OUT  (F2),A
4805  LD   BC,4000
4608  LD   DE,4000
480B  LD   HL,8000
480E  LDIR
4810  LD   A,86
4812  OUT  (F2),A
4814  EI
Abb. 1: Listing des RAM-Disk-Analyzers

Schauen wir uns nun den Speicher an, finden wir ab $4000 den Directory-Eintrag für das File, ab $4800 das gespeicherte Programm. (Auf diese Weise läßt sich auch die Systembank recht gut untersuchen, allerdings muß die Adreßverschiebung berücksichtigt werden.)

Soweit für dieses Sonderheft genug an JOYCE-Interna, ich bin sicher, daß in Sachen Interrupts das letzte Wort noch nicht gesprochen ist. (So wäre es z.B. schön, wenn man die Tastatur interruptgesteuert abfangen könnte und..und..und.) Aber vielleicht gibt's im nächsten Sonderheft etwas Genaueres - oder zumindest die Antworten auf noch offen gebliebene Fragen...
(Michael Anton/me)
Hinweise zur Anwendung:
Die Programme TIMEDEMO und PORTDEMO liegen als Assembler-und Turbo Pascal-Listings vor. [TIMEDEMO.PAS, TIME.INC, PORTDEMO.PAS, PORTVIEW.INC]

Für das Pascal-Listing benötigen Sie das Programmpaket Turbo PASCAL von Borland/Heimsoeth! Jedoch ist das Abtippen nur sinnvoll, wenn Erfahrungen mit diesen Sprachen vorhanden sind - für Einsteiger nicht empfehlenswert! Nur-Anwender finden das lauffertige Programm TIMEDEMO.COM bzw. PORTDEMO.COM auf der DATABOX-Diskette zu diesem Heft. [Hier als ZIP-Dateien]

Aufruf unter CP/M z.B.:

A>TIMEDEMO <RETURN>

Scanned by Werner Cirsovius
May 2003
© DMV-Verlag