My Marlin configs for Fabrikator Mini and CTC i3 Pro B
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

spindle_laser.h 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. /**
  2. * Marlin 3D Printer Firmware
  3. * Copyright (c) 2020 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. #pragma once
  23. /**
  24. * feature/spindle_laser.h
  25. * Support for Laser Power or Spindle Power & Direction
  26. */
  27. #include "../inc/MarlinConfig.h"
  28. #include "spindle_laser_types.h"
  29. #if HAS_BEEPER
  30. #include "../libs/buzzer.h"
  31. #endif
  32. // Inline laser power
  33. #include "../module/planner.h"
  34. #define PCT_TO_PWM(X) ((X) * 255 / 100)
  35. #define PCT_TO_SERVO(X) ((X) * 180 / 100)
  36. // Laser/Cutter operation mode
  37. enum CutterMode : int8_t {
  38. CUTTER_MODE_ERROR = -1,
  39. CUTTER_MODE_STANDARD, // M3 power is applied directly and waits for planner moves to sync.
  40. CUTTER_MODE_CONTINUOUS, // M3 or G1/2/3 move power is controlled within planner blocks, set with 'M3 I', cleared with 'M5 I'.
  41. CUTTER_MODE_DYNAMIC // M4 laser power is proportional to the feed rate, set with 'M4 I', cleared with 'M5 I'.
  42. };
  43. class SpindleLaser {
  44. public:
  45. static CutterMode cutter_mode;
  46. static constexpr uint8_t pct_to_ocr(const_float_t pct) { return uint8_t(PCT_TO_PWM(pct)); }
  47. // cpower = configured values (e.g., SPEED_POWER_MAX)
  48. // Convert configured power range to a percentage
  49. static constexpr cutter_cpower_t power_floor = TERN(CUTTER_POWER_RELATIVE, SPEED_POWER_MIN, 0);
  50. static constexpr uint8_t cpwr_to_pct(const cutter_cpower_t cpwr) {
  51. return cpwr ? round(100.0f * (cpwr - power_floor) / (SPEED_POWER_MAX - power_floor)) : 0;
  52. }
  53. // Convert config defines from RPM to %, angle or PWM when in Spindle mode
  54. // and convert from PERCENT to PWM when in Laser mode
  55. static constexpr cutter_power_t cpwr_to_upwr(const cutter_cpower_t cpwr) { // STARTUP power to Unit power
  56. return (
  57. #if ENABLED(SPINDLE_FEATURE)
  58. // Spindle configured define values are in RPM
  59. #if CUTTER_UNIT_IS(RPM)
  60. cpwr // to same
  61. #elif CUTTER_UNIT_IS(PERCENT)
  62. cpwr_to_pct(cpwr) // to Percent
  63. #elif CUTTER_UNIT_IS(SERVO)
  64. PCT_TO_SERVO(cpwr_to_pct(cpwr)) // to SERVO angle
  65. #else
  66. PCT_TO_PWM(cpwr_to_pct(cpwr)) // to PWM
  67. #endif
  68. #else
  69. // Laser configured define values are in Percent
  70. #if CUTTER_UNIT_IS(PWM255)
  71. PCT_TO_PWM(cpwr) // to PWM
  72. #else
  73. cpwr // to same
  74. #endif
  75. #endif
  76. );
  77. }
  78. static constexpr cutter_power_t mpower_min() { return cpwr_to_upwr(SPEED_POWER_MIN); }
  79. static constexpr cutter_power_t mpower_max() { return cpwr_to_upwr(SPEED_POWER_MAX); }
  80. #if ENABLED(LASER_FEATURE)
  81. static cutter_test_pulse_t testPulse; // (ms) Test fire pulse duration
  82. static uint8_t last_block_power; // Track power changes for dynamic power
  83. static feedRate_t feedrate_mm_m, last_feedrate_mm_m; // (mm/min) Track feedrate changes for dynamic power
  84. static bool laser_feedrate_changed() {
  85. const bool changed = last_feedrate_mm_m != feedrate_mm_m;
  86. if (changed) last_feedrate_mm_m = feedrate_mm_m;
  87. return changed;
  88. }
  89. #endif
  90. static bool isReadyForUI; // Ready to apply power setting from the UI to OCR
  91. static bool enable_state;
  92. static uint8_t power,
  93. last_power_applied; // Basic power state tracking
  94. static cutter_frequency_t frequency; // Set PWM frequency; range: 2K-50K
  95. static cutter_power_t menuPower, // Power as set via LCD menu in PWM, Percentage or RPM
  96. unitPower; // Power as displayed status in PWM, Percentage or RPM
  97. static void init();
  98. #if ENABLED(HAL_CAN_SET_PWM_FREQ) && SPINDLE_LASER_FREQUENCY
  99. static void refresh_frequency() { hal.set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), frequency); }
  100. #endif
  101. // Modifying this function should update everywhere
  102. static bool enabled(const cutter_power_t opwr) { return opwr > 0; }
  103. static bool enabled() { return enable_state; }
  104. static void apply_power(const uint8_t inpow);
  105. FORCE_INLINE static void refresh() { apply_power(power); }
  106. #if ENABLED(SPINDLE_LASER_USE_PWM)
  107. private:
  108. static void _set_ocr(const uint8_t ocr);
  109. public:
  110. static void set_ocr(const uint8_t ocr);
  111. static void ocr_off();
  112. /**
  113. * Update output for power->OCR translation
  114. */
  115. static uint8_t upower_to_ocr(const cutter_power_t upwr) {
  116. return uint8_t(
  117. #if CUTTER_UNIT_IS(PWM255)
  118. upwr
  119. #elif CUTTER_UNIT_IS(PERCENT)
  120. pct_to_ocr(upwr)
  121. #else
  122. pct_to_ocr(cpwr_to_pct(upwr))
  123. #endif
  124. );
  125. }
  126. #endif // SPINDLE_LASER_USE_PWM
  127. /**
  128. * Correct power to configured range
  129. */
  130. static cutter_power_t power_to_range(const cutter_power_t pwr, const uint8_t pwrUnit=_CUTTER_POWER(CUTTER_POWER_UNIT)) {
  131. static constexpr float
  132. min_pct = TERN(CUTTER_POWER_RELATIVE, 0, TERN(SPINDLE_FEATURE, round(100.0f * (SPEED_POWER_MIN) / (SPEED_POWER_MAX)), SPEED_POWER_MIN)),
  133. max_pct = TERN(SPINDLE_FEATURE, 100, SPEED_POWER_MAX);
  134. if (pwr <= 0) return 0;
  135. cutter_power_t upwr;
  136. switch (pwrUnit) {
  137. case _CUTTER_POWER_PWM255: { // PWM
  138. const uint8_t pmin = pct_to_ocr(min_pct), pmax = pct_to_ocr(max_pct);
  139. upwr = cutter_power_t(constrain(pwr, pmin, pmax));
  140. } break;
  141. case _CUTTER_POWER_PERCENT: // Percent
  142. upwr = cutter_power_t(constrain(pwr, min_pct, max_pct));
  143. break;
  144. case _CUTTER_POWER_RPM: // Calculate OCR value
  145. upwr = cutter_power_t(constrain(pwr, SPEED_POWER_MIN, SPEED_POWER_MAX));
  146. break;
  147. default: break;
  148. }
  149. return upwr;
  150. }
  151. /**
  152. * Enable Laser or Spindle output.
  153. * It's important to prevent changing the power output value during inline cutter operation.
  154. * Inline power is adjusted in the planner to support LASER_TRAP_POWER and CUTTER_MODE_DYNAMIC mode.
  155. *
  156. * This method accepts one of the following control states:
  157. *
  158. * - For CUTTER_MODE_STANDARD the cutter power is either full on/off or ocr-based and it will apply
  159. * SPEED_POWER_STARTUP if no value is assigned.
  160. *
  161. * - For CUTTER_MODE_CONTINUOUS inline and power remains where last set and the cutter output enable flag is set.
  162. *
  163. * - CUTTER_MODE_DYNAMIC is also inline-based and it just sets the enable output flag.
  164. *
  165. * - For CUTTER_MODE_ERROR set the output enable_state flag directly and set power to 0 for any mode.
  166. * This mode allows a global power shutdown action to occur.
  167. */
  168. static void set_enabled(bool enable) {
  169. switch (cutter_mode) {
  170. case CUTTER_MODE_STANDARD:
  171. apply_power(enable ? TERN(SPINDLE_LASER_USE_PWM, (power ?: (unitPower ? upower_to_ocr(cpwr_to_upwr(SPEED_POWER_STARTUP)) : 0)), 255) : 0);
  172. break;
  173. case CUTTER_MODE_CONTINUOUS:
  174. TERN_(LASER_FEATURE, set_inline_enabled(enable));
  175. break;
  176. case CUTTER_MODE_DYNAMIC:
  177. TERN_(LASER_FEATURE, set_inline_enabled(enable));
  178. break;
  179. case CUTTER_MODE_ERROR: // Error mode, no enable and kill power.
  180. enable = false;
  181. apply_power(0);
  182. }
  183. #if SPINDLE_LASER_ENA_PIN
  184. WRITE(SPINDLE_LASER_ENA_PIN, enable ? SPINDLE_LASER_ACTIVE_STATE : !SPINDLE_LASER_ACTIVE_STATE);
  185. #endif
  186. enable_state = enable;
  187. }
  188. static void disable() { isReadyForUI = false; set_enabled(false); }
  189. // Wait for spindle/laser to startup or shutdown
  190. static void power_delay(const bool on) {
  191. safe_delay(on ? SPINDLE_LASER_POWERUP_DELAY : SPINDLE_LASER_POWERDOWN_DELAY);
  192. }
  193. #if ENABLED(SPINDLE_CHANGE_DIR)
  194. static void set_reverse(const bool reverse);
  195. static bool is_reverse() { return READ(SPINDLE_DIR_PIN) == SPINDLE_INVERT_DIR; }
  196. #else
  197. static void set_reverse(const bool) {}
  198. static bool is_reverse() { return false; }
  199. #endif
  200. #if ENABLED(AIR_EVACUATION)
  201. static void air_evac_enable(); // Turn On Cutter Vacuum or Laser Blower motor
  202. static void air_evac_disable(); // Turn Off Cutter Vacuum or Laser Blower motor
  203. static void air_evac_toggle(); // Toggle Cutter Vacuum or Laser Blower motor
  204. static bool air_evac_state() { // Get current state
  205. return (READ(AIR_EVACUATION_PIN) == AIR_EVACUATION_ACTIVE);
  206. }
  207. #endif
  208. #if ENABLED(AIR_ASSIST)
  209. static void air_assist_enable(); // Turn on air assist
  210. static void air_assist_disable(); // Turn off air assist
  211. static void air_assist_toggle(); // Toggle air assist
  212. static bool air_assist_state() { // Get current state
  213. return (READ(AIR_ASSIST_PIN) == AIR_ASSIST_ACTIVE);
  214. }
  215. #endif
  216. #if HAS_MARLINUI_MENU
  217. #if ENABLED(SPINDLE_FEATURE)
  218. static void enable_with_dir(const bool reverse) {
  219. isReadyForUI = true;
  220. const uint8_t ocr = TERN(SPINDLE_LASER_USE_PWM, upower_to_ocr(menuPower), 255);
  221. if (menuPower)
  222. power = ocr;
  223. else
  224. menuPower = cpwr_to_upwr(SPEED_POWER_STARTUP);
  225. unitPower = menuPower;
  226. set_reverse(reverse);
  227. set_enabled(true);
  228. }
  229. FORCE_INLINE static void enable_forward() { enable_with_dir(false); }
  230. FORCE_INLINE static void enable_reverse() { enable_with_dir(true); }
  231. FORCE_INLINE static void enable_same_dir() { enable_with_dir(is_reverse()); }
  232. #endif // SPINDLE_FEATURE
  233. #if ENABLED(SPINDLE_LASER_USE_PWM)
  234. static void update_from_mpower() {
  235. if (isReadyForUI) power = upower_to_ocr(menuPower);
  236. unitPower = menuPower;
  237. }
  238. #endif
  239. #if ENABLED(LASER_FEATURE)
  240. // Toggle the laser on/off with menuPower. Apply SPEED_POWER_STARTUP if it was 0 on entry.
  241. static void menu_set_enabled(const bool state) {
  242. set_enabled(state);
  243. if (state) {
  244. if (!menuPower) menuPower = cpwr_to_upwr(SPEED_POWER_STARTUP);
  245. power = upower_to_ocr(menuPower);
  246. apply_power(power);
  247. } else
  248. apply_power(0);
  249. }
  250. /**
  251. * Test fire the laser using the testPulse ms duration
  252. * Also fires with any PWM power that was previous set
  253. * If not set defaults to 80% power
  254. */
  255. static void test_fire_pulse() {
  256. BUZZ(30, 3000);
  257. cutter_mode = CUTTER_MODE_STANDARD; // Menu needs standard mode.
  258. menu_set_enabled(true); // Laser On
  259. delay(testPulse); // Delay for time set by user in pulse ms menu screen.
  260. menu_set_enabled(false); // Laser Off
  261. }
  262. #endif // LASER_FEATURE
  263. #endif // HAS_MARLINUI_MENU
  264. #if ENABLED(LASER_FEATURE)
  265. // Dynamic mode rate calculation
  266. static uint8_t calc_dynamic_power() {
  267. if (feedrate_mm_m > 65535) return 255; // Too fast, go always on
  268. uint16_t rate = uint16_t(feedrate_mm_m); // 16 bits from the G-code parser float input
  269. rate >>= 8; // Take the G-code input e.g. F40000 and shift off the lower bits to get an OCR value from 1-255
  270. return uint8_t(rate);
  271. }
  272. // Inline modes of all other functions; all enable planner inline power control
  273. static void set_inline_enabled(const bool enable) { planner.laser_inline.status.isEnabled = enable; }
  274. // Set the power for subsequent movement blocks
  275. static void inline_power(const cutter_power_t cpwr) {
  276. TERN(SPINDLE_LASER_USE_PWM, power = planner.laser_inline.power = cpwr, planner.laser_inline.power = cpwr > 0 ? 255 : 0);
  277. }
  278. #endif // LASER_FEATURE
  279. static void kill() { disable(); }
  280. };
  281. extern SpindleLaser cutter;