view FXAnalyse.c @ 183:791ca26fee6a

Rewrite data writing to file Fmt() uses by default truncation to conver double arguments to their text representation. Rounding must be used. Rewrite using standard C functions to get rid of this problem (and probably make it more efficient). Extend to handle arbitrary number of channels and to report errors on opening the data files.
author Daniele Nicolodi <daniele.nicolodi@obspm.fr>
date Fri, 21 Feb 2014 18:42:30 +0100
parents 8faada7e4faf
children 12df3a2b18de
line wrap: on
line source

#include <tcpsupp.h>
#include <utility.h>
#include <ansi_c.h>
#include <lowlvlio.h>
#include <cvirte.h>		
#include <userint.h>
#include <formatio.h>
#include <inifile.h>
#include <string.h>

#include "FXAnalyse.h" 
#include "Plot.h"
#include "Allan.h"
#include "DDS4xAD9912.h"
#include "DDS_Fox.h" 
#include "muParserDLL.h"
#include "utils.h"
#include "stat.h"
#include "future.h"
#include "data-provider.h"
#include "sr-data-logger.h"
#include "config.h"
#include "logging.h"


#define FREP_STEP_SIZE 50000.0
#define SPEED_OF_LIGHT 299792458.0 // m/s
#define SR_WAVELENGTH  698.4460    // nm
#define HG_WAVELENGTH 1062.4000    // nm

// select which data provider to use
#ifdef NDEBUG
#define DataProvider KKDataProvider
#else
#define DataProvider FakeDataProvider
#endif


// data acquisition status
int acquiring;
// data queue
CmtTSQHandle dataQueue;
// data provider thread
CmtThreadFunctionID dataProviderThread;


struct event event;
double utc;
double Ch1, Ch2, Ch3, Ch4;
double Math1, Math2, Math3, Math4, Math5;
double N1, N2, N3;
double Ndiv = 8.0;
double Sign1 = 1.0, Sign2 = 1.0, Sign3 = 0.0;
void *MathParser1, *MathParser2, *MathParser3, *MathParser4, *MathParser5;


// panels
static int MainPanel;
static int CalcNPanel;
static int EstimateNPanel;


struct adev {
	Allan_Data allan;
	double *data;
	const char *title;
	double normalization;
	int control;
};


#define ADEV(__channel, __normalization)		\
	{											\
		.data = & ## __channel,					\
		.title = #__channel,					\
		.normalization = __normalization,		\
		.control = PANEL_ADEV_ ## __channel		\
	}


static int adev_toggle(struct adev *adev)
{
	if (adev->allan.active)
		Allan_ClosePanel(&(adev->allan));
	else
		Allan_InitPanel(&(adev->allan), adev->title,
			adev->normalization, MainPanel, adev->control);
	return adev->allan.active;
}


static inline void adev_update(struct adev *adev)
{
	if (adev->allan.active)
		Allan_AddFrequency(&(adev->allan), *(adev->data));
}


struct adev adevs[] = {
	ADEV(Ch1, 1.84e12),
	ADEV(Ch2, 10.0e3),
	ADEV(Ch3, 429.228e12),
	ADEV(Ch4, 275.0e3),
	ADEV(Math1, 250.0e6),
	ADEV(Math2, 194.395e12),
	ADEV(Math3, 282.143e12),
	ADEV(Math4, 429.228e12),
	ADEV(Math5, 1.0),
	{ NULL }
};


struct plot {
	Plot_Data plot;
	double *data;
	const char *title;
	double min;
	double max;
	int control;
};


#define PLOT(__channel, __min, __max) 			\
	{											\
		.data = & ## __channel, 				\
		.title = #__channel,					\
		.min = __min,							\
		.max = __max,							\
		.control = PANEL_PLOT_ ## __channel		\
	}


static int plot_toggle(struct plot *plot)
{
	if (plot->plot.active)
		Plot_ClosePanel(&(plot->plot));
	else
		Plot_InitPanel(&(plot->plot), plot->title,
			plot->min, plot->max, MainPanel, plot->control);
	return plot->plot.active;
}


static inline void plot_update(struct plot *plot)
{
	if (plot->plot.active)
		Plot_AddFrequency(&(plot->plot), *(plot->data));
}

struct plot plots[] = {
	PLOT(Ch1, 54.999e6, 55.001e6),
	PLOT(Ch2, 0.0, 0.0),
	PLOT(Ch3, 0.0, 0.0),
	PLOT(Ch4, 0.0, 0.0),
	PLOT(Math1, 0.0, 0.0),
	PLOT(Math2, 0.0, 0.0),
	PLOT(Math3, 0.0, 0.0),
	PLOT(Math4, 0.0, 0.0),
	PLOT(Math5, 0.0, 0.0),
	{ NULL }
};


// dedrift DDS
DDSParameter DDS1xAD9956;
// 4xAD9912 DDS box
DDS4xAD9912_Data DDS4xAD9912;


enum {
	LO = 1,
	HG = 2,
	SR = 4,
};

enum {
	N_MEASUREMENT_NONE,
	N_MEASUREMENT_INIT,
	N_MEASUREMENT_SLOPE,
	N_MEASUREMENT_ADJUST_FREQ_PLUS,
	N_MEASUREMENT_FREP_PLUS,
	N_MEASUREMENT_ADJUST_FREQ_MINUS,
	N_MEASUREMENT_FREP_MINUS,
};

int Measuring_1 = N_MEASUREMENT_NONE;
int Measuring_2 = N_MEASUREMENT_NONE;											  
int Measuring_3 = N_MEASUREMENT_NONE;

int nobs = 0;
int settling = 0;

double f0_DDS1 = 110000000.0, f0_DDS2, f0_DDS3, df_DDS3;

double SlopeTime1=40.0, DeltaT_1=20.0, delta_f_lock_1=500.0;
double SlopeTime2=40.0, DeltaT_2=20.0, delta_f_lock_2=500.0;
double SlopeTime3=40.0, DeltaT_3=20.0, delta_f_lock_3=500.0;

double t1, t2, t3;
double f_rep_slope, f_beat_slope;
double f_rep_plus, f_rep_minus;
double f_beat_plus, f_beat_minus;
double N_measured; 

double Frequencystep1=10000.0, tbegin1=0.0, Frepbefore1=0.0, Frequency1=0.0;
double Frequencystep2=10.0, tbegin2=0.0, Frepbefore2=0.0, Ch2before=0.0, Frequency2=0.0;
double Frequencystep3=100000.0, tbegin3=0.0, Frepbefore3=0.0, Frequency3=0.0;

int Getsign1=FALSE,Getsign2=FALSE,Getsign3=FALSE;

struct stat stat_math1, stat_ch2, stat_ch3, freq;
struct rollmean rollmean_ch1, rollmean_ch2, rollmean_ch3, rollmean_ch4;


// dedrift
enum {
	DEDRIFT_REFERENCE_MICROWAVE = 0,
	DEDRIFT_REFERENCE_HG = 1,
};

struct dedrift {
	int enabled;        // dedrift enabled
	int reference;      // reference frequency
	int invert;         // invert applied slope sign
	int doubleslope;    // double applied slope
	int keep_freq;      // keep current frequency value when dedrift is disabled
	int keep_slope;     // keep current slope value when dedrift is disabled
	double freq0;       // DDS center frequency
	double threshold;   // maximum allowed frequency change
	double applied;     // currently applied slope
	double interval;    // measurement duration
	double t0;          // beginning of currrent measurement interval
};

struct dedrift dedrift = {
	.enabled = FALSE,
	.reference = DEDRIFT_REFERENCE_MICROWAVE,
	.invert = FALSE,
	.doubleslope = FALSE,
	.keep_freq = TRUE,
	.keep_slope = TRUE,
	.freq0 = 70e6,
	.threshold = 100.0,
	.applied = 0.0,
	.interval = 30.0,
	.t0 = 0.0
};


// recenter
struct recenter {
	int enabled;		// recenter enabled
	int lo;				// recenter microwave beatnote
	int sr;				// recenter Sr beatnote
	int hg;				// recenter Hg beatnote
	double interval;	// interval
	double t0;			// beginning of current interval
};

struct recenter recenter = {
	.enabled = FALSE,
	.lo = FALSE,
	.sr = FALSE,
	.hg = FALSE,
	.interval = 1800.0,
	.t0 = 0.0
};


// data loggging
static char *datafolder;

struct datafile {
	char *name;
	double *data;
	int nchan;
	int control;
	int write;
};

#define DATAFILE(__name, __data, __nchan, __control, __write)	\
	{															\
		.name = __name,											\
		.data = __data,											\
		.nchan = __nchan,										\
		.control = __control,									\
		.write = __write										\
	}

