//******************************************************************************
// Консольная программа для организации вывода данных из РС на модуль USB3000
// Используется библиотека-оболочка wrRtusbapi.dll
//******************************************************************************
#include <ansi_c.h>
#include "wrRtusbapi.h"
#include <utility.h>

// аварийное завершение программы
void TerminateApplication(char *ErrorString, BOOL TerminationFlag);
// отображение ошибок
void ShowThreadErrorMessage(void);
// округление
WORD Round(double Data);

// функция потока вывода данных из РС в модуль
DWORD WINAPI ServiceWriteThread(PVOID /*Context*/);
// идентификатор потока вывода
HANDLE hWriteThread;
DWORD WriteTid;

// текущая версия библиотеки Rtusbapi.dll
DWORD DllVersion;
// указатель на интерфейс модуля
LPVOID pModule;
// дескриптор устройства 
HANDLE hModule;
// название модуля
char ModuleName[10];
// скорость работы шины USB
BYTE UsbSpeed;
// серийный номер модуля
char ModuleSerialNumber[9];
// версия драйвера AVR
char AvrVersion[5];
// структура информации в ППЗУ модуля
FLASH_USB3000 fi;
// структура, содержащая информацию о версии драйвера DSP
DSP_INFO_USB3000 di;
// структура, содержащая параметры работы ЦАП
OUTPUT_PARS_USB3000 dp;
// номер канала ЦАП
const WORD DacNumber = 0x0;

// кол-во передаваемых отсчетов для ф. WriteData()
DWORD DataStep = 64*1024;
// длина буфера для выводимых данных
DWORD WritePoints;
// указатель на буфер для выводимых данных
SHORT	*WriteBuffer;
// однократный отсчёт для ЦАП
SHORT DacSample;

// число пи
const double Pi = 3.14159265358979323846;
// частота  вывода данных в кГц
const double DacRate = 100.0;
// максимально возможное кол-во виртуальных слотов
const WORD MaxVirtualSoltsQuantity = 127;

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

// экранный счетчик-индикатор
DWORD Counter = 0x0, OldCounter = 0xFFFFFFFF;

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

