program ReadData;

{$APPTYPE CONSOLE}

uses
  SysUtils, Windows, Rtusbapi;

const
	// кол-во отсчетов в запросе (кратное 256) для ф. ReadData
	DataStep : DWORD = 1024*1024;
	// столько блоков по DataStep отсчётов нужно собрать в файл
	NBlockToRead : WORD = 20;
	// частота ввода данных
	ReadRate : double  = 3000.0;

var
	// идентификатор потока ввода
	hReadThread : THANDLE;
	ReadTid : DWORD;

	// файл для записи собранных данных
	FileHandle: Integer;

	// интерфейс модуля USB3000
	pModule : IRTUSB3000;
	// версия библиотеки Rtusbapi.dll
	DllVersion : DWORD;
	// идентификатор устройства
	ModuleHandle : THandle;
	// структура, содержащая информацию о версии драйвера DSP
	DspInfo : DSP_INFO_USB3000;
	// структура информации в ППЗУ модуля
	fi : FLASH_USB3000;
	// структура параметров работы АЦП
	ip : INPUT_PARS_USB3000;
	// название модуля
	ModuleName: String;
	// версия драйвера AVR
	AvrVersion : array [0..4] of CHAR;
	// серийный номер модуля
	ModuleSerialNumber : array [0..8] of CHAR;
	// экранный счетчик-индикатор
	Counter, OldCounter : WORD;
	// указатель на буфер для данных
	Buffer : array of SHORT;

	// номер ошибки при выполнения потока сбора данных
	ThreadErrorNumber : WORD;
	// флажок завершения потоков ввода данных
	IsThreadComplete : boolean;

	// вспомогаикльные переменные
	i : WORD;
	hConIn : THandle;
	ErrorString : string;

//------------------------------------------------------------------------------
//        функция обнаружения нажатия клавиши ESCAPE
//------------------------------------------------------------------------------
function Terminated : boolean;
var
	Buffer: INPUT_RECORD;
	Count: DWORD;

begin
	Result := false;
	GetNumberOfConsoleInputEvents(hConIn, Count);
	if Count > 0 then
		begin
			ReadConsoleInput(hConIn, Buffer, 1, Count);
			case Buffer.EventType of
				KEY_EVENT:
					case Buffer.Event.KeyEvent.wVirtualKeyCode of
	            VK_ESCAPE: Result := true;
            // Другие клавиши
          end;
       // Другие типы событий
      end;
    end;
end;

//------------------------------------------------------------------------------
// ожидание завершения выполнения очередного запроса на сбор данных
//------------------------------------------------------------------------------
function WaitingForRequestCompleted(var ReadOv : OVERLAPPED) : boolean;
var 	BytesTransferred : DWORD;
begin
	Result := true;
	while true do
	   begin
			if GetOverlappedResult(ModuleHandle, ReadOv, BytesTransferred, FALSE) then break
			else if (GetLastError() <>  ERROR_IO_INCOMPLETE) then
				begin
					// ошибка ожидания ввода очередной порции данных
					ThreadErrorNumber := 3; Result := false; break;
				end
			else if Terminated() then
				begin
					// программа прервана (нажата клавиша ESC)
					ThreadErrorNumber := 4; Result := false; break;
				end
			else Sleep(20);
		end;
end;

//------------------------------------------------------------------------------
//      фукция запускаемая в качестве отдельного потока
//             для сбора данных c модуля USB3000
//------------------------------------------------------------------------------
function ReadThread(var param : pointer): DWORD;
var
	RequestNumber : WORD;
	i : WORD ;
	// идентификатор массива их двух событий
	ReadEvent : array[0..1] of THANDLE;
	// массив OVERLAPPED структур из двух элементов
	ReadOv : array[0..1] of OVERLAPPED;
	BytesTransferred : array[0..1] of DWORD;