struct datafile datafiles[] = {
     DATAFILE("Raw", event.data, 4, PANEL_SAVE_RAW, TRUE),
     DATAFILE("DDS", DDS4xAD9912.frequency, 4, PANEL_SAVE_DDS, FALSE),
     DATAFILE("Lo", &Math2, 1, PANEL_SAVE_LO, FALSE),
     DATAFILE("Hg", &Math3, 1, PANEL_SAVE_HG, FALSE),
     DATAFILE("Sr", &Math4, 1, PANEL_SAVE_SR, FALSE),
     DATAFILE("Ex", &Math5, 1, PANEL_SAVE_EXTRA, FALSE),
     { NULL, }
};


static void write_data(const char *folder, const char *name, const char *id, 
		const char *timestr, double utc, double *v, int nchan)
{
	int i, fd, len;
	char line[1024];
	char filename[FILENAME_MAX];
	
	// construct filename in the form folder\\id-name.txt
	snprintf(filename, sizeof(filename), "%s\\%s-%s.txt", folder, id, name);
	
	fd = open(filename, O_CREAT|O_WRONLY|O_APPEND);
	if (fd < 0) {
		logmessage(ERROR, "open data file %s: %s", filename, strerror(errno));
		return;
	}
	
	// timestamp
	len = snprintf(line, sizeof(line), "%s\t%.3f", timestr, utc);

	// data channels
	for (i = 0; i < nchan; i++)
		len += snprintf(line + len, sizeof(line) - len, "\t%.3f", v[i]);
	
	// newline
	line[len++] = '\r';
	line[len++] = '\n';
			
	// write to file
	write(fd, line, len);
	
	close(fd);
}


static inline void datafile_append(struct datafile *d, char *id, char *timestr)
{
	if (d->write)
		write_data(datafolder, d->name, id, timestr, utc, d->data, d->nchan);
}


static struct datalogger datalogger;


void * muParserNew()
{
	void *parser = mupCreate();
	
	mupDefineOprtChars(parser, "abcdefghijklmnopqrstuvwxyzµ"
                        	   "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                        	   "+-*^/?<>=#!$%&|~'_");
	
	mupDefineVar(parser, "Ch1", &Ch1);
	mupDefineVar(parser, "Ch2", &Ch2);
	mupDefineVar(parser, "Ch3", &Ch3);
	mupDefineVar(parser, "Ch4", &Ch4);
	mupDefineVar(parser, "DDS1", &(DDS4xAD9912.frequency[0]));
	mupDefineVar(parser, "DDS2", &(DDS4xAD9912.frequency[1]));
	mupDefineVar(parser, "DDS3", &(DDS4xAD9912.frequency[2]));
	mupDefineVar(parser, "DDS4", &(DDS4xAD9912.frequency[3]));
	mupDefineVar(parser, "N1", &N1);
	mupDefineVar(parser, "N2", &N2);
	mupDefineVar(parser, "N3", &N3);
	mupDefineVar(parser, "Sign1", &Sign1);
	mupDefineVar(parser, "Sign2", &Sign2);
	mupDefineVar(parser, "Sign3", &Sign3);
	mupDefineVar(parser, "Ndiv", &Ndiv);
	
	mupDefinePostfixOprt(parser, "P", &Peta,  1);
	mupDefinePostfixOprt(parser, "T", &Tera,  1);
	mupDefinePostfixOprt(parser, "G", &Giga,  1);
	mupDefinePostfixOprt(parser, "M", &Mega,  1);
	mupDefinePostfixOprt(parser, "k", &kilo,  1);
	mupDefinePostfixOprt(parser, "m", &milli, 1);
	mupDefinePostfixOprt(parser, "u", &micro, 1);
	mupDefinePostfixOprt(parser, "µ", &micro, 1);
	mupDefinePostfixOprt(parser, "n", &nano,  1);
	mupDefinePostfixOprt(parser, "p", &pico,  1);
	mupDefinePostfixOprt(parser, "f", &femto, 1);
	
	return parser;
}


void CVICALLBACK DataAvailableCB (CmtTSQHandle queueHandle, unsigned int event,
		int value, void *callbackData);


int main (int argc, char *argv[])
{
	int rv;
	double frequency;
	char expr[1024];
	char host[256];
	int port;
	double clock;

	if ((MainPanel = LoadPanel (0, "FXAnalyse.uir", PANEL)) < 0)
		return -1;
	if ((CalcNPanel = LoadPanel (MainPanel, "FXAnalyse.uir", CALCN)) < 0)
		return -1;
	if ((EstimateNPanel = LoadPanel (MainPanel, "FXAnalyse.uir", ESTIMATEN)) < 0)
		return -1;

	// logging
	logger_init();
	
	// load configuration file
	char path[MAX_PATHNAME_LEN];
	GetIniFilePath(path);
	IniText configuration = Ini_New(TRUE);
	Ini_ReadFromFile(configuration, path);

	// data folder
	rv = Ini_GetStringCopy(configuration, "data", "folder", &datafolder);
	if (rv > 0) {
		logmessage(INFO, "writing data files in %s", datafolder);
	} else {
		logmessage(ERROR, "data folder not configured in %s", path);
		// do not allow to start the acquisition
		SetCtrlAttribute(MainPanel, PANEL_STARTBUTTON, ATTR_DIMMED, TRUE);
	}
	
	// AD9956 configuration parameters
	rv = Ini_GetStringIntoBuffer(configuration, "AD9956", "host", host, sizeof(host));
	if (! rv)
		return -1;
	rv = Ini_GetInt(configuration, "AD9956", "port", &port);
	if (! rv)
		return -1;
	rv = Ini_GetDouble(configuration, "AD9956", "clock", &clock);
	if (! rv)
		return -1;
	
	// dedrift DDS
	DDSFox_Initialize(&DDS1xAD9956, host, port, dedrift.freq0);
	
	// AD9912 configuration parameters
	rv = Ini_GetStringIntoBuffer(configuration, "AD9912", "host", host, sizeof(host));
	if (! rv)
		return -1;
	rv = Ini_GetDouble(configuration, "AD9912", "clock", &clock);
	if (! rv)
		return -1;
	
	// initialize 4x AD9912 DDS box
	DDS4xAD9912_Init(&DDS4xAD9912, host, clock);
	DDS4xAD9912_Reset(&DDS4xAD9912);
	GetCtrlVal(MainPanel, PANEL_DDS1, &frequency);
	DDS4xAD9912_SetFrequency(&DDS4xAD9912, 1, frequency);
	GetCtrlVal(MainPanel, PANEL_DDS2, &frequency);
	DDS4xAD9912_SetFrequency(&DDS4xAD9912, 2, frequency);
	GetCtrlVal(MainPanel, PANEL_DDS3, &frequency);
	DDS4xAD9912_SetFrequency(&DDS4xAD9912, 3, frequency);
	GetCtrlVal(MainPanel, PANEL_DDS4, &frequency);
	DDS4xAD9912_SetFrequency(&DDS4xAD9912, 4, frequency);
	
	// dispose configuration
	Ini_Dispose(configuration);

	// Sr data logger
	sr_datalogger_init(&datalogger);
	
	GetCtrlVal(MainPanel, PANEL_N1CHOICE, &N1);
	GetCtrlVal(MainPanel, PANEL_N2CHOICE, &N2);
	GetCtrlVal(MainPanel, PANEL_N3CHOICE, &N3);

	MathParser1 = muParserNew();
	GetCtrlVal(MainPanel, PANEL_MATHSTRING1, expr);
	mupSetExpr(MathParser1, expr);
	
	MathParser2 = muParserNew();
	mupDefineVar(MathParser2, "Math1", &Math1);
	GetCtrlVal(MainPanel, PANEL_MATHSTRING2, expr);
	mupSetExpr(MathParser2, expr);
	
	MathParser3 = muParserNew();
	mupDefineVar(MathParser3, "Math1", &Math1);
	mupDefineVar(MathParser3, "Math2", &Math2);
	GetCtrlVal(MainPanel, PANEL_MATHSTRING3, expr);
	mupSetExpr(MathParser3, expr);
	
	MathParser4 = muParserNew();
	mupDefineVar(MathParser4, "Math1", &Math1);
	mupDefineVar(MathParser4, "Math2", &Math2);
	mupDefineVar(MathParser4, "Math3", &Math3);
	GetCtrlVal(MainPanel, PANEL_MATHSTRING4, expr);
	mupSetExpr(MathParser4, expr);
	
	MathParser5 = muParserNew();
	mupDefineVar(MathParser5, "Math1", &Math1);
	mupDefineVar(MathParser5, "Math2", &Math2);
	mupDefineVar(MathParser5, "Math3", &Math3);
	mupDefineVar(MathParser5, "Math4", &Math4);
	GetCtrlVal(MainPanel, PANEL_MATHSTRING5, expr);
	mupSetExpr(MathParser5, expr);
	
	// data queue
	CmtNewTSQ(128, sizeof(struct event), 0, &dataQueue);

	// register callback to execute when data will be in the data queue
	CmtInstallTSQCallback(dataQueue, EVENT_TSQ_ITEMS_IN_QUEUE, 1,
		DataAvailableCB, NULL, CmtGetCurrentThreadID(), NULL);
	
	DisplayPanel(MainPanel);
	
	RunUserInterface();
	
	DiscardPanel(MainPanel);
	return 0;
}

int CVICALLBACK QuitCallback (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			QuitUserInterface(0);
			break;
	}
	return 0;
}

