home *** CD-ROM | disk | FTP | other *** search
- {$K-}
- {$R-}
- Program NewAsyInterrupt ;
-
- (*****************************************************************************
-
- First Release : March 10, 1986 by Gene Harris
-
- This program is designed to allow your PC, XT, AT or jr to have access to
- a full support RS-232 set of routines written in Turbo Pascal. The down
- load archive files should contain two versions of this program. The first
- program is NewAsyInterrupt (this routine). This program is written com-
- pletely in Turbo Pascal. I suspect it will only handle baud rates of about
- 2400. It has only been tested to 1200 baud.
-
- The second routine is AsyInlInterrupt. The second program has parts of
- the interrupt service routine coded in inline assembler code. The source
- code for the interrupt service routines is included as comments. The second
- version has been tested to 4800 baud and will probably handle 9600 baud.
-
- Both routines have been tested on an IBM PCjr equipped with an NEC V20,
- an XT clone running at six and eight MHz and an 8 MHz PC AT clone
- successfully. The OPENCOM routine must be changed to run with a jr.
- Appropriate changes and intructions for implementing these changes is
- included as comments.
-
- This code is copyrighted by M. Eugene Harris, Jr. It is released to the
- public domain, and may not be used for commercial gain. The program is
- user supported, and updates may be obtained from Remark BBS, in Oklahoma
- City 405 728-2463.
-
- A 3rd version of the program will be released in April which will
- automatically support XON/XOFF from the interrupt routine, as well as
- incorporate several enhanced string instructions. The program will also
- support the Intel 80188 and NEC-V20.
-
- *****************************************************************************
-
- Routines included in this code:
-
- OpenCom (icomport,ibaudrate,idatabits,istopbits:integer;
- iparity:char) ;
-
- icomport : 1 or 2 only, no other ports supported.
-
- ibaudrate : 110, 150, 300, 600, 1200, 2400, 4800, 9600 baud supported.
-
- idatabits : 5, 6, 7, 8 data bits supported.
-
- istopbits : 1 or 2 stopbits supported.
-
- iparity : 'E'ven, 'O'dd, 'N'one, 'M'ark, 'S'pace are supported.
-
- Purpose of the routine: User supplied communication port values are
- converted to the proper set values used by
- the asynch routines. Sets are used internally
- to avoid var conflicts and improve speed.
- The routine returns an OpenError = TRUE if
- the specified COM port does not exist. Therefore,
- the user code needs to check OpenError before
- preceding to the next logical phase.
-
- *****************************************************************************
-
- Break - No Parameters.
-
- Purpose : drop DTR to effectively hang up the modem.
-
- *****************************************************************************
-
- Purge - No Parameters.
-
- Purpose : Purge the circular queues.
- Reset the 8250 interrupt control register.
-
- *****************************************************************************
-
- RS232ChrAvailable : Boolean ;
-
- Purpose : Returns true if there are characters in the RS232 input queue
- otherwise returns false.
-
- ****************************************************************************
-
- Function ReadChar : char ;
-
- Purpose : Returns the next character in the RS232 input buffer. If no
- characters are present, the routine returns a null byte. The
- RS232ChrAvailable routine should ALWAYS be checked before
- trying to read a character, otherwise you will not be able
- to tell if the null returned is correct. The routine is
- designed to be used in the following format:
-
- begin
- auxoutptr := ofs(readchar) ; { Install device driver }
- ....
- program body
- ....
- if RS232ChrAvailable then read( aux, byte or char) ;
- ....
- rest of program
-
- ***************************************************************************
-
- Procedure WriteChar ;
-
- Purpose : Writes the next character into the RS232 character buffer.
- The Asynch_Interrupt routine will output the characters in
- the queue as THRE interrupts are generated by the 8250.
- This routine is intended to be a device driver for the logical
- devices USR or AUX.
-
- EXAMPLE :
- begin
- auxinptr := ofs(WriteChar) ; { Install Device Driver. }
- . . . .
- . . . .
- Write (aux, variable) ; { Put in RS232 output queue }
-
- ***************************************************************************
-
- Caveats : The Asynch_interrupt routine is written in pascal. It is fast
- enough to handle 1200 baud, and may handle 2400 baud. You will
- not get 4800 or 9600 baud support with this routine. If you
- need high speed support, use the companion routine ASYINL00.PAS
- which handles 8250 interrupt processing with inline assembler
- routines.
-
- The buffer input routine always checks that the variable
- LSRstat is zero. The LSRstat (Line Status Register) is utilized
- to show buffer overflow on input. Therefore, your program
- must monitor LSRstat to determine if a buffer overflow has
- occurred. Bit 1 will be set if buffer overflow has occured.
-
- EXAMPLE : if LSRstat <> 0 then begin
- writeln('Line Status Error: ',LSRstat) ;
-
- . . . .
- Handle LSR error condition. . .
- . . . .
-
- { Reset LSRstat so that transmission can
- begin again. Turn of bit 1. }
-
- LSRstat := LSRstat and $FD ;
- end ;
-
- *)
-
- Type
- tComPort = (Com1, Com2);
- tBaud = (b110, b150, b300, b600, b1200, b2400, b4800, b9600);
- tParity = (pSpace, pOdd, pMark, pEven, pNone);
- tDatabits = (d5, d6, d7, d8);
- tStopbits = (s1, s2);
-
- tSaveVector = record { Saved Com interrupt vector }
- IP: integer;
- CS: integer;
- end;
-
- regpak = record case integer of
- 1: (AX, BX, CX, DX, BP, SI, DI, DS, ES, FLAGS : integer) ;
- 2: (al,ah, bl,bh, cl,ch, dl,dh : byte) ;
- end;
-
- Const
- ourDS: integer = -1; { Will be init to contents of our DS
- for later use in Interrupt routine }
-
- { ASynch Interrupt Masks }
- imlist: array[Com1..Com2] of integer = ($EF, $F7);
-
- { ASynch hardware interrupt addresses }
- ivlist: array[Com1..Com2] of integer = ($000C, $000B);
-
- PICCMD = $20; { 8259 Priority Interrupt Controller }
- PICMSK = $21; { 8259 Priority Interrupt Controller }
- EOI = $20 ; { End of Interrupt command for 8259. }
- { Asynch base port addresses are
- in the ROM BIOS data area }
-
- var
- ComBaseAddr: array[Com1..Com2] of integer Absolute $0040:$0000;
-
- {
- Define a ring buffer for Asynch_Interrupt to write into
- and ReadCom to read from.
- }
- Input_Ring_Buffer : array[0..2048] of char;
- Input_Read_Ptr, { Index which ReadCom will next read from. }
- Input_Write_Ptr : integer ; { Index which Asynch_Interrupt will next }
- { write into. If readptr=writptr then }
- { the buffer is empty. }
-
- OutPut_Ring_Buffer : array[0..2048] of char ;
- OutPut_Read_Ptr, { Index which WriteCom will next input to. }
- OutPut_Write_Ptr : integer ;{ Index which Asynch_Interrupt will next }
- { read from. If readptr=writptr then the }
- { buffer is full. }
-
- LSRstat, { Line Status Reg at interrupt }
- MSRstat : byte ; { Modem Status Reg at interrupt. }
- ComSaveVec : tSaveVector ; { saved Async Interrupt vector }
- ComBase : integer ; { Opened Com port base address }
- ActiveComPort : tComPort ; { Opened Com }
- imvalue : integer ; { Interrupt Mask value in use }
- IERMask : byte ;
-
- { Define Equivalent Port Address Registers. }
-
- RBR_Port,
- THR_Port,
- IER_Port,
- IIR_Port,
- LCR_Port,
- MCR_Port,
- LSR_Port,
- MSR_Port,
- DLL_Port,
- DLM_Port : integer ;
-
- { Define Variables needed by Asynch_Interrupts procedure. }
-
- IIRreg,
- Int_Type : byte ;
- Temp : integer ;
- OpenError : boolean ;
- Max_output_buffer,
- Max_input_buffer : integer ;
-
- Procedure InstallInterrupt(IntVect: integer;
- Var SaveVector: tSaveVector);
- Var
- dosregs: regpak ;
-
- Begin
- inline($FA); { cli disable interrupts }
-
- With dosregs Do Begin
- ds := SaveVector.CS;
- dx := SaveVector.IP;
- ah := $25 ;
- al := IntVect ;
- MsDos(dosregs); { DOS function 25 - set vector }
- End;
- inline($FB); { sti re-enable ints }
- End;
-
- {********************************************************************}
- { }
- { This routine gets control upon an Asynch Interrupt }
- { We service all four interrupt types generated by the }
- { INS8250 chip: }
- { 1. Received character error or break. }
- { 2. Received data ready. }
- { 3. Transmit Hold Register Empty. }
- { 4. Modem Status Change }
- { }
- { In addition, circular queues are used for transmitting }
- { and receiveing data from the COM1 port. These queues }
- { can optionally be turned off if buffer overflow is }
- { detected. }
- { }
- {********************************************************************}
-
- Procedure asynch_interrupt ;
-
- begin
-
- Inline(
- $50 {push ax}
- /$53 {push bx}
- /$51 {push cx}
- /$52 {push dx}
- /$57 {push di}
- /$56 {push si}
- /$06 {push es}
- /$1E {push ds}
- {;}
- {; get the correct data segment}
- {;}
- /$2E/$A1/>OURDS {cs: mov ax, [>ourDS]}
- /$8E/$D8 {mov ds, ax}
- {;}
- {begin:}
- {;}
- /$8B/$16/>IIR_PORT {mov dx [>IIR_Port]}
- /$EC {in al, dx}
- /$A2/>IIRREG {mov [>IIRreg], al}
- {;}
- /$A0/>IIRREG {mov al, [>IIRreg]}
- /$A8/$01 {test al, $01}
- /$74/$03 {jz cont}
- /$E9/$9D/$00 {jmp noint}
- {;}
- {cont:}
- {;}
- /$24/$06 {and al, $06}
- /$A2/>INT_TYPE {mov [>int_type], al}
- {;}
- /$3C/$04 {cmp al, $04}
- /$75/$39 {jnz int0}
- {;}
- /$A1/>INPUT_WRITE_PTR {mov ax, [>input_write_ptr]}
- /$40 {inc ax}
- /$31/$D2 {xor dx, dx}
- /$F7/$36/>MAX_INPUT_BUFFER {div word ptr [>Max_input_buffer]}
- /$89/$16/>TEMP {mov [>temp], dx}
- {;}
- /$80/$3E/>LSRSTAT/$00 {cmp byte ptr [>lsrstat], $00}
- /$75/$D0 {jnz begin}
- {;}
- /$39/$16/>INPUT_READ_PTR {cmp word ptr [>input_read_ptr], dx}
- /$74/$16 {jz ovrflow}
- {;}
- /$8B/$16/>RBR_PORT {mov dx, [>rbr_port]}
- /$EC {in al, dx}
- /$8B/$1E/>INPUT_WRITE_PTR {mov bx, [>input_write_ptr]}
- /$88/$87/>INPUT_RING_BUFFER {mov [>input_ring_buffer][bx], al}
- {;}
- /$A1/>TEMP {mov ax, [>temp]}
- /$A3/>INPUT_WRITE_PTR {mov [>input_write_ptr], ax}
- /$E9/$B4/$FF {jmp near begin}
- {;}
- {ovrflow:}
- {;}
- /$80/$0E/>LSRSTAT/$02 {or byte ptr [>lsrstat], $02}
- /$E9/$AC/$FF {jmp near begin}
- {;}
- {int0:}
- {;}
- /$80/$3E/>INT_TYPE/$00 {cmp byte ptr [>int_type], $00}
- /$75/$08 {jnz int2}
- /$8B/$16/>MSR_PORT {mov dx, [>msr_port]}
- /$EC {in al, dx}
- /$A2/>MSRSTAT {mov [>msrstat], al}
- {;}
- {int2:}
- {;}
- /$80/$3E/>INT_TYPE/$02 {cmp byte ptr [>int_type], $02}
- /$75/$31 {jnz int6}
- {;}
- /$A1/>OUTPUT_WRITE_PTR {mov ax, [>output_write_ptr]}
- /$3B/$06/>OUTPUT_READ_PTR {cmp ax, [>output_read_ptr]}
- /$75/$0B {jnz wrtchr}
- {;}
- /$8B/$16/>IER_PORT {mov dx, [>ier_port]}
- /$EC {in al, dx}
- /$24/$FD {and al, $fd}
- /$EE {out dx, al}
- /$E9/$82/$FF {jmp near begin}
- {;}
- {wrtchr:}
- /$8B/$1E/>OUTPUT_WRITE_PTR {mov bx, [>output_write_ptr]}
- /$8A/$87/>OUTPUT_RING_BUFFER{mov al, [>output_ring_buffer][bx]}
- /$8B/$16/>THR_PORT {mov dx, [>thr_port]}
- /$EE {out dx, al}
- {;}
- /$43 {inc bx}
- /$89/$D8 {mov ax, bx}
- /$31/$D2 {xor dx, dx}
- /$F7/$36/>MAX_OUTPUT_BUFFER {div word ptr [>max_output_buffer]}
- /$89/$16/>OUTPUT_WRITE_PTR {mov [>output_write_ptr], dx}
- /$E9/$65/$FF {jmp near begin}
- {;}
- {int6:}
- {;}
- /$80/$3E/>INT_TYPE/$06 {cmp byte ptr [>int_type], $06}
- /$75/$0A {jnz alldone}
- {;}
- /$8B/$16/>LSR_PORT {mov dx, [>lsr_port]}
- /$EC {in al, dx}
- /$24/$1E {and al, $1e}
- /$A2/>LSRSTAT {mov [>lsrstat], al}
- {;}
- {alldone:}
- {;}
- /$E9/$51/$FF {jmp near begin}
- {;}
- {noint:}
- {;}
- /$B0/$20 {mov al, $20}
- /$E6/$20 {out $20, al}
- /$1F {pop ds}
- /$07 {pop es}
- /$5E {pop si}
- /$5F {pop di}
- /$5A {pop dx}
- /$59 {pop cx}
- /$5B {pop bx}
- /$58 {pop ax}
- /$89/$EC {mov sp, bp}
- /$5D {pop bp}
- /$FB {sti}
- /$CF {iret}
- );
-
- End;
-
- { Open COM1 or COM2, a la Basic }
-
- Procedure OpenCom(ComPort, Baud, Databits, Stopbits : integer ;
- Parity : char ) ;
-
- Const
-
- { Define addresses for the various Async card registers. }
-
- RBR = $00; { xF8 Receive Buffer Register }
- THR = $00; { xF8 Transmitter Holding Register }
- IER = $01; { xF9 Interrupt Enable Register }
- IIR = $02; { xFA Interrupt Identification Register }
- LCR = $03; { xFB Line Control Register }
- MCR = $04; { xFC Modem Control Register }
- LSR = $05; { xFD Line Status Register }
- MSR = $06; { xFE Modem Status Register }
- DLL = $00; { xF8 Divisor Latch Least Significant }
- DLM = $01; { xF9 Divisor Latch Most Significant }
-
- baudcode: array[b110..b9600] of integer =
- ($417, $300, $180, $C0, $60, $30, $18, $0C);
- paritycode: array[pSpace..pNone] of byte =
- ($38, $08, $28, $18, $00);
- databitscode: array[d5..d8] of byte = ($00, $01, $02, $03);
- stopbitscode: array[s1..s2] of byte = ($00, $04);
-
- Var
- LCRreg,
- errclear : byte ;
- baudindex : tbaud ;
- Begin
-
- OpenError := FALSE ; { Default no error on open. }
- { Init the Const "ourDS" for use by
- the Async_Interrupt routine }
- ourDS := DSEG ;
- { Swap Com interrupt vector }
- With ComSaveVec Do Begin
- CS := CSEG;
- IP := OFS(Asynch_Interrupt);
- End;
-
- if Comport = 2 then ActiveComPort := Com2 else ActiveComPort := Com1 ;
-
- InstallInterrupt(ivlist[ActiveComPort], ComSaveVec);
-
- imvalue := imlist[ActiveComPort] ; { Select Interrupt Mask val }
-
- ComBase := ComBaseAddr[ActiveComPort]; { Select Input Port }
- if ComBase = 0 then OpenError := TRUE ;
-
- Input_Read_Ptr := 0 ; { Init buffer pointers }
- Input_Write_Ptr := 0 ;
- OutPut_Read_Ptr := 0 ;
- OutPut_Write_Ptr := 0 ;
-
- RBR_Port := RBR + ComBase ;
- THR_Port := THR + ComBase ;
- IER_POrt := IER + ComBase ;
- IIR_Port := IIR + ComBase ;
- LCR_Port := LCR + ComBase ;
- MCR_Port := MCR + ComBase ;
- LSR_Port := LSR + ComBase ;
- MSR_Port := MSR + ComBase ;
- DLL_Port := DLL + ComBase ;
- DLM_Port := DLM + ComBase ;
-
- inline ($FA) ;
- {
- Reset any pending error conditions and turn off DLAB.
- }
- PORT[PICMSK] := PORT[PICMSK] or ($FF - imvalue);
- PORT[IER_Port] := 0 ; { Disable Data Avail interrupt }
- Port[MCR_Port] := 0 ;
- Port[LCR_Port] := Port[LCR_Port] and $7F ;
- errclear := PORT[LSR_Port] ;
- errclear := PORT[RBR_Port] ;
- errclear := PORT[MSR_Port] ;
-
- {
- Set Baud Rate Divisor Registers and the Line Control Register
- }
- LCRreg := $80; { Set Divisor Latch Access Bit in LCR }
-
- case Parity of
- 'S' : LCRreg := LCRreg or paritycode[pSpace] ;
- 'O' : LCRreg := LCRreg or paritycode[pOdd] ;
- 'M' : LCRreg := LCRreg or paritycode[pMark] ;
- 'E' : LCRreg := LCRreg or paritycode[pEven] ;
- 'N' : LCRreg := LCRreg or paritycode[pNone] ;
- else LCRreg := LCRreg or paritycode[pNone] ;
- end ; { case }
-
- case databits of
- 5 : LCRreg := LCRreg or databitscode[d5] ;
- 6 : LCRreg := LCRreg or databitscode[d6] ;
- 7 : LCRreg := LCRreg or databitscode[d7] ;
- 8 : LCRreg := LCRreg or databitscode[d8] ;
- else LCRreg := LCRreg or databitscode[d8] ;
- end ; { case }
-
- case stopbits of
- 1 : LCRreg := LCRreg or stopbitscode[s1] ;
- 2 : LCRreg := LCRreg or stopbitscode[s2] ;
- else LCRreg := LCRreg or stopbitscode[s1] ;
- end ; { case }
-
- PORT[LCR_Port] := LCRreg; { Set Parity, Data and Stop Bits
- and set DLAB }
- baudindex := b1200 ;
- if baud = 110 then baudindex := b110 ;
- if baud = 150 then baudindex := b150 ;
- if baud = 300 then baudindex := b300 ;
- if baud = 600 then baudindex := b600 ;
- if baud = 1200 then baudindex := b1200 ;
- if baud = 2400 then baudindex := b2400 ;
- if baud = 4800 then baudindex := b4800 ;
- if baud = 9600 then baudindex := b9600 ;
-
- PORT[DLM_Port] := Hi(baudcode[Baudindex]); { Set Baud rate }
- PORT[DLL_Port] := Lo(baudcode[Baudindex]); { Set Baud rate }
- PORT[LCR_Port] := LCRreg and $7F ; { Reset DLAB }
-
- PORT[PICMSK] := PORT[PICMSK] and imvalue; { Enable ASynch Int }
- PORT[IER_Port] := $0F ; { Enable some interrupts }
- { Note: OUT2, despite documentation,
- MUST be ON, to enable interrupts }
- PORT[MCR_Port] := $0F; { Set RTS, DTR, OUT1, OUT2 }
- inline ($FB) ;
- PORT[PICCMD] := EOI ;
- LSRstat := 0 ; { Reset LSR status }
- MSRstat := 0 ;
- End;
-
-
- { Close any initialized COM }
-
- Procedure CloseCom;
- Begin
- { Disable Async interrupt }
- PORT[PICMSK] := PORT[PICMSK] or ($FF - imvalue);
- PORT[IER_Port] := 0 ; { Disable Data Avail interrupt }
- Port[MCR_Port] := 0 ;
- End;
-
- Procedure Break ;
-
- var
- LCRreg,
- DropDTR : byte ;
-
- begin
- LCRreg := Port[LCR_Port] ;
- DropDTR := (LCRreg and $7F) or $40 ;
- Port[LCR_Port] := DropDTR ;
- Delay (600) ;
- Port[LCR_Port] := LCRreg ;
- end ;
-
- Procedure Purge ;
-
- { Purpose : Purge all circular queues. }
-
- begin
- Input_Read_Ptr := 0; { Init buffer pointers }
- Input_Write_Ptr := 0;
- OutPut_Read_Ptr := 0 ;
- OutPut_Write_Ptr := 0 ;
-
- { Reset 8250 Interrupt Enable Registers. }
-
- Port [IER_Port] := Port [IER_Port] and $0F ;
- end ;
-
- Function ReadChar : char ;
-
- {
- This routine is intended to function as an AUX or USR logical character
- input device driver. Usage is AuxInPtr := ofs(Readchar) followed by
- the actual read operation read(AUX, var).
- }
-
- Begin
- if Input_Read_Ptr = Input_Write_Ptr then ReadChar := chr(0)
- else begin
- ReadChar := Input_Ring_Buffer[Input_Read_Ptr];
- inline ($FA) ;
- Input_Read_Ptr := (Input_Read_Ptr + 1) mod Max_Input_Buffer ;
- inline ($FB) ;
- end
- End;
-
- Function RS232ChrAvailable : boolean ;
-
- {
- This routine should be checked before reading a character from the RS232
- port, otherwise garbage will be returned and the buffer count messed up.
- }
-
- begin
- If Input_Read_Ptr = Input_Write_Ptr then RS232ChrAvailable := FALSE
- else RS232ChrAvailable := TRUE ;
- end ;
-
- Procedure WriteChar (ch:char) ;
-
- {
- This is the corresponding AuxOutPtr routine for use with write (aux, var).
- }
-
- begin
- OutPut_Ring_Buffer[OutPut_Read_Ptr] := ch ;
- inline ($FA) ;
- OutPut_Read_Ptr := (Output_Read_Ptr + 1) mod Max_Output_Buffer ;
- inline ($FB) ;
- if (IERMask and $02) = 0 then Port [IER_Port] := (Port [IER_Port] or $02) ;
- end ;
-
- {=============================================================================
- The following program is a VERY dumb glass CRT example program on how to use
- the asynchronos routines. I hope this routine is of some help.
- =============================================================================}
-
- label quit ;
-
- var
- ser_char,
- key_char :char ;
-
- begin
- Max_Output_Buffer := 1024 ;
- Max_Input_Buffer := 1024 ;
- AUXINPTR := ofs(Readchar) ; { Assign aux input device driver }
- AUXOUTPTR := ofs(Writechar) ; { Assign aux output device driver }
- OPENCOM (1,1200,8,1,'N') ; { Open the communications port. }
- if OpenError then goto quit ;
- writeln ('Port Opened') ;
- key_char := #32 ;
- repeat
- if keypressed then begin { Correct procedure for reading the }
- read (kbd, key_char) ; { keyboard and sending a character }
- if key_char <> #27 then write( aux, key_char) ;
- end ;
- if RS232ChrAvailable then begin { Correct procedure to read the RS232 }
- read(aux, ser_char) ; { communications port. }
- write (ser_char)
- end ;
- if LSRstat <> 0 then begin { Check LSRstat byte for overflow. }
- writeln('Line Status Error: ',LSRstat) ;
- LSRstat := 0 ;
- end ;
- until key_char = #27 ;
- quit :
- Break ;
- CloseCom ;
- end. LCRreg or databitscode[d7] ;
- 8 : LCRreg := LCRreg or databitscode[d8] ;
- else LCRreg :=