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
Aus: ST-Computer 02 / 1993, Seite 75

Links

Copyright-Bestimmungen: siehe Über diese Seite