example_swtrigger_edgeΒΆ

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
function [data, config] = example_swtrigger_edge(device_id, varargin)
% EXAMPLE_SWTRIGGER_EDGE Record data using a demodulator trigger via ziDAQ's record module
%
% NOTE This example can only be ran on UHF and HF2 Instruments with the MF
% Option and MF Instruments with the MD Option enabled. The MF Option is not
% required to use the SW Trigger. However, this example is a stand-alone
% example that generates (by creating a beat in demodulator data) and captures
% it's own triggers. As such, it requires a simple feedback BNC cable between
% Signal Output 0 and Signal Input 0 of the device.
%
% USAGE DATA = EXAMPLE_SWTRIGGER_EDGE(DEVICE_ID)
%
% Record demodulator sample data using a software trigger from ziDAQ's
% 'record' module from the device specified by DEVICE_ID, e.g., 'dev2006' or
% 'uhf-dev2006'.
%
% The record module implements software triggering analogously to the types of
% triggering found in spectroscopes.
%
% This example generates a 'beat' in the demodulator signal in order to
% simulate 'events' in the demodulator data. Signal segments of these events
% are then recorded when the rising edge of the demodulator R value exceeds a
% certain threshold.
%
% NOTE Additional configuration: Connect signal output 1 to signal input 1
% with a BNC cable.
%
% NOTE Please ensure that the ziDAQ folders 'Driver' and 'Utils' are in your
% Matlab path. To do this (temporarily) for one Matlab session please navigate
% to the ziDAQ base folder containing the 'Driver', 'Examples' and 'Utils'
% subfolders and run the Matlab function ziAddPath().
% >>> ziAddPath;
%
% Use either of the commands:
% >>> help ziDAQ
% >>> doc ziDAQ
% in the Matlab command window to obtain help on all available ziDAQ commands.
%
% Copyright 2008-2018 Zurich Instruments AG

clear ziDAQ;

if ~exist('device_id', 'var')
    error(['No value for device_id specified. The first argument to the ' ...
           'example should be the device ID on which to run the example, ' ...
           'e.g. ''dev2006'' or ''uhf-dev2006''.'])
end

% Check the ziDAQ MEX (DLL) and Utility functions can be found in Matlab's path.
if ~(exist('ziDAQ') == 3) && ~(exist('ziCreateAPISession', 'file') == 2)
    fprintf('Failed to either find the ziDAQ mex file or ziDevices() utility.\n')
    fprintf('Please configure your path using the ziDAQ function ziAddPath().\n')
    fprintf('This can be found in the API subfolder of your LabOne installation.\n');
    fprintf('On Windows this is typically:\n');
    fprintf('C:\\Program Files\\Zurich Instruments\\LabOne\\API\\MATLAB2012\\\n');
    return
end

% The API level supported by this example.
supported_apilevel = 5;
% This example runs on any device type but requires either the Multifrequency
% or Multidemodulator option.
required_devtype = '.*';
required_options = {'MF|MFK|MD'};
required_err_msg = ['This example requires either an HF2/UHF Instrument ' ...
                    'with the Multifrequency (MF) Option installed or an MF ' ...
                    'Instrument with Multidemodulator (MD) option installed. ' ...
                    'Note: The MF/MD Option is not a requirement to use the ' ...
                    'SW Trigger module itself, just to run this example.'];
% Create an API session; connect to the correct Data Server for the device.
[device, props] = ziCreateAPISession(device_id, supported_apilevel, ...
                                     'required_devtype', '.*', ...
                                     'required_options', required_options, ...
                                     'required_err_msg', required_err_msg);
ziApiServerVersionCheck();

branches = ziDAQ('listNodes', ['/' device ], 0);
if ~any(strcmp([branches], 'DEMODS'))
  data = nan; config = nan;
  fprintf('\nThis example requires lock-in functionality which is not available on %s.\n', device);
  return
end

