Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
classdef KS_DSOX1204G
%% Class Properties
properties
% Instrument object
instr
% Define Input Buffer Size
inputBufferSize = 2000000;
end
%% Class Methods
methods
% Constructor
% ipAddress {String} where the oscilloscope can be located
function obj = KS_DSOX1204G(ipAddress)
% networkAdapter might need to be changed if moving to a
% different machine or changing the network adapter
% configuration.
networkAdapter = 'TCPIP0';
connectionString = [networkAdapter,'::', ipAddress, '::hislip0::INSTR'];
% Find a VISA_TCPIP object
obj.instr = instrfind('Type', 'visa-tcpip', 'RsrcName', connectionString , 'Tag', '');
% Create the VISA-TCPIP object if it does not exist
% otherwise use the object that was found.
if isempty(obj.instr)
obj.instr = visa('KEYSIGHT', connectionString);
else
fclose(obj.instr);
obj.instr = obj.instr(1);
end
set(obj.instr, 'InputBufferSize', obj.inputBufferSize);
end
% Connect and disconnect functionality
function obj = connect(obj)
% Connect to the instrument object
if(strcmp(obj.instr.Status,'closed'))
fopen(obj.instr);
end
end
function obj = disconnect(obj)
% Disconnect from the instrument object
fclose(obj.instr);
end
% Write and Query Functionality
function write(obj, msg)
obj.connect();
fprintf(obj.instr, msg);
end
function response = query(obj,msg)
obj.connect();
response = query(obj.instr, msg);
end
function resetScope(obj)
obj.connect();
obj.write('*RST');
end
% Acquisition Functionality
function data = captureChannels(obj, channels, timeRange, chanVoltRange)
% CAPTURECHANNELS(obj, channels, timeRange, chanVoltRange)
% This function performs data acquisition from the oscilloscope, on
% the channels specified in the channels argument.
% Input Arguments:
% channels: A vector containing all the channels to be
% acquired. Each channel must be a whole number of
% type double. At least 1 channel must be specified.
% Ex. [1 2], [1 2 3 4], etc.
% timeRange: A [double] specifying the number of [seconds] of
% data to capture. This effectively sets the range of
% the time vector captured over. As the scope can
% only handle so much data, the sampling rate will be
% affected, so small time ranges are preferred.
% NOTE: Specify an empty vector, [], to use the
% current horizontal scale set on the scope.
% chanVoltRange:
% A cell array containing the same number of elements
% as channels specified in the channels argument.
% Determines the voltage range for each channel to be
% acquired. Each cell can contain one of the
% following values:
% 1) 'auto' [string] - Uses the scope's automatic
% rangefinding functionality.
% 2) [min max] [1x2 double array] - Defines the lower and
% upper voltage bounds for the signal.
% 3) [] [empty double array] - Uses the range currently set
% for the channel on the scope
% Example: {'auto', [0, 4], []}
% The first channel is automatically scaled, the
% second channel has a range of 0V-4V, and the
% last channel will use whatever range is
% currently set on the scope.
% Output:
% The data variable is a struct containing the following data:
% Fs: Sampling frequency, samples per second
% Time_s: A vector containing sample times
% Chan<n>_V: Vectors containing the voltage samples on channel
% <n>
% Ensure connection to the scope
obj.connect();
% INPUT SANITIZATION:
% [channels] argument
if(~isa(channels, 'double'))
error('"channels" input argument must be of type double.');
end
if(0~=sum((channels<1)+(channels>4)+(channels~=floor(channels))))
error('"channels" input argument may only contain integer values of 1,2,3, or 4.');
end
% [timeRange] argument
if(~isa(timeRange, 'double'))
error('"timeRange" input argument must be of type double, or an empty vector');
end
% [chanVoltRange] argument
if(~isa(chanVoltRange,'cell'))
error('"chanVoltRange" input argument must be a cell array.');
end
if(length(chanVoltRange) ~= length(channels))
error(['"chanVoltRange" input argument must have the same' ...
'number of cells as input channels specified in the "channels" input argument.']);
end
for(curCell = chanVoltRange)
if(isa(curCell{1}, 'double') && (all(size(curCell{1})==[1,2]) || all(size(curCell{1})==[0,0])))
continue;
elseif(isa(curCell{1},'char') && strcmp(curCell{1}, 'auto'))
continue;
else
error('Invalid input given for "chanVoltRange" input argument');
end
end
% Set acquisition mode to high resolution
obj.write('acquire:type hresolution');
% Set the waveform transfer format to be of type WORD (2 bytes
% per data point)
obj.write('waveform:format WORD');
% Retrieve data from Measurement Record (p. 647)
obj.write('waveform:points:mode Normal');
% Retrieve ALL data points (p. 645)
obj.write('waveform:points MAX');
% Returned data should be unsigned integers (p. 660)
obj.write('waveform:unsigned 1');
% Set the bytes mode
obj.write('waveform:byteorder LSBFirst');
% Set the Voltage Range Scaling
for i = 1:length(channels)
if(isa(chanVoltRange{i}, 'char') && strcmp(chanVoltRange{i},'auto'))
obj.write([':autoscale channel' num2str(channels(i),'%i')]);
elseif(length(chanVoltRange{i})==2)
vRange = chanVoltRange{i}(2)-chanVoltRange{i}(1);
vOffset = vRange/2 + chanVoltRange{i}(1);
obj.write([':Channel' num2str(channels(i),'%i') ':Range ' num2str(vRange,'%G') 'V']);
obj.write([':Channel' num2str(channels(i),'%i') ':Offset ' num2str(vOffset,'%G') 'V']);
end
end
% Set the Time Range
obj.write(['Timebase:Range ' num2str(timeRange, '%G')]);
% Build a string of all the channels to acquire
chans2acquire = '';
for(curChannel = channels)
chans2acquire = [chans2acquire 'Channel' num2str(curChannel, '%i') ','];
end
% Strip the trailing comma off the end of the string
chans2acquire = chans2acquire(1:end-1);
% Perform the acquisition
obj.write([':digitize ' chans2acquire]);
for(curChannel = channels)
% Compute the string value of the current channel.
curChanString = num2str(curChannel, '%i');
% Select the channel to download from
obj.write([':waveform:source chan', curChanString]);
% Download the data
obj.write(':waveform:data?');
% First returned byte should be the character '#'
% See p. 47 of Programmer's Manual
if(not(strcmp(fscanf(obj.instr, '%c',1),'#')))
disp("ACQUISITION ERROR: First byte returned not a '#'. Aborting ascquisition.");
return;
end
if(not(strcmp(fscanf(obj.instr, '%c',1),'8')))
disp("ACQUISITION ERROR: Second byte returned not an '8'. Aborting ascquisition.");
return;
end
% The next 8 characters determine the size of the dataset to be
% transferred.
numBytes = str2num(fscanf(obj.instr, '%c', 8));
% Read numBytes of data (divide by 2 for uint16 data type: 2
% bytes per uint16)
data.(['chan' curChanString '_raw']) = fread(obj.instr,numBytes/2,'uint16');
% Next, query for the preamble block (p.649)
obj.write(':Waveform:preamble?');
preambleDataString = fscanf(obj.instr);
% Process the collected data
data.preambleData = KS_DSOX1204G.processPreambleData(preambleDataString);
% Find the sampling frequency
data.Fs = 1/data.preambleData.x_increment;
% Convert Channel 1 signal to voltage.
data.(['Chan' curChanString '_V']) = KS_DSOX1204G.processChannelVoltageSignal(data.(['chan' curChanString '_raw']), data.preambleData);
% Determine if the signal has clipped.
% Note: The Programmer's Guide lists the following clipping
% values on p. 642:
% Spec. Clipping Low: 0x0001 (0d1)
% Spec. Clipping High: 0xFFFF (0d65535)
% In practice, this seems to be incorrect. I obtained the
% following clipping values from using the scope:
% Actual Clipping Low: 240
% Actual Clipping High: 65264
% We will use these experimentally determined values to
% determine if the signal has clipped or not.
clip_low = min(data.(['chan' curChanString '_raw'])) <= 240;
clip_high = max(data.(['chan' curChanString '_raw'])) >= 65264;
data.Clipping.(['Chan' curChanString '_Low']) = clip_low;
data.Clipping.(['Chan' curChanString '_High']) = clip_high;
if(clip_low || clip_high)
warning(['Clipping has been detected in the captured signal for channel ' curChanString '.']);
end
% Remove the raw data field
data = rmfield(data, ['chan' curChanString '_raw']);
end
% Compute the Timeseries
data.Time_s = (0:data.preambleData.points-1)'*data.preambleData.x_increment;
end
% Waveform Generator Functionality
function wave_startSine(obj, freq, amplitude, offset)
% wave_startSine(freq, amplitude, offset)
% This function configures and starts the wave generator, producing
% a sine wave with the specified frequency, amplitude, and DC
% offset.
% Input Arguments:
% freq: The desired sine wave frequency, given as a double
% [double] in Hz.
% amplitude: The desired sine wave amplitude, given as a double
% [double] in Volts.
% offset: The desired DC voltage offset of the sine wave,
% [double] given as a double in Volts.
% Ensure connection
obj.connect();
% Reset to default settings
obj.write(':wgen:rst');
% Set the wave generator to sine mode
obj.write(':wgen:function sinusoid');
% Set the Frequency
obj.write([':wgen:frequency ' num2str(freq, '%G')]);
actFreq = obj.query(':wgen:frequency?');
if(freq ~= str2num(actFreq))
warning(['The requested frequency of ' num2str(freq) '[Hz]' ...
' was not successfully applied. The scope is using a ' ...
'frequency value of ' actFreq '[Hz]']);
end
% Set the Amplitude
obj.write([':wgen:voltage ' num2str(amplitude, '%G')]);
actAmplitude = obj.query(':wgen:voltage?');
if(amplitude ~= str2num(actAmplitude))
warning(['The requested amplitude of ' num2str(amplitude) '[V]' ...
' was not successfully applied. The scope is using an ' ...
'amplitude value of ' actAmplitude '[V]']);
end
% Set the DC Offset
obj.write([':wgen:voltage:offset ' num2str(offset, '%G')]);
actOffset = (obj.query(':wgen:voltage:offset?'));
if(offset ~= str2num(actOffset))
warning(['The requested offset of ' num2str(offset) '[V]' ...
' was not successfully applied. The scope is using an ' ...
'offset value of ' actOffset '[V]']);
end
% Enable the waveform generator
obj.write(':wgen:output ON');
end
function wave_stop(obj)
% This function simply turns off the waveform generator.
% Ensure connection
obj.connect();
% Stop the waveform
obj.write(':wgen:output OFF');
end
% Frequency Response Analysis Functionality
function data = fra_run(obj, fRange, points)
% fra_run(fRange, points)
% This function runs the frequency response analysis on the
% oscilloscope, returning the FRA data.
% Input Arguments:
% fRange: A a matrix containing the minimum and maximum
% [1x2 double] frequencies to be swept between [min, max]
% points: The total number of frequency points in the
% [doublw] sweep. This should be an integer.
ch_in = 1; % Input Scope Channel (System Input)
ch_out = 2; % Output Scope Channel (System Response)
timeout_s = 120; % Operation will timeout after this long
% Enable FRA Mode
obj.write(':FRAnalysis:Enable 1');
% Set to Sweep Mode
obj.write(':FRAnalysis:Frequency:Mode Sweep');
% Set the input and output channels
obj.write([':FRAnalysis:Source:Input Channel' num2str(ch_in,'%i')]);
obj.write([':FRAnalysis:Source:Output Channel' num2str(ch_out,'%i')]);
% Set the lower and upper bounds of the frequency sweep range
obj.write([':FRAnalysis:Frequency:Start ' num2str(fRange(1),'%G')]);
obj.write([':FRAnalysis:Frequency:Stop ' num2str(fRange(2),'%G')]);
% Set the number of frequency points
obj.write([':FRAnalysis:Sweep:Points ' num2str(points, '%i')]);
% Check ESR to clear prior to running analysis
obj.checkESR;
% Run the analysis
obj.write(':FRAnalysis:RUN');
% Block until complete (Check ESR Register, bit 0)
elapsed = 0;
while(elapsed<timeout_s)
esrResult = obj.checkESR;
if(esrResult(1))
break;
end
pause(.1);
elapsed = elapsed + .1;
end
% Collect the results from the scope
rawFRAdata = obj.query(':FRAnalysis:DATA?');
data = KS_DSOX1204G.processFRAData(rawFRAdata);
end
function result = checkESR(obj)
obj.write('*ESR?');
result = bitget(fscanf(obj.instr, '%u'),1:8);
end
end
methods (Static)
function result = processPreambleData(data)
pData = split(strip(data,'right', newline), ',');
pSettings.format = pData(1);
pSettings.type = pData(2);
pSettings.points = str2double(pData(3));
pSettings.count = pData(4);
pSettings.x_increment = str2double(pData(5));
pSettings.x_origin = str2double(pData(6));
pSettings.x_reference = str2double(pData(7));
pSettings.y_increment = str2double(pData(8));
pSettings.y_origin = str2double(pData(9));
pSettings.y_reference = str2double(pData(10));
result = pSettings;
end
% Takes the raw data from a channel reading and the preamble data
% and computes the voltage signal for the channel reading
function result = processChannelVoltageSignal(resultData, preambleData)
result = ((resultData-preambleData.y_reference)*preambleData.y_increment) + preambleData.y_origin;
end
% Computes the timeseries data from the preamble data packet
function result = getTimeSeries(preambleData)
result = 0;
end
function result = processFRAData(data)
% Split the complete string into lines by the newline character
lines = splitlines(data);
% Find the number of total datapoints
N = length(lines)-2;
% Initialize the result struct
result = struct();
result.Frequency = zeros(N,1);
result.Gain_DB = zeros(N,1);
result.Phase_Deg = zeros(N,1);
% Process each data line (Ignore first and last lines)
for i = 2:length(lines)-1
values = split(lines{i} ,',');
result.Frequency(i-1,1) = str2num(values{2}); % Freq
result.Gain_DB(i-1,1) = str2num(values{4}); % Gain, DB
result.Phase_Deg(i-1,1) = str2num(values{5}); % Phase
end
end
end
end
%% Notes
% Initialization:
% Set Channel Configuration, channel labels, threshold voltages, trigger
% specification, trigger mode, timebase, and acquisition type
% (:Autoscale) quickly and automatically scales the scope for the given
% waveform.
% (*RST) resets the instrument to a preset state
% Capturing Data
% Use the :DIGitize command to collect data. It will clear the waveform
% buffer and start the acquisition process. Acquisition continues until
% memory buffer is full, then stops.
% Data Analysis
% Use the :WAVeform commands to transfer data to the controller.
% Use the :Waveform:Preamble? command to get information about the data
% Data Conversion:
% voltage = [(data value - Y_Reference)*Y_increment] + Y_Origin