_STRUCTURED PROGRAMMING COLUMN_ by Jeff Duntemann [LISTING ONE] {------------------------------------------------------------------------} { INTTERM } { by Jeff Duntemann } { Turbo Pascal V5.0 or later } { Last update 6/2/91 } { This is an interrupt-driven "dumb terminal" program for the PC. It } { the use of Turbo Pascal's INTERRUPT procedures, and in a lesser fashion} { the use of serial port hardware. It can be set to use either COM1: or } { COM2: by setting the COMPORT constant to 1 (for COM1) or 2 (for COM2) } { as appropriate and recompiling. } {------------------------------------------------------------------------} PROGRAM INTTERM; USES DOS,CRT; CONST COMPORT = 2; { 1 = COM1: 2 = COM2: } COMINT = 13-COMPORT; { 12 = COM1: (IRQ4) 11 = COM2: (IRQ3) } COMBASE = $2F8; PORTBASE = COMBASE OR (COMPORT SHL 8); { $3F8 for COM1: $2F8 for COM2: } { 8250 control registers, masks, etc. } RBR = PORTBASE; { 8250 Receive Buffer Register } THR = PORTBASE; { 8250 Transmit Holding Register } LCR = PORTBASE + 3; { 8250 Line Control Register } IER = PORTBASE + 1; { 8250 Interrupt Enable Register } MCR = PORTBASE + 4; { 8250 Modem Control Register } LSR = PORTBASE + 5; { 8250 Line Status Register } DLL = PORTBASE; { 8250 Divisor Latch LSB } DLM = PORTBASE + 1; { 8250 Divisor Latch MSB } DLAB = $80; { 8250 Divisor Latch Access Bit } BAUD300 = 384; { Value for 300 baud operation } BAUD1200 = 96; { Value for 1200 baud operation } NOPARITY = 0; { Comm format value for no parity } BITS8 = $03; { Comm format value for 8 bits } DTR = $01; { Value for Data Terminal Ready } RTS = $02; { value for Ready To Send } OUT2 = $08; { Bit that enables adapter interrupts } BUFFSIZE = 1023; { 8259 control registers, masks, etc. } OCW1 = $21; { 8259 Operation Control Word 1 } OCW2 = $20; { 8259 Operation Control Word 2 } { The 8259 mask bit is calculated depending on } { which serial port is used... } { $10 for COM1: (IRQ4); $08 for COM2: (IRQ3): } IRQBIT = $20 SHR COMPORT; TYPE CircularBuffer = ARRAY[0..BUFFSIZE] OF Char; { Input buffer } VAR Quit : Boolean; { Flag for exiting the program } HiBaud : Boolean; { True if 1200 baud is being used } KeyChar : Char; { Character from keyboard } CommChar : Char; { Character from the comm port } Divisor : Word; { Divisor value for setting baud rate } Clearit : Byte; { Dummy variable } Buffer : CircularBuffer; { Our incoming character buffer } LastRead, { Index of the last character read } LastSaved : Integer; { Index of the last character stored } NoShow : SET OF Char; { Don't show characters set } OldVector : Pointer; { Global storage slot for the old } { interrupt vector } PROCEDURE EnableInterrupts; INLINE($FB); {->>>>Incoming (Interrupt Service Routine)<<<<--------------------------------} { This is the ISR (interrupt Service Routine) for comm ports. DO NOT call this} { routine directly; you'll crash hard. The only way Incoming takes control is } { when a character coming in from modem triggers a hardware interrupt from } { serial port chip, the 8250 UART. Note that the register pseudo-parameters } { are not needed here, and you could omit them. However, omitting them doesn't} { really get you any more speed or reliability. } {-----------------------------------------------------------------------------} PROCEDURE Incoming(Flags,CS,IP,AX,BX,CX,DX,SI,DI,DS,ES,BP : Word); INTERRUPT; BEGIN { First we have to enable interrupts *at the CPU* during the ISR: } EnableInterrupts; { The first "real work" we do is either wrap or increment the index of the } { last character saved. If index is "topped out" at buffer size (here, } { 1023) we force it to zero. This makes the 1024-byte buffer "circular," } { in that once the index hits the end, it rolls over to beginning again. } IF LastSaved >= BUFFSIZE THEN LastSaved := 0 ELSE Inc(LastSaved); { Next, we read the actual incoming character from the serial port's} { one-byte holding buffer: } Buffer[LastSaved] := Char(Port[RBR]); { Finally, we must send a control byte to the 8259 interrupt } { controller, telling it that the interrupt is finished: } Port[OCW2] := $20; { Send EOI byte to 8259 } END; {$F+} PROCEDURE IntTermExitProc; BEGIN Port[IER] := 0; { Disable interrupts at 8250 } Port[OCW1] := Port[OCW1] OR IRQBIT; { Disable comm int at 8259 } Port[MCR] := 0; { Bring the comm line down } SetIntVec(COMINT,OldVector); { Restore previously saved vector } END; {$F-} PROCEDURE SetupSerialPort; BEGIN LastRead := 0; { Initialize the circular buffer pointers } LastSaved := 0; Port[IER] := 0; { Disable 8250 interrupts while setting them up } GetIntVec(ComInt,OldVector); { Save old interrupt vector } ExitProc := @IntTermExitProc; { Hook exit proc into chain } SetIntVec(ComInt,@Incoming); { Put ISR address into vector table } Port[LCR] := Port[LCR] OR DLAB; { Set up 8250 to set baud rate } Port[DLL] := Lo(Divisor); { Set baud rate divisor } Port[DLM] := Hi(Divisor); Port[LCR] := BITS8 OR NOPARITY; { Set word length and parity } Port[MCR] := DTR OR RTS OR OUT2; { Enable adapter, DTR, & RTS } Port[OCW1] := Port[OCW1] AND (NOT IRQBIT); { Turn on 8259 comm ints } Clearit := Port[RBR]; { Clear any garbage from RBR } Clearit := Port[LSR]; { Clear any garbage from LSR } Port[IER] := $01; { Enable 8250 interrupt on received character } END; FUNCTION InStat : Boolean; BEGIN IF LastSaved <> LastRead THEN InStat := True ELSE InStat := False; END; FUNCTION InChar : Char; { Bring in the next character } BEGIN IF LastRead >= BUFFSIZE THEN LastRead := 0 ELSE Inc(LastRead); InChar := Buffer[LastRead]; END; PROCEDURE OutChar(Ch : Char); { Send a character to the comm port } BEGIN Port[THR] := Byte(Ch) { Put character ito Transmit Holding Register } END; PROCEDURE ShowHelp; BEGIN Writeln('>>>IntTerm by Jeff Duntemann >>>>>>>>>>>>>>>>>>>>>>>>>>>'); Writeln(' Defaults to 1200 Baud; to run at 300 Baud'); Writeln(' invoke with "300" after "INTTERM" on the command line.'); Writeln; Writeln(' Commands:'); Writeln(' ALT-X: Exits to DOS'); Writeln(' CTRL-Z: Clears the screen'); Writeln('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'); END; {>>>>>INTTERM MAIN PROGRAM<<<<<} BEGIN HiBaud := True; { Defaults to 1200 baud; if "300" is } Divisor := BAUD1200; { entered after "INTTERM" on the command } IF ParamCount > 0 THEN { line, then 300 baud is used instead. } IF ParamStr(1) = '300' THEN BEGIN HiBaud := False; Divisor := BAUD300 END; DirectVideo := True; NoShow := [#0,#127]; { Don't display NUL or RUBOUT } SetupSerialPort; { Set up serial port & turn on interrupts } ClrScr; Writeln('>>>INTTERM by Jeff Duntemann'); Quit := False; { Exit INTTERM when Quit goes to True } REPEAT IF InStat THEN { If a character comes in from the modem } BEGIN CommChar := InChar; { Go get character } CommChar := Char(Byte(CommChar) AND $7F); { Mask off high bit } IF NOT (CommChar IN NoShow) THEN { If we can show it,} Write(CommChar) { then show it! } END; IF KeyPressed THEN { If a character is typed at the keyboard } BEGIN KeyChar := ReadKey; { First, read the keystroke } IF KeyChar = Chr(0) THEN { We have an extended scan code here } BEGIN KeyChar := ReadKey; { Read second half of extended code } CASE Ord(KeyChar) OF 59 : ShowHelp; { F1 : Display help screen } 45 : Quit := True; { Alt-X: Exit IntTerm } END { CASE } END ELSE CASE Ord(KeyChar) OF 26 : ClrScr; { Ctrl-Z: Clear the screen } ELSE OutChar(KeyChar) END; { CASE } END UNTIL Quit END.