-
-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
pwm_tool: Add support for high-speed PWM pin updates
The output_pin module is only capable of updating an output pin at most once every 100ms. Add a new pwm_tool module that is capable of queuing updates in the micro-controller and thus allowing for much higher update rates. Signed-off-by: Kevin O'Connor <[email protected]>
- Loading branch information
1 parent
fe6c464
commit 41dd3e6
Showing
8 changed files
with
201 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
# Queued PWM gpio output | ||
# | ||
# Copyright (C) 2017-2023 Kevin O'Connor <[email protected]> | ||
# | ||
# This file may be distributed under the terms of the GNU GPLv3 license. | ||
import chelper | ||
|
||
PIN_MIN_TIME = 0.100 | ||
MAX_SCHEDULE_TIME = 5.0 | ||
|
||
class error(Exception): | ||
pass | ||
|
||
class MCU_queued_pwm: | ||
def __init__(self, pin_params): | ||
self._mcu = pin_params['chip'] | ||
self._hardware_pwm = False | ||
self._cycle_time = 0.100 | ||
self._max_duration = 2. | ||
self._oid = self._mcu.create_oid() | ||
ffi_main, ffi_lib = chelper.get_ffi() | ||
self._stepqueue = ffi_main.gc(ffi_lib.stepcompress_alloc(self._oid), | ||
ffi_lib.stepcompress_free) | ||
self._mcu.register_stepqueue(self._stepqueue) | ||
self._stepcompress_queue_mq_msg = ffi_lib.stepcompress_queue_mq_msg | ||
self._mcu.register_config_callback(self._build_config) | ||
self._pin = pin_params['pin'] | ||
self._invert = pin_params['invert'] | ||
self._start_value = self._shutdown_value = float(self._invert) | ||
self._last_clock = self._cycle_ticks = 0 | ||
self._pwm_max = 0. | ||
self._set_cmd_tag = None | ||
def get_mcu(self): | ||
return self._mcu | ||
def setup_max_duration(self, max_duration): | ||
self._max_duration = max_duration | ||
def setup_cycle_time(self, cycle_time, hardware_pwm=False): | ||
self._cycle_time = cycle_time | ||
self._hardware_pwm = hardware_pwm | ||
def setup_start_value(self, start_value, shutdown_value): | ||
if self._invert: | ||
start_value = 1. - start_value | ||
shutdown_value = 1. - shutdown_value | ||
self._start_value = max(0., min(1., start_value)) | ||
self._shutdown_value = max(0., min(1., shutdown_value)) | ||
def _build_config(self): | ||
if self._max_duration and self._start_value != self._shutdown_value: | ||
raise pins.error("Pin with max duration must have start" | ||
" value equal to shutdown value") | ||
cmd_queue = self._mcu.alloc_command_queue() | ||
curtime = self._mcu.get_printer().get_reactor().monotonic() | ||
printtime = self._mcu.estimated_print_time(curtime) | ||
self._last_clock = self._mcu.print_time_to_clock(printtime + 0.200) | ||
cycle_ticks = self._mcu.seconds_to_clock(self._cycle_time) | ||
mdur_ticks = self._mcu.seconds_to_clock(self._max_duration) | ||
if mdur_ticks >= 1<<31: | ||
raise pins.error("PWM pin max duration too large") | ||
if self._hardware_pwm: | ||
self._pwm_max = self._mcu.get_constant_float("PWM_MAX") | ||
self._mcu.add_config_cmd( | ||
"config_pwm_out oid=%d pin=%s cycle_ticks=%d value=%d" | ||
" default_value=%d max_duration=%d" | ||
% (self._oid, self._pin, cycle_ticks, | ||
self._start_value * self._pwm_max, | ||
self._shutdown_value * self._pwm_max, mdur_ticks)) | ||
svalue = int(self._start_value * self._pwm_max + 0.5) | ||
self._mcu.add_config_cmd("queue_pwm_out oid=%d clock=%d value=%d" | ||
% (self._oid, self._last_clock, svalue), | ||
on_restart=True) | ||
self._set_cmd_tag = self._mcu.lookup_command( | ||
"queue_pwm_out oid=%c clock=%u value=%hu", | ||
cq=cmd_queue).get_command_tag() | ||
return | ||
# Software PWM | ||
if self._shutdown_value not in [0., 1.]: | ||
raise pins.error("shutdown value must be 0.0 or 1.0 on soft pwm") | ||
if cycle_ticks >= 1<<31: | ||
raise pins.error("PWM pin cycle time too large") | ||
self._mcu.add_config_cmd( | ||
"config_digital_out oid=%d pin=%s value=%d" | ||
" default_value=%d max_duration=%d" | ||
% (self._oid, self._pin, self._start_value >= 1.0, | ||
self._shutdown_value >= 0.5, mdur_ticks)) | ||
self._mcu.add_config_cmd( | ||
"set_digital_out_pwm_cycle oid=%d cycle_ticks=%d" | ||
% (self._oid, cycle_ticks)) | ||
self._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" | ||
% (self._oid, self._last_clock, svalue), is_init=True) | ||
self._set_cmd_tag = self._mcu.lookup_command( | ||
"queue_digital_out oid=%c clock=%u on_ticks=%u", | ||
cq=cmd_queue).get_command_tag() | ||
def set_pwm(self, print_time, value): | ||
clock = self._mcu.print_time_to_clock(print_time) | ||
minclock = self._last_clock | ||
self._last_clock = clock | ||
if self._invert: | ||
value = 1. - value | ||
max_count = self._cycle_ticks | ||
if self._hardware_pwm: | ||
max_count = self._pwm_max | ||
v = int(max(0., min(1., value)) * max_count + 0.5) | ||
data = (self._set_cmd_tag, self._oid, clock, v) | ||
ret = self._stepcompress_queue_mq_msg(self._stepqueue, clock, | ||
data, len(data)) | ||
if ret: | ||
raise error("Internal error in stepcompress") | ||
|
||
class PrinterOutputPin: | ||
def __init__(self, config): | ||
self.printer = config.get_printer() | ||
ppins = self.printer.lookup_object('pins') | ||
# Determine pin type | ||
pin_params = ppins.lookup_pin(config.get('pin'), can_invert=True) | ||
self.mcu_pin = MCU_queued_pwm(pin_params) | ||
cycle_time = config.getfloat('cycle_time', 0.100, above=0., | ||
maxval=MAX_SCHEDULE_TIME) | ||
hardware_pwm = config.getboolean('hardware_pwm', False) | ||
self.mcu_pin.setup_cycle_time(cycle_time, hardware_pwm) | ||
self.scale = config.getfloat('scale', 1., above=0.) | ||
self.last_print_time = 0. | ||
self.mcu_pin.setup_max_duration(0.) | ||
# Determine start and shutdown values | ||
self.last_value = config.getfloat( | ||
'value', 0., minval=0., maxval=self.scale) / self.scale | ||
self.shutdown_value = config.getfloat( | ||
'shutdown_value', 0., minval=0., maxval=self.scale) / self.scale | ||
self.mcu_pin.setup_start_value(self.last_value, self.shutdown_value) | ||
# Register commands | ||
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): | ||
if value == self.last_value: | ||
return | ||
print_time = max(print_time, self.last_print_time) | ||
self.mcu_pin.set_pwm(print_time, value) | ||
self.last_value = value | ||
self.last_print_time = print_time | ||
cmd_SET_PIN_help = "Set the value of an output pin" | ||
def cmd_SET_PIN(self, gcmd): | ||
# Read requested value | ||
value = gcmd.get_float('VALUE', minval=0., maxval=self.scale) | ||
value /= self.scale | ||
# Obtain print_time and apply requested settings | ||
toolhead = self.printer.lookup_object('toolhead') | ||
toolhead.register_lookahead_callback( | ||
lambda print_time: self._set_pin(print_time, value)) | ||
|
||
def load_config_prefix(config): | ||
return PrinterOutputPin(config) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters