Servo Only Works Once

Basic Information:

Printer Model: Ender5 Plus + TradRack
MCU / Printerboard: SKR Pico
Host / SBC: BTTPi

I’ve just rebuilt my old ender 5,and am adding a TradRack MMU. In doing some bench tests on the unit, I’ve come across an issue where I can only adjust the servo position once. Looking through the klippy.log showed nothing. So, I spent my morning digging in, and have come to some understanding of what is happening.

As far as I can tell, this stems from a recent pull request that adds some sort of rescheduling of the setting of the servo pwm. Commenting out the return statement @line 58 in servo.py allows expected functionality to resume, but I don’t know what else it may impact, as I’m sure this code wasn’t added without it resolving some issue or meeting some need.

Hmm, I’ve tested my servo just now:

SET_SERVO SERVO=servo ANGLE=10
SET_SERVO SERVO=servo ANGLE=60
SET_SERVO SERVO=servo ANGLE=15
SET_SERVO SERVO=servo ANGLE=180

And it seems to work with the current code.

Are you sure that you have the correct angle/pulse_width options

[servo servo]
pin: gpio24
#maximum_servo_angle: 180
minimum_pulse_width: 0.0005
maximum_pulse_width: 0.0025

(They will differ for your servo).

Thanks.

That’s my fault. I’m commenting out line 54 the return "reschedule", aligned_ptime in _set_pwm(forgot to account for some debug logging I added).

Since I did this, I’ve been able to calibrate and test the TradRack completely. Maybe I should try to reflash the skr pico. Its the only component in this setup that isn’t new out of box.

So. Did that, and reverted (mostly) the code…..

Running

TR_servo_test angle=130
TR_servo_test angle=100
TR_servo_test angle=30

I get this logging

klipper@printtest:~/klipper/klippy/extras$ tail -f ~/printer_data/logs/klippy.log
MCU 'mcu' config: ADC_MAX=4095 CLOCK_FREQ=50000000 MCU=linux PCA9685_MAX=4096 PWM_MAX=32768 STATS_SUMSQ_BASE=256 STEPPER_STEP_BOTH_EDGE=1
mcu 'tr': Starting serial connect
Loaded MCU 'tr' 132 commands (v0.13.0-449-gdd625933-dirty-20251231_172344-printtest / gcc: (15:12.2.rel1-1) 12.2.1 20221205 binutils: (2.40-2+18+b1) 2.40)
MCU 'tr' config: ADC_MAX=4095 BUS_PINS_i2c0a=gpio0,gpio1 BUS_PINS_i2c0b=gpio4,gpio5 BUS_PINS_i2c0c=gpio8,gpio9 BUS_PINS_i2c0d=gpio12,gpio13 BUS_PINS_i2c0e=gpio16,gpio17 BUS_PINS_i2c0f=gpio20,gpio21 BUS_PINS_i2c0g=gpio24,gpio25 BUS_PINS_i2c0h=gpio28,gpio29 BUS_PINS_i2c1a=gpio2,gpio3 BUS_PINS_i2c1b=gpio6,gpio7 BUS_PINS_i2c1c=gpio10,gpio11 BUS_PINS_i2c1d=gpio14,gpio15 BUS_PINS_i2c1e=gpio18,gpio19 BUS_PINS_i2c1f=gpio22,gpio23 BUS_PINS_i2c1g=gpio26,gpio27 BUS_PINS_spi0_gpio0_gpio3_gpio2=gpio0,gpio3,gpio2 BUS_PINS_spi0_gpio16_gpio19_gpio18=gpio16,gpio19,gpio18 BUS_PINS_spi0_gpio20_gpio23_gpio22=gpio20,gpio23,gpio22 BUS_PINS_spi0_gpio4_gpio3_gpio2=gpio4,gpio3,gpio2 BUS_PINS_spi0_gpio4_gpio7_gpio6=gpio4,gpio7,gpio6 BUS_PINS_spi0a=gpio0,gpio3,gpio2 BUS_PINS_spi0b=gpio4,gpio7,gpio6 BUS_PINS_spi0c=gpio16,gpio19,gpio18 BUS_PINS_spi0d=gpio20,gpio23,gpio22 BUS_PINS_spi1_gpio12_gpio11_gpio10=gpio12,gpio11,gpio10 BUS_PINS_spi1_gpio12_gpio15_gpio14=gpio12,gpio15,gpio14 BUS_PINS_spi1_gpio24_gpio27_gpio26=gpio24,gpio27,gpio26 BUS_PINS_spi1_gpio8_gpio11_gpio10=gpio8,gpio11,gpio10 BUS_PINS_spi1a=gpio8,gpio11,gpio10 BUS_PINS_spi1b=gpio12,gpio15,gpio14 BUS_PINS_spi1c=gpio24,gpio27,gpio26 CLOCK_FREQ=12000000 MCU=rp2040 PWM_MAX=255 STATS_SUMSQ_BASE=256 STEPPER_OPTIMIZED_EDGE=2 STEPPER_STEP_BOTH_EDGE=1
Configured MCU 'mcu' (1024 moves)
Configured MCU 'tr' (1024 moves)
webhooks: registering remote method 'shutdown_machine' for connection id: 281473749530512
webhooks: registering remote method 'reboot_machine' for connection id: 281473749530512
webhooks: registering remote method 'pause_job_queue' for connection id: 281473749530512
webhooks: registering remote method 'start_job_queue' for connection id: 281473749530512
commanded angle: 130.000
raw angle: 130.000
trad_rack: set servo to 130.0
trad_rack: pwm is 0.11935114503816792
in _set_pwm
servo: setting pwm
commanded angle: 100.000
raw angle: 100.000
trad_rack: set servo to 100.0
trad_rack: pwm is 0.09988549618320611
in _set_pwm
slack 1421.5677815353524
commanded angle: 30.000
raw angle: 30.000
trad_rack: set servo to 30.0
trad_rack: pwm is 0.05446564885496183
in _set_pwm
slack 1436.1474302191154

