|
|
@ -141,8 +141,12 @@ |
|
|
|
#endif |
|
|
|
#endif |
|
|
|
|
|
|
|
#if EITHER(MPCTEMP, PID_EXTRUSION_SCALING) |
|
|
|
#if ENABLED(MPCTEMP) |
|
|
|
#include <math.h> |
|
|
|
#include "probe.h" |
|
|
|
#endif |
|
|
|
|
|
|
|
#if EITHER(MPCTEMP, PID_EXTRUSION_SCALING) |
|
|
|
#include "stepper.h" |
|
|
|
#endif |
|
|
|
|
|
|
@ -870,33 +874,65 @@ volatile bool Temperature::raw_temps_ready = false; |
|
|
|
|
|
|
|
if (ELAPSED(ms, next_report_ms)) { |
|
|
|
next_report_ms += 1000UL; |
|
|
|
SERIAL_ECHOLNPGM("Temperature ", current_temp); |
|
|
|
|
|
|
|
print_heater_states(active_extruder); |
|
|
|
SERIAL_EOL(); |
|
|
|
} |
|
|
|
|
|
|
|
hal.idletask(); |
|
|
|
TERN(DWIN_CREALITY_LCD, DWIN_Update(), ui.update()); |
|
|
|
|
|
|
|
if (!wait_for_heatup) { |
|
|
|
SERIAL_ECHOPGM(STR_MPC_AUTOTUNE); |
|
|
|
SERIAL_ECHOLNPGM(STR_MPC_AUTOTUNE_INTERRUPTED); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
return true; |
|
|
|
}; |
|
|
|
|
|
|
|
SERIAL_ECHOLNPGM("Measuring MPC constants for E", active_extruder); |
|
|
|
MPCHeaterInfo& hotend = temp_hotend[active_extruder]; |
|
|
|
MPC_t& constants = hotend.constants; |
|
|
|
struct OnExit { |
|
|
|
~OnExit() { |
|
|
|
wait_for_heatup = false; |
|
|
|
|
|
|
|
ui.reset_status(); |
|
|
|
|
|
|
|
temp_hotend[active_extruder].target = 0.0f; |
|
|
|
temp_hotend[active_extruder].soft_pwm_amount = 0; |
|
|
|
#if HAS_FAN |
|
|
|
set_fan_speed(ANY(MPC_FAN_0_ALL_HOTENDS, MPC_FAN_0_ACTIVE_HOTEND) ? 0 : active_extruder, 0); |
|
|
|
planner.sync_fan_speeds(fan_speed); |
|
|
|
#endif |
|
|
|
|
|
|
|
// move to center of bed, just above bed height and cool with max fan
|
|
|
|
TERN_(HAS_FAN, zero_fan_speeds()); |
|
|
|
do_z_clearance(MPC_TUNING_END_Z); |
|
|
|
} |
|
|
|
} on_exit; |
|
|
|
|
|
|
|
SERIAL_ECHOPGM(STR_MPC_AUTOTUNE); |
|
|
|
SERIAL_ECHOLNPGM(STR_MPC_AUTOTUNE_START, active_extruder); |
|
|
|
MPCHeaterInfo &hotend = temp_hotend[active_extruder]; |
|
|
|
MPC_t &constants = hotend.constants; |
|
|
|
|
|
|
|
// Move to center of bed, just above bed height and cool with max fan
|
|
|
|
disable_all_heaters(); |
|
|
|
TERN_(HAS_FAN, set_fan_speed(ANY(MPC_FAN_0_ALL_HOTENDS, MPC_FAN_0_ACTIVE_HOTEND) ? 0 : active_extruder, 255)); |
|
|
|
TERN_(HAS_FAN, planner.sync_fan_speeds(fan_speed)); |
|
|
|
#if HAS_FAN |
|
|
|
zero_fan_speeds(); |
|
|
|
set_fan_speed(ANY(MPC_FAN_0_ALL_HOTENDS, MPC_FAN_0_ACTIVE_HOTEND) ? 0 : active_extruder, 255); |
|
|
|
planner.sync_fan_speeds(fan_speed); |
|
|
|
#endif |
|
|
|
gcode.home_all_axes(true); |
|
|
|
const xyz_pos_t tuningpos = MPC_TUNING_POS; |
|
|
|
do_blocking_move_to(tuningpos); |
|
|
|
|
|
|
|
SERIAL_ECHOLNPGM("Cooling to ambient"); |
|
|
|
SERIAL_ECHOLNPGM(STR_MPC_COOLING_TO_AMBIENT); |
|
|
|
LCD_MESSAGE(MSG_COOLING); |
|
|
|
millis_t ms = millis(), next_report_ms = ms, next_test_ms = ms + 10000UL; |
|
|
|
celsius_float_t current_temp = degHotend(active_extruder), |
|
|
|
ambient_temp = current_temp; |
|
|
|
|
|
|
|
wait_for_heatup = true; // Can be interrupted with M108
|
|
|
|
while (wait_for_heatup) { |
|
|
|
housekeeping(ms, current_temp, next_report_ms); |
|
|
|
wait_for_heatup = true; |
|
|
|
for (;;) { // Can be interrupted with M108
|
|
|
|
if (!housekeeping(ms, current_temp, next_report_ms)) return; |
|
|
|
|
|
|
|
if (ELAPSED(ms, next_test_ms)) { |
|
|
|
if (current_temp >= ambient_temp) { |
|
|
@ -908,12 +944,16 @@ volatile bool Temperature::raw_temps_ready = false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
TERN_(HAS_FAN, set_fan_speed(ANY(MPC_FAN_0_ALL_HOTENDS, MPC_FAN_0_ACTIVE_HOTEND) ? 0 : active_extruder, 0)); |
|
|
|
TERN_(HAS_FAN, planner.sync_fan_speeds(fan_speed)); |
|
|
|
#if HAS_FAN |
|
|
|
set_fan_speed(ANY(MPC_FAN_0_ALL_HOTENDS, MPC_FAN_0_ACTIVE_HOTEND) ? 0 : active_extruder, 0); |
|
|
|
planner.sync_fan_speeds(fan_speed); |
|
|
|
#endif |
|
|
|
|
|
|
|
hotend.modeled_ambient_temp = ambient_temp; |
|
|
|
|
|
|
|
SERIAL_ECHOLNPGM("Heating to 200C"); |
|
|
|
SERIAL_ECHOLNPGM(STR_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]; |
|
|
@ -921,13 +961,13 @@ volatile bool Temperature::raw_temps_ready = false; |
|
|
|
uint16_t sample_distance = 1; |
|
|
|
float t1_time = 0; |
|
|
|
|
|
|
|
while (wait_for_heatup) { |
|
|
|
housekeeping(ms, current_temp, next_report_ms); |
|
|
|
for (;;) { // Can be interrupted with M108
|
|
|
|
if (!housekeeping(ms, current_temp, next_report_ms)) return; |
|
|
|
|
|
|
|
if (ELAPSED(ms, next_test_ms)) { |
|
|
|
// record samples between 100C and 200C
|
|
|
|
// Record samples between 100C and 200C
|
|
|
|
if (current_temp >= 100.0f) { |
|
|
|
// if there are too many samples, space them more widely
|
|
|
|
// 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]; |
|
|
@ -950,9 +990,9 @@ volatile bool Temperature::raw_temps_ready = false; |
|
|
|
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], |
|
|
|
asymp_temp = (t2 * t2 - t1 * t3) / (2 * t2 - t1 - t3), |
|
|
|
block_responsiveness = -log((t2 - asymp_temp) / (t1 - asymp_temp)) / (sample_distance * (sample_count >> 1)); |
|
|
|
t3 = temp_samples[sample_count - 1]; |
|
|
|
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)); |
|
|
|
|
|
|
|
constants.ambient_xfer_coeff_fan0 = constants.heater_power * MPC_MAX / 255 / (asymp_temp - ambient_temp); |
|
|
|
constants.fan255_adjustment = 0.0f; |
|
|
@ -963,7 +1003,8 @@ volatile bool Temperature::raw_temps_ready = false; |
|
|
|
hotend.modeled_sensor_temp = current_temp; |
|
|
|
|
|
|
|
// Allow the system to stabilize under MPC, then get a better measure of ambient loss with and without fan
|
|
|
|
SERIAL_ECHOLNPGM("Measuring ambient heatloss at target ", hotend.modeled_block_temp); |
|
|
|
SERIAL_ECHOLNPGM(STR_MPC_MEASURING_AMBIENT, hotend.modeled_block_temp); |
|
|
|
LCD_MESSAGE(MSG_MPC_MEASURING_AMBIENT); |
|
|
|
hotend.target = hotend.modeled_block_temp; |
|
|
|
next_test_ms = ms + MPC_dT * 1000; |
|
|
|
constexpr millis_t settle_time = 20000UL, test_duration = 20000UL; |
|
|
@ -976,18 +1017,16 @@ volatile bool Temperature::raw_temps_ready = false; |
|
|
|
#endif |
|
|
|
float last_temp = current_temp; |
|
|
|
|
|
|
|
while (wait_for_heatup) { |
|
|
|
housekeeping(ms, current_temp, next_report_ms); |
|
|
|
for (;;) { // Can be interrupted with M108
|
|
|
|
if (!housekeeping(ms, current_temp, next_report_ms)) return; |
|
|
|
|
|
|
|
if (ELAPSED(ms, next_test_ms)) { |
|
|
|
// use MPC to control the temperature, let it settle for 30s and then track power output for 10s
|
|
|
|
hotend.soft_pwm_amount = (int)get_pid_output_hotend(active_extruder) >> 1; |
|
|
|
|
|
|
|
if (ELAPSED(ms, settle_end_ms) && !ELAPSED(ms, test_end_ms) && TERN1(HAS_FAN, !fan0_done)) |
|
|
|
total_energy_fan0 += constants.heater_power * hotend.soft_pwm_amount / 127 * MPC_dT + (last_temp - current_temp) * constants.block_heat_capacity; |
|
|
|
#if HAS_FAN |
|
|
|
else if (ELAPSED(ms, test_end_ms) && !fan0_done) { |
|
|
|
SERIAL_ECHOLNPGM("Measuring ambient heatloss with full fan"); |
|
|
|
set_fan_speed(ANY(MPC_FAN_0_ALL_HOTENDS, MPC_FAN_0_ACTIVE_HOTEND) ? 0 : active_extruder, 255); |
|
|
|
planner.sync_fan_speeds(fan_speed); |
|
|
|
settle_end_ms = ms + settle_time; |
|
|
@ -1003,8 +1042,8 @@ volatile bool Temperature::raw_temps_ready = false; |
|
|
|
next_test_ms += MPC_dT * 1000; |
|
|
|
} |
|
|
|
|
|
|
|
if (!WITHIN(current_temp, hotend.target - 15.0f, hotend.target + 15.0f)) { |
|
|
|
SERIAL_ECHOLNPGM("Temperature error while measuring ambient loss"); |
|
|
|
if (!WITHIN(current_temp, t3 - 15.0f, hotend.target + 15.0f)) { |
|
|
|
SERIAL_ECHOLNPGM(STR_MPC_TEMPERATURE_ERROR); |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
@ -1018,16 +1057,14 @@ volatile bool Temperature::raw_temps_ready = false; |
|
|
|
constants.fan255_adjustment = ambient_xfer_coeff_fan255 - constants.ambient_xfer_coeff_fan0; |
|
|
|
#endif |
|
|
|
|
|
|
|
hotend.target = 0.0f; |
|
|
|
hotend.soft_pwm_amount = 0; |
|
|
|
TERN_(HAS_FAN, set_fan_speed(ANY(MPC_FAN_0_ALL_HOTENDS, MPC_FAN_0_ACTIVE_HOTEND) ? 0 : active_extruder, 0)); |
|
|
|
TERN_(HAS_FAN, planner.sync_fan_speeds(fan_speed)); |
|
|
|
|
|
|
|
if (!wait_for_heatup) SERIAL_ECHOLNPGM("Test was interrupted"); |
|
|
|
|
|
|
|
wait_for_heatup = false; |
|
|
|
// Calculate a new and better asymptotic temperature and re-evaluate the other constants
|
|
|
|
asymp_temp = ambient_temp + constants.heater_power / constants.ambient_xfer_coeff_fan0; |
|
|
|
block_responsiveness = -log((t2 - asymp_temp) / (t1 - asymp_temp)) / (sample_distance * (sample_count >> 1)); |
|
|
|
constants.block_heat_capacity = constants.ambient_xfer_coeff_fan0 / block_responsiveness; |
|
|
|
constants.sensor_responsiveness = block_responsiveness / (1.0f - (ambient_temp - asymp_temp) * exp(-block_responsiveness * t1_time) / (t1 - asymp_temp)); |
|
|
|
|
|
|
|
SERIAL_ECHOLNPGM("Done"); |
|
|
|
SERIAL_ECHOPGM(STR_MPC_AUTOTUNE); |
|
|
|
SERIAL_ECHOLNPGM(STR_MPC_AUTOTUNE_FINISHED); |
|
|
|
|
|
|
|
/* <-- add a slash to enable
|
|
|
|
SERIAL_ECHOLNPGM("t1_time ", t1_time); |
|
|
@ -1380,7 +1417,7 @@ void Temperature::min_temp_error(const heater_id_t heater_id) { |
|
|
|
|
|
|
|
// At startup, initialize modeled temperatures
|
|
|
|
if (isnan(hotend.modeled_block_temp)) { |
|
|
|
hotend.modeled_ambient_temp = min(30.0f, hotend.celsius); // cap initial value at reasonable max room temperature of 30C
|
|
|
|
hotend.modeled_ambient_temp = min(30.0f, hotend.celsius); // Cap initial value at reasonable max room temperature of 30C
|
|
|
|
hotend.modeled_block_temp = hotend.modeled_sensor_temp = hotend.celsius; |
|
|
|
} |
|
|
|
|
|
|
@ -1401,16 +1438,16 @@ void Temperature::min_temp_error(const heater_id_t heater_id) { |
|
|
|
const int32_t e_position = stepper.position(E_AXIS); |
|
|
|
const float e_speed = (e_position - mpc_e_position) * planner.mm_per_step[E_AXIS] / MPC_dT; |
|
|
|
|
|
|
|
// the position can appear to make big jumps when, e.g. homing
|
|
|
|
// The position can appear to make big jumps when, e.g. homing
|
|
|
|
if (fabs(e_speed) > planner.settings.max_feedrate_mm_s[E_AXIS]) |
|
|
|
mpc_e_position = e_position; |
|
|
|
else if (e_speed > 0.0f) { // ignore retract/recover moves
|
|
|
|
else if (e_speed > 0.0f) { // Ignore retract/recover moves
|
|
|
|
ambient_xfer_coeff += e_speed * FILAMENT_HEAT_CAPACITY_PERMM; |
|
|
|
mpc_e_position = e_position; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// update the modeled temperatures
|
|
|
|
// Update the modeled temperatures
|
|
|
|
float blocktempdelta = hotend.soft_pwm_amount * constants.heater_power * (MPC_dT / 127) / constants.block_heat_capacity; |
|
|
|
blocktempdelta += (hotend.modeled_ambient_temp - hotend.modeled_block_temp) * ambient_xfer_coeff * MPC_dT / constants.block_heat_capacity; |
|
|
|
hotend.modeled_block_temp += blocktempdelta; |
|
|
@ -1424,18 +1461,18 @@ void Temperature::min_temp_error(const heater_id_t heater_id) { |
|
|
|
hotend.modeled_block_temp += delta_to_apply; |
|
|
|
hotend.modeled_sensor_temp += delta_to_apply; |
|
|
|
|
|
|
|
// only correct ambient when close to steady state (output power is not clipped or asymptotic temperature is reached)
|
|
|
|
// Only correct ambient when close to steady state (output power is not clipped or asymptotic temperature is reached)
|
|
|
|
if (WITHIN(hotend.soft_pwm_amount, 1, 126) || fabs(blocktempdelta + delta_to_apply) < (MPC_STEADYSTATE * MPC_dT)) |
|
|
|
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); |
|
|
|
|
|
|
|
float power = 0.0; |
|
|
|
if (hotend.target != 0 && TERN1(HEATER_IDLE_HANDLER, !heater_idle[ee].timed_out)) { |
|
|
|
// plan power level to get to target temperature in 2 seconds
|
|
|
|
// Plan power level to get to target temperature in 2 seconds
|
|
|
|
power = (hotend.target - hotend.modeled_block_temp) * constants.block_heat_capacity / 2.0f; |
|
|
|
power -= (hotend.modeled_ambient_temp - hotend.modeled_block_temp) * ambient_xfer_coeff; |
|
|
|
} |
|
|
|
|
|
|
|
float pid_output = power * 254.0f / constants.heater_power + 1.0f; // ensure correct quantization into a range of 0 to 127
|
|
|
|
float pid_output = power * 254.0f / constants.heater_power + 1.0f; // Ensure correct quantization into a range of 0 to 127
|
|
|
|
pid_output = constrain(pid_output, 0, MPC_MAX); |
|
|
|
|
|
|
|
/* <-- add a slash to enable
|
|
|
@ -2081,7 +2118,7 @@ void Temperature::manage_heater() { |
|
|
|
- (t.beta_recip * t.res_25_log) - (t.sh_c_coeff * cu(t.res_25_log)); |
|
|
|
} |
|
|
|
|
|
|
|
// maximum adc value .. take into account the over sampling
|
|
|
|
// Maximum ADC value .. take into account the over sampling
|
|
|
|
constexpr raw_adc_t adc_max = MAX_RAW_THERMISTOR_VALUE; |
|
|
|
const raw_adc_t adc_raw = constrain(raw, 1, adc_max - 1); // constrain to prevent divide-by-zero
|
|
|
|
|
|
|
@ -3081,7 +3118,7 @@ void Temperature::disable_all_heaters() { |
|
|
|
spiInit(MAX_TC_SPEED_BITS); |
|
|
|
#endif |
|
|
|
|
|
|
|
MAXTC_CS_WRITE(LOW); // enable MAXTC
|
|
|
|
MAXTC_CS_WRITE(LOW); // Enable MAXTC
|
|
|
|
DELAY_NS(100); // Ensure 100ns delay
|
|
|
|
|
|
|
|
// Read a big-endian temperature value without using a library
|
|
|
@ -3090,7 +3127,7 @@ void Temperature::disable_all_heaters() { |
|
|
|
if (i > 0) max_tc_temp <<= 8; // shift left if not the last byte
|
|
|
|
} |
|
|
|
|
|
|
|
MAXTC_CS_WRITE(HIGH); // disable MAXTC
|
|
|
|
MAXTC_CS_WRITE(HIGH); // Disable MAXTC
|
|
|
|
#else |
|
|
|
#if HAS_MAX6675_LIBRARY |
|
|
|
MAX6675 &max6675ref = THERMO_SEL(max6675_0, max6675_1); |
|
|
@ -3301,7 +3338,7 @@ void Temperature::isr() { |
|
|
|
static ADCSensorState adc_sensor_state = StartupDelay; |
|
|
|
static uint8_t pwm_count = _BV(SOFT_PWM_SCALE); |
|
|
|
|
|
|
|
// avoid multiple loads of pwm_count
|
|
|
|
// Avoid multiple loads of pwm_count
|
|
|
|
uint8_t pwm_count_tmp = pwm_count; |
|
|
|
|
|
|
|
#if HAS_ADC_BUTTONS |
|
|
@ -3579,8 +3616,8 @@ void Temperature::isr() { |
|
|
|
// 5: / 4 = 244.1406 Hz
|
|
|
|
pwm_count = pwm_count_tmp + _BV(SOFT_PWM_SCALE); |
|
|
|
|
|
|
|
// increment slow_pwm_count only every 64th pwm_count,
|
|
|
|
// i.e. yielding a PWM frequency of 16/128 Hz (8s).
|
|
|
|
// Increment slow_pwm_count only every 64th pwm_count,
|
|
|
|
// i.e., yielding a PWM frequency of 16/128 Hz (8s).
|
|
|
|
if (((pwm_count >> SOFT_PWM_SCALE) & 0x3F) == 0) { |
|
|
|
slow_pwm_count++; |
|
|
|
slow_pwm_count &= 0x7F; |
|
|
@ -4036,7 +4073,7 @@ void Temperature::isr() { |
|
|
|
|
|
|
|
// Prevent a wait-forever situation if R is misused i.e. M109 R0
|
|
|
|
if (wants_to_cool) { |
|
|
|
// break after MIN_COOLING_SLOPE_TIME seconds
|
|
|
|
// Break after MIN_COOLING_SLOPE_TIME seconds
|
|
|
|
// if the temperature did not drop at least MIN_COOLING_SLOPE_DEG
|
|
|
|
if (!next_cool_check_ms || ELAPSED(now, next_cool_check_ms)) { |
|
|
|
if (old_temp - temp < float(MIN_COOLING_SLOPE_DEG)) break; |
|
|
@ -4058,7 +4095,7 @@ void Temperature::isr() { |
|
|
|
wait_for_heatup = false; |
|
|
|
#if HAS_DWIN_E3V2_BASIC |
|
|
|
HMI_flag.heat_flag = 0; |
|
|
|
duration_t elapsed = print_job_timer.duration(); // print timer
|
|
|
|
duration_t elapsed = print_job_timer.duration(); // Print timer
|
|
|
|
dwin_heat_time = elapsed.value; |
|
|
|
#else |
|
|
|
ui.reset_status(); |
|
|
|