From 28f44a3c4919a5211bec888483b375d6adf5543d Mon Sep 17 00:00:00 2001 From: AnHardt Date: Tue, 2 Jun 2015 13:29:40 +0200 Subject: [PATCH] A different heater check (V3) Based on an idea by @nophead: * If a heater is full on the temperature shall not decrease; * If a heater is full off the temperature shall not rise; The test is made after a delay to give the heater time to react and only when above a settable ambient temperature. A test(by @AnHardt)for sudden temperature jumps is integrated. If thermal systems are inert. * If a temperature is jumping something is wrong. All this is accomplished by noise on the thermometers Some more explanations in `Configuration_adv.h` A proposal for a setup-guide in the next comment. --- Marlin/Conditionals.h | 6 ++ Marlin/Configuration_adv.h | 34 ++++++++ Marlin/Marlin_main.cpp | 4 +- Marlin/language.h | 2 +- Marlin/language_en.h | 3 + Marlin/temperature.cpp | 157 ++++++++++++++++++++++++++++++++++++- Marlin/temperature.h | 4 + Marlin/thermistortables.h | 1 + 8 files changed, 206 insertions(+), 5 deletions(-) diff --git a/Marlin/Conditionals.h b/Marlin/Conditionals.h index 59a599bc846d..1fcae534edf7 100644 --- a/Marlin/Conditionals.h +++ b/Marlin/Conditionals.h @@ -500,5 +500,11 @@ #define WRITE_FAN(v) WRITE(FAN_PIN, v) #endif + #if HAS_TEMP_BED + #define FIRST_HEATER -1 + #else + #define FIRST_HEATER 0 + #endif + #endif //CONFIGURATION_LCD #endif //CONDITIONALS_H diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index 8a05298d59a9..09fbc99f5bf8 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -36,7 +36,41 @@ #define THERMAL_PROTECTION_BED_HYSTERESIS 2 // Degrees Celsius #endif +/** + * An alternate algorithm to test if a heater works as expected. + * If a heater is full on its temperature should not drop. + * If a heater is full off its temperature should not rise. + * In a first step we detect if a temperature is rising or failing. + * This is accomplished by noise on the thermometers. Therefore we oversample the raw measurements an other 16 times. + * Than we test if the difference between the average and the momentary temperature is bigger than TEMP_RAW_NOISE. + * Than the sign of difference tells us the tendency. When we have more than TEMP_CONSEC_COUNT consecutive readings in one + * direction we report rising or falling, else constant. + * In a second step we test if a heater is full on, full of, or somewhere in the PWM area and test if the + * temperature changes as expected. This is accomplished by temperature overshoots. A temperature can rise + * fuhrer when a heater is turned off. The longest time between switching a heater off and the change to failing + * temperatures we call MAX_TEMP_OVERSHOOT_TIME. + * When a heater is off, but the surrounding temperature is higher or equal to the heaters temperature, the temperature + * can not fall. Therefore we do not test for this below MAX_AMBIENT_TEMPERATURE. + * HEATER_STATE_DEBUG produces some output on the serial line to find the right parameters. + * + * Temperatures are inert. If the value of any thermometer jumps, there is something wrong with it. + * Reasons can be: shorted wires, broken wires, leaking water-cooling, ... + * but also: electronic noise, ... + * MAX THERMO_JUMP_AMOUNT is the maximum allowed temperature difference between two measurements of the raw temperatures, (an abstract number). + * The fastest expected change is about (full range of the ADC) / minute / (temp measurements / second). + * This is normally well below the noise. So we have to adjust for the noise. + * If you get 'unreasoned' "error: Thermal jump" messages increase the MAX_THERMO_JUMP_AMOUNT value. 30 is a good value to start. 0 disables. + * + * Set MAX_TEMP_OVERSHOOT_TIME to 0 to deactivate all this tests. 30 is a good value to start. + */ +#define MAX_TEMP_OVERSHOOT_TIME 0 +#define TEMP_RAW_NOISE 6 +#define TEMP_CONSEC_COUNT 6 +#define MAX_AMBIENT_TEMPERATURE 50 +#define MAX_THERMO_JUMP_AMOUNT 30 +#define HEATER_STATE_DEBUG #ifdef PIDTEMP + // this adds an experimental additional term to the heating power, proportional to the extrusion speed. // if Kc is chosen well, the additional required power due to increased melting should be compensated. #define PID_ADD_EXTRUSION_RATE diff --git a/Marlin/Marlin_main.cpp b/Marlin/Marlin_main.cpp index ec8072de777b..55b5759210bd 100644 --- a/Marlin/Marlin_main.cpp +++ b/Marlin/Marlin_main.cpp @@ -3414,7 +3414,7 @@ inline void gcode_M105() { SERIAL_PROTOCOLPGM(" ADC B:"); SERIAL_PROTOCOL_F(degBed(),1); SERIAL_PROTOCOLPGM("C->"); - SERIAL_PROTOCOL_F(rawBedTemp()/OVERSAMPLENR,0); + SERIAL_PROTOCOL_F(rawBedTemp() >> OVESRAMPLESHIFT,0); #endif for (int8_t cur_extruder = 0; cur_extruder < EXTRUDERS; ++cur_extruder) { SERIAL_PROTOCOLPGM(" T"); @@ -3422,7 +3422,7 @@ inline void gcode_M105() { SERIAL_PROTOCOLCHAR(':'); SERIAL_PROTOCOL_F(degHotend(cur_extruder),1); SERIAL_PROTOCOLPGM("C->"); - SERIAL_PROTOCOL_F(rawHotendTemp(cur_extruder)/OVERSAMPLENR,0); + SERIAL_PROTOCOL_F(rawHotendTemp(cur_extruder) >> OVESRAMPLESHIFT,0); } #endif diff --git a/Marlin/language.h b/Marlin/language.h index 66f4144d48cb..48502c995877 100644 --- a/Marlin/language.h +++ b/Marlin/language.h @@ -214,7 +214,7 @@ #define MSG_T_THERMAL_RUNAWAY "Thermal Runaway" #define MSG_T_MAXTEMP "MAXTEMP triggered" #define MSG_T_MINTEMP "MINTEMP triggered" - +#define MSG_T_JUMP "Thermal jump" // LCD Menu Messages diff --git a/Marlin/language_en.h b/Marlin/language_en.h index f2ca91beee9c..3d8e09f36430 100644 --- a/Marlin/language_en.h +++ b/Marlin/language_en.h @@ -402,6 +402,9 @@ #ifndef MSG_THERMAL_RUNAWAY #define MSG_THERMAL_RUNAWAY "THERMAL RUNAWAY" #endif +#ifndef MSG_ERR_THERMAL_JUMP +#define MSG_ERR_THERMAL_JUMP "Err: THER. JUMP" +#endif #ifndef MSG_ERR_MAXTEMP #define MSG_ERR_MAXTEMP "Err: MAXTEMP" #endif diff --git a/Marlin/temperature.cpp b/Marlin/temperature.cpp index d55ebffd9f89..1af4f79f3506 100644 --- a/Marlin/temperature.cpp +++ b/Marlin/temperature.cpp @@ -18,6 +18,8 @@ along with this program. If not, see . */ +#define __STDC_LIMIT_MACROS +#include #include "Marlin.h" #include "ultralcd.h" #include "temperature.h" @@ -602,6 +604,15 @@ void manage_heater() { if (ct < max(HEATER_0_MINTEMP, 0.01)) min_temp_error(0); #endif + + #if MAX_TEMP_OVERSHOOT_TIME > 0 + for (int8_t h = FIRST_HEATER; h < EXTRUDERS; h++) { + //SERIAL_PROTOCOLPGM("Heater: "); SERIAL_PROTOCOL(h+1); SERIAL_PROTOCOLPGM(" H_State: "); SERIAL_PROTOCOLLN(heater_state(h)); + if (heater_state(h) && (MAX_AMBIENT_TEMPERATURE < ((h<0) ? current_temperature_bed : current_temperature[h]))) + _temp_error(h, PSTR(MSG_T_THERMAL_RUNAWAY), PSTR(MSG_THERMAL_RUNAWAY)); + } + #endif + #if defined(THERMAL_PROTECTION_HOTENDS) || !defined(PIDTEMPBED) || HAS_AUTO_FAN millis_t ms = millis(); #endif @@ -748,7 +759,7 @@ static float analog2temp(int raw, uint8_t e) { return celsius; } - return ((raw * ((5.0 * 100.0) / 1024.0) / OVERSAMPLENR) * TEMP_SENSOR_AD595_GAIN) + TEMP_SENSOR_AD595_OFFSET; + return (((raw >> OVESRAMPLESHIFT) * 5.0 * 100.0 / 1024.0) * TEMP_SENSOR_AD595_GAIN) + TEMP_SENSOR_AD595_OFFSET; } // Derived from RepRap FiveD extruder::getTemperature() @@ -773,7 +784,7 @@ static float analog2tempBed(int raw) { return celsius; #elif defined BED_USES_AD595 - return ((raw * ((5.0 * 100.0) / 1024.0) / OVERSAMPLENR) * TEMP_SENSOR_AD595_GAIN) + TEMP_SENSOR_AD595_OFFSET; + return (((raw >> OVESRAMPLESHIFT) * 5.0 * 100.0 / 1024.0) * TEMP_SENSOR_AD595_GAIN) + TEMP_SENSOR_AD595_OFFSET; #else return 0; #endif @@ -1607,3 +1618,145 @@ ISR(TIMER0_COMPB_vect) { float scalePID_d(float d) { return d / PID_dT; } float unscalePID_d(float d) { return d * PID_dT; } #endif //PIDTEMP + +#if MAX_TEMP_OVERSHOOT_TIME > 0 + int8_t temperature_state(int8_t hh) { + static int average_temp_raw[EXTRUDERS + 1] = { 0 }; + static int last_temp_raw[EXTRUDERS + 1] = { 0 }; + static int16_t counter[5] = { INT16_MAX, INT16_MAX, INT16_MAX, INT16_MAX, INT16_MAX }; //init INT16_MAX + + int8_t h = hh + 1; // index correctur + // get current_temperature_raw[hh] and divide by OVERSAMPLENR + #ifdef HEATER_0_USES_MAX6675 + int t = ((h) ? ((hh) ? current_temperature_raw[hh] >> OVESRAMPLESHIFT: current_temperature_raw[hh]) : current_temperature_bed_raw >> OVESRAMPLESHIFT); + #else + int t = ((h) ? current_temperature_raw[hh]: current_temperature_bed_raw) >> OVESRAMPLESHIFT; + #endif + + // floating average over 16 values (MAX6675 is not oversampled until now. Thermistors are still a bit nervous) + int average = (average_temp_raw[h] >> OVESRAMPLESHIFT); // /16 + int dt = t - average; + average_temp_raw[h] += dt; + + #ifdef HEATER_STATE_DEBUG + SERIAL_PROTOCOLPGM("Temperature: "); SERIAL_PROTOCOL(h); SERIAL_PROTOCOLPGM(" count: "); SERIAL_PROTOCOL((int)counter[h]); SERIAL_PROTOCOLPGM(" Temp.: "); SERIAL_PROTOCOL((int)t); SERIAL_PROTOCOLPGM(" Temp.A.:"); SERIAL_PROTOCOL(average); + #endif + + // init + if (counter[h] == INT16_MAX) { + average_temp_raw[h] = (t << OVESRAMPLESHIFT); // *16 + last_temp_raw[h] = t; + counter[h] = 0; + return 0; + } + + #if MAX_THERMO_JUMP_AMOUNT > 0 + if (abs(last_temp_raw[h] - t) > MAX_THERMO_JUMP_AMOUNT) _temp_error(hh, PSTR(MSG_T_JUMP), PSTR(MSG_ERR_THERMAL_JUMP)); + else last_temp_raw[h] = t; + #endif + + if (abs(dt) < TEMP_RAW_NOISE) { counter[h] = 0; return 0;} // constant + switch (h) { + case 0: { + #if HAS_TEMP_BED + counter[h] += (dt GEBED 0) ? 1 : -1; + #endif + break; + } + case 1: { + #if HAS_TEMP_0 && !defined(HEATER_0_USES_MAX6675) + counter[h] += (dt GE0 0) ? 1 : -1; + #endif + #ifdef HEATER_0_USES_MAX6675 + counter[h] += (dt >= 0) ? 1 : -1; + #endif + break; + } + case 2: { + #if HAS_TEMP_1 + counter[h] += (dt GE1 0) ? 1 : -1; + #endif + break; + } + case 3: { + #if HAS_TEMP_2 + counter[h] += (dt GE2 0) ? 1 : -1; + #endif + break; + } + case 4: { + #if HAS_TEMP_3 + counter[h] += (dt GE3 0) ? 1 : -1; + #endif + break; + } + break; + } + // when we have n consecutive rising or falling values we are sure enough to return that + if (abs(counter[h]) >= TEMP_CONSEC_COUNT) return (counter[h] < 0) ? -1 : 1; + // else return constant + return 0; + } + + int8_t heater_state(int8_t hh) { + enum hstate_t { ON_S, FULL_ON_S, OFF_S }; + static hstate_t hstate[5] = { ON_S }; + static int8_t tstate[EXTRUDERS + 1] = { 0 }; + static millis_t timerc[EXTRUDERS + 1] = { 0 }; + static float initial_temp[EXTRUDERS + 1] = { 0.0 }; + + uint8_t h = hh + 1; + int8_t current_tstate = temperature_state(hh); + + #ifdef HEATER_STATE_DEBUG + SERIAL_PROTOCOLPGM(" Heater: "); SERIAL_PROTOCOL((int)h); SERIAL_PROTOCOLPGM(" T_State: "); SERIAL_PROTOCOL((int)current_tstate); SERIAL_PROTOCOLPGM(" Time:"); SERIAL_PROTOCOL(timerc[h]-millis()); SERIAL_PROTOCOLPGM(" H_State:"); SERIAL_PROTOCOLLN((int)hstate[h]); + #endif + + if (((h) ? soft_pwm[hh] : soft_pwm_bed) == ((h) ? PID_MAX >> 1: MAX_BED_POWER >> 1)) {// FULL_ON + if (hstate[h] != FULL_ON_S) { + hstate[h] = FULL_ON_S; + tstate[h] = current_tstate; + timerc[h] = millis() + MAX_TEMP_OVERSHOOT_TIME * 1000UL; + return 0; + } + else { + if (current_tstate != tstate[h]) { + if (current_tstate == 1) return 0; + if (current_tstate == -1) _temp_error(hh, PSTR(MSG_T_HEATING_FAILED), PSTR(MSG_HEATING_FAILED_LCD)); + } + if (millis() > timerc[h]) {// timeout + hstate[h] = ON_S; // retest + if (current_tstate != -1) return 0; + else _temp_error(hh, PSTR(MSG_T_HEATING_FAILED), PSTR(MSG_HEATING_FAILED_LCD)); + } + else + return 0; + } + } + else if (((h) ? soft_pwm[hh] : soft_pwm_bed) == 0) // FULL_OFF + if (hstate[h] != OFF_S) { + hstate[h] = OFF_S; + tstate[h] = current_tstate; + timerc[h] = millis() + MAX_TEMP_OVERSHOOT_TIME * 1000UL; + return 0; + } + else { + if (current_tstate != tstate[h]) { + if (current_tstate == 1) 1; // test for MAX_AMBIENT_TEMPERATURE // got false positives when touching the sensor. + else return 0; + } + if (millis() > timerc[h]) {// timeout + hstate[h] = ON_S; // retest + if (current_tstate != 1) return 0; + else return 1; // test for MAX_AMBIENT_TEMPERATURE + } + else + return 0; + } + else { + hstate[h] = ON_S; // on, but not full power so can't say anything about the temperature change + return 0; + } + return 0; + } +#endif // MAX_TEMP_OVERSHOOT_TIME > 0 \ No newline at end of file diff --git a/Marlin/temperature.h b/Marlin/temperature.h index 6ab35d52e9c3..eea590741ffe 100644 --- a/Marlin/temperature.h +++ b/Marlin/temperature.h @@ -146,6 +146,10 @@ void PID_autotune(float temp, int extruder, int ncycles); void setExtruderAutoFanState(int pin, bool state); void checkExtruderAutoFans(); +#if MAX_TEMP_OVERSHOOT_TIME > 0 + int8_t heater_state(int8_t); +#endif + FORCE_INLINE void autotempShutdown() { #ifdef AUTOTEMP if (autotemp_enabled) { diff --git a/Marlin/thermistortables.h b/Marlin/thermistortables.h index 61092f005e36..81e4bf24aea1 100644 --- a/Marlin/thermistortables.h +++ b/Marlin/thermistortables.h @@ -4,6 +4,7 @@ #include "Marlin.h" #define OVERSAMPLENR 16 +#define OVESRAMPLESHIFT 4 // x >> OVESRAMPLESHIFT == x / OVERSAMPLENR #if (THERMISTORHEATER_0 == 1) || (THERMISTORHEATER_1 == 1) || (THERMISTORHEATER_2 == 1) || (THERMISTORHEATER_3 == 1) || (THERMISTORBED == 1) //100k bed thermistor