int CVICALLBACK CB_OnEventMain(int panel, int event,
		void *callbackData, int eventData1, int eventData2)
{
	int control, index;
	double step;
	
#define do_arrow(__DDS, __STEP)										\
	do {															\
		GetCtrlIndex(panel, __STEP, &index);						\
		if ((eventData1 == VAL_RIGHT_ARROW_VKEY) && (index < 14))	\
			SetCtrlIndex(panel, __STEP, index + 1);					\
		if ((eventData1 == VAL_LEFT_ARROW_VKEY) && (index > 0))		\
			SetCtrlIndex(panel, __STEP, index - 1);					\
		GetCtrlVal(panel, __STEP, &step);							\
		SetCtrlAttribute(panel, __DDS, ATTR_INCR_VALUE, step);  	\
	} while (0) 
	
	switch (event) {
		case EVENT_KEYPRESS:
			/* key code */
			switch (eventData1) {
				case VAL_RIGHT_ARROW_VKEY:
				case VAL_LEFT_ARROW_VKEY:					
					control = GetActiveCtrl(panel);
					switch (control) {
						case PANEL_DDS1:
						case PANEL_DDS1STEP:
							do_arrow(PANEL_DDS1, PANEL_DDS1STEP);
							break;
						case PANEL_DDS2:
						case PANEL_DDS2STEP:
							do_arrow(PANEL_DDS2, PANEL_DDS2STEP);
							break;
						case PANEL_DDS3:
						case PANEL_DDS3STEP:
							do_arrow(PANEL_DDS3, PANEL_DDS3STEP);
							break;
						case PANEL_DDS4:
						case PANEL_DDS4STEP:
							do_arrow(PANEL_DDS4, PANEL_DDS4STEP);
							break;
					}
					break;
				case VAL_F2_VKEY :
					SetActiveCtrl(panel, PANEL_DDS1);
					break;
				case VAL_F3_VKEY :
					SetActiveCtrl(panel, PANEL_DDS2);
					break;
				case VAL_F4_VKEY :
					SetActiveCtrl(panel, PANEL_DDS3);
					break;
				case VAL_F5_VKEY :
					SetActiveCtrl(panel, PANEL_DDS4);
					break;
			}
			break;
	}
	return 0;
}

int CVICALLBACK CB_OnStart (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			if (acquiring)
				break;

			logmsg("start");
			SetCtrlAttribute(panel, PANEL_STARTBUTTON, ATTR_DIMMED, TRUE);
			acquiring = 1;
			
			// start data provider thread
			CmtScheduleThreadPoolFunctionAdv(
				DEFAULT_THREAD_POOL_HANDLE, DataProvider, NULL,
				THREAD_PRIORITY_HIGHEST, NULL, 0, NULL, 0, &dataProviderThread);

			break;
	}
	return 0;
}

int CVICALLBACK CB_OnStop (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event) {
		case EVENT_COMMIT:
			if (! acquiring)
				break;
			
			logmsg("stop");
			acquiring = 0;

			// wait for data provider thread to terminate
			CmtWaitForThreadPoolFunctionCompletion(
				DEFAULT_THREAD_POOL_HANDLE, dataProviderThread,
				OPT_TP_PROCESS_EVENTS_WHILE_WAITING);
			CmtReleaseThreadPoolFunctionID(
				DEFAULT_THREAD_POOL_HANDLE, dataProviderThread);

			SetCtrlAttribute(panel, PANEL_STARTBUTTON, ATTR_DIMMED, FALSE);
			
			break;
	}
	return 0;
}

