The Art of
ASSEMBLY LANGUAGE PROGRAMMING

Chapter Twenty One (Part 1)

Table of Content

Chapter Twenty One (Part 3) 

CHAPTER TWENTY ONE:
THE PC PARALLEL PORTS (Part 2)
21.3 - Controlling a Printer Through the Parallel Port
21.3.1 - Printing via DOS
21.3.2 - Printing via BIOS
21.3.3 - An INT 17h Interrupt Service Routine
21.3 Controlling a Printer Through the Parallel Port

Although there are many devices that connect to the PC's parallel port, printers still make up the vast number of such connections. Therefore, describing how to control a printer from the PC's parallel port is probably the best first example to present. As with the keyboard, your software can operate at three different levels: it can print data using DOS, using BIOS, or by writing directly to the parallel port hardware. As with the keyboard interface, using DOS or BIOS is the best approach if you want to maintain compatibility with other devices that plug into the parallel port[2]. Of course, if you are controlling some other type of device, going directly to the hardware is your only choice. However, the BIOS provides good printer support, so going directly to the hardware is rarely necessary if you simply want to send data to the printer.

21.3.1 Printing via DOS

MS-DOS provides two calls you can use to send data to the printer. DOS function 05h writes the character in the dl register directly to the printer. Function 40h, with a file handle of 04h, also sends data to the printer. Since the chapter on DOS and BIOS fully describes these functions, we will not discuss them any further here.

21.3.2 Printing via BIOS

Although DOS provides a reasonable set of functions to send characters to the printer, it does not provide functions to let you initialize the printer or obtain the current printer status. Furthermore, DOS only prints to LPT1:. The PC's int 17h BIOS routine provides three functions, print, initialize, and status. You can apply these functions to any supported parallel port on the system. The print function is roughly equivalent to DOS' print character function. The initialize function initializes the printer using system dependent timing information. The printer status returns the information from the printer status port along with time-out information.

21.3.3 An INT 17h Interrupt Service Routine

Perhaps the best way to see how the BIOS functions operate is to write a replacement int 17h ISR for a printer. This section explains the handshaking protocol and variables the printer driver uses. It also describes the operation and return results associated with each machine.

There are eight variables in the BIOS variable space (segment 40h) the printer driver uses. The following table describes each of these variables:

BIOS Parallel Port Variables
Address Description
40:08 Base address of LPT1: device.
40:0A Base address of LPT2: device.
40:0C Base address of LPT3: device.
40:0E Base address of LPT4: device.
40:78 LPT1: time-out value. The printer port driver software should return an error if the printer device does not respond in a reasonable amount of time. This variable (if non-zero) determines how many loops of 65,536 iterations each a driver will wait for a printer acknowledge. If zero, the driver will wait forever.
40:79 LPT2: time-out value. See description above.
40:7A LPT3: time-out value. See description above.
40:7B LPT4: time-out value. See description above.

You will notice a slight deviation in the handshake protocol in the following code. This printer driver does not wait for an acknowledge from the printer after sending a character. Instead, it checks to see if the printer has sent an acknowledge to the previous character before sending a character. This saves a small amount of time because the program printer then characters can continue to operating in parallel with the receipt of the acknowledge from the printer. You will also notice that this particular driver does not monitor the busy lines. Almost every printer in existence leaves this line inactive (not busy), so there is no need to check it. If you encounter a printer than does manipulate the busy line, the modification to this code is trivial. The following code implements the int 17h service:

; INT17.ASM
;
; A short passive TSR that replaces the BIOS' int 17h handler.
; This routine demonstrates the function of each of the int 17h
; functions that a standard BIOS would provide.
;
; Note that this code does not patch into int 2Fh (multiplex interrupt)
; nor can you remove this code from memory except by rebooting.
; If you want to be able to do these two things (as well as check for
; a previous installation), see the chapter on resident programs.  Such
; code was omitted from this program because of length constraints.
;
;
; cseg and EndResident must occur before the standard library segments!

cseg            segment para public 'code'
cseg            ends

; Marker segment, to find the end of the resident section.

