Unit Umpu;

{
****************************************************************************
This Unit is derived from an article written in the May 1989 issue of
Electronic Musician Magazine entitled 'Handling MPU-401 Interrupts with
Turbo Pascal' by William Millar.   The Code was coded into Unit form by
John Sloan CIS. 71310,2267.
****************************************************************************
}

Interface

Const
     MPU_reset     = $ff;
     MPU_UART_Mode = $3f;

var  Systemtick:longint absolute $40:$6c;
     MPU_in_UART_mode  : Boolean;

Procedure Disable_CPU_Interrupts;
     INLINE($FA);{CLI}

Procedure Enable_CPU_Interrupts;
     INLINE($FB); {STI}

Procedure Disable_MPU_Interrupts;
     INLINE($FA/$BA/$21/$00/$EC/$0C/$04/$EE/$FB);

Procedure Enable_MPU_Interrupts;
     INLINE($FA/$BA/$21/$00/$EC/$24/$FB/$EE/$FB);

Procedure Ack_Int_to_PIC;
     INLINE($BA/$20/$00/$B0/$20/$EE);

Function  Get_Data_From_MPU(Var data:Byte):Boolean;

Function  Get_Data_With_Timeout(Var data:Byte;
                                Expected:integer;
                                timeout:word):Boolean;

Procedure Send_Command_to_MPU(Command:Byte);

Procedure Send_Data_to_MPU(Data:Byte);

Procedure ClearMpuIn;

Procedure Mpu_Init;

Implementation

Uses Dos;

const
     MPU_ack       =$fe;
     MPU_Dataport  =$330;
     MPU_Statport  =$331;
     MPU_Comport   =$331;
     MPU_Data_Available_Mask = $80;
     MPU_Data_Ready_Mask     = $40;
     MPU_Interrupt_Number    = 10;
     Buffer_size  =4095;    {keep power of 2}
     Buffer_start =0;
     Buffer_End   =Buffer_size;

Type
     Data_Buffer = Array[Buffer_Start..Buffer_End] of Byte;

Var
     MPU_Indata_Buffer : Data_Buffer;
     Buffer_Head,
     Buffer_Tail       : Word;
     Old_Int_Vec       : Pointer;
     ExitSave          : Pointer;


Procedure MPU_Interrupt_Handler;

{***************************************************************************
This routine is called every time the hardware IRQ2 toggles as a result of
data being received by the MPU-401.  The hardware vector to this routine is
entered into the PC's interrupt vector table in the initialization section
of this Unit.  The pseudocode is something like:

               Disable any further interrupts from bothering us
               If the status port data available bit is reset then
                  Write the data to the next location in the buffer
                  If the pointer to this data location hasn't reached the
                      end of the buffer then increment it, otherwise make
                      it point to the beginning of the buffer
               Enable the CPU interrupt system
               Acknowledge to the PIC chip that we responded to this IRQ

*****************************************************************************
}

Interrupt;
Begin
     Disable_MPU_Interrupts;
     While((Port[MPU_statport] AND MPU_Data_Available_Mask)=0) DO
          Begin
               MPU_Indata_BUffer[Buffer_Head]:=Port[MPU_DataPort];
               buffer_head:=(buffer_head+1) and Buffer_size;
          End;
      Enable_MPU_Interrupts;
      ack_Int_to_PIC;
End;

Function Get_Data_From_MPU;
{
****************************************************************************
Disable CPU interrupts
If the buffer beginning pointer and the end pointer are different then we
   must have data therefore:
      Renable interrupts so we don't lose any data
      Get the data from the buffer
      If the pointer to this data isn't at the end of buffer increment it
         otherwise reset it to point to the buffer's beginning
      If data was available, pass the data back to the calling routine
           as well as a boolean true (data available) otherwise
           pass a boolean false (data not available)

*****************************************************************************
}

Var
   Valid:boolean;

