The Art of
ASSEMBLY LANGUAGE PROGRAMMING

Chapter Twenty One (Part 2)

Table of Content

Chapter Twenty Two  

CHAPTER TWENTY ONE:
THE PC PARALLEL PORTS (Part 3)
21.4 - Inter-Computer Communications on the Parallel Port
21.4 Inter-Computer Communications on the Parallel Port

Although printing is by far the most popular use for the parallel port on a PC many devices use the parallel port for other purposes as mentioned earlier. It would not be fitting to close this chapter without at least one example of a non-printer application for the parallel port. This section will describe how to get two computers to transmit files from one to the other across the parallel port.

The Laplink' program from Travelling Software is a good example of a commercial product that can transfer data across the PC's parallel port; although the following software is not as robust or feature laden as Laplink it does demonstrate the basic principles behind such software.

Note that you cannot connect two computer's parallel ports with a simple cable that has DB25 connectors at each end. In fact doing so could damage the computers' parallel ports because you'd be connecting digital outputs to digital outputs (a real no-no). However you purchase "Laplink compatible" cables (or buy real Laplink cables for that matter) the provide proper connections between the parallel ports of two computers. As you may recall from the section on the parallel port hardware the unidirectional parallel port provides five input signals. A Laplink cable routes four of the data lines to four of these input lines in both directions. The connections on a Laplink compatible cable are as follows:

Data written on bits zero through three of the data register at the transmitting site appear unchanged on bits three through six of the status port on the receiving site. Bit four of the transmitting site appears inverted at bit seven of the receiving site. Note that Laplink compatible cables are bidirectional. That is you can transmit data from either site to the other using the connections above. However since there are only five input bits on the parallel port you must transfer the data four bits at a time (we need one bit for the data strobe). Since the receiving site needs to acknowledge data transmissions we cannot simultaneously transmit data in both directions. We must use one of the output lines at the site receiving data to acknowledge the incoming data.

Since the two sites cooperating in a data transfer across the parallel cable must take turns transmitting and receiving data we must develop a protocol so each participant in the data transfer knows when it is okay to transmit and receive. Our protocol will be very simple - a site is either a transmitter or a receiver the roles will never switch. Designing a more complex protocol is not difficult but this simple protocol will suffice for the example you are about to see. Later in this section we will discuss ways to develop a protocol that allows two-way transmissions.

The following example programs will transmit and receive a single file across the parallel port. To use this software you run the transmit program on the transmitting site and the receive program on the receiving site. The transmission program fetches a file name from the DOS command line and opens that file for reading (generating an error and quitting if the file does not exist). Assuming the file exists the transmit program then queries the receiving site to see if it is available. The transmitter checks for the presence of the receiving site by alternately writing zeros and ones to all output bits then reading its input bits. The receiving site will invert these values and write them back when it comes on-line. Note that the order of execution (transmitter first or receiver first) does not matter. The two programs will attempt to handshake until the other comes on line.When both sites cycle through the inverting values three times they write the value 05h to their output ports to tell the other site they are ready to proceed. A time-out function aborts either program if the other site does not respond in a reasonable amount of time.

Once the two sites are synchronized the transmitting site determines the size of the file and then transmits the file name and size to the receiving site. The receiving site then begins waiting for the receipt of data.

The transmitting site sends the data 512 bytes at a time to the receiving site. After the transmission of 512 bytes the receiving site delays sending an acknowledgment and writes the 512 bytes of data to the disk. Then the receiving site sends the acknowledge and the transmitting site begins sending the next 512 bytes. This process repeats until the receiving site has accepted all the bytes from the file.

Here is the code for the transmitter:

; TRANSMIT.ASM
;
; This program is the transmitter portion of the programs that transmit files
; across a Laplink compatible parallel cable.
;
; This program assumes that the user want to use LPT1: for transmission.
; Adjust the equates
or read the port from the command line if this
; is inappropriate.

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


dseg            segment para public 'data'

TimeOutConst    equ     4000                    ;About 1 min on 66Mhz 486.
PrtrBase        equ     10                      ;Offset to LPT1: adrs.

MyPortAdrs      word    ?                       ;Holds printer port address.
FileHandle      word    ?                       ;Handle for output file.
FileBuffer      byte    512 dup (?)             ;Buffer for incoming data.

FileSize        dword   ?                       ;Size of incoming file.
FileNamePtr     dword   ?                       ;Holds ptr to filename

dseg            ends

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


; TestAbort-    Check to see if the user has pressed ctrl-C and wants to
;               abort this program.  This routine calls BIOS to see if the
;               user has pressed a key.  If so
it calls DOS to read the
;               key (function AH=8
read a key w/o echo and with ctrl-C
;               checking).

TestAbort       proc    near
push    ax
push    cx
push    dx
mov     ah
1
int     16h                     ;See if keypress.
je      NoKeyPress              ;Return if no keypress.
mov     ah
8                   ;Read char
chk for ctrl-C.
int     21h                     ;DOS aborts if ctrl-C.
NoKeyPress:     pop     dx
pop     cx
pop     ax
ret
TestAbort       endp



; SendByte-     Transmit the byte in AL to the receiving site four bits
;               at a time.

SendByte        proc    near
push    cx
push    dx
mov     ah
al          ;Save byte to xmit.

mov     dx
MyPortAdrs  ;Base address of LPT1: port.

; First
just to be sure
write a zero to bit #4.  This reads as a one
; in the busy bit of the receiver.

mov     al
0
out     dx
al          ;Data not ready yet.

; Wait until the receiver is not busy.  The receiver will write a zero
; to bit #4 of its data register while it is busy. This comes out as a
; one in our busy bit (bit 7 of the status register).  This loop waits
; until the receiver tells us its ready to receive data by writing a
; one to bit #4 (which we read as a zero).  Note that we check for a
; ctrl-C every so often in the event the user wants to abort the
; transmission.

inc     dx              ;Point at status register.
W4NBLp:         mov     cx
10000
Wait4NotBusy:   in      al
dx          ;Read status register value.
test    al
80h         ;Bit 7 = 1 if busy.
loopne  Wait4NotBusy    ;Repeat while busy
10000 times.
je      ItsNotbusy      ;Leave loop if not busy.
call    TestAbort       ;Check for Ctrl-C.
jmp     W4NBLp

; Okay
put the data on the data lines:

ItsNotBusy:     dec     dx              ;Point at data register.
mov     al
ah          ;Get a copy of the data.
and     al
0Fh         ;Strip out H.O. nibble
out     dx
al          ;"Prime" data lines
data not avail.
jmp     $+2             ;Short delay to allow the data time
jmp     $+2             ; to stablize on the data lines.
or      al
10h         ;Turn data available on.
out     dx
al          ;Send data w/data available strobe.

; Wait for the acknowledge from the receiving site.  Every now and then
; check for a ctrl-C so the user can abort the transmission program from
; within this loop.

inc     dx              ;Point at status register.
W4ALp:          mov     cx
10000       ;Times to loop between ctrl-C checks.
Wait4Ack:       in      al
dx          ;Read status port.
test    al
80h         ;Ack = 1 when rcvr acknowledges.
loope   Wait4Ack        ;Repeat 10000 times or until ack.
jne     GotAck          ;Branch if we got an ack.
call    TestAbort       ;Every 10000 calls
check for a
jmp     W4ALp           ; ctrl-C from the user.

; Send the data not available signal to the receiver:

GotAck:         dec     dx              ;Point at data register.
mov     al
0           ;Write a zero to bit 4
this appears
out     dx
al          ; as a one in the rcvr's busy bit.


; Okay
on to the H.O. nibble:

inc     dx              ;Point at status register.
W4NB2:          mov     cx
10000       ;10000 calls between ctrl-C checks.
Wait4NotBusy2:  in      al
dx          ;Read status register.
test    al
80h         ;Bit 7 = 1 if busy.
loopne  Wait4NotBusy2   ;Loop 10000 times while busy.
je      NotBusy2        ;H.O. bit clear (not busy)?
call    TestAbort       ;Check for ctrl-C.
jmp     W4NB2

; Okay
put the data on the data lines:

NotBusy2:       dec     dx              ;Point at data register.
mov     al
ah          ;Retrieve data to get H.O. nibble.
shr     al
4           ;Move H.O. nibble to L.O. nibble.
out     dx
al          ;"Prime" data lines.
or      al
10h         ;Data + data available strobe.
out     dx
al          ;Send data w/data available strobe.

; Wait for the acknowledge from the receiving site:

inc     dx              ;Point at status register.
W4A2Lp:         mov     cx
10000
Wait4Ack2:      in      al
dx          ;Read status port.
test    al
80h         ;Ack = 1
loope   Wait4Ack2       ;While while no acknowledge
jne     GotAck2         ;H.O. bit = 1 (ack)?
call    TestAbort       ;Check for ctrl-C
jmp     W4A2Lp

; Send the data not available signal to the receiver:

GotAck2:        dec     dx              ;Point at data register.
mov     al
0           ;Output a zero to bit #4 (that
out     dx
al          ; becomes busy=1 at rcvr).

mov     al
ah          ;Restore original data in AL.
pop     dx
pop     cx
ret
SendByte        endp



; Synchronization routines:
;
; Send0s-       Transmits a zero to the receiver site and then waits to
;               see if it gets a set of ones back.  Returns carry set if
;               this works
returns carry clear if we do not get a set of
;               ones back in a reasonable amount of time.

Send0s          proc    near
push    cx
push    dx

mov     dx
MyPortAdrs

mov     al
0                   ;Write the initial zero
out     dx
al                  ; value to our output port.

xor     cx
cx                  ;Checks for ones 10000 times.
Wait41s:        inc     dx                      ;Point at status port.
in      al
dx                  ;Read status port.
dec     dx                      ;Point back at data port.
and     al
78h                 ;Mask input bits.
cmp     al
78h                 ;All ones yet?
loopne  Wait41s
je      Got1s                   ;Branch if success.
clc                             ;Return failure.
pop     dx
pop     cx
ret

Got1s:          stc                             ;Return success.
pop     dx
pop     cx
ret
Send0s          endp


; Send1s-       Transmits all ones to the receiver site and then waits to
;               see if it gets a set of zeros back.  Returns carry set if
;               this works
returns carry clear if we do not get a set of
;               zeros back in a reasonable amount of time.

Send1s          proc    near
push    cx
push    dx

mov     dx
MyPortAdrs          ;LPT1: base address.

mov     al
0Fh                 ;Write the "all ones"
out     dx
al                  ; value to our output port.

mov     cx
0
Wait40s:        inc     dx                      ;Point at input port.
in      al
dx                  ;Read the status port.
dec     dx                      ;Point back at data port.
and     al
78h                 ;Mask input bits.
loopne  Wait40s                 ;Loop until we get zero back.
je      Got0s                   ;All zeros?  If so
branch.
clc                             ;Return failure.
pop     dx
pop     cx
ret


Got0s:          stc                             ;Return success.
pop     dx
pop     cx
ret
Send1s          endp




; Synchronize-  This procedure slowly writes all zeros and all ones to its
;               output port and checks the input status port to see if the
;               receiver site has synchronized.  When the receiver site
;               is synchronized
it will write the value 05h to its output
;               port.  So when this site sees the value 05h on its input
;               port
both sites are synchronized.  Returns with the
;               carry flag set if this operation is successful
clear if
;               unsuccessful.

Synchronize     proc    near
print
byte    "Synchronizing with receiver program"
byte    cr
lf
0

mov     dx
MyPortAdrs

mov     cx
TimeOutConst        ;Time out delay.
SyncLoop:       call    Send0s                  ;Send zero bits
wait for
jc      Got1s                   ; ones (carry set=got ones).

; If we didn't get what we wanted
write some ones at this point and see
; if we're out of phase with the receiving site.

Retry0:         call    Send1s                  ;Send ones
wait for zeros.
jc      SyncLoop                ;Carry set = got zeros.

; Well
we didn't get any response yet
see if the user has pressed ctrl-C
; to abort this program.

DoRetry:        call    TestAbort

; Okay
the receiving site has yet to respond.  Go back and try this again.

loop    SyncLoop

; If we've timed out
print an error message and return with the carry
; flag clear (to denote a timeout error).

print
byte    "Transmit: Timeout error waiting for receiver"
byte    cr
lf
0
clc
ret

; Okay
we wrote some zeros and we got some ones.  Let's write some ones
; and see if we get some zeros.  If not
retry the loop.

Got1s:
call    Send1s                  ;Send one bits
wait for
jnc     DoRetry                 ; zeros (carry set=got zeros).

; Well
we seem to be synchronized.  Just to be sure
let's play this out
; one more time.

call    Send0s                  ;Send zeros
wait for ones.
jnc     Retry0
call    Send1s                  ;Send ones
wait for zeros.
jnc     DoRetry

; We're syncronized.  Let's send out the 05h value to the receiving
; site to let it know everything is cool:

mov     al
05h                 ;Send signal to receiver to
out     dx
al                  ; tell it we're sync'd.

xor     cx
cx                  ;Long delay to give the rcvr
FinalDelay:     loop    FinalDelay              ; time to prepare.

print
byte    "Synchronized with receiving site"
byte    cr
lf
0
stc
ret
Synchronize     endp



; File I/O routines:
;
; GetFileInfo-  Opens the user specified file and passes along the file
;               name and file size to the receiving site.  Returns the
;               carry flag set if this operation is successful
clear if
;               unsuccessful.

GetFileInfo     proc    near

; Get the filename from the DOS command line:

mov     ax
1
argv
mov     word ptr FileNamePtr
di
mov     word ptr FileNamePtr+2
es

printf
byte    "Opening %^s\n"
0
dword   FileNamePtr

; Open the file:

push    ds
mov     ax
3D00h               ;Open for reading.
lds     dx
FileNamePtr
int     21h
pop     ds
jc      BadFile
mov     FileHandle
ax

; Compute the size of the file (do this by seeking to the last position
; in the file and using the return position as the file length):

mov     bx
ax                  ;Need handle in BX.
mov     ax
4202h               ;Seek to end of file.
xor     cx
cx                  ;Seek to position zero
xor     dx
dx                  ; from the end of file.
int     21h
jc      BadFile

mov     word ptr FileSize
ax   ;Save file length
mov     word ptr FileSize+2
dx

; Need to rewind file back to the beginning (seek to position zero):

mov     bx
FileHandle          ;Need handle in BX.
mov     ax
4200h               ;Seek to beginning of file.
xor     cx
cx                  ;Seek to position zero
xor     dx
dx
int     21h
jc      BadFile


; Okay
transmit the good stuff over to the receiving site:

mov     al
byte ptr FileSize           ;Send the file
call    SendByte                        ; size over.
mov     al
byte ptr FileSize+1
call    SendByte
mov     al
byte ptr FileSize+2
call    SendByte
mov     al
byte ptr FileSize+3
call    SendByte

les     bx
FileNamePtr                 ;Send the characters
SendName:       mov     al
es:[bx]                     ; in the filename to
call    SendByte                        ; the receiver until
inc     bx                              ; we hit a zero byte.
cmp     al
0
jne     SendName
stc                                     ;Return success.
ret

BadFile:        print
byte    "Error transmitting file information:"
0
puti
putcr
clc
ret
GetFileInfo     endp


; GetFileData-  This procedure reads the data from the file and transmits
;               it to the receiver a byte at a time.

GetFileData     proc    near
mov     ah
3Fh                 ;DOS read opcode.
mov     cx
512                 ;Read 512 bytes at a time.
mov     bx
FileHandle          ;File to read from.
lea     dx
FileBuffer          ;Buffer to hold data.
int     21h                     ;Read the data
jc      GFDError                ;Quit if error reading data.

mov     cx
ax                  ;Save # of bytes actually read.
jcxz    GFDDone                 ; quit if at EOF.
lea     bx
FileBuffer          ;Send the bytes in the file
XmitLoop:       mov     al
[bx]                ; buffer over to the rcvr
call    SendByte                ; one at a time.
inc     bx
loop    XmitLoop
jmp     GetFileData             ;Read rest of file.

GFDError:       print
byte    "DOS error #"
0
puti
print
byte    " while reading file"
cr
lf
0
GFDDone:        ret
GetFileData     endp



; Okay
here's the main program that controls everything.

Main            proc
mov     ax
dseg
mov     ds
ax
meminit


; First
get the address of LPT1: from the BIOS variables area.

mov     ax
40h
mov     es
ax
mov     ax
es:[PrtrBase]
mov     MyPortAdrs
ax

; See if we have a filename parameter:

argc
cmp     cx
1
je      GotName
print
byte    "Usage: transmit <filename>"
cr
lf
0
jmp     Quit



GotName:        call    Synchronize     ;Wait for the transmitter program.
jnc     Quit

call    GetFileInfo     ;Get file name and size.
jnc     Quit

call    GetFileData     ;Get the file's data.

Quit:           ExitPgm                 ;DOS macro to quit program.
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

Here is the receiver program that accepts and stores away the data sent by the program above:

; RECEIVE.ASM
;
; This program is the receiver portion of the programs that transmit files
; across a Laplink compatible parallel cable.
;
; This program assumes that the user want to use LPT1: for transmission.
; Adjust the equates
or read the port from the command line if this
; is inappropriate.

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


dseg            segment para public 'data'

TimeOutConst    equ     100                     ;About 1 min on 66Mhz 486.
PrtrBase        equ     8                       ;Offset to LPT1: adrs.

MyPortAdrs      word    ?                       ;Holds printer port address.
FileHandle      word    ?                       ;Handle for output file.
FileBuffer      byte    512 dup (?)             ;Buffer for incoming data.

FileSize        dword   ?                       ;Size of incoming file.
FileName        byte    128 dup (0)             ;Holds filename

dseg            ends

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


; TestAbort-    Reads the keyboard and gives the user the opportunity to
;               hit the ctrl-C key.

TestAbort       proc    near
push    ax
mov     ah
1
int     16h                     ;See if keypress.
je      NoKeypress
mov     ah
8                   ;Read char
chk for ctrl-C
int     21h
NoKeyPress:     pop     ax
ret
TestAbort       endp



; GetByte-      Reads a single byte from the parallel port (four bits at
;               at time).  Returns the byte in AL.

GetByte         proc    near
push    cx
push    dx

; Receive the L.O. Nibble.

mov     dx
MyPortAdrs
mov     al
10h                 ;Signal not busy.
out     dx
al

inc     dx                      ;Point at status port

W4DLp:          mov     cx
10000
Wait4Data:      in      al
dx                  ;See if data available.
test    al
80h                 ; (bit 7=0 if data available).
loopne  Wait4Data
je      DataIsAvail             ;Is data available?
call    TestAbort               ;If not
check for ctrl-C.
jmp     W4DLp

DataIsAvail:    shr     al
3                   ;Save this four bit package
and     al
0Fh                 ; (This is the L.O. nibble
mov     ah
al                  ; for our byte).

dec     dx                      ;Point at data register.
mov     al
0                   ;Signal data taken.
out     dx
al

inc     dx                      ;Point at status register.
W4ALp:          mov     cx
10000
Wait4Ack:       in      al
dx                  ;Wait for transmitter to
test    al
80h                 ; retract data available.
loope   Wait4Ack                ;Loop until data not avail.
jne     NextNibble              ;Branch if data not avail.
call    TestAbort               ;Let user hit ctrl-C.
jmp     W4ALp

; Receive the H.O. nibble:

