PROGRAM PortMonitor; { General purpose I/O Port monitor Based on PORTMON.PAS written by Jesse Bob Overholt, Carrollton, Texas. I could not get his original code to function correctly, compiled great but hung when requesting the port address. The concept has been retained although the code has been altered significanlty, encluding some helpful enhancements. Cursor, Port selection: ^E : move cursor upward, use wraparound ^X : move cursor downward, use wraparound ^S : move cursor left ^D : move cursor right Commands: P = Set Port address (in Binary, Octal, Hexidecimal, or Decimal ) M = Set port Mode I = Input, O = Output D = Set port Data, value entered according to type T = Set port Type (in D = Decimal, H = Hex, B = Binary, O = Octal) ENTER or RETURN = Do the actual I/O and display data if input I/O is limited to approximately 1200 baud. BREAK or ^C = Exit back to CP/M 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. Operation is really simple. Select the port by moving the cursor, using WordStar cursor controls, and press 'T' for the PORT type, it also determines the display and input ranges for the port address & data values. Press 'M' to set the mode of operation, either Input or Output, for this port. Then press 'P' to set the PORT address. If the mode is output then press 'D' to enter the desired data used on this port, remember that the type will limit the value entered to whatever base is being used. 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. If you have enhancements or problems with this please contact its author, John P. Hohensee of the nland omputer sers group via RCP/M at (714) 381-2887, rather than bothering the concept and original autor, Jesse Bob Overholt. } CONST Up = ^E; { WordStar Standard } Down = ^X; Left = ^S; Right = ^D; Break = ^C; Enter = ^M; Beep = ^G; Esc = ^[; Bin = 0; { Control values for conversions } Oct = 1; Hex = 2; Dec = 3; Inactive = 0; { Status of the port } Active = 1; Input = 0; { Which action should be taken } Output = 1; { 0 1 2 3 } { BIN OCT HEX DEC } Posns : ARRAY [0..3] OF BYTE = ( 8, 3, 2, 3 ); Mult : ARRAY [0..3] OF BYTE = ( 2, 8, 16, 10 ); Shift : ARRAY [0..2] OF BYTE = ( 1, 3, 4 ); Mask : ARRAY [0..2] OF BYTE = ( 1, 7, 15 ); Digits : ARRAY [0..15] OF CHAR = '0123456789ABCDEF'; TYPE DLine = STRING[8]; PortRec = RECORD Stat : BYTE; { ONE if Active } Addr : BYTE; { Range 00..FF } Data : BYTE; { Range 00..FF } Mode : BYTE; { ONE if Output } Base : BYTE; { 0 = Bin, 1 = Oct, 2 = Hex, 3 = Dec } END; VAR CurPort, OldPort, CurX, CurY : BYTE; IOPort : ARRAY [0..15] OF PortRec; Buffer, Display : DLine; KeyBuf, Ch : CHAR; BreakRequested : BOOLEAN; IO_Performed : BOOLEAN; { TRUE if I/O performed } {-------------------------------------------------------------------------} PROCEDURE Banner; BEGIN CLRSCR; LOWVIDEO; GOTOXY( 33, 1 ); WRITE( 'CP/M PORT DDT' ); GOTOXY( 20, 2 ); WRITE( 'Version 1.00, (c) 1985 by John P. Hohensee' ); GOTOXY( 17, 10 ); WRITE( 'Derived from PORTMON (c) by Jesse Bob Ovetholt.' ); NORMVIDEO; DELAY( 2000 ); GOTOXY( 6, 1 ); WRITE( '^E ^S ^D ^X to select port slot, to set up, ' ); WRITE( ' for I/O.' ); GOTOXY( 18, 2 ); WRITE( 'ESC to ReInitialize ALL Ports, or ^C to quit.' ); GOTOXY( 1, 10 ); CLREOL; END; {-------------------------------------------------------------------------} PROCEDURE SetXY; { Using the CurPort number determine the required X and Y values for cursor addressing. } BEGIN CurY := ( CurPort DIV 4 ) * 5 + 5; CurX := ( CurPort MOD 4 ) * 20 + 1; END; {-------------------------------------------------------------------------} PROCEDURE InitScrn; { Build Port screen and records } VAR i : BYTE; BEGIN i := CurPort; FOR CurPort := 0 TO 15 DO BEGIN SetXY; { Set screen location } { Titles } GOTOXY( CurX, CurY ); WRITE( '[ ] ' ); LOWVIDEO; WRITE( 'Port:' ); NORMVIDEO; WRITE( ' ' ); GOTOXY( CurX + 4, CurY + 1 ); LOWVIDEO; WRITE( 'Mode:' ); NORMVIDEO; WRITE( ' ' ); GOTOXY( CurX + 4, CurY + 2 ); LOWVIDEO; WRITE( 'Data:' ); NORMVIDEO; WRITE( ' ' ); GOTOXY( CurX + 4, CurY + 3 ); LOWVIDEO; WRITE( 'Type:' ); NORMVIDEO; WRITE( ' ' ); END; CurPort := i; END; {-------------------------------------------------------------------------} procedure InitPort; { Build Port display and init values } BEGIN FOR CurPort := 0 TO 15 DO BEGIN SetXY; { Set screen location } { Initial screen values } 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' ); IOPort[CurPort].Stat := InActive; IOPort[CurPort].Addr := $00; IOPort[CurPort].Data := $00; IOPort[CurPort].Mode := Input; IOPort[CurPort].Base := Hex; END; CurPort := 0; OldPort := 0; END; {-------------------------------------------------------------------------} FUNCTION GetKey : CHAR; { Wait for a keystroke, then return the key pressed as GetKey. } VAR Key : CHAR; BEGIN REPEAT UNTIL KeyPressed; READ( Kbd, Key ); GetKey := UPCASE( Key ); END; {-------------------------------------------------------------------------} FUNCTION GetLine : DLine; { Return a string of characters, length is determined by the specific port type set ( Binary, Octal, Hexidecimal, Decimal ). } VAR CurLen : BYTE; InKey : CHAR; MxLine : BYTE; { Local } PROCEDURE BackSpace; BEGIN WRITE( ^H, '_', ^H ); DELETE( Buffer, CurLen, 1 ); CurLen := CurLen - 1; END; { Local } PROCEDURE OutChar; BEGIN WRITE( InKey ); Buffer := Buffer + InKey; CurLen := CurLen + 1; END; { -- GetLine -- } BEGIN MxLine := Posns[IOPort[CurPort].Base]; CurLen := 0; WHILE CurLen < MxLine DO { Underline the field } BEGIN WRITE( '_' ); CurLen := CurLen + 1; END; WHILE CurLen > 0 DO { Return cursor to start of field } BackSpace; Buffer := ''; { Clear buffer } REPEAT InKey := GetKey; { Read Keyboard } CASE InKey OF ^H : IF CurLen > 0 THEN BackSpace ELSE Buffer := ''; ^X : BEGIN WHILE CurLen > 0 DO BackSpace; Buffer := ''; END; END; IF CurLen < MxLine THEN { Limit input length } CASE IOPort[CurPort].Base OF { Set limits to inputs } Bin : { Binary } IF InKey IN [ '0'..'1' ] THEN OutChar; Oct : { Octal } IF InKey IN [ '0'..'7' ] THEN OutChar; Dec : { Decimal } IF InKey IN [ '0'..'9' ] THEN OutChar; Hex : { Hexidecimal } IF InKey IN [ '0'..'9', 'A'..'F' ] THEN OutChar; END ELSE { Invalid keystroke } IF ( InKey <> ^M ) AND ( InKey <> ^I ) THEN WRITE( Beep ); UNTIL InKey IN [ ^M, ^I ]; GetLine := Buffer; END; {-------------------------------------------------------------------------} FUNCTION GetBase : BYTE; { Convert the ASCII value of Display into the binary from its source type determined by the Port Type ( Binary, Octal, Hexidecimal or Decimal ) } VAR Tmp : BYTE; Dig : BYTE; i : INTEGER; BEGIN Display := GetLine; Tmp := 0; FOR i := 1 TO LENGTH( Display ) DO BEGIN Dig := ORD( COPY( Display, i, 1 )) - ORD('0'); IF Dig > 9 THEN Dig := Dig - 7; Tmp := Tmp * Mult[IOPort[CurPort].Base] + Dig; END; GetBase := Tmp; END; {-------------------------------------------------------------------------} FUNCTION CvtBase( Value : BYTE ) : DLine; { Convert the binary value to the requested ASCII representation of the value ( Binary, Octal, Hexidecimal ) based on the Port Type } VAR i : INTEGER; Str : STRING[8]; BEGIN Str := ''; FOR i := 1 TO Posns[IOPort[CurPort].Base] DO BEGIN Str := Digits[Value AND Mask[IOPort[CurPort].Base]] + Str; Value := Value SHR Shift[IOPort[CurPort].Base]; END; CvtBase := Str; END; {-------------------------------------------------------------------------} PROCEDURE DisplayData; { Display the current port numbers data and address based upon the specified Port Type. } BEGIN GOTOXY( CurX + 10, CurY + 2 ); WRITE( ' ' ); GOTOXY( CurX + 10, CurY + 2 ); CASE IOPort[ CurPort ].Base OF Bin : WRITE( CvtBase( IOPort[CurPort].Data ) ); Oct : WRITE( CvtBase( IOPort[CurPort].Data ) ); Dec : WRITE( IOPort[ CurPort ].Data : 3 ); Hex : WRITE( CvtBase( IOPort[CurPort].Data ) ); END; IF IOPort[CurPort].Stat = Active THEN BEGIN GOTOXY( CurX + 10, CurY ); WRITE( ' ' ); GOTOXY( CurX + 10, CurY ); CASE IOPort [CurPort].Base OF Bin : WRITE( CvtBase( IOPort[CurPort].Addr ) ); Oct : WRITE( CvtBase( IOPort[CurPort].Addr ) ); Dec : WRITE( IOPort[CurPort].Addr : 3 ); Hex : WRITE( CvtBase( IOPort[CurPort].Addr ) ); END; END ELSE BEGIN GOTOXY( CurX + 10, CurY ); WRITE( '??' ); END; END; {-------------------------------------------------------------------------} BEGIN Banner; { Setup opening screen } InitScrn; { Initialize screen display } InitPort; { Initialize Port display } Display := ''; { Clear display line } Buffer := ''; { Clear GetLine buffer } CurPort := 0; { First port position } BreakRequested := FALSE; { No break requested } { Begin main command loop } REPEAT SetXY; 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; Esc : BEGIN InitScrn; InitPort; END; Break : BreakRequested := TRUE; 'P' : { Set PORT ADDRESS } BEGIN GOTOXY( CurX + 10, CurY ); WRITE( ' ' ); GOTOXY( CurX + 10, CurY ); IOPort[CurPort].Addr := GetBase; IF Display = '' THEN { Deactivate this port ? } BEGIN IOPort [CurPort].Stat := InActive; GOTOXY( CurX + 10, CurY ); WRITE( '?? ' ); END ELSE IOPort[CurPort].Stat := Active; END; 'M' : { Set PORT MODE } BEGIN GOTOXY( CurX + 10, CurY + 1 ); WRITE( '? I/O ' ); GOTOXY( CurX + 10, CurY + 1 ); REPEAT Ch := GetKey; UNTIL Ch IN [ 'I', 'O' ]; IF Ch = 'I' THEN BEGIN WRITE( 'Input ' ); IOPort[CurPort].Mode := Input; END ELSE BEGIN WRITE( 'Output ' ); IOPort[CurPort].Mode := Output; END; END; 'D' : { Set PORT DATA } BEGIN GOTOXY( CurX + 10, CurY + 2 ); WRITE( ' ' ); GOTOXY( CurX + 10, CurY + 2 ); IOPort[CurPort].Data := GetBase END; 'T' : { Set PORT TYPE } BEGIN GOTOXY( CurX + 10, CurY + 3 ); WRITE( '? B/O/H/D' ); GOTOXY( CurX + 10, CurY + 3 ); REPEAT Ch := GetKey; UNTIL Ch IN [ 'B', 'O', 'D', 'H' ]; CASE Ch OF 'B' : { Binary } BEGIN WRITE( 'Binary ' ); IOPort[CurPort].Base := Bin; END; 'O' : { Octal } BEGIN WRITE( 'Octal ' ); IOPort[CurPort].Base := Oct; END; 'D' : { Decimal } BEGIN WRITE( 'Decimal ' ); IOPort[CurPort].Base := Dec; END; 'H' : { Hexidecimal } BEGIN WRITE( 'Hex ' ); IOPort[CurPort].Base := Hex; END; END; DisplayData; { Display Address and data in base } END; Enter : { Perform all active PORT Input/Outputs } BEGIN IO_Performed := FALSE; OldPort := CurPort; FOR CurPort := 0 TO 15 DO IF IOPort[CurPort].Stat = Active THEN { Only active ports } BEGIN IO_Performed := TRUE; { Did I/O operation } SetXY; CASE IOPort[CurPort].Mode OF Input : BEGIN IOPort[CurPort].Data := Port[IOPort[CurPort].Addr]; DisplayData; { Display input data } END; Output : Port[IOPort[CurPort].Addr] := IOPort[CurPort].Data; END; END; IF NOT IO_Performed THEN WRITE( Beep ); { Nothing done, alert operator } InitScrn; { Initialize screen display } CurPort := OldPort; SetXY; { Restore screen location } END; END; UNTIL BreakRequested; CLRSCR; END.