From f53897758da58ac0201dcfaecbedc8409d828da2 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 25 Aug 2016 11:24:18 -0400 Subject: [PATCH] heater: Support max_power setting for heaters Change the mcu PWM value from an integer (0-255) to a float (0. - 1.). Add support for limiting the maximum power (as measured over a sufficiently long duration) to a particular heater. Signed-off-by: Kevin O'Connor --- config/example.cfg | 11 +++++++++-- klippy/fan.py | 8 ++++---- klippy/heater.py | 46 ++++++++++++++++++++++++---------------------- klippy/mcu.py | 4 +++- 4 files changed, 40 insertions(+), 29 deletions(-) diff --git a/config/example.cfg b/config/example.cfg index 9f89d5e0..af418ca6 100644 --- a/config/example.cfg +++ b/config/example.cfg @@ -153,6 +153,13 @@ filament_diameter: 3.500 heater_pin: ar4 # PWM output pin controlling the heater. This parameter must be # provided. +#max_power: 1.0 +# The maximum power (expressed as a value from 0.0 to 1.0) that the +# heater_pin may be set to. The value 1.0 allows the pin to be set +# fully enabled for extended periods, while a value of 0.5 would +# allow the pin to be enabled for no more than half the time. This +# setting may be used to limit the total power output (over extended +# periods) to the heater. The default is 1.0. thermistor_pin: analog1 # Analog input pin connected to thermistor. This parameter must be # provided. @@ -179,9 +186,9 @@ pid_Kd: 114 # 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: 255 +#pid_integral_max: # The maximum "windup" the integral term may accumulate. The default -# is 255. +# is to use the same value as max_power. #min_extrude_temp: 170 # The minimum temperature (in Celsius) at which extruder move # commands may be issued. The default is 170 Celsius. diff --git a/klippy/fan.py b/klippy/fan.py index 98e73ba6..739af2f1 100644 --- a/klippy/fan.py +++ b/klippy/fan.py @@ -11,7 +11,7 @@ class PrinterFan: self.printer = printer self.config = config self.mcu_fan = None - self.last_fan_value = 0 + self.last_fan_value = 0. self.last_fan_time = 0. self.kick_start_time = config.getfloat('kick_start_time', 0.1) def build_config(self): @@ -20,15 +20,15 @@ class PrinterFan: self.mcu_fan = self.printer.mcu.create_pwm(pin, hard_pwm, 0) # External commands def set_speed(self, print_time, value): - value = max(0, min(255, int(value*255. + 0.5))) + value = max(0., min(1., value)) if value == self.last_fan_value: return mcu_time = self.mcu_fan.print_to_mcu_time(print_time) mcu_time = max(self.last_fan_time + FAN_MIN_TIME, mcu_time) - if (value and value < 255 + if (value and value < 1. and not self.last_fan_value and self.kick_start_time): # Run fan at full speed for specified kick_start_time - self.mcu_fan.set_pwm(mcu_time, 255) + self.mcu_fan.set_pwm(mcu_time, 1.) mcu_time += self.kick_start_time self.mcu_fan.set_pwm(mcu_time, value) self.last_fan_time = mcu_time diff --git a/klippy/heater.py b/klippy/heater.py index 909307ac..77cb659f 100644 --- a/klippy/heater.py +++ b/klippy/heater.py @@ -19,7 +19,7 @@ REPORT_TIME = 0.300 KELVIN_TO_CELCIUS = -273.15 MAX_HEAT_TIME = 5.0 AMBIENT_TEMP = 25. -PWM_MAX = 255 +PID_PARAM_BASE = 255. class error(Exception): pass @@ -35,6 +35,7 @@ class PrinterHeater: self.min_extrude_temp = config.getfloat('min_extrude_temp', 170.) self.min_temp = self.config.getfloat('min_temp') self.max_temp = self.config.getfloat('max_temp') + self.max_power = max(0., min(1., self.config.getfloat('max_power', 1.))) self.can_extrude = (self.min_extrude_temp <= 0.) self.lock = threading.Lock() self.last_temp = 0. @@ -49,7 +50,7 @@ class PrinterHeater: algo = self.config.getchoice('control', algos) heater_pin = self.config.get('heater_pin') thermistor_pin = self.config.get('thermistor_pin') - if algo is ControlBangBang: + if algo is ControlBangBang and self.max_power == 1.: self.mcu_pwm = self.printer.mcu.create_digital_out( heater_pin, MAX_HEAT_TIME) else: @@ -69,7 +70,7 @@ class PrinterHeater: if self.target_temp <= 0.: return if (read_time < self.next_pwm_time - and abs(value - self.last_pwm_value) < 15): + and abs(value - self.last_pwm_value) < 0.05): return elif not self.last_pwm_value and ( self.target_temp <= 0. or read_time < self.next_pwm_time): @@ -77,7 +78,7 @@ class PrinterHeater: pwm_time = read_time + REPORT_TIME + SAMPLE_TIME*SAMPLE_COUNT self.next_pwm_time = pwm_time + 0.75 * MAX_HEAT_TIME self.last_pwm_value = value - logging.debug("%s: pwm=%d@%.3f (from %.3f@%.3f [%.3f])" % ( + logging.debug("%s: pwm=%.3f@%.3f (from %.3f@%.3f [%.3f])" % ( self.config.section, value, pwm_time, self.last_temp, self.last_temp_time, self.target_temp)) self.mcu_pwm.set_pwm(pwm_time, value) @@ -139,9 +140,9 @@ class ControlBangBang: elif not self.heating and temp <= self.heater.target_temp-self.max_delta: self.heating = True if self.heating: - self.heater.set_pwm(read_time, PWM_MAX) + self.heater.set_pwm(read_time, self.heater.max_power) else: - self.heater.set_pwm(read_time, 0) + self.heater.set_pwm(read_time, 0.) def check_busy(self, eventtime): return self.heater.last_temp < self.heater.target_temp-self.max_delta @@ -153,11 +154,11 @@ class ControlBangBang: class ControlPID: def __init__(self, heater, config): self.heater = heater - self.Kp = config.getfloat('pid_Kp') - self.Ki = config.getfloat('pid_Ki') - self.Kd = config.getfloat('pid_Kd') + 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.) - imax = config.getint('pid_integral_max', PWM_MAX) + imax = config.getfloat('pid_integral_max', heater.max_power) self.temp_integ_max = imax / self.Ki self.prev_temp = AMBIENT_TEMP self.prev_temp_time = 0. @@ -177,10 +178,10 @@ class ControlPID: temp_integ = self.prev_temp_integ + temp_err * time_diff temp_integ = max(0., min(self.temp_integ_max, temp_integ)) # Calculate output - co = int(self.Kp*temp_err + self.Ki*temp_integ - self.Kd*temp_deriv) + co = self.Kp*temp_err + self.Ki*temp_integ - self.Kd*temp_deriv #logging.debug("pid: %f@%.3f -> diff=%f deriv=%f err=%f integ=%f co=%d" % ( # temp, read_time, temp_diff, temp_deriv, temp_err, temp_integ, co)) - bounded_co = max(0, min(PWM_MAX, co)) + bounded_co = max(0., min(self.heater.max_power, co)) self.heater.set_pwm(read_time, bounded_co) # Store state for next measurement self.prev_temp = temp @@ -216,12 +217,12 @@ class ControlAutoTune: self.heating = True self.check_peaks() if self.heating: - self.heater.set_pwm(read_time, PWM_MAX) + self.heater.set_pwm(read_time, self.heater.max_power) if temp < self.peak: self.peak = temp self.peak_time = read_time else: - self.heater.set_pwm(read_time, 0) + self.heater.set_pwm(read_time, 0.) if temp > self.peak: self.peak = temp self.peak_time = read_time @@ -235,8 +236,8 @@ class ControlAutoTune: return temp_diff = self.peaks[-1][0] - self.peaks[-2][0] time_diff = self.peaks[-1][1] - self.peaks[-3][1] - pwm_diff = PWM_MAX - 0 - Ku = 4. * (2. * pwm_diff) / (abs(temp_diff) * math.pi) + max_power = self.heater.max_power + Ku = 4. * (2. * max_power) / (abs(temp_diff) * math.pi) Tu = time_diff Kp = 0.6 * Ku @@ -244,8 +245,9 @@ class ControlAutoTune: Td = 0.125 * Tu Ki = Kp / Ti Kd = Kp * Td - logging.info("Autotune: raw=%f/%d Ku=%f Tu=%f Kp=%f Ki=%f Kd=%f" % ( - temp_diff, pwm_diff, Ku, Tu, Kp, Ki, Kd)) + logging.info("Autotune: raw=%f/%f Ku=%f Tu=%f Kp=%f Ki=%f Kd=%f" % ( + temp_diff, max_power, Ku, Tu, + Kp * PID_PARAM_BASE, Ki * PID_PARAM_BASE, Kd * PID_PARAM_BASE)) def check_busy(self, eventtime): if self.heating or len(self.peaks) < 12: return True @@ -271,17 +273,17 @@ class ControlBumpTest: def adc_callback(self, read_time, temp): self.temp_samples[read_time] = temp if not self.state: - self.set_pwm(read_time, 0) + self.set_pwm(read_time, 0.) if len(self.temp_samples) >= 20: self.state += 1 elif self.state == 1: if temp < self.target_temp: - self.set_pwm(read_time, PWM_MAX) + self.set_pwm(read_time, self.heater.max_power) return - self.set_pwm(read_time, 0) + self.set_pwm(read_time, 0.) self.state += 1 elif self.state == 2: - self.set_pwm(read_time, 0) + self.set_pwm(read_time, 0.) if temp <= (self.target_temp + AMBIENT_TEMP) / 2.: self.dump_stats() self.state += 1 diff --git a/klippy/mcu.py b/klippy/mcu.py index 83ff1fdb..129024ba 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -245,11 +245,12 @@ class MCU_digital_out: return self._last_value def set_pwm(self, mcu_time, value): dval = 0 - if value > 127: + if value >= 0.5: dval = 1 self.set_digital(mcu_time, dval) class MCU_pwm: + PWM_MAX = 255. def __init__(self, mcu, pin, cycle_ticks, max_duration, hard_pwm=True): self._mcu = mcu self._oid = mcu.create_oid() @@ -272,6 +273,7 @@ class MCU_pwm: self.print_to_mcu_time = mcu.print_to_mcu_time def set_pwm(self, mcu_time, value): clock = int(mcu_time * self._mcu_freq) + value = int(value * self.PWM_MAX + 0.5) msg = self._set_cmd.encode(self._oid, clock, value) self._mcu.send(msg, minclock=self._last_clock, reqclock=clock , cq=self._cmd_queue)