unit ADCExports;

interface

uses
	WinAPI.Windows, System.SysUtils, System.Classes,
	Core;

// header files overload ADC_SetRange1 with ADC_SetRangeAll as ADC_SetRange
function ADC_SetRange1(iBoard: TBoardIndex; iSequencer: TSequencerIndex; Ch: TChannelIndex = ciAll; RangeCode: TRangeCode = rcBipolar10): TWinErrCode; cdecl;
function ADC_SetRangeAll(iBoard: TBoardIndex; iSequencer: TSequencerIndex; RangeCt: TUInt32; pRangeCodes: TPRangeCode): TWinErrCode; cdecl;
function ADC_SetRate(iBoard: TBoardIndex; iSequencer: TSequencerIndex; Hz: TDouble): TWinErrCode; cdecl;
function ADC_GetRate(iBoard: TBoardIndex; iSequencer: TSequencerIndex; pHz: TPDouble): TWinErrCode; cdecl;
function ADC_SetChannelCount(iBoard: TBoardIndex; iSequencer: TSequencerIndex; ChCt: TChannelIndex = ciAll): TWinErrCode; cdecl;
function ADC_SetDifferential(iBoard: TBoardIndex; bDifferential: LongBool): TWinErrCode; cdecl;
function ADC_GetImmediateV(iBoard: TBoardIndex; iSequencer: TSequencerIndex; out V: TDouble; Ch: TChannelIndex; Range: TRangeCode = rcNoChange): TWinErrCode; cdecl;
function ADC_GetImmediateRaw(iBoard: TBoardIndex; iSequencer: TSequencerIndex; out Data: TUInt32; Ch: TChannelIndex; Range: TRangeCode = rcNoChange): TWinErrCode; cdecl;
function ADC_GetImmediateScanV(iBoard: TBoardIndex; pVData: TPDouble; Range: TRangeCode = rcNoChange): TWinErrCode; cdecl;
function ADC_GetImmediateScanRaw(iBoard: TBoardIndex; pRawData: TPUInt32; Range: TRangeCode = rcNoChange): TWinErrCode; cdecl;

function ValidateADCRange(var Range: TRangeCode): TWinErrCode;
function VFromRow(Row: TUInt32): Double;
procedure ParseADRow(Row: TUInt32; pStatus: TPUInt16; pTimestamp: TPUInt16; pDataCounts: TPUInt16; pDataV: TPDouble);

function ValidateBoardAndSequencerIndex(iBoard: TBoardIndex; iSequencer: TSequencerIndex): TWinErrCode;



implementation

uses
	Math,
	PIOExports;

function ValidateADCRange(var Range: TRangeCode): TWinErrCode;
begin
	Result := ERROR_SUCCESS;
	case Range of
		rcBipolar12   : Range := rcRangeCode0; rcRangeCode0: ;
		rcBipolar5    : Range := rcRangeCode1; rcRangeCode1: ;
		rcBipolar2_5  : Range := rcRangeCode2; rcRangeCode2: ;
		rcBipolar1_25 : Range := rcRangeCode3; rcRangeCode3: ;
		rcBipolar_625 : Range := rcRangeCode4; rcRangeCode4: ;
		rcBipolar_3125: Range := rcRangeCode5; rcRangeCode5: ;
		rcBipolar10   : Range := rcRangeCode7; rcRangeCode7: ;
		rcNoChange: Result := ERROR_HANDLE_EOF;
		else Result := ERROR_INVALID_PARAMETER;
	end;
end;

function VFromRow(Row: TUInt32): Double;
var
	Counts: TUInt16;
	Control: TUInt16;

begin
	Counts := Row and $0000FFFF;
	Control := Row shr 16;
	if (Control and $8000) <> 0 then begin
		Result := NAN;
	end
	else case Control and rcRangeCode7 of
		//This conversion duplicates the range table from ValidateADCRange, at ×1.024.
		rcRangeCode0: Result := 24.576/$8000 * TSInt16(Counts);
		rcRangeCode1: Result := 10.24/$8000 * TSInt16(Counts);
		rcRangeCode2: Result := 5.12/$8000 * TSInt16(Counts);
		rcRangeCode3: Result := 2.56/$8000 * TSInt16(Counts);
		rcRangeCode4: Result := 1.28/$8000 * TSInt16(Counts);
		rcRangeCode5: Result := 0.64/$8000 * TSInt16(Counts);
		rcRangeCode7: Result := 20.48/$8000 * TSInt16(Counts);
		else Result := NAN;
	end;
end;

procedure ParseADRow(Row: TUInt32; pStatus: TPUInt16; pTimestamp: TPUInt16; pDataCounts: TPUInt16; pDataV: TPDouble);
begin
	if pStatus <> nil then pStatus^ := Row;

	if pTimestamp <> nil then pTimestamp^ := 0;

	if pDataCounts <> nil then pDataCounts^ := Row shr 16;
	if pDataV <> nil then pDataV^ := VFromRow(Row);
end;



function ValidateBoardAndSequencerIndex(iBoard: TBoardIndex; iSequencer: TSequencerIndex): TWinErrCode;
begin
	Result := ValidateBoardIndex(iBoard);
	if Result <> ERROR_SUCCESS then Exit;

	try
		if iSequencer >= 2 then begin
			Result := ERROR_INVALID_INDEX;
		end
		else begin
			Result := ERROR_SUCCESS;
		end;
		SetLastError(Result);
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;



