PROGRAM PortMonitor;

{
  General purpose I/O Port monitor
  Written by Jesse Bob Overholt, Carrollton, Texas
  Copyright (c) 1985, but you can have a copy if you want one!

  DDT and its various relatives does't provide much for examining and
  changing I/O ports under CP/M. When I found myself in need of that
  capability I turned (naturally) to Turbo Pascal and cranked out
  this little program. It should work without pain on most CP/M
  systems, and with a little effort maybe even on 16 bitters.

  You'll want to change the constants to set the control codes used for
  cursor movement. These are for Montezuma Micro CP/M 2.2 on a TRS-80
  Model 4/4P.

  Operation is simple. Select the port by moving the cursor to it. Then
  specify changes by entering the first letter of the item to change.
  For example, type 'P' to set the port number, then enter the number
  in hex. Port numbers are always entered in hex, but data entry
  follows the Type set for the port.

  Commands:
     P = Set Port address (in hex)
     M = Set port Mode (I for Input, O for Output)
     D = Set port Data (value entered according to type)
     T = Set port Type (D for Decimal, H for Hex, B for Binary)
     ENTER or RETURN = Do the actual I/O and display data if input
     S = Scan port to perform continous I/O until 'space' entered
     BREAK or ^C = Exit back to CP/M

  The real problem with quick and dirty software tools is that they
  are always under-documented. This program is no exception, and I'll
  leave it for you to decide how self-documenting Pascal really is.

  Having spent many years in software support for pay the author is not
  all that excited about phone calls with gripes, enhancements, etc. If
  you really need to contact me you can try CompuServe 70130,101 or send
  a SASE to 3139 Oak Hill Road, Carrollton, TX  75007.

Modifications:
14 July 85 by Gail Graham W5MLY
 Continous scan until keypressed added.

}

CONST
  Up = ^K;       {Kimtron KT-7 and/or (Televideo 925)}
  Down = ^J;
  Left = ^H;
  Right = ^L;
  Break = ^Q;
  Enter = ^M;
  Beep = ^G;

TYPE
  PortSet = SET OF 0..15;
  Modes = (Input, Output);
  DisplayTypes = (Binary, Hex, Decimal);
  KeyList = SET OF CHAR;
  InputLine = STRING[8];
  DispLine = STRING[8];

VAR
  CurPort, CurVal, CurX, CurY: BYTE;
  DefinedPorts: PortSet;
  PortAddress, PortData: ARRAY [0..15] OF BYTE;
  PortMode: ARRAY [0..15] OF Modes;
  PortType: ARRAY [0..15] OF DisplayTypes;
  KeyBuf: CHAR;
  BreakRequested: BOOLEAN;

{-------------------------------------------------------------------------}
(* Following Keypressed function replaces the KeyPressed Function in
   Turbo Pascal, and is for use only with the NEC-APC. The Turbo Pascal
   keypressed function does not work on the NEC-APC. If your keypressed
   function does not work and you do not have a NEC, then you will have to
   write a similar replacement function to match your machine. If your
   keypressed function does work then this entire function may be deleted.
   The function is included as a remark so it will not compile normally. *)


(*
Function  KEYPRESSED:BOOLEAN;
VAR
KEYSTAT:byte;
BEGIN
KeyStat  := Port[$4C] and $10;
If KeyStat <> 0 then keypressed  := true else keypressed  := false;
end;
 *)

{-------------------------------------------------------------------------}

PROCEDURE SetXY (DescNum: BYTE);

BEGIN
  CurY := (CurPort DIV 4) * 5 + 5;
  CurX := (CurPort MOD 4) * 20 + 1;
END;

{-------------------------------------------------------------------------}

FUNCTION GetKey (LegalKeys: KeyList): CHAR;

VAR
  InKey: CHAR;

