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