Rewrite/modularize the model checker
- Allow all parameters to be changed at runtime through M310 - Move the model prototypes into a separate temp_model.h header - Allow the checked to be enabled/disabled at runtime - Introduce a warning threshold
This commit is contained in:
parent
fabf511b97
commit
b0b2ff5f9e
@ -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 <a href="https://reprap.org/wiki/G-code#M400:_Wait_for_current_moves_to_finish">M400: Wait for current moves to finish</a>
|
||||
@ -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());
|
||||
|
110
Firmware/temp_model.h
Normal file
110
Firmware/temp_model.h
Normal file
@ -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
|
@ -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<<OCIE5A))
|
||||
#define ENABLE_TEMP_MGR_INTERRUPT() TIMSK5 |= (1<<OCIE5A)
|
||||
#define DISABLE_TEMP_MGR_INTERRUPT() TIMSK5 &= ~(1<<OCIE5A)
|
||||
|
||||
#ifdef TEMP_MODEL
|
||||
// temperature model interface
|
||||
#include "temp_model.h"
|
||||
#endif
|
||||
|
||||
//===========================================================================
|
||||
//=============================public variables============================
|
||||
//===========================================================================
|
||||
@ -454,7 +467,9 @@ enum class TempErrorType : uint8_t
|
||||
min,
|
||||
preheat,
|
||||
runaway,
|
||||
#ifdef TEMP_MODEL
|
||||
model,
|
||||
#endif
|
||||
};
|
||||
|
||||
// error state (updated via set_temp_error from isr context)
|
||||
@ -477,14 +492,8 @@ volatile static union
|
||||
void set_temp_error(TempErrorSource source, uint8_t index, TempErrorType type)
|
||||
{
|
||||
// keep disabling heaters and keep fans on as long as the condition is asserted
|
||||
#ifdef TEMP_MODEL_CHECK_WARN_ONLY
|
||||
if(type != TempErrorType::model) {
|
||||
#endif
|
||||
disable_heater();
|
||||
hotendFanSetFullSpeed();
|
||||
#ifdef TEMP_MODEL_CHECK_WARN_ONLY
|
||||
}
|
||||
#endif
|
||||
|
||||
// set the initial error source to the highest priority error
|
||||
if(!temp_error_state.error || (uint8_t)type < temp_error_state.type) {
|
||||
@ -500,11 +509,6 @@ void set_temp_error(TempErrorSource source, uint8_t index, TempErrorType type)
|
||||
|
||||
void handle_temp_error();
|
||||
|
||||
#ifdef TEMP_MODEL_LOGGING
|
||||
static void temp_model_log_usr();
|
||||
static void temp_model_log_isr();
|
||||
#endif
|
||||
|
||||
void manage_heater()
|
||||
{
|
||||
#ifdef WATCHDOG
|
||||
@ -519,6 +523,12 @@ void manage_heater()
|
||||
// syncronize temperatures with isr
|
||||
updateTemperatures();
|
||||
|
||||
#ifdef TEMP_MODEL
|
||||
// handle model warnings first, so not to override the error handler
|
||||
if(temp_model::warning_state.warning)
|
||||
temp_model::handle_warning();
|
||||
#endif
|
||||
|
||||
// handle temperature errors
|
||||
if(temp_error_state.v)
|
||||
handle_temp_error();
|
||||
@ -526,8 +536,8 @@ void manage_heater()
|
||||
// periodically check fans
|
||||
checkFans();
|
||||
|
||||
#ifdef TEMP_MODEL_LOGGING
|
||||
temp_model_log_usr();
|
||||
#ifdef TEMP_MODEL_DEBUG
|
||||
temp_model::log_usr();
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -1765,26 +1775,19 @@ void handle_temp_error()
|
||||
break;
|
||||
}
|
||||
break;
|
||||
#ifdef TEMP_MODEL
|
||||
case TempErrorType::model:
|
||||
#ifdef TEMP_MODEL_CHECK
|
||||
static bool is_new = true;
|
||||
static bool beep_on = false;
|
||||
printf_P(PSTR("TM: err:%u ass:%u\n"), (unsigned)temp_error_state.error,
|
||||
(unsigned)temp_error_state.assert);
|
||||
if(is_new) {
|
||||
beep_on = true;
|
||||
is_new = false;
|
||||
}
|
||||
WRITE(BEEPER, beep_on);
|
||||
beep_on = !beep_on;
|
||||
if(!temp_error_state.assert) {
|
||||
printf_P(PSTR("TM: assertion cleared - resetting\n"));
|
||||
if(temp_error_state.assert) {
|
||||
// TODO: do something meaningful
|
||||
SERIAL_ECHOLNPGM("TM: error triggered!");
|
||||
WRITE(BEEPER, HIGH);
|
||||
} else {
|
||||
temp_error_state.v = 0;
|
||||
WRITE(BEEPER, LOW);
|
||||
is_new = true;
|
||||
SERIAL_ECHOLNPGM("TM: error cleared");
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@ -1841,13 +1844,6 @@ bool has_temperature_compensation()
|
||||
#endif //PINDA_THERMISTOR
|
||||
|
||||
|
||||
#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<<OCIE5A))
|
||||
#define ENABLE_TEMP_MGR_INTERRUPT() TIMSK5 |= (1<<OCIE5A)
|
||||
#define DISABLE_TEMP_MGR_INTERRUPT() TIMSK5 &= ~(1<<OCIE5A)
|
||||
|
||||
// RAII helper class to run a code block with temp_mgr_isr disabled
|
||||
class TempMgrGuard
|
||||
{
|
||||
@ -1895,6 +1891,10 @@ void temp_mgr_init()
|
||||
TCNT5 = 0;
|
||||
OCR5A = TIMER5_OCRA_OVF;
|
||||
|
||||
#ifdef TEMP_MODEL
|
||||
temp_model::init();
|
||||
#endif
|
||||
|
||||
// clear pending interrupts, enable COMPA
|
||||
TIFR5 |= (1<<OCF5A);
|
||||
ENABLE_TEMP_MGR_INTERRUPT();
|
||||
@ -2192,9 +2192,6 @@ static void check_temp_runaway()
|
||||
}
|
||||
|
||||
static void check_temp_raw();
|
||||
#ifdef TEMP_MODEL_CHECK
|
||||
static void check_temp_model();
|
||||
#endif
|
||||
|
||||
static void temp_mgr_isr()
|
||||
{
|
||||
@ -2205,12 +2202,11 @@ static void temp_mgr_isr()
|
||||
temp_error_state.assert = false;
|
||||
check_temp_raw(); // check min/max temp using raw values
|
||||
check_temp_runaway(); // classic temperature hysteresis check
|
||||
#ifdef TEMP_MODEL_CHECK
|
||||
check_temp_model(); // model-based heater check
|
||||
#ifdef TEMP_MODEL
|
||||
temp_model::check(); // model-based heater check
|
||||
#ifdef TEMP_MODEL_DEBUG
|
||||
temp_model::log_isr();
|
||||
#endif
|
||||
|
||||
#ifdef TEMP_MODEL_LOGGING
|
||||
temp_model_log_isr();
|
||||
#endif
|
||||
|
||||
// PID regulation
|
||||
@ -2332,147 +2328,266 @@ static void check_temp_raw()
|
||||
check_min_temp_raw();
|
||||
}
|
||||
|
||||
#ifdef TEMP_MODEL_CHECK
|
||||
#ifndef TEMP_MODEL_VARS
|
||||
static const float TM_P = 38.; // heater power (W)
|
||||
static const float TM_C = 11.9; // heatblock capacitance (J/K)
|
||||
static const float TM_R = 27.; // heatblock resistance
|
||||
static const float TM_Rf = -20.; // full-power fan resistance change
|
||||
static const float TM_aC = -5; // ambient temperature correction (K)
|
||||
#endif
|
||||
#ifdef TEMP_MODEL
|
||||
namespace temp_model {
|
||||
|
||||
static const float TM_dTl = 2.1; // temperature transport delay (s)
|
||||
static const uint8_t TM_dTs = (TM_dTl / TEMP_MGR_INTV + 0.5); // temperature transport delay (samples)
|
||||
|
||||
static const float TM_fS = 0.065; // simulation (1st-order IIR factor)
|
||||
static const float TM_fE = 0.05; // error (1st-order IIR factor)
|
||||
|
||||
static const float TM_err = 1.; // error threshold (K/s)
|
||||
static const float TM_err_s = (TM_err * TEMP_MGR_INTV); // error threshold (per sample)
|
||||
|
||||
static float TM_dT_buf[TM_dTs]; // transport delay buffer
|
||||
static uint8_t TM_dT_idx = 0; // transport delay buffer index
|
||||
static float TM_dT_err = 0; // last error
|
||||
static float TM_T = 0; // last temperature
|
||||
|
||||
#ifndef MAX
|
||||
#define MAX(A, B) (A >= 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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user