Firmware/Marlin/temperature.h
Rowan Meara 39cc36d3f1 [1.1.x] M303 thermal runaway protection (#8209)
* Added M303 thermal runaway protection

Currently, thermal runaway protection is not available during M303.
Therefore, if someone plugs the thermistors in incorrectly and goes to
autotune their printer, the printer temperature could runaway and damage
could occur.

* Replace removed line, clarifying its logic
2017-11-03 03:16:39 -05:00

579 lines
16 KiB
C++

/**
* Marlin 3D Printer Firmware
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/**
* temperature.h - temperature controller
*/
#ifndef TEMPERATURE_H
#define TEMPERATURE_H
#include "thermistortables.h"
#include "MarlinConfig.h"
#if ENABLED(PID_EXTRUSION_SCALING)
#include "stepper.h"
#endif
#ifndef SOFT_PWM_SCALE
#define SOFT_PWM_SCALE 0
#endif
#define HOTEND_LOOP() for (int8_t e = 0; e < HOTENDS; e++)
#if HOTENDS == 1
#define HOTEND_INDEX 0
#define EXTRUDER_IDX 0
#else
#define HOTEND_INDEX e
#define EXTRUDER_IDX active_extruder
#endif
/**
* States for ADC reading in the ISR
*/
enum ADCSensorState {
#if HAS_TEMP_0
PrepareTemp_0,
MeasureTemp_0,
#endif
#if HAS_TEMP_1
PrepareTemp_1,
MeasureTemp_1,
#endif
#if HAS_TEMP_2
PrepareTemp_2,
MeasureTemp_2,
#endif
#if HAS_TEMP_3
PrepareTemp_3,
MeasureTemp_3,
#endif
#if HAS_TEMP_4
PrepareTemp_4,
MeasureTemp_4,
#endif
#if HAS_TEMP_BED
PrepareTemp_BED,
MeasureTemp_BED,
#endif
#if ENABLED(FILAMENT_WIDTH_SENSOR)
Prepare_FILWIDTH,
Measure_FILWIDTH,
#endif
#if ENABLED(ADC_KEYPAD)
Prepare_ADC_KEY,
Measure_ADC_KEY,
#endif
SensorsReady, // Temperatures ready. Delay the next round of readings to let ADC pins settle.
StartupDelay // Startup, delay initial temp reading a tiny bit so the hardware can settle
};
// Minimum number of Temperature::ISR loops between sensor readings.
// Multiplied by 16 (OVERSAMPLENR) to obtain the total time to
// get all oversampled sensor readings
#define MIN_ADC_ISR_LOOPS 10
#define ACTUAL_ADC_SAMPLES max(int(MIN_ADC_ISR_LOOPS), int(SensorsReady))
#if !HAS_HEATER_BED
constexpr int16_t target_temperature_bed = 0;
#endif
class Temperature {
public:
static float current_temperature[HOTENDS],
current_temperature_bed;
static int16_t current_temperature_raw[HOTENDS],
target_temperature[HOTENDS],
current_temperature_bed_raw;
#if HAS_HEATER_BED
static int16_t target_temperature_bed;
#endif
static volatile bool in_temp_isr;
static uint8_t soft_pwm_amount[HOTENDS],
soft_pwm_amount_bed;
#if ENABLED(FAN_SOFT_PWM)
static uint8_t soft_pwm_amount_fan[FAN_COUNT],
soft_pwm_count_fan[FAN_COUNT];
#endif
#if ENABLED(PIDTEMP) || ENABLED(PIDTEMPBED)
#define PID_dT ((OVERSAMPLENR * float(ACTUAL_ADC_SAMPLES)) / (F_CPU / 64.0 / 256.0))
#endif
#if ENABLED(PIDTEMP)
#if ENABLED(PID_PARAMS_PER_HOTEND) && HOTENDS > 1
static float Kp[HOTENDS], Ki[HOTENDS], Kd[HOTENDS];
#if ENABLED(PID_EXTRUSION_SCALING)
static float Kc[HOTENDS];
#endif
#define PID_PARAM(param, h) Temperature::param[h]
#else
static float Kp, Ki, Kd;
#if ENABLED(PID_EXTRUSION_SCALING)
static float Kc;
#endif
#define PID_PARAM(param, h) Temperature::param
#endif // PID_PARAMS_PER_HOTEND
// Apply the scale factors to the PID values
#define scalePID_i(i) ( (i) * PID_dT )
#define unscalePID_i(i) ( (i) / PID_dT )
#define scalePID_d(d) ( (d) / PID_dT )
#define unscalePID_d(d) ( (d) * PID_dT )
#endif
#if ENABLED(PIDTEMPBED)
static float bedKp, bedKi, bedKd;
#endif
#if ENABLED(BABYSTEPPING)
static volatile int babystepsTodo[3];
#endif
#if WATCH_HOTENDS
static uint16_t watch_target_temp[HOTENDS];
static millis_t watch_heater_next_ms[HOTENDS];
#endif
#if WATCH_THE_BED
static uint16_t watch_target_bed_temp;
static millis_t watch_bed_next_ms;
#endif
#if ENABLED(PREVENT_COLD_EXTRUSION)
static bool allow_cold_extrude;
static int16_t extrude_min_temp;
static bool tooColdToExtrude(uint8_t e) {
#if HOTENDS == 1
UNUSED(e);
#endif
return allow_cold_extrude ? false : degHotend(HOTEND_INDEX) < extrude_min_temp;
}
#else
static bool tooColdToExtrude(uint8_t e) { UNUSED(e); return false; }
#endif
private:
#if ENABLED(TEMP_SENSOR_1_AS_REDUNDANT)
static uint16_t redundant_temperature_raw;
static float redundant_temperature;
#endif
static volatile bool temp_meas_ready;
#if ENABLED(PIDTEMP)
static float temp_iState[HOTENDS],
temp_dState[HOTENDS],
pTerm[HOTENDS],
iTerm[HOTENDS],
dTerm[HOTENDS];
#if ENABLED(PID_EXTRUSION_SCALING)
static float cTerm[HOTENDS];
static long last_e_position;
static long lpq[LPQ_MAX_LEN];
static int lpq_ptr;
#endif
static float pid_error[HOTENDS];
static bool pid_reset[HOTENDS];
#endif
#if ENABLED(PIDTEMPBED)
static float temp_iState_bed,
temp_dState_bed,
pTerm_bed,
iTerm_bed,
dTerm_bed,
pid_error_bed;
#else
static millis_t next_bed_check_ms;
#endif
static uint16_t raw_temp_value[MAX_EXTRUDERS],
raw_temp_bed_value;
// Init min and max temp with extreme values to prevent false errors during startup
static int16_t minttemp_raw[HOTENDS],
maxttemp_raw[HOTENDS],
minttemp[HOTENDS],
maxttemp[HOTENDS];
#ifdef MAX_CONSECUTIVE_LOW_TEMPERATURE_ERROR_ALLOWED
static uint8_t consecutive_low_temperature_error[HOTENDS];
#endif
#ifdef MILLISECONDS_PREHEAT_TIME
static millis_t preheat_end_time[HOTENDS];
#endif
#ifdef BED_MINTEMP
static int16_t bed_minttemp_raw;
#endif
#ifdef BED_MAXTEMP
static int16_t bed_maxttemp_raw;
#endif
#if ENABLED(FILAMENT_WIDTH_SENSOR)
static int8_t meas_shift_index; // Index of a delayed sample in buffer
#endif
#if HAS_AUTO_FAN
static millis_t next_auto_fan_check_ms;
#endif
#if ENABLED(FILAMENT_WIDTH_SENSOR)
static uint16_t current_raw_filwidth; // Measured filament diameter - one extruder only
#endif
#if ENABLED(PROBING_HEATERS_OFF)
static bool paused;
#endif
#if HEATER_IDLE_HANDLER
static millis_t heater_idle_timeout_ms[HOTENDS];
static bool heater_idle_timeout_exceeded[HOTENDS];
#if HAS_TEMP_BED
static millis_t bed_idle_timeout_ms;
static bool bed_idle_timeout_exceeded;
#endif
#endif
public:
#if ENABLED(ADC_KEYPAD)
static uint32_t current_ADCKey_raw;
static uint8_t ADCKey_count;
#endif
/**
* Instance Methods
*/
Temperature();
void init();
/**
* Static (class) methods
*/
static float analog2temp(int raw, uint8_t e);
static float analog2tempBed(int raw);
/**
* Called from the Temperature ISR
*/
static void isr();
/**
* Call periodically to manage heaters
*/
static void manage_heater() _O2; // Added _O2 to work around a compiler error
/**
* Preheating hotends
*/
#ifdef MILLISECONDS_PREHEAT_TIME
static bool is_preheating(uint8_t e) {
#if HOTENDS == 1
UNUSED(e);
#endif
return preheat_end_time[HOTEND_INDEX] && PENDING(millis(), preheat_end_time[HOTEND_INDEX]);
}
static void start_preheat_time(uint8_t e) {
#if HOTENDS == 1
UNUSED(e);
#endif
preheat_end_time[HOTEND_INDEX] = millis() + MILLISECONDS_PREHEAT_TIME;
}
static void reset_preheat_time(uint8_t e) {
#if HOTENDS == 1
UNUSED(e);
#endif
preheat_end_time[HOTEND_INDEX] = 0;
}
#else
#define is_preheating(n) (false)
#endif
#if ENABLED(FILAMENT_WIDTH_SENSOR)
static float analog2widthFil(); // Convert raw Filament Width to millimeters
static int widthFil_to_size_ratio(); // Convert raw Filament Width to an extrusion ratio
#endif
//high level conversion routines, for use outside of temperature.cpp
//inline so that there is no performance decrease.
//deg=degreeCelsius
static float degHotend(uint8_t e) {
#if HOTENDS == 1
UNUSED(e);
#endif
return current_temperature[HOTEND_INDEX];
}
static float degBed() { return current_temperature_bed; }
#if ENABLED(SHOW_TEMP_ADC_VALUES)
static int16_t rawHotendTemp(uint8_t e) {
#if HOTENDS == 1
UNUSED(e);
#endif
return current_temperature_raw[HOTEND_INDEX];
}
static int16_t rawBedTemp() { return current_temperature_bed_raw; }
#endif
static int16_t degTargetHotend(uint8_t e) {
#if HOTENDS == 1
UNUSED(e);
#endif
return target_temperature[HOTEND_INDEX];
}
static int16_t degTargetBed() { return target_temperature_bed; }
#if WATCH_HOTENDS
static void start_watching_heater(uint8_t e = 0);
#endif
#if WATCH_THE_BED
static void start_watching_bed();
#endif
static void setTargetHotend(const int16_t celsius, uint8_t e) {
#if HOTENDS == 1
UNUSED(e);
#endif
#ifdef MILLISECONDS_PREHEAT_TIME
if (celsius == 0)
reset_preheat_time(HOTEND_INDEX);
else if (target_temperature[HOTEND_INDEX] == 0)
start_preheat_time(HOTEND_INDEX);
#endif
target_temperature[HOTEND_INDEX] = celsius;
#if WATCH_HOTENDS
start_watching_heater(HOTEND_INDEX);
#endif
}
static void setTargetBed(const int16_t celsius) {
#if HAS_HEATER_BED
target_temperature_bed =
#ifdef BED_MAXTEMP
min(celsius, BED_MAXTEMP)
#else
celsius
#endif
;
#if WATCH_THE_BED
start_watching_bed();
#endif
#endif
}
static bool isHeatingHotend(uint8_t e) {
#if HOTENDS == 1
UNUSED(e);
#endif
return target_temperature[HOTEND_INDEX] > current_temperature[HOTEND_INDEX];
}
static bool isHeatingBed() { return target_temperature_bed > current_temperature_bed; }
static bool isCoolingHotend(uint8_t e) {
#if HOTENDS == 1
UNUSED(e);
#endif
return target_temperature[HOTEND_INDEX] < current_temperature[HOTEND_INDEX];
}
static bool isCoolingBed() { return target_temperature_bed < current_temperature_bed; }
/**
* The software PWM power for a heater
*/
static int getHeaterPower(int heater);
/**
* Switch off all heaters, set all target temperatures to 0
*/
static void disable_all_heaters();
/**
* Perform auto-tuning for hotend or bed in response to M303
*/
#if HAS_PID_HEATING
static void PID_autotune(const float temp, const int8_t hotend, const int8_t ncycles, const bool set_result=false);
#endif
/**
* Update the temp manager when PID values change
*/
static void updatePID();
#if ENABLED(BABYSTEPPING)
static void babystep_axis(const AxisEnum axis, const int distance) {
if (axis_known_position[axis]) {
#if IS_CORE
#if ENABLED(BABYSTEP_XY)
switch (axis) {
case CORE_AXIS_1: // X on CoreXY and CoreXZ, Y on CoreYZ
babystepsTodo[CORE_AXIS_1] += distance * 2;
babystepsTodo[CORE_AXIS_2] += distance * 2;
break;
case CORE_AXIS_2: // Y on CoreXY, Z on CoreXZ and CoreYZ
babystepsTodo[CORE_AXIS_1] += CORESIGN(distance * 2);
babystepsTodo[CORE_AXIS_2] -= CORESIGN(distance * 2);
break;
case NORMAL_AXIS: // Z on CoreXY, Y on CoreXZ, X on CoreYZ
babystepsTodo[NORMAL_AXIS] += distance;
break;
}
#elif CORE_IS_XZ || CORE_IS_YZ
// Only Z stepping needs to be handled here
babystepsTodo[CORE_AXIS_1] += CORESIGN(distance * 2);
babystepsTodo[CORE_AXIS_2] -= CORESIGN(distance * 2);
#else
babystepsTodo[Z_AXIS] += distance;
#endif
#else
babystepsTodo[axis] += distance;
#endif
}
}
#endif // BABYSTEPPING
#if ENABLED(PROBING_HEATERS_OFF)
static void pause(const bool p);
static bool is_paused() { return paused; }
#endif
#if HEATER_IDLE_HANDLER
static void start_heater_idle_timer(uint8_t e, millis_t timeout_ms) {
#if HOTENDS == 1
UNUSED(e);
#endif
heater_idle_timeout_ms[HOTEND_INDEX] = millis() + timeout_ms;
heater_idle_timeout_exceeded[HOTEND_INDEX] = false;
}
static void reset_heater_idle_timer(uint8_t e) {
#if HOTENDS == 1
UNUSED(e);
#endif
heater_idle_timeout_ms[HOTEND_INDEX] = 0;
heater_idle_timeout_exceeded[HOTEND_INDEX] = false;
#if WATCH_HOTENDS
start_watching_heater(HOTEND_INDEX);
#endif
}
static bool is_heater_idle(uint8_t e) {
#if HOTENDS == 1
UNUSED(e);
#endif
return heater_idle_timeout_exceeded[HOTEND_INDEX];
}
#if HAS_TEMP_BED
static void start_bed_idle_timer(millis_t timeout_ms) {
bed_idle_timeout_ms = millis() + timeout_ms;
bed_idle_timeout_exceeded = false;
}
static void reset_bed_idle_timer() {
bed_idle_timeout_ms = 0;
bed_idle_timeout_exceeded = false;
#if WATCH_THE_BED
start_watching_bed();
#endif
}
static bool is_bed_idle() {
return bed_idle_timeout_exceeded;
}
#endif
#endif
private:
static void set_current_temp_raw();
static void updateTemperaturesFromRawValues();
#if ENABLED(HEATER_0_USES_MAX6675)
static int read_max6675();
#endif
static void checkExtruderAutoFans();
static float get_pid_output(const int8_t e);
#if ENABLED(PIDTEMPBED)
static float get_pid_output_bed();
#endif
static void _temp_error(const int8_t e, const char * const serial_msg, const char * const lcd_msg);
static void min_temp_error(const int8_t e);
static void max_temp_error(const int8_t e);
#if ENABLED(THERMAL_PROTECTION_HOTENDS) || HAS_THERMALLY_PROTECTED_BED
typedef enum TRState { TRInactive, TRFirstHeating, TRStable, TRRunaway } TRstate;
static void thermal_runaway_protection(TRState* state, millis_t* timer, float temperature, float target_temperature, int heater_id, int period_seconds, int hysteresis_degc);
#if ENABLED(THERMAL_PROTECTION_HOTENDS)
static TRState thermal_runaway_state_machine[HOTENDS];
static millis_t thermal_runaway_timer[HOTENDS];
#endif
#if HAS_THERMALLY_PROTECTED_BED
static TRState thermal_runaway_bed_state_machine;
static millis_t thermal_runaway_bed_timer;
#endif
#endif // THERMAL_PROTECTION
};
extern Temperature thermalManager;
#endif // TEMPERATURE_H