% Define parameters relevant to this example. Default values specified by the
% inputParser below are overwritten if specified as name-value pairs via the
% `varargin` input argument.
%
% NOTE The possible choice of the parameters:
% - demod_rate (and the number of demods enabled=^length(demod_idx)),
% - event_frequency (the number of triggers we record per second),
% are highly constrained by the performance of the PC where the Data Server
% and Matlab API client are running. The values of the parameters set here are
% conservative. Much higher values can be obtained on state-of-the-art PCs
% (e.g., 400e3 demod_rate (2 or 3 demods) and event_frequency 5000 for UHF).
% Whilst experimenting with parameter values, it's advisable to monitor CPU
% load and memory usage.
p = inputParser;
isnonnegscalar = @(x) isnumeric(x) && isscalar(x) && (x > 0);
isnonnegvector = @(x) isnumeric(x) && isvector(x) && all(x > 0);
% The indices of the demodulators to record for the experiment, 1-based
% indexing.
p.addParamValue('demod_idx', [1, 2], isnonnegvector);
% Pick a suitable (although conservative) demodulator rate based on the device
% class.
if strfind(props.devicetype, 'UHF')
    default_demod_rate = 100e3;
else
    default_demod_rate = 56e3;
end
p.addParamValue('demod_rate', default_demod_rate, isnonnegscalar);
p.addParamValue('event_frequency', 100, isnonnegscalar);
p.addParamValue('trigger_count', 1000, isnonnegscalar);
p.parse(varargin{:});
demod_rate = p.Results.demod_rate;
demod_idx = p.Results.demod_idx;
event_frequency = p.Results.event_frequency;
trigger_count = p.Results.trigger_count;

% More parameters relevant to this example, some of which we derive from the
% inputParser parameters. We package them in a struct for convenience.
config = struct();

% The value later used for the SW Trigger's '/0/duration' parameter:
% This is the duration in seconds of signal segment to record: Let's record
% half the duration of each beat.
config.trigger_duration = 0.5/event_frequency;  % [s]

% The value later used for the SW Trigger's '/0/delay' parameter: This
% specifies the delay in seconds to wait before recording the signal after the
% point in the time when the trigger is activated. A negative value indicates
% a pretrigger time.
config.trigger_delay = -0.125/event_frequency;  % [s]

% Signal output mixer amplitude, [V]. The trigger threshold must be based on
% this.
amplitude = 0.100;

% The value later used for the SW Trigger's '/0/level' parameter: This
% specifieds threshold level required to trigger an event.
config.trigger_level = 0.45*amplitude/sqrt(2.);

fprintf('Event frequency (beat freqency): %.1f\n', event_frequency);

% Define some other helper parameters.
config.demod_idx = demod_idx;
config.device = device;
config.clockbase = double(ziDAQ('getInt', ['/' device '/clockbase']));

% demod_idx is used to access the data in Matlab arrays and are therefore
% 1-based indexed. Node paths on the HF2/UHF use 0-based indexing.
config.demod_c = zeros(size(demod_idx));
for ii=demod_idx
    config.demod_c(ii) = num2str(demod_idx(ii)-1, '%0d');
end

out_c = '0';
in_c = '0';

% Create a base configuration: Disable all available outputs, awgs,
% demods, scopes,...
ziDisableEverything(device);

%% Configure the device ready for this experiment.
ziDAQ('setInt', ['/' device '/sigins/' in_c '/imp50'], 1);
ziDAQ('setInt', ['/' device '/sigins/' in_c '/ac'], 0);
ziDAQ('setDouble', ['/' device '/sigins/' in_c '/range'], 2);
ziDAQ('setInt', ['/' device '/sigouts/' out_c '/on'], 1);
ziDAQ('setDouble', ['/' device '/sigouts/' out_c '/range'], 1);
ziDAQ('setDouble', ['/' device '/sigouts/' out_c '/amplitudes/*'], 0);
if strfind(props.devicetype, 'HF2')
    ziDAQ('setInt', ['/' device '/sigins/' in_c '/diff'], 0);
    ziDAQ('setInt', ['/' device '/sigouts/' out_c '/add'], 0);
