Improve temperature runaway, idle timeout (#19339)

Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
This commit is contained in:
ellensp
2020-09-14 16:58:39 +12:00
committed by Scott Lahteine
parent 2396b4e27f
commit 4fdfdaf2e6
33 changed files with 153 additions and 201 deletions

View File

@ -252,7 +252,7 @@ const char str_t_thermal_runaway[] PROGMEM = STR_T_THERMAL_RUNAWAY,
hotend_watch_t Temperature::watch_hotend[HOTENDS]; // = { { 0 } }
#endif
#if HEATER_IDLE_HANDLER
hotend_idle_t Temperature::hotend_idle[HOTENDS]; // = { { 0 } }
Temperature::heater_idle_t Temperature::heater_idle[NR_HEATER_IDLE]; // = { { 0 } }
#endif
#if HAS_HEATED_BED
@ -266,7 +266,6 @@ const char str_t_thermal_runaway[] PROGMEM = STR_T_THERMAL_RUNAWAY,
#endif
TERN_(WATCH_BED, bed_watch_t Temperature::watch_bed); // = { 0 }
TERN(PIDTEMPBED,, millis_t Temperature::next_bed_check_ms);
TERN_(HEATER_IDLE_HANDLER, hotend_idle_t Temperature::bed_idle); // = { 0 }
#endif // HAS_HEATED_BED
#if HAS_TEMP_CHAMBER
@ -841,7 +840,7 @@ void Temperature::min_temp_error(const heater_id_t heater_id) {
if (temp_hotend[ee].target == 0
|| pid_error < -(PID_FUNCTIONAL_RANGE)
|| TERN0(HEATER_IDLE_HANDLER, hotend_idle[ee].timed_out)
|| TERN0(HEATER_IDLE_HANDLER, heater_idle[ee].timed_out)
) {
pid_output = 0;
pid_reset[ee] = true;
@ -926,7 +925,7 @@ void Temperature::min_temp_error(const heater_id_t heater_id) {
#else // No PID enabled
const bool is_idling = TERN0(HEATER_IDLE_HANDLER, hotend_idle[ee].timed_out);
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;
#endif
@ -1040,15 +1039,14 @@ void Temperature::manage_heater() {
HOTEND_LOOP() {
#if ENABLED(THERMAL_PROTECTION_HOTENDS)
if (degHotend(e) > temp_range[e].maxtemp)
_temp_error((heater_id_t)e, str_t_thermal_runaway, GET_TEXT(MSG_THERMAL_RUNAWAY));
if (degHotend(e) > temp_range[e].maxtemp) max_temp_error((heater_id_t)e);
#endif
TERN_(HEATER_IDLE_HANDLER, hotend_idle[e].update(ms));
TERN_(HEATER_IDLE_HANDLER, heater_idle[e].update(ms));
#if ENABLED(THERMAL_PROTECTION_HOTENDS)
// Check for thermal runaway
thermal_runaway_protection(tr_state_machine[e], temp_hotend[e].celsius, temp_hotend[e].target, (heater_id_t)e, THERMAL_PROTECTION_PERIOD, THERMAL_PROTECTION_HYSTERESIS);
tr_state_machine[e].run(temp_hotend[e].celsius, temp_hotend[e].target, (heater_id_t)e, THERMAL_PROTECTION_PERIOD, THERMAL_PROTECTION_HYSTERESIS);
#endif
temp_hotend[e].soft_pwm_amount = (temp_hotend[e].celsius > temp_range[e].mintemp || is_preheating(e)) && temp_hotend[e].celsius < temp_range[e].maxtemp ? (int)get_pid_output_hotend(e) >> 1 : 0;
@ -1093,8 +1091,7 @@ void Temperature::manage_heater() {
#if HAS_HEATED_BED
#if ENABLED(THERMAL_PROTECTION_BED)
if (degBed() > BED_MAXTEMP)
_temp_error(H_BED, str_t_thermal_runaway, GET_TEXT(MSG_THERMAL_RUNAWAY));
if (degBed() > BED_MAXTEMP) max_temp_error(H_BED);
#endif
#if WATCH_BED
@ -1127,12 +1124,14 @@ void Temperature::manage_heater() {
TERN_(PAUSE_CHANGE_REQD, last_pause_state = paused);
#endif
TERN_(HEATER_IDLE_HANDLER, bed_idle.update(ms));
TERN_(HEATER_IDLE_HANDLER, heater_idle[IDLE_INDEX_BED].update(ms));
TERN_(HAS_THERMALLY_PROTECTED_BED, thermal_runaway_protection(tr_state_machine_bed, temp_bed.celsius, temp_bed.target, H_BED, THERMAL_PROTECTION_BED_PERIOD, THERMAL_PROTECTION_BED_HYSTERESIS));
#if HAS_THERMALLY_PROTECTED_BED
tr_state_machine[RUNAWAY_IND_BED].run(temp_bed.celsius, temp_bed.target, H_BED, THERMAL_PROTECTION_BED_PERIOD, THERMAL_PROTECTION_BED_HYSTERESIS);
#endif
#if HEATER_IDLE_HANDLER
if (bed_idle.timed_out) {
if (heater_idle[IDLE_INDEX_BED].timed_out) {
temp_bed.soft_pwm_amount = 0;
#if DISABLED(PIDTEMPBED)
WRITE_HEATER_BED(LOW);
@ -1173,8 +1172,7 @@ void Temperature::manage_heater() {
#endif
#if ENABLED(THERMAL_PROTECTION_CHAMBER)
if (degChamber() > CHAMBER_MAXTEMP)
_temp_error(H_CHAMBER, str_t_thermal_runaway, GET_TEXT(MSG_THERMAL_RUNAWAY));
if (degChamber() > CHAMBER_MAXTEMP) max_temp_error(H_CHAMBER);
#endif
#if WATCH_CHAMBER
@ -1205,7 +1203,9 @@ void Temperature::manage_heater() {
WRITE_HEATER_CHAMBER(LOW);
}
TERN_(THERMAL_PROTECTION_CHAMBER, thermal_runaway_protection(tr_state_machine_chamber, temp_chamber.celsius, temp_chamber.target, H_CHAMBER, THERMAL_PROTECTION_CHAMBER_PERIOD, THERMAL_PROTECTION_CHAMBER_HYSTERESIS));
#if ENABLED(THERMAL_PROTECTION_CHAMBER)
tr_state_machine[RUNAWAY_IND_CHAMBER].run(temp_chamber.celsius, temp_chamber.target, H_CHAMBER, THERMAL_PROTECTION_CHAMBER_PERIOD, THERMAL_PROTECTION_CHAMBER_HYSTERESIS);
#endif
}
// TODO: Implement true PID pwm
@ -1935,61 +1935,66 @@ void Temperature::init() {
#if HAS_THERMAL_PROTECTION
#if ENABLED(THERMAL_PROTECTION_HOTENDS)
Temperature::tr_state_machine_t Temperature::tr_state_machine[HOTENDS]; // = { { TRInactive, 0 } };
#endif
#if HAS_THERMALLY_PROTECTED_BED
Temperature::tr_state_machine_t Temperature::tr_state_machine_bed; // = { TRInactive, 0 };
#endif
#if ENABLED(THERMAL_PROTECTION_CHAMBER)
Temperature::tr_state_machine_t Temperature::tr_state_machine_chamber; // = { TRInactive, 0 };
#endif
Temperature::tr_state_machine_t Temperature::tr_state_machine[NR_HEATER_RUNAWAY]; // = { { TRInactive, 0 } };
void Temperature::thermal_runaway_protection(Temperature::tr_state_machine_t &sm, const float &current, const float &target, const heater_id_t heater_id, const uint16_t period_seconds, const uint16_t hysteresis_degc) {
/**
* @brief Thermal Runaway state machine for a single heater
* @param current current measured temperature
* @param target current target temperature
* @param heater_id extruder index
* @param period_seconds missed temperature allowed time
* @param hysteresis_degc allowed distance from target
*
* TODO: Embed the last 3 parameters during init, if not less optimal
*/
void Temperature::tr_state_machine_t::run(const float &current, const float &target, const heater_id_t heater_id, const uint16_t period_seconds, const uint16_t hysteresis_degc) {
static float tr_target_temperature[HOTENDS + 1] = { 0.0 };
#if HEATER_IDLE_HANDLER
// Convert the given heater_id_t to an idle array index
const IdleIndex idle_index = idle_index_for_id(heater_id);
#endif
/**
SERIAL_ECHO_START();
SERIAL_ECHOPGM("Thermal Runaway Running. Heater ID: ");
if (heater_id == H_CHAMBER) SERIAL_ECHOPGM("chamber");
if (heater_id < 0) SERIAL_ECHOPGM("bed"); else SERIAL_ECHO(heater_id);
SERIAL_ECHOPAIR(" ; State:", sm.state, " ; Timer:", sm.timer, " ; Temperature:", current, " ; Target Temp:", target);
if (heater_id >= 0)
SERIAL_ECHOPAIR(" ; Idle Timeout:", hotend_idle[heater_id].timed_out);
else
SERIAL_ECHOPAIR(" ; Idle Timeout:", bed_idle.timed_out);
SERIAL_EOL();
switch (heater_id) {
case H_BED: SERIAL_ECHOPGM("bed"); break;
case H_CHAMBER: SERIAL_ECHOPGM("chamber"); break;
default: SERIAL_ECHO(heater_id);
}
SERIAL_ECHOLNPAIR(
" ; sizeof(running_temp):", sizeof(running_temp),
" ; State:", state, " ; Timer:", timer, " ; Temperature:", current, " ; Target Temp:", target
#if HEATER_IDLE_HANDLER
, " ; Idle Timeout:", heater_idle[idle_index].timed_out
#endif
);
//*/
const int heater_index = heater_id >= 0 ? heater_id : HOTENDS;
#if HEATER_IDLE_HANDLER
// If the heater idle timeout expires, restart
if ((heater_id >= 0 && hotend_idle[heater_id].timed_out)
|| TERN0(HAS_HEATED_BED, (heater_id < 0 && bed_idle.timed_out))
) {
sm.state = TRInactive;
tr_target_temperature[heater_index] = 0;
if (heater_idle[idle_index].timed_out) {
state = TRInactive;
running_temp = 0;
}
else
#endif
{
// If the target temperature changes, restart
if (tr_target_temperature[heater_index] != target) {
tr_target_temperature[heater_index] = target;
sm.state = target > 0 ? TRFirstHeating : TRInactive;
if (running_temp != target) {
running_temp = target;
state = target > 0 ? TRFirstHeating : TRInactive;
}
}
switch (sm.state) {
switch (state) {
// Inactive state waits for a target temperature to be set
case TRInactive: break;
// When first heating, wait for the temperature to be reached then go to Stable state
case TRFirstHeating:
if (current < tr_target_temperature[heater_index]) break;
sm.state = TRStable;
if (current < running_temp) break;
state = TRStable;
// While the temperature is stable watch for a bad temperature
case TRStable:
@ -1997,25 +2002,25 @@ void Temperature::init() {
#if ENABLED(ADAPTIVE_FAN_SLOWING)
if (adaptive_fan_slowing && heater_id >= 0) {
const int fan_index = _MIN(heater_id, FAN_COUNT - 1);
if (fan_speed[fan_index] == 0 || current >= tr_target_temperature[heater_id] - (hysteresis_degc * 0.25f))
if (fan_speed[fan_index] == 0 || current >= running_temp - (hysteresis_degc * 0.25f))
fan_speed_scaler[fan_index] = 128;
else if (current >= tr_target_temperature[heater_id] - (hysteresis_degc * 0.3335f))
else if (current >= running_temp - (hysteresis_degc * 0.3335f))
fan_speed_scaler[fan_index] = 96;
else if (current >= tr_target_temperature[heater_id] - (hysteresis_degc * 0.5f))
else if (current >= running_temp - (hysteresis_degc * 0.5f))
fan_speed_scaler[fan_index] = 64;
else if (current >= tr_target_temperature[heater_id] - (hysteresis_degc * 0.8f))
else if (current >= running_temp - (hysteresis_degc * 0.8f))
fan_speed_scaler[fan_index] = 32;
else
fan_speed_scaler[fan_index] = 0;
}
#endif
if (current >= tr_target_temperature[heater_index] - hysteresis_degc) {
sm.timer = millis() + SEC_TO_MS(period_seconds);
if (current >= running_temp - hysteresis_degc) {
timer = millis() + SEC_TO_MS(period_seconds);
break;
}
else if (PENDING(millis(), sm.timer)) break;
sm.state = TRRunaway;
else if (PENDING(millis(), timer)) break;
state = TRRunaway;
case TRRunaway:
TERN_(DWIN_CREALITY_LCD, Popup_Window_Temperature(0));
@ -2086,8 +2091,8 @@ void Temperature::disable_all_heaters() {
if (p != paused) {
paused = p;
if (p) {
HOTEND_LOOP() hotend_idle[e].expire(); // Timeout immediately
TERN_(HAS_HEATED_BED, bed_idle.expire()); // Timeout immediately
HOTEND_LOOP() heater_idle[e].expire(); // Timeout immediately
TERN_(HAS_HEATED_BED, heater_idle[IDLE_INDEX_BED].expire()); // Timeout immediately
}
else {
HOTEND_LOOP() reset_hotend_idle_timer(e);
@ -2333,9 +2338,7 @@ void Temperature::readings_ready() {
#else
#define BEDCMP(A,B) ((A)>(B))
#endif
const bool bed_on = temp_bed.target > 0
|| TERN0(PIDTEMPBED, temp_bed.soft_pwm_amount) > 0
;
const bool bed_on = (temp_bed.target > 0) || TERN0(PIDTEMPBED, temp_bed.soft_pwm_amount > 0);
if (BEDCMP(temp_bed.raw, maxtemp_raw_BED)) max_temp_error(H_BED);
if (bed_on && BEDCMP(mintemp_raw_BED, temp_bed.raw)) min_temp_error(H_BED);
#endif

View File

@ -40,7 +40,7 @@
#define HOTEND_INDEX TERN(HAS_MULTI_HOTEND, e, 0)
#define E_NAME TERN_(HAS_MULTI_HOTEND, e)
// Identifiers for other heaters
// Heater identifiers. Positive values are hotends. Negative values are other heaters.
typedef enum : int8_t {
INDEX_NONE = -5,
H_PROBE, H_REDUNDANT, H_CHAMBER, H_BED,
@ -211,16 +211,6 @@ struct PIDHeaterInfo : public HeaterInfo {
typedef temp_info_t chamber_info_t;
#endif
// Heater idle handling
typedef struct {
millis_t timeout_ms;
bool timed_out;
inline void update(const millis_t &ms) { if (!timed_out && timeout_ms && ELAPSED(ms, timeout_ms)) timed_out = true; }
inline void start(const millis_t &ms) { timeout_ms = millis() + ms; timed_out = false; }
inline void reset() { timeout_ms = 0; timed_out = false; }
inline void expire() { start(0); }
} hotend_idle_t;
// Heater watch handling
template <int INCREASE, int HYSTERESIS, millis_t PERIOD>
struct HeaterWatch {
@ -346,9 +336,38 @@ class Temperature {
FORCE_INLINE static bool targetHotEnoughToExtrude(const uint8_t e) { return !targetTooColdToExtrude(e); }
#if HEATER_IDLE_HANDLER
static hotend_idle_t hotend_idle[HOTENDS];
TERN_(HAS_HEATED_BED, static hotend_idle_t bed_idle);
TERN_(HAS_HEATED_CHAMBER, static hotend_idle_t chamber_idle);
// Heater idle handling. Marlin creates one per hotend and one for the heated bed.
typedef struct {
millis_t timeout_ms;
bool timed_out;
inline void update(const millis_t &ms) { if (!timed_out && timeout_ms && ELAPSED(ms, timeout_ms)) timed_out = true; }
inline void start(const millis_t &ms) { timeout_ms = millis() + ms; timed_out = false; }
inline void reset() { timeout_ms = 0; timed_out = false; }
inline void expire() { start(0); }
} heater_idle_t;
// Indices and size for the heater_idle array
#define _ENUM_FOR_E(N) IDLE_INDEX_E##N,
enum IdleIndex : uint8_t {
REPEAT(HOTENDS, _ENUM_FOR_E)
#if ENABLED(HAS_HEATED_BED)
IDLE_INDEX_BED,
#endif
NR_HEATER_IDLE
};
#undef _ENUM_FOR_E
// Convert the given heater_id_t to idle array index
static inline IdleIndex idle_index_for_id(const int8_t heater_id) {
#if HAS_HEATED_BED
if (heater_id == H_BED) return IDLE_INDEX_BED;
#endif
return (IdleIndex)_MAX(heater_id, 0);
}
static heater_idle_t heater_idle[NR_HEATER_IDLE];
#endif
private:
@ -747,13 +766,13 @@ class Temperature {
#if HEATER_IDLE_HANDLER
static void reset_hotend_idle_timer(const uint8_t E_NAME) {
hotend_idle[HOTEND_INDEX].reset();
heater_idle[HOTEND_INDEX].reset();
start_watching_hotend(HOTEND_INDEX);
}
#if HAS_HEATED_BED
static void reset_bed_idle_timer() {
bed_idle.reset();
heater_idle[IDLE_INDEX_BED].reset();
start_watching_bed();
}
#endif
@ -815,22 +834,47 @@ class Temperature {
static void min_temp_error(const heater_id_t e);
static void max_temp_error(const heater_id_t e);
#define HAS_THERMAL_PROTECTION (EITHER(THERMAL_PROTECTION_HOTENDS, THERMAL_PROTECTION_CHAMBER) || HAS_THERMALLY_PROTECTED_BED)
#define HAS_THERMAL_PROTECTION ANY(THERMAL_PROTECTION_HOTENDS, THERMAL_PROTECTION_CHAMBER, HAS_THERMALLY_PROTECTED_BED)
#if HAS_THERMAL_PROTECTION
// Indices and size for the tr_state_machine array. One for each protected heater.
#define _ENUM_FOR_E(N) RUNAWAY_IND_E##N,
enum RunawayIndex : uint8_t {
#if ENABLED(THERMAL_PROTECTION_HOTENDS)
REPEAT(HOTENDS, _ENUM_FOR_E)
#endif
#if ENABLED(HAS_THERMALLY_PROTECTED_BED)
RUNAWAY_IND_BED,
#endif
#if ENABLED(THERMAL_PROTECTION_CHAMBER)
RUNAWAY_IND_CHAMBER,
#endif
NR_HEATER_RUNAWAY
};
#undef _ENUM_FOR_E
// Convert the given heater_id_t to runaway state array index
static inline RunawayIndex runaway_index_for_id(const int8_t heater_id) {
#if HAS_THERMALLY_PROTECTED_CHAMBER
if (heater_id == H_CHAMBER) return RUNAWAY_IND_CHAMBER;
#endif
#if HAS_THERMALLY_PROTECTED_BED
if (heater_id == H_BED) return RUNAWAY_IND_BED;
#endif
return (RunawayIndex)_MAX(heater_id, 0);
}
enum TRState : char { TRInactive, TRFirstHeating, TRStable, TRRunaway };
typedef struct {
millis_t timer = 0;
TRState state = TRInactive;
float running_temp;
void run(const float &current, const float &target, const heater_id_t heater_id, const uint16_t period_seconds, const uint16_t hysteresis_degc);
} tr_state_machine_t;
TERN_(THERMAL_PROTECTION_HOTENDS, static tr_state_machine_t tr_state_machine[HOTENDS]);
TERN_(HAS_THERMALLY_PROTECTED_BED, static tr_state_machine_t tr_state_machine_bed);
TERN_(THERMAL_PROTECTION_CHAMBER, static tr_state_machine_t tr_state_machine_chamber);
static void thermal_runaway_protection(tr_state_machine_t &state, const float &current, const float &target, const heater_id_t heater_id, const uint16_t period_seconds, const uint16_t hysteresis_degc);
static tr_state_machine_t tr_state_machine[NR_HEATER_RUNAWAY];
#endif // HAS_THERMAL_PROTECTION
};