
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.
