mirror of https://github.com/Desuuuu/klipper.git
Merge remote-tracking branch 'upstream/master' into dev
This commit is contained in:
commit
ccf58ec4bf
|
@ -556,6 +556,32 @@
|
||||||
# measurements, e.g. with an accelerometer.
|
# measurements, e.g. with an accelerometer.
|
||||||
# Default value is 0.1 which is a good all-round value for most printers.
|
# Default value is 0.1 which is a good all-round value for most printers.
|
||||||
|
|
||||||
|
# Support for ADXL345 accelerometers. This support allows one to query
|
||||||
|
# accelerometer measurements from the sensor. This enables an
|
||||||
|
# ACCELEROMETER_MEASURE command (see docs/G-Codes.md for more
|
||||||
|
# information). The default chip name is "default", but one may
|
||||||
|
# specify an explicit name (eg, [adxl345 my_chip_name]).
|
||||||
|
#[adxl345]
|
||||||
|
#axes_map: x,y,z
|
||||||
|
# The accelerometer axis for each of the printer's x, y, and z axes.
|
||||||
|
# This may be useful if the accelerometer is mounted in an
|
||||||
|
# orientation that does not match the printer orientation. For
|
||||||
|
# example, one could set this to "y,x,z" to swap the x and y axes.
|
||||||
|
# It is also possible to negate an axis if the accelerometer
|
||||||
|
# direction is reversed (eg, "x,z,-y"). The default is "x,y,z".
|
||||||
|
#cs_pin:
|
||||||
|
# The SPI enable pin for the sensor. This parameter must be
|
||||||
|
# provided.
|
||||||
|
#spi_speed: 5000000
|
||||||
|
# The SPI speed (in hz) to use when communicating with the chip.
|
||||||
|
# The default is 5000000.
|
||||||
|
#spi_bus:
|
||||||
|
#spi_software_sclk_pin:
|
||||||
|
#spi_software_mosi_pin:
|
||||||
|
#spi_software_miso_pin:
|
||||||
|
# These optional parameters allow one to customize the SPI settings
|
||||||
|
# used to communicate with the chip.
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# Config file helpers
|
# Config file helpers
|
||||||
|
@ -1304,6 +1330,14 @@
|
||||||
#pin:
|
#pin:
|
||||||
# The pin on which the button is connected. This parameter must be
|
# The pin on which the button is connected. This parameter must be
|
||||||
# provided.
|
# provided.
|
||||||
|
#analog_range:
|
||||||
|
# Two comma separated resistances (in Ohms) specifying the minimum
|
||||||
|
# and maximum resistance range for the button. If analog_range is
|
||||||
|
# provided then the pin must be an analog capable pin. The default
|
||||||
|
# is to use digital gpio for the button.
|
||||||
|
#analog_pullup_resistor:
|
||||||
|
# The pullup resistance (in Ohms) when analog_range is specified.
|
||||||
|
# The default is 4700 ohms.
|
||||||
#press_gcode:
|
#press_gcode:
|
||||||
# A list of G-Code commands to execute when the button is pressed.
|
# A list of G-Code commands to execute when the button is pressed.
|
||||||
# G-Code templates are supported. This parameter must be provided.
|
# G-Code templates are supported. This parameter must be provided.
|
||||||
|
@ -1867,6 +1901,8 @@
|
||||||
# Set the Vcomh value on SSD1306/SH1106 displays. This value is
|
# Set the Vcomh value on SSD1306/SH1106 displays. This value is
|
||||||
# associated with a "smearing" effect on some OLED displays.
|
# associated with a "smearing" effect on some OLED displays.
|
||||||
# The value may range from 0 to 63. Default is 0.
|
# The value may range from 0 to 63. Default is 0.
|
||||||
|
#x_offset: 0
|
||||||
|
# Set the horizontal offset value on SSD1306/SH1106 displays. Default is 0.
|
||||||
#invert: FALSE
|
#invert: FALSE
|
||||||
# TRUE inverts the pixels on certain OLED (SSD1306/SH1106) displays
|
# TRUE inverts the pixels on certain OLED (SSD1306/SH1106) displays
|
||||||
# The default is FALSE
|
# The default is FALSE
|
||||||
|
@ -2188,6 +2224,23 @@
|
||||||
# communication. The pin must have a valid pinmux configuration
|
# communication. The pin must have a valid pinmux configuration
|
||||||
# for the given SERCOM peripheral. This parameter must be provided.
|
# for the given SERCOM peripheral. This parameter must be provided.
|
||||||
|
|
||||||
|
# Duet2 Maestro analog scaling by vref and vssa readings. Defining an
|
||||||
|
# adc_scaled section enables virtual adc pins (such as "my_name:PB0")
|
||||||
|
# that are automatically adjusted by the board's vref and vssa
|
||||||
|
# monitoring pins. Be sure to define this config section above any
|
||||||
|
# config sections that use one these virtual pins.
|
||||||
|
#[adc_scaled my_name]
|
||||||
|
#vref_pin:
|
||||||
|
# The ADC pin to use for VREF monitoring. This parameter must be
|
||||||
|
# provided.
|
||||||
|
#vssa_pin:
|
||||||
|
# The ADC pin to use for VSSA monitoring. This parameter must be
|
||||||
|
# provided.
|
||||||
|
#smooth_time: 2.0
|
||||||
|
# A time value (in seconds) over which the vref and vssa
|
||||||
|
# measurements will be smoothed to reduce the impact of measurement
|
||||||
|
# noise. The default is 2 seconds.
|
||||||
|
|
||||||
# Replicape support - see the generic-replicape.cfg file for further
|
# Replicape support - see the generic-replicape.cfg file for further
|
||||||
# details.
|
# details.
|
||||||
#[replicape]
|
#[replicape]
|
||||||
|
|
|
@ -59,6 +59,11 @@ microsteps: 16
|
||||||
run_current: 0.800
|
run_current: 0.800
|
||||||
stealthchop_threshold: 30
|
stealthchop_threshold: 30
|
||||||
|
|
||||||
|
# Support analog sensor adjustments using VREF/VSSA pins
|
||||||
|
[adc_scaled vref_scaled]
|
||||||
|
vref_pin: PA17
|
||||||
|
vssa_pin: PA19
|
||||||
|
|
||||||
[extruder]
|
[extruder]
|
||||||
step_pin: PC4
|
step_pin: PC4
|
||||||
dir_pin: PB7
|
dir_pin: PB7
|
||||||
|
@ -69,7 +74,7 @@ filament_diameter: 1.750
|
||||||
heater_pin: !PC1
|
heater_pin: !PC1
|
||||||
sensor_type: EPCOS 100K B57560G104F
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
pullup_resistor: 2200
|
pullup_resistor: 2200
|
||||||
sensor_pin: PB0
|
sensor_pin: vref_scaled:PB0
|
||||||
control: pid
|
control: pid
|
||||||
pid_Kp: 22.2
|
pid_Kp: 22.2
|
||||||
pid_Ki: 1.08
|
pid_Ki: 1.08
|
||||||
|
@ -110,7 +115,7 @@ stealthchop_threshold: 5
|
||||||
heater_pin: !PC0
|
heater_pin: !PC0
|
||||||
sensor_type: EPCOS 100K B57560G104F
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
pullup_resistor: 2200
|
pullup_resistor: 2200
|
||||||
sensor_pin: PA20
|
sensor_pin: vref_scaled:PA20
|
||||||
control: watermark
|
control: watermark
|
||||||
min_temp: 0
|
min_temp: 0
|
||||||
max_temp: 130
|
max_temp: 130
|
||||||
|
|
|
@ -134,3 +134,31 @@ pin: replicape:power_fan0
|
||||||
# PWM output pin controlling the servo. This parameter must be
|
# PWM output pin controlling the servo. This parameter must be
|
||||||
# provided.
|
# provided.
|
||||||
#...
|
#...
|
||||||
|
|
||||||
|
# Providing an example of a switch filament sensor using the Linux MCU for replicape, instead of the PRU which does not have enough memory:
|
||||||
|
#[filament_switch_sensor switch_sensor]
|
||||||
|
#switch_pin: HOST_X2_STOP
|
||||||
|
|
||||||
|
# providing board pin aliases
|
||||||
|
[board_pins]
|
||||||
|
aliases:
|
||||||
|
# step/dir pins
|
||||||
|
X_DIR=P8_26, X_STEP=P8_17, Y_DIR=P8_19, Y_STEP=P8_12, Z_DIR=P8_14, Z_STEP=P8_13,
|
||||||
|
E_DIR=P8_15, E_STEP=P9_12, H_DIR=P8_16, H_STEP=P8_11,
|
||||||
|
# stepper fault pins
|
||||||
|
FAULT_X=P8_10, FAULT_Y=P8_9, FAULT_Z=P9_24, FAULT_E=P8_18, FAULT_H=P8_8,
|
||||||
|
# endstops
|
||||||
|
STOP_X1=P9_25, STOP_X2=P9_11, STOP_Y1=P9_23, STOP_Y2=P9_28, STOP_Z1=P9_13, STOP_Z2=P9_18,
|
||||||
|
# enable steppers (all on one pin)
|
||||||
|
STEPPER_ENABLE=P9_41,
|
||||||
|
# servos
|
||||||
|
SERVO_0=P9_14, SERVO_1=P9_16,
|
||||||
|
# Thermistors
|
||||||
|
THERM_E=P9_33, THERM_H=P9_36, THERM_BED=P9_35,
|
||||||
|
# D1W pin
|
||||||
|
DALLAS=P9_22
|
||||||
|
|
||||||
|
[board_pins host]
|
||||||
|
aliases:
|
||||||
|
# Host aliases for Linux MCU
|
||||||
|
HOST_X2_STOP=gpio30, HOST_Y2_STOP=gpio113, HOST_Z2_STOP=gpio4
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
# This file contains pin mappings for the stock 2020 Creality Ender 3
|
||||||
|
# Pro with the 32-bit Creality 4.2.2 board. To use this config, during
|
||||||
|
# "make menuconfig" select the STM32F103 with a "28KiB bootloader" and
|
||||||
|
# with "Use USB for communication" disabled.
|
||||||
|
|
||||||
|
# If you prefer a direct serial connection, in "make menuconfig"
|
||||||
|
# select "Enable extra low-level configuration options" and select the
|
||||||
|
# USART3 serial port, which is broken out on the 10 pin IDC cable used
|
||||||
|
# for the LCD module as follows:
|
||||||
|
# 3: Tx, 4: Rx, 9: GND, 10: VCC
|
||||||
|
|
||||||
|
# Flash this firmware by copying "out/klipper.bin" to a SD card and
|
||||||
|
# turning on the printer with the card inserted. The firmware
|
||||||
|
# filename must end in ".bin" and must not match the last filename
|
||||||
|
# that was flashed.
|
||||||
|
|
||||||
|
# See the example.cfg file for a description of available parameters.
|
||||||
|
|
||||||
|
[stepper_x]
|
||||||
|
step_pin: PC2
|
||||||
|
dir_pin: PB9
|
||||||
|
enable_pin: !PC3
|
||||||
|
step_distance: .0125
|
||||||
|
endstop_pin: ^PA5
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 235
|
||||||
|
homing_speed: 50
|
||||||
|
|
||||||
|
[stepper_y]
|
||||||
|
step_pin: PB8
|
||||||
|
dir_pin: PB7
|
||||||
|
enable_pin: !PC3
|
||||||
|
step_distance: .0125
|
||||||
|
endstop_pin: ^PA6
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 235
|
||||||
|
homing_speed: 50
|
||||||
|
|
||||||
|
[stepper_z]
|
||||||
|
step_pin: PB6
|
||||||
|
dir_pin: !PB5
|
||||||
|
enable_pin: !PC3
|
||||||
|
step_distance: .0025
|
||||||
|
endstop_pin: ^PA7
|
||||||
|
position_endstop: 0.0
|
||||||
|
position_max: 250
|
||||||
|
|
||||||
|
[extruder]
|
||||||
|
max_extrude_only_distance: 100.0
|
||||||
|
step_pin: PB4
|
||||||
|
dir_pin: PB3
|
||||||
|
enable_pin: !PC3
|
||||||
|
step_distance: 0.010752
|
||||||
|
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 200 degree Celsius target
|
||||||
|
pid_Kp: 21.527
|
||||||
|
pid_Ki: 1.063
|
||||||
|
pid_Kd: 108.982
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 250
|
||||||
|
|
||||||
|
[heater_bed]
|
||||||
|
heater_pin: PA2
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PC4
|
||||||
|
control: pid
|
||||||
|
# tuned for stock hardware with 50 degree Celsius target
|
||||||
|
pid_Kp: 54.027
|
||||||
|
pid_Ki: 0.770
|
||||||
|
pid_Kd: 948.182
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 130
|
||||||
|
|
||||||
|
[fan]
|
||||||
|
pin: PA0
|
||||||
|
|
||||||
|
[mcu]
|
||||||
|
serial: /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: cartesian
|
||||||
|
max_velocity: 300
|
||||||
|
max_accel: 3000
|
||||||
|
max_z_velocity: 5
|
||||||
|
max_z_accel: 100
|
||||||
|
|
||||||
|
# Pin mappings for BL_T port
|
||||||
|
#[bltouch]
|
||||||
|
#sensor_pin: ^PB1
|
||||||
|
#control_pin: PB0
|
||||||
|
|
||||||
|
[display]
|
||||||
|
lcd_type: st7920
|
||||||
|
cs_pin: PB12
|
||||||
|
sclk_pin: PB13
|
||||||
|
sid_pin: PB15
|
||||||
|
encoder_pins: ^PB14, ^PB10
|
||||||
|
click_pin: ^!PB2
|
|
@ -0,0 +1,136 @@
|
||||||
|
# This is a Klipper configuration for TronXY X5SA, with
|
||||||
|
# CXY-V6 motherboard.
|
||||||
|
|
||||||
|
|
||||||
|
# === FLASHING WITH STOCK BOOTLOADER ===
|
||||||
|
# You should make firmware for STM32F103 with bootloader offset
|
||||||
|
# at 0x8008800 (Chitu v6 Bootloader). Uncheck USB, and leave default
|
||||||
|
# serial settings.
|
||||||
|
#
|
||||||
|
# Use "./update_chitu.py out/klipper.bin update.cbd" to generate update.cbd.
|
||||||
|
# Put `update.cbd` onto SD card, and reboot the printer.
|
||||||
|
# It will be automatically installed, and you will be able to update it this way.
|
||||||
|
|
||||||
|
[mcu]
|
||||||
|
serial: /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0
|
||||||
|
restart_method: command
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: corexy
|
||||||
|
max_velocity: 300
|
||||||
|
max_accel: 3000
|
||||||
|
max_z_velocity: 25
|
||||||
|
max_z_accel: 30
|
||||||
|
|
||||||
|
[stepper_x]
|
||||||
|
step_pin: PE5
|
||||||
|
dir_pin: !PE6
|
||||||
|
enable_pin: !PC13
|
||||||
|
step_distance: .006275
|
||||||
|
endstop_pin: !PG10
|
||||||
|
position_endstop: -1
|
||||||
|
position_min: -1
|
||||||
|
position_max: 350
|
||||||
|
homing_speed: 50
|
||||||
|
homing_retract_dist: 10
|
||||||
|
second_homing_speed: 10.0
|
||||||
|
|
||||||
|
[stepper_y]
|
||||||
|
step_pin: PE2
|
||||||
|
dir_pin: !PE3
|
||||||
|
enable_pin: !PE4
|
||||||
|
step_distance: .006275
|
||||||
|
endstop_pin: !PA12
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 330
|
||||||
|
homing_retract_dist: 10
|
||||||
|
homing_speed: 50.0
|
||||||
|
second_homing_speed: 10.0
|
||||||
|
|
||||||
|
[stepper_z]
|
||||||
|
step_pin: PB9
|
||||||
|
dir_pin: PE0
|
||||||
|
enable_pin: !PE1
|
||||||
|
step_distance: .00125
|
||||||
|
endstop_pin: probe:z_virtual_endstop
|
||||||
|
position_max: 400
|
||||||
|
position_min: -2
|
||||||
|
|
||||||
|
[extruder]
|
||||||
|
step_pin: PB4
|
||||||
|
dir_pin: PB5
|
||||||
|
enable_pin: !PB8
|
||||||
|
step_distance: 0.0111
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: PG12
|
||||||
|
sensor_type: ATC Semitec 104GT-2
|
||||||
|
sensor_pin: PA1
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 18.831
|
||||||
|
pid_Ki: 0.821
|
||||||
|
pid_Kd: 108.044
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 250
|
||||||
|
max_extrude_only_distance: 300
|
||||||
|
|
||||||
|
[heater_bed]
|
||||||
|
heater_pin: PG11
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PA0
|
||||||
|
control: pid
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 130
|
||||||
|
pid_Kp: 73.932
|
||||||
|
pid_Ki: 1.521
|
||||||
|
pid_Kd: 898.279
|
||||||
|
|
||||||
|
[heater_fan hotend_fan]
|
||||||
|
pin: PG14
|
||||||
|
fan_speed: 0.5
|
||||||
|
|
||||||
|
[fan]
|
||||||
|
pin: PG13
|
||||||
|
max_power: 0.5
|
||||||
|
|
||||||
|
[controller_fan drivers_fan]
|
||||||
|
pin: PD6
|
||||||
|
|
||||||
|
[filament_switch_sensor sentinel]
|
||||||
|
pause_on_runout: True
|
||||||
|
runout_gcode:
|
||||||
|
M25
|
||||||
|
switch_pin: PA15
|
||||||
|
|
||||||
|
[output_pin beeper]
|
||||||
|
pin: PB0
|
||||||
|
|
||||||
|
[safe_z_home]
|
||||||
|
home_xy_position: 165,165
|
||||||
|
speed: 50
|
||||||
|
z_hop: 10
|
||||||
|
z_hop_speed: 5
|
||||||
|
|
||||||
|
[bed_screws]
|
||||||
|
screw1: 5,5
|
||||||
|
screw2: 165,5
|
||||||
|
screw3: 325,5
|
||||||
|
screw4: 5,325
|
||||||
|
screw5: 165,325
|
||||||
|
screw6: 325,325
|
||||||
|
|
||||||
|
[bed_mesh]
|
||||||
|
speed: 120
|
||||||
|
probe_count: 5,5
|
||||||
|
horizontal_move_z: 5
|
||||||
|
algorithm: lagrange
|
||||||
|
mesh_min : 20,20
|
||||||
|
mesh_max : 310,310
|
||||||
|
mesh_pps: 0
|
||||||
|
|
||||||
|
[probe]
|
||||||
|
x_offset: -40
|
||||||
|
y_offset: 0
|
||||||
|
pin: !PG9
|
||||||
|
speed: 30
|
||||||
|
z_offset: 2
|
|
@ -295,13 +295,30 @@ the coordinates listed under the "Probe" header to find the correct index.
|
||||||
|
|
||||||
### Calibration
|
### Calibration
|
||||||
|
|
||||||
`BED_MESH_CALIBRATE METHOD=[manual | automatic]`\
|
`BED_MESH_CALIBRATE METHOD=[manual | automatic] [<probe_parameter>=<value>]
|
||||||
|
[<mesh_parameter>=<value>]`\
|
||||||
_Default Method: automatic if a probe is detected, otherwise manual_
|
_Default Method: automatic if a probe is detected, otherwise manual_
|
||||||
|
|
||||||
Initiates the probing procedure for Bed Mesh Calibration. If `METHOD=manual`
|
Initiates the probing procedure for Bed Mesh Calibration. If `METHOD=manual`
|
||||||
is selected then manual probing will occur. When switching between automatic
|
is selected then manual probing will occur. When switching between automatic
|
||||||
and manual probing the generated mesh points will automatically be adjusted.
|
and manual probing the generated mesh points will automatically be adjusted.
|
||||||
|
|
||||||
|
It is possible to specify mesh parameters to modify the probed area. The
|
||||||
|
following parameters are available:
|
||||||
|
- Rectangular beds (cartesian):
|
||||||
|
- `MESH_MIN`
|
||||||
|
- `MESH_MAX`
|
||||||
|
- `PROBE_COUNT`
|
||||||
|
- Round beds (delta):
|
||||||
|
- `MESH_RADIUS`
|
||||||
|
- `MESH_ORIGIN`
|
||||||
|
- `ROUND_PROBE_COUNT`
|
||||||
|
- All beds:
|
||||||
|
- `RELATIVE_REFERNCE_INDEX`
|
||||||
|
- `ALGORITHM`
|
||||||
|
See the configuration documentation above for details on how each parameter
|
||||||
|
applies to the mesh.
|
||||||
|
|
||||||
### Profiles
|
### Profiles
|
||||||
|
|
||||||
`BED_MESH_PROFILE SAVE=name LOAD=name REMOVE=name`
|
`BED_MESH_PROFILE SAVE=name LOAD=name REMOVE=name`
|
||||||
|
@ -357,3 +374,9 @@ set, the generated probed points will be output to the terminal:
|
||||||
The "Tool Adjusted" points refer to the nozzle location for each point, and
|
The "Tool Adjusted" points refer to the nozzle location for each point, and
|
||||||
the "Probe" points refer to the probe location. Note that when manually
|
the "Probe" points refer to the probe location. Note that when manually
|
||||||
probing the "Probe" points will refer to both the tool and nozzle locations.
|
probing the "Probe" points will refer to both the tool and nozzle locations.
|
||||||
|
|
||||||
|
### Clear Mesh State
|
||||||
|
|
||||||
|
`BED_MESH_CLEAR`
|
||||||
|
|
||||||
|
This gcode may be used to clear the internal mesh state.
|
||||||
|
|
|
@ -192,8 +192,12 @@ The following are common printer attributes:
|
||||||
- `printer.query_endstops.last_query["<endstop>"]`: Returns True if
|
- `printer.query_endstops.last_query["<endstop>"]`: Returns True if
|
||||||
the given endstop was reported as "triggered" during the last
|
the given endstop was reported as "triggered" during the last
|
||||||
QUERY_ENDSTOP command. Note, due to the order of template expansion
|
QUERY_ENDSTOP command. Note, due to the order of template expansion
|
||||||
(see above), the QUERY_STATUS command must be run prior to the macro
|
(see above), the QUERY_ENDSTOP command must be run prior to the
|
||||||
containing this reference.
|
macro containing this reference.
|
||||||
|
- `printer.probe.last_query`: Returns True if the probe was reported
|
||||||
|
as "triggered" during the last QUERY_PROBE command. Note, due to the
|
||||||
|
order of template expansion (see above), the QUERY_PROBE command
|
||||||
|
must be run prior to the macro containing this reference.
|
||||||
- `printer.configfile.config["<section>"]["<option>"]`: Returns the
|
- `printer.configfile.config["<section>"]["<option>"]`: Returns the
|
||||||
given config file setting as read by Klipper during the last
|
given config file setting as read by Klipper during the last
|
||||||
software start or restart. (Any settings changed at run-time will
|
software start or restart. (Any settings changed at run-time will
|
||||||
|
|
|
@ -6,6 +6,10 @@ All dates in this document are approximate.
|
||||||
|
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
|
20200902: The RTD resistance-to-temperature calculation for MAX31865
|
||||||
|
converters has been corrected to not read low. If you are using such a
|
||||||
|
device, you should recalibrate your print temperature and PID settings.
|
||||||
|
|
||||||
20200816: The gcode macro `printer.gcode` object has been renamed to
|
20200816: The gcode macro `printer.gcode` object has been renamed to
|
||||||
`printer.gcode_move`. Several undocumented variables in
|
`printer.gcode_move`. Several undocumented variables in
|
||||||
`printer.toolhead` and `printer.gcode` have been removed. See
|
`printer.toolhead` and `printer.gcode` have been removed. See
|
||||||
|
|
12
docs/FAQ.md
12
docs/FAQ.md
|
@ -163,6 +163,18 @@ machine. See the
|
||||||
[install-octopi.sh](https://github.com/KevinOConnor/klipper/tree/master/scripts/install-octopi.sh)
|
[install-octopi.sh](https://github.com/KevinOConnor/klipper/tree/master/scripts/install-octopi.sh)
|
||||||
script for further information on the necessary Linux admin steps.
|
script for further information on the necessary Linux admin steps.
|
||||||
|
|
||||||
|
If you are looking to run the Klipper host software on a low-end chip,
|
||||||
|
then be aware that, at a minimum, a machine with "double precision
|
||||||
|
floating point" hardware is required.
|
||||||
|
|
||||||
|
If you are looking to run the Klipper host software on a shared
|
||||||
|
general-purpose desktop or server class machine, then note that
|
||||||
|
Klipper has some real-time scheduling requirements. If, during a
|
||||||
|
print, the host computer also performs an intensive general-purpose
|
||||||
|
computing task (such as defragmenting a hard drive, 3d rendering,
|
||||||
|
heavy swapping, etc.), then it may cause Klipper to report print
|
||||||
|
errors.
|
||||||
|
|
||||||
Note: If you are not using an OctoPi image, be aware that several
|
Note: If you are not using an OctoPi image, be aware that several
|
||||||
Linux distributions enable a "ModemManager" (or similar) package that
|
Linux distributions enable a "ModemManager" (or similar) package that
|
||||||
can disrupt serial communication. (Which can cause Klipper to report
|
can disrupt serial communication. (Which can cause Klipper to report
|
||||||
|
|
|
@ -382,7 +382,8 @@ section is enabled:
|
||||||
|
|
||||||
The following commands are available when the "bed_mesh" config
|
The following commands are available when the "bed_mesh" config
|
||||||
section is enabled:
|
section is enabled:
|
||||||
- `BED_MESH_CALIBRATE [METHOD=manual] [<probe_parameter>=<value>]`:
|
- `BED_MESH_CALIBRATE [METHOD=manual] [<probe_parameter>=<value>]
|
||||||
|
[<mesh_parameter>=<value>]`:
|
||||||
This command probes the bed using generated points specified by the
|
This command probes the bed using generated points specified by the
|
||||||
parameters in the config. After probing, a mesh is generated and
|
parameters in the config. After probing, a mesh is generated and
|
||||||
z-movement is adjusted according to the mesh. See the PROBE command
|
z-movement is adjusted according to the mesh. See the PROBE command
|
||||||
|
@ -488,13 +489,15 @@ section is enabled:
|
||||||
the given distance (in mm) at the given constant velocity (in
|
the given distance (in mm) at the given constant velocity (in
|
||||||
mm/s). If ACCEL is specified and is greater than zero, then the
|
mm/s). If ACCEL is specified and is greater than zero, then the
|
||||||
given acceleration (in mm/s^2) will be used; otherwise no
|
given acceleration (in mm/s^2) will be used; otherwise no
|
||||||
acceleration is performed. No boundary checks are performed; no
|
acceleration is performed. If acceleration is not performed then it
|
||||||
kinematic updates are made; other parallel steppers on an axis will
|
can lead to the micro-controller reporting "No next step" errors
|
||||||
not be moved. Use caution as an incorrect command could cause
|
(avoid these errors by specifying an ACCEL value or use a very low
|
||||||
damage! Using this command will almost certainly place the low-level
|
VELOCITY). No boundary checks are performed; no kinematic updates
|
||||||
kinematics in an incorrect state; issue a G28 afterwards to reset
|
are made; other parallel steppers on an axis will not be moved. Use
|
||||||
the kinematics. This command is intended for low-level diagnostics
|
caution as an incorrect command could cause damage! Using this
|
||||||
and debugging.
|
command will almost certainly place the low-level kinematics in an
|
||||||
|
incorrect state; issue a G28 afterwards to reset the kinematics.
|
||||||
|
This command is intended for low-level diagnostics and debugging.
|
||||||
- `SET_KINEMATIC_POSITION [X=<value>] [Y=<value>] [Z=<value>]`: Force
|
- `SET_KINEMATIC_POSITION [X=<value>] [Y=<value>] [Z=<value>]`: Force
|
||||||
the low-level kinematic code to believe the toolhead is at the given
|
the low-level kinematic code to believe the toolhead is at the given
|
||||||
cartesian position. This is a diagnostic and debugging command; use
|
cartesian position. This is a diagnostic and debugging command; use
|
||||||
|
@ -633,3 +636,17 @@ section is enabled:
|
||||||
[target=<target_temperature>]`: Sets the target temperature for a
|
[target=<target_temperature>]`: Sets the target temperature for a
|
||||||
temperature_fan. If a target is not supplied, it is set to the
|
temperature_fan. If a target is not supplied, it is set to the
|
||||||
specified temperature in the config file.
|
specified temperature in the config file.
|
||||||
|
|
||||||
|
## Adxl345 Accelerometer Commands
|
||||||
|
|
||||||
|
The following command is available when an "adxl345" config section is
|
||||||
|
enabled:
|
||||||
|
- `ACCELEROMETER_MEASURE [CHIP=<config_name>] [RATE=<value>]
|
||||||
|
[NAME=<value>]`: Starts accelerometer measurements at the requested
|
||||||
|
number of samples per second. If CHIP is not specified it defaults
|
||||||
|
to "default". Valid rates are 25, 50, 100, 200, 400, 800, 1600,
|
||||||
|
and 3200. If RATE is zero (or not specified) then the current series
|
||||||
|
of measurements are stopped and the results are written to a file
|
||||||
|
named `/tmp/adxl345-<name>.csv` where "<name>" is the optional NAME
|
||||||
|
parameter. If NAME is not specified it defaults to the current time
|
||||||
|
in "YYYYMMDD_HHMMSS" format.
|
||||||
|
|
|
@ -13,11 +13,11 @@ Prepping an OS image
|
||||||
====================
|
====================
|
||||||
|
|
||||||
Start by installing [OctoPi](https://github.com/guysoft/OctoPi) on the
|
Start by installing [OctoPi](https://github.com/guysoft/OctoPi) on the
|
||||||
Raspberry Pi computer. Use OctoPi v0.16.0 or later - see the
|
Raspberry Pi computer. Use OctoPi v0.17.0 or later - see the
|
||||||
[octopi releases](https://github.com/guysoft/OctoPi/releases) for
|
[octopi releases](https://github.com/guysoft/OctoPi/releases) for
|
||||||
release information. One should verify that OctoPi boots and that the
|
release information. One should verify that OctoPi boots and that the
|
||||||
OctoPrint web server works. After connecting to the OctoPrint web
|
OctoPrint web server works. After connecting to the OctoPrint web
|
||||||
page, follow the prompt to upgrade OctoPrint to v1.3.12 or later.
|
page, follow the prompt to upgrade OctoPrint to v1.4.2 or later.
|
||||||
|
|
||||||
After installing OctoPi and upgrading OctoPrint, it will be necessary
|
After installing OctoPi and upgrading OctoPrint, it will be necessary
|
||||||
to ssh into the target machine to run a handful of system commands. If
|
to ssh into the target machine to run a handful of system commands. If
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Wrapper around C helper code
|
# Wrapper around C helper code
|
||||||
#
|
#
|
||||||
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2016-2020 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import os, logging
|
import os, logging
|
||||||
|
@ -11,9 +11,11 @@ import cffi
|
||||||
# c_helper.so compiling
|
# c_helper.so compiling
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
COMPILE_CMD = ("gcc -Wall -g -O2 -shared -fPIC"
|
GCC_CMD = "gcc"
|
||||||
" -flto -fwhole-program -fno-use-linker-plugin"
|
COMPILE_ARGS = ("-Wall -g -O2 -shared -fPIC"
|
||||||
" -o %s %s")
|
" -flto -fwhole-program -fno-use-linker-plugin"
|
||||||
|
" -o %s %s")
|
||||||
|
SSE_FLAGS = "-mfpmath=sse -msse2"
|
||||||
SOURCE_FILES = [
|
SOURCE_FILES = [
|
||||||
'pyhelper.c', 'serialqueue.c', 'stepcompress.c', 'itersolve.c', 'trapq.c',
|
'pyhelper.c', 'serialqueue.c', 'stepcompress.c', 'itersolve.c', 'trapq.c',
|
||||||
'kin_cartesian.c', 'kin_corexy.c', 'kin_corexz.c', 'kin_delta.c',
|
'kin_cartesian.c', 'kin_corexy.c', 'kin_corexz.c', 'kin_delta.c',
|
||||||
|
@ -190,7 +192,7 @@ def get_mtimes(srcdir, filelist):
|
||||||
|
|
||||||
# Check if the code needs to be compiled
|
# Check if the code needs to be compiled
|
||||||
def check_build_code(srcdir, target, sources, cmd, other_files=[]):
|
def check_build_code(srcdir, target, sources, cmd, other_files=[]):
|
||||||
src_times = get_mtimes(srcdir, sources + other_files)
|
src_times = get_mtimes(srcdir, sources + other_files + [__file__])
|
||||||
obj_times = get_mtimes(srcdir, [target])
|
obj_times = get_mtimes(srcdir, [target])
|
||||||
if not obj_times or max(src_times) > min(obj_times):
|
if not obj_times or max(src_times) > min(obj_times):
|
||||||
logging.info("Building C code module %s", target)
|
logging.info("Building C code module %s", target)
|
||||||
|
@ -202,6 +204,12 @@ def check_build_code(srcdir, target, sources, cmd, other_files=[]):
|
||||||
logging.error(msg)
|
logging.error(msg)
|
||||||
raise Exception(msg)
|
raise Exception(msg)
|
||||||
|
|
||||||
|
def check_gcc_option(option):
|
||||||
|
cmd = "%s %s -S -o /dev/null -xc /dev/null > /dev/null 2>&1" % (
|
||||||
|
GCC_CMD, option)
|
||||||
|
res = os.system(cmd)
|
||||||
|
return res == 0
|
||||||
|
|
||||||
FFI_main = None
|
FFI_main = None
|
||||||
FFI_lib = None
|
FFI_lib = None
|
||||||
pyhelper_logging_callback = None
|
pyhelper_logging_callback = None
|
||||||
|
@ -211,8 +219,11 @@ def get_ffi():
|
||||||
global FFI_main, FFI_lib, pyhelper_logging_callback
|
global FFI_main, FFI_lib, pyhelper_logging_callback
|
||||||
if FFI_lib is None:
|
if FFI_lib is None:
|
||||||
srcdir = os.path.dirname(os.path.realpath(__file__))
|
srcdir = os.path.dirname(os.path.realpath(__file__))
|
||||||
check_build_code(srcdir, DEST_LIB, SOURCE_FILES, COMPILE_CMD
|
if check_gcc_option(SSE_FLAGS):
|
||||||
, OTHER_FILES)
|
cmd = "%s %s %s" % (GCC_CMD, SSE_FLAGS, COMPILE_ARGS)
|
||||||
|
else:
|
||||||
|
cmd = "%s %s" % (GCC_CMD, COMPILE_ARGS)
|
||||||
|
check_build_code(srcdir, DEST_LIB, SOURCE_FILES, cmd, OTHER_FILES)
|
||||||
FFI_main = cffi.FFI()
|
FFI_main = cffi.FFI()
|
||||||
for d in defs_all:
|
for d in defs_all:
|
||||||
FFI_main.cdef(d)
|
FFI_main.cdef(d)
|
||||||
|
|
|
@ -13,6 +13,225 @@
|
||||||
#include "itersolve.h" // struct stepper_kinematics
|
#include "itersolve.h" // struct stepper_kinematics
|
||||||
#include "trapq.h" // struct move
|
#include "trapq.h" // struct move
|
||||||
|
|
||||||
|
|
||||||
|
/****************************************************************
|
||||||
|
* Shaper-specific initialization
|
||||||
|
****************************************************************/
|
||||||
|
|
||||||
|
struct shaper_pulses {
|
||||||
|
int num_pulses;
|
||||||
|
struct {
|
||||||
|
double t, a;
|
||||||
|
} pulses[5];
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline double
|
||||||
|
calc_ZV_K(double damping_ratio)
|
||||||
|
{
|
||||||
|
if (likely(!damping_ratio))
|
||||||
|
return 1.;
|
||||||
|
return exp(-damping_ratio * M_PI / sqrt(1. - damping_ratio*damping_ratio));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline double
|
||||||
|
calc_half_period(double shaper_freq, double damping_ratio)
|
||||||
|
{
|
||||||
|
return .5 / (shaper_freq * sqrt(1. - damping_ratio*damping_ratio));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
init_shaper_zv(double shaper_freq, double damping_ratio
|
||||||
|
, struct shaper_pulses *sp)
|
||||||
|
{
|
||||||
|
sp->num_pulses = 2;
|
||||||
|
|
||||||
|
double half_period = calc_half_period(shaper_freq, damping_ratio);
|
||||||
|
double K = calc_ZV_K(damping_ratio);
|
||||||
|
double inv_D = 1. / (1. + K);
|
||||||
|
|
||||||
|
sp->pulses[0].t = -half_period;
|
||||||
|
sp->pulses[1].t = 0.;
|
||||||
|
|
||||||
|
sp->pulses[0].a = K * inv_D;
|
||||||
|
sp->pulses[1].a = inv_D;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
init_shaper_zvd(double shaper_freq, double damping_ratio
|
||||||
|
, struct shaper_pulses *sp)
|
||||||
|
{
|
||||||
|
sp->num_pulses = 3;
|
||||||
|
|
||||||
|
double half_period = calc_half_period(shaper_freq, damping_ratio);
|
||||||
|
double K = calc_ZV_K(damping_ratio);
|
||||||
|
double K2 = K * K;
|
||||||
|
double inv_D = 1. / (K2 + 2. * K + 1.);
|
||||||
|
|
||||||
|
sp->pulses[0].t = -2. * half_period;
|
||||||
|
sp->pulses[1].t = -half_period;
|
||||||
|
sp->pulses[2].t = 0.;
|
||||||
|
|
||||||
|
sp->pulses[0].a = K2 * inv_D;
|
||||||
|
sp->pulses[1].a = 2. * K * inv_D;
|
||||||
|
sp->pulses[2].a = inv_D;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
init_shaper_mzv(double shaper_freq, double damping_ratio
|
||||||
|
, struct shaper_pulses *sp)
|
||||||
|
{
|
||||||
|
sp->num_pulses = 3;
|
||||||
|
|
||||||
|
double half_period = calc_half_period(shaper_freq, damping_ratio);
|
||||||
|
double K = exp(-.75 * damping_ratio * M_PI
|
||||||
|
/ sqrt(1. - damping_ratio*damping_ratio));
|
||||||
|
|
||||||
|
double a1 = 1. - 1. / sqrt(2.);
|
||||||
|
double a2 = (sqrt(2.) - 1.) * K;
|
||||||
|
double a3 = a1 * K * K;
|
||||||
|
double inv_D = 1. / (a1 + a2 + a3);
|
||||||
|
|
||||||
|
sp->pulses[0].t = -1.5 * half_period;
|
||||||
|
sp->pulses[1].t = -.75 * half_period;
|
||||||
|
sp->pulses[2].t = 0.;
|
||||||
|
|
||||||
|
sp->pulses[0].a = a3 * inv_D;
|
||||||
|
sp->pulses[1].a = a2 * inv_D;
|
||||||
|
sp->pulses[2].a = a1 * inv_D;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define EI_SHAPER_VIB_TOL 0.05
|
||||||
|
|
||||||
|
static void
|
||||||
|
init_shaper_ei(double shaper_freq, double damping_ratio
|
||||||
|
, struct shaper_pulses *sp)
|
||||||
|
{
|
||||||
|
sp->num_pulses = 3;
|
||||||
|
|
||||||
|
double half_period = calc_half_period(shaper_freq, damping_ratio);
|
||||||
|
double K = calc_ZV_K(damping_ratio);
|
||||||
|
double a1 = .25 * (1. + EI_SHAPER_VIB_TOL);
|
||||||
|
double a2 = .5 * (1. - EI_SHAPER_VIB_TOL) * K;
|
||||||
|
double a3 = a1 * K * K;
|
||||||
|
double inv_D = 1. / (a1 + a2 + a3);
|
||||||
|
|
||||||
|
sp->pulses[0].t = -2. * half_period;
|
||||||
|
sp->pulses[1].t = -half_period;
|
||||||
|
sp->pulses[2].t = 0.;
|
||||||
|
|
||||||
|
sp->pulses[0].a = a3 * inv_D;
|
||||||
|
sp->pulses[1].a = a2 * inv_D;
|
||||||
|
sp->pulses[2].a = a1 * inv_D;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
init_shaper_2hump_ei(double shaper_freq, double damping_ratio
|
||||||
|
, struct shaper_pulses *sp)
|
||||||
|
{
|
||||||
|
sp->num_pulses = 4;
|
||||||
|
|
||||||
|
double half_period = calc_half_period(shaper_freq, damping_ratio);
|
||||||
|
double K = calc_ZV_K(damping_ratio);
|
||||||
|
|
||||||
|
double V2 = EI_SHAPER_VIB_TOL * EI_SHAPER_VIB_TOL;
|
||||||
|
double X = pow(V2 * (sqrt(1. - V2) + 1.), 1./3.);
|
||||||
|
double a1 = (3.*X*X + 2.*X + 3.*V2) / (16.*X);
|
||||||
|
double a2 = (.5 - a1) * K;
|
||||||
|
double a3 = a2 * K;
|
||||||
|
double a4 = a1 * K * K * K;
|
||||||
|
double inv_D = 1. / (a1 + a2 + a3 + a4);
|
||||||
|
|
||||||
|
sp->pulses[0].t = -3. * half_period;
|
||||||
|
sp->pulses[1].t = -2. * half_period;
|
||||||
|
sp->pulses[2].t = -half_period;
|
||||||
|
sp->pulses[3].t = 0.;
|
||||||
|
|
||||||
|
sp->pulses[0].a = a4 * inv_D;
|
||||||
|
sp->pulses[1].a = a3 * inv_D;
|
||||||
|
sp->pulses[2].a = a2 * inv_D;
|
||||||
|
sp->pulses[3].a = a1 * inv_D;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
init_shaper_3hump_ei(double shaper_freq, double damping_ratio
|
||||||
|
, struct shaper_pulses *sp)
|
||||||
|
{
|
||||||
|
sp->num_pulses = 5;
|
||||||
|
|
||||||
|
double half_period = calc_half_period(shaper_freq, damping_ratio);
|
||||||
|
double K = calc_ZV_K(damping_ratio);
|
||||||
|
double K2 = K * K;
|
||||||
|
|
||||||
|
double a1 = 0.0625 * (1. + 3. * EI_SHAPER_VIB_TOL
|
||||||
|
+ 2. * sqrt(2. * (EI_SHAPER_VIB_TOL + 1.) * EI_SHAPER_VIB_TOL));
|
||||||
|
double a2 = 0.25 * (1. - EI_SHAPER_VIB_TOL) * K;
|
||||||
|
double a3 = (0.5 * (1. + EI_SHAPER_VIB_TOL) - 2. * a1) * K2;
|
||||||
|
double a4 = a2 * K2;
|
||||||
|
double a5 = a1 * K2 * K2;
|
||||||
|
double inv_D = 1. / (a1 + a2 + a3 + a4 + a5);
|
||||||
|
|
||||||
|
sp->pulses[0].t = -4. * half_period;
|
||||||
|
sp->pulses[1].t = -3. * half_period;
|
||||||
|
sp->pulses[2].t = -2. * half_period;
|
||||||
|
sp->pulses[3].t = -half_period;
|
||||||
|
sp->pulses[4].t = 0.;
|
||||||
|
|
||||||
|
sp->pulses[0].a = a5 * inv_D;
|
||||||
|
sp->pulses[1].a = a4 * inv_D;
|
||||||
|
sp->pulses[2].a = a3 * inv_D;
|
||||||
|
sp->pulses[3].a = a2 * inv_D;
|
||||||
|
sp->pulses[4].a = a1 * inv_D;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shift pulses around 'mid-point' t=0 so that the input shaper is an identity
|
||||||
|
// transformation for constant-speed motion (i.e. input_shaper(v * T) = v * T)
|
||||||
|
static void
|
||||||
|
shift_pulses(struct shaper_pulses *sp)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
double ts = 0.;
|
||||||
|
for (i = 0; i < sp->num_pulses; ++i)
|
||||||
|
ts += sp->pulses[i].a * sp->pulses[i].t;
|
||||||
|
for (i = 0; i < sp->num_pulses; ++i)
|
||||||
|
sp->pulses[i].t -= ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum INPUT_SHAPER_TYPE {
|
||||||
|
INPUT_SHAPER_ZV = 0,
|
||||||
|
INPUT_SHAPER_ZVD = 1,
|
||||||
|
INPUT_SHAPER_MZV = 2,
|
||||||
|
INPUT_SHAPER_EI = 3,
|
||||||
|
INPUT_SHAPER_2HUMP_EI = 4,
|
||||||
|
INPUT_SHAPER_3HUMP_EI = 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef void (*is_init_shaper_callback)(double shaper_freq
|
||||||
|
, double damping_ratio
|
||||||
|
, struct shaper_pulses *sp);
|
||||||
|
|
||||||
|
static is_init_shaper_callback init_shaper_callbacks[] = {
|
||||||
|
[INPUT_SHAPER_ZV] = &init_shaper_zv,
|
||||||
|
[INPUT_SHAPER_ZVD] = &init_shaper_zvd,
|
||||||
|
[INPUT_SHAPER_MZV] = &init_shaper_mzv,
|
||||||
|
[INPUT_SHAPER_EI] = &init_shaper_ei,
|
||||||
|
[INPUT_SHAPER_2HUMP_EI] = &init_shaper_2hump_ei,
|
||||||
|
[INPUT_SHAPER_3HUMP_EI] = &init_shaper_3hump_ei,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
init_shaper(int shaper_type, double shaper_freq, double damping_ratio
|
||||||
|
, struct shaper_pulses *sp)
|
||||||
|
{
|
||||||
|
if (shaper_type < 0 || shaper_type >= ARRAY_SIZE(init_shaper_callbacks)
|
||||||
|
|| shaper_freq <= 0.) {
|
||||||
|
sp->num_pulses = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
init_shaper_callbacks[shaper_type](shaper_freq, damping_ratio, sp);
|
||||||
|
shift_pulses(sp);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/****************************************************************
|
/****************************************************************
|
||||||
* Generic position calculation via shaper convolution
|
* Generic position calculation via shaper convolution
|
||||||
****************************************************************/
|
****************************************************************/
|
||||||
|
@ -40,224 +259,20 @@ get_axis_position_across_moves(struct move *m, int axis, double time)
|
||||||
return get_axis_position(m, axis, time);
|
return get_axis_position(m, axis, time);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct shaper_pulse {
|
|
||||||
double t, a;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Calculate the position from the convolution of the shaper with input signal
|
// Calculate the position from the convolution of the shaper with input signal
|
||||||
static inline double
|
static inline double
|
||||||
calc_position(struct move *m, int axis, double move_time
|
calc_position(struct move *m, int axis, double move_time
|
||||||
, struct shaper_pulse *pulses, int n)
|
, struct shaper_pulses *sp)
|
||||||
{
|
{
|
||||||
double res = 0.;
|
double res = 0.;
|
||||||
int i;
|
int num_pulses = sp->num_pulses, i;
|
||||||
for (i = 0; i < n; ++i)
|
for (i = 0; i < num_pulses; ++i) {
|
||||||
res += pulses[i].a * get_axis_position_across_moves(
|
double t = sp->pulses[i].t, a = sp->pulses[i].a;
|
||||||
m, axis, move_time + pulses[i].t);
|
res += a * get_axis_position_across_moves(m, axis, move_time + t);
|
||||||
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
/****************************************************************
|
|
||||||
* Shaper-specific initialization
|
|
||||||
****************************************************************/
|
|
||||||
|
|
||||||
#define EI_SHAPER_VIB_TOL 0.05
|
|
||||||
|
|
||||||
enum INPUT_SHAPER_TYPE {
|
|
||||||
INPUT_SHAPER_ZV = 0,
|
|
||||||
INPUT_SHAPER_ZVD = 1,
|
|
||||||
INPUT_SHAPER_MZV = 2,
|
|
||||||
INPUT_SHAPER_EI = 3,
|
|
||||||
INPUT_SHAPER_2HUMP_EI = 4,
|
|
||||||
INPUT_SHAPER_3HUMP_EI = 5,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct input_shaper {
|
|
||||||
struct stepper_kinematics sk;
|
|
||||||
struct stepper_kinematics *orig_sk;
|
|
||||||
struct move m;
|
|
||||||
struct shaper_pulse *x_pulses, *y_pulses;
|
|
||||||
int x_n, y_n;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef void (*is_init_shaper_callback)(double shaper_freq
|
|
||||||
, double damping_ratio
|
|
||||||
, struct shaper_pulse **pulses, int *n);
|
|
||||||
|
|
||||||
static inline double
|
|
||||||
calc_ZV_K(double damping_ratio)
|
|
||||||
{
|
|
||||||
if (likely(!damping_ratio))
|
|
||||||
return 1.;
|
|
||||||
return exp(-damping_ratio * M_PI / sqrt(1. - damping_ratio*damping_ratio));
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline double
|
|
||||||
calc_half_period(double shaper_freq, double damping_ratio)
|
|
||||||
{
|
|
||||||
return .5 / (shaper_freq * sqrt(1. - damping_ratio*damping_ratio));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
init_shaper_zv(double shaper_freq, double damping_ratio
|
|
||||||
, struct shaper_pulse **pulses, int *n)
|
|
||||||
{
|
|
||||||
*n = 2;
|
|
||||||
*pulses = malloc(*n * sizeof(struct shaper_pulse));
|
|
||||||
|
|
||||||
double half_period = calc_half_period(shaper_freq, damping_ratio);
|
|
||||||
double K = calc_ZV_K(damping_ratio);
|
|
||||||
double inv_D = 1. / (1. + K);
|
|
||||||
|
|
||||||
(*pulses)[0].t = -half_period;
|
|
||||||
(*pulses)[1].t = 0.;
|
|
||||||
|
|
||||||
(*pulses)[0].a = K * inv_D;
|
|
||||||
(*pulses)[1].a = inv_D;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
init_shaper_zvd(double shaper_freq, double damping_ratio
|
|
||||||
, struct shaper_pulse **pulses, int *n)
|
|
||||||
{
|
|
||||||
*n = 3;
|
|
||||||
*pulses = malloc(*n * sizeof(struct shaper_pulse));
|
|
||||||
|
|
||||||
double half_period = calc_half_period(shaper_freq, damping_ratio);
|
|
||||||
double K = calc_ZV_K(damping_ratio);
|
|
||||||
double K2 = K * K;
|
|
||||||
double inv_D = 1. / (K2 + 2. * K + 1.);
|
|
||||||
|
|
||||||
(*pulses)[0].t = -2. * half_period;
|
|
||||||
(*pulses)[1].t = -half_period;
|
|
||||||
(*pulses)[2].t = 0.;
|
|
||||||
|
|
||||||
(*pulses)[0].a = K2 * inv_D;
|
|
||||||
(*pulses)[1].a = 2. * K * inv_D;
|
|
||||||
(*pulses)[2].a = inv_D;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
init_shaper_mzv(double shaper_freq, double damping_ratio
|
|
||||||
, struct shaper_pulse **pulses, int *n)
|
|
||||||
{
|
|
||||||
*n = 3;
|
|
||||||
*pulses = malloc(*n * sizeof(struct shaper_pulse));
|
|
||||||
|
|
||||||
double half_period = calc_half_period(shaper_freq, damping_ratio);
|
|
||||||
double K = exp(-.75 * damping_ratio * M_PI
|
|
||||||
/ sqrt(1. - damping_ratio*damping_ratio));
|
|
||||||
|
|
||||||
double a1 = 1. - 1. / sqrt(2.);
|
|
||||||
double a2 = (sqrt(2.) - 1.) * K;
|
|
||||||
double a3 = a1 * K * K;
|
|
||||||
double inv_D = 1. / (a1 + a2 + a3);
|
|
||||||
|
|
||||||
(*pulses)[0].t = -1.5 * half_period;
|
|
||||||
(*pulses)[1].t = -.75 * half_period;
|
|
||||||
(*pulses)[2].t = 0.;
|
|
||||||
|
|
||||||
(*pulses)[0].a = a3 * inv_D;
|
|
||||||
(*pulses)[1].a = a2 * inv_D;
|
|
||||||
(*pulses)[2].a = a1 * inv_D;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
init_shaper_ei(double shaper_freq, double damping_ratio
|
|
||||||
, struct shaper_pulse **pulses, int *n)
|
|
||||||
{
|
|
||||||
*n = 3;
|
|
||||||
*pulses = malloc(*n * sizeof(struct shaper_pulse));
|
|
||||||
|
|
||||||
double half_period = calc_half_period(shaper_freq, damping_ratio);
|
|
||||||
double K = calc_ZV_K(damping_ratio);
|
|
||||||
double a1 = .25 * (1. + EI_SHAPER_VIB_TOL);
|
|
||||||
double a2 = .5 * (1. - EI_SHAPER_VIB_TOL) * K;
|
|
||||||
double a3 = a1 * K * K;
|
|
||||||
double inv_D = 1. / (a1 + a2 + a3);
|
|
||||||
|
|
||||||
(*pulses)[0].t = -2. * half_period;
|
|
||||||
(*pulses)[1].t = -half_period;
|
|
||||||
(*pulses)[2].t = 0.;
|
|
||||||
|
|
||||||
(*pulses)[0].a = a3 * inv_D;
|
|
||||||
(*pulses)[1].a = a2 * inv_D;
|
|
||||||
(*pulses)[2].a = a1 * inv_D;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
init_shaper_2hump_ei(double shaper_freq, double damping_ratio
|
|
||||||
, struct shaper_pulse **pulses, int *n)
|
|
||||||
{
|
|
||||||
*n = 4;
|
|
||||||
*pulses = malloc(*n * sizeof(struct shaper_pulse));
|
|
||||||
|
|
||||||
double half_period = calc_half_period(shaper_freq, damping_ratio);
|
|
||||||
double K = calc_ZV_K(damping_ratio);
|
|
||||||
|
|
||||||
double V2 = EI_SHAPER_VIB_TOL * EI_SHAPER_VIB_TOL;
|
|
||||||
double X = pow(V2 * (sqrt(1. - V2) + 1.), 1./3.);
|
|
||||||
double a1 = (3.*X*X + 2.*X + 3.*V2) / (16.*X);
|
|
||||||
double a2 = (.5 - a1) * K;
|
|
||||||
double a3 = a2 * K;
|
|
||||||
double a4 = a1 * K * K * K;
|
|
||||||
double inv_D = 1. / (a1 + a2 + a3 + a4);
|
|
||||||
|
|
||||||
(*pulses)[0].t = -3. * half_period;
|
|
||||||
(*pulses)[1].t = -2. * half_period;
|
|
||||||
(*pulses)[2].t = -half_period;
|
|
||||||
(*pulses)[3].t = 0.;
|
|
||||||
|
|
||||||
(*pulses)[0].a = a4 * inv_D;
|
|
||||||
(*pulses)[1].a = a3 * inv_D;
|
|
||||||
(*pulses)[2].a = a2 * inv_D;
|
|
||||||
(*pulses)[3].a = a1 * inv_D;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
init_shaper_3hump_ei(double shaper_freq, double damping_ratio
|
|
||||||
, struct shaper_pulse **pulses, int *n)
|
|
||||||
{
|
|
||||||
*n = 5;
|
|
||||||
*pulses = malloc(*n * sizeof(struct shaper_pulse));
|
|
||||||
|
|
||||||
double half_period = calc_half_period(shaper_freq, damping_ratio);
|
|
||||||
double K = calc_ZV_K(damping_ratio);
|
|
||||||
double K2 = K * K;
|
|
||||||
|
|
||||||
double a1 = 0.0625 * (1. + 3. * EI_SHAPER_VIB_TOL
|
|
||||||
+ 2. * sqrt(2. * (EI_SHAPER_VIB_TOL + 1.) * EI_SHAPER_VIB_TOL));
|
|
||||||
double a2 = 0.25 * (1. - EI_SHAPER_VIB_TOL) * K;
|
|
||||||
double a3 = (0.5 * (1. + EI_SHAPER_VIB_TOL) - 2. * a1) * K2;
|
|
||||||
double a4 = a2 * K2;
|
|
||||||
double a5 = a1 * K2 * K2;
|
|
||||||
double inv_D = 1. / (a1 + a2 + a3 + a4 + a5);
|
|
||||||
|
|
||||||
(*pulses)[0].t = -4. * half_period;
|
|
||||||
(*pulses)[1].t = -3. * half_period;
|
|
||||||
(*pulses)[2].t = -2. * half_period;
|
|
||||||
(*pulses)[3].t = -half_period;
|
|
||||||
(*pulses)[4].t = 0.;
|
|
||||||
|
|
||||||
(*pulses)[0].a = a5 * inv_D;
|
|
||||||
(*pulses)[1].a = a4 * inv_D;
|
|
||||||
(*pulses)[2].a = a3 * inv_D;
|
|
||||||
(*pulses)[3].a = a2 * inv_D;
|
|
||||||
(*pulses)[4].a = a1 * inv_D;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shift pulses around 'mid-point' t=0 so that the input shaper is an identity
|
|
||||||
// transformation for constant-speed motion (i.e. input_shaper(v * T) = v * T)
|
|
||||||
static void
|
|
||||||
shift_pulses(int n, struct shaper_pulse *pulses)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
double ts = 0.;
|
|
||||||
for (i = 0; i < n; ++i)
|
|
||||||
ts += pulses[i].a * pulses[i].t;
|
|
||||||
for (i = 0; i < n; ++i)
|
|
||||||
pulses[i].t -= ts;
|
|
||||||
}
|
|
||||||
|
|
||||||
/****************************************************************
|
/****************************************************************
|
||||||
* Kinematics-related shaper code
|
* Kinematics-related shaper code
|
||||||
|
@ -265,15 +280,22 @@ shift_pulses(int n, struct shaper_pulse *pulses)
|
||||||
|
|
||||||
#define DUMMY_T 500.0
|
#define DUMMY_T 500.0
|
||||||
|
|
||||||
|
struct input_shaper {
|
||||||
|
struct stepper_kinematics sk;
|
||||||
|
struct stepper_kinematics *orig_sk;
|
||||||
|
struct move m;
|
||||||
|
struct shaper_pulses sx, sy;
|
||||||
|
};
|
||||||
|
|
||||||
// Optimized calc_position when only x axis is needed
|
// Optimized calc_position when only x axis is needed
|
||||||
static double
|
static double
|
||||||
shaper_x_calc_position(struct stepper_kinematics *sk, struct move *m
|
shaper_x_calc_position(struct stepper_kinematics *sk, struct move *m
|
||||||
, double move_time)
|
, double move_time)
|
||||||
{
|
{
|
||||||
struct input_shaper *is = container_of(sk, struct input_shaper, sk);
|
struct input_shaper *is = container_of(sk, struct input_shaper, sk);
|
||||||
if (!is->x_n)
|
if (!is->sx.num_pulses)
|
||||||
return is->orig_sk->calc_position_cb(is->orig_sk, m, move_time);
|
return is->orig_sk->calc_position_cb(is->orig_sk, m, move_time);
|
||||||
is->m.start_pos.x = calc_position(m, 'x', move_time, is->x_pulses, is->x_n);
|
is->m.start_pos.x = calc_position(m, 'x', move_time, &is->sx);
|
||||||
return is->orig_sk->calc_position_cb(is->orig_sk, &is->m, DUMMY_T);
|
return is->orig_sk->calc_position_cb(is->orig_sk, &is->m, DUMMY_T);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,9 +305,9 @@ shaper_y_calc_position(struct stepper_kinematics *sk, struct move *m
|
||||||
, double move_time)
|
, double move_time)
|
||||||
{
|
{
|
||||||
struct input_shaper *is = container_of(sk, struct input_shaper, sk);
|
struct input_shaper *is = container_of(sk, struct input_shaper, sk);
|
||||||
if (!is->y_n)
|
if (!is->sy.num_pulses)
|
||||||
return is->orig_sk->calc_position_cb(is->orig_sk, m, move_time);
|
return is->orig_sk->calc_position_cb(is->orig_sk, m, move_time);
|
||||||
is->m.start_pos.y = calc_position(m, 'y', move_time, is->y_pulses, is->y_n);
|
is->m.start_pos.y = calc_position(m, 'y', move_time, &is->sy);
|
||||||
return is->orig_sk->calc_position_cb(is->orig_sk, &is->m, DUMMY_T);
|
return is->orig_sk->calc_position_cb(is->orig_sk, &is->m, DUMMY_T);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,48 +317,27 @@ shaper_xy_calc_position(struct stepper_kinematics *sk, struct move *m
|
||||||
, double move_time)
|
, double move_time)
|
||||||
{
|
{
|
||||||
struct input_shaper *is = container_of(sk, struct input_shaper, sk);
|
struct input_shaper *is = container_of(sk, struct input_shaper, sk);
|
||||||
if (!is->x_n && !is->y_n)
|
if (!is->sx.num_pulses && !is->sy.num_pulses)
|
||||||
return is->orig_sk->calc_position_cb(is->orig_sk, m, move_time);
|
return is->orig_sk->calc_position_cb(is->orig_sk, m, move_time);
|
||||||
is->m.start_pos = move_get_coord(m, move_time);
|
is->m.start_pos = move_get_coord(m, move_time);
|
||||||
if (is->x_n)
|
if (is->sx.num_pulses)
|
||||||
is->m.start_pos.x = calc_position(m, 'x', move_time
|
is->m.start_pos.x = calc_position(m, 'x', move_time, &is->sx);
|
||||||
, is->x_pulses, is->x_n);
|
if (is->sy.num_pulses)
|
||||||
if (is->y_n)
|
is->m.start_pos.y = calc_position(m, 'y', move_time, &is->sy);
|
||||||
is->m.start_pos.y = calc_position(m, 'y', move_time
|
|
||||||
, is->y_pulses, is->y_n);
|
|
||||||
return is->orig_sk->calc_position_cb(is->orig_sk, &is->m, DUMMY_T);
|
return is->orig_sk->calc_position_cb(is->orig_sk, &is->m, DUMMY_T);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
shaper_note_generation_time(struct input_shaper *is)
|
|
||||||
{
|
|
||||||
double pre_active = 0., post_active = 0.;
|
|
||||||
if ((is->sk.active_flags & AF_X) && is->x_n) {
|
|
||||||
pre_active = is->x_pulses[is->x_n-1].t;
|
|
||||||
post_active = -is->x_pulses[0].t;
|
|
||||||
}
|
|
||||||
if ((is->sk.active_flags & AF_Y) && is->y_n) {
|
|
||||||
pre_active = is->y_pulses[is->y_n-1].t > pre_active
|
|
||||||
? is->y_pulses[is->y_n-1].t : pre_active;
|
|
||||||
post_active = -is->y_pulses[0].t > post_active
|
|
||||||
? -is->y_pulses[0].t : post_active;
|
|
||||||
}
|
|
||||||
is->sk.gen_steps_pre_active = pre_active;
|
|
||||||
is->sk.gen_steps_post_active = post_active;
|
|
||||||
}
|
|
||||||
|
|
||||||
int __visible
|
int __visible
|
||||||
input_shaper_set_sk(struct stepper_kinematics *sk
|
input_shaper_set_sk(struct stepper_kinematics *sk
|
||||||
, struct stepper_kinematics *orig_sk)
|
, struct stepper_kinematics *orig_sk)
|
||||||
{
|
{
|
||||||
struct input_shaper *is = container_of(sk, struct input_shaper, sk);
|
struct input_shaper *is = container_of(sk, struct input_shaper, sk);
|
||||||
int af = orig_sk->active_flags & (AF_X | AF_Y);
|
if (orig_sk->active_flags == AF_X)
|
||||||
if (af == (AF_X | AF_Y))
|
|
||||||
is->sk.calc_position_cb = shaper_xy_calc_position;
|
|
||||||
else if (af & AF_X)
|
|
||||||
is->sk.calc_position_cb = shaper_x_calc_position;
|
is->sk.calc_position_cb = shaper_x_calc_position;
|
||||||
else if (af & AF_Y)
|
else if (orig_sk->active_flags == AF_Y)
|
||||||
is->sk.calc_position_cb = shaper_y_calc_position;
|
is->sk.calc_position_cb = shaper_y_calc_position;
|
||||||
|
else if (orig_sk->active_flags & (AF_X | AF_Y))
|
||||||
|
is->sk.calc_position_cb = shaper_xy_calc_position;
|
||||||
else
|
else
|
||||||
return -1;
|
return -1;
|
||||||
is->sk.active_flags = orig_sk->active_flags;
|
is->sk.active_flags = orig_sk->active_flags;
|
||||||
|
@ -344,14 +345,23 @@ input_shaper_set_sk(struct stepper_kinematics *sk
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static is_init_shaper_callback init_shaper_callbacks[] = {
|
static void
|
||||||
[INPUT_SHAPER_ZV] = &init_shaper_zv,
|
shaper_note_generation_time(struct input_shaper *is)
|
||||||
[INPUT_SHAPER_ZVD] = &init_shaper_zvd,
|
{
|
||||||
[INPUT_SHAPER_MZV] = &init_shaper_mzv,
|
double pre_active = 0., post_active = 0.;
|
||||||
[INPUT_SHAPER_EI] = &init_shaper_ei,
|
if ((is->sk.active_flags & AF_X) && is->sx.num_pulses) {
|
||||||
[INPUT_SHAPER_2HUMP_EI] = &init_shaper_2hump_ei,
|
pre_active = is->sx.pulses[is->sx.num_pulses-1].t;
|
||||||
[INPUT_SHAPER_3HUMP_EI] = &init_shaper_3hump_ei,
|
post_active = -is->sx.pulses[0].t;
|
||||||
};
|
}
|
||||||
|
if ((is->sk.active_flags & AF_Y) && is->sy.num_pulses) {
|
||||||
|
pre_active = is->sy.pulses[is->sy.num_pulses-1].t > pre_active
|
||||||
|
? is->sy.pulses[is->sy.num_pulses-1].t : pre_active;
|
||||||
|
post_active = -is->sy.pulses[0].t > post_active
|
||||||
|
? -is->sy.pulses[0].t : post_active;
|
||||||
|
}
|
||||||
|
is->sk.gen_steps_pre_active = pre_active;
|
||||||
|
is->sk.gen_steps_post_active = post_active;
|
||||||
|
}
|
||||||
|
|
||||||
int __visible
|
int __visible
|
||||||
input_shaper_set_shaper_params(struct stepper_kinematics *sk
|
input_shaper_set_shaper_params(struct stepper_kinematics *sk
|
||||||
|
@ -363,31 +373,14 @@ input_shaper_set_shaper_params(struct stepper_kinematics *sk
|
||||||
, double damping_ratio_y)
|
, double damping_ratio_y)
|
||||||
{
|
{
|
||||||
struct input_shaper *is = container_of(sk, struct input_shaper, sk);
|
struct input_shaper *is = container_of(sk, struct input_shaper, sk);
|
||||||
|
if (is->orig_sk->active_flags & AF_X)
|
||||||
if (shaper_type_x >= ARRAY_SIZE(init_shaper_callbacks) || shaper_type_x < 0)
|
init_shaper(shaper_type_x, shaper_freq_x, damping_ratio_x, &is->sx);
|
||||||
return -1;
|
else
|
||||||
if (shaper_type_y >= ARRAY_SIZE(init_shaper_callbacks) || shaper_type_y < 0)
|
is->sx.num_pulses = 0;
|
||||||
return -1;
|
if (is->orig_sk->active_flags & AF_Y)
|
||||||
|
init_shaper(shaper_type_y, shaper_freq_y, damping_ratio_y, &is->sy);
|
||||||
int af = is->orig_sk->active_flags & (AF_X | AF_Y);
|
else
|
||||||
free(is->x_pulses);
|
is->sy.num_pulses = 0;
|
||||||
if ((af & AF_X) && shaper_freq_x > 0.) {
|
|
||||||
init_shaper_callbacks[shaper_type_x](
|
|
||||||
shaper_freq_x, damping_ratio_x, &is->x_pulses, &is->x_n);
|
|
||||||
shift_pulses(is->x_n, is->x_pulses);
|
|
||||||
} else {
|
|
||||||
is->x_pulses = NULL;
|
|
||||||
is->x_n = 0;
|
|
||||||
}
|
|
||||||
free(is->y_pulses);
|
|
||||||
if ((af & AF_Y) && shaper_freq_y > 0.) {
|
|
||||||
init_shaper_callbacks[shaper_type_y](
|
|
||||||
shaper_freq_y, damping_ratio_y, &is->y_pulses, &is->y_n);
|
|
||||||
shift_pulses(is->y_n, is->y_pulses);
|
|
||||||
} else {
|
|
||||||
is->y_pulses = NULL;
|
|
||||||
is->y_n = 0;
|
|
||||||
}
|
|
||||||
shaper_note_generation_time(is);
|
shaper_note_generation_time(is);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -396,19 +389,13 @@ double __visible
|
||||||
input_shaper_get_step_generation_window(int shaper_type, double shaper_freq
|
input_shaper_get_step_generation_window(int shaper_type, double shaper_freq
|
||||||
, double damping_ratio)
|
, double damping_ratio)
|
||||||
{
|
{
|
||||||
if (shaper_freq <= 0.)
|
struct shaper_pulses sp;
|
||||||
|
init_shaper(shaper_type, shaper_freq, damping_ratio, &sp);
|
||||||
|
if (!sp.num_pulses)
|
||||||
return 0.;
|
return 0.;
|
||||||
if (shaper_type >= ARRAY_SIZE(init_shaper_callbacks) || shaper_type < 0)
|
double window = -sp.pulses[0].t;
|
||||||
return 0.;
|
if (sp.pulses[sp.num_pulses-1].t > window)
|
||||||
is_init_shaper_callback init_shaper_cb = init_shaper_callbacks[shaper_type];
|
window = sp.pulses[sp.num_pulses-1].t;
|
||||||
int n;
|
|
||||||
struct shaper_pulse *pulses;
|
|
||||||
init_shaper_cb(shaper_freq, damping_ratio, &pulses, &n);
|
|
||||||
shift_pulses(n, pulses);
|
|
||||||
double window = -pulses[0].t;
|
|
||||||
if (pulses[n-1].t > window)
|
|
||||||
window = pulses[n-1].t;
|
|
||||||
free(pulses);
|
|
||||||
return window;
|
return window;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
# Support for scaling ADC values based on measured VREF and VSSA
|
||||||
|
#
|
||||||
|
# Copyright (C) 2020 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
|
||||||
|
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):
|
||||||
|
self._main = main
|
||||||
|
self._last_state = (0., 0.)
|
||||||
|
self._mcu_adc = main.mcu.setup_pin('adc', pin_params)
|
||||||
|
query_adc = main.printer.lookup_object('query_adc')
|
||||||
|
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.get_mcu = self._mcu_adc.get_mcu
|
||||||
|
def _handle_callback(self, read_time, read_value):
|
||||||
|
max_adc = self._main.last_vref[1]
|
||||||
|
min_adc = self._main.last_vssa[1]
|
||||||
|
scaled_val = (read_value - min_adc) / (max_adc - min_adc)
|
||||||
|
self._last_state = (scaled_val, read_time)
|
||||||
|
self._callback(read_time, scaled_val)
|
||||||
|
def setup_adc_callback(self, report_time, callback):
|
||||||
|
self._callback = callback
|
||||||
|
self._mcu_adc.setup_adc_callback(report_time, self._handle_callback)
|
||||||
|
def get_last_value(self):
|
||||||
|
return self._last_state
|
||||||
|
|
||||||
|
class PrinterADCScaled:
|
||||||
|
def __init__(self, config):
|
||||||
|
self.printer = config.get_printer()
|
||||||
|
self.name = config.get_name().split()[1]
|
||||||
|
self.last_vref = (0., 0.)
|
||||||
|
self.last_vssa = (0., 0.)
|
||||||
|
# Configure vref and vssa pins
|
||||||
|
self.mcu_vref = self._config_pin(config, 'vref', self.vref_callback)
|
||||||
|
self.mcu_vssa = self._config_pin(config, 'vssa', self.vssa_callback)
|
||||||
|
smooth_time = config.getfloat('smooth_time', 2., above=0.)
|
||||||
|
self.inv_smooth_time = 1. / smooth_time
|
||||||
|
self.mcu = self.mcu_vref.get_mcu()
|
||||||
|
if self.mcu is not self.mcu_vssa.get_mcu():
|
||||||
|
raise config.error("vref and vssa must be on same mcu")
|
||||||
|
# Register setup_pin
|
||||||
|
ppins = self.printer.lookup_object('pins')
|
||||||
|
ppins.register_chip(self.name, self)
|
||||||
|
def _config_pin(self, config, name, callback):
|
||||||
|
pin_name = config.get(name + '_pin')
|
||||||
|
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)
|
||||||
|
query_adc = config.get_printer().load_object(config, 'query_adc')
|
||||||
|
query_adc.register_adc(self.name + ":" + name, mcu_adc)
|
||||||
|
return mcu_adc
|
||||||
|
def setup_pin(self, pin_type, pin_params):
|
||||||
|
if pin_type != 'adc':
|
||||||
|
raise self.printer.config_error("adc_scaled only supports adc pins")
|
||||||
|
return MCU_scaled_adc(self, pin_params)
|
||||||
|
def calc_smooth(self, read_time, read_value, last):
|
||||||
|
last_time, last_value = last
|
||||||
|
time_diff = read_time - last_time
|
||||||
|
value_diff = read_value - last_value
|
||||||
|
adj_time = min(time_diff * self.inv_smooth_time, 1.)
|
||||||
|
smoothed_value = last_value + value_diff * adj_time
|
||||||
|
return (read_time, smoothed_value)
|
||||||
|
def vref_callback(self, read_time, read_value):
|
||||||
|
self.last_vref = self.calc_smooth(read_time, read_value, self.last_vref)
|
||||||
|
def vssa_callback(self, read_time, read_value):
|
||||||
|
self.last_vssa = self.calc_smooth(read_time, read_value, self.last_vssa)
|
||||||
|
|
||||||
|
def load_config_prefix(config):
|
||||||
|
return PrinterADCScaled(config)
|
|
@ -262,7 +262,7 @@ AD8497 = [
|
||||||
]
|
]
|
||||||
|
|
||||||
def calc_pt100(base=100.):
|
def calc_pt100(base=100.):
|
||||||
# Calc PT100/PT1000 temperature/resistance pairs using formula
|
# Calc PT100/PT1000 resistances using Callendar-Van Dusen formula
|
||||||
A, B = (3.9083e-3, -5.775e-7)
|
A, B = (3.9083e-3, -5.775e-7)
|
||||||
return [(float(t), base * (1. + A*t + B*t*t)) for t in range(0, 500, 10)]
|
return [(float(t), base * (1. + A*t + B*t*t)) for t in range(0, 500, 10)]
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,225 @@
|
||||||
|
# Support for reading acceleration data from an adxl345 chip
|
||||||
|
#
|
||||||
|
# Copyright (C) 2020 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
import logging, time, collections
|
||||||
|
from . import bus
|
||||||
|
|
||||||
|
# ADXL345 registers
|
||||||
|
REG_DEVID = 0x00
|
||||||
|
REG_BW_RATE = 0x2C
|
||||||
|
REG_POWER_CTL = 0x2D
|
||||||
|
REG_DATA_FORMAT = 0x31
|
||||||
|
REG_FIFO_CTL = 0x38
|
||||||
|
REG_MOD_READ = 0x80
|
||||||
|
REG_MOD_MULTI = 0x40
|
||||||
|
|
||||||
|
QUERY_RATES = {
|
||||||
|
25: 0x8, 50: 0x9, 100: 0xa, 200: 0xb, 400: 0xc,
|
||||||
|
800: 0xd, 1600: 0xe, 3200: 0xf,
|
||||||
|
}
|
||||||
|
|
||||||
|
SCALE = 0.004 * 9.80665 * 1000. # 4mg/LSB * Earth gravity in mm/s**2
|
||||||
|
|
||||||
|
Accel_Measurement = collections.namedtuple(
|
||||||
|
'Accel_Measurement', ('time', 'accel_x', 'accel_y', 'accel_z'))
|
||||||
|
|
||||||
|
# Sample results
|
||||||
|
class ADXL345Results:
|
||||||
|
def __init__(self):
|
||||||
|
self.samples = []
|
||||||
|
self.drops = self.overflows = 0
|
||||||
|
self.time_per_sample = self.start_range = self.end_range = 0.
|
||||||
|
def get_samples(self):
|
||||||
|
return self.samples
|
||||||
|
def get_stats(self):
|
||||||
|
return ("drops=%d,overflows=%d"
|
||||||
|
",time_per_sample=%.9f,start_range=%.6f,end_range=%.6f"
|
||||||
|
% (self.drops, self.overflows,
|
||||||
|
self.time_per_sample, self.start_range, self.end_range))
|
||||||
|
def setup_data(self, axes_map, raw_samples, end_sequence, overflows,
|
||||||
|
start1_time, start2_time, end1_time, end2_time):
|
||||||
|
if not raw_samples or not end_sequence:
|
||||||
|
return
|
||||||
|
(x_pos, x_scale), (y_pos, y_scale), (z_pos, z_scale) = axes_map
|
||||||
|
self.overflows = overflows
|
||||||
|
self.start_range = start2_time - start1_time
|
||||||
|
self.end_range = end2_time - end1_time
|
||||||
|
total_count = (end_sequence - 1) * 8 + len(raw_samples[-1][1]) // 6
|
||||||
|
total_time = end2_time - start2_time
|
||||||
|
self.time_per_sample = time_per_sample = total_time / total_count
|
||||||
|
seq_to_time = time_per_sample * 8.
|
||||||
|
self.samples = samples = [None] * total_count
|
||||||
|
actual_count = 0
|
||||||
|
for seq, data in raw_samples:
|
||||||
|
d = bytearray(data)
|
||||||
|
count = len(data)
|
||||||
|
sdata = [(d[i] | (d[i+1] << 8)) - ((d[i+1] & 0x80) << 9)
|
||||||
|
for i in range(0, count-1, 2)]
|
||||||
|
seq_time = start2_time + seq * seq_to_time
|
||||||
|
for i in range(count//6):
|
||||||
|
samp_time = seq_time + i * time_per_sample
|
||||||
|
x = sdata[i*3 + x_pos] * x_scale
|
||||||
|
y = sdata[i*3 + y_pos] * y_scale
|
||||||
|
z = sdata[i*3 + z_pos] * z_scale
|
||||||
|
samples[actual_count] = Accel_Measurement(samp_time, x, y, z)
|
||||||
|
actual_count += 1
|
||||||
|
del samples[actual_count:]
|
||||||
|
self.drops = total_count - actual_count
|
||||||
|
|
||||||
|
# Printer class that controls measurments
|
||||||
|
class ADXL345:
|
||||||
|
def __init__(self, config):
|
||||||
|
self.printer = config.get_printer()
|
||||||
|
self.query_rate = 0
|
||||||
|
self.last_tx_time = 0.
|
||||||
|
am = {'x': (0, SCALE), 'y': (1, SCALE), 'z': (2, SCALE),
|
||||||
|
'-x': (0, -SCALE), '-y': (1, -SCALE), '-z': (2, -SCALE)}
|
||||||
|
axes_map = config.get('axes_map', 'x,y,z').split(',')
|
||||||
|
if len(axes_map) != 3 or any([a.strip() not in am for a in axes_map]):
|
||||||
|
raise config.error("Invalid adxl345 axes_map parameter")
|
||||||
|
self.axes_map = [am[a.strip()] for a in axes_map]
|
||||||
|
# Measurement storage (accessed from background thread)
|
||||||
|
self.raw_samples = []
|
||||||
|
self.last_sequence = 0
|
||||||
|
self.samples_start1 = self.samples_start2 = 0.
|
||||||
|
# Setup mcu sensor_adxl345 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_adxl345_cmd = self.query_adxl345_end_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"
|
||||||
|
% (oid,), on_restart=True)
|
||||||
|
mcu.register_config_callback(self._build_config)
|
||||||
|
mcu.register_response(self._handle_adxl345_start, "adxl345_start", oid)
|
||||||
|
mcu.register_response(self._handle_adxl345_data, "adxl345_data", oid)
|
||||||
|
# Register commands
|
||||||
|
name = "default"
|
||||||
|
if len(config.get_name().split()) > 1:
|
||||||
|
name = config.get_name().split()[1]
|
||||||
|
gcode = self.printer.lookup_object('gcode')
|
||||||
|
gcode.register_mux_command("ACCELEROMETER_MEASURE", "CHIP", name,
|
||||||
|
self.cmd_ACCELEROMETER_MEASURE,
|
||||||
|
desc=self.cmd_ACCELEROMETER_MEASURE_help)
|
||||||
|
if name == "default":
|
||||||
|
gcode.register_mux_command("ACCELEROMETER_MEASURE", "CHIP", None,
|
||||||
|
self.cmd_ACCELEROMETER_MEASURE)
|
||||||
|
def _build_config(self):
|
||||||
|
self.query_adxl345_cmd = self.mcu.lookup_command(
|
||||||
|
"query_adxl345 oid=%c clock=%u rest_ticks=%u",
|
||||||
|
cq=self.spi.get_command_queue())
|
||||||
|
self.query_adxl345_end_cmd = self.mcu.lookup_query_command(
|
||||||
|
"query_adxl345 oid=%c clock=%u rest_ticks=%u",
|
||||||
|
"adxl345_end oid=%c end1_time=%u end2_time=%u"
|
||||||
|
" limit_count=%hu sequence=%hu",
|
||||||
|
oid=self.oid, cq=self.spi.get_command_queue())
|
||||||
|
def _clock_to_print_time(self, clock):
|
||||||
|
return self.mcu.clock_to_print_time(self.mcu.clock32_to_clock64(clock))
|
||||||
|
def _handle_adxl345_start(self, params):
|
||||||
|
self.samples_start1 = self._clock_to_print_time(params['start1_time'])
|
||||||
|
self.samples_start2 = self._clock_to_print_time(params['start2_time'])
|
||||||
|
def _handle_adxl345_data(self, params):
|
||||||
|
last_sequence = self.last_sequence
|
||||||
|
sequence = (last_sequence & ~0xffff) | params['sequence']
|
||||||
|
if sequence < last_sequence:
|
||||||
|
sequence += 0x10000
|
||||||
|
self.last_sequence = sequence
|
||||||
|
raw_samples = self.raw_samples
|
||||||
|
if len(raw_samples) >= 200000:
|
||||||
|
# Avoid filling up memory with too many samples
|
||||||
|
return
|
||||||
|
raw_samples.append((sequence, params['data']))
|
||||||
|
def _convert_sequence(self, sequence):
|
||||||
|
sequence = (self.last_sequence & ~0xffff) | sequence
|
||||||
|
if sequence < self.last_sequence:
|
||||||
|
sequence += 0x10000
|
||||||
|
return sequence
|
||||||
|
def start_measurements(self, rate=None):
|
||||||
|
if rate is None:
|
||||||
|
rate = 3200
|
||||||
|
# Verify chip connectivity
|
||||||
|
params = self.spi.spi_transfer([REG_DEVID | REG_MOD_READ, 0x00])
|
||||||
|
response = bytearray(params['response'])
|
||||||
|
if response[1] != 0xe5:
|
||||||
|
raise self.printer.command_error("Invalid adxl345 id (got %x vs %x)"
|
||||||
|
% (response[1], 0xe5))
|
||||||
|
# Setup chip in requested query rate
|
||||||
|
clock = 0
|
||||||
|
if self.last_tx_time:
|
||||||
|
clock = self.mcu.print_time_to_clock(self.last_tx_time)
|
||||||
|
self.spi.spi_send([REG_POWER_CTL, 0x00], minclock=clock)
|
||||||
|
self.spi.spi_send([REG_FIFO_CTL, 0x00])
|
||||||
|
self.spi.spi_send([REG_DATA_FORMAT, 0x0B])
|
||||||
|
self.spi.spi_send([REG_BW_RATE, QUERY_RATES[rate]])
|
||||||
|
self.spi.spi_send([REG_FIFO_CTL, 0x80])
|
||||||
|
# Setup samples
|
||||||
|
print_time = self.printer.lookup_object('toolhead').get_last_move_time()
|
||||||
|
self.raw_samples = []
|
||||||
|
self.last_sequence = 0
|
||||||
|
self.samples_start1 = self.samples_start2 = print_time
|
||||||
|
# Start bulk reading
|
||||||
|
reqclock = self.mcu.print_time_to_clock(print_time)
|
||||||
|
rest_ticks = self.mcu.seconds_to_clock(4. / rate)
|
||||||
|
self.last_tx_time = print_time
|
||||||
|
self.query_rate = rate
|
||||||
|
self.query_adxl345_cmd.send([self.oid, reqclock, rest_ticks],
|
||||||
|
reqclock=reqclock)
|
||||||
|
def finish_measurements(self):
|
||||||
|
query_rate = self.query_rate
|
||||||
|
if not query_rate:
|
||||||
|
return ADXL345Results()
|
||||||
|
# Halt bulk reading
|
||||||
|
print_time = self.printer.lookup_object('toolhead').get_last_move_time()
|
||||||
|
clock = self.mcu.print_time_to_clock(print_time)
|
||||||
|
params = self.query_adxl345_end_cmd.send([self.oid, 0, 0],
|
||||||
|
minclock=clock)
|
||||||
|
self.last_tx_time = print_time
|
||||||
|
self.query_rate = 0
|
||||||
|
raw_samples = self.raw_samples
|
||||||
|
self.raw_samples = []
|
||||||
|
# Generate results
|
||||||
|
end1_time = self._clock_to_print_time(params['end1_time'])
|
||||||
|
end2_time = self._clock_to_print_time(params['end2_time'])
|
||||||
|
end_sequence = self._convert_sequence(params['sequence'])
|
||||||
|
overflows = params['limit_count']
|
||||||
|
res = ADXL345Results()
|
||||||
|
res.setup_data(self.axes_map, raw_samples, end_sequence, overflows,
|
||||||
|
self.samples_start1, self.samples_start2,
|
||||||
|
end1_time, end2_time)
|
||||||
|
logging.info("ADXL345 finished %d measurements: %s",
|
||||||
|
len(res.get_samples()), res.get_stats())
|
||||||
|
return res
|
||||||
|
def end_query(self, name):
|
||||||
|
if not self.query_rate:
|
||||||
|
return
|
||||||
|
res = self.finish_measurements()
|
||||||
|
# Write data to file
|
||||||
|
f = open("/tmp/adxl345-%s.csv" % (name,), "w")
|
||||||
|
f.write("##%s\n#time,accel_x,accel_y,accel_z\n" % (res.get_stats(),))
|
||||||
|
for t, accel_x, accel_y, accel_z in res.get_samples():
|
||||||
|
f.write("%.6f,%.6f,%.6f,%.6f\n" % (t, accel_x, accel_y, accel_z))
|
||||||
|
f.close()
|
||||||
|
cmd_ACCELEROMETER_MEASURE_help = "Start/stop accelerometer"
|
||||||
|
def cmd_ACCELEROMETER_MEASURE(self, gcmd):
|
||||||
|
rate = gcmd.get_int("RATE", 0)
|
||||||
|
if not rate:
|
||||||
|
name = gcmd.get("NAME", time.strftime("%Y%m%d_%H%M%S"))
|
||||||
|
if not name.replace('-', '').replace('_', '').isalnum():
|
||||||
|
raise gcmd.error("Invalid adxl345 NAME parameter")
|
||||||
|
self.end_query(name)
|
||||||
|
gcmd.respond_info("adxl345 measurements stopped")
|
||||||
|
elif self.query_rate:
|
||||||
|
raise gcmd.error("adxl345 already running")
|
||||||
|
elif rate not in QUERY_RATES:
|
||||||
|
raise gcmd.error("Not a valid adxl345 query rate")
|
||||||
|
else:
|
||||||
|
self.start_measurements(rate)
|
||||||
|
|
||||||
|
def load_config(config):
|
||||||
|
return ADXL345(config)
|
||||||
|
|
||||||
|
def load_config_prefix(config):
|
||||||
|
return ADXL345(config)
|
|
@ -235,13 +235,17 @@ class BedMeshCalibrate:
|
||||||
ALGOS = ['lagrange', 'bicubic']
|
ALGOS = ['lagrange', 'bicubic']
|
||||||
def __init__(self, config, bedmesh):
|
def __init__(self, config, bedmesh):
|
||||||
self.printer = config.get_printer()
|
self.printer = config.get_printer()
|
||||||
|
self.orig_config = {'radius': None, 'origin': None}
|
||||||
self.radius = self.origin = None
|
self.radius = self.origin = None
|
||||||
|
self.mesh_min = self.mesh_max = (0., 0.)
|
||||||
self.relative_reference_index = config.getint(
|
self.relative_reference_index = config.getint(
|
||||||
'relative_reference_index', None)
|
'relative_reference_index', None)
|
||||||
|
self.orig_config['rri'] = self.relative_reference_index
|
||||||
self.bedmesh = bedmesh
|
self.bedmesh = bedmesh
|
||||||
self.mesh_config = collections.OrderedDict()
|
self.mesh_config = collections.OrderedDict()
|
||||||
self.points = self._generate_points(config)
|
self._init_mesh_config(config)
|
||||||
self._init_mesh_config(config, self.points)
|
self._generate_points(config.error)
|
||||||
|
self.orig_points = self.points
|
||||||
self.probe_helper = probe.ProbePointsHelper(
|
self.probe_helper = probe.ProbePointsHelper(
|
||||||
config, self.probe_finalize, self.points)
|
config, self.probe_finalize, self.points)
|
||||||
self.probe_helper.minimum_points(3)
|
self.probe_helper.minimum_points(3)
|
||||||
|
@ -250,37 +254,18 @@ class BedMeshCalibrate:
|
||||||
self.gcode.register_command(
|
self.gcode.register_command(
|
||||||
'BED_MESH_CALIBRATE', self.cmd_BED_MESH_CALIBRATE,
|
'BED_MESH_CALIBRATE', self.cmd_BED_MESH_CALIBRATE,
|
||||||
desc=self.cmd_BED_MESH_CALIBRATE_help)
|
desc=self.cmd_BED_MESH_CALIBRATE_help)
|
||||||
def _generate_points(self, config):
|
def _generate_points(self, error):
|
||||||
self.radius = config.getfloat('mesh_radius', None, above=0.)
|
x_cnt = self.mesh_config['x_count']
|
||||||
if self.radius is not None:
|
y_cnt = self.mesh_config['y_count']
|
||||||
self.origin = parse_pair(config, ('mesh_origin', "0, 0"))
|
min_x, min_y = self.mesh_min
|
||||||
x_cnt = y_cnt = config.getint('round_probe_count', 5, minval=3)
|
max_x, max_y = self.mesh_max
|
||||||
# round beds must have an odd number of points along each axis
|
|
||||||
if not x_cnt & 1:
|
|
||||||
raise config.error(
|
|
||||||
"bed_mesh: probe_count must be odd for round beds")
|
|
||||||
# radius may have precision to .1mm
|
|
||||||
self.radius = math.floor(self.radius * 10) / 10
|
|
||||||
min_x = min_y = -self.radius
|
|
||||||
max_x = max_y = self.radius
|
|
||||||
else:
|
|
||||||
# rectangular
|
|
||||||
x_cnt, y_cnt = parse_pair(
|
|
||||||
config, ('probe_count', '3'), check=False, cast=int, minval=3)
|
|
||||||
min_x, min_y = parse_pair(config, ('mesh_min',))
|
|
||||||
max_x, max_y = parse_pair(config, ('mesh_max',))
|
|
||||||
if max_x <= min_x or max_y <= min_y:
|
|
||||||
raise config.error('bed_mesh: invalid min/max points')
|
|
||||||
|
|
||||||
self.mesh_config['x_count'] = x_cnt
|
|
||||||
self.mesh_config['y_count'] = y_cnt
|
|
||||||
x_dist = (max_x - min_x) / (x_cnt - 1)
|
x_dist = (max_x - min_x) / (x_cnt - 1)
|
||||||
y_dist = (max_y - min_y) / (y_cnt - 1)
|
y_dist = (max_y - min_y) / (y_cnt - 1)
|
||||||
# floor distances down to next hundredth
|
# floor distances down to next hundredth
|
||||||
x_dist = math.floor(x_dist * 100) / 100
|
x_dist = math.floor(x_dist * 100) / 100
|
||||||
y_dist = math.floor(y_dist * 100) / 100
|
y_dist = math.floor(y_dist * 100) / 100
|
||||||
if x_dist <= 1. or y_dist <= 1.:
|
if x_dist <= 1. or y_dist <= 1.:
|
||||||
raise config.error("bed_mesh: min/max points too close together")
|
raise error("bed_mesh: min/max points too close together")
|
||||||
|
|
||||||
if self.radius is not None:
|
if self.radius is not None:
|
||||||
# round bed, min/max needs to be recalculated
|
# round bed, min/max needs to be recalculated
|
||||||
|
@ -311,7 +296,7 @@ class BedMeshCalibrate:
|
||||||
points.append(
|
points.append(
|
||||||
(self.origin[0] + pos_x, self.origin[1] + pos_y))
|
(self.origin[0] + pos_x, self.origin[1] + pos_y))
|
||||||
pos_y += y_dist
|
pos_y += y_dist
|
||||||
return points
|
self.points = points
|
||||||
def print_generated_points(self, print_func):
|
def print_generated_points(self, print_func):
|
||||||
x_offset = y_offset = 0.
|
x_offset = y_offset = 0.
|
||||||
probe = self.printer.lookup_object('probe', None)
|
probe = self.printer.lookup_object('probe', None)
|
||||||
|
@ -329,33 +314,69 @@ class BedMeshCalibrate:
|
||||||
print_func(
|
print_func(
|
||||||
"bed_mesh: relative_reference_index %d is (%.2f, %.2f)"
|
"bed_mesh: relative_reference_index %d is (%.2f, %.2f)"
|
||||||
% (rri, self.points[rri][0], self.points[rri][1]))
|
% (rri, self.points[rri][0], self.points[rri][1]))
|
||||||
def _init_mesh_config(self, config, points):
|
def _init_mesh_config(self, config):
|
||||||
|
mesh_cfg = self.mesh_config
|
||||||
|
orig_cfg = self.orig_config
|
||||||
|
self.radius = config.getfloat('mesh_radius', None, above=0.)
|
||||||
|
if self.radius is not None:
|
||||||
|
self.origin = parse_pair(config, ('mesh_origin', "0, 0"))
|
||||||
|
x_cnt = y_cnt = config.getint('round_probe_count', 5, minval=3)
|
||||||
|
# round beds must have an odd number of points along each axis
|
||||||
|
if not x_cnt & 1:
|
||||||
|
raise config.error(
|
||||||
|
"bed_mesh: probe_count must be odd for round beds")
|
||||||
|
# radius may have precision to .1mm
|
||||||
|
self.radius = math.floor(self.radius * 10) / 10
|
||||||
|
orig_cfg['radius'] = self.radius
|
||||||
|
orig_cfg['origin'] = self.origin
|
||||||
|
min_x = min_y = -self.radius
|
||||||
|
max_x = max_y = self.radius
|
||||||
|
else:
|
||||||
|
# rectangular
|
||||||
|
x_cnt, y_cnt = parse_pair(
|
||||||
|
config, ('probe_count', '3'), check=False, cast=int, minval=3)
|
||||||
|
min_x, min_y = parse_pair(config, ('mesh_min',))
|
||||||
|
max_x, max_y = parse_pair(config, ('mesh_max',))
|
||||||
|
if max_x <= min_x or max_y <= min_y:
|
||||||
|
raise config.error('bed_mesh: invalid min/max points')
|
||||||
|
orig_cfg['x_count'] = mesh_cfg['x_count'] = x_cnt
|
||||||
|
orig_cfg['y_count'] = mesh_cfg['y_count'] = y_cnt
|
||||||
|
orig_cfg['mesh_min'] = self.mesh_min = (min_x, min_y)
|
||||||
|
orig_cfg['mesh_max'] = self.mesh_max = (max_x, max_y)
|
||||||
|
|
||||||
pps = parse_pair(config, ('mesh_pps', '2'), check=False,
|
pps = parse_pair(config, ('mesh_pps', '2'), check=False,
|
||||||
cast=int, minval=0)
|
cast=int, minval=0)
|
||||||
|
orig_cfg['mesh_x_pps'] = mesh_cfg['mesh_x_pps'] = pps[0]
|
||||||
|
orig_cfg['mesh_y_pps'] = mesh_cfg['mesh_y_pps'] = pps[1]
|
||||||
|
orig_cfg['algo'] = mesh_cfg['algo'] = \
|
||||||
|
config.get('algorithm', 'lagrange').strip().lower()
|
||||||
|
orig_cfg['tension'] = mesh_cfg['tension'] = config.getfloat(
|
||||||
|
'bicubic_tension', .2, minval=0., maxval=2.)
|
||||||
|
self._verify_algorithm(config.error)
|
||||||
|
def _verify_algorithm(self, error):
|
||||||
params = self.mesh_config
|
params = self.mesh_config
|
||||||
params['mesh_x_pps'] = pps[0]
|
x_pps = params['mesh_x_pps']
|
||||||
params['mesh_y_pps'] = pps[1]
|
y_pps = params['mesh_y_pps']
|
||||||
params['algo'] = config.get('algorithm', 'lagrange').strip().lower()
|
|
||||||
if params['algo'] not in self.ALGOS:
|
if params['algo'] not in self.ALGOS:
|
||||||
raise config.error(
|
raise error(
|
||||||
"bed_mesh: Unknown algorithm <%s>"
|
"bed_mesh: Unknown algorithm <%s>"
|
||||||
% (self.mesh_config['algo']))
|
% (self.mesh_config['algo']))
|
||||||
# Check the algorithm against the current configuration
|
# Check the algorithm against the current configuration
|
||||||
max_probe_cnt = max(params['x_count'], params['y_count'])
|
max_probe_cnt = max(params['x_count'], params['y_count'])
|
||||||
min_probe_cnt = min(params['x_count'], params['y_count'])
|
min_probe_cnt = min(params['x_count'], params['y_count'])
|
||||||
if max(pps[0], pps[1]) == 0:
|
if max(x_pps, y_pps) == 0:
|
||||||
# Interpolation disabled
|
# Interpolation disabled
|
||||||
self.mesh_config['algo'] = 'direct'
|
self.mesh_config['algo'] = 'direct'
|
||||||
elif params['algo'] == 'lagrange' and max_probe_cnt > 6:
|
elif params['algo'] == 'lagrange' and max_probe_cnt > 6:
|
||||||
# Lagrange interpolation tends to oscillate when using more
|
# Lagrange interpolation tends to oscillate when using more
|
||||||
# than 6 samples
|
# than 6 samples
|
||||||
raise config.error(
|
raise error(
|
||||||
"bed_mesh: cannot exceed a probe_count of 6 when using "
|
"bed_mesh: cannot exceed a probe_count of 6 when using "
|
||||||
"lagrange interpolation. Configured Probe Count: %d, %d" %
|
"lagrange interpolation. Configured Probe Count: %d, %d" %
|
||||||
(self.mesh_config['x_count'], self.mesh_config['y_count']))
|
(self.mesh_config['x_count'], self.mesh_config['y_count']))
|
||||||
elif params['algo'] == 'bicubic' and min_probe_cnt < 4:
|
elif params['algo'] == 'bicubic' and min_probe_cnt < 4:
|
||||||
if max_probe_cnt > 6:
|
if max_probe_cnt > 6:
|
||||||
raise config.error(
|
raise error(
|
||||||
"bed_mesh: invalid probe_count option when using bicubic "
|
"bed_mesh: invalid probe_count option when using bicubic "
|
||||||
"interpolation. Combination of 3 points on one axis with "
|
"interpolation. Combination of 3 points on one axis with "
|
||||||
"more than 6 on another is not permitted. "
|
"more than 6 on another is not permitted. "
|
||||||
|
@ -368,11 +389,74 @@ class BedMeshCalibrate:
|
||||||
"interpolation. Configured Probe Count: %d, %d" %
|
"interpolation. Configured Probe Count: %d, %d" %
|
||||||
(self.mesh_config['x_count'], self.mesh_config['y_count']))
|
(self.mesh_config['x_count'], self.mesh_config['y_count']))
|
||||||
params['algo'] = 'lagrange'
|
params['algo'] = 'lagrange'
|
||||||
params['tension'] = config.getfloat(
|
def update_config(self, gcmd):
|
||||||
'bicubic_tension', .2, minval=0., maxval=2.)
|
# reset default configuration
|
||||||
|
self.radius = self.orig_config['radius']
|
||||||
|
self.origin = self.orig_config['origin']
|
||||||
|
self.relative_reference_index = self.orig_config['rri']
|
||||||
|
self.mesh_min = self.orig_config['mesh_min']
|
||||||
|
self.mesh_max = self.orig_config['mesh_max']
|
||||||
|
for key in list(self.mesh_config.keys()):
|
||||||
|
self.mesh_config[key] = self.orig_config[key]
|
||||||
|
|
||||||
|
params = gcmd.get_command_parameters()
|
||||||
|
need_cfg_update = False
|
||||||
|
if 'RELATIVE_REFERENCE_INDEX' in params:
|
||||||
|
self.relative_reference_index = gcmd.get_int(
|
||||||
|
'RELATIVE_REFERENCE_INDEX')
|
||||||
|
need_cfg_update = True
|
||||||
|
if self.radius is not None:
|
||||||
|
if "MESH_RADIUS" in params:
|
||||||
|
self.radius = gcmd.get_float("MESH_RADIUS")
|
||||||
|
self.radius = math.floor(self.radius * 10) / 10
|
||||||
|
self.mesh_min = (-self.radius, -self.radius)
|
||||||
|
self.mesh_max = (self.radius, self.radius)
|
||||||
|
need_cfg_update = True
|
||||||
|
if "MESH_ORIGIN" in params:
|
||||||
|
self.origin = parse_pair(gcmd, ('MESH_ORIGIN',))
|
||||||
|
need_cfg_update = True
|
||||||
|
if "ROUND_PROBE_COUNT" in params:
|
||||||
|
cnt = gcmd.get_int('ROUND_PROBE_COUNT', minval=3)
|
||||||
|
self.mesh_config['x_count'] = cnt
|
||||||
|
self.mesh_config['y_count'] = cnt
|
||||||
|
need_cfg_update = True
|
||||||
|
else:
|
||||||
|
if "MESH_MIN" in params:
|
||||||
|
self.mesh_min = parse_pair(gcmd, ('MESH_MIN',))
|
||||||
|
need_cfg_update = True
|
||||||
|
if "MESH_MAX" in params:
|
||||||
|
self.mesh_max = parse_pair(gcmd, ('MESH_MAX',))
|
||||||
|
need_cfg_update = True
|
||||||
|
if "PROBE_COUNT" in params:
|
||||||
|
x_cnt, y_cnt = parse_pair(
|
||||||
|
gcmd, ('PROBE_COUNT',), check=False, cast=int, minval=3)
|
||||||
|
self.mesh_config['x_count'] = x_cnt
|
||||||
|
self.mesh_config['y_count'] = y_cnt
|
||||||
|
need_cfg_update = True
|
||||||
|
|
||||||
|
if "ALGORITHM" in params:
|
||||||
|
self.mesh_config['algo'] = gcmd.get('ALGORITHM').strip().lower()
|
||||||
|
need_cfg_update = True
|
||||||
|
|
||||||
|
if need_cfg_update:
|
||||||
|
self._verify_algorithm(gcmd.error)
|
||||||
|
self._generate_points(gcmd.error)
|
||||||
|
gcmd.respond_info("Generating new points...")
|
||||||
|
self.print_generated_points(gcmd.respond_info)
|
||||||
|
self.probe_helper.update_probe_points(self.points, 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()])
|
||||||
|
logging.info("Updated Mesh Configuration:\n" + msg)
|
||||||
|
else:
|
||||||
|
self.points = self.orig_points
|
||||||
|
self.probe_helper.update_probe_points(self.points, 3)
|
||||||
|
|
||||||
cmd_BED_MESH_CALIBRATE_help = "Perform Mesh Bed Leveling"
|
cmd_BED_MESH_CALIBRATE_help = "Perform Mesh Bed Leveling"
|
||||||
def cmd_BED_MESH_CALIBRATE(self, gcmd):
|
def cmd_BED_MESH_CALIBRATE(self, gcmd):
|
||||||
self.bedmesh.set_mesh(None)
|
self.bedmesh.set_mesh(None)
|
||||||
|
self.update_config(gcmd)
|
||||||
self.probe_helper.start_probe(gcmd)
|
self.probe_helper.start_probe(gcmd)
|
||||||
def probe_finalize(self, offsets, positions):
|
def probe_finalize(self, offsets, positions):
|
||||||
x_offset, y_offset, z_offset = offsets
|
x_offset, y_offset, z_offset = offsets
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
# Copyright (C) 2019 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2019 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import homing
|
|
||||||
|
|
||||||
def parse_coord(config, param):
|
def parse_coord(config, param):
|
||||||
pair = config.get(param).strip().split(',', 1)
|
pair = config.get(param).strip().split(',', 1)
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import logging
|
import logging
|
||||||
import homing
|
|
||||||
from . import probe
|
from . import probe
|
||||||
|
|
||||||
SIGNAL_PERIOD = 0.020
|
SIGNAL_PERIOD = 0.020
|
||||||
|
@ -82,7 +81,7 @@ class BLTouchEndstopWrapper:
|
||||||
self.set_output_mode(self.output_mode)
|
self.set_output_mode(self.output_mode)
|
||||||
try:
|
try:
|
||||||
self.raise_probe()
|
self.raise_probe()
|
||||||
except homing.CommandError as e:
|
except self.printer.command_error as e:
|
||||||
logging.warning("BLTouch raise probe error: %s", str(e))
|
logging.warning("BLTouch raise probe error: %s", str(e))
|
||||||
def sync_mcu_print_time(self):
|
def sync_mcu_print_time(self):
|
||||||
curtime = self.printer.get_reactor().monotonic()
|
curtime = self.printer.get_reactor().monotonic()
|
||||||
|
@ -128,7 +127,8 @@ class BLTouchEndstopWrapper:
|
||||||
# The "probe raised" test completed successfully
|
# The "probe raised" test completed successfully
|
||||||
break
|
break
|
||||||
if retry >= 2:
|
if retry >= 2:
|
||||||
raise homing.EndstopError("BLTouch failed to raise probe")
|
raise self.printer.command_error(
|
||||||
|
"BLTouch failed to raise probe")
|
||||||
msg = "Failed to verify BLTouch probe is raised; retrying."
|
msg = "Failed to verify BLTouch probe is raised; retrying."
|
||||||
self.gcode.respond_info(msg)
|
self.gcode.respond_info(msg)
|
||||||
self.sync_mcu_print_time()
|
self.sync_mcu_print_time()
|
||||||
|
@ -161,7 +161,7 @@ class BLTouchEndstopWrapper:
|
||||||
return
|
return
|
||||||
msg = "BLTouch failed to verify sensor state"
|
msg = "BLTouch failed to verify sensor state"
|
||||||
if retry >= 2:
|
if retry >= 2:
|
||||||
raise homing.EndstopError(msg)
|
raise self.printer.command_error(msg)
|
||||||
self.gcode.respond_info(msg + '; retrying.')
|
self.gcode.respond_info(msg + '; retrying.')
|
||||||
self.send_cmd('reset', duration=RETRY_RESET_TIME)
|
self.send_cmd('reset', duration=RETRY_RESET_TIME)
|
||||||
def multi_probe_begin(self):
|
def multi_probe_begin(self):
|
||||||
|
@ -191,7 +191,7 @@ class BLTouchEndstopWrapper:
|
||||||
# Verify the probe actually deployed during the attempt
|
# Verify the probe actually deployed during the attempt
|
||||||
for s, mcu_pos in self.start_mcu_pos:
|
for s, mcu_pos in self.start_mcu_pos:
|
||||||
if s.get_mcu_position() == mcu_pos:
|
if s.get_mcu_position() == mcu_pos:
|
||||||
raise homing.EndstopError("BLTouch failed to deploy")
|
raise self.printer.command_error("BLTouch failed to deploy")
|
||||||
def home_start(self, print_time, sample_time, sample_count, rest_time):
|
def home_start(self, print_time, sample_time, sample_count, rest_time):
|
||||||
rest_time = min(rest_time, ENDSTOP_REST_TIME)
|
rest_time = min(rest_time, ENDSTOP_REST_TIME)
|
||||||
return self.mcu_endstop.home_start(print_time, sample_time,
|
return self.mcu_endstop.home_start(print_time, sample_time,
|
||||||
|
|
|
@ -62,7 +62,7 @@ class MCU_buttons:
|
||||||
ack_diff -= 0x100
|
ack_diff -= 0x100
|
||||||
msg_ack_count = ack_count - ack_diff
|
msg_ack_count = ack_count - ack_diff
|
||||||
# Determine new buttons
|
# Determine new buttons
|
||||||
buttons = params['state']
|
buttons = bytearray(params['state'])
|
||||||
new_count = msg_ack_count + len(buttons) - self.ack_count
|
new_count = msg_ack_count + len(buttons) - self.ack_count
|
||||||
if new_count <= 0:
|
if new_count <= 0:
|
||||||
return
|
return
|
||||||
|
@ -71,9 +71,9 @@ class MCU_buttons:
|
||||||
self.ack_cmd.send([self.oid, new_count])
|
self.ack_cmd.send([self.oid, new_count])
|
||||||
self.ack_count += new_count
|
self.ack_count += new_count
|
||||||
# Call self.handle_button() with this event in main thread
|
# Call self.handle_button() with this event in main thread
|
||||||
for b in new_buttons:
|
for nb in new_buttons:
|
||||||
self.reactor.register_async_callback(
|
self.reactor.register_async_callback(
|
||||||
(lambda e, s=self, b=ord(b): s.handle_button(e, b)))
|
(lambda e, s=self, b=nb: s.handle_button(e, b)))
|
||||||
def handle_button(self, eventtime, button):
|
def handle_button(self, eventtime, button):
|
||||||
button ^= self.invert
|
button ^= self.invert
|
||||||
changed = button ^ self.last_button
|
changed = button ^ self.last_button
|
||||||
|
|
|
@ -182,21 +182,25 @@ class DeltaCalibrate:
|
||||||
z_weight = len(distances) / (MEASURE_WEIGHT * len(probe_positions))
|
z_weight = len(distances) / (MEASURE_WEIGHT * len(probe_positions))
|
||||||
# Perform coordinate descent
|
# Perform coordinate descent
|
||||||
def delta_errorfunc(params):
|
def delta_errorfunc(params):
|
||||||
# Build new delta_params for params under test
|
try:
|
||||||
delta_params = orig_delta_params.new_calibration(params)
|
# Build new delta_params for params under test
|
||||||
# Calculate z height errors
|
delta_params = orig_delta_params.new_calibration(params)
|
||||||
total_error = 0.
|
getpos = delta_params.get_position_from_stable
|
||||||
for z_offset, stable_pos in height_positions:
|
# Calculate z height errors
|
||||||
x, y, z = delta_params.get_position_from_stable(stable_pos)
|
total_error = 0.
|
||||||
total_error += (z - z_offset)**2
|
for z_offset, stable_pos in height_positions:
|
||||||
total_error *= z_weight
|
x, y, z = getpos(stable_pos)
|
||||||
# Calculate distance errors
|
total_error += (z - z_offset)**2
|
||||||
for dist, stable_pos1, stable_pos2 in distances:
|
total_error *= z_weight
|
||||||
x1, y1, z1 = delta_params.get_position_from_stable(stable_pos1)
|
# Calculate distance errors
|
||||||
x2, y2, z2 = delta_params.get_position_from_stable(stable_pos2)
|
for dist, stable_pos1, stable_pos2 in distances:
|
||||||
d = math.sqrt((x1-x2)**2 + (y1-y2)**2 + (z1-z2)**2)
|
x1, y1, z1 = getpos(stable_pos1)
|
||||||
total_error += (d - dist)**2
|
x2, y2, z2 = getpos(stable_pos2)
|
||||||
return total_error
|
d = math.sqrt((x1-x2)**2 + (y1-y2)**2 + (z1-z2)**2)
|
||||||
|
total_error += (d - dist)**2
|
||||||
|
return total_error
|
||||||
|
except ValueError:
|
||||||
|
return 9999999999999.9
|
||||||
new_params = mathutil.background_coordinate_descent(
|
new_params = mathutil.background_coordinate_descent(
|
||||||
self.printer, adj_params, params, delta_errorfunc)
|
self.printer, adj_params, params, delta_errorfunc)
|
||||||
# Log and report results
|
# Log and report results
|
||||||
|
|
|
@ -79,6 +79,7 @@ class DisplayGroup:
|
||||||
for row, col, template in self.data_items:
|
for row, col, template in self.data_items:
|
||||||
text = template.render(context)
|
text = template.render(context)
|
||||||
display.draw_text(row, col, text.replace('\n', ''), eventtime)
|
display.draw_text(row, col, text.replace('\n', ''), eventtime)
|
||||||
|
context.clear() # Remove circular references for better gc
|
||||||
|
|
||||||
class PrinterLCD:
|
class PrinterLCD:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
|
|
|
@ -103,7 +103,7 @@ class HD44780:
|
||||||
data = self.icons.get(glyph_name)
|
data = self.icons.get(glyph_name)
|
||||||
if data is not None:
|
if data is not None:
|
||||||
slot, bits = data
|
slot, bits = data
|
||||||
self.write_text(x, y, chr(slot))
|
self.write_text(x, y, [slot])
|
||||||
self.glyph_framebuffer[slot * 8:(slot + 1) * 8] = bits
|
self.glyph_framebuffer[slot * 8:(slot + 1) * 8] = bits
|
||||||
return 1
|
return 1
|
||||||
char = TextGlyphs.get(glyph_name)
|
char = TextGlyphs.get(glyph_name)
|
||||||
|
|
|
@ -397,8 +397,8 @@ class MenuContainer(MenuElement):
|
||||||
return self.select_at(index)
|
return self.select_at(index)
|
||||||
|
|
||||||
# override
|
# override
|
||||||
def render_container(self, eventtime):
|
def render_container(self, nrows, eventtime):
|
||||||
return ("", None)
|
return []
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return iter(self._items)
|
return iter(self._items)
|
||||||
|
@ -550,6 +550,7 @@ class MenuInput(MenuCommand):
|
||||||
class MenuList(MenuContainer):
|
class MenuList(MenuContainer):
|
||||||
def __init__(self, manager, config):
|
def __init__(self, manager, config):
|
||||||
super(MenuList, self).__init__(manager, config)
|
super(MenuList, self).__init__(manager, config)
|
||||||
|
self._viewport_top = 0
|
||||||
# create back item
|
# create back item
|
||||||
self._itemBack = self.manager.menuitem_from({
|
self._itemBack = self.manager.menuitem_from({
|
||||||
'type': 'command',
|
'type': 'command',
|
||||||
|
@ -562,29 +563,43 @@ class MenuList(MenuContainer):
|
||||||
|
|
||||||
def _populate(self):
|
def _populate(self):
|
||||||
super(MenuList, self)._populate()
|
super(MenuList, self)._populate()
|
||||||
|
self._viewport_top = 0
|
||||||
# add back as first item
|
# add back as first item
|
||||||
self.insert_item(self._itemBack, 0)
|
self.insert_item(self._itemBack, 0)
|
||||||
|
|
||||||
def render_container(self, eventtime):
|
def render_container(self, nrows, eventtime):
|
||||||
rows = []
|
manager = self.manager
|
||||||
selected_row = None
|
lines = []
|
||||||
|
selected_row = self.selected
|
||||||
|
# adjust viewport
|
||||||
|
if selected_row is not None:
|
||||||
|
if selected_row >= (self._viewport_top + nrows):
|
||||||
|
self._viewport_top = (selected_row - nrows) + 1
|
||||||
|
if selected_row < self._viewport_top:
|
||||||
|
self._viewport_top = selected_row
|
||||||
|
else:
|
||||||
|
self._viewport_top = 0
|
||||||
|
# clamps viewport
|
||||||
|
self._viewport_top = max(0, min(self._viewport_top, len(self) - nrows))
|
||||||
try:
|
try:
|
||||||
for row, item in enumerate(self):
|
for row in range(self._viewport_top, self._viewport_top + nrows):
|
||||||
s = ""
|
s = ""
|
||||||
selected = (row == self.selected)
|
if row < len(self):
|
||||||
if selected:
|
current = self[row]
|
||||||
item.heartbeat(eventtime)
|
selected = (row == selected_row)
|
||||||
selected_row = len(rows)
|
if selected:
|
||||||
name = str(item.render_name(selected))
|
current.heartbeat(eventtime)
|
||||||
if isinstance(item, MenuList):
|
name = manager.stripliterals(
|
||||||
s += name[:self.manager.cols-1].ljust(self.manager.cols-1)
|
manager.aslatin(current.render_name(selected)))
|
||||||
s += '>'
|
if isinstance(current, MenuList):
|
||||||
else:
|
s += name[:manager.cols-1].ljust(manager.cols-1)
|
||||||
s += name[:self.manager.cols].ljust(self.manager.cols)
|
s += '>'
|
||||||
rows.append(s)
|
else:
|
||||||
|
s += name
|
||||||
|
lines.append(s[:manager.cols].ljust(manager.cols))
|
||||||
except Exception:
|
except Exception:
|
||||||
logging.exception('List rendering error')
|
logging.exception('List rendering error')
|
||||||
return ("\n".join(rows), selected_row)
|
return lines
|
||||||
|
|
||||||
|
|
||||||
class MenuVSDList(MenuList):
|
class MenuVSDList(MenuList):
|
||||||
|
@ -624,7 +639,6 @@ class MenuManager:
|
||||||
self.menuitems = {}
|
self.menuitems = {}
|
||||||
self.menustack = []
|
self.menustack = []
|
||||||
self.children = {}
|
self.children = {}
|
||||||
self.top_row = 0
|
|
||||||
self.display = display
|
self.display = display
|
||||||
self.printer = config.get_printer()
|
self.printer = config.get_printer()
|
||||||
self.pconfig = self.printer.lookup_object('configfile')
|
self.pconfig = self.printer.lookup_object('configfile')
|
||||||
|
@ -682,7 +696,6 @@ class MenuManager:
|
||||||
|
|
||||||
def begin(self, eventtime):
|
def begin(self, eventtime):
|
||||||
self.menustack = []
|
self.menustack = []
|
||||||
self.top_row = 0
|
|
||||||
self.timer = 0
|
self.timer = 0
|
||||||
if isinstance(self.root, MenuContainer):
|
if isinstance(self.root, MenuContainer):
|
||||||
# send begin event
|
# send begin event
|
||||||
|
@ -780,21 +793,7 @@ class MenuManager:
|
||||||
container = self.stack_peek()
|
container = self.stack_peek()
|
||||||
if self.running and isinstance(container, MenuContainer):
|
if self.running and isinstance(container, MenuContainer):
|
||||||
container.heartbeat(eventtime)
|
container.heartbeat(eventtime)
|
||||||
content, viewport_row = container.render_container(eventtime)
|
lines = container.render_container(self.rows, eventtime)
|
||||||
if viewport_row is not None:
|
|
||||||
while viewport_row >= (self.top_row + self.rows):
|
|
||||||
self.top_row += 1
|
|
||||||
while viewport_row < self.top_row and self.top_row > 0:
|
|
||||||
self.top_row -= 1
|
|
||||||
else:
|
|
||||||
self.top_row = 0
|
|
||||||
rows = self.aslatin(content).splitlines()
|
|
||||||
for row in range(0, self.rows):
|
|
||||||
try:
|
|
||||||
text = self.stripliterals(rows[self.top_row + row])
|
|
||||||
except IndexError:
|
|
||||||
text = ""
|
|
||||||
lines.append(text.ljust(self.cols))
|
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
def screen_update_event(self, eventtime):
|
def screen_update_event(self, eventtime):
|
||||||
|
@ -884,6 +883,10 @@ class MenuManager:
|
||||||
current = container.selected_item()
|
current = container.selected_item()
|
||||||
if isinstance(current, MenuContainer):
|
if isinstance(current, MenuContainer):
|
||||||
self.stack_push(current)
|
self.stack_push(current)
|
||||||
|
elif isinstance(current, MenuInput):
|
||||||
|
if current.is_editing():
|
||||||
|
current.run_script('gcode', event=event)
|
||||||
|
current.run_script(event)
|
||||||
elif isinstance(current, MenuCommand):
|
elif isinstance(current, MenuCommand):
|
||||||
current.run_script('gcode', event=event)
|
current.run_script('gcode', event=event)
|
||||||
current.run_script(event)
|
current.run_script(event)
|
||||||
|
|
|
@ -13,10 +13,11 @@ BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000
|
||||||
TextGlyphs = { 'right_arrow': '\x1a', 'degrees': '\xf8' }
|
TextGlyphs = { 'right_arrow': '\x1a', 'degrees': '\xf8' }
|
||||||
|
|
||||||
class DisplayBase:
|
class DisplayBase:
|
||||||
def __init__(self, io, columns=128):
|
def __init__(self, io, columns=128, x_offset=0):
|
||||||
self.send = io.send
|
self.send = io.send
|
||||||
# framebuffers
|
# framebuffers
|
||||||
self.columns = columns
|
self.columns = columns
|
||||||
|
self.x_offset = x_offset
|
||||||
self.vram = [bytearray(self.columns) for i in range(8)]
|
self.vram = [bytearray(self.columns) for i in range(8)]
|
||||||
self.all_framebuffers = [(self.vram[i], bytearray('~'*self.columns), i)
|
self.all_framebuffers = [(self.vram[i], bytearray('~'*self.columns), i)
|
||||||
for i in range(8)]
|
for i in range(8)]
|
||||||
|
@ -71,10 +72,11 @@ class DisplayBase:
|
||||||
if x + len(data) > 16:
|
if x + len(data) > 16:
|
||||||
data = data[:16 - min(x, 16)]
|
data = data[:16 - min(x, 16)]
|
||||||
pix_x = x * 8
|
pix_x = x * 8
|
||||||
|
pix_x += self.x_offset
|
||||||
page_top = self.vram[y * 2]
|
page_top = self.vram[y * 2]
|
||||||
page_bot = self.vram[y * 2 + 1]
|
page_bot = self.vram[y * 2 + 1]
|
||||||
for c in data:
|
for c in bytearray(data):
|
||||||
bits_top, bits_bot = self.font[ord(c)]
|
bits_top, bits_bot = self.font[c]
|
||||||
page_top[pix_x:pix_x+8] = bits_top
|
page_top[pix_x:pix_x+8] = bits_top
|
||||||
page_bot[pix_x:pix_x+8] = bits_bot
|
page_bot[pix_x:pix_x+8] = bits_bot
|
||||||
pix_x += 8
|
pix_x += 8
|
||||||
|
@ -83,6 +85,7 @@ class DisplayBase:
|
||||||
return
|
return
|
||||||
bits_top, bits_bot = self._swizzle_bits(data)
|
bits_top, bits_bot = self._swizzle_bits(data)
|
||||||
pix_x = x * 8
|
pix_x = x * 8
|
||||||
|
pix_x += self.x_offset
|
||||||
page_top = self.vram[y * 2]
|
page_top = self.vram[y * 2]
|
||||||
page_bot = self.vram[y * 2 + 1]
|
page_bot = self.vram[y * 2 + 1]
|
||||||
for i in range(8):
|
for i in range(8):
|
||||||
|
@ -93,6 +96,7 @@ class DisplayBase:
|
||||||
if icon is not None and x < 15:
|
if icon is not None and x < 15:
|
||||||
# Draw icon in graphics mode
|
# Draw icon in graphics mode
|
||||||
pix_x = x * 8
|
pix_x = x * 8
|
||||||
|
pix_x += self.x_offset
|
||||||
page_idx = y * 2
|
page_idx = y * 2
|
||||||
self.vram[page_idx][pix_x:pix_x+16] = icon[0]
|
self.vram[page_idx][pix_x:pix_x+16] = icon[0]
|
||||||
self.vram[page_idx + 1][pix_x:pix_x+16] = icon[1]
|
self.vram[page_idx + 1][pix_x:pix_x+16] = icon[1]
|
||||||
|
@ -192,7 +196,7 @@ class UC1701(DisplayBase):
|
||||||
|
|
||||||
# The SSD1306 supports both i2c and "4-wire" spi
|
# The SSD1306 supports both i2c and "4-wire" spi
|
||||||
class SSD1306(DisplayBase):
|
class SSD1306(DisplayBase):
|
||||||
def __init__(self, config, columns=128):
|
def __init__(self, config, columns=128, x_offset=0):
|
||||||
cs_pin = config.get("cs_pin", None)
|
cs_pin = config.get("cs_pin", None)
|
||||||
if cs_pin is None:
|
if cs_pin is None:
|
||||||
io = I2C(config, 60)
|
io = I2C(config, 60)
|
||||||
|
@ -201,7 +205,7 @@ class SSD1306(DisplayBase):
|
||||||
io = SPI4wire(config, "dc_pin")
|
io = SPI4wire(config, "dc_pin")
|
||||||
io_bus = io.spi
|
io_bus = io.spi
|
||||||
self.reset = ResetHelper(config.get("reset_pin", None), io_bus)
|
self.reset = ResetHelper(config.get("reset_pin", None), io_bus)
|
||||||
DisplayBase.__init__(self, io, columns)
|
DisplayBase.__init__(self, io, columns, x_offset)
|
||||||
self.contrast = config.getint('contrast', 239, minval=0, maxval=255)
|
self.contrast = config.getint('contrast', 239, minval=0, maxval=255)
|
||||||
self.vcomh = config.getint('vcomh', 0, minval=0, maxval=63)
|
self.vcomh = config.getint('vcomh', 0, minval=0, maxval=63)
|
||||||
self.invert = config.getboolean('invert', False)
|
self.invert = config.getboolean('invert', False)
|
||||||
|
@ -232,4 +236,5 @@ class SSD1306(DisplayBase):
|
||||||
# the SH1106 is SSD1306 compatible with up to 132 columns
|
# the SH1106 is SSD1306 compatible with up to 132 columns
|
||||||
class SH1106(SSD1306):
|
class SH1106(SSD1306):
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
SSD1306.__init__(self, config, 132)
|
x_offset = config.getint('x_offset', 0, minval=0, maxval=3)
|
||||||
|
SSD1306.__init__(self, config, 132, x_offset=x_offset)
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import math, logging
|
import math, logging
|
||||||
import homing
|
|
||||||
|
|
||||||
TRINAMIC_DRIVERS = ["tmc2130", "tmc2208", "tmc2209", "tmc2660", "tmc5160"]
|
TRINAMIC_DRIVERS = ["tmc2130", "tmc2208", "tmc2209", "tmc2660", "tmc5160"]
|
||||||
|
|
||||||
|
@ -81,7 +80,7 @@ class EndstopPhase:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
msg = "Unable to get stepper %s phase: %s" % (self.name, str(e))
|
msg = "Unable to get stepper %s phase: %s" % (self.name, str(e))
|
||||||
logging.exception(msg)
|
logging.exception(msg)
|
||||||
raise homing.EndstopError(msg)
|
raise self.printer.command_error(msg)
|
||||||
if stepper.is_dir_inverted():
|
if stepper.is_dir_inverted():
|
||||||
phase = (self.phases - 1) - phase
|
phase = (self.phases - 1) - phase
|
||||||
else:
|
else:
|
||||||
|
@ -95,7 +94,7 @@ class EndstopPhase:
|
||||||
if delta >= self.phases - self.endstop_phase_accuracy:
|
if delta >= self.phases - self.endstop_phase_accuracy:
|
||||||
delta -= self.phases
|
delta -= self.phases
|
||||||
elif delta > self.endstop_phase_accuracy:
|
elif delta > self.endstop_phase_accuracy:
|
||||||
raise homing.EndstopError(
|
raise self.printer.command_error(
|
||||||
"Endstop %s incorrect phase (got %d vs %d)" % (
|
"Endstop %s incorrect phase (got %d vs %d)" % (
|
||||||
self.name, phase, self.endstop_phase))
|
self.name, phase, self.endstop_phase))
|
||||||
return delta * self.step_dist
|
return delta * self.step_dist
|
||||||
|
|
|
@ -12,7 +12,17 @@ class GCodeButton:
|
||||||
self.pin = config.get('pin')
|
self.pin = config.get('pin')
|
||||||
self.last_state = 0
|
self.last_state = 0
|
||||||
buttons = self.printer.load_object(config, "buttons")
|
buttons = self.printer.load_object(config, "buttons")
|
||||||
buttons.register_buttons([self.pin], self.button_callback)
|
analog_range = config.get('analog_range', None)
|
||||||
|
if analog_range is None:
|
||||||
|
buttons.register_buttons([self.pin], self.button_callback)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
amin, amax = map(float, analog_range.split(','))
|
||||||
|
except:
|
||||||
|
raise config.error("Unable to parse analog_range")
|
||||||
|
pullup = config.getfloat('analog_pullup_resistor', 4700., above=0.)
|
||||||
|
buttons.register_adc_button(self.pin, amin, amax, pullup,
|
||||||
|
self.button_callback)
|
||||||
gcode_macro = self.printer.load_object(config, 'gcode_macro')
|
gcode_macro = self.printer.load_object(config, 'gcode_macro')
|
||||||
self.press_template = gcode_macro.load_template(config, 'press_gcode')
|
self.press_template = gcode_macro.load_template(config, 'press_gcode')
|
||||||
self.release_template = gcode_macro.load_template(config,
|
self.release_template = gcode_macro.load_template(config,
|
||||||
|
|
|
@ -74,10 +74,10 @@ class Heater:
|
||||||
pwm_time = read_time + self.pwm_delay
|
pwm_time = read_time + self.pwm_delay
|
||||||
self.next_pwm_time = pwm_time + 0.75 * MAX_HEAT_TIME
|
self.next_pwm_time = pwm_time + 0.75 * MAX_HEAT_TIME
|
||||||
self.last_pwm_value = value
|
self.last_pwm_value = value
|
||||||
logging.debug("%s: pwm=%.3f@%.3f (from %.3f@%.3f [%.3f])",
|
|
||||||
self.name, value, pwm_time,
|
|
||||||
self.last_temp, self.last_temp_time, self.target_temp)
|
|
||||||
self.mcu_pwm.set_pwm(pwm_time, value)
|
self.mcu_pwm.set_pwm(pwm_time, value)
|
||||||
|
#logging.debug("%s: pwm=%.3f@%.3f (from %.3f@%.3f [%.3f])",
|
||||||
|
# self.name, value, pwm_time,
|
||||||
|
# self.last_temp, self.last_temp_time, self.target_temp)
|
||||||
def temperature_callback(self, read_time, temp):
|
def temperature_callback(self, read_time, temp):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
time_diff = read_time - self.last_temp_time
|
time_diff = read_time - self.last_temp_time
|
||||||
|
|
|
@ -124,7 +124,7 @@ class InputShaper:
|
||||||
self.shapers.keys()[
|
self.shapers.keys()[
|
||||||
self.shapers.values().index(shaper_type_x)]
|
self.shapers.values().index(shaper_type_x)]
|
||||||
, self.shapers.keys()[
|
, self.shapers.keys()[
|
||||||
self.shapers.values().index(shaper_type_x)]
|
self.shapers.values().index(shaper_type_y)]
|
||||||
, shaper_freq_x, shaper_freq_y
|
, shaper_freq_x, shaper_freq_y
|
||||||
, damping_ratio_x, damping_ratio_y))
|
, damping_ratio_x, damping_ratio_y))
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import logging, bisect
|
import logging, bisect
|
||||||
import homing
|
|
||||||
|
|
||||||
class ManualProbe:
|
class ManualProbe:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
|
@ -97,7 +96,7 @@ class ManualProbeHelper:
|
||||||
if curpos[2] < z_bob_pos:
|
if curpos[2] < z_bob_pos:
|
||||||
self.toolhead.manual_move([None, None, z_bob_pos], self.speed)
|
self.toolhead.manual_move([None, None, z_bob_pos], self.speed)
|
||||||
self.toolhead.manual_move([None, None, z_pos], self.speed)
|
self.toolhead.manual_move([None, None, z_pos], self.speed)
|
||||||
except homing.CommandError as e:
|
except self.printer.command_error as e:
|
||||||
self.finalize(False)
|
self.finalize(False)
|
||||||
raise
|
raise
|
||||||
def report_z_status(self, warn_no_change=False, prev_pos=None):
|
def report_z_status(self, warn_no_change=False, prev_pos=None):
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
# Copyright (C) 2019 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2019 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import stepper, homing, chelper
|
import stepper, chelper
|
||||||
from . import force_move
|
from . import force_move
|
||||||
|
|
||||||
ENDSTOP_SAMPLE_TIME = .000015
|
ENDSTOP_SAMPLE_TIME = .000015
|
||||||
|
@ -110,7 +110,7 @@ class ManualStepper:
|
||||||
error = str(e)
|
error = str(e)
|
||||||
self.sync_print_time()
|
self.sync_print_time()
|
||||||
if error is not None:
|
if error is not None:
|
||||||
raise homing.CommandError(error)
|
raise self.printer.command_error(error)
|
||||||
cmd_MANUAL_STEPPER_help = "Command a manually configured stepper"
|
cmd_MANUAL_STEPPER_help = "Command a manually configured stepper"
|
||||||
def cmd_MANUAL_STEPPER(self, gcmd):
|
def cmd_MANUAL_STEPPER(self, gcmd):
|
||||||
enable = gcmd.get_int('ENABLE', None)
|
enable = gcmd.get_int('ENABLE', None)
|
||||||
|
|
|
@ -130,7 +130,7 @@ class ControlAutoTune:
|
||||||
def calc_final_pid(self):
|
def calc_final_pid(self):
|
||||||
cycle_times = [(self.peaks[pos][1] - self.peaks[pos-2][1], pos)
|
cycle_times = [(self.peaks[pos][1] - self.peaks[pos-2][1], pos)
|
||||||
for pos in range(4, len(self.peaks))]
|
for pos in range(4, len(self.peaks))]
|
||||||
midpoint_pos = sorted(cycle_times)[len(cycle_times)/2][1]
|
midpoint_pos = sorted(cycle_times)[len(cycle_times)//2][1]
|
||||||
return self.calc_pid(midpoint_pos)
|
return self.calc_pid(midpoint_pos)
|
||||||
# Offline analysis helper
|
# Offline analysis helper
|
||||||
def write_file(self, filename):
|
def write_file(self, filename):
|
||||||
|
|
|
@ -8,10 +8,9 @@ import pins, homing
|
||||||
from . import manual_probe
|
from . import manual_probe
|
||||||
|
|
||||||
HINT_TIMEOUT = """
|
HINT_TIMEOUT = """
|
||||||
Make sure to home the printer before probing. If the probe
|
If the probe did not move far enough to trigger, then
|
||||||
did not move far enough to trigger, then consider reducing
|
consider reducing the Z axis minimum position so the probe
|
||||||
the Z axis minimum position so the probe can travel further
|
can travel further (the Z minimum position can be negative).
|
||||||
(the Z minimum position can be negative).
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class PrinterProbe:
|
class PrinterProbe:
|
||||||
|
@ -26,6 +25,7 @@ class PrinterProbe:
|
||||||
self.z_offset = config.getfloat('z_offset')
|
self.z_offset = config.getfloat('z_offset')
|
||||||
self.probe_calibrate_z = 0.
|
self.probe_calibrate_z = 0.
|
||||||
self.multi_probe_pending = False
|
self.multi_probe_pending = False
|
||||||
|
self.last_state = False
|
||||||
# Infer Z position to move to during a probe
|
# Infer Z position to move to during a probe
|
||||||
if config.has_section('stepper_z'):
|
if config.has_section('stepper_z'):
|
||||||
zconfig = config.getsection('stepper_z')
|
zconfig = config.getsection('stepper_z')
|
||||||
|
@ -107,6 +107,9 @@ class PrinterProbe:
|
||||||
return self.x_offset, self.y_offset, self.z_offset
|
return self.x_offset, self.y_offset, self.z_offset
|
||||||
def _probe(self, speed):
|
def _probe(self, speed):
|
||||||
toolhead = self.printer.lookup_object('toolhead')
|
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")
|
||||||
homing_state = homing.Homing(self.printer)
|
homing_state = homing.Homing(self.printer)
|
||||||
pos = toolhead.get_position()
|
pos = toolhead.get_position()
|
||||||
pos[2] = self.z_position
|
pos[2] = self.z_position
|
||||||
|
@ -115,11 +118,11 @@ class PrinterProbe:
|
||||||
try:
|
try:
|
||||||
homing_state.homing_move(pos, endstops, speed,
|
homing_state.homing_move(pos, endstops, speed,
|
||||||
probe_pos=True, verify_movement=verify)
|
probe_pos=True, verify_movement=verify)
|
||||||
except homing.CommandError as e:
|
except self.printer.command_error as e:
|
||||||
reason = str(e)
|
reason = str(e)
|
||||||
if "Timeout during endstop homing" in reason:
|
if "Timeout during endstop homing" in reason:
|
||||||
reason += HINT_TIMEOUT
|
reason += HINT_TIMEOUT
|
||||||
raise homing.CommandError(reason)
|
raise self.printer.command_error(reason)
|
||||||
pos = toolhead.get_position()
|
pos = toolhead.get_position()
|
||||||
self.gcode.respond_info("probe at %.3f,%.3f is z=%.6f"
|
self.gcode.respond_info("probe at %.3f,%.3f is z=%.6f"
|
||||||
% (pos[0], pos[1], pos[2]))
|
% (pos[0], pos[1], pos[2]))
|
||||||
|
@ -162,8 +165,7 @@ class PrinterProbe:
|
||||||
z_positions = [p[2] for p in positions]
|
z_positions = [p[2] for p in positions]
|
||||||
if max(z_positions) - min(z_positions) > samples_tolerance:
|
if max(z_positions) - min(z_positions) > samples_tolerance:
|
||||||
if retries >= samples_retries:
|
if retries >= samples_retries:
|
||||||
raise homing.CommandError(
|
raise gcmd.error("Probe samples exceed samples_tolerance")
|
||||||
"Probe samples exceed samples_tolerance")
|
|
||||||
gcmd.respond_info("Probe samples exceed tolerance. Retrying...")
|
gcmd.respond_info("Probe samples exceed tolerance. Retrying...")
|
||||||
retries += 1
|
retries += 1
|
||||||
positions = []
|
positions = []
|
||||||
|
@ -186,7 +188,10 @@ class PrinterProbe:
|
||||||
toolhead = self.printer.lookup_object('toolhead')
|
toolhead = self.printer.lookup_object('toolhead')
|
||||||
print_time = toolhead.get_last_move_time()
|
print_time = toolhead.get_last_move_time()
|
||||||
res = self.mcu_probe.query_endstop(print_time)
|
res = self.mcu_probe.query_endstop(print_time)
|
||||||
|
self.last_state = res
|
||||||
gcmd.respond_info("probe: %s" % (["open", "TRIGGERED"][not not res],))
|
gcmd.respond_info("probe: %s" % (["open", "TRIGGERED"][not not res],))
|
||||||
|
def get_status(self, eventtime):
|
||||||
|
return {'last_query': self.last_state}
|
||||||
cmd_PROBE_ACCURACY_help = "Probe Z-height accuracy at current XY position"
|
cmd_PROBE_ACCURACY_help = "Probe Z-height accuracy at current XY position"
|
||||||
def cmd_PROBE_ACCURACY(self, gcmd):
|
def cmd_PROBE_ACCURACY(self, gcmd):
|
||||||
speed = gcmd.get_float("PROBE_SPEED", self.speed, above=0.)
|
speed = gcmd.get_float("PROBE_SPEED", self.speed, above=0.)
|
||||||
|
@ -295,14 +300,14 @@ class ProbeEndstopWrapper:
|
||||||
start_pos = toolhead.get_position()
|
start_pos = toolhead.get_position()
|
||||||
self.activate_gcode.run_gcode_from_command()
|
self.activate_gcode.run_gcode_from_command()
|
||||||
if toolhead.get_position()[:3] != start_pos[:3]:
|
if toolhead.get_position()[:3] != start_pos[:3]:
|
||||||
raise homing.CommandError(
|
raise self.printer.command_error(
|
||||||
"Toolhead moved during probe activate_gcode script")
|
"Toolhead moved during probe activate_gcode script")
|
||||||
def probe_finish(self):
|
def probe_finish(self):
|
||||||
toolhead = self.printer.lookup_object('toolhead')
|
toolhead = self.printer.lookup_object('toolhead')
|
||||||
start_pos = toolhead.get_position()
|
start_pos = toolhead.get_position()
|
||||||
self.deactivate_gcode.run_gcode_from_command()
|
self.deactivate_gcode.run_gcode_from_command()
|
||||||
if toolhead.get_position()[:3] != start_pos[:3]:
|
if toolhead.get_position()[:3] != start_pos[:3]:
|
||||||
raise homing.CommandError(
|
raise self.printer.command_error(
|
||||||
"Toolhead moved during probe deactivate_gcode script")
|
"Toolhead moved during probe deactivate_gcode script")
|
||||||
def get_position_endstop(self):
|
def get_position_endstop(self):
|
||||||
return self.position_endstop
|
return self.position_endstop
|
||||||
|
@ -337,6 +342,9 @@ class ProbePointsHelper:
|
||||||
if len(self.probe_points) < n:
|
if len(self.probe_points) < n:
|
||||||
raise self.printer.config_error(
|
raise self.printer.config_error(
|
||||||
"Need at least %d probe points for %s" % (n, self.name))
|
"Need at least %d probe points for %s" % (n, self.name))
|
||||||
|
def update_probe_points(self, points, min_points):
|
||||||
|
self.probe_points = points
|
||||||
|
self.minimum_points(min_points)
|
||||||
def use_xy_offsets(self, use_offsets):
|
def use_xy_offsets(self, use_offsets):
|
||||||
self.use_offsets = use_offsets
|
self.use_offsets = use_offsets
|
||||||
def get_lift_speed(self):
|
def get_lift_speed(self):
|
||||||
|
|
|
@ -6,6 +6,22 @@
|
||||||
import logging
|
import logging
|
||||||
from . import probe, z_tilt
|
from . import probe, z_tilt
|
||||||
|
|
||||||
|
# Leveling code for XY rails that are controlled by Z steppers as in:
|
||||||
|
#
|
||||||
|
# Z stepper1 ----> O O <---- Z stepper2
|
||||||
|
# | * <-- probe1 probe2 --> * |
|
||||||
|
# | |
|
||||||
|
# | | <--- Y2 rail
|
||||||
|
# Y1 rail -----> | |
|
||||||
|
# | |
|
||||||
|
# |=============================|
|
||||||
|
# | ^ |
|
||||||
|
# | | |
|
||||||
|
# | X rail --/ |
|
||||||
|
# | |
|
||||||
|
# | * <-- probe0 probe3 --> * |
|
||||||
|
# Z stepper0 ----> O O <---- Z stepper3
|
||||||
|
|
||||||
class QuadGantryLevel:
|
class QuadGantryLevel:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.printer = config.get_printer()
|
self.printer = config.get_printer()
|
||||||
|
@ -48,29 +64,36 @@ class QuadGantryLevel:
|
||||||
" ".join(["%s: %.6f" % (z_id, z_positions[z_id])
|
" ".join(["%s: %.6f" % (z_id, z_positions[z_id])
|
||||||
for z_id in range(len(z_positions))]))
|
for z_id in range(len(z_positions))]))
|
||||||
self.gcode.respond_info(points_message)
|
self.gcode.respond_info(points_message)
|
||||||
p1 = [positions[0][0] + offsets[0],z_positions[0]]
|
# Calculate slope along X axis between probe point 0 and 3
|
||||||
p2 = [positions[1][0] + offsets[0],z_positions[1]]
|
ppx0 = [positions[0][0] + offsets[0], z_positions[0]]
|
||||||
p3 = [positions[2][0] + offsets[0],z_positions[2]]
|
ppx3 = [positions[3][0] + offsets[0], z_positions[3]]
|
||||||
p4 = [positions[3][0] + offsets[0],z_positions[3]]
|
slope_x_pp03 = self.linefit(ppx0, ppx3)
|
||||||
f1 = self.linefit(p1,p4)
|
# Calculate slope along X axis between probe point 1 and 2
|
||||||
f2 = self.linefit(p2,p3)
|
ppx1 = [positions[1][0] + offsets[0], z_positions[1]]
|
||||||
logging.info("quad_gantry_level f1: %s, f2: %s" % (f1,f2))
|
ppx2 = [positions[2][0] + offsets[0], z_positions[2]]
|
||||||
|
slope_x_pp12 = self.linefit(ppx1, ppx2)
|
||||||
|
logging.info("quad_gantry_level f1: %s, f2: %s"
|
||||||
|
% (slope_x_pp03, slope_x_pp12))
|
||||||
|
# Calculate gantry slope along Y axis between stepper 0 and 1
|
||||||
a1 = [positions[0][1] + offsets[1],
|
a1 = [positions[0][1] + offsets[1],
|
||||||
self.plot(f1,self.gantry_corners[0][0])]
|
self.plot(slope_x_pp03, self.gantry_corners[0][0])]
|
||||||
a2 = [positions[1][1] + offsets[1],
|
a2 = [positions[1][1] + offsets[1],
|
||||||
self.plot(f2,self.gantry_corners[0][0])]
|
self.plot(slope_x_pp12, self.gantry_corners[0][0])]
|
||||||
|
slope_y_s01 = self.linefit(a1, a2)
|
||||||
|
# Calculate gantry slope along Y axis between stepper 2 and 3
|
||||||
b1 = [positions[0][1] + offsets[1],
|
b1 = [positions[0][1] + offsets[1],
|
||||||
self.plot(f1,self.gantry_corners[1][0])]
|
self.plot(slope_x_pp03, self.gantry_corners[1][0])]
|
||||||
b2 = [positions[1][1] + offsets[1],
|
b2 = [positions[1][1] + offsets[1],
|
||||||
self.plot(f2,self.gantry_corners[1][0])]
|
self.plot(slope_x_pp12, self.gantry_corners[1][0])]
|
||||||
af = self.linefit(a1,a2)
|
slope_y_s23 = self.linefit(b1, b2)
|
||||||
bf = self.linefit(b1,b2)
|
logging.info("quad_gantry_level af: %s, bf: %s"
|
||||||
logging.info("quad_gantry_level af: %s, bf: %s" % (af,bf))
|
% (slope_y_s01, slope_y_s23))
|
||||||
|
# Calculate z height of each stepper
|
||||||
z_height = [0,0,0,0]
|
z_height = [0,0,0,0]
|
||||||
z_height[0] = self.plot(af,self.gantry_corners[0][1])
|
z_height[0] = self.plot(slope_y_s01, self.gantry_corners[0][1])
|
||||||
z_height[1] = self.plot(af,self.gantry_corners[1][1])
|
z_height[1] = self.plot(slope_y_s01, self.gantry_corners[1][1])
|
||||||
z_height[2] = self.plot(bf,self.gantry_corners[1][1])
|
z_height[2] = self.plot(slope_y_s23, self.gantry_corners[1][1])
|
||||||
z_height[3] = self.plot(bf,self.gantry_corners[0][1])
|
z_height[3] = self.plot(slope_y_s23, self.gantry_corners[0][1])
|
||||||
|
|
||||||
ainfo = zip(["z","z1","z2","z3"], z_height[0:4])
|
ainfo = zip(["z","z1","z2","z3"], z_height[0:4])
|
||||||
apos = " ".join(["%s: %06f" % (x) for x in ainfo])
|
apos = " ".join(["%s: %06f" % (x) for x in ainfo])
|
||||||
|
|
|
@ -267,15 +267,18 @@ MAX31865_FAULT_REFINHIGH = 0x10
|
||||||
MAX31865_FAULT_RTDINLOW = 0x08
|
MAX31865_FAULT_RTDINLOW = 0x08
|
||||||
MAX31865_FAULT_OVUV = 0x04
|
MAX31865_FAULT_OVUV = 0x04
|
||||||
|
|
||||||
VAL_A = 0.00390830
|
MAX31865_ADC_MAX = 1<<15
|
||||||
VAL_B = 0.0000005775
|
|
||||||
VAL_C = -0.00000000000418301
|
# Callendar-Van Dusen constants for platinum resistance thermometers (RTD)
|
||||||
VAL_ADC_MAX = 32768.0 # 2^15
|
CVD_A = 3.9083e-3
|
||||||
|
CVD_B = -5.775e-7
|
||||||
|
|
||||||
class MAX31865(SensorBase):
|
class MAX31865(SensorBase):
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.rtd_nominal_r = config.getint('rtd_nominal_r', 100)
|
rtd_nominal_r = config.getfloat('rtd_nominal_r', 100., above=0.)
|
||||||
self.reference_r = config.getfloat('rtd_reference_r', 430., above=0.)
|
rtd_reference_r = config.getfloat('rtd_reference_r', 430., above=0.)
|
||||||
|
adc_to_resist = rtd_reference_r / float(MAX31865_ADC_MAX)
|
||||||
|
self.adc_to_resist_div_nominal = adc_to_resist / rtd_nominal_r
|
||||||
SensorBase.__init__(self, config, "MAX31865",
|
SensorBase.__init__(self, config, "MAX31865",
|
||||||
self.build_spi_init(config))
|
self.build_spi_init(config))
|
||||||
def calc_temp(self, adc, fault):
|
def calc_temp(self, adc, fault):
|
||||||
|
@ -295,20 +298,20 @@ class MAX31865(SensorBase):
|
||||||
if fault & 0x03:
|
if fault & 0x03:
|
||||||
self.fault("Max31865 Unspecified error")
|
self.fault("Max31865 Unspecified error")
|
||||||
adc = adc >> 1 # remove fault bit
|
adc = adc >> 1 # remove fault bit
|
||||||
R_rtd = (self.reference_r * adc) / VAL_ADC_MAX
|
R_div_nominal = adc * self.adc_to_resist_div_nominal
|
||||||
temp = ((( ( -1 * self.rtd_nominal_r ) * VAL_A )
|
# Resistance (relative to rtd_nominal_r) is calculated using:
|
||||||
+ math.sqrt( ( self.rtd_nominal_r**2 * VAL_A * VAL_A )
|
# R_div_nominal = 1. + CVD_A * temp + CVD_B * temp**2
|
||||||
- ( 4 * self.rtd_nominal_r * VAL_B
|
# Solve for temp using quadratic equation:
|
||||||
* ( self.rtd_nominal_r - R_rtd ) )))
|
# temp = (-b +- sqrt(b**2 - 4ac)) / 2a
|
||||||
/ (2 * self.rtd_nominal_r * VAL_B))
|
discriminant = math.sqrt(CVD_A**2 - 4. * CVD_B * (1. - R_div_nominal))
|
||||||
|
temp = (-CVD_A + discriminant) / (2. * CVD_B)
|
||||||
return temp
|
return temp
|
||||||
def calc_adc(self, temp):
|
def calc_adc(self, temp):
|
||||||
R_rtd = temp * ( 2 * self.rtd_nominal_r * VAL_B )
|
# Calculate relative resistance via Callendar-Van Dusen formula:
|
||||||
R_rtd = math.pow( ( R_rtd + ( self.rtd_nominal_r * VAL_A ) ), 2)
|
# resistance = rtd_nominal_r * (1 + CVD_A * temp + CVD_B * temp**2)
|
||||||
R_rtd = -1 * ( R_rtd - (self.rtd_nominal_r**2 * VAL_A * VAL_A ) )
|
R_div_nominal = 1. + CVD_A * temp + CVD_B * temp * temp
|
||||||
R_rtd = R_rtd / ( 4 * self.rtd_nominal_r * VAL_B )
|
adc = int(R_div_nominal / self.adc_to_resist_div_nominal + 0.5)
|
||||||
R_rtd = ( -1 * R_rtd ) + self.rtd_nominal_r
|
adc = max(0, min(MAX31865_ADC_MAX, adc))
|
||||||
adc = int( ( ( R_rtd * VAL_ADC_MAX ) / self.reference_r) + 0.5 )
|
|
||||||
adc = adc << 1 # Add fault bit
|
adc = adc << 1 # Add fault bit
|
||||||
return adc
|
return adc
|
||||||
def build_spi_init(self, config):
|
def build_spi_init(self, config):
|
||||||
|
|
|
@ -251,7 +251,9 @@ class GCodeDispatch:
|
||||||
raise gcmd.error(self.printer.get_state_message()[0])
|
raise gcmd.error(self.printer.get_state_message()[0])
|
||||||
return
|
return
|
||||||
if not cmd:
|
if not cmd:
|
||||||
logging.debug(gcmd.get_commandline())
|
cmdline = gcmd.get_commandline()
|
||||||
|
if cmdline:
|
||||||
|
logging.debug(cmdline)
|
||||||
return
|
return
|
||||||
if cmd.startswith("M117 "):
|
if cmd.startswith("M117 "):
|
||||||
# Handle M117 gcode with numeric and special characters
|
# Handle M117 gcode with numeric and special characters
|
||||||
|
|
|
@ -101,8 +101,8 @@ class Homing:
|
||||||
for s, name, spos, epos in end_mcu_pos:
|
for s, name, spos, epos in end_mcu_pos:
|
||||||
if spos == epos:
|
if spos == epos:
|
||||||
if probe_pos:
|
if probe_pos:
|
||||||
raise EndstopError("Probe triggered prior to movement")
|
raise CommandError("Probe triggered prior to movement")
|
||||||
raise EndstopError(
|
raise CommandError(
|
||||||
"Endstop %s still triggered after retract" % (name,))
|
"Endstop %s still triggered after retract" % (name,))
|
||||||
def home_rails(self, rails, forcepos, movepos):
|
def home_rails(self, rails, forcepos, movepos):
|
||||||
# Notify of upcoming homing operation
|
# Notify of upcoming homing operation
|
||||||
|
@ -161,11 +161,4 @@ def multi_complete(printer, completions):
|
||||||
class CommandError(Exception):
|
class CommandError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class EndstopError(CommandError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def EndstopMoveError(pos, msg="Move out of range"):
|
|
||||||
return EndstopError("%s: %.3f %.3f %.3f [%.3f]" % (
|
|
||||||
msg, pos[0], pos[1], pos[2], pos[3]))
|
|
||||||
|
|
||||||
Coord = collections.namedtuple('Coord', ('x', 'y', 'z', 'e'))
|
Coord = collections.namedtuple('Coord', ('x', 'y', 'z', 'e'))
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import logging
|
import logging
|
||||||
import stepper, homing
|
import stepper
|
||||||
|
|
||||||
class CartKinematics:
|
class CartKinematics:
|
||||||
def __init__(self, toolhead, config):
|
def __init__(self, toolhead, config):
|
||||||
|
@ -100,9 +100,8 @@ class CartKinematics:
|
||||||
and (end_pos[i] < self.limits[i][0]
|
and (end_pos[i] < self.limits[i][0]
|
||||||
or end_pos[i] > self.limits[i][1])):
|
or end_pos[i] > self.limits[i][1])):
|
||||||
if self.limits[i][0] > self.limits[i][1]:
|
if self.limits[i][0] > self.limits[i][1]:
|
||||||
raise homing.EndstopMoveError(
|
raise move.move_error("Must home axis first")
|
||||||
end_pos, "Must home axis first")
|
raise move.move_error()
|
||||||
raise homing.EndstopMoveError(end_pos)
|
|
||||||
def check_move(self, move):
|
def check_move(self, move):
|
||||||
limits = self.limits
|
limits = self.limits
|
||||||
xpos, ypos = move.end_pos[:2]
|
xpos, ypos = move.end_pos[:2]
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import logging, math
|
import logging, math
|
||||||
import stepper, homing
|
import stepper
|
||||||
|
|
||||||
class CoreXYKinematics:
|
class CoreXYKinematics:
|
||||||
def __init__(self, toolhead, config):
|
def __init__(self, toolhead, config):
|
||||||
|
@ -77,9 +77,8 @@ class CoreXYKinematics:
|
||||||
and (end_pos[i] < self.limits[i][0]
|
and (end_pos[i] < self.limits[i][0]
|
||||||
or end_pos[i] > self.limits[i][1])):
|
or end_pos[i] > self.limits[i][1])):
|
||||||
if self.limits[i][0] > self.limits[i][1]:
|
if self.limits[i][0] > self.limits[i][1]:
|
||||||
raise homing.EndstopMoveError(
|
raise move.move_error("Must home axis first")
|
||||||
end_pos, "Must home axis first")
|
raise move.move_error()
|
||||||
raise homing.EndstopMoveError(end_pos)
|
|
||||||
def check_move(self, move):
|
def check_move(self, move):
|
||||||
limits = self.limits
|
limits = self.limits
|
||||||
xpos, ypos = move.end_pos[:2]
|
xpos, ypos = move.end_pos[:2]
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import logging, math
|
import logging, math
|
||||||
import stepper, homing
|
import stepper
|
||||||
|
|
||||||
class CoreXZKinematics:
|
class CoreXZKinematics:
|
||||||
def __init__(self, toolhead, config):
|
def __init__(self, toolhead, config):
|
||||||
|
@ -76,9 +76,8 @@ class CoreXZKinematics:
|
||||||
and (end_pos[i] < self.limits[i][0]
|
and (end_pos[i] < self.limits[i][0]
|
||||||
or end_pos[i] > self.limits[i][1])):
|
or end_pos[i] > self.limits[i][1])):
|
||||||
if self.limits[i][0] > self.limits[i][1]:
|
if self.limits[i][0] > self.limits[i][1]:
|
||||||
raise homing.EndstopMoveError(
|
raise move.move_error("Must home axis first")
|
||||||
end_pos, "Must home axis first")
|
raise move.move_error()
|
||||||
raise homing.EndstopMoveError(end_pos)
|
|
||||||
def check_move(self, move):
|
def check_move(self, move):
|
||||||
limits = self.limits
|
limits = self.limits
|
||||||
xpos, ypos = move.end_pos[:2]
|
xpos, ypos = move.end_pos[:2]
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import math, logging
|
import math, logging
|
||||||
import stepper, homing, mathutil
|
import stepper, mathutil
|
||||||
|
|
||||||
# Slow moves once the ratio of tower to XY movement exceeds SLOW_RATIO
|
# Slow moves once the ratio of tower to XY movement exceeds SLOW_RATIO
|
||||||
SLOW_RATIO = 3.
|
SLOW_RATIO = 3.
|
||||||
|
@ -118,7 +118,7 @@ class DeltaKinematics:
|
||||||
# Normal XY move
|
# Normal XY move
|
||||||
return
|
return
|
||||||
if self.need_home:
|
if self.need_home:
|
||||||
raise homing.EndstopMoveError(end_pos, "Must home first")
|
raise move.move_error("Must home first")
|
||||||
end_z = end_pos[2]
|
end_z = end_pos[2]
|
||||||
limit_xy2 = self.max_xy2
|
limit_xy2 = self.max_xy2
|
||||||
if end_z > self.limit_z:
|
if end_z > self.limit_z:
|
||||||
|
@ -127,7 +127,7 @@ class DeltaKinematics:
|
||||||
# Move out of range - verify not a homing move
|
# Move out of range - verify not a homing move
|
||||||
if (end_pos[:2] != self.home_position[:2]
|
if (end_pos[:2] != self.home_position[:2]
|
||||||
or end_z < self.min_z or end_z > self.home_position[2]):
|
or end_z < self.min_z or end_z > self.home_position[2]):
|
||||||
raise homing.EndstopMoveError(end_pos)
|
raise move.move_error()
|
||||||
limit_xy2 = -1.
|
limit_xy2 = -1.
|
||||||
if move.axes_d[2]:
|
if move.axes_d[2]:
|
||||||
move.limit_speed(self.max_z_velocity, move.accel)
|
move.limit_speed(self.max_z_velocity, move.accel)
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import math, logging
|
import math, logging
|
||||||
import stepper, homing, chelper
|
import stepper, chelper
|
||||||
|
|
||||||
class PrinterExtruder:
|
class PrinterExtruder:
|
||||||
def __init__(self, config, extruder_num):
|
def __init__(self, config, extruder_num):
|
||||||
|
@ -107,13 +107,13 @@ class PrinterExtruder:
|
||||||
def check_move(self, move):
|
def check_move(self, move):
|
||||||
axis_r = move.axes_r[3]
|
axis_r = move.axes_r[3]
|
||||||
if not self.heater.can_extrude:
|
if not self.heater.can_extrude:
|
||||||
raise homing.EndstopError(
|
raise self.printer.command_error(
|
||||||
"Extrude below minimum temp\n"
|
"Extrude below minimum temp\n"
|
||||||
"See the 'min_extrude_temp' config option for details")
|
"See the 'min_extrude_temp' config option for details")
|
||||||
if (not move.axes_d[0] and not move.axes_d[1]) or axis_r < 0.:
|
if (not move.axes_d[0] and not move.axes_d[1]) or axis_r < 0.:
|
||||||
# Extrude only move (or retraction move) - limit accel and velocity
|
# Extrude only move (or retraction move) - limit accel and velocity
|
||||||
if abs(move.axes_d[3]) > self.max_e_dist:
|
if abs(move.axes_d[3]) > self.max_e_dist:
|
||||||
raise homing.EndstopError(
|
raise self.printer.command_error(
|
||||||
"Extrude only move too long (%.3fmm vs %.3fmm)\n"
|
"Extrude only move too long (%.3fmm vs %.3fmm)\n"
|
||||||
"See the 'max_extrude_only_distance' config"
|
"See the 'max_extrude_only_distance' config"
|
||||||
" option for details" % (move.axes_d[3], self.max_e_dist))
|
" option for details" % (move.axes_d[3], self.max_e_dist))
|
||||||
|
@ -127,7 +127,7 @@ class PrinterExtruder:
|
||||||
area = axis_r * self.filament_area
|
area = axis_r * self.filament_area
|
||||||
logging.debug("Overextrude: %s vs %s (area=%.3f dist=%.3f)",
|
logging.debug("Overextrude: %s vs %s (area=%.3f dist=%.3f)",
|
||||||
axis_r, self.max_extrude_ratio, area, move.move_d)
|
axis_r, self.max_extrude_ratio, area, move.move_d)
|
||||||
raise homing.EndstopError(
|
raise self.printer.command_error(
|
||||||
"Move exceeds maximum extrusion (%.3fmm^2 vs %.3fmm^2)\n"
|
"Move exceeds maximum extrusion (%.3fmm^2 vs %.3fmm^2)\n"
|
||||||
"See the 'max_extrude_cross_section' config option for details"
|
"See the 'max_extrude_cross_section' config option for details"
|
||||||
% (area, self.max_extrude_ratio * self.filament_area))
|
% (area, self.max_extrude_ratio * self.filament_area))
|
||||||
|
@ -214,17 +214,18 @@ class PrinterExtruder:
|
||||||
|
|
||||||
# Dummy extruder class used when a printer has no extruder at all
|
# Dummy extruder class used when a printer has no extruder at all
|
||||||
class DummyExtruder:
|
class DummyExtruder:
|
||||||
|
def __init__(self, printer):
|
||||||
|
self.printer = printer
|
||||||
def update_move_time(self, flush_time):
|
def update_move_time(self, flush_time):
|
||||||
pass
|
pass
|
||||||
def check_move(self, move):
|
def check_move(self, move):
|
||||||
raise homing.EndstopMoveError(
|
raise move.move_error("Extrude when no extruder present")
|
||||||
move.end_pos, "Extrude when no extruder present")
|
|
||||||
def calc_junction(self, prev_move, move):
|
def calc_junction(self, prev_move, move):
|
||||||
return move.max_cruise_v2
|
return move.max_cruise_v2
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
return ""
|
return ""
|
||||||
def get_heater(self):
|
def get_heater(self):
|
||||||
raise homing.CommandError("Extruder not configured")
|
raise self.printer.command_error("Extruder not configured")
|
||||||
|
|
||||||
def add_printer_objects(config):
|
def add_printer_objects(config):
|
||||||
printer = config.get_printer()
|
printer = config.get_printer()
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import logging, math
|
import logging, math
|
||||||
import stepper, homing
|
import stepper
|
||||||
|
|
||||||
class PolarKinematics:
|
class PolarKinematics:
|
||||||
def __init__(self, toolhead, config):
|
def __init__(self, toolhead, config):
|
||||||
|
@ -94,18 +94,17 @@ class PolarKinematics:
|
||||||
xy2 = end_pos[0]**2 + end_pos[1]**2
|
xy2 = end_pos[0]**2 + end_pos[1]**2
|
||||||
if xy2 > self.limit_xy2:
|
if xy2 > self.limit_xy2:
|
||||||
if self.limit_xy2 < 0.:
|
if self.limit_xy2 < 0.:
|
||||||
raise homing.EndstopMoveError(end_pos, "Must home axis first")
|
raise move.move_error("Must home axis first")
|
||||||
raise homing.EndstopMoveError(end_pos)
|
raise move.move_error()
|
||||||
if move.axes_d[2]:
|
if move.axes_d[2]:
|
||||||
if end_pos[2] < self.limit_z[0] or end_pos[2] > self.limit_z[1]:
|
if end_pos[2] < self.limit_z[0] or end_pos[2] > self.limit_z[1]:
|
||||||
if self.limit_z[0] > self.limit_z[1]:
|
if self.limit_z[0] > self.limit_z[1]:
|
||||||
raise homing.EndstopMoveError(
|
raise move.move_error("Must home axis first")
|
||||||
end_pos, "Must home axis first")
|
raise move.move_error()
|
||||||
raise homing.EndstopMoveError(end_pos)
|
|
||||||
# Move with Z - update velocity and accel for slower Z axis
|
# Move with Z - update velocity and accel for slower Z axis
|
||||||
z_ratio = move.move_d / abs(move.axes_d[2])
|
z_ratio = move.move_d / abs(move.axes_d[2])
|
||||||
move.limit_speed(
|
move.limit_speed(self.max_z_velocity * z_ratio,
|
||||||
self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio)
|
self.max_z_accel * z_ratio)
|
||||||
def get_status(self, eventtime):
|
def get_status(self, eventtime):
|
||||||
xy_home = "xy" if self.limit_xy2 >= 0. else ""
|
xy_home = "xy" if self.limit_xy2 >= 0. else ""
|
||||||
z_home = "z" if self.limit_z[0] <= self.limit_z[1] else ""
|
z_home = "z" if self.limit_z[0] <= self.limit_z[1] else ""
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import math, logging
|
import math, logging
|
||||||
import stepper, homing, mathutil, chelper
|
import stepper, mathutil, chelper
|
||||||
|
|
||||||
class RotaryDeltaKinematics:
|
class RotaryDeltaKinematics:
|
||||||
def __init__(self, toolhead, config):
|
def __init__(self, toolhead, config):
|
||||||
|
@ -106,7 +106,7 @@ class RotaryDeltaKinematics:
|
||||||
# Normal XY move
|
# Normal XY move
|
||||||
return
|
return
|
||||||
if self.need_home:
|
if self.need_home:
|
||||||
raise homing.EndstopMoveError(end_pos, "Must home first")
|
raise move.move_error("Must home first")
|
||||||
end_z = end_pos[2]
|
end_z = end_pos[2]
|
||||||
limit_xy2 = self.max_xy2
|
limit_xy2 = self.max_xy2
|
||||||
if end_z > self.limit_z:
|
if end_z > self.limit_z:
|
||||||
|
@ -115,7 +115,7 @@ class RotaryDeltaKinematics:
|
||||||
# Move out of range - verify not a homing move
|
# Move out of range - verify not a homing move
|
||||||
if (end_pos[:2] != self.home_position[:2]
|
if (end_pos[:2] != self.home_position[:2]
|
||||||
or end_z < self.min_z or end_z > self.home_position[2]):
|
or end_z < self.min_z or end_z > self.home_position[2]):
|
||||||
raise homing.EndstopMoveError(end_pos)
|
raise move.move_error()
|
||||||
limit_xy2 = -1.
|
limit_xy2 = -1.
|
||||||
if move.axes_d[2]:
|
if move.axes_d[2]:
|
||||||
move.limit_speed(self.max_z_velocity, move.accel)
|
move.limit_speed(self.max_z_velocity, move.accel)
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
#!/usr/bin/env python2
|
#!/usr/bin/env python2
|
||||||
# Main code for host side printer firmware
|
# Main code for host side printer firmware
|
||||||
#
|
#
|
||||||
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2016-2020 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import sys, os, optparse, logging, time, threading, collections, importlib
|
import sys, os, gc, optparse, logging, time, threading, collections, importlib
|
||||||
import util, reactor, queuelogger, msgproto, homing
|
import util, reactor, queuelogger, msgproto, homing
|
||||||
import gcode, configfile, pins, mcu, toolhead, webhooks
|
import gcode, configfile, pins, mcu, toolhead, webhooks
|
||||||
|
|
||||||
|
@ -48,10 +48,10 @@ Printer is shutdown
|
||||||
class Printer:
|
class Printer:
|
||||||
config_error = configfile.error
|
config_error = configfile.error
|
||||||
command_error = homing.CommandError
|
command_error = homing.CommandError
|
||||||
def __init__(self, bglogger, start_args):
|
def __init__(self, main_reactor, bglogger, start_args):
|
||||||
self.bglogger = bglogger
|
self.bglogger = bglogger
|
||||||
self.start_args = start_args
|
self.start_args = start_args
|
||||||
self.reactor = reactor.Reactor()
|
self.reactor = main_reactor
|
||||||
self.reactor.register_callback(self._connect)
|
self.reactor.register_callback(self._connect)
|
||||||
self.state_message = message_startup
|
self.state_message = message_startup
|
||||||
self.in_shutdown_state = False
|
self.in_shutdown_state = False
|
||||||
|
@ -165,8 +165,8 @@ class Printer:
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.exception("Unhandled exception during connect")
|
logging.exception("Unhandled exception during connect")
|
||||||
self._set_state("Internal error during connect: %s\n%s" % (
|
self._set_state("Internal error during connect: %s\n%s"
|
||||||
str(e), message_restart,))
|
% (str(e), message_restart,))
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
self._set_state(message_ready)
|
self._set_state(message_ready)
|
||||||
|
@ -176,8 +176,8 @@ class Printer:
|
||||||
cb()
|
cb()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.exception("Unhandled exception during ready callback")
|
logging.exception("Unhandled exception during ready callback")
|
||||||
self.invoke_shutdown("Internal error during ready callback: %s" % (
|
self.invoke_shutdown("Internal error during ready callback: %s"
|
||||||
str(e),))
|
% (str(e),))
|
||||||
def run(self):
|
def run(self):
|
||||||
systime = time.time()
|
systime = time.time()
|
||||||
monotime = self.reactor.monotonic()
|
monotime = self.reactor.monotonic()
|
||||||
|
@ -187,8 +187,17 @@ class Printer:
|
||||||
try:
|
try:
|
||||||
self.reactor.run()
|
self.reactor.run()
|
||||||
except:
|
except:
|
||||||
logging.exception("Unhandled exception during run")
|
msg = "Unhandled exception during run"
|
||||||
return "error_exit"
|
logging.exception(msg)
|
||||||
|
# Exception from a reactor callback - try to shutdown
|
||||||
|
try:
|
||||||
|
self.reactor.register_callback((lambda e:
|
||||||
|
self.invoke_shutdown(msg)))
|
||||||
|
self.reactor.run()
|
||||||
|
except:
|
||||||
|
logging.exception("Repeat unhandled exception during run")
|
||||||
|
# Another exception - try to exit
|
||||||
|
self.run_result = "error_exit"
|
||||||
# Check restart flags
|
# Check restart flags
|
||||||
run_result = self.run_result
|
run_result = self.run_result
|
||||||
try:
|
try:
|
||||||
|
@ -215,6 +224,8 @@ class Printer:
|
||||||
cb()
|
cb()
|
||||||
except:
|
except:
|
||||||
logging.exception("Exception during shutdown handler")
|
logging.exception("Exception during shutdown handler")
|
||||||
|
logging.info("Reactor garbage collection: %s",
|
||||||
|
self.reactor.get_gc_stats())
|
||||||
def invoke_async_shutdown(self, msg):
|
def invoke_async_shutdown(self, msg):
|
||||||
self.reactor.register_async_callback(
|
self.reactor.register_async_callback(
|
||||||
(lambda e: self.invoke_shutdown(msg)))
|
(lambda e: self.invoke_shutdown(msg)))
|
||||||
|
@ -297,17 +308,22 @@ def main():
|
||||||
elif not options.debugoutput:
|
elif not options.debugoutput:
|
||||||
logging.warning("No log file specified!"
|
logging.warning("No log file specified!"
|
||||||
" Severe timing issues may result!")
|
" Severe timing issues may result!")
|
||||||
|
gc.disable()
|
||||||
|
|
||||||
# Start Printer() class
|
# Start Printer() class
|
||||||
while 1:
|
while 1:
|
||||||
if bglogger is not None:
|
if bglogger is not None:
|
||||||
bglogger.clear_rollover_info()
|
bglogger.clear_rollover_info()
|
||||||
bglogger.set_rollover_info('versions', versions)
|
bglogger.set_rollover_info('versions', versions)
|
||||||
printer = Printer(bglogger, start_args)
|
gc.collect()
|
||||||
|
main_reactor = reactor.Reactor(gc_checking=True)
|
||||||
|
printer = Printer(main_reactor, bglogger, start_args)
|
||||||
res = printer.run()
|
res = printer.run()
|
||||||
if res in ['exit', 'error_exit']:
|
if res in ['exit', 'error_exit']:
|
||||||
break
|
break
|
||||||
time.sleep(1.)
|
time.sleep(1.)
|
||||||
|
main_reactor.finalize()
|
||||||
|
main_reactor = printer = None
|
||||||
logging.info("Restarting printer")
|
logging.info("Restarting printer")
|
||||||
start_args['start_reason'] = res
|
start_args['start_reason'] = res
|
||||||
|
|
||||||
|
|
|
@ -213,10 +213,13 @@ class MCU_pwm:
|
||||||
self._start_value * self._pwm_max,
|
self._start_value * self._pwm_max,
|
||||||
self._shutdown_value * self._pwm_max,
|
self._shutdown_value * self._pwm_max,
|
||||||
self._mcu.seconds_to_clock(self._max_duration)))
|
self._mcu.seconds_to_clock(self._max_duration)))
|
||||||
self._mcu.add_config_cmd(
|
curtime = self._mcu.get_printer().get_reactor().monotonic()
|
||||||
"set_pwm_out pin=%s cycle_ticks=%d value=%d"
|
printtime = self._mcu.estimated_print_time(curtime)
|
||||||
% (self._pin, cycle_ticks, self._start_value * self._pwm_max),
|
self._last_clock = self._mcu.print_time_to_clock(printtime + 0.100)
|
||||||
on_restart=True)
|
svalue = int(self._start_value * self._pwm_max + 0.5)
|
||||||
|
self._mcu.add_config_cmd("schedule_pwm_out oid=%d clock=%d value=%d"
|
||||||
|
% (self._oid, self._last_clock, svalue),
|
||||||
|
on_restart=True)
|
||||||
self._set_cmd = self._mcu.lookup_command(
|
self._set_cmd = self._mcu.lookup_command(
|
||||||
"schedule_pwm_out oid=%c clock=%u value=%hu", cq=cmd_queue)
|
"schedule_pwm_out oid=%c clock=%u value=%hu", cq=cmd_queue)
|
||||||
return
|
return
|
||||||
|
@ -427,8 +430,8 @@ class MCU:
|
||||||
self._mcu_freq = 0.
|
self._mcu_freq = 0.
|
||||||
# Move command queuing
|
# Move command queuing
|
||||||
ffi_main, self._ffi_lib = chelper.get_ffi()
|
ffi_main, self._ffi_lib = chelper.get_ffi()
|
||||||
self._max_stepper_error = config.getfloat(
|
self._max_stepper_error = config.getfloat('max_stepper_error', 0.000025,
|
||||||
'max_stepper_error', 0.000025, minval=0.)
|
minval=0.)
|
||||||
self._stepqueues = []
|
self._stepqueues = []
|
||||||
self._steppersync = None
|
self._steppersync = None
|
||||||
# Stats
|
# Stats
|
||||||
|
@ -563,17 +566,19 @@ class MCU:
|
||||||
else:
|
else:
|
||||||
start_reason = self._printer.get_start_args().get("start_reason")
|
start_reason = self._printer.get_start_args().get("start_reason")
|
||||||
if start_reason == 'firmware_restart':
|
if start_reason == 'firmware_restart':
|
||||||
raise error("Failed automated reset of MCU '%s'" % (
|
raise error("Failed automated reset of MCU '%s'"
|
||||||
self._name,))
|
% (self._name,))
|
||||||
# Already configured - send init commands
|
# Already configured - send init commands
|
||||||
self._send_config(config_params['crc'])
|
self._send_config(config_params['crc'])
|
||||||
# Setup steppersync with the move_count returned by get_config
|
# Setup steppersync with the move_count returned by get_config
|
||||||
move_count = config_params['move_count']
|
move_count = config_params['move_count']
|
||||||
self._steppersync = self._ffi_lib.steppersync_alloc(
|
ffi_main, ffi_lib = chelper.get_ffi()
|
||||||
self._serial.serialqueue, self._stepqueues, len(self._stepqueues),
|
self._steppersync = ffi_main.gc(
|
||||||
move_count)
|
ffi_lib.steppersync_alloc(self._serial.serialqueue,
|
||||||
self._ffi_lib.steppersync_set_time(
|
self._stepqueues, len(self._stepqueues),
|
||||||
self._steppersync, 0., self._mcu_freq)
|
move_count),
|
||||||
|
ffi_lib.steppersync_free)
|
||||||
|
ffi_lib.steppersync_set_time(self._steppersync, 0., self._mcu_freq)
|
||||||
# Log config information
|
# Log config information
|
||||||
move_msg = "Configured MCU '%s' (%d moves)" % (self._name, move_count)
|
move_msg = "Configured MCU '%s' (%d moves)" % (self._name, move_count)
|
||||||
logging.info(move_msg)
|
logging.info(move_msg)
|
||||||
|
@ -679,9 +684,7 @@ class MCU:
|
||||||
# Restarts
|
# Restarts
|
||||||
def _disconnect(self):
|
def _disconnect(self):
|
||||||
self._serial.disconnect()
|
self._serial.disconnect()
|
||||||
if self._steppersync is not None:
|
self._steppersync = None
|
||||||
self._ffi_lib.steppersync_free(self._steppersync)
|
|
||||||
self._steppersync = None
|
|
||||||
def _shutdown(self, force=False):
|
def _shutdown(self, force=False):
|
||||||
if (self._emergency_stop_cmd is None
|
if (self._emergency_stop_cmd is None
|
||||||
or (self._is_shutdown and not force)):
|
or (self._is_shutdown and not force)):
|
||||||
|
@ -742,8 +745,8 @@ class MCU:
|
||||||
return
|
return
|
||||||
ret = self._ffi_lib.steppersync_flush(self._steppersync, clock)
|
ret = self._ffi_lib.steppersync_flush(self._steppersync, clock)
|
||||||
if ret:
|
if ret:
|
||||||
raise error("Internal error in MCU '%s' stepcompress" % (
|
raise error("Internal error in MCU '%s' stepcompress"
|
||||||
self._name,))
|
% (self._name,))
|
||||||
def check_active(self, print_time, eventtime):
|
def check_active(self, print_time, eventtime):
|
||||||
if self._steppersync is None:
|
if self._steppersync is None:
|
||||||
return
|
return
|
||||||
|
@ -763,8 +766,6 @@ class MCU:
|
||||||
self._mcu_tick_stddev)
|
self._mcu_tick_stddev)
|
||||||
return False, ' '.join([msg, self._serial.stats(eventtime),
|
return False, ' '.join([msg, self._serial.stats(eventtime),
|
||||||
self._clocksync.stats(eventtime)])
|
self._clocksync.stats(eventtime)])
|
||||||
def __del__(self):
|
|
||||||
self._disconnect()
|
|
||||||
|
|
||||||
Common_MCU_errors = {
|
Common_MCU_errors = {
|
||||||
("Timer too close", "No next step", "Missed scheduling of next "): """
|
("Timer too close", "No next step", "Missed scheduling of next "): """
|
||||||
|
|
|
@ -39,6 +39,9 @@ class QueueListener(logging.handlers.TimedRotatingFileHandler):
|
||||||
self.bg_queue.put_nowait(None)
|
self.bg_queue.put_nowait(None)
|
||||||
self.bg_thread.join()
|
self.bg_thread.join()
|
||||||
def set_rollover_info(self, name, info):
|
def set_rollover_info(self, name, info):
|
||||||
|
if info is None:
|
||||||
|
self.rollover_info.pop(name, None)
|
||||||
|
return
|
||||||
self.rollover_info[name] = info
|
self.rollover_info[name] = info
|
||||||
def clear_rollover_info(self):
|
def clear_rollover_info(self):
|
||||||
self.rollover_info.clear()
|
self.rollover_info.clear()
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
# File descriptor and timer event helper
|
# File descriptor and timer event helper
|
||||||
#
|
#
|
||||||
# Copyright (C) 2016-2019 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2016-2020 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import os, select, math, time, Queue as queue
|
import os, gc, select, math, time, logging, Queue as queue
|
||||||
import greenlet
|
import greenlet
|
||||||
import chelper, util
|
import chelper, util
|
||||||
|
|
||||||
|
@ -93,10 +93,13 @@ class ReactorMutex:
|
||||||
class SelectReactor:
|
class SelectReactor:
|
||||||
NOW = _NOW
|
NOW = _NOW
|
||||||
NEVER = _NEVER
|
NEVER = _NEVER
|
||||||
def __init__(self):
|
def __init__(self, gc_checking=False):
|
||||||
# Main code
|
# Main code
|
||||||
self._process = False
|
self._process = False
|
||||||
self.monotonic = chelper.get_ffi()[1].get_monotonic
|
self.monotonic = chelper.get_ffi()[1].get_monotonic
|
||||||
|
# Python garbage collection
|
||||||
|
self._check_gc = gc_checking
|
||||||
|
self._last_gc_times = [0., 0., 0.]
|
||||||
# Timers
|
# Timers
|
||||||
self._timers = []
|
self._timers = []
|
||||||
self._next_timer = self.NEVER
|
self._next_timer = self.NEVER
|
||||||
|
@ -108,6 +111,9 @@ class SelectReactor:
|
||||||
# Greenlets
|
# Greenlets
|
||||||
self._g_dispatch = None
|
self._g_dispatch = None
|
||||||
self._greenlets = []
|
self._greenlets = []
|
||||||
|
self._all_greenlets = []
|
||||||
|
def get_gc_stats(self):
|
||||||
|
return tuple(self._last_gc_times)
|
||||||
# Timers
|
# Timers
|
||||||
def update_timer(self, timer_handler, waketime):
|
def update_timer(self, timer_handler, waketime):
|
||||||
timer_handler.waketime = waketime
|
timer_handler.waketime = waketime
|
||||||
|
@ -124,8 +130,22 @@ class SelectReactor:
|
||||||
timers = list(self._timers)
|
timers = list(self._timers)
|
||||||
timers.pop(timers.index(timer_handler))
|
timers.pop(timers.index(timer_handler))
|
||||||
self._timers = timers
|
self._timers = timers
|
||||||
def _check_timers(self, eventtime):
|
def _check_timers(self, eventtime, busy):
|
||||||
if eventtime < self._next_timer:
|
if eventtime < self._next_timer:
|
||||||
|
if busy:
|
||||||
|
return 0.
|
||||||
|
if self._check_gc:
|
||||||
|
gi = gc.get_count()
|
||||||
|
if gi[0] >= 700:
|
||||||
|
# Reactor looks idle and gc is due - run it
|
||||||
|
gc_level = 0
|
||||||
|
if gi[1] >= 10:
|
||||||
|
gc_level = 1
|
||||||
|
if gi[2] >= 10:
|
||||||
|
gc_level = 2
|
||||||
|
self._last_gc_times[gc_level] = eventtime
|
||||||
|
gc.collect(gc_level)
|
||||||
|
return 0.
|
||||||
return min(1., max(.001, self._next_timer - eventtime))
|
return min(1., max(.001, self._next_timer - eventtime))
|
||||||
self._next_timer = self.NEVER
|
self._next_timer = self.NEVER
|
||||||
g_dispatch = self._g_dispatch
|
g_dispatch = self._g_dispatch
|
||||||
|
@ -139,9 +159,7 @@ class SelectReactor:
|
||||||
self._end_greenlet(g_dispatch)
|
self._end_greenlet(g_dispatch)
|
||||||
return 0.
|
return 0.
|
||||||
self._next_timer = min(self._next_timer, waketime)
|
self._next_timer = min(self._next_timer, waketime)
|
||||||
if eventtime >= self._next_timer:
|
return 0.
|
||||||
return 0.
|
|
||||||
return min(1., max(.001, self._next_timer - self.monotonic()))
|
|
||||||
# Callbacks and Completions
|
# Callbacks and Completions
|
||||||
def completion(self):
|
def completion(self):
|
||||||
return ReactorCompletion(self)
|
return ReactorCompletion(self)
|
||||||
|
@ -178,11 +196,6 @@ class SelectReactor:
|
||||||
util.set_nonblock(self._pipe_fds[0])
|
util.set_nonblock(self._pipe_fds[0])
|
||||||
util.set_nonblock(self._pipe_fds[1])
|
util.set_nonblock(self._pipe_fds[1])
|
||||||
self.register_fd(self._pipe_fds[0], self._got_pipe_signal)
|
self.register_fd(self._pipe_fds[0], self._got_pipe_signal)
|
||||||
def __del__(self):
|
|
||||||
if self._pipe_fds is not None:
|
|
||||||
os.close(self._pipe_fds[0])
|
|
||||||
os.close(self._pipe_fds[1])
|
|
||||||
self._pipe_fds = None
|
|
||||||
# Greenlets
|
# Greenlets
|
||||||
def _sys_pause(self, waketime):
|
def _sys_pause(self, waketime):
|
||||||
# Pause using system sleep for when reactor not running
|
# Pause using system sleep for when reactor not running
|
||||||
|
@ -202,6 +215,7 @@ class SelectReactor:
|
||||||
g_next = self._greenlets.pop()
|
g_next = self._greenlets.pop()
|
||||||
else:
|
else:
|
||||||
g_next = ReactorGreenlet(run=self._dispatch_loop)
|
g_next = ReactorGreenlet(run=self._dispatch_loop)
|
||||||
|
self._all_greenlets.append(g_next)
|
||||||
g_next.parent = g.parent
|
g_next.parent = g.parent
|
||||||
g.timer = self.register_timer(g.switch, waketime)
|
g.timer = self.register_timer(g.switch, waketime)
|
||||||
self._next_timer = self.NOW
|
self._next_timer = self.NOW
|
||||||
|
@ -231,12 +245,15 @@ class SelectReactor:
|
||||||
# Main loop
|
# Main loop
|
||||||
def _dispatch_loop(self):
|
def _dispatch_loop(self):
|
||||||
self._g_dispatch = g_dispatch = greenlet.getcurrent()
|
self._g_dispatch = g_dispatch = greenlet.getcurrent()
|
||||||
|
busy = True
|
||||||
eventtime = self.monotonic()
|
eventtime = self.monotonic()
|
||||||
while self._process:
|
while self._process:
|
||||||
timeout = self._check_timers(eventtime)
|
timeout = self._check_timers(eventtime, busy)
|
||||||
|
busy = False
|
||||||
res = select.select(self._fds, [], [], timeout)
|
res = select.select(self._fds, [], [], timeout)
|
||||||
eventtime = self.monotonic()
|
eventtime = self.monotonic()
|
||||||
for fd in res[0]:
|
for fd in res[0]:
|
||||||
|
busy = True
|
||||||
fd.callback(eventtime)
|
fd.callback(eventtime)
|
||||||
if g_dispatch is not self._g_dispatch:
|
if g_dispatch is not self._g_dispatch:
|
||||||
self._end_greenlet(g_dispatch)
|
self._end_greenlet(g_dispatch)
|
||||||
|
@ -248,13 +265,27 @@ class SelectReactor:
|
||||||
self._setup_async_callbacks()
|
self._setup_async_callbacks()
|
||||||
self._process = True
|
self._process = True
|
||||||
g_next = ReactorGreenlet(run=self._dispatch_loop)
|
g_next = ReactorGreenlet(run=self._dispatch_loop)
|
||||||
|
self._all_greenlets.append(g_next)
|
||||||
g_next.switch()
|
g_next.switch()
|
||||||
def end(self):
|
def end(self):
|
||||||
self._process = False
|
self._process = False
|
||||||
|
def finalize(self):
|
||||||
|
self._g_dispatch = None
|
||||||
|
self._greenlets = []
|
||||||
|
for g in self._all_greenlets:
|
||||||
|
try:
|
||||||
|
g.throw()
|
||||||
|
except:
|
||||||
|
logging.exception("reactor finalize greenlet terminate")
|
||||||
|
self._all_greenlets = []
|
||||||
|
if self._pipe_fds is not None:
|
||||||
|
os.close(self._pipe_fds[0])
|
||||||
|
os.close(self._pipe_fds[1])
|
||||||
|
self._pipe_fds = None
|
||||||
|
|
||||||
class PollReactor(SelectReactor):
|
class PollReactor(SelectReactor):
|
||||||
def __init__(self):
|
def __init__(self, gc_checking=False):
|
||||||
SelectReactor.__init__(self)
|
SelectReactor.__init__(self, gc_checking)
|
||||||
self._poll = select.poll()
|
self._poll = select.poll()
|
||||||
self._fds = {}
|
self._fds = {}
|
||||||
# File descriptors
|
# File descriptors
|
||||||
|
@ -273,12 +304,15 @@ class PollReactor(SelectReactor):
|
||||||
# Main loop
|
# Main loop
|
||||||
def _dispatch_loop(self):
|
def _dispatch_loop(self):
|
||||||
self._g_dispatch = g_dispatch = greenlet.getcurrent()
|
self._g_dispatch = g_dispatch = greenlet.getcurrent()
|
||||||
|
busy = True
|
||||||
eventtime = self.monotonic()
|
eventtime = self.monotonic()
|
||||||
while self._process:
|
while self._process:
|
||||||
timeout = self._check_timers(eventtime)
|
timeout = self._check_timers(eventtime, busy)
|
||||||
|
busy = False
|
||||||
res = self._poll.poll(int(math.ceil(timeout * 1000.)))
|
res = self._poll.poll(int(math.ceil(timeout * 1000.)))
|
||||||
eventtime = self.monotonic()
|
eventtime = self.monotonic()
|
||||||
for fd, event in res:
|
for fd, event in res:
|
||||||
|
busy = True
|
||||||
self._fds[fd](eventtime)
|
self._fds[fd](eventtime)
|
||||||
if g_dispatch is not self._g_dispatch:
|
if g_dispatch is not self._g_dispatch:
|
||||||
self._end_greenlet(g_dispatch)
|
self._end_greenlet(g_dispatch)
|
||||||
|
@ -287,8 +321,8 @@ class PollReactor(SelectReactor):
|
||||||
self._g_dispatch = None
|
self._g_dispatch = None
|
||||||
|
|
||||||
class EPollReactor(SelectReactor):
|
class EPollReactor(SelectReactor):
|
||||||
def __init__(self):
|
def __init__(self, gc_checking=False):
|
||||||
SelectReactor.__init__(self)
|
SelectReactor.__init__(self, gc_checking)
|
||||||
self._epoll = select.epoll()
|
self._epoll = select.epoll()
|
||||||
self._fds = {}
|
self._fds = {}
|
||||||
# File descriptors
|
# File descriptors
|
||||||
|
@ -307,12 +341,15 @@ class EPollReactor(SelectReactor):
|
||||||
# Main loop
|
# Main loop
|
||||||
def _dispatch_loop(self):
|
def _dispatch_loop(self):
|
||||||
self._g_dispatch = g_dispatch = greenlet.getcurrent()
|
self._g_dispatch = g_dispatch = greenlet.getcurrent()
|
||||||
|
busy = True
|
||||||
eventtime = self.monotonic()
|
eventtime = self.monotonic()
|
||||||
while self._process:
|
while self._process:
|
||||||
timeout = self._check_timers(eventtime)
|
timeout = self._check_timers(eventtime, busy)
|
||||||
|
busy = False
|
||||||
res = self._epoll.poll(timeout)
|
res = self._epoll.poll(timeout)
|
||||||
eventtime = self.monotonic()
|
eventtime = self.monotonic()
|
||||||
for fd, event in res:
|
for fd, event in res:
|
||||||
|
busy = True
|
||||||
self._fds[fd](eventtime)
|
self._fds[fd](eventtime)
|
||||||
if g_dispatch is not self._g_dispatch:
|
if g_dispatch is not self._g_dispatch:
|
||||||
self._end_greenlet(g_dispatch)
|
self._end_greenlet(g_dispatch)
|
||||||
|
|
|
@ -98,8 +98,9 @@ class SerialReader:
|
||||||
continue
|
continue
|
||||||
if self.baud:
|
if self.baud:
|
||||||
stk500v2_leave(self.ser, self.reactor)
|
stk500v2_leave(self.ser, self.reactor)
|
||||||
self.serialqueue = self.ffi_lib.serialqueue_alloc(
|
self.serialqueue = self.ffi_main.gc(
|
||||||
self.ser.fileno(), 0)
|
self.ffi_lib.serialqueue_alloc(self.ser.fileno(), 0),
|
||||||
|
self.ffi_lib.serialqueue_free)
|
||||||
self.background_thread = threading.Thread(target=self._bg_thread)
|
self.background_thread = threading.Thread(target=self._bg_thread)
|
||||||
self.background_thread.start()
|
self.background_thread.start()
|
||||||
# Obtain and load the data dictionary from the firmware
|
# Obtain and load the data dictionary from the firmware
|
||||||
|
@ -126,7 +127,9 @@ class SerialReader:
|
||||||
def connect_file(self, debugoutput, dictionary, pace=False):
|
def connect_file(self, debugoutput, dictionary, pace=False):
|
||||||
self.ser = debugoutput
|
self.ser = debugoutput
|
||||||
self.msgparser.process_identify(dictionary, decompress=False)
|
self.msgparser.process_identify(dictionary, decompress=False)
|
||||||
self.serialqueue = self.ffi_lib.serialqueue_alloc(self.ser.fileno(), 1)
|
self.serialqueue = self.ffi_main.gc(
|
||||||
|
self.ffi_lib.serialqueue_alloc(self.ser.fileno(), 1),
|
||||||
|
self.ffi_lib.serialqueue_free)
|
||||||
def set_clock_est(self, freq, last_time, last_clock):
|
def set_clock_est(self, freq, last_time, last_clock):
|
||||||
self.ffi_lib.serialqueue_set_clock_est(
|
self.ffi_lib.serialqueue_set_clock_est(
|
||||||
self.serialqueue, freq, last_time, last_clock)
|
self.serialqueue, freq, last_time, last_clock)
|
||||||
|
@ -135,7 +138,6 @@ class SerialReader:
|
||||||
self.ffi_lib.serialqueue_exit(self.serialqueue)
|
self.ffi_lib.serialqueue_exit(self.serialqueue)
|
||||||
if self.background_thread is not None:
|
if self.background_thread is not None:
|
||||||
self.background_thread.join()
|
self.background_thread.join()
|
||||||
self.ffi_lib.serialqueue_free(self.serialqueue)
|
|
||||||
self.background_thread = self.serialqueue = None
|
self.background_thread = self.serialqueue = None
|
||||||
if self.ser is not None:
|
if self.ser is not None:
|
||||||
self.ser.close()
|
self.ser.close()
|
||||||
|
@ -194,10 +196,10 @@ class SerialReader:
|
||||||
self.stats(self.reactor.monotonic()),))
|
self.stats(self.reactor.monotonic()),))
|
||||||
sdata = self.ffi_main.new('struct pull_queue_message[1024]')
|
sdata = self.ffi_main.new('struct pull_queue_message[1024]')
|
||||||
rdata = self.ffi_main.new('struct pull_queue_message[1024]')
|
rdata = self.ffi_main.new('struct pull_queue_message[1024]')
|
||||||
scount = self.ffi_lib.serialqueue_extract_old(
|
scount = self.ffi_lib.serialqueue_extract_old(self.serialqueue, 1,
|
||||||
self.serialqueue, 1, sdata, len(sdata))
|
sdata, len(sdata))
|
||||||
rcount = self.ffi_lib.serialqueue_extract_old(
|
rcount = self.ffi_lib.serialqueue_extract_old(self.serialqueue, 0,
|
||||||
self.serialqueue, 0, rdata, len(rdata))
|
rdata, len(rdata))
|
||||||
out.append("Dumping send queue %d messages" % (scount,))
|
out.append("Dumping send queue %d messages" % (scount,))
|
||||||
for i in range(scount):
|
for i in range(scount):
|
||||||
msg = sdata[i]
|
msg = sdata[i]
|
||||||
|
@ -222,8 +224,6 @@ class SerialReader:
|
||||||
logging.info("%s: %s", params['#name'], params['#msg'])
|
logging.info("%s: %s", params['#name'], params['#msg'])
|
||||||
def handle_default(self, params):
|
def handle_default(self, params):
|
||||||
logging.warn("got %s", params)
|
logging.warn("got %s", params)
|
||||||
def __del__(self):
|
|
||||||
self.disconnect()
|
|
||||||
|
|
||||||
# Class to send a query command and return the received response
|
# Class to send a query command and return the received response
|
||||||
class SerialRetryCommand:
|
class SerialRetryCommand:
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import math, logging, collections
|
import math, logging, collections
|
||||||
import homing, chelper
|
import chelper
|
||||||
|
|
||||||
class error(Exception):
|
class error(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -54,6 +54,10 @@ class Move:
|
||||||
self.accel = min(self.accel, accel)
|
self.accel = min(self.accel, accel)
|
||||||
self.delta_v2 = 2.0 * self.move_d * self.accel
|
self.delta_v2 = 2.0 * self.move_d * self.accel
|
||||||
self.smooth_delta_v2 = min(self.smooth_delta_v2, self.delta_v2)
|
self.smooth_delta_v2 = min(self.smooth_delta_v2, self.delta_v2)
|
||||||
|
def move_error(self, msg="Move out of range"):
|
||||||
|
ep = self.end_pos
|
||||||
|
m = "%s: %.3f %.3f %.3f [%.3f]" % (msg, ep[0], ep[1], ep[2], ep[3])
|
||||||
|
return self.toolhead.printer.command_error(m)
|
||||||
def calc_junction(self, prev_move):
|
def calc_junction(self, prev_move):
|
||||||
if not self.is_kinematic_move or not prev_move.is_kinematic_move:
|
if not self.is_kinematic_move or not prev_move.is_kinematic_move:
|
||||||
return
|
return
|
||||||
|
@ -246,7 +250,7 @@ class ToolHead:
|
||||||
self.trapq_free_moves = ffi_lib.trapq_free_moves
|
self.trapq_free_moves = ffi_lib.trapq_free_moves
|
||||||
self.step_generators = []
|
self.step_generators = []
|
||||||
# Create kinematics class
|
# Create kinematics class
|
||||||
self.extruder = kinematics.extruder.DummyExtruder()
|
self.extruder = kinematics.extruder.DummyExtruder(self.printer)
|
||||||
kin_name = config.get('kinematics')
|
kin_name = config.get('kinematics')
|
||||||
try:
|
try:
|
||||||
mod = importlib.import_module('kinematics.' + kin_name)
|
mod = importlib.import_module('kinematics.' + kin_name)
|
||||||
|
@ -466,7 +470,7 @@ class ToolHead:
|
||||||
# Submit move
|
# Submit move
|
||||||
try:
|
try:
|
||||||
self.move(newpos, speed)
|
self.move(newpos, speed)
|
||||||
except homing.CommandError as e:
|
except self.printer.command_error as e:
|
||||||
self.flush_step_generation()
|
self.flush_step_generation()
|
||||||
raise
|
raise
|
||||||
# Transmit move in "drip" mode
|
# Transmit move in "drip" mode
|
||||||
|
|
|
@ -61,7 +61,8 @@ class WebRequest:
|
||||||
value = self.params.get(item, default)
|
value = self.params.get(item, default)
|
||||||
if value is Sentinel:
|
if value is Sentinel:
|
||||||
raise WebRequestError("Missing Argument [%s]" % (item,))
|
raise WebRequestError("Missing Argument [%s]" % (item,))
|
||||||
if types is not None and type(value) not in types:
|
if (types is not None and type(value) not in types
|
||||||
|
and item in self.params):
|
||||||
raise WebRequestError("Invalid Argument Type [%s]" % (item,))
|
raise WebRequestError("Invalid Argument Type [%s]" % (item,))
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@ -168,19 +169,30 @@ class ClientConnection:
|
||||||
self.sock.fileno(), self.process_received)
|
self.sock.fileno(), self.process_received)
|
||||||
self.partial_data = self.send_buffer = ""
|
self.partial_data = self.send_buffer = ""
|
||||||
self.is_sending_data = False
|
self.is_sending_data = False
|
||||||
logging.info(
|
self.set_client_info("?", "New connection")
|
||||||
"webhooks: New connection established")
|
|
||||||
|
def set_client_info(self, client_info, state_msg=None):
|
||||||
|
if state_msg is None:
|
||||||
|
state_msg = "Client info %s" % (repr(client_info),)
|
||||||
|
logging.info("webhooks client %s: %s", self.uid, state_msg)
|
||||||
|
log_id = "webhooks %s" % (self.uid,)
|
||||||
|
if client_info is None:
|
||||||
|
self.printer.set_rollover_info(log_id, None, log=False)
|
||||||
|
return
|
||||||
|
rollover_msg = "webhooks client %s: %s" % (self.uid, repr(client_info))
|
||||||
|
self.printer.set_rollover_info(log_id, rollover_msg, log=False)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
if self.fd_handle is not None:
|
if self.fd_handle is None:
|
||||||
logging.info("webhooks: Client connection closed")
|
return
|
||||||
self.reactor.unregister_fd(self.fd_handle)
|
self.set_client_info(None, "Disconnected")
|
||||||
self.fd_handle = None
|
self.reactor.unregister_fd(self.fd_handle)
|
||||||
try:
|
self.fd_handle = None
|
||||||
self.sock.close()
|
try:
|
||||||
except socket.error:
|
self.sock.close()
|
||||||
pass
|
except socket.error:
|
||||||
self.server.pop_client(self.uid)
|
pass
|
||||||
|
self.server.pop_client(self.uid)
|
||||||
|
|
||||||
def is_closed(self):
|
def is_closed(self):
|
||||||
return self.fd_handle is None
|
return self.fd_handle is None
|
||||||
|
@ -216,7 +228,7 @@ class ClientConnection:
|
||||||
try:
|
try:
|
||||||
func = self.webhooks.get_callback(web_request.get_method())
|
func = self.webhooks.get_callback(web_request.get_method())
|
||||||
func(web_request)
|
func(web_request)
|
||||||
except homing.CommandError as e:
|
except self.printer.command_error as e:
|
||||||
web_request.set_error(WebRequestError(e.message))
|
web_request.set_error(WebRequestError(e.message))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
msg = ("Internal Error on WebRequest: %s"
|
msg = ("Internal Error on WebRequest: %s"
|
||||||
|
@ -276,9 +288,12 @@ class WebHooks:
|
||||||
web_request.send({'endpoints': self._endpoints.keys()})
|
web_request.send({'endpoints': self._endpoints.keys()})
|
||||||
|
|
||||||
def _handle_info_request(self, web_request):
|
def _handle_info_request(self, web_request):
|
||||||
|
client_info = web_request.get_dict('client_info', None)
|
||||||
|
if client_info is not None:
|
||||||
|
web_request.get_client_connection().set_client_info(client_info)
|
||||||
state_message, state = self.printer.get_state_message()
|
state_message, state = self.printer.get_state_message()
|
||||||
klipper_path = os.path.normpath(os.path.join(
|
src_path = os.path.dirname(__file__)
|
||||||
os.path.dirname(__file__), ".."))
|
klipper_path = os.path.normpath(os.path.join(src_path, ".."))
|
||||||
response = {'state': state, 'state_message': state_message,
|
response = {'state': state, 'state_message': state_message,
|
||||||
'hostname': socket.gethostname(),
|
'hostname': socket.gethostname(),
|
||||||
'klipper_path': klipper_path, 'python_path': sys.executable}
|
'klipper_path': klipper_path, 'python_path': sys.executable}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/env python2
|
#!/usr/bin/env python3
|
||||||
# Script to interact with simulavr by simulating a serial port.
|
# Script to interact with simulavr by simulating a serial port.
|
||||||
#
|
#
|
||||||
# Copyright (C) 2015-2018 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2015-2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
@ -17,7 +17,7 @@ class SerialRxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
|
||||||
pysimulavr.PySimulationMember.__init__(self)
|
pysimulavr.PySimulationMember.__init__(self)
|
||||||
self.terminal = terminal
|
self.terminal = terminal
|
||||||
self.sc = pysimulavr.SystemClock.Instance()
|
self.sc = pysimulavr.SystemClock.Instance()
|
||||||
self.delay = SIMULAVR_FREQ / baud
|
self.delay = SIMULAVR_FREQ // baud
|
||||||
self.current = 0
|
self.current = 0
|
||||||
self.pos = -1
|
self.pos = -1
|
||||||
def SetInState(self, pin):
|
def SetInState(self, pin):
|
||||||
|
@ -33,7 +33,7 @@ class SerialRxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
|
||||||
if self.pos == 1:
|
if self.pos == 1:
|
||||||
return int(self.delay * 1.5)
|
return int(self.delay * 1.5)
|
||||||
if self.pos >= SERIALBITS:
|
if self.pos >= SERIALBITS:
|
||||||
data = chr((self.current >> 1) & 0xff)
|
data = bytearray([(self.current >> 1) & 0xff])
|
||||||
self.terminal.write(data)
|
self.terminal.write(data)
|
||||||
self.pos = -1
|
self.pos = -1
|
||||||
self.current = 0
|
self.current = 0
|
||||||
|
@ -48,10 +48,10 @@ class SerialTxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
|
||||||
self.terminal = terminal
|
self.terminal = terminal
|
||||||
self.SetPin('H')
|
self.SetPin('H')
|
||||||
self.sc = pysimulavr.SystemClock.Instance()
|
self.sc = pysimulavr.SystemClock.Instance()
|
||||||
self.delay = SIMULAVR_FREQ / baud
|
self.delay = SIMULAVR_FREQ // baud
|
||||||
self.current = 0
|
self.current = 0
|
||||||
self.pos = 0
|
self.pos = 0
|
||||||
self.queue = ""
|
self.queue = bytearray()
|
||||||
self.sc.Add(self)
|
self.sc.Add(self)
|
||||||
def DoStep(self, trueHwStep):
|
def DoStep(self, trueHwStep):
|
||||||
if not self.pos:
|
if not self.pos:
|
||||||
|
@ -59,9 +59,8 @@ class SerialTxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
|
||||||
data = self.terminal.read()
|
data = self.terminal.read()
|
||||||
if not data:
|
if not data:
|
||||||
return self.delay * 100
|
return self.delay * 100
|
||||||
self.queue += data
|
self.queue.extend(data)
|
||||||
self.current = (ord(self.queue[0]) << 1) | 0x200
|
self.current = (self.queue.pop(0) << 1) | 0x200
|
||||||
self.queue = self.queue[1:]
|
|
||||||
newstate = 'L'
|
newstate = 'L'
|
||||||
if self.current & (1 << self.pos):
|
if self.current & (1 << self.pos):
|
||||||
newstate = 'H'
|
newstate = 'H'
|
||||||
|
@ -109,7 +108,7 @@ class Pacing(pysimulavr.PySimulationMember):
|
||||||
self.next_check_clock = 0
|
self.next_check_clock = 0
|
||||||
self.rel_time = time.time()
|
self.rel_time = time.time()
|
||||||
self.best_offset = 0.
|
self.best_offset = 0.
|
||||||
self.delay = SIMULAVR_FREQ / 10000
|
self.delay = SIMULAVR_FREQ // 10000
|
||||||
self.sc.Add(self)
|
self.sc.Add(self)
|
||||||
def DoStep(self, trueHwStep):
|
def DoStep(self, trueHwStep):
|
||||||
curtime = time.time()
|
curtime = time.time()
|
||||||
|
@ -135,7 +134,7 @@ class TerminalIO:
|
||||||
def read(self):
|
def read(self):
|
||||||
try:
|
try:
|
||||||
return os.read(self.fd, 64)
|
return os.read(self.fd, 64)
|
||||||
except os.error, e:
|
except os.error as e:
|
||||||
if e.errno not in (errno.EAGAIN, errno.EWOULDBLOCK):
|
if e.errno not in (errno.EAGAIN, errno.EWOULDBLOCK):
|
||||||
pysimulavr.SystemClock.Instance().stop()
|
pysimulavr.SystemClock.Instance().stop()
|
||||||
return ""
|
return ""
|
||||||
|
@ -198,7 +197,7 @@ def main():
|
||||||
trace = Tracing(options.tracefile, options.trace)
|
trace = Tracing(options.tracefile, options.trace)
|
||||||
dev = pysimulavr.AvrFactory.instance().makeDevice(proc)
|
dev = pysimulavr.AvrFactory.instance().makeDevice(proc)
|
||||||
dev.Load(elffile)
|
dev.Load(elffile)
|
||||||
dev.SetClockFreq(SIMULAVR_FREQ / speed)
|
dev.SetClockFreq(SIMULAVR_FREQ // speed)
|
||||||
sc.Add(dev)
|
sc.Add(dev)
|
||||||
pysimulavr.cvar.sysConHandler.SetUseExit(False)
|
pysimulavr.cvar.sysConHandler.SetUseExit(False)
|
||||||
trace.load_options()
|
trace.load_options()
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
#!/usr/bin/env python2
|
||||||
|
# Generate adxl345 accelerometer graphs
|
||||||
|
#
|
||||||
|
# Copyright (C) 2020 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
import optparse
|
||||||
|
import matplotlib
|
||||||
|
|
||||||
|
def parse_log(logname):
|
||||||
|
f = open(logname, 'r')
|
||||||
|
out = []
|
||||||
|
for line in f:
|
||||||
|
if line.startswith('#'):
|
||||||
|
continue
|
||||||
|
parts = line.split(',')
|
||||||
|
if len(parts) != 4:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
fparts = [float(p) for p in parts]
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
out.append(fparts)
|
||||||
|
return out
|
||||||
|
|
||||||
|
def plot_accel(data, logname):
|
||||||
|
half_smooth_samples = 15
|
||||||
|
first_time = data[0][0]
|
||||||
|
times = [d[0] - first_time for d in data]
|
||||||
|
fig, axes = matplotlib.pyplot.subplots(nrows=3, sharex=True)
|
||||||
|
axes[0].set_title("Accelerometer data (%s)" % (logname,))
|
||||||
|
axis_names = ['x', 'y', 'z']
|
||||||
|
for i in range(len(axis_names)):
|
||||||
|
adata = [d[i+1] for d in data]
|
||||||
|
avg = sum(adata) / len(adata)
|
||||||
|
adata = [ad - avg for ad in adata]
|
||||||
|
ax = axes[i]
|
||||||
|
ax.plot(times, adata, alpha=0.8)
|
||||||
|
ax.grid(True)
|
||||||
|
ax.set_ylabel('%s accel (%+.3f)\n(mm/s^2)' % (axis_names[i], -avg))
|
||||||
|
axes[-1].set_xlabel('Time (%+.3f)\n(s)' % (-first_time,))
|
||||||
|
fig.tight_layout()
|
||||||
|
return fig
|
||||||
|
|
||||||
|
def setup_matplotlib(output_to_file):
|
||||||
|
global matplotlib
|
||||||
|
if output_to_file:
|
||||||
|
matplotlib.rcParams.update({'figure.autolayout': True})
|
||||||
|
matplotlib.use('Agg')
|
||||||
|
import matplotlib.pyplot, matplotlib.dates, matplotlib.font_manager
|
||||||
|
import matplotlib.ticker
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Parse command-line arguments
|
||||||
|
usage = "%prog [options] <log>"
|
||||||
|
opts = optparse.OptionParser(usage)
|
||||||
|
opts.add_option("-o", "--output", type="string", dest="output",
|
||||||
|
default=None, help="filename of output graph")
|
||||||
|
options, args = opts.parse_args()
|
||||||
|
if len(args) != 1:
|
||||||
|
opts.error("Incorrect number of arguments")
|
||||||
|
|
||||||
|
# Parse data
|
||||||
|
data = parse_log(args[0])
|
||||||
|
|
||||||
|
# Draw graph
|
||||||
|
setup_matplotlib(options.output is not None)
|
||||||
|
fig = plot_accel(data, args[0])
|
||||||
|
|
||||||
|
# Show graph
|
||||||
|
if options.output is None:
|
||||||
|
matplotlib.pyplot.show()
|
||||||
|
else:
|
||||||
|
fig.set_size_inches(8, 6)
|
||||||
|
fig.savefig(options.output)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -77,6 +77,27 @@ def plot_adc_resolution(config, sensors):
|
||||||
fig.tight_layout()
|
fig.tight_layout()
|
||||||
return fig
|
return fig
|
||||||
|
|
||||||
|
def plot_resistance(config, sensors):
|
||||||
|
# Temperature list
|
||||||
|
all_temps = [float(i) for i in range(1, 351)]
|
||||||
|
# Build plot
|
||||||
|
fig, ax = matplotlib.pyplot.subplots()
|
||||||
|
pullup = config.getfloat('pullup_resistor', 0.)
|
||||||
|
ax.set_title("Temperature Sensor (pullup=%.0f)" % (pullup,))
|
||||||
|
ax.set_ylabel('Resistance (Ohms)')
|
||||||
|
for sensor in sensors:
|
||||||
|
sc = config.do_create_sensor(sensor)
|
||||||
|
adcs = [sc.calc_adc(t) for t in all_temps]
|
||||||
|
rs = [pullup * adc / (1.0 - adc) for adc in adcs]
|
||||||
|
ax.plot(all_temps, rs, label=sensor, alpha=0.6)
|
||||||
|
fontP = matplotlib.font_manager.FontProperties()
|
||||||
|
fontP.set_size('x-small')
|
||||||
|
ax.legend(loc='best', prop=fontP)
|
||||||
|
ax.set_xlabel('Temperature (C)')
|
||||||
|
ax.grid(True)
|
||||||
|
fig.tight_layout()
|
||||||
|
return fig
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# Startup
|
# Startup
|
||||||
|
@ -111,6 +132,8 @@ def main():
|
||||||
default=5., help="pullup resistor")
|
default=5., help="pullup resistor")
|
||||||
opts.add_option("-s", "--sensors", type="string", dest="sensors",
|
opts.add_option("-s", "--sensors", type="string", dest="sensors",
|
||||||
default="", help="list of sensors (comma separated)")
|
default="", help="list of sensors (comma separated)")
|
||||||
|
opts.add_option("-r", "--resistance", action="store_true",
|
||||||
|
help="graph sensor resistance")
|
||||||
options, args = opts.parse_args()
|
options, args = opts.parse_args()
|
||||||
if len(args) != 0:
|
if len(args) != 0:
|
||||||
opts.error("Incorrect number of arguments")
|
opts.error("Incorrect number of arguments")
|
||||||
|
@ -129,7 +152,10 @@ def main():
|
||||||
|
|
||||||
# Draw graph
|
# Draw graph
|
||||||
setup_matplotlib(options.output is not None)
|
setup_matplotlib(options.output is not None)
|
||||||
fig = plot_adc_resolution(config, sensors)
|
if options.resistance:
|
||||||
|
fig = plot_resistance(config, sensors)
|
||||||
|
else:
|
||||||
|
fig = plot_adc_resolution(config, sensors)
|
||||||
|
|
||||||
# Show graph
|
# Show graph
|
||||||
if options.output is None:
|
if options.output is None:
|
||||||
|
|
|
@ -137,6 +137,48 @@ def plot_mcu(data, maxbw):
|
||||||
ax1.grid(True)
|
ax1.grid(True)
|
||||||
return fig
|
return fig
|
||||||
|
|
||||||
|
def plot_system(data):
|
||||||
|
# Generate data for plot
|
||||||
|
lasttime = data[0]['#sampletime']
|
||||||
|
lastcputime = float(data[0]['cputime'])
|
||||||
|
times = []
|
||||||
|
sysloads = []
|
||||||
|
cputimes = []
|
||||||
|
memavails = []
|
||||||
|
for d in data:
|
||||||
|
st = d['#sampletime']
|
||||||
|
timedelta = st - lasttime
|
||||||
|
if timedelta <= 0.:
|
||||||
|
continue
|
||||||
|
lasttime = st
|
||||||
|
times.append(datetime.datetime.utcfromtimestamp(st))
|
||||||
|
cputime = float(d['cputime'])
|
||||||
|
cpudelta = max(0., min(1.5, (cputime - lastcputime) / timedelta))
|
||||||
|
lastcputime = cputime
|
||||||
|
cputimes.append(cpudelta * 100.)
|
||||||
|
sysloads.append(float(d['sysload']) * 100.)
|
||||||
|
memavails.append(float(d['memavail']))
|
||||||
|
|
||||||
|
# Build plot
|
||||||
|
fig, ax1 = matplotlib.pyplot.subplots()
|
||||||
|
ax1.set_title("MCU bandwidth and load utilization")
|
||||||
|
ax1.set_xlabel('Time')
|
||||||
|
ax1.set_ylabel('Load (% of a core)')
|
||||||
|
ax1.plot_date(times, sysloads, '-', label='system load',
|
||||||
|
color='cyan', alpha=0.8)
|
||||||
|
ax1.plot_date(times, cputimes, '-', label='process time',
|
||||||
|
color='red', alpha=0.8)
|
||||||
|
ax2 = ax1.twinx()
|
||||||
|
ax2.set_ylabel('Available memory (KB)')
|
||||||
|
ax2.plot_date(times, memavails, '-', label='system memory',
|
||||||
|
color='yellow', alpha=0.3)
|
||||||
|
fontP = matplotlib.font_manager.FontProperties()
|
||||||
|
fontP.set_size('x-small')
|
||||||
|
ax1.legend(loc='best', prop=fontP)
|
||||||
|
ax1.xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%H:%M'))
|
||||||
|
ax1.grid(True)
|
||||||
|
return fig
|
||||||
|
|
||||||
def plot_frequency(data, mcu):
|
def plot_frequency(data, mcu):
|
||||||
all_keys = {}
|
all_keys = {}
|
||||||
for d in data:
|
for d in data:
|
||||||
|
@ -211,6 +253,8 @@ def main():
|
||||||
opts = optparse.OptionParser(usage)
|
opts = optparse.OptionParser(usage)
|
||||||
opts.add_option("-f", "--frequency", action="store_true",
|
opts.add_option("-f", "--frequency", action="store_true",
|
||||||
help="graph mcu frequency")
|
help="graph mcu frequency")
|
||||||
|
opts.add_option("-s", "--system", action="store_true",
|
||||||
|
help="graph system load")
|
||||||
opts.add_option("-o", "--output", type="string", dest="output",
|
opts.add_option("-o", "--output", type="string", dest="output",
|
||||||
default=None, help="filename of output graph")
|
default=None, help="filename of output graph")
|
||||||
opts.add_option("-t", "--temperature", type="string", dest="heater",
|
opts.add_option("-t", "--temperature", type="string", dest="heater",
|
||||||
|
@ -233,6 +277,8 @@ def main():
|
||||||
fig = plot_temperature(data, options.heater)
|
fig = plot_temperature(data, options.heater)
|
||||||
elif options.frequency:
|
elif options.frequency:
|
||||||
fig = plot_frequency(data, options.mcu)
|
fig = plot_frequency(data, options.mcu)
|
||||||
|
elif options.system:
|
||||||
|
fig = plot_system(data)
|
||||||
else:
|
else:
|
||||||
fig = plot_mcu(data, MAXBANDWIDTH)
|
fig = plot_mcu(data, MAXBANDWIDTH)
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ create_virtualenv()
|
||||||
report_status "Updating python virtual environment..."
|
report_status "Updating python virtual environment..."
|
||||||
|
|
||||||
# Create virtualenv if it doesn't already exist
|
# Create virtualenv if it doesn't already exist
|
||||||
[ ! -d ${PYTHONDIR} ] && virtualenv ${PYTHONDIR}
|
[ ! -d ${PYTHONDIR} ] && virtualenv -p python2 ${PYTHONDIR}
|
||||||
|
|
||||||
# Install/update dependencies
|
# Install/update dependencies
|
||||||
${PYTHONDIR}/bin/pip install -r ${SRCDIR}/scripts/klippy-requirements.txt
|
${PYTHONDIR}/bin/pip install -r ${SRCDIR}/scripts/klippy-requirements.txt
|
||||||
|
|
|
@ -37,7 +37,7 @@ create_virtualenv()
|
||||||
report_status "Updating python virtual environment..."
|
report_status "Updating python virtual environment..."
|
||||||
|
|
||||||
# Create virtualenv if it doesn't already exist
|
# Create virtualenv if it doesn't already exist
|
||||||
[ ! -d ${PYTHONDIR} ] && virtualenv ${PYTHONDIR}
|
[ ! -d ${PYTHONDIR} ] && virtualenv -p python2 ${PYTHONDIR}
|
||||||
|
|
||||||
# Install/update dependencies
|
# Install/update dependencies
|
||||||
${PYTHONDIR}/bin/pip install -r ${SRCDIR}/scripts/klippy-requirements.txt
|
${PYTHONDIR}/bin/pip install -r ${SRCDIR}/scripts/klippy-requirements.txt
|
||||||
|
|
|
@ -34,7 +34,7 @@ create_virtualenv()
|
||||||
report_status "Updating python virtual environment..."
|
report_status "Updating python virtual environment..."
|
||||||
|
|
||||||
# Create virtualenv if it doesn't already exist
|
# Create virtualenv if it doesn't already exist
|
||||||
[ ! -d ${PYTHONDIR} ] && virtualenv ${PYTHONDIR}
|
[ ! -d ${PYTHONDIR} ] && virtualenv -p python2 ${PYTHONDIR}
|
||||||
|
|
||||||
# Install/update dependencies
|
# Install/update dependencies
|
||||||
${PYTHONDIR}/bin/pip install -r ${SRCDIR}/scripts/klippy-requirements.txt
|
${PYTHONDIR}/bin/pip install -r ${SRCDIR}/scripts/klippy-requirements.txt
|
||||||
|
|
|
@ -36,7 +36,7 @@ create_virtualenv()
|
||||||
report_status "Updating python virtual environment..."
|
report_status "Updating python virtual environment..."
|
||||||
|
|
||||||
# Create virtualenv if it doesn't already exist
|
# Create virtualenv if it doesn't already exist
|
||||||
[ ! -d ${PYTHONDIR} ] && virtualenv ${PYTHONDIR}
|
[ ! -d ${PYTHONDIR} ] && virtualenv -p python2 ${PYTHONDIR}
|
||||||
|
|
||||||
# Install/update dependencies
|
# Install/update dependencies
|
||||||
${PYTHONDIR}/bin/pip install -r ${SRCDIR}/scripts/klippy-requirements.txt
|
${PYTHONDIR}/bin/pip install -r ${SRCDIR}/scripts/klippy-requirements.txt
|
||||||
|
|
|
@ -62,5 +62,5 @@ fi
|
||||||
|
|
||||||
echo -e "\n\n=============== Install python virtualenv\n\n"
|
echo -e "\n\n=============== Install python virtualenv\n\n"
|
||||||
cd ${MAIN_DIR}
|
cd ${MAIN_DIR}
|
||||||
virtualenv ${BUILD_DIR}/python-env
|
virtualenv -p python2 ${BUILD_DIR}/python-env
|
||||||
${BUILD_DIR}/python-env/bin/pip install -r ${MAIN_DIR}/scripts/klippy-requirements.txt
|
${BUILD_DIR}/python-env/bin/pip install -r ${MAIN_DIR}/scripts/klippy-requirements.txt
|
||||||
|
|
|
@ -0,0 +1,134 @@
|
||||||
|
#!/usr/bin/env python2
|
||||||
|
# Encodes STM32 firmwares to be flashable from SD card by Chitu motherboards.
|
||||||
|
# Relocate firmware to 0x08008800!
|
||||||
|
|
||||||
|
# Copied from Marlin and modified.
|
||||||
|
# Licensed under GPL-3.0
|
||||||
|
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import struct
|
||||||
|
import uuid
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def calculate_crc(contents, seed):
|
||||||
|
accumulating_xor_value = seed;
|
||||||
|
|
||||||
|
for i in range(0, len(contents), 4):
|
||||||
|
value = struct.unpack('<I', contents[ i : i + 4])[0]
|
||||||
|
accumulating_xor_value = accumulating_xor_value ^ value
|
||||||
|
return accumulating_xor_value
|
||||||
|
|
||||||
|
def xor_block(r0, r1, block_number, block_size, file_key):
|
||||||
|
# This is the loop counter
|
||||||
|
loop_counter = 0x0
|
||||||
|
|
||||||
|
# This is the key length
|
||||||
|
key_length = 0x18
|
||||||
|
|
||||||
|
# This is an initial seed
|
||||||
|
xor_seed = 0x4bad
|
||||||
|
|
||||||
|
# This is the block counter
|
||||||
|
block_number = xor_seed * block_number
|
||||||
|
|
||||||
|
#load the xor key from the file
|
||||||
|
r7 = file_key
|
||||||
|
|
||||||
|
for loop_counter in range(0, block_size):
|
||||||
|
# meant to make sure different bits of the key are used.
|
||||||
|
xor_seed = int(loop_counter/key_length)
|
||||||
|
|
||||||
|
# IP is a scratch register / R12
|
||||||
|
ip = loop_counter - (key_length * xor_seed)
|
||||||
|
|
||||||
|
# xor_seed = (loop_counter * loop_counter) + block_number
|
||||||
|
xor_seed = (loop_counter * loop_counter) + block_number
|
||||||
|
|
||||||
|
# shift the xor_seed left by the bits in IP.
|
||||||
|
xor_seed = xor_seed >> ip
|
||||||
|
|
||||||
|
# load a byte into IP
|
||||||
|
ip = r0[loop_counter]
|
||||||
|
|
||||||
|
# XOR the seed with r7
|
||||||
|
xor_seed = xor_seed ^ r7
|
||||||
|
|
||||||
|
# and then with IP
|
||||||
|
xor_seed = xor_seed ^ ip
|
||||||
|
|
||||||
|
#Now store the byte back
|
||||||
|
r1[loop_counter] = xor_seed & 0xFF
|
||||||
|
|
||||||
|
#increment the loop_counter
|
||||||
|
loop_counter = loop_counter + 1
|
||||||
|
|
||||||
|
|
||||||
|
def encode_file(input, output_file, file_length):
|
||||||
|
input_file = bytearray(input.read())
|
||||||
|
block_size = 0x800
|
||||||
|
key_length = 0x18
|
||||||
|
|
||||||
|
uid_value = uuid.uuid4()
|
||||||
|
file_key = int(uid_value.hex[0:8], 16)
|
||||||
|
|
||||||
|
xor_crc = 0xef3d4323;
|
||||||
|
|
||||||
|
# the input file is exepcted to be in chunks of 0x800
|
||||||
|
# so round the size
|
||||||
|
while len(input_file) % block_size != 0:
|
||||||
|
input_file.extend(b'0x0')
|
||||||
|
|
||||||
|
# write the file header
|
||||||
|
output_file.write(struct.pack(">I", 0x443D2D3F))
|
||||||
|
# encode the contents using a known file header key
|
||||||
|
|
||||||
|
# write the file_key
|
||||||
|
output_file.write(struct.pack("<I", file_key))
|
||||||
|
|
||||||
|
#TODO - how to enforce that the firmware aligns to block boundaries?
|
||||||
|
block_count = int(len(input_file) / block_size)
|
||||||
|
print("Block Count is ", block_count)
|
||||||
|
for block_number in range(0, block_count):
|
||||||
|
block_offset = (block_number * block_size)
|
||||||
|
block_end = block_offset + block_size
|
||||||
|
block_array = bytearray(input_file[block_offset: block_end])
|
||||||
|
xor_block(block_array, block_array, block_number, block_size, file_key)
|
||||||
|
for n in range (0, block_size):
|
||||||
|
input_file[block_offset + n] = block_array[n]
|
||||||
|
|
||||||
|
# update the expected CRC value.
|
||||||
|
xor_crc = calculate_crc(block_array, xor_crc)
|
||||||
|
|
||||||
|
# write CRC
|
||||||
|
output_file.write(struct.pack("<I", xor_crc))
|
||||||
|
|
||||||
|
# finally, append the encoded results.
|
||||||
|
output_file.write(input_file)
|
||||||
|
return
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) != 3:
|
||||||
|
print("Usage: update_chitu <input_file> <output_file>")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
fw, output = sys.argv[1:]
|
||||||
|
|
||||||
|
if not os.path.isfile(fw):
|
||||||
|
print("Usage: update_chitu <input_file> <output_file>")
|
||||||
|
print("Firmware file", fw, "does not exist")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
firmware = open(fw, "rb")
|
||||||
|
update = open(output, "wb")
|
||||||
|
length = os.path.getsize(fw)
|
||||||
|
|
||||||
|
encode_file(firmware, update, length)
|
||||||
|
|
||||||
|
firmware.close()
|
||||||
|
update.close()
|
||||||
|
|
||||||
|
print("Encoding complete.")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -7,4 +7,4 @@ src-$(CONFIG_HAVE_GPIO_SPI) += spicmds.c thermocouple.c
|
||||||
src-$(CONFIG_HAVE_GPIO_I2C) += i2ccmds.c
|
src-$(CONFIG_HAVE_GPIO_I2C) += i2ccmds.c
|
||||||
src-$(CONFIG_HAVE_GPIO_HARD_PWM) += pwmcmds.c
|
src-$(CONFIG_HAVE_GPIO_HARD_PWM) += pwmcmds.c
|
||||||
src-$(CONFIG_HAVE_GPIO_BITBANGING) += lcd_st7920.c lcd_hd44780.c buttons.c \
|
src-$(CONFIG_HAVE_GPIO_BITBANGING) += lcd_st7920.c lcd_hd44780.c buttons.c \
|
||||||
tmcuart.c spi_software.c neopixel.c
|
tmcuart.c spi_software.c neopixel.c sensor_adxl345.c
|
||||||
|
|
|
@ -0,0 +1,179 @@
|
||||||
|
// Support for gathering acceleration data from ADXL345 chip
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
//
|
||||||
|
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
|
||||||
|
#include <string.h> // memcpy
|
||||||
|
#include "board/irq.h" // irq_disable
|
||||||
|
#include "board/misc.h" // timer_read_time
|
||||||
|
#include "basecmd.h" // oid_alloc
|
||||||
|
#include "command.h" // DECL_COMMAND
|
||||||
|
#include "sched.h" // DECL_TASK
|
||||||
|
#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[48];
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
AX_HAVE_START = 1<<0, AX_RUNNING = 1<<1, AX_PENDING = 1<<2,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct task_wake adxl345_wake;
|
||||||
|
|
||||||
|
static uint_fast8_t
|
||||||
|
adxl345_event(struct timer *timer)
|
||||||
|
{
|
||||||
|
struct adxl345 *ax = container_of(timer, struct adxl345, timer);
|
||||||
|
ax->flags |= AX_PENDING;
|
||||||
|
sched_wake_task(&adxl345_wake);
|
||||||
|
return SF_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
command_config_adxl345(uint32_t *args)
|
||||||
|
{
|
||||||
|
struct adxl345 *ax = oid_alloc(args[0], command_config_adxl345
|
||||||
|
, sizeof(*ax));
|
||||||
|
ax->timer.func = adxl345_event;
|
||||||
|
ax->spi = spidev_oid_lookup(args[1]);
|
||||||
|
}
|
||||||
|
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++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chip registers
|
||||||
|
#define AR_POWER_CTL 0x2D
|
||||||
|
#define AR_DATAX0 0x32
|
||||||
|
#define AR_FIFO_STATUS 0x39
|
||||||
|
#define AM_READ 0x80
|
||||||
|
#define AM_MULTI 0x40
|
||||||
|
|
||||||
|
// Query accelerometer data
|
||||||
|
static void
|
||||||
|
adxl_query(struct adxl345 *ax, uint8_t oid)
|
||||||
|
{
|
||||||
|
uint8_t msg[9] = { AR_DATAX0 | AM_READ | AM_MULTI, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||||
|
spidev_transfer(ax->spi, 1, sizeof(msg), msg);
|
||||||
|
memcpy(&ax->data[ax->data_count], &msg[1], 6);
|
||||||
|
ax->data_count += 6;
|
||||||
|
if (ax->data_count + 6 > ARRAY_SIZE(ax->data))
|
||||||
|
adxl_report(ax, oid);
|
||||||
|
uint_fast8_t fifo_status = msg[8] & ~0x80; // Ignore trigger bit
|
||||||
|
if (fifo_status >= 31 && ax->limit_count != 0xffff)
|
||||||
|
ax->limit_count++;
|
||||||
|
if (fifo_status > 1 && fifo_status <= 32) {
|
||||||
|
// More data in fifo - wake this task again
|
||||||
|
sched_wake_task(&adxl345_wake);
|
||||||
|
} else if (ax->flags & AX_RUNNING) {
|
||||||
|
// Sleep until next check time
|
||||||
|
sched_del_timer(&ax->timer);
|
||||||
|
ax->flags &= ~AX_PENDING;
|
||||||
|
irq_disable();
|
||||||
|
ax->timer.waketime = timer_read_time() + ax->rest_ticks;
|
||||||
|
sched_add_timer(&ax->timer);
|
||||||
|
irq_enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 };
|
||||||
|
uint32_t start1_time = timer_read_time();
|
||||||
|
spidev_transfer(ax->spi, 0, sizeof(msg), msg);
|
||||||
|
irq_disable();
|
||||||
|
uint32_t start2_time = timer_read_time();
|
||||||
|
ax->timer.waketime = start2_time + ax->rest_ticks;
|
||||||
|
sched_add_timer(&ax->timer);
|
||||||
|
irq_enable();
|
||||||
|
sendf("adxl345_start oid=%c start1_time=%u start2_time=%u"
|
||||||
|
, oid, start1_time, start2_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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] = 0;
|
||||||
|
spidev_transfer(ax->spi, 1, sizeof(msg), msg);
|
||||||
|
if (!(msg[1] & 0x3f))
|
||||||
|
break;
|
||||||
|
adxl_query(ax, oid);
|
||||||
|
}
|
||||||
|
// Report final data
|
||||||
|
if (ax->data_count)
|
||||||
|
adxl_report(ax, oid);
|
||||||
|
sendf("adxl345_end oid=%c end1_time=%u end2_time=%u"
|
||||||
|
" limit_count=%hu sequence=%hu"
|
||||||
|
, oid, end1_time, end2_time, ax->limit_count, ax->sequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
command_query_adxl345(uint32_t *args)
|
||||||
|
{
|
||||||
|
struct adxl345 *ax = oid_lookup(args[0], command_config_adxl345);
|
||||||
|
|
||||||
|
if (!args[2]) {
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
DECL_COMMAND(command_query_adxl345,
|
||||||
|
"query_adxl345 oid=%c clock=%u rest_ticks=%u");
|
||||||
|
|
||||||
|
void
|
||||||
|
adxl345_task(void)
|
||||||
|
{
|
||||||
|
if (!sched_check_wake(&adxl345_wake))
|
||||||
|
return;
|
||||||
|
uint8_t oid;
|
||||||
|
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
|
||||||
|
adxl_query(ax, oid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DECL_TASK(adxl345_task);
|
|
@ -116,6 +116,8 @@ choice
|
||||||
bool "28KiB bootloader" if MACH_STM32F103
|
bool "28KiB bootloader" if MACH_STM32F103
|
||||||
config STM32_FLASH_START_8000
|
config STM32_FLASH_START_8000
|
||||||
bool "32KiB bootloader (SKR-PRO or TFT35-V3.0)" if MACH_STM32F207 || MACH_STM32F407
|
bool "32KiB bootloader (SKR-PRO or TFT35-V3.0)" if MACH_STM32F207 || MACH_STM32F407
|
||||||
|
config STM32_FLASH_START_8800
|
||||||
|
bool "34KiB bootloader (Chitu v6 Bootloader)" if MACH_STM32F103
|
||||||
config STM32_FLASH_START_10000
|
config STM32_FLASH_START_10000
|
||||||
bool "64KiB bootloader (Alfawise)" if MACH_STM32F103
|
bool "64KiB bootloader (Alfawise)" if MACH_STM32F103
|
||||||
config STM32_FLASH_START_0000
|
config STM32_FLASH_START_0000
|
||||||
|
@ -129,6 +131,7 @@ config FLASH_START
|
||||||
default 0x8005000 if STM32_FLASH_START_5000
|
default 0x8005000 if STM32_FLASH_START_5000
|
||||||
default 0x8007000 if STM32_FLASH_START_7000
|
default 0x8007000 if STM32_FLASH_START_7000
|
||||||
default 0x8008000 if STM32_FLASH_START_8000
|
default 0x8008000 if STM32_FLASH_START_8000
|
||||||
|
default 0x8008800 if STM32_FLASH_START_8800
|
||||||
default 0x8010000 if STM32_FLASH_START_10000
|
default 0x8010000 if STM32_FLASH_START_10000
|
||||||
default 0x8000000
|
default 0x8000000
|
||||||
|
|
||||||
|
|
|
@ -127,6 +127,8 @@ CONFIG ../../config/generic-mks-robin-e3.cfg
|
||||||
CONFIG ../../config/generic-mks-robin-nano.cfg
|
CONFIG ../../config/generic-mks-robin-nano.cfg
|
||||||
CONFIG ../../config/printer-alfawise-u30-2018.cfg
|
CONFIG ../../config/printer-alfawise-u30-2018.cfg
|
||||||
CONFIG ../../config/printer-creality-ender3-v2-2020.cfg
|
CONFIG ../../config/printer-creality-ender3-v2-2020.cfg
|
||||||
|
CONFIG ../../config/printer-creality-ender3pro-2020.cfg
|
||||||
|
CONFIG ../../config/printer-tronxy-x5sa-v6-2019.cfg
|
||||||
CONFIG ../../config/printer-twotrees-sapphire-pro-2020.cfg
|
CONFIG ../../config/printer-twotrees-sapphire-pro-2020.cfg
|
||||||
|
|
||||||
# Printers using the stm32f407
|
# Printers using the stm32f407
|
||||||
|
|
Loading…
Reference in New Issue