function ADC_SetRange1(iBoard: TBoardIndex; iSequencer: TSequencerIndex; Ch: TChannelIndex = ciAll; RangeCode: TRangeCode = rcBipolar10): TWinErrCode;
var
	Status: TWinErrCode;

begin
	Result := ValidateBoardAndSequencerIndex(iBoard, iSequencer);
	if Result <> ERROR_SUCCESS then Exit;

	try
		Result := ValidateADCRange(RangeCode);
		if Result <> ERROR_SUCCESS then begin
			if Result = ERROR_HANDLE_EOF then Result := ERROR_SUCCESS;
			Exit;
		end;

		Status := ERROR_INTERNAL_ERROR;
		WithSeqThreadingMutexHeld(iBoard, iSequencer, procedure ()
		var
			RangeCodes: TUInt32;
		begin
			if Dev[iBoard].SequencerAcq[iSequencer].AcquireThread <> nil then begin
				Status := ERROR_BUSY;
			end
			else begin
				if Ch = ciAll then begin
					Status := PIO_Out32(iBoard, ADCRangeOffset + dSequencer*iSequencer, $11111111 * RangeCode);
				end
				else if Ch >= ADCChCt then begin
					Status := ERROR_INVALID_INDEX;
				end
				else begin
					RangeCodes := PIO_In32(iBoard, ADCRangeOffset + dSequencer*iSequencer);
					Status := GetLastError;
					if Status <> ERROR_SUCCESS then Exit;

					RangeCodes := RangeCodes and not ($F shl (4*Ch));
					RangeCodes := RangeCodes or (RangeCode shl (4*Ch));
					Status := PIO_Out32(iBoard, ADCRangeOffset + dSequencer*iSequencer, RangeCodes);
				end;
			end;
		end);
		Result := Status;
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_SetRangeAll(iBoard: TBoardIndex; iSequencer: TSequencerIndex; RangeCt: TUInt32; pRangeCodes: TPRangeCode): TWinErrCode;
var
	Ranges: array of TRangeCode;
	iCh: TUNative;
	Status: TWinErrCode;

begin
	Result := ValidateBoardAndSequencerIndex(iBoard, iSequencer);
	if Result <> ERROR_SUCCESS then Exit;

	try
		if RangeCt > ADCChCt then RangeCt := ADCChCt;
		//Copy in the provided ranges.
		SetLength(Ranges, ADCChCt);
		try
			Move(pRangeCodes^, Ranges[0], RangeCt);
		except
			Result := ERROR_INVALID_ADDRESS;
			Exit;
		end;
		//Validate.
		for iCh := 0 to RangeCt-1 do begin
			Result := ValidateADCRange(Ranges[iCh]);
			if Result = ERROR_HANDLE_EOF then Result := ERROR_SUCCESS;
			if Result <> ERROR_SUCCESS then Exit;
		end;
		//If they didn't provide enough, then continue the last one.
		if RangeCt < ADCChCt then
			for iCh := RangeCt to ADCChCt-1 do
				Ranges[iCh] := Ranges[RangeCt-1]
		;
		Status := ERROR_INTERNAL_ERROR;
		WithSeqThreadingMutexHeld(iBoard, iSequencer, procedure ()
		var
			RangeCodes: TUInt32;
			iCh: TUNative;

		begin
			if Dev[iBoard].SequencerAcq[iSequencer].AcquireThread <> nil then begin
				Status := ERROR_BUSY;
			end
			else begin
				//Read(in case of no-change).
				RangeCodes := PIO_In32(iBoard, ADCRangeOffset);
				//Modify.
				for iCh := 0 to ADCChCt-1 do begin
					if Ranges[iCh] = rcNoChange then Continue;

					RangeCodes := RangeCodes and not ($F shl (4*iCh));
					RangeCodes := RangeCodes or (Ranges[iCh] shl (4*iCh));
				end;
				//Write.
				Status := PIO_Out32(iBoard, ADCRangeOffset + dSequencer*iSequencer, RangeCodes);
			end;
		end);
		Result := Status;
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_SetRate(iBoard: TBoardIndex; iSequencer: TSequencerIndex; Hz: TDouble): TWinErrCode;
var
	Divisor: TUInt32;
	Status: TWinErrCode;

begin
	Result := ValidateBoardAndSequencerIndex(iBoard, iSequencer);
	if Result <> ERROR_SUCCESS then Exit;

	with Dev[iBoard] do try
		try
			if Hz = 0 then
				Divisor := 0
			else begin
				Divisor := Round(BaseClock / Hz);
				if Divisor <= 0 then begin
					Result := ERROR_INVALID_DATA;
					Exit;
				end;
			end;
		except
			Result := ERROR_INVALID_DATA;
			Exit;
		end;

		Status := ERROR_INTERNAL_ERROR;
		WithSeqThreadingMutexHeld(iBoard, iSequencer, procedure () begin
			if Dev[iBoard].SequencerAcq[iSequencer].AcquireThread <> nil then begin
				Status := ERROR_BUSY;
			end
			else begin
				Status := PIO_Out32(iBoard, DivisorOffset + dSequencer*iSequencer, Divisor);
			end;
		end);
		Result := Status;
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_GetRate(iBoard: TBoardIndex; iSequencer: TSequencerIndex; pHz: TPDouble): TWinErrCode;
var
	Divisor: LongWord;