With this code from servo.py

    def _set_pwm(self, print_time, value):
        logging.info('in _set_pwm')
        if value == self.last_value:
            logging.info('discarding pwm')
            return "discard", 0.
        aligned_ptime = self.mcu_servo.next_aligned_print_time(print_time,
                                                               RESCHEDULE_SLACK)
        if aligned_ptime > print_time + RESCHEDULE_SLACK:
            logging.info(f'slack {aligned_ptime}')
            return "reschedule", aligned_ptime
        self.last_value = value
        logging.info('servo: setting pwm')
        self.mcu_servo.set_pwm(aligned_ptime, value)
    def _get_pwm_from_angle(self, angle):

This should be the current code with logging.info lines added. If things were working as I expected, there would be a setting pwm message for every in _set_pwm message.

Again, commenting out the return “reschedule”, aligned_ptime line gets me both the correct behavior and logs.

And thank you for taking the time. Happy New Year

3 Likes
diff --git a/klippy/extras/servo.py b/klippy/extras/servo.py
index 303e39377..b6a142a3c 100644
--- a/klippy/extras/servo.py
+++ b/klippy/extras/servo.py
@@ -46,13 +46,17 @@ class PrinterServo:
     def get_status(self, eventtime):
         return {'value': self.last_value}
     def _set_pwm(self, print_time, value):
+        logging.info('in _set_pwm')
         if value == self.last_value:
+            logging.info('discarding pwm')
             return "discard", 0.
         aligned_ptime = self.mcu_servo.next_aligned_print_time(print_time,
                                                                RESCHEDULE_SLACK)
         if aligned_ptime > print_time + RESCHEDULE_SLACK:
+            logging.info(f'slack {aligned_ptime}')
             return "reschedule", aligned_ptime
         self.last_value = value
+        logging.info('servo: setting pwm')
         self.mcu_servo.set_pwm(aligned_ptime, value)
     def _get_pwm_from_angle(self, angle):
         angle = max(0., min(self.max_angle, angle))
INFO:root:in _set_pwm
INFO:root:servo: setting pwm
INFO:root:in _set_pwm
INFO:root:servo: setting pwm
INFO:root:in _set_pwm
INFO:root:slack 20.12747025
INFO:root:in _set_pwm
INFO:root:discarding pwm
INFO:root:in _set_pwm
INFO:root:slack 23.60747025
INFO:root:in _set_pwm
INFO:root:discarding pwm
INFO:root:in _set_pwm
INFO:root:slack 38.92747025
INFO:root:in _set_pwm
INFO:root:servo: setting pwm
INFO:root:in _set_pwm
INFO:root:slack 39.12747025
INFO:root:in _set_pwm
INFO:root:servo: setting pwm
INFO:root:in _set_pwm
INFO:root:slack 39.32747025
INFO:root:in _set_pwm
INFO:root:servo: setting pwm

If it helps.

I guess, if you run the commands one by one, they will be executed.
If you send them all at once, they will not, and only one last is applied

So, this one would do 1 move:

SET_SERVO SERVO=servo ANGLE=1 
SET_SERVO SERVO=servo ANGLE=15
SET_SERVO SERVO=servo ANGLE=30

Where this one will do all 3 (200ms time actually to do something):

SET_SERVO SERVO=servo ANGLE=1 
G4 P200
SET_SERVO SERVO=servo ANGLE=15
G4 P200
SET_SERVO SERVO=servo ANGLE=30

Hope that clarifies something.
Happy New Year!

-Timofey

2 Likes