diff --git a/Classes/05_Lab/Exfo_laser.m b/Classes/05_Lab/Exfo_laser.m index c2433cf..f70ada0 100644 --- a/Classes/05_Lab/Exfo_laser.m +++ b/Classes/05_Lab/Exfo_laser.m @@ -12,6 +12,7 @@ classdef Exfo_laser < handle safety_mode end + %% Any user can call these methods to alter the laser state methods (Access=public) function obj = Exfo_laser(options) @@ -169,6 +170,10 @@ classdef Exfo_laser < handle end end %public mehtods + + + % DAU must not be able to call these funcitons outside of the class! + % Hence they are private! :-) methods (Access=private) function v = connectLaser_(obj) @@ -180,7 +185,7 @@ classdef Exfo_laser < handle configureTerminator(v, "CR", "CR"); % Set the terminator to carriage return (CR) elseif obj.lab_interface == lab_interface.gpib % Connect via GPIB adress - visastring = ['GPIB0::',obj.connection_id,'::INSTR']; + visastring = ['GPIB1::',obj.connection_id,'::INSTR']; v = visadev(visastring); else error('Interface not supported'); @@ -342,8 +347,13 @@ classdef Exfo_laser < handle command = ['CH', num2str(channel), ':L=', num2str(desiredWavelength_nm)]; writeline(serialObj, command); + if abs(obj.cur_wavelength - desiredWavelength_nm)>30 + pause(5); %it takes time to tune the laser! If this is not here it WILL break! + else + pause(1.5); + end if serialObj.Type == visalib.InterfaceType.serial - pause(0.2); + pause(1.5); response = readline(serialObj); success = contains(response, 'OK'); end diff --git a/Classes/05_Lab/LabDeviceTemplate.m b/Classes/05_Lab/LabDeviceTemplate.m new file mode 100644 index 0000000..af613e5 --- /dev/null +++ b/Classes/05_Lab/LabDeviceTemplate.m @@ -0,0 +1,83 @@ +classdef LabDeviceTemplate < handle + + % Minimal template for lab device control via VISA / SCPI + % Copy this file and adapt: + % - visaAddress + % - public API methods + % - private SCPI commands + + properties (Access = public) + active logical = true + end + + properties (Access = protected) + visaAddress string + end + + methods (Access = public) + + function obj = LabDeviceTemplate(options) + arguments + options.active logical = true + options.visaAddress string = "" + end + + % apply options + fn = fieldnames(options); + for k = 1:numel(fn) + obj.(fn{k}) = options.(fn{k}); + end + + % optional: quick connectivity check + if obj.active + v = obj.connect(); + writeline(v,"*IDN?"); + idn = readline(v); + disp("Connected to: " + string(idn)); + delete(v); + end + end + + + function success = set(obj, value) + % Example public API + v = obj.connect(); + success = obj.setValue_(v, value); + delete(v); + end + + + function value = read(obj) + % Example readback + v = obj.connect(); + value = obj.readValue_(v); + delete(v); + end + + end + + + methods (Access = protected) + + function v = connect(obj) + assert(obj.visaAddress ~= "", "No VISA address defined"); + v = visadev(obj.visaAddress); + end + + end + + + methods (Access = private) + + function success = setValue_(~, v, value) + writeline(v, sprintf("SET %g", value)); + success = true; + end + + function value = readValue_(~, v) + writeline(v, "READ?"); + value = str2double(readline(v)); + end + + end +end diff --git a/Classes/05_Lab/OSA_Advantest.m b/Classes/05_Lab/OSA_Advantest.m new file mode 100644 index 0000000..3eb2a22 --- /dev/null +++ b/Classes/05_Lab/OSA_Advantest.m @@ -0,0 +1,327 @@ +classdef OSA_Advantest < handle + % Advantest Q8384 Optical Spectrum Analyzer (GPIB) + % User inputs in nanometers + + % EXAMPLE: + + % span_nm = 70; + % lambda_c_nm = 1310; + % osa = OSA_Advantest( ... + % 'span_nm', span_nm, ... + % 'lambda_c_nm', lambda_c_nm, ... + % 'resolution_nm', 0.1, ... + % 'sampling_points', 10001 ); + % + % osa.configure(); + % [lambda_nm, psd_dBm] = osa.measure(); + % figure() + % plot(lambda_nm,psd_dBm); + + properties (Access = public) + span_nm double = 5 + resolution_nm double = 0.05 + lambda_c_nm double = 1550 + sampling_points double = 1001 + timeout_s double = 10 + + lambda_nm + psd_dBm + end + + properties (Constant) + valid_resolutions_nm = [0.01 0.02 0.05 0.10 0.20 0.50] + valid_sampling_points = [101 201 501 1001 2001 5001 10001]; + end + + properties (Access = protected) + visaAddress string = "GPIB0::3::INSTR" + end + + methods + function obj = OSA_Advantest(options) + arguments + options.visaAddress string = "GPIB0::3::INSTR" + options.span_nm double = 5 + options.resolution_nm double = 0.05 + options.lambda_c_nm double = 1550 + options.sampling_points double = 1001 + options.timeout_s double = 10 + end + + fn = fieldnames(options); + for k = 1:numel(fn) + obj.(fn{k}) = options.(fn{k}); + end + + assert(ismember(obj.resolution_nm, obj.valid_resolutions_nm), ... + 'Invalid resolution. Allowed: 0.01 | 0.02 | 0.05 | 0.10 | 0.20 | 0.50 nm'); + assert(ismember(obj.sampling_points, obj.valid_sampling_points), ... + 'Invalid # of Samp. Points. Allowed: 101 201 501 1001 2001 5001 10001'); + + v = obj.connect(); + writeline(v,"*IDN?"); + disp(readline(v)); + delete(v); + end + + function configure(obj) + assert(ismember(obj.resolution_nm, obj.valid_resolutions_nm), ... + 'Invalid resolution. Allowed: 0.01 | 0.02 | 0.05 | 0.10 | 0.20 | 0.50 nm'); + assert(ismember(obj.sampling_points, obj.valid_sampling_points), ... + 'Invalid # of Samp. Points. Allowed: 101 201 501 1001 2001 5001 10001'); + + v = obj.connect(); + + writeline(v,"FMT0,HED0,SDL0"); + writeline(v,"BUZ0"); + writeline(v,"QUI0"); + + writeline(v, sprintf("RES %gnm", obj.resolution_nm)); + writeline(v, sprintf("SPT %g", obj.sampling_points)); + writeline(v, sprintf("CEN %gnm", obj.lambda_c_nm)); + writeline(v, sprintf("SPA %gnm", obj.span_nm)); + writeline(v,"SWE 0"); + + delete(v); + end + + function [osa_x_nm, osa_y_dBm] = measure(obj) + v = obj.connect(); + + writeline(v,"CSB"); + writeline(v,"MEA1"); + + active = 1; cnt = 0; + while active + pause(1); + writeline(v,"MEA?"); + active = str2double(readline(v)); + cnt = cnt + 1; + if cnt > obj.timeout_s + delete(v); + error("OSA_Q8384:Timeout","Measurement timeout"); + end + end + + writeline(v,"PKL"); + + writeline(v,"ODN?"); + readline(v); % number of points (not strictly needed) + + writeline(v,"FMT0,HED0,SDL0"); + + writeline(v,"OSD0"); % Y-axis + osa_y_dBm = str2num(readline(v)); + + writeline(v,"OSD1"); % X-axis + osa_x_m = str2num(readline(v)); + osa_x_nm = osa_x_m.*1e9; + delete(v); + + obj.lambda_nm=osa_x_nm; + obj.psd_dBm=osa_y_dBm; + + end + + % --- Replace / extend plot() in OSA_Q8384 class --- + + function plot(obj, options) + arguments + obj + options.FigureNumber double = 1 + options.Hold logical = true + options.Title string = "OSA Spectrum" + options.LineWidth double = 0.8 + options.Grid logical = true + options.Color (1,3) double = [0 0.4470 0.7410] + options.DisplayName string = "" + end + + if isempty(obj.psd_dBm) || isempty(obj.lambda_nm) + error('Record spectrum first: run osa.measure()'); + end + + fig = figure(options.FigureNumber); + fig.Color = 'w'; + + ax = gca; + if ~options.Hold + cla(ax); + else + hold(ax,'on'); + end + + plot(ax, obj.lambda_nm, obj.psd_dBm, ... + 'LineWidth', options.LineWidth, ... + 'Color', options.Color, ... + 'DisplayName', options.DisplayName); + + xlabel(ax,'Wavelength [nm]'); + ylabel(ax,'Optical Power [dBm]'); + + if options.Title ~= "" + title(ax, options.Title); + end + + xlim(ax,[min(obj.lambda_nm) max(obj.lambda_nm)]); + yl = ylim(ax); + ylim(ax,[yl(1)-1 yl(2)+1]); + + if options.Grid + grid(ax,'on'); + grid(ax,'minor'); + end + + set(ax, ... + 'FontSize',11, ... + 'LineWidth',1, ... + 'Box','on'); + + if options.DisplayName ~= "" + legend(ax,'show','Location','best'); + end + end + + function osnr = computeOSNR(obj, options) + % OSNR computation based on ratio method (RBW-independent) + % Assumes obj.lambda_nm [nm], obj.psd_dBm [dBm/RBW] exist + + arguments + obj + options.bw_signal_nm double = 0.1 % channel bandwidth (0.1 for CW?!?!) + options.ref_bw_nm double = 0.1 % OSNR reference bandwidth + options.carrier_frequency = NaN; + options.noise_guard_nm = 1; + options.noise_window_nm =1; + options.debugplots = 0; + end + + assert(~isempty(obj.lambda_nm) && ~isempty(obj.psd_dBm), ... + 'Run measure() before computing OSNR'); + + obj.lambda_nm = obj.lambda_nm; + psd = obj.psd_dBm; + + % --- find carrier wavelength recorded in OSA --- + % options.carrier_frequency [nm], options.noise_guard_nm, options.noise_window_nm + + if ~isnan(options.carrier_frequency) + + % local search window around expected CW carrier + idx_search = obj.lambda_nm > (options.carrier_frequency - options.noise_guard_nm) & ... + obj.lambda_nm < (options.carrier_frequency + options.noise_guard_nm); + + [~, imax_local] = max(psd(idx_search)); + lambda_search = obj.lambda_nm(idx_search); + lambda_c = lambda_search(imax_local); + [~,imax] = find(obj.lambda_nm==lambda_c); + + % define local noise windows directly adjacent to signal (guarded) + idx_noise_left = obj.lambda_nm > (lambda_c - options.noise_guard_nm - options.noise_window_nm) & ... + obj.lambda_nm < (lambda_c - options.noise_guard_nm); + + idx_noise_right = obj.lambda_nm > (lambda_c + options.noise_guard_nm) & ... + obj.lambda_nm < (lambda_c + options.noise_guard_nm + options.noise_window_nm); + + idx_noise = idx_noise_left | idx_noise_right; + + else + % fallback: global maximum (unsafe for pathological ASE cases) + [~, imax] = max(psd); + lambda_c = obj.lambda_nm(imax); + + % ASE from outer quarters of spectrum + N = numel(obj.lambda_nm); + idx_noise = false(size(obj.lambda_nm)); + idx_noise([3:round(N/4), round(3*N/4):N-1]) = true; + end + + % --- extract signal window --- + idx_signal = obj.lambda_nm > (lambda_c - options.bw_signal_nm/2) & ... + obj.lambda_nm < (lambda_c + options.bw_signal_nm/2); + + if ~any(idx_signal) + idx_signal(imax) = true; + end + + cc_lambda = obj.lambda_nm(idx_signal); + cc_psd = psd(idx_signal); + + % --- estimate ASE from local noise (flat ASE assumption) --- + noise_lambda = obj.lambda_nm(idx_noise); + noise_psd = psd(idx_noise); + + + ase_psd = interp1(noise_lambda, noise_psd, cc_lambda, ... + 'linear', 'extrap'); + + % --- convert to linear (mW / RBW) --- + sig_lin = 10.^(cc_psd/10); + ase_lin = 10.^(ase_psd/10); + + % --- integrate (RBW cancels) --- + P_sig_lin = sum(sig_lin); + P_ase_lin = sum(ase_lin); + + P_sig_dB = 10*log10(P_sig_lin); + P_ase_dB = 10*log10(P_ase_lin); + + osnr.p_sig_db = P_sig_dB; + osnr.p_ase_db = P_ase_dB; + + % --- OSNR direct from peak value to the noise estimate out of band --- + osnr.direct_dB = psd(imax) - 10*log10(mean(ase_lin)) ; + + % --- OSNR in measurement bandwidth --- + osnr.measured_dB = P_sig_dB - P_ase_dB; + + % --- normalize to reference bandwidth (usually 0.1 nm) --- + osnr.ref_dB = osnr.measured_dB + ... + 10*log10(options.bw_signal_nm / options.ref_bw_nm); + + % --- ASE-corrected OSNR (optional definition) --- + osnr.corrected_dB = ... + 10*log10(10^(P_sig_dB/10) - 10^(P_ase_dB/10)) - P_ase_dB; + + osnr.corrected_ref_dB = osnr.corrected_dB + ... + 10*log10(options.bw_signal_nm / options.ref_bw_nm); + + % metadata + osnr.lambda_c_nm = lambda_c; + osnr.bw_signal_nm = options.bw_signal_nm; + osnr.ref_bw_nm = options.ref_bw_nm; + + if options.debugplots + figure(100) + xline(lambda_c,'LineWidth',2,'DisplayName','Max. OSA Value','Color',[0.8,0.8,0.8]) + obj.plot("DisplayName",'OSNR','FigureNumber',100); + yline(osnr.p_ase_db,'LineWidth',2,'DisplayName','Signal Estimation'); + yline(osnr.p_sig_db,'LineWidth',2,'DisplayName','Noise Estimation'); + scatter(obj.lambda_nm(idx_noise),obj.psd_dBm(idx_noise),'Marker','.','LineWidth',4,'DisplayName','Noise Samples','MarkerEdgeColor','red'); + scatter(obj.lambda_nm(idx_signal),obj.psd_dBm(idx_signal),'Marker','.','LineWidth',4,'DisplayName','Signal Samples','MarkerEdgeColor','green'); + scatter(obj.lambda_nm(idx_signal),ase_psd,'Marker','.','LineWidth',4,'DisplayName','Signal Samples','MarkerEdgeColor','magenta'); + ylim([min(obj.psd_dBm), max([obj.psd_dBm,osnr.p_sig_db+5])]); + + figure(101);hold on + xline(lambda_c,'LineWidth',2,'DisplayName','Max. OSA Value','Color',[0.8,0.8,0.8]) + xline(lambda_c - options.bw_signal_nm/2,'LineWidth',2,'DisplayName','CW - 0.5 BW','Color',[0.6,0.6,0.8]) + xline(lambda_c + options.bw_signal_nm/2,'LineWidth',2,'DisplayName','CW + 0.5 BW','Color',[0.6,0.8,0.8]) + obj.plot("DisplayName",'OSNR','FigureNumber',101); + yline(osnr.p_ase_db,'LineWidth',2,'DisplayName','Signal Estimation'); + yline(osnr.p_sig_db,'LineWidth',2,'DisplayName','Noise Estimation'); + scatter(obj.lambda_nm(idx_signal),obj.psd_dBm(idx_signal),'Marker','.','LineWidth',4,'DisplayName','Signal Samples','MarkerEdgeColor','green'); + scatter(obj.lambda_nm(idx_signal),ase_psd,'Marker','.','LineWidth',4,'DisplayName','Signal Samples','MarkerEdgeColor','magenta'); + xlim([lambda_c - options.bw_signal_nm, lambda_c + options.bw_signal_nm]) + ylim([min(obj.psd_dBm), max([obj.psd_dBm,osnr.p_sig_db+5])]); + end + + end + + end + + methods (Access = protected) + function v = connect(obj) + v = visadev(obj.visaAddress); + end + end +end diff --git a/Classes/05_Lab/OptPowerMeter8153A.m b/Classes/05_Lab/OptPowerMeter8153A.m new file mode 100644 index 0000000..a815f8c --- /dev/null +++ b/Classes/05_Lab/OptPowerMeter8153A.m @@ -0,0 +1,168 @@ +classdef OptPowerMeter8153A < handle + + % Code for HEWLETT-PACKARD 8153A Optical Power Meter (2-channel). + % https://www.artisantg.com/info/Agilent_8153A_Manual.pdf?srsltid=AfmBOoqTe38sbGRNHUFXjw5uO6Br7I7aSfgAdRpx-hVvM4sTIhFYyD5x + % PAGE 166++ + + % Typical usage: + % pm = HPPowerMeter8153A( ... + % 'active', [false true], ... + % 'wavelength_nm', [1550 1310] ); + % + % pm.setWavelength([1550 1310]); % only CH2 is modified + % p = pm.readPower(); % reads only active channels + + + properties (Access = public) + active logical = [true true] % channel enable + wavelength_nm double = [1550 1550] % per-channel wavelength [nm] + power_dBm double = [NaN NaN] % last read power + offset_dB double = [0 0] % per-channel power offset [dB] + avgTime_s double = [0.2 0.2]; + end + + properties (Constant) + avgTime_min_s = 0.02 % 20 ms + avgTime_max_s = 3600 % 1 hour + end + + properties (Access = protected) + visaAddress string = "GPIB1::22::INSTR" + numChannels double = 2 + end + + methods + function obj = OptPowerMeter8153A(options) + arguments + options.visaAddress string = "GPIB1::22::INSTR" + options.active logical = [true true] + options.wavelength_nm double = [1550 1550] + end + + fn = fieldnames(options); + for k = 1:numel(fn) + obj.(fn{k}) = options.(fn{k}); + end + + assert(numel(obj.active)==obj.numChannels) + assert(numel(obj.wavelength_nm)==obj.numChannels) + + v = obj.connect(); + writeline(v,"*IDN?"); + disp(readline(v)); + pause(0.1); + delete(v); + pause(0.1); + + obj.setWavelength(obj.wavelength_nm); + end + + function setWavelength(obj, lambda_nm) + if nargin > 1 + obj.wavelength_nm = lambda_nm; + end + + v = obj.connect(); + for ch = 1:obj.numChannels + if obj.active(ch) + writeline(v, ... + sprintf("SENS%d:POW:WAVE %gNM", ch, obj.wavelength_nm(ch))); + end + end + pause(0.1); + delete(v); + pause(0.1); + end + + function p = readPower(obj) + v = obj.connect(); + for ch = 1:obj.numChannels + if obj.active(ch) + writeline(v, sprintf("READ%d:POW?", ch)); + obj.power_dBm(ch) = str2double(readline(v)); + else + obj.power_dBm(ch) = NaN; + end + end + delete(v); + p = obj.power_dBm; + end + + function setOffsetCorrection(obj, offset_dB) + obj.offset_dB = offset_dB; + + v = obj.connect(); + for ch = 1:obj.numChannels + if obj.active(ch) + writeline(v, ... + sprintf("SENS%d:CORR:LOSS:INP:MAGN %gDB", ch, obj.offset_dB(ch))); + end + end + delete(v); + end + + function offset_dB = getOffsetCorrection(obj) + v = obj.connect(); + offset_dB = NaN(1,obj.numChannels); + for ch = 1:obj.numChannels + if obj.active(ch) + writeline(v, sprintf("SENS%d:CORR:LOSS:INP:MAGN?", ch)); + offset_dB(ch) = str2double(readline(v)); + end + end + delete(v); + obj.offset_dB = offset_dB; + end + + function setAvgTime(obj, avgTime_s) + + if any(avgTime_s < obj.avgTime_min_s) || any(avgTime_s > obj.avgTime_max_s) + + oldv = avgTime_s; + + avgTime_s = max(avgTime_s, obj.avgTime_min_s); + avgTime_s = min(avgTime_s, obj.avgTime_max_s); + + fprintf('[HPPowerMeter8153A] Averaging time clipped:\n'); + for ch = 1:obj.numChannels + if oldv(ch) ~= avgTime_s(ch) + fprintf(' CH%d: %.3g s → %.3g s (allowed %.3g … %.3g s)\n', ... + ch, oldv(ch), avgTime_s(ch), ... + obj.avgTime_min_s, obj.avgTime_max_s); + end + end + end + + obj.avgTime_s = avgTime_s; + + v = obj.connect(); + for ch = 1:obj.numChannels + if obj.active(ch) && ~isnan(obj.avgTime_s(ch)) + writeline(v, ... + sprintf("SENS%d:POW:ATIME %g", ch, obj.avgTime_s(ch))); + end + end + delete(v); + end + + function avgTime_s = getAvgTime(obj) + v = obj.connect(); + avgTime_s = NaN(1,obj.numChannels); + for ch = 1:obj.numChannels + if obj.active(ch) + writeline(v, sprintf("SENS%d:POW:ATIME?", ch)); + avgTime_s(ch) = str2double(readline(v)); + end + end + delete(v); + obj.avgTime_s = avgTime_s; + end + + end + + methods (Access = protected) + function v = connect(obj) + v = visadev(obj.visaAddress); + end + end +end diff --git a/Classes/05_Lab/Thor_PDFA.m b/Classes/05_Lab/Thor_PDFA.m index fefad8f..9f0a88a 100644 --- a/Classes/05_Lab/Thor_PDFA.m +++ b/Classes/05_Lab/Thor_PDFA.m @@ -20,7 +20,7 @@ classdef Thor_PDFA < handle function obj = Thor_PDFA(options) arguments - options.serialport_number = 'COM12' + options.serialport_number = 'COM18' options.safety_mode = 1; end @@ -37,7 +37,7 @@ classdef Thor_PDFA < handle end function o = connectSerial(obj) - + o = []; try % Connect to the PDFA o = serialport(obj.serialport_number, 9600); @@ -62,11 +62,15 @@ classdef Thor_PDFA < handle function getStatus(obj) o = obj.connectSerial(); - obj.getStatus_(o); - if obj.safety_mode - obj + + if ~isempty(o) + obj.getStatus_(o); + if obj.safety_mode + obj + end end + end function enablePDFA(obj) diff --git a/Functions/Lab_helper/waitUntilClick.m b/Functions/Lab_helper/waitUntilClick.m index 6e06737..4aec3c2 100644 --- a/Functions/Lab_helper/waitUntilClick.m +++ b/Functions/Lab_helper/waitUntilClick.m @@ -1,20 +1,50 @@ -function waitUntilClick() - % Create the UI figure - fig = uifigure('Name', 'Pause Execution', 'Position', [100 100 300 150]); +function waitUntilClick(options) - % Create a label to inform the user - uilabel(fig, 'Position', [50 80 200 40], 'Text', 'Click "Continue" to proceed', ... - 'FontSize', 14, 'HorizontalAlignment', 'center'); +% use like this to stop any computation and wait for user to press ENTER or +% Continue button. I used this in the lab to wait until smth settled or I +% set a device to a certain operating point... - % Create the "Continue" button - continueButton = uibutton(fig, 'push', 'Text', 'Continue', 'Position', [100 20 100 40], ... - 'ButtonPushedFcn', @(src, event) closeWindow()); +% USAGE: +% waitUntilClick; + +% OPTIONAL: provide input text that is prompted :-) +% usrcmd = sprintf('Laser to %d dBm',p); +% waitUntilClick('Text',usrcmd); + + +arguments + options.Text string = "Click ""Continue"" to proceed" +end + +% Create the UI figure +fig = uifigure('Name', 'Pause Execution', ... + 'Position', [100 100 300 150], ... + 'KeyPressFcn', @(src,event) keyHandler(event)); + +% Create a label with custom text +uilabel(fig, ... + 'Position', [30 70 240 60], ... + 'Text', options.Text, ... + 'FontSize', 14, ... + 'HorizontalAlignment', 'center', ... + 'WordWrap','on'); + +% Create the "Continue" button +uibutton(fig, 'push', ... + 'Text', 'Continue', ... + 'Position', [100 20 100 40], ... + 'ButtonPushedFcn', @(src, event) closeWindow()); - % Function to close the window when "Continue" is clicked function closeWindow() - delete(fig); % Close the figure window + delete(fig); end - % Wait until the figure is closed to resume execution - waitfor(fig); -end \ No newline at end of file + function keyHandler(event) + if strcmp(event.Key,'return') || strcmp(event.Key,'enter') + closeWindow(); + end + end + +% Wait until the figure is closed +waitfor(fig); +end diff --git a/Functions/Metrics/noiseFigureFromOSNR.m b/Functions/Metrics/noiseFigureFromOSNR.m new file mode 100644 index 0000000..5491200 --- /dev/null +++ b/Functions/Metrics/noiseFigureFromOSNR.m @@ -0,0 +1,20 @@ +function NF_dB = noiseFigureFromOSNR(Pin_dBm, OSNR_dB, lambda_nm, Bref_nm) + + if nargin < 4 + Bref_nm = 0.1; % OSNR reference bandwidth + end + + h = 6.62607015e-34; % Planck [J*s] + c = 299792458; % speed of light [m/s] + + lambda = lambda_nm * 1e-9; + nu = c / lambda; + + % convert reference bandwidth from nm to Hz + Bref_Hz = Bref_nm*1e-9 * c / lambda^2; + + % ASE noise power density in dBm + Pn_dBm = 10*log10(h*nu*Bref_Hz) + 30; + + NF_dB = Pin_dBm - OSNR_dB - Pn_dBm; +end diff --git a/projects.mat b/projects.mat new file mode 100644 index 0000000..8880e51 Binary files /dev/null and b/projects.mat differ diff --git a/projects/Lab_analysis/aeon_soa_measurement_lambda_plaser_pump.mat b/projects/Lab_analysis/aeon_soa_measurement_lambda_plaser_pump.mat new file mode 100644 index 0000000..eb804e8 Binary files /dev/null and b/projects/Lab_analysis/aeon_soa_measurement_lambda_plaser_pump.mat differ diff --git a/projects/Lab_analysis/aeon_soa_measurement_pump_level_sweep.mat b/projects/Lab_analysis/aeon_soa_measurement_pump_level_sweep.mat new file mode 100644 index 0000000..3db4986 Binary files /dev/null and b/projects/Lab_analysis/aeon_soa_measurement_pump_level_sweep.mat differ diff --git a/projects/Lab_analysis/amplifier_characterization.m b/projects/Lab_analysis/amplifier_characterization.m new file mode 100644 index 0000000..7af1c85 --- /dev/null +++ b/projects/Lab_analysis/amplifier_characterization.m @@ -0,0 +1,127 @@ +% Silas Amplifier characterization + +laser = Exfo_laser("mainframe_channel",1,"safety_mode",0,"connection_id",'10','lab_interface','gpib'); +laser.getLaserInfo(); +laser.disableLaser; +laser.setWavelength(1310); +laser.enableLaser; + +% pm = OptPowerMeter8153A("active",[0,1],"wavelength_nm",[1550,1310]); +% pm.readPower + +% pdfa = Thor_PDFA(); +%% +params.laserpower = [-30,-20,-10,0]; +params.lambda = [1260:5:1360]; %calcWavelengthPlan(16, 400e9 , 1310); +params.pump = [50,75,100]; + +wh = DataStorage(params); +wh.addStorage("spectrum_osa"); +wh.addStorage("wavelength_osa"); +wh.addStorage("psig_osa"); +wh.addStorage("pase_osa"); +wh.addStorage("osnr_osa"); +wh.addStorage("amp_gain"); +wh.addStorage("psig_total"); + +cols = linspecer(numel(params.laserpower)); +ccnt = 0; +for p = params.laserpower + ccnt=ccnt+1; + laser.setWavelength(params.lambda(1)); + usrcmd = sprintf('Laser to %d dBm',p); + waitUntilClick('Text',usrcmd); + + for pmp = params.pump + % pdfa.setPumpLevel(pmp); + usrcmd = sprintf('Amp to %d mA',2*pmp); + waitUntilClick('Text',usrcmd); + + for w = params.lambda + laser.setWavelength(w) + % pm.setWavelength([1550, w]); + span_nm = 5; + lambda_c_nm = w; + osa = OSA_Advantest( ... + 'span_nm', 40, ... + 'lambda_c_nm', w, ... + 'resolution_nm', 0.1, ... + 'sampling_points', 1001 ); + osa.configure(); + + + + % OSA + [lambda_nm, psd_dBm] = osa.measure(); + osnr = osa.computeOSNR('bw_signal_nm',0.01,'ref_bw_nm',0.1,'debugplots',0,... + 'carrier_frequency',w,'noise_guard_nm',1,'noise_window_nm',1); + + osa.plot("FigureNumber",pmp+1,"Color",cols(ccnt,:)); + xlabel('Wavelength [nm]'); + ylabel('Opt. Spectrum [dBm]'); + xlim([params.lambda(1)-5 params.lambda(end)+5]) + grid minor + + % Powermeter + totalpower=10*log10(sum(10.^(psd_dBm./10))); + % [totalpower]=pm.readPower; + % totalpower=totalpower(2) + 20; %20dB attenuation with eigenlight to satisfy 3dB max of PMeter + + wh.addValueToStorage(lambda_nm,'wavelength_osa',p,w,pmp); + wh.addValueToStorage(psd_dBm,'spectrum_osa',p,w,pmp); + wh.addValueToStorage(osnr.p_ase_db,'pase_osa',p,w,pmp); + wh.addValueToStorage(osnr.p_sig_db,'psig_osa',p,w,pmp); + wh.addValueToStorage(osnr,'osnr_osa',p,w,pmp); + wh.addValueToStorage(totalpower,'psig_total',p,w,pmp); + + end + end + + % before tuning all the way back to 1270nm set PDFA off + % pdfa.setPumpLevel(0); + % pdfa.disablePDFA; +end + + + + +%% OSNR + +figure(2);hold on +for p = [-30:10:0] + pmp = 100; + osnr = wh.getStoValue('osnr_osa',p,params.lambda,pmp); + osnr_measured_dB = cellfun(@(x) x.measured_dB, osnr); + osnr_direct_dB = cellfun(@(x) x.direct_dB, osnr); + plot(params.lambda,osnr_direct_dB,'DisplayName',sprintf('P_{in}: %d dB ',p)); +end +legend + +%% P out + +figure(3);hold on +for p = [-30:10:0] + pmp = 100; + psig_osa = wh.getStoValue('psig_osa',p,params.lambda,pmp)+3; + pl=plot(params.lambda,psig_osa,'DisplayName',sprintf('P_{signal}: %d dB ',p)); + + psig_powermeter = wh.getStoValue('psig_total',p,params.lambda,pmp)+3; + plot(params.lambda,psig_powermeter,'DisplayName',sprintf('P_{total}: %d dB ',p),'LineStyle','--','Color',pl.Color); + + yline(p,'Color',pl.Color,'DisplayName',sprintf('P_{total}: %d dB ',p)); +end +legend + +%% GAIN + +figure(3);hold on +for p = [-30:10:0] + + pmp = 50; + psig_osa = wh.getStoValue('psig_osa',p,params.lambda,pmp); + % psig_powermeter = wh.getStoValue('psig_powermeter',p,params.lambda,pmp); + + g = psig_osa-p; + pl=plot(params.lambda,g,'DisplayName',sprintf('P_{in}: %d dB ',p)); +end +legend \ No newline at end of file diff --git a/projects/Lab_analysis/amplifier_input_output_curve.m b/projects/Lab_analysis/amplifier_input_output_curve.m new file mode 100644 index 0000000..c95b46f --- /dev/null +++ b/projects/Lab_analysis/amplifier_input_output_curve.m @@ -0,0 +1,111 @@ +% Silas Amplifier characterization + +laser = Exfo_laser("mainframe_channel",1,"safety_mode",0,"connection_id",'10','lab_interface','gpib'); +laser.getLaserInfo(); +laser.disableLaser; +laser.setWavelength(1310); +laser.enableLaser; + +pdfa = Thor_PDFA(); +%% +params.laserpower = [-30,-20,-10,0]; +params.lambda = [1310]; %calcWavelengthPlan(16, 400e9 , 1310); +params.pump = [0:5:100]; + +wh = DataStorage(params); +wh.addStorage("spectrum_osa"); +wh.addStorage("wavelength_osa"); +wh.addStorage("psig_osa"); +wh.addStorage("pase_osa"); +wh.addStorage("osnr_osa"); +wh.addStorage("amp_gain"); +wh.addStorage("psig_total"); + +cols = linspecer(numel(params.laserpower)); +ccnt = 0; +for p = params.laserpower + ccnt=ccnt+1; + laser.setWavelength(params.lambda(1)); + usrcmd = sprintf('Laser to %d dBm',p); + waitUntilClick('Text',usrcmd); + + + for w = params.lambda + laser.setWavelength(w) + % pm.setWavelength([1550, w]); + span_nm = 5; + lambda_c_nm = w; + osa = OSA_Advantest( ... + 'span_nm', 40, ... + 'lambda_c_nm', w, ... + 'resolution_nm', 0.1, ... + 'sampling_points', 1001 ); + osa.configure(); + + + for pmp = params.pump + % pdfa.setPumpLevel(pmp); + usrcmd = sprintf('Amp to %d %',pmp); + waitUntilClick('Text',usrcmd); + + % OSA + [lambda_nm, psd_dBm] = osa.measure(); + osnr = osa.computeOSNR('bw_signal_nm',0.01,'ref_bw_nm',0.1,'debugplots',0,... + 'carrier_frequency',w,'noise_guard_nm',1,'noise_window_nm',1); + + osa.plot("FigureNumber",abs(p)+10,"Color",cols(ccnt,:)); + xlabel('Wavelength [nm]'); + ylabel('Opt. Spectrum [dBm]'); + grid minor + + % Powermeter + totalpower=10*log10(sum(10.^(psd_dBm./10))); + % [totalpower]=pm.readPower; + % totalpower=totalpower(2) + 20; %20dB attenuation with eigenlight to satisfy 3dB max of PMeter + + wh.addValueToStorage(lambda_nm,'wavelength_osa',p,w,pmp); + wh.addValueToStorage(psd_dBm,'spectrum_osa',p,w,pmp); + wh.addValueToStorage(osnr.p_ase_db,'pase_osa',p,w,pmp); + wh.addValueToStorage(osnr.p_sig_db,'psig_osa',p,w,pmp); + wh.addValueToStorage(osnr,'osnr_osa',p,w,pmp); + wh.addValueToStorage(totalpower,'psig_total',p,w,pmp); + + end + end +end + + +%% P out + +figure(3);hold on +for p = [-30:10:0] + + psig_osa = wh.getStoValue('psig_osa',p,params.lambda,params.pump); + pl=plot(params.pump,psig_osa,'DisplayName',sprintf('P_{signal}: %d dB ',p)); + + psig_total = wh.getStoValue('psig_total',p,params.lambda,params.pump); + plot(params.pump,psig_total,'DisplayName',sprintf('P_{total}: %d dB ',p),'LineStyle','--','Color',pl.Color); + + pase = wh.getStoValue('pase_osa',p,params.lambda,params.pump); + plot(params.pump,pase,'DisplayName',sprintf('P_{ASE}: %d dB ',p),'LineStyle',':','Color',pl.Color); + + +end +legend + + + +%% Gain + +figure(4);hold on +for p = [-30:10:0] + + % Die -2dB sind Korrekturfaktor aus dem Setup! Gilt für PDFA und SOA + psig_osa = wh.getStoValue('psig_osa',p,params.lambda,params.pump); + pl=plot(params.pump,psig_osa-(p-2),'DisplayName',sprintf('Gain: %d dB ',p)); + + psig_total = wh.getStoValue('psig_total',p,params.lambda,params.pump); + pl=plot(params.pump,psig_total-(p-2),'DisplayName',sprintf('Gain: %d dB ',p),'Color',pl.Color,'LineStyle','--'); + +end +legend \ No newline at end of file diff --git a/projects/Lab_analysis/available_output_power_exfo_t100_oband.fig b/projects/Lab_analysis/available_output_power_exfo_t100_oband.fig new file mode 100644 index 0000000..f7af6a4 Binary files /dev/null and b/projects/Lab_analysis/available_output_power_exfo_t100_oband.fig differ diff --git a/projects/Lab_analysis/available_output_power_exfo_t100_oband_20_averages.fig b/projects/Lab_analysis/available_output_power_exfo_t100_oband_20_averages.fig new file mode 100644 index 0000000..5c672cb Binary files /dev/null and b/projects/Lab_analysis/available_output_power_exfo_t100_oband_20_averages.fig differ diff --git a/projects/Lab_analysis/laser_characterization.m b/projects/Lab_analysis/laser_characterization.m new file mode 100644 index 0000000..bf7d8f6 --- /dev/null +++ b/projects/Lab_analysis/laser_characterization.m @@ -0,0 +1,54 @@ +laser = Exfo_laser("mainframe_channel",1,"safety_mode",0,"connection_id",'10','lab_interface','gpib'); +laser.getLaserInfo(); + +pm = OptPowerMeter8153A("active",[0,1],"wavelength_nm",[1550,start_wavelength]); +pm.readPower + +laserparams.lambda = [1260:2:1360]; +laserparams.laserpower = [0,3,6,8,10]; +laser_output_wh = DataStorage(laserparams); +laser_output_wh.addStorage("plaser"); + +pm = OptPowerMeter8153A("active",[0,1],"wavelength_nm",[1550,1310]); +laser.setWavelength(1310); + +cw_pwr = NaN(numel(laserparams.laserpower),numel(laserparams.lambda)); +row = 1; +for p = laserparams.laserpower + laser.setPower(p); + cnt = 1; + for w = laserparams.lambda + laser.setWavelength(w); + pm.setWavelength([1550,w]); + + for i = 1:20 + [totalpower]=pm.readPower; + cw_measurement(i)=totalpower(2)+10; + cw_pwr(row,cnt) = mean(cw_measurement); + pause(3); + end + + laser_output_wh.addValueToStorage(cw_measurement,'plaser',w,p); + + figure(2027);clf + plot(laserparams.lambda,cw_pwr(1:row,:),'Marker','*'); + xlabel('Wavelength [nm]'); + ylabel('Laser Output Power [dBm]'); + ylim([0, 11]); + xlim([laserparams.lambda(1) laserparams.lambda(end)]) + grid minor + + cnt = cnt+1; + end + row = row+1; +end + +figure();hold on +for p = laserparams.laserpower + plot(laserparams.lambda,laser_output_wh.getStoValue('plaser',laserparams.lambda,p)) +end +xlabel('Wavelength [nm]'); +ylabel('Laser Output Power [dBm]'); +ylim([0, 11]); +xlim([laserparams.lambda(1) laserparams.lambda(end)]) +grid minor \ No newline at end of file diff --git a/projects/Lab_analysis/laser_outputpower_sweep_1.mat b/projects/Lab_analysis/laser_outputpower_sweep_1.mat new file mode 100644 index 0000000..107034a Binary files /dev/null and b/projects/Lab_analysis/laser_outputpower_sweep_1.mat differ diff --git a/projects/Lab_analysis/laser_outputpower_sweep_2_many_averages.mat b/projects/Lab_analysis/laser_outputpower_sweep_2_many_averages.mat new file mode 100644 index 0000000..828136b Binary files /dev/null and b/projects/Lab_analysis/laser_outputpower_sweep_2_many_averages.mat differ diff --git a/projects/Lab_analysis/pdfa_thorlabs_pin_vs_pout.fig b/projects/Lab_analysis/pdfa_thorlabs_pin_vs_pout.fig new file mode 100644 index 0000000..f3c74b0 Binary files /dev/null and b/projects/Lab_analysis/pdfa_thorlabs_pin_vs_pout.fig differ diff --git a/projects/Lab_analysis/pdfa_thorlabs_pout.fig b/projects/Lab_analysis/pdfa_thorlabs_pout.fig new file mode 100644 index 0000000..4c5e6d2 Binary files /dev/null and b/projects/Lab_analysis/pdfa_thorlabs_pout.fig differ diff --git a/projects/Lab_analysis/thorlabs_pdfa_100prozent_sweep.mat b/projects/Lab_analysis/thorlabs_pdfa_100prozent_sweep.mat new file mode 100644 index 0000000..c02f5e3 Binary files /dev/null and b/projects/Lab_analysis/thorlabs_pdfa_100prozent_sweep.mat differ diff --git a/projects/Lab_analysis/thorlabs_pdfa_measurement_lambda_plaser_pump.mat b/projects/Lab_analysis/thorlabs_pdfa_measurement_lambda_plaser_pump.mat new file mode 100644 index 0000000..e2be0b1 Binary files /dev/null and b/projects/Lab_analysis/thorlabs_pdfa_measurement_lambda_plaser_pump.mat differ diff --git a/projects/Lab_analysis/thorlabs_pdfa_measurement_pump_level_sweep.mat b/projects/Lab_analysis/thorlabs_pdfa_measurement_pump_level_sweep.mat new file mode 100644 index 0000000..44a7aa2 Binary files /dev/null and b/projects/Lab_analysis/thorlabs_pdfa_measurement_pump_level_sweep.mat differ diff --git a/projects/Lab_base_system/exfo_laser_example.m b/projects/Lab_base_system/exfo_laser_example.m index e059344..a2c28c7 100644 --- a/projects/Lab_base_system/exfo_laser_example.m +++ b/projects/Lab_base_system/exfo_laser_example.m @@ -1,15 +1,15 @@ %% Laser -start_wavelength = 1550; -laser = Exfo_laser("mainframe_channel",3,"safety_mode",0,"connection_id",'10','lab_interface','gpib'); +start_wavelength = 1310; +laser = Exfo_laser("mainframe_channel",1,"safety_mode",0,"connection_id",'10','lab_interface','gpib'); laser.getLaserInfo(); laser.setWavelength(start_wavelength) laser.setPower(0); laser.enableLaser(); -for l = 1550:1560 - laser.setWavelength(l); -end +% for l = 1550:1560 +% laser.setWavelength(l); +% end laser.disableLaser();