#======================================================== #=====================iHeater cfg======================== #======================================================== #=====================iHeater pins======================= [board_pins iHeater_Aliases] mcu: iHeater aliases: TH0=PA3, TH1=PA0, TH2=PB1, HEATER=PA1, FAN=PA2, MODE=PA4, LED1=PA7, LED2=PA6, LED3=PA5, USART1_TX=PA9, USART1_RX=PA10 #=====================iHeater mcu======================= # [mcu] # serial: /dev/serial/by-id/usb-Klipper_stm32f042x6_0C0018000D53304347373020-if00 [mcu iHeater] #second MCU #is_non_critical: true: true # flag this as non critical serial: /dev/serial/by-id/usb-Klipper_stm32f042x6_0C0020000E43304754393320-if00 restart_method: command [temperature_sensor iHeater] sensor_type: temperature_mcu sensor_mcu: iHeater #============================================================ #======================= iHeater CONFIG ====================== #============================================================ # Make sure to perform PID calibration for the chamber heater: #🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥 # PID_CALIBRATE HEATER=iHeater_H TARGET=70 #🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥 # This is essential for accurate and stable chamber heating. # Without PID tuning, the temperature may overshoot or take too long to stabilize! #=========================================================== [heater_generic iHeater_H] heater_pin: iHeater:HEATER max_power: 1 sensor_type: NTC 100K MGB18-104F39050L32 sensor_pin: iHeater:TH0 pwm_cycle_time: 0.3 min_temp: 0 max_temp: 120 control: pid # pid_Kp: 37.948 # pid_Ki: 2.040 # pid_Kd: 176.497 pid_Kp: 26.656 pid_Ki: 0.96 pid_Kd: 184.971 [verify_heater iHeater_H] max_error: 240 check_gain_time: 1800 heating_gain: 1 [heater_fan chamber_heater_fan] pin: iHeater:FAN heater: iHeater_H heater_temp: 50.0 fan_speed: 1.0 shutdown_speed: 0 kick_start_time: 0.5 [temperature_sensor iHeater_S] sensor_pin: iHeater:TH1 sensor_type: NTC 100K MGB18-104F39050L32 min_temp: 0 max_temp: 140 # [temperature_sensor iHeater_Chamber_ext] # sensor_pin: iHeater:TH2 # sensor_type: NTC 100K MGB18-104F39050L32 # min_temp: 0 # max_temp: 140 [gcode_button mode_button] pin: iHeater:MODE # debounce_delay: 50 press_gcode: iHEATER_OFF # [output_pin led_1] # pin: iHeater:LED1 # [output_pin led_2] # pin: iHeater:LED2 # [output_pin led_3] # pin: iHeater:LED3 ####################################################### ##########################SETUP######################## ####################################################### [gcode_macro CHAMBER_VARS] variable_chamber_target: 0 # target chamber temperature, °C variable_start_offset: 10 # printing can start when chamber reaches (target - offset) variable_delta_temp: 10 # delta between chamber and heater, °C variable_min_heater_temp: 50 # minimum heater temperature (for cooling), °C variable_max_heater_temp: 100 # maximum heater temperature, °C variable_control_interval: 1.0 # interval for control function call, seconds variable_air_min_delta: 0.5 # minimum difference between desired and current chamber temperature (heater temp = desired + delta_temp), °C variable_air_max_delta: 5.0 # maximum difference between desired and current chamber temperature (heater temp = max_heater_temp), °C gcode: ##########################MACRO######################## [gcode_macro M141] #rename_existing: M141.1 gcode: # Get desired chamber temperature: # - if parameter S is passed, use it # - otherwise use chamber_target from CHAMBER_VARS {% set chamber_target = params.S|default(printer["gcode_macro CHAMBER_VARS"].chamber_target)|int %} # Get delta temperature: # - if parameter D is passed, use it # - otherwise use delta_temp from CHAMBER_VARS {% set delta_temp = params.D|default(printer["gcode_macro CHAMBER_VARS"].delta_temp)|int %} # Get maximum heater temperature: # - if parameter H is passed, use it # - otherwise use max_heater_temp from CHAMBER_VARS {% set max_heater_temp = params.H|default(printer["gcode_macro CHAMBER_VARS"].max_heater_temp)|int %} # Get interval for control function call {% set control_interval = printer["gcode_macro CHAMBER_VARS"].control_interval|float %} RESPOND prefix="M141" msg="S{chamber_target} D{delta_temp} H{max_heater_temp}" # Save parameters to CHAMBER_VARS for delayed_gcode SET_GCODE_VARIABLE MACRO=CHAMBER_VARS VARIABLE=chamber_target VALUE={chamber_target} SET_GCODE_VARIABLE MACRO=CHAMBER_VARS VARIABLE=delta_temp VALUE={delta_temp} SET_GCODE_VARIABLE MACRO=CHAMBER_VARS VARIABLE=max_heater_temp VALUE={max_heater_temp} # Initial calculation iHEATER_CONTROL # Start temperature control macro UPDATE_DELAYED_GCODE ID=_iHEATER_CONTROL DURATION={control_interval} [gcode_macro M191] #rename_existing: M191.1 gcode: # Get desired chamber temperature: # - if parameter S is passed, use it # - otherwise use chamber_target from CHAMBER_VARS {% set chamber_target = params.S|default(printer["gcode_macro CHAMBER_VARS"].chamber_target)|int %} # Get delta temperature: # - if parameter D is passed, use it # - otherwise use delta_temp from CHAMBER_VARS {% set delta_temp = params.D|default(printer["gcode_macro CHAMBER_VARS"].delta_temp)|int %} # Get maximum heater temperature: # - if parameter H is passed, use it # - otherwise use max_heater_temp from CHAMBER_VARS {% set max_heater_temp = params.H|default(printer["gcode_macro CHAMBER_VARS"].max_heater_temp)|int %} # Get minimum chamber temperature for print start: # - if parameter W is passed, use it # - otherwise use start_offset from CHAMBER_VARS {% set start_offset = params.W|default(printer["gcode_macro CHAMBER_VARS"].start_offset)|int %} RESPOND prefix="M191" msg="S{chamber_target} D{delta_temp} H{max_heater_temp} W{start_offset}" M141 S{chamber_target} D{delta_temp} H{max_heater_temp} # Save parameter to CHAMBER_VARS SET_GCODE_VARIABLE MACRO=CHAMBER_VARS VARIABLE=start_offset VALUE={start_offset} # Wait until chamber reaches the target temperature TEMPERATURE_WAIT SENSOR="temperature_sensor iHeater_S" MINIMUM={chamber_target - start_offset} RESPOND prefix="M191" msg="Chamber reached {chamber_target - start_offset}°C" [gcode_macro iHEATER_ON] description: Alternative to M141 & M191 with alternative parameter names gcode: {% set s = params.CHAMBER_TEMP|default(printer["gcode_macro CHAMBER_VARS"].chamber_target)|int %} {% set d = params.DELTA|default(printer["gcode_macro CHAMBER_VARS"].delta_temp)|int %} {% set h = params.HEATER_MAX|default(printer["gcode_macro CHAMBER_VARS"].max_heater_temp)|int %} {% set w = params.CHAMBER_AWAIT_TEMP|default(printer["gcode_macro CHAMBER_VARS"].start_offset)|int %} {% if w > 0 %} # If w is greater than 0, use M191 with wait for minimum temperature M191 S{s} D{d} H{h} W{s - w} {% else %} # Otherwise use M141 without waiting M141 S{s} D{d} H{h} {% endif %} [gcode_macro iHEATER_OFF] gcode: SET_GCODE_VARIABLE MACRO=CHAMBER_VARS VARIABLE=chamber_target VALUE=0 # SET_GCODE_VARIABLE MACRO=CHAMBER_VARS VARIABLE=start_offset VALUE=0 UPDATE_DELAYED_GCODE ID=_iHEATER_CONTROL DURATION=1 RESPOND prefix="iHeater_off" msg="Heating disabled." [gcode_macro iHEATER_CONTROL] gcode: # Target chamber temperature {% set target_chamber_temp = printer["gcode_macro CHAMBER_VARS"].chamber_target|float %} # Delta - how much the heater temp exceeds chamber temp {% set delta_temp = printer["gcode_macro CHAMBER_VARS"].delta_temp|float %} # Minimum heater temperature {% set min_heater_temp = printer["gcode_macro CHAMBER_VARS"].min_heater_temp|float %} # Maximum heater temperature {% set max_heater_temp = printer["gcode_macro CHAMBER_VARS"].max_heater_temp|float %} # Current heater temperature {% set heater_temp = printer["heater_generic iHeater_H"].temperature|float %} # Current chamber air temperature (from sensor) {% set chamber_temp = printer["temperature_sensor iHeater_S"].temperature|float %} # Control function call interval {% set control_interval = printer["gcode_macro CHAMBER_VARS"].control_interval|float %} # Minimum difference between desired and current chamber temperature {% set air_min_delta = printer["gcode_macro CHAMBER_VARS"].air_min_delta|float %} # Maximum difference between desired and current chamber temperature {% set air_max_delta = printer["gcode_macro CHAMBER_VARS"].air_max_delta|float %} # Variable to set target heater temperature {% set target_heater_temp = 0.0 %} # RESPOND prefix="iHeater_control" msg="Heater control..." {% if target_chamber_temp > 0 %} # Heating is active {% set temp_diff = target_chamber_temp - chamber_temp %} # MapToRange {% set value = temp_diff %} {% set inputLowerBound = air_min_delta %} {% set inputUpperBound = air_max_delta %} {% set outputLowerBound = delta_temp %} {% set outputUpperBound = [delta_temp, max_heater_temp - target_chamber_temp]|max %} {% set slope = (outputUpperBound - outputLowerBound) / (inputUpperBound - inputLowerBound) %} {% set clamped = ([inputLowerBound, value, inputUpperBound]|sort)[1] %} {% set adjustment = outputLowerBound + slope * (clamped - inputLowerBound) %} {% set target_heater_temp = (target_chamber_temp + adjustment)|round(2) %} {% elif heater_temp > min_heater_temp %} # Heating is off, but heater hasn't cooled down yet RESPOND prefix="iHeater_control" msg="Cooling down the heater..." {% else %} # Heater cooled down - end macro RESPOND prefix="iHeater_control" msg="Heater cooldown complete." {% set control_interval = 0.0 %} {% endif %} SET_HEATER_TEMPERATURE HEATER=iHeater_H TARGET={target_heater_temp} UPDATE_DELAYED_GCODE ID=_iHEATER_CONTROL DURATION={control_interval} [delayed_gcode _iHEATER_CONTROL] gcode: iHEATER_CONTROL