EndResident     segment para public 'Resident'
EndResident     ends

                .xlist
                include         stdlib.a
                includelib      stdlib.lib
                .list


byp             equ     <byte ptr>

cseg            segment para public 'code'
                assume  cs:cseg, ds:cseg

OldInt17        dword   ?


; BIOS variables:

PrtrBase        equ     8
PrtrTimeOut     equ     78h




; This code handles the INT 17H operation.  INT 17H is the BIOS routine
; to send data to the printer and report on the printer's status.  There
; are three different calls to this routine, depending on the contents
; of the AH register.  The DX register contains the printer port number.
;
; DX=0 -- Use LPT1:
; DX=1 -- Use LPT2:
; DX=2 -- Use LPT3:
; DX=3 -- Use LPT4:
;
; AH=0 -- Print the character in AL to the printer.  Printer status is
;         returned in AH.  If bit #0 = 1 then a timeout error occurred.
;
; AH=1 -- Initialize printer.  Status is returned in AH.
;
; AH=2 -- Return printer status in AH.
;
;
; The status bits returned in AH are as follows:
;
;  Bit    Function                      Non-error values
;  ---   --------------------------     ----------------
;   0     1=time out error                      0
;   1       unused                              x
;   2       unused                              x
;   3     1=I/O error                           0
;   4     1=selected, 0=deselected.             1
;   5     1=out of paper                        0
;   6     1=acknowledge                         x
;   7     1=not busy                            x
;
; Note that the hardware returns bit 3 with zero if an error has occurred,
; with one if there is no error.  The software normally inverts this bit
; before returning it to the caller.
;
;
; Printer port hardware locations:
;
; There are three ports used by the printer hardware:
;
; PrtrPortAdrs   ---  Output port where data is sent to printer (8 bits).
; PrtrPortAdrs+1 ---  Input port where printer status can be read (8 bits).
; PrtrPortAdrs+2 ---  Output port where control information is sent to the
;                     printer.
;
; Data output port- 8-bit data is transmitted to the printer via this port.
;
; Input status port:
;                       bit 0: unused.
;                       bit 1: unused.
;                       bit 2: unused.
;
;                       bit 3: -Error, normally this bit means that the
;                               printer has encountered an error.  However,
;                               with the P101 installed this is a data
;                               return line for the keyboard scan.
;
;                       bit 4: +SLCT, normally this bit is used to determine
;                               if the printer is selected or not.  With the
;                               P101 installed this is a data return
;                               line for the keyboard scan.
;
;                       bit 5: +PE, a 1 in this bit location means that the
;                               printer has detected the end of paper.  On
;                               many printer ports, this bit has been found
;                               to be inoperative.
;
;                       bit 6: -ACK, A zero in this bit position means that
;                               the printer has accepted the last character
;                               and is ready to accept another.  This bit
;                               is not normally used by the BIOS as bit 7
;                               also provides this function (and more).
;
;                       bit 7: -Busy, When this signal is active (0) the
;                               printer is busy and cannot accept data.
;                               When this bit is set to one, the printer
;                               can accept another character.
;
;
;
; Output control port:
;
;                       Bit 0: +Strobe, A 0.5 us (minimum) active high pulse
;                               on this bit clocks the data latched into the
;                               printer data output port to the printer.
;
;                       Bit 1: +Auto FD XT - A 1 stored at this bit causes
;                               the printer to line feed after a line is
;                               printed.  On some printer interfaces (e.g.,
;                               the Hercules Graphics Card) this bit is
;                               inoperative.
;
;                       Bit 2: -INIT, a zero on this bit (for a minimum of
;                               50 us) will cause the printer to (re)init-
;                               ialize itself.
;
;                       Bit 3: +SLCT IN, a one in this bit selects the
;                               printer.  A zero will cause the printer to
;                               go off-line.
;
;                       Bit 4: +IRQ ENABLE, a one in this bit position
;                               allows an interrupt to occur when -ACK
;                               changes from one to zero.
;
;                       Bit 5: Direction control on BI-DIR port. 0=output,
;                              1=input.
;                       Bit 6: reserved, must be zero.
;                       Bit 7: reserved, must be zero.

