unit Dd12isr;
{ ----------------------------------------------------------
  Interrupt service routine for Digidata 1200 interface card
  2/6/98 Modified to permit continuous cyclic D/A output waveform
  DAC timer only initiates a restart of DAC clock if the
  variable DACStartRequest=1 (Set of EnableDACTimer or UpdateDACTimer)
  Note that DMA data is intermittently corrupted when a large number
  of DMA cycles are executed with DAC waveforms. Suspect that a
  one byte shift is occurring, possibly due to a bug
  within Windows 95 virtual DMA system
  ----------------------------------------------------------}

{$C FIXED PRELOAD PERMANENT}

interface
uses winprocs,wintypes,global ;


function InstallISR( const IRQNum : Word ;
                     const DD1200_Base : Word ) : Word ; export ;
Procedure RemoveISR( var IRQNum : word ) ; export ;
Procedure EnableADCMonitor( ADC  : Pointer ;
                            const BufSize : Word ;
                            const CircularBuffer : Word ) ; export ;
Procedure DisableADCMonitor ; export ;
Procedure EnableDACTimer( const InterSweepInterval : single  ) ; export ;
Procedure DisableDACTimer ; export ;
Procedure UpdateDACTimer( const InterSweepInterval : single ) ; export ;
Function ReadDACTimer : Word ; export ;
procedure DisableIRQ( var IRQNum : word ) ; export ;
procedure EnableIRQ( var IRQNum : word ) ; export ;
function GetSegment( Pntr : Pointer ) : Word ;
function GetOffset( Pntr : Pointer ) : Word ;

implementation



const
     PIC2_IRQ8 = $1 ;
     PIC2_IRQ9 = $2 ;
     PIC2_IRQ10 = $4 ;
     PIC2_IRQ11 = $8 ;
     PIC2_IRQ12 = $10 ;
     PIC2_IRQ13 = $20 ;
     PIC2_IRQ14 = $40 ;
     PIC2_IRQ15 = $80 ;
     PIC2_MaskRegister = $A1 ;
     PIC2_EndOfInterruptRegister = $A0 ;
     PIC1_MaskRegister = $21 ;
     PIC1_EndOfInterruptRegister = $20 ;
     PIC_EOI = $20 ;
     ADC_DATA_AVAILABLE = $40 ;
     CLEAR_DONE4 = $4 ;
     COUNTER1 = $01;
     COUNTER2 = $02;
     COUNTER3 = $04;
     COUNTER4 = $08;
     COUNTER5 = $10;
     ARM 	  = $20 ;
     LOADCOUNT	  = $40 ;
     LOADARM	  = $60 ;
     DISARM	  = $C0 ;
     CLEAROUT	  = $E0 ;
     SETOUT	  = $E8 ;
     SET16BITMODE  = $EF ;
     MASTERRESET   = $FF ;

var
   { Digidata 1200 I/O port addresses }
   DD1200_Status : Word ;
   DD1200_ADCData : Word ;
   DD1200_Clear : Word ;
   DD1200_TimerControl : Word ;
   DD1200_TimerData : Word ;
   BufSeg : Word ;
   BufOffset : Word ;
   BufPointer : Word ;
   EndOfBuf : Word ;
   OldSeg : Word ;
   OldOffset : Word ;
   CircularBufferFlag : Word ;
   EndOfSweep : Word ;
   ADCEnabled : Word ;
   DACEnabled : Word ;
   DACTimer : Word ;
   DACSweepStartAt : Word ;
   DACStartRequest : Word ;


function InstallISR( const IRQNum : Word ;
                     const DD1200_Base : Word ) : Word ;
{ ---------------------------------
  Install interrupt service routine
  ---------------------------------}

label
     ISRStart,Exit,GetSamples,NoRollOver, NODACStart , SkipDACStart,
     ADCProcedure,EndOfADCProcedure,
     DACProcedure,EndOfDACProcedure ;
var
   Dummy : Integer ;
   Hnd : THandle ;
   IRQVector : Word ;
   IRQMaskBit : Word ;
begin
     { Page lock data segment to keep it in RAM }
     Hnd := LockData( Dummy ) ;

     { Interrupt vectors : IRQ8=$70...IRQ15=$77 }
     IRQVector := $70 + (IRQNum - 8) ;
     { PIC Mask bit }
     IRQMaskBit := 1 shl (IRQNum-8) ;

     { Digidata 1200 I/O ports }
     DD1200_Status := DD1200_Base + $18 ;
     DD1200_ADCData := DD1200_Base ;
     DD1200_Clear := DD1200_Base + $1A ;
     DD1200_TimerControl := DD1200_Base + $6 ;
     DD1200_TimerData := DD1200_Base + $4 ;


     ADCEnabled := 0 ;
     DACEnabled := 0 ;
     DACTimer := 0 ;
     asm

        push ds ;
        push es ;

        { Save address of existing interrupt handler }
        mov ax,IRQVector ;
        mov ah,$35 ;
        int $21 ;
        mov ax,es ;
        mov OldSeg,ax ;
        mov OldOffset,bx ;

        { Load new interrupt handler into table }
        mov ax,seg ISRStart ;
        mov dx,offset ISRStart ;
        push ds ;
        mov ds,ax
        mov ax,IRQVector ;
        mov ah,$25 ;
        int $21 ;
        pop ds ;

        { Enable IRQ channel by clearing bit in PIC mask register }
        mov bx,IRQMaskBit ;
        not bx ;
        in al,PIC2_MaskRegister ;
        and al,bl ;
        out PIC2_MaskRegister,al ;

        jmp exit

