Permalink
Cannot retrieve contributors at this time
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?
Keysight-Scope-Matlab-Connector/KS_DSOX1204G.m
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
450 lines (377 sloc)
18.7 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |