{--------------------------------------------------------------} { ITERM } { by Jeff Duntemann } { } { Interrupt-driven terminal program testbed } { } { V2.01 CP/M Turbo Pascal V2.0 } { Last Update 12/6/84 } { } {--------------------------------------------------------------} PROGRAM ITERM; CONST BAUD_PORT = $00; { SIO Baud rate control port on 820-II } CTRL_PORT = $06; { SIO control port on 820-II } DATA_PORT = $04; { SIO data port on 820-II } INT_LOC = $F800; { Address of SIO interrupt routine } INT_BASE = $FF00; { Base of mode 2 interrupt vector table } { RING BUFFER INTERRUPT SERVICE ROUTINE } { This routine is an interrupt routine for incoming serial port data. } { This routine executes each time the SIO chip fills up with a complete } { data character from the RS232 line. The character is put in a ring } { buffer and a buffer pointer incremented. The buffer and pointer are } { absolute variables that were previously defined at a particular place } { in high memory. } ROUTINE : ARRAY[0..29] OF BYTE = ($F5, { PUSH AF Save accumulator } $E5, { PUSH HL Save HL register } $F3, { DI Disable interrupts } $2A,$19,$F8, { LD HL,(LAST_SAVED) Get current count } $DB,$04, { IN A,(04H) Get the incoming character } $77, { LD HL,A Store it in the buffer } $23, { INC HL Bump insertion pointer } $CB,$64, { BIT 4,H Make ring } $28,$03, { JR Z,SIOINTL Relative jump 3 forward } $21,$00,$C3, { LD HL,$C300 over reload of buffer head } $22,$19,$F8, { LD (LAST_SAVED),HL SIOINTL: Save counter } $E1, { POP HL Restore HL register } $F1, { POP AL Restore accumulator } $FB, { EI Re-enable interrupts } $ED,$4D, { RETI Return from routine } $00,$C3, { DW $C300 LAST_SAVED } $00,$C3, { DW $C300 LAST_READ } $00); TYPE STRING80 = STRING[80]; CODE_BLOCK = ARRAY[0..63] OF BYTE; VECT_ARRAY = ARRAY[0..7] OF INTEGER; VAR I,J,K : INTEGER; CH : CHAR; NOSHOW : SET OF BYTE; PARITY : INTEGER; { 0=no parity; 1=odd parity; 2=even parity } PARITAG : ARRAY[0..2] OF STRING[8]; { Holds parity tags } OK : BOOLEAN; HIBAUD : BOOLEAN; { TRUE = using 1200 baud, else 300 baud } QUIT : BOOLEAN; { Flag for exiting the terminal loop } DUMMY : STRING80; { The following variables all support the interrupt-driven ring buffer: } INT_CODE : CODE_BLOCK ABSOLUTE INT_LOC; { Holds ring buffer serv. routine } INT_VECT : INTEGER ABSOLUTE $FF02; LAST_READ : INTEGER ABSOLUTE $F81B; { Offset of last char. read } LAST_SAVED : INTEGER ABSOLUTE $F819; { Offset of last char. saved } RINGPTR : ^CHAR ABSOLUTE $F81B; { ON TOP OF LAST_READ! } VECT_TBL : VECT_ARRAY ABSOLUTE $FF00; { SIO interrupt jump tbl } {<<>>} { This function is called AFTER function INSTAT has determined that a char } { is ready to be read from the ring buffer. The char at LAST_READ/RINGPTR } { (the two are the same) is assigned to INCHAR's function value. Then the } { value of LAST_READ is bumped by one via SUCC. If the value of LAST_READ } { is found to have gone over the high ring buffer boundary of $CFFF to $D000 } { then LAST_READ is "rolled over" to become $C300 (the low boundary of the } { buffer) again. When LAST_READ "catches up to" LAST_SAVED (by being =) the } { ring buffer is considered empty. } FUNCTION INCHAR : CHAR; BEGIN INCHAR := RINGPTR^; { Grab a character from the ring buffer } LAST_READ := SUCC(LAST_READ); { Increment the pointer; check bounds: } IF LAST_READ >= $D000 THEN LAST_READ := $C300 { Correct if it hits $D000 } END; {<<>>} { This function determines if there is a new character to be read from the } { ring buffer. There are two pointers into the ring buffer: LAST_SAVED, } { and LAST_READ. LAST_SAVED is the address of the last character placed } { into the buffer by the SIO interrupt service routine. LAST_READ is the } { address of the last character read from the ring buffer. When the two are } { equal, the last character read was the last character saved, so we know we } { have read all the characters that have been placed into the buffer. Only } { when LAST_SAVED gets "ahead" of LAST_READ must we read characters from the } { ring buffer again. These two pointers chase each other around and around } { the ring. As the ring buffer is just a hair over 3300 bytes long, } { LAST_SAVED can get WAAAAY ahead of LAST_READ before there's trouble in } { River City. On the other hand, if this ever happens, there will be no } { warning. Just trouble. } FUNCTION INSTAT : BOOLEAN; BEGIN IF LAST_SAVED <> LAST_READ THEN INSTAT := TRUE ELSE INSTAT := FALSE END; PROCEDURE OUTCHR(CH : CHAR); BEGIN { Loop until TBMT goes high } REPEAT I := PORT[CTRL_PORT] UNTIL (I AND $04) <> 0; PORT[DATA_PORT]:=ORD(CH) { Then send char out the port } END; PROCEDURE SET_7_BITS; BEGIN PORT[CTRL_PORT]:=$13; { Select write register 3 } PORT[CTRL_PORT]:=$41; { 7 bits per RX char, enable RX} PORT[CTRL_PORT]:=$15; { Select write register 5 } PORT[CTRL_PORT]:=$AA { 7 bits per TX char, enable TX} END; PROCEDURE SET_8_BITS; BEGIN PORT[CTRL_PORT]:=$13; { Select write register 3 } PORT[CTRL_PORT]:=$C1; { 8 bits per RX char, enable RX} PORT[CTRL_PORT]:=$15; { Select write register 5 } PORT[CTRL_PORT]:=$EA { 8 bits per TX char, enable TX} END; PROCEDURE SET_PARITY(PARITY : INTEGER); BEGIN PORT[CTRL_PORT]:=$14; { Select SIO Register 4 } CASE PARITY OF { All 3: 16X clock, 1 stop } 0 : PORT[CTRL_PORT]:=$44; { 0=No parity } 1 : PORT[CTRL_PORT]:=$45; { 1=Odd parity } 2 : PORT[CTRL_PORT]:=$47; { 2=Even parity } ELSE PORT[CTRL_PORT]:=$47; { Defaults to even parity } END; { CASE } END; PROCEDURE INT_ENABLE; BEGIN PORT[CTRL_PORT] := $11; { Select write register 1 } PORT[CTRL_PORT] := $18 { and turn interrupts on } END; PROCEDURE INT_DISABLE; BEGIN PORT[CTRL_PORT] := $01; { Select write register 1 } PORT[CTRL_PORT] := $00 { and disable interrupts } END; {<<>>} PROCEDURE INT_SETUP; BEGIN FILLCHAR(INT_CODE,SIZEOF(INT_CODE),CHR(0)); { Zero array to hold routine } FOR I := 0 TO 29 DO { Move the routine out of the } INT_CODE[I] := ROUTINE[I]; { constant into the array. } FOR I := 0 TO 7 DO VECT_TBL[I] := ADDR(INT_CODE); INT_ENABLE; { Finally, enable SIO interrupts. } END; {>>>>INITSIO<<<<<} PROCEDURE INITSIO(HIBAUD : BOOLEAN; PARITY : INTEGER); BEGIN SET_PARITY(PARITY); { Set parity } SET_7_BITS; { Set SIO to 7 bits RX/TX } IF HIBAUD THEN { Set baud rate: } PORT[BAUD_PORT]:=$07 { 1200 baud code to baud port } ELSE PORT[BAUD_PORT]:=$05; { 300 baud code to baud port } WRITE('') ELSE WRITELN('300 baud>') END; { INITSIO } FUNCTION GET_KEY : CHAR; BEGIN GET_KEY := CHR(BDOS(6,255)) END; {>>>>CLEAR_BIT<<<<<<} PROCEDURE CLEAR_BIT(VAR CH : CHAR; BIT : INTEGER); VAR I,J : INTEGER; BEGIN I := NOT(1 SHL BIT); { Create a bit mask } J := ORD(CH) AND I; CH := CHR(J) END; {>>>>INIT_ITERM<<<<} PROCEDURE INIT_ITERM; BEGIN NOSHOW:=[0,127]; { Don't display these! } PARITY:=2; { Defaults to even parity } PARITAG[0]:='No'; PARITAG[1]:='Odd'; PARITAG[2]:='Even'; HIBAUD := TRUE; { Defaults to 1200 baud } INITSIO(HIBAUD,PARITY); { Do init on serial port A } INT_SETUP { Init interrupt system } END; { INIT_TERM } BEGIN {**** ITERM MAIN ****} LOWVIDEO; INIT_ITERM; { Do inits on variables & hardware } CLRSCR; { Clear screen } QUIT:=FALSE; { Init flag for terminal exit } REPEAT { Can only be exited by CTRL/E } IF INSTAT THEN { If a char has come } BEGIN { from the serial port } CH := INCHAR; { Go get it from the port; } CLEAR_BIT(CH,7); { Scuttle the parity bit; } IF NOT (ORD(CH) IN NOSHOW) THEN WRITE(CH); { Write CH to the CRT } END; { Incoming character handling } CH:=GET_KEY; { See if a char was typed } IF ORD(CH)<>0 THEN { If non-zero, char was typed } CASE ORD(CH) OF { Parse the typed character } 5 : QUIT:=TRUE; { CTRL-E: Raise flag to exit } 17 : BEGIN { CTRL-Q: Step through parity } IF PARITY=2 THEN PARITY:=0 ELSE PARITY:=PARITY+1; INITSIO(HIBAUD,PARITY); WRITELN('') END; 18 : BEGIN { CTRL-R: Toggle baud rate } HIBAUD:=NOT HIBAUD; INITSIO(HIBAUD,PARITY) END; 26 : CLRSCR; { CTRL-Z: Clear CRT } ELSE OUTCHR(CH); { Send all others to modem, } END { CASE } UNTIL QUIT; INT_DISABLE; { Turn off SIO interrupts... } END. { ITERM } { ...and blow this crazyhouse...}