library AIO16; uses SysUtils, Classes, Windows, //AIO16NT in 'AIO16NT.pas', //AIO169x in 'AIO169x.pas', //AIO16Global in 'AIO16Global.pas', Registry, WinSvc; {$R *.RES} const FPERR: Double = 22222; METHOD_BUFFERED = 0; METHOD_IN_DIRECT = 1; METHOD_OUT_DIRECT = 2; METHOD_NEITHER = 3; FILE_ANY_ACCESS = 0; FILE_READ_ACCESS = 1; FILE_WRITE_ACCESS = 2; AIO16WDM_TYPE = $9C40; IOCTL_EEPROM_Read = DWORD((AIO16WDM_TYPE shl 16) or (FILE_ANY_ACCESS shl 14) or ($820 shl 2) or METHOD_BUFFERED ); IOCTL_EEPROM_Write = DWORD((AIO16WDM_TYPE shl 16) or (FILE_ANY_ACCESS shl 14) or ($830 shl 2) or METHOD_BUFFERED ); IOCTL_EEPROM_Enable = DWORD((AIO16WDM_TYPE shl 16) or (FILE_ANY_ACCESS shl 14) or ($841 shl 2) or METHOD_BUFFERED ); IOCTL_EEPROM_Disable = DWORD((AIO16WDM_TYPE shl 16) or (FILE_ANY_ACCESS shl 14) or ($840 shl 2) or METHOD_BUFFERED ); IOCTL_ADC_ACQUIRE = DWORD((AIO16WDM_TYPE shl 16) or (FILE_ANY_ACCESS shl 14) or ($800 shl 2) or METHOD_IN_DIRECT); IOCTL_ADC_STOP = DWORD((AIO16WDM_TYPE shl 16) or (FILE_ANY_ACCESS shl 14) or ($801 shl 2) or METHOD_BUFFERED ); IOCTL_ADC_READ = DWORD((AIO16WDM_TYPE shl 16) or (FILE_ANY_ACCESS shl 14) or ($802 shl 2) or METHOD_BUFFERED ); IOCTL_DAC_WRITE = DWORD((AIO16WDM_TYPE shl 16) or (FILE_ANY_ACCESS shl 14) or ($810 shl 2) or METHOD_BUFFERED ); IOCTL_ADC_CAL = DWORD((AIO16WDM_TYPE shl 16) or (FILE_ANY_ACCESS shl 14) or ($808 shl 2) or METHOD_BUFFERED ); IOCTL_DAC_CAL = DWORD((AIO16WDM_TYPE shl 16) or (FILE_ANY_ACCESS shl 14) or ($818 shl 2) or METHOD_BUFFERED ); IOCTL_INIT = DWORD((AIO16WDM_TYPE shl 16) or (FILE_ANY_ACCESS shl 14) or ($880 shl 2) or METHOD_BUFFERED ); IOCTL_GETSETTINGS = DWORD((AIO16WDM_TYPE shl 16) or (FILE_ANY_ACCESS shl 14) or ($881 shl 2) or METHOD_BUFFERED ); IOCTL_BOGUS = DWORD((AIO16WDM_TYPE shl 16) or (FILE_ANY_ACCESS shl 14) or ($8C0 shl 2) or METHOD_IN_DIRECT); IFKey = 'System\CurrentControlSet\Control\DeviceClasses\{E7B61F57-B513-4E81-9EC4-5FAD55FCBF0B}'; { SERVICE_AUTO_START = $02; SERVICE_DEMAND_START = $03; SERVICE_ERROR_NORMAL = $01; SERVICE_KERNEL_DRIVER = $01; } type TOS = (osDunno, osWin95, osWin98, osNT, osNT5); TJumper = (jpADC16SE, jpADCBip, jpADC5V, jpDACB5V, jpDACA5V, jp5, jp6, jp7); TJumpers = set of TJumper; TDriverIOData = record Addr: LongWord; Data: LongWord; ExData: LongWord; end; PDriverIOData = ^TDriverIOData; TBufferEntry = packed record case Boolean of True: ( SingleForm: Single; ); False: ( RationalForm: packed record Numerator: Word; Denominator: Word; end; ); end; PBufferEntry = ^TBufferEntry; var OS: TOS = osDunno; Devices: array of record SymbolicLink: String; hDevice: THandle; Initialized: Boolean; OVL: TOverlapped; SavedBuffer: PBufferEntry; end; CurCardNum: Integer = 0; NextEEOK: LongWord = 0; Buffer: TDriverIOData; Args: PChar = nil; cbRet: LongWord; procedure Rate8254(var RateHz: LongWord; ClockHz: LongWord; var A, B: Word); var I, Best: Integer; Counts, D, Err, LeastErr: Double; SqDivisor: Word; begin Counts := ClockHz / RateHz; SqDivisor := Trunc(SqRt(Counts)); Best := 2; LeastErr := 65535.0; for I := 2 to SqDivisor do begin D := I; Err := Abs(Counts - (D * Round(Counts / D))); if (Err < LeastErr) and (Round(Counts / I) <= 65535) then begin LeastErr := Err; Best := I; if LeastErr = 0 then Break ; end; end; A := Best; B := Round(Counts / A); RateHz := Round(ClockHz / (A * B)); end; function ReadyToTalk(CardNum: LongWord; ErrorResult: PLongWord = nil): Boolean; var Err: LongWord; begin Result := False; if CardNum >= LongWord(Length(Devices)) then Err := ERROR_INVALID_INDEX else if not Devices[CardNum].Initialized then Err := ERROR_NOT_READY else begin Result := True; Err := ERROR_SUCCESS; end; SetLastError(Err); if ErrorResult <> nil then ErrorResult^ := Err ; end; function AIO16_ADC_Acquire(CardNum: LongWord; RateHz: PLongWord; Gain, LoChannel, HiChannel, Count, Oversample: LongWord; UserBuffer: PSingle): LongWord; cdecl; var A, B: Word; begin if not ReadyToTalk(CardNum, @Result) then Exit; if (RateHz = nil) or (RateHz^ = 0) then begin Result := ERROR_INVALID_PARAMETER; SetLastError(Result); end else begin if RateHz^ > 500000 then RateHz^ := 500000; HiChannel := HiChannel and $F; LoChannel := LoChannel and $F; Gain := Gain and $FF; Oversample := Oversample and $FF; Rate8254(RateHz^, 5000000, A, B); Buffer.Addr := (Oversample shl 16) or (HiChannel shl 12) or (LoChannel shl 8) or Gain; //00MMHLGG Buffer.Data := A shl 16 or B; //AAAABBBB Buffer.ExData := Count; if Devices[CardNum].OVL.hEvent = INVALID_HANDLE_VALUE then Devices[CardNum].OVL.hEvent := CreateEvent(nil, True, False, '') ; if DeviceIoControl( Devices[CardNum].hDevice, IOCTL_ADC_ACQUIRE, @Buffer, sizeof(Buffer), UserBuffer, Count * 4, cbRet, @Devices[CardNum].OVL ) then begin Devices[CardNum].SavedBuffer := PBufferEntry(UserBuffer); Result := ERROR_SUCCESS; end else if GetLastError = ERROR_IO_PENDING then begin Devices[CardNum].SavedBuffer := PBufferEntry(UserBuffer); Result := ERROR_SUCCESS; SetLastError(Result); end else Result := GetLastError ; end; end; function Inner_Read(CardNum, MinResults: LongWord): LongWord; var I: Integer; PtrForm: PBufferEntry; Offset: LongInt; begin if not ReadyToTalk(CardNum) then Result := 0 else begin Buffer.Addr := MinResults; Buffer.Data := 0; Buffer.ExData := 0; if DeviceIoControl( Devices[CardNum].hDevice, IOCTL_ADC_Read, @Buffer, sizeof(Buffer), @Buffer, sizeof(Buffer), cbRet, nil ) then begin PtrForm := Devices[CardNum].SavedBuffer; Inc(PtrForm, Buffer.Addr); if Lo(Buffer.Data) <> 0 then //Word4Mult now carries a bool byte for "is bipolar" Offset := -32768 else Offset := 0 ; Buffer.Data := Buffer.Data and $FFFF0000; for I := Buffer.Addr to Buffer.ExData - 1 do begin with PtrForm^ do SingleForm := ((RationalForm.Numerator + Offset) * 20) / (Buffer.Data) ; Inc(PtrForm); end; Result := Buffer.ExData - Buffer.Addr; end else Result := 0 ; end; end; function AIO16_ADC_Read(CardNum: LongWord): LongWord; cdecl; begin Result := Inner_Read(CardNum, 0); end; function AIO16_ADC_WaitRead(CardNum: LongWord; MinResults: LongWord): LongWord; cdecl; begin if MinResults = 0 then MinResults := LongWord(-1) ; Result := Inner_Read(CardNum, MinResults); end; function AIO16_ADC_Stop(CardNum: LongWord): LongWord; cdecl; begin if ReadyToTalk(CardNum, @Result) then begin Buffer.Addr := 0; Buffer.Data := 0; Buffer.ExData := 0; if DeviceIoControl( Devices[CardNum].hDevice, IOCTL_ADC_Stop, @Buffer, sizeof(Buffer), @Buffer, sizeof(Buffer), cbRet, nil ) then Result := ERROR_SUCCESS else Result := ERROR_BAD_DRIVER ; end; end; function AIO16_DAC_Write(CardNum, DACNum: LongWord; Volts: Double): Double; cdecl; begin if not ReadyToTalk(CardNum) then Result := FPERR else begin Buffer.Addr := DACNum; Buffer.Data := Round((Volts * 4096) / 5); //May need /2 if 10V range, but the .SYS handles that Buffer.ExData := 0; if DeviceIoControl( Devices[CardNum].hDevice, IOCTL_DAC_Write, @Buffer, sizeof(Buffer), @Buffer, sizeof(Buffer), cbRet, nil ) then Result := (Buffer.Data * 5) / 4096//actual V - Buffer.Data will be doubled counts if 10V range else Result := FPERR ; end; end; function AIO16_EEPROM_Enable(CardNum: LongWord): LongWord; cdecl; begin if ReadyToTalk(CardNum, @Result) then begin Buffer.Addr := 0; Buffer.Data := 0; Buffer.ExData := 0; if DeviceIoControl( Devices[CardNum].hDevice, IOCTL_EEPROM_Enable, @Buffer, sizeof(Buffer), @Buffer, sizeof(Buffer), cbRet, nil ) then Result := ERROR_SUCCESS else Result := ERROR_BAD_DRIVER ; end; end; function AIO16_EEPROM_Disable(CardNum: LongWord): LongWord; cdecl; begin if ReadyToTalk(CardNum, @Result) then begin Buffer.Addr := 0; Buffer.Data := 0; Buffer.ExData := 0; if DeviceIoControl( Devices[CardNum].hDevice, IOCTL_EEPROM_Disable, @Buffer, sizeof(Buffer), @Buffer, sizeof(Buffer), cbRet, nil ) then Result := ERROR_SUCCESS else Result := ERROR_BAD_DRIVER ; end; end; function WaitForEEOK: LongWord; begin Result := GetTickCount; //Prevent NextEEOK corruption from causing a massive loop if Result + 10 < NextEEOK then NextEEOK := Result + 10 ; while Result < NextEEOK do //Make sure it's at least 10ms after we last wrote Result := GetTickCount ; end; function AIO16_EEPROM_Read(CardNum, Addr: LongWord): LongWord; cdecl; begin if ReadyToTalk(CardNum, @Result) then begin WaitForEEOK; Buffer.Addr := Addr; Buffer.Data := ERROR_BAD_DRIVER; Buffer.ExData := 0; if DeviceIoControl( Devices[CardNum].hDevice, IOCTL_EEPROM_Read, @Buffer, sizeof(Buffer), @Buffer, sizeof(Buffer), cbRet, nil ) then Result := Buffer.Data else Result := ERROR_BAD_DRIVER ; end; end; { procedure DelayOfPlenty; var TickCount: LongWord; begin TickCount := GetTickCount + 5; repeat until GetTickCount > TickCount; end; } function AIO16_EEPROM_Write(CardNum: LongWord; Addr: LongWord; Data: LongWord): LongWord; cdecl; begin if ReadyToTalk(CardNum, @Result) then begin NextEEOK := WaitForEEOK + 10; //Wait, then set NextEEOK to 10ms from now Buffer.Addr := Addr; Buffer.Data := Data; Buffer.ExData := 0; if DeviceIoControl( Devices[CardNum].hDevice, IOCTL_EEPROM_Write, @Buffer, sizeof(Buffer), @Buffer, sizeof(Buffer), cbRet, nil ) then Result := ERROR_SUCCESS else Result := ERROR_BAD_DRIVER ; end; end; function AIO16_ADC_Cal(CardNum: LongWord; M, B: LongWord): LongWord; cdecl; begin if ReadyToTalk(CardNum, @Result) then begin Buffer.Addr := 0; Buffer.Data := M; Buffer.ExData := B; if DeviceIoControl( Devices[CardNum].hDevice, IOCTL_ADC_CAL, @Buffer, sizeof(Buffer), @Buffer, sizeof(Buffer), cbRet, nil ) then Result := ERROR_SUCCESS else Result := ERROR_BAD_DRIVER ; end; end; function AIO16_DAC_Cal(CardNum: LongWord; DACNum, M: LongWord): LongWord; cdecl; begin if ReadyToTalk(CardNum, @Result) then begin Buffer.Addr := DACNum; Buffer.Data := M; Buffer.ExData := 0; if DeviceIoControl( Devices[CardNum].hDevice, IOCTL_DAC_CAL, @Buffer, sizeof(Buffer), @Buffer, sizeof(Buffer), cbRet, nil ) then Result := ERROR_SUCCESS else Result := ERROR_BAD_DRIVER ; end; end; function AIO16_LoadCal(CardNum: LongWord): LongWord; cdecl; begin if ReadyToTalk(CardNum, @Result) then begin Buffer.Addr := 0; Buffer.Data := 0; Buffer.ExData := 0; if DeviceIoControl( Devices[CardNum].hDevice, IOCTL_Init, @Buffer, sizeof(Buffer), @Buffer, sizeof(Buffer), cbRet, nil ) then Result := ERROR_SUCCESS else Result := ERROR_BAD_DRIVER ; end; end; function AIO16_GetSettings(CardNum: LongWord; ADC16SE, ADCBip, ADC5V, DACA5V, DACB5V: PLongWord): LongWord; cdecl; var Jumpers: TJumpers; JumperByte: Byte absolute Jumpers; begin if ReadyToTalk(CardNum, @Result) then begin Buffer.Addr := 0; Buffer.Data := 0; Buffer.ExData := 0; if DeviceIoControl( Devices[CardNum].hDevice, IOCTL_GetSettings, @Buffer, sizeof(Buffer), @Buffer, sizeof(Buffer), cbRet, nil ) then begin JumperByte := Buffer.Data; if ADC16SE <> nil then ADC16SE^ := Ord(jpADC16SE in Jumpers); if ADCBip <> nil then ADCBip ^ := Ord(jpADCBip in Jumpers); if ADC5V <> nil then ADC5V ^ := Ord(jpADC5V in Jumpers); if DACA5V <> nil then DACA5V ^ := Ord(jpDACA5V in Jumpers); if DACB5V <> nil then DACB5V ^ := Ord(jpDACB5V in Jumpers); Result := 0; end else Result := ERROR_BAD_DRIVER ; end; end; function AIO16_Init(CardNum: LongWord; Base: PLongWord): LongWord; cdecl; begin if OS in [osDunno, osWin95, osNT] then begin Result := ERROR_OLD_WIN_VERSION; SetLastError(Result); end else if CardNum >= LongWord(Length(Devices)) then begin Result := ERROR_INVALID_INDEX; SetLastError(Result); end else begin Devices[CardNum].hDevice := CreateFile( PChar(Devices[CardNum].SymbolicLink), GENERIC_READ or GENERIC_WRITE, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0 ); if Devices[CardNum].hDevice = INVALID_HANDLE_VALUE then begin Result := GetLastError; Exit; end; Buffer.Addr := 0; Buffer.Data := ERROR_BAD_DRIVER; Buffer.ExData := 0; if DeviceIoControl( Devices[CardNum].hDevice, IOCTL_Init, @Buffer, sizeof(Buffer), @Buffer, sizeof(Buffer), cbRet, nil ) then begin if Base <> nil then Base^ := Buffer.Addr ; Result := Buffer.Data; end else Result := GetLastError ; end; if Result = ERROR_SUCCESS then Devices[CardNum].Initialized := True; end; function AIO16_GetNumCards: LongWord; cdecl; begin Result := Length(Devices); end; function VBAIO16_ADC_Acquire(CardNum: LongWord; RateHz: PLongWord; Gain, LoChannel, HiChannel, Count, Oversample: LongWord; UserBuffer: PSingle): LongWord; stdcall; begin Result := AIO16_ADC_Acquire(CardNum, RateHz, Gain, LoChannel, HiChannel, Count, Oversample, UserBuffer); end; function VBAIO16_ADC_Stop(CardNum: LongWord): LongWord; stdcall; begin Result := AIO16_ADC_Stop(CardNum); end; function VBAIO16_ADC_Read(CardNum: LongWord): LongWord; stdcall; begin Result := AIO16_ADC_Read(CardNum); end; function VBAIO16_ADC_WaitRead(CardNum: LongWord; MinResults: Integer): LongWord; stdcall; begin Result := AIO16_ADC_WaitRead(CardNum, MinResults); end; function VBAIO16_DAC_Write(CardNum, DACNum: LongWord; Volts: Double): Double; stdcall; begin Result := AIO16_DAC_Write(CardNum, DACNum, Volts); end; function VBAIO16_EEPROM_Enable(CardNum: LongWord): LongWord; stdcall; begin Result := AIO16_EEPROM_Enable(CardNum); end; function VBAIO16_EEPROM_Disable(CardNum: LongWord): LongWord; stdcall; begin Result := AIO16_EEPROM_Disable(CardNum); end; function VBAIO16_EEPROM_Read(CardNum, Addr: LongWord): LongWord; stdcall; begin Result := AIO16_EEPROM_Read(CardNum, Addr); end; function VBAIO16_EEPROM_Write(CardNum, Addr, Data: LongWord): LongWord; stdcall; begin Result := AIO16_EEPROM_Write(CardNum, Addr, Data); end; function VBAIO16_ADC_Cal(CardNum, M, B: LongWord): LongWord; stdcall; begin Result := AIO16_ADC_Cal(CardNum, M, B); end; function VBAIO16_DAC_Cal(CardNum, DACNum, M: LongWord): LongWord; stdcall; begin Result := AIO16_DAC_Cal(CardNum, DACNum, M); end; function VBAIO16_LoadCal(CardNum: LongWord): LongWord; stdcall; begin Result := AIO16_LoadCal(CardNum); end; function VBAIO16_GetSettings(CardNum: LongWord; ADC16SE, ADCBip, ADC5V, DACA5V, DACB5V: PLongWord): LongWord; stdcall; begin Result := AIO16_GetSettings(CardNum, ADC16SE, ADCBip, ADC5V, DACA5V, DACB5V); end; function VBAIO16_Init(CardNum: LongWord; Base: PLongWord): LongWord; stdcall; begin Result := AIO16_Init(CardNum, Base); end; function VBAIO16_GetNumCards: LongWord; stdcall; begin Result := AIO16_GetNumCards; end; exports //Order is inviolate; add new entries to the beginning AIO16_EEPROM_Disable , VBAIO16_EEPROM_Disable , AIO16_EEPROM_Disable name '_AIO16_EEPROM_Disable', AIO16_EEPROM_Enable , VBAIO16_EEPROM_Enable , AIO16_EEPROM_Enable name '_AIO16_EEPROM_Enable' , AIO16_EEPROM_Read , VBAIO16_EEPROM_Read , AIO16_EEPROM_Read name '_AIO16_EEPROM_Read' , AIO16_EEPROM_Write , VBAIO16_EEPROM_Write , AIO16_EEPROM_Write name '_AIO16_EEPROM_Write' , AIO16_ADC_Acquire , VBAIO16_ADC_Acquire , AIO16_ADC_Acquire name '_AIO16_ADC_Acquire' , AIO16_ADC_Stop , VBAIO16_ADC_Stop , AIO16_ADC_Stop name '_AIO16_ADC_Stop' , AIO16_ADC_Read , VBAIO16_ADC_Read , AIO16_ADC_Read name '_AIO16_ADC_Read' , AIO16_ADC_WaitRead , VBAIO16_ADC_WaitRead , AIO16_ADC_WaitRead name '_AIO16_ADC_WaitRead' , AIO16_DAC_Write , VBAIO16_DAC_Write , AIO16_DAC_Write name '_AIO16_DAC_Write' , AIO16_ADC_Cal , VBAIO16_ADC_Cal , AIO16_ADC_Cal name '_AIO16_ADC_Cal' , AIO16_DAC_Cal , VBAIO16_DAC_Cal , AIO16_DAC_Cal name '_AIO16_DAC_Cal' , AIO16_LoadCal , VBAIO16_LoadCal , AIO16_LoadCal name '_AIO16_LoadCal' , AIO16_GetSettings , VBAIO16_GetSettings , AIO16_GetSettings name '_AIO16_GetSettings' , AIO16_Init , VBAIO16_Init , AIO16_Init name '_AIO16_Init' , AIO16_GetNumCards , VBAIO16_GetNumCards , AIO16_GetNumCards name '_AIO16_GetNumCards' ; procedure LoadCardList; var Reg: TRegistry; I: Integer; SubKeys: TStringList; Linked: Boolean; begin Reg := TRegistry.Create(KEY_ENUMERATE_SUB_KEYS or KEY_QUERY_VALUE); Reg.RootKey := HKEY_LOCAL_MACHINE; if Reg.OpenKey(IFKey, False) then begin SubKeys := TStringList.Create; Reg.GetKeyNames(SubKeys); Reg.CloseKey; I := 0; while I < SubKeys.Count do begin if Reg.OpenKey(IFKey + '\' + SubKeys[I] + '\#\Control', False) then begin try Linked := Reg.ReadBool('Linked'); except Linked := False; end; Reg.CloseKey; if not Linked then begin SubKeys.Delete(I); Continue; end; end else begin SubKeys.Delete(I); Continue; end; if not Reg.OpenKey(IFKey + '\' + SubKeys[I] + '\#', False) then begin SubKeys.Delete(I); Continue; end; try SubKeys[I] := Reg.ReadString('SymbolicLink'); Inc(I); except SubKeys.Delete(I); end; Reg.CloseKey; end; SetLength(Devices, SubKeys.Count); for I := 0 to High(Devices) do with Devices[I] do begin SymbolicLink := SubKeys[I]; hDevice := INVALID_HANDLE_VALUE; Initialized := False; OVL.Internal := 0; OVL.InternalHigh := 0; OVL.Offset := 0; OVL.OffsetHigh := 0; OVL.hEvent := INVALID_HANDLE_VALUE; SavedBuffer := nil; end; SubKeys.Free; end; Reg.Free; end; function GetOS: TOS; var V: OSVERSIONINFO; begin Result := osDunno; V.dwOSVersionInfoSize := sizeof(OSVERSIONINFO); GetVersionEx(V); case V.dwPlatformId of VER_PLATFORM_WIN32s: Result := osWin95; // Win32s doesn't have WDM, does it? VER_PLATFORM_WIN32_WINDOWS: if ((V.dwMajorVersion = 4) and (V.dwMinorVersion < 10)) or (V.dwMajorVersion < 4) then Result := osWin95 else Result := osWin98 ; VER_PLATFORM_WIN32_NT: if V.dwMajorVersion <= 4 then Result := osNT else Result := osNT5 ; end; end; procedure DLLHandler(Reason: Integer); var I: Integer; begin if (Reason = DLL_PROCESS_DETACH) then for I := 0 to High(Devices) do if Devices[I].hDevice <> INVALID_HANDLE_VALUE then CloseHandle(Devices[I].hDevice) ; end; begin DLLProc := @DLLHandler; OS := GetOS; //if (OS = osNT) or (OS = osWin95) then Exit; LoadCardList; end.