Drucker-Spooler: Flexibel & schnell

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

Horst Albrecht
Links

Copyright-Bestimmungen: siehe Über diese Seite