From c6d0494cbc42a78b65fe22bf1ea77503311f2541 Mon Sep 17 00:00:00 2001 From: Yuri D'Elia Date: Sun, 15 May 2022 23:35:46 +0200 Subject: [PATCH] Split temperature management into it's own ISR Use a new low-priority "temp_mgr_isr" running at constant rate for temperature management. This is done so that the temperatures are sampled at a constant independent interval *and* with reduced jitter. Likewise for actual PID management. This will require further adjustment for the min/max/runaway display, which cannot be done directly into this function anymore (the code will need to disable heaters but flag for display to be handled in manage_heaters). --- Firmware/Marlin_main.cpp | 5 +- Firmware/temperature.cpp | 496 ++++++++++++++++++++++----------------- Firmware/temperature.h | 1 + 3 files changed, 283 insertions(+), 219 deletions(-) diff --git a/Firmware/Marlin_main.cpp b/Firmware/Marlin_main.cpp index c2153524..09843598 100644 --- a/Firmware/Marlin_main.cpp +++ b/Firmware/Marlin_main.cpp @@ -1279,8 +1279,9 @@ void setup() // performed inside the soft_pwm_isr) SdFatUtil::set_stack_guard(); - // Initialize temperature loop + // Initialize pwm/temperature loops soft_pwm_init(); + temp_mgr_init(); #ifdef EXTRUDER_ALTFAN_DETECT SERIAL_ECHORPGM(_n("Extruder fan type: ")); @@ -1365,7 +1366,9 @@ void setup() setup_photpin(); +#if 0 servo_init(); +#endif // Reset the machine correction matrix. // It does not make sense to load the correction matrix until the machine is homed. diff --git a/Firmware/temperature.cpp b/Firmware/temperature.cpp index 512afa97..a85d1149 100755 --- a/Firmware/temperature.cpp +++ b/Firmware/temperature.cpp @@ -97,7 +97,6 @@ unsigned char soft_pwm_bed; //=========================================================================== //=============================private variables============================ //=========================================================================== -#define TEMP_MEAS_RATE 250 static volatile bool temp_meas_ready = false; #ifdef PIDTEMP @@ -442,208 +441,7 @@ void manage_heater() #ifdef WATCHDOG wdt_reset(); #endif //WATCHDOG - - float pid_input; - float pid_output; - - // run at TEMP_MEAS_RATE - if(temp_meas_ready != true) return; - static unsigned long old_stamp = _millis(); - unsigned long new_stamp = _millis(); - unsigned long diff = new_stamp - old_stamp; - if(diff < TEMP_MEAS_RATE) return; - old_stamp = new_stamp; - - // ADC values need to be converted before checking: converted values are later used in MINTEMP - updateTemperaturesFromRawValues(); - - check_max_temp(); - check_min_temp(); - -#ifdef TEMP_RUNAWAY_BED_HYSTERESIS - temp_runaway_check(0, target_temperature_bed, current_temperature_bed, (int)soft_pwm_bed, true); -#endif - - for(uint8_t e = 0; e < EXTRUDERS; e++) - { - -#ifdef TEMP_RUNAWAY_EXTRUDER_HYSTERESIS - temp_runaway_check(e+1, target_temperature[e], current_temperature[e], (int)soft_pwm[e], false); -#endif - - #ifdef PIDTEMP - pid_input = current_temperature[e]; - - #ifndef PID_OPENLOOP - if(target_temperature[e] == 0) { - pid_output = 0; - pid_reset[e] = true; - } else { - pid_error[e] = target_temperature[e] - pid_input; - if(pid_reset[e]) { - iState_sum[e] = 0.0; - dTerm[e] = 0.0; // 'dState_last[e]' initial setting is not necessary (see end of if-statement) - pid_reset[e] = false; - } -#ifndef PonM - pTerm[e] = cs.Kp * pid_error[e]; - iState_sum[e] += pid_error[e]; - iState_sum[e] = constrain(iState_sum[e], iState_sum_min[e], iState_sum_max[e]); - iTerm[e] = cs.Ki * iState_sum[e]; - // PID_K1 defined in Configuration.h in the PID settings - #define K2 (1.0-PID_K1) - dTerm[e] = (cs.Kd * (pid_input - dState_last[e]))*K2 + (PID_K1 * dTerm[e]); // e.g. digital filtration of derivative term changes - pid_output = pTerm[e] + iTerm[e] - dTerm[e]; // subtraction due to "Derivative on Measurement" method (i.e. derivative of input instead derivative of error is used) - if (pid_output > PID_MAX) { - if (pid_error[e] > 0 ) iState_sum[e] -= pid_error[e]; // conditional un-integration - pid_output=PID_MAX; - } else if (pid_output < 0) { - if (pid_error[e] < 0 ) iState_sum[e] -= pid_error[e]; // conditional un-integration - pid_output=0; - } -#else // PonM ("Proportional on Measurement" method) - iState_sum[e] += cs.Ki * pid_error[e]; - iState_sum[e] -= cs.Kp * (pid_input - dState_last[e]); - iState_sum[e] = constrain(iState_sum[e], 0, PID_INTEGRAL_DRIVE_MAX); - dTerm[e] = cs.Kd * (pid_input - dState_last[e]); - pid_output = iState_sum[e] - dTerm[e]; // subtraction due to "Derivative on Measurement" method (i.e. derivative of input instead derivative of error is used) - pid_output = constrain(pid_output, 0, PID_MAX); -#endif // PonM - } - dState_last[e] = pid_input; - #else - pid_output = constrain(target_temperature[e], 0, PID_MAX); - #endif //PID_OPENLOOP - #ifdef PID_DEBUG - SERIAL_ECHO_START; - SERIAL_ECHO(" PID_DEBUG "); - SERIAL_ECHO(e); - SERIAL_ECHO(": Input "); - SERIAL_ECHO(pid_input); - SERIAL_ECHO(" Output "); - SERIAL_ECHO(pid_output); - SERIAL_ECHO(" pTerm "); - SERIAL_ECHO(pTerm[e]); - SERIAL_ECHO(" iTerm "); - SERIAL_ECHO(iTerm[e]); - SERIAL_ECHO(" dTerm "); - SERIAL_ECHOLN(-dTerm[e]); - #endif //PID_DEBUG - #else /* PID off */ - pid_output = 0; - if(current_temperature[e] < target_temperature[e]) { - pid_output = PID_MAX; - } - #endif - - // Check if temperature is within the correct range - if((current_temperature[e] < maxttemp[e]) && (target_temperature[e] != 0)) - { - soft_pwm[e] = (int)pid_output >> 1; - } - else - { - soft_pwm[e] = 0; - } - } // End extruder for loop - - checkFans(); - - #ifndef PIDTEMPBED - if(_millis() - previous_millis_bed_heater < BED_CHECK_INTERVAL) - return; - previous_millis_bed_heater = _millis(); - #endif - - #if TEMP_SENSOR_BED != 0 - - #ifdef PIDTEMPBED - pid_input = current_temperature_bed; - - #ifndef PID_OPENLOOP - pid_error_bed = target_temperature_bed - pid_input; - pTerm_bed = cs.bedKp * pid_error_bed; - temp_iState_bed += pid_error_bed; - temp_iState_bed = constrain(temp_iState_bed, temp_iState_min_bed, temp_iState_max_bed); - iTerm_bed = cs.bedKi * temp_iState_bed; - - //PID_K1 defined in Configuration.h in the PID settings - #define K2 (1.0-PID_K1) - dTerm_bed= (cs.bedKd * (pid_input - temp_dState_bed))*K2 + (PID_K1 * dTerm_bed); - temp_dState_bed = pid_input; - - pid_output = pTerm_bed + iTerm_bed - dTerm_bed; - if (pid_output > MAX_BED_POWER) { - if (pid_error_bed > 0 ) temp_iState_bed -= pid_error_bed; // conditional un-integration - pid_output=MAX_BED_POWER; - } else if (pid_output < 0){ - if (pid_error_bed < 0 ) temp_iState_bed -= pid_error_bed; // conditional un-integration - pid_output=0; - } - - #else - pid_output = constrain(target_temperature_bed, 0, MAX_BED_POWER); - #endif //PID_OPENLOOP - - if(current_temperature_bed < BED_MAXTEMP) - { - soft_pwm_bed = (int)pid_output >> 1; - timer02_set_pwm0(soft_pwm_bed << 1); - } - else { - soft_pwm_bed = 0; - timer02_set_pwm0(soft_pwm_bed << 1); - } - - #elif !defined(BED_LIMIT_SWITCHING) - // Check if temperature is within the correct range - if(current_temperature_bed < BED_MAXTEMP) - { - if(current_temperature_bed >= target_temperature_bed) - { - soft_pwm_bed = 0; - timer02_set_pwm0(soft_pwm_bed << 1); - } - else - { - soft_pwm_bed = MAX_BED_POWER>>1; - timer02_set_pwm0(soft_pwm_bed << 1); - } - } - else - { - soft_pwm_bed = 0; - timer02_set_pwm0(soft_pwm_bed << 1); - WRITE(HEATER_BED_PIN,LOW); - } - #else //#ifdef BED_LIMIT_SWITCHING - // Check if temperature is within the correct band - if(current_temperature_bed < BED_MAXTEMP) - { - if(current_temperature_bed > target_temperature_bed + BED_HYSTERESIS) - { - soft_pwm_bed = 0; - timer02_set_pwm0(soft_pwm_bed << 1); - } - else if(current_temperature_bed <= target_temperature_bed - BED_HYSTERESIS) - { - soft_pwm_bed = MAX_BED_POWER>>1; - timer02_set_pwm0(soft_pwm_bed << 1); - } - } - else - { - soft_pwm_bed = 0; - timer02_set_pwm0(soft_pwm_bed << 1); - WRITE(HEATER_BED_PIN,LOW); - } - #endif - if(target_temperature_bed==0) - { - soft_pwm_bed = 0; - timer02_set_pwm0(soft_pwm_bed << 1); - } - #endif + checkFans(); } #define PGM_RD_W(x) (short)pgm_read_word(&x) @@ -769,15 +567,8 @@ static float analog2tempAmbient(int raw) } #endif //AMBIENT_THERMISTOR -/* Called to get the raw values into the the actual temperatures. The raw values are created in interrupt context, - and this function is called from normal context as it is too slow to run in interrupts and will block the stepper routine otherwise */ -static void updateTemperaturesFromRawValues() +static void setTemperaturesFromRawValues() { - CRITICAL_SECTION_START; - adc_start_cycle(); - temp_meas_ready = false; - CRITICAL_SECTION_END; - for(uint8_t e=0;e PID_MAX) { + if (pid_error[e] > 0 ) iState_sum[e] -= pid_error[e]; // conditional un-integration + pid_output=PID_MAX; + } else if (pid_output < 0) { + if (pid_error[e] < 0 ) iState_sum[e] -= pid_error[e]; // conditional un-integration + pid_output=0; + } +#else // PonM ("Proportional on Measurement" method) + iState_sum[e] += cs.Ki * pid_error[e]; + iState_sum[e] -= cs.Kp * (pid_input - dState_last[e]); + iState_sum[e] = constrain(iState_sum[e], 0, PID_INTEGRAL_DRIVE_MAX); + dTerm[e] = cs.Kd * (pid_input - dState_last[e]); + pid_output = iState_sum[e] - dTerm[e]; // subtraction due to "Derivative on Measurement" method (i.e. derivative of input instead derivative of error is used) + pid_output = constrain(pid_output, 0, PID_MAX); +#endif // PonM + } + dState_last[e] = pid_input; +#else //PID_OPENLOOP + pid_output = constrain(target_temperature[e], 0, PID_MAX); +#endif //PID_OPENLOOP + +#ifdef PID_DEBUG + SERIAL_ECHO_START; + SERIAL_ECHO(" PID_DEBUG "); + SERIAL_ECHO(e); + SERIAL_ECHO(": Input "); + SERIAL_ECHO(pid_input); + SERIAL_ECHO(" Output "); + SERIAL_ECHO(pid_output); + SERIAL_ECHO(" pTerm "); + SERIAL_ECHO(pTerm[e]); + SERIAL_ECHO(" iTerm "); + SERIAL_ECHO(iTerm[e]); + SERIAL_ECHO(" dTerm "); + SERIAL_ECHOLN(-dTerm[e]); +#endif //PID_DEBUG + +#else /* PID off */ + pid_output = 0; + if(current_temperature[e] < target_temperature[e]) { + pid_output = PID_MAX; + } +#endif + + // Check if temperature is within the correct range + if((current_temperature[e] < maxttemp[e]) && (target_temperature[e] != 0)) + soft_pwm[e] = (int)pid_output >> 1; + else + soft_pwm[e] = 0; +} + +static void pid_bed() +{ + float pid_input; + float pid_output; + +#ifdef TEMP_RUNAWAY_BED_HYSTERESIS + temp_runaway_check(0, target_temperature_bed, current_temperature_bed, (int)soft_pwm_bed, true); +#endif + +#ifndef PIDTEMPBED + if(_millis() - previous_millis_bed_heater < BED_CHECK_INTERVAL) + return; + previous_millis_bed_heater = _millis(); +#endif + +#if TEMP_SENSOR_BED != 0 + +#ifdef PIDTEMPBED + pid_input = current_temperature_bed; + +#ifndef PID_OPENLOOP + pid_error_bed = target_temperature_bed - pid_input; + pTerm_bed = cs.bedKp * pid_error_bed; + temp_iState_bed += pid_error_bed; + temp_iState_bed = constrain(temp_iState_bed, temp_iState_min_bed, temp_iState_max_bed); + iTerm_bed = cs.bedKi * temp_iState_bed; + + //PID_K1 defined in Configuration.h in the PID settings +#define K2 (1.0-PID_K1) + dTerm_bed= (cs.bedKd * (pid_input - temp_dState_bed))*K2 + (PID_K1 * dTerm_bed); + temp_dState_bed = pid_input; + + pid_output = pTerm_bed + iTerm_bed - dTerm_bed; + if (pid_output > MAX_BED_POWER) { + if (pid_error_bed > 0 ) temp_iState_bed -= pid_error_bed; // conditional un-integration + pid_output=MAX_BED_POWER; + } else if (pid_output < 0){ + if (pid_error_bed < 0 ) temp_iState_bed -= pid_error_bed; // conditional un-integration + pid_output=0; + } + +#else + pid_output = constrain(target_temperature_bed, 0, MAX_BED_POWER); +#endif //PID_OPENLOOP + + if(current_temperature_bed < BED_MAXTEMP) + { + soft_pwm_bed = (int)pid_output >> 1; + timer02_set_pwm0(soft_pwm_bed << 1); + } + else + { + soft_pwm_bed = 0; + timer02_set_pwm0(soft_pwm_bed << 1); + } + +#elif !defined(BED_LIMIT_SWITCHING) + // Check if temperature is within the correct range + if(current_temperature_bed < BED_MAXTEMP) + { + if(current_temperature_bed >= target_temperature_bed) + { + soft_pwm_bed = 0; + timer02_set_pwm0(soft_pwm_bed << 1); + } + else + { + soft_pwm_bed = MAX_BED_POWER>>1; + timer02_set_pwm0(soft_pwm_bed << 1); + } + } + else + { + soft_pwm_bed = 0; + timer02_set_pwm0(soft_pwm_bed << 1); + WRITE(HEATER_BED_PIN,LOW); + } +#else //#ifdef BED_LIMIT_SWITCHING + // Check if temperature is within the correct band + if(current_temperature_bed < BED_MAXTEMP) + { + if(current_temperature_bed > target_temperature_bed + BED_HYSTERESIS) + { + soft_pwm_bed = 0; + timer02_set_pwm0(soft_pwm_bed << 1); + } + else if(current_temperature_bed <= target_temperature_bed - BED_HYSTERESIS) + { + soft_pwm_bed = MAX_BED_POWER>>1; + timer02_set_pwm0(soft_pwm_bed << 1); + } + } + else + { + soft_pwm_bed = 0; + timer02_set_pwm0(soft_pwm_bed << 1); + WRITE(HEATER_BED_PIN,LOW); + } +#endif //BED_LIMIT_SWITCHING + + if(target_temperature_bed==0) + { + soft_pwm_bed = 0; + timer02_set_pwm0(soft_pwm_bed << 1); + } +#endif //TEMP_SENSOR_BED +} + +static void temp_mgr_isr() +{ + // ADC values need to be converted before checking: converted values are later used in MINTEMP + setTemperaturesFromRawValues(); + + // TODO: this is now running inside an isr and cannot directly manipulate the lcd, + // this needs to disable temperatures and flag the error to be shown in manage_heaters! + check_max_temp(); + check_min_temp(); + + for(uint8_t e = 0; e < EXTRUDERS; e++) + pid_heater(e); + pid_bed(); +} + +ISR(TIMER5_COMPA_vect) +{ + // immediately schedule a new conversion + if(temp_meas_ready != true) return; + adc_start_cycle(); + temp_meas_ready = false; + + // run temperature management with interrupts enabled to reduce latency + DISABLE_TEMP_MGR_INTERRUPT(); + sei(); + temp_mgr_isr(); + cli(); + ENABLE_TEMP_MGR_INTERRUPT(); +} diff --git a/Firmware/temperature.h b/Firmware/temperature.h index 0ac1a4a9..427bb896 100755 --- a/Firmware/temperature.h +++ b/Firmware/temperature.h @@ -40,6 +40,7 @@ // public functions void soft_pwm_init(); //initialize the soft pwm isr +void temp_mgr_init(); //initialize the temperature handler void manage_heater(); //it is critical that this is called periodically. extern bool checkAllHotends(void);