diff --git a/@MyAvgTrace/MyAvgTrace.m b/@MyAvgTrace/MyAvgTrace.m index 0530cc1..03149b0 100644 --- a/@MyAvgTrace/MyAvgTrace.m +++ b/@MyAvgTrace/MyAvgTrace.m @@ -1,38 +1,113 @@ % Adds averaging capabilities to MyTrace +% +% Averaging type is 'linear' or 'exp' ('exponential'). +% Linear averaging is a simple mean +% x=\sum_{n=0}^N x_n, +% exponential is an unlimited weighted sum +% x=(1-exp(-1/n_avg))*\sum_{n=0}^\inf x_n exp(-n/n_avg). classdef MyAvgTrace < MyTrace - + + properties (Access=public) + % Target number of averages, when it is reached or exceeded + % AveragingDone event is triggered + n_avg=1 + + avg_type='linear' + end + properties (GetAccess=public, SetAccess=protected) % Counter for the averaging function, can be reset by clearData - avg_count=1 + avg_count=0 end - methods + events + AveragingDone + end + + methods (Access=public) + + % Adds data to the average accumulator. When the averaging counter + % exceeds n_avg, a notification is issued, but the averaging is not + % stopped. function addAverage(this, b) + assert(isa(b,'MyTrace') || (isvector(b) && isnumeric(b)), ... + ['Second argument must be a MyTrace object or ', ... + 'a numeric vector']) if isa(b,'MyTrace') - checkArithmetic(this,b); - this.y=(this.y*this.avg_count+b.y)/(this.avg_count+1); - elseif isnumeric(b) - assert(isvector(b) && length(b)==length(this.y), ... - ['Numeric array must be a vector of the same ', ... - 'length as y data of MyTrace in order to perform ', ... - 'averanging']) - this.y=(this.y*this.avg_count+b)/(this.avg_count+1); + % The new data is supplied as MyTrace + new_y=b.y; else - error(['Second argument must be a MyTrace object or ', ... - 'a numerical vector of the same length as y data ', ... - 'of MyTrace in order to perform averanging']) + % The new data is supplied directly as a numeric vector + new_y=b(:); + end + + if isempty(this) + % Initialize new data and return + this.y=new_y; + this.avg_count=1; + return end + assert(length(new_y)==length(this.y), ... + ['New vector of y values must be of the same', ... + 'length as the exisiting y data of MyTrace in ', ... + 'order to perform averanging']) + this.avg_count=this.avg_count+1; + + switch this.avg_type + case 'linear' + this.y = (this.y*(this.avg_count-1)+new_y)/... + this.avg_count; + case 'exp' + this.y = new_y*(1-exp(-1/this.n_avg))+ ... + this.y*exp(-1/this.n_avg); + otherwise + error('Averaging type %s is not supported', ... + this.avg_type) + end + + if this.avg_count>=this.n_avg + triggerAveragingDone(this); + end + end + + % Provide restricted access to the trace averaging counter + function resetCounter(this) + this.avg_count=0; end + function triggerAveragingDone(this) + notify(this, 'AveragingDone') + end + + % Overload clearData so that it reset the averaging counter in + % addition to clearing the x and y values function clearData(this) this.x=[]; this.y=[]; - this.avg_count=1; + resetCounter(this); + end + end + + %% Set and get methods + + methods + + function set.avg_type(this, val) + assert(strcmpi(val, 'linear')||strcmpi(val, 'exp')|| ... + strcmpi(val, 'exponential'), ... + 'Averaging type must be ''linear'' or ''exp''') + this.avg_type=val; end + + function set.n_avg(this, val) + % The number of averages should be integer not smaller than one + this.n_avg=max(1, round(val)); + end + end end diff --git a/@MyInstrument/MyInstrument.m b/@MyInstrument/MyInstrument.m index 1dbffff..dcd559e 100644 --- a/@MyInstrument/MyInstrument.m +++ b/@MyInstrument/MyInstrument.m @@ -1,253 +1,249 @@ % Generic class to implement communication with instruments classdef MyInstrument < dynamicprops % Access for these variables is 'protected' and in addition % granted to MyClassParser in order to use construction parser properties (GetAccess=public, SetAccess={?MyClassParser,?MyInstrument}) % name is sometimes used as identifier in listeners callbacks, so % it better not to be changed after the instrument object is % created name=''; interface=''; address=''; end properties (Access=public) Device %Device communication object Trace %MyTrace object for storing data end properties (GetAccess=public, SetAccess=protected) idn_str=''; % identification string end properties (Constant=true) % Default parameters for device connection DEFAULT_INP_BUFF_SIZE = 1e7; % buffer size bytes DEFAULT_OUT_BUFF_SIZE = 1e7; % buffer size bytes DEFAULT_TIMEOUT = 10; % Timeout in s end events NewData PropertyRead end methods (Access=public) function this=MyInstrument(interface, address, varargin) P=MyClassParser(); addRequired(P,'interface',@ischar); addRequired(P,'address',@ischar); addParameter(P,'name','',@ischar); processInputs(P, this, interface, address, varargin{:}); % Create an empty trace this.Trace=MyTrace(); % Create dummy device object that supports properties this.Device=struct(); this.Device.Status='not connected'; % Interface and address can correspond to an entry in the list % of local instruments. Read this entry in such case. if strcmpi(interface, 'instr_list') % load the InstrumentList structure InstrumentList = getLocalSettings('InstrumentList'); % In this case 'address' is the instrument name in % the list instr_name = address; if ~isfield(InstrumentList, instr_name) error('%s is not a field of InstrumentList',... instr_name); end if ~isfield(InstrumentList.(instr_name), 'interface') error(['InstrumentList entry ', instr_name,... ' has no ''interface'' field']); else this.interface = InstrumentList.(instr_name).interface; end if ~isfield(InstrumentList.(instr_name), 'address') error(['InstrumentList entry ', instr_name,... ' has no ''address'' field']); else this.address = InstrumentList.(instr_name).address; end % Assign name automatically, but not overwrite if % already specified if isempty(this.name) this.name = instr_name; end end % Connecting device creates a communication object, % but does not attempt communication connectDevice(this); end function delete(this) %Closes the connection to the device closeDevice(this); %Deletes the device object try delete(this.Device); catch end end %Trigger event signaling the acquisition of a new trace. %Any properties of MyNewDataEvent can be set by indicating the %corresponding name-value pars in varargin. For the list of options %see the definition of MyNewDataEvent. function triggerNewData(this, varargin) EventData = MyNewDataEvent(varargin{:}); EventData.Instr=this; - % An option to suppress collection of new header so that - % NewData can be used to transfer previously acquired trace - % to Daq - notify(this,'NewData',EventData); end %Triggers event for property read from device function triggerPropertyRead(this) notify(this,'PropertyRead') end % Read all the relevant instrument properties and return as a % MyMetadata object. % Dummy method that needs to be re-defined by a parent class function Hdr=readHeader(this) Hdr=MyMetadata(); % Generate valid field name from instrument name if present and % class name otherwise if ~isempty(this.name) field_name=genvarname(this.name); else field_name=class(this); end addField(Hdr, field_name); % Add identification string as parameter addParam(Hdr, field_name, 'idn', this.idn_str); end %% Connect, open, configure, identificate and close the device % Connects to the device, explicit indication of interface and % address is for ability to handle instr_list as interface function connectDevice(this) int_list={'constructor','visa','tcpip','serial'}; if ~ismember(lower(this.interface), int_list) warning(['Device is not connected, unknown interface ',... this.interface,'. Valid interfaces are ',... '''constructor'', ''visa'', ''tcpip'' and ''serial''']) return end try switch lower(this.interface) % Use 'constructor' interface to connect device with % more that one parameter, specifying its address case 'constructor' % in this case the 'address' is a command % (ObjectConstructorName), e.g. as returned by the % instrhwinfo, that creates communication object % when executed this.Device=eval(this.address); case 'visa' % visa brand is 'ni' by default this.Device=visa('ni', this.address); case 'tcpip' % Works only with default socket. Use 'constructor' % if socket or other options need to be specified this.Device=tcpip(this.address); case 'serial' this.Device=serial(this.address); otherwise error('Unknown interface'); end configureDeviceDefault(this); catch warning(['Device is not connected, ',... 'error while creating communication object.']); end end % Opens the device if it is not open. Does not throw error if % device is already open for communication with another object, but % tries to close existing connections instead. function openDevice(this) if ~isopen(this) try fopen(this.Device); catch % try to find and close all the devices with the same % VISA resource name try instr_list=instrfind('RsrcName',this.Device.RsrcName); fclose(instr_list); fopen(this.Device); warning('Multiple instrument objects of address %s exist',... this.address); catch error('Could not open device') end end end end % Closes the connection to the device function closeDevice(this) if isopen(this) fclose(this.Device); end end function configureDeviceDefault(this) dev_prop_list = properties(this.Device); if ismember('OutputBufferSize',dev_prop_list) this.Device.OutputBufferSize = this.DEFAULT_OUT_BUFF_SIZE; end if ismember('InputBufferSize',dev_prop_list) this.Device.InputBufferSize = this.DEFAULT_INP_BUFF_SIZE; end if ismember('Timeout',dev_prop_list) this.Device.Timeout = this.DEFAULT_TIMEOUT; end end % Checks if the connection to the device is open function bool=isopen(this) try bool=strcmp(this.Device.Status, 'open'); catch warning('Cannot access device Status property'); bool=false; end end %% Identification % Attempt communication and identification of the device function [str, msg]=idn(this) was_open=isopen(this); try openDevice(this); [str,~,msg]=query(this.Device,'*IDN?'); catch ErrorMessage str=''; msg=ErrorMessage.message; end % Remove carriage return and new line symbols from the string newline_smb={sprintf('\n'),sprintf('\r')}; %#ok str=replace(str, newline_smb,' '); this.idn_str=str; % Leave device in the state it was in the beginning if ~was_open try closeDevice(this); catch end end end end end \ No newline at end of file diff --git a/@MyZiRingdown/MyZiRingdown.m b/@MyZiRingdown/MyZiRingdown.m index 6207b3d..1eda370 100644 --- a/@MyZiRingdown/MyZiRingdown.m +++ b/@MyZiRingdown/MyZiRingdown.m @@ -1,752 +1,802 @@ % Class for performing ringdown measurements of mechanical oscillators % using Zurich Instruments UHF or MF lock-in. % % Operation: sweep the driving tone (drive_osc) using the sweep module % in LabOne web user interface, when the magnitude of the demodulator % signal exceeds trig_threshold the driving tone is switched off and % the recording of demodulated signal is started, the signal is recorded % for the duration of record_time. % % Features: % Adaptive measurement oscillator frequency % Averaging -% Output signal +% +% Auto saving +% +% Auxiliary output signal: If enable_aux_out=true +% then after a ringdown is started a sequence of pulses is applied +% to the output consisting of itermittent on and off periods +% starting from on. classdef MyZiRingdown < handle properties (Access=public) % Ringdown is recorded if the signal in the triggering demodulation % channel exceeds this value trig_threshold=1e-3 % V % Duration of the recorded ringdown - record_time=1 % s + record_time=1 % (s) % If enable_trig is true, then the drive is on and the acquisition % of record is triggered when signal exceeds trig_threshold enable_trig=false + % Auxiliary output signal during ringdown. + enable_aux_out=false % If auxiliary output is applied + % time during which the output is in aux_out_on_lev state + aux_out_on_t=1 % (s) + % time during which the output is in aux_out_off_lev state + aux_out_off_t=1 % (s) + % Average the trace over n points to reduce amount of stored data % while keeping the demodulator bandwidth large downsample_n=1 fft_length=128 - n_avg=1 % number of ringdowns to be averaged + n_avg=1 % number of ringdowns to be averaged + + auto_save=false % if all ringdowns should be automatically saved % In adaptive measurement oscillator mode the oscillator frequency % is continuously changed to follow the signal frequency during % ringdown acquisition. This helps against the oscillator frequency % drift. adaptive_meas_osc=false end % The properties which are read or set only once during the class % initialization properties (GetAccess=public, SetAccess={?MyClassParser,?MyZiRingdown}) name='ziRingdown' dev_serial='dev4090' % enumeration for demodulators, oscillators and output starts from 1 demod=1 % demodulator used for both triggering and measurement % Enumeration in the node structure starts from 0, so, for example, % the default path to the trigger demodulator refers to the % demodulator #1 demod_path='/dev4090/demods/0' drive_osc=1 meas_osc=2 % Signal input, integers above 1 correspond to main inputs, aux % input etc. (see the user interface for device-specific details) signal_in=1 drive_out=1 % signal output used for driving - % Auxiliary channel used for the output of triggering signal, - % primarily intended to switch the measurement apparatus off during - % a part of the ringdown and thus allow for free evolution of - % the oscillator during that period. + % Number of an auxiliary channel used for the output of triggering + % signal, primarily intended to switch the measurement apparatus + % off during a part of the ringdown and thus allow for free + % evolution of the oscillator during that period. aux_out=1 + aux_out_on_lev=1 % (V), output trigger on level aux_out_off_lev=0 % (V), output trigger off level % Device clock frequency, i.e. the number of timestamps per second clockbase % The string that specifies the device name as appears % in the server's node tree. Can be the same as dev_serial. dev_id % Device information string containing the data returned by % ziDAQ('discoveryGet', ... idn_str % Poll duration of 1 ms practically means that ziDAQ('poll', ... % returns immediately with the data accumulated since the % previous function call. poll_duration = 0.001 % s poll_timeout = 50 % ms % Margin for adaptive oscillator frequency adjustment - oscillator % follows the signal if the dispersion of frequency in the % demodulator band is below ad_osc_margin times the demodulation % bandwidth (under the condition that adaptive_meas_osc=true) ad_osc_margin = 0.1 end % Internal variables properties (GetAccess=public, SetAccess=protected) recording=false % true if a ringdown is being recorded % true if adaptive measurement oscillator mode is on and if the % measurement oscillator is actually actively following. ad_osc_following=false - n_avg_completed=0 - % Reference timestamp at the beginning of measurement record. % Stored as uint64. t0 elapsed_t=0 % Time elapsed since the last recording was started Trace % MyTrace object storing the ringdown DemodSpectrum % MyTrace object to store FFT of the demodulator data end % Setting or reading the properties below automatically invokes % communication with the device properties (Dependent=true) drive_osc_freq meas_osc_freq drive_on % true when the dirive output is on % demodulator sampling rate (as transferred to the computer) demod_rate % The properties below are only used within the program to display % the information about device state. drive_amp % (V), peak-to-peak amplitude of the driving tone lowpass_order % low-pass filter order lowpass_bw % low-pass filter bandwidth end % Other dependent variables that are dont device properties properties (Dependent=true) % Downsample the measurement record to reduce the amount of data % while keeping the large demodulation bandwidth. % (samples/s), sampling rate of the trace after avraging downsampled_rate % number of the oscillator presently in use with the demodulator current_osc % true/false, true if the signal output from aux out is in on state aux_out_on fft_rbw % resolution bandwidth of fft end properties (Access=private) PollTimer + AuxOutOffTimer % Timer responsible for switching the aux out off + AuxOutOnTimer % Timer responsible for switching the aux out on + % Demodulator samples z(t) stored to continuously calculate % spectrum, values of z are complex here, z=x+iy. % osc_freq is the demodulation frequency DemodRecord=struct('t',[],'z',[],'osc_freq',[]) - TraceAvg % MyTrace object used for averaging ringdowns + AvgTrace % MyAvgTrace object used for averaging ringdowns end events % Event for communication with Daq that signals the acquisition of % a new ringdown NewData % New demodulator samples received NewDemodSample % Device settings changed, used mostly for syncronization with Gui NewSetting end methods (Access=public) %% Constructor and destructor function this = MyZiRingdown(dev_serial, varargin) P=MyClassParser(this); % Poll timer period addParameter(P,'poll_period',0.1,@isnumeric); processInputs(P, varargin{:}); % Create and configure trace objects this.Trace=MyTrace(... 'name_x','Time',... 'unit_x','s',... 'name_y','Magnitude r',... 'unit_y','V'); this.DemodSpectrum=MyTrace(... 'name_x','Frequency',... 'unit_x','Hz',... 'name_y','PSD',... 'unit_y','V^2/Hz'); + this.AvgTrace=MyAvgTrace(); % Set up the poll timer. Using a timer for anyncronous % data readout allows to use the wait time for execution % of other programs. % Fixed spacing is preferred as it is the most robust mode of % operation when keeping the intervals between callbacks % precisely defined is not the biggest concern. this.PollTimer=timer(... 'ExecutionMode','fixedSpacing',... 'Period',P.Results.poll_period,... 'TimerFcn',@(~,~)pollTimerCallback(this)); + % Aux out timers use fixedRate mode for more precise timing. + % First timer is executed periodically to switch the auxiliary + % output off + this.AuxOutOffTimer=timer(... + 'ExecutionMode','fixedRate',... + 'TimerFcn',@(~,~)auxOutOffTimerCallback(this)); + % Second timer is executed periodically to switch the auxiliary + % output on + this.AuxOutOnTimer=timer(... + 'ExecutionMode','fixedRate',... + 'TimerFcn',@(~,~)auxOutOffTimerCallback(this)); + % Check the ziDAQ MEX (DLL) and Utility functions can be found in Matlab's path. if ~(exist('ziDAQ', 'file') == 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 % Create an API session and connect to the correct Data Server. % This is a high level function that uses ziDAQ('connect',.. % and ziDAQ('connectDevice', ... when necessary apilevel=6; [this.dev_id,~]=ziCreateAPISession(dev_serial, apilevel); % Read the divice clock frequency this.clockbase = ... double(ziDAQ('getInt',['/',this.dev_id,'/clockbase'])); end function delete(this) % delete function should never throw errors, so protect % statements with try-catch try stopPoll(this) catch warning(['Could not usubscribe from the demodulator ', ... 'or stop the poll timer.']) end try delete(this.PollTimer) catch warning('Could not delete the poll timer.') end end %% Other methods function startPoll(this) % Configure the oscillators, demodulator and driving output % -1 accounts for the difference in enumeration conventions % in the software names (starting from 1) and node numbers % (starting from 0) this.demod_path = sprintf('/%s/demods/%i', ... this.dev_id, this.demod-1); % Set the data transfer rate so that it satisfies the Nyquist % criterion (>x2 the bandwidth of interest) this.demod_rate=4*this.lowpass_bw; % Configure the demodulator. Signal input: ziDAQ('setInt', ... [this.demod_path,'/adcselect'], this.signal_in-1); % Oscillator: ziDAQ('setInt', ... [this.demod_path,'/oscselect'], this.drive_osc-1); % Enable data transfer from the demodulator to the computer ziDAQ('setInt', [this.demod_path,'/enable'], 1); % Configure the signal output - disable all the oscillator % contributions including the driving tone since we start % form 'enable_trig=false' state path = sprintf('/%s/sigouts/%i/enables/*', ... this.dev_id, this.drive_out-1); ziDAQ('setInt', path, 0); % Configure the auxiliary trigger output - put it in the manual % mode so it does not output demodulator readings path=sprintf('/%s/auxouts/%i/outputselect', ... this.dev_id, this.aux_out-1); ziDAQ('setInt', path, -1); % Subscribe to continuously receive samples from the % demodulator. Samples accumulated between timer callbacks % will be read out using ziDAQ('poll', ... ziDAQ('subscribe',[this.demod_path,'/sample']); % Start continuous polling start(this.PollTimer) end function stopPoll(this) stop(this.PollTimer) ziDAQ('unsubscribe',[this.demod_path,'/sample']); end % Main function that polls data from the device demodulator function pollTimerCallback(this) % ziDAQ('poll', ... with short poll_duration returns % immediately with the data accumulated since the last timer % callback Data = ziDAQ('poll', this.poll_duration, this.poll_timeout); if ziCheckPathInData(Data, [this.demod_path,'/sample']) % Demodulator returns data DemodSample= ... Data.(this.dev_id).demods(this.demod).sample; % Append new samples to the record and recalculate spectrum appendSamplesToBuff(this, DemodSample); calcfft(this); if this.recording % If recording is under way, append the new samples to % the trace rec_finished = appendSamplesToTrace(this, DemodSample); % Update elapsed time this.elapsed_t=this.Trace.x(end); % If the adaptive measurement frequency mode is on, % update the measurement oscillator frequency. % Make sure that the demodulator record actually % contains signal by comparing the dispersion of % frequency to demodulator bandwidth. if this.adaptive_meas_osc [df_avg, df_dev]=calcfreq(this); if df_dev < this.ad_osc_margin*this.lowpass_bw this.meas_osc_freq=df_avg; % Change indicator this.ad_osc_following=true; else this.ad_osc_following=false; end else this.ad_osc_following=false; end else r=sqrt(DemodSample.x.^2+DemodSample.y.^2); if this.enable_trig && max(r)>this.trig_threshold % Start acquisition of a new trace if the maximum % of the signal exceeds threshold this.recording=true; % Find index at which the threshold was % exceeded ind0=find(r>this.trig_threshold,1,'first'); this.t0=DemodSample.timestamp(ind0); this.elapsed_t=0; % Switch the drive off this.drive_on=false; % Set the measurement oscillator frequency to be % the frequency at which triggering occurred this.meas_osc_freq=this.drive_osc_freq; % Switch the oscillator this.current_osc=this.meas_osc; + % Optionally start the auxiliary output timers + if this.enable_aux_out + % Configure measurement periods and delays + T=this.aux_out_on_t+this.aux_out_on_t; + this.AuxOutOffTimer.Period=T; + this.AuxOutOnTimer.Period=T; + + this.AuxOutOffTimer.startDelay=... + this.aux_out_on_t; + this.AuxOutOnTimer.startDelay=T; + + % Start timers + start(this.AuxOutOffTimer + start(this.AuxOutOnTimer + end + % Clear trace and append new data starting from the % index, at which triggering occurred clearData(this.Trace); rec_finished = ... appendSamplesToTrace(this, DemodSample, ind0); else rec_finished=false; end % Indicator for adaptive measurement is off, since % recording is not under way this.ad_osc_following=false; end notify(this,'NewDemodSample'); % Stop recording if a record was finished if rec_finished % stop recording this.recording=false; this.ad_osc_following=false; % Switch the oscillator this.current_osc=this.drive_osc; % Downsample the trace to reduce the amount of data downsample(this.Trace, this.downsample_n, 'avg'); % Do trace averaging - this.TraceAvg=this.TraceAvg+this.trace; - this.n_avg_completed=this.n_avg_completed+1; + addAverage(this.AvgTrace, this.Trace); - triggerNewData(this); + triggerNewData(this, 'save', this.auto_save); - if this.n_avg_completed>=this.n_avg - % Normalize the average trace - this.TraceAvg=this.TraceAvg/n; + if this.AvgTrace.avg_count>=this.n_avg % Do not enable acquisition after a ringdown is % recorded to prevent possible overwriting this.enable_trig=false; % Signal new data one more time to transfer the % average trace - this.Trace=copy(this.TraceAvg); + this.Trace.x=this.AvgTrace.x; + this.Trace.y=this.AvgTrace.y; + triggerNewData(this); else % Restart acquisition of a new trace this.enable_trig=true; this.drive_on=true; end end end end % Append timestamps vs r=sqrt(x^2+y^2) to the measurement record. % Starting index can be supplied as varargin. % The output variable tells if the record is finished. function isfin = appendSamplesToTrace(this, DemodSample, varargin) if isempty(varargin) startind=1; else startind=varargin{1}; end r=sqrt(DemodSample.x(startind:end).^2 + ... DemodSample.y(startind:end).^2); % Subtract the reference time, convert timestamps to seconds ts=double(DemodSample.timestamp(startind:end) -... this.t0)/this.clockbase; % Check if recording should be stopped isfin=(ts(end)>=this.record_time); if isfin % Remove excess data points from the new data ind=(tsflen this.DemodRecord.t = this.DemodRecord.t(end-flen+1:end); this.DemodRecord.z = this.DemodRecord.z(end-flen+1:end); end end function calcfft(this) flen=min(this.fft_length, length(this.DemodRecord.t)); [freq, spectr]=xyFourier( ... this.DemodRecord.t(end-flen+1:end), ... this.DemodRecord.z(end-flen+1:end)); this.DemodSpectrum.x=freq; this.DemodSpectrum.y=abs(spectr).^2; end % Calculate the average frequency and dispersion of the demodulator % signal function [f_avg, f_dev]=calcfreq(this) if ~isempty(this.DemodSpectrum) norm=sum(this.DemodSpectrum.y); % Calculate the center frequency of the spectrum f_avg=dot(this.DemodSpectrum.x, this.DemodSpectrum.y)/norm; % Shift the FFT center by the demodulation frequency to % output absolute value f_avg=f_avg+mean(this.DemodRecord.osc_freq); f_dev=sqrt(dot(this.DemodSpectrum.x.^2, ... this.DemodSpectrum.y)/norm-f_avg^2); else f_avg=[]; f_dev=[]; end end function str=idn(this) DevProp=ziDAQ('discoveryGet', this.dev_id); str=this.dev_id; if isfield(DevProp, 'devicetype') str=[str,'; device type: ', DevProp.devicetype]; end if isfield(DevProp, 'options') % Print options from the list as comma-separated values and % discard the last comma. opt_str=sprintf('%s,',DevProp.options{:}); str=[str,'; options: ', opt_str(1:end-1)]; end if isfield(DevProp, 'serverversion') str=[str,'; server version: ', DevProp.serverversion]; end this.idn_str=str; end - % Provide restricted access to the trace averaging counter - function resetAveraging(this) - this.n_avg_completed=0; + function triggerNewData(this, varargin) + EventData = MyNewDataEvent(varargin{:}); + EventData.Instr=this; + notify(this,'NewData',EventData); + end + + function auxOutOffTimerCallback(this) + this.aux_out_on=false; end - function triggerNewData(this) - notify(this,'NewData') + function auxOutOnTimerCallback(this) + this.aux_out_on=true; end function Hdr=readHeader(this) Hdr=MyMetadata(); % Generate valid field name from instrument name if present and % class name otherwise if ~isempty(this.name) field_name=genvarname(this.name); else field_name=class(this); end addField(Hdr, field_name); % Add identification string addParam(Hdr, field_name, 'idn', this.idn_str); % Add the measurement configuration addParam(Hdr, field_name, 'demod', this.demod, ... 'comment', 'Demodulator number (starting from 1)'); addParam(Hdr, field_name, 'drive_osc', this.drive_osc, ... 'comment', 'Swept oscillator number'); addParam(Hdr, field_name, 'meas_osc', this.meas_osc, ... 'comment', 'Measurement oscillator number'); addParam(Hdr, field_name, 'signal_in', this.signal_in, ... 'comment', 'Singnal input number'); addParam(Hdr, field_name, 'drive_out', this.drive_out, ... 'comment', 'Driving output number'); addParam(Hdr, field_name, 'clockbase', this.clockbase, ... 'comment', ['Device clock frequency, i.e. the number ', ... 'of timestamps per second']); addParam(Hdr, field_name, 'drive_amp', this.drive_amp, ... 'comment', '(V) peak to peak'); addParam(Hdr, field_name, 'meas_osc_freq', ... this.meas_osc_freq, 'comment', '(Hz)'); addParam(Hdr, field_name, 'trig_threshold', ... this.drive_threshold, 'comment', '(V)'); addParam(Hdr, field_name, 'record_time', ... this.record_time, 'comment', '(s)'); addParam(Hdr, field_name, 'lowpass_order', ... this.lowpass_order, 'comment', ... 'Order of the demodulator low-pass filter'); addParam(Hdr, field_name, 'lowpass_bw', this.lowpass_bw, ... 'comment', ['(Hz), 3 dB bandwidth of the demodulator ', ... 'low-pass filter']); addParam(Hdr, field_name, 'demod_rate', this.demod_rate, ... 'comment', '(samples/s), demodulator data transfer rate'); addParam(Hdr, field_name, 'downsampled_rate', ... this.downsampled_rate, 'comment', ... '(samples/s), downsampling with averaging'); addParam(Hdr, field_name, 'poll_duration', ... this.poll_duration, 'comment', '(s)'); addParam(Hdr, field_name, 'poll_timeout', ... this.poll_timeout, 'comment', '(ms)'); end end %% Set and get methods. methods function freq=get.drive_osc_freq(this) path=sprintf('/%s/oscs/%i/freq', this.dev_id, this.drive_osc-1); freq=ziDAQ('getDouble', path); end function set.drive_osc_freq(this, val) assert(isfloat(val), ... 'Oscillator frequency must be a floating point number') path=sprintf('/%s/oscs/%i/freq', this.dev_id, this.drive_osc-1); ziDAQ('setDouble', path, val); notify(this,'NewSetting'); end function freq=get.meas_osc_freq(this) path=sprintf('/%s/oscs/%i/freq', this.dev_id, this.meas_osc-1); freq=ziDAQ('getDouble', path); end function set.meas_osc_freq(this, val) assert(isfloat(val), ... 'Oscillator frequency must be a floating point number') path=sprintf('/%s/oscs/%i/freq', this.dev_id, this.meas_osc-1); ziDAQ('setDouble', path, val); notify(this,'NewSetting'); end function set.drive_on(this, val) path=sprintf('/%s/sigouts/%i/on',this.dev_id,this.drive_out-1); % Use double() to convert from logical type ziDAQ('setInt', path, double(val)); notify(this,'NewSetting'); end function bool=get.drive_on(this) path=sprintf('/%s/sigouts/%i/on',this.dev_id,this.drive_out-1); bool=logical(ziDAQ('getInt', path)); end function set.current_osc(this, val) assert((val==this.drive_osc) || (val==this.meas_osc), ... ['The number of current oscillator must be that of ', ... 'the drive or measurement oscillator, not ', num2str(val)]) ziDAQ('setInt', [this.demod_path,'/oscselect'], val-1); notify(this,'NewSetting') end function osc_num=get.current_osc(this) osc_num=double(ziDAQ('getInt', ... [this.demod_path,'/oscselect']))+1; end function amp=get.drive_amp(this) path=sprintf('/%s/sigouts/%i/amplitudes/%i', ... this.dev_id, this.drive_out-1, this.drive_osc-1); amp=ziDAQ('getDouble', path); end function set.drive_amp(this, val) path=sprintf('/%s/sigouts/%i/amplitudes/%i', ... this.dev_id, this.drive_out-1, this.drive_osc-1); ziDAQ('setDouble', path, val); notify(this,'NewSetting'); end function set.lowpass_order(this, val) assert(any(val==[1,2,3,4,5,6,7,8]), ['Low-pass filter ', ... 'order must be an integer between 1 and 8']) ziDAQ('setInt', [this.demod_path,'/order'], val); notify(this,'NewSetting'); end function n=get.lowpass_order(this) n=ziDAQ('getInt', [this.demod_path,'/order']); end function bw=get.lowpass_bw(this) tc=ziDAQ('getDouble', [this.demod_path,'/timeconstant']); bw=ziBW2TC(tc, this.lowpass_order); end function set.lowpass_bw(this, val) tc=ziBW2TC(val, this.lowpass_order); ziDAQ('setDouble', [this.demod_path,'/timeconstant'], tc); notify(this,'NewSetting'); end function rate=get.demod_rate(this) rate=ziDAQ('getDouble', [this.demod_path,'/rate']); end function set.demod_rate(this, val) ziDAQ('setDouble', [this.demod_path,'/rate'], val); notify(this,'NewSetting'); end function set.downsample_n(this, val) n=round(val); assert(n>=1, ['Number of points for trace averaging must ', ... 'be greater than 1']) this.downsample_n=n; notify(this,'NewSetting'); end function set.aux_out_on(this, bool) path=sprintf('/%s/auxouts/%i/offset', ... this.dev_id, this.aux_out-1); if bool out_offset=this.aux_out_on_lev; else out_offset=this.aux_out_off_lev; end ziDAQ('setDouble', path, out_offset); end function bool=get.aux_out_on(this) path=sprintf('/%s/auxouts/%i/offset', ... this.dev_id, this.aux_out-1); val=ziDAQ('getDouble', path); % Signal from the auxiliary output is continuous, we make the % binary decision about the output state depending on if % the signal is closer to the ON or OFF level bool=(abs(val-this.aux_out_on_lev) < ... abs(val-this.aux_out_off_lev)); end function set.downsampled_rate(this, val) dr=this.demod_rate; if val>dr % Downsampled rate should not exceed the data transfer rate val=dr; end % Round so that the averaging is done over an integer number of % points this.downsample_n=round(dr/val); notify(this,'NewSetting'); end function val=get.downsampled_rate(this) val=this.demod_rate/this.downsample_n; end function set.fft_length(this, val) if val<1 val=1; end % Round val to the nearest 2^n to make the calculation of % Fourier transform efficient n=round(log2(val)); this.fft_length=2^n; notify(this,'NewSetting'); end function val=get.fft_rbw(this) val=this.demod_rate/this.fft_length; end function set.fft_rbw(this, val) assert(val>0,'FFT resolution bandwidth must be greater than 0') % Rounding of fft_length to the nearest integer is handled by % its own set method this.fft_length=this.demod_rate/val; notify(this,'NewSetting'); end function set.n_avg(this, val) % Number of averages needs to be integer and greater than one if val<1 val=1; end this.n_avg=round(val); notify(this,'NewSetting'); end end end