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:
Kevin O'Connor 2018-07-06 13:27:05 -04:00
parent 32175bc66a
commit b0ee323e2e
3 changed files with 36 additions and 25 deletions

View File

@ -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.

View File

@ -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

View File

@ -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)