begin
	Result := ValidateBoardAndSequencerIndex(iBoard, iSequencer);
	if Result <> ERROR_SUCCESS then Exit;

	try
		pHz^ := 0;
	except
		Result := ERROR_INVALID_ADDRESS;
		Exit;
	end;

	with Dev[iBoard] do try
		Divisor := PIO_In32(iBoard, DivisorOffset + dSequencer*iSequencer);
		Result := GetLastError;
		if Result <> ERROR_SUCCESS then Exit;

		if Divisor = 0 then
			pHz^ := 0
		else
			pHz^ := BaseClock / Divisor
		;
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_SetChannelCount(iBoard: TBoardIndex; iSequencer: TSequencerIndex; ChCt: TChannelIndex = ciAll): TWinErrCode;
var
	StartStop: TUInt32;
	hCh: TChannelIndex;
	Status: TWinErrCode;
	bDifferential: Bool;

begin
	Result := ValidateBoardAndSequencerIndex(iBoard, iSequencer);
	if Result <> ERROR_SUCCESS then Exit;

	try
		if ChCt = ciAll then ChCt := ADCChCt;
		if (ChCt < 1) or (ChCt > ADCChCt) then begin
			Result := ERROR_INVALID_INDEX;
			Exit;
		end;
		hCh := ChCt-1;

		Status := ERROR_INTERNAL_ERROR;
		WithSeqThreadingMutexHeld(iBoard, iSequencer, procedure () begin
			if Dev[iBoard].SequencerAcq[iSequencer].AcquireThread <> nil then begin
				Status := ERROR_BUSY;
				Exit;
			end;

			case Dev[iBoard].RegisterMap of
				rmAIO: begin
					StartStop := PIO_In16(iBoard, ADCStartStopOffset+ dSequencer*iSequencer);
					bDifferential := not (StartStop and ADCCmd_SingleEnded = 0);
					if bDifferential then
						hCh := hCh shl 1
					;
					StartStop := StartStop and not ($7 shl 12);
					StartStop := StartStop or (hCh shl 12);
					Status := PIO_Out16(iBoard, ADCStartStopOffset + dSequencer*iSequencer, StartStop);
				end;
				rmADIO, rmHMAIO: begin
					StartStop := PIO_In32(iBoard, ADCStartStopOffset + dSequencer*iSequencer) or $20000;
					bDifferential := not (StartStop and ADCCmd_SingleEnded = 0);
					if bDifferential then
						hCh := hCh shl 1
					;
					StartStop := StartStop and not ($7 shl 12);
					StartStop := StartStop or (hCh shl 12);
					Status := PIO_Out32(iBoard, ADCStartStopOffset + dSequencer*iSequencer, StartStop);
				end;
			end;
		end);
		Result := Status;
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_SetDifferential(iBoard: TBoardIndex; bDifferential: LongBool): TWinErrCode; cdecl;
var
	StartStop: TUInt32;
	Status: TWinErrCode;

begin
	Result := ValidateBoardIndex(iBoard);
	if Result <> ERROR_SUCCESS then Exit;

	try
		Status := ERROR_INTERNAL_ERROR;
		WithAllThreadingMutexesHeld(iBoard, procedure ()
		var
			iSequencer: TSequencerIndex;
			ThisStatus: TWinErrCode;

		begin
			for iSequencer := 0 to High(Dev[iBoard].SequencerAcq) do
				if Dev[iBoard].SequencerAcq[iSequencer].AcquireThread <> nil then begin
					Status := ERROR_BUSY;
					Exit;
				end
			;

			Status := ERROR_SUCCESS;
			for iSequencer := 0 to High(Dev[iBoard].SequencerAcq) do begin
				case Dev[iBoard].RegisterMap of
					rmAIO: begin
						StartStop := PIO_In16(iBoard, ADCStartStopOffset+ dSequencer*iSequencer);
						if bDifferential then
							StartStop := StartStop and not ADCCmd_SingleEnded
						else
							StartStop := StartStop or ADCCmd_SingleEnded
						;
						ThisStatus := PIO_Out16(iBoard, ADCStartStopOffset + dSequencer*iSequencer, StartStop);
					end;
					rmADIO, rmHMAIO: begin
						StartStop := PIO_In32(iBoard, ADCStartStopOffset + dSequencer*iSequencer) or $20000;
						if bDifferential then
							StartStop := StartStop and not ADCCmd_SingleEnded
						else
							StartStop := StartStop or ADCCmd_SingleEnded
						;
						ThisStatus := PIO_Out32(iBoard, ADCStartStopOffset + dSequencer*iSequencer, StartStop);
					end;

					else ThisStatus := ERROR_BAD_DEV_TYPE;
				end;
				if Status = ERROR_SUCCESS then Status := ThisStatus;
			end;
		end);
		Result := Status;
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_GetImmediateV(iBoard: TBoardIndex; iSequencer: TSequencerIndex; out V: TDouble; Ch: TChannelIndex; Range: TRangeCode = rcNoChange): TWinErrCode;
var
	DataRow: TUInt32;

begin
	try
		try
			V := NAN;
		except
			Result := ERROR_INVALID_ADDRESS;
			Exit;
		end;

		DataRow := 0;
		Result := ADC_GetImmediateRaw(iBoard, iSequencer, DataRow, Ch, Range);
		if Result <> ERROR_SUCCESS then Exit;

		V := VFromRow(DataRow);
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_GetImmediateRaw(iBoard: TBoardIndex; iSequencer: TSequencerIndex; out Data: TUInt32; Ch: TChannelIndex; Range: TRangeCode = rcNoChange): TWinErrCode;
var
	ADData: array of TUInt32;

begin
	Result := ValidateBoardAndSequencerIndex(iBoard, iSequencer);
	if Result <> ERROR_SUCCESS then Exit;

	try
		try
			Data := 0;
		except
			Result := ERROR_INVALID_ADDRESS;
			Exit;
		end;

		if Ch >= ADCChCt then begin
			Result := ERROR_INVALID_INDEX;
			Exit;
		end;

		SetLength(ADData, 2*ADCChCt);
		Result := ADC_GetImmediateScanRaw(iBoard, @ADData[0], Range);
		if Result <> ERROR_SUCCESS then Exit;

		Data := ADData[ADCChCt*iSequencer + Ch];
		//`@
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

(*
function ADC_GetImmediateRaw(iBoard: TBoardIndex; iSequencer: TSequencerIndex; out Data: TUInt32; Ch: TChannelIndex; Range: TRangeCode = rcNoChange): TWinErrCode;
var
	ImmediateCmd: TUInt16;
	Gains: TUInt32;
	Status: TWinErrCode;
	DataRow: array[0..$C] of TUInt32;
	iData: Integer;
	{$IFDEF DEBUG}
		lData: Integer;
		Buf: AnsiString;
	{$ENDIF}

begin
	Result := ValidateBoardAndSequencerIndex(iBoard, iSequencer);
	if Result <> ERROR_SUCCESS then Exit;

	try
		if Ch >= ADCChCt then begin
			Result := ERROR_INVALID_INDEX;
			Exit;
		end;

		if Range <> rcNoChange then begin
			Result := ValidateADCRange(Range);
			if Result <> ERROR_SUCCESS then Exit;

			Gains := PIO_In32(iBoard, ADCRangeOffset + dSequencer*iSequencer);
			Gains := Gains and not ($F shl (Ch*4));
			Gains := Gains or (Range shl (Ch*4));
			PIO_Out32(iBoard, ADCRangeOffset + dSequencer*iSequencer, Gains);
		end;

		for iData := High(DataRow) downto 0 do
			DataRow[iData] := $80000000
		;
		Status := ERROR_INTERNAL_ERROR;
		WithThreadingMutexHeld(iBoard, iSequencer, procedure ()
		var
			iData: Integer;

		begin
			with Dev[iBoard].SequencerAcq[iSequencer] do begin
				if AcquireThread <> nil then begin
					Status := ERROR_BUSY;
					Exit;
				end;

				//PIO_Out8(iBoard, ResetOffset, 1 shl iSequencer);
				//PIO_Out8(iBoard, IRQEnableOffset, 0);
				PIO_out32(iBoard, ADCDataOffset[iSequencer], 0);

				{$IFDEF DEBUG}
					lData := PIO_In32(iBoard, ADCDepthOffset);
					if lData <> 0 then begin
						for iData := 0 to lData-1 do
							PIO_In32(iBoard, ADCDataOffset[iSequencer])
						;
						if (Dev[iBoard].DebugMask and 1) <> 0 then
							AnsiMessageBox('Had to drain ' + AnsiString(IntToHex(lData, 1)) + 'h samples after clear FIFO.', 'AIOAIO', MB_ICONEXCLAMATION)
						;
					end;
				{$ENDIF}

				ImmediateCmd := PIO_In16(iBoard, ADCStartStopOffset + dSequencer*iSequencer);
				ImmediateCmd := ImmediateCmd and not ADCCmd_Continuous;
				ImmediateCmd := ImmediateCmd and not ($7 shl 12);
				ImmediateCmd := ImmediateCmd or (Ch shl 12);
				if Range = rcNoChange then begin
					Range := (PIO_In32(iBoard, ADCRangeOffset + dSequencer*iSequencer) shr (4 * Ch)) and $F;
				end;
				ImmediateCmd := ImmediateCmd and not ($7 shl 7);
				ImmediateCmd := ImmediateCmd or (Range shl 7);

				for iData := High(DataRow) downto 0 do begin
					PIO_Out16(iBoard, ADCImmediateOffset + dSequencer*iSequencer, ImmediateCmd);
					DataRow[iData] := PIO_In32(iBoard, ADCDataOffset[iSequencer]);
				end;
				//The first start is read on the last read.

				PIO_out32(iBoard, ADCDataOffset[iSequencer], 0);

				{
				PIO_Out16(iBoard, ADCImmediateOffset + dSequencer*iSequencer, ImmediateCmd); //This start is read…
				DataRow[5] := PIO_In32(iBoard, ADCDataOffset[iSequencer]);
				PIO_Out16(iBoard, ADCImmediateOffset + dSequencer*iSequencer, ImmediateCmd);
				DataRow[4] := PIO_In32(iBoard, ADCDataOffset[iSequencer]);
				PIO_Out16(iBoard, ADCImmediateOffset + dSequencer*iSequencer, ImmediateCmd);
				DataRow[3] := PIO_In32(iBoard, ADCDataOffset[iSequencer]);
				PIO_Out16(iBoard, ADCImmediateOffset + dSequencer*iSequencer, ImmediateCmd);
				DataRow[2] := PIO_In32(iBoard, ADCDataOffset[iSequencer]);
				PIO_Out16(iBoard, ADCImmediateOffset + dSequencer*iSequencer, ImmediateCmd);
				DataRow[1] := PIO_In32(iBoard, ADCDataOffset[iSequencer]);
				PIO_Out16(iBoard, ADCImmediateOffset + dSequencer*iSequencer, ImmediateCmd);
				DataRow[0] := PIO_In32(iBoard, ADCDataOffset[iSequencer]);          //…on this read.
				}

				Status := ERROR_SUCCESS;
			end;
		end);
		Result := Status;
		Data := DataRow[0];
		{$IFDEF DEBUG}
			if (Dev[iBoard].DebugMask and 1) <> 0 then begin
				Buf := 'ADC_GetImmediateRaw: ' + AnsiString(IntToHex(ImmediateCmd, 4));
				for iData := High(DataRow) downto 0 do
					Buf := Buf + #13#10 + AnsiString(IntToHex(DataRow[iData], 8))
				;
				AnsiMessageBox(Buf, 'AIOAIO', MB_ICONINFORMATION);
			end;
		{$ENDIF}
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;
*)

function ADC_GetImmediateScanV(iBoard: TBoardIndex; pVData: TPDouble; Range: TRangeCode = rcNoChange): TWinErrCode;
var
	RawData: array of TUInt32;
	pThisData: TPDouble;
	iSequencer, hSequencer: TSequencerIndex;
	iChannel: TChannelIndex;
	{$IFDEF DEBUG}
		ReportBuf: AnsiString;
	{$ENDIF}

begin
	Result := ValidateBoardIndex(iBoard);
	if Result <> ERROR_SUCCESS then Exit;

	try
		case Dev[iBoard].RegisterMap of
			rmAIO, rmHMAIO: hSequencer := 1;
			rmADIO: hSequencer := 0;
			else begin
				Result := ERROR_BAD_TOKEN_TYPE;
				Exit;
			end;
		end;

		try
			pThisData := pVData;
			for iSequencer := 0 to hSequencer do
				for iChannel := 0 to 7 do begin
					pThisData^ := NAN;
					Inc(pThisData);
				end
			;
		except
			Result := ERROR_INVALID_ADDRESS;
			Exit;
		end;

		SetLength(RawData, 16);
		for iSequencer := 0 to hSequencer do
			for iChannel := 0 to 7 do
				RawData[iSequencer * 8 + iChannel] := $80000000
		;

		Result := ADC_GetImmediateScanRaw(iBoard, @RawData[0], Range);
		if Result <> ERROR_SUCCESS then Exit;

		{$IFDEF DEBUG}
			ReportBuf := 'Data Conversion:';
		{$ENDIF}
		pThisData := pVData;
		for iSequencer := 0 to hSequencer do
			for iChannel := 0 to 7 do begin
				pThisData^ := VFromRow(RawData[iSequencer * 8 + iChannel]);
				{$IFDEF DEBUG}
					ReportBuf := ReportBuf + (#13#10
						+ AnsiString(IntToHex(RawData[iSequencer * 8 + iChannel], 8))
						+ ' > '
						+ AnsiString(FloatToStrF(pThisData^, ffFixed, 16, 3))
					);
				{$ENDIF}
				Inc(pThisData);
			end
		;
		{$IFDEF DEBUG}
			if (Dev[iBoard].DebugMask and 1) <> 0 then begin
				//AnsiMessageBox(ReportBuf, 'Data Conversion', MB_ICONINFORMATION);
			end;
			with Dev[iBoard] do if DebugMask <> 0 then DebugMask := DebugMask shr 1;
		{$ENDIF}
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

procedure ClearFIFO(iBoard: TBoardIndex; iSequencer: TSequencerIndex);
{$IFDEF DEBUG}
var
	lData: Integer;
{$ENDIF}

begin
	PIO_Out32(iBoard, ADCDataOffset[iSequencer], 0);
	{$IFDEF DEBUG}
		if (Dev[iBoard].DebugMask and 1) <> 0 then begin
			lData := PIO_In32(iBoard, ADCDepthOffset);
			if lData <> 0 then
				AnsiMessageBox(AnsiString(IntToHex(lData, 1) + 'h samples left after clear-FIFO.'), 'ClearFIFO Not Enough', MB_ICONEXCLAMATION)
			;
		end;
	{$ENDIF}
end;

procedure SoftDrainFIFO(iBoard: TBoardIndex; iSequencer: TSequencerIndex);
var
	iData, lData: Integer;

begin
	lData := PIO_In32(iBoard, ADCDepthOffset);
	for iData := 0 to lData-1 do
		PIO_In32(iBoard, ADCDataOffset[iSequencer])
	;
end;

procedure HardDrainFIFO(iBoard: TBoardIndex; iSequencer: TSequencerIndex);
var
	iData: Integer;

begin
	for iData := 0 to $FFF do
		PIO_In32(iBoard, ADCDataOffset[iSequencer])
	;
	{$IFDEF DEBUG}
		if (Dev[iBoard].DebugMask and 1) <> 0 then begin
			iData := PIO_In32(iBoard, ADCDepthOffset);
			if iData <> 0 then
				AnsiMessageBox(AnsiString(IntToHex(iData, 1) + 'h samples left after 1000h reads.'), 'HardDrainFIFO Not Enough', MB_ICONEXCLAMATION)
			;
		end;
	{$ENDIF}
end;