end
ziDAQ('setDouble', ['/' device '/demods/*/phaseshift'], 0);
for d=config.demod_c
    ziDAQ('setDouble', ['/' device '/demods/' d '/rate'], demod_rate);
end
% Ensure the configuration has taken effect before reading back the value actually set.
ziDAQ('sync');
demod_rate_set = ziDAQ('getDouble', ['/' device '/demods/' config.demod_c(1) '/rate']);

ziDAQ('setInt', ['/' device '/demods/*/harmonic'], 1);
for d=config.demod_c
    ziDAQ('setInt', ['/' device '/demods/' d '/enable'], 1);
end
ziDAQ('setInt', ['/' device '/demods/*/adcselect'], str2double(in_c));
ziDAQ('setInt', ['/' device '/demods/*/oscselect'], 0);

% Generate the beat.
ziDAQ('setInt', ['/' device '/demods/0/oscselect'], 0);
ziDAQ('setInt', ['/' device '/demods/1/oscselect'], 1);  % requires MF option.
ziDAQ('setDouble', ['/' device '/oscs/*/freq'], 400e3);  % [Hz]
ziDAQ('setDouble', ['/' device '/oscs/1/freq'], 400e3 + event_frequency);  % [Hz]
% We require 2 Signal Output mixer channels to superimpose the two signals
% with different frequencies in order to generate the beat.
out_mixer_c_0 = ziGetDefaultSigoutMixerChannel(props, 0);
out_mixer_c_1 = out_mixer_c_0 + 1;
ziDAQ('setDouble', ['/' device '/sigouts/' out_c '/amplitudes/' out_mixer_c_0], amplitude);
ziDAQ('setDouble', ['/' device '/sigouts/' out_c '/amplitudes/' out_mixer_c_1], amplitude);
ziDAQ('setDouble', ['/' device '/sigouts/' out_c '/enables/' out_mixer_c_0], 1);
ziDAQ('setDouble', ['/' device '/sigouts/' out_c '/enables/' out_mixer_c_1], 1);
order = 4;
ziDAQ('setInt', ['/' device '/demods/*/order'], order);
% A small timeconstant is required to see the interference between the
% demodulators
timeconstant = ziBW2TC(demod_rate_set/2, order);
ziDAQ('setDouble', ['/' device '/demods/0/timeconstant'], timeconstant);
timeconstant = ziBW2TC(10e3, order);
ziDAQ('setDouble', ['/' device '/demods/1/timeconstant'], timeconstant);
timeconstant = ziBW2TC(event_frequency, order);
ziDAQ('setDouble', ['/' device '/demods/2/timeconstant'], timeconstant);
for i=1:length(config.demod_c)
    timeconstants_set(i) = ziDAQ('getDouble', ['/' device '/demods/' config.demod_c(i) '/timeconstant']);
end

% Unsubscribe from any streaming data
ziDAQ('unsubscribe', '*');
% Flush all the buffers.
ziDAQ('sync');

% Wait for the demodulator filter to settle
pause(20*max(timeconstants_set));

% Create a recorder thread, the return argument is a handle to the recorder
% (thread).
h = ziDAQ('record');