Begin
   Disable_MPU_Interrupts;
   Valid:=(Buffer_Head <> Buffer_Tail);
   Enable_MPU_Interrupts;
   If (Valid) then
      begin
           Data:=MPU_Indata_Buffer[buffer_Tail];
           buffer_tail:=(buffer_tail+1) and Buffer_size;
      end;
   Get_data_From_MPU:=valid
end;{Get..}

{wait for data, ignore clock, timeout}
Function  Get_Data_With_Timeout(Var data:Byte;
                                Expected:integer;
                                timeout:word):Boolean;
 var t:longint;
 begin
  Get_data_with_timeout:=false;
  t:=systemtick;
  while not (Get_data_from_mpu(data) and
             (data<>$F8) and
             ((expected<0) or (data=expected))) do
   if (systemtick-t)>=timeout then exit;

  Get_data_with_timeout:=true
 end;

Procedure Send_Command_to_MPU;

Var
   Data:Byte;
   t:longint;

Begin
 Disable_MPU_Interrupts;

 t:=systemtick;
 While (Port[MPU_Statport] And MPU_Data_Ready_Mask)<>0 do
  if systemtick-t>18 then exit;

 Port[MPU_Comport]:=Command;

 If (Command=MPU_Reset) and MPU_in_UART_Mode then Mpu_In_Uart_Mode:=false
 else
  begin
   t:=systemtick;
   Repeat
    if (Port[MPU_Statport] and MPU_Data_Available_Mask)=0 then
     begin
      data:=port[Mpu_DataPort];
      if data<>mpu_ack then
       begin
        MPU_Indata_buffer[buffer_head]:=data;
        buffer_head:=succ(buffer_head) and Buffer_size;
       end
     end
    else data:=0
   Until (data=Mpu_ack) or (systemtick-t>18);
  end;
 If Command=Mpu_Uart_Mode then Mpu_In_Uart_Mode:=true;
 Enable_mpu_Interrupts;
end; {Send_Command..}

procedure Send_data_to_mpu(data:byte);
 begin
  Repeat
  Until((Port[MPU_Statport] And MPU_Data_Ready_Mask)=0);
  port[mpu_dataport]:=data
 end;

procedure clearmpuin;
 var b:byte;
 begin
  while get_data_from_mpu(b) do
 end;


{$F+} Procedure Uninstall_MPU401;
Begin
     ExitProc:=ExitSave;
     Disable_MPU_Interrupts;
     SetIntVec(MPU_Interrupt_Number,Old_Int_Vec);
End;

procedure Mpu_Init;
 var b,c:byte;
     t:longint;
     ok:boolean;
 begin
  Disable_Mpu_Interrupts;
{flush in}
  Buffer_Head := Buffer_Start;
  Buffer_Tail := Buffer_Start;
  b:=port[mpu_dataport];
  while ((Port[MPU_Statport] and MPU_Data_Available_Mask)=0) do
         b:=port[mpu_dataPort];

  ok:=false;
  c:=3; {3 tries to reset}
  repeat
   t:=systemtick;
   repeat
    ok:=(Port[MPU_Statport] And MPU_Data_Ready_Mask)=0
   until ok or (systemtick-t>18);
   if not ok then exit;
   port[mpu_ComPort]:=mpu_reset;
   t:=systemtick;
   repeat
    ok:=((Port[MPU_Statport] And MPU_Data_Available_Mask)=0) and
        (Port[Mpu_DataPort]=Mpu_ack);
   until ok or (systemtick-t>18);
   Dec(c);
  until ok or (c=0);
  Mpu_In_Uart_mode:=false;
  Enable_mpu_interrupts;
  Send_Command_to_mpu(mpu_Uart_mode);
 end;

Begin {initialization Code}
 ExitSave:=ExitProc;
 ExitProc:=@Uninstall_MPU401;
 GetIntVec(MPU_Interrupt_Number,Old_Int_Vec);
 SetIntVec(MPU_Interrupt_Number,@MPU_Interrupt_Handler);
 Mpu_init;
End. {Init Code}

