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.
|
||||
# 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
|
||||
|
@ -1304,6 +1330,14 @@
|
|||
#pin:
|
||||
# The pin on which the button is connected. This parameter must be
|
||||
# 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:
|
||||
# A list of G-Code commands to execute when the button is pressed.
|
||||
# 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
|
||||
# associated with a "smearing" effect on some OLED displays.
|
||||
# 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
|
||||
# TRUE inverts the pixels on certain OLED (SSD1306/SH1106) displays
|
||||
# The default is FALSE
|
||||
|
@ -2188,6 +2224,23 @@
|
|||
# communication. The pin must have a valid pinmux configuration
|
||||
# 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
|
||||
# details.
|
||||
#[replicape]
|
||||
|
|
|
@ -59,6 +59,11 @@ microsteps: 16
|
|||
run_current: 0.800
|
||||
stealthchop_threshold: 30
|
||||
|
||||
# Support analog sensor adjustments using VREF/VSSA pins
|
||||
[adc_scaled vref_scaled]
|
||||
vref_pin: PA17
|
||||
vssa_pin: PA19
|
||||
|
||||
[extruder]
|
||||
step_pin: PC4
|
||||
dir_pin: PB7
|
||||
|
@ -69,7 +74,7 @@ filament_diameter: 1.750
|
|||
heater_pin: !PC1
|
||||
sensor_type: EPCOS 100K B57560G104F
|
||||
pullup_resistor: 2200
|
||||
sensor_pin: PB0
|
||||
sensor_pin: vref_scaled:PB0
|
||||
control: pid
|
||||
pid_Kp: 22.2
|
||||
pid_Ki: 1.08
|
||||
|
@ -110,7 +115,7 @@ stealthchop_threshold: 5
|
|||
heater_pin: !PC0
|
||||
sensor_type: EPCOS 100K B57560G104F
|
||||
pullup_resistor: 2200
|
||||
sensor_pin: PA20
|
||||
sensor_pin: vref_scaled:PA20
|
||||
control: watermark
|
||||
min_temp: 0
|
||||
max_temp: 130
|
||||
|
|
|
@ -134,3 +134,31 @@ pin: replicape:power_fan0
|
|||
# PWM output pin controlling the servo. This parameter must be
|
||||
# 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
|
||||
|
||||
`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_
|
||||
|
||||
Initiates the probing procedure for Bed Mesh Calibration. If `METHOD=manual`
|
||||
is selected then manual probing will occur. When switching between automatic
|
||||
and manual probing the generated mesh points will automatically be adjusted.
|
||||
|
||||
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
|
||||
|
||||
`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 "Probe" points refer to the probe location. Note that when manually
|
||||
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
|
||||
the given endstop was reported as "triggered" during the last
|
||||
QUERY_ENDSTOP command. Note, due to the order of template expansion
|
||||
(see above), the QUERY_STATUS command must be run prior to the macro
|
||||
containing this reference.
|
||||
(see above), the QUERY_ENDSTOP command must be run prior to the
|
||||
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
|
||||
given config file setting as read by Klipper during the last
|
||||
software start or restart. (Any settings changed at run-time will
|
||||
|
|
|
@ -6,6 +6,10 @@ All dates in this document are approximate.
|
|||
|
||||
# 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
|
||||
`printer.gcode_move`. Several undocumented variables in
|
||||
`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)
|
||||
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
|
||||
Linux distributions enable a "ModemManager" (or similar) package that
|
||||
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
|
||||
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
|
||||
parameters in the config. After probing, a mesh is generated and
|
||||
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
|
||||
mm/s). If ACCEL is specified and is greater than zero, then the
|
||||
given acceleration (in mm/s^2) will be used; otherwise no
|
||||
acceleration is performed. No boundary checks are performed; no
|
||||
kinematic updates are made; other parallel steppers on an axis will
|
||||
not be moved. Use caution as an incorrect command could cause
|
||||
damage! Using this 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.
|
||||
acceleration is performed. If acceleration is not performed then it
|
||||
can lead to the micro-controller reporting "No next step" errors
|
||||
(avoid these errors by specifying an ACCEL value or use a very low
|
||||
VELOCITY). No boundary checks are performed; no kinematic updates
|
||||
are made; other parallel steppers on an axis will not be moved. Use
|
||||
caution as an incorrect command could cause damage! Using this
|
||||
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
|
||||
the low-level kinematic code to believe the toolhead is at the given
|
||||
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
|
||||
temperature_fan. If a target is not supplied, it is set to the
|
||||
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
|
||||
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
|
||||
release information. One should verify that OctoPi boots and that the
|
||||
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
|
||||
to ssh into the target machine to run a handful of system commands. If
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# 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.
|
||||
import os, logging
|
||||
|
@ -11,9 +11,11 @@ import cffi
|
|||
# c_helper.so compiling
|
||||
######################################################################
|
||||
|
||||
COMPILE_CMD = ("gcc -Wall -g -O2 -shared -fPIC"
|
||||
" -flto -fwhole-program -fno-use-linker-plugin"
|
||||
" -o %s %s")
|
||||
GCC_CMD = "gcc"
|
||||
COMPILE_ARGS = ("-Wall -g -O2 -shared -fPIC"
|
||||
" -flto -fwhole-program -fno-use-linker-plugin"
|
||||
" -o %s %s")
|
||||
SSE_FLAGS = "-mfpmath=sse -msse2"
|
||||
SOURCE_FILES = [
|
||||
'pyhelper.c', 'serialqueue.c', 'stepcompress.c', 'itersolve.c', 'trapq.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
|
||||
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])
|
||||
if not obj_times or max(src_times) > min(obj_times):
|
||||
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)
|
||||
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_lib = None
|
||||
pyhelper_logging_callback = None
|
||||
|
@ -211,8 +219,11 @@ def get_ffi():
|
|||
global FFI_main, FFI_lib, pyhelper_logging_callback
|
||||
if FFI_lib is None:
|
||||
srcdir = os.path.dirname(os.path.realpath(__file__))
|
||||
check_build_code(srcdir, DEST_LIB, SOURCE_FILES, COMPILE_CMD
|
||||
, OTHER_FILES)
|
||||
if check_gcc_option(SSE_FLAGS):
|
||||
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()
|
||||
for d in defs_all:
|
||||
FFI_main.cdef(d)
|
||||
|
|
|
@ -13,6 +13,225 @@
|
|||
#include "itersolve.h" // struct stepper_kinematics
|
||||
#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
|
||||
****************************************************************/
|
||||
|
@ -40,224 +259,20 @@ get_axis_position_across_moves(struct move *m, int axis, double 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
|
||||
static inline double
|
||||
calc_position(struct move *m, int axis, double move_time
|
||||
, struct shaper_pulse *pulses, int n)
|
||||
, struct shaper_pulses *sp)
|
||||
{
|
||||
double res = 0.;
|
||||
int i;
|
||||
for (i = 0; i < n; ++i)
|
||||
res += pulses[i].a * get_axis_position_across_moves(
|
||||
m, axis, move_time + pulses[i].t);
|
||||
int num_pulses = sp->num_pulses, i;
|
||||
for (i = 0; i < num_pulses; ++i) {
|
||||
double t = sp->pulses[i].t, a = sp->pulses[i].a;
|
||||
res += a * get_axis_position_across_moves(m, axis, move_time + t);
|
||||
}
|
||||
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
|
||||
|
@ -265,15 +280,22 @@ shift_pulses(int n, struct shaper_pulse *pulses)
|
|||
|
||||
#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
|
||||
static double
|
||||
shaper_x_calc_position(struct stepper_kinematics *sk, struct move *m
|
||||
, double move_time)
|
||||
{
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -283,9 +305,9 @@ shaper_y_calc_position(struct stepper_kinematics *sk, struct move *m
|
|||
, double move_time)
|
||||
{
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -295,48 +317,27 @@ shaper_xy_calc_position(struct stepper_kinematics *sk, struct move *m
|
|||
, double move_time)
|
||||
{
|
||||
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);
|
||||
is->m.start_pos = move_get_coord(m, move_time);
|
||||
if (is->x_n)
|
||||
is->m.start_pos.x = calc_position(m, 'x', move_time
|
||||
, is->x_pulses, is->x_n);
|
||||
if (is->y_n)
|
||||
is->m.start_pos.y = calc_position(m, 'y', move_time
|
||||
, is->y_pulses, is->y_n);
|
||||
if (is->sx.num_pulses)
|
||||
is->m.start_pos.x = calc_position(m, 'x', move_time, &is->sx);
|
||||
if (is->sy.num_pulses)
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
input_shaper_set_sk(struct stepper_kinematics *sk
|
||||
, struct stepper_kinematics *orig_sk)
|
||||
{
|
||||
struct input_shaper *is = container_of(sk, struct input_shaper, sk);
|
||||
int af = orig_sk->active_flags & (AF_X | AF_Y);
|
||||
if (af == (AF_X | AF_Y))
|
||||
is->sk.calc_position_cb = shaper_xy_calc_position;
|
||||
else if (af & AF_X)
|
||||
if (orig_sk->active_flags == AF_X)
|
||||
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;
|
||||
else if (orig_sk->active_flags & (AF_X | AF_Y))
|
||||
is->sk.calc_position_cb = shaper_xy_calc_position;
|
||||
else
|
||||
return -1;
|
||||
is->sk.active_flags = orig_sk->active_flags;
|
||||
|
@ -344,14 +345,23 @@ input_shaper_set_sk(struct stepper_kinematics *sk
|
|||
return 0;
|
||||
}
|
||||
|
||||
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
|
||||
shaper_note_generation_time(struct input_shaper *is)
|
||||
{
|
||||
double pre_active = 0., post_active = 0.;
|
||||
if ((is->sk.active_flags & AF_X) && is->sx.num_pulses) {
|
||||
pre_active = is->sx.pulses[is->sx.num_pulses-1].t;
|
||||
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
|
||||
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)
|
||||
{
|
||||
struct input_shaper *is = container_of(sk, struct input_shaper, sk);
|
||||
|
||||
if (shaper_type_x >= ARRAY_SIZE(init_shaper_callbacks) || shaper_type_x < 0)
|
||||
return -1;
|
||||
if (shaper_type_y >= ARRAY_SIZE(init_shaper_callbacks) || shaper_type_y < 0)
|
||||
return -1;
|
||||
|
||||
int af = is->orig_sk->active_flags & (AF_X | AF_Y);
|
||||
free(is->x_pulses);
|
||||
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;
|
||||
}
|
||||
if (is->orig_sk->active_flags & AF_X)
|
||||
init_shaper(shaper_type_x, shaper_freq_x, damping_ratio_x, &is->sx);
|
||||
else
|
||||
is->sx.num_pulses = 0;
|
||||
if (is->orig_sk->active_flags & AF_Y)
|
||||
init_shaper(shaper_type_y, shaper_freq_y, damping_ratio_y, &is->sy);
|
||||
else
|
||||
is->sy.num_pulses = 0;
|
||||
shaper_note_generation_time(is);
|
||||
return 0;
|
||||
}
|
||||
|
@ -396,19 +389,13 @@ double __visible
|
|||
input_shaper_get_step_generation_window(int shaper_type, double shaper_freq
|
||||
, 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.;
|
||||
if (shaper_type >= ARRAY_SIZE(init_shaper_callbacks) || shaper_type < 0)
|
||||
return 0.;
|
||||
is_init_shaper_callback init_shaper_cb = init_shaper_callbacks[shaper_type];
|
||||
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);
|
||||
double window = -sp.pulses[0].t;
|
||||
if (sp.pulses[sp.num_pulses-1].t > window)
|
||||
window = sp.pulses[sp.num_pulses-1].t;
|
||||
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.):
|
||||
# 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)
|
||||
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']
|
||||
def __init__(self, config, bedmesh):
|
||||
self.printer = config.get_printer()
|
||||
self.orig_config = {'radius': None, 'origin': None}
|
||||
self.radius = self.origin = None
|
||||
self.mesh_min = self.mesh_max = (0., 0.)
|
||||
self.relative_reference_index = config.getint(
|
||||
'relative_reference_index', None)
|
||||
self.orig_config['rri'] = self.relative_reference_index
|
||||
self.bedmesh = bedmesh
|
||||
self.mesh_config = collections.OrderedDict()
|
||||
self.points = self._generate_points(config)
|
||||
self._init_mesh_config(config, self.points)
|
||||
self._init_mesh_config(config)
|
||||
self._generate_points(config.error)
|
||||
self.orig_points = self.points
|
||||
self.probe_helper = probe.ProbePointsHelper(
|
||||
config, self.probe_finalize, self.points)
|
||||
self.probe_helper.minimum_points(3)
|
||||
|
@ -250,37 +254,18 @@ class BedMeshCalibrate:
|
|||
self.gcode.register_command(
|
||||
'BED_MESH_CALIBRATE', self.cmd_BED_MESH_CALIBRATE,
|
||||
desc=self.cmd_BED_MESH_CALIBRATE_help)
|
||||
def _generate_points(self, 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
|
||||
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
|
||||
def _generate_points(self, error):
|
||||
x_cnt = self.mesh_config['x_count']
|
||||
y_cnt = self.mesh_config['y_count']
|
||||
min_x, min_y = self.mesh_min
|
||||
max_x, max_y = self.mesh_max
|
||||
x_dist = (max_x - min_x) / (x_cnt - 1)
|
||||
y_dist = (max_y - min_y) / (y_cnt - 1)
|
||||
# floor distances down to next hundredth
|
||||
x_dist = math.floor(x_dist * 100) / 100
|
||||
y_dist = math.floor(y_dist * 100) / 100
|
||||
if x_dist <= 1. or y_dist <= 1.:
|
||||
raise 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:
|
||||
# round bed, min/max needs to be recalculated
|
||||
|
@ -311,7 +296,7 @@ class BedMeshCalibrate:
|
|||
points.append(
|
||||
(self.origin[0] + pos_x, self.origin[1] + pos_y))
|
||||
pos_y += y_dist
|
||||
return points
|
||||
self.points = points
|
||||
def print_generated_points(self, print_func):
|
||||
x_offset = y_offset = 0.
|
||||
probe = self.printer.lookup_object('probe', None)
|
||||
|
@ -329,33 +314,69 @@ class BedMeshCalibrate:
|
|||
print_func(
|
||||
"bed_mesh: relative_reference_index %d is (%.2f, %.2f)"
|
||||
% (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,
|
||||
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['mesh_x_pps'] = pps[0]
|
||||
params['mesh_y_pps'] = pps[1]
|
||||
params['algo'] = config.get('algorithm', 'lagrange').strip().lower()
|
||||
x_pps = params['mesh_x_pps']
|
||||
y_pps = params['mesh_y_pps']
|
||||
if params['algo'] not in self.ALGOS:
|
||||
raise config.error(
|
||||
raise error(
|
||||
"bed_mesh: Unknown algorithm <%s>"
|
||||
% (self.mesh_config['algo']))
|
||||
# Check the algorithm against the current configuration
|
||||
max_probe_cnt = max(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
|
||||
self.mesh_config['algo'] = 'direct'
|
||||
elif params['algo'] == 'lagrange' and max_probe_cnt > 6:
|
||||
# Lagrange interpolation tends to oscillate when using more
|
||||
# than 6 samples
|
||||
raise config.error(
|
||||
raise error(
|
||||
"bed_mesh: cannot exceed a probe_count of 6 when using "
|
||||
"lagrange interpolation. Configured Probe Count: %d, %d" %
|
||||
(self.mesh_config['x_count'], self.mesh_config['y_count']))
|
||||
elif params['algo'] == 'bicubic' and min_probe_cnt < 4:
|
||||
if max_probe_cnt > 6:
|
||||
raise config.error(
|
||||
raise error(
|
||||
"bed_mesh: invalid probe_count option when using bicubic "
|
||||
"interpolation. Combination of 3 points on one axis with "
|
||||
"more than 6 on another is not permitted. "
|
||||
|
@ -368,11 +389,74 @@ class BedMeshCalibrate:
|
|||
"interpolation. Configured Probe Count: %d, %d" %
|
||||
(self.mesh_config['x_count'], self.mesh_config['y_count']))
|
||||
params['algo'] = 'lagrange'
|
||||
params['tension'] = config.getfloat(
|
||||
'bicubic_tension', .2, minval=0., maxval=2.)
|
||||
def update_config(self, gcmd):
|
||||
# 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"
|
||||
def cmd_BED_MESH_CALIBRATE(self, gcmd):
|
||||
self.bedmesh.set_mesh(None)
|
||||
self.update_config(gcmd)
|
||||
self.probe_helper.start_probe(gcmd)
|
||||
def probe_finalize(self, offsets, positions):
|
||||
x_offset, y_offset, z_offset = offsets
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
# Copyright (C) 2019 Kevin O'Connor <kevin@koconnor.net>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import homing
|
||||
|
||||
def parse_coord(config, param):
|
||||
pair = config.get(param).strip().split(',', 1)
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import logging
|
||||
import homing
|
||||
from . import probe
|
||||
|
||||
SIGNAL_PERIOD = 0.020
|
||||
|
@ -82,7 +81,7 @@ class BLTouchEndstopWrapper:
|
|||
self.set_output_mode(self.output_mode)
|
||||
try:
|
||||
self.raise_probe()
|
||||
except homing.CommandError as e:
|
||||
except self.printer.command_error as e:
|
||||
logging.warning("BLTouch raise probe error: %s", str(e))
|
||||
def sync_mcu_print_time(self):
|
||||
curtime = self.printer.get_reactor().monotonic()
|
||||
|
@ -128,7 +127,8 @@ class BLTouchEndstopWrapper:
|
|||
# The "probe raised" test completed successfully
|
||||
break
|
||||
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."
|
||||
self.gcode.respond_info(msg)
|
||||
self.sync_mcu_print_time()
|
||||
|
@ -161,7 +161,7 @@ class BLTouchEndstopWrapper:
|
|||
return
|
||||
msg = "BLTouch failed to verify sensor state"
|
||||
if retry >= 2:
|
||||
raise homing.EndstopError(msg)
|
||||
raise self.printer.command_error(msg)
|
||||
self.gcode.respond_info(msg + '; retrying.')
|
||||
self.send_cmd('reset', duration=RETRY_RESET_TIME)
|
||||
def multi_probe_begin(self):
|
||||
|
@ -191,7 +191,7 @@ class BLTouchEndstopWrapper:
|
|||
# Verify the probe actually deployed during the attempt
|
||||
for s, mcu_pos in self.start_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):
|
||||
rest_time = min(rest_time, ENDSTOP_REST_TIME)
|
||||
return self.mcu_endstop.home_start(print_time, sample_time,
|
||||
|
|
|
@ -62,7 +62,7 @@ class MCU_buttons:
|
|||
ack_diff -= 0x100
|
||||
msg_ack_count = ack_count - ack_diff
|
||||
# Determine new buttons
|
||||
buttons = params['state']
|
||||
buttons = bytearray(params['state'])
|
||||
new_count = msg_ack_count + len(buttons) - self.ack_count
|
||||
if new_count <= 0:
|
||||
return
|
||||
|
@ -71,9 +71,9 @@ class MCU_buttons:
|
|||
self.ack_cmd.send([self.oid, new_count])
|
||||
self.ack_count += new_count
|
||||
# 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(
|
||||
(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):
|
||||
button ^= self.invert
|
||||
changed = button ^ self.last_button
|
||||
|
|
|
@ -182,21 +182,25 @@ class DeltaCalibrate:
|
|||
z_weight = len(distances) / (MEASURE_WEIGHT * len(probe_positions))
|
||||
# Perform coordinate descent
|
||||
def delta_errorfunc(params):
|
||||
# Build new delta_params for params under test
|
||||
delta_params = orig_delta_params.new_calibration(params)
|
||||
# Calculate z height errors
|
||||
total_error = 0.
|
||||
for z_offset, stable_pos in height_positions:
|
||||
x, y, z = delta_params.get_position_from_stable(stable_pos)
|
||||
total_error += (z - z_offset)**2
|
||||
total_error *= z_weight
|
||||
# Calculate distance errors
|
||||
for dist, stable_pos1, stable_pos2 in distances:
|
||||
x1, y1, z1 = delta_params.get_position_from_stable(stable_pos1)
|
||||
x2, y2, z2 = delta_params.get_position_from_stable(stable_pos2)
|
||||
d = math.sqrt((x1-x2)**2 + (y1-y2)**2 + (z1-z2)**2)
|
||||
total_error += (d - dist)**2
|
||||
return total_error
|
||||
try:
|
||||
# Build new delta_params for params under test
|
||||
delta_params = orig_delta_params.new_calibration(params)
|
||||
getpos = delta_params.get_position_from_stable
|
||||
# Calculate z height errors
|
||||
total_error = 0.
|
||||
for z_offset, stable_pos in height_positions:
|
||||
x, y, z = getpos(stable_pos)
|
||||
total_error += (z - z_offset)**2
|
||||
total_error *= z_weight
|
||||
# Calculate distance errors
|
||||
for dist, stable_pos1, stable_pos2 in distances:
|
||||
x1, y1, z1 = getpos(stable_pos1)
|
||||
x2, y2, z2 = getpos(stable_pos2)
|
||||
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(
|
||||
self.printer, adj_params, params, delta_errorfunc)
|
||||
# Log and report results
|
||||
|
|
|
@ -79,6 +79,7 @@ class DisplayGroup:
|
|||
for row, col, template in self.data_items:
|
||||
text = template.render(context)
|
||||
display.draw_text(row, col, text.replace('\n', ''), eventtime)
|
||||
context.clear() # Remove circular references for better gc
|
||||
|
||||
class PrinterLCD:
|
||||
def __init__(self, config):
|
||||
|
|
|
@ -103,7 +103,7 @@ class HD44780:
|
|||
data = self.icons.get(glyph_name)
|
||||
if data is not None:
|
||||
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
|
||||
return 1
|
||||
char = TextGlyphs.get(glyph_name)
|
||||
|
|
|
@ -397,8 +397,8 @@ class MenuContainer(MenuElement):
|
|||
return self.select_at(index)
|
||||
|
||||
# override
|
||||
def render_container(self, eventtime):
|
||||
return ("", None)
|
||||
def render_container(self, nrows, eventtime):
|
||||
return []
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._items)
|
||||
|
@ -550,6 +550,7 @@ class MenuInput(MenuCommand):
|
|||
class MenuList(MenuContainer):
|
||||
def __init__(self, manager, config):
|
||||
super(MenuList, self).__init__(manager, config)
|
||||
self._viewport_top = 0
|
||||
# create back item
|
||||
self._itemBack = self.manager.menuitem_from({
|
||||
'type': 'command',
|
||||
|
@ -562,29 +563,43 @@ class MenuList(MenuContainer):
|
|||
|
||||
def _populate(self):
|
||||
super(MenuList, self)._populate()
|
||||
self._viewport_top = 0
|
||||
# add back as first item
|
||||
self.insert_item(self._itemBack, 0)
|
||||
|
||||
def render_container(self, eventtime):
|
||||
rows = []
|
||||
selected_row = None
|
||||
def render_container(self, nrows, eventtime):
|
||||
manager = self.manager
|
||||
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:
|
||||
for row, item in enumerate(self):
|
||||
for row in range(self._viewport_top, self._viewport_top + nrows):
|
||||
s = ""
|
||||
selected = (row == self.selected)
|
||||
if selected:
|
||||
item.heartbeat(eventtime)
|
||||
selected_row = len(rows)
|
||||
name = str(item.render_name(selected))
|
||||
if isinstance(item, MenuList):
|
||||
s += name[:self.manager.cols-1].ljust(self.manager.cols-1)
|
||||
s += '>'
|
||||
else:
|
||||
s += name[:self.manager.cols].ljust(self.manager.cols)
|
||||
rows.append(s)
|
||||
if row < len(self):
|
||||
current = self[row]
|
||||
selected = (row == selected_row)
|
||||
if selected:
|
||||
current.heartbeat(eventtime)
|
||||
name = manager.stripliterals(
|
||||
manager.aslatin(current.render_name(selected)))
|
||||
if isinstance(current, MenuList):
|
||||
s += name[:manager.cols-1].ljust(manager.cols-1)
|
||||
s += '>'
|
||||
else:
|
||||
s += name
|
||||
lines.append(s[:manager.cols].ljust(manager.cols))
|
||||
except Exception:
|
||||
logging.exception('List rendering error')
|
||||
return ("\n".join(rows), selected_row)
|
||||
return lines
|
||||
|
||||
|
||||
class MenuVSDList(MenuList):
|
||||
|
@ -624,7 +639,6 @@ class MenuManager:
|
|||
self.menuitems = {}
|
||||
self.menustack = []
|
||||
self.children = {}
|
||||
self.top_row = 0
|
||||
self.display = display
|
||||
self.printer = config.get_printer()
|
||||
self.pconfig = self.printer.lookup_object('configfile')
|
||||
|
@ -682,7 +696,6 @@ class MenuManager:
|
|||
|
||||
def begin(self, eventtime):
|
||||
self.menustack = []
|
||||
self.top_row = 0
|
||||
self.timer = 0
|
||||
if isinstance(self.root, MenuContainer):
|
||||
# send begin event
|
||||
|
@ -780,21 +793,7 @@ class MenuManager:
|
|||
container = self.stack_peek()
|
||||
if self.running and isinstance(container, MenuContainer):
|
||||
container.heartbeat(eventtime)
|
||||
content, viewport_row = container.render_container(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))
|
||||
lines = container.render_container(self.rows, eventtime)
|
||||
return lines
|
||||
|
||||
def screen_update_event(self, eventtime):
|
||||
|
@ -884,6 +883,10 @@ class MenuManager:
|
|||
current = container.selected_item()
|
||||
if isinstance(current, MenuContainer):
|
||||
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):
|
||||
current.run_script('gcode', event=event)
|
||||
current.run_script(event)
|
||||
|
|
|
@ -13,10 +13,11 @@ BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000
|
|||
TextGlyphs = { 'right_arrow': '\x1a', 'degrees': '\xf8' }
|
||||
|
||||
class DisplayBase:
|
||||
def __init__(self, io, columns=128):
|
||||
def __init__(self, io, columns=128, x_offset=0):
|
||||
self.send = io.send
|
||||
# framebuffers
|
||||
self.columns = columns
|
||||
self.x_offset = x_offset
|
||||
self.vram = [bytearray(self.columns) for i in range(8)]
|
||||
self.all_framebuffers = [(self.vram[i], bytearray('~'*self.columns), i)
|
||||
for i in range(8)]
|
||||
|
@ -71,10 +72,11 @@ class DisplayBase:
|
|||
if x + len(data) > 16:
|
||||
data = data[:16 - min(x, 16)]
|
||||
pix_x = x * 8
|
||||
pix_x += self.x_offset
|
||||
page_top = self.vram[y * 2]
|
||||
page_bot = self.vram[y * 2 + 1]
|
||||
for c in data:
|
||||
bits_top, bits_bot = self.font[ord(c)]
|
||||
for c in bytearray(data):
|
||||
bits_top, bits_bot = self.font[c]
|
||||
page_top[pix_x:pix_x+8] = bits_top
|
||||
page_bot[pix_x:pix_x+8] = bits_bot
|
||||
pix_x += 8
|
||||
|
@ -83,6 +85,7 @@ class DisplayBase:
|
|||
return
|
||||
bits_top, bits_bot = self._swizzle_bits(data)
|
||||
pix_x = x * 8
|
||||
pix_x += self.x_offset
|
||||
page_top = self.vram[y * 2]
|
||||
page_bot = self.vram[y * 2 + 1]
|
||||
for i in range(8):
|
||||
|
@ -93,6 +96,7 @@ class DisplayBase:
|
|||
if icon is not None and x < 15:
|
||||
# Draw icon in graphics mode
|
||||
pix_x = x * 8
|
||||
pix_x += self.x_offset
|
||||
page_idx = y * 2
|
||||
self.vram[page_idx][pix_x:pix_x+16] = icon[0]
|
||||
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
|
||||
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)
|
||||
if cs_pin is None:
|
||||
io = I2C(config, 60)
|
||||
|
@ -201,7 +205,7 @@ class SSD1306(DisplayBase):
|
|||
io = SPI4wire(config, "dc_pin")
|
||||
io_bus = io.spi
|
||||
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.vcomh = config.getint('vcomh', 0, minval=0, maxval=63)
|
||||
self.invert = config.getboolean('invert', False)
|
||||
|
@ -232,4 +236,5 @@ class SSD1306(DisplayBase):
|
|||
# the SH1106 is SSD1306 compatible with up to 132 columns
|
||||
class SH1106(SSD1306):
|
||||
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.
|
||||
import math, logging
|
||||
import homing
|
||||
|
||||
TRINAMIC_DRIVERS = ["tmc2130", "tmc2208", "tmc2209", "tmc2660", "tmc5160"]
|
||||
|
||||
|
@ -81,7 +80,7 @@ class EndstopPhase:
|
|||
except Exception as e:
|
||||
msg = "Unable to get stepper %s phase: %s" % (self.name, str(e))
|
||||
logging.exception(msg)
|
||||
raise homing.EndstopError(msg)
|
||||
raise self.printer.command_error(msg)
|
||||
if stepper.is_dir_inverted():
|
||||
phase = (self.phases - 1) - phase
|
||||
else:
|
||||
|
@ -95,7 +94,7 @@ class EndstopPhase:
|
|||
if delta >= self.phases - self.endstop_phase_accuracy:
|
||||
delta -= self.phases
|
||||
elif delta > self.endstop_phase_accuracy:
|
||||
raise homing.EndstopError(
|
||||
raise self.printer.command_error(
|
||||
"Endstop %s incorrect phase (got %d vs %d)" % (
|
||||
self.name, phase, self.endstop_phase))
|
||||
return delta * self.step_dist
|
||||
|
|
|
@ -12,7 +12,17 @@ class GCodeButton:
|
|||
self.pin = config.get('pin')
|
||||
self.last_state = 0
|
||||
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')
|
||||
self.press_template = gcode_macro.load_template(config, 'press_gcode')
|
||||
self.release_template = gcode_macro.load_template(config,
|
||||
|
|
|
@ -74,10 +74,10 @@ class Heater:
|
|||
pwm_time = read_time + self.pwm_delay
|
||||
self.next_pwm_time = pwm_time + 0.75 * MAX_HEAT_TIME
|
||||
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)
|
||||
#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):
|
||||
with self.lock:
|
||||
time_diff = read_time - self.last_temp_time
|
||||
|
|
|
@ -124,7 +124,7 @@ class InputShaper:
|
|||
self.shapers.keys()[
|
||||
self.shapers.values().index(shaper_type_x)]
|
||||
, self.shapers.keys()[
|
||||
self.shapers.values().index(shaper_type_x)]
|
||||
self.shapers.values().index(shaper_type_y)]
|
||||
, shaper_freq_x, shaper_freq_y
|
||||
, damping_ratio_x, damping_ratio_y))
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import logging, bisect
|
||||
import homing
|
||||
|
||||
class ManualProbe:
|
||||
def __init__(self, config):
|
||||
|
@ -97,7 +96,7 @@ class ManualProbeHelper:
|
|||
if curpos[2] < z_bob_pos:
|
||||
self.toolhead.manual_move([None, None, z_bob_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)
|
||||
raise
|
||||
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>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import stepper, homing, chelper
|
||||
import stepper, chelper
|
||||
from . import force_move
|
||||
|
||||
ENDSTOP_SAMPLE_TIME = .000015
|
||||
|
@ -110,7 +110,7 @@ class ManualStepper:
|
|||
error = str(e)
|
||||
self.sync_print_time()
|
||||
if error is not None:
|
||||
raise homing.CommandError(error)
|
||||
raise self.printer.command_error(error)
|
||||
cmd_MANUAL_STEPPER_help = "Command a manually configured stepper"
|
||||
def cmd_MANUAL_STEPPER(self, gcmd):
|
||||
enable = gcmd.get_int('ENABLE', None)
|
||||
|
|
|
@ -130,7 +130,7 @@ class ControlAutoTune:
|
|||
def calc_final_pid(self):
|
||||
cycle_times = [(self.peaks[pos][1] - self.peaks[pos-2][1], pos)
|
||||
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)
|
||||
# Offline analysis helper
|
||||
def write_file(self, filename):
|
||||
|
|
|
@ -8,10 +8,9 @@ import pins, homing
|
|||
from . import manual_probe
|
||||
|
||||
HINT_TIMEOUT = """
|
||||
Make sure to home the printer before probing. If the probe
|
||||
did not move far enough to trigger, then consider reducing
|
||||
the Z axis minimum position so the probe can travel further
|
||||
(the Z minimum position can be negative).
|
||||
If the probe did not move far enough to trigger, then
|
||||
consider reducing the Z axis minimum position so the probe
|
||||
can travel further (the Z minimum position can be negative).
|
||||
"""
|
||||
|
||||
class PrinterProbe:
|
||||
|
@ -26,6 +25,7 @@ class PrinterProbe:
|
|||
self.z_offset = config.getfloat('z_offset')
|
||||
self.probe_calibrate_z = 0.
|
||||
self.multi_probe_pending = False
|
||||
self.last_state = False
|
||||
# Infer Z position to move to during a probe
|
||||
if config.has_section('stepper_z'):
|
||||
zconfig = config.getsection('stepper_z')
|
||||
|
@ -107,6 +107,9 @@ class PrinterProbe:
|
|||
return self.x_offset, self.y_offset, self.z_offset
|
||||
def _probe(self, speed):
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
curtime = self.printer.get_reactor().monotonic()
|
||||
if 'z' not in toolhead.get_status(curtime)['homed_axes']:
|
||||
raise self.printer.command_error("Must home before probe")
|
||||
homing_state = homing.Homing(self.printer)
|
||||
pos = toolhead.get_position()
|
||||
pos[2] = self.z_position
|
||||
|
@ -115,11 +118,11 @@ class PrinterProbe:
|
|||
try:
|
||||
homing_state.homing_move(pos, endstops, speed,
|
||||
probe_pos=True, verify_movement=verify)
|
||||
except homing.CommandError as e:
|
||||
except self.printer.command_error as e:
|
||||
reason = str(e)
|
||||
if "Timeout during endstop homing" in reason:
|
||||
reason += HINT_TIMEOUT
|
||||
raise homing.CommandError(reason)
|
||||
raise self.printer.command_error(reason)
|
||||
pos = toolhead.get_position()
|
||||
self.gcode.respond_info("probe at %.3f,%.3f is z=%.6f"
|
||||
% (pos[0], pos[1], pos[2]))
|
||||
|
@ -162,8 +165,7 @@ class PrinterProbe:
|
|||
z_positions = [p[2] for p in positions]
|
||||
if max(z_positions) - min(z_positions) > samples_tolerance:
|
||||
if retries >= samples_retries:
|
||||
raise homing.CommandError(
|
||||
"Probe samples exceed samples_tolerance")
|
||||
raise gcmd.error("Probe samples exceed samples_tolerance")
|
||||
gcmd.respond_info("Probe samples exceed tolerance. Retrying...")
|
||||
retries += 1
|
||||
positions = []
|
||||
|
@ -186,7 +188,10 @@ class PrinterProbe:
|
|||
toolhead = self.printer.lookup_object('toolhead')
|
||||
print_time = toolhead.get_last_move_time()
|
||||
res = self.mcu_probe.query_endstop(print_time)
|
||||
self.last_state = res
|
||||
gcmd.respond_info("probe: %s" % (["open", "TRIGGERED"][not not res],))
|
||||
def get_status(self, eventtime):
|
||||
return {'last_query': self.last_state}
|
||||
cmd_PROBE_ACCURACY_help = "Probe Z-height accuracy at current XY position"
|
||||
def cmd_PROBE_ACCURACY(self, gcmd):
|
||||
speed = gcmd.get_float("PROBE_SPEED", self.speed, above=0.)
|
||||
|
@ -295,14 +300,14 @@ class ProbeEndstopWrapper:
|
|||
start_pos = toolhead.get_position()
|
||||
self.activate_gcode.run_gcode_from_command()
|
||||
if toolhead.get_position()[:3] != start_pos[:3]:
|
||||
raise homing.CommandError(
|
||||
raise self.printer.command_error(
|
||||
"Toolhead moved during probe activate_gcode script")
|
||||
def probe_finish(self):
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
start_pos = toolhead.get_position()
|
||||
self.deactivate_gcode.run_gcode_from_command()
|
||||
if toolhead.get_position()[:3] != start_pos[:3]:
|
||||
raise homing.CommandError(
|
||||
raise self.printer.command_error(
|
||||
"Toolhead moved during probe deactivate_gcode script")
|
||||
def get_position_endstop(self):
|
||||
return self.position_endstop
|
||||
|
@ -337,6 +342,9 @@ class ProbePointsHelper:
|
|||
if len(self.probe_points) < n:
|
||||
raise self.printer.config_error(
|
||||
"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):
|
||||
self.use_offsets = use_offsets
|
||||
def get_lift_speed(self):
|
||||
|
|
|
@ -6,6 +6,22 @@
|
|||
import logging
|
||||
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:
|
||||
def __init__(self, config):
|
||||
self.printer = config.get_printer()
|
||||
|
@ -48,29 +64,36 @@ class QuadGantryLevel:
|
|||
" ".join(["%s: %.6f" % (z_id, z_positions[z_id])
|
||||
for z_id in range(len(z_positions))]))
|
||||
self.gcode.respond_info(points_message)
|
||||
p1 = [positions[0][0] + offsets[0],z_positions[0]]
|
||||
p2 = [positions[1][0] + offsets[0],z_positions[1]]
|
||||
p3 = [positions[2][0] + offsets[0],z_positions[2]]
|
||||
p4 = [positions[3][0] + offsets[0],z_positions[3]]
|
||||
f1 = self.linefit(p1,p4)
|
||||
f2 = self.linefit(p2,p3)
|
||||
logging.info("quad_gantry_level f1: %s, f2: %s" % (f1,f2))
|
||||
# Calculate slope along X axis between probe point 0 and 3
|
||||
ppx0 = [positions[0][0] + offsets[0], z_positions[0]]
|
||||
ppx3 = [positions[3][0] + offsets[0], z_positions[3]]
|
||||
slope_x_pp03 = self.linefit(ppx0, ppx3)
|
||||
# Calculate slope along X axis between probe point 1 and 2
|
||||
ppx1 = [positions[1][0] + offsets[0], z_positions[1]]
|
||||
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],
|
||||
self.plot(f1,self.gantry_corners[0][0])]
|
||||
self.plot(slope_x_pp03, self.gantry_corners[0][0])]
|
||||
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],
|
||||
self.plot(f1,self.gantry_corners[1][0])]
|
||||
self.plot(slope_x_pp03, self.gantry_corners[1][0])]
|
||||
b2 = [positions[1][1] + offsets[1],
|
||||
self.plot(f2,self.gantry_corners[1][0])]
|
||||
af = self.linefit(a1,a2)
|
||||
bf = self.linefit(b1,b2)
|
||||
logging.info("quad_gantry_level af: %s, bf: %s" % (af,bf))
|
||||
self.plot(slope_x_pp12, self.gantry_corners[1][0])]
|
||||
slope_y_s23 = self.linefit(b1, b2)
|
||||
logging.info("quad_gantry_level af: %s, bf: %s"
|
||||
% (slope_y_s01, slope_y_s23))
|
||||
# Calculate z height of each stepper
|
||||
z_height = [0,0,0,0]
|
||||
z_height[0] = self.plot(af,self.gantry_corners[0][1])
|
||||
z_height[1] = self.plot(af,self.gantry_corners[1][1])
|
||||
z_height[2] = self.plot(bf,self.gantry_corners[1][1])
|
||||
z_height[3] = self.plot(bf,self.gantry_corners[0][1])
|
||||
z_height[0] = self.plot(slope_y_s01, self.gantry_corners[0][1])
|
||||
z_height[1] = self.plot(slope_y_s01, self.gantry_corners[1][1])
|
||||
z_height[2] = self.plot(slope_y_s23, self.gantry_corners[1][1])
|
||||
z_height[3] = self.plot(slope_y_s23, self.gantry_corners[0][1])
|
||||
|
||||
ainfo = zip(["z","z1","z2","z3"], z_height[0:4])
|
||||
apos = " ".join(["%s: %06f" % (x) for x in ainfo])
|
||||
|
|
|
@ -267,15 +267,18 @@ MAX31865_FAULT_REFINHIGH = 0x10
|
|||
MAX31865_FAULT_RTDINLOW = 0x08
|
||||
MAX31865_FAULT_OVUV = 0x04
|
||||
|
||||
VAL_A = 0.00390830
|
||||
VAL_B = 0.0000005775
|
||||
VAL_C = -0.00000000000418301
|
||||
VAL_ADC_MAX = 32768.0 # 2^15
|
||||
MAX31865_ADC_MAX = 1<<15
|
||||
|
||||
# Callendar-Van Dusen constants for platinum resistance thermometers (RTD)
|
||||
CVD_A = 3.9083e-3
|
||||
CVD_B = -5.775e-7
|
||||
|
||||
class MAX31865(SensorBase):
|
||||
def __init__(self, config):
|
||||
self.rtd_nominal_r = config.getint('rtd_nominal_r', 100)
|
||||
self.reference_r = config.getfloat('rtd_reference_r', 430., above=0.)
|
||||
rtd_nominal_r = config.getfloat('rtd_nominal_r', 100., 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",
|
||||
self.build_spi_init(config))
|
||||
def calc_temp(self, adc, fault):
|
||||
|
@ -295,20 +298,20 @@ class MAX31865(SensorBase):
|
|||
if fault & 0x03:
|
||||
self.fault("Max31865 Unspecified error")
|
||||
adc = adc >> 1 # remove fault bit
|
||||
R_rtd = (self.reference_r * adc) / VAL_ADC_MAX
|
||||
temp = ((( ( -1 * self.rtd_nominal_r ) * VAL_A )
|
||||
+ math.sqrt( ( self.rtd_nominal_r**2 * VAL_A * VAL_A )
|
||||
- ( 4 * self.rtd_nominal_r * VAL_B
|
||||
* ( self.rtd_nominal_r - R_rtd ) )))
|
||||
/ (2 * self.rtd_nominal_r * VAL_B))
|
||||
R_div_nominal = adc * self.adc_to_resist_div_nominal
|
||||
# Resistance (relative to rtd_nominal_r) is calculated using:
|
||||
# R_div_nominal = 1. + CVD_A * temp + CVD_B * temp**2
|
||||
# Solve for temp using quadratic equation:
|
||||
# temp = (-b +- sqrt(b**2 - 4ac)) / 2a
|
||||
discriminant = math.sqrt(CVD_A**2 - 4. * CVD_B * (1. - R_div_nominal))
|
||||
temp = (-CVD_A + discriminant) / (2. * CVD_B)
|
||||
return temp
|
||||
def calc_adc(self, temp):
|
||||
R_rtd = temp * ( 2 * self.rtd_nominal_r * VAL_B )
|
||||
R_rtd = math.pow( ( R_rtd + ( self.rtd_nominal_r * VAL_A ) ), 2)
|
||||
R_rtd = -1 * ( R_rtd - (self.rtd_nominal_r**2 * VAL_A * VAL_A ) )
|
||||
R_rtd = R_rtd / ( 4 * self.rtd_nominal_r * VAL_B )
|
||||
R_rtd = ( -1 * R_rtd ) + self.rtd_nominal_r
|
||||
adc = int( ( ( R_rtd * VAL_ADC_MAX ) / self.reference_r) + 0.5 )
|
||||
# Calculate relative resistance via Callendar-Van Dusen formula:
|
||||
# resistance = rtd_nominal_r * (1 + CVD_A * temp + CVD_B * temp**2)
|
||||
R_div_nominal = 1. + CVD_A * temp + CVD_B * temp * temp
|
||||
adc = int(R_div_nominal / self.adc_to_resist_div_nominal + 0.5)
|
||||
adc = max(0, min(MAX31865_ADC_MAX, adc))
|
||||
adc = adc << 1 # Add fault bit
|
||||
return adc
|
||||
def build_spi_init(self, config):
|
||||
|
|
|
@ -251,7 +251,9 @@ class GCodeDispatch:
|
|||
raise gcmd.error(self.printer.get_state_message()[0])
|
||||
return
|
||||
if not cmd:
|
||||
logging.debug(gcmd.get_commandline())
|
||||
cmdline = gcmd.get_commandline()
|
||||
if cmdline:
|
||||
logging.debug(cmdline)
|
||||
return
|
||||
if cmd.startswith("M117 "):
|
||||
# Handle M117 gcode with numeric and special characters
|
||||
|
|
|
@ -101,8 +101,8 @@ class Homing:
|
|||
for s, name, spos, epos in end_mcu_pos:
|
||||
if spos == epos:
|
||||
if probe_pos:
|
||||
raise EndstopError("Probe triggered prior to movement")
|
||||
raise EndstopError(
|
||||
raise CommandError("Probe triggered prior to movement")
|
||||
raise CommandError(
|
||||
"Endstop %s still triggered after retract" % (name,))
|
||||
def home_rails(self, rails, forcepos, movepos):
|
||||
# Notify of upcoming homing operation
|
||||
|
@ -161,11 +161,4 @@ def multi_complete(printer, completions):
|
|||
class CommandError(Exception):
|
||||
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'))
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import logging
|
||||
import stepper, homing
|
||||
import stepper
|
||||
|
||||
class CartKinematics:
|
||||
def __init__(self, toolhead, config):
|
||||
|
@ -100,9 +100,8 @@ class CartKinematics:
|
|||
and (end_pos[i] < self.limits[i][0]
|
||||
or end_pos[i] > self.limits[i][1])):
|
||||
if self.limits[i][0] > self.limits[i][1]:
|
||||
raise homing.EndstopMoveError(
|
||||
end_pos, "Must home axis first")
|
||||
raise homing.EndstopMoveError(end_pos)
|
||||
raise move.move_error("Must home axis first")
|
||||
raise move.move_error()
|
||||
def check_move(self, move):
|
||||
limits = self.limits
|
||||
xpos, ypos = move.end_pos[:2]
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import logging, math
|
||||
import stepper, homing
|
||||
import stepper
|
||||
|
||||
class CoreXYKinematics:
|
||||
def __init__(self, toolhead, config):
|
||||
|
@ -77,9 +77,8 @@ class CoreXYKinematics:
|
|||
and (end_pos[i] < self.limits[i][0]
|
||||
or end_pos[i] > self.limits[i][1])):
|
||||
if self.limits[i][0] > self.limits[i][1]:
|
||||
raise homing.EndstopMoveError(
|
||||
end_pos, "Must home axis first")
|
||||
raise homing.EndstopMoveError(end_pos)
|
||||
raise move.move_error("Must home axis first")
|
||||
raise move.move_error()
|
||||
def check_move(self, move):
|
||||
limits = self.limits
|
||||
xpos, ypos = move.end_pos[:2]
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import logging, math
|
||||
import stepper, homing
|
||||
import stepper
|
||||
|
||||
class CoreXZKinematics:
|
||||
def __init__(self, toolhead, config):
|
||||
|
@ -76,9 +76,8 @@ class CoreXZKinematics:
|
|||
and (end_pos[i] < self.limits[i][0]
|
||||
or end_pos[i] > self.limits[i][1])):
|
||||
if self.limits[i][0] > self.limits[i][1]:
|
||||
raise homing.EndstopMoveError(
|
||||
end_pos, "Must home axis first")
|
||||
raise homing.EndstopMoveError(end_pos)
|
||||
raise move.move_error("Must home axis first")
|
||||
raise move.move_error()
|
||||
def check_move(self, move):
|
||||
limits = self.limits
|
||||
xpos, ypos = move.end_pos[:2]
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import math, logging
|
||||
import stepper, homing, mathutil
|
||||
import stepper, mathutil
|
||||
|
||||
# Slow moves once the ratio of tower to XY movement exceeds SLOW_RATIO
|
||||
SLOW_RATIO = 3.
|
||||
|
@ -118,7 +118,7 @@ class DeltaKinematics:
|
|||
# Normal XY move
|
||||
return
|
||||
if self.need_home:
|
||||
raise homing.EndstopMoveError(end_pos, "Must home first")
|
||||
raise move.move_error("Must home first")
|
||||
end_z = end_pos[2]
|
||||
limit_xy2 = self.max_xy2
|
||||
if end_z > self.limit_z:
|
||||
|
@ -127,7 +127,7 @@ class DeltaKinematics:
|
|||
# Move out of range - verify not a homing move
|
||||
if (end_pos[:2] != 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.
|
||||
if move.axes_d[2]:
|
||||
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.
|
||||
import math, logging
|
||||
import stepper, homing, chelper
|
||||
import stepper, chelper
|
||||
|
||||
class PrinterExtruder:
|
||||
def __init__(self, config, extruder_num):
|
||||
|
@ -107,13 +107,13 @@ class PrinterExtruder:
|
|||
def check_move(self, move):
|
||||
axis_r = move.axes_r[3]
|
||||
if not self.heater.can_extrude:
|
||||
raise homing.EndstopError(
|
||||
raise self.printer.command_error(
|
||||
"Extrude below minimum temp\n"
|
||||
"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.:
|
||||
# Extrude only move (or retraction move) - limit accel and velocity
|
||||
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"
|
||||
"See the 'max_extrude_only_distance' config"
|
||||
" option for details" % (move.axes_d[3], self.max_e_dist))
|
||||
|
@ -127,7 +127,7 @@ class PrinterExtruder:
|
|||
area = axis_r * self.filament_area
|
||||
logging.debug("Overextrude: %s vs %s (area=%.3f dist=%.3f)",
|
||||
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"
|
||||
"See the 'max_extrude_cross_section' config option for details"
|
||||
% (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
|
||||
class DummyExtruder:
|
||||
def __init__(self, printer):
|
||||
self.printer = printer
|
||||
def update_move_time(self, flush_time):
|
||||
pass
|
||||
def check_move(self, move):
|
||||
raise homing.EndstopMoveError(
|
||||
move.end_pos, "Extrude when no extruder present")
|
||||
raise move.move_error("Extrude when no extruder present")
|
||||
def calc_junction(self, prev_move, move):
|
||||
return move.max_cruise_v2
|
||||
def get_name(self):
|
||||
return ""
|
||||
def get_heater(self):
|
||||
raise homing.CommandError("Extruder not configured")
|
||||
raise self.printer.command_error("Extruder not configured")
|
||||
|
||||
def add_printer_objects(config):
|
||||
printer = config.get_printer()
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import logging, math
|
||||
import stepper, homing
|
||||
import stepper
|
||||
|
||||
class PolarKinematics:
|
||||
def __init__(self, toolhead, config):
|
||||
|
@ -94,18 +94,17 @@ class PolarKinematics:
|
|||
xy2 = end_pos[0]**2 + end_pos[1]**2
|
||||
if xy2 > self.limit_xy2:
|
||||
if self.limit_xy2 < 0.:
|
||||
raise homing.EndstopMoveError(end_pos, "Must home axis first")
|
||||
raise homing.EndstopMoveError(end_pos)
|
||||
raise move.move_error("Must home axis first")
|
||||
raise move.move_error()
|
||||
if move.axes_d[2]:
|
||||
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]:
|
||||
raise homing.EndstopMoveError(
|
||||
end_pos, "Must home axis first")
|
||||
raise homing.EndstopMoveError(end_pos)
|
||||
raise move.move_error("Must home axis first")
|
||||
raise move.move_error()
|
||||
# Move with Z - update velocity and accel for slower Z axis
|
||||
z_ratio = move.move_d / abs(move.axes_d[2])
|
||||
move.limit_speed(
|
||||
self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio)
|
||||
move.limit_speed(self.max_z_velocity * z_ratio,
|
||||
self.max_z_accel * z_ratio)
|
||||
def get_status(self, eventtime):
|
||||
xy_home = "xy" if self.limit_xy2 >= 0. 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.
|
||||
import math, logging
|
||||
import stepper, homing, mathutil, chelper
|
||||
import stepper, mathutil, chelper
|
||||
|
||||
class RotaryDeltaKinematics:
|
||||
def __init__(self, toolhead, config):
|
||||
|
@ -106,7 +106,7 @@ class RotaryDeltaKinematics:
|
|||
# Normal XY move
|
||||
return
|
||||
if self.need_home:
|
||||
raise homing.EndstopMoveError(end_pos, "Must home first")
|
||||
raise move.move_error("Must home first")
|
||||
end_z = end_pos[2]
|
||||
limit_xy2 = self.max_xy2
|
||||
if end_z > self.limit_z:
|
||||
|
@ -115,7 +115,7 @@ class RotaryDeltaKinematics:
|
|||
# Move out of range - verify not a homing move
|
||||
if (end_pos[:2] != 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.
|
||||
if move.axes_d[2]:
|
||||
move.limit_speed(self.max_z_velocity, move.accel)
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
#!/usr/bin/env python2
|
||||
# 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.
|
||||
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 gcode, configfile, pins, mcu, toolhead, webhooks
|
||||
|
||||
|
@ -48,10 +48,10 @@ Printer is shutdown
|
|||
class Printer:
|
||||
config_error = configfile.error
|
||||
command_error = homing.CommandError
|
||||
def __init__(self, bglogger, start_args):
|
||||
def __init__(self, main_reactor, bglogger, start_args):
|
||||
self.bglogger = bglogger
|
||||
self.start_args = start_args
|
||||
self.reactor = reactor.Reactor()
|
||||
self.reactor = main_reactor
|
||||
self.reactor.register_callback(self._connect)
|
||||
self.state_message = message_startup
|
||||
self.in_shutdown_state = False
|
||||
|
@ -165,8 +165,8 @@ class Printer:
|
|||
return
|
||||
except Exception as e:
|
||||
logging.exception("Unhandled exception during connect")
|
||||
self._set_state("Internal error during connect: %s\n%s" % (
|
||||
str(e), message_restart,))
|
||||
self._set_state("Internal error during connect: %s\n%s"
|
||||
% (str(e), message_restart,))
|
||||
return
|
||||
try:
|
||||
self._set_state(message_ready)
|
||||
|
@ -176,8 +176,8 @@ class Printer:
|
|||
cb()
|
||||
except Exception as e:
|
||||
logging.exception("Unhandled exception during ready callback")
|
||||
self.invoke_shutdown("Internal error during ready callback: %s" % (
|
||||
str(e),))
|
||||
self.invoke_shutdown("Internal error during ready callback: %s"
|
||||
% (str(e),))
|
||||
def run(self):
|
||||
systime = time.time()
|
||||
monotime = self.reactor.monotonic()
|
||||
|
@ -187,8 +187,17 @@ class Printer:
|
|||
try:
|
||||
self.reactor.run()
|
||||
except:
|
||||
logging.exception("Unhandled exception during run")
|
||||
return "error_exit"
|
||||
msg = "Unhandled exception during run"
|
||||
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
|
||||
run_result = self.run_result
|
||||
try:
|
||||
|
@ -215,6 +224,8 @@ class Printer:
|
|||
cb()
|
||||
except:
|
||||
logging.exception("Exception during shutdown handler")
|
||||
logging.info("Reactor garbage collection: %s",
|
||||
self.reactor.get_gc_stats())
|
||||
def invoke_async_shutdown(self, msg):
|
||||
self.reactor.register_async_callback(
|
||||
(lambda e: self.invoke_shutdown(msg)))
|
||||
|
@ -297,17 +308,22 @@ def main():
|
|||
elif not options.debugoutput:
|
||||
logging.warning("No log file specified!"
|
||||
" Severe timing issues may result!")
|
||||
gc.disable()
|
||||
|
||||
# Start Printer() class
|
||||
while 1:
|
||||
if bglogger is not None:
|
||||
bglogger.clear_rollover_info()
|
||||
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()
|
||||
if res in ['exit', 'error_exit']:
|
||||
break
|
||||
time.sleep(1.)
|
||||
main_reactor.finalize()
|
||||
main_reactor = printer = None
|
||||
logging.info("Restarting printer")
|
||||
start_args['start_reason'] = res
|
||||
|
||||
|
|
|
@ -213,10 +213,13 @@ class MCU_pwm:
|
|||
self._start_value * self._pwm_max,
|
||||
self._shutdown_value * self._pwm_max,
|
||||
self._mcu.seconds_to_clock(self._max_duration)))
|
||||
self._mcu.add_config_cmd(
|
||||
"set_pwm_out pin=%s cycle_ticks=%d value=%d"
|
||||
% (self._pin, cycle_ticks, self._start_value * self._pwm_max),
|
||||
on_restart=True)
|
||||
curtime = self._mcu.get_printer().get_reactor().monotonic()
|
||||
printtime = self._mcu.estimated_print_time(curtime)
|
||||
self._last_clock = self._mcu.print_time_to_clock(printtime + 0.100)
|
||||
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(
|
||||
"schedule_pwm_out oid=%c clock=%u value=%hu", cq=cmd_queue)
|
||||
return
|
||||
|
@ -427,8 +430,8 @@ class MCU:
|
|||
self._mcu_freq = 0.
|
||||
# Move command queuing
|
||||
ffi_main, self._ffi_lib = chelper.get_ffi()
|
||||
self._max_stepper_error = config.getfloat(
|
||||
'max_stepper_error', 0.000025, minval=0.)
|
||||
self._max_stepper_error = config.getfloat('max_stepper_error', 0.000025,
|
||||
minval=0.)
|
||||
self._stepqueues = []
|
||||
self._steppersync = None
|
||||
# Stats
|
||||
|
@ -563,17 +566,19 @@ class MCU:
|
|||
else:
|
||||
start_reason = self._printer.get_start_args().get("start_reason")
|
||||
if start_reason == 'firmware_restart':
|
||||
raise error("Failed automated reset of MCU '%s'" % (
|
||||
self._name,))
|
||||
raise error("Failed automated reset of MCU '%s'"
|
||||
% (self._name,))
|
||||
# Already configured - send init commands
|
||||
self._send_config(config_params['crc'])
|
||||
# Setup steppersync with the move_count returned by get_config
|
||||
move_count = config_params['move_count']
|
||||
self._steppersync = self._ffi_lib.steppersync_alloc(
|
||||
self._serial.serialqueue, self._stepqueues, len(self._stepqueues),
|
||||
move_count)
|
||||
self._ffi_lib.steppersync_set_time(
|
||||
self._steppersync, 0., self._mcu_freq)
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
self._steppersync = ffi_main.gc(
|
||||
ffi_lib.steppersync_alloc(self._serial.serialqueue,
|
||||
self._stepqueues, len(self._stepqueues),
|
||||
move_count),
|
||||
ffi_lib.steppersync_free)
|
||||
ffi_lib.steppersync_set_time(self._steppersync, 0., self._mcu_freq)
|
||||
# Log config information
|
||||
move_msg = "Configured MCU '%s' (%d moves)" % (self._name, move_count)
|
||||
logging.info(move_msg)
|
||||
|
@ -679,9 +684,7 @@ class MCU:
|
|||
# Restarts
|
||||
def _disconnect(self):
|
||||
self._serial.disconnect()
|
||||
if self._steppersync is not None:
|
||||
self._ffi_lib.steppersync_free(self._steppersync)
|
||||
self._steppersync = None
|
||||
self._steppersync = None
|
||||
def _shutdown(self, force=False):
|
||||
if (self._emergency_stop_cmd is None
|
||||
or (self._is_shutdown and not force)):
|
||||
|
@ -742,8 +745,8 @@ class MCU:
|
|||
return
|
||||
ret = self._ffi_lib.steppersync_flush(self._steppersync, clock)
|
||||
if ret:
|
||||
raise error("Internal error in MCU '%s' stepcompress" % (
|
||||
self._name,))
|
||||
raise error("Internal error in MCU '%s' stepcompress"
|
||||
% (self._name,))
|
||||
def check_active(self, print_time, eventtime):
|
||||
if self._steppersync is None:
|
||||
return
|
||||
|
@ -763,8 +766,6 @@ class MCU:
|
|||
self._mcu_tick_stddev)
|
||||
return False, ' '.join([msg, self._serial.stats(eventtime),
|
||||
self._clocksync.stats(eventtime)])
|
||||
def __del__(self):
|
||||
self._disconnect()
|
||||
|
||||
Common_MCU_errors = {
|
||||
("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_thread.join()
|
||||
def set_rollover_info(self, name, info):
|
||||
if info is None:
|
||||
self.rollover_info.pop(name, None)
|
||||
return
|
||||
self.rollover_info[name] = info
|
||||
def clear_rollover_info(self):
|
||||
self.rollover_info.clear()
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
# 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.
|
||||
import os, select, math, time, Queue as queue
|
||||
import os, gc, select, math, time, logging, Queue as queue
|
||||
import greenlet
|
||||
import chelper, util
|
||||
|
||||
|
@ -93,10 +93,13 @@ class ReactorMutex:
|
|||
class SelectReactor:
|
||||
NOW = _NOW
|
||||
NEVER = _NEVER
|
||||
def __init__(self):
|
||||
def __init__(self, gc_checking=False):
|
||||
# Main code
|
||||
self._process = False
|
||||
self.monotonic = chelper.get_ffi()[1].get_monotonic
|
||||
# Python garbage collection
|
||||
self._check_gc = gc_checking
|
||||
self._last_gc_times = [0., 0., 0.]
|
||||
# Timers
|
||||
self._timers = []
|
||||
self._next_timer = self.NEVER
|
||||
|
@ -108,6 +111,9 @@ class SelectReactor:
|
|||
# Greenlets
|
||||
self._g_dispatch = None
|
||||
self._greenlets = []
|
||||
self._all_greenlets = []
|
||||
def get_gc_stats(self):
|
||||
return tuple(self._last_gc_times)
|
||||
# Timers
|
||||
def update_timer(self, timer_handler, waketime):
|
||||
timer_handler.waketime = waketime
|
||||
|
@ -124,8 +130,22 @@ class SelectReactor:
|
|||
timers = list(self._timers)
|
||||
timers.pop(timers.index(timer_handler))
|
||||
self._timers = timers
|
||||
def _check_timers(self, eventtime):
|
||||
def _check_timers(self, eventtime, busy):
|
||||
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))
|
||||
self._next_timer = self.NEVER
|
||||
g_dispatch = self._g_dispatch
|
||||
|
@ -139,9 +159,7 @@ class SelectReactor:
|
|||
self._end_greenlet(g_dispatch)
|
||||
return 0.
|
||||
self._next_timer = min(self._next_timer, waketime)
|
||||
if eventtime >= self._next_timer:
|
||||
return 0.
|
||||
return min(1., max(.001, self._next_timer - self.monotonic()))
|
||||
return 0.
|
||||
# Callbacks and Completions
|
||||
def completion(self):
|
||||
return ReactorCompletion(self)
|
||||
|
@ -178,11 +196,6 @@ class SelectReactor:
|
|||
util.set_nonblock(self._pipe_fds[0])
|
||||
util.set_nonblock(self._pipe_fds[1])
|
||||
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
|
||||
def _sys_pause(self, waketime):
|
||||
# Pause using system sleep for when reactor not running
|
||||
|
@ -202,6 +215,7 @@ class SelectReactor:
|
|||
g_next = self._greenlets.pop()
|
||||
else:
|
||||
g_next = ReactorGreenlet(run=self._dispatch_loop)
|
||||
self._all_greenlets.append(g_next)
|
||||
g_next.parent = g.parent
|
||||
g.timer = self.register_timer(g.switch, waketime)
|
||||
self._next_timer = self.NOW
|
||||
|
@ -231,12 +245,15 @@ class SelectReactor:
|
|||
# Main loop
|
||||
def _dispatch_loop(self):
|
||||
self._g_dispatch = g_dispatch = greenlet.getcurrent()
|
||||
busy = True
|
||||
eventtime = self.monotonic()
|
||||
while self._process:
|
||||
timeout = self._check_timers(eventtime)
|
||||
timeout = self._check_timers(eventtime, busy)
|
||||
busy = False
|
||||
res = select.select(self._fds, [], [], timeout)
|
||||
eventtime = self.monotonic()
|
||||
for fd in res[0]:
|
||||
busy = True
|
||||
fd.callback(eventtime)
|
||||
if g_dispatch is not self._g_dispatch:
|
||||
self._end_greenlet(g_dispatch)
|
||||
|
@ -248,13 +265,27 @@ class SelectReactor:
|
|||
self._setup_async_callbacks()
|
||||
self._process = True
|
||||
g_next = ReactorGreenlet(run=self._dispatch_loop)
|
||||
self._all_greenlets.append(g_next)
|
||||
g_next.switch()
|
||||
def end(self):
|
||||
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):
|
||||
def __init__(self):
|
||||
SelectReactor.__init__(self)
|
||||
def __init__(self, gc_checking=False):
|
||||
SelectReactor.__init__(self, gc_checking)
|
||||
self._poll = select.poll()
|
||||
self._fds = {}
|
||||
# File descriptors
|
||||
|
@ -273,12 +304,15 @@ class PollReactor(SelectReactor):
|
|||
# Main loop
|
||||
def _dispatch_loop(self):
|
||||
self._g_dispatch = g_dispatch = greenlet.getcurrent()
|
||||
busy = True
|
||||
eventtime = self.monotonic()
|
||||
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.)))
|
||||
eventtime = self.monotonic()
|
||||
for fd, event in res:
|
||||
busy = True
|
||||
self._fds[fd](eventtime)
|
||||
if g_dispatch is not self._g_dispatch:
|
||||
self._end_greenlet(g_dispatch)
|
||||
|
@ -287,8 +321,8 @@ class PollReactor(SelectReactor):
|
|||
self._g_dispatch = None
|
||||
|
||||
class EPollReactor(SelectReactor):
|
||||
def __init__(self):
|
||||
SelectReactor.__init__(self)
|
||||
def __init__(self, gc_checking=False):
|
||||
SelectReactor.__init__(self, gc_checking)
|
||||
self._epoll = select.epoll()
|
||||
self._fds = {}
|
||||
# File descriptors
|
||||
|
@ -307,12 +341,15 @@ class EPollReactor(SelectReactor):
|
|||
# Main loop
|
||||
def _dispatch_loop(self):
|
||||
self._g_dispatch = g_dispatch = greenlet.getcurrent()
|
||||
busy = True
|
||||
eventtime = self.monotonic()
|
||||
while self._process:
|
||||
timeout = self._check_timers(eventtime)
|
||||
timeout = self._check_timers(eventtime, busy)
|
||||
busy = False
|
||||
res = self._epoll.poll(timeout)
|
||||
eventtime = self.monotonic()
|
||||
for fd, event in res:
|
||||
busy = True
|
||||
self._fds[fd](eventtime)
|
||||
if g_dispatch is not self._g_dispatch:
|
||||
self._end_greenlet(g_dispatch)
|
||||
|
|
|
@ -98,8 +98,9 @@ class SerialReader:
|
|||
continue
|
||||
if self.baud:
|
||||
stk500v2_leave(self.ser, self.reactor)
|
||||
self.serialqueue = self.ffi_lib.serialqueue_alloc(
|
||||
self.ser.fileno(), 0)
|
||||
self.serialqueue = self.ffi_main.gc(
|
||||
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.start()
|
||||
# Obtain and load the data dictionary from the firmware
|
||||
|
@ -126,7 +127,9 @@ class SerialReader:
|
|||
def connect_file(self, debugoutput, dictionary, pace=False):
|
||||
self.ser = debugoutput
|
||||
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):
|
||||
self.ffi_lib.serialqueue_set_clock_est(
|
||||
self.serialqueue, freq, last_time, last_clock)
|
||||
|
@ -135,7 +138,6 @@ class SerialReader:
|
|||
self.ffi_lib.serialqueue_exit(self.serialqueue)
|
||||
if self.background_thread is not None:
|
||||
self.background_thread.join()
|
||||
self.ffi_lib.serialqueue_free(self.serialqueue)
|
||||
self.background_thread = self.serialqueue = None
|
||||
if self.ser is not None:
|
||||
self.ser.close()
|
||||
|
@ -194,10 +196,10 @@ class SerialReader:
|
|||
self.stats(self.reactor.monotonic()),))
|
||||
sdata = 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(
|
||||
self.serialqueue, 1, sdata, len(sdata))
|
||||
rcount = self.ffi_lib.serialqueue_extract_old(
|
||||
self.serialqueue, 0, rdata, len(rdata))
|
||||
scount = self.ffi_lib.serialqueue_extract_old(self.serialqueue, 1,
|
||||
sdata, len(sdata))
|
||||
rcount = self.ffi_lib.serialqueue_extract_old(self.serialqueue, 0,
|
||||
rdata, len(rdata))
|
||||
out.append("Dumping send queue %d messages" % (scount,))
|
||||
for i in range(scount):
|
||||
msg = sdata[i]
|
||||
|
@ -222,8 +224,6 @@ class SerialReader:
|
|||
logging.info("%s: %s", params['#name'], params['#msg'])
|
||||
def handle_default(self, params):
|
||||
logging.warn("got %s", params)
|
||||
def __del__(self):
|
||||
self.disconnect()
|
||||
|
||||
# Class to send a query command and return the received response
|
||||
class SerialRetryCommand:
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import math, logging, collections
|
||||
import homing, chelper
|
||||
import chelper
|
||||
|
||||
class error(Exception):
|
||||
pass
|
||||
|
|
|
@ -54,6 +54,10 @@ class Move:
|
|||
self.accel = min(self.accel, accel)
|
||||
self.delta_v2 = 2.0 * self.move_d * self.accel
|
||||
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):
|
||||
if not self.is_kinematic_move or not prev_move.is_kinematic_move:
|
||||
return
|
||||
|
@ -246,7 +250,7 @@ class ToolHead:
|
|||
self.trapq_free_moves = ffi_lib.trapq_free_moves
|
||||
self.step_generators = []
|
||||
# Create kinematics class
|
||||
self.extruder = kinematics.extruder.DummyExtruder()
|
||||
self.extruder = kinematics.extruder.DummyExtruder(self.printer)
|
||||
kin_name = config.get('kinematics')
|
||||
try:
|
||||
mod = importlib.import_module('kinematics.' + kin_name)
|
||||
|
@ -466,7 +470,7 @@ class ToolHead:
|
|||
# Submit move
|
||||
try:
|
||||
self.move(newpos, speed)
|
||||
except homing.CommandError as e:
|
||||
except self.printer.command_error as e:
|
||||
self.flush_step_generation()
|
||||
raise
|
||||
# Transmit move in "drip" mode
|
||||
|
|
|
@ -61,7 +61,8 @@ class WebRequest:
|
|||
value = self.params.get(item, default)
|
||||
if value is Sentinel:
|
||||
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,))
|
||||
return value
|
||||
|
||||
|
@ -168,19 +169,30 @@ class ClientConnection:
|
|||
self.sock.fileno(), self.process_received)
|
||||
self.partial_data = self.send_buffer = ""
|
||||
self.is_sending_data = False
|
||||
logging.info(
|
||||
"webhooks: New connection established")
|
||||
self.set_client_info("?", "New connection")
|
||||
|
||||
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):
|
||||
if self.fd_handle is not None:
|
||||
logging.info("webhooks: Client connection closed")
|
||||
self.reactor.unregister_fd(self.fd_handle)
|
||||
self.fd_handle = None
|
||||
try:
|
||||
self.sock.close()
|
||||
except socket.error:
|
||||
pass
|
||||
self.server.pop_client(self.uid)
|
||||
if self.fd_handle is None:
|
||||
return
|
||||
self.set_client_info(None, "Disconnected")
|
||||
self.reactor.unregister_fd(self.fd_handle)
|
||||
self.fd_handle = None
|
||||
try:
|
||||
self.sock.close()
|
||||
except socket.error:
|
||||
pass
|
||||
self.server.pop_client(self.uid)
|
||||
|
||||
def is_closed(self):
|
||||
return self.fd_handle is None
|
||||
|
@ -216,7 +228,7 @@ class ClientConnection:
|
|||
try:
|
||||
func = self.webhooks.get_callback(web_request.get_method())
|
||||
func(web_request)
|
||||
except homing.CommandError as e:
|
||||
except self.printer.command_error as e:
|
||||
web_request.set_error(WebRequestError(e.message))
|
||||
except Exception as e:
|
||||
msg = ("Internal Error on WebRequest: %s"
|
||||
|
@ -276,9 +288,12 @@ class WebHooks:
|
|||
web_request.send({'endpoints': self._endpoints.keys()})
|
||||
|
||||
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()
|
||||
klipper_path = os.path.normpath(os.path.join(
|
||||
os.path.dirname(__file__), ".."))
|
||||
src_path = os.path.dirname(__file__)
|
||||
klipper_path = os.path.normpath(os.path.join(src_path, ".."))
|
||||
response = {'state': state, 'state_message': state_message,
|
||||
'hostname': socket.gethostname(),
|
||||
'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.
|
||||
#
|
||||
# Copyright (C) 2015-2018 Kevin O'Connor <kevin@koconnor.net>
|
||||
|
@ -17,7 +17,7 @@ class SerialRxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
|
|||
pysimulavr.PySimulationMember.__init__(self)
|
||||
self.terminal = terminal
|
||||
self.sc = pysimulavr.SystemClock.Instance()
|
||||
self.delay = SIMULAVR_FREQ / baud
|
||||
self.delay = SIMULAVR_FREQ // baud
|
||||
self.current = 0
|
||||
self.pos = -1
|
||||
def SetInState(self, pin):
|
||||
|
@ -33,7 +33,7 @@ class SerialRxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
|
|||
if self.pos == 1:
|
||||
return int(self.delay * 1.5)
|
||||
if self.pos >= SERIALBITS:
|
||||
data = chr((self.current >> 1) & 0xff)
|
||||
data = bytearray([(self.current >> 1) & 0xff])
|
||||
self.terminal.write(data)
|
||||
self.pos = -1
|
||||
self.current = 0
|
||||
|
@ -48,10 +48,10 @@ class SerialTxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
|
|||
self.terminal = terminal
|
||||
self.SetPin('H')
|
||||
self.sc = pysimulavr.SystemClock.Instance()
|
||||
self.delay = SIMULAVR_FREQ / baud
|
||||
self.delay = SIMULAVR_FREQ // baud
|
||||
self.current = 0
|
||||
self.pos = 0
|
||||
self.queue = ""
|
||||
self.queue = bytearray()
|
||||
self.sc.Add(self)
|
||||
def DoStep(self, trueHwStep):
|
||||
if not self.pos:
|
||||
|
@ -59,9 +59,8 @@ class SerialTxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
|
|||
data = self.terminal.read()
|
||||
if not data:
|
||||
return self.delay * 100
|
||||
self.queue += data
|
||||
self.current = (ord(self.queue[0]) << 1) | 0x200
|
||||
self.queue = self.queue[1:]
|
||||
self.queue.extend(data)
|
||||
self.current = (self.queue.pop(0) << 1) | 0x200
|
||||
newstate = 'L'
|
||||
if self.current & (1 << self.pos):
|
||||
newstate = 'H'
|
||||
|
@ -109,7 +108,7 @@ class Pacing(pysimulavr.PySimulationMember):
|
|||
self.next_check_clock = 0
|
||||
self.rel_time = time.time()
|
||||
self.best_offset = 0.
|
||||
self.delay = SIMULAVR_FREQ / 10000
|
||||
self.delay = SIMULAVR_FREQ // 10000
|
||||
self.sc.Add(self)
|
||||
def DoStep(self, trueHwStep):
|
||||
curtime = time.time()
|
||||
|
@ -135,7 +134,7 @@ class TerminalIO:
|
|||
def read(self):
|
||||
try:
|
||||
return os.read(self.fd, 64)
|
||||
except os.error, e:
|
||||
except os.error as e:
|
||||
if e.errno not in (errno.EAGAIN, errno.EWOULDBLOCK):
|
||||
pysimulavr.SystemClock.Instance().stop()
|
||||
return ""
|
||||
|
@ -198,7 +197,7 @@ def main():
|
|||
trace = Tracing(options.tracefile, options.trace)
|
||||
dev = pysimulavr.AvrFactory.instance().makeDevice(proc)
|
||||
dev.Load(elffile)
|
||||
dev.SetClockFreq(SIMULAVR_FREQ / speed)
|
||||
dev.SetClockFreq(SIMULAVR_FREQ // speed)
|
||||
sc.Add(dev)
|
||||
pysimulavr.cvar.sysConHandler.SetUseExit(False)
|
||||
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()
|
||||
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
|
||||
|
@ -111,6 +132,8 @@ def main():
|
|||
default=5., help="pullup resistor")
|
||||
opts.add_option("-s", "--sensors", type="string", dest="sensors",
|
||||
default="", help="list of sensors (comma separated)")
|
||||
opts.add_option("-r", "--resistance", action="store_true",
|
||||
help="graph sensor resistance")
|
||||
options, args = opts.parse_args()
|
||||
if len(args) != 0:
|
||||
opts.error("Incorrect number of arguments")
|
||||
|
@ -129,7 +152,10 @@ def main():
|
|||
|
||||
# Draw graph
|
||||
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
|
||||
if options.output is None:
|
||||
|
|
|
@ -137,6 +137,48 @@ def plot_mcu(data, maxbw):
|
|||
ax1.grid(True)
|
||||
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):
|
||||
all_keys = {}
|
||||
for d in data:
|
||||
|
@ -211,6 +253,8 @@ def main():
|
|||
opts = optparse.OptionParser(usage)
|
||||
opts.add_option("-f", "--frequency", action="store_true",
|
||||
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",
|
||||
default=None, help="filename of output graph")
|
||||
opts.add_option("-t", "--temperature", type="string", dest="heater",
|
||||
|
@ -233,6 +277,8 @@ def main():
|
|||
fig = plot_temperature(data, options.heater)
|
||||
elif options.frequency:
|
||||
fig = plot_frequency(data, options.mcu)
|
||||
elif options.system:
|
||||
fig = plot_system(data)
|
||||
else:
|
||||
fig = plot_mcu(data, MAXBANDWIDTH)
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ create_virtualenv()
|
|||
report_status "Updating python virtual environment..."
|
||||
|
||||
# Create virtualenv if it doesn't already exist
|
||||
[ ! -d ${PYTHONDIR} ] && virtualenv ${PYTHONDIR}
|
||||
[ ! -d ${PYTHONDIR} ] && virtualenv -p python2 ${PYTHONDIR}
|
||||
|
||||
# Install/update dependencies
|
||||
${PYTHONDIR}/bin/pip install -r ${SRCDIR}/scripts/klippy-requirements.txt
|
||||
|
|
|
@ -37,7 +37,7 @@ create_virtualenv()
|
|||
report_status "Updating python virtual environment..."
|
||||
|
||||
# Create virtualenv if it doesn't already exist
|
||||
[ ! -d ${PYTHONDIR} ] && virtualenv ${PYTHONDIR}
|
||||
[ ! -d ${PYTHONDIR} ] && virtualenv -p python2 ${PYTHONDIR}
|
||||
|
||||
# Install/update dependencies
|
||||
${PYTHONDIR}/bin/pip install -r ${SRCDIR}/scripts/klippy-requirements.txt
|
||||
|
|
|
@ -34,7 +34,7 @@ create_virtualenv()
|
|||
report_status "Updating python virtual environment..."
|
||||
|
||||
# Create virtualenv if it doesn't already exist
|
||||
[ ! -d ${PYTHONDIR} ] && virtualenv ${PYTHONDIR}
|
||||
[ ! -d ${PYTHONDIR} ] && virtualenv -p python2 ${PYTHONDIR}
|
||||
|
||||
# Install/update dependencies
|
||||
${PYTHONDIR}/bin/pip install -r ${SRCDIR}/scripts/klippy-requirements.txt
|
||||
|
|
|
@ -36,7 +36,7 @@ create_virtualenv()
|
|||
report_status "Updating python virtual environment..."
|
||||
|
||||
# Create virtualenv if it doesn't already exist
|
||||
[ ! -d ${PYTHONDIR} ] && virtualenv ${PYTHONDIR}
|
||||
[ ! -d ${PYTHONDIR} ] && virtualenv -p python2 ${PYTHONDIR}
|
||||
|
||||
# Install/update dependencies
|
||||
${PYTHONDIR}/bin/pip install -r ${SRCDIR}/scripts/klippy-requirements.txt
|
||||
|
|
|
@ -62,5 +62,5 @@ fi
|
|||
|
||||
echo -e "\n\n=============== Install python virtualenv\n\n"
|
||||
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
|
||||
|
|
|
@ -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_HARD_PWM) += pwmcmds.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
|
||||
config STM32_FLASH_START_8000
|
||||
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
|
||||
bool "64KiB bootloader (Alfawise)" if MACH_STM32F103
|
||||
config STM32_FLASH_START_0000
|
||||
|
@ -129,6 +131,7 @@ config FLASH_START
|
|||
default 0x8005000 if STM32_FLASH_START_5000
|
||||
default 0x8007000 if STM32_FLASH_START_7000
|
||||
default 0x8008000 if STM32_FLASH_START_8000
|
||||
default 0x8008800 if STM32_FLASH_START_8800
|
||||
default 0x8010000 if STM32_FLASH_START_10000
|
||||
default 0x8000000
|
||||
|
||||
|
|
|
@ -127,6 +127,8 @@ CONFIG ../../config/generic-mks-robin-e3.cfg
|
|||
CONFIG ../../config/generic-mks-robin-nano.cfg
|
||||
CONFIG ../../config/printer-alfawise-u30-2018.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
|
||||
|
||||
# Printers using the stm32f407
|
||||
|
|
Loading…
Reference in New Issue