void CVICALLBACK DataAvailableCB (CmtTSQHandle queueHandle, unsigned int ev,
		int value, void *callbackData)
{
	int read;
	
	switch (ev) {
		case EVENT_TSQ_ITEMS_IN_QUEUE:
			// read data from the data queue
			while (value > 0) {
				
				read = CmtReadTSQData(queueHandle, &event, 1, TSQ_INFINITE_TIMEOUT, 0);
				if (read != 1)
					logmsg("Error!");
				value = value - read;
				
				utc = event.time.tv_sec + event.time.tv_usec * 1e-6;
				Ch1 = event.data[0];
				Ch2 = event.data[1];
				Ch3 = event.data[2];
				Ch4 = event.data[3];

				// update display
				SetCtrlVal(MainPanel, PANEL_UTC, utc);
				SetCtrlVal(MainPanel, PANEL_CH1, Ch1);
				SetCtrlVal(MainPanel, PANEL_CH2, Ch2);
				SetCtrlVal(MainPanel, PANEL_CH3, Ch3);  
				SetCtrlVal(MainPanel, PANEL_CH4, Ch4);

				// compute
				Math1 = mupEval(MathParser1);
				Math2 = mupEval(MathParser2);
				Math3 = mupEval(MathParser3);
				Math4 = mupEval(MathParser4);
				Math5 = mupEval(MathParser5);
				
				// update display.  numeric controllers do not format values
				// with a thousands separator: use string controllers and a 
				// custom formatting function
				char buffer[256];
				SetCtrlVal(MainPanel, PANEL_MATH1, thousands(buffer, sizeof(buffer), "%.6f", Math1));
				SetCtrlVal(MainPanel, PANEL_MATH2, thousands(buffer, sizeof(buffer), "%.3f", Math2));
				SetCtrlVal(MainPanel, PANEL_MATH3, thousands(buffer, sizeof(buffer), "%.3f", Math3));
				SetCtrlVal(MainPanel, PANEL_MATH4, thousands(buffer, sizeof(buffer), "%.3f", Math4));
				SetCtrlVal(MainPanel, PANEL_MATH5, thousands(buffer, sizeof(buffer), "%.3f", Math5));

				// update timeseries plots
				for (struct plot *plot = plots; plot->data; plot++)
					plot_update(plot);

				// update allan deviation plots
				for (struct adev *adev = adevs; adev->data; adev++)
					adev_update(adev);
				
				// N measurement

				switch (Measuring_1) {
					
					case N_MEASUREMENT_NONE:
						// not measuring
						break;
					
					case N_MEASUREMENT_INIT:
						// initialization step
						
						// set DDS1 to nominal frequency
						DDS4xAD9912_SetFrequency(&DDS4xAD9912, 1, f0_DDS1);
						SetCtrlVal(MainPanel, PANEL_DDS1, DDS4xAD9912.frequency[0]);
						
						// record current DDS frequencies
						f0_DDS2 = DDS4xAD9912_GetFrequency(&DDS4xAD9912, 2);
						
						t1 = utc;
						t2 = t3 = 0.0;
						nobs = 0;
						stat_zero(&stat_math1);
						f_rep_plus = f_rep_minus = 0.0;
						
						// next step
						Measuring_1 += 1;
						break;
						
					case N_MEASUREMENT_SLOPE:
						// slope measurement
						
						stat_accumulate(&stat_math1, Math1);
						
						if ((utc - t1) > SlopeTime1) {
							f_rep_slope = stat_math1.slope;
							
							// frep positive step
							DDS4xAD9912_RampFrequency(&DDS4xAD9912, 1, f0_DDS1 + delta_f_lock_1, FREP_STEP_SIZE);
							SetCtrlVal(MainPanel, PANEL_DDS1, DDS4xAD9912.frequency[0]);
							
							// allow counter to settle
							settling = 3;
							
							// next step
							Measuring_1 += 1;
						}
						break;
						
					case N_MEASUREMENT_ADJUST_FREQ_PLUS:
					case N_MEASUREMENT_ADJUST_FREQ_MINUS:
						// adjust DDS frequency to keep beatnote within the bandpass filter
						
						if (settling-- > 0)
							break;
						
						double fDDS2 = DDS4xAD9912_GetFrequency(&DDS4xAD9912, 2);
						DDS4xAD9912_SetFrequency(&DDS4xAD9912, 2, fDDS2 + 275000 - Ch4);
						SetCtrlVal(MainPanel, PANEL_DDS2, DDS4xAD9912.frequency[1]);
						
						// allow counter to settle
						settling = 3;
						
						// next step
						Measuring_1 += 1;
						break;								
						
					case N_MEASUREMENT_FREP_PLUS:
						// frep positive step
						
						if (settling-- > 0)
							break;
						
						if (t2 == 0.0)
							t2 = utc;
						
						f_rep_plus += Math1 - f_rep_slope * (utc - t2);
						nobs += 1;
						
						if ((utc - t2) > DeltaT_1) {
							f_rep_plus = f_rep_plus / nobs;
							nobs = 0;
							
							// frep negative step
							DDS4xAD9912_RampFrequency(&DDS4xAD9912, 1, f0_DDS1 - delta_f_lock_1, FREP_STEP_SIZE);
							SetCtrlVal(MainPanel, PANEL_DDS1, DDS4xAD9912.frequency[0]);
							
							// allow counter to settle
							settling = 3;
							
							// next step
							Measuring_1 += 1;
						}
						break;
						
					case N_MEASUREMENT_FREP_MINUS:
						// frep negative step
						
						if (settling-- > 0)
							break;
						
						if (t3 == 0.0)
							t3 = utc;
						
						f_rep_minus += Math1 - f_rep_slope * (utc - t2);
						nobs += 1;
						
						if ((utc - t3) > DeltaT_1) {
							f_rep_minus = f_rep_minus / nobs;
							nobs = 0;
							
							// compute N1
							double delta_f_rep = f_rep_minus - f_rep_plus;
							N_measured = Sign1 * 2 * Ndiv * delta_f_lock_1 / delta_f_rep;
							SetCtrlVal(CalcNPanel, CALCN_N, N_measured);
							
							// back to nominal frep
							DDS4xAD9912_RampFrequency(&DDS4xAD9912, 1, f0_DDS1, FREP_STEP_SIZE);
							DDS4xAD9912_SetFrequency(&DDS4xAD9912, 2, f0_DDS2);
							SetCtrlVal(MainPanel, PANEL_DDS1, DDS4xAD9912.frequency[0]);
							SetCtrlVal(MainPanel, PANEL_DDS2, DDS4xAD9912.frequency[1]);
							
							// done
							Measuring_1 = N_MEASUREMENT_NONE;
						}
						break;
				}

				switch (Measuring_2) {

					case N_MEASUREMENT_NONE:
						// not measuring
						break;
					
					case N_MEASUREMENT_INIT:
						// initialization step
						
						// set DDS1 to nominal frequency
						DDS4xAD9912_SetFrequency(&DDS4xAD9912, 1, f0_DDS1);
						SetCtrlVal(MainPanel, PANEL_DDS1, DDS4xAD9912.frequency[0]);
						
						// record current DDS frequencies
						f0_DDS2 = DDS4xAD9912_GetFrequency(&DDS4xAD9912, 2);
						f0_DDS3 = DDS4xAD9912_GetFrequency(&DDS4xAD9912, 3);
						
						t1 = utc;
						t2 = t3 = 0.0;
						nobs = 0;
						stat_zero(&stat_math1);
						stat_zero(&stat_ch2);
						f_rep_plus = f_rep_minus = 0.0;
						f_beat_plus = f_beat_minus = 0.0;
						
						// next step
						Measuring_2 += 1;
						break;
						
					case N_MEASUREMENT_SLOPE:
						// slope measurement
						
						stat_accumulate(&stat_math1, Math1);
						stat_accumulate(&stat_ch2, Ch2);

						if ((utc - t1) > SlopeTime2) {
							f_rep_slope = stat_math1.slope;
							f_beat_slope = stat_ch2.slope;
							
							// frep positive step
							double fDDS1 = f0_DDS1 + delta_f_lock_2;
							DDS4xAD9912_RampFrequency(&DDS4xAD9912, 1, fDDS1, FREP_STEP_SIZE);
							SetCtrlVal(MainPanel, PANEL_DDS1, DDS4xAD9912.frequency[0]);
							
							// adjust DDS3 to keep beatnote within the bandpass filter. prediction
							double fDDS3 = f0_DDS3 + Sign1 * Sign2 * N2/N1 * Ndiv * delta_f_lock_2;
							df_DDS3 = fDDS3 - DDS4xAD9912_GetFrequency(&DDS4xAD9912, 3);
							DDS4xAD9912_SetFrequency(&DDS4xAD9912, 3, fDDS3);
							SetCtrlVal(MainPanel, PANEL_DDS3, DDS4xAD9912.frequency[2]);
						
							// allow counter to settle
							settling = 3;
							
							// next step
							Measuring_2 += 1;
						}
						break;

					case N_MEASUREMENT_ADJUST_FREQ_PLUS:
					case N_MEASUREMENT_ADJUST_FREQ_MINUS:
						// adjust DDS frequency to keep beatnote within the bandpass filter
						
						if (settling-- > 0)
							break;
						
						double fDDS2 = DDS4xAD9912_GetFrequency(&DDS4xAD9912, 2) + 275000 - Ch4;
						DDS4xAD9912_SetFrequency(&DDS4xAD9912, 2, fDDS2);
						SetCtrlVal(MainPanel, PANEL_DDS2, DDS4xAD9912.frequency[1]);
						
						double fDDS3 = DDS4xAD9912_GetFrequency(&DDS4xAD9912, 3) + 10000 - Ch2;
						df_DDS3 = df_DDS3 + 10000 - Ch2;
						DDS4xAD9912_SetFrequency(&DDS4xAD9912, 3, fDDS3);
						SetCtrlVal(MainPanel, PANEL_DDS3, DDS4xAD9912.frequency[2]);

						// allow counter to settle
						settling = 3;

						// next step
						Measuring_2 += 1;
						break;
						
					case N_MEASUREMENT_FREP_PLUS:
						// frep positive step
						
						if (settling-- > 0)
							break;

						if (t2 == 0.0)
							t2 = utc;

						f_rep_plus += Math1 + 250000000 - f_rep_slope * (utc - t2);
						f_beat_plus += Ch2 - f_beat_slope * (utc - t2);
						nobs += 1;
						
						if ((utc - t2) > DeltaT_2) {
							f_rep_plus = f_rep_plus / nobs;
							f_beat_plus = f_beat_plus / nobs;
							nobs = 0;

							// negative frequency step
							double fDDS1 = f0_DDS1 - delta_f_lock_2;
							DDS4xAD9912_RampFrequency(&DDS4xAD9912, 1, fDDS1, FREP_STEP_SIZE);
							SetCtrlVal(MainPanel, PANEL_DDS1, DDS4xAD9912.frequency[0]);
							
							// adjust DDS3 to keep beatnote within the bandpass filter. prediction
							double fDDS3 = f0_DDS3 - Sign1 * Sign2 * N2/N1 * Ndiv * delta_f_lock_2;
							df_DDS3 = fDDS3 - DDS4xAD9912_GetFrequency(&DDS4xAD9912, 3);
							DDS4xAD9912_SetFrequency(&DDS4xAD9912, 3, fDDS3);
							SetCtrlVal(MainPanel, PANEL_DDS3, DDS4xAD9912.frequency[2]);

							// allow counter to settle
							settling = 3;
							
							// next step
							Measuring_2 += 1;
						}
						break;
						
					case N_MEASUREMENT_FREP_MINUS:
						// frep negative step
						
						if (settling-- > 0)
							break;
						
						if (t3 == 0.0)
							t3 = utc;

						f_rep_minus += Math1 + 250000000 - f_rep_slope * (utc - t2);
						f_beat_minus += Ch2 + f_beat_slope * (utc - t2);
						nobs += 1;

						if ((utc -t3) > DeltaT_2) {
							f_rep_minus = f_rep_minus / nobs;
							f_beat_minus = f_beat_minus / nobs;
							nobs = 0;

							// compute N2
							double delta_f_rep_m = f_rep_minus - f_rep_plus;
							double delta_f_rep = Sign1 * Ndiv * 2.0 * delta_f_lock_3 / N1;
							
							logmsg("delta frep: measured=%e expected=%e difference=%e",
								delta_f_rep_m, delta_f_rep, delta_f_rep_m - delta_f_rep);
							
							N_measured = -Sign2 * (df_DDS3 + f_beat_minus - f_beat_plus) / delta_f_rep;
							SetCtrlVal(CalcNPanel, CALCN_N, N_measured);
							
							// back to nominal frequency
							DDS4xAD9912_RampFrequency(&DDS4xAD9912, 1, f0_DDS1, FREP_STEP_SIZE);
							DDS4xAD9912_SetFrequency(&DDS4xAD9912, 2, f0_DDS2);
							DDS4xAD9912_SetFrequency(&DDS4xAD9912, 3, f0_DDS3);
							SetCtrlVal(MainPanel, PANEL_DDS1, DDS4xAD9912.frequency[0]);
							SetCtrlVal(MainPanel, PANEL_DDS2, DDS4xAD9912.frequency[1]);
							SetCtrlVal(MainPanel, PANEL_DDS3, DDS4xAD9912.frequency[2]);
							
							// done
							Measuring_2 = N_MEASUREMENT_NONE;
						}
						break;
				}
				
				switch (Measuring_3) {
					
					case N_MEASUREMENT_NONE:
						// not measuring N3
						break;
						
					case N_MEASUREMENT_INIT:
						// init
						
						// set DDS1 to nominal frequency
						DDS4xAD9912_SetFrequency(&DDS4xAD9912, 1, f0_DDS1);
						SetCtrlVal(MainPanel, PANEL_DDS1, DDS4xAD9912.frequency[0]);
						
						// record current DDS frequencies
						f0_DDS2 = DDS4xAD9912_GetFrequency(&DDS4xAD9912, 2);
						f0_DDS3 = DDS4xAD9912_GetFrequency(&DDS4xAD9912, 3);
						
						t1 = utc;
						t2 = t3 = 0.0;
						nobs = 0;
						stat_zero(&stat_math1);
						stat_zero(&stat_ch3);
						f_rep_plus = f_rep_minus = 0.0;
						f_beat_plus = f_beat_minus = 0.0;
						
						// next step
						Measuring_3 += 1;
						break;
						
					case N_MEASUREMENT_SLOPE:
						// slope measurement
						
						if (settling-- > 0)
							break;
						
						stat_accumulate(&stat_math1, Math1);
						stat_accumulate(&stat_ch3, Ch3);
						
						if (utc - t1 > SlopeTime3) {
							// slope measurement
							f_rep_slope = stat_math1.slope;
							f_beat_slope = stat_ch3.slope;
							
							logmsg("f_rep_slope=%e Hz/s", f_rep_slope);
							logmsg("f_beat_slope=%e Hz/s", f_rep_slope);
							
							// frep positive step
							DDS4xAD9912_RampFrequency(&DDS4xAD9912, 1, f0_DDS1 + delta_f_lock_3, FREP_STEP_SIZE);
							SetCtrlVal(MainPanel, PANEL_DDS1, DDS4xAD9912.frequency[0]);
							
							// adjust DDS3 to keep beatnote within the bandpass filter
							double fDDS3 = f0_DDS3 + Sign1 * Sign3 * N3/N1 * Ndiv * delta_f_lock_3;
							DDS4xAD9912_SetFrequency(&DDS4xAD9912, 3, fDDS3);
							SetCtrlVal(MainPanel, PANEL_DDS3, DDS4xAD9912.frequency[2]);
							
							// allow counter to settle
							settling = 3;
							
							// next step
							Measuring_3 += 1;
						}
						break;

					case N_MEASUREMENT_ADJUST_FREQ_PLUS:
					case N_MEASUREMENT_ADJUST_FREQ_MINUS:
						// adjust DDS frequency to keep beatnote within the bandpass filter

						if (settling-- > 0)
							break;
						
						// adjust DDS frequency to keep 55 MHz tracker oscillator locked
						double fDDS2 = DDS4xAD9912_GetFrequency(&DDS4xAD9912, 2) + 275000 - Ch4;
						DDS4xAD9912_SetFrequency(&DDS4xAD9912, 2, fDDS2);
						SetCtrlVal(MainPanel, PANEL_DDS2, DDS4xAD9912.frequency[1]);
						
						// allow counter to settle
						settling = 3;
						
						// next step
						Measuring_3 += 1;
						break;						
						
					case N_MEASUREMENT_FREP_PLUS:
						// frep positive step
						
						if (settling-- > 0)
							break;

						if (t2 == 0.0)
							t2 = utc;
						
						f_rep_plus += Math1 + 250000000 - f_rep_slope * (utc - t2);
						f_beat_plus += Ch3 - f_beat_slope * (utc - t2);
						nobs += 1;
						
						if (utc - t2 > DeltaT_3) {
							f_rep_plus = f_rep_plus / nobs;
							f_beat_plus = f_beat_plus / nobs;
							nobs = 0;
							
							// frep negative step
							DDS4xAD9912_RampFrequency(&DDS4xAD9912, 1, f0_DDS1 - delta_f_lock_3, FREP_STEP_SIZE);
							SetCtrlVal(MainPanel, PANEL_DDS1, DDS4xAD9912.frequency[0]);

							// adjust DDS3 to keep beatnote within the bandpass filter
							double fDDS3 = f0_DDS3 - Sign1 * Sign3 * N3/N1 * Ndiv * delta_f_lock_3;
							DDS4xAD9912_SetFrequency(&DDS4xAD9912, 3, fDDS3);
							SetCtrlVal(MainPanel, PANEL_DDS3, DDS4xAD9912.frequency[2]);
							
							// allow counter to settle
							settling = 3;
							
							// next step
							Measuring_3 += 1;
						}
						break;
					
					case N_MEASUREMENT_FREP_MINUS:
						// frep negative step
						
						if (settling-- > 0)
							break;
						
						if (t3 == 0.0)
							t3 = utc;
						
						f_rep_minus += Math1 + 250000000 - f_rep_slope * (utc - t2);
						f_beat_minus += Ch3 - f_beat_slope * (utc - t2);
						nobs += 1;
						
						if (utc - t3 > DeltaT_3) {
							f_rep_minus = f_rep_minus / nobs;
							f_beat_minus = f_beat_minus / nobs;
							nobs = 0;
							
							// check delta frep
							double delta_f_rep_m = f_rep_plus - f_rep_minus;
							double delta_f_rep = Sign1 * Ndiv * 2.0 * delta_f_lock_3 / N1;
							
							logmsg("delta frep: measured=%e expected=%e difference=%e",
								delta_f_rep_m, delta_f_rep, delta_f_rep_m - delta_f_rep);
							
							logmsg("f_beat_minus=%e", f_beat_minus);
							logmsg("f_beat_plus =%e", f_beat_plus);
							
							// compute N3
							double delta_f_beat = f_beat_plus - f_beat_minus + 2.0 * Sign1 * Sign3 * N3/N1 * Ndiv * delta_f_lock_3;
							double delta_f_beat_expected = delta_f_rep * N3;
							
							logmsg("delta fbeat: measured=%e expected=%e difference=%e",
								delta_f_beat, delta_f_beat_expected, delta_f_beat - delta_f_beat_expected);
							
							N_measured = delta_f_beat / delta_f_rep;
							SetCtrlVal(CalcNPanel, CALCN_N, N_measured);
							
							logmsg("measured N3=%.3f", N_measured);
							
							// back to nominal frep
							DDS4xAD9912_RampFrequency(&DDS4xAD9912, 1, f0_DDS1, FREP_STEP_SIZE);
							DDS4xAD9912_SetFrequency(&DDS4xAD9912, 2, f0_DDS2);
							DDS4xAD9912_SetFrequency(&DDS4xAD9912, 3, f0_DDS3);
							SetCtrlVal(MainPanel, PANEL_DDS1, DDS4xAD9912.frequency[0]);
							SetCtrlVal(MainPanel, PANEL_DDS2, DDS4xAD9912.frequency[1]);
							SetCtrlVal(MainPanel, PANEL_DDS3, DDS4xAD9912.frequency[2]);
							
							// done
							Measuring_3 = N_MEASUREMENT_NONE;
						}
						break;
				}
				
				// beatnote sign determination
				
				if (Getsign1 == TRUE) {
					if (utc > tbegin1+2) {
						if (Math1 > Frepbefore1)
							Sign1 = -1.0;
						else
							Sign1 = +1.0;
						DDS4xAD9912_SetFrequency(&DDS4xAD9912, 1, Frequency1);
						Getsign1 = FALSE;
						SetCtrlVal(MainPanel, PANEL_SIGN1, Sign1);
					}
				}
				if (Getsign2 == TRUE) {
					if (utc > tbegin2+2){
						if (Math1 > Frepbefore2) { 
							if (Ch2 > Ch2before)
								Sign2 = +1.0;
							else
								Sign2 = -1.0;
						} else {
							if (Ch2 > Ch2before)
								Sign2 = -1.0;
							else 
								Sign2 = +1.0;
						}
						DDS4xAD9912_SetFrequency(&DDS4xAD9912, 1, Frequency2);
						Getsign2 = FALSE;
						SetCtrlVal(MainPanel, PANEL_SIGN2, Sign2);
					}
				}
				if (Getsign3 == TRUE) {
					if (utc > tbegin3+2){
						if (Ch3 > Frepbefore3)
							Sign3 = -1.0;
						else
							Sign3 = +1.0;
						DDS4xAD9912_SetFrequency(&DDS4xAD9912, 4, Frequency3);
						Getsign3 = FALSE;
						SetCtrlVal(MainPanel, PANEL_SIGN3, Sign3);
					}
				}
				
				// select reference
				double f = 0.0;
				switch (dedrift.reference) {
					case DEDRIFT_REFERENCE_MICROWAVE:
						f = Math2;
						break;
					case DEDRIFT_REFERENCE_HG:
						f = Ch2 * 1062.5 / 1542.2;
						break;
				}
				
				// stop dedrift if the comb is not locked
				if ((dedrift.enabled)
					& (dedrift.threshold != 0.0)
					& (freq.previous != 0.0)
					& (fabs(f - freq.previous) > dedrift.threshold)) {
						
					if (! dedrift.keep_slope) {
						dedrift.applied = 0.0;
						DDSFox_SetSweepRate(&DDS1xAD9956, dedrift.applied);
						SetCtrlVal(MainPanel, PANEL_SLOPE_APPLIED, dedrift.applied);
					}
					if (! dedrift.keep_freq) {
						DDSFox_Set(&DDS1xAD9956, dedrift.freq0, dedrift.applied);
					}
					
					stat_zero(&freq);
					SetCtrlVal(MainPanel, PANEL_SLOPE_MEASURED, freq.slope);
					dedrift.enabled = FALSE;
					SetCtrlVal(MainPanel, PANEL_MEASURE_SLOPE, 0);
				}
				
				// dedrifting 
				if (dedrift.enabled)
				{
					// update slope measurement
					stat_accumulate(&freq, f);
					
					// update indicator
					SetCtrlVal(MainPanel, PANEL_SLOPE_MEASURED, freq.slope);
					
					// update applied slope
					if ((utc - dedrift.t0) > dedrift.interval) {
						
						if (dedrift.invert)
							dedrift.applied -= freq.slope;
						else
							dedrift.applied += freq.slope;
						
						SetCtrlVal(MainPanel, PANEL_SLOPE_APPLIED, dedrift.applied);
						
						if (dedrift.doubleslope)
							DDSFox_SetSweepRate(&DDS1xAD9956, dedrift.applied * 2.0);
						else
							DDSFox_SetSweepRate(&DDS1xAD9956, dedrift.applied);
						
						logmsg("dedrift update: adjustment=%+3e slope=%+3e", freq.slope, dedrift.applied);
						
						stat_zero(&freq);
						dedrift.t0 = utc;
					}
				}
				
				// recenter
				if (recenter.enabled)
				{
					rollmean_accumulate(&rollmean_ch2, Ch2);
					rollmean_accumulate(&rollmean_ch3, Ch3);
					rollmean_accumulate(&rollmean_ch4, Ch4);
					
					if ((utc - recenter.t0) > recenter.interval) {
		
						if (recenter.lo) {
							// adjust DDS2 frequency to keep Ch4 reading at 275 kHz
							double freq = DDS4xAD9912_GetFrequency(&DDS4xAD9912, 2);
							double adj = 275000.0 - rollmean_ch4.mean;
							freq = freq + adj;
							DDS4xAD9912_SetFrequency(&DDS4xAD9912, 2, freq);
							SetCtrlVal(MainPanel, PANEL_DDS2, DDS4xAD9912.frequency[1]);
							logmsg("recenter ch4 to 275 kHz: DDS2 adjustment=%+3e", adj);
						}
					
						if (recenter.hg) {
							// adjust DDS3 frequency to keep Ch2 reading at 10 kHz
							double freq = DDS4xAD9912_GetFrequency(&DDS4xAD9912, 3);
							double adj = 10000 - rollmean_ch2.mean;
							freq = freq + adj;
							DDS4xAD9912_SetFrequency(&DDS4xAD9912, 3, freq);
							SetCtrlVal(MainPanel, PANEL_DDS3, DDS4xAD9912.frequency[2]);
							logmsg("recenter Hg beatnote (ch2) to 10 kHz: DDS3 adjustment=%+3e", adj);
						}
					
						if (recenter.sr) {
							// adjust DDS3 frequency to keep Ch3 reading at 10 kHz
							double freq = DDS4xAD9912_GetFrequency(&DDS4xAD9912, 3);
							double adj = 10000 - rollmean_ch3.mean;
							freq = freq + adj;
							DDS4xAD9912_SetFrequency(&DDS4xAD9912, 3, freq);
							SetCtrlVal(MainPanel, PANEL_DDS3, DDS4xAD9912.frequency[2]);
							logmsg("recenter Sr beatnote (ch3) to 10 kHz: DDS3 adjustment=%+3e", adj);
						}
					
						recenter.t0 = utc;
						rollmean_zero(&rollmean_ch2);
						rollmean_zero(&rollmean_ch3);
						rollmean_zero(&rollmean_ch4);
					}
				}

				// local time
				struct tm *ltime = localtime(&event.time.tv_sec);
				// round to milliseconds
				int msec = round(event.time.tv_usec / 1000.0);
				while (msec >= 1000) {
					ltime->tm_sec += 1;
					msec -= 1000;
				}
				// format local time
				char timestr[24];
				int len = strftime(timestr, sizeof(timestr), "%d/%m/%Y %H:%M:%S", ltime);
				snprintf(timestr + len, sizeof(timestr) - len, ".%03d", msec);
				// display local time
				SetCtrlVal(MainPanel, PANEL_TIME, timestr);
				
				// run id derived from current local date in the form YYMMDD
				char id[7];
				strftime(id, sizeof(id), "%y%m%d", ltime);
				
				// write datafiles
				for (struct datafile *d = datafiles; d->data; d++)
					datafile_append(d, id, timestr);
				
				// send Sr frequency (Math4) to Sr data logger
				sr_datalogger_send(&datalogger, utc, Math4);
			}		
			break;
	}
}

