From 0acf3035e8193d69ebe1bccb9906852500987b35 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 10 Nov 2023 18:23:35 -0500 Subject: [PATCH 01/33] docs: Update Features.md Note additional features that have been added recently. Signed-off-by: Kevin O'Connor --- docs/Features.md | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/docs/Features.md b/docs/Features.md index 96b17a756f1d..9c61ccf72845 100644 --- a/docs/Features.md +++ b/docs/Features.md @@ -60,7 +60,7 @@ Klipper has several compelling features: if it is unable to). This makes it easier to use available hardware, to upgrade to new hardware, and to have confidence in the hardware. -* Portable code. Klipper works on ARM, AVR, and PRU based +* Portable code. Klipper works on ARM, AVR, PRU, and other micro-controllers. Existing "reprap" style printers can run Klipper without hardware modification - just add a Raspberry Pi. Klipper's internal code layout makes it easier to support other @@ -105,7 +105,8 @@ Klipper supports many standard 3d printer features: bed tilt detection or full mesh bed leveling. If the bed uses multiple Z steppers then Klipper can also level by independently manipulating the Z steppers. Most Z height probes are supported, - including BL-Touch probes and servo activated probes. + including BL-Touch probes and servo activated probes. Probes may be + calibrated for axis twist compensation. * Automatic delta calibration support. The calibration tool can perform basic height calibration as well as an enhanced X and Y @@ -117,10 +118,11 @@ Klipper supports many standard 3d printer features: * Support for common temperature sensors (eg, common thermistors, AD595, AD597, AD849x, PT100, PT1000, MAX6675, MAX31855, MAX31856, - MAX31865, BME280, HTU21D, DS18B20, and LM75). Custom thermistors and - custom analog temperature sensors can also be configured. One can - monitor the internal micro-controller temperature sensor and the - internal temperature sensor of a Raspberry Pi. + MAX31865, BME280, HTU21D, DS18B20, AHT10, and LM75). Custom + thermistors and custom analog temperature sensors can also be + configured. One can monitor the internal micro-controller + temperature sensor and the internal temperature sensor of a + Raspberry Pi. * Basic thermal heater protection enabled by default. @@ -129,9 +131,9 @@ Klipper supports many standard 3d printer features: speed can be monitored on fans that have a tachometer. * Support for run-time configuration of TMC2130, TMC2208/TMC2224, - TMC2209, TMC2660, and TMC5160 stepper motor drivers. There is also - support for current control of traditional stepper drivers via - AD5206, DAC084S085, MCP4451, MCP4728, MCP4018, and PWM pins. + TMC2209, TMC2240, TMC2660, and TMC5160 stepper motor drivers. There + is also support for current control of traditional stepper drivers + via AD5206, DAC084S085, MCP4451, MCP4728, MCP4018, and PWM pins. * Support for common LCD displays attached directly to the printer. A default menu is also available. The contents of the display and menu @@ -151,8 +153,8 @@ Klipper supports many standard 3d printer features: * Support for filament presence sensors, filament motion sensors, and filament width sensors. -* Support for measuring and recording acceleration using an adxl345, - mpu9250, and mpu6050 accelerometers. +* Support for measuring and recording acceleration using adxl345, + mpu9250, mpu6050, and lis2dw12 accelerometers. * Support for limiting the top speed of short "zigzag" moves to reduce printer vibration and noise. See the [kinematics](Kinematics.md) From 0d67d9c45d2dc39f8b4be7d1bb54b94b2698a2b6 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 12 Oct 2023 20:54:45 -0400 Subject: [PATCH 02/33] docs: Note the release of v0.12.0 Signed-off-by: Kevin O'Connor --- docs/Releases.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/Releases.md b/docs/Releases.md index 7054a0b30e5d..e564a9d24125 100644 --- a/docs/Releases.md +++ b/docs/Releases.md @@ -3,6 +3,24 @@ History of Klipper releases. Please see [installation](Installation.md) for information on installing Klipper. +## Klipper 0.12.0 + +Available on 20231110. Major changes in this release: +* Support for COPY and MIRROR modes on IDEX printers. +* Several micro-controller improvements: + * Support for new ar100 and hc32f460 architectures. + * Support for stm32f7, stm32g0b0, stm32g07x, stm32g4, stm32h723, + n32g45x, samc21, and samd21j18 chip variants. + * Improved DFU and Katapult reboot handling. + * Improved performance on USB to CANbus bridge mode. + * Improved performance on "linux mcu". + * New support for software based i2c. +* New hardware support for tmc2240 stepper motor drivers, lis2dw12 + accelerometers, and aht10 temperature sensors. +* New axis_twist_compensation and temperature_combined modules added. +* New support for gcode arcs in XY, XZ, and YZ planes. +* Several bug fixes and code cleanups. + ## Klipper 0.11.0 Available on 20221128. Major changes in this release: From 74473322e550e8ca1d7a9c513ea3a909b4845126 Mon Sep 17 00:00:00 2001 From: Herb McNew Date: Tue, 14 Nov 2023 13:15:46 -0500 Subject: [PATCH 03/33] config: Add Sovol SV06 Plus (#6397) New configuration for the Sovol SV06 Plus Initial pass at adding a printer configuration for the Sovol SV06 Plus based on the existing Sovol SV06 config. Updated for the larger build volume, added filament runout sensor, and removed any references to the LCD screen since the stock screen doesn't work with Klipper. Signed-off-by: Herb McNew --- config/printer-sovol-sv06-plus-2023.cfg | 147 ++++++++++++++++++++++++ test/klippy/printers.test | 1 + 2 files changed, 148 insertions(+) create mode 100644 config/printer-sovol-sv06-plus-2023.cfg diff --git a/config/printer-sovol-sv06-plus-2023.cfg b/config/printer-sovol-sv06-plus-2023.cfg new file mode 100644 index 000000000000..cb6fbd873f9d --- /dev/null +++ b/config/printer-sovol-sv06-plus-2023.cfg @@ -0,0 +1,147 @@ +# This file contains pin mappings for the stock Sovol SV06 Plus +# To use this config, during "make menuconfig" select the +# STM32F103 with a "28KiB bootloader" and serial (on USART1 PA10/PA9) communication. +# Also, since it is using the GD32F103, please select Disable SWD at startup +# +# Flash this firmware by copying "out/klipper.bin" to a SD card and +# turning on the printer with the card inserted. The firmware +# filename must end in ".bin" and must not match the last filename +# that was flashed. +# +# Note: The stock LCD display does not currently work with Klipper +# +# See docs/Config_Reference.md for a description of parameters. +[mcu] +serial: /dev/serial/by-id/usb-1a86_USB2.0-Serial-if00-port0 +restart_method: command + +[printer] +kinematics: cartesian +max_velocity: 500 +max_accel: 2000 +max_z_velocity: 10 +max_z_accel: 100 + +[stepper_x] +step_pin: PC2 +dir_pin: !PB9 +enable_pin: !PC3 +microsteps: 16 +rotation_distance: 40 +endstop_pin: tmc2209_stepper_x:virtual_endstop +position_endstop: 0 +position_max: 305 +homing_speed: 40 +homing_retract_dist: 0 + +[tmc2209 stepper_x] +uart_pin: PC1 +run_current: 0.860 +sense_resistor: 0.150 +uart_address: 3 +driver_SGTHRS: 81 +diag_pin: PA5 + +[stepper_y] +step_pin: PB8 +dir_pin: PB7 +enable_pin: !PC3 +microsteps: 16 +rotation_distance: 40 +endstop_pin: tmc2209_stepper_y:virtual_endstop +position_endstop: 0 +position_max: 305 +homing_speed: 40 +homing_retract_dist: 0 + +[tmc2209 stepper_y] +uart_pin: PC0 +run_current: 0.900 +sense_resistor: 0.150 +uart_address: 3 +driver_SGTHRS: 82 +diag_pin: PA6 + +[stepper_z] +step_pin: PB6 +dir_pin: !PB5 +enable_pin: !PC3 +microsteps: 16 +rotation_distance: 4 +endstop_pin: probe:z_virtual_endstop +position_min: -4 +position_max: 350 +homing_speed: 4 + +[tmc2209 stepper_z] +uart_pin: PA15 +run_current: 1.000 +interpolate: False +sense_resistor: 0.150 +uart_address: 3 +diag_pin: PA7 + +[extruder] +max_extrude_only_distance: 100.0 +step_pin: PB4 +dir_pin: !PB3 +enable_pin: !PC3 +microsteps: 16 +rotation_distance: 4.56 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PA1 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PC5 +control: pid +pid_kd: 41.96 +pid_kp: 15.66 +pid_ki: 1.49 +min_temp: 0 +max_temp: 300 + +[tmc2209 extruder] +uart_pin: PC14 +run_current: 0.550 +stealthchop_threshold: 0 +interpolate: False +sense_resistor: 0.150 +uart_address: 3 + +[heater_bed] +heater_pin: PA2 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PC4 +control: pid +pid_kp: 186.38 +pid_ki: 36.12 +pid_kd: 637.30 +min_temp: 0 +max_temp: 130 + +[fan] +pin: PA0 + +[probe] +pin: PB1 +x_offset: 28 +y_offset: -20 +z_offset: 0 + +[safe_z_home] +home_xy_position: 123,170 +z_hop: 10 +z_hop_speed: 5 + +[bed_mesh] +speed: 120 +mesh_min: 28, 20 +mesh_max: 270, 270 +probe_count: 5 +algorithm: bicubic +fade_end: 10 +fade_target: 0 + +[filament_switch_sensor filament_runout_sensor] +switch_pin: PA4 +pause_on_runout: True diff --git a/test/klippy/printers.test b/test/klippy/printers.test index d9168adc58e7..2ab96acc85e3 100644 --- a/test/klippy/printers.test +++ b/test/klippy/printers.test @@ -157,6 +157,7 @@ CONFIG ../../config/printer-anycubic-vyper-2021.cfg CONFIG ../../config/printer-monoprice-select-mini-v1-2016.cfg CONFIG ../../config/printer-sovol-sv05-2022.cfg CONFIG ../../config/printer-sovol-sv06-2022.cfg +CONFIG ../../config/printer-sovol-sv06-plus-2023.cfg CONFIG ../../config/printer-sunlu-t3-2022.cfg # Printers using the stm32f103 via serial From 3f8f30d6123c647c05ba3f4f58477d9dda36f03f Mon Sep 17 00:00:00 2001 From: Herb McNew Date: Thu, 16 Nov 2023 21:57:45 -0500 Subject: [PATCH 04/33] config: Update printer-sovol-sv06-plus-2023.cfg (#6401) In testing with a user on Discord we discovered the sensorless homing thresholds were out of line with what Sovol ships with Marlin. This aligns with their settings. Signed-off-by: Herb McNew --- config/printer-sovol-sv06-plus-2023.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/printer-sovol-sv06-plus-2023.cfg b/config/printer-sovol-sv06-plus-2023.cfg index cb6fbd873f9d..8c72192f3d32 100644 --- a/config/printer-sovol-sv06-plus-2023.cfg +++ b/config/printer-sovol-sv06-plus-2023.cfg @@ -39,7 +39,7 @@ uart_pin: PC1 run_current: 0.860 sense_resistor: 0.150 uart_address: 3 -driver_SGTHRS: 81 +driver_SGTHRS: 86 diag_pin: PA5 [stepper_y] @@ -59,7 +59,7 @@ uart_pin: PC0 run_current: 0.900 sense_resistor: 0.150 uart_address: 3 -driver_SGTHRS: 82 +driver_SGTHRS: 110 diag_pin: PA6 [stepper_z] From 4688c21c54dfb89ef9902b607521a8cc0e99c94a Mon Sep 17 00:00:00 2001 From: Thijs Triemstra Date: Fri, 17 Nov 2023 04:06:13 +0100 Subject: [PATCH 05/33] klippy: Replace deprecated logger.warn with logger.warning (#6385) Replace deprecated logger.warn with logger.warning logger.warn will be removed in Python 3.13 Signed-off-by: Thijs Triemstra --- klippy/configfile.py | 8 ++++---- klippy/extras/adc_temperature.py | 4 ++-- klippy/extras/htu21d.py | 14 ++++++++------ klippy/extras/spi_temperature.py | 2 +- klippy/extras/thermistor.py | 3 ++- klippy/serialhdl.py | 4 ++-- 6 files changed, 19 insertions(+), 16 deletions(-) diff --git a/klippy/configfile.py b/klippy/configfile.py index dd32d47fd3c6..e5c0fb20bd0e 100644 --- a/klippy/configfile.py +++ b/klippy/configfile.py @@ -172,16 +172,16 @@ def _find_autosave_data(self, data): autosave_data = data[pos + len(AUTOSAVE_HEADER):].strip() # Check for errors and strip line prefixes if "\n#*# " in regular_data: - logging.warn("Can't read autosave from config file" - " - autosave state corrupted") + logging.warning("Can't read autosave from config file" + " - autosave state corrupted") return data, "" out = [""] for line in autosave_data.split('\n'): if ((not line.startswith("#*#") or (len(line) >= 4 and not line.startswith("#*# "))) and autosave_data): - logging.warn("Can't read autosave from config file" - " - modifications after header") + logging.warning("Can't read autosave from config file" + " - modifications after header") return data, "" out.append(line[4:]) out.append("") diff --git a/klippy/extras/adc_temperature.py b/klippy/extras/adc_temperature.py index d456a653d188..b76e8c66fa85 100644 --- a/klippy/extras/adc_temperature.py +++ b/klippy/extras/adc_temperature.py @@ -95,8 +95,8 @@ def __init__(self, config, params): for temp, volt in params: adc = (volt - voltage_offset) / adc_voltage if adc < 0. or adc > 1.: - logging.warn("Ignoring adc sample %.3f/%.3f in heater %s", - temp, volt, config.get_name()) + logging.warning("Ignoring adc sample %.3f/%.3f in heater %s", + temp, volt, config.get_name()) continue samples.append((adc, temp)) try: diff --git a/klippy/extras/htu21d.py b/klippy/extras/htu21d.py index 16da7f8eb188..ec44da633f12 100644 --- a/klippy/extras/htu21d.py +++ b/klippy/extras/htu21d.py @@ -126,7 +126,7 @@ def _init_htu21d(self): rdevId |= response[1] checksum = response[2] if self._chekCRC8(rdevId) != checksum: - logging.warn("htu21d: Reading deviceId !Checksum error!") + logging.warning("htu21d: Reading deviceId !Checksum error!") rdevId = rdevId >> 8 deviceId_list = list( filter( @@ -135,10 +135,10 @@ def _init_htu21d(self): if len(deviceId_list) != 0: logging.info("htu21d: Found Device Type %s" % deviceId_list[0]) else: - logging.warn("htu21d: Unknown Device ID %#x " % rdevId) + logging.warning("htu21d: Unknown Device ID %#x " % rdevId) - if(self.deviceId != deviceId_list[0]): - logging.warn( + if self.deviceId != deviceId_list[0]: + logging.warning( "htu21d: Found device %s. Forcing to type %s as config.", deviceId_list[0],self.deviceId) @@ -169,7 +169,9 @@ def _sample_htu21d(self, eventtime): rtemp = response[0] << 8 rtemp |= response[1] if self._chekCRC8(rtemp) != response[2]: - logging.warn("htu21d: Checksum error on Temperature reading!") + logging.warning( + "htu21d: Checksum error on Temperature reading!" + ) else: self.temp = (0.002681 * float(rtemp) - 46.85) logging.debug("htu21d: Temperature %.2f " % self.temp) @@ -190,7 +192,7 @@ def _sample_htu21d(self, eventtime): rhumid = response[0] << 8 rhumid|= response[1] if self._chekCRC8(rhumid) != response[2]: - logging.warn("htu21d: Checksum error on Humidity reading!") + logging.warning("htu21d: Checksum error on Humidity reading!") else: #clear status bits, # humidity always returns xxxxxx10 in the LSB field diff --git a/klippy/extras/spi_temperature.py b/klippy/extras/spi_temperature.py index 77033b1b151b..92d1b385f47a 100644 --- a/klippy/extras/spi_temperature.py +++ b/klippy/extras/spi_temperature.py @@ -62,7 +62,7 @@ def _handle_spi_response(self, params): last_read_time = self.mcu.clock_to_print_time(last_read_clock) self._callback(last_read_time, temp) def report_fault(self, msg): - logging.warn(msg) + logging.warning(msg) ###################################################################### diff --git a/klippy/extras/thermistor.py b/klippy/extras/thermistor.py index d04225b7b1ff..16d6ca61b838 100644 --- a/klippy/extras/thermistor.py +++ b/klippy/extras/thermistor.py @@ -33,7 +33,8 @@ def setup_coefficients(self, t1, r1, t2, r2, t3, r3, name=""): / (ln3_r12 - ln3_r13 * ln_r12 / ln_r13)) if self.c3 <= 0.: beta = ln_r13 / inv_t13 - logging.warn("Using thermistor beta %.3f in heater %s", beta, name) + logging.warning("Using thermistor beta %.3f in heater %s", + beta, name) self.setup_coefficients_beta(t1, r1, beta) return self.c2 = (inv_t12 - self.c3 * ln3_r12) / ln_r12 diff --git a/klippy/serialhdl.py b/klippy/serialhdl.py index f3c4ed99049e..f9e542d35029 100644 --- a/klippy/serialhdl.py +++ b/klippy/serialhdl.py @@ -137,8 +137,8 @@ def connect_canbus(self, canbus_uuid, canbus_nodeid, canbus_iface="can0"): bustype='socketcan') bus.send(set_id_msg) except (can.CanError, os.error) as e: - logging.warn("%sUnable to open CAN port: %s", - self.warn_prefix, e) + logging.warning("%sUnable to open CAN port: %s", + self.warn_prefix, e) self.reactor.pause(self.reactor.monotonic() + 5.) continue bus.close = bus.shutdown # XXX From 48a05eaa542bfd4a1099c2224bda443049a6fa64 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 13 Oct 2023 20:43:27 -0400 Subject: [PATCH 06/33] stepcompress: Add support for queuing messages that consume move queue space Signed-off-by: Kevin O'Connor --- klippy/chelper/__init__.py | 2 ++ klippy/chelper/stepcompress.c | 15 +++++++++++++++ klippy/chelper/stepcompress.h | 2 ++ 3 files changed, 19 insertions(+) diff --git a/klippy/chelper/__init__.py b/klippy/chelper/__init__.py index c94e9677e637..290234c578ed 100644 --- a/klippy/chelper/__init__.py +++ b/klippy/chelper/__init__.py @@ -49,6 +49,8 @@ , uint64_t clock); int stepcompress_queue_msg(struct stepcompress *sc , uint32_t *data, int len); + int stepcompress_queue_mq_msg(struct stepcompress *sc, uint64_t req_clock + , uint32_t *data, int len); int stepcompress_extract_old(struct stepcompress *sc , struct pull_history_steps *p, int max , uint64_t start_clock, uint64_t end_clock); diff --git a/klippy/chelper/stepcompress.c b/klippy/chelper/stepcompress.c index e261f1d30483..e5514b952021 100644 --- a/klippy/chelper/stepcompress.c +++ b/klippy/chelper/stepcompress.c @@ -623,6 +623,21 @@ stepcompress_queue_msg(struct stepcompress *sc, uint32_t *data, int len) return 0; } +// Queue an mcu command that will consume space in the mcu move queue +int __visible +stepcompress_queue_mq_msg(struct stepcompress *sc, uint64_t req_clock + , uint32_t *data, int len) +{ + int ret = stepcompress_flush(sc, UINT64_MAX); + if (ret) + return ret; + + struct queue_message *qm = message_alloc_and_encode(data, len); + qm->min_clock = qm->req_clock = req_clock; + list_add_tail(&qm->node, &sc->msg_queue); + return 0; +} + // Return history of queue_step commands int __visible stepcompress_extract_old(struct stepcompress *sc, struct pull_history_steps *p diff --git a/klippy/chelper/stepcompress.h b/klippy/chelper/stepcompress.h index b8a950575dc3..bfc0dfcde197 100644 --- a/klippy/chelper/stepcompress.h +++ b/klippy/chelper/stepcompress.h @@ -29,6 +29,8 @@ int stepcompress_set_last_position(struct stepcompress *sc, uint64_t clock int64_t stepcompress_find_past_position(struct stepcompress *sc , uint64_t clock); int stepcompress_queue_msg(struct stepcompress *sc, uint32_t *data, int len); +int stepcompress_queue_mq_msg(struct stepcompress *sc, uint64_t req_clock + , uint32_t *data, int len); int stepcompress_extract_old(struct stepcompress *sc , struct pull_history_steps *p, int max , uint64_t start_clock, uint64_t end_clock); From 29b7550ce5330da8e5ca4f22c1420cbeadf01eaf Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 13 Oct 2023 21:10:11 -0400 Subject: [PATCH 07/33] 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 --- config/sample-pwm-tool.cfg | 11 ++- docs/Config_Reference.md | 20 +++++ docs/G-Codes.md | 3 +- docs/Status_Reference.md | 3 +- docs/Using_PWM_Tools.md | 10 +-- klippy/extras/pwm_tool.py | 157 +++++++++++++++++++++++++++++++++++++ test/klippy/pwm.cfg | 6 ++ test/klippy/pwm.test | 8 ++ 8 files changed, 201 insertions(+), 17 deletions(-) create mode 100644 klippy/extras/pwm_tool.py diff --git a/config/sample-pwm-tool.cfg b/config/sample-pwm-tool.cfg index 4adcea6f1d7e..489860617afc 100644 --- a/config/sample-pwm-tool.cfg +++ b/config/sample-pwm-tool.cfg @@ -2,9 +2,8 @@ # such as a laser or spindle. # See docs/Using_PWM_Tools.md for a more detailed description. -[output_pin TOOL] +[pwm_tool TOOL] pin: !ar9 # use your fan's pin number -pwm: True hardware_pwm: True cycle_time: 0.001 shutdown_value: 0 @@ -36,9 +35,9 @@ gcode: [menu __main __control __toolonoff] type: input -enable: {'output_pin TOOL' in printer} +enable: {'pwm_tool TOOL' in printer} name: Fan: {'ON ' if menu.input else 'OFF'} -input: {printer['output_pin TOOL'].value} +input: {printer['pwm_tool TOOL'].value} input_min: 0 input_max: 1 input_step: 1 @@ -47,9 +46,9 @@ gcode: [menu __main __control __toolspeed] type: input -enable: {'output_pin TOOL' in printer} +enable: {'pwm_tool TOOL' in printer} name: Tool speed: {'%3d' % (menu.input*100)}% -input: {printer['output_pin TOOL'].value} +input: {printer['pwm_tool TOOL'].value} input_min: 0 input_max: 1 input_step: 0.01 diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 241391834e0f..c7a2a3dbfbdd 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -3127,6 +3127,26 @@ pin: # parameter. ``` +### [pwm_tool] + +Pulse width modulation digital output pins capable of high speed +updates (one may define any number of sections with an "output_pin" +prefix). Pins configured here will be setup as output pins and one may +modify them at run-time using "SET_PIN PIN=my_pin VALUE=.1" type +extended [g-code commands](G-Codes.md#output_pin). + +``` +[pwm_tool my_tool] +pin: +# The pin to configure as an output. This parameter must be provided. +#value: +#shutdown_value: +#cycle_time: 0.100 +#hardware_pwm: False +#scale: +# See the "output_pin" section for the definition of these parameters. +``` + ### [static_digital_output] Statically configured digital output pins (one may define any number diff --git a/docs/G-Codes.md b/docs/G-Codes.md index 1f466dcd3eb1..6d492c25137c 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -834,7 +834,8 @@ commands to manage the LED's color settings). ### [output_pin] The following command is available when an -[output_pin config section](Config_Reference.md#output_pin) is +[output_pin config section](Config_Reference.md#output_pin) or +[pwm_tool config section](Config_Reference.md#pwm_tool) is enabled. #### SET_PIN diff --git a/docs/Status_Reference.md b/docs/Status_Reference.md index 13139dd02b73..5477049a100e 100644 --- a/docs/Status_Reference.md +++ b/docs/Status_Reference.md @@ -318,7 +318,8 @@ is defined): ## output_pin The following information is available in -[output_pin some_name](Config_Reference.md#output_pin) objects: +[output_pin some_name](Config_Reference.md#output_pin) and +[pwm_tool some_name](Config_Reference.md#pwm_tool) objects: - `value`: The "value" of the pin, as set by a `SET_PIN` command. ## palette2 diff --git a/docs/Using_PWM_Tools.md b/docs/Using_PWM_Tools.md index a67df84d3b6f..108ae37ad0d1 100644 --- a/docs/Using_PWM_Tools.md +++ b/docs/Using_PWM_Tools.md @@ -1,7 +1,7 @@ # Using PWM tools This document describes how to setup a PWM-controlled laser or spindle -using `output_pin` and some macros. +using `pwm_tool` and some macros. ## How does it work? @@ -26,14 +26,6 @@ so that when your host or MCU encounters an error, the tool will stop. For an example configuration, see [config/sample-pwm-tool.cfg](/config/sample-pwm-tool.cfg). -## Current Limitations - -There is a limitation of how frequent PWM updates may occur. -While being very precise, a PWM update may only occur every 0.1 seconds, -rendering it almost useless for raster engraving. -However, there exists an [experimental branch](https://github.com/Cirromulus/klipper/tree/laser_tool) with its own tradeoffs. -In long term, it is planned to add this functionality to main-line klipper. - ## Commands `M3/M4 S` : Set PWM duty-cycle. Values between 0 and 255. diff --git a/klippy/extras/pwm_tool.py b/klippy/extras/pwm_tool.py new file mode 100644 index 000000000000..c012bb317752 --- /dev/null +++ b/klippy/extras/pwm_tool.py @@ -0,0 +1,157 @@ +# Queued PWM gpio output +# +# Copyright (C) 2017-2023 Kevin O'Connor +# +# 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 & 0xffffffff, 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) diff --git a/test/klippy/pwm.cfg b/test/klippy/pwm.cfg index 45555ca87e87..fbda912694ac 100644 --- a/test/klippy/pwm.cfg +++ b/test/klippy/pwm.cfg @@ -13,6 +13,12 @@ value: 0 shutdown_value: 0 cycle_time: 0.01 +[pwm_tool test_pwm_tool] +pin: PH4 +value: 0 +shutdown_value: 0 +cycle_time: 0.01 + [mcu] serial: /dev/ttyACM0 diff --git a/test/klippy/pwm.test b/test/klippy/pwm.test index d204cbc6dcdc..5e74a3e0569e 100644 --- a/test/klippy/pwm.test +++ b/test/klippy/pwm.test @@ -28,3 +28,11 @@ SET_PIN PIN=soft_pwm_pin VALUE=0.5 CYCLE_TIME=0.5 SET_PIN PIN=soft_pwm_pin VALUE=0.5 CYCLE_TIME=0.5 SET_PIN PIN=soft_pwm_pin VALUE=0.75 CYCLE_TIME=0.5 SET_PIN PIN=soft_pwm_pin VALUE=0.75 CYCLE_TIME=0.75 + +# PWM tool +# Basic test +SET_PIN PIN=test_pwm_tool VALUE=0 +SET_PIN PIN=test_pwm_tool VALUE=0.5 +SET_PIN PIN=test_pwm_tool VALUE=0.5 +SET_PIN PIN=test_pwm_tool VALUE=0.25 +SET_PIN PIN=test_pwm_tool VALUE=1 From 83df4a8627ce98316e9515425f01d443caa52d52 Mon Sep 17 00:00:00 2001 From: docgalaxyblock Date: Wed, 1 Nov 2023 17:26:12 +0100 Subject: [PATCH 08/33] stm32: enable 64KiB bootloader offset for all F4 Signed-off-by: Joshua Schlicker --- src/stm32/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index e39611764c2e..7f50ea3ca868 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -288,7 +288,7 @@ choice config STM32_FLASH_START_C000 bool "48KiB bootloader (MKS Robin Nano V3)" if MACH_STM32F4x5 config STM32_FLASH_START_10000 - bool "64KiB bootloader" if MACH_STM32F103 || MACH_STM32F446 || MACH_STM32F401 + bool "64KiB bootloader" if MACH_STM32F103 || MACH_STM32F4 config STM32_FLASH_START_800 bool "2KiB bootloader (HID Bootloader)" if MACH_STM32F103 From 3c8f202dfa4a581dd52ad0cc98f3b7e41b23559b Mon Sep 17 00:00:00 2001 From: vaxxi Date: Fri, 17 Nov 2023 05:27:44 +0200 Subject: [PATCH 09/33] bmp280: Add support for BMP180 sensor (#6370) Extends the BMxx80 category with support for the older BMP180 sensor, providing temperature and humidity output. Signed-off-by: VAXXi Popescu --- docs/Config_Reference.md | 6 +- klippy/extras/bme280.py | 127 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 124 insertions(+), 9 deletions(-) diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index c7a2a3dbfbdd..cc78ddbe3894 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -2440,9 +2440,9 @@ sensor_pin: # name in the above list. ``` -### BMP280/BME280/BME680 temperature sensor +### BMP180/BMP280/BME280/BME680 temperature sensor -BMP280/BME280/BME680 two wire interface (I2C) environmental sensors. +BMP180/BMP280/BME280/BME680 two wire interface (I2C) environmental sensors. Note that these sensors are not intended for use with extruders and heater beds, but rather for monitoring ambient temperature (C), pressure (hPa), relative humidity and in case of the BME680 gas level. @@ -2453,7 +2453,7 @@ temperature. ``` sensor_type: BME280 #i2c_address: -# Default is 118 (0x76). Some BME280 sensors have an address of 119 +# Default is 118 (0x76). The BMP180 and some BME280 sensors have an address of 119 # (0x77). #i2c_mcu: #i2c_bus: diff --git a/klippy/extras/bme280.py b/klippy/extras/bme280.py index e27bfb4791d9..3bc3c471cb1b 100644 --- a/klippy/extras/bme280.py +++ b/klippy/extras/bme280.py @@ -8,6 +8,7 @@ REPORT_TIME = .8 BME280_CHIP_ADDR = 0x76 + BME280_REGS = { 'RESET': 0xE0, 'CTRL_HUM': 0xF2, 'STATUS': 0xF3, 'CTRL_MEAS': 0xF4, 'CONFIG': 0xF5, @@ -46,6 +47,16 @@ 15: (1., 244.140625) } +BMP180_REGS = { + 'RESET': 0xE0, + 'CAL_1': 0xAA, + 'CTRL_MEAS': 0xF4, + 'REG_MSB': 0xF6, + 'REG_LSB': 0xF7, + 'CRV_TEMP': 0x2E, + 'CRV_PRES': 0x34 +} + STATUS_MEASURING = 1 << 3 STATUS_IM_UPDATE = 1 MODE = 1 @@ -57,7 +68,7 @@ RESET_CHIP_VALUE = 0xB6 BME_CHIPS = { - 0x58: 'BMP280', 0x60: 'BME280', 0x61: 'BME680' + 0x58: 'BMP280', 0x60: 'BME280', 0x61: 'BME680', 0x55: 'BMP180' } BME_CHIP_ID_REG = 0xD0 @@ -81,6 +92,14 @@ def get_signed_byte(bits): return get_twos_complement(bits, 8) +def get_unsigned_short_msb(bits): + return bits[0] << 8 | bits[1] + + +def get_signed_short_msb(bits): + val = get_unsigned_short_msb(bits) + return get_twos_complement(val, 16) + class BME280: def __init__(self, config): self.printer = config.get_printer() @@ -188,6 +207,23 @@ def read_calibration_data_bme680(calib_data_1, calib_data_2): dig['G3'] = get_signed_byte(calib_data_2[13]) return dig + def read_calibration_data_bmp180(calib_data_1): + dig = {} + dig['AC1'] = get_signed_short_msb(calib_data_1[0:2]) + dig['AC2'] = get_signed_short_msb(calib_data_1[2:4]) + dig['AC3'] = get_signed_short_msb(calib_data_1[4:6]) + dig['AC4'] = get_unsigned_short_msb(calib_data_1[6:8]) + dig['AC5'] = get_unsigned_short_msb(calib_data_1[8:10]) + dig['AC6'] = get_unsigned_short_msb(calib_data_1[10:12]) + + dig['B1'] = get_signed_short_msb(calib_data_1[12:14]) + dig['B2'] = get_signed_short_msb(calib_data_1[14:16]) + + dig['MB'] = get_signed_short_msb(calib_data_1[16:18]) + dig['MC'] = get_signed_short_msb(calib_data_1[18:20]) + dig['MD'] = get_signed_short_msb(calib_data_1[20:22]) + return dig + chip_id = self.read_id() if chip_id not in BME_CHIPS.keys(): logging.info("bme280: Unknown Chip ID received %#x" % chip_id) @@ -201,15 +237,21 @@ def read_calibration_data_bme680(calib_data_1, calib_data_2): self.reactor.pause(self.reactor.monotonic() + .5) # Make sure non-volatile memory has been copied to registers - status = self.read_register('STATUS', 1)[0] - while status & STATUS_IM_UPDATE: - self.reactor.pause(self.reactor.monotonic() + .01) + if self.chip_type != 'BMP180': + # BMP180 has no status register available status = self.read_register('STATUS', 1)[0] + while status & STATUS_IM_UPDATE: + self.reactor.pause(self.reactor.monotonic() + .01) + status = self.read_register('STATUS', 1)[0] if self.chip_type == 'BME680': self.max_sample_time = 0.5 self.sample_timer = self.reactor.register_timer(self._sample_bme680) self.chip_registers = BME680_REGS + elif self.chip_type == 'BMP180': + self.max_sample_time = (1.25 + ((2.3 * self.os_pres) + .575)) / 1000 + self.sample_timer = self.reactor.register_timer(self._sample_bmp180) + self.chip_registers = BMP180_REGS else: self.max_sample_time = \ (1.25 + (2.3 * self.os_temp) + ((2.3 * self.os_pres) + .575) @@ -221,14 +263,19 @@ def read_calibration_data_bme680(calib_data_1, calib_data_2): self.write_register('CONFIG', (self.iir_filter & 0x07) << 2) # Read out and calculate the trimming parameters - cal_1 = self.read_register('CAL_1', 26) - cal_2 = self.read_register('CAL_2', 16) + if self.chip_type == 'BMP180': + cal_1 = self.read_register('CAL_1', 22) + else: + cal_1 = self.read_register('CAL_1', 26) + cal_2 = self.read_register('CAL_2', 16) if self.chip_type == 'BME280': self.dig = read_calibration_data_bme280(cal_1, cal_2) elif self.chip_type == 'BMP280': self.dig = read_calibration_data_bmp280(cal_1) elif self.chip_type == 'BME680': self.dig = read_calibration_data_bme680(cal_1, cal_2) + elif self.chip_type == 'BMP180': + self.dig = read_calibration_data_bmp180(cal_1) def _sample_bme280(self, eventtime): # Enter forced mode @@ -334,6 +381,43 @@ def data_ready(stat): self._callback(self.mcu.estimated_print_time(measured_time), self.temp) return measured_time + REPORT_TIME + def _sample_bmp180(self, eventtime): + meas = self.chip_registers['CRV_TEMP'] + self.write_register('CTRL_MEAS', meas) + + try: + self.reactor.pause(self.reactor.monotonic() + .01) + data = self.read_register('REG_MSB', 2) + temp_raw = (data[0] << 8) | data[1] + except Exception: + logging.exception("BMP180: Error reading temperature") + self.temp = self.pressure = .0 + return self.reactor.NEVER + + meas = self.chip_registers['CRV_PRES'] | (self.os_pres << 6) + self.write_register('CTRL_MEAS', meas) + + try: + self.reactor.pause(self.reactor.monotonic() + .01) + data = self.read_register('REG_MSB', 3) + pressure_raw = \ + ((data[0] << 16)|(data[1] << 8)|data[2]) >> (8 - self.os_pres) + except Exception: + logging.exception("BMP180: Error reading pressure") + self.temp = self.pressure = .0 + return self.reactor.NEVER + + self.temp = self._compensate_temp_bmp180(temp_raw) + self.pressure = self._compensate_pressure_bmp180(pressure_raw) / 100. + if self.temp < self.min_temp or self.temp > self.max_temp: + self.printer.invoke_shutdown( + "BMP180 temperature %0.1f outside range of %0.1f:%.01f" + % (self.temp, self.min_temp, self.max_temp)) + measured_time = self.reactor.monotonic() + self._callback(self.mcu.estimated_print_time(measured_time), self.temp) + return measured_time + REPORT_TIME + + def _compensate_temp(self, raw_temp): dig = self.dig var1 = ((raw_temp / 16384. - (dig['T1'] / 1024.)) * dig['T2']) @@ -443,6 +527,37 @@ def _calculate_gas_heater_duration(self, duration_ms): return duration_reg + def _compensate_temp_bmp180(self, raw_temp): + dig = self.dig + x1 = (raw_temp - dig['AC6']) * dig['AC5'] / 32768. + x2 = dig['MC'] * 2048 / (x1 + dig['MD']) + b5 = x1 + x2 + self.t_fine = b5 + return (b5 + 8)/16./10. + + def _compensate_pressure_bmp180(self, raw_pressure): + dig = self.dig + b5 = self.t_fine + b6 = b5 - 4000 + x1 = (dig['B2'] * (b6 * b6 / 4096)) / 2048 + x2 = dig['AC2'] * b6 / 2048 + x3 = x1 + x2 + b3 = ((int(dig['AC1'] * 4 + x3) << self.os_pres) + 2) / 4 + x1 = dig['AC3'] * b6 / 8192 + x2 = (dig['B1'] * (b6 * b6 / 4096)) / 65536 + x3 = ((x1 + x2) + 2) / 4 + b4 = dig['AC4'] * (x3 + 32768) / 32768 + b7 = (raw_pressure - b3) * (50000 >> self.os_pres) + if (b7 < 0x80000000): + p = (b7 * 2) / b4 + else: + p = (b7 / b4) * 2 + x1 = (p / 256) * (p / 256) + x1 = (x1 * 3038) / 65536 + x2 = (-7357 * p) / 65536 + p = p + (x1 + x2 + 3791) / 16. + return p + def read_id(self): # read chip id register regs = [BME_CHIP_ID_REG] From 187cc2f1b89e3870d694f8db6a64b116992106b7 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 1 Nov 2023 20:38:15 -0400 Subject: [PATCH 10/33] configfile: Improve support for python3.12 It seems python3.12 has removed support for readfp() - use read_file() instead. Signed-off-by: Kevin O'Connor --- klippy/configfile.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/klippy/configfile.py b/klippy/configfile.py index e5c0fb20bd0e..f099b563406d 100644 --- a/klippy/configfile.py +++ b/klippy/configfile.py @@ -217,7 +217,10 @@ def _parse_config_buffer(self, buffer, filename, fileconfig): data = '\n'.join(buffer) del buffer[:] sbuffer = io.StringIO(data) - fileconfig.readfp(sbuffer, filename) + if sys.version_info.major >= 3: + fileconfig.read_file(sbuffer, filename) + else: + fileconfig.readfp(sbuffer, filename) def _resolve_include(self, source_filename, include_spec, fileconfig, visited): dirname = os.path.dirname(source_filename) From bb4711c5d31e8159945f945c662e6668059a174f Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 8 Nov 2023 13:46:13 -0500 Subject: [PATCH 11/33] tmc5160: Increase maximum current error check It's possible to build and configure tmc5160 drivers with external mosfets that support more than 3 amps. The actual maximum for tmc5160 drivers is dependent on how the board is wired and the mosfets used. Increase the error check to 10 amps. This error checking is primarily intended to catch "obvious misconfigurations" (eg, specifying milli-amps instead of amps), and the new value of 10 amps should suffice for this task. Signed-off-by: Kevin O'Connor --- klippy/extras/tmc5160.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/klippy/extras/tmc5160.py b/klippy/extras/tmc5160.py index b82b5fecd2ff..7ff47abf9f88 100644 --- a/klippy/extras/tmc5160.py +++ b/klippy/extras/tmc5160.py @@ -257,7 +257,7 @@ ###################################################################### VREF = 0.325 -MAX_CURRENT = 3.000 +MAX_CURRENT = 10.000 # Maximum dependent on board, but 10 is safe sanity check class TMC5160CurrentHelper: def __init__(self, config, mcu_tmc): From ea2f6bc0f544132738c7f052ffcc586fa884a19a Mon Sep 17 00:00:00 2001 From: Andrei Ignat Date: Wed, 22 Nov 2023 17:53:38 +0100 Subject: [PATCH 12/33] exclude_object: Don't use gcmd.respond_error() (#6407) gcmd.respond_error() has been deprecated: 61524542d20e50c4866836d6ed23ca03521ffb15 Signed-off-by: Andrei Ignat --- klippy/extras/exclude_object.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/klippy/extras/exclude_object.py b/klippy/extras/exclude_object.py index 0a68d9b5ec37..1940127960ff 100644 --- a/klippy/extras/exclude_object.py +++ b/klippy/extras/exclude_object.py @@ -234,7 +234,7 @@ def cmd_EXCLUDE_OBJECT(self, gcmd): elif current: if not self.current_object: - gcmd.respond_error('There is no current object to cancel') + raise self.gcode.error('There is no current object to cancel') else: self._exclude_object(self.current_object) From 03f69cd81a129456a66e2deb03bc8b137504c179 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 20 Nov 2023 19:43:36 -0500 Subject: [PATCH 13/33] tmc: Query latest value during _init_registers() The set_register() code may block, and it therefore may be possible that the loop in _init_registers() could occur in parallel with other updates. That could result in a "OrderedDict mutated during iteration" error. Avoid the error by querying the latest value during each iteration of the loop. Signed-off-by: Kevin O'Connor --- klippy/extras/tmc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/klippy/extras/tmc.py b/klippy/extras/tmc.py index c3ece070d8ce..8143882abf19 100644 --- a/klippy/extras/tmc.py +++ b/klippy/extras/tmc.py @@ -262,7 +262,8 @@ def __init__(self, config, mcu_tmc, current_helper): desc=self.cmd_SET_TMC_CURRENT_help) def _init_registers(self, print_time=None): # Send registers - for reg_name, val in self.fields.registers.items(): + for reg_name in list(self.fields.registers.keys()): + val = self.fields.registers[reg_name] # Val may change during loop self.mcu_tmc.set_register(reg_name, val, print_time) cmd_INIT_TMC_help = "Initialize TMC stepper driver registers" def cmd_INIT_TMC(self, gcmd): From 38221df83ada0a1a9731b18b0226a02726b1c236 Mon Sep 17 00:00:00 2001 From: CODeRUS Date: Wed, 29 Nov 2023 17:31:49 +0100 Subject: [PATCH 14/33] avr: enable small code size options for 328 and 328p (#6411) Signed-off-by: Andrei Kozhevnikov --- src/avr/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/avr/Kconfig b/src/avr/Kconfig index ac3ffa2ab6e4..f1ad54395442 100644 --- a/src/avr/Kconfig +++ b/src/avr/Kconfig @@ -11,7 +11,7 @@ config AVR_SELECT select HAVE_GPIO_I2C select HAVE_GPIO_HARD_PWM select HAVE_STRICT_TIMING - select HAVE_LIMITED_CODE_SIZE if MACH_atmega168 + select HAVE_LIMITED_CODE_SIZE if MACH_atmega168 || MACH_atmega328 || MACH_atmega328p config BOARD_DIRECTORY string From 795ce490a05b2ebae724cff634317f38bc38d721 Mon Sep 17 00:00:00 2001 From: Thijs Triemstra Date: Mon, 4 Dec 2023 03:10:59 +0100 Subject: [PATCH 15/33] doc: updates to Installation.md (#6398) omit rpi device version nrs rpi2 or newer Signed-off-by: Thijs Triemstra --- docs/Installation.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/Installation.md b/docs/Installation.md index 5e97436feb2d..004d963a0796 100644 --- a/docs/Installation.md +++ b/docs/Installation.md @@ -2,7 +2,7 @@ These instructions assume the software will run on a Raspberry Pi computer in conjunction with OctoPrint. It is recommended that a -Raspberry Pi 2, 3, or 4 computer be used as the host machine (see the +Raspberry Pi 2 (or later) be used as the host machine (see the [FAQ](FAQ.md#can-i-run-klipper-on-something-other-than-a-raspberry-pi-3) for other machines). @@ -50,7 +50,7 @@ using a Linux or MacOS desktop, then the "ssh" software should already be installed on the desktop. There are free ssh clients available for other desktops (eg, [PuTTY](https://www.chiark.greenend.org.uk/~sgtatham/putty/)). Use the -ssh utility to connect to the Raspberry Pi (ssh pi@octopi -- password +ssh utility to connect to the Raspberry Pi (`ssh pi@octopi` -- password is "raspberry") and run the following commands: ``` @@ -135,18 +135,18 @@ web page and then configure the following items: Navigate to the Settings tab (the wrench icon at the top of the page). Under "Serial Connection" in "Additional serial ports" add -"/tmp/printer". Then click "Save". +`/tmp/printer`. Then click "Save". Enter the Settings tab again and under "Serial Connection" change the -"Serial Port" setting to "/tmp/printer". +"Serial Port" setting to `/tmp/printer`. In the Settings tab, navigate to the "Behavior" sub-tab and select the "Cancel any ongoing prints but stay connected to the printer" option. Click "Save". From the main page, under the "Connection" section (at the top left of -the page) make sure the "Serial Port" is set to "/tmp/printer" and -click "Connect". (If "/tmp/printer" is not an available selection then +the page) make sure the "Serial Port" is set to `/tmp/printer` and +click "Connect". (If `/tmp/printer` is not an available selection then try reloading the page.) Once connected, navigate to the "Terminal" tab and type "status" @@ -165,8 +165,8 @@ Arguably the easiest way to set the Klipper configuration file is to use a desktop editor that supports editing files over the "scp" and/or "sftp" protocols. There are freely available tools that support this (eg, Notepad++, WinSCP, and Cyberduck). Load the printer config file -in the editor and then save it as a file named "printer.cfg" in the -home directory of the pi user (ie, /home/pi/printer.cfg). +in the editor and then save it as a file named `printer.cfg` in the +home directory of the pi user (ie, `/home/pi/printer.cfg`). Alternatively, one can also copy and edit the file directly on the Raspberry Pi via ssh. That may look something like the following (be From 05d5451347e665e211662009837ee861a62dc372 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 3 Dec 2023 17:56:22 -0500 Subject: [PATCH 16/33] docs: Improve max_accel documentation in Config_Reference.md Note that the max_accel parameter is the actual acceleration used in most movements. Note that the accel/velocity limits can be changed using the SET_VELOCITY_LIMIT command. Signed-off-by: Kevin O'Connor --- docs/Config_Reference.md | 18 +++++++++++++----- docs/G-Codes.md | 7 +++++-- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index cc78ddbe3894..bcccebe06813 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -88,16 +88,22 @@ kinematics: # deltesian, polar, winch, or none. This parameter must be specified. max_velocity: # Maximum velocity (in mm/s) of the toolhead (relative to the -# print). This parameter must be specified. +# print). This value may be changed at runtime using the +# SET_VELOCITY_LIMIT command. This parameter must be specified. max_accel: # Maximum acceleration (in mm/s^2) of the toolhead (relative to the -# print). This parameter must be specified. +# print). Although this parameter is described as a "maximum" +# acceleration, in practice most moves that accelerate or decelerate +# will do so at the rate specified here. The value specified here +# may be changed at runtime using the SET_VELOCITY_LIMIT command. +# This parameter must be specified. #max_accel_to_decel: # A pseudo acceleration (in mm/s^2) controlling how fast the # toolhead may go from acceleration to deceleration. It is used to # reduce the top speed of short zig-zag moves (and thus reduce -# printer vibration from these moves). The default is half of -# max_accel. +# printer vibration from these moves). The value specified here may +# be changed at runtime using the SET_VELOCITY_LIMIT command. The +# default is half of max_accel. #square_corner_velocity: 5.0 # The maximum velocity (in mm/s) that the toolhead may travel a 90 # degree corner at. A non-zero value can reduce changes in extruder @@ -107,7 +113,9 @@ max_accel: # larger than 90 degrees will have a higher cornering velocity while # corners with angles less than 90 degrees will have a lower # cornering velocity. If this is set to zero then the toolhead will -# decelerate to zero at each corner. The default is 5mm/s. +# decelerate to zero at each corner. The value specified here may be +# changed at runtime using the SET_VELOCITY_LIMIT command. The +# default is 5mm/s. ``` ### [stepper] diff --git a/docs/G-Codes.md b/docs/G-Codes.md index 6d492c25137c..8c70609f18c9 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -1281,8 +1281,11 @@ The toolhead module is automatically loaded. #### SET_VELOCITY_LIMIT `SET_VELOCITY_LIMIT [VELOCITY=] [ACCEL=] -[ACCEL_TO_DECEL=] [SQUARE_CORNER_VELOCITY=]`: Modify the -printer's velocity limits. +[ACCEL_TO_DECEL=] [SQUARE_CORNER_VELOCITY=]`: This +command can alter the velocity limits that were specified in the +printer config file. See the +[printer config section](Config_Reference.md#printer) for a +description of each parameter. ### [tuning_tower] From fc102edc242a7c5e9b8c6415f447bf1a6a022ea7 Mon Sep 17 00:00:00 2001 From: Will Puckett Date: Tue, 5 Dec 2023 14:44:51 -0800 Subject: [PATCH 17/33] hard_pwm: Add pin defs for STM32F070 and STM32F072 (#6409) Define hard_pwm pins for STM32F070 and STM32F072, and update KConfig accordingly. Signed-off-by: Will Puckett --- src/stm32/Kconfig | 2 +- src/stm32/hard_pwm.c | 65 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index 7f50ea3ca868..c06bb6ffb0c2 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -10,7 +10,7 @@ config STM32_SELECT select HAVE_GPIO_I2C if !MACH_STM32F031 select HAVE_GPIO_SPI if !MACH_STM32F031 select HAVE_GPIO_SDIO if MACH_STM32F4 - select HAVE_GPIO_HARD_PWM if MACH_STM32F1 || MACH_STM32F4 || MACH_STM32F7 || MACH_STM32G0 || MACH_STM32H7 + select HAVE_GPIO_HARD_PWM if MACH_STM32F070 || MACH_STM32F072 || MACH_STM32F1 || MACH_STM32F4 || MACH_STM32F7 || MACH_STM32G0 || MACH_STM32H7 select HAVE_STRICT_TIMING select HAVE_CHIPID select HAVE_STEPPER_BOTH_EDGE diff --git a/src/stm32/hard_pwm.c b/src/stm32/hard_pwm.c index ad30051f83a5..bbdd18b2139e 100644 --- a/src/stm32/hard_pwm.c +++ b/src/stm32/hard_pwm.c @@ -20,7 +20,70 @@ struct gpio_pwm_info { }; static const struct gpio_pwm_info pwm_regs[] = { -#if CONFIG_MACH_STM32F1 +#if CONFIG_MACH_STM32F0 + #if CONFIG_MACH_STM32F070 + {TIM15, GPIO('A', 2), 1, GPIO_FUNCTION(0)}, + {TIM15, GPIO('A', 3), 2, GPIO_FUNCTION(0)}, + {TIM14, GPIO('A', 4), 1, GPIO_FUNCTION(4)}, + {TIM3, GPIO('A', 6), 1, GPIO_FUNCTION(1)}, + {TIM3, GPIO('A', 7), 2, GPIO_FUNCTION(1)}, + {TIM1, GPIO('A', 8), 1, GPIO_FUNCTION(2)}, + {TIM1, GPIO('A', 9), 2, GPIO_FUNCTION(2)}, + {TIM1, GPIO('A', 10), 3, GPIO_FUNCTION(2)}, + {TIM1, GPIO('A', 11), 4, GPIO_FUNCTION(2)}, + {TIM3, GPIO('B', 0), 3, GPIO_FUNCTION(1)}, + {TIM3, GPIO('B', 1), 4, GPIO_FUNCTION(1)}, + {TIM3, GPIO('B', 4), 1, GPIO_FUNCTION(1)}, + {TIM3, GPIO('B', 5), 2, GPIO_FUNCTION(1)}, + {TIM16, GPIO('B', 8), 1, GPIO_FUNCTION(2)}, + {TIM17, GPIO('B', 9), 1, GPIO_FUNCTION(2)}, + {TIM15, GPIO('B', 14), 1, GPIO_FUNCTION(1)}, + {TIM15, GPIO('B', 15), 2, GPIO_FUNCTION(1)}, + {TIM3, GPIO('C', 6), 1, GPIO_FUNCTION(0)}, + {TIM3, GPIO('C', 7), 2, GPIO_FUNCTION(0)}, + {TIM3, GPIO('C', 8), 3, GPIO_FUNCTION(0)}, + {TIM3, GPIO('C', 9), 4, GPIO_FUNCTION(0)} + #endif + #if CONFIG_MACH_STM32F072 + {TIM2, GPIO('A', 1), 2, GPIO_FUNCTION(2)}, + {TIM2, GPIO('A', 2), 3, GPIO_FUNCTION(2)}, + {TIM2, GPIO('A', 3), 4, GPIO_FUNCTION(2)}, + {TIM14, GPIO('A', 4), 1, GPIO_FUNCTION(4)}, + {TIM3, GPIO('A', 6), 1, GPIO_FUNCTION(1)}, + {TIM3, GPIO('A', 7), 2, GPIO_FUNCTION(1)}, + {TIM1, GPIO('A', 8), 1, GPIO_FUNCTION(2)}, + {TIM1, GPIO('A', 9), 2, GPIO_FUNCTION(2)}, + {TIM1, GPIO('A', 10), 3, GPIO_FUNCTION(2)}, + {TIM1, GPIO('A', 11), 4, GPIO_FUNCTION(2)}, + {TIM3, GPIO('B', 0), 3, GPIO_FUNCTION(1)}, + {TIM3, GPIO('B', 1), 4, GPIO_FUNCTION(1)}, + {TIM2, GPIO('B', 3), 2, GPIO_FUNCTION(2)}, + {TIM3, GPIO('B', 4), 1, GPIO_FUNCTION(1)}, + {TIM3, GPIO('B', 5), 2, GPIO_FUNCTION(1)}, + {TIM16, GPIO('B', 8), 1, GPIO_FUNCTION(2)}, + {TIM17, GPIO('B', 9), 1, GPIO_FUNCTION(2)}, + {TIM2, GPIO('B', 10), 3, GPIO_FUNCTION(2)}, + {TIM2, GPIO('B', 11), 4, GPIO_FUNCTION(2)}, + {TIM15, GPIO('B', 14), 1, GPIO_FUNCTION(1)}, + {TIM15, GPIO('B', 15), 2, GPIO_FUNCTION(1)}, + {TIM3, GPIO('C', 6), 1, GPIO_FUNCTION(0)}, + {TIM3, GPIO('C', 7), 2, GPIO_FUNCTION(0)}, + {TIM3, GPIO('C', 8), 3, GPIO_FUNCTION(0)}, + {TIM3, GPIO('C', 9), 4, GPIO_FUNCTION(0)}, + {TIM16, GPIO('E', 0), 1, GPIO_FUNCTION(0)}, + {TIM17, GPIO('E', 1), 1, GPIO_FUNCTION(0)}, + {TIM3, GPIO('E', 3), 1, GPIO_FUNCTION(0)}, + {TIM3, GPIO('E', 4), 2, GPIO_FUNCTION(0)}, + {TIM3, GPIO('E', 5), 3, GPIO_FUNCTION(0)}, + {TIM3, GPIO('E', 6), 4, GPIO_FUNCTION(0)}, + {TIM1, GPIO('E', 9), 1, GPIO_FUNCTION(0)}, + {TIM1, GPIO('E', 11), 2, GPIO_FUNCTION(0)}, + {TIM1, GPIO('E', 13), 3, GPIO_FUNCTION(0)}, + {TIM1, GPIO('E', 14), 4, GPIO_FUNCTION(0)}, + {TIM15, GPIO('F', 9), 1, GPIO_FUNCTION(0)}, + {TIM15, GPIO('F', 10), 2, GPIO_FUNCTION(0)} + #endif +#elif CONFIG_MACH_STM32F1 {TIM2, GPIO('A', 0), 1, GPIO_FUNCTION(2)}, {TIM2, GPIO('A', 1), 2, GPIO_FUNCTION(2)}, {TIM2, GPIO('A', 2), 3, GPIO_FUNCTION(2)}, From 0ccf5f8e479a9801b065034e07d974e694918772 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 5 Dec 2023 17:55:11 -0500 Subject: [PATCH 18/33] github: Temporarily disable close_reviewer_needed automation Temporarily disable the closing of PRs marked as "reviewer needed". Signed-off-by: Kevin O'Connor --- .github/workflows/stale-issue-bot.yaml | 96 +++++++++++++------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/.github/workflows/stale-issue-bot.yaml b/.github/workflows/stale-issue-bot.yaml index 2f05abc1fb7b..b23511615555 100644 --- a/.github/workflows/stale-issue-bot.yaml +++ b/.github/workflows/stale-issue-bot.yaml @@ -62,54 +62,54 @@ jobs: state: 'closed' }); } - # Close tickets marked with "reviewer needed" label for 2+ weeks - close_reviewer_needed: - if: github.repository == 'Klipper3d/klipper' - runs-on: ubuntu-latest - steps: - - uses: actions/github-script@v6 - with: - script: | - const issues = await github.rest.issues.listForRepo({ - owner: context.repo.owner, - repo: context.repo.repo, - state: 'open', - labels: 'reviewer needed', - assignee: 'none', - per_page: 100, - page: 1 - }); - msg = "Unfortunately a reviewer has not assigned themselves to" - + " this GitHub Pull Request and it is therefore being" - + " closed. It is a good idea to move" - + " further discussion to the [Klipper Discourse]" - + "(https://www.klipper3d.org/Contact.html#discourse-forum)" - + " server. Reviewers can reach out on that forum to let you" - + " know if they are interested and when they are available." - + "\n\n" - + "Best regards,\n" - + "~ Your friendly GitIssueBot" - + "\n\n" - + "PS: I'm just an automated script, not a human being."; - const expireMillis = 1000 * 60 * 60 * 24 * 14; - const curtime = new Date().getTime(); - for (const issue of issues.data.values()) { - const updatetime = new Date(issue.updated_at).getTime(); - if (curtime < updatetime + expireMillis) - continue; - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - body: msg - }); - await github.rest.issues.update({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - state: 'closed' - }); - } +# # Close tickets marked with "reviewer needed" label for 2+ weeks +# close_reviewer_needed: +# if: github.repository == 'Klipper3d/klipper' +# runs-on: ubuntu-latest +# steps: +# - uses: actions/github-script@v6 +# with: +# script: | +# const issues = await github.rest.issues.listForRepo({ +# owner: context.repo.owner, +# repo: context.repo.repo, +# state: 'open', +# labels: 'reviewer needed', +# assignee: 'none', +# per_page: 100, +# page: 1 +# }); +# msg = "Unfortunately a reviewer has not assigned themselves to" +# + " this GitHub Pull Request and it is therefore being" +# + " closed. It is a good idea to move" +# + " further discussion to the [Klipper Discourse]" +# + "(https://www.klipper3d.org/Contact.html#discourse-forum)" +# + " server. Reviewers can reach out on that forum to let you" +# + " know if they are interested and when they are available." +# + "\n\n" +# + "Best regards,\n" +# + "~ Your friendly GitIssueBot" +# + "\n\n" +# + "PS: I'm just an automated script, not a human being."; +# const expireMillis = 1000 * 60 * 60 * 24 * 14; +# const curtime = new Date().getTime(); +# for (const issue of issues.data.values()) { +# const updatetime = new Date(issue.updated_at).getTime(); +# if (curtime < updatetime + expireMillis) +# continue; +# await github.rest.issues.createComment({ +# owner: context.repo.owner, +# repo: context.repo.repo, +# issue_number: issue.number, +# body: msg +# }); +# await github.rest.issues.update({ +# owner: context.repo.owner, +# repo: context.repo.repo, +# issue_number: issue.number, +# state: 'closed' +# }); +# } # Mark unassigned PRs that are idle for 2 weeks mark_reviewer_needed: if: github.repository == 'Klipper3d/klipper' From 7ed3c679dc3cadb8cc853dbc30573774ee797597 Mon Sep 17 00:00:00 2001 From: Stefan Dej Date: Tue, 5 Dec 2023 21:53:41 +0100 Subject: [PATCH 19/33] config: Add LDO Leviathan v1.2 generic config Signed-off-by: Stefan Dej --- config/generic-ldo-leviathan-v1.2.cfg | 241 ++++++++++++++++++++++++++ test/klippy/printers.test | 1 + 2 files changed, 242 insertions(+) create mode 100644 config/generic-ldo-leviathan-v1.2.cfg diff --git a/config/generic-ldo-leviathan-v1.2.cfg b/config/generic-ldo-leviathan-v1.2.cfg new file mode 100644 index 000000000000..61fc1cd71a8e --- /dev/null +++ b/config/generic-ldo-leviathan-v1.2.cfg @@ -0,0 +1,241 @@ +# This file contains common pin mappings for the LDO Leviathan v1.2. + +# To use this config, during "make menuconfig", select "Enable +# low-level configuration options", select the STM32F446 micro-controller, +# select a "32KiB bootloader", and select a "12Mhz crystal". + +# See docs/Config_Reference.md for a description of parameters. + +# HV-STEPPER-0 +[stepper_x] +step_pin: PB10 +dir_pin: PB11 +enable_pin: !PG0 +microsteps: 32 +rotation_distance: 40 +endstop_pin: PC1 # X-ENDSTOP +position_endstop: 0 +position_max: 200 +homing_speed: 50 + +[tmc5160 stepper_x] +spi_bus: spi4 +cs_pin: PE15 +#diag0_pin: PG1 +interpolate: False +sense_resistor: 0.075 +run_current: 0.8 +stealthchop_threshold: 0 + +# HV-STEPPER-1 +[stepper_y] +step_pin: PF15 +dir_pin: PF14 +enable_pin: !PE9 +microsteps: 32 +rotation_distance: 40 +endstop_pin: PC2 # Y-ENDSTOP +position_endstop: 0 +position_max: 200 +homing_speed: 50 + +[tmc5160 stepper_y] +spi_bus: spi4 +cs_pin: PE11 +#diag0_pin: PE10 +interpolate: False +sense_resistor: 0.075 +run_current: 0.8 +stealthchop_threshold: 0 + +# STEPPER-0 +[stepper_z] +step_pin: PD4 +dir_pin: PD3 +enable_pin: !PD7 +microsteps: 32 +rotation_distance: 8 +endstop_pin: PC3 # Z-ENDSTOP +position_endstop: 0 +position_max: 200 + +[tmc2209 stepper_z] +uart_pin: PD5 +#diag_pin: PD6 +interpolate: False +run_current: 0.6 +stealthchop_threshold: 999999 + +# The Leviathan was developed for Voron printers. It therefore has several +# steppers for the z-axes, but only one heater for one extruder. + +# STEPPER-1 +#[stepper_z1] +#step_pin: PC12 +#dir_pin: PC11 +#enable_pin: !PD2 +#microsteps: 32 +#rotation_distance: 8 +# +#[tmc2209 stepper_z1] +#uart_pin: PD5 +##diag_pin: PD6 +#interpolate: False +#run_current: 0.6 +#stealthchop_threshold: 999999 + +# STEPPER-2 +#[stepper_z2] +#step_pin: PC9 +#dir_pin: PC8 +#enable_pin: !PC10 +#microsteps: 32 +#rotation_distance: 8 +# +#[tmc2209 stepper_z2] +#uart_pin: PA8 +##diag_pin: PA15 +#interpolate: False +#run_current: 0.6 +#stealthchop_threshold: 999999 + +# STEPPER-3 +#[stepper_z3] +#step_pin: PG7 +#dir_pin: PG6 +#enable_pin: !PC7 +#microsteps: 32 +#rotation_distance: 8 +# +#[tmc2209 stepper_z2] +#uart_pin: PG8 +##diag_pin: PC6 +#interpolate: False +#run_current: 0.6 +#stealthchop_threshold: 999999 + +# STEPPER-4 +[extruder] +step_pin: PD10 +dir_pin: PD9 +enable_pin: !PD13 +microsteps: 32 +rotation_distance: 22.67 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PG10 # HEATER +sensor_pin: PA2 # TH1 +sensor_type: ATC Semitec 104NT-4-R025H42G +pullup_resistor: 2200 +control: pid +pid_Kp: 36.787 +pid_Ki: 4.716 +pid_Kd: 71.735 +min_temp: 0 +max_temp: 250 + +[tmc2209 stepper_z] +uart_pin: PD11 +#diag_pin: PD12 +interpolate: False +run_current: 0.5 +stealthchop_threshold: 0 + +#[filament_switch_sensor material_0] +#switch_pin: PC0 # FILAMENT-SENSOR + +[heater_bed] +heater_pin: PG11 # HEATBED +sensor_pin: PA1 # TH0 +sensor_type: ATC Semitec 104GT-2 +pullup_resistor: 2200 +control: pid +pid_kp: 56.723 +pid_ki: 5.561 +pid_kd: 144.642 +min_temp: 0 +max_temp: 130 + +[fan] +pin: PB7 # FAN0 +#tachometer_pin: PB0 + +#[heater_fan fan1] +#pin: PB3 +#tachometer_pin: PB4 + +#[heater_fan fan2] +#pin: PF7 +#tachometer_pin: PF6 + +#[controller_fan fan3] +#pin: PF9 +#tachometer_pin: PF8 + +[mcu] +serial: /dev/serial/by-id/usb-Klipper_Klipper_firmware_12345-if00 +# CAN bus is also available on this board + +[printer] +kinematics: cartesian +max_velocity: 300 +max_accel: 3000 +max_z_velocity: 5 +max_z_accel: 100 + +[board_pins] +aliases: + # EXP1 header + EXP1_1=PG9, EXP1_2=PG12, + EXP1_3=PG13, EXP1_4=PG14, + EXP1_5=PC13, EXP1_6=PC14, + EXP1_7=PC15, EXP1_8=PF0, + EXP1_9=, EXP1_10=<5V>, + + # EXP2 header + EXP2_1=PA6, EXP2_2=PA5, + EXP2_3=PE2, EXP2_4=PE4, + EXP2_5=PE3, EXP2_6=PA7, + EXP2_7=PE5, EXP2_8=, + EXP2_9=, EXP2_10=PE4, + + # See the sample-lcd.cfg file for definitions of common LCD displays. + + # EXTENSION PORT + EXP3_1=<5V>, EXP3_2=<5V>, # max. 0.5A + EXP3_3=, EXP3_4=, + EXP3_5=<3.3V>, EXP3_6=<3.3V>, # max. 0.5A + EXP3_7=PF5, EXP3_8=PF4, + EXP3_9=PF3, EXP3_10=PF2, + EXP3_11=PC4, EXP3_12=PC5, # EXP3_11 and EXP3_12 are ADC inputs + EXP3_13=PB0, EXP3_14=PB1, # EXP3_13 and EXP3_14 are ADC inputs + EXP3_15=PE8, EXP3_16=PE7, # EXP3_15 is UART5_TX, EXP3_16 is UART5_RX + EXP3_17=PG5, EXP3_18=PG4, + EXP3_19=PG3, EXP3_20=PG2, + EXP3_21=PD15, EXP3_22=PD14, + EXP3_23=PB15, EXP3_24=PB14, # EXP3_23 is SPI2_MOSI + # EXP3_24 is SPI2_MISO + EXP3_25=PB13, EXP3_26=PB12, # EXP3_25 is SPI2_SCK + CAN2_TX + # EXP3_26 is SPI2_CS + CAN2_RX + EXP3_27=, EXP3_28=, + EXP3_29=<24V>, EXP3_30=<24V>, # max. 0.5A + +#[probe] +#sensor_pin: PF1 # Z-PROBE +#z_offset: 0 + +#[led my_led] +#white_pin: PE6 # LED-Strip + +#[neopixel my_neopixel] +#pin: PF10 # NEOPIXEL + +#[temperature_sensor TH2] +#sensor_type: ATC Semitec 104GT-2 +#sensor_pin: PA0 # TH2 +#pullup_resistor: 2200 + +#[temperature_sensor TH3] +#sensor_type: ATC Semitec 104GT-2 +#sensor_pin: PA3 # TH3 +#pullup_resistor: 2200 diff --git a/test/klippy/printers.test b/test/klippy/printers.test index 2ab96acc85e3..9edc57506eb9 100644 --- a/test/klippy/printers.test +++ b/test/klippy/printers.test @@ -238,6 +238,7 @@ CONFIG ../../config/generic-bigtreetech-octopus-pro-v1.0.cfg CONFIG ../../config/generic-fysetc-s6.cfg CONFIG ../../config/generic-fysetc-s6-v2.cfg CONFIG ../../config/generic-fysetc-spider.cfg +CONFIG ../../config/generic-ldo-leviathan-v1.2.cfg CONFIG ../../config/generic-mks-rumba32-v1.0.cfg CONFIG ../../config/printer-ratrig-v-minion-2021.cfg From d929be487b169111e78e62c9bec1b2fb2d065393 Mon Sep 17 00:00:00 2001 From: Bassam <61985779+bassamanator@users.noreply.github.com> Date: Wed, 6 Dec 2023 12:56:12 -0500 Subject: [PATCH 20/33] config: Update printer-sovol-sv06-2022.cfg to stock (#6416) This printer is advertised as having a `220*220*250mm build volume`. Signed-off-by: Bassam Husain --- config/printer-sovol-sv06-2022.cfg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/printer-sovol-sv06-2022.cfg b/config/printer-sovol-sv06-2022.cfg index 78a627c7ddca..9b476894bf71 100644 --- a/config/printer-sovol-sv06-2022.cfg +++ b/config/printer-sovol-sv06-2022.cfg @@ -28,7 +28,7 @@ microsteps: 16 rotation_distance: 40 endstop_pin: tmc2209_stepper_x:virtual_endstop position_endstop: 0 -position_max: 225 +position_max: 220 homing_speed: 40 homing_retract_dist: 0 @@ -50,7 +50,7 @@ microsteps: 16 rotation_distance: 40 endstop_pin: tmc2209_stepper_y:virtual_endstop position_endstop: 0 -position_max: 225 +position_max: 220 homing_speed: 40 homing_retract_dist: 0 @@ -72,7 +72,7 @@ microsteps: 16 rotation_distance: 4 endstop_pin: probe:z_virtual_endstop position_min: -4 -position_max: 261 +position_max: 250 homing_speed: 4 [tmc2209 stepper_z] From 5bd32e2984b5d34ab54267aed3f078bd990d33b5 Mon Sep 17 00:00:00 2001 From: Bassam <61985779+bassamanator@users.noreply.github.com> Date: Wed, 6 Dec 2023 12:57:10 -0500 Subject: [PATCH 21/33] config: Update printer-sovol-sv06-2022.cfg x_offset (#6417) Signed-off-by: Bassam Husain --- config/printer-sovol-sv06-2022.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/printer-sovol-sv06-2022.cfg b/config/printer-sovol-sv06-2022.cfg index 9b476894bf71..e9f179eeace1 100644 --- a/config/printer-sovol-sv06-2022.cfg +++ b/config/printer-sovol-sv06-2022.cfg @@ -127,7 +127,7 @@ pin: PA0 [probe] pin: PB1 -x_offset: 28 +x_offset: 27 y_offset: -20 z_offset: 0 samples: 2 From bafb126abd77edd0cb2e5ae3b5d99ff83272594c Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 26 Nov 2023 19:01:24 -0500 Subject: [PATCH 22/33] toolhead: Remove undocumented buffer management config parameters These internal low-level config parameters were never documented. Going forward, developers may modify them by altering the internal settings in toolhead.py. Signed-off-by: Kevin O'Connor --- docs/Config_Changes.md | 6 ++++++ klippy/toolhead.py | 33 ++++++++++++++------------------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/docs/Config_Changes.md b/docs/Config_Changes.md index 6d83238d0295..3606c6d00123 100644 --- a/docs/Config_Changes.md +++ b/docs/Config_Changes.md @@ -8,6 +8,12 @@ All dates in this document are approximate. ## Changes +20231207: Several undocumented config parameters in the `[printer]` +config section have been removed (the buffer_time_low, +buffer_time_high, buffer_time_start, and move_flush_time parameters). + +20231110: Klipper v0.12.0 released. + 20230826: If `safe_distance` is set or calculated to be 0 in `[dual_carriage]`, the carriages proximity checks will be disabled as per documentation. A user may wish to configure `safe_distance` explicitly to prevent accidental crashes diff --git a/klippy/toolhead.py b/klippy/toolhead.py index d8b9382570b6..58fa429f2e50 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -186,8 +186,12 @@ def add_move(self, move): # Enough moves have been queued to reach the target flush time. self.flush(lazy=True) +BUFFER_TIME_LOW = 1.0 +BUFFER_TIME_HIGH = 2.0 +BUFFER_TIME_START = 0.250 MIN_KIN_TIME = 0.100 MOVE_BATCH_TIME = 0.500 +STEPCOMPRESS_FLUSH_TIME = 0.050 SDS_CHECK_TIME = 0.001 # step+dir+step filter in stepcompress.c DRIP_SEGMENT_TIME = 0.050 @@ -221,19 +225,11 @@ def __init__(self, config): self.junction_deviation = 0. self._calc_junction_deviation() # Print time tracking - self.buffer_time_low = config.getfloat( - 'buffer_time_low', 1.000, above=0.) - self.buffer_time_high = config.getfloat( - 'buffer_time_high', 2.000, above=self.buffer_time_low) - self.buffer_time_start = config.getfloat( - 'buffer_time_start', 0.250, above=0.) - self.move_flush_time = config.getfloat( - 'move_flush_time', 0.050, above=0.) self.print_time = 0. self.special_queuing_state = "Flushed" self.need_check_stall = -1. self.flush_timer = self.reactor.register_timer(self._flush_handler) - self.move_queue.set_flush_time(self.buffer_time_high) + self.move_queue.set_flush_time(BUFFER_TIME_HIGH) self.idle_flush_print_time = 0. self.print_stall = 0 self.drip_completion = None @@ -288,7 +284,7 @@ def _update_move_time(self, next_print_time): free_time = max(fft, sg_flush_time - kin_flush_delay) self.trapq_finalize_moves(self.trapq, free_time) self.extruder.update_move_time(free_time) - mcu_flush_time = max(fft, sg_flush_time - self.move_flush_time) + mcu_flush_time = max(fft, sg_flush_time - STEPCOMPRESS_FLUSH_TIME) for m in self.all_mcus: m.flush_moves(mcu_flush_time) if self.print_time >= next_print_time: @@ -298,7 +294,7 @@ def _calc_print_time(self): est_print_time = self.mcu.estimated_print_time(curtime) kin_time = max(est_print_time + MIN_KIN_TIME, self.force_flush_time) kin_time += self.kin_flush_delay - min_print_time = max(est_print_time + self.buffer_time_start, kin_time) + min_print_time = max(est_print_time + BUFFER_TIME_START, kin_time) if min_print_time > self.print_time: self.print_time = min_print_time self.printer.send_event("toolhead:sync_print_time", @@ -339,7 +335,7 @@ def flush_step_generation(self): self.special_queuing_state = "Flushed" self.need_check_stall = -1. self.reactor.update_timer(self.flush_timer, self.reactor.NEVER) - self.move_queue.set_flush_time(self.buffer_time_high) + self.move_queue.set_flush_time(BUFFER_TIME_HIGH) self.idle_flush_print_time = 0. # Determine actual last "itersolve" flush time lastf = self.print_time - self.kin_flush_delay @@ -377,7 +373,7 @@ def _check_stall(self): while 1: est_print_time = self.mcu.estimated_print_time(eventtime) buffer_time = self.print_time - est_print_time - stall_time = buffer_time - self.buffer_time_high + stall_time = buffer_time - BUFFER_TIME_HIGH if stall_time <= 0.: break if not self.can_pause: @@ -386,15 +382,14 @@ def _check_stall(self): eventtime = self.reactor.pause(eventtime + min(1., stall_time)) if not self.special_queuing_state: # In main state - defer stall checking until needed - self.need_check_stall = (est_print_time + self.buffer_time_high - + 0.100) + self.need_check_stall = est_print_time + BUFFER_TIME_HIGH + 0.100 def _flush_handler(self, eventtime): try: print_time = self.print_time buffer_time = print_time - self.mcu.estimated_print_time(eventtime) - if buffer_time > self.buffer_time_low: + if buffer_time > BUFFER_TIME_LOW: # Running normally - reschedule check - return eventtime + buffer_time - self.buffer_time_low + return eventtime + buffer_time - BUFFER_TIME_LOW # Under ran low buffer mark - flush lookahead queue self.flush_step_generation() if print_time != self.print_time: @@ -452,7 +447,7 @@ def get_extruder(self): return self.extruder # Homing "drip move" handling def _update_drip_move_time(self, next_print_time): - flush_delay = DRIP_TIME + self.move_flush_time + self.kin_flush_delay + flush_delay = DRIP_TIME + STEPCOMPRESS_FLUSH_TIME + self.kin_flush_delay while self.print_time < next_print_time: if self.drip_completion.test(): raise DripModeEndSignal() @@ -472,7 +467,7 @@ def drip_move(self, newpos, speed, drip_completion): self.special_queuing_state = "Drip" self.need_check_stall = self.reactor.NEVER self.reactor.update_timer(self.flush_timer, self.reactor.NEVER) - self.move_queue.set_flush_time(self.buffer_time_high) + self.move_queue.set_flush_time(BUFFER_TIME_HIGH) self.idle_flush_print_time = 0. self.drip_completion = drip_completion # Submit move From 93cd8834f373a4a6fc0ad679575137f8b2e41da7 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 26 Nov 2023 19:05:36 -0500 Subject: [PATCH 23/33] toolhead: Clarify internal toolhead "stall" and "pause" naming Clarify the internal naming to make a more clear distinction between "stalling" (input not coming fast enough) and "pausing" (the need to hold up reading of input to avoid buffering too far into the future). Signed-off-by: Kevin O'Connor --- klippy/toolhead.py | 62 ++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/klippy/toolhead.py b/klippy/toolhead.py index 58fa429f2e50..0f7da6b908da 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -207,13 +207,9 @@ def __init__(self, config): self.all_mcus = [ m for n, m in self.printer.lookup_objects(module='mcu')] self.mcu = self.all_mcus[0] - self.can_pause = True - if self.mcu.is_fileoutput(): - self.can_pause = False self.move_queue = MoveQueue(self) + self.move_queue.set_flush_time(BUFFER_TIME_HIGH) self.commanded_pos = [0., 0., 0., 0.] - self.printer.register_event_handler("klippy:shutdown", - self._handle_shutdown) # Velocity and acceleration control self.max_velocity = config.getfloat('max_velocity', above=0.) self.max_accel = config.getfloat('max_accel', above=0.) @@ -224,14 +220,18 @@ def __init__(self, config): 'square_corner_velocity', 5., minval=0.) self.junction_deviation = 0. self._calc_junction_deviation() + # Input stall detection + self.check_stall_time = 0. + self.print_stall = 0 + # Input pause tracking + self.can_pause = True + if self.mcu.is_fileoutput(): + self.can_pause = False + self.need_check_pause = -1. # Print time tracking self.print_time = 0. self.special_queuing_state = "Flushed" - self.need_check_stall = -1. self.flush_timer = self.reactor.register_timer(self._flush_handler) - self.move_queue.set_flush_time(BUFFER_TIME_HIGH) - self.idle_flush_print_time = 0. - self.print_stall = 0 self.drip_completion = None # Kinematic step generation scan window time tracking self.kin_flush_delay = SDS_CHECK_TIME @@ -266,6 +266,8 @@ def __init__(self, config): self.cmd_SET_VELOCITY_LIMIT, desc=self.cmd_SET_VELOCITY_LIMIT_help) gcode.register_command('M204', self.cmd_M204) + self.printer.register_event_handler("klippy:shutdown", + self._handle_shutdown) # Load some default modules modules = ["gcode_move", "homing", "idle_timeout", "statistics", "manual_probe", "tuning_tower"] @@ -305,7 +307,7 @@ def _process_moves(self, moves): if self.special_queuing_state != "Drip": # Transition from "Flushed"/"Priming" state to main state self.special_queuing_state = "" - self.need_check_stall = -1. + self.need_check_pause = -1. self.reactor.update_timer(self.flush_timer, self.reactor.NOW) self._calc_print_time() # Queue moves into trapezoid motion queue (trapq) @@ -333,10 +335,10 @@ def flush_step_generation(self): # Transition from "Flushed"/"Priming"/main state to "Flushed" state self.move_queue.flush() self.special_queuing_state = "Flushed" - self.need_check_stall = -1. + self.need_check_pause = -1. self.reactor.update_timer(self.flush_timer, self.reactor.NEVER) self.move_queue.set_flush_time(BUFFER_TIME_HIGH) - self.idle_flush_print_time = 0. + self.check_stall_time = 0. # Determine actual last "itersolve" flush time lastf = self.print_time - self.kin_flush_delay # Calculate flush time that includes kinematic scan windows @@ -356,33 +358,33 @@ def get_last_move_time(self): if self.special_queuing_state: self._calc_print_time() return self.print_time - def _check_stall(self): + def _check_pause(self): eventtime = self.reactor.monotonic() if self.special_queuing_state: - if self.idle_flush_print_time: + if self.check_stall_time: # Was in "Flushed" state and got there from idle input est_print_time = self.mcu.estimated_print_time(eventtime) - if est_print_time < self.idle_flush_print_time: + if est_print_time < self.check_stall_time: self.print_stall += 1 - self.idle_flush_print_time = 0. + self.check_stall_time = 0. # Transition from "Flushed"/"Priming" state to "Priming" state self.special_queuing_state = "Priming" - self.need_check_stall = -1. + self.need_check_pause = -1. self.reactor.update_timer(self.flush_timer, eventtime + 0.100) - # Check if there are lots of queued moves and stall if so + # Check if there are lots of queued moves and pause if so while 1: est_print_time = self.mcu.estimated_print_time(eventtime) buffer_time = self.print_time - est_print_time - stall_time = buffer_time - BUFFER_TIME_HIGH - if stall_time <= 0.: + pause_time = buffer_time - BUFFER_TIME_HIGH + if pause_time <= 0.: break if not self.can_pause: - self.need_check_stall = self.reactor.NEVER + self.need_check_pause = self.reactor.NEVER return - eventtime = self.reactor.pause(eventtime + min(1., stall_time)) + eventtime = self.reactor.pause(eventtime + min(1., pause_time)) if not self.special_queuing_state: - # In main state - defer stall checking until needed - self.need_check_stall = est_print_time + BUFFER_TIME_HIGH + 0.100 + # In main state - defer pause checking until needed + self.need_check_pause = est_print_time + BUFFER_TIME_HIGH + 0.100 def _flush_handler(self, eventtime): try: print_time = self.print_time @@ -393,7 +395,7 @@ def _flush_handler(self, eventtime): # Under ran low buffer mark - flush lookahead queue self.flush_step_generation() if print_time != self.print_time: - self.idle_flush_print_time = self.print_time + self.check_stall_time = self.print_time except: logging.exception("Exception in flush_handler") self.printer.invoke_shutdown("Exception in flush_handler") @@ -419,8 +421,8 @@ def move(self, newpos, speed): self.extruder.check_move(move) self.commanded_pos[:] = move.end_pos self.move_queue.add_move(move) - if self.print_time > self.need_check_stall: - self._check_stall() + if self.print_time > self.need_check_pause: + self._check_pause() def manual_move(self, coord, speed): curpos = list(self.commanded_pos) for i in range(len(coord)): @@ -431,7 +433,7 @@ def manual_move(self, coord, speed): def dwell(self, delay): next_print_time = self.get_last_move_time() + max(0., delay) self._update_move_time(next_print_time) - self._check_stall() + self._check_pause() def wait_moves(self): self._flush_lookahead() eventtime = self.reactor.monotonic() @@ -465,10 +467,10 @@ def drip_move(self, newpos, speed, drip_completion): # Transition from "Flushed"/"Priming"/main state to "Drip" state self.move_queue.flush() self.special_queuing_state = "Drip" - self.need_check_stall = self.reactor.NEVER + self.need_check_pause = self.reactor.NEVER self.reactor.update_timer(self.flush_timer, self.reactor.NEVER) self.move_queue.set_flush_time(BUFFER_TIME_HIGH) - self.idle_flush_print_time = 0. + self.check_stall_time = 0. self.drip_completion = drip_completion # Submit move try: From 9e574c34970fe4c68c5ad44d4f1f5faac4682e1e Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 27 Nov 2023 18:02:47 -0500 Subject: [PATCH 24/33] toolhead: Separate out priming flush notification to its own timer Simplify the code by introducing a separate lookahead priming flush timer. After this change, the flush_timer is not active in any of the special queuing states. Signed-off-by: Kevin O'Connor --- klippy/toolhead.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/klippy/toolhead.py b/klippy/toolhead.py index 0f7da6b908da..95131c80b51a 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -232,6 +232,7 @@ def __init__(self, config): self.print_time = 0. self.special_queuing_state = "Flushed" self.flush_timer = self.reactor.register_timer(self._flush_handler) + self.priming_timer = None self.drip_completion = None # Kinematic step generation scan window time tracking self.kin_flush_delay = SDS_CHECK_TIME @@ -360,21 +361,24 @@ def get_last_move_time(self): return self.print_time def _check_pause(self): eventtime = self.reactor.monotonic() + est_print_time = self.mcu.estimated_print_time(eventtime) + buffer_time = self.print_time - est_print_time if self.special_queuing_state: if self.check_stall_time: # Was in "Flushed" state and got there from idle input - est_print_time = self.mcu.estimated_print_time(eventtime) if est_print_time < self.check_stall_time: self.print_stall += 1 self.check_stall_time = 0. # Transition from "Flushed"/"Priming" state to "Priming" state self.special_queuing_state = "Priming" self.need_check_pause = -1. - self.reactor.update_timer(self.flush_timer, eventtime + 0.100) + if self.priming_timer is None: + self.priming_timer = self.reactor.register_timer( + self._priming_handler) + wtime = eventtime + max(0.100, buffer_time - BUFFER_TIME_LOW) + self.reactor.update_timer(self.priming_timer, wtime) # Check if there are lots of queued moves and pause if so while 1: - est_print_time = self.mcu.estimated_print_time(eventtime) - buffer_time = self.print_time - est_print_time pause_time = buffer_time - BUFFER_TIME_HIGH if pause_time <= 0.: break @@ -382,9 +386,22 @@ def _check_pause(self): self.need_check_pause = self.reactor.NEVER return eventtime = self.reactor.pause(eventtime + min(1., pause_time)) + est_print_time = self.mcu.estimated_print_time(eventtime) + buffer_time = self.print_time - est_print_time if not self.special_queuing_state: # In main state - defer pause checking until needed self.need_check_pause = est_print_time + BUFFER_TIME_HIGH + 0.100 + def _priming_handler(self, eventtime): + self.reactor.unregister_timer(self.priming_timer) + self.priming_timer = None + try: + if self.special_queuing_state == "Priming": + self.flush_step_generation() + self.check_stall_time = self.print_time + except: + logging.exception("Exception in priming_handler") + self.printer.invoke_shutdown("Exception in priming_handler") + return self.reactor.NEVER def _flush_handler(self, eventtime): try: print_time = self.print_time From b7b13588c7bf8925e739954860eedc3a5eea42e7 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 27 Nov 2023 19:54:39 -0500 Subject: [PATCH 25/33] toolhead: Rework flushing to be based on mcu flush time Rename last_kin_move_time to need_flush_time and rename force_flush_time to last_flush_time to improve variable name clarity. Move low-level flushing to new _advance_flush_time() so that it is possible to flush the queues without needing to advance print_time. Signed-off-by: Kevin O'Connor --- klippy/toolhead.py | 73 ++++++++++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/klippy/toolhead.py b/klippy/toolhead.py index 95131c80b51a..b605a5694a1b 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -231,13 +231,14 @@ def __init__(self, config): # Print time tracking self.print_time = 0. self.special_queuing_state = "Flushed" - self.flush_timer = self.reactor.register_timer(self._flush_handler) self.priming_timer = None self.drip_completion = None + # Flush tracking + self.flush_timer = self.reactor.register_timer(self._flush_handler) + self.last_flush_time = self.need_flush_time = 0. # Kinematic step generation scan window time tracking self.kin_flush_delay = SDS_CHECK_TIME self.kin_flush_times = [] - self.force_flush_time = self.last_kin_move_time = 0. # Setup iterative solver ffi_main, ffi_lib = chelper.get_ffi() self.trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free) @@ -274,28 +275,36 @@ def __init__(self, config): "manual_probe", "tuning_tower"] for module_name in modules: self.printer.load_object(config, module_name) - # Print time tracking - def _update_move_time(self, next_print_time): - batch_time = MOVE_BATCH_TIME - kin_flush_delay = self.kin_flush_delay - fft = self.force_flush_time + # Print time and flush tracking + def _advance_flush_time(self, flush_time): + flush_time = max(flush_time, self.last_flush_time) + # Generate steps via itersolve + sg_flush_ceil = max(flush_time, self.print_time - self.kin_flush_delay) + sg_flush_time = min(flush_time + STEPCOMPRESS_FLUSH_TIME, sg_flush_ceil) + for sg in self.step_generators: + sg(sg_flush_time) + # Free trapq entries that are no longer needed + free_time = sg_flush_time - self.kin_flush_delay + self.trapq_finalize_moves(self.trapq, free_time) + self.extruder.update_move_time(free_time) + # Flush stepcompress and mcu steppersync + for m in self.all_mcus: + m.flush_moves(flush_time) + self.last_flush_time = flush_time + def _advance_move_time(self, next_print_time): + pt_delay = self.kin_flush_delay + STEPCOMPRESS_FLUSH_TIME + flush_time = max(self.last_flush_time, self.print_time - pt_delay) + self.print_time = max(self.print_time, next_print_time) + want_flush_time = max(flush_time, self.print_time - pt_delay) while 1: - self.print_time = min(self.print_time + batch_time, next_print_time) - sg_flush_time = max(fft, self.print_time - kin_flush_delay) - for sg in self.step_generators: - sg(sg_flush_time) - free_time = max(fft, sg_flush_time - kin_flush_delay) - self.trapq_finalize_moves(self.trapq, free_time) - self.extruder.update_move_time(free_time) - mcu_flush_time = max(fft, sg_flush_time - STEPCOMPRESS_FLUSH_TIME) - for m in self.all_mcus: - m.flush_moves(mcu_flush_time) - if self.print_time >= next_print_time: + flush_time = min(flush_time + MOVE_BATCH_TIME, want_flush_time) + self._advance_flush_time(flush_time) + if flush_time >= want_flush_time: break def _calc_print_time(self): curtime = self.reactor.monotonic() est_print_time = self.mcu.estimated_print_time(curtime) - kin_time = max(est_print_time + MIN_KIN_TIME, self.force_flush_time) + kin_time = max(est_print_time + MIN_KIN_TIME, self.last_flush_time) kin_time += self.kin_flush_delay min_print_time = max(est_print_time + BUFFER_TIME_START, kin_time) if min_print_time > self.print_time: @@ -330,8 +339,8 @@ def _process_moves(self, moves): # Generate steps for moves if self.special_queuing_state: self._update_drip_move_time(next_move_time) - self._update_move_time(next_move_time) - self.last_kin_move_time = max(self.last_kin_move_time, next_move_time) + self.note_kinematic_activity(next_move_time + self.kin_flush_delay) + self._advance_move_time(next_move_time) def flush_step_generation(self): # Transition from "Flushed"/"Priming"/main state to "Flushed" state self.move_queue.flush() @@ -340,16 +349,8 @@ def flush_step_generation(self): self.reactor.update_timer(self.flush_timer, self.reactor.NEVER) self.move_queue.set_flush_time(BUFFER_TIME_HIGH) self.check_stall_time = 0. - # Determine actual last "itersolve" flush time - lastf = self.print_time - self.kin_flush_delay - # Calculate flush time that includes kinematic scan windows - flush_time = max(lastf, self.last_kin_move_time + self.kin_flush_delay) - if flush_time > self.print_time: - # Flush in small time chunks - self._update_move_time(flush_time) - # Flush kinematic scan windows and step buffers - self.force_flush_time = max(self.force_flush_time, flush_time) - self._update_move_time(max(self.print_time, self.force_flush_time)) + # Flush all queues + self._advance_flush_time(self.need_flush_time) def _flush_lookahead(self): if self.special_queuing_state: return self.flush_step_generation() @@ -449,7 +450,7 @@ def manual_move(self, coord, speed): self.printer.send_event("toolhead:manual_move") def dwell(self, delay): next_print_time = self.get_last_move_time() + max(0., delay) - self._update_move_time(next_print_time) + self._advance_move_time(next_print_time) self._check_pause() def wait_moves(self): self._flush_lookahead() @@ -478,7 +479,8 @@ def _update_drip_move_time(self, next_print_time): self.drip_completion.wait(curtime + wait_time) continue npt = min(self.print_time + DRIP_SEGMENT_TIME, next_print_time) - self._update_move_time(npt) + self.note_kinematic_activity(npt + self.kin_flush_delay) + self._advance_move_time(npt) def drip_move(self, newpos, speed, drip_completion): self.dwell(self.kin_flush_delay) # Transition from "Flushed"/"Priming"/main state to "Drip" state @@ -505,8 +507,9 @@ def drip_move(self, newpos, speed, drip_completion): self.flush_step_generation() # Misc commands def stats(self, eventtime): + max_queue_time = max(self.print_time, self.last_flush_time) for m in self.all_mcus: - m.check_active(self.print_time, eventtime) + m.check_active(max_queue_time, eventtime) buffer_time = self.print_time - self.mcu.estimated_print_time(eventtime) is_active = buffer_time > -60. or not self.special_queuing_state if self.special_queuing_state == "Drip": @@ -556,7 +559,7 @@ def register_lookahead_callback(self, callback): return last_move.timing_callbacks.append(callback) def note_kinematic_activity(self, kin_time): - self.last_kin_move_time = max(self.last_kin_move_time, kin_time) + self.need_flush_time = max(self.need_flush_time, kin_time) def get_max_velocity(self): return self.max_velocity, self.max_accel def _calc_junction_deviation(self): From c491ea669f8b189f72824b9d20f005fb6e86afe7 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 28 Nov 2023 11:27:25 -0500 Subject: [PATCH 26/33] toolhead: Support flushing even while lookahead queue is idle Track a "NeedPrime" queue state instead of the "Flushed" state, and continue running the background flushing timer as long as there may be data in any of the move queues. Signed-off-by: Kevin O'Connor --- klippy/toolhead.py | 71 +++++++++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 26 deletions(-) diff --git a/klippy/toolhead.py b/klippy/toolhead.py index b605a5694a1b..a04451c67dd0 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -189,6 +189,8 @@ def add_move(self, move): BUFFER_TIME_LOW = 1.0 BUFFER_TIME_HIGH = 2.0 BUFFER_TIME_START = 0.250 +BGFLUSH_LOW_TIME = 0.200 +BGFLUSH_BATCH_TIME = 0.200 MIN_KIN_TIME = 0.100 MOVE_BATCH_TIME = 0.500 STEPCOMPRESS_FLUSH_TIME = 0.050 @@ -230,11 +232,12 @@ def __init__(self, config): self.need_check_pause = -1. # Print time tracking self.print_time = 0. - self.special_queuing_state = "Flushed" + self.special_queuing_state = "NeedPrime" self.priming_timer = None self.drip_completion = None # Flush tracking self.flush_timer = self.reactor.register_timer(self._flush_handler) + self.do_kick_flush_timer = True self.last_flush_time = self.need_flush_time = 0. # Kinematic step generation scan window time tracking self.kin_flush_delay = SDS_CHECK_TIME @@ -315,10 +318,9 @@ def _process_moves(self, moves): # Resync print_time if necessary if self.special_queuing_state: if self.special_queuing_state != "Drip": - # Transition from "Flushed"/"Priming" state to main state + # Transition from "NeedPrime"/"Priming" state to main state self.special_queuing_state = "" self.need_check_pause = -1. - self.reactor.update_timer(self.flush_timer, self.reactor.NOW) self._calc_print_time() # Queue moves into trapezoid motion queue (trapq) next_move_time = self.print_time @@ -341,24 +343,22 @@ def _process_moves(self, moves): self._update_drip_move_time(next_move_time) self.note_kinematic_activity(next_move_time + self.kin_flush_delay) self._advance_move_time(next_move_time) - def flush_step_generation(self): - # Transition from "Flushed"/"Priming"/main state to "Flushed" state + def _flush_lookahead(self): + # Transit from "NeedPrime"/"Priming"/"Drip"/main state to "NeedPrime" self.move_queue.flush() - self.special_queuing_state = "Flushed" + self.special_queuing_state = "NeedPrime" self.need_check_pause = -1. - self.reactor.update_timer(self.flush_timer, self.reactor.NEVER) self.move_queue.set_flush_time(BUFFER_TIME_HIGH) self.check_stall_time = 0. - # Flush all queues + def flush_step_generation(self): + self._flush_lookahead() self._advance_flush_time(self.need_flush_time) - def _flush_lookahead(self): - if self.special_queuing_state: - return self.flush_step_generation() - self.move_queue.flush() def get_last_move_time(self): - self._flush_lookahead() if self.special_queuing_state: + self._flush_lookahead() self._calc_print_time() + else: + self.move_queue.flush() return self.print_time def _check_pause(self): eventtime = self.reactor.monotonic() @@ -366,11 +366,11 @@ def _check_pause(self): buffer_time = self.print_time - est_print_time if self.special_queuing_state: if self.check_stall_time: - # Was in "Flushed" state and got there from idle input + # Was in "NeedPrime" state and got there from idle input if est_print_time < self.check_stall_time: self.print_stall += 1 self.check_stall_time = 0. - # Transition from "Flushed"/"Priming" state to "Priming" state + # Transition from "NeedPrime"/"Priming" state to "Priming" state self.special_queuing_state = "Priming" self.need_check_pause = -1. if self.priming_timer is None: @@ -397,7 +397,7 @@ def _priming_handler(self, eventtime): self.priming_timer = None try: if self.special_queuing_state == "Priming": - self.flush_step_generation() + self._flush_lookahead() self.check_stall_time = self.print_time except: logging.exception("Exception in priming_handler") @@ -405,15 +405,28 @@ def _priming_handler(self, eventtime): return self.reactor.NEVER def _flush_handler(self, eventtime): try: - print_time = self.print_time - buffer_time = print_time - self.mcu.estimated_print_time(eventtime) - if buffer_time > BUFFER_TIME_LOW: - # Running normally - reschedule check - return eventtime + buffer_time - BUFFER_TIME_LOW - # Under ran low buffer mark - flush lookahead queue - self.flush_step_generation() - if print_time != self.print_time: - self.check_stall_time = self.print_time + est_print_time = self.mcu.estimated_print_time(eventtime) + if not self.special_queuing_state: + # In "main" state - flush lookahead if buffer runs low + print_time = self.print_time + buffer_time = print_time - est_print_time + if buffer_time > BUFFER_TIME_LOW: + # Running normally - reschedule check + return eventtime + buffer_time - BUFFER_TIME_LOW + # Under ran low buffer mark - flush lookahead queue + self._flush_lookahead() + if print_time != self.print_time: + self.check_stall_time = self.print_time + # In "NeedPrime"/"Priming" state - flush queues if needed + while 1: + if self.last_flush_time >= self.need_flush_time: + self.do_kick_flush_timer = True + return self.reactor.NEVER + buffer_time = self.last_flush_time - est_print_time + if buffer_time > BGFLUSH_LOW_TIME: + return eventtime + buffer_time - BGFLUSH_LOW_TIME + ftime = est_print_time + BGFLUSH_LOW_TIME + BGFLUSH_BATCH_TIME + self._advance_flush_time(min(self.need_flush_time, ftime)) except: logging.exception("Exception in flush_handler") self.printer.invoke_shutdown("Exception in flush_handler") @@ -483,11 +496,12 @@ def _update_drip_move_time(self, next_print_time): self._advance_move_time(npt) def drip_move(self, newpos, speed, drip_completion): self.dwell(self.kin_flush_delay) - # Transition from "Flushed"/"Priming"/main state to "Drip" state + # Transition from "NeedPrime"/"Priming"/main state to "Drip" state self.move_queue.flush() self.special_queuing_state = "Drip" self.need_check_pause = self.reactor.NEVER self.reactor.update_timer(self.flush_timer, self.reactor.NEVER) + self.do_kick_flush_timer = False self.move_queue.set_flush_time(BUFFER_TIME_HIGH) self.check_stall_time = 0. self.drip_completion = drip_completion @@ -495,6 +509,7 @@ def drip_move(self, newpos, speed, drip_completion): try: self.move(newpos, speed) except self.printer.command_error as e: + self.reactor.update_timer(self.flush_timer, self.reactor.NOW) self.flush_step_generation() raise # Transmit move in "drip" mode @@ -504,6 +519,7 @@ def drip_move(self, newpos, speed, drip_completion): self.move_queue.reset() self.trapq_finalize_moves(self.trapq, self.reactor.NEVER) # Exit "Drip" state + self.reactor.update_timer(self.flush_timer, self.reactor.NOW) self.flush_step_generation() # Misc commands def stats(self, eventtime): @@ -560,6 +576,9 @@ def register_lookahead_callback(self, callback): last_move.timing_callbacks.append(callback) def note_kinematic_activity(self, kin_time): self.need_flush_time = max(self.need_flush_time, kin_time) + if self.do_kick_flush_timer: + self.do_kick_flush_timer = False + self.reactor.update_timer(self.flush_timer, self.reactor.NOW) def get_max_velocity(self): return self.max_velocity, self.max_accel def _calc_junction_deviation(self): From 99d7af87fd7cd74b9a6eab355eb71327ed3b8256 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 28 Nov 2023 12:49:53 -0500 Subject: [PATCH 27/33] pwm_tool: Notify the toolhead that the move_queue needs to be flushed Call toolhead.note_kinematic_activity() on each pin update to ensure that those updates will be flushed properly. This fixes "Timer too close" errors on SET_PIN commands that are issued when the toolhead is idle. Signed-off-by: Kevin O'Connor --- klippy/extras/pwm_tool.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/klippy/extras/pwm_tool.py b/klippy/extras/pwm_tool.py index c012bb317752..e3a7377c86a6 100644 --- a/klippy/extras/pwm_tool.py +++ b/klippy/extras/pwm_tool.py @@ -143,6 +143,8 @@ def _set_pin(self, print_time, value): self.mcu_pin.set_pwm(print_time, value) self.last_value = value self.last_print_time = print_time + toolhead = self.printer.lookup_object('toolhead') + toolhead.note_kinematic_activity(print_time) cmd_SET_PIN_help = "Set the value of an output pin" def cmd_SET_PIN(self, gcmd): # Read requested value From 62bf52bfcf1aab6d97236c3945320d790774ad92 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 4 Dec 2023 14:00:47 -0500 Subject: [PATCH 28/33] serialqueue: Simplify sequence number upconversion Signed-off-by: Kevin O'Connor --- klippy/chelper/serialqueue.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/klippy/chelper/serialqueue.c b/klippy/chelper/serialqueue.c index e6810933aabc..c207495cdc6c 100644 --- a/klippy/chelper/serialqueue.c +++ b/klippy/chelper/serialqueue.c @@ -222,12 +222,11 @@ handle_message(struct serialqueue *sq, double eventtime, int len) pthread_mutex_lock(&sq->lock); // Calculate receive sequence number - uint64_t rseq = ((sq->receive_seq & ~MESSAGE_SEQ_MASK) - | (sq->input_buf[MESSAGE_POS_SEQ] & MESSAGE_SEQ_MASK)); + uint32_t rseq_delta = ((sq->input_buf[MESSAGE_POS_SEQ] - sq->receive_seq) + & MESSAGE_SEQ_MASK); + uint64_t rseq = sq->receive_seq + rseq_delta; if (rseq != sq->receive_seq) { // New sequence number - if (rseq < sq->receive_seq) - rseq += MESSAGE_SEQ_MASK+1; if (rseq > sq->send_seq && sq->receive_seq != 1) { // An ack for a message not sent? Out of order message? sq->bytes_invalid += len; From c5bd813d8b2199b3a1c08d1e29ac0a97d49e3ff8 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 4 Dec 2023 14:01:12 -0500 Subject: [PATCH 29/33] clocksync: Simplify 32bit clock upconversion code Signed-off-by: Kevin O'Connor --- klippy/clocksync.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/klippy/clocksync.py b/klippy/clocksync.py index f32ed3cd6188..80ed9db61f25 100644 --- a/klippy/clocksync.py +++ b/klippy/clocksync.py @@ -66,10 +66,8 @@ def _handle_clock(self, params): self.queries_pending = 0 # Extend clock to 64bit last_clock = self.last_clock - clock = (last_clock & ~0xffffffff) | params['clock'] - if clock < last_clock: - clock += 0x100000000 - self.last_clock = clock + clock_delta = (params['clock'] - last_clock) & 0xffffffff + self.last_clock = clock = last_clock + clock_delta # Check if this is the best round-trip-time seen so far sent_time = params['#sent_time'] if not sent_time: @@ -138,10 +136,9 @@ def estimated_print_time(self, eventtime): # misc commands def clock32_to_clock64(self, clock32): last_clock = self.last_clock - clock_diff = (last_clock - clock32) & 0xffffffff - if clock_diff & 0x80000000: - return last_clock + 0x100000000 - clock_diff - return last_clock - clock_diff + clock_diff = (clock32 - last_clock) & 0xffffffff + clock_diff -= (clock_diff & 0x80000000) << 1 + return last_clock + clock_diff def is_active(self): return self.queries_pending <= 4 def dump_debug(self): From fe7082e4a8e0beb7014584a16aaa22dc7084a01a Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 4 Dec 2023 14:03:27 -0500 Subject: [PATCH 30/33] buttons: Simplify ack upconversion code Signed-off-by: Kevin O'Connor --- klippy/extras/buttons.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/klippy/extras/buttons.py b/klippy/extras/buttons.py index 1a6147d1df28..472742576dae 100644 --- a/klippy/extras/buttons.py +++ b/klippy/extras/buttons.py @@ -57,10 +57,9 @@ def build_config(self): def handle_buttons_state(self, params): # Expand the message ack_count from 8-bit ack_count = self.ack_count - ack_diff = (ack_count - params['ack_count']) & 0xff - if ack_diff & 0x80: - ack_diff -= 0x100 - msg_ack_count = ack_count - ack_diff + ack_diff = (params['ack_count'] - ack_count) & 0xff + ack_diff -= (ack_diff & 0x80) << 1 + msg_ack_count = ack_count + ack_diff # Determine new buttons buttons = bytearray(params['state']) new_count = msg_ack_count + len(buttons) - self.ack_count From 1a83845c9f16577921b09053589e6a90627e3674 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 4 Dec 2023 14:08:22 -0500 Subject: [PATCH 31/33] angle: Simplify sequence upconversion Signed-off-by: Kevin O'Connor --- klippy/extras/angle.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/klippy/extras/angle.py b/klippy/extras/angle.py index 2e17749e90c7..30a4447ad65c 100644 --- a/klippy/extras/angle.py +++ b/klippy/extras/angle.py @@ -85,9 +85,9 @@ def apply_calibration(self, samples): cal2 = calibration[bucket + 1] adj = (angle & interp_mask) * (cal2 - cal1) adj = cal1 + ((adj + interp_round) >> interp_bits) - angle_diff = (angle - adj) & 0xffff + angle_diff = (adj - angle) & 0xffff angle_diff -= (angle_diff & 0x8000) << 1 - new_angle = angle - angle_diff + new_angle = angle + angle_diff if calibration_reversed: new_angle = -new_angle samples[i] = (samp_time, new_angle) @@ -375,9 +375,9 @@ def update_clock(self): mcu_clock, chip_clock = self._query_clock() mdiff = mcu_clock - self.last_chip_mcu_clock chip_mclock = self.last_chip_clock + int(mdiff * self.chip_freq + .5) - cdiff = (chip_mclock - chip_clock) & 0xffff + cdiff = (chip_clock - chip_mclock) & 0xffff cdiff -= (cdiff & 0x8000) << 1 - new_chip_clock = chip_mclock - cdiff + new_chip_clock = chip_mclock + cdiff self.chip_freq = float(new_chip_clock - self.last_chip_clock) / mdiff self.last_chip_clock = new_chip_clock self.last_chip_mcu_clock = mcu_clock @@ -489,21 +489,19 @@ def _extract_samples(self, raw_samples): count = error_count = 0 samples = [None] * (len(raw_samples) * 16) for params in raw_samples: - seq = (last_sequence & ~0xffff) | params['sequence'] - if seq < last_sequence: - seq += 0x10000 - last_sequence = seq + seq_diff = (params['sequence'] - last_sequence) & 0xffff + last_sequence += seq_diff d = bytearray(params['data']) - msg_mclock = start_clock + seq*16*sample_ticks + msg_mclock = start_clock + last_sequence*16*sample_ticks for i in range(len(d) // 3): tcode = d[i*3] if tcode == TCODE_ERROR: error_count += 1 continue raw_angle = d[i*3 + 1] | (d[i*3 + 2] << 8) - angle_diff = (last_angle - raw_angle) & 0xffff + angle_diff = (raw_angle - last_angle) & 0xffff angle_diff -= (angle_diff & 0x8000) << 1 - last_angle -= angle_diff + last_angle += angle_diff mclock = msg_mclock + i*sample_ticks if is_tcode_absolute: # tcode is tle5012b frame counter From 2c2bb720fae54422cf10a71569ce40476e909c09 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 4 Dec 2023 14:13:37 -0500 Subject: [PATCH 32/33] adxl345: Simplify sequence and limit_count upconversion Signed-off-by: Kevin O'Connor --- klippy/extras/adxl345.py | 18 +++++++----------- klippy/extras/lis2dw.py | 18 +++++++----------- klippy/extras/mpu9250.py | 18 +++++++----------- 3 files changed, 21 insertions(+), 33 deletions(-) diff --git a/klippy/extras/adxl345.py b/klippy/extras/adxl345.py index f5af2f166324..52698cb6cfa7 100644 --- a/klippy/extras/adxl345.py +++ b/klippy/extras/adxl345.py @@ -307,9 +307,9 @@ def _extract_samples(self, raw_samples): count = seq = 0 samples = [None] * (len(raw_samples) * SAMPLES_PER_BLOCK) for params in raw_samples: - seq_diff = (last_sequence - params['sequence']) & 0xffff + seq_diff = (params['sequence'] - last_sequence) & 0xffff seq_diff -= (seq_diff & 0x8000) << 1 - seq = last_sequence - seq_diff + seq = last_sequence + seq_diff d = bytearray(params['data']) msg_cdiff = seq * SAMPLES_PER_BLOCK - chip_base for i in range(len(d) // BYTES_PER_SAMPLE): @@ -343,15 +343,11 @@ def _update_clock(self, minclock=0): else: raise self.printer.command_error("Unable to query adxl345 fifo") mcu_clock = self.mcu.clock32_to_clock64(params['clock']) - sequence = (self.last_sequence & ~0xffff) | params['next_sequence'] - if sequence < self.last_sequence: - sequence += 0x10000 - self.last_sequence = sequence + seq_diff = (params['next_sequence'] - self.last_sequence) & 0xffff + self.last_sequence += seq_diff buffered = params['buffered'] - limit_count = (self.last_limit_count & ~0xffff) | params['limit_count'] - if limit_count < self.last_limit_count: - limit_count += 0x10000 - self.last_limit_count = limit_count + lc_diff = (params['limit_count'] - self.last_limit_count) & 0xffff + self.last_limit_count += lc_diff duration = params['query_ticks'] if duration > self.max_query_duration: # Skip measurement as a high query time could skew clock tracking @@ -359,7 +355,7 @@ def _update_clock(self, minclock=0): self.mcu.seconds_to_clock(.000005)) return self.max_query_duration = 2 * duration - msg_count = (sequence * SAMPLES_PER_BLOCK + msg_count = (self.last_sequence * SAMPLES_PER_BLOCK + buffered // BYTES_PER_SAMPLE + fifo) # The "chip clock" is the message counter plus .5 for average # inaccuracy of query responses and plus .5 for assumed offset diff --git a/klippy/extras/lis2dw.py b/klippy/extras/lis2dw.py index 5cf6763939c5..ae62c22f67df 100644 --- a/klippy/extras/lis2dw.py +++ b/klippy/extras/lis2dw.py @@ -118,9 +118,9 @@ def _extract_samples(self, raw_samples): count = seq = 0 samples = [None] * (len(raw_samples) * SAMPLES_PER_BLOCK) for params in raw_samples: - seq_diff = (last_sequence - params['sequence']) & 0xffff + seq_diff = (params['sequence'] - last_sequence) & 0xffff seq_diff -= (seq_diff & 0x8000) << 1 - seq = last_sequence - seq_diff + seq = last_sequence + seq_diff d = bytearray(params['data']) msg_cdiff = seq * SAMPLES_PER_BLOCK - chip_base @@ -156,15 +156,11 @@ def _update_clock(self, minclock=0): else: raise self.printer.command_error("Unable to query lis2dw fifo") mcu_clock = self.mcu.clock32_to_clock64(params['clock']) - sequence = (self.last_sequence & ~0xffff) | params['next_sequence'] - if sequence < self.last_sequence: - sequence += 0x10000 - self.last_sequence = sequence + seq_diff = (params['next_sequence'] - self.last_sequence) & 0xffff + self.last_sequence += seq_diff buffered = params['buffered'] - limit_count = (self.last_limit_count & ~0xffff) | params['limit_count'] - if limit_count < self.last_limit_count: - limit_count += 0x10000 - self.last_limit_count = limit_count + lc_diff = (params['limit_count'] - self.last_limit_count) & 0xffff + self.last_limit_count += lc_diff duration = params['query_ticks'] if duration > self.max_query_duration: # Skip measurement as a high query time could skew clock tracking @@ -172,7 +168,7 @@ def _update_clock(self, minclock=0): self.mcu.seconds_to_clock(.000005)) return self.max_query_duration = 2 * duration - msg_count = (sequence * SAMPLES_PER_BLOCK + msg_count = (self.last_sequence * SAMPLES_PER_BLOCK + buffered // BYTES_PER_SAMPLE + fifo) # The "chip clock" is the message counter plus .5 for average # inaccuracy of query responses and plus .5 for assumed offset diff --git a/klippy/extras/mpu9250.py b/klippy/extras/mpu9250.py index dc25449bba7b..72c1f6817ca3 100644 --- a/klippy/extras/mpu9250.py +++ b/klippy/extras/mpu9250.py @@ -132,9 +132,9 @@ def _extract_samples(self, raw_samples): count = seq = 0 samples = [None] * (len(raw_samples) * SAMPLES_PER_BLOCK) for params in raw_samples: - seq_diff = (last_sequence - params['sequence']) & 0xffff + seq_diff = (params['sequence'] - last_sequence) & 0xffff seq_diff -= (seq_diff & 0x8000) << 1 - seq = last_sequence - seq_diff + seq = last_sequence + seq_diff d = bytearray(params['data']) msg_cdiff = seq * SAMPLES_PER_BLOCK - chip_base @@ -168,15 +168,11 @@ def _update_clock(self, minclock=0): else: raise self.printer.command_error("Unable to query mpu9250 fifo") mcu_clock = self.mcu.clock32_to_clock64(params['clock']) - sequence = (self.last_sequence & ~0xffff) | params['next_sequence'] - if sequence < self.last_sequence: - sequence += 0x10000 - self.last_sequence = sequence + seq_diff = (params['next_sequence'] - self.last_sequence) & 0xffff + self.last_sequence += seq_diff buffered = params['buffered'] - limit_count = (self.last_limit_count & ~0xffff) | params['limit_count'] - if limit_count < self.last_limit_count: - limit_count += 0x10000 - self.last_limit_count = limit_count + lc_diff = (params['limit_count'] - self.last_limit_count) & 0xffff + self.last_limit_count += lc_diff duration = params['query_ticks'] if duration > self.max_query_duration: # Skip measurement as a high query time could skew clock tracking @@ -184,7 +180,7 @@ def _update_clock(self, minclock=0): self.mcu.seconds_to_clock(.000005)) return self.max_query_duration = 2 * duration - msg_count = (sequence * SAMPLES_PER_BLOCK + msg_count = (self.last_sequence * SAMPLES_PER_BLOCK + buffered // BYTES_PER_SAMPLE + fifo) # The "chip clock" is the message counter plus .5 for average # inaccuracy of query responses and plus .5 for assumed offset From 6676c1df86e9cb9480b9dd98f77357f69d8ef0d8 Mon Sep 17 00:00:00 2001 From: Pedro Lamas Date: Mon, 6 Nov 2023 15:06:36 +0000 Subject: [PATCH 33/33] gcode: expose status with available commands Signed-off-by: Pedro Lamas --- docs/Status_Reference.md | 6 ++++++ klippy/gcode.py | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/docs/Status_Reference.md b/docs/Status_Reference.md index 5477049a100e..b64108ae25c0 100644 --- a/docs/Status_Reference.md +++ b/docs/Status_Reference.md @@ -168,6 +168,12 @@ The following information is available in the module. These settings may differ from the config file if a `SET_RETRACTION` command alters them. +## gcode + +The following information is available in the `gcode` object: +- `commands`: Returns a list of all currently available commands. For each + command, if a help string is defined it will also be provided. + ## gcode_button The following information is available in diff --git a/klippy/gcode.py b/klippy/gcode.py index 07e312f98f3d..7d980585ff25 100644 --- a/klippy/gcode.py +++ b/klippy/gcode.py @@ -104,6 +104,7 @@ def __init__(self, printer): self.ready_gcode_handlers = {} self.mux_commands = {} self.gcode_help = {} + self.status_commands = {} # Register commands needed before config file is loaded handlers = ['M110', 'M112', 'M115', 'RESTART', 'FIRMWARE_RESTART', 'ECHO', 'STATUS', 'HELP'] @@ -126,6 +127,7 @@ def register_command(self, cmd, func, when_not_ready=False, desc=None): del self.ready_gcode_handlers[cmd] if cmd in self.base_gcode_handlers: del self.base_gcode_handlers[cmd] + self._build_status_commands() return old_cmd if cmd in self.ready_gcode_handlers: raise self.printer.config_error( @@ -138,6 +140,7 @@ def register_command(self, cmd, func, when_not_ready=False, desc=None): self.base_gcode_handlers[cmd] = func if desc is not None: self.gcode_help[cmd] = desc + self._build_status_commands() def register_mux_command(self, cmd, key, value, func, desc=None): prev = self.mux_commands.get(cmd) if prev is None: @@ -156,6 +159,14 @@ def register_mux_command(self, cmd, key, value, func, desc=None): prev_values[value] = func def get_command_help(self): return dict(self.gcode_help) + def get_status(self, eventtime): + return {'commands': self.status_commands} + def _build_status_commands(self): + commands = {cmd: {} for cmd in self.gcode_handlers} + for cmd in self.gcode_help: + if cmd in commands: + commands[cmd]['help'] = self.gcode_help[cmd] + self.status_commands = commands def register_output_handler(self, cb): self.output_callbacks.append(cb) def _handle_shutdown(self): @@ -163,12 +174,14 @@ def _handle_shutdown(self): return self.is_printer_ready = False self.gcode_handlers = self.base_gcode_handlers + self._build_status_commands() self._respond_state("Shutdown") def _handle_disconnect(self): self._respond_state("Disconnect") def _handle_ready(self): self.is_printer_ready = True self.gcode_handlers = self.ready_gcode_handlers + self._build_status_commands() self._respond_state("Ready") # Parse input into commands args_r = re.compile('([A-Z_]+|[A-Z*/])')