From b0ee323e2e01ba2084bea8de733f16474f1167eb Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 6 Jul 2018 13:27:05 -0400 Subject: [PATCH] heater: Introduce smooth_time config option; remove pid_deriv_time Add generic temperature smoothing to the Heater class. This is useful to avoid min_extrude_temp and verify_heater errors due to measurement noise. Rename the pid_deriv_time config option to smooth_time so that the smoothing amount need only be specified once. Signed-off-by: Kevin O'Connor --- config/example.cfg | 8 +++--- klippy/extras/pid_calibrate.py | 2 +- klippy/heater.py | 51 +++++++++++++++++++++------------- 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/config/example.cfg b/config/example.cfg index dd4b7d28..cf02bf19 100644 --- a/config/example.cfg +++ b/config/example.cfg @@ -161,6 +161,10 @@ sensor_pin: analog13 #adc_voltage: 5.0 # The ADC comparison voltage. This parameter is only valid when the # sensor is an AD595 or "PT100 INA826". The default is 5 volts. +#smooth_time: 2.0 +# A time value (in seconds) over which temperature measurements will +# be smoothed to reduce the impact of measurement noise. The default +# is 2 seconds. control: pid # Control algorithm (either pid or watermark). This parameter must # be provided. @@ -173,10 +177,6 @@ pid_Ki: 1.08 pid_Kd: 114 # Kd is the "derivative" constant for the pid. This parameter must # be provided for PID heaters. -#pid_deriv_time: 2.0 -# A time value (in seconds) over which the derivative in the pid -# will be smoothed to reduce the impact of measurement noise. The -# default is 2 seconds. #pid_integral_max: # The maximum "windup" the integral term may accumulate. The default # is to use the same value as max_power. diff --git a/klippy/extras/pid_calibrate.py b/klippy/extras/pid_calibrate.py index 05ec6e99..b087f2b3 100644 --- a/klippy/extras/pid_calibrate.py +++ b/klippy/extras/pid_calibrate.py @@ -86,7 +86,7 @@ class ControlAutoTune: if temp > self.peak: self.peak = temp self.peak_time = read_time - def check_busy(self, eventtime, last_temp, target_temp): + def check_busy(self, eventtime, smoothed_temp, target_temp): if self.heating or len(self.peaks) < 12: return True return False diff --git a/klippy/heater.py b/klippy/heater.py index 0cb54343..7bbe4a82 100644 --- a/klippy/heater.py +++ b/klippy/heater.py @@ -21,23 +21,34 @@ class error(Exception): class Heater: error = error def __init__(self, config, sensor): - self.sensor = sensor - self.name = config.get_name() printer = config.get_printer() + self.name = config.get_name() + # Setup sensor + self.sensor = sensor self.min_temp = config.getfloat('min_temp', minval=KELVIN_TO_CELCIUS) self.max_temp = config.getfloat('max_temp', above=self.min_temp) self.sensor.setup_minmax(self.min_temp, self.max_temp) self.sensor.setup_callback(self.temperature_callback) self.pwm_delay = self.sensor.get_report_time_delta() + # Setup temperature checks self.min_extrude_temp = config.getfloat( 'min_extrude_temp', 170., minval=self.min_temp, maxval=self.max_temp) + is_fileoutput = printer.get_start_args().get('debugoutput') is not None + self.can_extrude = self.min_extrude_temp <= 0. or is_fileoutput self.max_power = config.getfloat('max_power', 1., above=0., maxval=1.) + self.smooth_time = config.getfloat('smooth_time', 2., above=0.) + self.inv_smooth_time = 1. / self.smooth_time self.lock = threading.Lock() - self.last_temp = 0. + self.last_temp = self.smoothed_temp = self.target_temp = 0. self.last_temp_time = 0. - self.target_temp = 0. + # pwm caching + self.next_pwm_time = 0. + self.last_pwm_value = 0. + # Setup control algorithm sub-class algos = {'watermark': ControlBangBang, 'pid': ControlPID} algo = config.getchoice('control', algos) + self.control = algo(self, config) + # Setup output heater pin heater_pin = config.get('heater_pin') ppins = printer.lookup_object('pins') if algo is ControlBangBang and self.max_power == 1.: @@ -48,12 +59,6 @@ class Heater: 'pwm_cycle_time', 0.100, above=0., maxval=self.pwm_delay) self.mcu_pwm.setup_cycle_time(pwm_cycle_time) self.mcu_pwm.setup_max_duration(MAX_HEAT_TIME) - is_fileoutput = self.mcu_pwm.get_mcu().is_fileoutput() - self.can_extrude = self.min_extrude_temp <= 0. or is_fileoutput - self.control = algo(self, config) - # pwm caching - self.next_pwm_time = 0. - self.last_pwm_value = 0. # Load additional modules printer.try_load_module(config, "verify_heater %s" % (self.name,)) printer.try_load_module(config, "pid_calibrate") @@ -73,16 +78,22 @@ class Heater: self.mcu_pwm.set_pwm(pwm_time, value) def temperature_callback(self, read_time, temp): with self.lock: + time_diff = read_time - self.last_temp_time self.last_temp = temp self.last_temp_time = read_time - self.can_extrude = (temp >= self.min_extrude_temp) self.control.temperature_update(read_time, temp, self.target_temp) + temp_diff = temp - self.smoothed_temp + adj_time = min(time_diff * self.inv_smooth_time, 1.) + self.smoothed_temp += temp_diff * adj_time + self.can_extrude = (self.smoothed_temp >= self.min_extrude_temp) #logging.debug("temp: %.3f %f = %f", read_time, temp) # External commands def get_pwm_delay(self): return self.pwm_delay def get_max_power(self): return self.max_power + def get_smooth_time(self): + return self.smooth_time def set_temp(self, print_time, degrees): if degrees and (degrees < self.min_temp or degrees > self.max_temp): raise error("Requested temperature (%.1f) out of range (%.1f:%.1f)" @@ -94,11 +105,11 @@ class Heater: with self.lock: if self.last_temp_time < print_time: return 0., self.target_temp - return self.last_temp, self.target_temp + return self.smoothed_temp, self.target_temp def check_busy(self, eventtime): with self.lock: return self.control.check_busy( - eventtime, self.last_temp, self.target_temp) + eventtime, self.smoothed_temp, self.target_temp) def set_control(self, control): with self.lock: old_control = self.control @@ -120,8 +131,8 @@ class Heater: def get_status(self, eventtime): with self.lock: target_temp = self.target_temp - last_temp = self.last_temp - return {'temperature': last_temp, 'target': target_temp} + smoothed_temp = self.smoothed_temp + return {'temperature': smoothed_temp, 'target': target_temp} ###################################################################### @@ -143,8 +154,8 @@ class ControlBangBang: self.heater.set_pwm(read_time, self.heater_max_power) else: self.heater.set_pwm(read_time, 0.) - def check_busy(self, eventtime, last_temp, target_temp): - return last_temp < target_temp-self.max_delta + def check_busy(self, eventtime, smoothed_temp, target_temp): + return smoothed_temp < target_temp-self.max_delta ###################################################################### @@ -161,7 +172,7 @@ class ControlPID: self.Kp = config.getfloat('pid_Kp') / PID_PARAM_BASE self.Ki = config.getfloat('pid_Ki') / PID_PARAM_BASE self.Kd = config.getfloat('pid_Kd') / PID_PARAM_BASE - self.min_deriv_time = config.getfloat('pid_deriv_time', 2., above=0.) + self.min_deriv_time = heater.get_smooth_time() imax = config.getfloat('pid_integral_max', self.heater_max_power, minval=0.) self.temp_integ_max = imax / self.Ki @@ -194,8 +205,8 @@ class ControlPID: self.prev_temp_deriv = temp_deriv if co == bounded_co: self.prev_temp_integ = temp_integ - def check_busy(self, eventtime, last_temp, target_temp): - temp_diff = target_temp - last_temp + def check_busy(self, eventtime, smoothed_temp, target_temp): + temp_diff = target_temp - smoothed_temp return (abs(temp_diff) > PID_SETTLE_DELTA or abs(self.prev_temp_deriv) > PID_SETTLE_SLOPE)