Metropoli BBS
VIEWER: parallel.asm MODE: TEXT (ASCII)
        IDEAL
        MODEL small
        P486

        public Init
        public SendChar
        public ReadChar
        public IsTransmitReady
        public updateTX
        public updateRX

        DATASEG
;
; written on Sun  12-19-1993  by Ed Beroset
;   and released to the public domain by the author
;
; This library is intended to allow simplified I/O via parallel ports
; hooked up "back-to-back" using a cable like that used by Laplink.
; Interrupt driven would be nice, but cheap parallel cards often have
; faulty interrupt hardware.  Also, I have found through experience that
; only five output and five input lines are reliable on all machines,
; I've chosen to use just those ten even though using other lines on
; machines that support it would speed things greatly.  (Actually just
; one more bit in both directions would increase throughput by about 33%.)
; In any case, here is how the computers see the interconnection:
;
;
; base I/O address (DATA out)
;
;          connected  logical
;      bit    to       name
;      --- --------- ---------
;       7
;       6
;       5
;       4  STATUS.7- RX_READY
;       3  STATUS.6  TX_DAV
;       2  STATUS.5  TX_DATA2
;       1  STATUS.4  TX_DATA1
;       0  STATUS.3  TX_DATA0
;
; base I/O address + 1 (STATUS in)
;
;       7  DATA.4-   TX_BUSY
;       6  DATA.3    RX_DAV
;       5  DATA.2    RX_DATA2
;       4  DATA.1    RX_DATA1
;       3  DATA.0    RX_DATA0
;       2
;       1
;       0
;
; the '-' behind the 'connected to' line name means that the bit is
; inverted by hardware between the sender and receiver.  That is, if
; RX_READY (DATA port, bit 4) is set to '1', TX_READY (STATUS port, bit 7)
; reads '0'.
;
; I've decided to use two-wire handshaking on both TX and RX, allowing
; full duplex I/O with positive ACK's for all data sent.  Since we
; only have three data bits at a time, the following simplified
; handshaking script is repeated three times per byte:
;
; tx:  wait until not TX_BUSY
;
; tx:  write DATA & assert TX_DAV (data valid)
;
;         rx:  wait until RX_DAV
;
;         rx:  read DATA
;
;         rx:  clear RX_READY (inverted by hardware and read as TX_BUSY)
;
; tx:  wait until TX_BUSY set
;
; tx:  clear TX_DAV
;
;         rx:  wait until not RX_DAV
;
;         rx:  set RX_READY
;
; A byte is sent (and received) in three steps as follows:
;
;  data     step    step    step
;   bit       1       2       3
; --------  ------  ------  ------
; TX_DATA2    0     bit 5   bit 2
; TX_DATA1  bit 7   bit 4   bit 1
; TX_DATA0  bit 6   bit 3   bit 0
;
; Right now, the first step puts a dummy '0' in the top bit, which allows
; us to gracefully handle fitting 8-bit bytes into 9-bits, but it's a bit
; of a waste.  It could be used for parity, but parity bits are not of
; much use in either detecting or correcting errors.  Other possibilities
; include
;  - sending data as 72-bit chunks (sending 9 bytes in the time it would
;    take the current scheme to send 8 bytes)
;  - using compression and using all 9 bits as compressed data.  This
;    might be particularly useful in large file transfers.
;  - mode toggle.  E.g. switch from ACKing each byte to some kind of
;    burst mode and back again.
;
; Here are the equates used by the TX state machine:

TX_EMPTY        =       0     ; all bits clear means TX empty
TX_ONE          =       01h   ; one more bit triplet to send
TX_TWO          =       02h   ; two more bit triplets to send
TX_THREE        =       04h   ; three more bit triplets to send
TX_WAIT_BUSY    =       80h   ; we're waiting for the distant end to read

; Here are the equates used by the RX state machine

RX_EMPTY        =       0     ; all bits clear means we've just
                              ;   completed constructing a whole byte
RX_ONE          =       01h   ; one more bit triplet to receive
RX_TWO          =       02h   ; two more bit triplets to receive
RX_THREE        =       04h   ; three more bit triplets to receive
RX_WAIT_ACK     =       80h   ; waiting for distant end to ACK our last read

; The following are equates for the input byte at [baseio] + 1

TX_BUSY         =       080h  ; the distant end is busy receiving
RX_DAV          =       040h  ; distant end indicates data valid
RX_DATA2        =       020h  ;
RX_DATA1        =       010h  ; data bits from distant end
RX_DATA0        =       008h  ;

;
; The following are equates for the output byte at [baseio]
;
RX_READY        =       010h  ; we are ready to receive data
TX_DAV          =       008h  ; data we're sending is valid
TX_DATA2        =       004h  ;
TX_DATA1        =       002h  ; data bits we're transmitting
TX_DATA0        =       001h  ;


tx_state        db      TX_EMPTY
rx_state        db      RX_ONE OR RX_TWO OR RX_THREE
tx_char         db      0
rx_char         db      0

        CODESEG

;**********************************************************************
;
; Init
;
;   initialize the parallel port for tx & rx
;
; Entry:
;
;   dx = base I/O address of parallel port
;
; Exit:
;
; Trashed:
;
;   al
;
;**********************************************************************
PROC Init
        mov     al,RX_READY     ; tell distant end we're ready
        out     dx,al           ;
        ret
ENDP Init

;**********************************************************************
;
; SendChar
;
;   send a character via the parallel port (in 4-bit bidirectional
;   mode)
;
; Entry:
;
;   dx = base I/O address of parallel port
;   al = byte to transmit
;
; Exit:
;
; Trashed:
;
;   none
;
;**********************************************************************
PROC SendChar
        mov     [tx_char],al    ; save character to transmit
        mov     [tx_state], TX_ONE OR TX_TWO OR TX_THREE
        ret
