969 lines
42 KiB
C++
969 lines
42 KiB
C++
#include <cmath>
|
|
#include <stack>
|
|
#include <cerrno>
|
|
#include <cstring>
|
|
#include "pthread.h"
|
|
#include "core/core.h"
|
|
#include "core/stopwatch.h"
|
|
#include "core/result.h"
|
|
#include "core/xml_states.h"
|
|
#include "Spectrum-MI40xxSeries.h"
|
|
|
|
#include <sstream>
|
|
|
|
#ifndef SPC_DEBUG
|
|
# define SPC_DEBUG 0
|
|
#endif
|
|
#ifndef SIZETPRINTFLETTER
|
|
# ifndef __LP64__
|
|
# define SIZETPRINTFLETTER "u"
|
|
# else
|
|
# define SIZETPRINTFLETTER "lu"
|
|
# endif
|
|
#endif
|
|
|
|
void SpectrumMI40xxSeries::Configuration::print(FILE* f) {
|
|
fprintf(f, "Spectrum MI40xx Series Configuration:\n");
|
|
fprintf(f, " Sample Frequency %g Hz, Acquisition Timeout %g\n",samplefreq, timeout);
|
|
if (data_structure!=NULL)
|
|
data_structure->print(f,2);
|
|
else
|
|
fprintf(f, " No Data to acquire\n");
|
|
|
|
for (int i=0; i < lSetChannels; i++) {
|
|
fprintf(f, " Channel %i: Impedance %f Ohm, Sensitivity %f V, Coupling %s\n", i, impedance[i], sensitivity[i], (coupling==0)?"DC":"AC");
|
|
}
|
|
}
|
|
|
|
|
|
SpectrumMI40xxSeries::SpectrumMI40xxSeries(const ttlout& t_line, float impedance, int ext_reference_clock) {
|
|
// print neat string to inform the user
|
|
fprintf(stderr, "Initializing ADC card\n");
|
|
|
|
// to be configured
|
|
device_id=0;
|
|
trigger_line=t_line;
|
|
|
|
default_settings.qwSetChEnableMap = channel_array(ADC_MI_DEFAULT_CHANNELS);
|
|
default_settings.lSetChannels = default_settings.qwSetChEnableMap.count();
|
|
default_settings.impedance = new double[default_settings.lSetChannels];
|
|
default_settings.sensitivity = new double[default_settings.lSetChannels];
|
|
default_settings.offset = new int[default_settings.lSetChannels];
|
|
|
|
for(int i = 0; i < default_settings.lSetChannels; i++) {
|
|
default_settings.impedance[i] = ADC_MI_DEFAULT_IMPEDANCE;
|
|
|
|
default_settings.sensitivity[i] = ADC_MI_DEFAULT_SENSITIVITY; // Set sensitivity in Volts to the default (maximum) value
|
|
default_settings.offset[i] = ADC_MI_DEFAULT_OFFSET; // Set offsets in % of sensitivity to default (0)
|
|
}
|
|
|
|
|
|
default_settings.ext_reference_clock=ext_reference_clock; // Hz
|
|
effective_settings=NULL;
|
|
|
|
# if defined __linux__
|
|
// ----- open driver -----
|
|
deviceno = open ("/dev/spc0", O_RDWR);
|
|
if (deviceno == -1) {
|
|
std::string error_message="could not open /dev/spc0";
|
|
if (errno==0) throw SpectrumMI40xxSeries_error( error_message+" (unknown error)");
|
|
else throw SpectrumMI40xxSeries_error( error_message+" ("+strerror(errno)+")");
|
|
}
|
|
# endif
|
|
# if defined __CYGWIN__
|
|
// open library dll
|
|
spectrum_driver_dll = LoadLibrary("spectrum.dll");
|
|
if (spectrum_driver_dll==NULL)
|
|
throw SpectrumMI40xxSeries_error("could not open driver dll");
|
|
deviceno=0;
|
|
// load driver functions
|
|
# define load_spectrum_function(name) name = ( name##_type)GetProcAddress(spectrum_driver_dll, #name); \
|
|
if (name == NULL) { FreeLibrary(spectrum_driver_dll); \
|
|
throw SpectrumMI40xxSeries_error("failed to load function" #name "" ); \
|
|
}
|
|
load_spectrum_function(SpcInitPCIBoards);
|
|
load_spectrum_function(SpcGetParam);
|
|
load_spectrum_function(SpcSetParam);
|
|
load_spectrum_function(SpcGetData);
|
|
|
|
// find spectrum boards
|
|
boardcount=0;
|
|
SpcInitPCIBoards(&boardcount,&PCIVersion);
|
|
if (boardcount<=0) {
|
|
FreeLibrary(spectrum_driver_dll);
|
|
throw SpectrumMI40xxSeries_error("no spectrum boards found");
|
|
}
|
|
|
|
# endif
|
|
/* calculate the sample number that can be saved without fifo */
|
|
int memory_size=0;
|
|
SpcGetParam(deviceno,SPC_PCIMEMSIZE,&memory_size);
|
|
fifo_minimal_size=memory_size/(sizeof(short int)*2);
|
|
|
|
/* get the device type */
|
|
SpcGetParam(deviceno, SPC_PCITYP, &cardType);
|
|
if (cardType != TYP_MI4020 && cardType != TYP_MI4021 && cardType != TYP_MI4022 && cardType != TYP_MI4030 && cardType != TYP_MI4031 && cardType != TYP_MI4032) {
|
|
throw SpectrumMI40xxSeries_error("Board type not supported");
|
|
}
|
|
|
|
/* make a check, whether spectrum board is running */
|
|
int actual_status=0;
|
|
SpcGetParam(deviceno, SPC_STATUS, &actual_status);
|
|
if (actual_status!=SPC_READY) {
|
|
fprintf(stderr, "Warning: Spectrum board was is running before reset.\n");
|
|
}
|
|
#if SPC_DEBUG
|
|
fprintf(stderr,"External reference clock: %i\n",default_settings.ext_reference_clock);
|
|
#endif
|
|
int ErrorOccurred=1;
|
|
SpcSetParam(deviceno, SPC_COMMAND, SPC_RESET)==ERR_OK && // reset device first
|
|
SpcSetParam (deviceno, SPC_GATE, 1)==ERR_OK && // Gated Triggering
|
|
SpcSetParam (deviceno, SPC_PLL_ENABLE, 1)==ERR_OK && // Internal PLL enabled for clock
|
|
SpcSetParam (deviceno, SPC_CLOCKOUT, 0)==ERR_OK && // No clock output
|
|
SpcSetParam (deviceno, SPC_REFERENCECLOCK, default_settings.ext_reference_clock)==ERR_OK && // external reference clock
|
|
SpcSetParam (deviceno, SPC_EXTERNALCLOCK, 0)==ERR_OK && // but no external sample clock
|
|
SpcSetParam (deviceno, SPC_CLOCK50OHM, 0)==ERR_OK && // clock input NOT with 50Ohm impedance
|
|
SpcSetParam (deviceno, SPC_TRIGGERMODE, TM_TTLPOS)==ERR_OK && // ttl trigger is used
|
|
SpcSetParam (deviceno, SPC_TRIGGEROUT, 0)==ERR_OK && // No trigger output
|
|
SpcSetParam (deviceno, SPC_TRIGGER50OHM, 0)==ERR_OK && // Trigger to 1MOhm, necessary for steep slopes
|
|
(ErrorOccurred=0); // done, really no error occurred
|
|
|
|
// ----- driver error: request error and end program -----
|
|
if (ErrorOccurred!=0) {
|
|
int32 lErrorCode, lErrorReg, lErrorValue;
|
|
SpcGetParam (deviceno, SPC_LASTERRORCODE, &lErrorCode);
|
|
SpcGetParam (deviceno, SPC_LASTERRORREG, &lErrorReg);
|
|
SpcGetParam (deviceno, SPC_LASTERRORVALUE, &lErrorValue);
|
|
char error_message[256];
|
|
snprintf(error_message, sizeof(error_message),"Configuration error %d in register %d at value %d", lErrorCode, lErrorReg, lErrorValue);
|
|
throw SpectrumMI40xxSeries_error(error_message);
|
|
}
|
|
|
|
/* print lines with useful information: */
|
|
fprintf(stderr, "Spectrum MI40xx series board with %d byte memory\n", memory_size);
|
|
fprintf(stderr,
|
|
" impedance set to %g Ohm\n expecting trigger on id=%d ttls=0x%lx\n external clock frequency set to %g Hz\n",
|
|
impedance,
|
|
t_line.id,
|
|
t_line.ttls.to_ulong(),
|
|
(float)ext_reference_clock );
|
|
}
|
|
|
|
void SpectrumMI40xxSeries::sample_after_external_trigger(double rate, size_t samples, double sensitivity, size_t resolution) {
|
|
throw SpectrumMI40xxSeries_error("SpectrumMI40xxSeries::sample_after_external_trigger is not jet implemented");
|
|
}
|
|
|
|
|
|
|
|
void SpectrumMI40xxSeries::collect_config_recursive(state_sequent& exp, SpectrumMI40xxSeries::Configuration& settings) {
|
|
|
|
/* start with dummy node */
|
|
DataManagementNode* new_branch=new DataManagementNode(NULL);
|
|
DataManagementNode* where_to_append=new_branch;
|
|
double parent_timeout=settings.timeout;
|
|
settings.timeout=0.0;
|
|
|
|
/* loop over all states and sequences */
|
|
for (state_sequent::iterator i=exp.begin(); i!=exp.end(); ++i) {
|
|
|
|
state* a_state=dynamic_cast<state*>(*i);
|
|
if (a_state==NULL)
|
|
throw SpectrumMI40xxSeries_error("state_atom not expected in sate_sequent");
|
|
if (dynamic_cast<state_parallel*>(*i)!=NULL)
|
|
throw SpectrumMI40xxSeries_error("state_parallel handling is not jet implemented");
|
|
state_sequent* a_sequence=dynamic_cast<state_sequent*>(a_state);
|
|
if (a_sequence!=NULL) {
|
|
// found a sequence
|
|
DataManagementNode* tmp_structure=settings.data_structure;
|
|
settings.data_structure=where_to_append;
|
|
collect_config_recursive(*a_sequence, settings);
|
|
settings.data_structure=tmp_structure;
|
|
//} /* end working on sequence */
|
|
//else {
|
|
// found a state, not a sequence
|
|
settings.timeout+=a_state->length;
|
|
#if SPC_DEBUG
|
|
fprintf(stderr,"SETTINGS %e %e\n",settings.timeout,a_state->length);
|
|
#endif
|
|
// collect analogin sections in state
|
|
std::list<analogin*> inputs;
|
|
/* loop over all device definitions in a state */
|
|
state::iterator j=a_state->begin();
|
|
while (j!=a_state->end()) {
|
|
analogin* input=dynamic_cast<analogin*>(*j);
|
|
if (input!=NULL && input->id==device_id) {
|
|
/* collect appropiate analogin sections, forget others */
|
|
if (input->samples<=0 || input->sample_frequency<=0)
|
|
delete input;
|
|
else
|
|
inputs.push_back(input);
|
|
j=a_state->erase(j);
|
|
} else
|
|
++j;
|
|
}
|
|
if (!inputs.empty()) {
|
|
/* evaluate the found analogin definitions */
|
|
if (inputs.size()>1) {
|
|
while (!inputs.empty()) {
|
|
delete inputs.front();
|
|
inputs.pop_front();
|
|
}
|
|
throw ADC_exception("can not handle more than one analogin section per state");
|
|
}
|
|
/* save sampling frequency */
|
|
if (settings.samplefreq<=0)
|
|
settings.samplefreq=inputs.front()->sample_frequency;
|
|
else if (settings.samplefreq != inputs.front()->sample_frequency) {
|
|
while (!inputs.empty()) {
|
|
delete inputs.front();
|
|
inputs.pop_front();
|
|
}
|
|
throw ADC_exception("Sorry, but gated sampling requires same sampling frequency in all analogin sections");
|
|
}
|
|
|
|
/* save channel mask and number of channels */
|
|
if (settings.lSetChannels > 0 && settings.qwSetChEnableMap.to_ulong() > 0) {
|
|
if (settings.qwSetChEnableMap != inputs.front()->channels) {
|
|
fprintf(stderr, "Warning! different channels enabled in input %lu and in config %lu, setting to default \n",
|
|
settings.qwSetChEnableMap.to_ulong(),
|
|
inputs.front()->channels.to_ulong());
|
|
settings.qwSetChEnableMap = channel_array(ADC_MI_DEFAULT_CHANNELS);
|
|
settings.lSetChannels = settings.qwSetChEnableMap.count();
|
|
}
|
|
} else {
|
|
settings.qwSetChEnableMap = inputs.front()->channels;
|
|
settings.lSetChannels = inputs.front()->nchannels;
|
|
}
|
|
|
|
/* save sensitivity */
|
|
if (settings.sensitivity != NULL) { // if sensitivity is set, make sure it's valid (i.e. the same for all inputs)
|
|
for (int k = 0; k < inputs.front()->nchannels; k++) {
|
|
if (settings.sensitivity[k] != inputs.front()->sensitivity[k]) {
|
|
fprintf(stderr, "Warning! different sensitivity specified (here %f, elsewhere %f), choosing higher voltage\n",
|
|
settings.sensitivity[k],
|
|
inputs.front()->sensitivity[k]);
|
|
if (settings.sensitivity[k] < inputs.front()->sensitivity[k]) {
|
|
settings.sensitivity[k] = inputs.front()->sensitivity[k];
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
settings.sensitivity = inputs.front()->sensitivity;
|
|
}
|
|
// check if sensitivity is valid
|
|
for (int k = 0; k < inputs.front()->nchannels; k++) {
|
|
bool sensAllowed = false;
|
|
for (int l = 0; l < ADC_MI_ALLOWED_SENSITIVITY_LENGTH; l++) {
|
|
if (settings.sensitivity[k] == ADC_MI_ALLOWED_SENSITIVITY[l] ) {
|
|
sensAllowed = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!sensAllowed) {
|
|
fprintf(stderr, "Warning! Illegal sensitivity specified for channel %i: %f", k, inputs.front()->sensitivity[k]);
|
|
settings.sensitivity[k] = ADC_MI_DEFAULT_SENSITIVITY;
|
|
}
|
|
}
|
|
|
|
/* save impedance */
|
|
if (settings.impedance != NULL) {
|
|
for (int k = 0; k < inputs.front()->nchannels; k++) {
|
|
if (settings.impedance[k] != inputs.front()->impedance[k]) {
|
|
fprintf(stderr, "Warning! different impedance specified (here %f, elsewhere %f), setting to default\n",
|
|
settings.impedance[k],
|
|
inputs.front()->impedance[k]);
|
|
settings.impedance[k] = ADC_MI_DEFAULT_IMPEDANCE;
|
|
}
|
|
if (settings.impedance[k] != ADC_MI_DEFAULT_IMPEDANCE && settings.impedance[k] != ADC_MI_ALLOWED_IMPEDANCE) {
|
|
fprintf(stderr, "Warning! Illegal impedance specified for channel %i: %f",k, inputs.front()->impedance[k]);
|
|
settings.offset[k] = 0;
|
|
}
|
|
}
|
|
} else {
|
|
settings.impedance = inputs.front()->impedance;
|
|
}
|
|
|
|
/* save offsets */
|
|
if (settings.offset != NULL) {
|
|
for (int k = 0; k < inputs.front()->nchannels; k++) {
|
|
if (settings.offset[k] != inputs.front()->offset[k]) {
|
|
fprintf(stderr, "Warning! different impedance specified (here %i, elsewhere %i), setting to default\n",
|
|
settings.offset[k],
|
|
inputs.front()->offset[k]);
|
|
settings.offset[k] = ADC_MI_DEFAULT_OFFSET;
|
|
}
|
|
if (inputs.front()->offset[k] > 100 || inputs.front()->offset[k] < -100) {
|
|
fprintf(stderr, "Warning! Illegal offset specified for channel %i: %i", k, inputs.front()->offset[k]);
|
|
settings.offset[k] = 0;
|
|
}
|
|
}
|
|
} else {
|
|
settings.offset = inputs.front()->offset;
|
|
}
|
|
|
|
if (inputs.front()->samples%4 != 0) {
|
|
throw ADC_exception("Number of samples must be a multiple of four");
|
|
}
|
|
|
|
// calculate the time required
|
|
double delayed_gating_time=0.0;
|
|
// the gating time has an offset, which was found to be 1.5 dwelltimes for <2.5MHz and 4.5 dwelltimes for >=2.5MHz
|
|
double gating_time;
|
|
|
|
#if SPC_DEBUG
|
|
fprintf(stderr, "Channels: %lu\n", settings.qwSetChEnableMap.to_ulong());
|
|
#endif
|
|
|
|
/* check if channel mask is legal for the card */
|
|
if (this->IsChannelMaskLegal(inputs.front()->channels.to_ulong())) {
|
|
settings.qwSetChEnableMap = inputs.front()->channels;
|
|
settings.lSetChannels = inputs.front()->nchannels;
|
|
} else {
|
|
throw SpectrumMI40xxSeries_error("Selected channels combination not allowed for this card type");
|
|
}
|
|
/* apply proper timing */
|
|
if ( settings.qwSetChEnableMap.to_ulong()==(CHANNEL0|CHANNEL1) || settings.qwSetChEnableMap.to_ulong()==(CHANNEL0|CHANNEL1|CHANNEL2|CHANNEL3) ) {
|
|
#if SPC_DEBUG
|
|
fprintf(stderr, "Default Channels\n");
|
|
#endif
|
|
if (settings.samplefreq<2.5e6) {
|
|
// if sampling rate is <2.5MHz, there is another data handling mode,
|
|
// see MI4021 manual page 79: "Accquisition Delay: -6 Samples"
|
|
// it might be necessary to add 0.1 dwelltime to shift the sampling start a little more...
|
|
|
|
// edit by Stefan Reutter @2013-06, it seems that the MI4021 cards actually have a second
|
|
// threshold at 500 kHz that is not mentioned in the manual.
|
|
// this can be tested by disabling the if below and switching over the threshold
|
|
gating_time=(inputs.front()->samples)/settings.samplefreq;
|
|
if (settings.samplefreq >= 5e5) {
|
|
delayed_gating_time=ceil(1e8*6.0/settings.samplefreq)/1e8;
|
|
} else {
|
|
delayed_gating_time=0.0;
|
|
}
|
|
} else {
|
|
gating_time=(inputs.front()->samples+3)/settings.samplefreq;
|
|
delayed_gating_time=0.0;
|
|
}
|
|
}
|
|
// disabled the more exotic channel setup as it is untested with the updated timings and probably not used anyways
|
|
/* else if (settings.qwSetChEnableMap.to_ulong()==CHANNEL0 || settings.qwSetChEnableMap.to_ulong()==(CHANNEL0|CHANNEL2) ) {
|
|
#if SPC_DEBUG
|
|
fprintf(stderr, "Weird Channels\n");
|
|
#endif
|
|
if (settings.samplefreq<5e6) {
|
|
gating_time=(inputs.front()->samples+1.5)/settings.samplefreq;
|
|
delayed_gating_time=ceil(1e8*6.0/settings.samplefreq)/1e8;
|
|
} else {
|
|
gating_time=(inputs.front()->samples+4.5)/settings.samplefreq;
|
|
delayed_gating_time=-ceil(1e8*7.0/settings.samplefreq)/1e8;
|
|
|
|
} */else {
|
|
throw SpectrumMI40xxSeries_error("Selected channels combination not allowed");
|
|
}
|
|
|
|
gating_time=ceil(1e8*gating_time)/1e8;
|
|
double time_required=delayed_gating_time+gating_time;
|
|
// check time requirements
|
|
if (a_state->length < gating_time) {
|
|
#if SPC_DEBUG
|
|
fprintf(stderr, "state is shorter than acquisition time %e time required, %e state time\n", gating_time, a_state->length);
|
|
#endif
|
|
// update the state length if it's shorter than the gate. this is usually due to rounding to 10 ns for the pulseblaster
|
|
if (ceil(1e8*(a_state->length + 3.0f/settings.samplefreq))/1e8 < time_required) { //
|
|
std::stringstream parameter_inf;
|
|
parameter_inf << "state is (" << inputs.front()->samples << " samples, " << settings.samplefreq << " sampling rate, " << time_required << " time required, " << a_state->length << " state time)" << std::endl;
|
|
throw ADC_exception(parameter_inf.str());
|
|
} else {
|
|
a_state->length = time_required;
|
|
}
|
|
}
|
|
// if necessary, add the gating pulse delay...
|
|
if (delayed_gating_time>0.0) {
|
|
state* delayed_gating_state=new state(*a_state);
|
|
delayed_gating_state->length=delayed_gating_time;
|
|
// insert before me
|
|
exp.insert(i,(state_atom*)delayed_gating_state);
|
|
} else if (delayed_gating_time < 0.0) {
|
|
/*
|
|
For +samples delays
|
|
1. get the previous state
|
|
2. if the length is not long enough (6*dw) add the state before
|
|
3. split the state(s) so that we have the gating on 6*dw before the actual recording
|
|
*/
|
|
double rest_length = delayed_gating_time;
|
|
state_sequent::iterator i_prev;
|
|
state* prev_state;
|
|
i_prev = i;
|
|
do {
|
|
i_prev--;
|
|
prev_state = dynamic_cast<state*>(*(i_prev));
|
|
rest_length -= prev_state->length;
|
|
fprintf(stderr, "DW rest_length: %g\n", rest_length);
|
|
fprintf(stderr, "DW state_length: %g\n", prev_state->length);
|
|
if (rest_length >= 0)
|
|
prev_state->push_back(trigger_line.copy_new()); // add trigger to this state
|
|
else { // split final state
|
|
state* prev_state_1 = prev_state->copy_flat(); //create copy of current state
|
|
prev_state_1->length += rest_length; // adjust 1st part length
|
|
exp.insert(i_prev,(state_atom*) prev_state_1); // insert AFTER prev_state
|
|
prev_state->length = -rest_length; // adjust 2nd part length
|
|
prev_state->push_back(trigger_line.copy_new()); // add trigger to 2nd part
|
|
break;
|
|
}
|
|
|
|
} while (i_prev!=exp.begin() || rest_length > 0.0);
|
|
}
|
|
# if SPC_DEBUG
|
|
fprintf(stderr, "sequence after pre_trigger correction:\n");
|
|
xml_state_writer().write_states(stderr, exp);
|
|
# endif
|
|
|
|
// adapt the pulse program for gated sampling
|
|
if (a_state->length == gating_time) {
|
|
// state has proper length
|
|
a_state->push_back(trigger_line.copy_new());
|
|
} else {
|
|
# if SPC_DEBUG
|
|
fprintf(stderr, "state too long, length %e, time required %e\n", a_state->length, time_required);
|
|
# endif
|
|
// state is too long...
|
|
// create new one with proper time and gated sampling pulse
|
|
state* gated_sampling_pulse=new state(*a_state);
|
|
gated_sampling_pulse->length=gating_time;
|
|
gated_sampling_pulse->push_back(trigger_line.copy_new());
|
|
// insert gate pulse state before remaining (original) state
|
|
exp.insert(i,(state_atom*)gated_sampling_pulse);
|
|
// shorten this state
|
|
a_state->length-=time_required;
|
|
}
|
|
|
|
# if SPC_DEBUG
|
|
fprintf(stderr, "sequence after correcting trigger state:\n");
|
|
xml_state_writer().write_states(stderr, exp);
|
|
# endif
|
|
|
|
/* save sampleno */
|
|
DataManagementNode* new_one=new DataManagementNode(new_branch);
|
|
new_one->n=inputs.front()->samples;
|
|
new_one->child=NULL;
|
|
new_one->next=where_to_append->next;
|
|
where_to_append->next=new_one;
|
|
where_to_append=new_one;
|
|
while (!inputs.empty()) {delete inputs.front(); inputs.pop_front();}
|
|
} /* !inputs.empty() */
|
|
|
|
|
|
|
|
} /* end working on state */
|
|
|
|
|
|
} /* i */
|
|
|
|
/* something happened? */
|
|
if (new_branch->next!=NULL) {
|
|
/* make dummy node to a loop */
|
|
new_branch->n=exp.repeat;
|
|
new_branch->child=new_branch->next;
|
|
|
|
/* if possible, append it */
|
|
if (settings.data_structure!=NULL) {
|
|
new_branch->parent=settings.data_structure->parent;
|
|
new_branch->next=settings.data_structure->next;
|
|
settings.data_structure->next=new_branch;
|
|
}
|
|
else {
|
|
new_branch->parent=NULL;
|
|
new_branch->next=NULL;
|
|
settings.data_structure=new_branch;
|
|
}
|
|
}
|
|
else
|
|
delete new_branch;
|
|
|
|
settings.timeout*=exp.repeat;
|
|
settings.timeout+=parent_timeout;
|
|
#ifdef SPC_DEBUG
|
|
fprintf(stderr,"setting.timout %g\n",settings.timeout);
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
static void* SpectrumMI40xxSeries_TimeoutThread(void* data) {
|
|
int *ret = new int;
|
|
*ret = ((SpectrumMI40xxSeries*)data)->TimeoutThread();
|
|
return (void*) ret;
|
|
}
|
|
|
|
void SpectrumMI40xxSeries::set_daq(state & exp) {
|
|
|
|
// cleanup?!
|
|
if (!fifobuffers.empty()) {
|
|
fprintf(stderr, "normally there should be a cleanup at the end of acquisition\n");
|
|
for (std::vector<short int*>::iterator i=fifobuffers.begin(); i!=fifobuffers.end(); ++i) free(*i);
|
|
fifobuffers.clear();
|
|
}
|
|
|
|
/* what could be done: PCIBIT_GATE, PCIBIT_MULTI */
|
|
int features;
|
|
SpcGetParam(deviceno,SPC_PCIFEATURES,&features);
|
|
|
|
state_sequent* exp_sequence=dynamic_cast<state_sequent*>(&exp);
|
|
if (exp_sequence==NULL)
|
|
throw ADC_exception("Spectrum-MI40xxSeries::set_daq only working on sequences");
|
|
|
|
# if SPC_DEBUG
|
|
fprintf(stderr, "working on sequence:\n");
|
|
xml_state_writer().write_states(stderr, *exp_sequence);
|
|
# endif
|
|
|
|
/* find out what to do */
|
|
Configuration* conf=new Configuration();
|
|
collect_config_recursive(*exp_sequence, *conf);
|
|
if (conf->samplefreq<=0) conf->samplefreq=default_settings.samplefreq;
|
|
if (conf->impedance == NULL) conf->impedance = default_settings.impedance;
|
|
if (conf->sensitivity == NULL) conf->sensitivity = default_settings.sensitivity;
|
|
if (conf->offset == NULL) conf->offset = default_settings.offset;
|
|
|
|
if (conf->lSetChannels == 0) {
|
|
conf->lSetChannels = default_settings.lSetChannels;
|
|
conf->qwSetChEnableMap = default_settings.qwSetChEnableMap;
|
|
}
|
|
|
|
size_t sampleno=(conf->data_structure==NULL)?0:conf->data_structure->size();
|
|
|
|
/* nothing to do! */
|
|
if (sampleno==0) {
|
|
delete conf;
|
|
effective_settings=NULL;
|
|
return;
|
|
}
|
|
|
|
|
|
if (sampleno<16 || sampleno%16!=0) {
|
|
delete conf;
|
|
throw SpectrumMI40xxSeries_error("Total number of samples must be multiple of 16 and at least 16");
|
|
}
|
|
|
|
effective_settings=conf;
|
|
|
|
#if SPC_DEBUG
|
|
/* settings for this experiment */
|
|
conf->print(stderr);
|
|
#endif
|
|
|
|
/* make a check, whether spectrum board is running */
|
|
int actual_status=0;
|
|
SpcGetParam (deviceno, SPC_STATUS, &actual_status);
|
|
if (actual_status!=SPC_READY) {
|
|
fprintf(stderr, "Warning: Spectrum board was/is running before starting data aquisition.\n");
|
|
SpcSetParam (deviceno, SPC_COMMAND, SPC_STOP); // stop the board
|
|
}
|
|
|
|
/* and the dirty things there...
|
|
----- setup board for recording -----
|
|
*/
|
|
for (unsigned int j=0; j<(unsigned int)effective_settings->lSetChannels; j++) {
|
|
SpcSetParam (deviceno, SPC_AMP0 + 100*j, (int)floor(effective_settings->sensitivity[j]*1000)); // +/- 5V input range
|
|
SpcSetParam (deviceno, SPC_50OHM0 + 100*j, ((effective_settings->impedance[j]==50.0)?1:0)); // 1 = 50 Ohm input impedance, 0 = 1MOhm input impedance
|
|
SpcSetParam (deviceno, SPC_OFFS0 + 100*j, effective_settings->offset[j]); // set offset to zero
|
|
}
|
|
|
|
SpcSetParam (deviceno, SPC_CHENABLE, effective_settings->qwSetChEnableMap.to_ulong()); // Enable channels for recording
|
|
int activated_channels;
|
|
SpcGetParam (deviceno, SPC_CHENABLE, &activated_channels);
|
|
|
|
SpcSetParam (deviceno, SPC_SAMPLERATE, (int)floor(effective_settings->samplefreq)); // Samplerate
|
|
|
|
// the MI4021 card doesn't accept all sampling frequency settings. check if the sampling rate is set correctly. if not, throw an exception
|
|
int setSamplingRate = 0;
|
|
SpcGetParam (deviceno, SPC_SAMPLERATE, &setSamplingRate);
|
|
if (setSamplingRate != (int)floor(effective_settings->samplefreq)) {
|
|
char parameter_info[16];
|
|
snprintf(parameter_info,sizeof(parameter_info), "%d", setSamplingRate);
|
|
throw ADC_exception(std::string("DAC sampling rate not available. Try setting to: ")+parameter_info);
|
|
}
|
|
|
|
// decide for aquisition mode and start it
|
|
int16 nErr=ERR_OK;
|
|
if (sampleno<fifo_minimal_size) {
|
|
|
|
#if SPC_DEBUG
|
|
fprintf(stderr, "expecting %" SIZETPRINTFLETTER " samples in normal mode\n",sampleno);
|
|
#endif
|
|
|
|
SpcSetParam (deviceno, SPC_MEMSIZE, sampleno); // Memory size
|
|
|
|
// ----- start the board -----
|
|
nErr = SpcSetParam (deviceno, SPC_COMMAND, SPC_START); // start the board
|
|
} else {
|
|
|
|
#if SPC_DEBUG
|
|
fprintf(stderr, "expecting %" SIZETPRINTFLETTER " samples in fifo mode\n",sampleno);
|
|
#endif
|
|
// todo: fifo should really write directly to recursive gated sampling structures
|
|
fifo_adc_data=(short int*)malloc(sampleno*sizeof(short int)*2);
|
|
if (fifo_adc_data==NULL) {
|
|
throw SpectrumMI40xxSeries_error("could not allocate adc data memory for fifo recording");
|
|
}
|
|
// ToDo: do some magic calculations
|
|
fifobufferno=16;
|
|
fifobufferlen=1<<20;
|
|
#if SPC_DEBUG
|
|
fprintf(stderr, "configuring for %" SIZETPRINTFLETTER " buffers, each of size %" SIZETPRINTFLETTER "\n", fifobufferno, fifobufferlen);
|
|
#endif
|
|
SpcSetParam(deviceno,SPC_FIFO_BUFFERS,fifobufferno);
|
|
SpcSetParam(deviceno,SPC_FIFO_BUFLEN,fifobufferlen);
|
|
// allocate FIFO buffers
|
|
fifobuffers.resize(fifobufferno,(short int*)NULL);
|
|
for (size_t i=0; i!=fifobufferno; ++i) {
|
|
void* newbuffer;
|
|
#if SPC_DEBUG
|
|
fprintf(stderr, "allocating fifobuffer %" SIZETPRINTFLETTER "...", i);
|
|
#endif
|
|
newbuffer=malloc(fifobufferlen);
|
|
if (newbuffer==NULL) {
|
|
// free the buffers, if there is not enough memory
|
|
for (size_t j=0; j!=i; ++j) free(fifobuffers[j]);
|
|
fifobuffers.clear();
|
|
throw SpectrumMI40xxSeries_error("could not allocate buffers for fifo mode");
|
|
}
|
|
#if SPC_DEBUG
|
|
fprintf(stderr, "and registering with ADC driver....");
|
|
#endif
|
|
fifobuffers[i]=(short int*)newbuffer;
|
|
// todo: check for errors
|
|
#ifndef _LINUX64
|
|
SpcSetParam (deviceno, SPC_FIFO_BUFADR0+i, (int32) newbuffer);
|
|
#else
|
|
// 64 bit Linux needs SetAdr function, 32 bit linux can also use this if driver build > 1093
|
|
SpcSetAdr (deviceno, SPC_FIFO_BUFADR0+i, newbuffer);
|
|
#endif
|
|
#if SPC_DEBUG
|
|
fprintf(stderr, "success.\n");
|
|
#endif
|
|
}
|
|
#if SPC_DEBUG
|
|
fprintf(stderr, "starting fifo thread\n");
|
|
#endif
|
|
// to do: append a new state to overcome problems with fifo...
|
|
// need another thread, that collects the data
|
|
pthread_attr_init(&timeout_pt_attrs);
|
|
pthread_create(&timeout_pt, &timeout_pt_attrs, SpectrumMI40xxSeries_TimeoutThread,(void*)this);
|
|
}
|
|
// ----- driver error: request error and end program -----
|
|
if (nErr != ERR_OK) {
|
|
if (effective_settings!=NULL) delete effective_settings;
|
|
effective_settings=NULL;
|
|
for (std::vector<short int*>::iterator i=fifobuffers.begin(); i!=fifobuffers.end(); ++i) free(*i);
|
|
fifobuffers.clear();
|
|
// create an error message
|
|
int32 lErrorCode, lErrorReg, lErrorValue;
|
|
SpcGetParam (deviceno, SPC_LASTERRORCODE, &lErrorCode);
|
|
SpcGetParam (deviceno, SPC_LASTERRORREG, &lErrorReg);
|
|
SpcGetParam (deviceno, SPC_LASTERRORVALUE, &lErrorValue);
|
|
char error_message[256];
|
|
snprintf(error_message, sizeof(error_message),"Configuration error %d in register %d at value %d", lErrorCode, lErrorReg, lErrorValue);
|
|
throw SpectrumMI40xxSeries_error(error_message);
|
|
}
|
|
|
|
}
|
|
|
|
double SpectrumMI40xxSeries::get_sample_clock_frequency() const {
|
|
|
|
if (effective_settings==NULL ||
|
|
effective_settings->samplefreq<=0 ||
|
|
effective_settings->data_structure==NULL ||
|
|
effective_settings->data_structure->size()==0)
|
|
return 0;
|
|
|
|
return effective_settings->samplefreq;
|
|
}
|
|
|
|
int SpectrumMI40xxSeries::TimeoutThread() {
|
|
// now collect data
|
|
assert(effective_settings!=NULL);
|
|
size_t sampleno=effective_settings->data_structure->size();
|
|
size_t buff_number_expected=(sampleno*2*sizeof(short int)+fifobufferlen-1)/fifobufferlen;
|
|
SpcSetParam(deviceno, SPC_FIFO_BUFMAXCNT, buff_number_expected);
|
|
size_t buff_number=0;
|
|
short int* buff_pointer=fifo_adc_data;
|
|
|
|
// start fifo aquisition
|
|
#if SPC_DEBUG
|
|
fprintf(stderr, "SPC_FIFOSTART\n");
|
|
#endif
|
|
SpcSetParam(deviceno, SPC_COMMAND, SPC_FIFOSTART);
|
|
|
|
do {
|
|
// wait for the buffers
|
|
pthread_testcancel();
|
|
#if SPC_DEBUG
|
|
fprintf(stderr, "reading buffer no %" SIZETPRINTFLETTER "/%" SIZETPRINTFLETTER "\n", buff_number+1, buff_number_expected);
|
|
#endif
|
|
if (buff_number+1==buff_number_expected) {
|
|
// the last one is special, copy only expected bytes
|
|
memcpy(buff_pointer, fifobuffers[buff_number%(fifobuffers.size())], sampleno*2*sizeof(short int)-fifobufferlen*buff_number);
|
|
break;
|
|
}
|
|
else
|
|
buff_pointer=(short int*)mempcpy(buff_pointer, fifobuffers[buff_number%(fifobuffers.size())], fifobufferlen);
|
|
SpcSetParam(deviceno,SPC_FIFO_BUFREADY, buff_number%(fifobuffers.size()));
|
|
buff_number++;
|
|
int buff_available;
|
|
SpcGetParam(deviceno, SPC_FIFO_BUFCOUNT, &buff_available);
|
|
if (((size_t)buff_available)>buff_number) {
|
|
// get also the next buffer...
|
|
#if SPC_DEBUG
|
|
fprintf(stderr, "read %" SIZETPRINTFLETTER " buffers, %d buffers gained by the hardware sofar\n", buff_number, buff_available);
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
int adc_status;
|
|
#if SPC_DEBUG
|
|
fprintf(stderr, "SPC_STATUS\n");
|
|
#endif
|
|
SpcGetParam (deviceno, SPC_STATUS, &adc_status);
|
|
if (adc_status==SPC_READY) {
|
|
// this must be a timeout!
|
|
//pthread_cancel(timeout_pt);
|
|
//pthread_join(timeout_pt,NULL);
|
|
//pthread_attr_destroy(&timeout_pt_attrs);
|
|
free(fifo_adc_data);
|
|
fifo_adc_data=NULL;
|
|
throw SpectrumMI40xxSeries_error("timeout occured while collecting data");
|
|
return 0;
|
|
}
|
|
#if SPC_DEBUG
|
|
fprintf(stderr, "waiting for next fifo buffer\n");
|
|
fprintf(stderr, "SPC_FIFOWAIT\n");
|
|
#endif
|
|
SpcSetParam(deviceno, SPC_COMMAND, SPC_FIFOWAIT);
|
|
}
|
|
while (buff_number<buff_number_expected);
|
|
#if SPC_DEBUG
|
|
fprintf(stderr, "SPC_STOP\n");
|
|
#endif
|
|
SpcSetParam(deviceno, SPC_COMMAND, SPC_STOP);
|
|
|
|
return 1;
|
|
}
|
|
|
|
short int* SpectrumMI40xxSeries::split_adcdata_recursive(short int* data, const DataManagementNode& structure, adc_results& result_splitted) {
|
|
short nchannels = effective_settings->lSetChannels;
|
|
if (structure.child==NULL) {
|
|
// simple case: do real work
|
|
// todo: Channel selection
|
|
short int* datachunk=(short int*)malloc(sizeof(short int)*structure.n*nchannels);
|
|
if (datachunk==NULL) {
|
|
throw SpectrumMI40xxSeries_error("not enough memory to create results");
|
|
}
|
|
// todo: error checking
|
|
memcpy(datachunk, data, sizeof(short int)*structure.n*nchannels);
|
|
data+=structure.n*nchannels;
|
|
adc_result* the_result=new adc_result(0, structure.n, datachunk, effective_settings->samplefreq,nchannels);
|
|
result_splitted.push_back(the_result);
|
|
if (structure.next!=NULL)
|
|
data=split_adcdata_recursive(data, *(structure.next), result_splitted);
|
|
}
|
|
else
|
|
for (size_t i=0; i<structure.n; ++i) {
|
|
data=split_adcdata_recursive(data, *(structure.child), result_splitted);
|
|
}
|
|
return data;
|
|
|
|
}
|
|
|
|
result* SpectrumMI40xxSeries::get_samples(double _timeout) {
|
|
|
|
if (core::term_signal!=0) return NULL;
|
|
size_t sampleno=(effective_settings==NULL || effective_settings->data_structure==NULL)?0:effective_settings->data_structure->size();
|
|
if (sampleno==0) return new adc_result(1,0,NULL);
|
|
short nchannels=(effective_settings==NULL)?0:effective_settings->lSetChannels;
|
|
if (nchannels==0) throw SpectrumMI40xxSeries_error("No channels specified");
|
|
|
|
// double timeout=_timeout;
|
|
|
|
#if SPC_DEBUG
|
|
fprintf(stderr,"effective_settings in get_samples: %g\n",effective_settings->timeout);
|
|
#endif
|
|
// allocate a lot of space
|
|
// todo: use GatedSampling buffers directly!
|
|
|
|
if (fifobuffers.empty()) {
|
|
#if SPC_DEBUG
|
|
printf("ADC not in FIFO mode\n");
|
|
#endif
|
|
short int* adc_data=(short int*)malloc(sampleno*sizeof(short int)*nchannels);
|
|
if (adc_data==NULL) {
|
|
for (std::vector<short int*>::iterator i=fifobuffers.begin(); i!=fifobuffers.end(); ++i) free(*i);
|
|
fifobuffers.clear();
|
|
throw SpectrumMI40xxSeries_error("could not allocate adc data memory");
|
|
}
|
|
|
|
// simple version: standard acquisition, wait and get...
|
|
#if SPC_DEBUG
|
|
fprintf(stderr, "fetching %" SIZETPRINTFLETTER " samples in normal mode (timeout is %g)\n",sampleno,effective_settings->timeout);
|
|
#endif
|
|
stopwatch adc_timer;
|
|
adc_timer.start();
|
|
int adc_status, i;
|
|
double elapsed_time=0;
|
|
/*
|
|
The gate-end signal can only be recognized at an eigth samples alignement.
|
|
The board can record up to 7 more samples, the timeout has to be allowed to be longer
|
|
*/
|
|
double post_gate_maxtime=(1e8*7.5/effective_settings->samplefreq)/1e8;
|
|
i = 0;
|
|
SpcGetParam(deviceno, SPC_STATUS, &adc_status);
|
|
/*
|
|
Sometimes the timeout is not enough, the reason could be that the timer
|
|
starts running before the actual start of the pulse program because the OS is not loading and starting
|
|
the pulseblaster immediatly. It is not possible to
|
|
synchronize the timers for now.
|
|
Thus, we add a generous 1s to the timeout to be on the safe side.
|
|
*/
|
|
while (core::term_signal==0 && adc_status!=SPC_READY && elapsed_time<=(effective_settings->timeout + post_gate_maxtime + 1) ) {
|
|
timespec sleeptime;
|
|
sleeptime.tv_nsec=10*1000*1000; // 10 ms
|
|
sleeptime.tv_sec=0;
|
|
nanosleep(&sleeptime,NULL);
|
|
SpcGetParam(deviceno, SPC_STATUS, &adc_status);
|
|
elapsed_time=adc_timer.elapsed();
|
|
#if SPC_DEBUG
|
|
fprintf(stderr, "(nofifo) loop %i (adc_status, t, t_out, post_gate, core): %i %g %g %g %i\n",i,adc_status, elapsed_time, effective_settings->timeout, post_gate_maxtime, core::term_signal);
|
|
#endif
|
|
i++;
|
|
}
|
|
#if SPC_DEBUG
|
|
fprintf(stderr, "(nofifo) loop stopped (adc_status, t, t_out, core): %i %g %g %i\n",adc_status, elapsed_time, effective_settings->timeout, core::term_signal);
|
|
#endif
|
|
/* card ready to transfer samples */
|
|
if (adc_status!=SPC_READY) {
|
|
free(adc_data);
|
|
#if SPC_DEBUG
|
|
fprintf(stderr, "ERROR: adc_status not ready (%i) \n",adc_status );
|
|
#endif
|
|
throw SpectrumMI40xxSeries_error("timeout occured while collecting data");
|
|
}
|
|
SpcSetParam(deviceno, SPC_COMMAND, SPC_STOP);
|
|
if (core::term_signal!=0) {
|
|
#if SPC_DEBUG
|
|
fprintf(stderr, "received core::term_signal %i\n",core::term_signal);
|
|
#endif
|
|
free(adc_data);
|
|
return NULL;
|
|
}
|
|
|
|
# if defined __linux__
|
|
size_t data_length=SpcGetData (deviceno, 0, 0, nchannels * sampleno, 2, (dataptr) adc_data);
|
|
# if SPC_DEBUG
|
|
fprintf(stderr, "SpcGetData returned %" SIZETPRINTFLETTER " bytes, got %" SIZETPRINTFLETTER " out of %" SIZETPRINTFLETTER " expected samples\n", data_length, data_length/sizeof(short int)/2, sampleno);
|
|
# endif
|
|
if (nchannels*sizeof(short int)*sampleno!=data_length) {
|
|
free(adc_data);
|
|
throw SpectrumMI40xxSeries_error("wrong amount of data from adc card");
|
|
}
|
|
// current position in adc data array
|
|
# elif defined __CYGWIN__
|
|
int16 read_result=SpcGetData (deviceno, 0, 0, nchannels * sampleno,(void*) adc_data);
|
|
# if SPC_DEBUG
|
|
fprintf(stderr, "SpcGetData returned %d\n", read_result);
|
|
# endif
|
|
if (read_result!=0) {
|
|
free(adc_data);
|
|
throw SpectrumMI40xxSeries_error("wrong amount of data from adc card");
|
|
}
|
|
# endif
|
|
short int* data_position=adc_data;
|
|
// produced results
|
|
adc_results* the_results=new adc_results(0);
|
|
data_position=split_adcdata_recursive(data_position, *(effective_settings->data_structure), *the_results);
|
|
if (data_position==0 || (size_t)(data_position-adc_data)!=(sampleno*nchannels)) {
|
|
fprintf(stderr,"something went wrong while spliting data\n");
|
|
}
|
|
free(adc_data);
|
|
delete effective_settings;
|
|
effective_settings=NULL;
|
|
return the_results;
|
|
}
|
|
else {
|
|
#if SPC_DEBUG
|
|
printf("ADC in FIFO mode!\n");
|
|
fprintf(stderr, "fetching %" SIZETPRINTFLETTER " samples in fifo mode (timeout is %g)\n",sampleno,effective_settings->timeout);
|
|
#endif
|
|
// FIFO method
|
|
stopwatch timer;
|
|
timer.start();
|
|
do {
|
|
int32 lStatus;
|
|
timespec sleeptime;
|
|
sleeptime.tv_sec=0;
|
|
sleeptime.tv_nsec=100*1000*1000;
|
|
nanosleep(&sleeptime,NULL);
|
|
//pthread_testcancel();
|
|
SpcGetParam (deviceno, SPC_STATUS, &lStatus);
|
|
#if SPC_DEBUG
|
|
fprintf(stderr, "while loop FIFO (lStatis, timer, effective_timeout): %i %g %g\n",lStatus, timer.elapsed(), effective_settings->timeout );
|
|
#endif
|
|
if (lStatus == SPC_READY) break;
|
|
} while (timer.elapsed()<effective_settings->timeout); //rosi _timeout
|
|
SpcSetParam(deviceno, SPC_COMMAND, SPC_STOP);
|
|
|
|
#if SPC_DEBUG
|
|
fprintf(stderr, "waiting for read thread terminating\n");
|
|
#endif
|
|
/* wait for read thread */
|
|
pthread_cancel(timeout_pt);
|
|
pthread_join(timeout_pt,NULL);
|
|
pthread_attr_destroy(&timeout_pt_attrs);
|
|
/* free it's resources */
|
|
#if SPC_DEBUG
|
|
fprintf(stderr, "freeing fifo data buffers\n");
|
|
#endif
|
|
for (std::vector<short int*>::iterator i=fifobuffers.begin(); i!=fifobuffers.end(); ++i) free(*i);
|
|
fifobuffers.clear();
|
|
|
|
adc_results* the_results=NULL;
|
|
if (fifo_adc_data!=NULL) {
|
|
// split data to (small) pieces
|
|
short int* data_position=fifo_adc_data;
|
|
the_results=new adc_results(0);
|
|
data_position=split_adcdata_recursive(data_position, *(effective_settings->data_structure), *the_results);
|
|
if (data_position==0 || (size_t)(data_position-fifo_adc_data)!=(sampleno*nchannels)) {
|
|
fprintf(stderr,"something went wrong while spliting data from fifo mode\n");
|
|
}
|
|
free(fifo_adc_data);
|
|
fifo_adc_data=NULL;
|
|
}
|
|
else
|
|
fprintf(stderr,"something went wrong while collecting data with fifo mode\n");
|
|
|
|
delete effective_settings;
|
|
effective_settings=NULL;
|
|
return the_results;
|
|
}
|
|
}
|
|
|
|
/* check whether a channel mask is legal for this board */
|
|
bool SpectrumMI40xxSeries::IsChannelMaskLegal(int mask) {
|
|
if ( (cardType == TYP_MI4020 || cardType == TYP_MI4030) && mask != CHANNEL0) {
|
|
return false;
|
|
} else if ( (cardType == TYP_MI4021 || cardType == TYP_MI4031) && mask != (CHANNEL0 | CHANNEL1) && mask != CHANNEL0) {
|
|
return false;
|
|
} else if ((cardType == TYP_MI4022 || cardType == TYP_MI4032) && mask != (CHANNEL0 | CHANNEL1 | CHANNEL2 | CHANNEL3) && mask != (CHANNEL0 | CHANNEL1) && mask != (CHANNEL0 | CHANNEL2) && mask != CHANNEL0) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
SpectrumMI40xxSeries::~SpectrumMI40xxSeries() {
|
|
SpcSetParam (deviceno, SPC_COMMAND, SPC_STOP); // stop the board
|
|
# if defined __linux__
|
|
close (deviceno);
|
|
# elif defined __CYGWIN__
|
|
FreeLibrary(spectrum_driver_dll);
|
|
# endif
|
|
}
|
|
|