Unit MPU401;

{
****************************************************************************
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

Uses DOS;

Const
     MPU_reset     = $ff;
     MPU_UART_Mode = $3f;
     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_start = 1;
     Buffer_End   =512; {increase this if there appears to be data loss with
                         long messages ie. make it 1024, 2048 etc.}

Type
     MPU_Comdata_Send_Buffer = ARRAY[1..128] of Byte;
     Data_Buffer = Array[Buffer_Start..Buffer_End] of Byte;

Var
     MPU_Indata_Buffer : Data_Buffer;
     MPU_in_UART_mode : Boolean;
     buffer_Head, Buffer_Tail : Word;
     Old_Int_Vec : Pointer;
     ExitSave:pointer;

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;
Procedure Send_Command_to_MPU(Command:Byte);

Implementation

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];
               If(Buffer_Head < Buffer_End) Then
                   Inc(buffer_Head) Else
                   Buffer_Head:=Buffer_Start
          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];
           If (Buffer_Tail < Buffer_End)
           Then Inc(buffer_Tail)
           Else buffer_Tail:=Buffer_Start;
      end;
   Get_data_From_MPU:=valid
end;{Get..}

Procedure Send_Command_to_MPU;
{
*****************************************************************************
Wait until there is no data in the MPU's buffer then:
   Disable interrupts
   Send a command byte (ie. MPU reset) to the MPU
   If the command was a reset and the MPU was in UART mode
        Then set UART_MODE flag false otherwise
           Get data from MPU, buffer it as in Get_Data_From_MPU function
           until an ACK byte from the MPU comes in
   If the command was to put the MPU in UART mode then set UART_MODE flag true;
   Enable interrupts
   Get out of here

*****************************************************************************
}

Var
   Data:Byte;
   a:byte;

Begin
     Repeat
     Until((Port[MPU_Statport] And MPU_Data_Ready_Mask)=0);
     Disable_MPU_Interrupts;
     Port[MPU_Comport]:=Command;
     If (Command = MPU_Reset) and (MPU_in_UART_Mode)
        Then MPU_in_UART_Mode:=False
        Else Repeat
                 Repeat
                 a:=port[mpu_statport];
                 Until ((Port[MPU_Statport] and MPU_Data_Available_Mask)=0);
                 Data:=Port[MPU_Dataport];
                 If (Data <> MPU_ack) Then
                    Begin
                       MPU_Indata_buffer[buffer_head]:=data;
                       If(Buffer_head < buffer_end)
                       Then Inc(buffer_Head)
                       Else Buffer_head:=buffer_start;
                    End;
                 Until (Data = MPU_ack);
        If (Command = MPU_UART_Mode) then MPU_in_UART_Mode:=True;
        Enable_MPU_Interrupts
end; {Send_Command..}

{$F+} Procedure Uninstall_MPU401; {$F-}
{
****************************************************************************
This procedure gains control when the program terminates, normal or otherwise
The code is:
         Disable interrupts
         Put the old IRQ2 interrupt back where it was before we started
         Put the pointer to the next exit procedure (if any) back
             in Turbo's internal ExitProc pointer
*****************************************************************************
}

Begin
     Disable_MPU_Interrupts;
     SetIntVec(MPU_Interrupt_Number,Old_Int_Vec);
     ExitProc:=ExitSave;
End;

{
*****************************************************************************
This code gets called at the Begin statement of the Main program.  The code is
as follows:
           Save any Previously stored Exit code address
           Put our exit procedure's code into Turbo's intrinsic pointer
           Initialize our data buffer to empty
           Get the existing IRQ2 interrupt vector and save it
           Put in its place the address to our Interrupt service routine
               (MPU_Interrupt_Handler)
           Enable from the MPU
           Set the UART_Mode flag to false
           Go and execute the Main Program

*****************************************************************************
}

Begin {initialization Code}

     ExitSave:=ExitProc;
     ExitProc:=@Uninstall_MPU401;

{Procedure Install_MPU401;}

     Buffer_Head := Buffer_Start;
     Buffer_Tail := Buffer_Start;
     GetIntVec(MPU_Interrupt_Number,Old_Int_Vec);
     SetIntVec(MPU_Interrupt_Number,@MPU_Interrupt_Handler);
     Enable_MPU_Interrupts;
     MPU_In_UART_Mode := False;

End. {Init Code}

