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 all 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).
#maximum_mcu_duration:
# 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
2 changes: 2 additions & 0 deletions docs/Overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ communication with the Klipper developers.
sensorless homing.
- [Skew correction](skew_correction.md): Adjustments for axes not
perfectly square.
- [PWM tools](Using_PWM_Tools.md): Guide on how to use PWM controlled
tools such as lasers or spindles.
- [G-Codes](G-Codes.md): Information on commands supported by Klipper.

# Developer Documentation
Expand Down
67 changes: 67 additions & 0 deletions docs/Using_PWM_Tools.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
This document describes how to setup a PWM-controlled laser or spindle
using `output_pin` and some macros.


## How does it work?
With re-purposing the printhead's fan pwm output, you can control
lasers or spindles.
This is useful if you use switchable print heads, for example
the E3D toolchanger or a DIY solution.
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`).


**Warning:** When driving a laser, keep all security precautions
that you can think of! Diode lasers are usually inverted.
This means, that when the MCU restarts, the laser will be
_fully on_ for the time it takes the MCU to start up again.
For good measure, it is recommended to _always_ wear appropriate
laser-goggles of the right wavelength if the laser is powered;
and to disconnect the laser when it is not needed.
Also, you should configure a safety timeout,
so that when your host or MCU encounters an error, the tool will stop.

For an example configuration, see `config/sample-pwm-tool-cfg`.

## Current Limitations

There is a limitation of how frequent PWM updates may occur.
While being very precise, a PWM update may only occur every 0.1 seconds,
rendering it almost useless for raster engraving.
However, there exists an [experimental branch](https://github.com/Cirromulus/klipper/tree/laser_tool) with its own tradeoffs.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this mention ok for you, or shall this be kept out until the PR is actually started?

In long term, it is planned to add this functionality to main-line klipper.

## Commands

`M3/M4 S<value>` : Set PWM duty-cycle. Values between 0 and 255.
`M5` : Stop PWM output to shutdown value.

## Laserweb Configuration

If you use Laserweb, a working configuration would be:

GCODE START:
M5 ; Disable Laser
G21 ; Set units to mm
G90 ; Absolute positioning
G0 Z0 F7000 ; Set Non-Cutting speed

GCODE END:
M5 ; Disable Laser
G91 ; relative
G0 Z+20 F4000 ;
G90 ; absolute

GCODE HOMING:
M5 ; Disable Laser
G28 ; Home all axis

TOOL ON:
M3 $INTENSITY

TOOL OFF:
M5 ; Disable Laser

LASER INTENSITY:
S
38 changes: 33 additions & 5 deletions klippy/extras/output_pin.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,30 +22,39 @@ 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)
self.reactor = self.printer.get_reactor()
self.resend_timer = None
self.resend_interval = 0
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.max_mcu_duration = config.getfloat('maximum_mcu_duration',
0, minval=0.500)
self.mcu_pin.setup_max_duration(self.max_mcu_duration)
self.resend_interval = .8 * self.max_mcu_duration - PIN_MIN_TIME

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 +63,9 @@ 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.max_mcu_duration != 0 and self.resend_timer is None:
self.resend_timer = self.reactor.register_timer(
self._resend_current_val, self.reactor.NOW)
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 +78,21 @@ 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):
if self.last_value == self.shutdown_value:
self.reactor.unregister_timer(self.resend_timer)
self.resend_timer = None
return self.reactor.NEVER

systime = self.reactor.monotonic()
print_time = self.mcu_pin.get_mcu().estimated_print_time(systime)
time_diff = print_time - (self.last_print_time + self.resend_interval)
if time_diff > 0.:
# Reschedule for resend time
return systime + time_diff
self._set_pin(print_time + PIN_MIN_TIME,
self.last_value, self.last_cycle_time, True)
return systime + self.resend_interval

def load_config_prefix(config):
return PrinterOutputPin(config)