{ This program is to use the Osborne 1 on the IEEE488 bus    }
{ With this program you should be able to use the Osborne 1  }
{ as a bus controller.                                       }
{                                                            }
{ Written by Charles J. Arnett                               }
{            Pastor Union Baptist Church                     }
{            111 Worman Dr.                                  }
{            Union     OH    45322                           }
{ December 23, 1985                                           }

PROGRAM GPIB_CONTROLLER;

{ *********************************************************** }
{ CP/M BIOS CALL          CP/M Function #                     }
{ Control Out               20                                }
{ Status In                 21                                }
{ Go To Standby             22                                }
{ Take Control              23                                }
{ Output Interface Message  24                                }
{ Output Device Message     25                                }
{ Input Device Message      26                                }
{ Input Parallel Poll       27                                }
{                                                             }
{ *********************************************************** }

Var  Dummy,
     Kind,
     CharA                        : Char;

     CharB                        : byte absolute CharA;


     VerseCount,
     TempI,
     Message,
     DelayTime,
     HandL,
     I,W,X,Z                      : Integer;

     Temp,
     IeeeTalkAddress,
     IeeeListenAddress,
     BusMessage,
     RegH,
     RegL                         : Byte;

     Mess                         : array[1..99] of Integer;

Const
     SerialPollOn           :  Byte = $18;
     SerialPollOff          :  Byte = $19;
     Unlisten               :  Byte = $3F;
     Untalk                 :  Byte = $5F;
     DeviceClearAddress     :  Byte = $04;
     DeviceClearUniversal   :  Byte = $14;
     InterfaceClear         :  Byte = $01;

Procedure Error1; { error routine for outputs }
Begin
  ClrScr;
  GOTOXY(1,10);
  If X and 4 = 4 then
    WriteLn(' Data not accepted timeout ');
  If X and 2 = 2 then
    WriteLn(' Device not ready for data timeout ');
  If X and 1 = 1 then
    WriteLn(' No device present ');
  WriteLn(' Press any key to return to program');
  Read (KBD,Dummy);
  Kind := 'M';
  X := 0;
  Bios(20);
End;

Procedure Error2; { Input  error routine }
Begin
  ClrScr;
  GOTOXY(1,10);
  If RegL and $04 = $04 then
    WriteLn(' Data not valid  timeout ');
  If RegL and $02 = $02 then
    WriteLn(' Data valid timeout ');
  WriteLn(' Press any key to return to program');
  Read (KBD,Dummy);
  RegL := 1;
  Kind := 'M';
  Bios(20);
End;


{ OutBusMessage is used to send a message byte to the Bus }
Procedure OutBusMessage(BusMessage:byte);
Begin
  I := 0;
  Repeat
    I := I + 1;
    X := Bios(24,BusMessage); { Put interface message on the bus }
    If I = DelayTime THEN Error1;
      { This gives devices 600 microSec. to  respond. }
      { Increase DelayTime for slow devices. }
  Until X = 0;
End;


{ OutDeviceMessage is used to send a message byte to a listener }
{ Put the message byte in Lo(Message) }
{ Put the  EOI information in Hi(Message) }
Procedure OutDeviceMessage(Message:integer);
Begin
  I := 0;
  Repeat
    I := I + 1;
    X := Bios(25,Message);
    If I = DelayTime THEN Error1;
      { This gives devices 600 microSec. to  respond. }
      { Increase DelayTime for slow devices. }
  Until X = 0;
End;

{ InDeviceMessage is used to receive a message byte from a talker }
Procedure InDeviceMessage;
begin
  I := 0;
  Repeat
    I := I + 1;
    HandL := BiosHL(26); { Receive CHaracter in H reg. and }
    RegH  := Hi(HandL);  { EOI & Err. message in L reg. }
    RegL  := HandL;
    If I = DelayTime then Error2;
      { This gives devices 600 microSec. to  respond }
      { increase for slow devices. }
  Until RegL <= 1;  { 1 if EOI }
  Write(Chr(RegH)); { writes byte in ASCII }
End;

{ Check to see if any device is requesting service }
Procedure CheckSrq;
begin
  X := Bios(21);
  begin
    ClrScr;
    GOTOXY(1,9);
    If X and $01 = $01 then
      WriteLn(' there is a device requesting service on the line')
    Else WriteLn(' There is no device requesting service on the bus');
  end;
end;

Procedure DeviceClear;
Begin
  ClrScr;
  GOTOXY(1,9);
  WriteLn('Universal or Addressed   U/A');
  WriteLn;
  WriteLn(' Note - You may have to increase delay time ');
  WriteLn('        to prevent error message.');
  Read(KBD,Dummy);
  WriteLn(UpCase(Dummy));
  if UpCase(Dummy) = 'A' then
    begin
      OutBusMessage(IeeeListenAddress);
      OutBusMessage(DeviceClearAddress);
    end
  else OutBusMessage(DeviceClearUniversal);
end;

Procedure SerialPoll;  { Perform Serial Poll }
Begin
ClrScr;
GOTOXY(9,10);
  { ------ Now read Serial Poll & check for SRQ ------ }
  Begin
    OutBusMessage(SerialPollOn); { Turn on Serial Poll }
    OutBusMessage(IeeeTalkAddress); { Address talker  }
    begin
      I := 0;
      Repeat
        I := I + 1;
        HandL := BiosHL(26); { Receive CHaracter in H reg. and }
        RegH  := Hi(HandL);  { EOI & Err. message in L reg. }
        RegL  := HandL;
        If I = DelayTime then Error2;
      Until RegL <= 1;
      Write(RegH); { write Serial Poll message as a byte of information }
    End;
    Write('   ');
    OutBusMessage(SerialPollOff); { Turn off Serial Poll }
  End;  { end of Serial Poll 3 times }
  WriteLn;
End; { End of serial Poll }

Procedure SendMessage;
Begin
  ClrScr;
  GOTOXY(9,10);
  WriteLn('Type Message to send <CR>');
  Z := 0;
  Repeat
    Z := Z + 1;
    Read(KBD,CharA);
    Write(CharA);
    Mess[Z] := CharB and $00FF;
    if Mess[Z] = $0D then
    begin
      Z := Z + 1;
      Mess[Z] := $0A;
    end;
  Until (Z = 99) or (Mess[Z] = $0A);
                    { stop on Line feed }
  OutBusMessage(IeeeListenAddress);
  for W := 1 to Z do
    begin
      if Mess[W] = $0A then Mess[W] := Mess[W] + $0100;
           { above routine is to send an EOI with the line feed }
      OutDeviceMessage(Mess[W]);
    end;
  OutBusMessage(UnListen);
END;

Procedure SetAddress;
begin
  ClrScr;
  GOTOXY(1,8);
  repeat
    WriteLn('  Enter the IEEE Talk Address ');
    ReadLn(TempI);
  Until (TempI > 0) and (TempI < 32);
  IeeeTalkAddress := TempI + $40;
  WriteLn('The bus talk command = ',IeeeTalkAddress);
  repeat
    WriteLn('  Enter the IEEE Listen Address');
    ReadLn(TempI);
  Until (TempI > 0) and (TempI < 32);
  IeeeListenAddress := TempI + $20;
  WriteLn('The bus listen command = ',IeeeListenAddress);
end;

Procedure SetDelay;
Begin
  ClrScr;
  GOTOXY(1,8);
  repeat
    WriteLn('  Enter the Number of 100 Micro Sec. delays before');
    WriteLn('  giving up.  The default number is 6 but a ');
    WriteLn('  slow device may need more time. ');
    WriteLn;
    WriteLn('  Note:  the number must be less that 32000');
    ReadLn(TempI);
  Until (DelayTime < 32000) and (DelayTime > 1);
  DelayTime := TempI;
end;

Procedure RomansRoad;
Begin
  VerseCount := VerseCount + 1;
  Case VerseCount of
   1 : begin
         WriteLn(' "... There is none righteous, no, not one." ');
         WriteLn('                                  Rom. 3:10');
       end;
   2 : begin
         WriteLn(' "For all have sinned, and come short of the ');
         WriteLn('  glory of God."                   Rom. 3:23');
       end;
   3 : begin
         WriteLn(' "For the wages of sin is death; but the gift');
         WriteLn('  of God is eternal life through Jesus Christ ');
         WriteLn('  our Lord."                        Rom. 6:23');
       end;
   4 : begin
         WriteLn(' "That if thou shalt confess with thy mouth');
         WriteLn('  the Lord Jesus, and shalt believe in thine ');
         WriteLn('  heart that God hath raised him from the ');
         WriteLn('  dead, thou shalt be saved."     Rom. 10:9');
       end;
  end;
  if VerseCount = 4 then VerseCount := 0;
end;

Begin { Main Program }
 IeeeTalkAddress := $47;
 IeeeListenAddress := $27;
 DelayTime := $06;
 ClrScr;
 WriteLn('   Written by Charles Arnett ');
 WriteLn('              111 Worman Dr. ');
 WriteLn('              Union      OH  ');
 WriteLn('                      45322  ');
 WriteLn('               513-836-3272  ');
 WriteLn;
 WriteLn;
 WriteLn('   Type M for Menu ');
 Read(KBD,Kind);
 Kind := UpCase(Kind);
 VerseCount := 0;
 REPEAT
  If Kind = 'M' then
  begin
    ClrScr;
    RomansRoad;
    GOTOXY(1,8);
    WriteLn('    Type A - Set Talk & Listen Address, default 7');
    WriteLn('    Type B - Set Delay, default 600 micro secs.');
    WriteLn('    Type C - Perform Serial POLL');
    WriteLn('    Type D - Device Clear Addressed/Universal');
    WriteLn('    Type I - Interface Clear');
    WriteLn('    Type Q - Check SRQ');
    WriteLn('    Type R - Make Device a Listener & Send Message');
    WriteLn('    Type T - Make Device a Talker');
    WriteLn('    Type M - Menu');
    WriteLn('    Type X - exit');
  end;
  READ(KBD,Kind);
  Kind := UpCase(Kind);
  Case Kind of
    'A' : SetAddress;
    'B' : SetDelay;
    'C' : SerialPoll;
    'D' : DeviceClear;
    'Q' : CheckSrq;
    'R' : SendMessage;
    'T' : Begin
            OutBusMessage(IeeeTalkAddress);
            Repeat
              InDeviceMessage;
            Until RegL = 1;
            OutBusMessage(UnTalk);
          end;
    'I' : Bios(20,InterfaceClear); { Conrtol out clears the bus }
  End;
UNTIL Kind = 'X';
END.

