//******************************************************************************
// Консольная программа для обнаружения и работы с несколькими модулями USB3000
//******************************************************************************
#include <stdio.h>
#include <conio.h>
#include <math.h>
#include "Rtusbapi.h"

// функция аварийного завершения программы
void TerminateApplication(char *ErrorString, bool TerminationFlag = true);
// функция потока ввода данных
DWORD WINAPI ServiceReadThread(PVOID ThreadIndex);

// максимально возможное кол-во модулей типа USB3000
const WORD MAX_DEVICE_QUANTITY	= 0x10;
// кол-во опрашиваемых каналов для каждого модуля
const WORD CHANNELS_QUANTITY 		= 0x4;
// частота  ввода данных
const double ReadRate = 3000.0;

// идентификаторы потока
HANDLE 	hReadThread[MAX_DEVICE_QUANTITY];
WORD 		ThreadIndex[MAX_DEVICE_QUANTITY];
DWORD 	ReadTid[MAX_DEVICE_QUANTITY];

// текущая версия библиотеки Rtusbapi.dll
DWORD DllVersion;
// массив указателей на интерфейс модулей типа USB3000
IRTUSB3000 *pModule[MAX_DEVICE_QUANTITY];
// название модуля
char ModuleName[10];
// скорость работы шины USB
BYTE UsbSpeed;
// серийный номер модуля
char ModuleSerialNumber[9];
// версия драйвера AVR
char AvrVersion[5];
// структура, содержащая информацию о версии драйвера DSP
RTUSB3000::DSP_INFO di;
// структура информации в ППЗУ модуля
RTUSB3000::FLASH fi;
// структура параметров работы АЦП
RTUSB3000::INPUT_PARS ip;
// кол-во обнаруженных модулей
WORD ModuleQuantity;

// размер запроса сбора данных
DWORD DataStep = 1024*1024;
// буфер данных
SHORT *Buffer[MAX_DEVICE_QUANTITY];
// флажок аварийного завершения потока
bool IsThreadTerminated[MAX_DEVICE_QUANTITY];

// номер ошибки при выполнении сбора данных для каждого потока
WORD ThreadErrorNumber[MAX_DEVICE_QUANTITY];
// номер ошибки выполнения основной программы
WORD ErrorNumber;

// экранные координаты вывода значения счётчиков
WORD XCoordCounter, YCoordCounter;
// счетчик потоков
WORD Counter[MAX_DEVICE_QUANTITY];
// критическая секция
CRITICAL_SECTION cs;

