-
-
Notifications
You must be signed in to change notification settings - Fork 103
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Initial Implementation * Forgot min and max temp * me being dumb * Update curve_control.cfg * Updated curve definition and docs * fix tests
- Loading branch information
Showing
5 changed files
with
246 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,8 @@ | |
# Copyright (C) 2016-2020 Kevin O'Connor <[email protected]> | ||
# | ||
# This file may be distributed under the terms of the GNU GPLv3 license. | ||
import statistics | ||
|
||
from . import fan | ||
|
||
KELVIN_TO_CELSIUS = -273.15 | ||
|
@@ -41,7 +43,11 @@ def __init__(self, config): | |
maxval=self.max_temp, | ||
) | ||
self.target_temp = self.target_temp_conf | ||
algos = {"watermark": ControlBangBang, "pid": ControlPID} | ||
algos = { | ||
"watermark": ControlBangBang, | ||
"pid": ControlPID, | ||
"curve": ControlCurve, | ||
} | ||
algo = config.getchoice("control", algos) | ||
self.control = algo(self, config) | ||
self.next_speed_time = 0.0 | ||
|
@@ -89,6 +95,7 @@ def get_status(self, eventtime): | |
status = self.fan.get_status(eventtime) | ||
status["temperature"] = round(self.last_temp, 2) | ||
status["target"] = self.target_temp | ||
status["control"] = self.control.get_type() | ||
return status | ||
|
||
def is_adc_faulty(self): | ||
|
@@ -101,8 +108,9 @@ def is_adc_faulty(self): | |
) | ||
|
||
def cmd_SET_TEMPERATURE_FAN_TARGET(self, gcmd): | ||
temp = gcmd.get_float("TARGET", self.target_temp_conf) | ||
self.set_temp(temp) | ||
temp = gcmd.get_float("TARGET", None) | ||
if temp is not None and self.control.get_type() == "curve": | ||
raise gcmd.error("Setting Target not supported for control curve") | ||
min_speed = gcmd.get_float("MIN_SPEED", self.min_speed) | ||
max_speed = gcmd.get_float("MAX_SPEED", self.max_speed) | ||
if min_speed > max_speed: | ||
|
@@ -112,6 +120,7 @@ def cmd_SET_TEMPERATURE_FAN_TARGET(self, gcmd): | |
) | ||
self.set_min_speed(min_speed) | ||
self.set_max_speed(max_speed) | ||
self.set_temp(self.target_temp_conf if temp is None else temp) | ||
|
||
def set_temp(self, degrees): | ||
if degrees and (degrees < self.min_temp or degrees > self.max_temp): | ||
|
@@ -167,6 +176,9 @@ def temperature_callback(self, read_time, temp): | |
read_time, self.temperature_fan.get_max_speed() | ||
) | ||
|
||
def get_type(self): | ||
return "watermark" | ||
|
||
|
||
###################################################################### | ||
# Proportional Integral Derivative (PID) control algo | ||
|
@@ -231,6 +243,120 @@ def temperature_callback(self, read_time, temp): | |
if co == bounded_co: | ||
self.prev_temp_integ = temp_integ | ||
|
||
def get_type(self): | ||
return "pid" | ||
|
||
|
||
class ControlCurve: | ||
def __init__(self, temperature_fan, config, controlled_fan=None): | ||
self.temperature_fan = temperature_fan | ||
self.controlled_fan = ( | ||
temperature_fan if controlled_fan is None else controlled_fan | ||
) | ||
self.points = [] | ||
points = config.getlists( | ||
"points", seps=(",", "\n"), parser=float, count=2 | ||
) | ||
for temp, pwm in points: | ||
current_point = [temp, pwm] | ||
if current_point is None: | ||
continue | ||
if len(current_point) != 2: | ||
raise temperature_fan.printer.config_error( | ||
"Point needs to have exactly one temperature and one speed " | ||
"value." | ||
) | ||
if current_point[0] > temperature_fan.target_temp: | ||
raise temperature_fan.printer.config_error( | ||
"Temperature in point can not exceed target temperature." | ||
) | ||
if current_point[0] < temperature_fan.min_temp: | ||
raise temperature_fan.printer.config_error( | ||
"Temperature in point can not fall below min_temp." | ||
) | ||
if current_point[1] > temperature_fan.get_max_speed(): | ||
raise temperature_fan.printer.config_error( | ||
"Speed in point can not exceed max_speed." | ||
) | ||
if current_point[1] < temperature_fan.get_min_speed(): | ||
raise temperature_fan.printer.config_error( | ||
"Speed in point can not fall below min_speed." | ||
) | ||
self.points.append(current_point) | ||
self.points.append( | ||
[temperature_fan.target_temp, temperature_fan.get_max_speed()] | ||
) | ||
if len(self.points) < 2: | ||
raise temperature_fan.printer.config_error( | ||
"At least two points need to be defined for curve in " | ||
"temperature_fan." | ||
) | ||
self.points.sort(key=lambda p: p[0]) | ||
last_point = [temperature_fan.min_temp, temperature_fan.get_min_speed()] | ||
for point in self.points: | ||
if point[1] < last_point[1]: | ||
raise temperature_fan.printer.config_error( | ||
"Points with higher temperatures have to have higher or " | ||
"equal speed than points with lower temperatures." | ||
) | ||
last_point = point | ||
self.cooling_hysteresis = config.getfloat("cooling_hysteresis", 0.0) | ||
self.heating_hysteresis = config.getfloat("heating_hysteresis", 0.0) | ||
self.smooth_readings = config.getint("smooth_readings", 10, minval=1) | ||
self.stored_temps = [] | ||
for i in range(self.smooth_readings): | ||
self.stored_temps.append(0.0) | ||
self.last_temp = 0.0 | ||
|
||
def temperature_callback(self, read_time, temp): | ||
current_temp, target_temp = self.temperature_fan.get_temp(read_time) | ||
temp = self.smooth_temps(temp) | ||
if temp >= target_temp: | ||
self.temperature_fan.set_speed( | ||
read_time, self.temperature_fan.get_max_speed() | ||
) | ||
return | ||
below = [ | ||
self.temperature_fan.min_temp, | ||
self.temperature_fan.get_min_speed(), | ||
] | ||
above = [ | ||
self.temperature_fan.max_temp, | ||
self.temperature_fan.get_max_speed(), | ||
] | ||
for config_temp in self.points: | ||
if config_temp[0] < temp: | ||
below = config_temp | ||
else: | ||
above = config_temp | ||
break | ||
self.controlled_fan.set_speed( | ||
read_time, self.interpolate(below, above, temp) | ||
) | ||
|
||
def interpolate(self, below, above, temp): | ||
return ( | ||
(below[1] * (above[0] - temp)) + (above[1] * (temp - below[0])) | ||
) / (above[0] - below[0]) | ||
|
||
def smooth_temps(self, current_temp): | ||
if ( | ||
self.last_temp - self.cooling_hysteresis | ||
<= current_temp | ||
<= self.last_temp + self.heating_hysteresis | ||
): | ||
temp = self.last_temp | ||
else: | ||
temp = current_temp | ||
self.last_temp = temp | ||
for i in range(1, len(self.stored_temps)): | ||
self.stored_temps[i] = self.stored_temps[i - 1] | ||
self.stored_temps[0] = temp | ||
return statistics.median(self.stored_temps) | ||
|
||
def get_type(self): | ||
return "curve" | ||
|
||
|
||
def load_config_prefix(config): | ||
return TemperatureFan(config) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
# Config for extruder testing | ||
[stepper_x] | ||
step_pin: PF0 | ||
dir_pin: PF1 | ||
enable_pin: !PD7 | ||
microsteps: 16 | ||
rotation_distance: 40 | ||
endstop_pin: ^PE5 | ||
position_endstop: 0 | ||
position_max: 200 | ||
homing_speed: 50 | ||
|
||
[stepper_y] | ||
step_pin: PF6 | ||
dir_pin: !PF7 | ||
enable_pin: !PF2 | ||
microsteps: 16 | ||
rotation_distance: 40 | ||
endstop_pin: ^PJ1 | ||
position_endstop: 0 | ||
position_max: 200 | ||
homing_speed: 50 | ||
|
||
[stepper_z] | ||
step_pin: PL3 | ||
dir_pin: PL1 | ||
enable_pin: !PK0 | ||
microsteps: 16 | ||
rotation_distance: 8 | ||
endstop_pin: ^PD3 | ||
position_endstop: 0.5 | ||
position_max: 200 | ||
|
||
[extruder] | ||
step_pin: PA4 | ||
dir_pin: PA6 | ||
enable_pin: !PA2 | ||
microsteps: 16 | ||
rotation_distance: 33.5 | ||
nozzle_diameter: 0.500 | ||
filament_diameter: 3.500 | ||
heater_pin: PB4 | ||
sensor_type: EPCOS 100K B57560G104F | ||
sensor_pin: PK5 | ||
control: pid | ||
pid_Kp: 22.2 | ||
pid_Ki: 1.08 | ||
pid_Kd: 114 | ||
min_temp: 0 | ||
max_temp: 210 | ||
|
||
[temperature_fan my_temp_fan] | ||
pin: PB5 | ||
sensor_type: EPCOS 100K B57560G104F | ||
sensor_pin: PK6 | ||
target_temp: 65.0 | ||
control: curve | ||
points: | ||
50.0, 0.0 | ||
55.0, 0.5 | ||
60.0, 1.0 | ||
smooth_readings: 100 | ||
cooling_hysteresis: 5.0 | ||
heating_hysteresis: 0.0 | ||
min_temp: 0 | ||
max_temp: 200 | ||
min_speed: 0.0 | ||
max_speed: 1.0 | ||
|
||
[mcu] | ||
serial: /dev/ttyACM0 | ||
|
||
[printer] | ||
kinematics: cartesian | ||
max_velocity: 300 | ||
max_accel: 3000 | ||
max_z_velocity: 5 | ||
max_z_accel: 100 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# Pid hot modify tests | ||
DICTIONARY atmega2560.dict | ||
CONFIG curve_control.cfg | ||
|
||
# Extrude only | ||
G1 E5 | ||
G1 E-2 | ||
G1 E7 | ||
|
||
# Home and extrusion moves | ||
G28 | ||
G1 X20 Y20 Z1 | ||
G1 X25 Y25 E7.5 |