diff --git a/Modules/+Drivers/+PulseStreamerMaster/PulseStreamerMaster.m b/Modules/+Drivers/+PulseStreamerMaster/PulseStreamerMaster.m new file mode 100644 index 000000000..1dbad79f6 --- /dev/null +++ b/Modules/+Drivers/+PulseStreamerMaster/PulseStreamerMaster.m @@ -0,0 +1,851 @@ + +classdef PulseStreamerMaster < Modules.Driver & Drivers.PulseTimer_invisible + % PulseStreamerMaster is located in in CommandCenter > Modules > + % +Drivers > +PulseStreamer + % The PulseStreamerMaster class is a driver module class which + % builds and uploads pulse sequences to the Swabian Pulse Streamer and, + % optionally, runs them. Pulse sequences can also be run or halted + % using hardware or software triggers. + % + % The PulseStreamerMaster module require the Matlab Toolbox for the + % Pusle Streamer (provided by Swabian). + % + % The constructor of this class accepts two inputs upon instantiation: + % the IP address of the hardware and a pulse sequence instruction set + % written in JSON format. An example of the JSON instruction set format + % is given below: + % + % { + % "name":"MyPulseSequence", + % "units":"ns", + % "forever":false, + % "channels":["channel1","MW Switch","","channel4"], + % "sequence":[ + % {"flags":[0,0,0,1],"duration":0,"instruction":"CONTINUE","data":null}, + % {"flags":[0,1,1,0],"duration":1,"instruction":"LOOP","data":2}, + % {"flags":[0,0,0,1],"duration":18,"instruction":"CONTINUE","data":null}, + % {"flags":[0,1,0,1],"duration":19,"instruction":"END_LOOP","data":null}, + % {"flags":[0,0,0,1],"duration":20,"instruction":"CONTINUE","data":null}, + % ] + % } + % + % Here "name", "units", and "channels" are human readable descriptors. + % The "forever" field is a boolean that specifies whether or not to + % loop the pulse sequence indefinitely. + % + % The sequence is defined by four fields: + % 1. flags: an array of boolean states for the relevant . + % 2. duration: the duration of the pulse for the associated states. + % 3. data: the span of the loop, i.e., the numer of loop interations. + % 4. instruction: determines the action to be performed. + % i. CONTINUE: run the associated line of instructions + % ii. LOOP: run the associated line of instructions and all + % the instructions between LOOP and END_LOOP for 'data' + % iterations. + % iii. END_LOOP: terminate the loop and run the associated + % instructions. + % + % The above defines a simple programming language (an example + % of a domain specific language) to describe pulse sequences. + % + % Typical workflow for running a pulse sequence: + % 1. Initialize instructions and communication to Pulse Streamer + % 2. Build pulse sequence from JSON string + % 3. Load pulse sequence into Pulsestreamer + % 4. Run pulse sequence (optional, since this can be done through + % software or hardware triggers). + % 5. Halt pulse sequence (optional). + % 6. Delete function to deallocate resources for the Pulse Streamer Master object. + % + % Example Code: + % ip = '192.168.11.2'; + % filename = 'JSON_sequence.js'; + % json = fileread(filename); + % PSM = PulseStreamerMaster(ip); + % PSM.build(json); + % PSM.load(); + % PSM.run(); + % PSM.delete(); + + + % ipAddress='192.168.11.2'; + properties(Constant) + clk = 1000; % clock sampling rate + resolution = 3; % ns can be s low as 1 nd but output voltage is lower than 3.3V (TTL) so depending on uWave source trigger might be able to go down to 1ns + minDuration = 2; % ns (can go down to 1ns but output waveform will be distorted). + maxRepeats = 2^63-1; % positive integer value + end + properties(SetAccess=private) + % Object that provides the interface to the + % Pulse Streamer hardware. this object provides access run, halt + % and trigger operation, and plotting features for the Pulse + % Streamer. + PS; + % + pulseName; + builder; %PusleSequenceBuilder object used to build pulse sequences. + triggerStart; + triggerMode; + % string containing pulse sequence instruction set in JSON format + json; + % A fully unrolled sequence of instructions. By "unrolled", we + % mean that all loops at all depths have been written out as an + % iteration by iteration sequence of instructions. Note: the + % recursive algorithm we use to unroll the loops requires that + % the cmd object be passed by reference; that is, we pass the + % actual object, not a copy of it. + cmd = Drivers.PulseStreamerMaster.seqCommand(); + map = Drivers.PulseStreamerMaster.seqCommand(); + sequence = []; + seq_meta = []; + finalState; + end + + properties(SetObservable,SetAccess=private,AbortSet) + running = 0; + end + + methods(Static) + function obj = instance(ip) + mlock; + persistent Objects + if isempty(Objects) %|| ~isvalid(Objects) + Objects = Drivers.PulseStreamerMaster.PulseStreamerMaster.empty(1,0); + %changed from Object to Objects in above line + end + [~,resolvedIP] = resolvehost(ip); + for i = 1:length(Objects) + if isvalid(Objects(i)) && isequal(resolvedIP,Objects(i).singleton_id) + obj = Objects(i); + return + end + end + obj = Drivers.PulseStreamerMaster.PulseStreamerMaster(ip); + obj.singleton_id = resolvedIP; + Objects(end+1) = obj; + end + end + + methods(Access={?Drivers.PulseStreamerMaster.PulseStreamerMaster}) + + function obj = PulseStreamerMaster(ip) + % Constructor for this class. + % ip: IP address of hardware + % json: string containing instructions in JSON format + % + % Workflow for constructor: + % 1. set obj.ipAddress to ip address and obj.json to json string + % 2. instantiate a PulseStreamer object as the property obj.PS. + % The PulseStreamer class is provided by Swabain. It + % contains stream, trigger, ForceFinal, and start methods which + % enable the loading, running and halting of pulse sequences. + % 3. instantiate a PSSequenceBuidler object as the property + % obj.builder. PSSequenceBuilder is also provided by Swabian. + % It contains a builder method for building pulse sequences + % in the format required by the PulseStreamer class. + + obj.PS = PulseStreamer.PulseStreamer(ip); + obj.triggerStart = PulseStreamer.TriggerStart.SOFTWARE(); + obj.triggerMode = PulseStreamer.TriggerRearm.AUTO(); + obj.sequence = obj.PS.createSequence(); + obj.map.command(:)=[]; + obj.cmd.command(:)=[]; + % initialize final state to 0V for all channels. + obj.finalState= PulseStreamer.OutputState.ZERO; + end + + end + + methods(Access=public) + function build(obj,json) + % This method converts the instructions in the obj.json to a + % PulseStreamer sequence object. Meta data for the sequence is + % reported in the seq_meta output. Its structure is as follows + % + % seq_meta: + % seq_meta.channels : 1X8 (channels 0-7). Order of channels + % is as follows. + % ['Ch0', 'Ch1', 'Ch2', 'Ch3', + % 'Ch4', 'Ch5', 'Ch6', 'Ch7'] + % Label for each channel is determined by channel field + % E.G. seq_meta.channels = ['532nm', + % 'Res_Repump','MW1','MW2', + % 'MW3','ORES','1050nm',''] + % Ch0 -> 532nm + % Ch0 -> Res_Repump + % Ch0 -> MW1 + % Ch0 -> MW2 + % Ch0 -> MW3 + % Ch0 -> ORES + % Ch0 -> 1050nm + % + % + % seq_meta.units: time units for pulse durations + % seq_meta.name: label for sequence. + % seq_meta.forever: boolean indicating whether to + % repeat sequence indefinitely until explicitly halted. + % + % The build method workflow is as follows: + % 1. Determine if json input is a string or a json file name + % (a name with extension .js). If it is the name of a JSON + % file, open the file and reads the entire contents into + % a single string and replace the obj.json with that + % string; otherwise, we assume this is a string in JSON + % format. + % 2. Parse JSON string into + % a. json_seq: a cell array of maps (i.e., a list of + % dictionaries) + % b. seq_meta: the remaining meta data for the + % sequnece + % 3. Build instruction tree recursively from json_seq using + % the decodeInstructions method. + % 4. Unroll the instruction tree recurrsivley into a 1D cell + % array of maps, where each map contains the duration and + % channel flags for that step in the pulse sequence. + % 5. Initialize pulse_train, a cell array of dimension + % C{num_channels}{1}{length(map_list),2} + % 6. Fill the cell array with pulse sequence data. + % 7. Distribute pulse sequence data for each channel and + % set digital channels of ???pulsestrerer???. + % 8. Build pulse sequence object. + DEBUG = 0; + + obj.json = json; + depth = 0; % depth of recursion + index = 1; % index into cell array of intructions + pulse_trains = {}; % initialize pulse_train cell array + + % Test if obj.json is a file. If a file name, read + % file and replace obj.json with the file contents as a + % single string +% if strcmp(obj.json(end-2:end),'.js') +% obj.json = fileread(obj.json); +% elseif strcmp(obj.json(end-3:end),'.txt') +% %write error checking in the future. +% end + + % convert obj.json into a list (actually a cell array) of + % ordered maps (json_seq) describing the pulse sequence and + % seq_meta + [json_seq,obj] = obj.readJSON(); + + %debugging issues if this class cannot inherit from the handle + %class. + if DEBUG > 0 + fprintf('output from readJSON is: \n'); + fprintf('PSM.seq_meta is: \n'); + disp(obj.seq_meta); + end + + % expand list of instructions into a tree or instructions + % recurrsively. The result is contained in the object obj.cmd. + % obj.cmd is passed throughout the code by reference (via a + % handle). + [index] = obj.decodeInstructions(json_seq, obj.cmd); + + % unroll the instruction tree and all loops recursively + % into a properly ordered cell array of maps. Each map contains + % the instructions for a pulse sequence step (duration and flag + % fields). + obj.unroll_tree(obj.cmd,obj.map); + + % ------------------------- + % BUILD PULSE TRAINS + % A pulse train is a list + % comprising an alternating + % sequence of duration + % followed by flag, that is, + % channel state, 0 (low) or + % 1 (high). + % ------------------------- + % initialize pulse_trains (a cell array), to be filled with + % an alternating sequence of duration and flag, using the + % unrolled instructions in map.command. + ch_train = cell(length(obj.map.command),2); + + for kk = 1:1:length(obj.seq_meta.channels) + pulse_trains{end+1} = {ch_train}; + end + + % fill pulse trains + for ii = 1:1:length(obj.map.command) + % for each channel fill the pulse duration and flag + % (channel state) into the pulse_trains cell array. + flags = obj.map.command{ii}('flag'); + duration = obj.map.command{ii}('duration'); + for jj = 1:1:length(obj.seq_meta.channels) + state = flags(jj); + pulse_trains{jj}{1}{ii,1} = duration; + pulse_trains{jj}{1}{ii,2} = state; + + end + end + + % Build the digital pattern (a sequence of duration, + % channel state pairs in that order) for each channel. + for kk = 1:1:length(obj.seq_meta.channels) + %ch = kk-1; + %digitalPattern = pulse_trains{kk}{1}; + %obj.sequence.setDigital(ch, digitalPattern); + + ch = obj.seq_meta.channels{kk}; + digitalPattern = pulse_trains{kk}{1}; + obj.sequence.setDigital(ch, digitalPattern); + end + end + + function plot(obj) + %plot the sequence + obj.sequence.plot(); + end + + function program = reorder_program(obj,program) + [m,~] = size(program.sequence); + [~,n] = size(program.channels); + i = 1; + while i <= m + program.sequence(i).flags = transpose(program.sequence(i).flags); + i=i+1; + end + i=1; + while i <= n + program.channels{i}= int8(str2double(program.channels{i})); + i=i+1; + end + program.channels = transpose(program.channels); + obj.pulseName = program.name; + +end + + function load(obj,program) + % The load method converts the sequence object to the properly + % formated 64 bit string and uploads this to the xilinx chip in the + % pulse streamer via an eithernet connection managed by a hardware + % server. + + program = obj.reorder_program(program); + obj.build(program); + start = obj.triggerStart;%initialize the trigger to be software defined + mode = obj.triggerMode; % initialize the trigger to be rearmed after each run. + obj.PS.setTrigger(start, mode); % set triggers + + % upload sequence to hardware, but do not run the sequence. + obj.PS.stream(obj.sequence,obj.seq_meta.repeat,obj.finalState); + end + + + function start(obj) + % starts the sequence if the sequence was uploaded with the + % PSStart.Software option + +% % Get caller info +% a = dbstack('-completenames'); +% caller = strsplit(a(end).file,filesep); +% prefix = ''; +% for i = 1:numel(caller) +% if numel(caller{i}) && caller{i}(1)=='+' +% prefix = [prefix caller{i}(2:end) '.']; +% end +% end +% [~,name,~]=fileparts(caller{end}); +% caller = [prefix name]; + obj.PS.startNow(); +% obj.running = caller; + end + + function stop(obj) + %stops the PulseStreamer + % Interrupt the sequence and set the final state of the + % hardware. This method does not modify the final state if + % the sequence has already finished. This method also releases + % the hardware resources of the Pulse Streamer and, therefore, + % allows for faster upload sequence during next call of + % "stream" method. + obj.PS.forceFinal() + if obj.PS.isStreaming() == 1 + obj.PulseStreamerHandle.PS.constant(obj.finalState); + end + end + + function reset(obj) + obj.PS.reset(); + end + + function [index] = decodeInstructions(obj, json_seq, cmd, depth, index) + % NOTE: we shall use list and cell array interchangeably. + % NOTE: cmd is the variable in which data is appended to it upon + % recursion. To ensure the cmd is appropriately appended the + % property obj.cmd is passed to decodeInstructor when this function + % is called. Whereas in the recursive step within this defintion + % the local variable cmd is passed to decodeInstructions. + % Decode instruction takes in a cell array of maps (json_seq), + % where each map contains the information in a single line of the + % JSON string and builds a tree structure that describes the + % program to be executed that yields ultimately the pulse + % sequences. This method handles arbitrary sequences of nested + % LOOP, END_LOOP, and CONTINUE instructions. The method is called + % recursively for each loop instruction. + + % When DEBUG = 0 then report the length cmd.command, the recursion depth, + % the index of the current instuction line and the instruction. + DEBUG = 0; + + if DEBUG > 0 + %a = length(json_seq); + %fprintf('length of JSON string %i\n\n',a); + %fprintf('number of args %i\n\n',nargin); + end + + % When DEBUG = 0 notify user they have entered the + % decodeInstructions method. + if DEBUG > 0 + fprintf('Enter decodeInstructions function\n'); + end + + % default values for recurrsion depth and instruction index + switch nargin + case 3 + depth = 0; + index = 1; + end + + % When the index has reached or surpassed the number of + % instructions then exit decodeInstructions and return values. + if index >= length(json_seq) + return; + end + + % get current instruction + instruction = json_seq{index}('instruction'); + + % Prior to any recursive calls to decodeInstructions create an + % empty cell array (similar to a list in python) when the + % instruction is a LOOP. + + % NOTE: we shall use list and cell array interchangeably. + + depth = depth + 1; + if depth == 1 + % create empty list only if first instruction is a LOOP + if strcmp(instruction, 'LOOP') + cmd.append(Drivers.PulseStreamerMaster.seqCommand()); + end + % The number of nested loops should never exceed 10 since + % the pulse blaster has a maximum allowable nesting depth of 8. + elseif depth > 10 + return + end + + + % if the last element of cmd is a list, then get that list + % otherwise use the list cmd directly. this takes into + % account the cases where CONTINUEs may not reside within + % LOOPs. + c_cmd = cmd; + + % verify cmd isn not empty, otherwise you will keep nesting lists + % unwantedly + if length(cmd.command) > 0 + % Is the last element in cmd a cell array of maps + if isa(cmd.command{end}, 'Drivers.PulseStreamerMaster.seqCommand') + c_cmd = cmd.command{end}; + end + end + + %Debugging: report the length of cmd.command to track its growth + if DEBUG > 0 + fprintf('CMD length is: %i\n\n',length(cmd.command)) + end + + % add the instruction at index in the JSON file to the + % current list of instructions + c_cmd.append(json_seq{index}); + + %Debugging: report recursion depth, current instruction and + %instruction index. + if DEBUG > 0 + fprintf('depth = %5d\tinstruction: %s, %d\n',depth,instruction,index); + end + + % loop over the instructions in the json list + while index < length(json_seq) + index = index + 1; % IMPORTANT: remember to increment index into json list + + % get instruction type + instruction = json_seq{index}('instruction'); + + %Debugging: report recursion depth, current instruction and + %instruction index. + if DEBUG > 0 + fprintf('depth = %5d\tinstruction: %s, %d\n', depth,instruction,index); + end + + % Decide what to do depending on instruction type + if strcmp(instruction,'END_LOOP') + c_cmd.append(json_seq{index}); + % we must return in order that decodeInstructions + % terminates so that the decoder can continue to + % the next json instruction + return % Important, this is a terminating case + % for the recursion + + elseif strcmp(instruction,'LOOP') + % since this is a loop, create a new empty list to + % receive the instructions for this loop. + % IMPORTANT: the value of index can change within + % decodeInstructions, so we must return it + c_cmd.append(Drivers.PulseStreamerMaster.seqCommand) + + %Debugging: report current instruction data. + if DEBUG > 0 + fprintf('In loop current command is\n') + disp(c_cmd(end)) + end + index = obj.decodeInstructions(json_seq,c_cmd(end),depth,index); + + %Debugging: report current index. + if DEBUG > 0 + fprintf('index %d\n', index); + end + + elseif strcmp(instruction,'CONTINUE') + %since this is a continue statement we will simply + %append the instruction data to the cmd.(remember c_cmd + %is handle for cmd). + c_cmd.append(json_seq{index}); + + else + error('** error decoding JSON file\n') + + end + end + end + + + function unroll_tree(obj,cmd,map,depth) + % unroll_tree rolls all the instructions in the instruction + % tree into a 1 dimensional cell array of maps. + % NOTE: cmd and map are the variables in which data is appended to each + % upon recursion. To ensure the cmd and map are appropriately appended the + % property obj.cmd and obj.map are passed to unroll_tree when this function + % is called. Whereas in the recursive step within this defintion + % the local variables cmd.command{c} and map are passed to decodeInstructions. + + DEBUG = 0; + %DEBUGGING: reprot when unroll_tree function has been called. + if DEBUG>0 + fprintf('\n\nIn unroll_tree\n'); + end + + %Default values for map and depth. + switch nargin + case 3 + depth = 0; + end + + % Debugging: grow/ concatentate a string of tabs every time + % unroll_tree recurses. Proves pretty printing to ilustrate + % loop nesting. + if DEBUG > 0 + tab = ''; + for i=1:1:depth + tab = strcat('...',tab); + end + end + + % loop over top level instructions of abstract syntax tree, + % here called cmd + for c = 1:1:length(cmd.command) + % if current instruction is a list of instructions, then + % loop over those instructions + + % Debugging: Confirm tht execute_loop should be ran. + if DEBUG > 1 + fprintf('Condition to enter execute loop true? '); + disp(isa(cmd.command{c},'Drivers.PulseStreamerMaster.seqCommand')) + end + + if isa(cmd.command{c},'Drivers.PulseStreamerMaster.seqCommand') + obj.execute_loop(cmd.command{c},map,depth); + else + % this is not a list, but a map + + % Debugging: report recursion depth, current instruction and + %i nstruction index. + if DEBUG > 0 + fprintf('%s%s: duration = %i, flags = %i %i %i %i\n', ... + tab, ... + cmd.command{c}('instruction'), ... + cmd.command{c}('duration'), ... + cmd.command{c}('flag').'); + end + + obj.map.append(cmd.command{c}); + + end + end + end + + + function printTree(obj, cmd, depth) + % Print the Tree of instructions + + % Default arguments + switch nargin + case 2 + depth = 0; + end + + % track recurrsion depths + %kick out printTree if recurrsion depth exceeds 10 + depth = depth + 1; + if depth == 1 + fprintf('\n'); + elseif depth > 10 + return + end + fprintf('depth: %i\n' , depth); + + tab = ''; + for i=1:depth + + tab = strcat('...',tab); + end + + % Loop acrous length of cmd cell array (list). If a list is found then + % printTree of that list. Please note that the seqCommand is a + % hadnle class which allows for the concatenation of any item + % type (i.e. itis analogous to a list). + for ii = 1:1:length(cmd.command) + c = cmd.command{ii}; + if isa(c,'Drivers.PulseStreamerMaster.seqCommand') + obj.printTree(c,depth); + else + % Otherwise the current intstruction is a map + % print is and the depth layer, flags and index. + fprintf('%s%s: duration = %i, flags = %i %i %i %i, data = %d\n', ... + tab, ... + c('instruction'), ... + c('duration'), ... + c('flag').', ... + c('data')); + end + end + end + + function map = execute_loop(obj,cmd,map,depth) + + DEBUG = 0; + + if DEBUG > 1 + fprintf('\n\nIn execute_loop\n'); + end + + % Default arguments + switch nargin + case 2 + map = Drivers.PulseStreamerMaster.seqCommand(); + depth = 0; + case 3 + depth = 0; + end + + %increment recurssion depth and return if depth exceeds 10. + depth = depth+1; + + if depth > 10 + return; + end + + if DEBUG > 0 + tab = ''; + for i=1:depth + tab = strcat('...',tab); + end + end + + %current loop instruction. By default if this function is + %executed then the first instruction in the list is LOOP. + c = cmd.command{1}; + + % LOOP instruction + %Debugging: Report instruction in loop + if DEBUG > 0 + fprintf('%s%s: duration = %i, flags = %i %i %i %i, data = %d\n', ... + tab, ... + c('instruction'), ... + c('duration'), ... + c('flag').', ... + c('data')); + + end + + %error handling + if ~strcmp(c('instruction'),'LOOP') + error('Expected LOOP instruction, but found %s\n', c('instruction')) + end + + %Debugging: Current map to concatenate + if DEBUG > 1 + fprintf('Current map to be appended: '); + disp(c) + end + + %append current map + map.append(cmd.command{1}); + + %Debugging: Display updated list of instruction maps + if DEBUG > 1 + fprintf('Current list of maps: '); + disp(map) + end + + % Implement loop + loop_cmd = cmd.command; + span = c('data'); %number of times to increment loop + + for ii = 1:1:span + % loop over instructions for current loop + % excluding the LOOP and END_LOOP instructions + + if DEBUG > 1 + disp(length(loop_cmd)) + end + + %skip 1 and last instruction set since these are not looped + %over (i.e. the instructions on LOOP and END_LOOP are + %always ran before and after the curret loop, respectively. + %Loop over remainging instructions. + for jj = 2:1:(length(loop_cmd)-1) + + if DEBUG > 1 + fprintf('current jj: '); + disp(jj) + end + + %if an instruction within this loop is LOOP then + %recurse into execute_loop. + if isa(loop_cmd{jj},'Drivers.PulseStreamerMaster.seqCommand') + + if DEBUG > 1 + fprintf('current loop instruction: '); + disp(loop_cmd{jj}) + end + obj.execute_loop(loop_cmd{jj},map,depth); + % continue to next instruction for this loop + continue + end + % Otherwise current instruction must be a CONTINUE + % so just execute it + c = loop_cmd{jj}; + + %append map of continue instruction. + map.append(c); + + % Debugging: report recursion depth, current instruction and + % instruction index. + if DEBUG > 0 + + fprintf('%s%s: duration = %i, flags = %i %i %i %i\n', ... + tab, ... + c('instruction'), ... + c('duration'), ... + c('flag').'); + end + + %If instruction is not CONTINUE then throw error. + if ~strcmp(c('instruction'),'CONTINUE') + error('Expected CONTINUE instruction, but found %s\n', ... + c('instruction')) + end + end + end + + % END_LOOP instruction is last instruction in list of loop + % instructions + c = cmd.command{end}; + + % append map of continue instruction. + map.append(c); + + % Debugging: report recursion depth, current instruction and + % instruction index. + if DEBUG > 0 + + fprintf('%s%s: duration = %i, flags = %i %i %i %i\n', ... + tab, ... + c('instruction'), ... + c('duration'), ... + c('flag').'); + end + + %Throw error last instruction in loop is not 'END_LOOP' + if ~strcmp(c('instruction'),'END_LOOP') + error('Expected END_LOOP instruction, but found %s\n', ... + c('instruction')) + end + + %Debugging: Notify when exiting execute_loop + if DEBUG > 0 + fprintf('\n\nExiting execute_loop\n'); + end + end + + function [json_seq,obj] = readJSON(obj) + % readJSON converts the json string stored in obj.json into a cell + % array of maps, where each field in the map correspond to the + % fields in the json string sequence field. The remaining data in + % the obj.json string is store in seq_meta: a structure data type + % which houses the channels, units name and forever fields for the + % pulse sequence. + + DEBUG = 0; + + % compile now pass a matlab structure equivalent to + % the old structure = jsondecode(json_pulse_sequence) + %json_total = jsondecode(obj.json); + json_total = obj.json; + json_struct = json_total.sequence; + + + %initialize the json_seq cell array and fill each element in + %array with map for each instruction line. + json_seq = cell(1,length(json_struct)); + for i=1:1:length(json_struct) + cells = struct2cell(json_struct(i)); + key_labels = {'flag','duration','instruction','notes','data'}; + json_seq{i} = containers.Map(key_labels,cells); + end + + %return remaining human readabl fields (channels, units, name, + %forever) to seq_meta + seq_metadata.channels = json_total.channels; + seq_metadata.units = json_total.units; + seq_metadata.name = json_total.name; + seq_metadata.repeat = json_total.repeat; + + obj.seq_meta = seq_metadata; + + if DEBUG>0 + fprintf('seq_meta output of readJSON: \n'); + disp(obj.seq_meta); + end + end + + + end + + methods + + % Destructor method. Clears object properties. + function delete(obj) + if obj.PS.isStreaming() == 1 + obj.stop() + elseif obj.PS.hasSequence() == 1 + obj.stop() + end + end + end +end + + diff --git a/Modules/+Drivers/+PulseStreamerMaster/seqCommand.m b/Modules/+Drivers/+PulseStreamerMaster/seqCommand.m new file mode 100644 index 000000000..24b71e40e --- /dev/null +++ b/Modules/+Drivers/+PulseStreamerMaster/seqCommand.m @@ -0,0 +1,30 @@ +classdef seqCommand < handle + properties + command={}; + %appended_value = {}; + + end + + + + methods + + function obj = seqCommand() + obj.command(:) = []; + end + + function append(obj,appended_value) + obj.command{end+1} = appended_value; + end + + function delete(obj) + %fprintf("I'm a sequence command and that's ok. I'm about to be deleted.\n") + end + + function element = end_array(obj) + element = obj.command{end}; + end + + end + +end diff --git a/Modules/+Drivers/CWave.m b/Modules/+Drivers/CWave.m new file mode 100644 index 000000000..b4828d708 --- /dev/null +++ b/Modules/+Drivers/CWave.m @@ -0,0 +1,1005 @@ +classdef CWave < Modules.Driver + %CWave connects with host machine to control cwave laser + % + % for fine-tuning best used with a wavemeter feedback loop + + %properties(SetAccess = immutable) + %ip = '192.168.11.3'; + %password = 457; %457 gives engineering access. 111 gives full superuser access. Not recommended...why? + %end + + properties + init_warnings + dll_ver + ip + status + target_wavelength = 0; % Target Wavelength + all_shutters = 'open'; + shg_shutter = 'open'; + lsr_shutter = 'open'; + opo_stepper_stat = 0; + opo_temp_stat = false(1); + shg_stepper_stat = false(1); + shg_temp_stat = false(1); + thin_etalon_stat = false(1); + opo_lock_stat = false(1); + shg_lock_stat = false(1); + etalon_lock_stat = false(1); + laser_emission_stat = false(1); + ref_temp_stat = false(1); + CurrentLaserPWR; + end + + properties(Constant,Hidden) + % constants for C library + %Pathx64 = 'C:\Program Files (x86)\Hubner\C-WAVE Control\MatlabControl\x64\'; + %Pathx86 = 'C:\Program Files (x86)\Hubner\C-WAVE Control\MatlabControl\x86\'; + %HPath = 'C:\Program Files (x86)\Hubner\C-WAVE Control\MatlabControl\'; + %Pathx64 = 'C:\Program Files (x86)\Hubner\C-WAVE Control\C-WAVE SDK v22\bin\x64\'; + %Pathx86 = 'C:\Program Files (x86)\Hubner\C-WAVE Control\C-WAVE SDK v22\bin\x86\'; + %HPath = 'C:\Program Files (x86)\Hubner\C-WAVE Control\C-WAVE SDK v22\bin\'; + Pathx64 = 'C:\Program Files (x86)\Hubner\C-WAVE Control\C-WAVE SDK v24\bin\x64\'; + Pathx86 = 'C:\Program Files (x86)\Hubner\C-WAVE Control\C-WAVE SDK v24\bin\x86\'; + HPath = 'C:\Program Files (x86)\Hubner\C-WAVE Control\C-WAVE SDK v24\bin\'; + LibraryName = 'CWAVE_DLL'; % alias for library + LibraryFilePath = 'CWAVE_DLL.dll'; % Path to dll + LibraryHeader = 'CWAVE_DLL.h'; + OS_64bit = 'win64'; + ComputerArch = 'arch'; + ConnectCwave = 'cwave_connect'; + DLL_Version= 'DLL_Version'; + DLL_identity = 24; %22; %20; + Admin = 'admin_elevate'; + OPO_rLambda = 'opo_rlambda'; + TempRef = 'tref_is'; + TempOPO = 'topo_is'; + TempSHG1 = 'tshg_is1'; + TempSHG2 = 'tshg_is2'; + TempOPO_sp = 'topo_set'; + TempSHG_sp = 'tshg_set'; + TempRef_sp = 'tref_set'; + TempFPGA = 'temp_fpga'; + TempBase = 'temp_base'; + UpdateStatus = 'cwave_updatestatus'; + Get_IntValue = 'get_intvalue'; + Get_floatValue = 'get_floatvalue'; + Set_IntValue = 'set_intvalue'; + Set_FloatValue = 'set_floatvalue'; + Is_Ready = 'is_ready'; + SetCommand = 'set_command'; + LaserPower = 'get_photodiode_laser'; + OPO_Lambda = 'opo_lambda'; + OPO_Power = 'get_photodiode_opo'; + OptimizeSHG = 'opt_tempshg'; + SHG_Power = 'get_photodiode_shg'; + StatusReport = 'get_statusbits'; + LaserStatus = 'get_status_laser'; + Reference_TempStatus = 'get_status_temp_ref'; + OPO_TempStatus = 'get_status_temp_opo'; + SHG_TempStatus = 'get_status_temp_shg'; + OPO_LockStatus = 'get_status_lock_opo'; + SHG_LockStatus = 'get_status_lock_shg'; + Etalon_LockStatus = 'get_status_lock_etalon'; + WLM_PID_Optimize = 'WLM_PID_Compute'; + WLM_PID_Setpoint = 'WLM_pid_setpoint'; % valid range 450 - 1300 (nm), double + WLM_BigSteps = 'WLM_bigsteps'; + WLM_EtalonSteps = 'WLM_etalonsteps'; + WLM_PiezoSteps = 'WLM_piezosteps'; + WLM_targetdeviation = 'WLM_targetdeviation'; + WLM_pid_p = 'WLM_pid_p'; + WLM_pid_i = 'WLM_pid_i'; + Ext_SetCommand = 'ext_set_command'; + ExtGet_IntValue = 'ext_get_intvalue'; + ExtGet_FloatValue = 'ext_get_floatvalue'; + RelockEtalon = 'reg_etacatch'; + ShutterSHG = 'shtter_shg'; + ShutterLaser = 'shtter_las'; + Open = 'open'; + Close = 'close'; + StopOptimization = 'opt_stop'; + CoarseTune = 'coarse'; + FineTune = 'fine'; + Disconnect = 'cwave_disconnect'; + RegOpo_On = 'regopo_on'; + RegOpo_Out = 'regopo_out'; + %%RegOpo_Set = 'regopo_set'; + RefCavity_Piezo = 'x'; + SetRefOpoExtramp = 'set_regopo_extramp'; + ThickEtalon_Piezo_hr = 'thicketa_rel_hr'; + ThickEtalon_Piezo = 'thicketa_rel'; + Piezo_maxBit = 65535/100; + Laser_MaxPower = 5000; %dummy value. value needs to be calibrated (testing needed) + Laser_MinPower = 1000; %dummy value. value needs to be calibrated (testing needed) + OPO_MaxPower = 10000; %dummy value. value needs to be calibrated (testing needed) + OPO_MinPower = 100; %dummy value. value needs to be calibrated (testing needed) + SHG_MaxPower = 10000; %dummy value. value needs to be calibrated (testing needed) + SHG_MinPower = 25; %was 100%dummy value. value needs to be calibrated (testing needed) + end + %% Signleton Method + methods(Static) + function obj = instance(ip) + mlock; + persistent Objects + if isempty(Objects) + Objects = Drivers.CWave.empty(1,0); + end + for i = 1:length(Objects) + if isvalid(Objects(i)) && isequal({ip},Objects(i).singleton_id) + obj = Objects(i); + return + end + end + obj = Drivers.CWave(ip); + obj.ip = ip; + obj.singleton_id = {ip}; + Objects(end+1) = obj; + end + end + + %% Constructor Method + methods(Access={?Drivers.CWave}) + function obj = CWave(ip) + obj.dll_ver = load_cwave_dll(obj); %load dll for cwave + obj.status = obj.cwave_connect(ip); %connect cwave + pause(1) + %open all internal and output shutters in cwave system + %obj.shutter_lsr(); + %obj.shutter_shg(); + %obj.initialize_shutters; + %ret = obj.set_intvalue(obj.WLM_BigSteps, 0); + %assert(ret == 1, 'Turning off large steps in PID failed'); + end + end + + %% Methods accessible to user + methods + + function dll_ver = load_cwave_dll(obj) + % load DLL + if (~libisloaded(obj.LibraryName)) + if (strcmp(computer(obj.ComputerArch), obj.OS_64bit)) + %Change file path to full file path. Do not use relative + %file path. Also all strings should be switched to constant + %properties + %loadlibrary('x64/CWAVE_DLL', obj.LibraryHeader); + path = fullfile(obj.Pathx64 ,obj.LibraryFilePath); % 64bit + hpath = fullfile(obj.HPath, obj.LibraryHeader); + [~,obj.init_warnings] = loadlibrary(path, hpath, 'alias',obj.LibraryName); + else + %loadlibrary('x86/CWAVE_DLL', obj.LibraryHeader); + path = fullfile(obj.Pathx86 ,obj.LibraryFilePath); % 32bit + hpath = fullfile(obj.HPath, obj.LibraryHeader); + [~,obj.init_warnings] = loadlibrary(path, hpath, 'alias',obj.LibraryName); + end + end + if (libisloaded(obj.LibraryName)) + %% return dll version + [~,dll_ver] = obj.dll_version(); + if (dll_ver ~= obj.DLL_identity) + assert(dll_ver == obj.DLL_identity, ['CWAVE DLL library not loaded, DLL version ' dll_ver ' not equal to ' obj.DLL_identity]); + end + end + end + + function [status,varargout] = LibraryFunction(obj,FunctionName,varargin) + % use this function to call arbitrary library functions from + % CWAVE DLL. Checks for error, and returns all bit status + + nargs = Base.libnargout(obj.LibraryName,FunctionName); + if nargs == 0 + varargout = {}; + status = []; + calllib(obj.LibraryName, FunctionName, varargin{:}); + elseif nargs < 2 + varargout = {}; + status = calllib(obj.LibraryName,FunctionName,varargin{:}); + else + [status,varargout{1:nargs-1}] = calllib(obj.LibraryName,FunctionName,varargin{:}); + end + status = obj.CheckErrorStatus(status, FunctionName,varargin{:}); + end + + function status = CheckErrorStatus(obj,status,FunctionName,varargin) + %edit cases TBD. Need to sort out string to report and + %flag/status for each function + inversion_condition = {obj.ConnectCwave,obj.Admin,obj.UpdateStatus,obj.Set_IntValue, obj.SetRefOpoExtramp ... + obj.Set_FloatValue,obj.SetCommand,obj.LaserStatus, obj.Ext_SetCommand}; + if(ismember(FunctionName, inversion_condition)) + if(status==-1) + status = boolean(0); + end + status = boolean(~status); + else + status = boolean(status); + end + switch FunctionName + case obj.ConnectCwave + % 0=connection successful, 1==connection failed + assert(status ==0, ['CWAVE Error: Connecting to ' obj.ip ' failed. Disconnect Cwave from Hubner software.']); + case obj.DLL_Version + assert(status == 0, ['CWAVE Error: Unauthorized CWAVE DLL version loaded']); + case obj.Admin + % 0==admin rights granted, 1=no admin rights granted + assert(status ==0, ['CWAVE Error: Admin Rights Not Granted. Incorrect password given']); + case obj.UpdateStatus + % 0==update succeeded, 1=update failed + assert(status == 0, ['CWAVE Error: Measurement status of C-wave not updated']); + case obj.Set_IntValue + switch varargin{1} + case obj.RegOpo_Out + %0: integer value set, 1: integer value not set + assert(status==0,'OPO cavity not tuned'); + case obj.RefCavity_Piezo + %0: integer value set, 1: integer value not set + assert(status==0,'Reference cavity not tuned.'); + case obj.WLM_BigSteps + %0: integer value set, 1: integer value not set + assert(status==0,'Turning off big wavelength steps during PID failed'); + case obj.WLM_EtalonSteps + %0: integer value set, 1: integer value not set + assert(status==0,'Turning off etalon steps during PID failed'); + case obj.WLM_PiezoSteps + %0: integer value set, 1: integer value not set + assert(status==0,'Turning on cavity piezo during PID failed'); + case obj.OPO_Lambda + %0: integer value set, 1: integer value not set + assert(status==0,'Setting target wavelength failed'); + case obj.ShutterSHG + %0: integer value set, 1: integer value not set + assert(status==0,'SHG shutter failed'); + case obj.ShutterLaser + %0: integer value set, 1: integer value not set + assert(status==0,'Laser shutter failed'); + end + % 0== integer value set, 1= integer value not set + assert(status == 0, 'CWAVE Error: Int value not set'); + case obj.SetRefOpoExtramp + assert(status == 0, 'Opo Piezo was not set to ramp.'); + case obj.Is_Ready + % 0=C-wave is ready, Optimization has completed; 1==C-wave still optimizing + if status == 1 + if obj.get_regopo == 2 + disp('CWAVE Warning: C-Wave not ready. Optimization still in progress.\n') + elseif obj.get_regopo == 4 + regOPO4 = ~(obj.opo_stepper_stat & obj.opo_temp_stat & obj.shg_stepper_stat & ... + obj.shg_temp_stat & obj.thin_etalon_stat & obj.etalon_lock_stat & ... + obj.laser_emission_stat & obj.ref_temp_stat); + %obj.opo_lock_stat; + %obj.shg_lock_stat; + [SHGpower, ~] = obj.get_photodiode_shg; + if regOPO4 == true & SHGpower > obj.SHG_MinPower % sSHGpower == false + status = 0; + %disp('CWAVE Warning: C-Wave ready (in RegOPO mode 4).\n'); + else + disp('CWAVE Warning: C-Wave not ready. Optimization still in progress.\n') + end + end + elseif status == 0 + disp('Cwave ready!\n'); + end + %assert(status == 0, ['CWAVE Error: C-Wave not ready. Optimization still in progress']); + case obj.Set_FloatValue + switch varargin{1} + case obj.WLM_PID_Setpoint + % 0==update succeeded, 1=update failed + assert(status == 0, 'Setting setpoint wavelength failed'); + end + % 0==update succeeded, 1=update failed + assert(status == 0, ['CWAVE Error: float value not set']); + case obj.SetCommand + switch varargin{1} + case obj.StopOptimization + assert(status == 0, 'Optimization has not stopped'); + case obj.OptimizeSHG + assert(status == 0, 'SHG optimization failed.'); + case obj.RelockEtalon + assert(status == 0, 'Etalon failed to relock.'); + end + % 0=update succeeded, 1=update failed + assert(status == 0, ['CWAVE Error: command not executed. Check that set_command input are valid.']); + case obj.LaserPower + % 0=update succeeded, 1=update failed + if (obj.CurrentLaserPWR > obj.Laser_MaxPower | status ==1) + assert(status == 0,'CWAVE Error: Laser power not within standard operating range. Lower Power Immediately!!!'); + + %elseif status == 0 + % disp('CWAVE Announcement: Laser power within standard operating range.'); + end + case obj.OPO_Power + % 0=update succeeded, 1=update failed + if status == 1 + error('CWAVE Warning: OPO power not within standard operating range.'); + %elseif status == 0 + % disp('CWAVE Announcement: OPO power in standard operating range.'); + end + case obj.SHG_Power + % 0=update succeeded, 1=update failed + if status == 1 + disp('CWAVE Warning: SHG power not within standard operating range.'); + %elseif status == 0 + % disp('CWAVE Announcement: SHG power in standard operating range.'); + end + case obj.StatusReport + if (obj.opo_stepper_stat == 1) + disp('OPO stepper not locked') + elseif (obj.opo_temp_stat == 1) + disp('OPO temperature not locked'); + elseif (obj.shg_stepper_stat == 1) + disp('SHG stepper not locked'); + elseif (obj.shg_temp_stat == 1) + disp('SHG temp not locked'); + elseif (obj.thin_etalon_stat == 1) + disp('Thin etalon not locked'); + elseif (obj.opo_lock_stat == 1 && obj.get_intvalue(obj.RegOpo_On) == 2) + disp('OPO cavity piezo not locked.'); + elseif (obj.shg_lock_stat == 1 && obj.get_intvalue(obj.RegOpo_On) == 2) + disp('SHG cavity piezo not locked'); + %elseif (obj.opo_lock_stat == 1 && obj.shg_lock_stat == 1 && obj.get_intvalue(obj.RegOpo_On) == 4) + % disp('In RegOPO mode 4. OPO and SHG are not locked.'); + elseif (obj.etalon_lock_stat == 1) + disp('thick etalon not locked'); + elseif (obj.laser_emission_stat == 1) + disp('No Pump emission! Unshutter Millenia Edge!'); + elseif (obj.ref_temp_stat == 1) + disp('Reference cavity temperature is not locked!!!'); + end + + % 0=update succeeded, 1=update failed + %disp('CWAVE Warning: All elements are not stable and/or locked.'); + case obj.LaserStatus + % 0=update failed, 1==update succeeded + assert(status == 0, ['Insufficient Laser power. Check that pump laser is active and that the laser shutter is open']); + case obj.Reference_TempStatus + % 0=referance temperature stabilized, 1==reference temperature not at setpoint + assert(status == 0, ['Reference temperature not at setpoint']); + case obj.OPO_TempStatus + % 0=referance temperature stabilized, 1==reference temperature not at setpoint + assert(status == 0, ['OPO temperature not at setpoint']); + case obj.SHG_TempStatus + % 0=SHG temperature stabilized, 1==SHG temperature not at setpoint + assert(status == 0, ['SHG temperature not at setpoint']); + case obj.OPO_LockStatus + % 0=OPO lock stabilized, 1==OPO not locked to reference cavity. Still optimizing + assert(status == 0, ['OPO not locked to reference cavity. Optimization still in progress']); + case obj.SHG_LockStatus + % 0=SHG lock stabilized, 1==SHG not locked to reference cavity. Still optimizing + assert(status == 0, ['SHG not locked to reference cavity. Optimization still in progress']); + case obj.Etalon_LockStatus + % 0=etalon lock stabilized, 1==etalon not locked to reference cavity. Still optimizing + assert(status == 0, ['etalon not locked to reference cavity. Optimization still in progress']); + case obj.WLM_PID_Optimize + case obj.Ext_SetCommand + % 0=command executed correctly, 1==error command not executed by external module + assert(status == 1, ['Command not executed by external module. Check that it is on.']); + end + end + + function status = cwave_connect(obj, ip) + %Description: Connects to the C-Wave. This function has to be executed once during runtime. + %Arguments: ipAddress is the IP address of the CWAVE as string in the format 123.123.123.123 + %Returns: int value, 0 means connection failed, 1 means successfully connected. + %Logic inverted from original DLL status bit by LibraryFunction. + %% connect to device + status = obj.LibraryFunction(obj.ConnectCwave, ip); + % mitigate bug in DLL, first connection attempt might fail -> retry + if (status == 1) + status = obj.LibraryFunction(obj.ConnectCwave, ip); + end + end + + function [status,dllVer] = dll_version(obj) + %Description: Reads version of the DLL + %Returns: Returns an integer value with the version of the DLL + % also return status bit (0 = correct dll version, 1 = + % incorrect dll version). + dllVer = calllib(obj.LibraryName, obj.DLL_Version); + disp('dllver:') + disp(dllVer) + if( dllVer ~= obj.DLL_identity) + status = 1; + else + status = 0; + end + disp(['C-WAVE DLL loaded. Version: ' num2str(dllVer)]); + obj.CheckErrorStatus(status,obj.DLL_Version); + end + + function admin_status = admin_elevate(obj,password) + %Description: Grants admin rights to the user to access advanced commands. + %Arguments: password is the password as string. + %Returns: Returns an integer value. 1 means no admin rights, 0 means admin rights + admin_status = LibraryFunction(obj.LibraryName,obj.Admin,password); + end + + function measure_status = cwave_updatestatus(obj) + %Description: Manually updates status info (photodiode values, temperatures, statusbits) in the library. + %This function is automatically executed on demand, so there is usually no need to execute it manually. + %Returns: Returns status int value. 0 means update succeeded, 1 means update failed. + measure_status = LibraryFunction(obj.LibraryName,obj.UpdateStatus); + end + + function intvalue = get_intvalue(obj, cmd) + %Description: Reads the value of an integer parameter. + %Arguments: Parameter as string. See parameter list for valid parameters. + %Returns: Returns the requested integer value. + + %% INT PARAMETER LIST + % Name Type Valid range Read / Write Description + %% topo_set Int 20000-170000 RW Setpoint of the OPO temperature in mK + % topo_is Int 20000-170000 R Current OPO temperature in mK + %% tshg_set Int 20000-170000 RW Setpoint of the SHG temperature in mK + % tshg_is1 Int 20000-170000 R Current SHG1 temperature in mK + %% tshg_is2 Int 20000-170000 R Current SHG2 temperature in mK + % tref_set Int 20000-170000 RW Setpoint of the reference temperature in mK + %% tref_is Int 20000-170000 R Current reference temperature in mK + % shtter_las Int 0, 1 RW Laser shutter position. 1 means open, 0 closed + %% shtter_shg Int 0, 1 RW SHG shutter position. 1 means open, 0 closed + % shtter_las_out Int 0, 1 RW Laser output shutter position. 1 means open, 0 closed + %% shtter_opo_out Int 0, 1 RW OPO output shutter position. 1 means open, 0 closed + % shtter_shg_out Int 0, 1 RW SHG output shutter position. 1 means open, 0 closed + %% laser_en Int 0, 1 RW Enable internal pump laser. 0 disabled, 1 enabled + % monout_sel Int 0-12 RW Select Signal at monitor 1 output. + % 0: Error Signal OPO + % 1: Error Signal SHG + % 2: Error Signal Etalon + % 4: Piezo OPO + % 5: Piezo SHG + % 6: Piezo Etalon + % 7: Piezo Reference + % 9: Pump laser power + % 11: SHG power + % 12: OPO power + %% monout2_sel Int 0-12 RW Select Signal at monitor 2 output. See Monitor 1 for details. + % regopo_on Int 0-2 RW OPO regulator mode. + % 0: off + % 1: scan + % 2: regulate + %% regshg_on Int 0-2 RW SHG regulator mode. Mode description see regopo_on + % regeta_on Int 0-2 RW SHG regulator mode. Mode description see regopo_on + %% regopo_set Int 0-65535 RW Setpoint of the OPO regulator. Mid-range values are normal. + %% Should not need to be touched. + % regshg_set Int 0-65535 RW Setpoint of the OPO regulator. Mid-range values are normal. + % Should not need to be touched. + %% regeta_set Int 0-65535 RW Setpoint of the OPO regulator. Mid-range values are normal. + %% Should not need to be touched. + % reghsg_threshold Int 0-4095 RW SHG power threshold above which SHG regulator is active. + % Needed to select proper mode. Is set automatically, + % usually no user input required. + %% opo_lambda Int 45000-130000 W Wavelength setpoint of the C-Wave in nm*100. + % opo_rlambda Int -100-100 W Execute relative wavelength step (fundamental wavelength!) + % in nm*100. Maximum step is 1 nm. + %% thicketa_rel Int -100-100 W Execute relative wavelength step of the thick etalon only + %% (fundamental wavelength!) in nm*100. Maximum step is 1 nm. + % thicketa_rel_hr Int -1000...1000 W Same as thicketa_rel but resolution is 1 pm + + %% WAVELENGTH STABILIZATION PARAMETERS + % Name Type Valid range Default Description + %% WLM_pid_p Int 0-100000 0 Proportional constant of the wavelength regulator. + %% Not needed for many applications + % WLM_pid_i Int 0-100000 500 Integral constant for wavelength regulator + %% WLM_pid_direction Int -1, 0, 1 -1 Direction of the regulator. Does not have to be changed + %% in most cases. + % WLM_bigsteps Int 0, 1 1 Allow the regulator to do big wavelength steps, e. g. completely + % re-dial a wavelength if the setpoint is too far away from the + % current output wavelength. + %% WLM_etalonsteps Int 0, 1 1 Allow the regulator to touch the thick etalon to reach + %% the desired wavelength. + % WLM_piezosteps Int 0, 1 1 Allow the regulator to move the cavity piezo to reach + % the desired wavelength. + %% WLM_regout Int 0-65535 0 Regulator output, good for checking if it works + + %% Read value of integer parameter + % no error status bit is returned so calllib is used. + intvalue = calllib(obj.LibraryName,obj.Get_IntValue,cmd); + end + + function floatvalue = get_floatvalue(obj, cmd) + %Description: Reads the value of an floating point parameter. + %Arguments: Parameter as string. See parameter list for valid parameters. + %Returns: Returns the requested floating point value. + + %% INT PARAMETER LIST + % Name Type Valid range Read / Write Description + %% laser_pow Double 0?1.5 RW Laser power of internal pump laser in W + + %% WAVELENGTH STABILIZATION PARAMETERS + % Name Type Valid range Default Description + %% WLM_pid_setpoint Double 450?1300 Desired wavelength in nm. + % WLM_targetdeviation Double 0?1 0.01 Desired maximum deviation from the setpoint in nm. + % The minimum value depends on the used wavemeter resolution. + % Smaller values give higher accuracy but may require longer + % time or manual input. Larger values result in faster settling + % but a less accurate output wavelength. + + %% Read value of float parameter + floatvalue = calllib(obj.LibraryName,obj.Get_floatValue,cmd); + end + + function status = set_intvalue(obj, cmd,value) + % Description: Sets the value of an integer parameter. + % Arguments: cmd is the Parameter as string. See parameter list + % for valid parameters. value is the desired new value of the parameter. + % Returns: Returns 0 (1 before inversion) if the new value was set correctly. + % Returns 1 (-1 before inversion) if an error occurred. + %% Writable Int Parameters are listed above in get_intvalue function comments + %% Writable Wavelength stabilization parameters are listed above in get_intvalue function comments + status = obj.LibraryFunction(obj.Set_IntValue,cmd, value); + end + + function tBase = get_tBase(obj) + tBase = calllib(obj.LibraryName, obj.Get_IntValue,obj.TempBase); + tBase = tBase/1000; %Celcius + end + function tFPGA = get_tFPGA(obj) + tFPGA = calllib(obj.LibraryName, obj.Get_IntValue,obj.TempFPGA); + tFPGA = tFPGA/1000; %Celcius + end + function tref = get_tref(obj) + tref = calllib(obj.LibraryName,obj.Get_IntValue,obj.TempRef); + tref = tref/1000; % Celcius + end + function topo = get_topo(obj) + %topo = get_intvalue(obj.TempOPO); + topo = calllib(obj.LibraryName,obj.Get_IntValue,obj.TempOPO); + topo = topo/1000; % Celcius + end + function tshg = get_tshg(obj) + t_shg1 = calllib(obj.LibraryName,obj.Get_IntValue,obj.TempSHG1); %mk + t_shg2 = calllib(obj.LibraryName,obj.Get_IntValue,obj.TempSHG2); %mk + tshg = (t_shg1 + t_shg2)/2000; %Celcius + end + function tref = get_tref_sp(obj) + tref = calllib(obj.LibraryName,obj.Get_IntValue,obj.TempRef_sp); + tref= tref/1000; % Celcius + end + function topo = get_topo_sp(obj) + topo = calllib(obj.LibraryName,obj.Get_IntValue,obj.TempOPO_sp); + topo = topo/1000; % Celcius + end + function tshg = get_tshg_sp(obj) + tshg = calllib(obj.LibraryName,obj.Get_IntValue,obj.TempSHG_sp); + tshg = tshg/1000; % Celcius + end + + function optimize_status = is_ready(obj) + %Description: Checks if all C-Wave routines have finished and the C-Wave produces the desired output + %Arguments: none + %Returns: Returns an integer value. 0 means no errors, C-Wave is ready. 1 means C-Wave is still in optimization + %% Check if optimization is complete + optimize_status = obj.LibraryFunction(obj.Is_Ready); + end + + function status = set_floatvalue(obj, cmd,value) + % Description: Sets the value of an floating point parameter. + % Arguments: cmd is the Parameter as string. See parameter list + % for valid parameters. value is the desired new value of the parameter. + % Returns: Returns 0 (1 before inversion) if the new value was set correctly. + % Returns 1 (-1 before inversion) if an error occurred. + %% Writable Int Parameters are listed above in get_floatvalue function comments + %% Writable Wavelength stabilization parameters are listed above in get_floatvalue function comments + status = obj.LibraryFunction(obj.Set_FloatValue,cmd, value); + end + + function status = set_command(obj, cmd) + % Description: Executes a command which has no numerical argument. + % Arguments: cmd is the command as string. See the command list for reference. + % Returns: Returns 0 (1 before inversion) if the new command was executed correctly. Returns 1 (-1 before inversion) + % if an error occurred. + %% Parameter Name Description + % opt_tempshg Re-optimize SHG temperature by doing a temperature search. + % This command is automatically executed each time a new wavelength is selected. + %% regeta_catch Try to re-lock thick etalon to prevent multimode operation. + %% If SHG output is required, a successive SHG temperature search may be required. + % opt_stop Stop all optimizations. Usefull for full manual control of the C-Wave. + %% Set Command + status = obj.LibraryFunction(obj.SetCommand,cmd); + end + + function [laser_power,status] = get_photodiode_laser(obj) + % Description: Reads the current laser power (what unit??) + % Arguments: none + % Returns: Returns the laser photodiode value + %% Read laser power + laser_power = calllib(obj.LibraryName,obj.LaserPower); + %laser_power = calllib('CWAVE_DLL','get_photodiode_laser'); + if( laser_power > obj.Laser_MaxPower || laser_power < obj.Laser_MinPower) + status = 1; + else + status = 0; + end + obj.CurrentLaserPWR = laser_power; + obj.CheckErrorStatus(status,obj.LaserPower); + end + + function [opo_power,status] = get_photodiode_opo(obj) + % Description: Reads the current OPO infrared power + % Arguments: none + % Returns: Returns the infrared output power in mW + %% Read IR opo power + opo_power = calllib(obj.LibraryName,obj.OPO_Power); + %opo_power = calllib('CWAVE_DLL','get_photodiode_opo'); + if (opo_power > obj.OPO_MaxPower || opo_power < obj.OPO_MinPower) + status = 1; + else + status = 0; + end + obj.CheckErrorStatus(status,obj.OPO_Power); + end + + function [shg_power,status] = get_photodiode_shg(obj) + % Description: Reads the current (second harmonic generator) SHG visible power + % Arguments: none + % Returns: Returns the visible output power in mW + %% Read SHG power + shg_power = calllib(obj.LibraryName, obj.SHG_Power); + if (shg_power > obj.SHG_MaxPower || shg_power < obj.SHG_MinPower) + status = 1; + else + status = 0; + end + obj.CheckErrorStatus(status,obj.SHG_Power); + end + + function status = get_statusbits(obj) + % Description: Reads the current status of the C-Wave + % Arguments: none + % Returns: Returns an 16-bit integer value. Each bit corresponds to the + % status of one component. 0 means, the component is ready for operation, + % 1 means the component is not yet stable. Current valid bits from LSB to MSB are: + % 1 OPO stepper + % 2 OPO temperature + % 3 SHG stepper + % 4 SHG temperature + % 5 Thin etalon + % 6 OPO lock + % 7 SHG lock + % 8 Etalon lock + % 9 Laser emission (inverted) + % 10 Reference temperature + % Poll cwave status + cwave_status = calllib(obj.LibraryName, obj.StatusReport); %change to callib + status_vector = de2bi(cwave_status); + obj.opo_stepper_stat = boolean(status_vector(1)); + obj.opo_temp_stat = boolean(status_vector(2)); + obj.shg_stepper_stat = boolean(status_vector(3)); + obj.shg_temp_stat = boolean(status_vector(4)); + obj.thin_etalon_stat = boolean(status_vector(5)); + obj.opo_lock_stat = boolean(status_vector(6)); + obj.shg_lock_stat = boolean(status_vector(7)); + obj.etalon_lock_stat = boolean(status_vector(8)); + obj.laser_emission_stat = ~boolean(status_vector(9)); + obj.ref_temp_stat = obj.get_status_temp_ref; %doesnt sense error bit + %sometimes obj.Reference_TempStatus + if(all([status_vector(1:8),~status_vector(9),obj.ref_temp_stat] == 0)) + % there is an 11th bit with no documented hw meaning; should be ignored + status = 0; + else + status = 1; + end + obj.CheckErrorStatus(status,obj.StatusReport); + end + + function WLM_PID_Compute(obj, wl_measured) + % Description: This function executes automatic wavelength regulation of the + % C-Wave if the current output wavelength is measured by an external wavemeter + % and monitored back into the C-Wave by this function. See WLM parameters for + % details. The C-Wave is automatically adapted to the new wavelength measurement + % each time this function is executed. To disable the automatic wavelength + % regulation just do not execute this function. + % Arguments: measurement is the current measured wavelength in nm. You can + % provide fundamental or SHG measurement. However, measuring the fundamental + % wavelength will be more reliable for complete automation. + % Returns: none + obj.LibraryFunction(obj.WLM_PID_Optimize,wl_measured); % suggest change to callib + end + + function shutter_lsr(obj) + %open or close internal pump laser shutter + if strcmp(obj.lsr_shutter, obj.Open) + obj.set_intvalue(obj.ShutterLaser,1); + elseif strcmp(obj.lsr_shutter, obj.Close) + obj.set_intvalue(obj.ShutterLaser,0); + end + end + + function shutter_shg(obj) + %open or close SHG shutter + if strcmp(obj.shg_shutter, obj.Open) + obj.set_intvalue(obj.ShutterSHG,1); + elseif strcmp(obj.shg_shutter, obj.Close) + obj.set_intvalue(obj.ShutterSHG,0); + end + end + + %should this function still exist seems useless + function status = getStatus(obj) + % poll connection status if CWAVE + % Function Call currently not avialable waiting DLL file info + % from Hubner + status = obj.cwave_connect(obj.ip); + end + + function delete(obj) + %Delete instance of CWAVE object. + %Disconnect CWAVE + obj.disconnect_cwave(); + + %clear public properties + obj.init_warnings = []; + obj.dll_ver = []; + obj.ip = []; + obj.status = []; + obj.target_wavelength = []; % Target Wavelength + obj.all_shutters = []; + obj.shg_shutter = []; + obj.lsr_shutter = []; + obj.opo_stepper_stat = []; + obj.opo_temp_stat = []; + obj.shg_stepper_stat = []; + obj.shg_temp_stat = []; + obj.thin_etalon_stat = []; + obj.opo_lock_stat = []; + obj.shg_lock_stat = []; + obj.etalon_lock_stat = []; + obj.laser_emission_stat = []; + obj.ref_temp_stat = []; + + %clean up loaded library from memory + unloadlibrary(obj.LibraryName); + %status = libisloaded(obj.LibraryName); + %if status + % assert(status==0, 'CWAVE Library still in memory!'); + %end + end + + function disconnect_cwave(obj) + % Probably easiset if using a disconnect function to disconnect + % CWAVE + obj.LibraryFunction(obj.Disconnect); + end + + function set_target_wavelength(obj) + %set target wavelength with a coarse 0.01 nm resolution + obj.set_intvalue(obj.OPO_Lambda,round(obj.target_wavelength*100)); + disp(['Target wavelength set: ' num2str(round(obj.target_wavelength*100)/100) 'nm']); + % IMPORTANT: wait one second before starting to poll for ready + pause(1); + end + + function set_pid_target_wavelength(obj, setpoint) + % set the target wavelength to fine tune toward + typecheck = isa(setpoint, 'double'); + assert(typecheck == 1, 'Setpoint must be double precision float'); + + ret = obj.set_floatvalue(obj.WLM_PID_Setpoint, setpoint); + assert(ret == 0, 'Setting setpoint wavelength failed'); + disp(['Setpoint wavelength set: ' num2str(setpoint) 'nm']); + % IMPORTANT: wait one second before starting to poll for ready + pause(1); + end + + function set_OPOrLambda(obj,val) + %set realtive wavlength shift for shifts 0.1 nm or greater. + obj.set_intvalue(obj.OPO_rLambda, val); + end + + function set_target_deviation(obj, target_dev) + obj.set_floatvalue(obj.WLM_targetdeviation, target_dev); + end + + function fine_tune(obj) + % fine tune based on wavemeter measurement + obj.set_intvalue(obj.WLM_PiezoSteps, 1); + % turn off etalon control + obj.set_intvalue(obj.WLM_EtalonSteps, 0); + %turn off big wavelength steps (OPO and SHG remain locked) + obj.set_intvalue(obj.WLM_BigSteps, 0); + end + + function coarse_tune(obj) + % coarse tune based on wavemeter measurement + % turn on reference cavity steps + obj.set_intvalue(obj.WLM_PiezoSteps, 1); + %turn on etalon steps + obj.set_intvalue(obj.WLM_EtalonSteps, 1); + %turn off big wavelength steps (OPO and SHG remain locked) + obj.set_intvalue(obj.WLM_BigSteps, 0); + end + + function flag = abort_tune(obj) + %Stops optimization of wavelength tuning. + flag = obj.set_command(obj.StopOptimization); + end + + function piezo = get_ref_cavity_percent(obj) + % returns reference cavity piezo percent value + piezo_voltage = obj.get_intvalue(obj.RefCavity_Piezo); + piezo = piezo_voltage/obj.Piezo_maxBit; + end + + function piezo = get_opo_cavity_percent(obj) + % returns reference cavity piezo percent value + piezo_voltage = obj.get_intvalue(obj.RegOpo_Out); + piezo = piezo_voltage/obj.Piezo_maxBit; + end + + function tune_ref_cavity(obj,piezo_percent) + %Piezo voltage is passed a a percentage of the total range + %Total range is 0-65535 + %Convert from percentage to integer + piezo_voltage = round(piezo_percent*obj.Piezo_maxBit); + obj.set_intvalue(obj.RefCavity_Piezo,piezo_voltage); + end + + function tune_thick_etalon(obj,relative_wl_pm) + %execute relative wavelength step of thick etalon in pm. + %Total range is -1000 to 1000 pm (type int) + obj.set_intvalue(obj.ThickEtalon_Piezo_hr,relative_wl_pm); + end + + function optimize_shg(obj) + obj.set_command(obj.OptimizeSHG); + end + + function relock_etalon(obj) + obj.set_command(obj.RelockEtalon); + end + + function tune_opo_cavity(obj,val) + %Use for wider fsr tuning range ~ 20-40 GHz in the visible + %Piezo voltage is passed a a percentage of the total range + %Total range is 0-65535 + %Convert from percentage to integer + + piezo_voltage = round(val*obj.Piezo_maxBit); + obj.set_intvalue(obj.RegOpo_Out,piezo_voltage); + %assert(flag == 0, 'OPO cavity not tuned') + end + + function set_regopo(obj,val) + %val = OPO regulator mode. + %0: off + %1: scan + %2: regulate + %3: user ramp + %4: manual setpoint (by regopo_out) + obj.set_intvalue(obj.RegOpo_On,val) + end + + function mode = get_regopo(obj) + %read opo piezo votlage bit. + mode = obj.get_intvalue(obj.RegOpo_On); + end + + function status = set_regopo_extramp(obj, duration, mode, lowerLimit, upperLimit) + % Description: Configures the waveform to be output to the OPO?s + % thick etalon when control mode is set to ?user ramp? (regopo_on=3). + % Arguments: duration is the waveform's duration in milliseconds + % mode is the waveform's mode (0=sawtooth, 1=triangle) + % lowerLimit/upperLimit are the waveform?s start and endpoint in percent of full scale output + % Returns: Returns 0 if the new command was executed correctly. Returns 1 if an error occurred. + % DLL bugs: minimum ramp is 10%. Maximum ramp duration is 60000 ms. + status = obj.LibraryFunction(obj.SetRefOpoExtramp, duration, mode, lowerLimit, upperLimit); + end + + function opo_ramp_piezo(obj) + %ramp piezo with sawtooth or triangle waveform + obj.set_regopo(3); + end + + function setWLM_gains(obj,p,i) + obj.set_intvalue(obj.WLM_pid_p,p) + obj.set_intvalue(obj.WLM_pid_i,i) + end + + + + + end + + %Methods I will likey never use + methods + function initialize_shutters(obj) + %Open or close all shutters + %Our CWAVE only has internal pump laser and SHG shutters + if strcmp(obj.all_shutters,obj.Open) + ret_lsr = set_intvalue(obj.ShutterLaser,1); + assert(ret_lsr == 1, 'Opening pump laser shutter failed'); + ret_shg = set_intvalue(obj.ShutterSHG,1); + assert(ret_shg == 1, 'Opening shg shutter failed'); + elseif strcmp(obj.all_shutters,obj.Close) + ret_lsr = set_intvalue(obj.ShutterLaser,0); + assert(ret_lsr == 1, 'Closing pump laser shutter failed'); + ret_shg = set_intvalue(obj.ShutterSHG,0); + assert(ret_shg == 1, 'Closing shg shutter failed'); + end + end + + function [status] = ext_set_command(cmd) + % Description: Sends a command to the external module. + % Arguments: cmd is the command as string. See the documentation of the external module for valid commands. + % Returns: Returns 1 if the new command was executed correctly. Returns -1 if an error occurred. + %% external commands for WS8-10 High Finesse wavemeter + %% set command for external module (i.e. the wavemeter) + status = obj.LibraryFunction(obj.Ext_SetCommand,cmd); + end + + function [ext_intvalue] = ext_get_intvalue(cmd) + % Description: Reads the value of an integer parameter from the external module. + % Arguments: Parameter as string. See documentation of external module for valid parameters. + % Returns: Returns the requested integer value. + %% Table of allowing integer paramters for high finesse WS8-10 wavemeter + ext_intvalue = calllib(obj.LibraryName, obj.ExtGet_IntValue,cmd); + end + + function [ext_floatvalue] = ext_get_floatvalue(cmd) + % Description: Reads the value of an floating point parameter from the external module. + % Arguments: Parameter as string. See documentation of external module for valid parameters. + % Returns: Returns the requested floating point value. + %% Table of allowable float parameters for High Finesse WS8-10 wavemeter + ext_floatvalue = calllib(obj.LibraryName, obj.ExtGet_FloatValue, cmd); + end + + function [laser_status] = get_status_laser(obj) + % Description: Reads the current status of the pump laser. + % Arguments: none + % Returns: Returns 0 (1 before inversion) if the pump laser is active and the laser shutter is open. + % Returns 1 (0 before inversion) if no or not sufficient laser power is available. + laser_status = obj.LibraryFunction(obj.LaserStatus); + end + + function [ref_temp_status] = get_status_temp_ref(obj) + % Description: Reads the current status of the reference temperature. + % Arguments: none + % Returns: Returns 0 if the reference temperature is stable. + % Returns 1 if the reference temperature is not at setpoint. + %% Poll temperature status + ref_temp_status = obj.LibraryFunction(obj.Reference_TempStatus); + end + + function [opo_temp_status] = get_status_temp_opo(obj) + % Description: Reads the current status of the OPO temperature. + % Arguments: none + % Returns: Returns 0 if the OPO temperature is stable. + % Returns 1 if the OPO temperature is not at setpoint. + %% Poll OPO Temperature Status + opo_temp_status = obj.LibraryFunction(obj.OPO_TempStatus); + end + + function [shg_temp_status] = get_status_temp_shg(obj) + % Description: Reads the current status of the SHG temperature. + % Arguments: none + % Returns: Returns 0 if the SHG temperature is stable. + % Returns 1 if the SHG temperature is not at setpoint. + %% Poll SHG temperature Status + shg_temp_status = obj.LibraryFunction(obj.SHG_TempStatus); + end + + function [opo_lock_status] = get_status_lock_opo(obj) + % Description: Reads the current status of the OPO lock. + % Arguments: none + % Returns: Returns 0 if the OPO is locked to the reference cavity and + % produces stable output. Returns 1 if optimization is still in progress. + %% Poll opo_lock_status...What exactly is the OPO lock? + opo_lock_status = obj.LibraryFunction(obj.OPO_LockStatus); + end + + function [shg_lock_status] = get_status_lock_shg(obj) + % Description: Reads the current status of the SHG lock. + % Arguments: none + % Returns: Returns 0 if the SHG cavity is locked and produces stable output. + % Returns 1 if optimization is still in progress. + shg_lock_status = obj.LibraryFunction(obj.SHG_LockStatus); + end + + function [etalon_lock_status] = get_status_lock_etalon(obj) + % Description: Reads the current status of the etalon lock. + % Arguments: none + % Returns: Returns 0 if the etalon is locked. Returns 1 if optimization is still in progress. + etalon_lock_status = obj.LibraryFunction(obj.Etalon_LockStatus); + end + end + +end + + diff --git a/Modules/+Sources/CWave.m b/Modules/+Sources/CWave.m new file mode 100644 index 000000000..af999d59c --- /dev/null +++ b/Modules/+Sources/CWave.m @@ -0,0 +1,1544 @@ +classdef CWave < Modules.Source & Sources.TunableLaser_invisible + %Cwave controls all aspects of the cwave laser which powers AOM + % and the PulseStreamer which triggers AOM + % + % Wavemeter used for tuning and scanning of laser + % + % The cwave is continuously operated and used to control + % an AOM whose on/off state is controlled by the + % PulseStreamer. + % + % The laser tuning is controlled by the methods required by the + % TunableLaser_invisible superclass. + + properties(SetObservable,SetAccess=private) + source_on = false; + end + properties(Constant,Hidden) + no_server = 'No Server'; % Message when not connected + OPOwindupGuardmax= 2; %1.5 + OPOwindupGuardmin= -2; %-1.5 + EtaWindupGuardmax = 10000; %was 100 on 11/6/19 + EtaWindupGuardmin = -10000; % was -100 on 11/6/19 + MaxPercent = 99.5; % percent + MinPercent = 0.5; % percent + MaxThickEtalonRange = 0.375; %nm + pWLM_gain = 10; %10; + iWLM_gain = 75 ; %75; + EtalonStepperDelay = 0.1; % ms + EtalonMeasureDelay = 1.5; %seconds + wmExposureTolerance = 0.99; %was 0.05 % percent/100 + powerStatusDelay = 1; % seconds + timeoutAllElements = 300; + timeoutSHG = 95; % seconds + timeoutThickEtalon = 300; %seconds + wmPower_min = 0; %units? + end + properties(Hidden) + MaxEtalon = 15; % in pm %maybe step these down to 15 look a little unstable when trying to ajdust thick etalon manually with 0.025nm steps + MinEtalon = -10;% in pm %maybe step this down to 15 + LocalMaxEtalon = 6; + LocalMinEtalon = -6; + windupGuardmax = 0; + windupGuardmin = 0; + sOPO_power; + sSHG_power; + sPump_power; + ThickEtalonTolerance = 0.005; %nm + midTuneTolerance = 0.1; + end + properties(SetAccess=protected) + range = [Sources.TunableLaser_invisible.c./[450, 650],Sources.TunableLaser_invisible.c./[900, 1300]]; + end + properties + prefs = {'tuning','enabled','target_wavelength','wavelength_lock','etalon_lock',... + 'opo_stepper_lock','opo_temp_lock','shg_stepper_lock', 'shg_temp_lock','thin_etalon_lock',... + 'opo_lock','shg_lock','pump_emission','ref_temp_lock','resonator_percent',... + 'etalon_percent','PBline','pulseStreamer_ip','cwave_ip','wavemeter_ip',... + 'resonator_tune_speed','tunePercentRange','MaxEtalon_wl','MinEtalon_wl',... + 'EtalonStep','MidEtalon_wl','OPO_power','SHG_power','Pump_power','AllElementStep',... + 'TempRef','TempOPO', 'TempSHG','TempRef_setpoint','TempOPO_setpoint', 'TempSHG_setpoint','TempBase','TempFPGA'}; %,'TempBase','TempFPGA' + show_prefs = {'TempBase','TempFPGA','target_wavelength','resonator_percent','AllElementStep','EtalonStep','tunePercentRange',... + 'resonator_tune_speed','MidEtalon_wl','MaxEtalon_wl','MinEtalon_wl','tuning',... + 'enabled','wavelength_lock','etalon_lock','opo_stepper_lock','opo_temp_lock','shg_stepper_lock',... + 'shg_temp_lock','thin_etalon_lock','opo_lock','shg_lock','pump_emission','ref_temp_lock',... + 'etalon_percent','OPO_power','SHG_power','Pump_power','TempOPO','TempOPO_setpoint',... + 'TempSHG','TempSHG_setpoint','TempRef','TempRef_setpoint','PBline',... + 'pulseStreamer_ip','cwave_ip','wavemeter_ip'}; % 'TempBase','TempFPGA', + readonly_prefs = {'tuning','etalon_lock','opo_stepper_lock','opo_temp_lock',... + 'shg_stepper_lock', 'shg_temp_lock','thin_etalon_lock','opo_lock','shg_lock',... + 'pump_emission','ref_temp_lock','MidEtalon_wl','resonator_percent', ... + 'OPO_power','SHG_power','Pump_power','TempRef','TempOPO', 'TempSHG',... + 'TempRef_setpoint','TempOPO_setpoint', 'TempSHG_setpoint','TempBase','TempFPGA'}; % ,'TempBase','TempFPGA' + end + properties(SetObservable,GetObservable) + MidEtalon_wl = ''; + enabled = Prefs.Boolean(); + resonator_percent = Prefs.Double(0.00,'units','percent','min',0.00,'max',100.00); + tuning = Prefs.Boolean(false, 'help_text','This is a readonly string.','readonly',true); +% cwave_ip = Prefs.String('default',Sources.CWave.no_server,'allow_empty',true,'set',''); +% pulseStreamer_ip = Prefs.String(Sources.CWave.no_server,'allow_empty',false),'set',''; +% wavemeter_ip = Prefs.String(Sources.CWave.no_server,'allow_empty',false),'set',''; + PBline = Prefs.Integer('min',0,'help_text','indexed from 0'); % Index from 0 (Pulsestreamer has 8 digital out channels) + resonator_tune_speed = Prefs.Double(0.5,'units','percent','min',0.001,'max',1); % percent per step + etalon_lock = Prefs.Boolean('help_text','This is a readonly string.','readonly',true); + etalon_percent = Prefs.Boolean( 'help_text','This is a readonly string.','readonly',true); + opo_stepper_lock = Prefs.Boolean( 'help_text','This is a readonly string.','readonly',true); + opo_temp_lock = Prefs.Boolean( 'help_text','This is a readonly string.','readonly',true); + shg_stepper_lock = Prefs.Boolean( 'help_text','This is a readonly string.','readonly',true); + shg_temp_lock = Prefs.Boolean( 'help_text','This is a readonly string.','readonly',true); + thin_etalon_lock = Prefs.Boolean( 'help_text','This is a readonly string.','readonly',true); + opo_lock = Prefs.Boolean( 'help_text','This is a readonly string.','readonly',true); + shg_lock = Prefs.Boolean( 'help_text','This is a readonly string.','readonly',true); + pump_emission = Prefs.Boolean( 'help_text','This is a readonly string.','readonly',true); + ref_temp_lock = Prefs.Boolean( 'help_text','This is a readonly string.','readonly',true); + wavelength_lock = Prefs.Boolean(); + OPO_power = Prefs.Double(0.0,'units','mW','min',0,'max',5000); + SHG_power = Prefs.Double(0.0,'units','mW','min',0,'max',5000); + Pump_power = Prefs.Double(0.0,'units','mW','min',0,'max',5000); + TempBase = Prefs.Double(20.00,'units','Celcius','min',20,'max',170.00); + TempFPGA = Prefs.Double(20.00,'units','Celcius','min',20,'max',170.00); + TempRef = Prefs.Double(20.00,'units','Celcius','min',20,'max',170.00); + TempOPO = Prefs.Double(20.00,'units','Celcius','min',20,'max',170.00); + TempSHG = Prefs.Double(20.00,'units','Celcius','min',20,'max',170.00); + TempRef_setpoint = Prefs.Double(20.00,'units','Celcius','min',20,'max',170.00); + TempOPO_setpoint = Prefs.Double(20.00,'units','Celcius','min',20,'max',170.00); + TempSHG_setpoint = Prefs.Double(20.00,'units','Celcius','min',20,'max',170.00); + %target_wavelength = Prefs.Double('default',NaN,'units','nm','min',450,'max',1300,'set','set_target_wavelength'); + cwave_ip = Sources.CWave.no_server; + pulseStreamer_ip = Sources.CWave.no_server; + wavemeter_ip = Sources.CWave.no_server; + target_wavelength=''; + tunePercentRange = ''; %tunePercentRange = Prefs.DoubleArray(); + EtalonStep=''; + AllElementStep = ''; + MinEtalon_wl = '0'; + MaxEtalon_wl = '.25'; + end + + properties(SetAccess=private) + PulseStreamerHandle %hardware handle + wavemeterHandle + cwaveHandle + end + + methods(Access=private) + function obj = CWave() + obj.loadPrefs; + % while loop with poling to cehck that all drivers are loaded. + % if they are then run updateStatus and set initial conditions + % such as wavemeter continuous model alternatively set these to + % directly in the set.ip methods. + %obj.updateStatus; %updateStatus before cwave library loaded. + %can have it here. + end + + function err = connect_driver(obj,propname,drivername,varargin) + err = []; + if ~isempty(obj.(propname)) + delete(obj.(propname)); %remove any old connection + end + if ischar(varargin{1}) && strcmpi(varargin{1},'No Server') %first input is always an ip address + obj.(propname) = []; + else + try + obj.(propname) = Drivers.(drivername).instance(varargin{:}); + catch err + obj.(propname) = []; + end + end + end + end + + methods(Static) + function obj = instance() + mlock; + persistent Object + if isempty(Object) || ~isvalid(Object) + Object = Sources.CWave(); + end + obj = Object; + end + end + + methods + + % source methods + + function on(obj) + assert(~isempty(obj.PulseStreamerHandle), 'No IP set for PulseStreamer!') + state = PulseStreamer.OutputState([obj.PBline],0,0); + obj.PulseStreamerHandle.PS.constant(state); + obj.source_on = true; + + end + function off(obj) + assert(~isempty(obj.PulseStreamerHandle), 'No IP set for PulseStreamer!') + obj.source_on = false; + state = PulseStreamer.OutputState([],0,0); + obj.PulseStreamerHandle.PS.constant(state); + end + function arm(obj) + obj.enabled = true; + end + function blackout(obj) + obj.off() + %possibly add code to depower switch (assuming it can be + %powered by nidaq) + end + + % tunable laser methods + function tune(obj, setpoint,target_dev,coarse,lock) + if (obj.wavemeterHandle.getResultMode ~= 0) + obj.wavemeterHandle.setResultMode(0); + % initialize into WavelengthVac mode. + % 'cReturnWavelengthVac' = 0 + end + % target in nm + obj.tuning = true; + if setpoint < 899 + assert(setpoint>=obj.c/max(obj.range(1:2))&&setpoint<=obj.c/min(obj.range(1:2)),... + sprintf('Wavelength must be in range [%g, %g] nm!!',obj.c./obj.range(1:2))) + elseif setpoint >= 899 + assert(target>=obj.c/max(obj.range(3:4))&&target<=obj.c/min(obj.range(3:4)),... + sprintf('Wavelength must be in range [%g, %g] nm!!',obj.c./obj.range(3:4))) + end + assert(~isempty(obj.cwaveHandle), 'no cwave handle') + err = []; + % The CWave is slow at tuning, so message is useful until a non-blocking operation exists + dlg = msgbox('Please wait while CWave tunes to target wavelength.',mfilename,'modal'); + textH = findall(dlg,'tag','MessageBox'); + delete(findall(dlg,'tag','OKButton')); + drawnow; + obj.tuning = true; + + try + obj.updateStatus; + if (coarse == true) + if (obj.sOPO_power | obj.sSHG_power | obj.sPump_power | obj.wavemeterHandle.getExposure == 2000) + detune = 2*obj.MaxThickEtalonRange; + disp(detune) + obj.wavemeterHandle.setExposureMode(false); %Manually set exposure + obj.wavemeterHandle.setExposure(1); %set to 1ms + else + detune = abs(setpoint-obj.getWavelength); + end + + if (detune > abs(obj.MaxEtalon_wl-obj.MinEtalon_wl)) | (detune > obj.MaxThickEtalonRange ) + % need to add warning option allowsing user to exit + % this is a very costly operation + dlg = questdlg('Target wavelength exceeds tuning etalon and OPO range (> 0.2 nm)! Tuning will be very slow. Do you wish to continue with tuning?', ... + 'Tuning Warning', ... + 'Continue Tuning','Abort','Abort'); + % Handle response + switch dlg + case 'Continue Tuning' + obj.cwaveHandle.target_wavelength = setpoint + 1; + obj.cwaveHandle.set_target_wavelength(); + pause(10); + obj.cwaveHandle.target_wavelength = setpoint; + obj.cwaveHandle.set_target_wavelength(); + isCoarseTuned = false; + while( isCoarseTuned == false) + abort_tune = obj.is_cwaveReady(1); + isCoarseTuned = obj.locked; + if (isCoarseTuned == true) + return; + elseif (abort_tune == true) + + return; + end + end + %check if user has selected to exit + %tuning + if (abort_tune) + delete(dlg) + return + end + obj.wavemeterHandle.setExposureMode(true); %set exposure mode to automatic + obj.updateStatus; + + if ( obj.locked == true & abs(setpoint-obj.getWavelength) > obj.midTuneTolerance ) + direction = sign(setpoint-obj.getWavelength); + while abs(setpoint-obj.getWavelength) > obj.midTuneTolerance % = 0.1 nm + control = direction*abs(setpoint-obj.getWavelength); + if abs(control) < 2.5*obj.OPOrLambdaFloor + control = direction*(obj.OPOrLambdaFloor); + end + obj.cwaveHandle.set_OPOrLambda(obj,floor(control)); + abort_tune = obj.is_cwaveReady(1,true); + %check if user has selected to exit + %tuning + if (abort_tune ) + break + end + + obj.updateStatus; + tic + while (( obj.SHG_power < obj.cwaveHandle.SHG_MinPower) ) % | powerSHG_status == false) + pause(1) + abort = obj.is_cwaveReady(1,false); + %check if user has selected to exit + %tuning + if (abort_tune ) + break + end + obj.updateStatus; + %[currSHG_power, powerSHG_status] = obj.cwaveHandle.get_photodiode_shg; + timer = toc; + if timer > obj.timeoutSHG + abort_tune = true; + break + end + end + end + %check if user has selected to exit + %tuning + if (abort_tune ) + delete(dlg) + %maybe I should have a error + %statement here + return + end + elseif (obj.locked == false) + error('Coarse tuning failed...Are you banging the table you fool. It is an OPO!') + elseif (abs(setpoint-obj.getWavelength) > obj.midTuneTolerance) + regopo4_locked = obj.etalon_lock & obj.opo_stepper_lock & obj.opo_temp_lock... + & obj.shg_stepper_lock & obj.shg_temp_lock & obj.thin_etalon_lock & obj.pump_emission; + regOPO4_cond = regopo4_locked == true & obj.cwaveHandle.get_regopo == 4 & ... + abs(setpoint-obj.getWavelength) > obj.midTuneTolerance; + regOPO2_cond = obj.locked == true & obj.cwaveHandle.get_regopo == 2 & ... + abs(setpoint-obj.getWavelength) > obj.midTuneTolerance; + + obj.updateStatus; + if (regOPO2_cond | regOPO4_cond) + direction = sign(setpoint-obj.getWavelength); + while abs(setpoint-obj.getWavelength) > obj.midTuneTolerance % = 0.1 nm + control = direction*abs(setpoint-obj.getWavelength); + if abs(control) < 2.5*obj.OPOrLambdaFloor + control = direction*(obj.OPOrLambdaFloor); + end + obj.cwaveHandle.set_OPOrLambda(obj,floor(control)); + abort_tune = obj.is_cwaveReady(1,true); + %check if user has selected to exit + %tuning + if (abort_tune ) + break + end + obj.updateStatus; + tic + while (( obj.SHG_power < obj.cwaveHandle.SHG_MinPower) ) % | powerSHG_status == false) + pause(1) + abort = obj.is_cwaveReady(1,false); + %check if user has selected to exit + %tuning + if (abort_tune ) + break + end + obj.updateStatus; + timer = toc; + if timer > 2*obj.timeoutSHG + abort_tune = true; + break + end + end + end + %check if user has selected to exit + %tuning + if (abort_tune ) + delete(dlg) + %maybe I should have a error + %statement here + return + end + else + error('Coarse tuning failed...Are you banging the table you fool. It is an OPO!') + end + elseif ( obj.locked == true & abs(setpoint-obj.getWavelength) < obj.midTuneTolerance & abs(setpoint-obj.getWavelength) > obj.ThickEtalonTolerance ) + abort = obj.centerThickEtalon; + if (abort ) + delete(dlg) + return + end + else + if (lock == true) + while ( obj.cwaveHandle.get_regopo() ~= 2) + obj.cwaveHandle.set_regopo(2); + pause(0.1); + end + if (obj.cwaveHandle.get_ref_cavity_percent ~= 50) + obj.TuneRefPercent(50.0); + end + obj.etalon_pid(setpoint,obj.ThickEtalonTolerance); + %obj.etalon_pid(setpoint); + for i=1:2 + obj.WLM_tune(setpoint,target_dev) + end + elseif (lock == false) + while ( obj.cwaveHandle.get_regopo() ~= 4) + obj.cwaveHandle.set_regopo(4); + pause(0.1); + end + % error tuning occure 11/08/19 + % somewhere between line 378-385 + if (obj.GetPercent() ~= 50) + obj.TunePercent(50); + end + obj.etalon_pid(setpoint); + for i=1:2 + obj.opo_pid(setpoint,target_dev); + end + end + obj.updateStatus; + end + case 'Abort' + return + end + elseif abs(setpoint-obj.getWavelength) < abs(obj.MaxEtalon_wl-obj.MinEtalon_wl) + abort = obj.centerThickEtalon; + if (abort ) + delete(dlg) + return + end + + if (lock == true) + while ( obj.cwaveHandle.get_regopo() ~= 2) + obj.cwaveHandle.set_regopo(2); + end + if (obj.cwaveHandle.get_ref_cavity_percent ~= 50) + obj.TuneRefPercent(50.0); + end + obj.etalon_pid(setpoint,obj.ThickEtalonTolerance); + %obj.etalon_pid(setpoint); + for i=1:2 + obj.WLM_tune(setpoint,target_dev) + end + elseif (lock == false) + while ( obj.cwaveHandle.get_regopo() ~= 4) + obj.cwaveHandle.set_regopo(4); + pause(1); + disp('regopo'); + obj.cwaveHandle.get_regopo + disp('end iteration') + end + if (obj.GetPercent() ~= 50) + obj.TunePercent(50); + end + obj.etalon_pid(setpoint); + for i=1:2 + obj.opo_pid(setpoint,target_dev); + end + end + obj.updateStatus; + end + + elseif coarse == false + if (lock == true) + if (obj.cwaveHandle.get_ref_cavity_percent ~= 50) + obj.TuneRefPercent(50.0); + end + obj.cwaveHandle.set_regopo(2); + for i=1:2 + obj.WLM_tune(setpoint,target_dev) + end + else + if (obj.GetPercent() ~= 50) + obj.TunePercent(50); + end + %obj.set_regopo(4); + %obj.cwaveHandle.set_intvalue(obj.cwaveHandle.RegOpo_On,4); + obj.cwaveHandle.set_regopo(4); + for i=1:2 + obj.opo_pid(setpoint,target_dev); + end + end + obj.updateStatus; + end + obj.tuning = false; + catch err + end + delete(dlg) + obj.tuning = false; + if ~isempty(err) + obj.locked = false; + obj.wavelength_lock = false; + obj.setpoint = NaN; + rethrow(err) + elseif (lock) + obj.wavelength_lock = true; + end + obj.setpoint = setpoint; + end + + function WLM_tune(obj,setpoint,target_dev) + switch nargin + case 1 + target_dev = 0.000001; + end + obj.cwaveHandle.fine_tune(); + obj.cwaveHandle.setWLM_gains(obj.pWLM_gain,obj.iWLM_gain); + obj.cwaveHandle.set_target_deviation(target_dev); + obj.cwaveHandle.set_pid_target_wavelength(setpoint); + measured_wavelength = obj.wavemeterHandle.getWavelength(); + while abs(measured_wavelength - setpoint) > target_dev + obj.cwaveHandle.WLM_PID_Compute(measured_wavelength); + if obj.wavemeterHandle.getExposure() > 100 + delay = obj.wavemeterHandle.getExposure()/1000; %in seconds + pause(delay) + else + pause(0.0001) %(was 0.001 s) + end + measured_wavelength = obj.wavemeterHandle.getWavelength(); + end + end + + + function TuneSetpoint(obj,setpoint) + %TuneSetpoint Sets the wavemeter setpoint + % setpoint = setpoint in THz + %obj.cwaveHandle.fine_tune(); + %cwave.cwaveHandle.setWLM_gains(20,150); + target_dev = 0.000001; + isLocked = true; + isCoarse = false; + user_speed = obj.resonator_tune_speed; + obj.resonator_tune_speed = 0.1; + obj.tune(obj.c/setpoint,target_dev,isCoarse, isLocked); + obj.resonator_tune_speed = user_speed; +% parfor i = 1:500 +% obj.tune(setpoint,target_dev,isCoarse); +% end + end + + function TuneCoarse(obj, setpoint) + %TuneCoarse moves the laser to the target frequency (THz) + % + % It assumes the laser is already close enough to not + % require changing of the OPO temperature to reach the target. + % + % First it achieves accuracy to within a picometer by + % changing the thick etalon piezo, then adjusts with + % the cavity piezo. + % + % setpoint = setpoint in nm + %obj.cwaveHandle.setWLM_gains(20,150); + %obj.cwaveHandle.coarse_tune(); + + dlgs = questdlg('Tune with OPO Cavity (Open Loop) or Reference Cavity (Closed Loop)?', ... + 'Tuning Warning', ... + 'OPO Cavity','Reference Cavity','Reference Cavity'); + % Handle response + switch dlgs + case 'OPO Cavity' + isLocked = false; + obj.MaxEtalon = 50; %25; %25; + obj.MinEtalon = -50; %-1; %-25; + case 'Reference Cavity' + isLocked = true; + obj.MaxEtalon = 15; + obj.MinEtalon = -10; + end + target_dev = 0.000001; + isCoarse = true; + user_speed = obj.resonator_tune_speed; + obj.resonator_tune_speed = 0.1; + obj.tune( obj.c/setpoint,target_dev,isCoarse,isLocked); + obj.resonator_tune_speed = user_speed; + %include error checks for power and clamping + end + + function wl = TunePercent(obj, target) + %TunePercent sets the resonator or the opo cavity piezo percentage + %ref cavity has fsr = 10GHz, opo cavity has fsr = 40 GHz + % For both cavties spectral drift for ~10 MHz steps is about 5-7 MHz + % + % percent = desired piezo percentage from 1 to 100 (float type) + %This is the OPO resonator + assert(~isempty(obj.cwaveHandle)&&isobject(obj.cwaveHandle) && isvalid(obj.cwaveHandle),'no cwave handle') + assert(target>=0 && target<=100,'Target must be a percentage') + %set opo cavity to tuning mode + if (obj.cwaveHandle.get_regopo() ~= 4) + %obj.cwaveHandle.set_intvalue(obj.cwaveHandle.RegOpo_On,4) + obj.cwaveHandle.set_regopo(4); + end + % tune at a limited rate per step + currentPercent = obj.GetPercent; + numberSteps = floor(abs(currentPercent-target)/obj.resonator_tune_speed); + direction = sign(target-currentPercent); + for i = 1:numberSteps + %tstart = tic; + obj.cwaveHandle.tune_opo_cavity(currentPercent+(i)*direction*obj.resonator_tune_speed); + wl(i) = obj.wavemeterHandle.getWavelength; + %telapsed(i+1) = toc(tstart); + end + obj.cwaveHandle.tune_opo_cavity(target); + obj.resonator_percent = obj.GetPercent(); + obj.updateStatus(); % Get voltage of resonator + end + + function wl = TuneRefPercent(obj, target) + %TunePercent sets the resonator or the opo cavity piezo percentage + %ref cavity has fsr = 10GHz, opo cavity has fsr = 40 GHz + % For both cavties spectral drift for ~10 MHz steps is about 5-7 MHz + % + % percent = desired piezo percentage from 1 to 100 (float type) + %This is the OPO resonator + assert(~isempty(obj.cwaveHandle)&&isobject(obj.cwaveHandle) && isvalid(obj.cwaveHandle),'no cwave handle') + assert(target>=0 && target<=100,'Target must be a percentage') + %set opo cavity to tuning mode + if (obj.cwaveHandle.get_regopo() ~= 2) + %obj.cwaveHandle.set_intvalue(obj.cwaveHandle.RegOpo_On,4) + obj.cwaveHandle.set_regopo(2); + end + % tune at a limited rate per step + currentPercent = obj.GetPercent; + numberSteps = floor(abs(currentPercent-target)/obj.resonator_tune_speed); + direction = sign(target-currentPercent); + for i = 1:numberSteps + %tstart = tic; + obj.cwaveHandle.tune_ref_cavity(currentPercent+(i)*direction*obj.resonator_tune_speed); + wl(i) = obj.wavemeterHandle.getWavelength; + %telapsed(i+1) = toc(tstart); + end + obj.cwaveHandle.tune_ref_cavity(target); + obj.resonator_percent = obj.GetPercent(); + obj.updateStatus(); % Get voltage of resonator + end + + function updateStatus(obj) + % Get status report from laser and update a few fields + tic; + obj.TempBase = obj.cwaveHandle.get_tBase; + obj.TempFPGA = obj.cwaveHandle.get_tFPGA; + obj.TempRef = obj.cwaveHandle.get_tref; + obj.TempOPO = obj.cwaveHandle.get_topo; + obj.TempSHG = obj.cwaveHandle.get_tshg; + obj.TempRef_setpoint = obj.cwaveHandle.get_tref_sp; + obj.TempOPO_setpoint = obj.cwaveHandle.get_topo_sp; + obj.TempSHG_setpoint = obj.cwaveHandle.get_tshg_sp; + obj.locked = ~(obj.cwaveHandle.get_statusbits); + obj.etalon_lock = ~obj.cwaveHandle.etalon_lock_stat; + obj.opo_stepper_lock = ~obj.cwaveHandle.opo_stepper_stat; + obj.opo_temp_lock = ~obj.cwaveHandle.opo_temp_stat; + obj.shg_stepper_lock = ~obj.cwaveHandle.shg_stepper_stat; + obj.shg_temp_lock = ~obj.cwaveHandle.shg_temp_stat; + obj.thin_etalon_lock = ~obj.cwaveHandle.thin_etalon_stat; + obj.opo_lock = ~obj.cwaveHandle.opo_lock_stat; + obj.shg_lock = ~obj.cwaveHandle.shg_lock_stat; + obj.pump_emission = ~obj.cwaveHandle.laser_emission_stat; + obj.ref_temp_lock = ~obj.cwaveHandle.ref_temp_stat; + %obj.ref_temp_lock = ~obj.cwaveHandle.get_status_temp_ref; + + [obj.OPO_power,obj.sOPO_power] = obj.cwaveHandle.get_photodiode_opo; + [obj.SHG_power,obj.sSHG_power] = obj.cwaveHandle.get_photodiode_shg; + [obj.Pump_power,obj.sPump_power] = obj.cwaveHandle.get_photodiode_laser; + + + regopo4_locked = obj.etalon_lock & obj.opo_stepper_lock & obj.opo_temp_lock... + & obj.shg_stepper_lock & obj.shg_temp_lock & obj.thin_etalon_lock & obj.pump_emission; + + if( (obj.locked ) | ( (obj.cwaveHandle.get_regopo == 4 ... + & regopo4_locked & obj.sSHG_power == false))) + obj.setpoint = obj.c/obj.getWavelength; % This sets wavelength_lock + else + obj.setpoint = NaN; + end + + obj.tuning = ~(obj.locked); + toc; + %fprintf('toc %.8f\n',toc); + %obj.setpoint = obj.cwaveHandle.WLM_PID_Setpoint; + % Overwrite getWavelength tuning status with EMM tuning state + end + + function piezo = GetPercent(obj) + piezo = obj.cwaveHandle.get_opo_cavity_percent(); + %piezo = obj.cwaveHandle.get_ref_cavity_percent(); + end + + function freq = getFrequency(obj) + wavelength = obj.wavemeterHandle.getWavelength(); + freq = Sources.TunableLaser_invisible.c/wavelength; + end + + function wavelength = getWavelength(obj) + wavelength = obj.wavemeterHandle.getWavelength(); + end + + + % set methods + + function set.cwave_ip(obj,ip) + err = obj.connect_driver('cwaveHandle', 'CWave', ip); + if ~isempty(err) + obj.cwave_ip = obj.no_server; + rethrow(err) + end + obj.cwave_ip = ip; + + while(1) + if libisloaded(obj.cwaveHandle.LibraryName) + obj.updateStatus; + break + end + pause(1); + end + end + + function set.pulseStreamer_ip(obj, ip) + err = obj.connect_driver('PulseStreamerHandle', 'PulseStreamerMaster.PulseStreamerMaster', ip); + if ~isempty(err) + obj.pulseStreamer_ip = obj.no_server; + rethrow(err) + end + obj.pulseStreamer_ip = ip; + end + + function set.wavemeter_ip(obj, ip) + err = obj.connect_driver('wavemeterHandle', 'Wavemeter1Ch', ip); + if ~isempty(err) + obj.wavemeter_ip = obj.no_server; + rethrow(err) + end + obj.wavemeter_ip = ip; + end + + function tf = internal_call(obj) + tf = false; % Assume false, verify that true later + st = dbstack(2); % Exclude this method, and its caller + if ~isempty(st) + caller_class = strsplit(st(1).name,'.'); + caller_class = caller_class{1}; + this_class = strsplit(class(obj),'.'); + this_class = this_class{end}; + tf = strcmp(this_class,caller_class); + end + end + + function set.target_wavelength(obj,val) + %edite 10/30/19 note sure why this is read only??? + %if isnan(val); obj.target_wavelength = val; return; end % Short circuit on NaN + %if obj.internal_call; obj.target_wavelength = val; return; + %else + obj.target_wavelength = eval(val); + %end + if strcmp(val,''); return; end + obj.TuneCoarse(obj.c/obj.target_wavelength); + end + + function exit = EtalonStepper(obj ,step, delay_val) + %step etalon + %direction = sign(step); + obj.cwaveHandle.tune_thick_etalon(step); + obj.is_cwaveReady(delay_val,false,false); %(no SHG reset, only update SHG power) + %pause(obj.EtalonStepperDelay); + exit = false; + %correct for excessive stick-slip motion....= + [obj.SHG_power, obj.sSHG_power] = obj.cwaveHandle.get_photodiode_shg; + %obj.updateStatus; + if (obj.SHG_power > obj.cwaveHandle.SHG_MinPower) + exit = true; + end + + end + + function [wm_wl,wm_power,wm_exptime] = powerStatus(obj, tol,delay_val) + i = 0; + max_interation = 10; + MaxExposuretime = 1500; + obj.wavemeterHandle.setExposureMode(false); % manually set exposure + obj.wavemeterHandle.setExposure(1) % set exposure to 1 ms + obj.wavemeterHandle.setExposureMode(true); %sert exposure mode auto + prev_expTime = obj.wavemeterHandle.getExposure(); + curr_expTime = 100000*prev_expTime; + while ( curr_expTime <= (1-tol)*prev_expTime | curr_expTime >= (1+tol)*prev_expTime ) + i = i+1; + %obj.updateStatus; + %pause(delay_val) + obj.is_cwaveReady(delay_val,false,false) %try is_cwaveReady instead of updateStatus and pause. SHould be faster + if i > max_interation + regopo4_locked = obj.etalon_lock & obj.opo_stepper_lock & obj.opo_temp_lock... + & obj.shg_stepper_lock & obj.shg_temp_lock & obj.thin_etalon_lock & obj.pump_emission; + + regopo4_noEta_locked = obj.opo_stepper_lock & obj.opo_temp_lock... + & obj.shg_stepper_lock & obj.shg_temp_lock & obj.thin_etalon_lock & obj.pump_emission; + + if (obj.cwaveHandle.get_regopo == 4) + if( regopo4_locked == false) + error('CWave is not locked for regopo4. Refer to lock status to determine failing elements. Currently in OPO regulator mode 4.'); + elseif (regopo4_noEta_locked == true & obj.etalon_lock == false) + error('Etalon is not locked. Currently in OPO regulator mode 4.'); + elseif (regopo4_locked == true & powerSHG_status == true) + dialog2 = msgbox('insufficient power from SHG.',mfilename,'modal'); + textH = findall(dlg,'tag','MessageBox'); + %delete(findall(dlg,'tag','OKButton')); + drawnow; + %delete( dialog2); + disp('insufficient power from SHG.'); + end + end + + if (obj.cwaveHandle.get_regopo == 2) + if (obj.regopo4_locked == true & obj.etalon_lock == true & obj.shg_lock == false ) + error('SHG cannot lock. Try retuning manually. Currently in OPO regulator mode 2.') + elseif (obj.regopo4_locked == true & obj.etalon_lock == false & obj.shg_lock == true ) + error('Etalon cannot lock. Try retuning manually. Currently in OPO regulator mode 2.') + elseif (obj.regopo4_locked == true & obj.etalon_lock == false & obj.shg_lock == false ) + error('Etalon and SHG cannot lock. Try retuning manually. Currently in OPO regulator mode 2.') + end + end + + if( obj.wavemeterHandle.setExposure >= MaxExposuretime) + error('Dim emission. Check that light is well coupled into wave meter') + else + error('Large fluctuations in CWave power.') + end + + elseif ( (1+tol)*prev_expTime >= curr_expTime & curr_expTime >= (1-tol)*prev_expTime ) + wm_exptime = obj.wavemeterHandle.getExposure(); + + if (powerSHG_status == false) + wm_wl = obj.getWavlength(); + wm_power = obj.wavemeterHandle.getPower(); + wm_exptime = obj.wavemeterHandle.getExposure(); + elseif (powerSHG_status == true) + wm_wl = NaN; + wm_power = NaN; + end + return; + end + end + end + + function abort = is_cwaveReady(obj,delay_val,SHG_tuning,allStatus) + switch nargin + case 1 + delay_val = 1; + SHG_tuning = false; + allStatus = true; + case 2 + SHG_tuning = false; + allStatus = true; + case 3 + allStatus = true; + end + + abort = false; + time = 0; + tic; + + while(obj.cwaveHandle.is_ready) + pause(delay_val); %in seconds + if allStatus == true + obj.updateStatus; + %[obj.SHG_power, obj.sSHG_power] = obj.cwaveHandle.get_photodiode_shg; + else + [obj.SHG_power, obj.sSHG_power] = obj.cwaveHandle.get_photodiode_shg; + end + time = time + toc; + if (time > obj.timeoutSHG & SHG_tuning == true & (obj.shg_lock == false | obj.shg_temp_lock == false | obj.sSHG_power == 0 ) ) + dialog = msgbox('Please wait while CWave re-optimizes SHG power.',mfilename,'modal'); + textH = findall(dialog,'tag','MessageBox'); + delete(findall(dialog,'tag','OKButton')); + drawnow; + obj.cwaveHandle.optimize_shg; + while(obj.cwaveHandle.is_ready) + pause(5) + end + delete(dialog); + elseif time > obj.timeoutAllElements + dialog = questdlg('Tuning Timed out. Continue or abort tuning?', ... + 'Cwave Not Ready', ... + 'Continue','Abort','Abort'); + % Handle response + switch dialog + case 'Continue' + tic; + case 'Abort' + obj.cwaveHandle.abort_tune; + abort = true; + return; + end + end + + if (obj.cwaveHandle.get_regopo == 4) + regopo4_locked = obj.etalon_lock & obj.opo_stepper_lock & obj.opo_temp_lock... + & obj.shg_stepper_lock & obj.shg_temp_lock & obj.thin_etalon_lock & obj.pump_emission; + if regopo4_locked == true & obj.sSHG_power == 0 + break; + end + elseif obj.cwaveHandle.getregopo == 2 & obj.locked == true + break; + else + continue; + end + end + end + + function [wm_lambda_c,wmPower_c,wm_exptime_c, abort, exit] = reset_hysteresis(obj,pstep) + i =1; + %total_step = 0; + obj.updateStatus; + while(obj.SHG_power < obj.cwaveHandle.SHG_MinPower) + %obj.is_cwaveReady(obj.EtalonStepperDelay,false,false); + %obj.updateStatus; %updatStatus is slow ~0.1-0.5 s long may need to replace with [obj.SHG_power,~] = obj.cwaveHandle.get_photodiode_shg + %direction = sign(EtalonTotalStep); + %correction_step = direction*pstep; + correction_step = pstep; + %[wm_lambda_i,wmPower_i,wm_exptime_i] = obj.powerStatus(obj.wmExposureTolerance, obj.powerStatusDelay); %updateStatus called internally + exiting = obj.EtalonStepper(correction_step, obj.EtalonMeasureDelay); % update Status called internally. + if exiting == true + exit = true; + end + %obj.EtalonStep = nstep; + abort = obj.is_cwaveReady(obj.EtalonStepperDelay,false); + pause(obj.EtalonStepperDelay) + obj.updateStatus; + + if i == 1 + correction_step = 15; + elseif i == 2 + correction_step = 20; + elseif i == 3 + correction_step = 25; + elseif i == 4 + correction_step = 50; + elseif i == 5 + correction_step = 75; + elseif i == 6 + correction_step = 100; + elseif i > 6 + correction_step = 100; + end + i = i+1; + if i >= 25 + error('Etalon hysteresis not reset'); + %return; + end + abort = obj.is_cwaveReady(obj.EtalonStepperDelay,false,false); + + %pause(delay_val); + %total_step = correction_step + total_step; + %if (currentSHG_Power >= obj.cwaveHandle.SHG_MinPower) + %wm_lambda_c = obj.getWavelength; + [wm_lambda_c,wmPower_c,wm_exptime_c] = obj.powerStatus(obj.wmExposureTolerance, obj.powerStatusDelay); + %elseif (currentSHG_Power < obj.cwaveHandle.SHG_MinPower) + % [wm_lambda_c,wmPower_c,wm_exptime_c,currPower] = obj.powerStatus(obj.wmExposureTolerance, obj.powerStatusDelay); + end + end + + function abort = centerThickEtalon(obj) + dlgs = questdlg('Is Min and Max range of Etalon correctly set? MaxEtalon_wl and MinEtalon_wl must be measured by autotune or manually by adjusting EtalonStep. If tuning manually MaxEtalon_wl and MinEtalon_wl in Cwave Source preferences', ... + 'Centering Etalon Warning', ... + 'Yes, Continue Tuning','No, Please Autotune', 'No, Abort','No, Abort'); + % Handle response + switch dlgs + case 'Yes, Continue Tuning' + + if abs(obj.MaxEtalon_wl-obj.MinEtalon_wl) > obj.MaxThickEtalonRange + dlgs2 = questdlg('Exiting Tuning. User Selected Etalon range exceeds thick etalon range. Difference bewteen MaxEtalon_wl and MinEtalon_wl should be less than 0.2 nm.', ... + 'Thick Etalon Range Warning', ... + 'Reselect MaxEtalon_wl and MinEtalon_wl','Reselect MaxEtalon_wl and MinEtalon_wl'); + abort = true; + return; + else + obj.MidEtalon_wl = obj.MinEtalon_wl + abs(obj.MaxEtalon_wl-obj.MinEtalon_wl)/2; + obj.etalon_pid(obj.MidEtalon_wl); + abort = false; + end + + case 'No, Please Autotune' + pstep = 25; + nstep = -25; + obj.updateStatus; + + PowerMeasureCondition_regopo2 = (obj.ref_temp_lock == true) & (obj.pump_emission == true) & ... + (obj.opo_lock == true) & (obj.thin_etalon_lock == true) & ... + (obj.shg_temp_lock == true) & (obj.shg_stepper_lock == true) & ... + (obj.opo_temp_lock == true) & (obj.opo_stepper_lock == true); + + PowerMeasureCondition_regopo4 = (obj.ref_temp_lock == true) & (obj.pump_emission == true) & ... + (obj.thin_etalon_lock == true) & (obj.shg_temp_lock == true) &... + (obj.shg_stepper_lock == true) & (obj.opo_temp_lock == true) & ... + (obj.opo_stepper_lock == true); + %(obj.etalon_lock == true) & (obj.shg_lock); + if (obj.cwaveHandle.get_regopo == 2 & PowerMeasureCondition_regopo2 == false) + obj.cwaveHandle.abort_tune; + error('Etalon not tunable. Check lock status of OPO elements.') + elseif (obj.cwaveHandle.get_regopo == 4 & PowerMeasureCondition_regopo4 == false) + obj.cwaveHandle.abort_tune; + error('Etalon not tunable. Check lock status of OPO elements.') + elseif (PowerMeasureCondition_regopo2 == true | PowerMeasureCondition_regopo4 | obj.locked) + [wm_lambda_i,wmPower_i,wm_exptime_i] = obj.powerStatus(obj.wmExposureTolerance, obj.powerStatusDelay); + if (obj.SHG_power < obj.cwaveHandle.SHG_MinPower) + dlgs1 = questdlg('SHG power is very low (SHG is not lock)! Would you like to reoptimize SHG power?', ... + 'Yes, optimize SHG Power','No, retune etalon', 'No, Abort','No, Abort'); + % Handle response + switch dlgs1 + case 'Yes, optimize SHG Power' + % The CWave is slow at tuning, so message is useful until a non-blocking operation exists + dlgs2 = msgbox('Please wait while CWave re-optimizes SHG power.',mfilename,'modal'); + textH = findall(dlg,'tag','MessageBox'); + delete(findall(dlg,'tag','OKButton')); + drawnow; + tic + obj.cwaveHandle.optimize_shg(); + while( (obj.SHG_power < obj.cwaveHandle.SHG_MinPower & obj.shg_lock == false & obj.etalon_lock == false) | toc <= obj.timeoutSHG) + %obj.updateStatus; + %updateStatus called + %in powerStatus + [wm_lambda_i,wmPower_i,wm_exptime_i] = obj.powerStatus(obj.wmExposureTolerance, obj.powerStatusDelay); + end + delete(dlgs2); + if(toc > obj.timeoutSHG) + obj.cwaveHandle.abort_tune; + error('Re-optimizing of SHG timed out. Tune Manually.') + end + case 'No, retune etalon' + % The CWave is slow at tuning, so message is useful until a non-blocking operation exists + dlgs4 = msgbox('Please wait while CWave relocks etalon.',mfilename,'modal'); + textH = findall(dlg,'tag','MessageBox'); + delete(findall(dlg,'tag','OKButton')); + drawnow; + tic + obj.cwaveHandle.relock_etalon(); + while( (obj.etalon_lock == false) | toc <= obj.timeoutThickEtalon) + obj.updateStatus; + [wm_lambda_i,wmPower_i,wm_exptime_i] = obj.powerStatus(obj.wmExposureTolerance, obj.powerStatusDelay); + end + delete(dlgs4); + if(toc > obj.timeoutThickEtalon) + obj.cwaveHandle.abort_tune; + error('Retuning of thick etalon timed out. Tune Manually') + end + if (obj.SHG_power < obj.cwaveHandle.SHG_MinPower | obj.shg_lock == false | obj.shg_temp_lock == false) + % The CWave is slow at tuning, so message is useful until a non-blocking operation exists + dlgs6 = msgbox('Please wait while CWave re-optimizes SHG power.',mfilename,'modal'); + textH = findall(dlg,'tag','MessageBox'); + delete(findall(dlg,'tag','OKButton')); + drawnow; + tic + obj.cwaveHandle.optimize_shg(); + while( (obj.SHG_power < obj.cwaveHandle.SHG_MinPower & obj.shg_lock == false & obj.etalon_lock == false) | toc <= obj.timeoutSHG) + obj.updateStatus; + [wm_lambda_i,wmPower_i,wm_exptime_i] = obj.powerStatus(obj.wmExposureTolerance, obj.powerStatusDelay); + end + delete(dlgs6); + if(toc > obj.obj.timeoutSHG) + obj.cwaveHandle.abort_tune; + error('Re-optimizing of SHG timed out. Tune Manually.') + end + end + case 'No, Abort' + abort = true; + return + end + + %prompt user to either manually + %tune first or to reset SHG + end + end + + j=0; + %EtalonTotalStep = 0; + obj.updateStatus; + wm_exptime_c = wm_exptime_i; + wm_exptime_c = wm_exptime_i; + wm_lambda_c = wm_lambda_i; + currPower = obj.SHG_power; + while ( ( currPower > obj.cwaveHandle.SHG_MinPower) ) + obj.updateStatus; %not sure that we need this. It would be useful to have but it slows things down. + [wm_lambda_i,wmPower_i,wm_exptime_i] = obj.powerStatus(obj.wmExposureTolerance, obj.powerStatusDelay); + exiting = obj.EtalonStepper(pstep, obj.EtalonMeasureDelay); + pause(obj.EtalonMeasureDelay) + + disp('Enter +eta loop') + fprintf('Control power: %.8f\n',currPower); + fprintf('Initial Power: %.8f\n',powerSHG_i); + fprintf('lock status (1 is locked): %.8f\n',obj.locked); + fprintf('initial exposure: %.8f\n',wm_exptime_i); + fprintf('current exposure: %.8f\n',wm_exptime_c); + + if (obj.SHG_power < obj.cwaveHandle.SHG_MinPower) + pause(1) + if (obj.SHG_power < obj.cwaveHandle.SHG_MinPower) + [wm_lambda_c,wmPower_c,wm_exptime_c,abort, exiting] = obj.reset_hysteresis(currPower); + end + + else + [wm_lambda_c,wmPower_c,wm_exptime_c] = obj.powerStatus(obj.wmExposureTolerance, obj.powerStatusDelay); + obj.MaxEtalon_wl = sprintf('%.80f',wm_lambda_c); + end + + del_wl = (wm_lambda_c-wl_lambda_i); % in nm + + if (obj.SHG_power > obj.cwaveHandle.SHG_MinPower) & (del_wl > 0) & exiting == false %not sure you need eixiting condition + currPower = obj.SHG_power; + continue; + elseif (obj.SHG_power > obj.cwaveHandle.SHG_MinPower) & (del_wl < 0) & exiting == false %not sure you need eixiting condition + currPower = obj.SHG_power; + continue; + elseif (obj.SHG_power < obj.cwaveHandle.SHG_MinPower) & (del_wl < 0) & exiting == false %not sure you need eixiting condition + currPower = obj.SHG_power; + continue; + elseif (obj.SHG_power < obj.cwaveHandle.SHG_MinPower) & (del_wl > 0) & exiting == false + currPower = 2*obj.cwaveHandle.SHG_MinPower; + %should force back to reset + %hysteris + continue; + elseif (obj.SHG_power < obj.cwaveHandle.SHG_MinPower) & (del_wl > 0) & exiting == true + currPower = 2*obj.cwaveHandle.SHG_MinPower; + %should force back to reset + %hysteris + continue; + elseif ( (obj.SHG_power > obj.cwaveHandle.SHG_MinPower) & exiting == true) + + if (del_wl < 0) + %maybe put obj.MaxEtalon_wl = + %sprintf('%.80f',wm_lambda_c); + %here + if wmPower_c < obj.wmPower_min + obj.cwaveHandle.abort_tune; + error('Re-optimizing of SHG timed out. Tune Manually.') + elseif (wmPower_c < wmPower_i/10 | wm_exptime_c > 10*wm_exptime_i) + dlgs9 = msgbox('Warning: Low Power reading from wavemeter. Check wavemeter coupling.',mfilename,'modal'); + textH = findall(dlg,'tag','MessageBox'); + delete(findall(dlg,'tag','OKButton')); + drawnow; + %delete( dlgs3); + else + obj.MinEtalon_wl = wm_lambda_c; + return; + end + elseif (del_wl > 0) + currPower = 2*obj.cwaveHandle.SHG_MinPower; %renter loop + obj.MaxEtalon_wl = 'NaN'; + j = j+1; + elseif j>2 + obj.cwaveHandle.abort_tune; + error('etalon is sticking. Reset etalon manually.') + %maybe replace error with + %dialog box to prompt user to + %adjust etalon mannually + end + end + end + obj.MidEtalon_wl = obj.MinEtalon_wl + abs(obj.MaxEtalon_wl-obj.MinEtalon_wl)/2; + obj.etalon_pid(obj.MidEtalon_wl); + abort = false; + + case 'No, Abort' + abort = true; + return + end + + end + + function set.MaxEtalon_wl(obj,val) + a = eval(val); + obj.MaxEtalon_wl = a; + end + function set.MinEtalon_wl(obj,val) + a = eval(val); + obj.MinEtalon_wl = a; + end +% + function set.EtalonStep(obj,val) + obj.EtalonStep = eval(val); + obj.tune_etalon; + pause(0.010); + obj.updateStatus; + end + + function set.AllElementStep(obj,val) + if obj.cwaveHandle.is_ready + [~,sSHGpower] = obj.cwaveHandle.get_photodiode_shg; + if obj.sSHG_power + dlg = msgbox('Please wait while CWave re-optimizes SHG power.',mfilename,'modal'); + textH = findall(dlg,'tag','MessageBox'); + delete(findall(dlg,'tag','OKButton')); + drawnow; + obj.cwaveHandle.optimize_shg(); + while obj.cwaveHandle.is_ready + pause(1) + if ~obj.cwaveHandle.is_ready + delete(dlg) + break; + else + obj.updateStatus; + end + end + else + return + end + end + + + obj.AllElementStep = floor( eval(val) ); + obj.cwaveHandle.set_OPOrLambda(obj.AllElementStep); + obj.is_cwaveReady(0.001,true,false); + obj.updateStatus; + end + + function tune_etalon(obj) +% if obj.cwaveHandle.is_ready == true +% return +% end + obj.cwaveHandle.tune_thick_etalon(obj.EtalonStep); + end + + function set_regopo(obj,val) + obj.cwaveHandle.set_intvalue(obj.cwaveHandle.RegOpo_On,val); + end + + function val = get_regopo(obj) + val = obj.cwaveHandle.get_regopo; + end + + function set.tunePercentRange(obj,val) + + obj.tunePercentRange = eval(val); + if (strcmp( val,'')) + return + end + + dlgs = questdlg('Tune with OPO Cavity (Open Loop) or Reference Cavity (Closed Loop)?', ... + 'Tuning Warning', ... + 'OPO Cavity','Reference Cavity','Reference Cavity'); + % Handle response + switch dlgs + case 'OPO Cavity' + isLocked = false; + case 'Reference Cavity' + isLocked = true; + end + + if (isLocked == true) + while ( obj.get_regopo() ~= 2) + obj.set_regopo(2); + pause(1); + disp('regopo'); + obj.get_regopo + disp('end iteration') + end + elseif (isLocked == false) + obj.set_regopo(4); + while ( obj.get_regopo() ~= 4) + obj.set_regopo(4); + pause(1); + disp('regopo'); + obj.get_regopo + disp('end iteration') + end + end + tuneRange = obj.tunePercentRange; + + %Tune to opo center + str1 = 'Please wait while cavity tunes to'; + str = strcat(str1, ' 50%.'); + dlg = msgbox(str,mfilename,'modal'); + textH = findall(dlg,'tag','MessageBox'); + delete(findall(dlg,'tag','OKButton')); + drawnow; + if (isLocked == true) + obj.TuneRefPercent(50); + elseif (isLocked == false) + obj.TunePercent(50); + end + delete(dlg); + + %Tune to low end + str = strcat(str1, sprintf(' %.2f%%',tuneRange(1))); + dlg = msgbox(str,mfilename,'modal'); + textH = findall(dlg,'tag','MessageBox'); + delete(findall(dlg,'tag','OKButton')); + drawnow; + if (isLocked == true) + obj.TuneRefPercent(tuneRange(1)); + elseif (isLocked == false) + obj.TunePercent(tuneRange(1)); + end + delete(dlg); + + %Tune to high end + str = strcat(str1, sprintf(' %.2f%%',tuneRange(2))); + dlg = msgbox(str,mfilename,'modal'); + textH = findall(dlg,'tag','MessageBox'); + delete(findall(dlg,'tag','OKButton')); + drawnow; + if (isLocked == true) + obj.TuneRefPercent(tuneRange(2)); + elseif (isLocked == false) + obj.TunePercent(tuneRange(2)); + end + delete(dlg); + + %Tune back to center + str1 = 'Please wait while cavity tunes to'; + str = strcat(str1, ' 50%.'); + dlg = msgbox(str,mfilename,'modal'); + textH = findall(dlg,'tag','MessageBox'); + delete(findall(dlg,'tag','OKButton')); + drawnow; + if (isLocked == true) + obj.TuneRefPercent(50.0); + elseif (isLocked == false) + obj.TunePercent(50.0); + end + delete(dlg); + end + + function delete(obj) + obj.cwaveHandle.delete(); + obj.PulseStreamerHandle.delete(); + obj.wavemeterHandle.delete(); + end + + %%%%%%%%%%%% + function [Control, Measured, Dt, IntError, Error, P_term,I_term,D_term] = opo_pid(obj,setpoint,tolerance) + i=0; + kp_slow = 200; + ki_slow = 1; + kd_slow = 0; + kp_fast = 1250;%1000; %.1; %kcr = 5510, Pcr = 6.748-3.3046 = 3.4434 + ki_fast = 1250; %200; %.4; + kd_fast = 0; + obj.windupGuardmax = obj.OPOwindupGuardmax; + obj.windupGuardmin = obj.OPOwindupGuardmin; + + if (obj.cwaveHandle.get_regopo ~= 4) + obj.cwaveHandle.set_regopo(4); + %obj.cwaveHandle.set_intvalue(obj.cwaveHandle.RegOpo_On,4) + end + + curr_error = 2*tolerance; %arbitrary condidtion to start PID loop. + ctrl = 0; + p_term = 0; + i_term = 0; + d_term = 0; + Error = []; + Dt = []; + IntError = []; + Control = []; + Measured = []; + while (abs(curr_error) > tolerance ) + tic + measured = obj.getWavelength(); + initial_percent = obj.GetPercent(); + if i == 0 + dt = 0; + prev_error = 0; + int_error = 0; + curr_error = setpoint - measured; + else + curr_error = setpoint - measured; + end + i=i+1; + + %slow control + if abs(curr_error) > 0.00125 + + [ctrl,prev_error,int_error,p_term,i_term,d_term] = obj.pid_update(curr_error,prev_error,int_error,kp_slow,ki_slow,kd_slow,dt); + if (initial_percent+ctrl < obj.MinPercent) + obj.TunePercent(obj.MinPercent); + elseif (initial_percent+ctrl > obj.MaxPercent) + obj.TunePercent(obj.MaxPercent); + else + obj.TunePercent(initial_percent+ctrl); + end + dt = toc; + %fast control + elseif abs(curr_error) > tolerance + j = 0; + if j == 0 + int_error = 0; + j = j+1; + end + + [ctrl,prev_error,int_error,p_term,i_term,d_term] = obj.pid_update(curr_error,prev_error,int_error,kp_fast,ki_fast,kd_fast,dt); + delay = obj.wavemeterHandle.getExposure()/1000 + 0.010; %in seconds + pause(delay) + if (initial_percent+ctrl < obj.MinPercent) + obj.cwaveHandle.tune_opo_cavity(obj.MinPercent); + elseif (initial_percent+ctrl > obj.MaxPercent) + obj.cwaveHandle.tune_opo_cavity(obj.MaxPercent); + else + obj.cwaveHandle.tune_opo_cavity(initial_percent+ctrl); + end + dt = toc; + end + + Dt(i) = dt; + Error(i) = curr_error; + IntError(i) = int_error; + Control(i) = ctrl; + Measured(i) = measured; + P_term(i) = p_term; + I_term(i) = i_term; + D_term(i) = d_term; + end + end + + function [Control, Measured, Dt, IntError, Error, P_term,I_term,D_term] = etalon_pid(obj,setpoint,tolerance,kp,ki,kd) + + obj.windupGuardmax = obj.EtaWindupGuardmax; + obj.windupGuardmin = obj.EtaWindupGuardmin; + + switch nargin + case 2 + tolerance = 0.005; + kp = 100; %was 500 on 11/6/19 noticed it has strong kick back sometimes when near edged of etalon. + ki = 100; + kd = 0; + case 3 + kp = 100; %was 500 on 11/6/19 noticed it has strong kick back sometimes + ki = 100; + kd = 0; + end + + i=0; + %if (obj.cwaveHandle.get_regopo ~= 4) + % obj.cwaveHandle.set_regopo(4); + % %obj.cwaveHandle.set_intvalue(obj.cwaveHandle.RegOpo_On,4) + %end + + curr_error = 2*tolerance; %arbitrary condidtion to start PID loop. + ctrl = 0; + p_term = 0; + i_term = 0; + d_term = 0; + Error = []; + Dt = []; + IntError = []; + Control = []; + Measured = []; + exit = false; + while (abs(curr_error) > tolerance ) + tic + %obj.updateStatus; + obj.is_cwaveReady(0.01,false,false); + if obj.SHG_power < obj.cwaveHandle.SHG_MinPower + while ( ((exit == false) & (obj.SHG_power < obj.cwaveHandle.SHG_MinPower)) | curr_error < 0) + pause(1) + if (obj.SHG_power < obj.cwaveHandle.SHG_MinPower) + [wm_lambda_c,wmPower_c,wm_exptime_c, obj.SHG_power, abort, exit] = reset_hysteresis(obj.SHG_power); + else + break + end + end + curr_error = 2*tolerance; %arbitrary condidtion to start PID loop. + ctrl = 0; + p_term = 0; + i_term = 0; + d_term = 0; + Error = []; + Dt = []; + IntError = []; + Control = []; + Measured = []; + exit = false; + end + measured = obj.getWavelength(); + if i == 0 + dt = 0; + prev_error = 0; + int_error = 0; + curr_error = setpoint - measured; + else + curr_error = setpoint - measured; + end + i=i+1; + + if abs(curr_error) > tolerance %was elseif + j = 0; + if j == 0 + int_error = 0; + j = j+1; + end + + [ctrl,prev_error,int_error,p_term,i_term,d_term] = obj.pid_update(curr_error,prev_error,int_error,kp,ki,kd,dt); + if obj.wavemeterHandle.getExposure() > 25 % was 100 + delay = obj.wavemeterHandle.getExposure()/1000+0.025; %in seconds + pause(delay) + else + if (obj.get_regopo == 2) + pause(0.075) %(was 0.001 s) + elseif (obj.get_regopo == 4) + %pause(obj.wavemeterHandle.getExposure()/1000+0.025) + pause(0.001); %pause(0.025) + end + end + if (ctrl < obj.MinEtalon) + obj.cwaveHandle.tune_thick_etalon(obj.MinEtalon); + Control(i) = obj.MinEtalon; + elseif (ctrl > obj.LocalMinEtalon & ctrl < 0) + obj.cwaveHandle.tune_thick_etalon(obj.LocalMinEtalon); + Control(i) = obj.MinEtalon; + elseif (ctrl > obj.MaxEtalon) + obj.cwaveHandle.tune_thick_etalon(obj.MaxEtalon); + Control(i) = obj.MaxEtalon; + elseif (ctrl < obj.LocalMaxEtalon & ctrl > 0) + obj.cwaveHandle.tune_thick_etalon(obj.LocalMaxEtalon); + Control(i) = obj.LocalMaxEtalon; + else + obj.cwaveHandle.tune_thick_etalon(ctrl); + Control(i) = round(ctrl); + end + dt = toc; + + end + + Dt(i) = dt; + Error(i) = curr_error; + IntError(i) = int_error; + Measured(i) = measured; + P_term(i) = p_term; + I_term(i) = i_term; + D_term(i) = d_term; + end + end + + function [ctrl,prev_error,int_error,p_term,i_term,d_term] = pid_update(obj, curr_error,prev_error, int_error, kp,ki,kd,dt) + + % integration + int_error = int_error + (curr_error * dt); + + % integration windup guarding + if (int_error < obj.windupGuardmin) + int_error = obj.windupGuardmin; + saturation = 1; + elseif (int_error > obj.windupGuardmax) + int_error = obj.windupGuardmax; + saturation = 1; + else + saturation = 0; + end + + if ((sign(curr_error) == sign(prev_error)) && (saturation == 1)) + int_error = 0; + end + + % differentiation + if dt == 0 + int_error = 0; + diff = 0; + else + diff = ((curr_error - prev_error) / dt); + end + + % scaling + p_term = (kp * curr_error); + i_term = (ki * int_error); + d_term = (kd * diff); + + % summation of terms + ctrl = p_term + i_term + d_term; + + % save current error as previous error for next iteration + prev_error = curr_error; + end + %%%%%%%%%%%% + + + end +end \ No newline at end of file