//------------------------------------------------------------------------
// основная программа
//------------------------------------------------------------------------
void main(void)
{
	WORD i, j;

	// зачищаем экран монитора
	clrscr();

	printf(" ***************************************\n");
	printf(" Console example for Multi USB3000		\n");
	printf(" ***************************************\n\n");

	// инициализация критической секции
	InitializeCriticalSection(&cs);

	// проверим версию используемой библиотеки Rtusbapi.dll
	if((DllVersion = RtGetDllVersion()) != CURRENT_VERSION_RTUSBAPI)
	{
		char String[128];
		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);
	}
	else printf(" Rtusbapi.dll Version --> OK\n");

	printf("\n USB3000 modules List:\n");
	// обнуляем счётчик обнаруженных модулей
	ModuleQuantity = 0x0;
	// Сканируем первые MAX_DEVICE_QUANTITY виртуальных слотов в поисках модулей типа USB3000
	for(i = 0x0; i < MAX_DEVICE_QUANTITY; i++)
	{
		// обнулим указатель на интерфейс
		pModule[i] = NULL;
		// получим указатель на интерфейс
		pModule[i] = static_cast<IRTUSB3000 *>(RtCreateInstance("usb3000"));
		if(!pModule[i]) TerminateApplication(" Module Interface --> Bad\n");
		// попробуем обнаружить какой-нибудь модуль в соответствующем виртуальном слоте
		else if(!pModule[i]->OpenDevice(i))  { pModule[i]->ReleaseInstance(); pModule[i] = NULL; continue; }
		// прочитаем название модуля в нулевом виртуальном слоте
		else if(!pModule[i]->GetModuleName(ModuleName)) TerminateApplication(" GetModuleName() --> Bad\n");
		// проверим, что это USB3000
		else if(strcmp(ModuleName, "USB3000")) TerminateApplication(" The detected module is not USB3000. The appication is terminated.\n");
		// попробуем узнать скорость USB
		else if(!pModule[i]->GetUsbSpeed(&UsbSpeed)) TerminateApplication(" GetUsbSpeed() --> Bad\n");
		// попробуем прочитать серийный номер модуля
		else if(!pModule[i]->GetModuleSerialNumber(ModuleSerialNumber)) TerminateApplication(" GetModuleSerialNumber() --> Bad\n");
		// загрузим драйвера DSP из ресурсов DLL
		else if(!pModule[i]->LOAD_DSP()) TerminateApplication(" LOAD_LBIOS() --> Bad\n");
		// проверим загрузку модуля
	 	else if(!pModule[i]->MODULE_TEST()) TerminateApplication(" MODULE_TEST() --> Bad\n");
		// получим версию загруженного LBIOS
		else if(!pModule[i]->GET_DSP_INFO(&di)) TerminateApplication(" GET_DSP_INFO() --> Bad\n");

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

		// обязательно проинициализируем поле size структуры RTUSB3000::INPUT_PARS
		ip.size = sizeof(RTUSB3000::INPUT_PARS);
		// получим текущие параметры работы АЦП
		if(!pModule[i]->GET_INPUT_PARS(&ip)) TerminateApplication(" GET_INPUT_PARS() --> Bad\n");

		// установим желаемые параметры АЦП
		ip.CorrectionEnabled = true;				  		// разрешим корректировку данных
		ip.InputClockSource = RTUSB3000::INTERNAL_INPUT_CLOCK;	// будем использовать внутренние тактовые испульсы для ввода данных
//		ip.InputClockSource = RTUSB3000::EXTERNAL_INPUT_CLOCK;	// будем использовать внешние тактовые испульсы для ввода данных
		ip.SynchroType = RTUSB3000::NO_SYNCHRO;			// не будем использовать никакую синхронизацию при вводе данных
//		ip.SynchroType = RTUSB3000::TTL_START_SYNCHRO;	// будем использовать цифровую синхронизацию старта при вводе данных
		ip.ChannelsQuantity = CHANNELS_QUANTITY; 			// кол-во активных каналов
		for(j = 0x0; j < CHANNELS_QUANTITY; j++) ip.ControlTable[j] = (WORD)(j);
		ip.InputRate = ReadRate; 					 			// частота ввода данных в кГц
		ip.InterKadrDelay = 0.0;
		// будем использовать фирменные калибровочные коэффициенты, которые храняться в ППЗУ модуля
		for(j = 0x0; j < 0x8; j++) { ip.AdcOffsetCoef[j] = fi.AdcOffsetCoef[j]; ip.AdcScaleCoef[j] = fi.AdcScaleCoef[j]; }
		// передадим требуемые параметры работы АЦП в модуль
		if(!pModule[i]->SET_INPUT_PARS(&ip)) TerminateApplication(" SET_INPUT_PARS() --> Bad\n");

		// выделим память под двойной буфер
		Buffer[i] = NULL;
		Buffer[i] = new SHORT[2*DataStep];
		if(!Buffer[i]) TerminateApplication(" Cannot allocate buffer memory.\n");

		// инкрементируем счётчик обнаруженных модулей
		ModuleQuantity++;
		printf("   %2u. Virtual Slot %2u. Module (S/N %s, %s) is ready ... \n", ModuleQuantity, i, fi.SerialNumber, UsbSpeed ? "HIGH-SPEED (480 Mbit/s)" : "FULL-SPEED (12 Mbit/s)");
	}

	// проверим: мы нашли хоть что-нибудь?
	if(!ModuleQuantity) { printf("   Empty...\n");  TerminateApplication("\n Can't detect any USB3000 module  :(((\n"); }

	// текущие координаты курсора
	XCoordCounter = (WORD)wherex();
	YCoordCounter = (WORD)wherey(); YCoordCounter += (WORD)0x2;

	// попробуем запустить поток сбора данных для каждого из обнаруженных модуля
	ErrorNumber = 0x0;
	for(i = 0x0; i < MAX_DEVICE_QUANTITY; i++)
	{
		// обнулим коды ошибок
		ThreadErrorNumber[i] = 0x0;
		IsThreadTerminated[i] = false;

		// создаем и запускаем потоки сбора данных
		if(!pModule[i]) continue;
		ThreadIndex[i] = i;
		hReadThread[i] = CreateThread(0, 0x2000, ServiceReadThread, &ThreadIndex[i], 0, &ReadTid[i]);
		if(!hReadThread[i]) TerminateApplication("\n CreateThread() --> Bad\n");
	}

	// ждем завершения работы потоков по факту нажатия любой клавиши клавиатуры
	printf("\n  Press any key to terminate the program ...\n");
	while(true)
	{
		if(kbhit()) { ErrorNumber = 0x1; break; }
		Sleep(100);
	}

	// выставим положение курсора
	if(ModuleQuantity) gotoxy(XCoordCounter, YCoordCounter + ModuleQuantity + 0x2);
	else printf("\n\n");

	// если программа была злобно прервана, предъявим ноту протеста
	if(ErrorNumber == 0x1) TerminateApplication(" The program was terminated successfully!\n", false);
	else TerminateApplication(" The program was completed successfully!!!\n", false);
}

