diff --git a/ADwinProII/blacs_workers.py b/ADwinProII/blacs_workers.py index d69de1fd9aff044e8b601b4da5b5a16c68914c40..5b8cec38b65483230666a4bf5359c6cef99f04dc 100644 --- a/ADwinProII/blacs_workers.py +++ b/ADwinProII/blacs_workers.py @@ -29,7 +29,7 @@ class ADwinProIIWorker(Worker): def init(self): self.timing = None self.h5file = None - self.smart_cache = {"AOUT":None, "PIDs":None, "AIN":None} + self.smart_cache = {"AOUT":None, "PIDs":None, "PID_CONFIG":None, "AIN":None} self.smart_cache.update({DIO:None for DIO in self.DIO_ADwin_DataNo}) self.process_number_buffered = int(self.process_buffered[-1]) self.process_number_manual = int(self.process_manual[-1]) @@ -164,12 +164,19 @@ class ADwinProIIWorker(Worker): self.adw.SetData_Long(PIDs["n_cycles"], 4, 1, PIDs.shape[0]) self.adw.SetData_Long(PIDs["AOUT_channel"], 5, 1, PIDs.shape[0]) self.adw.SetData_Long(PIDs["PID_channel"], 6, 1, PIDs.shape[0]) - self.adw.SetData_Float(PIDs["PID_P"], 25, 1, PIDs.shape[0]) - self.adw.SetData_Float(PIDs["PID_I"], 26, 1, PIDs.shape[0]) - self.adw.SetData_Float(PIDs["PID_D"], 27, 1, PIDs.shape[0]) - self.adw.SetData_Long(PIDs["PID_min"], 28, 1, PIDs.shape[0]) - self.adw.SetData_Long(PIDs["PID_max"], 29, 1, PIDs.shape[0]) self.adw.SetData_Long(PIDs["PID_start"], 30, 1, PIDs.shape[0]) + PID_config = group["ANALOG_OUT/PID_CONFIG"] + if fresh or not np.array_equal(PID_config[:],self.smart_cache["PID_CONFIG"]): + print("PID_CONFIG programmed.") + self.smart_cache["PID_CONFIG"] = PID_config[:] + n_PID = PID_config.shape[0] + self.adw.Set_Par(22,n_PID) + self.adw.SetData_Long(PID_config["PID_channel"], 24, 1, n_PID) + self.adw.SetData_Float(PID_config["PID_P"], 25, 1, n_PID) + self.adw.SetData_Float(PID_config["PID_I"], 26, 1, n_PID) + self.adw.SetData_Float(PID_config["PID_D"], 27, 1, n_PID) + self.adw.SetData_Long(PID_config["PID_min"], 28, 1, n_PID) + self.adw.SetData_Long(PID_config["PID_max"], 29, 1, n_PID) AIN = group["ANALOG_IN/TIMES"] if fresh or not np.array_equal(AIN[:],self.smart_cache["AIN"]): print("AIN programmed.") diff --git a/ADwinProII/labscript_devices.py b/ADwinProII/labscript_devices.py index d1bee26ac79993ac01ba804af3866b9d46de0644..f74a85b4056302be67bca20f1bfae9040378ece7 100644 --- a/ADwinProII/labscript_devices.py +++ b/ADwinProII/labscript_devices.py @@ -232,12 +232,14 @@ class ADwinProII(PseudoclockDevice): # Lists to collect instructions of analog channels analog_output = [] PID_channels = [] + PID_config = [] analog_input = [] for device in self.modules: if isinstance(device,ADwinAO8): analog_output.append(device.outputs) - PID_channels.append(device.PID_channels) + PID_channels.append(device.PID_table) + PID_config.append(device.PID_config) elif isinstance(device,ADwinDIO32): group.create_dataset("DIGITAL_OUT/"+device.name, data=device.digital_data) elif isinstance(device,ADwinAI8): @@ -252,14 +254,17 @@ class ADwinProII(PseudoclockDevice): PID_channels.append(np.full(1,last_values,dtype=PID_channels[0].dtype)) # Concatenate arrays PID_channels = np.concatenate(PID_channels) + PID_config = np.concatenate(PID_config) analog_output = np.concatenate(analog_output) analog_input = np.concatenate(analog_input) # Sort analog outputs and PID settings analog_output = np.sort(analog_output, axis=0, order="n_cycles") PID_channels = np.sort(PID_channels, axis=0, order="n_cycles") + PID_config = np.sort(PID_config, axis=0, order="PID_channel") # Save datasets AO_group.create_dataset('VALUES', compression=config.compression, data=analog_output) AO_group.create_dataset('PID_CHANNELS', compression=config.compression, data=PID_channels) + AO_group.create_dataset('PID_CONFIG', compression=config.compression, data=PID_config) AI_group.create_dataset('TIMES', data=analog_input) AI_datapoints = analog_input["stop_time"] - analog_input["start_time"] diff --git a/ADwinProII/labscript_devices_ADwin_modules.py b/ADwinProII/labscript_devices_ADwin_modules.py index 29ef0f9c183578751aca4498ac3728575ecac266..46385ab1763d254182b8e575699dc8f8d443a805 100644 --- a/ADwinProII/labscript_devices_ADwin_modules.py +++ b/ADwinProII/labscript_devices_ADwin_modules.py @@ -96,17 +96,13 @@ class ADwinAnalogOut(AnalogOut): ) self.PID = {} # instructions for PID settings - - def set_PID(self,t,PID_AnalogIn,P=0,I=0,D=0,limits=None, start="last"): - """(De-)activate PID for analog output, or change settings + def init_PID(self,pid_no,P=0,I=0,D=0,limits=None): + """Set parameters for PID once at beginning of shot. Parameters ---------- - t : float - Time when to apply the PID settings - PID_AnalogIn : int or `AnalogIn` or None + pid_no : int or `AnalogIn` Channel of analog input for error siganl of PID feedback. - If `None` PID is deactivated. P : float Proportional parameter I : float @@ -115,38 +111,54 @@ class ADwinAnalogOut(AnalogOut): Differential parameter limits : tuple of float, optional Limits for output voltage, defaults to output limits - start : float or "last" - Inital value of the output when the PID is turned on, assigned to the I part. - If start="last" when the PID is turned on, the I value from the PID earlier - in the shot is kept. If start="last" when the PID is turned off, the last output - value is kept as "set_target". """ + if hasattr(self,"pid_no"): + raise NotImplementedError("Only one set of PID parameters per channel is implemented.") if limits is None: # Use the Output limits if there are none specified here. limits = self.limits if limits[0]<self.limits[0] or limits[1]>self.limits[1]: raise LabscriptError(f"Limits of {self.name} with PID must not be larger than channel limits {self.limits}!") + + if isinstance(pid_no,AnalogIn) and isinstance(pid_no.parent_device,_ADwinCard): + pid_no = int(pid_no.connection) + pid_no.parent_device.start_index + self.pid_no = pid_no + self.P = P + self.I = I + self.D = D + self.PID_min = limits[0] + self.PID_max = limits[1] + + + def set_PID(self,t,pid_no,start="last"): + """(De-)activate PID for analog output, or change settings + + Parameters + ---------- + t : float + Time when to apply the PID settings + pid_no : int or `AnalogIn` or None + Channel of analog input for error siganl of PID feedback. + If `None` PID is deactivated. + start : float or "last" + Inital value of the output when the PID is turned on, assigned to the I part. + If start="last" when the PID is turned on, the I value from the PID earlier + in the shot is kept. If start="last" when the PID is turned off, the last output + value is kept as "set_target". + """ # TURN OFF PID - if PID_AnalogIn is None: + if pid_no is None: self.PID[t] = { "PID_channel": 0, - "P": P, - "I": I, - "D": D, - "limits": limits, "start": start, } # TURN ON PID - elif (isinstance(PID_AnalogIn,AnalogIn) and isinstance(PID_AnalogIn.parent_device,_ADwinCard)) \ - or isinstance(PID_AnalogIn,int): + elif (isinstance(pid_no,AnalogIn) and isinstance(pid_no.parent_device,_ADwinCard)) \ + or isinstance(pid_no,int): self.PID[t] = { - "PID_channel": PID_AnalogIn, - "P": P, - "I": I, - "D": D, - "limits": limits, + "PID_channel": pid_no, "start": start, } # TODO: Do we need scale factors for setting a PID with integer? @@ -321,6 +333,7 @@ class ADwinAO8(_ADwinCard): def __init__(self, name, parent_device, module_address, num_AO=8, **kwargs): self.num_AO = num_AO self.start_index = module_start_index[int(module_address)] + super().__init__(name, parent_device, module_address, **kwargs) @@ -341,10 +354,9 @@ class ADwinAO8(_ADwinCard): f"The ramp sample rate ({rate_kHz:.0f}kHz) of {output.name} must not be faster than ADwin ({ADwin_rate_kHz:.0f}kHz)." ) # Check if the PID channel is allowed - if np.any(self.PID_channels["PID_channel"] > PIDNO): - max_PID_channel = self.PID_channels["PID_channel"].max() + if np.any(self.PID_table["PID_channel"] > PIDNO): raise LabscriptError(f"ADwin: Setting the PID channel to more than {PIDNO} is not possible!") - if np.any(self.PID_channels["PID_channel"] < 0): + if np.any(self.PID_table["PID_channel"] < 0): raise LabscriptError("ADwin: Setting the PID channel to less than 0 is not possible!") def generate_code(self,hdf5_file): @@ -353,25 +365,36 @@ class ADwinAO8(_ADwinCard): pseudoclock = clockline.parent_device output_dtypes = [("n_cycles",np.int32),("channel",np.int32),("value",np.int32)] - PID_dtypes = [ - ("n_cycles",np.int32),("AOUT_channel",np.int32),("PID_channel",np.int32), - ("PID_P",np.float64),("PID_I",np.float64),("PID_D",np.float64),("PID_min",np.int32),("PID_max",np.int32),("PID_start",np.int32) + PID_config_dtypes = [ + ("PID_channel",np.int32),("PID_P",np.float64),("PID_I",np.float64),("PID_D",np.float64),("PID_min",np.int32),("PID_max",np.int32) + ] + PID_table_dtypes = [ + ("n_cycles",np.int32),("AOUT_channel",np.int32),("PID_channel",np.int32),("PID_start",np.int32) ] outputs = [] - PID_channels = [] + PID_table = [] + PID_config = [] for output in sorted(self.child_devices, key=lambda dev:int(dev.connection)): output.expand_output() # Get input channels for PID, collect changed for time table and store bare channels as dataset if output.PID: - PID_array = np.zeros(len(output.PID),dtype=PID_dtypes) + # Get PID parameters + if not hasattr(output,"pid_no"): + raise LabscriptError(f"For {self.name} you try to use a PID, but never set the parameters via {self.name}.init_PID().") + PID_config. append( + np.array([ + (output.pid_no,output.P,output.I,output.D,ADC(output.PID_min,self.resolution_bits,self.min_V,self.max_V),ADC(output.PID_max,self.resolution_bits,self.min_V,self.max_V)) + ], dtype=PID_config_dtypes) + ) + PID_array = np.zeros(len(output.PID),dtype=PID_table_dtypes) PID_times = np.array(list(output.PID.keys())) PID_times.sort() PID_array["n_cycles"] = np.round(PID_times * pseudoclock.clock_limit) # PID_array["PID_channel"] = list(output.PID_channel.values()) PID_array["AOUT_channel"] = int(output.connection) + self.start_index - PID_channels.append(PID_array) + PID_table.append(PID_array) # If a PID is enabled, the set values are not the actual voltage values of the # Output, but those measured at the input. If the input has a gain enabled, the # set values have the be scaled too, to have the PID stabilized to the right values. @@ -385,12 +408,12 @@ class ADwinAO8(_ADwinCard): PID_array["PID_channel"][i] = output.PID[t]['PID_channel'] # If PID_channel[t]=None, there are zeros in the PID_array - PID_array["PID_P"][i] = output.PID[t]['P'] - PID_array["PID_I"][i] = output.PID[t]['I'] - PID_array["PID_D"][i] = output.PID[t]['D'] - limits = output.PID[t]['limits'] - PID_array["PID_min"][i] = ADC(limits[0],self.resolution_bits,self.min_V,self.max_V) - PID_array["PID_max"][i] = ADC(limits[1],self.resolution_bits,self.min_V,self.max_V) + # PID_array["PID_P"][i] = output.PID[t]['P'] + # PID_array["PID_I"][i] = output.PID[t]['I'] + # PID_array["PID_D"][i] = output.PID[t]['D'] + # limits = output.PID[t]['limits'] + # PID_array["PID_min"][i] = ADC(limits[0],self.resolution_bits,self.min_V,self.max_V) + # PID_array["PID_max"][i] = ADC(limits[1],self.resolution_bits,self.min_V,self.max_V) if output.PID[t]["start"]=="last": # When we want to use the previous value during the shot, # we use a value that's out of the 16 bit ADC range to identify. @@ -406,7 +429,8 @@ class ADwinAO8(_ADwinCard): outputs.append(out) self.outputs = np.concatenate(outputs) if outputs else np.array([],dtype=output_dtypes) - self.PID_channels = np.concatenate(PID_channels) if PID_channels else np.array([],dtype=PID_dtypes) + self.PID_table = np.concatenate(PID_table) if PID_table else np.array([],dtype=PID_table_dtypes) + self.PID_config = np.concatenate(PID_config) if PID_config else np.array([],dtype=PID_config_dtypes)