Skip to content

Commit

Permalink
✨ Fan tachometer support (MarlinFirmware#23086, MarlinFirmware#23180, M…
Browse files Browse the repository at this point in the history
…arlinFirmware#23199)

Co-Authored-By: Scott Lahteine <github@thinkyhead.com>
  • Loading branch information
2 people authored and AlexColello committed Jan 14, 2022
1 parent b1ba065 commit d43ff42
Show file tree
Hide file tree
Showing 21 changed files with 592 additions and 44 deletions.
40 changes: 40 additions & 0 deletions Marlin/Configuration_adv.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down Expand Up @@ -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>
*/
Expand Down
7 changes: 7 additions & 0 deletions Marlin/src/MarlinCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -832,6 +836,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());
Expand Down Expand Up @@ -1278,6 +1283,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).

Expand Down
1 change: 1 addition & 0 deletions Marlin/src/core/language.h
Original file line number Diff line number Diff line change
Expand Up @@ -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: "
Expand Down
207 changes: 207 additions & 0 deletions Marlin/src/feature/fancheck.cpp
Original file line number Diff line number Diff line change
@@ -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 changes: 89 additions & 0 deletions Marlin/src/feature/fancheck.h
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion Marlin/src/gcode/control/M999.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading

0 comments on commit d43ff42

Please sign in to comment.