In der DVM-Sonderheft 3/88" wurde der folgende Artikel abgedruckt.
Ein interessantes Projekt zum Arbeiten mit Interrupts am 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
|
Eingescanned von
Werner Cirsovius
Mai 2003
© DMV-Verlag