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 — Set the DAC sample frequency. % :OUTP:STAT ON — Enable output for a channel. % :VOLT:LEVel[:IMMediate][:AMPLitude] — Set output voltage. % :TRAC: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=protected) 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'); 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']); switch obj.model case awg_model.M8196A obj.writeNcheck(v,':INST:DACM FOUR'); obj.writeNcheck(v,':ABORt'); obj.writeNcheck(v,sprintf(':FREQuency:RASTer %.15g;', obj.fdac)); case awg_model.M8199B 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 switch obj.model case 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)); case awg_model.M8199B % 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' , , , | 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 switch obj.model case awg_model.M8199B obj.writeNcheck(v, sprintf(':OUTP ''M2.DataOut%d'', OFF',chan)); end end end % Set global output on/ off to start generation switch obj.model case awg_model.M8196A obj.writeNcheck(v,':INIT:IMMediate'); case awg_model.M8199B % 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