//------------------------------------------------------------------------
// основная программа
//------------------------------------------------------------------------
void main(void)
{
	char String[128];
	WORD i;

	// пока ничего не выделено под буфер вывода данных
	WriteBuffer = NULL;
	// пока не созданы потоки ввода-вывода данных
	hWriteThread = NULL;
	// сбросим флаг ошибок потока ввода данных
	ThreadErrorNumber = 0x0;

	// зачистим экран монитора	
	Cls();

	printf(" *******************************************\n");
	printf(" Console example of Data Writing to USB3000 \n");
	printf(" *******************************************\n\n");

	// проверим версию используемой библиотеки Rtusbapi.dll
	if((DllVersion = GetDllVersion_Rtusbapi()) != CURRENT_VERSION_RTUSBAPI) 
	{	
		sprintf(String, " Rtusbapi.dll Version Error!!!\n   Current: %1u.%1u. Required: %1u.%1u",
											DllVersion >> 0x10, DllVersion & 0xFFFF,
											CURRENT_VERSION_RTUSBAPI >> 0x10, CURRENT_VERSION_RTUSBAPI & 0xFFFF);
		
		TerminateApplication(String, TRUE);
	}		
	else printf(" Dll Version --> OK\n");

	// получим указатель на интерфейс модуля USB3000
	pModule = (LPVOID *)CreateInstance_Rtusbapi("usb3000");
	if(!pModule) TerminateApplication(" Module Interface --> Bad\n", TRUE);
	else printf(" Module Interface --> OK\n");

	// попробуем обнаружить модуль USB3000 в первых 127 виртуальных слотах
	for(i = 0x0; i < MaxVirtualSoltsQuantity; i++) if(OpenDevice_Usb3000(pModule, i)) break;
	// что-нибудь обнаружили?
	if(i == MaxVirtualSoltsQuantity) TerminateApplication(" Can't find module USB3000 in first 127 virtual slots!\n", TRUE);
	else printf(" OpenDevice_Usb3000(%u) --> OK\n", i);

	// попробуем получить дескриптор (handle) устройства
	hModule = GetModuleHandle_Usb3000(pModule);
	if(hModule == INVALID_HANDLE_VALUE) TerminateApplication(" GetModuleHandle_Usb3000() --> Bad\n", TRUE);
	else printf(" GetModuleHandle_Usb3000() --> OK\n");

	// прочитаем название модуля в нулевом виртуальном слоте
	if(!GetModuleName_Usb3000(pModule, ModuleName)) TerminateApplication(" GetModuleName_Usb3000() --> Bad\n", TRUE);
	else printf(" GetModuleName_Usb3000() --> OK\n");
	// проверим, что это 'USB3000'
	if(strcmp(ModuleName, "USB3000")) TerminateApplication(" The module is not 'USB3000'\n", TRUE);
	else printf(" The module is 'USB3000'\n");

	// узнаем текущую скорость работы шины USB20
	if(!GetUsbSpeed_Usb3000(pModule, &UsbSpeed)) TerminateApplication(" GetUsbSpeed_Usb3000() --> Bad\n", TRUE);
	else printf(" GetUsbSpeed_Usb3000() --> OK\n");
	// теперь отобразим версию драйвера AVR
	printf(" USB Speed is %s\n", UsbSpeed ? "HIGH (480 Mbit/s)" : "FULL (12 Mbit/s)");

	// прочитаем серийный номер модуля
	if(!GetModuleSerialNumber_Usb3000(pModule, ModuleSerialNumber)) TerminateApplication(" GetModuleSerialNumber_Usb3000() --> Bad\n", TRUE);
	else printf(" GetModuleSerialNumber_Usb3000() --> OK\n");
	// теперь отобразим серийный номер модуля
	printf(" Module Serial Number is %s\n", ModuleSerialNumber);

	// прочитаем версию драйвера AVR
	if(!GetAvrVersion_Usb3000(pModule, AvrVersion)) TerminateApplication(" GetAvrVersion_Usb3000() --> Bad\n", TRUE);
	else printf(" GetAvrVersion_Usb3000() --> OK\n");
	// теперь отобразим версию драйвера AVR
	printf(" Avr Driver Version is %s\n", AvrVersion);

	// код драйвера DSP возьмём из соответствующего ресурса штатной DLL библиотеки
	if(!LOAD_DSP_Usb3000(pModule, NULL)) TerminateApplication(" LOAD_DSP_Usb3000() --> Bad\n", TRUE);
	else printf(" LOAD_DSP_Usb3000() --> OK\n");

	// проверим загрузку модуля
 	if(!MODULE_TEST_Usb3000(pModule)) TerminateApplication(" MODULE_TEST_Usb3000() --> Bad\n", TRUE);
	else printf(" MODULE_TEST_Usb3000() --> OK\n");

	// получим версию загруженного драйвера DSP
	if(!GET_DSP_INFO_Usb3000(pModule, &di)) TerminateApplication(" GET_DSP_INFO_Usb3000() --> Bad\n", TRUE);
	else printf(" GET_DSP_INFO_Usb3000() --> OK\n");
	// теперь отобразим версию загруженного драйвера DSP
	printf(" DSP Driver version is %1u.%1u\n", di.DspMajor, di.DspMinor);

	// обязательно проинициализируем поле size структуры RTUSB3000::FLASH
	fi.size = sizeof(FLASH_USB3000);
	// получим информацию из ППЗУ модуля
	if(!GET_FLASH_Usb3000(pModule, &fi)) TerminateApplication(" GET_FLASH_Usb3000() --> Bad\n", TRUE);
	else printf(" GET_FLASH_Usb3000() --> OK\n");

	// выведем нулевой отсчёт на первый канал ЦАП
	DacSample = 0x0;
	// откалибруем отсчёт для первого канала
	DacSample = Round((DacSample + fi.DacOffsetCoef[0])*fi.DacScaleCoef[0]);
	if(!WRITE_SAMPLE_Usb3000(pModule, 0x0, &DacSample)) TerminateApplication(" WRITE_SAMPLE_Usb3000(0) --> Bad\n", TRUE);
	else printf(" WRITE_SAMPLE_Usb3000(0) --> OK\n");

	// также нулевой отсчёт выведем на второй канал ЦАП
	DacSample = 0x0;
	// откалибруем отсчёт для второго канала
	DacSample = Round((DacSample + fi.DacOffsetCoef[1])*fi.DacScaleCoef[1]);
	if(!WRITE_SAMPLE_Usb3000(pModule, 0x1, &DacSample)) TerminateApplication(" WRITE_SAMPLE_Usb3000(1) --> Bad\n", TRUE);
	else printf(" WRITE_SAMPLE_Usb3000(1) --> OK\n");

	// прочитаем текущие параметры вывода данных
	dp.size = sizeof(OUTPUT_PARS_USB3000);
	if(!GET_OUTPUT_PARS_Usb3000(pModule, &dp)) TerminateApplication(" GET_OUTPUT_PARS_Usb3000() --> Bad\n", TRUE);
	else printf(" GET_OUTPUT_PARS_Usb3000() --> OK\n");

	// установим желаемые параметры вывода данных на модуль USB3000
	dp.OutputRate = DacRate;		  			// частота вывода данных в кГц
	dp.OutputFifoBaseAddress = 0x3000;   	// базовый адрес FIFO буфера вывода
	dp.OutputFifoLength = 0xF80;				// длина FIFO буфера вывода

	// установим требуемые параметры вывода данных
	if(!SET_OUTPUT_PARS_Usb3000(pModule, &dp)) TerminateApplication(" SET_OUTPUT_PARS_Usb3000() --> Bad\n", TRUE);
	else printf(" SET_OUTPUT_PARS_Usb3000() --> OK\n");

	// отобразим на экране монитора параметры работы модуля по выводу данных 
	printf(" \n");
	printf(" Module USB3000 (S/N %s) is ready ... \n", ModuleSerialNumber);
	printf("  Ouput parameters:\n");
	printf("    OutputRate = %6.1f kHz\n", dp.OutputRate);
	printf("  Signal parameters:\n");
	printf("    SignalFrequency  = %2.2f kHz\n", SignalFrequency);
	printf("    SignalAmplitude  = %1.3f V\n", SignalAmplitude*5.0/2000.0);

	// сбросим флаг наличия ошибок потока ввода данных
	ThreadErrorNumber = 0x0;

	// кол-во отсчётов в двойном буфере ОЗУ РС
	WritePoints = 2*DataStep;
	// попробуем выделить память под буфер для выводимых с модуля данных
	WriteBuffer = malloc(WritePoints * sizeof(SHORT));
	if(!WriteBuffer) TerminateApplication(" Cannot allocate memory for data buffer \n", TRUE);

	// Создаем и запускаем поток вывода данных из РС в модуль
	hWriteThread = CreateThread(0, 0x2000, ServiceWriteThread, 0x0, 0x0, &WriteTid);
	if(!hWriteThread) TerminateApplication("Cann't start output data thread!", TRUE);

	// ждем завершения работы нужного потока
	printf("\n   Now SINUS signal is on the %s DAC channel\n", DacNumber ? "second" : "first");
	printf(" (you can press any key to terminate the program)\n");
	while(!IsThreadComplete)
	{
		if(OldCounter != Counter) { printf(" Counter %3u\r", Counter); OldCounter = Counter; }
		else Sleep(20);
	}

	// ждём окончания работы потока вывода данных
	WaitForSingleObject(hWriteThread, INFINITE);
	// две пустые строчки
	printf("\n\n");
	
	// если была ошибка - сообщим об этом
	if(ThreadErrorNumber) { TerminateApplication(NULL, FALSE); ShowThreadErrorMessage(); }
	else { printf("\n"); TerminateApplication("\n The program was completed successfully!!!\n", FALSE); }
}