begin
	Result := 0;
	// остановим ввод данных и одновременно прочистим соответствующий канал bulk USB
	if not pModule.STOP_READ() then begin ThreadErrorNumber := 1; IsThreadComplete := true; exit; end;

	// создадим два события
	ReadEvent[0] := CreateEvent(nil, FALSE , FALSE, nil);
	FillMemory(@ReadOv[0], sizeof(OVERLAPPED), 0);
	ReadOv[0].hEvent := ReadEvent[0];
	ReadEvent[1] := CreateEvent(nil, FALSE , FALSE, nil);
	FillMemory(@ReadOv[1], sizeof(OVERLAPPED), 0);
	ReadOv[1].hEvent := ReadEvent[1];

	// заранее закажем первый асинхронный сбор данных в Buffer
	RequestNumber := 0;
	if not pModule.ReadData(@Buffer[0], @DataStep, @BytesTransferred[RequestNumber], @ReadOv[RequestNumber]) then
		begin
			if (GetLastError() <> ERROR_IO_PENDING) then
				begin
					CloseHandle(ReadEvent[0]); CloseHandle(ReadEvent[1]); ThreadErrorNumber := 2; IsThreadComplete := true; exit;
				end
		end;

	// а теперь можно запускать сбор данных
	if pModule.START_READ() then
   	begin
			// цикл сбора данных
			for i := 1 to (NBlockToRead-1) do
				begin
					RequestNumber := RequestNumber xor $1;
					// сделаем запрос на очередную порции вводимых данных
					if not pModule.ReadData(@Buffer[i*DataStep], @DataStep, @BytesTransferred[RequestNumber], @ReadOv[RequestNumber]) then
						begin
							if (GetLastError() <> ERROR_IO_PENDING) then
								begin
									ThreadErrorNumber := 2; break;
								end
						end;

					// ожидание выполнение очередного запроса на сбор данных
					if not WaitingForRequestCompleted(ReadOv[RequestNumber xor $1]) then break;

					// были ли ошибки или пользователь прервал ввод данных
					if ThreadErrorNumber <> 0 then break
					else if Terminated() then
						begin
							// программа прервана (нажата клавиша ESC)
							ThreadErrorNumber := 4; break;
						end
					else Sleep(20);

					// увеличиваем счётчик
					Inc(Counter);
				end
		end
	else ThreadErrorNumber := 5;

	// ждём окончания операции сбора последней порции данных
	if ThreadErrorNumber = 0 then
		begin
			if WaitingForRequestCompleted(ReadOv[RequestNumber]) then Inc(Counter);
		end;

	// остановим сбор данных
	if not pModule.STOP_READ() then ThreadErrorNumber := 1;

	// если надо, то прервём незавершённый асинхронный запрос
	if not CancelIo(ModuleHandle) then ThreadErrorNumber := 6;

	// освободим идентификаторы событий
	CloseHandle(ReadEvent[0]); CloseHandle(ReadEvent[1]);

	// установим флажок окончания потока сбора данных
	IsThreadComplete := true;

end;

//------------------------------------------------------------------------------
// аварийное завершение программы
//------------------------------------------------------------------------------
procedure TerminateApplication(ErrorString: string);
begin
	// сообщение с ошибкой
	MessageBox(HWND(nil), pCHAR(ErrorString), 'ОШИБКА!!!', MB_OK + MB_ICONINFORMATION);
	// освободим идентификатор потока сбора данных
	if hReadThread = THANDLE(nil) then CloseHandle(hReadThread);
	// освободим интерфейс модуля
	if pModule <> nil then pModule.ReleaseInstance();
	// освободим память буфера данных
	if Buffer <> nil then Buffer := nil;
	halt;
end;

//------------------------------------------------------------------------------
// отображение ошибок возникших во время работы потока сбора данных
//------------------------------------------------------------------------------
procedure ShowThreadErrorMessage;
begin
	case ThreadErrorNumber of
		$0 : ;
		$1 : WriteLn(' READ Thread: STOP_READ() --> Bad! :(((');
		$2 : WriteLn(' READ Thread: ReadData() --> Bad :(((');
		$3 : WriteLn(' READ Thread: Waiting data Error! :(((');
		// если программа была злобно прервана, предъявим ноту протеста
		$4 : WriteLn(' READ Thread: The program was terminated! :(((');
		$5 : WriteLn(' READ Thread: START_READ() --> Bad :(((');
		else WriteLn(' READ Thread: Unknown error! :(((');
	end;
end;

//******************************************************************************
//      						main program
//******************************************************************************
begin
	// хэндл стандартного устройства ввода компьютера - клавиатура
	hConIn := GetStdHandle(STD_INPUT_HANDLE);

	// проверим версию используемой DLL библиотеки
	DllVersion := RtGetDllVersion;
	if DllVersion <> CURRENT_VERSION_RTUSBAPI then
		begin
			ErrorString := 'Неверная версия DLL библиотеки Rtusbapi.dll! ' + #10#13 +
						'           Текущая: ' + IntToStr(DllVersion shr 16) +  '.' + IntToStr(DllVersion and $FFFF) + '.' +
						' Требуется: ' + IntToStr(CURRENT_VERSION_RTUSBAPI shr 16) + '.' + IntToStr(CURRENT_VERSION_RTUSBAPI and $FFFF) + '.';
			TerminateApplication(ErrorString);
		end
	else WriteLn(' DLL Version --> OK');

	// попробуем получить указатель на интерфейс для модуля USB3000
	pModule := RtCreateInstance(pCHAR('usb3000'));
	if pModule = nil then TerminateApplication('Не могу найти интерфейс модуля USB3000!')
	else WriteLn(' Module Interface --> OK');

	// попробуем обнаружить модуль USB3000 в первых 127 виртуальных слотах
	for i := 0 to 126 do
   	if pModule.OpenDevice(i) then break;

	// что-нибудь обнаружили?
	if i > 126 then TerminateApplication('Не удалось обнаружить модуль USB3000 в первых 256 виртуальных слотах!')
	else WriteLn(Format(' OpenDevice(%u) --> OK', [i]));

	// получим идентификатор устройства
	ModuleHandle := pModule.GetModuleHandle();

	// прочитаем название модуля в текущем виртуальном слоте
	ModuleName := '0123456789';
	if not pModule.GetModuleName(pCHAR(ModuleName)) then TerminateApplication('Не могу прочитать название модуля!')
	else WriteLn(' GetModuleName() --> OK');

	// проверим, что это модуль USB3000
	if Boolean(AnsiCompareStr(ModuleName, 'USB3000')) then TerminateApplication('К открытому виртуальному слоту подключён не модуль USB3000!')
	else WriteLn(' The module is ''USB3000''');

	// прочитаем серийный номер модуля
	if not pModule.GetModuleSerialNumber(@ModuleSerialNumber) then TerminateApplication('Не могу прочитать серийный номер модуля!')
	else WriteLn(' GetModuleSerialNumber() --> OK');
	// выведем на экран серийный номер обнаруженного модуля
	WriteLn(' The Serial Number is ' + String(ModuleSerialNumber));

	// прочитаем версию драйвера AVR
	if not pModule.GetAvrVersion(@AvrVersion) then TerminateApplication('Не могу прочитать версию драйвера AVR модуля!')
	else WriteLn(' GetAvrVersion() --> OK');
	// выведем на экран версию драйвера AVR
	WriteLn(' The AVR Driver Version is ' + String(AvrVersion));

	// Код драйвера DSP возьмём из соответствующего ресурса DLL библиотеки Rtusbapi.dll
	if not pModule.LOAD_DSP(nil) then TerminateApplication('Не могу загрузить модуль USB3000!')
	else WriteLn(' LOAD_DSP() --> OK');

	// проверим загрузку модуля
 	if not pModule.MODULE_TEST() then TerminateApplication('Ошибка в загрузке модуля USB3000!')
	else WriteLn(' MODULE_TEST() --> OK');

	// теперь получим номер версии загруженного драйвера DSP
	if not pModule.GET_DSP_INFO(@DspInfo) then TerminateApplication('Не могу прочитать версию драйвера DSP!')
	else WriteLn(' GET_DSP_INFO() --> OK');
	// выведем на экран версию драйвера DSP
	WriteLn(' The DSP Driver Version is ' + IntToStr(DspInfo.DspMajor) + '.' + IntToStr(DspInfo.DspMinor));

	// обязательно проинициализируем поле size структуры FLASH_USB3000
	fi.size := sizeof(FLASH_USB3000);
	// получим информацию из ППЗУ модуля
	if not pModule.GET_FLASH(@fi) then TerminateApplication('Не могу прочитать ППЗУ модуля USB3000!')
	else WriteLn(' GET_MODULE_DESCR() --> OK');

	// обязательно проинициализируем поле size структуры INPUT_PARS_USB3000
	ip.size := sizeof(INPUT_PARS_USB3000);
	// получим текущие параметры работы ввода данных
	if not pModule.GET_INPUT_PARS(@ip) then TerminateApplication('Не могу получить текущие параметры ввода данных!')
	else WriteLn(' GET_INPUT_PARS --> OK');

	// установим желаемые параметры ввода данных с модуля USB3000
	ip.CorrectionEnabled := true;				// разрешим корректировку данных
	ip.InputClockSource := INTERNAL_INPUT_CLOCK_USB3000;	// будем использовать внутренние тактовые испульсы для ввода данных
	ip.ChannelsQuantity := 4;					// четыре активных канала
	for i:=0 to (ip.ChannelsQuantity-1) do ip.ControlTable[i] := i;
	ip.InputRate := ReadRate;					// частота ввода данных в кГц
	ip.InterKadrDelay := 0.0;					// межкадровая задержка - пока всегда устанавливать в 0.0
	ip.InputFifoBaseAddress := $0;  			// базовый адрес FIFO буфера ввода
	ip.InputFifoLength := $3000;	 			// длина FIFO буфера ввода
	// будем использовать фирменные калибровочные коэффициенты, которые храняться в ППЗУ модуля USB3000
	for i:=0 to 7 do
		begin
			ip.AdcOffsetCoef[i] := fi.AdcOffsetCoef[i];
			ip.AdcScaleCoef[i] := fi.AdcScaleCoef[i];
		end;

	// передадим в модуль требуемые параметры по вводу данных
	if not pModule.SET_INPUT_PARS(@ip) then TerminateApplication('Не могу установить параметры ввода данных!')
	else WriteLn(' SET_INPUT_PARS --> OK');

	// отобразим параметры работы модуля по вводу данных на экране монитора
	WriteLn('');
	Write(' Module USB3000 (S/N ', StrPas(@fi.SerialNumber)); WriteLn(') is ready ... ');
	WriteLn(' Input parameters:');
	if ip.InputClockSource = INTERNAL_INPUT_CLOCK_USB3000 then WriteLn('   InputClockSource is INTERNAL')
	else WriteLn('   InputClockSource is EXTERNAL');
	WriteLn('   ChannelsQuantity = ', ip.ChannelsQuantity);
	WriteLn(Format('   InputRate = %5.3f kHz', [ip.InputRate]));
	WriteLn(Format('   InterKadrDelay = %2.4f ms', [ip.InterKadrDelay]));
	WriteLn(Format('   ChannelRate =  %5.3f kHz', [ip.ChannelRate]));

	// попробуем выделить нужное кол-во памяти под буфер
	SetLength(Buffer, NBlockToRead*DataStep);
	// запустим поток сбора данных
	hReadThread := CreateThread(nil, $2000, @ReadThread, nil, 0, ReadTid);
	if hReadThread = THANDLE(nil) then TerminateApplication('Не могу запустить поток сбора данных!');

{ !!!  Основной цикл программы ожидания конца сбора данных !!!													}
	WriteLn(#10#13' You can press ESC key to terminate the program...');
	// сбросим флаги ошибки потока ввода
	ThreadErrorNumber := 0;
	// сбросим флажок завершённости потока сбора данных
	IsThreadComplete := false;
	// установим счётчики
	Counter := $0; OldCounter := $FFFF;
	repeat
		if Counter <> OldCounter then
			begin
				Write(Format(' Counter %3u from %3u'#13, [Counter, NBlockToRead]));
				OldCounter := Counter;
			end
		else Sleep(20);
	until IsThreadComplete;

	// ждём окончания работы потока ввода данных   
	WaitForSingleObject(hReadThread, INFINITE);

	// две пустые строчки
	Write(#10#13#10#13);

	// если не было ошибок при вводе - попробуем сохранить собранные данные
	if ThreadErrorNumber = 0 then
	begin
		// попробуем открыть файл для записи собранных данных
		FileHandle := FileCreate('Test.dat');
		if FileHandle = -1 then TerminateApplication('Не могу открыть файл Test.dat для записи собранных данных!')
		else WriteLn(' Open file Test.dat... OK!');

		// теперь попробуем записать в файл собранные данные
		if FileWrite(FileHandle, Buffer[0], NBlockToRead*DataStep*sizeof(SHORT)) = -1 then  TerminateApplication('Не могу записать собранные данные в файл Test.dat!')
		else WriteLn(' Write file Test.dat... OK!');

		// закроем файл данных
		FileClose(FileHandle);
	end;

	// освободим идентификатор потока сбора данных
	if hReadThread = THANDLE(nil) then CloseHandle(hReadThread);
	// освободим память буфера данных
	Buffer := nil;
	// освободим интерфейс модуля
	if not pModule.ReleaseInstance() then  TerminateApplication('Не могу освободить интерфейс модуля USB3000!')
	else WriteLn(' ReleaseInstance() --> OK');

	// посмотрим были ли ошибки при сборе данных
	WriteLn('');
	if ThreadErrorNumber <> 0 then ShowThreadErrorMessage()
	else WriteLn(' The program was completed successfully!!!');

end.
