Skip to content

Commit

Permalink
⚗️ Temperature Model Predictive Control (MarlinFirmware#23751)
Browse files Browse the repository at this point in the history
  • Loading branch information
tombrazier authored and thinkyhead committed Apr 1, 2022
1 parent 8fb6521 commit 72b2e2b
Show file tree
Hide file tree
Showing 13 changed files with 566 additions and 32 deletions.
48 changes: 44 additions & 4 deletions Marlin/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -608,10 +608,12 @@
//===========================================================================
//============================= PID Settings ================================
//===========================================================================
// PID Tuning Guide here: https://reprap.org/wiki/PID_Tuning

// Comment the following line to disable PID and enable bang-bang.
#define PIDTEMP
// Enable PIDTEMP for PID control or MPCTEMP for Predictive Model.
// temperature control. Disable both for bang-bang heating.
#define PIDTEMP // See the PID Tuning Guide at https://reprap.org/wiki/PID_Tuning
//#define MPCTEMP // ** EXPERIMENTAL **

#define BANG_MAX 255 // Limits current to nozzle while in bang-bang mode; 255=full current
#define PID_MAX BANG_MAX // Limits current to nozzle while PID is active (see PID_FUNCTIONAL_RANGE below); 255=full current
#define PID_K1 0.95 // Smoothing factor within any PID loop
Expand All @@ -633,7 +635,45 @@
#define DEFAULT_Ki 1.08
#define DEFAULT_Kd 114.00
#endif
#endif // PIDTEMP
#endif

/**
* Model Predictive Control for hotend
*
* Use a physical model of the hotend to control temperature. When configured correctly
* this gives better responsiveness and stability than PID and it also removes the need
* for PID_EXTRUSION_SCALING and PID_FAN_SCALING. Use M306 to autotune the model.
*/
#if ENABLED(MPCTEMP)
#define MPC_MAX BANG_MAX // (0..255) Current to nozzle while MPC is active.
#define MPC_HEATER_POWER { 40.0f } // (W) Heat cartridge powers.

#define MPC_INCLUDE_FAN // Model the fan speed?

// Measured physical constants from M306
#define MPC_BLOCK_HEAT_CAPACITY { 16.7f } // (J/K) Heat block heat capacities.
#define MPC_SENSOR_RESPONSIVENESS { 0.22f } // (K/s per ∆K) Rate of change of sensor temperature from heat block.
#define MPC_AMBIENT_XFER_COEFF { 0.068f } // (W/K) Heat transfer coefficients from heat block to room air with fan off.
#if ENABLED(MPC_INCLUDE_FAN)
#define MPC_AMBIENT_XFER_COEFF_FAN255 { 0.097f } // (W/K) Heat transfer coefficients from heat block to room air with fan on full.
#endif

// For one fan and multiple hotends MPC needs to know how to apply the fan cooling effect.
#if ENABLED(MPC_INCLUDE_FAN)
//#define MPC_FAN_0_ALL_HOTENDS
//#define MPC_FAN_0_ACTIVE_HOTEND
#endif

#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).
//#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).

// Advanced options
#define MPC_SMOOTHING_FACTOR 0.5f // (0.0...1.0) Noisy temperature sensors may need a lower value for stabilization.
#define MPC_MIN_AMBIENT_CHANGE 1.0f // (K/s) Modeled ambient temperature rate of change, when correcting model inaccuracies.
#define MPC_STEADYSTATE 0.5f // (K/s) Temperature change rate for steady state logic to be enforced.

#define MPC_TUNING_POS { X_CENTER, Y_CENTER, 1.0f } // (mm) M306 Autotuning position, ideally bed center just above the surface.
#endif

//===========================================================================
//====================== PID > Bed Temperature Control ======================
Expand Down
26 changes: 13 additions & 13 deletions Marlin/src/HAL/ESP32/HAL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -209,19 +209,19 @@ void MarlinHAL::adc_init() {
adc1_config_width(ADC_WIDTH_12Bit);

// Configure channels only if used as (re-)configuring a pin for ADC that is used elsewhere might have adverse effects
TERN_(HAS_TEMP_ADC_0, adc1_set_attenuation(get_channel(TEMP_0_PIN), ADC_ATTEN_11db));
TERN_(HAS_TEMP_ADC_1, adc1_set_attenuation(get_channel(TEMP_1_PIN), ADC_ATTEN_11db));
TERN_(HAS_TEMP_ADC_2, adc1_set_attenuation(get_channel(TEMP_2_PIN), ADC_ATTEN_11db));
TERN_(HAS_TEMP_ADC_3, adc1_set_attenuation(get_channel(TEMP_3_PIN), ADC_ATTEN_11db));
TERN_(HAS_TEMP_ADC_4, adc1_set_attenuation(get_channel(TEMP_4_PIN), ADC_ATTEN_11db));
TERN_(HAS_TEMP_ADC_5, adc1_set_attenuation(get_channel(TEMP_5_PIN), ADC_ATTEN_11db));
TERN_(HAS_TEMP_ADC_6, adc2_set_attenuation(get_channel(TEMP_6_PIN), ADC_ATTEN_11db));
TERN_(HAS_TEMP_ADC_7, adc3_set_attenuation(get_channel(TEMP_7_PIN), ADC_ATTEN_11db));
TERN_(HAS_HEATED_BED, adc1_set_attenuation(get_channel(TEMP_BED_PIN), ADC_ATTEN_11db));
TERN_(HAS_TEMP_CHAMBER, adc1_set_attenuation(get_channel(TEMP_CHAMBER_PIN), ADC_ATTEN_11db));
TERN_(HAS_TEMP_PROBE, adc1_set_attenuation(get_channel(TEMP_PROBE_PIN), ADC_ATTEN_11db));
TERN_(HAS_TEMP_COOLER, adc1_set_attenuation(get_channel(TEMP_COOLER_PIN), ADC_ATTEN_11db));
TERN_(HAS_TEMP_BOARD, adc1_set_attenuation(get_channel(TEMP_BOARD_PIN), ADC_ATTEN_11db));
TERN_(HAS_TEMP_ADC_0, adc1_set_attenuation(get_channel(TEMP_0_PIN), ADC_ATTEN_11db));
TERN_(HAS_TEMP_ADC_1, adc1_set_attenuation(get_channel(TEMP_1_PIN), ADC_ATTEN_11db));
TERN_(HAS_TEMP_ADC_2, adc1_set_attenuation(get_channel(TEMP_2_PIN), ADC_ATTEN_11db));
TERN_(HAS_TEMP_ADC_3, adc1_set_attenuation(get_channel(TEMP_3_PIN), ADC_ATTEN_11db));
TERN_(HAS_TEMP_ADC_4, adc1_set_attenuation(get_channel(TEMP_4_PIN), ADC_ATTEN_11db));
TERN_(HAS_TEMP_ADC_5, adc1_set_attenuation(get_channel(TEMP_5_PIN), ADC_ATTEN_11db));
TERN_(HAS_TEMP_ADC_6, adc2_set_attenuation(get_channel(TEMP_6_PIN), ADC_ATTEN_11db));
TERN_(HAS_TEMP_ADC_7, adc3_set_attenuation(get_channel(TEMP_7_PIN), ADC_ATTEN_11db));
TERN_(HAS_HEATED_BED, adc1_set_attenuation(get_channel(TEMP_BED_PIN), ADC_ATTEN_11db));
TERN_(HAS_TEMP_CHAMBER, adc1_set_attenuation(get_channel(TEMP_CHAMBER_PIN), ADC_ATTEN_11db));
TERN_(HAS_TEMP_PROBE, adc1_set_attenuation(get_channel(TEMP_PROBE_PIN), ADC_ATTEN_11db));
TERN_(HAS_TEMP_COOLER, adc1_set_attenuation(get_channel(TEMP_COOLER_PIN), ADC_ATTEN_11db));
TERN_(HAS_TEMP_BOARD, adc1_set_attenuation(get_channel(TEMP_BOARD_PIN), ADC_ATTEN_11db));
TERN_(FILAMENT_WIDTH_SENSOR, adc1_set_attenuation(get_channel(FILWIDTH_PIN), ADC_ATTEN_11db));

// Note that adc2 is shared with the WiFi module, which has higher priority, so the conversion may fail.
Expand Down
4 changes: 4 additions & 0 deletions Marlin/src/gcode/gcode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,10 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
case 305: M305(); break; // M305: Set user thermistor parameters
#endif

#if ENABLED(MPCTEMP)
case 306: M306(); break; // M306: MPC autotune
#endif

#if ENABLED(REPETIER_GCODE_M360)
case 360: M360(); break; // M360: Firmware settings
#endif
Expand Down
6 changes: 6 additions & 0 deletions Marlin/src/gcode/gcode.h
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@
* M303 - PID relay autotune S<temperature> 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)
* M306 - MPC autotune. (Requires MPCTEMP)
* 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.)
Expand Down Expand Up @@ -929,6 +930,11 @@ class GcodeSuite {
static void M305();
#endif

#if ENABLED(MPCTEMP)
static void M306();
static void M306_report(const bool forReplay=true);
#endif

#if ENABLED(PIDTEMPCHAMBER)
static void M309();
static void M309_report(const bool forReplay=true);
Expand Down
86 changes: 86 additions & 0 deletions Marlin/src/gcode/temp/M306.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2022 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 <https://www.gnu.org/licenses/>.
*
*/

#include "../../inc/MarlinConfig.h"

#if ENABLED(MPCTEMP)

#include "../gcode.h"
#include "../../module/temperature.h"

/**
* M306: MPC settings and autotune
*
* T Autotune the active extruder.
*
* A<watts/kelvin> Ambient heat transfer coefficient (no fan).
* C<joules/kelvin> Block heat capacity.
* E<extruder> Extruder number to set. (Default: E0)
* F<watts/kelvin> Ambient heat transfer coefficient (fan on full).
* P<watts> Heater power.
* R<kelvin/second/kelvin> Sensor responsiveness (= transfer coefficient / heat capcity).
*/

void GcodeSuite::M306() {
if (parser.seen_test('T')) { thermalManager.MPC_autotune(); return; }

if (parser.seen("ACFPR")) {
const heater_id_t hid = (heater_id_t)parser.intval('E', 0);
MPC_t &constants = thermalManager.temp_hotend[hid].constants;
if (parser.seenval('P')) constants.heater_power = parser.value_float();
if (parser.seenval('C')) constants.block_heat_capacity = parser.value_float();
if (parser.seenval('R')) constants.sensor_responsiveness = parser.value_float();
if (parser.seenval('A')) constants.ambient_xfer_coeff_fan0 = parser.value_float();
#if ENABLED(MPC_INCLUDE_FAN)
if (parser.seenval('F')) constants.fan255_adjustment = parser.value_float() - constants.ambient_xfer_coeff_fan0;
#endif
return;
}

HOTEND_LOOP() {
SERIAL_ECHOLNPGM("MPC constants for hotend ", e);
MPC_t& constants = thermalManager.temp_hotend[e].constants;
SERIAL_ECHOLNPGM("Heater power: ", constants.heater_power);
SERIAL_ECHOLNPGM("Heatblock heat capacity: ", constants.block_heat_capacity);
SERIAL_ECHOLNPAIR_F("Sensor responsivness: ", constants.sensor_responsiveness, 4);
SERIAL_ECHOLNPAIR_F("Ambient heat transfer coeff. (no fan): ", constants.ambient_xfer_coeff_fan0, 4);
#if ENABLED(MPC_INCLUDE_FAN)
SERIAL_ECHOLNPAIR_F("Ambient heat transfer coeff. (full fan): ", constants.ambient_xfer_coeff_fan0 + constants.fan255_adjustment, 4);
#endif
}
}

void GcodeSuite::M306_report(const bool forReplay/*=true*/) {
report_heading(forReplay, F("Model predictive control"));
HOTEND_LOOP() {
report_echo_start(forReplay);
MPC_t& constants = thermalManager.temp_hotend[e].constants;
SERIAL_ECHOPGM(" M306 E", e);
SERIAL_ECHOPAIR_F(" P", constants.heater_power, 2);
SERIAL_ECHOPAIR_F(" C", constants.block_heat_capacity, 2);
SERIAL_ECHOPAIR_F(" R", constants.sensor_responsiveness, 4);
SERIAL_ECHOPAIR_F(" A", constants.ambient_xfer_coeff_fan0, 4);
SERIAL_ECHOLNPAIR_F(" F", constants.ambient_xfer_coeff_fan0 + constants.fan255_adjustment, 4);
}
}

#endif // MPCTEMP
20 changes: 20 additions & 0 deletions Marlin/src/inc/SanityCheck.h
Original file line number Diff line number Diff line change
Expand Up @@ -1405,6 +1405,26 @@ static_assert(Y_MAX_LENGTH >= Y_BED_SIZE, "Movement bounds (Y_MIN_POS, Y_MAX_POS
#error "You must set DISPLAY_CHARSET_HD44780 to JAPANESE, WESTERN or CYRILLIC for your LCD controller."
#endif

/**
* Extruder temperature control algorithm - There can be only one!
*/
#if BOTH(PIDTEMP, MPCTEMP)
#error "Only enable PIDTEMP or MPCTEMP, but not both."
#endif

#if ENABLED(MPC_INCLUDE_FAN)
#if FAN_COUNT < 1
#error "MPC_INCLUDE_FAN requires at least one fan."
#endif
#if FAN_COUNT < HOTENDS
#if COUNT_ENABLED(MPC_FAN_0_ALL_HOTENDS, MPC_FAN_0_ACTIVE_HOTEND) > 1
#error "Enable either MPC_FAN_0_ALL_HOTENDS or MPC_FAN_0_ACTIVE_HOTEND, not both."
#elif NONE(MPC_FAN_0_ALL_HOTENDS, MPC_FAN_0_ACTIVE_HOTEND)
#error "MPC_INCLUDE_FAN requires MPC_FAN_0_ALL_HOTENDS or MPC_FAN_0_ACTIVE_HOTEND for one fan with multiple hotends."
#endif
#endif
#endif

/**
* Bed Heating Options - PID vs Limit Switching
*/
Expand Down
2 changes: 1 addition & 1 deletion Marlin/src/lcd/extui/anycubic_chiron/chiron_tft.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ void ChironTFT::Startup() {
// opt_enable FIL_RUNOUT_PULLUP
TFTSer.begin(115200);

// wait for the TFT panel to initialise and finish the animation
// Wait for the TFT panel to initialize and finish the animation
safe_delay(1000);

// There are different panels for the Chiron with slightly different commands
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ void AnycubicTFTClass::OnSetup() {
SENDLINE_DBG_PGM("J17", "TFT Serial Debug: Main board reset... J17"); // J17 Main board reset
delay_ms(10);

// initialise the state of the key pins running on the tft
// Init the state of the key pins running on the TFT
#if ENABLED(SDSUPPORT) && PIN_EXISTS(SD_DETECT)
SET_INPUT_PULLUP(SD_DETECT_PIN);
#endif
Expand Down
61 changes: 61 additions & 0 deletions Marlin/src/module/settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,13 @@ typedef struct SettingsDataStruct {
uint8_t ui_language; // M414 S
#endif

//
// Model predictive control
//
#if ENABLED(MPCTEMP)
MPC_t mpc_constants[HOTENDS]; // M306
#endif

} SettingsData;

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

//
// Model predictive control
//
#if ENABLED(MPCTEMP)
HOTEND_LOOP()
EEPROM_WRITE(thermalManager.temp_hotend[e].constants);
#endif

//
// Report final CRC and Data Size
//
Expand Down Expand Up @@ -2549,6 +2564,16 @@ void MarlinSettings::postprocess() {
}
#endif

//
// Model predictive control
//
#if ENABLED(MPCTEMP)
{
HOTEND_LOOP()
EEPROM_READ(thermalManager.temp_hotend[e].constants);
}
#endif

//
// Validate Final Size and CRC
//
Expand Down Expand Up @@ -3267,6 +3292,37 @@ void MarlinSettings::reset() {
#endif

TERN_(EXTENSIBLE_UI, ExtUI::onFactoryReset());

//
// Model predictive control
//
#if ENABLED(MPCTEMP)
constexpr float _mpc_heater_power[] = MPC_HEATER_POWER;
constexpr float _mpc_block_heat_capacity[] = MPC_BLOCK_HEAT_CAPACITY;
constexpr float _mpc_sensor_responsiveness[] = MPC_SENSOR_RESPONSIVENESS;
constexpr float _mpc_ambient_xfer_coeff[] = MPC_AMBIENT_XFER_COEFF;
#if ENABLED(MPC_INCLUDE_FAN)
constexpr float _mpc_ambient_xfer_coeff_fan255[] = MPC_AMBIENT_XFER_COEFF_FAN255;
#endif

static_assert(COUNT(_mpc_heater_power) == HOTENDS, "MPC_HEATER_POWER must have HOTENDS items.");
static_assert(COUNT(_mpc_block_heat_capacity) == HOTENDS, "MPC_BLOCK_HEAT_CAPACITY must have HOTENDS items.");
static_assert(COUNT(_mpc_sensor_responsiveness) == HOTENDS, "MPC_SENSOR_RESPONSIVENESS must have HOTENDS items.");
static_assert(COUNT(_mpc_ambient_xfer_coeff) == HOTENDS, "MPC_AMBIENT_XFER_COEFF must have HOTENDS items.");
#if ENABLED(MPC_INCLUDE_FAN)
static_assert(COUNT(_mpc_ambient_xfer_coeff_fan255) == HOTENDS, "MPC_AMBIENT_XFER_COEFF_FAN255 must have HOTENDS items.");
#endif

HOTEND_LOOP() {
thermalManager.temp_hotend[e].constants.heater_power = _mpc_heater_power[e];
thermalManager.temp_hotend[e].constants.block_heat_capacity = _mpc_block_heat_capacity[e];
thermalManager.temp_hotend[e].constants.sensor_responsiveness = _mpc_sensor_responsiveness[e];
thermalManager.temp_hotend[e].constants.ambient_xfer_coeff_fan0 = _mpc_ambient_xfer_coeff[e];
#if ENABLED(MPC_INCLUDE_FAN)
thermalManager.temp_hotend[e].constants.fan255_adjustment = _mpc_ambient_xfer_coeff_fan255[e] - _mpc_ambient_xfer_coeff[e];
#endif
}
#endif
}

#if DISABLED(DISABLE_M503)
Expand Down Expand Up @@ -3543,6 +3599,11 @@ void MarlinSettings::reset() {
#endif

TERN_(HAS_MULTI_LANGUAGE, gcode.M414_report(forReplay));

//
// Model predictive control
//
TERN_(MPCTEMP, gcode.M306_report(forReplay));
}

#endif // !DISABLE_M503
Expand Down
Loading

0 comments on commit 72b2e2b

Please sign in to comment.