diff --git a/@MyAvgTrace/MyAvgTrace.m b/@MyAvgTrace/MyAvgTrace.m index 4aa6967..224daed 100644 --- a/@MyAvgTrace/MyAvgTrace.m +++ b/@MyAvgTrace/MyAvgTrace.m @@ -1,142 +1,154 @@ % Adds averaging capabilities to MyTrace % % The averaging type is 'lin' (or 'linear')/ 'exp' (or '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='lin' end properties (GetAccess=public, SetAccess=protected) % Counter for the averaging function, can be reset by clearData avg_count=0 end methods (Access=public) % Adds data to the average accumulator. When the averaging counter % reaches n_avg (or exceeds it in the exponential case), completed % is set to 'true', otherwise 'false'. % In exponential regime the averaging proceeds indefinitely so that % avg_count can exceed n_avg. % In linear regime when the averaging counter exceeds n_avg, new % data is discarded. function completed = addAverage(this, b) assert(isa(b,'MyTrace'), ['Second argument must be a ' ... 'MyTrace object']); - if isempty(this) + if isempty(this) || length(this.x)~=length(b.x) || ... + any(this.x~=b.x) % Initialize new data and return this.x=b.x; this.y=b.y; this.name_x=b.name_x; this.unit_x=b.unit_x; this.name_y=b.name_y; this.unit_y=b.unit_y; this.avg_count=1; completed=(this.avg_count>=this.n_avg); return end assert(length(b.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']) switch this.avg_type case 'lin' if this.avg_count=this.n_avg); otherwise error('Averaging type %s is not supported', ... this.avg_type) end - - completed=(this.avg_count>=this.n_avg); end % Provide restricted access to the trace averaging counter function resetCounter(this) this.avg_count=0; 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=[]; resetCounter(this); end % Extend the info stored in trace metadata compare to MyTrace function Mdt=makeMetadata(this) Mdt=makeMetadata@MyTrace(this); addParam(Mdt,'Info','avg_type',this.avg_type, ... 'comment','Averaging type, linear or exponential'); addParam(Mdt,'Info','avg_count',this.avg_count, 'comment', ... 'Number of accomplished averages'); addParam(Mdt,'Info','n_avg',this.n_avg, 'comment', ... ['Target number of averages (lin) or exponential ' ... 'averaging constant (exp)']); end function setFromMetadata(this, Mdt) setFromMetadata@MyTrace(this, Mdt) if isfield(Mdt.Info, 'avg_type') this.avg_type=Mdt.Info.avg_type.value; end if isfield(Mdt.Info, 'n_avg') this.n_avg=Mdt.Info.n_avg.value; end if isfield(Mdt.Info, 'avg_count') this.avg_count=Mdt.Info.avg_count.value; end end end %% Set and get methods methods % Ensure the supplied value for averaging mode is assigned in its % standard form - lowercase and abbreviated function set.avg_type(this, val) + old_val=this.avg_type; + switch lower(val) case {'lin', 'linear'} this.avg_type='lin'; case {'exp', 'exponential'} this.avg_type='exp'; otherwise error(['Averaging type must be ''lin'' ' ... '(''linear'') or ''exp'' (''exponential'')']) end + % Clear data if the averaging type was changed + if this.avg_type~=old_val + clearData(this); + end 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/@MyZiLi/MyZiLi.m b/@MyZiLi/MyZiLi.m index 8bc9aec..ca9db62 100644 --- a/@MyZiLi/MyZiLi.m +++ b/@MyZiLi/MyZiLi.m @@ -1,88 +1,88 @@ % A generic class for programs based on Zurich Instruments UHFLI and MFLI % lock-in amplifiers classdef MyZiLi < handle properties (GetAccess=public, SetAccess={?MyClassParser, ?MyZiLi}) dev_serial='dev4090' % 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 % Device clock frequency, i.e. the number of timestamps per second clockbase end events NewSetting % Device settings changed end methods - function this=MyZiLi(dev_serial, varargin) + function this=MyZiLi(dev_serial) % 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 % Do not throw errors in the constructor to allow creating an % instance when the physical device is disconnected try % 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'])); catch ME warning(ME.message) 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 function Hdr=readHeader(this) Hdr=MyMetadata(); % name is always a valid variable as ensured by its set method addField(Hdr, this.name); % Instrument identification addParam(Hdr, this.name, 'idn', this.idn_str); addObjProp(Hdr, this, 'clockbase', 'comment', ... ['Device clock frequency, i.e. the number of ', ... 'timestamps per second']); end end end diff --git a/@MyZiScopeFt/MyZiScopeFt.m b/@MyZiScopeFt/MyZiScopeFt.m index 7a5edf4..7fccbef 100644 --- a/@MyZiScopeFt/MyZiScopeFt.m +++ b/@MyZiScopeFt/MyZiScopeFt.m @@ -1,198 +1,223 @@ % Spectrum analyzer based on Zurich Instruments UHFLI or MFLI classdef MyZiScopeFt < MyZiLi & MyDataSource properties (Access=public) end properties (GetAccess=public, SetAccess={?MyClassParser}) - n_scope=1 % number of scope node + n_scope=1 % number of hardware scope + n_ch=1 % number of scope channel % Input numbers between 1 and 148 correspond to various signals % including physical inputs, outputs, demodulator channels and % results of arthmetic operations. See the LabOne user interface % for the complete list of choices and corresponding numbers. % This number is shifted by +1 compare to the hardware node % enumeration as usual. signal_in=1 + + % Deas time between scope frame acquisitions. Smaller time results + % in faster averaging but may not look nice during real time + % gui update. + trigholdoff=0.02 % seconds end properties (Access=private) scope_module % 'handle' (in quotes) to a ZI software scope module PollTimer % Timer that regularly reads data drom the scope TmpTrace % Temporary variable used for averaging end properties (Dependent=true) scope_path scope_rate % samples/sec n_pt % length of scope wave fft_rbw % Spacing between fft bins end events - NewWaves % Triggered when + NewWave % Triggered when the scope acquires new waves end methods (Access=public) function this = MyZiScopeFt(dev_serial, varargin) - this=this@MyZiLi(); + this=this@MyZiLi(dev_serial); P=MyClassParser(this); addRequired(P, dev_serial, @ischar) % Poll timer period addParameter(P,'poll_period',0.042,@isnumeric); processInputs(P, this, dev_serial, varargin{:}); % Trace object in this case is directly used for averaging this.Trace=MyAvgTrace(... 'name_x','Time',... 'unit_x','s',... 'name_y','Magnitude r',... 'unit_y','V'); this.TmpTrace=MyTrace(); this.PollTimer=timer(... 'ExecutionMode','fixedSpacing',... 'Period',P.Results.poll_period,... 'TimerFcn',@(~,~)pollTimerCallback(this)); 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 scope node ', ... 'or stop the poll timer.']) end % Clear the module's thread. try ziDAQ('clear', this.scope_module); catch warning('Could not clear the scope module.') end % Delete timers to prevent them from running indefinitely in % the case of program crash try delete(this.PollTimer) catch warning('Could not delete the poll timer.') end end function startPoll(this) % Configure hardware scope % Signal input - ziDAQ('setInt', [this.scope_path,'/inputselect'], ... - this.signal_in+1); + path=sprintf('%s/channels/%i/inputselect', ... + this.scope_path, this.n_ch); + ziDAQ('setInt', path, this.signal_in-1); % Disable segmented mode of data transfer. This mode is only % useful if records longer than 5Mpts are required. ziDAQ('setInt', [this.scope_path '/segments/enable'], 0); % Set sampling rate ziDAQ('setInt', [this.scope_path '/time'], 0); % Take continuous records ziDAQ('setInt', [this.scope_path '/single'], 0); % Disable the scope trigger ziDAQ('setInt', [this.scope_path '/trigenable'], 0); % The scope hold off time inbetween acquiring triggers (still % relevant if triggering is disabled). - ziDAQ('setDouble', [this.scope_path '/trigholdoff'], 0.05); + ziDAQ('setDouble', [this.scope_path '/trigholdoff'], ... + this.trigholdoff); % Enable the scope ziDAQ('setInt', [this.scope_path '/enable'], 1); % Initialize and configure a software Scope Module. this.scope_module = ziDAQ('scopeModule'); % Do not average ziDAQ('set', this.scope_module, ... 'scopeModule/averager/weight', 1); % Set the Scope Module's mode to return frequency domain data. ziDAQ('set', this.scope_module, 'scopeModule/mode', 3); % Use rectangular window function. ziDAQ('set', this.scope_module, 'scopeModule/fft/window', 0); ziDAQ('set', this.scope_module, 'scopeModule/fft/power', 1); ziDAQ('set', this.scope_module, ... 'scopeModule/fft/spectraldensity', 1); ziDAQ('subscribe', this.scope_module, ... [this.scope_path '/wave']); ziDAQ('execute', this.scope_module); - stop(this.PollTimer); + start(this.PollTimer); end function stopPoll(this) stop(this.PollTimer); ziDAQ('finish', this.scope_module); end function pollTimerCallback(this) Data = ziDAQ('read', this.scope_module); if ziCheckPathInData(Data, [this.scope_path,'/wave']) % Get the list of scope waves recorded since the previous % poll new_waves=Data.(this.dev_id).scopes(this.n_scope).wave; % Add waves to the average trace for i=1:length(new_waves) dt=new_waves{i}.dt; - n=new_waves{i}.totalsamples; + n=double(new_waves{i}.totalsamples); % Calculate the frequency axis - this.tmpTrace.x=linspace(0, (1-1/n)/(2*dt), n); - this.tmpTrace.y=new_waves{i}.wave; - addAverage(this.Trace, this.tmpTrace); - if this.Trace.avg_count>=this.Trace.n_avg && ... - strcmpi(this.Trace.avg_type, 'lin') + this.TmpTrace.x=linspace(0, (1-1/n)/(2*dt), n); + this.TmpTrace.y=new_waves{i}.wave; + is_compl=addAverage(this.Trace, this.TmpTrace); + if is_compl && strcmpi(this.Trace.avg_type, 'lin') triggerNewData(this); end end - notify(this, 'NewWaves'); + notify(this, 'NewWave'); end end function Hdr=readHeader(this) Hdr=readHeader@MyZiLi(this); + addObjProp(Hdr, this, 'n_scope', 'comment', ... + 'Hardware scope number'); + addObjProp(Hdr, this, 'n_ch', 'comment', ... + 'Scope channel'); + addObjProp(Hdr, this, 'signal_in', 'comment', ... + 'Signal input number'); + addObjProp(Hdr, this, 'trigholdoff', 'comment', ... + ['(s), the scope hold off time inbetween acquiring ' ... + 'triggers']); + addParam(Hdr, this.name, 'poll_period', ... + this.PollTimer.Period, 'comment', '(s)'); end end %% Set and get methods methods function val=get.scope_path(this) - val=sprintf('/%s/scopes/%i',this.dev_id,this.n_scope+1); + val=sprintf('/%s/scopes/%i',this.dev_id,this.n_scope-1); end function val=get.scope_rate(this) tn=ziDAQ('getDouble', [this.scope_path '/time']); val=this.clockbase/(2^tn); end function set.scope_rate(this, val) - tn=round(log2(val/this.clockbase)); + tn=round(log2(this.clockbase/val)); % Trim to be withn 0 and 16 tn=max(0,tn); tn=min(tn, 16); ziDAQ('setDouble', [this.scope_path '/time'], tn); + clearData(this.Trace); notify(this, 'NewSetting'); end function val=get.fft_rbw(this) l=length(this.Trace.x); if l>=2 val=this.Trace.x(2)-this.Trace.x(1); else val=Inf; end end function val=get.n_pt(this) val=ziDAQ('getDouble', [this.scope_path '/length']); end + + function set.n_pt(this, val) + ziDAQ('setDouble', [this.scope_path '/length'], val); + clearData(this.Trace); + notify(this, 'NewSetting'); + end end end diff --git a/GUIs/GuiZiScopeFt.mlapp b/GUIs/GuiZiScopeFt.mlapp index c7cd8e1..c9dc4ad 100644 Binary files a/GUIs/GuiZiScopeFt.mlapp and b/GUIs/GuiZiScopeFt.mlapp differ diff --git a/Utility functions/App utilities/updateLinkedElement.m b/Utility functions/App utilities/updateLinkedElement.m index 0e00522..5812a08 100644 --- a/Utility functions/App utilities/updateLinkedElement.m +++ b/Utility functions/App utilities/updateLinkedElement.m @@ -1,35 +1,35 @@ function updateLinkedElement(app, elem) try % get value using the subreference structure val = subsref(app, elem.UserData.LinkSubs); % Apply the output processing function or input prescaler if isfield(elem.UserData, 'OutputProcessingFcn') val = elem.UserData.OutputProcessingFcn(val); elseif isfield(elem.UserData, 'InputPrescaler') val = val*elem.UserData.InputPrescaler; end % Get the gui property to be updated. The default is Value. if isfield(elem.UserData, 'elem_prop') elem_prop=elem.UserData.elem_prop; else elem_prop='Value'; end % Setting value of a matlab app elemen is time consuming, so do % this only if the value has actually changed if ~isequal(elem.(elem_prop),val) elem.(elem_prop) = val; end - catch + catch ME % Try converting the subreference structure to a readable % format and throw a warning try tag=substruct2str(elem.UserData.LinkSubs); catch tag=''; end - warning(['Could not update the value of element with tag ''%s'' ',... - 'and value ''%s''.'], tag, var2str(val)); + warning(['Could not update the value of element with tag ''%s'' ' ... + 'and value ''%s''. Error: ' ME.message], tag, var2str(val)); end end