NextNibble:     dec     dx                      ;Point at data register.
mov     al
10h                 ;Signal not busy
out     dx
al
inc     dx                      ;Point at status port
W4D2Lp:         mov     cx
10000
Wait4Data2:     in      al
dx                  ;See if data available.
test    al
80h                 ; (bit 7=0 if data available).
loopne  Wait4Data2              ;Loop until data available.
je      DataAvail2              ;Branch if data available.
call    TestAbort               ;Check for ctrl-C.
jmp     W4D2Lp

DataAvail2:     shl     al
1                   ;Merge this H.O. nibble
and     al
0F0h                ; with the existing L.O.
or      ah
al                  ; nibble.
dec     dx                      ;Point at data register.
mov     al
0                   ;Signal data taken.
out     dx
al

inc     dx                      ;Point at status register.
W4A2Lp:         mov     cx
10000
Wait4Ack2:      in      al
dx                  ;Wait for transmitter to
test    al
80h                 ; retract data available.
loope   Wait4Ack2               ;Wait for data not available.
jne     ReturnData              ;Branch if ack.
call    TestAbort               ;Check for ctrl-C
jmp     W4A2Lp

ReturnData:     mov     al
ah                  ;Put data in al.
pop     dx
pop     cx
ret
GetByte         endp




; Synchronize-  This procedure waits until it sees all zeros on the input
;               bits we receive from the transmiting site.  Once it receives
;               all zeros
it writes all ones to the output port.  When
;               all ones come back
it writes all zeros.  It repeats this
;               process until the transmiting site writes the value 05h.

Synchronize     proc    near

print
byte    "Synchronizing with transmitter program"
byte    cr
lf
0

mov     dx
MyPortAdrs
mov     al
0                   ;Initialize our output port
out     dx
al                  ; to prevent confusion.
mov     bx
TimeOutConst        ;Time out condition.
SyncLoop:       mov     cx
0                   ;For time out purposes.
SyncLoop0:      inc     dx                      ;Point at input port.
in      al
dx                  ;Read our input bits.
dec     dx
and     al
78h                 ;Keep only the data bits.
cmp     al
78h                 ;Check for all ones.
je      Got1s                   ;Branch if all ones.
cmp     al
0                   ;See if all zeros.
loopne  SyncLoop0

; Since we just saw a zero
write all ones to the output port.

mov     al
0FFh                ;Write all ones
out     dx
al

; Now wait for all ones to arrive from the transmiting site.

SyncLoop1:      inc     dx                      ;Point at status register.
in      al
dx                  ;Read status port.
dec     dx                      ;Point back at data register.
and     al
78h                 ;Keep only the data bits.
cmp     al
78h                 ;Are they all ones?
loopne  SyncLoop1               ;Repeat while not ones.
je      Got1s                   ;Branch if got ones.

; If we've timed out
check to see if the user has pressed ctrl-C to
; abort.

call    TestAbort               ;Check for ctrl-C.
dec     bx                      ;See if we've timed out.
jne     SyncLoop                ;Repeat if time-out.

print
byte    "Receive: connection timed out during synchronization"
byte    cr
lf
0
clc                             ;Signal time-out.
ret

; Jump down here once we've seen both a zero and a one.  Send the two
; in combinations until we get a 05h from the transmiting site or the
; user presses Ctrl-C.

Got1s:          inc     dx                      ;Point at status register.
in      al
dx                  ;Just copy whatever appears
dec     dx                      ; in our input port to the
shr     al
3                   ; output port until the
and     al
0Fh                 ; transmiting site sends
cmp     al
05h                 ; us the value 05h
je      Synchronized
not     al                      ;Keep inverting what we get
out     dx
al                  ; and send it to xmitter.
call    TestAbort               ;Check for CTRL-C here.
jmp     Got1s


; Okay
we're synchronized.  Return to the caller.

Synchronized:
and     al
0Fh                 ;Make sure busy bit is one
out     dx
al                  ; (bit 4=0 for busy=1).
print
byte    "Synchronized with transmiting site"
byte    cr
lf
0
stc
ret
Synchronize     endp


