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 <kevin@koconnor.net>
This commit is contained in:
parent
32175bc66a
commit
b0ee323e2e
|
@ -161,6 +161,10 @@ sensor_pin: analog13
|
||||||
#adc_voltage: 5.0
|
#adc_voltage: 5.0
|
||||||
# The ADC comparison voltage. This parameter is only valid when the
|
# The ADC comparison voltage. This parameter is only valid when the
|
||||||
# sensor is an AD595 or "PT100 INA826". The default is 5 volts.
|
# 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: pid
|
||||||
# Control algorithm (either pid or watermark). This parameter must
|
# Control algorithm (either pid or watermark). This parameter must
|
||||||
# be provided.
|
# be provided.
|
||||||
|
@ -173,10 +177,6 @@ pid_Ki: 1.08
|
||||||
pid_Kd: 114
|
pid_Kd: 114
|
||||||
# Kd is the "derivative" constant for the pid. This parameter must
|
# Kd is the "derivative" constant for the pid. This parameter must
|
||||||
# be provided for PID heaters.
|
# 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:
|
#pid_integral_max:
|
||||||
# The maximum "windup" the integral term may accumulate. The default
|
# The maximum "windup" the integral term may accumulate. The default
|
||||||
# is to use the same value as max_power.
|
# is to use the same value as max_power.
|
||||||
|
|
|
@ -86,7 +86,7 @@ class ControlAutoTune:
|
||||||
if temp > self.peak:
|
if temp > self.peak:
|
||||||
self.peak = temp
|
self.peak = temp
|
||||||
self.peak_time = read_time
|
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:
|
if self.heating or len(self.peaks) < 12:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -21,23 +21,34 @@ class error(Exception):
|
||||||
class Heater:
|
class Heater:
|
||||||
error = error
|
error = error
|
||||||
def __init__(self, config, sensor):
|
def __init__(self, config, sensor):
|
||||||
self.sensor = sensor
|
|
||||||
self.name = config.get_name()
|
|
||||||
printer = config.get_printer()
|
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.min_temp = config.getfloat('min_temp', minval=KELVIN_TO_CELCIUS)
|
||||||
self.max_temp = config.getfloat('max_temp', above=self.min_temp)
|
self.max_temp = config.getfloat('max_temp', above=self.min_temp)
|
||||||
self.sensor.setup_minmax(self.min_temp, self.max_temp)
|
self.sensor.setup_minmax(self.min_temp, self.max_temp)
|
||||||
self.sensor.setup_callback(self.temperature_callback)
|
self.sensor.setup_callback(self.temperature_callback)
|
||||||
self.pwm_delay = self.sensor.get_report_time_delta()
|
self.pwm_delay = self.sensor.get_report_time_delta()
|
||||||
|
# Setup temperature checks
|
||||||
self.min_extrude_temp = config.getfloat(
|
self.min_extrude_temp = config.getfloat(
|
||||||
'min_extrude_temp', 170., minval=self.min_temp, maxval=self.max_temp)
|
'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.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.lock = threading.Lock()
|
||||||
self.last_temp = 0.
|
self.last_temp = self.smoothed_temp = self.target_temp = 0.
|
||||||
self.last_temp_time = 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}
|
algos = {'watermark': ControlBangBang, 'pid': ControlPID}
|
||||||
algo = config.getchoice('control', algos)
|
algo = config.getchoice('control', algos)
|
||||||
|
self.control = algo(self, config)
|
||||||
|
# Setup output heater pin
|
||||||
heater_pin = config.get('heater_pin')
|
heater_pin = config.get('heater_pin')
|
||||||
ppins = printer.lookup_object('pins')
|
ppins = printer.lookup_object('pins')
|
||||||
if algo is ControlBangBang and self.max_power == 1.:
|
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)
|
'pwm_cycle_time', 0.100, above=0., maxval=self.pwm_delay)
|
||||||
self.mcu_pwm.setup_cycle_time(pwm_cycle_time)
|
self.mcu_pwm.setup_cycle_time(pwm_cycle_time)
|
||||||
self.mcu_pwm.setup_max_duration(MAX_HEAT_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
|
# Load additional modules
|
||||||
printer.try_load_module(config, "verify_heater %s" % (self.name,))
|
printer.try_load_module(config, "verify_heater %s" % (self.name,))
|
||||||
printer.try_load_module(config, "pid_calibrate")
|
printer.try_load_module(config, "pid_calibrate")
|
||||||
|
@ -73,16 +78,22 @@ class Heater:
|
||||||
self.mcu_pwm.set_pwm(pwm_time, value)
|
self.mcu_pwm.set_pwm(pwm_time, value)
|
||||||
def temperature_callback(self, read_time, temp):
|
def temperature_callback(self, read_time, temp):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
|
time_diff = read_time - self.last_temp_time
|
||||||
self.last_temp = temp
|
self.last_temp = temp
|
||||||
self.last_temp_time = read_time
|
self.last_temp_time = read_time
|
||||||
self.can_extrude = (temp >= self.min_extrude_temp)
|
|
||||||
self.control.temperature_update(read_time, temp, self.target_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)
|
#logging.debug("temp: %.3f %f = %f", read_time, temp)
|
||||||
# External commands
|
# External commands
|
||||||
def get_pwm_delay(self):
|
def get_pwm_delay(self):
|
||||||
return self.pwm_delay
|
return self.pwm_delay
|
||||||
def get_max_power(self):
|
def get_max_power(self):
|
||||||
return self.max_power
|
return self.max_power
|
||||||
|
def get_smooth_time(self):
|
||||||
|
return self.smooth_time
|
||||||
def set_temp(self, print_time, degrees):
|
def set_temp(self, print_time, degrees):
|
||||||
if degrees and (degrees < self.min_temp or degrees > self.max_temp):
|
if degrees and (degrees < self.min_temp or degrees > self.max_temp):
|
||||||
raise error("Requested temperature (%.1f) out of range (%.1f:%.1f)"
|
raise error("Requested temperature (%.1f) out of range (%.1f:%.1f)"
|
||||||
|
@ -94,11 +105,11 @@ class Heater:
|
||||||
with self.lock:
|
with self.lock:
|
||||||
if self.last_temp_time < print_time:
|
if self.last_temp_time < print_time:
|
||||||
return 0., self.target_temp
|
return 0., self.target_temp
|
||||||
return self.last_temp, self.target_temp
|
return self.smoothed_temp, self.target_temp
|
||||||
def check_busy(self, eventtime):
|
def check_busy(self, eventtime):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
return self.control.check_busy(
|
return self.control.check_busy(
|
||||||
eventtime, self.last_temp, self.target_temp)
|
eventtime, self.smoothed_temp, self.target_temp)
|
||||||
def set_control(self, control):
|
def set_control(self, control):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
old_control = self.control
|
old_control = self.control
|
||||||
|
@ -120,8 +131,8 @@ class Heater:
|
||||||
def get_status(self, eventtime):
|
def get_status(self, eventtime):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
target_temp = self.target_temp
|
target_temp = self.target_temp
|
||||||
last_temp = self.last_temp
|
smoothed_temp = self.smoothed_temp
|
||||||
return {'temperature': last_temp, 'target': target_temp}
|
return {'temperature': smoothed_temp, 'target': target_temp}
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
|
@ -143,8 +154,8 @@ class ControlBangBang:
|
||||||
self.heater.set_pwm(read_time, self.heater_max_power)
|
self.heater.set_pwm(read_time, self.heater_max_power)
|
||||||
else:
|
else:
|
||||||
self.heater.set_pwm(read_time, 0.)
|
self.heater.set_pwm(read_time, 0.)
|
||||||
def check_busy(self, eventtime, last_temp, target_temp):
|
def check_busy(self, eventtime, smoothed_temp, target_temp):
|
||||||
return last_temp < target_temp-self.max_delta
|
return smoothed_temp < target_temp-self.max_delta
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
|
@ -161,7 +172,7 @@ class ControlPID:
|
||||||
self.Kp = config.getfloat('pid_Kp') / PID_PARAM_BASE
|
self.Kp = config.getfloat('pid_Kp') / PID_PARAM_BASE
|
||||||
self.Ki = config.getfloat('pid_Ki') / PID_PARAM_BASE
|
self.Ki = config.getfloat('pid_Ki') / PID_PARAM_BASE
|
||||||
self.Kd = config.getfloat('pid_Kd') / 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,
|
imax = config.getfloat('pid_integral_max', self.heater_max_power,
|
||||||
minval=0.)
|
minval=0.)
|
||||||
self.temp_integ_max = imax / self.Ki
|
self.temp_integ_max = imax / self.Ki
|
||||||
|
@ -194,8 +205,8 @@ class ControlPID:
|
||||||
self.prev_temp_deriv = temp_deriv
|
self.prev_temp_deriv = temp_deriv
|
||||||
if co == bounded_co:
|
if co == bounded_co:
|
||||||
self.prev_temp_integ = temp_integ
|
self.prev_temp_integ = temp_integ
|
||||||
def check_busy(self, eventtime, last_temp, target_temp):
|
def check_busy(self, eventtime, smoothed_temp, target_temp):
|
||||||
temp_diff = target_temp - last_temp
|
temp_diff = target_temp - smoothed_temp
|
||||||
return (abs(temp_diff) > PID_SETTLE_DELTA
|
return (abs(temp_diff) > PID_SETTLE_DELTA
|
||||||
or abs(self.prev_temp_deriv) > PID_SETTLE_SLOPE)
|
or abs(self.prev_temp_deriv) > PID_SETTLE_SLOPE)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue