Browse Source

✨ Fan tachometer support (#23086)

Co-authored-by: Scott Lahteine <github@thinkyhead.com>
Giuliano Zaro 3 years ago
parent
commit
7110d11c9d

+ 40 - 0
Marlin/Configuration_adv.h

@@ -603,6 +603,40 @@
 #define COOLER_AUTO_FAN_TEMPERATURE 18
 #define COOLER_AUTO_FAN_SPEED 255
 
+/**
+ * Hotend Cooling Fans tachometers
+ *
+ * Define one or more tachometer pins to enable fan speed
+ * monitoring, and reporting of fan speeds with M123.
+ *
+ * NOTE: Only works with fans up to 7000 RPM.
+ */
+//#define FOURWIRES_FANS      // Needed with AUTO_FAN when 4-wire PWM fans are installed
+//#define E0_FAN_TACHO_PIN -1
+//#define E0_FAN_TACHO_PULLUP
+//#define E0_FAN_TACHO_PULLDOWN
+//#define E1_FAN_TACHO_PIN -1
+//#define E1_FAN_TACHO_PULLUP
+//#define E1_FAN_TACHO_PULLDOWN
+//#define E2_FAN_TACHO_PIN -1
+//#define E2_FAN_TACHO_PULLUP
+//#define E2_FAN_TACHO_PULLDOWN
+//#define E3_FAN_TACHO_PIN -1
+//#define E3_FAN_TACHO_PULLUP
+//#define E3_FAN_TACHO_PULLDOWN
+//#define E4_FAN_TACHO_PIN -1
+//#define E4_FAN_TACHO_PULLUP
+//#define E4_FAN_TACHO_PULLDOWN
+//#define E5_FAN_TACHO_PIN -1
+//#define E5_FAN_TACHO_PULLUP
+//#define E5_FAN_TACHO_PULLDOWN
+//#define E6_FAN_TACHO_PIN -1
+//#define E6_FAN_TACHO_PULLUP
+//#define E6_FAN_TACHO_PULLDOWN
+//#define E7_FAN_TACHO_PIN -1
+//#define E7_FAN_TACHO_PULLUP
+//#define E7_FAN_TACHO_PULLDOWN
+
 /**
  * Part-Cooling Fan Multiplexer
  *
@@ -3607,6 +3641,12 @@
  */
 //#define CNC_COORDINATE_SYSTEMS
 
+/**
+ * Auto-report fan speed with M123 S<seconds>
+ * Requires fans with tachometer pins
+ */
+//#define AUTO_REPORT_FANS
+
 /**
  * Auto-report temperatures with M155 S<seconds>
  */

+ 7 - 0
Marlin/src/MarlinCore.cpp

@@ -212,6 +212,10 @@
 
 #include "module/tool_change.h"
 
+#if HAS_FANCHECK
+  #include "feature/fancheck.h"
+#endif
+
 #if ENABLED(USE_CONTROLLER_FAN)
   #include "feature/controllerfan.h"
 #endif
@@ -829,6 +833,7 @@ void idle(bool no_stepper_sleep/*=false*/) {
   #if HAS_AUTO_REPORTING
     if (!gcode.autoreport_paused) {
       TERN_(AUTO_REPORT_TEMPERATURES, thermalManager.auto_reporter.tick());
+      TERN_(AUTO_REPORT_FANS, fan_check.auto_reporter.tick());
       TERN_(AUTO_REPORT_SD_STATUS, card.auto_reporter.tick());
       TERN_(AUTO_REPORT_POSITION, position_auto_reporter.tick());
       TERN_(BUFFER_MONITORING, queue.auto_report_buffer_statistics());
@@ -1275,6 +1280,8 @@ void setup() {
     SETUP_RUN(controllerFan.setup());
   #endif
 
+  TERN_(HAS_FANCHECK, fan_check.init());
+
   // UI must be initialized before EEPROM
   // (because EEPROM code calls the UI).
 

+ 1 - 0
Marlin/src/core/language.h

@@ -140,6 +140,7 @@
 #define STR_RESEND                          "Resend: "
 #define STR_UNKNOWN_COMMAND                 "Unknown command: \""
 #define STR_ACTIVE_EXTRUDER                 "Active Extruder: "
+#define STR_ERR_FANSPEED                    "Fan speed E"
 
 #define STR_PROBE_OFFSET                    "Probe Offset"
 #define STR_SKEW_MIN                        "min_skew_factor: "

+ 207 - 0
Marlin/src/feature/fancheck.cpp

@@ -0,0 +1,207 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2021 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * fancheck.cpp - fan tachometer check
+ */
+
+#include "../inc/MarlinConfig.h"
+
+#if HAS_FANCHECK
+
+#include "fancheck.h"
+#include "../module/temperature.h"
+
+#if HAS_AUTO_FAN && EXTRUDER_AUTO_FAN_SPEED != 255 && DISABLED(FOURWIRES_FANS)
+  bool FanCheck::measuring = false;
+#endif
+bool FanCheck::tacho_state[TACHO_COUNT];
+uint16_t FanCheck::edge_counter[TACHO_COUNT];
+uint8_t FanCheck::rps[TACHO_COUNT];
+FanCheck::TachoError FanCheck::error = FanCheck::TachoError::NONE;
+bool FanCheck::enabled;
+
+void FanCheck::init() {
+  #define _TACHINIT(N) TERN(E##N##_FAN_TACHO_PULLUP, SET_INPUT_PULLUP, TERN(E##N##_FAN_TACHO_PULLDOWN, SET_INPUT_PULLDOWN, SET_INPUT))(E##N##_FAN_TACHO_PIN)
+  #if HAS_E0_FAN_TACHO
+    _TACHINIT(0);
+  #endif
+  #if HAS_E1_FAN_TACHO
+    _TACHINIT(1);
+  #endif
+  #if HAS_E2_FAN_TACHO
+    _TACHINIT(2);
+  #endif
+  #if HAS_E3_FAN_TACHO
+    _TACHINIT(3);
+  #endif
+  #if HAS_E4_FAN_TACHO
+    _TACHINIT(4);
+  #endif
+  #if HAS_E5_FAN_TACHO
+    _TACHINIT(5);
+  #endif
+  #if HAS_E6_FAN_TACHO
+    _TACHINIT(6);
+  #endif
+  #if HAS_E7_FAN_TACHO
+    _TACHINIT(7);
+  #endif
+}
+
+void FanCheck::update_tachometers() {
+  bool status;
+
+  #define _TACHO_CASE(N) case N: status = READ(E##N##_FAN_TACHO_PIN); break;
+  LOOP_L_N(f, TACHO_COUNT) {
+    switch (f) {
+      #if HAS_E0_FAN_TACHO
+        _TACHO_CASE(0)
+      #endif
+      #if HAS_E1_FAN_TACHO
+        _TACHO_CASE(1)
+      #endif
+      #if HAS_E2_FAN_TACHO
+        _TACHO_CASE(2)
+      #endif
+      #if HAS_E3_FAN_TACHO
+        _TACHO_CASE(3)
+      #endif
+      #if HAS_E4_FAN_TACHO
+        _TACHO_CASE(4)
+      #endif
+      #if HAS_E5_FAN_TACHO
+        _TACHO_CASE(5)
+      #endif
+      #if HAS_E6_FAN_TACHO
+        _TACHO_CASE(6)
+      #endif
+      #if HAS_E7_FAN_TACHO
+        _TACHO_CASE(7)
+      #endif
+      default: continue;
+    }
+
+    if (status != tacho_state[f]) {
+      if (measuring) ++edge_counter[f];
+      tacho_state[f] = status;
+    }
+  }
+}
+
+void FanCheck::compute_speed(uint16_t elapsedTime) {
+  static uint8_t errors_count[TACHO_COUNT];
+  static uint8_t fan_reported_errors_msk = 0;
+
+  uint8_t fan_error_msk = 0;
+  LOOP_L_N(f, TACHO_COUNT) {
+    switch (f) {
+      TERN_(HAS_E0_FAN_TACHO, case 0:)
+      TERN_(HAS_E1_FAN_TACHO, case 1:)
+      TERN_(HAS_E2_FAN_TACHO, case 2:)
+      TERN_(HAS_E3_FAN_TACHO, case 3:)
+      TERN_(HAS_E4_FAN_TACHO, case 4:)
+      TERN_(HAS_E5_FAN_TACHO, case 5:)
+      TERN_(HAS_E6_FAN_TACHO, case 6:)
+      TERN_(HAS_E7_FAN_TACHO, case 7:)
+        // Compute fan speed
+        rps[f] = edge_counter[f] * float(250) / elapsedTime;
+        edge_counter[f] = 0;
+
+        // Check fan speed
+        constexpr int8_t max_extruder_fan_errors = TERN(HAS_PWMFANCHECK, 10000, 5000) / Temperature::fan_update_interval_ms;
+
+        if (rps[f] >= 20 || TERN0(HAS_AUTO_FAN, thermalManager.autofan_speed[f] == 0))
+          errors_count[f] = 0;
+        else if (errors_count[f] < max_extruder_fan_errors)
+          ++errors_count[f];
+        else if (enabled)
+          SBI(fan_error_msk, f);
+        break;
+      }
+    }
+
+  // Drop the error when all fans are ok
+  if (!fan_error_msk && error == TachoError::REPORTED) error = TachoError::FIXED;
+
+  if (error == TachoError::FIXED && !printJobOngoing() && !printingIsPaused()) {
+    error = TachoError::NONE; // if the issue has been fixed while the printer is idle, reenable immediately
+    ui.reset_alert_level();
+  }
+
+  if (fan_error_msk & ~fan_reported_errors_msk) {
+    // Handle new faults only
+    LOOP_L_N(f, TACHO_COUNT) if (TEST(fan_error_msk, f)) report_speed_error(f);
+  }
+  fan_reported_errors_msk = fan_error_msk;
+}
+
+void FanCheck::report_speed_error(uint8_t fan) {
+  if (printJobOngoing()) {
+    if (error == TachoError::NONE) {
+      if (thermalManager.degTargetHotend(fan) != 0) {
+        kill(GET_TEXT_F(MSG_FAN_SPEED_FAULT));
+        error = TachoError::REPORTED;
+      }
+      else
+        error = TachoError::DETECTED;   // Plans error for next processed command
+    }
+  }
+  else if (!printingIsPaused()) {
+    thermalManager.setTargetHotend(0, fan); // Always disable heating
+    if (error == TachoError::NONE) error = TachoError::REPORTED;
+  }
+
+  SERIAL_ERROR_MSG(STR_ERR_FANSPEED, fan);
+  LCD_ALERTMESSAGE(MSG_FAN_SPEED_FAULT);
+}
+
+void FanCheck::print_fan_states() {
+  LOOP_L_N(s, 2) {
+    LOOP_L_N(f, TACHO_COUNT) {
+      switch (f) {
+        TERN_(HAS_E0_FAN_TACHO, case 0:)
+        TERN_(HAS_E1_FAN_TACHO, case 1:)
+        TERN_(HAS_E2_FAN_TACHO, case 2:)
+        TERN_(HAS_E3_FAN_TACHO, case 3:)
+        TERN_(HAS_E4_FAN_TACHO, case 4:)
+        TERN_(HAS_E5_FAN_TACHO, case 5:)
+        TERN_(HAS_E6_FAN_TACHO, case 6:)
+        TERN_(HAS_E7_FAN_TACHO, case 7:)
+          SERIAL_ECHOPGM("E", f);
+          if (s == 0)
+            SERIAL_ECHOPGM(":", 60 * rps[f], " RPM ");
+          else
+            SERIAL_ECHOPGM("@:", TERN(HAS_AUTO_FAN, thermalManager.autofan_speed[f], 255), " ");
+          break;
+      }
+    }
+  }
+  SERIAL_EOL();
+}
+
+#if ENABLED(AUTO_REPORT_FANS)
+  AutoReporter<FanCheck::AutoReportFan> FanCheck::auto_reporter;
+  void FanCheck::AutoReportFan::report() { print_fan_states(); }
+#endif
+
+#endif // HAS_FANCHECK

+ 89 - 0
Marlin/src/feature/fancheck.h

@@ -0,0 +1,89 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2021 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+#include "../inc/MarlinConfig.h"
+
+#if HAS_FANCHECK
+
+#include "../MarlinCore.h"
+#include "../lcd/marlinui.h"
+
+#if ENABLED(AUTO_REPORT_FANS)
+  #include "../libs/autoreport.h"
+#endif
+
+#if ENABLED(PARK_HEAD_ON_PAUSE)
+  #include "../gcode/queue.h"
+#endif
+
+/**
+ * fancheck.h
+ */
+#define TACHO_COUNT TERN(HAS_E7_FAN_TACHO, 8, TERN(HAS_E6_FAN_TACHO, 7, TERN(HAS_E5_FAN_TACHO, 6, TERN(HAS_E4_FAN_TACHO, 5, TERN(HAS_E3_FAN_TACHO, 4, TERN(HAS_E2_FAN_TACHO, 3, TERN(HAS_E1_FAN_TACHO, 2, 1)))))))
+
+class FanCheck {
+  private:
+
+    enum class TachoError : uint8_t { NONE, DETECTED, REPORTED, FIXED };
+
+    #if HAS_PWMFANCHECK
+      static bool measuring;  // For future use (3 wires PWM controlled fans)
+    #else
+      static constexpr bool measuring = true;
+    #endif
+    static bool tacho_state[TACHO_COUNT];
+    static uint16_t edge_counter[TACHO_COUNT];
+    static uint8_t rps[TACHO_COUNT];
+    static TachoError error;
+
+    static inline void report_speed_error(uint8_t fan);
+
+  public:
+
+    static bool enabled;
+
+    static void init();
+    static void update_tachometers();
+    static void compute_speed(uint16_t elapsedTime);
+    static void print_fan_states();
+    #if HAS_PWMFANCHECK
+      static inline void toggle_measuring() { measuring = !measuring; }
+      static inline bool is_measuring() { return measuring; }
+    #endif
+
+    static inline void check_deferred_error() {
+      if (error == TachoError::DETECTED) {
+        error = TachoError::REPORTED;
+        TERN(PARK_HEAD_ON_PAUSE, queue.inject(F("M125")), kill(GET_TEXT_F(MSG_FAN_SPEED_FAULT)));
+      }
+    }
+
+    #if ENABLED(AUTO_REPORT_FANS)
+      struct AutoReportFan { static void report(); };
+      static AutoReporter<AutoReportFan> auto_reporter;
+    #endif
+};
+
+extern FanCheck fan_check;
+
+#endif // HAS_FANCHECK

+ 1 - 1
Marlin/src/gcode/control/M999.cpp

@@ -22,7 +22,7 @@
 
 #include "../gcode.h"
 
-#include "../../lcd/marlinui.h" // for lcd_reset_alert_level
+#include "../../lcd/marlinui.h" // for ui.reset_alert_level
 #include "../../MarlinCore.h"   // for marlin_state
 #include "../queue.h"           // for flush_and_request_resend
 

+ 10 - 0
Marlin/src/gcode/gcode.cpp

@@ -65,6 +65,10 @@ GcodeSuite gcode;
   #include "../feature/password/password.h"
 #endif
 
+#if HAS_FANCHECK
+  #include "../feature/fancheck.h"
+#endif
+
 #include "../MarlinCore.h" // for idle, kill
 
 // Inactivity shutdown
@@ -296,6 +300,8 @@ void GcodeSuite::dwell(millis_t time) {
  * Process the parsed command and dispatch it to its handler
  */
 void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
+  TERN_(HAS_FANCHECK, fan_check.check_deferred_error());
+
   KEEPALIVE_STATE(IN_HANDLER);
 
  /**
@@ -577,6 +583,10 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
         case 113: M113(); break;                                  // M113: Set Host Keepalive interval
       #endif
 
+      #if HAS_FANCHECK
+        case 123: M123(); break;                                  // M123: Report fan states or set fans auto-report interval
+      #endif
+
       #if HAS_HEATED_BED
         case 140: M140(); break;                                  // M140: Set bed temperature
         case 190: M190(); break;                                  // M190: Wait for bed temperature to reach target

+ 5 - 0
Marlin/src/gcode/gcode.h

@@ -156,6 +156,7 @@
  * M121 - Disable endstops detection.
  *
  * M122 - Debug stepper (Requires at least one _DRIVER_TYPE defined as TMC2130/2160/5130/5160/2208/2209/2660 or L6470)
+ * M123 - Report fan tachometers. (Requires En_FAN_TACHO_PIN) Optionally set auto-report interval. (Requires AUTO_REPORT_FANS)
  * M125 - Save current position and move to filament change position. (Requires PARK_HEAD_ON_PAUSE)
  *
  * M126 - Solenoid Air Valve Open. (Requires BARICUDA)
@@ -736,6 +737,10 @@ private:
   static void M120();
   static void M121();
 
+  #if HAS_FANCHECK
+    static void M123();
+  #endif
+
   #if ENABLED(PARK_HEAD_ON_PAUSE)
     static void M125();
   #endif

+ 48 - 0
Marlin/src/gcode/temp/M123.cpp

@@ -0,0 +1,48 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2021 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_FANCHECK
+
+#include "../gcode.h"
+#include "../../feature/fancheck.h"
+
+/**
+ * M123: Report fan states -or- set interval for auto-report
+ *
+ *   S<seconds> : Set auto-report interval
+ */
+void GcodeSuite::M123() {
+
+  #if ENABLED(AUTO_REPORT_FANS)
+    if (parser.seenval('S')) {
+      fan_check.auto_reporter.set_interval(parser.value_byte());
+      return;
+    }
+  #endif
+
+  fan_check.print_fan_states();
+
+}
+
+#endif // HAS_FANCHECK

+ 33 - 1
Marlin/src/inc/Conditionals_post.h

@@ -2510,10 +2510,42 @@
   #define AUTO_CHAMBER_IS_E 1
 #endif
 
+// Fans check
+#if HAS_HOTEND && PIN_EXISTS(E0_FAN_TACHO)
+  #define HAS_E0_FAN_TACHO 1
+#endif
+#if HOTENDS > 1 && PIN_EXISTS(E1_FAN_TACHO)
+  #define HAS_E1_FAN_TACHO 1
+#endif
+#if HOTENDS > 2 && PIN_EXISTS(E2_FAN_TACHO)
+  #define HAS_E2_FAN_TACHO 1
+#endif
+#if HOTENDS > 3 && PIN_EXISTS(E3_FAN_TACHO)
+  #define HAS_E3_FAN_TACHO 1
+#endif
+#if HOTENDS > 4 && PIN_EXISTS(E4_FAN_TACHO)
+  #define HAS_E4_FAN_TACHO 1
+#endif
+#if HOTENDS > 5 && PIN_EXISTS(E5_FAN_TACHO)
+  #define HAS_E5_FAN_TACHO 1
+#endif
+#if HOTENDS > 6 && PIN_EXISTS(E6_FAN_TACHO)
+  #define HAS_E6_FAN_TACHO 1
+#endif
+#if HOTENDS > 7 && PIN_EXISTS(E7_FAN_TACHO)
+  #define HAS_E7_FAN_TACHO 1
+#endif
+#if ANY(HAS_E0_FAN_TACHO, HAS_E1_FAN_TACHO, HAS_E2_FAN_TACHO, HAS_E3_FAN_TACHO, HAS_E4_FAN_TACHO, HAS_E5_FAN_TACHO, HAS_E6_FAN_TACHO, HAS_E7_FAN_TACHO)
+  #define HAS_FANCHECK 1
+  #if HAS_AUTO_FAN && EXTRUDER_AUTO_FAN_SPEED != 255 && DISABLED(FOURWIRES_FANS)
+    #define HAS_PWMFANCHECK 1
+  #endif
+#endif
+
 #if !HAS_TEMP_SENSOR
   #undef AUTO_REPORT_TEMPERATURES
 #endif
-#if ANY(AUTO_REPORT_TEMPERATURES, AUTO_REPORT_SD_STATUS, AUTO_REPORT_POSITION)
+#if ANY(AUTO_REPORT_TEMPERATURES, AUTO_REPORT_SD_STATUS, AUTO_REPORT_POSITION, AUTO_REPORT_FANS)
   #define HAS_AUTO_REPORTING 1
 #endif
 

Some files were not shown because too many files changed in this diff