diff --git a/@MyInstrument/MyInstrument.m b/@MyInstrument/MyInstrument.m index d7e5ffd..fbf30b9 100644 --- a/@MyInstrument/MyInstrument.m +++ b/@MyInstrument/MyInstrument.m @@ -1,238 +1,288 @@ 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 (Dependent=true) command_names; command_no; end events NewData; end methods (Access=public) function this=MyInstrument(name, interface, address, varargin) createParser(this); parse(this.Parser,name,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 + end + %Writes properties to device. Can take multiple inputs. With the + %option init_device, 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 the commands that are supplied by the user - ind=~ismember(this.CommandParser.Parameters,... - this.CommandParser.UsingDefaults); - %Creates a list of commands to be executed - exec=this.CommandParser.Parameters(ind); + if ~p.Results.write_all_defaults + %Finds the writeable commands that are supplied by the user + ind=~ismember(this.CommandParser.Parameters,... + this.CommandParser.UsingDefaults); + %Creates a list of commands to be executed + exec=this.CommandParser.Parameters(ind); + else + exec=this.CommandParser.Parameters; + end for i=1:length(exec) - command=sprintf(this.CommandList.(exec{i}).command,... - this.CommandParser.Results.(exec{i})); - fprintf(this.Device, command); + %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 + write_val=this.CommandList.Results.(exec{i}); + %Sends command to device + fprintf(this.Device, write_command, write_val); + this.(exec{i})=write_val; end end + %Wrapper for writeProperty that opens the device + function writePropertyHedged(this, varargin) + this.openDevice(); + try + this.writeProperty(varargin{:}); + catch + disp('Error while writing the properties:'); + disp(varargin); + end + this.readAll(); + closeDevice(this); + end + function result=readProperty(this, varargin) result=struct(); for i=1:length(varargin) - %Finds the index of the % sign which indicates where the value - %to be written is supplied - ind=strfind(this.CommandList.(varargin{i}).command,'%'); - if ~any(ind) - error('%s is not a valid tag for a command in %s',... - varargin{i},class(this)); - end - %Creates the correct read command - read_command=... - [this.CommandList.(varargin{i}).command(1:(ind-2)),'?']; + read_command=[this.CommandList.(varargin{i}).command,'?']; + %Reads the property from the device and stores it in the %correct place - result.(varargin{i})=... - str2double(query(this.Device,read_command)); + res_str = query(this.Device,read_command); + if strcmp(this.CommandList.(varargin{i}).attributes{1},'string') + result.(varargin{i})= res_str(1:(end-1)); + else + result.(varargin{i})= str2double(res_str); + end + end + end + + function result = readPropertyHedged(this, varargin) + this.openDevice(); + try + result = this.readProperty(varargin{:}); + catch + disp('Error while reading the properties:'); + disp(varargin); + end + this.closeDevice(); + end + + % Execute all the read commands and update corresponding properties + function readAll(this) + result=readProperty(this, this.command_names{:}); + res_names=fieldnames(result); + for i=1:length(res_names) + this.(res_names{i})=result.(res_names{i}); end end end methods (Access=private) function createParser(this) p=inputParser; addRequired(p,'name',@ischar); addRequired(p,'interface',@ischar); addRequired(p,'address',@ischar); addParameter(p,'gui','placeholder',@ischar); this.Parser=p; end end methods (Access=protected) %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) bool=strcmp(this.Device.Status, 'open'); 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,'attributes','placeholder',@iscell); + addParameter(p,'str_spec','d',@ischar); %If the write flag is on, it means this command can be used to %write a parameter to the device - addParameter(p,'write_flag',false,@islogical); + addParameter(p,'access','rw',@ischar); addParameter(p,'conv_factor',1,@isnumeric); parse(p,tag,command,varargin{:}); %Adds the command to be sent to the device this.CommandList.(tag).command=command; - this.CommandList.(tag).write_flag=p.Results.write_flag; + this.CommandList.(tag).access=p.Results.access; + + write_flag=contains(p.Results.access,'w'); +% 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 p.Results.write_flag + if write_flag + %Adds the string specifier to the list + this.CommandList.(tag).str_spec=p.Results.str_spec; %Adds the default value this.CommandList.(tag).default=p.Results.default; %Adds the necessary attributes for the input to the command this.CommandList.(tag).attributes=p.Results.attributes; %Adds a conversion factor for displaying the value this.CommandList.(tag).conv_factor=p.Results.conv_factor; %Adds a property to the class corresponding to the tag addprop(this,tag); end 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, 'write_all_defaults',false,@islogical); for i=1:this.command_no %Adds optional inputs for each command, with the %appropriate default value from the command list and the %required attributes for the command input. addParameter(p, this.command_names{i},... this.CommandList.(this.command_names{i}).default),... @(x) validateattributes(x,... this.CommandList.(this.command_names{i}).attributes{1:end}); end this.CommandParser=p; end %Connects to the device if it is not connected function openDevice(this) if ~isopen(this) try fopen(this.Device); catch try instr_list=instrfind('RemoteHost',this.address); 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 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/@MyRsa/MyRsa.m b/@MyRsa/MyRsa.m index 1da6ef8..21b5f43 100644 --- a/@MyRsa/MyRsa.m +++ b/@MyRsa/MyRsa.m @@ -1,372 +1,361 @@ classdef MyRsa < MyInstrument properties (SetAccess=protected, GetAccess=public) valid_points; end properties (Dependent=true) freq_vec; end %% Constructor and destructor methods (Access=public) function this=MyRsa(name,interface, address,varargin) this@MyInstrument(name, interface, address,varargin{:}); if this.enable_gui; initGui(this); end createCommandList(this); createCommandParser(this); %Valid point numbers for Tektronix 5103 and 5106. %Depends on the RSA. Remove this in the future. this.valid_points=[801,2401,4001,10401]; switch interface 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 if this.enable_gui; setGuiProps(this); end %Opens communications openDevice(this); %Finds the current status of the device readStatus(this); %Initializes the device initDevice(this); closeDevice(this); end end %% Private functions methods (Access=private) 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 function setGuiProps(this) prop_names=fieldnames(this.CommandList); for i=1:length(prop_names) tag=prop_names{i}; h_prop=findprop(this,tag); h_prop.GetMethod=@(this) getInstrProp(this, tag); h_prop.SetMethod=@(this, val) setInstrProp(this, tag, val); h_prop.Dependent=true; - h_prop end end function setInstrProp(this, tag, val) writeProperty(this, tag, val); this.Gui.(tag).String=num2str(val); end function val=getInstrProp(this, tag) val=str2double(this.Gui.(tag)); end function initGui(this) this.Gui.Title.String{1}=sprintf('Real-Time Signal Analyzer %s',... this.name); this.Gui.reinit.Callback=@(hObject, eventdata)... reinitCallback(this, hObject,eventdata); this.Gui.point_no.Callback=@(hObject, eventdata)... point_noCallback(this, hObject,eventdata); this.Gui.start_freq.Callback=@(hObject, eventdata)... start_freqCallback(this, hObject, eventdata); this.Gui.stop_freq.Callback=@(hObject, eventdata)... stop_freqCallback(this, hObject,eventdata); this.Gui.cent_freq.Callback=@(hObject, eventdata)... cent_freqCallback(this, hObject, eventdata); this.Gui.span.Callback=@(hObject, eventdata)... spanCallback(this, hObject, eventdata); this.Gui.rbw.Callback=@(hObject, eventdata)... rbwCallback(this, hObject, eventdata); this.Gui.fetch_single.Callback=@(hObject, eventdata)... fetchCallback(this, hObject,eventdata); this.Gui.average_no.Callback=@(hObject, eventdata)... average_noCallback(this, hObject,eventdata); this.Gui.enable_avg.Callback=@(hObject, eventdata)... enable_avgCallback(this, hObject,eventdata); end - function initDevice(this) - for i=1:this.command_no - if this.CommandList.(this.command_names{i}).write_flag - fprintf(this.Device, ... - sprintf(this.CommandList.(this.command_names{i}).command,... - this.CommandList.(this.command_names{i}).default)); - this.(this.command_names{i})=... - this.CommandList.(this.command_names{i}).default; - end - end - end + function createCommandList(this) addCommand(this,'average_no','TRAC3:DPSA:AVER:COUN %d',... - 'default',1,'attributes',{{'numeric'}},'write_flag',true); - addCommand(this, 'rbw','DPSA:BAND:RES %d Hz',... - 'default',1e3,'attributes',{{'numeric'}},'write_flag',true,... + 'default',1,'attributes',{{'numeric'}}); + addCommand(this, 'rbw','DPSA:BAND:RES %d',... + 'default',1e3,'attributes',{{'numeric'}},... 'conv_factor',1e3); - addCommand(this, 'span', 'DPSA:FREQ:SPAN %d Hz',... - 'default',1e6,'attributes',{{'numeric'}},'write_flag',true,... + addCommand(this, 'span', 'DPSA:FREQ:SPAN %d',... + 'default',1e6,'attributes',{{'numeric'}},... 'conv_factor',1e6); - addCommand(this, 'start_freq','DPSA:FREQ:STAR %d Hz',... - 'default',1e6,'attributes',{{'numeric'}},'write_flag',true,... + addCommand(this, 'start_freq','DPSA:FREQ:STAR %d',... + 'default',1e6,'attributes',{{'numeric'}},... 'conv_factor',1e6); - addCommand(this, 'stop_freq','DPSA:FREQ:STOP %d Hz',... - 'default',2e6,'attributes',{{'numeric'}},'write_flag',true,... + addCommand(this, 'stop_freq','DPSA:FREQ:STOP %d',... + 'default',2e6,'attributes',{{'numeric'}},... 'conv_factor',1e6); - addCommand(this, 'cent_freq','DPSA:FREQ:CENT %d Hz',... - 'default',1.5e6,'attributes',{{'numeric'}},'write_flag',true,... + addCommand(this, 'cent_freq','DPSA:FREQ:CENT %d',... + 'default',1.5e6,'attributes',{{'numeric'}},... 'conv_factor',1e6); addCommand(this, 'point_no','DPSA:POIN:COUN P%i',... - 'default',10401,'attributes',{{'numeric'}},'write_flag',true); + 'default',10401,'attributes',{{'numeric'}},true); addCommand(this,'enable_avg','TRAC3:DPSA:COUN:ENABLE %d',... - 'default',0,'attributes',{{'numeric'}},'write_flag',true); + 'default',0,'attributes',{{'numeric'}}); addCommand(this,'read_cont','INIT:CONT %s','default','on',... - 'attributes',{{'char'}},'write_flag',true); + 'attributes',{{'char'}}); end end %% Public functions including callbacks methods (Access=public) function readStatus(this) result=readProperty(this,'rbw','cent_freq','span','start_freq',... 'stop_freq','enable_avg'); res_names=fieldnames(result); for i=1:length(res_names) this.(res_names{i})=result.(res_names{i}); end end function reinitDevice(this) openDevice(this); readStatus(this); initDevice(this); writeProperty(this, 'read_cont','on') closeDevice(this); end function readSingle(this) openDevice(this); fwrite(this.Device, 'fetch:dpsa:res:trace3?'); data = binblockread(this.Device,'float'); %Reads status at the end. readStatus(this); closeDevice(this); x=this.freq_vec/1e6; unit_x='MHz'; name_x='Frequency'; %Calculates the power spectrum from the data, which is in dBm. %Output is in V^2/Hz power_spectrum = (10.^(data/10))/this.rbw*50*0.001; %Trace object is created containing the data and its units setTrace(this.Trace,'name','RsaData','x',x,'y',power_spectrum,'unit_y',... '$\mathrm{V}^2/\mathrm{Hz}$','name_y','Power','unit_x',... unit_x,'name_x',name_x); %Trigger acquired data event (inherited from MyInstrument) triggerNewData(this); end function reinitCallback(this, hObject, ~) reinitDevice(this); %Turns off indicator set(hObject,'Value',0); end function point_noCallback(this, hObject, ~) value_list=get(hObject,'String'); this.point_no=str2double(value_list{get(hObject,'Value')}); openDevice(this); writeProperty(this,'point_no',this.point_no); readStatus(this); closeDevice(this); end function start_freqCallback(this, hObject, ~) this.start_freq=str2double(get(hObject,'String'))*1e6; openDevice(this); writeProperty(this,'start_freq',this.start_freq); readStatus(this); closeDevice(this); end function stop_freqCallback(this, hObject, ~) this.stop_freq=str2double(get(hObject,'String'))*1e6; openDevice(this); writeProperty(this,'stop_freq',this.stop_freq); readStatus(this); closeDevice(this); end function cent_freqCallback(this, hObject, ~) this.cent_freq=str2double(get(hObject,'String'))*1e6; openDevice(this); writeProperty(this,'cent_freq',this.cent_freq); readStatus(this); closeDevice(this); end function spanCallback(this, hObject, ~) this.span=str2double(get(hObject,'String'))*1e6; openDevice(this); writeProperty(this,'span',this.span); readStatus(this) closeDevice(this); end function rbwCallback(this, hObject, ~) this.rbw=str2double(get(hObject,'String'))*1e3; openDevice(this); writeProperty(this,'rbw',this.rbw); closeDevice(this); end function average_noCallback(this, hObject, ~) this.average_no=str2double(get(hObject,'String')); %Writes the average_no to the device only if averaging is %enabled openDevice(this); writeProperty(this,'average_no',this.average_no); closeDevice(this); end function fetchCallback(this, hObject, ~) %Fetches the data using the settings given. This function can %in principle be used in the future to add further fetch %functionality. switch get(hObject,'Tag') case 'fetch_single' readSingle(this) end set(this.Gui.fetch_single,'Value',0); end function enable_avgCallback(this, hObject, ~) this.enable_avg=get(hObject,'Value'); openDevice(this) writeProperty(this,'enable_avg',this.enable_avg); closeDevice(this); end end % %% Set functions % methods % %Set function for central frequency, changes gui to show central % %frequency in MHz % function set.cent_freq(this, cent_freq) % this.cent_freq=cent_freq; % if this.enable_gui % set(this.Gui.cent_freq,'String',this.cent_freq/1e6); % end % end % % %Set function for rbw, changes gui to show rbw in kHz % function set.rbw(this, rbw) % assert(isnumeric(rbw) && rbw>0,'RBW must be a positive double'); % this.rbw=rbw; % if this.enable_gui % set(this.Gui.rbw,'String',this.rbw/1e3); % end % end % % %Set function for enable_avg, changes gui % function set.enable_avg(this, enable_avg) % assert(isnumeric(enable_avg),... % 'Flag for averaging must be a number') % assert(enable_avg==1 || enable_avg==0,... % 'Flag for averaging must be 0 or 1') % this.enable_avg=enable_avg; % if this.enable_gui % set(this.Gui.enable_avg,'Value',this.enable_avg) % end % end % % %Set function for span, changes gui to show span in MHz % function set.span(this, span) % assert(isnumeric(span) && span>0,... % 'Span must be a positive number'); % this.span=span; % if this.enable_gui % set(this.Gui.span,'String',this.span/1e6); % end % end % % % %Set function for start frequency, changes gui to show start % %frequency in MHz % function set.start_freq(this, start_freq) % assert(isnumeric(start_freq),'Start frequency must be a number'); % this.start_freq=start_freq; % if this.enable_gui % set(this.Gui.start_freq,'String',this.start_freq/1e6); % end % end % % %Set function for stop frequency, changes gui to show stop % %frequency in MHz % function set.stop_freq(this, stop_freq) % assert(isnumeric(stop_freq),... % 'Stop frequency must be a number'); % this.stop_freq=stop_freq; % if this.enable_gui % set(this.Gui.stop_freq,'String',this.stop_freq/1e6) % end % end % % %Set function for average number, also changes GUI % function set.average_no(this, average_no) % assert(isnumeric(average_no),'Number of averages must be a number') % assert(logical(mod(average_no,1))==0 && average_no>0,... % 'Number of averages must be a positive integer') % this.average_no=average_no; % if this.enable_gui % set(this.Gui.average_no,'String',this.average_no); % end % end % % %Set function for point number, checks it is valid and changes GUI % function set.point_no(this, point_no) % if ismember(point_no,this.valid_points) % this.point_no=point_no; % if this.enable_gui % ind=strcmp(get(this.Gui.point_no,'String'),... % num2str(point_no)); % set(this.Gui.point_no,'Value',find(ind)); % end % else % error('Invalid number of points chosen for RSA') % end % end % end % %% Get functions methods %Generates a vector of frequencies between the start and stop %frequency of length equal to the point number function freq_vec=get.freq_vec(this) freq_vec=linspace(this.start_freq,this.stop_freq,... this.point_no) ; end end end