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