home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Oakland CPM Archive
/
oakcpm.iso
/
sigm
/
vol229
/
apcserio.lbr
/
SERIALIO.A86
next >
Wrap
Text File
|
1986-02-10
|
13KB
|
457 lines
title 'Interrupt-Driven Serial Port Handler'
; THIS PROGRAM IS HEREBY PLACED IN THE PUBLIC DOMAIN,
; AND MAY BE FREELY COPIED AND DISTRIBUTED.
; Context Sensitive Inc.
; 4200 Aurora Ave. N.
; Seattle, WA 98103
; (206) NEC-0301
;
; Program: SERIALIO.A86
;
; Version: 1.2 23 February 1984
; Author: Ron Blanford
; This is only a fragment of a program. The routines given
; here can be included in any (assembly language) program,
; and called as required to perform input and output to the
; primary serial (RS232) port on the NEC Advanced Personal
; Computer. These routines duplicate functions that are
; provided by CP/M-86, with the difference that input from
; the serial port is handled on an interrupt-driven basis
; and characters are stored until requested.
;
; To add these routines to your program, insert just prior
; to the END statement of your program the statement
; INCLUDE SERIALIO.A86
;
; These routines preserve all registers, except possibly AL
; and the flags.
;
; The routines available to your program are:
;
; 1. sio_rxstatus: Returns -1 in AL if a character is
; available in the serial input buffer,
; and 0 if no character is waiting.
;
; 2. sio_receive: Returns a character from the serial
; input buffer in AL. If no character
; is available, it waits until one
; arrives.
;
; 3. sio_txstatus: Returns -1 in AL if the transmit
; buffer is empty and a character may be
; transmitted, and 0 if transmission is
; not possible.
;
; 4. sio_transmit: Sends the character in AL out the
; serial port. If the port is not ready,
; it waits until it is.
;
; 5. sio_start: Called once to initialize the port
; before using it. Sets initial values:
; baud = 300, parity = none
; data = 8, stop = 1.
;
; 6. sio_finish: Called once to de-initialize the port
; just before ending your program.
;
; 7. sio_setmode: Called to set the baud rate and mode
; of the port. DX contains the parameters
; that are described in the CP/M-86 System
; Reference Guide, page 5-40, as follows:
;
; DH = baud rate DL = asynchronous mode byte
; 0 = 150 bps 7 6 5 4 3 2 1 0
; 1 = 200 bps _________________________________
; 2 = 300 bps | | | | 1 0 |
; 3 = 600 bps ----^-------^-------^-------^----
; 4 = 1200 bps | | | +- must be 10
; 5 = 2400 bps | | +- data bits: 00=5, 01=6
; 6 = 4800 bps | | 10=7, 11=8
; 7 = 9600 bps | +--- parity: 00 or 10=none
; 8 = 19200 bps | 01=odd, 11=even
; +--- stop bits: 01=1, 10=1.5
; 11=2, 00 illegal
;
; If DH is greater than 8, the baud rate will
; not be changed. If DL is 0, the mode will
; not be changed.
;
;
; An alternative entry point to these routines which is
; more suitable for high-level languages is to use the
; initial jump vector table. The vectors are allocated
; in the same sequence as listed above, and are each the
; standard 3-byte relative jump instruction. The address
; of the vector table depends on the load address assigned
; by the high-level language.
cseg $
sio_jumptable:
jmp sio_rxstatus ; receive status
jmp sio_receive ; receive character
jmp sio_txstatus ; transmit status
jmp sio_transmit ; transmit character
jmp sio_start ; initialize serial port
jmp sio_finish ; de-initialize serial port
jmp sio_setmode ; set port characteristics
; Interrupt vector locations, in data segment 0
sio_offset equ 84h ; Sio interrupt offset
sio_segment equ 86h ; Sio interrupt segment
; 8259 Interrupt controller (master) port addresses
ic_command equ 20h ; Interrupt command register
ic_mask equ 22h ; Interrupt mask register
; 8259 commands and masks
icmd_endInt equ 20h ; End of interrupt
imask_timerOff equ 08h ; Disable timer interrupt
imask_sioOff equ 02h ; Disable RS232 interrupt
; 8253-5 Interval timer port addresses
timer_data equ 2Bh ; Baud set (for channel 1)
timer_command equ 2Fh ; Baud timer command port
; 8253 Timer commands
tcmd_ch1 equ 76h ; Select & init channel 1
; 8251A USART controller port addresses
sio_data equ 30h ; Data port
sio_sts1 equ 32h ; in Status port 1
sio_sts2 equ 34h ; in Status port 2
sio_command equ 32h ; out Command port
sio_mask equ 34h ; out Interrupt mask port
sio_disable equ 36h ; out Disable trans. port
; 8251 status port 1 bits
sio_RxRDY equ 02h ; Receive ready value
sio_TxRDY equ 01h ; Send ready value
sio_DSR equ 80h ; Data set ready
; 8251 status port 2 bits
sio_CS equ 04h ; Clear to send
; 8251 initialization instructions
; command instructions
scmd_TxE equ 01h ; Transmit enable
scmd_DTR equ 02h ; DTR signal high
scmd_RxE equ 04h ; Receive enable
scmd_BRK equ 08h ; Send break
scmd_ERR equ 10h ; Error reset
scmd_RTS equ 20h ; RTS signal high
scmd_MODE equ 40h ; Reset - accept mode inst.
scmd_HUNT equ 80h ; Hunt for sync characters
; mode instructions
smode_1x equ 01h ; Baud rate factor: 1x
smode_16x equ 02h ; 16x
smode_64x equ 03h ; 64x
smode_5data equ 00h ; Data bits: 5
smode_6data equ 04h ; 6
smode_7data equ 08h ; 7
smode_8data equ 0Ch ; 8
smode_pnone equ 00h ; Parity: none
smode_podd equ 10h ; odd
smode_peven equ 30h ; even
smode_1stop equ 40h ; Stop bits: 1
smode_15stop equ 80h ; 1.5
smode_2stop equ 0C0h ; 2
; 8251 interrupt mask port bits
sint_txOff equ 01h ; Transmit complete int.
sint_rxOff equ 02h ; Receive complete int.
sint_tbeOff equ 04h ; Trans. buffer empty int.
; sio_rxstatus: returns -1 in AL if a character has been received
; returns 0 if the input interrupt buffer is empty.
sio_rxstatus:
cmp sio_chars,0 ; Any chars in the buffer?
je siorx1 ; If not, report failure
or al,0FFh ; Otherwise give positive
jmps siorx2 ; response
siorx1: and al,0
siorx2: ret
; sio_receive: returns the next input character in AL
; waits for a character if none have arrived.
sio_receive:
call sio_rxstatus ; Wait for a character
jz sio_receive
push bx
cli ; Prevent interrupts.
dec sio_chars ; Uncount the character.
mov bx,sio_charPtr ; Get the buffer pointer.
inc bx ; Point to the next char.
cmp bx,offset sio_buffer+sio_size ; Past the end?
jb sio_r2
lea bx,sio_buffer ; If so wrap to the start.
sio_r2: mov sio_charPtr,bx ; Save the updated pointer.
mov al,[bx] ; Get the character.
sti ; Re-enable interrupts.
pop bx
ret
; sio_int: Handles the serial port input interrupts
sio_int:
cli ; Prevent interrupts.
push ax ; Save registers we'll use.
push bx
push ds
mov ax,cs:sio_dataSeg ; Get our own data segment.
mov ds,ax
call sio_process ; Receive and store char.
mov al,icmd_endInt ; Signal End of Interrupt.
out ic_command,al
pop ds ; Restore registers we used.
pop bx
pop ax
iret ; Return from the interrupt.
; sio_process: Reads the input character from the serial port and
; stores it in the ring buffer.
sio_process:
in al,sio_sts1 ; Get the port status.
and al,sio_RxRDY ; Is a character waiting?
jz sio_p3 ; No, just a false alarm.
in al,sio_data ; Otherwise read character.
cmp sio_chars,sio_size ; Is the buffer full?
je sio_p3 ; If so, discard character.
inc sio_chars ; Otherwise count character.
mov bx,sio_spacePtr ; Point to the next space.
inc bx ; Increment pointer.
cmp bx,offset sio_buffer+sio_size ; Past the end?
jb sio_p2
lea bx,sio_buffer ; Yes, wrap to the start.
sio_p2: mov sio_spacePtr,bx ; Save the pointer.
mov [bx],al ; Save the character.
sio_p3: ret
; sio_txstatus: returns -1 in AL if the serial port can accept a
; character for transmission, returns 0 otherwise.
sio_txstatus:
in al,sio_sts1 ; Read status port 1.
and al,sio_DSR+sio_TxRDY ; Mask DSR and TxRDY bits.
xor al,sio_DSR+sio_TxRDY ; Check that both are set.
jnz siotx1 ; If not, port is not ready.
in al,sio_sts2 ; Read status port 2.
and al,sio_CS ; Check for clear to send.
jz siotx1 ; If not, return failure.
or al,0FFh ; Otherwise ready to send.
jmps siotx2
siotx1: and al,0
siotx2: ret
; sio_transmit: sends the character in AL to the serial port.
; doesn't return until the character has been sent.
sio_transmit:
push ax ; Save character to be sent.
sio_t1: call sio_txstatus ; Wait for port to be ready.
jz sio_t1
pop ax
out sio_data,al ; Send the character.
ret
; sio_start: Initializes the interrupt vectors and controller
; and the serial port to default values
sio_start:
cmp sio_init,0 ; Skip initialization
jne sio_s1 ; if already performed.
mov sio_init,0FFh ; Set "initialized" flag.
push ax ; Save registers we'll use.
push dx
push es
mov ax,ds ; Save data segment in CSEG
mov cs:sio_dataSeg,ax ; for the int. handler.
in al,ic_mask ; Get current interrupt mask
mov sio_oldInt,al ; Save for later restoration
mov ax,0 ; Point interrupt vector
mov es,ax ; table in page zero.
mov ax,es:.sio_segment ; Save the current vector
mov sio_oldSeg,ax ; segment and offset.
mov ax,es:.sio_offset
mov sio_oldOff,ax
cli
mov ax,cs ; Replace with address of
mov es:.sio_segment,ax ; interrupt handler.
mov ax,offset sio_int
mov es:.sio_offset,ax
mov dh,8 ; Set default baud 19200
mov dl,smode_1stop+smode_pnone+smode_8data+smode_16x
call sio_setmode ; and default mode.
mov al,00h ; Reset disable register.
out sio_disable,al
in al,sio_data ; Clear input buffer.
mov al,sint_txOff+sint_tbeOff ; Set serial int. mask.
out sio_mask,al
in al,ic_mask ; Set master controller to
or al,imask_timerOff ; disable timer
and al,not imask_sioOff ; and enable sio.
out ic_mask,al
sti
pop es ; Restore our registers.
pop dx
pop ax
sio_s1: ret
; sio_finish: This routine restores the interrupt controller
; mask and vector table to the original values.
sio_finish:
cmp sio_init,0 ; Was initialization done?
je sio_f1 ; If not, don't undo it.
mov sio_init,0
push ax ; Save our registers.
push es
cli ; Prevent interrupts.
mov al,sio_oldInt ; Restore the old mask.
out ic_mask,al
mov ax,0 ; restore the old vector.
mov es,ax
mov ax,sio_oldSeg
mov es:.sio_segment,ax
mov ax,sio_oldOff
mov es:.sio_offset,ax
sti
pop es ; Restore our registers.
pop ax
sio_f1: ret
; sio_setmode: sets the baud rate, data bits, parity, and stop
; bits for the serial port. DH contains an index to
; the baud rate table, and DL contains the mode byte.
sio_setmode:
push ax ; Save our registers.
push bx
or dl,dl ; See if mode is to be set:
jz sio_m1 ; not if DL = 0.
mov al,0 ; Suggested reset sequence:
out sio_command,al ; 3 zeros and scmd_MODE.
mov al,0
out sio_command,al
mov al,0
out sio_command,al
mov al,scmd_MODE
out sio_command,al
push ax ; waste some time to allow
pop ax ; the 8251 time to reset
mov al,dl ; Set mode with supplied
out sio_command,al ; parameters.
mov al,scmd_RTS+scmd_ERR+scmd_RxE+scmd_DTR+scmd_TxE
out sio_command,al ; RTS & DTR high, Tx & Rx on
sio_m1: mov al,dh ; Get the baud rate index.
cmp al,8 ; Validate range (0-8).
ja sio_m2
lea bx,cs:sio_baud ; Point to baud table.
add al,al ; Make index a word offset.
mov ah,0
add bx,ax ; Point to desired entry.
mov al,tcmd_ch1 ; Set timer channel 1 mode.
out timer_command,al
mov ax,cs:[bx] ; Get the baud table entry.
out timer_data,al ; Output low byte.
mov al,ah
out timer_data,al ; Output high byte.
sio_m2: pop bx ; Restore our registers.
pop ax
ret
sio_dataSeg dw 0 ; Storage in CSEG to point
; to DSEG.
; Interval Timer values (assumes 16x baud rate mode)
sio_baud dw 0400h ; 150 baud 0
dw 0300h ; 200 baud 1
dw 0200h ; 300 baud 2
dw 0100h ; 600 baud 3
dw 0080h ; 1200 baud 4
dw 0040h ; 2400 baud 5
dw 0020h ; 4800 baud 6
dw 0010h ; 9600 baud 7
dw 0008h ; 19200 baud 8
dseg $
; Impure storage with contents that are changed dynamically
sio_init db 0 ; "Initialized" flag.
sio_oldInt db 0 ; 8259 interrupt mask
sio_oldSeg dw 0 ; Sio interrupt vector
sio_oldOff dw 0
sio_size equ 128 ; Size of circular buffer.
sio_buffer rb sio_size ; Circular character buffer.
sio_spacePtr dw sio_buffer ; Char. insertion pointer.
sio_charPtr dw sio_buffer ; Char. retrieval pointer.
sio_cha