From 8d05a823e96fe43209243591739eca106296fadb Mon Sep 17 00:00:00 2001 From: Ken Sanislo Date: Wed, 24 Feb 2021 16:26:51 -0800 Subject: [PATCH] Chamber Heater PID (#21156) Co-authored-by: Scott Lahteine --- Marlin/Configuration.h | 55 ++- Marlin/Configuration_adv.h | 17 +- Marlin/src/core/language.h | 2 +- .../src/feature/leds/printer_event_leds.cpp | 13 + Marlin/src/feature/leds/printer_event_leds.h | 7 +- Marlin/src/gcode/config/M309.cpp | 48 +++ Marlin/src/gcode/gcode.cpp | 4 + Marlin/src/gcode/gcode.h | 3 + Marlin/src/gcode/temp/M303.cpp | 37 +- Marlin/src/inc/Conditionals_post.h | 21 +- Marlin/src/inc/SanityCheck.h | 7 + Marlin/src/lcd/menu/menu_advanced.cpp | 79 ++-- Marlin/src/module/settings.cpp | 61 ++- Marlin/src/module/temperature.cpp | 380 ++++++++++-------- Marlin/src/module/temperature.h | 21 +- 15 files changed, 519 insertions(+), 236 deletions(-) create mode 100644 Marlin/src/gcode/config/M309.cpp diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h index 3234a73eda..1c98168236 100644 --- a/Marlin/Configuration.h +++ b/Marlin/Configuration.h @@ -447,6 +447,10 @@ #define TEMP_BED_WINDOW 1 // (°C) Temperature proximity for the "temperature reached" timer #define TEMP_BED_HYSTERESIS 3 // (°C) Temperature proximity considered "close enough" to the target +#define TEMP_CHAMBER_RESIDENCY_TIME 10 // (seconds) Time to wait for chamber to "settle" in M191 +#define TEMP_CHAMBER_WINDOW 1 // (°C) Temperature proximity for the "temperature reached" timer +#define TEMP_CHAMBER_HYSTERESIS 3 // (°C) Temperature proximity considered "close enough" to the target + // Below this temperature the heater will be switched off // because it probably indicates a broken thermistor wire. #define HEATER_0_MINTEMP 5 @@ -458,6 +462,7 @@ #define HEATER_6_MINTEMP 5 #define HEATER_7_MINTEMP 5 #define BED_MINTEMP 5 +#define CHAMBER_MINTEMP 5 // Above this temperature the heater will be switched off. // This can protect components from overheating, but NOT from shorts and failures. @@ -471,6 +476,7 @@ #define HEATER_6_MAXTEMP 275 #define HEATER_7_MAXTEMP 275 #define BED_MAXTEMP 150 +#define CHAMBER_MAXTEMP 60 //=========================================================================== //============================= PID Settings ================================ @@ -544,7 +550,52 @@ // FIND YOUR OWN: "M303 E-1 C8 S90" to run autotune on the bed at 90 degreesC for 8 cycles. #endif // PIDTEMPBED -#if EITHER(PIDTEMP, PIDTEMPBED) +//=========================================================================== +//==================== PID > Chamber Temperature Control ==================== +//=========================================================================== + +/** + * PID Chamber Heating + * + * If this option is enabled set PID constants below. + * If this option is disabled, bang-bang will be used and CHAMBER_LIMIT_SWITCHING will enable + * hysteresis. + * + * The PID frequency will be the same as the extruder PWM. + * If PID_dT is the default, and correct for the hardware/configuration, that means 7.689Hz, + * which is fine for driving a square wave into a resistive load and does not significantly + * impact FET heating. This also works fine on a Fotek SSR-10DA Solid State Relay into a 200W + * heater. If your configuration is significantly different than this and you don't understand + * the issues involved, don't use chamber PID until someone else verifies that your hardware works. + */ +//#define PIDTEMPCHAMBER + +//#define CHAMBER_LIMIT_SWITCHING + +/** + * Max Chamber Power + * Applies to all forms of chamber control (PID, bang-bang, and bang-bang with hysteresis). + * When set to any value below 255, enables a form of PWM to the chamber heater that acts like a divider + * so don't use it unless you are OK with PWM on your heater. (See the comment on enabling PIDTEMPCHAMBER) + */ +#define MAX_CHAMBER_POWER 255 // limits duty cycle to chamber heater; 255=full current + +#if ENABLED(PIDTEMPCHAMBER) + #define MIN_CHAMBER_POWER 0 + //#define PID_CHAMBER_DEBUG // Sends debug data to the serial port. + + // Lasko "MyHeat Personal Heater" (200w) modified with a Fotek SSR-10DA to control only the heating element + // and placed inside the small Creality printer enclosure tent. + // + #define DEFAULT_chamberKp 37.04 + #define DEFAULT_chamberKi 1.40 + #define DEFAULT_chamberKd 655.17 + // M309 P37.04 I1.04 D655.17 + + // FIND YOUR OWN: "M303 E-2 C8 S50" to run autotune on the chamber at 50 degreesC for 8 cycles. +#endif // PIDTEMPCHAMBER + +#if ANY(PIDTEMP, PIDTEMPBED, PIDTEMPCHAMBER) //#define PID_DEBUG // Sends debug data to the serial port. Use 'M303 D' to toggle activation. //#define PID_OPENLOOP // Puts PID in open loop. M104/M140 sets the output power from 0 to PID_MAX //#define SLOW_PWM_HEATERS // PWM with very low frequency (roughly 0.125Hz=8s) and minimum state time of approximately 1s useful for heaters driven by a relay @@ -1624,11 +1675,13 @@ #define PREHEAT_1_LABEL "PLA" #define PREHEAT_1_TEMP_HOTEND 180 #define PREHEAT_1_TEMP_BED 70 +#define PREHEAT_1_TEMP_CHAMBER 35 #define PREHEAT_1_FAN_SPEED 0 // Value from 0 to 255 #define PREHEAT_2_LABEL "ABS" #define PREHEAT_2_TEMP_HOTEND 240 #define PREHEAT_2_TEMP_BED 110 +#define PREHEAT_2_TEMP_CHAMBER 35 #define PREHEAT_2_FAN_SPEED 0 // Value from 0 to 255 /** diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index 987ac293b2..50f17d1b9e 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -143,12 +143,19 @@ // // Heated Chamber options // +#if DISABLED(PIDTEMPCHAMBER) + #define CHAMBER_CHECK_INTERVAL 5000 // (ms) Interval between checks in bang-bang control + #if ENABLED(CHAMBER_LIMIT_SWITCHING) + #define CHAMBER_HYSTERESIS 2 // (°C) Only set the relevant heater state when ABS(T-target) > CHAMBER_HYSTERESIS + #endif +#endif + #if TEMP_SENSOR_CHAMBER - #define CHAMBER_MINTEMP 5 - #define CHAMBER_MAXTEMP 60 - #define TEMP_CHAMBER_HYSTERESIS 1 // (°C) Temperature proximity considered "close enough" to the target - //#define CHAMBER_LIMIT_SWITCHING - //#define HEATER_CHAMBER_PIN 44 // Chamber heater on/off pin + // Make sure you define where your heater is connected, the following works on a BTT SKR 1.4 Turbo + // using the secondary tool heater output. (FAN1 by default). + //#define FAN1_PIN -1 // Remove the fan signal on pin P2_04 (SKR 1.4 Turbo specific) + //#define HEATER_CHAMBER_PIN P2_04 // Chamber heater on/off pin (HE1 connector on SKR 1.4 Turbo) + //#define HEATER_CHAMBER_INVERTING false //#define CHAMBER_FAN // Enable a fan on the chamber diff --git a/Marlin/src/core/language.h b/Marlin/src/core/language.h index 923ad903cb..6024e9be3e 100644 --- a/Marlin/src/core/language.h +++ b/Marlin/src/core/language.h @@ -221,7 +221,7 @@ // temperature.cpp strings #define STR_PID_AUTOTUNE_START "PID Autotune start" -#define STR_PID_BAD_EXTRUDER_NUM "PID Autotune failed! Bad extruder number" +#define STR_PID_BAD_HEATER_ID "PID Autotune failed! Bad heater id" #define STR_PID_TEMP_TOO_HIGH "PID Autotune failed! Temperature too high" #define STR_PID_TIMEOUT "PID Autotune failed! timeout" #define STR_BIAS " bias: " diff --git a/Marlin/src/feature/leds/printer_event_leds.cpp b/Marlin/src/feature/leds/printer_event_leds.cpp index 3a6b91a258..32c6862704 100644 --- a/Marlin/src/feature/leds/printer_event_leds.cpp +++ b/Marlin/src/feature/leds/printer_event_leds.cpp @@ -77,6 +77,19 @@ PrinterEventLEDs printerEventLEDs; pel_set_rgb(red, 0, 255); } } + +#endif + +#if HAS_HEATED_CHAMBER + + void PrinterEventLEDs::onChamberHeating(const float &start, const float ¤t, const float &target) { + const uint8_t green = pel_intensity(start, current, target); + if (green != old_intensity) { + old_intensity = green; + pel_set_rgb(255, green, 255); + } + } + #endif #endif // PRINTER_EVENT_LEDS diff --git a/Marlin/src/feature/leds/printer_event_leds.h b/Marlin/src/feature/leds/printer_event_leds.h index 86ec292aa3..668c9c969b 100644 --- a/Marlin/src/feature/leds/printer_event_leds.h +++ b/Marlin/src/feature/leds/printer_event_leds.h @@ -55,7 +55,12 @@ public: static void onBedHeating(const float &start, const float ¤t, const float &target); #endif - #if HAS_TEMP_HOTEND || HAS_HEATED_BED + #if HAS_HEATED_CHAMBER + static inline LEDColor onChamberHeatingStart() { old_intensity = 127; return leds.get_color(); } + static void onChamberHeating(const float &start, const float ¤t, const float &target); + #endif + + #if HAS_TEMP_HOTEND || HAS_HEATED_BED || HAS_HEATED_CHAMBER static inline void onHeatingDone() { leds.set_white(); } static inline void onPidTuningDone(LEDColor c) { leds.set_color(c); } #endif diff --git a/Marlin/src/gcode/config/M309.cpp b/Marlin/src/gcode/config/M309.cpp new file mode 100644 index 0000000000..2247481b25 --- /dev/null +++ b/Marlin/src/gcode/config/M309.cpp @@ -0,0 +1,48 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * + * Based on Sprinter and grbl. + * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "../../inc/MarlinConfig.h" + +#if ENABLED(PIDTEMPCHAMBER) + +#include "../gcode.h" +#include "../../module/temperature.h" + +/** + * M309 - Set and/or Report the current Chamber PID values + * + * P - Set the P value + * I - Set the I value + * D - Set the D value + */ +void GcodeSuite::M309() { + if (parser.seen('P')) thermalManager.temp_chamber.pid.Kp = parser.value_float(); + if (parser.seen('I')) thermalManager.temp_chamber.pid.Ki = scalePID_i(parser.value_float()); + if (parser.seen('D')) thermalManager.temp_chamber.pid.Kd = scalePID_d(parser.value_float()); + + SERIAL_ECHO_START(); + SERIAL_ECHOLNPAIR(" p:", thermalManager.temp_chamber.pid.Kp, + " i:", unscalePID_i(thermalManager.temp_chamber.pid.Ki), + " d:", unscalePID_d(thermalManager.temp_chamber.pid.Kd)); +} + +#endif // PIDTEMPCHAMBER diff --git a/Marlin/src/gcode/gcode.cpp b/Marlin/src/gcode/gcode.cpp index 77ac1fbff8..a410ad90a4 100644 --- a/Marlin/src/gcode/gcode.cpp +++ b/Marlin/src/gcode/gcode.cpp @@ -682,6 +682,10 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) { case 304: M304(); break; // M304: Set bed PID parameters #endif + #if ENABLED(PIDTEMPCHAMBER) + case 309: M309(); break; // M309: Set chamber PID parameters + #endif + #if ENABLED(PHOTO_GCODE) case 240: M240(); break; // M240: Trigger a camera #endif diff --git a/Marlin/src/gcode/gcode.h b/Marlin/src/gcode/gcode.h index 7fd8d6904a..123b648f90 100644 --- a/Marlin/src/gcode/gcode.h +++ b/Marlin/src/gcode/gcode.h @@ -197,6 +197,7 @@ * M303 - PID relay autotune S sets the target temperature. Default 150C. (Requires PIDTEMP) * M304 - Set bed PID parameters P I and D. (Requires PIDTEMPBED) * M305 - Set user thermistor parameters R T and P. (Requires TEMP_SENSOR_x 1000) + * M309 - Set chamber PID parameters P I and D. (Requires PIDTEMPCHAMBER) * M350 - Set microstepping mode. (Requires digital microstepping pins.) * M351 - Toggle MS1 MS2 pins directly. (Requires digital microstepping pins.) * M355 - Set Case Light on/off and set brightness. (Requires CASE_LIGHT_PIN) @@ -711,6 +712,8 @@ private: TERN_(HAS_USER_THERMISTORS, static void M305()); + TERN_(PIDTEMPCHAMBER, static void M309()); + #if HAS_MICROSTEPS static void M350(); static void M351(); diff --git a/Marlin/src/gcode/temp/M303.cpp b/Marlin/src/gcode/temp/M303.cpp index a066ddc88d..159a52bf26 100644 --- a/Marlin/src/gcode/temp/M303.cpp +++ b/Marlin/src/gcode/temp/M303.cpp @@ -40,19 +40,15 @@ * C Number of times to repeat the procedure. (Minimum: 3, Default: 5) * U Flag to apply the result to the current PID values * - * With PID_DEBUG: + * With PID_DEBUG, PID_BED_DEBUG, or PID_CHAMBER_DEBUG: * D Toggle PID debugging and EXIT without further action. */ -#if ENABLED(PID_DEBUG) - bool pid_debug_flag = 0; -#endif - void GcodeSuite::M303() { - #if ENABLED(PID_DEBUG) + #if ANY(PID_DEBUG, PID_BED_DEBUG, PID_CHAMBER_DEBUG) if (parser.seen('D')) { - pid_debug_flag = !pid_debug_flag; + thermalManager.pid_debug_flag ^= true; SERIAL_ECHO_START(); SERIAL_ECHOPGM("PID Debug "); serialprintln_onoff(pid_debug_flag); @@ -60,25 +56,34 @@ void GcodeSuite::M303() { } #endif - #define SI TERN(PIDTEMPBED, H_BED, H_E0) - #define EI TERN(PIDTEMP, HOTENDS - 1, H_BED) - const heater_id_t e = (heater_id_t)parser.intval('E'); - if (!WITHIN(e, SI, EI)) { - SERIAL_ECHOLNPGM(STR_PID_BAD_EXTRUDER_NUM); - TERN_(EXTENSIBLE_UI, ExtUI::onPidTuning(ExtUI::result_t::PID_BAD_EXTRUDER_NUM)); - return; + const heater_id_t hid = (heater_id_t)parser.intval('E'); + int16_t default_temp; + switch (hid) { + #if ENABLED(PIDTEMP) + case 0 ... HOTENDS - 1: default_temp = PREHEAT_1_TEMP_HOTEND; break; + #endif + #if ENABLED(PIDTEMPBED) + case H_BED: default_temp = PREHEAT_1_TEMP_BED; break; + #endif + #if ENABLED(PIDTEMPCHAMBER) + case H_CHAMBER: default_temp = PREHEAT_1_TEMP_CHAMBER; break; + #endif + default: + SERIAL_ECHOLNPGM(STR_PID_BAD_HEATER_ID); + TERN_(EXTENSIBLE_UI, ExtUI::onPidTuning(ExtUI::result_t::PID_BAD_EXTRUDER_NUM)); + return; } + const int16_t temp = parser.celsiusval('S', default_temp); const int c = parser.intval('C', 5); const bool u = parser.boolval('U'); - const int16_t temp = parser.celsiusval('S', e < 0 ? PREHEAT_1_TEMP_BED : PREHEAT_1_TEMP_HOTEND); #if DISABLED(BUSY_WHILE_HEATING) KEEPALIVE_STATE(NOT_BUSY); #endif LCD_MESSAGEPGM(MSG_PID_AUTOTUNE); - thermalManager.PID_autotune(temp, e, c, u); + thermalManager.PID_autotune(temp, hid, c, u); ui.reset_status(); } diff --git a/Marlin/src/inc/Conditionals_post.h b/Marlin/src/inc/Conditionals_post.h index f8360767d9..585d48de8c 100644 --- a/Marlin/src/inc/Conditionals_post.h +++ b/Marlin/src/inc/Conditionals_post.h @@ -1992,27 +1992,31 @@ #define BED_OVERSHOOT 10 #endif #define BED_MAX_TARGET (BED_MAXTEMP - (BED_OVERSHOOT)) +#else + #undef PIDTEMPBED #endif + #if HAS_HEATED_BED || HAS_TEMP_CHAMBER #define BED_OR_CHAMBER 1 #endif #if HAS_TEMP_HOTEND || BED_OR_CHAMBER || HAS_TEMP_PROBE #define HAS_TEMP_SENSOR 1 #endif + #if HAS_TEMP_CHAMBER && PIN_EXISTS(HEATER_CHAMBER) #define HAS_HEATED_CHAMBER 1 + #ifndef CHAMBER_OVERSHOOT + #define CHAMBER_OVERSHOOT 10 + #endif + #define CHAMBER_MAX_TARGET (CHAMBER_MAXTEMP - (CHAMBER_OVERSHOOT)) +#else + #undef PIDTEMPCHAMBER #endif // PID heating -#if !HAS_HEATED_BED - #undef PIDTEMPBED -#endif -#if EITHER(PIDTEMP, PIDTEMPBED) +#if ANY(PIDTEMP, PIDTEMPBED, PIDTEMPCHAMBER) #define HAS_PID_HEATING 1 #endif -#if BOTH(PIDTEMP, PIDTEMPBED) - #define HAS_PID_FOR_BOTH 1 -#endif // Thermal protection #if BOTH(HAS_HEATED_BED, THERMAL_PROTECTION_BED) @@ -2346,6 +2350,9 @@ * Heated chamber requires settings */ #if HAS_HEATED_CHAMBER + #ifndef MIN_CHAMBER_POWER + #define MIN_CHAMBER_POWER 0 + #endif #ifndef MAX_CHAMBER_POWER #define MAX_CHAMBER_POWER 255 #endif diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h index c1b0290ac1..ba69b4792e 100644 --- a/Marlin/src/inc/SanityCheck.h +++ b/Marlin/src/inc/SanityCheck.h @@ -1203,6 +1203,13 @@ static_assert(Y_MAX_LENGTH >= Y_BED_SIZE, "Movement bounds (Y_MIN_POS, Y_MAX_POS #error "To use BED_LIMIT_SWITCHING you must disable PIDTEMPBED." #endif +/** + * Chamber Heating Options - PID vs Limit Switching + */ +#if BOTH(PIDTEMPCHAMBER, CHAMBER_LIMIT_SWITCHING) + #error "To use CHAMBER_LIMIT_SWITCHING you must disable PIDTEMPCHAMBER." +#endif + /** * Kinematics */ diff --git a/Marlin/src/lcd/menu/menu_advanced.cpp b/Marlin/src/lcd/menu/menu_advanced.cpp index cb7827168b..aab93c3bdd 100644 --- a/Marlin/src/lcd/menu/menu_advanced.cpp +++ b/Marlin/src/lcd/menu/menu_advanced.cpp @@ -177,18 +177,25 @@ void menu_backlash(); #if ENABLED(PIDTEMPBED) int16_t autotune_temp_bed = PREHEAT_1_TEMP_BED; #endif + #if ENABLED(PIDTEMPCHAMBER) + int16_t autotune_temp_chamber = PREHEAT_1_TEMP_CHAMBER; + #endif #include "../../gcode/queue.h" - void _lcd_autotune(const int16_t e) { + void _lcd_autotune(const heater_id_t hid) { char cmd[30]; - sprintf_P(cmd, PSTR("M303 U1 E%i S%i"), e, - #if HAS_PID_FOR_BOTH - e < 0 ? autotune_temp_bed : autotune_temp[e] - #else - TERN(PIDTEMPBED, autotune_temp_bed, autotune_temp[e]) + int16_t tune_temp; + switch (hid) { + #if ENABLED(PIDTEMPBED) + case H_BED: tune_temp = autotune_temp_bed; break; #endif - ); + #if ENABLED(PIDTEMPCHAMBER) + case H_CHAMBER: tune_temp = autotune_temp_chamber; break; + #endif + default: tune_temp = autotune_temp[hid]; break; + } + sprintf_P(cmd, PSTR("M303 U1 E%i S%i"), hid, tune_temp); queue.inject(cmd); ui.return_to_status(); } @@ -225,7 +232,7 @@ void menu_backlash(); #if ENABLED(PID_AUTOTUNE_MENU) #define DEFINE_PIDTEMP_FUNCS(N) \ _DEFINE_PIDTEMP_BASE_FUNCS(N); \ - void lcd_autotune_callback_E##N() { _lcd_autotune(N); } + void lcd_autotune_callback_E##N() { _lcd_autotune(heater_id_t(N)); } #else #define DEFINE_PIDTEMP_FUNCS(N) _DEFINE_PIDTEMP_BASE_FUNCS(N); #endif @@ -269,56 +276,70 @@ void menu_backlash(); // #if ENABLED(PID_EDIT_MENU) - #define __PID_BASE_MENU_ITEMS(N) \ - raw_Ki = unscalePID_i(TERN(PID_BED_MENU_SECTION, thermalManager.temp_bed.pid.Ki, PID_PARAM(Ki, N))); \ - raw_Kd = unscalePID_d(TERN(PID_BED_MENU_SECTION, thermalManager.temp_bed.pid.Kd, PID_PARAM(Kd, N))); \ - EDIT_ITEM_FAST_N(float41sign, N, MSG_PID_P_E, &TERN(PID_BED_MENU_SECTION, thermalManager.temp_bed.pid.Kp, PID_PARAM(Kp, N)), 1, 9990); \ + #define _PID_EDIT_ITEMS_TMPL(N,T) \ + raw_Ki = unscalePID_i(T.pid.Ki); \ + raw_Kd = unscalePID_d(T.pid.Kd); \ + EDIT_ITEM_FAST_N(float41sign, N, MSG_PID_P_E, &T.pid.Kp, 1, 9990); \ + EDIT_ITEM_FAST_N(float52sign, N, MSG_PID_I_E, &raw_Ki, 0.01f, 9990, []{ copy_and_scalePID_i(N); }); \ + EDIT_ITEM_FAST_N(float41sign, N, MSG_PID_D_E, &raw_Kd, 1, 9990, []{ copy_and_scalePID_d(N); }) + + #define __PID_HOTEND_MENU_ITEMS(N) \ + raw_Ki = unscalePID_i(PID_PARAM(Ki, N)); \ + raw_Kd = unscalePID_d(PID_PARAM(Kd, N)); \ + EDIT_ITEM_FAST_N(float41sign, N, MSG_PID_P_E, &PID_PARAM(Kp, N), 1, 9990); \ EDIT_ITEM_FAST_N(float52sign, N, MSG_PID_I_E, &raw_Ki, 0.01f, 9990, []{ copy_and_scalePID_i(N); }); \ EDIT_ITEM_FAST_N(float41sign, N, MSG_PID_D_E, &raw_Kd, 1, 9990, []{ copy_and_scalePID_d(N); }) #if ENABLED(PID_EXTRUSION_SCALING) - #define _PID_BASE_MENU_ITEMS(N) \ - __PID_BASE_MENU_ITEMS(N); \ + #define _PID_HOTEND_MENU_ITEMS(N) \ + __PID_HOTEND_MENU_ITEMS(N); \ EDIT_ITEM_N(float4, N, MSG_PID_C_E, &PID_PARAM(Kc, N), 1, 9990) #else - #define _PID_BASE_MENU_ITEMS(N) __PID_BASE_MENU_ITEMS(N) + #define _PID_HOTEND_MENU_ITEMS(N) __PID_HOTEND_MENU_ITEMS(N) #endif #if ENABLED(PID_FAN_SCALING) - #define _PID_EDIT_MENU_ITEMS(N) \ - _PID_BASE_MENU_ITEMS(N); \ + #define _HOTEND_PID_EDIT_MENU_ITEMS(N) \ + _PID_HOTEND_MENU_ITEMS(N); \ EDIT_ITEM_N(float4, N, MSG_PID_F_E, &PID_PARAM(Kf, N), 1, 9990) #else - #define _PID_EDIT_MENU_ITEMS(N) _PID_BASE_MENU_ITEMS(N) + #define _HOTEND_PID_EDIT_MENU_ITEMS(N) _PID_HOTEND_MENU_ITEMS(N) #endif #else - #define _PID_EDIT_MENU_ITEMS(N) NOOP + #define _HOTEND_PID_EDIT_MENU_ITEMS(N) NOOP #endif #if ENABLED(PID_AUTOTUNE_MENU) - #define PID_EDIT_MENU_ITEMS(N) \ - _PID_EDIT_MENU_ITEMS(N); \ - EDIT_ITEM_FAST_N(int3, N, MSG_PID_AUTOTUNE_E, &autotune_temp[N], 150, thermalManager.heater_maxtemp[N] - HOTEND_OVERSHOOT, []{ _lcd_autotune(MenuItemBase::itemIndex); }); + #define HOTEND_PID_EDIT_MENU_ITEMS(N) \ + _HOTEND_PID_EDIT_MENU_ITEMS(N); \ + EDIT_ITEM_FAST_N(int3, N, MSG_PID_AUTOTUNE_E, &autotune_temp[N], 150, thermalManager.heater_maxtemp[N] - HOTEND_OVERSHOOT, []{ _lcd_autotune(heater_id_t(MenuItemBase::itemIndex)); }); #else - #define PID_EDIT_MENU_ITEMS(N) _PID_EDIT_MENU_ITEMS(N); + #define HOTEND_PID_EDIT_MENU_ITEMS(N) _HOTEND_PID_EDIT_MENU_ITEMS(N); #endif - PID_EDIT_MENU_ITEMS(0); + HOTEND_PID_EDIT_MENU_ITEMS(0); #if ENABLED(PID_PARAMS_PER_HOTEND) - REPEAT_S(1, HOTENDS, PID_EDIT_MENU_ITEMS) + REPEAT_S(1, HOTENDS, HOTEND_PID_EDIT_MENU_ITEMS) #endif #if ENABLED(PIDTEMPBED) #if ENABLED(PID_EDIT_MENU) - #define PID_BED_MENU_SECTION - __PID_BASE_MENU_ITEMS(-1); - #undef PID_BED_MENU_SECTION + _PID_EDIT_ITEMS_TMPL(H_BED, thermalManager.temp_bed); #endif #if ENABLED(PID_AUTOTUNE_MENU) - EDIT_ITEM_FAST_N(int3, -1, MSG_PID_AUTOTUNE_E, &autotune_temp_bed, PREHEAT_1_TEMP_BED, BED_MAX_TARGET, []{ _lcd_autotune(-1); }); + EDIT_ITEM_FAST_N(int3, H_BED, MSG_PID_AUTOTUNE_E, &autotune_temp_bed, PREHEAT_1_TEMP_BED, BED_MAX_TARGET, []{ _lcd_autotune(H_BED); }); + #endif + #endif + + #if ENABLED(PIDTEMPCHAMBER) + #if ENABLED(PID_EDIT_MENU) + _PID_EDIT_ITEMS_TMPL(H_CHAMBER, thermalManager.temp_chamber); + #endif + #if ENABLED(PID_AUTOTUNE_MENU) + EDIT_ITEM_FAST_N(int3, H_CHAMBER, MSG_PID_AUTOTUNE_E, &autotune_temp_chamber, PREHEAT_1_TEMP_CHAMBER, CHAMBER_MAX_TARGET, []{ _lcd_autotune(H_CHAMBER); }); #endif #endif diff --git a/Marlin/src/module/settings.cpp b/Marlin/src/module/settings.cpp index 7fb378191e..f4b4bf0b8b 100644 --- a/Marlin/src/module/settings.cpp +++ b/Marlin/src/module/settings.cpp @@ -318,6 +318,11 @@ typedef struct SettingsDataStruct { // PID_t bedPID; // M304 PID / M303 E-1 U + // + // PIDTEMPCHAMBER + // + PID_t chamberPID; // M309 PID / M303 E-2 U + // // User-defined Thermistors // @@ -926,6 +931,25 @@ void MarlinSettings::postprocess() { EEPROM_WRITE(bed_pid); } + // + // PIDTEMPCHAMBER + // + { + _FIELD_TEST(chamberPID); + + const PID_t chamber_pid = { + #if DISABLED(PIDTEMPCHAMBER) + NAN, NAN, NAN + #else + // Store the unscaled PID values + thermalManager.temp_chamber.pid.Kp, + unscalePID_i(thermalManager.temp_chamber.pid.Ki), + unscalePID_d(thermalManager.temp_chamber.pid.Kd) + #endif + }; + EEPROM_WRITE(chamber_pid); + } + // // User-defined Thermistors // @@ -1787,6 +1811,22 @@ void MarlinSettings::postprocess() { #endif } + // + // Heated Chamber PID + // + { + PID_t pid; + EEPROM_READ(pid); + #if ENABLED(PIDTEMPCHAMBER) + if (!validating && !isnan(pid.Kp)) { + // Scale PID values since EEPROM values are unscaled + thermalManager.temp_chamber.pid.Kp = pid.Kp; + thermalManager.temp_chamber.pid.Ki = scalePID_i(pid.Ki); + thermalManager.temp_chamber.pid.Kd = scalePID_d(pid.Kd); + } + #endif + } + // // User-defined Thermistors // @@ -2811,6 +2851,16 @@ void MarlinSettings::reset() { thermalManager.temp_bed.pid.Kd = scalePID_d(DEFAULT_bedKd); #endif + // + // Heated Chamber PID + // + + #if ENABLED(PIDTEMPCHAMBER) + thermalManager.temp_chamber.pid.Kp = DEFAULT_chamberKp; + thermalManager.temp_chamber.pid.Ki = scalePID_i(DEFAULT_chamberKi); + thermalManager.temp_chamber.pid.Kd = scalePID_d(DEFAULT_chamberKd); + #endif + // // User-Defined Thermistors // @@ -3386,7 +3436,16 @@ void MarlinSettings::reset() { ); #endif - #endif // PIDTEMP || PIDTEMPBED + #if ENABLED(PIDTEMPCHAMBER) + CONFIG_ECHO_START(); + SERIAL_ECHOLNPAIR( + " M309 P", thermalManager.temp_chamber.pid.Kp + , " I", unscalePID_i(thermalManager.temp_chamber.pid.Ki) + , " D", unscalePID_d(thermalManager.temp_chamber.pid.Kd) + ); + #endif + + #endif // PIDTEMP || PIDTEMPBED || PIDTEMPCHAMBER #if HAS_USER_THERMISTORS CONFIG_ECHO_HEADING("User thermistors:"); diff --git a/Marlin/src/module/temperature.cpp b/Marlin/src/module/temperature.cpp index edd76df3b7..58b0a965c9 100644 --- a/Marlin/src/module/temperature.cpp +++ b/Marlin/src/module/temperature.cpp @@ -371,10 +371,8 @@ const char str_t_thermal_runaway[] PROGMEM = STR_T_THERMAL_RUNAWAY, #ifdef CHAMBER_MAXTEMP int16_t Temperature::maxtemp_raw_CHAMBER = TEMP_SENSOR_CHAMBER_RAW_HI_TEMP; #endif - #if WATCH_CHAMBER - chamber_watch_t Temperature::watch_chamber{0}; - #endif - millis_t Temperature::next_chamber_check_ms; + TERN_(WATCH_CHAMBER, chamber_watch_t Temperature::watch_chamber{0}); + IF_DISABLED(PIDTEMPCHAMBER, millis_t Temperature::next_chamber_check_ms); #endif // HAS_HEATED_CHAMBER #endif // HAS_TEMP_CHAMBER @@ -382,11 +380,6 @@ const char str_t_thermal_runaway[] PROGMEM = STR_T_THERMAL_RUNAWAY, probe_info_t Temperature::temp_probe; // = { 0 } #endif -// Initialized by settings.load() -#if ENABLED(PIDTEMP) - //hotend_pid_t Temperature::pid[HOTENDS]; -#endif - #if ENABLED(PREVENT_COLD_EXTRUSION) bool Temperature::allow_cold_extrude = false; int16_t Temperature::extrude_min_temp = EXTRUDE_MINTEMP; @@ -485,41 +478,44 @@ volatile bool Temperature::raw_temps_ready = false; millis_t next_temp_ms = millis(), t1 = next_temp_ms, t2 = next_temp_ms; long t_high = 0, t_low = 0; - long bias, d; PID_t tune_pid = { 0, 0, 0 }; float maxT = 0, minT = 10000; const bool isbed = (heater_id == H_BED); + const bool ischamber = (heater_id == H_CHAMBER); - #if HAS_PID_FOR_BOTH - #define GHV(B,H) (isbed ? (B) : (H)) - #define SHV(B,H) do{ if (isbed) temp_bed.soft_pwm_amount = B; else temp_hotend[heater_id].soft_pwm_amount = H; }while(0) - #define ONHEATINGSTART() (isbed ? printerEventLEDs.onBedHeatingStart() : printerEventLEDs.onHotendHeatingStart()) - #define ONHEATING(S,C,T) (isbed ? printerEventLEDs.onBedHeating(S,C,T) : printerEventLEDs.onHotendHeating(S,C,T)) - #elif ENABLED(PIDTEMPBED) - #define GHV(B,H) B - #define SHV(B,H) (temp_bed.soft_pwm_amount = B) - #define ONHEATINGSTART() printerEventLEDs.onBedHeatingStart() - #define ONHEATING(S,C,T) printerEventLEDs.onBedHeating(S,C,T) + #if ENABLED(PIDTEMPCHAMBER) + #define C_TERN(T,A,B) ((T) ? (A) : (B)) #else - #define GHV(B,H) H - #define SHV(B,H) (temp_hotend[heater_id].soft_pwm_amount = H) - #define ONHEATINGSTART() printerEventLEDs.onHotendHeatingStart() - #define ONHEATING(S,C,T) printerEventLEDs.onHotendHeating(S,C,T) + #define C_TERN(T,A,B) (B) #endif - #define WATCH_PID BOTH(WATCH_BED, PIDTEMPBED) || BOTH(WATCH_HOTENDS, PIDTEMP) + #if ENABLED(PIDTEMPBED) + #define B_TERN(T,A,B) ((T) ? (A) : (B)) + #else + #define B_TERN(T,A,B) (B) + #endif + #define GHV(C,B,H) C_TERN(ischamber, C, B_TERN(isbed, B, H)) + #define SHV(V) C_TERN(ischamber, temp_chamber.soft_pwm_amount = V, B_TERN(isbed, temp_bed.soft_pwm_amount = V, temp_hotend[heater_id].soft_pwm_amount = V)) + #define ONHEATINGSTART() C_TERN(ischamber, printerEventLEDs.onChamberHeatingStart(), B_TERN(isbed, printerEventLEDs.onBedHeatingStart(), printerEventLEDs.onHotendHeatingStart())) + #define ONHEATING(S,C,T) C_TERN(ischamber, printerEventLEDs.onChamberHeating(S,C,T), B_TERN(isbed, printerEventLEDs.onBedHeating(S,C,T), printerEventLEDs.onHotendHeating(S,C,T))) + + #define WATCH_PID BOTH(WATCH_CHAMBER, PIDTEMPCHAMBER) || BOTH(WATCH_BED, PIDTEMPBED) || BOTH(WATCH_HOTENDS, PIDTEMP) #if WATCH_PID - #if ALL(THERMAL_PROTECTION_HOTENDS, PIDTEMP, THERMAL_PROTECTION_BED, PIDTEMPBED) - #define GTV(B,H) (isbed ? (B) : (H)) - #elif BOTH(THERMAL_PROTECTION_HOTENDS, PIDTEMP) - #define GTV(B,H) (H) + #if BOTH(THERMAL_PROTECTION_CHAMBER, PIDTEMPCHAMBER) + #define C_GTV(T,A,B) ((T) ? (A) : (B)) #else - #define GTV(B,H) (B) + #define C_GTV(T,A,B) (B) #endif - const uint16_t watch_temp_period = GTV(WATCH_BED_TEMP_PERIOD, WATCH_TEMP_PERIOD); - const uint8_t watch_temp_increase = GTV(WATCH_BED_TEMP_INCREASE, WATCH_TEMP_INCREASE); - const float watch_temp_target = target - float(watch_temp_increase + GTV(TEMP_BED_HYSTERESIS, TEMP_HYSTERESIS) + 1); + #if BOTH(THERMAL_PROTECTION_BED, PIDTEMPBED) + #define B_GTV(T,A,B) ((T) ? (A) : (B)) + #else + #define B_GTV(T,A,B) (B) + #endif + #define GTV(C,B,H) C_GTV(ischamber, C, B_GTV(isbed, B, H)) + const uint16_t watch_temp_period = GTV(WATCH_CHAMBER_TEMP_PERIOD, WATCH_BED_TEMP_PERIOD, WATCH_TEMP_PERIOD); + const uint8_t watch_temp_increase = GTV(WATCH_CHAMBER_TEMP_INCREASE, WATCH_BED_TEMP_INCREASE, WATCH_TEMP_INCREASE); + const float watch_temp_target = target - float(watch_temp_increase + GTV(TEMP_CHAMBER_HYSTERESIS, TEMP_BED_HYSTERESIS, TEMP_HYSTERESIS) + 1); millis_t temp_change_ms = next_temp_ms + SEC_TO_MS(watch_temp_period); float next_watch_temp = 0.0; bool heated = false; @@ -527,7 +523,7 @@ volatile bool Temperature::raw_temps_ready = false; TERN_(HAS_AUTO_FAN, next_auto_fan_check_ms = next_temp_ms + 2500UL); - if (target > GHV(BED_MAX_TARGET, temp_range[heater_id].maxtemp - HOTEND_OVERSHOOT)) { + if (target > GHV(CHAMBER_MAX_TARGET, BED_MAX_TARGET, temp_range[heater_id].maxtemp - HOTEND_OVERSHOOT)) { SERIAL_ECHOLNPGM(STR_PID_TEMP_TOO_HIGH); TERN_(EXTENSIBLE_UI, ExtUI::onPidTuning(ExtUI::result_t::PID_TEMP_TOO_HIGH)); return; @@ -538,10 +534,11 @@ volatile bool Temperature::raw_temps_ready = false; disable_all_heaters(); TERN_(AUTO_POWER_CONTROL, powerManager.power_on()); - SHV(bias = d = (MAX_BED_POWER) >> 1, bias = d = (PID_MAX) >> 1); + long bias = GHV(MAX_CHAMBER_POWER, MAX_BED_POWER, PID_MAX) >> 1, d = bias; + SHV(bias); #if ENABLED(PRINTER_EVENT_LEDS) - const float start_temp = GHV(temp_bed.celsius, temp_hotend[heater_id].celsius); + const float start_temp = GHV(temp_chamber.celsius, temp_bed.celsius, temp_hotend[heater_id].celsius); LEDColor color = ONHEATINGSTART(); #endif @@ -557,7 +554,7 @@ volatile bool Temperature::raw_temps_ready = false; updateTemperaturesFromRawValues(); // Get the current temperature and constrain it - current_temp = GHV(temp_bed.celsius, temp_hotend[heater_id].celsius); + current_temp = GHV(temp_chamber.celsius, temp_bed.celsius, temp_hotend[heater_id].celsius); NOLESS(maxT, current_temp); NOMORE(minT, current_temp); @@ -572,67 +569,46 @@ volatile bool Temperature::raw_temps_ready = false; } #endif - if (heating && current_temp > target) { - if (ELAPSED(ms, t2 + 5000UL)) { - heating = false; - SHV((bias - d) >> 1, (bias - d) >> 1); - t1 = ms; - t_high = t1 - t2; - maxT = target; - } + if (heating && current_temp > target && ELAPSED(ms, t2 + 5000UL)) { + heating = false; + SHV((bias - d) >> 1); + t1 = ms; + t_high = t1 - t2; + maxT = target; } - if (!heating && current_temp < target) { - if (ELAPSED(ms, t1 + 5000UL)) { - heating = true; - t2 = ms; - t_low = t2 - t1; - if (cycles > 0) { - const long max_pow = GHV(MAX_BED_POWER, PID_MAX); - bias += (d * (t_high - t_low)) / (t_low + t_high); - LIMIT(bias, 20, max_pow - 20); - d = (bias > max_pow >> 1) ? max_pow - 1 - bias : bias; + if (!heating && current_temp < target && ELAPSED(ms, t1 + 5000UL)) { + heating = true; + t2 = ms; + t_low = t2 - t1; + if (cycles > 0) { + const long max_pow = GHV(MAX_CHAMBER_POWER, MAX_BED_POWER, PID_MAX); + bias += (d * (t_high - t_low)) / (t_low + t_high); + LIMIT(bias, 20, max_pow - 20); + d = (bias > max_pow >> 1) ? max_pow - 1 - bias : bias; - SERIAL_ECHOPAIR(STR_BIAS, bias, STR_D_COLON, d, STR_T_MIN, minT, STR_T_MAX, maxT); - if (cycles > 2) { - const float Ku = (4.0f * d) / (float(M_PI) * (maxT - minT) * 0.5f), - Tu = float(t_low + t_high) * 0.001f, - pf = isbed ? 0.2f : 0.6f, - df = isbed ? 1.0f / 3.0f : 1.0f / 8.0f; + SERIAL_ECHOPAIR(STR_BIAS, bias, STR_D_COLON, d, STR_T_MIN, minT, STR_T_MAX, maxT); + if (cycles > 2) { + const float Ku = (4.0f * d) / (float(M_PI) * (maxT - minT) * 0.5f), + Tu = float(t_low + t_high) * 0.001f, + pf = ischamber ? 0.2f : (isbed ? 0.2f : 0.6f), + df = ischamber ? 1.0f / 3.0f : (isbed ? 1.0f / 3.0f : 1.0f / 8.0f); - SERIAL_ECHOPAIR(STR_KU, Ku, STR_TU, Tu); - if (isbed) { // Do not remove this otherwise PID autotune won't work right for the bed! - tune_pid.Kp = Ku * 0.2f; - tune_pid.Ki = 2 * tune_pid.Kp / Tu; - tune_pid.Kd = tune_pid.Kp * Tu / 3; - SERIAL_ECHOLNPGM("\n" " No overshoot"); // Works far better for the bed. Classic and some have bad ringing. - SERIAL_ECHOLNPAIR(STR_KP, tune_pid.Kp, STR_KI, tune_pid.Ki, STR_KD, tune_pid.Kd); - } - else { - tune_pid.Kp = Ku * pf; - tune_pid.Kd = tune_pid.Kp * Tu * df; - tune_pid.Ki = 2 * tune_pid.Kp / Tu; - SERIAL_ECHOLNPGM("\n" STR_CLASSIC_PID); - SERIAL_ECHOLNPAIR(STR_KP, tune_pid.Kp, STR_KI, tune_pid.Ki, STR_KD, tune_pid.Kd); - } + tune_pid.Kp = Ku * pf; + tune_pid.Ki = tune_pid.Kp * 2.0f / Tu; + tune_pid.Kd = tune_pid.Kp * Tu * df; - /** - tune_pid.Kp = 0.33 * Ku; - tune_pid.Ki = tune_pid.Kp / Tu; - tune_pid.Kd = tune_pid.Kp * Tu / 3; - SERIAL_ECHOLNPGM(" Some overshoot"); - SERIAL_ECHOLNPAIR(" Kp: ", tune_pid.Kp, " Ki: ", tune_pid.Ki, " Kd: ", tune_pid.Kd, " No overshoot"); - tune_pid.Kp = 0.2 * Ku; - tune_pid.Ki = 2 * tune_pid.Kp / Tu; - tune_pid.Kd = tune_pid.Kp * Tu / 3; - SERIAL_ECHOPAIR(" Kp: ", tune_pid.Kp, " Ki: ", tune_pid.Ki, " Kd: ", tune_pid.Kd); - */ - } + SERIAL_ECHOLNPAIR(STR_KU, Ku, STR_TU, Tu); + if (ischamber || isbed) + SERIAL_ECHOLNPGM(" No overshoot"); + else + SERIAL_ECHOLNPGM(STR_CLASSIC_PID); + SERIAL_ECHOLNPAIR(STR_KP, tune_pid.Kp, STR_KI, tune_pid.Ki, STR_KD, tune_pid.Kd); } - SHV((bias + d) >> 1, (bias + d) >> 1); - cycles++; - minT = target; } + SHV((bias + d) >> 1); + cycles++; + minT = target; } } @@ -649,14 +625,14 @@ volatile bool Temperature::raw_temps_ready = false; // Report heater states every 2 seconds if (ELAPSED(ms, next_temp_ms)) { #if HAS_TEMP_SENSOR - print_heater_states(isbed ? active_extruder : heater_id); + print_heater_states(ischamber ? active_extruder : (isbed ? active_extruder : heater_id)); SERIAL_EOL(); #endif next_temp_ms = ms + 2000UL; // Make sure heating is actually working #if WATCH_PID - if (BOTH(WATCH_BED, WATCH_HOTENDS) || isbed == DISABLED(WATCH_HOTENDS)) { + if (BOTH(WATCH_BED, WATCH_HOTENDS) || isbed == DISABLED(WATCH_HOTENDS) || ischamber == DISABLED(WATCH_HOTENDS)) { if (!heated) { // If not yet reached target... if (current_temp > next_watch_temp) { // Over the watch temp? next_watch_temp = current_temp + watch_temp_increase; // - set the next temp to watch for @@ -686,43 +662,47 @@ volatile bool Temperature::raw_temps_ready = false; if (cycles > ncycles && cycles > 2) { SERIAL_ECHOLNPGM(STR_PID_AUTOTUNE_FINISHED); - #if HAS_PID_FOR_BOTH - const char * const estring = GHV(PSTR("bed"), NUL_STR); + #if EITHER(PIDTEMPBED, PIDTEMPCHAMBER) + PGM_P const estring = GHV(PSTR("chamber"), PSTR("bed"), NUL_STR); say_default_(); serialprintPGM(estring); SERIAL_ECHOLNPAIR("Kp ", tune_pid.Kp); say_default_(); serialprintPGM(estring); SERIAL_ECHOLNPAIR("Ki ", tune_pid.Ki); say_default_(); serialprintPGM(estring); SERIAL_ECHOLNPAIR("Kd ", tune_pid.Kd); - #elif ENABLED(PIDTEMP) + #else say_default_(); SERIAL_ECHOLNPAIR("Kp ", tune_pid.Kp); say_default_(); SERIAL_ECHOLNPAIR("Ki ", tune_pid.Ki); say_default_(); SERIAL_ECHOLNPAIR("Kd ", tune_pid.Kd); - #else - say_default_(); SERIAL_ECHOLNPAIR("bedKp ", tune_pid.Kp); - say_default_(); SERIAL_ECHOLNPAIR("bedKi ", tune_pid.Ki); - say_default_(); SERIAL_ECHOLNPAIR("bedKd ", tune_pid.Kd); #endif - #define _SET_BED_PID() do { \ - temp_bed.pid.Kp = tune_pid.Kp; \ - temp_bed.pid.Ki = scalePID_i(tune_pid.Ki); \ - temp_bed.pid.Kd = scalePID_d(tune_pid.Kd); \ - }while(0) + auto _set_hotend_pid = [](const uint8_t e, const PID_t &in_pid) { + #if ENABLED(PIDTEMP) + PID_PARAM(Kp, e) = in_pid.Kp; + PID_PARAM(Ki, e) = scalePID_i(in_pid.Ki); + PID_PARAM(Kd, e) = scalePID_d(in_pid.Kd); + updatePID(); + #else + UNUSED(e); UNUSED(in_pid); + #endif + }; - #define _SET_EXTRUDER_PID() do { \ - PID_PARAM(Kp, heater_id) = tune_pid.Kp; \ - PID_PARAM(Ki, heater_id) = scalePID_i(tune_pid.Ki); \ - PID_PARAM(Kd, heater_id) = scalePID_d(tune_pid.Kd); \ - updatePID(); }while(0) + #if ENABLED(PIDTEMPBED) + auto _set_bed_pid = [](const PID_t &in_pid) { + temp_bed.pid.Kp = in_pid.Kp; + temp_bed.pid.Ki = scalePID_i(in_pid.Ki); + temp_bed.pid.Kd = scalePID_d(in_pid.Kd); + }; + #endif + + #if ENABLED(PIDTEMPCHAMBER) + auto _set_chamber_pid = [](const PID_t &in_pid) { + temp_chamber.pid.Kp = in_pid.Kp; + temp_chamber.pid.Ki = scalePID_i(in_pid.Ki); + temp_chamber.pid.Kd = scalePID_d(in_pid.Kd); + }; + #endif // Use the result? (As with "M303 U1") - if (set_result) { - #if HAS_PID_FOR_BOTH - if (isbed) _SET_BED_PID(); else _SET_EXTRUDER_PID(); - #elif ENABLED(PIDTEMP) - _SET_EXTRUDER_PID(); - #else - _SET_BED_PID(); - #endif - } + if (set_result) + GHV(_set_chamber_pid(tune_pid), _set_bed_pid(tune_pid), _set_hotend_pid(heater_id, tune_pid)); TERN_(PRINTER_EVENT_LEDS, printerEventLEDs.onPidTuningDone(color)); @@ -939,10 +919,11 @@ void Temperature::min_temp_error(const heater_id_t heater_id) { _temp_error(heater_id, PSTR(STR_T_MINTEMP), GET_TEXT(MSG_ERR_MINTEMP)); } +#if ANY(PID_DEBUG, PID_BED_DEBUG, PID_CHAMBER_DEBUG) + bool Temperature::pid_debug_flag; // = 0 +#endif + #if HAS_HOTEND - #if ENABLED(PID_DEBUG) - extern bool pid_debug_flag; - #endif float Temperature::get_pid_output_hotend(const uint8_t E_NAME) { const uint8_t ee = HOTEND_INDEX; @@ -1023,23 +1004,18 @@ void Temperature::min_temp_error(const heater_id_t heater_id) { #if ENABLED(PID_DEBUG) if (ee == active_extruder && pid_debug_flag) { - SERIAL_ECHO_START(); - SERIAL_ECHOPAIR(STR_PID_DEBUG, ee, STR_PID_DEBUG_INPUT, temp_hotend[ee].celsius, STR_PID_DEBUG_OUTPUT, pid_output); - #if DISABLED(PID_OPENLOOP) - { - SERIAL_ECHOPAIR( - STR_PID_DEBUG_PTERM, work_pid[ee].Kp, - STR_PID_DEBUG_ITERM, work_pid[ee].Ki, - STR_PID_DEBUG_DTERM, work_pid[ee].Kd + SERIAL_ECHO_MSG(STR_PID_DEBUG, ee, STR_PID_DEBUG_INPUT, temp_hotend[ee].celsius, STR_PID_DEBUG_OUTPUT, pid_output + #if DISABLED(PID_OPENLOOP) + , STR_PID_DEBUG_PTERM, work_pid[ee].Kp + , STR_PID_DEBUG_ITERM, work_pid[ee].Ki + , STR_PID_DEBUG_DTERM, work_pid[ee].Kd #if ENABLED(PID_EXTRUSION_SCALING) , STR_PID_DEBUG_CTERM, work_pid[ee].Kc #endif - ); - } - #endif - SERIAL_EOL(); + #endif + ); } - #endif // PID_DEBUG + #endif #else // No PID enabled @@ -1099,13 +1075,76 @@ void Temperature::min_temp_error(const heater_id_t heater_id) { #endif // PID_OPENLOOP #if ENABLED(PID_BED_DEBUG) + if (pid_debug_flag) { + SERIAL_ECHO_MSG( + " PID_BED_DEBUG : Input ", temp_bed.celsius, " Output ", pid_output + #if DISABLED(PID_OPENLOOP) + , STR_PID_DEBUG_PTERM, work_pid.Kp + , STR_PID_DEBUG_ITERM, work_pid.Ki + , STR_PID_DEBUG_DTERM, work_pid.Kd + #endif + ); + } + #endif + + return pid_output; + } + +#endif // PIDTEMPBED + +#if ENABLED(PIDTEMPCHAMBER) + + float Temperature::get_pid_output_chamber() { + + #if DISABLED(PID_OPENLOOP) + + static PID_t work_pid{0}; + static float temp_iState = 0, temp_dState = 0; + static bool pid_reset = true; + float pid_output = 0; + const float max_power_over_i_gain = float(MAX_CHAMBER_POWER) / temp_chamber.pid.Ki - float(MIN_CHAMBER_POWER), + pid_error = temp_chamber.target - temp_chamber.celsius; + + if (!temp_chamber.target || pid_error < -(PID_FUNCTIONAL_RANGE)) { + pid_output = 0; + pid_reset = true; + } + else if (pid_error > PID_FUNCTIONAL_RANGE) { + pid_output = MAX_CHAMBER_POWER; + pid_reset = true; + } + else { + if (pid_reset) { + temp_iState = 0.0; + work_pid.Kd = 0.0; + pid_reset = false; + } + + temp_iState = constrain(temp_iState + pid_error, 0, max_power_over_i_gain); + + work_pid.Kp = temp_chamber.pid.Kp * pid_error; + work_pid.Ki = temp_chamber.pid.Ki * temp_iState; + work_pid.Kd = work_pid.Kd + PID_K2 * (temp_chamber.pid.Kd * (temp_dState - temp_chamber.celsius) - work_pid.Kd); + + temp_dState = temp_chamber.celsius; + + pid_output = constrain(work_pid.Kp + work_pid.Ki + work_pid.Kd + float(MIN_CHAMBER_POWER), 0, MAX_CHAMBER_POWER); + } + + #else // PID_OPENLOOP + + const float pid_output = constrain(temp_chamber.target, 0, MAX_CHAMBER_POWER); + + #endif // PID_OPENLOOP + + #if ENABLED(PID_CHAMBER_DEBUG) { SERIAL_ECHO_MSG( - " PID_BED_DEBUG : Input ", temp_bed.celsius, " Output ", pid_output, + " PID_CHAMBER_DEBUG : Input ", temp_chamber.celsius, " Output ", pid_output #if DISABLED(PID_OPENLOOP) - STR_PID_DEBUG_PTERM, work_pid.Kp, - STR_PID_DEBUG_ITERM, work_pid.Ki, - STR_PID_DEBUG_DTERM, work_pid.Kd, + , STR_PID_DEBUG_PTERM, work_pid.Kp + , STR_PID_DEBUG_ITERM, work_pid.Ki + , STR_PID_DEBUG_DTERM, work_pid.Kd #endif ); } @@ -1114,7 +1153,7 @@ void Temperature::min_temp_error(const heater_id_t heater_id) { return pid_output; } -#endif // PIDTEMPBED +#endif // PIDTEMPCHAMBER /** * Manage heating activities for extruder hot-ends and a heated bed @@ -1363,42 +1402,47 @@ void Temperature::manage_heater() { } #endif + + + + #if ENABLED(PIDTEMPCHAMBER) + // PIDTEMPCHAMBER doens't support a CHAMBER_VENT yet. + temp_chamber.soft_pwm_amount = WITHIN(temp_chamber.celsius, CHAMBER_MINTEMP, CHAMBER_MAXTEMP) ? (int)get_pid_output_chamber() >> 1 : 0; + #else if (ELAPSED(ms, next_chamber_check_ms)) { next_chamber_check_ms = ms + CHAMBER_CHECK_INTERVAL; if (WITHIN(temp_chamber.celsius, CHAMBER_MINTEMP, CHAMBER_MAXTEMP)) { - if (flag_chamber_excess_heat) { - temp_chamber.soft_pwm_amount = 0; - #if ENABLED(CHAMBER_VENT) - if (!flag_chamber_off) MOVE_SERVO(CHAMBER_VENT_SERVO_NR, temp_chamber.celsius <= temp_chamber.target ? 0 : 90); - #endif + if (flag_chamber_excess_heat) { + temp_chamber.soft_pwm_amount = 0; + #if ENABLED(CHAMBER_VENT) + if (!flag_chamber_off) MOVE_SERVO(CHAMBER_VENT_SERVO_NR, temp_chamber.celsius <= temp_chamber.target ? 0 : 90); + #endif + } + else { + #if ENABLED(CHAMBER_LIMIT_SWITCHING) + if (temp_chamber.celsius >= temp_chamber.target + TEMP_CHAMBER_HYSTERESIS) + temp_chamber.soft_pwm_amount = 0; + else if (temp_chamber.celsius <= temp_chamber.target - (TEMP_CHAMBER_HYSTERESIS)) + temp_chamber.soft_pwm_amount = (MAX_CHAMBER_POWER) >> 1; + #else + temp_chamber.soft_pwm_amount = temp_chamber.celsius < temp_chamber.target ? (MAX_CHAMBER_POWER) >> 1 : 0; + #endif + #if ENABLED(CHAMBER_VENT) + if (!flag_chamber_off) MOVE_SERVO(CHAMBER_VENT_SERVO_NR, 0); + #endif + } } else { - #if ENABLED(CHAMBER_LIMIT_SWITCHING) - if (temp_chamber.celsius >= temp_chamber.target + TEMP_CHAMBER_HYSTERESIS) - temp_chamber.soft_pwm_amount = 0; - else if (temp_chamber.celsius <= temp_chamber.target - (TEMP_CHAMBER_HYSTERESIS)) - temp_chamber.soft_pwm_amount = (MAX_CHAMBER_POWER) >> 1; - #else - temp_chamber.soft_pwm_amount = temp_chamber.celsius < temp_chamber.target ? (MAX_CHAMBER_POWER) >> 1 : 0; - #endif - #if ENABLED(CHAMBER_VENT) - if (!flag_chamber_off) MOVE_SERVO(CHAMBER_VENT_SERVO_NR, 0); - #endif + temp_chamber.soft_pwm_amount = 0; + WRITE_HEATER_CHAMBER(LOW); } - } - else { - temp_chamber.soft_pwm_amount = 0; - WRITE_HEATER_CHAMBER(LOW); - } - #if ENABLED(THERMAL_PROTECTION_CHAMBER) - tr_state_machine[RUNAWAY_IND_CHAMBER].run(temp_chamber.celsius, temp_chamber.target, H_CHAMBER, THERMAL_PROTECTION_CHAMBER_PERIOD, THERMAL_PROTECTION_CHAMBER_HYSTERESIS); - #endif - } - - // TODO: Implement true PID pwm - //temp_bed.soft_pwm_amount = WITHIN(temp_chamber.celsius, CHAMBER_MINTEMP, CHAMBER_MAXTEMP) ? (int)get_pid_output_chamber() >> 1 : 0; + } + #if ENABLED(THERMAL_PROTECTION_CHAMBER) + tr_state_machine[RUNAWAY_IND_CHAMBER].run(temp_chamber.celsius, temp_chamber.target, H_CHAMBER, THERMAL_PROTECTION_CHAMBER_PERIOD, THERMAL_PROTECTION_CHAMBER_HYSTERESIS); + #endif + #endif #endif // HAS_HEATED_CHAMBER diff --git a/Marlin/src/module/temperature.h b/Marlin/src/module/temperature.h index 75da232874..1019c10fad 100644 --- a/Marlin/src/module/temperature.h +++ b/Marlin/src/module/temperature.h @@ -210,7 +210,11 @@ struct PIDHeaterInfo : public HeaterInfo { typedef temp_info_t probe_info_t; #endif #if HAS_HEATED_CHAMBER - typedef heater_info_t chamber_info_t; + #if ENABLED(PIDTEMPCHAMBER) + typedef struct PIDHeaterInfo chamber_info_t; + #else + typedef heater_info_t chamber_info_t; + #endif #elif HAS_TEMP_CHAMBER typedef temp_info_t chamber_info_t; #endif @@ -415,7 +419,7 @@ class Temperature { #if HAS_HEATED_CHAMBER TERN_(WATCH_CHAMBER, static chamber_watch_t watch_chamber); - static millis_t next_chamber_check_ms; + TERN(PIDTEMPCHAMBER,,static millis_t next_chamber_check_ms); #ifdef CHAMBER_MINTEMP static int16_t mintemp_raw_CHAMBER; #endif @@ -751,6 +755,11 @@ class Temperature { * Perform auto-tuning for hotend or bed in response to M303 */ #if HAS_PID_HEATING + + #if ANY(PID_DEBUG, PID_BED_DEBUG, PID_CHAMBER_DEBUG) + static bool pid_debug_flag; + #endif + static void PID_autotune(const float &target, const heater_id_t heater_id, const int8_t ncycles, const bool set_result=false); #if ENABLED(NO_FAN_SLOWING_IN_PID_TUNING) @@ -826,11 +835,9 @@ class Temperature { static void checkExtruderAutoFans(); - static float get_pid_output_hotend(const uint8_t e); - - TERN_(PIDTEMPBED, static float get_pid_output_bed()); - - TERN_(HAS_HEATED_CHAMBER, static float get_pid_output_chamber()); + TERN_(HAS_HOTEND, static float get_pid_output_hotend(const uint8_t e)); + TERN_(PIDTEMPBED, static float get_pid_output_bed()); + TERN_(PIDTEMPCHAMBER, static float get_pid_output_chamber()); static void _temp_error(const heater_id_t e, PGM_P const serial_msg, PGM_P const lcd_msg); static void min_temp_error(const heater_id_t e);