{ --------------------------
  Interrupt Service Routine
  -------------------------}

ISRStart:
        push ds ;
        push es ;
        push ax ;
        push dx ;
        push bx ;
        push di ;

        { Disable interrupts for this VM (Note DPMI call, not CLI) }
        mov ax, $900 ;
        int $31 ;
        push ax ;     { VM interrupt state returned in AX and saved}

        mov ax,seg @DATA ; { Get our data segment }
        mov ds,ax


        cmp ADCEnabled,0
        jz EndOfADCProcedure ;
ADCProcedure :

        { Transfer any A/D samples found in FIFO to PC memory
          ---------------------------------------------------}

        mov ax,BufSeg ;
        mov es,ax ;
        mov di,BufOffset ;

        { If no A/D samples available ... skip FIFO read loop }
        mov dx,DD1200_Status ;
        in ax,dx ;
        and ax, ADC_DATA_AVAILABLE ;
        jz EndofADCProcedure ;

        mov bx,BufPointer ;

        { Transfer A/D samples from FIFO to buffer }
GetSamples :
           mov dx,DD1200_ADCData ; { Get A/D sample from FIFO }
           in ax,dx ;              { Put it into buffer }

           sar ax,1 ;                ; { Shift word right 4 bits to }
           sar ax,1 ;                ; { get 12 bit A/D sample}
           sar ax,1 ;
           sar ax,1 ;

           mov es:[di][bx],ax ;
           inc bx ;
           inc bx ;
           { If pointer exceeds buffer ... rollover to zero }
           cmp bx,EndOfBuf ;
           jnz NoRollOver ;
               mov bx,0 ;
               { If single sweep mode ... stop collecting }
               cmp CircularBufferFlag,1 ;
               jz NoRollOver ;
                  mov ADCEnabled,0 ;
                  jmp EndofADCProcedure ;
NoRollOver :
           { Quit loop if no more A/D samples in FIFO }
           mov dx,DD1200_Status ;
           in ax,dx ;
           and ax, ADC_DATA_AVAILABLE ;
           jnz GetSamples ;

           mov BufPointer,bx ;        ; { Save buffer pointer }

EndofADCProcedure :



DACProcedure :

        cmp DACEnabled,0 ;
        jz EndofDACProcedure ;

        { Start D/A clock if at end of DAC output cycle
          and D/A clocks need to be re-started
          ---------------------------------------}
        mov ax,DACSweepStartAt ;
        cmp DACTimer,ax ;
        jl NoDACStart ;
            cmp DACStartRequest,0 ; { Start clock if request flag set }
            jz SkipDACStart ;
                { Start DD1200 Counter 1 (DAC clock) }
                mov dx, DD1200_TimerControl ;
                mov ax, LOADCOUNT or COUNTER1 ;
                out dx,ax ;
                mov ax, ARM or COUNTER1 ;
                out dx,ax ;
                mov DACStartRequest,0 ;
SkipDACStart :
            { Reset DAC intersweep interval counter }
            mov ax,0 ;
            mov DACTimer,ax ;
            jmp EndOfDACProcedure ;

NoDACStart:
           Inc DACTimer ;

EndOfDACProcedure :

        { Clear Counter 4 DONE bit }
        mov dx, DD1200_Clear ;
        mov ax, CLEAR_DONE4 ;
        out dx,ax ;

        { Send end-of-interrupt to both PICs }
        mov al,PIC_EOI ;
        out PIC2_EndOfInterruptRegister,al ;
        out PIC1_EndOfInterruptRegister,al ;

        { Restore VM interrupt state which was saved on stack using DPMI call }
        pop ax ;
        int $31 ;

        pop di ;
        pop bx ;
        pop dx ;
        pop ax ;
        pop es ;
        pop ds ;
        iret ;

Exit :
        pop es ;
        pop ds ;
        end ;
     end ;


Procedure RemoveISR( var IRQNum : Word ) ;
{ ---------------------------------
  Remove interrupt service routine
  ---------------------------------}
var
   Dummy : Integer ;
   Hnd : THandle ;
   IRQVector : Word ;
   IRQMaskBit : Word ;
begin

     { Interrupt vectors : IRQ8=$70...IRQ15=$77 }
     IRQVector := $70 + (IRQNum - 8) ;
     { PIC Mask bit }
     IRQMaskBit := 1 shl (IRQNum - 8) ;

     asm
        push ds ;
        push es ;

        { Mask (i.e. disable) IRQ channel }
        mov bx,IRQMaskBit ;
        in al,PIC2_MaskRegister ;
        or al,bl ;
        out PIC2_MaskRegister, al ;

        { Restore old interrupt handler into table }
        mov ax,OldSeg ;
        mov dx,OldOffset ;
        mov ds,ax
        mov ax,IRQVector ;
        mov ah,$25 ;
        int $21 ;

        pop es ;
        pop ds ;
        end ;

     Hnd := UnLockData( Dummy ) ;
     end ;


