; Ser.asm Updated 1/27/88 (c) Anticlimactic Teleservices Ltd.

; com3/4 support added Mar92/Brent Bottles - untested
; according to some silly contract, this update is copyright by Autodesk, Inc.
; as i wrote it on a computer belonging to Autodesk.  but I don't care.
; use it all you want.

; 19200/38400 baud support and CTS checking added May92/Brent Bottles

; 57600 support Jun92/Brent Bottles

; 115200 support Nov92/Brent Bottles

         PAGE    60,132
        TITLE   RS232 utilities for the IBM

Comment         /
******************************************************************
**                                                              **
**              Copyright 1986 Lauren J. Guimont                **
**                                                              **
**      Permission is granted to use this module for any        **
**      personal purposes.  Permission is not granted for       **
**      use of monetary gain or favor without the express       **
**      written permission of the author, Lauren J. Guimont.    **
**                                                              **
**      Regardless of the purpose of use,  removal of the       **
**      copyright message within the DATA segment violates      **
**      all previous rights of use.  In doing so, the user      **
**      gives up all rights to the use of this module in any    **
**      form.                                                   **
**                                                              **
******************************************************************

******************************************************************
**      To convert back to small model, change all 'Far's to    **
**     'Near's.  Change RetAdd 'dd' in StackFrame to 'dw'.      **
*****************************************************************/

RS232_INT       Equ     0014h
RING_BUF_SIZE	Equ	4096d
COM1PORT        Equ     3f8h
COM2PORT        Equ     2f8h

StackFrame      Struc
  OldBP         dw      ?               ; saved BP value
  RetAdd        dd      ?               ; dw for small memory model
  Arg1          dw      ?               ; first argument passed
  Arg2          dw      ?               ; second argument passed
  Arg3          dw      ?               ; third argument passed
  Arg4          dw      ?               ; fourth argument passed
  Arg5          dw      ?               ; fifth argument passed
  Arg6          dw      ?               ; sixth argument passed
StackFrame      EndS

;
; These are currently set to support the small model of the
; Microsoft C compiler.
;
IGROUP  Group   _Text
DGROUP  Group   _Data

        Assume  cs:IGROUP, ds:DGROUP, es:Nothing, ss:DGROUP

        SUBTTL  Data area
        PAGE

_Data   Segment         Word Public 'DATA'

                db      'Copyright 1986 Lauren J. Guimont'

CommX           dw      0
RS_Base         dw      0
OldRS_Off       dw      0
OldRS_Seg       dw      0
Head            dw      0
Tail            dw      0

InputBuffer     db      RING_BUF_SIZE dup(0)

checkCTS        db      0

_Data   EndS

        SUBTTL  Public declarations
        PAGE

_Text   Segment         Byte Public 'CODE'

	Public	_InitRS
        Public  _DeInitRS
        Public  _FlushRS
        Public  _StatRS
        Public  _GetRS
        Public  _PutRS
        Public  _DtrRS
        Public  _CarrStatRS
        Public  _RingStatRS

	Public	_StatCON
        Public  _GetCON
;	Public	_PutCON

        SUBTTL  RS232 support routines
        PAGE

;******************************************************************************
;
; Name:                 _InitRS
;
; Synopsis:     void = InitRS(Port, Baud, Stops, Parity, Length, cCTS);
;
;               int     Port;           0 = COMM1
;                                       1 = COMM2
;                                       2 = COMM3
;                                       3 = COMM4
;               int     Baud;           0 = 110
;                                       1 = 150
;                                       2 = 300
;                                       3 = 600
;                                       4 = 1200
;                                       5 = 2400
;                                       6 = 4800
;                                       7 = 9600
;                                       8 = 19200
;                                       9 = 38400
;                                       A = 57600
;					B = 115200
;               int     Stops;          0 = 1 stop bit
;                                       1 = 2 stop bits
;               int     Parity;         0 = none
;                                       1 = odd
;                                       2 = none
;                                       3 = even
;               int     Length;         0 = 5 bit data length
;                                       1 = 6 bit data length
;                                       2 = 7 bit data length
;                                       3 = 8 bit data length
;               int     cCTS            0 = don't check CTS on output
;                                       1 = check CTS on output
;
; Description:  Initialize one of the COMM ports based upon the passed
;               parameters.
;
; Cautions:     NOTE!
;               There is no error checking on the passed parameters,
;               only that each is masked for the number of bits that
;               will be used for each one.
;
;******************************************************************************
RsInitRecord    Record  Rate:3, Parity:2, Stops:1, WLen:2

_InitRS         Proc    Far
        push    bp
        mov     bp, sp

        mov     bx, [bp].Arg6
        mov     checkCTS, bl

        push    es
        mov     ax, 40h                 ; BIOS data area
        mov     es, ax
        mov     bx, [bp].Arg1           ; get the COMM number
        and     bx, 11b                 ; mask everything else off
        mov     CommX, bx               ; save it for INT control
        cmp     bx, 1
        ja      good_set
        or      bx, bx
        jz      is_com1

        mov     RS_Base, COM2PORT
        jmp short done_set

is_com1:
        mov     RS_Base, COM1PORT
        jmp short done_set

good_set:
        shl     bx, 1                   ; make it a word value
        mov     bx, es:[bx]             ; get the base port number
        mov     RS_Base, bx             ; save it
done_set:
        pop     es

        mov     dx, [bp].Arg1           ; get the COMM number
        sub     bl, bl                  ; clear it for building RsInitRecord

        mov     ax, [bp].Arg2           ; get the baud rate
        mov     cl, Rate                ; get the shift factor
        shl     ax, cl                  ; shift it into place
        and     ax, mask Rate           ; mask the rate
        or      bl, al                  ; OR it into RsInitRecord

        mov     ax, [bp].Arg3           ; get the stop bits
        mov     cl, Stops               ; get the shift factor
        shl     ax, cl                  ; shift it into place
        and     ax, mask Stops          ; mask the stop bits
        or      bl, al                  ; OR it into RsInitRecord

        mov     ax, [bp].Arg4           ; get the parity
        mov     cl, Parity              ; get the shift factor
        shl     ax, cl                  ; shift it into place
        and     ax, mask Parity         ; mask the parity
        or      bl, al                  ; OR it into RsInitRecord

        mov     ax, [bp].Arg5           ; get the word length
        mov     cl, WLen                ; get the shift factor
        shl     ax, cl                  ; shift it into place
        and     ax, mask WLen           ; mask the length
        or      al, bl                  ; get the rest of RsInitRecord
;
; OK......now:
;       AH = 0
;       AL = initialization bit pattern
;       DX = comm number (0 or 1)
;
        int     RS232_INT               ; let BIOS do it

; we have to do 57600/19200/38400 baud manually...

        mov     ax, [bp].Arg2
        cmp     ax, 8
        jb      no_special
        cmp     ax, 9
        je      do_38400
        cmp     ax, 10
        je      do_57600
	cmp	ax, 11
	je	do_115200

; do 19200
        mov     bl, 6
        jmp short set_HS

do_38400:
        mov     bl, 3
        jmp short set_HS

do_57600:
        mov     bl, 2
	jmp short set_HS

do_115200:
	mov	bl, 1

set_HS: mov     dx, RS_Base
        add     dx, 3
        in      al, dx
        or      al, 10000000b
        out     dx, al
        mov     dx, RS_Base
	mov	al, bl
	out	dx, al
        mov     al, 0
        inc     dx
        out     dx, al
        inc     dx
        inc     dx
        in      al, dx
        and     al, 01111111b
        out     dx, al

;
; Set up the ring buffer pointers, our interrupt vector,
; set the UART to interrupt on data available, then set
; the interrupt controller to handle the UART requests.
;
no_special:
        mov     Head, 0                 ; set the head...
        mov     Tail, 0                 ;...and the tail pointers

        push    es
        sub     ax, ax
        mov     es, ax                  ; point to the INT vectors
        mov     bx, 1                   ; preset it
        mov     cx, CommX
        and     cx, 1
        sub     bx, cx                  ; COMM1 = 1, COMM2 = 0, 3 = 1, 4 = 0
        add     bx, 0bh                 ; add in the base INT number
        shl     bx, 1                   ; x4 to adjust it to point to the INT
        shl     bx, 1
        mov     ax, es:[bx]             ; get the offset
        mov     OldRS_Off, ax           ; save it
        mov     ax, es:[bx + Word]      ; get the segment
        mov     OldRS_Seg, ax           ; save it

        lea     ax, _Service_RS         ; point to the interrupt routine
        pushf                           ; save the flag status
        cli                             ; no interrupts during this
        mov     es:[bx], ax             ; install the offset
        mov     es:[bx + Word], cs      ; install the vector
        popf                            ; restore int. flag to entry status
        pop     es