MyInt17         proc    far
                assume  ds:nothing

                push    ds
                push    bx
                push    cx
                push    dx

                mov     bx, 40h                 ;Point DS at BIOS vars.
                mov     ds, bx

                cmp     dx, 3                   ;Must be LPT1..LPT4.
                ja      InvalidPrtr

                cmp     ah, 0           ;Branch to the appropriate code for
                jz      PrtChar         ; the printer function
                cmp     ah, 2
                jb      PrtrInit
                je      PrtrStatus

; If they passed us an opcode we don't know about, just return.

InvalidPrtr:    jmp     ISR17Done



; Initialize the printer by pulsing the init line for at least 50 us.
; The delay loop below will delay well beyond 50 usec even on the fastest
; machines.

PrtrInit:       mov     bx, dx                  ;Get printer port value.
                shl     bx, 1                   ;Convert to byte index.
                mov     dx, PrtrBase[bx]        ;Get printer base address.
                test    dx, dx                  ;Does this printer exist?
                je      InvalidPrtr             ;Quit if no such printer.
                add     dx, 2                   ;Point dx at control reg.
                in      al, dx                  ;Read current status.
                and     al, 11011011b           ;Clear INIT/BIDIR bits.
                out     dx, al                  ;Reset printer.
                mov     cx, 0                   ;This will produce at least
PIDelay:        loop    PIDelay                 ; a 50 usec delay.
                or      al, 100b                ;Stop resetting printer.
                out     dx, al
                jmp     ISR17Done


; Return the current printer status.  This code reads the printer status
; port and formats the bits for return to the calling code.

PrtrStatus:     mov     bx, dx                  ;Get printer port value.
                shl     bx, 1                   ;Convert to byte index.
                mov     dx, PrtrBase[bx]        ;Base address of printer port.
                mov     al, 00101001b           ;Dflt: every possible error.
                test    dx, dx                  ;Does this printer exist?
                je      InvalidPrtr             ;Quit if no such printer.
                inc     dx                      ;Point at status port.
                in      al, dx                  ;Read status port.
                and     al, 11111000b           ;Clear unused/timeout bits.
                jmp     ISR17Done



; Print the character in the accumulator!

PrtChar:        mov     bx, dx
                mov     cl, PrtrTimeOut[bx]     ;Get time out value.
                shl     bx, 1                   ;Convert to byte index.
                mov     dx, PrtrBase[bx]        ;Get Printer port address
                or      dx, dx                  ;Non-nil pointer?
                jz      NoPrtr2                 ; Branch if a nil ptr

; The following code checks to see if an acknowlege was received from
; the printer.  If this code waits too long, a time-out error is returned.
; Acknowlege is supplied in bit #7 of the printer status port (which is
; the next address after the printer data port).

                push    ax
                inc     dx                      ;Point at status port
                mov     bl, cl                  ;Put timeout value in bl
                mov     bh, cl                  ; and bh.
WaitLp1:        xor     cx, cx                  ;Init count to 65536.
WaitLp2:        in      al, dx                  ;Read status port
                mov     ah, al                  ;Save status for now.
                test    al, 80h                 ;Printer acknowledge?
                jnz     GotAck                  ;Branch if acknowledge.
                loop    WaitLp2                 ;Repeat 65536 times.
                dec     bl                      ;Decrement time out value.
                jnz     WaitLp1                 ;Repeat 65536*TimeOut times.

; See if the user has selected no timeout:

                cmp     bh, 0
                je      WaitLp1

; TIMEOUT ERROR HAS OCCURRED!
;
; A timeout - I/O error is returned to the system at this point.
; Either we fall through to this point from above (time out error) or
; the referenced printer port doesn't exist.  In any case, return an error.

NoPrtr2:        or      ah, 9                   ;Set timeout-I/O error flags
                and     ah, 0F9h                ;Turn off unused flags.
                xor     ah, 40h                 ;Flip busy bit.