//------------------------------------------------------------------------
// Поток в котором осуществляется вывод данных из РС в модуль
//------------------------------------------------------------------------
#pragma argsused
DWORD WINAPI ServiceWriteThread(PVOID Context)
{
	WORD RequestNumber;
	DWORD i;
	DWORD BaseIndex;
	// идентификатор массива их двух событий
	HANDLE WriteEvent[2];
	// массив OVERLAPPED структур из двух элементов
	OVERLAPPED WriteOv[2];
	DWORD BytesTransferred[2];
	DWORD TimeOut;

	// формируем данные для целого FIFO буфера вывода в DSP модуля (учитывая корректировку)
	for(i = 0x0; i < (DWORD)dp.OutputFifoLength; i++)
	{
   	WriteBuffer[i] = Round((2047.0 + SignalAmplitude*sin(2.0*Pi*SignalFrequency*CurrentTime) + fi.DacOffsetCoef[DacNumber])*fi.DacScaleCoef[DacNumber]);
		WriteBuffer[i] &= (WORD)(0xFFF);
		WriteBuffer[i] |= (WORD)(DacNumber << 15) | (WORD)(0x1 << 14);
		CurrentTime += 1.0/dp.OutputRate;
	}
	// заполняем целиком FIFO буфер вывода в DSP модуля
	if(!PUT_DM_ARRAY_Usb3000(pModule, dp.OutputFifoBaseAddress, dp.OutputFifoLength, (SHORT *)WriteBuffer))
   										{ ThreadErrorNumber = 0x1; IsThreadComplete = TRUE; return 1; }

	// теперь формируем выводимые данные для всего буфера WriteBuffer (учитывая корректировку)
	for(i = 0x0; i < 2*DataStep; i++)
	{
		WriteBuffer[i] = Round((2047.0 + SignalAmplitude*sin(2.0*Pi*SignalFrequency*CurrentTime) + fi.DacOffsetCoef[DacNumber])*fi.DacScaleCoef[DacNumber]);
		WriteBuffer[i] &= (WORD)(0xFFF);
		WriteBuffer[i] |= (WORD)(DacNumber << 15) | (WORD)(0x1 << 14);
		CurrentTime += 1.0/dp.OutputRate;
	}

	// остановим вывод данных и одновременно прочистим соответствующий канал bulk USB (PIPE_RESET)
	if(!STOP_WRITE_Usb3000(pModule)) { ThreadErrorNumber = 0x6; IsThreadComplete = TRUE; return 0; }

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

	// таймаут вывода данных
	TimeOut = DataStep/dp.OutputRate + 1000;

	// делаем предварительный запрос на вывод данных
	RequestNumber = 0;
	if(!WriteData_Usb3000(pModule, WriteBuffer, &DataStep, &BytesTransferred[RequestNumber], &WriteOv[RequestNumber]))
				if(GetLastError() != ERROR_IO_PENDING) { CloseHandle(WriteEvent[0]); CloseHandle(WriteEvent[1]); ThreadErrorNumber = 0x2; IsThreadComplete = TRUE; return 0; }

	// теперь запускаем собственно сам вывод данных
	if(START_WRITE_Usb3000(pModule))
	{
		// цикл перманентного вывода данных
		for(;;)
		{
			RequestNumber ^= 0x1;
			// сделаем запрос на вывод очередной порции данных в DSP модуля
			if(!WriteData_Usb3000(pModule, WriteBuffer + RequestNumber*DataStep, &DataStep, &BytesTransferred[RequestNumber], &WriteOv[RequestNumber]))
					if(GetLastError() != ERROR_IO_PENDING) { ThreadErrorNumber = 0x2; break; }

			// ждём окончания операции вывода очередной порции данных
			if(WaitForSingleObject(WriteEvent[RequestNumber^0x1], TimeOut) == WAIT_TIMEOUT)
				            		{ ThreadErrorNumber = 0x3; break; }

			// сформируем следующую порцию выводимых данных (учитывая корректировку)
			BaseIndex = (RequestNumber^0x1)*DataStep;
			for(i = 0x0; i < DataStep; i++)
			{
				WriteBuffer[i + BaseIndex] = Round((2047.0 + SignalAmplitude*sin(2.0*Pi*SignalFrequency*CurrentTime) + fi.DacOffsetCoef[DacNumber])*fi.DacScaleCoef[DacNumber]);
				WriteBuffer[i + BaseIndex] &= (WORD)(0xFFF);
				WriteBuffer[i + BaseIndex] |= (WORD)(DacNumber << 15) | (WORD)(0x1 << 14);
				CurrentTime += 1.0/dp.OutputRate;
	  		}

			if(ThreadErrorNumber) break;
			else if(KeyHit()) { GetKey(); ThreadErrorNumber = 0x1; break; }
			else Sleep(20);
			Counter++;
		}
	}
	else { ThreadErrorNumber = 0x5; }

	// остановим вывод данных
	if(!STOP_WRITE_Usb3000(pModule)) ThreadErrorNumber = 0x6;
	// уберём за собой
	if(!CancelIo(hModule)) ThreadErrorNumber = 0x7;
	// освободим все идентификаторы события
	for(i = 0x0; i < 0x2; i++) CloseHandle(WriteEvent[i]);
	// небольшая задержка
	Sleep(100);
	// установим флажок окончания потока вывода данных
	IsThreadComplete = TRUE;
	// теперь можно спокойно выйти из потока вывода данных
	return 0;
}

