diff --git a/.travis.yml b/.travis.yml index 3609fb30c..2d88cecc8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,6 @@ before_install: - sudo iptables -A OUTPUT -o lo -j ACCEPT - sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT script: - - bash -x test.sh - cp Firmware/variants/1_75mm_MK3S-EINSy10a-E3Dv6full.h Firmware/Configuration_prusa.h - bash -x build.sh || { echo "1_75mm_MK3S-EINSy10a-E3Dv6full variant failed" && false; } - rm Firmware/Configuration_prusa.h diff --git a/Firmware/ConfigurationStore.cpp b/Firmware/ConfigurationStore.cpp index 77ef6df66..0917ee181 100644 --- a/Firmware/ConfigurationStore.cpp +++ b/Firmware/ConfigurationStore.cpp @@ -75,6 +75,9 @@ void Config_StoreSettings() if (EEPROM_writeData(reinterpret_cast(EEPROM_M500_base),reinterpret_cast(&cs),sizeof(cs),0), "cs, invalid version") { +#ifdef TEMP_MODEL + temp_model_save_settings(); +#endif strcpy(cs.version,EEPROM_VERSION); //!< validate data if write succeed EEPROM_writeData(reinterpret_cast(EEPROM_M500_base->version), reinterpret_cast(cs.version), sizeof(cs.version), "cs.version valid"); } @@ -173,6 +176,9 @@ void Config_PrintSettings(uint8_t level) printf_P(PSTR( "%SArc Settings: P:Max length(mm) S:Min length (mm) N:Corrections R:Min segments F:Segments/sec.\n%S M214 P%.2f S%.2f N%d R%d F%d\n"), echomagic, echomagic, cs.mm_per_arc_segment, cs.min_mm_per_arc_segment, cs.n_arc_correction, cs.min_arc_segments, cs.arc_segments_per_sec); +#ifdef TEMP_MODEL + temp_model_report_settings(); +#endif } #endif @@ -321,6 +327,10 @@ bool Config_RetrieveSettings() // Call updatePID (similar to when we have processed M301) updatePID(); +#ifdef TEMP_MODEL + temp_model_load_settings(); +#endif + SERIAL_ECHO_START; SERIAL_ECHOLNPGM("Stored settings retrieved"); } @@ -353,6 +363,9 @@ void Config_ResetDefault() #ifdef PIDTEMP updatePID(); #endif//PIDTEMP +#ifdef TEMP_MODEL + temp_model_reset_settings(); +#endif calculate_extruder_multipliers(); diff --git a/Firmware/Dcodes.cpp b/Firmware/Dcodes.cpp index 95daaf805..9302f4ac9 100644 --- a/Firmware/Dcodes.cpp +++ b/Firmware/Dcodes.cpp @@ -573,6 +573,7 @@ void dcode_9() for (uint8_t i = 0; i < ADC_CHAN_CNT; i++) printf_P(PSTR("\tADC%d=%4d\t(%S)\n"), i, dcode_9_ADC_val(i) >> 4, dcode_9_ADC_name(i)); } +#if 0 else { uint8_t index = 0xff; @@ -588,6 +589,7 @@ void dcode_9() } } } +#endif } /*! diff --git a/Firmware/Marlin.h b/Firmware/Marlin.h index f98e0148c..6c4ff484f 100755 --- a/Firmware/Marlin.h +++ b/Firmware/Marlin.h @@ -241,9 +241,10 @@ void prepare_move(); void kill(const char *full_screen_message = NULL, unsigned char id = 0); void finishAndDisableSteppers(); -void UnconditionalStop(); // Stop heaters, motion and clear current print status -void Stop(); // Emergency stop used by overtemp functions which allows recovery -bool IsStopped(); // Returns true if the print has been stopped +void UnconditionalStop(); // Stop heaters, motion and clear current print status +void ThermalStop(bool allow_pause = false); // Emergency stop used by overtemp functions which allows + // recovery (with pause=true) +bool IsStopped(); // Returns true if the print has been stopped //put an ASCII command at the end of the current buffer, read from flash #define enquecommand_P(cmd) enquecommand(cmd, true) @@ -255,27 +256,6 @@ void prepare_arc_move(bool isclockwise); void clamp_to_software_endstops(float target[3]); void refresh_cmd_timeout(void); -// Timer counter, incremented by the 1ms Arduino timer. -// The standard Arduino timer() function returns this value atomically -// by disabling / enabling interrupts. This is costly, if the interrupts are known -// to be disabled. -#ifdef SYSTEM_TIMER_2 -extern volatile unsigned long timer2_millis; -#else //SYSTEM_TIMER_2 -extern volatile unsigned long timer0_millis; -#endif //SYSTEM_TIMER_2 - -// An unsynchronized equivalent to a standard Arduino _millis() function. -// To be used inside an interrupt routine. - -FORCE_INLINE unsigned long millis_nc() { -#ifdef SYSTEM_TIMER_2 - return timer2_millis; -#else //SYSTEM_TIMER_2 - return timer0_millis; -#endif //SYSTEM_TIMER_2 -} - #ifdef FAST_PWM_FAN void setPwmFrequency(uint8_t pin, int val); #endif @@ -315,18 +295,12 @@ void homeaxis(uint8_t axis, uint8_t cnt = 1, uint8_t* pstep = 0); void homeaxis(uint8_t axis, uint8_t cnt = 1); #endif //TMC2130 - -#ifdef FAN_SOFT_PWM -extern unsigned char fanSpeedSoftPwm; -#endif - #ifdef FWRETRACT extern bool retracted[EXTRUDERS]; extern float retract_length_swap; extern float retract_recover_length_swap; #endif - extern uint8_t host_keepalive_interval; extern unsigned long starttime; @@ -364,6 +338,10 @@ extern uint8_t saved_printing_type; #define PRINTING_TYPE_USB 1 #define PRINTING_TYPE_NONE 2 +extern float saved_extruder_temperature; //!< Active extruder temperature +extern float saved_bed_temperature; //!< Bed temperature +extern int saved_fan_speed; //!< Print fan speed + //save/restore printing in case that mmu is not responding extern bool mmu_print_saved; @@ -385,6 +363,8 @@ extern LongTimer safetyTimer; #define PRINT_PERCENT_DONE_INIT 0xff #define PRINTER_ACTIVE (IS_SD_PRINTING || usb_timer.running() || isPrintPaused || (custom_message_type == CustomMsg::TempCal) || saved_printing || (lcd_commands_type == LcdCommands::Layer1Cal) || mmu_print_saved || homing_flag || mesh_bed_leveling_flag) +extern bool printer_active(); + //! Beware - mcode_in_progress is set as soon as the command gets really processed, //! which is not the same as posting the M600 command into the command queue //! There can be a considerable lag between posting M600 and its real processing which might result @@ -467,6 +447,7 @@ extern uint8_t calc_percent_done(); #define KEEPALIVE_STATE(n) do { busy_state = n;} while (0) extern void host_keepalive(); +extern void host_autoreport(); //extern MarlinBusyState busy_state; extern int8_t busy_state; @@ -504,7 +485,6 @@ void raise_z_above(float target, bool plan=true); extern "C" void softReset(); void stack_error(); -void pullup_error(bool fromTempISR); extern uint32_t IP_address; diff --git a/Firmware/MarlinSerial.h b/Firmware/MarlinSerial.h index 3bb0926b2..61da27603 100644 --- a/Firmware/MarlinSerial.h +++ b/Firmware/MarlinSerial.h @@ -37,11 +37,10 @@ // These are macros to build serial port register names for the selected SERIAL_PORT (C preprocessor // requires two levels of indirection to expand macro values properly) -#define SERIAL_REGNAME(registerbase,number,suffix) SERIAL_REGNAME_INTERNAL(registerbase,number,suffix) #if SERIAL_PORT == 0 && (!defined(UBRR0H) || !defined(UDR0)) // use un-numbered registers if necessary -#define SERIAL_REGNAME_INTERNAL(registerbase,number,suffix) registerbase##suffix +#define SERIAL_REGNAME(registerbase,number,suffix) _REGNAME_SHORT(registerbase, suffix) #else -#define SERIAL_REGNAME_INTERNAL(registerbase,number,suffix) registerbase##number##suffix +#define SERIAL_REGNAME(registerbase,number,suffix) _REGNAME(registerbase, number, suffix) #endif // Registers used by MarlinSerial class (these are expanded diff --git a/Firmware/Marlin_main.cpp b/Firmware/Marlin_main.cpp index f0641ab76..d26affdaa 100644 --- a/Firmware/Marlin_main.cpp +++ b/Firmware/Marlin_main.cpp @@ -72,6 +72,7 @@ #include "planner.h" #include "stepper.h" #include "temperature.h" +#include "fancheck.h" #include "motion_control.h" #include "cardreader.h" #include "ConfigurationStore.h" @@ -379,9 +380,10 @@ static float saved_pos[4] = { X_COORD_INVALID, 0, 0, 0 }; static uint16_t saved_feedrate2 = 0; //!< Default feedrate (truncated from float) static int saved_feedmultiply2 = 0; static uint8_t saved_active_extruder = 0; -static float saved_extruder_temperature = 0.0; //!< Active extruder temperature +float saved_extruder_temperature = 0.0; //!< Active extruder temperature +float saved_bed_temperature = 0.0; //!< Bed temperature static bool saved_extruder_relative_mode = false; -static int saved_fanSpeed = 0; //!< Print fan speed +int saved_fan_speed = 0; //!< Print fan speed //! @} static int saved_feedmultiply_mm = 100; @@ -571,6 +573,10 @@ void servo_init() #endif } +bool printer_active() +{ + return PRINTER_ACTIVE; +} bool fans_check_enabled = true; @@ -1272,9 +1278,15 @@ void setup() else { //printer version was changed so use default settings Config_ResetDefault(); } - SdFatUtil::set_stack_guard(); //writes magic number at the end of static variables to protect against overwriting static memory by stack - tp_init(); // Initialize temperature loop + // writes a magic number at the end of static variables to monitor against incorrect overwriting + // of static memory by stack (this needs to be done before soft_pwm_init, since the check is + // performed inside the soft_pwm_isr) + SdFatUtil::set_stack_guard(); + + // Initialize pwm/temperature loops + soft_pwm_init(); + temp_mgr_init(); #ifdef EXTRUDER_ALTFAN_DETECT SERIAL_ECHORPGM(_n("Extruder fan type: ")); @@ -1359,7 +1371,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. @@ -1692,10 +1706,6 @@ void stack_error() { crash_and_burn(dump_crash_reason::stack_error); } -void pullup_error(bool fromTempISR) { - crash_and_burn(fromTempISR ? dump_crash_reason::bad_pullup_temp_isr : dump_crash_reason::bad_pullup_step_isr); -} - #ifdef PRUSA_M28 void trace(); @@ -1774,7 +1784,7 @@ void serial_read_stream() { * Output autoreport values according to features requested in M155 */ #if defined(AUTO_REPORT) -static void host_autoreport() +void host_autoreport() { if (autoReportFeatures.TimerExpired()) { @@ -1834,7 +1844,17 @@ void host_keepalive() { // Before loop(), the setup() function is called by the main() routine. void loop() { - KEEPALIVE_STATE(NOT_BUSY); + // Reset a previously aborted command, we can now start processing motion again + planner_aborted = false; + + if(Stopped) { + // Currently Stopped (possibly due to an error) and not accepting new serial commands. + // Signal to the host that we're currently busy waiting for supervision. + KEEPALIVE_STATE(PAUSED_FOR_USER); + } else { + // Printer is available for processing, reset state + KEEPALIVE_STATE(NOT_BUSY); + } if (isPrintPaused && saved_printing_type == PRINTING_TYPE_USB) { //keep believing that usb is being printed. Prevents accessing dangerous menus while pausing. usb_timer.start(); @@ -1843,13 +1863,6 @@ void loop() ; } -#ifdef FANCHECK - if (fan_check_error && isPrintPaused && !IS_SD_PRINTING) { - KEEPALIVE_STATE(PAUSED_FOR_USER); - host_keepalive(); //prevent timeouts since usb processing is disabled until print is resumed. This is for a crude way of pausing a print on all hosts. - } -#endif - #ifdef PRUSA_M28 if (prusa_sd_card_upload) { @@ -2206,6 +2219,7 @@ void raise_z_above(float target, bool plan) // Z needs raising current_position[Z_AXIS] = target; + clamp_to_software_endstops(current_position); #if defined(Z_MIN_PIN) && (Z_MIN_PIN > -1) && !defined(DEBUG_DISABLE_ZMINLIMIT) bool z_min_endstop = (READ(Z_MIN_PIN) != Z_MIN_ENDSTOP_INVERTING); @@ -2982,7 +2996,7 @@ static void gcode_G28(bool home_x_axis, bool home_y_axis, bool home_z_axis) static void gcode_G80() { st_synchronize(); - if (waiting_inside_plan_buffer_line_print_aborted) + if (planner_aborted) return; mesh_bed_leveling_flag = true; @@ -3076,7 +3090,7 @@ static void gcode_G80() plan_buffer_line_curposXYZE(XY_AXIS_FEEDRATE); // Wait until the move is finished. st_synchronize(); - if (waiting_inside_plan_buffer_line_print_aborted) + if (planner_aborted) { custom_message_type = custom_message_type_old; custom_message_state = custom_message_state_old; @@ -3151,7 +3165,7 @@ static void gcode_G80() //printf_P(PSTR("after clamping: [%f;%f]\n"), current_position[X_AXIS], current_position[Y_AXIS]); plan_buffer_line_curposXYZE(XY_AXIS_FEEDRATE); st_synchronize(); - if (waiting_inside_plan_buffer_line_print_aborted) + if (planner_aborted) { custom_message_type = custom_message_type_old; custom_message_state = custom_message_state_old; @@ -4138,6 +4152,7 @@ extern uint8_t st_backlash_y; //!@n M302 - Allow cold extrudes, or set the minimum extrude S. //!@n M303 - PID relay autotune S sets the target temperature. (default target temperature = 150C) //!@n M304 - Set bed PID parameters P I and D +//!@n M310 - Temperature model settings //!@n M400 - Finish all moves //!@n M401 - Lower z-probe if present //!@n M402 - Raise z-probe if present @@ -4179,16 +4194,6 @@ There are reasons why some G Codes aren't in numerical order. void process_commands() { -#ifdef FANCHECK - if(fan_check_error == EFCE_DETECTED) { - fan_check_error = EFCE_REPORTED; - if (usb_timer.running()) - lcd_pause_usb_print(); - else - lcd_pause_print(); - } -#endif - if (!buflen) return; //empty command #ifdef CMDBUFFER_DEBUG @@ -4642,7 +4647,7 @@ eeprom_update_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM,0xFFFF); */ case 0: // G0 -> G1 case 1: // G1 - if(Stopped == false) { + { get_coordinates(); // For X Y Z E F // When recovering from a previous print move, restore the originally @@ -4701,7 +4706,7 @@ eeprom_update_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM,0xFFFF); */ case 2: - if(Stopped == false) { + { get_arc_coordinates(); prepare_arc_move(true); } @@ -4709,7 +4714,7 @@ eeprom_update_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM,0xFFFF); // ------------------------------- case 3: - if(Stopped == false) { + { get_arc_coordinates(); prepare_arc_move(false); } @@ -7760,6 +7765,67 @@ Sigma_Exit: PID_autotune(temp, e, c); } break; + +#ifdef TEMP_MODEL + /*! + ### M310 - Temperature model settings M310: Temperature model settings + #### Usage + + M310 ; report values + M310 [ A ] ; autotune + M310 [ S ] ; set 0=disable 1=enable + M310 [ I ] [ R ] ; set resistance at index + M310 [ P | C ] ; set power, capacitance + M310 [ B | E | W ] ; set beeper, warning and error threshold + M310 [ T ] ; set ambient temperature correction + + #### Parameters + - `I` - resistance index position (0-15) + - `R` - resistance value at index (K/W; requires `I`) + - `P` - power (W) + - `C` - capacitance (J/K) + - `S` - set 0=disable 1=enable + - `B` - beep and warn when reaching warning threshold 0=disable 1=enable (default: 1) + - `E` - error threshold (K/s; default in variant) + - `W` - warning threshold (K/s; default in variant) + - `T` - ambient temperature correction (K; default in variant) + - `A` - autotune C+R values + */ + case 310: + { + // parse all parameters + float P = NAN, C = NAN, R = NAN, E = NAN, W = NAN, T = NAN; + int8_t I = -1, S = -1, B = -1, A = -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('B')) B = 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_short(); + + // report values if nothing has been requested + if(isnan(C) && isnan(P) && isnan(R) && isnan(E) && isnan(W) && isnan(T) && I < 0 && S < 0 && B < 0 && A < 0) { + temp_model_report_settings(); + break; + } + + // update all parameters + if(B >= 0) temp_model_set_warn_beep(B); + 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); + + // enable the model last, if requested + if(S >= 0) temp_model_set_enabled(S); + + // run autotune + if(A >= 0) temp_model_autotune(A); + } + break; +#endif /*! ### M400 - Wait for all moves to finish M400: Wait for current moves to finish @@ -8717,16 +8783,6 @@ Sigma_Exit: } break; - /*! - ### M999 - Restart after being stopped M999: Restart after being stopped by error - @todo Usually doesn't work. Should be fixed or removed. Most of the time, if `Stopped` it set, the print fails and is unrecoverable. - */ - case 999: - Stopped = false; - lcd_reset_alert_level(); - gcode_LastN = Stopped_gcode_LastN; - FlushSerialRequestResend(); - break; /*! #### End of M-Commands */ @@ -8870,7 +8926,7 @@ Sigma_Exit: active_extruder = tmp_extruder; plan_set_position_curposXYZE(); // Move to the old position if 'F' was in the parameters - if (make_move && Stopped == false) { + if (make_move) { prepare_move(); } } @@ -9144,6 +9200,23 @@ Sigma_Exit: }; #endif +#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()); + break; + } +#endif + #ifdef HEATBED_ANALYSIS /*! @@ -9469,7 +9542,7 @@ void mesh_plan_buffer_line(const float &x, const float &y, const float &z, const current_position[Z_AXIS] + t * dz, current_position[E_AXIS] + t * de, feed_rate, extruder, gcode_target); - if (waiting_inside_plan_buffer_line_print_aborted) + if (planner_aborted) return; } } @@ -9871,6 +9944,7 @@ void UnconditionalStop() // Disable all heaters and unroll the temperature wait loop stack disable_heater(); cancel_heatup = true; + heating_status = HeatingStatus::NO_HEATING; // Clear any saved printing state cancel_saved_printing(); @@ -9890,39 +9964,58 @@ void UnconditionalStop() CRITICAL_SECTION_END; } -// Stop: Emergency stop used by overtemp functions which allows recovery +// Emergency stop used by overtemp functions which allows recovery +// WARNING: This function is called *continuously* during a thermal failure. // -// In addition to stopping the print, this prevents subsequent G[0-3] commands to be -// processed via USB (using "Stopped") until the print is resumed via M999 or -// manually started from scratch with the LCD. -// -// Note that the current instruction is completely discarded, so resuming from Stop() -// will introduce either over/under extrusion on the current segment, and will not -// survive a power panic. Switching Stop() to use the pause machinery instead (with -// the addition of disabling the headers) could allow true recovery in the future. -void Stop() +// This either pauses (for thermal model errors) or stops *without recovery* depending on +// "allow_pause". If pause is allowed, this forces a printer-initiated instantanenous pause (just +// like an LCD pause) that bypasses the host pausing functionality. In this state the printer is +// kept in busy state and *must* be recovered from the LCD. +void ThermalStop(bool allow_pause) { - // Keep disabling heaters - disable_heater(); + if(Stopped == false) { + Stopped = true; + if(allow_pause && (IS_SD_PRINTING || usb_timer.running())) { + if (!isPrintPaused) { + // we cannot make a distinction for the host here, the pause must be instantaneous + // so we call the lcd_pause_print to save the print state internally. Thermal errors + // disable heaters and save the original temperatures to saved_*, which will get + // overwritten by stop_and_save_print_to_ram. For this corner-case, re-instate the + // original values after the pause handler is called. + float bed_temp = saved_bed_temperature; + float ext_temp = saved_extruder_temperature; + int fan_speed = saved_fan_speed; + lcd_pause_print(); + saved_bed_temperature = bed_temp; + saved_extruder_temperature = ext_temp; + saved_fan_speed = fan_speed; + } + } else { + // We got a hard thermal error and/or there is no print going on. Just stop. + lcd_print_stop(); - // Call the regular stop function if that's the first time during a new print - if(Stopped == false) { - Stopped = true; - lcd_print_stop(); - Stopped_gcode_LastN = gcode_LastN; // Save last g_code for restart + // Also prevent further menu entry + menu_set_block(MENU_BLOCK_THERMAL_ERROR); + } - // Eventually report the stopped status (though this is usually overridden by a - // higher-priority alert status message) - SERIAL_ERROR_START; - SERIAL_ERRORLNRPGM(MSG_ERR_STOPPED); - LCD_MESSAGERPGM(_T(MSG_STOPPED)); - } + // Report the status on the serial, switch to a busy state + SERIAL_ERROR_START; + SERIAL_ERRORLNRPGM(MSG_ERR_STOPPED); - // Return to the status screen to stop any pending menu action which could have been - // started by the user while stuck in the Stopped state. This also ensures the NEW - // error is immediately shown. - if (menu_menu != lcd_status_screen) - lcd_return_to_status(); + // Eventually report the stopped status on the lcd (though this is usually overridden by a + // higher-priority alert status message) + LCD_MESSAGERPGM(_T(MSG_STOPPED)); + + // Make a warning sound! We cannot use Sound_MakeCustom as this would stop further moves. + // Turn on the speaker here (if not already), and turn it off when back in the main loop. + WRITE(BEEPER, HIGH); + } + + // Return to the status screen to stop any pending menu action which could have been + // started by the user while stuck in the Stopped state. This also ensures the NEW + // error is immediately shown. + if (menu_menu != lcd_status_screen) + lcd_return_to_status(); } bool IsStopped() { return Stopped; }; @@ -10754,20 +10847,27 @@ void long_pause() //long pause print start_pause_print = _millis(); // Stop heaters + heating_status = HeatingStatus::NO_HEATING; setAllTargetHotends(0); - //lift z - current_position[Z_AXIS] += Z_PAUSE_LIFT; - clamp_to_software_endstops(current_position); - plan_buffer_line_curposXYZE(15); + // Lift z + raise_z_above(current_position[Z_AXIS] + Z_PAUSE_LIFT, true); - //Move XY to side - current_position[X_AXIS] = X_PAUSE_POS; - current_position[Y_AXIS] = Y_PAUSE_POS; - plan_buffer_line_curposXYZE(50); + // Move XY to side + if (axis_known_position[X_AXIS] && axis_known_position[Y_AXIS]) { + current_position[X_AXIS] = X_PAUSE_POS; + current_position[Y_AXIS] = Y_PAUSE_POS; + plan_buffer_line_curposXYZE(50); + } - // Turn off the print fan - fanSpeed = 0; + // did we come here from a thermal error? + if(get_temp_error()) { + // time to stop the error beep + WRITE(BEEPER, LOW); + } else { + // Turn off the print fan + fanSpeed = 0; + } } void serialecho_temperatures() { @@ -10871,6 +10971,7 @@ void uvlo_() // Enable stepper driver interrupt to move Z axis. This should be fine as the planner and // command queues are empty, SD card printing is disabled, usb is inhibited. + planner_aborted = false; sei(); // Retract @@ -11003,6 +11104,7 @@ void uvlo_tiny() // Enable stepper driver interrupt to move Z axis. This should be fine as the planner and // command queues are empty, SD card printing is disabled, usb is inhibited. + planner_aborted = false; sei(); // The axis was moved: adjust Z as done on a regular UVLO. @@ -11124,7 +11226,7 @@ void recover_print(uint8_t automatic) { // Set the target bed and nozzle temperatures and wait. sprintf_P(cmd, PSTR("M104 S%d"), target_temperature[active_extruder]); enquecommand(cmd); - sprintf_P(cmd, PSTR("M190 S%d"), target_temperature_bed); + sprintf_P(cmd, PSTR("M140 S%d"), target_temperature_bed); enquecommand(cmd); sprintf_P(cmd, PSTR("M109 S%d"), target_temperature[active_extruder]); enquecommand(cmd); @@ -11472,8 +11574,9 @@ void stop_and_save_print_to_ram(float z_move, float e_move) saved_feedmultiply2 = feedmultiply; //save feedmultiply saved_active_extruder = active_extruder; //save active_extruder saved_extruder_temperature = degTargetHotend(active_extruder); + saved_bed_temperature = degBed(); saved_extruder_relative_mode = axis_relative_modes & E_AXIS_MASK; - saved_fanSpeed = fanSpeed; + saved_fan_speed = fanSpeed; cmdqueue_reset(); //empty cmdqueue card.sdprinting = false; // card.closefile(); @@ -11482,7 +11585,7 @@ void stop_and_save_print_to_ram(float z_move, float e_move) st_reset_timer(); sei(); if ((z_move != 0) || (e_move != 0)) { // extruder or z move -#if 1 + // Rather than calling plan_buffer_line directly, push the move into the command queue so that // the caller can continue processing. This is used during powerpanic to save the state as we // move away from the print. @@ -11514,13 +11617,6 @@ void stop_and_save_print_to_ram(float z_move, float e_move) // If this call is invoked from the main Arduino loop() function, let the caller know that the command // in the command queue is not the original command, but a new one, so it should not be removed from the queue. repeatcommand_front(); -#else - plan_buffer_line(saved_pos[X_AXIS], saved_pos[Y_AXIS], saved_pos[Z_AXIS] + z_move, saved_pos[E_AXIS] + e_move, homing_feedrate[Z_AXIS], active_extruder); - st_synchronize(); //wait moving - memcpy(current_position, saved_pos, sizeof(saved_pos)); - set_destination_to_current(); -#endif - waiting_inside_plan_buffer_line_print_aborted = true; //unroll the stack } } @@ -11543,10 +11639,13 @@ void restore_print_from_ram_and_continue(float e_move) if (fan_check_error == EFCE_FIXED) fan_check_error = EFCE_OK; //reenable serial stream processing if printing from usb #endif -// for (int axis = X_AXIS; axis <= E_AXIS; axis++) -// current_position[axis] = st_get_position_mm(axis); - active_extruder = saved_active_extruder; //restore active_extruder - fanSpeed = saved_fanSpeed; + // restore bed temperature (bed can be disabled during a thermal warning) + if (degBed() != saved_bed_temperature) + setTargetBed(saved_bed_temperature); + + // restore active_extruder + active_extruder = saved_active_extruder; + fanSpeed = saved_fan_speed; if (degTargetHotend(saved_active_extruder) != saved_extruder_temperature) { setTargetHotendSafe(saved_extruder_temperature, saved_active_extruder); @@ -11604,7 +11703,7 @@ void restore_print_from_ram_and_continue(float e_move) lcd_setstatuspgm(MSG_WELCOME); saved_printing_type = PRINTING_TYPE_NONE; saved_printing = false; - waiting_inside_plan_buffer_line_print_aborted = true; //unroll the stack + planner_aborted = true; // unroll the stack } // Cancel the state related to a currently saved print diff --git a/Firmware/Timer.cpp b/Firmware/Timer.cpp index f63bf2969..d0a552b0a 100644 --- a/Firmware/Timer.cpp +++ b/Firmware/Timer.cpp @@ -77,5 +77,11 @@ T Timer::elapsed() { return m_isRunning ? (_millis() - m_started) : 0; } +template +bool Timer::expired_cont(T msPeriod) +{ + return !m_isRunning || expired(msPeriod); +} + template class Timer; template class Timer; diff --git a/Firmware/Timer.h b/Firmware/Timer.h index dcff52232..9cb18a304 100644 --- a/Firmware/Timer.h +++ b/Firmware/Timer.h @@ -21,8 +21,9 @@ public: void start(); void stop(){m_isRunning = false;} bool running()const {return m_isRunning;} - bool expired(T msPeriod); - T elapsed(); + bool expired(T msPeriod); // returns true only once after expiration, then stops running + T elapsed(); // returns the time in milliseconds since the timer was started or 0 otherwise + bool expired_cont(T msPeriod); // return true when continuosly when expired / not running protected: T started()const {return m_started;} private: diff --git a/Firmware/adc.c b/Firmware/adc.c deleted file mode 100644 index b41e58f69..000000000 --- a/Firmware/adc.c +++ /dev/null @@ -1,95 +0,0 @@ -//adc.c - -#include "adc.h" -#include -#include -#include -#include "pins.h" - -uint8_t adc_state; -uint8_t adc_count; -uint16_t adc_values[ADC_CHAN_CNT]; -uint16_t adc_sim_mask; - - -#ifdef ADC_CALLBACK - extern void ADC_CALLBACK(void); -#endif //ADC_CALLBACK - - -void adc_init(void) -{ - puts_P(PSTR("adc_init")); - adc_sim_mask = 0x00; - ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); - ADMUX |= (1 << REFS0); - ADCSRA |= (1 << ADEN); -// ADCSRA |= (1 << ADIF) | (1 << ADSC); - DIDR0 = ((ADC_CHAN_MSK & ADC_DIDR_MSK) & 0xff); - DIDR2 = ((ADC_CHAN_MSK & ADC_DIDR_MSK) >> 8); - adc_reset(); -// adc_sim_mask = 0b0101; -// adc_sim_mask = 0b100101; -// adc_values[0] = 1023 * 16; -// adc_values[2] = 1023 * 16; -// adc_values[5] = 1002 * 16; -} - -void adc_reset(void) -{ - adc_state = 0; - adc_count = 0; - uint8_t i; for (i = 0; i < ADC_CHAN_CNT; i++) - if ((adc_sim_mask & (1 << i)) == 0) - adc_values[i] = 0; -} - -void adc_setmux(uint8_t ch) -{ - ch &= 0x0f; - if (ch & 0x08) ADCSRB |= (1 << MUX5); - else ADCSRB &= ~(1 << MUX5); - ADMUX = (ADMUX & ~(0x07)) | (ch & 0x07); -} - -uint8_t adc_chan(uint8_t index) -{ - uint8_t chan = 0; - uint16_t mask = 1; - while (mask) - { - if ((mask & ADC_CHAN_MSK) && (index-- == 0)) break; - mask <<= 1; - chan++; - } - return chan; -} - -void adc_cycle(void) -{ - if (adc_state & 0x80) - { - uint8_t index = adc_state & 0x0f; - if ((adc_sim_mask & (1 << index)) == 0) - adc_values[index] += ADC; - if (++index >= ADC_CHAN_CNT) - { - index = 0; - adc_count++; - if (adc_count >= ADC_OVRSAMPL) - { -#ifdef ADC_CALLBACK - ADC_CALLBACK(); -#endif //ADC_CALLBACK - adc_reset(); - } - } - adc_setmux(adc_chan(index)); - adc_state = index; - } - else - { - ADCSRA |= (1 << ADSC); //start conversion - adc_state |= 0x80; - } -} diff --git a/Firmware/adc.cpp b/Firmware/adc.cpp new file mode 100644 index 000000000..7503fdcba --- /dev/null +++ b/Firmware/adc.cpp @@ -0,0 +1,81 @@ +#include "adc.h" +#include +#include +#include +#include +#include +#include "pins.h" + +static uint8_t adc_count; //used for oversampling +static uint8_t adc_channel_idx; //bitmask index +volatile uint8_t adc_channel; //regular index +volatile uint16_t adc_values[ADC_CHAN_CNT]; + +static void adc_reset(); +static void adc_setmux(uint8_t ch); + +void adc_init() +{ + puts_P(PSTR("adc_init")); + DIDR0 = ((ADC_CHAN_MSK & ADC_DIDR_MSK) & 0xff); //disable digital inputs PORTF + DIDR2 = ((ADC_CHAN_MSK & ADC_DIDR_MSK) >> 8); //disable digital inputs PORTK + ADMUX |= (1 << REFS0); //use AVCC as reference + + //enable ADC, set prescaler/128, enable interrupt + ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0) | (1 << ADIF) | (1 << ADIE); +} + +static void adc_reset() +{ + static const uint8_t first_channel_idx = 0; + static_assert((1 << first_channel_idx) & ADC_CHAN_MSK); + + ADCSRA &= ~(1 << ADSC); //stop conversion just in case + adc_count = 0; + adc_channel = 0; + adc_channel_idx = first_channel_idx; + adc_setmux(adc_channel_idx); + memset((void*)adc_values, 0, sizeof(adc_values)); +} + +static void adc_setmux(uint8_t ch) +{ + ch &= 0x0f; + if (ch & 0x08) ADCSRB |= (1 << MUX5); + else ADCSRB &= ~(1 << MUX5); + ADMUX = (ADMUX & ~(0x07)) | (ch & 0x07); +} + +void adc_start_cycle() { + adc_reset(); + ADCSRA |= (1 << ADSC); //start conversion +} + +#ifdef ADC_CALLBACK +extern void ADC_CALLBACK(); +#endif //ADC_CALLBACK + +ISR(ADC_vect) +{ + adc_values[adc_channel] += ADC; + if (++adc_count == ADC_OVRSAMPL) + { + // go to the next channel + if (++adc_channel == ADC_CHAN_CNT) { +#ifdef ADC_CALLBACK + ADC_CALLBACK(); +#endif + return; // do not start the next measurement since there are no channels remaining + } + + // find the next channel + while (++adc_channel_idx) { + if (ADC_CHAN_MSK & (1 << adc_channel_idx)) { + adc_setmux(adc_channel_idx); + adc_count = 0; + break; + } + } + } + ADCSRA |= (1 << ADSC); //start conversion +} diff --git a/Firmware/adc.h b/Firmware/adc.h index 9ff137dfd..bece541e8 100644 --- a/Firmware/adc.h +++ b/Firmware/adc.h @@ -1,15 +1,8 @@ -//adc.h -#ifndef _ADC_H -#define _ADC_H +#pragma once #include #include "config.h" - -#if defined(__cplusplus) -extern "C" { -#endif //defined(__cplusplus) - /* http://resnet.uoregon.edu/~gurney_j/jmpc/bitwise.html */ @@ -22,24 +15,9 @@ http://resnet.uoregon.edu/~gurney_j/jmpc/bitwise.html # error "ADC_CHAN_MSK oes not match ADC_CHAN_CNT" #endif -extern uint8_t adc_state; -extern uint8_t adc_count; -extern uint16_t adc_values[ADC_CHAN_CNT]; -extern uint16_t adc_sim_mask; +extern volatile uint8_t adc_channel; +extern volatile uint16_t adc_values[ADC_CHAN_CNT]; - -extern void adc_init(void); - -extern void adc_reset(void); - -extern void adc_setmux(uint8_t ch); - -extern uint8_t adc_chan(uint8_t index); - -extern void adc_cycle(void); - - -#if defined(__cplusplus) -} -#endif //defined(__cplusplus) -#endif //_ADC_H +extern void adc_init(); +extern void adc_start_cycle(); //should be called from an atomic context only +static inline bool adc_cycle_done() { return adc_channel >= ADC_CHAN_CNT; } diff --git a/Firmware/cmdqueue.cpp b/Firmware/cmdqueue.cpp index e53122a4f..5ec79ab00 100755 --- a/Firmware/cmdqueue.cpp +++ b/Firmware/cmdqueue.cpp @@ -28,7 +28,6 @@ ShortTimer serialTimeoutTimer; long gcode_N = 0; long gcode_LastN = 0; -long Stopped_gcode_LastN = 0; uint32_t sdpos_atomic = 0; @@ -464,8 +463,6 @@ void get_command() // Don't parse N again with code_seen('N') cmdbuffer[bufindw + CMDHDRSIZE] = '$'; - //if no errors, continue parsing - gcode_LastN = gcode_N; } // if we don't receive 'N' but still see '*' if ((cmdbuffer[bufindw + CMDHDRSIZE] != 'N') && (cmdbuffer[bufindw + CMDHDRSIZE] != '$') && (strchr(cmdbuffer+bufindw+CMDHDRSIZE, '*') != NULL)) @@ -478,35 +475,48 @@ void get_command() serial_count = 0; return; } + // Handle KILL early, even when Stopped + if(strcmp(cmdbuffer+bufindw+CMDHDRSIZE, "M112") == 0) + kill(MSG_M112_KILL, 2); + // Handle the USB timer if ((strchr_pointer = strchr(cmdbuffer+bufindw+CMDHDRSIZE, 'G')) != NULL) { if (!IS_SD_PRINTING) { usb_timer.start(); } - if (Stopped == true) { - if (code_value_uint8() <= 3) { - SERIAL_ERRORLNRPGM(MSG_ERR_STOPPED); - LCD_MESSAGERPGM(_T(MSG_STOPPED)); - } - } - } // end of 'G' command + } + if (Stopped == true) { + // Stopped can be set either during error states (thermal error: cannot continue), or + // when a printer-initiated action is processed. In such case the printer will send to + // the host an action, but cannot know if the action has been processed while new + // commands are being sent. In this situation we just drop the command while issuing + // periodic "busy" messages in the main loop. Since we're not incrementing the received + // line number, a request for resend will happen (if necessary), ensuring we don't skip + // commands whenever Stopped is cleared and processing resumes. + serial_count = 0; + return; + } + + // Command is complete: store the current line into buffer, move to the next line. - //If command was e-stop process now - if(strcmp(cmdbuffer+bufindw+CMDHDRSIZE, "M112") == 0) - kill(MSG_M112_KILL, 2); - - // Store the current line into buffer, move to the next line. // Store type of entry cmdbuffer[bufindw] = gcode_N ? CMDBUFFER_CURRENT_TYPE_USB_WITH_LINENR : CMDBUFFER_CURRENT_TYPE_USB; + #ifdef CMDBUFFER_DEBUG SERIAL_ECHO_START; SERIAL_ECHOPGM("Storing a command line to buffer: "); SERIAL_ECHO(cmdbuffer+bufindw+CMDHDRSIZE); SERIAL_ECHOLNPGM(""); #endif /* CMDBUFFER_DEBUG */ + + // Store command itself bufindw += strlen(cmdbuffer+bufindw+CMDHDRSIZE) + (1 + CMDHDRSIZE); if (bufindw == sizeof(cmdbuffer)) bufindw = 0; ++ buflen; + + // Update the processed gcode line + gcode_LastN = gcode_N; + #ifdef CMDBUFFER_DEBUG SERIAL_ECHOPGM("Number of commands in the buffer: "); SERIAL_ECHO(buflen); diff --git a/Firmware/cmdqueue.h b/Firmware/cmdqueue.h index 0a573c477..5229b96b0 100644 --- a/Firmware/cmdqueue.h +++ b/Firmware/cmdqueue.h @@ -54,7 +54,6 @@ extern char *strchr_pointer; extern long gcode_N; extern long gcode_LastN; -extern long Stopped_gcode_LastN; extern bool cmdqueue_pop_front(); extern void cmdqueue_reset(); diff --git a/Firmware/config.h b/Firmware/config.h index acd6cb81f..28b85a109 100644 --- a/Firmware/config.h +++ b/Firmware/config.h @@ -6,7 +6,8 @@ #include "pins.h" #if (defined(VOLT_IR_PIN) && defined(IR_SENSOR)) -# define IR_SENSOR_ANALOG +// TODO: IR_SENSOR_ANALOG currently disabled as being incompatible with the new thermal regulation +// # define IR_SENSOR_ANALOG #endif //ADC configuration @@ -20,7 +21,7 @@ #define ADC_CHAN_CNT 8 //number of used channels) #endif //!IR_SENSOR_ANALOG #define ADC_OVRSAMPL 16 //oversampling multiplier -#define ADC_CALLBACK adc_ready //callback function () +#define ADC_CALLBACK adc_callback //callback function () //SWI2C configuration //#define SWI2C_SDA 20 //SDA on P3 diff --git a/Firmware/eeprom.h b/Firmware/eeprom.h index abfe33b58..d3ef0a28f 100644 --- a/Firmware/eeprom.h +++ b/Firmware/eeprom.h @@ -550,8 +550,16 @@ static Sheets * const EEPROM_Sheets_base = (Sheets*)(EEPROM_SHEETS_BASE); #define EEPROM_ECOOL_ENABLE (EEPROM_JOB_ID-1) // uint8_t #define EEPROM_FW_CRASH_FLAG (EEPROM_ECOOL_ENABLE-1) // uint8_t +#define EEPROM_TEMP_MODEL_ENABLE (EEPROM_FW_CRASH_FLAG-1) // uint8_t +#define EEPROM_TEMP_MODEL_P (EEPROM_TEMP_MODEL_ENABLE-4) // float +#define EEPROM_TEMP_MODEL_C (EEPROM_TEMP_MODEL_P-4) // float +#define EEPROM_TEMP_MODEL_R (EEPROM_TEMP_MODEL_C-4*16) // float[16] +#define EEPROM_TEMP_MODEL_Ta_corr (EEPROM_TEMP_MODEL_R-4) // float +#define EEPROM_TEMP_MODEL_W (EEPROM_TEMP_MODEL_Ta_corr-4) // float +#define EEPROM_TEMP_MODEL_E (EEPROM_TEMP_MODEL_W-4) // float + //This is supposed to point to last item to allow EEPROM overrun check. Please update when adding new items. -#define EEPROM_LAST_ITEM EEPROM_FW_CRASH_FLAG +#define EEPROM_LAST_ITEM EEPROM_TEMP_MODEL_E // !!!!! // !!!!! this is end of EEPROM section ... all updates MUST BE inserted before this mark !!!!! // !!!!! diff --git a/Firmware/fancheck.cpp b/Firmware/fancheck.cpp new file mode 100755 index 000000000..421216f37 --- /dev/null +++ b/Firmware/fancheck.cpp @@ -0,0 +1,300 @@ +// fan control and check +#include "fancheck.h" +#include "cardreader.h" +#include "ultralcd.h" +#include "sound.h" +#include "messages.h" +#include "temperature.h" +#include "stepper.h" + +#define FAN_CHECK_PERIOD 5000 //5s +#define FAN_CHECK_DURATION 100 //100ms + +#ifdef FANCHECK +volatile uint8_t fan_check_error = EFCE_OK; +#endif + +#if (defined(EXTRUDER_0_AUTO_FAN_PIN) && EXTRUDER_0_AUTO_FAN_PIN > -1) + #ifdef EXTRUDER_ALTFAN_DETECT + static struct + { + uint8_t isAltfan : 1; + uint8_t altfanOverride : 1; + } altfanStatus; + #endif //EXTRUDER_ALTFAN_DETECT + + unsigned long extruder_autofan_last_check = _millis(); + bool fan_measuring = false; + static uint8_t fanState = 0; +#endif + +#if (defined(EXTRUDER_0_AUTO_FAN_PIN) && EXTRUDER_0_AUTO_FAN_PIN > -1) + #if defined(FAN_PIN) && FAN_PIN > -1 + #if EXTRUDER_0_AUTO_FAN_PIN == FAN_PIN + #error "You cannot set EXTRUDER_0_AUTO_FAN_PIN equal to FAN_PIN" + #endif + #endif + +void setExtruderAutoFanState(uint8_t state) +{ + //If bit 1 is set (0x02), then the extruder fan speed won't be adjusted according to temperature. Useful for forcing + //the fan to either On or Off during certain tests/errors. + + fanState = state; + newFanSpeed = 0; + if (fanState & 0x01) + { +#ifdef EXTRUDER_ALTFAN_DETECT + if (altfanStatus.isAltfan && !altfanStatus.altfanOverride) newFanSpeed = EXTRUDER_ALTFAN_SPEED_SILENT; + else newFanSpeed = EXTRUDER_AUTO_FAN_SPEED; +#else //EXTRUDER_ALTFAN_DETECT + newFanSpeed = EXTRUDER_AUTO_FAN_SPEED; +#endif //EXTRUDER_ALTFAN_DETECT + } + timer4_set_fan0(newFanSpeed); +} + +#if (defined(FANCHECK) && (((defined(TACH_0) && (TACH_0 >-1)) || (defined(TACH_1) && (TACH_1 > -1))))) + +void countFanSpeed() +{ + //SERIAL_ECHOPGM("edge counter 1:"); MYSERIAL.println(fan_edge_counter[1]); + fan_speed[0] = (fan_edge_counter[0] * (float(250) / (_millis() - extruder_autofan_last_check))); + fan_speed[1] = (fan_edge_counter[1] * (float(250) / (_millis() - extruder_autofan_last_check))); + /*SERIAL_ECHOPGM("time interval: "); MYSERIAL.println(_millis() - extruder_autofan_last_check); + SERIAL_ECHOPGM("extruder fan speed:"); MYSERIAL.print(fan_speed[0]); SERIAL_ECHOPGM("; edge counter:"); MYSERIAL.println(fan_edge_counter[0]); + SERIAL_ECHOPGM("print fan speed:"); MYSERIAL.print(fan_speed[1]); SERIAL_ECHOPGM("; edge counter:"); MYSERIAL.println(fan_edge_counter[1]); + SERIAL_ECHOLNPGM(" ");*/ + fan_edge_counter[0] = 0; + fan_edge_counter[1] = 0; +} + +//! Prints serialMsg to serial port, displays lcdMsg onto the LCD and beeps. +//! Extracted from fanSpeedError to save some space. +//! @param serialMsg pointer into PROGMEM, this text will be printed to the serial port +//! @param lcdMsg pointer into PROGMEM, this text will be printed onto the LCD +static void fanSpeedErrorBeep(const char *serialMsg, const char *lcdMsg){ + SERIAL_ECHOLNRPGM(serialMsg); + if (get_message_level() == 0) { + Sound_MakeCustom(200,0,true); + LCD_ALERTMESSAGERPGM(lcdMsg); + } +} + +void fanSpeedError(unsigned char _fan) { + if (fan_check_error == EFCE_REPORTED) return; + fan_check_error = EFCE_REPORTED; + + if (IS_SD_PRINTING || usb_timer.running()) { + // A print is ongoing, pause the print normally + if(!isPrintPaused) { + if (usb_timer.running()) + lcd_pause_usb_print(); + else + lcd_pause_print(); + } + } + else { + // Nothing is going on, but still turn off heaters and report the error + setTargetHotend0(0); + heating_status = HeatingStatus::NO_HEATING; + } + switch (_fan) { + case 0: // extracting the same code from case 0 and case 1 into a function saves 72B + fanSpeedErrorBeep(PSTR("Extruder fan speed is lower than expected"), MSG_FANCHECK_EXTRUDER); + break; + case 1: + fanSpeedErrorBeep(PSTR("Print fan speed is lower than expected"), MSG_FANCHECK_PRINT); + break; + } +} + +void checkFanSpeed() +{ + uint8_t max_fan_errors[2]; +#ifdef FAN_SOFT_PWM + max_fan_errors[1] = 3; // 15 seconds (Print fan) + max_fan_errors[0] = 2; // 10 seconds (Extruder fan) +#else //FAN_SOFT_PWM + max_fan_errors[1] = 15; // 15 seconds (Print fan) + max_fan_errors[0] = 5; // 5 seconds (Extruder fan) +#endif //FAN_SOFT_PWM + + if(fans_check_enabled) + fans_check_enabled = (eeprom_read_byte((uint8_t*)EEPROM_FAN_CHECK_ENABLED) > 0); + static uint8_t fan_speed_errors[2] = { 0,0 }; +#if (defined(FANCHECK) && defined(TACH_0) && (TACH_0 >-1)) + if ((fan_speed[0] < 20) && (current_temperature[0] > EXTRUDER_AUTO_FAN_TEMPERATURE)){ fan_speed_errors[0]++;} + else fan_speed_errors[0] = 0; +#endif +#if (defined(FANCHECK) && defined(TACH_1) && (TACH_1 >-1)) + if ((fan_speed[1] < 5) && ((blocks_queued() ? block_buffer[block_buffer_tail].fan_speed : fanSpeed) > MIN_PRINT_FAN_SPEED)) fan_speed_errors[1]++; + else fan_speed_errors[1] = 0; +#endif + + // drop the fan_check_error flag when both fans are ok + if( fan_speed_errors[0] == 0 && fan_speed_errors[1] == 0 && fan_check_error == EFCE_REPORTED){ + // we may even send some info to the LCD from here + fan_check_error = EFCE_FIXED; + } + if ((fan_check_error == EFCE_FIXED) && !PRINTER_ACTIVE){ + fan_check_error = EFCE_OK; //if the issue is fixed while the printer is doing nothing, reenable processing immediately. + lcd_reset_alert_level(); //for another fan speed error + } + if (fans_check_enabled && (fan_check_error == EFCE_OK)) + { + for (uint8_t fan = 0; fan < 2; fan++) + { + if (fan_speed_errors[fan] > max_fan_errors[fan]) + { + fan_speed_errors[fan] = 0; + fanSpeedError(fan); + } + } + } +} +#endif //(defined(TACH_0) && TACH_0 >-1) || (defined(TACH_1) && TACH_1 > -1) + +#ifdef EXTRUDER_ALTFAN_DETECT +ISR(INT6_vect) { + fan_edge_counter[0]++; +} + +bool extruder_altfan_detect() +{ + setExtruderAutoFanState(3); + + SET_INPUT(TACH_0); + + uint8_t overrideVal = eeprom_read_byte((uint8_t *)EEPROM_ALTFAN_OVERRIDE); + if (overrideVal == EEPROM_EMPTY_VALUE) + { + overrideVal = (calibration_status() == CALIBRATION_STATUS_CALIBRATED) ? 1 : 0; + eeprom_update_byte((uint8_t *)EEPROM_ALTFAN_OVERRIDE, overrideVal); + } + altfanStatus.altfanOverride = overrideVal; + + CRITICAL_SECTION_START; + EICRB &= ~(1 << ISC61); + EICRB |= (1 << ISC60); + EIMSK |= (1 << INT6); + fan_edge_counter[0] = 0; + CRITICAL_SECTION_END; + extruder_autofan_last_check = _millis(); + + _delay(1000); + + EIMSK &= ~(1 << INT6); + + countFanSpeed(); + altfanStatus.isAltfan = fan_speed[0] > 100; + setExtruderAutoFanState(1); + return altfanStatus.isAltfan; +} + +void altfanOverride_toggle() +{ + altfanStatus.altfanOverride = !altfanStatus.altfanOverride; + eeprom_update_byte((uint8_t *)EEPROM_ALTFAN_OVERRIDE, altfanStatus.altfanOverride); +} + +bool altfanOverride_get() +{ + return altfanStatus.altfanOverride; +} + +#endif //EXTRUDER_ALTFAN_DETECT + +void checkExtruderAutoFans() +{ +#if defined(EXTRUDER_0_AUTO_FAN_PIN) && EXTRUDER_0_AUTO_FAN_PIN > -1 + if (!(fanState & 0x02)) + { + fanState &= ~1; + fanState |= current_temperature[0] > EXTRUDER_AUTO_FAN_TEMPERATURE; + fanState |= get_temp_error(); + } + setExtruderAutoFanState(fanState); +#endif +} + +#endif // any extruder auto fan pins set + +#if (defined(FANCHECK) && defined(TACH_0) && (TACH_0 > -1)) +void readFanTach() { +#ifdef FAN_SOFT_PWM + if (READ(TACH_0) != fan_state[0]) { + if(fan_measuring) fan_edge_counter[0] ++; + fan_state[0] = !fan_state[0]; + } +#else //FAN_SOFT_PWM + if (READ(TACH_0) != fan_state[0]) { + fan_edge_counter[0] ++; + fan_state[0] = !fan_state[0]; + } +#endif + //if (READ(TACH_1) != fan_state[1]) { + // fan_edge_counter[1] ++; + // fan_state[1] = !fan_state[1]; + //} +} +#endif //TACH_0 + +void checkFans() +{ +#ifndef DEBUG_DISABLE_FANCHECK +#if (defined(EXTRUDER_0_AUTO_FAN_PIN) && EXTRUDER_0_AUTO_FAN_PIN > -1) + +#ifdef FAN_SOFT_PWM +#ifdef FANCHECK + if ((_millis() - extruder_autofan_last_check > FAN_CHECK_PERIOD) && (!fan_measuring)) { + extruder_autofan_last_check = _millis(); + fanSpeedBckp = fanSpeedSoftPwm; + + if (fanSpeedSoftPwm >= MIN_PRINT_FAN_SPEED) { //if we are in rage where we are doing fan check, set full PWM range for a short time to measure fan RPM by reading tacho signal without modulation by PWM signal + // printf_P(PSTR("fanSpeedSoftPwm 1: %d\n"), fanSpeedSoftPwm); + fanSpeedSoftPwm = 255; + } + fan_measuring = true; + } + if ((_millis() - extruder_autofan_last_check > FAN_CHECK_DURATION) && (fan_measuring)) { + countFanSpeed(); + checkFanSpeed(); + //printf_P(PSTR("fanSpeedSoftPwm 1: %d\n"), fanSpeedSoftPwm); + fanSpeedSoftPwm = fanSpeedBckp; + //printf_P(PSTR("fan PWM: %d; extr fanSpeed measured: %d; print fan speed measured: %d \n"), fanSpeedBckp, fan_speed[0], fan_speed[1]); + extruder_autofan_last_check = _millis(); + fan_measuring = false; + } +#endif //FANCHECK + checkExtruderAutoFans(); +#else //FAN_SOFT_PWM + if(_millis() - extruder_autofan_last_check > 1000) // only need to check fan state very infrequently + { +#if (defined(FANCHECK) && ((defined(TACH_0) && (TACH_0 >-1)) || (defined(TACH_1) && (TACH_1 > -1)))) + countFanSpeed(); + checkFanSpeed(); +#endif //(defined(TACH_0) && TACH_0 >-1) || (defined(TACH_1) && TACH_1 > -1) + checkExtruderAutoFans(); + extruder_autofan_last_check = _millis(); + } +#endif //FAN_SOFT_PWM + +#endif +#endif //DEBUG_DISABLE_FANCHECK +} + +void hotendFanSetFullSpeed() +{ +#ifdef EXTRUDER_ALTFAN_DETECT + altfanStatus.altfanOverride = 1; //full speed +#endif //EXTRUDER_ALTFAN_DETECT + setExtruderAutoFanState(3); + SET_OUTPUT(FAN_PIN); +#ifdef FAN_SOFT_PWM + fanSpeedSoftPwm = 255; +#else //FAN_SOFT_PWM + analogWrite(FAN_PIN, 255); +#endif //FAN_SOFT_PWM + fanSpeed = 255; +} diff --git a/Firmware/fancheck.h b/Firmware/fancheck.h new file mode 100755 index 000000000..312143972 --- /dev/null +++ b/Firmware/fancheck.h @@ -0,0 +1,35 @@ +// fan control and check +#pragma once + +#include "Configuration.h" +#include "config.h" + +#if (defined(FANCHECK) && defined(TACH_0) && (TACH_0 > -1)) +enum { + EFCE_OK = 0, //!< normal operation, both fans are ok + EFCE_FIXED, //!< previous fan error was fixed + EFCE_REPORTED //!< fan error detected and reported to LCD and serial +}; +extern volatile uint8_t fan_check_error; + +void readFanTach(); +#endif //(defined(TACH_0)) + +#ifdef EXTRUDER_ALTFAN_DETECT +extern bool extruder_altfan_detect(); +extern void altfanOverride_toggle(); +extern bool altfanOverride_get(); +#endif //EXTRUDER_ALTFAN_DETECT + +#if (defined(EXTRUDER_0_AUTO_FAN_PIN) && EXTRUDER_0_AUTO_FAN_PIN > -1) +#ifdef FAN_SOFT_PWM +extern bool fan_measuring; +#endif //FAN_SOFT_PWM + +extern unsigned long extruder_autofan_last_check; +void setExtruderAutoFanState(uint8_t state); +void checkExtruderAutoFans(); +#endif + +void checkFans(); +void hotendFanSetFullSpeed(); diff --git a/Firmware/fsensor.cpp b/Firmware/fsensor.cpp index 3eab45383..85728001c 100755 --- a/Firmware/fsensor.cpp +++ b/Firmware/fsensor.cpp @@ -688,62 +688,8 @@ void fsensor_update(void) { if (READ(IR_SENSOR_PIN)) { // IR_SENSOR_PIN ~ H -#ifdef IR_SENSOR_ANALOG - if(!bIRsensorStateFlag) - { - bIRsensorStateFlag=true; - tIRsensorCheckTimer.start(); - } - else - { - if(tIRsensorCheckTimer.expired(IR_SENSOR_STEADY)) - { - uint8_t nMUX1,nMUX2; - uint16_t nADC; - bIRsensorStateFlag=false; - // sequence for direct data reading from AD converter - DISABLE_TEMPERATURE_INTERRUPT(); - nMUX1=ADMUX; // ADMUX saving - nMUX2=ADCSRB; - adc_setmux(VOLT_IR_PIN); - ADCSRA|=(1<4.6V - // If it does, it means a disconnected cables or faulty board - if( (oFsensorPCB == ClFsensorPCB::_Rev04) && ( (nADC*OVERSAMPLENR) > IRsensor_Hopen_TRESHOLD ) ) - { - fsensor_disable(); - fsensor_not_responding = true; - printf_P(PSTR("IR sensor not responding (%d)!\n"),1); - if((ClFsensorActionNA)eeprom_read_byte((uint8_t*)EEPROM_FSENSOR_ACTION_NA)==ClFsensorActionNA::_Pause) - - // if we are printing and FS action is set to "Pause", force pause the print - if(oFsensorActionNA==ClFsensorActionNA::_Pause) - lcd_pause_print(); - } - else - { -#endif //IR_SENSOR_ANALOG - fsensor_checkpoint_print(); - fsensor_enque_M600(); -#ifdef IR_SENSOR_ANALOG - } - } - } - } - else - { // IR_SENSOR_PIN ~ L - bIRsensorStateFlag=false; -#endif //IR_SENSOR_ANALOG + fsensor_checkpoint_print(); + fsensor_enque_M600(); } } #endif //PAT9125 diff --git a/Firmware/macros.h b/Firmware/macros.h index 95a737e1e..aa282dd5e 100644 --- a/Firmware/macros.h +++ b/Firmware/macros.h @@ -11,6 +11,9 @@ #define CRITICAL_SECTION_END SREG = _sreg; #endif //CRITICAL_SECTION_START +#define _REGNAME(registerbase,number,suffix) registerbase##number##suffix +#define _REGNAME_SHORT(registerbase,suffix) registerbase##suffix + // Macros to make a string from a macro #define STRINGIFY_(M) #M #define STRINGIFY(M) STRINGIFY_(M) diff --git a/Firmware/menu.cpp b/Firmware/menu.cpp index 2d5c2c9d1..b4761304f 100755 --- a/Firmware/menu.cpp +++ b/Firmware/menu.cpp @@ -24,7 +24,7 @@ uint8_t menu_data[MENU_DATA_SIZE]; #endif uint8_t menu_depth = 0; -uint8_t menu_block_entering_on_serious_errors = SERIOUS_ERR_NONE; +uint8_t menu_block_mask = MENU_BLOCK_NONE; uint8_t menu_line = 0; uint8_t menu_item = 0; uint8_t menu_row = 0; diff --git a/Firmware/menu.h b/Firmware/menu.h index 54c8a2cbb..31d364836 100755 --- a/Firmware/menu.h +++ b/Firmware/menu.h @@ -29,26 +29,26 @@ extern uint8_t menu_data[MENU_DATA_SIZE]; extern uint8_t menu_depth; -//! definition of serious errors possibly blocking the main menu +//! definition of reasons blocking the main menu //! Use them as bit mask, so that the code may set various errors at the same time enum ESeriousErrors { - SERIOUS_ERR_NONE = 0, - SERIOUS_ERR_MINTEMP_HEATER = 0x01, - SERIOUS_ERR_MINTEMP_BED = 0x02 + MENU_BLOCK_NONE = 0, + MENU_BLOCK_THERMAL_ERROR = 0x01, +#ifdef TEMP_MODEL + MENU_BLOCK_TEMP_MODEL_AUTOTUNE = 0x02, +#endif }; // and possibly others in the future. -//! this is a flag for disabling entering the main menu. If this is set -//! to anything != 0, the only the main status screen will be shown on the -//! LCD and the user will be prevented from entering the menu. -//! Now used only to block doing anything with the printer when there is -//! the infamous MINTEMP error (SERIOUS_ERR_MINTEMP). -extern uint8_t menu_block_entering_on_serious_errors; +//! this is a flag for disabling entering the main menu and longpress. If this is set to anything != +//! 0, the only the main status screen will be shown on the LCD and the user will be prevented from +//! entering the menu. +extern uint8_t menu_block_mask; -//! a pair of macros for manipulating the serious errors +//! a pair of macros for manipulating menu entry //! a c++ class would have been better -#define menu_set_serious_error(x) menu_block_entering_on_serious_errors |= x; -#define menu_unset_serious_error(x) menu_block_entering_on_serious_errors &= ~x; -#define menu_is_serious_error(x) (menu_block_entering_on_serious_errors & x) != 0 +#define menu_set_block(x) menu_block_mask |= x; +#define menu_unset_block(x) menu_block_mask &= ~x; +#define menu_is_blocked(x) (menu_block_mask & x) != 0 extern uint8_t menu_line; extern uint8_t menu_item; diff --git a/Firmware/messages.cpp b/Firmware/messages.cpp index 35a7e3bf8..817890a05 100644 --- a/Firmware/messages.cpp +++ b/Firmware/messages.cpp @@ -159,6 +159,10 @@ const char MSG_IR_04_OR_NEWER[] PROGMEM_I1 = ISTR(" 0.4 or newer");////MSG_IR_04 const char MSG_IR_03_OR_OLDER[] PROGMEM_I1 = ISTR(" 0.3 or older");////MSG_IR_03_OR_OLDER c=18 const char MSG_IR_UNKNOWN[] PROGMEM_I1 = ISTR("unknown state");////MSG_IR_UNKNOWN c=18 #endif +#ifdef TEMP_MODEL +extern const char MSG_THERMAL_ANOMALY[] PROGMEM_I1 = ISTR("THERMAL ANOMALY");////c=20 +extern const char MSG_PAUSED_THERMAL_ERROR[] PROGMEM_I1 = ISTR("PAUSED THERMAL ERROR");////c=20 +#endif //not internationalized messages const char MSG_AUTO_DEPLETE[] PROGMEM_N1 = ISTR("SpoolJoin"); ////MSG_AUTO_DEPLETE c=13 @@ -186,11 +190,11 @@ const char MSG_OK[] PROGMEM_N1 = "ok"; //// const char MSG_SD_OPEN_FILE_FAIL[] PROGMEM_N1 = "open failed, File: "; //// const char MSG_ENDSTOP_OPEN[] PROGMEM_N1 = "open"; //// const char MSG_POWERUP[] PROGMEM_N1 = "PowerUp"; //// -const char MSG_ERR_STOPPED[] PROGMEM_N1 = "Printer stopped due to errors. Fix the error and use M999 to restart. (Temperature is reset. Set it after restarting)"; //// +const char MSG_ERR_STOPPED[] PROGMEM_N1 = "Printer stopped due to errors. Supervision required."; //// const char MSG_ENDSTOP_HIT[] PROGMEM_N1 = "TRIGGERED"; //// -const char MSG_OCTOPRINT_PAUSE[] PROGMEM_N1 = "// action:pause"; //// +const char MSG_OCTOPRINT_ASK_PAUSE[] PROGMEM_N1 = "// action:pause"; //// const char MSG_OCTOPRINT_PAUSED[] PROGMEM_N1 = "// action:paused"; //// -const char MSG_OCTOPRINT_RESUME[] PROGMEM_N1 = "// action:resume"; //// +const char MSG_OCTOPRINT_ASK_RESUME[] PROGMEM_N1 = "// action:resume"; //// const char MSG_OCTOPRINT_RESUMED[] PROGMEM_N1 = "// action:resumed"; //// const char MSG_OCTOPRINT_CANCEL[] PROGMEM_N1 = "// action:cancel"; //// const char MSG_FANCHECK_EXTRUDER[] PROGMEM_N1 = "Err: EXTR. FAN ERROR"; ////c=20 diff --git a/Firmware/messages.h b/Firmware/messages.h index 2c9a4a5fc..c25a25ad7 100644 --- a/Firmware/messages.h +++ b/Firmware/messages.h @@ -168,6 +168,10 @@ extern const char MSG_IR_04_OR_NEWER[]; extern const char MSG_IR_03_OR_OLDER[]; extern const char MSG_IR_UNKNOWN[]; #endif +#ifdef TEMP_MODEL +extern const char MSG_THERMAL_ANOMALY[]; +extern const char MSG_PAUSED_THERMAL_ERROR[]; +#endif //not internationalized messages extern const char MSG_BROWNOUT_RESET[]; @@ -193,9 +197,9 @@ extern const char MSG_ERR_STOPPED[]; extern const char MSG_ENDSTOP_HIT[]; extern const char MSG_EJECT_FILAMENT[]; extern const char MSG_CUT_FILAMENT[]; -extern const char MSG_OCTOPRINT_PAUSE[]; +extern const char MSG_OCTOPRINT_ASK_PAUSE[]; extern const char MSG_OCTOPRINT_PAUSED[]; -extern const char MSG_OCTOPRINT_RESUME[]; +extern const char MSG_OCTOPRINT_ASK_RESUME[]; extern const char MSG_OCTOPRINT_RESUMED[]; extern const char MSG_OCTOPRINT_CANCEL[]; extern const char MSG_FANCHECK_EXTRUDER[]; diff --git a/Firmware/mmu.cpp b/Firmware/mmu.cpp index 3da5c54eb..2ad15375c 100755 --- a/Firmware/mmu.cpp +++ b/Firmware/mmu.cpp @@ -10,6 +10,7 @@ #include "fsensor.h" #include "cardreader.h" #include "cmdqueue.h" +#include "stepper.h" #include "ultralcd.h" #include "menu.h" #include "sound.h" diff --git a/Firmware/motion_control.cpp b/Firmware/motion_control.cpp index e24428944..5b062d3f1 100644 --- a/Firmware/motion_control.cpp +++ b/Firmware/motion_control.cpp @@ -151,7 +151,7 @@ void mc_arc(float* position, float* target, float* offset, float feed_rate, floa // Insert the segment into the buffer plan_buffer_line(position[X_AXIS], position[Y_AXIS], position[Z_AXIS], position[E_AXIS], feed_rate, extruder, position); // Handle the situation where the planner is aborted hard. - if (waiting_inside_plan_buffer_line_print_aborted) + if (planner_aborted) return; } } diff --git a/Firmware/pins_Einsy_1_0.h b/Firmware/pins_Einsy_1_0.h index 3f73bdd9f..d8c316b44 100755 --- a/Firmware/pins_Einsy_1_0.h +++ b/Firmware/pins_Einsy_1_0.h @@ -78,6 +78,9 @@ #define VOLT_IR_PIN 8 //A8 +#define TEMP_TIM 5 + + #define E0_TMC2130_CS 66 #define E0_TMC2130_DIAG 65 #define E0_STEP_PIN 34 diff --git a/Firmware/pins_Rambo_1_0.h b/Firmware/pins_Rambo_1_0.h index 5cd0a6704..1bccd5c57 100644 --- a/Firmware/pins_Rambo_1_0.h +++ b/Firmware/pins_Rambo_1_0.h @@ -57,6 +57,8 @@ #define TEMP_PINDA_PIN 1 //A1 +#define TEMP_TIM 3 + #define E0_STEP_PIN 34 #define E0_DIR_PIN 43 diff --git a/Firmware/pins_Rambo_1_3.h b/Firmware/pins_Rambo_1_3.h index f733c105a..c5f0e4c1c 100644 --- a/Firmware/pins_Rambo_1_3.h +++ b/Firmware/pins_Rambo_1_3.h @@ -60,6 +60,8 @@ #define TEMP_PINDA_PIN 1 //A1 +#define TEMP_TIM 3 + #define E0_STEP_PIN 34 #define E0_DIR_PIN 43 diff --git a/Firmware/planner.cpp b/Firmware/planner.cpp index 16f4dfee8..c3f5262cc 100644 --- a/Firmware/planner.cpp +++ b/Firmware/planner.cpp @@ -55,6 +55,7 @@ #include "planner.h" #include "stepper.h" #include "temperature.h" +#include "fancheck.h" #include "ultralcd.h" #include "language.h" #include "ConfigurationStore.h" @@ -68,6 +69,9 @@ #include "tmc2130.h" #endif //TMC2130 +#include + + //=========================================================================== //=============================public variables ============================ //=========================================================================== @@ -592,17 +596,7 @@ void check_axes_activity() #endif } -bool waiting_inside_plan_buffer_line_print_aborted = false; -/* -void planner_abort_soft() -{ - // Empty the queue. - while (blocks_queued()) plan_discard_current_block(); - // Relay to planner wait routine, that the current line shall be canceled. - waiting_inside_plan_buffer_line_print_aborted = true; - //current_position[i] -} -*/ +bool planner_aborted = false; #ifdef PLANNER_DIAGNOSTICS static inline void planner_update_queue_min_counter() @@ -615,12 +609,8 @@ static inline void planner_update_queue_min_counter() extern volatile uint32_t step_events_completed; // The number of step events executed in the current block -void planner_abort_hard() +void planner_reset_position() { - // Abort the stepper routine and flush the planner queue. - DISABLE_STEPPER_DRIVER_INTERRUPT(); - - // Now the front-end (the Marlin_main.cpp with its current_position) is out of sync. // First update the planner's current position in the physical motor steps. position[X_AXIS] = st_get_position(X_AXIS); position[Y_AXIS] = st_get_position(Y_AXIS); @@ -632,6 +622,7 @@ void planner_abort_hard() current_position[Y_AXIS] = st_get_position_mm(Y_AXIS); current_position[Z_AXIS] = st_get_position_mm(Z_AXIS); current_position[E_AXIS] = st_get_position_mm(E_AXIS); + // Apply the mesh bed leveling correction to the Z axis. #ifdef MESH_BED_LEVELING if (mbl.active) { @@ -664,8 +655,6 @@ void planner_abort_hard() #endif } #endif - // Clear the planner queue, reset and re-enable the stepper timer. - quickStop(); // Apply inverse world correction matrix. machine2world(current_position[X_AXIS], current_position[Y_AXIS]); @@ -673,15 +662,29 @@ void planner_abort_hard() #ifdef LIN_ADVANCE memcpy(position_float, current_position, sizeof(position_float)); #endif +} + +void planner_abort_hard() +{ + // Abort the stepper routine and flush the planner queue. + DISABLE_STEPPER_DRIVER_INTERRUPT(); + + // Now the front-end (the Marlin_main.cpp with its current_position) is out of sync. + planner_reset_position(); + + // Relay to planner wait routine that the current line shall be canceled. + planner_aborted = true; + + // Clear the planner queue, reset and re-enable the stepper timer. + quickStop(); + // Resets planner junction speeds. Assumes start from rest. previous_nominal_speed = 0.0; memset(previous_speed, 0, sizeof(previous_speed)); + // Reset position sync requests plan_reset_next_e_queue = false; plan_reset_next_e_sched = false; - - // Relay to planner wait routine, that the current line shall be canceled. - waiting_inside_plan_buffer_line_print_aborted = true; } void plan_buffer_line_curposXYZE(float feed_rate) { @@ -702,12 +705,11 @@ float junction_deviation = 0.1; // calculation the caller must also provide the physical length of the line in millimeters. void plan_buffer_line(float x, float y, float z, const float &e, float feed_rate, uint8_t extruder, const float* gcode_target) { - // Calculate the buffer head after we push this byte + // Calculate the buffer head after we push this byte uint8_t next_buffer_head = next_block_index(block_buffer_head); - // If the buffer is full: good! That means we are well ahead of the robot. + // If the buffer is full: good! That means we are well ahead of the robot. // Rest here until there is room in the buffer. - waiting_inside_plan_buffer_line_print_aborted = false; if (block_buffer_tail == next_buffer_head) { do { manage_heater(); @@ -715,18 +717,14 @@ void plan_buffer_line(float x, float y, float z, const float &e, float feed_rate manage_inactivity(false); lcd_update(0); } while (block_buffer_tail == next_buffer_head); - if (waiting_inside_plan_buffer_line_print_aborted) { - // Inside the lcd_update(0) routine the print has been aborted. - // Cancel the print, do not plan the current line this routine is waiting on. -#ifdef PLANNER_DIAGNOSTICS - planner_update_queue_min_counter(); -#endif /* PLANNER_DIAGNOSTICS */ - return; - } } #ifdef PLANNER_DIAGNOSTICS planner_update_queue_min_counter(); #endif /* PLANNER_DIAGNOSTICS */ + if(planner_aborted) { + // avoid planning the block early if aborted + return; + } // Prepare to set up new block block_t *block = &block_buffer[block_buffer_head]; @@ -1331,8 +1329,12 @@ Having the real displacement of the head, we can calculate the total movement le if (block->step_event_count.wide <= 32767) block->flag |= BLOCK_FLAG_DDA_LOWRES; - // Move the buffer head. From now the block may be picked up by the stepper interrupt controller. - block_buffer_head = next_buffer_head; + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + // Move the buffer head ensuring the current block hasn't been cancelled from an isr context + // (this is possible both during crash detection *and* uvlo, thus needing a global cli) + if(planner_aborted) return; + block_buffer_head = next_buffer_head; + } // Update position memcpy(position, target, sizeof(target)); // position[] = target[] diff --git a/Firmware/planner.h b/Firmware/planner.h index 72235703b..8534288ea 100644 --- a/Firmware/planner.h +++ b/Firmware/planner.h @@ -255,11 +255,14 @@ FORCE_INLINE bool planner_queue_full() { return block_buffer_tail == next_block_index; } +// Reset machine position from stepper counters +extern void planner_reset_position(); + // Abort the stepper routine, clean up the block queue, // wait for the steppers to stop, // update planner's current position and the current_position of the front end. extern void planner_abort_hard(); -extern bool waiting_inside_plan_buffer_line_print_aborted; +extern bool planner_aborted; #ifdef PREVENT_DANGEROUS_EXTRUDE extern int extrude_min_temp; diff --git a/Firmware/stepper.cpp b/Firmware/stepper.cpp index 34fc19ad2..0b221911d 100644 --- a/Firmware/stepper.cpp +++ b/Firmware/stepper.cpp @@ -287,15 +287,6 @@ ISR(TIMER1_COMPA_vect) { if (sp < SP_min) SP_min = sp; #endif //DEBUG_STACK_MONITOR -#ifdef DEBUG_PULLUP_CRASH - // check for faulty pull-ups enabled on thermistor inputs - if ((PORTF & (uint8_t)(ADC_DIDR_MSK & 0xff)) || (PORTK & (uint8_t)((ADC_DIDR_MSK >> 8) & 0xff))) - pullup_error(false); -#else - PORTF &= ~(uint8_t)(ADC_DIDR_MSK & 0xff); - PORTK &= ~(uint8_t)((ADC_DIDR_MSK >> 8) & 0xff); -#endif // DEBUG_PULLUP_CRASH - #ifdef LIN_ADVANCE advance_isr_scheduler(); #else diff --git a/Firmware/system_timer.h b/Firmware/system_timer.h index 9906460ae..d8ef8a266 100644 --- a/Firmware/system_timer.h +++ b/Firmware/system_timer.h @@ -4,6 +4,7 @@ #define FIRMWARE_SYSTEM_TIMER_H_ #include "Arduino.h" +#include "macros.h" #define SYSTEM_TIMER_2 #ifdef SYSTEM_TIMER_2 @@ -26,4 +27,24 @@ #define timer02_set_pwm0(pwm0) #endif //SYSTEM_TIMER_2 +// Timer counter, incremented by the 1ms Arduino timer. +// The standard Arduino timer() function returns this value atomically +// by disabling / enabling interrupts. This is costly, if the interrupts are known +// to be disabled. +#ifdef SYSTEM_TIMER_2 +extern volatile unsigned long timer2_millis; +#else //SYSTEM_TIMER_2 +extern volatile unsigned long timer0_millis; +#endif //SYSTEM_TIMER_2 + +// An unsynchronized equivalent to a standard Arduino _millis() function. +// To be used inside an interrupt routine. +FORCE_INLINE unsigned long millis_nc() { +#ifdef SYSTEM_TIMER_2 + return timer2_millis; +#else //SYSTEM_TIMER_2 + return timer0_millis; +#endif //SYSTEM_TIMER_2 +} + #endif /* FIRMWARE_SYSTEM_TIMER_H_ */ diff --git a/Firmware/temp_model.h b/Firmware/temp_model.h new file mode 100644 index 000000000..d2afcf973 --- /dev/null +++ b/Firmware/temp_model.h @@ -0,0 +1,115 @@ +// 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 uint8_t TEMP_MODEL_CAL_R_STEP = 4; // Fan interpolation steps during calibration +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 { + +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; // 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; // 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 bool warn_beep = true; // beep on warning threshold +static model_data data; // default heater data + +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 + +namespace temp_model_cal { + +// 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"); + +} // namespace temp_model_cal diff --git a/Firmware/temperature.cpp b/Firmware/temperature.cpp index 6eea8de87..a96eb0f4b 100755 --- a/Firmware/temperature.cpp +++ b/Firmware/temperature.cpp @@ -28,26 +28,73 @@ */ - -#include "Marlin.h" -#include "cmdqueue.h" +#include "temperature.h" +#include "stepper.h" #include "ultralcd.h" #include "menu.h" -#include "conv2str.h" #include "sound.h" -#include "temperature.h" -#include "cardreader.h" +#include "fancheck.h" +#include "messages.h" +#include "language.h" #include "SdFatUtil.h" #include +#include #include "adc.h" #include "ConfigurationStore.h" -#include "messages.h" #include "Timer.h" #include "Configuration_prusa.h" -#include "config.h" +#if (ADC_OVRSAMPL != OVERSAMPLENR) +#error "ADC_OVRSAMPL oversampling must match OVERSAMPLENR" +#endif + +#ifdef SYSTEM_TIMER_2 +#define ENABLE_SOFT_PWM_INTERRUPT() TIMSK2 |= (1< -1) - unsigned long extruder_autofan_last_check = _millis(); - - bool fan_measuring = false; - uint8_t fanState = 0; -#ifdef EXTRUDER_ALTFAN_DETECT - struct - { - uint8_t isAltfan : 1; - uint8_t altfanOverride : 1; - } altfanStatus; -#endif //EXTRUDER_ALTFAN_DETECT -#endif - - #if EXTRUDERS > 3 # error Unsupported number of extruders #elif EXTRUDERS > 2 @@ -171,8 +200,6 @@ uint8_t fanSpeedBckp = 255; # define ARRAY_BY_EXTRUDERS(v1, v2, v3) { v1 } #endif -static ShortTimer oTimer4minTempHeater,oTimer4minTempBed; - // Init min and max temp with extreme values to prevent false errors during startup static int minttemp_raw[EXTRUDERS] = ARRAY_BY_EXTRUDERS( HEATER_0_RAW_LO_TEMP , HEATER_1_RAW_LO_TEMP , HEATER_2_RAW_LO_TEMP ); static int maxttemp_raw[EXTRUDERS] = ARRAY_BY_EXTRUDERS( HEATER_0_RAW_HI_TEMP , HEATER_1_RAW_HI_TEMP , HEATER_2_RAW_HI_TEMP ); @@ -199,7 +226,7 @@ static float analog2tempBed(int raw); #ifdef AMBIENT_MAXTEMP static float analog2tempAmbient(int raw); #endif -static void updateTemperaturesFromRawValues(); +static void updateTemperatures(); enum TempRunawayStates : uint8_t { @@ -226,56 +253,6 @@ static void temp_runaway_check(uint8_t _heater_id, float _target_temperature, fl static void temp_runaway_stop(bool isPreheat, bool isBed); #endif -#ifdef EXTRUDER_ALTFAN_DETECT -ISR(INT6_vect) { - fan_edge_counter[0]++; -} - -bool extruder_altfan_detect() -{ - setExtruderAutoFanState(3); - - SET_INPUT(TACH_0); - - uint8_t overrideVal = eeprom_read_byte((uint8_t *)EEPROM_ALTFAN_OVERRIDE); - if (overrideVal == EEPROM_EMPTY_VALUE) - { - overrideVal = (calibration_status() == CALIBRATION_STATUS_CALIBRATED) ? 1 : 0; - eeprom_update_byte((uint8_t *)EEPROM_ALTFAN_OVERRIDE, overrideVal); - } - altfanStatus.altfanOverride = overrideVal; - - CRITICAL_SECTION_START; - EICRB &= ~(1 << ISC61); - EICRB |= (1 << ISC60); - EIMSK |= (1 << INT6); - fan_edge_counter[0] = 0; - CRITICAL_SECTION_END; - extruder_autofan_last_check = _millis(); - - _delay(1000); - - EIMSK &= ~(1 << INT6); - - countFanSpeed(); - altfanStatus.isAltfan = fan_speed[0] > 100; - setExtruderAutoFanState(1); - return altfanStatus.isAltfan; -} - -void altfanOverride_toggle() -{ - altfanStatus.altfanOverride = !altfanStatus.altfanOverride; - eeprom_update_byte((uint8_t *)EEPROM_ALTFAN_OVERRIDE, altfanStatus.altfanOverride); -} - -bool altfanOverride_get() -{ - return altfanStatus.altfanOverride; -} - -#endif //EXTRUDER_ALTFAN_DETECT - // return "false", if all extruder-heaters are 'off' (ie. "true", if any heater is 'on') bool checkAllHotends(void) { @@ -288,8 +265,9 @@ bool checkAllHotends(void) // codegen bug causing a stack overwrite issue in process_commands() void __attribute__((noinline)) PID_autotune(float temp, int extruder, int ncycles) { + preparePidTuning(); + pid_number_of_cycles = ncycles; - pid_tuning_finished = false; float input = 0.0; pid_cycle=0; bool heating = true; @@ -316,15 +294,13 @@ void __attribute__((noinline)) PID_autotune(float temp, int extruder, int ncycle ||(extruder < 0) #endif ){ - SERIAL_ECHOLN("PID Autotune failed. Bad extruder number."); + SERIAL_ECHOLNPGM("PID Autotune failed. Bad extruder number."); pid_tuning_finished = true; pid_cycle = 0; return; } - SERIAL_ECHOLN("PID Autotune start"); - - disable_heater(); // switch off all heaters. + SERIAL_ECHOLNPGM("PID Autotune start"); if (extruder<0) { @@ -340,15 +316,12 @@ void __attribute__((noinline)) PID_autotune(float temp, int extruder, int ncycle target_temperature[extruder] = (int)temp; // to display the requested target extruder temperature properly on the main screen } - - - - for(;;) { + for(;;) { #ifdef WATCHDOG wdt_reset(); #endif //WATCHDOG if(temp_meas_ready == true) { // temp sample ready - updateTemperaturesFromRawValues(); + updateTemperatures(); input = (extruder<0)?current_temperature_bed:current_temperature[extruder]; @@ -495,6 +468,7 @@ void __attribute__((noinline)) PID_autotune(float temp, int extruder, int ncycle void updatePID() { + // TODO: iState_sum_max and PID values should be synchronized for temp_mgr_isr #ifdef PIDTEMP for(uint_least8_t e = 0; e < EXTRUDERS; e++) { iState_sum_max[e] = PID_INTEGRAL_DRIVE_MAX / cs.Ki; @@ -511,399 +485,109 @@ int getHeaterPower(int heater) { return soft_pwm[heater]; } -#if (defined(EXTRUDER_0_AUTO_FAN_PIN) && EXTRUDER_0_AUTO_FAN_PIN > -1) +// reset PID state after changing target_temperature +void resetPID(uint8_t extruder _UNUSED) {} - #if defined(FAN_PIN) && FAN_PIN > -1 - #if EXTRUDER_0_AUTO_FAN_PIN == FAN_PIN - #error "You cannot set EXTRUDER_0_AUTO_FAN_PIN equal to FAN_PIN" - #endif - #endif - -void setExtruderAutoFanState(uint8_t state) +enum class TempErrorSource : uint8_t { - //If bit 1 is set (0x02), then the extruder fan speed won't be adjusted according to temperature. Useful for forcing - //the fan to either On or Off during certain tests/errors. - - fanState = state; - newFanSpeed = 0; - if (fanState & 0x01) - { -#ifdef EXTRUDER_ALTFAN_DETECT - if (altfanStatus.isAltfan && !altfanStatus.altfanOverride) newFanSpeed = EXTRUDER_ALTFAN_SPEED_SILENT; - else newFanSpeed = EXTRUDER_AUTO_FAN_SPEED; -#else //EXTRUDER_ALTFAN_DETECT - newFanSpeed = EXTRUDER_AUTO_FAN_SPEED; -#endif //EXTRUDER_ALTFAN_DETECT - } - timer4_set_fan0(newFanSpeed); -} - -#if (defined(FANCHECK) && (((defined(TACH_0) && (TACH_0 >-1)) || (defined(TACH_1) && (TACH_1 > -1))))) - -void countFanSpeed() -{ - //SERIAL_ECHOPGM("edge counter 1:"); MYSERIAL.println(fan_edge_counter[1]); - fan_speed[0] = (fan_edge_counter[0] * (float(250) / (_millis() - extruder_autofan_last_check))); - fan_speed[1] = (fan_edge_counter[1] * (float(250) / (_millis() - extruder_autofan_last_check))); - /*SERIAL_ECHOPGM("time interval: "); MYSERIAL.println(_millis() - extruder_autofan_last_check); - SERIAL_ECHOPGM("extruder fan speed:"); MYSERIAL.print(fan_speed[0]); SERIAL_ECHOPGM("; edge counter:"); MYSERIAL.println(fan_edge_counter[0]); - SERIAL_ECHOPGM("print fan speed:"); MYSERIAL.print(fan_speed[1]); SERIAL_ECHOPGM("; edge counter:"); MYSERIAL.println(fan_edge_counter[1]); - SERIAL_ECHOLNPGM(" ");*/ - fan_edge_counter[0] = 0; - fan_edge_counter[1] = 0; -} - -void checkFanSpeed() -{ - uint8_t max_fan_errors[2]; -#ifdef FAN_SOFT_PWM - max_fan_errors[1] = 3; // 15 seconds (Print fan) - max_fan_errors[0] = 2; // 10 seconds (Extruder fan) -#else //FAN_SOFT_PWM - max_fan_errors[1] = 15; // 15 seconds (Print fan) - max_fan_errors[0] = 5; // 5 seconds (Extruder fan) -#endif //FAN_SOFT_PWM - - if(fans_check_enabled) - fans_check_enabled = (eeprom_read_byte((uint8_t*)EEPROM_FAN_CHECK_ENABLED) > 0); - static uint8_t fan_speed_errors[2] = { 0,0 }; -#if (defined(FANCHECK) && defined(TACH_0) && (TACH_0 >-1)) - if ((fan_speed[0] < 20) && (current_temperature[0] > EXTRUDER_AUTO_FAN_TEMPERATURE)){ fan_speed_errors[0]++;} - else fan_speed_errors[0] = 0; + hotend, + bed, +#ifdef AMBIENT_THERMISTOR + ambient, #endif -#if (defined(FANCHECK) && defined(TACH_1) && (TACH_1 >-1)) - if ((fan_speed[1] < 5) && ((blocks_queued() ? block_buffer[block_buffer_tail].fan_speed : fanSpeed) > MIN_PRINT_FAN_SPEED)) fan_speed_errors[1]++; - else fan_speed_errors[1] = 0; +}; + +// thermal error type (in order of decreasing priority!) +enum class TempErrorType : uint8_t +{ + max, + min, + preheat, + runaway, +#ifdef TEMP_MODEL + model, #endif +}; - // drop the fan_check_error flag when both fans are ok - if( fan_speed_errors[0] == 0 && fan_speed_errors[1] == 0 && fan_check_error == EFCE_REPORTED){ - // we may even send some info to the LCD from here - fan_check_error = EFCE_FIXED; - } - if ((fan_check_error == EFCE_FIXED) && !PRINTER_ACTIVE){ - fan_check_error = EFCE_OK; //if the issue is fixed while the printer is doing nothing, reenable processing immediately. - lcd_reset_alert_level(); //for another fan speed error - } - if (fans_check_enabled && (fan_check_error == EFCE_OK)) - { - for (uint8_t fan = 0; fan < 2; fan++) - { - if (fan_speed_errors[fan] > max_fan_errors[fan]) - { - fan_speed_errors[fan] = 0; - fanSpeedError(fan); - } - } - } -} - -//! Prints serialMsg to serial port, displays lcdMsg onto the LCD and beeps. -//! Extracted from fanSpeedError to save some space. -//! @param serialMsg pointer into PROGMEM, this text will be printed to the serial port -//! @param lcdMsg pointer into PROGMEM, this text will be printed onto the LCD -static void fanSpeedErrorBeep(const char *serialMsg, const char *lcdMsg){ - SERIAL_ECHOLNRPGM(serialMsg); - if (get_message_level() == 0) { - Sound_MakeCustom(200,0,true); - LCD_ALERTMESSAGERPGM(lcdMsg); - } -} - -void fanSpeedError(unsigned char _fan) { - if (get_message_level() != 0 && isPrintPaused) return; - //to ensure that target temp. is not set to zero in case that we are resuming print - if (card.sdprinting || usb_timer.running()) { - if (heating_status != HeatingStatus::NO_HEATING) { - lcd_print_stop(); - } - else { - fan_check_error = EFCE_DETECTED; //plans error for next processed command - } - } - else { - // SERIAL_PROTOCOLLNRPGM(MSG_OCTOPRINT_PAUSED); //Why pause octoprint? usb_timer.running() would be true in that case, so there is no need for this. - setTargetHotend0(0); - heating_status = HeatingStatus::NO_HEATING; - fan_check_error = EFCE_REPORTED; - } - switch (_fan) { - case 0: // extracting the same code from case 0 and case 1 into a function saves 72B - fanSpeedErrorBeep(PSTR("Extruder fan speed is lower than expected"), MSG_FANCHECK_EXTRUDER); - break; - case 1: - fanSpeedErrorBeep(PSTR("Print fan speed is lower than expected"), MSG_FANCHECK_PRINT); - break; - } -} -#endif //(defined(TACH_0) && TACH_0 >-1) || (defined(TACH_1) && TACH_1 > -1) - - -void checkExtruderAutoFans() +// error state (updated via set_temp_error from isr context) +volatile static union { -#if defined(EXTRUDER_0_AUTO_FAN_PIN) && EXTRUDER_0_AUTO_FAN_PIN > -1 - if (!(fanState & 0x02)) - { - fanState &= ~1; - fanState |= current_temperature[0] > EXTRUDER_AUTO_FAN_TEMPERATURE; - } - setExtruderAutoFanState(fanState); -#endif -} + uint8_t v; + struct + { + uint8_t error: 1; // error condition + uint8_t assert: 1; // error is still asserted + uint8_t source: 2; // source + uint8_t index: 1; // source index + uint8_t type: 3; // error type + }; +} temp_error_state; -#endif // any extruder auto fan pins set - -// ready for eventually parameters adjusting -void resetPID(uint8_t) // only for compiler-warning elimination (if function do nothing) -//void resetPID(uint8_t extruder) +// set the error type from within the temp_mgr isr to be handled in manager_heater +// - immediately disable all heaters and turn on all fans at full speed +// - prevent the user to set temperatures until all errors are cleared +void set_temp_error(TempErrorSource source, uint8_t index, TempErrorType type) { + // save the original target temperatures for recovery before disabling heaters + if(!temp_error_state.error && !saved_printing) { + saved_bed_temperature = target_temperature_bed; + saved_extruder_temperature = target_temperature[index]; + saved_fan_speed = fanSpeed; + } + + // keep disabling heaters and keep fans on as long as the condition is asserted + disable_heater(); + hotendFanSetFullSpeed(); + + // set the initial error source to the highest priority error + if(!temp_error_state.error || (uint8_t)type < temp_error_state.type) { + temp_error_state.source = (uint8_t)source; + temp_error_state.index = index; + temp_error_state.type = (uint8_t)type; + } + + // always set the error state + temp_error_state.error = true; + temp_error_state.assert = true; } +bool get_temp_error() +{ + return temp_error_state.v; +} + +void handle_temp_error(); + void manage_heater() { #ifdef WATCHDOG wdt_reset(); #endif //WATCHDOG - float pid_input; - float pid_output; + // limit execution to the same rate as temp_mgr (low-level fault handling is already handled - + // any remaining error handling is just user-facing and can wait one extra cycle) + if(!temp_meas_ready) + return; - if(temp_meas_ready != true) //better readability - return; -// more precisely - this condition partially stabilizes time interval for regulation values evaluation (@ ~ 230ms) + // syncronize temperatures with isr + updateTemperatures(); - // 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); +#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 - for(uint8_t e = 0; e < EXTRUDERS; e++) - { + // handle temperature errors + if(temp_error_state.v) + handle_temp_error(); -#ifdef TEMP_RUNAWAY_EXTRUDER_HYSTERESIS - temp_runaway_check(e+1, target_temperature[e], current_temperature[e], (int)soft_pwm[e], false); + // periodically check fans + checkFans(); + +#ifdef TEMP_MODEL_DEBUG + temp_model::log_usr(); #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 - -#define FAN_CHECK_PERIOD 5000 //5s -#define FAN_CHECK_DURATION 100 //100ms - -#ifndef DEBUG_DISABLE_FANCHECK - #if (defined(EXTRUDER_0_AUTO_FAN_PIN) && EXTRUDER_0_AUTO_FAN_PIN > -1) - -#ifdef FAN_SOFT_PWM -#ifdef FANCHECK - if ((_millis() - extruder_autofan_last_check > FAN_CHECK_PERIOD) && (!fan_measuring)) { - extruder_autofan_last_check = _millis(); - fanSpeedBckp = fanSpeedSoftPwm; - - if (fanSpeedSoftPwm >= MIN_PRINT_FAN_SPEED) { //if we are in rage where we are doing fan check, set full PWM range for a short time to measure fan RPM by reading tacho signal without modulation by PWM signal - // printf_P(PSTR("fanSpeedSoftPwm 1: %d\n"), fanSpeedSoftPwm); - fanSpeedSoftPwm = 255; - } - fan_measuring = true; - } - if ((_millis() - extruder_autofan_last_check > FAN_CHECK_DURATION) && (fan_measuring)) { - countFanSpeed(); - checkFanSpeed(); - //printf_P(PSTR("fanSpeedSoftPwm 1: %d\n"), fanSpeedSoftPwm); - fanSpeedSoftPwm = fanSpeedBckp; - //printf_P(PSTR("fan PWM: %d; extr fanSpeed measured: %d; print fan speed measured: %d \n"), fanSpeedBckp, fan_speed[0], fan_speed[1]); - extruder_autofan_last_check = _millis(); - fan_measuring = false; - } -#endif //FANCHECK - checkExtruderAutoFans(); -#else //FAN_SOFT_PWM - if(_millis() - extruder_autofan_last_check > 1000) // only need to check fan state very infrequently - { -#if (defined(FANCHECK) && ((defined(TACH_0) && (TACH_0 >-1)) || (defined(TACH_1) && (TACH_1 > -1)))) - countFanSpeed(); - checkFanSpeed(); -#endif //(defined(TACH_0) && TACH_0 >-1) || (defined(TACH_1) && TACH_1 > -1) - checkExtruderAutoFans(); - extruder_autofan_last_check = _millis(); - } -#endif //FAN_SOFT_PWM - - #endif -#endif //DEBUG_DISABLE_FANCHECK - - #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 } #define PGM_RD_W(x) (short)pgm_read_word(&x) @@ -1029,36 +713,7 @@ 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() -{ - for(uint8_t e=0;e> 2; - current_temperature_pinda = analog2tempBed(current_temperature_raw_pinda); -#endif - -#ifdef AMBIENT_THERMISTOR - current_temperature_ambient = analog2tempAmbient(current_temperature_raw_ambient); //thermistor for ambient is NTCG104LH104JT1 (2000) -#endif - -#ifdef DEBUG_HEATER_BED_SIM - current_temperature_bed = target_temperature_bed; -#else //DEBUG_HEATER_BED_SIM - current_temperature_bed = analog2tempBed(current_temperature_bed_raw); -#endif //DEBUG_HEATER_BED_SIM - - CRITICAL_SECTION_START; - temp_meas_ready = false; - CRITICAL_SECTION_END; -} - -void tp_init() +void soft_pwm_init() { #if MB(RUMBA) && ((TEMP_SENSOR_0==-1)||(TEMP_SENSOR_1==-1)||(TEMP_SENSOR_2==-1)||(TEMP_SENSOR_BED==-1)) //disable RUMBA JTAG in case the thermocouple extension is plugged on top of JTAG connector @@ -1122,20 +777,6 @@ void tp_init() digitalWrite(MAX6675_SS,1); #endif - adc_init(); - - timer0_init(); //enables the heatbed timer. - - // timer2 already enabled earlier in the code - // now enable the COMPB temperature interrupt - OCR2B = 128; - TIMSK2 |= (1< 0) || (defined (TEMP_RUNAWAY_EXTRUDER_HYSTERESIS) && TEMP_RUNAWAY_EXTRUDER_TIMEOUT > 0) -void temp_runaway_check(uint8_t _heater_id, float _target_temperature, float _current_temperature, float _output, bool _isbed) +static void temp_runaway_check(uint8_t _heater_id, float _target_temperature, float _current_temperature, float _output, bool _isbed) { - float __delta; + float __delta; float __hysteresis = 0; uint16_t __timeout = 0; bool temp_runaway_check_active = false; static float __preheat_start[2] = { 0,0}; //currently just bed and one extruder static uint8_t __preheat_counter[2] = { 0,0}; static uint8_t __preheat_errors[2] = { 0,0}; - if (_millis() - temp_runaway_timer[_heater_id] > 2000) { @@ -1325,11 +974,8 @@ void temp_runaway_check(uint8_t _heater_id, float _target_temperature, float _cu } if (__preheat_errors[_heater_id] > ((_isbed) ? 3 : 5)) - { - if (farm_mode) { prusa_statistics(0); } - temp_runaway_stop(true, _isbed); - if (farm_mode) { prusa_statistics(91); } - } + set_temp_error((_isbed?TempErrorSource::bed:TempErrorSource::hotend), _heater_id, TempErrorType::preheat); + __preheat_start[_heater_id] = _current_temperature; __preheat_counter[_heater_id] = 0; } @@ -1366,11 +1012,7 @@ void temp_runaway_check(uint8_t _heater_id, float _target_temperature, float _cu { temp_runaway_error_counter[_heater_id]++; if (temp_runaway_error_counter[_heater_id] * 2 > __timeout) - { - if (farm_mode) { prusa_statistics(0); } - temp_runaway_stop(false, _isbed); - if (farm_mode) { prusa_statistics(90); } - } + set_temp_error((_isbed?TempErrorSource::bed:TempErrorSource::hotend), _heater_id, TempErrorType::runaway); } } } @@ -1378,106 +1020,44 @@ void temp_runaway_check(uint8_t _heater_id, float _target_temperature, float _cu } } -void temp_runaway_stop(bool isPreheat, bool isBed) +static void temp_runaway_stop(bool isPreheat, bool isBed) { - disable_heater(); - Sound_MakeCustom(200,0,true); - - if (isPreheat) - { - lcd_setalertstatuspgm(isBed? PSTR("BED PREHEAT ERROR") : PSTR("PREHEAT ERROR"), LCD_STATUS_CRITICAL); - SERIAL_ERROR_START; - isBed ? SERIAL_ERRORLNPGM(" THERMAL RUNAWAY (PREHEAT HEATBED)") : SERIAL_ERRORLNPGM(" THERMAL RUNAWAY (PREHEAT HOTEND)"); - -#ifdef EXTRUDER_ALTFAN_DETECT - altfanStatus.altfanOverride = 1; //full speed -#endif //EXTRUDER_ALTFAN_DETECT - setExtruderAutoFanState(3); - SET_OUTPUT(FAN_PIN); -#ifdef FAN_SOFT_PWM - fanSpeedSoftPwm = 255; -#else //FAN_SOFT_PWM - analogWrite(FAN_PIN, 255); -#endif //FAN_SOFT_PWM - fanSpeed = 255; - } - else - { - lcd_setalertstatuspgm(isBed? PSTR("BED THERMAL RUNAWAY") : PSTR("THERMAL RUNAWAY"), LCD_STATUS_CRITICAL); - SERIAL_ERROR_START; - isBed ? SERIAL_ERRORLNPGM(" HEATBED THERMAL RUNAWAY") : SERIAL_ERRORLNPGM(" HOTEND THERMAL RUNAWAY"); - } - - Stop(); + if(IsStopped() == false) { + if (isPreheat) { + lcd_setalertstatuspgm(isBed? PSTR("BED PREHEAT ERROR") : PSTR("PREHEAT ERROR"), LCD_STATUS_CRITICAL); + SERIAL_ERROR_START; + if (isBed) { + SERIAL_ERRORLNPGM(" THERMAL RUNAWAY (PREHEAT HEATBED)"); + } else { + SERIAL_ERRORLNPGM(" THERMAL RUNAWAY (PREHEAT HOTEND)"); + } + } else { + lcd_setalertstatuspgm(isBed? PSTR("BED THERMAL RUNAWAY") : PSTR("THERMAL RUNAWAY"), LCD_STATUS_CRITICAL); + SERIAL_ERROR_START; + if (isBed) { + SERIAL_ERRORLNPGM(" HEATBED THERMAL RUNAWAY"); + } else { + SERIAL_ERRORLNPGM(" HOTEND THERMAL RUNAWAY"); + } + } + if (farm_mode) { + prusa_statistics(0); + prusa_statistics(isPreheat? 91 : 90); + } + } + ThermalStop(); } #endif - -void disable_heater() -{ - setAllTargetHotends(0); - setTargetBed(0); - #if defined(TEMP_0_PIN) && TEMP_0_PIN > -1 - target_temperature[0]=0; - soft_pwm[0]=0; - #if defined(HEATER_0_PIN) && HEATER_0_PIN > -1 - WRITE(HEATER_0_PIN,LOW); - #endif - #endif - - #if defined(TEMP_1_PIN) && TEMP_1_PIN > -1 && EXTRUDERS > 1 - target_temperature[1]=0; - soft_pwm[1]=0; - #if defined(HEATER_1_PIN) && HEATER_1_PIN > -1 - WRITE(HEATER_1_PIN,LOW); - #endif - #endif - - #if defined(TEMP_2_PIN) && TEMP_2_PIN > -1 && EXTRUDERS > 2 - target_temperature[2]=0; - soft_pwm[2]=0; - #if defined(HEATER_2_PIN) && HEATER_2_PIN > -1 - WRITE(HEATER_2_PIN,LOW); - #endif - #endif - - #if defined(TEMP_BED_PIN) && TEMP_BED_PIN > -1 - target_temperature_bed=0; - soft_pwm_bed=0; - timer02_set_pwm0(soft_pwm_bed << 1); - bedPWMDisabled = 0; - #if defined(HEATER_BED_PIN) && HEATER_BED_PIN > -1 - //WRITE(HEATER_BED_PIN,LOW); - #endif - #endif -} -//! codes of alert messages for the LCD - it is shorter to compare an uin8_t -//! than raw const char * of the messages themselves. -//! Could be used for MAXTEMP situations too - after reaching MAXTEMP and turning off the heater automagically -//! the heater/bed may cool down and a similar alert message like "MAXTERM fixed..." may be displayed. -enum { LCDALERT_NONE = 0, LCDALERT_HEATERMINTEMP, LCDALERT_BEDMINTEMP, LCDALERT_MINTEMPFIXED, LCDALERT_PLEASERESTART }; - -//! remember the last alert message sent to the LCD -//! to prevent flicker and improve speed -uint8_t last_alert_sent_to_lcd = LCDALERT_NONE; - - -//! update the current temperature error message +//! signal a temperature error on both the lcd and serial //! @param type short error abbreviation (PROGMEM) -void temp_update_messagepgm(const char* PROGMEM type) +//! @param e optional extruder index for hotend errors +static void temp_error_messagepgm(const char* PROGMEM type, uint8_t e = EXTRUDERS) { char msg[LCD_WIDTH]; strcpy_P(msg, PSTR("Err: ")); strcat_P(msg, type); lcd_setalertstatus(msg, LCD_STATUS_CRITICAL); -} - -//! signal a temperature error on both the lcd and serial -//! @param type short error abbreviation (PROGMEM) -//! @param e optional extruder index for hotend errors -void temp_error_messagepgm(const char* PROGMEM type, uint8_t e = EXTRUDERS) -{ - temp_update_messagepgm(type); SERIAL_ERROR_START; @@ -1492,106 +1072,54 @@ void temp_error_messagepgm(const char* PROGMEM type, uint8_t e = EXTRUDERS) } -void max_temp_error(uint8_t e) { - disable_heater(); - if(IsStopped() == false) { - temp_error_messagepgm(PSTR("MAXTEMP"), e); - } - #ifndef BOGUS_TEMPERATURE_FAILSAFE_OVERRIDE - Stop(); - #endif - - SET_OUTPUT(FAN_PIN); - SET_OUTPUT(BEEPER); - WRITE(FAN_PIN, 1); - WRITE(BEEPER, 1); -#ifdef EXTRUDER_ALTFAN_DETECT - altfanStatus.altfanOverride = 1; //full speed -#endif //EXTRUDER_ALTFAN_DETECT - setExtruderAutoFanState(3); - // fanSpeed will consumed by the check_axes_activity() routine. - fanSpeed=255; - if (farm_mode) { prusa_statistics(93); } -} - -void min_temp_error(uint8_t e) { -#ifdef DEBUG_DISABLE_MINTEMP - return; +static void max_temp_error(uint8_t e) { + if(IsStopped() == false) { + temp_error_messagepgm(PSTR("MAXTEMP"), e); + if (farm_mode) prusa_statistics(93); + } +#ifndef BOGUS_TEMPERATURE_FAILSAFE_OVERRIDE + ThermalStop(); #endif - disable_heater(); -//if (current_temperature_ambient < MINTEMP_MINAMBIENT) return; - static const char err[] PROGMEM = "MINTEMP"; - if(IsStopped() == false) { - temp_error_messagepgm(err, e); - last_alert_sent_to_lcd = LCDALERT_HEATERMINTEMP; - } else if( last_alert_sent_to_lcd != LCDALERT_HEATERMINTEMP ){ // only update, if the lcd message is to be changed (i.e. not the same as last time) - // we are already stopped due to some error, only update the status message without flickering - temp_update_messagepgm(err); - last_alert_sent_to_lcd = LCDALERT_HEATERMINTEMP; - } - #ifndef BOGUS_TEMPERATURE_FAILSAFE_OVERRIDE -// if( last_alert_sent_to_lcd != LCDALERT_HEATERMINTEMP ){ -// last_alert_sent_to_lcd = LCDALERT_HEATERMINTEMP; -// lcd_print_stop(); -// } - Stop(); - #endif - if (farm_mode) { prusa_statistics(92); } - } -void bed_max_temp_error(void) { - disable_heater(); - if(IsStopped() == false) { - temp_error_messagepgm(PSTR("MAXTEMP BED")); - } - #ifndef BOGUS_TEMPERATURE_FAILSAFE_OVERRIDE - Stop(); - #endif +static void min_temp_error(uint8_t e) { + static const char err[] PROGMEM = "MINTEMP"; + if(IsStopped() == false) { + temp_error_messagepgm(err, e); + if (farm_mode) prusa_statistics(92); + } + ThermalStop(); } -void bed_min_temp_error(void) { -#ifdef DEBUG_DISABLE_MINTEMP - return; -#endif - disable_heater(); +static void bed_max_temp_error(void) { + if(IsStopped() == false) { + temp_error_messagepgm(PSTR("MAXTEMP BED")); + } + ThermalStop(); +} + +static void bed_min_temp_error(void) { static const char err[] PROGMEM = "MINTEMP BED"; if(IsStopped() == false) { temp_error_messagepgm(err); - last_alert_sent_to_lcd = LCDALERT_BEDMINTEMP; - } else if( last_alert_sent_to_lcd != LCDALERT_BEDMINTEMP ){ // only update, if the lcd message is to be changed (i.e. not the same as last time) - // we are already stopped due to some error, only update the status message without flickering - temp_update_messagepgm(err); - last_alert_sent_to_lcd = LCDALERT_BEDMINTEMP; - } -#ifndef BOGUS_TEMPERATURE_FAILSAFE_OVERRIDE - Stop(); -#endif + } + ThermalStop(); } #ifdef AMBIENT_THERMISTOR -void ambient_max_temp_error(void) { - disable_heater(); +static void ambient_max_temp_error(void) { if(IsStopped() == false) { temp_error_messagepgm(PSTR("MAXTEMP AMB")); } -#ifndef BOGUS_TEMPERATURE_FAILSAFE_OVERRIDE - Stop(); -#endif + ThermalStop(); } -void ambient_min_temp_error(void) { -#ifdef DEBUG_DISABLE_MINTEMP - return; -#endif - disable_heater(); +static void ambient_min_temp_error(void) { if(IsStopped() == false) { temp_error_messagepgm(PSTR("MINTEMP AMB")); } -#ifndef BOGUS_TEMPERATURE_FAILSAFE_OVERRIDE - Stop(); -#endif + ThermalStop(); } #endif @@ -1652,49 +1180,33 @@ int read_max6675() } #endif +#ifdef BABYSTEPPING +FORCE_INLINE static void applyBabysteps() { + for(uint8_t axis=0;axis<3;axis++) + { + int curTodo=babystepsTodo[axis]; //get rid of volatile for performance -extern "C" { - - -void adc_ready(void) //callback from adc when sampling finished -{ - current_temperature_raw[0] = adc_values[ADC_PIN_IDX(TEMP_0_PIN)]; //heater -#ifdef PINDA_THERMISTOR - current_temperature_raw_pinda_fast = adc_values[ADC_PIN_IDX(TEMP_PINDA_PIN)]; -#endif //PINDA_THERMISTOR - current_temperature_bed_raw = adc_values[ADC_PIN_IDX(TEMP_BED_PIN)]; -#ifdef VOLT_PWR_PIN - current_voltage_raw_pwr = adc_values[ADC_PIN_IDX(VOLT_PWR_PIN)]; -#endif -#ifdef AMBIENT_THERMISTOR - current_temperature_raw_ambient = adc_values[ADC_PIN_IDX(TEMP_AMBIENT_PIN)]; // 5->6 -#endif //AMBIENT_THERMISTOR -#ifdef VOLT_BED_PIN - current_voltage_raw_bed = adc_values[ADC_PIN_IDX(VOLT_BED_PIN)]; // 6->9 -#endif -#ifdef IR_SENSOR_ANALOG - current_voltage_raw_IR = adc_values[ADC_PIN_IDX(VOLT_IR_PIN)]; -#endif //IR_SENSOR_ANALOG - temp_meas_ready = true; + if(curTodo>0) + { + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + babystep(axis,/*fwd*/true); + babystepsTodo[axis]--; //less to do next time + } + } + else + if(curTodo<0) + { + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + babystep(axis,/*fwd*/false); + babystepsTodo[axis]++; //less to do next time + } + } + } } +#endif //BABYSTEPPING -} // extern "C" - -FORCE_INLINE static void temperature_isr() +FORCE_INLINE static void soft_pwm_core() { -#ifdef DEBUG_PULLUP_CRASH - // check for faulty pull-ups enabled on thermistor inputs - if ((PORTF & (uint8_t)(ADC_DIDR_MSK & 0xff)) || (PORTK & (uint8_t)((ADC_DIDR_MSK >> 8) & 0xff))) - pullup_error(true); -#else - PORTF &= ~(uint8_t)(ADC_DIDR_MSK & 0xff); - PORTK &= ~(uint8_t)((ADC_DIDR_MSK >> 8) & 0xff); -#endif // DEBUG_PULLUP_CRASH - - - if (!temp_meas_ready) adc_cycle(); - lcd_buttons_update(); - static uint8_t pwm_count = (1 << SOFT_PWM_SCALE); static uint8_t soft_pwm_0; #ifdef SLOW_PWM_HEATERS @@ -2029,36 +1541,22 @@ FORCE_INLINE static void temperature_isr() } //if ((pwm_count % 64) == 0) { #endif //ifndef SLOW_PWM_HEATERS +} + +FORCE_INLINE static void soft_pwm_isr() +{ + lcd_buttons_update(); + soft_pwm_core(); - #ifdef BABYSTEPPING - for(uint8_t axis=0;axis<3;axis++) - { - int curTodo=babystepsTodo[axis]; //get rid of volatile for performance - - if(curTodo>0) - { - CRITICAL_SECTION_START; - babystep(axis,/*fwd*/true); - babystepsTodo[axis]--; //less to do next time - CRITICAL_SECTION_END; - } - else - if(curTodo<0) - { - CRITICAL_SECTION_START; - babystep(axis,/*fwd*/false); - babystepsTodo[axis]++; //less to do next time - CRITICAL_SECTION_END; - } - } + applyBabysteps(); #endif //BABYSTEPPING // Check if a stack overflow happened if (!SdFatUtil::test_stack_integrity()) stack_error(); #if (defined(FANCHECK) && defined(TACH_0) && (TACH_0 > -1)) - check_fans(); + readFanTach(); #endif //(defined(TACH_0)) } @@ -2069,48 +1567,45 @@ ISR(TIMER2_COMPB_vect) ISR(TIMER0_COMPB_vect) #endif //SYSTEM_TIMER_2 { - static bool _lock = false; - if (!_lock) - { - _lock = true; - sei(); - temperature_isr(); - cli(); - _lock = false; + DISABLE_SOFT_PWM_INTERRUPT(); + NONATOMIC_BLOCK(NONATOMIC_FORCEOFF) { + soft_pwm_isr(); } + ENABLE_SOFT_PWM_INTERRUPT(); } -void check_max_temp() +void check_max_temp_raw() { -//heater + //heater #if HEATER_0_RAW_LO_TEMP > HEATER_0_RAW_HI_TEMP if (current_temperature_raw[0] <= maxttemp_raw[0]) { #else if (current_temperature_raw[0] >= maxttemp_raw[0]) { #endif - max_temp_error(0); + set_temp_error(TempErrorSource::hotend, 0, TempErrorType::max); } -//bed + //bed #if defined(BED_MAXTEMP) && (TEMP_SENSOR_BED != 0) #if HEATER_BED_RAW_LO_TEMP > HEATER_BED_RAW_HI_TEMP if (current_temperature_bed_raw <= bed_maxttemp_raw) { #else if (current_temperature_bed_raw >= bed_maxttemp_raw) { #endif - bed_max_temp_error(); + set_temp_error(TempErrorSource::bed, 0, TempErrorType::max); } #endif -//ambient + //ambient #if defined(AMBIENT_MAXTEMP) && (TEMP_SENSOR_AMBIENT != 0) #if AMBIENT_RAW_LO_TEMP > AMBIENT_RAW_HI_TEMP if (current_temperature_raw_ambient <= ambient_maxttemp_raw) { #else if (current_temperature_raw_ambient >= ambient_maxttemp_raw) { #endif - ambient_max_temp_error(); + set_temp_error(TempErrorSource::ambient, 0, TempErrorType::max); } #endif } + //! number of repeating the same state with consecutive step() calls //! used to slow down text switching struct alert_automaton_mintemp { @@ -2122,9 +1617,10 @@ private: States state = States::Init; uint8_t repeat = ALERT_AUTOMATON_SPEED_DIV; - void substep(States next_state){ + void substep(const char* next_msg, States next_state){ if( repeat == 0 ){ state = next_state; // advance to the next state + lcd_setalertstatuspgm(next_msg, LCD_STATUS_CRITICAL); repeat = ALERT_AUTOMATON_SPEED_DIV; // and prepare repeating for it too } else { --repeat; @@ -2139,25 +1635,18 @@ public: switch(state){ case States::Init: // initial state - check hysteresis if( current_temp > mintemp ){ + lcd_setalertstatuspgm(m2, LCD_STATUS_CRITICAL); state = States::TempAboveMintemp; } // otherwise keep the Err MINTEMP alert message on the display, // i.e. do not transfer to state 1 break; case States::TempAboveMintemp: // the temperature has risen above the hysteresis check - lcd_setalertstatuspgm(m2); - substep(States::ShowMintemp); - last_alert_sent_to_lcd = LCDALERT_MINTEMPFIXED; + case States::ShowMintemp: // displaying "MINTEMP fixed" + substep(m1, States::ShowPleaseRestart); break; case States::ShowPleaseRestart: // displaying "Please restart" - lcd_updatestatuspgm(m1); - substep(States::ShowMintemp); - last_alert_sent_to_lcd = LCDALERT_PLEASERESTART; - break; - case States::ShowMintemp: // displaying "MINTEMP fixed" - lcd_updatestatuspgm(m2); - substep(States::ShowPleaseRestart); - last_alert_sent_to_lcd = LCDALERT_MINTEMPFIXED; + substep(m2, States::ShowMintemp); break; } } @@ -2168,23 +1657,12 @@ static alert_automaton_mintemp alert_automaton_hotend(m2hotend), alert_automaton void check_min_temp_heater0() { -//heater #if HEATER_0_RAW_LO_TEMP > HEATER_0_RAW_HI_TEMP if (current_temperature_raw[0] >= minttemp_raw[0]) { #else if (current_temperature_raw[0] <= minttemp_raw[0]) { #endif - menu_set_serious_error(SERIOUS_ERR_MINTEMP_HEATER); - min_temp_error(0); - } else if( menu_is_serious_error(SERIOUS_ERR_MINTEMP_HEATER) ) { - // no recovery, just force the user to restart the printer - // which is a safer variant than just continuing printing - // The automaton also checks for hysteresis - the temperature must have reached a few degrees above the MINTEMP, before - // we shall signalize, that MINTEMP has been fixed - // Code notice: normally the alert_automaton instance would have been placed here - // as static alert_automaton_mintemp alert_automaton_hotend, but - // due to stupid compiler that takes 16 more bytes. - alert_automaton_hotend.step(current_temperature[0], minttemp[0] + TEMP_HYSTERESIS); + set_temp_error(TempErrorSource::hotend, 0, TempErrorType::min); } } @@ -2195,12 +1673,7 @@ void check_min_temp_bed() #else if (current_temperature_bed_raw <= bed_minttemp_raw) { #endif - menu_set_serious_error(SERIOUS_ERR_MINTEMP_BED); - bed_min_temp_error(); - } else if( menu_is_serious_error(SERIOUS_ERR_MINTEMP_BED) ){ - // no recovery, just force the user to restart the printer - // which is a safer variant than just continuing printing - alert_automaton_bed.step(current_temperature_bed, BED_MINTEMP + TEMP_HYSTERESIS); + set_temp_error(TempErrorSource::bed, 0, TempErrorType::min); } } @@ -2212,89 +1685,105 @@ void check_min_temp_ambient() #else if (current_temperature_raw_ambient <= ambient_minttemp_raw) { #endif - ambient_min_temp_error(); + set_temp_error(TempErrorSource::ambient, 0, TempErrorType::min); } } #endif -void check_min_temp() +void handle_temp_error() { -static bool bCheckingOnHeater=false; // state variable, which allows to short no-checking delay (is set, when temperature is (first time) over heaterMintemp) -static bool bCheckingOnBed=false; // state variable, which allows to short no-checking delay (is set, when temperature is (first time) over bedMintemp) + // relay to the original handler + switch((TempErrorType)temp_error_state.type) { + case TempErrorType::min: + switch((TempErrorSource)temp_error_state.source) { + case TempErrorSource::hotend: + if(temp_error_state.assert) { + min_temp_error(temp_error_state.index); + } else { + // no recovery, just force the user to restart the printer + // which is a safer variant than just continuing printing + // The automaton also checks for hysteresis - the temperature must have reached a few degrees above the MINTEMP, before + // we shall signalize, that MINTEMP has been fixed + // Code notice: normally the alert_automaton instance would have been placed here + // as static alert_automaton_mintemp alert_automaton_hotend, but + alert_automaton_hotend.step(current_temperature[0], minttemp[0] + TEMP_HYSTERESIS); + } + break; + case TempErrorSource::bed: + if(temp_error_state.assert) { + bed_min_temp_error(); + } else { + // no recovery, just force the user to restart the printer + // which is a safer variant than just continuing printing + alert_automaton_bed.step(current_temperature_bed, BED_MINTEMP + TEMP_HYSTERESIS); + } + break; #ifdef AMBIENT_THERMISTOR -#ifdef AMBIENT_MINTEMP -check_min_temp_ambient(); + case TempErrorSource::ambient: + ambient_min_temp_error(); + break; #endif -#if AMBIENT_RAW_LO_TEMP > AMBIENT_RAW_HI_TEMP -if(current_temperature_raw_ambient>(OVERSAMPLENR*MINTEMP_MINAMBIENT_RAW)) // thermistor is NTC type -#else -if(current_temperature_raw_ambient=<(OVERSAMPLENR*MINTEMP_MINAMBIENT_RAW)) -#endif - { // ambient temperature is low -#endif //AMBIENT_THERMISTOR -// *** 'common' part of code for MK2.5 & MK3 -// * nozzle checking -if(target_temperature[active_extruder]>minttemp[active_extruder]) - { // ~ nozzle heating is on - bCheckingOnHeater=bCheckingOnHeater||(current_temperature[active_extruder]>(minttemp[active_extruder]+TEMP_HYSTERESIS)); // for eventually delay cutting - if(oTimer4minTempHeater.expired(HEATER_MINTEMP_DELAY)||(!oTimer4minTempHeater.running())||bCheckingOnHeater) - { - bCheckingOnHeater=true; // not necessary - check_min_temp_heater0(); // delay is elapsed or temperature is/was over minTemp => periodical checking is active - } - } -else { // ~ nozzle heating is off - oTimer4minTempHeater.start(); - bCheckingOnHeater=false; - } -// * bed checking -if(target_temperature_bed>BED_MINTEMP) - { // ~ bed heating is on - bCheckingOnBed=bCheckingOnBed||(current_temperature_bed>(BED_MINTEMP+TEMP_HYSTERESIS)); // for eventually delay cutting - if(oTimer4minTempBed.expired(BED_MINTEMP_DELAY)||(!oTimer4minTempBed.running())||bCheckingOnBed) - { - bCheckingOnBed=true; // not necessary - check_min_temp_bed(); // delay is elapsed or temperature is/was over minTemp => periodical checking is active - } - } -else { // ~ bed heating is off - oTimer4minTempBed.start(); - bCheckingOnBed=false; - } -// *** end of 'common' part + } + break; + case TempErrorType::max: + switch((TempErrorSource)temp_error_state.source) { + case TempErrorSource::hotend: + max_temp_error(temp_error_state.index); + break; + case TempErrorSource::bed: + bed_max_temp_error(); + break; #ifdef AMBIENT_THERMISTOR - } -else { // ambient temperature is standard - check_min_temp_heater0(); - check_min_temp_bed(); - } -#endif //AMBIENT_THERMISTOR -} - -#if (defined(FANCHECK) && defined(TACH_0) && (TACH_0 > -1)) -void check_fans() { -#ifdef FAN_SOFT_PWM - if (READ(TACH_0) != fan_state[0]) { - if(fan_measuring) fan_edge_counter[0] ++; - fan_state[0] = !fan_state[0]; - } -#else //FAN_SOFT_PWM - if (READ(TACH_0) != fan_state[0]) { - fan_edge_counter[0] ++; - fan_state[0] = !fan_state[0]; - } + case TempErrorSource::ambient: + ambient_max_temp_error(); + break; #endif - //if (READ(TACH_1) != fan_state[1]) { - // fan_edge_counter[1] ++; - // fan_state[1] = !fan_state[1]; - //} + } + break; + case TempErrorType::preheat: + case TempErrorType::runaway: + switch((TempErrorSource)temp_error_state.source) { + case TempErrorSource::hotend: + case TempErrorSource::bed: + temp_runaway_stop( + ((TempErrorType)temp_error_state.type == TempErrorType::preheat), + ((TempErrorSource)temp_error_state.source == TempErrorSource::bed)); + break; +#ifdef AMBIENT_THERMISTOR + case TempErrorSource::ambient: + // not needed + break; +#endif + } + break; +#ifdef TEMP_MODEL + case TempErrorType::model: + if(temp_error_state.assert) { + if(IsStopped() == false) { + lcd_setalertstatuspgm(MSG_PAUSED_THERMAL_ERROR, LCD_STATUS_CRITICAL); + SERIAL_ECHOLNPGM("TM: error triggered!"); + } + ThermalStop(true); + WRITE(BEEPER, HIGH); + } else { + temp_error_state.v = 0; + WRITE(BEEPER, LOW); + menu_unset_block(MENU_BLOCK_THERMAL_ERROR); + + // hotend error was transitory and disappeared, re-enable bed + if (!target_temperature_bed) + target_temperature_bed = saved_bed_temperature; + + SERIAL_ECHOLNPGM("TM: error cleared"); + } + break; +#endif + } } -#endif //TACH_0 #ifdef PIDTEMP // Apply the scale factors to the PID values - float scalePID_i(float i) { return i*PID_dT; @@ -2345,3 +1834,1056 @@ bool has_temperature_compensation() #endif //PINDA_THERMISTOR +// RAII helper class to run a code block with temp_mgr_isr disabled +class TempMgrGuard +{ + bool temp_mgr_state; + +public: + TempMgrGuard() { + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + temp_mgr_state = TEMP_MGR_INTERRUPT_STATE(); + DISABLE_TEMP_MGR_INTERRUPT(); + } + } + + ~TempMgrGuard() throw() { + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + if(temp_mgr_state) ENABLE_TEMP_MGR_INTERRUPT(); + } + } +}; + +void temp_mgr_init() +{ + // initialize the ADC and start a conversion + adc_init(); + adc_start_cycle(); + + // initialize temperature timer + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + + // CTC + TCCRxB &= ~(1< 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[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[e] < target[e]) { + pid_output = PID_MAX; + } +#endif + + // Check if temperature is within the correct range + if((current < maxttemp[e]) && (target != 0)) + soft_pwm[e] = (int)pid_output >> 1; + else + soft_pwm[e] = 0; +} + +static void pid_bed(const float current, const int target) +{ + float pid_input; + float pid_output; + +#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; + +#ifndef PID_OPENLOOP + pid_error_bed = target - 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, 0, MAX_BED_POWER); +#endif //PID_OPENLOOP + + if(current < 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 < BED_MAXTEMP) + { + if(current >= target) + { + 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 < BED_MAXTEMP) + { + if(current > target + BED_HYSTERESIS) + { + soft_pwm_bed = 0; + timer02_set_pwm0(soft_pwm_bed << 1); + } + else if(current <= target - 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==0) + { + soft_pwm_bed = 0; + timer02_set_pwm0(soft_pwm_bed << 1); + } +#endif //TEMP_SENSOR_BED +} + +// ISR-safe temperatures +static volatile bool adc_values_ready = false; +float current_temperature_isr[EXTRUDERS]; +int target_temperature_isr[EXTRUDERS]; +float current_temperature_bed_isr; +int target_temperature_bed_isr; +#ifdef PINDA_THERMISTOR +float current_temperature_pinda_isr; +#endif +#ifdef AMBIENT_THERMISTOR +float current_temperature_ambient_isr; +#endif + +// ISR callback from adc when sampling finished +void adc_callback() +{ + current_temperature_raw[0] = adc_values[ADC_PIN_IDX(TEMP_0_PIN)]; //heater + current_temperature_bed_raw = adc_values[ADC_PIN_IDX(TEMP_BED_PIN)]; +#ifdef PINDA_THERMISTOR + current_temperature_raw_pinda = adc_values[ADC_PIN_IDX(TEMP_PINDA_PIN)]; +#endif //PINDA_THERMISTOR +#ifdef AMBIENT_THERMISTOR + current_temperature_raw_ambient = adc_values[ADC_PIN_IDX(TEMP_AMBIENT_PIN)]; // 5->6 +#endif //AMBIENT_THERMISTOR +#ifdef VOLT_PWR_PIN + current_voltage_raw_pwr = adc_values[ADC_PIN_IDX(VOLT_PWR_PIN)]; +#endif +#ifdef VOLT_BED_PIN + current_voltage_raw_bed = adc_values[ADC_PIN_IDX(VOLT_BED_PIN)]; // 6->9 +#endif +#ifdef IR_SENSOR_ANALOG + current_voltage_raw_IR = adc_values[ADC_PIN_IDX(VOLT_IR_PIN)]; +#endif //IR_SENSOR_ANALOG + adc_values_ready = true; +} + +static void setCurrentTemperaturesFromIsr() +{ + for(uint8_t e=0;e -1 && EXTRUDERS > 0 + WRITE(HEATER_0_PIN,LOW); +#endif +#if defined(HEATER_1_PIN) && HEATER_1_PIN > -1 && EXTRUDERS > 1 + WRITE(HEATER_1_PIN,LOW); +#endif +#if defined(HEATER_2_PIN) && HEATER_2_PIN > -1 && EXTRUDERS > 2 + WRITE(HEATER_2_PIN,LOW); +#endif +#if defined(HEATER_BED_PIN) && HEATER_BED_PIN > -1 + // TODO: this doesn't take immediate effect! + timer02_set_pwm0(0); + bedPWMDisabled = 0; +#endif + } +} + +static void check_min_temp_raw() +{ + static bool bCheckingOnHeater = false; // state variable, which allows to short no-checking delay (is set, when temperature is (first time) over heaterMintemp) + static bool bCheckingOnBed = false; // state variable, which allows to short no-checking delay (is set, when temperature is (first time) over bedMintemp) + static ShortTimer oTimer4minTempHeater; + static ShortTimer oTimer4minTempBed; + +#ifdef AMBIENT_THERMISTOR +#ifdef AMBIENT_MINTEMP + // we need to check ambient temperature + check_min_temp_ambient(); +#endif +#if AMBIENT_RAW_LO_TEMP > AMBIENT_RAW_HI_TEMP + if(current_temperature_raw_ambient>(OVERSAMPLENR*MINTEMP_MINAMBIENT_RAW)) // thermistor is NTC type +#else + if(current_temperature_raw_ambient=<(OVERSAMPLENR*MINTEMP_MINAMBIENT_RAW)) +#endif + { + // ambient temperature is low +#endif //AMBIENT_THERMISTOR + // *** 'common' part of code for MK2.5 & MK3 + // * nozzle checking + if(target_temperature_isr[active_extruder]>minttemp[active_extruder]) { + // ~ nozzle heating is on + bCheckingOnHeater=bCheckingOnHeater||(current_temperature_isr[active_extruder]>(minttemp[active_extruder]+TEMP_HYSTERESIS)); // for eventually delay cutting + if(oTimer4minTempHeater.expired(HEATER_MINTEMP_DELAY)||(!oTimer4minTempHeater.running())||bCheckingOnHeater) { + bCheckingOnHeater=true; // not necessary + check_min_temp_heater0(); // delay is elapsed or temperature is/was over minTemp => periodical checking is active + } + } + else { + // ~ nozzle heating is off + oTimer4minTempHeater.start(); + bCheckingOnHeater=false; + } + // * bed checking + if(target_temperature_bed_isr>BED_MINTEMP) { + // ~ bed heating is on + bCheckingOnBed=bCheckingOnBed||(current_temperature_bed_isr>(BED_MINTEMP+TEMP_HYSTERESIS)); // for eventually delay cutting + if(oTimer4minTempBed.expired(BED_MINTEMP_DELAY)||(!oTimer4minTempBed.running())||bCheckingOnBed) { + bCheckingOnBed=true; // not necessary + check_min_temp_bed(); // delay is elapsed or temperature is/was over minTemp => periodical checking is active + } + } + else { + // ~ bed heating is off + oTimer4minTempBed.start(); + bCheckingOnBed=false; + } + // *** end of 'common' part +#ifdef AMBIENT_THERMISTOR + } + else { + // ambient temperature is standard + check_min_temp_heater0(); + check_min_temp_bed(); + } +#endif //AMBIENT_THERMISTOR +} + +static void check_temp_raw() +{ + // order is relevant: check_min_temp_raw requires max to be reliable due to + // ambient temperature being used for low handling temperatures + check_max_temp_raw(); + check_min_temp_raw(); +} + +#ifdef TEMP_MODEL +namespace temp_model { + +void model_data::reset(uint8_t heater_pwm, uint8_t fan_pwm, float heater_temp, float ambient_temp) +{ + // 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 + 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) + + 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 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_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 + 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; +} + +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; + } +} + +void handle_warning() +{ + // update values + float warn = data.warn; + float dT_err; + { + TempMgrGuard temp_mgr_guard; + dT_err = warning_state.dT_err; + } + dT_err /= TEMP_MGR_INTV; // per-sample => K/s + + printf_P(PSTR("TM: error |%f|>%f\n"), (double)dT_err, (double)warn); + + static bool first = true; + if(warning_state.assert) { + if (first) { + if(warn_beep) { + lcd_setalertstatuspgm(MSG_THERMAL_ANOMALY, LCD_STATUS_INFO); + WRITE(BEEPER, HIGH); + } + } else { + if(warn_beep) TOGGLE(BEEPER); + } + } else { + // warning cleared, reset state + warning_state.warning = false; + if(warn_beep) WRITE(BEEPER, LOW); + first = true; + } +} + +#ifdef TEMP_MODEL_DEBUG +void log_usr() +{ + if(!log_buf.enabled) return; + + uint8_t counter = log_buf.entry.counter; + if (counter == log_buf.serial) return; + + int8_t delta_ms; + uint8_t cur_pwm; + + // 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 = 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 - 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_b, (unsigned long)cur_amb_b); +} + +void log_isr() +{ + if(!log_buf.enabled) return; + + uint32_t stamp = _millis(); + uint8_t delta_ms = stamp - log_buf.entry.stamp - (TEMP_MGR_INTV * 1000); + log_buf.entry.stamp = stamp; + + ++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_warn_beep(bool enabled) +{ + temp_model::warn_beep = enabled; +} + +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() +{ + SERIAL_ECHO_START; + SERIAL_ECHOLNPGM("Temperature Model settings:"); + 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]); + printf_P(PSTR("%S M310 P%.2f C%.2f S%u B%u E%.2f W%.2f T%.2f\n"), + echomagic, (double)temp_model::data.P, (double)temp_model::data.C, + (unsigned)temp_model::enabled, (unsigned)temp_model::warn_beep, + (double)temp_model::data.err, (double)temp_model::data.warn, + (double)temp_model::data.Ta_corr); +} + +void temp_model_reset_settings() +{ + TempMgrGuard temp_mgr_guard; + + temp_model::data.P = TEMP_MODEL_P; + temp_model::data.C = NAN; + for(uint8_t i = 0; i != TEMP_MODEL_R_SIZE; ++i) + temp_model::data.R[i] = NAN; + temp_model::data.Ta_corr = TEMP_MODEL_Ta_corr; + temp_model::data.warn = TEMP_MODEL_W; + temp_model::data.err = TEMP_MODEL_E; + temp_model::warn_beep = true; + temp_model::enabled = false; +} + +void temp_model_load_settings() +{ + static_assert(TEMP_MODEL_R_SIZE == 16); // ensure we don't desync with the eeprom table + TempMgrGuard temp_mgr_guard; + + temp_model::enabled = eeprom_read_byte((uint8_t*)EEPROM_TEMP_MODEL_ENABLE); + temp_model::data.P = eeprom_read_float((float*)EEPROM_TEMP_MODEL_P); + temp_model::data.C = eeprom_read_float((float*)EEPROM_TEMP_MODEL_C); + for(uint8_t i = 0; i != TEMP_MODEL_R_SIZE; ++i) + temp_model::data.R[i] = eeprom_read_float((float*)EEPROM_TEMP_MODEL_R + i); + temp_model::data.Ta_corr = eeprom_read_float((float*)EEPROM_TEMP_MODEL_Ta_corr); + temp_model::data.warn = eeprom_read_float((float*)EEPROM_TEMP_MODEL_W); + temp_model::data.err = eeprom_read_float((float*)EEPROM_TEMP_MODEL_E); + + if(!temp_model::calibrated()) { + SERIAL_ECHOLNPGM("TM: stored calibration invalid, resetting"); + temp_model_reset_settings(); + } + temp_model::setup(); +} + +void temp_model_save_settings() +{ + eeprom_update_byte((uint8_t*)EEPROM_TEMP_MODEL_ENABLE, temp_model::enabled); + eeprom_update_float((float*)EEPROM_TEMP_MODEL_P, temp_model::data.P); + eeprom_update_float((float*)EEPROM_TEMP_MODEL_C, temp_model::data.C); + for(uint8_t i = 0; i != TEMP_MODEL_R_SIZE; ++i) + eeprom_update_float((float*)EEPROM_TEMP_MODEL_R + i, temp_model::data.R[i]); + eeprom_update_float((float*)EEPROM_TEMP_MODEL_Ta_corr, temp_model::data.Ta_corr); + eeprom_update_float((float*)EEPROM_TEMP_MODEL_W, temp_model::data.warn); + eeprom_update_float((float*)EEPROM_TEMP_MODEL_E, temp_model::data.err); +} + +namespace temp_model_cal { + +void waiting_handler() +{ + manage_heater(); + host_keepalive(); + host_autoreport(); + checkFans(); + lcd_update(0); +} + +void wait(unsigned ms) +{ + unsigned long mark = _millis() + ms; + while(_millis() < mark) { + if(temp_error_state.v) break; + waiting_handler(); + } +} + +void wait_temp() +{ + while(current_temperature[0] < (target_temperature[0] - TEMP_HYSTERESIS)) { + if(temp_error_state.v) break; + waiting_handler(); + } +} + +void cooldown(float temp) +{ + float old_speed = fanSpeedSoftPwm; + fanSpeedSoftPwm = 255; + while(current_temperature[0] >= temp) { + if(temp_error_state.v) break; + float ambient = current_temperature_ambient + temp_model::data.Ta_corr; + if(current_temperature[0] < (ambient + TEMP_HYSTERESIS)) { + // do not get stuck waiting very close to ambient temperature + break; + } + waiting_handler(); + } + fanSpeedSoftPwm = old_speed; +} + +uint16_t record(uint16_t samples = REC_BUFFER_SIZE) { + TempMgrGuard temp_mgr_guard; + + uint16_t pos = 0; + while(pos < samples) { + if(!TEMP_MGR_INT_FLAG_STATE()) { + // temperatures not ready yet, just manage heaters while waiting to reduce jitter + manage_heater(); + continue; + } + TEMP_MGR_INT_FLAG_CLEAR(); + + // manually repeat what the regular isr would do + if(adc_values_ready != true) continue; + adc_values_ready = false; + adc_start_cycle(); + temp_mgr_isr(); + + // stop recording for an hard error condition + if(temp_error_state.v) + return 0; + + // record a new entry + rec_entry& entry = rec_buffer[pos]; + entry.temp = current_temperature_isr[0]; + entry.pwm = soft_pwm[0]; + ++pos; + + // it's now safer to give regular serial/lcd updates a shot + waiting_handler(); + } + + return pos; +} + +float cost_fn(uint16_t samples, float* const var, float v, uint8_t fan_pwm, float ambient) +{ + *var = v; + temp_model::data.reset(rec_buffer[0].pwm, fan_pwm, rec_buffer[0].temp, ambient); + float err = 0; + for(uint16_t i = 1; i < samples; ++i) { + temp_model::data.step(rec_buffer[i].pwm, fan_pwm, rec_buffer[i].temp, ambient); + err += fabsf(temp_model::data.dT_err_prev); + } + return (err / (samples - 1)); +} + +constexpr float GOLDEN_RATIO = 0.6180339887498949; + +void update_section(float points[2], const float bounds[2]) +{ + float d = GOLDEN_RATIO * (bounds[1] - bounds[0]); + points[0] = bounds[0] + d; + points[1] = bounds[1] - d; +} + +float estimate(uint16_t samples, + float* const var, float min, float max, + float thr, uint16_t max_itr, + uint8_t fan_pwm, float ambient) +{ + float orig = *var; + float e = NAN; + float points[2]; + float bounds[2] = {min, max}; + update_section(points, bounds); + + for(uint8_t it = 0; it != max_itr; ++it) { + float c1 = cost_fn(samples, var, points[0], fan_pwm, ambient); + float c2 = cost_fn(samples, var, points[1], fan_pwm, ambient); + bool dir = (c2 < c1); + bounds[dir] = points[!dir]; + update_section(points, bounds); + float x = points[!dir]; + e = (1-GOLDEN_RATIO) * fabsf((bounds[0]-bounds[1]) / x); + + printf_P(PSTR("TM iter:%u v:%.2f e:%.3f\n"), it, x, e); + if(e < thr) { + if(x == min || x == max) { + // real value likely outside of the search boundaries + break; + } + + *var = x; + return e; + } + } + + SERIAL_ECHOLNPGM("TM estimation did not converge"); + *var = orig; + return NAN; +} + +bool autotune(int16_t cal_temp) +{ + uint16_t samples; + float e; + + // bootstrap C/R values without fan + fanSpeedSoftPwm = 0; + + for(uint8_t i = 0; i != 2; ++i) { + const char* PROGMEM verb = (i == 0? PSTR("initial"): PSTR("refining")); + + target_temperature[0] = 0; + if(current_temperature[0] >= TEMP_MODEL_CAL_Tl) { + printf_P(PSTR("TM: cooling down to %dC\n"), TEMP_MODEL_CAL_Tl); + cooldown(TEMP_MODEL_CAL_Tl); + wait(10000); + } + + // we need a valid R value for the initial C guess + if(isnan(temp_model::data.R[0])) + temp_model::data.R[0] = TEMP_MODEL_Rh; + + printf_P(PSTR("TM: %S C estimation\n"), verb); + target_temperature[0] = cal_temp; + samples = record(); + if(temp_error_state.v || !samples) + return true; + + e = estimate(samples, &temp_model::data.C, + TEMP_MODEL_Cl, TEMP_MODEL_Ch, TEMP_MODEL_C_thr, TEMP_MODEL_C_itr, + 0, current_temperature_ambient); + if(isnan(e)) + return true; + + wait_temp(); + if(i) break; // we don't need to refine R + wait(30000); // settle PID regulation + + printf_P(PSTR("TM: %S R estimation @ %dC\n"), verb, cal_temp); + samples = record(); + if(temp_error_state.v || !samples) + return true; + + e = estimate(samples, &temp_model::data.R[0], + TEMP_MODEL_Rl, TEMP_MODEL_Rh, TEMP_MODEL_R_thr, TEMP_MODEL_R_itr, + 0, current_temperature_ambient); + if(isnan(e)) + return true; + } + + // Estimate fan losses at regular intervals, starting from full speed to avoid low-speed + // kickstart issues, although this requires us to wait more for the PID stabilization. + // Normally exhibits logarithmic behavior with the stock fan+shroud, so the shorter interval + // at lower speeds is helpful to increase the resolution of the interpolation. + fanSpeedSoftPwm = 255; + wait(30000); + + for(int8_t i = TEMP_MODEL_R_SIZE - 1; i > 0; i -= TEMP_MODEL_CAL_R_STEP) { + fanSpeedSoftPwm = 256 / TEMP_MODEL_R_SIZE * (i + 1) - 1; + wait(10000); + + printf_P(PSTR("TM: R[%u] estimation\n"), (unsigned)i); + samples = record(); + if(temp_error_state.v || !samples) + return true; + + // a fixed fan pwm (the norminal value) is used here, as soft_pwm_fan will be modified + // during fan measurements and we'd like to include that skew during normal operation. + e = estimate(samples, &temp_model::data.R[i], + TEMP_MODEL_Rl, temp_model::data.R[0], TEMP_MODEL_R_thr, TEMP_MODEL_R_itr, + i, current_temperature_ambient); + if(isnan(e)) + return true; + } + + // interpolate remaining steps to speed-up calibration + // TODO: verify that the sampled values are monotically increasing? + int8_t next = TEMP_MODEL_R_SIZE - 1; + for(uint8_t i = TEMP_MODEL_R_SIZE - 2; i != 0; --i) { + if(!((TEMP_MODEL_R_SIZE - i - 1) % TEMP_MODEL_CAL_R_STEP)) { + next = i; + continue; + } + int8_t prev = next - TEMP_MODEL_CAL_R_STEP; + if(prev < 0) prev = 0; + float f = (float)(i - prev) / TEMP_MODEL_CAL_R_STEP; + float d = (temp_model::data.R[next] - temp_model::data.R[prev]); + temp_model::data.R[i] = temp_model::data.R[prev] + d * f; + } + + return false; +} + +} // namespace temp_model_cal + +void temp_model_autotune(int16_t temp) +{ + if(moves_planned() || printer_active()) { + SERIAL_ECHOLNPGM("TM: printer needs to be idle for calibration"); + return; + } + + // lockout the printer during calibration + KEEPALIVE_STATE(IN_PROCESS); + menu_set_block(MENU_BLOCK_TEMP_MODEL_AUTOTUNE); + lcd_setstatuspgm(_i("Temp. model autotune")); + lcd_return_to_status(); + + // disable the model checking during self-calibration + bool was_enabled = temp_model::enabled; + temp_model_set_enabled(false); + + SERIAL_ECHOLNPGM("TM: autotune start"); + bool err = temp_model_cal::autotune(temp > 0 ? temp : TEMP_MODEL_CAL_Th); + + // always reset temperature + target_temperature[0] = 0; + + if(err) { + SERIAL_ECHOLNPGM("TM: autotune failed"); + lcd_setstatuspgm(_i("TM autotune failed")); + if(temp_error_state.v) + fanSpeedSoftPwm = 255; + } else { + lcd_setstatuspgm(MSG_WELCOME); + fanSpeedSoftPwm = 0; + temp_model_set_enabled(was_enabled); + temp_model_report_settings(); + } + + menu_unset_block(MENU_BLOCK_TEMP_MODEL_AUTOTUNE); +} + +#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.enabled = enable; +} +#endif +#endif diff --git a/Firmware/temperature.h b/Firmware/temperature.h index 3dd1c999a..05251d85f 100755 --- a/Firmware/temperature.h +++ b/Firmware/temperature.h @@ -22,29 +22,13 @@ #define temperature_h #include "Marlin.h" -#include "planner.h" - -#include "stepper.h" - #include "config.h" - -#ifdef SYSTEM_TIMER_2 - -#define ENABLE_TEMPERATURE_INTERRUPT() TIMSK2 |= (1< -1 - extern unsigned char soft_pwm_bed; -#endif - extern bool bedPWMDisabled; #ifdef PIDTEMP extern int pid_cycle, pid_number_of_cycles; extern float _Kp,_Ki,_Kd; - extern bool pid_tuning_finished; float scalePID_i(float i); float scalePID_d(float d); float unscalePID_i(float i); float unscalePID_d(float d); + + bool pidTuningRunning(); // returns true if PID tuning is still running + void preparePidTuning(); // non-blocking call to set "pidTuningRunning" to true immediately #endif @@ -156,8 +138,7 @@ FORCE_INLINE void setTargetHotend(const float &celsius, uint8_t extruder) { static inline void setTargetHotendSafe(const float &celsius, uint8_t extruder) { if (extruder -1)) +void temp_model_autotune(int16_t temp = 0); -enum { - EFCE_OK = 0, //!< normal operation, both fans are ok - EFCE_FIXED, //!< previous fan error was fixed - EFCE_DETECTED, //!< fan error detected, but not reported yet - EFCE_REPORTED //!< fan error detected and reported to LCD and serial -}; -extern volatile uint8_t fan_check_error; +#ifdef TEMP_MODEL_DEBUG +void temp_model_log_enable(bool enable); +#endif +#endif -void countFanSpeed(); -void checkFanSpeed(); -void fanSpeedError(unsigned char _fan); - -void check_fans(); - -#endif //(defined(TACH_0)) - -void check_min_temp(); -void check_max_temp(); - -#ifdef EXTRUDER_ALTFAN_DETECT - extern bool extruder_altfan_detect(); - extern void altfanOverride_toggle(); - extern bool altfanOverride_get(); -#endif //EXTRUDER_ALTFAN_DETECT - -extern unsigned long extruder_autofan_last_check; +#ifdef FAN_SOFT_PWM +extern unsigned char fanSpeedSoftPwm; +#endif extern uint8_t fanSpeedBckp; -extern bool fan_measuring; #endif diff --git a/Firmware/ultralcd.cpp b/Firmware/ultralcd.cpp index 2d28c9f66..662b9e9f2 100755 --- a/Firmware/ultralcd.cpp +++ b/Firmware/ultralcd.cpp @@ -10,7 +10,7 @@ #include "Marlin.h" #include "language.h" #include "cardreader.h" -#include "temperature.h" +#include "fancheck.h" #include "stepper.h" #include "ConfigurationStore.h" #include "printers.h" @@ -94,14 +94,15 @@ static bool lcd_autoDeplete; static float manual_feedrate[] = MANUAL_FEEDRATE; +/* LCD message status */ +static LongTimer lcd_status_message_timeout; +static uint8_t lcd_status_message_level; +static char lcd_status_message[LCD_WIDTH + 1] = WELCOME_MSG; + /* !Configuration settings */ -uint8_t lcd_status_message_level; -char lcd_status_message[LCD_WIDTH + 1] = WELCOME_MSG; - static uint8_t lay1cal_filament = 0; - static const char separator[] PROGMEM = "--------------------"; /** forward declarations **/ @@ -597,7 +598,12 @@ void lcdui_print_status_line(void) break; } } - else if ((IS_SD_PRINTING) && (custom_message_type == CustomMsg::Status)) { // If printing from SD, show what we are printing + else if ((IS_SD_PRINTING) && + (custom_message_type == CustomMsg::Status) && + (lcd_status_message_level <= LCD_STATUS_INFO) && + lcd_status_message_timeout.expired_cont(LCD_STATUS_INFO_TIMEOUT)) + { + // If printing from SD, show what we are printing const char* longFilenameOLD = (card.longFilename[0] ? card.longFilename : card.filename); if(strlen(longFilenameOLD) > LCD_WIDTH) { uint8_t gh = scrollstuff; @@ -860,7 +866,7 @@ void lcd_status_screen() // NOT static due to using ins } if (current_click - && ( menu_block_entering_on_serious_errors == SERIOUS_ERR_NONE ) // or a serious error blocks entering the menu + && ( menu_block_mask == MENU_BLOCK_NONE ) // or a serious error blocks entering the menu ) { menu_depth = 0; //redundant, as already done in lcd_return_to_status(), just to be sure @@ -869,14 +875,34 @@ void lcd_status_screen() // NOT static due to using ins } } +void print_stop(); + void lcd_commands() { + if (planner_aborted) { + // we are still within an aborted command. do not process any LCD command until we return + return; + } + + if (lcd_commands_type == LcdCommands::StopPrint) + { + if (!blocks_queued() && !homing_flag) + { + custom_message_type = CustomMsg::Status; + lcd_setstatuspgm(_T(MSG_PRINT_ABORTED)); + lcd_commands_type = LcdCommands::Idle; + lcd_commands_step = 0; + print_stop(); + } + } + if (lcd_commands_type == LcdCommands::LongPause) { if (!blocks_queued() && !homing_flag) { if (custom_message_type != CustomMsg::M117) { + custom_message_type = CustomMsg::Status; lcd_setstatuspgm(_i("Print paused"));////MSG_PRINT_PAUSED c=20 } lcd_commands_type = LcdCommands::Idle; @@ -1021,14 +1047,14 @@ void lcd_commands() lcd_commands_step = 3; } if (lcd_commands_step == 3 && !blocks_queued()) { //PID calibration + preparePidTuning(); // ensure we don't move to the next step early sprintf_P(cmd1, PSTR("M303 E0 S%3u"), pid_temp); // setting the correct target temperature (for visualization) is done in PID_autotune enquecommand(cmd1); lcd_setstatuspgm(_i("PID cal."));////MSG_PID_RUNNING c=20 lcd_commands_step = 2; } - if (lcd_commands_step == 2 && pid_tuning_finished) { //saving to eeprom - pid_tuning_finished = false; + if (lcd_commands_step == 2 && !pidTuningRunning()) { //saving to eeprom custom_message_state = 0; lcd_setstatuspgm(_i("PID cal. finished"));////MSG_PID_FINISHED c=20 setAllTargetHotends(0); // reset all hotends temperature including the number displayed on the main screen @@ -1067,18 +1093,19 @@ void lcd_return_to_status() void lcd_pause_print() { stop_and_save_print_to_ram(0.0, -default_retraction); - lcd_return_to_status(); + + SERIAL_ECHOLNRPGM(MSG_OCTOPRINT_PAUSED); isPrintPaused = true; - if (LcdCommands::Idle == lcd_commands_type) { - lcd_commands_type = LcdCommands::LongPause; - } - SERIAL_PROTOCOLLNRPGM(MSG_OCTOPRINT_PAUSED); + + // return to status is required to continue processing in the main loop! + lcd_commands_type = LcdCommands::LongPause; + lcd_return_to_status(); } //! @brief Send host action "pause" void lcd_pause_usb_print() { - SERIAL_PROTOCOLLNRPGM(MSG_OCTOPRINT_PAUSE); + SERIAL_PROTOCOLLNRPGM(MSG_OCTOPRINT_ASK_PAUSE); } static void lcd_move_menu_axis(); @@ -5665,22 +5692,37 @@ static bool fan_error_selftest() return 0; } +bool resume_print_checks() { + // reset the lcd status so that a newer error will be shown + lcd_return_to_status(); + lcd_reset_alert_level(); + + // ensure thermal issues (temp or fan) are resolved before we allow to resume + if (get_temp_error() +#ifdef FANCHECK + || fan_error_selftest() +#endif + ) { + return false; // abort if error persists + } + + return true; +} + //! @brief Resume paused print, send host action "resumed" //! @todo It is not good to call restore_print_from_ram_and_continue() from function called by lcd_update(), //! as restore_print_from_ram_and_continue() calls lcd_update() internally. void lcd_resume_print() { - lcd_return_to_status(); - lcd_reset_alert_level(); //for fan speed error - if (fan_error_selftest()) { - if (usb_timer.running()) SERIAL_PROTOCOLLNRPGM(MSG_OCTOPRINT_PAUSED); - return; //abort if error persists - } + // reset lcd and ensure we can resume first + if (!resume_print_checks()) return; + cmdqueue_serial_disabled = false; lcd_setstatuspgm(_T(MSG_FINISHING_MOVEMENTS)); st_synchronize(); custom_message_type = CustomMsg::Resuming; isPrintPaused = false; + Stopped = false; // resume processing USB commands again restore_print_from_ram_and_continue(default_retraction); pause_time += (_millis() - start_pause_print); //accumulate time when print is paused for correct statistics calculation refresh_cmd_timeout(); @@ -5691,7 +5733,11 @@ void lcd_resume_print() //! @brief Resume paused USB/host print, send host action "resume" void lcd_resume_usb_print() { - SERIAL_PROTOCOLLNRPGM(MSG_OCTOPRINT_RESUME); //resume octoprint + // reset lcd and ensure we can resume first + if (!resume_print_checks()) return; + + // resume the usb host + SERIAL_PROTOCOLLNRPGM(MSG_OCTOPRINT_ASK_RESUME); } static void change_sheet() @@ -5872,14 +5918,16 @@ static void lcd_main_menu() } if(isPrintPaused) { + // only allow resuming if hardware errors (temperature or fan) are cleared + if(!get_temp_error() #ifdef FANCHECK - if((fan_check_error == EFCE_FIXED) || (fan_check_error == EFCE_OK)) + && ((fan_check_error == EFCE_FIXED) || (fan_check_error == EFCE_OK)) #endif //FANCHECK - { - if (usb_timer.running()) { - MENU_ITEM_SUBMENU_P(_T(MSG_RESUME_PRINT), lcd_resume_usb_print); - } else { + ) { + if (saved_printing) { MENU_ITEM_SUBMENU_P(_T(MSG_RESUME_PRINT), lcd_resume_print); + } else { + MENU_ITEM_SUBMENU_P(_T(MSG_RESUME_PRINT), lcd_resume_usb_print); } } } @@ -6286,56 +6334,61 @@ static void lcd_sd_updir() menu_data_reset(); //Forces reloading of cached variables. } -void lcd_print_stop() +// continue stopping the print from the main loop after lcd_print_stop() is called +void print_stop() { - if (!card.sdprinting) { - SERIAL_ECHOLNRPGM(MSG_OCTOPRINT_CANCEL); // for Octoprint - } - UnconditionalStop(); + // save printing time + stoptime = _millis(); + unsigned long t = (stoptime - starttime - pause_time) / 1000; //time in s + save_statistics(total_filament_used, t); - // TODO: all the following should be moved in the main marlin loop! -#ifdef MESH_BED_LEVELING - mbl.active = false; //also prevents undoing the mbl compensation a second time in the second planner_abort_hard() -#endif + // lift Z + raise_z_above(current_position[Z_AXIS] + 10, true); - lcd_setstatuspgm(_T(MSG_PRINT_ABORTED)); - stoptime = _millis(); - unsigned long t = (stoptime - starttime - pause_time) / 1000; //time in s - pause_time = 0; - save_statistics(total_filament_used, t); - - // reset current command - lcd_commands_step = 0; - lcd_commands_type = LcdCommands::Idle; - - lcd_cooldown(); //turns off heaters and fan; goes to status screen. - - if (axis_known_position[Z_AXIS]) { - current_position[Z_AXIS] += Z_CANCEL_LIFT; - clamp_to_software_endstops(current_position); - plan_buffer_line_curposXYZE(manual_feedrate[Z_AXIS] / 60); - } - - if (axis_known_position[X_AXIS] && axis_known_position[Y_AXIS]) //if axis are homed, move to parked position. - { + // if axis are homed, move to parking position. + if (axis_known_position[X_AXIS] && axis_known_position[Y_AXIS]) { current_position[X_AXIS] = X_CANCEL_POS; current_position[Y_AXIS] = Y_CANCEL_POS; plan_buffer_line_curposXYZE(manual_feedrate[0] / 60); } st_synchronize(); + // did we come here from a thermal error? + if(get_temp_error()) { + // time to stop the error beep + WRITE(BEEPER, LOW); + } else { + // Turn off the print fan + fanSpeed = 0; + } + if (mmu_enabled) extr_unload(); //M702 C - finishAndDisableSteppers(); //M84 - - lcd_setstatuspgm(MSG_WELCOME); - custom_message_type = CustomMsg::Status; - - planner_abort_hard(); //needs to be done since plan_buffer_line resets waiting_inside_plan_buffer_line_print_aborted to false. Also copies current to destination. - axis_relative_modes = E_AXIS_MASK; //XYZ absolute, E relative - - isPrintPaused = false; //clear isPrintPaused flag to allow starting next print after pause->stop scenario. +} + +void lcd_print_stop() +{ + // UnconditionalStop() will internally cause planner_abort_hard(), meaning we _cannot_ plan + // any more move in this call! Any further move must happen inside print_stop(), which is called + // by the main loop one iteration later. + UnconditionalStop(); + + if (!card.sdprinting) { + SERIAL_ECHOLNRPGM(MSG_OCTOPRINT_CANCEL); // for Octoprint + } + +#ifdef MESH_BED_LEVELING + mbl.active = false; +#endif + + // clear any pending paused state immediately + pause_time = 0; + isPrintPaused = false; + + // return to status is required to continue processing in the main loop! + lcd_commands_type = LcdCommands::StopPrint; + lcd_return_to_status(); } void lcd_sdcard_stop() @@ -7952,57 +8005,70 @@ void lcd_finishstatus() { } -void lcd_setstatus(const char* message) +static bool lcd_message_check(uint8_t priority) { - if (lcd_status_message_level > 0) - return; - lcd_updatestatus(message); + // regular priority check + if (priority >= lcd_status_message_level) + return true; + + // check if we can override an info message yet + if (lcd_status_message_level == LCD_STATUS_INFO) { + return lcd_status_message_timeout.expired_cont(LCD_STATUS_INFO_TIMEOUT); + } + + return false; } -void lcd_updatestatuspgm(const char *message){ - strncpy_P(lcd_status_message, message, LCD_WIDTH); +static void lcd_updatestatus(const char *message, bool progmem = false) +{ + if (progmem) + strncpy_P(lcd_status_message, message, LCD_WIDTH); + else + strncpy(lcd_status_message, message, LCD_WIDTH); + lcd_status_message[LCD_WIDTH] = 0; lcd_finishstatus(); // hack lcd_draw_update to 1, i.e. without clear lcd_draw_update = 1; } +void lcd_setstatus(const char* message) +{ + if (lcd_message_check(LCD_STATUS_NONE)) + lcd_updatestatus(message); +} + void lcd_setstatuspgm(const char* message) { - if (lcd_status_message_level > 0) - return; - lcd_updatestatuspgm(message); + if (lcd_message_check(LCD_STATUS_NONE)) + lcd_updatestatus(message, true); } -void lcd_updatestatus(const char *message){ - strncpy(lcd_status_message, message, LCD_WIDTH); - lcd_status_message[LCD_WIDTH] = 0; - lcd_finishstatus(); - // hack lcd_draw_update to 1, i.e. without clear - lcd_draw_update = 1; -} - -void lcd_setalertstatuspgm(const char* message, uint8_t severity) +void lcd_setalertstatus_(const char* message, uint8_t severity, bool progmem) { - if (severity > lcd_status_message_level) { - lcd_updatestatuspgm(message); - lcd_status_message_level = severity; - lcd_return_to_status(); - } + if (lcd_message_check(severity)) { + lcd_updatestatus(message, progmem); + lcd_status_message_timeout.start(); + lcd_status_message_level = severity; + custom_message_type = CustomMsg::Status; + custom_message_state = 0; + lcd_return_to_status(); + } } void lcd_setalertstatus(const char* message, uint8_t severity) { - if (severity > lcd_status_message_level) { - lcd_updatestatus(message); - lcd_status_message_level = severity; - lcd_return_to_status(); - } + lcd_setalertstatus_(message, severity, false); +} + +void lcd_setalertstatuspgm(const char* message, uint8_t severity) +{ + lcd_setalertstatus_(message, severity, true); } void lcd_reset_alert_level() { - lcd_status_message_level = 0; + lcd_status_message_level = 0; } uint8_t get_message_level() @@ -8013,9 +8079,9 @@ uint8_t get_message_level() void menu_lcd_longpress_func(void) { backlight_wake(); - if (homing_flag || mesh_bed_leveling_flag || menu_menu == lcd_babystep_z || menu_menu == lcd_move_z) + if (homing_flag || mesh_bed_leveling_flag || menu_menu == lcd_babystep_z || menu_menu == lcd_move_z || menu_block_mask != MENU_BLOCK_NONE) { - // disable longpress during re-entry, while homing or calibration + // disable longpress during re-entry, while homing, calibration or if a serious error lcd_quick_feedback(); return; } diff --git a/Firmware/ultralcd.h b/Firmware/ultralcd.h index b7f63ac97..d3c94e897 100755 --- a/Firmware/ultralcd.h +++ b/Firmware/ultralcd.h @@ -9,14 +9,19 @@ extern void menu_lcd_lcdupdate_func(void); // Call with a false parameter to suppress the LCD update from various places like the planner or the temp control. void ultralcd_init(); -void lcd_setstatus(const char* message); -void lcd_setstatuspgm(const char* message); //! LCD status severities -#define LCD_STATUS_CRITICAL 2 //< Heater failure -#define LCD_STATUS_ALERT 1 //< Other hardware issue +#define LCD_STATUS_CRITICAL 3 //< Heater failure +#define LCD_STATUS_ALERT 2 //< Other hardware issue +#define LCD_STATUS_INFO 1 //< Message times out after a while #define LCD_STATUS_NONE 0 //< No alert message set +#define LCD_STATUS_INFO_TIMEOUT 20000 + +// Set the current status message (equivalent to LCD_STATUS_NONE) +void lcd_setstatus(const char* message); +void lcd_setstatuspgm(const char* message); + //! return to the main status screen and display the alert message //! Beware - it has sideeffects: //! - always returns the display to the main status screen @@ -25,13 +30,10 @@ void lcd_setstatuspgm(const char* message); void lcd_setalertstatus(const char* message, uint8_t severity = LCD_STATUS_ALERT); void lcd_setalertstatuspgm(const char* message, uint8_t severity = LCD_STATUS_ALERT); -//! only update the alert message on the main status screen -//! has no sideeffects, may be called multiple times -void lcd_updatestatus(const char *message); -void lcd_updatestatuspgm(const char *message); - -void lcd_reset_alert_level(); +//! Get/reset the current alert level uint8_t get_message_level(); +void lcd_reset_alert_level(); + void lcd_adjust_z(); void lcd_pick_babystep(); void lcd_alright(); diff --git a/Firmware/variants/1_75mm_MK3-EINSy10a-E3Dv6full.h b/Firmware/variants/1_75mm_MK3-EINSy10a-E3Dv6full.h index f31999afd..d32369a84 100644 --- a/Firmware/variants/1_75mm_MK3-EINSy10a-E3Dv6full.h +++ b/Firmware/variants/1_75mm_MK3-EINSy10a-E3Dv6full.h @@ -413,6 +413,34 @@ #define TEMP_RUNAWAY_EXTRUDER_HYSTERESIS 15 #define TEMP_RUNAWAY_EXTRUDER_TIMEOUT 45 +// model-based temperature check +#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_Ta_corr -7 // Default ambient temperature correction +#define TEMP_MODEL_LAG 2.1 // Temperature transport delay (s) + +#define TEMP_MODEL_W 1.2 // Default 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 *------------------------------------*/ diff --git a/Firmware/variants/1_75mm_MK3S-EINSy10a-E3Dv6full.h b/Firmware/variants/1_75mm_MK3S-EINSy10a-E3Dv6full.h index ddd40186f..501868578 100644 --- a/Firmware/variants/1_75mm_MK3S-EINSy10a-E3Dv6full.h +++ b/Firmware/variants/1_75mm_MK3S-EINSy10a-E3Dv6full.h @@ -417,6 +417,34 @@ #define TEMP_RUNAWAY_EXTRUDER_HYSTERESIS 15 #define TEMP_RUNAWAY_EXTRUDER_TIMEOUT 45 +// model-based temperature check +#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_Ta_corr -7 // Default ambient temperature correction +#define TEMP_MODEL_LAG 2.1 // Temperature transport delay (s) + +#define TEMP_MODEL_W 1.2 // Default 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 *------------------------------------*/ diff --git a/Firmware/xyzcal.cpp b/Firmware/xyzcal.cpp index cc2d940df..e8689d3b5 100644 --- a/Firmware/xyzcal.cpp +++ b/Firmware/xyzcal.cpp @@ -138,15 +138,18 @@ pos_mm_t pos_2_mm(float pos){ void xyzcal_meassure_enter(void) { DBG(_n("xyzcal_meassure_enter\n")); + + // disable heaters and stop motion before we initialize sm4 disable_heater(); - DISABLE_TEMPERATURE_INTERRUPT(); -#if (defined(FANCHECK) && defined(TACH_1) && (TACH_1 >-1)) - DISABLE_FANCHECK_INTERRUPT(); -#endif //(defined(FANCHECK) && defined(TACH_1) && (TACH_1 >-1)) + st_synchronize(); + + // disable incompatible interrupts DISABLE_STEPPER_DRIVER_INTERRUPT(); #ifdef WATCHDOG wdt_disable(); #endif //WATCHDOG + + // setup internal callbacks sm4_stop_cb = 0; sm4_update_pos_cb = xyzcal_update_pos; sm4_calc_delay_cb = xyzcal_calc_delay; @@ -155,21 +158,18 @@ void xyzcal_meassure_enter(void) void xyzcal_meassure_leave(void) { DBG(_n("xyzcal_meassure_leave\n")); - planner_abort_hard(); - ENABLE_TEMPERATURE_INTERRUPT(); -#if (defined(FANCHECK) && defined(TACH_1) && (TACH_1 >-1)) - ENABLE_FANCHECK_INTERRUPT(); -#endif //(defined(FANCHECK) && defined(TACH_1) && (TACH_1 >-1)) - ENABLE_STEPPER_DRIVER_INTERRUPT(); + + // resync planner position from counters (changed by xyzcal_update_pos) + planner_reset_position(); + + // re-enable interrupts #ifdef WATCHDOG wdt_enable(WDTO_4S); #ifdef EMERGENCY_HANDLERS WDTCSR |= (1 << WDIE); #endif //EMERGENCY_HANDLERS #endif //WATCHDOG - sm4_stop_cb = 0; - sm4_update_pos_cb = 0; - sm4_calc_delay_cb = 0; + ENABLE_STEPPER_DRIVER_INTERRUPT(); } @@ -999,13 +999,9 @@ BedSkewOffsetDetectionResultType xyzcal_scan_and_process(){ return ret; } -BedSkewOffsetDetectionResultType xyzcal_find_bed_induction_sensor_point_xy(void){ - BedSkewOffsetDetectionResultType ret = BED_SKEW_OFFSET_DETECTION_POINT_NOT_FOUND; - - //@size=258 +BedSkewOffsetDetectionResultType xyzcal_find_bed_induction_sensor_point_xy(void) { // DBG(_n("xyzcal_find_bed_induction_sensor_point_xy x=%ld y=%ld z=%ld\n"), count_position[X_AXIS], count_position[Y_AXIS], count_position[Z_AXIS]); - st_synchronize(); - + BedSkewOffsetDetectionResultType ret = BED_SKEW_OFFSET_DETECTION_POINT_NOT_FOUND; xyzcal_meassure_enter(); if (xyzcal_searchZ()) ret = xyzcal_scan_and_process();