Module DC_Hayes_Modem;

{		Written by Warren A. Smith		}
{	    Intended for use in the Public Domain	}
{			01/30/82			}

{ This set of routines will give you complete access to the functions	}
{ of a D.C. Hayes Modem.						}

{ Port assignments for D.C. Hayes S-100 Modem board }
Const
	Base_Register	= $10;
	Modem_Rcv_Reg	= $10;	{ Base_register		}
	Modem_Xmit_Reg	= $10;	{ Base_register		}
	Status_Reg_Modem= $11;	{ Base_register + 1	}
	Reg1Modem	= $11;	{ Base_register + 1	}
	Reg2Modem	= $12;	{ Base_register + 2	}

{ These routines require that you declare a global variable in your	}
{ main program that will hold the current status of the modem.  It	}
{ should be a BYTE type and will be passed into the various routines	}
{ that set the modems modes.	}

Procedure Init_Modem;

  Const	Char_Length     = 3;	{ 8 data bits	}
	Stop_bits	= 0;	{ 1 stop bits	}
	Parity_Inhibit	= 1;	{ no parity	}
	Parity_Type	= 1;	{ even parity	}

  begin { Init_Modem }
	{ Initializes the serial port on the modem to the above values.	}
	{ This is the only routine that outputs to Reg1Modem.	}
  Out [Reg1Modem] := SHL(Parity_Inhibit,4) ! SHL(Stop_bits,3) !
		     SHL(Char_Length,1) ! Parity_Type
  end;  { Init_Modem }

Procedure Set_Modem (Modebyte : byte);

  begin { Set_Modem }
	{ Used to set various modes of the modem, (this is the only	}
	{ routine that outputs to Reg2Modem).	}
  Out [Reg2Modem] := Modebyte
  end;  { Set_Modem }

Procedure Go_Onhook (Var Modem_Mode : byte);

  begin { Go_Onhook }
  Clrbit (Modem_Mode, 7);
  Set_Modem (Modem_Mode)
  end;  { Go_Onhook }

Procedure Go_Offhook (Var Modem_Mode : byte);

  begin { Go_Offhook }
  SetBit (Modem_Mode, 7);
  Set_Modem (Modem_Mode)
  end;  { Go_Offhook }

Procedure Set_Ans_Mode (Var Modem_Mode : byte);

  begin { Set_Ans_Mode }
  Clrbit (Modem_Mode, 2);
  Set_Modem (Modem_Mode)
  end;  { Set_Modem_Mode }

Procedure Set_Org_Mode (Var Modem_Mode : byte);

  begin { Set_Org_Mode }
  Setbit (Modem_Mode, 2);
  Set_Modem (Modem_Mode)
  end;  { Set_Org_Mode }

Procedure Set_Baud (Baud_Rate : integer; Var Modem_Mode : byte);

  begin { Set_Baud }
	{ The D.C. Hayes modem only allows 110 or 300 bps communication	}
	{ so those are the only values allowed.  Any other value results}
	{ in 300 baud.	}
  Case Baud_Rate of
    110 : Clrbit (Modem_Mode, 0);
    300 : Setbit (Modem_Mode, 0);
    else  Setbit (Modem_Mode, 0)
    end;
  Set_Modem (Modem_Mode)
  end;  { Set_Baud }

Procedure Enable_Xmit (Var Modem_Mode : byte);

  begin { Enable_Xmit }
  Setbit (Modem_Mode, 1);
  Set_Modem (Modem_mode)
  end;  { Enable_Xmit }

Procedure Disable_Xmit (Var Modem_Mode : byte);

  begin { Disable_Xmit }
  Clrbit (Modem_Mode, 1);
  Set_Modem (Modem_Mode)
  end;  { Disable_Xmit }

Function Carrier_Present : boolean;

  begin { Carrier_Present }
  Carrier_Present := Tstbit (Inp[Status_Reg_Modem], 6)
  end;  { Carrier_Present }

Function Ringing : boolean;

  begin { Ringing }
  Ringing := not Tstbit (Inp[Status_Reg_Modem], 7)
  end;  { Ringing }

Function Modem_Char_Rdy : boolean;

  begin { Modem_Char_Rdy }
  Modem_Char_Rdy := Tstbit (Inp[Status_Reg_Modem], 0)
  end;  { Modem_Char_Rdy }

Function Modem_In : char;

  begin { Modem_In }
	{ Some systems require that bit 7 of incoming data be masked	}
	{ off.  That is because the sender may be forcing bit 7 high.	}
	{ If the system you are talking to lets bit 7 be a real data	}
	{ bit then you will not want to mask it off as it is here.  This}
	{ setup will work for just about any time share service.	}
  Modem_In := chr(Inp[Modem_Rcv_Reg] & $7F)
  end;  { Modem_In }

Function Modem_Out (OutChar : char) : boolean;

	{ Modem_Out is a boolean function so that it can return the	}
	{ status of the line.  If carrier is lost you don't want to get	}
	{ hung up in a status loop, and it would be nice to know when	}
	{ carrier was actually lost.	}
  Function Modem_Busy : boolean;

    begin { Modem_Busy }
    Modem_Busy := not Tstbit (Inp[Status_Reg_Modem], 1)
    end;  { Modem_Busy }

  begin { Modem_Out }
  While Modem_Busy do;
  If Carrier_Present then
    begin
    Out [Modem_Xmit_Reg] := ord(OutChar);
    Modem_Out := TRUE
    end
  else
    Modem_Out := FALSE
  end;  { Modem_Out }

Procedure Delay;	{ delay's for 10 millisecond }

  Const
	Count = 477;
  Var
	I : integer;

  begin { Delay }
	{ This loop was determined empirically.  I put it in a tight	}
	{ loop toggling an output port and attached a frequency counter	}
	{ to that port.  You may have to change the value of the	}
	{ constant for your machine.  I am using a 5 MHz 8085 on a	}
	{ Godbout dual processor board and Pascal MT+ ver. 5.5		}
  For I := 1 to Count do
  end;  { Delay }

Procedure Dial_a_Number (Var Modem_Mode : byte; Number : string);

  Var
	I, J, Pulse_Count : integer;

  Procedure Pulse_Line;

    Var
	I : integer;
    begin { Pulse_Line }
    Go_Onhook (Modem_Mode);
    For I := 1 to 5 do
      Delay;			{ leave on for 50 ms }
    Go_Offhook (Modem_Mode);
    For I := 1 to 5 do
      Delay			{ leave off for 50 ms }
    end;  { Pulse_Line }

  begin { Dial_a_Number }
	{ If this routine doesn't seem to be able to actually dial, the	}
	{ problem is probably in the DELAY procedure.  If it is not	}
	{ delaying enough, the pulses will be too fast and the local	}
	{ phone exchange may not respond to them.  Try increasing the	}
	{ constant in DELAY (doubling it will not hurt).		}
  Go_Offhook (Modem_Mode);
  For I := 1 to 100 do
    Delay;
  For I := 1 to Length(Number) do
    If (Number[I] < '0') OR (Number[I] > '9') then
      begin
      Write (Number[I]);
      For J := 1 to 300 do	{ wait 3 seconds for non_digit }
	Delay
      end
    else
      begin
      Pulse_Count := ord(Number[I]) - $30;
      If Pulse_Count = 0 then
	Pulse_Count := 10;
      Write (Number[I]);
      For J := 1 to Pulse_Count do
	Pulse_Line;
      For J := 1 to 60 do
	Delay			{ 600 ms delay between digits }
      end;
  Writeln
  end;  { Dial_a_Number }

Modend.