;
; Enable UART to generate interrupts on input.
;
        mov     dx, RS_Base             ; get the port address
        inc     dx                      ; InterruptEnableRegister
        mov     al, 1                   ; enable data available mask
        out     dx, al                  ; RS232 data in is now interrupt driven
;
; Set the OUT2 bit in Modem Control Register (enables interrupt line)
; And RTS
;
        mov     dx, RS_Base             ; get the port address
        in      al, dx                  ; read the serial port to clear it
        add     dx, 4                   ; ModemControlRegister
        in      al, dx                  ; get contents of ModemControlRegister
        or      al, 10                  ; set the OUT2 bit (enables interrupts)
                                        ; and rts
        out     dx, al                  ; Interrupts will be handled properly

;
; Enable the interrupt controller to respond to the UART requests.
;
        mov     bx, 1                   ; preset it
        mov     ax, CommX
        and     ax, 1
        sub     bx, ax                  ; COMM1 = 1, COMM2 = 0,  3 = 1, 4 = 0
        inc     bx                      ; set bit 0 or 1
        shl     bx, 1                   ; shift it out to bit 3 or 4
        shl     bx, 1
        shl     bx, 1
        not     bl                      ; invert the mask

        pushf                           ; save the flag status
        cli                             ; no interrupts when setting controller
        in      al, 21h                 ; get current status
        and     al, bl                  ; mask out the COMM bit
        out     21h, al                 ; reset the controller
        popf                            ; restore int. flag to entry status

        pop     bp
        ret
_InitRS         EndP

;******************************************************************************
;
; Name:                 _DeInitRS
;
; Synopsis:     void = DeInitRS();
;
; Description:  Restores the saved INT vector set during _InitRS,
;               resets the UART not to generate interrupts, and
;               resets the interrupt controller not to respond to
;               the UART.
;
;******************************************************************************
_DeInitRS       Proc    Far
        mov     dx, RS_Base             ; get the port address
	or	dx, dx
	jz	dirs_ret

        inc     dx                      ; InterruptEnableRegister
        sub     al, al                  ; disable all RS232 interrupts
        out     dx, al                  ; its done
;
; Turn off OUT2 bit in Modem Control Register (disables interrupt line) 
;
        mov     dx, RS_Base             ; get the port address
        add     dx, 4                   ; ModemControlRegister
        in      al, dx                  ; get contents of ModemControlRegister
        and     al, 0F7h                ; set the OUT2 bit (disables interrupts)
        out     dx, al                  ; Interrupts will be disabled

        mov     bx, 1                   ; preset it
        mov     cx, CommX
        and     cx, 1
        sub     bx, cx                  ; COMM1 = 1, COMM2 = 0, 3 = 1, 4 = 0
        inc     bx                      ; set bit 0 or 1
        shl     bx, 1                   ; shift it out to bit 3 or 4
        shl     bx, 1
        shl     bx, 1

        pushf                           ; save the flag status
        cli                             ; no interrupts when setting controller
        in      al, 21h                 ; get current status
        or      al, bl                  ; force the COMM bit on for disable
        out     21h, al                 ; reset the controller
        popf                            ; restore int. flag to entry status

        push    es
        sub     ax, ax
        mov     es, ax                  ; point to the INT vectors
        mov     bx, 1                   ; preset it
        mov     cx, CommX
        and     cx, 1
        sub     bx, cx                  ; COMM1 = 1, COMM2 = 0, 3 = 1, 4 = 0
        add     bx, 0bh                 ; add in the base INT number
        shl     bx, 1                   ; adjust it to point to the INT
        shl     bx, 1
        mov     ax, OldRS_Off           ; get the offset
        mov     cx, OldRS_Seg           ; get the segment
        pushf                           ; save the flag status
        cli                             ; no interrupts during this
        mov     es:[bx], ax             ; reset the offset
        mov     es:[bx + Word], cx      ; reset the segment
        popf                            ; restore int. flag to entry status
        pop     es

	mov	RS_Base, 0
dirs_ret:
	ret
_DeInitRS       EndP

;******************************************************************************
;
; Name:                 _FlushRS
;
; Synopsis:     void FlushRS();
;
; Description:  Reset the RS232 input buffer to empty.
;
;******************************************************************************
_FlushRS        Proc    Far
        sub     ax, ax                  ; clear it to zero
        mov     Head, ax                ; clear the head
        mov     Tail, ax                ; clear the tail
        ret
