Skip to content

Commit

Permalink
[Doc] Include safety output pin
Browse files Browse the repository at this point in the history
Signed-off-by: Pascal Pieper <[email protected]>
  • Loading branch information
Cirromulus committed Sep 7, 2023
1 parent 3b7e546 commit d55d303
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 40 deletions.
13 changes: 10 additions & 3 deletions config/sample-pwm-tool.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# See docs/Using_PWM_Tools.md for a more detailed description.

[output_pin TOOL]
pin: !ar9 # use your fan's pin number
pin: !PIN # use your fan's pin number
pwm: True
#hardware_pwm: True
# Using hardware PWM is optional.
Expand All @@ -27,33 +27,40 @@ high_throughput: True
# needs to be in default speed (off), or else the MCU will
# do a safety-shutdown.

[output_pin TOOL_ENABLE]
pin: !OTHER_PIN
shutdown_value: 0

[gcode_macro M3]
gcode:
SET_PIN PIN=TOOL_ENABLE VALUE=1
{% set S = params.S|default(0.0)|float %}
SET_PIN PIN=TOOL VALUE={S / 255.0}

[gcode_macro M4]
gcode:
SET_PIN PIN=TOOL_ENABLE VALUE=1
{% set S = params.S|default(0.0)|float %}
SET_PIN PIN=TOOL VALUE={S / 255.0}

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


# Optional: LCD Menu Control

[menu __main __control __toolonoff]
type: input
enable: {'output_pin TOOL' in printer}
name: Fan: {'ON ' if menu.input else 'OFF'}
name: Tool: {'ON ' if menu.input else 'OFF'}
input: {printer['output_pin TOOL'].value}
input_min: 0
input_max: 1
input_step: 1
gcode:
M3 S{255 if menu.input else 0}
SET_PIN PIN=TOOL_ENABLE VALUE={1 if menu.input else 0}

[menu __main __control __toolspeed]
type: input
Expand Down
2 changes: 2 additions & 0 deletions docs/Using_PWM_Tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ laser-goggles of the right wavelength if the laser is powered;
and to disconnect the laser when it is not needed.
Also, you should consider using a safety timeout,
so that when your host dies unexpectedly, the tool will stop.
An additional safety layer would be powering your tool through the hotend-heater pin.
In the configuration, the additional `[output_pin]` would be activated by `M3/M4` and disabled by `M5`.

For an example configuration, see [config/sample-pwm-tool.cfg](/config/sample-pwm-tool.cfg).

Expand Down
93 changes: 56 additions & 37 deletions klippy/mcu.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,10 +424,9 @@ def _build_config(self):
self._start_value * self._pwm_max))
return
self._oid = self._mcu.create_oid()
cmd_queue = None
if not self._is_ht:
self._mcu.request_move_queue_slot()
cmd_queue = self._mcu.alloc_command_queue()
cmd_queue = self._mcu.alloc_command_queue(
uses_move_queue=True, high_throughput=self._is_ht
)

self._mcu.add_config_cmd(
"config_pwm_out oid=%d pin=%s cycle_ticks=%d value=%d"
Expand All @@ -441,7 +440,7 @@ def _build_config(self):
on_restart=True)
self._set_cmd = self._mcu.lookup_command(
"queue_pwm_out oid=%c clock=%u value=%hu", cq=cmd_queue,
fast_track=self._is_ht)
high_throughput=self._is_ht)
return
# Software PWM
if self._shutdown_value not in [0., 1.]:
Expand All @@ -453,10 +452,9 @@ def _build_config(self):
if cycle_ticks >= 1<<31:
raise pins.error("PWM pin cycle time too large")
self._oid = self._mcu.create_oid()
cmd_queue = None
if not self._is_ht:
self._mcu.request_move_queue_slot()
cmd_queue = self._mcu.alloc_command_queue()
cmd_queue = self._mcu.alloc_command_queue(
uses_move_queue=True, high_throughput=self._is_ht
)
self._mcu.add_config_cmd(
"config_digital_out oid=%d pin=%s value=%d"
" default_value=%d max_duration=%d"
Expand All @@ -468,18 +466,19 @@ def _build_config(self):
self._last_cycle_ticks = cycle_ticks
svalue = int(self._start_value * cycle_ticks + 0.5)
self._mcu.add_config_cmd(
"queue_digital_out oid=%d clock=%d on_ticks=%d"
"queue_digital_out oid=%d clock=%d on_ticks=%u"
% (self._oid, self._last_clock, svalue), is_init=True)
self._set_cmd = self._mcu.lookup_command(
"queue_digital_out oid=%c clock=%u on_ticks=%u", cq=cmd_queue,
fast_track=self._is_ht)
high_throughput=self._is_ht)
self._set_cycle_ticks = self._mcu.lookup_command(
"set_digital_out_pwm_cycle oid=%c cycle_ticks=%u", cq=cmd_queue)
"set_digital_out_pwm_cycle oid=%c cycle_ticks=%u", cq=cmd_queue,
high_throughput=self._is_ht) # this is a lie
def set_pwm(self, print_time, value, cycle_time=None):
req_clock = self._mcu.print_time_to_clock(print_time)
# FIXME: let sync_channel replace uncommitted values
# so that this is not necessary
minclock = self._last_clock
# sync_channel can't replace uncommitted values
# so this max is necessary
clock = max(req_clock, self._last_clock + self._min_clock_diff)
if self._invert:
value = 1. - value
Expand All @@ -496,14 +495,17 @@ def set_pwm(self, print_time, value, cycle_time=None):
if cycle_ticks >= 1<<31:
raise self._mcu.get_printer().command_error(
"PWM cycle time too large")

