diff --git a/Classes/00_signals/Signal.m b/Classes/00_signals/Signal.m index 7e1c3c6..e955679 100644 --- a/Classes/00_signals/Signal.m +++ b/Classes/00_signals/Signal.m @@ -193,14 +193,14 @@ classdef Signal end %% Add signals from one signal to another, the first object will sustain - function Difference = minus(X,y) + function Diff = minus(X,y) if isa(y,'Signal') - Difference = X; - Difference.signal = X.signal - y.signal; + Diff = X; + Diff.signal = X.signal - y.signal; elseif isnumeric(y) - Difference = X; - Difference.signal = X.signal - y; + Diff = X; + Diff.signal = X.signal - y; end end @@ -303,9 +303,11 @@ classdef Signal xlim([-obj.fs/2 obj.fs/2].*1e-9) edgetick = 2^(nextpow2(obj.fs*1e-9)); % xticks([-edgetick:16:edgetick]); - xlim([-244, 244]) - ylim([-120,10]); + xlim([100*round( min(w.*1e-9)/100,1)-10,100*round( max(w.*1e-9)/100,1)+10]) + ylim([100*round( min(p_dbm)/100,1)-3,100*round( max(p_dbm)/100,1)+3]); yticks([-200:10:10]); + grid on + grid minor legend end @@ -423,8 +425,8 @@ classdef Signal end - %% Synchronize with reference - function [obj,D,cuts] = tsynch(obj,options) + %% + function [obj,S] = tsynch(obj,options) % time sync and cut arguments obj Signal @@ -440,29 +442,36 @@ classdef Signal b = options.reference.resample("fs_in",options.fs_ref,"fs_out",obj.fs).normalize("mode","oneone").signal; %estimate delay between signals - [co,lags] = xcorr(a,b,100); + [co,lags] = xcorr(a,b); [~,pos] = max(co); D = lags(pos); - if D == 100 - warning('Check Sync: Delay is found to be 100 which is the max. windowlength is xcorr function!') + %estimate start pos of signal + maxpeaknum = floor(length(a)/length(b)); + [pks,pkpos] = findpeaks(co./max(co),'MinPeakDistance',length(b)/2,'MinPeakHeight',0.2,'NPeaks',maxpeaknum); + shifts = lags(pkpos); + + %Cut occurences of ref signal from signal + S = {}; + for c = shifts + sig = obj.delay(-c,'mode','samples'); + sig.signal = sig.signal(1:length(b)); + S{end+1,1} = sig; end - - % delay by lagging samples - obj = obj.delay(-D,'mode','samples'); - - cuts = obj.length-(options.reference.length*q); - - if cuts > 10 - warning('Check Sync: Signal difference larger than 10.') - end - - if cuts < 0 - % warning('Check Sync: Reference Signal shorter than signal to sync.') - else - % then cut out the length of the reference - obj.signal = obj.signal(1:end-cuts); + + %plot all synced signals and the ref signal + debug = 0; + if debug + figure;hold on; + for i = 1:size(S,1) + plot(S{i}.normalize('mode','oneone').signal(1000:1100),'LineWidth',0.1,'Color',[0.2157 0.4941 0.7216]); + plot(b(1000:1100),'LineWidth',1); + end end + + %return the sinal with the highest correlation... + [~,idx]=max(pks); + obj.signal = S{idx}.signal; end @@ -623,7 +632,6 @@ classdef Signal % beautify colormap(cbrewer2("Blues",4096)); - if isa(obj,'Opticalsignal') title("Optical Eye") ylabel("Power in mW"); @@ -673,41 +681,15 @@ classdef Signal - % Define properties - boxPosition = [0.15 0.86 0.2 0.05]; % Position for the first box [x y width height] - boxColor = [0.9 0.9 0.9]; % Light grey background color - boxEdgeColor = 'k'; % Black edge color - boxLineStyle = '--'; % Dashed line style - boxFontWeight = 'bold'; % Bold font - - % Create first annotation box for Power - annotation('textbox', boxPosition, ... - 'String', ['Power: ',num2str(pwr_dbm),' dBm'], ... - 'BackgroundColor', boxColor, ... - 'EdgeColor', boxEdgeColor, ... - 'LineStyle', boxLineStyle, ... - 'FontWeight', boxFontWeight, ... - 'HorizontalAlignment', 'center'); - - % Adjust position for the second box (slightly to the right) - boxPosition = [0.37 0.86 0.2 0.05]; % Adjusted position - - % Create second annotation box for PAPR - annotation('textbox', boxPosition, ... - 'String', ['PAPR(lin):',num2str(papr_),''], ... - 'BackgroundColor', boxColor, ... - 'EdgeColor', boxEdgeColor, ... - 'LineStyle', boxLineStyle, ... - 'FontWeight', boxFontWeight, ... - 'HorizontalAlignment', 'center'); - try hist_interest = plot_data(:,posxall); hist_interest_smoth = smooth(hist_interest,20); a = scatter(hist_interest_smoth+posxall,1:length(hist_interest_smoth),4,'.','MarkerEdgeColor','red'); - [pk,loc] = findpeaks(hist_interest_smoth,"MinPeakDistance",40,"NPeaks",M,"MinPeakHeight",30); + [pk,loc] = findpeaks(hist_interest_smoth,"MinPeakDistance",10,"NPeaks",M,"MinPeakHeight",30,"MinPeakProminence",10); + scatter(posxall,loc,'red','Marker','x','LineWidth',2); + yline(loc,'Color','red','LineWidth',1,'LineStyle',':'); for i = 1:numel(loc) @@ -738,6 +720,37 @@ classdef Signal thirdboxstring = ['OMA outer:',num2str(er),' V']; end + plot_infos = 0; + if plot_infos + + % Define properties + boxPosition = [0.15 0.86 0.2 0.05]; % Position for the first box [x y width height] + boxColor = [0.9 0.9 0.9]; % Light grey background color + boxEdgeColor = 'k'; % Black edge color + boxLineStyle = '--'; % Dashed line style + boxFontWeight = 'bold'; % Bold font + + % Create first annotation box for Power + annotation('textbox', boxPosition, ... + 'String', ['Power: ',num2str(pwr_dbm),' dBm'], ... + 'BackgroundColor', boxColor, ... + 'EdgeColor', boxEdgeColor, ... + 'LineStyle', boxLineStyle, ... + 'FontWeight', boxFontWeight, ... + 'HorizontalAlignment', 'center'); + + % Adjust position for the second box (slightly to the right) + boxPosition = [0.37 0.86 0.2 0.05]; % Adjusted position + + % Create second annotation box for PAPR + annotation('textbox', boxPosition, ... + 'String', ['PAPR(lin):',num2str(papr_),''], ... + 'BackgroundColor', boxColor, ... + 'EdgeColor', boxEdgeColor, ... + 'LineStyle', boxLineStyle, ... + 'FontWeight', boxFontWeight, ... + 'HorizontalAlignment', 'center'); + annotation('textbox', boxPosition, ... @@ -749,6 +762,8 @@ classdef Signal 'HorizontalAlignment', 'center'); end + end + yticks(linspace(0,histpoints,16)); y_tickstring = sprintfc('%.2f', y_tickstring); yticklabels(y_tickstring); diff --git a/Classes/01_transmit/ChannelFreqResp.m b/Classes/01_transmit/ChannelFreqResp.m index 7c3da7f..b8da77c 100644 --- a/Classes/01_transmit/ChannelFreqResp.m +++ b/Classes/01_transmit/ChannelFreqResp.m @@ -132,7 +132,7 @@ classdef ChannelFreqResp < handle fstarget = Target.fs; - % Build new frequencie axis (with current fs) + % Build new frequency axis (with current fs) fnew = linspace(0,fstarget/2,length(Target)/2+1); fnew = fnew(2:end-1); @@ -154,7 +154,7 @@ classdef ChannelFreqResp < handle %smoothing takes time and sometimes the result looks odd, %however the performance is most of the time better - smoothing = 1; + smoothing = 0; if smoothing iH = smooth(fnew,iH,0.1,'loess')'; end @@ -163,16 +163,10 @@ classdef ChannelFreqResp < handle % angle (-pi,pi) at lowest frequency iH = iH.*exp(-1j*angle(iH(1))); % to be checked (<- not from silas, so what needs to be checked?) - % Phase difference between lowest and hiughest frequency - % component -> but what is this for? dPhase is not used... - noncausal = 0; - if noncausal - dPhase = angle(iH(end))-angle(iH(1)); - end % normalize complex freq. resp. by magnitude at the first % five frequencies -> should be the vaue at f=0=DC component? - iH = iH./mean(abs(iH(1:100))); %why 1:5?? + iH = iH./mean(abs(iH)); %why 1:5?? % set maximum amplification % set als values higher than hmax to hmax and keep the @@ -191,19 +185,20 @@ classdef ChannelFreqResp < handle % iH(1) is DC ---> iH(end) is High Freq. H_inv = [iH(1) iH fliplr(conj(iH)) conj(iH(1))]; + H_inv = [iH(1) iH 0 fliplr(conj(iH))]; - H_inv = [iH(1) iH iH(end) fliplr(conj(iH)) conj(iH(1))]; + % H_inv = [iH(1) iH iH(end) fliplr(conj(iH)) conj(iH(1))]; obj.H_apply = H_inv; - Target.signal = real((ifft( ( fft(real( Target.signal )) .* H_inv' ) ))); - + Target.signal = real((ifft( ( fft(real( Target.signal' )) .* H_inv ) ))); + Target.signal = Target.signal'; end function plot(obj) - figure(55551); + figure(55); clf; Havg = obj.H; @@ -290,6 +285,7 @@ classdef ChannelFreqResp < handle end function data = load(obj, options) + % Function to load data from a specified file and path. arguments obj @@ -334,12 +330,20 @@ classdef ChannelFreqResp < handle % Load the data from the specified file loadedData = load(fullFileName); - % Replace whole obj here.. is this save or unsave?! - fn = fieldnames(loadedData.obj); - for n = 1:numel(fn) - try - obj.(fn{n}) = loadedData.obj.(fn{n}); + if ~strcmp(fieldnames(loadedData),'obj') + % user want to load a moveit precomp file + obj.H = 1./loadedData.uFF; + obj.faxis = loadedData.f; + else + + % Replace whole obj here.. is this save or unsave?! + fn = fieldnames(loadedData.obj); + for n = 1:numel(fn) + try + obj.(fn{n}) = loadedData.obj.(fn{n}); + end end + end fprintf('Frequency response information successfully loaded from %s\n', fullFileName); @@ -373,26 +377,29 @@ classdef ChannelFreqResp < handle function [rH] = estHfromDMT(obj, data_in, ref_in) - %! Dont (circ)shift the signal here as this would remove the phase information! - %tested with a butterworth filter this exactly reconstructs the - %phase and the magnitude. However, the option is here - estimatephase = 1; - if ~estimatephase + Nfft = 2*obj.Nacq + 1 ; + data_in = reshape(data_in,1,length(data_in)); + ref_in = reshape(ref_in,1,length(ref_in)); + + if length(data_in) ~= length(ref_in) % 0. cross-correlate the received signal with its reference to extract the periods - corr = abs(ifft( fft(data_in(1:length(ref_in))).* conj(fft(ref_in)) )) ; + corr = abs(ifft( fft(data_in(1:length(ref_in))) .* conj(fft(ref_in)) )) ; % find max [~, peak] = max(corr) ; peak=max(1,peak-1); - data_in = circshift(data_in,-peak) ; - %Y = data_in(peak:peak+(Nfft+obj.Ncp)*obj.Navg-1) ; + %! Dont (circ)shift the signal here (if signals are equally long) as this would remove the phase information! + %tested with a butterworth filter this exactly reconstructs the + %phase and the magnitude. However, the option is here + + % data_in = circshift(data_in,-peak) ; + data_in = data_in(peak:peak+(Nfft+obj.Ncp)*obj.Navg-1) ; end % 1. Reshape signal to a matrix to support noise averaging - Nfft = 2*obj.Nacq + 1 ; - + Y = reshape(data_in, Nfft+obj.Ncp, obj.Navg).' ; X = reshape(ref_in, Nfft + obj.Ncp, obj.Navg).' ; diff --git a/Classes/01_transmit/PAMsource.m b/Classes/01_transmit/PAMsource.m index 1566ee0..a4e1723 100644 --- a/Classes/01_transmit/PAMsource.m +++ b/Classes/01_transmit/PAMsource.m @@ -59,7 +59,7 @@ classdef PAMsource end end - if boolean(obj.applypulseform) && isempty(obj.pulseformer) + if obj.applypulseform && isempty(obj.pulseformer) warning('No Pulseformer given. Proceeding with RRC and alpha 0.05'); options.pulseformer = Pulseformer("fsym",obj.fsym,"fdac",obj.fs_out,"pulse","rrc","pulselength",16,"rrcalpha",0.05); end diff --git a/Classes/04_DSP/Equalizer/EQ.m b/Classes/04_DSP/Equalizer/EQ.m index b999c69..888f85a 100644 --- a/Classes/04_DSP/Equalizer/EQ.m +++ b/Classes/04_DSP/Equalizer/EQ.m @@ -110,6 +110,9 @@ classdef EQ [signalclass_in.signal,error_log] = obj.process_(signalclass_in.signal', reference_signalclass_in.signal'); signalclass_in.signal = signalclass_in.signal'; + + signalclass_in.fs = reference_signalclass_in.fs; + % append to logbook lbdesc = ['EQ ']; signalclass_in = signalclass_in.logbookentry(lbdesc); diff --git a/Classes/04_DSP/Equalizer/FFE_adaptive_decision.m b/Classes/04_DSP/Equalizer/FFE_adaptive_decision.m index 892305a..f1f9c9d 100644 --- a/Classes/04_DSP/Equalizer/FFE_adaptive_decision.m +++ b/Classes/04_DSP/Equalizer/FFE_adaptive_decision.m @@ -124,6 +124,7 @@ classdef FFE_adaptive_decision < handle d_hat(symbol,1) = obj.constellation(symbol_idx); end + y_buffer(symbol_idx,1) = y(symbol); y_buffer(symbol_idx,:) = circshift(y_buffer(symbol_idx,:),1); diff --git a/Classes/04_DSP/Equalizer/VNLE.m b/Classes/04_DSP/Equalizer/VNLE.m index 0901fa5..397d58e 100644 --- a/Classes/04_DSP/Equalizer/VNLE.m +++ b/Classes/04_DSP/Equalizer/VNLE.m @@ -148,18 +148,19 @@ classdef VNLE < handle y(symbol,1) = obj.e.' * x_in; % Calculating output of LMS __ * | if training - err(symbol) = y(symbol) - d(symbol); % Instantaneous error + err = y(symbol) - d(symbol); % Instantaneous error else [~,symbol_idx] = min(abs(y(symbol) - obj.constellation)); % decision for closest constellation point d_hat(symbol,1) = obj.constellation(symbol_idx); - err(symbol) = y(symbol) - d_hat(symbol); % Instantaneous error + err = y(symbol) - d_hat(symbol); % Instantaneous error end if ~all(mu==0,'all') %mu has not only zeros - obj.e = obj.e - (mu * err(symbol) * x_in) ; % Weight update rule of LMS + % obj.e = obj.e - (mu * err * x_in) ; % Weight update rule of LMS + obj.e = obj.e - ( (mu * x_in) * err ) ; % Weight update rule of LMS else normalizationfactor = (x_in.' * x_in); - obj.e = obj.e - err(symbol) * x_in / normalizationfactor; % Weight update rule of NLMS + obj.e = obj.e - err * x_in / normalizationfactor; % Weight update rule of NLMS end if mod(sample,100) == 1 && showviz @@ -173,7 +174,7 @@ classdef VNLE < handle drawnow; end - obj.error(epoch,symbol) = err(symbol) * err(symbol)'; % Instantaneous square error + obj.error(epoch,symbol) = err * err'; % Instantaneous square error end end diff --git a/Classes/05_Lab/AwgKeysight.m b/Classes/05_Lab/AwgKeysight.m new file mode 100644 index 0000000..f02a7c5 --- /dev/null +++ b/Classes/05_Lab/AwgKeysight.m @@ -0,0 +1,419 @@ +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 + + 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 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); + + 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)))]; + warning(['data should have a length multiple of ',num2str(obj.waveformGranularity),'!']); + 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.M8196A + v = visadev('TCPIP0::localhost::hislip0::INSTR'); + end + + disp(['Connected to Instrument: ',char(v.Vendor),' ',char(v.Model),' SerNo:',char(v.SerialNumber)]); + + %check if channel config matches + 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'''); %war auf M2, das wird wohl der CH sein oder? + 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 waveform + xfprintf(f, sprintf(':TRAC:DEL ''M2.DataOut1'', %d', segm_num), 1); + xfprintf(f, sprintf(':TRAC:DEL ''M2.DataOut2'', %d', segm_num), 1); + + % 2) TRAC DEF - define properties of waveform + xfprintf(f, sprintf(':TRAC:DEF ''M2.DataOut1'', %d,%d', segm_num, len)); + xfprintf(f, sprintf(':TRAC:DEF ''M2.DataOut2'', %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' , , , | + + cmd = sprintf(':TRAC:DATA ''M2.DataOut1'', %d,%d,', segm_num, offset); + binblockwrite(f, data(1+offset:offset+len), dataFormat, cmd); + + cmd = sprintf(':TRAC:DATA ''M2.DataOut2'', %d,%d,', segm_num, offset); + binblockwrite(f, data(1+offset:offset+len), dataFormat, cmd); + + % 4) SET VOLTAGE (not possible to set 0v) + xfprintf(f, sprintf(':VOLT:AMPL ''M2.DataOut1'', %gv', awg_volt(1))); + xfprintf(f, sprintf(':VOLT:AMPL ''M2.DataOut2'', %gv', awg_volt(2))); + % xfprintf(f, ':VOLT:AMPL ''M2.DataOut2'', 0.1v'); + + % 5) SET OUTPUTS ON/ OFF + xfprintf(f, ':OUTP ''M2.DataOut1'', ON'); %sioe 11.23: Hier habe ich absichtlich auf OFF gestellt, wenn Kanal nicht bentigt! + xfprintf(f, ':OUTP ''M2.DataOut2'', ON'); + + + + 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 + + % :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 diff --git a/Classes/05_Lab/DC_supply.m b/Classes/05_Lab/DC_supply.m new file mode 100644 index 0000000..5d79681 --- /dev/null +++ b/Classes/05_Lab/DC_supply.m @@ -0,0 +1,122 @@ +classdef DC_supply + % Simple control of DC supply via fixed GBIB address. + % Agilent E3646A + % No OVP or OCP implementation + % Not tested what happens if this script asks for Voltage in higher V + % Range (8V vs. 22V) + + properties(Access=public) + active + voltage + end + + methods (Access=public) + + function obj = DC_supply(options) + + + arguments + options.active logical = true + options.voltage double = [0,0]; + end + + % + fn = fieldnames(options); + for n = 1:numel(fn) + try + obj.(fn{n}) = options.(fn{n}); + end + end + + assert(size(obj.voltage,1)==1,'Set Voltage for CH1 and CH2 must be given as vector [V1, V2]'); + assert(size(obj.voltage,2)==2,'Set Voltage for CH1 and CH2 must be given as vector [V1, V2]'); + + end + + function success = set(obj,options) + + + arguments + obj + options.voltage double = obj.voltage; + end + + success = [0,0]; + + try + %connect to device + v = visadev("GPIB1::19::INSTR"); + + disp(['Connected to Instrument: ',char(v.Vendor),' ',char(v.Model),' SerNo:',char(v.SerialNumber)]); + + cmd = 'INST:SEL?'; + writeline(v, cmd); + prev_selected_channel = readline(v); + + for ch = 1:2 + + % choose channel + cmd = ['INST:SEL OUT',num2str(ch)]; + writeline(v, cmd); + + % get current voltage level + cmd = 'VOLT?'; + writeline(v, cmd); + act_volt = str2num(readline(v)); + + % desired voltage (round to two digits after comma) + des_volt = round(options.voltage(ch),2); + + cnt = 0; + while abs(act_volt-des_volt) > 0 + + selected_channel = ['OUT',num2str(ch)]; + + % get current voltage level + cmd = 'VOLT?'; + writeline(v, cmd); + act_volt = str2num(readline(v)); + + % difference + diff_volt = act_volt-des_volt; + + % set new voltage + increment_voltage = -0.01* sign(diff_volt) ; + cmd = ['VOLT ',num2str(act_volt+increment_voltage)]; + writeline(v, cmd); + + cnt = cnt+1; + if mod(cnt,100) == 0 + wait(1); + end + + % get current voltage level + cmd = 'VOLT?'; + writeline(v, cmd); + act_volt = str2num(readline(v)); + + end + + % check if voltage is set + if act_volt ~= des_volt + hMsgBox = msgbox('An error occurred in dc supply module. Check if voltage is set correctly.'); + uiwait(hMsgBox); + else + success(ch) = 1; + end + + end + + % choose channel + cmd = ['INST:SEL ',char(strtrim(prev_selected_channel))]; + writeline(v, cmd); + + %disconnect + delete(v); + + catch + + end + end + end +end diff --git a/Classes/05_Lab/OptAtten.m b/Classes/05_Lab/OptAtten.m new file mode 100644 index 0000000..5e0e320 --- /dev/null +++ b/Classes/05_Lab/OptAtten.m @@ -0,0 +1,295 @@ +classdef OptAtten < handle + % control optical attenuator Keysight N7764a via LAN + % set parameters of optical attenuator, e.g. attenuation, wavelength, etc. and read out output power from attenuator + + properties(Access=public) + active %'Enable/Disable attenuator slots. Array position indexes detemines attenuator slot: [1->2 3->4 5->6 7->8]. Value determines mode: 0: off; 1: constant attenuation value; 2: constant output power'; + value %'Set attenuation [dB] or constant output power [dBm]. Range attenuation [0, 45], range constant output power [-50, 20]'; + speed %'Enter a desired speed for attenuation changes [dB/s]. Range: [0.1, 1000]'; + wavelength %'Enter the wavelength of the applied light [nm]. Range: [1260, 1640]'; + avgTime %'Enter a desired averaging time [ms]. Higher averaging times will increase stability of the readings but will slow down measurement speed. Range: [2, 1000]'; + dbOffset %'A non-zero attenuation offset shifts the logarithmic attenuation scale. Then, the set attenuation value differs from the internally calibrated attenuation. Range [-200, 200]'; + psetOffset %'A non-zero power offset shifts the logarithnic power scale. Then, the power reading and the set power (Pset) values differ from the internal power calibration. Range [-60, 60]'; + + atten_state + value_state + power_state + speed_state + wavelength_state + + + end + + methods (Access=public) + function obj = OptAtten(options) + + arguments + options.active double = [0,0,0,0]; + options.value double = [0,0,0,0]; + options.speed double = [1000 1000 1000 1000]; + options.wavelength double = [1550 1550 1550 1550]; + options.avgTime double = [2 2 2 2]; + options.dbOffset double = [0 0 0 0]; + options.psetOffset double = [0 0 0 0]; + end + + fn = fieldnames(options); + for n = 1:numel(fn) + try + obj.(fn{n}) = options.(fn{n}); + end + end + + end + + + function success = set(obj,options) + + arguments + obj + options.active = obj.active; %if not used as arg of .set('active',[0,0,0,0]) method the class values are used... + options.value = obj.value; + end + + try + %connect to device + v = visadev('TCPIP::134.245.243.248::INSTR'); + + disp(['Connected to Instrument: ',char(v.Vendor),' ',char(v.Model),' SerNo:',char(v.SerialNumber)]); + %Keysight Technologies, N7764A, MY49A00696, 1.13.1 + + + slot = 0; %init slot number + %loop to set values for every attenuator slot + for i = 1:length(options.active) + %convert index number i into slot number + slot = 2*i - 1; + + %check whether attenuator is used or unused + if ~options.active(i) %atten slot unused + writeline(v, [':OUTP' num2str(slot) ':STAT OFF']); + + else %atten slot used (attenuation/const output power) + + %set attenuation or output power + if options.active(i) == 1 %attenuation + if options.value(i) < 0 + options.value(i) = 0; + hMsgBox = msgbox(['Value for attenuation out of range. Please consider minimum value. Attenuation set to ' num2str(options.value(i)) 'dB']); + uiwait(hMsgBox); + elseif options.value(i) > 45 + options.value(i) = 45; + hMsgBox = msgbox(['Value for attenuation out of range. Please consider maximum value. Attenuation set to ' num2str(options.value(i)) 'dB']); + uiwait(hMsgBox); + end + writeline(v, [':OUTP' num2str(slot) ':POW:CONTR OFF']); + writeline(v, [':INP' num2str(slot) ':ATT ' num2str(options.value(i)) 'dB']); + elseif options.active(i) == 2 %constant output power + if options.value(i) < -50 + options.value(i) = -50; + hMsgBox = msgbox(['Value for output power out of range. Please consider minimum value. Output power set to ' num2str(options.value(i)) 'dBm']); + uiwait(hMsgBox); + elseif options.value(i) > 20 + options.value(i) = 20; + hMsgBox = msgbox(['Value for output power out of range. Please consider maximum value. Output set to ' num2str(options.value(i)) 'dBm']); + uiwait(hMsgBox); + end + writeline(v, [':OUTP' num2str(slot) ':POW:CONTR ON']); + writeline(v, [':OUTP' num2str(slot) ':POW ' num2str(options.value(i))]); + end + + %set value for speed + if obj.speed(i) < 0.1 + obj.speed(i) = 0.1; + hMsgBox = msgbox(['Value for speed out of range. Please consider minimum value. Speed set to ' num2str(obj.speed(i)) 'dB/s']); + uiwait(hMsgBox); + elseif obj.speed(i) > 1000 + obj.speed(i) = 1000; + hMsgBox = msgbox(['Value for speed out of range. Please consider maximum value. Speed set to ' num2str(obj.speed(i)) 'dB/s']); + uiwait(hMsgBox); + end + writeline(v, [':INP' num2str(slot) ':ATT:SPE ' num2str(obj.speed(i))]); + + %set value for wavelength + if obj.wavelength(i) < 1260 + obj.wavelength(i) = 1260; + hMsgBox = msgbox(['Value for wavelength out of range. Please consider minimum value. Wavelength set to ' num2str(obj.wavelength(i)) 'nm']); + uiwait(hMsgBox); + elseif obj.wavelength(i) > 1640 + obj.wavelength(i) = 1640; + hMsgBox = msgbox(['Value for wavelength out of range. Please consider maximum value. Wavelength set to ' num2str(obj.wavelength(i)) 'nm']); + uiwait(hMsgBox); + end + wavelength_nm = obj.wavelength(i) * 1e-9; + writeline(v, [':INP' num2str(slot) ':WAV ' num2str(wavelength_nm)]); + + %set value for averaging times + if obj.avgTime(i) < 2 + obj.avgTime(i) = 2; + hMsgBox = msgbox(['Value for average time out of range. Please consider minimum value. Average time set to ' num2str(obj.avgTime(i)) 'ms']); + uiwait(hMsgBox); + elseif obj.avgTime(i) > 1000 + obj.avgTime(i) = 1000; + hMsgBox = msgbox(['Value for average time out of range. Please consider maximum value. Average time set to ' num2str(obj.avgTime(i)) 'ms']); + uiwait(hMsgBox); + end + avgTime_sec = obj.avgTime(i) / 1000; + writeline(v, [':OUTP' num2str(slot) ':ATIM ' num2str(avgTime_sec)]); + + %set value for attenuation offset and output power offset + if obj.dbOffset(i) < -200 + obj.dbOffset(i) = -200; + hMsgBox = msgbox(['Value for attenuation offset out of range. Please consider minimum value. Attenuation offset set to ' num2str(obj.dbOffset(i)) 'dB']); + uiwait(hMsgBox); + elseif obj.dbOffset(i) > 200 + obj.dbOffset(i) = 200; + hMsgBox = msgbox(['Value for attenuation out of range. Please consider maximum value. Attenuation offset set to ' num2str(obj.dbOffset(i)) 'dB']); + uiwait(hMsgBox); + end + writeline(v, [':INP' num2str(slot) ':OFFS ' num2str(obj.dbOffset(i)) 'dB']); + + if obj.psetOffset < -60 + obj.psetOffset = -60; + hMsgBox = msgbox(['Value for output power offset out of range. Please consider minimum value. Output power offset set to ' num2str(obj.psetOffset(i)) 'dBm']); + uiwait(hMsgBox); + elseif obj.psetOffset > 60 + obj.psetOffset = 60; + hMsgBox = msgbox(['Value for output power offset out of range. Please consider maximum value. Output power offset set to ' num2str(obj.psetOffset(i)) 'dBm']); + uiwait(hMsgBox); + end + writeline(v, [':OUTP' num2str(slot) ':POW:OFFS ' num2str(obj.psetOffset(i))]); + + %turn on attenuator slot + writeline(v, [':OUTP' num2str(slot) ':STAT ON']); + + end + end + + %pause 2 seconds before measuring output power + pause(2); + + %read output power from attenuator and save to state.ouputpower + for i = 1:length(options.active) + if options.active(i) + + %convert index number i into slot number + slot = 2*i - 1; + + %read and save + writeline(v, [':READ' num2str(slot) ':POW?']); + state.outputpower(i) = sscanf(readline(v),'%f'); + end + end + + %create warning message when measured output power differs from + %set output power by 2dB. + for i = 1:length(options.active) + if options.active(i) == 2 + + %determine absolute difference + differ = abs(state.outputpower(i)-options.value(i)); + %create msgbox when necessary + if differ >= 2 + hMsgBox = msgbox(['Output Power at attenuator slot ' num2str(2) ' differs by 2dB or more!']); + uiwait(hMsgBox); + end + end + end + + %create output + silent = 0; + if silent + for i = 1:length(options.active) + if options.active(i) == 2 + % Output power mode: + %differ = abs(state.outputpower(i)-options.value(i)); + ch_info_txt = ['OptAtten: ',num2str(i),' -> Desired: ',num2str(options.value(i)),' dBm; Cur Output: ',num2str(state.outputpower(i)),'dBm']; + disp(ch_info_txt); + else + ch_info_txt = ['OptAtten: ',num2str(i),' -> Attenuated by: ',num2str(options.value(i)),' dB; Cur Output: ',num2str(state.outputpower(i)),'dBm']; + disp(ch_info_txt); + end + + end + end + + %disconnect + delete(v); + + obj.readvals(); + + catch error + + %disconnect if error occurrs + delete(v); + hMsgBox = msgbox('An error occurred while connecting or writing to the attenuator. Please try again. Otherwise, please restart MATLAB. For more information see error message in command window.'); + uiwait(hMsgBox); + rethrow(error); + + end + + end + + function readvals(obj,options) + + arguments + obj + options.print2console = 0; + end + + try + %connect to device + v = visadev('TCPIP::134.245.243.248::INSTR'); + + cnt = 1; + for s = 1:2:7 + + writeline(v, [':OUTP' num2str(s) ':POW?']); + line = readline(v); + answer = sscanf(line,'%f'); + obj.value_state(cnt) = answer; + + writeline(v, [':READ' num2str(s) ':POW?']); + line = readline(v); + answer = sscanf(line,'%f'); + obj.power_state(cnt) = answer; + + writeline(v, [':READ' num2str(s) ':POW?']); + line = readline(v); + answer = sscanf(line,'%f'); + obj.power_state(cnt) = answer; + + writeline(v, [':INP' num2str(s) ':ATT?']); + line = readline(v); + answer = sscanf(line,'%f'); + obj.atten_state(cnt) = answer; + + writeline(v, [':INP' num2str(s) ':WAV?']); + line = readline(v); + answer = sscanf(line,'%f'); + obj.wavelength_state(cnt) = answer; + + writeline(v, [':INP' num2str(s) ':WAV?']); + line = readline(v); + answer = sscanf(line,'%f'); + obj.speed_state(cnt) = answer; + + cnt = cnt+1; + end + + delete(v); + + catch error + + %disconnect if error occurrs + delete(v); + hMsgBox = msgbox('An error occurred while connecting or writing to the attenuator. Please try again. Otherwise, please restart MATLAB. For more information see error message in command window.'); + uiwait(hMsgBox); + rethrow(error); + + end + end + + end + +end diff --git a/Classes/05_Lab/ScopeKeysight.m b/Classes/05_Lab/ScopeKeysight.m new file mode 100644 index 0000000..9b33923 --- /dev/null +++ b/Classes/05_Lab/ScopeKeysight.m @@ -0,0 +1,301 @@ +classdef ScopeKeysight + %NAME Summary of this class goes here + % Detailed explanation goes here + + properties(Access=public) + model scope_model + fadc scope_fadc + channel + autoScale + extRef + removeDC + interpolate + recordLen + IPaddress + end + + methods (Access=public) + + function obj = ScopeKeysight(options) + %NAME Construct an instance of this class + % Detailed explanation goes here + + arguments + options.model scope_model = scope_model.DSAZ634A; + options.fadc scope_fadc = scope_fadc.GSa_160; + options.channel logical = [0,0,0,0]; + options.autoScale logical = 0; + options.extRef logical = 1; + options.removeDC logical = 1; + options.interpolate logical = 0; + options.recordLen double = 1000000; + options.IPaddress + end + + % + fn = fieldnames(options); + for n = 1:numel(fn) + try + obj.(fn{n}) = options.(fn{n}); + end + end + + switch obj.model + case scope_model.DSAZ634A + + case scope_model.UXR1102A + + end + + + end + + function recordedSignals = read(obj,options) + + arguments + obj + options.channel logical = obj.channel + end + + %%%%%%%%%%%%%%%%%%%%%%%% + %%% CONNECT TO SCOPE %%% + %%%%%%%%%%%%%%%%%%%%%%%% + + if isempty(obj.IPaddress) + switch obj.model + case scope_model.DSAZ634A + v=visadev('TCPIP0::keysight.tf.uni-kiel.de::inst0::INSTR');% TCPIP0::127.0.0.1::inst0::INSTR + case scope_model.UXR1102A + v=visadev('TCPIP0::testscope.tf.uni-kiel.de::inst0::INSTR');% TCPIP0::127.0.0.1::inst0::INSTR + case scope_model.UXR1104A + v=visadev('TCPIP0::134.245.243.223::inst0::INSTR'); + end + else + try + ressourceName=['TCPIP0::',char(obj.IPaddress),'::inst0::INSTR']; + v=visadev(ressourceName); + catch + error(['Could not connect to instrument using VISA. Check VISA ressourcename -> ', ressourceName]); + end + end + + disp(['Connected to Instrument: ',char(v.Vendor),' ',char(v.Model),' SerNo:',char(v.SerialNumber)]); + + % Check if Scope is ready to go + opdone = 0; + acqdone = 0; + procdone = 0; + while ~opdone || ~acqdone || ~procdone + opdone = sscanf(obj.writeReceiveCheck(v,'*opc?'), '%f'); + acqdone = sscanf(obj.writeReceiveCheck(v,'ader?'), '%f'); + procdone = sscanf(obj.writeReceiveCheck(v,'pder?'), '%f'); + pause(0.005); + end + armed = sscanf(obj.writeReceiveCheck(v,'aer?'), '%f'); + scope_state = strtrim(obj.writeReceiveCheck(v,'RSTate?')); + + % Set sample rate / activate real edge + if obj.fadc == scope_fadc.GSa_160 + obj.writeNcheck(v,':ACQuire:REDGe 1'); + if length(obj.channel)>1 + if obj.channel(2) == 1 + error("CH 2 is invalid for Real Edge configuration. ONLY use channel 1 and 3 as shown on scope! Simply use [1,0,1,0] as channel config to record 1 and 3.") + end + elseif length(obj.channel)>3 + if obj.channel(2) == 1 || obj.channel(4) == 1 + error("CH 2 is invalid for Real Edge configuration. ONLY use channel 1 and 3 as shown on scope! Simply use [1,0,1,0] as channel config to record 1 and 3.") + end + end + else + obj.writeNcheck(v,':ACQuire:REDGe 0'); + obj.writeNcheck(v,':ACQuire:SRATe %u',double(obj.fadc).*1e9); + end + + % + for n = find(obj.channel == 1) + obj.writeNcheck(v,sprintf(':CHANnel%u:DISPlay ON',n));% display captured data trace + end + + if obj.autoScale + obj.writeNcheck(v,':AUTOscale'); + for n = 1:4 + range = str2double(obj.writeReceiveCheck(v,sprintf(':CHANnel%u:RANGe?',n))); + obj.writeNcheck(v,sprintf(':CHANnel%u:RANGe %.3f',n,range/1.2)); + end + else + obj.writeNcheck(v,':SINGLE'); + end + + if obj.extRef + switch obj.model + case scope_model.DSAZ634A + obj.writeNcheck(v,':TIMebase:REFClock HFR'); + case scope_model.UXR1102A || scope_model.UXR1104A + obj.writeNcheck(v,':TIMebase:REFClock 1'); + end + + REFclock = strtrim(obj.writeReceiveCheck(v,':TIMebase:REFClock?')); + + if ~strcmp(REFclock,'HFR') + obj.writeNcheck(v,':TIMebase:REFClock ON'); + REFclock = strtrim(obj.writeReceiveCheck(v,':TIMebase:REFClock?')); + assert(~(strcmp(REFclock,'OFF') || strcmp(REFclock,'0')),'No Refclock found: Using internal') + end + + else + obj.writeNcheck(v,':TIMebase:REFClock 0'); + end + + if obj.interpolate + obj.writeNcheck(v,'ACQ:INTerpolate INT16'); + obj.writeNcheck(v,sprintf(':ACQuire:POINts %u',obj.recordLen+100)); + else + obj.writeNcheck(v,sprintf(':ACQuire:POINts %u',obj.recordLen+100)); + obj.writeNcheck(v,'ACQ:INTerpolate OFF'); + end + + obj.writeNcheck(v,':SYSTem:HEADer OFF'); % turn off system headers + obj.writeNcheck(v,':WAVEFORM:FORMAT WORD');% set the data format type to WORD + obj.writeNcheck(v,':WAVEFORM:BYTeorder LSBFirst');% Setup transfer of LSB first + obj.writeNcheck(v,':WAVEFORM:STReaming off');% Turn off waveform streaming + + for n = find(obj.channel == 1) + obj.writeNcheck(v,sprintf(':CHANnel%u:DISPlay ON',n));% display captured data trace + end + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%% Acquire Data from Scope %%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + obj.writeNcheck(v,':DIGitize'); + + recordedSignals = cell(size(obj.channel)); + for n = find(obj.channel == 1) + + % record + recordedSignals{n} = obj.readChannel(v,n); + + % cut "safety samples" that were added at the end + recordedSignals{n} = recordedSignals{n}(1:obj.recordLen); + + if obj.removeDC + recordedSignals{n} = recordedSignals{n}-mean(recordedSignals{n}); + end + + obj.writeNcheck(v,sprintf(':CHANnel%u:DISPlay ON',n)); + end + + % fill all other channels with nothing + for n = find(obj.channel == 0) + recordedSignals{n} = []; + end + + obj.writeNcheck(v,sprintf(':%s',scope_state)); + obj.writeNcheck(v,'*OPC?'); + + opdone = 0; + acqdone = 0; + procdone = 0; + while ~opdone || ~acqdone || ~procdone + opdone = sscanf(obj.writeReceiveCheck(v,'*opc?'), '%f'); + acqdone = sscanf(obj.writeReceiveCheck(v,'ader?'), '%f'); + procdone = sscanf(obj.writeReceiveCheck(v,'pder?'), '%f'); + pause(0.005); + end + + end + + end + + methods (Access=private) + + function captured_signal = readChannel(obj, v, aCH) + + Format = 'word'; + if strcmpi(Format,'word') + nBytes = 2; + blockReadType = 'int16'; + elseif strcmpi(Format,'byte') + nBytes = 1; + blockReadType = 'int8'; + else + error('Param.Format not recognized'); + end + + maxBlockSize = pow2(20); + + obj.writeNcheck(v,sprintf(':WAVeform:SOURce CHANnel%u',aCH)); % read x origin:WAVeform:SOURce + + xOrg = str2double(obj.writeReceiveCheck(v,':wav:xor?'));% read x origin + + xInc = str2double(obj.writeReceiveCheck(v,':wav:xinc?'));% read x increment => 1/fadc + + xRef = str2double(obj.writeReceiveCheck(v,':wav:xref?')); + + yOrg = str2double(obj.writeReceiveCheck(v,':wav:yor?'));% read y origin + + yInc = str2double(obj.writeReceiveCheck(v,':wav:yInc?'));% read y increment + + yRef = str2double(obj.writeReceiveCheck(v,':wav:yref?')); + + points = str2double(obj.writeReceiveCheck(v,':wav:points?')); + + % timerperiod = max(10,1e-6*points*nBytes); + % set(v,'TimerPeriod',timerperiod); + + timeout = min([max(10,1e-6*points*nBytes) 1000]); + set(v,'Timeout',timeout); + + blockSize = min(pow2(ceil(log2(points))),maxBlockSize); + set(v,'InputBufferSize',nBytes*blockSize); + + blockCount = int8(ceil(max(1,points/blockSize))); + + for block = 0:blockCount - 1 + + startPoint = double(block) * blockSize + 1; + endPoint = min((double(block + 1)) * blockSize,points); + pointsRead = endPoint - startPoint + 1; + + obj.writeNcheck(v, sprintf(':wav:data? %d,%d', startPoint, pointsRead)); + + %Wfm.data(startPoint:endPoint,1) = binblockread(v,blockReadType); %old Matlab + data(startPoint:endPoint,1) = readbinblock(v,blockReadType); + + Junk = fread(v,1,'schar'); %#ok Read out end of file character. + + end + + %scale the signal back to volt + captured_signal = data*yInc+yOrg; + + 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 i f 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 + + end +end diff --git a/Datatypes/awg_model.m b/Datatypes/awg_model.m new file mode 100644 index 0000000..7487a7b --- /dev/null +++ b/Datatypes/awg_model.m @@ -0,0 +1,10 @@ +classdef awg_model < int32 + + enumeration + M8196A (1) + M8199A (2) + M8199A_ILV (3) + M8199B (4) + end + +end \ No newline at end of file diff --git a/Datatypes/scope_fadc.m b/Datatypes/scope_fadc.m new file mode 100644 index 0000000..cc9c3c6 --- /dev/null +++ b/Datatypes/scope_fadc.m @@ -0,0 +1,22 @@ +classdef scope_fadc < int32 + + + enumeration + GSa_2_5 (2.5) %Gigasamples + GSa_4 (4) %Gigasamples + GSa_5 (5) %Gigasamples + GSa_10 (10) %Gigasamples + GSa_20 (20) + GSa_40 (40) + GSa_80 (80) + GSa_160 (160) + GSa_256 (256) + end + + methods + function val = getValue(obj) + val = double(obj); % Convert int32 to double to get the numeric value + end + end + +end \ No newline at end of file diff --git a/Datatypes/scope_model.m b/Datatypes/scope_model.m new file mode 100644 index 0000000..d5b7587 --- /dev/null +++ b/Datatypes/scope_model.m @@ -0,0 +1,9 @@ +classdef scope_model < int32 + + enumeration + DSAZ634A (1) + UXR1102A (2) + UXR1104A (3) + end + +end \ No newline at end of file diff --git a/Tests/05_Lab/DC_Supply_test.m b/Tests/05_Lab/DC_Supply_test.m new file mode 100644 index 0000000..b4855e7 --- /dev/null +++ b/Tests/05_Lab/DC_Supply_test.m @@ -0,0 +1,16 @@ +classdef DC_Supply_test < matlab.unittest.TestCase + % Test class for DC_Supply + + methods (Test) + function testExample(testCase) + % Example test case for DC_Supply + % Add your test code here + + dc_supply = DC_supply("voltage",[0,9],"active",1); + + dc_supply.set(); + + testCase.verifyTrue(true); + end + end +end diff --git a/Tests/generateTests.m b/Tests/generateTests.m index 998f0a3..f31fa47 100644 --- a/Tests/generateTests.m +++ b/Tests/generateTests.m @@ -1,10 +1,11 @@ function generateTests() if ismac - cd('/Users/silasoettinghaus/Documents/MATLAB/imdd_simulation'); + cd('/Users/silasoettinghaus/Documents/MATLAB/imdd_simulation'); else end + % Define the root folders for classes and tests classesFolder = 'Classes'; % Folder containing the class files testsFolder = 'Tests'; % Folder where test files should be created diff --git a/projects/Lab_2024/lab_minimal.m b/projects/Lab_2024/lab_minimal.m new file mode 100644 index 0000000..dbb690f --- /dev/null +++ b/projects/Lab_2024/lab_minimal.m @@ -0,0 +1,146 @@ +%%% SIR Sweep for MPI Experiment %%% +params = struct; +params.i_atten = 10; + +wh = DataStorage(params); + +wh.addStorage("ber"); +wh.addStorage("sir"); +wh.addStorage("pd_in"); + +playandrecord = 0; + +precomp_mode = 2; %0=do nothing ; 1= measure; 2=precomp active +M = 4; +pn_key = 2; +usemrds = 0; +fdac = 92e9; +fsym = 92e9; +fadc = 160e9; +rrcalpha = 0.05; +v_bias = 2.27; +i_atten = 40; +pd_in_desired = 7; + +disp(['Start Measurement of ',num2str(prod(wh.dim)),' loops...']) + +%%%%% SET Volatges %%%%%% +dcs = DC_supply("active",[1,1],"voltage",[v_bias, 9]); +dcs.set("voltage",[v_bias, 9]); + +voa = OptAtten("active",[0,2,1,1],"value",[0,pd_in_desired,0,i_atten],"wavelength",[1310,1310,1310,1310]); +voa.set('active',[0,2,1,1],'value',[0,pd_in_desired,0,i_atten]); +voa.readvals(); +%%%%% SET Volatges %%%%%% + + +if 1 + precomp_path = "C:\Users\sioe\Documents\MATLAB\imdd_simulation\projects\standard_system\"; + precomp_fn = "lab_mpi_setup_2"; +else + precomp_path = "C:\Users\sioe\Documents\MATLAB\model-collection\sioe_models\Labor_2024\Lab_PAM4\"; + precomp_fn = "precomp_bla__loop1_1"; +end + + +%%%%% Symbol Generation %%%%%% +Pform = Pulseformer("fsym",fsym,"fdac",4*fsym,"pulse","rrc","pulselength",16,"rrcalpha",rrcalpha); +[Digi_sig,Symbols,Bits] = PAMsource("fsym",fsym,"M",M,"order",18,"useprbs",1,... + "fs_out",fdac,"applyclipping",0,"clipfactor",1.7,... + "applypulseform",0,"pulseformer",Pform,"randkey",pn_key,... + "db_precode",0,... + "mrds_code",usemrds,"mrds_blocklength",512).process(); + +if precomp_mode == 1 % measure channel + freqresp = ChannelFreqResp("Nacq",1024,"Navg",64,"Ncp",63,'f_ref',Digi_sig.fs); + Digi_sig = freqresp.buildOFDM(); +elseif precomp_mode == 2 % apply precomp + freqresp = ChannelFreqResp("Nacq",1024,"Navg",64,"Ncp",63,'f_ref',Digi_sig.fs); + Digi_sig = freqresp.precomp(Digi_sig,'maxampdb',3,'loadPath',precomp_path,'fileName',precomp_fn); +end + +%%%%% AWG %%%%%% +AWG = AwgKeysight("model","M8196A","fdac",fdac,"scaletodac",[1,1,1,1],"skews",[0,0,0,0],"voltages",[0,0,0,0.62]); +AWG.upload("signal4",Digi_sig); + +for i_atten = wh.parameter.i_atten.values + + %%%%% SET ATTENUATOR %%%%%% + voa.set('active',[0,2,1,1],'value',[0,7,0,i_atten]); + + %%%%% Scope %%%%%% + SCP = ScopeKeysight("model","DSAZ634A",'autoscale',1,"fadc",'GSa_160',"channel",[1],"recordLen",2000000,"removeDC",1); + Scpe_sig = SCP.read(); + Scpe_sig = Electricalsignal(Scpe_sig{1},"fs",fadc); + + % Scpe_sig.spectrum("displayname",'Rx Signal','fignum',10); + + %%%%%% Sample to 2x fsym %%%%%% + Scpe_sig = Scpe_sig.resample("fs_in",160e9,"fs_out",2*92e9); + + if precomp_mode == 1 + freqresp.estimate(Scpe_sig,"save",true,"savePath",precomp_path,"fileName",precomp_fn); + freqresp.plot(); + end + + %%%%%% Sync Rx signal with reference %%%%%% + [Scpe_sig,S] = Scpe_sig.tsynch("reference",Symbols,"fs_ref",fsym); + + Scpe_sig.eye(fsym,M); + + %%%%% EQUALIZE %%%%%% + + Eq_ffe_only = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",1e-4,"mu_tr",0,"order",25,"sps",2,"decide",0); + ber_ffe_only = runEQ(Eq_ffe_only,Scpe_sig,Symbols,Bits,M); + + Eq_move_it = EQ("Ne",[50,7,7],"Nb",[0,0,0],"training_length",4096*2,"training_loops",5,"dd_loops",5,"K",2,"DCmu",0.05,"DDmu",[0.0004 0.0004 0.0004 0.0004 ],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",1); + ber_move_it = runEQ(Eq_move_it,Scpe_sig,Symbols,Bits,M); + + Eq_nonlin = EQ("Ne",[50,7,7],"Nb",[0,0,0],"training_length",4096*2,"training_loops",5,"dd_loops",5,"K",2,"DCmu",0.0,"DDmu",[0.0004 0.0004 0.0004 0.0004 ],"DFEmu",0.005,"FFEmu",0,"plotfinal",0,"ideal_dfe",1); + %VNLE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",[0.0004 0.0005 0.0006],"mu_tr",0,"order",[50,7,7],"sps",2,"decide",0); + ber_nonlin = runEQ(Eq_nonlin,Scpe_sig,Symbols,Bits,M); + + Eq_adaptive_decision = FFE_adaptive_decision("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",1e-4,"mu_tr",0,"order",25,"sps",2,"decide",0,"buffer_length",80); + ber_adaptive_decision = runEQ(Eq_adaptive_decision,Scpe_sig,Symbols,Bits,M); + + FFE_FFDCAVG("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",1e-4,"mu_tr",0,"order",25,"sps",2,"decide",0,"mu_buff",128); + ber_ff_dcavg = runEQ(FFE_FFDCAVG,Scpe_sig,Symbols,Bits,M); + + Eq_feedback_removal = FFE_DCremoval("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",1e-4,"mu_tr",0,"order",25,"sps",2,"decide",0,"mu_dc",0.05,"dc_buffer_len",1); + ber_feedback_removal = runEQ(Eq_feedback_removal,Scpe_sig,Symbols,Bits,M); + + + + sir = voa.power_state(3)-voa.power_state(4); + pd_in = voa.power_state(2); + + disp(['BER: ',sprintf('%.1E',ber_eq),' | SIR: ',num2str(sir),' dB | PD_in: ',num2str(pd_in),' dBm']); + + wh.addValueToStorage(ber_eq,'ber',i_atten); + wh.addValueToStorage(sir,'sir',i_atten); + wh.addValueToStorage(pd_in,'pd_in',i_atten); + +end + + + +cols = linspecer(8); + +sir_vals = wh.parameter.i_atten.values; +bers = wh.getStoValue('ber',sir_vals); + +figure(44); +a = gca; +hold on; % Retain the plot so new points can be added without complete redraw + +plot(sir_vals,bers,"LineWidth",0.5,"LineStyle","-","Marker",".","MarkerSize",15,"DisplayName","FFE only"); +yline(3.8e-3,'DisplayName','HD-FEC','LineStyle','--','HandleVisibility','off'); +xlabel('Received Optical Power (dBm)'); +ylabel('Bit Error Rate (BER)'); +title('Bit Error Rate vs. ROP'); +set(gca,'yscale','log'); +set(gca,'Box','on'); +grid on; +grid minor +legend + diff --git a/projects/Lab_2024/runEQ.m b/projects/Lab_2024/runEQ.m new file mode 100644 index 0000000..1fe66a8 --- /dev/null +++ b/projects/Lab_2024/runEQ.m @@ -0,0 +1,13 @@ + +function BER = runEQ(Eq_class,Scpe_sig,Symbols,Bits,M) + +[EQ_sig] = Eq_class.process(Scpe_sig,Symbols); + +error = EQ_sig-Symbols; +error.spectrum("displayname",['Error PSD after: ',inputname(1),' '],'fignum',564); + +Rx_bits = PAMmapper(M,0).demap(EQ_sig); + +[~,errors_bm,BER,errors] = calc_ber(Rx_bits.signal,Bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); + +end \ No newline at end of file diff --git a/projects/Lab_analysis/eq_lab.m b/projects/Lab_analysis/eq_lab.m new file mode 100644 index 0000000..5e912f4 --- /dev/null +++ b/projects/Lab_analysis/eq_lab.m @@ -0,0 +1,59 @@ + +%% Parameter to simulate and save +params = struct; + +params.M = [4]; +params.datarate = [300]; +params.rop = [0]; + +precomp_mode = 2; %0=do nothing ; 1= measure; 2=precomp active + +if ismac + precomp_path = "/Users/silasoettinghaus/Documents/MATLAB/imdd_simulation/projects/standard_system"; +else + precomp_path = "C:\Users\Silas\Documents\MATLAB\imdd_simulation\projects\standard_system\"; +end + +precomp_fn = "400G_simulative_setup"; +usemrds = 0; + +name = ['wh_',strrep(num2str(now),'.','')]; + +wh = DataStorage(params); + +wh.addStorage("ber_ffe"); + +%% Init Params +link_length = 0; %meter +pn_key = 2; +laser_linewidth = 0; + +endcnt = prod(wh.dim); +cnt=0; + +disp(['Start Simulation of ',num2str(endcnt),' loops...']) +tic + + +% SETUP HERE: %% +fsym = 92e9; + +Symbols = Informationsignal(digi_mod_out',"fs",92e9); + +Scpe_sig = Electricalsignal(awg2scope_keysight_out.no1',"fs",160e9); +Scpe_sig = Scpe_sig.resample("fs_in",160e9,"fs_out",2*92e9); + +Bits = Informationsignal(prms_out',"fs",92e9); + +%%%%%% Sync Rx signal with reference %%%%%% +[Scpe_sig,S] = Scpe_sig.tsynch("reference",Symbols,"fs_ref",fsym); + +%%%%% EQUALIZE %%%%%% +% Eq = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",1e-4,"mu_tr",0,"order",25,"sps",2,"decide",1); +Eq = VNLE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",[0.0004 0.0005 0.0006],"mu_tr",0,"order",[50,7,7],"sps",2,"decide",1); +[EQ_sig] = Eq.process(Scpe_sig,Symbols); +Rx_bits = PAMmapper(M,0).demap(EQ_sig); +[~,errors_bm,ber_ffe,errors] = calc_ber(Rx_bits.signal,Bits.signal,"skip_front",100,"skip_end",150,"returnErrorLocation",1); +disp(['BER: ',sprintf('%.1E',ber_ffe),' - - - ',num2str(fsym*1e-9),' GBd']); + + diff --git a/projects/standard_system/400G_simulative_setup.mat b/projects/standard_system/400G_simulative_setup.mat index f4a118f..0202264 100644 Binary files a/projects/standard_system/400G_simulative_setup.mat and b/projects/standard_system/400G_simulative_setup.mat differ diff --git a/projects/standard_system/imdd_minimal_2.m b/projects/standard_system/imdd_minimal_2.m index 2c6cddd..4421922 100644 --- a/projects/standard_system/imdd_minimal_2.m +++ b/projects/standard_system/imdd_minimal_2.m @@ -17,7 +17,7 @@ db_channelapproach = 0; if ismac precomp_path = "/Users/silasoettinghaus/Documents/MATLAB/imdd_simulation/projects/standard_system"; else - precomp_path = "C:\Users\Silas\Documents\MATLAB\imdd_simulation\projects\standard_system\"; + precomp_path = "C:\Users\sioe\Documents\MATLAB\imdd_simulation\projects\standard_system\"; end precomp_fn = "400G_simulative_setup"; @@ -157,7 +157,7 @@ for M = wh.parameter.M.values Scpe_sig = Scpe_sig.resample("fs_in",fadc,"fs_out",2*fsym); %%%%%% Sync Rx signal with reference %%%%%% - [Scpe_sig,D,cuts] = Scpe_sig.tsynch("reference",Symbols,"fs_ref",fsym); + [Scpe_sig,S] = Scpe_sig.tsynch("reference",Symbols,"fs_ref",fsym); %%%%% EQUALIZE %%%%%% Eq = FFE("epochs_tr",5,"epochs_dd",5,"len_tr",4096*2,"mu_dd",1e-4,"mu_tr",0,"order",25,"sps",2,"decide",0); @@ -208,7 +208,7 @@ for M = wh.parameter.M.values for i = 1:i_ rop=wh.parameter.rop.values(i); - wh.addValueToStorage(ber_ffe(i) ,'ber_ffe',M,datarate,rop); + wh.addValueToStorage(ber_ffe(i),'ber_ffe',M,datarate,rop); end @@ -235,8 +235,6 @@ a = gca; hold on; % Retain the plot so new points can be added without complete redraw plot(wh.parameter.rop.values,ber_ffe,"LineWidth",0.5,"LineStyle","-","Marker",".","MarkerSize",15,"DisplayName","FFE only"); - - yline(3.8e-3,'DisplayName','HD-FEC','LineStyle','--','HandleVisibility','off'); xlabel('Received Optical Power (dBm)'); ylabel('Bit Error Rate (BER)'); diff --git a/projects/standard_system/lab_mpi_setup.mat b/projects/standard_system/lab_mpi_setup.mat new file mode 100644 index 0000000..36decc1 Binary files /dev/null and b/projects/standard_system/lab_mpi_setup.mat differ diff --git a/projects/standard_system/lab_mpi_setup_2.mat b/projects/standard_system/lab_mpi_setup_2.mat new file mode 100644 index 0000000..d91e330 Binary files /dev/null and b/projects/standard_system/lab_mpi_setup_2.mat differ diff --git a/test/awg_test.m b/test/awg_test.m new file mode 100644 index 0000000..98bd341 --- /dev/null +++ b/test/awg_test.m @@ -0,0 +1,15 @@ + +if 0 + + AWG = AwgKeysight("model","M8196A","fdac",92e9,"scaletodac",[1,1,1,1],"skews",[0,0,0,0],"voltages",[0,0,0,0.6]); + + tx_sig = Electricalsignal(clip_out,"fs",92e9); + + tic + AWG.upload("signal4",clip_out); + toc + +end + +SCP = ScopeKeysight("model","DSAZ634A",'autoscale',1,"fadc",'GSa_160',"channel",[1],"recordLen",2000000); +signals = SCP.read();