unit Dd1200;
{ =================================================================
  WinWCP -Axon Instruments Digidata 1200 Interface Library V1.0
  (c) John Dempster, University of Strathclyde, All Rights Reserved
  13/5/97
  =================================================================}

interface

uses WinTypes,Dialogs, SysUtils, WinProcs,global, shared, vds ;

  procedure DD_ConfigureHardware( BaseAddress, IRQNum, DMAChannel : Word ) ;
  Procedure DD_GetADCVoltageRangeOptions( var RangeOptions : array of TADCRange ;
                                          var NumOptions : Integer) ;
  procedure  DD_GetSamplingIntervalRange( var MinInterval,MaxInterval : single ) ;
  procedure DD_InitialiseBoard ;
  procedure DD_ADCToMemory( var ADCBuf : Array of Integer ;
                            nChannels : LongInt ;
                            nSamples : LongInt ;
                            var dt : Single ;
                            ADCVoltageRange : Single ;
                            WaitForExtTrigger : Boolean ;
                            CircularBuffer : Boolean ) ;
procedure DD_ReadFIFO( var ADCBuf : Array of Integer ;
                       var ADCPointer : LongInt  ) ;
  procedure DD_SetADCClock( dt : single ; WaitForExtTrigger : Boolean ) ;
  procedure DD_CalculateClockTicks( var SamplingInterval : single ;
                                  var Ticks : LongInt ;
                                  var FrequencySource : Word ) ;
  procedure  DD_StopADC ;
  procedure  DD_MemoryToDAC( var DACBuf : array of integer ;
                             nChannels : LongInt ;
                             nPoints : LongInt ;
                             dt : Single ;
                             NumRepeats : LongInt ;
                             var DACActive : boolean ) ;

  procedure DD_ConvertToDACCodes( var DACBuf : Array of Integer ;
                                  nChannels : LongInt ;
                                  nPoints : LongInt ) ;
  procedure DD_StartTimer4( dt : single ) ;
  procedure DD_StopTimer4 ;
  procedure DD_SetDACClock( dt : single ; nChannels : LongInt ) ;
  procedure  DD_StopDAC ;
  procedure  DD_WriteDACsAndDigitalPort( var DACVolts : array of Single ;
                                         nChannels : LongInt ;
                                         DigValue : Integer ) ;
  procedure DD_CombineDACAndDigitalWaveforms( var DACBuf : Array of Integer ;
                                              var DigBuf : Array of Integer ;
                                              nChannels : LongInt ;
                                              nPoints : LongInt ) ;

 procedure DD_GetLabInterfaceInfo( var Supplier, Model : String ) ;

  function  DD_GetMaxDACVolts : single ;
  procedure  DD_GetChannelOffsets( var Offsets : Array of LongInt ; NumChannels : LongInt ) ;
  function  DD_IsLabInterfaceAvailable : boolean ;
  procedure DD_CloseLaboratoryInterface ;


implementation


function InstallISR( const IRQNum : Word ;
                     const DD1200_Base : Word ) : Word ; far ; external 'dd1200i' ;
Procedure EnableADCMonitor( ADCPointer  : Pointer ;
                            const BufSize : Word ;
                            const CircularBuffer : Word ) ; far ; external 'dd1200i' ;
Procedure DisableADCMonitor ; far ; external 'dd1200i' ;
Procedure EnableDACTimer( const InterSweepInterval : Single ) ; far ; external 'dd1200i' ;
Procedure UpdateDACTimer( const InterSweepInterval : Single ) ; far ; external 'dd1200i' ;
Procedure DisableDACTimer ; far ; external 'dd1200i' ;
Procedure RemoveISR( var IRQNum : Word )  ; far ; external 'dd1200i' ;
procedure DisableIRQ( var IRQNum : Word )  ; far ; external 'dd1200i' ;
procedure EnableIRQ( var IRQNum : Word )  ; far ; external 'dd1200i' ;

const
     Device = 1 ;
     ClockPeriod = 2.5E-7 ; { 4MHz clock }
     DACVoltageRangeMax = 10.23 ;

     { Bit maps for DAC data at 00 (HEX) }

     DACINHIBITADC = $0001 ; {DAC bit 0 used to inhibit ADC   }
     DACDATAMASK = $FFF0 ; { Mask out the non-DAC bits }

     { Bit maps for ADC data at 00 (HEX) }

     ADCTAGMASK = $0001 ; { Mask for Tag bit in ADC word }
     ADCXTRIGMASK = $0002 ; { Mask for XTrig bit in ADC word }
     ADCDATAMASK = $FFF0 ; { Mask out the non-ADC bits }

     DIGIDATA1200ID = 2 ; { ID value for DD1200 }

     { Bit maps for ADC/DAC/Trig control regsiter at 08h (HEX) }
     ADCINHIBIT = $0080 ; { Enable ADC inhibit via DAC bit 0 }
     ADCGEARENABLE = $0020 ; { Enable ADC gearshift timing }
     { Enable/disable scanlist programming }
     ADCSCANLISTENABLE   = $10 ; {0000000000010000}
     ADCSCANLISTDISABLE  = $FFEF ; {1111111111101111}
     { Enable/disable split-click mode }
     AdcSplitClockEnable = $20 ; {0000000000100000}
     AdcSplitClockDisable = $FFDF ; {1111111111011111}

     ASYNCHDIGITALENABLE = $0008 ; { Enable asynch digital output }
     ADCASYNCDAC = $0004 ;    {Enable ADC asynchronous with DAC }
     DACCHAN1ENABLE = $0002 ; { Enable DAC channel 1 for output }
     DACCHAN0ENABLE = $0001 ; { Enable DAC channel 0 for output }

     { Constants for DMA/Interrupt configuration port at 0A (HEX) }

     INTERRUPTENABLE = $8000 ;{ Enable interrupt requests }
     INTERRUPTIRQ10 = $0000 ; { Enable interrupt via IRQ 10 }
     INTERRUPTIRQ11 = $2000 ;{ Enable interrupt via IRQ 11 }
     INTERRUPTIRQ12 = $4000 ; { Enable interrupt via IRQ 12 }
     INTERRUPTIRQ15 = $6000 ; { Enable interrupt via IRQ 15 }
     INTWHENADCDONE = $0800 ; { Enable interrupt when ADC done }
     INTWHENADCOVERRUN = $0400 ; { Enable interrupt on ADC overrun }
     INTWHENDMADONE = $0200 ; { Enable interrupt on DMA complete }
     INTWHENTIMER4DONE = $0100 ; { Enable interrupt on Timer 4 done }
     INTWHENTIMER3DONE = $0080 ; { Enable interrupt on Timer 3 done }
     INTWHENTIMER1DONE = $0040 ; { Enable interrupt on Timer 1 done }
     ADCSINGLEDMA = $0010 ; { Enable ADC single transfer mode }


     DMACHANNEL5 = $0001 ; { Use DMA channel 5 }
     DMACHANNEL6  = $0002 ; { Use DMA channel 6 }
     DMACHANNEL7 = $0003 ; { Use DMA channel 7 }
     DMAADCSHIFT = $1 ; { DMA channel shift for ADC }
     DMADACSHIFT = $4 ;{  DMA channel shift for DAC }

     { Bit maps for Scan list register at 0E (HEX) }

     SCANLASTENTRYFLAG = $8000 ; { Set this bit in last list entry }
     SCANCHANNELGAIN1 = $0000 ; { Gain bits for gain of 1 }
     SCANCHANNELGAIN2 = $2000 ; { Gain bits for gain of 2 }
     SCANCHANNELGAIN4 = $4000 ; { Gain bits for gain of 4 }
     SCANCHANNELGAIN8 = $6000 ; { Gain bits for gain of 8NULL }
     SCANCHANNELSHIFT = 256 ; { Shift for ADC channel number }

     {  Status code read from ADC/DAC status at 16 (HEX) }
     ADCOVERRUN	 = $0080 ; {ADC has overrun the FIFO }
     ADCDATAREADY = $0040 ; {ADC FIFO has one or more samples}
     DMATRANSFERDONE = $0020 ; {A DMA channel has reached TC}
     TIMER4DONE	 = $0004 ; {Timer channel 4 has reached TC}
     TIMER3DONE	 = $0002 ; {Timer channel 3 has reached TC}
     TIMER1DONE	 = $0001 ; {Timer channel 1 has reached TC}

     { Commands issued to the Clear register at 1A (HEX) }
     ADCSTARTCONVERT	 = $0080 ;
     RESETDMADONE	 = $0020 ;
     RESETADCFLAGS	 = $0010 ;
     RESET_SCAN_LIST	 = $10 ;
     RESET_XTRIG_TAG	 = $10 ;
     RESETDACFLAGS	 = $08 ;
     RESETTIMER4DONE	 = $04 ;
     RESETTIMER3DONE	 = $02 ;
     RESETTIMER1DONE	 = $01 ;
     RESETWHOLEBOARD	 = $7F ;

     { Bit-map for the registers accessed from the 9513 Command Port. }
     MASTERMODE   = $17 ;
     STATUSREG	  = $1F ;
     MODEREGISTER = $00 ;
     LOADREGISTER = $08 ;
     HOLDREGISTER = $10 ;

     { The following three constants need to be ORed with a counter selector. }
     MODE_REG	  = $00 ;
     LOAD_REG	  = $08 ;
     HOLD_REG	  = $10 ;
     CTR1_GRP	  = $01 ;
     CTR2_GRP	  = $02 ;
     CTR3_GRP	  = $03 ;
     CTR4_GRP	  = $04 ;
     CTR5_GRP	  = $05 ;

     { Commands for the 9513 Command Port. }
     ARM 	  = $20 ;
     LOADCOUNT	  = $40 ;
     LOADARM	  = $60 ;
     DISARM	  = $C0 ;
     CLEAROUT	  = $E0 ;
     SETOUT	  = $E8 ;
     SET16BITMODE  = $EF ;
     MASTERRESET   = $FF ;

     {Master mode configuration
c Set up the Master Mode Register :
c     15 : scaler control = BCD division.	1:BCD
c     14 : enable data pointer auto-increment.	1:Off
c     13 : data-bus width = 8 bits.		1:16-bit
c     12 : FOUT gate ON.			1:Off
c   11-8 : FOUT divider = divided by 16.	0000
c    7-4 : FOUT source = F1 (1MHZ oscillator).
c      3 : comparator 2 disabled.
c      2 : comparator 1 disabled.
    1-0 : Time of Day disabled. }

     MASTER = $f000 ;

     { Bit-maps for the 9513 counters. }
     COUNTER1 = $01;
     COUNTER2 = $02;
     COUNTER3 = $04;
     COUNTER4 = $08;
     COUNTER5 = $10;

     { Bit-maps for the counter output states in the 9513 Status register. }
     OUT1  = 2 * COUNTER1 ;
     OUT2  = 2 * COUNTER2 ;
     OUT3  = 2 * COUNTER3 ;
     OUT4  = 2 * COUNTER4 ;
     OUT5  = 2 * COUNTER5 ;

     TOGGLE = $2 ;
     REPEAT_COUNT = $20 ; {2#100000 }
     ACTIVE_HIGH_TC = $1 ;
     ACTIVE_HIGH_LEVEL_GATE = $8000 ;

     { Minimum timer counts (TC-toggled) for known versions of the board.
       333 kHz = 3 micro-second period @ 4 MHz TCToggled }
     MINACQCLOCK = 6  ;
     MAXCHANNEL	= 15 ;

     { Error code }
     ERROR_BAD = 1 ;
     ERROR_WARNING = 2 ;
     ERROR_BADKEY = 3 ;
     DIGI_OK  = 0 ;
     DIGI_ID_ERROR  = 1 ;
     DIGI_9513_ERROR  = 2 ;

type
    TDD1200Registers = record
           Base : Word ;
           DACData : Word ;
           ADCData: Word ;
           ID : Word ;
           TimerData : Word ;
           TimerControl : Word ;
           Control : Word ;
           DMAConfig : Word ;
           DigitalIO : Word ;
           ADCScanList : Word ;
           T8254Channel0 : Word ;
           T8254Channel1 : Word ;
           T8254Channel2 : Word ;
           T8254Control : Word ;
           Status : Word ;
           Reset : Word ;
           end ;

var
   DeviceNumber : Integer ;     { Lab. interface board in use }
   ADCVoltageRangeMax : single ;  { Max. positive A/D input voltage range}
   MinSamplingInterval : single ;
   MaxSamplingInterval : single ;
   DD1200Loaded : boolean ;      { True if Digidata 1200 procedures loaded }
   DeviceInitialised : boolean ; { True if hardware has been initialised }
   IOPorts : TDD1200Registers ;
   IRQLine : Word ;
   DACDMAChannel : Word ;
   IRQInstalled : boolean ;
   ControlWord : Word ;
   ConfigWord : Word ;
   DACDDS : TDDS ;
   DACPointer : Integer ;
   DACEndOfBuf : Integer ;
   DMAChannelInUse : Boolean ;


procedure DD_GetLabInterfaceInfo( var Supplier, Model : String ) ;
var
   DeviceType : Word ;
begin
     if not DeviceInitialised then DD_InitialiseBoard ;

     Supplier := 'Axon Instruments Inc.' ;
     DeviceType := InpW(IOPorts.ID) and $3 ;
     case DeviceType of
          2 : Model := 'Digidata 1200 ' ;
          3 : Model := 'Digidata 1200A ' ;
          else Model := 'Digidata 1200? ' ;
          end ;
     Model := Model + format('IO=%xh, IRQ=%d, DMA=%d',
                      [IOPorts.Base,IRQLine,DACDMAChannel]) ;
     end ;


procedure DD_ConfigureHardware( BaseAddress, IRQNum, DMAChannel : Word ) ;
{ -----------------------------------------------------------
  Set Digidata 1200 base address,IRQ line and DAC DMA channel
  -----------------------------------------------------------}
begin
     IOPorts.Base := BaseAddress ;
     IRQLine := IRQNum ;
     DACDMAChannel := DMAChannel ;
     end ;

function DD_IsLabInterfaceAvailable : boolean ;
{ ------------------------------------------------------------
  Check to see if a lab. interface library is available
  ------------------------------------------------------------}
begin
     DD1200Loaded := True ;
     DD_IsLabInterfaceAvailable := DD1200Loaded ;
     end ;


Procedure DD_GetADCVoltageRangeOptions( var RangeOptions : array of TADCRange ;
                                        var NumOptions : Integer) ;
{ ------------------------------------------------------------
  Return a string array of A/D converter input voltage ranges
  ------------------------------------------------------------}
begin
     RangeOptions[0] := ' ±10.24 V ' ;
     RangeOptions[1] := ' ±5.12 V ' ;
     RangeOptions[2] := ' ±2.56 V ' ;
     RangeOptions[3] := ' ±1.28 V ' ;
     NumOptions := 4 ;
     end ;


procedure DD_GetSamplingIntervalRange( var MinInterval,MaxInterval : single ) ;
{ ------------------------------------------------------------
  Return the range of valid sampling intervals for this device
  ------------------------------------------------------------}
begin
     MinInterval := MinSamplingInterval ;
     MaxInterval := MaxSamplingInterval ;
     end ;


function  DD_GetMaxDACVolts : single ;
{ -----------------------------------------------------------------
  Return the maximum positive value of the D/A output voltage range
  -----------------------------------------------------------------}

begin
     Result := DACVoltageRangeMax ;
     end ;


procedure DD_InitialiseBoard ;
{ -------------------------------------------
  Initialise Digidata 1200 interface hardware
  -------------------------------------------}
var
   i : INteger ;
   Value,Err : Word ;
begin

     { Define Digidata 1200 I/O port addresses }
     IOPorts.DACData := IOPorts.Base ;
     IOPorts.ADCData := IOPorts.Base ;
     IOPorts.ID  := IOPorts.Base + $2 ;
     IOPorts.TimerData := IOPorts.Base + $4;
     IOPorts.TimerControl := IOPorts.Base + $6;
     IOPorts.Control := IOPorts.Base + $8 ;
     IOPorts.DMAConfig := IOPorts.Base + $0A ;
     IOPorts.DigitalIO := IOPorts.Base +$0c ;
     IOPorts.ADCScanList := IOPorts.Base + $0E;
     IOPorts.T8254Channel0 := IOPorts.Base + $10 ;
     IOPorts.T8254Channel1 := IOPorts.Base + $12 ;
     IOPorts.T8254Channel2 := IOPorts.Base + $14 ;
     IOPorts.T8254Control := IOPorts.Base + $16 ;
     IOPorts.Status := IOPorts.Base + $18 ;
     IOPorts.Reset  := IOPorts.Base + $1A ;

     { Reset all board functions }
     OutW (IOPorts.Reset, RESETWHOLEBOARD ) ;

     i := InpW( IOPorts.ID ) and $FF ;

     { Set up the 9513 timer chip: master reset. Do I/O in 8-bit mode.
       then set to 16-bit mode. }
     OutB (IOPorts.TimerControl, MASTERRESET ) ;
     OutB (IOPorts.TimerControl, SET16BITMODE ) ;

     { Point the Data Pointer register at the Master Mode register. }
     OutW ( IOPorts.TimerControl, MASTERMODE ) ;

     {	  Set up the Master Mode register:
 	      15  scaler control = BCD division
 	      14  enable data pointer auto-increment
 	      13  data-bus width = 16 bits
 	      12  FOUT gate OFF
 	    11-8  FOUT divider = divide by 16
 	     7-4  FOUT source = F1 (1 MHz oscillator)
 	       3  comparator 2 disabled
 	       2  comparator 1 disabled
 	     1-0  Time-of-Day disabled }

	Value := $6000 ; { 0110 0000 0000 0000 }
        OutW ( IOPorts.TimerData, Value ) ;

        { Configure board to use DMA for D/A output }
        VDS_DisableDMAChannel( DACDMAChannel ) ;
        case DACDMAChannel of
             5 : ConfigWord := (DMACHANNEL5 * DMADACSHIFT) or $10;
             6 : ConfigWord := (DMACHANNEL6 * DMADACSHIFT) or $10;
             7 : ConfigWord := (DMACHANNEL7 * DMADACSHIFT) or $10;
             end ;
        OutW( IOPorts.DMAConfig, ConfigWord ) ;

        { Enable D/A 0 and use COUNTER 1 to control it
 	(Note the use of the variable "ControlWord" to keep the port settings }
        ControlWord := ADCASYNCDAC or DACCHAN0ENABLE ;
	OutW ( IOPorts.Control, ControlWord ) ;

        { Clear D/A outputs }
	OutW( IOPorts.DACData, 0 ) ;

        { Install A/D and D/A activity monitor,
          (interrupt routine activated every 10ms) }
        if not IRQInstalled then begin
           Err := InstallISR(IRQLine,IOPorts.Base) ;
           DD_StartTimer4( 0.01 ) ;
           IRQInstalled := True ;
           end ;

        DeviceInitialised := True ;

     end ;


procedure DD_ADCToMemory( var ADCBuf : Array of Integer ;
                          nChannels : LongInt ;
                          nSamples : LongInt ;
                          var dt : Single ;
                          ADCVoltageRange : Single ;
                          WaitForExtTrigger : Boolean ;
                          CircularBuffer : Boolean ) ;
{ -----------------------------
  Start A/D converter sampling
  -----------------------------}

var
   Gain,GainBits,ChannelBits,iCircFlag : Word ;
   err,Channel,i,ch : Integer ;
   dt1,SamplingInterval : single ;
   nWords,nBytes : LongInt ;
begin
     if not DeviceInitialised then DD_InitialiseBoard ;

     { Inter-sample interval is channel group sampling interval
       divided by number of channels in group. Note that DT1 and DT
       are modified after SET_ADC_CLOCK_DIGIDATA to be precisely equal to an
       interval supported by the interface. }

     dt1 := dt / nChannels ;
     DD_SetADCClock( dt1, WaitForExtTrigger ) ;
     dt := dt1 * nChannels ;

     { Select a gain setting }
     Gain := Trunc(Round( ADCVoltageRangeMax/ADCVoltageRange )) ;
     Case Gain of
          1 : GainBits := 0 ;
          2 : GainBits := 1 ;
          4 : GainBits := 2 ;
          8 : GainBits := 3 ;
          else GainBits := 0 ;
          end ;

     { Program channel gain/select list }

     SetBits( ControlWord, ADCSCANLISTENABLE ) ;
     OutW( IOPorts.Control, ControlWord ) ;

     for ch := 0 to nChannels-1 do begin
         ChannelBits := ch or ($100*ch) or (GainBits*$2000) ;
         if ch = (nChannels-1) then ChannelBits := ChannelBits or $8000 ;
         OutW(IOPorts.ADCScanList, ChannelBits ) ;
         end ;

     ClearBits( ControlWord, ADCSCANLISTENABLE ) ;
     OutW( IOPorts.Control, ControlWord ) ;

     { Reset A/D FIFO & scan list pointer (bit 4)
       9513 DONE3 flag (bit 1) }
     OutW ( IOPorts.Reset, RESET_SCAN_LIST ) ;

     { Enable interrupt routine to monitor A/D conversions
       and to transfer from DD1200 FIFO to ADCBuf }
     nBytes := nChannels*nSamples*2 ;
     if CircularBuffer then iCircFlag := 1
                       else iCircFlag := 0 ;
     EnableADCMonitor( @ADCBuf, nBytes, iCircFlag ) ;

     { Start sampling immediately OR wait for external trigger pulse into GATE 3 }

     if WaitForExtTrigger then begin
           { External trigger mode }
	   OutW (IOPorts.TimerControl, LOADCOUNT or COUNTER2 ) ;
	   OutW (IOPorts.TimerControl, ARM or COUNTER2 ) ;

           { Enable split-clock mode (bit5) which connects OUT 3 to GATE 2 }
           SetBits( ControlWord, ADCSPLITCLOCKENABLE ) ;
	   OutW (IOPorts.Control, ControlWord ) ;
	   OutW (IOPorts.TimerControl, ARM or COUNTER3 ) ;

           end
     else begin
           { Free run mode }
	   ClearBits( ControlWord, ADCSPLITCLOCKENABLE ) ;
	   OutW (IOPorts.Control, ControlWord ) ;
	   OutW (IOPorts.TimerControl, ARM or COUNTER2 ) ;
           end ;

     end ;


procedure DD_SetADCClock( dt : single ; WaitForExtTrigger : Boolean ) ;
{-------------------------------------------------------------
 Set ADC sampling clock
 A/D samples are timed using a 16 bit counter fed via dividers
 from a 0.25us clock. (The digidata 1200's 9513A time channel No.2 is used)
-------------------------------------------------------------}
var
   Mode2Bits,Mode3Bits,FrequencySource : Word ;
   Ticks : LongInt ;
begin

       { Convert A/D sampling period from <dt> (in s) into
       clocks ticks, using a clock frequency which
       ensures that the number of ticks fits into the 9513A's
       16 bit counter register. }

       DD_CalculateClockTicks( dt, Ticks, FrequencySource ) ;

       { Set counter No. 2 mode register to:- repeated counts,
         frequency source period (set by ifreq_source):
         ACTIVE-HIGH terminal count toggled On/Off pulse }

	Mode2bits := (FrequencySource + $A)*$100 or REPEAT_COUNT or ACTIVE_HIGH_TC ;

        { If external triggering requested, set Counter 2 (A/D sampling timer)
          to be gated by an ACTIVE-HIGH level from the terminal count of
          Counter 3. Set Counter 3 for a single count, triggered by an
          ACTIVE-HIGH LEVEL pulse on GATE 3. }

	if WaitForExtTrigger then begin

           { Set up Channel 3 to to a single 2us count when
             the GATE 3 goes high and to toggle its OUT 3 line
             high when the terminal count is reached }

	   OutW( IOPorts.TimerControl, DISARM or COUNTER3 ) ;
	   OutW( IOPorts.TimerControl, CLEAROUT or 3 ) ;

	   Mode3Bits := $B00 or TOGGLE or ACTIVE_HIGH_LEVEL_GATE ;
	   OutW( IOPorts.TimerControl, MODE_REG or CTR3_GRP ) ;
	   OutW( IOPorts.TimerData, Mode3Bits ) ;
	   OutW( IOPorts.TimerControl, LOAD_REG or CTR3_GRP ) ;
	   OutW( IOPorts.TimerData, 3 ) ;
	   OutW( IOPorts.TimerControl, LOADCOUNT or COUNTER3 ) ;

	   Mode2bits := Mode2bits or ACTIVE_HIGH_LEVEL_GATE ;
	   end ;

        { Set Counter 2's mode and load registers and initialise counter
         (If in External Trigger mode, gate Counter 2 with the GATE 2 input }

	OutW( IOPorts.TimerControl, MODE_REG or CTR2_GRP ) ;
	OutW( IOPorts.TimerData, Mode2bits ) ;
	OutW( IOPorts.TimerControl, LOAD_REG or CTR2_GRP ) ;
	OutW( IOPorts.TimerData, Ticks ) ;
	OutW( IOPorts.TimerControl, LOADCOUNT or COUNTER2 ) ;

	end ;


Procedure DD_StopADC ;
{ -------------------------------
  Reset A/D conversion sub-system
  -------------------------------}
begin

     if not DeviceInitialised then DD_InitialiseBoard ;

     { Stop COUNTER 2 which times A/D samples }
     OutW ( IOPorts.TimerControl, DISARM or COUNTER2 ) ;
     OutW ( IOPorts.TimerControl, CLEAROUT or COUNTER2 ) ;

     { Turn FIFO monitor routine off }
     DisableADCMonitor ;
     end ;


procedure DD_ReadFIFO( var ADCBuf : Array of Integer ;
                       var ADCPointer : LongInt  ) ;
var
   DataAvailable : Word ;
   Done : boolean ;
begin
     if ADCPointer <= High(ADCBuf) then begin
        repeat
            DataAvailable := InpW( IOPorts.Status ) and ADCDATAREADY;
            if DataAvailable <> 0 then begin
               ADCBuf[ADCPointer] := InpW( IOPorts.ADCData ) ;
               Inc(ADCPointer) ;
               if ADCPointer <= High(ADCBuf) then Done := False
                                             else Done := True ;
               end
            else Done := True ;
            until Done ;
        end ;
     end ;


procedure DD_CalculateClockTicks( var SamplingInterval : single ;
                                  var Ticks : LongInt ;
                                  var FrequencySource : Word ) ;
{ ---------------------------------------------------
  Convert sampling period from <SamplingInterval> (in s) into
  clocks ticks, using a clock period which ensures that the no.
  of ticks fits into the 9513A's 16 bit counter register.
  Returns no. of ticks in "Ticks" and the clock frequency
  selection index in "FrequencySource
  ---------------------------------------------------}
var
   Scale : single ;
begin
	Scale := 1. / 16. ;
        FrequencySource := 0 ;
        repeat
            Inc(FrequencySource) ;
            Scale := Scale*16. ;
	    Ticks := Trunc( SamplingInterval/(ClockPeriod*Scale) );
            until (Ticks <= 32767) or (FrequencySource >= 5) ;
	SamplingInterval := Ticks*Scale*ClockPeriod ;
	end ;


procedure  DD_MemoryToDAC( var DACBuf : array of integer ;
                           nChannels : LongInt ;
                           nPoints : LongInt ;
                           dt : Single ;
                           NumRepeats : LongInt ;
                           var DACActive : boolean ) ;
{ --------------------------------------------------------------
  Send a voltage waveform stored in DACBuf to the D/A converters
  --------------------------------------------------------------}
var
   Err : Word ;
   AutoInit : boolean ;
   nBytes : LongInt ;
begin
     if not DeviceInitialised then DD_InitialiseBoard ;

     { Stop COUNTER 1 which times D/A updates }
     OutW( IOPorts.TimerControl, DISARM or COUNTER1 ) ;
     OutW( IOPorts.TimerControl, CLEAROUT or COUNTER1 ) ;

     { Disable DMA channel }
     VDS_DisableDMAChannel( DACDMAChannel ) ;
     if DMAChannelInUse then Err := VDS_ReleaseDMABuffer( DACDDS, 0 ) ;
     DMAChannelInUse := False ;

     { *NOTE* DMA channel must be disabled BEFORE D/A FIFO is reset
        to avoid intermittent problems with initiating D/A sweep.
        Don't know why this is necessary 21/5/97 J.D. }

     { Clear D/A FIFO buffer }
     OutW(IOPorts.Reset, RESETDACFLAGS or $229 ) ;

     { Make ADC and DAC have different clocks (DAC clocked by COUNTER 1)
       Take Digital outputs 0..3 from lower 4 bits of D/A word }
     ClearBits( ControlWord, ASYNCHDIGITALENABLE ) ;
     ControlWord := ControlWord or ADCASYNCDAC ;
     OutW( IOPorts.Control, ControlWord ) ;

     { Disable DACs 0 and 1 }
     ClearBits( ControlWord, DACCHAN0ENABLE or DACCHAN1ENABLE ) ;
     { Note ... this disabling step seems to be necessary to make the
       D/A subsystem start reliably when repeating initiated }

     { Enable DAC channel 0 }
     OutW( IOPorts.Control, ControlWord ) ;
     ControlWord := ControlWord or DACCHAN0ENABLE ;
     OutW( IOPorts.Control, ControlWord ) ;
     { Enable DAC channel 1 (if in use }
     if nChannels > 1 then  begin
        ControlWord := ControlWord or DACCHAN1ENABLE ;
        OutW( IOPorts.Control, ControlWord ) ;
        end ;

     { NOTE ... The above two stage enabling of DACs 0 and 1 ensure that
       the DACs take their data from in the DAC FIFO in the order 0,1,0,1...}

     { Set DACs to initial values }
     OutW( IOPorts.DACData, DACBuf[0] ) ;
     if nChannels > 1 then OutW( IOPorts.DACData, DACBuf[1] ) ;

     { Set DAC clock period }
     DD_SetDACClock( dt, nChannels ) ;

     { Request Windows DMA buffer and transfer D/A data into it }
     VDS_DisableDMAChannel( DACDMAChannel ) ;
     if DMAChannelInUse then Err := VDS_ReleaseDMABuffer( DACDDS, 0 ) ;

     nBytes := nChannels*nPoints*2 ;
     Err := VDS_RequestDMABuffer( DACDDS, 2, @DACBuf, nBytes ) ;
     if Err = 0 then begin
        { Program DMA channel }
        if NumRepeats <> 1 then AutoInit := True
                           else AutoInit := False ;
        VDS_ProgramDMAChannel( DACDMAChannel,ReadFromMemory,DACDDS,nBytes,AutoInit ) ;
        DMAChannelInUse := true ;
        end ;

     { If this is the first D/A sweep ... enable the interrupt-driven
       DAC inter-sweep interval timer. Otherwise just update the sweep interval }
     if not DACActive then EnableDACTimer( npoints*dt )
                      else UpdateDACTimer( npoints*dt ) ;

     { Note D/A clock is not running at this point.
       It is initiated by the 10msec monitor interrupt routine
       (see DD12ISR.PAS) }

     DACActive := True ;

     end ;


procedure DD_SetDACClock( dt : single ; nChannels : LongInt ) ;
{ ----------------------------------------
  Set D/A output clock
  Enter with : dt = D/A update interval (s)
  ----------------------------------------}
var
   Ticks : LongInt ;
   FrequencySource,ModeBits : Word ;
   dt1 : single ;
begin
     { D/A outputs are timed using a 16 bit counter fed via dividers
      from a 0.25us clock. (The digidata 1200's 9513A time channel No.1 is used) }
     dt1 := dt / (nChannels) ;
     DD_CalculateClockTicks( dt1, Ticks, FrequencySource ) ;

     { Set counter No. 1 mode register to:- repeated counts,
       frequency source period, ACTIVE-HIGH terminal count toggled On/Off pulse }

     ModeBits := (FrequencySource + $A)*$100 or REPEAT_COUNT or ACTIVE_HIGH_TC ;
     OutW (IOPorts.TimerControl, MODE_REG or CTR1_GRP ) ;
     OutW (IOPorts.TimerData, ModeBits ) ;
     OutW (IOPorts.TimerControl, LOAD_REG or CTR1_GRP ) ;
     OutW (IOPorts.TimerData, Ticks ) ;

     {Note clock does not start yet, ARM command needed }
     end ;


procedure  DD_StopDAC ;
{ ---------------------------------
  Disable D/A conversion sub-system
  ---------------------------------}
var
   Err : Word ;
begin

     if not DeviceInitialised then DD_InitialiseBoard ;

     { Stop COUNTER 1 which times D/A updates }
     OutW( IOPorts.TimerControl, DISARM or COUNTER1 ) ;
     OutW( IOPorts.TimerControl, CLEAROUT or COUNTER1 ) ;

     { Clear D/A FIFO buffer }
     OutW(IOPorts.Reset, RESETDACFLAGS and $300) ;

     { Disable DMA channel }
     VDS_DisableDMAChannel( DACDMAChannel ) ;
     if DMAChannelInUse then Err := VDS_ReleaseDMABuffer( DACDDS, 0 ) ;
     DMAChannelInUse := False ;

     { Disable DAC inter-sweep timer (In interrupt routine) }
     DisableDACTimer ;

     end ;


procedure DD_StartTimer4( dt : single ) ;
{ ----------------------------------------------------------------
  Start DD1200 Counter 4 which drives an interrupt service routine
  which supervises A/D sampling and D/A sweep initiation output
  Enter with :
  dt = D/A update interval (s)
  ----------------------------------------------------------------}
var
   Ticks : LongInt ;
   FrequencySource,ModeBits,ii : Word ;
begin
     { D/A outputs are timed using a 16 bit counter fed via dividers
      from a 0.25us clock. (The digidata 1200's 9513A time channel No.1 is used) }
     DD_CalculateClockTicks( dt, Ticks, FrequencySource ) ;

     { Set counter No. 1 mode register to:- repeated counts,
       frequency source period, ACTIVE-HIGH terminal count toggled On/Off pulse }

     OutW ( IOPorts.TimerControl, DISARM or COUNTER4 ) ;
     OutW ( IOPorts.TimerControl, CLEAROUT or COUNTER4 ) ;

     { Enable selected IRQ line to be activated by Timer 4 DONE flag }
     case IRQLine of
          10 : ConfigWord := ConfigWord or $8100 ;
          11 : ConfigWord := ConfigWord or $A100 ;
          12 : ConfigWord := ConfigWord or $C100 ;
          15 : ConfigWord := ConfigWord or $E100 ;
          end ;
     OutW( IOPorts.DMAConfig, ConfigWord ) ;

     ModeBits := (FrequencySource + $A)*$100 or REPEAT_COUNT or ACTIVE_HIGH_TC ;
     OutW (IOPorts.TimerControl, MODE_REG or CTR4_GRP ) ;
     OutW (IOPorts.TimerData, ModeBits ) ;
     OutW (IOPorts.TimerControl, LOAD_REG or CTR4_GRP ) ;
     OutW (IOPorts.TimerData, Ticks ) ;

     { Start timer ticking }
     OutW(IOPorts.TimerControl, LOADCOUNT or COUNTER4 ) ;
     OutW(IOPorts.TimerControl, ARM or COUNTER4 ) ;

     end ;


procedure DD_StopTimer4 ;
{ -----------------------------------------------
  Stop Counter 4 and disable interrupt generation
  -----------------------------------------------}
begin
     { Stop clock }
     OutW ( IOPorts.TimerControl, DISARM or COUNTER4 ) ;
     OutW ( IOPorts.TimerControl, CLEAROUT or COUNTER4 ) ;
     { Disable interrupts }
     ConfigWord := ConfigWord and $7FF ;
     OutW( IOPorts.DMAConfig, ConfigWord ) ;
     end ;


procedure  DD_WriteDACsAndDigitalPort( var DACVolts : array of Single ;
                                       nChannels : LongInt ;
                                       DigValue : Integer ) ;
{ ----------------------------------------------------
  Update D/A outputs with voltages suppled in DACVolts
  ----------------------------------------------------}
var
   DACScale,dt : single ;
   DACBuf : Array[0..1] of Integer ;
begin

     if not DeviceInitialised then DD_InitialiseBoard ;

     DACScale := MaxADCValue/DACVoltageRangeMax ;
     { D/A 0 }
     DACBuf[0] := (Trunc(DACVolts[0]*DACScale) shl 4) or (DigValue and $F) ;
     { D/A 1 }
     if nChannels > 1 then DACBuf[1] := (Trunc(DACVolts[1]*DACScale) shl 4)
                                           or ((DigValue ) and $F ) ;

     { Stop any D/A activity }
     DD_StopDAC ;

     { Disable DAC channels }
     ClearBits( ControlWord, DACCHAN0ENABLE or DACCHAN1ENABLE ) ;
     OutW( IOPorts.Control, ControlWord ) ;

     { Enable DAC 0 }
     SetBits( ControlWord, DACCHAN0ENABLE ) ;
     OutW( IOPorts.Control, ControlWord ) ;

     { Enable DAC 1 (if needed) }
     if nChannels > 1 then begin
        SetBits( ControlWord, DACCHAN0ENABLE or DACCHAN1ENABLE ) ;
        OutW( IOPorts.Control, ControlWord ) ;
        end ;

     { NOTE ... Enabling the DACs in the order DAC0 then DAC1
       results in the DACs being written to in the order 0,1,0,1...
       see page 27 Digidata 1200 manual }

     { Make digital O/P synchronous with D/A output }
     ClearBits( ControlWord, ASYNCHDIGITALENABLE ) ;

     { Write to DAC 0 }
     OutW( IOPorts.DACData, DACBuf[0] ) ;
     { Write to DAC 1 }
     if nChannels > 1 then OutW( IOPorts.DACData, DACBuf[1] ) ;
     end ;


procedure DD_ConvertToDACCodes( var DACBuf : Array of Integer ;
                                nChannels : LongInt ;
                                nPoints : LongInt ) ;
{ ---------------------------------------------------------------------
  Convert D/A output integers (in -2048 .. 2047 range) to Digidata 1200
  DAC Code (located in upper 12 bits of 16 bit word
  ---------------------------------------------------------------------}
var
   i,iEnd : LongInt ;
begin
     iEnd := (nChannels*nPoints)-1 ;
     for i := 0 to iEnd do DACBuf[i] := DACBuf[i] shl 4 ;
     end ;


procedure DD_CombineDACAndDigitalWaveforms( var DACBuf : Array of Integer ;
                                            var DigBuf : Array of Integer ;
                                            nChannels : LongInt ;
                                            nPoints : LongInt ) ;
{ ------------------------------------------------------------------------
  Digital outputs 0,1,2,3 are derived from lower 4 bits of DAC word.
  This routine ionserts lower 4 bits of digital output words (from DigBuf)
  into D/A output array (DACBuf)
  Enter with :
  DACBuf : Contains DAC output values (in upper 12 bits of word)
  DIGBuf : Contains Digital output word
  nChannels : No of D/A channels
  nPoints : No. of output values
  ------------------------------------------------------------------------}
var
   i,j : LongInt ;
   LoNibble,HiNibble : Integer ;
begin
     j := 0 ;
     for i := 0 to nPoints do begin
         LoNibble := DigBuf[i] and $F ;
         DACBuf[j] := DACBuf[j] or LoNibble ;
         Inc(j) ;
         if nChannels > 1 then begin
            DACBuf[j] := DACBuf[j] or LoNibble ;
            Inc(j) ;
            end ;
         end ;
     end ;


procedure  DD_GetChannelOffsets( var Offsets : Array of LongInt ;
                                 NumChannels : LongInt ) ;
{ --------------------------------------------------------
  Returns the order in which analog channels are acquired
  and stored in the A/D data buffers
  --------------------------------------------------------}
var
   ch : Integer ;
begin
     for ch := 0 to NumChannels-1 do Offsets[ch] := ch ;
     end ;


procedure DD_CloseLaboratoryInterface ;
begin
     if IRQInstalled then begin
        DD_StopTimer4 ;
        RemoveISR( IRQLine ) ;
        IRQInstalled := False ;
        end ;
     end ;



initialization
    DeviceNumber := -2 ;
    DD1200Loaded := False ;
    DeviceInitialised := False ;
    MinSamplingInterval := 5E-6 ;
    MaxSamplingInterval := 1000. ;
    ADCVoltageRangeMax := 10.23 ;
    IOPorts.Base := $320 ;
    IRQLine := 10 ;
    DACDMAChannel := 5 ;
    IRQInstalled := false ;
    DMAChannelInUse := False ;


end.