_FlushRS        EndP

;******************************************************************************
;
; Name:                 _StatRS
;
; Synopsis:     Status = StatRS();
;
;               int     Status;                 current COMM status
;
; Description:  If Status = 0, then the ring buffer is empty, else
;               there is at least one character waiting.
;
;******************************************************************************
_StatRS         Proc    Far
        mov     ax, Head                ; get the head pointer
        sub     ax, Tail                ; if they are the same, its empty
        ret
_StatRS         EndP

;******************************************************************************
;
; Name:                 _GetRS
;
; Synopsis:     Character = GetRS();
;
;               int     Character;              byte from RS232 RX buffer
;
; Description:  Gets the next character from the input ring buffer.
;
; Cautions:     If there is nothing there, it will wait for one.
;
;******************************************************************************
_GetRS  Proc    Far
	mov	dx, RS_Base
	or	dx, dx
	jz	Exit_GetRS

wait_rs:
	mov	ax, Head		; get the head pointer
        sub     ax, Tail                ; if they are the same, its empty
	jz	wait_rs 		; wait for something

        mov     bx, Tail                ; get the tail pointer
        mov     al, InputBuffer[bx]     ; get whats there
        sub     ah, ah                  ; clear the MSB
        inc     bx                      ; bump the pointer
        cmp     bx, RING_BUF_SIZE       ; about to wrap?
        jne     SetTheTail              ; nope

        sub     bx, bx                  ; reset for the wrap

SetTheTail:
        mov     Tail, bx                ; update the pointer

Exit_GetRS:
        ret
_GetRS  EndP

;******************************************************************************
;
; Name:                 _PutRS
;
; Synopsis:     void = PutRS(Character);
;
;               char    Character;              byte for RS232 TX buffer
;
; Description:  Stuffs a character into THRE.
;
;******************************************************************************
_PutRS  Proc    Far
	mov	dx, RS_Base		; get the base address
	or	dx, dx
	jz	prs_ret

        push    bp
        mov     bp, sp

        add     dx, 5                   ; line status register

THRE_Status:
        in      al, dx                  ; get the line status
        test    al, 20h                 ; THRE empty?
        jz      THRE_Status             ; not yet

        inc     dx

        cmp     checkCTS, 0
        je      no_CTS

CTS_Status:
        in      al, dx
        test    al, 10h
        jz      CTS_Status

no_CTS:
        sub     dx, 6                   ; back to the base register
        mov     ax, [bp].Arg1           ; get the character
        out     dx, al                  ; send it out

        pop     bp
prs_ret:
	ret
_PutRS  EndP

;******************************************************************************
;
; Name:                 _DtrRS
;
; Synopsis:     void = DtrRS(Dtr);
;
;               int     Port;           0 = dtr off
;                                       1 = dtr on
;
; Description:  Sets bit 0:DTR of ModemControlRegister according to Dtr.
;
;******************************************************************************
_DtrRS  Proc    Far
        mov     dx, RS_Base             ; get the port address
	or	dx, dx
	jz	drs_ret

        push    bp
        mov     bp, sp
;
; Change the DTR bit in Modem Control Register
;
        add     dx, 4                   ; ModemControlRegister
        in      al, dx                  ; get contents of ModemControlRegister

        cmp     [bp].Arg1, 0            ; Dtr on? or Off.
        je      DtrOff                  ; Off, jump to DtrOff

        or      al, 1                   ; DtrOn, turn bit 0:DTR on
        jmp     short EndOfDtrRS        ; Jump to set bit

DtrOff:
        and     al, 0FEh                ; DtrOff, turn bit 0:DTR off

EndOfDtrRS:
        out     dx, al 

        pop     bp
drs_ret:
	ret
_DtrRS  EndP

;******************************************************************************
;
; Name:                 _CarrStatRS
;
; Synopsis:     Status = CarrStatRS();
;
;               int     Status;                 current COMM carrier status
;
; Description:  If Status = 0, then carrier is not present
;               else there is carrier detected.
;
;******************************************************************************
_CarrStatRS  Proc    Far
        mov     dx, RS_Base             ; get the port address
	or	dx, dx
	jz	csrs_ret

;	push	bp
;	mov	bp, sp

        add     dx, 6                   ; ModemStatusRegister
        in      al, dx                  ; get contents of ModemStatusRegister

        and     al, 080h                ; mask out all but bit 7:CD bit
        cmp     al, 080h                ; test cd bit
        mov     ax, 0                   ; assume status is zero

        jne     EndOfCarrStatRS         ; don't set AX 
	inc	ax

