Skip to content

Commit 0308c3a

Browse files
tombrazierLCh-77
authored andcommitted
⚗️ Temperature Model Predictive Control (MarlinFirmware#23751)
1 parent 5b6feae commit 0308c3a

File tree

13 files changed

+566
-32
lines changed

13 files changed

+566
-32
lines changed

Marlin/Configuration.h

+44-4
Original file line numberDiff line numberDiff line change
@@ -608,10 +608,12 @@
608608
//===========================================================================
609609
//============================= PID Settings ================================
610610
//===========================================================================
611-
// PID Tuning Guide here: https://reprap.org/wiki/PID_Tuning
612611

613-
// Comment the following line to disable PID and enable bang-bang.
614-
#define PIDTEMP
612+
// Enable PIDTEMP for PID control or MPCTEMP for Predictive Model.
613+
// temperature control. Disable both for bang-bang heating.
614+
#define PIDTEMP // See the PID Tuning Guide at https://reprap.org/wiki/PID_Tuning
615+
//#define MPCTEMP // ** EXPERIMENTAL **
616+
615617
#define BANG_MAX 255 // Limits current to nozzle while in bang-bang mode; 255=full current
616618
#define PID_MAX BANG_MAX // Limits current to nozzle while PID is active (see PID_FUNCTIONAL_RANGE below); 255=full current
617619
#define PID_K1 0.95 // Smoothing factor within any PID loop
@@ -633,7 +635,45 @@
633635
#define DEFAULT_Ki 1.08
634636
#define DEFAULT_Kd 114.00
635637
#endif
636-
#endif // PIDTEMP
638+
#endif
639+
640+
/**
641+
* Model Predictive Control for hotend
642+
*
643+
* Use a physical model of the hotend to control temperature. When configured correctly
644+
* this gives better responsiveness and stability than PID and it also removes the need
645+
* for PID_EXTRUSION_SCALING and PID_FAN_SCALING. Use M306 to autotune the model.
646+
*/
647+
#if ENABLED(MPCTEMP)
648+
#define MPC_MAX BANG_MAX // (0..255) Current to nozzle while MPC is active.
649+
#define MPC_HEATER_POWER { 40.0f } // (W) Heat cartridge powers.
650+
651+
#define MPC_INCLUDE_FAN // Model the fan speed?
652+
653+
// Measured physical constants from M306
654+
#define MPC_BLOCK_HEAT_CAPACITY { 16.7f } // (J/K) Heat block heat capacities.
655+
#define MPC_SENSOR_RESPONSIVENESS { 0.22f } // (K/s per ∆K) Rate of change of sensor temperature from heat block.
656+
#define MPC_AMBIENT_XFER_COEFF { 0.068f } // (W/K) Heat transfer coefficients from heat block to room air with fan off.
657+
#if ENABLED(MPC_INCLUDE_FAN)
658+
#define MPC_AMBIENT_XFER_COEFF_FAN255 { 0.097f } // (W/K) Heat transfer coefficients from heat block to room air with fan on full.
659+
#endif
660+
661+
// For one fan and multiple hotends MPC needs to know how to apply the fan cooling effect.
662+
#if ENABLED(MPC_INCLUDE_FAN)
663+
//#define MPC_FAN_0_ALL_HOTENDS
664+
//#define MPC_FAN_0_ACTIVE_HOTEND
665+
#endif
666+
667+
#define FILAMENT_HEAT_CAPACITY_PERMM 5.6e-3f // 0.0056 J/K/mm for 1.75mm PLA (0.0149 J/K/mm for 2.85mm PLA).
668+
//#define FILAMENT_HEAT_CAPACITY_PERMM 3.6e-3f // 0.0036 J/K/mm for 1.75mm PETG (0.0094 J/K/mm for 2.85mm PETG).
669+
670+
// Advanced options
671+
#define MPC_SMOOTHING_FACTOR 0.5f // (0.0...1.0) Noisy temperature sensors may need a lower value for stabilization.
672+
#define MPC_MIN_AMBIENT_CHANGE 1.0f // (K/s) Modeled ambient temperature rate of change, when correcting model inaccuracies.
673+
#define MPC_STEADYSTATE 0.5f // (K/s) Temperature change rate for steady state logic to be enforced.
674+
675+
#define MPC_TUNING_POS { X_CENTER, Y_CENTER, 1.0f } // (mm) M306 Autotuning position, ideally bed center just above the surface.
676+
#endif
637677

638678
//===========================================================================
639679
//====================== PID > Bed Temperature Control ======================

Marlin/src/HAL/ESP32/HAL.cpp

+13-13
Original file line numberDiff line numberDiff line change
@@ -209,19 +209,19 @@ void MarlinHAL::adc_init() {
209209
adc1_config_width(ADC_WIDTH_12Bit);
210210

211211
// Configure channels only if used as (re-)configuring a pin for ADC that is used elsewhere might have adverse effects
212-
TERN_(HAS_TEMP_ADC_0, adc1_set_attenuation(get_channel(TEMP_0_PIN), ADC_ATTEN_11db));
213-
TERN_(HAS_TEMP_ADC_1, adc1_set_attenuation(get_channel(TEMP_1_PIN), ADC_ATTEN_11db));
214-
TERN_(HAS_TEMP_ADC_2, adc1_set_attenuation(get_channel(TEMP_2_PIN), ADC_ATTEN_11db));
215-
TERN_(HAS_TEMP_ADC_3, adc1_set_attenuation(get_channel(TEMP_3_PIN), ADC_ATTEN_11db));
216-
TERN_(HAS_TEMP_ADC_4, adc1_set_attenuation(get_channel(TEMP_4_PIN), ADC_ATTEN_11db));
217-
TERN_(HAS_TEMP_ADC_5, adc1_set_attenuation(get_channel(TEMP_5_PIN), ADC_ATTEN_11db));
218-
TERN_(HAS_TEMP_ADC_6, adc2_set_attenuation(get_channel(TEMP_6_PIN), ADC_ATTEN_11db));
219-
TERN_(HAS_TEMP_ADC_7, adc3_set_attenuation(get_channel(TEMP_7_PIN), ADC_ATTEN_11db));
220-
TERN_(HAS_HEATED_BED, adc1_set_attenuation(get_channel(TEMP_BED_PIN), ADC_ATTEN_11db));
221-
TERN_(HAS_TEMP_CHAMBER, adc1_set_attenuation(get_channel(TEMP_CHAMBER_PIN), ADC_ATTEN_11db));
222-
TERN_(HAS_TEMP_PROBE, adc1_set_attenuation(get_channel(TEMP_PROBE_PIN), ADC_ATTEN_11db));
223-
TERN_(HAS_TEMP_COOLER, adc1_set_attenuation(get_channel(TEMP_COOLER_PIN), ADC_ATTEN_11db));
224-
TERN_(HAS_TEMP_BOARD, adc1_set_attenuation(get_channel(TEMP_BOARD_PIN), ADC_ATTEN_11db));
212+
TERN_(HAS_TEMP_ADC_0, adc1_set_attenuation(get_channel(TEMP_0_PIN), ADC_ATTEN_11db));
213+
TERN_(HAS_TEMP_ADC_1, adc1_set_attenuation(get_channel(TEMP_1_PIN), ADC_ATTEN_11db));
214+
TERN_(HAS_TEMP_ADC_2, adc1_set_attenuation(get_channel(TEMP_2_PIN), ADC_ATTEN_11db));
215+
TERN_(HAS_TEMP_ADC_3, adc1_set_attenuation(get_channel(TEMP_3_PIN), ADC_ATTEN_11db));
216+
TERN_(HAS_TEMP_ADC_4, adc1_set_attenuation(get_channel(TEMP_4_PIN), ADC_ATTEN_11db));
217+
TERN_(HAS_TEMP_ADC_5, adc1_set_attenuation(get_channel(TEMP_5_PIN), ADC_ATTEN_11db));
218+
TERN_(HAS_TEMP_ADC_6, adc2_set_attenuation(get_channel(TEMP_6_PIN), ADC_ATTEN_11db));
219+
TERN_(HAS_TEMP_ADC_7, adc3_set_attenuation(get_channel(TEMP_7_PIN), ADC_ATTEN_11db));
220+
TERN_(HAS_HEATED_BED, adc1_set_attenuation(get_channel(TEMP_BED_PIN), ADC_ATTEN_11db));
221+
TERN_(HAS_TEMP_CHAMBER, adc1_set_attenuation(get_channel(TEMP_CHAMBER_PIN), ADC_ATTEN_11db));
222+
TERN_(HAS_TEMP_PROBE, adc1_set_attenuation(get_channel(TEMP_PROBE_PIN), ADC_ATTEN_11db));
223+
TERN_(HAS_TEMP_COOLER, adc1_set_attenuation(get_channel(TEMP_COOLER_PIN), ADC_ATTEN_11db));
224+
TERN_(HAS_TEMP_BOARD, adc1_set_attenuation(get_channel(TEMP_BOARD_PIN), ADC_ATTEN_11db));
225225
TERN_(FILAMENT_WIDTH_SENSOR, adc1_set_attenuation(get_channel(FILWIDTH_PIN), ADC_ATTEN_11db));
226226

227227
// Note that adc2 is shared with the WiFi module, which has higher priority, so the conversion may fail.

Marlin/src/gcode/gcode.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,10 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
790790
case 305: M305(); break; // M305: Set user thermistor parameters
791791
#endif
792792

793+
#if ENABLED(MPCTEMP)
794+
case 306: M306(); break; // M306: MPC autotune
795+
#endif
796+
793797
#if ENABLED(REPETIER_GCODE_M360)
794798
case 360: M360(); break; // M360: Firmware settings
795799
#endif

Marlin/src/gcode/gcode.h

+6
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@
215215
* M303 - PID relay autotune S<temperature> sets the target temperature. Default 150C. (Requires PIDTEMP)
216216
* M304 - Set bed PID parameters P I and D. (Requires PIDTEMPBED)
217217
* M305 - Set user thermistor parameters R T and P. (Requires TEMP_SENSOR_x 1000)
218+
* M306 - MPC autotune. (Requires MPCTEMP)
218219
* M309 - Set chamber PID parameters P I and D. (Requires PIDTEMPCHAMBER)
219220
* M350 - Set microstepping mode. (Requires digital microstepping pins.)
220221
* M351 - Toggle MS1 MS2 pins directly. (Requires digital microstepping pins.)
@@ -929,6 +930,11 @@ class GcodeSuite {
929930
static void M305();
930931
#endif
931932

933+
#if ENABLED(MPCTEMP)
934+
static void M306();
935+
static void M306_report(const bool forReplay=true);
936+
#endif
937+
932938
#if ENABLED(PIDTEMPCHAMBER)
933939
static void M309();
934940
static void M309_report(const bool forReplay=true);

Marlin/src/gcode/temp/M306.cpp

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/**
2+
* Marlin 3D Printer Firmware
3+
* Copyright (c) 2022 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
4+
*
5+
* Based on Sprinter and grbl.
6+
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU General Public License as published by
10+
* the Free Software Foundation, either version 3 of the License, or
11+
* (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* GNU General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU General Public License
19+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
20+
*
21+
*/
22+
23+
#include "../../inc/MarlinConfig.h"
24+
25+
#if ENABLED(MPCTEMP)
26+
27+
#include "../gcode.h"
28+
#include "../../module/temperature.h"
29+
30+
/**
31+
* M306: MPC settings and autotune
32+
*
33+
* T Autotune the active extruder.
34+
*
35+
* A<watts/kelvin> Ambient heat transfer coefficient (no fan).
36+
* C<joules/kelvin> Block heat capacity.
37+
* E<extruder> Extruder number to set. (Default: E0)
38+
* F<watts/kelvin> Ambient heat transfer coefficient (fan on full).
39+
* P<watts> Heater power.
40+
* R<kelvin/second/kelvin> Sensor responsiveness (= transfer coefficient / heat capcity).
41+
*/
42+
43+
void GcodeSuite::M306() {
44+
if (parser.seen_test('T')) { thermalManager.MPC_autotune(); return; }
45+
46+
if (parser.seen("ACFPR")) {
47+
const heater_id_t hid = (heater_id_t)parser.intval('E', 0);
48+
MPC_t &constants = thermalManager.temp_hotend[hid].constants;
49+
if (parser.seenval('P')) constants.heater_power = parser.value_float();
50+
if (parser.seenval('C')) constants.block_heat_capacity = parser.value_float();
51+
if (parser.seenval('R')) constants.sensor_responsiveness = parser.value_float();
52+
if (parser.seenval('A')) constants.ambient_xfer_coeff_fan0 = parser.value_float();
53+
#if ENABLED(MPC_INCLUDE_FAN)
54+
if (parser.seenval('F')) constants.fan255_adjustment = parser.value_float() - constants.ambient_xfer_coeff_fan0;
55+
#endif
56+
return;
57+
}
58+
59+
HOTEND_LOOP() {
60+
SERIAL_ECHOLNPGM("MPC constants for hotend ", e);
61+
MPC_t& constants = thermalManager.temp_hotend[e].constants;
62+
SERIAL_ECHOLNPGM("Heater power: ", constants.heater_power);
63+
SERIAL_ECHOLNPGM("Heatblock heat capacity: ", constants.block_heat_capacity);
64+
SERIAL_ECHOLNPAIR_F("Sensor responsivness: ", constants.sensor_responsiveness, 4);
65+
SERIAL_ECHOLNPAIR_F("Ambient heat transfer coeff. (no fan): ", constants.ambient_xfer_coeff_fan0, 4);
66+
#if ENABLED(MPC_INCLUDE_FAN)
67+
SERIAL_ECHOLNPAIR_F("Ambient heat transfer coeff. (full fan): ", constants.ambient_xfer_coeff_fan0 + constants.fan255_adjustment, 4);
68+
#endif
69+
}
70+
}
71+
72+
void GcodeSuite::M306_report(const bool forReplay/*=true*/) {
73+
report_heading(forReplay, F("Model predictive control"));
74+
HOTEND_LOOP() {
75+
report_echo_start(forReplay);
76+
MPC_t& constants = thermalManager.temp_hotend[e].constants;
77+
SERIAL_ECHOPGM(" M306 E", e);
78+
SERIAL_ECHOPAIR_F(" P", constants.heater_power, 2);
79+
SERIAL_ECHOPAIR_F(" C", constants.block_heat_capacity, 2);
80+
SERIAL_ECHOPAIR_F(" R", constants.sensor_responsiveness, 4);
81+
SERIAL_ECHOPAIR_F(" A", constants.ambient_xfer_coeff_fan0, 4);
82+
SERIAL_ECHOLNPAIR_F(" F", constants.ambient_xfer_coeff_fan0 + constants.fan255_adjustment, 4);
83+
}
84+
}
85+
86+
#endif // MPCTEMP

Marlin/src/inc/SanityCheck.h

+20
Original file line numberDiff line numberDiff line change
@@ -1405,6 +1405,26 @@ static_assert(Y_MAX_LENGTH >= Y_BED_SIZE, "Movement bounds (Y_MIN_POS, Y_MAX_POS
14051405
#error "You must set DISPLAY_CHARSET_HD44780 to JAPANESE, WESTERN or CYRILLIC for your LCD controller."
14061406
#endif
14071407

1408+
/**
1409+
* Extruder temperature control algorithm - There can be only one!
1410+
*/
1411+
#if BOTH(PIDTEMP, MPCTEMP)
1412+
#error "Only enable PIDTEMP or MPCTEMP, but not both."
1413+
#endif
1414+
1415+
#if ENABLED(MPC_INCLUDE_FAN)
1416+
#if FAN_COUNT < 1
1417+
#error "MPC_INCLUDE_FAN requires at least one fan."
1418+
#endif
1419+
#if FAN_COUNT < HOTENDS
1420+
#if COUNT_ENABLED(MPC_FAN_0_ALL_HOTENDS, MPC_FAN_0_ACTIVE_HOTEND) > 1
1421+
#error "Enable either MPC_FAN_0_ALL_HOTENDS or MPC_FAN_0_ACTIVE_HOTEND, not both."
1422+
#elif NONE(MPC_FAN_0_ALL_HOTENDS, MPC_FAN_0_ACTIVE_HOTEND)
1423+
#error "MPC_INCLUDE_FAN requires MPC_FAN_0_ALL_HOTENDS or MPC_FAN_0_ACTIVE_HOTEND for one fan with multiple hotends."
1424+
#endif
1425+
#endif
1426+
#endif
1427+
14081428
/**
14091429
* Bed Heating Options - PID vs Limit Switching
14101430
*/

Marlin/src/lcd/extui/anycubic_chiron/chiron_tft.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ void ChironTFT::Startup() {
8585
// opt_enable FIL_RUNOUT_PULLUP
8686
TFTSer.begin(115200);
8787

88-
// wait for the TFT panel to initialise and finish the animation
88+
// Wait for the TFT panel to initialize and finish the animation
8989
safe_delay(1000);
9090

9191
// There are different panels for the Chiron with slightly different commands

Marlin/src/lcd/extui/anycubic_i3mega/anycubic_i3mega_lcd.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ void AnycubicTFTClass::OnSetup() {
8585
SENDLINE_DBG_PGM("J17", "TFT Serial Debug: Main board reset... J17"); // J17 Main board reset
8686
delay_ms(10);
8787

88-
// initialise the state of the key pins running on the tft
88+
// Init the state of the key pins running on the TFT
8989
#if ENABLED(SDSUPPORT) && PIN_EXISTS(SD_DETECT)
9090
SET_INPUT_PULLUP(SD_DETECT_PIN);
9191
#endif

Marlin/src/module/settings.cpp

+61
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,13 @@ typedef struct SettingsDataStruct {
554554
uint8_t ui_language; // M414 S
555555
#endif
556556

557+
//
558+
// Model predictive control
559+
//
560+
#if ENABLED(MPCTEMP)
561+
MPC_t mpc_constants[HOTENDS]; // M306
562+
#endif
563+
557564
} SettingsData;
558565

559566
//static_assert(sizeof(SettingsData) <= MARLIN_EEPROM_SIZE, "EEPROM too small to contain SettingsData!");
@@ -1581,6 +1588,14 @@ void MarlinSettings::postprocess() {
15811588
EEPROM_WRITE(ui.language);
15821589
#endif
15831590

1591+
//
1592+
// Model predictive control
1593+
//
1594+
#if ENABLED(MPCTEMP)
1595+
HOTEND_LOOP()
1596+
EEPROM_WRITE(thermalManager.temp_hotend[e].constants);
1597+
#endif
1598+
15841599
//
15851600
// Report final CRC and Data Size
15861601
//
@@ -2549,6 +2564,16 @@ void MarlinSettings::postprocess() {
25492564
}
25502565
#endif
25512566

2567+
//
2568+
// Model predictive control
2569+
//
2570+
#if ENABLED(MPCTEMP)
2571+
{
2572+
HOTEND_LOOP()
2573+
EEPROM_READ(thermalManager.temp_hotend[e].constants);
2574+
}
2575+
#endif
2576+
25522577
//
25532578
// Validate Final Size and CRC
25542579
//
@@ -3267,6 +3292,37 @@ void MarlinSettings::reset() {
32673292
#endif
32683293

32693294
TERN_(EXTENSIBLE_UI, ExtUI::onFactoryReset());
3295+
3296+
//
3297+
// Model predictive control
3298+
//
3299+
#if ENABLED(MPCTEMP)
3300+
constexpr float _mpc_heater_power[] = MPC_HEATER_POWER;
3301+
constexpr float _mpc_block_heat_capacity[] = MPC_BLOCK_HEAT_CAPACITY;
3302+
constexpr float _mpc_sensor_responsiveness[] = MPC_SENSOR_RESPONSIVENESS;
3303+
constexpr float _mpc_ambient_xfer_coeff[] = MPC_AMBIENT_XFER_COEFF;
3304+
#if ENABLED(MPC_INCLUDE_FAN)
3305+
constexpr float _mpc_ambient_xfer_coeff_fan255[] = MPC_AMBIENT_XFER_COEFF_FAN255;
3306+
#endif
3307+
3308+
static_assert(COUNT(_mpc_heater_power) == HOTENDS, "MPC_HEATER_POWER must have HOTENDS items.");
3309+
static_assert(COUNT(_mpc_block_heat_capacity) == HOTENDS, "MPC_BLOCK_HEAT_CAPACITY must have HOTENDS items.");
3310+
static_assert(COUNT(_mpc_sensor_responsiveness) == HOTENDS, "MPC_SENSOR_RESPONSIVENESS must have HOTENDS items.");
3311+
static_assert(COUNT(_mpc_ambient_xfer_coeff) == HOTENDS, "MPC_AMBIENT_XFER_COEFF must have HOTENDS items.");
3312+
#if ENABLED(MPC_INCLUDE_FAN)
3313+
static_assert(COUNT(_mpc_ambient_xfer_coeff_fan255) == HOTENDS, "MPC_AMBIENT_XFER_COEFF_FAN255 must have HOTENDS items.");
3314+
#endif
3315+
3316+
HOTEND_LOOP() {
3317+
thermalManager.temp_hotend[e].constants.heater_power = _mpc_heater_power[e];
3318+
thermalManager.temp_hotend[e].constants.block_heat_capacity = _mpc_block_heat_capacity[e];
3319+
thermalManager.temp_hotend[e].constants.sensor_responsiveness = _mpc_sensor_responsiveness[e];
3320+
thermalManager.temp_hotend[e].constants.ambient_xfer_coeff_fan0 = _mpc_ambient_xfer_coeff[e];
3321+
#if ENABLED(MPC_INCLUDE_FAN)
3322+
thermalManager.temp_hotend[e].constants.fan255_adjustment = _mpc_ambient_xfer_coeff_fan255[e] - _mpc_ambient_xfer_coeff[e];
3323+
#endif
3324+
}
3325+
#endif
32703326
}
32713327

32723328
#if DISABLED(DISABLE_M503)
@@ -3543,6 +3599,11 @@ void MarlinSettings::reset() {
35433599
#endif
35443600

35453601
TERN_(HAS_MULTI_LANGUAGE, gcode.M414_report(forReplay));
3602+
3603+
//
3604+
// Model predictive control
3605+
//
3606+
TERN_(MPCTEMP, gcode.M306_report(forReplay));
35463607
}
35473608

35483609
#endif // !DISABLE_M503

0 commit comments

Comments
 (0)