Files
imdd_silas/Classes/05_Lab/AwgKeysight.m
2025-04-16 09:18:18 +02:00

454 lines
17 KiB
Matlab
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 = 256;
obj.sampleMemorySize = 512000; %see documentation "strg+f: granularity"
case awg_model.M8199A_ILV
obj.numChannels = 2;
obj.waveformGranularity = 512; %see documentation "strg+f: granularity"
obj.sampleMemorySize = 1024000;
case awg_model.M8199B
obj.numChannels = 2;
obj.waveformGranularity = 512; %see documentation "strg+f: granularity"
obj.sampleMemorySize = 1024000;
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
fill_length = (obj.waveformGranularity-rem(length(signal{s}),obj.waveformGranularity));
segment__fill_samples = signal{s}(:,1:fill_length);
signal{s} = [signal{s} segment__fill_samples];
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),' too long was truncated to ',num2str(obj.sampleMemorySize),' to fit into AWG sample memory! Reduce Num Symbols or Sampling Rate!'])
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
v = visadev('TCPIP0::Zizou::hislip2::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 100 MHz
obj.writeNcheck(v, ':OUTPut:FREQuency ''M1.RefClkOut'',100000000');
% :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