int CVICALLBACK CB_OnFreqPlot (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			for (struct plot *plot = plots; plot->data; plot++) {
				if (plot->control == control)
					plot_toggle(plot);
			}
			break;
	}
	return 0;
}

int CVICALLBACK CB_OnAllanPlot (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			for (struct adev *adev = adevs; adev->data; adev++) {
				if (adev->control == control)
					adev_toggle(adev);
			}
			break;
	}
	return 0;
}

int  CVICALLBACK CB_ChangeDDSOut (int panel, int control, int event, 
		 void *callbackData, int eventData1, int eventData2)
{
	switch (event) {
		case EVENT_COMMIT:
			double frequency;
			GetCtrlVal(MainPanel, control, &frequency);
			switch (control) {
				case PANEL_DDS1:
					DDS4xAD9912_SetFrequency(&DDS4xAD9912, 1, frequency);
					SetCtrlVal(MainPanel, PANEL_DDS1, DDS4xAD9912.frequency[0]);
					break;
				case PANEL_DDS2:
					DDS4xAD9912_SetFrequency(&DDS4xAD9912, 2, frequency);
					SetCtrlVal(MainPanel, PANEL_DDS2, DDS4xAD9912.frequency[1]);
					break;
				case PANEL_DDS3:
					DDS4xAD9912_SetFrequency(&DDS4xAD9912, 3, frequency);
					SetCtrlVal(MainPanel, PANEL_DDS3, DDS4xAD9912.frequency[2]);
					break;
				case PANEL_DDS4:
					DDS4xAD9912_SetFrequency(&DDS4xAD9912, 4, frequency);
					SetCtrlVal(MainPanel, PANEL_DDS4, DDS4xAD9912.frequency[3]);
					break;
			}
			break;
	}
	return 0;
}

int  CVICALLBACK CB_ChangeDDSStep (int panel, int control, int event, 
		 void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			double step;
			GetCtrlVal(panel, control, &step);
			switch (control)
			{
				case PANEL_DDS1STEP:
					SetCtrlAttribute(panel, PANEL_DDS1, ATTR_INCR_VALUE, step);
					break;
				case PANEL_DDS2STEP:
					SetCtrlAttribute(panel, PANEL_DDS2, ATTR_INCR_VALUE, step);
					break;
				case PANEL_DDS3STEP:
					SetCtrlAttribute(panel, PANEL_DDS3, ATTR_INCR_VALUE, step);
					break;
				case PANEL_DDS4STEP:
					SetCtrlAttribute(panel, PANEL_DDS4, ATTR_INCR_VALUE, step);
					break;
			}
			break;
	}
	return 0;
}

int CVICALLBACK CB_ChangeMath (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	int len;
	char *string;
		
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlAttribute(panel, control, ATTR_STRING_TEXT_LENGTH, &len);
			string = (char *)malloc(sizeof(char) * (len + 1));
			GetCtrlVal(panel, control, string);
			switch (control) {
				case PANEL_MATHSTRING1:
					mupSetExpr(MathParser1, string);
					break;
				case PANEL_MATHSTRING2:
					mupSetExpr(MathParser2, string);
					break;
				case PANEL_MATHSTRING3:
					mupSetExpr(MathParser3, string);
					break;
				case PANEL_MATHSTRING4:
					mupSetExpr(MathParser4, string);
					break;
				case PANEL_MATHSTRING5:
					mupSetExpr(MathParser5, string);
					break;
			}
			free(string);
			break;
	}
	return 0;
}

int CVICALLBACK CB_ChangeN (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			switch (control)
			{
				case PANEL_N1CHOICE:
					GetCtrlVal(panel, control, &N1);
					break;
				case PANEL_N2CHOICE:
					GetCtrlVal(panel, control, &N2);
					break;
				case PANEL_N3CHOICE:
					GetCtrlVal(panel, control, &N3);
					break;
			}
			break;
	}
	return 0;
}

int CVICALLBACK CB_OnAcceptN (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	int measure;
	
	switch (event) {
		case EVENT_COMMIT:
			GetPanelAttribute(panel, ATTR_CALLBACK_DATA, &measure);
			switch (measure) {
				case LO:
					N1 = round(N_measured);
					SetCtrlVal(MainPanel, PANEL_N1CHOICE, N1);
					break;
				case HG:
					N2 = round(N_measured);
					SetCtrlVal(MainPanel, PANEL_N2CHOICE, N2);
					break;
				case SR:
					N3 = round(N_measured);
					SetCtrlVal(MainPanel, PANEL_N3CHOICE, N3);
					break;
			} 
			break;
	}
	return 0;
}