ENDP SendChar

;**********************************************************************
;
; IsTransmitReady
;
;   check to see if transmit buffer is ready for more data
;
; Entry:
;
;   dx = base I/O address of parallel port
;
; Exit:
;
;   ZF set if ready for transmit; otherwise ZF clear
;
; Trashed:
;
;
;**********************************************************************
PROC IsTransmitReady
        cmp     [tx_state],TX_EMPTY
        ret
ENDP IsTransmitReady

;**********************************************************************
;
; ReadChar
;
;   if a character is ready, this will fetch the char and reset the
;   state machine.  This call should ONLY be made if updateRX returns
;   ZF set (i.e. it indicates that a character is ready).  If it is
;   called under any other conditions, you'll probably get a bad RX
;   byte and you may de-synchronize the RX state machine.
;
; Entry:
;
;   dx = base I/O address of parallel port
;
; Exit:
;
;   al = character just received
;
; Trashed:
;
;   none
;
;**********************************************************************
PROC ReadChar
        mov     al,[rx_char]    ; print byte and reset state machine
        mov     [rx_state], RX_ONE OR RX_TWO OR RX_THREE
        ret
ENDP ReadChar

;**********************************************************************
;
; updateTX
;
;   update the transmit state machine.  Since this is a polled I/O
;   scheme, this routine should be called on a regular basis.  There
;   are no I/O loops within this routine, so it is guarenteed that
;   this routine will return in a timely basis.
;
; Entry:
;
;   dx = base I/O address of parallel port
;
; Exit:
;
;
; Trashed:
;
;   ax
;
;**********************************************************************
PROC updateTX
        push    dx
;
; do we have data to send?
;
        cmp     [tx_state],TX_EMPTY ; Q: data to send?
        jz      @@exit          ;  N: skip tx section
        inc     dx              ;  Y: we'll need the status bits in AL
        in      al,dx           ;
        test    [tx_state],TX_WAIT_BUSY ; Q: waiting to complete last TX?
        jnz     finish_tx       ;    Y: waiting for distant end to read

        test    al,TX_BUSY      ; Q: is distant end still busy?
        jnz     @@exit          ;  Y: keep waiting
        test    [tx_state],TX_THREE ; Q: first transmit of this byte?
        jz      not_first
        rol     [tx_char],2     ; prepare first two bits
        mov     ah,3            ; only use bottom two bits
        jmp     apply_mask      ; go to it
not_first:
        rol     [tx_char],3     ; prepare next three bits
        mov     ah,7            ; put mask in al
apply_mask:
        and     ah,[tx_char]    ; fetch next three data bits into ah
        dec     dx
        in      al,dx           ; recall current byte
        and     al,RX_READY     ; save current RX_READY bit
        or      al,ah           ; put data into it
        or      al,TX_DAV       ; and set data valid line
        out     dx,al           ; send it
        or      [tx_state],TX_WAIT_BUSY ; set status to indicate 2nd half
        jmp     @@exit

finish_tx:
        ; we're still waiting for distant end to read last data
        test    al,TX_BUSY      ; Q: has distant read data yet?
        jz      @@exit          ;  N: keep waiting
        and     [tx_state],NOT (TX_WAIT_BUSY) ; clear bit
        shr     [tx_state],1    ; and go to next state
        dec     dx
        in      al,dx           ;
        and     al,NOT TX_DAV   ;
        out     dx,al           ;

@@exit:
        pop     dx
        ret
ENDP updateTX


;**********************************************************************
;
; updateRX
;
;   update the receive state machine.  Since this is a polled I/O
;   scheme, this routine should be called on a regular basis.  There
;   are no I/O loops within this routine, so it is guarenteed that
;   this routine will return in a timely basis.
;
;
; Entry:
;
;   dx = base I/O address of parallel port
;
; Exit:
;
;   ZF set if a character is ready in the buffer; otherwise ZF clear
;
; Trashed:
;
;   ax
;
;**********************************************************************
PROC updateRX
        push    dx
;
; incoming data?
;
        inc     dx              ; we'll  need status address first
        test    [rx_state],RX_WAIT_ACK  ; Q: are we waiting for an ACK
        jnz     finish_rx               ;  Y: waiting for ack

        in      al,dx           ;
        test    al,RX_DAV       ; Q: new valid data?
        jz      @@exit          ;  N: nope, so skip out
        shr     al,3            ;isolate data bits
        and     al,7            ;fetch only data bits
        shl     [rx_char],3     ; rotate existing data bits
        or      [rx_char],al
        stc                     ; prepare to set RX_WAIT_ACK bit
        rcr     [rx_state],1    ; ack bit was already clear, so this is safe
        dec     dx
        in      al,dx
        and     al,NOT (RX_READY) ; clear RX_READY bit
        out     dx,al
        jmp     @@exit

finish_rx:
        ; we were just waiting for distant end to ack
        in      al,dx           ; fetch status
        test    al,RX_DAV       ; Q: has distant end ACK'd our read?
        jnz     @@exit          ;  N: nope, so skip out
        dec     dx
        in      al,dx
        or      al,RX_READY     ; we're ready again
        out     dx,al
        and     [rx_state],NOT (RX_WAIT_ACK) ; wait for next byte

@@exit:
        cmp     [rx_state],RX_EMPTY ;set ZF if char ready
        pop     dx
        ret
ENDP updateRX

        END
[ RETURN TO DIRECTORY ]