Controlling the HF2LI Lock-in with Matlab

Controlling the Zurich Instruments device directly with Matlab is extremely powerful for repetitive measurements and scripting. This blog entry explains a method to communicate with the ziServer directly from Matlab. This interface is very fast and thus well suited to record large data sets at high-speed.
Loading data from a binary file is explained in the blog entry Loading HF2LI Lock-in Measurements with Matlab.

Simple Loop-back Measurement with the Instrument

To perform the measurement connect the HF output of your instrument with the HF input with a short BNC cable. This routes the output signal back to the input of the instrument. Also connect your Zurich Instruments HF2LI device with a USB cable to a computer with the ziServer running. The ziServer is automatically started if the Zurich Instruments driver was installed beforehand.

Next you need to install the Matlab driver ziDAQ for the instrument. You can get the latest version by supplying following information in a support request:

  • Matlab version e.g 7.6
  • Matlab processor architecture e.g. 64Bit (win64)
  • Operating system e.g. Windows 7

The appropriate DLL driver (e.g. ziDAQ.mexw64) needs to be copied in the local directory you are working in. Or you also may add the driver directory to your Matlab path (type help path in Matlab).

Now you are ready to start the example in Matlab.

The interface to the Zurich Instruments lock-in amplifier is based on the function call ziDAQ. The first parameter is used to specify the interface functionality that should be executed. Use the command help ziDAQ to get a help text on all available commands.
The simplest method of accessing demodulator data offers the function call ziDAQ('getSample',...). This function call returns a single sample from the specified demodulator path. Download the file simple_example.m.

function simple_example
clear ziDAQ
device = autoDetect;
% Record one demodulator sample from the specified node.
sample = ziDAQ('getSample', ['/' device '/demods/0/sample']);
% Calculate r from x and y.
r = sqrt(sample.x.^2 + sample.y.^2);
fprintf('Measured rms amplitude %gVn', r);

In order to select the connected device automatically, the following helper function should be copied in the working directory. The function assumes, that only one device is connected to the computer. Download the file autoDetect.m.

function device = autoDetect
nodes = lower(ziDAQ('listNodes', '/'));
dutIndex = strmatch('dev', nodes);
if length(dutIndex) > 1
error('autoDetect does only support a single device configuration.');
elseif isempty(dutIndex)
error('No DUT found. Make sure that the USB cable is connected to the host and the device is turned on.');
% Found only one device -> selection valid.
device = lower(nodes{dutIndex});
fprintf('Will perform measurement for device %s ...n', device)

Polling Demodulator Data

If continuous data recording is required the polling functions are best suited. To select the nodes from which data should be polled, the subscribe / unsubscribe commands are used. The following example demonstrates the synchronous poll usage. Thus, the poll command will block during the specified recording time. Due to the blocking behavior, too long recording times should be avoided as the interface is not responsive during that time.
For recoding times longer than 10s it is recommended to used the poll command inside a loop. With this method continuous data can be recorded over a longer time frame. Internal data buffering on the ziServer ensures that no data is lost between the poll commands. As these buffers are limited in size, the time between poll commands must not be too large for high sampling rates. If extensive calculations should be performed during the poll commands, the asynchronous poll interface may be better suited.
Download the file synchronous_example.m.

function synchronous_example
clear ziDAQ
device = autoDetect;
measureSynchronousFeedback(device, 1, 1e5);

function measureSynchronousFeedback(device, channel, frequency)
c = num2str(channel - 1, '%0d');
% Settings
rate = 200;
tc = 0.001;
ziDAQ('setInt',['/' device '/sigins/' c '/imp50'], int64(1));
ziDAQ('setInt',['/' device '/sigins/' c '/ac'], int64(0));
ziDAQ('setInt',['/' device '/sigins/' c '/diff'], int64(0));
ziDAQ('setDouble',['/' device '/sigins/' c '/range'], 2);
ziDAQ('setInt',['/' device '/sigouts/' c '/on'], int64(1));
ziDAQ('setInt',['/' device '/sigouts/' c '/enables/' c], int64(1));
ziDAQ('setInt',['/' device '/sigouts/' c '/add'], int64(0));
ziDAQ('setDouble',['/' device '/sigouts/' c '/range'], 1);
ziDAQ('setDouble',['/' device '/sigouts/' c '/amplitudes/*'], 0);
ziDAQ('setDouble',['/' device '/sigouts/' c '/amplitudes/' c], 1);
ziDAQ('setDouble',['/' device '/demods/' c '/rate'], rate);
ziDAQ('setDouble',['/' device '/demods/' c '/phaseshift'], 0);
ziDAQ('setInt',['/' device '/demods/' c '/order/'], int64(8));
ziDAQ('setInt',['/' device '/demods/' c '/harmonic/'], int64(1));
ziDAQ('setInt',['/' device '/demods/' c '/adcselect/'], int64(channel - 1));
ziDAQ('setDouble',['/' device '/demods/' c '/timeconstant'], tc);
ziDAQ('setInt',['/' device '/demods/' c '/oscselect'], int64(channel - 1));
ziDAQ('setDouble',['/' device '/oscs/' c '/freq'], frequency);
% Unsubscribe all streaming data
ziDAQ('unsubscribe', '*');
% Clean queue
% Pause to get a settled lowpass filter
ziDAQ('subscribe',['/' device '/demods/' c '/sample']);
d = ziDAQ('poll', 1, int64(200));
ziDAQ('unsubscribe',['/' device '/demods/' c '/sample']);
if ~isempty(d)
device = getfield(d, device);
if device.demods(channel).sample.time.dataloss
fprintf('Sample loss detected.');
r = mean(sqrt(device.demods(channel).sample.x.^2 + device.demods(channel).sample.y.^2));
e = 0.5 / sqrt(2);
fprintf('Measured rms amplitude %gV (expected: %gV)n', r, e);

The function call ziDAQ('poll', ...) returns the following data structure.

sample.timestamp % Time stamp data [uint64]. Divide value by 210e6 to calculate the time stamp in seconds.
sample.x % Demodulator x value in volt [double]
sample.y % Demodulator y value in volt [double]
sample.frequency % Current demodulator frequency in hertz [double].
sample.phase % Phase value [double].
sample.dio % Digital IO data [uint32].
sample.auxin0 % Auxiliary input value of channel 0 in volt [double].
sample.auxin1 % Auxiliary input value of channel 1 in volt [double].
sample.time.dataloss % Indication of sample loss (including block loss).
sample.time.blockloss % Indication of data block loss over the socket connection. This may be the result of a too long break between subsequent poll commands.
sample.time.invalidtimestamp % Indication of invalid time stamp data as a result of a sampling rate change during the measurement.

Vectors that contain NaN’s indicate sample loss. In this case the sample.time.dataloss flag is set.