diff --git a/docs/Config_Changes.md b/docs/Config_Changes.md index b5212ce19930..cb031ee2b388 100644 --- a/docs/Config_Changes.md +++ b/docs/Config_Changes.md @@ -8,6 +8,19 @@ All dates in this document are approximate. ## Changes +20240912: `SET_PIN`, `SET_SERVO`, `SET_FAN_SPEED`, `M106`, and `M107` +commands are now collated. Previously, if many updates to the same +object were issued faster than the minimum scheduling time (typically +100ms) then actual updates could be queued far into the future. Now if +many updates are issued in rapid succession then it is possible that +only the latest request will be applied. If the previous behavior is +requried then consider adding explicit `G4` delay commands between +updates. + +20240912: Support for `maximum_mcu_duration` and `static_value` +parameters in `[output_pin]` config sections have been removed. These +options have been deprecated since 20240123. + 20240415: The `on_error_gcode` parameter in the `[virtual_sdcard]` config section now has a default. If this parameter is not specified it now defaults to `TURN_OFF_HEATERS`. If the previous behavior is diff --git a/klippy/extras/ads1220.py b/klippy/extras/ads1220.py index 14d47581ec8a..396f68547c4e 100644 --- a/klippy/extras/ads1220.py +++ b/klippy/extras/ads1220.py @@ -24,7 +24,7 @@ def hexify(byte_array): return "[%s]" % (", ".join([hex(b) for b in byte_array])) -class ADS1220(): +class ADS1220: def __init__(self, config): self.printer = printer = config.get_printer() self.name = config.get_name().split()[-1] diff --git a/klippy/extras/hx71x.py b/klippy/extras/hx71x.py index 85eff85f954d..5c59ed0b4fdc 100644 --- a/klippy/extras/hx71x.py +++ b/klippy/extras/hx71x.py @@ -14,7 +14,7 @@ SAMPLE_ERROR_LONG_READ = 0x40000000 # Implementation of HX711 and HX717 -class HX71xBase(): +class HX71xBase: def __init__(self, config, sensor_type, sample_rate_options, default_sample_rate, gain_options, default_gain): @@ -145,23 +145,21 @@ def _process_batch(self, eventtime): 'overflows': self.ffreader.get_last_overflows()} -class HX711(HX71xBase): - def __init__(self, config): - super(HX711, self).__init__(config, "hx711", - # HX711 sps options - {80: 80, 10: 10}, 80, - # HX711 gain/channel options - {'A-128': 1, 'B-32': 2, 'A-64': 3}, 'A-128') +def HX711(config): + return HX71xBase(config, "hx711", + # HX711 sps options + {80: 80, 10: 10}, 80, + # HX711 gain/channel options + {'A-128': 1, 'B-32': 2, 'A-64': 3}, 'A-128') -class HX717(HX71xBase): - def __init__(self, config): - super(HX717, self).__init__(config, "hx717", - # HX717 sps options - {320: 320, 80: 80, 20: 20, 10: 10}, 320, - # HX717 gain/channel options - {'A-128': 1, 'B-64': 2, 'A-64': 3, - 'B-8': 4}, 'A-128') +def HX717(config): + return HX71xBase(config, "hx717", + # HX717 sps options + {320: 320, 80: 80, 20: 20, 10: 10}, 320, + # HX717 gain/channel options + {'A-128': 1, 'B-64': 2, 'A-64': 3, + 'B-8': 4}, 'A-128') HX71X_SENSOR_TYPES = { diff --git a/klippy/extras/output_pin.py b/klippy/extras/output_pin.py index ef094674c1c1..3d7a952c1b47 100644 --- a/klippy/extras/output_pin.py +++ b/klippy/extras/output_pin.py @@ -5,9 +5,48 @@ # This file may be distributed under the terms of the GNU GPLv3 license. PIN_MIN_TIME = 0.100 -RESEND_HOST_TIME = 0.300 + PIN_MIN_TIME MAX_SCHEDULE_TIME = 5.0 +# Helper code to queue g-code requests +class GCodeRequestQueue: + def __init__(self, config, mcu, callback): + self.printer = printer = config.get_printer() + self.mcu = mcu + self.callback = callback + self.rqueue = [] + self.next_min_flush_time = 0. + self.toolhead = None + mcu.register_flush_callback(self._flush_notification) + printer.register_event_handler("klippy:connect", self._handle_connect) + def _handle_connect(self): + self.toolhead = self.printer.lookup_object('toolhead') + def _flush_notification(self, print_time, clock): + rqueue = self.rqueue + while rqueue: + next_time = max(rqueue[0][0], self.next_min_flush_time) + if next_time > print_time: + return + # Skip requests that have been overridden with a following request + pos = 0 + while pos + 1 < len(rqueue) and rqueue[pos + 1][0] <= next_time: + pos += 1 + req_pt, req_val = rqueue[pos] + # Invoke callback for the request + want_dequeue, min_wait_time = self.callback(next_time, req_val) + self.next_min_flush_time = next_time + max(min_wait_time, + PIN_MIN_TIME) + if want_dequeue: + pos += 1 + del rqueue[:pos] + # Ensure following queue items are flushed + self.toolhead.note_mcu_movequeue_activity(self.next_min_flush_time) + def queue_request(self, print_time, value): + self.rqueue.append((print_time, value)) + self.toolhead.note_mcu_movequeue_activity(print_time) + def queue_gcode_request(self, value): + self.toolhead.register_lookahead_callback( + (lambda pt: self.queue_request(pt, value))) + class PrinterOutputPin: def __init__(self, config): self.printer = config.get_printer() @@ -24,30 +63,16 @@ def __init__(self, config): else: self.mcu_pin = ppins.setup_pin('digital_out', config.get('pin')) self.scale = 1. - self.last_print_time = 0. - # Support mcu checking for maximum duration - self.reactor = self.printer.get_reactor() - self.resend_timer = None - self.resend_interval = 0. - max_mcu_duration = config.getfloat('maximum_mcu_duration', 0., - minval=0.500, - maxval=MAX_SCHEDULE_TIME) - self.mcu_pin.setup_max_duration(max_mcu_duration) - if max_mcu_duration: - config.deprecate('maximum_mcu_duration') - self.resend_interval = max_mcu_duration - RESEND_HOST_TIME + self.mcu_pin.setup_max_duration(0.) # Determine start and shutdown values - static_value = config.getfloat('static_value', None, - minval=0., maxval=self.scale) - if static_value is not None: - config.deprecate('static_value') - self.last_value = self.shutdown_value = static_value / self.scale - else: - 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.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) + # Create gcode request queue + self.gcrq = GCodeRequestQueue(config, self.mcu_pin.get_mcu(), + self._set_pin) # Register commands pin_name = config.get_name().split()[1] gcode = self.printer.lookup_object('gcode') @@ -56,19 +81,14 @@ def __init__(self, config): desc=self.cmd_SET_PIN_help) def get_status(self, eventtime): return {'value': self.last_value} - def _set_pin(self, print_time, value, is_resend=False): - if value == self.last_value and 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) - else: - self.mcu_pin.set_digital(print_time, value) - self.last_value = value - self.last_print_time = print_time - if self.resend_interval and self.resend_timer is None: - self.resend_timer = self.reactor.register_timer( - self._resend_current_val, self.reactor.NOW) + def _set_pin(self, print_time, value): + if value != self.last_value: + self.last_value = value + if self.is_pwm: + self.mcu_pin.set_pwm(print_time, value) + else: + self.mcu_pin.set_digital(print_time, value) + return (True, 0.) cmd_SET_PIN_help = "Set the value of an output pin" def cmd_SET_PIN(self, gcmd): # Read requested value @@ -76,25 +96,8 @@ def cmd_SET_PIN(self, gcmd): value /= self.scale if not self.is_pwm and value not in [0., 1.]: raise gcmd.error("Invalid pin value") - # 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 _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 = (self.last_print_time + self.resend_interval) - print_time - if time_diff > 0.: - # Reschedule for resend time - return systime + time_diff - self._set_pin(print_time + PIN_MIN_TIME, self.last_value, True) - return systime + self.resend_interval + # Queue requested value + self.gcrq.queue_gcode_request(value) def load_config_prefix(config): return PrinterOutputPin(config) diff --git a/klippy/extras/servo.py b/klippy/extras/servo.py index 344d6a31d6bf..10537e673471 100644 --- a/klippy/extras/servo.py +++ b/klippy/extras/servo.py @@ -1,11 +1,11 @@ # Support for servos # -# Copyright (C) 2017-2020 Kevin O'Connor +# Copyright (C) 2017-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. +from . import output_pin SERVO_SIGNAL_PERIOD = 0.020 -PIN_MIN_TIME = 0.100 class PrinterServo: def __init__(self, config): @@ -18,7 +18,7 @@ def __init__(self, config): self.max_angle = config.getfloat('maximum_servo_angle', 180.) self.angle_to_width = (self.max_width - self.min_width) / self.max_angle self.width_to_value = 1. / SERVO_SIGNAL_PERIOD - self.last_value = self.last_value_time = 0. + self.last_value = 0. initial_pwm = 0. iangle = config.getfloat('initial_angle', None, minval=0., maxval=360.) if iangle is not None: @@ -33,6 +33,9 @@ def __init__(self, config): self.mcu_servo.setup_max_duration(0.) self.mcu_servo.setup_cycle_time(SERVO_SIGNAL_PERIOD) self.mcu_servo.setup_start_value(initial_pwm, 0.) + # Create gcode request queue + self.gcrq = output_pin.GCodeRequestQueue( + config, self.mcu_servo.get_mcu(), self._set_pwm) # Register commands servo_name = config.get_name().split()[1] gcode = self.printer.lookup_object('gcode') @@ -42,12 +45,10 @@ def __init__(self, config): def get_status(self, eventtime): return {'value': self.last_value} def _set_pwm(self, print_time, value): - if value == self.last_value: - return - print_time = max(print_time, self.last_value_time + PIN_MIN_TIME) - self.mcu_servo.set_pwm(print_time, value) - self.last_value = value - self.last_value_time = print_time + if value != self.last_value: + self.last_value = value + self.mcu_servo.set_pwm(print_time, value) + return (True, 0.) def _get_pwm_from_angle(self, angle): angle = max(0., min(self.max_angle, angle)) width = self.min_width + angle * self.angle_to_width @@ -64,9 +65,7 @@ def cmd_SET_SERVO(self, gcmd): else: angle = gcmd.get_float('ANGLE') value = self._get_pwm_from_angle(angle) - toolhead = self.printer.lookup_object('toolhead') - toolhead.register_lookahead_callback((lambda pt: - self._set_pwm(pt, value))) + self.gcrq.queue_gcode_request(value) def load_config_prefix(config): return PrinterServo(config) diff --git a/scripts/spi_flash/board_defs.py b/scripts/spi_flash/board_defs.py index c0a8b5772130..9924fefcd70e 100644 --- a/scripts/spi_flash/board_defs.py +++ b/scripts/spi_flash/board_defs.py @@ -46,6 +46,7 @@ 'mcu': "stm32f103xe", 'spi_bus': "spi2", "cs_pin": "PA15", + "conversion_script": "scripts/update_mks_robin.py", "firmware_path": "Robin_e3.bin", "current_firmware_path": "Robin_e3.cur" }, @@ -133,6 +134,16 @@ 'mcu': "stm32g0b1xx", 'spi_bus': "spi1", "cs_pin": "PB8" + }, + 'chitu-v6': { + 'mcu': "stm32f103xe", + 'spi_bus': "swspi", + 'spi_pins': "PC8,PD2,PC12", + "cs_pin": "PC11", + #'sdio_bus': 'sdio', + "conversion_script": "scripts/update_chitu.py", + "firmware_path": "update.cbd", + 'skip_verify': True } } @@ -182,7 +193,8 @@ 'fysetc-s6-v1.2': BOARD_DEFS['fysetc-spider'], 'fysetc-s6-v2': BOARD_DEFS['fysetc-spider'], 'robin_v3': BOARD_DEFS['monster8'], - 'btt-skrat-v1.0': BOARD_DEFS['btt-skrat'] + 'btt-skrat-v1.0': BOARD_DEFS['btt-skrat'], + 'chitu-v6': BOARD_DEFS['chitu-v6'] } def list_boards(): diff --git a/scripts/spi_flash/spi_flash.py b/scripts/spi_flash/spi_flash.py index a3231b693219..cbe769e573f1 100644 --- a/scripts/spi_flash/spi_flash.py +++ b/scripts/spi_flash/spi_flash.py @@ -74,20 +74,19 @@ def translate_serial_to_tty(device): return ttyname, ttyname def check_need_convert(board_name, config): - if board_name.lower().startswith('mks-robin-e3'): - # we need to convert this file - robin_util = os.path.join( - fatfs_lib.KLIPPER_DIR, "scripts/update_mks_robin.py") - klipper_bin = config['klipper_bin_path'] - robin_bin = os.path.join( + conv_script = config.get("conversion_script") + if conv_script is None: + return + conv_util = os.path.join(fatfs_lib.KLIPPER_DIR, conv_script) + klipper_bin = config['klipper_bin_path'] + dest_bin = os.path.join( os.path.dirname(klipper_bin), os.path.basename(config['firmware_path'])) - cmd = "%s %s %s %s" % (sys.executable, robin_util, klipper_bin, - robin_bin) - output("Converting Klipper binary to MKS Robin format...") - os.system(cmd) - output_line("Done") - config['klipper_bin_path'] = robin_bin + cmd = "%s %s %s %s" % (sys.executable, conv_util, klipper_bin, dest_bin) + output("Converting Klipper binary to custom format...") + os.system(cmd) + output_line("Done") + config['klipper_bin_path'] = dest_bin ########################################################### diff --git a/test/klippy/load_cell.cfg b/test/klippy/load_cell.cfg new file mode 100644 index 000000000000..fa599d10e366 --- /dev/null +++ b/test/klippy/load_cell.cfg @@ -0,0 +1,23 @@ +# Test config for load_cell +[mcu] +serial: /dev/ttyACM0 + +[printer] +kinematics: none +max_velocity: 300 +max_accel: 3000 + +[load_cell my_ads1220] +sensor_type: ads1220 +cs_pin: PA0 +data_ready_pin: PA1 + +[load_cell my_hx711] +sensor_type: hx711 +sclk_pin: PA2 +dout_pin: PA3 + +[load_cell my_hx717] +sensor_type: hx717 +sclk_pin: PA4 +dout_pin: PA5 diff --git a/test/klippy/load_cell.test b/test/klippy/load_cell.test new file mode 100644 index 000000000000..880f840aa388 --- /dev/null +++ b/test/klippy/load_cell.test @@ -0,0 +1,5 @@ +# Tests for loadcell sensors +DICTIONARY atmega2560.dict +CONFIG load_cell.cfg + +G4 P1000