CONST
  Printable: SET OF CHAR = [#$20..#$7E];

BEGIN
  REPEAT
     Read(Kbd,InKey);
     InKey := UpCase(InKey);
  UNTIL InKey IN LegalKeys;
  IF InKey IN Printable THEN Write(InKey);
  GetKey := InKey;
END;

{-------------------------------------------------------------------------}

FUNCTION GetLine (Length: BYTE; ValidKeys: KeyList): InputLine;

VAR
  Buffer: InputLine;
  CurLen: BYTE;
  InKey: CHAR;

{ Local } PROCEDURE BackSpace;
         BEGIN
            Write(^H,'_',^H);
            Delete (Buffer, CurLen, 1);
            CurLen := CurLen - 1;
         END;

BEGIN
  CurLen := 0;
  WHILE CurLen < Length DO
  BEGIN
     Write('*');
     CurLen := CurLen + 1;
  END;
  WHILE CurLen > 0 DO BackSpace;
  Buffer := '';
  REPEAT
     InKey := GetKey (ValidKeys + [^H, ^X]);
     CASE InKey OF
        ^H: IF CurLen > 0 THEN BackSpace;
        ^X: WHILE CurLen > 0 DO BackSpace;
     ELSE
        BEGIN
           Buffer := Buffer + InKey;
           CurLen := CurLen + 1;
        END;
     END;
  UNTIL CurLen = Length;
  GetLine := Buffer;
END;

{-------------------------------------------------------------------------}

FUNCTION GetHex: BYTE;

VAR
  InBuf: InputLine;

{ Local } FUNCTION HexVal (Digit: CHAR): BYTE;
         VAR
            DigitVal: BYTE;
         BEGIN
            DigitVal := Ord(Digit) - Ord('0');
            IF DigitVal > 15 THEN DigitVal := DigitVal - 7;
            HexVal := DigitVal;
         END;

BEGIN
  InBuf := GetLine (2, ['0'..'9']+['A'..'F']);
  GetHex := HexVal(InBuf[1]) SHL 4 + HexVal(InBuf[2]);
END;

{-------------------------------------------------------------------------}

FUNCTION GetDec: BYTE;

VAR
  InBuf: InputLine;
  I, Value: INTEGER;

BEGIN
  REPEAT
     InBuf := GetLine (3, ['0'..'9']);
     Val(InBuf, Value, I);
     IF Value > 255 THEN I := 1;
     IF I > 0 THEN Write(^H,^H,^H,Beep);
  UNTIL I = 0;
  GetDec := Value;
END;

{-------------------------------------------------------------------------}

FUNCTION GetBin: BYTE;

VAR
  Value: BYTE;
  InBuf: InputLine;

BEGIN
  Value := 0;
  InBuf := GetLine (8, ['0'..'1']);
  WHILE Length(InBuf) > 0 DO
  BEGIN
     Value := Value SHL 1;
     IF InBuf[1] = '1' THEN Value := Value + 1;
     Delete (InBuf, 1, 1);
  END;
  GetBin := Value;
END;

{-------------------------------------------------------------------------}

FUNCTION CvtBin: DispLine;

VAR
  Buffer: DispLine;
  Value: BYTE;

BEGIN
  Buffer := '';
  Value := PortData[CurPort];
  REPEAT
     IF Odd(Value) THEN Buffer := '1' + Buffer
     ELSE Buffer := '0' + Buffer;
     Value := Value SHR 1;
  UNTIL Length(Buffer) = 8;
  CvtBin := Buffer;
END;

{-------------------------------------------------------------------------}

FUNCTION CvtHex: DispLine;

VAR
  Buffer: DispLine;

{ Local } FUNCTION HexDig (Value: BYTE): CHAR;
         BEGIN
            Value := Value + Ord('0');
            IF Value > Ord('9') THEN Value := Value + 7;
            HexDig := Chr(Value);
         END;

BEGIN
  CvtHex := HexDig(PortData[CurPort] SHR 4)
          + HexDig(PortData[CurPort] AND $0F);
END;

{-------------------------------------------------------------------------}

PROCEDURE DisplayData;

BEGIN
  GotoXY(CurX+10,CurY+2); Write('        ',^H^H^H^H^H^H^H^H);
  CASE PortType[CurPort] OF
     Binary: Write(CvtBin);
     Hex: Write(CvtHex);
     Decimal: Write(PortData[CurPort]:3);
  END;
END;

{-------------------------------------------------------------------------}

BEGIN

{ Set up opening banner }
  ClrScr;
  LowVideo;
  ClrEol; GotoXY(10,1); Writeln('CCP/M Port Monitor Version 0.02');
  Writeln('     (c) (p) 1985  by Jessie Overholt and modified by others');

  NormVideo;
  Write('    Arrows to select, <P,M,D,T> to set up, ');
  Writeln('<RETURN> for I/O, <BREAK> to quit');
  Writeln('<S> to scan assigned ports, <any key> to stop scan');
{ Build Port desciptors }
  FOR CurPort := 0 TO 15 DO
  BEGIN
     SetXY (CurPort);
     GotoXY(CurX, CurY); Write('[ ] ');
     LowVideo; Write('Port:');
     GotoXY(CurX+4,CurY+1); Write('Mode:');
     GotoXY(CurX+4,CurY+2); Write('Data:');
     GotoXY(CurX+4,CurY+3); Write('Type:');
     NormVideo;
     GotoXY(CurX+10,CurY); Write('??');
     GotoXY(CurX+10,CurY+1); Write('Input');
     GotoXY(CurX+10,CurY+2); Write('00');
     GotoXY(CurX+10,CurY+3); Write('Hex');
     PortAddress[CurPort] := $00;
     PortMode[CurPort] := Input;
     PortData[CurPort] := $00;
     PortType[CurPort] := Hex;
  END;
  DefinedPorts := [];
  CurPort := 0;
  BreakRequested := FALSE;

{ Begin main command loop }
  REPEAT
     SetXY (CurPort); GotoXY(CurX+1, CurY);
     Read (Kbd,KeyBuf);
     CASE UpCase(KeyBuf) OF
        Up: CurPort := (CurPort - 4) AND $0F;
        Down: CurPort := (CurPort + 4) AND $0F;
        Left: CurPort := (CurPort - 1) AND $0F;
        Right: CurPort := (CurPort + 1) AND $0F;
        Break: BreakRequested := TRUE;
        'P': BEGIN
                GotoXY(CurX+10,CurY);
                PortAddress[CurPort] := GetHex;
                DefinedPorts := DefinedPorts + [CurPort];
             END;
        'M': BEGIN
                GotoXY(CurX+10,CurY+1); Write('? I/O ',^H^H^H^H^H^H);
                IF GetKey(['I','O']) = 'I' THEN
                BEGIN
                   Write('nput ');
                   PortMode[CurPort] := Input;
                END
                ELSE
                BEGIN
                   Write('utput');
                   PortMode[CurPort] := Output;
                END;
             END;
        'D': BEGIN
                GotoXY(CurX+10,CurY+2);
                Write('        ',^H^H^H^H^H^H^H^H);
                CASE PortType[CurPort] OF
                   Binary: PortData[CurPort] := GetBin;
                   Hex: PortData[CurPort] := GetHex;
                   Decimal: PortData[CurPort] := GetDec;
                END;
             END;
        'T': BEGIN
                GotoXY(CurX+10,CurY+3); Write('? B/H/D',^H^H^H^H^H^H^H);
                CASE GetKey(['B','H','D']) OF
                   'B': BEGIN
                           Write('inary  ');
                           PortType[CurPort] := Binary;
                        END;
                   'H': BEGIN
                           Write('ex     ');
                           PortType[CurPort] := Hex;
                        END;
                   'D': BEGIN
                           Write('ecimal ');
                           PortType[CurPort] := Decimal;
                        END;
                END;
                DisplayData;
             END;

          'S':BEGIN
               Delay(300); (* allow keypressed time to clear *)
               WHILE not keypressed  DO
                BEGIN
                 DELAY(500);
                 FOR CurPort  := 0 to 15 do
                  BEGIN
                   IF CurPort IN DefinedPorts THEN
                    BEGIN
                      SetXY(CurPort);
                      GotoXY(CurX,CurY);
                      CASE PortMode[CurPort] OF
                        Input: BEGIN
                                  PortData[CurPort] :=
                                     Port[PortAddress[CurPort]];
                                  DisplayData;
                               END;
                        Output: Port[PortAddress[CurPort]] :=
                                   PortData[CurPort];
                     END;
                    END;
                   END;
                END;
               END;
        Enter: BEGIN
                  IF CurPort IN DefinedPorts THEN
                  BEGIN
                     CASE PortMode[CurPort] OF
                        Input: BEGIN
                                  PortData[CurPort] :=
                                     Port[PortAddress[CurPort]];
                                  DisplayData;
                               END;
                        Output: Port[PortAddress[CurPort]] :=
                                   PortData[CurPort];
                     END;
                  END
                ELSE Write(Beep);
               END;
     END;
  UNTIL BreakRequested;
  ClrScr;

END.