// Пример, демонстрирующий работу в одной системе модуля АЦП QMS20
// (собирает данные с 4х каналов с частотой 300 кГц/канал)
// И одного модуля ЦАП QMS45, который работает в динаическом режиме 
// (генерирует синусоиды с подкачкой данных из ПК).


#include "../../dll/qmx.h"
#include <shellapi.h>
#include <stdio.h>
#include <conio.h> 
#include <math.h>


HANDLE SD;                              // Дескриптор системы
QMX_CONFIG CFG;                         // Конфигурация системы
WORD QMS20Table[16];					// Таблица опроса каналов для QMS20
QMX_CC_F CC_Adc1[4];					// Калибровочные коэффициенты
QMX_CC_F CC_Dac1[8];

static unsigned int DataPtr, DataPtrOld=0, CurrentPtr, LengthDataBuf, out_cntr=0;
static unsigned short int *Data, *out_buf[2];


static unsigned int QMS_data[8][32], QMS_iter[8][32];

void CheckErr(void);
void quit(void);
void DisplayData(void);
void FillBuf(WORD *buf, DWORD len);

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




//void main(void)
void main(int argc, char* argv[])
{
	char s_wrk[64];
	int i_wrk, mod, ch;
	double CRate;

	printf("Please wait....\r");
	
	// Запустим сервер
	ShellExecuteW(NULL, TEXT("open"), TEXT("qmbox_ds.exe"), TEXT("-p3"), NULL, SW_SHOWNORMAL);
	Sleep(500);

	// Получим дискриптор системы
	SD = 0;
	SD=QMX_CircBufAttach(0);
	if (SD == NULL) {
		printf("ERROR: No QMBox found.");
		quit(); 
	}

	// Проинициализуем систему
	QMX_InitModules(SD, QMX_LOG_NONE, NULL);
	CheckErr();

	// Фиктивный вызов QMX_Prepare - выясним конфигурацию устройства
	// т.е. из каких модулей оно состоит
	QMX_MOD_SetActive(SD, 0, QMX_ON);
	QMX_Prepare(SD, 100, &CFG);
	CheckErr();
	QMX_MOD_SetActive(SD, 0, QMX_OFF);
	
	// Убедимся, что система состоит из нужных модулей - QMS20 и QMS45
	strncpy_s (s_wrk, CFG.ModName[0], 5); s_wrk[5] = 0;
	if (!_stricmp(s_wrk, "QMS20"))	printf("1st Module name OK\n");
	else{ printf("1st Module Name error\n");	quit();	}
	strncpy_s (s_wrk, CFG.ModName[1], 5); s_wrk[5] = 0;
	if (!_stricmp(s_wrk, "QMS45"))	printf("2nd Module name OK\n");
	else{ printf("2nd Module Name error\n");	quit();	}

	// Определим нужные указатели
	out_buf[0]=(WORD *)CFG.CBOutput.buf_start;								// указатель на первую половину буфера вывода
	out_buf[1]=(WORD *)(CFG.CBOutput.buf_start+(*CFG.CBOutput.buf_size>>1));	// указатель на вторую половину буфера вывода
	Data = (unsigned short int*)CFG.CBInput.buf_start;						// указатель на буфер ввода
	LengthDataBuf = *CFG.CBInput.buf_size;									// размер кольцевого буфера данных, заполняемого сервером	


	// Установим частоту работы АЦП
	QMX_QMS20_SetAdcRate(SD, 0, 1200000, &CRate); 
	printf("ADC  Rate = %8.2f Hz \n", CRate);

	// Установим частоту работы ЦАП
	QMX_QMS45_SetDacRate(SD, 1, 1000000.0, &CRate);
	printf("DAC  Rate = %8.2f Hz \n", CRate);


	// Считаем из ППЗУ АЦП колибровочные коэффициенты
	// Четырех первых входных каналов для диапазона +-5 В.
	for (i_wrk=0; i_wrk<4; i_wrk++) QMX_QMS20_ReadCC(SD, 0, i_wrk, QMX_QMS20_GAIN_1, &CC_Adc1[i_wrk]);

	// Заполним таблицу опроса модуля QMS20:
	// в сеансе сбора данных будут участвовать 4 первых каналов устройства - 
	// все с усилениями 1
	QMX_QMS20_BuildTableItem(SD, 0, QMX_QMS20_MODE_D, QMX_QMS20_GAIN_1,  &QMS20Table[0]);
	QMX_QMS20_BuildTableItem(SD, 1, QMX_QMS20_MODE_D, QMX_QMS20_GAIN_1,  &QMS20Table[1]);
	QMX_QMS20_BuildTableItem(SD, 2, QMX_QMS20_MODE_D, QMX_QMS20_GAIN_1,  &QMS20Table[2]);
	QMX_QMS20_BuildTableItem(SD, 3, QMX_QMS20_MODE_D, QMX_QMS20_GAIN_1,  &QMS20Table[3]);

	// Запишем таблицу опроса в модуль
	QMX_QMS20_SetTable(SD, 0, QMS20Table, 4);
	// Объявим АЦП активным
	QMX_MOD_SetActive(SD, 0, QMX_ON);


	// Теперь осуществим предварительную заливку буфера динамического ЦАП
	// для того, чтобы к моменту "Старта" в его локальном буфере уже были наготове нужные данные
	QMX_MOD_SetActive(SD, 1, QMX_ON);	// Для этого он объявляется активным
	QMX_Prepare(SD, 0, &CFG);			// После этого обязательно вызвать QMX_Prepare
	CheckErr();
	FillBuf(out_buf[0], *CFG.CBOutput.buf_size);		// Заполняем весь буфер вывода (обе половины) нужными данными
	QMX_MOD_OutputPrestage(SD, 1);						// Заливаем этот буфер в модуль


	// Создаем и запускаем отдельный поток вывода,
	// в котором будут формироваться данные для динамического ЦАП
	hWriteThread = CreateThread(0, 0x2000, ServiceWriteThread, 0x0, 0x0, &WriteTid);
	if(!hWriteThread) printf("Can't start output data thread!");


	// Запустим сеанс сбора данных - дадим "Старт"
	QMX_Start(SD);
	CheckErr();


	while (!IsThreadComplete){									// цикл непрерывного сбора данных (до окончания потока вывода на ЦАП)
																// который закончится по нажатию кнопки
		
		DataPtr = *CFG.CBInput.buf_ptr;							//получим значение указателя на последний 
																// полученный элемент данных						
		for (CurrentPtr = DataPtrOld; CurrentPtr != DataPtr; CurrentPtr += 2) { // цикл считывания всех полученных элементов данных
			
			if(CurrentPtr >= *CFG.CBInput.buf_size)				// Проверки, необходимые
				CurrentPtr = 0;									// чтобы текущий указатель 
			if ((!CurrentPtr)&&(!DataPtr))break;				// не перескочил через границу буфера

			mod = (Data[CurrentPtr] >> 5) & 0x07;				// в четном (младшем) 16-ти битном слове элемента данных содержится 
			ch  = Data[CurrentPtr]  & 0x1f;						// информация о номерах канала (младщие 5-ть бит) и модуля (сл. 3-ри бита).
			QMS_data[mod][ch] += Data[CurrentPtr+1];			// в нечетном (старшем) 16-ти битном слове элемента данных содержится
																// в нечетном (старшем) 16-ти битном слове элемента данных содержится
																// информация о величине сигнала измеренного в данном канале данного модуля
																// (в кодах АЦП).
																// в настоящем примере измерения интегрируются в соответствующей ([модуль][канал])
																// ячейке массива накопления. В реальной программе пользователя измерения могут
																// перемещаться в поканальные массивы без усреднения, для дальнейшей обработки или
																// записи в выходные файлы.
			QMS_iter[mod][ch]++;								// счетчик количества суммирований для данного канала
	
		}

		
		if(DataPtrOld != DataPtr){
				DisplayData();						// отобразим полученные с АЦП данные
		}
		
		DataPtrOld = DataPtr;						// запоминание предыдущего указателя на последний полученный
													// элемент данных

		printf("Write Counter: %3u\r", out_cntr);	// Отобразим счетчик отправленных половинок буферов
													// (счетчик меняется в параллельном потоке)


		Sleep(100);									// Ожидание следующей порции данных от сервера в течении 100 мс
													// В реальной программе в этом месте вместо Sleep может производится
													// обработка полученных данных и их запись в файл

 }

	quit();											// выход из программы
}


