|
| 1 | +/** |
| 2 | + * Marlin 3D Printer Firmware |
| 3 | + * Copyright (c) 2021 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] |
| 4 | + * |
| 5 | + * Based on Sprinter and grbl. |
| 6 | + * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm |
| 7 | + * |
| 8 | + * This program is free software: you can redistribute it and/or modify |
| 9 | + * it under the terms of the GNU General Public License as published by |
| 10 | + * the Free Software Foundation, either version 3 of the License, or |
| 11 | + * (at your option) any later version. |
| 12 | + * |
| 13 | + * This program is distributed in the hope that it will be useful, |
| 14 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 16 | + * GNU General Public License for more details. |
| 17 | + * |
| 18 | + * You should have received a copy of the GNU General Public License |
| 19 | + * along with this program. If not, see <https://www.gnu.org/licenses/>. |
| 20 | + * |
| 21 | + */ |
| 22 | + |
| 23 | +/** |
| 24 | + * fancheck.cpp - fan tachometer check |
| 25 | + */ |
| 26 | + |
| 27 | +#include "../inc/MarlinConfig.h" |
| 28 | + |
| 29 | +#if HAS_FANCHECK |
| 30 | + |
| 31 | +#include "fancheck.h" |
| 32 | +#include "../module/temperature.h" |
| 33 | + |
| 34 | +#if HAS_AUTO_FAN && EXTRUDER_AUTO_FAN_SPEED != 255 && DISABLED(FOURWIRES_FANS) |
| 35 | + bool FanCheck::measuring = false; |
| 36 | +#endif |
| 37 | +bool FanCheck::tacho_state[TACHO_COUNT]; |
| 38 | +uint16_t FanCheck::edge_counter[TACHO_COUNT]; |
| 39 | +uint8_t FanCheck::rps[TACHO_COUNT]; |
| 40 | +FanCheck::TachoError FanCheck::error = FanCheck::TachoError::NONE; |
| 41 | +bool FanCheck::enabled; |
| 42 | + |
| 43 | +void FanCheck::init() { |
| 44 | + #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) |
| 45 | + #if HAS_E0_FAN_TACHO |
| 46 | + _TACHINIT(0); |
| 47 | + #endif |
| 48 | + #if HAS_E1_FAN_TACHO |
| 49 | + _TACHINIT(1); |
| 50 | + #endif |
| 51 | + #if HAS_E2_FAN_TACHO |
| 52 | + _TACHINIT(2); |
| 53 | + #endif |
| 54 | + #if HAS_E3_FAN_TACHO |
| 55 | + _TACHINIT(3); |
| 56 | + #endif |
| 57 | + #if HAS_E4_FAN_TACHO |
| 58 | + _TACHINIT(4); |
| 59 | + #endif |
| 60 | + #if HAS_E5_FAN_TACHO |
| 61 | + _TACHINIT(5); |
| 62 | + #endif |
| 63 | + #if HAS_E6_FAN_TACHO |
| 64 | + _TACHINIT(6); |
| 65 | + #endif |
| 66 | + #if HAS_E7_FAN_TACHO |
| 67 | + _TACHINIT(7); |
| 68 | + #endif |
| 69 | +} |
| 70 | + |
| 71 | +void FanCheck::update_tachometers() { |
| 72 | + bool status; |
| 73 | + |
| 74 | + #define _TACHO_CASE(N) case N: status = READ(E##N##_FAN_TACHO_PIN); break; |
| 75 | + LOOP_L_N(f, TACHO_COUNT) { |
| 76 | + switch (f) { |
| 77 | + #if HAS_E0_FAN_TACHO |
| 78 | + _TACHO_CASE(0) |
| 79 | + #endif |
| 80 | + #if HAS_E1_FAN_TACHO |
| 81 | + _TACHO_CASE(1) |
| 82 | + #endif |
| 83 | + #if HAS_E2_FAN_TACHO |
| 84 | + _TACHO_CASE(2) |
| 85 | + #endif |
| 86 | + #if HAS_E3_FAN_TACHO |
| 87 | + _TACHO_CASE(3) |
| 88 | + #endif |
| 89 | + #if HAS_E4_FAN_TACHO |
| 90 | + _TACHO_CASE(4) |
| 91 | + #endif |
| 92 | + #if HAS_E5_FAN_TACHO |
| 93 | + _TACHO_CASE(5) |
| 94 | + #endif |
| 95 | + #if HAS_E6_FAN_TACHO |
| 96 | + _TACHO_CASE(6) |
| 97 | + #endif |
| 98 | + #if HAS_E7_FAN_TACHO |
| 99 | + _TACHO_CASE(7) |
| 100 | + #endif |
| 101 | + default: continue; |
| 102 | + } |
| 103 | + |
| 104 | + if (status != tacho_state[f]) { |
| 105 | + if (measuring) ++edge_counter[f]; |
| 106 | + tacho_state[f] = status; |
| 107 | + } |
| 108 | + } |
| 109 | +} |
| 110 | + |
| 111 | +void FanCheck::compute_speed(uint16_t elapsedTime) { |
| 112 | + static uint8_t errors_count[TACHO_COUNT]; |
| 113 | + static uint8_t fan_reported_errors_msk = 0; |
| 114 | + |
| 115 | + uint8_t fan_error_msk = 0; |
| 116 | + LOOP_L_N(f, TACHO_COUNT) { |
| 117 | + switch (f) { |
| 118 | + TERN_(HAS_E0_FAN_TACHO, case 0:) |
| 119 | + TERN_(HAS_E1_FAN_TACHO, case 1:) |
| 120 | + TERN_(HAS_E2_FAN_TACHO, case 2:) |
| 121 | + TERN_(HAS_E3_FAN_TACHO, case 3:) |
| 122 | + TERN_(HAS_E4_FAN_TACHO, case 4:) |
| 123 | + TERN_(HAS_E5_FAN_TACHO, case 5:) |
| 124 | + TERN_(HAS_E6_FAN_TACHO, case 6:) |
| 125 | + TERN_(HAS_E7_FAN_TACHO, case 7:) |
| 126 | + // Compute fan speed |
| 127 | + rps[f] = edge_counter[f] * float(250) / elapsedTime; |
| 128 | + edge_counter[f] = 0; |
| 129 | + |
| 130 | + // Check fan speed |
| 131 | + constexpr int8_t max_extruder_fan_errors = TERN(HAS_PWMFANCHECK, 10000, 5000) / Temperature::fan_update_interval_ms; |
| 132 | + |
| 133 | + if (rps[f] >= 20 || TERN0(HAS_AUTO_FAN, thermalManager.autofan_speed[f] == 0)) |
| 134 | + errors_count[f] = 0; |
| 135 | + else if (errors_count[f] < max_extruder_fan_errors) |
| 136 | + ++errors_count[f]; |
| 137 | + else if (enabled) |
| 138 | + SBI(fan_error_msk, f); |
| 139 | + break; |
| 140 | + } |
| 141 | + } |
| 142 | + |
| 143 | + // Drop the error when all fans are ok |
| 144 | + if (!fan_error_msk && error == TachoError::REPORTED) error = TachoError::FIXED; |
| 145 | + |
| 146 | + if (error == TachoError::FIXED && !printJobOngoing() && !printingIsPaused()) { |
| 147 | + error = TachoError::NONE; // if the issue has been fixed while the printer is idle, reenable immediately |
| 148 | + ui.reset_alert_level(); |
| 149 | + } |
| 150 | + |
| 151 | + if (fan_error_msk & ~fan_reported_errors_msk) { |
| 152 | + // Handle new faults only |
| 153 | + LOOP_L_N(f, TACHO_COUNT) if (TEST(fan_error_msk, f)) report_speed_error(f); |
| 154 | + } |
| 155 | + fan_reported_errors_msk = fan_error_msk; |
| 156 | +} |
| 157 | + |
| 158 | +void FanCheck::report_speed_error(uint8_t fan) { |
| 159 | + if (printJobOngoing()) { |
| 160 | + if (error == TachoError::NONE) { |
| 161 | + if (thermalManager.degTargetHotend(fan) != 0) { |
| 162 | + kill(GET_TEXT_F(MSG_FAN_SPEED_FAULT)); |
| 163 | + error = TachoError::REPORTED; |
| 164 | + } |
| 165 | + else |
| 166 | + error = TachoError::DETECTED; // Plans error for next processed command |
| 167 | + } |
| 168 | + } |
| 169 | + else if (!printingIsPaused()) { |
| 170 | + thermalManager.setTargetHotend(0, fan); // Always disable heating |
| 171 | + if (error == TachoError::NONE) error = TachoError::REPORTED; |
| 172 | + } |
| 173 | + |
| 174 | + SERIAL_ERROR_MSG(STR_ERR_FANSPEED, fan); |
| 175 | + LCD_ALERTMESSAGE(MSG_FAN_SPEED_FAULT); |
| 176 | +} |
| 177 | + |
| 178 | +void FanCheck::print_fan_states() { |
| 179 | + LOOP_L_N(s, 2) { |
| 180 | + LOOP_L_N(f, TACHO_COUNT) { |
| 181 | + switch (f) { |
| 182 | + TERN_(HAS_E0_FAN_TACHO, case 0:) |
| 183 | + TERN_(HAS_E1_FAN_TACHO, case 1:) |
| 184 | + TERN_(HAS_E2_FAN_TACHO, case 2:) |
| 185 | + TERN_(HAS_E3_FAN_TACHO, case 3:) |
| 186 | + TERN_(HAS_E4_FAN_TACHO, case 4:) |
| 187 | + TERN_(HAS_E5_FAN_TACHO, case 5:) |
| 188 | + TERN_(HAS_E6_FAN_TACHO, case 6:) |
| 189 | + TERN_(HAS_E7_FAN_TACHO, case 7:) |
| 190 | + SERIAL_ECHOPGM("E", f); |
| 191 | + if (s == 0) |
| 192 | + SERIAL_ECHOPGM(":", 60 * rps[f], " RPM "); |
| 193 | + else |
| 194 | + SERIAL_ECHOPGM("@:", TERN(HAS_AUTO_FAN, thermalManager.autofan_speed[f], 255), " "); |
| 195 | + break; |
| 196 | + } |
| 197 | + } |
| 198 | + } |
| 199 | + SERIAL_EOL(); |
| 200 | +} |
| 201 | + |
| 202 | +#if ENABLED(AUTO_REPORT_FANS) |
| 203 | + AutoReporter<FanCheck::AutoReportFan> FanCheck::auto_reporter; |
| 204 | + void FanCheck::AutoReportFan::report() { print_fan_states(); } |
| 205 | +#endif |
| 206 | + |
| 207 | +#endif // HAS_FANCHECK |
0 commit comments