diff --git a/Makefile b/Makefile index 106157265478..735d18633272 100644 --- a/Makefile +++ b/Makefile @@ -29,10 +29,11 @@ dirs-y = src cc-option=$(shell if test -z "`$(1) $(2) -S -o /dev/null -xc /dev/null 2>&1`" \ ; then echo "$(2)"; else echo "$(3)"; fi ;) -CFLAGS := -I$(OUT) -Isrc -I$(OUT)board-generic/ -std=gnu11 -O2 -MD \ - -Wall -Wold-style-definition $(call cc-option,$(CC),-Wtype-limits,) \ +CFLAGS := -iquote $(OUT) -iquote src -iquote $(OUT)board-generic/ \ + -std=gnu11 -O2 -MD -Wall \ + -Wold-style-definition $(call cc-option,$(CC),-Wtype-limits,) \ -ffunction-sections -fdata-sections -fno-delete-null-pointer-checks -CFLAGS += -flto -fwhole-program -fno-use-linker-plugin -ggdb3 +CFLAGS += -flto=auto -fwhole-program -fno-use-linker-plugin -ggdb3 OBJS_klipper.elf = $(patsubst %.c, $(OUT)src/%.o,$(src-y)) OBJS_klipper.elf += $(OUT)compile_time_request.o diff --git a/config/generic-mini-rambo.cfg b/config/generic-mini-rambo.cfg index 61e2ac847d01..1a616cf80418 100644 --- a/config/generic-mini-rambo.cfg +++ b/config/generic-mini-rambo.cfg @@ -84,7 +84,7 @@ pwm: True scale: 2.0 cycle_time: .000030 hardware_pwm: True -static_value: 1.3 +value: 1.3 [output_pin stepper_z_current] pin: PL4 @@ -92,7 +92,7 @@ pwm: True scale: 2.0 cycle_time: .000030 hardware_pwm: True -static_value: 1.3 +value: 1.3 [output_pin stepper_e_current] pin: PL5 @@ -100,7 +100,7 @@ pwm: True scale: 2.0 cycle_time: .000030 hardware_pwm: True -static_value: 1.25 +value: 1.25 [static_digital_output stepper_config] pins: diff --git a/config/generic-ultimaker-ultimainboard-v2.cfg b/config/generic-ultimaker-ultimainboard-v2.cfg index 9a4d4e6da09a..b1ce3fa55882 100644 --- a/config/generic-ultimaker-ultimainboard-v2.cfg +++ b/config/generic-ultimaker-ultimainboard-v2.cfg @@ -97,7 +97,7 @@ max_z_accel: 30 [output_pin case_light] pin: PH5 -static_value: 1.0 +value: 1.0 # Motor current settings. [output_pin stepper_xy_current] @@ -107,7 +107,7 @@ scale: 2.000 # Max power setting. cycle_time: .000030 hardware_pwm: True -static_value: 1.200 +value: 1.200 # Power adjustment setting. [output_pin stepper_z_current] @@ -116,7 +116,7 @@ pwm: True scale: 2.000 cycle_time: .000030 hardware_pwm: True -static_value: 1.200 +value: 1.200 [output_pin stepper_e_current] pin: PL3 @@ -124,4 +124,4 @@ pwm: True scale: 2.000 cycle_time: .000030 hardware_pwm: True -static_value: 1.250 +value: 1.250 diff --git a/config/printer-adimlab-2018.cfg b/config/printer-adimlab-2018.cfg index 2f02173dde1b..d810e9d7e6fd 100644 --- a/config/printer-adimlab-2018.cfg +++ b/config/printer-adimlab-2018.cfg @@ -89,7 +89,7 @@ pwm: True scale: 2.0 cycle_time: .000030 hardware_pwm: True -static_value: 1.3 +value: 1.3 [output_pin stepper_z_current] pin: PL4 @@ -97,7 +97,7 @@ pwm: True scale: 2.0 cycle_time: .000030 hardware_pwm: True -static_value: 1.3 +value: 1.3 [output_pin stepper_e_current] pin: PL3 @@ -105,7 +105,7 @@ pwm: True scale: 2.0 cycle_time: .000030 hardware_pwm: True -static_value: 1.25 +value: 1.25 [display] lcd_type: st7920 diff --git a/config/printer-anycubic-4maxpro-2.0-2021.cfg b/config/printer-anycubic-4maxpro-2.0-2021.cfg index e0c83b6aeaa3..203e3c8f2f37 100644 --- a/config/printer-anycubic-4maxpro-2.0-2021.cfg +++ b/config/printer-anycubic-4maxpro-2.0-2021.cfg @@ -126,7 +126,6 @@ restart_method: arduino kinematics: cartesian max_velocity: 150 max_accel: 3000 -max_accel_to_decel: 1500 max_z_velocity: 7 max_z_accel: 50 square_corner_velocity: 5 diff --git a/config/printer-anycubic-kobra-go-2022.cfg b/config/printer-anycubic-kobra-go-2022.cfg index d4fc409f0cfd..bb8a9267f68b 100644 --- a/config/printer-anycubic-kobra-go-2022.cfg +++ b/config/printer-anycubic-kobra-go-2022.cfg @@ -118,7 +118,7 @@ cycle_time: 0.00005 #20kHz [output_pin enable_pin] pin: PB6 -static_value: 1 +value: 1 #This pin enables the bed, hotend, extruder fan, part fan. [mcu] diff --git a/config/printer-anycubic-kobra-plus-2022.cfg b/config/printer-anycubic-kobra-plus-2022.cfg index 96099797042c..3f71caab81b5 100644 --- a/config/printer-anycubic-kobra-plus-2022.cfg +++ b/config/printer-anycubic-kobra-plus-2022.cfg @@ -14,6 +14,7 @@ # To build the firmware, use the following configuration: # - Micro-controller: Huada Semiconductor HC32F460 # - Communication interface: Serial (PA3 & PA2) - Anycube +# - Clock Speed: 200 MHz # # Installation: # 1. Rename the klipper bin to `firmware.bin` and copy it to an SD Card. @@ -144,10 +145,9 @@ max_temp: 120 pause_on_runout: True switch_pin: !PC13 -[heater_fan controller_fan] +[controller_fan controller_fan] pin: PA14 heater: heater_bed -heater_temp: 45.0 [heater_fan hotend_fan] pin: PA13 diff --git a/config/printer-artillery-genius-pro-2022.cfg b/config/printer-artillery-genius-pro-2022.cfg new file mode 100644 index 000000000000..aec7841c5e55 --- /dev/null +++ b/config/printer-artillery-genius-pro-2022.cfg @@ -0,0 +1,126 @@ +# This file contains pin mappings for the Artillery Genius Pro (2022) +# with a Artillery_Ruby-v1.2 board. To use this config, during "make menuconfig" +# select the STM32F401 with "No bootloader" and USB (on PA11/PA12) +# communication. + +# To flash this firmware, set the physical bridge between +3.3V and Boot0 PIN +# on Artillery_Ruby mainboard. Then run the command: +# make flash FLASH_DEVICE=/dev/serial/by-id/usb-Klipper_stm32f401xc_*-if00 + +# See docs/Config_Reference.md for a description of parameters. + +[extruder] +max_extrude_only_distance: 700.0 +step_pin: PA7 +dir_pin: PA6 +enable_pin: !PC4 +microsteps: 16 +rotation_distance: 7.1910 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PC9 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PC0 +min_temp: 0 +max_temp: 250 +control: pid +pid_Kp: 23.223 +pid_Ki: 1.518 +pid_Kd: 88.826 + +[stepper_x] +step_pin: !PB14 +dir_pin: PB13 +enable_pin: !PB15 +microsteps: 16 +rotation_distance: 40 +endstop_pin: !PA2 +position_endstop: 0 +position_max: 220 +homing_speed: 60 + +[stepper_y] +step_pin: PB10 +dir_pin: PB2 +enable_pin: !PB12 +microsteps: 16 +rotation_distance: 40 +endstop_pin: !PA1 +position_endstop: 0 +position_max: 220 +homing_speed: 60 + +[stepper_z] +step_pin: PB0 +dir_pin: !PC5 +enable_pin: !PB1 +microsteps: 16 +rotation_distance: 8 +endstop_pin: probe:z_virtual_endstop +position_max: 250 +position_min: -5 + +[heater_bed] +heater_pin: PA8 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PC1 +min_temp: 0 +max_temp: 130 +control: pid +pid_Kp: 23.223 +pid_Ki: 1.518 +pid_Kd: 88.826 + +[bed_screws] +screw1: 38,45 +screw2: 180,45 +screw3: 180,180 +screw4: 38,180 + +[fan] +pin: PC8 +off_below: 0.1 + +[heater_fan hotend_fan] +pin: PC7 +heater: extruder +heater_temp: 50.0 + +[controller_fan stepper_fan] +pin: PC6 +idle_timeout: 300 + +[mcu] +serial: /dev/serial/by-id/usb-Klipper_stm32f401xc_ + +[printer] +kinematics: cartesian +max_velocity: 500 +max_accel: 4000 +max_z_velocity: 50 +square_corner_velocity: 5.0 +max_z_accel: 100 + +[bltouch] +sensor_pin: PC2 +control_pin: PC3 +x_offset:27.25 +y_offset:-12.8 +z_offset: 0.25 +speed:10 +samples:1 +samples_result:average + +[bed_mesh] +speed: 800 +mesh_min: 30, 20 +mesh_max: 210, 200 +probe_count: 5,5 +algorithm: bicubic +move_check_distance: 3.0 + +[safe_z_home] +home_xy_position: 110,110 +speed: 100 +z_hop: 10 +z_hop_speed: 5 diff --git a/config/printer-artillery-sidewinder-x3-plus-2024.cfg b/config/printer-artillery-sidewinder-x3-plus-2024.cfg new file mode 100644 index 000000000000..eea1a980a8c0 --- /dev/null +++ b/config/printer-artillery-sidewinder-x3-plus-2024.cfg @@ -0,0 +1,188 @@ +# For the Artillery Sidewinder X3 Pro/Plus that came factory installed with V1.29 firmware, follow these steps. +# - Compile with the processor model STM32F401. +# - Select the 48KiB bootloader, +# - Select USB PA11/PA12 for USB communication interface. +# - Select USART2 PA3/PA2 for UART communication via the Wi-Fi Tx/Rx pins +# To set 48KiB bootloader, you need to make a change to make menuconfig Kconfig file +# Here is a link to a how-to video: https://youtu.be/dpc76zN7Dh0 +# Rename klipper.bin to yuntu.bin +# Copy the file out/yuntu.bin to an SD card and then restart the printer with that SD card +# +# For models that did not come with V1.29 installed +# - Compile with the processor model STM32F401. +# - Select the NO BOOTLOADER +# - Select USB PA11/PA12 for USB communication interface. +# - Select USART2 PA3/PA2 for UART communication via the Wi-Fi Tx/Rx pins +# - quit, save, make +# - Connect your printer to a computer running Pronterface, Octoprint, Repetier, BedLeveler5000 (anything with Console capability) +# - Power on the machine and send M997 through console into Marlin, this will put the board into "DFU" mode +# - DO NOT TURN OFF THE PRINTER +# - Connect your Linux/Klipper device to the USB port +# - Run lsusb and verify that the STM32 DFU device is visible (Bus 001 Device 006: ID 0483:df11 STMicroelectronics STM Device in DFU Mode) +# - Run sudo make flash 0483:df11 +# - Run lsusb again and there should be two devices: +# Bus 001 Device 007: ID 1d50:614e OpenMoko, Inc. stm32f401xc +# Bus 001 Device 003: ID 0cf3:e010 Qualcomm Atheros Communications stm32f401xc +# See docs/Config_Reference.md for a description of parameters. + +[mcu] +serial: /dev/ttyACM0 +restart_method: command + +[printer] +kinematics: cartesian +max_velocity: 300 +max_accel: 3000 +max_z_velocity: 15 +max_z_accel: 100 +square_corner_velocity: 5 + +[led LED_Light] +white_pin: PC2 +initial_white: 1.0 + +[neopixel hotend_neopixel] +pin: PD2 +color_order: GRB +initial_RED: 1.0 +initial_GREEN: 1.0 +initial_BLUE: 1.0 + +[stepper_x] +step_pin: PA8 +dir_pin: PC9 +enable_pin: !PA15 +microsteps: 16 +rotation_distance: 40 +endstop_pin: !PB9 +position_min: 0 +position_endstop: 0 +position_max: 315 +homing_speed: 50 + +[stepper_y] +step_pin: PC7 +dir_pin: !PC6 +enable_pin: !PC8 +microsteps: 16 +rotation_distance: 40 +endstop_pin: !PB8 +position_endstop: 0 +position_max: 315 +homing_speed: 50 + +[stepper_z] +step_pin: PB10 +dir_pin: !PA4 +enable_pin: !PC4 +rotation_distance: 8 +microsteps: 16 +position_min: -1 +position_max: 400 +endstop_pin: probe:z_virtual_endstop # Use Z- as endstop +#homing_speed: 10.0 + +[extruder] +max_extrude_only_distance: 100.0 +step_pin: PC11 +dir_pin: !PC10 +enable_pin: !PC12 +microsteps: 64 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PA6 +sensor_type: EPCOS 100K B57560G104F #Generic 3950 +sensor_pin: PC5 +min_extrude_temp: 170 +min_temp: 0 +max_temp: 300 +# Calibrate E-Steps https://www.klipper3d.org/Rotation_Distance.html#calibrating-rotation_distance-on-extruders +rotation_distance: 17.75 +# Calibrate PID: https://www.klipper3d.org/Config_checks.html#calibrate-pid-settings +# - Example: PID_CALIBRATE HEATER=extruder TARGET=200 +control: pid +pid_kp: 30.356 +pid_ki: 1.857 +pid_kd: 124.081 +# Calibrate PA: https://www.klipper3d.org/Pressure_Advance.html + +[heater_bed] +heater_pin: PA7 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PC0 +max_temp: 100 +min_temp: 0 +# Calibrate PID: https://www.klipper3d.org/Config_checks.html#calibrate-pid-settings +# - Example: PID_CALIBRATE HEATER=heater_bed TARGET=60 +control: pid +pid_kp: 64.230 +pid_ki: 0.723 +pid_kd: 1425.905 + +[heater_fan hotend_fan] +pin: PB1 +heater: extruder +heater_temp: 50.0 + +[fan] +pin: PB0 + +[temperature_fan Artillery_MCU] +sensor_type: temperature_mcu +pin: PA5 +max_temp: 60.0 +target_temp: 40.0 +min_temp: 0 +shutdown_speed: 0.0 +kick_start_time: 0.5 +off_below: 0.19 +max_speed: 1.0 +min_speed: 0.0 +control: watermark + +[filament_switch_sensor filament_sensor] +pause_on_runout: true +switch_pin: PC1 + +[probe] +pin: PC14 +x_offset:45.2 +y_offset:11.6 +speed:5 +lift_speed:15 +z_offset: 2.350 + +[safe_z_home] +home_xy_position: 110, 145 # X, Y coordinate (e.g. 100, 100) where the Z homing should be +speed: 300.0 +z_hop: 10 +z_hop_speed: 15.0 + +[bed_mesh] +speed: 300 +horizontal_move_z: 6 +mesh_min: 46,15 +mesh_max: 300,300 +probe_count: 10, 10 +fade_start: 1.0 +fade_end: 0.0 +algorithm: bicubic + +[screws_tilt_adjust] +screw1: 120, 153 +screw1_name: center reference +screw2: 7, 45 +screw2_name: front left +screw3: 210, 45 +screw3_name: front right +screw4: 227, 145 +screw4_name: right center +screw5: 210, 245 +screw5_name: rear right +screw6: 7, 245 +screw6_name: rear left +screw7: 7, 145 +screw7_name: left center +horizontal_move_z: 8 +speed: 300 +screw_thread: CW-M4 diff --git a/config/printer-creality-cr30-2021.cfg b/config/printer-creality-cr30-2021.cfg index de946920032b..1edc75313660 100644 --- a/config/printer-creality-cr30-2021.cfg +++ b/config/printer-creality-cr30-2021.cfg @@ -98,7 +98,6 @@ max_temp: 100 [output_pin led] pin: PC14 -static_value: 0 # Neopixel LED support # [neopixel led_neopixel] diff --git a/config/printer-creality-cr6se-2020.cfg b/config/printer-creality-cr6se-2020.cfg index 11d827eaf56f..2929a9ac4c30 100644 --- a/config/printer-creality-cr6se-2020.cfg +++ b/config/printer-creality-cr6se-2020.cfg @@ -98,6 +98,10 @@ z_offset: 0.0 speed: 2.0 samples: 5 +[safe_z_home] +home_xy_position: 117, 117 +z_hop: 10 + [filament_switch_sensor filament_sensor] pause_on_runout: true switch_pin: ^!PA7 diff --git a/config/printer-creality-cr6se-2021.cfg b/config/printer-creality-cr6se-2021.cfg index 12c17120f125..932a9263d20c 100644 --- a/config/printer-creality-cr6se-2021.cfg +++ b/config/printer-creality-cr6se-2021.cfg @@ -98,6 +98,10 @@ z_offset: 0.0 speed: 2.0 samples: 5 +[safe_z_home] +home_xy_position: 117, 117 +z_hop: 10 + [filament_switch_sensor filament_sensor] pause_on_runout: true switch_pin: ^!PA7 diff --git a/config/printer-creality-ender5-s1-2023.cfg b/config/printer-creality-ender5-s1-2023.cfg new file mode 100644 index 000000000000..68a89fa5e3d3 --- /dev/null +++ b/config/printer-creality-ender5-s1-2023.cfg @@ -0,0 +1,170 @@ +# Creality Ender 5 S1 (HW version: CR4NS200141C13) +# +# printer_size: 220x220x280 +# To use this config, during "make menuconfig" select the STM32F401 +# with a "64KiB bootloader" and serial (on USART1 PA10/PA9) +# communication. +# +# Flash this firmware by creating a directory named "STM32F4_UPDATE" +# on an SD card, copying the "out/klipper.bin" to it and then turn +# on the printer with the card inserted. The firmware filename must +# end in ".bin" and must not match the last filename that was flashed. +# +# See docs/Config_Reference.md for a description of parameters. + +[stepper_x] +step_pin: PC2 +dir_pin: !PB9 +enable_pin: !PC3 +rotation_distance: 40 +microsteps: 16 +endstop_pin: !PA5 +position_endstop: 220 +position_max: 222 +homing_speed: 80 + +[stepper_y] +step_pin: PB8 +dir_pin: !PB7 +enable_pin: !PC3 +rotation_distance: 40 +microsteps: 16 +endstop_pin: !PA6 +position_endstop: 220 +position_max: 220 +homing_speed: 80 + +[stepper_z] +step_pin: PB6 +dir_pin: PB5 +enable_pin: !PC3 +rotation_distance: 8 +microsteps: 16 +endstop_pin: probe:z_virtual_endstop +position_max: 280 +homing_speed: 20 +second_homing_speed: 1 +homing_retract_dist: 2.0 + +[extruder] +step_pin: PB4 +dir_pin: PB3 +enable_pin: !PC3 +rotation_distance: 7.5 +microsteps: 16 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PA1 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PC5 +control: pid # tuned for stock hardware with 210 degree Celsius target +pid_kp: 20.749 +pid_ki: 1.064 +pid_kd: 101.153 +min_temp: 0 +max_temp: 305 + +[heater_bed] +heater_pin: PA7 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PC4 +control: pid # tuned for stock hardware with 60 degree Celsius target +pid_kp: 66.566 +pid_ki: 0.958 +pid_kd: 1155.761 +min_temp: 0 +max_temp: 110 + +# Part cooling fan +[fan] +pin: PA0 +kick_start_time: 0.5 + +# Hotend fan +# set fan runnig when extruder temperature is over 60 +[heater_fan heatbreak_fan] +pin: PC0 +heater:extruder +heater_temp: 60 +fan_speed: 0.8 + +[filament_switch_sensor filament_sensor] +pause_on_runout: true +switch_pin: ^!PC15 + +# Stock CR Touch bed sensor +[bltouch] +sensor_pin: ^PC14 +control_pin: PC13 +x_offset: -13 +y_offset: 27 +z_offset: 2.0 +speed: 10 +stow_on_each_sample: true # Occasional bed crashes when false +samples: 4 +sample_retract_dist: 2 +samples_result: average +probe_with_touch_mode: true + +[bed_mesh] +speed: 150 +mesh_min: 3,28 # need to handle head distance with bl_touch +mesh_max: 205,218 +mesh_pps: 3 +probe_count: 4,4 +fade_start: 1 +fade_end: 10 +fade_target: 0 + +[mcu] +serial: /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0 +restart_method: command + +[safe_z_home] +home_xy_position: 123,83 +speed: 200 +z_hop: 10 +z_hop_speed: 10 + +# Many Ender 5 S1 printers appear to suffer from a slight twist +# in the X axis. This twist can be measured, and compensated for +# using the AXIS_TWIST_COMPENSATION_CALIBRATE G-Code command. See +# https://www.klipper3d.org/Axis_Twist_Compensation.html for more +# information. This section provides the setup for this optional +# calibration step. +[axis_twist_compensation] +calibrate_start_x: 3 +calibrate_end_x: 207 +calibrate_y: 110 + +# Probe locations for assisted bed screw adjustment. +[screws_tilt_adjust] +screw1: 38,6 +screw1_name: Front Left Screw +screw2: 215,6 +screw2_name: Front Right Screw +screw3: 215,175 +screw3_name: Rear Right Screw +screw4: 38,175 +screw4_name: Rear Left Screw +horizontal_move_z: 5 +speed: 100 +screw_thread: CW-M4 + +[bed_screws] +screw1: 25,25 +screw1_name: Front Left Screw +screw2: 195,25 +screw2_name: Front Right Screw +screw3: 195,195 +screw3_name: Rear Right Screw +screw4: 25,195 +screw4_name: Rear Left Screw + +[printer] +kinematics: cartesian +max_velocity: 300 +max_accel: 5000 +max_z_velocity: 5 +max_z_accel: 100 +square_corner_velocity: 5.0 diff --git a/config/printer-geeetech-301-2019.cfg b/config/printer-geeetech-301-2019.cfg index 5b3a9a4ab25e..6889fae5ae08 100644 --- a/config/printer-geeetech-301-2019.cfg +++ b/config/printer-geeetech-301-2019.cfg @@ -71,25 +71,21 @@ pid_Kp: 39 pid_Ki: 2 pid_Kd: 210 -[extruder1] +[extruder_stepper e1] +extruder: step_pin: PA0 dir_pin: !PB6 enable_pin: !PA1 microsteps: 16 rotation_distance: 32 -nozzle_diameter: 0.4 -filament_diameter: 1.75 -shared_heater: extruder -[extruder2] +[extruder_stepper e2] +extruder: step_pin: PB2 dir_pin: !PB11 enable_pin: !PC4 microsteps: 16 rotation_distance: 32 -nozzle_diameter: 0.4 -filament_diameter: 1.75 -shared_heater: extruder [heater_bed] heater_pin: PB1 diff --git a/config/printer-lulzbot-mini1-2016.cfg b/config/printer-lulzbot-mini1-2016.cfg index 9be60cbddaaf..52b8061eeb4a 100644 --- a/config/printer-lulzbot-mini1-2016.cfg +++ b/config/printer-lulzbot-mini1-2016.cfg @@ -125,7 +125,7 @@ pwm: True scale: 2.0 cycle_time: .000030 hardware_pwm: True -static_value: 1.300 +value: 1.300 [output_pin stepper_z_current] pin: PL4 @@ -133,7 +133,7 @@ pwm: True scale: 2.0 cycle_time: .000030 hardware_pwm: True -static_value: 1.630 +value: 1.630 [output_pin stepper_e_current] pin: PL5 @@ -141,7 +141,7 @@ pwm: True scale: 2.0 cycle_time: .000030 hardware_pwm: True -static_value: 1.250 +value: 1.250 [static_digital_output stepper_config] # Microstepping pins diff --git a/config/printer-modix-big60-2020.cfg b/config/printer-modix-big60-2020.cfg index 0224851ea4b9..2fc311cf19dc 100644 --- a/config/printer-modix-big60-2020.cfg +++ b/config/printer-modix-big60-2020.cfg @@ -199,7 +199,6 @@ algorithm: bicubic bicubic_tension: 0.15 fade_start: 0.5 fade_end: 2.5 -relative_reference_index: 60 [bed_screws] screw1: 0,0 diff --git a/config/printer-sovol-sv05-2022.cfg b/config/printer-sovol-sv05-2022.cfg index dafc6f3cf322..0ec03f84a186 100644 --- a/config/printer-sovol-sv05-2022.cfg +++ b/config/printer-sovol-sv05-2022.cfg @@ -19,7 +19,7 @@ restart_method: command kinematics: cartesian max_velocity: 300 max_accel: 1000 -max_accel_to_decel: 1000 +minimum_cruise_ratio: 0.0 max_z_velocity: 5 max_z_accel: 100 diff --git a/config/printer-tronxy-crux1-2022.cfg b/config/printer-tronxy-crux1-2022.cfg new file mode 100644 index 000000000000..e3254d85b322 --- /dev/null +++ b/config/printer-tronxy-crux1-2022.cfg @@ -0,0 +1,138 @@ +# Klipper configuration for the TronXY Crux1 printer +# CXY-V10.1-220921 mainboard, GD32F4XX or STM32F446 MCU +# +# ======================= +# BUILD AND FLASH OPTIONS +# ======================= +# +# MCU-architecture: STMicroelectronics +# Processor model: STM32F446 +# Bootloader offset: 64KiB +# Comms interface: Serial on USART1 PA10/PA9 +# +# Build the firmware with these options +# Rename the resulting klipper.bin into fmw_tronxy.bin +# Put the file into a directory called "update" on a FAT32 formatted SD card. +# Turn off the printer, plug in the SD card and turn the printer back on +# Flashing will start automatically and progress will be indicated on the LCD +# Once the flashing is completed the display will get stuck on the white Tronxy logo bootscreen +# The LCD display will NOT work anymore after flashing Klipper onto this printer + +[mcu] +serial: /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0 +restart_method: command + +[printer] +kinematics: cartesian +max_velocity: 250 +max_accel: 1500 +square_corner_velocity: 5 +max_z_velocity: 15 +max_z_accel: 100 + +[controller_fan drivers_fan] +pin: PD7 + +[pwm_cycle_time BEEPER_pin] +pin: PA8 +value: 0 +shutdown_value: 0 +cycle_time: 0.001 + +[safe_z_home] +home_xy_position: 0, 0 +speed: 100 +z_hop: 10 +z_hop_speed: 5 + +[stepper_x] +step_pin: PE5 +dir_pin: PF1 +enable_pin: !PF0 +microsteps: 16 +rotation_distance: 20 +endstop_pin: ^!PC15 +position_endstop: -1 +position_min: -1 +position_max: 180 +homing_speed: 100 +homing_retract_dist: 10 +second_homing_speed: 25 + +[stepper_y] +step_pin: PF9 +dir_pin: !PF3 +enable_pin: !PF5 +microsteps: 16 +rotation_distance: 20 +endstop_pin: ^!PC14 +position_endstop: -3 +position_min: -3 +position_max: 180 +homing_retract_dist: 10 +homing_speed: 100 +second_homing_speed: 25 + +[stepper_z] +step_pin: PA6 +dir_pin: !PF15 +enable_pin: !PA5 +microsteps: 16 +rotation_distance: 4 +endstop_pin: ^!PC13 +position_endstop: 0 +position_max: 180 +position_min: 0 + +[extruder] +step_pin: PB1 +dir_pin: PF13 +enable_pin: !PF14 +microsteps: 16 +rotation_distance: 16.75 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PG7 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PC3 +control: pid +pid_kp: 22.2 +pid_ki: 1.08 +pid_kd: 114.00 +min_temp: 0 +max_temp: 250 +min_extrude_temp: 170 +max_extrude_only_distance: 450 + +[heater_fan hotend_fan] +heater: extruder +heater_temp: 50.0 +pin: PG9 + +[fan] +pin: PG0 + +[filament_switch_sensor filament_sensor] +pause_on_runout: True +switch_pin: ^!PE6 + +[heater_bed] +heater_pin: PE2 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PC2 +min_temp: 0 +max_temp: 130 +control: pid +pid_kp: 10.00 +pid_ki: 0.023 +pid_kd: 305.4 + +[bed_screws] +screw1: 17.5, 11 +screw1_name: front_left +screw2: 162.5, 11 +screw2_name: front_right +screw3: 162.5, 162.5 +screw3_name: back_right +screw4: 17.5, 162.5 +screw4_name: back_left diff --git a/config/printer-wanhao-duplicator-6-2016.cfg b/config/printer-wanhao-duplicator-6-2016.cfg index b1d35faecb82..de8a3de87d2a 100644 --- a/config/printer-wanhao-duplicator-6-2016.cfg +++ b/config/printer-wanhao-duplicator-6-2016.cfg @@ -86,7 +86,7 @@ pwm: True scale: 2.782 cycle_time: .000030 hardware_pwm: True -static_value: 1.2 +value: 1.2 [output_pin stepper_z_current] pin: PL4 @@ -94,7 +94,7 @@ pwm: True scale: 2.782 cycle_time: .000030 hardware_pwm: True -static_value: 1.2 +value: 1.2 [output_pin stepper_e_current] pin: PL3 @@ -102,7 +102,7 @@ pwm: True scale: 2.782 cycle_time: .000030 hardware_pwm: True -static_value: 1.0 +value: 1.0 [display] lcd_type: ssd1306 diff --git a/config/sample-macros.cfg b/config/sample-macros.cfg index 5132e1c99c37..f5649d61a396 100644 --- a/config/sample-macros.cfg +++ b/config/sample-macros.cfg @@ -61,12 +61,10 @@ gcode: # P is the tone duration, S the tone frequency. # The frequency won't be pitch perfect. -[output_pin BEEPER_pin] +[pwm_cycle_time BEEPER_pin] pin: ar37 # Beeper pin. This parameter must be provided. # ar37 is the default RAMPS/MKS pin. -pwm: True -# A piezo beeper needs a PWM signal, a DC buzzer doesn't. value: 0 # Silent at power on, set to 1 if active low. shutdown_value: 0 diff --git a/docs/API_Server.md b/docs/API_Server.md index 4af1812a3a1d..cc0922e3ca81 100644 --- a/docs/API_Server.md +++ b/docs/API_Server.md @@ -401,3 +401,130 @@ might return: As with the "gcode/script" endpoint, this endpoint only completes after any pending G-Code commands complete. + +### bed_mesh/dump_mesh + +Dumps the configuration and state for the current mesh and all +saved profiles. + +For example: +`{"id": 123, "method": "bed_mesh/dump_mesh"}` + +might return: + +``` +{ + "current_mesh": { + "name": "eddy-scan-test", + "probed_matrix": [...], + "mesh_matrix": [...], + "mesh_params": { + "x_count": 9, + "y_count": 9, + "mesh_x_pps": 2, + "mesh_y_pps": 2, + "algo": "bicubic", + "tension": 0.5, + "min_x": 20, + "max_x": 330, + "min_y": 30, + "max_y": 320 + } + }, + "profiles": { + "default": { + "points": [...], + "mesh_params": { + "min_x": 20, + "max_x": 330, + "min_y": 30, + "max_y": 320, + "x_count": 9, + "y_count": 9, + "mesh_x_pps": 2, + "mesh_y_pps": 2, + "algo": "bicubic", + "tension": 0.5 + } + }, + "eddy-scan-test": { + "points": [...], + "mesh_params": { + "x_count": 9, + "y_count": 9, + "mesh_x_pps": 2, + "mesh_y_pps": 2, + "algo": "bicubic", + "tension": 0.5, + "min_x": 20, + "max_x": 330, + "min_y": 30, + "max_y": 320 + } + }, + "eddy-rapid-test": { + "points": [...], + "mesh_params": { + "x_count": 9, + "y_count": 9, + "mesh_x_pps": 2, + "mesh_y_pps": 2, + "algo": "bicubic", + "tension": 0.5, + "min_x": 20, + "max_x": 330, + "min_y": 30, + "max_y": 320 + } + } + }, + "calibration": { + "points": [...], + "config": { + "x_count": 9, + "y_count": 9, + "mesh_x_pps": 2, + "mesh_y_pps": 2, + "algo": "bicubic", + "tension": 0.5, + "mesh_min": [ + 20, + 30 + ], + "mesh_max": [ + 330, + 320 + ], + "origin": null, + "radius": null + }, + "probe_path": [...], + "rapid_path": [...] + }, + "probe_offsets": [ + 0, + 25, + 0.5 + ], + "axis_minimum": [ + 0, + 0, + -5, + 0 + ], + "axis_maximum": [ + 351, + 358, + 330, + 0 + ] +} +``` + +The `dump_mesh` endpoint takes one optional parameter, `mesh_args`. +This parameter must be an object, where the keys and values are +parameters available to [BED_MESH_CALIBRATE](#bed_mesh_calibrate). +This will update the mesh configuration and probe points using the +supplied parameters prior to returning the result. It is recommended +to omit mesh parameters unless it is desired to visualize the probe points +and/or travel path before performing `BED_MESH_CALIBRATE`. diff --git a/docs/Beaglebone.md b/docs/Beaglebone.md index 67fc96572cb4..700da90e032a 100644 --- a/docs/Beaglebone.md +++ b/docs/Beaglebone.md @@ -6,23 +6,64 @@ PRU. ## Building an OS image Start by installing the -[Debian 9.9 2019-08-03 4GB SD IoT](https://beagleboard.org/latest-images) +[Debian 11.7 2023-09-02 4GB microSD IoT](https://beagleboard.org/latest-images) image. One may run the image from either a micro-SD card or from builtin eMMC. If using the eMMC, install it to eMMC now by following the instructions from the above link. Then ssh into the Beaglebone machine (`ssh debian@beaglebone` -- -password is `temppwd`) and install Klipper by running the following +password is `temppwd`). + +Before start installing Klipper you need to free-up additional space. +there are 3 options to do that: +1. remove some BeagleBone "Demo" resources +2. if you did boot from SD-Card, and it's bigger than 4Gb - you can expand +current filesystem to take whole card space +3. do option #1 and #2 together. + +To remove some BeagleBone "Demo" resources execute these commands +``` +sudo apt remove bb-node-red-installer +sudo apt remove bb-code-server +``` + +To expand filesystem to full size of your SD-Card execute this command, reboot is not required. +``` +sudo growpart /dev/mmcblk0 1 +sudo resize2fs /dev/mmcblk0p1 +``` + + +Install Klipper by running the following commands: ``` -git clone https://github.com/Klipper3d/klipper +git clone https://github.com/Klipper3d/klipper.git ./klipper/scripts/install-beaglebone.sh ``` -## Install Octoprint +After installing Klipper you need to decide what kind of deployment do you need, +but take a note that BeagleBone is 3.3v based hardware and in most cases you can't +directly connect pins to 5v or 12v based hardware without conversion boards. + +As Klipper have multimodule architecture on BeagleBone you can achieve many different use cases, +but general ones are following: + +Use case 1: Use BeagleBone only as a host system to run Klipper and additional software +like OctoPrint/Fluidd + Moonraker/... and this configuration will be driving +external micro-controllers via serial/usb/canbus connections. + +Use case 2: Use BeagleBone with extension board (cape) like CRAMPS board. +in this configuration BeagleBone will host Klipper + additional software, and +it will drive extension board with BeagleBone PRU cores (2 additional cores 200Mh, 32Bit). + +Use case 3: It's same as "Use case 1" but additionally you want to drive +BeagleBone GPIOs with high speed by utilizing PRU cores to offload main CPU. + + +## Installing Octoprint -One may then install Octoprint: +One may then install Octoprint or fully skip this section if desired other software: ``` git clone https://github.com/foosel/OctoPrint.git cd OctoPrint/ @@ -51,25 +92,89 @@ Then start the Octoprint service: ``` sudo systemctl start octoprint ``` - -Make sure the OctoPrint web server is accessible - it should be at: +Wait 1-2 minutes and make sure the OctoPrint web server is accessible - it should be at: [http://beaglebone:5000/](http://beaglebone:5000/) -## Building the micro-controller code -To compile the Klipper micro-controller code, start by configuring it -for the "Beaglebone PRU": +## Building the BeagleBone PRU micro-controller code (PRU firmware) +This section is required for "Use case 2" and "Use case 3" mentioned above, +you should skip it for "Use case 1". + +Check that required devices are present + +``` +sudo beagle-version +``` +You should check that output contains successful "remoteproc" drivers loading and presence of PRU cores, +in Kernel 5.10 they should be "remoteproc1" and "remoteproc2" (4a334000.pru, 4a338000.pru) +Also check that many GPIOs are loaded they will look like "Allocated GPIO id=0 name='P8_03'" +Usually everything is fine and no hardware configuration is required. +If something is missing - try to play with "uboot overlays" options or with cape-overlays +Just for reference some output of working BeagleBone Black configuration with CRAMPS board: +``` +model:[TI_AM335x_BeagleBone_Black] +UBOOT: Booted Device-Tree:[am335x-boneblack-uboot-univ.dts] +UBOOT: Loaded Overlay:[BB-ADC-00A0.bb.org-overlays] +UBOOT: Loaded Overlay:[BB-BONE-eMMC1-01-00A0.bb.org-overlays] +kernel:[5.10.168-ti-r71] +/boot/uEnv.txt Settings: +uboot_overlay_options:[enable_uboot_overlays=1] +uboot_overlay_options:[disable_uboot_overlay_video=0] +uboot_overlay_options:[disable_uboot_overlay_audio=1] +uboot_overlay_options:[disable_uboot_overlay_wireless=1] +uboot_overlay_options:[enable_uboot_cape_universal=1] +pkg:[bb-cape-overlays]:[4.14.20210821.0-0~bullseye+20210821] +pkg:[bb-customizations]:[1.20230720.1-0~bullseye+20230720] +pkg:[bb-usb-gadgets]:[1.20230414.0-0~bullseye+20230414] +pkg:[bb-wl18xx-firmware]:[1.20230414.0-0~bullseye+20230414] +............. +............. + +``` + +To compile the Klipper micro-controller code, start by configuring it for the "Beaglebone PRU", +for "BeagleBone Black" additionally disable options "Support GPIO Bit-banging devices" and disable "Support LCD devices" +inside the "Optional features" because they will not fit in 8Kb PRU firmware memory, +then exit and save config: ``` cd ~/klipper/ make menuconfig ``` -To build and install the new micro-controller code, run: +To build and install the new PRU micro-controller code, run: ``` sudo service klipper stop make flash sudo service klipper start ``` +After previous commands was executed your PRU firmware should be ready and started +to check if everything was fine you can execute following command +``` +dmesg +``` +and compare last messages with sample one which indicate that everything started properly: +``` +[ 71.105499] remoteproc remoteproc1: 4a334000.pru is available +[ 71.157155] remoteproc remoteproc2: 4a338000.pru is available +[ 73.256287] remoteproc remoteproc1: powering up 4a334000.pru +[ 73.279246] remoteproc remoteproc1: Booting fw image am335x-pru0-fw, size 97112 +[ 73.285807] remoteproc1#vdev0buffer: registered virtio0 (type 7) +[ 73.285836] remoteproc remoteproc1: remote processor 4a334000.pru is now up +[ 73.286322] remoteproc remoteproc2: powering up 4a338000.pru +[ 73.313717] remoteproc remoteproc2: Booting fw image am335x-pru1-fw, size 188560 +[ 73.313753] remoteproc remoteproc2: header-less resource table +[ 73.329964] remoteproc remoteproc2: header-less resource table +[ 73.348321] remoteproc remoteproc2: remote processor 4a338000.pru is now up +[ 73.443355] virtio_rpmsg_bus virtio0: creating channel rpmsg-pru addr 0x1e +[ 73.443727] virtio_rpmsg_bus virtio0: msg received with no recipient +[ 73.444352] virtio_rpmsg_bus virtio0: rpmsg host is online +[ 73.540993] rpmsg_pru virtio0.rpmsg-pru.-1.30: new rpmsg_pru device: /dev/rpmsg_pru30 +``` +take a note about "/dev/rpmsg_pru30" - it's your future serial device for main mcu configuration +this device is required to be present, if it's absent - your PRU cores did not start properly. + +## Building and installing Linux host micro-controller code +This section is required for "Use case 2" and optional for "Use case 3" mentioned above It is also necessary to compile and install the micro-controller code for a Linux host process. Configure it a second time for a "Linux process": @@ -83,12 +188,24 @@ sudo service klipper stop make flash sudo service klipper start ``` +take a note about "/tmp/klipper_host_mcu" - it will be your future serial device for "mcu host" +if that file don't exist - refer to "scripts/klipper-mcu.service" file, it was installed by +previous commands, and it's responsible for it. + + +Take a note for "Use case 2" about following: when you will define printer configuration you should always +use temperature sensors from "mcu host" because ADCs not present in default "mcu" (PRU cores). +Sample configuration of "sensor_pin" for extruder and heated bed are available in "generic-cramps.cfg" +You can use any other GPIO directly from "mcu host" by referencing them this way "host:gpiochip1/gpio17" +but that should be avoided because it will be creating additional load on main CPU and most probably +you can't use them for stepper control. + ## Remaining configuration -Complete the installation by configuring Klipper and Octoprint +Complete the installation by configuring Klipper following the instructions in -the main [Installation](Installation.md#configuring-klipper) document. +the main [Installation](Installation.md#configuring-octoprint-to-use-klipper) document. ## Printing on the Beaglebone @@ -97,4 +214,111 @@ OctoPrint well. Print stalls have been known to occur on complex prints (the printer may move faster than OctoPrint can send movement commands). If this occurs, consider using the "virtual_sdcard" feature (see [Config Reference](Config_Reference.md#virtual_sdcard) for -details) to print directly from Klipper. +details) to print directly from Klipper +and disable any DEBUG or VERBOSE logging options if you did enable them. + + +## AVR micro-controller code build +This environment have everything to build necessary micro-controller code except AVR, +AVR packages was removed because of conflict with PRU packages. +if you still want to build AVR micro-controller code in this environment you need to remove +PRU packages and install AVR packages by executing following commands + +``` +sudo apt-get remove gcc-pru +sudo apt-get install avrdude gcc-avr binutils-avr avr-libc +``` +if you need to restore PRU packages - then remove ARV packages before that +``` +sudo apt-get remove avrdude gcc-avr binutils-avr avr-libc +sudo apt-get install gcc-pru +``` + + +## Hardware Pin designation +BeagleBone is very flexible in terms of pin designation, same pin can be configured for different function +but always single function for single pin, same function can be present on different pins. +So you can't have multiple functions on single pin or have same function on multiple pins. +Example: +P9_20 - i2c2_sda/can0_tx/spi1_cs0/gpio0_12/uart1_ctsn +P9_19 - i2c2_scl/can0_rx/spi1_cs1/gpio0_13/uart1_rtsn +P9_24 - i2c1_scl/can1_rx/gpio0_15/uart1_tx +P9_26 - i2c1_sda/can1_tx/gpio0_14/uart1_rx + +Pin designation is defined by using special "overlays" which will be loaded during linux boot +they are configured by editing file /boot/uEnv.txt with elevated permissions +``` +sudo editor /boot/uEnv.txt +``` +and defining which functionality to load, for example to enable CAN1 you need to define overlay for it +``` +uboot_overlay_addr4=/lib/firmware/BB-CAN1-00A0.dtbo +``` +This overlay BB-CAN1-00A0.dtbo will reconfigure all required pins for CAN1 and create CAN device in Linux. +Any change in overlays will require system reboot to be applied. +If you need to understand which pins are involved in some overlay - you can analyze source files in +this location: /opt/sources/bb.org-overlays/src/arm/ +or search info in BeagleBone forums. + + +## Enabling hardware SPI +BeagleBone usually have multiple hardware SPI buses, for example BeagleBone Black can have 2 of them, +they can work up to 48Mhz, but usually they are limited to 16Mhz by Kernel Device-tree. +By default, in BeagleBone Black some of SPI1 pins are configured for HDMI-Audio output, +to fully enable 4-wire SPI1 you need to disable HDMI Audio and enable SPI1 +To do that edit file /boot/uEnv.txt with elevated permissions +``` +sudo editor /boot/uEnv.txt +``` +uncomment variable +``` +disable_uboot_overlay_audio=1 +``` + +next uncomment variable and define it this way +``` +uboot_overlay_addr4=/lib/firmware/BB-SPIDEV1-00A0.dtbo +``` +Save changes in /boot/uEnv.txt and reboot the board. +Now you have SPI1 Enabled, to verify its presence execute command +``` +ls /dev/spidev1.* +``` +Take a note that BeagleBone usually is 3.3v based hardware and to use 5V SPI devices +you need to add Level-Shifting chip, for example SN74CBTD3861, SN74LVC1G34 or similar. +If you are using CRAMPS board - it already contains Level-Shifting chip and SPI1 pins +will become available on P503 port, and they can accept 5v hardware, +check CRAMPS board Schematics for pin references. + +## Enabling hardware I2C +BeagleBone usually have multiple hardware I2C buses, for example BeagleBone Black can have 3 of them, +they support speed up-to 400Kbit Fast mode. +By default, in BeagleBone Black there are two of them (i2c-1 and i2c-2) usually both are already configured and +present on P9, third ic2-0 usually reserved for internal use. +If you are using CRAMPS board then i2c-2 is present on P303 port with 3.3v level, +If you want to obtain I2c-1 in CRAMPS board - you can get them on Extruder1.Step, Extruder1.Dir pins, +they also are 3.3v based, check CRAMPS board Schematics for pin references. +Related overlays, for [Hardware Pin designation](#hardware-pin-designation): +I2C1(100Kbit): BB-I2C1-00A0.dtbo +I2C1(400Kbit): BB-I2C1-FAST-00A0.dtbo +I2C2(100Kbit): BB-I2C2-00A0.dtbo +I2C2(400Kbit): BB-I2C2-FAST-00A0.dtbo + +## Enabling hardware UART(Serial)/CAN +BeagleBone have up to 6 hardware UART(Serial) buses (up to 3Mbit) +and up to 2 hardware CAN(1Mbit) buses. +UART1(RX,TX) and CAN1(TX,RX) and I2C2(SDA,SCL) are using same pins - so you need to chose what to use +UART1(CTSN,RTSN) and CAN0(TX,RX) and I2C1(SDA,SCL) are using same pins - so you need to chose what to use +All UART/CAN related pins are 3.3v based, so you will need to use Transceiver chips/boards like SN74LVC2G241DCUR (for UART), +SN65HVD230 (for CAN), TTL-RS485 (for RS-485) or something similar which can convert 3.3v signals to appropriate levels. + +Related overlays, for [Hardware Pin designation](#hardware-pin-designation) +CAN0: BB-CAN0-00A0.dtbo +CAN1: BB-CAN1-00A0.dtbo +UART0: - used for Console +UART1(RX,TX): BB-UART1-00A0.dtbo +UART1(RTS,CTS): BB-UART1-RTSCTS-00A0.dtbo +UART2(RX,TX): BB-UART2-00A0.dtbo +UART3(RX,TX): BB-UART3-00A0.dtbo +UART4(RS-485): BB-UART4-RS485-00A0.dtbo +UART5(RX,TX): BB-UART5-00A0.dtbo diff --git a/docs/Bed_Mesh.md b/docs/Bed_Mesh.md index d2a417dd40ec..62f1dee84ed7 100644 --- a/docs/Bed_Mesh.md +++ b/docs/Bed_Mesh.md @@ -44,10 +44,9 @@ probe_count: 5, 3 - `mesh_max: 240, 198`\ _Required_\ - The probed coordinate farthest farthest from the origin. This is not - necessarily the last point probed, as the probing process occurs in a - zig-zag fashion. As with `mesh_min`, this coordinate is relative to - the probe's location. + The probed coordinate farthest from the origin. This is not necessarily + the last point probed, as the probing process occurs in a zig-zag fashion. + As with `mesh_min`, this coordinate is relative to the probe's location. - `probe_count: 5, 3`\ _Default Value: 3, 3_\ @@ -370,21 +369,146 @@ are identified in green. ![bedmesh_interpolated](img/bedmesh_faulty_regions.svg) +### Adaptive Meshes + +Adaptive bed meshing is a way to speed up the bed mesh generation by only probing +the area of the bed used by the objects being printed. When used, the method will +automatically adjust the mesh parameters based on the area occupied by the defined +print objects. + +The adapted mesh area will be computed from the area defined by the boundaries of all +the defined print objects so it covers every object, including any margins defined in +the configuration. After the area is computed, the number of probe points will be +scaled down based on the ratio of the default mesh area and the adapted mesh area. To +illustrate this consider the following example: + +For a 150mmx150mm bed with `mesh_min` set to `25,25` and `mesh_max` set to `125,125`, +the default mesh area is a 100mmx100mm square. An adapted mesh area of `50,50` +means a ratio of `0.5x0.5` between the adapted area and default mesh area. + +If the `bed_mesh` configuration specified `probe_count` as `7x7`, the adapted bed +mesh will use 4x4 probe points (7 * 0.5 rounded up). + +![adaptive_bedmesh](img/adaptive_bed_mesh.svg) + +``` +[bed_mesh] +speed: 120 +horizontal_move_z: 5 +mesh_min: 35, 6 +mesh_max: 240, 198 +probe_count: 5, 3 +adaptive_margin: 5 +``` + +- `adaptive_margin` \ + _Default Value: 0_ \ + Margin (in mm) to add around the area of the bed used by the defined objects. The diagram + below shows the adapted bed mesh area with an `adaptive_margin` of 5mm. The adapted mesh + area (area in green) is computed as the used bed area (area in blue) plus the defined margin. + + ![adaptive_bedmesh_margin](img/adaptive_bed_mesh_margin.svg) + +By nature, adaptive bed meshes use the objects defined by the Gcode file being printed. +Therefore, it is expected that each Gcode file will generate a mesh that probes a different +area of the print bed. Therefore, adapted bed meshes should not be re-used. The expectation +is that a new mesh will be generated for each print if adaptive meshing is used. + +It is also important to consider that adaptive bed meshing is best used on machines that can +normally probe the entire bed and achieve a maximum variance less than or equal to 1 layer +height. Machines with mechanical issues that a full bed mesh normally compensates for may +have undesirable results when attempting print moves **outside** of the probed area. If a +full bed mesh has a variance greater than 1 layer height, caution must be taken when using +adaptive bed meshes and attempting print moves outside of the meshed area. + +## Surface Scans + +Some probes, such as the [Eddy Current Probe](./Eddy_Probe.md), are capable of +"scanning" the surface of the bed. That is, these probes can sample a mesh +without lifting the tool between samples. To activate scanning mode, the +`METHOD=scan` or `METHOD=rapid_scan` probe parameter should be passed in the +`BED_MESH_CALIBRATE` gcode command. + +### Scan Height + +The scan height is set by the `horizontal_move_z` option in `[bed_mesh]`. In +addition it can be supplied with the `BED_MESH_CALIBRATE` gcode command via the +`HORIZONTAL_MOVE_Z` parameter. + +The scan height must be sufficiently low to avoid scanning errors. Typically +a height of 2mm (ie: `HORIZONTAL_MOVE_Z=2`) should work well, presuming that the +probe is mounted correctly. + +It should be noted that if the probe is more than 4mm above the surface then the +results will be invalid. Thus, scanning is not possible on beds with severe +surface deviation or beds with extreme tilt that hasn't been corrected. + +### Rapid (Continuous) Scanning + +When performing a `rapid_scan` one should keep in mind that the results will +have some amount of error. This error should be low enough to be useful on +large print areas with reasonably thick layer heights. Some probes may be +more prone to error than others. + +It is not recommended that rapid mode be used to scan a "dense" mesh. Some of +the error introduced during a rapid scan may be gaussian noise from the sensor, +and a dense mesh will reflect this noise (ie: there will be peaks and valleys). + +Bed Mesh will attempt to optimize the travel path to provide the best possible +result based on the configuration. This includes avoiding faulty regions +when collecting samples and "overshooting" the mesh when changing direction. +This overshoot improves sampling at the edges of a mesh, however it requires +that the mesh be configured in a way that allows the tool to travel outside +of the mesh. + +``` +[bed_mesh] +speed: 120 +horizontal_move_z: 5 +mesh_min: 35, 6 +mesh_max: 240, 198 +probe_count: 5 +scan_overshoot: 8 +``` + +- `scan_overshoot` + _Default Value: 0 (disabled)_\ + The maximum amount of travel (in mm) available outside of the mesh. + For rectangular beds this applies to travel on the X axis, and for round beds + it applies to the entire radius. The tool must be able to travel the amount + specified outside of the mesh. This value is used to optimize the travel + path when performing a "rapid scan". The minimum value that may be specified + is 1. The default is no overshoot. + +If no scan overshoot is configured then travel path optimization will not +be applied to changes in direction. + ## Bed Mesh Gcodes ### Calibration -`BED_MESH_CALIBRATE PROFILE= METHOD=[manual | automatic] [=] - [=]`\ +`BED_MESH_CALIBRATE PROFILE= METHOD=[manual | automatic | scan | rapid_scan] \ +[=] [=] [ADAPTIVE=[0|1] \ +[ADAPTIVE_MARGIN=]`\ _Default Profile: default_\ -_Default Method: automatic if a probe is detected, otherwise manual_ +_Default Method: automatic if a probe is detected, otherwise manual_ \ +_Default Adaptive: 0_ \ +_Default Adaptive Margin: 0_ Initiates the probing procedure for Bed Mesh Calibration. The mesh will be saved into a profile specified by the `PROFILE` parameter, -or `default` if unspecified. If `METHOD=manual` is selected then manual probing -will occur. When switching between automatic and manual probing the generated -mesh points will automatically be adjusted. +or `default` if unspecified. The `METHOD` parameter takes one of the following +values: + +- `METHOD=manual`: enables manual probing using the nozzle and the paper test +- `METHOD=automatic`: Automatic (standard) probing. This is the default. +- `METHOD=scan`: Enables surface scanning. The tool will pause over each position + to collect a sample. +- `METHOD=rapid_scan`: Enables continuous surface scanning. + +XY positions are automatically adjusted to include the X and/or Y offsets +when a probing method other than `manual` is selected. It is possible to specify mesh parameters to modify the probed area. The following parameters are available: @@ -398,7 +522,10 @@ following parameters are available: - `MESH_ORIGIN` - `ROUND_PROBE_COUNT` - All beds: + - `MESH_PPS` - `ALGORITHM` + - `ADAPTIVE` + - `ADAPTIVE_MARGIN` See the configuration documentation above for details on how each parameter applies to the mesh. @@ -486,11 +613,207 @@ This gcode may be used to clear the internal mesh state. ### Apply X/Y offsets -`BED_MESH_OFFSET [X=] [Y=]` +`BED_MESH_OFFSET [X=] [Y=] [ZFADE=]` This is useful for printers with multiple independent extruders, as an offset is necessary to produce correct Z adjustment after a tool change. Offsets should be specified relative to the primary extruder. That is, a positive X offset should be specified if the secondary extruder is mounted to the -right of the primary extruder, and a positive Y offset should be specified -if the secondary extruder is mounted "behind" the primary extruder. +right of the primary extruder, a positive Y offset should be specified +if the secondary extruder is mounted "behind" the primary extruder, and +a positive ZFADE offset should be specified if the secondary extruder's +nozzle is above the primary extruder's. + +Note that a ZFADE offset does *NOT* directly apply additional adjustment. It +is intended to compensate for a `gcode offset` when [mesh fade](#mesh-fade) +is enabled. For example, if a secondary extruder is higher than the primary +and needs a negative gcode offset, ie: `SET_GCODE_OFFSET Z=-.2`, it can be +accounted for in `bed_mesh` with `BED_MESH_OFFSET ZFADE=.2`. + +## Bed Mesh Webhooks APIs + +### Dumping mesh data + +`{"id": 123, "method": "bed_mesh/dump_mesh"}` + +Dumps the configuration and state for the current mesh and all +saved profiles. + +The `dump_mesh` endpoint takes one optional parameter, `mesh_args`. +This parameter must be an object, where the keys and values are +parameters available to [BED_MESH_CALIBRATE](#bed_mesh_calibrate). +This will update the mesh configuration and probe points using the +supplied parameters prior to returning the result. It is recommended +to omit mesh parameters unless it is desired to visualize the probe points +and/or travel path before performing `BED_MESH_CALIBRATE`. + +## Visualization and analysis + +Most users will likely find that the visualizers included with +applications such as Mainsail, Fluidd, and Octoprint are sufficient +for basic analysis. However, Klipper's `scripts` folder contains the +`graph_mesh.py` script that may be used to perform additional +visualizations and more detailed analysis, particularly useful +for debugging hardware or the results produced by `bed_mesh`: + +``` +usage: graph_mesh.py [-h] {list,plot,analyze,dump} ... + +Graph Bed Mesh Data + +positional arguments: + {list,plot,analyze,dump} + list List available plot types + plot Plot a specified type + analyze Perform analysis on mesh data + dump Dump API response to json file + +options: + -h, --help show this help message and exit +``` + +### Pre-requisites + +Like most graphing tools provided by Klipper, `graph_mesh.py` requires +the `matplotlib` and `numpy` python dependencies. In addition, connecting +to Klipper via Moonraker's websocket requires the `websockets` python +dependency. While all visualizations can be output to an `svg` file, most of +the visualizations offered by `graph_mesh.py` are better viewed in live +preview mode on a desktop class PC. For example, the 3D visualizations may be +rotated and zoomed in preview mode, and the path visualizations can optionally +be animated in preview mode. + +### Plotting Mesh data + +The `graph_mesh.py` tool can plot several types of visualizations. +Available types can be shown by running `graph_mesh.py list`: + +``` +graph_mesh.py list +points Plot original generated points +path Plot probe travel path +rapid Plot rapid scan travel path +probedz Plot probed Z values +meshz Plot mesh Z values +overlay Plots the current probed mesh overlaid with a profile +delta Plots the delta between current probed mesh and a profile +``` + +Several options are available when plotting visualizations: + +``` +usage: graph_mesh.py plot [-h] [-a] [-s] [-p PROFILE_NAME] [-o OUTPUT] + +positional arguments: + Type of data to graph + Path/url to Klipper Socket or path to json file + +options: + -h, --help show this help message and exit + -a, --animate Animate paths in live preview + -s, --scale-plot Use axis limits reported by Klipper to scale plot X/Y + -p PROFILE_NAME, --profile-name PROFILE_NAME + Optional name of a profile to plot for 'probedz' + -o OUTPUT, --output OUTPUT + Output file path +``` + +Below is a description of each argument: + +- `plot type`: A required positional argument designating the type of + visualization to generate. Must be one of the types output by the + `graph_mesh.py list` command. +- `input`: A required positional argument containing a path or url + to the input source. This must be one of the following: + - A path to Klipper's Unix Domain Socket + - A url to an instance of Moonraker + - A path to a json file produced by `graph_mesh.py dump ` +- `-a`: Optional animation for the `path` and `rapid` visualization types. + Animations only apply to a live preview. +- `-s`: Optionally scales a plot using the `axis_minimum` and `axis_maximum` + values reported by Klipper's `toolhead` object when the dump file was + generated. +- `-p`: A profile name that may be specified when generating the + `probedz` 3D mesh visualization. When generating an `overlay` or + `delta` visualization this argument must be provided. +- `-o`: An optional file path indicating that the script should save the + visualization to this location rather than run in preview mode. Images + are saved in `svg` format. + +For example, to plot an animated rapid path, connecting via Klipper's unix +socket: + +``` +graph_mesh.py plot -a rapid ~/printer_data/comms/klippy.sock +``` + +Or to plot a 3d visualization of the mesh, connecting via Moonraker: + +``` +graph_mesh.py plot meshz http://my-printer.local +``` + +### Bed Mesh Analysis + +The `graph_mesh.py` tool may also be used to perform an analysis on the +data provided by the [bed_mesh/dump_mesh](#dumping-mesh-data) API: + +``` +graph_mesh.py analyze +``` + +As with the `plot` command, the `` must be a path to Klipper's +unix socket, a URL to an instance of Moonraker, or a path to a json file +generated by the dump command. + +To begin, the analysis will perform various checks on the points and +probe paths generated by `bed_mesh` at the time of the dump. This +includes the following: + +- The number of probe points generated, without any additions +- The number of probe points generated including any points generated + as the result faulty regions and/or a configured zero reference position. +- The number of probe points generated when performing a rapid scan. +- The total number of moves generated for a rapid scan. +- A validation that the probe points generated for a rapid scan are + identical to the probe points generated for a standard probing procedure. +- A "backtracking" check for both the standard probe path and a rapid scan + path. Backtracking can be defined as moving to the same position more than + once during the probing procedure. Backtracking should never occur during a + standard probe. Faulty regions *can* result in backtracking during a rapid + scan in an attempt to avoid entering a faulty region when approaching or + leaving a probe location, however should never occur otherwise. + +Next each probed mesh present in the dump will by analyzed, beginning with +the mesh loaded at the time of the dump (if present) and followed by any +saved profiles. The following data is extracted: + +- Mesh shape (Min X,Y, Max X,Y Probe Count) +- Mesh Z range, (Minimum Z, Maximum Z) +- Mean Z value in the mesh +- Standard Deviation of the Z values in the Mesh + +In addition to the above, a delta analysis is performed between meshes +with the same shape, reporting the following: +- The range of the delta between to meshes (Minimum and Maximum) +- The mean delta +- Standard Deviation of the delta +- The absolute maximum difference +- The absolute mean + +### Save mesh data to a file + +The `dump` command may be used to save the response to a file which +can be shared for analysis when troubleshooting: + +``` +graph_mesh.py dump -o +``` + +The `` should be a path to Klipper's unix socket or +a URL to an instance of Moonraker. The `-o` option may be used to +specify the path to the output file. If omitted, the file will be +saved in the working directory, with a file name in the following +format: + +`klipper-bedmesh-{year}{month}{day}{hour}{minute}{second}.json` diff --git a/docs/CANBUS.md b/docs/CANBUS.md index e80141a955dd..321f8e8914c6 100644 --- a/docs/CANBUS.md +++ b/docs/CANBUS.md @@ -31,7 +31,7 @@ adapter. This is typically done by creating a new file named allow-hotplug can0 iface can0 can static bitrate 1000000 - up ifconfig $IFACE txqueuelen 128 + up ip link set $IFACE txqueuelen 128 ``` ## Terminating Resistors @@ -113,7 +113,7 @@ Some important notes when using this mode: allow-hotplug can0 iface can0 can static bitrate 1000000 - up ifconfig $IFACE txqueuelen 128 + up ip link set $IFACE txqueuelen 128 ``` * The "bridge mcu" is not actually on the CAN bus. Messages to and diff --git a/docs/CANBUS_Troubleshooting.md b/docs/CANBUS_Troubleshooting.md index bd9ef0456205..de0deaf74e06 100644 --- a/docs/CANBUS_Troubleshooting.md +++ b/docs/CANBUS_Troubleshooting.md @@ -52,6 +52,56 @@ Reordered messages is a severe problem that must be fixed. It will result in unstable behavior and can lead to confusing errors at any part of a print. +## Use an appropriate txqueuelen setting + +The Klipper code uses the Linux kernel to manage CAN bus traffic. By +default, the kernel will only queue 10 CAN transmit packets. It is +recommended to [configure the can0 device](CANBUS.md#host-hardware) +with a `txqueuelen 128` to increase that size. + +If Klipper transmits a packet and Linux has filled all of its transmit +queue space then Linux will drop that packet and messages like the +following will appear in the Klipper log: +``` +Got error -1 in can write: (105)No buffer space available +``` +Klipper will automatically retransmit the lost messages as part of its +normal application level message retransmit system. Thus, this log +message is a warning and it does not indicate an unrecoverable error. + +If a complete CAN bus failure occurs (such as a CAN wire break) then +Linux will not be able to transmit any messages on the CAN bus and it +is common to find the above message in the Klipper log. In this case, +the log message is a symptom of a larger problem (the inability to +transmit any messages) and is not directly related to Linux +`txqueuelen`. + +One may check the current queue size by running the Linux command `ip +link show can0`. It should report a bunch of text including the +snippet `qlen 128`. If one sees something like `qlen 10` then it +indicates the CAN device has not been properly configured. + +It is not recommended to use a `txqueuelen` significantly larger +than 128. A CAN bus running at a frequency of 1000000 will typically +take around 120us to transmit a CAN packet. Thus a queue of 128 +packets is likely to take around 15-20ms to drain. A substantially +larger queue could cause excessive spikes in message round-trip-time +which could lead to unrecoverable errors. Said another way, Klipper's +application retransmit system is more robust if it does not have to +wait for Linux to drain an excessively large queue of possibly stale +data. This is analogous to the problem of +[bufferbloat](https://en.wikipedia.org/wiki/Bufferbloat) on internet +routers. + +Under normal circumstances Klipper may utilize ~25 queue slots per +MCU - typically only utilizing more slots during retransmits. +(Specifically, the Klipper host may transmit up to 192 bytes to each +Klipper MCU before receiving an acknowledgment from that MCU.) If a +single CAN bus has 5 or more Klipper MCUs on it, then it might be +necessary to increase the `txqueuelen` above the recommended value +of 128. However, as above, care should be taken when selecting a new +value to avoid excessive round-trip-time latency. + ## Obtaining candump logs The CAN bus messages sent to and from the micro-controller are handled diff --git a/docs/Code_Overview.md b/docs/Code_Overview.md index 5b3e07e11da5..0e4836acf5ec 100644 --- a/docs/Code_Overview.md +++ b/docs/Code_Overview.md @@ -136,8 +136,9 @@ provides further information on the mechanics of moves. * The ToolHead class (in toolhead.py) handles "look-ahead" and tracks the timing of printing actions. The main codepath for a move is: - `ToolHead.move() -> MoveQueue.add_move() -> MoveQueue.flush() -> - Move.set_junction() -> ToolHead._process_moves()`. + `ToolHead.move() -> LookAheadQueue.add_move() -> + LookAheadQueue.flush() -> Move.set_junction() -> + ToolHead._process_moves()`. * ToolHead.move() creates a Move() object with the parameters of the move (in cartesian space and in units of seconds and millimeters). * The kinematics class is given the opportunity to audit each move @@ -146,10 +147,10 @@ provides further information on the mechanics of moves. may raise an error if the move is not valid. If check_move() completes successfully then the underlying kinematics must be able to handle the move. - * MoveQueue.add_move() places the move object on the "look-ahead" - queue. - * MoveQueue.flush() determines the start and end velocities of each - move. + * LookAheadQueue.add_move() places the move object on the + "look-ahead" queue. + * LookAheadQueue.flush() determines the start and end velocities of + each move. * Move.set_junction() implements the "trapezoid generator" on a move. The "trapezoid generator" breaks every move into three parts: a constant acceleration phase, followed by a constant velocity @@ -170,17 +171,18 @@ provides further information on the mechanics of moves. placed on a "trapezoid motion queue": `ToolHead._process_moves() -> trapq_append()` (in klippy/chelper/trapq.c). The step times are then generated: `ToolHead._process_moves() -> - ToolHead._update_move_time() -> MCU_Stepper.generate_steps() -> - itersolve_generate_steps() -> itersolve_gen_steps_range()` (in - klippy/chelper/itersolve.c). The goal of the iterative solver is to - find step times given a function that calculates a stepper position - from a time. This is done by repeatedly "guessing" various times - until the stepper position formula returns the desired position of - the next step on the stepper. The feedback produced from each guess - is used to improve future guesses so that the process rapidly - converges to the desired time. The kinematic stepper position - formulas are located in the klippy/chelper/ directory (eg, - kin_cart.c, kin_corexy.c, kin_delta.c, kin_extruder.c). + ToolHead._advance_move_time() -> ToolHead._advance_flush_time() -> + MCU_Stepper.generate_steps() -> itersolve_generate_steps() -> + itersolve_gen_steps_range()` (in klippy/chelper/itersolve.c). The + goal of the iterative solver is to find step times given a function + that calculates a stepper position from a time. This is done by + repeatedly "guessing" various times until the stepper position + formula returns the desired position of the next step on the + stepper. The feedback produced from each guess is used to improve + future guesses so that the process rapidly converges to the desired + time. The kinematic stepper position formulas are located in the + klippy/chelper/ directory (eg, kin_cart.c, kin_corexy.c, + kin_delta.c, kin_extruder.c). * Note that the extruder is handled in its own kinematic class: `ToolHead._process_moves() -> PrinterExtruder.move()`. Since diff --git a/docs/Config_Changes.md b/docs/Config_Changes.md index 2ceb868db0b9..b5212ce19930 100644 --- a/docs/Config_Changes.md +++ b/docs/Config_Changes.md @@ -8,6 +8,41 @@ All dates in this document are approximate. ## Changes +20240415: The `on_error_gcode` parameter in the `[virtual_sdcard]` +config section now has a default. If this parameter is not specified +it now defaults to `TURN_OFF_HEATERS`. If the previous behavior is +desired (take no default action on an error during a virtual_sdcard +print) then define `on_error_gcode` with an empty value. + +20240313: The `max_accel_to_decel` parameter in the `[printer]` config +section has been deprecated. The `ACCEL_TO_DECEL` parameter of the +`SET_VELOCITY_LIMIT` command has been deprecated. The +`printer.toolhead.max_accel_to_decel` status has been removed. Use the +[minimum_cruise_ratio parameter](./Config_Reference.md#printer) +instead. The deprecated features will be removed in the near future, +and using them in the interim may result in subtly different behavior. + +20240215: Several deprecated features have been removed. Using "NTC +100K beta 3950" as a thermistor name has been removed (deprecated on +20211110). The `SYNC_STEPPER_TO_EXTRUDER` and +`SET_EXTRUDER_STEP_DISTANCE` commands have been removed, and the +extruder `shared_heater` config option has been removed (deprecated on +20220210). The bed_mesh `relative_reference_index` option has been +removed (deprecated on 20230619). + +20240123: The output_pin SET_PIN CYCLE_TIME parameter has been +removed. Use the new +[pwm_cycle_time](Config_Reference.md#pwm_cycle_time) module if it is +necessary to dynamically change a pwm pin's cycle time. + +20240123: The output_pin `maximum_mcu_duration` parameter is +deprecated. Use a [pwm_tool config section](Config_Reference.md#pwm_tool) +instead. The option will be removed in the near future. + +20240123: The output_pin `static_value` parameter is deprecated. +Replace with `value` and `shutdown_value` parameters. The option will +be removed in the near future. + 20231216: The `[hall_filament_width_sensor]` is changed to trigger filament runout when the thickness of the filament exceeds `max_diameter`. The maximum diameter defaults to `default_nominal_filament_diameter + max_difference`. See diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 4b53ef264e44..b192e7362c76 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -97,13 +97,22 @@ max_accel: # 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 value specified here may -# be changed at runtime using the SET_VELOCITY_LIMIT command. The -# default is half of max_accel. +#minimum_cruise_ratio: 0.5 +# Most moves will accelerate to a cruising speed, travel at that +# cruising speed, and then decelerate. However, some moves that +# travel a short distance could nominally accelerate and then +# immediately decelerate. This option reduces the top speed of these +# moves to ensure there is always a minimum distance traveled at a +# cruising speed. That is, it enforces a minimum distance traveled +# at cruising speed relative to the total distance traveled. It is +# intended to reduce the top speed of short zigzag moves (and thus +# reduce printer vibration from these moves). For example, a +# minimum_cruise_ratio of 0.5 would ensure that a standalone 1.5mm +# move would have a minimum cruising distance of 0.75mm. Specify a +# ratio of 0.0 to disable this feature (there would be no minimum +# cruising distance enforced between acceleration and deceleration). +# The value specified here may be changed at runtime using the +# SET_VELOCITY_LIMIT command. The default is 0.5. #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 @@ -116,6 +125,8 @@ max_accel: # 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. +#max_accel_to_decel: +# This parameter is deprecated and should no longer be used. ``` ### [stepper] @@ -979,18 +990,21 @@ Visual Examples: # where Z = 0. When this option is specified the mesh will be offset # so that zero Z adjustment occurs at this location. The default is # no zero reference. -#relative_reference_index: -# **DEPRECATED, use the "zero_reference_position" option** -# The legacy option superceded by the "zero reference position". -# Rather than a coordinate this option takes an integer "index" that -# refers to the location of one of the generated points. It is recommended -# to use the "zero_reference_position" instead of this option for new -# configurations. The default is no relative reference index. #faulty_region_1_min: #faulty_region_1_max: # Optional points that define a faulty region. See docs/Bed_Mesh.md # for details on faulty regions. Up to 99 faulty regions may be added. # By default no faulty regions are set. +#adaptive_margin: +# An optional margin (in mm) to be added around the bed area used by +# the defined print objects when generating an adaptive mesh. +#scan_overshoot: +# The maximum amount of travel (in mm) available outside of the mesh. +# For rectangular beds this applies to travel on the X axis, and for round beds +# it applies to the entire radius. The tool must be able to travel the amount +# specified outside of the mesh. This value is used to optimize the travel +# path when performing a "rapid scan". The minimum value that may be specified +# is 1. The default is no overshoot. ``` ### [bed_tilt] @@ -1465,7 +1479,8 @@ path: # be provided. #on_error_gcode: # A list of G-Code commands to execute when an error is reported. - +# See docs/Command_Templates.md for G-Code format. The default is to +# run TURN_OFF_HEATERS. ``` ### [sdcard_loop] @@ -1988,6 +2003,43 @@ z_offset: # See the "probe" section for more information on the parameters above. ``` +### [probe_eddy_current] + +Support for eddy current inductive probes. One may define this section +(instead of a probe section) to enable this probe. See the +[command reference](G-Codes.md#probe_eddy_current) for further information. + +``` +[probe_eddy_current my_eddy_probe] +sensor_type: ldc1612 +# The sensor chip used to perform eddy current measurements. This +# parameter must be provided and must be set to ldc1612. +#intb_pin: +# MCU gpio pin connected to the ldc1612 sensor's INTB pin (if +# available). The default is to not use the INTB pin. +#z_offset: +# The nominal distance (in mm) between the nozzle and bed that a +# probing attempt should stop at. This parameter must be provided. +#i2c_address: +#i2c_mcu: +#i2c_bus: +#i2c_software_scl_pin: +#i2c_software_sda_pin: +#i2c_speed: +# The i2c settings for the sensor chip. See the "common I2C +# settings" section for a description of the above parameters. +#x_offset: +#y_offset: +#speed: +#lift_speed: +#samples: +#sample_retract_dist: +#samples_result: +#samples_tolerance: +#samples_tolerance_retries: +# See the "probe" section for information on these parameters. +``` + ### [axis_twist_compensation] A tool to compensate for inaccurate probe readings due to twist in X gantry. See @@ -2448,9 +2500,9 @@ sensor_pin: # name in the above list. ``` -### BMP180/BMP280/BME280/BME680 temperature sensor +### BMP180/BMP280/BME280/BMP388/BME680 temperature sensor -BMP180/BMP280/BME280/BME680 two wire interface (I2C) environmental sensors. +BMP180/BMP280/BME280/BMP388/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. @@ -2461,8 +2513,8 @@ temperature. ``` sensor_type: BME280 #i2c_address: -# Default is 118 (0x76). The BMP180 and some BME280 sensors have an address of 119 -# (0x77). +# Default is 118 (0x76). The BMP180, BMP388 and some BME280 sensors +# have an address of 119 (0x77). #i2c_mcu: #i2c_bus: #i2c_software_scl_pin: @@ -2533,6 +2585,25 @@ sensor_type: # Interval in seconds between readings. Default is 30 ``` +### SHT3X sensor + +SHT3X family two wire interface (I2C) environmental sensor. These sensors +have a range of -55~125 C, so are usable for e.g. chamber temperature +monitoring. They can also function as simple fan/heater controllers. + +``` +sensor_type: SHT3X +#i2c_address: +# Default is 68 (0x44). +#i2c_mcu: +#i2c_bus: +#i2c_software_scl_pin: +#i2c_software_sda_pin: +#i2c_speed: +# See the "common I2C settings" section for a description of the +# above parameters. +``` + ### LM75 temperature sensor LM75/LM75A two wire (I2C) connected temperature sensors. These sensors @@ -3096,24 +3167,12 @@ pin: # If this is true, the value fields should be between 0 and 1; if it # is false the value fields should be either 0 or 1. The default is # False. -#static_value: -# If this is set, then the pin is assigned to this value at startup -# and the pin can not be changed during runtime. A static pin uses -# slightly less ram in the micro-controller. The default is to use -# runtime configuration of pins. #value: # The value to initially set the pin to during MCU configuration. # The default is 0 (for low voltage). #shutdown_value: # The value to set the pin to on an MCU shutdown event. The default # is 0 (for low voltage). -#maximum_mcu_duration: -# The maximum duration a non-shutdown value may be driven by the MCU -# without an acknowledge from the host. -# If host can not keep up with an update, the MCU will shutdown -# and set all pins to their respective shutdown values. -# Default: 0 (disabled) -# Usual values are around 5 seconds. #cycle_time: 0.100 # The amount of time (in seconds) per PWM cycle. It is recommended # this be 10 milliseconds or greater when using software based PWM. @@ -3133,6 +3192,9 @@ pin: # then the 'value' parameter can be specified using the desired # amperage for the stepper. The default is to not scale the 'value' # parameter. +#maximum_mcu_duration: +#static_value: +# These options are deprecated and should no longer be specified. ``` ### [pwm_tool] @@ -3147,15 +3209,39 @@ 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. +#maximum_mcu_duration: +# The maximum duration a non-shutdown value may be driven by the MCU +# without an acknowledge from the host. +# If host can not keep up with an update, the MCU will shutdown +# and set all pins to their respective shutdown values. +# Default: 0 (disabled) +# Usual values are around 5 seconds. #value: #shutdown_value: -#maximum_mcu_duration: #cycle_time: 0.100 #hardware_pwm: False #scale: # See the "output_pin" section for the definition of these parameters. ``` +### [pwm_cycle_time] + +Run-time configurable output pins with dynamic pwm cycle timing (one +may define any number of sections with an "pwm_cycle_time" 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 CYCLE_TIME=0.100" +type extended [g-code commands](G-Codes.md#pwm_cycle_time). + +``` +[pwm_cycle_time my_pin] +pin: +#value: +#shutdown_value: +#cycle_time: 0.100 +#scale: +# See the "output_pin" section for information on these parameters. +``` + ### [static_digital_output] Statically configured digital output pins (one may define any number @@ -3241,6 +3327,18 @@ run_current: # set, "stealthChop" mode will be enabled if the stepper motor # velocity is below this value. The default is 0, which disables # "stealthChop" mode. +#coolstep_threshold: +# The velocity (in mm/s) to set the TMC driver internal "CoolStep" +# threshold to. If set, the coolstep feature will be enabled when +# the stepper motor velocity is near or above this value. Important +# - if coolstep_threshold is set and "sensorless homing" is used, +# then one must ensure that the homing speed is above the coolstep +# threshold! The default is to not enable the coolstep feature. +#high_velocity_threshold: +# The velocity (in mm/s) to set the TMC driver internal "high +# velocity" threshold (THIGH) to. This is typically used to disable +# the "CoolStep" feature at high speeds. The default is to not set a +# TMC "high velocity" threshold. #driver_MSLUT0: 2863314260 #driver_MSLUT1: 1251300522 #driver_MSLUT2: 608774441 @@ -3271,11 +3369,19 @@ run_current: #driver_TOFF: 4 #driver_HEND: 7 #driver_HSTRT: 0 +#driver_VHIGHFS: 0 +#driver_VHIGHCHM: 0 #driver_PWM_AUTOSCALE: True #driver_PWM_FREQ: 1 #driver_PWM_GRAD: 4 #driver_PWM_AMPL: 128 #driver_SGT: 0 +#driver_SEMIN: 0 +#driver_SEUP: 0 +#driver_SEMAX: 0 +#driver_SEDN: 0 +#driver_SEIMIN: 0 +#driver_SFILT: 0 # Set the given register during the configuration of the TMC2130 # chip. This may be used to set custom motor parameters. The # defaults for each parameter are next to the parameter name in the @@ -3372,6 +3478,13 @@ run_current: #sense_resistor: 0.110 #stealthchop_threshold: 0 # See the "tmc2208" section for the definition of these parameters. +#coolstep_threshold: +# The velocity (in mm/s) to set the TMC driver internal "CoolStep" +# threshold to. If set, the coolstep feature will be enabled when +# the stepper motor velocity is near or above this value. Important +# - if coolstep_threshold is set and "sensorless homing" is used, +# then one must ensure that the homing speed is above the coolstep +# threshold! The default is to not enable the coolstep feature. #uart_address: # The address of the TMC2209 chip for UART messages (an integer # between 0 and 3). This is typically used when multiple TMC2209 @@ -3391,6 +3504,11 @@ run_current: #driver_PWM_GRAD: 14 #driver_PWM_OFS: 36 #driver_SGTHRS: 0 +#driver_SEMIN: 0 +#driver_SEUP: 0 +#driver_SEMAX: 0 +#driver_SEDN: 0 +#driver_SEIMIN: 0 # Set the given register during the configuration of the TMC2209 # chip. This may be used to set custom motor parameters. The # defaults for each parameter are next to the parameter name in the @@ -3525,6 +3643,18 @@ run_current: # set, "stealthChop" mode will be enabled if the stepper motor # velocity is below this value. The default is 0, which disables # "stealthChop" mode. +#coolstep_threshold: +# The velocity (in mm/s) to set the TMC driver internal "CoolStep" +# threshold to. If set, the coolstep feature will be enabled when +# the stepper motor velocity is near or above this value. Important +# - if coolstep_threshold is set and "sensorless homing" is used, +# then one must ensure that the homing speed is above the coolstep +# threshold! The default is to not enable the coolstep feature. +#high_velocity_threshold: +# The velocity (in mm/s) to set the TMC driver internal "high +# velocity" threshold (THIGH) to. This is typically used to disable +# the "CoolStep" feature at high speeds. The default is to not set a +# TMC "high velocity" threshold. #driver_MSLUT0: 2863314260 #driver_MSLUT1: 1251300522 #driver_MSLUT2: 608774441 @@ -3646,6 +3776,18 @@ run_current: # set, "stealthChop" mode will be enabled if the stepper motor # velocity is below this value. The default is 0, which disables # "stealthChop" mode. +#coolstep_threshold: +# The velocity (in mm/s) to set the TMC driver internal "CoolStep" +# threshold to. If set, the coolstep feature will be enabled when +# the stepper motor velocity is near or above this value. Important +# - if coolstep_threshold is set and "sensorless homing" is used, +# then one must ensure that the homing speed is above the coolstep +# threshold! The default is to not enable the coolstep feature. +#high_velocity_threshold: +# The velocity (in mm/s) to set the TMC driver internal "high +# velocity" threshold (THIGH) to. This is typically used to disable +# the "CoolStep" feature at high speeds. The default is to not set a +# TMC "high velocity" threshold. #driver_MSLUT0: 2863314260 #driver_MSLUT1: 1251300522 #driver_MSLUT2: 608774441 diff --git a/docs/Eddy_Probe.md b/docs/Eddy_Probe.md new file mode 100644 index 000000000000..221c855b6d66 --- /dev/null +++ b/docs/Eddy_Probe.md @@ -0,0 +1,56 @@ +# Eddy Current Inductive probe + +This document describes how to use an +[eddy current](https://en.wikipedia.org/wiki/Eddy_current) inductive +probe in Klipper. + +Currently, an eddy current probe can not be used for Z homing. The +sensor can only be used for Z probing. + +Start by declaring a +[probe_eddy_current config section](Config_Reference.md#probe_eddy_current) +in the printer.cfg file. It is recommended to set the `z_offset` to +0.5mm. It is typical for the sensor to require an `x_offset` and +`y_offset`. If these values are not known, one should estimate the +values during initial calibration. + +The first step in calibration is to determine the appropriate +DRIVE_CURRENT for the sensor. Home the printer and navigate the +toolhead so that the sensor is near the center of the bed and is about +20mm above the bed. Then issue an `LDC_CALIBRATE_DRIVE_CURRENT +CHIP=` command. For example, if the config section was +named `[probe_eddy_current my_eddy_probe]` then one would run +`LDC_CALIBRATE_DRIVE_CURRENT CHIP=my_eddy_probe`. This command should +complete in a few seconds. After it completes, issue a `SAVE_CONFIG` +command to save the results to the printer.cfg and restart. + +The second step in calibration is to correlate the sensor readings to +the corresponding Z heights. Home the printer and navigate the +toolhead so that the nozzle is near the center of the bed. Then run an +`PROBE_EDDY_CURRENT_CALIBRATE CHIP=my_eddy_probe` command. Once the +tool starts, follow the steps described at +["the paper test"](Bed_Level.md#the-paper-test) to determine the +actual distance between the nozzle and bed at the given location. Once +those steps are complete one can `ACCEPT` the position. The tool will +then move the the toolhead so that the sensor is above the point where +the nozzle used to be and run a series of movements to correlate the +sensor to Z positions. This will take a couple of minutes. After the +tool completes, issue a `SAVE_CONFIG` command to save the results to +the printer.cfg and restart. + +After initial calibration it is a good idea to verify that the +`x_offset` and `y_offset` are accurate. Follow the steps to +[calibrate probe x and y offsets](Probe_Calibrate.md#calibrating-probe-x-and-y-offsets). +If either the `x_offset` or `y_offset` is modified then be sure to run +the `PROBE_EDDY_CURRENT_CALIBRATE` command (as described above) after +making the change. + +Once calibration is complete, one may use all the standard Klipper +tools that use a Z probe. + +Note that eddy current sensors (and inductive probes in general) are +susceptible to "thermal drift". That is, changes in temperature can +result in changes in reported Z height. Changes in either the bed +surface temperature or sensor hardware temperature can skew the +results. It is important that calibration and probing is only done +when the printer is at a stable temperature. diff --git a/docs/G-Codes.md b/docs/G-Codes.md index 8c70609f18c9..e55fba35db80 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -139,6 +139,17 @@ Writes raw "value" into register "register". Both "value" and and refer to sensor data sheet for the reference. This is only available for tle5012b chips. +### [axis_twist_compensation] + +The following commands are available when the +[axis_twist_compensation config +section](Config_Reference.md#axis_twist_compensation) is enabled. + +#### AXIS_TWIST_COMPENSATION_CALIBRATE +`AXIS_TWIST_COMPENSATION_CALIBRATE [SAMPLE_COUNT=]`: Initiates the X +twist calibration wizard. `SAMPLE_COUNT` specifies the number of points along +the X axis to calibrate at and defaults to 3. + ### [bed_mesh] The following commands are available when the @@ -146,15 +157,21 @@ The following commands are available when the (also see the [bed mesh guide](Bed_Mesh.md)). #### BED_MESH_CALIBRATE -`BED_MESH_CALIBRATE [METHOD=manual] [HORIZONTAL_MOVE_Z=] -[=] [=]`: This command probes -the bed using generated points specified by the parameters in the config. After -probing, a mesh is generated and z-movement is adjusted according to the mesh. +`BED_MESH_CALIBRATE [PROFILE=] [METHOD=manual] [HORIZONTAL_MOVE_Z=] +[=] [=] [ADAPTIVE=1] +[ADAPTIVE_MARGIN=]`: This command probes the bed using generated points +specified by the parameters in the config. After probing, a mesh is generated +and z-movement is adjusted according to the mesh. +The mesh will be saved into a profile specified by the `PROFILE` parameter, +or `default` if unspecified. See the PROBE command for details on the optional probe parameters. If METHOD=manual is specified then the manual probing tool is activated - see the MANUAL_PROBE command above for details on the additional commands available while this tool is active. The optional `HORIZONTAL_MOVE_Z` value overrides the -`horizontal_move_z` option specified in the config file. +`horizontal_move_z` option specified in the config file. If ADAPTIVE=1 is +specified then the objects defined by the Gcode file being printed will be used +to define the probed area. The optional `ADAPTIVE_MARGIN` value overrides the +`adaptive_margin` option specified in the config file. #### BED_MESH_OUTPUT `BED_MESH_OUTPUT PGP=[<0:1>]`: This command outputs the current probed @@ -184,10 +201,12 @@ SAVE_CONFIG gcode must be run to make the changes to persistent memory permanent. #### BED_MESH_OFFSET -`BED_MESH_OFFSET [X=] [Y=]`: Applies X and/or Y offsets -to the mesh lookup. This is useful for printers with independent -extruders, as an offset is necessary to produce correct Z adjustment -after a tool change. +`BED_MESH_OFFSET [X=] [Y=] [ZFADE= [CYCLE_TIME=]`: Set -the pin to the given output `VALUE`. VALUE should be 0 or 1 for -"digital" output pins. For PWM pins, set to a value between 0.0 and -1.0, or between 0.0 and `scale` if a scale is configured in the -output_pin config section. - -Some pins (currently only "soft PWM" pins) support setting an explicit -cycle time using the CYCLE_TIME parameter (specified in seconds). Note -that the CYCLE_TIME parameter is not stored between SET_PIN commands -(any SET_PIN command without an explicit CYCLE_TIME parameter will use -the `cycle_time` specified in the output_pin config section). +`SET_PIN PIN=config_name VALUE=`: Set the pin to the given +output `VALUE`. VALUE should be 0 or 1 for "digital" output pins. For +PWM pins, set to a value between 0.0 and 1.0, or between 0.0 and +`scale` if a scale is configured in the output_pin config section. ### [palette2] @@ -978,6 +984,43 @@ babystepping), and subtract if from the probe's z_offset. This acts to take a frequently used babystepping value, and "make it permanent". Requires a `SAVE_CONFIG` to take effect. +### [probe_eddy_current] + +The following commands are available when a +[probe_eddy_current config section](Config_Reference.md#probe_eddy_current) +is enabled. + +#### PROBE_EDDY_CURRENT_CALIBRATE +`PROBE_EDDY_CURRENT_CALIBRATE CHIP=`: This starts a tool +that calibrates the sensor resonance frequencies to corresponding Z +heights. The tool will take a couple of minutes to complete. After +completion, use the SAVE_CONFIG command to store the results in the +printer.cfg file. + +#### LDC_CALIBRATE_DRIVE_CURRENT +`LDC_CALIBRATE_DRIVE_CURRENT CHIP=` This tool will +calibrate the ldc1612 DRIVE_CURRENT0 register. Prior to using this +tool, move the sensor so that it is near the center of the bed and +about 20mm above the bed surface. Run this command to determine an +appropriate DRIVE_CURRENT for the sensor. After running this command +use the SAVE_CONFIG command to store that new setting in the +printer.cfg config file. + +### [pwm_cycle_time] + +The following command is available when a +[pwm_cycle_time config section](Config_Reference.md#pwm_cycle_time) +is enabled. + +#### SET_PIN +`SET_PIN PIN=config_name VALUE= [CYCLE_TIME=]`: +This command works similarly to [output_pin](#output_pin) SET_PIN +commands. The command here supports setting an explicit cycle time +using the CYCLE_TIME parameter (specified in seconds). Note that the +CYCLE_TIME parameter is not stored between SET_PIN commands (any +SET_PIN command without an explicit CYCLE_TIME parameter will use the +`cycle_time` specified in the pwm_cycle_time config section). + ### [query_adc] The query_adc module is automatically loaded. @@ -1281,7 +1324,7 @@ The toolhead module is automatically loaded. #### SET_VELOCITY_LIMIT `SET_VELOCITY_LIMIT [VELOCITY=] [ACCEL=] -[ACCEL_TO_DECEL=] [SQUARE_CORNER_VELOCITY=]`: This +[MINIMUM_CRUISE_RATIO=] [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 @@ -1343,17 +1386,6 @@ print. #### SDCARD_RESET_FILE `SDCARD_RESET_FILE`: Unload file and clear SD state. -### [axis_twist_compensation] - -The following commands are available when the -[axis_twist_compensation config -section](Config_Reference.md#axis_twist_compensation) is enabled. - -#### AXIS_TWIST_COMPENSATION_CALIBRATE -`AXIS_TWIST_COMPENSATION_CALIBRATE [SAMPLE_COUNT=]`: Initiates the X -twist calibration wizard. `SAMPLE_COUNT` specifies the number of points along -the X axis to calibrate at and defaults to 3. - ### [z_thermal_adjust] The following commands are available when the diff --git a/docs/Kinematics.md b/docs/Kinematics.md index 313d5dce035c..1996579d2315 100644 --- a/docs/Kinematics.md +++ b/docs/Kinematics.md @@ -96,7 +96,7 @@ Key formula for look-ahead: end_velocity^2 = start_velocity^2 + 2*accel*move_distance ``` -### Smoothed look-ahead +### Minimum cruise ratio Klipper also implements a mechanism for smoothing out the motions of short "zigzag" moves. Consider the following moves: @@ -105,21 +105,27 @@ short "zigzag" moves. Consider the following moves: In the above, the frequent changes from acceleration to deceleration can cause the machine to vibrate which causes stress on the machine -and increases the noise. To reduce this, Klipper tracks both regular -move acceleration as well as a virtual "acceleration to deceleration" -rate. Using this system, the top speed of these short "zigzag" moves -are limited to smooth out the printer motion: +and increases the noise. Klipper implements a mechanism to ensure +there is always some movement at a cruising speed between acceleration +and deceleration. This is done by reducing the top speed of some moves +(or sequence of moves) to ensure there is a minimum distance traveled +at cruising speed relative to the distance traveled during +acceleration and deceleration. + +Klipper implements this feature by tracking both a regular move +acceleration as well as a virtual "acceleration to deceleration" rate: ![smoothed](img/smoothed.svg.png) Specifically, the code calculates what the velocity of each move would be if it were limited to this virtual "acceleration to deceleration" -rate (half the normal acceleration rate by default). In the above -picture the dashed gray lines represent this virtual acceleration rate -for the first move. If a move can not reach its full cruising speed -using this virtual acceleration rate then its top speed is reduced to -the maximum speed it could obtain at this virtual acceleration -rate. For most moves the limit will be at or above the move's existing +rate. In the above picture the dashed gray lines represent this +virtual acceleration rate for the first move. If a move can not reach +its full cruising speed using this virtual acceleration rate then its +top speed is reduced to the maximum speed it could obtain at this +virtual acceleration rate. + +For most moves the limit will be at or above the move's existing limits and no change in behavior is induced. For short zigzag moves, however, this limit reduces the top speed. Note that it does not change the actual acceleration within the move - the move continues to diff --git a/docs/Measuring_Resonances.md b/docs/Measuring_Resonances.md index 28cda9d0c656..d5f7f54cc537 100644 --- a/docs/Measuring_Resonances.md +++ b/docs/Measuring_Resonances.md @@ -207,7 +207,7 @@ software dependencies not installed by default. First, run on your Raspberry Pi the following commands: ``` sudo apt update -sudo apt install python3-numpy python3-matplotlib libatlas-base-dev +sudo apt install python3-numpy python3-matplotlib libatlas-base-dev libopenblas-dev ``` Next, in order to install NumPy in the Klipper environment, run the command: @@ -450,7 +450,11 @@ TEST_RESONANCES AXIS=Y ``` This will generate 2 CSV files (`/tmp/resonances_x_*.csv` and `/tmp/resonances_y_*.csv`). These files can be processed with the stand-alone -script on a Raspberry Pi. To do that, run the following commands: +script on a Raspberry Pi. This script is intended to be run with a single CSV +file for each axis measured, although it can be used with multiple CSV files +if you desire to average the results. Averaging results can be useful, for +example, if resonance tests were done at multiple test points. Delete the extra +CSV files if you do not desire to average them. ``` ~/klipper/scripts/calibrate_shaper.py /tmp/resonances_x_*.csv -o /tmp/shaper_calibrate_x.png ~/klipper/scripts/calibrate_shaper.py /tmp/resonances_y_*.csv -o /tmp/shaper_calibrate_y.png @@ -662,6 +666,19 @@ The same notice applies to the input shaper `max_accel` value after the auto-calibration, and the suggested acceleration limits will not be applied automatically. +Keep in mind that the maximum acceleration without too much smoothing depends +on the `square_corner_velocity`. The general recommendation is not to change +it from its default value 5.0, and this is the value used by default by the +`calibrate_shaper.py` script. If you did change it though, you should inform +the script about it by passing `--square_corner_velocity=...` parameter, e.g. +``` +~/klipper/scripts/calibrate_shaper.py /tmp/resonances_x_*.csv -o /tmp/shaper_calibrate_x.png --square_corner_velocity=10.0 +``` +so that it can calculate the maximum acceleration recommendations correctly. +Note that the `SHAPER_CALIBRATE` command already takes the configured +`square_corner_velocity` parameter into account, and there is no need +to specify it explicitly. + If you are doing a shaper re-calibration and the reported smoothing for the suggested shaper configuration is almost the same as what you got during the previous calibration, this step can be skipped. diff --git a/docs/Overview.md b/docs/Overview.md index 477bc68b7530..2b9253c26e67 100644 --- a/docs/Overview.md +++ b/docs/Overview.md @@ -99,3 +99,4 @@ communication with the Klipper developers. troubleshooting CAN bus. - [TSL1401CL filament width sensor](TSL1401CL_Filament_Width_Sensor.md) - [Hall filament width sensor](Hall_Filament_Width_Sensor.md) +- [Eddy Current Inductive probe](Eddy_Probe.md) diff --git a/docs/Probe_Calibrate.md b/docs/Probe_Calibrate.md index ed3964e68923..5f1d44365d91 100644 --- a/docs/Probe_Calibrate.md +++ b/docs/Probe_Calibrate.md @@ -64,7 +64,7 @@ automatic probe point, then `ABORT` the manual probe tool and perform the XY probe offset calibration described above. Once the manual probe tool starts, follow the steps described at -["the paper test"](Bed_Level.md#the-paper-test)) to determine the +["the paper test"](Bed_Level.md#the-paper-test) to determine the actual distance between the nozzle and bed at the given location. Once those steps are complete one can `ACCEPT` the position and save the results to the config file with: diff --git a/docs/Resonance_Compensation.md b/docs/Resonance_Compensation.md index aaeaf7abf054..8f2d2b643c86 100644 --- a/docs/Resonance_Compensation.md +++ b/docs/Resonance_Compensation.md @@ -48,8 +48,8 @@ First, measure the **ringing frequency**. to 5.0. It is not advised to increase it when using input shaper because it can cause more smoothing in parts - it is better to use higher acceleration value instead. -2. Increase `max_accel_to_decel` by issuing the following command: - `SET_VELOCITY_LIMIT ACCEL_TO_DECEL=7000` +2. Disable the `minimum_cruise_ratio` feature by issuing the following + command: `SET_VELOCITY_LIMIT MINIMUM_CRUISE_RATIO=0` 3. Disable Pressure Advance: `SET_PRESSURE_ADVANCE ADVANCE=0` 4. If you have already added `[input_shaper]` section to the printer.cfg, execute `SET_INPUT_SHAPER SHAPER_FREQ_X=0 SHAPER_FREQ_Y=0` command. If you @@ -149,7 +149,7 @@ a few other related parameters. Print the ringing test model as follows: 1. Restart the firmware: `RESTART` -2. Prepare for test: `SET_VELOCITY_LIMIT ACCEL_TO_DECEL=7000` +2. Prepare for test: `SET_VELOCITY_LIMIT MINIMUM_CRUISE_RATIO=0` 3. Disable Pressure Advance: `SET_PRESSURE_ADVANCE ADVANCE=0` 4. Execute: `SET_INPUT_SHAPER SHAPER_TYPE=MZV` 5. Execute the command: @@ -270,7 +270,7 @@ frequencies after enabling [input_shaper], this section will not help with that. Assuming that you have sliced the ringing model with suggested parameters, complete the following steps for each of the axes X and Y: -1. Prepare for test: `SET_VELOCITY_LIMIT ACCEL_TO_DECEL=7000` +1. Prepare for test: `SET_VELOCITY_LIMIT MINIMUM_CRUISE_RATIO=0` 2. Make sure Pressure Advance is disabled: `SET_PRESSURE_ADVANCE ADVANCE=0` 3. Execute: `SET_INPUT_SHAPER SHAPER_TYPE=ZV` 4. From the existing ringing test model with your chosen input shaper select @@ -331,7 +331,7 @@ with suggested parameters, print the test model 3 times as follows. First time, prior to printing, run 1. `RESTART` -2. `SET_VELOCITY_LIMIT ACCEL_TO_DECEL=7000` +2. `SET_VELOCITY_LIMIT MINIMUM_CRUISE_RATIO=0` 3. `SET_PRESSURE_ADVANCE ADVANCE=0` 4. `SET_INPUT_SHAPER SHAPER_TYPE=2HUMP_EI SHAPER_FREQ_X=60 SHAPER_FREQ_Y=60` 5. `TUNING_TOWER COMMAND=SET_VELOCITY_LIMIT PARAMETER=ACCEL START=1500 STEP_DELTA=500 STEP_HEIGHT=5` diff --git a/docs/Skew_Correction.md b/docs/Skew_Correction.md index 0475de89ae66..55bc22b0a5cc 100644 --- a/docs/Skew_Correction.md +++ b/docs/Skew_Correction.md @@ -21,7 +21,7 @@ or by issuing a `SET_SKEW CLEAR=1` gcode. ## Take your measurements -The `[skew_correcton]` module requires 3 measurements for each plane you want +The `[skew_correction]` module requires 3 measurements for each plane you want to correct; the length from Corner A to Corner C, the length from Corner B to Corner D, and the length from Corner A to Corner D. When measuring length AD do not include the flats on the corners that some test objects provide. diff --git a/docs/Status_Reference.md b/docs/Status_Reference.md index b64108ae25c0..66d840d16666 100644 --- a/docs/Status_Reference.md +++ b/docs/Status_Reference.md @@ -374,6 +374,13 @@ is defined): template expansion, the PROBE (or similar) command must be run prior to the macro containing this reference. +## pwm_cycle_time + +The following information is available in +[pwm_cycle_time some_name](Config_Reference.md#pwm_cycle_time) +objects: +- `value`: The "value" of the pin, as set by a `SET_PIN` command. + ## quad_gantry_level The following information is available in the `quad_gantry_level` object @@ -438,6 +445,7 @@ The following information is available in [bme280 config_section_name](Config_Reference.md#bmp280bme280bme680-temperature-sensor), [htu21d config_section_name](Config_Reference.md#htu21d-sensor), +[sht3x config_section_name](Config_Reference.md#sht31-sensor), [lm75 config_section_name](Config_Reference.md#lm75-temperature-sensor), [temperature_host config_section_name](Config_Reference.md#host-temperature-sensor) and @@ -445,7 +453,7 @@ and objects: - `temperature`: The last read temperature from the sensor. - `humidity`, `pressure`, `gas`: The last read values from the sensor - (only on bme280, htu21d, and lm75 sensors). + (only on bme280, htu21d, sht3x and lm75 sensors). ## temperature_fan @@ -504,7 +512,7 @@ The following information is available in the `toolhead` object limit value (eg, `axis_minimum.x`, `axis_maximum.z`). - For Delta printers the `cone_start_z` is the max z height at maximum radius (`printer.toolhead.cone_start_z`). -- `max_velocity`, `max_accel`, `max_accel_to_decel`, +- `max_velocity`, `max_accel`, `minimum_cruise_ratio`, `square_corner_velocity`: The current printing limits that are in effect. This may differ from the config file settings if a `SET_VELOCITY_LIMIT` (or `M204`) command alters them at run-time. diff --git a/docs/_klipper3d/mkdocs-requirements.txt b/docs/_klipper3d/mkdocs-requirements.txt index 9ea6d21924fe..96bf60051896 100644 --- a/docs/_klipper3d/mkdocs-requirements.txt +++ b/docs/_klipper3d/mkdocs-requirements.txt @@ -1,6 +1,6 @@ # Python virtualenv module requirements for mkdocs -jinja2==3.0.3 -mkdocs==1.2.3 +jinja2==3.1.4 +mkdocs==1.2.4 mkdocs-material==8.1.3 mkdocs-simple-hooks==0.1.3 mkdocs-exclude==1.0.2 diff --git a/docs/_klipper3d/mkdocs.yml b/docs/_klipper3d/mkdocs.yml index d290a45f5764..4bfdd169422a 100644 --- a/docs/_klipper3d/mkdocs.yml +++ b/docs/_klipper3d/mkdocs.yml @@ -71,7 +71,7 @@ extra: # https://squidfunk.github.io/mkdocs-material/setup/setting-up-site-analytics/#site-search-tracking analytics: provider: google - property: UA-138371409-1 + property: G-VEN1PGNQL4 # Language Selection alternate: - name: English @@ -138,4 +138,5 @@ nav: - CANBUS_Troubleshooting.md - TSL1401CL_Filament_Width_Sensor.md - Hall_Filament_Width_Sensor.md + - Eddy_Probe.md - Sponsors.md diff --git a/docs/img/adaptive_bed_mesh.svg b/docs/img/adaptive_bed_mesh.svg new file mode 100644 index 000000000000..954ca0b322c7 --- /dev/null +++ b/docs/img/adaptive_bed_mesh.svg @@ -0,0 +1,4 @@ + + + +
Origin
(0,0)
Origin...

Legend


Legend
Object Polygon
Object Polygon
Used Bed Area
Used Bed Area
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/img/adaptive_bed_mesh_margin.svg b/docs/img/adaptive_bed_mesh_margin.svg new file mode 100644 index 000000000000..6c6216d041aa --- /dev/null +++ b/docs/img/adaptive_bed_mesh_margin.svg @@ -0,0 +1,4 @@ + + + +
Origin
(0,0)
Origin...

Legend


Legend
Object Polygon
Object Polygon
Used Bed Area
Used Bed Area
Adapted Bed Mesh Area
Adapted Bed Mesh Area
(90,75)
(90,75)
(130,140)
(130,140)
(125,135)
(125,1...
(95,120)
(95,12...
(60,90)
(60,90)
(90,130)
(90,13...
(95,80)
(95,80)
(125,110)
(125,1...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/klippy/configfile.py b/klippy/configfile.py index f099b563406d..a8a4a4ff76f3 100644 --- a/klippy/configfile.py +++ b/klippy/configfile.py @@ -69,6 +69,8 @@ def getboolean(self, option, default=sentinel, note_valid=True): return self._get_wrapper(self.fileconfig.getboolean, option, default, note_valid=note_valid) def getchoice(self, option, choices, default=sentinel, note_valid=True): + if type(choices) == type([]): + choices = {i: i for i in choices} if choices and type(list(choices.keys())[0]) == int: c = self.getint(option, default, note_valid=note_valid) else: @@ -143,6 +145,8 @@ def __init__(self, printer): self.printer = printer self.autosave = None self.deprecated = {} + self.runtime_warnings = [] + self.deprecate_warnings = [] self.status_raw_config = {} self.status_save_pending = {} self.status_settings = {} @@ -189,7 +193,6 @@ def _find_autosave_data(self, data): comment_r = re.compile('[#;].*$') value_r = re.compile('[^A-Za-z0-9_].*$') def _strip_duplicates(self, data, config): - fileconfig = config.fileconfig # Comment out fields in 'data' that are defined in 'config' lines = data.split('\n') section = None @@ -314,6 +317,11 @@ def log_config(self, config): "======================="] self.printer.set_rollover_info("config", "\n".join(lines)) # Status reporting + def runtime_warning(self, msg): + logging.warning(msg) + res = {'type': 'runtime_warning', 'message': msg} + self.runtime_warnings.append(res) + self.status_warnings = self.runtime_warnings + self.deprecate_warnings def deprecate(self, section, option, value=None, msg=None): self.deprecated[(section, option, value)] = msg def _build_status(self, config): @@ -325,7 +333,7 @@ def _build_status(self, config): self.status_settings = {} for (section, option), value in config.access_tracking.items(): self.status_settings.setdefault(section, {})[option] = value - self.status_warnings = [] + self.deprecate_warnings = [] for (section, option, value), msg in self.deprecated.items(): if value is None: res = {'type': 'deprecated_option'} @@ -334,7 +342,8 @@ def _build_status(self, config): res['message'] = msg res['section'] = section res['option'] = option - self.status_warnings.append(res) + self.deprecate_warnings.append(res) + self.status_warnings = self.runtime_warnings + self.deprecate_warnings def get_status(self, eventtime): return {'config': self.status_raw_config, 'settings': self.status_settings, diff --git a/klippy/extras/adc_scaled.py b/klippy/extras/adc_scaled.py index c2d2cb877f78..80ea452f3109 100644 --- a/klippy/extras/adc_scaled.py +++ b/klippy/extras/adc_scaled.py @@ -7,7 +7,6 @@ SAMPLE_TIME = 0.001 SAMPLE_COUNT = 8 REPORT_TIME = 0.300 -RANGE_CHECK_COUNT = 4 class MCU_scaled_adc: def __init__(self, main, pin_params): @@ -18,7 +17,7 @@ def __init__(self, main, pin_params): qname = main.name + ":" + pin_params['pin'] query_adc.register_adc(qname, self._mcu_adc) self._callback = None - self.setup_minmax = self._mcu_adc.setup_minmax + self.setup_adc_sample = self._mcu_adc.setup_adc_sample self.get_mcu = self._mcu_adc.get_mcu def _handle_callback(self, read_time, read_value): max_adc = self._main.last_vref[1] @@ -54,8 +53,7 @@ def _config_pin(self, config, name, callback): ppins = self.printer.lookup_object('pins') mcu_adc = ppins.setup_pin('adc', pin_name) mcu_adc.setup_adc_callback(REPORT_TIME, callback) - mcu_adc.setup_minmax(SAMPLE_TIME, SAMPLE_COUNT, minval=0., maxval=1., - range_check_count=RANGE_CHECK_COUNT) + mcu_adc.setup_adc_sample(SAMPLE_TIME, SAMPLE_COUNT) query_adc = config.get_printer().load_object(config, 'query_adc') query_adc.register_adc(self.name + ":" + name, mcu_adc) return mcu_adc diff --git a/klippy/extras/adc_temperature.py b/klippy/extras/adc_temperature.py index b76e8c66fa85..c53ae7056adf 100644 --- a/klippy/extras/adc_temperature.py +++ b/klippy/extras/adc_temperature.py @@ -1,6 +1,6 @@ # Obtain temperature using linear interpolation of ADC values # -# Copyright (C) 2016-2018 Kevin O'Connor +# Copyright (C) 2016-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. import logging, bisect @@ -22,8 +22,8 @@ def __init__(self, config, adc_convert): ppins = config.get_printer().lookup_object('pins') self.mcu_adc = ppins.setup_pin('adc', config.get('sensor_pin')) self.mcu_adc.setup_adc_callback(REPORT_TIME, self.adc_callback) - query_adc = config.get_printer().load_object(config, 'query_adc') - query_adc.register_adc(config.get_name(), self.mcu_adc) + self.diag_helper = HelperTemperatureDiagnostics( + config, self.mcu_adc, adc_convert.calc_temp) def setup_callback(self, temperature_callback): self.temperature_callback = temperature_callback def get_report_time_delta(self): @@ -32,10 +32,44 @@ def adc_callback(self, read_time, read_value): temp = self.adc_convert.calc_temp(read_value) self.temperature_callback(read_time + SAMPLE_COUNT * SAMPLE_TIME, temp) def setup_minmax(self, min_temp, max_temp): - adc_range = [self.adc_convert.calc_adc(t) for t in [min_temp, max_temp]] - self.mcu_adc.setup_minmax(SAMPLE_TIME, SAMPLE_COUNT, - minval=min(adc_range), maxval=max(adc_range), - range_check_count=RANGE_CHECK_COUNT) + arange = [self.adc_convert.calc_adc(t) for t in [min_temp, max_temp]] + min_adc, max_adc = sorted(arange) + self.mcu_adc.setup_adc_sample(SAMPLE_TIME, SAMPLE_COUNT, + minval=min_adc, maxval=max_adc, + range_check_count=RANGE_CHECK_COUNT) + self.diag_helper.setup_diag_minmax(min_temp, max_temp, min_adc, max_adc) + +# Tool to register with query_adc and report extra info on ADC range errors +class HelperTemperatureDiagnostics: + def __init__(self, config, mcu_adc, calc_temp_cb): + self.printer = config.get_printer() + self.name = config.get_name() + self.mcu_adc = mcu_adc + self.calc_temp_cb = calc_temp_cb + self.min_temp = self.max_temp = self.min_adc = self.max_adc = None + query_adc = self.printer.load_object(config, 'query_adc') + query_adc.register_adc(self.name, self.mcu_adc) + error_mcu = self.printer.load_object(config, 'error_mcu') + error_mcu.add_clarify("ADC out of range", self._clarify_adc_range) + def setup_diag_minmax(self, min_temp, max_temp, min_adc, max_adc): + self.min_temp, self.max_temp = min_temp, max_temp + self.min_adc, self.max_adc = min_adc, max_adc + def _clarify_adc_range(self, msg, details): + if self.min_temp is None: + return None + last_value, last_read_time = self.mcu_adc.get_last_value() + if not last_read_time: + return None + if last_value >= self.min_adc and last_value <= self.max_adc: + return None + tempstr = "?" + try: + last_temp = self.calc_temp_cb(last_value) + tempstr = "%.3f" % (last_temp,) + except e: + logging.exception("Error in calc_temp callback") + return ("Sensor '%s' temperature %s not in range %.3f:%.3f" + % (self.name, tempstr, self.min_temp, self.max_temp)) ###################################################################### diff --git a/klippy/extras/adxl345.py b/klippy/extras/adxl345.py index 8f40c7fec9a6..bbc9e32b86cb 100644 --- a/klippy/extras/adxl345.py +++ b/klippy/extras/adxl345.py @@ -176,19 +176,14 @@ def cmd_ACCELEROMETER_DEBUG_WRITE(self, gcmd): self.chip.set_reg(reg, val) # Helper to read the axes_map parameter from the config -def read_axes_map(config): - am = {'x': (0, SCALE_XY), 'y': (1, SCALE_XY), 'z': (2, SCALE_Z), - '-x': (0, -SCALE_XY), '-y': (1, -SCALE_XY), '-z': (2, -SCALE_Z)} +def read_axes_map(config, scale_x, scale_y, scale_z): + am = {'x': (0, scale_x), 'y': (1, scale_y), 'z': (2, scale_z), + '-x': (0, -scale_x), '-y': (1, -scale_y), '-z': (2, -scale_z)} axes_map = config.getlist('axes_map', ('x','y','z'), count=3) if any([a not in am for a in axes_map]): raise config.error("Invalid axes_map parameter") return [am[a.strip()] for a in axes_map] -MIN_MSG_TIME = 0.100 - -BYTES_PER_SAMPLE = 5 -SAMPLES_PER_BLOCK = 10 - BATCH_UPDATES = 0.100 # Printer class that controls ADXL345 chip @@ -196,7 +191,7 @@ class ADXL345: def __init__(self, config): self.printer = config.get_printer() AccelCommandHelper(config, self) - self.axes_map = read_axes_map(config) + self.axes_map = read_axes_map(config, SCALE_XY, SCALE_XY, SCALE_Z) self.data_rate = config.getint('rate', 3200) if self.data_rate not in QUERY_RATES: raise config.error("Invalid rate parameter: %d" % (self.data_rate,)) @@ -204,19 +199,15 @@ def __init__(self, config): self.spi = bus.MCU_SPI_from_config(config, 3, default_speed=5000000) self.mcu = mcu = self.spi.get_mcu() self.oid = oid = mcu.create_oid() - self.query_adxl345_cmd = self.query_adxl345_end_cmd = None - self.query_adxl345_status_cmd = None + self.query_adxl345_cmd = None mcu.add_config_cmd("config_adxl345 oid=%d spi_oid=%d" % (oid, self.spi.get_oid())) - mcu.add_config_cmd("query_adxl345 oid=%d clock=0 rest_ticks=0" + mcu.add_config_cmd("query_adxl345 oid=%d rest_ticks=0" % (oid,), on_restart=True) mcu.register_config_callback(self._build_config) - self.bulk_queue = bulk_sensor.BulkDataQueue(mcu, "adxl345_data", oid) - # Clock tracking + # Bulk sample message reading chip_smooth = self.data_rate * BATCH_UPDATES * 2 - self.clock_sync = bulk_sensor.ClockSyncRegression(mcu, chip_smooth) - self.clock_updater = bulk_sensor.ChipClockUpdater(self.clock_sync, - BYTES_PER_SAMPLE) + self.ffreader = bulk_sensor.FixedFreqReader(mcu, chip_smooth, "BBBBB") self.last_error_count = 0 # Process messages in batches self.batch_bulk = bulk_sensor.BatchBulkHelper( @@ -229,15 +220,9 @@ def __init__(self, config): def _build_config(self): cmdqueue = self.spi.get_command_queue() self.query_adxl345_cmd = self.mcu.lookup_command( - "query_adxl345 oid=%c clock=%u rest_ticks=%u", cq=cmdqueue) - self.query_adxl345_end_cmd = self.mcu.lookup_query_command( - "query_adxl345 oid=%c clock=%u rest_ticks=%u", - "adxl345_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" - " buffered=%c fifo=%c limit_count=%hu", oid=self.oid, cq=cmdqueue) - self.query_adxl345_status_cmd = self.mcu.lookup_query_command( - "query_adxl345_status oid=%c", - "adxl345_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" - " buffered=%c fifo=%c limit_count=%hu", oid=self.oid, cq=cmdqueue) + "query_adxl345 oid=%c rest_ticks=%u", cq=cmdqueue) + self.ffreader.setup_query_command("query_adxl345_status oid=%c", + oid=self.oid, cq=cmdqueue) def read_reg(self, reg): params = self.spi.spi_transfer([reg | REG_MOD_READ, 0x00]) response = bytearray(params['response']) @@ -256,51 +241,24 @@ def start_internal_client(self): self.batch_bulk.add_client(aqh.handle_batch) return aqh # Measurement decoding - def _extract_samples(self, raw_samples): - # Load variables to optimize inner loop below + def _convert_samples(self, samples): (x_pos, x_scale), (y_pos, y_scale), (z_pos, z_scale) = self.axes_map - last_sequence = self.clock_updater.get_last_sequence() - time_base, chip_base, inv_freq = self.clock_sync.get_time_translation() - # Process every message in raw_samples - count = seq = 0 - samples = [None] * (len(raw_samples) * SAMPLES_PER_BLOCK) - for params in raw_samples: - seq_diff = (params['sequence'] - last_sequence) & 0xffff - seq_diff -= (seq_diff & 0x8000) << 1 - 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): - d_xyz = d[i*BYTES_PER_SAMPLE:(i+1)*BYTES_PER_SAMPLE] - xlow, ylow, zlow, xzhigh, yzhigh = d_xyz - if yzhigh & 0x80: - self.last_error_count += 1 - continue - rx = (xlow | ((xzhigh & 0x1f) << 8)) - ((xzhigh & 0x10) << 9) - ry = (ylow | ((yzhigh & 0x1f) << 8)) - ((yzhigh & 0x10) << 9) - rz = ((zlow | ((xzhigh & 0xe0) << 3) | ((yzhigh & 0xe0) << 6)) - - ((yzhigh & 0x40) << 7)) - raw_xyz = (rx, ry, rz) - x = round(raw_xyz[x_pos] * x_scale, 6) - y = round(raw_xyz[y_pos] * y_scale, 6) - z = round(raw_xyz[z_pos] * z_scale, 6) - ptime = round(time_base + (msg_cdiff + i) * inv_freq, 6) - samples[count] = (ptime, x, y, z) - count += 1 - self.clock_sync.set_last_chip_clock(seq * SAMPLES_PER_BLOCK + i) + count = 0 + for ptime, xlow, ylow, zlow, xzhigh, yzhigh in samples: + if yzhigh & 0x80: + self.last_error_count += 1 + continue + rx = (xlow | ((xzhigh & 0x1f) << 8)) - ((xzhigh & 0x10) << 9) + ry = (ylow | ((yzhigh & 0x1f) << 8)) - ((yzhigh & 0x10) << 9) + rz = ((zlow | ((xzhigh & 0xe0) << 3) | ((yzhigh & 0xe0) << 6)) + - ((yzhigh & 0x40) << 7)) + raw_xyz = (rx, ry, rz) + x = round(raw_xyz[x_pos] * x_scale, 6) + y = round(raw_xyz[y_pos] * y_scale, 6) + z = round(raw_xyz[z_pos] * z_scale, 6) + samples[count] = (round(ptime, 6), x, y, z) + count += 1 del samples[count:] - return samples - def _update_clock(self, minclock=0): - # Query current state - for retry in range(5): - params = self.query_adxl345_status_cmd.send([self.oid], - minclock=minclock) - fifo = params['fifo'] & 0x7f - if fifo <= 32: - break - else: - raise self.printer.command_error("Unable to query adxl345 fifo") - self.clock_updater.update_clock(params) # Start, stop, and process message batches def _start_measurements(self): # In case of miswiring, testing ADXL345 device ID prevents treating @@ -319,34 +277,26 @@ def _start_measurements(self): self.set_reg(REG_BW_RATE, QUERY_RATES[self.data_rate]) self.set_reg(REG_FIFO_CTL, SET_FIFO_CTL) # Start bulk reading - self.bulk_queue.clear_samples() - systime = self.printer.get_reactor().monotonic() - print_time = self.mcu.estimated_print_time(systime) + MIN_MSG_TIME - reqclock = self.mcu.print_time_to_clock(print_time) rest_ticks = self.mcu.seconds_to_clock(4. / self.data_rate) - self.query_adxl345_cmd.send([self.oid, reqclock, rest_ticks], - reqclock=reqclock) + self.query_adxl345_cmd.send([self.oid, rest_ticks]) + self.set_reg(REG_POWER_CTL, 0x08) logging.info("ADXL345 starting '%s' measurements", self.name) # Initialize clock tracking - self.clock_updater.note_start(reqclock) - self._update_clock(minclock=reqclock) - self.clock_updater.clear_duration_filter() + self.ffreader.note_start() self.last_error_count = 0 def _finish_measurements(self): # Halt bulk reading - params = self.query_adxl345_end_cmd.send([self.oid, 0, 0]) - self.bulk_queue.clear_samples() + self.set_reg(REG_POWER_CTL, 0x00) + self.query_adxl345_cmd.send_wait_ack([self.oid, 0]) + self.ffreader.note_end() logging.info("ADXL345 finished '%s' measurements", self.name) def _process_batch(self, eventtime): - self._update_clock() - raw_samples = self.bulk_queue.pull_samples() - if not raw_samples: - return {} - samples = self._extract_samples(raw_samples) + samples = self.ffreader.pull_samples() + self._convert_samples(samples) if not samples: return {} return {'data': samples, 'errors': self.last_error_count, - 'overflows': self.clock_updater.get_last_limit_count()} + 'overflows': self.ffreader.get_last_overflows()} def load_config(config): return ADXL345(config) diff --git a/klippy/extras/angle.py b/klippy/extras/angle.py index b1aa0d9679ae..c51d8bf0662d 100644 --- a/klippy/extras/angle.py +++ b/klippy/extras/angle.py @@ -412,7 +412,7 @@ def cmd_ANGLE_DEBUG_WRITE(self, gcmd): self._write_reg(reg, val) BYTES_PER_SAMPLE = 3 -SAMPLES_PER_BLOCK = 16 +SAMPLES_PER_BLOCK = bulk_sensor.MAX_BULK_MSG_SIZE // BYTES_PER_SAMPLE SAMPLE_PERIOD = 0.000400 BATCH_UPDATES = 0.100 @@ -437,7 +437,7 @@ def __init__(self, config): self.oid = oid = mcu.create_oid() self.sensor_helper = sensor_class(config, self.spi, oid) # Setup mcu sensor_spi_angle bulk query code - self.query_spi_angle_cmd = self.query_spi_angle_end_cmd = None + self.query_spi_angle_cmd = None mcu.add_config_cmd( "config_spi_angle oid=%d spi_oid=%d spi_angle_type=%s" % (oid, self.spi.get_oid(), sensor_type)) @@ -445,7 +445,7 @@ def __init__(self, config): "query_spi_angle oid=%d clock=0 rest_ticks=0 time_shift=0" % (oid,), on_restart=True) mcu.register_config_callback(self._build_config) - self.bulk_queue = bulk_sensor.BulkDataQueue(mcu, "spi_angle_data", oid) + self.bulk_queue = bulk_sensor.BulkDataQueue(mcu, oid=oid) # Process messages in batches self.batch_bulk = bulk_sensor.BatchBulkHelper( self.printer, self._process_batch, @@ -462,9 +462,6 @@ def _build_config(self): self.query_spi_angle_cmd = self.mcu.lookup_command( "query_spi_angle oid=%c clock=%u rest_ticks=%u time_shift=%c", cq=cmdqueue) - self.query_spi_angle_end_cmd = self.mcu.lookup_query_command( - "query_spi_angle oid=%c clock=%u rest_ticks=%u time_shift=%c", - "spi_angle_end oid=%c sequence=%hu", oid=self.oid, cq=cmdqueue) def get_status(self, eventtime=None): return {'temperature': self.sensor_helper.last_temperature} def add_client(self, client_cb): @@ -532,7 +529,7 @@ def _start_measurements(self): logging.info("Starting angle '%s' measurements", self.name) self.sensor_helper.start() # Start bulk reading - self.bulk_queue.clear_samples() + self.bulk_queue.clear_queue() self.last_sequence = 0 systime = self.printer.get_reactor().monotonic() print_time = self.mcu.estimated_print_time(systime) + MIN_MSG_TIME @@ -543,14 +540,14 @@ def _start_measurements(self): self.time_shift], reqclock=reqclock) def _finish_measurements(self): # Halt bulk reading - params = self.query_spi_angle_end_cmd.send([self.oid, 0, 0, 0]) - self.bulk_queue.clear_samples() + self.query_spi_angle_cmd.send_wait_ack([self.oid, 0, 0, 0]) + self.bulk_queue.clear_queue() self.sensor_helper.last_temperature = None logging.info("Stopped angle '%s' measurements", self.name) def _process_batch(self, eventtime): if self.sensor_helper.is_tcode_absolute: self.sensor_helper.update_clock() - raw_samples = self.bulk_queue.pull_samples() + raw_samples = self.bulk_queue.pull_queue() if not raw_samples: return {} samples, error_count = self._extract_samples(raw_samples) diff --git a/klippy/extras/axis_twist_compensation.py b/klippy/extras/axis_twist_compensation.py index ad08ad5593c7..e7aad52c047f 100644 --- a/klippy/extras/axis_twist_compensation.py +++ b/klippy/extras/axis_twist_compensation.py @@ -5,7 +5,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import math -from . import manual_probe as ManualProbe, bed_mesh as BedMesh +from . import manual_probe, bed_mesh, probe DEFAULT_SAMPLE_COUNT = 3 @@ -38,10 +38,13 @@ def __init__(self, config): # setup calibrater self.calibrater = Calibrater(self, config) + # register events + self.printer.register_event_handler("probe:update_results", + self._update_z_compensation_value) - def get_z_compensation_value(self, pos): + def _update_z_compensation_value(self, pos): if not self.z_compensations: - return 0 + return x_coord = pos[0] z_compensations = self.z_compensations @@ -50,12 +53,12 @@ def get_z_compensation_value(self, pos): / (sample_count - 1)) interpolate_t = (x_coord - self.calibrate_start_x) / spacing interpolate_i = int(math.floor(interpolate_t)) - interpolate_i = BedMesh.constrain(interpolate_i, 0, sample_count - 2) + interpolate_i = bed_mesh.constrain(interpolate_i, 0, sample_count - 2) interpolate_t -= interpolate_i - interpolated_z_compensation = BedMesh.lerp( + interpolated_z_compensation = bed_mesh.lerp( interpolate_t, z_compensations[interpolate_i], z_compensations[interpolate_i + 1]) - return interpolated_z_compensation + pos[2] += interpolated_z_compensation def clear_compensations(self): self.z_compensations = [] @@ -95,7 +98,7 @@ def _handle_connect(self): config = self.printer.lookup_object('configfile') raise config.error( "AXIS_TWIST_COMPENSATION requires [probe] to be defined") - self.lift_speed = self.probe.get_lift_speed() + self.lift_speed = self.probe.get_probe_params()['lift_speed'] self.probe_x_offset, self.probe_y_offset, _ = \ self.probe.get_offsets() @@ -134,7 +137,7 @@ def cmd_AXIS_TWIST_COMPENSATION_CALIBRATE(self, gcmd): nozzle_points, self.probe_x_offset, self.probe_y_offset) # verify no other manual probe is in progress - ManualProbe.verify_no_manual_probe(self.printer) + manual_probe.verify_no_manual_probe(self.printer) # begin calibration self.current_point_index = 0 @@ -186,7 +189,8 @@ def _calibration(self, probe_points, nozzle_points, interval): probe_points[self.current_point_index][1], None)) # probe the point - self.current_measured_z = self.probe.run_probe(self.gcmd)[2] + pos = probe.run_single_probe(self.probe, self.gcmd) + self.current_measured_z = pos[2] # horizontal_move_z (to prevent probe trigger or hitting bed) self._move_helper((None, None, self.horizontal_move_z)) @@ -195,7 +199,7 @@ def _calibration(self, probe_points, nozzle_points, interval): self._move_helper((nozzle_points[self.current_point_index])) # start the manual (nozzle) probe - ManualProbe.ManualProbeHelper( + manual_probe.ManualProbeHelper( self.printer, self.gcmd, self._manual_probe_callback_factory( probe_points, nozzle_points, interval)) diff --git a/klippy/extras/bed_mesh.py b/klippy/extras/bed_mesh.py index 6c714304fb8a..bcfd7c74ded1 100644 --- a/klippy/extras/bed_mesh.py +++ b/klippy/extras/bed_mesh.py @@ -1,6 +1,5 @@ # Mesh Bed Leveling # -# Copyright (C) 2018 Kevin O'Connor # Copyright (C) 2018-2019 Eric Callahan # # This file may be distributed under the terms of the GNU GPLv3 license. @@ -103,6 +102,7 @@ def __init__(self, config): self.log_fade_complete = False self.base_fade_target = config.getfloat('fade_target', None) self.fade_target = 0. + self.tool_offset = 0. self.gcode = self.printer.lookup_object('gcode') self.splitter = MoveSplitter(config, self.gcode) # setup persistent storage @@ -121,6 +121,11 @@ def __init__(self, config): self.gcode.register_command( 'BED_MESH_OFFSET', self.cmd_BED_MESH_OFFSET, desc=self.cmd_BED_MESH_OFFSET_help) + # Register dump webhooks + webhooks = self.printer.lookup_object('webhooks') + webhooks.register_endpoint( + "bed_mesh/dump_mesh", self._handle_dump_request + ) # Register transform gcode_move = self.printer.load_object(config, 'gcode_move') gcode_move.set_move_transform(self) @@ -158,6 +163,7 @@ def set_mesh(self, mesh): "mesh max: %.4f" % (self.fade_dist, min_z, max_z)) else: self.fade_target = 0. + self.tool_offset = 0. self.z_mesh = mesh self.splitter.initialize(mesh, self.fade_target) # cache the current position before a transform takes place @@ -165,6 +171,7 @@ def set_mesh(self, mesh): gcode_move.reset_last_position() self.update_status() def get_z_factor(self, z_pos): + z_pos += self.tool_offset if z_pos >= self.fade_end: return 0. elif z_pos >= self.fade_start: @@ -183,14 +190,15 @@ def get_position(self): max_adj = self.z_mesh.calc_z(x, y) factor = 1. z_adj = max_adj - self.fade_target - if min(z, (z - max_adj)) >= self.fade_end: + fade_z_pos = z + self.tool_offset + if min(fade_z_pos, (fade_z_pos - max_adj)) >= self.fade_end: # Fade out is complete, no factor factor = 0. - elif max(z, (z - max_adj)) >= self.fade_start: + elif max(fade_z_pos, (fade_z_pos - max_adj)) >= self.fade_start: # Likely in the process of fading out adjustment. # Because we don't yet know the gcode z position, use # algebra to calculate the factor from the toolhead pos - factor = ((self.fade_end + self.fade_target - z) / + factor = ((self.fade_end + self.fade_target - fade_z_pos) / (self.fade_dist - z_adj)) factor = constrain(factor, 0., 1.) final_z_adj = factor * z_adj + self.fade_target @@ -234,7 +242,7 @@ def update_status(self): mesh_max = (params['max_x'], params['max_y']) probed_matrix = self.z_mesh.get_probed_matrix() mesh_matrix = self.z_mesh.get_mesh_matrix() - self.status['profile_name'] = self.pmgr.get_current_profile() + self.status['profile_name'] = self.z_mesh.get_profile_name() self.status['mesh_min'] = mesh_min self.status['mesh_max'] = mesh_max self.status['probed_matrix'] = probed_matrix @@ -272,10 +280,38 @@ def cmd_BED_MESH_OFFSET(self, gcmd): for i, axis in enumerate(['X', 'Y']): offsets[i] = gcmd.get_float(axis, None) self.z_mesh.set_mesh_offsets(offsets) + tool_offset = gcmd.get_float("ZFADE", None) + if tool_offset is not None: + self.tool_offset = tool_offset gcode_move = self.printer.lookup_object('gcode_move') gcode_move.reset_last_position() else: gcmd.respond_info("No mesh loaded to offset") + def _handle_dump_request(self, web_request): + eventtime = self.printer.get_reactor().monotonic() + prb = self.printer.lookup_object("probe", None) + th_sts = self.printer.lookup_object("toolhead").get_status(eventtime) + result = {"current_mesh": {}, "profiles": self.pmgr.get_profiles()} + if self.z_mesh is not None: + result["current_mesh"] = { + "name": self.z_mesh.get_profile_name(), + "probed_matrix": self.z_mesh.get_probed_matrix(), + "mesh_matrix": self.z_mesh.get_mesh_matrix(), + "mesh_params": self.z_mesh.get_mesh_params() + } + mesh_args = web_request.get_dict("mesh_args", {}) + gcmd = None + if mesh_args: + gcmd = self.gcode.create_gcode_command("", "", mesh_args) + with self.gcode.get_mutex(): + result["calibration"] = self.bmc.dump_calibration(gcmd) + else: + result["calibration"] = self.bmc.dump_calibration() + offsets = [0, 0, 0] if prb is None else prb.get_offsets() + result["probe_offsets"] = offsets + result["axis_minimum"] = th_sts["axis_minimum"] + result["axis_maximum"] = th_sts["axis_maximum"] + web_request.send(result) class ZrefMode: @@ -291,149 +327,25 @@ def __init__(self, config, bedmesh): self.orig_config = {'radius': None, 'origin': None} self.radius = self.origin = None self.mesh_min = self.mesh_max = (0., 0.) - self.zero_ref_pos = config.getfloatlist( - "zero_reference_position", None, count=2 - ) - self.relative_reference_index = config.getint( - 'relative_reference_index', None, minval=0) - config.deprecate('relative_reference_index') - if ( - self.zero_ref_pos is not None and - self.relative_reference_index is not None - ): - self.relative_reference_index = None - logging.info( - "bed_mesh: both 'zero_reference_postion' and " - "'relative_reference_index' options are specified, " - "the 'zero_reference_position' value will be used." - ) - self.zero_reference_mode = ZrefMode.DISABLED - self.faulty_regions = [] - self.substituted_indices = collections.OrderedDict() + self.adaptive_margin = config.getfloat('adaptive_margin', 0.0) self.bedmesh = bedmesh self.mesh_config = collections.OrderedDict() self._init_mesh_config(config) - self._generate_points(config.error) - self._profile_name = None - self.probe_helper = probe.ProbePointsHelper( - config, self.probe_finalize, self._get_adjusted_points()) - self.probe_helper.minimum_points(3) - self.probe_helper.use_xy_offsets(True) + self.probe_mgr = ProbeManager( + config, self.orig_config, self.probe_finalize + ) + try: + self.probe_mgr.generate_points( + self.mesh_config, self.mesh_min, self.mesh_max, + self.radius, self.origin + ) + except BedMeshError as e: + raise config.error(str(e)) + self._profile_name = "default" self.gcode = self.printer.lookup_object('gcode') self.gcode.register_command( 'BED_MESH_CALIBRATE', self.cmd_BED_MESH_CALIBRATE, desc=self.cmd_BED_MESH_CALIBRATE_help) - def _generate_points(self, error): - x_cnt = self.mesh_config['x_count'] - y_cnt = self.mesh_config['y_count'] - min_x, min_y = self.mesh_min - max_x, max_y = self.mesh_max - x_dist = (max_x - min_x) / (x_cnt - 1) - y_dist = (max_y - min_y) / (y_cnt - 1) - # floor distances down to next hundredth - x_dist = math.floor(x_dist * 100) / 100 - y_dist = math.floor(y_dist * 100) / 100 - if x_dist < 1. or y_dist < 1.: - raise error("bed_mesh: min/max points too close together") - - if self.radius is not None: - # round bed, min/max needs to be recalculated - y_dist = x_dist - new_r = (x_cnt // 2) * x_dist - min_x = min_y = -new_r - max_x = max_y = new_r - else: - # rectangular bed, only re-calc max_x - max_x = min_x + x_dist * (x_cnt - 1) - pos_y = min_y - points = [] - for i in range(y_cnt): - for j in range(x_cnt): - if not i % 2: - # move in positive directon - pos_x = min_x + j * x_dist - else: - # move in negative direction - pos_x = max_x - j * x_dist - if self.radius is None: - # rectangular bed, append - points.append((pos_x, pos_y)) - else: - # round bed, check distance from origin - dist_from_origin = math.sqrt(pos_x*pos_x + pos_y*pos_y) - if dist_from_origin <= self.radius: - points.append( - (self.origin[0] + pos_x, self.origin[1] + pos_y)) - pos_y += y_dist - self.points = points - rri = self.relative_reference_index - if self.zero_ref_pos is None and rri is not None: - # Zero ref position needs to be initialized - if rri >= len(self.points): - raise error("bed_mesh: relative reference index out of range") - self.zero_ref_pos = points[rri] - if self.zero_ref_pos is None: - # Zero Reference Disabled - self.zero_reference_mode = ZrefMode.DISABLED - elif within(self.zero_ref_pos, self.mesh_min, self.mesh_max): - # Zero Reference position within mesh - self.zero_reference_mode = ZrefMode.IN_MESH - else: - # Zero Reference position outside of mesh - self.zero_reference_mode = ZrefMode.PROBE - if not self.faulty_regions: - return - self.substituted_indices.clear() - if self.zero_reference_mode == ZrefMode.PROBE: - # Cannot probe a reference within a faulty region - for min_c, max_c in self.faulty_regions: - if within(self.zero_ref_pos, min_c, max_c): - opt = "zero_reference_position" - if self.relative_reference_index is not None: - opt = "relative_reference_index" - raise error( - "bed_mesh: Cannot probe zero reference position at " - "(%.2f, %.2f) as it is located within a faulty region." - " Check the value for option '%s'" - % (self.zero_ref_pos[0], self.zero_ref_pos[1], opt,) - ) - # Check to see if any points fall within faulty regions - last_y = self.points[0][1] - is_reversed = False - for i, coord in enumerate(self.points): - if not isclose(coord[1], last_y): - is_reversed = not is_reversed - last_y = coord[1] - adj_coords = [] - for min_c, max_c in self.faulty_regions: - if within(coord, min_c, max_c, tol=.00001): - # Point lies within a faulty region - adj_coords = [ - (min_c[0], coord[1]), (coord[0], min_c[1]), - (coord[0], max_c[1]), (max_c[0], coord[1])] - if is_reversed: - # Swap first and last points for zig-zag pattern - first = adj_coords[0] - adj_coords[0] = adj_coords[-1] - adj_coords[-1] = first - break - if not adj_coords: - # coord is not located within a faulty region - continue - valid_coords = [] - for ac in adj_coords: - # make sure that coordinates are within the mesh boundary - if self.radius is None: - if within(ac, (min_x, min_y), (max_x, max_y), .000001): - valid_coords.append(ac) - else: - dist_from_origin = math.sqrt(ac[0]*ac[0] + ac[1]*ac[1]) - if dist_from_origin <= self.radius: - valid_coords.append(ac) - if not valid_coords: - raise error("bed_mesh: Unable to generate coordinates" - " for faulty region at index: %d" % (i)) - self.substituted_indices[i] = valid_coords def print_generated_points(self, print_func): x_offset = y_offset = 0. probe = self.printer.lookup_object('probe', None) @@ -441,27 +353,23 @@ def print_generated_points(self, print_func): x_offset, y_offset = probe.get_offsets()[:2] print_func("bed_mesh: generated points\nIndex" " | Tool Adjusted | Probe") - for i, (x, y) in enumerate(self.points): + points = self.probe_mgr.get_base_points() + for i, (x, y) in enumerate(points): adj_pt = "(%.1f, %.1f)" % (x - x_offset, y - y_offset) mesh_pt = "(%.1f, %.1f)" % (x, y) print_func( " %-4d| %-16s| %s" % (i, adj_pt, mesh_pt)) - if self.zero_ref_pos is not None: - rri = self.relative_reference_index - if rri is not None: - print_func( - "bed_mesh: relative_reference_index %d is (%.2f, %.2f)" - % (rri, self.zero_ref_pos[0], self.zero_ref_pos[1]) - ) - else: - print_func( - "bed_mesh: zero_reference_position is (%.2f, %.2f)" - % (self.zero_ref_pos[0], self.zero_ref_pos[1]) - ) - if self.substituted_indices: + zero_ref_pos = self.probe_mgr.get_zero_ref_pos() + if zero_ref_pos is not None: + print_func( + "bed_mesh: zero_reference_position is (%.2f, %.2f)" + % (zero_ref_pos[0], zero_ref_pos[1]) + ) + substitutes = self.probe_mgr.get_substitutes() + if substitutes: print_func("bed_mesh: faulty region points") - for i, v in self.substituted_indices.items(): - pt = self.points[i] + for i, v in substitutes.items(): + pt = points[i] print_func("%d (%.2f, %.2f), substituted points: %s" % (i, pt[0], pt[1], repr(v))) def _init_mesh_config(self, config): @@ -500,42 +408,6 @@ def _init_mesh_config(self, config): config.get('algorithm', 'lagrange').strip().lower() orig_cfg['tension'] = mesh_cfg['tension'] = config.getfloat( 'bicubic_tension', .2, minval=0., maxval=2.) - for i in list(range(1, 100, 1)): - start = config.getfloatlist("faulty_region_%d_min" % (i,), None, - count=2) - if start is None: - break - end = config.getfloatlist("faulty_region_%d_max" % (i,), count=2) - # Validate the corners. If necessary reorganize them. - # c1 = min point, c3 = max point - # c4 ---- c3 - # | | - # c1 ---- c2 - c1 = [min([s, e]) for s, e in zip(start, end)] - c3 = [max([s, e]) for s, e in zip(start, end)] - c2 = [c1[0], c3[1]] - c4 = [c3[0], c1[1]] - # Check for overlapping regions - for j, (prev_c1, prev_c3) in enumerate(self.faulty_regions): - prev_c2 = [prev_c1[0], prev_c3[1]] - prev_c4 = [prev_c3[0], prev_c1[1]] - # Validate that no existing corner is within the new region - for coord in [prev_c1, prev_c2, prev_c3, prev_c4]: - if within(coord, c1, c3): - raise config.error( - "bed_mesh: Existing faulty_region_%d %s overlaps " - "added faulty_region_%d %s" - % (j+1, repr([prev_c1, prev_c3]), - i, repr([c1, c3]))) - # Validate that no new corner is within an existing region - for coord in [c1, c2, c3, c4]: - if within(coord, prev_c1, prev_c3): - raise config.error( - "bed_mesh: Added faulty_region_%d %s overlaps " - "existing faulty_region_%d %s" - % (i, repr([c1, c3]), - j+1, repr([prev_c1, prev_c3]))) - self.faulty_regions.append((c1, c3)) self._verify_algorithm(config.error) def _verify_algorithm(self, error): params = self.mesh_config @@ -573,6 +445,116 @@ def _verify_algorithm(self, error): "interpolation. Configured Probe Count: %d, %d" % (self.mesh_config['x_count'], self.mesh_config['y_count'])) params['algo'] = 'lagrange' + def set_adaptive_mesh(self, gcmd): + if not gcmd.get_int('ADAPTIVE', 0): + return False + exclude_objects = self.printer.lookup_object("exclude_object", None) + if exclude_objects is None: + gcmd.respond_info("Exclude objects not enabled. Using full mesh...") + return False + objects = exclude_objects.get_status().get("objects", []) + if not objects: + return False + margin = gcmd.get_float('ADAPTIVE_MARGIN', self.adaptive_margin) + + # List all exclude_object points by axis and iterate over + # all polygon points, and pick the min and max or each axis + list_of_xs = [] + list_of_ys = [] + gcmd.respond_info("Found %s objects" % (len(objects))) + for obj in objects: + for point in obj["polygon"]: + list_of_xs.append(point[0]) + list_of_ys.append(point[1]) + + # Define bounds of adaptive mesh area + mesh_min = [min(list_of_xs), min(list_of_ys)] + mesh_max = [max(list_of_xs), max(list_of_ys)] + adjusted_mesh_min = [x - margin for x in mesh_min] + adjusted_mesh_max = [x + margin for x in mesh_max] + + # Force margin to respect original mesh bounds + adjusted_mesh_min[0] = max(adjusted_mesh_min[0], + self.orig_config["mesh_min"][0]) + adjusted_mesh_min[1] = max(adjusted_mesh_min[1], + self.orig_config["mesh_min"][1]) + adjusted_mesh_max[0] = min(adjusted_mesh_max[0], + self.orig_config["mesh_max"][0]) + adjusted_mesh_max[1] = min(adjusted_mesh_max[1], + self.orig_config["mesh_max"][1]) + + adjusted_mesh_size = (adjusted_mesh_max[0] - adjusted_mesh_min[0], + adjusted_mesh_max[1] - adjusted_mesh_min[1]) + + # Compute a ratio between the adapted and original sizes + ratio = (adjusted_mesh_size[0] / + (self.orig_config["mesh_max"][0] - + self.orig_config["mesh_min"][0]), + adjusted_mesh_size[1] / + (self.orig_config["mesh_max"][1] - + self.orig_config["mesh_min"][1])) + + gcmd.respond_info("Original mesh bounds: (%s,%s)" % + (self.orig_config["mesh_min"], + self.orig_config["mesh_max"])) + gcmd.respond_info("Original probe count: (%s,%s)" % + (self.mesh_config["x_count"], + self.mesh_config["y_count"])) + gcmd.respond_info("Adapted mesh bounds: (%s,%s)" % + (adjusted_mesh_min, adjusted_mesh_max)) + gcmd.respond_info("Ratio: (%s, %s)" % ratio) + + new_x_probe_count = int( + math.ceil(self.mesh_config["x_count"] * ratio[0])) + new_y_probe_count = int( + math.ceil(self.mesh_config["y_count"] * ratio[1])) + + # There is one case, where we may have to adjust the probe counts: + # axis0 < 4 and axis1 > 6 (see _verify_algorithm). + min_num_of_probes = 3 + if max(new_x_probe_count, new_y_probe_count) > 6 and \ + min(new_x_probe_count, new_y_probe_count) < 4: + min_num_of_probes = 4 + + new_x_probe_count = max(min_num_of_probes, new_x_probe_count) + new_y_probe_count = max(min_num_of_probes, new_y_probe_count) + + gcmd.respond_info("Adapted probe count: (%s,%s)" % + (new_x_probe_count, new_y_probe_count)) + + # If the adapted mesh size is too small, adjust it to something + # useful. + adjusted_mesh_size = (max(adjusted_mesh_size[0], new_x_probe_count), + max(adjusted_mesh_size[1], new_y_probe_count)) + + if self.radius is not None: + adapted_radius = math.sqrt((adjusted_mesh_size[0] ** 2) + + (adjusted_mesh_size[1] ** 2)) / 2 + adapted_origin = (adjusted_mesh_min[0] + + (adjusted_mesh_size[0] / 2), + adjusted_mesh_min[1] + + (adjusted_mesh_size[1] / 2)) + to_adapted_origin = math.sqrt(adapted_origin[0]**2 + + adapted_origin[1]**2) + # If the adapted mesh size is smaller than the default/full + # mesh, adjust the parameters. Otherwise, just do the full mesh. + if adapted_radius + to_adapted_origin < self.radius: + self.radius = adapted_radius + self.origin = adapted_origin + self.mesh_min = (-self.radius, -self.radius) + self.mesh_max = (self.radius, self.radius) + new_probe_count = max(new_x_probe_count, new_y_probe_count) + # Adaptive meshes require odd number of points + new_probe_count += 1 - (new_probe_count % 2) + self.mesh_config["x_count"] = self.mesh_config["y_count"] = \ + new_probe_count + else: + self.mesh_min = adjusted_mesh_min + self.mesh_max = adjusted_mesh_max + self.mesh_config["x_count"] = new_x_probe_count + self.mesh_config["y_count"] = new_y_probe_count + self._profile_name = None + return True def update_config(self, gcmd): # reset default configuration self.radius = self.orig_config['radius'] @@ -612,55 +594,65 @@ def update_config(self, gcmd): self.mesh_config['y_count'] = y_cnt need_cfg_update = True + if "MESH_PPS" in params: + xpps, ypps = parse_gcmd_pair(gcmd, 'MESH_PPS', minval=0) + self.mesh_config['mesh_x_pps'] = xpps + self.mesh_config['mesh_y_pps'] = ypps + need_cfg_update = True + if "ALGORITHM" in params: self.mesh_config['algo'] = gcmd.get('ALGORITHM').strip().lower() need_cfg_update = True + need_cfg_update |= self.set_adaptive_mesh(gcmd) + probe_method = gcmd.get("METHOD", "automatic") + if need_cfg_update: self._verify_algorithm(gcmd.error) - self._generate_points(gcmd.error) + self.probe_mgr.generate_points( + self.mesh_config, self.mesh_min, self.mesh_max, + self.radius, self.origin, probe_method + ) gcmd.respond_info("Generating new points...") self.print_generated_points(gcmd.respond_info) - pts = self._get_adjusted_points() - self.probe_helper.update_probe_points(pts, 3) - msg = "relative_reference_index: %s\n" % \ - (self.relative_reference_index) - msg += "\n".join(["%s: %s" % (k, v) for k, v - in self.mesh_config.items()]) + msg = "\n".join(["%s: %s" % (k, v) + for k, v in self.mesh_config.items()]) logging.info("Updated Mesh Configuration:\n" + msg) else: - self._generate_points(gcmd.error) - pts = self._get_adjusted_points() - self.probe_helper.update_probe_points(pts, 3) - def _get_adjusted_points(self): - adj_pts = [] - if self.substituted_indices: - last_index = 0 - for i, pts in self.substituted_indices.items(): - adj_pts.extend(self.points[last_index:i]) - adj_pts.extend(pts) - # Add one to the last index to skip the point - # we are replacing - last_index = i + 1 - adj_pts.extend(self.points[last_index:]) - else: - adj_pts = list(self.points) - if self.zero_reference_mode == ZrefMode.PROBE: - adj_pts.append(self.zero_ref_pos) - return adj_pts + self.probe_mgr.generate_points( + self.mesh_config, self.mesh_min, self.mesh_max, + self.radius, self.origin, probe_method + ) + def dump_calibration(self, gcmd=None): + if gcmd is not None and gcmd.get_command_parameters(): + self.update_config(gcmd) + cfg = dict(self.mesh_config) + cfg["mesh_min"] = self.mesh_min + cfg["mesh_max"] = self.mesh_max + cfg["origin"] = self.origin + cfg["radius"] = self.radius + return { + "points": self.probe_mgr.get_base_points(), + "config": cfg, + "probe_path": self.probe_mgr.get_std_path(), + "rapid_path": list(self.probe_mgr.iter_rapid_path()) + } cmd_BED_MESH_CALIBRATE_help = "Perform Mesh Bed Leveling" def cmd_BED_MESH_CALIBRATE(self, gcmd): self._profile_name = gcmd.get('PROFILE', "default") if not self._profile_name.strip(): raise gcmd.error("Value for parameter 'PROFILE' must be specified") self.bedmesh.set_mesh(None) - self.update_config(gcmd) - self.probe_helper.start_probe(gcmd) + try: + self.update_config(gcmd) + except BedMeshError as e: + raise gcmd.error(str(e)) + self.probe_mgr.start_probe(gcmd) def probe_finalize(self, offsets, positions): - x_offset, y_offset, z_offset = offsets + z_offset = offsets[2] positions = [[round(p[0], 2), round(p[1], 2), p[2]] for p in positions] - if self.zero_reference_mode == ZrefMode.PROBE : + if self.probe_mgr.get_zero_ref_mode() == ZrefMode.PROBE: ref_pos = positions.pop() logging.info( "bed_mesh: z-offset replaced with probed z value at " @@ -668,23 +660,26 @@ def probe_finalize(self, offsets, positions): % (ref_pos[0], ref_pos[1], ref_pos[2]) ) z_offset = ref_pos[2] + base_points = self.probe_mgr.get_base_points() params = dict(self.mesh_config) - params['min_x'] = min(positions, key=lambda p: p[0])[0] + x_offset - params['max_x'] = max(positions, key=lambda p: p[0])[0] + x_offset - params['min_y'] = min(positions, key=lambda p: p[1])[1] + y_offset - params['max_y'] = max(positions, key=lambda p: p[1])[1] + y_offset + params['min_x'] = min(base_points, key=lambda p: p[0])[0] + params['max_x'] = max(base_points, key=lambda p: p[0])[0] + params['min_y'] = min(base_points, key=lambda p: p[1])[1] + params['max_y'] = max(base_points, key=lambda p: p[1])[1] x_cnt = params['x_count'] y_cnt = params['y_count'] - if self.substituted_indices: + substitutes = self.probe_mgr.get_substitutes() + probed_pts = positions + if substitutes: # Replace substituted points with the original generated # point. Its Z Value is the average probed Z of the # substituted points. corrected_pts = [] idx_offset = 0 start_idx = 0 - for i, pts in self.substituted_indices.items(): - fpt = [p - o for p, o in zip(self.points[i], offsets[:2])] + for i, pts in substitutes.items(): + fpt = [p - o for p, o in zip(base_points[i], offsets[:2])] # offset the index to account for additional samples idx = i + idx_offset # Add "normal" points @@ -700,38 +695,42 @@ def probe_finalize(self, offsets, positions): % (i, fpt[0], fpt[1], avg_z, avg_z - z_offset)) corrected_pts.append(fpt) corrected_pts.extend(positions[start_idx:]) - # validate corrected positions - if len(self.points) != len(corrected_pts): - self._dump_points(positions, corrected_pts, offsets) - raise self.gcode.error( - "bed_mesh: invalid position list size, " - "generated count: %d, probed count: %d" - % (len(self.points), len(corrected_pts))) - for gen_pt, probed in zip(self.points, corrected_pts): - off_pt = [p - o for p, o in zip(gen_pt, offsets[:2])] - if not isclose(off_pt[0], probed[0], abs_tol=.1) or \ - not isclose(off_pt[1], probed[1], abs_tol=.1): - self._dump_points(positions, corrected_pts, offsets) - raise self.gcode.error( - "bed_mesh: point mismatch, orig = (%.2f, %.2f)" - ", probed = (%.2f, %.2f)" - % (off_pt[0], off_pt[1], probed[0], probed[1])) positions = corrected_pts + # validate length of result + if len(base_points) != len(positions): + self._dump_points(probed_pts, positions, offsets) + raise self.gcode.error( + "bed_mesh: invalid position list size, " + "generated count: %d, probed count: %d" + % (len(base_points), len(positions)) + ) + probed_matrix = [] row = [] - prev_pos = positions[0] - for pos in positions: + prev_pos = base_points[0] + for pos, result in zip(base_points, positions): + offset_pos = [p - o for p, o in zip(pos, offsets[:2])] + if ( + not isclose(offset_pos[0], result[0], abs_tol=.5) or + not isclose(offset_pos[1], result[1], abs_tol=.5) + ): + logging.info( + "bed_mesh: point deviation > .5mm: orig pt = (%.2f, %.2f)" + ", probed pt = (%.2f, %.2f)" + % (offset_pos[0], offset_pos[1], result[0], result[1]) + ) + z_pos = result[2] - z_offset if not isclose(pos[1], prev_pos[1], abs_tol=.1): # y has changed, append row and start new probed_matrix.append(row) row = [] if pos[0] > prev_pos[0]: # probed in the positive direction - row.append(pos[2] - z_offset) + row.append(z_pos) else: # probed in the negative direction - row.insert(0, pos[2] - z_offset) + row.insert(0, z_pos) prev_pos = pos # append last row probed_matrix.append(row) @@ -769,30 +768,33 @@ def probe_finalize(self, offsets, positions): "Probed table length: %d Probed Table:\n%s") % (len(probed_matrix), str(probed_matrix))) - z_mesh = ZMesh(params) + z_mesh = ZMesh(params, self._profile_name) try: z_mesh.build_mesh(probed_matrix) except BedMeshError as e: raise self.gcode.error(str(e)) - if self.zero_reference_mode == ZrefMode.IN_MESH: + if self.probe_mgr.get_zero_ref_mode() == ZrefMode.IN_MESH: # The reference can be anywhere in the mesh, therefore # it is necessary to set the reference after the initial mesh # is generated to lookup the correct z value. - z_mesh.set_zero_reference(*self.zero_ref_pos) + zero_ref_pos = self.probe_mgr.get_zero_ref_pos() + z_mesh.set_zero_reference(*zero_ref_pos) self.bedmesh.set_mesh(z_mesh) self.gcode.respond_info("Mesh Bed Leveling Complete") - self.bedmesh.save_profile(self._profile_name) + if self._profile_name is not None: + self.bedmesh.save_profile(self._profile_name) def _dump_points(self, probed_pts, corrected_pts, offsets): # logs generated points with offset applied, points received # from the finalize callback, and the list of corrected points - max_len = max([len(self.points), len(probed_pts), len(corrected_pts)]) + points = self.probe_mgr.get_base_points() + max_len = max([len(points), len(probed_pts), len(corrected_pts)]) logging.info( "bed_mesh: calibration point dump\nIndex | %-17s| %-25s|" " Corrected Point" % ("Generated Point", "Probed Point")) for i in list(range(max_len)): gen_pt = probed_pt = corr_pt = "" - if i < len(self.points): - off_pt = [p - o for p, o in zip(self.points[i], offsets[:2])] + if i < len(points): + off_pt = [p - o for p, o in zip(points[i], offsets[:2])] gen_pt = "(%.2f, %.2f)" % tuple(off_pt) if i < len(probed_pts): probed_pt = "(%.2f, %.2f, %.4f)" % tuple(probed_pts[i]) @@ -801,6 +803,453 @@ def _dump_points(self, probed_pts, corrected_pts, offsets): logging.info( " %-4d| %-17s| %-25s| %s" % (i, gen_pt, probed_pt, corr_pt)) +class ProbeManager: + def __init__(self, config, orig_config, finalize_cb): + self.printer = config.get_printer() + self.cfg_overshoot = config.getfloat("scan_overshoot", 0, minval=1.) + self.orig_config = orig_config + self.faulty_regions = [] + self.overshoot = self.cfg_overshoot + self.zero_ref_pos = config.getfloatlist( + "zero_reference_position", None, count=2 + ) + self.zref_mode = ZrefMode.DISABLED + self.base_points = [] + self.substitutes = collections.OrderedDict() + self.is_round = orig_config["radius"] is not None + self.probe_helper = probe.ProbePointsHelper(config, finalize_cb, []) + self.probe_helper.use_xy_offsets(True) + self.rapid_scan_helper = RapidScanHelper(config, self, finalize_cb) + self._init_faulty_regions(config) + + def _init_faulty_regions(self, config): + for i in list(range(1, 100, 1)): + start = config.getfloatlist("faulty_region_%d_min" % (i,), None, + count=2) + if start is None: + break + end = config.getfloatlist("faulty_region_%d_max" % (i,), count=2) + # Validate the corners. If necessary reorganize them. + # c1 = min point, c3 = max point + # c4 ---- c3 + # | | + # c1 ---- c2 + c1 = [min([s, e]) for s, e in zip(start, end)] + c3 = [max([s, e]) for s, e in zip(start, end)] + c2 = [c1[0], c3[1]] + c4 = [c3[0], c1[1]] + # Check for overlapping regions + for j, (prev_c1, prev_c3) in enumerate(self.faulty_regions): + prev_c2 = [prev_c1[0], prev_c3[1]] + prev_c4 = [prev_c3[0], prev_c1[1]] + # Validate that no existing corner is within the new region + for coord in [prev_c1, prev_c2, prev_c3, prev_c4]: + if within(coord, c1, c3): + raise config.error( + "bed_mesh: Existing faulty_region_%d %s overlaps " + "added faulty_region_%d %s" + % (j+1, repr([prev_c1, prev_c3]), + i, repr([c1, c3]))) + # Validate that no new corner is within an existing region + for coord in [c1, c2, c3, c4]: + if within(coord, prev_c1, prev_c3): + raise config.error( + "bed_mesh: Added faulty_region_%d %s overlaps " + "existing faulty_region_%d %s" + % (i, repr([c1, c3]), + j+1, repr([prev_c1, prev_c3]))) + self.faulty_regions.append((c1, c3)) + + def start_probe(self, gcmd): + method = gcmd.get("METHOD", "automatic").lower() + can_scan = False + pprobe = self.printer.lookup_object("probe", None) + if pprobe is not None: + probe_name = pprobe.get_status(None).get("name", "") + can_scan = probe_name.startswith("probe_eddy_current") + if method == "rapid_scan" and can_scan: + self.rapid_scan_helper.perform_rapid_scan(gcmd) + else: + self.probe_helper.start_probe(gcmd) + + def get_zero_ref_pos(self): + return self.zero_ref_pos + + def get_zero_ref_mode(self): + return self.zref_mode + + def get_substitutes(self): + return self.substitutes + + def generate_points( + self, mesh_config, mesh_min, mesh_max, radius, origin, + probe_method="automatic" + ): + x_cnt = mesh_config['x_count'] + y_cnt = mesh_config['y_count'] + min_x, min_y = mesh_min + max_x, max_y = mesh_max + x_dist = (max_x - min_x) / (x_cnt - 1) + y_dist = (max_y - min_y) / (y_cnt - 1) + # floor distances down to next hundredth + x_dist = math.floor(x_dist * 100) / 100 + y_dist = math.floor(y_dist * 100) / 100 + if x_dist < 1. or y_dist < 1.: + raise BedMeshError("bed_mesh: min/max points too close together") + + if radius is not None: + # round bed, min/max needs to be recalculated + y_dist = x_dist + new_r = (x_cnt // 2) * x_dist + min_x = min_y = -new_r + max_x = max_y = new_r + else: + # rectangular bed, only re-calc max_x + max_x = min_x + x_dist * (x_cnt - 1) + pos_y = min_y + points = [] + for i in range(y_cnt): + for j in range(x_cnt): + if not i % 2: + # move in positive directon + pos_x = min_x + j * x_dist + else: + # move in negative direction + pos_x = max_x - j * x_dist + if radius is None: + # rectangular bed, append + points.append((pos_x, pos_y)) + else: + # round bed, check distance from origin + dist_from_origin = math.sqrt(pos_x*pos_x + pos_y*pos_y) + if dist_from_origin <= radius: + points.append( + (origin[0] + pos_x, origin[1] + pos_y)) + pos_y += y_dist + if self.zero_ref_pos is None or probe_method == "manual": + # Zero Reference Disabled + self.zref_mode = ZrefMode.DISABLED + elif within(self.zero_ref_pos, mesh_min, mesh_max): + # Zero Reference position within mesh + self.zref_mode = ZrefMode.IN_MESH + else: + # Zero Reference position outside of mesh + self.zref_mode = ZrefMode.PROBE + self.base_points = points + self.substitutes.clear() + # adjust overshoot + og_min_x = self.orig_config["mesh_min"][0] + og_max_x = self.orig_config["mesh_max"][0] + add_ovs = min(max(0, min_x - og_min_x), max(0, og_max_x - max_x)) + self.overshoot = self.cfg_overshoot + math.floor(add_ovs) + min_pt, max_pt = (min_x, min_y), (max_x, max_y) + self._process_faulty_regions(min_pt, max_pt, radius) + self.probe_helper.update_probe_points(self.get_std_path(), 3) + + def _process_faulty_regions(self, min_pt, max_pt, radius): + if not self.faulty_regions: + return + # Cannot probe a reference within a faulty region + if self.zref_mode == ZrefMode.PROBE: + for min_c, max_c in self.faulty_regions: + if within(self.zero_ref_pos, min_c, max_c): + opt = "zero_reference_position" + raise BedMeshError( + "bed_mesh: Cannot probe zero reference position at " + "(%.2f, %.2f) as it is located within a faulty region." + " Check the value for option '%s'" + % (self.zero_ref_pos[0], self.zero_ref_pos[1], opt,) + ) + # Check to see if any points fall within faulty regions + last_y = self.base_points[0][1] + is_reversed = False + for i, coord in enumerate(self.base_points): + if not isclose(coord[1], last_y): + is_reversed = not is_reversed + last_y = coord[1] + adj_coords = [] + for min_c, max_c in self.faulty_regions: + if within(coord, min_c, max_c, tol=.00001): + # Point lies within a faulty region + adj_coords = [ + (min_c[0], coord[1]), (coord[0], min_c[1]), + (coord[0], max_c[1]), (max_c[0], coord[1])] + if is_reversed: + # Swap first and last points for zig-zag pattern + first = adj_coords[0] + adj_coords[0] = adj_coords[-1] + adj_coords[-1] = first + break + if not adj_coords: + # coord is not located within a faulty region + continue + valid_coords = [] + for ac in adj_coords: + # make sure that coordinates are within the mesh boundary + if radius is None: + if within(ac, min_pt, max_pt, .000001): + valid_coords.append(ac) + else: + dist_from_origin = math.sqrt(ac[0]*ac[0] + ac[1]*ac[1]) + if dist_from_origin <= radius: + valid_coords.append(ac) + if not valid_coords: + raise BedMeshError( + "bed_mesh: Unable to generate coordinates" + " for faulty region at index: %d" % (i) + ) + self.substitutes[i] = valid_coords + + def get_base_points(self): + return self.base_points + + def get_std_path(self): + path = [] + for idx, pt in enumerate(self.base_points): + if idx in self.substitutes: + for sub_pt in self.substitutes[idx]: + path.append(sub_pt) + else: + path.append(pt) + if self.zref_mode == ZrefMode.PROBE: + path.append(self.zero_ref_pos) + return path + + def iter_rapid_path(self): + ascnd_x = True + last_base_pt = last_mv_pt = self.base_points[0] + # Generate initial move point + if self.overshoot: + overshoot = min(8, self.overshoot) + last_mv_pt = (last_base_pt[0] - overshoot, last_base_pt[1]) + yield last_mv_pt, False + for idx, pt in enumerate(self.base_points): + # increasing Y indicates direction change + dir_change = not isclose(pt[1], last_base_pt[1], abs_tol=1e-6) + if idx in self.substitutes: + fp_gen = self._gen_faulty_path( + last_mv_pt, idx, ascnd_x, dir_change + ) + for sub_pt, is_smp in fp_gen: + yield sub_pt, is_smp + last_mv_pt = sub_pt + else: + if dir_change: + for dpt in self._gen_dir_change(last_mv_pt, pt, ascnd_x): + yield dpt, False + yield pt, True + last_mv_pt = pt + last_base_pt = pt + ascnd_x ^= dir_change + if self.zref_mode == ZrefMode.PROBE: + if self.overshoot: + ovs = min(4, self.overshoot) + ovs = ovs if ascnd_x else -ovs + yield (last_mv_pt[0] + ovs, last_mv_pt[1]), False + yield self.zero_ref_pos, True + + def _gen_faulty_path(self, last_pt, idx, ascnd_x, dir_change): + subs = self.substitutes[idx] + sub_cnt = len(subs) + if dir_change: + for dpt in self._gen_dir_change(last_pt, subs[0], ascnd_x): + yield dpt, False + if self.is_round: + # No faulty region path handling for round beds + for pt in subs: + yield pt, True + return + # Check to see if this is the first corner + first_corner = False + sorted_sub_idx = sorted(self.substitutes.keys()) + if sub_cnt == 2 and idx < len(sorted_sub_idx): + first_corner = sorted_sub_idx[idx] == idx + yield subs[0], True + if sub_cnt == 1: + return + last_pt, next_pt = subs[:2] + if sub_cnt == 2: + if first_corner or dir_change: + # horizontal move first + yield (next_pt[0], last_pt[1]), False + else: + yield (last_pt[0], next_pt[1]), False + yield next_pt, True + elif sub_cnt >= 3: + if dir_change: + # first move should be a vertical switch up. If overshoot + # is available, simulate another direction change. Otherwise + # move inward 2 mm, then up through the faulty region. + if self.overshoot: + for dpt in self._gen_dir_change(last_pt, next_pt, ascnd_x): + yield dpt, False + else: + shift = -2 if ascnd_x else 2 + yield (last_pt[0] + shift, last_pt[1]), False + yield (last_pt[0] + shift, next_pt[1]), False + yield next_pt, True + last_pt, next_pt = subs[1:3] + else: + # vertical move + yield (last_pt[0], next_pt[1]), False + yield next_pt, True + last_pt, next_pt = subs[1:3] + if sub_cnt == 4: + # Vertical switch up within faulty region + shift = 2 if ascnd_x else -2 + yield (last_pt[0] + shift, last_pt[1]), False + yield (next_pt[0] - shift, next_pt[1]), False + yield next_pt, True + last_pt, next_pt = subs[2:4] + # horizontal move before final point + yield (next_pt[0], last_pt[1]), False + yield next_pt, True + + def _gen_dir_change(self, last_pt, next_pt, ascnd_x): + if not self.overshoot: + return + # overshoot X beyond the outer point + xdir = 1 if ascnd_x else -1 + overshoot = 2. if self.overshoot >= 3. else self.overshoot + ovr_pt = (last_pt[0] + overshoot * xdir, last_pt[1]) + yield ovr_pt + if self.overshoot < 3.: + # No room to generate an arc, move up to next y + yield (next_pt[0] + overshoot * xdir, next_pt[1]) + else: + # generate arc + STEP_ANGLE = 3 + START_ANGLE = 270 + ydiff = abs(next_pt[1] - last_pt[1]) + xdiff = abs(next_pt[0] - last_pt[0]) + max_radius = min(self.overshoot - 2, 8) + radius = min(ydiff / 2, max_radius) + origin = [ovr_pt[0], last_pt[1] + radius] + next_origin_y = next_pt[1] - radius + # determine angle + if xdiff < .01: + # Move is aligned on the x-axis + angle = 90 + if next_origin_y - origin[1] < .05: + # The move can be completed in a single arc + angle = 180 + else: + angle = int(math.degrees(math.atan(ydiff / xdiff))) + if ( + (ascnd_x and next_pt[0] < last_pt[0]) or + (not ascnd_x and next_pt[0] > last_pt[0]) + ): + angle = 180 - angle + count = int(angle // STEP_ANGLE) + # Gen first arc + step = STEP_ANGLE * xdir + start = START_ANGLE + step + for arc_pt in self._gen_arc(origin, radius, start, step, count): + yield arc_pt + if angle == 180: + # arc complete + return + # generate next arc + origin = [next_pt[0] + overshoot * xdir, next_origin_y] + # start at the angle where the last arc finished + start = START_ANGLE + count * step + # recalculate the count to make sure we generate a full 180 + # degrees. Add a step for the repeated connecting angle + count = 61 - count + for arc_pt in self._gen_arc(origin, radius, start, step, count): + yield arc_pt + + def _gen_arc(self, origin, radius, start, step, count): + end = start + step * count + # create a segent for every 3 degress of travel + for angle in range(start, end, step): + rad = math.radians(angle % 360) + opp = math.sin(rad) * radius + adj = math.cos(rad) * radius + yield (origin[0] + adj, origin[1] + opp) + + +MAX_HIT_DIST = 2. +MM_WIN_SPEED = 125 + +class RapidScanHelper: + def __init__(self, config, probe_mgr, finalize_cb): + self.printer = config.get_printer() + self.probe_manager = probe_mgr + self.speed = config.getfloat("speed", 50., above=0.) + self.scan_height = config.getfloat("horizontal_move_z", 5.) + self.finalize_callback = finalize_cb + + def perform_rapid_scan(self, gcmd): + speed = gcmd.get_float("SCAN_SPEED", self.speed) + scan_height = gcmd.get_float("HORIZONTAL_MOVE_Z", self.scan_height) + gcmd.respond_info( + "Beginning rapid surface scan at height %.2f..." % (scan_height) + ) + pprobe = self.printer.lookup_object("probe") + toolhead = self.printer.lookup_object("toolhead") + # Calculate time window around which a sample is valid. Current + # assumption is anything within 2mm is usable, so: + # window = 2 / max_speed + # + # TODO: validate maximum speed allowed based on sample rate of probe + # Scale the hit distance window for speeds lower than 125mm/s. The + # lower the speed the less the window shrinks. + scale = max(0, 1 - speed / MM_WIN_SPEED) + 1 + hit_dist = min(MAX_HIT_DIST, scale * speed / MM_WIN_SPEED) + half_window = hit_dist / speed + gcmd.respond_info( + "Sample hit distance +/- %.4fmm, time window +/- ms %.4f" + % (hit_dist, half_window * 1000) + ) + gcmd_params = gcmd.get_command_parameters() + gcmd_params["SAMPLE_TIME"] = half_window * 2 + self._raise_tool(gcmd, scan_height) + probe_session = pprobe.start_probe_session(gcmd) + offsets = pprobe.get_offsets() + initial_move = True + for pos, is_probe_pt in self.probe_manager.iter_rapid_path(): + pos = self._apply_offsets(pos[:2], offsets) + toolhead.manual_move(pos, speed) + if initial_move: + initial_move = False + self._move_to_scan_height(gcmd, scan_height) + if is_probe_pt: + probe_session.run_probe(gcmd) + results = probe_session.pull_probed_results() + toolhead.get_last_move_time() + self.finalize_callback(offsets, results) + probe_session.end_probe_session() + + def _raise_tool(self, gcmd, scan_height): + # If the nozzle is below scan height raise the tool + toolhead = self.printer.lookup_object("toolhead") + pprobe = self.printer.lookup_object("probe") + cur_pos = toolhead.get_position() + if cur_pos[2] >= scan_height: + return + pparams = pprobe.get_probe_params(gcmd) + lift_speed = pparams["lift_speed"] + cur_pos[2] = self.scan_height + .5 + toolhead.manual_move(cur_pos, lift_speed) + + def _move_to_scan_height(self, gcmd, scan_height): + time_window = gcmd.get_float("SAMPLE_TIME") + toolhead = self.printer.lookup_object("toolhead") + pprobe = self.printer.lookup_object("probe") + cur_pos = toolhead.get_position() + pparams = pprobe.get_probe_params(gcmd) + lift_speed = pparams["lift_speed"] + probe_speed = pparams["probe_speed"] + cur_pos[2] = scan_height + .5 + toolhead.manual_move(cur_pos, lift_speed) + cur_pos[2] = scan_height + toolhead.manual_move(cur_pos, probe_speed) + toolhead.dwell(time_window / 2 + .01) + + def _apply_offsets(self, point, offsets): + return [(pos - ofs) for pos, ofs in zip(point, offsets)] + class MoveSplitter: def __init__(self, config, gcode): @@ -867,7 +1316,8 @@ def split(self): class ZMesh: - def __init__(self, params): + def __init__(self, params, name): + self.profile_name = name or "adaptive-%X" % (id(self),) self.probed_matrix = self.mesh_matrix = None self.mesh_params = params self.mesh_offsets = [0., 0.] @@ -916,6 +1366,8 @@ def get_probed_matrix(self): return [[]] def get_mesh_params(self): return self.mesh_params + def get_profile_name(self): + return self.profile_name def print_probed_matrix(self, print_func): if self.probed_matrix is not None: msg = "Mesh Leveling Probed Z positions:\n" @@ -1176,7 +1628,6 @@ def __init__(self, config, bedmesh): self.gcode = self.printer.lookup_object('gcode') self.bedmesh = bedmesh self.profiles = {} - self.current_profile = "" self.incompatible_profiles = [] # Fetch stored profiles from Config stored_profs = config.get_prefix_sections(self.name) @@ -1210,8 +1661,6 @@ def __init__(self, config, bedmesh): desc=self.cmd_BED_MESH_PROFILE_help) def get_profiles(self): return self.profiles - def get_current_profile(self): - return self.current_profile def _check_incompatible_profiles(self): if self.incompatible_profiles: configfile = self.printer.lookup_object('configfile') @@ -1252,7 +1701,6 @@ def save_profile(self, prof_name): profile['points'] = probed_matrix profile['mesh_params'] = collections.OrderedDict(mesh_params) self.profiles = profiles - self.current_profile = prof_name self.bedmesh.update_status() self.gcode.respond_info( "Bed Mesh state has been saved to profile [%s]\n" @@ -1266,12 +1714,11 @@ def load_profile(self, prof_name): "bed_mesh: Unknown profile [%s]" % prof_name) probed_matrix = profile['points'] mesh_params = profile['mesh_params'] - z_mesh = ZMesh(mesh_params) + z_mesh = ZMesh(mesh_params, prof_name) try: z_mesh.build_mesh(probed_matrix) except BedMeshError as e: raise self.gcode.error(str(e)) - self.current_profile = prof_name self.bedmesh.set_mesh(z_mesh) def remove_profile(self, prof_name): if prof_name in self.profiles: diff --git a/klippy/extras/bltouch.py b/klippy/extras/bltouch.py index 48759752ef51..ae461f4b83be 100644 --- a/klippy/extras/bltouch.py +++ b/klippy/extras/bltouch.py @@ -1,6 +1,6 @@ # BLTouch support # -# Copyright (C) 2018-2021 Kevin O'Connor +# Copyright (C) 2018-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. import logging @@ -23,13 +23,9 @@ } # BLTouch "endstop" wrapper -class BLTouchEndstopWrapper: +class BLTouchProbe: def __init__(self, config): self.printer = config.get_printer() - self.printer.register_event_handler("klippy:connect", - self.handle_connect) - self.printer.register_event_handler('klippy:mcu_identify', - self.handle_mcu_identify) self.position_endstop = config.getfloat('z_offset', minval=0.) self.stow_on_each_sample = config.getboolean('stow_on_each_sample', True) @@ -44,12 +40,9 @@ def __init__(self, config): self.next_cmd_time = self.action_end_time = 0. self.finish_home_complete = self.wait_trigger_complete = None # Create an "endstop" object to handle the sensor pin - pin = config.get('sensor_pin') - pin_params = ppins.lookup_pin(pin, can_invert=True, can_pullup=True) - mcu = pin_params['chip'] - self.mcu_endstop = mcu.setup_pin('endstop', pin_params) + self.mcu_endstop = ppins.setup_pin('endstop', config.get('sensor_pin')) # output mode - omodes = {'5V': '5V', 'OD': 'OD', None: None} + omodes = ['5V', 'OD', None] self.output_mode = config.getchoice('set_output_mode', omodes, None) # Setup for sensor test self.next_test_time = 0. @@ -65,19 +58,30 @@ def __init__(self, config): self.get_steppers = self.mcu_endstop.get_steppers self.home_wait = self.mcu_endstop.home_wait self.query_endstop = self.mcu_endstop.query_endstop + # multi probes state + self.multi = 'OFF' + # Common probe implementation helpers + self.cmd_helper = probe.ProbeCommandHelper( + config, self, self.mcu_endstop.query_endstop) + self.probe_offsets = probe.ProbeOffsetsHelper(config) + self.probe_session = probe.ProbeSessionHelper(config, self) # Register BLTOUCH_DEBUG command self.gcode = self.printer.lookup_object('gcode') self.gcode.register_command("BLTOUCH_DEBUG", self.cmd_BLTOUCH_DEBUG, desc=self.cmd_BLTOUCH_DEBUG_help) self.gcode.register_command("BLTOUCH_STORE", self.cmd_BLTOUCH_STORE, desc=self.cmd_BLTOUCH_STORE_help) - # multi probes state - self.multi = 'OFF' - def handle_mcu_identify(self): - kin = self.printer.lookup_object('toolhead').get_kinematics() - for stepper in kin.get_steppers(): - if stepper.is_active_axis('z'): - self.add_stepper(stepper) + # Register events + self.printer.register_event_handler("klippy:connect", + self.handle_connect) + def get_probe_params(self, gcmd=None): + return self.probe_session.get_probe_params(gcmd) + def get_offsets(self): + return self.probe_offsets.get_offsets() + def get_status(self, eventtime): + return self.cmd_helper.get_status(eventtime) + def start_probe_session(self, gcmd): + return self.probe_session.start_probe_session(gcmd) def handle_connect(self): self.sync_mcu_print_time() self.next_cmd_time += 0.200 @@ -116,7 +120,11 @@ def verify_state(self, triggered): self.mcu_endstop.home_start(self.action_end_time, ENDSTOP_SAMPLE_TIME, ENDSTOP_SAMPLE_COUNT, ENDSTOP_REST_TIME, triggered=triggered) - trigger_time = self.mcu_endstop.home_wait(self.action_end_time + 0.100) + try: + trigger_time = self.mcu_endstop.home_wait( + self.action_end_time + 0.100) + except self.printer.command_error as e: + return False return trigger_time > 0. def raise_probe(self): self.sync_mcu_print_time() @@ -183,6 +191,9 @@ def multi_probe_end(self): self.verify_raise_probe() self.sync_print_time() self.multi = 'OFF' + def probing_move(self, pos, speed): + phoming = self.printer.lookup_object('homing') + return phoming.probing_move(self, pos, speed) def probe_prepare(self, hmove): if self.multi == 'OFF' or self.multi == 'FIRST': self.lower_probe() @@ -271,6 +282,6 @@ def cmd_BLTOUCH_STORE(self, gcmd): self.sync_print_time() def load_config(config): - blt = BLTouchEndstopWrapper(config) - config.get_printer().add_object('probe', probe.PrinterProbe(config, blt)) + blt = BLTouchProbe(config) + config.get_printer().add_object('probe', blt) return blt diff --git a/klippy/extras/bme280.py b/klippy/extras/bme280.py index 3bc3c471cb1b..262dc130f417 100644 --- a/klippy/extras/bme280.py +++ b/klippy/extras/bme280.py @@ -17,6 +17,29 @@ 'HUM_MSB': 0xFD, 'HUM_LSB': 0xFE, 'CAL_1': 0x88, 'CAL_2': 0xE1 } +BMP388_REGS = { + "CMD": 0x7E, + "STATUS": 0x03, + "PWR_CTRL": 0x1B, + "OSR": 0x1C, + "ORD": 0x1D, + "INT_CTRL": 0x19, + "CAL_1": 0x31, + "TEMP_MSB": 0x09, + "TEMP_LSB": 0x08, + "TEMP_XLSB": 0x07, + "PRESS_MSB": 0x06, + "PRESS_LSB": 0x05, + "PRESS_XLSB": 0x04, +} +BMP388_REG_VAL_PRESS_EN = 0x01 +BMP388_REG_VAL_TEMP_EN = 0x02 +BMP388_REG_VAL_PRESS_OS_NO = 0b000 +BMP388_REG_VAL_TEMP_OS_NO = 0b000000 +BMP388_REG_VAL_ODR_50_HZ = 0x02 +BMP388_REG_VAL_DRDY_EN = 0b100000 +BMP388_REG_VAL_NORMAL_MODE = 0x30 + BME680_REGS = { 'RESET': 0xE0, 'CTRL_HUM': 0x72, 'CTRL_GAS_1': 0x71, 'CTRL_GAS_0': 0x70, 'GAS_WAIT_0': 0x64, 'RES_HEAT_0': 0x5A, 'IDAC_HEAT_0': 0x50, @@ -68,9 +91,11 @@ RESET_CHIP_VALUE = 0xB6 BME_CHIPS = { - 0x58: 'BMP280', 0x60: 'BME280', 0x61: 'BME680', 0x55: 'BMP180' + 0x58: 'BMP280', 0x60: 'BME280', 0x61: 'BME680', 0x55: 'BMP180', + 0x50: 'BMP388' } BME_CHIP_ID_REG = 0xD0 +BMP3_CHIP_ID_REG = 0x00 def get_twos_complement(val, bit_size): @@ -163,6 +188,29 @@ def read_calibration_data_bmp280(calib_data_1): dig['P9'] = get_signed_short(calib_data_1[22:24]) return dig + def read_calibration_data_bmp388(calib_data_1): + dig = {} + dig["T1"] = get_unsigned_short(calib_data_1[0:2]) / 0.00390625 + dig["T2"] = get_unsigned_short(calib_data_1[2:4]) / 1073741824.0 + dig["T3"] = get_signed_byte(calib_data_1[4]) / 281474976710656.0 + + dig["P1"] = get_signed_short(calib_data_1[5:7]) - 16384 + dig["P1"] /= 1048576.0 + dig["P2"] = get_signed_short(calib_data_1[7:9]) - 16384 + dig["P2"] /= 536870912.0 + dig["P3"] = get_signed_byte(calib_data_1[9]) / 4294967296.0 + dig["P4"] = get_signed_byte(calib_data_1[10]) / 137438953472.0 + dig["P5"] = get_unsigned_short(calib_data_1[11:13]) / 0.125 + dig["P6"] = get_unsigned_short(calib_data_1[13:15]) / 64.0 + dig["P7"] = get_signed_byte(calib_data_1[15]) / 256.0 + dig["P8"] = get_signed_byte(calib_data_1[16]) / 32768.0 + dig["P9"] = get_signed_short(calib_data_1[17:19]) + dig["P9"] /= 281474976710656.0 + dig["P10"] = get_signed_byte(calib_data_1[19]) / 281474976710656.0 + dig["P11"] = get_signed_byte(calib_data_1[20]) + dig["P11"] /= 36893488147419103232.0 + return dig + def read_calibration_data_bme280(calib_data_1, calib_data_2): dig = read_calibration_data_bmp280(calib_data_1) dig['H1'] = calib_data_1[25] & 0xFF @@ -224,7 +272,7 @@ def read_calibration_data_bmp180(calib_data_1): dig['MD'] = get_signed_short_msb(calib_data_1[20:22]) return dig - chip_id = self.read_id() + chip_id = self.read_id() or self.read_bmp3_id() if chip_id not in BME_CHIPS.keys(): logging.info("bme280: Unknown Chip ID received %#x" % chip_id) else: @@ -252,6 +300,24 @@ def read_calibration_data_bmp180(calib_data_1): 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 + elif self.chip_type == 'BMP388': + self.max_sample_time = 0.5 + self.chip_registers = BMP388_REGS + self.write_register( + "PWR_CTRL", + [ + BMP388_REG_VAL_PRESS_EN + | BMP388_REG_VAL_TEMP_EN + | BMP388_REG_VAL_NORMAL_MODE + ], + ) + self.write_register( + "OSR", [BMP388_REG_VAL_PRESS_OS_NO | BMP388_REG_VAL_TEMP_OS_NO] + ) + self.write_register("ORD", [BMP388_REG_VAL_ODR_50_HZ]) + self.write_register("INT_CTRL", [BMP388_REG_VAL_DRDY_EN]) + + self.sample_timer = self.reactor.register_timer(self._sample_bmp388) else: self.max_sample_time = \ (1.25 + (2.3 * self.os_temp) + ((2.3 * self.os_pres) + .575) @@ -265,6 +331,8 @@ def read_calibration_data_bmp180(calib_data_1): # Read out and calculate the trimming parameters if self.chip_type == 'BMP180': cal_1 = self.read_register('CAL_1', 22) + elif self.chip_type == 'BMP388': + cal_1 = self.read_register('CAL_1', 21) else: cal_1 = self.read_register('CAL_1', 26) cal_2 = self.read_register('CAL_2', 16) @@ -276,6 +344,8 @@ def read_calibration_data_bmp180(calib_data_1): self.dig = read_calibration_data_bme680(cal_1, cal_2) elif self.chip_type == 'BMP180': self.dig = read_calibration_data_bmp180(cal_1) + elif self.chip_type == 'BMP388': + self.dig = read_calibration_data_bmp388(cal_1) def _sample_bme280(self, eventtime): # Enter forced mode @@ -318,6 +388,79 @@ def _sample_bme280(self, eventtime): self._callback(self.mcu.estimated_print_time(measured_time), self.temp) return measured_time + REPORT_TIME + def _sample_bmp388(self, eventtime): + status = self.read_register("STATUS", 1) + if status[0] & 0b100000: + self.temp = self._sample_bmp388_temp() + if self.temp < self.min_temp or self.temp > self.max_temp: + self.printer.invoke_shutdown( + "BME280 temperature %0.1f outside range of %0.1f:%.01f" + % (self.temp, self.min_temp, self.max_temp) + ) + + if status[0] & 0b010000: + self.pressure = self._sample_bmp388_press() / 100.0 + + measured_time = self.reactor.monotonic() + self._callback(self.mcu.estimated_print_time(measured_time), self.temp) + return measured_time + REPORT_TIME + + def _sample_bmp388_temp(self): + xlsb = self.read_register("TEMP_XLSB", 1) + lsb = self.read_register("TEMP_LSB", 1) + msb = self.read_register("TEMP_MSB", 1) + adc_T = (msb[0] << 16) + (lsb[0] << 8) + (xlsb[0]) + + partial_data1 = adc_T - self.dig["T1"] + partial_data2 = self.dig["T2"] * partial_data1 + + self.t_fine = partial_data2 + self.t_fine += (partial_data1 * partial_data1) * self.dig["T3"] + + if self.t_fine < -40.0: + self.t_fine = -40.0 + + if self.t_fine > 85.0: + self.t_fine = 85.0 + + return self.t_fine + + def _sample_bmp388_press(self): + xlsb = self.read_register("PRESS_XLSB", 1) + lsb = self.read_register("PRESS_LSB", 1) + msb = self.read_register("PRESS_MSB", 1) + adc_P = (msb[0] << 16) + (lsb[0] << 8) + (xlsb[0]) + + partial_data1 = self.dig["P6"] * self.t_fine + partial_data2 = self.dig["P7"] * (self.t_fine * self.t_fine) + partial_data3 = self.dig["P8"] + partial_data3 *= self.t_fine * self.t_fine * self.t_fine + partial_out1 = self.dig["P5"] + partial_out1 += partial_data1 + partial_data2 + partial_data3 + + partial_data1 = self.dig["P2"] * self.t_fine + partial_data2 = self.dig["P3"] * (self.t_fine * self.t_fine) + partial_data3 = self.dig["P4"] + partial_data3 *= (self.t_fine * self.t_fine * self.t_fine) + partial_out2 = adc_P * ( + self.dig["P1"] + partial_data1 + partial_data2 + partial_data3 + ) + + partial_data1 = adc_P * adc_P + partial_data2 = self.dig["P9"] + (self.dig["P10"] * self.t_fine) + partial_data3 = partial_data1 * partial_data2 + partial_data4 = partial_data3 + adc_P * adc_P * adc_P * self.dig["P11"] + + comp_press = partial_out1 + partial_out2 + partial_data4 + + if comp_press < 30000: + comp_press = 30000 + + if comp_press > 125000: + comp_press = 125000 + + return comp_press + def _sample_bme680(self, eventtime): self.write_register('CTRL_HUM', self.os_hum & 0x07) meas = self.os_temp << 5 | self.os_pres << 2 @@ -564,6 +707,12 @@ def read_id(self): params = self.i2c.i2c_read(regs, 1) return bytearray(params['response'])[0] + def read_bmp3_id(self): + # read chip id register + regs = [BMP3_CHIP_ID_REG] + params = self.i2c.i2c_read(regs, 1) + return bytearray(params['response'])[0] + def read_register(self, reg_name, read_len): # read a single register regs = [self.chip_registers[reg_name]] diff --git a/klippy/extras/bulk_sensor.py b/klippy/extras/bulk_sensor.py index 8d0c05416560..b0aa320d085a 100644 --- a/klippy/extras/bulk_sensor.py +++ b/klippy/extras/bulk_sensor.py @@ -3,7 +3,7 @@ # Copyright (C) 2020-2023 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import logging, threading +import logging, threading, struct # This "bulk sensor" module facilitates the processing of sensor chip # measurements that do not require the host to respond with low @@ -114,7 +114,7 @@ def handle_batch(self, msg): # Helper class to store incoming messages in a queue class BulkDataQueue: - def __init__(self, mcu, msg_name, oid): + def __init__(self, mcu, msg_name="sensor_bulk_data", oid=None): # Measurement storage (accessed from background thread) self.lock = threading.Lock() self.raw_samples = [] @@ -123,13 +123,13 @@ def __init__(self, mcu, msg_name, oid): def _handle_data(self, params): with self.lock: self.raw_samples.append(params) - def pull_samples(self): + def pull_queue(self): with self.lock: raw_samples = self.raw_samples self.raw_samples = [] return raw_samples - def clear_samples(self): - self.pull_samples() + def clear_queue(self): + self.pull_queue() ###################################################################### @@ -198,39 +198,54 @@ def get_time_translation(self): inv_freq = clock_to_print_time(base_mcu + inv_cfreq) - base_time return base_time, base_chip, inv_freq -MAX_BULK_MSG_SIZE = 52 +MAX_BULK_MSG_SIZE = 51 -# Handle common periodic chip status query responses -class ChipClockUpdater: - def __init__(self, clock_sync, bytes_per_sample): - self.clock_sync = clock_sync - self.bytes_per_sample = bytes_per_sample - self.samples_per_block = MAX_BULK_MSG_SIZE // bytes_per_sample - self.mcu = clock_sync.mcu +# Read sensor_bulk_data and calculate timestamps for devices that take +# samples at a fixed frequency (and produce fixed data size samples). +class FixedFreqReader: + def __init__(self, mcu, chip_clock_smooth, unpack_fmt): + self.mcu = mcu + self.clock_sync = ClockSyncRegression(mcu, chip_clock_smooth) + unpack = struct.Struct(unpack_fmt) + self.unpack_from = unpack.unpack_from + self.bytes_per_sample = unpack.size + self.samples_per_block = MAX_BULK_MSG_SIZE // self.bytes_per_sample self.last_sequence = self.max_query_duration = 0 - self.last_limit_count = 0 - def get_last_sequence(self): - return self.last_sequence - def get_last_limit_count(self): - return self.last_limit_count - def clear_duration_filter(self): + self.last_overflows = 0 + self.bulk_queue = self.oid = self.query_status_cmd = None + def setup_query_command(self, msgformat, oid, cq): + # Lookup sensor query command (that responds with sensor_bulk_status) + self.oid = oid + self.query_status_cmd = self.mcu.lookup_query_command( + msgformat, "sensor_bulk_status oid=%c clock=%u query_ticks=%u" + " next_sequence=%hu buffered=%u possible_overflows=%hu", + oid=oid, cq=cq) + # Read sensor_bulk_data messages and store in a queue + self.bulk_queue = BulkDataQueue(self.mcu, oid=oid) + def get_last_overflows(self): + return self.last_overflows + def _clear_duration_filter(self): self.max_query_duration = 1 << 31 - def note_start(self, reqclock): + def note_start(self): self.last_sequence = 0 - self.last_limit_count = 0 - self.clock_sync.reset(reqclock, 0) - self.clear_duration_filter() - def update_clock(self, params): - # Handle a status response message of the form: - # adxl345_status oid=x clock=x query_ticks=x next_sequence=x - # buffered=x fifo=x limit_count=x - fifo = params['fifo'] + self.last_overflows = 0 + # Clear local queue (clear any stale samples from previous session) + self.bulk_queue.clear_queue() + # Set initial clock + self._clear_duration_filter() + self._update_clock(is_reset=True) + self._clear_duration_filter() + def note_end(self): + # Clear local queue (free no longer needed memory) + self.bulk_queue.clear_queue() + def _update_clock(self, is_reset=False): + params = self.query_status_cmd.send([self.oid]) mcu_clock = self.mcu.clock32_to_clock64(params['clock']) seq_diff = (params['next_sequence'] - self.last_sequence) & 0xffff self.last_sequence += seq_diff buffered = params['buffered'] - lc_diff = (params['limit_count'] - self.last_limit_count) & 0xffff - self.last_limit_count += lc_diff + po_diff = (params['possible_overflows'] - self.last_overflows) & 0xffff + self.last_overflows += po_diff duration = params['query_ticks'] if duration > self.max_query_duration: # Skip measurement as a high query time could skew clock tracking @@ -239,9 +254,44 @@ def update_clock(self, params): return self.max_query_duration = 2 * duration msg_count = (self.last_sequence * self.samples_per_block - + buffered // self.bytes_per_sample + fifo) + + buffered // self.bytes_per_sample) # The "chip clock" is the message counter plus .5 for average # inaccuracy of query responses and plus .5 for assumed offset # of hardware processing time. chip_clock = msg_count + 1 - self.clock_sync.update(mcu_clock + duration // 2, chip_clock) + avg_mcu_clock = mcu_clock + duration // 2 + if is_reset: + self.clock_sync.reset(avg_mcu_clock, chip_clock) + else: + self.clock_sync.update(avg_mcu_clock, chip_clock) + # Convert sensor_bulk_data responses into list of samples + def pull_samples(self): + # Query MCU for sample timing and update clock synchronization + self._update_clock() + # Pull sensor_bulk_data messages from local queue + raw_samples = self.bulk_queue.pull_queue() + if not raw_samples: + return [] + # Load variables to optimize inner loop below + last_sequence = self.last_sequence + time_base, chip_base, inv_freq = self.clock_sync.get_time_translation() + unpack_from = self.unpack_from + bytes_per_sample = self.bytes_per_sample + samples_per_block = self.samples_per_block + # Process every message in raw_samples + count = seq = 0 + samples = [None] * (len(raw_samples) * samples_per_block) + for params in raw_samples: + seq_diff = (params['sequence'] - last_sequence) & 0xffff + seq_diff -= (seq_diff & 0x8000) << 1 + seq = last_sequence + seq_diff + msg_cdiff = seq * samples_per_block - chip_base + data = params['data'] + for i in range(len(data) // bytes_per_sample): + ptime = time_base + (msg_cdiff + i) * inv_freq + udata = unpack_from(data, i * bytes_per_sample) + samples[count] = (ptime,) + udata + count += 1 + self.clock_sync.set_last_chip_clock(seq * samples_per_block + i) + del samples[count:] + return samples diff --git a/klippy/extras/buttons.py b/klippy/extras/buttons.py index 70d76a60e175..daa998a93dcd 100644 --- a/klippy/extras/buttons.py +++ b/klippy/extras/buttons.py @@ -104,7 +104,7 @@ def __init__(self, printer, pin, pullup): self.max_value = 0. ppins = printer.lookup_object('pins') self.mcu_adc = ppins.setup_pin('adc', self.pin) - self.mcu_adc.setup_minmax(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT) + self.mcu_adc.setup_adc_sample(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT) self.mcu_adc.setup_adc_callback(ADC_REPORT_TIME, self.adc_callback) query_adc = printer.lookup_object('query_adc') query_adc.register_adc('adc_button:' + pin.strip(), self.mcu_adc) diff --git a/klippy/extras/display/hd44780.py b/klippy/extras/display/hd44780.py index 9adfa20f7992..2da49c51ed16 100644 --- a/klippy/extras/display/hd44780.py +++ b/klippy/extras/display/hd44780.py @@ -8,7 +8,7 @@ BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000 LINE_LENGTH_DEFAULT=20 -LINE_LENGTH_OPTIONS={16:16, 20:20} +LINE_LENGTH_OPTIONS=[16, 20] TextGlyphs = { 'right_arrow': b'\x7e' } diff --git a/klippy/extras/display/hd44780_spi.py b/klippy/extras/display/hd44780_spi.py index cd1d9e3ea773..f21accbb4291 100644 --- a/klippy/extras/display/hd44780_spi.py +++ b/klippy/extras/display/hd44780_spi.py @@ -9,7 +9,7 @@ from .. import bus LINE_LENGTH_DEFAULT=20 -LINE_LENGTH_OPTIONS={16:16, 20:20} +LINE_LENGTH_OPTIONS=[16, 20] TextGlyphs = { 'right_arrow': b'\x7e' } diff --git a/klippy/extras/display/menu_keys.py b/klippy/extras/display/menu_keys.py index 91a96e19fbaa..8094c9964ae9 100644 --- a/klippy/extras/display/menu_keys.py +++ b/klippy/extras/display/menu_keys.py @@ -18,7 +18,7 @@ def __init__(self, config, callback): # Register rotary encoder encoder_pins = config.get('encoder_pins', None) encoder_steps_per_detent = config.getchoice('encoder_steps_per_detent', - {2: 2, 4: 4}, 4) + [2, 4], 4) if encoder_pins is not None: try: pin1, pin2 = encoder_pins.split(',') diff --git a/klippy/extras/dotstar.py b/klippy/extras/dotstar.py index d75f438a795c..4186534fead6 100644 --- a/klippy/extras/dotstar.py +++ b/klippy/extras/dotstar.py @@ -10,7 +10,6 @@ class PrinterDotstar: def __init__(self, config): self.printer = printer = config.get_printer() - name = config.get_name().split()[1] # Configure a software spi bus ppins = printer.lookup_object('pins') data_pin_params = ppins.lookup_pin(config.get('data_pin')) diff --git a/klippy/extras/endstop_phase.py b/klippy/extras/endstop_phase.py index feb9e8b8b082..2c7468bce3e0 100644 --- a/klippy/extras/endstop_phase.py +++ b/klippy/extras/endstop_phase.py @@ -191,7 +191,6 @@ def cmd_ENDSTOP_PHASE_CALIBRATE(self, gcmd): def generate_stats(self, stepper_name, phase_calc): phase_history = phase_calc.phase_history wph = phase_history + phase_history - count = sum(phase_history) phases = len(phase_history) half_phases = phases // 2 res = [] diff --git a/klippy/extras/error_mcu.py b/klippy/extras/error_mcu.py new file mode 100644 index 000000000000..dc91c33a92c7 --- /dev/null +++ b/klippy/extras/error_mcu.py @@ -0,0 +1,133 @@ +# More verbose information on micro-controller errors +# +# Copyright (C) 2024 Kevin O'Connor +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import logging + +message_shutdown = """ +Once the underlying issue is corrected, use the +"FIRMWARE_RESTART" command to reset the firmware, reload the +config, and restart the host software. +Printer is shutdown +""" + +message_protocol_error1 = """ +This is frequently caused by running an older version of the +firmware on the MCU(s). Fix by recompiling and flashing the +firmware. +""" + +message_protocol_error2 = """ +Once the underlying issue is corrected, use the "RESTART" +command to reload the config and restart the host software. +""" + +message_mcu_connect_error = """ +Once the underlying issue is corrected, use the +"FIRMWARE_RESTART" command to reset the firmware, reload the +config, and restart the host software. +Error configuring printer +""" + +Common_MCU_errors = { + ("Timer too close",): """ +This often indicates the host computer is overloaded. Check +for other processes consuming excessive CPU time, high swap +usage, disk errors, overheating, unstable voltage, or +similar system problems on the host computer.""", + ("Missed scheduling of next ",): """ +This is generally indicative of an intermittent +communication failure between micro-controller and host.""", + ("ADC out of range",): """ +This generally occurs when a heater temperature exceeds +its configured min_temp or max_temp.""", + ("Rescheduled timer in the past", "Stepper too far in past"): """ +This generally occurs when the micro-controller has been +requested to step at a rate higher than it is capable of +obtaining.""", + ("Command request",): """ +This generally occurs in response to an M112 G-Code command +or in response to an internal error in the host software.""", +} + +def error_hint(msg): + for prefixes, help_msg in Common_MCU_errors.items(): + for prefix in prefixes: + if msg.startswith(prefix): + return help_msg + return "" + +class PrinterMCUError: + def __init__(self, config): + self.printer = config.get_printer() + self.clarify_callbacks = {} + self.printer.register_event_handler("klippy:notify_mcu_shutdown", + self._handle_notify_mcu_shutdown) + self.printer.register_event_handler("klippy:notify_mcu_error", + self._handle_notify_mcu_error) + def add_clarify(self, msg, callback): + self.clarify_callbacks.setdefault(msg, []).append(callback) + def _check_mcu_shutdown(self, msg, details): + mcu_name = details['mcu'] + mcu_msg = details['reason'] + event_type = details['event_type'] + prefix = "MCU '%s' shutdown: " % (mcu_name,) + if event_type == 'is_shutdown': + prefix = "Previous MCU '%s' shutdown: " % (mcu_name,) + # Lookup generic hint + hint = error_hint(mcu_msg) + # Add per instance help + clarify = [cb(msg, details) + for cb in self.clarify_callbacks.get(mcu_msg, [])] + clarify = [cm for cm in clarify if cm is not None] + clarify_msg = "" + if clarify: + clarify_msg = "\n".join(["", ""] + clarify + [""]) + # Update error message + newmsg = "%s%s%s%s%s" % (prefix, mcu_msg, clarify_msg, + hint, message_shutdown) + self.printer.update_error_msg(msg, newmsg) + def _handle_notify_mcu_shutdown(self, msg, details): + if msg == "MCU shutdown": + self._check_mcu_shutdown(msg, details) + else: + self.printer.update_error_msg(msg, "%s%s" % (msg, message_shutdown)) + def _check_protocol_error(self, msg, details): + host_version = self.printer.start_args['software_version'] + msg_update = [] + msg_updated = [] + for mcu_name, mcu in self.printer.lookup_objects('mcu'): + try: + mcu_version = mcu.get_status()['mcu_version'] + except: + logging.exception("Unable to retrieve mcu_version from mcu") + continue + if mcu_version != host_version: + msg_update.append("%s: Current version %s" + % (mcu_name.split()[-1], mcu_version)) + else: + msg_updated.append("%s: Current version %s" + % (mcu_name.split()[-1], mcu_version)) + if not msg_update: + msg_update.append("") + if not msg_updated: + msg_updated.append("") + newmsg = ["MCU Protocol error", + message_protocol_error1, + "Your Klipper version is: %s" % (host_version,), + "MCU(s) which should be updated:"] + newmsg += msg_update + ["Up-to-date MCU(s):"] + msg_updated + newmsg += [message_protocol_error2, details['error']] + self.printer.update_error_msg(msg, "\n".join(newmsg)) + def _check_mcu_connect_error(self, msg, details): + newmsg = "%s%s" % (details['error'], message_mcu_connect_error) + self.printer.update_error_msg(msg, newmsg) + def _handle_notify_mcu_error(self, msg, details): + if msg == "Protocol error": + self._check_protocol_error(msg, details) + elif msg == "MCU error during connect": + self._check_mcu_connect_error(msg, details) + +def load_config(config): + return PrinterMCUError(config) diff --git a/klippy/extras/force_move.py b/klippy/extras/force_move.py index 7501ea986e2d..50b801412dbd 100644 --- a/klippy/extras/force_move.py +++ b/klippy/extras/force_move.py @@ -90,8 +90,9 @@ def manual_move(self, stepper, dist, speed, accel=0.): print_time + 99999.9) stepper.set_trapq(prev_trapq) stepper.set_stepper_kinematics(prev_sk) - toolhead.note_kinematic_activity(print_time) + toolhead.note_mcu_movequeue_activity(print_time) toolhead.dwell(accel_t + cruise_t + accel_t) + toolhead.flush_step_generation() def _lookup_stepper(self, gcmd): name = gcmd.get('STEPPER') if name not in self.steppers: diff --git a/klippy/extras/hall_filament_width_sensor.py b/klippy/extras/hall_filament_width_sensor.py index e080288741d7..8dab35226666 100644 --- a/klippy/extras/hall_filament_width_sensor.py +++ b/klippy/extras/hall_filament_width_sensor.py @@ -49,10 +49,10 @@ def __init__(self, config): # Start adc self.ppins = self.printer.lookup_object('pins') self.mcu_adc = self.ppins.setup_pin('adc', self.pin1) - self.mcu_adc.setup_minmax(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT) + self.mcu_adc.setup_adc_sample(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT) self.mcu_adc.setup_adc_callback(ADC_REPORT_TIME, self.adc_callback) self.mcu_adc2 = self.ppins.setup_pin('adc', self.pin2) - self.mcu_adc2.setup_minmax(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT) + self.mcu_adc2.setup_adc_sample(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT) self.mcu_adc2.setup_adc_callback(ADC_REPORT_TIME, self.adc2_callback) # extrude factor updating self.extrude_factor_update_timer = self.reactor.register_timer( diff --git a/klippy/extras/heaters.py b/klippy/extras/heaters.py index 0e9411c9f6eb..5480501326b4 100644 --- a/klippy/extras/heaters.py +++ b/klippy/extras/heaters.py @@ -18,7 +18,8 @@ class Heater: def __init__(self, config, sensor): self.printer = config.get_printer() - self.name = config.get_name().split()[-1] + self.name = config.get_name() + self.short_name = short_name = self.name.split()[-1] # Setup sensor self.sensor = sensor self.min_temp = config.getfloat('min_temp', minval=KELVIN_TO_CELSIUS) @@ -36,6 +37,7 @@ def __init__(self, config, sensor): self.max_power = config.getfloat('max_power', 1., above=0., maxval=1.) self.smooth_time = config.getfloat('smooth_time', 1., above=0.) self.inv_smooth_time = 1. / self.smooth_time + self.is_shutdown = False self.lock = threading.Lock() self.last_temp = self.smoothed_temp = self.target_temp = 0. self.last_temp_time = 0. @@ -55,14 +57,16 @@ def __init__(self, config, sensor): self.mcu_pwm.setup_cycle_time(pwm_cycle_time) self.mcu_pwm.setup_max_duration(MAX_HEAT_TIME) # Load additional modules - self.printer.load_object(config, "verify_heater %s" % (self.name,)) + self.printer.load_object(config, "verify_heater %s" % (short_name,)) self.printer.load_object(config, "pid_calibrate") gcode = self.printer.lookup_object("gcode") gcode.register_mux_command("SET_HEATER_TEMPERATURE", "HEATER", - self.name, self.cmd_SET_HEATER_TEMPERATURE, + short_name, self.cmd_SET_HEATER_TEMPERATURE, desc=self.cmd_SET_HEATER_TEMPERATURE_help) + self.printer.register_event_handler("klippy:shutdown", + self._handle_shutdown) def set_pwm(self, read_time, value): - if self.target_temp <= 0.: + if self.target_temp <= 0. or self.is_shutdown: value = 0. if ((read_time < self.next_pwm_time or not self.last_pwm_value) and abs(value - self.last_pwm_value) < 0.05): @@ -86,7 +90,11 @@ def temperature_callback(self, read_time, temp): self.smoothed_temp += temp_diff * adj_time self.can_extrude = (self.smoothed_temp >= self.min_extrude_temp) #logging.debug("temp: %.3f %f = %f", read_time, temp) + def _handle_shutdown(self): + self.is_shutdown = True # External commands + def get_name(self): + return self.name def get_pwm_delay(self): return self.pwm_delay def get_max_power(self): @@ -127,7 +135,7 @@ def stats(self, eventtime): last_pwm_value = self.last_pwm_value is_active = target_temp or last_temp > 50. return is_active, '%s: target=%.0f temp=%.1f pwm=%.3f' % ( - self.name, target_temp, last_temp, last_pwm_value) + self.short_name, target_temp, last_temp, last_pwm_value) def get_status(self, eventtime): with self.lock: target_temp = self.target_temp @@ -281,8 +289,6 @@ def setup_sensor(self, config): if sensor_type not in self.sensor_factories: raise self.printer.config_error( "Unknown temperature sensor '%s'" % (sensor_type,)) - if sensor_type == 'NTC 100K beta 3950': - config.deprecate('sensor_type', 'NTC 100K beta 3950') return self.sensor_factories[sensor_type](config) def register_sensor(self, config, psensor, gcode_id=None): self.available_sensors.append(config.get_name()) diff --git a/klippy/extras/homing.py b/klippy/extras/homing.py index 634ad81b74c6..06b52f1ec56e 100644 --- a/klippy/extras/homing.py +++ b/klippy/extras/homing.py @@ -98,11 +98,14 @@ def homing_move(self, movepos, speed, probe_pos=False, trigger_times = {} move_end_print_time = self.toolhead.get_last_move_time() for mcu_endstop, name in self.endstops: - trigger_time = mcu_endstop.home_wait(move_end_print_time) + try: + trigger_time = mcu_endstop.home_wait(move_end_print_time) + except self.printer.command_error as e: + if error is None: + error = "Error during homing %s: %s" % (name, str(e)) + continue if trigger_time > 0.: trigger_times[name] = trigger_time - elif trigger_time < 0. and error is None: - error = "Communication timeout during homing %s" % (name,) elif check_triggered and error is None: error = "No trigger on %s after full movement" % (name,) # Determine stepper halt positions diff --git a/klippy/extras/homing_override.py b/klippy/extras/homing_override.py index a1cf94b59db8..e498c8b8de05 100644 --- a/klippy/extras/homing_override.py +++ b/klippy/extras/homing_override.py @@ -55,6 +55,7 @@ def cmd_G28(self, gcmd): # Perform homing context = self.template.create_template_context() context['params'] = gcmd.get_command_parameters() + context['rawparams'] = gcmd.get_raw_command_parameters() try: self.in_script = True self.template.run_gcode_from_command(context) diff --git a/klippy/extras/idle_timeout.py b/klippy/extras/idle_timeout.py index 479c0b5c7999..6ab2a34a533a 100644 --- a/klippy/extras/idle_timeout.py +++ b/klippy/extras/idle_timeout.py @@ -45,7 +45,7 @@ def transition_idle_state(self, eventtime): self.state = "Printing" try: script = self.idle_gcode.render() - res = self.gcode.run_script(script) + self.gcode.run_script(script) except: logging.exception("idle timeout gcode execution") self.state = "Ready" diff --git a/klippy/extras/input_shaper.py b/klippy/extras/input_shaper.py index 7f37d302bd46..a628289ef2d0 100644 --- a/klippy/extras/input_shaper.py +++ b/klippy/extras/input_shaper.py @@ -59,7 +59,6 @@ def get_shaper(self): return self.n, self.A, self.T def update(self, gcmd): self.params.update(gcmd) - old_n, old_A, old_T = self.n, self.A, self.T self.n, self.A, self.T = self.params.get_shaper() def set_shaper_kinematics(self, sk): ffi_main, ffi_lib = chelper.get_ffi() diff --git a/klippy/extras/ldc1612.py b/klippy/extras/ldc1612.py new file mode 100644 index 000000000000..281d34226997 --- /dev/null +++ b/klippy/extras/ldc1612.py @@ -0,0 +1,206 @@ +# Support for reading frequency samples from ldc1612 +# +# Copyright (C) 2020-2024 Kevin O'Connor +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import logging +from . import bus, bulk_sensor + +MIN_MSG_TIME = 0.100 + +BATCH_UPDATES = 0.100 + +LDC1612_ADDR = 0x2a + +LDC1612_FREQ = 12000000 +SETTLETIME = 0.005 +DRIVECUR = 15 +DEGLITCH = 0x05 # 10 Mhz + +LDC1612_MANUF_ID = 0x5449 +LDC1612_DEV_ID = 0x3055 + +REG_RCOUNT0 = 0x08 +REG_OFFSET0 = 0x0c +REG_SETTLECOUNT0 = 0x10 +REG_CLOCK_DIVIDERS0 = 0x14 +REG_ERROR_CONFIG = 0x19 +REG_CONFIG = 0x1a +REG_MUX_CONFIG = 0x1b +REG_DRIVE_CURRENT0 = 0x1e +REG_MANUFACTURER_ID = 0x7e +REG_DEVICE_ID = 0x7f + +# Tool for determining appropriate DRIVE_CURRENT register +class DriveCurrentCalibrate: + def __init__(self, config, sensor): + self.printer = config.get_printer() + self.sensor = sensor + self.drive_cur = config.getint("reg_drive_current", DRIVECUR, + minval=0, maxval=31) + self.name = config.get_name() + gcode = self.printer.lookup_object('gcode') + gcode.register_mux_command("LDC_CALIBRATE_DRIVE_CURRENT", + "CHIP", self.name.split()[-1], + self.cmd_LDC_CALIBRATE, + desc=self.cmd_LDC_CALIBRATE_help) + def get_drive_current(self): + return self.drive_cur + cmd_LDC_CALIBRATE_help = "Calibrate LDC1612 DRIVE_CURRENT register" + def cmd_LDC_CALIBRATE(self, gcmd): + is_in_progress = True + def handle_batch(msg): + return is_in_progress + self.sensor.add_client(handle_batch) + toolhead = self.printer.lookup_object("toolhead") + toolhead.dwell(0.100) + toolhead.wait_moves() + old_config = self.sensor.read_reg(REG_CONFIG) + self.sensor.set_reg(REG_CONFIG, 0x001 | (1<<9)) + toolhead.wait_moves() + toolhead.dwell(0.100) + toolhead.wait_moves() + reg_drive_current0 = self.sensor.read_reg(REG_DRIVE_CURRENT0) + self.sensor.set_reg(REG_CONFIG, old_config) + is_in_progress = False + # Report found value to user + drive_cur = (reg_drive_current0 >> 6) & 0x1f + gcmd.respond_info( + "%s: reg_drive_current: %d\n" + "The SAVE_CONFIG command will update the printer config file\n" + "with the above and restart the printer." % (self.name, drive_cur)) + configfile = self.printer.lookup_object('configfile') + configfile.set(self.name, 'reg_drive_current', "%d" % (drive_cur,)) + +# Interface class to LDC1612 mcu support +class LDC1612: + def __init__(self, config, calibration=None): + self.printer = config.get_printer() + self.calibration = calibration + self.dccal = DriveCurrentCalibrate(config, self) + self.data_rate = 250 + # Setup mcu sensor_ldc1612 bulk query code + self.i2c = bus.MCU_I2C_from_config(config, + default_addr=LDC1612_ADDR, + default_speed=400000) + self.mcu = mcu = self.i2c.get_mcu() + self.oid = oid = mcu.create_oid() + self.query_ldc1612_cmd = None + self.ldc1612_setup_home_cmd = self.query_ldc1612_home_state_cmd = None + if config.get('intb_pin', None) is not None: + ppins = config.get_printer().lookup_object("pins") + pin_params = ppins.lookup_pin(config.get('intb_pin')) + if pin_params['chip'] != mcu: + raise config.error("ldc1612 intb_pin must be on same mcu") + mcu.add_config_cmd( + "config_ldc1612_with_intb oid=%d i2c_oid=%d intb_pin=%s" + % (oid, self.i2c.get_oid(), pin_params['pin'])) + else: + mcu.add_config_cmd("config_ldc1612 oid=%d i2c_oid=%d" + % (oid, self.i2c.get_oid())) + mcu.add_config_cmd("query_ldc1612 oid=%d rest_ticks=0" + % (oid,), on_restart=True) + mcu.register_config_callback(self._build_config) + # Bulk sample message reading + chip_smooth = self.data_rate * BATCH_UPDATES * 2 + self.ffreader = bulk_sensor.FixedFreqReader(mcu, chip_smooth, ">I") + self.last_error_count = 0 + # Process messages in batches + self.batch_bulk = bulk_sensor.BatchBulkHelper( + self.printer, self._process_batch, + self._start_measurements, self._finish_measurements, BATCH_UPDATES) + self.name = config.get_name().split()[-1] + hdr = ('time', 'frequency', 'z') + self.batch_bulk.add_mux_endpoint("ldc1612/dump_ldc1612", "sensor", + self.name, {'header': hdr}) + def _build_config(self): + cmdqueue = self.i2c.get_command_queue() + self.query_ldc1612_cmd = self.mcu.lookup_command( + "query_ldc1612 oid=%c rest_ticks=%u", cq=cmdqueue) + self.ffreader.setup_query_command("query_status_ldc1612 oid=%c", + oid=self.oid, cq=cmdqueue) + self.ldc1612_setup_home_cmd = self.mcu.lookup_command( + "ldc1612_setup_home oid=%c clock=%u threshold=%u" + " trsync_oid=%c trigger_reason=%c error_reason=%c", cq=cmdqueue) + self.query_ldc1612_home_state_cmd = self.mcu.lookup_query_command( + "query_ldc1612_home_state oid=%c", + "ldc1612_home_state oid=%c homing=%c trigger_clock=%u", + oid=self.oid, cq=cmdqueue) + def get_mcu(self): + return self.i2c.get_mcu() + def read_reg(self, reg): + params = self.i2c.i2c_read([reg], 2) + response = bytearray(params['response']) + return (response[0] << 8) | response[1] + def set_reg(self, reg, val, minclock=0): + self.i2c.i2c_write([reg, (val >> 8) & 0xff, val & 0xff], + minclock=minclock) + def add_client(self, cb): + self.batch_bulk.add_client(cb) + # Homing + def setup_home(self, print_time, trigger_freq, + trsync_oid, hit_reason, err_reason): + clock = self.mcu.print_time_to_clock(print_time) + tfreq = int(trigger_freq * (1<<28) / float(LDC1612_FREQ) + 0.5) + self.ldc1612_setup_home_cmd.send( + [self.oid, clock, tfreq, trsync_oid, hit_reason, err_reason]) + def clear_home(self): + self.ldc1612_setup_home_cmd.send([self.oid, 0, 0, 0, 0, 0]) + if self.mcu.is_fileoutput(): + return 0. + params = self.query_ldc1612_home_state_cmd.send([self.oid]) + tclock = self.mcu.clock32_to_clock64(params['trigger_clock']) + return self.mcu.clock_to_print_time(tclock) + # Measurement decoding + def _convert_samples(self, samples): + freq_conv = float(LDC1612_FREQ) / (1<<28) + count = 0 + for ptime, val in samples: + mv = val & 0x0fffffff + if mv != val: + self.last_error_count += 1 + samples[count] = (round(ptime, 6), round(freq_conv * mv, 3), 999.9) + count += 1 + # Start, stop, and process message batches + def _start_measurements(self): + # In case of miswiring, testing LDC1612 device ID prevents treating + # noise or wrong signal as a correctly initialized device + manuf_id = self.read_reg(REG_MANUFACTURER_ID) + dev_id = self.read_reg(REG_DEVICE_ID) + if manuf_id != LDC1612_MANUF_ID or dev_id != LDC1612_DEV_ID: + raise self.printer.command_error( + "Invalid ldc1612 id (got %x,%x vs %x,%x).\n" + "This is generally indicative of connection problems\n" + "(e.g. faulty wiring) or a faulty ldc1612 chip." + % (manuf_id, dev_id, LDC1612_MANUF_ID, LDC1612_DEV_ID)) + # Setup chip in requested query rate + rcount0 = LDC1612_FREQ / (16. * (self.data_rate - 4)) + self.set_reg(REG_RCOUNT0, int(rcount0 + 0.5)) + self.set_reg(REG_OFFSET0, 0) + self.set_reg(REG_SETTLECOUNT0, int(SETTLETIME*LDC1612_FREQ/16. + .5)) + self.set_reg(REG_CLOCK_DIVIDERS0, (1 << 12) | 1) + self.set_reg(REG_ERROR_CONFIG, (0x1f << 11) | 1) + self.set_reg(REG_MUX_CONFIG, 0x0208 | DEGLITCH) + self.set_reg(REG_CONFIG, 0x001 | (1<<12) | (1<<10) | (1<<9)) + self.set_reg(REG_DRIVE_CURRENT0, self.dccal.get_drive_current() << 11) + # Start bulk reading + rest_ticks = self.mcu.seconds_to_clock(0.5 / self.data_rate) + self.query_ldc1612_cmd.send([self.oid, rest_ticks]) + logging.info("LDC1612 starting '%s' measurements", self.name) + # Initialize clock tracking + self.ffreader.note_start() + self.last_error_count = 0 + def _finish_measurements(self): + # Halt bulk reading + self.query_ldc1612_cmd.send_wait_ack([self.oid, 0]) + self.ffreader.note_end() + logging.info("LDC1612 finished '%s' measurements", self.name) + def _process_batch(self, eventtime): + samples = self.ffreader.pull_samples() + self._convert_samples(samples) + if not samples: + return {} + if self.calibration is not None: + self.calibration.apply_calibration(samples) + return {'data': samples, 'errors': self.last_error_count, + 'overflows': self.ffreader.get_last_overflows()} diff --git a/klippy/extras/lis2dw.py b/klippy/extras/lis2dw.py index 28591c21b1b4..45c12ea1aba1 100644 --- a/klippy/extras/lis2dw.py +++ b/klippy/extras/lis2dw.py @@ -1,7 +1,7 @@ # Support for reading acceleration data from an LIS2DW chip # # Copyright (C) 2023 Zhou.XianMing -# Copyright (C) 2020-2021 Kevin O'Connor +# Copyright (C) 2020-2023 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. import logging @@ -30,11 +30,6 @@ FREEFALL_ACCEL = 9.80665 SCALE = FREEFALL_ACCEL * 1.952 / 4 -MIN_MSG_TIME = 0.100 - -BYTES_PER_SAMPLE = 6 -SAMPLES_PER_BLOCK = 8 - BATCH_UPDATES = 0.100 # Printer class that controls LIS2DW chip @@ -42,25 +37,21 @@ class LIS2DW: def __init__(self, config): self.printer = config.get_printer() adxl345.AccelCommandHelper(config, self) - self.axes_map = adxl345.read_axes_map(config) + self.axes_map = adxl345.read_axes_map(config, SCALE, SCALE, SCALE) self.data_rate = 1600 # Setup mcu sensor_lis2dw bulk query code self.spi = bus.MCU_SPI_from_config(config, 3, default_speed=5000000) self.mcu = mcu = self.spi.get_mcu() self.oid = oid = mcu.create_oid() - self.query_lis2dw_cmd = self.query_lis2dw_end_cmd = None - self.query_lis2dw_status_cmd = None + self.query_lis2dw_cmd = None mcu.add_config_cmd("config_lis2dw oid=%d spi_oid=%d" % (oid, self.spi.get_oid())) - mcu.add_config_cmd("query_lis2dw oid=%d clock=0 rest_ticks=0" + mcu.add_config_cmd("query_lis2dw oid=%d rest_ticks=0" % (oid,), on_restart=True) mcu.register_config_callback(self._build_config) - self.bulk_queue = bulk_sensor.BulkDataQueue(mcu, "lis2dw_data", oid) - # Clock tracking + # Bulk sample message reading chip_smooth = self.data_rate * BATCH_UPDATES * 2 - self.clock_sync = bulk_sensor.ClockSyncRegression(mcu, chip_smooth) - self.clock_updater = bulk_sensor.ChipClockUpdater(self.clock_sync, - BYTES_PER_SAMPLE) + self.ffreader = bulk_sensor.FixedFreqReader(mcu, chip_smooth, "hhh") self.last_error_count = 0 # Process messages in batches self.batch_bulk = bulk_sensor.BatchBulkHelper( @@ -91,18 +87,12 @@ def _build_config(self): cmdqueue = self.i2c.get_command_queue() self.mcu.add_config_cmd("config_mpu9250 oid=%d i2c_oid=%d" % (self.oid, self.i2c.get_oid())) - self.mcu.add_config_cmd("query_mpu9250 oid=%d clock=0 rest_ticks=0" + self.mcu.add_config_cmd("query_mpu9250 oid=%d rest_ticks=0" % (self.oid,), on_restart=True) self.query_mpu9250_cmd = self.mcu.lookup_command( - "query_mpu9250 oid=%c clock=%u rest_ticks=%u", cq=cmdqueue) - self.query_mpu9250_end_cmd = self.mcu.lookup_query_command( - "query_mpu9250 oid=%c clock=%u rest_ticks=%u", - "mpu9250_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" - " buffered=%c fifo=%u limit_count=%hu", oid=self.oid, cq=cmdqueue) - self.query_mpu9250_status_cmd = self.mcu.lookup_query_command( - "query_mpu9250_status oid=%c", - "mpu9250_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" - " buffered=%c fifo=%u limit_count=%hu", oid=self.oid, cq=cmdqueue) + "query_mpu9250 oid=%c rest_ticks=%u", cq=cmdqueue) + self.ffreader.setup_query_command("query_mpu9250_status oid=%c", + oid=self.oid, cq=cmdqueue) def read_reg(self, reg): params = self.i2c.i2c_read([reg], 1) return bytearray(params['response'])[0] @@ -113,44 +103,16 @@ def start_internal_client(self): self.batch_bulk.add_client(aqh.handle_batch) return aqh # Measurement decoding - def _extract_samples(self, raw_samples): - # Load variables to optimize inner loop below + def _convert_samples(self, samples): (x_pos, x_scale), (y_pos, y_scale), (z_pos, z_scale) = self.axes_map - last_sequence = self.clock_updater.get_last_sequence() - time_base, chip_base, inv_freq = self.clock_sync.get_time_translation() - # Process every message in raw_samples - count = seq = 0 - samples = [None] * (len(raw_samples) * SAMPLES_PER_BLOCK) - for params in raw_samples: - seq_diff = (params['sequence'] - last_sequence) & 0xffff - seq_diff -= (seq_diff & 0x8000) << 1 - 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): - d_xyz = d[i*BYTES_PER_SAMPLE:(i+1)*BYTES_PER_SAMPLE] - xhigh, xlow, yhigh, ylow, zhigh, zlow = d_xyz - # Merge and perform twos-complement - rx = ((xhigh << 8) | xlow) - ((xhigh & 0x80) << 9) - ry = ((yhigh << 8) | ylow) - ((yhigh & 0x80) << 9) - rz = ((zhigh << 8) | zlow) - ((zhigh & 0x80) << 9) - - raw_xyz = (rx, ry, rz) - x = round(raw_xyz[x_pos] * x_scale, 6) - y = round(raw_xyz[y_pos] * y_scale, 6) - z = round(raw_xyz[z_pos] * z_scale, 6) - ptime = round(time_base + (msg_cdiff + i) * inv_freq, 6) - samples[count] = (ptime, x, y, z) - count += 1 - self.clock_sync.set_last_chip_clock(seq * SAMPLES_PER_BLOCK + i) - del samples[count:] - return samples - - def _update_clock(self, minclock=0): - params = self.query_mpu9250_status_cmd.send([self.oid], - minclock=minclock) - self.clock_updater.update_clock(params) + count = 0 + for ptime, rx, ry, rz in samples: + raw_xyz = (rx, ry, rz) + x = round(raw_xyz[x_pos] * x_scale, 6) + y = round(raw_xyz[y_pos] * y_scale, 6) + z = round(raw_xyz[z_pos] * z_scale, 6) + samples[count] = (round(ptime, 6), x, y, z) + count += 1 # Start, stop, and process message batches def _start_measurements(self): # In case of miswiring, testing MPU9250 device ID prevents treating @@ -176,38 +138,35 @@ def _start_measurements(self): self.set_reg(REG_CONFIG, SET_CONFIG) self.set_reg(REG_ACCEL_CONFIG, SET_ACCEL_CONFIG) self.set_reg(REG_ACCEL_CONFIG2, SET_ACCEL_CONFIG2) + # Reset fifo + self.set_reg(REG_FIFO_EN, SET_DISABLE_FIFO) + self.set_reg(REG_USER_CTRL, SET_USER_FIFO_RESET) + self.set_reg(REG_USER_CTRL, SET_USER_FIFO_EN) + self.read_reg(REG_INT_STATUS) # clear FIFO overflow flag # Start bulk reading - self.bulk_queue.clear_samples() - systime = self.printer.get_reactor().monotonic() - print_time = self.mcu.estimated_print_time(systime) + MIN_MSG_TIME - reqclock = self.mcu.print_time_to_clock(print_time) rest_ticks = self.mcu.seconds_to_clock(4. / self.data_rate) - self.query_mpu9250_cmd.send([self.oid, reqclock, rest_ticks], - reqclock=reqclock) + self.query_mpu9250_cmd.send([self.oid, rest_ticks]) + self.set_reg(REG_FIFO_EN, SET_ENABLE_FIFO) logging.info("MPU9250 starting '%s' measurements", self.name) # Initialize clock tracking - self.clock_updater.note_start(reqclock) - self._update_clock(minclock=reqclock) - self.clock_updater.clear_duration_filter() + self.ffreader.note_start() self.last_error_count = 0 def _finish_measurements(self): # Halt bulk reading - params = self.query_mpu9250_end_cmd.send([self.oid, 0, 0]) - self.bulk_queue.clear_samples() + self.set_reg(REG_FIFO_EN, SET_DISABLE_FIFO) + self.query_mpu9250_cmd.send_wait_ack([self.oid, 0]) + self.ffreader.note_end() logging.info("MPU9250 finished '%s' measurements", self.name) self.set_reg(REG_PWR_MGMT_1, SET_PWR_MGMT_1_SLEEP) self.set_reg(REG_PWR_MGMT_2, SET_PWR_MGMT_2_OFF) def _process_batch(self, eventtime): - self._update_clock() - raw_samples = self.bulk_queue.pull_samples() - if not raw_samples: - return {} - samples = self._extract_samples(raw_samples) + samples = self.ffreader.pull_samples() + self._convert_samples(samples) if not samples: return {} return {'data': samples, 'errors': self.last_error_count, - 'overflows': self.clock_updater.get_last_limit_count()} + 'overflows': self.ffreader.get_last_overflows()} def load_config(config): return MPU9250(config) diff --git a/klippy/extras/multi_pin.py b/klippy/extras/multi_pin.py index f5177bd97534..c834ee077f04 100644 --- a/klippy/extras/multi_pin.py +++ b/klippy/extras/multi_pin.py @@ -46,9 +46,9 @@ def setup_cycle_time(self, cycle_time, hardware_pwm=False): def set_digital(self, print_time, value): for mcu_pin in self.mcu_pins: mcu_pin.set_digital(print_time, value) - def set_pwm(self, print_time, value, cycle_time=None): + def set_pwm(self, print_time, value): for mcu_pin in self.mcu_pins: - mcu_pin.set_pwm(print_time, value, cycle_time) + mcu_pin.set_pwm(print_time, value) def load_config_prefix(config): return PrinterMultiPin(config) diff --git a/klippy/extras/output_pin.py b/klippy/extras/output_pin.py index 8b41aca7a454..ef094674c1c1 100644 --- a/klippy/extras/output_pin.py +++ b/klippy/extras/output_pin.py @@ -1,6 +1,6 @@ -# Code to configure miscellaneous chips +# PWM and digital output pin handling # -# Copyright (C) 2017-2021 Kevin O'Connor +# Copyright (C) 2017-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. @@ -12,6 +12,7 @@ class PrinterOutputPin: def __init__(self, config): self.printer = config.get_printer() ppins = self.printer.lookup_object('pins') + # Determine pin type self.is_pwm = config.getboolean('pwm', False) if self.is_pwm: self.mcu_pin = ppins.setup_pin('pwm', config.get('pin')) @@ -20,68 +21,65 @@ def __init__(self, config): 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_cycle_time = self.default_cycle_time = cycle_time else: self.mcu_pin = ppins.setup_pin('digital_out', config.get('pin')) self.scale = 1. - self.last_cycle_time = self.default_cycle_time = 0. self.last_print_time = 0. - static_value = config.getfloat('static_value', None, - minval=0., maxval=self.scale) + # Support mcu checking for maximum duration self.reactor = self.printer.get_reactor() self.resend_timer = None self.resend_interval = 0. + max_mcu_duration = config.getfloat('maximum_mcu_duration', 0., + minval=0.500, + maxval=MAX_SCHEDULE_TIME) + self.mcu_pin.setup_max_duration(max_mcu_duration) + if max_mcu_duration: + config.deprecate('maximum_mcu_duration') + self.resend_interval = max_mcu_duration - RESEND_HOST_TIME + # Determine start and shutdown values + static_value = config.getfloat('static_value', None, + minval=0., maxval=self.scale) if static_value is not None: - self.mcu_pin.setup_max_duration(0.) - self.last_value = static_value / self.scale - self.mcu_pin.setup_start_value( - self.last_value, self.last_value, True) + config.deprecate('static_value') + self.last_value = self.shutdown_value = static_value / self.scale else: - max_mcu_duration = config.getfloat('maximum_mcu_duration', 0., - minval=0.500, - maxval=MAX_SCHEDULE_TIME) - self.mcu_pin.setup_max_duration(max_mcu_duration) - if max_mcu_duration: - self.resend_interval = max_mcu_duration - RESEND_HOST_TIME - 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) - 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) + 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, cycle_time, is_resend=False): - if value == self.last_value and cycle_time == self.last_cycle_time: - if not is_resend: - return + def _set_pin(self, print_time, value, is_resend=False): + if value == self.last_value and not is_resend: + return print_time = max(print_time, self.last_print_time + PIN_MIN_TIME) if self.is_pwm: - self.mcu_pin.set_pwm(print_time, value, cycle_time) + self.mcu_pin.set_pwm(print_time, value) else: self.mcu_pin.set_digital(print_time, value) self.last_value = value - self.last_cycle_time = cycle_time self.last_print_time = print_time if self.resend_interval and self.resend_timer is None: self.resend_timer = self.reactor.register_timer( self._resend_current_val, self.reactor.NOW) 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 - cycle_time = gcmd.get_float('CYCLE_TIME', self.default_cycle_time, - above=0., maxval=MAX_SCHEDULE_TIME) if not self.is_pwm and value not in [0., 1.]: raise gcmd.error("Invalid pin value") + # Obtain print_time and apply requested settings toolhead = self.printer.lookup_object('toolhead') toolhead.register_lookahead_callback( - lambda print_time: self._set_pin(print_time, value, cycle_time)) + lambda print_time: self._set_pin(print_time, value)) def _resend_current_val(self, eventtime): if self.last_value == self.shutdown_value: @@ -95,8 +93,7 @@ def _resend_current_val(self, eventtime): if time_diff > 0.: # Reschedule for resend time return systime + time_diff - self._set_pin(print_time + PIN_MIN_TIME, - self.last_value, self.last_cycle_time, True) + self._set_pin(print_time + PIN_MIN_TIME, self.last_value, True) return systime + self.resend_interval def load_config_prefix(config): diff --git a/klippy/extras/pid_calibrate.py b/klippy/extras/pid_calibrate.py index f32f1be79856..20641167224d 100644 --- a/klippy/extras/pid_calibrate.py +++ b/klippy/extras/pid_calibrate.py @@ -43,11 +43,12 @@ def cmd_PID_CALIBRATE(self, gcmd): "The SAVE_CONFIG command will update the printer config file\n" "with these parameters and restart the printer." % (Kp, Ki, Kd)) # Store results for SAVE_CONFIG + cfgname = heater.get_name() configfile = self.printer.lookup_object('configfile') - configfile.set(heater_name, 'control', 'pid') - configfile.set(heater_name, 'pid_Kp', "%.3f" % (Kp,)) - configfile.set(heater_name, 'pid_Ki', "%.3f" % (Ki,)) - configfile.set(heater_name, 'pid_Kd', "%.3f" % (Kd,)) + configfile.set(cfgname, 'control', 'pid') + configfile.set(cfgname, 'pid_Kp', "%.3f" % (Kp,)) + configfile.set(cfgname, 'pid_Ki', "%.3f" % (Ki,)) + configfile.set(cfgname, 'pid_Kd', "%.3f" % (Kd,)) TUNE_PID_DELTA = 5.0 diff --git a/klippy/extras/probe.py b/klippy/extras/probe.py index 337c41b13c9d..c467e181e4d5 100644 --- a/klippy/extras/probe.py +++ b/klippy/extras/probe.py @@ -1,6 +1,6 @@ # Z-Probe support # -# Copyright (C) 2017-2021 Kevin O'Connor +# Copyright (C) 2017-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. import logging @@ -13,44 +13,176 @@ can travel further (the Z minimum position can be negative). """ -class PrinterProbe: - def __init__(self, config, mcu_probe): +# Calculate the average Z from a set of positions +def calc_probe_z_average(positions, method='average'): + if method != 'median': + # Use mean average + count = float(len(positions)) + return [sum([pos[i] for pos in positions]) / count + for i in range(3)] + # Use median + z_sorted = sorted(positions, key=(lambda p: p[2])) + middle = len(positions) // 2 + if (len(positions) & 1) == 1: + # odd number of samples + return z_sorted[middle] + # even number of samples + return calc_probe_z_average(z_sorted[middle-1:middle+1], 'average') + + +###################################################################### +# Probe device implementation helpers +###################################################################### + +# Helper to implement common probing commands +class ProbeCommandHelper: + def __init__(self, config, probe, query_endstop=None): self.printer = config.get_printer() + self.probe = probe + self.query_endstop = query_endstop self.name = config.get_name() - self.mcu_probe = mcu_probe - self.speed = config.getfloat('speed', 5.0, above=0.) - self.lift_speed = config.getfloat('lift_speed', self.speed, above=0.) - self.x_offset = config.getfloat('x_offset', 0.) - self.y_offset = config.getfloat('y_offset', 0.) - self.z_offset = config.getfloat('z_offset') - self.probe_calibrate_z = 0. - self.multi_probe_pending = False + gcode = self.printer.lookup_object('gcode') + # QUERY_PROBE command self.last_state = False + gcode.register_command('QUERY_PROBE', self.cmd_QUERY_PROBE, + desc=self.cmd_QUERY_PROBE_help) + # PROBE command self.last_z_result = 0. - self.gcode_move = self.printer.load_object(config, "gcode_move") - # Infer Z position to move to during a probe - if config.has_section('stepper_z'): - zconfig = config.getsection('stepper_z') - self.z_position = zconfig.getfloat('position_min', 0., - note_valid=False) - else: - pconfig = config.getsection('printer') - self.z_position = pconfig.getfloat('minimum_z_position', 0., - note_valid=False) - # Multi-sample support (for improved accuracy) - self.sample_count = config.getint('samples', 1, minval=1) - self.sample_retract_dist = config.getfloat('sample_retract_dist', 2., - above=0.) - atypes = {'median': 'median', 'average': 'average'} - self.samples_result = config.getchoice('samples_result', atypes, - 'average') - self.samples_tolerance = config.getfloat('samples_tolerance', 0.100, - minval=0.) - self.samples_retries = config.getint('samples_tolerance_retries', 0, - minval=0) + gcode.register_command('PROBE', self.cmd_PROBE, + desc=self.cmd_PROBE_help) + # PROBE_CALIBRATE command + self.probe_calibrate_z = 0. + gcode.register_command('PROBE_CALIBRATE', self.cmd_PROBE_CALIBRATE, + desc=self.cmd_PROBE_CALIBRATE_help) + # Other commands + gcode.register_command('PROBE_ACCURACY', self.cmd_PROBE_ACCURACY, + desc=self.cmd_PROBE_ACCURACY_help) + gcode.register_command('Z_OFFSET_APPLY_PROBE', + self.cmd_Z_OFFSET_APPLY_PROBE, + desc=self.cmd_Z_OFFSET_APPLY_PROBE_help) + def _move(self, coord, speed): + self.printer.lookup_object('toolhead').manual_move(coord, speed) + def get_status(self, eventtime): + return {'name': self.name, + 'last_query': self.last_state, + 'last_z_result': self.last_z_result} + cmd_QUERY_PROBE_help = "Return the status of the z-probe" + def cmd_QUERY_PROBE(self, gcmd): + if self.query_endstop is None: + raise gcmd.error("Probe does not support QUERY_PROBE") + toolhead = self.printer.lookup_object('toolhead') + print_time = toolhead.get_last_move_time() + res = self.query_endstop(print_time) + self.last_state = res + gcmd.respond_info("probe: %s" % (["open", "TRIGGERED"][not not res],)) + cmd_PROBE_help = "Probe Z-height at current XY position" + def cmd_PROBE(self, gcmd): + pos = run_single_probe(self.probe, gcmd) + gcmd.respond_info("Result is z=%.6f" % (pos[2],)) + self.last_z_result = pos[2] + def probe_calibrate_finalize(self, kin_pos): + if kin_pos is None: + return + z_offset = self.probe_calibrate_z - kin_pos[2] + gcode = self.printer.lookup_object('gcode') + gcode.respond_info( + "%s: z_offset: %.3f\n" + "The SAVE_CONFIG command will update the printer config file\n" + "with the above and restart the printer." % (self.name, z_offset)) + configfile = self.printer.lookup_object('configfile') + configfile.set(self.name, 'z_offset', "%.3f" % (z_offset,)) + cmd_PROBE_CALIBRATE_help = "Calibrate the probe's z_offset" + def cmd_PROBE_CALIBRATE(self, gcmd): + manual_probe.verify_no_manual_probe(self.printer) + params = self.probe.get_probe_params(gcmd) + # Perform initial probe + curpos = run_single_probe(self.probe, gcmd) + # Move away from the bed + self.probe_calibrate_z = curpos[2] + curpos[2] += 5. + self._move(curpos, params['lift_speed']) + # Move the nozzle over the probe point + x_offset, y_offset, z_offset = self.probe.get_offsets() + curpos[0] += x_offset + curpos[1] += y_offset + self._move(curpos, params['probe_speed']) + # Start manual probe + manual_probe.ManualProbeHelper(self.printer, gcmd, + self.probe_calibrate_finalize) + cmd_PROBE_ACCURACY_help = "Probe Z-height accuracy at current XY position" + def cmd_PROBE_ACCURACY(self, gcmd): + params = self.probe.get_probe_params(gcmd) + sample_count = gcmd.get_int("SAMPLES", 10, minval=1) + toolhead = self.printer.lookup_object('toolhead') + pos = toolhead.get_position() + gcmd.respond_info("PROBE_ACCURACY at X:%.3f Y:%.3f Z:%.3f" + " (samples=%d retract=%.3f" + " speed=%.1f lift_speed=%.1f)\n" + % (pos[0], pos[1], pos[2], + sample_count, params['sample_retract_dist'], + params['probe_speed'], params['lift_speed'])) + # Create dummy gcmd with SAMPLES=1 + fo_params = dict(gcmd.get_command_parameters()) + fo_params['SAMPLES'] = '1' + gcode = self.printer.lookup_object('gcode') + fo_gcmd = gcode.create_gcode_command("", "", fo_params) + # Probe bed sample_count times + probe_session = self.probe.start_probe_session(fo_gcmd) + probe_num = 0 + while probe_num < sample_count: + # Probe position + probe_session.run_probe(fo_gcmd) + probe_num += 1 + # Retract + pos = toolhead.get_position() + liftpos = [None, None, pos[2] + params['sample_retract_dist']] + self._move(liftpos, params['lift_speed']) + positions = probe_session.pull_probed_results() + probe_session.end_probe_session() + # Calculate maximum, minimum and average values + max_value = max([p[2] for p in positions]) + min_value = min([p[2] for p in positions]) + range_value = max_value - min_value + avg_value = calc_probe_z_average(positions, 'average')[2] + median = calc_probe_z_average(positions, 'median')[2] + # calculate the standard deviation + deviation_sum = 0 + for i in range(len(positions)): + deviation_sum += pow(positions[i][2] - avg_value, 2.) + sigma = (deviation_sum / len(positions)) ** 0.5 + # Show information + gcmd.respond_info( + "probe accuracy results: maximum %.6f, minimum %.6f, range %.6f, " + "average %.6f, median %.6f, standard deviation %.6f" % ( + max_value, min_value, range_value, avg_value, median, sigma)) + cmd_Z_OFFSET_APPLY_PROBE_help = "Adjust the probe's z_offset" + def cmd_Z_OFFSET_APPLY_PROBE(self, gcmd): + gcode_move = self.printer.lookup_object("gcode_move") + offset = gcode_move.get_status()['homing_origin'].z + if offset == 0: + gcmd.respond_info("Nothing to do: Z Offset is 0") + return + z_offset = self.probe.get_offsets()[2] + new_calibrate = z_offset - offset + gcmd.respond_info( + "%s: z_offset: %.3f\n" + "The SAVE_CONFIG command will update the printer config file\n" + "with the above and restart the printer." + % (self.name, new_calibrate)) + configfile = self.printer.lookup_object('configfile') + configfile.set(self.name, 'z_offset', "%.3f" % (new_calibrate,)) + +# Homing via probe:z_virtual_endstop +class HomingViaProbeHelper: + def __init__(self, config, mcu_probe): + self.printer = config.get_printer() + self.mcu_probe = mcu_probe + self.multi_probe_pending = False # Register z_virtual_endstop pin self.printer.lookup_object('pins').register_chip('probe', self) - # Register homing event handlers + # Register event handlers + self.printer.register_event_handler('klippy:mcu_identify', + self._handle_mcu_identify) self.printer.register_event_handler("homing:homing_move_begin", self._handle_homing_move_begin) self.printer.register_event_handler("homing:homing_move_end", @@ -61,19 +193,11 @@ def __init__(self, config, mcu_probe): self._handle_home_rails_end) self.printer.register_event_handler("gcode:command_error", self._handle_command_error) - # Register PROBE/QUERY_PROBE commands - self.gcode = self.printer.lookup_object('gcode') - self.gcode.register_command('PROBE', self.cmd_PROBE, - desc=self.cmd_PROBE_help) - self.gcode.register_command('QUERY_PROBE', self.cmd_QUERY_PROBE, - desc=self.cmd_QUERY_PROBE_help) - self.gcode.register_command('PROBE_CALIBRATE', self.cmd_PROBE_CALIBRATE, - desc=self.cmd_PROBE_CALIBRATE_help) - self.gcode.register_command('PROBE_ACCURACY', self.cmd_PROBE_ACCURACY, - desc=self.cmd_PROBE_ACCURACY_help) - self.gcode.register_command('Z_OFFSET_APPLY_PROBE', - self.cmd_Z_OFFSET_APPLY_PROBE, - desc=self.cmd_Z_OFFSET_APPLY_PROBE_help) + def _handle_mcu_identify(self): + kin = self.printer.lookup_object('toolhead').get_kinematics() + for stepper in kin.get_steppers(): + if stepper.is_active_axis('z'): + self.mcu_probe.add_stepper(stepper) def _handle_homing_move_begin(self, hmove): if self.mcu_probe in hmove.get_mcu_endstops(): self.mcu_probe.probe_prepare(hmove) @@ -83,281 +207,174 @@ def _handle_homing_move_end(self, hmove): def _handle_home_rails_begin(self, homing_state, rails): endstops = [es for rail in rails for es, name in rail.get_endstops()] if self.mcu_probe in endstops: - self.multi_probe_begin() + self.mcu_probe.multi_probe_begin() + self.multi_probe_pending = True def _handle_home_rails_end(self, homing_state, rails): endstops = [es for rail in rails for es, name in rail.get_endstops()] - if self.mcu_probe in endstops: - self.multi_probe_end() + if self.multi_probe_pending and self.mcu_probe in endstops: + self.multi_probe_pending = False + self.mcu_probe.multi_probe_end() def _handle_command_error(self): - try: - self.multi_probe_end() - except: - logging.exception("Multi-probe end") - def multi_probe_begin(self): - self.mcu_probe.multi_probe_begin() - self.multi_probe_pending = True - def multi_probe_end(self): if self.multi_probe_pending: self.multi_probe_pending = False - self.mcu_probe.multi_probe_end() + try: + self.mcu_probe.multi_probe_end() + except: + logging.exception("Homing multi-probe end") def setup_pin(self, pin_type, pin_params): if pin_type != 'endstop' or pin_params['pin'] != 'z_virtual_endstop': raise pins.error("Probe virtual endstop only useful as endstop pin") if pin_params['invert'] or pin_params['pullup']: raise pins.error("Can not pullup/invert probe virtual endstop") return self.mcu_probe - def get_lift_speed(self, gcmd=None): - if gcmd is not None: - return gcmd.get_float("LIFT_SPEED", self.lift_speed, above=0.) - return self.lift_speed - def get_offsets(self): - return self.x_offset, self.y_offset, self.z_offset + +# Helper to track multiple probe attempts in a single command +class ProbeSessionHelper: + def __init__(self, config, mcu_probe): + self.printer = config.get_printer() + self.mcu_probe = mcu_probe + gcode = self.printer.lookup_object('gcode') + self.dummy_gcode_cmd = gcode.create_gcode_command("", "", {}) + # Infer Z position to move to during a probe + if config.has_section('stepper_z'): + zconfig = config.getsection('stepper_z') + self.z_position = zconfig.getfloat('position_min', 0., + note_valid=False) + else: + pconfig = config.getsection('printer') + self.z_position = pconfig.getfloat('minimum_z_position', 0., + note_valid=False) + self.homing_helper = HomingViaProbeHelper(config, mcu_probe) + # Configurable probing speeds + self.speed = config.getfloat('speed', 5.0, above=0.) + self.lift_speed = config.getfloat('lift_speed', self.speed, above=0.) + # Multi-sample support (for improved accuracy) + self.sample_count = config.getint('samples', 1, minval=1) + self.sample_retract_dist = config.getfloat('sample_retract_dist', 2., + above=0.) + atypes = ['median', 'average'] + self.samples_result = config.getchoice('samples_result', atypes, + 'average') + self.samples_tolerance = config.getfloat('samples_tolerance', 0.100, + minval=0.) + self.samples_retries = config.getint('samples_tolerance_retries', 0, + minval=0) + # Session state + self.multi_probe_pending = False + self.results = [] + # Register event handlers + self.printer.register_event_handler("gcode:command_error", + self._handle_command_error) + def _handle_command_error(self): + if self.multi_probe_pending: + try: + self.end_probe_session() + except: + logging.exception("Multi-probe end") + def _probe_state_error(self): + raise self.printer.command_error( + "Internal probe error - start/end probe session mismatch") + def start_probe_session(self, gcmd): + if self.multi_probe_pending: + self._probe_state_error() + self.mcu_probe.multi_probe_begin() + self.multi_probe_pending = True + self.results = [] + return self + def end_probe_session(self): + if not self.multi_probe_pending: + self._probe_state_error() + self.results = [] + self.multi_probe_pending = False + self.mcu_probe.multi_probe_end() + def get_probe_params(self, gcmd=None): + if gcmd is None: + gcmd = self.dummy_gcode_cmd + probe_speed = gcmd.get_float("PROBE_SPEED", self.speed, above=0.) + lift_speed = gcmd.get_float("LIFT_SPEED", self.lift_speed, above=0.) + samples = gcmd.get_int("SAMPLES", self.sample_count, minval=1) + sample_retract_dist = gcmd.get_float("SAMPLE_RETRACT_DIST", + self.sample_retract_dist, above=0.) + samples_tolerance = gcmd.get_float("SAMPLES_TOLERANCE", + self.samples_tolerance, minval=0.) + samples_retries = gcmd.get_int("SAMPLES_TOLERANCE_RETRIES", + self.samples_retries, minval=0) + samples_result = gcmd.get("SAMPLES_RESULT", self.samples_result) + return {'probe_speed': probe_speed, + 'lift_speed': lift_speed, + 'samples': samples, + 'sample_retract_dist': sample_retract_dist, + 'samples_tolerance': samples_tolerance, + 'samples_tolerance_retries': samples_retries, + 'samples_result': samples_result} def _probe(self, speed): toolhead = self.printer.lookup_object('toolhead') curtime = self.printer.get_reactor().monotonic() if 'z' not in toolhead.get_status(curtime)['homed_axes']: raise self.printer.command_error("Must home before probe") - phoming = self.printer.lookup_object('homing') pos = toolhead.get_position() pos[2] = self.z_position try: - epos = phoming.probing_move(self.mcu_probe, pos, speed) + epos = self.mcu_probe.probing_move(pos, speed) except self.printer.command_error as e: reason = str(e) if "Timeout during endstop homing" in reason: reason += HINT_TIMEOUT raise self.printer.command_error(reason) - # get z compensation from axis_twist_compensation - axis_twist_compensation = self.printer.lookup_object( - 'axis_twist_compensation', None) - z_compensation = 0 - if axis_twist_compensation is not None: - z_compensation = ( - axis_twist_compensation.get_z_compensation_value(pos)) - # add z compensation to probe position - epos[2] += z_compensation - self.gcode.respond_info("probe at %.3f,%.3f is z=%.6f" - % (epos[0], epos[1], epos[2])) + # Allow axis_twist_compensation to update results + self.printer.send_event("probe:update_results", epos) + # Report results + gcode = self.printer.lookup_object('gcode') + gcode.respond_info("probe at %.3f,%.3f is z=%.6f" + % (epos[0], epos[1], epos[2])) return epos[:3] - def _move(self, coord, speed): - self.printer.lookup_object('toolhead').manual_move(coord, speed) - def _calc_mean(self, positions): - count = float(len(positions)) - return [sum([pos[i] for pos in positions]) / count - for i in range(3)] - def _calc_median(self, positions): - z_sorted = sorted(positions, key=(lambda p: p[2])) - middle = len(positions) // 2 - if (len(positions) & 1) == 1: - # odd number of samples - return z_sorted[middle] - # even number of samples - return self._calc_mean(z_sorted[middle-1:middle+1]) def run_probe(self, gcmd): - speed = gcmd.get_float("PROBE_SPEED", self.speed, above=0.) - lift_speed = self.get_lift_speed(gcmd) - sample_count = gcmd.get_int("SAMPLES", self.sample_count, minval=1) - sample_retract_dist = gcmd.get_float("SAMPLE_RETRACT_DIST", - self.sample_retract_dist, above=0.) - samples_tolerance = gcmd.get_float("SAMPLES_TOLERANCE", - self.samples_tolerance, minval=0.) - samples_retries = gcmd.get_int("SAMPLES_TOLERANCE_RETRIES", - self.samples_retries, minval=0) - samples_result = gcmd.get("SAMPLES_RESULT", self.samples_result) - must_notify_multi_probe = not self.multi_probe_pending - if must_notify_multi_probe: - self.multi_probe_begin() - probexy = self.printer.lookup_object('toolhead').get_position()[:2] + if not self.multi_probe_pending: + self._probe_state_error() + params = self.get_probe_params(gcmd) + toolhead = self.printer.lookup_object('toolhead') + probexy = toolhead.get_position()[:2] retries = 0 positions = [] + sample_count = params['samples'] while len(positions) < sample_count: # Probe position - pos = self._probe(speed) + pos = self._probe(params['probe_speed']) positions.append(pos) # Check samples tolerance z_positions = [p[2] for p in positions] - if max(z_positions) - min(z_positions) > samples_tolerance: - if retries >= samples_retries: + if max(z_positions)-min(z_positions) > params['samples_tolerance']: + if retries >= params['samples_tolerance_retries']: raise gcmd.error("Probe samples exceed samples_tolerance") gcmd.respond_info("Probe samples exceed tolerance. Retrying...") retries += 1 positions = [] # Retract if len(positions) < sample_count: - self._move(probexy + [pos[2] + sample_retract_dist], lift_speed) - if must_notify_multi_probe: - self.multi_probe_end() - # Calculate and return result - if samples_result == 'median': - return self._calc_median(positions) - return self._calc_mean(positions) - cmd_PROBE_help = "Probe Z-height at current XY position" - def cmd_PROBE(self, gcmd): - pos = self.run_probe(gcmd) - gcmd.respond_info("Result is z=%.6f" % (pos[2],)) - self.last_z_result = pos[2] - cmd_QUERY_PROBE_help = "Return the status of the z-probe" - def cmd_QUERY_PROBE(self, gcmd): - toolhead = self.printer.lookup_object('toolhead') - print_time = toolhead.get_last_move_time() - res = self.mcu_probe.query_endstop(print_time) - self.last_state = res - gcmd.respond_info("probe: %s" % (["open", "TRIGGERED"][not not res],)) - def get_status(self, eventtime): - return {'name': self.name, - 'last_query': self.last_state, - 'last_z_result': self.last_z_result} - cmd_PROBE_ACCURACY_help = "Probe Z-height accuracy at current XY position" - def cmd_PROBE_ACCURACY(self, gcmd): - speed = gcmd.get_float("PROBE_SPEED", self.speed, above=0.) - lift_speed = self.get_lift_speed(gcmd) - sample_count = gcmd.get_int("SAMPLES", 10, minval=1) - sample_retract_dist = gcmd.get_float("SAMPLE_RETRACT_DIST", - self.sample_retract_dist, above=0.) - toolhead = self.printer.lookup_object('toolhead') - pos = toolhead.get_position() - gcmd.respond_info("PROBE_ACCURACY at X:%.3f Y:%.3f Z:%.3f" - " (samples=%d retract=%.3f" - " speed=%.1f lift_speed=%.1f)\n" - % (pos[0], pos[1], pos[2], - sample_count, sample_retract_dist, - speed, lift_speed)) - # Probe bed sample_count times - self.multi_probe_begin() - positions = [] - while len(positions) < sample_count: - # Probe position - pos = self._probe(speed) - positions.append(pos) - # Retract - liftpos = [None, None, pos[2] + sample_retract_dist] - self._move(liftpos, lift_speed) - self.multi_probe_end() - # Calculate maximum, minimum and average values - max_value = max([p[2] for p in positions]) - min_value = min([p[2] for p in positions]) - range_value = max_value - min_value - avg_value = self._calc_mean(positions)[2] - median = self._calc_median(positions)[2] - # calculate the standard deviation - deviation_sum = 0 - for i in range(len(positions)): - deviation_sum += pow(positions[i][2] - avg_value, 2.) - sigma = (deviation_sum / len(positions)) ** 0.5 - # Show information - gcmd.respond_info( - "probe accuracy results: maximum %.6f, minimum %.6f, range %.6f, " - "average %.6f, median %.6f, standard deviation %.6f" % ( - max_value, min_value, range_value, avg_value, median, sigma)) - def probe_calibrate_finalize(self, kin_pos): - if kin_pos is None: - return - z_offset = self.probe_calibrate_z - kin_pos[2] - self.gcode.respond_info( - "%s: z_offset: %.3f\n" - "The SAVE_CONFIG command will update the printer config file\n" - "with the above and restart the printer." % (self.name, z_offset)) - configfile = self.printer.lookup_object('configfile') - configfile.set(self.name, 'z_offset', "%.3f" % (z_offset,)) - cmd_PROBE_CALIBRATE_help = "Calibrate the probe's z_offset" - def cmd_PROBE_CALIBRATE(self, gcmd): - manual_probe.verify_no_manual_probe(self.printer) - # Perform initial probe - lift_speed = self.get_lift_speed(gcmd) - curpos = self.run_probe(gcmd) - # Move away from the bed - self.probe_calibrate_z = curpos[2] - curpos[2] += 5. - self._move(curpos, lift_speed) - # Move the nozzle over the probe point - curpos[0] += self.x_offset - curpos[1] += self.y_offset - self._move(curpos, self.speed) - # Start manual probe - manual_probe.ManualProbeHelper(self.printer, gcmd, - self.probe_calibrate_finalize) - def cmd_Z_OFFSET_APPLY_PROBE(self,gcmd): - offset = self.gcode_move.get_status()['homing_origin'].z - configfile = self.printer.lookup_object('configfile') - if offset == 0: - self.gcode.respond_info("Nothing to do: Z Offset is 0") - else: - new_calibrate = self.z_offset - offset - self.gcode.respond_info( - "%s: z_offset: %.3f\n" - "The SAVE_CONFIG command will update the printer config file\n" - "with the above and restart the printer." - % (self.name, new_calibrate)) - configfile.set(self.name, 'z_offset', "%.3f" % (new_calibrate,)) - cmd_Z_OFFSET_APPLY_PROBE_help = "Adjust the probe's z_offset" + toolhead.manual_move( + probexy + [pos[2] + params['sample_retract_dist']], + params['lift_speed']) + # Calculate result + epos = calc_probe_z_average(positions, params['samples_result']) + self.results.append(epos) + def pull_probed_results(self): + res = self.results + self.results = [] + return res -# Endstop wrapper that enables probe specific features -class ProbeEndstopWrapper: +# Helper to read the xyz probe offsets from the config +class ProbeOffsetsHelper: def __init__(self, config): - self.printer = config.get_printer() - self.position_endstop = config.getfloat('z_offset') - self.stow_on_each_sample = config.getboolean( - 'deactivate_on_each_sample', True) - gcode_macro = self.printer.load_object(config, 'gcode_macro') - self.activate_gcode = gcode_macro.load_template( - config, 'activate_gcode', '') - self.deactivate_gcode = gcode_macro.load_template( - config, 'deactivate_gcode', '') - # Create an "endstop" object to handle the probe pin - ppins = self.printer.lookup_object('pins') - pin = config.get('pin') - pin_params = ppins.lookup_pin(pin, can_invert=True, can_pullup=True) - mcu = pin_params['chip'] - self.mcu_endstop = mcu.setup_pin('endstop', pin_params) - self.printer.register_event_handler('klippy:mcu_identify', - self._handle_mcu_identify) - # Wrappers - self.get_mcu = self.mcu_endstop.get_mcu - self.add_stepper = self.mcu_endstop.add_stepper - self.get_steppers = self.mcu_endstop.get_steppers - self.home_start = self.mcu_endstop.home_start - self.home_wait = self.mcu_endstop.home_wait - self.query_endstop = self.mcu_endstop.query_endstop - # multi probes state - self.multi = 'OFF' - def _handle_mcu_identify(self): - kin = self.printer.lookup_object('toolhead').get_kinematics() - for stepper in kin.get_steppers(): - if stepper.is_active_axis('z'): - self.add_stepper(stepper) - def raise_probe(self): - toolhead = self.printer.lookup_object('toolhead') - start_pos = toolhead.get_position() - self.deactivate_gcode.run_gcode_from_command() - if toolhead.get_position()[:3] != start_pos[:3]: - raise self.printer.command_error( - "Toolhead moved during probe activate_gcode script") - def lower_probe(self): - toolhead = self.printer.lookup_object('toolhead') - start_pos = toolhead.get_position() - self.activate_gcode.run_gcode_from_command() - if toolhead.get_position()[:3] != start_pos[:3]: - raise self.printer.command_error( - "Toolhead moved during probe deactivate_gcode script") - def multi_probe_begin(self): - if self.stow_on_each_sample: - return - self.multi = 'FIRST' - def multi_probe_end(self): - if self.stow_on_each_sample: - return - self.raise_probe() - self.multi = 'OFF' - def probe_prepare(self, hmove): - if self.multi == 'OFF' or self.multi == 'FIRST': - self.lower_probe() - if self.multi == 'FIRST': - self.multi = 'ON' - def probe_finish(self, hmove): - if self.multi == 'OFF': - self.raise_probe() - def get_position_endstop(self): - return self.position_endstop + self.x_offset = config.getfloat('x_offset', 0.) + self.y_offset = config.getfloat('y_offset', 0.) + self.z_offset = config.getfloat('z_offset') + def get_offsets(self): + return self.x_offset, self.y_offset, self.z_offset + + +###################################################################### +# Tools for utilizing the probe +###################################################################### # Helper code that can probe a series of points and report the # position at each point. @@ -379,7 +396,7 @@ def __init__(self, config, finalize_callback, default_points=None): # Internal probing state self.lift_speed = self.speed self.probe_offsets = (0., 0., 0.) - self.results = [] + self.manual_results = [] def minimum_points(self,n): if len(self.probe_points) < n: raise self.printer.config_error( @@ -391,68 +408,173 @@ def use_xy_offsets(self, use_offsets): self.use_offsets = use_offsets def get_lift_speed(self): return self.lift_speed - def _move_next(self): - toolhead = self.printer.lookup_object('toolhead') - # Lift toolhead + def _move(self, coord, speed): + self.printer.lookup_object('toolhead').manual_move(coord, speed) + def _raise_tool(self, is_first=False): speed = self.lift_speed - if not self.results: + if is_first: # Use full speed to first probe position speed = self.speed - toolhead.manual_move([None, None, self.horizontal_move_z], speed) - # Check if done probing - if len(self.results) >= len(self.probe_points): - toolhead.get_last_move_time() - res = self.finalize_callback(self.probe_offsets, self.results) - if res != "retry": - return True - self.results = [] + self._move([None, None, self.horizontal_move_z], speed) + def _invoke_callback(self, results): + # Flush lookahead queue + toolhead = self.printer.lookup_object('toolhead') + toolhead.get_last_move_time() + # Invoke callback + res = self.finalize_callback(self.probe_offsets, results) + return res != "retry" + def _move_next(self, probe_num): # Move to next XY probe point - nextpos = list(self.probe_points[len(self.results)]) + nextpos = list(self.probe_points[probe_num]) if self.use_offsets: nextpos[0] -= self.probe_offsets[0] nextpos[1] -= self.probe_offsets[1] - toolhead.manual_move(nextpos, self.speed) - return False + self._move(nextpos, self.speed) def start_probe(self, gcmd): manual_probe.verify_no_manual_probe(self.printer) # Lookup objects probe = self.printer.lookup_object('probe', None) method = gcmd.get('METHOD', 'automatic').lower() - self.results = [] def_move_z = self.default_horizontal_move_z self.horizontal_move_z = gcmd.get_float('HORIZONTAL_MOVE_Z', def_move_z) - if probe is None or method != 'automatic': + if probe is None or method == 'manual': # Manual probe self.lift_speed = self.speed self.probe_offsets = (0., 0., 0.) + self.manual_results = [] self._manual_probe_start() return # Perform automatic probing - self.lift_speed = probe.get_lift_speed(gcmd) + self.lift_speed = probe.get_probe_params(gcmd)['lift_speed'] self.probe_offsets = probe.get_offsets() if self.horizontal_move_z < self.probe_offsets[2]: raise gcmd.error("horizontal_move_z can't be less than" " probe's z_offset") - probe.multi_probe_begin() + probe_session = probe.start_probe_session(gcmd) + probe_num = 0 while 1: - done = self._move_next() - if done: - break - pos = probe.run_probe(gcmd) - self.results.append(pos) - probe.multi_probe_end() + self._raise_tool(not probe_num) + if probe_num >= len(self.probe_points): + results = probe_session.pull_probed_results() + done = self._invoke_callback(results) + if done: + break + # Caller wants a "retry" - restart probing + probe_num = 0 + self._move_next(probe_num) + probe_session.run_probe(gcmd) + probe_num += 1 + probe_session.end_probe_session() def _manual_probe_start(self): - done = self._move_next() - if not done: - gcmd = self.gcode.create_gcode_command("", "", {}) - manual_probe.ManualProbeHelper(self.printer, gcmd, - self._manual_probe_finalize) + self._raise_tool(not self.manual_results) + if len(self.manual_results) >= len(self.probe_points): + done = self._invoke_callback(self.manual_results) + if done: + return + # Caller wants a "retry" - clear results and restart probing + self.manual_results = [] + self._move_next(len(self.manual_results)) + gcmd = self.gcode.create_gcode_command("", "", {}) + manual_probe.ManualProbeHelper(self.printer, gcmd, + self._manual_probe_finalize) def _manual_probe_finalize(self, kin_pos): if kin_pos is None: return - self.results.append(kin_pos) + self.manual_results.append(kin_pos) self._manual_probe_start() +# Helper to obtain a single probe measurement +def run_single_probe(probe, gcmd): + probe_session = probe.start_probe_session(gcmd) + probe_session.run_probe(gcmd) + pos = probe_session.pull_probed_results()[0] + probe_session.end_probe_session() + return pos + + +###################################################################### +# Handle [probe] config +###################################################################### + +# Endstop wrapper that enables probe specific features +class ProbeEndstopWrapper: + def __init__(self, config): + self.printer = config.get_printer() + self.position_endstop = config.getfloat('z_offset') + self.stow_on_each_sample = config.getboolean( + 'deactivate_on_each_sample', True) + gcode_macro = self.printer.load_object(config, 'gcode_macro') + self.activate_gcode = gcode_macro.load_template( + config, 'activate_gcode', '') + self.deactivate_gcode = gcode_macro.load_template( + config, 'deactivate_gcode', '') + # Create an "endstop" object to handle the probe pin + ppins = self.printer.lookup_object('pins') + self.mcu_endstop = ppins.setup_pin('endstop', config.get('pin')) + # Wrappers + self.get_mcu = self.mcu_endstop.get_mcu + self.add_stepper = self.mcu_endstop.add_stepper + self.get_steppers = self.mcu_endstop.get_steppers + self.home_start = self.mcu_endstop.home_start + self.home_wait = self.mcu_endstop.home_wait + self.query_endstop = self.mcu_endstop.query_endstop + # multi probes state + self.multi = 'OFF' + def _raise_probe(self): + toolhead = self.printer.lookup_object('toolhead') + start_pos = toolhead.get_position() + self.deactivate_gcode.run_gcode_from_command() + if toolhead.get_position()[:3] != start_pos[:3]: + raise self.printer.command_error( + "Toolhead moved during probe deactivate_gcode script") + def _lower_probe(self): + toolhead = self.printer.lookup_object('toolhead') + start_pos = toolhead.get_position() + self.activate_gcode.run_gcode_from_command() + if toolhead.get_position()[:3] != start_pos[:3]: + raise self.printer.command_error( + "Toolhead moved during probe activate_gcode script") + def multi_probe_begin(self): + if self.stow_on_each_sample: + return + self.multi = 'FIRST' + def multi_probe_end(self): + if self.stow_on_each_sample: + return + self._raise_probe() + self.multi = 'OFF' + def probing_move(self, pos, speed): + phoming = self.printer.lookup_object('homing') + return phoming.probing_move(self, pos, speed) + def probe_prepare(self, hmove): + if self.multi == 'OFF' or self.multi == 'FIRST': + self._lower_probe() + if self.multi == 'FIRST': + self.multi = 'ON' + def probe_finish(self, hmove): + if self.multi == 'OFF': + self._raise_probe() + def get_position_endstop(self): + return self.position_endstop + +# Main external probe interface +class PrinterProbe: + def __init__(self, config): + self.printer = config.get_printer() + self.mcu_probe = ProbeEndstopWrapper(config) + self.cmd_helper = ProbeCommandHelper(config, self, + self.mcu_probe.query_endstop) + self.probe_offsets = ProbeOffsetsHelper(config) + self.probe_session = ProbeSessionHelper(config, self.mcu_probe) + def get_probe_params(self, gcmd=None): + return self.probe_session.get_probe_params(gcmd) + def get_offsets(self): + return self.probe_offsets.get_offsets() + def get_status(self, eventtime): + return self.cmd_helper.get_status(eventtime) + def start_probe_session(self, gcmd): + return self.probe_session.start_probe_session(gcmd) + def load_config(config): - return PrinterProbe(config, ProbeEndstopWrapper(config)) + return PrinterProbe(config) diff --git a/klippy/extras/probe_eddy_current.py b/klippy/extras/probe_eddy_current.py new file mode 100644 index 000000000000..345096e60b44 --- /dev/null +++ b/klippy/extras/probe_eddy_current.py @@ -0,0 +1,440 @@ +# Support for eddy current based Z probes +# +# Copyright (C) 2021-2024 Kevin O'Connor +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import logging, math, bisect +import mcu +from . import ldc1612, probe, manual_probe + +OUT_OF_RANGE = 99.9 + +# Tool for calibrating the sensor Z detection and applying that calibration +class EddyCalibration: + def __init__(self, config): + self.printer = config.get_printer() + self.name = config.get_name() + # Current calibration data + self.cal_freqs = [] + self.cal_zpos = [] + cal = config.get('calibrate', None) + if cal is not None: + cal = [list(map(float, d.strip().split(':', 1))) + for d in cal.split(',')] + self.load_calibration(cal) + # Probe calibrate state + self.probe_speed = 0. + # Register commands + cname = self.name.split()[-1] + gcode = self.printer.lookup_object('gcode') + gcode.register_mux_command("PROBE_EDDY_CURRENT_CALIBRATE", "CHIP", + cname, self.cmd_EDDY_CALIBRATE, + desc=self.cmd_EDDY_CALIBRATE_help) + def is_calibrated(self): + return len(self.cal_freqs) > 2 + def load_calibration(self, cal): + cal = sorted([(c[1], c[0]) for c in cal]) + self.cal_freqs = [c[0] for c in cal] + self.cal_zpos = [c[1] for c in cal] + def apply_calibration(self, samples): + for i, (samp_time, freq, dummy_z) in enumerate(samples): + pos = bisect.bisect(self.cal_freqs, freq) + if pos >= len(self.cal_zpos): + zpos = -OUT_OF_RANGE + elif pos == 0: + zpos = OUT_OF_RANGE + else: + # XXX - could further optimize and avoid div by zero + this_freq = self.cal_freqs[pos] + prev_freq = self.cal_freqs[pos - 1] + this_zpos = self.cal_zpos[pos] + prev_zpos = self.cal_zpos[pos - 1] + gain = (this_zpos - prev_zpos) / (this_freq - prev_freq) + offset = prev_zpos - prev_freq * gain + zpos = freq * gain + offset + samples[i] = (samp_time, freq, round(zpos, 6)) + def freq_to_height(self, freq): + dummy_sample = [(0., freq, 0.)] + self.apply_calibration(dummy_sample) + return dummy_sample[0][2] + def height_to_freq(self, height): + # XXX - could optimize lookup + rev_zpos = list(reversed(self.cal_zpos)) + rev_freqs = list(reversed(self.cal_freqs)) + pos = bisect.bisect(rev_zpos, height) + if pos == 0 or pos >= len(rev_zpos): + raise self.printer.command_error( + "Invalid probe_eddy_current height") + this_freq = rev_freqs[pos] + prev_freq = rev_freqs[pos - 1] + this_zpos = rev_zpos[pos] + prev_zpos = rev_zpos[pos - 1] + gain = (this_freq - prev_freq) / (this_zpos - prev_zpos) + offset = prev_freq - prev_zpos * gain + return height * gain + offset + def do_calibration_moves(self, move_speed): + toolhead = self.printer.lookup_object('toolhead') + kin = toolhead.get_kinematics() + move = toolhead.manual_move + # Start data collection + msgs = [] + is_finished = False + def handle_batch(msg): + if is_finished: + return False + msgs.append(msg) + return True + self.printer.lookup_object(self.name).add_client(handle_batch) + toolhead.dwell(1.) + # Move to each 40um position + max_z = 4.0 + samp_dist = 0.040 + req_zpos = [i*samp_dist for i in range(int(max_z / samp_dist) + 1)] + start_pos = toolhead.get_position() + times = [] + for zpos in req_zpos: + # Move to next position (always descending to reduce backlash) + hop_pos = list(start_pos) + hop_pos[2] += zpos + 0.500 + move(hop_pos, move_speed) + next_pos = list(start_pos) + next_pos[2] += zpos + move(next_pos, move_speed) + # Note sample timing + start_query_time = toolhead.get_last_move_time() + 0.050 + end_query_time = start_query_time + 0.100 + toolhead.dwell(0.200) + # Find Z position based on actual commanded stepper position + toolhead.flush_step_generation() + kin_spos = {s.get_name(): s.get_commanded_position() + for s in kin.get_steppers()} + kin_pos = kin.calc_position(kin_spos) + times.append((start_query_time, end_query_time, kin_pos[2])) + toolhead.dwell(1.0) + toolhead.wait_moves() + # Finish data collection + is_finished = True + # Correlate query responses + cal = {} + step = 0 + for msg in msgs: + for query_time, freq, old_z in msg['data']: + # Add to step tracking + while step < len(times) and query_time > times[step][1]: + step += 1 + if step < len(times) and query_time >= times[step][0]: + cal.setdefault(times[step][2], []).append(freq) + if len(cal) != len(times): + raise self.printer.command_error( + "Failed calibration - incomplete sensor data") + return cal + def calc_freqs(self, meas): + total_count = total_variance = 0 + positions = {} + for pos, freqs in meas.items(): + count = len(freqs) + freq_avg = float(sum(freqs)) / count + positions[pos] = freq_avg + total_count += count + total_variance += sum([(f - freq_avg)**2 for f in freqs]) + return positions, math.sqrt(total_variance / total_count), total_count + def post_manual_probe(self, kin_pos): + if kin_pos is None: + # Manual Probe was aborted + return + curpos = list(kin_pos) + move = self.printer.lookup_object('toolhead').manual_move + # Move away from the bed + probe_calibrate_z = curpos[2] + curpos[2] += 5. + move(curpos, self.probe_speed) + # Move sensor over nozzle position + pprobe = self.printer.lookup_object("probe") + x_offset, y_offset, z_offset = pprobe.get_offsets() + curpos[0] -= x_offset + curpos[1] -= y_offset + move(curpos, self.probe_speed) + # Descend back to bed + curpos[2] -= 5. - 0.050 + move(curpos, self.probe_speed) + # Perform calibration movement and capture + cal = self.do_calibration_moves(self.probe_speed) + # Calculate each sample position average and variance + positions, std, total = self.calc_freqs(cal) + last_freq = 0. + for pos, freq in reversed(sorted(positions.items())): + if freq <= last_freq: + raise self.printer.command_error( + "Failed calibration - frequency not increasing each step") + last_freq = freq + gcode = self.printer.lookup_object("gcode") + gcode.respond_info( + "probe_eddy_current: stddev=%.3f in %d queries\n" + "The SAVE_CONFIG command will update the printer config file\n" + "and restart the printer." % (std, total)) + # Save results + cal_contents = [] + for i, (pos, freq) in enumerate(sorted(positions.items())): + if not i % 3: + cal_contents.append('\n') + cal_contents.append("%.6f:%.3f" % (pos - probe_calibrate_z, freq)) + cal_contents.append(',') + cal_contents.pop() + configfile = self.printer.lookup_object('configfile') + configfile.set(self.name, 'calibrate', ''.join(cal_contents)) + cmd_EDDY_CALIBRATE_help = "Calibrate eddy current probe" + def cmd_EDDY_CALIBRATE(self, gcmd): + self.probe_speed = gcmd.get_float("PROBE_SPEED", 5., above=0.) + # Start manual probe + manual_probe.ManualProbeHelper(self.printer, gcmd, + self.post_manual_probe) + +# Tool to gather samples and convert them to probe positions +class EddyGatherSamples: + def __init__(self, printer, sensor_helper, calibration, z_offset): + self._printer = printer + self._sensor_helper = sensor_helper + self._calibration = calibration + self._z_offset = z_offset + # Results storage + self._samples = [] + self._probe_times = [] + self._probe_results = [] + self._need_stop = False + # Start samples + if not self._calibration.is_calibrated(): + raise self._printer.command_error( + "Must calibrate probe_eddy_current first") + sensor_helper.add_client(self._add_measurement) + def _add_measurement(self, msg): + if self._need_stop: + del self._samples[:] + return False + self._samples.append(msg) + self._check_samples() + return True + def finish(self): + self._need_stop = True + def _await_samples(self): + # Make sure enough samples have been collected + reactor = self._printer.get_reactor() + mcu = self._sensor_helper.get_mcu() + while self._probe_times: + start_time, end_time, pos_time, toolhead_pos = self._probe_times[0] + systime = reactor.monotonic() + est_print_time = mcu.estimated_print_time(systime) + if est_print_time > end_time + 1.0: + raise self._printer.command_error( + "probe_eddy_current sensor outage") + reactor.pause(systime + 0.010) + def _pull_freq(self, start_time, end_time): + # Find average sensor frequency between time range + msg_num = discard_msgs = 0 + samp_sum = 0. + samp_count = 0 + while msg_num < len(self._samples): + msg = self._samples[msg_num] + msg_num += 1 + data = msg['data'] + if data[0][0] > end_time: + break + if data[-1][0] < start_time: + discard_msgs = msg_num + continue + for time, freq, z in data: + if time >= start_time and time <= end_time: + samp_sum += freq + samp_count += 1 + del self._samples[:discard_msgs] + if not samp_count: + # No sensor readings - raise error in pull_probed() + return 0. + return samp_sum / samp_count + def _lookup_toolhead_pos(self, pos_time): + toolhead = self._printer.lookup_object('toolhead') + kin = toolhead.get_kinematics() + kin_spos = {s.get_name(): s.mcu_to_commanded_position( + s.get_past_mcu_position(pos_time)) + for s in kin.get_steppers()} + return kin.calc_position(kin_spos) + def _check_samples(self): + while self._samples and self._probe_times: + start_time, end_time, pos_time, toolhead_pos = self._probe_times[0] + if self._samples[-1]['data'][-1][0] < end_time: + break + freq = self._pull_freq(start_time, end_time) + if pos_time is not None: + toolhead_pos = self._lookup_toolhead_pos(pos_time) + self._probe_results.append((freq, toolhead_pos)) + self._probe_times.pop(0) + def pull_probed(self): + self._await_samples() + results = [] + for freq, toolhead_pos in self._probe_results: + if not freq: + raise self._printer.command_error( + "Unable to obtain probe_eddy_current sensor readings") + sensor_z = self._calibration.freq_to_height(freq) + if sensor_z <= -OUT_OF_RANGE or sensor_z >= OUT_OF_RANGE: + raise self._printer.command_error( + "probe_eddy_current sensor not in valid range") + # Callers expect position relative to z_offset, so recalculate + bed_deviation = toolhead_pos[2] - sensor_z + toolhead_pos[2] = self._z_offset + bed_deviation + results.append(toolhead_pos) + del self._probe_results[:] + return results + def note_probe(self, start_time, end_time, toolhead_pos): + self._probe_times.append((start_time, end_time, None, toolhead_pos)) + self._check_samples() + def note_probe_and_position(self, start_time, end_time, pos_time): + self._probe_times.append((start_time, end_time, pos_time, None)) + self._check_samples() + +# Helper for implementing PROBE style commands (descend until trigger) +class EddyEndstopWrapper: + REASON_SENSOR_ERROR = mcu.MCU_trsync.REASON_COMMS_TIMEOUT + 1 + def __init__(self, config, sensor_helper, calibration): + self._printer = config.get_printer() + self._sensor_helper = sensor_helper + self._mcu = sensor_helper.get_mcu() + self._calibration = calibration + self._z_offset = config.getfloat('z_offset', minval=0.) + self._dispatch = mcu.TriggerDispatch(self._mcu) + self._trigger_time = 0. + self._gather = None + # Interface for MCU_endstop + def get_mcu(self): + return self._mcu + def add_stepper(self, stepper): + self._dispatch.add_stepper(stepper) + def get_steppers(self): + return self._dispatch.get_steppers() + def home_start(self, print_time, sample_time, sample_count, rest_time, + triggered=True): + self._trigger_time = 0. + trigger_freq = self._calibration.height_to_freq(self._z_offset) + trigger_completion = self._dispatch.start(print_time) + self._sensor_helper.setup_home( + print_time, trigger_freq, self._dispatch.get_oid(), + mcu.MCU_trsync.REASON_ENDSTOP_HIT, self.REASON_SENSOR_ERROR) + return trigger_completion + def home_wait(self, home_end_time): + self._dispatch.wait_end(home_end_time) + trigger_time = self._sensor_helper.clear_home() + res = self._dispatch.stop() + if res >= mcu.MCU_trsync.REASON_COMMS_TIMEOUT: + if res == mcu.MCU_trsync.REASON_COMMS_TIMEOUT: + raise self._printer.command_error( + "Communication timeout during homing") + raise self._printer.command_error("Eddy current sensor error") + if res != mcu.MCU_trsync.REASON_ENDSTOP_HIT: + return 0. + if self._mcu.is_fileoutput(): + return home_end_time + self._trigger_time = trigger_time + return trigger_time + def query_endstop(self, print_time): + return False # XXX + # Interface for ProbeEndstopWrapper + def probing_move(self, pos, speed): + # Perform probing move + phoming = self._printer.lookup_object('homing') + trig_pos = phoming.probing_move(self, pos, speed) + if not self._trigger_time: + return trig_pos + # Extract samples + start_time = self._trigger_time + 0.050 + end_time = start_time + 0.100 + toolhead = self._printer.lookup_object("toolhead") + toolhead_pos = toolhead.get_position() + self._gather.note_probe(start_time, end_time, toolhead_pos) + return self._gather.pull_probed()[0] + def multi_probe_begin(self): + self._gather = EddyGatherSamples(self._printer, self._sensor_helper, + self._calibration, self._z_offset) + def multi_probe_end(self): + self._gather.finish() + self._gather = None + def probe_prepare(self, hmove): + pass + def probe_finish(self, hmove): + pass + def get_position_endstop(self): + return self._z_offset + +# Implementing probing with "METHOD=scan" +class EddyScanningProbe: + def __init__(self, printer, sensor_helper, calibration, z_offset, gcmd): + self._printer = printer + self._sensor_helper = sensor_helper + self._calibration = calibration + self._z_offset = z_offset + self._gather = EddyGatherSamples(printer, sensor_helper, + calibration, z_offset) + self._sample_time_delay = 0.050 + self._sample_time = gcmd.get_float("SAMPLE_TIME", 0.100, above=0.0) + self._is_rapid = gcmd.get("METHOD", "scan") == 'rapid_scan' + def _rapid_lookahead_cb(self, printtime): + start_time = printtime - self._sample_time / 2 + self._gather.note_probe_and_position( + start_time, start_time + self._sample_time, printtime) + def run_probe(self, gcmd): + toolhead = self._printer.lookup_object("toolhead") + if self._is_rapid: + toolhead.register_lookahead_callback(self._rapid_lookahead_cb) + return + printtime = toolhead.get_last_move_time() + toolhead.dwell(self._sample_time_delay + self._sample_time) + start_time = printtime + self._sample_time_delay + self._gather.note_probe_and_position( + start_time, start_time + self._sample_time, start_time) + def pull_probed_results(self): + if self._is_rapid: + # Flush lookahead (so all lookahead callbacks are invoked) + toolhead = self._printer.lookup_object("toolhead") + toolhead.get_last_move_time() + results = self._gather.pull_probed() + # Allow axis_twist_compensation to update results + for epos in results: + self._printer.send_event("probe:update_results", epos) + return results + def end_probe_session(self): + self._gather.finish() + self._gather = None + +# Main "printer object" +class PrinterEddyProbe: + def __init__(self, config): + self.printer = config.get_printer() + self.calibration = EddyCalibration(config) + # Sensor type + sensors = { "ldc1612": ldc1612.LDC1612 } + sensor_type = config.getchoice('sensor_type', {s: s for s in sensors}) + self.sensor_helper = sensors[sensor_type](config, self.calibration) + # Probe interface + self.mcu_probe = EddyEndstopWrapper(config, self.sensor_helper, + self.calibration) + self.cmd_helper = probe.ProbeCommandHelper( + config, self, self.mcu_probe.query_endstop) + self.probe_offsets = probe.ProbeOffsetsHelper(config) + self.probe_session = probe.ProbeSessionHelper(config, self.mcu_probe) + self.printer.add_object('probe', self) + def add_client(self, cb): + self.sensor_helper.add_client(cb) + def get_probe_params(self, gcmd=None): + return self.probe_session.get_probe_params(gcmd) + def get_offsets(self): + return self.probe_offsets.get_offsets() + def get_status(self, eventtime): + return self.cmd_helper.get_status(eventtime) + def start_probe_session(self, gcmd): + method = gcmd.get('METHOD', 'automatic').lower() + if method in ('scan', 'rapid_scan'): + z_offset = self.get_offsets()[2] + return EddyScanningProbe(self.printer, self.sensor_helper, + self.calibration, z_offset, gcmd) + return self.probe_session.start_probe_session(gcmd) + +def load_config_prefix(config): + return PrinterEddyProbe(config) diff --git a/klippy/extras/pwm_cycle_time.py b/klippy/extras/pwm_cycle_time.py new file mode 100644 index 000000000000..cebbec7512b8 --- /dev/null +++ b/klippy/extras/pwm_cycle_time.py @@ -0,0 +1,123 @@ +# Handle pwm output pins with variable frequency +# +# Copyright (C) 2017-2023 Kevin O'Connor +# +# This file may be distributed under the terms of the GNU GPLv3 license. + +PIN_MIN_TIME = 0.100 +MAX_SCHEDULE_TIME = 5.0 + +class MCU_pwm_cycle: + def __init__(self, pin_params, cycle_time, start_value, shutdown_value): + self._mcu = pin_params['chip'] + self._cycle_time = cycle_time + self._oid = None + self._mcu.register_config_callback(self._build_config) + self._pin = pin_params['pin'] + self._invert = pin_params['invert'] + 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)) + self._last_clock = self._cycle_ticks = 0 + self._set_cmd = self._set_cycle_ticks = None + def _build_config(self): + 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) + if self._shutdown_value not in [0., 1.]: + raise self._mcu.get_printer().config_error( + "shutdown value must be 0.0 or 1.0 on soft pwm") + if cycle_ticks >= 1<<31: + raise self._mcu.get_printer().config_error( + "PWM pin cycle time too large") + self._mcu.request_move_queue_slot() + self._oid = self._mcu.create_oid() + 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, 0)) + 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 = self._mcu.lookup_command( + "queue_digital_out oid=%c clock=%u on_ticks=%u", cq=cmd_queue) + self._set_cycle_ticks = self._mcu.lookup_command( + "set_digital_out_pwm_cycle oid=%c cycle_ticks=%u", cq=cmd_queue) + def set_pwm_cycle(self, print_time, value, cycle_time): + clock = self._mcu.print_time_to_clock(print_time) + minclock = self._last_clock + # Send updated cycle_time if necessary + cycle_ticks = self._mcu.seconds_to_clock(cycle_time) + if cycle_ticks != self._cycle_ticks: + if cycle_ticks >= 1<<31: + raise self._mcu.get_printer().command_error( + "PWM cycle time too large") + self._set_cycle_ticks.send([self._oid, cycle_ticks], + minclock=minclock, reqclock=clock) + self._cycle_ticks = cycle_ticks + # Send pwm update + if self._invert: + value = 1. - value + v = int(max(0., min(1., value)) * float(self._cycle_ticks) + 0.5) + self._set_cmd.send([self._oid, clock, v], + minclock=self._last_clock, reqclock=clock) + self._last_clock = clock + +class PrinterOutputPWMCycle: + def __init__(self, config): + self.printer = config.get_printer() + self.last_print_time = 0. + cycle_time = config.getfloat('cycle_time', 0.100, above=0., + maxval=MAX_SCHEDULE_TIME) + self.last_cycle_time = self.default_cycle_time = cycle_time + # Determine start and shutdown values + self.scale = config.getfloat('scale', 1., above=0.) + 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 + # Create pwm pin object + ppins = self.printer.lookup_object('pins') + pin_params = ppins.lookup_pin(config.get('pin'), can_invert=True) + self.mcu_pin = MCU_pwm_cycle(pin_params, cycle_time, + 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, cycle_time): + if value == self.last_value and cycle_time == self.last_cycle_time: + return + print_time = max(print_time, self.last_print_time + PIN_MIN_TIME) + self.mcu_pin.set_pwm_cycle(print_time, value, cycle_time) + self.last_value = value + self.last_cycle_time = cycle_time + 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 + cycle_time = gcmd.get_float('CYCLE_TIME', self.default_cycle_time, + above=0., maxval=MAX_SCHEDULE_TIME) + # 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, cycle_time)) + +def load_config_prefix(config): + return PrinterOutputPWMCycle(config) diff --git a/klippy/extras/pwm_tool.py b/klippy/extras/pwm_tool.py index 5fc09eab9883..aa95ecbef1cc 100644 --- a/klippy/extras/pwm_tool.py +++ b/klippy/extras/pwm_tool.py @@ -6,7 +6,6 @@ import chelper MAX_SCHEDULE_TIME = 5.0 -CLOCK_SYNC_EXTRA_TIME = 0.050 class error(Exception): pass @@ -118,8 +117,7 @@ def _send_update(self, clock, val): # Continue flushing to resend time wakeclock += self._duration_ticks wake_print_time = self._mcu.clock_to_print_time(wakeclock) - self._toolhead.note_kinematic_activity(wake_print_time - + CLOCK_SYNC_EXTRA_TIME) + self._toolhead.note_mcu_movequeue_activity(wake_print_time) def set_pwm(self, print_time, value): clock = self._mcu.print_time_to_clock(print_time) if self._invert: diff --git a/klippy/extras/replicape.py b/klippy/extras/replicape.py index 4c3762974654..f7f7bb64bd22 100644 --- a/klippy/extras/replicape.py +++ b/klippy/extras/replicape.py @@ -30,7 +30,6 @@ def __init__(self, replicape, channel, pin_type, pin_params): self._invert = pin_params['invert'] self._start_value = self._shutdown_value = float(self._invert) self._is_enable = not not self._start_value - self._is_static = False self._last_clock = 0 self._pwm_max = 0. self._set_cmd = None @@ -44,28 +43,18 @@ def setup_cycle_time(self, cycle_time, hardware_pwm=False): if cycle_time != self._cycle_time: logging.info("Ignoring pca9685 cycle time of %.6f (using %.6f)", cycle_time, self._cycle_time) - def setup_start_value(self, start_value, shutdown_value, is_static=False): - if is_static and start_value != shutdown_value: - raise pins.error("Static pin can not have shutdown value") + 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)) - self._is_static = is_static self._replicape.note_pwm_start_value( self._channel, self._start_value, self._shutdown_value) self._is_enable = not not self._start_value def _build_config(self): self._pwm_max = self._mcu.get_constant_float("PCA9685_MAX") cycle_ticks = self._mcu.seconds_to_clock(self._cycle_time) - if self._is_static: - self._mcu.add_config_cmd( - "set_pca9685_out bus=%d addr=%d channel=%d" - " cycle_ticks=%d value=%d" % ( - self._bus, self._address, self._channel, - cycle_ticks, self._start_value * self._pwm_max)) - return self._mcu.request_move_queue_slot() self._oid = self._mcu.create_oid() self._mcu.add_config_cmd( @@ -78,7 +67,7 @@ def _build_config(self): cmd_queue = self._mcu.alloc_command_queue() self._set_cmd = self._mcu.lookup_command( "queue_pca9685_out oid=%c clock=%u value=%hu", cq=cmd_queue) - def set_pwm(self, print_time, value, cycle_time=None): + def set_pwm(self, print_time, value): clock = self._mcu.print_time_to_clock(print_time) if self._invert: value = 1. - value @@ -171,7 +160,7 @@ def __init__(self, config): printer = config.get_printer() ppins = printer.lookup_object('pins') ppins.register_chip('replicape', self) - revisions = {'B3': 'B3'} + revisions = ['B3'] config.getchoice('revision', revisions) self.host_mcu = mcu.get_printer_mcu(printer, config.get('host_mcu')) # Setup enable pin diff --git a/klippy/extras/resonance_tester.py b/klippy/extras/resonance_tester.py index d5f43b77f531..fe8717d58545 100644 --- a/klippy/extras/resonance_tester.py +++ b/klippy/extras/resonance_tester.py @@ -1,6 +1,6 @@ # A utility class to test resonances of the printer # -# Copyright (C) 2020 Dmitry Butyugin +# Copyright (C) 2020-2024 Dmitry Butyugin # # This file may be distributed under the terms of the GNU GPLv3 license. import logging, math, os, time @@ -52,7 +52,7 @@ def __init__(self, config): self.min_freq = config.getfloat('min_freq', 5., minval=1.) # Defaults are such that max_freq * accel_per_hz == 10000 (max_accel) self.max_freq = config.getfloat('max_freq', 10000. / 75., - minval=self.min_freq, maxval=200.) + minval=self.min_freq, maxval=300.) self.accel_per_hz = config.getfloat('accel_per_hz', 75., above=0.) self.hz_per_sec = config.getfloat('hz_per_sec', 1., minval=0.1, maxval=2.) @@ -64,7 +64,7 @@ def get_start_test_points(self): def prepare_test(self, gcmd): self.freq_start = gcmd.get_float("FREQ_START", self.min_freq, minval=1.) self.freq_end = gcmd.get_float("FREQ_END", self.max_freq, - minval=self.freq_start, maxval=200.) + minval=self.freq_start, maxval=300.) self.hz_per_sec = gcmd.get_float("HZ_PER_SEC", self.hz_per_sec, above=0., maxval=2.) def run_test(self, axis, gcmd): @@ -77,11 +77,11 @@ def run_test(self, axis, gcmd): systime = self.printer.get_reactor().monotonic() toolhead_info = toolhead.get_status(systime) old_max_accel = toolhead_info['max_accel'] - old_max_accel_to_decel = toolhead_info['max_accel_to_decel'] + old_minimum_cruise_ratio = toolhead_info['minimum_cruise_ratio'] max_accel = self.freq_end * self.accel_per_hz self.gcode.run_script_from_command( - "SET_VELOCITY_LIMIT ACCEL=%.3f ACCEL_TO_DECEL=%.3f" % ( - max_accel, max_accel)) + "SET_VELOCITY_LIMIT ACCEL=%.3f MINIMUM_CRUISE_RATIO=0" + % (max_accel,)) input_shaper = self.printer.lookup_object('input_shaper', None) if input_shaper is not None and not gcmd.get_int('INPUT_SHAPING', 0): input_shaper.disable_shaping() @@ -108,12 +108,14 @@ def run_test(self, axis, gcmd): gcmd.respond_info("Testing frequency %.0f Hz" % (freq,)) # Restore the original acceleration values self.gcode.run_script_from_command( - "SET_VELOCITY_LIMIT ACCEL=%.3f ACCEL_TO_DECEL=%.3f" % ( - old_max_accel, old_max_accel_to_decel)) + "SET_VELOCITY_LIMIT ACCEL=%.3f MINIMUM_CRUISE_RATIO=%.3f" + % (old_max_accel, old_minimum_cruise_ratio)) # Restore input shaper if it was disabled for resonance testing if input_shaper is not None: input_shaper.enable_shaping() gcmd.respond_info("Re-enabled [input_shaper]") + def get_max_freq(self): + return self.freq_end class ResonanceTester: def __init__(self, config): @@ -217,6 +219,8 @@ def _parse_chips(self, accel_chips): chip = self.printer.lookup_object(chip_lookup_name) parsed_chips.append(chip) return parsed_chips + def _get_max_calibration_freq(self): + return 1.5 * self.test.get_max_freq() cmd_TEST_RESONANCES_help = ("Runs the resonance test for a specifed axis") def cmd_TEST_RESONANCES(self, gcmd): # Parse parameters @@ -261,9 +265,9 @@ def cmd_TEST_RESONANCES(self, gcmd): raw_name_suffix=name_suffix if raw_output else None, accel_chips=accel_chips, test_point=test_point)[axis] if csv_output: - csv_name = self.save_calibration_data('resonances', name_suffix, - helper, axis, data, - point=test_point) + csv_name = self.save_calibration_data( + 'resonances', name_suffix, helper, axis, data, + point=test_point, max_freq=self._get_max_calibration_freq()) gcmd.respond_info( "Resonances data written to %s file" % (csv_name,)) cmd_SHAPER_CALIBRATE_help = ( @@ -302,8 +306,14 @@ def cmd_SHAPER_CALIBRATE(self, gcmd): "Calculating the best input shaper parameters for %s axis" % (axis_name,)) calibration_data[axis].normalize_to_frequencies() + systime = self.printer.get_reactor().monotonic() + toolhead = self.printer.lookup_object('toolhead') + toolhead_info = toolhead.get_status(systime) + scv = toolhead_info['square_corner_velocity'] + max_freq = self._get_max_calibration_freq() best_shaper, all_shapers = helper.find_best_shaper( - calibration_data[axis], max_smoothing, gcmd.respond_info) + calibration_data[axis], max_smoothing=max_smoothing, + scv=scv, max_freq=max_freq, logger=gcmd.respond_info) gcmd.respond_info( "Recommended shaper_type_%s = %s, shaper_freq_%s = %.1f Hz" % (axis_name, best_shaper.name, @@ -315,7 +325,7 @@ def cmd_SHAPER_CALIBRATE(self, gcmd): best_shaper.name, best_shaper.freq) csv_name = self.save_calibration_data( 'calibration_data', name_suffix, helper, axis, - calibration_data[axis], all_shapers) + calibration_data[axis], all_shapers, max_freq=max_freq) gcmd.respond_info( "Shaper calibration data written to %s file" % (csv_name,)) gcmd.respond_info( @@ -361,10 +371,10 @@ def get_filename(self, base, name_suffix, axis=None, def save_calibration_data(self, base_name, name_suffix, shaper_calibrate, axis, calibration_data, - all_shapers=None, point=None): + all_shapers=None, point=None, max_freq=None): output = self.get_filename(base_name, name_suffix, axis, point) shaper_calibrate.save_calibration_data(output, calibration_data, - all_shapers) + all_shapers, max_freq) return output def load_config(config): diff --git a/klippy/extras/shaper_calibrate.py b/klippy/extras/shaper_calibrate.py index af77845c5cd5..6891fefb3bf9 100644 --- a/klippy/extras/shaper_calibrate.py +++ b/klippy/extras/shaper_calibrate.py @@ -1,6 +1,6 @@ # Automatic calibration of input shapers # -# Copyright (C) 2020 Dmitry Butyugin +# Copyright (C) 2020-2024 Dmitry Butyugin # # This file may be distributed under the terms of the GNU GPLv3 license. import collections, importlib, logging, math, multiprocessing, traceback @@ -227,34 +227,49 @@ def _get_shaper_smoothing(self, shaper, accel=5000, scv=5.): offset_180 *= inv_D return max(offset_90, offset_180) - def fit_shaper(self, shaper_cfg, calibration_data, max_smoothing): + def fit_shaper(self, shaper_cfg, calibration_data, shaper_freqs, + damping_ratio, scv, max_smoothing, test_damping_ratios, + max_freq): np = self.numpy - test_freqs = np.arange(shaper_cfg.min_freq, MAX_SHAPER_FREQ, .2) + damping_ratio = damping_ratio or shaper_defs.DEFAULT_DAMPING_RATIO + test_damping_ratios = test_damping_ratios or TEST_DAMPING_RATIOS + + if not shaper_freqs: + shaper_freqs = (None, None, None) + if isinstance(shaper_freqs, tuple): + freq_end = shaper_freqs[1] or MAX_SHAPER_FREQ + freq_start = min(shaper_freqs[0] or shaper_cfg.min_freq, + freq_end - 1e-7) + freq_step = shaper_freqs[2] or .2 + test_freqs = np.arange(freq_start, freq_end, freq_step) + else: + test_freqs = np.array(shaper_freqs) + + max_freq = max(max_freq or MAX_FREQ, test_freqs.max()) freq_bins = calibration_data.freq_bins - psd = calibration_data.psd_sum[freq_bins <= MAX_FREQ] - freq_bins = freq_bins[freq_bins <= MAX_FREQ] + psd = calibration_data.psd_sum[freq_bins <= max_freq] + freq_bins = freq_bins[freq_bins <= max_freq] best_res = None results = [] for test_freq in test_freqs[::-1]: shaper_vibrations = 0. shaper_vals = np.zeros(shape=freq_bins.shape) - shaper = shaper_cfg.init_func( - test_freq, shaper_defs.DEFAULT_DAMPING_RATIO) - shaper_smoothing = self._get_shaper_smoothing(shaper) + shaper = shaper_cfg.init_func(test_freq, damping_ratio) + shaper_smoothing = self._get_shaper_smoothing(shaper, scv=scv) if max_smoothing and shaper_smoothing > max_smoothing and best_res: return best_res # Exact damping ratio of the printer is unknown, pessimizing # remaining vibrations over possible damping values - for dr in TEST_DAMPING_RATIOS: + for dr in test_damping_ratios: vibrations, vals = self._estimate_remaining_vibrations( shaper, dr, freq_bins, psd) shaper_vals = np.maximum(shaper_vals, vals) if vibrations > shaper_vibrations: shaper_vibrations = vibrations - max_accel = self.find_shaper_max_accel(shaper) + max_accel = self.find_shaper_max_accel(shaper, scv) # The score trying to minimize vibrations, but also accounting # the growth of smoothing. The formula itself does not have any # special meaning, it simply shows good results on real user data @@ -278,6 +293,8 @@ def fit_shaper(self, shaper_cfg, calibration_data, max_smoothing): def _bisect(self, func): left = right = 1. + if not func(1e-9): + return 0. while not func(left): right = left left *= .5 @@ -292,22 +309,27 @@ def _bisect(self, func): right = middle return left - def find_shaper_max_accel(self, shaper): + def find_shaper_max_accel(self, shaper, scv): # Just some empirically chosen value which produces good projections # for max_accel without much smoothing TARGET_SMOOTHING = 0.12 max_accel = self._bisect(lambda test_accel: self._get_shaper_smoothing( - shaper, test_accel) <= TARGET_SMOOTHING) + shaper, test_accel, scv) <= TARGET_SMOOTHING) return max_accel - def find_best_shaper(self, calibration_data, max_smoothing, logger=None): + def find_best_shaper(self, calibration_data, shapers=None, + damping_ratio=None, scv=None, shaper_freqs=None, + max_smoothing=None, test_damping_ratios=None, + max_freq=None, logger=None): best_shaper = None all_shapers = [] + shapers = shapers or AUTOTUNE_SHAPERS for shaper_cfg in shaper_defs.INPUT_SHAPERS: - if shaper_cfg.name not in AUTOTUNE_SHAPERS: + if shaper_cfg.name not in shapers: continue shaper = self.background_process_exec(self.fit_shaper, ( - shaper_cfg, calibration_data, max_smoothing)) + shaper_cfg, calibration_data, shaper_freqs, damping_ratio, + scv, max_smoothing, test_damping_ratios, max_freq)) if logger is not None: logger("Fitted shaper '%s' frequency = %.1f Hz " "(vibrations = %.1f%%, smoothing ~= %.3f)" % ( @@ -346,8 +368,10 @@ def apply_params(self, input_shaper, axis, shaper_name, shaper_freq): "SHAPER_TYPE_" + axis: shaper_name, "SHAPER_FREQ_" + axis: shaper_freq})) - def save_calibration_data(self, output, calibration_data, shapers=None): + def save_calibration_data(self, output, calibration_data, shapers=None, + max_freq=None): try: + max_freq = max_freq or MAX_FREQ with open(output, "w") as csvfile: csvfile.write("freq,psd_x,psd_y,psd_z,psd_xyz") if shapers: @@ -356,7 +380,7 @@ def save_calibration_data(self, output, calibration_data, shapers=None): csvfile.write("\n") num_freqs = calibration_data.freq_bins.shape[0] for i in range(num_freqs): - if calibration_data.freq_bins[i] >= MAX_FREQ: + if calibration_data.freq_bins[i] >= max_freq: break csvfile.write("%.1f,%.3e,%.3e,%.3e,%.3e" % ( calibration_data.freq_bins[i], diff --git a/klippy/extras/sht3x.py b/klippy/extras/sht3x.py new file mode 100644 index 000000000000..699d3f2095f4 --- /dev/null +++ b/klippy/extras/sht3x.py @@ -0,0 +1,165 @@ +# SHT3X i2c based temperature sensors support +# +# Copyright (C) 2024 Timofey Titovets +# Based on htu21d.py code +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import logging +from . import bus + +###################################################################### +# Compatible Sensors: +# SHT31 - Tested on octopus pro and Linux MCU +# +###################################################################### +SHT3X_I2C_ADDR = 0x44 + +SHT3X_CMD = { + 'MEASURE': { + 'STRETCH_ENABLED': { + 'HIGH_REP': [0x2c, 0x06], # High (15ms) repeatability measurement + 'MED_REP': [0x2c, 0x0D], # Medium (6ms) repeatability measurement + 'LOW_REP': [0x2c, 0x10], # Low (4ms) repeatability measurement + }, + 'STRETCH_DISABLED' : { + 'HIGH_REP': [0x24, 0x00], + 'MED_REP': [0x24, 0x0B], + 'LOW_REP': [0x24, 0x16], + }, + }, + 'OTHER': { + 'STATUS': { + 'READ': [0xF3, 0x2D], + 'CLEAN': [0x30, 0x41], + }, + 'SOFTRESET': [0x30, 0xA2], # Soft reset + 'HEATER': { + "ENABLE": [0x30, 0x6D], + "DISABLE": [0x30, 0x66], + }, + 'FETCH': [0xE0, 0x00], + 'BREAK': [0x30, 0x93], + } +} + +class SHT3X: + def __init__(self, config): + self.printer = config.get_printer() + self.name = config.get_name().split()[-1] + self.reactor = self.printer.get_reactor() + self.i2c = bus.MCU_I2C_from_config( + config, default_addr=SHT3X_I2C_ADDR, default_speed=100000) + self.report_time = config.getint('sht3x_report_time', 1, minval=1) + self.deviceId = config.get('sensor_type') + self.temp = self.min_temp = self.max_temp = self.humidity = 0. + self.sample_timer = self.reactor.register_timer(self._sample_sht3x) + self.printer.add_object("sht3x " + self.name, self) + self.printer.register_event_handler("klippy:connect", + self.handle_connect) + def handle_connect(self): + self._init_sht3x() + self.reactor.update_timer(self.sample_timer, self.reactor.NOW) + + def setup_minmax(self, min_temp, max_temp): + self.min_temp = min_temp + self.max_temp = max_temp + + def setup_callback(self, cb): + self._callback = cb + + def get_report_time_delta(self): + return self.report_time + + def _init_sht3x(self): + # Device Soft Reset + self.i2c.i2c_write(SHT3X_CMD['OTHER']['SOFTRESET']) + + # Wait 2ms after reset + self.reactor.pause(self.reactor.monotonic() + .02) + + status = self.i2c.i2c_read(SHT3X_CMD['OTHER']['STATUS']['READ'], 3) + response = bytearray(status['response']) + status = response[0] << 8 + status |= response[1] + checksum = response[2] + + if self._crc8(status) != checksum: + logging.warning("sht3x: Reading status - checksum error!") + + def _sample_sht3x(self, eventtime): + try: + # Read Temeprature + params = self.i2c.i2c_write( + SHT3X_CMD['MEASURE']['STRETCH_ENABLED']['HIGH_REP'] + ) + # Wait + self.reactor.pause(self.reactor.monotonic() + + .20) + + params = self.i2c.i2c_read([], 6) + + response = bytearray(params['response']) + rtemp = response[0] << 8 + rtemp |= response[1] + if self._crc8(rtemp) != response[2]: + logging.warning( + "sht3x: Checksum error on Temperature reading!" + ) + else: + self.temp = -45 + (175 * rtemp / 65535) + logging.debug("sht3x: Temperature %.2f " % self.temp) + + rhumid = response[3] << 8 + rhumid |= response[4] + if self._crc8(rhumid) != response[5]: + logging.warning("sht3x: Checksum error on Humidity reading!") + else: + self.humidity = 100 * rhumid / 65535 + logging.debug("sht3x: Humidity %.2f " % self.humidity) + + except Exception: + logging.exception("sht3x: Error reading data") + self.temp = self.humidity = .0 + return self.reactor.NEVER + + if self.temp < self.min_temp or self.temp > self.max_temp: + self.printer.invoke_shutdown( + "sht3x: temperature %0.1f outside range of %0.1f:%.01f" + % (self.temp, self.min_temp, self.max_temp)) + + measured_time = self.reactor.monotonic() + print_time = self.i2c.get_mcu().estimated_print_time(measured_time) + self._callback(print_time, self.temp) + return measured_time + self.report_time + + def _split_bytes(self, data): + bytes = [] + for i in range((data.bit_length() + 7) // 8): + bytes.append((data >> i*8) & 0xFF) + bytes.reverse() + return bytes + + def _crc8(self, data): + #crc8 polynomial for 16bit value, CRC8 -> x^8 + x^5 + x^4 + 1 + SHT3X_CRC8_POLYNOMINAL= 0x31 + crc = 0xFF + data_bytes = self._split_bytes(data) + for byte in data_bytes: + crc ^= byte + for _ in range(8): + if crc & 0x80: + crc = (crc << 1) ^ SHT3X_CRC8_POLYNOMINAL + else: + crc <<= 1 + return crc & 0xFF + + def get_status(self, eventtime): + return { + 'temperature': round(self.temp, 2), + 'humidity': round(self.humidity, 1), + } + +def load_config(config): + # Register sensor + pheater = config.get_printer().lookup_object("heaters") + pheater.add_sensor_factory("SHT3X", SHT3X) diff --git a/klippy/extras/smart_effector.py b/klippy/extras/smart_effector.py index 6076a246867e..726531421ac9 100644 --- a/klippy/extras/smart_effector.py +++ b/klippy/extras/smart_effector.py @@ -48,7 +48,7 @@ def write_bits(self, start_time, bit_stream): bit_time += bit_step return bit_time -class SmartEffectorEndstopWrapper: +class SmartEffectorProbe: def __init__(self, config): self.printer = config.get_printer() self.gcode = self.printer.lookup_object('gcode') @@ -64,6 +64,11 @@ def __init__(self, config): self.query_endstop = self.probe_wrapper.query_endstop self.multi_probe_begin = self.probe_wrapper.multi_probe_begin self.multi_probe_end = self.probe_wrapper.multi_probe_end + # Common probe implementation helpers + self.cmd_helper = probe.ProbeCommandHelper( + config, self, self.probe_wrapper.query_endstop) + self.probe_offsets = probe.ProbeOffsetsHelper(config) + self.probe_session = probe.ProbeSessionHelper(config, self) # SmartEffector control control_pin = config.get('control_pin', None) if control_pin: @@ -78,6 +83,17 @@ def __init__(self, config): self.gcode.register_command("SET_SMART_EFFECTOR", self.cmd_SET_SMART_EFFECTOR, desc=self.cmd_SET_SMART_EFFECTOR_help) + def get_probe_params(self, gcmd=None): + return self.probe_session.get_probe_params(gcmd) + def get_offsets(self): + return self.probe_offsets.get_offsets() + def get_status(self, eventtime): + return self.cmd_helper.get_status(eventtime) + def start_probe_session(self, gcmd): + return self.probe_session.start_probe_session(gcmd) + def probing_move(self, pos, speed): + phoming = self.printer.lookup_object('homing') + return phoming.probing_move(self, pos, speed) def probe_prepare(self, hmove): toolhead = self.printer.lookup_object('toolhead') self.probe_wrapper.probe_prepare(hmove) @@ -148,7 +164,6 @@ def cmd_RESET_SMART_EFFECTOR(self, gcmd): gcmd.respond_info('SmartEffector sensitivity was reset') def load_config(config): - smart_effector = SmartEffectorEndstopWrapper(config) - config.get_printer().add_object('probe', - probe.PrinterProbe(config, smart_effector)) + smart_effector = SmartEffectorProbe(config) + config.get_printer().add_object('probe', smart_effector) return smart_effector diff --git a/klippy/extras/static_digital_output.py b/klippy/extras/static_digital_output.py index ce20937204c5..2fa0bb3f52c6 100644 --- a/klippy/extras/static_digital_output.py +++ b/klippy/extras/static_digital_output.py @@ -10,8 +10,10 @@ def __init__(self, config): ppins = printer.lookup_object('pins') pin_list = config.getlist('pins') for pin_desc in pin_list: - mcu_pin = ppins.setup_pin('digital_out', pin_desc) - mcu_pin.setup_start_value(1, 1, True) + pin_params = ppins.lookup_pin(pin_desc, can_invert=True) + mcu = pin_params['chip'] + mcu.add_config_cmd("set_digital_out pin=%s value=%d" + % (pin_params['pin'], not pin_params['invert'])) def load_config_prefix(config): return PrinterStaticDigitalOut(config) diff --git a/klippy/extras/sx1509.py b/klippy/extras/sx1509.py index 8b19dda801db..3bfecb7831c3 100644 --- a/klippy/extras/sx1509.py +++ b/klippy/extras/sx1509.py @@ -104,7 +104,6 @@ def __init__(self, sx1509, pin_params): self._invert = pin_params['invert'] self._mcu.register_config_callback(self._build_config) self._start_value = self._shutdown_value = self._invert - self._is_static = False self._max_duration = 2. self._set_cmd = self._clear_cmd = None # Set direction to output @@ -116,12 +115,9 @@ def get_mcu(self): return self._mcu def setup_max_duration(self, max_duration): self._max_duration = max_duration - def setup_start_value(self, start_value, shutdown_value, is_static=False): - if is_static and start_value != shutdown_value: - raise pins.error("Static pin can not have shutdown value") + def setup_start_value(self, start_value, shutdown_value): self._start_value = (not not start_value) ^ self._invert self._shutdown_value = self._invert - self._is_static = is_static # We need to set the start value here so the register is # updated before the SX1509 class writes it. if self._start_value: @@ -148,7 +144,6 @@ def __init__(self, sx1509, pin_params): self._invert = pin_params['invert'] self._mcu.register_config_callback(self._build_config) self._start_value = self._shutdown_value = float(self._invert) - self._is_static = False self._max_duration = 2. self._hardware_pwm = False self._pwm_max = 0. @@ -182,16 +177,13 @@ def setup_max_duration(self, 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, is_static=False): - if is_static and start_value != shutdown_value: - raise pins.error("Static pin can not have shutdown value") + 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)) - self._is_static = is_static - def set_pwm(self, print_time, value, cycle_time=None): + def set_pwm(self, print_time, value): self._sx1509.set_register(self._i_on_reg, ~int(255 * value) if not self._invert else int(255 * value) & 0xFF) diff --git a/klippy/extras/temperature_mcu.py b/klippy/extras/temperature_mcu.py index 585ec4c1d20d..be2cd145c455 100644 --- a/klippy/extras/temperature_mcu.py +++ b/klippy/extras/temperature_mcu.py @@ -1,10 +1,11 @@ # Support for micro-controller chip based temperature sensors # -# Copyright (C) 2020 Kevin O'Connor +# Copyright (C) 2020-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. import logging import mcu +from . import adc_temperature SAMPLE_TIME = 0.001 SAMPLE_COUNT = 8 @@ -31,30 +32,33 @@ def __init__(self, config): self.mcu_adc = ppins.setup_pin('adc', '%s:ADC_TEMPERATURE' % (mcu_name,)) self.mcu_adc.setup_adc_callback(REPORT_TIME, self.adc_callback) - query_adc = config.get_printer().load_object(config, 'query_adc') - query_adc.register_adc(config.get_name(), self.mcu_adc) + self.diag_helper = adc_temperature.HelperTemperatureDiagnostics( + config, self.mcu_adc, self.calc_temp) # Register callbacks if self.printer.get_start_args().get('debugoutput') is not None: - self.mcu_adc.setup_minmax(SAMPLE_TIME, SAMPLE_COUNT, - range_check_count=RANGE_CHECK_COUNT) + self.mcu_adc.setup_adc_sample(SAMPLE_TIME, SAMPLE_COUNT) return self.printer.register_event_handler("klippy:mcu_identify", - self._mcu_identify) + self.handle_mcu_identify) + # Temperature interface def setup_callback(self, temperature_callback): self.temperature_callback = temperature_callback def get_report_time_delta(self): return REPORT_TIME - def adc_callback(self, read_time, read_value): - temp = self.base_temperature + read_value * self.slope - self.temperature_callback(read_time + SAMPLE_COUNT * SAMPLE_TIME, temp) def setup_minmax(self, min_temp, max_temp): self.min_temp = min_temp self.max_temp = max_temp + # Internal code + def adc_callback(self, read_time, read_value): + temp = self.base_temperature + read_value * self.slope + self.temperature_callback(read_time + SAMPLE_COUNT * SAMPLE_TIME, temp) + def calc_temp(self, adc): + return self.base_temperature + adc * self.slope def calc_adc(self, temp): return (temp - self.base_temperature) / self.slope def calc_base(self, temp, adc): return temp - adc * self.slope - def _mcu_identify(self): + def handle_mcu_identify(self): # Obtain mcu information mcu = self.mcu_adc.get_mcu() self.debug_read_cmd = mcu.lookup_query_command( @@ -89,10 +93,13 @@ def _mcu_identify(self): self.slope = (self.temp2 - self.temp1) / (self.adc2 - self.adc1) self.base_temperature = self.calc_base(self.temp1, self.adc1) # Setup min/max checks - adc_range = [self.calc_adc(t) for t in [self.min_temp, self.max_temp]] - self.mcu_adc.setup_minmax(SAMPLE_TIME, SAMPLE_COUNT, - minval=min(adc_range), maxval=max(adc_range), - range_check_count=RANGE_CHECK_COUNT) + arange = [self.calc_adc(t) for t in [self.min_temp, self.max_temp]] + min_adc, max_adc = sorted(arange) + self.mcu_adc.setup_adc_sample(SAMPLE_TIME, SAMPLE_COUNT, + minval=min_adc, maxval=max_adc, + range_check_count=RANGE_CHECK_COUNT) + self.diag_helper.setup_diag_minmax(self.min_temp, self.max_temp, + min_adc, max_adc) def config_unknown(self): raise self.printer.config_error("MCU temperature not supported on %s" % (self.mcu_type,)) diff --git a/klippy/extras/temperature_sensors.cfg b/klippy/extras/temperature_sensors.cfg index 96aa996401ae..4fbe5492c709 100644 --- a/klippy/extras/temperature_sensors.cfg +++ b/klippy/extras/temperature_sensors.cfg @@ -18,6 +18,8 @@ # Load "SI7013", "SI7020", "SI7021", "SHT21", and "HTU21D" sensors [htu21d] +[sht3x] + # Load "AHT10" [aht10] @@ -102,12 +104,6 @@ temperature1: 25 resistance1: 100000 beta: 3974 -# Definition inherent from name. This sensor is deprecated! -[thermistor NTC 100K beta 3950] -temperature1: 25 -resistance1: 100000 -beta: 3950 - # Definition from description of Marlin "thermistor 75" [thermistor NTC 100K MGB18-104F39050L32] temperature1: 25 diff --git a/klippy/extras/tmc.py b/klippy/extras/tmc.py index 8143882abf19..1d8599e2ed2d 100644 --- a/klippy/extras/tmc.py +++ b/klippy/extras/tmc.py @@ -278,16 +278,14 @@ def cmd_SET_TMC_FIELD(self, gcmd): raise gcmd.error("Unknown field name '%s'" % (field_name,)) value = gcmd.get_int('VALUE', None) velocity = gcmd.get_float('VELOCITY', None, minval=0.) - tmc_frequency = self.mcu_tmc.get_tmc_frequency() - if tmc_frequency is None and velocity is not None: - raise gcmd.error("VELOCITY parameter not supported by this driver") if (value is None) == (velocity is None): raise gcmd.error("Specify either VALUE or VELOCITY") if velocity is not None: - step_dist = self.stepper.get_step_dist() - mres = self.fields.get_field("mres") - value = TMCtstepHelper(step_dist, mres, tmc_frequency, - velocity) + if self.mcu_tmc.get_tmc_frequency() is None: + raise gcmd.error( + "VELOCITY parameter not supported by this driver") + value = TMCtstepHelper(self.mcu_tmc, velocity, + pstepper=self.stepper) reg_val = self.fields.set_field(field_name, value) print_time = self.printer.lookup_object('toolhead').get_last_move_time() self.mcu_tmc.set_register(reg_name, reg_val, print_time) @@ -481,7 +479,7 @@ def __init__(self, config, mcu_tmc): self.diag_pin_field = None self.mcu_endstop = None self.en_pwm = False - self.pwmthrs = self.coolthrs = 0 + self.pwmthrs = self.coolthrs = self.thigh = 0 # Register virtual_endstop pin name_parts = config.get_name().split() ppins = self.printer.lookup_object("pins") @@ -505,8 +503,8 @@ def setup_pin(self, pin_type, pin_params): def handle_homing_move_begin(self, hmove): if self.mcu_endstop not in hmove.get_mcu_endstops(): return + # Enable/disable stealthchop self.pwmthrs = self.fields.get_field("tpwmthrs") - self.coolthrs = self.fields.get_field("tcoolthrs") reg = self.fields.lookup_register("en_pwm_mode", None) if reg is None: # On "stallguard4" drivers, "stealthchop" must be enabled @@ -520,12 +518,21 @@ def handle_homing_move_begin(self, hmove): self.fields.set_field("en_pwm_mode", 0) val = self.fields.set_field(self.diag_pin_field, 1) self.mcu_tmc.set_register("GCONF", val) + # Enable tcoolthrs (if not already) + self.coolthrs = self.fields.get_field("tcoolthrs") if self.coolthrs == 0: tc_val = self.fields.set_field("tcoolthrs", 0xfffff) self.mcu_tmc.set_register("TCOOLTHRS", tc_val) + # Disable thigh + reg = self.fields.lookup_register("thigh", None) + if reg is not None: + self.thigh = self.fields.get_field("thigh") + th_val = self.fields.set_field("thigh", 0) + self.mcu_tmc.set_register(reg, th_val) def handle_homing_move_end(self, hmove): if self.mcu_endstop not in hmove.get_mcu_endstops(): return + # Restore stealthchop/spreadcycle reg = self.fields.lookup_register("en_pwm_mode", None) if reg is None: tp_val = self.fields.set_field("tpwmthrs", self.pwmthrs) @@ -535,8 +542,14 @@ def handle_homing_move_end(self, hmove): self.fields.set_field("en_pwm_mode", self.en_pwm) val = self.fields.set_field(self.diag_pin_field, 0) self.mcu_tmc.set_register("GCONF", val) + # Restore tcoolthrs tc_val = self.fields.set_field("tcoolthrs", self.coolthrs) self.mcu_tmc.set_register("TCOOLTHRS", tc_val) + # Restore thigh + reg = self.fields.lookup_register("thigh", None) + if reg is not None: + th_val = self.fields.set_field("thigh", self.thigh) + self.mcu_tmc.set_register(reg, th_val) ###################################################################### @@ -564,7 +577,7 @@ def TMCWaveTableHelper(config, mcu_tmc): set_config_field(config, "start_sin", 0) set_config_field(config, "start_sin90", 247) -# Helper to configure and query the microstep settings +# Helper to configure the microstep settings def TMCMicrostepHelper(config, mcu_tmc): fields = mcu_tmc.get_fields() stepper_name = " ".join(config.get_name().split()[1:]) @@ -572,27 +585,31 @@ def TMCMicrostepHelper(config, mcu_tmc): raise config.error( "Could not find config section '[%s]' required by tmc driver" % (stepper_name,)) - stepper_config = ms_config = config.getsection(stepper_name) - if (stepper_config.get('microsteps', None, note_valid=False) is None - and config.get('microsteps', None, note_valid=False) is not None): - # Older config format with microsteps in tmc config section - ms_config = config + sconfig = config.getsection(stepper_name) steps = {256: 0, 128: 1, 64: 2, 32: 3, 16: 4, 8: 5, 4: 6, 2: 7, 1: 8} - mres = ms_config.getchoice('microsteps', steps) + mres = sconfig.getchoice('microsteps', steps) fields.set_field("mres", mres) fields.set_field("intpol", config.getboolean("interpolate", True)) # Helper for calculating TSTEP based values from velocity -def TMCtstepHelper(step_dist, mres, tmc_freq, velocity): - if velocity > 0.: - step_dist_256 = step_dist / (1 << mres) - threshold = int(tmc_freq * step_dist_256 / velocity + .5) - return max(0, min(0xfffff, threshold)) - else: +def TMCtstepHelper(mcu_tmc, velocity, pstepper=None, config=None): + if velocity <= 0.: return 0xfffff + if pstepper is not None: + step_dist = pstepper.get_step_dist() + else: + stepper_name = " ".join(config.get_name().split()[1:]) + sconfig = config.getsection(stepper_name) + rotation_dist, steps_per_rotation = stepper.parse_step_distance(sconfig) + step_dist = rotation_dist / steps_per_rotation + mres = mcu_tmc.get_fields().get_field("mres") + step_dist_256 = step_dist / (1 << mres) + tmc_freq = mcu_tmc.get_tmc_frequency() + threshold = int(tmc_freq * step_dist_256 / velocity + .5) + return max(0, min(0xfffff, threshold)) # Helper to configure stealthChop-spreadCycle transition velocity -def TMCStealthchopHelper(config, mcu_tmc, tmc_freq): +def TMCStealthchopHelper(config, mcu_tmc): fields = mcu_tmc.get_fields() en_pwm_mode = False velocity = config.getfloat('stealthchop_threshold', None, minval=0.) @@ -600,13 +617,7 @@ def TMCStealthchopHelper(config, mcu_tmc, tmc_freq): if velocity is not None: en_pwm_mode = True - - stepper_name = " ".join(config.get_name().split()[1:]) - sconfig = config.getsection(stepper_name) - rotation_dist, steps_per_rotation = stepper.parse_step_distance(sconfig) - step_dist = rotation_dist / steps_per_rotation - mres = fields.get_field("mres") - tpwmthrs = TMCtstepHelper(step_dist, mres, tmc_freq, velocity) + tpwmthrs = TMCtstepHelper(mcu_tmc, velocity, config=config) fields.set_field("tpwmthrs", tpwmthrs) reg = fields.lookup_register("en_pwm_mode", None) @@ -615,3 +626,22 @@ def TMCStealthchopHelper(config, mcu_tmc, tmc_freq): else: # TMC2208 uses en_spreadCycle fields.set_field("en_spreadcycle", not en_pwm_mode) + +# Helper to configure StallGuard and CoolStep minimum velocity +def TMCVcoolthrsHelper(config, mcu_tmc): + fields = mcu_tmc.get_fields() + velocity = config.getfloat('coolstep_threshold', None, minval=0.) + tcoolthrs = 0 + if velocity is not None: + tcoolthrs = TMCtstepHelper(mcu_tmc, velocity, config=config) + fields.set_field("tcoolthrs", tcoolthrs) + +# Helper to configure StallGuard and CoolStep maximum velocity and +# SpreadCycle-FullStepping (High velocity) mode threshold. +def TMCVhighHelper(config, mcu_tmc): + fields = mcu_tmc.get_fields() + velocity = config.getfloat('high_velocity_threshold', None, minval=0.) + thigh = 0 + if velocity is not None: + thigh = TMCtstepHelper(mcu_tmc, velocity, config=config) + fields.set_field("thigh", thigh) diff --git a/klippy/extras/tmc2130.py b/klippy/extras/tmc2130.py index 62a9abbfe56b..20a25c66c514 100644 --- a/klippy/extras/tmc2130.py +++ b/klippy/extras/tmc2130.py @@ -296,7 +296,9 @@ def __init__(self, config): self.get_status = cmdhelper.get_status # Setup basic register values tmc.TMCWaveTableHelper(config, self.mcu_tmc) - tmc.TMCStealthchopHelper(config, self.mcu_tmc, TMC_FREQUENCY) + tmc.TMCStealthchopHelper(config, self.mcu_tmc) + tmc.TMCVcoolthrsHelper(config, self.mcu_tmc) + tmc.TMCVhighHelper(config, self.mcu_tmc) # Allow other registers to be set from the config set_config_field = self.fields.set_config_field # CHOPCONF @@ -304,8 +306,16 @@ def __init__(self, config): set_config_field(config, "hstrt", 0) set_config_field(config, "hend", 7) set_config_field(config, "tbl", 1) + set_config_field(config, "vhighfs", 0) + set_config_field(config, "vhighchm", 0) # COOLCONF + set_config_field(config, "semin", 0) + set_config_field(config, "seup", 0) + set_config_field(config, "semax", 0) + set_config_field(config, "sedn", 0) + set_config_field(config, "seimin", 0) set_config_field(config, "sgt", 0) + set_config_field(config, "sfilt", 0) # IHOLDIRUN set_config_field(config, "iholddelay", 8) # PWMCONF diff --git a/klippy/extras/tmc2208.py b/klippy/extras/tmc2208.py index 421c53781613..86476acc57a2 100644 --- a/klippy/extras/tmc2208.py +++ b/klippy/extras/tmc2208.py @@ -197,7 +197,7 @@ def __init__(self, config): self.get_status = cmdhelper.get_status # Setup basic register values self.fields.set_field("mstep_reg_select", True) - tmc.TMCStealthchopHelper(config, self.mcu_tmc, TMC_FREQUENCY) + tmc.TMCStealthchopHelper(config, self.mcu_tmc) # Allow other registers to be set from the config set_config_field = self.fields.set_config_field # GCONF diff --git a/klippy/extras/tmc2209.py b/klippy/extras/tmc2209.py index c248c2d68c6f..fbd8d1c101e0 100644 --- a/klippy/extras/tmc2209.py +++ b/klippy/extras/tmc2209.py @@ -73,7 +73,8 @@ def __init__(self, config): self.get_status = cmdhelper.get_status # Setup basic register values self.fields.set_field("mstep_reg_select", True) - tmc.TMCStealthchopHelper(config, self.mcu_tmc, TMC_FREQUENCY) + tmc.TMCStealthchopHelper(config, self.mcu_tmc) + tmc.TMCVcoolthrsHelper(config, self.mcu_tmc) # Allow other registers to be set from the config set_config_field = self.fields.set_config_field # GCONF @@ -83,6 +84,12 @@ def __init__(self, config): set_config_field(config, "hstrt", 5) set_config_field(config, "hend", 0) set_config_field(config, "tbl", 2) + # COOLCONF + set_config_field(config, "semin", 0) + set_config_field(config, "seup", 0) + set_config_field(config, "semax", 0) + set_config_field(config, "sedn", 0) + set_config_field(config, "seimin", 0) # IHOLDIRUN set_config_field(config, "iholddelay", 8) # PWMCONF diff --git a/klippy/extras/tmc2240.py b/klippy/extras/tmc2240.py index aef2280f279a..21c835e26591 100644 --- a/klippy/extras/tmc2240.py +++ b/klippy/extras/tmc2240.py @@ -95,7 +95,7 @@ "s2vsb": 0x01 << 13, "stealth": 0x01 << 14, "fsactive": 0x01 << 15, - "csactual": 0x1F << 16, + "cs_actual": 0x1F << 16, "stallguard": 0x01 << 24, "ot": 0x01 << 25, "otpw": 0x01 << 26, @@ -259,6 +259,8 @@ "s2vsa": (lambda v: "1(ShortToSupply_A!)" if v else ""), "s2vsb": (lambda v: "1(ShortToSupply_B!)" if v else ""), "adc_temp": (lambda v: "0x%04x(%.1fC)" % (v, ((v - 2038) / 7.7))), + "adc_vsupply": (lambda v: "0x%04x(%.3fV)" % (v, v * 0.009732)), + "adc_ain": (lambda v: "0x%04x(%.3fmV)" % (v, v * 0.3052)), }) @@ -362,7 +364,10 @@ def __init__(self, config): # Setup basic register values tmc.TMCWaveTableHelper(config, self.mcu_tmc) self.fields.set_config_field(config, "offset_sin90", 0) - tmc.TMCStealthchopHelper(config, self.mcu_tmc, TMC_FREQUENCY) + tmc.TMCStealthchopHelper(config, self.mcu_tmc) + tmc.TMCVcoolthrsHelper(config, self.mcu_tmc) + tmc.TMCVhighHelper(config, self.mcu_tmc) + # Allow other registers to be set from the config set_config_field = self.fields.set_config_field # GCONF set_config_field(config, "multistep_filt", True) diff --git a/klippy/extras/tmc5160.py b/klippy/extras/tmc5160.py index 7ff47abf9f88..b773135c25d8 100644 --- a/klippy/extras/tmc5160.py +++ b/klippy/extras/tmc5160.py @@ -118,7 +118,7 @@ "s2vsb": 0x01 << 13, "stealth": 0x01 << 14, "fsactive": 0x01 << 15, - "csactual": 0xFF << 16, + "cs_actual": 0x1F << 16, "stallguard": 0x01 << 24, "ot": 0x01 << 25, "otpw": 0x01 << 26, @@ -242,6 +242,9 @@ Fields["TSTEP"] = { "tstep": 0xfffff << 0 } +Fields["THIGH"] = { + "thigh": 0xfffff << 0 +} SignedFields = ["cur_a", "cur_b", "sgt", "xactual", "vactual", "pwm_scale_auto"] @@ -335,7 +338,10 @@ def __init__(self, config): self.get_status = cmdhelper.get_status # Setup basic register values tmc.TMCWaveTableHelper(config, self.mcu_tmc) - tmc.TMCStealthchopHelper(config, self.mcu_tmc, TMC_FREQUENCY) + tmc.TMCStealthchopHelper(config, self.mcu_tmc) + tmc.TMCVcoolthrsHelper(config, self.mcu_tmc) + tmc.TMCVhighHelper(config, self.mcu_tmc) + # Allow other registers to be set from the config set_config_field = self.fields.set_config_field # GCONF set_config_field(config, "multistep_filt", True) diff --git a/klippy/extras/tsl1401cl_filament_width_sensor.py b/klippy/extras/tsl1401cl_filament_width_sensor.py index fb2d97131130..83480f46714a 100644 --- a/klippy/extras/tsl1401cl_filament_width_sensor.py +++ b/klippy/extras/tsl1401cl_filament_width_sensor.py @@ -33,7 +33,7 @@ def __init__(self, config): # Start adc self.ppins = self.printer.lookup_object('pins') self.mcu_adc = self.ppins.setup_pin('adc', self.pin) - self.mcu_adc.setup_minmax(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT) + self.mcu_adc.setup_adc_sample(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT) self.mcu_adc.setup_adc_callback(ADC_REPORT_TIME, self.adc_callback) # extrude factor updating self.extrude_factor_update_timer = self.reactor.register_timer( diff --git a/klippy/extras/virtual_sdcard.py b/klippy/extras/virtual_sdcard.py index 31f283ff256b..6dc49e2f5c39 100644 --- a/klippy/extras/virtual_sdcard.py +++ b/klippy/extras/virtual_sdcard.py @@ -1,12 +1,18 @@ # Virtual sdcard support (print files directly from a host g-code file) # -# Copyright (C) 2018 Kevin O'Connor +# Copyright (C) 2018-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import os, logging, io +import os, sys, logging, io VALID_GCODE_EXTS = ['gcode', 'g', 'gco'] +DEFAULT_ERROR_GCODE = """ +{% if 'heaters' in printer %} + TURN_OFF_HEATERS +{% endif %} +""" + class VirtualSD: def __init__(self, config): self.printer = config.get_printer() @@ -27,7 +33,7 @@ def __init__(self, config): # Error handling gcode_macro = self.printer.load_object(config, 'gcode_macro') self.on_error_gcode = gcode_macro.load_template( - config, 'on_error_gcode', '') + config, 'on_error_gcode', DEFAULT_ERROR_GCODE) # Register commands self.gcode = self.printer.lookup_object('gcode') for cmd in ['M20', 'M21', 'M23', 'M24', 'M25', 'M26', 'M27']: @@ -258,7 +264,10 @@ def work_handler(self, eventtime): # Dispatch command self.cmd_from_sd = True line = lines.pop() - next_file_position = self.file_position + len(line) + 1 + if sys.version_info.major >= 3: + next_file_position = self.file_position + len(line.encode()) + 1 + else: + next_file_position = self.file_position + len(line) + 1 self.next_file_position = next_file_position try: self.gcode.run_script(line) diff --git a/klippy/kinematics/cartesian.py b/klippy/kinematics/cartesian.py index 6d576b5b5a54..0c4bb9255853 100644 --- a/klippy/kinematics/cartesian.py +++ b/klippy/kinematics/cartesian.py @@ -23,7 +23,7 @@ def __init__(self, toolhead, config): self.dc_module = None if config.has_section('dual_carriage'): dc_config = config.getsection('dual_carriage') - dc_axis = dc_config.getchoice('axis', {'x': 'x', 'y': 'y'}) + dc_axis = dc_config.getchoice('axis', ['x', 'y']) self.dual_carriage_axis = {'x': 0, 'y': 1}[dc_axis] # setup second dual carriage rail self.rails.append(stepper.LookupMultiRail(dc_config)) @@ -52,20 +52,27 @@ def __init__(self, toolhead, config): def get_steppers(self): return [s for rail in self.rails for s in rail.get_steppers()] def calc_position(self, stepper_positions): - return [stepper_positions[rail.get_name()] for rail in self.rails] + rails = self.rails + if self.dc_module: + primary_rail = self.dc_module.get_primary_rail().get_rail() + rails = (rails[:self.dc_module.axis] + + [primary_rail] + rails[self.dc_module.axis+1:]) + return [stepper_positions[rail.get_name()] for rail in rails] def update_limits(self, i, range): l, h = self.limits[i] # Only update limits if this axis was already homed, # otherwise leave in un-homed state. if l <= h: self.limits[i] = range - def override_rail(self, i, rail): - self.rails[i] = rail def set_position(self, newpos, homing_axes): for i, rail in enumerate(self.rails): rail.set_position(newpos) - if i in homing_axes: - self.limits[i] = rail.get_range() + for axis in homing_axes: + if self.dc_module and axis == self.dc_module.axis: + rail = self.dc_module.get_primary_rail().get_rail() + else: + rail = self.rails[axis] + self.limits[axis] = rail.get_range() def note_z_not_homed(self): # Helper for Safe Z Home self.limits[2] = (1.0, -1.0) diff --git a/klippy/kinematics/extruder.py b/klippy/kinematics/extruder.py index 4fe041c5b5e3..6924003783e8 100644 --- a/klippy/kinematics/extruder.py +++ b/klippy/kinematics/extruder.py @@ -38,12 +38,6 @@ def __init__(self, config): gcode.register_mux_command("SYNC_EXTRUDER_MOTION", "EXTRUDER", self.name, self.cmd_SYNC_EXTRUDER_MOTION, desc=self.cmd_SYNC_EXTRUDER_MOTION_help) - gcode.register_mux_command("SET_EXTRUDER_STEP_DISTANCE", "EXTRUDER", - self.name, self.cmd_SET_E_STEP_DISTANCE, - desc=self.cmd_SET_E_STEP_DISTANCE_help) - gcode.register_mux_command("SYNC_STEPPER_TO_EXTRUDER", "STEPPER", - self.name, self.cmd_SYNC_STEPPER_TO_EXTRUDER, - desc=self.cmd_SYNC_STEPPER_TO_EXTRUDER_help) def _handle_connect(self): toolhead = self.printer.lookup_object('toolhead') toolhead.register_step_generator(self.stepper.generate_steps) @@ -133,24 +127,6 @@ def cmd_SYNC_EXTRUDER_MOTION(self, gcmd): self.sync_to_extruder(ename) gcmd.respond_info("Extruder '%s' now syncing with '%s'" % (self.name, ename)) - cmd_SET_E_STEP_DISTANCE_help = "Set extruder step distance" - def cmd_SET_E_STEP_DISTANCE(self, gcmd): - step_dist = gcmd.get_float('DISTANCE', None, above=0.) - if step_dist is not None: - toolhead = self.printer.lookup_object('toolhead') - toolhead.flush_step_generation() - rd, steps_per_rotation = self.stepper.get_rotation_distance() - self.stepper.set_rotation_distance(step_dist * steps_per_rotation) - else: - step_dist = self.stepper.get_step_dist() - gcmd.respond_info("Extruder '%s' step distance set to %0.6f" - % (self.name, step_dist)) - cmd_SYNC_STEPPER_TO_EXTRUDER_help = "Set extruder stepper" - def cmd_SYNC_STEPPER_TO_EXTRUDER(self, gcmd): - ename = gcmd.get('EXTRUDER') - self.sync_to_extruder(ename) - gcmd.respond_info("Extruder '%s' now syncing with '%s'" - % (self.name, ename)) # Tracking for hotend heater, extrusion motion queue, and extruder stepper class PrinterExtruder: @@ -159,14 +135,9 @@ def __init__(self, config, extruder_num): self.name = config.get_name() self.last_position = 0. # Setup hotend heater - shared_heater = config.get('shared_heater', None) pheaters = self.printer.load_object(config, 'heaters') gcode_id = 'T%d' % (extruder_num,) - if shared_heater is None: - self.heater = pheaters.setup_heater(config, gcode_id) - else: - config.deprecate('shared_heater') - self.heater = pheaters.lookup_heater(shared_heater) + self.heater = pheaters.setup_heater(config, gcode_id) # Setup kinematic checks self.nozzle_diameter = config.getfloat('nozzle_diameter', above=0.) filament_diameter = config.getfloat( diff --git a/klippy/kinematics/hybrid_corexy.py b/klippy/kinematics/hybrid_corexy.py index 26032039c016..265a0e6da33d 100644 --- a/klippy/kinematics/hybrid_corexy.py +++ b/klippy/kinematics/hybrid_corexy.py @@ -11,7 +11,6 @@ class HybridCoreXYKinematics: def __init__(self, toolhead, config): self.printer = config.get_printer() - printer_config = config.getsection('printer') # itersolve parameters self.rails = [ stepper.PrinterRail(config.getsection('stepper_x')), stepper.LookupMultiRail(config.getsection('stepper_y')), @@ -28,7 +27,7 @@ def __init__(self, toolhead, config): if config.has_section('dual_carriage'): dc_config = config.getsection('dual_carriage') # dummy for cartesian config users - dc_config.getchoice('axis', {'x': 'x'}, default='x') + dc_config.getchoice('axis', ['x'], default='x') # setup second dual carriage rail self.rails.append(stepper.PrinterRail(dc_config)) self.rails[1].get_endstops()[0][0].add_stepper( @@ -58,7 +57,7 @@ def calc_position(self, stepper_positions): pos = [stepper_positions[rail.get_name()] for rail in self.rails] if (self.dc_module is not None and 'PRIMARY' == \ self.dc_module.get_status()['carriage_1']): - return [pos[0] - pos[1], pos[1], pos[2]] + return [pos[3] - pos[1], pos[1], pos[2]] else: return [pos[0] + pos[1], pos[1], pos[2]] def update_limits(self, i, range): @@ -67,13 +66,15 @@ def update_limits(self, i, range): # otherwise leave in un-homed state. if l <= h: self.limits[i] = range - def override_rail(self, i, rail): - self.rails[i] = rail def set_position(self, newpos, homing_axes): for i, rail in enumerate(self.rails): rail.set_position(newpos) - if i in homing_axes: - self.limits[i] = rail.get_range() + for axis in homing_axes: + if self.dc_module and axis == self.dc_module.axis: + rail = self.dc_module.get_primary_rail().get_rail() + else: + rail = self.rails[axis] + self.limits[axis] = rail.get_range() def note_z_not_homed(self): # Helper for Safe Z Home self.limits[2] = (1.0, -1.0) diff --git a/klippy/kinematics/hybrid_corexz.py b/klippy/kinematics/hybrid_corexz.py index e13c9aa4cb4a..2d89e3f7bd03 100644 --- a/klippy/kinematics/hybrid_corexz.py +++ b/klippy/kinematics/hybrid_corexz.py @@ -11,7 +11,6 @@ class HybridCoreXZKinematics: def __init__(self, toolhead, config): self.printer = config.get_printer() - printer_config = config.getsection('printer') # itersolve parameters self.rails = [ stepper.PrinterRail(config.getsection('stepper_x')), stepper.LookupMultiRail(config.getsection('stepper_y')), @@ -28,7 +27,7 @@ def __init__(self, toolhead, config): if config.has_section('dual_carriage'): dc_config = config.getsection('dual_carriage') # dummy for cartesian config users - dc_config.getchoice('axis', {'x': 'x'}, default='x') + dc_config.getchoice('axis', ['x'], default='x') # setup second dual carriage rail self.rails.append(stepper.PrinterRail(dc_config)) self.rails[2].get_endstops()[0][0].add_stepper( @@ -58,7 +57,7 @@ def calc_position(self, stepper_positions): pos = [stepper_positions[rail.get_name()] for rail in self.rails] if (self.dc_module is not None and 'PRIMARY' == \ self.dc_module.get_status()['carriage_1']): - return [pos[0] - pos[2], pos[1], pos[2]] + return [pos[3] - pos[2], pos[1], pos[2]] else: return [pos[0] + pos[2], pos[1], pos[2]] def update_limits(self, i, range): @@ -67,13 +66,15 @@ def update_limits(self, i, range): # otherwise leave in un-homed state. if l <= h: self.limits[i] = range - def override_rail(self, i, rail): - self.rails[i] = rail def set_position(self, newpos, homing_axes): for i, rail in enumerate(self.rails): rail.set_position(newpos) - if i in homing_axes: - self.limits[i] = rail.get_range() + for axis in homing_axes: + if self.dc_module and axis == self.dc_module.axis: + rail = self.dc_module.get_primary_rail().get_rail() + else: + rail = self.rails[axis] + self.limits[axis] = rail.get_range() def note_z_not_homed(self): # Helper for Safe Z Home self.limits[2] = (1.0, -1.0) diff --git a/klippy/kinematics/idex_modes.py b/klippy/kinematics/idex_modes.py index 2ce91afe85a5..f2618d0805a7 100644 --- a/klippy/kinematics/idex_modes.py +++ b/klippy/kinematics/idex_modes.py @@ -42,7 +42,12 @@ def __init__(self, dc_config, rail_0, rail_1, axis): desc=self.cmd_RESTORE_DUAL_CARRIAGE_STATE_help) def get_rails(self): return self.dc - def toggle_active_dc_rail(self, index, override_rail=False): + def get_primary_rail(self): + for rail in self.dc: + if rail.mode == PRIMARY: + return rail + return None + def toggle_active_dc_rail(self, index): toolhead = self.printer.lookup_object('toolhead') toolhead.flush_step_generation() pos = toolhead.get_position() @@ -52,15 +57,11 @@ def toggle_active_dc_rail(self, index, override_rail=False): if i != index: if dc.is_active(): dc.inactivate(pos) - if override_rail: - kin.override_rail(3, dc_rail) target_dc = self.dc[index] if target_dc.mode != PRIMARY: newpos = pos[:self.axis] + [target_dc.get_axis_position(pos)] \ + pos[self.axis+1:] target_dc.activate(PRIMARY, newpos, old_position=pos) - if override_rail: - kin.override_rail(self.axis, target_dc.get_rail()) toolhead.set_position(newpos) kin.update_limits(self.axis, target_dc.get_rail().get_range()) def home(self, homing_state): @@ -72,10 +73,10 @@ def home(self, homing_state): # the same direction and the first carriage homes on the second one enumerated_dcs.reverse() for i, dc_rail in enumerated_dcs: - self.toggle_active_dc_rail(i, override_rail=True) + self.toggle_active_dc_rail(i) kin.home_axis(homing_state, self.axis, dc_rail.get_rail()) # Restore the original rails ordering - self.toggle_active_dc_rail(0, override_rail=True) + self.toggle_active_dc_rail(0) def get_status(self, eventtime=None): return {('carriage_%d' % (i,)) : dc.mode for (i, dc) in enumerate(self.dc)} diff --git a/klippy/klippy.py b/klippy/klippy.py index 097cff998c22..75ee6887ad71 100644 --- a/klippy/klippy.py +++ b/klippy/klippy.py @@ -1,7 +1,7 @@ #!/usr/bin/env python2 # Main code for host side printer firmware # -# Copyright (C) 2016-2020 Kevin O'Connor +# Copyright (C) 2016-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. import sys, os, gc, optparse, logging, time, collections, importlib @@ -22,31 +22,6 @@ Printer is halted """ -message_protocol_error1 = """ -This is frequently caused by running an older version of the -firmware on the MCU(s). Fix by recompiling and flashing the -firmware. -""" - -message_protocol_error2 = """ -Once the underlying issue is corrected, use the "RESTART" -command to reload the config and restart the host software. -""" - -message_mcu_connect_error = """ -Once the underlying issue is corrected, use the -"FIRMWARE_RESTART" command to reset the firmware, reload the -config, and restart the host software. -Error configuring printer -""" - -message_shutdown = """ -Once the underlying issue is corrected, use the -"FIRMWARE_RESTART" command to reset the firmware, reload the -config, and restart the host software. -Printer is shutdown -""" - class Printer: config_error = configfile.error command_error = gcode.CommandError @@ -85,6 +60,13 @@ def _set_state(self, msg): if (msg != message_ready and self.start_args.get('debuginput') is not None): self.request_exit('error_exit') + def update_error_msg(self, oldmsg, newmsg): + if (self.state_message != oldmsg + or self.state_message in (message_ready, message_startup) + or newmsg in (message_ready, message_startup)): + return + self.state_message = newmsg + logging.error(newmsg) def add_object(self, name, obj): if name in self.objects: raise self.config_error( @@ -143,33 +125,6 @@ def _read_config(self): m.add_printer_objects(config) # Validate that there are no undefined parameters in the config file pconfig.check_unused_options(config) - def _build_protocol_error_message(self, e): - host_version = self.start_args['software_version'] - msg_update = [] - msg_updated = [] - for mcu_name, mcu in self.lookup_objects('mcu'): - try: - mcu_version = mcu.get_status()['mcu_version'] - except: - logging.exception("Unable to retrieve mcu_version from mcu") - continue - if mcu_version != host_version: - msg_update.append("%s: Current version %s" - % (mcu_name.split()[-1], mcu_version)) - else: - msg_updated.append("%s: Current version %s" - % (mcu_name.split()[-1], mcu_version)) - if not msg_update: - msg_update.append("") - if not msg_updated: - msg_updated.append("") - msg = ["MCU Protocol error", - message_protocol_error1, - "Your Klipper version is: %s" % (host_version,), - "MCU(s) which should be updated:"] - msg += msg_update + ["Up-to-date MCU(s):"] + msg_updated - msg += [message_protocol_error2, str(e)] - return "\n".join(msg) def _connect(self, eventtime): try: self._read_config() @@ -183,13 +138,17 @@ def _connect(self, eventtime): self._set_state("%s\n%s" % (str(e), message_restart)) return except msgproto.error as e: - logging.exception("Protocol error") - self._set_state(self._build_protocol_error_message(e)) + msg = "Protocol error" + logging.exception(msg) + self._set_state(msg) + self.send_event("klippy:notify_mcu_error", msg, {"error": str(e)}) util.dump_mcu_build() return except mcu.error as e: - logging.exception("MCU error during connect") - self._set_state("%s%s" % (str(e), message_mcu_connect_error)) + msg = "MCU error during connect" + logging.exception(msg) + self._set_state(msg) + self.send_event("klippy:notify_mcu_error", msg, {"error": str(e)}) util.dump_mcu_build() return except Exception as e: @@ -241,12 +200,12 @@ def set_rollover_info(self, name, info, log=True): logging.info(info) if self.bglogger is not None: self.bglogger.set_rollover_info(name, info) - def invoke_shutdown(self, msg): + def invoke_shutdown(self, msg, details={}): if self.in_shutdown_state: return logging.error("Transition to shutdown state: %s", msg) self.in_shutdown_state = True - self._set_state("%s%s" % (msg, message_shutdown)) + self._set_state(msg) for cb in self.event_handlers.get("klippy:shutdown", []): try: cb() @@ -254,9 +213,10 @@ def invoke_shutdown(self, msg): logging.exception("Exception during shutdown handler") logging.info("Reactor garbage collection: %s", self.reactor.get_gc_stats()) - def invoke_async_shutdown(self, msg): + self.send_event("klippy:notify_mcu_shutdown", msg, details) + def invoke_async_shutdown(self, msg, details): self.reactor.register_async_callback( - (lambda e: self.invoke_shutdown(msg))) + (lambda e: self.invoke_shutdown(msg, details))) def register_event_handler(self, event, callback): self.event_handlers.setdefault(event, []).append(callback) def send_event(self, event, *params): diff --git a/klippy/mcu.py b/klippy/mcu.py index 6318ff7f57a4..1122ff865845 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -1,6 +1,6 @@ # Interface to Klipper micro-controller code # -# Copyright (C) 2016-2023 Kevin O'Connor +# Copyright (C) 2016-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. import sys, os, zlib, logging, math @@ -87,10 +87,13 @@ def __init__(self, serial, msgformat, cmd_queue=None): if cmd_queue is None: cmd_queue = serial.get_default_command_queue() self._cmd_queue = cmd_queue - self._msgtag = msgparser.lookup_msgtag(msgformat) & 0xffffffff + self._msgtag = msgparser.lookup_msgid(msgformat) & 0xffffffff def send(self, data=(), minclock=0, reqclock=0): cmd = self._cmd.encode(data) self._serial.raw_send(cmd, minclock, reqclock, self._cmd_queue) + def send_wait_ack(self, data=(), minclock=0, reqclock=0): + cmd = self._cmd.encode(data) + self._serial.raw_send_wait_ack(cmd, minclock, reqclock, self._cmd_queue) def get_command_tag(self): return self._msgtag @@ -101,9 +104,9 @@ def get_command_tag(self): class MCU_trsync: REASON_ENDSTOP_HIT = 1 - REASON_COMMS_TIMEOUT = 2 - REASON_HOST_REQUEST = 3 - REASON_PAST_END_TIME = 4 + REASON_HOST_REQUEST = 2 + REASON_PAST_END_TIME = 3 + REASON_COMMS_TIMEOUT = 4 def __init__(self, mcu, trdispatch): self._mcu = mcu self._trdispatch = trdispatch @@ -177,7 +180,7 @@ def _handle_trsync_state(self, params): if tc is not None: self._trigger_completion = None reason = params['trigger_reason'] - is_failure = (reason == self.REASON_COMMS_TIMEOUT) + is_failure = (reason >= self.REASON_COMMS_TIMEOUT) self._reactor.async_complete(tc, is_failure) elif self._home_end_clock is not None: clock = self._mcu.clock32_to_clock64(params['clock']) @@ -223,23 +226,17 @@ def stop(self): TRSYNC_TIMEOUT = 0.025 TRSYNC_SINGLE_MCU_TIMEOUT = 0.250 -class MCU_endstop: - RETRY_QUERY = 1.000 - def __init__(self, mcu, pin_params): +class TriggerDispatch: + def __init__(self, mcu): self._mcu = mcu - self._pin = pin_params['pin'] - self._pullup = pin_params['pullup'] - self._invert = pin_params['invert'] - self._oid = self._mcu.create_oid() - self._home_cmd = self._query_cmd = None - self._mcu.register_config_callback(self._build_config) self._trigger_completion = None - self._rest_ticks = 0 ffi_main, ffi_lib = chelper.get_ffi() self._trdispatch = ffi_main.gc(ffi_lib.trdispatch_alloc(), ffi_lib.free) self._trsyncs = [MCU_trsync(mcu, self._trdispatch)] - def get_mcu(self): - return self._mcu + def get_oid(self): + return self._trsyncs[0].get_oid() + def get_command_queue(self): + return self._trsyncs[0].get_command_queue() def add_stepper(self, stepper): trsyncs = {trsync.get_mcu(): trsync for trsync in self._trsyncs} trsync = trsyncs.get(stepper.get_mcu()) @@ -258,6 +255,52 @@ def add_stepper(self, stepper): " multi-mcu shared axis") def get_steppers(self): return [s for trsync in self._trsyncs for s in trsync.get_steppers()] + def start(self, print_time): + reactor = self._mcu.get_printer().get_reactor() + self._trigger_completion = reactor.completion() + expire_timeout = TRSYNC_TIMEOUT + if len(self._trsyncs) == 1: + expire_timeout = TRSYNC_SINGLE_MCU_TIMEOUT + for i, trsync in enumerate(self._trsyncs): + report_offset = float(i) / len(self._trsyncs) + trsync.start(print_time, report_offset, + self._trigger_completion, expire_timeout) + etrsync = self._trsyncs[0] + ffi_main, ffi_lib = chelper.get_ffi() + ffi_lib.trdispatch_start(self._trdispatch, etrsync.REASON_HOST_REQUEST) + return self._trigger_completion + def wait_end(self, end_time): + etrsync = self._trsyncs[0] + etrsync.set_home_end_time(end_time) + if self._mcu.is_fileoutput(): + self._trigger_completion.complete(True) + self._trigger_completion.wait() + def stop(self): + ffi_main, ffi_lib = chelper.get_ffi() + ffi_lib.trdispatch_stop(self._trdispatch) + res = [trsync.stop() for trsync in self._trsyncs] + err_res = [r for r in res if r >= MCU_trsync.REASON_COMMS_TIMEOUT] + if err_res: + return err_res[0] + return res[0] + +class MCU_endstop: + def __init__(self, mcu, pin_params): + self._mcu = mcu + self._pin = pin_params['pin'] + self._pullup = pin_params['pullup'] + self._invert = pin_params['invert'] + self._oid = self._mcu.create_oid() + self._home_cmd = self._query_cmd = None + self._mcu.register_config_callback(self._build_config) + self._rest_ticks = 0 + self._dispatch = TriggerDispatch(mcu) + def get_mcu(self): + return self._mcu + def add_stepper(self, stepper): + self._dispatch.add_stepper(stepper) + def get_steppers(self): + return self._dispatch.get_steppers() def _build_config(self): # Setup config self._mcu.add_config_cmd("config_endstop oid=%d pin=%s pull_up=%d" @@ -267,7 +310,7 @@ def _build_config(self): " rest_ticks=0 pin_value=0 trsync_oid=0 trigger_reason=0" % (self._oid,), on_restart=True) # Lookup commands - cmd_queue = self._trsyncs[0].get_command_queue() + cmd_queue = self._dispatch.get_command_queue() self._home_cmd = self._mcu.lookup_command( "endstop_home oid=%c clock=%u sample_ticks=%u sample_count=%c" " rest_ticks=%u pin_value=%c trsync_oid=%c trigger_reason=%c", @@ -281,36 +324,21 @@ def home_start(self, print_time, sample_time, sample_count, rest_time, clock = self._mcu.print_time_to_clock(print_time) rest_ticks = self._mcu.print_time_to_clock(print_time+rest_time) - clock self._rest_ticks = rest_ticks - reactor = self._mcu.get_printer().get_reactor() - self._trigger_completion = reactor.completion() - expire_timeout = TRSYNC_TIMEOUT - if len(self._trsyncs) == 1: - expire_timeout = TRSYNC_SINGLE_MCU_TIMEOUT - for i, trsync in enumerate(self._trsyncs): - report_offset = float(i) / len(self._trsyncs) - trsync.start(print_time, report_offset, - self._trigger_completion, expire_timeout) - etrsync = self._trsyncs[0] - ffi_main, ffi_lib = chelper.get_ffi() - ffi_lib.trdispatch_start(self._trdispatch, etrsync.REASON_HOST_REQUEST) + trigger_completion = self._dispatch.start(print_time) self._home_cmd.send( [self._oid, clock, self._mcu.seconds_to_clock(sample_time), sample_count, rest_ticks, triggered ^ self._invert, - etrsync.get_oid(), etrsync.REASON_ENDSTOP_HIT], reqclock=clock) - return self._trigger_completion + self._dispatch.get_oid(), MCU_trsync.REASON_ENDSTOP_HIT], + reqclock=clock) + return trigger_completion def home_wait(self, home_end_time): - etrsync = self._trsyncs[0] - etrsync.set_home_end_time(home_end_time) - if self._mcu.is_fileoutput(): - self._trigger_completion.complete(True) - self._trigger_completion.wait() + self._dispatch.wait_end(home_end_time) self._home_cmd.send([self._oid, 0, 0, 0, 0, 0, 0, 0]) - ffi_main, ffi_lib = chelper.get_ffi() - ffi_lib.trdispatch_stop(self._trdispatch) - res = [trsync.stop() for trsync in self._trsyncs] - if any([r == etrsync.REASON_COMMS_TIMEOUT for r in res]): - return -1. - if res[0] != etrsync.REASON_ENDSTOP_HIT: + res = self._dispatch.stop() + if res >= MCU_trsync.REASON_COMMS_TIMEOUT: + cmderr = self._mcu.get_printer().command_error + raise cmderr("Communication timeout during homing") + if res != MCU_trsync.REASON_ENDSTOP_HIT: return 0. if self._mcu.is_fileoutput(): return home_end_time @@ -332,7 +360,6 @@ def __init__(self, mcu, pin_params): self._pin = pin_params['pin'] self._invert = pin_params['invert'] self._start_value = self._shutdown_value = self._invert - self._is_static = False self._max_duration = 2. self._last_clock = 0 self._set_cmd = None @@ -340,17 +367,10 @@ def get_mcu(self): return self._mcu def setup_max_duration(self, max_duration): self._max_duration = max_duration - def setup_start_value(self, start_value, shutdown_value, is_static=False): - if is_static and start_value != shutdown_value: - raise pins.error("Static pin can not have shutdown value") + def setup_start_value(self, start_value, shutdown_value): self._start_value = (not not start_value) ^ self._invert self._shutdown_value = (not not shutdown_value) ^ self._invert - self._is_static = is_static def _build_config(self): - if self._is_static: - self._mcu.add_config_cmd("set_digital_out pin=%s value=%d" - % (self._pin, self._start_value)) - return 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") @@ -386,10 +406,9 @@ def __init__(self, mcu, pin_params): self._pin = pin_params['pin'] self._invert = pin_params['invert'] self._start_value = self._shutdown_value = float(self._invert) - self._is_static = False - self._last_clock = self._last_cycle_ticks = 0 + self._last_clock = 0 self._pwm_max = 0. - self._set_cmd = self._set_cycle_ticks = None + self._set_cmd = None def get_mcu(self): return self._mcu def setup_max_duration(self, max_duration): @@ -397,15 +416,12 @@ def setup_max_duration(self, 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, is_static=False): - if is_static and start_value != shutdown_value: - raise pins.error("Static pin can not have shutdown value") + 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)) - self._is_static = is_static 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" @@ -420,12 +436,6 @@ def _build_config(self): raise pins.error("PWM pin max duration too large") if self._hardware_pwm: self._pwm_max = self._mcu.get_constant_float("PWM_MAX") - if self._is_static: - self._mcu.add_config_cmd( - "set_pwm_out pin=%s cycle_ticks=%d value=%d" - % (self._pin, cycle_ticks, - self._start_value * self._pwm_max)) - return self._mcu.request_move_queue_slot() self._oid = self._mcu.create_oid() self._mcu.add_config_cmd( @@ -444,10 +454,6 @@ def _build_config(self): # 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 self._is_static: - self._mcu.add_config_cmd("set_digital_out pin=%s value=%d" - % (self._pin, self._start_value >= 0.5)) - return if cycle_ticks >= 1<<31: raise pins.error("PWM pin cycle time too large") self._mcu.request_move_queue_slot() @@ -460,40 +466,21 @@ def _build_config(self): self._mcu.add_config_cmd( "set_digital_out_pwm_cycle oid=%d cycle_ticks=%d" % (self._oid, cycle_ticks)) - self._last_cycle_ticks = cycle_ticks + self._pwm_max = float(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 = self._mcu.lookup_command( "queue_digital_out oid=%c clock=%u on_ticks=%u", cq=cmd_queue) - self._set_cycle_ticks = self._mcu.lookup_command( - "set_digital_out_pwm_cycle oid=%c cycle_ticks=%u", cq=cmd_queue) - def set_pwm(self, print_time, value, cycle_time=None): - clock = self._mcu.print_time_to_clock(print_time) - minclock = self._last_clock - self._last_clock = clock + def set_pwm(self, print_time, value): if self._invert: value = 1. - value - if self._hardware_pwm: - v = int(max(0., min(1., value)) * self._pwm_max + 0.5) - self._set_cmd.send([self._oid, clock, v], - minclock=minclock, reqclock=clock) - return - # Soft pwm update - if cycle_time is None: - cycle_time = self._cycle_time - cycle_ticks = self._mcu.seconds_to_clock(cycle_time) - if cycle_ticks != self._last_cycle_ticks: - if cycle_ticks >= 1<<31: - raise self._mcu.get_printer().command_error( - "PWM cycle time too large") - self._set_cycle_ticks.send([self._oid, cycle_ticks], - minclock=minclock, reqclock=clock) - self._last_cycle_ticks = cycle_ticks - on_ticks = int(max(0., min(1., value)) * float(cycle_ticks) + 0.5) - self._set_cmd.send([self._oid, clock, on_ticks], - minclock=minclock, reqclock=clock) + v = int(max(0., min(1., value)) * self._pwm_max + 0.5) + clock = self._mcu.print_time_to_clock(print_time) + self._set_cmd.send([self._oid, clock, v], + minclock=self._last_clock, reqclock=clock) + self._last_clock = clock class MCU_adc: def __init__(self, mcu, pin_params): @@ -509,8 +496,8 @@ def __init__(self, mcu, pin_params): self._inv_max_adc = 0. def get_mcu(self): return self._mcu - def setup_minmax(self, sample_time, sample_count, - minval=0., maxval=1., range_check_count=0): + def setup_adc_sample(self, sample_time, sample_count, + minval=0., maxval=1., range_check_count=0): self._sample_time = sample_time self._sample_count = sample_count self._min_sample = minval @@ -587,9 +574,8 @@ def __init__(self, config, clocksync): restart_methods = [None, 'arduino', 'cheetah', 'command', 'rpi_usb'] self._restart_method = 'command' if self._baud: - rmethods = {m: m for m in restart_methods} self._restart_method = config.getchoice('restart_method', - rmethods, None) + restart_methods, None) self._reset_cmd = self._config_reset_cmd = None self._is_mcu_bridge = False self._emergency_stop_cmd = None @@ -619,6 +605,7 @@ def __init__(self, config, clocksync): self._mcu_tick_stddev = 0. self._mcu_tick_awake = 0. # Register handlers + printer.load_object(config, "error_mcu") printer.register_event_handler("klippy:firmware_restart", self._firmware_restart) printer.register_event_handler("klippy:mcu_identify", @@ -626,6 +613,7 @@ def __init__(self, config, clocksync): printer.register_event_handler("klippy:connect", self._connect) printer.register_event_handler("klippy:shutdown", self._shutdown) printer.register_event_handler("klippy:disconnect", self._disconnect) + printer.register_event_handler("klippy:ready", self._ready) # Serial callbacks def _handle_mcu_stats(self, params): count = params['count'] @@ -644,13 +632,13 @@ def _handle_shutdown(self, params): if clock is not None: self._shutdown_clock = self.clock32_to_clock64(clock) self._shutdown_msg = msg = params['static_string_id'] - logging.info("MCU '%s' %s: %s\n%s\n%s", self._name, params['#name'], + event_type = params['#name'] + self._printer.invoke_async_shutdown( + "MCU shutdown", {"reason": msg, "mcu": self._name, + "event_type": event_type}) + logging.info("MCU '%s' %s: %s\n%s\n%s", self._name, event_type, self._shutdown_msg, self._clocksync.dump_debug(), self._serial.dump_debug()) - prefix = "MCU '%s' shutdown: " % (self._name,) - if params['#name'] == 'is_shutdown': - prefix = "Previous MCU '%s' shutdown: " % (self._name,) - self._printer.invoke_async_shutdown(prefix + msg + error_help(msg)) def _handle_starting(self, params): if not self._is_shutdown: self._printer.invoke_async_shutdown("MCU '%s' spontaneous restart" @@ -692,7 +680,6 @@ def _send_config(self, prev_crc): self._config_cmds.insert(0, "allocate_oids count=%d" % (self._oid_count,)) # Resolve pin names - mcu_type = self._serial.get_msgparser().get_constant('MCU') ppins = self._printer.lookup_object('pins') pin_resolver = ppins.get_pin_resolver(self._name) for cmdlist in (self._config_cmds, self._restart_cmds, self._init_cmds): @@ -837,6 +824,21 @@ def _mcu_identify(self): self.register_response(self._handle_shutdown, 'shutdown') self.register_response(self._handle_shutdown, 'is_shutdown') self.register_response(self._handle_mcu_stats, 'stats') + def _ready(self): + if self.is_fileoutput(): + return + # Check that reported mcu frequency is in range + mcu_freq = self._mcu_freq + systime = self._reactor.monotonic() + get_clock = self._clocksync.get_clock + calc_freq = get_clock(systime + 1) - get_clock(systime) + mcu_freq_mhz = int(mcu_freq / 1000000. + 0.5) + calc_freq_mhz = int(calc_freq / 1000000. + 0.5) + if mcu_freq_mhz != calc_freq_mhz: + pconfig = self._printer.lookup_object('configfile') + msg = ("MCU '%s' configured for %dMhz but running at %dMhz!" + % (self._name, mcu_freq_mhz, calc_freq_mhz)) + pconfig.runtime_warning(msg) # Config creation helpers def setup_pin(self, pin_type, pin_params): pcs = {'endstop': MCU_endstop, @@ -1007,34 +1009,6 @@ def stats(self, eventtime): self._get_status_info['last_stats'] = last_stats return False, '%s: %s' % (self._name, stats) -Common_MCU_errors = { - ("Timer too close",): """ -This often indicates the host computer is overloaded. Check -for other processes consuming excessive CPU time, high swap -usage, disk errors, overheating, unstable voltage, or -similar system problems on the host computer.""", - ("Missed scheduling of next ",): """ -This is generally indicative of an intermittent -communication failure between micro-controller and host.""", - ("ADC out of range",): """ -This generally occurs when a heater temperature exceeds -its configured min_temp or max_temp.""", - ("Rescheduled timer in the past", "Stepper too far in past"): """ -This generally occurs when the micro-controller has been -requested to step at a rate higher than it is capable of -obtaining.""", - ("Command request",): """ -This generally occurs in response to an M112 G-Code command -or in response to an internal error in the host software.""", -} - -def error_help(msg): - for prefixes, help_msg in Common_MCU_errors.items(): - for prefix in prefixes: - if msg.startswith(prefix): - return help_msg - return "" - def add_printer_objects(config): printer = config.get_printer() reactor = printer.get_reactor() diff --git a/klippy/msgproto.py b/klippy/msgproto.py index f8a12530e67e..0fe765934590 100644 --- a/klippy/msgproto.py +++ b/klippy/msgproto.py @@ -1,6 +1,6 @@ # Protocol definitions for firmware communication # -# Copyright (C) 2016-2021 Kevin O'Connor +# Copyright (C) 2016-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. import json, zlib, logging @@ -160,8 +160,8 @@ def convert_msg_format(msgformat): return msgformat class MessageFormat: - def __init__(self, msgid, msgformat, enumerations={}): - self.msgid = msgid + def __init__(self, msgid_bytes, msgformat, enumerations={}): + self.msgid_bytes = msgid_bytes self.msgformat = msgformat self.debugformat = convert_msg_format(msgformat) self.name = msgformat.split()[0] @@ -169,19 +169,17 @@ def __init__(self, msgid, msgformat, enumerations={}): self.param_types = [t for name, t in self.param_names] self.name_to_type = dict(self.param_names) def encode(self, params): - out = [] - out.append(self.msgid) + out = list(self.msgid_bytes) for i, t in enumerate(self.param_types): t.encode(out, params[i]) return out def encode_by_name(self, **params): - out = [] - out.append(self.msgid) + out = list(self.msgid_bytes) for name, t in self.param_names: t.encode(out, params[name]) return out def parse(self, s, pos): - pos += 1 + pos += len(self.msgid_bytes) out = {} for name, t in self.param_names: v, pos = t.parse(s, pos) @@ -198,13 +196,13 @@ def format_params(self, params): class OutputFormat: name = '#output' - def __init__(self, msgid, msgformat): - self.msgid = msgid + def __init__(self, msgid_bytes, msgformat): + self.msgid_bytes = msgid_bytes self.msgformat = msgformat self.debugformat = convert_msg_format(msgformat) self.param_types = lookup_output_params(msgformat) def parse(self, s, pos): - pos += 1 + pos += len(self.msgid_bytes) out = [] for t in self.param_types: v, pos = t.parse(s, pos) @@ -219,7 +217,7 @@ def format_params(self, params): class UnknownFormat: name = '#unknown' def parse(self, s, pos): - msgid = s[pos] + msgid, param_pos = PT_int32().parse(s, pos) msg = bytes(bytearray(s)) return {'#msgid': msgid, '#msg': msg}, len(s)-MESSAGE_TRAILER_SIZE def format_params(self, params): @@ -234,7 +232,8 @@ def __init__(self, warn_prefix=""): self.messages = [] self.messages_by_id = {} self.messages_by_name = {} - self.msgtag_by_format = {} + self.msgid_by_format = {} + self.msgid_parser = PT_int32() self.config = {} self.version = self.build_versions = "" self.raw_identify_data = "" @@ -266,7 +265,7 @@ def dump(self, s): out = ["seq: %02x" % (msgseq,)] pos = MESSAGE_HEADER_SIZE while 1: - msgid = s[pos] + msgid, param_pos = self.msgid_parser.parse(s, pos) mid = self.messages_by_id.get(msgid, self.unknown) params, pos = mid.parse(s, pos) out.append(mid.format_params(params)) @@ -283,14 +282,14 @@ def format_params(self, params): return "%s %s" % (name, msg) return str(params) def parse(self, s): - msgid = s[MESSAGE_HEADER_SIZE] + msgid, param_pos = self.msgid_parser.parse(s, MESSAGE_HEADER_SIZE) mid = self.messages_by_id.get(msgid, self.unknown) params, pos = mid.parse(s, MESSAGE_HEADER_SIZE) if pos != len(s)-MESSAGE_TRAILER_SIZE: self._error("Extra data at end of message") params['#name'] = mid.name return params - def encode(self, seq, cmd): + def encode_msgblock(self, seq, cmd): msglen = MESSAGE_MIN + len(cmd) seq = (seq & MESSAGE_SEQ_MASK) | MESSAGE_DEST out = [msglen, seq] + cmd @@ -317,11 +316,11 @@ def lookup_command(self, msgformat): self._error("Command format mismatch: %s vs %s", msgformat, mp.msgformat) return mp - def lookup_msgtag(self, msgformat): - msgtag = self.msgtag_by_format.get(msgformat) - if msgtag is None: + def lookup_msgid(self, msgformat): + msgid = self.msgid_by_format.get(msgformat) + if msgid is None: self._error("Unknown command: %s", msgformat) - return msgtag + return msgid def create_command(self, msg): parts = msg.strip().split() if not parts: @@ -372,22 +371,22 @@ def fill_enumerations(self, enumerations): start_value, count = value for i in range(count): enums[enum_root + str(start_enum + i)] = start_value + i - def _init_messages(self, messages, command_tags=[], output_tags=[]): - for msgformat, msgtag in messages.items(): + def _init_messages(self, messages, command_ids=[], output_ids=[]): + for msgformat, msgid in messages.items(): msgtype = 'response' - if msgtag in command_tags: + if msgid in command_ids: msgtype = 'command' - elif msgtag in output_tags: + elif msgid in output_ids: msgtype = 'output' - self.messages.append((msgtag, msgtype, msgformat)) - if msgtag < -32 or msgtag > 95: - self._error("Multi-byte msgtag not supported") - self.msgtag_by_format[msgformat] = msgtag - msgid = msgtag & 0x7f + self.messages.append((msgid, msgtype, msgformat)) + self.msgid_by_format[msgformat] = msgid + msgid_bytes = [] + self.msgid_parser.encode(msgid_bytes, msgid) if msgtype == 'output': - self.messages_by_id[msgid] = OutputFormat(msgid, msgformat) + self.messages_by_id[msgid] = OutputFormat(msgid_bytes, + msgformat) else: - msg = MessageFormat(msgid, msgformat, self.enumerations) + msg = MessageFormat(msgid_bytes, msgformat, self.enumerations) self.messages_by_id[msgid] = msg self.messages_by_name[msg.name] = msg def process_identify(self, data, decompress=True): diff --git a/klippy/serialhdl.py b/klippy/serialhdl.py index f9e542d35029..6aee564814d9 100644 --- a/klippy/serialhdl.py +++ b/klippy/serialhdl.py @@ -166,7 +166,8 @@ def connect_pipe(self, filename): try: fd = os.open(filename, os.O_RDWR | os.O_NOCTTY) except OSError as e: - logging.warn("%sUnable to open port: %s", self.warn_prefix, e) + logging.warning("%sUnable to open port: %s", + self.warn_prefix, e) self.reactor.pause(self.reactor.monotonic() + 5.) continue serial_dev = os.fdopen(fd, 'rb+', 0) @@ -187,7 +188,7 @@ def connect_uart(self, serialport, baud, rts=True): serial_dev.rts = rts serial_dev.open() except (OSError, IOError, serial.SerialException) as e: - logging.warn("%sUnable to open serial port: %s", + logging.warning("%sUnable to open serial port: %s", self.warn_prefix, e) self.reactor.pause(self.reactor.monotonic() + 5.) continue @@ -291,13 +292,13 @@ def _handle_unknown_init(self, params): logging.debug("%sUnknown message %d (len %d) while identifying", self.warn_prefix, params['#msgid'], len(params['#msg'])) def handle_unknown(self, params): - logging.warn("%sUnknown message type %d: %s", + logging.warning("%sUnknown message type %d: %s", self.warn_prefix, params['#msgid'], repr(params['#msg'])) def handle_output(self, params): logging.info("%s%s: %s", self.warn_prefix, params['#name'], params['#msg']) def handle_default(self, params): - logging.warn("%sgot %s", self.warn_prefix, params) + logging.warning("%sgot %s", self.warn_prefix, params) # Class to send a query command and return the received response class SerialRetryCommand: diff --git a/klippy/stepper.py b/klippy/stepper.py index 56c8ec758e17..9b692904dcf9 100644 --- a/klippy/stepper.py +++ b/klippy/stepper.py @@ -265,6 +265,7 @@ def parse_gear_ratio(config, note_valid): # Obtain "step distance" information from a config section def parse_step_distance(config, units_in_radians=None, note_valid=False): + # Check rotation_distance and gear_ratio if units_in_radians is None: # Caller doesn't know if units are in radians - infer it rd = config.get('rotation_distance', None, note_valid=False) @@ -276,7 +277,7 @@ def parse_step_distance(config, units_in_radians=None, note_valid=False): else: rotation_dist = config.getfloat('rotation_distance', above=0., note_valid=note_valid) - # Newer config format with rotation_distance + # Check microsteps and full_steps_per_rotation microsteps = config.getint('microsteps', minval=1, note_valid=note_valid) full_steps = config.getint('full_steps_per_rotation', 200, minval=1, note_valid=note_valid) diff --git a/klippy/toolhead.py b/klippy/toolhead.py index 477b5fce08cc..4149d53b12f6 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -1,6 +1,6 @@ # Code for coordinating events on the printer toolhead # -# Copyright (C) 2016-2021 Kevin O'Connor +# Copyright (C) 2016-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. import math, logging, importlib @@ -110,7 +110,7 @@ def set_junction(self, start_v2, cruise_v2, end_v2): # Class to track a list of pending move requests and to facilitate # "look-ahead" across moves to reduce acceleration between moves. -class MoveQueue: +class LookAheadQueue: def __init__(self, toolhead): self.toolhead = toolhead self.queue = [] @@ -191,6 +191,7 @@ def add_move(self, move): BUFFER_TIME_START = 0.250 BGFLUSH_LOW_TIME = 0.200 BGFLUSH_BATCH_TIME = 0.200 +BGFLUSH_EXTRA_TIME = 0.250 MIN_KIN_TIME = 0.100 MOVE_BATCH_TIME = 0.500 STEPCOMPRESS_FLUSH_TIME = 0.050 @@ -210,18 +211,26 @@ 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.move_queue = MoveQueue(self) - self.move_queue.set_flush_time(BUFFER_TIME_HIGH) + self.lookahead = LookAheadQueue(self) + self.lookahead.set_flush_time(BUFFER_TIME_HIGH) self.commanded_pos = [0., 0., 0., 0.] # Velocity and acceleration control self.max_velocity = config.getfloat('max_velocity', above=0.) self.max_accel = config.getfloat('max_accel', above=0.) - self.requested_accel_to_decel = config.getfloat( - 'max_accel_to_decel', self.max_accel * 0.5, above=0.) - self.max_accel_to_decel = self.requested_accel_to_decel + min_cruise_ratio = 0.5 + if config.getfloat('minimum_cruise_ratio', None) is None: + req_accel_to_decel = config.getfloat('max_accel_to_decel', None, + above=0.) + if req_accel_to_decel is not None: + config.deprecate('max_accel_to_decel') + min_cruise_ratio = 1. - min(1., (req_accel_to_decel + / self.max_accel)) + self.min_cruise_ratio = config.getfloat('minimum_cruise_ratio', + min_cruise_ratio, + below=1., minval=0.) self.square_corner_velocity = config.getfloat( 'square_corner_velocity', 5., minval=0.) - self.junction_deviation = 0. + self.junction_deviation = self.max_accel_to_decel = 0. self._calc_junction_deviation() # Input stall detection self.check_stall_time = 0. @@ -239,7 +248,7 @@ def __init__(self, config): # Flush tracking self.flush_timer = self.reactor.register_timer(self._flush_handler) self.do_kick_flush_timer = True - self.last_flush_time = self.last_sg_flush_time = 0. + self.last_flush_time = self.min_restart_time = 0. self.need_flush_time = self.step_gen_time = self.clear_history_time = 0. # Kinematic step generation scan window time tracking self.kin_flush_delay = SDS_CHECK_TIME @@ -289,7 +298,7 @@ def _advance_flush_time(self, flush_time): sg_flush_time = max(sg_flush_want, flush_time) for sg in self.step_generators: sg(sg_flush_time) - self.last_sg_flush_time = sg_flush_time + self.min_restart_time = max(self.min_restart_time, sg_flush_time) # Free trapq entries that are no longer needed clear_history_time = self.clear_history_time if not self.can_pause: @@ -314,7 +323,7 @@ def _advance_move_time(self, next_print_time): 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.last_sg_flush_time) + kin_time = max(est_print_time + MIN_KIN_TIME, self.min_restart_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: @@ -348,25 +357,26 @@ def _process_moves(self, moves): # Generate steps for moves if self.special_queuing_state: self._update_drip_move_time(next_move_time) - self.note_kinematic_activity(next_move_time + self.kin_flush_delay, - set_step_gen_time=True) + self.note_mcu_movequeue_activity(next_move_time + self.kin_flush_delay, + set_step_gen_time=True) self._advance_move_time(next_move_time) def _flush_lookahead(self): # Transit from "NeedPrime"/"Priming"/"Drip"/main state to "NeedPrime" - self.move_queue.flush() + self.lookahead.flush() self.special_queuing_state = "NeedPrime" self.need_check_pause = -1. - self.move_queue.set_flush_time(BUFFER_TIME_HIGH) + self.lookahead.set_flush_time(BUFFER_TIME_HIGH) self.check_stall_time = 0. def flush_step_generation(self): self._flush_lookahead() self._advance_flush_time(self.step_gen_time) + self.min_restart_time = max(self.min_restart_time, self.print_time) def get_last_move_time(self): if self.special_queuing_state: self._flush_lookahead() self._calc_print_time() else: - self.move_queue.flush() + self.lookahead.flush() return self.print_time def _check_pause(self): eventtime = self.reactor.monotonic() @@ -427,14 +437,15 @@ def _flush_handler(self, eventtime): 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: + end_flush = self.need_flush_time + BGFLUSH_EXTRA_TIME + if self.last_flush_time >= end_flush: 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)) + self._advance_flush_time(min(end_flush, ftime)) except: logging.exception("Exception in flush_handler") self.printer.invoke_shutdown("Exception in flush_handler") @@ -459,7 +470,7 @@ def move(self, newpos, speed): if move.axes_d[3]: self.extruder.check_move(move) self.commanded_pos[:] = move.end_pos - self.move_queue.add_move(move) + self.lookahead.add_move(move) if self.print_time > self.need_check_pause: self._check_pause() def manual_move(self, coord, speed): @@ -500,18 +511,18 @@ 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.note_kinematic_activity(npt + self.kin_flush_delay, - set_step_gen_time=True) + self.note_mcu_movequeue_activity(npt + self.kin_flush_delay, + set_step_gen_time=True) self._advance_move_time(npt) def drip_move(self, newpos, speed, drip_completion): self.dwell(self.kin_flush_delay) # Transition from "NeedPrime"/"Priming"/main state to "Drip" state - self.move_queue.flush() + self.lookahead.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.lookahead.set_flush_time(BUFFER_TIME_HIGH) self.check_stall_time = 0. self.drip_completion = drip_completion # Submit move @@ -523,9 +534,9 @@ def drip_move(self, newpos, speed, drip_completion): raise # Transmit move in "drip" mode try: - self.move_queue.flush() + self.lookahead.flush() except DripModeEndSignal as e: - self.move_queue.reset() + self.lookahead.reset() self.trapq_finalize_moves(self.trapq, self.reactor.NEVER, 0) # Exit "Drip" state self.reactor.update_timer(self.flush_timer, self.reactor.NOW) @@ -545,7 +556,7 @@ def stats(self, eventtime): self.print_time, max(buffer_time, 0.), self.print_stall) def check_busy(self, eventtime): est_print_time = self.mcu.estimated_print_time(eventtime) - lookahead_empty = not self.move_queue.queue + lookahead_empty = not self.lookahead.queue return self.print_time, est_print_time, lookahead_empty def get_status(self, eventtime): print_time = self.print_time @@ -558,12 +569,12 @@ def get_status(self, eventtime): 'position': self.Coord(*self.commanded_pos), 'max_velocity': self.max_velocity, 'max_accel': self.max_accel, - 'max_accel_to_decel': self.requested_accel_to_decel, + 'minimum_cruise_ratio': self.min_cruise_ratio, 'square_corner_velocity': self.square_corner_velocity}) return res def _handle_shutdown(self): self.can_pause = False - self.move_queue.reset() + self.lookahead.reset() def get_kinematics(self): return self.kin def get_trapq(self): @@ -572,7 +583,6 @@ def register_step_generator(self, handler): self.step_generators.append(handler) def note_step_generation_scan_time(self, delay, old_delay=0.): self.flush_step_generation() - cur_delay = self.kin_flush_delay if old_delay: self.kin_flush_times.pop(self.kin_flush_times.index(old_delay)) if delay: @@ -580,15 +590,15 @@ def note_step_generation_scan_time(self, delay, old_delay=0.): new_delay = max(self.kin_flush_times + [SDS_CHECK_TIME]) self.kin_flush_delay = new_delay def register_lookahead_callback(self, callback): - last_move = self.move_queue.get_last() + last_move = self.lookahead.get_last() if last_move is None: callback(self.get_last_move_time()) return last_move.timing_callbacks.append(callback) - def note_kinematic_activity(self, kin_time, set_step_gen_time=False): - self.need_flush_time = max(self.need_flush_time, kin_time) + def note_mcu_movequeue_activity(self, mq_time, set_step_gen_time=False): + self.need_flush_time = max(self.need_flush_time, mq_time) if set_step_gen_time: - self.step_gen_time = max(self.step_gen_time, kin_time) + self.step_gen_time = max(self.step_gen_time, mq_time) if self.do_kick_flush_timer: self.do_kick_flush_timer = False self.reactor.update_timer(self.flush_timer, self.reactor.NOW) @@ -597,8 +607,7 @@ def get_max_velocity(self): def _calc_junction_deviation(self): scv2 = self.square_corner_velocity**2 self.junction_deviation = scv2 * (math.sqrt(2.) - 1.) / self.max_accel - self.max_accel_to_decel = min(self.requested_accel_to_decel, - self.max_accel) + self.max_accel_to_decel = self.max_accel * (1. - self.min_cruise_ratio) def cmd_G4(self, gcmd): # Dwell delay = gcmd.get_float('P', 0., minval=0.) / 1000. @@ -612,29 +621,34 @@ def cmd_SET_VELOCITY_LIMIT(self, gcmd): max_accel = gcmd.get_float('ACCEL', None, above=0.) square_corner_velocity = gcmd.get_float( 'SQUARE_CORNER_VELOCITY', None, minval=0.) - requested_accel_to_decel = gcmd.get_float( - 'ACCEL_TO_DECEL', None, above=0.) + min_cruise_ratio = gcmd.get_float( + 'MINIMUM_CRUISE_RATIO', None, minval=0., below=1.) + if min_cruise_ratio is None: + req_accel_to_decel = gcmd.get_float('ACCEL_TO_DECEL', + None, above=0.) + if req_accel_to_decel is not None and max_accel is not None: + min_cruise_ratio = 1. - min(1., req_accel_to_decel / max_accel) + elif req_accel_to_decel is not None and max_accel is None: + min_cruise_ratio = 1. - min(1., (req_accel_to_decel + / self.max_accel)) if max_velocity is not None: self.max_velocity = max_velocity if max_accel is not None: self.max_accel = max_accel if square_corner_velocity is not None: self.square_corner_velocity = square_corner_velocity - if requested_accel_to_decel is not None: - self.requested_accel_to_decel = requested_accel_to_decel + if min_cruise_ratio is not None: + self.min_cruise_ratio = min_cruise_ratio self._calc_junction_deviation() msg = ("max_velocity: %.6f\n" "max_accel: %.6f\n" - "max_accel_to_decel: %.6f\n" + "minimum_cruise_ratio: %.6f\n" "square_corner_velocity: %.6f" % ( self.max_velocity, self.max_accel, - self.requested_accel_to_decel, - self.square_corner_velocity)) + self.min_cruise_ratio, self.square_corner_velocity)) self.printer.set_rollover_info("toolhead", "toolhead: %s" % (msg,)) - if (max_velocity is None and - max_accel is None and - square_corner_velocity is None and - requested_accel_to_decel is None): + if (max_velocity is None and max_accel is None + and square_corner_velocity is None and min_cruise_ratio is None): gcmd.respond_info(msg, log=False) def cmd_M204(self, gcmd): # Use S for accel diff --git a/lib/README b/lib/README index e981df59f78d..106b2cfc35fa 100644 --- a/lib/README +++ b/lib/README @@ -132,7 +132,7 @@ details. See changes.diff for the modifications. The pru_rpmsg directory contains code from: https://github.com/dinuxbg/pru-gcc-examples -revision 425a42d82006cf0aa24be27b483d2f6a41607489. The code is taken +revision e2bd170d4d61b3e642da65e0f0d487e10872fe22. The code is taken from the repo's hc-sr04-range-sensor directory. It has been modified so that the IEP definitions compile correctly. See pru_rpmsg.patch for the modifications. diff --git a/lib/pru_rpmsg/include/am335x/pru_cfg.h b/lib/pru_rpmsg/include/am335x/pru_cfg.h deleted file mode 100644 index 175867c6fac9..000000000000 --- a/lib/pru_rpmsg/include/am335x/pru_cfg.h +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ - * - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the - * distribution. - * - * * Neither the name of Texas Instruments Incorporated nor the names of - * its contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef _PRU_CFG_H_ -#define _PRU_CFG_H_ - -/* PRU_CFG register set */ -typedef struct { - - /* PRU_CFG_REVID register bit field */ - union { - volatile uint32_t REVID; - - volatile struct { - unsigned REVID : 32; - } REVID_bit; - }; // 0x0 - - - /* PRU_CFG_SYSCFG register bit field */ - union { - volatile uint32_t SYSCFG; - - volatile struct { - unsigned IDLE_MODE : 2; - unsigned STANDBY_MODE : 2; - unsigned STANDBY_INIT : 1; - unsigned SUB_MWAIT : 1; - unsigned rsvd6 : 26; - } SYSCFG_bit; - }; // 0x4 - - - /* PRU_CFG_GPCFG0 register bit field */ - union { - volatile uint32_t GPCFG0; - - volatile struct { - unsigned PRU0_GPI_MODE : 2; // 1:0 - unsigned PRU0_GPI_CLK_MODE : 1; // 2 - unsigned PRU0_GPI_DIV0 : 5; // 7:3 - unsigned PRU0_GPI_DIV1 : 5; // 12:8 - unsigned PRU0_GPI_SB : 1; // 13 - unsigned PRU0_GPO_MODE : 1; // 14 - unsigned PRU0_GPO_DIV0 : 5; // 19:15 - unsigned PRU0_GPO_DIV1 : 5; // 24:20 - unsigned PRU0_GPO_SH_SEL : 1; // 25 - unsigned rsvd26 : 6; // 31:26 - } GPCFG0_bit; - }; // 0x8 - - - /* PRU_CFG_GPCFG1 register bit field */ - union { - volatile uint32_t GPCFG1; - - volatile struct { - unsigned PRU1_GPI_MODE : 2; // 1:0 - unsigned PRU1_GPI_CLK_MODE : 1; // 2 - unsigned PRU1_GPI_DIV0 : 5; // 7:3 - unsigned PRU1_GPI_DIV1 : 5; // 12:8 - unsigned PRU1_GPI_SB : 1; // 13 - unsigned PRU1_GPO_MODE : 1; // 14 - unsigned PRU1_GPO_DIV0 : 5; // 19:15 - unsigned PRU1_GPO_DIV1 : 5; // 24:20 - unsigned PRU1_GPO_SH_SEL : 1; // 25 - unsigned rsvd26 : 6; // 31:26 - } GPCFG1_bit; - }; // 0xC - - - /* PRU_CFG_CGR register bit field */ - union { - volatile uint32_t CGR; - - volatile struct { - unsigned PRU0_CLK_STOP_REQ : 1; // 0 - unsigned PRU0_CLK_STOP_ACK : 1; // 1 - unsigned PRU0_CLK_EN : 1; // 2 - unsigned PRU1_CLK_STOP_REQ : 1; // 3 - unsigned PRU1_CLK_STOP_ACK : 1; // 4 - unsigned PRU1_CLK_EN : 1; // 5 - unsigned INTC_CLK_STOP_REQ : 1; // 6 - unsigned INTC_CLK_STOP_ACK : 1; // 7 - unsigned INTC_CLK_EN : 1; // 8 - unsigned UART_CLK_STOP_REQ : 1; // 9 - unsigned UART_CLK_STOP_ACK : 1; // 10 - unsigned UART_CLK_EN : 1; // 11 - unsigned ECAP_CLK_STOP_REQ : 1; // 12 - unsigned ECAP_CLK_STOP_ACK : 1; // 13 - unsigned ECAP_CLK_EN : 1; // 14 - unsigned IEP_CLK_STOP_REQ : 1; // 15 - unsigned IEP_CLK_STOP_ACK : 1; // 16 - unsigned IEP_CLK_EN : 1; // 17 - unsigned rsvd18 : 14; // 31:18 - } CGR_bit; - }; // 0x10 - - - /* PRU_CFG_ISRP register bit field */ - union { - volatile uint32_t ISRP; - - volatile struct { - unsigned PRU0_IMEM_PE_RAW : 4; // 3:0 - unsigned PRU0_DMEM_PE_RAW : 4; // 7:4 - unsigned PRU1_IMEM_PE_RAW : 4; // 11:8 - unsigned PRU1_DMEM_PE_RAW : 4; // 15:12 - unsigned RAM_PE_RAW : 4; // 19:16 - unsigned rsvd20 : 12; // 31:20 - } ISRP_bit; - }; // 0x14 - - - /* PRU_CFG_ISP register bit field */ - union { - volatile uint32_t ISP; - - volatile struct { - unsigned PRU0_IMEM_PE : 4; // 3:0 - unsigned PRU0_DMEM_PE : 4; // 7:4 - unsigned PRU1_IMEM_PE : 4; // 11:8 - unsigned PRU1_DMEM_PE : 4; // 15:12 - unsigned RAM_PE : 4; // 19:16 - unsigned rsvd20 : 12; // 31:20 - } ISP_bit; - }; // 0x18 - - /* PRU_CFG_IESP register bit field */ - union { - volatile uint32_t IESP; - - volatile struct { - unsigned PRU0_IMEM_PE_SET : 4; // 3:0 - unsigned PRU0_DMEM_PE_SET : 4; // 7:4 - unsigned PRU1_IMEM_PE_SET : 4; // 11:8 - unsigned PRU1_DMEM_PE_SET : 4; // 15:12 - unsigned RAM_PE_SET : 4; // 19:16 - unsigned rsvd20 : 12; // 31:20 - } IESP_bit; - }; // 0x1C - - - /* PRU_CFG_IECP register bit field */ - union { - volatile uint32_t IECP; - - volatile struct { - unsigned PRU0_IMEM_PE_CLR : 4; // 3:0 - unsigned PRU0_DMEM_PE_CLR : 4; // 7:4 - unsigned PRU1_IMEM_PE_CLR : 4; // 11:8 - unsigned PRU1_DMEM_PE_CLR : 4; // 15:12 - unsigned rsvd16 : 16; // 31:16 - } IECP_bit; - }; // 0x20 - - - uint32_t rsvd24; // 0x24 - - - /* PRU_CFG_PMAO register bit field */ - union { - volatile uint32_t PMAO; - - volatile struct { - unsigned PMAO_PRU0 : 1; // 0 - unsigned PMAO_PRU1 : 1; // 1 - unsigned rsvd2 : 30; // 31:2 - } PMAO_bit; - }; // 0x28 - - - uint32_t rsvd2c[1]; // 0x2C - - - /* PRU_CFG_IEPCLK register bit field */ - union { - volatile uint32_t IEPCLK; - - volatile struct { - unsigned OCP_EN : 1; // 0 - unsigned rsvd1 : 31; // 31:1 - } IEPCLK_bit; - }; // 0x30 - - - /* PRU_CFG_SPP register bit field */ - union { - volatile uint32_t SPP; - - volatile struct { - unsigned PRU1_PAD_HP_EN : 1; // 0 - unsigned XFR_SHIFT_EN : 1; // 1 - unsigned rsvd2 : 30; // 31:2 - } SPP_bit; - }; // 0x34 - - - uint32_t rsvd38[2]; // 0x38 - 0x3C - - - union { - volatile uint32_t PIN_MX; - - volatile struct { - unsigned PIN_MUX_SEL : 8; // 7:0 - unsigned rsvd2 : 24; // 31:8 - } PIN_MX_bit; - }; //0x40 -} pruCfg; - -#ifdef __GNUC__ -static volatile pruCfg *__CT_CFG = (void *)0x00026000; -#define CT_CFG (*__CT_CFG) -#else -volatile __far pruCfg CT_CFG __attribute__((cregister("PRU_CFG", near), peripheral)); -#endif - -#endif /* _PRU_CFG_H_ */ diff --git a/lib/pru_rpmsg/include/am335x/pru_ctrl.h b/lib/pru_rpmsg/include/am335x/pru_ctrl.h deleted file mode 100644 index 3bade9f26dc2..000000000000 --- a/lib/pru_rpmsg/include/am335x/pru_ctrl.h +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ - * - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the - * distribution. - * - * * Neither the name of Texas Instruments Incorporated nor the names of - * its contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef _PRU_CTRL_H_ -#define _PRU_CTRL_H_ - -/* PRU_CTRL register set */ -typedef struct { - - /* PRU_CTRL_CTRL register bit field */ - union { - volatile uint32_t CTRL; - - volatile struct { - unsigned SOFT_RST_N : 1; - unsigned EN : 1; - unsigned SLEEPING : 1; - unsigned CTR_EN : 1; - unsigned rsvd4 : 4; - unsigned SINGLE_STEP : 1; - unsigned rsvd9 : 6; - unsigned RUNSTATE : 1; - unsigned PCTR_RST_VAL : 16; - } CTRL_bit; - }; // 0x0 - - - /* PRU_CTRL_STS register bit field */ - union { - volatile uint32_t STS; - - volatile struct { - unsigned PCTR : 16; - unsigned rsvd16 : 16; - } STS_bit; - }; // 0x4 - - - /* PRU_CTRL_WAKEUP_EN register bit field */ - union { - volatile uint32_t WAKEUP_EN; - - volatile struct { - unsigned BITWISE_ENS : 32; - } WAKEUP_EN_bit; - }; // 0x8 - - - /* PRU_CTRL_CYCLE register bit field */ - union { - volatile uint32_t CYCLE; - - volatile struct { - unsigned CYCLECOUNT : 32; - } CYCLE_bit; - }; // 0xC - - - /* PRU_CTRL_STALL register bit field */ - union { - volatile uint32_t STALL; - - volatile struct { - unsigned STALLCOUNT : 32; - } STALL_bit; - }; // 0x10 - - - uint32_t rsvd14[3]; // 0x14 - 0x1C - - - /* PRU_CTRL_CTBIR0 register bit field */ - union { - volatile uint32_t CTBIR0; - - volatile struct { - unsigned C24_BLK_IDX : 8; - unsigned rsvd8 : 8; - unsigned C25_BLK_IDX : 8; - unsigned rsvd24 : 8; - } CTBIR0_bit; - }; // 0x20 - - - /* PRU_CTRL_CTBIR1 register bit field */ - union { - volatile uint32_t CTBIR1; - - volatile struct { - unsigned C26_BLK_IDX : 8; - unsigned rsvd8 : 8; - unsigned C27_BLK_IDX : 8; - unsigned rsvd24 : 8; - } CTBIR1_bit; - }; // 0x24 - - - /* PRU_CTRL_CTPPR0 register bit field */ - union { - volatile uint32_t CTPPR0; - - volatile struct { - unsigned C28_BLK_POINTER : 16; - unsigned C29_BLK_POINTER : 16; - } CTPPR0_bit; - }; // 0x28 - - - /* PRU_CTRL_CTPPR1 register bit field */ - union { - volatile uint32_t CTPPR1; - - volatile struct { - unsigned C30_BLK_POINTER : 16; - unsigned C31_BLK_POINTER : 16; - } CTPPR1_bit; - }; // 0x2C - -} pruCtrl; - -/* Definition of control register structures. */ -#define PRU0_CTRL (*((volatile pruCtrl*)0x22000)) -#define PRU1_CTRL (*((volatile pruCtrl*)0x24000)) - -#endif /* _PRU_CTRL_H_ */ diff --git a/lib/pru_rpmsg/include/am335x/pru_ecap.h b/lib/pru_rpmsg/include/am335x/pru_ecap.h deleted file mode 100644 index 8385fc9b686a..000000000000 --- a/lib/pru_rpmsg/include/am335x/pru_ecap.h +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ - * - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the - * distribution. - * - * * Neither the name of Texas Instruments Incorporated nor the names of - * its contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef _PRU_ECAP_H_ -#define _PRU_ECAP_H_ - -/* PRU_ECAP register set */ -typedef struct { - - /* PRU_ECAP_TSCTR register bit field */ - union { - volatile uint32_t TSCTR; - - volatile struct { - unsigned TSCTR : 32; //31:0 - } TSCTR_bit; - }; // 0x0 - - /* PRU_ECAP_CTRPHS register bit field */ - union { - volatile uint32_t CTRPHS; - - volatile struct { - unsigned CTRPHS : 32; //31:0 - } CTRPHS_bit; - }; // 0x4 - - /* PRU_ECAP_CAP1 register bit field */ - union { - volatile uint32_t CAP1; - - volatile struct { - unsigned CAP1 : 32; //31:0 - } CAP1_bit; - }; // 0x8 - - /* PRU_ECAP_CAP2 register bit field */ - union { - volatile uint32_t CAP2; - - volatile struct { - unsigned CAP2 : 32; //31:0 - } CAP2_bit; - }; // 0xC - - /* PRU_ECAP_CAP3 register bit field */ - union { - volatile uint32_t CAP3; - - volatile struct { - unsigned CAP3 : 32; //31:0 - } CAP3_bit; - }; // 0x10 - - /* PRU_ECAP_CAP4 register bit field */ - union { - volatile uint32_t CAP4; - - volatile struct { - unsigned CAP4 : 32; //31:0 - } CAP4_bit; - }; // 0x14 - - uint32_t rsvd118[4]; // 0x118 - 0x124 - - /* PRU_ECAP_ECCTL1 register bit field */ - volatile uint16_t ECCTL1; // 0x28 - - /* PRU_ECAP_ECCTL2 register bit field */ - volatile uint16_t ECCTL2; // 0x2A - - /* PRU_ECAP_ECEINT register bit field */ - volatile uint16_t ECEINT; // 0x2C - - /* PRU_ECAP_ECFLG register bit field */ - volatile uint16_t ECFLG; // 0x2E - - /* PRU_ECAP_ECCLR register bit field */ - volatile uint16_t ECCLR; // 0x30 - - /* PRU_ECAP_ECFRC register bit field */ - volatile uint16_t ECFRC; // 0x32 - - uint32_t rsvd34[10]; // 0x34 - 0x58 - - /* PRU_ECAP_REVID register bit field */ - union { - volatile uint32_t REVID; - - volatile struct { - unsigned REV : 32; //31:0 - } REVID_bit; - }; // 0x5C -} pruEcap; - -volatile __far pruEcap CT_ECAP __attribute__((cregister("PRU_ECAP", near), peripheral)); - -#endif /* _PRU_ECAP_H_ */ diff --git a/lib/pru_rpmsg/include/am335x/pru_intc.h b/lib/pru_rpmsg/include/am335x/pru_intc.h deleted file mode 100644 index 1291940da500..000000000000 --- a/lib/pru_rpmsg/include/am335x/pru_intc.h +++ /dev/null @@ -1,912 +0,0 @@ -/* - * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ - * - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the - * distribution. - * - * * Neither the name of Texas Instruments Incorporated nor the names of - * its contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef _PRU_INTC_H_ -#define _PRU_INTC_H_ - -/* PRU INTC register set */ -typedef struct { - - /* PRU_INTC_REVID register bit field */ - union { - volatile uint32_t REVID; - - volatile struct { - unsigned REV_MINOR : 6; // 5:0 - unsigned REV_CUSTOM : 2; // 7:6 - unsigned REV_MAJOR : 3; // 10:8 - unsigned REV_RTL : 5; // 15:11 - unsigned REV_MODULE : 12; // 27:16 - unsigned rsvd28 : 2; // 29:28 - unsigned REV_SCHEME : 2; // 31:30 - } REVID_bit; - }; // 0x0 - - - /* PRU_INTC_CR register bit field */ - union { - volatile uint32_t CR; - - volatile struct { - unsigned rsvd0 : 2; // 1:0 - unsigned NEST_MODE : 2; // 3:2 - unsigned rsvd4 : 28; // 31:4 - } CR_bit; - }; // 0x4 - - - uint32_t rsvd8[2]; // 0x8 - 0xC - - - /* PRU_INTC_GER register bit field */ - union { - volatile uint32_t GER; - - volatile struct { - unsigned EN_HINT_ANY : 1; // 0 - unsigned rsvd1 : 31; // 31:1 - } GER_bit; - }; // 0x10 - - - uint32_t rsvd14[2]; // 0x14 - 0x18 - - - /* PRU_INTC_GNLR register bit field */ - union { - volatile uint32_t GNLR; - - volatile struct { - unsigned GLB_NEST_LEVEL : 9; // 8:0 - unsigned rsvd9 : 22; // 30:9 - unsigned AUTO_OVERRIDE : 1; // 31 - } GNLR_bit; - }; // 0x1C - - - /* PRU_INTC_SISR register bit field */ - union { - volatile uint32_t SISR; - - volatile struct { - unsigned STS_SET_IDX : 10; // 9:0 - unsigned rsvd10 : 22; // 31:10 - } SISR_bit; - }; // 0x20 - - - /* PRU_INTC_SICR register bit field */ - union { - volatile uint32_t SICR; - - volatile struct { - unsigned STS_CLR_IDX : 10; // 9:0 - unsigned rsvd10 : 22; // 31:10 - } SICR_bit; - }; // 0x24 - - - /* PRU_INTC_EISR register bit field */ - union { - volatile uint32_t EISR; - - volatile struct { - unsigned EN_SET_IDX : 10; // 9:0 - unsigned rsvd10 : 22; // 31:10 - } EISR_bit; - }; // 0x28 - - - /* PRU_INTC_EICR register bit field */ - union { - volatile uint32_t EICR; - - volatile struct { - unsigned EN_CLR_IDX : 10; // 9:0 - unsigned rsvd10 : 22; // 31:10 - } EICR_bit; - }; // 0x2C - - - uint32_t rsvd30; // 0x30 - - - /* PRU_INTC_HIEISR register bit field */ - union { - volatile uint32_t HIEISR; - - volatile struct { - unsigned HINT_EN_SET_IDX : 4; // 3:0 - unsigned rsvd4 : 28; // 31:4 - } HIEISR_bit; - }; // 0x34 - - - /* PRU_INTC_HIDISR register bit field */ - union { - volatile uint32_t HIDISR; - - volatile struct { - unsigned HINT_EN_CLR_IDX : 4; // 3:0 - unsigned rsvd4 : 28; // 31:4 - } HIDISR_bit; - }; // 0x38 - - - uint32_t rsvd3C[17]; // 0x3C - 0x7C - - - /* PRU_INTC_GPIR register bit field */ - union { - volatile uint32_t GPIR; - - volatile struct { - unsigned GLB_PRI_INTR : 10; // 9:0 - unsigned rsvd10 : 21; // 30:10 - unsigned GLB_NONE : 1; // 31 - } GPIR_bit; - }; // 0x80 - - - uint32_t rsvd84[95]; // 0x84 - 0x1FC - - - /* PRU_INTC_SRSR0 register bit field */ - union { - volatile uint32_t SRSR0; - - volatile struct { - unsigned RAW_STS_31_0 : 32; // 31:0 - } SRSR0_bit; - }; // 0x200 - - - /* PRU_INTC_SRSR1 register bit field */ - union { - volatile uint32_t SRSR1; - - volatile struct { - unsigned RAW_STS_63_32 : 32; // 31:0 - } SRSR1_bit; - }; // 0x204 - - - uint32_t rsvd208[30]; // 0x208 - 0x27C - - - /* PRU_INTC_SECR0 register bit field */ - union { - volatile uint32_t SECR0; - - volatile struct { - unsigned ENA_STS_31_0 : 32; // 31:0 - } SECR0_bit; - }; // 0x280 - - - /* PRU_INTC_SECR1 register bit field */ - union { - volatile uint32_t SECR1; - - volatile struct { - unsigned ENA_STS_63_32 : 32; // 31:0 - } SECR1_bit; - }; // 0x284 - - - uint32_t rsvd288[30]; // 0x288 - 0x2FC - - - /* PRU_INTC_ESR0 register bit field */ - union { - volatile uint32_t ESR0; - - volatile struct { - unsigned EN_SET_31_0 : 32; // 31:0 - } ESR0_bit; - }; // 0x300 - - - /* PRU_INTC_ESR1 register bit field */ - union { - volatile uint32_t ESR1; - - volatile struct { - unsigned EN_SET_63_32 : 32; // 31:0 - } ESR1_bit; - }; // 0x304 - - - uint32_t rsvd308[30]; // 0x308 - 0x37C - - - /* PRU_INTC_ECR0 register bit field */ - union { - volatile uint32_t ECR0; - - volatile struct { - unsigned EN_CLR_31_0 : 32; // 31:0 - } ECR0_bit; - }; // 0x380 - - - /* PRU_INTC_ECR1 register bit field */ - union { - volatile uint32_t ECR1; - - volatile struct { - unsigned EN_CLR_63_32 : 32; // 31:0 - } ECR1_bit; - }; // 0x384 - - - uint32_t rsvd388[30]; // 0x388 - 0x3FC - - - /* PRU_INTC_CMR0 register bit field */ - union { - volatile uint32_t CMR0; - - volatile struct { - unsigned CH_MAP_0 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_1 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_2 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_3 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR0_bit; - }; // 0x400 - - - /* PRU_INTC_CMR1 register bit field */ - union { - volatile uint32_t CMR1; - - volatile struct { - unsigned CH_MAP_4 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_5 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_6 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_7 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR1_bit; - }; // 0x404 - - - /* PRU_INTC_CMR2 register bit field */ - union { - volatile uint32_t CMR2; - - volatile struct { - unsigned CH_MAP_8 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_9 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_10 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_11 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR2_bit; - }; // 0x408 - - - /* PRU_INTC_CMR3 register bit field */ - union { - volatile uint32_t CMR3; - - volatile struct { - unsigned CH_MAP_12 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_13 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_14 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_15 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR3_bit; - }; // 0x40C - - - /* PRU_INTC_CMR4 register bit field */ - union { - volatile uint32_t CMR4; - - volatile struct { - unsigned CH_MAP_16 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_17 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_18 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_19 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR4_bit; - }; // 0x410 - - - /* PRU_INTC_CMR5 register bit field */ - union { - volatile uint32_t CMR5; - - volatile struct { - unsigned CH_MAP_20 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_21 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_22 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_23 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR5_bit; - }; // 0x414 - - - /* PRU_INTC_CMR6 register bit field */ - union { - volatile uint32_t CMR6; - - volatile struct { - unsigned CH_MAP_24 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_25 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_26 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_27 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR6_bit; - }; // 0x418 - - - /* PRU_INTC_CMR7 register bit field */ - union { - volatile uint32_t CMR7; - - volatile struct { - unsigned CH_MAP_28 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_29 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_30 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_31 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR7_bit; - }; // 0x41C - - - /* PRU_INTC_CMR8 register bit field */ - union { - volatile uint32_t CMR8; - - volatile struct { - unsigned CH_MAP_32 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_33 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_34 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_35 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR8_bit; - }; // 0x420 - - - /* PRU_INTC_CMR9 register bit field */ - union { - volatile uint32_t CMR9; - - volatile struct { - unsigned CH_MAP_36 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_37 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_38 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_39 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR9_bit; - }; // 0x424 - - - /* PRU_INTC_CMR10 register bit field */ - union { - volatile uint32_t CMR10; - - volatile struct { - unsigned CH_MAP_40 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_41 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_42 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_43 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR10_bit; - }; // 0x428 - - - /* PRU_INTC_CMR11 register bit field */ - union { - volatile uint32_t CMR11; - - volatile struct { - unsigned CH_MAP_44 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_45 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_46 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_47 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR11_bit; - }; // 0x42C - - - /* PRU_INTC_CMR12 register bit field */ - union { - volatile uint32_t CMR12; - - volatile struct { - unsigned CH_MAP_48 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_49 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_50 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_51 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR12_bit; - }; // 0x430 - - - /* PRU_INTC_CMR13 register bit field */ - union { - volatile uint32_t CMR13; - - volatile struct { - unsigned CH_MAP_52 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_53 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_54 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_55 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR13_bit; - }; // 0x434 - - - /* PRU_INTC_CMR14 register bit field */ - union { - volatile uint32_t CMR14; - - volatile struct { - unsigned CH_MAP_56 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_57 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_58 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_59 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR14_bit; - }; // 0x438 - - - /* PRU_INTC_CMR15 register bit field */ - union { - volatile uint32_t CMR15; - - volatile struct { - unsigned CH_MAP_60 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_61 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_62 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_63 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR15_bit; - }; // 0x43C - - - uint32_t rsvd440[240]; // 0x440 - 0x7FC - - - /* PRU_INTC_HMR0 register bit field */ - union { - volatile uint32_t HMR0; - - volatile struct { - unsigned HINT_MAP_0 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned HINT_MAP_1 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned HINT_MAP_2 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned HINT_MAP_3 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } HMR0_bit; - }; // 0x800 - - - /* PRU_INTC_HMR1 register bit field */ - union { - volatile uint32_t HMR1; - - volatile struct { - unsigned HINT_MAP_4 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned HINT_MAP_5 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned HINT_MAP_6 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned HINT_MAP_7 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } HMR1_bit; - }; // 0x804 - - - /* PRU_INTC_HMR2 register bit field */ - union { - volatile uint32_t HMR2; - - volatile struct { - unsigned HINT_MAP_8 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned HINT_MAP_9 : 4; // 11:8 - unsigned rsvd12 : 20; // 31:12 - } HMR2_bit; - }; // 0x808 - - - uint32_t rsvd80C[61]; // 0x80C - 0x8FC - - - /* PRU_INTC_HIPIR0 register bit field */ - union { - volatile uint32_t HIPIR0; - - volatile struct { - unsigned PRI_HINT_0 : 10; // 9:0 - unsigned rsvd10 : 21; // 30:10 - unsigned NONE_HINT_0 : 1; // 31 - } HIPIR0_bit; - }; // 0x900 - - - /* PRU_INTC_HIPIR1 register bit field */ - union { - volatile uint32_t HIPIR1; - - volatile struct { - unsigned PRI_HINT_1 : 10; // 9:0 - unsigned rsvd10 : 21; // 30:10 - unsigned NONE_HINT_1 : 1; // 31 - } HIPIR1_bit; - }; // 0x904 - - - /* PRU_INTC_HIPIR2 register bit field */ - union { - volatile uint32_t HIPIR2; - - volatile struct { - unsigned PRI_HINT_2 : 10; // 9:0 - unsigned rsvd10 : 21; // 30:10 - unsigned NONE_HINT_2 : 1; // 31 - } HIPIR2_bit; - }; // 0x908 - - - /* PRU_INTC_HIPIR3 register bit field */ - union { - volatile uint32_t HIPIR3; - - volatile struct { - unsigned PRI_HINT_3 : 10; // 9:0 - unsigned rsvd10 : 21; // 30:10 - unsigned NONE_HINT_3 : 1; // 31 - } HIPIR3_bit; - }; // 0x90C - - - /* PRU_INTC_HIPIR4 register bit field */ - union { - volatile uint32_t HIPIR4; - - volatile struct { - unsigned PRI_HINT_4 : 10; // 9:0 - unsigned rsvd10 : 21; // 30:10 - unsigned NONE_HINT_4 : 1; // 31 - } HIPIR4_bit; - }; // 0x910 - - - /* PRU_INTC_HIPIR5 register bit field */ - union { - volatile uint32_t HIPIR5; - - volatile struct { - unsigned PRI_HINT_5 : 10; // 9:0 - unsigned rsvd10 : 21; // 30:10 - unsigned NONE_HINT_5 : 1; // 31 - } HIPIR5_bit; - }; // 0x914 - - - /* PRU_INTC_HIPIR6 register bit field */ - union { - volatile uint32_t HIPIR6; - - volatile struct { - unsigned PRI_HINT_6 : 10; // 9:0 - unsigned rsvd10 : 21; // 30:10 - unsigned NONE_HINT_6 : 1; // 31 - } HIPIR6_bit; - }; // 0x918 - - - /* PRU_INTC_HIPIR7 register bit field */ - union { - volatile uint32_t HIPIR7; - - volatile struct { - unsigned PRI_HINT_7 : 10; // 9:0 - unsigned rsvd10 : 21; // 30:10 - unsigned NONE_HINT_7 : 1; // 31 - } HIPIR7_bit; - }; // 0x91C - - - /* PRU_INTC_HIPIR8 register bit field */ - union { - volatile uint32_t HIPIR8; - - volatile struct { - unsigned PRI_HINT_8 : 10; // 9:0 - unsigned rsvd10 : 21; // 30:10 - unsigned NONE_HINT_8 : 1; // 31 - } HIPIR8_bit; - }; // 0x920 - - - /* PRU_INTC_HIPIR9 register bit field */ - union { - volatile uint32_t HIPIR9; - - volatile struct { - unsigned PRI_HINT_9 : 10; // 9:0 - unsigned rsvd10 : 21; // 30:10 - unsigned NONE_HINT_9 : 1; // 31 - } HIPIR9_bit; - }; // 0x924 - - - uint32_t rsvd928[246]; // 0x928 - 0xCFC - - - /* PRU_INTC_SIPR0 register bit field */ - union { - volatile uint32_t SIPR0; - - volatile struct { - unsigned POLARITY_31_0 : 32; // 31:0 - } SIPR0_bit; - }; // 0xD00 - - - /* PRU_INTC_SIPR1 register bit field */ - union { - volatile uint32_t SIPR1; - - volatile struct { - unsigned POLARITY_63_32 : 32; // 31:0 - } SIPR1_bit; - }; // 0xD04 - - - uint32_t rsvdD08[30]; // 0xD08 - 0xD7C - - - /* PRU_INTC_SITR0 register bit field */ - union { - volatile uint32_t SITR0; - - volatile struct { - unsigned TYPE_31_0 : 32; // 31:0 - } SITR0_bit; - }; // 0xD80 - - - /* PRU_INTC_SITR1 register bit field */ - union { - volatile uint32_t SITR1; - - volatile struct { - unsigned TYPE_63_32 : 32; // 31:0 - } SITR1_bit; - }; // 0xD84 - - - uint32_t rsvdD84[222]; // 0xD88 - 0x10FC - - - /* PRU_INTC_HINLR0 register bit field */ - union { - volatile uint32_t HINLR0; - - volatile struct { - unsigned NEST_HINT_0 : 9; // 8:0 - unsigned rsvd9 : 22; // 30:9 - unsigned AUTO_OVERRIDE : 1; // 31 - } HINLR0_bit; - }; // 0x1100 - - - /* PRU_INTC_HINLR1 register bit field */ - union { - volatile uint32_t HINLR1; - - volatile struct { - unsigned NEST_HINT_1 : 9; // 8:0 - unsigned rsvd9 : 22; // 30:9 - unsigned AUTO_OVERRIDE : 1; // 31 - } HINLR1_bit; - }; // 0x1104 - - - /* PRU_INTC_HINLR2 register bit field */ - union { - volatile uint32_t HINLR2; - - volatile struct { - unsigned NEST_HINT_2 : 9; // 8:0 - unsigned rsvd9 : 22; // 30:9 - unsigned AUTO_OVERRIDE : 1; // 31 - } HINLR2_bit; - }; // 0x1108 - - - /* PRU_INTC_HINLR3 register bit field */ - union { - volatile uint32_t HINLR3; - - volatile struct { - unsigned NEST_HINT_3 : 9; // 8:0 - unsigned rsvd9 : 22; // 30:9 - unsigned AUTO_OVERRIDE : 1; // 31 - } HINLR3_bit; - }; // 0x110C - - - /* PRU_INTC_HINLR4 register bit field */ - union { - volatile uint32_t HINLR4; - - volatile struct { - unsigned NEST_HINT_4 : 9; // 8:0 - unsigned rsvd9 : 22; // 30:9 - unsigned AUTO_OVERRIDE : 1; // 31 - } HINLR4_bit; - }; // 0x1110 - - - /* PRU_INTC_HINLR5 register bit field */ - union { - volatile uint32_t HINLR5; - - volatile struct { - unsigned NEST_HINT_5 : 9; // 8:0 - unsigned rsvd9 : 22; // 30:9 - unsigned AUTO_OVERRIDE : 1; // 31 - } HINLR5_bit; - }; // 0x1114 - - - /* PRU_INTC_HINLR6 register bit field */ - union { - volatile uint32_t HINLR6; - - volatile struct { - unsigned NEST_HINT_6 : 9; // 8:0 - unsigned rsvd9 : 22; // 30:9 - unsigned AUTO_OVERRIDE : 1; // 31 - } HINLR6_bit; - }; // 0x1118 - - - /* PRU_INTC_HINLR7 register bit field */ - union { - volatile uint32_t HINLR7; - - volatile struct { - unsigned NEST_HINT_7 : 9; // 8:0 - unsigned rsvd9 : 22; // 30:9 - unsigned AUTO_OVERRIDE : 1; // 31 - } HINLR7_bit; - }; // 0x111C - - - /* PRU_INTC_HINLR8 register bit field */ - union { - volatile uint32_t HINLR8; - - volatile struct { - unsigned NEST_HINT_8 : 9; // 8:0 - unsigned rsvd9 : 22; // 30:9 - unsigned AUTO_OVERRIDE : 1; // 31 - } HINLR8_bit; - }; // 0x1120 - - - /* PRU_INTC_HINLR9 register bit field */ - union { - volatile uint32_t HINLR9; - - volatile struct { - unsigned NEST_HINT_9 : 9; // 8:0 - unsigned rsvd9 : 22; // 30:9 - unsigned AUTO_OVERRIDE : 1; // 31 - } HINLR9_bit; - }; // 0x1124 - - - uint32_t rsvd1128[246]; // 0x1128 - 0x14FC - - - /* PRU_INTC_HIER register bit field */ - union { - volatile uint32_t HIER; - - volatile struct { - unsigned EN_HINT : 10; // 9:0 - unsigned rsvd9 : 22; // 31:10 - } HIER_bit; - }; // 0x1500 - -} pruIntc; - -#ifdef __GNUC__ -static volatile pruIntc *__CT_INTC = (void *)0x00020000; -#define CT_INTC (*__CT_INTC) -#else -volatile __far pruIntc CT_INTC __attribute__((cregister("PRU_INTC", far), peripheral)); -#endif - -#endif /* _PRU_INTC_H_ */ diff --git a/lib/pru_rpmsg/include/am335x/pru_uart.h b/lib/pru_rpmsg/include/am335x/pru_uart.h deleted file mode 100644 index 999f81ab7d86..000000000000 --- a/lib/pru_rpmsg/include/am335x/pru_uart.h +++ /dev/null @@ -1,285 +0,0 @@ -/* - * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ - * - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the - * distribution. - * - * * Neither the name of Texas Instruments Incorporated nor the names of - * its contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef _PRU_UART_H_ -#define _PRU_UART_H_ - -/* UART Register set */ -typedef struct { - - /* - * RBR and THR register pair - * This is a unique register pair in that RBR and THR - * share the same address. RBR is read-only while THR is - * write-only. - * - * Additionally, RBR and THR share an address with DLL. To - * read/write RBR/THR write 0 to the DLAB bit in the LCR - * register. To modify DLL write a 1. - * - * DLL also has a dedicated - * address which does not require toggling the DLAB bit. - */ - union { - /* PRU_UART_RBR register bit field */ - union { - volatile uint32_t RBR; - - volatile struct { - unsigned DATA : 8; // 7:0 - unsigned rsvd8 : 24; // 31:8 - } RBR_bit; - }; - - /* PRU_UART_THR register bit field */ - union { - volatile uint32_t THR; - - volatile struct { - unsigned DATA : 8; // 7:0 - unsigned rsvd8 : 24; // 31:8 - } THR_bit; - }; - }; // 0x0 - - - /* PRU_UART_IER register bit field */ - /* - * IER shares an address with DLH. To modify IER write 0 - * to the DLAB bit in the LCR register. To modify DLH write a 1. - * - * DLH also has a dedicated address which does not require - * toggling the DLAB bit. - */ - union { - volatile uint32_t IER; - - volatile struct { - unsigned ERBI : 1; // 0 - unsigned ETBEI : 1; // 1 - unsigned ELSI : 1; // 2 - unsigned EDSSI : 1; // 3 - unsigned rsvd4 : 28; // 31:4 - } IER_bit; - }; // 0x4 - - - /* - * IIR and FCR register pair - * This is a unique register pair in that IIR and FCR - * share the same address. IIR is read-only while FCR is - * write-only. - */ - union { - /* PRU_UART_IIR register bit field */ - union { - volatile uint32_t IIR; - - volatile struct { - unsigned IPEND : 1; // 0 - unsigned INTID : 3; // 3:1 - unsigned rsvd4 : 2; // 5:4 - unsigned FIFOEN : 2; // 7:6 - unsigned rsvd8 : 24; // 31:8 - } IIR_bit; - }; - - /* PRU_UART_FCR register bit field */ - union { - volatile uint32_t FCR; - - volatile struct { - unsigned FIFOEN : 1; // 0 - unsigned RXCLR : 1; // 1 - unsigned TXCLR : 1; // 2 - unsigned DMAMODE1 : 1; // 3 - unsigned rsvd4 : 2; // 5:4 - unsigned RXFIFTL : 2; // 7:6 - unsigned rsvd8 : 24; // 31:8 - } FCR_bit; - }; - }; // 0x8 - - - /* PRU_UART_LCR register bit field */ - union { - volatile uint32_t LCR; - - volatile struct { - unsigned WLS : 2; // 1:0 - unsigned STB : 1; // 2 - unsigned PEN : 1; // 3 - unsigned EPS : 1; // 4 - unsigned SP : 1; // 5 - unsigned BC : 1; // 6 - unsigned DLAB : 1; // 7 - unsigned rsvd8 : 24; // 31:8 - } LCR_bit; - }; // 0xC - - - /* PRU_UART_MCR register bit field */ - union { - volatile uint32_t MCR; - - volatile struct { - unsigned rsvd0 : 1; // 0 - unsigned RTS : 1; // 1 - unsigned OUT1 : 1; // 2 - unsigned OUT2 : 1; // 3 - unsigned LOOP : 1; // 4 - unsigned AFE : 1; // 5 - unsigned rsvd8 : 26; // 31:6 - } MCR_bit; - }; // 0x10 - - - /* PRU_UART_LSR register bit field */ - union { - volatile uint32_t LSR; - - volatile struct { - unsigned DR : 1; // 0 - unsigned OE : 1; // 1 - unsigned PE : 1; // 2 - unsigned FE : 1; // 3 - unsigned BI : 1; // 4 - unsigned THRE : 1; // 5 - unsigned TEMT : 1; // 6 - unsigned RXFIFOE : 1; // 7 - unsigned rsvd8 : 24; // 31:8 - } LSR_bit; - }; // 0x14 - - - /* PRU_UART_MSR register bit field */ - union { - volatile uint32_t MSR; - - volatile struct { - unsigned DCTS : 1; // 0 - unsigned DDSR : 1; // 1 - unsigned TERI : 1; // 2 - unsigned DCD : 1; // 3 - unsigned CTS : 1; // 4 - unsigned DSR : 1; // 5 - unsigned RI : 1; // 6 - unsigned CD : 1; // 7 - unsigned rsvd8 : 24; // 31:8 - } MSR_bit; - }; // 0x18 - - - /* PRU_UART_SCR register bit field */ - union { - volatile uint32_t SCR; - - volatile struct { - unsigned SCR : 8; // 7:0 - unsigned rsvd8 : 24; // 31:8 - } SCR_bit; - }; // 0x1C - - - /* PRU_UART_DLL register bit field */ - union { - volatile uint32_t DLL; - - volatile struct { - unsigned DLL : 8; // 7:0 - unsigned rsvd8 : 24; // 31:8 - } DLL_bit; - }; // 0x20 - - - /* PRU_UART_DLH register bit field */ - union { - volatile uint32_t DLH; - - volatile struct { - unsigned DLH : 8; // 7:0 - unsigned rsvd8 : 24; // 31:8 - } DLH_bit; - }; // 0x24 - - - /* PRU_UART_REVID1 register bit field */ - union { - volatile uint32_t REVID1; - - volatile struct { - unsigned REVID1 : 32; // 31:0 - } REVID1_bit; - }; // 0x28 - - - /* PRU_UART_REVID2 register bit field */ - union { - volatile uint32_t REVID2; - - volatile struct { - unsigned REVID2 : 8; // 7:0 - unsigned rsvd8 : 24; // 31:8 - } REVID2_bit; - }; // 0x2C - - - /* PRU_UART_PWREMU_MGMT register bit field */ - union { - volatile uint32_t PWREMU_MGMT; - - volatile struct { - unsigned FREE : 1; // 0 - unsigned rsvd1 : 12; // 12:1 - unsigned URRST : 1; // 13 - unsigned UTRST : 1; // 14 - unsigned rsvd15 : 17; // 31:15 - } PWREMU_MGMT_bit; - }; // 0x30 - - - /* PRU_UART_MDR register bit field */ - union { - volatile uint32_t MDR; - - volatile struct { - unsigned OSM_SEL : 1; // 0 - unsigned rsvd1 : 31; // 31:1 - } MDR_bit; - }; // 0x34 - -} pruUart; - -volatile __far pruUart CT_UART __attribute__((cregister("PRU_UART", near), peripheral)); - -#endif /* _PRU_UART_H_ */ diff --git a/lib/pru_rpmsg/include/am335x/sys_mailbox.h b/lib/pru_rpmsg/include/am335x/sys_mailbox.h deleted file mode 100644 index f2750f75e11b..000000000000 --- a/lib/pru_rpmsg/include/am335x/sys_mailbox.h +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ - * - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the - * distribution. - * - * * Neither the name of Texas Instruments Incorporated nor the names of - * its contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef _SYS_MAILBOX_H_ -#define _SYS_MAILBOX_H_ - -/* SYS_MAILBOX register set */ -typedef struct { - - /* SYS_MAILBOX_REVISION register bit field */ - union { - volatile uint32_t REVISION; - - volatile struct { - unsigned MINOR : 6; //5:0 - unsigned CUSTOM : 2; //7:6 - unsigned MAJOR : 3; //10:8 - unsigned RTL : 5; //15:11 - unsigned FUNC : 12; //27:16 - unsigned rsvd28 : 2; //29:28 - unsigned SCHEME : 2; //31:30 - } REVISION_bit; - }; // 0x0 - - uint32_t rsvd4[3]; // 0x4 - 0xC - - /* SYS_MAILBOX_SYSCONFIG register bit field */ - union { - volatile uint32_t SYSCONFIG; - - volatile struct { - unsigned SOFTRESET : 1; //0 - unsigned rsvd : 1; //1 - unsigned SLIDLEMODE : 2; //3:2 - unsigned rsvd1 : 28; //31:4 - } SYSCONFIG_bit; - }; // 0x10 - - uint32_t rsvd14[11]; // 0x14 - 0x3C - - /* SYS_MAILBOX_MESSAGE register bit fields */ - union { - volatile uint32_t MESSAGE[8]; - - volatile struct { - unsigned MESSAGE : 32; //31:0 - } MESSAGE_bit[8]; - }; // 0x40-0x5C - - uint32_t rsvd60[8]; // 0x60 - 0x7C - - /* SYS_MAILBOX_FIFOSTATUS register bit fields */ - union { - volatile uint32_t FIFOSTATUS[8]; - - volatile struct { - unsigned FIFOFULL : 1; //0 - unsigned rsvd : 31; //31:1 - } FIFOSTATUS_bit[8]; - }; // 0x80-0x9C - - uint32_t rsvdA0[8]; // 0xA0 - 0xBC - - /* SYS_MAILBOX_MSGSTATUS register bit fields */ - union { - volatile uint32_t MSGSTATUS[8]; - - volatile struct { - unsigned NBOFMSG : 3; //2:0 - unsigned rsvd : 29; //31:3 - } MSGSTATUS_bit[8]; - }; // 0xC0-DC - - uint32_t rsvdE0[8]; // 0xE0 - 0xFC - - volatile struct { - union { - volatile uint32_t STATUS_RAW; - - volatile struct { - unsigned NEWMSGSTATUSMB0 : 1; //0 - unsigned NOTFULLSTATUSMB0 : 1; //1 - unsigned NEWMSGSTATUSMB1 : 1; //2 - unsigned NOTFULLSTATUSMB1 : 1; //3 - unsigned NEWMSGSTATUSMB2 : 1; //4 - unsigned NOTFULLSTATUSMB2 : 1; //5 - unsigned NEWMSGSTATUSMB3 : 1; //6 - unsigned NOTFULLSTATUSMB3 : 1; //7 - unsigned NEWMSGSTATUSMB4 : 1; //8 - unsigned NOTFULLSTATUSMB4 : 1; //9 - unsigned NEWMSGSTATUSMB5 : 1; //10 - unsigned NOTFULLSTATUSMB5 : 1; //11 - unsigned NEWMSGSTATUSMB6 : 1; //12 - unsigned NOTFULLSTATUSMB6 : 1; //13 - unsigned NEWMSGSTATUSMB7 : 1; //14 - unsigned NOTFULLSTATUSMB7 : 1; //15 - unsigned rsvd : 16; //31:16 - } STATUS_RAW_bit; - }; - union { - volatile uint32_t STATUS_CLR; - - volatile struct { - unsigned NEWMSGSTATUSMB0 : 1; //0 - unsigned NOTFULLSTATUSMB0 : 1; //1 - unsigned NEWMSGSTATUSMB1 : 1; //2 - unsigned NOTFULLSTATUSMB1 : 1; //3 - unsigned NEWMSGSTATUSMB2 : 1; //4 - unsigned NOTFULLSTATUSMB2 : 1; //5 - unsigned NEWMSGSTATUSMB3 : 1; //6 - unsigned NOTFULLSTATUSMB3 : 1; //7 - unsigned NEWMSGSTATUSMB4 : 1; //8 - unsigned NOTFULLSTATUSMB4 : 1; //9 - unsigned NEWMSGSTATUSMB5 : 1; //10 - unsigned NOTFULLSTATUSMB5 : 1; //11 - unsigned NEWMSGSTATUSMB6 : 1; //12 - unsigned NOTFULLSTATUSMB6 : 1; //13 - unsigned NEWMSGSTATUSMB7 : 1; //14 - unsigned NOTFULLSTATUSMB7 : 1; //15 - unsigned rsvd : 16; //31:16 - } STATUS_CLR_bit; - }; - union { - volatile uint32_t ENABLE_SET; - - volatile struct { - unsigned NEWMSGSTATUSMB0 : 1; //0 - unsigned NOTFULLSTATUSMB0 : 1; //1 - unsigned NEWMSGSTATUSMB1 : 1; //2 - unsigned NOTFULLSTATUSMB1 : 1; //3 - unsigned NEWMSGSTATUSMB2 : 1; //4 - unsigned NOTFULLSTATUSMB2 : 1; //5 - unsigned NEWMSGSTATUSMB3 : 1; //6 - unsigned NOTFULLSTATUSMB3 : 1; //7 - unsigned NEWMSGSTATUSMB4 : 1; //8 - unsigned NOTFULLSTATUSMB4 : 1; //9 - unsigned NEWMSGSTATUSMB5 : 1; //10 - unsigned NOTFULLSTATUSMB5 : 1; //11 - unsigned NEWMSGSTATUSMB6 : 1; //12 - unsigned NOTFULLSTATUSMB6 : 1; //13 - unsigned NEWMSGSTATUSMB7 : 1; //14 - unsigned NOTFULLSTATUSMB7 : 1; //15 - unsigned rsvd : 16; //31:16 - } ENABLE_SET_bit; - }; - union { - volatile uint32_t ENABLE_CLR; - - volatile struct { - unsigned NEWMSGSTATUSMB0 : 1; //0 - unsigned NOTFULLSTATUSMB0 : 1; //1 - unsigned NEWMSGSTATUSMB1 : 1; //2 - unsigned NOTFULLSTATUSMB1 : 1; //3 - unsigned NEWMSGSTATUSMB2 : 1; //4 - unsigned NOTFULLSTATUSMB2 : 1; //5 - unsigned NEWMSGSTATUSMB3 : 1; //6 - unsigned NOTFULLSTATUSMB3 : 1; //7 - unsigned NEWMSGSTATUSMB4 : 1; //8 - unsigned NOTFULLSTATUSMB4 : 1; //9 - unsigned NEWMSGSTATUSMB5 : 1; //10 - unsigned NOTFULLSTATUSMB5 : 1; //11 - unsigned NEWMSGSTATUSMB6 : 1; //12 - unsigned NOTFULLSTATUSMB6 : 1; //13 - unsigned NEWMSGSTATUSMB7 : 1; //14 - unsigned NOTFULLSTATUSMB7 : 1; //15 - unsigned rsvd : 16; //31:16 - } ENABLE_CLR_bit; - }; - } IRQ[4]; - -} sysMailbox; - -#ifdef __GNUC__ -static volatile sysMailbox *__CT_MBX = (void *)0x480C8000; -#define CT_MBX (*__CT_MBX) -#else -volatile __far sysMailbox CT_MBX __attribute__((cregister("MBX0", far), peripheral)); -#endif - -#endif /* _SYS_MAILBOX_H_ */ diff --git a/lib/pru_rpmsg/include/am335x/sys_pwmss.h b/lib/pru_rpmsg/include/am335x/sys_pwmss.h deleted file mode 100644 index ee9036c0a5a0..000000000000 --- a/lib/pru_rpmsg/include/am335x/sys_pwmss.h +++ /dev/null @@ -1,446 +0,0 @@ -/* - * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ - * - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the - * distribution. - * - * * Neither the name of Texas Instruments Incorporated nor the names of - * its contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef _SYS_PWMSS_H_ -#define _SYS_PWMSS_H_ - -/* SYS_PWMSS register set */ -typedef struct { - - /***************************/ - /* PWM Subsystem Registers */ - /***************************/ - /* SYS_PWMSS_IDVER register bit field */ - union { - volatile uint32_t IDVER; - - volatile struct { - unsigned Y_MINOR : 6; //5:0 - unsigned CUSTOM : 2; //7:6 - unsigned X_MAJOR : 3; //10:8 - unsigned R_RTL : 5; //15:11 - unsigned FUNC : 12; //27:16 - unsigned rsvd28 : 2; //29:28 - unsigned SCHEME : 2; //31:30 - } IDVER_bit; - }; // 0x0 - - /* SYS_PWMSS_SYSCONFIG register bit field */ - union { - volatile uint32_t SYSCONFIG; - - volatile struct { - unsigned SOFTRESET : 1; //0 - unsigned FREEEMU : 1; //1 - unsigned IDLEMODE : 2; //3:2 - unsigned STANDBYMODE : 2; //5:4 - unsigned rsvd6 : 26; //31:6 - } SYSCONFIG_bit; - }; // 0x4 - - /* SYS_PWMSS_CLKCONFIG register bit field */ - union { - volatile uint32_t CLKCONFIG; - - volatile struct { - unsigned ECAPCLK_EN : 1; //0 - unsigned ECAPCLKSTOP_REQ : 1; //1 - unsigned rsvd2 : 2; //3:2 - unsigned EQEPCLK_EN : 1; //4 - unsigned EQEPCLKSTOP_REQ : 1; //5 - unsigned rsvd6 : 2; //7:6 - unsigned EPWMCLK_EN : 1; //8 - unsigned EPWMCLKSTOP_REQ : 1; //9 - unsigned rsvd10 : 22; //31:10 - } CLKCONFIG_bit; - }; // 0x8 - - /* SYS_PWMSS_CLKSTATUS register bit field */ - union { - volatile uint32_t CLKSTATUS; - - volatile struct { - unsigned ECAPCLK_EN_ACK : 1; //0 - unsigned ECAPCLKSTOP_ACK : 1; //1 - unsigned rsvd2 : 2; //3:2 - unsigned EQEPCLK_EN_ACK : 1; //4 - unsigned EQEPCLKSTOP_ACK : 1; //5 - unsigned rsvd6 : 2; //7:6 - unsigned EPWMCLK_EN_ACK : 1; //8 - unsigned EPWMCLKSTOP_ACK : 1; //9 - unsigned rsvd10 : 22; //31:10 - } CLKSTATUS_bit; - }; // 0xC - - uint32_t rsvd10[60]; // 0x10 - 0xFC - - /*************************/ - /* eCAP Module Registers */ - /*************************/ - /* SYS_PWMSS_ECAP_TSCTR register bit field */ - union { - volatile uint32_t ECAP_TSCTR; - - volatile struct { - unsigned TSCTR : 32; //31:0 - } ECAP_TSCTR_bit; - }; // 0x100 - - /* SYS_PWMSS_ECAP_CTRPHS register bit field */ - union { - volatile uint32_t ECAP_CTRPHS; - - volatile struct { - unsigned CTRPHS : 32; //31:0 - } ECAP_CTRPHS_bit; - }; // 0x104 - - /* SYS_PWMSS_ECAP_CAP1 register bit field */ - union { - volatile uint32_t ECAP_CAP1; - - volatile struct { - unsigned CAP1 : 32; //31:0 - } ECAP_CAP1_bit; - }; // 0x108 - - /* SYS_PWMSS_ECAP_CAP2 register bit field */ - union { - volatile uint32_t ECAP_CAP2; - - volatile struct { - unsigned CAP2 : 32; //31:0 - } ECAP_CAP2_bit; - }; // 0x10C - - /* SYS_PWMSS_ECAP_CAP3 register bit field */ - union { - volatile uint32_t ECAP_CAP3; - - volatile struct { - unsigned CAP3 : 32; //31:0 - } ECAP_CAP3_bit; - }; // 0x110 - - /* SYS_PWMSS_ECAP_CAP4 register bit field */ - union { - volatile uint32_t ECAP_CAP4; - - volatile struct { - unsigned CAP4 : 32; //31:0 - } ECAP_CAP4_bit; - }; // 0x114 - - uint32_t rsvd118[4]; // 0x118 - 0x124 - - /* SYS_PWMSS_ECAP_ECCTL1 register bit field */ - volatile uint16_t ECAP_ECCTL1; // 0x128 - - /* SYS_PWMSS_ECAP_ECCTL2 register bit field */ - volatile uint16_t ECAP_ECCTL2; // 0x12A - - /* SYS_PWMSS_ECAP_ECEINT register bit field */ - volatile uint16_t ECAP_ECEINT; // 0x12C - - /* SYS_PWMSS_ECAP_ECFLG register bit field */ - volatile uint16_t ECAP_ECFLG; // 0x12E - - /* SYS_PWMSS_ECAP_ECCLR register bit field */ - volatile uint16_t ECAP_ECCLR; // 0x130 - - /* SYS_PWMSS_ECAP_ECFRC register bit field */ - volatile uint16_t ECAP_ECFRC; // 0x132 - - uint32_t rsvd134[10]; // 0x134 - 0x158 - - /* SYS_PWMSS_ECAP_REVID register bit field */ - union { - volatile uint32_t ECAP_REVID; - - volatile struct { - unsigned REV : 32; //31:0 - } ECAP_REVID_bit; - }; // 0x15C - - uint32_t rsvd160[8]; // 0x160 - 0x17C - - /*************************/ - /* eQEP Module Registers */ - /*************************/ - /* SYS_PWMSS_EQEP_QPOSCNT register bit field */ - union { - volatile uint32_t EQEP_QPOSCNT; - - volatile struct { - unsigned QPOSCNT : 32; //31:0 - } EQEP_QPOSCNT_bit; - }; // 0x180 - - /* SYS_PWMSS_EQEP_QPOSINIT register bit field */ - union { - volatile uint32_t EQEP_QPOSINIT; - - volatile struct { - unsigned QPOSINIT : 32; //31:0 - } EQEP_QPOSINIT_bit; - }; // 0x184 - - /* SYS_PWMSS_EQEP_QPOSMAX register bit field */ - union { - volatile uint32_t EQEP_QPOSMAX; - - volatile struct { - unsigned QPOSMAX : 32; //31:0 - } EQEP_QPOSMAX_bit; - }; // 0x188 - - /* SYS_PWMSS_EQEP_QPOSCMP register bit field */ - union { - volatile uint32_t EQEP_QPOSCMP; - - volatile struct { - unsigned QPOSCMP : 32; //31:0 - } EQEP_QPOSCMP_bit; - }; // 0x18C - - /* SYS_PWMSS_EQEP_QPOSILAT register bit field */ - union { - volatile uint32_t EQEP_QPOSILAT; - - volatile struct { - unsigned QPOSILAT : 32; //31:0 - } EQEP_QPOSILAT_bit; - }; // 0x190 - - /* SYS_PWMSS_EQEP_QPOSSLAT register bit field */ - union { - volatile uint32_t EQEP_QPOSSLAT; - - volatile struct { - unsigned QPOSSLAT : 32; //31:0 - } EQEP_QPOSSLAT_bit; - }; // 0x194 - - /* SYS_PWMSS_EQEP_QPOSLAT register bit field */ - union { - volatile uint32_t EQEP_QPOSLAT; - - volatile struct { - unsigned QPOSLAT : 32; //31:0 - } EQEP_QPOSLAT_bit; - }; // 0x198 - - /* SYS_PWMSS_EQEP_QUTMR register bit field */ - union { - volatile uint32_t EQEP_QUTMR; - - volatile struct { - unsigned QUTMR : 32; //31:0 - } EQEP_QUTMR_bit; - }; // 0x19C - - /* SYS_PWMSS_EQEP_QUPRD register bit field */ - union { - volatile uint32_t EQEP_QUPRD; - - volatile struct { - unsigned QUPRD : 32; //31:0 - } EQEP_QUPRD_bit; - }; // 0x1A0 - - /* SYS_PWMSS_EQEP_QWDTMR register bit field */ - volatile uint16_t EQEP_QWDTMR; // 0x1A4 - - /* SYS_PWMSS_EQEP_QWDPRD register bit field */ - volatile uint16_t EQEP_QWDPRD; // 0x1A6 - - /* SYS_PWMSS_EQEP_QDECCTL register bit field */ - volatile uint16_t EQEP_QDECCTL; // 0x1A8 - - /* SYS_PWMSS_EQEP_QEPCTL register bit field */ - volatile uint16_t EQEP_QEPCTL; // 0x1AA - - /* SYS_PWMSS_EQEP_QCAPCTL register bit field */ - volatile uint16_t EQEP_QCAPCTL; // 0x1AC - - /* SYS_PWMSS_EQEP_QPOSCTL register bit field */ - volatile uint16_t EQEP_QPOSCTL; // 0x1AE - - /* SYS_PWMSS_EQEP_QEINT register bit field */ - volatile uint16_t EQEP_QEINT; // 0x1B0 - - /* SYS_PWMSS_EQEP_QFLG register bit field */ - volatile uint16_t EQEP_QFLG; // 0x1B2 - - /* SYS_PWMSS_EQEP_QCLR register bit field */ - volatile uint16_t EQEP_QCLR; // 0x1B4 - - /* SYS_PWMSS_EQEP_QFRC register bit field */ - volatile uint16_t EQEP_QFRC; // 0x1B6 - - /* SYS_PWMSS_EQEP_QEPSTS register bit field */ - volatile uint16_t EQEP_QEPSTS; // 0x1B8 - - /* SYS_PWMSS_EQEP_QCTMR register bit field */ - volatile uint16_t EQEP_QCTMR; // 0x1BA - - /* SYS_PWMSS_EQEP_QCPRD register bit field */ - volatile uint16_t EQEP_QCPRD; // 0x1BC - - /* SYS_PWMSS_EQEP_QCTMRLAT register bit field */ - volatile uint16_t EQEP_QCTMRLAT; // 0x1BE - - /* SYS_PWMSS_EQEP_QCPRDLAT register bit field */ - volatile uint16_t EQEP_QCPRDLAT; // 0x1C0 - - uint16_t rsvd1C2[1]; // 0x1C2 - 0x1C3 - uint32_t rsvd1C4[6]; // 0x1C4 - 0x1D8 - - /* SYS_PWMSS_EQEP_REVID register bit field */ - union { - volatile uint32_t EQEP_REVID; - - volatile struct { - unsigned REVID : 32; //31:0 - } EQEP_REVID_bit; - }; // 0x1DC - - uint32_t rsvd1E0[8]; // 0x1E0 - 0x1FC - - /*************************/ - /* ePWM Module Registers */ - /*************************/ - /* SYS_PWMSS_EPWM_TBCTL register bit field */ - volatile uint16_t EPWM_TBCTL; // 0x200 - - /* SYS_PWMSS_EPWM_TBSTS register bit field */ - volatile uint16_t EPWM_TBSTS; // 0x202 - - /* SYS_PWMSS_EPWM_TBPHSHR register bit field */ - volatile uint16_t EPWM_TBPHSHR; // 0x204 - - /* SYS_PWMSS_EPWM_TBPHS register bit field */ - volatile uint16_t EPWM_TBPHS; // 0x206 - - /* SYS_PWMSS_EPWM_TBCNT register bit field */ - volatile uint16_t EPWM_TBCNT; // 0x208 - - /* SYS_PWMSS_EPWM_TBPRD register bit field */ - volatile uint16_t EPWM_TBPRD; // 0x20A - - uint16_t rsvd20C[1]; // 0x20C - 0x20D - - /* SYS_PWMSS_EPWM_CMPCTL register bit field */ - volatile uint16_t EPWM_CMPCTL; // 0x20E - - /* SYS_PWMSS_EPWM_CMPAHR register bit field */ - volatile uint16_t EPWM_CMPAHR; // 0x210 - - /* SYS_PWMSS_EPWM_CMPA register bit field */ - volatile uint16_t EPWM_CMPA; // 0x212 - - /* SYS_PWMSS_EPWM_CMPB register bit field */ - volatile uint16_t EPWM_CMPB; // 0x214 - - /* SYS_PWMSS_EPWM_AQCTLA register bit field */ - volatile uint16_t EPWM_AQCTLA; // 0x216 - - /* SYS_PWMSS_EPWM_AQCTLB register bit field */ - volatile uint16_t EPWM_AQCTLB; // 0x218 - - /* SYS_PWMSS_EPWM_AQSFRC register bit field */ - volatile uint16_t EPWM_AQSFRC; // 0x21A - - /* SYS_PWMSS_EPWM_AQCSFRC register bit field */ - volatile uint16_t EPWM_AQCSFRC; // 0x21C - - /* SYS_PWMSS_EPWM_DBCTL register bit field */ - volatile uint16_t EPWM_DBCTL; // 0x21E - - /* SYS_PWMSS_EPWM_DBRED register bit field */ - volatile uint16_t EPWM_DBRED; // 0x220 - - /* SYS_PWMSS_EPWM_DBFED register bit field */ - volatile uint16_t EPWM_DBFED; // 0x222 - - /* SYS_PWMSS_EPWM_TZSEL register bit field */ - volatile uint16_t EPWM_TZSEL; // 0x224 - - uint16_t rsvd226[1]; // 0x226 - 0x227 - - /* SYS_PWMSS_EPWM_TZCTL register bit field */ - volatile uint16_t EPWM_TZCTL; // 0x228 - - /* SYS_PWMSS_EPWM_TZEINT register bit field */ - volatile uint16_t EPWM_TZEINT; // 0x22A - - /* SYS_PWMSS_EPWM_TZFLG register bit field */ - volatile uint16_t EPWM_TZFLG; // 0x22C - - /* SYS_PWMSS_EPWM_TZCLR register bit field */ - volatile uint16_t EPWM_TZCLR; // 0x22E - - /* SYS_PWMSS_EPWM_TZFRC register bit field */ - volatile uint16_t EPWM_TZFRC; // 0x230 - - /* SYS_PWMSS_EPWM_ETSEL register bit field */ - volatile uint16_t EPWM_ETSEL; // 0x232 - - /* SYS_PWMSS_EPWM_ETPS register bit field */ - volatile uint16_t EPWM_ETPS; // 0x234 - - /* SYS_PWMSS_EPWM_ETFLG register bit field */ - volatile uint16_t EPWM_ETFLG; // 0x236 - - /* SYS_PWMSS_EPWM_ETCLR register bit field */ - volatile uint16_t EPWM_ETCLR; // 0x238 - - /* SYS_PWMSS_EPWM_ETFRC register bit field */ - volatile uint16_t EPWM_ETFRC; // 0x23A - - /* SYS_PWMSS_EPWM_PCCTL register bit field */ - volatile uint16_t EPWM_PCCTL; // 0x23C - - uint16_t rsvd23E[1]; // 0x23E - 0x23F - uint32_t rsvd240[32]; // 0x240 - 0x2BC - - /* SYS_PWMSS_EPWM_HRCNGF register bit field */ - volatile uint16_t EPWM_HRCNGF; // 0x2C0 - -} sysPwmss; - -volatile __far sysPwmss PWMSS0 __attribute__((cregister("PWMSS0", far), peripheral)); -volatile __far sysPwmss PWMSS1 __attribute__((cregister("PWMSS1", far), peripheral)); -volatile __far sysPwmss PWMSS2 __attribute__((cregister("PWMSS2", far), peripheral)); - -#endif /* _SYS_PWMSS_H_ */ diff --git a/lib/pru_rpmsg/include/pru_rpmsg.h b/lib/pru_rpmsg/include/pru_rpmsg.h index 6f087207844a..5d6d65e24890 100644 --- a/lib/pru_rpmsg/include/pru_rpmsg.h +++ b/lib/pru_rpmsg/include/pru_rpmsg.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 Texas Instruments Incorporated - http://www.ti.com/ + * Copyright (C) 2016-2018 Texas Instruments Incorporated - http://www.ti.com/ * * * Redistribution and use in source and binary forms, with or without @@ -84,6 +84,10 @@ #define RPMSG_NAME_SIZE 32 /* The maximum size of the buffer (including the header) */ #define RPMSG_BUF_SIZE 512 +/* The size of the buffer header */ +#define RPMSG_HEADER_SIZE 16 +/* The maximum size of the buffer message */ +#define RPMSG_MESSAGE_SIZE 496 enum pru_rpmsg_ns_flags { RPMSG_NS_CREATE = 0, diff --git a/lib/pru_rpmsg/include/pru_types.h b/lib/pru_rpmsg/include/pru_types.h index c8ab8b1ba8eb..191ae8984af3 100644 --- a/lib/pru_rpmsg/include/pru_types.h +++ b/lib/pru_rpmsg/include/pru_types.h @@ -1,6 +1,5 @@ /* - * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ - * + * Copyright (C) 2015-2021 Texas Instruments Incorporated - http://www.ti.com/ * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -37,29 +36,53 @@ /* Custom Resource info: Must match drivers/remoteproc/pru_rproc.h */ #define TYPE_PRU_INTS 1 +#define PRU_INTS_VER0 (0 << 16) +#define PRU_INTS_VER1 (1 << 16) + /** - * struct ch_map - sysevts-to-channel mapping + * struct pruss_int_map - sysevt to channel to host interrupt mapping * - * @evt: the number of the sysevt - * @ch: channel number assigned to a given @sysevt + * @event: the number of the sysevt + * @chnl: channel number assigned to a given @event + * @host: the host interrupt assigned to a given @chnl * * PRU system events are mapped to channels, and these channels are mapped to * hosts. Events can be mapped to channels in a one-to-one or many-to-one ratio * (multiple events per channel), and channels can be mapped to hosts in a * one-to-one or many-to-one ratio (multiple events per channel). * - * @evt is the number of the sysevt, and @ch is the number of the channel to be - * mapped. + * @event is the number of the sysevt, @chnl is the number of the channel to be + * mapped, and @host is the number of the host interrupt to be mapped. + * + * pruss_int_map is defined in Linux at drivers/remoteproc/pru_rproc.h */ +struct pruss_int_map { + uint8_t event; + uint8_t chnl; + uint8_t host; +}; -struct ch_map { - uint8_t evt; - uint8_t ch; +/** + * struct pru_irq_rsc - PRU firmware section header for IRQ data + * @type: resource type + * @num_evts: number of described events + * @pru_intc_map: PRU interrupt routing description + * + * The PRU firmware blob can contain optional .pru_irq_map ELF section, which + * provides the PRUSS interrupt mapping description. The pru_irq_rsc struct + * describes resource entry format. + * + * pru_irq_rsc is defined in Linux at drivers/remoteproc/pru_rproc.h + */ +struct pru_irq_rsc { + uint8_t type; + uint8_t num_evts; + struct pruss_int_map pru_intc_map[]; }; /** * struct fw_rsc_custom_ints - custom resource to define PRU interrupts - * @version: revision number of the custom ints type + * @reserved: reserved field, value should be 0 for backward compatibility * @channel_host: assignment of PRU channels to hosts * @num_evts: device address of INTC * @event_channel: mapping of sysevts to channels @@ -74,10 +97,36 @@ struct ch_map { * specifies to which host, if any, a channel is mapped. */ struct fw_rsc_custom_ints { - uint16_t version; + uint16_t reserved; uint8_t channel_host[10]; uint32_t num_evts; struct ch_map *event_channel; }; +/** + * struct fw_rsc_custom_ints_k3 - custom resource to define PRU/RTU interrupts + * @channel_host: assignment of PRU interrupt channels to host interrupts + * @num_evts: number of mappings defined in the @event_channel map + * @event_channel: PRU device address of pointer to array of events to channel + * mappings + * + * PRU system events are mapped to channels, and these channels are mapped + * to host interrupts. Events can be mapped to channels in a one-to-one or + * many-to-one ratio (multiple events per channel), and channels can be + * mapped to host interrupts in a one-to-one or many-to-one ratio (multiple + * channels per interrupt). + * + * This structure needs to be used using custom interrupt resource version + * number 1. This structure is to be used with firmwares dealing with the + * additional host interrupts on ICSSG IP instances. The firmwares for PRU + * cores on ICSSG can get away with the standard version (if not dealing with + * Task Manager), but the firmwares for RTU cores would definitely need this + * for mapping to the corresponding higher host interrupts. + */ +struct fw_rsc_custom_ints_k3 { + uint8_t channel_host[20]; + uint32_t num_evts; + struct ch_map *event_channel; +}; + #endif /* _PRU_TYPES_H_ */ diff --git a/lib/pru_rpmsg/include/pru_virtio_ring.h b/lib/pru_rpmsg/include/pru_virtio_ring.h index 54817352b026..d17b68bf9ccb 100644 --- a/lib/pru_rpmsg/include/pru_virtio_ring.h +++ b/lib/pru_rpmsg/include/pru_virtio_ring.h @@ -109,25 +109,25 @@ struct vring { * struct vring_desc desc[num]; * * // A ring of available descriptor heads with free-running index. - * __u16 avail_flags; - * __u16 avail_idx; - * __u16 available[num]; - * __u16 used_event_idx; + * uint16_t avail_flags; + * uint16_t avail_idx; + * uint16_t available[num]; + * uint16_t used_event_idx; * * // Padding to the next align boundary. * char pad[]; * * // A ring of used descriptor heads with free-running index. - * __u16 used_flags; - * __u16 used_idx; + * uint16_t used_flags; + * uint16_t used_idx; * struct vring_used_elem used[num]; - * __u16 avail_event_idx; + * uint16_t avail_event_idx; * }; */ /* We publish the used event index at the end of the available ring, and vice * versa. They are at the end for backwards compatibility. */ #define vring_used_event(vr) ((vr)->avail->ring[(vr)->num]) -#define vring_avail_event(vr) (*(__u16 *)&(vr)->used->ring[(vr)->num]) +#define vring_avail_event(vr) (*(uint16_t *)&(vr)->used->ring[(vr)->num]) static inline void vring_init(struct vring *vr, uint32_t num, void *p, uint64_t align) diff --git a/lib/pru_rpmsg/include/rsc_types.h b/lib/pru_rpmsg/include/rsc_types.h index 755ab601bf97..a52f86c5a49e 100644 --- a/lib/pru_rpmsg/include/rsc_types.h +++ b/lib/pru_rpmsg/include/rsc_types.h @@ -1,5 +1,5 @@ /* - * Copyright(c) 2011 Texas Instruments, Inc. + * Copyright(c) 2011-2018 Texas Instruments, Inc. * Copyright(c) 2011 Google, Inc. * All rights reserved. * @@ -45,9 +45,14 @@ #define TYPE_DEVMEM 1 #define TYPE_TRACE 2 #define TYPE_VDEV 3 -#define TYPE_INTMEM 4 +#define TYPE_PRELOAD_VENDOR 4 +#define TYPE_POSTLOAD_VENDOR 5 +/* deprecated, define only for backward compatibility */ #define TYPE_CUSTOM 5 +/* Linux kernel defines this as (-1), below define avoids compile warnings */ +#define FW_RSC_ADDR_ANY (~0) + union fw_custom { /* add custom resources here */ struct fw_rsc_custom_ints pru_ints; @@ -321,25 +326,59 @@ struct fw_rsc_intmem { char name[32]; }; +/** + * struct fw_rsc_custom_hdr - header to be used with custom resource types + * @type: type of custom resource, value should be one of vendor resource types + * @u: union identifying the vendor/custom resource sub-type + * @sub_type: type to identify the custom resource + * @rsc_size: size of the specific custom resource structure (in bytes) + * + * This is a header structure to be used before any specific custom resource + * type. @type is one of the generic VENDOR types, the @u is an union of the + * overall @sub_type field which is made up of the custom resource version + * number in the upper 16-bits and the custom resource sub-type itself in the + * lower 16-bits. @rsc_size is the length of the actual custom resource sub-type + * (in bytes). These will be interpreted by the host-side device-specific + * driver. + */ +struct fw_rsc_custom_hdr { + uint32_t type; + union { + uint32_t sub_type; + struct { + uint16_t type; + uint16_t ver; + } st; + } u; + uint32_t rsc_size; +}; + /** * struct fw_rsc_custom - used for custom resource types - * @type: type of resource - * @sub_type: type of custom resource + * @type: type of resource, value should be one of vendor resource types + * @u: union identifying the type of vendor/custom resource * @rsc_size: size of @rsc (in bytes) * @rsc: the custom resource * * This resource allows for custom resources specific to an architecture or * device. * - * @type is the generic CUSTOM type, @sub_type is the specific custom resource, - * @rsc_size is the length of @rsc (in bytes), and @rsc is the actual - * parameters. These will be interpreted by the host-side device-specific + * @type is one of the generic VENDOR types, the @u is an union of the + * overall @sub_type field which is made up of the custom resource version + * number in the upper 16-bits and the custom resource type itself in the + * lower 16-bits. @rsc_size is the length of @rsc (in bytes), and @rsc is the + * actual parameters. These will be interpreted by the host-side device-specific * driver. */ - struct fw_rsc_custom { uint32_t type; - uint32_t sub_type; + union { + uint32_t sub_type; + struct { + uint16_t type; + uint16_t ver; + } st; + } u; uint32_t rsc_size; union fw_custom rsc; }; diff --git a/lib/pru_rpmsg/include/types.h b/lib/pru_rpmsg/include/types.h deleted file mode 100644 index 35acdcf696d2..000000000000 --- a/lib/pru_rpmsg/include/types.h +++ /dev/null @@ -1,18 +0,0 @@ -/* - * types.h - standard redefined types - */ - -#ifndef _TYPES_H_ -#define _TYPES_H_ - -typedef uint8_t __u8; -typedef uint16_t __u16; -typedef uint32_t __u32; -typedef uint64_t __u64; - -typedef uint8_t u8; -typedef uint16_t u16; -typedef uint32_t u32; -typedef uint64_t u64; - -#endif /* _TYPES_H_ */ diff --git a/lib/pru_rpmsg/pru_virtqueue.c b/lib/pru_rpmsg/pru_virtqueue.c index 5b14c6e4957b..cce93d01fede 100644 --- a/lib/pru_rpmsg/pru_virtqueue.c +++ b/lib/pru_rpmsg/pru_virtqueue.c @@ -41,10 +41,10 @@ */ #include -#ifndef __GNUC__ -volatile register uint32_t __R31; -#else +#ifdef __GNUC__ #include +#else +volatile register uint32_t __R31; #endif /* bit 5 is the valid strobe to generate system events with __R31 */ @@ -131,19 +131,12 @@ int16_t pru_virtqueue_kick( struct pru_virtqueue *vq ) { - unsigned int r31; - /* If requested, do not kick the ARM host */ if (vq->vring.avail->flags & VRING_AVAIL_F_NO_INTERRUPT) return PRU_VIRTQUEUE_NO_KICK; /* Generate a system event to kick the ARM */ - r31 = (INT_ENABLE | (vq->to_arm_event - INT_OFFSET)); -#ifdef __GNUC__ - write_r31(r31); -#else - __R31 = r31; -#endif + __R31 = (INT_ENABLE | (vq->to_arm_event - INT_OFFSET)); return PRU_VIRTQUEUE_SUCCESS; } diff --git a/scripts/buildcommands.py b/scripts/buildcommands.py index 2acf3e84699d..b35873840add 100644 --- a/scripts/buildcommands.py +++ b/scripts/buildcommands.py @@ -1,7 +1,7 @@ #!/usr/bin/env python2 # Script to handle build time requests embedded in C code. # -# Copyright (C) 2016-2021 Kevin O'Connor +# Copyright (C) 2016-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. import sys, os, subprocess, optparse, logging, shlex, socket, time, traceback @@ -251,8 +251,9 @@ class HandleCommandGeneration: def __init__(self): self.commands = {} self.encoders = [] - self.msg_to_id = dict(msgproto.DefaultMessages) - self.messages_by_name = { m.split()[0]: m for m in self.msg_to_id } + self.msg_to_encid = dict(msgproto.DefaultMessages) + self.encid_to_msgid = {} + self.messages_by_name = { m.split()[0]: m for m in self.msg_to_encid } self.all_param_types = {} self.ctr_dispatch = { 'DECL_COMMAND_FLAGS': self.decl_command, @@ -280,37 +281,47 @@ def decl_encoder(self, req): def decl_output(self, req): msg = req.split(None, 1)[1] self.encoders.append((None, msg)) + def convert_encoded_msgid(self, encoded_msgid): + if encoded_msgid >= 0x80: + data = [(encoded_msgid >> 7) | 0x80, encoded_msgid & 0x7f] + else: + data = [encoded_msgid] + return msgproto.PT_int32().parse(data, 0)[0] def create_message_ids(self): # Create unique ids for each message type - msgid = max(self.msg_to_id.values()) + encoded_msgid = max(self.msg_to_encid.values()) mlist = list(self.commands.keys()) + [m for n, m in self.encoders] for msgname in mlist: msg = self.messages_by_name.get(msgname, msgname) - if msg not in self.msg_to_id: - msgid += 1 - self.msg_to_id[msg] = msgid - if msgid >= 128: - # The mcu currently assumes all message ids encode to one byte + if msg not in self.msg_to_encid: + encoded_msgid += 1 + self.msg_to_encid[msg] = encoded_msgid + if encoded_msgid >= 1<<14: + # The mcu currently assumes all message ids encode to 1 or 2 bytes error("Too many message ids") + self.encid_to_msgid = { + encoded_msgid: self.convert_encoded_msgid(encoded_msgid) + for encoded_msgid in self.msg_to_encid.values() + } def update_data_dictionary(self, data): - # Handle message ids over 96 (they are decoded as negative numbers) - msg_to_tag = {msg: msgid if msgid < 96 else msgid - 128 - for msg, msgid in self.msg_to_id.items()} - command_tags = [msg_to_tag[msg] + # Convert ids to standard form (use both positive and negative numbers) + msg_to_msgid = {msg: self.encid_to_msgid[encoded_msgid] + for msg, encoded_msgid in self.msg_to_encid.items()} + command_ids = [msg_to_msgid[msg] + for msgname, msg in self.messages_by_name.items() + if msgname in self.commands] + response_ids = [msg_to_msgid[msg] for msgname, msg in self.messages_by_name.items() - if msgname in self.commands] - response_tags = [msg_to_tag[msg] - for msgname, msg in self.messages_by_name.items() - if msgname not in self.commands] - data['commands'] = { msg: msgtag for msg, msgtag in msg_to_tag.items() - if msgtag in command_tags } - data['responses'] = { msg: msgtag for msg, msgtag in msg_to_tag.items() - if msgtag in response_tags } - output = {msg: msgtag for msg, msgtag in msg_to_tag.items() - if msgtag not in command_tags and msgtag not in response_tags} + if msgname not in self.commands] + data['commands'] = { msg: msgid for msg, msgid in msg_to_msgid.items() + if msgid in command_ids } + data['responses'] = { msg: msgid for msg, msgid in msg_to_msgid.items() + if msgid in response_ids } + output = {msg: msgid for msg, msgid in msg_to_msgid.items() + if msgid not in command_ids and msgid not in response_ids} if output: data['output'] = output - def build_parser(self, msgid, msgformat, msgtype): + def build_parser(self, encoded_msgid, msgformat, msgtype): if msgtype == "output": param_types = msgproto.lookup_output_params(msgformat) comment = "Output: " + msgformat @@ -327,17 +338,21 @@ def build_parser(self, msgid, msgformat, msgtype): params = 'command_parameters%d' % (paramid,) out = """ // %s - .msg_id=%d, + .encoded_msgid=%d, // msgid=%d .num_params=%d, .param_types = %s, -""" % (comment, msgid, len(types), params) +""" % (comment, encoded_msgid, self.encid_to_msgid[encoded_msgid], + len(types), params) if msgtype == 'response': num_args = (len(types) + types.count('PT_progmem_buffer') + types.count('PT_buffer')) out += " .num_args=%d," % (num_args,) else: + msgid_size = 1 + if encoded_msgid >= 0x80: + msgid_size = 2 max_size = min(msgproto.MESSAGE_MAX, - (msgproto.MESSAGE_MIN + 1 + (msgproto.MESSAGE_MIN + msgid_size + sum([t.max_length for t in param_types]))) out += " .max_size=%d," % (max_size,) return out @@ -347,22 +362,23 @@ def generate_responses_code(self): encoder_code = [] did_output = {} for msgname, msg in self.encoders: - msgid = self.msg_to_id[msg] - if msgid in did_output: + encoded_msgid = self.msg_to_encid[msg] + if encoded_msgid in did_output: continue - did_output[msgid] = True + did_output[encoded_msgid] = True code = (' if (__builtin_strcmp(str, "%s") == 0)\n' - ' return &command_encoder_%s;\n' % (msg, msgid)) + ' return &command_encoder_%s;\n' + % (msg, encoded_msgid)) if msgname is None: - parsercode = self.build_parser(msgid, msg, 'output') + parsercode = self.build_parser(encoded_msgid, msg, 'output') output_code.append(code) else: - parsercode = self.build_parser(msgid, msg, 'command') + parsercode = self.build_parser(encoded_msgid, msg, 'command') encoder_code.append(code) encoder_defs.append( "const struct command_encoder command_encoder_%s PROGMEM = {" " %s\n};\n" % ( - msgid, parsercode)) + encoded_msgid, parsercode)) fmt = """ %s @@ -384,21 +400,21 @@ def generate_responses_code(self): "".join(encoder_code).strip(), "".join(output_code).strip()) def generate_commands_code(self): - cmd_by_id = { - self.msg_to_id[self.messages_by_name.get(msgname, msgname)]: cmd + cmd_by_encid = { + self.msg_to_encid[self.messages_by_name.get(msgname, msgname)]: cmd for msgname, cmd in self.commands.items() } - max_cmd_msgid = max(cmd_by_id.keys()) + max_cmd_encid = max(cmd_by_encid.keys()) index = [] externs = {} - for msgid in range(max_cmd_msgid+1): - if msgid not in cmd_by_id: + for encoded_msgid in range(max_cmd_encid+1): + if encoded_msgid not in cmd_by_encid: index.append(" {\n},") continue - funcname, flags, msgname = cmd_by_id[msgid] + funcname, flags, msgname = cmd_by_encid[encoded_msgid] msg = self.messages_by_name[msgname] externs[funcname] = 1 - parsercode = self.build_parser(msgid, msg, 'response') + parsercode = self.build_parser(encoded_msgid, msg, 'response') index.append(" {%s\n .flags=%s,\n .func=%s\n}," % ( parsercode, flags, funcname)) index = "".join(index).strip() @@ -411,7 +427,7 @@ def generate_commands_code(self): %s }; -const uint8_t command_index_size PROGMEM = ARRAY_SIZE(command_index); +const uint16_t command_index_size PROGMEM = ARRAY_SIZE(command_index); """ return fmt % (externs, index) def generate_param_code(self): @@ -518,6 +534,8 @@ def __init__(self): def update_data_dictionary(self, data): data['version'] = self.version data['build_versions'] = self.toolstr + data['app'] = 'Klipper' + data['license'] = 'GNU GPLv3' def generate_code(self, options): cleanbuild, self.toolstr = tool_versions(options.tools) self.version = build_version(options.extra, cleanbuild) diff --git a/scripts/calibrate_shaper.py b/scripts/calibrate_shaper.py index 8a0fcdf00b6b..b56ce5daa836 100755 --- a/scripts/calibrate_shaper.py +++ b/scripts/calibrate_shaper.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # Shaper auto-calibration script # -# Copyright (C) 2020 Dmitry Butyugin +# Copyright (C) 2020-2024 Dmitry Butyugin # Copyright (C) 2020 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. @@ -40,7 +40,9 @@ def parse_log(logname): ###################################################################### # Find the best shaper parameters -def calibrate_shaper(datas, csv_output, max_smoothing): +def calibrate_shaper(datas, csv_output, *, shapers, damping_ratio, scv, + shaper_freqs, max_smoothing, test_damping_ratios, + max_freq): helper = shaper_calibrate.ShaperCalibrate(printer=None) if isinstance(datas[0], shaper_calibrate.CalibrationData): calibration_data = datas[0] @@ -52,8 +54,17 @@ def calibrate_shaper(datas, csv_output, max_smoothing): for data in datas[1:]: calibration_data.add_data(helper.process_accelerometer_data(data)) calibration_data.normalize_to_frequencies() + + shaper, all_shapers = helper.find_best_shaper( - calibration_data, max_smoothing, print) + calibration_data, shapers=shapers, damping_ratio=damping_ratio, + scv=scv, shaper_freqs=shaper_freqs, max_smoothing=max_smoothing, + test_damping_ratios=test_damping_ratios, max_freq=max_freq, + logger=print) + if not shaper: + print("No recommended shaper, possibly invalid value for --shapers=%s" % + (','.join(shapers))) + return None, None, None print("Recommended shaper is %s @ %.1f Hz" % (shaper.name, shaper.freq)) if csv_output is not None: helper.save_calibration_data( @@ -140,28 +151,94 @@ def main(): opts.add_option("-c", "--csv", type="string", dest="csv", default=None, help="filename of output csv file") opts.add_option("-f", "--max_freq", type="float", default=200., - help="maximum frequency to graph") - opts.add_option("-s", "--max_smoothing", type="float", default=None, - help="maximum shaper smoothing to allow") + help="maximum frequency to plot") + opts.add_option("-s", "--max_smoothing", type="float", dest="max_smoothing", + default=None, help="maximum shaper smoothing to allow") + opts.add_option("--scv", "--square_corner_velocity", type="float", + dest="scv", default=5., help="square corner velocity") + opts.add_option("--shaper_freq", type="string", dest="shaper_freq", + default=None, help="shaper frequency(-ies) to test, " + + "either a comma-separated list of floats, or a range in " + + "the format [start]:end[:step]") + opts.add_option("--shapers", type="string", dest="shapers", default=None, + help="a comma-separated list of shapers to test") + opts.add_option("--damping_ratio", type="float", dest="damping_ratio", + default=None, help="shaper damping_ratio parameter") + opts.add_option("--test_damping_ratios", type="string", + dest="test_damping_ratios", default=None, + help="a comma-separated liat of damping ratios to test " + + "input shaper for") options, args = opts.parse_args() if len(args) < 1: opts.error("Incorrect number of arguments") if options.max_smoothing is not None and options.max_smoothing < 0.05: opts.error("Too small max_smoothing specified (must be at least 0.05)") + max_freq = options.max_freq + if options.shaper_freq is None: + shaper_freqs = [] + elif options.shaper_freq.find(':') >= 0: + freq_start = None + freq_end = None + freq_step = None + try: + freqs_parsed = options.shaper_freq.partition(':') + if freqs_parsed[0]: + freq_start = float(freqs_parsed[0]) + freqs_parsed = freqs_parsed[-1].partition(':') + freq_end = float(freqs_parsed[0]) + if freq_start and freq_start > freq_end: + opts.error("Invalid --shaper_freq param: start range larger " + + "than its end") + if freqs_parsed[-1].find(':') >= 0: + opts.error("Invalid --shaper_freq param format") + if freqs_parsed[-1]: + freq_step = float(freqs_parsed[-1]) + except ValueError: + opts.error("--shaper_freq param does not specify correct range " + + "in the format [start]:end[:step]") + shaper_freqs = (freq_start, freq_end, freq_step) + max_freq = max(max_freq, freq_end * 4./3.) + else: + try: + shaper_freqs = [float(s) for s in options.shaper_freq.split(',')] + except ValueError: + opts.error("invalid floating point value in --shaper_freq param") + max_freq = max(max_freq, max(shaper_freqs) * 4./3.) + if options.test_damping_ratios: + try: + test_damping_ratios = [float(s) for s in + options.test_damping_ratios.split(',')] + except ValueError: + opts.error("invalid floating point value in " + + "--test_damping_ratios param") + else: + test_damping_ratios = None + if options.shapers is None: + shapers = None + else: + shapers = options.shapers.lower().split(',') + # Parse data datas = [parse_log(fn) for fn in args] # Calibrate shaper and generate outputs selected_shaper, shapers, calibration_data = calibrate_shaper( - datas, options.csv, options.max_smoothing) + datas, options.csv, shapers=shapers, + damping_ratio=options.damping_ratio, + scv=options.scv, shaper_freqs=shaper_freqs, + max_smoothing=options.max_smoothing, + test_damping_ratios=test_damping_ratios, + max_freq=max_freq) + if selected_shaper is None: + return if not options.csv or options.output: # Draw graph setup_matplotlib(options.output is not None) fig = plot_freq_response(args, calibration_data, shapers, - selected_shaper, options.max_freq) + selected_shaper, max_freq) # Show graph if options.output is None: diff --git a/scripts/ci-install.sh b/scripts/ci-install.sh index a7d2599a29f8..f5a18612ffa8 100755 --- a/scripts/ci-install.sh +++ b/scripts/ci-install.sh @@ -35,7 +35,7 @@ if [ ! -f ${PRU_FILE} ]; then cd ${BUILD_DIR} git config --global user.email "you@example.com" git config --global user.name "Your Name" - git clone https://github.com/dinuxbg/gnupru -b 2023.01 --depth 1 + git clone https://github.com/dinuxbg/gnupru -b 2024.05 --depth 1 cd gnupru export PREFIX=${PRU_DIR} ./download-and-prepare.sh 2>&1 | pv -nli 30 > ${BUILD_DIR}/gnupru-build.log diff --git a/scripts/graph_mesh.py b/scripts/graph_mesh.py new file mode 100755 index 000000000000..3a331e5d5dca --- /dev/null +++ b/scripts/graph_mesh.py @@ -0,0 +1,533 @@ +#!/usr/bin/env python3 +# Bed Mesh data plotting and analysis +# +# Copyright (C) 2024 Eric Callahan +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import argparse +import sys +import os +import stat +import errno +import time +import socket +import re +import json +import collections +import numpy as np +import matplotlib +import matplotlib.cm as cm +import matplotlib.pyplot as plt +import matplotlib.animation as ani + +MESH_DUMP_REQUEST = json.dumps( + {"id": 1, "method": "bed_mesh/dump_mesh"} +) + +def sock_error_exit(msg): + sys.stderr.write(msg + "\n") + sys.exit(-1) + +def webhook_socket_create(uds_filename): + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + while 1: + try: + sock.connect(uds_filename) + except socket.error as e: + if e.errno == errno.ECONNREFUSED: + time.sleep(0.1) + continue + sock_error_exit( + "Unable to connect socket %s [%d,%s]" + % (uds_filename, e.errno, errno.errorcode[e.errno]) + ) + break + print("Connected") + return sock + +def process_message(msg): + try: + resp = json.loads(msg) + except json.JSONDecodeError: + return None + if resp.get("id", -1) != 1: + return None + if "error" in resp: + err = resp["error"].get("message", "Unknown") + sock_error_exit( + "Error requesting mesh dump: %s" % (err,) + ) + return resp["result"] + + +def request_from_unixsocket(unix_sock_name): + print("Connecting to Unix Socket File '%s'" % (unix_sock_name,)) + whsock = webhook_socket_create(unix_sock_name) + whsock.settimeout(1.) + # send mesh query + whsock.send(MESH_DUMP_REQUEST.encode() + b"\x03") + sock_data = b"" + end_time = time.monotonic() + 10.0 + try: + while time.monotonic() < end_time: + try: + data = whsock.recv(4096) + except TimeoutError: + pass + else: + if not data: + sock_error_exit("Socket closed before mesh received") + parts = data.split(b"\x03") + parts[0] = sock_data + parts[0] + sock_data = parts.pop() + for msg in parts: + result = process_message(msg) + if result is not None: + return result + time.sleep(.1) + finally: + whsock.close() + sock_error_exit("Mesh dump request timed out") + +def request_from_websocket(url): + print("Connecting to websocket url '%s'" % (url,)) + try: + from websockets.sync.client import connect + except ModuleNotFoundError: + sock_error_exit("Python module 'websockets' not installed.") + raise + with connect(url) as websocket: + websocket.send(MESH_DUMP_REQUEST) + end_time = time.monotonic() + 20.0 + while time.monotonic() < end_time: + try: + msg = websocket.recv(10.) + except TimeoutError: + continue + result = process_message(msg) + if result is not None: + return result + time.sleep(.1) + sock_error_exit("Mesh dump request timed out") + +def request_mesh_data(input_name): + url_match = re.match(r"((?:https?)|(?:wss?))://(.+)", input_name.lower()) + if url_match is None: + file_path = os.path.abspath(os.path.expanduser(input_name)) + if not os.path.exists(file_path): + sock_error_exit("Path '%s' does not exist" % (file_path,)) + st_res = os.stat(file_path) + if stat.S_ISSOCK(st_res.st_mode): + return request_from_unixsocket(file_path) + else: + print("Reading mesh data from json file '%s'" % (file_path,)) + with open(file_path, "r") as f: + return json.load(f) + scheme = url_match.group(1) + host = url_match.group(2).rstrip("/") + scheme = scheme.replace("http", "ws") + url = "%s://%s/klippysocket" % (scheme, host) + return request_from_websocket(url) + +class PathAnimation: + instance = None + def __init__(self, artist, x_travel, y_travel): + self.travel_artist = artist + self.x_travel = x_travel + self.y_travel = y_travel + fig = plt.gcf() + self.animation = ani.FuncAnimation( + fig=fig, func=self.update, frames=self.gen_path_position(), + cache_frame_data=False, interval=60 + ) + PathAnimation.instance = self + + def gen_path_position(self): + count = 1 + x_travel, y_travel = self.x_travel, self.y_travel + last_x, last_y = x_travel[0], y_travel[0] + yield count + for xpos, ypos in zip(x_travel[1:], y_travel[1:]): + count += 1 + if xpos == last_x or ypos == last_y: + yield count + last_x, last_y = xpos, ypos + + def update(self, frame): + x_travel, y_travel = self.x_travel, self.y_travel + self.travel_artist.set_xdata(x_travel[:frame]) + self.travel_artist.set_ydata(y_travel[:frame]) + return (self.travel_artist,) + + +def _gen_mesh_coords(min_c, max_c, count): + dist = (max_c - min_c) / (count - 1) + return [min_c + i * dist for i in range(count)] + +def _plot_path(travel_path, probed, diff, cmd_args): + x_travel, y_travel = np.array(travel_path).transpose() + x_probed, y_probed = np.array(probed).transpose() + plt.xlabel("X") + plt.ylabel("Y") + # plot travel + travel_line = plt.plot(x_travel, y_travel, "b-")[0] + # plot intermediate points + plt.plot(x_probed, y_probed, "k.") + # plot start point + plt.plot([x_travel[0]], [y_travel[0]], "g>") + # plot stop point + plt.plot([x_travel[-1]], [y_travel[-1]], "r*") + if diff: + diff_x, diff_y = np.array(diff).transpose() + plt.plot(diff_x, diff_y, "m.") + if cmd_args.animate and cmd_args.output is None: + PathAnimation(travel_line, x_travel, y_travel) + +def _format_mesh_data(matrix, params): + min_pt = (params["min_x"], params["min_y"]) + max_pt = (params["max_x"], params["max_y"]) + xvals = _gen_mesh_coords(min_pt[0], max_pt[0], len(matrix[0])) + yvals = _gen_mesh_coords(min_pt[1], max_pt[0], len(matrix)) + x, y = np.meshgrid(xvals, yvals) + z = np.array(matrix) + return x, y, z + +def _set_xy_limits(mesh_data, cmd_args): + if not cmd_args.scale_plot: + return + ax = plt.gca() + axis_min = mesh_data["axis_minimum"] + axis_max = mesh_data["axis_maximum"] + ax.set_xlim((axis_min[0], axis_max[0])) + ax.set_ylim((axis_min[1], axis_max[1])) + +def _plot_mesh(ax, matrix, params, cmap=cm.viridis, label=None): + x, y, z = _format_mesh_data(matrix, params) + surface = ax.plot_surface(x, y, z, cmap=cmap, label=label) + scale = max(abs(z.min()), abs(z.max())) * 3 + return surface, scale + +def plot_probe_points(mesh_data, cmd_args): + """Plot original generated points""" + calibration = mesh_data["calibration"] + x, y = np.array(calibration["points"]).transpose() + plt.title("Generated Probe Points") + plt.xlabel("X") + plt.ylabel("Y") + plt.plot(x, y, "b.") + _set_xy_limits(mesh_data, cmd_args) + +def plot_probe_path(mesh_data, cmd_args): + """Plot probe travel path""" + calibration = mesh_data["calibration"] + orig_pts = calibration["points"] + path_pts = calibration["probe_path"] + diff = [pt for pt in orig_pts if pt not in path_pts] + plt.title("Probe Travel Path") + _plot_path(path_pts, path_pts[1:-1], diff, cmd_args) + _set_xy_limits(mesh_data, cmd_args) + +def plot_rapid_path(mesh_data, cmd_args): + """Plot rapid scan travel path""" + calibration = mesh_data["calibration"] + orig_pts = calibration["points"] + rapid_pts = calibration["rapid_path"] + rapid_path = [pt[0] for pt in rapid_pts] + probed = [pt for pt, is_ppt in rapid_pts if is_ppt] + diff = [pt for pt in orig_pts if pt not in probed] + plt.title("Rapid Scan Travel Path") + _plot_path(rapid_path, probed, diff, cmd_args) + _set_xy_limits(mesh_data, cmd_args) + +def plot_probed_matrix(mesh_data, cmd_args): + """Plot probed Z values""" + ax = plt.subplot(projection="3d") + profile = cmd_args.profile_name + if profile is not None: + req_mesh = mesh_data["profiles"].get(profile) + if req_mesh is None: + raise Exception("Profile %s not found" % (profile,)) + matrix = req_mesh["points"] + name = profile + else: + req_mesh = mesh_data["current_mesh"] + if not req_mesh: + raise Exception("No current mesh data in dump") + matrix = req_mesh["probed_matrix"] + name = req_mesh["name"] + params = req_mesh["mesh_params"] + surface, scale = _plot_mesh(ax, matrix, params) + ax.set_title("Probed Mesh (%s)" % (name,)) + ax.set(zlim=(-scale, scale)) + plt.gcf().colorbar(surface, shrink=.75) + _set_xy_limits(mesh_data, cmd_args) + +def plot_mesh_matrix(mesh_data, cmd_args): + """Plot mesh Z values""" + ax = plt.subplot(projection="3d") + req_mesh = mesh_data["current_mesh"] + if not req_mesh: + raise Exception("No current mesh data in dump") + matrix = req_mesh["mesh_matrix"] + params = req_mesh["mesh_params"] + surface, scale = _plot_mesh(ax, matrix, params) + name = req_mesh["name"] + ax.set_title("Interpolated Mesh (%s)" % (name,)) + ax.set(zlim=(-scale, scale)) + plt.gcf().colorbar(surface, shrink=.75) + _set_xy_limits(mesh_data, cmd_args) + +def plot_overlay(mesh_data, cmd_args): + """Plots the current probed mesh overlaid with a profile""" + ax = plt.subplot(projection="3d") + # Plot Profile + profile = cmd_args.profile_name + if profile is None: + raise Exception("A profile must be specified to plot an overlay") + req_mesh = mesh_data["profiles"].get(profile) + if req_mesh is None: + raise Exception("Profile %s not found" % (profile,)) + matrix = req_mesh["points"] + params = req_mesh["mesh_params"] + prof_surf, prof_scale = _plot_mesh(ax, matrix, params, label=profile) + # Plot Current + req_mesh = mesh_data["current_mesh"] + if not req_mesh: + raise Exception("No current mesh data in dump") + matrix = req_mesh["probed_matrix"] + params = req_mesh["mesh_params"] + cur_name = req_mesh["name"] + cur_surf, cur_scale = _plot_mesh(ax, matrix, params, cm.inferno, cur_name) + ax.set_title("Probed Mesh Overlay") + scale = max(cur_scale, prof_scale) + ax.set(zlim=(-scale, scale)) + ax.legend(loc='best') + plt.gcf().colorbar(prof_surf, shrink=.75) + _set_xy_limits(mesh_data, cmd_args) + +def plot_delta(mesh_data, cmd_args): + """Plots the delta between current probed mesh and a profile""" + ax = plt.subplot(projection="3d") + # Plot Profile + profile = cmd_args.profile_name + if profile is None: + raise Exception("A profile must be specified to plot an overlay") + req_mesh = mesh_data["profiles"].get(profile) + if req_mesh is None: + raise Exception("Profile %s not found" % (profile,)) + prof_matix = req_mesh["points"] + prof_params = req_mesh["mesh_params"] + req_mesh = mesh_data["current_mesh"] + if not req_mesh: + raise Exception("No current mesh data in dump") + cur_matrix = req_mesh["probed_matrix"] + cur_params = req_mesh["mesh_params"] + cur_name = req_mesh["name"] + # validate that the params match + pfields = ("x_count", "y_count", "min_x", "max_x", "min_y", "max_y") + for field in pfields: + if abs(prof_params[field] - cur_params[field]) >= 1e-6: + raise Exception( + "Values for field %s do not match, cant plot deviation" + ) + delta = np.array(cur_matrix) - np.array(prof_matix) + surface, scale = _plot_mesh(ax, delta, cur_params) + ax.set(zlim=(-scale, scale)) + ax.set_title("Probed Mesh Delta (%s, %s)" % (cur_name, profile)) + _set_xy_limits(mesh_data, cmd_args) + + +PLOT_TYPES = { + "points": plot_probe_points, + "path": plot_probe_path, + "rapid": plot_rapid_path, + "probedz": plot_probed_matrix, + "meshz": plot_mesh_matrix, + "overlay": plot_overlay, + "delta": plot_delta, +} + +def print_types(cmd_args): + typelist = [ + "%-10s%s" % (name, func.__doc__) for name, func in PLOT_TYPES.items() + ] + print("\n".join(typelist)) + +def plot_mesh_data(cmd_args): + mesh_data = request_mesh_data(cmd_args.input) + if cmd_args.output is not None: + matplotlib.use("svg") + + fig = plt.figure() + plot_func = PLOT_TYPES[cmd_args.type] + plot_func(mesh_data, cmd_args) + fig.set_size_inches(10, 8) + fig.tight_layout() + if cmd_args.output is None: + plt.show() + else: + fig.savefig(cmd_args.output) + +def _check_path_unique(name, path): + path = np.array(path) + unique_pts, counts = np.unique(path, return_counts=True, axis=0) + for idx, count in enumerate(counts): + if count != 1: + coord = unique_pts[idx] + print( + " WARNING: Backtracking or duplicate found in %s path at %s, " + "this may be due to multiple samples in a faulty region." + % (name, coord) + ) + +def _analyze_mesh(name, mesh_axes): + print("\nAnalyzing Probed Mesh %s..." % (name,)) + x, y, z = mesh_axes + min_idx, max_idx = z.argmin(), z.argmax() + min_x, min_y = x.flatten()[min_idx], y.flatten()[min_idx] + max_x, max_y = x.flatten()[max_idx], y.flatten()[max_idx] + + print( + " Min Coord (%.2f, %.2f), Max Coord (%.2f, %.2f), " + "Probe Count: (%d, %d)" % + (x.min(), y.min(), x.max(), y.max(), len(z), len(z[0])) + ) + print( + " Mesh range: min %.4f (%.2f, %.2f), max %.4f (%.2f, %.2f)" + % (z.min(), min_x, min_y, z.max(), max_x, max_y) + ) + print(" Mean: %.4f, Standard Deviation: %.4f" % (z.mean(), z.std())) + +def _compare_mesh(name_a, name_b, mesh_a, mesh_b): + ax, ay, az = mesh_a + bx, by, bz = mesh_b + if not np.array_equal(ax, bx) or not np.array_equal(ay, by): + return + delta = az - bz + abs_max = max(abs(delta.max()), abs(delta.min())) + abs_mean = sum([abs(z) for z in delta.flatten()]) / len(delta.flatten()) + min_idx, max_idx = delta.argmin(), delta.argmax() + min_x, min_y = ax.flatten()[min_idx], ay.flatten()[min_idx] + max_x, max_y = ax.flatten()[max_idx], ay.flatten()[max_idx] + print(" Delta from %s to %s..." % (name_a, name_b)) + print( + " Range: min %.4f (%.2f, %.2f), max %.4f (%.2f, %.2f)\n" + " Mean: %.6f, Standard Deviation: %.6f\n" + " Absolute Max: %.6f, Absolute Mean: %.6f" + % (delta.min(), min_x, min_y, delta.max(), max_x, max_y, + delta.mean(), delta.std(), abs_max, abs_mean) + ) + +def analyze(cmd_args): + mesh_data = request_mesh_data(cmd_args.input) + print("Analyzing Travel Path...") + calibration = mesh_data["calibration"] + org_pts = calibration["points"] + probe_path = calibration["probe_path"] + rapid_path = calibration["rapid_path"] + rapid_points = [pt for pt, is_pt in rapid_path if is_pt] + rapid_moves = [pt[0] for pt in rapid_path] + print(" Original point count: %d" % (len(org_pts))) + print(" Probe path count: %d" % (len(probe_path))) + print(" Rapid scan sample count: %d" % (len(probe_path))) + print(" Rapid scan move count: %d" % (len(rapid_moves))) + if np.array_equal(rapid_points, probe_path): + print(" Rapid scan points match probe path points") + else: + diff = [pt for pt in rapid_points if pt not in probe_path] + print( + " ERROR: Rapid scan points do not match probe points\n" + "difference: %s" % (diff,) + ) + _check_path_unique("probe", probe_path) + _check_path_unique("rapid scan", rapid_moves) + req_mesh = mesh_data["current_mesh"] + formatted_data = collections.OrderedDict() + if req_mesh: + matrix = req_mesh["probed_matrix"] + params = req_mesh["mesh_params"] + name = req_mesh["name"] + formatted_data[name] = _format_mesh_data(matrix, params) + profiles = mesh_data["profiles"] + for prof_name, prof_data in profiles.items(): + if prof_name in formatted_data: + continue + matrix = prof_data["points"] + params = prof_data["mesh_params"] + formatted_data[prof_name] = _format_mesh_data(matrix, params) + while formatted_data: + name, current_axes = formatted_data.popitem() + _analyze_mesh(name, current_axes) + for prof_name, prof_axes in formatted_data.items(): + _compare_mesh(name, prof_name, current_axes, prof_axes) + +def dump_request(cmd_args): + mesh_data = request_mesh_data(cmd_args.input) + outfile = cmd_args.output + if outfile is None: + postfix = time.strftime("%Y%m%d_%H%M%S") + outfile = "klipper-bedmesh-%s.json" % (postfix,) + outfile = os.path.abspath(os.path.expanduser(outfile)) + print("Saving Mesh Output to '%s'" % (outfile)) + with open(outfile, "w") as f: + f.write(json.dumps(mesh_data)) + +def main(): + parser = argparse.ArgumentParser(description="Graph Bed Mesh Data") + sub_parsers = parser.add_subparsers() + list_parser = sub_parsers.add_parser( + "list", help="List available plot types" + ) + list_parser.set_defaults(func=print_types) + plot_parser = sub_parsers.add_parser("plot", help="Plot a specified type") + analyze_parser = sub_parsers.add_parser( + "analyze", help="Perform analysis on mesh data" + ) + dump_parser = sub_parsers.add_parser( + "dump", help="Dump API response to json file" + ) + plot_parser.add_argument( + "-a", "--animate", action="store_true", + help="Animate paths in live preview" + ) + plot_parser.add_argument( + "-s", "--scale-plot", action="store_true", + help="Use axis limits reported by Klipper to scale plot X/Y" + ) + plot_parser.add_argument( + "-p", "--profile-name", type=str, default=None, + help="Optional name of a profile to plot for 'probedz'" + ) + plot_parser.add_argument( + "-o", "--output", type=str, default=None, + help="Output file path" + ) + plot_parser.add_argument( + "type", metavar="", type=str, choices=PLOT_TYPES.keys(), + help="Type of data to graph" + ) + plot_parser.add_argument( + "input", metavar="", + help="Path/url to Klipper Socket or path to json file" + ) + plot_parser.set_defaults(func=plot_mesh_data) + analyze_parser.add_argument( + "input", metavar="", + help="Path/url to Klipper Socket or path to json file" + ) + analyze_parser.set_defaults(func=analyze) + dump_parser.add_argument( + "-o", "--output", type=str, default=None, + help="Json output file path" + ) + dump_parser.add_argument( + "input", metavar="", + help="Path or url to Klipper Socket" + ) + dump_parser.set_defaults(func=dump_request) + cmd_args = parser.parse_args() + cmd_args.func(cmd_args) + + +if __name__ == "__main__": + main() diff --git a/scripts/install-beaglebone.sh b/scripts/install-beaglebone.sh index a68c8d060397..58424c05fc7a 100755 --- a/scripts/install-beaglebone.sh +++ b/scripts/install-beaglebone.sh @@ -1,5 +1,5 @@ #!/bin/bash -# This script installs Klipper on a Beaglebone running Debian Jessie +# This script installs Klipper on a Beaglebone running Debian Bullseye # for use with its PRU micro-controller. # Step 1: Do main install @@ -13,6 +13,12 @@ install_main() # Step 2: Install additional system packages install_packages() { + # Remove conflicting AVR packages + PKGLIST_REMOVE="avrdude gcc-avr binutils-avr avr-libc" + + report_status "Removing ARM packages because of conflicts with PRU packages" + sudo apt-get remove --yes ${PKGLIST_REMOVE} + # Install desired packages PKGLIST="gcc-pru" @@ -25,7 +31,7 @@ install_script() { report_status "Installing pru start script..." sudo cp "${SRCDIR}/scripts/klipper-pru-start.sh" /etc/init.d/klipper_pru - sudo update-rc.d klipper_pru defaults + sudo update-rc.d klipper_pru defaults-disabled } # Step 4: Install pru udev rule @@ -33,6 +39,7 @@ install_udev() { report_status "Installing pru udev rule..." sudo /bin/sh -c "cat > /etc/udev/rules.d/pru.rules" <= '3.12' Jinja2==2.11.3 python-can==3.3.4 markupsafe==1.1.1 diff --git a/scripts/motan/data_logger.py b/scripts/motan/data_logger.py index 7d704c83cec6..e00c99560bf2 100755 --- a/scripts/motan/data_logger.py +++ b/scripts/motan/data_logger.py @@ -151,16 +151,17 @@ def handle_subscribe(self, msg, raw_msg): self.send_subscribe("stepq:" + stepper, "motion_report/dump_stepper", {"name": stepper}) # Subscribe to additional sensor data + stypes = ["adxl345", "lis2dw", "mpu9250", "angle"] + stypes = {st:st for st in stypes} + stypes['probe_eddy_current'] = 'ldc1612' config = status["configfile"]["settings"] for cfgname in config.keys(): - if cfgname == "adxl345" or cfgname.startswith("adxl345 "): - aname = cfgname.split()[-1] - self.send_subscribe("adxl345:" + aname, "adxl345/dump_adxl345", - {"sensor": aname}) - if cfgname.startswith("angle "): - aname = cfgname.split()[1] - self.send_subscribe("angle:" + aname, "angle/dump_angle", - {"sensor": aname}) + for capprefix, st in sorted(stypes.items()): + if cfgname == capprefix or cfgname.startswith(capprefix + " "): + aname = cfgname.split()[-1] + lname = "%s:%s" % (st, aname) + qcmd = "%s/dump_%s" % (st, st) + self.send_subscribe(lname, qcmd, {"sensor": aname}) def handle_dump(self, msg, raw_msg): msg_id = msg["id"] if "result" not in msg: diff --git a/scripts/motan/readlog.py b/scripts/motan/readlog.py index f49f0b593602..48284ec2be05 100644 --- a/scripts/motan/readlog.py +++ b/scripts/motan/readlog.py @@ -293,7 +293,7 @@ def pull_data(self, req_time): self._pull_block(req_time) continue step_pos = step_data[data_pos][1] - return (step_pos - self.mcu_phase_offset) % self.phases + return (step_pos + self.mcu_phase_offset) % self.phases def _pull_block(self, req_time): step_data = self.step_data del step_data[:-1] @@ -439,6 +439,71 @@ def pull_data(self, req_time): self.data_pos += 1 LogHandlers["angle"] = HandleAngle +def interpolate(next_val, prev_val, next_time, prev_time, req_time): + vdiff = next_val - prev_val + tdiff = next_time - prev_time + rtdiff = req_time - prev_time + return prev_val + rtdiff * vdiff / tdiff + +# Extract eddy current data +class HandleEddyCurrent: + SubscriptionIdParts = 2 + ParametersMin = 1 + ParametersMax = 2 + DataSets = [ + ('ldc1612()', 'Coil resonant frequency'), + ('ldc1612(,period)', 'Coil resonant period'), + ('ldc1612(,z)', 'Estimated Z height'), + ] + def __init__(self, lmanager, name, name_parts): + self.name = name + self.sensor_name = name_parts[1] + if len(name_parts) == 3 and name_parts[2] not in ("period", "z"): + raise error("Unknown ldc1612 selection '%s'" % (name_parts[2],)) + self.report_frequency = len(name_parts) == 2 + self.report_z = len(name_parts) == 3 and name_parts[2] == "z" + self.jdispatch = lmanager.get_jdispatch() + self.next_samp = self.prev_samp = [0., 0., 0.] + self.cur_data = [] + self.data_pos = 0 + def get_label(self): + if self.report_frequency: + label = '%s frequency' % (self.sensor_name,) + return {'label': label, 'units': 'Frequency\n(Hz)'} + if self.report_z: + label = '%s height' % (self.sensor_name,) + return {'label': label, 'units': 'Position\n(mm)'} + label = '%s period' % (self.sensor_name,) + return {'label': label, 'units': 'Period\n(s)'} + def pull_data(self, req_time): + while 1: + next_time, next_freq, next_z = self.next_samp + if req_time <= next_time: + prev_time, prev_freq, prev_z = self.prev_samp + if self.report_frequency: + next_val = next_freq + prev_val = prev_freq + elif self.report_z: + next_val = next_z + prev_val = prev_z + else: + next_val = 1. / next_freq + prev_val = 1. / prev_freq + return interpolate(next_val, prev_val, next_time, prev_time, + req_time) + if self.data_pos >= len(self.cur_data): + # Read next data block + jmsg = self.jdispatch.pull_msg(req_time, self.name) + if jmsg is None: + return 0. + self.cur_data = jmsg['data'] + self.data_pos = 0 + continue + self.prev_samp = self.next_samp + self.next_samp = self.cur_data[self.data_pos] + self.data_pos += 1 +LogHandlers["ldc1612"] = HandleEddyCurrent + ###################################################################### # Log reading diff --git a/src/Kconfig b/src/Kconfig index aaf506539493..7dcea3bab59d 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -104,6 +104,10 @@ config WANT_LIS2DW bool depends on HAVE_GPIO_SPI default y +config WANT_LDC1612 + bool + depends on HAVE_GPIO_I2C + default y config WANT_SOFTWARE_I2C bool depends on HAVE_GPIO && HAVE_GPIO_I2C @@ -112,6 +116,10 @@ config WANT_SOFTWARE_SPI bool depends on HAVE_GPIO && HAVE_GPIO_SPI default y +config NEED_SENSOR_BULK + bool + depends on WANT_SENSORS || WANT_LIS2DW || WANT_LDC1612 + default y menu "Optional features (to reduce code size)" depends on HAVE_LIMITED_CODE_SIZE config WANT_GPIO_BITBANGING @@ -126,6 +134,9 @@ config WANT_SENSORS config WANT_LIS2DW bool "Support lis2dw 3-axis accelerometer" depends on HAVE_GPIO_SPI +config WANT_LDC1612 + bool "Support ldc1612 eddy current sensor" + depends on HAVE_GPIO_I2C config WANT_SOFTWARE_I2C bool "Support software based I2C \"bit-banging\"" depends on HAVE_GPIO && HAVE_GPIO_I2C diff --git a/src/Makefile b/src/Makefile index 8d771f9eb484..ed98172e4e8d 100644 --- a/src/Makefile +++ b/src/Makefile @@ -16,6 +16,8 @@ src-$(CONFIG_WANT_SOFTWARE_SPI) += spi_software.c src-$(CONFIG_WANT_SOFTWARE_I2C) += i2c_software.c sensors-src-$(CONFIG_HAVE_GPIO_SPI) := thermocouple.c sensor_adxl345.c \ sensor_angle.c -src-$(CONFIG_WANT_LIS2DW) += sensor_lis2dw.c sensors-src-$(CONFIG_HAVE_GPIO_I2C) += sensor_mpu9250.c src-$(CONFIG_WANT_SENSORS) += $(sensors-src-y) +src-$(CONFIG_WANT_LIS2DW) += sensor_lis2dw.c +src-$(CONFIG_WANT_LDC1612) += sensor_ldc1612.c +src-$(CONFIG_NEED_SENSOR_BULK) += sensor_bulk.c diff --git a/src/atsam/Makefile b/src/atsam/Makefile index 7ab69b823e00..3595d0cef40f 100644 --- a/src/atsam/Makefile +++ b/src/atsam/Makefile @@ -20,7 +20,7 @@ CFLAGS-$(CONFIG_MACH_SAM4E) += -Ilib/sam4e/include CFLAGS-$(CONFIG_MACH_SAME70) += -Ilib/same70b/include CFLAGS += $(CFLAGS-y) -D__$(MCU)__ -mthumb -Ilib/cmsis-core -Ilib/fast-hash -CFLAGS_klipper.elf += --specs=nano.specs --specs=nosys.specs +CFLAGS_klipper.elf += -nostdlib -lgcc -lc_nano CFLAGS_klipper.elf += -T $(OUT)src/generic/armcm_link.ld $(OUT)klipper.elf: $(OUT)src/generic/armcm_link.ld diff --git a/src/atsamd/Makefile b/src/atsamd/Makefile index d241cd8cbd23..8b9722b629fd 100644 --- a/src/atsamd/Makefile +++ b/src/atsamd/Makefile @@ -14,7 +14,7 @@ CFLAGS-$(CONFIG_MACH_SAME51) += -Ilib/same51/include CFLAGS-$(CONFIG_MACH_SAMX5) += -mcpu=cortex-m4 -Ilib/same54/include CFLAGS += $(CFLAGS-y) -D__$(MCU)__ -mthumb -Ilib/cmsis-core -Ilib/fast-hash -CFLAGS_klipper.elf += --specs=nano.specs --specs=nosys.specs +CFLAGS_klipper.elf += -nostdlib -lgcc -lc_nano CFLAGS_klipper.elf += -T $(OUT)src/generic/armcm_link.ld $(OUT)klipper.elf: $(OUT)src/generic/armcm_link.ld diff --git a/src/avr/Kconfig b/src/avr/Kconfig index 2f59d4a1e52d..80e4208478f1 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 || MACH_atmega328 || MACH_atmega328p + select HAVE_LIMITED_CODE_SIZE if MACH_atmega168 || MACH_atmega328 || MACH_atmega328p || MACH_atmega32u4 config BOARD_DIRECTORY string diff --git a/src/command.c b/src/command.c index 39c09458b0f5..d2d05aff9378 100644 --- a/src/command.c +++ b/src/command.c @@ -1,6 +1,6 @@ // Code for parsing incoming commands and encoding outgoing messages // -// Copyright (C) 2016,2017 Kevin O'Connor +// Copyright (C) 2016-2024 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -69,6 +69,28 @@ parse_int(uint8_t **pp) return v; } +// Write an encoded msgid (optimized 2-byte VLQ encoder) +static uint8_t * +encode_msgid(uint8_t *p, uint_fast16_t encoded_msgid) +{ + if (encoded_msgid >= 0x80) + *p++ = (encoded_msgid >> 7) | 0x80; + *p++ = encoded_msgid & 0x7f; + return p; +} + +// Parse an encoded msgid (optimized 2-byte parser, return as positive number) +uint_fast16_t +command_parse_msgid(uint8_t **pp) +{ + uint8_t *p = *pp; + uint_fast16_t encoded_msgid = *p++; + if (encoded_msgid & 0x80) + encoded_msgid = ((encoded_msgid & 0x7f) << 7) | (*p++); + *pp = p; + return encoded_msgid; +} + // Parse an incoming command into 'args' uint8_t * command_parsef(uint8_t *p, uint8_t *maxend @@ -119,7 +141,7 @@ command_encodef(uint8_t *buf, const struct command_encoder *ce, va_list args) uint8_t *maxend = &p[max_size - MESSAGE_MIN]; uint_fast8_t num_params = READP(ce->num_params); const uint8_t *param_types = READP(ce->param_types); - *p++ = READP(ce->msg_id); + p = encode_msgid(p, READP(ce->encoded_msgid)); while (num_params--) { if (p > maxend) goto error; @@ -227,7 +249,7 @@ DECL_SHUTDOWN(sendf_shutdown); // Find the command handler associated with a command static const struct command_parser * -command_lookup_parser(uint_fast8_t cmdid) +command_lookup_parser(uint_fast16_t cmdid) { if (!cmdid || cmdid >= READP(command_index_size)) shutdown("Invalid command"); @@ -309,7 +331,7 @@ command_dispatch(uint8_t *buf, uint_fast8_t msglen) uint8_t *p = &buf[MESSAGE_HEADER_SIZE]; uint8_t *msgend = &buf[msglen-MESSAGE_TRAILER_SIZE]; while (p < msgend) { - uint_fast8_t cmdid = *p++; + uint_fast16_t cmdid = command_parse_msgid(&p); const struct command_parser *cp = command_lookup_parser(cmdid); uint32_t args[READP(cp->num_args)]; p = command_parsef(p, msgend, cp, args); diff --git a/src/command.h b/src/command.h index 894114d71ed6..21b3f79b8ecf 100644 --- a/src/command.h +++ b/src/command.h @@ -57,11 +57,13 @@ #define MESSAGE_SYNC 0x7E struct command_encoder { - uint8_t msg_id, max_size, num_params; + uint16_t encoded_msgid; + uint8_t max_size, num_params; const uint8_t *param_types; }; struct command_parser { - uint8_t msg_id, num_args, flags, num_params; + uint16_t encoded_msgid; + uint8_t num_args, flags, num_params; const uint8_t *param_types; void (*func)(uint32_t *args); }; @@ -72,6 +74,7 @@ enum { // command.c void *command_decode_ptr(uint32_t v); +uint_fast16_t command_parse_msgid(uint8_t **pp); uint8_t *command_parsef(uint8_t *p, uint8_t *maxend , const struct command_parser *cp, uint32_t *args); uint_fast8_t command_encode_and_frame( @@ -86,7 +89,7 @@ int_fast8_t command_find_and_dispatch(uint8_t *buf, uint_fast8_t buf_len // out/compile_time_request.c (auto generated file) extern const struct command_parser command_index[]; -extern const uint8_t command_index_size; +extern const uint16_t command_index_size; extern const uint8_t command_identify_data[]; extern const uint32_t command_identify_size; const struct command_encoder *ctr_lookup_encoder(const char *str); diff --git a/src/generic/armcm_boot.c b/src/generic/armcm_boot.c index f83ca60de0ff..9d2ce0bbfe5e 100644 --- a/src/generic/armcm_boot.c +++ b/src/generic/armcm_boot.c @@ -22,7 +22,31 @@ extern uint32_t _stack_end; * Basic interrupt handlers ****************************************************************/ -static void __noreturn +// Inlined version of memset (to avoid function calls during intial boot code) +static void __always_inline +boot_memset(void *s, int c, size_t n) +{ + volatile uint32_t *p = s; + while (n) { + *p++ = c; + n -= sizeof(*p); + } +} + +// Inlined version of memcpy (to avoid function calls during intial boot code) +static void __always_inline +boot_memcpy(void *dest, const void *src, size_t n) +{ + const uint32_t *s = src; + volatile uint32_t *d = dest; + while (n) { + *d++ = *s++; + n -= sizeof(*d); + } +} + +// Main initialization code (called from ResetHandler below) +static void __noreturn __section(".text.armcm_boot.stage_two") reset_handler_stage_two(void) { int i; @@ -60,10 +84,10 @@ reset_handler_stage_two(void) // Copy global variables from flash to ram uint32_t count = (&_data_end - &_data_start) * 4; - __builtin_memcpy(&_data_start, &_data_flash, count); + boot_memcpy(&_data_start, &_data_flash, count); // Clear the bss segment - __builtin_memset(&_bss_start, 0, (&_bss_end - &_bss_start) * 4); + boot_memset(&_bss_start, 0, (&_bss_end - &_bss_start) * 4); barrier(); @@ -80,7 +104,7 @@ reset_handler_stage_two(void) // Initial code entry point - invoked by the processor after a reset // Reset interrupts and stack to take control from bootloaders -void +void __section(".text.armcm_boot.stage_one") ResetHandler(void) { __disable_irq(); diff --git a/src/generic/armcm_link.lds.S b/src/generic/armcm_link.lds.S index 2f789f1301e3..94dd2100d596 100644 --- a/src/generic/armcm_link.lds.S +++ b/src/generic/armcm_link.lds.S @@ -69,5 +69,8 @@ SECTIONS // that isn't needed so no need to include them in the binary. *(.init) *(.fini) + // Don't include exception tables + *(.ARM.extab) + *(.ARM.exidx) } } diff --git a/src/hc32f460/Makefile b/src/hc32f460/Makefile index c44267369362..85d2fa19d692 100644 --- a/src/hc32f460/Makefile +++ b/src/hc32f460/Makefile @@ -7,7 +7,7 @@ dirs-y += src/hc32f460 src/generic lib/hc32f460/driver/src lib/hc32f460/mcu/comm CFLAGS += -mthumb -mcpu=cortex-m4 -Isrc/hc32f460 -Ilib/hc32f460/driver/inc -Ilib/hc32f460/mcu/common -Ilib/cmsis-core -DHC32F460 -CFLAGS_klipper.elf += --specs=nano.specs --specs=nosys.specs +CFLAGS_klipper.elf += -nostdlib -lgcc -lc_nano CFLAGS_klipper.elf += -T $(OUT)src/generic/armcm_link.ld $(OUT)klipper.elf: $(OUT)src/generic/armcm_link.ld diff --git a/src/hc32f460/gpio.c b/src/hc32f460/gpio.c index e3b98df443f2..a1144927005a 100644 --- a/src/hc32f460/gpio.c +++ b/src/hc32f460/gpio.c @@ -18,7 +18,8 @@ DECL_ENUMERATION_RANGE("pin", "PA0", GPIO('A', 0), 16); DECL_ENUMERATION_RANGE("pin", "PB0", GPIO('B', 0), 16); DECL_ENUMERATION_RANGE("pin", "PC0", GPIO('C', 0), 16); -DECL_ENUMERATION_RANGE("pin", "PD2", GPIO('D', 2), 1); +DECL_ENUMERATION_RANGE("pin", "PD0", GPIO('D', 0), 16); +DECL_ENUMERATION_RANGE("pin", "PE0", GPIO('E', 0), 16); DECL_ENUMERATION_RANGE("pin", "PH2", PortH * 16 + 2, 1); // H: special case diff --git a/src/hc32f460/hard_pwm.c b/src/hc32f460/hard_pwm.c index 9d3387e604d6..8cf57acf7ed7 100644 --- a/src/hc32f460/hard_pwm.c +++ b/src/hc32f460/hard_pwm.c @@ -75,6 +75,22 @@ static const struct gpio_pwm_info pwm_mapping[] = { {GPIO('C',14), 4, TimeraCh5}, {GPIO('C',15), 4, TimeraCh6}, {GPIO('D', 2), 2, TimeraCh4}, + {GPIO('D',12), 4, TimeraCh1}, + {GPIO('D',13), 4, TimeraCh2}, + {GPIO('D',14), 4, TimeraCh3}, + {GPIO('D',15), 4, TimeraCh4}, + {GPIO('E', 2), 3, TimeraCh5}, + {GPIO('E', 3), 3, TimeraCh6}, + {GPIO('E', 4), 3, TimeraCh7}, + {GPIO('E', 5), 3, TimeraCh8}, + {GPIO('E', 8), 1, TimeraCh5}, + {GPIO('E', 9), 1, TimeraCh1}, + {GPIO('E',10), 1, TimeraCh6}, + {GPIO('E',11), 1, TimeraCh2}, + {GPIO('E',12), 1, TimeraCh7}, + {GPIO('E',13), 1, TimeraCh3}, + {GPIO('E',14), 1, TimeraCh4}, + {GPIO('E',15), 1, TimeraCh8}, }; diff --git a/src/linux/gpio.c b/src/linux/gpio.c index bb07f5a07a9f..c7f4c5bf66c2 100644 --- a/src/linux/gpio.c +++ b/src/linux/gpio.c @@ -10,7 +10,7 @@ #include // memset #include // ioctl #include // close -#include // GPIOHANDLE_REQUEST_OUTPUT +#include // GPIOHANDLE_REQUEST_OUTPUT #include "command.h" // shutdown #include "gpio.h" // gpio_out_write #include "internal.h" // report_errno diff --git a/src/linux/i2c.c b/src/linux/i2c.c index 2994403f2161..b328dc56eaa6 100644 --- a/src/linux/i2c.c +++ b/src/linux/i2c.c @@ -14,7 +14,7 @@ #include "internal.h" // report_errno #include "sched.h" // sched_shutdown -DECL_ENUMERATION_RANGE("i2c_bus", "i2c.0", 0, 7); +DECL_ENUMERATION_RANGE("i2c_bus", "i2c.0", 0, 15); struct i2c_s { uint32_t bus; diff --git a/src/linux/main.c b/src/linux/main.c index f9ea3f6daaa8..b260f162b800 100644 --- a/src/linux/main.c +++ b/src/linux/main.c @@ -4,7 +4,7 @@ // // This file may be distributed under the terms of the GNU GPLv3 license. -#include // sched_setscheduler sched_get_priority_max +#include // sched_setscheduler sched_get_priority_max #include // fprintf #include // memset #include // getopt diff --git a/src/lpc176x/Makefile b/src/lpc176x/Makefile index 7ed80b26bca1..6814969c5db7 100644 --- a/src/lpc176x/Makefile +++ b/src/lpc176x/Makefile @@ -7,7 +7,7 @@ dirs-y += src/lpc176x src/generic lib/lpc176x/device CFLAGS += -mthumb -mcpu=cortex-m3 -Ilib/lpc176x/device -Ilib/cmsis-core -CFLAGS_klipper.elf += --specs=nano.specs --specs=nosys.specs +CFLAGS_klipper.elf += -nostdlib -lgcc -lc_nano CFLAGS_klipper.elf += -T $(OUT)src/generic/armcm_link.ld $(OUT)klipper.elf: $(OUT)src/generic/armcm_link.ld diff --git a/src/pru/Makefile b/src/pru/Makefile index 5fea9e1767f7..c304219ece4a 100644 --- a/src/pru/Makefile +++ b/src/pru/Makefile @@ -27,7 +27,11 @@ target-y += $(OUT)pru0.elf $(OUT)pru1.elf $(OUT)pru0.elf: $(patsubst %.c, $(OUT)src/%.o,$(pru0-y)) @echo " Linking $@" $(Q)$(CC) $(CFLAGS_klipper.elf) $^ -o $(OUT)pru0.o + # dumping info about INTC + $(Q)$(OBJCOPY) --dump-section '.pru_irq_map'=$(OUT)pru0.pru_irq_map.bin $(OUT)src/pru/pru0.o $(OUT)pru0.o.discard $(Q)$(CC) $(CFLAGS_pru0.elf) $(OUT)pru0.o -o $@ + # in previous step linker did loose info about INTC during optimization, restoring it here (P.S. pru.lds is not helping) + $(Q)$(OBJCOPY) --add-section '.pru_irq_map'=$(OUT)pru0.pru_irq_map.bin $@ $(OUT)pru1.elf: $(OUT)klipper.elf @echo " Linking $@" diff --git a/src/pru/gpio.c b/src/pru/gpio.c index 2f77a071a2c5..43effa28a179 100644 --- a/src/pru/gpio.c +++ b/src/pru/gpio.c @@ -20,7 +20,7 @@ #define GPIO2BIT(PIN) (1<<((PIN) % 32)) struct gpio_regs { - uint32_t pad_0[77]; + uint32_t pad_0[77]; // 77*4=308=134h volatile uint32_t oe; volatile uint32_t datain; volatile uint32_t dataout; diff --git a/src/pru/intc_map_0.h b/src/pru/intc_map_0.h new file mode 100644 index 000000000000..a89729927b66 --- /dev/null +++ b/src/pru/intc_map_0.h @@ -0,0 +1,62 @@ +#ifndef _INTC_MAP_0_H_ +#define _INTC_MAP_0_H_ __attribute__((used, section(".pru_irq_map"))) + +/* + * ======== PRU INTC Map ======== + * + * Define the INTC mapping for interrupts going to the ICSS / ICSSG: + * ICSS Host interrupts 0, 1 + * ICSSG Host interrupts 0, 1, 10-19 + * + * Note that INTC interrupts going to the ARM Linux host should not be defined + * in this file (ICSS/ICSSG Host interrupts 2-9). + * + * The INTC configuration for interrupts going to the ARM host should be defined + * in the device tree node of the client driver, "interrupts" property. + * See Documentation/devicetree/bindings/interrupt-controller/ti,pruss-intc.yaml + * entry #interrupt-cells for more. + * + * For example, on ICSSG: + * + * &client_driver0 { + * interrupt-parent = <&icssg0_intc>; + * interrupts = <21 2 2>, <22 3 3>; + * interrupt-names = "interrupt_name1", "interrupt_name2"; + * }; + * + */ + +#include + +/* + * .pru_irq_map is used by the RemoteProc driver during initialization. However, + * the map is NOT used by the PRU firmware. That means DATA_SECTION and RETAIN + * are required to prevent the PRU compiler from optimizing out .pru_irq_map. + */ + +#if !defined(__GNUC__) + #pragma DATA_SECTION(my_irq_rsc, ".pru_irq_map") + #pragma RETAIN(my_irq_rsc) + #define __pru_irq_map +#else + #define __pru_irq_map __attribute__((section(".pru_irq_map"), \ + unavailable("pru_irq_map is for usage by the host only"))) +#endif + +struct pru_irq_rsc my_irq_rsc __pru_irq_map = { + 0, /* type = 0 */ + 4, /* number of system events being mapped */ + { +// this item (sysevt=16) is obsolete in Linux 5.10 +// {16, 2, 2}, /* {sysevt, channel, host interrupt} */ + {17, 0, 0}, /* {sysevt, channel, host interrupt} */ + {18, 0, 0}, /* {sysevt, channel, host interrupt} */ + {19, 1, 1}, /* {sysevt, channel, host interrupt} */ +// next item is responsible for timer to function, +// it should be kept last in Linux 5.10 +// (if it will be first - gpios will not work) + {7, 1, 1}, /* {sysevt, channel, host interrupt} */ + }, +}; + +#endif /* _INTC_MAP_0_H_ */ diff --git a/src/pru/internal.h b/src/pru/internal.h index 2aef0da3c624..f735f80af950 100644 --- a/src/pru/internal.h +++ b/src/pru/internal.h @@ -15,6 +15,12 @@ #define WAKE_PRU1_IRQ 1 #define WAKE_ARM_IRQ 2 +/* Host-0 Interrupt sets bit 31 in register R31 */ +#define HOST_INT_0 ((uint32_t) 1 << 30) + +/* Host-1 Interrupt sets bit 31 in register R31 */ +#define HOST_INT_1 ((uint32_t) 1 << 31) + #define R31_IRQ_OFFSET 30 #define R31_WRITE_IRQ_SELECT (1<<5) diff --git a/src/pru/main.c b/src/pru/main.c index 27634a12b525..2127479f4b60 100644 --- a/src/pru/main.c +++ b/src/pru/main.c @@ -7,7 +7,7 @@ #include // uint32_t #include // read_r31 #include // CT_IEP -#include // CT_INTC + #include // resource_table #include "board/misc.h" // dynmem_start #include "board/io.h" // readl @@ -75,7 +75,7 @@ timer_kick(void) timer_set(timer_read_time() + 50); CT_IEP.TMR_CMP_STS = 0xff; __delay_cycles(4); - CT_INTC.SECR0 = 1 << IEP_EVENT; + PRU_INTC.SECR0 = 1 << IEP_EVENT; } static uint32_t in_timer_dispatch; @@ -83,9 +83,9 @@ static uint32_t in_timer_dispatch; static void _irq_poll(void) { - uint32_t secr0 = CT_INTC.SECR0; + uint32_t secr0 = PRU_INTC.SECR0; if (secr0 & (1 << KICK_PRU1_EVENT)) { - CT_INTC.SECR0 = 1 << KICK_PRU1_EVENT; + PRU_INTC.SECR0 = 1 << KICK_PRU1_EVENT; sched_wake_tasks(); } if (secr0 & (1 << IEP_EVENT)) { @@ -93,7 +93,7 @@ _irq_poll(void) in_timer_dispatch = 1; uint32_t next = timer_dispatch_many(); timer_set(next); - CT_INTC.SECR0 = 1 << IEP_EVENT; + PRU_INTC.SECR0 = 1 << IEP_EVENT; in_timer_dispatch = 0; } } @@ -148,7 +148,7 @@ console_sendf(const struct command_encoder *ce, va_list args) SHARED_MEM->next_encoder_args = args; writel(&SHARED_MEM->next_encoder, (uint32_t)ce); - // Signal PRU0 to transmit message + // Signal PRU0 to transmit message - 20 | (18-16) = 22 = 0010 0010 write_r31(R31_WRITE_IRQ_SELECT | (KICK_PRU0_EVENT - R31_WRITE_IRQ_OFFSET)); uint32_t itd = in_timer_dispatch; while (readl(&SHARED_MEM->next_encoder)) @@ -197,25 +197,6 @@ dynmem_end(void) return (void*)(8*1024 - STACK_SIZE); } - -/**************************************************************** - * Resource table - ****************************************************************/ - -struct my_resource_table { - struct resource_table base; - - uint32_t offset[1]; /* Should match 'num' in actual definition */ -} resourceTable __visible __section(".resource_table") = { - { - 1, /* Resource table version: only version 1 is - * supported by the current driver */ - 0, /* number of entries in the table */ - { 0, 0 }, /* reserved, must be zero */ - }, -}; - - /**************************************************************** * Startup ****************************************************************/ diff --git a/src/pru/pru0.c b/src/pru/pru0.c index efede04466f0..8a11e14027a8 100644 --- a/src/pru/pru0.c +++ b/src/pru/pru0.c @@ -8,17 +8,16 @@ #include // uint32_t #include // memset #include // write_r31 -#include // CT_CFG -#include // CT_INTC #include // pru_rpmsg_send #include // VIRTIO_ID_RPMSG -#include // resource_table #include "board/io.h" // readl #include "board/misc.h" // console_sendf #include "command.h" // command_encode_add_frame #include "compiler.h" // __section #include "internal.h" // SHARED_MEM #include "sched.h" // sched_shutdown +#include "intc_map_0.h" +#include "resource_table.h" struct pru_rpmsg_transport transport; static uint16_t transport_dst; @@ -32,8 +31,7 @@ static uint16_t transport_dst; #define CHAN_DESC "Channel 30" #define CHAN_PORT 30 -#define RPMSG_HDR_SIZE 16 -static uint8_t transmit_buf[RPMSG_BUF_SIZE - RPMSG_HDR_SIZE]; +static uint8_t transmit_buf[RPMSG_MESSAGE_SIZE]; static int transmit_pos; // Transmit all pending message blocks @@ -143,7 +141,7 @@ do_dispatch(uint8_t *buf, uint32_t msglen) uint8_t *msgend = &buf[msglen-MESSAGE_TRAILER_SIZE]; while (p < msgend) { // Parse command - uint_fast8_t cmdid = *p++; + uint_fast16_t cmdid = command_parse_msgid(&p); const struct command_parser *cp = &SHARED_MEM->command_index[cmdid]; if (!cmdid || cmdid >= SHARED_MEM->command_index_size || cp->num_args > ARRAY_SIZE(SHARED_MEM->next_command_args)) { @@ -195,7 +193,7 @@ static void process_io(void) { for (;;) { - CT_INTC.SECR0 = ((1 << KICK_PRU0_FROM_ARM_EVENT) + PRU_INTC.SECR0 = ((1 << KICK_PRU0_FROM_ARM_EVENT) | (1 << KICK_PRU0_EVENT)); check_can_send(); int can_sleep = check_can_read(); @@ -251,112 +249,6 @@ console_sendf(const struct command_encoder *ce, va_list args) } -/**************************************************************** - * Resource table - ****************************************************************/ - -/* - * Sizes of the virtqueues (expressed in number of buffers supported, - * and must be power of 2) - */ -#define PRU_RPMSG_VQ0_SIZE 16 -#define PRU_RPMSG_VQ1_SIZE 16 - -/* - * The feature bitmap for virtio rpmsg - */ -#define VIRTIO_RPMSG_F_NS 0 //name service notifications - -/* This firmware supports name service notifications as one of its features */ -#define RPMSG_PRU_C0_FEATURES (1 << VIRTIO_RPMSG_F_NS) - -/* Definition for unused interrupts */ -#define HOST_UNUSED 255 - -/* Mapping sysevts to a channel. Each pair contains a sysevt, channel. */ -static struct ch_map pru_intc_map[] = { - {IEP_EVENT, WAKE_PRU1_IRQ}, - {KICK_ARM_EVENT, WAKE_ARM_IRQ}, - {KICK_PRU0_FROM_ARM_EVENT, WAKE_PRU0_IRQ}, - {KICK_PRU0_EVENT, WAKE_PRU0_IRQ}, - {KICK_PRU1_EVENT, WAKE_PRU1_IRQ}, -}; - -struct my_resource_table { - struct resource_table base; - - uint32_t offset[2]; /* Should match 'num' in actual definition */ - - /* rpmsg vdev entry */ - struct fw_rsc_vdev rpmsg_vdev; - struct fw_rsc_vdev_vring rpmsg_vring0; - struct fw_rsc_vdev_vring rpmsg_vring1; - - /* intc definition */ - struct fw_rsc_custom pru_ints; -} resourceTable __section(".resource_table") = { - { - 1, /* Resource table version: only version 1 is - * supported by the current driver */ - 2, /* number of entries in the table */ - { 0, 0 }, /* reserved, must be zero */ - }, - /* offsets to entries */ - { - offsetof(struct my_resource_table, rpmsg_vdev), - offsetof(struct my_resource_table, pru_ints), - }, - - /* rpmsg vdev entry */ - { - (uint32_t)TYPE_VDEV, //type - (uint32_t)VIRTIO_ID_RPMSG, //id - (uint32_t)0, //notifyid - (uint32_t)RPMSG_PRU_C0_FEATURES,//dfeatures - (uint32_t)0, //gfeatures - (uint32_t)0, //config_len - (uint8_t)0, //status - (uint8_t)2, //num_of_vrings, only two is supported - {(uint8_t)0, (uint8_t)0 }, //reserved - /* no config data */ - }, - /* the two vrings */ - { - 0, //da, will be populated by host, can't pass it in - 16, //align (bytes), - PRU_RPMSG_VQ0_SIZE, //num of descriptors - 0, //notifyid, will be populated, can't pass right now - 0 //reserved - }, - { - 0, //da, will be populated by host, can't pass it in - 16, //align (bytes), - PRU_RPMSG_VQ1_SIZE, //num of descriptors - 0, //notifyid, will be populated, can't pass right now - 0 //reserved - }, - - { - TYPE_CUSTOM, TYPE_PRU_INTS, - sizeof(struct fw_rsc_custom_ints), - { /* PRU_INTS version */ - { - 0x0000, - /* Channel-to-host mapping, 255 for unused */ - { - WAKE_PRU0_IRQ, WAKE_PRU1_IRQ, WAKE_ARM_IRQ, - HOST_UNUSED, HOST_UNUSED, HOST_UNUSED, - HOST_UNUSED, HOST_UNUSED, HOST_UNUSED, HOST_UNUSED - }, - /* Number of evts being mapped to channels */ - (sizeof(pru_intc_map) / sizeof(struct ch_map)), - /* Pointer to the structure containing mapped events */ - pru_intc_map, - }, - }, - }, -}; - /**************************************************************** * Startup @@ -368,11 +260,26 @@ int main(void) { // allow access to external memory +#if defined(__AM335X__) + /* AM335x must enable OCP master port access in order for the PRU to + * read external memories.*/ CT_CFG.SYSCFG_bit.STANDBY_INIT = 0; +#endif + + /* Clear the status of the PRU-ICSS system event that the ARM + will use to 'kick' us */ +#if defined(__AM335X__) + PRU_INTC.SICR_bit.STS_CLR_IDX = KICK_PRU0_FROM_ARM_EVENT; +#elif defined(__TDA4VM__) || defined(__AM62X__) + PRU_INTC.STATUS_CLR_INDEX_REG_bit.STATUS_CLR_INDEX = \ + KICK_PRU0_FROM_ARM_EVENT; +#else + #error "Unsupported SoC." +#endif // clear all irqs - CT_INTC.SECR0 = 0xffffffff; - CT_INTC.SECR1 = 0xffffffff; + PRU_INTC.SECR0 = 0xffffffff; + PRU_INTC.SECR1 = 0xffffffff; /* Make sure the Linux drivers are ready for RPMsg communication */ volatile uint8_t *status = &resourceTable.rpmsg_vdev.status; diff --git a/src/pru/resource_table.h b/src/pru/resource_table.h new file mode 100644 index 000000000000..9dff2b9c8541 --- /dev/null +++ b/src/pru/resource_table.h @@ -0,0 +1,91 @@ +/**************************************************************** + * Resource table + ****************************************************************/ + +#ifndef _RESOURCE_TABLE_H_ +#define _RESOURCE_TABLE_H_ + +#include +#include "pru_virtio_ids.h" + +/* + * Sizes of the virtqueues (expressed in number of buffers supported, + * and must be power of 2) + */ +#define PRU_RPMSG_VQ0_SIZE 16 +#define PRU_RPMSG_VQ1_SIZE 16 + +/* + * The feature bitmap for virtio rpmsg + */ +#define VIRTIO_RPMSG_F_NS 0 //name service notifications + +/* This firmware supports name service notifications as one of its features */ +#define RPMSG_PRU_C0_FEATURES (1 << VIRTIO_RPMSG_F_NS) + +/* Definition for unused interrupts */ +#define HOST_UNUSED 255 + +struct my_resource_table { + struct resource_table base; + + uint32_t offset[1]; /* Should match 'num' in actual definition */ + + /* rpmsg vdev entry */ + struct fw_rsc_vdev rpmsg_vdev; + struct fw_rsc_vdev_vring rpmsg_vring0; + struct fw_rsc_vdev_vring rpmsg_vring1; + +}; + +#if !defined(__GNUC__) + #pragma DATA_SECTION(resourceTable, ".resource_table") + #pragma RETAIN(resourceTable) + #define __resource_table /* */ +#else + #define __resource_table __attribute__((section(".resource_table"))) +#endif + +struct my_resource_table resourceTable __resource_table = { + { + 1, /* Resource table version: only version 1 is + * supported by the current driver */ + 1, /* number of entries in the table */ + {0, 0}, /* reserved, must be zero */ + }, + /* offsets to entries */ + { + offsetof(struct my_resource_table, rpmsg_vdev), + }, + + /* rpmsg vdev entry */ + { + (uint32_t)TYPE_VDEV, //type + (uint32_t)VIRTIO_ID_RPMSG, //id + (uint32_t)0, //notifyid + (uint32_t)RPMSG_PRU_C0_FEATURES,//dfeatures + (uint32_t)0, //gfeatures + (uint32_t)0, //config_len + (uint8_t)0, //status + (uint8_t)2, //num_of_vrings, only two is supported + {(uint8_t)0, (uint8_t)0 }, //reserved + /* no config data */ + }, + /* the two vrings */ + { + FW_RSC_ADDR_ANY, //da, will be populated by host, can't pass it in + 16, //align (bytes), + PRU_RPMSG_VQ0_SIZE, //num of descriptors + 0, //notifyid, will be populated, can't pass right now + 0 //reserved + }, + { + FW_RSC_ADDR_ANY, //da, will be populated by host, can't pass it in + 16, //align (bytes), + PRU_RPMSG_VQ1_SIZE, //num of descriptors + 0, //notifyid, will be populated, can't pass right now + 0 //reserved + }, +}; + +#endif /* _RESOURCE_TABLE_H_ */ diff --git a/src/rp2040/Kconfig b/src/rp2040/Kconfig index 59891649e6b0..ec8a1af3205d 100644 --- a/src/rp2040/Kconfig +++ b/src/rp2040/Kconfig @@ -95,19 +95,40 @@ config RP2040_STAGE2_CLKDIV ###################################################################### choice - prompt "Communication interface" - config RP2040_USB - bool "USB" - select USBSERIAL - config RP2040_SERIAL_UART0 - bool "Serial (on UART0 GPIO1/GPIO0)" - select SERIAL - config RP2040_CANBUS - bool "CAN bus" - select CANSERIAL - config RP2040_USBCANBUS - bool "USB to CAN bus bridge" - select USBCANBUS + prompt "Communication Interface" + config RP2040_USB + bool "USBSERIAL" + select USBSERIAL + config RP2040_SERIAL_UART0_PINS_0_1 + bool "UART0 on GPIO0/GPIO1" + select SERIAL + config RP2040_SERIAL_UART0_PINS_12_13 + bool "UART0 on GPIO12/GPIO13" if LOW_LEVEL_OPTIONS + select SERIAL + config RP2040_SERIAL_UART0_PINS_16_17 + bool "UART0 on GPIO16/GPIO17" if LOW_LEVEL_OPTIONS + select SERIAL + config RP2040_SERIAL_UART0_PINS_28_29 + bool "UART0 on GPIO28/GPIO29" if LOW_LEVEL_OPTIONS + select SERIAL + config RP2040_SERIAL_UART1_PINS_4_5 + bool "UART1 on GPIO4/GPIO5" if LOW_LEVEL_OPTIONS + select SERIAL + config RP2040_SERIAL_UART1_PINS_8_9 + bool "UART1 on GPIO8/GPIO9" if LOW_LEVEL_OPTIONS + select SERIAL + config RP2040_SERIAL_UART1_PINS_20_21 + bool "UART1 on GPIO20/GPIO21" if LOW_LEVEL_OPTIONS + select SERIAL + config RP2040_SERIAL_UART1_PINS_24_25 + bool "UART1 on GPIO24/GPIO25" if LOW_LEVEL_OPTIONS + select SERIAL + config RP2040_CANBUS + bool "CAN bus" + select CANSERIAL + config RP2040_USBCANBUS + bool "USB to CAN bus bridge" + select USBCANBUS endchoice config RP2040_CANBUS_GPIO_RX diff --git a/src/rp2040/Makefile b/src/rp2040/Makefile index 71ed90a0cc0a..b82503a39669 100644 --- a/src/rp2040/Makefile +++ b/src/rp2040/Makefile @@ -46,7 +46,6 @@ $(OUT)klipper.uf2: $(OUT)klipper.elf $(OUT)lib/rp2040/elf2uf2/elf2uf2 $(Q)$(OUT)lib/rp2040/elf2uf2/elf2uf2 $< $@ rptarget-$(CONFIG_RP2040_HAVE_STAGE2) := $(OUT)klipper.uf2 -rplink-$(CONFIG_RP2040_HAVE_STAGE2) := $(OUT)src/rp2040/rp2040_link.ld stage2-$(CONFIG_RP2040_HAVE_STAGE2) := $(OUT)stage2.o # rp2040 building when using a bootloader @@ -55,13 +54,13 @@ $(OUT)klipper.bin: $(OUT)klipper.elf $(Q)$(OBJCOPY) -O binary $< $@ rptarget-$(CONFIG_RP2040_HAVE_BOOTLOADER) := $(OUT)klipper.bin -rplink-$(CONFIG_RP2040_HAVE_BOOTLOADER) := $(OUT)src/generic/armcm_link.ld # Set klipper.elf linker rules target-y += $(rptarget-y) -CFLAGS_klipper.elf += --specs=nano.specs --specs=nosys.specs -T $(rplink-y) +CFLAGS_klipper.elf += -nostdlib -lgcc -lc_nano +CFLAGS_klipper.elf += -T $(OUT)src/rp2040/rp2040_link.ld OBJS_klipper.elf += $(stage2-y) -$(OUT)klipper.elf: $(stage2-y) $(rplink-y) +$(OUT)klipper.elf: $(stage2-y) $(OUT)src/rp2040/rp2040_link.ld # Flash rules lib/rp2040_flash/rp2040_flash: diff --git a/src/rp2040/main.c b/src/rp2040/main.c index 0b144d0bba0c..e7b64e5f0da2 100644 --- a/src/rp2040/main.c +++ b/src/rp2040/main.c @@ -16,6 +16,26 @@ #include "sched.h" // sched_main +/**************************************************************** + * Ram IRQ vector table + ****************************************************************/ + +// Copy vector table to ram and activate it +static void +enable_ram_vectortable(void) +{ + // Symbols created by rp2040_link.lds.S linker script + extern uint32_t _ram_vectortable_start, _ram_vectortable_end; + extern uint32_t _text_vectortable_start; + + uint32_t count = (&_ram_vectortable_end - &_ram_vectortable_start) * 4; + __builtin_memcpy(&_ram_vectortable_start, &_text_vectortable_start, count); + barrier(); + + SCB->VTOR = (uint32_t)&_ram_vectortable_start; +} + + /**************************************************************** * Bootloader ****************************************************************/ @@ -145,6 +165,7 @@ clock_setup(void) void armcm_main(void) { + enable_ram_vectortable(); clock_setup(); sched_main(); } diff --git a/src/rp2040/rp2040_link.lds.S b/src/rp2040/rp2040_link.lds.S index 43d6115e4983..abc5be6c5d2f 100644 --- a/src/rp2040/rp2040_link.lds.S +++ b/src/rp2040/rp2040_link.lds.S @@ -1,6 +1,6 @@ // rp2040 linker script (based on armcm_link.lds.S and customized for stage2) // -// Copyright (C) 2019-2021 Kevin O'Connor +// Copyright (C) 2019-2024 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -9,36 +9,61 @@ OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") OUTPUT_ARCH(arm) +#if CONFIG_RP2040_HAVE_STAGE2 + #define ROM_ORIGIN 0x10000000 +#else + #define ROM_ORIGIN CONFIG_FLASH_APPLICATION_ADDRESS +#endif + MEMORY { - rom (rx) : ORIGIN = 0x10000000 , LENGTH = CONFIG_FLASH_SIZE + rom (rx) : ORIGIN = ROM_ORIGIN , LENGTH = CONFIG_FLASH_SIZE ram (rwx) : ORIGIN = CONFIG_RAM_START , LENGTH = CONFIG_RAM_SIZE } +// Force flags for each output section to avoid RWX linker warning +PHDRS +{ + text_segment PT_LOAD FLAGS(5); // RX flags + ram_vectortable_segment PT_LOAD FLAGS(6); // RW flags + data_segment PT_LOAD FLAGS(6); // RW flags + bss_segment PT_LOAD FLAGS(6); // RW flags + stack_segment PT_LOAD FLAGS(6); // RW flags +} + SECTIONS { .text : { . = ALIGN(4); +#if CONFIG_RP2040_HAVE_STAGE2 KEEP(*(.boot2)) +#endif _text_vectortable_start = .; KEEP(*(.vector_table)) _text_vectortable_end = .; - *(.text .text.*) - *(.rodata .rodata*) - } > rom + *(.text.armcm_boot*) + } > rom :text_segment . = ALIGN(4); _data_flash = .; + .ram_vectortable (NOLOAD) : { + _ram_vectortable_start = .; + . = . + ( _text_vectortable_end - _text_vectortable_start ) ; + _ram_vectortable_end = .; + } > ram :ram_vectortable_segment + .data : AT (_data_flash) { . = ALIGN(4); _data_start = .; + *(.text .text.*) *(.ramfunc .ramfunc.*); + *(.rodata .rodata*) *(.data .data.*); . = ALIGN(4); _data_end = .; - } > ram + } > ram :data_segment .bss (NOLOAD) : { @@ -48,19 +73,22 @@ SECTIONS *(COMMON) . = ALIGN(4); _bss_end = .; - } > ram + } > ram :bss_segment _stack_start = CONFIG_RAM_START + CONFIG_RAM_SIZE - CONFIG_STACK_SIZE ; .stack _stack_start (NOLOAD) : { . = . + CONFIG_STACK_SIZE; _stack_end = .; - } > ram + } > ram :stack_segment /DISCARD/ : { // The .init/.fini sections are used by __libc_init_array(), but // that isn't needed so no need to include them in the binary. *(.init) *(.fini) + // Don't include exception tables + *(.ARM.extab) + *(.ARM.exidx) } } diff --git a/src/rp2040/serial.c b/src/rp2040/serial.c index abfdcb8b343b..351a873e55bf 100644 --- a/src/rp2040/serial.c +++ b/src/rp2040/serial.c @@ -4,20 +4,64 @@ // // This file may be distributed under the terms of the GNU GPLv3 license. + #include // uint32_t -#include "autoconf.h" // CONFIG_SERIAL +#include "autoconf.h" // Include configuration header #include "board/armcm_boot.h" // armcm_enable_irq #include "board/irq.h" // irq_save #include "board/serial_irq.h" // serial_rx_data -#include "hardware/structs/resets.h" // RESETS_RESET_UART0_BITS -#include "hardware/structs/uart.h" // UART0_BASE -#include "internal.h" // UART0_IRQn +#include "hardware/structs/resets.h" // RESETS_RESET_UART0/1_BITS +#include "hardware/structs/uart.h" // uart0_hw, uart1_hw +#include "internal.h" // UART0_IRQn, UART1_IRQn #include "sched.h" // DECL_INIT -#define UARTx uart0_hw -#define UARTx_IRQn UART0_IRQ_IRQn -#define GPIO_Rx 1 -#define GPIO_Tx 0 +// Dynamically select UART and IRQ based on configuration + + + #if CONFIG_RP2040_SERIAL_UART0_PINS_0_1 + #define GPIO_Rx 1 + #define GPIO_Tx 0 + #define UARTx uart0_hw + #define UARTx_IRQn UART0_IRQ_IRQn + #elif CONFIG_RP2040_SERIAL_UART0_PINS_12_13 + #define GPIO_Rx 13 + #define GPIO_Tx 12 + #define UARTx uart0_hw + #define UARTx_IRQn UART0_IRQ_IRQn + #elif CONFIG_RP2040_SERIAL_UART0_PINS_16_17 + #define GPIO_Rx 17 + #define GPIO_Tx 16 + #define UARTx uart0_hw + #define UARTx_IRQn UART0_IRQ_IRQn + #elif CONFIG_RP2040_SERIAL_UART0_PINS_28_29 + #define GPIO_Rx 29 + #define GPIO_Tx 28 + #define UARTx uart1_hw + #define UARTx_IRQn UART1_IRQ_IRQn + #define UARTx uart0_hw + #define UARTx_IRQn UART0_IRQ_IRQn + #elif CONFIG_RP2040_SERIAL_UART1_PINS_4_5 + #define GPIO_Rx 5 + #define GPIO_Tx 4 + #define UARTx uart1_hw + #define UARTx_IRQn UART1_IRQ_IRQn + #elif CONFIG_RP2040_SERIAL_UART1_PINS_8_9 + #define GPIO_Rx 9 + #define GPIO_Tx 8 + #define UARTx uart1_hw + #define UARTx_IRQn UART1_IRQ_IRQn + #elif CONFIG_RP2040_SERIAL_UART1_PINS_20_21 + #define GPIO_Rx 20 + #define GPIO_Tx 21 + #define UARTx uart1_hw + #define UARTx_IRQn UART1_IRQ_IRQn + #elif CONFIG_RP2040_SERIAL_UART1_PINS_24_25 + #define GPIO_Rx 24 + #define GPIO_Tx 25 + #define UARTx uart1_hw + #define UARTx_IRQn UART1_IRQ_IRQn + #endif + // Write tx bytes to the serial port static void @@ -67,10 +111,19 @@ serial_enable_tx_irq(void) void serial_init(void) { - enable_pclock(RESETS_RESET_UART0_BITS); + + uint32_t pclk= 0x00; + if (UARTx == uart0_hw){ + enable_pclock(RESETS_RESET_UART0_BITS); + pclk= get_pclock_frequency(RESETS_RESET_UART0_BITS); + } else { + enable_pclock(RESETS_RESET_UART1_BITS); + pclk = get_pclock_frequency(RESETS_RESET_UART1_BITS); + } + // Setup baud - uint32_t pclk = get_pclock_frequency(RESETS_RESET_UART0_BITS); + uint32_t div = DIV_ROUND_CLOSEST(pclk * 4, CONFIG_SERIAL_BAUD); UARTx->ibrd = div >> 6; UARTx->fbrd = div & 0x3f; diff --git a/src/rp2040/spi.c b/src/rp2040/spi.c index e6aafa0053a4..758d57308b79 100644 --- a/src/rp2040/spi.c +++ b/src/rp2040/spi.c @@ -89,8 +89,12 @@ void spi_prepare(struct spi_config config) { spi_hw_t *spi = config.spi; + if (spi->cr0 == config.cr0 && spi->cpsr == config.cpsr) + return; + spi->cr1 = 0; spi->cr0 = config.cr0; spi->cpsr = config.cpsr; + spi->cr1 = SPI_SSPCR1_SSE_BITS; } void diff --git a/src/sensor_adxl345.c b/src/sensor_adxl345.c index 3d80059d0f93..32ce4c65365e 100644 --- a/src/sensor_adxl345.c +++ b/src/sensor_adxl345.c @@ -1,6 +1,6 @@ // Support for gathering acceleration data from ADXL345 chip // -// Copyright (C) 2020 Kevin O'Connor +// Copyright (C) 2020-2023 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -10,19 +10,19 @@ #include "basecmd.h" // oid_alloc #include "command.h" // DECL_COMMAND #include "sched.h" // DECL_TASK +#include "sensor_bulk.h" // sensor_bulk_report #include "spicmds.h" // spidev_transfer struct adxl345 { struct timer timer; uint32_t rest_ticks; struct spidev_s *spi; - uint16_t sequence, limit_count; - uint8_t flags, data_count; - uint8_t data[50]; + uint8_t flags; + struct sensor_bulk sb; }; enum { - AX_HAVE_START = 1<<0, AX_RUNNING = 1<<1, AX_PENDING = 1<<2, + AX_PENDING = 1<<0, }; static struct task_wake adxl345_wake; @@ -47,27 +47,6 @@ command_config_adxl345(uint32_t *args) } DECL_COMMAND(command_config_adxl345, "config_adxl345 oid=%c spi_oid=%c"); -// Report local measurement buffer -static void -adxl_report(struct adxl345 *ax, uint8_t oid) -{ - sendf("adxl345_data oid=%c sequence=%hu data=%*s" - , oid, ax->sequence, ax->data_count, ax->data); - ax->data_count = 0; - ax->sequence++; -} - -// Report buffer and fifo status -static void -adxl_status(struct adxl345 *ax, uint_fast8_t oid - , uint32_t time1, uint32_t time2, uint_fast8_t fifo) -{ - sendf("adxl345_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" - " buffered=%c fifo=%c limit_count=%hu" - , oid, time1, time2-time1, ax->sequence - , ax->data_count, fifo, ax->limit_count); -} - // Helper code to reschedule the adxl345_event() timer static void adxl_reschedule_timer(struct adxl345 *ax) @@ -79,7 +58,6 @@ adxl_reschedule_timer(struct adxl345 *ax) } // Chip registers -#define AR_POWER_CTL 0x2D #define AR_DATAX0 0x32 #define AR_FIFO_STATUS 0x39 #define AM_READ 0x80 @@ -87,6 +65,8 @@ adxl_reschedule_timer(struct adxl345 *ax) #define SET_FIFO_CTL 0x90 +#define BYTES_PER_SAMPLE 5 + // Query accelerometer data static void adxl_query(struct adxl345 *ax, uint8_t oid) @@ -96,7 +76,7 @@ adxl_query(struct adxl345 *ax, uint8_t oid) spidev_transfer(ax->spi, 1, sizeof(msg), msg); // Extract x, y, z measurements uint_fast8_t fifo_status = msg[8] & ~0x80; // Ignore trigger bit - uint8_t *d = &ax->data[ax->data_count]; + uint8_t *d = &ax->sb.data[ax->sb.data_count]; if (((msg[2] & 0xf0) && (msg[2] & 0xf0) != 0xf0) || ((msg[4] & 0xf0) && (msg[4] & 0xf0) != 0xf0) || ((msg[6] & 0xf0) && (msg[6] & 0xf0) != 0xf0) @@ -112,94 +92,56 @@ adxl_query(struct adxl345 *ax, uint8_t oid) d[3] = (msg[2] & 0x1f) | (msg[6] << 5); // x high bits and z high bits d[4] = (msg[4] & 0x1f) | ((msg[6] << 2) & 0x60); // y high and z high } - ax->data_count += 5; - if (ax->data_count + 5 > ARRAY_SIZE(ax->data)) - adxl_report(ax, oid); + ax->sb.data_count += BYTES_PER_SAMPLE; + if (ax->sb.data_count + BYTES_PER_SAMPLE > ARRAY_SIZE(ax->sb.data)) + sensor_bulk_report(&ax->sb, oid); // Check fifo status if (fifo_status >= 31) - ax->limit_count++; - if (fifo_status > 1 && fifo_status <= 32) { + ax->sb.possible_overflows++; + if (fifo_status > 1) { // More data in fifo - wake this task again sched_wake_task(&adxl345_wake); - } else if (ax->flags & AX_RUNNING) { + } else { // Sleep until next check time - sched_del_timer(&ax->timer); ax->flags &= ~AX_PENDING; adxl_reschedule_timer(ax); } } -// Startup measurements -static void -adxl_start(struct adxl345 *ax, uint8_t oid) -{ - sched_del_timer(&ax->timer); - ax->flags = AX_RUNNING; - uint8_t msg[2] = { AR_POWER_CTL, 0x08 }; - spidev_transfer(ax->spi, 0, sizeof(msg), msg); - adxl_reschedule_timer(ax); -} - -// End measurements -static void -adxl_stop(struct adxl345 *ax, uint8_t oid) -{ - // Disable measurements - sched_del_timer(&ax->timer); - ax->flags = 0; - uint8_t msg[2] = { AR_POWER_CTL, 0x00 }; - uint32_t end1_time = timer_read_time(); - spidev_transfer(ax->spi, 0, sizeof(msg), msg); - uint32_t end2_time = timer_read_time(); - // Drain any measurements still in fifo - uint_fast8_t i; - for (i=0; i<33; i++) { - msg[0] = AR_FIFO_STATUS | AM_READ; - msg[1] = 0x00; - spidev_transfer(ax->spi, 1, sizeof(msg), msg); - uint_fast8_t fifo_status = msg[1] & ~0x80; - if (!fifo_status) - break; - if (fifo_status <= 32) - adxl_query(ax, oid); - } - // Report final data - if (ax->data_count) - adxl_report(ax, oid); - adxl_status(ax, oid, end1_time, end2_time, msg[1]); -} - void command_query_adxl345(uint32_t *args) { struct adxl345 *ax = oid_lookup(args[0], command_config_adxl345); - if (!args[2]) { + sched_del_timer(&ax->timer); + ax->flags = 0; + if (!args[1]) // End measurements - adxl_stop(ax, args[0]); return; - } + // Start new measurements query - sched_del_timer(&ax->timer); - ax->timer.waketime = args[1]; - ax->rest_ticks = args[2]; - ax->flags = AX_HAVE_START; - ax->sequence = ax->limit_count = 0; - ax->data_count = 0; - sched_add_timer(&ax->timer); + ax->rest_ticks = args[1]; + sensor_bulk_reset(&ax->sb); + adxl_reschedule_timer(ax); } -DECL_COMMAND(command_query_adxl345, - "query_adxl345 oid=%c clock=%u rest_ticks=%u"); +DECL_COMMAND(command_query_adxl345, "query_adxl345 oid=%c rest_ticks=%u"); void command_query_adxl345_status(uint32_t *args) { struct adxl345 *ax = oid_lookup(args[0], command_config_adxl345); uint8_t msg[2] = { AR_FIFO_STATUS | AM_READ, 0x00 }; + uint32_t time1 = timer_read_time(); spidev_transfer(ax->spi, 1, sizeof(msg), msg); uint32_t time2 = timer_read_time(); - adxl_status(ax, args[0], time1, time2, msg[1]); + + uint_fast8_t fifo_status = msg[1] & ~0x80; // Ignore trigger bit + if (fifo_status > 32) + // Query error - don't send response - host will retry + return; + sensor_bulk_status(&ax->sb, args[0], time1, time2-time1 + , fifo_status * BYTES_PER_SAMPLE); } DECL_COMMAND(command_query_adxl345_status, "query_adxl345_status oid=%c"); @@ -212,11 +154,7 @@ adxl345_task(void) struct adxl345 *ax; foreach_oid(oid, ax, command_config_adxl345) { uint_fast8_t flags = ax->flags; - if (!(flags & AX_PENDING)) - continue; - if (flags & AX_HAVE_START) - adxl_start(ax, oid); - else + if (flags & AX_PENDING) adxl_query(ax, oid); } } diff --git a/src/sensor_angle.c b/src/sensor_angle.c index 4d35aadf1483..54caecc219e7 100644 --- a/src/sensor_angle.c +++ b/src/sensor_angle.c @@ -10,6 +10,7 @@ #include "board/irq.h" // irq_disable #include "command.h" // DECL_COMMAND #include "sched.h" // DECL_TASK +#include "sensor_bulk.h" // sensor_bulk_report #include "spicmds.h" // spidev_transfer enum { SA_CHIP_A1333, SA_CHIP_AS5047D, SA_CHIP_TLE5012B, SA_CHIP_MAX }; @@ -29,15 +30,16 @@ struct spi_angle { struct timer timer; uint32_t rest_ticks; struct spidev_s *spi; - uint16_t sequence; - uint8_t flags, chip_type, data_count, time_shift, overflow; - uint8_t data[48]; + uint8_t flags, chip_type, time_shift, overflow; + struct sensor_bulk sb; }; enum { SA_PENDING = 1<<2, }; +#define BYTES_PER_SAMPLE 3 + static struct task_wake angle_wake; // Event handler that wakes spi_angle_task() periodically @@ -72,32 +74,22 @@ command_config_spi_angle(uint32_t *args) DECL_COMMAND(command_config_spi_angle, "config_spi_angle oid=%c spi_oid=%c spi_angle_type=%c"); -// Report local measurement buffer -static void -angle_report(struct spi_angle *sa, uint8_t oid) -{ - sendf("spi_angle_data oid=%c sequence=%hu data=%*s" - , oid, sa->sequence, sa->data_count, sa->data); - sa->data_count = 0; - sa->sequence++; -} - // Send spi_angle_data message if buffer is full static void angle_check_report(struct spi_angle *sa, uint8_t oid) { - if (sa->data_count + 3 > ARRAY_SIZE(sa->data)) - angle_report(sa, oid); + if (sa->sb.data_count + BYTES_PER_SAMPLE > ARRAY_SIZE(sa->sb.data)) + sensor_bulk_report(&sa->sb, oid); } // Add an entry to the measurement buffer static void angle_add(struct spi_angle *sa, uint_fast8_t tcode, uint_fast16_t data) { - sa->data[sa->data_count] = tcode; - sa->data[sa->data_count + 1] = data; - sa->data[sa->data_count + 2] = data >> 8; - sa->data_count += 3; + sa->sb.data[sa->sb.data_count] = tcode; + sa->sb.data[sa->sb.data_count + 1] = data; + sa->sb.data[sa->sb.data_count + 2] = data >> 8; + sa->sb.data_count += BYTES_PER_SAMPLE; } // Add an error indicator to the measurement buffer @@ -230,18 +222,14 @@ command_query_spi_angle(uint32_t *args) sched_del_timer(&sa->timer); sa->flags = 0; - if (!args[2]) { + if (!args[2]) // End measurements - if (sa->data_count) - angle_report(sa, oid); - sendf("spi_angle_end oid=%c sequence=%hu", oid, sa->sequence); return; - } + // Start new measurements query sa->timer.waketime = args[1]; sa->rest_ticks = args[2]; - sa->sequence = 0; - sa->data_count = 0; + sensor_bulk_reset(&sa->sb); sa->time_shift = args[3]; sched_add_timer(&sa->timer); } diff --git a/src/sensor_bulk.c b/src/sensor_bulk.c new file mode 100644 index 000000000000..9b5c782c5c93 --- /dev/null +++ b/src/sensor_bulk.c @@ -0,0 +1,38 @@ +// Helper code for collecting and sending bulk sensor measurements +// +// Copyright (C) 2020-2023 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include "command.h" // sendf +#include "sensor_bulk.h" // sensor_bulk_report + +// Reset counters +void +sensor_bulk_reset(struct sensor_bulk *sb) +{ + sb->sequence = 0; + sb->possible_overflows = 0; + sb->data_count = 0; +} + +// Report local measurement buffer +void +sensor_bulk_report(struct sensor_bulk *sb, uint8_t oid) +{ + sendf("sensor_bulk_data oid=%c sequence=%hu data=%*s" + , oid, sb->sequence, sb->data_count, sb->data); + sb->data_count = 0; + sb->sequence++; +} + +// Report buffer and fifo status +void +sensor_bulk_status(struct sensor_bulk *sb, uint8_t oid + , uint32_t time1, uint32_t query_ticks, uint32_t fifo) +{ + sendf("sensor_bulk_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" + " buffered=%u possible_overflows=%hu" + , oid, time1, query_ticks, sb->sequence + , sb->data_count + fifo, sb->possible_overflows); +} diff --git a/src/sensor_bulk.h b/src/sensor_bulk.h new file mode 100644 index 000000000000..c750dbdaeded --- /dev/null +++ b/src/sensor_bulk.h @@ -0,0 +1,15 @@ +#ifndef __SENSOR_BULK_H +#define __SENSOR_BULK_H + +struct sensor_bulk { + uint16_t sequence, possible_overflows; + uint8_t data_count; + uint8_t data[51]; +}; + +void sensor_bulk_reset(struct sensor_bulk *sb); +void sensor_bulk_report(struct sensor_bulk *sb, uint8_t oid); +void sensor_bulk_status(struct sensor_bulk *sb, uint8_t oid + , uint32_t time1, uint32_t query_ticks, uint32_t fifo); + +#endif // sensor_bulk.h diff --git a/src/sensor_ldc1612.c b/src/sensor_ldc1612.c new file mode 100644 index 000000000000..01cf3ee04597 --- /dev/null +++ b/src/sensor_ldc1612.c @@ -0,0 +1,251 @@ +// Support for eddy current sensor data from ldc1612 chip +// +// Copyright (C) 2023 Alan.Ma +// Copyright (C) 2024 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include // memcpy +#include "basecmd.h" // oid_alloc +#include "board/gpio.h" // i2c_read +#include "board/irq.h" // irq_disable +#include "board/misc.h" // timer_read_time +#include "command.h" // DECL_COMMAND +#include "i2ccmds.h" // i2cdev_oid_lookup +#include "sched.h" // DECL_TASK +#include "sensor_bulk.h" // sensor_bulk_report +#include "trsync.h" // trsync_do_trigger + +enum { + LDC_PENDING = 1<<0, LDC_HAVE_INTB = 1<<1, + LH_AWAIT_HOMING = 1<<1, LH_CAN_TRIGGER = 1<<2 +}; + +struct ldc1612 { + struct timer timer; + uint32_t rest_ticks; + struct i2cdev_s *i2c; + uint8_t flags; + struct sensor_bulk sb; + struct gpio_in intb_pin; + // homing + struct trsync *ts; + uint8_t homing_flags; + uint8_t trigger_reason, error_reason; + uint32_t trigger_threshold; + uint32_t homing_clock; +}; + +static struct task_wake ldc1612_wake; + +// Check if the intb line is "asserted" +static int +check_intb_asserted(struct ldc1612 *ld) +{ + return !gpio_in_read(ld->intb_pin); +} + +// Query ldc1612 data +static uint_fast8_t +ldc1612_event(struct timer *timer) +{ + struct ldc1612 *ld = container_of(timer, struct ldc1612, timer); + if (ld->flags & LDC_PENDING) + ld->sb.possible_overflows++; + if (!(ld->flags & LDC_HAVE_INTB) || check_intb_asserted(ld)) { + ld->flags |= LDC_PENDING; + sched_wake_task(&ldc1612_wake); + } + ld->timer.waketime += ld->rest_ticks; + return SF_RESCHEDULE; +} + +void +command_config_ldc1612(uint32_t *args) +{ + struct ldc1612 *ld = oid_alloc(args[0], command_config_ldc1612 + , sizeof(*ld)); + ld->timer.func = ldc1612_event; + ld->i2c = i2cdev_oid_lookup(args[1]); +} +DECL_COMMAND(command_config_ldc1612, "config_ldc1612 oid=%c i2c_oid=%c"); + +void +command_config_ldc1612_with_intb(uint32_t *args) +{ + command_config_ldc1612(args); + struct ldc1612 *ld = oid_lookup(args[0], command_config_ldc1612); + ld->intb_pin = gpio_in_setup(args[2], 1); + ld->flags = LDC_HAVE_INTB; +} +DECL_COMMAND(command_config_ldc1612_with_intb, + "config_ldc1612_with_intb oid=%c i2c_oid=%c intb_pin=%c"); + +void +command_ldc1612_setup_home(uint32_t *args) +{ + struct ldc1612 *ld = oid_lookup(args[0], command_config_ldc1612); + + ld->trigger_threshold = args[2]; + if (!ld->trigger_threshold) { + ld->ts = NULL; + ld->homing_flags = 0; + return; + } + ld->homing_clock = args[1]; + ld->ts = trsync_oid_lookup(args[3]); + ld->trigger_reason = args[4]; + ld->error_reason = args[5]; + ld->homing_flags = LH_AWAIT_HOMING | LH_CAN_TRIGGER; +} +DECL_COMMAND(command_ldc1612_setup_home, + "ldc1612_setup_home oid=%c clock=%u threshold=%u" + " trsync_oid=%c trigger_reason=%c error_reason=%c"); + +void +command_query_ldc1612_home_state(uint32_t *args) +{ + struct ldc1612 *ld = oid_lookup(args[0], command_config_ldc1612); + sendf("ldc1612_home_state oid=%c homing=%c trigger_clock=%u" + , args[0], !!(ld->homing_flags & LH_CAN_TRIGGER), ld->homing_clock); +} +DECL_COMMAND(command_query_ldc1612_home_state, + "query_ldc1612_home_state oid=%c"); + +// Check if a sample should trigger a homing event +static void +check_home(struct ldc1612 *ld, uint32_t data) +{ + uint8_t homing_flags = ld->homing_flags; + if (!(homing_flags & LH_CAN_TRIGGER)) + return; + if (data > 0x0fffffff) { + // Sensor reports an issue - cancel homing + ld->homing_flags = 0; + trsync_do_trigger(ld->ts, ld->error_reason); + return; + } + uint32_t time = timer_read_time(); + if ((homing_flags & LH_AWAIT_HOMING) + && timer_is_before(time, ld->homing_clock)) + return; + homing_flags &= ~LH_AWAIT_HOMING; + if (data > ld->trigger_threshold) { + homing_flags = 0; + ld->homing_clock = time; + trsync_do_trigger(ld->ts, ld->trigger_reason); + } + ld->homing_flags = homing_flags; +} + +// Chip registers +#define REG_DATA0_MSB 0x00 +#define REG_DATA0_LSB 0x01 +#define REG_STATUS 0x18 + +// Read a register on the ldc1612 +static void +read_reg(struct ldc1612 *ld, uint8_t reg, uint8_t *res) +{ + i2c_read(ld->i2c->i2c_config, sizeof(reg), ®, 2, res); +} + +// Read the status register on the ldc1612 +static uint16_t +read_reg_status(struct ldc1612 *ld) +{ + uint8_t data_status[2]; + read_reg(ld, REG_STATUS, data_status); + return (data_status[0] << 8) | data_status[1]; +} + +#define BYTES_PER_SAMPLE 4 + +// Query ldc1612 data +static void +ldc1612_query(struct ldc1612 *ld, uint8_t oid) +{ + // Check if data available (and clear INTB line) + uint16_t status = read_reg_status(ld); + irq_disable(); + ld->flags &= ~LDC_PENDING; + irq_enable(); + if (!(status & 0x08)) + return; + + // Read coil0 frequency + uint8_t *d = &ld->sb.data[ld->sb.data_count]; + read_reg(ld, REG_DATA0_MSB, &d[0]); + read_reg(ld, REG_DATA0_LSB, &d[2]); + ld->sb.data_count += BYTES_PER_SAMPLE; + + // Check for endstop trigger + uint32_t data = (d[0] << 24L) | (d[1] << 16L) | (d[2] << 8) | d[3]; + check_home(ld, data); + + // Flush local buffer if needed + if (ld->sb.data_count + BYTES_PER_SAMPLE > ARRAY_SIZE(ld->sb.data)) + sensor_bulk_report(&ld->sb, oid); +} + +void +command_query_ldc1612(uint32_t *args) +{ + struct ldc1612 *ld = oid_lookup(args[0], command_config_ldc1612); + + sched_del_timer(&ld->timer); + ld->flags &= ~LDC_PENDING; + if (!args[1]) + // End measurements + return; + + // Start new measurements query + ld->rest_ticks = args[1]; + sensor_bulk_reset(&ld->sb); + irq_disable(); + ld->timer.waketime = timer_read_time() + ld->rest_ticks; + sched_add_timer(&ld->timer); + irq_enable(); +} +DECL_COMMAND(command_query_ldc1612, "query_ldc1612 oid=%c rest_ticks=%u"); + +void +command_query_status_ldc1612(uint32_t *args) +{ + struct ldc1612 *ld = oid_lookup(args[0], command_config_ldc1612); + + if (ld->flags & LDC_HAVE_INTB) { + // Check if a sample is pending in the chip via the intb line + irq_disable(); + uint32_t time = timer_read_time(); + int p = check_intb_asserted(ld); + irq_enable(); + sensor_bulk_status(&ld->sb, args[0], time, 0, p ? BYTES_PER_SAMPLE : 0); + return; + } + + // Query sensor to see if a sample is pending + uint32_t time1 = timer_read_time(); + uint16_t status = read_reg_status(ld); + uint32_t time2 = timer_read_time(); + + uint32_t fifo = status & 0x08 ? BYTES_PER_SAMPLE : 0; + sensor_bulk_status(&ld->sb, args[0], time1, time2-time1, fifo); +} +DECL_COMMAND(command_query_status_ldc1612, "query_status_ldc1612 oid=%c"); + +void +ldc1612_task(void) +{ + if (!sched_check_wake(&ldc1612_wake)) + return; + uint8_t oid; + struct ldc1612 *ld; + foreach_oid(oid, ld, command_config_ldc1612) { + uint_fast8_t flags = ld->flags; + if (!(flags & LDC_PENDING)) + continue; + ldc1612_query(ld, oid); + } +} +DECL_TASK(ldc1612_task); diff --git a/src/sensor_lis2dw.c b/src/sensor_lis2dw.c index 52612623f085..83922003c31b 100644 --- a/src/sensor_lis2dw.c +++ b/src/sensor_lis2dw.c @@ -1,7 +1,7 @@ // Support for gathering acceleration data from LIS2DW chip // // Copyright (C) 2023 Zhou.XianMing -// Copyright (C) 2020 Kevin O'Connor +// Copyright (C) 2020-2023 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -11,24 +11,25 @@ #include "basecmd.h" // oid_alloc #include "command.h" // DECL_COMMAND #include "sched.h" // DECL_TASK +#include "sensor_bulk.h" // sensor_bulk_report #include "spicmds.h" // spidev_transfer #define LIS_AR_DATAX0 0x28 #define LIS_AM_READ 0x80 -#define LIS_FIFO_CTRL 0x2E #define LIS_FIFO_SAMPLES 0x2F +#define BYTES_PER_SAMPLE 6 + struct lis2dw { struct timer timer; uint32_t rest_ticks; struct spidev_s *spi; - uint16_t sequence, limit_count; - uint8_t flags, data_count, fifo_disable; - uint8_t data[48]; + uint8_t flags; + struct sensor_bulk sb; }; enum { - LIS_HAVE_START = 1<<0, LIS_RUNNING = 1<<1, LIS_PENDING = 1<<2, + LIS_PENDING = 1<<0, }; static struct task_wake lis2dw_wake; @@ -53,27 +54,6 @@ command_config_lis2dw(uint32_t *args) } DECL_COMMAND(command_config_lis2dw, "config_lis2dw oid=%c spi_oid=%c"); -// Report local measurement buffer -static void -lis2dw_report(struct lis2dw *ax, uint8_t oid) -{ - sendf("lis2dw_data oid=%c sequence=%hu data=%*s" - , oid, ax->sequence, ax->data_count, ax->data); - ax->data_count = 0; - ax->sequence++; -} - -// Report buffer and fifo status -static void -lis2dw_status(struct lis2dw *ax, uint_fast8_t oid - , uint32_t time1, uint32_t time2, uint_fast8_t fifo) -{ - sendf("lis2dw_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" - " buffered=%c fifo=%c limit_count=%hu" - , oid, time1, time2-time1, ax->sequence - , ax->data_count, fifo, ax->limit_count); -} - // Helper code to reschedule the lis2dw_event() timer static void lis2dw_reschedule_timer(struct lis2dw *ax) @@ -93,7 +73,7 @@ lis2dw_query(struct lis2dw *ax, uint8_t oid) uint8_t fifo_empty,fifo_ovrn = 0; msg[0] = LIS_AR_DATAX0 | LIS_AM_READ ; - uint8_t *d = &ax->data[ax->data_count]; + uint8_t *d = &ax->sb.data[ax->sb.data_count]; spidev_transfer(ax->spi, 1, sizeof(msg), msg); @@ -108,86 +88,42 @@ lis2dw_query(struct lis2dw *ax, uint8_t oid) d[4] = msg[5]; // z low bits d[5] = msg[6]; // z high bits - ax->data_count += 6; - if (ax->data_count + 6 > ARRAY_SIZE(ax->data)) - lis2dw_report(ax, oid); + ax->sb.data_count += BYTES_PER_SAMPLE; + if (ax->sb.data_count + BYTES_PER_SAMPLE > ARRAY_SIZE(ax->sb.data)) + sensor_bulk_report(&ax->sb, oid); // Check fifo status if (fifo_ovrn) - ax->limit_count++; + ax->sb.possible_overflows++; // check if we need to run the task again (more packets in fifo?) - if (!fifo_empty&&!(ax->fifo_disable)) { + if (!fifo_empty) { // More data in fifo - wake this task again sched_wake_task(&lis2dw_wake); - } else if (ax->flags & LIS_RUNNING) { + } else { // Sleep until next check time - sched_del_timer(&ax->timer); ax->flags &= ~LIS_PENDING; lis2dw_reschedule_timer(ax); } } -// Startup measurements -static void -lis2dw_start(struct lis2dw *ax, uint8_t oid) -{ - sched_del_timer(&ax->timer); - ax->flags = LIS_RUNNING; - ax->fifo_disable = 0; - uint8_t ctrl[2] = {LIS_FIFO_CTRL , 0xC0}; - spidev_transfer(ax->spi, 0, sizeof(ctrl), ctrl); - lis2dw_reschedule_timer(ax); -} - -// End measurements -static void -lis2dw_stop(struct lis2dw *ax, uint8_t oid) -{ - // Disable measurements - sched_del_timer(&ax->timer); - ax->flags = 0; - // Drain any measurements still in fifo - ax->fifo_disable = 1; - lis2dw_query(ax, oid); - - uint8_t ctrl[2] = {LIS_FIFO_CTRL , 0}; - uint32_t end1_time = timer_read_time(); - spidev_transfer(ax->spi, 0, sizeof(ctrl), ctrl); - uint32_t end2_time = timer_read_time(); - - uint8_t msg[2] = { LIS_FIFO_SAMPLES | LIS_AM_READ , 0}; - spidev_transfer(ax->spi, 1, sizeof(msg), msg); - uint8_t fifo_status = msg[1]&0x1f; - - //Report final data - if (ax->data_count) - lis2dw_report(ax, oid); - lis2dw_status(ax, oid, end1_time, end2_time, fifo_status); -} - void command_query_lis2dw(uint32_t *args) { struct lis2dw *ax = oid_lookup(args[0], command_config_lis2dw); - if (!args[2]) { + sched_del_timer(&ax->timer); + ax->flags = 0; + if (!args[1]) // End measurements - lis2dw_stop(ax, args[0]); return; - } + // Start new measurements query - sched_del_timer(&ax->timer); - ax->timer.waketime = args[1]; - ax->rest_ticks = args[2]; - ax->flags = LIS_HAVE_START; - ax->sequence = ax->limit_count = 0; - ax->data_count = 0; - ax->fifo_disable = 0; - sched_add_timer(&ax->timer); + ax->rest_ticks = args[1]; + sensor_bulk_reset(&ax->sb); + lis2dw_reschedule_timer(ax); } -DECL_COMMAND(command_query_lis2dw, - "query_lis2dw oid=%c clock=%u rest_ticks=%u"); +DECL_COMMAND(command_query_lis2dw, "query_lis2dw oid=%c rest_ticks=%u"); void command_query_lis2dw_status(uint32_t *args) @@ -197,7 +133,8 @@ command_query_lis2dw_status(uint32_t *args) uint32_t time1 = timer_read_time(); spidev_transfer(ax->spi, 1, sizeof(msg), msg); uint32_t time2 = timer_read_time(); - lis2dw_status(ax, args[0], time1, time2, msg[1]&0x1f); + sensor_bulk_status(&ax->sb, args[0], time1, time2-time1 + , (msg[1] & 0x1f) * BYTES_PER_SAMPLE); } DECL_COMMAND(command_query_lis2dw_status, "query_lis2dw_status oid=%c"); @@ -210,11 +147,7 @@ lis2dw_task(void) struct lis2dw *ax; foreach_oid(oid, ax, command_config_lis2dw) { uint_fast8_t flags = ax->flags; - if (!(flags & LIS_PENDING)) - continue; - if (flags & LIS_HAVE_START) - lis2dw_start(ax, oid); - else + if (flags & LIS_PENDING) lis2dw_query(ax, oid); } } diff --git a/src/sensor_mpu9250.c b/src/sensor_mpu9250.c index c535d0971dc4..23c029211f72 100644 --- a/src/sensor_mpu9250.c +++ b/src/sensor_mpu9250.c @@ -2,7 +2,7 @@ // // Copyright (C) 2023 Matthew Swabey // Copyright (C) 2022 Harry Beyel -// Copyright (C) 2020-2021 Kevin O'Connor +// Copyright (C) 2020-2023 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -12,31 +12,15 @@ #include "basecmd.h" // oid_alloc #include "command.h" // DECL_COMMAND #include "sched.h" // DECL_TASK +#include "sensor_bulk.h" // sensor_bulk_report #include "board/gpio.h" // i2c_read #include "i2ccmds.h" // i2cdev_oid_lookup // Chip registers -#define AR_FIFO_SIZE 512 - -#define AR_PWR_MGMT_1 0x6B -#define AR_PWR_MGMT_2 0x6C -#define AR_FIFO_EN 0x23 -#define AR_ACCEL_OUT_XH 0x3B -#define AR_USER_CTRL 0x6A #define AR_FIFO_COUNT_H 0x72 #define AR_FIFO 0x74 #define AR_INT_STATUS 0x3A -#define SET_ENABLE_FIFO 0x08 -#define SET_DISABLE_FIFO 0x00 -#define SET_USER_FIFO_RESET 0x04 -#define SET_USER_FIFO_EN 0x40 - -#define SET_PWR_SLEEP 0x40 -#define SET_PWR_WAKE 0x00 -#define SET_PWR_2_ACCEL 0x07 // only enable accelerometers -#define SET_PWR_2_NONE 0x3F // disable all sensors - #define FIFO_OVERFLOW_INT 0x10 #define BYTES_PER_FIFO_ENTRY 6 @@ -46,32 +30,17 @@ struct mpu9250 { struct timer timer; uint32_t rest_ticks; struct i2cdev_s *i2c; - uint16_t sequence, limit_count, fifo_max, fifo_pkts_bytes; - uint8_t flags, data_count; - // msg size must be <= 255 due to Klipper api - // = SAMPLES_PER_BLOCK (from mpu9250.py) * BYTES_PER_FIFO_ENTRY + 1 - uint8_t data[BYTES_PER_BLOCK]; + uint16_t fifo_max, fifo_pkts_bytes; + uint8_t flags; + struct sensor_bulk sb; }; enum { - AX_HAVE_START = 1<<0, AX_RUNNING = 1<<1, AX_PENDING = 1<<2, + AX_PENDING = 1<<0, }; static struct task_wake mpu9250_wake; -// Reads the fifo byte count from the device. -static uint16_t -get_fifo_status (struct mpu9250 *mp) -{ - uint8_t reg[] = {AR_FIFO_COUNT_H}; - uint8_t msg[2]; - i2c_read(mp->i2c->i2c_config, sizeof(reg), reg, sizeof(msg), msg); - uint16_t fifo_bytes = ((msg[0] & 0x1f) << 8) | msg[1]; - if (fifo_bytes > mp->fifo_max) - mp->fifo_max = fifo_bytes; - return fifo_bytes; -} - // Event handler that wakes mpu9250_task() periodically static uint_fast8_t mpu9250_event(struct timer *timer) @@ -92,27 +61,6 @@ command_config_mpu9250(uint32_t *args) } DECL_COMMAND(command_config_mpu9250, "config_mpu9250 oid=%c i2c_oid=%c"); -// Report local measurement buffer -static void -mp9250_report(struct mpu9250 *mp, uint8_t oid) -{ - sendf("mpu9250_data oid=%c sequence=%hu data=%*s" - , oid, mp->sequence, mp->data_count, mp->data); - mp->data_count = 0; - mp->sequence++; -} - -// Report buffer and fifo status -static void -mp9250_status(struct mpu9250 *mp, uint_fast8_t oid - , uint32_t time1, uint32_t time2, uint16_t fifo) -{ - sendf("mpu9250_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" - " buffered=%c fifo=%u limit_count=%hu" - , oid, time1, time2-time1, mp->sequence - , mp->data_count, fifo, mp->limit_count); -} - // Helper code to reschedule the mpu9250_event() timer static void mp9250_reschedule_timer(struct mpu9250 *mp) @@ -123,6 +71,19 @@ mp9250_reschedule_timer(struct mpu9250 *mp) irq_enable(); } +// Reads the fifo byte count from the device. +static uint16_t +get_fifo_status(struct mpu9250 *mp) +{ + uint8_t reg[] = {AR_FIFO_COUNT_H}; + uint8_t msg[2]; + i2c_read(mp->i2c->i2c_config, sizeof(reg), reg, sizeof(msg), msg); + uint16_t fifo_bytes = ((msg[0] & 0x1f) << 8) | msg[1]; + if (fifo_bytes > mp->fifo_max) + mp->fifo_max = fifo_bytes; + return fifo_bytes; +} + // Query accelerometer data static void mp9250_query(struct mpu9250 *mp, uint8_t oid) @@ -135,103 +96,45 @@ mp9250_query(struct mpu9250 *mp, uint8_t oid) if (mp->fifo_pkts_bytes >= BYTES_PER_BLOCK) { uint8_t reg = AR_FIFO; i2c_read(mp->i2c->i2c_config, sizeof(reg), ® - , BYTES_PER_BLOCK, &mp->data[0]); - mp->data_count = BYTES_PER_BLOCK; + , BYTES_PER_BLOCK, &mp->sb.data[0]); + mp->sb.data_count = BYTES_PER_BLOCK; mp->fifo_pkts_bytes -= BYTES_PER_BLOCK; - mp9250_report(mp, oid); + sensor_bulk_report(&mp->sb, oid); } // If we have enough bytes remaining to fill another report wake again // otherwise schedule timed wakeup if (mp->fifo_pkts_bytes >= BYTES_PER_BLOCK) { sched_wake_task(&mpu9250_wake); - } else if (mp->flags & AX_RUNNING) { - sched_del_timer(&mp->timer); + } else { mp->flags &= ~AX_PENDING; mp9250_reschedule_timer(mp); } } -// Startup measurements -static void -mp9250_start(struct mpu9250 *mp, uint8_t oid) -{ - sched_del_timer(&mp->timer); - mp->flags = AX_RUNNING; - uint8_t msg[2]; - - msg[0] = AR_FIFO_EN; - msg[1] = SET_DISABLE_FIFO; // disable FIFO - i2c_write(mp->i2c->i2c_config, sizeof(msg), msg); - - msg[0] = AR_USER_CTRL; - msg[1] = SET_USER_FIFO_RESET; // reset FIFO buffer - i2c_write(mp->i2c->i2c_config, sizeof(msg), msg); - - msg[0] = AR_USER_CTRL; - msg[1] = SET_USER_FIFO_EN; // enable FIFO buffer access - i2c_write(mp->i2c->i2c_config, sizeof(msg), msg); - - uint8_t int_reg[] = {AR_INT_STATUS}; // clear FIFO overflow flag - i2c_read(mp->i2c->i2c_config, sizeof(int_reg), int_reg, 1, msg); - - msg[0] = AR_FIFO_EN; - msg[1] = SET_ENABLE_FIFO; // enable accel output to FIFO - i2c_write(mp->i2c->i2c_config, sizeof(msg), msg); - - mp9250_reschedule_timer(mp); -} - -// End measurements -static void -mp9250_stop(struct mpu9250 *mp, uint8_t oid) -{ - // Disable measurements - sched_del_timer(&mp->timer); - mp->flags = 0; - - // disable accel FIFO - uint8_t msg[2] = { AR_FIFO_EN, SET_DISABLE_FIFO }; - uint32_t end1_time = timer_read_time(); - i2c_write(mp->i2c->i2c_config, sizeof(msg), msg); - uint32_t end2_time = timer_read_time(); - - // Report final data - if (mp->data_count > 0) - mp9250_report(mp, oid); - uint16_t bytes_to_read = get_fifo_status(mp); - mp9250_status(mp, oid, end1_time, end2_time, - bytes_to_read / BYTES_PER_FIFO_ENTRY); - - // Uncomment and rebuilt to check for FIFO overruns when tuning - //output("mpu9240 limit_count=%u fifo_max=%u", - // mp->limit_count, mp->fifo_max); -} - void command_query_mpu9250(uint32_t *args) { struct mpu9250 *mp = oid_lookup(args[0], command_config_mpu9250); - if (!args[2]) { + sched_del_timer(&mp->timer); + mp->flags = 0; + if (!args[1]) { // End measurements - mp9250_stop(mp, args[0]); + + // Uncomment and rebuilt to check for FIFO overruns when tuning + //output("mpu9240 fifo_max=%u", mp->fifo_max); return; } + // Start new measurements query - sched_del_timer(&mp->timer); - mp->timer.waketime = args[1]; - mp->rest_ticks = args[2]; - mp->flags = AX_HAVE_START; - mp->sequence = 0; - mp->limit_count = 0; - mp->data_count = 0; + mp->rest_ticks = args[1]; + sensor_bulk_reset(&mp->sb); mp->fifo_max = 0; mp->fifo_pkts_bytes = 0; - sched_add_timer(&mp->timer); + mp9250_reschedule_timer(mp); } -DECL_COMMAND(command_query_mpu9250, - "query_mpu9250 oid=%c clock=%u rest_ticks=%u"); +DECL_COMMAND(command_query_mpu9250, "query_mpu9250 oid=%c rest_ticks=%u"); void command_query_mpu9250_status(uint32_t *args) @@ -244,7 +147,7 @@ command_query_mpu9250_status(uint32_t *args) i2c_read(mp->i2c->i2c_config, sizeof(int_reg), int_reg, sizeof(int_msg), &int_msg); if (int_msg & FIFO_OVERFLOW_INT) - mp->limit_count++; + mp->sb.possible_overflows++; // Read latest FIFO count (with precise timing) uint8_t reg[] = {AR_FIFO_COUNT_H}; @@ -255,7 +158,7 @@ command_query_mpu9250_status(uint32_t *args) uint16_t fifo_bytes = ((msg[0] & 0x1f) << 8) | msg[1]; // Report status - mp9250_status(mp, args[0], time1, time2, fifo_bytes / BYTES_PER_FIFO_ENTRY); + sensor_bulk_status(&mp->sb, args[0], time1, time2-time1, fifo_bytes); } DECL_COMMAND(command_query_mpu9250_status, "query_mpu9250_status oid=%c"); @@ -268,15 +171,8 @@ mpu9250_task(void) struct mpu9250 *mp; foreach_oid(oid, mp, command_config_mpu9250) { uint_fast8_t flags = mp->flags; - if (!(flags & AX_PENDING)) { - continue; - } - if (flags & AX_HAVE_START) { - mp9250_start(mp, oid); - } - else { + if (flags & AX_PENDING) mp9250_query(mp, oid); - } } } DECL_TASK(mpu9250_task); diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index c06bb6ffb0c2..037e37bbe949 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -88,6 +88,9 @@ choice config MACH_STM32G431 bool "STM32G431" select MACH_STM32G4 + config MACH_STM32G474 + bool "STM32G474" + select MACH_STM32G4 config MACH_STM32H723 bool "STM32H723" select MACH_STM32H7 @@ -181,6 +184,7 @@ config MCU default "stm32g0b0xx" if MACH_STM32G0B0 default "stm32g0b1xx" if MACH_STM32G0B1 default "stm32g431xx" if MACH_STM32G431 + default "stm32g474xx" if MACH_STM32G474 default "stm32h723xx" if MACH_STM32H723 default "stm32h743xx" if MACH_STM32H743 default "stm32h750xx" if MACH_STM32H750 @@ -199,6 +203,7 @@ config CLOCK_FREQ default 216000000 if MACH_STM32F765 default 64000000 if MACH_STM32G0 default 150000000 if MACH_STM32G431 + default 170000000 if MACH_STM32G474 default 400000000 if MACH_STM32H7 # 400Mhz is max Klipper currently supports default 80000000 if MACH_STM32L412 default 64000000 if MACH_N32G45x && STM32_CLOCK_REF_INTERNAL @@ -206,13 +211,13 @@ config CLOCK_FREQ config FLASH_SIZE hex - default 0x4000 if MACH_STM32F031 - default 0x8000 if MACH_STM32F042 + default 0x8000 if MACH_STM32F031 || MACH_STM32F042 default 0x20000 if MACH_STM32F070 || MACH_STM32F072 default 0x10000 if MACH_STM32F103 || MACH_STM32L412 # Flash size of stm32f103x8 (64KiB) default 0x40000 if MACH_STM32F2 || MACH_STM32F401 || MACH_STM32H723 default 0x80000 if MACH_STM32F4x5 || MACH_STM32F446 default 0x20000 if MACH_STM32G0 || MACH_STM32G431 + default 0x40000 if MACH_STM32G474 default 0x20000 if MACH_STM32H750 default 0x200000 if MACH_STM32H743 || MACH_STM32F765 default 0x20000 if MACH_N32G45x @@ -233,6 +238,7 @@ config RAM_SIZE default 0x2800 if MACH_STM32F103x6 default 0x5000 if MACH_STM32F103 && !MACH_STM32F103x6 # Ram size of stm32f103x8 default 0x8000 if MACH_STM32G431 + default 0x20000 if MACH_STM32G474 default 0xa000 if MACH_STM32L412 default 0x20000 if MACH_STM32F207 default 0x10000 if MACH_STM32F401 @@ -282,22 +288,24 @@ choice config STM32_FLASH_START_8000 bool "32KiB bootloader" if MACH_STM32F1 || MACH_STM32F2 || MACH_STM32F4 || MACH_STM32F7 config STM32_FLASH_START_8800 - bool "34KiB bootloader (Chitu v6 Bootloader)" if MACH_STM32F103 + bool "34KiB bootloader" if MACH_STM32F103 config STM32_FLASH_START_20200 - bool "128KiB bootloader with 512 byte offset (Prusa Buddy)" if MACH_STM32F4x5 + bool "128KiB bootloader with 512 byte offset" if MACH_STM32F4x5 + config STM32_FLASH_START_9000 + bool "36KiB bootloader" if MACH_STM32F1 config STM32_FLASH_START_C000 - bool "48KiB bootloader (MKS Robin Nano V3)" if MACH_STM32F4x5 + bool "48KiB bootloader" if MACH_STM32F4x5 || MACH_STM32F401 config STM32_FLASH_START_10000 bool "64KiB bootloader" if MACH_STM32F103 || MACH_STM32F4 config STM32_FLASH_START_800 - bool "2KiB bootloader (HID Bootloader)" if MACH_STM32F103 + bool "2KiB bootloader" if MACH_STM32F103 config STM32_FLASH_START_1000 bool "4KiB bootloader" if MACH_STM32F1 || MACH_STM32F0 config STM32_FLASH_START_4000 - bool "16KiB bootloader (HID Bootloader)" if MACH_STM32F207 || MACH_STM32F401 || MACH_STM32F4x5 || MACH_STM32F103 || MACH_STM32F072 + bool "16KiB bootloader" if MACH_STM32F207 || MACH_STM32F401 || MACH_STM32F4x5 || MACH_STM32F103 || MACH_STM32F072 config STM32_FLASH_START_20000 - bool "128KiB bootloader (SKR SE BX v2.0)" if MACH_STM32H743 || MACH_STM32H723 || MACH_STM32F7 + bool "128KiB bootloader" if MACH_STM32H743 || MACH_STM32H723 || MACH_STM32F7 config STM32_FLASH_START_0000 bool "No bootloader" @@ -312,6 +320,7 @@ config FLASH_APPLICATION_ADDRESS default 0x8007000 if STM32_FLASH_START_7000 default 0x8008000 if STM32_FLASH_START_8000 default 0x8008800 if STM32_FLASH_START_8800 + default 0x8009000 if STM32_FLASH_START_9000 default 0x800C000 if STM32_FLASH_START_C000 default 0x8010000 if STM32_FLASH_START_10000 default 0x8020000 if STM32_FLASH_START_20000 diff --git a/src/stm32/Makefile b/src/stm32/Makefile index 18af2e9d7b4e..5f4d3af5cc5b 100644 --- a/src/stm32/Makefile +++ b/src/stm32/Makefile @@ -31,7 +31,7 @@ CFLAGS-$(CONFIG_MACH_STM32H7) += -mcpu=cortex-m7 -Ilib/stm32h7/include CFLAGS-$(CONFIG_MACH_STM32L4) += -mcpu=cortex-m4 -Ilib/stm32l4/include CFLAGS += $(CFLAGS-y) -D$(MCU_UPPER) -mthumb -Ilib/cmsis-core -Ilib/fast-hash -CFLAGS_klipper.elf += --specs=nano.specs --specs=nosys.specs +CFLAGS_klipper.elf += -nostdlib -lgcc -lc_nano CFLAGS_klipper.elf += -T $(OUT)src/generic/armcm_link.ld $(OUT)klipper.elf: $(OUT)src/generic/armcm_link.ld diff --git a/src/stm32/fdcan.c b/src/stm32/fdcan.c index b0e8c01d17fa..5344d26b195b 100644 --- a/src/stm32/fdcan.c +++ b/src/stm32/fdcan.c @@ -60,16 +60,23 @@ || CONFIG_STM32_CANBUS_PB5_PB6 ||CONFIG_STM32_CANBUS_PB12_PB13) #define SOC_CAN FDCAN1 #define MSG_RAM (((struct fdcan_ram_layout*)SRAMCAN_BASE)->fdcan1) + #if CONFIG_MACH_STM32H7 || CONFIG_MACH_STM32G4 + #define CAN_IT0_IRQn FDCAN1_IT0_IRQn + #endif #else #define SOC_CAN FDCAN2 #define MSG_RAM (((struct fdcan_ram_layout*)SRAMCAN_BASE)->fdcan2) + #if CONFIG_MACH_STM32H7 || CONFIG_MACH_STM32G4 + #define CAN_IT0_IRQn FDCAN2_IT0_IRQn + #endif #endif #if CONFIG_MACH_STM32G0 #define CAN_IT0_IRQn TIM16_FDCAN_IT0_IRQn #define CAN_FUNCTION GPIO_FUNCTION(3) // Alternative function mapping number -#elif CONFIG_MACH_STM32H7 || CONFIG_MACH_STM32G4 - #define CAN_IT0_IRQn FDCAN1_IT0_IRQn +#endif + +#if CONFIG_MACH_STM32H7 || CONFIG_MACH_STM32G4 #define CAN_FUNCTION GPIO_FUNCTION(9) // Alternative function mapping number #endif diff --git a/src/stm32/spi.c b/src/stm32/spi.c index 709055c2ac4f..648e9bcd0639 100644 --- a/src/stm32/spi.c +++ b/src/stm32/spi.c @@ -12,7 +12,7 @@ struct spi_info { SPI_TypeDef *spi; - uint8_t miso_pin, mosi_pin, sck_pin, function; + uint8_t miso_pin, mosi_pin, sck_pin, miso_af, mosi_af, sck_af; }; DECL_ENUMERATION("spi_bus", "spi2", 0); @@ -21,6 +21,7 @@ DECL_ENUMERATION("spi_bus", "spi1", 1); DECL_CONSTANT_STR("BUS_PINS_spi1", "PA6,PA7,PA5"); DECL_ENUMERATION("spi_bus", "spi1a", 2); DECL_CONSTANT_STR("BUS_PINS_spi1a", "PB4,PB5,PB3"); + #if CONFIG_MACH_STM32G4 DECL_ENUMERATION("spi_bus", "spi2_PA10_PA11_PF1", 3); DECL_CONSTANT_STR("BUS_PINS_spi2_PA10_PA11_PF1", "PA10,PA11,PF1"); @@ -42,33 +43,44 @@ DECL_CONSTANT_STR("BUS_PINS_spi1a", "PB4,PB5,PB3"); DECL_CONSTANT_STR("BUS_PINS_spi2b", "PI2,PI3,PI1"); #endif #endif + #if CONFIG_MACH_STM32G0B1 + DECL_ENUMERATION("spi_bus", "spi2_PB2_PB11_PB10", 5); + DECL_CONSTANT_STR("BUS_PINS_spi2_PB2_PB11_PB10", "PB2,PB11,PB10"); +#endif #endif +#define GPIO_FUNCTION_ALL(fn) GPIO_FUNCTION(fn), \ + GPIO_FUNCTION(fn), GPIO_FUNCTION(fn) + #if CONFIG_MACH_STM32F0 || CONFIG_MACH_STM32G0 - #define SPI_FUNCTION GPIO_FUNCTION(0) + #define SPI_FUNCTION_ALL GPIO_FUNCTION_ALL(0) #else - #define SPI_FUNCTION GPIO_FUNCTION(5) + #define SPI_FUNCTION_ALL GPIO_FUNCTION_ALL(5) #endif static const struct spi_info spi_bus[] = { - { SPI2, GPIO('B', 14), GPIO('B', 15), GPIO('B', 13), SPI_FUNCTION }, - { SPI1, GPIO('A', 6), GPIO('A', 7), GPIO('A', 5), SPI_FUNCTION }, - { SPI1, GPIO('B', 4), GPIO('B', 5), GPIO('B', 3), SPI_FUNCTION }, + { SPI2, GPIO('B', 14), GPIO('B', 15), GPIO('B', 13), SPI_FUNCTION_ALL }, + { SPI1, GPIO('A', 6), GPIO('A', 7), GPIO('A', 5), SPI_FUNCTION_ALL }, + { SPI1, GPIO('B', 4), GPIO('B', 5), GPIO('B', 3), SPI_FUNCTION_ALL }, #if CONFIG_MACH_STM32G4 - { SPI2, GPIO('A', 10), GPIO('A', 11), GPIO('F', 1), SPI_FUNCTION }, + { SPI2, GPIO('A', 10), GPIO('A', 11), GPIO('F', 1), SPI_FUNCTION_ALL }, #else - { SPI2, GPIO('C', 2), GPIO('C', 3), GPIO('B', 10), SPI_FUNCTION }, + { SPI2, GPIO('C', 2), GPIO('C', 3), GPIO('B', 10), SPI_FUNCTION_ALL }, #endif #ifdef SPI3 - { SPI3, GPIO('B', 4), GPIO('B', 5), GPIO('B', 3), GPIO_FUNCTION(6) }, + { SPI3, GPIO('B', 4), GPIO('B', 5), GPIO('B', 3), GPIO_FUNCTION_ALL(6) }, #if CONFIG_MACH_STM32F4 || CONFIG_MACH_STM32G4 - { SPI3, GPIO('C', 11), GPIO('C', 12), GPIO('C', 10), GPIO_FUNCTION(6) }, + { SPI3, GPIO('C', 11), GPIO('C', 12), GPIO('C', 10), GPIO_FUNCTION_ALL(6) }, #ifdef SPI4 - { SPI4, GPIO('E', 13), GPIO('E', 14), GPIO('E', 12), GPIO_FUNCTION(5) }, + { SPI4, GPIO('E', 13), GPIO('E', 14), GPIO('E', 12), GPIO_FUNCTION_ALL(5) }, #elif defined(GPIOI) - { SPI2, GPIO('I', 2), GPIO('I', 3), GPIO('I', 1), GPIO_FUNCTION(5) }, + { SPI2, GPIO('I', 2), GPIO('I', 3), GPIO('I', 1), GPIO_FUNCTION_ALL(5) }, #endif #endif + #if CONFIG_MACH_STM32G0B1 + { SPI2, GPIO('B', 2), GPIO('B', 11), GPIO('B', 10), + GPIO_FUNCTION(1), GPIO_FUNCTION(0), GPIO_FUNCTION(5) }, + #endif #endif }; @@ -82,9 +94,9 @@ spi_setup(uint32_t bus, uint8_t mode, uint32_t rate) SPI_TypeDef *spi = spi_bus[bus].spi; if (!is_enabled_pclock((uint32_t)spi)) { enable_pclock((uint32_t)spi); - gpio_peripheral(spi_bus[bus].miso_pin, spi_bus[bus].function, 1); - gpio_peripheral(spi_bus[bus].mosi_pin, spi_bus[bus].function, 0); - gpio_peripheral(spi_bus[bus].sck_pin, spi_bus[bus].function, 0); + gpio_peripheral(spi_bus[bus].miso_pin, spi_bus[bus].miso_af, 1); + gpio_peripheral(spi_bus[bus].mosi_pin, spi_bus[bus].mosi_af, 0); + gpio_peripheral(spi_bus[bus].sck_pin, spi_bus[bus].sck_af, 0); // Configure CR2 on stm32 f0/f7/g0/l4/g4 #if CONFIG_MACH_STM32F0 || CONFIG_MACH_STM32F7 || \ diff --git a/src/stm32/stm32f0_i2c.c b/src/stm32/stm32f0_i2c.c index e9cadccbdfad..597b48460eb7 100644 --- a/src/stm32/stm32f0_i2c.c +++ b/src/stm32/stm32f0_i2c.c @@ -22,6 +22,8 @@ struct i2c_info { DECL_CONSTANT_STR("BUS_PINS_i2c1_PF1_PF0", "PF1,PF0"); DECL_ENUMERATION("i2c_bus", "i2c1_PB8_PB9", 2); DECL_CONSTANT_STR("BUS_PINS_i2c1_PB8_PB9", "PB8,PB9"); + DECL_ENUMERATION("i2c_bus", "i2c1_PB8_PB7", 3); + DECL_CONSTANT_STR("BUS_PINS_i2c1_PB8_PB7", "PB8,PB7"); // Deprecated "i2c1a" style mappings DECL_ENUMERATION("i2c_bus", "i2c1", 0); DECL_CONSTANT_STR("BUS_PINS_i2c1", "PB6,PB7"); @@ -44,6 +46,8 @@ struct i2c_info { #ifdef I2C3 DECL_ENUMERATION("i2c_bus", "i2c3_PB3_PB4", 5); DECL_CONSTANT_STR("BUS_PINS_i2c3_PB3_PB4", "PB3,PB4"); + DECL_ENUMERATION("i2c_bus", "i2c3_PC0_PC1", 6); + DECL_CONSTANT_STR("BUS_PINS_i2c3_PC0_PC1", "PC0,PC1"); #endif #elif CONFIG_MACH_STM32L4 DECL_ENUMERATION("i2c_bus", "i2c1_PB6_PB7", 0); @@ -82,6 +86,8 @@ struct i2c_info { DECL_CONSTANT_STR("BUS_PINS_i2c1_PB8_PB9", "PB8,PB9"); DECL_ENUMERATION("i2c_bus", "i2c2_PB10_PB11", 2); DECL_CONSTANT_STR("BUS_PINS_i2c2_PB10_PB11", "PB10,PB11"); + DECL_ENUMERATION("i2c_bus", "i2c3_PA8_PC9", 3); + DECL_CONSTANT_STR("BUS_PINS_i2c3_PA8_PC9", "PA8,PC9"); #endif static const struct i2c_info i2c_bus[] = { @@ -89,6 +95,7 @@ static const struct i2c_info i2c_bus[] = { { I2C1, GPIO('B', 6), GPIO('B', 7), GPIO_FUNCTION(1) }, { I2C1, GPIO('F', 1), GPIO('F', 0), GPIO_FUNCTION(1) }, { I2C1, GPIO('B', 8), GPIO('B', 9), GPIO_FUNCTION(1) }, + { I2C1, GPIO('B', 8), GPIO('B', 7), GPIO_FUNCTION(1) }, #elif CONFIG_MACH_STM32F7 { I2C1, GPIO('B', 6), GPIO('B', 7), GPIO_FUNCTION(1) }, #elif CONFIG_MACH_STM32G0 @@ -99,6 +106,7 @@ static const struct i2c_info i2c_bus[] = { { I2C2, GPIO('B', 13), GPIO('B', 14), GPIO_FUNCTION(6) }, #ifdef I2C3 { I2C3, GPIO('B', 3), GPIO('B', 4), GPIO_FUNCTION(6) }, + { I2C3, GPIO('C', 0), GPIO('C', 1), GPIO_FUNCTION(6) }, #endif #elif CONFIG_MACH_STM32L4 { I2C1, GPIO('B', 6), GPIO('B', 7), GPIO_FUNCTION(4) }, @@ -120,6 +128,7 @@ static const struct i2c_info i2c_bus[] = { { I2C1, GPIO('B', 6), GPIO('B', 7), GPIO_FUNCTION(4) }, { I2C1, GPIO('B', 8), GPIO('B', 9), GPIO_FUNCTION(4) }, { I2C2, GPIO('B', 10), GPIO('B', 11), GPIO_FUNCTION(4) }, + { I2C3, GPIO('A', 8), GPIO('C', 9), GPIO_FUNCTION(4) }, #endif }; diff --git a/src/stm32/stm32f0_serial.c b/src/stm32/stm32f0_serial.c index e48960f1163c..c987f149e561 100644 --- a/src/stm32/stm32f0_serial.c +++ b/src/stm32/stm32f0_serial.c @@ -68,7 +68,7 @@ DECL_CONSTANT_STR("RESERVE_PINS_serial", "PD9,PD8"); #define GPIO_Rx GPIO('D', 9) #define GPIO_Tx GPIO('D', 8) - #define USARTx_FUNCTION GPIO_FUNCTION(7) + #define USARTx_FUNCTION GPIO_FUNCTION(CONFIG_MACH_STM32G0 ? 0 : 7) #define USARTx USART3 #define USARTx_IRQn USART3_IRQn #elif CONFIG_STM32_SERIAL_UART4 @@ -102,6 +102,13 @@ #define USART5_IRQn USART3_4_5_6_LPUART1_IRQn #define USART6_IRQn USART3_4_5_6_LPUART1_IRQn #endif + #if CONFIG_MACH_STM32G0B0 + #define USART2_IRQn USART2_IRQn + #define USART3_IRQn USART3_4_5_6_IRQn + #define USART4_IRQn USART3_4_5_6_IRQn + #define USART5_IRQn USART3_4_5_6_IRQn + #define USART6_IRQn USART3_4_5_6_IRQn + #endif #define USART_CR1_RXNEIE USART_CR1_RXNEIE_RXFNEIE #define USART_CR1_TXEIE USART_CR1_TXEIE_TXFNFIE #define USART_ISR_RXNE USART_ISR_RXNE_RXFNE diff --git a/src/stm32/stm32g0.c b/src/stm32/stm32g0.c index 7408612ade2e..819a5edd448f 100644 --- a/src/stm32/stm32g0.c +++ b/src/stm32/stm32g0.c @@ -162,6 +162,8 @@ bootloader_request(void) void armcm_main(void) { + // Disable internal pull-down resistors on UCPDx CCx pins + SYSCFG->CFGR1 |= (SYSCFG_CFGR1_UCPD1_STROBE | SYSCFG_CFGR1_UCPD2_STROBE); SCB->VTOR = (uint32_t)VectorTable; // Reset clock registers (in case bootloader has changed them) diff --git a/src/stm32/stm32g4.c b/src/stm32/stm32g4.c index 139ea8eaad64..1eed3ec188d6 100644 --- a/src/stm32/stm32g4.c +++ b/src/stm32/stm32g4.c @@ -22,6 +22,12 @@ lookup_clock_line(uint32_t periph_base) if (periph_base < APB2PERIPH_BASE) { uint32_t pos = (periph_base - APB1PERIPH_BASE) / 0x400; if (pos < 32) { +#if defined(FDCAN2_BASE) + if (periph_base == FDCAN2_BASE) + return (struct cline){.en = &RCC->APB1ENR1, + .rst = &RCC->APB1RSTR1, + .bit = 1 << 25}; +#endif return (struct cline){.en = &RCC->APB1ENR1, .rst = &RCC->APB1RSTR1, .bit = 1 << pos}; diff --git a/src/stm32/stm32h7_adc.c b/src/stm32/stm32h7_adc.c index e9dc8f845473..3c217ca273c6 100644 --- a/src/stm32/stm32h7_adc.c +++ b/src/stm32/stm32h7_adc.c @@ -189,7 +189,11 @@ gpio_adc_setup(uint32_t pin) if (chan >= 2 * ADCIN_BANK_SIZE) { chan -= 2 * ADCIN_BANK_SIZE; adc = ADC3; +#if CONFIG_MACH_STM32G4 + adc_common = ADC345_COMMON; +#else adc_common = ADC3_COMMON; +#endif } else #endif #ifdef ADC2 diff --git a/test/configs/stm32f031.config b/test/configs/stm32f031.config index aa9c282beb36..a8c95cfe7d70 100644 --- a/test/configs/stm32f031.config +++ b/test/configs/stm32f031.config @@ -1,5 +1,9 @@ -# Base config file for STM32F031 boards +# Base config file for STM32F031x4 (16KB) boards with serial on PA14/PA15 CONFIG_MACH_STM32=y CONFIG_MACH_STM32F031=y -CONFIG_WANT_GPIO_BITBANGING=n +CONFIG_LOW_LEVEL_OPTIONS=y +CONFIG_STM32_SERIAL_USART2_ALT_PA15_PA14=y +CONFIG_STM32_CLOCK_REF_INTERNAL=y +CONFIG_STM32_FLASH_START_0000=y CONFIG_WANT_DISPLAYS=n +CONFIG_WANT_GPIO_BITBANGING=n diff --git a/test/configs/stm32f042.config b/test/configs/stm32f042.config index 7f1e879fbb18..12cc0922e45e 100644 --- a/test/configs/stm32f042.config +++ b/test/configs/stm32f042.config @@ -3,3 +3,4 @@ CONFIG_MACH_STM32=y CONFIG_MACH_STM32F042=y CONFIG_WANT_SOFTWARE_I2C=n CONFIG_WANT_LIS2DW=n +CONFIG_WANT_LDC1612=n diff --git a/test/configs/stm32g474.config b/test/configs/stm32g474.config new file mode 100644 index 000000000000..da38a9179fb5 --- /dev/null +++ b/test/configs/stm32g474.config @@ -0,0 +1,3 @@ +# Base config file for STM32G474 ARM processor +CONFIG_MACH_STM32=y +CONFIG_MACH_STM32G474=y diff --git a/test/klippy/printers.test b/test/klippy/printers.test index 8a876f002d75..9f7ab3c20f38 100644 --- a/test/klippy/printers.test +++ b/test/klippy/printers.test @@ -204,7 +204,10 @@ CONFIG ../../config/printer-voxelab-aquila-2021.cfg # Printers using the stm32f401 DICTIONARY stm32f401.dict CONFIG ../../config/generic-fysetc-cheetah-v2.0.cfg +CONFIG ../../config/printer-artillery-genius-pro-2022.cfg CONFIG ../../config/printer-artillery-sidewinder-x2-2022.cfg +CONFIG ../../config/printer-artillery-sidewinder-x3-plus-2024.cfg +CONFIG ../../config/printer-creality-ender5-s1-2023.cfg CONFIG ../../config/printer-elegoo-neptune3-pro-2023.cfg # Printers using the stm32f405 @@ -244,6 +247,7 @@ 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 +CONFIG ../../config/printer-tronxy-crux1-2022.cfg # Printers using the stm32h723 DICTIONARY stm32h723.dict diff --git a/test/klippy/pwm.cfg b/test/klippy/pwm.cfg index fbda912694ac..af5b5b10ec45 100644 --- a/test/klippy/pwm.cfg +++ b/test/klippy/pwm.cfg @@ -5,6 +5,12 @@ value: 0 shutdown_value: 0 cycle_time: 0.01 +[pwm_cycle_time cycle_pwm_pin] +pin: PH7 +value: 0 +shutdown_value: 0 +cycle_time: 0.01 + [output_pin hard_pwm_pin] pin: PH6 pwm: True diff --git a/test/klippy/pwm.test b/test/klippy/pwm.test index 5e74a3e0569e..fdbf42f2acf8 100644 --- a/test/klippy/pwm.test +++ b/test/klippy/pwm.test @@ -16,18 +16,24 @@ SET_PIN PIN=soft_pwm_pin VALUE=0 SET_PIN PIN=soft_pwm_pin VALUE=0.5 SET_PIN PIN=soft_pwm_pin VALUE=1 +# Soft PWM with dynamic cycle time +# Test basic on off +SET_PIN PIN=cycle_pwm_pin VALUE=0 +SET_PIN PIN=cycle_pwm_pin VALUE=0.5 +SET_PIN PIN=cycle_pwm_pin VALUE=1 + # Test cycle time -SET_PIN PIN=soft_pwm_pin VALUE=0 CYCLE_TIME=0.1 -SET_PIN PIN=soft_pwm_pin VALUE=1 CYCLE_TIME=0.5 -SET_PIN PIN=soft_pwm_pin VALUE=0.5 CYCLE_TIME=0.001 -SET_PIN PIN=soft_pwm_pin VALUE=0.75 CYCLE_TIME=0.01 -SET_PIN PIN=soft_pwm_pin VALUE=0.5 CYCLE_TIME=1 +SET_PIN PIN=cycle_pwm_pin VALUE=0 CYCLE_TIME=0.1 +SET_PIN PIN=cycle_pwm_pin VALUE=1 CYCLE_TIME=0.5 +SET_PIN PIN=cycle_pwm_pin VALUE=0.5 CYCLE_TIME=0.001 +SET_PIN PIN=cycle_pwm_pin VALUE=0.75 CYCLE_TIME=0.01 +SET_PIN PIN=cycle_pwm_pin VALUE=0.5 CYCLE_TIME=1 # Test duplicate values -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 +SET_PIN PIN=cycle_pwm_pin VALUE=0.5 CYCLE_TIME=0.5 +SET_PIN PIN=cycle_pwm_pin VALUE=0.5 CYCLE_TIME=0.5 +SET_PIN PIN=cycle_pwm_pin VALUE=0.75 CYCLE_TIME=0.5 +SET_PIN PIN=cycle_pwm_pin VALUE=0.75 CYCLE_TIME=0.75 # PWM tool # Basic test