From 2b1fbe38db5fec94242d700e8ea412f2a41ae668 Mon Sep 17 00:00:00 2001 From: Michael Moon Date: Thu, 11 Nov 2010 00:51:25 +1100 Subject: [PATCH] preliminary heater sanity check code, disabled by default until tested --- config.h.dist | 11 +++-- heater.c | 110 ++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 93 insertions(+), 28 deletions(-) diff --git a/config.h.dist b/config.h.dist index a257d21..ffaf896 100644 --- a/config.h.dist +++ b/config.h.dist @@ -182,19 +182,21 @@ \***************************************************************************/ /* - TEMP_HYSTERESIS: actual temperature must be target +/- hysteresis before target temperature can be achieved + TEMP_HYSTERESIS: actual temperature must be target +/- hysteresis before target temperature can be achieved. + NOTE: format is 30.2 fixed point, so value of 20 actually means +/- 5 degrees + TEMP_RESIDENCY_TIME: actual temperature must be close to target for this long before target is achieved temperature is "achieved" for purposes of M109 and friends when actual temperature is within [hysteresis] of target for [residency] seconds */ -#define TEMP_HYSTERESIS 5 +#define TEMP_HYSTERESIS 20 #define TEMP_RESIDENCY_TIME 60 // which temperature sensors are you using? (intercom is the gen3-style separate extruder board) // #define TEMP_MAX6675 // #define TEMP_THERMISTOR // #define TEMP_AD595 -// #define TEMP_INTERCOM +#define TEMP_INTERCOM // if you selected thermistor or AD595, what pin is it on? (this value only used to fill ANALOG_MASK for you) #define TEMP_PIN_CHANNEL AIO0_PIN @@ -245,6 +247,9 @@ struct { // number of heaters- for GEN3, set to zero as extruder manages the heater by itself #define NUM_HEATERS 0 +// check if heater responds to changes in target temperature, disable and spit errors if not +// #define HEATER_SANITY_CHECK + /***************************************************************************\ * * * Fill in the following struct according to your hardware * diff --git a/heater.c b/heater.c index 525a58e..e71ca9b 100644 --- a/heater.c +++ b/heater.c @@ -1,14 +1,12 @@ #include "heater.h" +#include #include #include #include "arduino.h" -// #include "timer.h" #include "debug.h" -#ifdef DEBUG - #include "sersendf.h" -#endif +#include "sersendf.h" #define HEATER_C #include "config.h" @@ -21,21 +19,22 @@ struct { int16_t i_limit; } heaters_pid[NUM_HEATERS]; -// this struct holds the runtime heater data- PID counters and such +// this struct holds the runtime heater data- PID integrator history, temperature history, sanity checker struct { - int16_t heater_p; int16_t heater_i; - int16_t heater_d; - - uint8_t pid_output; - + uint16_t temp_history[TH_COUNT]; uint8_t temp_history_pointer; + + #ifdef HEATER_SANITY_CHECK + uint16_t sanity_counter; + uint16_t sane_temperature; + #endif } heaters_runtime[NUM_HEATERS]; #define DEFAULT_P 8192 #define DEFAULT_I 512 -#define DEFAULT_D -24576 +#define DEFAULT_D 24576 #define DEFAULT_I_LIMIT 384 // this lives in the eeprom so we can save our PID settings for each heater @@ -55,9 +54,10 @@ void heater_init() { for (i = 0; i < NUM_HEATERS; i++) { *(heaters[i].heater_port) &= ~MASK(heaters[i].heater_pin); // DDR is always 1 address below PORT. ugly code but saves ram and an extra field in heaters[] which will never be used anywhere but here - *((volatile uint8_t *) (heaters[i].heater_port - 1)) |= MASK(heaters[i].heater_pin); + *(heaters[i].heater_port - 1) |= MASK(heaters[i].heater_pin); if (heaters[i].heater_pwm) { *heaters[i].heater_pwm = 0; + // this is somewhat ugly too, but switch() won't accept pointers for reasons unknown switch((uint16_t) heaters[i].heater_pwm) { case (uint16_t) &OCR0A: TCCR0A |= MASK(COM0A1); @@ -73,10 +73,11 @@ void heater_init() { break; } } - } + + // 0 is a "sane" temperature when we're trying to cool down + heaters_runtime[i].sane_temperature = 0; // read factors from eeprom - for (i = 0; i < NUM_HEATERS; i++) { heaters_pid[i].p_factor = eeprom_read_dword((uint32_t *) &EE_factors[i].EE_p_factor); heaters_pid[i].i_factor = eeprom_read_dword((uint32_t *) &EE_factors[i].EE_i_factor); heaters_pid[i].d_factor = eeprom_read_dword((uint32_t *) &EE_factors[i].EE_d_factor); @@ -103,15 +104,18 @@ void heater_save_settings() { } void heater_tick(uint8_t h, uint16_t current_temp, uint16_t target_temp) { - // now for heater stuff - int16_t t_error = target_temp - current_temp; + int16_t heater_p; + int16_t heater_d; + uint8_t pid_output; + + int16_t t_error = target_temp - current_temp; heaters_runtime[h].temp_history[heaters_runtime[h].temp_history_pointer++] = current_temp; heaters_runtime[h].temp_history_pointer &= (TH_COUNT - 1); // PID stuff // proportional - heaters_runtime[h].heater_p = t_error; + heater_p = t_error; // integral heaters_runtime[h].heater_i += t_error; @@ -123,31 +127,87 @@ void heater_tick(uint8_t h, uint16_t current_temp, uint16_t target_temp) { // derivative // note: D follows temp rather than error so there's no large derivative when the target changes - heaters_runtime[h].heater_d = current_temp - heaters_runtime[h].temp_history[heaters_runtime[h].temp_history_pointer]; + heater_d = heaters_runtime[h].temp_history[heaters_runtime[h].temp_history_pointer] - current_temp; // combine factors int32_t pid_output_intermed = ( ( - (((int32_t) heaters_runtime[h].heater_p) * heaters_pid[h].p_factor) + + (((int32_t) heater_p) * heaters_pid[h].p_factor) + (((int32_t) heaters_runtime[h].heater_i) * heaters_pid[h].i_factor) + - (((int32_t) heaters_runtime[h].heater_d) * heaters_pid[h].d_factor) + (((int32_t) heater_d) * heaters_pid[h].d_factor) ) / PID_SCALE ); // rebase and limit factors if (pid_output_intermed > 255) - heaters_runtime[h].pid_output = 255; + pid_output = 255; else if (pid_output_intermed < 0) - heaters_runtime[h].pid_output = 0; + pid_output = 0; else - heaters_runtime[h].pid_output = pid_output_intermed & 0xFF; + pid_output = pid_output_intermed & 0xFF; #ifdef DEBUG if (debug_flags & DEBUG_PID) - sersendf_P(PSTR("T{E:%d, P:%d * %ld = %ld / I:%d * %ld = %ld / D:%d * %ld = %ld # O: %ld = %u}\n"), t_error, heaters_runtime[h].heater_p, heaters_pid[h].p_factor, (int32_t) heaters_runtime[h].heater_p * heaters_pid[h].p_factor / PID_SCALE, heaters_runtime[h].heater_i, heaters_pid[h].i_factor, (int32_t) heaters_runtime[h].heater_i * heaters_pid[h].i_factor / PID_SCALE, heaters_runtime[h].heater_d, heaters_pid[h].d_factor, (int32_t) heaters_runtime[h].heater_d * heaters_pid[h].d_factor / PID_SCALE, pid_output_intermed, heaters_runtime[h].pid_output); + sersendf_P(PSTR("T{E:%d, P:%d * %ld = %ld / I:%d * %ld = %ld / D:%d * %ld = %ld # O: %ld = %u}\n"), t_error, heater_p, heaters_pid[h].p_factor, (int32_t) heater_p * heaters_pid[h].p_factor / PID_SCALE, heaters_runtime[h].heater_i, heaters_pid[h].i_factor, (int32_t) heaters_runtime[h].heater_i * heaters_pid[h].i_factor / PID_SCALE, heater_d, heaters_pid[h].d_factor, (int32_t) heater_d * heaters_pid[h].d_factor / PID_SCALE, pid_output_intermed, pid_output); #endif - heater_set(h, heaters_runtime[h].pid_output); + #ifdef HEATER_SANITY_CHECK + // check heater sanity + // implementation is a moving window with some slow-down to compensate for thermal mass + if (target_temp > (current_temp + TEMP_HYSTERESIS)) { + // heating + if (current_temp > heaters_runtime[h].sane_temperature) + // hotter than sane- good since we're heating unless too hot + heaters_runtime[h].sane_temperature = current_temp; + else { + if (heaters_runtime[h].sanity_counter < 40) + heaters_runtime[h].sanity_counter++; + else { + heaters_runtime[h].sanity_counter = 0; + // ratchet up expected temp + heaters_runtime[h].sane_temperature++; + } + } + // limit to target, so if we overshoot by too much for too long an error is flagged + if (heaters_runtime[h].sane_temperature > target_temp) + heaters_runtime[h].sane_temperature = target_temp; + } + else if (target_temp < (current_temp - TEMP_HYSTERESIS)) { + // cooling + if (current_temp < heaters_runtime[h].sane_temperature) + // cooler than sane- good since we're cooling + heaters_runtime[h].sane_temperature = current_temp; + else { + if (heaters_runtime[h].sanity_counter < 125) + heaters_runtime[h].sanity_counter++; + else { + heaters_runtime[h].sanity_counter = 0; + // ratchet down expected temp + heaters_runtime[h].sane_temperature--; + } + } + // if we're at or below 60 celsius, don't freak out if we can't drop any more. + if (current_temp <= 240) + heaters_runtime[h].sane_temperature = current_temp; + // limit to target, so if we don't cool down for too long an error is flagged + else if (heaters_runtime[h].sane_temperature < target_temp) + heaters_runtime[h].sane_temperature = target_temp; + } + // we're within HYSTERESIS of our target + else { + heaters_runtime[h].sane_temperature = current_temp; + heaters_runtime[h].sanity_counter = 0; + } + + // compare where we're at to where we should be + if (labs(current_temp - heaters_runtime[h].sane_temperature) > TEMP_HYSTERESIS) { + // no change, or change in wrong direction for a long time- heater is broken! + pid_output = 0; + sersendf_P(PSTR("!! heater %d broken- temp is %d.%dC, target is %d.%dC, didn't reach %d.%dC in %d0 milliseconds\n"), h, current_temp >> 2, (current_temp & 3) * 25, target_temp >> 2, (target_temp & 3) * 25, heaters_runtime[h].sane_temperature >> 2, (heaters_runtime[h].sane_temperature & 3) * 25, heaters_runtime[h].sanity_counter); + } + #endif + + heater_set(h, pid_output); } void heater_set(uint8_t index, uint8_t value) {