; Okay, restore registers and return to caller.

                pop     cx                      ;Remove old ax.
                mov     al, cl                  ;Restore old al.
                jmp     ISR17Done


; If the printer port exists and we've received an acknowlege, then it's
; okay to transmit data to the printer.  That job is handled down here.

GotAck:         mov     cx, 16                  ;Short delay if crazy prtr
GALp:           loop    GALp                    ; needs hold time after ack.
                pop     ax                      ;Get char to output and
                push    ax                      ; save again.
                dec     dx                      ;Point DX at printer port.
                pushf                           ;Turn off interrupts for now.
                cli
                out     dx, al                  ;Output data to the printer.

; The following short delay gives the data time to travel through the
; parallel lines.  This makes sure the data arrives at the printer before
; the strobe (the times can vary depending upon the capacitance of the
; parallel cable's lines).

                mov     cx, 16                  ;Give data time to settle
DataSettleLp:   loop    DataSettleLp            ; before sending strobe.

; Now that the data has been latched on the printer data output port, a
; strobe must be sent to the printer.  The strobe line is connected to
; bit zero of the printer port.  Also note that this clears bit 5 of the
; control port.  This ensures that the port continues to operate as an
; output port if it is a bidirectional device.  This code also clears bits
; six and seven which IBM claims should be left zero.

                inc     dx                      ;Point DX at the printer
                inc     dx                      ; control output port.
                in      al, dx                  ;Get current control bits.
                and     al, 01eh                ;Force strobe line to zero and
                out     dx, al                  ; make sure it's an output port.


                mov     cx, 16                  ;Short delay to allow data
Delay0:         loop    Delay0                  ; to become good.

                or      al, 1                   ;Send out the (+) strobe.
                out     dx, al                  ;Output (+) strobe to bit 0

                mov     cx, 16                  ;Short delay to lengthen strobe
StrobeDelay:    loop    StrobeDelay

                and     al, 0FEh                ;Clear the strobe bit.
                out     dx, al                  ;Output to control port.
                popf                            ;Restore interrupts.

                pop     dx                      ;Get old AX value
                mov     al, dl                  ;Restore old AL value

ISR17Done:      pop     dx
                pop     cx
                pop     bx
                pop     ds
                iret
MyInt17         endp





Main            proc

                mov     ax, cseg
                mov     ds, ax

                print
                byte    "INT 17h Replacement",cr,lf
                byte    "Installing....",cr,lf,0

; Patch into the INT 17 interrupt vector.  Note that the
; statements above have made cseg the current data segment,
; so we can store the old INT 17 value directly into
; the OldInt17 variable.

                cli                             ;Turn off interrupts!
                mov     ax, 0
                mov     es, ax
                mov     ax, es:[17h*4]
                mov     word ptr OldInt17, ax
                mov     ax, es:[17h*4 + 2]
                mov     word ptr OldInt17+2, ax
                mov     es:[17h*4], offset MyInt17
                mov     es:[17h*4+2], cs
                sti                             ;Okay, ints back on.


; We're hooked up, the only thing that remains is to terminate and
; stay resident.

                print
                byte    "Installed.",cr,lf,0

                mov     ah, 62h                 ;Get this program's PSP
                int     21h                     ; value.

                mov     dx, EndResident         ;Compute size of program.
                sub     dx, bx
                mov     ax, 3100h               ;DOS TSR command.
                int     21h
Main            endp
cseg            ends

sseg            segment para stack 'stack'
stk             byte    1024 dup ("stack   ")
sseg            ends

zzzzzzseg       segment para public 'zzzzzz'
LastBytes       byte    16 dup (?)
zzzzzzseg       ends
                end     Main

[2] Many devices connect to the parallel port with a pass-through plug allowing you to use that device and still use the parallel port for your printer. However, if you talk directly to the parallel port with your software, it may conflict with that device's operation.

Chapter Twenty One (Part 1)

Table of Content

Chapter Twenty One (Part 3) 

Chapter Twenty Two: The PC Parralel Ports (Part 2)
30 SEP 1996