%% Configure the recorder
% Device on which trigger will be performed
ziDAQ('set', h, '/device', device)
% The number of triggers to capture (if not running in endless mode).
ziDAQ('set', h, '/0/count', trigger_count);
ziDAQ('set', h, '/endless', 0);
%   type:
%     NO_TRIGGER = 0
%     EDGE_TRIGGER = 1
%     DIGITAL_TRIGGER = 2
%     PULSE_TRIGGER = 3
%     TRACKING_TRIGGER = 4
%     HW_TRIGGER = 6
%     TRACKING_PULSE_TRIGGER = 7
%     EVENT_COUNT_TRIGGER = 8
ziDAQ('set', h, '/0/type', 1);
%   triggernode, specify the triggernode to trigger on.
%     SAMPLE.X = Demodulator X value
%     SAMPLE.Y = Demodulator Y value
%     SAMPLE.R = Demodulator Magnitude
%     SAMPLE.THETA = Demodulator Phase
%     SAMPLE.AUXIN0 = Auxilliary input 1 value
%     SAMPLE.AUXIN1 = Auxilliary input 2 value
%     SAMPLE.DIO = Digital I/O value
triggernode = ['/' device '/demods/' config.demod_c(1) '/sample.r'];
ziDAQ('set', h, '/0/triggernode', triggernode);
%   edge:
%     POS_EDGE = 1
%     NEG_EDGE = 2
%     BOTH_EDGE = 3
ziDAQ('set', h, '/0/edge', 1)
% The size of the internal buffer used to store data, this should be larger
% than trigger_duration.
ziDAQ('set', h, '/buffersize', max(0.2, 1.2*config.trigger_duration));
% The length of each trigger to record (in seconds).
ziDAQ('set', h, '/0/duration', config.trigger_duration);
ziDAQ('set', h, '/0/delay', config.trigger_delay);
% Do not extend the size of the returned trigger frame if new triggers arrive
% whilst recording a trigger.
ziDAQ('set', h, '/0/retrigger', 0);
% Do not return overlapped trigger events.
ziDAQ('set', h, '/0/holdoff/time', config.trigger_duration);
ziDAQ('set', h, '/0/holdoff/count', 0);
ziDAQ('set', h, '/0/level', config.trigger_level)
% The hysterisis is effectively a second criteria (if non-zero) for triggering
% and makes triggering more robust in noisy signals. When the trigger `level`
% is violated, then the signal must return beneath (for positive trigger edge)
% the hysteresis value in order to trigger.
ziDAQ('set', h, '/0/hysteresis', 0.1*config.trigger_level)

% Unrequired parameters when /0/type is EDGE_TRIGGER:
% ziDAQ('set', h, '/0/bitmask', 1);
% ziDAQ('set', h, '/0/bits', 1);

%% Subscribe to the demodulators
% fliplr: Subscribe in descending order so that we subscribe to the trigger
% demdulator last (demod 0). This way we will not start acquiring data on the
% trigger demod before we subscribe to other demodulators.
for d=fliplr(config.demod_c)
    ziDAQ('subscribe', h, ['/' device '/demods/' d '/sample']);
end

% Prepare a figure for plotting
figure(1); clf;
box on; grid on; hold on;
xlabel('\bf Time (relative to trigger position) [s]');
ylabel('\bf Signal');

fprintf('Num samples per signal segment: %d\n', demod_rate_set*config.trigger_duration);

%% Start recording
% now start the thread -> ready to be triggered
ziDAQ('execute', h);

timeout = 20; % [s]
num_triggers = 0;
n = 0;
t0 = tic;
tRead = tic;
dt_read = 0.250;
while ~ziDAQ('finished', h)
    pause(0.05);
    % Perform an intermediate readout of the data. the data between reads is
    % not acculmulated in the module - it is cleared, so that the next time
    % you do a read you (should) only get the triggers that came inbetween the
    % two reads.
    if toc(tRead) > dt_read
        data = ziDAQ('read', h);
        fprintf('Performed an intermediate read() of recorded data (time since last read %.3f s).\n', toc(tRead));
        fprintf('Recorder progress (acquired %d of total %d triggers): %.1f%%\n', num_triggers, trigger_count, 100*ziDAQ('progress', h));
        tRead = tic;
        if ziCheckPathInData(data, ['/' device '/demods/' config.demod_c(1) '/sample'])
            num_triggers = num_triggers + check_data(data, config);
            % Do some other processing and save data...
            % ...
        end
    end
    % Timeout check
    if toc(t0) > timeout
        % If for some reason we're not obtaining triggers quickly enough, the
        % following command will force the end of the recording.
        if num_triggers == 0
            ziDAQ('finish', h);
            ziDAQ('clear', h);
            error('Failed to record any triggers before timeout (%d seconds). Missing feedback cable between sigout 0 and sigin 0?', timeout);
        else
            fprintf('Recorded %d triggers. Loop timeout (%.2f s) before acquiring %d triggers\n');
            fprintf('Increase loop `timeout` to record more.\n', num_triggers, timeout, trigger_count);
        end
    end