int CVICALLBACK CB_OnNCalculus (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	int visible;
	
	switch (event) {
		case EVENT_COMMIT:
			switch (control) {
				case PANEL_N1CALCULUS:
					GetPanelAttribute(CalcNPanel, ATTR_VISIBLE, &visible);
					if (! visible) {
						SetPanelAttribute(panel, ATTR_CALLBACK_DATA, LO);
						SetPanelAttribute(CalcNPanel, ATTR_TITLE, "Measure N_Lo");
						SetCtrlVal(CalcNPanel, CALCN_INTEGRATIONTIME, DeltaT_1);
						SetCtrlVal(CalcNPanel, CALCN_SLOPETIME, SlopeTime1);
						SetCtrlVal(CalcNPanel, CALCN_DELTAFREQ, delta_f_lock_1 / 1000.0);
						SetCtrlVal(CalcNPanel, CALCN_N, 0.0);
						DisplayPanel(CalcNPanel);
					}
					break;
				case PANEL_N2CALCULUS:
					GetPanelAttribute(CalcNPanel, ATTR_VISIBLE, &visible);
					if (! visible) {
						SetPanelAttribute(panel, ATTR_CALLBACK_DATA, HG);
						SetPanelAttribute(CalcNPanel, ATTR_TITLE, "Measure N_Hg");
						SetCtrlVal(CalcNPanel, CALCN_INTEGRATIONTIME, DeltaT_2);
						SetCtrlVal(CalcNPanel, CALCN_SLOPETIME, SlopeTime2);
						SetCtrlVal(CalcNPanel, CALCN_DELTAFREQ, delta_f_lock_2 / 1000.0);
						SetCtrlVal(CalcNPanel, CALCN_N, 0.0);
						DisplayPanel(CalcNPanel);
					} 
					break;
				case PANEL_N3CALCULUS:
					GetPanelAttribute(CalcNPanel, ATTR_VISIBLE, &visible);
					if (! visible) {
						SetPanelAttribute(panel, ATTR_CALLBACK_DATA, SR);
						SetPanelAttribute(CalcNPanel, ATTR_TITLE, "Measure N_Sr");
						SetCtrlVal(CalcNPanel, CALCN_INTEGRATIONTIME, DeltaT_3);
						SetCtrlVal(CalcNPanel, CALCN_SLOPETIME, SlopeTime3);
						SetCtrlVal(CalcNPanel, CALCN_DELTAFREQ, delta_f_lock_3 / 1000.0);
						SetCtrlVal(CalcNPanel, CALCN_N, 0.0);
						DisplayPanel(CalcNPanel);
					}
					break;
			}
			break;
	}
	return 0;
}

int CVICALLBACK CB_OnStartNCalculus (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	int measuring;
	
	switch (event) {
		case EVENT_COMMIT:
			GetPanelAttribute(panel, ATTR_CALLBACK_DATA, &measuring);
			switch (measuring) {
				case LO:
					GetCtrlVal(CalcNPanel, CALCN_INTEGRATIONTIME, &DeltaT_1);
					GetCtrlVal(CalcNPanel, CALCN_SLOPETIME, &SlopeTime1);
					GetCtrlVal(CalcNPanel, CALCN_DELTAFREQ, &delta_f_lock_1);
					// convert from kHz to Hz
					delta_f_lock_1 = delta_f_lock_1 * 1000.0;
					Measuring_1 = TRUE;
					break;
				case HG:
					GetCtrlVal(CalcNPanel, CALCN_INTEGRATIONTIME, &DeltaT_2);
					GetCtrlVal(CalcNPanel, CALCN_SLOPETIME, &SlopeTime2);
					GetCtrlVal(CalcNPanel, CALCN_DELTAFREQ, &delta_f_lock_2);
					// convert from kHz to Hz
					delta_f_lock_2 = delta_f_lock_2 * 1000.0;
					Measuring_2 = TRUE;
					break;
				case SR:
					GetCtrlVal(CalcNPanel, CALCN_INTEGRATIONTIME, &DeltaT_3);
					GetCtrlVal(CalcNPanel, CALCN_SLOPETIME, &SlopeTime3);
					GetCtrlVal(CalcNPanel, CALCN_DELTAFREQ, &delta_f_lock_3);
					// convert from kHz to Hz
					delta_f_lock_3 = delta_f_lock_3 * 1000.0;
					Measuring_3 = TRUE;
					break;
			}
			break;
	}
	return 0;
}

int CVICALLBACK CB_OnNStop (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	int measuring;
	
	switch (event) {
		case EVENT_COMMIT:
			HidePanel(CalcNPanel);
			GetPanelAttribute(panel, ATTR_CALLBACK_DATA, &measuring);
			switch (measuring) {
				case LO:
					Measuring_1 = FALSE;
					DDS4xAD9912_RampFrequency(&DDS4xAD9912, 1, f0_DDS1, FREP_STEP_SIZE);
					DDS4xAD9912_SetFrequency(&DDS4xAD9912, 2, f0_DDS2);
					break;
				case HG:
					Measuring_2 = FALSE;
					DDS4xAD9912_RampFrequency(&DDS4xAD9912, 1, f0_DDS1, FREP_STEP_SIZE);
					DDS4xAD9912_SetFrequency(&DDS4xAD9912, 2, f0_DDS2);
					DDS4xAD9912_SetFrequency(&DDS4xAD9912, 3, f0_DDS3);
					break;
				case SR:
					Measuring_3 = FALSE;
					DDS4xAD9912_RampFrequency(&DDS4xAD9912, 1, f0_DDS1, FREP_STEP_SIZE);
					DDS4xAD9912_SetFrequency(&DDS4xAD9912, 2, f0_DDS2);
					DDS4xAD9912_SetFrequency(&DDS4xAD9912, 3, f0_DDS3);
					break;
			}
			
			// update DDS frequencies display
			SetCtrlVal(MainPanel, PANEL_DDS1, DDS4xAD9912.frequency[0]);
			SetCtrlVal(MainPanel, PANEL_DDS2, DDS4xAD9912.frequency[1]);
			SetCtrlVal(MainPanel, PANEL_DDS3, DDS4xAD9912.frequency[2]);
			SetCtrlVal(MainPanel, PANEL_DDS4, DDS4xAD9912.frequency[3]);
			
			break;
	}
	return 0;
}

int  CVICALLBACK CB_OnFindSign (int panel, int control, int event, 
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			switch (control)
			{
				case PANEL_FINDSIGN1:
					tbegin1 = utc;
					Frepbefore1 = Math1;
					GetCtrlVal(MainPanel, PANEL_DDS1, &Frequency1) ;
					SetCtrlVal(MainPanel, PANEL_DDS1, Frequency1+Frequencystep1) ;  
					DDS4xAD9912_SetFrequency(&DDS4xAD9912, 1, Frequency1+Frequencystep1);
					Getsign1 = TRUE;
					break;
				case PANEL_FINDSIGN2:
					tbegin2 = utc;
					Frepbefore2 = Math1;
					Ch2before = Ch2;
					GetCtrlVal(MainPanel, PANEL_DDS1, &Frequency2) ;
					SetCtrlVal(MainPanel, PANEL_DDS1, Frequency2+Frequencystep2) ;
					DDS4xAD9912_SetFrequency(&DDS4xAD9912, 1, Frequency2+Frequencystep2);
					Getsign2 = TRUE;
					break;
				case PANEL_FINDSIGN3:
					tbegin3 = utc;
					Frepbefore3 = Math1;
					GetCtrlVal(MainPanel, PANEL_DDS4, &Frequency3) ;
					SetCtrlVal(MainPanel, PANEL_DDS4, Frequency3+Frequencystep3) ; 
					DDS4xAD9912_SetFrequency(&DDS4xAD9912, 4, Frequency3+Frequencystep3);
					Getsign3 = TRUE;
					break;
			}
			break;
	}
	return 0;
}

int  CVICALLBACK CB_OnFind275K (int panel, int control, int event, 
		 void *callbackData, int eventData1, int eventData2)
{
	double frequency;
	
	switch (event)
	{
		case EVENT_COMMIT:
			switch (control)
			{
				case PANEL_FIND275K2:
					frequency = DDS4xAD9912_GetFrequency(&DDS4xAD9912, 2) + 275000 - Ch4;
					DDS4xAD9912_SetFrequency(&DDS4xAD9912, 2, frequency);
					SetCtrlVal(MainPanel, PANEL_DDS2, DDS4xAD9912.frequency[1]);
					break;
				case PANEL_FIND10K3:
					frequency = DDS4xAD9912_GetFrequency(&DDS4xAD9912, 3) + 10000 - Ch2;
					DDS4xAD9912_SetFrequency(&DDS4xAD9912, 3, frequency);
					SetCtrlVal(MainPanel, PANEL_DDS3, DDS4xAD9912.frequency[2]);
					break;
			}
			break;
	}
	return 0;
}

int  CVICALLBACK CB_OnChangeNdiv (int panel, int control, int event, 
		 void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(MainPanel, PANEL_CHANGENDIV, &Ndiv);
			f0_DDS1 = 880000000.0 / Ndiv;
			DDS4xAD9912_SetFrequency(&DDS4xAD9912, 1, f0_DDS1);
			SetCtrlVal(MainPanel, PANEL_DDS1, DDS4xAD9912.frequency[0]);
			break;
	}
	return 0;
}

