🚸 Improved MPCTEMP autotune (#25503)
This commit is contained in:
parent
8fb9b5804e
commit
01f5bd3330
|
@ -650,13 +650,17 @@
|
|||
|
||||
// @section hotend temp
|
||||
|
||||
// Enable PIDTEMP for PID control or MPCTEMP for Predictive Model.
|
||||
// temperature control. Disable both for bang-bang heating.
|
||||
/**
|
||||
* Temperature Control
|
||||
*
|
||||
* (NONE) : Bang-bang heating
|
||||
* PIDTEMP : PID temperature control (~4.1K)
|
||||
* MPCTEMP : Predictive Model temperature control. (~1.8K without auto-tune)
|
||||
*/
|
||||
#define PIDTEMP // See the PID Tuning Guide at https://reprap.org/wiki/PID_Tuning
|
||||
//#define MPCTEMP // ** EXPERIMENTAL **
|
||||
//#define MPCTEMP // ** EXPERIMENTAL ** See https://marlinfw.org/docs/features/model_predictive_control.html
|
||||
|
||||
#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_MAX 255 // Limit hotend current while PID is active (see PID_FUNCTIONAL_RANGE below); 255=full current
|
||||
#define PID_K1 0.95 // Smoothing factor within any PID loop
|
||||
|
||||
#if ENABLED(PIDTEMP)
|
||||
|
@ -675,6 +679,8 @@
|
|||
#define DEFAULT_Ki 1.08
|
||||
#define DEFAULT_Kd 114.00
|
||||
#endif
|
||||
#else
|
||||
#define BANG_MAX 255 // Limit hotend current while in bang-bang mode; 255=full current
|
||||
#endif
|
||||
|
||||
/**
|
||||
|
@ -686,11 +692,11 @@
|
|||
* @section mpctemp
|
||||
*/
|
||||
#if ENABLED(MPCTEMP)
|
||||
//#define MPC_AUTOTUNE // Include a method to do MPC auto-tuning (~5664-5882 bytes of flash)
|
||||
//#define MPC_EDIT_MENU // Add MPC editing to the "Advanced Settings" menu. (~1300 bytes of flash)
|
||||
//#define MPC_AUTOTUNE // Include a method to do MPC auto-tuning (~6.3K bytes of flash)
|
||||
//#define MPC_EDIT_MENU // Add MPC editing to the "Advanced Settings" menu. (~1.3K bytes of flash)
|
||||
//#define MPC_AUTOTUNE_MENU // Add MPC auto-tuning to the "Advanced Settings" menu. (~350 bytes of flash)
|
||||
|
||||
#define MPC_MAX BANG_MAX // (0..255) Current to nozzle while MPC is active.
|
||||
#define MPC_MAX 255 // (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?
|
||||
|
@ -725,23 +731,7 @@
|
|||
//====================== PID > Bed Temperature Control ======================
|
||||
//===========================================================================
|
||||
|
||||
/**
|
||||
* PID Bed Heating
|
||||
*
|
||||
* If this option is enabled set PID constants below.
|
||||
* If this option is disabled, bang-bang will be used and BED_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 250W
|
||||
* heater. If your configuration is significantly different than this and you don't understand
|
||||
* the issues involved, don't use bed PID until someone else verifies that your hardware works.
|
||||
* @section bed temp
|
||||
*/
|
||||
//#define PIDTEMPBED
|
||||
|
||||
//#define BED_LIMIT_SWITCHING
|
||||
// @section bed temp
|
||||
|
||||
/**
|
||||
* Max Bed Power
|
||||
|
@ -751,6 +741,20 @@
|
|||
*/
|
||||
#define MAX_BED_POWER 255 // limits duty cycle to bed; 255=full current
|
||||
|
||||
/**
|
||||
* PID Bed Heating
|
||||
*
|
||||
* 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 250W
|
||||
* heater. If your configuration is significantly different than this and you don't understand
|
||||
* the issues involved, don't use bed PID until someone else verifies that your hardware works.
|
||||
*
|
||||
* With this option disabled, bang-bang will be used. BED_LIMIT_SWITCHING enables hysteresis.
|
||||
*/
|
||||
//#define PIDTEMPBED
|
||||
|
||||
#if ENABLED(PIDTEMPBED)
|
||||
//#define MIN_BED_POWER 0
|
||||
//#define PID_BED_DEBUG // Print Bed PID debug data to the serial port.
|
||||
|
@ -762,7 +766,9 @@
|
|||
#define DEFAULT_bedKd 305.4
|
||||
|
||||
// FIND YOUR OWN: "M303 E-1 C8 S90" to run autotune on the bed at 90 degreesC for 8 cycles.
|
||||
#endif // PIDTEMPBED
|
||||
#else
|
||||
//#define BED_LIMIT_SWITCHING // Keep the bed temperature within BED_HYSTERESIS of the target
|
||||
#endif
|
||||
|
||||
//===========================================================================
|
||||
//==================== PID > Chamber Temperature Control ====================
|
||||
|
|
|
@ -28,6 +28,7 @@ typedef uint32_t millis_t;
|
|||
#define SEC_TO_MS(N) millis_t((N)*1000UL)
|
||||
#define MIN_TO_MS(N) SEC_TO_MS((N)*60UL)
|
||||
#define MS_TO_SEC(N) millis_t((N)/1000UL)
|
||||
#define MS_TO_SEC_PRECISE(N) (float(N)/1000.0f)
|
||||
|
||||
#define PENDING(NOW,SOON) ((int32_t)(NOW-(SOON))<0)
|
||||
#define ELAPSED(NOW,SOON) (!PENDING(NOW,SOON))
|
||||
|
|
|
@ -42,7 +42,10 @@
|
|||
* R<kelvin/second/kelvin> Sensor responsiveness (= transfer coefficient / heat capcity).
|
||||
*
|
||||
* With MPC_AUTOTUNE:
|
||||
* T Autotune the specified or active extruder.
|
||||
* T Autotune the extruder specified with 'E' or the active extruder.
|
||||
* S0 : Autotuning method AUTO (default)
|
||||
* S1 : Autotuning method DIFFERENTIAL
|
||||
* S2 : Autotuning method ASYMPTOTIC
|
||||
*/
|
||||
|
||||
void GcodeSuite::M306() {
|
||||
|
@ -54,8 +57,15 @@ void GcodeSuite::M306() {
|
|||
|
||||
#if ENABLED(MPC_AUTOTUNE)
|
||||
if (parser.seen_test('T')) {
|
||||
Temperature::MPCTuningType tuning_type;
|
||||
const uint8_t type = parser.byteval('S', 0);
|
||||
switch (type) {
|
||||
case 1: tuning_type = Temperature::MPCTuningType::FORCE_DIFFERENTIAL; break;
|
||||
case 2: tuning_type = Temperature::MPCTuningType::FORCE_ASYMPTOTIC; break;
|
||||
default: tuning_type = Temperature::MPCTuningType::AUTO; break;
|
||||
}
|
||||
LCD_MESSAGE(MSG_MPC_AUTOTUNE);
|
||||
thermalManager.MPC_autotune(e);
|
||||
thermalManager.MPC_autotune(e, tuning_type);
|
||||
ui.reset_status();
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -722,16 +722,14 @@ volatile bool Temperature::raw_temps_ready = false;
|
|||
TERN_(DWIN_PID_TUNE, DWIN_PidTuning(isbed ? PIDTEMPBED_START : PIDTEMP_START));
|
||||
|
||||
if (target > GHV(CHAMBER_MAX_TARGET, BED_MAX_TARGET, temp_range[heater_id].maxtemp - (HOTEND_OVERSHOOT))) {
|
||||
SERIAL_ECHOPGM(STR_PID_AUTOTUNE);
|
||||
SERIAL_ECHOLNPGM(STR_PID_TEMP_TOO_HIGH);
|
||||
SERIAL_ECHOPGM(STR_PID_AUTOTUNE); SERIAL_ECHOLNPGM(STR_PID_TEMP_TOO_HIGH);
|
||||
TERN_(EXTENSIBLE_UI, ExtUI::onPidTuning(ExtUI::result_t::PID_TEMP_TOO_HIGH));
|
||||
TERN_(DWIN_PID_TUNE, DWIN_PidTuning(PID_TEMP_TOO_HIGH));
|
||||
TERN_(HOST_PROMPT_SUPPORT, hostui.notify(GET_TEXT_F(MSG_PID_TEMP_TOO_HIGH)));
|
||||
return;
|
||||
}
|
||||
|
||||
SERIAL_ECHOPGM(STR_PID_AUTOTUNE);
|
||||
SERIAL_ECHOLNPGM(STR_PID_AUTOTUNE_START);
|
||||
SERIAL_ECHOPGM(STR_PID_AUTOTUNE); SERIAL_ECHOLNPGM(STR_PID_AUTOTUNE_START);
|
||||
|
||||
disable_all_heaters();
|
||||
TERN_(AUTO_POWER_CONTROL, powerManager.power_on());
|
||||
|
@ -816,8 +814,7 @@ volatile bool Temperature::raw_temps_ready = false;
|
|||
#define MAX_OVERSHOOT_PID_AUTOTUNE 30
|
||||
#endif
|
||||
if (current_temp > target + MAX_OVERSHOOT_PID_AUTOTUNE) {
|
||||
SERIAL_ECHOPGM(STR_PID_AUTOTUNE);
|
||||
SERIAL_ECHOLNPGM(STR_PID_TEMP_TOO_HIGH);
|
||||
SERIAL_ECHOPGM(STR_PID_AUTOTUNE); SERIAL_ECHOLNPGM(STR_PID_TEMP_TOO_HIGH);
|
||||
TERN_(EXTENSIBLE_UI, ExtUI::onPidTuning(ExtUI::result_t::PID_TEMP_TOO_HIGH));
|
||||
TERN_(DWIN_PID_TUNE, DWIN_PidTuning(PID_TEMP_TOO_HIGH));
|
||||
TERN_(HOST_PROMPT_SUPPORT, hostui.notify(GET_TEXT_F(MSG_PID_TEMP_TOO_HIGH)));
|
||||
|
@ -859,14 +856,12 @@ volatile bool Temperature::raw_temps_ready = false;
|
|||
TERN_(DWIN_PID_TUNE, DWIN_PidTuning(PID_TUNING_TIMEOUT));
|
||||
TERN_(EXTENSIBLE_UI, ExtUI::onPidTuning(ExtUI::result_t::PID_TUNING_TIMEOUT));
|
||||
TERN_(HOST_PROMPT_SUPPORT, hostui.notify(GET_TEXT_F(MSG_PID_TIMEOUT)));
|
||||
SERIAL_ECHOPGM(STR_PID_AUTOTUNE);
|
||||
SERIAL_ECHOLNPGM(STR_PID_TIMEOUT);
|
||||
SERIAL_ECHOPGM(STR_PID_AUTOTUNE); SERIAL_ECHOLNPGM(STR_PID_TIMEOUT);
|
||||
break;
|
||||
}
|
||||
|
||||
if (cycles > ncycles && cycles > 2) {
|
||||
SERIAL_ECHOPGM(STR_PID_AUTOTUNE);
|
||||
SERIAL_ECHOLNPGM(STR_PID_AUTOTUNE_FINISHED);
|
||||
SERIAL_ECHOPGM(STR_PID_AUTOTUNE); SERIAL_ECHOLNPGM(STR_PID_AUTOTUNE_FINISHED);
|
||||
TERN_(HOST_PROMPT_SUPPORT, hostui.notify(GET_TEXT_F(MSG_PID_AUTOTUNE_DONE)));
|
||||
|
||||
#if EITHER(PIDTEMPBED, PIDTEMPCHAMBER)
|
||||
|
@ -944,38 +939,26 @@ volatile bool Temperature::raw_temps_ready = false;
|
|||
#define SINGLEFAN 1
|
||||
#endif
|
||||
|
||||
void Temperature::MPC_autotune(const uint8_t e) {
|
||||
auto housekeeping = [] (millis_t &ms, const uint8_t e, celsius_float_t ¤t_temp, millis_t &next_report_ms) {
|
||||
ms = millis();
|
||||
#define DEBUG_MPC_AUTOTUNE 1
|
||||
|
||||
if (updateTemperaturesIfReady()) { // temp sample ready
|
||||
current_temp = degHotend(e);
|
||||
TERN_(HAS_FAN_LOGIC, manage_extruder_fans(ms));
|
||||
millis_t Temperature::MPC_autotuner::curr_time_ms, Temperature::MPC_autotuner::next_report_ms;
|
||||
|
||||
celsius_float_t Temperature::MPC_autotuner::temp_samples[16];
|
||||
uint8_t Temperature::MPC_autotuner::sample_count;
|
||||
uint16_t Temperature::MPC_autotuner::sample_distance;
|
||||
|
||||
// Parameters from differential analysis
|
||||
celsius_float_t Temperature::MPC_autotuner::temp_fastest;
|
||||
|
||||
#if HAS_FAN
|
||||
float Temperature::MPC_autotuner::power_fan255;
|
||||
#endif
|
||||
|
||||
Temperature::MPC_autotuner::MPC_autotuner(const uint8_t extruderIdx) : e(extruderIdx) {
|
||||
TERN_(TEMP_TUNING_MAINTAIN_FAN, adaptive_fan_slowing = false);
|
||||
}
|
||||
|
||||
if (ELAPSED(ms, next_report_ms)) {
|
||||
next_report_ms += 1000UL;
|
||||
|
||||
print_heater_states(e);
|
||||
SERIAL_EOL();
|
||||
}
|
||||
|
||||
hal.idletask();
|
||||
TERN(DWIN_CREALITY_LCD, DWIN_Update(), ui.update());
|
||||
|
||||
if (!wait_for_heatup) {
|
||||
SERIAL_ECHOLNPGM(STR_MPC_AUTOTUNE_INTERRUPTED);
|
||||
TERN_(DWIN_LCD_PROUI, DWIN_MPCTuning(MPC_INTERRUPTED));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
struct OnExit {
|
||||
uint8_t e;
|
||||
OnExit(const uint8_t _e) { this->e = _e; }
|
||||
~OnExit() {
|
||||
Temperature::MPC_autotuner::~MPC_autotuner() {
|
||||
wait_for_heatup = false;
|
||||
|
||||
ui.reset_status();
|
||||
|
@ -991,13 +974,231 @@ volatile bool Temperature::raw_temps_ready = false;
|
|||
|
||||
TERN_(TEMP_TUNING_MAINTAIN_FAN, adaptive_fan_slowing = true);
|
||||
}
|
||||
} on_exit(e);
|
||||
|
||||
SERIAL_ECHOLNPGM(STR_MPC_AUTOTUNE_START, e);
|
||||
Temperature::MPC_autotuner::MeasurementState Temperature::MPC_autotuner::measure_ambient_temp() {
|
||||
init_timers();
|
||||
const millis_t test_interval_ms = 10000UL;
|
||||
millis_t next_test_ms = curr_time_ms + test_interval_ms;
|
||||
ambient_temp = current_temp = degHotend(e);
|
||||
wait_for_heatup = true;
|
||||
|
||||
for (;;) { // Can be interrupted with M108
|
||||
if (housekeeping() == CANCELLED) return CANCELLED;
|
||||
|
||||
if (ELAPSED(curr_time_ms, next_test_ms)) {
|
||||
if (current_temp >= ambient_temp) {
|
||||
ambient_temp = (ambient_temp + current_temp) / 2.0f;
|
||||
break;
|
||||
}
|
||||
ambient_temp = current_temp;
|
||||
next_test_ms += test_interval_ms;
|
||||
}
|
||||
}
|
||||
wait_for_heatup = false;
|
||||
|
||||
#if ENABLED(DEBUG_MPC_AUTOTUNE)
|
||||
SERIAL_ECHOLNPGM("MPC_autotuner::measure_ambient_temp() Completed");
|
||||
SERIAL_ECHOLNPGM("=====");
|
||||
SERIAL_ECHOLNPGM("ambient_temp ", get_ambient_temp());
|
||||
#endif
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
Temperature::MPC_autotuner::MeasurementState Temperature::MPC_autotuner::measure_heatup() {
|
||||
init_timers();
|
||||
constexpr millis_t test_interval_ms = 1000UL;
|
||||
millis_t next_test_time_ms = curr_time_ms + test_interval_ms;
|
||||
MPCHeaterInfo &hotend = temp_hotend[e];
|
||||
|
||||
current_temp = degHotend(e);
|
||||
millis_t heat_start_time_ms = curr_time_ms;
|
||||
sample_count = 0;
|
||||
sample_distance = 1;
|
||||
t1_time = 0;
|
||||
|
||||
hotend.target = 200.0f; // So M105 looks nice
|
||||
hotend.soft_pwm_amount = (MPC_MAX) >> 1;
|
||||
|
||||
// Initialise rate of change to to steady state at current time
|
||||
temp_samples[0] = temp_samples[1] = temp_samples[2] = current_temp;
|
||||
time_fastest = rate_fastest = 0;
|
||||
|
||||
wait_for_heatup = true;
|
||||
for (;;) { // Can be interrupted with M108
|
||||
if (housekeeping() == CANCELLED) return CANCELLED;
|
||||
|
||||
if (ELAPSED(curr_time_ms, next_test_time_ms)) {
|
||||
if (current_temp < 100.0f) {
|
||||
// Initial regime (below 100deg): Measure rate of change of heating for differential tuning
|
||||
|
||||
// Update the buffer of previous readings
|
||||
temp_samples[0] = temp_samples[1];
|
||||
temp_samples[1] = temp_samples[2];
|
||||
temp_samples[2] = current_temp;
|
||||
|
||||
// Measure the rate of change of temperature, https://en.wikipedia.org/wiki/Symmetric_derivative
|
||||
const float h = MS_TO_SEC_PRECISE(test_interval_ms),
|
||||
curr_rate = (temp_samples[2] - temp_samples[0]) / 2 * h;
|
||||
if (curr_rate > rate_fastest) {
|
||||
// Update fastest values
|
||||
rate_fastest = curr_rate;
|
||||
temp_fastest = temp_samples[1];
|
||||
time_fastest = get_elapsed_heating_time();
|
||||
}
|
||||
|
||||
next_test_time_ms += test_interval_ms;
|
||||
|
||||
}
|
||||
else if (current_temp < 200.0f) {
|
||||
// Second regime (after 100deg) measure 3 points to determine asymptotic temperature
|
||||
|
||||
// If there are too many samples, space them more widely
|
||||
if (sample_count == COUNT(temp_samples)) {
|
||||
for (uint8_t i = 0; i < COUNT(temp_samples) / 2; i++)
|
||||
temp_samples[i] = temp_samples[i * 2];
|
||||
sample_count /= 2;
|
||||
sample_distance *= 2;
|
||||
}
|
||||
|
||||
if (sample_count == 0) t1_time = MS_TO_SEC_PRECISE(curr_time_ms - heat_start_time_ms);
|
||||
temp_samples[sample_count++] = current_temp;
|
||||
|
||||
if (current_temp >= 200.0f) break;
|
||||
|
||||
next_test_time_ms += test_interval_ms * sample_distance;
|
||||
|
||||
}
|
||||
else {
|
||||
// Third regime (after 200deg) finished gathering data so finish
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
wait_for_heatup = false;
|
||||
|
||||
hotend.soft_pwm_amount = 0;
|
||||
|
||||
elapsed_heating_time = MS_TO_SEC_PRECISE(curr_time_ms - heat_start_time_ms);
|
||||
|
||||
// Ensure sample count is odd so that we have 3 equally spaced samples
|
||||
if (sample_count == 0) return FAILED;
|
||||
if (sample_count % 2 == 0) sample_count--;
|
||||
|
||||
#if ENABLED(DEBUG_MPC_AUTOTUNE)
|
||||
SERIAL_ECHOLNPGM("MPC_autotuner::measure_heatup() Completed");
|
||||
SERIAL_ECHOLNPGM("=====");
|
||||
SERIAL_ECHOLNPGM("t1_time ", t1_time);
|
||||
SERIAL_ECHOLNPGM("sample_count ", sample_count);
|
||||
SERIAL_ECHOLNPGM("sample_distance ", sample_distance);
|
||||
for (uint8_t i = 0; i < sample_count; i++)
|
||||
SERIAL_ECHOLNPGM("sample ", i, " : ", temp_samples[i]);
|
||||
SERIAL_ECHOLNPGM("t1 ", get_sample_1_temp(), " t2 ", get_sample_2_temp(), " t3 ", get_sample_3_temp());
|
||||
#endif
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
Temperature::MPC_autotuner::MeasurementState Temperature::MPC_autotuner::measure_transfer() {
|
||||
init_timers();
|
||||
const millis_t test_interval_ms = SEC_TO_MS(MPC_dT);
|
||||
millis_t next_test_ms = curr_time_ms + test_interval_ms;
|
||||
MPCHeaterInfo &hotend = temp_hotend[e];
|
||||
MPC_t &mpc = hotend.mpc;
|
||||
|
||||
TERN_(TEMP_TUNING_MAINTAIN_FAN, adaptive_fan_slowing = false);
|
||||
constexpr millis_t settle_time = 20000UL, test_duration = 20000UL;
|
||||
millis_t settle_end_ms = curr_time_ms + settle_time,
|
||||
test_end_ms = settle_end_ms + test_duration;
|
||||
float total_energy_fan0 = 0.0f;
|
||||
#if HAS_FAN
|
||||
bool fan0_done = false;
|
||||
float total_energy_fan255 = 0.0f;
|
||||
#endif
|
||||
float last_temp = current_temp;
|
||||
|
||||
wait_for_heatup = true;
|
||||
for (;;) { // Can be interrupted with M108
|
||||
if (housekeeping() == CANCELLED) return CANCELLED;
|
||||
|
||||
if (ELAPSED(curr_time_ms, next_test_ms)) {
|
||||
hotend.soft_pwm_amount = (int)get_pid_output_hotend(e) >> 1;
|
||||
|
||||
if (ELAPSED(curr_time_ms, settle_end_ms) && !ELAPSED(curr_time_ms, test_end_ms) && TERN1(HAS_FAN, !fan0_done))
|
||||
total_energy_fan0 += mpc.heater_power * hotend.soft_pwm_amount / 127 * MPC_dT + (last_temp - current_temp) * mpc.block_heat_capacity;
|
||||
#if HAS_FAN
|
||||
else if (ELAPSED(curr_time_ms, test_end_ms) && !fan0_done) {
|
||||
set_fan_speed(TERN(SINGLEFAN, 0, e), 255);
|
||||
planner.sync_fan_speeds(fan_speed);
|
||||
settle_end_ms = curr_time_ms + settle_time;
|
||||
test_end_ms = settle_end_ms + test_duration;
|
||||
fan0_done = true;
|
||||
}
|
||||
else if (ELAPSED(curr_time_ms, settle_end_ms) && !ELAPSED(curr_time_ms, test_end_ms))
|
||||
total_energy_fan255 += mpc.heater_power * hotend.soft_pwm_amount / 127 * MPC_dT + (last_temp - current_temp) * mpc.block_heat_capacity;
|
||||
#endif
|
||||
else if (ELAPSED(curr_time_ms, test_end_ms)) break;
|
||||
|
||||
last_temp = current_temp;
|
||||
next_test_ms += test_interval_ms;
|
||||
}
|
||||
|
||||
// Ensure we don't drift too far from the window between the last sampled temp and the target temperature
|
||||
if (!WITHIN(current_temp, get_sample_3_temp() - 15.0f, hotend.target + 15.0f)) {
|
||||
SERIAL_ECHOLNPGM(STR_MPC_TEMPERATURE_ERROR);
|
||||
TERN_(DWIN_LCD_PROUI, DWIN_MPCTuning(MPC_TEMP_ERROR));
|
||||
wait_for_heatup = false;
|
||||
return FAILED;
|
||||
}
|
||||
}
|
||||
wait_for_heatup = false;
|
||||
|
||||
power_fan0 = total_energy_fan0 / MS_TO_SEC_PRECISE(test_duration);
|
||||
TERN_(HAS_FAN, power_fan255 = (total_energy_fan255 * 1000) / test_duration);
|
||||
|
||||
#if ENABLED(DEBUG_MPC_AUTOTUNE)
|
||||
SERIAL_ECHOLNPGM("MPC_autotuner::measure_transfer() Completed");
|
||||
SERIAL_ECHOLNPGM("=====");
|
||||
SERIAL_ECHOLNPGM("power_fan0 ", power_fan0);
|
||||
TERN_(HAS_FAN, SERIAL_ECHOLNPGM("power_fan255 ", power_fan255));
|
||||
#endif
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
Temperature::MPC_autotuner::MeasurementState Temperature::MPC_autotuner::housekeeping() {
|
||||
const millis_t report_interval_ms = 1000UL;
|
||||
curr_time_ms = millis();
|
||||
|
||||
if (updateTemperaturesIfReady()) { // temp sample ready
|
||||
current_temp = degHotend(e);
|
||||
TERN_(HAS_FAN_LOGIC, manage_extruder_fans(curr_time_ms));
|
||||
}
|
||||
|
||||
if (ELAPSED(curr_time_ms, next_report_ms)) {
|
||||
next_report_ms += report_interval_ms;
|
||||
print_heater_states(e);
|
||||
SERIAL_EOL();
|
||||
}
|
||||
|
||||
hal.idletask();
|
||||
TERN(DWIN_CREALITY_LCD, DWIN_Update(), ui.update());
|
||||
|
||||
if (!wait_for_heatup) {
|
||||
SERIAL_ECHOLNPGM(STR_MPC_AUTOTUNE_INTERRUPTED);
|
||||
TERN_(DWIN_LCD_PROUI, DWIN_MPCTuning(MPC_INTERRUPTED));
|
||||
return MeasurementState::CANCELLED;
|
||||
}
|
||||
|
||||
return MeasurementState::SUCCESS;
|
||||
}
|
||||
|
||||
void Temperature::MPC_autotune(const uint8_t e, MPCTuningType tuning_type=AUTO) {
|
||||
SERIAL_ECHOLNPGM(STR_MPC_AUTOTUNE_START, e);
|
||||
|
||||
MPC_autotuner tuner(e);
|
||||
|
||||
MPCHeaterInfo &hotend = temp_hotend[e];
|
||||
MPC_t &mpc = hotend.mpc;
|
||||
|
||||
// Move to center of bed, just above bed height and cool with max fan
|
||||
gcode.home_all_axes(true);
|
||||
|
@ -1009,6 +1210,7 @@ volatile bool Temperature::raw_temps_ready = false;
|
|||
#endif
|
||||
do_blocking_move_to(xyz_pos_t(MPC_TUNING_POS));
|
||||
|
||||
// Determine ambient temperature.
|
||||
SERIAL_ECHOLNPGM(STR_MPC_COOLING_TO_AMBIENT);
|
||||
#if ENABLED(DWIN_LCD_PROUI)
|
||||
DWIN_MPCTuning(MPCTEMP_START);
|
||||
|
@ -1017,164 +1219,92 @@ volatile bool Temperature::raw_temps_ready = false;
|
|||
LCD_MESSAGE(MSG_COOLING);
|
||||
#endif
|
||||
|
||||
millis_t ms = millis(), next_report_ms = ms, next_test_ms = ms + 10000UL;
|
||||
celsius_float_t current_temp = degHotend(e),
|
||||
ambient_temp = current_temp;
|
||||
|
||||
wait_for_heatup = true;
|
||||
for (;;) { // Can be interrupted with M108
|
||||
if (housekeeping(ms, e, current_temp, next_report_ms)) return;
|
||||
|
||||
if (ELAPSED(ms, next_test_ms)) {
|
||||
if (current_temp >= ambient_temp) {
|
||||
ambient_temp = (ambient_temp + current_temp) / 2.0f;
|
||||
break;
|
||||
}
|
||||
ambient_temp = current_temp;
|
||||
next_test_ms += 10000UL;
|
||||
}
|
||||
}
|
||||
wait_for_heatup = false;
|
||||
if (tuner.measure_ambient_temp() != MPC_autotuner::MeasurementState::SUCCESS) return;
|
||||
hotend.modeled_ambient_temp = tuner.get_ambient_temp();
|
||||
|
||||
#if HAS_FAN
|
||||
set_fan_speed(TERN(SINGLEFAN, 0, e), 0);
|
||||
planner.sync_fan_speeds(fan_speed);
|
||||
#endif
|
||||
|
||||
hotend.modeled_ambient_temp = ambient_temp;
|
||||
|
||||
// Heat to 200 degrees
|
||||
SERIAL_ECHOLNPGM(STR_MPC_HEATING_PAST_200);
|
||||
TERN(DWIN_LCD_PROUI, LCD_ALERTMESSAGE(MSG_MPC_HEATING_PAST_200), LCD_MESSAGE(MSG_HEATING));
|
||||
hotend.target = 200.0f; // So M105 looks nice
|
||||
hotend.soft_pwm_amount = (MPC_MAX) >> 1;
|
||||
const millis_t heat_start_time = next_test_ms = ms;
|
||||
celsius_float_t temp_samples[16];
|
||||
uint8_t sample_count = 0;
|
||||
uint16_t sample_distance = 1;
|
||||
float t1_time = 0;
|
||||
|
||||
wait_for_heatup = true;
|
||||
for (;;) { // Can be interrupted with M108
|
||||
if (housekeeping(ms, e, current_temp, next_report_ms)) return;
|
||||
|
||||
if (ELAPSED(ms, next_test_ms)) {
|
||||
// Record samples between 100C and 200C
|
||||
if (current_temp >= 100.0f) {
|
||||
// If there are too many samples, space them more widely
|
||||
if (sample_count == COUNT(temp_samples)) {
|
||||
for (uint8_t i = 0; i < COUNT(temp_samples) / 2; i++)
|
||||
temp_samples[i] = temp_samples[i*2];
|
||||
sample_count /= 2;
|
||||
sample_distance *= 2;
|
||||
}
|
||||
|
||||
if (sample_count == 0) t1_time = float(ms - heat_start_time) / 1000.0f;
|
||||
temp_samples[sample_count++] = current_temp;
|
||||
}
|
||||
|
||||
if (current_temp >= 200.0f) break;
|
||||
|
||||
next_test_ms += 1000UL * sample_distance;
|
||||
}
|
||||
}
|
||||
wait_for_heatup = false;
|
||||
|
||||
hotend.soft_pwm_amount = 0;
|
||||
if (tuner.measure_heatup() != MPC_autotuner::MeasurementState::SUCCESS) return;
|
||||
|
||||
// Calculate physical constants from three equally-spaced samples
|
||||
sample_count = (sample_count + 1) / 2 * 2 - 1;
|
||||
const float t1 = temp_samples[0],
|
||||
t2 = temp_samples[(sample_count - 1) >> 1],
|
||||
t3 = temp_samples[sample_count - 1];
|
||||
const float t1 = tuner.get_sample_1_temp(),
|
||||
t2 = tuner.get_sample_2_temp(),
|
||||
t3 = tuner.get_sample_3_temp();
|
||||
float asymp_temp = (t2 * t2 - t1 * t3) / (2 * t2 - t1 - t3),
|
||||
block_responsiveness = -log((t2 - asymp_temp) / (t1 - asymp_temp)) / (sample_distance * (sample_count >> 1));
|
||||
block_responsiveness = -log((t2 - asymp_temp) / (t1 - asymp_temp)) / tuner.get_sample_interval();
|
||||
|
||||
mpc.ambient_xfer_coeff_fan0 = mpc.heater_power * (MPC_MAX) / 255 / (asymp_temp - ambient_temp);
|
||||
mpc.block_heat_capacity = mpc.ambient_xfer_coeff_fan0 / block_responsiveness;
|
||||
mpc.sensor_responsiveness = block_responsiveness / (1.0f - (ambient_temp - asymp_temp) * exp(-block_responsiveness * t1_time) / (t1 - asymp_temp));
|
||||
#if ENABLED(DEBUG_MPC_AUTOTUNE)
|
||||
SERIAL_ECHOLNPGM("asymp_temp ", asymp_temp);
|
||||
SERIAL_ECHOLNPAIR_F("block_responsiveness ", block_responsiveness, 4);
|
||||
#endif
|
||||
|
||||
// Make initial guess at transfer coefficients
|
||||
mpc.ambient_xfer_coeff_fan0 = mpc.heater_power * (MPC_MAX) / 255 / (asymp_temp - tuner.get_ambient_temp());
|
||||
TERN_(MPC_INCLUDE_FAN, mpc.fan255_adjustment = 0.0f);
|
||||
|
||||
hotend.modeled_block_temp = asymp_temp + (ambient_temp - asymp_temp) * exp(-block_responsiveness * (ms - heat_start_time) / 1000.0f);
|
||||
hotend.modeled_sensor_temp = current_temp;
|
||||
if (tuning_type == AUTO || tuning_type == FORCE_ASYMPTOTIC) {
|
||||
// Analytic tuning
|
||||
mpc.block_heat_capacity = mpc.ambient_xfer_coeff_fan0 / block_responsiveness;
|
||||
mpc.sensor_responsiveness = block_responsiveness / (1.0f - (tuner.get_ambient_temp() - asymp_temp) * exp(-block_responsiveness * tuner.get_sample_1_time()) / (t1 - asymp_temp));
|
||||
}
|
||||
|
||||
// If analytic tuning fails, fall back to differential tuning
|
||||
if (tuning_type == AUTO) {
|
||||
if (mpc.sensor_responsiveness <= 0 || mpc.block_heat_capacity <= 0)
|
||||
tuning_type = FORCE_DIFFERENTIAL;
|
||||
}
|
||||
|
||||
if (tuning_type == FORCE_DIFFERENTIAL) {
|
||||
// Differential tuning
|
||||
mpc.block_heat_capacity = mpc.heater_power / tuner.get_rate_fastest();
|
||||
mpc.sensor_responsiveness = tuner.get_rate_fastest() / (tuner.get_rate_fastest() * tuner.get_time_fastest() + tuner.get_ambient_temp() - tuner.get_time_fastest());
|
||||
}
|
||||
|
||||
hotend.modeled_block_temp = asymp_temp + (tuner.get_ambient_temp() - asymp_temp) * exp(-block_responsiveness * tuner.get_elapsed_heating_time());
|
||||
hotend.modeled_sensor_temp = tuner.get_last_measured_temp();
|
||||
|
||||
// Allow the system to stabilize under MPC, then get a better measure of ambient loss with and without fan
|
||||
SERIAL_ECHOLNPGM(STR_MPC_MEASURING_AMBIENT, hotend.modeled_block_temp);
|
||||
TERN(DWIN_LCD_PROUI, LCD_ALERTMESSAGE(MSG_MPC_MEASURING_AMBIENT), LCD_MESSAGE(MSG_MPC_MEASURING_AMBIENT));
|
||||
|
||||
// Use the estimated overshoot of the temperature as the target to achieve.
|
||||
hotend.target = hotend.modeled_block_temp;
|
||||
next_test_ms = ms + MPC_dT * 1000;
|
||||
constexpr millis_t settle_time = 20000UL, test_duration = 20000UL;
|
||||
millis_t settle_end_ms = ms + settle_time,
|
||||
test_end_ms = settle_end_ms + test_duration;
|
||||
float total_energy_fan0 = 0.0f;
|
||||
if (tuner.measure_transfer() != MPC_autotuner::MeasurementState::SUCCESS) return;
|
||||
|
||||
// Update the transfer coefficients
|
||||
mpc.ambient_xfer_coeff_fan0 = tuner.get_power_fan0() / (hotend.target - tuner.get_ambient_temp());
|
||||
#if HAS_FAN
|
||||
bool fan0_done = false;
|
||||
float total_energy_fan255 = 0.0f;
|
||||
#endif
|
||||
float last_temp = current_temp;
|
||||
|
||||
wait_for_heatup = true;
|
||||
for (;;) { // Can be interrupted with M108
|
||||
if (housekeeping(ms, e, current_temp, next_report_ms)) return;
|
||||
|
||||
if (ELAPSED(ms, next_test_ms)) {
|
||||
hotend.soft_pwm_amount = (int)get_pid_output_hotend(e) >> 1;
|
||||
|
||||
if (ELAPSED(ms, settle_end_ms) && !ELAPSED(ms, test_end_ms) && TERN1(HAS_FAN, !fan0_done))
|
||||
total_energy_fan0 += mpc.heater_power * hotend.soft_pwm_amount / 127 * MPC_dT + (last_temp - current_temp) * mpc.block_heat_capacity;
|
||||
#if HAS_FAN
|
||||
else if (ELAPSED(ms, test_end_ms) && !fan0_done) {
|
||||
set_fan_speed(TERN(SINGLEFAN, 0, e), 255);
|
||||
planner.sync_fan_speeds(fan_speed);
|
||||
settle_end_ms = ms + settle_time;
|
||||
test_end_ms = settle_end_ms + test_duration;
|
||||
fan0_done = true;
|
||||
}
|
||||
else if (ELAPSED(ms, settle_end_ms) && !ELAPSED(ms, test_end_ms))
|
||||
total_energy_fan255 += mpc.heater_power * hotend.soft_pwm_amount / 127 * MPC_dT + (last_temp - current_temp) * mpc.block_heat_capacity;
|
||||
#endif
|
||||
else if (ELAPSED(ms, test_end_ms)) break;
|
||||
|
||||
last_temp = current_temp;
|
||||
next_test_ms += MPC_dT * 1000;
|
||||
}
|
||||
|
||||
if (!WITHIN(current_temp, t3 - 15.0f, hotend.target + 15.0f)) {
|
||||
SERIAL_ECHOLNPGM(STR_MPC_TEMPERATURE_ERROR);
|
||||
TERN_(DWIN_LCD_PROUI, DWIN_MPCTuning(MPC_TEMP_ERROR));
|
||||
break;
|
||||
}
|
||||
}
|
||||
wait_for_heatup = false;
|
||||
|
||||
const float power_fan0 = total_energy_fan0 * 1000 / test_duration;
|
||||
mpc.ambient_xfer_coeff_fan0 = power_fan0 / (hotend.target - ambient_temp);
|
||||
|
||||
#if HAS_FAN
|
||||
const float power_fan255 = total_energy_fan255 * 1000 / test_duration,
|
||||
ambient_xfer_coeff_fan255 = power_fan255 / (hotend.target - ambient_temp);
|
||||
const float ambient_xfer_coeff_fan255 = tuner.get_power_fan255() / (hotend.target - tuner.get_ambient_temp());
|
||||
mpc.applyFanAdjustment(ambient_xfer_coeff_fan255);
|
||||
#endif
|
||||
|
||||
if (tuning_type == AUTO || tuning_type == FORCE_ASYMPTOTIC) {
|
||||
// Calculate a new and better asymptotic temperature and re-evaluate the other constants
|
||||
asymp_temp = ambient_temp + mpc.heater_power * (MPC_MAX) / 255 / mpc.ambient_xfer_coeff_fan0;
|
||||
block_responsiveness = -log((t2 - asymp_temp) / (t1 - asymp_temp)) / (sample_distance * (sample_count >> 1));
|
||||
mpc.block_heat_capacity = mpc.ambient_xfer_coeff_fan0 / block_responsiveness;
|
||||
mpc.sensor_responsiveness = block_responsiveness / (1.0f - (ambient_temp - asymp_temp) * exp(-block_responsiveness * t1_time) / (t1 - asymp_temp));
|
||||
asymp_temp = tuner.get_ambient_temp() + mpc.heater_power * (MPC_MAX) / 255 / mpc.ambient_xfer_coeff_fan0;
|
||||
block_responsiveness = -log((t2 - asymp_temp) / (t1 - asymp_temp)) / tuner.get_sample_interval();
|
||||
|
||||
SERIAL_ECHOLNPGM(STR_MPC_AUTOTUNE_FINISHED);
|
||||
TERN_(DWIN_LCD_PROUI, DWIN_MPCTuning(MPC_DONE));
|
||||
|
||||
#if 0
|
||||
SERIAL_ECHOLNPGM("t1_time ", t1_time);
|
||||
SERIAL_ECHOLNPGM("sample_count ", sample_count);
|
||||
SERIAL_ECHOLNPGM("sample_distance ", sample_distance);
|
||||
for (uint8_t i = 0; i < sample_count; i++)
|
||||
SERIAL_ECHOLNPGM("sample ", i, " : ", temp_samples[i]);
|
||||
SERIAL_ECHOLNPGM("t1 ", t1, " t2 ", t2, " t3 ", t3);
|
||||
#if ENABLED(DEBUG_MPC_AUTOTUNE)
|
||||
SERIAL_ECHOLN("Refining estimates for:");
|
||||
SERIAL_ECHOLNPGM("asymp_temp ", asymp_temp);
|
||||
SERIAL_ECHOLNPAIR_F("block_responsiveness ", block_responsiveness, 4);
|
||||
#endif
|
||||
|
||||
// Update analytic tuning values based on the above
|
||||
mpc.block_heat_capacity = mpc.ambient_xfer_coeff_fan0 / block_responsiveness;
|
||||
mpc.sensor_responsiveness = block_responsiveness / (1.0f - (tuner.get_ambient_temp() - asymp_temp) * exp(-block_responsiveness * tuner.get_sample_1_time()) / (t1 - asymp_temp));
|
||||
|
||||
}
|
||||
|
||||
SERIAL_ECHOLNPGM(STR_MPC_AUTOTUNE_FINISHED);
|
||||
TERN_(DWIN_LCD_PROUI, DWIN_MPCTuning(MPC_DONE));
|
||||
|
||||
SERIAL_ECHOLNPGM("MPC_BLOCK_HEAT_CAPACITY ", mpc.block_heat_capacity);
|
||||
SERIAL_ECHOLNPAIR_F("MPC_SENSOR_RESPONSIVENESS ", mpc.sensor_responsiveness, 4);
|
||||
SERIAL_ECHOLNPAIR_F("MPC_AMBIENT_XFER_COEFF ", mpc.ambient_xfer_coeff_fan0, 4);
|
||||
|
@ -1702,9 +1832,9 @@ void Temperature::mintemp_error(const heater_id_t heater_id) {
|
|||
// Check if temperature is within the correct band
|
||||
if (WITHIN(temp_bed.celsius, BED_MINTEMP, BED_MAXTEMP)) {
|
||||
#if ENABLED(BED_LIMIT_SWITCHING)
|
||||
if (temp_bed.is_above_target((BED_HYSTERESIS) - 1))
|
||||
if (temp_bed.is_above_target(BED_HYSTERESIS))
|
||||
temp_bed.soft_pwm_amount = 0;
|
||||
else if (temp_bed.is_below_target((BED_HYSTERESIS) - 1))
|
||||
else if (temp_bed.is_below_target(BED_HYSTERESIS))
|
||||
temp_bed.soft_pwm_amount = MAX_BED_POWER >> 1;
|
||||
#else // !PIDTEMPBED && !BED_LIMIT_SWITCHING
|
||||
temp_bed.soft_pwm_amount = temp_bed.is_below_target() ? MAX_BED_POWER >> 1 : 0;
|
||||
|
@ -1778,7 +1908,7 @@ void Temperature::mintemp_error(const heater_id_t heater_id) {
|
|||
#ifndef MIN_COOLING_SLOPE_DEG_CHAMBER_VENT
|
||||
#define MIN_COOLING_SLOPE_DEG_CHAMBER_VENT 1.5
|
||||
#endif
|
||||
if (!flag_chamber_excess_heat && temp_chamber.is_above_target((HIGH_EXCESS_HEAT_LIMIT) - 1)) {
|
||||
if (!flag_chamber_excess_heat && temp_chamber.is_above_target(HIGH_EXCESS_HEAT_LIMIT)) {
|
||||
// Open vent after MIN_COOLING_SLOPE_TIME_CHAMBER_VENT seconds if the
|
||||
// temperature didn't drop at least MIN_COOLING_SLOPE_DEG_CHAMBER_VENT
|
||||
if (next_cool_check_ms == 0 || ELAPSED(ms, next_cool_check_ms)) {
|
||||
|
@ -1792,7 +1922,7 @@ void Temperature::mintemp_error(const heater_id_t heater_id) {
|
|||
next_cool_check_ms = 0;
|
||||
old_temp = 9999;
|
||||
}
|
||||
if (flag_chamber_excess_heat && temp_chamber.is_above_target((LOW_EXCESS_HEAT_LIMIT) - 1))
|
||||
if (flag_chamber_excess_heat && temp_chamber.is_above_target(LOW_EXCESS_HEAT_LIMIT))
|
||||
flag_chamber_excess_heat = false;
|
||||
#endif
|
||||
}
|
||||
|
@ -1824,9 +1954,9 @@ void Temperature::mintemp_error(const heater_id_t heater_id) {
|
|||
}
|
||||
else {
|
||||
#if ENABLED(CHAMBER_LIMIT_SWITCHING)
|
||||
if (temp_chamber.is_above_target((TEMP_CHAMBER_HYSTERESIS) - 1))
|
||||
if (temp_chamber.is_above_target(TEMP_CHAMBER_HYSTERESIS))
|
||||
temp_chamber.soft_pwm_amount = 0;
|
||||
else if (temp_chamber.is_below_target((TEMP_CHAMBER_HYSTERESIS) - 1))
|
||||
else if (temp_chamber.is_below_target(TEMP_CHAMBER_HYSTERESIS))
|
||||
temp_chamber.soft_pwm_amount = (MAX_CHAMBER_POWER) >> 1;
|
||||
#else
|
||||
temp_chamber.soft_pwm_amount = temp_chamber.is_below_target() ? (MAX_CHAMBER_POWER) >> 1 : 0;
|
||||
|
|
|
@ -150,7 +150,7 @@ typedef struct { float p, i, d, c, f; } raw_pidcf_t;
|
|||
|
||||
#if HAS_PID_HEATING
|
||||
|
||||
#define PID_K2 (1-float(PID_K1))
|
||||
#define PID_K2 (1.0f - float(PID_K1))
|
||||
#define PID_dT ((OVERSAMPLENR * float(ACTUAL_ADC_SAMPLES)) / (TEMP_TIMER_FREQUENCY))
|
||||
|
||||
// Apply the scale factors to the PID values
|
||||
|
@ -231,7 +231,7 @@ typedef struct { float p, i, d, c, f; } raw_pidcf_t;
|
|||
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif // HAS_PID_HEATING
|
||||
|
||||
#if ENABLED(PIDTEMP)
|
||||
|
||||
|
@ -1215,12 +1215,69 @@ class Temperature {
|
|||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#endif // HAS_PID_HEATING
|
||||
|
||||
#if ENABLED(MPC_AUTOTUNE)
|
||||
void MPC_autotune(const uint8_t e);
|
||||
|
||||
// Utility class to perform MPCTEMP auto tuning measurements
|
||||
class MPC_autotuner {
|
||||
public:
|
||||
enum MeasurementState { CANCELLED, FAILED, SUCCESS };
|
||||
MPC_autotuner(const uint8_t extruderIdx);
|
||||
~MPC_autotuner();
|
||||
MeasurementState measure_ambient_temp();
|
||||
MeasurementState measure_heatup();
|
||||
MeasurementState measure_transfer();
|
||||
|
||||
celsius_float_t get_ambient_temp() { return ambient_temp; }
|
||||
celsius_float_t get_last_measured_temp() { return current_temp; }
|
||||
|
||||
float get_elapsed_heating_time() { return elapsed_heating_time; }
|
||||
float get_sample_1_time() { return t1_time; }
|
||||
static float get_sample_1_temp() { return temp_samples[0]; }
|
||||
static float get_sample_2_temp() { return temp_samples[(sample_count - 1) >> 1]; }
|
||||
static float get_sample_3_temp() { return temp_samples[sample_count - 1]; }
|
||||
static float get_sample_interval() { return sample_distance * (sample_count >> 1); }
|
||||
|
||||
static celsius_float_t get_temp_fastest() { return temp_fastest; }
|
||||
float get_time_fastest() { return time_fastest; }
|
||||
float get_rate_fastest() { return rate_fastest; }
|
||||
|
||||
float get_power_fan0() { return power_fan0; }
|
||||
#if HAS_FAN
|
||||
static float get_power_fan255() { return power_fan255; }
|
||||
#endif
|
||||
|
||||
protected:
|
||||
static void init_timers() { curr_time_ms = next_report_ms = millis(); }
|
||||
MeasurementState housekeeping();
|
||||
|
||||
uint8_t e;
|
||||
|
||||
float elapsed_heating_time;
|
||||
celsius_float_t ambient_temp, current_temp;
|
||||
float t1_time;
|
||||
|
||||
static millis_t curr_time_ms, next_report_ms;
|
||||
static celsius_float_t temp_samples[16];
|
||||
static uint8_t sample_count;
|
||||
static uint16_t sample_distance;
|
||||
|
||||
// Parameters from differential analysis
|
||||
static celsius_float_t temp_fastest;
|
||||
float time_fastest, rate_fastest;
|
||||
|
||||
float power_fan0;
|
||||
#if HAS_FAN
|
||||
static float power_fan255;
|
||||
#endif
|
||||
};
|
||||
|
||||
enum MPCTuningType { AUTO, FORCE_ASYMPTOTIC, FORCE_DIFFERENTIAL };
|
||||
static void MPC_autotune(const uint8_t e, MPCTuningType tuning_type);
|
||||
|
||||
#endif // MPC_AUTOTUNE
|
||||
|
||||
#if ENABLED(PROBING_HEATERS_OFF)
|
||||
static void pause_heaters(const bool p);
|
||||
#endif
|
||||
|
|
Loading…
Reference in a new issue