diff --git a/Firmware/Marlin_main.cpp b/Firmware/Marlin_main.cpp
index e8027aa3..ed71ce35 100644
--- a/Firmware/Marlin_main.cpp
+++ b/Firmware/Marlin_main.cpp
@@ -7760,6 +7760,54 @@ Sigma_Exit:
PID_autotune(temp, e, c);
}
break;
+
+ /*!
+ ### M310 - Temperature model
+ #### Usage
+
+ M310 [ C ] [ P ] [ I R ] [ S ] [ E ] [ W ] [ A ] [ T ]
+
+ #### Parameters
+ - `P` - power
+ - `C` - capacitance
+ - `I` - resistance index position
+ - `R` - resistance value (requires `I`)
+ - `S` - set 0=disable 1=enable (default)
+ - `E` - error threshold (define min/max values in variants)
+ - `W` - warning threshold (define min/max values in variants)
+ - `T` - ambient temperature correction
+ - `A` - autotune C+R values
+ */
+ case 310:
+ {
+ // parse all parameters
+ float P = NAN, C = NAN, R = NAN, E = NAN, W = NAN, T = NAN, A = NAN;
+ int8_t I = -1, S = -1;
+ if(code_seen('C')) C = code_value();
+ if(code_seen('P')) P = code_value();
+ if(code_seen('I')) I = code_value_short();
+ if(code_seen('R')) R = code_value();
+ if(code_seen('S')) S = code_value_short();
+ if(code_seen('E')) E = code_value();
+ if(code_seen('W')) W = code_value();
+ if(code_seen('T')) T = code_value();
+ if(code_seen('A')) A = code_value();
+
+ // report values if nothing has been requested
+ if(isnan(C) && isnan(P) && isnan(R) && isnan(E) && isnan(W) && isnan(T) && isnan(A) && I < 0 && S < 0) {
+ temp_model_report_settings();
+ break;
+ }
+
+ // update all set parameters
+ if(S >= 0) temp_model_set_enabled(S);
+ if(!isnan(C) || !isnan(P) || !isnan(T) || !isnan(W) || !isnan(E)) temp_model_set_params(C, P, T, W, E);
+ if(I >= 0 && !isnan(R)) temp_model_set_resistance(I, R);
+
+ // run autotune
+ if(!isnan(A)) temp_model_autotune(A != 0? A: NAN);
+ }
+ break;
/*!
### M400 - Wait for all moves to finish M400: Wait for current moves to finish
@@ -9144,7 +9192,16 @@ Sigma_Exit:
};
#endif
-#ifdef TEMP_MODEL_LOGGING
+#ifdef TEMP_MODEL_DEBUG
+ /*!
+ ## D70 - Enable low-level temperature model logging for offline simulation
+ #### Usage
+
+ D70 [ I ]
+
+ #### Parameters
+ - `I` - Enable 0-1 (default 0)
+ */
case 70: {
if(code_seen('I'))
temp_model_log_enable(code_value_short());
diff --git a/Firmware/temp_model.h b/Firmware/temp_model.h
new file mode 100644
index 00000000..30445f0f
--- /dev/null
+++ b/Firmware/temp_model.h
@@ -0,0 +1,110 @@
+// model-based temperature safety checker declarations
+#ifndef TEMP_MGR_INTV
+#error "this file is not a public interface, it should be used *only* within temperature.cpp!"
+#endif
+
+#include "planner.h"
+
+constexpr uint8_t TEMP_MODEL_CAL_S = 60; // Maximum recording lenght during calibration (s)
+constexpr float TEMP_MODEL_fS = 0.065; // simulation filter (1st-order IIR factor)
+constexpr float TEMP_MODEL_fE = 0.05; // error filter (1st-order IIR factor)
+
+// transport delay buffer size (samples)
+constexpr uint8_t TEMP_MODEL_LAG_SIZE = (TEMP_MODEL_LAG / TEMP_MGR_INTV + 0.5);
+
+// resistance values for all fan levels
+constexpr uint8_t TEMP_MODEL_R_SIZE = (1 << FAN_SOFT_PWM_BITS);
+
+namespace temp_model {
+
+// recording scratch buffer
+struct rec_entry
+{
+ float temp; // heater temperature
+ uint8_t pwm; // heater PWM
+};
+
+constexpr uint16_t rec_buffer_size = TEMP_MODEL_CAL_S / TEMP_MGR_INTV;
+static rec_entry* const rec_buffer = (rec_entry*)block_buffer; // oh-hey, free memory!
+static_assert(sizeof(rec_entry[rec_buffer_size]) <= sizeof(block_buffer),
+ "recording length too long to fit within available buffer");
+
+struct model_data
+{
+ // temporary buffers
+ float dT_lag_buf[TEMP_MODEL_LAG_SIZE]; // transport delay buffer
+ uint8_t dT_lag_idx = 0; // transport delay buffer index
+ float dT_err_prev = 0; // previous temperature delta error
+ float T_prev = 0; // last temperature extruder
+
+ // configurable parameters
+ float P; // heater power (W)
+ float C; // heatblock capacitance (J/K)
+ float R[TEMP_MODEL_R_SIZE]; // heatblock resistance for all fan levels (K/W)
+ float Ta_corr; // ambient temperature correction (K)
+
+ // thresholds
+ float warn; // pre-warning threshold (K/s)
+ float err; // error threshold (K/s)
+
+ // status flags
+ union
+ {
+ bool flags;
+ struct
+ {
+ bool uninitialized: 1; // model is not initialized
+ bool error: 1; // error threshold set
+ bool warning: 1; // warning threshold set
+ } flag_bits;
+ };
+
+ // pre-computed values (initialized via reset)
+ float C_i; // heatblock capacitance (precomputed dT/C)
+ float warn_s; // pre-warning threshold (per sample)
+ float err_s; // error threshold (per sample)
+
+ // simulation functions
+ void reset(uint8_t heater_pwm, uint8_t fan_pwm, float heater_temp, float ambient_temp);
+ void step(uint8_t heater_pwm, uint8_t fan_pwm, float heater_temp, float ambient_temp);
+};
+
+static bool enabled; // model check enabled
+static model_data data; // default heater data
+
+static void init(); // initialize and setup the model subsystem
+static bool calibrated(); // return calibration/model validity status
+static void check(); // check and trigger errors or warnings based on current state
+
+// warning state (updated from from isr context)
+volatile static struct
+{
+ float dT_err; // temperature delta error (per sample)
+ bool warning: 1; // warning condition
+ bool assert: 1; // warning is still asserted
+} warning_state;
+
+static void handle_warning(); // handle warnings from user context
+
+#ifdef TEMP_MODEL_DEBUG
+static struct
+{
+ volatile struct
+ {
+ uint32_t stamp;
+ int8_t delta_ms;
+ uint8_t counter;
+ uint8_t cur_pwm;
+ float cur_temp;
+ float cur_amb;
+ } entry;
+
+ uint8_t serial;
+ bool enabled;
+} log_buf;
+
+static void log_usr(); // user log handler
+static void log_isr(); // isr log handler
+#endif
+
+} // namespace temp_model
diff --git a/Firmware/temperature.cpp b/Firmware/temperature.cpp
index d9353d2e..f306618f 100755
--- a/Firmware/temperature.cpp
+++ b/Firmware/temperature.cpp
@@ -48,6 +48,19 @@
#error "ADC_OVRSAMPL oversampling must match OVERSAMPLENR"
#endif
+// temperature manager timer configuration
+#define TEMP_MGR_INTV 0.27 // seconds, ~3.7Hz
+#define TIMER5_PRESCALE 256
+#define TIMER5_OCRA_OVF (uint16_t)(TEMP_MGR_INTV / ((long double)TIMER5_PRESCALE / F_CPU))
+#define TEMP_MGR_INTERRUPT_STATE() (TIMSK5 & (1<= B? A: B)
-#endif
-// samples required for settling the model (crude approximation)
-static uint8_t TM_dT_smp = MAX(TM_dTs, MAX(3/TM_fS, 3/TM_fE));
-
-static void check_temp_model()
+void model_data::reset(uint8_t heater_pwm, uint8_t fan_pwm, float heater_temp, float ambient_temp)
{
- const float soft_pwm_inv = 1. / ((1 << 7) - 1);
- const float soft_pwm_fan_inv = 1. / ((1 << FAN_SOFT_PWM_BITS) - 1);
+ // pre-compute invariant values
+ C_i = (TEMP_MGR_INTV / C);
+ warn_s = warn * TEMP_MGR_INTV;
+ err_s = err * TEMP_MGR_INTV;
+
+ // initial values
+ memset(dT_lag_buf, 0, sizeof(dT_lag_buf));
+ dT_lag_idx = 0;
+ dT_err_prev = 0;
+ T_prev = heater_temp;
+
+ // perform one step to initialize the first delta
+ step(heater_pwm, fan_pwm, heater_temp, ambient_temp);
+
+ // clear the initialization flag
+ flag_bits.uninitialized = false;
+}
+
+void model_data::step(uint8_t heater_pwm, uint8_t fan_pwm, float heater_temp, float ambient_temp)
+{
+ constexpr float soft_pwm_inv = 1. / ((1 << 7) - 1);
// input values
- float heater_scale = soft_pwm_inv * soft_pwm[0];
- float fan_scale = soft_pwm_fan_inv * soft_pwm_fan;
- float cur_temp_heater = current_temperature_isr[0];
- float cur_temp_ambient = current_temperature_ambient_isr + TM_aC;
+ const float heater_scale = soft_pwm_inv * heater_pwm;
+ const float cur_heater_temp = heater_temp;
+ const float cur_ambient_temp = ambient_temp + Ta_corr;
+ const float cur_R = R[fan_pwm]; // resistance at current fan power (K/W)
- // model invariants
- float C_i = (TEMP_MGR_INTV / TM_C);
-
- float dP = TM_P * heater_scale; // current power [W]
- float R = TM_R + TM_Rf * fan_scale; // resistance (constant + fan modulation)
- float dPl = (cur_temp_heater - cur_temp_ambient) / R; // [W] leakage power
+ float dP = P * heater_scale; // current power [W]
+ float dPl = (cur_heater_temp - cur_ambient_temp) / cur_R; // [W] leakage power
float dT = (dP - dPl) * C_i; // expected temperature difference (K)
// filter and lag dT
- uint8_t next_dT_idx = (TM_dT_idx == (TM_dTs - 1) ? 0: TM_dT_idx + 1);
- float lag_dT = TM_dT_buf[next_dT_idx];
- float prev_dT = TM_dT_buf[TM_dT_idx];
- float dTf = (prev_dT * (1. - TM_fS)) + (dT * TM_fS);
- TM_dT_buf[next_dT_idx] = dTf;
- TM_dT_idx = next_dT_idx;
+ uint8_t dT_next_idx = (dT_lag_idx == (TEMP_MODEL_LAG_SIZE - 1) ? 0: dT_lag_idx + 1);
+ float dT_lag = dT_lag_buf[dT_next_idx];
+ float dT_lag_prev = dT_lag_buf[dT_lag_idx];
+ float dT_f = (dT_lag_prev * (1.f - TEMP_MODEL_fS)) + (dT * TEMP_MODEL_fS);
+ dT_lag_buf[dT_next_idx] = dT_f;
+ dT_lag_idx = dT_next_idx;
// calculate and filter dT_err
- float dT_err = (cur_temp_heater - TM_T) - lag_dT;
- float dT_err_f = (TM_dT_err * (1. - TM_fE)) + (dT_err * TM_fE);
- TM_T = cur_temp_heater;
- TM_dT_err = dT_err_f;
+ float dT_err = (cur_heater_temp - T_prev) - dT_lag;
+ float dT_err_f = (dT_err_prev * (1.f - TEMP_MODEL_fE)) + (dT_err * TEMP_MODEL_fE);
+ T_prev = cur_heater_temp;
+ dT_err_prev = dT_err_f;
// check and trigger errors
- if(TM_dT_smp) {
- // model not ready
- --TM_dT_smp;
- } else {
- if(dT_err_f > TM_err_s || dT_err_f < -TM_err_s)
- set_temp_error(TempErrorSource::hotend, 0, TempErrorType::model);
+ flag_bits.error = (fabsf(dT_err_f) > err_s);
+ flag_bits.warning = (fabsf(dT_err_f) > warn_s);
+}
+
+// verify calibration status and trigger a model reset if valid
+void setup()
+{
+ if(!calibrated()) enabled = false;
+ data.flag_bits.uninitialized = true;
+}
+
+void init()
+{
+ // TODO: initialize the model with eeprom values
+ data.P = TEMP_MODEL_P;
+ data.C = TEMP_MODEL_C;
+ for(uint8_t i = 0; i != TEMP_MODEL_R_SIZE; ++i)
+ data.R[i] = TEMP_MODEL_R;
+ data.Ta_corr = TEMP_MODEL_Ta_corr;
+ data.warn = TEMP_MODEL_W;
+ data.err = TEMP_MODEL_E;
+
+ enabled = true;
+ setup();
+}
+
+bool calibrated()
+{
+ if(!(data.P >= 0)) return false;
+ if(!(data.C >= 0)) return false;
+ if(!(data.Ta_corr != NAN)) return false;
+ for(uint8_t i = 0; i != TEMP_MODEL_R_SIZE; ++i) {
+ if(!(temp_model::data.R[i] >= 0))
+ return false;
+ }
+ if(!(data.warn != NAN)) return false;
+ if(!(data.err != NAN)) return false;
+ return true;
+}
+
+void check()
+{
+ if(!enabled) return;
+
+ uint8_t heater_pwm = soft_pwm[0];
+ uint8_t fan_pwm = soft_pwm_fan;
+ float heater_temp = current_temperature_isr[0];
+ float ambient_temp = current_temperature_ambient_isr;
+
+ // check if a reset is required to seed the model: this needs to be done with valid
+ // ADC values, so we can't do that directly in init()
+ if(data.flag_bits.uninitialized)
+ data.reset(heater_pwm, fan_pwm, heater_temp, ambient_temp);
+
+ // step the model
+ data.step(heater_pwm, fan_pwm, heater_temp, ambient_temp);
+
+ // handle errors
+ if(data.flag_bits.error)
+ set_temp_error(TempErrorSource::hotend, 0, TempErrorType::model);
+
+ // handle warning conditions as lower-priority but with greater feedback
+ warning_state.assert = data.flag_bits.warning;
+ if(warning_state.assert) {
+ warning_state.warning = true;
+ warning_state.dT_err = temp_model::data.dT_err_prev;
}
}
-#ifdef TEMP_MODEL_LOGGING
-static struct
+void handle_warning()
{
- volatile struct
+ // update values
+ float warn = data.warn;
+ float dT_err;
{
- uint32_t stamp;
- int8_t delta_ms;
- uint8_t counter;
- uint8_t cur_pwm;
- float cur_temp;
- float cur_amb;
- } entry;
+ TempMgrGuard temp_mgr_guard;
+ dT_err = warning_state.dT_err;
+ }
+ dT_err /= TEMP_MGR_INTV; // per-sample => K/s
- uint8_t serial;
- bool enabled;
-} temp_model_log_buf;
+ // TODO: alert the user on the lcd
+ printf_P(PSTR("TM: error |%f|>%f\n"), (double)dT_err, (double)warn);
-static void temp_model_log_usr()
+ static bool beeper = false;
+ if(warning_state.assert) {
+ // beep periodically
+ beeper = !beeper;
+ WRITE(BEEPER, beeper);
+ } else {
+ // warning cleared, reset state
+ warning_state.warning = false;
+ beeper = false;
+ WRITE(BEEPER, LOW);
+ }
+}
+
+#ifdef TEMP_MODEL_DEBUG
+void log_usr()
{
- if(!temp_model_log_buf.enabled)
- return;
+ if(!log_buf.enabled) return;
- uint8_t counter = temp_model_log_buf.entry.counter;
- if (counter == temp_model_log_buf.serial)
- return;
+ uint8_t counter = log_buf.entry.counter;
+ if (counter == log_buf.serial) return;
int8_t delta_ms;
uint8_t cur_pwm;
- float cur_temp;
- float cur_amb;
+
+ // avoid strict-aliasing warnings
+ union { float cur_temp; uint32_t cur_temp_b; };
+ union { float cur_amb; uint32_t cur_amb_b; };
+
{
TempMgrGuard temp_mgr_guard;
- delta_ms = temp_model_log_buf.entry.delta_ms;
- counter = temp_model_log_buf.entry.counter;
- cur_pwm = temp_model_log_buf.entry.cur_pwm;
- cur_temp = temp_model_log_buf.entry.cur_temp;
- cur_amb = temp_model_log_buf.entry.cur_amb;
+ delta_ms = log_buf.entry.delta_ms;
+ counter = log_buf.entry.counter;
+ cur_pwm = log_buf.entry.cur_pwm;
+ cur_temp = log_buf.entry.cur_temp;
+ cur_amb = log_buf.entry.cur_amb;
}
- uint8_t d = counter - temp_model_log_buf.serial;
- temp_model_log_buf.serial = counter;
+ uint8_t d = counter - log_buf.serial;
+ log_buf.serial = counter;
- printf_P(PSTR("TML %d %d %x %lx %lx\n"), (unsigned)d - 1, (int)delta_ms + 1, (int)cur_pwm,
- *(unsigned long*)&cur_temp, *(unsigned long*)&cur_amb);
+ printf_P(PSTR("TML %d %d %x %lx %lx\n"), (unsigned)d - 1, (int)delta_ms + 1,
+ (int)cur_pwm, (unsigned long)cur_temp_b, (unsigned long)cur_amb_b);
}
-static void temp_model_log_isr()
+void log_isr()
{
- if(!temp_model_log_buf.enabled)
- return;
+ if(!log_buf.enabled) return;
uint32_t stamp = _millis();
- uint8_t delta_ms = stamp - temp_model_log_buf.entry.stamp - (TEMP_MGR_INTV * 1000);
- temp_model_log_buf.entry.stamp = stamp;
+ uint8_t delta_ms = stamp - log_buf.entry.stamp - (TEMP_MGR_INTV * 1000);
+ log_buf.entry.stamp = stamp;
- ++temp_model_log_buf.entry.counter;
- temp_model_log_buf.entry.delta_ms = delta_ms;
- temp_model_log_buf.entry.cur_pwm = soft_pwm[0];
- temp_model_log_buf.entry.cur_temp = current_temperature_isr[0];
- temp_model_log_buf.entry.cur_amb = current_temperature_ambient_isr;
+ ++log_buf.entry.counter;
+ log_buf.entry.delta_ms = delta_ms;
+ log_buf.entry.cur_pwm = soft_pwm[0];
+ log_buf.entry.cur_temp = current_temperature_isr[0];
+ log_buf.entry.cur_amb = current_temperature_ambient_isr;
+}
+#endif
+
+} // namespace temp_model
+
+void temp_model_set_enabled(bool enabled)
+{
+ // set the enabled flag
+ {
+ TempMgrGuard temp_mgr_guard;
+ temp_model::enabled = enabled;
+ temp_model::setup();
+ }
+
+ // verify that the model has been enabled
+ if(enabled && !temp_model::enabled)
+ SERIAL_ECHOLNPGM("TM: invalid parameters, cannot enable");
}
+void temp_model_set_params(float C, float P, float Ta_corr, float warn, float err)
+{
+ TempMgrGuard temp_mgr_guard;
+ if(!isnan(C) && C > 0) temp_model::data.C = C;
+ if(!isnan(P) && P > 0) temp_model::data.P = P;
+ if(!isnan(Ta_corr)) temp_model::data.Ta_corr = Ta_corr;
+ if(!isnan(err) && err > 0) temp_model::data.err = err;
+ if(!isnan(warn) && warn > 0) temp_model::data.warn = warn;
+
+ // ensure warn <= err
+ if (temp_model::data.warn > temp_model::data.err)
+ temp_model::data.warn = temp_model::data.err;
+
+ temp_model::setup();
+}
+
+void temp_model_set_resistance(uint8_t index, float R)
+{
+ if(index >= TEMP_MODEL_R_SIZE || R <= 0)
+ return;
+
+ TempMgrGuard temp_mgr_guard;
+ temp_model::data.R[index] = R;
+ temp_model::setup();
+}
+
+void temp_model_report_settings()
+{
+ printf_P(PSTR("%STemperature Model settings:\n"
+ "%S M310 P%.2f C%.2f S%u E%.2f W%.2f T%.2f\n"),
+ echomagic, echomagic, (double)temp_model::data.P, (double)temp_model::data.C, (unsigned)temp_model::enabled,
+ (double)temp_model::data.err, (double)temp_model::data.warn, (double)temp_model::data.Ta_corr);
+ for(uint8_t i = 0; i != TEMP_MODEL_R_SIZE; ++i)
+ printf_P(PSTR("%S M310 I%u R%.2f\n"), echomagic, (unsigned)i, (double)temp_model::data.R[i]);
+}
+
+void temp_model_autotune(float temp)
+{
+ // TODO
+}
+
+#ifdef TEMP_MODEL_DEBUG
void temp_model_log_enable(bool enable)
{
if(enable) {
TempMgrGuard temp_mgr_guard;
- temp_model_log_buf.entry.stamp = _millis();
+ temp_model::log_buf.entry.stamp = _millis();
}
- temp_model_log_buf.enabled = enable;
+ temp_model::log_buf.enabled = enable;
}
#endif
#endif
diff --git a/Firmware/temperature.h b/Firmware/temperature.h
index 558c1fe2..ad170ecf 100755
--- a/Firmware/temperature.h
+++ b/Firmware/temperature.h
@@ -228,9 +228,18 @@ FORCE_INLINE void autotempShutdown(){
}
void PID_autotune(float temp, int extruder, int ncycles);
-#ifdef TEMP_MODEL_LOGGING
+
+#ifdef TEMP_MODEL
+void temp_model_set_enabled(bool enabled);
+void temp_model_set_params(float C = NAN, float P = NAN, float Ta_corr = NAN, float warn = NAN, float err = NAN);
+void temp_model_set_resistance(uint8_t index, float R);
+void temp_model_report_settings();
+void temp_model_autotune(float temp = NAN);
+
+#ifdef TEMP_MODEL_DEBUG
void temp_model_log_enable(bool enable);
#endif
+#endif
#ifdef FAN_SOFT_PWM
extern unsigned char fanSpeedSoftPwm;
diff --git a/Firmware/variants/1_75mm_MK3S-EINSy10a-E3Dv6full.h b/Firmware/variants/1_75mm_MK3S-EINSy10a-E3Dv6full.h
index 205e43f0..574689cc 100644
--- a/Firmware/variants/1_75mm_MK3S-EINSy10a-E3Dv6full.h
+++ b/Firmware/variants/1_75mm_MK3S-EINSy10a-E3Dv6full.h
@@ -418,9 +418,33 @@
#define TEMP_RUNAWAY_EXTRUDER_TIMEOUT 45
// model-based temperature check
-#define TEMP_MODEL_CHECK 1
-#define TEMP_MODEL_CHECK_WARN_ONLY 1
-#define TEMP_MODEL_LOGGING 1
+#define TEMP_MODEL 1 // enable model-based temperature checks
+#define TEMP_MODEL_DEBUG 1 // extended runtime logging
+
+#define TEMP_MODEL_P 38. // heater power (W)
+
+#define TEMP_MODEL_C 11. // initial guess for heatblock capacitance (J/K)
+#define TEMP_MODEL_Cl 5 // C estimation lower limit
+#define TEMP_MODEL_Ch 20 // C estimation upper limit
+#define TEMP_MODEL_C_thr 0.01 // C estimation iteration threshold
+#define TEMP_MODEL_C_itr 30 // C estimation iteration limit
+
+#define TEMP_MODEL_R 25 // initial guess for heatblock resistance (K/W)
+#define TEMP_MODEL_Rl 5 // R estimation lower limit
+#define TEMP_MODEL_Rh 50 // R estimation upper limit
+#define TEMP_MODEL_R_thr 0.01 // R estimation iteration threshold
+#define TEMP_MODEL_R_itr 30 // R estimation iteration limit
+
+#define TEMP_MODEL_Rf_D -15 // initial guess for resistance change with full-power fan
+#define TEMP_MODEL_Ta_corr -7 // Default ambient temperature correction
+#define TEMP_MODEL_LAG 2.1 // Temperature transport delay (s)
+
+#define TEMP_MODEL_W 1.2 // Default pre-warning threshold (K/s)
+#define TEMP_MODEL_E 1.74 // Default error threshold (K/s)
+
+#define TEMP_MODEL_CAL_Th 230 // Default calibration working temperature (C)
+#define TEMP_MODEL_CAL_Tl 50 // Default calibration cooling temperature (C)
+
/*------------------------------------
MOTOR CURRENT SETTINGS