program WriteData;

{$APPTYPE CONSOLE}

uses
  SysUtils, Windows, Rtusbapi;

const
	// кол-во отсчетов в запросе (кратное 256) для ф. WriteData
	DataStep : DWORD = 64*1024;
	// номер канала ЦАП для вывода данных: 0 или 1
	DacNumber : WORD = $0;
	// частота вывода данных
	WriteRate : double  = 100.0;

var
	// идентификатор потока вывода
	hWriteThread : THANDLE;
	WriteTid : DWORD;

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

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

	// параметры выводимого синусоидального сигнала
	CurrentTime : double;		  			// в млс
	SignalFrequency : double; 	  			// в кГц
	SignalAmplitude : double;				// в кодах ЦАП

	// вспомогаикльные переменные
	i : WORD;
	hConIn : THandle;
	Str : 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 WriteOv : OVERLAPPED) : boolean;
var 	BytesTransferred : DWORD;
begin
	Result := true;
	while true do
	   begin
			if GetOverlappedResult(ModuleHandle, WriteOv, 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;

//------------------------------------------------------------------------------
// формирование данных синусоидального сигнала и заполнение ими
// FIFO буфера вывода в драйвере DSP
//------------------------------------------------------------------------------
function FillOutputBufer : boolean;
var i : WORD;
begin
	Result := true;
	// формируем данные для целого FIFO буфера вывода
	for i := 0 to (op.OutputFifoLength-1) do
		begin
			Buffer[i] := Round(2047.0 + SignalAmplitude*sin(2.0*pi*SignalFrequency*CurrentTime) + fi.DacOffsetCoef[DacNumber]);
			Buffer[i] := Round(Buffer[i] * fi.DacScaleCoef[DacNumber]);
			Buffer[i] := Buffer[i] and WORD($0FFF);
			Buffer[i] := Buffer[i] or (DacNumber shl 15) or ($1 shl 14);
			CurrentTime := CurrentTime + 1.0/op.OutputRate;
		end;
	// заполняем целиком FIFO буфер вывода в DSP модуля
	if not pModule.PUT_DM_ARRAY(op.OutputFifoBaseAddress, op.OutputFifoLength, @Buffer[0]) then
									begin ThreadErrorNumber := 7; Result := false; end;
end;

//------------------------------------------------------------------------------
// формирование очередной порции данных синусоидального сигнала
//------------------------------------------------------------------------------
procedure MakeOutputData(RequestNumber : WORD);
var
i : WORD;
BaseIndex : DWORD;
begin
	// смещение в буфере данных
	BaseIndex := RequestNumber*DataStep;
	for i := 0 to (DataStep-1) do
		begin
			Buffer[i + BaseIndex] := Round(2047.0 + SignalAmplitude*sin(2.*pi*SignalFrequency*CurrentTime) + fi.DacOffsetCoef[DacNumber]);
			Buffer[i + BaseIndex] := Round(Buffer[i + BaseIndex] * fi.DacScaleCoef[DacNumber]);
			Buffer[i + BaseIndex] := Buffer[i + BaseIndex] and WORD($0FFF);
			Buffer[i + BaseIndex] := Buffer[i + BaseIndex] or (DacNumber shl 15) or ($1 shl 14);
			CurrentTime := CurrentTime + 1.0/op.OutputRate;
		end;
end;

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

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

	// заполняем FIFO буфер вывода сформированными данными
	if not FillOutputBufer() then begin	IsThreadComplete := true; exit; end;

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

	RequestNumber := 0;
	// формируем в буфере первую порцию данных для вывода
	MakeOutputData(RequestNumber);
	// а также же и вторую порцию данных для вывода
	MakeOutputData(RequestNumber xor $1);

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

	// а теперь можно запускать вывод данных
	if pModule.START_WRITE() then
   	begin
			// перманентный цикл вывода данных
			while true do
				begin
					RequestNumber := RequestNumber xor $1;
					// сделаем запрос на вывод очередной порции данных
					if not pModule.WriteData(@Buffer[RequestNumber*DataStep], @DataStep, @BytesTransferred[RequestNumber], @WriteOv[RequestNumber]) then
						begin
							if (GetLastError() <> ERROR_IO_PENDING) then
								begin
									ThreadErrorNumber := 2; break;
								end
						end;

					// ожидание завершения запроса на вывод очередной порции данных
					if not WaitingForRequestCompleted(WriteOv[RequestNumber xor $1]) then break;

					// формируем очередную порцию данных для вывода
					MakeOutputData(RequestNumber xor $1);

					// были ли ошибки или пользователь прервал вывод данных
					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 not pModule.STOP_WRITE() then ThreadErrorNumber := 1;

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

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

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

end;

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

//------------------------------------------------------------------------------
// отображение ошибок возникших во время работы потока вывода данных
//------------------------------------------------------------------------------
procedure ShowThreadErrorMessage;
begin
	case ThreadErrorNumber of
		$0 : ;
		$1 : WriteLn(' WRITE Thread: STOP_WRITE() --> Bad! :(((');
		$2 : WriteLn(' WRITE Thread: ReadData() --> Bad :(((');
		$3 : WriteLn(' WRITE Thread: Waiting data Error! :(((');
		// если программа была злобно прервана, предъявим ноту протеста
		$4 : WriteLn(' WRITE Thread: The program was terminated! :(((');
		$5 : WriteLn(' WRITE Thread: START_WRITE() --> Bad :(((');
		$6 : WriteLn(' WRITE Thread: Can''t complete input and output (I/O) operations! :(((');
		$7 : WriteLn(' WRITE Thread: Can''t fill FIFO output buffer in DSP driver! :(((');
		else WriteLn(' WRITE Thread: Unknown error! :(((');
	end;
end;

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

	// проверим версию используемой DLL библиотеки
	DllVersion := RtGetDllVersion;
	if DllVersion <> CURRENT_VERSION_RTUSBAPI then
		begin
			Str := 'Неверная версия 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(Str);
		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');

	// выведем нулевой отсчёт на первый канал ЦАП
	DacSample := 0;
	// откалибруем отсчёт для первого канала
	DacSample := Round(DacSample + fi.DacOffsetCoef[0]);
	DacSample := Round(DacSample * fi.DacScaleCoef[0]);
	if not pModule.WRITE_SAMPLE(0, @DacSample) then TerminateApplication('Не могу установить первый канал ЦАП!')
	else WriteLn(' WRITE_SAMPLE(0) --> OK');

	// также нулевой отсчёт выведем на второй канал ЦАП
	DacSample := 0;
	// откалибруем отсчёт для второго канала
	DacSample := Round(DacSample + fi.DacOffsetCoef[1]);
	DacSample := Round(DacSample * fi.DacScaleCoef[1]);
	if not pModule.WRITE_SAMPLE(1, @DacSample) then TerminateApplication('Не могу установить второй канал ЦАП!')
	else WriteLn(' WRITE_SAMPLE(1) --> OK');

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

	// установим желаемые параметры вывода данных на модуль USB3000
	op.OutputRate := WriteRate;					// частота вывода данных в кГц
	op.OutputFifoBaseAddress := $3000;			// базовый адрес FIFO буфера вывода
	op.OutputFifoLength := $F80;	 				// длина FIFO буфера вывода

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

	// проинициализируем параметры выводимого синусоидального сигнала
	SignalFrequency := 1.0; 			// в кГц
	SignalAmplitude := 2000.0;			// в кодах ЦАП
	CurrentTime := 0.0 ;		  			// в млс

	// отобразим на экране монитора параметры работы модуля по выводу данных
	WriteLn('');
	Write(' Module USB3000 (S/N ', StrPas(@fi.SerialNumber)); WriteLn(') is ready ... ');
	WriteLn(' Ouput parameters:');
	WriteLn(Format('   OutputRate = %5.3f kHz', [op.OutputRate]));
	WriteLn(' Signal parameters:');
	WriteLn(Format('   SignalFrequency  = %2.2f kHz', [SignalFrequency]));
	WriteLn(Format('   SignalAmplitude  = %1.3f V', [SignalAmplitude*5.0/2000.0]));

	// установим счётчики
	Counter := $0; OldCounter := $FFFFFFFF;
	// сбросим флаги ошибки потока вывода
	ThreadErrorNumber := 0;
	// попробуем выделить нужное кол-во памяти под буфер
	SetLength(Buffer, 2*DataStep);
	// запустим поток вывода данных
	hWriteThread := CreateThread(nil, $2000, @WriteThread, nil, 0, WriteTid);
	if hWriteThread = THANDLE(nil) then TerminateApplication('Не могу запустить поток вывода данных!');

{ !!!  Бесконечный цикл программы по выводу данных !!!													}
	if DacNumber = 0 then Str := 'first'
	else Str := 'second';
	WriteLn(Format(#10#13'   Now SINUS signal is on the %s DAC channel', [Str]));
	WriteLn(' (you can press ESC key to terminate the program)');
	repeat
		if Counter <> OldCounter then
			begin
				Write(Format(' Counter %8u'#13, [Counter]));
				OldCounter := Counter;
			end
		else Sleep(20);
	until IsThreadComplete;

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

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

	// освободим идентификатор потока вывода данных
	if hWriteThread = THANDLE(nil) then CloseHandle(hWriteThread);
	// освободим память буфера данных
	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.