//------------------------------------------------------------------------
// Поток в котором осуществляется вывод данных в динамический ЦАП
//------------------------------------------------------------------------
DWORD WINAPI ServiceWriteThread(PVOID /*Context*/)
{
	while(!_kbhit()){				// Бесконечный цикл (до нажатия кнопки)				
	  if(out_cntr<*CFG.CBOutput.buf_ptr) {								// Если в сервере есть свободный буфер для отправки						
		   FillBuf(out_buf[out_cntr++&1], (*CFG.CBOutput.buf_size)>>1); // Заполним его очередной порцией данных
		   QMX_CircBufOutputUpdate(SD);									// И дадим команду серверу на отправку

	  }
	  Sleep(20);
	}

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

	return 0;							// Выйдем из потока

}

// Выход из программы
void quit(void){
	
	printf("\n\n Please wait....\r");
	QMX_Stop(SD, QMX_STOP_ALL);			// Остановим систему
	QMX_MOD_SetStartMode(SD, 0xFF, QMX_START_MODE_MANUAL); 									

	QMX_CircBufDetach(SD);              // Освободим виртуальный слот USB
										// Остановим сервер
	ShellExecute(NULL, TEXT("open"), TEXT("qmbox_ds.exe"), TEXT("-s0"), NULL, SW_SHOWNORMAL);
	printf("\n\n");
	ExitProcess(0);
}