Procedure EnableADCMonitor( ADC  : Pointer ;
                            const BufSize : Word ;
                            const CircularBuffer : Word ) ;
{ ----------------------------------------------------------
  Initiate monitoring of DD1200's A/D FIFO buffer
  Enter with :
  ADC : Pointer to A/D buffer to hold incoming samples
  BufSize : No. of samples in A/D buffer
  CirculareBuffer : 1=repeated collectiion into circular buffer
  -------------------------------------------------------------}
begin
     { Clear A/D buffer pointer }
     BufPointer := 0 ;
     BufSeg := GetSegment(ADC) ;
     BufOffset := GetOffset(ADC) ;
     EndOfBuf := BufSize ;
     CircularBufferFlag := CircularBuffer ;
     ADCEnabled := 1 ;
     end ;


Procedure DisableADCMonitor ;
{ ---------------------------------
  Stop monitoring DD1200's A/D FIFO
  ---------------------------------}
begin
     ADCEnabled := 0 ;
     end ;


Procedure EnableDACTimer( const InterSweepInterval : single ) ;
{ ----------------------------------------------------------------------
  Enable D/A sweep timer for the first time
  D/A output sweep is initiated at intervals of "InterSweepInteval" secs
  ----------------------------------------------------------------------}
begin
     DACEnabled := 1 ;
     DACSweepStartAt := Trunc( InterSweepInterval / 0.01 ) ;
     DACTimer := DACSweepStartAt ;
     DACStartRequest := 1 ;
     end ;


Procedure UpdateDACTimer( const InterSweepInterval : single ) ;
{ ----------------------------------------------------------------------
  Update the interval of D/A sweep timer which is already running
  to "InterSweepInteval" secs
  ----------------------------------------------------------------------}
begin
     DACEnabled := 1 ;
     DACSweepStartAt := Trunc( InterSweepInterval / 0.01 ) ;
     { Request DAC clocks to be started at end of DAC cycle }
     DACStartRequest := 1 ;
     end ;



Procedure DisableDACTimer ;
{ --------------------------
  Disable D/A output timing
  -------------------------}
begin
     DACEnabled := 0 ;
     end ;


Function ReadDACTimer : Word ;
begin
     Result := DACTimer ;
     end ;

procedure DisableIRQ( var IRQNum : Word ) ;
var
   IRQVector : Word ;
   IRQMaskBit : Word ;
begin

     { Interrupt vectors : IRQ8=$70...IRQ15=$77 }
     IRQVector := $70 + (IRQNum - 8) ;
     { PIC Mask bit }
     IRQMaskBit := 1 shl (IRQNum-8) ;
     asm
        { Mask (i.e. disable) IRQ channel }
        mov bx,IRQMaskBit ;
        in al,PIC2_MaskRegister ;
        or al,bl ;
        out PIC2_MaskRegister,al ;
        end ;
     end ;

procedure EnableIRQ( var IRQNum : Word ) ;
var
   IRQVector : Word ;
   IRQMaskBit : Word ;
begin

     { Interrupt vectors : IRQ8=$70...IRQ15=$77 }
     IRQVector := $70 + (IRQNum - 8) ;
     { PIC Mask bit }
     IRQMaskBit := 1 shl (IRQNum-8) ;
     asm
        { Mask (i.e. disable) IRQ channel }
        mov bx,IRQMaskBit ;
        not bx ;
        in al,PIC2_MaskRegister ;
        and al,bl ;
        out PIC2_MaskRegister,al ;
        end ;
     end ;


function GetSegment( Pntr : Pointer ) : Word ;
{ --------------------------------------------------------
  Return the segment part of the address stored in Pointer
  --------------------------------------------------------}
var
   pSeg,pOffset,Segment : Word ;
begin
     pSeg := seg(Pntr) ;
     pOffset := Ofs(Pntr) ;
     asm
        push ds ;
        mov ax, pSeg ;
        mov ds,ax ;
        mov si, pOffset ;
        mov ax,ds:[si]+2 ;    { Segment part of address (upper word) }
        mov Segment,ax ;
        pop ds ;
        end ;
     Result := Segment ;
     end ;


function GetOffset( Pntr : Pointer ) : Word ;
{ --------------------------------------------------------
  Return the offset part of the address stored in Pointer
  --------------------------------------------------------}
var
   pSeg,pOffset,OffsetWord : Word ;
begin
     pSeg := seg(Pntr) ;
     pOffset := Ofs(Pntr) ;
     asm
        push ds ;
        mov ax, pSeg ;
        mov ds,ax ;
        mov si, pOffset ;
        mov ax,ds:[si] ;    { offset part of address }
        mov OffsetWord,ax ;
        pop ds ;
        end ;
     Result := OffsetWord ;
     end ;



end.
