In the SoftSync section, we used Dewesoft's clock as the master clock and the device clock as the slave clock, so we changed the timestamps of our samples according to the master clock. But in this mode, our device will be the clock provider, meaning that the device will be the master clock. When in Clock provider mode, our output channel can be synchronous or asynchronous type. First, we will describe the simpler one, which is the asynchronous type.
To use a Clock provider mode, we have to specify that we are the clock provider. We do this by selecting the Test DAQ Plugin item from the drop-down Time source menu.
Image 10: In settings set the Test DAQ plugin as the Time source
We can later go to Settings > Devices tab > Test DAQ Plugin and under Synchronization Type choose Clock provider sync or Clock provider async. The sample rate in Clock provider mode is NOT determined by the value you choose in the Device Sample Rate combo box, therefore, it does not matter which value is selected. The sample rate is the same as the Dynamic acquisition rate, which is set in Ch. Setup under Analog in tab.
Asynchronous clock provider mode
If your device is intended to be the clock provider, Dewesoft needs to somehow be notified. This is done in plugin_impl.h file using STDMETHOD(raw_ProvidesClock(VARIANT_BOOL * Value));
function. As you can see in the function prototype, the argument Value
is passed by reference. You should set its value to true if you want your plugin to be the master clock, or to false otherwise.
Function definition should, therefore, look like this.
STDMETHODIMP_(HRESULT __stdcall) Plugin::raw_ProvidesClock(VARIANT_BOOL * Value)
{
if(bridge.providesClock())
*Value = TRUE;
else *Value = FALSE;
return S_OK;
}
Now that we have specified that our plugin will provide the clock, we have to specify the time for other plugins whenever they ask for it. We do this by keeping track of number of samples which were outputted into the output channel. We then calculate the time as a function of number of samples outputted and the device sample rate inside plugin_impl.h file using STDMETHOD(raw_OnGetClock(long * clockLow, long * clockHigh));
function:
void DewesoftBridge::onGetClock(long * clockLow, long * clockHigh)
{
int64_t samples;
if (OtherPluginsAskingForClock(*clockLow))
samples = EvaluatedSamplesCount();
else
samples = acquiredSamplesFromDevice;
*clockLow = getLowest32Bits(samples);
*clockHigh = getHighest32Bits(samples);
}
The getLowest32Bits
and getHighest32Bits
functions use the number of samples acquired and split them into two 32-bit numbers, representing the lowest and highest 32 bits of the actual count. Mentioned functions are using
long getLowest32Bits(int64_t samples)
{
return samples & 0xffffffff;
}
long getHighest32Bits(int64_t samples)
{
return (samples >> 32);
}
Now all that we have to do is insert the samples to the output channel. This is done the same way as it is done in the SoftSync mode, but we do not have to modify the time which was returned from the device. Therefore we just insert sample values and sample timestamps directly into the output channel using the following lines of code.
for (size_t i = 0; i < data.size(); i++)
{
outputChannel->AddAsyncDoubleSample(data[i], time[i]);
acquiredSamples++;
}
When using the asynchronous channel, the number of samples that are inserted into the output channel is determined by the programmer. In the example code above we inserted a whole block of data into the output channel whenever we received new ones. That is why we added the following line of code, acquiredSamples++;
so our DewesoftBridge::onGetClock
function will provide the correct time whenever asked for time (as already mentioned, when your plugin is clock provider, you provide a clock by specifying the number of inserted samples).
Synchronous Clock provider
Whenever we are using Synchronous Clock provider mode, we have to set our output channel to be synchronous. It is done in Dewesoft_bridge.h inside STDMETHODIMP onPreinitiate();
event. If you can not find this function, you should create your own implementation. It is done in plugin_impl.cpp file inside STDMETHODIMP Plugin::raw_OnEvent(enum EventIDs eventID, VARIANT inParam, VARIANT* outParam)
function. You have to catch a new event called evPreInitiate and add it to the switch-case block. Example code looks like this:
STDMETHODIMP Plugin::raw_OnEvent(enum EventIDs eventID, VARIANT inParam, VARIANT* outParam)
{
switch (eventID)
{
case evPreInitiate:
returnValue = eventPreinitiate();
break;
}
}
plugin_impl.h
STDMETHODIMP eventPreinitiate();
plugin_impl.cpp
STDMETHODIMP Plugin::eventPreinitiate()
{
bridge.onPreinitiate();
return S_OK;
}
dewesoft_bridge.h
STDMETHODIMP onPreinitiate();
dewesoft_bridge.cpp
STDMETHODIMP_(HRESULT __stdcall) DewesoftBridge::onPreinitiate()
{
if (mode == ClockProviderSync)
outputChannel->SetAsync(false);
return S_OK;
}
When your device is meant to provide clock and performs in synchronous mode we have to specify it by using the same code as we did in asynchronous clock provider (pay attention to STDMETHOD(raw_ProvidesClock(VARIANT_BOOL * Value))
function).
As we have already mentioned, when in Asynchronous mode, we can decide how many samples we want to insert into the output channel. Well, this is not possible when in Synchronous mode because we must insert an exact number of samples. To get this exact number, we have to call getCurrentSampleCount
function, which returns the number of all samples that could have been added since the start of the measurement, and subtract the number of samples we already outputted. That is why we need to increment the value of insertedSamples
variable whenever we output data into the output channel.
int64_t samplesToInsert = getCurrentSampleCount() - insertedSamples;
At this point, we have modified the Device::GetData
function to provide us with the exact number of samples. It now looks like this: int GetData(HANDLE device, void* data, int numberOfsamples = 0);
. By adding , int numberOfsamples = 0
parameter, we will not change Device::GetData function calls from other modes.
But there are a few things which we need to take into the account when we are using the device to provide us the data. In this example, we modified the Device::GetData
function to return the exact number of samples which we need to insert. In this case, we just insert all the samples into the output channel. We do this using the following code.
int64_t samplesToInsert = getCurrentSampleCount() - insertedSamples;
for (int i = 0; i < samplesToInsert; i++) {
outputChannel->AddDoubleSample(data[i]);
insertedSamples++;
}
But what if the device returns fewer samples than the output channel needs? Or what if the device returns more samples than needed?
In the first scenario (numberOfSamples < samplesToInsert), we have to set the calcDelay property of the output channel to the number of samples that are missing. Example of setting the calcDelay: outputChannel->CalcDelay = samplesToInsert - data.size();
In the second scenario (numberOfSamples > samplesToInsert), we have to store the extra samples, so they will be ready to be inserted in the next onGetData function call. To store the extra samples we will generate a new class member which will hold all un-inserted data.
Function void onGetClock(long* clockLow, long* clockHigh);
should remain the same as it was when in Asynchronous clock provider mode. In asynchronous we were counting a number of samples we inserted, while in synchronous, we are calculating samples by multiplying time (since the start of the measurement) with device sample rate.
double secondsPassed = (std::chrono::high_resolution_clock::now() - startTime).count() * 1e-9;
acquiredSamples = secondsPassed * Device::GetDeviceSampleRate(SineWaveGenerator);