// Проверка на ошибки
void CheckErr(void) {
char tmp_str[1024];

if (QMX_GetLastError(SD, tmp_str, sizeof(tmp_str))==QMX_OK){ return;}
else  {	printf("%s\n", tmp_str);	quit();}
}


// Отображение данных
void DisplayData(void){

	double d_wrk;

	// вычисление усредненных значений результатов измерений, приведение их к Вольтам при помощи 
	// калибровочных коэффициентов и печать на консоль :
	d_wrk = ((double)QMS_data[0][0]) / ((double)QMS_iter[0][0]) * CC_Adc1[0].Scale + CC_Adc1[0].Offset;
	printf("                     Data from ADC: %6.3f V ", d_wrk);
	d_wrk = ((double)QMS_data[0][1]) / ((double)QMS_iter[0][1]) * CC_Adc1[1].Scale + CC_Adc1[1].Offset;
	printf("%6.3f V ", d_wrk);
	d_wrk = ((double)QMS_data[0][2]) / ((double)QMS_iter[0][2]) * CC_Adc1[2].Scale + CC_Adc1[2].Offset;
	printf("%6.3f V ", d_wrk);
	d_wrk = ((double)QMS_data[0][3]) / ((double)QMS_iter[0][3]) * CC_Adc1[0].Scale + CC_Adc1[0].Offset;
	printf("%6.3f V\r", d_wrk);
	
	// обнуление рабочих ячеек усреднения результатов измерений :
	QMS_data[0][0] = 0; QMS_iter[0][0] = 0; 
	QMS_data[0][1] = 0; QMS_iter[0][1] = 0;
	QMS_data[0][2] = 0; QMS_iter[0][2] = 0;
	QMS_data[0][3] = 0; QMS_iter[0][3] = 0;

}


// Заполнение очередной порции данных для динамического ЦАП
void FillBuf(WORD *buf, DWORD len) {
#define _2PI	6.283185307179586476925286766559
DWORD i;
static double phase[8]={0.7, 1.5, 2.3, 2.9, 3.6, 4.3, 5.0, 5.7};
static double amp[8]={16000., 15000., 14000., 13000., 12000., 10000., 8000., 4000.}; 
static double freq[8]={0.2, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35};
static double j[8]={0};
 
  for(i=0; i<len; i++) *buf++=(WORD)(32767.-amp[i&0x7]*sin(phase[i&0x7]+(j[i&0x7]+=freq[i&0x7])>_2PI?j[i&0x7]-=_2PI:j[i&0x7]));
}
