diff --git a/@MyAppMechanics/MyAppMechanics.asv b/@MyAppMechanics/MyAppMechanics.asv new file mode 100644 index 0000000..3c41fde --- /dev/null +++ b/@MyAppMechanics/MyAppMechanics.asv @@ -0,0 +1,69 @@ +% The class contains methods for interaction with AppDesigner guis +classdef MyAppMechanics < handle + + properties (Access = public) + Instr; + linkedElementsList = {} % list of Gui control elements + % which have counterpart properties + end + + methods (Access = public) + function this=MyAppMechanics(Instrument) + this.Instr=instrument; + end + + function linkControlElement(this, elem, prop_tag, varargin) + p=inputParser; + addRequired(p,'elem'); + addRequired(p,'prop_tag',@ischar); + addParameter(p,'input_presc',1,@isnumeric); + addParameter(p,'auto_callback',false, @islogical); + parse(p,elem,prop_tag,varargin{:}); + + % The property-control link is established by assigning the tag + % and adding the control to the list of linked elements + elem.Tag = prop_tag; + this.linkedElementsList = [this.linkedElementsList, elem]; + + % If the auto callback flag is set, assign the default + % ValueChangedFcn which passes the field input to the instument + if p.Results.auto_callback + elem.ValueChangedFcn = createCallbackFcn(this,... + @(src, event)genericValueChanged(src, event), true); + end + + % If the prescaler is indicated, add it to the element as a new property + if p.Results.input_presc ~= 1 + if isprop(elem, 'InputPrescaler') + warning('The InputPrescaler propety already exists in the control element'); + else + addprop(elem,'InputPrescaler'); + end + elem.InputPrescaler = p.Results.input_presc; + end + end + + % update all the linked control elements according to their counterpart properties + function updateGui(this) + for i=1:length(this.linkedElementsList) + tmpelem = this.linkedElementsList(i); + tmpval = this.Instr.(tmpelem.Tag); + % scale the value if the control element has a prescaler + if isprop(tmpelem, 'InputPrescaler') + tmpval = tmpval*tmpelem.InputPrescaler; + end + tmpelem.Value = tmpval; + end + end + + function genericValueChanged(this, event) + val = event.Value; + % scale the value if the control element has a prescaler + if isprop(event.Source, 'InputPrescaler') + val = val/event.Source.InputPrescaler; + end + this.Instr.writePropertyHedged(event.Source.Tag, val); + this.updateGui(); + end + end +end \ No newline at end of file diff --git a/@MyAppMechanics/MyAppMechanics.m b/@MyAppMechanics/MyAppMechanics.m new file mode 100644 index 0000000..0668a35 --- /dev/null +++ b/@MyAppMechanics/MyAppMechanics.m @@ -0,0 +1,69 @@ +% The class contains methods for interaction with AppDesigner guis +classdef MyAppMechanics < handle + + properties (Access = public) + Instr; % MyInstrument class object + linkedElementsList = {} % list of Gui control elements + % which have counterpart properties + end + + methods (Access = public) + function this=MyAppMechanics(Instrument) + this.Instr=Instrument; + end + + function linkControlElement(this, elem, prop_tag, varargin) + p=inputParser; + addRequired(p,'elem'); + addRequired(p,'prop_tag',@ischar); + addParameter(p,'input_presc',1,@isnumeric); + addParameter(p,'auto_callback',false, @islogical); + parse(p,elem,prop_tag,varargin{:}); + + % The property-control link is established by assigning the tag + % and adding the control to the list of linked elements + elem.Tag = prop_tag; + this.linkedElementsList = [this.linkedElementsList, elem]; + + % If the auto callback flag is set, assign the default + % ValueChangedFcn which passes the field input to the instument + if p.Results.auto_callback + elem.ValueChangedFcn = createCallbackFcn(this,... + @(src, event)genericValueChanged(src, event), true); + end + + % If the prescaler is indicated, add it to the element as a new property + if p.Results.input_presc ~= 1 + if isprop(elem, 'InputPrescaler') + warning('The InputPrescaler propety already exists in the control element'); + else + addprop(elem,'InputPrescaler'); + end + elem.InputPrescaler = p.Results.input_presc; + end + end + + % update all the linked control elements according to their counterpart properties + function updateGui(this) + for i=1:length(this.linkedElementsList) + tmpelem = this.linkedElementsList(i); + tmpval = this.Instr.(tmpelem.Tag); + % scale the value if the control element has a prescaler + if isprop(tmpelem, 'InputPrescaler') + tmpval = tmpval*tmpelem.InputPrescaler; + end + tmpelem.Value = tmpval; + end + end + + function genericValueChanged(this, event) + val = event.Value; + % scale the value if the control element has a prescaler + if isprop(event.Source, 'InputPrescaler') + val = val/event.Source.InputPrescaler; + end + this.Instr.writePropertyHedged(event.Source.Tag, val); + this.updateGui(); + end + end +end \ No newline at end of file diff --git a/@MyInstrument/MyInstrument.m b/@MyInstrument/MyInstrument.m index 127c41d..0ed90dc 100644 --- a/@MyInstrument/MyInstrument.m +++ b/@MyInstrument/MyInstrument.m @@ -1,384 +1,383 @@ classdef MyInstrument < dynamicprops properties (SetAccess=protected, GetAccess=public) name=''; interface=''; address=''; %Logical for whether gui is enabled enable_gui=false; %Contains the GUI handles Gui; %Contains the device object Device; %Input parser for class constructor Parser; %Contains a list of the commands available for the instrument as %well as the default values and input requirements CommandList; %Parses commands using an inputParser object CommandParser; %Trace object for storing data Trace=MyTrace(); end properties (Constant=true) % Default parameters for VISA connection DEFAULT_INP_BUFF_SIZE = 1e7; % buffer size bytes DEFAULT_OUT_BUFF_SIZE = 1e7; % buffer size bytes DEFAULT_TIMEOUT = 10; % Timeout in s + DEFAULT_VISA_BRAND = 'ni'; end properties (Dependent=true) command_names; command_no; end events NewData; end methods (Access=private) function createParser(this) - p=inputParser; + p=inputParser(); addRequired(p,'interface',@ischar); addRequired(p,'address',@ischar); - addParameter(p,'name','placeholder',@ischar); - addParameter(p,'gui','placeholder',@ischar); - addParameter(p,'visa_brand','ni',@ischar); + addParameter(p,'name','',@ischar); + addParameter(p,'gui','',@ischar); + addParameter(p,'visa_brand',this.DEFAULT_VISA_BRAND,@ischar); this.Parser=p; end end methods (Access=public) function this=MyInstrument(interface, address, varargin) createParser(this); parse(this.Parser,interface,address,varargin{:}); %Loads parsed variables into class properties this.name=this.Parser.Results.name; this.interface=this.Parser.Results.interface; this.address=this.Parser.Results.address; this.enable_gui=~ismember('gui',this.Parser.UsingDefaults); %If a gui input is given, load the gui if this.enable_gui %Loads the gui from the input gui string this.Gui=guihandles(eval(this.Parser.Results.gui)); %Sets figure close function such that class will know when %figure is closed set(this.Gui.figure1, 'CloseRequestFcn',... @(hObject,eventdata) closeFigure(this, hObject, ... eventdata)); end end function delete(this) %Removes close function from figure, prevents infinite loop if this.enable_gui set(this.Gui.figure1,'CloseRequestFcn',''); %Deletes the figure handles structfun(@(x) delete(x), this.Gui); %Removes the figure handle to prevent memory leaks this.Gui=[]; end %Closes the connection to the device closeDevice(this); %Deletes the device object delete(this.Device); clear('this.Device'); end %Clears data from trace to save memory. function clearData(this) this.Trace.x=[]; this.Trace.y=[]; end %Writes properties to device. Can take multiple inputs. With the %option all, the function writes default to all the %available writeable parameters. function writeProperty(this, varargin) %Parses the inputs using the CommandParser parse(this.CommandParser, varargin{:}); % Finds writable commands to be included in the execution list ind_w=structfun(@(x) x.write_flag, this.CommandList); if this.CommandParser.Results.all % If the 'all' is true, write all the commands ind=ind_w; else % Check which commands were passed values ind_val=cellfun(@(x)... (~ismember(x, this.CommandParser.UsingDefaults)),... this.command_names); ind=ind_w&ind_val; if any((~ind_w)&ind_val) % Issue warnings for read-only commands warning('The following commands are read only:'); disp(this.command_names((~ind_w)&ind_val)); end end exec=this.command_names(ind); for i=1:length(exec) %Creates the write command using the right string spec write_command=[this.CommandList.(exec{i}).command,... ' %',this.CommandList.(exec{i}).str_spec]; %Gets the value to write to the device this.(exec{i})=this.CommandParser.Results.(exec{i}); command=sprintf(write_command, this.(exec{i})); %Sends command to device fprintf(this.Device, command); end end % Wrapper for writeProperty that opens and closes the device function writePropertyHedged(this, varargin) this.openDevice(); try this.writeProperty(varargin{:}); catch warning('Error while writing the properties:'); disp(varargin); end this.readProperty('all'); this.closeDevice(); end function result=readProperty(this, varargin) result = struct(); read_all_flag = any(strcmp('all',varargin)); if read_all_flag % Read all the commands with read access ind=structfun(@(x) x.read_flag, this.CommandList); exec=this.command_names(ind); else ind_val=ismember(varargin,this.command_names); varargin_val=varargin(ind_val); if any(~ind_val) % Issue warnings for commands not in the command_names warning('The following are not valid commands:'); disp(varargin{~ind_val}); end ind_r=cellfun(@(x) this.CommandList.(x).read_flag,... varargin_val); if any(~ind_r) % Issue warnings for write-only commands warning('The following commands are write only:'); disp(varargin_val{~ind_r}); end exec=varargin_val(ind_r); end % Iterate over the commands list for i=1:length(exec) %Creates the correct read command read_command=[this.CommandList.(exec{i}).command,'?']; %Reads the property from the device and stores it in the %correct place res_str = query(this.Device,read_command); if ismember('char',this.CommandList.(exec{i}).classes) result.(exec{i})= res_str(1:(end-1)); else result.(exec{i})= str2double(res_str); end %Assign the values to the MyInstrument properties this.(exec{i})=result.(exec{i}); end end % Wrapper for readProperty that opens and closes the device function result = readPropertyHedged(this, varargin) this.openDevice(); try result = this.readProperty(varargin{:}); catch warning('Error while reading the properties:'); disp(varargin); end this.closeDevice(); end %Connects to the device if it is not connected 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('RemoteHost',this.address); + 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) - try - fclose(this.Device); - catch - error('Could not close device') - end + fclose(this.Device); end end function configureDefaultVisa(this) this.Device.OutputBufferSize = this.DEFAULT_OUT_BUFF_SIZE; this.Device.InputBufferSize = this.DEFAULT_INP_BUFF_SIZE; this.Device.Timeout = this.DEFAULT_TIMEOUT; end end methods (Access=public) %Triggers event for acquired data function triggerNewData(this) notify(this,'NewData') end %Checks if the connection to the device is open function bool=isopen(this) try bool=strcmp(this.Device.Status, 'open'); catch warning('Cannot verify device Status property'); bool=false; end end %Adds a command to the CommandList function addCommand(this, tag, command, varargin) p=inputParser(); addRequired(p,'tag',@ischar); addRequired(p,'command',@ischar); addParameter(p,'default','placeholder'); addParameter(p,'classes','placeholder',@iscell); addParameter(p,'attributes','placeholder',@iscell); addParameter(p,'str_spec','d',@ischar); addParameter(p,'access','rw',@ischar); parse(p,tag,command,varargin{:}); %Adds the command to be sent to the device this.CommandList.(tag).command=command; this.CommandList.(tag).access=p.Results.access; this.CommandList.(tag).write_flag=contains(p.Results.access,'w'); this.CommandList.(tag).read_flag=contains(p.Results.access,'r'); %Adds a default value and the attributes the inputs must have %and creates a new property in the class if this.CommandList.(tag).write_flag % Adds the string specifier to the list. if the format % specifier is not given explicitly, try to infer if ismember('str_spec',p.UsingDefaults) this.CommandList.(tag).str_spec=... formatSpecFromAttributes(this,p.Results.classes... ,p.Results.attributes); else if isequal(p.Results.str_spec,'b') % b is a non-system specifier to represent the % logical type this.CommandList.(tag).str_spec='i'; else this.CommandList.(tag).str_spec=p.Results.str_spec; end end % Adds the default value this.CommandList.(tag).default=p.Results.default; % Adds the attributes for the input to the command. If not % given explicitly, infer from the format specifier if ismember('classes',p.UsingDefaults) [this.CommandList.(tag).classes,... this.CommandList.(tag).attributes]=... AttributesFromFormatSpec(this, p.Results.str_spec); else this.CommandList.(tag).classes=p.Results.classes; this.CommandList.(tag).attributes=p.Results.attributes; end end % Adds a property to the class corresponding to the tag if ~isprop(this,tag) addprop(this,tag); end this.(tag)=p.Results.default; end %Creates inputParser using the command list function createCommandParser(this) %Use input parser %Requires input of the appropriate class p=inputParser; p.StructExpand=0; %Flag for whether the command should initialize the device with %defaults addParameter(p, 'all',false,@islogical); for i=1:length(this.command_names) %Adds optional inputs for each command, with the %appropriate default value from the command list and the %required attributes for the command input. tag=this.command_names{i}; addParameter(p, tag,... this.CommandList.(tag).default,... @(x) validateattributes(x,... this.CommandList.(tag).classes,... this.CommandList.(tag).attributes)); end this.CommandParser=p; end function str_spec=formatSpecFromAttributes(~,classes,attributes) if ismember('char',classes) str_spec='s'; elseif ismember('logical',classes)||... (ismember('numeric',classes)&&... ismember('integer',attributes)) str_spec='i'; else %assign default value, i.e. double str_spec='d'; end end function [class,attribute]=AttributesFromFormatSpec(~, str_spec) switch str_spec case 'd' class={'numeric'}; attribute={}; case 'i' class={'numeric'}; attribute={'integer'}; case 's' class={'char'}; attribute={}; case 'b' class={'logical'}; attribute={}; otherwise class={}; attribute={}; end end %Close figure callback simply calls delete function for class function closeFigure(this,~,~) delete(this); end end % Get functions methods function command_names=get.command_names(this) command_names=fieldnames(this.CommandList); end function command_no=get.command_no(this) command_no=length(this.command_names); end end end \ No newline at end of file diff --git a/@MyNa/MyNa.m b/@MyNa/MyNa.m index 6d0cb90..7b0b0ea 100644 --- a/@MyNa/MyNa.m +++ b/@MyNa/MyNa.m @@ -1,175 +1,176 @@ % The class for communication with Agilent E5061B Network Analyzer classdef MyNa < MyInstrument properties (SetAccess=protected, GetAccess=public) active_trace = -1; % manipulating with active traces seems unavoidable % for selecting the data format. -1 stands for unknown % data formats for the traces 1-2, options: % 'PHAS', 'SLIN', 'SLOG', 'SCOM', 'SMIT', 'SADM', 'MLOG', 'MLIN', %'PLIN', 'PLOG', 'POL' form1 = 'MLOG'; form2 = 'PHAS'; data1 = struct(); data2 = struct(); end methods function this=MyNa(interface, address, varargin) this@MyInstrument(interface, address, varargin{:}); createCommandList(this); createCommandParser(this); switch interface case 'visa' - this.Device=visa(this.CommandParser.visa_brand,... + this.Device=visa(this.Parser.Results.visa_brand,... address); configureDefaultVisa(this); case 'TCPIP' connectTCPIP(this); end %Tests if device is working. try openDevice(this); closeDevice(this); catch error(['Failed to open communications with device.',... ' Check that the address and interface is correct.',... ' Currently the address is %s and the interface is ',... '%s.'],this.address,this.interface) end end % Command attributes are {class, attributtes} accepted by % validateattributes() function createCommandList(this) addCommand(this,... 'cent_freq','SENS1:FREQ:CENT', 'default',1.5e6,... 'str_spec','d'); addCommand(this,... 'start_freq','SENS1:FREQ:START', 'default',1e6,... 'str_spec','d'); addCommand(this,... 'stop_freq','SENS1:FREQ:STOP', 'default',2e6,... 'str_spec','d'); addCommand(this,... 'span','SENS1:FREQ:SPAN', 'default',1e6,... 'str_spec','d'); % IF bandwidth addCommand(this,... 'ifbw','SENS1:BAND', 'default',100,... 'str_spec','d'); % number of points in the sweep addCommand(this,... 'point_no','SENS1:SWE:POIN', 'default',1000,... 'str_spec','i'); % number of averages addCommand(this,... 'average_no','SENS1:AVER:COUN', 'default',1,... 'str_spec','i'); % number of traces addCommand(this,... 'trace_no','CALC1:PAR:COUN', 'default',1,... 'str_spec','i'); % linear or log sweep addCommand(this,... 'sweep_type','SENS1:SWE:TYPE', 'default','LIN',... 'str_spec','s'); % switch the output signal on/off addCommand(this,... 'enable_out','OUTP', 'default',0,... 'str_spec','b'); % probe power [dB] addCommand(this,... 'power','SOUR:POW:LEV:IMM:AMPL', 'default',-10,... 'str_spec','d'); % windows arrangement at the display, e.g 'D1' addCommand(this,... 'disp_type','DISP:WIND1:SPL', 'default','D1',... 'str_spec','s'); - % Continuous sweep triggering 'ON'/'OFF' + % Continuous sweep triggering addCommand(this,... - 'cont_trig',':INIT1:CONT', 'default','OFF',... - 'str_spec','s'); + 'cont_trig',':INIT1:CONT', 'default', 0,... + 'str_spec','b'); + addCommand(this,... + 'trig_source', ':TRIG:SOUR', 'default', 'BUS',... + 'str_spec','s') % Parametric commands for traces, i can be extended to 4 for i = 1:2 % measurement parameters for the traces 1-2, e.g. 'S21' i_str = num2str(i); addCommand(this,... ['meas_par',i_str],['CALC1:PAR',i_str,':DEF'],... 'default','S21',... 'str_spec','s'); end end function readTrace(this, nTrace) this.writeActiveTrace(nTrace); dtag = sprintf('data%i', nTrace); freq_str = strsplit(query(this.Device,'SENS1:FREQ:DATA?'),','); data_str = strsplit(query(this.Device,'CALC1:DATA:FDAT?'),','); this.(dtag).x = str2double(freq_str); % In the returned string there is in general 2 values for each % frequency point. In the Smith data format this can be used to % transfer magnitude and phase of the signal in one trace. With % MLOG, MLIN and PHAS format settings every 2-nd element should % be 0 this.(dtag).y = str2double(data_str(1:2:end)); this.(dtag).y2 = str2double(data_str(2:2:end)); end function writeActiveTrace(this, nTrace) fprintf(this.Device, sprintf('CALC1:PAR%i:SEL',nTrace)); this.active_trace = nTrace; end function writeTraceFormat(this, nTrace, fmt) this.writeActiveTrace(nTrace); n_str = num2str(nTrace); this.(['form',n_str]) = fmt; fprintf(this.Device, sprintf('CALC1:FORM %s', fmt)); end function singleSweep(this) this.openDevice(); - % Set the triger source to be remote control - fprintf(this.Device,':TRIG:SOUR BUS'); + this.writeProperty('cont_trig', true); + % Set the triger source to remote control + this.writeProperty('trig_source', 'BUS'); % Start a sweep cycle fprintf(this.Device,':TRIG:SING'); - % Wait for the sweep to finish (the command returns 1) when it - % happens + % Wait for the sweep to finish (for the query to return 1) query(this.Device,'*OPC?'); this.closeDevice(); end function startContSweep(this) this.openDevice(); - %this.writeProperty('cont_trig', 'ON'); - fprintf(this.Device,':INIT1:CONT'); - % Set the triger source to be remote control - fprintf(this.Device,':TRIG:SOUR BUS'); - % Start a sweep cycle - fprintf(this.Device,':TRIG'); + this.writeProperty('cont_trig', true); + % Set the triger source to be internal + this.writeProperty('trig_source', 'INT'); this.closeDevice(); end function abortSweep(this) this.openDevice(); + this.writeProperty('trig_source', 'BUS'); fprintf(this.Device,':ABOR'); this.closeDevice(); end function connectTCPIP(this) buffer = 1000 * 1024; visa_brand = 'ni'; visa_address_rsa = sprintf('TCPIP0::%s::inst0::INSTR',... this.address); this.Device=visa(visa_brand, visa_address_rsa,... 'InputBufferSize', buffer,... 'OutputBufferSize', buffer); set(this.Device,'InputBufferSize',1e6); set(this.Device,'Timeout',10); end end end diff --git a/GUIs/GuiNa.mlapp b/GUIs/GuiNa.mlapp deleted file mode 100644 index 662e7c1..0000000 Binary files a/GUIs/GuiNa.mlapp and /dev/null differ diff --git a/GuiNa.mlapp b/GuiNa.mlapp new file mode 100644 index 0000000..dbfa277 Binary files /dev/null and b/GuiNa.mlapp differ