2026-10-10 14:24:37 +02:00
|
|
|
# iHeater chamber heater: MCU, heater, sensors and M141/M191
|
|
|
|
|
|
2026-10-10 13:45:11 +02:00
|
|
|
#========================================================
|
|
|
|
|
#=====================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
|