//---------------------------------------------------------------------------
//
//---------------------------------------------------------------------------
WORD Round(double Data)
{
	if(Data > 0.0) Data += 0.5;
	else if(Data < 0.0) Data = -0.5;
	return (WORD)Data;
}


//------------------------------------------------------------------------
// Отобразим сообщение с ошибкой
//------------------------------------------------------------------------
void ShowThreadErrorMessage(void)
{
	switch(ThreadErrorNumber)
	{
		case 0x0:
			break;

		case 0x1:
			// если программа была злобно прервана, предъявим ноту протеста
			printf("\n WRITE Thread: The program was terminated! :(((\n");
			break;

		case 0x2:
			printf("\n WRITE Thread: WriteData_Usb3000() --> Bad :(((\n");
			break;

		case 0x3:
			printf("\n WRITE Thread: Timeout is occured :(((\n");
			break;

		case 0x4:
			printf("\n WRITE Thread: Buffer Data Error! :(((\n");
			break;

		case 0x5:
			printf("\n WRITE Thread: START_WRITE_Usb3000() --> Bad :(((\n");
			break;

		case 0x6:
			printf("\n WRITE Thread: STOP_WRITE_Usb3000() --> Bad! :(((\n");
			break;

		case 0x7:
			printf("\n READ Thread: Can't complete input and output (I/O) operations! :(((");
			break;

		default:
			printf("\n WRITE Thread: Unknown error! :(((\n");
			break;
	}

	return;
}

//------------------------------------------------------------------------
// вывод сообщения и, если нужно, аварийный выход из программы
//------------------------------------------------------------------------
void TerminateApplication(char *ErrorString, BOOL TerminationFlag)
{
	// подчищаем интерфейс модуля
	if(pModule)
	{ 
		// освободим интерфейс модуля
		if(!ReleaseInstance_Usb3000(pModule)) printf(" ReleaseInstance_Usb3000() --> Bad\n", TRUE);
		else printf(" ReleaseInstance_Usb3000() --> OK\n");
		// обнулим указатель на интерфейс модуля
		pModule = NULL;
	}

	// освободим память буфера данных
	if(WriteBuffer) {	free(WriteBuffer); WriteBuffer = NULL; }
	// освободим идентификатор потока вывода данных
	if(hWriteThread) { CloseHandle(hWriteThread); hWriteThread = NULL; }

	// выводим текст сообщения
	if(ErrorString) printf(ErrorString);

	// если нужно - аварийно завершаем программу
	if(TerminationFlag) exit(1);
	else return;
}

