diff --git a/ADwinProII/labscript_devices_ADwin_modules.py b/ADwinProII/labscript_devices_ADwin_modules.py index 46385ab1763d254182b8e575699dc8f8d443a805..60a86ecbf7946fef5e181302607bd42e9231a5fe 100644 --- a/ADwinProII/labscript_devices_ADwin_modules.py +++ b/ADwinProII/labscript_devices_ADwin_modules.py @@ -131,7 +131,7 @@ class ADwinAnalogOut(AnalogOut): self.PID_max = limits[1] - def set_PID(self,t,pid_no,start="last"): + def set_PID(self,t,pid_no,set_output=0): """(De-)activate PID for analog output, or change settings Parameters @@ -141,25 +141,34 @@ class ADwinAnalogOut(AnalogOut): 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". + set_value : float or "last" + When the PID is turned on, 'set_value' is the initially chosen output value, + 'last' means that the I value from the previous PID is taken. + When the PID is turned off, 'set_value' is programmed as the new output/target value, + 'last' means that the output from the PID loop is kept as 'set_target'. """ + # Error check of bounds for set_output + if set_output!="last": + if set_output<self.PID_min or set_output>self.PID_max: + raise LabscriptError( + f"{self.name}: PID 'set_output={set_output}' must be within ({self.PID_min},{self.PID_max})" + ) # TURN OFF PID if pid_no is None: self.PID[t] = { "PID_channel": 0, - "start": start, + "start": set_output, } + # If we don't keep the PID output, set the output to the set_value + if set_output!="last": + self.constant(t,set_output) # TURN ON PID elif (isinstance(pid_no,AnalogIn) and isinstance(pid_no.parent_device,_ADwinCard)) \ or isinstance(pid_no,int): self.PID[t] = { "PID_channel": pid_no, - "start": start, + "start": set_output, } # TODO: Do we need scale factors for setting a PID with integer? else: @@ -203,19 +212,17 @@ class ADwinAnalogOut(AnalogOut): def add_instruction(self,time,instruction,units=None): # Overwrite Output.add_instruction without limit check, becasue the value can be off-limits when this is the target value of the PID - # TODO / WARNING: THIS IS QUITE HACKY AND COULD LEAD TO OFF-LIMIT VALUES NOT NOTICED - # (the actual limits are also checked in the ADwin, so the actual output should be always within limits!) limits_temp = self.limits - self.limits = (-10,10) + if hasattr(self,"pid_no"): + self.limits = (-10,10) super().add_instruction(time,instruction,units) self.limits = limits_temp def expand_timeseries(self,all_times,flat_all_times_len): # Overwrite Output.add_instruction without limit check, becasue the value can be off-limits when this is the target value of the PID - # TODO / WARNING: THIS IS QUITE HACKY AND COULD LEAD TO OFF-LIMIT VALUES NOT NOTICED - # (the actual limits are also checked in the ADwin, so the actual output should be always within limits!) limits_temp = self.limits - self.limits = (-10,10) + if hasattr(self,"pid_no"): + self.limits = (-10,10) super().expand_timeseries(all_times,flat_all_times_len) self.limits = limits_temp @@ -353,6 +360,38 @@ class ADwinAO8(_ADwinCard): raise LabscriptError( f"The ramp sample rate ({rate_kHz:.0f}kHz) of {output.name} must not be faster than ADwin ({ADwin_rate_kHz:.0f}kHz)." ) + + # Check limits of output, but only when PID is NOT enabled (becasue then the target can be out of limits) + # Get all times when PID is not enabled + PID = output.PID.copy() + if 0 not in PID: + # Because 'np.digitize' determines the bins, we also have to make sure t=0 is included. + # If output.PID does not have the key t=0, then the PID is disabled in the beginning. + PID[0] = {"PID_channel":0,"start":0} + PID_times = np.array(list(PID.keys())) + PID_times.sort() + PID_off_times = [] + # For each output value, digitize gets the next highest time in PID_times. + # Using '-1' to get next lowest time. + for i_out,i_PID in enumerate(np.digitize(output.all_times, PID_times)-1): + t = PID_times[i_PID] + if PID[t]["PID_channel"]==0: + # When we turn the PID off but keep the last output, we make sure that + # - at least once in the end the output is (re)set, otherwise the get_final_values() in the Worker is wrong, + # - don't try to also set the target value at the same time, as this would overwrite the target in the ADwin with the PID output. + if PID[t]["start"]=="last": + if i_out+1==len(output.all_times): + raise LabscriptError(f"{output.name}: You must set the output at the end after turning off PID with 'last'.") + elif output.all_times[i_out] == t: + raise LabscriptError(f"{output.name}: Don't turn off PID with persitent value ('last') and also set new value.") + PID_off_times.append(i_out) + PID_off_outputs = output.raw_output[PID_off_times] + if np.any(PID_off_outputs < output.limits[0]) or np.any(PID_off_outputs > output.limits[1]): + raise LabscriptError( + f"Limits of {output.name} (when PID is off) must be in {output.limits}, " + + f"you try to set ({PID_off_outputs.min()},{PID_off_outputs.max()}) " + + f"or turning off the PID with target value beyond limits.") + # Check if the PID channel is allowed if np.any(self.PID_table["PID_channel"] > PIDNO): raise LabscriptError(f"ADwin: Setting the PID channel to more than {PIDNO} is not possible!") @@ -406,14 +445,6 @@ class ADwinAO8(_ADwinCard): output.raw_output[indices==i+1] *= output.PID[t]['PID_channel'].scale_factor elif isinstance(output.PID[t]['PID_channel'],int): 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) 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.