//------------------------------------------------------------------------
// вывод сообщения об ошибке
//------------------------------------------------------------------------
void TerminateApplication(char *ErrorString, bool TerminationFlag)
{
	WORD i;

	// подчистим за собой
	for(i = 0x0; i < MAX_DEVICE_QUANTITY; i++)
	{
		if(hReadThread[i] != NULL)
		{
			// прервем данный поток
			IsThreadTerminated[i] = true;
			// ждём окончания работы данного потока
			WaitForSingleObject(hReadThread[i], INFINITE);
			// освободим идентификатор данного потока
			CloseHandle(hReadThread[i]);
		}
		// освободим память буфера
		if(Buffer[i]) { delete[] Buffer[i]; Buffer[i] = NULL; }
		// освободим модуль
		if(pModule[i]) { pModule[i]->ReleaseInstance(); pModule[i] = NULL; }
		// если была ошибка в потоке -  выведем сообщение об этом
//		if(!ErrorNumber) { if(ThreadErrorNumber[i]) ShowThreadErrorMessage(ThreadErrorNumber[i], i); }
	}

	// уничтожение критической секции
	DeleteCriticalSection(&cs);

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

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

//------------------------------------------------------------------------
// Поток сбора данных с модуля USB3000
//------------------------------------------------------------------------
DWORD WINAPI ServiceReadThread(PVOID ThreadIndex)
{
	// номер запроса
	WORD RequestNumber;
	// номер текущего потока
	WORD ti = *(WORD *)ThreadIndex;
	// идентификатор массива их двух событий
	HANDLE ReadEvent[2];
	// массив OVERLAPPED структур из двух элементов
	OVERLAPPED ReadOv[2];
	DWORD BytesTransferred[2];

	// остановим ввод данных и одновременно прочистим соответствующий канал bulk USB
	if(pModule[ti]->STOP_READ())
	{
		// создадим два события
		ReadEvent[0] = CreateEvent(NULL, FALSE , FALSE, NULL);
		memset(&ReadOv[0], 0, sizeof(OVERLAPPED)); ReadOv[0].hEvent = ReadEvent[0];
		ReadEvent[1] = CreateEvent(NULL, FALSE , FALSE, NULL);
		memset(&ReadOv[1], 0, sizeof(OVERLAPPED)); ReadOv[1].hEvent = ReadEvent[1];

		// координаты курсора текущего потока
		EnterCriticalSection(&cs);
			gotoxy(XCoordCounter, YCoordCounter + 1*ti);
			printf("  Counter[%2u]: %8u", ti+0x1, Counter[ti] = 0x0);
		LeaveCriticalSection(&cs);

		// делаем предварительный запрос на ввод данных
		RequestNumber = 0x0;
		if(!pModule[ti]->ReadData(Buffer[ti], &DataStep, &BytesTransferred[RequestNumber], &ReadOv[RequestNumber]))
				if(GetLastError() != ERROR_IO_PENDING) { ThreadErrorNumber[ti] = 0x2; goto ThreadFinish; }

		// теперь запускаем ввод данных
		if(pModule[ti]->START_READ())
		{
			// цикл сбора данных
			while(!IsThreadTerminated[ti])
			{
				// сделаем запрос на очередную порции данных
				RequestNumber ^= 0x1;
				if(!pModule[ti]->ReadData(Buffer[ti] + RequestNumber*DataStep, &DataStep, &BytesTransferred[RequestNumber], &ReadOv[RequestNumber]))
						if(GetLastError() != ERROR_IO_PENDING) { ThreadErrorNumber[ti] = 0x2; break; }

				// ждём завершения очередного запроса
				while(!IsThreadTerminated[ti])
				{
					if(HasOverlappedIoCompleted(&ReadOv[RequestNumber^0x1])) break;
					else if(IsThreadTerminated[ti]) break;
					else Sleep(20);
				}

				// инкремент счётчика текущего потока
				Counter[ti]++;
				// отобразим состояние счётчика данного потока при отсутствии ошибок
				if(!IsThreadTerminated[ti] && !ThreadErrorNumber[ti])
				{
					EnterCriticalSection(&cs);
						gotoxy(XCoordCounter, YCoordCounter + 1*ti);
						printf("  Counter[%2u]: %8u", ti+0x1, Counter[ti]);
					LeaveCriticalSection(&cs);
				}

				// была ли какая-нибудь ошибка в работе данного потока
				if(IsThreadTerminated[ti]) break;
				else if(ThreadErrorNumber[ti]) break;
				else Sleep(20);
			}
		}
		else ThreadErrorNumber[ti] = 0x4;
	}
	else ThreadErrorNumber[ti] = 0x6;

ThreadFinish:
	// остановим ввод данных
	if(!pModule[ti]->STOP_READ()) ThreadErrorNumber[ti] = 0x6;
	// уберём за собой
	if(!CancelIo(pModule[ti]->GetModuleHandle())) ThreadErrorNumber[ti] = 0x7;
	// освободим все идентификаторы событий
	for(WORD i = 0x0; i < 0x2; i++) CloseHandle(ReadEvent[i]);
	// отобразим состояние счётчика данного потока при наличии ошибок
	if(ThreadErrorNumber[ti])
	{
		EnterCriticalSection(&cs);
			gotoxy(XCoordCounter, YCoordCounter + 1*ti);
			printf("  Counter[%2u]: Thread Error!", ti+0x1, Counter[ti]);
		LeaveCriticalSection(&cs);
	}
	// теперь спокойно можно выйти из потока
	return 0x0;
}
