In letzter Zeit hatte ich häufig längere Texte zu erstellen und ärgerte mich über die langen Wartezeiten beim Druck.
Zu dieser Zeit stieß ich auf einen Artikel zur Beschleunigung der Druckausgabe [1]. Wichtig in diesem Artikel ist auch die Information, daß es für eine umfassende Lösung nicht genügt, die entsprechende BIOS-Funktion durch die eigene Druckroutine zu ersetzen, sondern daß dies auch für die GEMDOS-Druckfunktion geschehen muß.
Das genügte mir allerdings nicht. Was ich brauchte, war ein schneller Drucker-Spooler, ich kramte in meinen Unterlagen und fand einen weiteren wichtigen Artikel!?]. Der dort beschriebene Spooler vermeidet unnötigen Leerlauf, indem immer dann, wenn der Drucker bereit ist zur Aufnahme des nächsten Bytes, ein entsprechender Interrupt ausgelöst wird, der dem Drucker das nächste Byte aus dem Druckpuffer liefert. Periodische Abfragen, ob der Drucker bereit ist, entfallen also. Die Details der MFP-Programmierung werden eingehend beschrieben; hilfreich für das Verständnis ist auch [3].
Ganz zufrieden war ich aber immer noch nicht. Das angegebene Programm bewirkt, daß ohne Spooler gedruckt wird, wenn die druckende Anwendung die Ausgabe per GEMDOS-Druckfunktion erledigt. Vor allem aber ist die Größe des Druckpuffers fest vorgegeben. Da ich für unterschiedliche Anwendungen unterschiedliche Disketten nutze, hätte ich gerne auf jeder Diskette eine andere, der Anwendung angemessene Größe des Druckpuffers. Einen sehr großen Druckpuffer benötige ich nur bei der Diskette mit der Textverarbeitung und der mit einem Grafikprogramm. Bei den anderen Anwendungsdisketten möchte ich auf den Spooler nicht verzichten, benötige aber nur kleine Puffer. Daß der Spooler als Accessory konzipiert ist, ich - wegen der begrenzten Anzahl der Accessories und aus Gründen der Kompaktheit - jedoch ein Programm im AUTO-Ordner vorziehe, ist hingegen eher Geschmackssache.
Einmal entschlossen, meinen eigenen Drucker-Spooler zu schreiben, wollte ich dann noch einige mir wichtige Eigenschaften realisieren. Mich hat schon immer der 30-Sekunden-Timeout-Wert für die Druckausgabe gestört, der etwa beim Drucken vom Desktop aus voll zuschlägt. Bei Einsatz meines Spoolers gibt es sofort eine Fehlermeldung beim Druck, wenn der Drucker nicht bereit ist, unabhängig davon, wie die Druckausgabe in der druckenden Anwendung realisiert ist. Einen gestarteten Druck wollte ich ferner jederzeit abbrechen können, und zwar auf die natürlichste Weise durch Ausschalten des Druckers (für 10 Sekunden).
Die Größe des Druckpuffers wird über den Programmnamen eingestellt: das Programm heißt SPOOLxxx.PRG, wobei xxx die Größe des Druckpuffers in KB darstellt (immer dreistellig angeben!). Die Entnahme von Betriebsparametern aus dem Programmnamen habe ich [4] entlehnt, wo von dieser Technik reichlich Gebrauch gemacht wird.
Das Programm steht üblicherweise im AUTO-Ordner, und zwar als erstes von allen den Druck beeinflussenden Programmen (da die Druckausgabe per Hardware-Programmierung realisiert ist, wird nicht auf die BIOS- bzw. GEMDOS-Druckroutine zurückgegriffen und damit auch nicht auf andere eingehängte Druckroutinen). Systemvoraussetzung ist TOS 1.2 oder höher.
Programmiert wurde mit Profimat ST, es müßte aber ohne große Anpassungen jeder andere Assembler verwendbar sein. Profimat-spezifisch sind vielleicht die Makros zur Realisierung der wichtigsten Betriebssystemfunktionen im Include-File D:\TOS.Q (TOS.Q steht bei mir beim Assemblieren auf einer RAM-Disk D:). Im Zweifelsfall kann man auf die Makros verzichten und jeden Betriebssystemfunktionsaufruf durch drei Zeilen ersetzen: Funktionsnummer auf den Stack legen, entsprechenden Trap aufrufen, Stack restaurieren.
Im Programm wird zunächst an das Programmende gesprungen zur Installation der neuen BIOS-BCONOUT- und der neuen GEMDOS-Routine. Ferner wird der Interrupt-Vektor für I/O-Port 0 (Drucker-Busy) des MFP 68901 gesetzt. Daneben wird eine Startmeldung auf den Bildschirm ausgegeben. Der Installationsteil dient nach der Installation gleichzeitig als Beginn des als Ringpuffer organisierten Druckpuffers.
Bei der neuen GEMDOS-Routine muß man die feine Unterscheidung machen, ob GEMDOS im User- oder Supervisor-Modus aufgerufen wurde. Je nachdem sind die Verhältnisse auf dem Stack unterschiedlich. Die neue GEMDOS-Routine realisiert die CPRNOUT-Funktion mit Hilfe der neuen BIOS-BCONOUT-Funktion. Die Details sind dem kommentierten Listing entnehmbar, ebenso wie die Einzelheiten der Druckausgabe auf die parallele Schnittstelle (Routine Ausgabe), der neuen BIOS-BCONOUT-Funktion (Routine BconOut) sowie der Busy-IRQ-Routine (Routine Irq).
Literatur:
[1] H. Emmerl: Volldampf für Centronics, ST-Computer 5/92, S. 79ff.
[2] M. Rogge: Von der Spule, c 't 6/90, S. 212 ff.
[3] H.-D. Jankowski, J. F. Reschke, D. Rabich: ATARI ST Profibuch, SYBEX-Verlag GmbH, Düsseldorf
[4] C. Brod, A. Stepper: Scheibenkleister II, Maxon Computer GmbH
; ***********************************************
; * *
; * S P O O L x x x *
; * relozierbar assemblieren! *
; * (c) 1992 MAXON Computer *
; * *
; ***********************************************
include d:\tos.q
mfp equ $fffffa00
gi equ $ffff8800
hz200 equ $4ba
timeout equ 2000 ; Timeout: 10 Sek.
top bra install
fname2 dc.b '\AUTO\'
fname dc.b 'SPOOL*.PRG',0
buflen dc.l $8000 ; Puffergröße
; (Default: 32 KB)
last dc.l 0 ; Zeitpunkt des letzten
; Non-Busy-Irq bzw. warm
; bei einem Druckvorgang
; das 1. Zeichen ausgege-
; ben wird.
lobuf ds.l 1 ; Anf.adresse buffer
hibuf ds.l 1 ; Adresse hinter buffer
outmark ds.l 1 ; Adresse mit nächstem
; auszugebendem Zeichen
inmark ds.l 1 ; nächste freie Adresse
; im buffer
; inmark = outmark :
; buffer leer
; inmark + 1 = outmark :
; buffer voll
Irq movem.l d0/d1/a0,-(sp)
move.l last(pc),d1 ; letzter NonBusy-
move.l hz200,d0 ; Irq älter als
move.l d0,last ; (aktuelle Zeit
sub.l d1,d0 ; nach last
; bringen)
cmp.l #timeout,d0 ; timeout-Wert ?
bcs.s Irq0 ; Nein: Irq0
move.l inmark(pc),outmark
; Puffer löschen
Irq0 move.l outmark(pc),a0 ; Puffer leer ?
cmp.l inmark(pc),a0 ;
beq.s Irq2 ; Ja: Irq2
move.b (a0)+,d0 ; Nächstes Zeichen
cmp.l hibuf(pc),a0 ;
bne.s Irq1 ;
move.l lobuf(pc),a0 ;
Irq1 move.l a0,outmark ; nach d0
bsr.s Ausgabe
Irq2 bclr #0,mfp+$11 ; Busy-Interrupt
; wieder freigeben
; (I/O-Port 0 des Interrupt-In-
; Service-Register
; B des MFP 68901)
movem.l (sp)+,d0/d1/a0
rte
Ausgabe lea gi,a0 ; Reg. 7 des
move.b #7,(a0) ; Soundchip wählen
move.b (a0),d1 ; Reg. 7 nach d1
bset #7,d1 ; Port B des
; Soundchip (Druk-
; kerdaten) auf
move.b d1,2(a0) ; Ausgabe stellen
move.b #15,(a0) ; Reg. 15 (Port B)
; auswählen
move.b d0,2(a0) ; d0 auf Port B
; ausgeben
move.b #14,(a0) ; Reg. 14 (Port A)
; auswählen
move.b (a0),d1 ; Bit 5 des Port A
bclr #5,d1 ; (Strobe)
move.b d1,2(a0) ; Low setzen
bset #5,d1 ; Strobe High
move.b d1,2(a0) ; setzen
; Strobe Low- und
; High-Setzen er-
; gibt, einen
; Strobe-Impuls
rts
dc.b 'XBRA' ; XBRA-
dc.b 'SPLX' ; Kennung
OldBconOut: ; alter
dc.l 1 ; BConOut-Vektor
BconOut:
move.l last(pc),d1 ; letzter NonBusy-
move.l hz200,d0 ; Irq älter als
sub.l d1,d0 ;
cmp.l #timeout,d0 ; timeout-Wert ?
bes.s BconOut1 ; Nein: BconOut1
move.l inmark(pc),a0 ; Puffer leer ?
cmp.l outmark(pc),a0 ;
bne.s BconOut0 ; Nein: BconOut0
move.l $55e,a0 ; Bcostat
jsr (a0) ; für PRT
tst.l d0 ; Drucker nicht
; bereit ?
beg.s BconOut0 ; Ja: BconOut0
move.l hz200,last ; last mit aktuel-
bra.s BconOut2a ; ler Zeit versor-
; gen
BconOut0:
move.l inmark(pc),outmark
; Puffer löschen
moveq #0,d0 ; Fehler zurück-
rts ; melden
BconOut1:
move.l inmark(pc),d0 ; liegt inmark
addq.l #1,d0 ; nur 1 Stelle
cmp.l hibuf(pc),d0 ; hinter outmark,
bne.s BconOut2 ; d.h. ist der
move.l lobuf(pc),d0 ; Puffer
BconOut2:
sub.l outmark(pc),d0 ; voll ?
beq.s BconOut0 ; Ja: BconOut0
BconOut2a:
move.w 6(sp),d0 ; auszugebendes
; Zeichen
move.w sr,d2 ; Statusregister
; nach d2 retten
or.w #$700,sr ; Interrupts
; ausmaskieren
move.l inmark(pc),a1 ; Puffer leer ?
cmp.l outmark(pc),a1 ;
bne.s BconOut3 ; Nein: BconOut3
lea mfp,a0
btst #0,1(a0) ; Drucker busy ?
bne.s BconOut3 ; Ja: BconOut3
btst #0,$d(a0) ; Ist ein
; Busy-Interrupt
; pending ?
bne.s BconOut3 ; Ja: BconOut3
bsr Ausgabe
bra.s BconOut5
BconOut3:
move.b d0,(a1)+ ; Zeichen in den
cmp.l hibuf(pc),a1
bne.s BconOut4
move.l lobuf(pc),a1 ;
BconOut4:
move.l a1,inmark ; Puffer schreiben
BconOut 5:
move.w d2,sr ; Statusregister
; restaurieren,
; insbes. Inter-
; rupts wieder
; zulassen
moveq #-1,d0 ; OK zurückgeben
rts
dc.b 'XBRA' ; XBRA-
dc.b 'SPLX' ; Kennung
OldGemDos: ; alter
dc.l 1 ; GemDos-Vektor
NewGemDos:
move.l usp,a0
move.w (sp),d0 ; Trap-Aufruf im
btst #13,d0 ; Supervisor-Mode?
beq.s NewGemDos1 ; Nein: NewGemDos1
lea 6(sp),a0
NewGemDos1:
cmpi.w #5,(a0)+ ; Funktions-Nr. 5
; (Cprnout) ?
bne.s ExitNewGemDos ; Nein:
; ExitNewGemDos
move.w (a0),-(sp) ; Zeichen auf
; Stack legen
clr.w -(sp) ; Gerät 0 für
; BIOS-Bconout
move.l $57e,a0 ; Aufruf
jsr (a0) ; bconout für PRN
addq.l #4,sp ; Stack-Korrektur
rte
ExitNewGemDos:
jmp $11111111
buffer
Install movea.l 4(sp),a5 ; a5=Basepageadr.
clr.l -(ap)
Super
move.l d0,-(sp) ; alten ssp auf
; Stack retten
bsr.s GetBufSize
pea logo(pc)
Cconws
lea buffer(pc),a0
move.l a0,lobuf
move.l a0,inmark
move.l a0,outmark
adda.l buflen(pc),a0
move.l a0,hibuf
bsr Setvec
Super
pea Irq ; Setzt Interrupt-
; vektor für I/O-
clr.w -(sp) ; Port 0 (Drucker-
; Busy des
Mfpint ; MFP 68901
clr.w -(sp)
move.l #$100+Install-top,d0
add.l buflen(pc),d0
move.l d0,-(sp)
Ptermres
GetBufSize:
clr.w -(sp) ; Prg-Datei suchen
pea fname(pc) ; ->'SPOOL*.PRG’
move.l 36(a5),a5 ; PD-Adresse der
; Eltern
move.l 36(a5),a5 ; PD-Adresse der
; Großeltern
tst.l 36(a5) ; Urgroßeltern
; vorhandenn ?
bne.s GetBufSize1 ; Ja: GetBufSize1
move.l #fname2,(sp) ; Nein: Programm
; stammt aus \AUTO
GetBufSize1:
Fsfirst
tst.w d0 ; Datei gefunden?
bne.s GetBufSize2 ; Nein :
; GetBufSize2
Fgetdta
addi.l #30+5,d0 ; Zeiger auf Da-
; teiname (nach
movea.l d0,a0 ; 'SPOOL') in a0
clr.w d0 ; Puffergröße
bsr.s Digit ; nächste Stelle
bne.s GetBufSize2 ; fehlerhafte An-
; gabe: GetBufSize2
bsr.s Digit ; nächste Stelle
bne.s GetBufSize2 ; fehlerhafte An-
; gabe:GetBufSize2
bsr.s Digit ; nächste Stelle
bne.s GetBufSize2 ; fehlerhafte An-
; gabe:GetBufSize2
mulu #1024,d0
move.l d0,buflen
lea size(pc),a1 ; size im logo
move.b -(a0),-(a1) ;
move.b -(a0),-(a1)
cmpi.b #'0',-(a0)
beq.s GetBufSize2 ; mit Puffergröße
move.b (a0),-(a1) ; laden
GetBufSize2:
rts
Digit move.b (a0)+,d1
cmpi.b #'0',d1
bcs.s Digit1 ; < '0'
cmpi.b #'9',d1
bhi.s Digit1 ; > '9'
andi.b #$0f,d1
mulu #10,d0
add.w d1,d0
moveq #0,d1
rts
Digit1 moveq #1,d1
rts
Setvec move.l $57e,OldBconOut ; BIOS-Bconout
move.l #BconOut,$57e ; umleiten
pea NewGemDos(pc) ; GEMDOS
move.w #33,-(sp)
Setexc ; umleiten
move.l d0,ExitNewGemDos+2
move.l d0,OldGemDos
lea mfp,a0
bclr #0,3(a0) ; Interrupt, wenn
; Busy auf 0 geht!
bclr #0,5(a0) ; I/O-Port Busy-
; Eingang auf
; Eingabe !
rts
logo dc.b $1b,'E',$1b,'p',' Spooler '
dc.b '(C) 26.6.1992 Horst '
dc.b 'Albrecht, Brahmsstr. 25, 4047 '
dc.b 'Dormagen 5 ',$1b,'q',$0d,$0a
dc.b ' - Puffergroße: 32'
size dc.b ' KB',$0d,$0a
dc.b ' - nutzt die Geschwindigkeit des '
dc.b 'Druckers optimal‘,$0d,$0a
dc.b ' - Ausschalten des Druckers für 10 '
dc.b 'Sek. beendet jeden Druckvorgang'
dc.b $0d,$0a
dc.b ' - am Beginn eines Drucks erscheint'
dc.b ' bei ausgeschaltetem Drucker nach’
dc.b $0d,$0a
dc.b ' spätestens 10 Sek. eine Fehler'
dc.b 'meldung unabhängig von der Art der'
dc.b $0d,$0a
dc.b ' Programmierung der Anwendung'
dc.b $0d,$0a
dc.b ' - muß vor jedem anderen Programm '
dc.b 'gestartet werden, welches ebenfalls'
dc.b $0d,$0a
dc.b ' die BIOS-PRN-bconout-Routine '
dc.b 'verändert',$0d,$0a
dc.b ' - Durch Umbenennung des Programms '
dc.b 'in SPOOLxxx.PRG läßt sich der '
dc.b 'Spooler',$0d,$0a
dc.b ' mit frei wählbarer Puffergröße '
dc.b 'verwenden.’,$0d,$0a
dc.b ' xxx ist die Puffergröße in KB '
dc.b 'und muß mit 3 Stellen angegeben '
dc.b 'werden.',$0d,$0a
dc.b ' Bei einer fehlerhaften Angabe '
dc.b 'wird der Puffer mit 32 KB '
dc.b 'eingerichtet.',0
END
; ************************************************
; * *
; * T O S . Q *
; * *
; * Makros für die wichtigsten BIOS-, XBIOS- *
; * und GEMDOS-Funktionen *
; * (c) 1992 MAXON Computer *
; ************************************************
DOTRAP macro %\trap, %\fct
; %\trap: Nr. des Trap
; %\fct: <2-stellig; Fkt>
; <2-stellig: #Bytes für
; Stack-Korrektur nach trap>
move.w #(\fct/$100),-(sp)
trap #\trap
stk@ =\fct-$100*(\fct/$100)
IFNE 0,stk@
IFHI 8,stk@
lea stk@(sp),sp
ELSE
addq.l #stk@,sp
ENDIF
ENDIF
endm
ERR_BRA macro $\label
tst.w d0
blt \label
endm
TSTL_ERR_BRA macro $\label
tst.l d0
blt \label
endm
Rwabs equ DOTRAP 13,$040e
Setexc equ DOTRAP 13,$0508
Getbpb equ DOTRAP 13,$0704
Bcostat equ DOTRAP 13,$0804
Mediach equ DOTRAP 13,$0904
Kbshift equ DOTRAP 13,$0b04
Floprd equ DOTRAP 14,$0814
Flopwr equ DOTRAP 14,$0914
Flopfmt equ DOTRAP 14,$0a1a
Mfpint equ DOTRAP 14,$0d08
Protobt equ DOTRAP 14,$120e
Flopver equ DOTRAP 14,$1314
Cursconf equ DOTRAP 14,$1506
Settime equ DOTRAP 14,$1606
Gettime equ DOTRAP 14,$1702
Kbdvbase equ DOTRAP 14,$2202
Supexec equ DOTRAP 14,$2606
Floprate equ DOTRAP 14,$2906
Pterm0 equ DOTRAP 1,$0000
Cconin equ DOTRAP 1,$0102
Cconout equ DOTRAP 1,$0204
Cprnout equ DOTRAP 1,$0504
Crawcin equ DOTRAP 1,$0702
Cconws equ DOTRAP 1,$0906
Cconrs equ DOTRAP 1,$0a06
Cprnos equ DOTRAP 1,$1102
Dgetdrv equ DOTRAP 1,$1902
Fsetdta equ DOTRAP 1,$1a06
Super equ DOTRAP 1,$2006
Tgetdate equ DOTRAP 1,$2a02
Tsetdate equ DOTRAP 1,$2b04
Tgettime equ DOTRAP 1,$2c02
Tsettime equ DOTRAP 1,$2d04
Fgetdta equ DOTRAP 1,$2f02
Ptermres equ DOTRAP 1,$3100
Dereate equ DOTRAP 1,$3906
Ddelete equ DOTRAP 1,$3a06
Dsetpath equ DOTRAP 1,$3b06
Fcreate equ DOTRAP 1,$3c08
Fopen equ DOTRAP 1,$3d08
Fclose equ DOTRAP 1,$3e04
Fread equ DOTRAP 1,$3f0c
Fwrite equ DOTRAP 1,$400c
Fdelete equ DOTRAP 1,$4106
Fseek equ DOTRAP 1,$420a
Fattrib equ DOTRAP 1,$430a
Fdup equ DOTRAP 1,$4504
Fforce equ DOTRAP 1,$4606
Dgetpath equ DOTRAP 1,$4708
Malloc equ DOTRAP 1,$4806
Mfree equ DOTRAP 1,$4906
Mshrink equ DOTRAP 1,$4a0c
Pexec equ DOTRAP 1,$4b10
Pterm equ DOTRAP 1,$4c00
Fsfirst equ DOTRAP 1,$4e08
Fsnext equ DOTRAP 1,$4f02
Frename equ DOTRAP 1,$560c
Fdatime equ DOTRAP 1,$570a