# Make sure move queue is flushed as
# cycle time updates are not queueable (yet)
# Value is a rough estimate and should be calculated
clock += self._mcu.print_time_to_clock(.100)

self._set_cycle_ticks.send([self._oid, cycle_ticks],
minclock=minclock, reqclock=clock)
# FIXME: this makes sure new cycle_ticks is applied
# _before_ new on_ticks arrive
clock = self._mcu.print_time_to_clock(print_time +
self._last_cycle_ticks)
self._last_cycle_ticks = cycle_ticks
self._min_clock_diff = cycle_ticks

on_ticks = int(max(0., min(1., value)) * float(cycle_ticks) + 0.5)
self._set_cmd.send([self._oid, clock & 0xFFFFFFFF, on_ticks],
minclock=minclock, reqclock=clock)
Expand Down Expand Up @@ -574,16 +576,20 @@ def _handle_analog_in_state(self, params):
######################################################################

class FastCommandWrapper:
def __init__(self, mcu, cmd_id, sync_channel, queue_msg_fn):
def __init__(self, mcu, cmd_id, cmd_queue, queue_msg_fn):
self._mcu = mcu
self._cmd_id = cmd_id
self._sync_channel = sync_channel
if not cmd_queue:
cmd_queue = alloc_command_queue(
uses_move_queue=True, high_throughput=True
)
self._sync_channel_obj = cmd_queue
self._queue_msg_fn = queue_msg_fn
self._reactor = mcu.get_printer().get_reactor()
self._toolhead = mcu.get_printer().lookup_object('toolhead')
def send(self, data, minclock, reqclock):
data.insert(0, self._cmd_id)
self._queue_msg_fn(self._sync_channel, data, len(data), reqclock)
self._queue_msg_fn(self._sync_channel_obj, data, len(data), reqclock)
print_time = self._mcu.clock_to_print_time(reqclock)
# TODO: Is here actually `_async_` needed?
self._reactor.register_async_callback(
Expand Down Expand Up @@ -645,7 +651,7 @@ def __init__(self, config, clocksync):

#
self._sync_channels = []
self._active_fasttrack_queues = 0
self._active_ht_queues = 0
#

# Stats
Expand Down Expand Up @@ -916,23 +922,36 @@ def get_name(self):
return self._name
def register_response(self, cb, msg, oid=None):
self._serial.register_response(cb, msg, oid)
def alloc_command_queue(self):
return self._serial.alloc_command_queue()
def lookup_command(self, msgformat, cq=None, fast_track=False):
if not fast_track:
return CommandWrapper(self._serial, msgformat, cq)

if self._active_fasttrack_queues > 0:
raise error ("Currently, only one high throughput pin is allowed!")
self._active_fasttrack_queues = self._active_fasttrack_queues + 1
sync_channel = self._ffi_main.gc(
def alloc_command_queue(self, uses_move_queue=False, high_throughput=False):
if high_throughput:
if not uses_move_queue:
raise error (
"high throughput mode only works with move_queue items!"
)
if self._active_ht_queues > 0:
raise error (
"Currently, only one high throughput pin "
"is currently supported!"
)
self._active_ht_queues = self._active_ht_queues + 1
cq = self._ffi_main.gc(
self._ffi_lib.sync_channel_alloc(),
self._ffi_lib.sync_channel_free)
self.register_sync_channel(sync_channel)
cmd_tag = self.lookup_command(msgformat).get_command_tag()
return FastCommandWrapper(self, cmd_tag,
sync_channel,
self._ffi_lib.sync_channel_queue_msg)
self.register_sync_channel(cq)
return cq
else:
# TODO: Automatically determine command's usage of move queue
if uses_move_queue:
self.request_move_queue_slot()
return self._serial.alloc_command_queue()

def lookup_command(self, msgformat, cq=None, high_throughput=False):
if high_throughput:
cmd_tag = self.lookup_command(msgformat).get_command_tag()
return FastCommandWrapper(self, cmd_tag, cq,
self._ffi_lib.sync_channel_queue_msg)
else:
return CommandWrapper(self._serial, msgformat, cq)

def lookup_query_command(self, msgformat, respformat, oid=None,
cq=None, is_async=False):
Expand Down

0 comments on commit d55d303

Please sign in to comment.