EndOfCarrStatRS:
;	pop	bp
csrs_ret:
        ret
_CarrStatRS  EndP

;******************************************************************************
;
; Name:                 _RingStatRS
;
; Synopsis:     Status = RingStatRS();
;
;               int     Status;                 current COMM ring status
;
; Description:  If Status = 0, then ring indicator bit is false
;               else ring indicator bit is true.
;
;******************************************************************************
_RingStatRS  Proc    Far
        mov     dx, RS_Base             ; get the port address
	or	dx, dx
	jz	rsrs_end

        push    bp
        mov     bp, sp

        add     dx, 6                   ; ModemStatusRegister
        in      al, dx                  ; get contents of ModemStatusRegister

        and     al, 040h                ; mask out all but bit 6:RI bit
        cmp     al, 040h                ; test ri bit
        mov     ax, 0                   ; assume status is zero

        jne     EndOfRingStatRS         ; don't set AX 
        mov     ax, 1

EndOfRingStatRS:
        pop     bp
rsrs_end:
        ret
_RingStatRS  EndP


        SUBTTL  RS232 interrupt handler
        PAGE

;******************************************************************************
;               R S 2 3 2   I N T E R R U P T   H A N D L E R
;******************************************************************************

_Service_RS     Proc    Far
        sti                             ; interrupts back on
        push    ds
        push    dx
        push    cx
        push    bx
        push    ax

        mov     ax, DGROUP
        mov     ds, ax                  ; get pointer to our data

        mov     dx, RS_Base             ; base address of COMM port
        in      al, dx                  ; get the character
        mov     bx, Head                ; point to the head of the buffer
        mov     cx, bx                  ; get the offset
        inc     cx                      ; bump it
        cmp     cx, RING_BUF_SIZE       ; about to wrap?
        jne     NoWrapYet               ; nope

        sub     cx, cx                  ; reset for the wrap

NoWrapYet:
        cmp     cx, Tail                ; about to overflow?
        je      Short EndOfRS232Service ; yup, get out

        mov     InputBuffer[bx], al     ; save the character
        mov     Head, cx                ; reset the buffer head

EndOfRS232Service:
        mov     al, 20h                 ; EOI command
        out     20h, al                 ; tell the interrupt controller

        pop     ax
        pop     bx
        pop     cx
        pop     dx
        pop     ds
        iret
_Service_RS     EndP

;******************************************************************************
;
; Name:                 _StatCON
;
; Synopsis:     Status = StatCON();
;
;               int     Status;                 current CON status
;
; Description:  If Status = 0, the keyboard buffer is empty, else not
;
;******************************************************************************
_StatCON        Proc    Far
        mov     ah, 1                   ; read keyboard status
        int     16h                     ; BIOS keyboard service

	jnz	Char			; is there a character?

	xor	ax, ax			; no character, return zero
        ret
Char:
;==========================================================================
        mov     ax, 1                   ; character, return non zero
        ret
;========================================================================== 
_StatCON	EndP

;******************************************************************************
;
; Name:                 _GetCON
;
; Synopsis:     Character = GetCON();
;
;               int     Character;              int from keyboard buffer
;
; Description:  Gets the next character and scan code from the keyboard buffer.
;               Is not affected by Ctrl-C, Ctrl-S etc.
;
; Cautions:     If there is nothing there, it will wait for one.
;
;******************************************************************************
_GetCON         Proc    Far
        mov     ah, 0                   ; read keyboard character
        int     16h                     ; BIOS keyboard service

        or      al, al                  ; extended character?
        je      Extended                
        xor     ah, ah                  ; no, zero high byte

Extended:
        ret
_GetCON         EndP

ifdef GOODBYE
;******************************************************************************
;
; Name:                 _PutCON
;
; Synopsis:     void = PutCON(Character);
;
;               char    Character;              byte to be written to display
;
; Description:  Writes character but is not affected by Ctrl-C, Ctrl-S etc.
;
;******************************************************************************
_PutCON         Proc    Far
        push    bp
        mov     bp, sp

        mov     ax, [bp].Arg1           ; character to display
        mov     ah, 0eh                 ; write text in teletype mode
        mov     bh, 0                   ; page zero
        int     10h                     ; BIOS video service

        pop     bp
        ret     
_PutCON         EndP
endif

_Text   Ends
        End