; GetFileInfo-  The transmitting program sends us the file length and a
;               zero terminated filename.  Get that data here.

GetFileInfo     proc    near
mov     dx
MyPortAdrs
mov     al
10h                 ;Set busy bit to zero.
out     dx
al                  ;Tell xmit pgm
we're ready.

; First four bytes contain the filesize:

call    GetByte
mov     byte ptr FileSize
al
call    GetByte
mov     byte ptr FileSize+1
al
call    GetByte
mov     byte ptr FileSize+2
al
call    GetByte
mov     byte ptr FileSize+3
al

; The next n bytes (up to a zero terminating byte) contain the filename:

mov     bx
0
GetFileName:    call    GetByte
mov     FileName[bx]
al
call    TestAbort
inc     bx
cmp     al
0
jne     GetFileName

ret
GetFileInfo     endp


; GetFileData-  Receives the file data from the transmitting site
;               and writes it to the output file.

GetFileData     proc    near

; First
see if we have more than 512 bytes left to go

cmp     word ptr FileSize+2
0          ;If H.O. word is not
jne     MoreThan512                     ; zero
more than 512.
cmp     word ptr FileSize
512          ;If H.O. is zero
just
jbe     LastBlock                       ; check L.O. word.

; We've got more than 512 bytes left to go in this file
read 512 bytes
; at this point.

MoreThan512:    mov     cx
512                         ;Receive 512 bytes
lea     bx
FileBuffer                  ; from the xmitter.
ReadLoop:       call    GetByte                         ;Read a byte.
mov     [bx]
al                        ;Save the byte away.
inc     bx                              ;Move on to next
loop    ReadLoop                        ; buffer element.

; Okay
write the data to the file:

mov     ah
40h                         ;DOS write opcode.
mov     bx
FileHandle                  ;Write to this file.
mov     cx
512                         ;Write 512 bytes.
lea     dx
Filebuffer                  ;From this address.
int     21h
jc      BadWrite                        ;Quit if error.

; Decrement the file size by 512 bytes:

sub     word ptr FileSize
512          ;32-bit subtraction
sbb     word ptr FileSize
0            ; of 512.
jmp     GetFileData

; Process the last block
that contains 1..511 bytes
here.

LastBlock:
mov     cx
word ptr FileSize           ;Receive the last
lea     bx
FileBuffer                  ; 1..511 bytes from
ReadLB:         call    GetByte                         ; the transmitter.
mov     [bx]
al
inc     bx
loop    ReadLB

mov     ah
40h                         ;Write the last block
mov     bx
FileHandle                  ; of bytes to the
mov     cx
word ptr FileSize           ; file.
lea     dx
Filebuffer
int     21h
jnc     Closefile

BadWrite:       print
byte    "DOS error #"
0
puti
print
byte    " while writing data."
cr
lf
0

; Close the file here.

CloseFile:      mov     bx
FileHandle                  ;Close this file.
mov     ah
3Eh                         ;DOS close opcode.
int     21h
ret
GetFileData     endp



; Here's the main program that gets the whole ball rolling.

Main            proc
mov     ax
dseg
mov     ds
ax
meminit


; First
get the address of LPT1: from the BIOS variables area.

mov     ax
40h         ;Point at BIOS variable segment.
mov     es
ax
mov     ax
es:[PrtrBase]
mov     MyPortAdrs
ax

call    Synchronize     ;Wait for the transmitter program.
jnc     Quit

call    GetFileInfo     ;Get file name and size.

printf
byte    "Filename: %s\nFile size: %ld\n"
0
dword   Filename
FileSize

mov     ah
3Ch         ;Create file.
mov     cx
0           ;Standard attributes
lea     dx
Filename
int     21h
jnc     GoodOpen
print
byte    "Error opening file"
cr
lf
0
jmp     Quit

GoodOpen:       mov     FileHandle
ax
call    GetFileData     ;Get the file's data.

Quit:           ExitPgm                 ;DOS macro to quit program.
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

Chapter Twenty One (Part 2)

Table of Content

Chapter Twenty Two  

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