Browse Source

⚗️ Temperature Model Predictive Control (#23751)

tombrazier 2 years ago
parent
commit
72b2e2b2c7

+ 44
- 4
Marlin/Configuration.h View File

@@ -608,10 +608,12 @@
608 608
 //===========================================================================
609 609
 //============================= PID Settings ================================
610 610
 //===========================================================================
611
-// PID Tuning Guide here: https://reprap.org/wiki/PID_Tuning
612 611
 
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
+
615 617
 #define BANG_MAX 255     // Limits current to nozzle while in bang-bang mode; 255=full current
616 618
 #define PID_MAX BANG_MAX // Limits current to nozzle while PID is active (see PID_FUNCTIONAL_RANGE below); 255=full current
617 619
 #define PID_K1 0.95      // Smoothing factor within any PID loop
@@ -633,7 +635,45 @@
633 635
     #define DEFAULT_Ki   1.08
634 636
     #define DEFAULT_Kd 114.00
635 637
   #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
637 677
 
638 678
 //===========================================================================
639 679
 //====================== PID > Bed Temperature Control ======================

+ 13
- 13
Marlin/src/HAL/ESP32/HAL.cpp View File

@@ -209,19 +209,19 @@ void MarlinHAL::adc_init() {
209 209
   adc1_config_width(ADC_WIDTH_12Bit);
210 210
 
211 211
   // 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));
225 225
   TERN_(FILAMENT_WIDTH_SENSOR, adc1_set_attenuation(get_channel(FILWIDTH_PIN), ADC_ATTEN_11db));
226 226
 
227 227
   // Note that adc2 is shared with the WiFi module, which has higher priority, so the conversion may fail.

+ 4
- 0
Marlin/src/gcode/gcode.cpp View File

@@ -790,6 +790,10 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
790 790
         case 305: M305(); break;                                  // M305: Set user thermistor parameters
791 791
       #endif
792 792
 
793
+      #if ENABLED(MPCTEMP)
794
+        case 306: M306(); break;                                  // M306: MPC autotune
795
+      #endif
796
+
793 797
       #if ENABLED(REPETIER_GCODE_M360)
794 798
         case 360: M360(); break;                                  // M360: Firmware settings
795 799
       #endif

+ 6
- 0
Marlin/src/gcode/gcode.h View File

@@ -215,6 +215,7 @@
215 215
  * M303 - PID relay autotune S<temperature> sets the target temperature. Default 150C. (Requires PIDTEMP)
216 216
  * M304 - Set bed PID parameters P I and D. (Requires PIDTEMPBED)
217 217
  * M305 - Set user thermistor parameters R T and P. (Requires TEMP_SENSOR_x 1000)
218
+ * M306 - MPC autotune. (Requires MPCTEMP)
218 219
  * M309 - Set chamber PID parameters P I and D. (Requires PIDTEMPCHAMBER)
219 220
  * M350 - Set microstepping mode. (Requires digital microstepping pins.)
220 221
  * M351 - Toggle MS1 MS2 pins directly. (Requires digital microstepping pins.)
@@ -929,6 +930,11 @@ private:
929 930
     static void M305();
930 931
   #endif
931 932
 
933
+  #if ENABLED(MPCTEMP)
934
+    static void M306();
935
+    static void M306_report(const bool forReplay=true);
936
+  #endif
937
+
932 938
   #if ENABLED(PIDTEMPCHAMBER)
933 939
     static void M309();
934 940
     static void M309_report(const bool forReplay=true);

+ 86
- 0
Marlin/src/gcode/temp/M306.cpp View File

@@ -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

+ 20
- 0
Marlin/src/inc/SanityCheck.h View File

@@ -1406,6 +1406,26 @@ static_assert(Y_MAX_LENGTH >= Y_BED_SIZE, "Movement bounds (Y_MIN_POS, Y_MAX_POS
1406 1406
 #endif
1407 1407
 
1408 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
+
1428
+/**
1409 1429
  * Bed Heating Options - PID vs Limit Switching
1410 1430
  */
1411 1431
 #if BOTH(PIDTEMPBED, BED_LIMIT_SWITCHING)

+ 1
- 1
Marlin/src/lcd/extui/anycubic_chiron/chiron_tft.cpp View File

@@ -85,7 +85,7 @@ void ChironTFT::Startup() {
85 85
   // opt_enable FIL_RUNOUT_PULLUP
86 86
   TFTSer.begin(115200);
87 87
 
88
-  // wait for the TFT panel to initialise and finish the animation
88
+  // Wait for the TFT panel to initialize and finish the animation
89 89
   safe_delay(1000);
90 90
 
91 91
   // There are different panels for the Chiron with slightly different commands

+ 1
- 1
Marlin/src/lcd/extui/anycubic_i3mega/anycubic_i3mega_lcd.cpp View File

@@ -85,7 +85,7 @@ void AnycubicTFTClass::OnSetup() {
85 85
   SENDLINE_DBG_PGM("J17", "TFT Serial Debug: Main board reset... J17"); // J17 Main board reset
86 86
   delay_ms(10);
87 87
 
88
-  // initialise the state of the key pins running on the tft
88
+  // Init the state of the key pins running on the TFT
89 89
   #if ENABLED(SDSUPPORT) && PIN_EXISTS(SD_DETECT)
90 90
     SET_INPUT_PULLUP(SD_DETECT_PIN);
91 91
   #endif

+ 61
- 0
Marlin/src/module/settings.cpp View File

@@ -554,6 +554,13 @@ typedef struct SettingsDataStruct {
554 554
     uint8_t ui_language;                                // M414 S
555 555
   #endif
556 556
 
557
+  //
558
+  // Model predictive control
559
+  //
560
+  #if ENABLED(MPCTEMP)
561
+    MPC_t mpc_constants[HOTENDS];                       // M306
562
+  #endif
563
+
557 564
 } SettingsData;
558 565
 
559 566
 //static_assert(sizeof(SettingsData) <= MARLIN_EEPROM_SIZE, "EEPROM too small to contain SettingsData!");
@@ -1582,6 +1589,14 @@ void MarlinSettings::postprocess() {
1582 1589
     #endif
1583 1590
 
1584 1591
     //
1592
+    // Model predictive control
1593
+    //
1594
+    #if ENABLED(MPCTEMP)
1595
+      HOTEND_LOOP()
1596
+        EEPROM_WRITE(thermalManager.temp_hotend[e].constants);
1597
+    #endif
1598
+
1599
+    //
1585 1600
     // Report final CRC and Data Size
1586 1601
     //
1587 1602
     if (!eeprom_error) {
@@ -2550,6 +2565,16 @@ void MarlinSettings::postprocess() {
2550 2565
       #endif
2551 2566
 
2552 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
+
2577
+      //
2553 2578
       // Validate Final Size and CRC
2554 2579
       //
2555 2580
       eeprom_error = size_error(eeprom_index - (EEPROM_OFFSET));
@@ -3267,6 +3292,37 @@ void MarlinSettings::reset() {
3267 3292
   #endif
3268 3293
 
3269 3294
   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
3270 3326
 }
3271 3327
 
3272 3328
 #if DISABLED(DISABLE_M503)
@@ -3543,6 +3599,11 @@ void MarlinSettings::reset() {
3543 3599
     #endif
3544 3600
 
3545 3601
     TERN_(HAS_MULTI_LANGUAGE, gcode.M414_report(forReplay));
3602
+
3603
+    //
3604
+    // Model predictive control
3605
+    //
3606
+    TERN_(MPCTEMP, gcode.M306_report(forReplay));
3546 3607
   }
3547 3608
 
3548 3609
 #endif // !DISABLE_M503

+ 290
- 10
Marlin/src/module/temperature.cpp View File

@@ -141,7 +141,8 @@
141 141
   #endif
142 142
 #endif
143 143
 
144
-#if ENABLED(PID_EXTRUSION_SCALING)
144
+#if EITHER(MPCTEMP, PID_EXTRUSION_SCALING)
145
+  #include <math.h>
145 146
   #include "stepper.h"
146 147
 #endif
147 148
 
@@ -503,10 +504,14 @@ PGMSTR(str_t_heating_failed, STR_T_HEATING_FAILED);
503 504
 volatile bool Temperature::raw_temps_ready = false;
504 505
 
505 506
 #if ENABLED(PID_EXTRUSION_SCALING)
506
-  int32_t Temperature::last_e_position, Temperature::lpq[LPQ_MAX_LEN];
507
+  int32_t Temperature::pes_e_position, Temperature::lpq[LPQ_MAX_LEN];
507 508
   lpq_ptr_t Temperature::lpq_ptr = 0;
508 509
 #endif
509 510
 
511
+#if ENABLED(MPCTEMP)
512
+  int32_t Temperature::mpc_e_position; // = 0
513
+#endif
514
+
510 515
 #define TEMPDIR(N) ((TEMP_SENSOR_##N##_RAW_LO_TEMP) < (TEMP_SENSOR_##N##_RAW_HI_TEMP) ? 1 : -1)
511 516
 #define TP_CMP(S,A,B) (TEMPDIR(S) < 0 ? ((A)<(B)) : ((A)>(B)))
512 517
 
@@ -581,8 +586,8 @@ volatile bool Temperature::raw_temps_ready = false;
581 586
     PID_t tune_pid = { 0, 0, 0 };
582 587
     celsius_float_t maxT = 0, minT = 10000;
583 588
 
584
-    const bool isbed = (heater_id == H_BED);
585
-    const bool ischamber = (heater_id == H_CHAMBER);
589
+    const bool isbed = (heater_id == H_BED),
590
+           ischamber = (heater_id == H_CHAMBER);
586 591
 
587 592
     #if ENABLED(PIDTEMPCHAMBER)
588 593
       #define C_TERN(T,A,B) ((T) ? (A) : (B))
@@ -840,6 +845,198 @@ volatile bool Temperature::raw_temps_ready = false;
840 845
 
841 846
 #endif // HAS_PID_HEATING
842 847
 
848
+#if ENABLED(MPCTEMP)
849
+
850
+  void Temperature::MPC_autotune() {
851
+    auto housekeeping = [] (millis_t& ms, celsius_float_t& current_temp, millis_t& next_report_ms) {
852
+      ms = millis();
853
+
854
+      if (updateTemperaturesIfReady()) { // temp sample ready
855
+        current_temp = degHotend(active_extruder);
856
+        TERN_(HAS_FAN_LOGIC, manage_extruder_fans(ms));
857
+      }
858
+
859
+      if (ELAPSED(ms, next_report_ms)) {
860
+        next_report_ms += 1000UL;
861
+        SERIAL_ECHOLNPGM("Temperature ", current_temp);
862
+      }
863
+
864
+      hal.idletask();
865
+    };
866
+
867
+    SERIAL_ECHOLNPGM("Measuring MPC constants for E", active_extruder);
868
+    MPCHeaterInfo& hotend = temp_hotend[active_extruder];
869
+    MPC_t& constants = hotend.constants;
870
+
871
+    // move to center of bed, just above bed height and cool with max fan
872
+    SERIAL_ECHOLNPGM("Moving to tuning position");
873
+    TERN_(HAS_FAN, zero_fan_speeds());
874
+    disable_all_heaters();
875
+    TERN_(HAS_FAN, set_fan_speed(ANY(MPC_FAN_0_ALL_HOTENDS, MPC_FAN_0_ACTIVE_HOTEND) ? 0 : active_extruder, 255));
876
+    TERN_(HAS_FAN, planner.sync_fan_speeds(fan_speed));
877
+    gcode.home_all_axes(true);
878
+    const xyz_pos_t tuningpos = MPC_TUNING_POS;
879
+    do_blocking_move_to(tuningpos);
880
+
881
+    SERIAL_ECHOLNPGM("Cooling to ambient");
882
+    millis_t ms = millis(), next_report_ms = ms, next_test_ms = ms + 10000UL;
883
+    celsius_float_t current_temp = degHotend(active_extruder),
884
+                    ambient_temp = current_temp;
885
+
886
+    wait_for_heatup = true; // Can be interrupted with M108
887
+    while (wait_for_heatup) {
888
+      housekeeping(ms, current_temp, next_report_ms);
889
+
890
+      if (ELAPSED(ms, next_test_ms)) {
891
+        if (current_temp >= ambient_temp) {
892
+          ambient_temp = (ambient_temp + current_temp) / 2.0f;
893
+          break;
894
+        }
895
+        ambient_temp = current_temp;
896
+        next_test_ms += 10000UL;
897
+      }
898
+    }
899
+    TERN_(HAS_FAN, set_fan_speed(ANY(MPC_FAN_0_ALL_HOTENDS, MPC_FAN_0_ACTIVE_HOTEND) ? 0 : active_extruder, 0));
900
+    TERN_(HAS_FAN, planner.sync_fan_speeds(fan_speed));
901
+
902
+    hotend.modeled_ambient_temp = ambient_temp;
903
+
904
+    SERIAL_ECHOLNPGM("Heating to 200C");
905
+    hotend.soft_pwm_amount = MPC_MAX >> 1;
906
+    const millis_t heat_start_time = ms;
907
+    next_test_ms = ms;
908
+    celsius_float_t temp_samples[16];
909
+    uint8_t sample_count = 0;
910
+    uint16_t sample_distance = 1;
911
+    float t1_time = 0;
912
+
913
+    while (wait_for_heatup) {
914
+      housekeeping(ms, current_temp, next_report_ms);
915
+
916
+      if (ELAPSED(ms, next_test_ms)) {
917
+        // record samples between 100C and 200C
918
+        if (current_temp >= 100.0f) {
919
+          // if there are too many samples, space them more widely
920
+          if (sample_count == COUNT(temp_samples)) {
921
+            for (uint8_t i = 0; i < COUNT(temp_samples) / 2; i++)
922
+              temp_samples[i] = temp_samples[i*2];
923
+            sample_count /= 2;
924
+            sample_distance *= 2;
925
+          }
926
+
927
+          if (sample_count == 0) t1_time = float(ms - heat_start_time) / 1000.0f;
928
+          temp_samples[sample_count++] = current_temp;
929
+        }
930
+
931
+        if (current_temp >= 200.0f) break;
932
+
933
+        next_test_ms += 1000UL * sample_distance;
934
+      }
935
+    }
936
+    hotend.soft_pwm_amount = 0;
937
+
938
+    // calculate physical constants from three equally spaced samples
939
+    sample_count = (sample_count + 1) / 2 * 2 - 1;
940
+    const float t1 = temp_samples[0],
941
+                t2 = temp_samples[(sample_count - 1) >> 1],
942
+                t3 = temp_samples[sample_count - 1],
943
+                asymp_temp = (t2 * t2 - t1 * t3) / (2 * t2 - t1 - t3),
944
+                block_responsiveness = -log((t2 - asymp_temp) / (t1 - asymp_temp)) / (sample_distance * (sample_count >> 1));
945
+
946
+    constants.ambient_xfer_coeff_fan0 = constants.heater_power * MPC_MAX / 255 / (asymp_temp - ambient_temp);
947
+    constants.fan255_adjustment = 0.0f;
948
+    constants.block_heat_capacity = constants.ambient_xfer_coeff_fan0 / block_responsiveness;
949
+    constants.sensor_responsiveness = block_responsiveness / (1.0f - (ambient_temp - asymp_temp) * exp(-block_responsiveness * t1_time) / (t1 - asymp_temp));
950
+
951
+    hotend.modeled_block_temp = asymp_temp + (ambient_temp - asymp_temp) * exp(-block_responsiveness * (ms - heat_start_time) / 1000.0f);
952
+    hotend.modeled_sensor_temp = current_temp;
953
+
954
+    // let the system stabilise under MPC control then get a better measure of ambient loss without and with fan
955
+    SERIAL_ECHOLNPGM("Measuring ambient heatloss at target ", hotend.modeled_block_temp);
956
+    hotend.target = hotend.modeled_block_temp;
957
+    next_test_ms = ms + MPC_dT * 1000;
958
+    constexpr millis_t settle_time = 20000UL,
959
+                       test_length = 20000UL;
960
+    millis_t settle_end_ms = ms + settle_time,
961
+             test_end_ms = settle_end_ms + test_length;
962
+    float total_energy_fan0 = 0.0f;
963
+    #if HAS_FAN
964
+      bool fan0_done = false;
965
+      float total_energy_fan255 = 0.0f;
966
+    #endif
967
+    float last_temp = current_temp;
968
+
969
+    while (wait_for_heatup) {
970
+      housekeeping(ms, current_temp, next_report_ms);
971
+
972
+      if (ELAPSED(ms, next_test_ms)) {
973
+        // use MPC to control the temperature, let it settle for 30s and then track power output for 10s
974
+        hotend.soft_pwm_amount = (int)get_pid_output_hotend(active_extruder) >> 1;
975
+
976
+        if (ELAPSED(ms, settle_end_ms) && !ELAPSED(ms, test_end_ms) && TERN1(HAS_FAN, !fan0_done))
977
+          total_energy_fan0 += constants.heater_power * hotend.soft_pwm_amount / 127 * MPC_dT + (last_temp - current_temp) * constants.block_heat_capacity;
978
+        #if HAS_FAN
979
+          else if (ELAPSED(ms, test_end_ms) && !fan0_done) {
980
+            SERIAL_ECHOLNPGM("Measuring ambient heatloss with full fan");
981
+            set_fan_speed(ANY(MPC_FAN_0_ALL_HOTENDS, MPC_FAN_0_ACTIVE_HOTEND) ? 0 : active_extruder, 255);
982
+            planner.sync_fan_speeds(fan_speed);
983
+            settle_end_ms = ms + settle_time;
984
+            test_end_ms = settle_end_ms + test_length;
985
+            fan0_done = true;
986
+          }
987
+          else if (ELAPSED(ms, settle_end_ms) && !ELAPSED(ms, test_end_ms))
988
+            total_energy_fan255 += constants.heater_power * hotend.soft_pwm_amount / 127 * MPC_dT + (last_temp - current_temp) * constants.block_heat_capacity;
989
+        #endif
990
+        else if (ELAPSED(ms, test_end_ms)) break;
991
+
992
+        last_temp = current_temp;
993
+        next_test_ms += MPC_dT * 1000;
994
+      }
995
+
996
+      if (!WITHIN(current_temp, hotend.target - 15.0f, hotend.target + 15.0f)) {
997
+        SERIAL_ECHOLNPGM("Temperature error while measuring ambient loss");
998
+        break;
999
+      }
1000
+    }
1001
+
1002
+    const float power_fan0 = total_energy_fan0 * 1000 / test_length;
1003
+    constants.ambient_xfer_coeff_fan0 = power_fan0 / (hotend.target - ambient_temp);
1004
+
1005
+    #if HAS_FAN
1006
+      const float power_fan255 = total_energy_fan255 * 1000 / test_length,
1007
+                  ambient_xfer_coeff_fan255 = power_fan255 / (hotend.target - ambient_temp);
1008
+      constants.fan255_adjustment = ambient_xfer_coeff_fan255 - constants.ambient_xfer_coeff_fan0;
1009
+    #endif
1010
+
1011
+    hotend.target = 0.0f;
1012
+    hotend.soft_pwm_amount = 0;
1013
+    TERN_(HAS_FAN, set_fan_speed(ANY(MPC_FAN_0_ALL_HOTENDS, MPC_FAN_0_ACTIVE_HOTEND) ? 0 : active_extruder, 0));
1014
+    TERN_(HAS_FAN, planner.sync_fan_speeds(fan_speed));
1015
+
1016
+    if (!wait_for_heatup) SERIAL_ECHOLNPGM("Test was interrupted");
1017
+
1018
+    wait_for_heatup = false;
1019
+
1020
+    SERIAL_ECHOLNPGM("Done");
1021
+
1022
+    /* <-- add a slash to enable
1023
+      SERIAL_ECHOLNPGM("t1_time ", t1_time);
1024
+      SERIAL_ECHOLNPGM("sample_count ", sample_count);
1025
+      SERIAL_ECHOLNPGM("sample_distance ", sample_distance);
1026
+      for (uint8_t i = 0; i < sample_count; i++)
1027
+        SERIAL_ECHOLNPGM("sample ", i, " : ", temp_samples[i]);
1028
+      SERIAL_ECHOLNPGM("t1 ", t1, " t2 ", t2, " t3 ", t3);
1029
+      SERIAL_ECHOLNPGM("asymp_temp ", asymp_temp);
1030
+      SERIAL_ECHOLNPAIR_F("block_responsiveness ", block_responsiveness, 4);
1031
+    //*/
1032
+    SERIAL_ECHOLNPGM("MPC_BLOCK_HEAT_CAPACITY ", constants.block_heat_capacity);
1033
+    SERIAL_ECHOLNPAIR_F("MPC_SENSOR_RESPONSIVENESS ", constants.sensor_responsiveness, 4);
1034
+    SERIAL_ECHOLNPAIR_F("MPC_AMBIENT_XFER_COEFF ", constants.ambient_xfer_coeff_fan0, 4);
1035
+    TERN_(HAS_FAN, SERIAL_ECHOLNPAIR_F("MPC_AMBIENT_XFER_COEFF_FAN255 ", ambient_xfer_coeff_fan255, 4));
1036
+  }
1037
+
1038
+#endif // MPCTEMP
1039
+
843 1040
 int16_t Temperature::getHeaterPower(const heater_id_t heater_id) {
844 1041
   switch (heater_id) {
845 1042
     #if HAS_HEATED_BED
@@ -1099,7 +1296,7 @@ void Temperature::min_temp_error(const heater_id_t heater_id) {
1099 1296
           pid_reset[ee] = true;
1100 1297
         }
1101 1298
         else if (pid_error > PID_FUNCTIONAL_RANGE) {
1102
-          pid_output = BANG_MAX;
1299
+          pid_output = PID_MAX;
1103 1300
           pid_reset[ee] = true;
1104 1301
         }
1105 1302
         else {
@@ -1126,9 +1323,9 @@ void Temperature::min_temp_error(const heater_id_t heater_id) {
1126 1323
             work_pid[ee].Kc = 0;
1127 1324
             if (this_hotend) {
1128 1325
               const long e_position = stepper.position(E_AXIS);
1129
-              if (e_position > last_e_position) {
1130
-                lpq[lpq_ptr] = e_position - last_e_position;
1131
-                last_e_position = e_position;
1326
+              if (e_position > pes_e_position) {
1327
+                lpq[lpq_ptr] = e_position - pes_e_position;
1328
+                pes_e_position = e_position;
1132 1329
               }
1133 1330
               else
1134 1331
                 lpq[lpq_ptr] = 0;
@@ -1171,7 +1368,86 @@ void Temperature::min_temp_error(const heater_id_t heater_id) {
1171 1368
         }
1172 1369
       #endif
1173 1370
 
1174
-    #else // No PID enabled
1371
+    #elif ENABLED(MPCTEMP)
1372
+      MPCHeaterInfo& hotend = temp_hotend[ee];
1373
+      MPC_t& constants = hotend.constants;
1374
+
1375
+      // At startup, initialize modeled temperatures
1376
+      if (isnan(hotend.modeled_block_temp)) {
1377
+        hotend.modeled_ambient_temp = min(30.0f, hotend.celsius);   // cap initial value at reasonable max room temperature of 30C
1378
+        hotend.modeled_block_temp = hotend.modeled_sensor_temp = hotend.celsius;
1379
+      }
1380
+
1381
+      #if HOTENDS == 1
1382
+        constexpr bool this_hotend = true;
1383
+      #else
1384
+        const bool this_hotend = (ee == active_extruder);
1385
+      #endif
1386
+
1387
+      float ambient_xfer_coeff = constants.ambient_xfer_coeff_fan0;
1388
+      #if ENABLED(MPC_INCLUDE_FAN)
1389
+        const uint8_t fan_index = ANY(MPC_FAN_0_ACTIVE_HOTEND, MPC_FAN_0_ALL_HOTENDS) ? 0 : ee;
1390
+        const float fan_fraction = TERN_(MPC_FAN_0_ACTIVE_HOTEND, !this_hotend ? 0.0f : ) fan_speed[fan_index] * RECIPROCAL(255);
1391
+        ambient_xfer_coeff += fan_fraction * constants.fan255_adjustment;
1392
+      #endif
1393
+
1394
+      if (this_hotend) {
1395
+        const int32_t e_position = stepper.position(E_AXIS);
1396
+        const float e_speed = (e_position - mpc_e_position) * planner.mm_per_step[E_AXIS] / MPC_dT;
1397
+
1398
+        // the position can appear to make big jumps when, e.g. homing
1399
+        if (fabs(e_speed) > planner.settings.max_feedrate_mm_s[E_AXIS])
1400
+          mpc_e_position = e_position;
1401
+        else if (e_speed > 0.0f) {  // ignore retract/recover moves
1402
+          ambient_xfer_coeff += e_speed * FILAMENT_HEAT_CAPACITY_PERMM;
1403
+          mpc_e_position = e_position;
1404
+        }
1405
+      }
1406
+
1407
+      // update the modeled temperatures
1408
+      float blocktempdelta = hotend.soft_pwm_amount * constants.heater_power * (MPC_dT / 127) / constants.block_heat_capacity;
1409
+      blocktempdelta += (hotend.modeled_ambient_temp - hotend.modeled_block_temp) * ambient_xfer_coeff * MPC_dT / constants.block_heat_capacity;
1410
+      hotend.modeled_block_temp += blocktempdelta;
1411
+
1412
+      const float sensortempdelta = (hotend.modeled_block_temp - hotend.modeled_sensor_temp) * (constants.sensor_responsiveness * MPC_dT);
1413
+      hotend.modeled_sensor_temp += sensortempdelta;
1414
+
1415
+      // Any delta between hotend.modeled_sensor_temp and hotend.celsius is either model
1416
+      // error diverging slowly or (fast) noise. Slowly correct towards this temperature and noise will average out.
1417
+      const float delta_to_apply = (hotend.celsius - hotend.modeled_sensor_temp) * (MPC_SMOOTHING_FACTOR);
1418
+      hotend.modeled_block_temp += delta_to_apply;
1419
+      hotend.modeled_sensor_temp += delta_to_apply;
1420
+
1421
+      // only correct ambient when close to steady state (output power is not clipped or asymptotic temperature is reached)
1422
+      if (WITHIN(hotend.soft_pwm_amount, 1, 126) || fabs(blocktempdelta + delta_to_apply) < (MPC_STEADYSTATE * MPC_dT))
1423
+        hotend.modeled_ambient_temp += delta_to_apply > 0.f ? max(delta_to_apply, MPC_MIN_AMBIENT_CHANGE * MPC_dT) : min(delta_to_apply, -MPC_MIN_AMBIENT_CHANGE * MPC_dT);
1424
+
1425
+      float power = 0.0;
1426
+      if (hotend.target != 0 && TERN1(HEATER_IDLE_HANDLER, !heater_idle[ee].timed_out)) {
1427
+        // plan power level to get to target temperature in 2 seconds
1428
+        power = (hotend.target - hotend.modeled_block_temp) * constants.block_heat_capacity / 2.0f;
1429
+        power -= (hotend.modeled_ambient_temp - hotend.modeled_block_temp) * ambient_xfer_coeff;
1430
+      }
1431
+
1432
+      float pid_output = power * 254.0f / constants.heater_power + 1.0f;        // ensure correct quantization into a range of 0 to 127
1433
+      pid_output = constrain(pid_output, 0, MPC_MAX);
1434
+
1435
+      /* <-- add a slash to enable
1436
+        static uint32_t nexttime = millis() + 1000;
1437
+        if (ELAPSED(millis(), nexttime)) {
1438
+          nexttime += 1000;
1439
+          SERIAL_ECHOLNPGM("block temp ", hotend.modeled_block_temp,
1440
+                           ", celsius ", hotend.celsius,
1441
+                           ", blocktempdelta ", blocktempdelta,
1442
+                           ", delta_to_apply ", delta_to_apply,
1443
+                           ", ambient ", hotend.modeled_ambient_temp,
1444
+                           ", power ", power,
1445
+                           ", pid_output ", pid_output,
1446
+                           ", pwm ", (int)pid_output >> 1);
1447
+        }
1448
+      //*/
1449
+
1450
+    #else // No PID or MPC enabled
1175 1451
 
1176 1452
       const bool is_idling = TERN0(HEATER_IDLE_HANDLER, heater_idle[ee].timed_out);
1177 1453
       const float pid_output = (!is_idling && temp_hotend[ee].celsius < temp_hotend[ee].target) ? BANG_MAX : 0;
@@ -2176,7 +2452,7 @@ void Temperature::init() {
2176 2452
   TERN_(PROBING_HEATERS_OFF, paused_for_probing = false);
2177 2453
 
2178 2454
   #if BOTH(PIDTEMP, PID_EXTRUSION_SCALING)
2179
-    last_e_position = 0;
2455
+    pes_e_position = 0;
2180 2456
   #endif
2181 2457
 
2182 2458
   // Init (and disable) SPI thermocouples
@@ -2246,6 +2522,10 @@ void Temperature::init() {
2246 2522
     ));
2247 2523
   #endif
2248 2524
 
2525
+  #if ENABLED(MPCTEMP)
2526
+    HOTEND_LOOP() temp_hotend[e].modeled_block_temp = NAN;
2527
+  #endif
2528
+
2249 2529
   #if HAS_HEATER_0
2250 2530
     #ifdef BOARD_OPENDRAIN_MOSFETS
2251 2531
       OUT_WRITE_OD(HEATER_0_PIN, HEATER_0_INVERTING);

+ 38
- 3
Marlin/src/module/temperature.h View File

@@ -94,6 +94,18 @@ hotend_pid_t;
94 94
   #define _PID_Kf(H) 0
95 95
 #endif
96 96
 
97
+#if ENABLED(MPCTEMP)
98
+  typedef struct {
99
+    float heater_power;             // M306 P
100
+    float block_heat_capacity;      // M306 C
101
+    float sensor_responsiveness;    // M306 R
102
+    float ambient_xfer_coeff_fan0;  // M306 A
103
+    #if ENABLED(MPC_INCLUDE_FAN)
104
+      float fan255_adjustment;      // M306 F
105
+    #endif
106
+  } MPC_t;
107
+#endif
108
+
97 109
 /**
98 110
  * States for ADC reading in the ISR
99 111
  */
@@ -177,7 +189,7 @@ enum ADCSensorState : char {
177 189
 
178 190
 #if HAS_PID_HEATING
179 191
   #define PID_K2 (1-float(PID_K1))
180
-  #define PID_dT ((OVERSAMPLENR * float(ACTUAL_ADC_SAMPLES)) / TEMP_TIMER_FREQUENCY)
192
+  #define PID_dT ((OVERSAMPLENR * float(ACTUAL_ADC_SAMPLES)) / (TEMP_TIMER_FREQUENCY))
181 193
 
182 194
   // Apply the scale factors to the PID values
183 195
   #define scalePID_i(i)   ( float(i) * PID_dT )
@@ -186,6 +198,10 @@ enum ADCSensorState : char {
186 198
   #define unscalePID_d(d) ( float(d) * PID_dT )
187 199
 #endif
188 200
 
201
+#if ENABLED(MPCTEMP)
202
+  #define MPC_dT ((OVERSAMPLENR * float(ACTUAL_ADC_SAMPLES)) / (TEMP_TIMER_FREQUENCY))
203
+#endif
204
+
189 205
 #if ENABLED(G26_MESH_VALIDATION) && EITHER(HAS_MARLINUI_MENU, EXTENSIBLE_UI)
190 206
   #define G26_CLICK_CAN_CANCEL 1
191 207
 #endif
@@ -223,8 +239,19 @@ struct PIDHeaterInfo : public HeaterInfo {
223 239
   T pid;  // Initialized by settings.load()
224 240
 };
225 241
 
242
+#if ENABLED(MPCTEMP)
243
+  struct MPCHeaterInfo : public HeaterInfo {
244
+    MPC_t constants;
245
+    float modeled_ambient_temp,
246
+          modeled_block_temp,
247
+          modeled_sensor_temp;
248
+  };
249
+#endif
250
+
226 251
 #if ENABLED(PIDTEMP)
227 252
   typedef struct PIDHeaterInfo<hotend_pid_t> hotend_info_t;
253
+#elif ENABLED(MPCTEMP)
254
+  typedef struct MPCHeaterInfo hotend_info_t;
228 255
 #else
229 256
   typedef heater_info_t hotend_info_t;
230 257
 #endif
@@ -481,10 +508,14 @@ class Temperature {
481 508
     #endif
482 509
 
483 510
     #if ENABLED(PID_EXTRUSION_SCALING)
484
-      static int32_t last_e_position, lpq[LPQ_MAX_LEN];
511
+      static int32_t pes_e_position, lpq[LPQ_MAX_LEN];
485 512
       static lpq_ptr_t lpq_ptr;
486 513
     #endif
487 514
 
515
+    #if ENABLED(MPCTEMP)
516
+      static int32_t mpc_e_position;
517
+    #endif
518
+
488 519
     #if HAS_HOTEND
489 520
       static temp_range_t temp_range[HOTENDS];
490 521
     #endif
@@ -924,12 +955,16 @@ class Temperature {
924 955
        */
925 956
       #if ENABLED(PIDTEMP)
926 957
         static void updatePID() {
927
-          TERN_(PID_EXTRUSION_SCALING, last_e_position = 0);
958
+          TERN_(PID_EXTRUSION_SCALING, pes_e_position = 0);
928 959
         }
929 960
       #endif
930 961
 
931 962
     #endif
932 963
 
964
+    #if ENABLED(MPCTEMP)
965
+      void MPC_autotune();
966
+    #endif
967
+
933 968
     #if ENABLED(PROBING_HEATERS_OFF)
934 969
       static void pause_heaters(const bool p);
935 970
     #endif

+ 1
- 0
ini/features.ini View File

@@ -218,6 +218,7 @@ HAS_EXTRUDERS                          = src_filter=+<src/gcode/units/M82_M83.cp
218 218
 HAS_TEMP_PROBE                         = src_filter=+<src/gcode/temp/M192.cpp>
219 219
 HAS_COOLER                             = src_filter=+<src/gcode/temp/M143_M193.cpp>
220 220
 AUTO_REPORT_TEMPERATURES               = src_filter=+<src/gcode/temp/M155.cpp>
221
+MPCTEMP                                = src_filter=+<src/gcode/temp/M306.cpp>
221 222
 INCH_MODE_SUPPORT                      = src_filter=+<src/gcode/units/G20_G21.cpp>
222 223
 TEMPERATURE_UNITS_SUPPORT              = src_filter=+<src/gcode/units/M149.cpp>
223 224
 NEED_HEX_PRINT                         = src_filter=+<src/libs/hex_print.cpp>

+ 1
- 0
platformio.ini View File

@@ -239,6 +239,7 @@ default_src_filter = +<src/*> -<src/config> -<src/HAL> +<src/HAL/shared>
239 239
   -<src/gcode/temp/M123.cpp>
240 240
   -<src/gcode/temp/M155.cpp>
241 241
   -<src/gcode/temp/M192.cpp>
242
+  -<src/gcode/temp/M306.cpp>
242 243
   -<src/gcode/units/G20_G21.cpp>
243 244
   -<src/gcode/units/M82_M83.cpp>
244 245
   -<src/gcode/units/M149.cpp>

Loading…
Cancel
Save