function GetImmediateScanRaw_Native16(iBoard: TBoardIndex; pRawData: TPUInt32; Range: TRangeCode; const NewGains: array of TUInt32): TWinErrCode;
var
	ImmediateCmd: TUInt16;
	DataRow: array[0..$E] of TUInt32;
	Status: TWinErrCode;
	pThisData: TPUInt32;
	iData: Integer;
	iSequencer: TSequencerIndex;
	iChannel: TChannelIndex;
	{$IFDEF DEBUG}
		LogCmd: array of TUInt16;
		Buf: AnsiString;
	{$ENDIF}
	Gains: array of TUInt32;

begin
	Result := ERROR_SUCCESS;

	//Workaround for a flaw in functional programming.
	SetLength(Gains, Length(NewGains));
	for iSequencer := 0 to High(Gains) do
		Gains[iSequencer] := NewGains[iSequencer]
	;

	{$IFDEF DEBUG}
		SetLength(LogCmd, Length(DataRow));
	{$ENDIF}

	try
		pThisData := pRawData;
		for iChannel := $0 to $F do begin
			pThisData^ := $80000000;
			Inc(pThisData);
		end;
	except
		Result := ERROR_INVALID_ADDRESS;
		Exit;
	end;

	pThisData := pRawData;
	for iSequencer := 0 to 1 do begin
		for iData := High(DataRow) downto 0 do DataRow[iData] := $80000000;
		Status := ERROR_INTERNAL_ERROR;
		WithSeqThreadingMutexHeld(iBoard, iSequencer, procedure ()
		var
			iData: Integer;
			iChannel: TChannelIndex;

		begin
			with Dev[iBoard].SequencerAcq[iSequencer] do begin
				if AcquireThread <> nil then begin
					Status := ERROR_BUSY;
					Exit;
				end;

				ClearFIFO(iBoard, iSequencer);

				ImmediateCmd := PIO_In16(iBoard, ADCStartStopOffset + dSequencer*iSequencer);
				ImmediateCmd := ImmediateCmd and not ADCCmd_Continuous;

				for iChannel := 0 to 7 do begin
					Range := (Gains[iSequencer] shr (4*iChannel)) and $F;

					ImmediateCmd := ImmediateCmd and not (($7 shl 12) or ($7 shl 7));
					ImmediateCmd := ImmediateCmd or (iChannel shl 12) or (Range shl 7);

					{$IFDEF DEBUG}
						LogCmd[iChannel] := ImmediateCmd;
					{$ENDIF}

					PIO_Out16(iBoard, ADCStartStopOffset + dSequencer*iSequencer, ImmediateCmd);
				end;
				for iData := 8 to High(DataRow) do begin
					{$IFDEF DEBUG}
						LogCmd[iData] := ImmediateCmd;
					{$ENDIF}

					PIO_Out16(iBoard, ADCStartStopOffset + dSequencer*iSequencer, ImmediateCmd);
				end;
				for iData := 0 to High(DataRow) do DataRow[iData] := PIO_In32(iBoard, ADCDataOffset[iSequencer]);
				{$IFDEF DEBUG}
					if (Dev[iBoard].DebugMask and 1) <> 0 then if iSequencer = 0 then begin
						Buf := 'ADC ' + AnsiString(IntToStr(iSequencer)) + ':';
						for iData := 0 to High(DataRow) do
							Buf := Buf + (#13#10 + AnsiString(IntToHex(LogCmd[iData], 4)) + '>' + AnsiString(IntToHex(DataRow[iData], 8)))
						;
						AnsiMessageBox(Buf, 'AIOAIO', MB_ICONINFORMATION);
					end;
				{$ENDIF}
				if (ImmediateCmd and ADCCmd_SingleEnded) <> 0 then begin
					for iChannel := 0 to 7 do begin
						pThisData^ := DataRow[1 + iChannel];
						Inc(pThisData);
					end;
				end
				else begin
					for iChannel := 0 to 3 do begin
						pThisData^ := DataRow[1 + iChannel*2];
						Inc(pThisData);
					end;
					Inc(pThisData, 4);
				end;

				ClearFIFO(iBoard, iSequencer);

				Status := ERROR_SUCCESS;
			end;
		end);
		Result := Status;
		if Result <> ERROR_SUCCESS then begin
			pThisData := pRawData;
			for iChannel := $0 to $F do begin
				pThisData^ := $80000000;
				Inc(pThisData);
			end;
			Exit;
		end;
	end;
end;

// ADIO and HMAIO compatible GetScan
function GetImmediateScanRaw_Native32(iBoard: TBoardIndex; pRawData: TPUInt32; Range: TRangeCode; const Gains: array of TUInt32): TWinErrCode;
const
	IRQEnableOffset = $40;

var
	pThisData: TPUInt32;
	iChannel, hChannel: TChannelIndex;
	hSequencer: byte;
	Hz: TDouble;
	BaseImmediateCmd, ThisImmediateCmd: TUInt32;
	bDifferential: Boolean;
	ChannelShift, Depth: TUInt32;
	ThisTime, NextTime, TicksPerStart: TSInt64;
	iSequencer: Integer;
	{$IFDEF DEBUG}
		Buf: AnsiString;
		ImmediateCmd: array of TUInt32;
		ImmediateCmdUsed: Cardinal;
		ThisPrints: Integer;
	{$ENDIF}

begin
	Result := ERROR_SUCCESS;
	case Dev[iBoard].RegisterMap of
		rmAIO, rmHMAIO: hSequencer := 1;
		rmADIO: hSequencer := 0;
		else begin
			Result := ERROR_BAD_TOKEN_TYPE;
			Exit;
		end;
	end;
	BaseImmediateCmd := PIO_In32(iBoard, ADCStartStopOffset);
	BaseImmediateCmd := BaseImmediateCmd or $30000;
	BaseImmediateCmd := BaseImmediateCmd and not ADCCmd_Continuous;
	BaseImmediateCmd := BaseImmediateCmd and not ((7 shl 12) or (7 shl 7)); //Clear channel and range bits.
	bDifferential := (BaseImmediateCmd and ADCCmd_SingleEnded) = 0;

	if bDifferential then begin
		hChannel := $3;
		ChannelShift := 13;
	end
	else begin
		hChannel := $7;
		ChannelShift := 12;
	end;

	try
		pThisData := pRawData;
		for iSequencer := 0 to hSequencer do
			for iChannel := $0 to hChannel do begin
				pThisData^ := $80000000;
				Inc(pThisData);
			end
		;
	except
		Result := ERROR_INVALID_ADDRESS;
		Exit;
	end;

	//No trigger frequency is software-start mode.
	Hz := 0;
	ADC_SetRate(iBoard, 0, Hz);

	{$IFDEF Debug}
		ImmediateCmdUsed := 0;
	{$ENDIF}
	// start all the conversions (4, 8, 16)
	// Must allow 25µs for each software-start conversion.
	QueryPerformanceFrequency(TicksPerStart);
	TicksPerStart := Round(TicksPerStart * 25/1000000);
	for iSequencer := 0 to hSequencer do
		for iChannel := $0 to hChannel do begin
			Range := (Gains[iSequencer] shr (4*iChannel)) and $F;
			ThisImmediateCmd := BaseImmediateCmd or (iChannel shl ChannelShift) or (Range shl 7);
			PIO_Out32(iBoard, ADCStartStopOffset + 4*iSequencer, ThisImmediateCmd);

			{$IFDEF Debug}
				SetLength(ImmediateCmd, ImmediateCmdUsed+1);
				ImmediateCmd[ImmediateCmdUsed] := ThisImmediateCmd;
				Inc(ImmediateCmdUsed);
			{$ENDIF}

			QueryPerformanceCounter(ThisTime);
			NextTime := ThisTime + TicksPerStart;
			repeat
				QueryPerformanceCounter(ThisTime);
			until (ThisTime - NextTime) >= 0;
		end
	;

	Depth := PIO_In32(iBoard, ADCDepthOffset);
	if Depth < (hSequencer+1)*(hChannel+1) then begin
		{
		pThisData := pRawData;
		for iChannel := 0 to Depth-1 do begin
			pThisData^ := PIO_In32(iBoard, ADCDataOffset[0]);
			Inc(pThisData);
		end;
		}
		Result := ERROR_READ_FAULT;
		Exit;
	end;

	pThisData := pRawData;
	for iSequencer := 0 to hSequencer do
		for iChannel := $0 to hChannel do begin
			pThisData^ := PIO_In32(iBoard, ADCDataOffset[0]);
			Inc(pThisData);
		end
	;

	{$IFDEF DEBUG}
		if (Dev[iBoard].DebugMask and 1) <> 0 then begin
			Buf := 'ADC Command/Data:';
			pThisData := pRawData;
			for iChannel := $0 to Max((hSequencer+1)*(hChannel+1), ImmediateCmdUsed)-1 do begin
				ThisPrints := 0;
				if iChannel < ImmediateCmdUsed then ThisPrints := ThisPrints or 1;
				if iChannel < (hSequencer+1)*(hChannel+1) then ThisPrints := ThisPrints or 2;
				case ThisPrints of
					1: Buf := Buf + (#13#10 + AnsiString(IntToHex(ImmediateCmd[iChannel], 1)) + '/');
					2: Buf := Buf + (#13#10'/' + AnsiString(IntToHex(pThisData^, 8)));
					3: Buf := Buf + (#13#10 + AnsiString(IntToHex(ImmediateCmd[iChannel], 1)) + '/' + AnsiString(IntToHex(pThisData^, 8)));
				end;
				Inc(pThisData);
			end;
			AnsiMessageBox(Buf, 'AIOAIO', MB_ICONINFORMATION);
		end;
	{$ENDIF}
end;

function GetImmediateScanRaw_Emulated(iBoard: TBoardIndex; pRawData: TPUInt32; Range: TRangeCode; const Gains: array of TUInt32): TWinErrCode;
const
	IRQEnableOffset = $40;

var
	AFLevel, BakAFLevel: TUInt32;
	StartStopCmd, BakStartStopCmd: TUInt32;
	Hz: TDouble;
	iChannel: TChannelIndex;
	iSample: TUInt32;
	pThisData: TPUInt32;
	{$IFDEF DEBUG}
		Buf: AnsiString;
	{$ENDIF}

	procedure DoStop;
	begin
		PIO_Out32(iBoard, ADCStartStopOffset, StartStopCmd);
		Hz := 0;
		ADC_SetRate(iBoard, 0, Hz);
		PIO_Out32(iBoard, IRQEnableOffset, 0);
		PIO_PollForIRQ(iBoard);
		PIO_Out32(iBoard, ADCDepthOffset, 0);
	end;

begin
	Result := ERROR_SUCCESS;

	try
		pThisData := pRawData;
		for iChannel := $0 to $7 do begin
			pThisData^ := $80000000;
			Inc(pThisData);
		end;
	except
		Result := ERROR_INVALID_ADDRESS;
		Exit;
	end;

	//Back up persistent control params.
	BakAFLevel := PIO_In32(iBoard, AFLevelOffset);
	BakStartStopCmd := (PIO_In32(iBoard, ADCStartStopOffset) and $FFFF) or $20000;
	try
		//Select all channels.
		if (StartStopCmd and ADCCmd_SingleEnded) <> 0 then begin
			AFLevel := 8;
			StartStopCmd := BakStartStopCmd or (7 shl 12);
		end
		else begin
			AFLevel := 4;
			StartStopCmd := BakStartStopCmd or (6 shl 12);
		end;

		//Set ch0 range for FPGA that doesn't handle it.
		StartStopCmd := StartStopCmd and not ($7 shl 7);
		StartStopCmd := StartStopCmd or (Range shl 7);

		//Set sequenced.
		StartStopCmd := StartStopCmd or ADCCmd_Continuous;

		DoStop; //Just in case.

		//Add 16-sample margin in case of sync issues. At 10kHz who cares.
		Inc(AFLevel, 16);

		//Start.
		PIO_Out32(iBoard, IRQEnableOffset, $10);
		PIO_Out32(iBoard, AFLevelOffset, AFLevel);
		PIO_Out32(iBoard, ADCStartStopOffset, StartStopCmd or $10000);
		Hz := 10000;
		ADC_SetRate(iBoard, 0, Hz);

		//Wait for AFLevel pairs of samples to be taken.
		//Result := PIO_WaitForIRQ(iBoard);
		repeat
		until PIO_In32(iBoard, ADCDepthOffset) >= AFLevel;

		Dec(AFLevel, 16);

		//Manual clear IRQ because that seems to be necessary.
		PIO_Out32(iBoard, IRQEnableOffset, $FFFF0000);

		DoStop;

		//Bail if it was an abort.
		if Result <> ERROR_SUCCESS then Exit;

		//Eat until ch0.
		repeat
			iSample := PIO_In32(iBoard, ADCDataOffset[0]);
		until ((iSample shr 20) and $F) = 0;

		//Read data into pRawData.
		pThisData := pRawData;
		pThisData^ := iSample;
		for iSample := 1 to AFLevel*2-1 do begin
			Inc(pThisData);
			pThisData^ := PIO_In32(iBoard, ADCDataOffset[0]);
		end;
		{$IFDEF DEBUG}
			if (Dev[iBoard].DebugMask and 1) <> 0 then begin
				Buf := 'ADC Data:';
				pThisData := pRawData;
				for iSample := 0 to AFLevel*2-1 do begin
					Buf := Buf + (#13#10 + AnsiString(IntToHex(pThisData^, 8)));
					Inc(pThisData);
				end;
				AnsiMessageBox(Buf, 'AIOAIO', MB_ICONINFORMATION);
			end;
		{$ENDIF}

	finally
		//Put back persistent control params.
		PIO_Out32(iBoard, AFLevelOffset, BakAFLevel);
		PIO_Out32(iBoard, ADCStartStopOffset, BakStartStopCmd);
	end;

	{$IFDEF DEBUG}
		if Dev[iBoard].LogFil = nil then Dev[iBoard].LogFil := TFileStream.Create('Log.bin', fmCreate);
		Dev[iBoard].LogFil.WriteData(pRawData, AFLevel*2*SizeOf(pRawData^));
	{$ENDIF}
end;

function ADC_GetImmediateScanRaw(iBoard: TBoardIndex; pRawData: TPUInt32; Range: TRangeCode = rcNoChange): TWinErrCode;
var
	iSequencer: TSequencerIndex;
	Gains: array[0..1] of TUInt32;

begin
	Result := ValidateBoardIndex(iBoard);
	if Result <> ERROR_SUCCESS then Exit;

	try
		if Range = rcNoChange then begin
			for iSequencer := 0 to 1 do
				Gains[iSequencer] := PIO_In32(iBoard, ADCRangeOffset + dSequencer*iSequencer)
			;
		end
		else begin
			Result := ValidateADCRange(Range);
			if Result <> ERROR_SUCCESS then Exit;

			for iSequencer := 0 to 1 do begin
				Gains[iSequencer] := Range * $11111111;
				//PIO_Out32(iBoard, ADCRangeOffset + dSequencer*iSequencer, Gains[iSequencer]);
			end;
		end;

		for iSequencer := 0 to 1 do
			PIO_Out32(iBoard, ADCRangeOffset + dSequencer*iSequencer, Gains[iSequencer])
		;

		case Dev[iBoard].ImmediateMode of
			imNative16: Result := GetImmediateScanRaw_Native16(iBoard, pRawData, Range, Gains);
			imNative32: Result := GetImmediateScanRaw_Native32(iBoard, pRawData, Range, Gains);
			imEmulated: Result := GetImmediateScanRaw_Emulated(iBoard, pRawData, Range, Gains);
			else Result := ERROR_BAD_DEV_TYPE;
		end;

		{$IFDEF Debug}
			if Result <> ERROR_SUCCESS then
				AnsiMessageBox('Error ' + AnsiString(IntToStr(Result)), 'AIOAIO', MB_ICONEXCLAMATION)
			;
		{$ENDIF}
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

end.
