From 79ff804d9eeda62197308abf6c521e57f0c1101f Mon Sep 17 00:00:00 2001 From: Pascal Pieper Date: Fri, 26 Feb 2021 18:20:21 +0100 Subject: [PATCH] Added 'tool' module to control PWM-tools like lasers or spindles via M3-M5 Signed-off-by: Pascal Pieper --- docs/Tool.md | 38 +++++++++++++++++++ klippy/extras/tool.py | 86 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 docs/Tool.md create mode 100644 klippy/extras/tool.py diff --git a/docs/Tool.md b/docs/Tool.md new file mode 100644 index 000000000000..334e8224b89e --- /dev/null +++ b/docs/Tool.md @@ -0,0 +1,38 @@ +This document describes the _tool_ module. + + +## How does it work? +This enables the use of _M3 M4 M5_ GCODE commands. +It is based on the generic fan PWM module, but adds a shutdown value +and makes use of the MCU's safety timeout. + +## Configuration + [tool] + pin: !ar9 + + hardware_pwm: True + # Default: True + + cycle_time: 0.001 + # Default: 0.01 + + max_power: 1 + # Default: 1 + + off_below: 0 + # Default: 0 + + shutdown_value: 0 + # Default: 0 + + safety_timeout: 20 + # Default: 0 (disabled) + # Suggested value is a minimal amount longer + # than the longest running command. + # Currently, moves are not split into individual sections. + + +## Commands + +`M3/M4 S` : Set PWM duty-cycle. Values between 0 and 255. +`M5` : Stop PWM output to shutdown value. diff --git a/klippy/extras/tool.py b/klippy/extras/tool.py new file mode 100644 index 000000000000..222c6f56ea1f --- /dev/null +++ b/klippy/extras/tool.py @@ -0,0 +1,86 @@ +# Printer cooling fan +# +# This file may be distributed under the terms of the GNU GPLv3 license. + +class PWM_tool: + def __init__(self, config, default_shutdown_value=0.): + self.printer = config.get_printer() + self.last_value = 0. + self.last_time = 0. + self.safety_timeout = config.getfloat('safety_timeout', 5, + minval=0.) + self.reactor = self.printer.get_reactor() + self.resend_timer = self.reactor.register_timer( + self._resend_current_val) + # Read config + self.max_value = config.getfloat('max_power', 1., above=0., maxval=1.) + self.off_below = config.getfloat('off_below', default=0., + minval=0., maxval=1.) + cycle_time = config.getfloat('cycle_time', 0.01, above=0.) + hardware_pwm = config.getboolean('hardware_pwm', True) + shutdown_value = config.getfloat( + 'shutdown_value', default_shutdown_value, minval=0., maxval=1.) + # Setup pwm object + ppins = self.printer.lookup_object('pins') + self.mcu_pwm = ppins.setup_pin('pwm', config.get('pin')) + self.mcu_pwm.setup_max_duration(self.safety_timeout) + self.mcu_pwm.setup_cycle_time(cycle_time, hardware_pwm) + self.shutdown_value = max(0., min(self.max_value, shutdown_value)) + self.mcu_pwm.setup_start_value(0., shutdown_value) + # Register callbacks + self.printer.register_event_handler("gcode:request_restart", + self._handle_request_restart) + def get_mcu(self): + return self.mcu_pwm.get_mcu() + def set_value(self, print_time, value, resend=False): + if value < self.off_below: + value = 0. + value = max(0., min(self.max_value, value * self.max_value)) + if value == self.last_value and not resend: + return + print_time = max(self.last_time, print_time) + self.last_time = print_time + self.last_value = value + self.mcu_pwm.set_pwm(print_time, value) + if self.safety_timeout != 0 and not resend: + if value != self.shutdown_value: + self.reactor.update_timer( + self.resend_timer, + self.reactor.monotonic() + 0.75 * self.safety_timeout) + def set_value_from_command(self, value): + toolhead = self.printer.lookup_object('toolhead') + toolhead.register_lookahead_callback((lambda pt: + self.set_value(pt, value))) + def _handle_request_restart(self, print_time): + self.set_value(print_time, 0.) + def get_status(self, eventtime): + return {'value': self.last_value} + def _resend_current_val(self, eventtime): + # TODO: Split moves into smaller segments to enforce resend + toolhead = self.printer.lookup_object('toolhead') + toolhead.register_lookahead_callback((lambda pt: + self.set_value(pt, self.last_value, True))) + if self.last_value != self.shutdown_value: + return eventtime + 0.75 * self.safety_timeout + return self.reactor.NEVER + +class PrinterSpindle: + def __init__(self, config): + self.tool = PWM_tool(config) + # Register commands + gcode = config.get_printer().lookup_object('gcode') + gcode.register_command("M3", self.cmd_M3_M4) + gcode.register_command("M4", self.cmd_M3_M4) + gcode.register_command("M5", self.cmd_M5) + def get_status(self, eventtime): + return self.tool.get_status(eventtime) + def cmd_M3_M4(self, gcmd): + # Set spindle speed + value = gcmd.get_float('S', 255., minval=0.) / 255. + self.tool.set_value_from_command(value) + def cmd_M5(self, gcmd): + # Turn spindle off + self.tool.set_value_from_command(0.) + +def load_config(config): + return PrinterSpindle(config)