end
tEnd = toc(t0);

ziDAQ('unsubscribe', h, ['/' device '/demods/*/sample']);

% Release module resources. Especially important if modules are created
% inside a loop to prevent excessive resource consumption.
ziDAQ('clear', h);

end

function num_triggers = check_data(data, config)
%CHECK_DATA check data for sampleloss and plot some triggers for feedback

device = config.device;
demod_idx = config.demod_idx;

% We use cell arrays to address the individual segments from each trigger
num_triggers = length(data.(device).demods(demod_idx(1)).sample);
if num_triggers == 0
    return
end
fprintf('Data contains %d data segments (triggers).\n', num_triggers);
sampleloss = check_segments_for_sampleloss(data, config);
if any(sampleloss)
    fprintf('Warning: Sampleloss detected in %d triggers.\n', sum(sampleloss));
    if sum(sampleloss) == num_triggers
        error('Error all triggers contained sampleloss.\n');
    end
else
    fprintf('No sampleloss detected.\n');
end

figure(1); cla;
plot_style = {'r-', 'b-', 'g-', 'k-'};
num_triggers_plotted = 0;
for i=1:num_triggers
    if num_triggers_plotted >= 100;
        % Only plot first 100 valid triggers.
        break;
    end
    if sampleloss(i)
        continue
    end
    for d=1:length(config.demod_c)
        % Convert the trigger timestamp to seconds. The trigger timestamp is
        % the timestamp at which the trigger criteria was fulfilled and has in
        % general a higher resolution than the demodulator rate.
        t_trigger = double(data.(device).demods(demod_idx(d)).sample{i}.time.trigger)/config.clockbase;
        t = double(data.(device).demods(demod_idx(d)).sample{i}.timestamp)/config.clockbase;
        R = sqrt(data.(device).demods(demod_idx(d)).sample{i}.x.^2 + data.(device).demods(demod_idx(d)).sample{i}.y.^2);
        % Plot the signal segment, aligned using the trigger time.
        s(d) = plot(t - t_trigger, R, plot_style{d});
        set(s(d), 'LineWidth', 1.2);
    end
    num_triggers_plotted = num_triggers_plotted + 1;
end
plot(get(gca, 'xlim'), [config.trigger_level, config.trigger_level], '--k');
plot([0.0, 0.0], get(gca, 'ylim'), '--k');

num_demods = length(config.demod_c);
title(sprintf('\\bf %d signal segments from %d demodulators', num_triggers_plotted, num_demods))

end


function sampleloss = check_segments_for_sampleloss(data, config)
num_triggers = length(data.(config.device).demods(config.demod_idx(1)).sample);
sampleloss = logical(zeros(1, num_triggers));
for i=1:num_triggers
    for d=config.demod_idx
        % Check if any data is invalid. Unfortunately, sampleloss indicators not
        % implemented on software trigger yet.
        if any(isnan(data.(config.device).demods(config.demod_idx(d)).sample{i}.x)) | ...
                any(isnan(data.(config.device).demods(config.demod_idx(d)).sample{i}.y))
            sampleloss(i) = (1 | sampleloss(i));
        end
        if isempty(data.(config.device).demods(config.demod_idx(d)).sample{i}.timestamp)
            sampleloss(i) = (1 | sampleloss(i));
        end
    end
end
end