

{--------------------------------------------------------------}
{                            ITERM                             }
{                                                              }
{    Interrupt-driven terminal program for the Xerox 820-II    }
{                                                              }
{                                 By Jeff Duntemann            }
{                                 CP/M Turbo Pascal V2.0       }
{                                 Last Update 12/6/84          }
{                                                              }
{    From the book, COMPLETE TURBO PASCAL, by Jeff Duntemann   }
{    Scott, Foresman & Co. (c) 1986,1987  ISBN 0-673-18600-8   }
{--------------------------------------------------------------}

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   }


{<<<INCHAR>>>}
{ 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;


{<<<INSTAT>>>}
{ 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;


{<<<INT_SETUP>>>}

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('<Changing to ');
  IF HIBAUD THEN WRITELN('1200 baud>') 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('<NOW USING ',PARITAG[PARITY],' PARITY>')
            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...}
