|
|
@ -141,7 +141,8 @@ |
|
|
|
#endif |
|
|
|
#endif |
|
|
|
|
|
|
|
#if ENABLED(PID_EXTRUSION_SCALING) |
|
|
|
#if EITHER(MPCTEMP, PID_EXTRUSION_SCALING) |
|
|
|
#include <math.h> |
|
|
|
#include "stepper.h" |
|
|
|
#endif |
|
|
|
|
|
|
@ -509,10 +510,14 @@ PGMSTR(str_t_heating_failed, STR_T_HEATING_FAILED); |
|
|
|
volatile bool Temperature::raw_temps_ready = false; |
|
|
|
|
|
|
|
#if ENABLED(PID_EXTRUSION_SCALING) |
|
|
|
int32_t Temperature::last_e_position, Temperature::lpq[LPQ_MAX_LEN]; |
|
|
|
int32_t Temperature::pes_e_position, Temperature::lpq[LPQ_MAX_LEN]; |
|
|
|
lpq_ptr_t Temperature::lpq_ptr = 0; |
|
|
|
#endif |
|
|
|
|
|
|
|
#if ENABLED(MPCTEMP) |
|
|
|
int32_t Temperature::mpc_e_position; // = 0
|
|
|
|
#endif |
|
|
|
|
|
|
|
#define TEMPDIR(N) ((TEMP_SENSOR_##N##_RAW_LO_TEMP) < (TEMP_SENSOR_##N##_RAW_HI_TEMP) ? 1 : -1) |
|
|
|
#define TP_CMP(S,A,B) (TEMPDIR(S) < 0 ? ((A)<(B)) : ((A)>(B))) |
|
|
|
|
|
|
@ -587,8 +592,8 @@ volatile bool Temperature::raw_temps_ready = false; |
|
|
|
PID_t tune_pid = { 0, 0, 0 }; |
|
|
|
celsius_float_t maxT = 0, minT = 10000; |
|
|
|
|
|
|
|
const bool isbed = (heater_id == H_BED); |
|
|
|
const bool ischamber = (heater_id == H_CHAMBER); |
|
|
|
const bool isbed = (heater_id == H_BED), |
|
|
|
ischamber = (heater_id == H_CHAMBER); |
|
|
|
|
|
|
|
#if ENABLED(PIDTEMPCHAMBER) |
|
|
|
#define C_TERN(T,A,B) ((T) ? (A) : (B)) |
|
|
@ -846,6 +851,198 @@ volatile bool Temperature::raw_temps_ready = false; |
|
|
|
|
|
|
|
#endif // HAS_PID_HEATING
|
|
|
|
|
|
|
|
#if ENABLED(MPCTEMP) |
|
|
|
|
|
|
|
void Temperature::MPC_autotune() { |
|
|
|
auto housekeeping = [] (millis_t& ms, celsius_float_t& current_temp, millis_t& next_report_ms) { |
|
|
|
ms = millis(); |
|
|
|
|
|
|
|
if (updateTemperaturesIfReady()) { // temp sample ready
|
|
|
|
current_temp = degHotend(active_extruder); |
|
|
|
TERN_(HAS_FAN_LOGIC, manage_extruder_fans(ms)); |
|
|
|
} |
|
|
|
|
|
|
|
if (ELAPSED(ms, next_report_ms)) { |
|
|
|
next_report_ms += 1000UL; |
|
|
|
SERIAL_ECHOLNPGM("Temperature ", current_temp); |
|
|
|
} |
|
|
|
|
|
|
|
hal.idletask(); |
|
|
|
}; |
|
|
|
|
|
|
|
SERIAL_ECHOLNPGM("Measuring MPC constants for E", 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
|
|
|
|
SERIAL_ECHOLNPGM("Moving to tuning position"); |
|
|
|
TERN_(HAS_FAN, zero_fan_speeds()); |
|
|
|
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)); |
|
|
|
gcode.home_all_axes(true); |
|
|
|
const xyz_pos_t tuningpos = MPC_TUNING_POS; |
|
|
|
do_blocking_move_to(tuningpos); |
|
|
|
|
|
|
|
SERIAL_ECHOLNPGM("Cooling to ambient"); |
|
|
|
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); |
|
|
|
|
|
|
|
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; |
|
|
|
} |
|
|
|
} |
|
|
|
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)); |
|
|
|
|
|
|
|
hotend.modeled_ambient_temp = ambient_temp; |
|
|
|
|
|
|
|
SERIAL_ECHOLNPGM("Heating to 200C"); |
|
|
|
hotend.soft_pwm_amount = MPC_MAX >> 1; |
|
|
|
const millis_t heat_start_time = ms; |
|
|
|
next_test_ms = ms; |
|
|
|
celsius_float_t temp_samples[16]; |
|
|
|
uint8_t sample_count = 0; |
|
|
|
uint16_t sample_distance = 1; |
|
|
|
float t1_time = 0; |
|
|
|
|
|
|
|
while (wait_for_heatup) { |
|
|
|
housekeeping(ms, current_temp, next_report_ms); |
|
|
|
|
|
|
|
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; |
|
|
|
} |
|
|
|
} |
|
|
|
hotend.soft_pwm_amount = 0; |
|
|
|
|
|
|
|
// 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], |
|
|
|
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; |
|
|
|
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)); |
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
// let the system stabilise under MPC control then get a better measure of ambient loss without and with fan
|
|
|
|
SERIAL_ECHOLNPGM("Measuring ambient heatloss at target ", hotend.modeled_block_temp); |
|
|
|
hotend.target = hotend.modeled_block_temp; |
|
|
|
next_test_ms = ms + MPC_dT * 1000; |
|
|
|
constexpr millis_t settle_time = 20000UL, |
|
|
|
test_length = 20000UL; |
|
|
|
millis_t settle_end_ms = ms + settle_time, |
|
|
|
test_end_ms = settle_end_ms + test_length; |
|
|
|
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; |
|
|
|
|
|
|
|
while (wait_for_heatup) { |
|
|
|
housekeeping(ms, current_temp, next_report_ms); |
|
|
|
|
|
|
|
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; |
|
|
|
test_end_ms = settle_end_ms + test_length; |
|
|
|
fan0_done = true; |
|
|
|
} |
|
|
|
else if (ELAPSED(ms, settle_end_ms) && !ELAPSED(ms, test_end_ms)) |
|
|
|
total_energy_fan255 += constants.heater_power * hotend.soft_pwm_amount / 127 * MPC_dT + (last_temp - current_temp) * constants.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, hotend.target - 15.0f, hotend.target + 15.0f)) { |
|
|
|
SERIAL_ECHOLNPGM("Temperature error while measuring ambient loss"); |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const float power_fan0 = total_energy_fan0 * 1000 / test_length; |
|
|
|
constants.ambient_xfer_coeff_fan0 = power_fan0 / (hotend.target - ambient_temp); |
|
|
|
|
|
|
|
#if HAS_FAN |
|
|
|
const float power_fan255 = total_energy_fan255 * 1000 / test_length, |
|
|
|
ambient_xfer_coeff_fan255 = power_fan255 / (hotend.target - ambient_temp); |
|
|
|
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; |
|
|
|
|
|
|
|
SERIAL_ECHOLNPGM("Done"); |
|
|
|
|
|
|
|
/* <-- add a slash to enable
|
|
|
|
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); |
|
|
|
SERIAL_ECHOLNPGM("asymp_temp ", asymp_temp); |
|
|
|
SERIAL_ECHOLNPAIR_F("block_responsiveness ", block_responsiveness, 4); |
|
|
|
//*/
|
|
|
|
SERIAL_ECHOLNPGM("MPC_BLOCK_HEAT_CAPACITY ", constants.block_heat_capacity); |
|
|
|
SERIAL_ECHOLNPAIR_F("MPC_SENSOR_RESPONSIVENESS ", constants.sensor_responsiveness, 4); |
|
|
|
SERIAL_ECHOLNPAIR_F("MPC_AMBIENT_XFER_COEFF ", constants.ambient_xfer_coeff_fan0, 4); |
|
|
|
TERN_(HAS_FAN, SERIAL_ECHOLNPAIR_F("MPC_AMBIENT_XFER_COEFF_FAN255 ", ambient_xfer_coeff_fan255, 4)); |
|
|
|
} |
|
|
|
|
|
|
|
#endif // MPCTEMP
|
|
|
|
|
|
|
|
int16_t Temperature::getHeaterPower(const heater_id_t heater_id) { |
|
|
|
switch (heater_id) { |
|
|
|
#if HAS_HEATED_BED |
|
|
@ -1101,7 +1298,7 @@ void Temperature::min_temp_error(const heater_id_t heater_id) { |
|
|
|
pid_reset.set(ee); |
|
|
|
} |
|
|
|
else if (pid_error > PID_FUNCTIONAL_RANGE) { |
|
|
|
pid_output = BANG_MAX; |
|
|
|
pid_output = PID_MAX; |
|
|
|
pid_reset.set(ee); |
|
|
|
} |
|
|
|
else { |
|
|
@ -1128,9 +1325,9 @@ void Temperature::min_temp_error(const heater_id_t heater_id) { |
|
|
|
work_pid[ee].Kc = 0; |
|
|
|
if (this_hotend) { |
|
|
|
const long e_position = stepper.position(E_AXIS); |
|
|
|
if (e_position > last_e_position) { |
|
|
|
lpq[lpq_ptr] = e_position - last_e_position; |
|
|
|
last_e_position = e_position; |
|
|
|
if (e_position > pes_e_position) { |
|
|
|
lpq[lpq_ptr] = e_position - pes_e_position; |
|
|
|
pes_e_position = e_position; |
|
|
|
} |
|
|
|
else |
|
|
|
lpq[lpq_ptr] = 0; |
|
|
@ -1173,7 +1370,86 @@ void Temperature::min_temp_error(const heater_id_t heater_id) { |
|
|
|
} |
|
|
|
#endif |
|
|
|
|
|
|
|
#else // No PID enabled
|
|
|
|
#elif ENABLED(MPCTEMP) |
|
|
|
MPCHeaterInfo& hotend = temp_hotend[ee]; |
|
|
|
MPC_t& constants = hotend.constants; |
|
|
|
|
|
|
|
// 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_block_temp = hotend.modeled_sensor_temp = hotend.celsius; |
|
|
|
} |
|
|
|
|
|
|
|
#if HOTENDS == 1 |
|
|
|
constexpr bool this_hotend = true; |
|
|
|
#else |
|
|
|
const bool this_hotend = (ee == active_extruder); |
|
|
|
#endif |
|
|
|
|
|
|
|
float ambient_xfer_coeff = constants.ambient_xfer_coeff_fan0; |
|
|
|
#if ENABLED(MPC_INCLUDE_FAN) |
|
|
|
const uint8_t fan_index = ANY(MPC_FAN_0_ACTIVE_HOTEND, MPC_FAN_0_ALL_HOTENDS) ? 0 : ee; |
|
|
|
const float fan_fraction = TERN_(MPC_FAN_0_ACTIVE_HOTEND, !this_hotend ? 0.0f : ) fan_speed[fan_index] * RECIPROCAL(255); |
|
|
|
ambient_xfer_coeff += fan_fraction * constants.fan255_adjustment; |
|
|
|
#endif |
|
|
|
|
|
|
|
if (this_hotend) { |
|
|
|
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
|
|
|
|
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
|
|
|
|
ambient_xfer_coeff += e_speed * FILAMENT_HEAT_CAPACITY_PERMM; |
|
|
|
mpc_e_position = e_position; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 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; |
|
|
|
|
|
|
|
const float sensortempdelta = (hotend.modeled_block_temp - hotend.modeled_sensor_temp) * (constants.sensor_responsiveness * MPC_dT); |
|
|
|
hotend.modeled_sensor_temp += sensortempdelta; |
|
|
|
|
|
|
|
// Any delta between hotend.modeled_sensor_temp and hotend.celsius is either model
|
|
|
|
// error diverging slowly or (fast) noise. Slowly correct towards this temperature and noise will average out.
|
|
|
|
const float delta_to_apply = (hotend.celsius - hotend.modeled_sensor_temp) * (MPC_SMOOTHING_FACTOR); |
|
|
|
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)
|
|
|
|
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
|
|
|
|
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
|
|
|
|
pid_output = constrain(pid_output, 0, MPC_MAX); |
|
|
|
|
|
|
|
/* <-- add a slash to enable
|
|
|
|
static uint32_t nexttime = millis() + 1000; |
|
|
|
if (ELAPSED(millis(), nexttime)) { |
|
|
|
nexttime += 1000; |
|
|
|
SERIAL_ECHOLNPGM("block temp ", hotend.modeled_block_temp, |
|
|
|
", celsius ", hotend.celsius, |
|
|
|
", blocktempdelta ", blocktempdelta, |
|
|
|
", delta_to_apply ", delta_to_apply, |
|
|
|
", ambient ", hotend.modeled_ambient_temp, |
|
|
|
", power ", power, |
|
|
|
", pid_output ", pid_output, |
|
|
|
", pwm ", (int)pid_output >> 1); |
|
|
|
} |
|
|
|
//*/
|
|
|
|
|
|
|
|
#else // No PID or MPC enabled
|
|
|
|
|
|
|
|
const bool is_idling = TERN0(HEATER_IDLE_HANDLER, heater_idle[ee].timed_out); |
|
|
|
const float pid_output = (!is_idling && temp_hotend[ee].celsius < temp_hotend[ee].target) ? BANG_MAX : 0; |
|
|
@ -2178,7 +2454,7 @@ void Temperature::init() { |
|
|
|
TERN_(PROBING_HEATERS_OFF, paused_for_probing = false); |
|
|
|
|
|
|
|
#if BOTH(PIDTEMP, PID_EXTRUSION_SCALING) |
|
|
|
last_e_position = 0; |
|
|
|
pes_e_position = 0; |
|
|
|
#endif |
|
|
|
|
|
|
|
// Init (and disable) SPI thermocouples
|
|
|
@ -2248,6 +2524,10 @@ void Temperature::init() { |
|
|
|
)); |
|
|
|
#endif |
|
|
|
|
|
|
|
#if ENABLED(MPCTEMP) |
|
|
|
HOTEND_LOOP() temp_hotend[e].modeled_block_temp = NAN; |
|
|
|
#endif |
|
|
|
|
|
|
|
#if HAS_HEATER_0 |
|
|
|
#ifdef BOARD_OPENDRAIN_MOSFETS |
|
|
|
OUT_WRITE_OD(HEATER_0_PIN, HEATER_0_INVERTING); |
|
|
|