Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 'tool' module #3995

Merged
merged 14 commits into from
Mar 26, 2021
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/Config_Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -2484,6 +2484,13 @@ pin:
#shutdown_value:
# The value to set the pin to on an MCU shutdown event. The default
# is 0 (for low voltage).
#host_acknowledge_timeout:
Cirromulus marked this conversation as resolved.
Show resolved Hide resolved
# The maximum duration a non-shutdown value may be driven by the MCU
# without an acknowledge from the host.
# If host can not keep up with an update, the MCU will shutdown
# and set all pins to their respective shutdown values.
# Default: 0 (disabled)
# Usual values are around 5 seconds.
#cycle_time: 0.100
# The amount of time (in seconds) per PWM cycle. It is recommended
# this be 10 milliseconds or greater when using software based PWM.
Expand Down
50 changes: 50 additions & 0 deletions docs/Using_PWM_Tools.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
This document describes how to setup a PWM-Controlled laser or spindle
using `output_pin` and some macros.
Cirromulus marked this conversation as resolved.
Show resolved Hide resolved


## How does it work?
With re-purposing the printhead's fan pwm output, you can control
lasers or spindles.
Usually, cam-tools such as LaserWeb can be configured to use `M3-M5`
commands, which stand for _spindle speed CW_ (`M3 S[0-255]`),
_spindle speed CCW_ (`M4 S[0-255]`) and _spindle stop_ (`M5`).

For safety reasons, you should configure a safety timeout,
so that when your host or MCU encounters an error, the tool will stop.


## Configuration
[output_pin TOOL]
pin: !ar9
pwm: True
hardware_pwm: False
cycle_time: 0.001
shutdown_value: 0

host_acknowledge_timeout: 5
# Default: 0 (disabled)
# Amount of time in which the host has to acknowledge
# a non-shutdown output value.
# Suggested value is around 5 seconds.
# Use a value that does not burn up your stock.
# Please note that during homing, your tool
# needs to be in default speed.

[gcode_macro M3]
default_parameter_S: 0
gcode:
SET_PIN PIN=TOOL VALUE={S|float / 255}

[gcode_macro M4]
default_parameter_S: 0
gcode:
SET_PIN PIN=TOOL VALUE={S|float / 255}

[gcode_macro M5]
gcode:
SET_PIN PIN=TOOL VALUE=0

## Commands

`M3/M4 S<value>` : Set PWM duty-cycle. Values between 0 and 255.
`M5` : Stop PWM output to shutdown value.
39 changes: 34 additions & 5 deletions klippy/extras/output_pin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

PIN_MIN_TIME = 0.100


Cirromulus marked this conversation as resolved.
Show resolved Hide resolved
class PrinterOutputPin:
def __init__(self, config):
self.printer = config.get_printer()
Expand All @@ -22,30 +23,41 @@ def __init__(self, config):
self.mcu_pin = ppins.setup_pin('digital_out', config.get('pin'))
self.scale = 1.
self.last_cycle_time = self.default_cycle_time = 0.
self.mcu_pin.setup_max_duration(0.)
self.last_print_time = 0.
static_value = config.getfloat('static_value', None,
minval=0., maxval=self.scale)
if static_value is not None:
self.mcu_pin.setup_max_duration(0.)
self.last_value = static_value / self.scale
self.mcu_pin.setup_start_value(
self.last_value, self.last_value, True)
else:
self.reactor = self.printer.get_reactor()
Cirromulus marked this conversation as resolved.
Show resolved Hide resolved
self.host_ack_timeout = config.getfloat('host_acknowledge_timeout',
0, minval=0.)
#ensure that safety timeout is big enough for comm. latency
if self.host_ack_timeout > 0:
self.host_ack_timeout = max(self.host_ack_timeout, 0.500)
Cirromulus marked this conversation as resolved.
Show resolved Hide resolved
self.mcu_pin.setup_max_duration(self.host_ack_timeout)
self.resend_timer = self.reactor.register_timer(
self._resend_current_val)

self.last_value = config.getfloat(
'value', 0., minval=0., maxval=self.scale) / self.scale
shutdown_value = config.getfloat(
self.shutdown_value = config.getfloat(
'shutdown_value', 0., minval=0., maxval=self.scale) / self.scale
self.mcu_pin.setup_start_value(self.last_value, shutdown_value)
self.mcu_pin.setup_start_value(self.last_value, self.shutdown_value)
pin_name = config.get_name().split()[1]
gcode = self.printer.lookup_object('gcode')
gcode.register_mux_command("SET_PIN", "PIN", pin_name,
self.cmd_SET_PIN,
desc=self.cmd_SET_PIN_help)
def get_status(self, eventtime):
return {'value': self.last_value}
def _set_pin(self, print_time, value, cycle_time):
def _set_pin(self, print_time, value, cycle_time, is_resend=False):
if value == self.last_value and cycle_time == self.last_cycle_time:
return
if not is_resend:
return
print_time = max(print_time, self.last_print_time + PIN_MIN_TIME)
if self.is_pwm:
self.mcu_pin.set_pwm(print_time, value, cycle_time)
Expand All @@ -54,6 +66,14 @@ def _set_pin(self, print_time, value, cycle_time):
self.last_value = value
self.last_cycle_time = cycle_time
self.last_print_time = print_time
if self.host_ack_timeout != 0 and not is_resend:
if value == self.shutdown_value:
self.reactor.update_timer(
self.resend_timer, self.reactor.NEVER)
else:
self.reactor.update_timer(
self.resend_timer, self.reactor.monotonic()
Cirromulus marked this conversation as resolved.
Show resolved Hide resolved
+ (0.8 * self.host_ack_timeout) - PIN_MIN_TIME)
cmd_SET_PIN_help = "Set the value of an output pin"
def cmd_SET_PIN(self, gcmd):
value = gcmd.get_float('VALUE', minval=0., maxval=self.scale)
Expand All @@ -66,5 +86,14 @@ def cmd_SET_PIN(self, gcmd):
toolhead.register_lookahead_callback(
lambda print_time: self._set_pin(print_time, value, cycle_time))

def _resend_current_val(self, eventtime):
print_time = self.mcu_pin.get_mcu().estimated_print_time(eventtime)
self._set_pin(print_time + PIN_MIN_TIME,
self.last_value, self.last_cycle_time, True)

if self.last_value != self.shutdown_value:
return eventtime + (0.8 * self.host_ack_timeout) - PIN_MIN_TIME
return self.reactor.NEVER

def load_config_prefix(config):
return PrinterOutputPin(config)