450 lines
17 KiB
Matlab
450 lines
17 KiB
Matlab
classdef AwgKeysight
|
||
|
||
% AWG implementation only for Keysight devices:
|
||
% a) M8196A (92 GSa/s 4Ch)
|
||
% b) M8199A (128 GSa/s 4Ch) % TODO
|
||
% c) M8199A_ILV (256 GSa/s 2Ch) % TODO
|
||
% d) M8199B (256 GSa/s 2Ch) % Commands worked in 2023
|
||
|
||
% Summary of SCPI Commands:
|
||
% *IDN? — Identify the instrument.
|
||
% *RST — Reset the instrument.
|
||
% :SOUR:FREQ:RAST <frequency> — Set the DAC sample frequency.
|
||
% :OUTP<channel>:STAT ON — Enable output for a channel.
|
||
% :VOLT<channel>:LEVel[:IMMediate][:AMPLitude] — Set output voltage.
|
||
% :TRAC<channel>:DATA <data> — Load waveform data.
|
||
% :INIT:IMM — Start waveform generation.
|
||
|
||
% Construct Class:
|
||
% AWG = AwgKeysight("model","M8196A","fdac",92e9,"scaletodac",[1,1,1,1],"skews",[0,0,0,0],"voltages",[0,0,0,0.6]);
|
||
|
||
% Upload new Signal (use either normal array of doubles or Silas Informationsignal class)
|
||
% AWG.upload("Signal4",Signal);
|
||
|
||
|
||
properties(Access=public)
|
||
model awg_model
|
||
%fdac double
|
||
skews double
|
||
%voltages double
|
||
scaletodac logical
|
||
|
||
numChannels double
|
||
waveformGranularity double
|
||
sampleMemorySize double
|
||
|
||
numProvidedSignals double
|
||
end
|
||
|
||
properties(Access=public)
|
||
fdac double
|
||
voltages double
|
||
end
|
||
|
||
methods (Access=public)
|
||
|
||
function obj = AwgKeysight(options)
|
||
|
||
|
||
arguments
|
||
options.model awg_model
|
||
options.fdac double
|
||
options.skews double
|
||
options.voltages double
|
||
options.scaletodac logical
|
||
end
|
||
|
||
%
|
||
fn = fieldnames(options);
|
||
for n = 1:numel(fn)
|
||
try
|
||
obj.(fn{n}) = options.(fn{n});
|
||
end
|
||
end
|
||
|
||
switch obj.model
|
||
case awg_model.M8196A
|
||
obj.numChannels = 4;
|
||
obj.waveformGranularity = 128;
|
||
obj.sampleMemorySize = 512000;
|
||
case awg_model.M8199A
|
||
obj.numChannels = 4;
|
||
obj.waveformGranularity = 512;
|
||
obj.sampleMemorySize = 1024000; %to check
|
||
case awg_model.M8199A_ILV
|
||
obj.numChannels = 2;
|
||
obj.waveformGranularity = 512;
|
||
obj.sampleMemorySize = 1024000; %to check
|
||
case awg_model.M8199B
|
||
obj.numChannels = 2;
|
||
obj.waveformGranularity = 512;
|
||
obj.sampleMemorySize = 1024000; %to check
|
||
end
|
||
|
||
end
|
||
|
||
function [signal1,signal2,signal3,signal4] = upload(obj,channels,options)
|
||
|
||
arguments
|
||
obj
|
||
% leave this as it is! Important for further handling/ parsing
|
||
channels.signal1 Informationsignal = Informationsignal([])
|
||
channels.signal2 Informationsignal = Informationsignal([])
|
||
channels.signal3 Informationsignal = Informationsignal([])
|
||
channels.signal4 Informationsignal = Informationsignal([])
|
||
|
||
% add new optional arguments here
|
||
options.bla = 1;
|
||
end
|
||
|
||
% Every signal at this point is an Informationsignal!
|
||
% Argument block above casts everything to Informaionsignal...
|
||
|
||
% Count the number of nonempty Informationsignal
|
||
obj.numProvidedSignals = sum(structfun(@(x) ~isempty(x.signal), channels));
|
||
assert(obj.numProvidedSignals<=obj.numChannels,['Only ',num2str(obj.numChannels),' AWG Channels. User provided ',num2str(obj.numProvidedSignals),' waveforms!'])
|
||
|
||
% Additional check for invalid signals beyond available channels
|
||
if obj.numChannels == 2
|
||
% Check if Signal3 or Signal4 is nonempty
|
||
if ~isempty(channels.signal3.signal) || ~isempty(channels.signal4.signal)
|
||
error('Only 2 channels are available. Signals for Channel 3 and 4 should not be defined.');
|
||
end
|
||
end
|
||
|
||
% Extract the actual signals to cell array
|
||
unpackedSignals = {};
|
||
fn = fieldnames(channels);
|
||
for s = 1:obj.numChannels
|
||
unpackedSignals{s} = channels.(fn{s}).signal;
|
||
end
|
||
|
||
success = obj.upload_(unpackedSignals);
|
||
|
||
for s = 1:obj.numChannels
|
||
lbdesc = ['Upload to Awg'];
|
||
obj = channels.(fn{s}).logbookentry(lbdesc,obj);
|
||
end
|
||
|
||
signal1 = channels.signal1;
|
||
signal2 = channels.signal2;
|
||
signal3 = channels.signal3;
|
||
signal4 = channels.signal4;
|
||
|
||
end
|
||
|
||
end
|
||
|
||
|
||
|
||
|
||
|
||
methods(Access=private)
|
||
|
||
function success = upload_(obj,signal)
|
||
|
||
%%%%%%%%%%%%%%%%%%%%%%
|
||
%%% PREPARE SIGNALS %%
|
||
%%%%%%%%%%%%%%%%%%%%%%
|
||
|
||
for s = 1:obj.numChannels
|
||
|
||
% assure laying row vector
|
||
signal{s} = reshape(signal{s},[1, length(signal{s})]);
|
||
|
||
% set to zeros if voltage == 0
|
||
if obj.voltages(s) == 0
|
||
signal{s} = signal{s} .* 0;
|
||
end
|
||
|
||
%scale to max DAC resolution
|
||
if obj.scaletodac(s)
|
||
factor = max(abs(signal{s}));
|
||
if factor~=0
|
||
signal{s} = signal{s}./factor;
|
||
end
|
||
end
|
||
|
||
% clip warning (if autoscale is off)
|
||
if ~isempty(signal{s})
|
||
clip(s) = sum(signal{s}>1);
|
||
if clip(s)
|
||
warning(['Clipwarning CH ',num2str(s),'. Clipped Samples', num2str(clip(s))]);
|
||
end
|
||
end
|
||
|
||
% The waveform granularity is e.g. 128. This means that the waveform length must be a multiple of this granularity. The minimum waveform length is 128 samples.
|
||
if rem(length(signal{s}),obj.waveformGranularity) ~= 0
|
||
signal{s} = [signal{s} signal{s}(:,1:(obj.waveformGranularity-rem(length(signal{s}),obj.waveformGranularity)))];
|
||
|
||
debug = 0;
|
||
if debug
|
||
warning(['data should have a length multiple of ',num2str(obj.waveformGranularity),'!']);
|
||
end
|
||
end
|
||
|
||
% Digitally apply skews to the signals
|
||
if obj.skews(s) > 0
|
||
delay_sec = obj.skews(s)*1e-12;
|
||
signal{s} = delayseq(signal{s},delay_sec,obj.fdac);
|
||
end
|
||
|
||
% Scale to DAC values
|
||
signal{s} = int8(round(127 * signal{s}));
|
||
|
||
% Check signal length, truncate if too long for AWG
|
||
siglen = min(length(signal{s}),obj.sampleMemorySize);
|
||
signal{s} = signal{s}(1:siglen);
|
||
if siglen==obj.sampleMemorySize
|
||
warning(['Signal ',num2str(s),' was truncated to ',num2str(obj.sampleMemorySize),' to fit into AWG sample memory'])
|
||
end
|
||
|
||
|
||
end
|
||
|
||
%%%%%%%%%%%%%%%%%%%%%%
|
||
%%% CONNECT TO AWG %%%
|
||
%%%%%%%%%%%%%%%%%%%%%%
|
||
|
||
% read/write lock file to our ntserver/scratch
|
||
try
|
||
obj.check_lock(char(obj.model),1);
|
||
obj.lock_device(char(obj.model));
|
||
catch
|
||
warning('Could not reach ntserver to write lock!')
|
||
end
|
||
|
||
% Open Visa
|
||
switch obj.model
|
||
case awg_model.M8196A
|
||
v = visadev('TCPIP0::zizou.tf.uni-kiel.de::hislip0::INSTR');
|
||
case awg_model.M8199B
|
||
v = visadev('TCPIP0::localhost::hislip0::INSTR');
|
||
case awg_model.M8199A_ILV
|
||
v = visadev('TCPIP0::Zizou::hislip0::INSTR');
|
||
case awg_model.M8199A_ILV
|
||
v = visadev('TCPIP0::Zizou::hislip0::INSTR');
|
||
end
|
||
|
||
debug = 0;
|
||
|
||
if debug
|
||
disp(['Connected to Instrument: ',char(v.Vendor),' ',char(v.Model),' SerNo:',char(v.SerialNumber)]);
|
||
end
|
||
|
||
% %check if channel config matches (ONLY FOR M8196...)
|
||
% writeline(v,'*opt?');
|
||
% aw=readline(v);
|
||
% assert(obj.numChannels==sscanf(aw, '%f'),['Wrong Channel config. AWG response: ',num2str(sscanf(aw, '%f')), ' Channels']);
|
||
|
||
|
||
if obj.model == awg_model.M8196A
|
||
|
||
obj.writeNcheck(v,':INST:DACM FOUR');
|
||
obj.writeNcheck(v,':ABORt');
|
||
obj.writeNcheck(v,sprintf(':FREQuency:RASTer %.15g;', obj.fdac));
|
||
|
||
elseif obj.model == awg_model.M8199B || obj.model == awg_model.M8199A || obj.model == awg_model.M8199A_ILV
|
||
|
||
obj.writeNcheck(v,':ABORt ''M2''');
|
||
obj.writeNcheck(v,sprintf(':FREQ ''M1.ClkGen'', %.15g;', obj.fdac));
|
||
|
||
end
|
||
|
||
%%%%%%%%%%%%%%%%%%%%%%
|
||
%%% UPLOAD SIGNALS %%%
|
||
%%%%%%%%%%%%%%%%%%%%%%
|
||
|
||
for chan = 1:obj.numChannels
|
||
|
||
segm_num = 1; %we usually have one segment
|
||
segm_len = length(signal{chan});%there are max signl lengths
|
||
|
||
if segm_len > 0
|
||
|
||
if obj.model == awg_model.M8196A
|
||
|
||
% 1) TRAC DEl - delete the previous waveform
|
||
obj.writeNcheck(v,sprintf(':TRACe%d:DELete %d', chan, segm_num));
|
||
|
||
% 2) TRAC DEF - define properties of waveform
|
||
obj.writeNcheck(v,sprintf(':TRACe%d:DEFine %d,%d', chan, segm_num, segm_len));
|
||
|
||
% 3) TRAC DATA - load the new waveform
|
||
offset = 0;
|
||
cmd = sprintf(':TRACe%d:DATA %d,%d,', chan, segm_num, offset);
|
||
binblockwrite(v, signal{chan}, 'int8', cmd);
|
||
aw = obj.writeReceiveCheck(v,'*opc?');
|
||
|
||
% 4) SET VOLTAGE (not possible to set 0v)
|
||
obj.writeNcheck(v, sprintf(':VOLTage%d:AMPLitude %g', chan, obj.voltages(chan)));
|
||
|
||
% 5) SET OUTPUTS ON/ OFF (generation not started at this point)
|
||
obj.writeNcheck(v, sprintf(':OUTPut%d ON', chan));
|
||
|
||
elseif obj.model == awg_model.M8199B || obj.model == awg_model.M8199A || obj.model == awg_model.M8199A_ILV
|
||
|
||
% 1) TRAC DEl - delete the previous ch waveform
|
||
obj.writeNcheck(v,sprintf(':TRAC:DEL ''M2.DataOut%d'', %d', chan, segm_num));
|
||
|
||
% 2) TRAC DEF - define properties of waveform
|
||
obj.writeNcheck(v,sprintf(':TRAC:DEF ''M2.DataOut%d'', %d,%d', chan, segm_num, segm_len));
|
||
% xfprintf(f, sprintf(':TRAC:DEF ''M2.DataOut1'', %d,%d', segm_num, len));
|
||
|
||
% 3) TRAC DATA - load the new waveform
|
||
|
||
% Example command: :TRAC:DATA 'M1.DataOut1', 1, 0 , -127,-126,,-1,0,1,,127,-127,,-1,0,1,,127
|
||
% means : :TRAC:DATA 'identifier' , <segment_id>, <offset>, <block>|<numeric_values>
|
||
offset = 0;
|
||
cmd = sprintf(':TRAC:DATA ''M2.DataOut%d'', %d,%d,',chan, segm_num, offset);
|
||
binblockwrite(v, signal{chan}, 'int8', cmd);
|
||
|
||
% 4) SET VOLTAGE (not possible to set 0v)
|
||
obj.writeNcheck(v, sprintf(':VOLT:AMPL ''M2.DataOut%d'', %gv',chan, obj.voltages(chan)));
|
||
% xfprintf(f, ':VOLT:AMPL ''M2.DataOut2'', 0.1v');
|
||
|
||
% 5) SET OUTPUTS ON/ OFF
|
||
obj.writeNcheck(v, sprintf(':OUTP ''M2.DataOut%d'', ON',chan)); %sioe 11.23: Hier habe ich absichtlich auf OFF gestellt, wenn Kanal nicht bentigt!
|
||
|
||
|
||
|
||
end
|
||
|
||
else
|
||
% If no signal defined, turn off channel
|
||
if obj.model == awg_model.M8199B || obj.model == awg_model.M8199A || obj.model == awg_model.M8199A_ILV
|
||
obj.writeNcheck(v, sprintf(':OUTP ''M2.DataOut%d'', OFF',chan));
|
||
end
|
||
|
||
end
|
||
|
||
end
|
||
|
||
% Set global output on/ off to start generation
|
||
if obj.model==awg_model.M8196A
|
||
|
||
obj.writeNcheck(v,':INIT:IMMediate');
|
||
|
||
elseif obj.model == awg_model.M8199B || obj.model == awg_model.M8199A || obj.model == awg_model.M8199A_ILV
|
||
|
||
% SET Ref clock out (which is conn. to scope) to 10 MHz
|
||
obj.writeNcheck(v, ':OUTPut:FREQuency ''M1.RefClkOut'',10000000');
|
||
|
||
% :OUTP:GLOB 'M1.System', OFF
|
||
obj.writeNcheck(v, ':OUTP:GLOB ''M1.System'', ON');
|
||
obj.writeNcheck(v, ':OUTP:GLOB ''M2.System'', ON');
|
||
%obj.writeNcheck(v,':INIT:IMMediate ''M2''');
|
||
end
|
||
|
||
% operation ceomplete (opc)
|
||
obj.writeNcheck(v,'*OPC');
|
||
aw = obj.writeReceiveCheck(v,'*opc?');
|
||
|
||
% close visa connection
|
||
delete(v);
|
||
|
||
% delete lock file to our ntserver/scratch
|
||
try
|
||
obj.release_lock(char(obj.model));
|
||
catch
|
||
warning('Could not reach ntserver to write lock!')
|
||
end
|
||
|
||
% at this point the signal upload should be successful :-)
|
||
success = 1;
|
||
|
||
end
|
||
|
||
function writeNcheck(~,visaobj,command)
|
||
% 1) send command to intstument
|
||
% 2) check if everything is okay
|
||
|
||
writeline(visaobj,char(command));
|
||
|
||
writeline(visaobj,':syst:err?');
|
||
err_aw=readline(visaobj);
|
||
assert(strfind(err_aw,"No error"),['AWG System Error? after command: ', char(command)])
|
||
|
||
end
|
||
|
||
function [aw]=writeReceiveCheck(~,visaobj,command)
|
||
% 1) send command to intstument
|
||
% 2) receive answer from instrument
|
||
% 3) check if everything is okay
|
||
|
||
writeline(visaobj,char(command));
|
||
aw = readline(visaobj);
|
||
|
||
writeline(visaobj,':syst:err?');
|
||
err_aw=readline(visaobj);
|
||
assert(strfind(err_aw,"No error"),['AWG System Error? after command: ', char(command)])
|
||
|
||
end
|
||
|
||
function check_lock(~,aDeviceName,lock_timeout)
|
||
pause(0.2);
|
||
rdy = 0;
|
||
for n = 1:(lock_timeout*100)
|
||
pause(0.1);
|
||
if rem(n,100) == 0
|
||
display(['waiting for device lock! (' num2str(n/10) 's)']);
|
||
|
||
button = questdlg(['Lab Measurement is LOCKED! ',...
|
||
'be careful'],...
|
||
'LOCKED','Wait','Unlock','Abort Meas','Wait') ;
|
||
|
||
switch button
|
||
case {'Abort Meas' ,''}
|
||
delete(findobj('name','Simulation Status Window'));
|
||
error('Measurement aborted by the user') ;
|
||
case 'Unlock'
|
||
rdy = 1;
|
||
break;
|
||
case 'Wait'
|
||
%wait another ten secs
|
||
end
|
||
|
||
end
|
||
% file_handle = fopen(['\\ntserver\scratch\labor\lock\' aDeviceName '.lock'],'r');
|
||
file_handle = fopen([filesep, filesep, 'ntserver.tf.uni-kiel.de', filesep, 'scratch', filesep 'Labor', filesep, 'lock', filesep, aDeviceName, '.lock'],'r');
|
||
if file_handle == -1
|
||
rdy = 1;
|
||
break;
|
||
end
|
||
time_flag=datenum(clock);
|
||
dev_flag = fread(file_handle,time_flag,'double');
|
||
fclose(file_handle);
|
||
|
||
if time_flag > dev_flag+60/(24*60)
|
||
rdy = 1;
|
||
break;
|
||
end
|
||
|
||
end
|
||
|
||
if ~rdy
|
||
|
||
errordlg([aDeviceName ' is locked by someone else!!!']);
|
||
errormsg('AWG2SCOPE',[aDeviceName ' is locked by someone else!!!']);
|
||
else
|
||
end
|
||
end
|
||
|
||
function lock_device(~,aDeviceName)
|
||
file_handle = fopen([filesep, filesep, 'ntserver.tf.uni-kiel.de', filesep, 'scratch', filesep 'Labor', filesep, 'lock', filesep, aDeviceName, '.lock'],'wb');
|
||
|
||
if ~isempty(file_handle)
|
||
time_flag=datenum(clock);
|
||
fwrite(file_handle,time_flag,'double');
|
||
fclose(file_handle);
|
||
end
|
||
end
|
||
|
||
function release_lock(~,aDeviceName)
|
||
pause(0.5);
|
||
delete([filesep, filesep, 'ntserver.tf.uni-kiel.de', filesep, 'scratch', filesep 'Labor', filesep, 'lock', filesep, aDeviceName '.lock']);
|
||
end
|
||
|
||
end
|
||
end
|