int  CVICALLBACK CB_MeasureSlope (int panel, int control, int event, 
		 void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(panel, control, &dedrift.enabled);
			if (dedrift.enabled) {
				dedrift.t0 = utc;
				stat_zero(&freq);
				logmsg("dedrift start");
			} else {
				if (! dedrift.keep_slope) {
					dedrift.applied = 0.0;
					DDSFox_SetSweepRate(&DDS1xAD9956, dedrift.applied);
					SetCtrlVal(MainPanel, PANEL_SLOPE_APPLIED, dedrift.applied);
				}
				if (! dedrift.keep_freq) {
					DDSFox_Set(&DDS1xAD9956, dedrift.freq0, dedrift.applied);
				}
				stat_zero(&freq);
				SetCtrlVal(panel, PANEL_SLOPE_MEASURED, freq.slope);
				logmsg("dedrift stop");
			}
			break;
	}
	return 0;
}

int  CVICALLBACK CB_OnResetSlope (int panel, int control, int event, 
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			dedrift.applied = 0.0; 
			SetCtrlVal(panel, PANEL_SLOPE_APPLIED, dedrift.applied);
			DDSFox_Set(&DDS1xAD9956, dedrift.freq0, dedrift.applied);
			logmsg("dedrift reset");
			break;
	}
	return 0;
}
  
int  CVICALLBACK CB_ChangeSlopeTime (int panel, int control, int event, 
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(MainPanel, PANEL_SLOPETIME, &dedrift.interval); 
			break;
	}
	return 0;
}

int  CVICALLBACK CB_OnCROX (int panel, int control, int event, 
		 void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			switch (control) {
				case PANEL_CHECKBOX_KEEP:
					// keep current dedrifting frequency when dedrifting is disabled
				   	GetCtrlVal(MainPanel, PANEL_CHECKBOX_KEEP, &dedrift.keep_freq);
					break;
				case PANEL_CHECKBOX_KEEPSLOPE:
					// keep current dedrifting slope when dedrifting is disabled
				   	GetCtrlVal(MainPanel, PANEL_CHECKBOX_KEEPSLOPE, &dedrift.keep_slope);
					break;
			}
 			break;
	}
	return 0;
}

int  CVICALLBACK CB_RecenterEnable (int panel, int control, int event, 
		 void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(panel, control, &recenter.enabled);
			recenter.t0 = utc;
			rollmean_zero(&rollmean_ch2);
			rollmean_zero(&rollmean_ch3);
			rollmean_zero(&rollmean_ch4);
 			break;
	}
	return 0;
}

int  CVICALLBACK CB_OnStopSlopeCancellingOnUnlocked (int panel, int control, int event, 
		 void *callbackData, int eventData1, int eventData2)
{
	int value;
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(MainPanel, PANEL_CHECKBOX_STOPIFAUTODE, &value);
			dedrift.threshold = value ? 100.0 : 0.0;
			break;
	}
	return 0;
}

int CVICALLBACK CB_OnSlopeReference (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(MainPanel, PANEL_SLOPE_REFERENCE, &dedrift.reference);
			break;
	}
	return 0;
}

int CVICALLBACK CB_OnEstimateN (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	int visible;
	
	switch (event)
	{
		case EVENT_COMMIT:
			// reset N estimate
			SetCtrlVal(EstimateNPanel, ESTIMATEN_N, 0.0);
			// set current frep
			SetCtrlVal(EstimateNPanel, ESTIMATEN_FREP, 250e6 + Math1);
			switch (control) {
				case PANEL_ESTIMATE_N2:
					SetCtrlVal(EstimateNPanel, ESTIMATEN_WAVELENGTH, HG_WAVELENGTH);
					SetPanelAttribute(EstimateNPanel, ATTR_TITLE, "Estimate N_Hg");
					SetPanelAttribute(EstimateNPanel, ATTR_CALLBACK_DATA, HG);
					break;
				case PANEL_ESTIMATE_N3:
					SetCtrlVal(EstimateNPanel, ESTIMATEN_WAVELENGTH, SR_WAVELENGTH);
					SetPanelAttribute(EstimateNPanel, ATTR_TITLE, "Estimate N_Sr");
					SetPanelAttribute(EstimateNPanel, ATTR_CALLBACK_DATA, SR);
					break;
			}
			GetPanelAttribute(EstimateNPanel, ATTR_VISIBLE , &visible);
			if (! visible)
				DisplayPanel(EstimateNPanel);
			break;
	}
	return 0;
}			

int CVICALLBACK CB_OnNEstimateCancel (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	int visible;
	
	switch (event)
	{
		case EVENT_COMMIT:
			GetPanelAttribute(panel, ATTR_VISIBLE, &visible);
			if (visible)
				HidePanel(panel);
			break;
	}
	return 0;
}

int CVICALLBACK CB_OnNEstimateSet (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	int estimate;
	
	switch (event)
	{
		case EVENT_COMMIT:
			GetPanelAttribute(panel, ATTR_CALLBACK_DATA, &estimate);
			switch (estimate) {
				case HG:
					GetCtrlVal(panel, ESTIMATEN_N, &N2);
					SetCtrlVal(MainPanel, PANEL_N3CHOICE, N2);
					break;
				case SR:
					GetCtrlVal(panel, ESTIMATEN_N, &N3);
					SetCtrlVal(MainPanel, PANEL_N3CHOICE, N3);
					break;
			}
			HidePanel(panel);
			break;
	}
	return 0;
}

int CVICALLBACK CB_OnNEstimate (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	double frep, wl, N;
	
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(panel, ESTIMATEN_FREP, &frep);
			GetCtrlVal(panel, ESTIMATEN_WAVELENGTH, &wl);
			N = round(SPEED_OF_LIGHT / wl / 1e-9 / frep);
			SetCtrlVal(panel, ESTIMATEN_N, N);
			break;
	}
	return 0;
}

int CVICALLBACK CB_SetSlope (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(panel, control, &dedrift.applied);
			DDSFox_SetSweepRate(&DDS1xAD9956, dedrift.applied);
			break;
	}
	return 0;
}

int CVICALLBACK CB_InvertSlopeSign (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(panel, control, &dedrift.invert);
			break;
	}
	return 0;
}

int CVICALLBACK CB_ResetDedriftDDS (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			// stop slope measurement and reset slope 
			dedrift.enabled = FALSE;
			SetCtrlVal(panel, PANEL_MEASURE_SLOPE, 0);
			dedrift.applied = 0.0;
			SetCtrlVal(panel, PANEL_SLOPE_APPLIED, dedrift.applied);
			// reset DDS
			DDSFox_Reset(&DDS1xAD9956);
			DDSFox_SetProfile(&DDS1xAD9956);
			DDSFox_SetDiv(&DDS1xAD9956, 1);
			DDSFox_Set(&DDS1xAD9956, dedrift.freq0, 0.0);
			break;
	}
	return 0;
}

int CVICALLBACK CB_ShowLog (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	int visible;
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(panel, PANEL_SHOWLOG, &visible);
			logger_panel_visible(visible);
			break;
	}
	return 0;
}

int CVICALLBACK CB_OnLoggingPanelEvent(int panel, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_CLOSE:
			logger_panel_visible(0);
			SetCtrlVal(MainPanel, PANEL_SHOWLOG, 0);
			break;
	}
	return 0;
}

int CVICALLBACK CB_Sign3 (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(panel, control, &Sign3);
			break;
	}
	return 0;
}

int CVICALLBACK CB_SlopeX2 (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(panel, control, &dedrift.doubleslope);
			break;
	}
	return 0;
}

int CVICALLBACK CB_SaveData (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			for (struct datafile *d = datafiles; d->data; d++) {
				if (d->control == control)
					GetCtrlVal(panel, control, &(d->write));
			}
			break;
	}
	return 0;
}


int CVICALLBACK CB_RecenterInterval (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(panel, control, &recenter.interval);
			break;
	}
	return 0;
}

int CVICALLBACK CB_RecenterChannel (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			switch (control)
			{
				case PANEL_RECENTER_LO:
					GetCtrlVal(panel, control, &recenter.lo);
					break;
				case PANEL_RECENTER_HG:
					GetCtrlVal(panel, control, &recenter.hg);
					break;
				case PANEL_RECENTER_SR:
					GetCtrlVal(panel, control, &recenter.sr);
					break;
			}
			break;
	}
	return 0;
}

int CVICALLBACK CB_SrDatalogger (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(panel, control, &datalogger.enabled);
			break;
	}
	return 0;
}
int CVICALLBACK CB_DedriftDDSFreq (int panel, int control, int event,
		void *callbackData, int eventData1, int eventData2)
{
	switch (event)
	{
		case EVENT_COMMIT:
			GetCtrlVal(panel, control, &dedrift.freq0);
			DDSFox_Set(&DDS1xAD9956, dedrift.freq0, dedrift.applied);
			break;
	}
	return 0;
}