servo: sync pwm clock times

Arriving of SW PWM out of sync
can cause pulse width distortion - make them longer
Synchronize the update clock to avoid that

Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
Timofey Titovets
2025-12-03 01:12:53 +01:00
committed by Kevin O'Connor
parent f9108496a1
commit 2b4c55ffd1
6 changed files with 38 additions and 1 deletions

View File

@@ -46,6 +46,8 @@ class PrinterMultiPin:
def set_digital(self, print_time, value): def set_digital(self, print_time, value):
for mcu_pin in self.mcu_pins: for mcu_pin in self.mcu_pins:
mcu_pin.set_digital(print_time, value) mcu_pin.set_digital(print_time, value)
def next_aligned_print_time(self, print_time, allow_early=0.):
return print_time
def set_pwm(self, print_time, value): def set_pwm(self, print_time, value):
for mcu_pin in self.mcu_pins: for mcu_pin in self.mcu_pins:
mcu_pin.set_pwm(print_time, value) mcu_pin.set_pwm(print_time, value)

View File

@@ -46,6 +46,11 @@ class GCodeRequestQueue:
if action == "discard": if action == "discard":
del rqueue[:pos+1] del rqueue[:pos+1]
continue continue
if action == "reschedule":
del rqueue[:pos]
self.next_min_flush_time = max(self.next_min_flush_time,
min_wait)
continue
if action == "delay": if action == "delay":
pos -= 1 pos -= 1
del rqueue[:pos+1] del rqueue[:pos+1]
@@ -75,6 +80,10 @@ class GCodeRequestQueue:
action, min_wait = ret action, min_wait = ret
if action == "discard": if action == "discard":
break break
if action == "reschedule":
self.next_min_flush_time = max(self.next_min_flush_time,
min_wait)
continue
self.next_min_flush_time = next_time + max(min_wait, min_sched_time) self.next_min_flush_time = next_time + max(min_wait, min_sched_time)
if action != "delay": if action != "delay":
break break

View File

@@ -67,6 +67,8 @@ class pca9685_pwm:
cmd_queue = self._mcu.alloc_command_queue() cmd_queue = self._mcu.alloc_command_queue()
self._set_cmd = self._mcu.lookup_command( self._set_cmd = self._mcu.lookup_command(
"queue_pca9685_out oid=%c clock=%u value=%hu", cq=cmd_queue) "queue_pca9685_out oid=%c clock=%u value=%hu", cq=cmd_queue)
def next_aligned_print_time(self, print_time, allow_early=0.):
return print_time
def set_pwm(self, print_time, value): def set_pwm(self, print_time, value):
clock = self._mcu.print_time_to_clock(print_time) clock = self._mcu.print_time_to_clock(print_time)
if self._invert: if self._invert:

View File

@@ -6,6 +6,7 @@
from . import output_pin from . import output_pin
SERVO_SIGNAL_PERIOD = 0.020 SERVO_SIGNAL_PERIOD = 0.020
RESCHEDULE_SLACK = 0.000500
class PrinterServo: class PrinterServo:
def __init__(self, config): def __init__(self, config):
@@ -47,8 +48,12 @@ class PrinterServo:
def _set_pwm(self, print_time, value): def _set_pwm(self, print_time, value):
if value == self.last_value: if value == self.last_value:
return "discard", 0. return "discard", 0.
aligned_ptime = self.mcu_servo.next_aligned_print_time(print_time,
RESCHEDULE_SLACK)
if aligned_ptime > print_time + RESCHEDULE_SLACK:
return "reschedule", aligned_ptime
self.last_value = value self.last_value = value
self.mcu_servo.set_pwm(print_time, value) self.mcu_servo.set_pwm(aligned_ptime, value)
def _get_pwm_from_angle(self, angle): def _get_pwm_from_angle(self, angle):
angle = max(0., min(self.max_angle, angle)) angle = max(0., min(self.max_angle, angle))
width = self.min_width + angle * self.angle_to_width width = self.min_width + angle * self.angle_to_width

View File

@@ -178,6 +178,8 @@ class SX1509_pwm(object):
self._shutdown_value = max(0., min(1., shutdown_value)) self._shutdown_value = max(0., min(1., shutdown_value))
self._sx1509.set_register(self._i_on_reg, self._sx1509.set_register(self._i_on_reg,
~int(255 * self._start_value) & 0xFF) ~int(255 * self._start_value) & 0xFF)
def next_aligned_print_time(self, print_time, allow_early=0.):
return print_time
def set_pwm(self, print_time, value): def set_pwm(self, print_time, value):
self._sx1509.set_register(self._i_on_reg, ~int(255 * value) self._sx1509.set_register(self._i_on_reg, ~int(255 * value)
if not self._invert if not self._invert

View File

@@ -422,6 +422,7 @@ class MCU_pwm:
self._invert = pin_params['invert'] self._invert = pin_params['invert']
self._start_value = self._shutdown_value = float(self._invert) self._start_value = self._shutdown_value = float(self._invert)
self._last_clock = 0 self._last_clock = 0
self._last_value = .0
self._pwm_max = 0. self._pwm_max = 0.
self._set_cmd = None self._set_cmd = None
def get_mcu(self): def get_mcu(self):
@@ -437,6 +438,7 @@ class MCU_pwm:
shutdown_value = 1. - shutdown_value shutdown_value = 1. - shutdown_value
self._start_value = max(0., min(1., start_value)) self._start_value = max(0., min(1., start_value))
self._shutdown_value = max(0., min(1., shutdown_value)) self._shutdown_value = max(0., min(1., shutdown_value))
self._last_value = self._start_value
def _build_config(self): def _build_config(self):
if self._max_duration and self._start_value != self._shutdown_value: if self._max_duration and self._start_value != self._shutdown_value:
raise pins.error("Pin with max duration must have start" raise pins.error("Pin with max duration must have start"
@@ -488,6 +490,20 @@ class MCU_pwm:
% (self._oid, self._last_clock, svalue), is_init=True) % (self._oid, self._last_clock, svalue), is_init=True)
self._set_cmd = self._mcu.lookup_command( self._set_cmd = self._mcu.lookup_command(
"queue_digital_out oid=%c clock=%u on_ticks=%u", cq=cmd_queue) "queue_digital_out oid=%c clock=%u on_ticks=%u", cq=cmd_queue)
def next_aligned_print_time(self, print_time, allow_early=0.):
# Filter cases where there is no need to sync anything
if self._hardware_pwm:
return print_time
if self._last_value == 1. or self._last_value == .0:
return print_time
# Simplify the calling and allow scheduling slightly earlier
req_ptime = print_time - min(allow_early, 0.5 * self._cycle_time)
cycle_ticks = self._mcu.seconds_to_clock(self._cycle_time)
req_clock = self._mcu.print_time_to_clock(req_ptime)
last_clock = self._last_clock
pulses = (req_clock - last_clock + cycle_ticks - 1) // cycle_ticks
next_clock = last_clock + pulses * cycle_ticks
return self._mcu.clock_to_print_time(next_clock)
def set_pwm(self, print_time, value): def set_pwm(self, print_time, value):
if self._invert: if self._invert:
value = 1. - value value = 1. - value
@@ -496,6 +512,7 @@ class MCU_pwm:
self._set_cmd.send([self._oid, clock, v], self._set_cmd.send([self._oid, clock, v],
minclock=self._last_clock, reqclock=clock) minclock=self._last_clock, reqclock=clock)
self._last_clock = clock self._last_clock = clock
self._last_value = value
class MCU_adc: class MCU_adc:
def __init__(self, mcu, pin_params): def __init__(self, mcu, pin_params):