mirror of
https://github.com/Klipper3d/klipper.git
synced 2025-12-24 00:28:34 -07:00
Merge branch 'master' into patch-1
This commit is contained in:
commit
47430c0aa5
71 changed files with 2489 additions and 1556 deletions
|
|
@ -133,44 +133,34 @@ max_z_accel: 100
|
|||
|
||||
#[tmc2130 stepper_x]
|
||||
#cs_pin: PB8
|
||||
#spi_software_miso_pin: PC11
|
||||
#spi_software_mosi_pin: PC12
|
||||
#spi_software_sclk_pin: PC10
|
||||
#spi_bus: spi3_PC11_PC12_PC10
|
||||
##diag1_pin: PF3
|
||||
#run_current: 0.800
|
||||
#stealthchop_threshold: 999999
|
||||
|
||||
#[tmc2130 stepper_y]
|
||||
#cs_pin: PC9
|
||||
#spi_software_miso_pin: PC11
|
||||
#spi_software_mosi_pin: PC12
|
||||
#spi_software_sclk_pin: PC10
|
||||
#spi_bus: spi3_PC11_PC12_PC10
|
||||
##diag1_pin: PF4
|
||||
#run_current: 0.800
|
||||
#stealthchop_threshold: 999999
|
||||
|
||||
#[tmc2130 stepper_z]
|
||||
#cs_pin: PD0
|
||||
#spi_software_miso_pin: PC11
|
||||
#spi_software_mosi_pin: PC12
|
||||
#spi_software_sclk_pin: PC10
|
||||
#spi_bus: spi3_PC11_PC12_PC10
|
||||
##diag1_pin: PF5
|
||||
#run_current: 0.650
|
||||
#stealthchop_threshold: 999999
|
||||
|
||||
#[tmc2130 extruder]
|
||||
#cs_pin: PD1
|
||||
#spi_software_miso_pin: PC11
|
||||
#spi_software_mosi_pin: PC12
|
||||
#spi_software_sclk_pin: PC10
|
||||
#spi_bus: spi3_PC11_PC12_PC10
|
||||
#run_current: 0.800
|
||||
#stealthchop_threshold: 999999
|
||||
|
||||
#[tmc2130 extruder1]
|
||||
#cs_pin: PB5
|
||||
#spi_software_miso_pin: PC11
|
||||
#spi_software_mosi_pin: PC12
|
||||
#spi_software_sclk_pin: PC10
|
||||
#spi_bus: spi3_PC11_PC12_PC10
|
||||
#run_current: 0.800
|
||||
#stealthchop_threshold: 999999
|
||||
|
||||
|
|
@ -195,6 +185,4 @@ aliases:
|
|||
|
||||
#[adxl345]
|
||||
#cs_pin: PC15
|
||||
#spi_software_miso_pin: PC11
|
||||
#spi_software_mosi_pin: PC12
|
||||
#spi_software_sclk_pin: PC10
|
||||
#spi_bus: spi3_PC11_PC12_PC10
|
||||
|
|
|
|||
|
|
@ -11,9 +11,7 @@ serial: /dev/serial/by-id/usb-Klipper_Klipper_firmware_12345-if00
|
|||
|
||||
[adxl345]
|
||||
cs_pin: EBBCan:PB12
|
||||
spi_software_sclk_pin: EBBCan:PB10
|
||||
spi_software_mosi_pin: EBBCan:PB11
|
||||
spi_software_miso_pin: EBBCan:PB2
|
||||
spi_bus: spi2_PB2_PB11_PB10
|
||||
axes_map: x,y,z
|
||||
|
||||
[extruder]
|
||||
|
|
|
|||
|
|
@ -11,9 +11,7 @@ serial: /dev/serial/by-id/usb-Klipper_Klipper_firmware_12345-if00
|
|||
|
||||
[adxl345]
|
||||
cs_pin: EBBCan:PB12
|
||||
spi_software_sclk_pin: EBBCan:PB10
|
||||
spi_software_mosi_pin: EBBCan:PB11
|
||||
spi_software_miso_pin: EBBCan:PB2
|
||||
spi_bus: spi2_PB2_PB11_PB10
|
||||
axes_map: x,y,z
|
||||
|
||||
[extruder]
|
||||
|
|
|
|||
|
|
@ -15,9 +15,7 @@ sensor_pin: EBBCan:PA2
|
|||
|
||||
[adxl345]
|
||||
cs_pin: EBBCan:PB12
|
||||
spi_software_sclk_pin: EBBCan:PB10
|
||||
spi_software_mosi_pin: EBBCan:PB11
|
||||
spi_software_miso_pin: EBBCan:PB2
|
||||
spi_bus: spi2_PB2_PB11_PB10
|
||||
axes_map: x,y,z
|
||||
|
||||
[extruder]
|
||||
|
|
|
|||
|
|
@ -102,20 +102,35 @@ some functionality in C code.
|
|||
Initial execution starts in **klippy/klippy.py**. This reads the
|
||||
command-line arguments, opens the printer config file, instantiates
|
||||
the main printer objects, and starts the serial connection. The main
|
||||
execution of G-code commands is in the process_commands() method in
|
||||
execution of G-code commands is in the _process_commands() method in
|
||||
**klippy/gcode.py**. This code translates the G-code commands into
|
||||
printer object calls, which frequently translate the actions to
|
||||
commands to be executed on the micro-controller (as declared via the
|
||||
DECL_COMMAND macro in the micro-controller code).
|
||||
|
||||
There are four threads in the Klippy host code. The main thread
|
||||
handles incoming gcode commands. A second thread (which resides
|
||||
entirely in the **klippy/chelper/serialqueue.c** C code) handles
|
||||
low-level IO with the serial port. The third thread is used to process
|
||||
response messages from the micro-controller in the Python code (see
|
||||
**klippy/serialhdl.py**). The fourth thread writes debug messages to
|
||||
the log (see **klippy/queuelogger.py**) so that the other threads
|
||||
never block on log writes.
|
||||
There are several threads in the Klipper host code:
|
||||
* There is a Python "main thread" that handles incoming G-Code
|
||||
commands and is the starting point for most actions. This thread
|
||||
runs the [reactor](https://en.wikipedia.org/wiki/Reactor_pattern)
|
||||
(**klippy/reactor.py**) and most high-level actions originate from
|
||||
IO and timer event callbacks from that reactor.
|
||||
* A thread for writing messages to the log so that the other threads
|
||||
do not block on log writes. This thread resides in the
|
||||
**klippy/queuelogger.py** code and its multi-threaded nature is not
|
||||
exposed to the main Python thread.
|
||||
* A thread per micro-controller that performs the low-level reading
|
||||
and writing of messages to that micro-controller. It resides in the
|
||||
**klippy/chelper/serialqueue.c** C code and its multi-threaded
|
||||
nature is not exposed to the Python code.
|
||||
* A thread per micro-controller for processing messages received from
|
||||
that micro-controller in the Python code. This thread is created in
|
||||
**klippy/serialhdl.py**. Care must be taken in Python callbacks
|
||||
invoked from this thread as this thread may directly interact with
|
||||
the main Python thread.
|
||||
* A thread per stepper motor that calculates the timing of stepper
|
||||
motor step pulses and compresses those times. This thread resides in
|
||||
the **klippy/chelper/steppersync.c** C code and its multi-threaded
|
||||
nature is not exposed to the Python code.
|
||||
|
||||
## Code flow of a move command
|
||||
|
||||
|
|
@ -136,9 +151,10 @@ provides further information on the mechanics of moves.
|
|||
|
||||
* The ToolHead class (in toolhead.py) handles "look-ahead" and tracks
|
||||
the timing of printing actions. The main codepath for a move is:
|
||||
`ToolHead.move() -> LookAheadQueue.add_move() ->
|
||||
LookAheadQueue.flush() -> Move.set_junction() ->
|
||||
ToolHead._process_moves()`.
|
||||
`ToolHead.move() -> LookAheadQueue.add_move()`, then
|
||||
`ToolHead.move() -> ToolHead._process_lookahead() ->
|
||||
LookAheadQueue.flush() -> Move.set_junction()`, and then
|
||||
`ToolHead._process_lookahead() -> trapq_append()`.
|
||||
* ToolHead.move() creates a Move() object with the parameters of the
|
||||
move (in cartesian space and in units of seconds and millimeters).
|
||||
* The kinematics class is given the opportunity to audit each move
|
||||
|
|
@ -157,39 +173,47 @@ provides further information on the mechanics of moves.
|
|||
phase, followed by a constant deceleration phase. Every move
|
||||
contains these three phases in this order, but some phases may be of
|
||||
zero duration.
|
||||
* When ToolHead._process_moves() is called, everything about the
|
||||
* When ToolHead._process_lookahead() resumes, everything about the
|
||||
move is known - its start location, its end location, its
|
||||
acceleration, its start/cruising/end velocity, and distance traveled
|
||||
during acceleration/cruising/deceleration. All the information is
|
||||
stored in the Move() class and is in cartesian space in units of
|
||||
millimeters and seconds.
|
||||
* The moves are then placed on a "trapezoid motion queue" via
|
||||
trapq_append() (in klippy/chelper/trapq.c). The trapq stores all the
|
||||
information in the Move() class in a C struct accessible to the host
|
||||
C code.
|
||||
|
||||
* Note that the extruder is handled in its own kinematic class:
|
||||
`ToolHead._process_lookahead() -> PrinterExtruder.process_move()`.
|
||||
Since the Move() class specifies the exact movement time and since
|
||||
step pulses are sent to the micro-controller with specific timing,
|
||||
stepper movements produced by the extruder class will be in sync
|
||||
with head movement even though the code is kept separate.
|
||||
|
||||
* For efficiency reasons, stepper motion is generated in the C code in
|
||||
a thread per stepper motor. The threads are notified when steps
|
||||
should be generated by the motion_queuing module
|
||||
(klippy/extras/motion_queuing.py):
|
||||
`PrinterMotionQueuing._flush_handler() ->
|
||||
PrinterMotionQueuing._advance_move_time() ->
|
||||
steppersyncmgr_gen_steps() -> se_start_gen_steps()`.
|
||||
|
||||
* Klipper uses an
|
||||
[iterative solver](https://en.wikipedia.org/wiki/Root-finding_algorithm)
|
||||
to generate the step times for each stepper. For efficiency reasons,
|
||||
the stepper pulse times are generated in C code. The moves are first
|
||||
placed on a "trapezoid motion queue": `ToolHead._process_moves() ->
|
||||
trapq_append()` (in klippy/chelper/trapq.c). The step times are then
|
||||
generated: `ToolHead._process_moves() ->
|
||||
ToolHead._advance_move_time() -> ToolHead._advance_flush_time() ->
|
||||
MCU_Stepper.generate_steps() -> itersolve_generate_steps() ->
|
||||
itersolve_gen_steps_range()` (in klippy/chelper/itersolve.c). The
|
||||
goal of the iterative solver is to find step times given a function
|
||||
that calculates a stepper position from a time. This is done by
|
||||
repeatedly "guessing" various times until the stepper position
|
||||
formula returns the desired position of the next step on the
|
||||
stepper. The feedback produced from each guess is used to improve
|
||||
future guesses so that the process rapidly converges to the desired
|
||||
time. The kinematic stepper position formulas are located in the
|
||||
klippy/chelper/ directory (eg, kin_cart.c, kin_corexy.c,
|
||||
kin_delta.c, kin_extruder.c).
|
||||
|
||||
* Note that the extruder is handled in its own kinematic class:
|
||||
`ToolHead._process_moves() -> PrinterExtruder.move()`. Since
|
||||
the Move() class specifies the exact movement time and since step
|
||||
pulses are sent to the micro-controller with specific timing,
|
||||
stepper movements produced by the extruder class will be in sync
|
||||
with head movement even though the code is kept separate.
|
||||
to generate the step times for each stepper. The step times are
|
||||
generated from the background thread (klippy/chelper/steppersync.c):
|
||||
`se_background_thread() -> se_generate_steps() ->
|
||||
itersolve_generate_steps() -> itersolve_gen_steps_range()` (in
|
||||
klippy/chelper/itersolve.c). The goal of the iterative solver is to
|
||||
find step times given a function that calculates a stepper position
|
||||
from a time. This is done by repeatedly "guessing" various times
|
||||
until the stepper position formula returns the desired position of
|
||||
the next step on the stepper. The feedback produced from each guess
|
||||
is used to improve future guesses so that the process rapidly
|
||||
converges to the desired time. The kinematic stepper position
|
||||
formulas are located in the klippy/chelper/ directory (eg,
|
||||
kin_cart.c, kin_corexy.c, kin_delta.c, kin_extruder.c).
|
||||
|
||||
* After the iterative solver calculates the step times they are added
|
||||
to an array: `itersolve_gen_steps_range() -> stepcompress_append()`
|
||||
|
|
@ -206,7 +230,7 @@ provides further information on the mechanics of moves.
|
|||
commands that correspond to the list of stepper step times built in
|
||||
the previous stage. These "queue_step" commands are then queued,
|
||||
prioritized, and sent to the micro-controller (via
|
||||
stepcompress.c:steppersync and serialqueue.c:serialqueue).
|
||||
steppersync.c:steppersync and serialqueue.c:serialqueue).
|
||||
|
||||
* Processing of the queue_step commands on the micro-controller starts
|
||||
in src/command.c which parses the command and calls
|
||||
|
|
|
|||
|
|
@ -8,6 +8,17 @@ All dates in this document are approximate.
|
|||
|
||||
## Changes
|
||||
|
||||
20251010: During normal printing the command processing will now
|
||||
attempt to stay one second ahead of printer movement (reduced from two
|
||||
seconds previously).
|
||||
|
||||
20251003: Support for the undocumented `max_stepper_error` option in
|
||||
the `[printer]` config section has been removed.
|
||||
|
||||
20250916: The definitions of EI, 2HUMP_EI, and 3HUMP_EI input shapers
|
||||
were updated. For best performance it is recommended to recalibrate
|
||||
input shapers, especially if some of these shapers are currently used.
|
||||
|
||||
20250811: Support for the `max_accel_to_decel` parameter in the
|
||||
`[printer]` config section has been removed and support for the
|
||||
`ACCEL_TO_DECEL` parameter in the `SET_VELOCITY_LIMIT` command has
|
||||
|
|
|
|||
|
|
@ -1780,17 +1780,22 @@ the [command reference](G-Codes.md#input_shaper).
|
|||
# input shapers, this parameter can be set from different
|
||||
# considerations. The default value is 0, which disables input
|
||||
# shaping for Y axis.
|
||||
#shaper_freq_z: 0
|
||||
# A frequency (in Hz) of the input shaper for Z axis. The default
|
||||
# value is 0, which disables input shaping for Z axis.
|
||||
#shaper_type: mzv
|
||||
# A type of the input shaper to use for both X and Y axes. Supported
|
||||
# A type of the input shaper to use for all axes. Supported
|
||||
# shapers are zv, mzv, zvd, ei, 2hump_ei, and 3hump_ei. The default
|
||||
# is mzv input shaper.
|
||||
#shaper_type_x:
|
||||
#shaper_type_y:
|
||||
# If shaper_type is not set, these two parameters can be used to
|
||||
# configure different input shapers for X and Y axes. The same
|
||||
#shaper_type_z:
|
||||
# If shaper_type is not set, these parameters can be used to
|
||||
# configure different input shapers for X, Y, and Z axes. The same
|
||||
# values are supported as for shaper_type parameter.
|
||||
#damping_ratio_x: 0.1
|
||||
#damping_ratio_y: 0.1
|
||||
#damping_ratio_z: 0.1
|
||||
# Damping ratios of vibrations of X and Y axes used by input shapers
|
||||
# to improve vibration suppression. Default value is 0.1 which is a
|
||||
# good all-round value for most printers. In most circumstances this
|
||||
|
|
@ -1955,11 +1960,10 @@ section of the measuring resonances guide for more information on
|
|||
# are reachable by the toolhead.
|
||||
#accel_chip:
|
||||
# A name of the accelerometer chip to use for measurements. If
|
||||
# adxl345 chip was defined without an explicit name, this parameter
|
||||
# can simply reference it as "accel_chip: adxl345", otherwise an
|
||||
# explicit name must be supplied as well, e.g. "accel_chip: adxl345
|
||||
# my_chip_name". Either this, or the next two parameters must be
|
||||
# set.
|
||||
# an accelerometer was defined without an explicit name, this parameter
|
||||
# can simply reference it by type, e.g. "accel_chip: adxl345", otherwise
|
||||
# a full name must be supplied, e.g. "accel_chip: adxl345 my_chip_name".
|
||||
# Either this, or the next two parameters must be set.
|
||||
#accel_chip_x:
|
||||
#accel_chip_y:
|
||||
# Names of the accelerometer chips to use for measurements for each
|
||||
|
|
@ -1968,6 +1972,10 @@ section of the measuring resonances guide for more information on
|
|||
# and on the toolhead (for X axis). These parameters have the same
|
||||
# format as 'accel_chip' parameter. Only 'accel_chip' or these two
|
||||
# parameters must be provided.
|
||||
#accel_chip_z:
|
||||
# A name of the accelerometer chip to use for measurements of Z axis.
|
||||
# This parameter has the same format as 'accel_chip'. The default is
|
||||
# not to configure an accelerometer for Z axis.
|
||||
#max_smoothing:
|
||||
# Maximum input shaper smoothing to allow for each axis during shaper
|
||||
# auto-calibration (with 'SHAPER_CALIBRATE' command). By default no
|
||||
|
|
@ -1978,16 +1986,20 @@ section of the measuring resonances guide for more information on
|
|||
# during the calibration. The default is 50.
|
||||
#min_freq: 5
|
||||
# Minimum frequency to test for resonances. The default is 5 Hz.
|
||||
#max_freq: 133.33
|
||||
# Maximum frequency to test for resonances. The default is 133.33 Hz.
|
||||
#max_freq: 135
|
||||
# Maximum frequency to test for resonances. The default is 135 Hz.
|
||||
#max_freq_z: 100
|
||||
# Maximum frequency to test Z axis for resonances. The default is 100 Hz.
|
||||
#accel_per_hz: 60
|
||||
# This parameter is used to determine which acceleration to use to
|
||||
# test a specific frequency: accel = accel_per_hz * freq. Higher the
|
||||
# value, the higher is the energy of the oscillations. Can be set to
|
||||
# a lower than the default value if the resonances get too strong on
|
||||
# the printer. However, lower values make measurements of
|
||||
# high-frequency resonances less precise. The default value is 75
|
||||
# (mm/sec).
|
||||
# the printer. However, lower values make measurements of high-frequency
|
||||
# resonances less precise. The default value is 60 (mm/sec).
|
||||
#accel_per_hz_z: 15
|
||||
# This parameter has the same meaning as accel_per_hz, but applies to
|
||||
# Z axis specifically. The default is 15 (mm/sec).
|
||||
#hz_per_sec: 1
|
||||
# Determines the speed of the test. When testing all frequencies in
|
||||
# range [min_freq, max_freq], each second the frequency increases by
|
||||
|
|
@ -1996,6 +2008,8 @@ section of the measuring resonances guide for more information on
|
|||
# (Hz/sec == sec^-2).
|
||||
#sweeping_accel: 400
|
||||
# An acceleration of slow sweeping moves. The default is 400 mm/sec^2.
|
||||
#sweeping_accel_z: 50
|
||||
# Same as sweeping_accel above, but for Z axis. The default is 50 mm/sec^2.
|
||||
#sweeping_period: 1.2
|
||||
# A period of slow sweeping moves. Setting this parameter to 0
|
||||
# disables slow sweeping moves. Avoid setting it to a too small
|
||||
|
|
@ -4944,8 +4958,8 @@ detection_length: 7.0
|
|||
# a state change on the switch_pin
|
||||
# Default is 7 mm.
|
||||
extruder:
|
||||
# The name of the extruder section this sensor is associated with.
|
||||
# This parameter must be provided.
|
||||
# The name of the extruder or extruder_stepper section this sensor
|
||||
# is associated with. This parameter must be provided.
|
||||
switch_pin:
|
||||
#pause_on_runout:
|
||||
#runout_gcode:
|
||||
|
|
|
|||
17
docs/FAQ.md
17
docs/FAQ.md
|
|
@ -98,17 +98,16 @@ bootloaders.
|
|||
|
||||
## Can I run Klipper on something other than a Raspberry Pi 3?
|
||||
|
||||
The recommended hardware is a Raspberry Pi 2, Raspberry Pi 3, or
|
||||
Raspberry Pi 4.
|
||||
The recommended hardware is a Raspberry Pi Zero2w, Raspberry Pi 3,
|
||||
Raspberry Pi 4 or Raspberry Pi 5. Klipper will also run on other SBC
|
||||
devices as well as x86 hardware, as described below.
|
||||
|
||||
Klipper will run on a Raspberry Pi 1 and on the Raspberry Pi Zero, but
|
||||
these boards don't have enough processing power to run OctoPrint
|
||||
Klipper will run on a Raspberry Pi 1, 2 and on the Raspberry Pi Zero1,
|
||||
but these boards don't have enough processing power to run Klipper
|
||||
well. It is common for print stalls to occur on these slower machines
|
||||
when printing directly from OctoPrint. (The printer may move faster
|
||||
than OctoPrint can send movement commands.) If you wish to run on one
|
||||
one of these slower boards anyway, consider using the "virtual_sdcard"
|
||||
feature when printing (see
|
||||
[config reference](Config_Reference.md#virtual_sdcard) for details).
|
||||
when printing (The printer may move faster than Klipper can send
|
||||
movement commands.) It is not reccomended to run Klipper on these older
|
||||
machines.
|
||||
|
||||
For running on the Beaglebone, see the
|
||||
[Beaglebone specific installation instructions](Beaglebone.md).
|
||||
|
|
|
|||
|
|
@ -493,7 +493,7 @@ enabled.
|
|||
`SET_FAN_SPEED FAN=config_name SPEED=<speed>` This command sets the
|
||||
speed of a fan. "speed" must be between 0.0 and 1.0.
|
||||
|
||||
`SET_FAN_SPEED PIN=config_name TEMPLATE=<template_name>
|
||||
`SET_FAN_SPEED FAN=config_name TEMPLATE=<template_name>
|
||||
[<param_x>=<literal>]`: If `TEMPLATE` is specified then it assigns a
|
||||
[display_template](Config_Reference.md#display_template) to the given
|
||||
fan. For example, if one defined a `[display_template
|
||||
|
|
@ -828,15 +828,17 @@ been enabled (also see the
|
|||
|
||||
#### SET_INPUT_SHAPER
|
||||
`SET_INPUT_SHAPER [SHAPER_FREQ_X=<shaper_freq_x>]
|
||||
[SHAPER_FREQ_Y=<shaper_freq_y>] [DAMPING_RATIO_X=<damping_ratio_x>]
|
||||
[DAMPING_RATIO_Y=<damping_ratio_y>] [SHAPER_TYPE=<shaper>]
|
||||
[SHAPER_TYPE_X=<shaper_type_x>] [SHAPER_TYPE_Y=<shaper_type_y>]`:
|
||||
[SHAPER_FREQ_Y=<shaper_freq_y>] [SHAPER_FREQ_Y=<shaper_freq_z>]
|
||||
[DAMPING_RATIO_X=<damping_ratio_x>] [DAMPING_RATIO_Y=<damping_ratio_y>]
|
||||
[DAMPING_RATIO_Z=<damping_ratio_z>] [SHAPER_TYPE=<shaper>]
|
||||
[SHAPER_TYPE_X=<shaper_type_x>] [SHAPER_TYPE_Y=<shaper_type_y>]
|
||||
[SHAPER_TYPE_Z=<shaper_type_z>]`:
|
||||
Modify input shaper parameters. Note that SHAPER_TYPE parameter resets
|
||||
input shaper for both X and Y axes even if different shaper types have
|
||||
input shaper for all axes even if different shaper types have
|
||||
been configured in [input_shaper] section. SHAPER_TYPE cannot be used
|
||||
together with either of SHAPER_TYPE_X and SHAPER_TYPE_Y parameters.
|
||||
See [config reference](Config_Reference.md#input_shaper) for more
|
||||
details on each of these parameters.
|
||||
together with any of SHAPER_TYPE_X, SHAPER_TYPE_Y, and SHAPER_TYPE_Z
|
||||
parameters. See [config reference](Config_Reference.md#input_shaper)
|
||||
for more details on each of these parameters.
|
||||
|
||||
### [led]
|
||||
|
||||
|
|
@ -1284,13 +1286,14 @@ all enabled accelerometer chips.
|
|||
[POINT=x,y,z] [INPUT_SHAPING=<0:1>]`: Runs the resonance
|
||||
test in all configured probe points for the requested "axis" and
|
||||
measures the acceleration using the accelerometer chips configured for
|
||||
the respective axis. "axis" can either be X or Y, or specify an
|
||||
arbitrary direction as `AXIS=dx,dy`, where dx and dy are floating
|
||||
the respective axis. "axis" can either be X, Y or Z, or specify an
|
||||
arbitrary direction as `AXIS=dx,dy[,dz]`, where dx, dy, dz are floating
|
||||
point numbers defining a direction vector (e.g. `AXIS=X`, `AXIS=Y`, or
|
||||
`AXIS=1,-1` to define a diagonal direction). Note that `AXIS=dx,dy`
|
||||
and `AXIS=-dx,-dy` is equivalent. `chip_name` can be one or
|
||||
more configured accel chips, delimited with comma, for example
|
||||
`CHIPS="adxl345, adxl345 rpi"`. If POINT is specified it will override the point(s)
|
||||
`AXIS=1,-1` to define a diagonal direction in XY plane, or `AXIS=0,1,1`
|
||||
to define a direction in YZ plane). Note that `AXIS=dx,dy` and `AXIS=-dx,-dy`
|
||||
is equivalent. `chip_name` can be one or more configured accel chips,
|
||||
delimited with comma, for example `CHIPS="adxl345, adxl345 rpi"`.
|
||||
If POINT is specified it will override the point(s)
|
||||
configured in `[resonance_tester]`. If `INPUT_SHAPING=0` or not set(default),
|
||||
disables input shaping for the resonance testing, because
|
||||
it is not valid to run the resonance testing with the input shaper
|
||||
|
|
|
|||
|
|
@ -697,6 +697,95 @@ If you are doing a shaper re-calibration and the reported smoothing for the
|
|||
suggested shaper configuration is almost the same as what you got during the
|
||||
previous calibration, this step can be skipped.
|
||||
|
||||
### Measuring the resonances of Z axis
|
||||
|
||||
Measuring the resonances of Z axis is similar in many aspects to measuring
|
||||
resonances of X and Y axes, with some subtle differences. Similarly to other
|
||||
axes measurements, you will need to have an accelerometer mounted on the
|
||||
moving parts of Z axis - either the bed itself (if the bed moves over Z axis),
|
||||
or the toolhead (if the toolhead/gantry moves over Z). You will need to
|
||||
add the appropriate chip configuration to `printer.cfg` and also add it to
|
||||
`[resonance_tester]` section, e.g.
|
||||
```
|
||||
[resonance_tester]
|
||||
accel_chip_z: <accelerometer full name>
|
||||
```
|
||||
Also make sure that `probe_points` configured in `[resonance_tester]` allow
|
||||
sufficient clearance for Z axis movements (20 mm above bed surface should
|
||||
provide enough clearance with the default test parameters).
|
||||
|
||||
The next consideration is that Z axis can typically reach lower maximum
|
||||
speeds and accelerations that X and Y axes. Default parameters of the test
|
||||
take that into consideration and are much less agressive, but it may still
|
||||
be necessary to increase `max_z_accel` and `max_z_velocity`. If you have
|
||||
them configured in `[printer]` section, make sure to set them to at least
|
||||
```
|
||||
[printer]
|
||||
max_z_velocity: 20
|
||||
max_z_accel: 1550
|
||||
```
|
||||
but only for the duration of the test, afterwards you can revert them back
|
||||
to their original values if necessary. And if you use custom test parameters
|
||||
for Z axis, `TEST_RESONANCES` and `SHAPER_CALIBRATE` will provide the minimum
|
||||
required limits if necessary for your specific case.
|
||||
|
||||
After all changes to `printer.cfg` have been made, restart Klipper and run
|
||||
either
|
||||
```
|
||||
TEST_RESONANCES AXIS=Z
|
||||
```
|
||||
or
|
||||
```
|
||||
SHAPER_CALIBRATE AXIS=Z
|
||||
```
|
||||
and proceed from there accordingly how you would for other axes.
|
||||
For example, after `TEST_RESONANCES` command you can run
|
||||
`calibrate_shaper.py` script and get shaper recommendations and
|
||||
the chart of resonance response:
|
||||
|
||||

|
||||
|
||||
After the calibration, the shaper parameters can be stored in the
|
||||
`printer.cfg`, e.g. from the example above:
|
||||
```
|
||||
[input_shaper]
|
||||
...
|
||||
shaper_type_z: mzv
|
||||
shaper_freq_z: 42.6
|
||||
```
|
||||
|
||||
Also, given the movements of Z axis are slow, you can easily consider
|
||||
more aggressive input shapers, e.g.
|
||||
```
|
||||
[input_shaper]
|
||||
...
|
||||
shaper_type_z: 2hump_ei
|
||||
shaper_freq_z: 63.0
|
||||
```
|
||||
|
||||
If the test produces bogus results, you may try to increase
|
||||
`accel_per_hz_z` parameter in `[resonance_tester]` from its
|
||||
default value 15 to a larger value in the range of 20-30, e.g.
|
||||
```
|
||||
[resonance_tester]
|
||||
accel_per_hz_z: 25
|
||||
```
|
||||
and repeat the test. Increasing this value will likely require
|
||||
increasing `max_z_accel` and `max_z_velocity` parameters as well.
|
||||
You can run `TEST_RESONANCES AXIS=Z` command to get the required
|
||||
minimum values.
|
||||
|
||||
However, if you are unable to measure the resonances of Z axis,
|
||||
you can consider just using
|
||||
```
|
||||
[input_shaper]
|
||||
...
|
||||
shaper_type_z: 3hump_ei
|
||||
shaper_freq_z: 65
|
||||
```
|
||||
as an acceptable all-round choice, given that the smoothing of
|
||||
Z axis movements is not of particular concerns.
|
||||
|
||||
### Unreliable measurements of resonance frequencies
|
||||
|
||||
Sometimes the resonance measurements can produce bogus results, leading to
|
||||
|
|
|
|||
|
|
@ -439,9 +439,12 @@ gcode:
|
|||
SET_INPUT_SHAPER SHAPER_TYPE_X=<primary_carriage_shaper> SHAPER_FREQ_X=<primary_carriage_freq> SHAPER_TYPE_Y=<y_shaper> SHAPER_FREQ_Y=<y_freq>
|
||||
```
|
||||
Note that `SHAPER_TYPE_Y` and `SHAPER_FREQ_Y` should be the same in both
|
||||
commands. It is also possible to put a similar snippet into the start g-code
|
||||
in the slicer, however then the shaper will not be enabled until any print
|
||||
is started.
|
||||
commands. If you need to configure an input shaper for Z axis, include
|
||||
its parameters in both `SET_INPUT_SHAPER` commands.
|
||||
|
||||
Besides `delayed_gcode`, it is also possible to put a similar snippet into
|
||||
the start g-code in the slicer, however then the shaper will not be enabled
|
||||
until any print is started.
|
||||
|
||||
Note that the input shaper only needs to be configured once. Subsequent changes
|
||||
of the carriages or their modes via `SET_DUAL_CARRIAGE` command will preserve
|
||||
|
|
@ -453,15 +456,40 @@ No, `input_shaper` feature has pretty much no impact on the print times by
|
|||
itself. However, the value of `max_accel` certainly does (tuning of this
|
||||
parameter described in [this section](#selecting-max_accel)).
|
||||
|
||||
### Should I enable and tune input shaper for Z axis?
|
||||
|
||||
Most of the users are not likely to see improvements in the quality of
|
||||
the prints directly, much unlike X and Y shapers. However, users of
|
||||
delta printers, printers with flying gantry, or printers with heavy
|
||||
moving beds may be able to increase the `max_z_accel` and `max_z_velocity`
|
||||
kinematics limits and thus get faster Z movements. This can be especially
|
||||
useful e.g. for toolchangers, but also when Z-hops are enabled in slicer.
|
||||
And in general, after enabling Z input shaper many users will hear that
|
||||
Z axis operates more smoothly, which may increase the comfort of printer
|
||||
operation, and may somewhat extend lifespan of Z axis parts.
|
||||
|
||||
## Technical details
|
||||
|
||||
### Input shapers
|
||||
|
||||
Input shapers used in Klipper are rather standard, and one can find more
|
||||
in-depth overview in the articles describing the corresponding shapers.
|
||||
This section contains a brief overview of some technical aspects of the
|
||||
supported input shapers. The table below shows some (usually approximate)
|
||||
parameters of each shaper.
|
||||
supported input shapers. Input shapers used in Klipper are rather standard,
|
||||
with the exception of MZV, and one can find more in-depth overview in
|
||||
the articles describing the corresponding shapers.
|
||||
|
||||
MZV stands for a Modified-ZV input shaper. The classic definition of ZV shaper
|
||||
assumes the duration Ts equal to 1/2 of the damped period of oscillations Td and
|
||||
has two pulses. However, ZV input shaper has a generalized form for an arbitrary
|
||||
duration in the range (0, Td] with three pulses (Specified-Duration ZV, see also
|
||||
SNA-ZV), with a negative middle pulse if Ts < Td and a positive one if Ts > Td.
|
||||
The MZV shaper was designed as an intermediate shaper between ZV and ZVD,
|
||||
offering better vibrations suppression than ZV when the determined (measured)
|
||||
shaper parameters deviate from the ones actually required by the printer,
|
||||
and smaller smoothing than ZVD. Effectively, it is a SD-ZV shaper with the
|
||||
specific duration Ts = 3/4 Td, exactly between ZV (Ts = 1/2 Td) and
|
||||
ZVD (Ts = Td), and it happens to work well for many real-life 3D printers.
|
||||
|
||||
The table below shows some (usually approximate) parameters of each shaper.
|
||||
|
||||
| Input <br> shaper | Shaper <br> duration | Vibration reduction 20x <br> (5% vibration tolerance) | Vibration reduction 10x <br> (10% vibration tolerance) |
|
||||
|:--:|:--:|:--:|:--:|
|
||||
|
|
@ -469,8 +497,8 @@ parameters of each shaper.
|
|||
| MZV | 0.75 / shaper_freq | ± 4% shaper_freq | -10%...+15% shaper_freq |
|
||||
| ZVD | 1 / shaper_freq | ± 15% shaper_freq | ± 22% shaper_freq |
|
||||
| EI | 1 / shaper_freq | ± 20% shaper_freq | ± 25% shaper_freq |
|
||||
| 2HUMP_EI | 1.5 / shaper_freq | ± 35% shaper_freq | ± 40 shaper_freq |
|
||||
| 3HUMP_EI | 2 / shaper_freq | -45...+50% shaper_freq | -50%...+55% shaper_freq |
|
||||
| 2HUMP_EI | 1.5 / shaper_freq | -40...+45% shaper_freq | -45..+50% shaper_freq |
|
||||
| 3HUMP_EI | 2 / shaper_freq | -50...+60% shaper_freq | -55%...+65% shaper_freq |
|
||||
|
||||
A note on vibration reduction: the values in the table above are approximate.
|
||||
If the damping ratio of the printer is known for each axis, the shaper can be
|
||||
|
|
@ -502,11 +530,11 @@ so the values for 10% vibration tolerance are provided only for the reference.
|
|||
resonances at 35 Hz and 60 Hz on the same axis: a) EI shaper needs to have
|
||||
shaper_freq = 35 / (1 - 0.2) = 43.75 Hz, and it will reduce resonances
|
||||
until 43.75 * (1 + 0.2) = 52.5 Hz, so it is not sufficient; b) 2HUMP_EI
|
||||
shaper needs to have shaper_freq = 35 / (1 - 0.35) = 53.85 Hz and will
|
||||
reduce vibrations until 53.85 * (1 + 0.35) = 72.7 Hz - so this is an
|
||||
shaper needs to have shaper_freq = 35 / (1 - 0.4) = 58.3 Hz and will
|
||||
reduce vibrations until 58.3 * (1 + 0.45) = 84.5 Hz - so this is an
|
||||
acceptable configuration. Always try to use as high shaper_freq as possible
|
||||
for a given shaper (perhaps with some safety margin, so in this example
|
||||
shaper_freq ≈ 50-52 Hz would work best), and try to use a shaper with as
|
||||
shaper_freq ≈ 55 Hz would work best), and try to use a shaper with as
|
||||
small shaper duration as possible.
|
||||
* If one needs to reduce vibrations at several very different frequencies
|
||||
(say, 30 Hz and 100 Hz), they may see that the table above does not provide
|
||||
|
|
|
|||
BIN
docs/img/calibrate-z.png
Normal file
BIN
docs/img/calibrate-z.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 158 KiB |
|
|
@ -36,38 +36,41 @@ defs_stepcompress = """
|
|||
int step_count, interval, add;
|
||||
};
|
||||
|
||||
struct stepcompress *stepcompress_alloc(uint32_t oid);
|
||||
void stepcompress_fill(struct stepcompress *sc, uint32_t max_error
|
||||
, int32_t queue_step_msgtag, int32_t set_next_step_dir_msgtag);
|
||||
void stepcompress_fill(struct stepcompress *sc, uint32_t oid
|
||||
, uint32_t max_error, int32_t queue_step_msgtag
|
||||
, int32_t set_next_step_dir_msgtag);
|
||||
void stepcompress_set_invert_sdir(struct stepcompress *sc
|
||||
, uint32_t invert_sdir);
|
||||
void stepcompress_free(struct stepcompress *sc);
|
||||
int stepcompress_reset(struct stepcompress *sc, uint64_t last_step_clock);
|
||||
int stepcompress_set_last_position(struct stepcompress *sc
|
||||
, uint64_t clock, int64_t last_position);
|
||||
int64_t stepcompress_find_past_position(struct stepcompress *sc
|
||||
, uint64_t clock);
|
||||
int stepcompress_queue_msg(struct stepcompress *sc
|
||||
, uint32_t *data, int len);
|
||||
int stepcompress_queue_mq_msg(struct stepcompress *sc, uint64_t req_clock
|
||||
, uint32_t *data, int len);
|
||||
int stepcompress_extract_old(struct stepcompress *sc
|
||||
, struct pull_history_steps *p, int max
|
||||
, uint64_t start_clock, uint64_t end_clock);
|
||||
void stepcompress_set_stepper_kinematics(struct stepcompress *sc
|
||||
, struct stepper_kinematics *sk);
|
||||
"""
|
||||
|
||||
defs_steppersync = """
|
||||
struct steppersync *steppersync_alloc(struct serialqueue *sq
|
||||
, struct stepcompress **sc_list, int sc_num, int move_num);
|
||||
void steppersync_free(struct steppersync *ss);
|
||||
struct stepcompress *syncemitter_get_stepcompress(struct syncemitter *se);
|
||||
void syncemitter_set_stepper_kinematics(struct syncemitter *se
|
||||
, struct stepper_kinematics *sk);
|
||||
struct stepper_kinematics *syncemitter_get_stepper_kinematics(
|
||||
struct syncemitter *se);
|
||||
void syncemitter_queue_msg(struct syncemitter *se, uint64_t req_clock
|
||||
, uint32_t *data, int len);
|
||||
struct syncemitter *steppersync_alloc_syncemitter(struct steppersync *ss
|
||||
, char name[16], int alloc_stepcompress);
|
||||
void steppersync_setup_movequeue(struct steppersync *ss
|
||||
, struct serialqueue *sq, int move_num);
|
||||
void steppersync_set_time(struct steppersync *ss
|
||||
, double time_offset, double mcu_freq);
|
||||
int32_t steppersync_generate_steps(struct steppersync *ss
|
||||
, double gen_steps_time, uint64_t flush_clock);
|
||||
void steppersync_history_expire(struct steppersync *ss, uint64_t end_clock);
|
||||
int steppersync_flush(struct steppersync *ss, uint64_t move_clock);
|
||||
struct steppersyncmgr *steppersyncmgr_alloc(void);
|
||||
void steppersyncmgr_free(struct steppersyncmgr *ssm);
|
||||
struct steppersync *steppersyncmgr_alloc_steppersync(
|
||||
struct steppersyncmgr *ssm);
|
||||
int32_t steppersyncmgr_gen_steps(struct steppersyncmgr *ssm
|
||||
, double flush_time, double gen_steps_time, double clear_history_time);
|
||||
"""
|
||||
|
||||
defs_itersolve = """
|
||||
|
|
@ -76,11 +79,14 @@ defs_itersolve = """
|
|||
int32_t itersolve_is_active_axis(struct stepper_kinematics *sk, char axis);
|
||||
void itersolve_set_trapq(struct stepper_kinematics *sk, struct trapq *tq
|
||||
, double step_dist);
|
||||
struct trapq *itersolve_get_trapq(struct stepper_kinematics *sk);
|
||||
double itersolve_calc_position_from_coord(struct stepper_kinematics *sk
|
||||
, double x, double y, double z);
|
||||
void itersolve_set_position(struct stepper_kinematics *sk
|
||||
, double x, double y, double z);
|
||||
double itersolve_get_commanded_pos(struct stepper_kinematics *sk);
|
||||
double itersolve_get_gen_steps_pre_active(struct stepper_kinematics *sk);
|
||||
double itersolve_get_gen_steps_post_active(struct stepper_kinematics *sk);
|
||||
"""
|
||||
|
||||
defs_trapq = """
|
||||
|
|
@ -157,8 +163,6 @@ defs_kin_extruder = """
|
|||
"""
|
||||
|
||||
defs_kin_shaper = """
|
||||
double input_shaper_get_step_generation_window(
|
||||
struct stepper_kinematics *sk);
|
||||
int input_shaper_set_shaper_params(struct stepper_kinematics *sk, char axis
|
||||
, int n, double a[], double t[]);
|
||||
int input_shaper_set_sk(struct stepper_kinematics *sk
|
||||
|
|
|
|||
|
|
@ -151,7 +151,6 @@ itersolve_generate_steps(struct stepper_kinematics *sk, struct stepcompress *sc
|
|||
sk->last_flush_time = flush_time;
|
||||
if (!sk->tq)
|
||||
return 0;
|
||||
trapq_check_sentinels(sk->tq);
|
||||
struct move *m = list_first_entry(&sk->tq->moves, struct move, node);
|
||||
while (last_flush_time >= m->print_time + m->move_t)
|
||||
m = list_next_entry(m, node);
|
||||
|
|
@ -248,6 +247,12 @@ itersolve_set_trapq(struct stepper_kinematics *sk, struct trapq *tq
|
|||
sk->step_dist = step_dist;
|
||||
}
|
||||
|
||||
struct trapq * __visible
|
||||
itersolve_get_trapq(struct stepper_kinematics *sk)
|
||||
{
|
||||
return sk->tq;
|
||||
}
|
||||
|
||||
double __visible
|
||||
itersolve_calc_position_from_coord(struct stepper_kinematics *sk
|
||||
, double x, double y, double z)
|
||||
|
|
@ -273,3 +278,15 @@ itersolve_get_commanded_pos(struct stepper_kinematics *sk)
|
|||
{
|
||||
return sk->commanded_pos;
|
||||
}
|
||||
|
||||
double __visible
|
||||
itersolve_get_gen_steps_pre_active(struct stepper_kinematics *sk)
|
||||
{
|
||||
return sk->gen_steps_pre_active;
|
||||
}
|
||||
|
||||
double __visible
|
||||
itersolve_get_gen_steps_post_active(struct stepper_kinematics *sk)
|
||||
{
|
||||
return sk->gen_steps_post_active;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,10 +31,13 @@ double itersolve_check_active(struct stepper_kinematics *sk, double flush_time);
|
|||
int32_t itersolve_is_active_axis(struct stepper_kinematics *sk, char axis);
|
||||
void itersolve_set_trapq(struct stepper_kinematics *sk, struct trapq *tq
|
||||
, double step_dist);
|
||||
struct trapq *itersolve_get_trapq(struct stepper_kinematics *sk);
|
||||
double itersolve_calc_position_from_coord(struct stepper_kinematics *sk
|
||||
, double x, double y, double z);
|
||||
void itersolve_set_position(struct stepper_kinematics *sk
|
||||
, double x, double y, double z);
|
||||
double itersolve_get_commanded_pos(struct stepper_kinematics *sk);
|
||||
double itersolve_get_gen_steps_pre_active(struct stepper_kinematics *sk);
|
||||
double itersolve_get_gen_steps_post_active(struct stepper_kinematics *sk);
|
||||
|
||||
#endif // itersolve.h
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Kinematic input shapers to minimize motion vibrations in XY plane
|
||||
//
|
||||
// Copyright (C) 2019-2020 Kevin O'Connor <kevin@koconnor.net>
|
||||
// Copyright (C) 2020 Dmitry Butyugin <dmbutyugin@google.com>
|
||||
// Copyright (C) 2020-2025 Dmitry Butyugin <dmbutyugin@google.com>
|
||||
//
|
||||
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
|
||||
|
|
@ -18,6 +18,8 @@
|
|||
* Shaper initialization
|
||||
****************************************************************/
|
||||
|
||||
static const int KIN_FLAGS[3] = { AF_X, AF_Y, AF_Z };
|
||||
|
||||
struct shaper_pulses {
|
||||
int num_pulses;
|
||||
struct {
|
||||
|
|
@ -113,7 +115,7 @@ struct input_shaper {
|
|||
struct stepper_kinematics sk;
|
||||
struct stepper_kinematics *orig_sk;
|
||||
struct move m;
|
||||
struct shaper_pulses sx, sy;
|
||||
struct shaper_pulses sp[3];
|
||||
};
|
||||
|
||||
// Optimized calc_position when only x axis is needed
|
||||
|
|
@ -122,9 +124,10 @@ 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->sx.num_pulses)
|
||||
struct shaper_pulses *sx = &is->sp[0];
|
||||
if (!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->sx);
|
||||
is->m.start_pos.x = calc_position(m, 'x', move_time, sx);
|
||||
return is->orig_sk->calc_position_cb(is->orig_sk, &is->m, DUMMY_T);
|
||||
}
|
||||
|
||||
|
|
@ -134,25 +137,41 @@ 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->sy.num_pulses)
|
||||
struct shaper_pulses *sy = &is->sp[1];
|
||||
if (!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->sy);
|
||||
is->m.start_pos.y = calc_position(m, 'y', move_time, sy);
|
||||
return is->orig_sk->calc_position_cb(is->orig_sk, &is->m, DUMMY_T);
|
||||
}
|
||||
|
||||
// General calc_position for both x and y axes
|
||||
// Optimized calc_position when only z axis is needed
|
||||
static double
|
||||
shaper_xy_calc_position(struct stepper_kinematics *sk, struct move *m
|
||||
, double move_time)
|
||||
shaper_z_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->sx.num_pulses && !is->sy.num_pulses)
|
||||
struct shaper_pulses *sz = &is->sp[2];
|
||||
if (!sz->num_pulses)
|
||||
return is->orig_sk->calc_position_cb(is->orig_sk, m, move_time);
|
||||
is->m.start_pos.z = calc_position(m, 'z', move_time, sz);
|
||||
return is->orig_sk->calc_position_cb(is->orig_sk, &is->m, DUMMY_T);
|
||||
}
|
||||
|
||||
// General calc_position for all x, y, and z axes
|
||||
static double
|
||||
shaper_xyz_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->sp[0].num_pulses && !is->sp[1].num_pulses && !is->sp[2].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->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);
|
||||
if (is->sp[0].num_pulses)
|
||||
is->m.start_pos.x = calc_position(m, 'x', move_time, &is->sp[0]);
|
||||
if (is->sp[1].num_pulses)
|
||||
is->m.start_pos.y = calc_position(m, 'y', move_time, &is->sp[1]);
|
||||
if (is->sp[2].num_pulses)
|
||||
is->m.start_pos.z = calc_position(m, 'z', move_time, &is->sp[2]);
|
||||
return is->orig_sk->calc_position_cb(is->orig_sk, &is->m, DUMMY_T);
|
||||
}
|
||||
|
||||
|
|
@ -170,15 +189,24 @@ 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;
|
||||
struct shaper_pulses *sx = &is->sp[0];
|
||||
if ((is->sk.active_flags & AF_X) && sx->num_pulses) {
|
||||
pre_active = sx->pulses[sx->num_pulses-1].t;
|
||||
post_active = -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;
|
||||
struct shaper_pulses *sy = &is->sp[1];
|
||||
if ((is->sk.active_flags & AF_Y) && sy->num_pulses) {
|
||||
pre_active = sy->pulses[sy->num_pulses-1].t > pre_active
|
||||
? sy->pulses[sy->num_pulses-1].t : pre_active;
|
||||
post_active = -sy->pulses[0].t > post_active
|
||||
? -sy->pulses[0].t : post_active;
|
||||
}
|
||||
struct shaper_pulses *sz = &is->sp[2];
|
||||
if ((is->sk.active_flags & AF_Z) && sz->num_pulses) {
|
||||
pre_active = sz->pulses[sz->num_pulses-1].t > pre_active
|
||||
? sz->pulses[sz->num_pulses-1].t : pre_active;
|
||||
post_active = -sz->pulses[0].t > post_active
|
||||
? -sz->pulses[0].t : post_active;
|
||||
}
|
||||
is->sk.gen_steps_pre_active = pre_active;
|
||||
is->sk.gen_steps_post_active = post_active;
|
||||
|
|
@ -188,12 +216,15 @@ void __visible
|
|||
input_shaper_update_sk(struct stepper_kinematics *sk)
|
||||
{
|
||||
struct input_shaper *is = container_of(sk, struct input_shaper, sk);
|
||||
if ((is->orig_sk->active_flags & (AF_X | AF_Y)) == (AF_X | AF_Y))
|
||||
is->sk.calc_position_cb = shaper_xy_calc_position;
|
||||
else if (is->orig_sk->active_flags & AF_X)
|
||||
int kin_flags = is->orig_sk->active_flags & (AF_X | AF_Y | AF_Z);
|
||||
if (kin_flags == AF_X)
|
||||
is->sk.calc_position_cb = shaper_x_calc_position;
|
||||
else if (is->orig_sk->active_flags & AF_Y)
|
||||
else if (kin_flags == AF_Y)
|
||||
is->sk.calc_position_cb = shaper_y_calc_position;
|
||||
else if (kin_flags == AF_Z)
|
||||
is->sk.calc_position_cb = shaper_z_calc_position;
|
||||
else
|
||||
is->sk.calc_position_cb = shaper_xyz_calc_position;
|
||||
is->sk.active_flags = is->orig_sk->active_flags;
|
||||
shaper_note_generation_time(is);
|
||||
}
|
||||
|
|
@ -207,8 +238,10 @@ input_shaper_set_sk(struct stepper_kinematics *sk
|
|||
is->sk.calc_position_cb = shaper_x_calc_position;
|
||||
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 if (orig_sk->active_flags == AF_Z)
|
||||
is->sk.calc_position_cb = shaper_z_calc_position;
|
||||
else if (orig_sk->active_flags & (AF_X | AF_Y | AF_Z))
|
||||
is->sk.calc_position_cb = shaper_xyz_calc_position;
|
||||
else
|
||||
return -1;
|
||||
is->sk.active_flags = orig_sk->active_flags;
|
||||
|
|
@ -226,27 +259,20 @@ int __visible
|
|||
input_shaper_set_shaper_params(struct stepper_kinematics *sk, char axis
|
||||
, int n, double a[], double t[])
|
||||
{
|
||||
if (axis != 'x' && axis != 'y')
|
||||
int axis_ind = axis-'x';
|
||||
if (axis_ind < 0 || axis_ind >= ARRAY_SIZE(KIN_FLAGS))
|
||||
return -1;
|
||||
struct input_shaper *is = container_of(sk, struct input_shaper, sk);
|
||||
struct shaper_pulses *sp = axis == 'x' ? &is->sx : &is->sy;
|
||||
struct shaper_pulses *sp = &is->sp[axis_ind];
|
||||
int status = 0;
|
||||
// Ignore input shaper update if the axis is not active
|
||||
if (is->orig_sk->active_flags & (axis == 'x' ? AF_X : AF_Y)) {
|
||||
if (is->orig_sk->active_flags & KIN_FLAGS[axis_ind]) {
|
||||
status = init_shaper(n, a, t, sp);
|
||||
shaper_note_generation_time(is);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
double __visible
|
||||
input_shaper_get_step_generation_window(struct stepper_kinematics *sk)
|
||||
{
|
||||
struct input_shaper *is = container_of(sk, struct input_shaper, sk);
|
||||
return is->sk.gen_steps_pre_active > is->sk.gen_steps_post_active
|
||||
? is->sk.gen_steps_pre_active : is->sk.gen_steps_post_active;
|
||||
}
|
||||
|
||||
struct stepper_kinematics * __visible
|
||||
input_shaper_alloc(void)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -116,6 +116,11 @@ list_join_tail(struct list_head *add, struct list_head *h)
|
|||
; &pos->member != &(head)->root \
|
||||
; pos = list_next_entry(pos, member))
|
||||
|
||||
#define list_for_each_entry_reverse(pos, head, member) \
|
||||
for (pos = list_last_entry((head), typeof(*pos), member) \
|
||||
; &pos->member != &(head)->root \
|
||||
; pos = list_prev_entry(pos, member))
|
||||
|
||||
#define list_for_each_entry_safe(pos, n, head, member) \
|
||||
for (pos = list_first_entry((head), typeof(*pos), member) \
|
||||
, n = list_next_entry(pos, member) \
|
||||
|
|
|
|||
|
|
@ -207,3 +207,14 @@ clock_from_time(struct clock_estimate *ce, double time)
|
|||
{
|
||||
return (int64_t)((time - ce->conv_time)*ce->est_freq + .5) + ce->conv_clock;
|
||||
}
|
||||
|
||||
// Fill the fields of a 'struct clock_estimate'
|
||||
void
|
||||
clock_fill(struct clock_estimate *ce, double est_freq, double conv_time
|
||||
, uint64_t conv_clock, uint64_t last_clock)
|
||||
{
|
||||
ce->est_freq = est_freq;
|
||||
ce->conv_time = conv_time;
|
||||
ce->conv_clock = conv_clock;
|
||||
ce->last_clock = last_clock;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,5 +50,7 @@ void message_queue_free(struct list_head *root);
|
|||
uint64_t clock_from_clock32(struct clock_estimate *ce, uint32_t clock32);
|
||||
double clock_to_time(struct clock_estimate *ce, uint64_t clock);
|
||||
uint64_t clock_from_time(struct clock_estimate *ce, double time);
|
||||
void clock_fill(struct clock_estimate *ce, double est_freq, double conv_time
|
||||
, uint64_t conv_clock, uint64_t last_clock);
|
||||
|
||||
#endif // msgblock.h
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
#include <stdio.h> // fprintf
|
||||
#include <string.h> // strerror
|
||||
#include <time.h> // struct timespec
|
||||
#include <linux/prctl.h> // PR_SET_NAME
|
||||
#include <sys/prctl.h> // prctl
|
||||
#include "compiler.h" // __visible
|
||||
#include "pyhelper.h" // get_monotonic
|
||||
|
|
|
|||
|
|
@ -909,10 +909,7 @@ serialqueue_set_clock_est(struct serialqueue *sq, double est_freq
|
|||
, uint64_t last_clock)
|
||||
{
|
||||
pthread_mutex_lock(&sq->lock);
|
||||
sq->ce.est_freq = est_freq;
|
||||
sq->ce.conv_time = conv_time;
|
||||
sq->ce.conv_clock = conv_clock;
|
||||
sq->ce.last_clock = last_clock;
|
||||
clock_fill(&sq->ce, est_freq, conv_time, conv_clock, last_clock);
|
||||
pthread_mutex_unlock(&sq->lock);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
#include <stdlib.h> // malloc
|
||||
#include <string.h> // memset
|
||||
#include "compiler.h" // DIV_ROUND_UP
|
||||
#include "itersolve.h" // itersolve_generate_steps
|
||||
#include "pyhelper.h" // errorf
|
||||
#include "serialqueue.h" // struct queue_message
|
||||
#include "stepcompress.h" // stepcompress_alloc
|
||||
|
|
@ -37,7 +36,7 @@ struct stepcompress {
|
|||
double mcu_time_offset, mcu_freq, last_step_print_time;
|
||||
// Message generation
|
||||
uint64_t last_step_clock;
|
||||
struct list_head msg_queue;
|
||||
struct list_head *msg_queue;
|
||||
uint32_t oid;
|
||||
int32_t queue_step_msgtag, set_next_step_dir_msgtag;
|
||||
int sdir, invert_sdir;
|
||||
|
|
@ -47,8 +46,6 @@ struct stepcompress {
|
|||
// History tracking
|
||||
int64_t last_position;
|
||||
struct list_head history_list;
|
||||
// Itersolve reference
|
||||
struct stepper_kinematics *sk;
|
||||
};
|
||||
|
||||
struct step_move {
|
||||
|
|
@ -245,23 +242,23 @@ check_line(struct stepcompress *sc, struct step_move move)
|
|||
****************************************************************/
|
||||
|
||||
// Allocate a new 'stepcompress' object
|
||||
struct stepcompress * __visible
|
||||
stepcompress_alloc(uint32_t oid)
|
||||
struct stepcompress *
|
||||
stepcompress_alloc(struct list_head *msg_queue)
|
||||
{
|
||||
struct stepcompress *sc = malloc(sizeof(*sc));
|
||||
memset(sc, 0, sizeof(*sc));
|
||||
list_init(&sc->msg_queue);
|
||||
list_init(&sc->history_list);
|
||||
sc->oid = oid;
|
||||
sc->sdir = -1;
|
||||
sc->msg_queue = msg_queue;
|
||||
return sc;
|
||||
}
|
||||
|
||||
// Fill message id information
|
||||
void __visible
|
||||
stepcompress_fill(struct stepcompress *sc, uint32_t max_error
|
||||
stepcompress_fill(struct stepcompress *sc, uint32_t oid, uint32_t max_error
|
||||
, int32_t queue_step_msgtag, int32_t set_next_step_dir_msgtag)
|
||||
{
|
||||
sc->oid = oid;
|
||||
sc->max_error = max_error;
|
||||
sc->queue_step_msgtag = queue_step_msgtag;
|
||||
sc->set_next_step_dir_msgtag = set_next_step_dir_msgtag;
|
||||
|
|
@ -294,13 +291,12 @@ stepcompress_history_expire(struct stepcompress *sc, uint64_t end_clock)
|
|||
}
|
||||
|
||||
// Free memory associated with a 'stepcompress' object
|
||||
void __visible
|
||||
void
|
||||
stepcompress_free(struct stepcompress *sc)
|
||||
{
|
||||
if (!sc)
|
||||
return;
|
||||
free(sc->queue);
|
||||
message_queue_free(&sc->msg_queue);
|
||||
stepcompress_history_expire(sc, UINT64_MAX);
|
||||
free(sc);
|
||||
}
|
||||
|
|
@ -317,12 +313,6 @@ stepcompress_get_step_dir(struct stepcompress *sc)
|
|||
return sc->next_step_dir;
|
||||
}
|
||||
|
||||
struct list_head *
|
||||
stepcompress_get_msg_queue(struct stepcompress *sc)
|
||||
{
|
||||
return &sc->msg_queue;
|
||||
}
|
||||
|
||||
// Determine the "print time" of the last_step_clock
|
||||
static void
|
||||
calc_last_step_print_time(struct stepcompress *sc)
|
||||
|
|
@ -360,7 +350,7 @@ add_move(struct stepcompress *sc, uint64_t first_clock, struct step_move *move)
|
|||
qm->min_clock = qm->req_clock = sc->last_step_clock;
|
||||
if (move->count == 1 && first_clock >= sc->last_step_clock + CLOCK_DIFF_MAX)
|
||||
qm->req_clock = first_clock;
|
||||
list_add_tail(&qm->node, &sc->msg_queue);
|
||||
list_add_tail(&qm->node, sc->msg_queue);
|
||||
sc->last_step_clock = last_clock;
|
||||
|
||||
// Create and store move in history tracking
|
||||
|
|
@ -424,7 +414,7 @@ set_next_step_dir(struct stepcompress *sc, int sdir)
|
|||
};
|
||||
struct queue_message *qm = message_alloc_and_encode(msg, 3);
|
||||
qm->req_clock = sc->last_step_clock;
|
||||
list_add_tail(&qm->node, &sc->msg_queue);
|
||||
list_add_tail(&qm->node, sc->msg_queue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -541,7 +531,7 @@ stepcompress_commit(struct stepcompress *sc)
|
|||
}
|
||||
|
||||
// Flush pending steps
|
||||
static int
|
||||
int
|
||||
stepcompress_flush(struct stepcompress *sc, uint64_t move_clock)
|
||||
{
|
||||
if (sc->next_step_clock && move_clock >= sc->next_step_clock) {
|
||||
|
|
@ -613,35 +603,6 @@ stepcompress_find_past_position(struct stepcompress *sc, uint64_t clock)
|
|||
return last_position;
|
||||
}
|
||||
|
||||
// Queue an mcu command to go out in order with stepper commands
|
||||
int __visible
|
||||
stepcompress_queue_msg(struct stepcompress *sc, uint32_t *data, int len)
|
||||
{
|
||||
int ret = stepcompress_flush(sc, UINT64_MAX);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
struct queue_message *qm = message_alloc_and_encode(data, len);
|
||||
qm->req_clock = sc->last_step_clock;
|
||||
list_add_tail(&qm->node, &sc->msg_queue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Queue an mcu command that will consume space in the mcu move queue
|
||||
int __visible
|
||||
stepcompress_queue_mq_msg(struct stepcompress *sc, uint64_t req_clock
|
||||
, uint32_t *data, int len)
|
||||
{
|
||||
int ret = stepcompress_flush(sc, UINT64_MAX);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
struct queue_message *qm = message_alloc_and_encode(data, len);
|
||||
qm->min_clock = qm->req_clock = req_clock;
|
||||
list_add_tail(&qm->node, &sc->msg_queue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Return history of queue_step commands
|
||||
int __visible
|
||||
stepcompress_extract_old(struct stepcompress *sc, struct pull_history_steps *p
|
||||
|
|
@ -665,26 +626,3 @@ stepcompress_extract_old(struct stepcompress *sc, struct pull_history_steps *p
|
|||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// Store a reference to stepper_kinematics
|
||||
void __visible
|
||||
stepcompress_set_stepper_kinematics(struct stepcompress *sc
|
||||
, struct stepper_kinematics *sk)
|
||||
{
|
||||
sc->sk = sk;
|
||||
}
|
||||
|
||||
// Generate steps (via itersolve) and flush
|
||||
int32_t
|
||||
stepcompress_generate_steps(struct stepcompress *sc, double gen_steps_time
|
||||
, uint64_t flush_clock)
|
||||
{
|
||||
if (!sc->sk)
|
||||
return 0;
|
||||
// Generate steps
|
||||
int32_t ret = itersolve_generate_steps(sc->sk, sc, gen_steps_time);
|
||||
if (ret)
|
||||
return ret;
|
||||
// Flush steps
|
||||
return stepcompress_flush(sc, flush_clock);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,9 @@ struct pull_history_steps {
|
|||
int step_count, interval, add;
|
||||
};
|
||||
|
||||
struct stepcompress *stepcompress_alloc(uint32_t oid);
|
||||
void stepcompress_fill(struct stepcompress *sc, uint32_t max_error
|
||||
struct list_head;
|
||||
struct stepcompress *stepcompress_alloc(struct list_head *msg_queue);
|
||||
void stepcompress_fill(struct stepcompress *sc, uint32_t oid, uint32_t max_error
|
||||
, int32_t queue_step_msgtag
|
||||
, int32_t set_next_step_dir_msgtag);
|
||||
void stepcompress_set_invert_sdir(struct stepcompress *sc
|
||||
|
|
@ -21,28 +22,19 @@ void stepcompress_history_expire(struct stepcompress *sc, uint64_t end_clock);
|
|||
void stepcompress_free(struct stepcompress *sc);
|
||||
uint32_t stepcompress_get_oid(struct stepcompress *sc);
|
||||
int stepcompress_get_step_dir(struct stepcompress *sc);
|
||||
struct list_head *stepcompress_get_msg_queue(struct stepcompress *sc);
|
||||
void stepcompress_set_time(struct stepcompress *sc
|
||||
, double time_offset, double mcu_freq);
|
||||
int stepcompress_append(struct stepcompress *sc, int sdir
|
||||
, double print_time, double step_time);
|
||||
int stepcompress_commit(struct stepcompress *sc);
|
||||
int stepcompress_flush(struct stepcompress *sc, uint64_t move_clock);
|
||||
int stepcompress_reset(struct stepcompress *sc, uint64_t last_step_clock);
|
||||
int stepcompress_set_last_position(struct stepcompress *sc, uint64_t clock
|
||||
, int64_t last_position);
|
||||
int64_t stepcompress_find_past_position(struct stepcompress *sc
|
||||
, uint64_t clock);
|
||||
int stepcompress_queue_msg(struct stepcompress *sc, uint32_t *data, int len);
|
||||
int stepcompress_queue_mq_msg(struct stepcompress *sc, uint64_t req_clock
|
||||
, uint32_t *data, int len);
|
||||
int stepcompress_extract_old(struct stepcompress *sc
|
||||
, struct pull_history_steps *p, int max
|
||||
, uint64_t start_clock, uint64_t end_clock);
|
||||
struct stepper_kinematics;
|
||||
void stepcompress_set_stepper_kinematics(struct stepcompress *sc
|
||||
, struct stepper_kinematics *sk);
|
||||
int32_t stepcompress_generate_steps(struct stepcompress *sc
|
||||
, double gen_steps_time
|
||||
, uint64_t flush_clock);
|
||||
|
||||
#endif // stepcompress.h
|
||||
|
|
|
|||
|
|
@ -11,57 +11,250 @@
|
|||
// mcu step queue is ordered between steppers so that no stepper
|
||||
// starves the other steppers of space in the mcu step queue.
|
||||
|
||||
#include <pthread.h> // pthread_mutex_lock
|
||||
#include <stddef.h> // offsetof
|
||||
#include <stdlib.h> // malloc
|
||||
#include <string.h> // memset
|
||||
#include "compiler.h" // __visible
|
||||
#include "pyhelper.h" // set_thread_name
|
||||
#include "itersolve.h" // itersolve_generate_steps
|
||||
#include "serialqueue.h" // struct queue_message
|
||||
#include "stepcompress.h" // stepcompress_flush
|
||||
#include "steppersync.h" // steppersync_alloc
|
||||
#include "trapq.h" // trapq_check_sentinels
|
||||
|
||||
|
||||
/****************************************************************
|
||||
* SyncEmitter - message generation for each stepper
|
||||
****************************************************************/
|
||||
|
||||
struct syncemitter {
|
||||
// List node for storage in steppersync list
|
||||
struct list_node ss_node;
|
||||
// Transmit message queue
|
||||
struct list_head msg_queue;
|
||||
// Thread for step generation
|
||||
struct stepcompress *sc;
|
||||
struct stepper_kinematics *sk;
|
||||
char name[16];
|
||||
pthread_t tid;
|
||||
pthread_mutex_t lock; // protects variables below
|
||||
pthread_cond_t cond;
|
||||
int have_work;
|
||||
double bg_gen_steps_time;
|
||||
uint64_t bg_flush_clock, bg_clear_history_clock;
|
||||
int32_t bg_result;
|
||||
};
|
||||
|
||||
// Return this emitters 'struct stepcompress' (or NULL if not allocated)
|
||||
struct stepcompress * __visible
|
||||
syncemitter_get_stepcompress(struct syncemitter *se)
|
||||
{
|
||||
return se->sc;
|
||||
}
|
||||
|
||||
// Store a reference to stepper_kinematics
|
||||
void __visible
|
||||
syncemitter_set_stepper_kinematics(struct syncemitter *se
|
||||
, struct stepper_kinematics *sk)
|
||||
{
|
||||
se->sk = sk;
|
||||
}
|
||||
|
||||
// Report current stepper_kinematics
|
||||
struct stepper_kinematics * __visible
|
||||
syncemitter_get_stepper_kinematics(struct syncemitter *se)
|
||||
{
|
||||
return se->sk;
|
||||
}
|
||||
|
||||
// Queue an mcu command that will consume space in the mcu move queue
|
||||
void __visible
|
||||
syncemitter_queue_msg(struct syncemitter *se, uint64_t req_clock
|
||||
, uint32_t *data, int len)
|
||||
{
|
||||
struct queue_message *qm = message_alloc_and_encode(data, len);
|
||||
qm->min_clock = qm->req_clock = req_clock;
|
||||
list_add_tail(&qm->node, &se->msg_queue);
|
||||
}
|
||||
|
||||
// Generate steps (via itersolve) and flush
|
||||
static int32_t
|
||||
se_generate_steps(struct syncemitter *se)
|
||||
{
|
||||
if (!se->sc || !se->sk)
|
||||
return 0;
|
||||
double gen_steps_time = se->bg_gen_steps_time;
|
||||
uint64_t flush_clock = se->bg_flush_clock;
|
||||
uint64_t clear_history_clock = se->bg_clear_history_clock;
|
||||
// Generate steps
|
||||
int32_t ret = itersolve_generate_steps(se->sk, se->sc, gen_steps_time);
|
||||
if (ret)
|
||||
return ret;
|
||||
// Flush steps
|
||||
ret = stepcompress_flush(se->sc, flush_clock);
|
||||
if (ret)
|
||||
return ret;
|
||||
// Clear history
|
||||
stepcompress_history_expire(se->sc, clear_history_clock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Main background thread for generating steps
|
||||
static void *
|
||||
se_background_thread(void *data)
|
||||
{
|
||||
struct syncemitter *se = data;
|
||||
set_thread_name(se->name);
|
||||
|
||||
pthread_mutex_lock(&se->lock);
|
||||
for (;;) {
|
||||
if (!se->have_work) {
|
||||
pthread_cond_wait(&se->cond, &se->lock);
|
||||
continue;
|
||||
}
|
||||
if (se->have_work < 0)
|
||||
// Exit request
|
||||
break;
|
||||
|
||||
// Request to generate steps
|
||||
se->bg_result = se_generate_steps(se);
|
||||
if (se->bg_result)
|
||||
errorf("Error in syncemitter '%s' step generation", se->name);
|
||||
se->have_work = 0;
|
||||
pthread_cond_signal(&se->cond);
|
||||
}
|
||||
pthread_mutex_unlock(&se->lock);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Signal background thread to start step generation
|
||||
static void
|
||||
se_start_gen_steps(struct syncemitter *se, double gen_steps_time
|
||||
, uint64_t flush_clock, uint64_t clear_history_clock)
|
||||
{
|
||||
if (!se->sc || !se->sk)
|
||||
return;
|
||||
pthread_mutex_lock(&se->lock);
|
||||
while (se->have_work)
|
||||
pthread_cond_wait(&se->cond, &se->lock);
|
||||
se->bg_gen_steps_time = gen_steps_time;
|
||||
se->bg_flush_clock = flush_clock;
|
||||
se->bg_clear_history_clock = clear_history_clock;
|
||||
se->have_work = 1;
|
||||
pthread_mutex_unlock(&se->lock);
|
||||
pthread_cond_signal(&se->cond);
|
||||
}
|
||||
|
||||
// Wait for background thread to complete last step generation request
|
||||
static int32_t
|
||||
se_finalize_gen_steps(struct syncemitter *se)
|
||||
{
|
||||
if (!se->sc || !se->sk)
|
||||
return 0;
|
||||
pthread_mutex_lock(&se->lock);
|
||||
while (se->have_work)
|
||||
pthread_cond_wait(&se->cond, &se->lock);
|
||||
int32_t res = se->bg_result;
|
||||
pthread_mutex_unlock(&se->lock);
|
||||
return res;
|
||||
}
|
||||
|
||||
// Allocate syncemitter and start thread
|
||||
static struct syncemitter *
|
||||
syncemitter_alloc(char name[16], int alloc_stepcompress)
|
||||
{
|
||||
struct syncemitter *se = malloc(sizeof(*se));
|
||||
memset(se, 0, sizeof(*se));
|
||||
list_init(&se->msg_queue);
|
||||
strncpy(se->name, name, sizeof(se->name));
|
||||
se->name[sizeof(se->name)-1] = '\0';
|
||||
if (!alloc_stepcompress)
|
||||
return se;
|
||||
se->sc = stepcompress_alloc(&se->msg_queue);
|
||||
int ret = pthread_mutex_init(&se->lock, NULL);
|
||||
if (ret)
|
||||
goto fail;
|
||||
ret = pthread_cond_init(&se->cond, NULL);
|
||||
if (ret)
|
||||
goto fail;
|
||||
ret = pthread_create(&se->tid, NULL, se_background_thread, se);
|
||||
if (ret)
|
||||
goto fail;
|
||||
return se;
|
||||
fail:
|
||||
report_errno("se alloc", ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Free syncemitter and exit background thread
|
||||
static void
|
||||
syncemitter_free(struct syncemitter *se)
|
||||
{
|
||||
if (!se)
|
||||
return;
|
||||
if (se->sc) {
|
||||
pthread_mutex_lock(&se->lock);
|
||||
while (se->have_work)
|
||||
pthread_cond_wait(&se->cond, &se->lock);
|
||||
se->have_work = -1;
|
||||
pthread_cond_signal(&se->cond);
|
||||
pthread_mutex_unlock(&se->lock);
|
||||
int ret = pthread_join(se->tid, NULL);
|
||||
if (ret)
|
||||
report_errno("se pthread_join", ret);
|
||||
stepcompress_free(se->sc);
|
||||
}
|
||||
message_queue_free(&se->msg_queue);
|
||||
free(se);
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************
|
||||
* StepperSync - sort move queue for a micro-controller
|
||||
****************************************************************/
|
||||
|
||||
struct steppersync {
|
||||
// List node for storage in steppersyncmgr list
|
||||
struct list_node ssm_node;
|
||||
// Serial port
|
||||
struct serialqueue *sq;
|
||||
struct command_queue *cq;
|
||||
// Storage for associated stepcompress objects
|
||||
struct stepcompress **sc_list;
|
||||
int sc_num;
|
||||
// The syncemitters that generate messages on this mcu
|
||||
struct list_head se_list;
|
||||
// Convert from time to clock
|
||||
struct clock_estimate ce;
|
||||
// Storage for list of pending move clocks
|
||||
uint64_t *move_clocks;
|
||||
int num_move_clocks;
|
||||
};
|
||||
|
||||
// Allocate a new 'steppersync' object
|
||||
struct steppersync * __visible
|
||||
steppersync_alloc(struct serialqueue *sq, struct stepcompress **sc_list
|
||||
, int sc_num, int move_num)
|
||||
// Allocate a new syncemitter instance
|
||||
struct syncemitter * __visible
|
||||
steppersync_alloc_syncemitter(struct steppersync *ss, char name[16]
|
||||
, int alloc_stepcompress)
|
||||
{
|
||||
struct steppersync *ss = malloc(sizeof(*ss));
|
||||
memset(ss, 0, sizeof(*ss));
|
||||
struct syncemitter *se = syncemitter_alloc(name, alloc_stepcompress);
|
||||
if (se)
|
||||
list_add_tail(&se->ss_node, &ss->se_list);
|
||||
return se;
|
||||
}
|
||||
|
||||
// Fill information on mcu move queue
|
||||
void __visible
|
||||
steppersync_setup_movequeue(struct steppersync *ss, struct serialqueue *sq
|
||||
, int move_num)
|
||||
{
|
||||
serialqueue_free_commandqueue(ss->cq);
|
||||
free(ss->move_clocks);
|
||||
|
||||
ss->sq = sq;
|
||||
ss->cq = serialqueue_alloc_commandqueue();
|
||||
|
||||
ss->sc_list = malloc(sizeof(*sc_list)*sc_num);
|
||||
memcpy(ss->sc_list, sc_list, sizeof(*sc_list)*sc_num);
|
||||
ss->sc_num = sc_num;
|
||||
|
||||
ss->move_clocks = malloc(sizeof(*ss->move_clocks)*move_num);
|
||||
memset(ss->move_clocks, 0, sizeof(*ss->move_clocks)*move_num);
|
||||
ss->num_move_clocks = move_num;
|
||||
|
||||
return ss;
|
||||
}
|
||||
|
||||
// Free memory associated with a 'steppersync' object
|
||||
void __visible
|
||||
steppersync_free(struct steppersync *ss)
|
||||
{
|
||||
if (!ss)
|
||||
return;
|
||||
free(ss->sc_list);
|
||||
free(ss->move_clocks);
|
||||
serialqueue_free_commandqueue(ss->cq);
|
||||
free(ss);
|
||||
}
|
||||
|
||||
// Set the conversion rate of 'print_time' to mcu clock
|
||||
|
|
@ -69,37 +262,11 @@ void __visible
|
|||
steppersync_set_time(struct steppersync *ss, double time_offset
|
||||
, double mcu_freq)
|
||||
{
|
||||
int i;
|
||||
for (i=0; i<ss->sc_num; i++) {
|
||||
struct stepcompress *sc = ss->sc_list[i];
|
||||
stepcompress_set_time(sc, time_offset, mcu_freq);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate steps and flush stepcompress objects
|
||||
int32_t __visible
|
||||
steppersync_generate_steps(struct steppersync *ss, double gen_steps_time
|
||||
, uint64_t flush_clock)
|
||||
{
|
||||
int i;
|
||||
for (i=0; i<ss->sc_num; i++) {
|
||||
struct stepcompress *sc = ss->sc_list[i];
|
||||
int32_t ret = stepcompress_generate_steps(sc, gen_steps_time
|
||||
, flush_clock);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Expire the stepcompress history before the given clock time
|
||||
void __visible
|
||||
steppersync_history_expire(struct steppersync *ss, uint64_t end_clock)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < ss->sc_num; i++) {
|
||||
struct stepcompress *sc = ss->sc_list[i];
|
||||
stepcompress_history_expire(sc, end_clock);
|
||||
clock_fill(&ss->ce, mcu_freq, time_offset, 0, 0);
|
||||
struct syncemitter *se;
|
||||
list_for_each_entry(se, &ss->se_list, ss_node) {
|
||||
if (se->sc)
|
||||
stepcompress_set_time(se->sc, time_offset, mcu_freq);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -129,7 +296,7 @@ heap_replace(struct steppersync *ss, uint64_t req_clock)
|
|||
}
|
||||
|
||||
// Find and transmit any scheduled steps prior to the given 'move_clock'
|
||||
int __visible
|
||||
static void
|
||||
steppersync_flush(struct steppersync *ss, uint64_t move_clock)
|
||||
{
|
||||
// Order commands by the reqclock of each pending command
|
||||
|
|
@ -139,13 +306,11 @@ steppersync_flush(struct steppersync *ss, uint64_t move_clock)
|
|||
// Find message with lowest reqclock
|
||||
uint64_t req_clock = MAX_CLOCK;
|
||||
struct queue_message *qm = NULL;
|
||||
int i;
|
||||
for (i=0; i<ss->sc_num; i++) {
|
||||
struct stepcompress *sc = ss->sc_list[i];
|
||||
struct list_head *sc_mq = stepcompress_get_msg_queue(sc);
|
||||
if (!list_empty(sc_mq)) {
|
||||
struct syncemitter *se;
|
||||
list_for_each_entry(se, &ss->se_list, ss_node) {
|
||||
if (!list_empty(&se->msg_queue)) {
|
||||
struct queue_message *m = list_first_entry(
|
||||
sc_mq, struct queue_message, node);
|
||||
&se->msg_queue, struct queue_message, node);
|
||||
if (m->req_clock < req_clock) {
|
||||
qm = m;
|
||||
req_clock = m->req_clock;
|
||||
|
|
@ -172,6 +337,100 @@ steppersync_flush(struct steppersync *ss, uint64_t move_clock)
|
|||
// Transmit commands
|
||||
if (!list_empty(&msgs))
|
||||
serialqueue_send_batch(ss->sq, ss->cq, &msgs);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************
|
||||
* StepperSyncMgr - manage a list of steppersync
|
||||
****************************************************************/
|
||||
|
||||
struct steppersyncmgr {
|
||||
struct list_head ss_list;
|
||||
};
|
||||
|
||||
// Allocate a new 'steppersyncmgr' object
|
||||
struct steppersyncmgr * __visible
|
||||
steppersyncmgr_alloc(void)
|
||||
{
|
||||
struct steppersyncmgr *ssm = malloc(sizeof(*ssm));
|
||||
memset(ssm, 0, sizeof(*ssm));
|
||||
list_init(&ssm->ss_list);
|
||||
return ssm;
|
||||
}
|
||||
|
||||
// Free memory associated with a 'steppersync' object
|
||||
void __visible
|
||||
steppersyncmgr_free(struct steppersyncmgr *ssm)
|
||||
{
|
||||
if (!ssm)
|
||||
return;
|
||||
while (!list_empty(&ssm->ss_list)) {
|
||||
struct steppersync *ss = list_first_entry(
|
||||
&ssm->ss_list, struct steppersync, ssm_node);
|
||||
list_del(&ss->ssm_node);
|
||||
free(ss->move_clocks);
|
||||
serialqueue_free_commandqueue(ss->cq);
|
||||
while (!list_empty(&ss->se_list)) {
|
||||
struct syncemitter *se = list_first_entry(
|
||||
&ss->se_list, struct syncemitter, ss_node);
|
||||
list_del(&se->ss_node);
|
||||
syncemitter_free(se);
|
||||
}
|
||||
free(ss);
|
||||
}
|
||||
free(ssm);
|
||||
}
|
||||
|
||||
// Allocate a new 'steppersync' object
|
||||
struct steppersync * __visible
|
||||
steppersyncmgr_alloc_steppersync(struct steppersyncmgr *ssm)
|
||||
{
|
||||
struct steppersync *ss = malloc(sizeof(*ss));
|
||||
memset(ss, 0, sizeof(*ss));
|
||||
list_init(&ss->se_list);
|
||||
list_add_tail(&ss->ssm_node, &ssm->ss_list);
|
||||
return ss;
|
||||
}
|
||||
|
||||
// Generate and flush steps
|
||||
int32_t __visible
|
||||
steppersyncmgr_gen_steps(struct steppersyncmgr *ssm, double flush_time
|
||||
, double gen_steps_time, double clear_history_time)
|
||||
{
|
||||
struct steppersync *ss;
|
||||
// Prepare trapqs for step generation
|
||||
list_for_each_entry(ss, &ssm->ss_list, ssm_node) {
|
||||
struct syncemitter *se;
|
||||
list_for_each_entry(se, &ss->se_list, ss_node) {
|
||||
if (!se->sc || !se->sk)
|
||||
continue;
|
||||
struct trapq *tq = itersolve_get_trapq(se->sk);
|
||||
if (tq)
|
||||
trapq_check_sentinels(tq);
|
||||
}
|
||||
}
|
||||
// Start step generation threads
|
||||
list_for_each_entry(ss, &ssm->ss_list, ssm_node) {
|
||||
uint64_t flush_clock = clock_from_time(&ss->ce, flush_time);
|
||||
uint64_t clear_clock = clock_from_time(&ss->ce, clear_history_time);
|
||||
struct syncemitter *se;
|
||||
list_for_each_entry(se, &ss->se_list, ss_node) {
|
||||
se_start_gen_steps(se, gen_steps_time, flush_clock, clear_clock);
|
||||
}
|
||||
}
|
||||
// Wait for step generation threads to complete
|
||||
int32_t res = 0;
|
||||
list_for_each_entry(ss, &ssm->ss_list, ssm_node) {
|
||||
struct syncemitter *se;
|
||||
list_for_each_entry(se, &ss->se_list, ss_node) {
|
||||
int32_t ret = se_finalize_gen_steps(se);
|
||||
if (ret)
|
||||
res = ret;
|
||||
}
|
||||
if (res)
|
||||
continue;
|
||||
uint64_t flush_clock = clock_from_time(&ss->ce, flush_time);
|
||||
steppersync_flush(ss, flush_clock);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,16 +3,30 @@
|
|||
|
||||
#include <stdint.h> // uint64_t
|
||||
|
||||
struct serialqueue;
|
||||
struct steppersync *steppersync_alloc(
|
||||
struct serialqueue *sq, struct stepcompress **sc_list, int sc_num
|
||||
, int move_num);
|
||||
void steppersync_free(struct steppersync *ss);
|
||||
struct syncemitter;
|
||||
struct stepcompress *syncemitter_get_stepcompress(struct syncemitter *se);
|
||||
void syncemitter_set_stepper_kinematics(struct syncemitter *se
|
||||
, struct stepper_kinematics *sk);
|
||||
struct stepper_kinematics *syncemitter_get_stepper_kinematics(
|
||||
struct syncemitter *se);
|
||||
void syncemitter_queue_msg(struct syncemitter *se, uint64_t req_clock
|
||||
, uint32_t *data, int len);
|
||||
|
||||
struct steppersync;
|
||||
struct syncemitter *steppersync_alloc_syncemitter(
|
||||
struct steppersync *ss, char name[16], int alloc_stepcompress);
|
||||
void steppersync_setup_movequeue(struct steppersync *ss, struct serialqueue *sq
|
||||
, int move_num);
|
||||
void steppersync_set_time(struct steppersync *ss, double time_offset
|
||||
, double mcu_freq);
|
||||
int32_t steppersync_generate_steps(struct steppersync *ss, double gen_steps_time
|
||||
, uint64_t flush_clock);
|
||||
void steppersync_history_expire(struct steppersync *ss, uint64_t end_clock);
|
||||
int steppersync_flush(struct steppersync *ss, uint64_t move_clock);
|
||||
|
||||
struct steppersyncmgr *steppersyncmgr_alloc(void);
|
||||
void steppersyncmgr_free(struct steppersyncmgr *ssm);
|
||||
struct serialqueue;
|
||||
struct steppersync *steppersyncmgr_alloc_steppersync(
|
||||
struct steppersyncmgr *ssm);
|
||||
int32_t steppersyncmgr_gen_steps(struct steppersyncmgr *ssm, double flush_time
|
||||
, double gen_steps_time
|
||||
, double clear_history_time);
|
||||
|
||||
#endif // steppersync.h
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ trapq_alloc(void)
|
|||
list_init(&tq->moves);
|
||||
list_init(&tq->history);
|
||||
struct move *head_sentinel = move_alloc(), *tail_sentinel = move_alloc();
|
||||
head_sentinel->print_time = -1.0;
|
||||
tail_sentinel->print_time = tail_sentinel->move_t = NEVER_TIME;
|
||||
list_add_head(&head_sentinel->node, &tq->moves);
|
||||
list_add_tail(&tail_sentinel->node, &tq->moves);
|
||||
|
|
@ -103,7 +104,7 @@ trapq_add_move(struct trapq *tq, struct move *m)
|
|||
// Add a null move to fill time gap
|
||||
struct move *null_move = move_alloc();
|
||||
null_move->start_pos = m->start_pos;
|
||||
if (!prev->print_time && m->print_time > MAX_NULL_MOVE)
|
||||
if (prev->print_time <= 0. && m->print_time > MAX_NULL_MOVE)
|
||||
// Limit the first null move to improve numerical stability
|
||||
null_move->print_time = m->print_time - MAX_NULL_MOVE;
|
||||
else
|
||||
|
|
@ -227,6 +228,22 @@ trapq_set_position(struct trapq *tq, double print_time
|
|||
list_add_head(&m->node, &tq->history);
|
||||
}
|
||||
|
||||
// Copy the info in a 'struct move' to a 'struct pull_move'
|
||||
static void
|
||||
copy_pull_move(struct pull_move *p, struct move *m)
|
||||
{
|
||||
p->print_time = m->print_time;
|
||||
p->move_t = m->move_t;
|
||||
p->start_v = m->start_v;
|
||||
p->accel = 2. * m->half_accel;
|
||||
p->start_x = m->start_pos.x;
|
||||
p->start_y = m->start_pos.y;
|
||||
p->start_z = m->start_pos.z;
|
||||
p->x_r = m->axes_r.x;
|
||||
p->y_r = m->axes_r.y;
|
||||
p->z_r = m->axes_r.z;
|
||||
}
|
||||
|
||||
// Return history of movement queue
|
||||
int __visible
|
||||
trapq_extract_old(struct trapq *tq, struct pull_move *p, int max
|
||||
|
|
@ -234,21 +251,21 @@ trapq_extract_old(struct trapq *tq, struct pull_move *p, int max
|
|||
{
|
||||
int res = 0;
|
||||
struct move *m;
|
||||
list_for_each_entry_reverse(m, &tq->moves, node) {
|
||||
if (start_time >= m->print_time + m->move_t || res >= max)
|
||||
break;
|
||||
if (end_time <= m->print_time || (!m->start_v && !m->half_accel))
|
||||
continue;
|
||||
copy_pull_move(p, m);
|
||||
p++;
|
||||
res++;
|
||||
}
|
||||
list_for_each_entry(m, &tq->history, node) {
|
||||
if (start_time >= m->print_time + m->move_t || res >= max)
|
||||
break;
|
||||
if (end_time <= m->print_time)
|
||||
continue;
|
||||
p->print_time = m->print_time;
|
||||
p->move_t = m->move_t;
|
||||
p->start_v = m->start_v;
|
||||
p->accel = 2. * m->half_accel;
|
||||
p->start_x = m->start_pos.x;
|
||||
p->start_y = m->start_pos.y;
|
||||
p->start_z = m->start_pos.z;
|
||||
p->x_r = m->axes_r.x;
|
||||
p->y_r = m->axes_r.y;
|
||||
p->z_r = m->axes_r.z;
|
||||
copy_pull_move(p, m);
|
||||
p++;
|
||||
res++;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -212,6 +212,12 @@ class MCU_I2C:
|
|||
"i2c_read oid=%c reg=%*s read_len=%u",
|
||||
"i2c_read_response oid=%c response=%*s", oid=self.oid,
|
||||
cq=self.cmd_queue)
|
||||
def i2c_write_noack(self, data, minclock=0, reqclock=0):
|
||||
if self.i2c_write_cmd is None:
|
||||
self._to_write.append(data)
|
||||
return
|
||||
self.i2c_write_cmd.send([self.oid, data],
|
||||
minclock=minclock, reqclock=reqclock)
|
||||
def i2c_write(self, data, minclock=0, reqclock=0):
|
||||
if self.i2c_write_cmd is None:
|
||||
self._to_write.append(data)
|
||||
|
|
|
|||
|
|
@ -236,6 +236,8 @@ class PrinterLCD:
|
|||
except:
|
||||
logging.exception("Error during display screen update")
|
||||
self.lcd_chip.flush()
|
||||
if self.redraw_request_pending:
|
||||
return self.redraw_time
|
||||
return eventtime + REDRAW_TIME
|
||||
def request_redraw(self):
|
||||
if self.redraw_request_pending:
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ class I2C:
|
|||
hdr = 0x00
|
||||
cmds = bytearray(cmds)
|
||||
cmds.insert(0, hdr)
|
||||
self.i2c.i2c_write(cmds, reqclock=BACKGROUND_PRIORITY_CLOCK)
|
||||
self.i2c.i2c_write_noack(cmds, reqclock=BACKGROUND_PRIORITY_CLOCK)
|
||||
|
||||
# Helper code for toggling a reset pin on startup
|
||||
class ResetHelper:
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ class PrinterExtruderStepper:
|
|||
self.handle_connect)
|
||||
def handle_connect(self):
|
||||
self.extruder_stepper.sync_to_extruder(self.extruder_name)
|
||||
def find_past_position(self, print_time):
|
||||
return self.extruder_stepper.find_past_position(print_time)
|
||||
def get_status(self, eventtime):
|
||||
return self.extruder_stepper.get_status(eventtime)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Utility for manually moving a stepper for diagnostic purposes
|
||||
#
|
||||
# Copyright (C) 2018-2019 Kevin O'Connor <kevin@koconnor.net>
|
||||
# Copyright (C) 2018-2025 Kevin O'Connor <kevin@koconnor.net>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import math, logging
|
||||
|
|
@ -10,7 +10,6 @@ BUZZ_DISTANCE = 1.
|
|||
BUZZ_VELOCITY = BUZZ_DISTANCE / .250
|
||||
BUZZ_RADIANS_DISTANCE = math.radians(1.)
|
||||
BUZZ_RADIANS_VELOCITY = BUZZ_RADIANS_DISTANCE / .250
|
||||
STALL_TIME = 0.100
|
||||
|
||||
# Calculate a move's accel_t, cruise_t, and cruise_v
|
||||
def calc_move_time(dist, speed, accel):
|
||||
|
|
@ -56,24 +55,16 @@ class ForceMove:
|
|||
raise self.printer.config_error("Unknown stepper %s" % (name,))
|
||||
return self.steppers[name]
|
||||
def _force_enable(self, stepper):
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
print_time = toolhead.get_last_move_time()
|
||||
stepper_name = stepper.get_name()
|
||||
stepper_enable = self.printer.lookup_object('stepper_enable')
|
||||
enable = stepper_enable.lookup_enable(stepper.get_name())
|
||||
was_enable = enable.is_motor_enabled()
|
||||
if not was_enable:
|
||||
enable.motor_enable(print_time)
|
||||
toolhead.dwell(STALL_TIME)
|
||||
return was_enable
|
||||
def _restore_enable(self, stepper, was_enable):
|
||||
if not was_enable:
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
toolhead.dwell(STALL_TIME)
|
||||
print_time = toolhead.get_last_move_time()
|
||||
stepper_enable = self.printer.lookup_object('stepper_enable')
|
||||
enable = stepper_enable.lookup_enable(stepper.get_name())
|
||||
enable.motor_disable(print_time)
|
||||
toolhead.dwell(STALL_TIME)
|
||||
did_enable = stepper_enable.set_motors_enable([stepper_name], True)
|
||||
return did_enable
|
||||
def _restore_enable(self, stepper, did_enable):
|
||||
if not did_enable:
|
||||
return
|
||||
stepper_name = stepper.get_name()
|
||||
stepper_enable = self.printer.lookup_object('stepper_enable')
|
||||
stepper_enable.set_motors_enable([stepper_name], False)
|
||||
def manual_move(self, stepper, dist, speed, accel=0.):
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
toolhead.flush_step_generation()
|
||||
|
|
@ -85,7 +76,7 @@ class ForceMove:
|
|||
self.trapq_append(self.trapq, print_time, accel_t, cruise_t, accel_t,
|
||||
0., 0., 0., axis_r, 0., 0., 0., cruise_v, accel)
|
||||
print_time = print_time + accel_t + cruise_t + accel_t
|
||||
toolhead.note_mcu_movequeue_activity(print_time)
|
||||
self.motion_queuing.note_mcu_movequeue_activity(print_time)
|
||||
toolhead.dwell(accel_t + cruise_t + accel_t)
|
||||
toolhead.flush_step_generation()
|
||||
stepper.set_trapq(prev_trapq)
|
||||
|
|
@ -100,7 +91,7 @@ class ForceMove:
|
|||
def cmd_STEPPER_BUZZ(self, gcmd):
|
||||
stepper = self._lookup_stepper(gcmd)
|
||||
logging.info("Stepper buzz %s", stepper.get_name())
|
||||
was_enable = self._force_enable(stepper)
|
||||
did_enable = self._force_enable(stepper)
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
dist, speed = BUZZ_DISTANCE, BUZZ_VELOCITY
|
||||
if stepper.units_in_radians():
|
||||
|
|
@ -110,7 +101,7 @@ class ForceMove:
|
|||
toolhead.dwell(.050)
|
||||
self.manual_move(stepper, -dist, speed)
|
||||
toolhead.dwell(.450)
|
||||
self._restore_enable(stepper, was_enable)
|
||||
self._restore_enable(stepper, did_enable)
|
||||
cmd_FORCE_MOVE_help = "Manually move a stepper; invalidates kinematics"
|
||||
def cmd_FORCE_MOVE(self, gcmd):
|
||||
stepper = self._lookup_stepper(gcmd)
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ class GCodeMove:
|
|||
def _get_gcode_position(self):
|
||||
p = [lp - bp for lp, bp in zip(self.last_position, self.base_position)]
|
||||
p[3] /= self.extrude_factor
|
||||
return p
|
||||
return p[:4]
|
||||
def _get_gcode_speed(self):
|
||||
return self.speed / self.speed_factor
|
||||
def _get_gcode_speed_override(self):
|
||||
|
|
@ -107,7 +107,7 @@ class GCodeMove:
|
|||
'absolute_extrude': self.absolute_extrude,
|
||||
'homing_origin': self.Coord(*self.homing_position[:4]),
|
||||
'position': self.Coord(*self.last_position[:4]),
|
||||
'gcode_position': self.Coord(*move_position[:4]),
|
||||
'gcode_position': self.Coord(*move_position),
|
||||
}
|
||||
def reset_last_position(self):
|
||||
if self.is_printer_ready:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Kinematic input shaper to minimize motion vibrations in XY plane
|
||||
#
|
||||
# Copyright (C) 2019-2020 Kevin O'Connor <kevin@koconnor.net>
|
||||
# Copyright (C) 2020 Dmitry Butyugin <dmbutyugin@google.com>
|
||||
# Copyright (C) 2020-2025 Dmitry Butyugin <dmbutyugin@google.com>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import collections
|
||||
|
|
@ -11,34 +11,39 @@ from . import shaper_defs
|
|||
class InputShaperParams:
|
||||
def __init__(self, axis, config):
|
||||
self.axis = axis
|
||||
self.shapers = {s.name : s.init_func for s in shaper_defs.INPUT_SHAPERS}
|
||||
self.shapers = {s.name : s for s in shaper_defs.INPUT_SHAPERS}
|
||||
shaper_type = config.get('shaper_type', 'mzv')
|
||||
self.shaper_type = config.get('shaper_type_' + axis, shaper_type)
|
||||
if self.shaper_type not in self.shapers:
|
||||
raise config.error(
|
||||
'Unsupported shaper type: %s' % (self.shaper_type,))
|
||||
self.damping_ratio = config.getfloat('damping_ratio_' + axis,
|
||||
shaper_defs.DEFAULT_DAMPING_RATIO,
|
||||
minval=0., maxval=1.)
|
||||
self.damping_ratio = config.getfloat(
|
||||
'damping_ratio_' + axis,
|
||||
shaper_defs.DEFAULT_DAMPING_RATIO, minval=0.,
|
||||
maxval=self.shapers[self.shaper_type].max_damping_ratio)
|
||||
self.shaper_freq = config.getfloat('shaper_freq_' + axis, 0., minval=0.)
|
||||
def update(self, gcmd):
|
||||
axis = self.axis.upper()
|
||||
self.damping_ratio = gcmd.get_float('DAMPING_RATIO_' + axis,
|
||||
self.damping_ratio,
|
||||
minval=0., maxval=1.)
|
||||
self.shaper_freq = gcmd.get_float('SHAPER_FREQ_' + axis,
|
||||
self.shaper_freq, minval=0.)
|
||||
shaper_type = gcmd.get('SHAPER_TYPE', None)
|
||||
if shaper_type is None:
|
||||
shaper_type = gcmd.get('SHAPER_TYPE_' + axis, self.shaper_type)
|
||||
if shaper_type.lower() not in self.shapers:
|
||||
raise gcmd.error('Unsupported shaper type: %s' % (shaper_type,))
|
||||
damping_ratio = gcmd.get_float('DAMPING_RATIO_' + axis,
|
||||
self.damping_ratio, minval=0.)
|
||||
if damping_ratio > self.shapers[shaper_type.lower()].max_damping_ratio:
|
||||
raise gcmd.error(
|
||||
'Too high value of damping_ratio=%.3f for shaper %s'
|
||||
' on axis %c' % (damping_ratio, shaper_type, axis))
|
||||
self.shaper_freq = gcmd.get_float('SHAPER_FREQ_' + axis,
|
||||
self.shaper_freq, minval=0.)
|
||||
self.damping_ratio = damping_ratio
|
||||
self.shaper_type = shaper_type.lower()
|
||||
def get_shaper(self):
|
||||
if not self.shaper_freq:
|
||||
A, T = shaper_defs.get_none_shaper()
|
||||
else:
|
||||
A, T = self.shapers[self.shaper_type](
|
||||
A, T = self.shapers[self.shaper_type].init_func(
|
||||
self.shaper_freq, self.damping_ratio)
|
||||
return len(A), A, T
|
||||
def get_status(self):
|
||||
|
|
@ -95,7 +100,8 @@ class InputShaper:
|
|||
self._update_kinematics)
|
||||
self.toolhead = None
|
||||
self.shapers = [AxisInputShaper('x', config),
|
||||
AxisInputShaper('y', config)]
|
||||
AxisInputShaper('y', config),
|
||||
AxisInputShaper('z', config)]
|
||||
self.input_shaper_stepper_kinematics = []
|
||||
self.orig_stepper_kinematics = []
|
||||
# Register gcode commands
|
||||
|
|
@ -138,6 +144,7 @@ class InputShaper:
|
|||
if self.toolhead is None:
|
||||
# Klipper initialization is not yet completed
|
||||
return
|
||||
self.toolhead.flush_step_generation()
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
kin = self.toolhead.get_kinematics()
|
||||
for s in kin.get_steppers():
|
||||
|
|
@ -146,12 +153,9 @@ class InputShaper:
|
|||
is_sk = self._get_input_shaper_stepper_kinematics(s)
|
||||
if is_sk is None:
|
||||
continue
|
||||
old_delay = ffi_lib.input_shaper_get_step_generation_window(is_sk)
|
||||
ffi_lib.input_shaper_update_sk(is_sk)
|
||||
new_delay = ffi_lib.input_shaper_get_step_generation_window(is_sk)
|
||||
if old_delay != new_delay:
|
||||
self.toolhead.note_step_generation_scan_time(new_delay,
|
||||
old_delay)
|
||||
motion_queuing = self.printer.lookup_object("motion_queuing")
|
||||
motion_queuing.check_step_generation_scan_windows()
|
||||
def _update_input_shaping(self, error=None):
|
||||
self.toolhead.flush_step_generation()
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
|
|
@ -163,16 +167,13 @@ class InputShaper:
|
|||
is_sk = self._get_input_shaper_stepper_kinematics(s)
|
||||
if is_sk is None:
|
||||
continue
|
||||
old_delay = ffi_lib.input_shaper_get_step_generation_window(is_sk)
|
||||
for shaper in self.shapers:
|
||||
if shaper in failed_shapers:
|
||||
continue
|
||||
if not shaper.set_shaper_kinematics(is_sk):
|
||||
failed_shapers.append(shaper)
|
||||
new_delay = ffi_lib.input_shaper_get_step_generation_window(is_sk)
|
||||
if old_delay != new_delay:
|
||||
self.toolhead.note_step_generation_scan_time(new_delay,
|
||||
old_delay)
|
||||
motion_queuing = self.printer.lookup_object("motion_queuing")
|
||||
motion_queuing.check_step_generation_scan_windows()
|
||||
if failed_shapers:
|
||||
error = error or self.printer.command_error
|
||||
raise error("Failed to configure shaper(s) %s with given parameters"
|
||||
|
|
@ -191,8 +192,9 @@ class InputShaper:
|
|||
for shaper in self.shapers:
|
||||
shaper.update(gcmd)
|
||||
self._update_input_shaping()
|
||||
for shaper in self.shapers:
|
||||
shaper.report(gcmd)
|
||||
for ind, shaper in enumerate(self.shapers):
|
||||
if ind < 2 or shaper.is_enabled():
|
||||
shaper.report(gcmd)
|
||||
|
||||
def load_config(config):
|
||||
return InputShaper(config)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import logging
|
||||
import stepper, chelper
|
||||
import stepper
|
||||
from . import force_move
|
||||
|
||||
class ManualStepper:
|
||||
|
|
@ -49,17 +49,9 @@ class ManualStepper:
|
|||
else:
|
||||
self.next_cmd_time = print_time
|
||||
def do_enable(self, enable):
|
||||
self.sync_print_time()
|
||||
stepper_names = [s.get_name() for s in self.steppers]
|
||||
stepper_enable = self.printer.lookup_object('stepper_enable')
|
||||
if enable:
|
||||
for s in self.steppers:
|
||||
se = stepper_enable.lookup_enable(s.get_name())
|
||||
se.motor_enable(self.next_cmd_time)
|
||||
else:
|
||||
for s in self.steppers:
|
||||
se = stepper_enable.lookup_enable(s.get_name())
|
||||
se.motor_disable(self.next_cmd_time)
|
||||
self.sync_print_time()
|
||||
stepper_enable.set_motors_enable(stepper_names, enable)
|
||||
def do_set_position(self, setpos):
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
toolhead.flush_step_generation()
|
||||
|
|
@ -80,8 +72,7 @@ class ManualStepper:
|
|||
self.sync_print_time()
|
||||
self.next_cmd_time = self._submit_move(self.next_cmd_time, movepos,
|
||||
speed, accel)
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
toolhead.note_mcu_movequeue_activity(self.next_cmd_time)
|
||||
self.motion_queuing.note_mcu_movequeue_activity(self.next_cmd_time)
|
||||
if sync:
|
||||
self.sync_print_time()
|
||||
def do_homing_move(self, movepos, speed, accel, triggered, check_trigger):
|
||||
|
|
@ -205,13 +196,13 @@ class ManualStepper:
|
|||
def drip_move(self, newpos, speed, drip_completion):
|
||||
# Submit move to trapq
|
||||
self.sync_print_time()
|
||||
maxtime = self._submit_move(self.next_cmd_time, newpos[0],
|
||||
speed, self.homing_accel)
|
||||
start_time = self.next_cmd_time
|
||||
end_time = self._submit_move(start_time, newpos[0],
|
||||
speed, self.homing_accel)
|
||||
# Drip updates to motors
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
toolhead.drip_update_time(maxtime, drip_completion)
|
||||
self.motion_queuing.drip_update_time(start_time, end_time,
|
||||
drip_completion)
|
||||
# Clear trapq of any remaining parts of movement
|
||||
reactor = self.printer.get_reactor()
|
||||
self.motion_queuing.wipe_trapq(self.trapq)
|
||||
self.rail.set_position([self.commanded_pos, 0., 0.])
|
||||
self.sync_print_time()
|
||||
|
|
|
|||
|
|
@ -6,95 +6,269 @@
|
|||
import logging
|
||||
import chelper
|
||||
|
||||
BGFLUSH_LOW_TIME = 0.200
|
||||
BGFLUSH_HIGH_TIME = 0.400
|
||||
BGFLUSH_SG_LOW_TIME = 0.450
|
||||
BGFLUSH_SG_HIGH_TIME = 0.700
|
||||
BGFLUSH_EXTRA_TIME = 0.250
|
||||
|
||||
MOVE_HISTORY_EXPIRE = 30.
|
||||
MIN_KIN_TIME = 0.100
|
||||
STEPCOMPRESS_FLUSH_TIME = 0.050
|
||||
SDS_CHECK_TIME = 0.001 # step+dir+step filter in stepcompress.c
|
||||
|
||||
DRIP_SEGMENT_TIME = 0.050
|
||||
DRIP_TIME = 0.100
|
||||
|
||||
class PrinterMotionQueuing:
|
||||
def __init__(self, config):
|
||||
self.printer = config.get_printer()
|
||||
self.printer = printer = config.get_printer()
|
||||
self.reactor = printer.get_reactor()
|
||||
# C trapq tracking
|
||||
self.trapqs = []
|
||||
self.stepcompress = []
|
||||
self.steppersyncs = []
|
||||
self.flush_callbacks = []
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves
|
||||
self.steppersync_generate_steps = ffi_lib.steppersync_generate_steps
|
||||
self.steppersync_flush = ffi_lib.steppersync_flush
|
||||
self.steppersync_history_expire = ffi_lib.steppersync_history_expire
|
||||
# C steppersync tracking
|
||||
self.steppersyncmgr = ffi_main.gc(ffi_lib.steppersyncmgr_alloc(),
|
||||
ffi_lib.steppersyncmgr_free)
|
||||
self.syncemitters = []
|
||||
self.steppersyncs = []
|
||||
self.steppersyncmgr_gen_steps = ffi_lib.steppersyncmgr_gen_steps
|
||||
# History expiration
|
||||
self.clear_history_time = 0.
|
||||
is_debug = self.printer.get_start_args().get('debugoutput') is not None
|
||||
self.is_debugoutput = is_debug
|
||||
# Flush notification callbacks
|
||||
self.flush_callbacks = []
|
||||
# Kinematic step generation scan window time tracking
|
||||
self.kin_flush_delay = SDS_CHECK_TIME
|
||||
# MCU tracking
|
||||
self.all_mcus = [m for n, m in printer.lookup_objects(module='mcu')]
|
||||
self.mcu = self.all_mcus[0]
|
||||
self.can_pause = True
|
||||
if self.mcu.is_fileoutput():
|
||||
self.can_pause = False
|
||||
# Flush tracking
|
||||
flush_handler = self._flush_handler
|
||||
if not self.can_pause:
|
||||
flush_handler = self._flush_handler_debug
|
||||
self.flush_timer = self.reactor.register_timer(flush_handler)
|
||||
self.do_kick_flush_timer = True
|
||||
self.last_flush_time = self.last_step_gen_time = 0.
|
||||
self.need_flush_time = self.need_step_gen_time = 0.
|
||||
# Register handlers
|
||||
printer.register_event_handler("klippy:shutdown", self._handle_shutdown)
|
||||
# C trapq tracking
|
||||
def allocate_trapq(self):
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free)
|
||||
self.trapqs.append(trapq)
|
||||
return trapq
|
||||
def allocate_stepcompress(self, mcu, oid):
|
||||
def wipe_trapq(self, trapq):
|
||||
# Expire any remaining movement in the trapq (force to history list)
|
||||
self.trapq_finalize_moves(trapq, self.reactor.NEVER, 0.)
|
||||
def lookup_trapq_append(self):
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
sc = ffi_main.gc(ffi_lib.stepcompress_alloc(oid),
|
||||
ffi_lib.stepcompress_free)
|
||||
self.stepcompress.append((mcu, sc))
|
||||
return sc
|
||||
def allocate_steppersync(self, mcu, serialqueue, move_count):
|
||||
stepqueues = []
|
||||
for sc_mcu, sc in self.stepcompress:
|
||||
if sc_mcu is mcu:
|
||||
stepqueues.append(sc)
|
||||
return ffi_lib.trapq_append
|
||||
# C steppersync tracking
|
||||
def _lookup_steppersync(self, mcu):
|
||||
for ss_mcu, ss in self.steppersyncs:
|
||||
if ss_mcu is mcu:
|
||||
return ss
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
ss = ffi_main.gc(
|
||||
ffi_lib.steppersync_alloc(serialqueue, stepqueues, len(stepqueues),
|
||||
move_count),
|
||||
ffi_lib.steppersync_free)
|
||||
ss = ffi_lib.steppersyncmgr_alloc_steppersync(self.steppersyncmgr)
|
||||
self.steppersyncs.append((mcu, ss))
|
||||
return ss
|
||||
def register_flush_callback(self, callback):
|
||||
self.flush_callbacks.append(callback)
|
||||
def allocate_syncemitter(self, mcu, name, alloc_stepcompress=True):
|
||||
name = name.encode("utf-8")[:15]
|
||||
ss = self._lookup_steppersync(mcu)
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
se = ffi_lib.steppersync_alloc_syncemitter(ss, name, alloc_stepcompress)
|
||||
self.syncemitters.append(se)
|
||||
return se
|
||||
def setup_mcu_movequeue(self, mcu, serialqueue, move_count):
|
||||
# Setup steppersync object for the mcu's main movequeue
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
ss = self._lookup_steppersync(mcu)
|
||||
ffi_lib.steppersync_setup_movequeue(ss, serialqueue, move_count)
|
||||
mcu_freq = float(mcu.seconds_to_clock(1.))
|
||||
ffi_lib.steppersync_set_time(ss, 0., mcu_freq)
|
||||
def stats(self, eventtime):
|
||||
# Globally calibrate mcu clocks (and step generation clocks)
|
||||
sync_time = self.last_step_gen_time
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
for mcu, ss in self.steppersyncs:
|
||||
offset, freq = mcu.calibrate_clock(sync_time, eventtime)
|
||||
ffi_lib.steppersync_set_time(ss, offset, freq)
|
||||
# Calculate history expiration
|
||||
est_print_time = self.mcu.estimated_print_time(eventtime)
|
||||
self.clear_history_time = max(0., est_print_time - MOVE_HISTORY_EXPIRE)
|
||||
return False, ""
|
||||
# Flush notification callbacks
|
||||
def register_flush_callback(self, callback, can_add_trapq=False):
|
||||
if can_add_trapq:
|
||||
self.flush_callbacks = [callback] + self.flush_callbacks
|
||||
else:
|
||||
self.flush_callbacks = self.flush_callbacks + [callback]
|
||||
def unregister_flush_callback(self, callback):
|
||||
if callback in self.flush_callbacks:
|
||||
fcbs = list(self.flush_callbacks)
|
||||
fcbs.remove(callback)
|
||||
self.flush_callbacks = fcbs
|
||||
def flush_motion_queues(self, must_flush_time, max_step_gen_time,
|
||||
trapq_free_time):
|
||||
# Kinematic step generation scan window time tracking
|
||||
def get_kin_flush_delay(self):
|
||||
return self.kin_flush_delay
|
||||
def check_step_generation_scan_windows(self):
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
kin_flush_delay = SDS_CHECK_TIME
|
||||
for se in self.syncemitters:
|
||||
sk = ffi_lib.syncemitter_get_stepper_kinematics(se)
|
||||
if sk == ffi_main.NULL:
|
||||
continue
|
||||
trapq = ffi_lib.itersolve_get_trapq(sk)
|
||||
if trapq == ffi_main.NULL:
|
||||
continue
|
||||
pre_active = ffi_lib.itersolve_get_gen_steps_pre_active(sk)
|
||||
post_active = ffi_lib.itersolve_get_gen_steps_post_active(sk)
|
||||
kin_flush_delay = max(kin_flush_delay, pre_active, post_active)
|
||||
self.kin_flush_delay = kin_flush_delay
|
||||
# Flush tracking
|
||||
def _handle_shutdown(self):
|
||||
self.can_pause = False
|
||||
def _advance_flush_time(self, want_flush_time, want_step_gen_time=0.):
|
||||
flush_time = max(want_flush_time, self.last_flush_time,
|
||||
want_step_gen_time - STEPCOMPRESS_FLUSH_TIME)
|
||||
step_gen_time = max(want_step_gen_time, self.last_step_gen_time,
|
||||
flush_time)
|
||||
# Invoke flush callbacks (if any)
|
||||
for cb in self.flush_callbacks:
|
||||
cb(must_flush_time, max_step_gen_time)
|
||||
# Generate stepper movement and transmit
|
||||
for mcu, ss in self.steppersyncs:
|
||||
clock = max(0, mcu.print_time_to_clock(must_flush_time))
|
||||
# Generate steps
|
||||
ret = self.steppersync_generate_steps(ss, max_step_gen_time, clock)
|
||||
if ret:
|
||||
raise mcu.error("Internal error in MCU '%s' stepcompress"
|
||||
% (mcu.get_name(),))
|
||||
# Flush steps from steppersync
|
||||
ret = self.steppersync_flush(ss, clock)
|
||||
if ret:
|
||||
raise mcu.error("Internal error in MCU '%s' stepcompress"
|
||||
% (mcu.get_name(),))
|
||||
cb(flush_time, step_gen_time)
|
||||
# Determine maximum history to keep
|
||||
trapq_free_time = step_gen_time - self.kin_flush_delay
|
||||
clear_history_time = self.clear_history_time
|
||||
if self.is_debugoutput:
|
||||
clear_history_time = trapq_free_time - MOVE_HISTORY_EXPIRE
|
||||
# Move processed trapq moves to history list, and expire old history
|
||||
if not self.can_pause:
|
||||
clear_history_time = max(0., trapq_free_time - MOVE_HISTORY_EXPIRE)
|
||||
# Generate stepper movement and transmit
|
||||
ret = self.steppersyncmgr_gen_steps(self.steppersyncmgr, flush_time,
|
||||
step_gen_time, clear_history_time)
|
||||
if ret:
|
||||
raise self.mcu.error("Internal error in stepcompress")
|
||||
self.last_flush_time = flush_time
|
||||
self.last_step_gen_time = step_gen_time
|
||||
# Move processed trapq entries to history list, and expire old history
|
||||
for trapq in self.trapqs:
|
||||
self.trapq_finalize_moves(trapq, trapq_free_time,
|
||||
clear_history_time)
|
||||
# Clean up old history entries in stepcompress objects
|
||||
for mcu, ss in self.steppersyncs:
|
||||
clock = max(0, mcu.print_time_to_clock(clear_history_time))
|
||||
self.steppersync_history_expire(ss, clock)
|
||||
def wipe_trapq(self, trapq):
|
||||
# Expire any remaining movement in the trapq (force to history list)
|
||||
NEVER = 9999999999999999.
|
||||
self.trapq_finalize_moves(trapq, NEVER, 0.)
|
||||
def lookup_trapq_append(self):
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
return ffi_lib.trapq_append
|
||||
def stats(self, eventtime):
|
||||
mcu = self.printer.lookup_object('mcu')
|
||||
est_print_time = mcu.estimated_print_time(eventtime)
|
||||
self.clear_history_time = est_print_time - MOVE_HISTORY_EXPIRE
|
||||
return False, ""
|
||||
def _await_flush_time(self, want_flush_time):
|
||||
while 1:
|
||||
if self.last_flush_time >= want_flush_time or not self.can_pause:
|
||||
return
|
||||
systime = self.reactor.monotonic()
|
||||
est_print_time = self.mcu.estimated_print_time(systime)
|
||||
wait = want_flush_time - BGFLUSH_HIGH_TIME - est_print_time
|
||||
if wait <= 0.:
|
||||
return
|
||||
self.reactor.pause(systime + min(1., wait))
|
||||
def flush_all_steps(self):
|
||||
flush_time = self.need_step_gen_time
|
||||
self._await_flush_time(flush_time)
|
||||
self._advance_flush_time(flush_time)
|
||||
def calc_step_gen_restart(self, est_print_time):
|
||||
kin_time = max(est_print_time + MIN_KIN_TIME, self.last_step_gen_time)
|
||||
return kin_time + self.kin_flush_delay
|
||||
def _flush_handler(self, eventtime):
|
||||
try:
|
||||
est_print_time = self.mcu.estimated_print_time(eventtime)
|
||||
aggr_sg_time = self.need_step_gen_time - 2.*self.kin_flush_delay
|
||||
if self.last_step_gen_time < aggr_sg_time:
|
||||
# Actively stepping - want more aggressive flushing
|
||||
want_sg_time = est_print_time + BGFLUSH_SG_HIGH_TIME
|
||||
batch_time = BGFLUSH_SG_HIGH_TIME - BGFLUSH_SG_LOW_TIME
|
||||
next_batch_time = self.last_step_gen_time + batch_time
|
||||
if next_batch_time > est_print_time:
|
||||
# Improve run-to-run reproducibility by batching from last
|
||||
if next_batch_time > want_sg_time + 0.005:
|
||||
# Delay flushing until next wakeup
|
||||
next_batch_time = self.last_step_gen_time
|
||||
want_sg_time = next_batch_time
|
||||
want_sg_time = min(want_sg_time, aggr_sg_time)
|
||||
# Flush motion queues (if needed)
|
||||
if want_sg_time > self.last_step_gen_time:
|
||||
self._advance_flush_time(0., want_sg_time)
|
||||
else:
|
||||
# Not stepping (or only step remnants) - use relaxed flushing
|
||||
want_flush_time = est_print_time + BGFLUSH_HIGH_TIME
|
||||
max_flush_time = self.need_flush_time + BGFLUSH_EXTRA_TIME
|
||||
want_flush_time = min(want_flush_time, max_flush_time)
|
||||
# Flush motion queues (if needed)
|
||||
if want_flush_time > self.last_flush_time:
|
||||
self._advance_flush_time(want_flush_time)
|
||||
# Reschedule timer
|
||||
aggr_sg_time = self.need_step_gen_time - 2.*self.kin_flush_delay
|
||||
if self.last_step_gen_time < aggr_sg_time:
|
||||
waketime = self.last_step_gen_time - BGFLUSH_SG_LOW_TIME
|
||||
else:
|
||||
self.do_kick_flush_timer = True
|
||||
max_flush_time = self.need_flush_time + BGFLUSH_EXTRA_TIME
|
||||
if self.last_flush_time >= max_flush_time:
|
||||
return self.reactor.NEVER
|
||||
waketime = self.last_flush_time - BGFLUSH_LOW_TIME
|
||||
return eventtime + waketime - est_print_time
|
||||
except:
|
||||
logging.exception("Exception in flush_handler")
|
||||
self.printer.invoke_shutdown("Exception in flush_handler")
|
||||
return self.reactor.NEVER
|
||||
def _flush_handler_debug(self, eventtime):
|
||||
# Use custom flushing code when in batch output mode
|
||||
try:
|
||||
faux_time = self.need_flush_time - 1.5
|
||||
batch_time = BGFLUSH_SG_HIGH_TIME - BGFLUSH_SG_LOW_TIME
|
||||
flush_count = 0
|
||||
while self.last_step_gen_time < faux_time:
|
||||
target = self.last_step_gen_time + batch_time
|
||||
if flush_count > 100. and faux_time > target:
|
||||
target += int((faux_time-target) / batch_time) * batch_time
|
||||
self._advance_flush_time(0., target)
|
||||
flush_count += 1
|
||||
if flush_count:
|
||||
return self.reactor.NOW
|
||||
self._advance_flush_time(self.need_flush_time + BGFLUSH_EXTRA_TIME)
|
||||
self.do_kick_flush_timer = True
|
||||
return self.reactor.NEVER
|
||||
except:
|
||||
logging.exception("Exception in flush_handler_debug")
|
||||
self.printer.invoke_shutdown("Exception in flush_handler_debug")
|
||||
return self.reactor.NEVER
|
||||
def note_mcu_movequeue_activity(self, mq_time, is_step_gen=True):
|
||||
if is_step_gen:
|
||||
mq_time += self.kin_flush_delay
|
||||
self.need_step_gen_time = max(self.need_step_gen_time, mq_time)
|
||||
self.need_flush_time = max(self.need_flush_time, mq_time)
|
||||
if self.do_kick_flush_timer:
|
||||
self.do_kick_flush_timer = False
|
||||
self.reactor.update_timer(self.flush_timer, self.reactor.NOW)
|
||||
def drip_update_time(self, start_time, end_time, drip_completion):
|
||||
self._await_flush_time(start_time)
|
||||
# Disable background flushing from timer
|
||||
self.reactor.update_timer(self.flush_timer, self.reactor.NEVER)
|
||||
self.do_kick_flush_timer = False
|
||||
self._advance_flush_time(start_time - SDS_CHECK_TIME, start_time)
|
||||
# Flush in segments until drip_completion signal
|
||||
flush_time = start_time
|
||||
while flush_time < end_time:
|
||||
if drip_completion.test():
|
||||
break
|
||||
curtime = self.reactor.monotonic()
|
||||
est_print_time = self.mcu.estimated_print_time(curtime)
|
||||
wait_time = flush_time - est_print_time - DRIP_TIME
|
||||
if wait_time > 0. and self.can_pause:
|
||||
# Pause before sending more steps
|
||||
drip_completion.wait(curtime + wait_time)
|
||||
continue
|
||||
flush_time = min(flush_time + DRIP_SEGMENT_TIME, end_time)
|
||||
self.note_mcu_movequeue_activity(flush_time)
|
||||
self._advance_flush_time(flush_time - SDS_CHECK_TIME, flush_time)
|
||||
# Restore background flushing
|
||||
self.reactor.update_timer(self.flush_timer, self.reactor.NOW)
|
||||
self._advance_flush_time(flush_time + self.kin_flush_delay)
|
||||
|
||||
def load_config(config):
|
||||
return PrinterMotionQueuing(config)
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ class GCodeRequestQueue:
|
|||
self.rqueue = []
|
||||
self.next_min_flush_time = 0.
|
||||
self.toolhead = None
|
||||
motion_queuing = printer.load_object(config, 'motion_queuing')
|
||||
motion_queuing.register_flush_callback(self._flush_notification)
|
||||
self.motion_queuing = printer.load_object(config, 'motion_queuing')
|
||||
self.motion_queuing.register_flush_callback(self._flush_notification)
|
||||
printer.register_event_handler("klippy:connect", self._handle_connect)
|
||||
def _handle_connect(self):
|
||||
self.toolhead = self.printer.lookup_object('toolhead')
|
||||
|
|
@ -51,11 +51,12 @@ class GCodeRequestQueue:
|
|||
del rqueue[:pos+1]
|
||||
self.next_min_flush_time = next_time + max(min_wait, min_sched_time)
|
||||
# Ensure following queue items are flushed
|
||||
self.toolhead.note_mcu_movequeue_activity(self.next_min_flush_time,
|
||||
is_step_gen=False)
|
||||
self.motion_queuing.note_mcu_movequeue_activity(
|
||||
self.next_min_flush_time, is_step_gen=False)
|
||||
def _queue_request(self, print_time, value):
|
||||
self.rqueue.append((print_time, value))
|
||||
self.toolhead.note_mcu_movequeue_activity(print_time, is_step_gen=False)
|
||||
self.motion_queuing.note_mcu_movequeue_activity(
|
||||
print_time, is_step_gen=False)
|
||||
def queue_gcode_request(self, value):
|
||||
self.toolhead.register_lookahead_callback(
|
||||
(lambda pt: self._queue_request(pt, value)))
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ class PCA9533:
|
|||
minclock = 0
|
||||
if print_time is not None:
|
||||
minclock = self.i2c.get_mcu().print_time_to_clock(print_time)
|
||||
self.i2c.i2c_write([PCA9533_PLS0, ls0], minclock=minclock,
|
||||
reqclock=BACKGROUND_PRIORITY_CLOCK)
|
||||
self.i2c.i2c_write_noack([PCA9533_PLS0, ls0], minclock=minclock,
|
||||
reqclock=BACKGROUND_PRIORITY_CLOCK)
|
||||
def get_status(self, eventtime):
|
||||
return self.led_helper.get_status(eventtime)
|
||||
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ class PCA9632:
|
|||
if self.prev_regs.get(reg) == val:
|
||||
return
|
||||
self.prev_regs[reg] = val
|
||||
self.i2c.i2c_write([reg, val], minclock=minclock,
|
||||
reqclock=BACKGROUND_PRIORITY_CLOCK)
|
||||
self.i2c.i2c_write_noack([reg, val], minclock=minclock,
|
||||
reqclock=BACKGROUND_PRIORITY_CLOCK)
|
||||
def handle_connect(self):
|
||||
#Configure MODE1
|
||||
self.reg_write(PCA9632_MODE1, 0x00)
|
||||
|
|
|
|||
|
|
@ -14,12 +14,14 @@ class MCU_queued_pwm:
|
|||
self._hardware_pwm = False
|
||||
self._cycle_time = 0.100
|
||||
self._max_duration = 2.
|
||||
self._oid = oid = mcu.create_oid()
|
||||
self._oid = mcu.create_oid()
|
||||
printer = mcu.get_printer()
|
||||
motion_queuing = printer.load_object(config, 'motion_queuing')
|
||||
self._stepqueue = motion_queuing.allocate_stepcompress(mcu, oid)
|
||||
sname = config.get_name().split()[-1]
|
||||
self._motion_queuing = printer.load_object(config, 'motion_queuing')
|
||||
self._syncemitter = self._motion_queuing.allocate_syncemitter(
|
||||
mcu, sname, alloc_stepcompress=False)
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
self._stepcompress_queue_mq_msg = ffi_lib.stepcompress_queue_mq_msg
|
||||
self._syncemitter_queue_msg = ffi_lib.syncemitter_queue_msg
|
||||
mcu.register_config_callback(self._build_config)
|
||||
self._pin = pin_params['pin']
|
||||
self._invert = pin_params['invert']
|
||||
|
|
@ -62,8 +64,8 @@ class MCU_queued_pwm:
|
|||
if self._duration_ticks >= 1<<31:
|
||||
raise config_error("PWM pin max duration too large")
|
||||
if self._duration_ticks:
|
||||
motion_queuing = printer.lookup_object('motion_queuing')
|
||||
motion_queuing.register_flush_callback(self._flush_notification)
|
||||
self._motion_queuing.register_flush_callback(
|
||||
self._flush_notification)
|
||||
if self._hardware_pwm:
|
||||
self._pwm_max = self._mcu.get_constant_float("PWM_MAX")
|
||||
self._default_value = self._shutdown_value * self._pwm_max
|
||||
|
|
@ -106,18 +108,15 @@ class MCU_queued_pwm:
|
|||
self._last_clock = clock = max(self._last_clock, clock)
|
||||
self._last_value = val
|
||||
data = (self._set_cmd_tag, self._oid, clock & 0xffffffff, val)
|
||||
ret = self._stepcompress_queue_mq_msg(self._stepqueue, clock,
|
||||
data, len(data))
|
||||
if ret:
|
||||
raise error("Internal error in stepcompress")
|
||||
self._syncemitter_queue_msg(self._syncemitter, clock, data, len(data))
|
||||
# Notify toolhead so that it will flush this update
|
||||
wakeclock = clock
|
||||
if self._last_value != self._default_value:
|
||||
# Continue flushing to resend time
|
||||
wakeclock += self._duration_ticks
|
||||
wake_print_time = self._mcu.clock_to_print_time(wakeclock)
|
||||
self._toolhead.note_mcu_movequeue_activity(wake_print_time,
|
||||
is_step_gen=False)
|
||||
self._motion_queuing.note_mcu_movequeue_activity(wake_print_time,
|
||||
is_step_gen=False)
|
||||
def set_pwm(self, print_time, value):
|
||||
clock = self._mcu.print_time_to_clock(print_time)
|
||||
if self._invert:
|
||||
|
|
|
|||
|
|
@ -1,19 +1,23 @@
|
|||
# A utility class to test resonances of the printer
|
||||
#
|
||||
# Copyright (C) 2020-2024 Dmitry Butyugin <dmbutyugin@google.com>
|
||||
# Copyright (C) 2020-2025 Dmitry Butyugin <dmbutyugin@google.com>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import logging, math, os, time
|
||||
import itertools, logging, math, os, time
|
||||
from . import shaper_calibrate
|
||||
|
||||
class TestAxis:
|
||||
def __init__(self, axis=None, vib_dir=None):
|
||||
if axis is None:
|
||||
self._name = "axis=%.3f,%.3f" % (vib_dir[0], vib_dir[1])
|
||||
self._name = "axis=%.3f,%.3f,%.3f" % (
|
||||
vib_dir[0], vib_dir[1],
|
||||
(vib_dir[2] if len(vib_dir) == 3 else 0.))
|
||||
else:
|
||||
self._name = axis
|
||||
if vib_dir is None:
|
||||
self._vib_dir = (1., 0.) if axis == 'x' else (0., 1.)
|
||||
self._vib_dir = [(1., 0., 0.),
|
||||
(0., 1., 0.),
|
||||
(0., 0., 1.)][ord(axis)-ord('x')]
|
||||
else:
|
||||
s = math.sqrt(sum([d*d for d in vib_dir]))
|
||||
self._vib_dir = [d / s for d in vib_dir]
|
||||
|
|
@ -22,43 +26,54 @@ class TestAxis:
|
|||
return True
|
||||
if self._vib_dir[1] and 'y' in chip_axis:
|
||||
return True
|
||||
if self._vib_dir[2] and 'z' in chip_axis:
|
||||
return True
|
||||
return False
|
||||
def get_dir(self):
|
||||
return self._vib_dir
|
||||
def get_name(self):
|
||||
return self._name
|
||||
def get_point(self, l):
|
||||
return (self._vib_dir[0] * l, self._vib_dir[1] * l)
|
||||
return tuple(d * l for d in self._vib_dir)
|
||||
|
||||
def _parse_axis(gcmd, raw_axis):
|
||||
if raw_axis is None:
|
||||
return None
|
||||
raw_axis = raw_axis.lower()
|
||||
if raw_axis in ['x', 'y']:
|
||||
if raw_axis in ['x', 'y', 'z']:
|
||||
return TestAxis(axis=raw_axis)
|
||||
dirs = raw_axis.split(',')
|
||||
if len(dirs) != 2:
|
||||
if len(dirs) not in (2, 3):
|
||||
raise gcmd.error("Invalid format of axis '%s'" % (raw_axis,))
|
||||
try:
|
||||
dir_x = float(dirs[0].strip())
|
||||
dir_y = float(dirs[1].strip())
|
||||
dir_z = float(dirs[2].strip()) if len(dirs) == 3 else 0.
|
||||
except:
|
||||
raise gcmd.error(
|
||||
"Unable to parse axis direction '%s'" % (raw_axis,))
|
||||
return TestAxis(vib_dir=(dir_x, dir_y))
|
||||
return TestAxis(vib_dir=(dir_x, dir_y, dir_z))
|
||||
|
||||
class VibrationPulseTestGenerator:
|
||||
def __init__(self, config):
|
||||
self.min_freq = config.getfloat('min_freq', 5., minval=1.)
|
||||
self.max_freq = config.getfloat('max_freq', 135.,
|
||||
minval=self.min_freq, maxval=300.)
|
||||
self.max_freq_z = config.getfloat('max_freq_z', 100.,
|
||||
minval=self.min_freq, maxval=300.)
|
||||
self.accel_per_hz = config.getfloat('accel_per_hz', 60., above=0.)
|
||||
self.accel_per_hz_z = config.getfloat('accel_per_hz_z', 15., above=0.)
|
||||
self.hz_per_sec = config.getfloat('hz_per_sec', 1.,
|
||||
minval=0.1, maxval=2.)
|
||||
def prepare_test(self, gcmd):
|
||||
def prepare_test(self, gcmd, is_z):
|
||||
self.freq_start = gcmd.get_float("FREQ_START", self.min_freq, minval=1.)
|
||||
self.freq_end = gcmd.get_float("FREQ_END", self.max_freq,
|
||||
self.freq_end = gcmd.get_float("FREQ_END", (self.max_freq_z if is_z
|
||||
else self.max_freq),
|
||||
minval=self.freq_start, maxval=300.)
|
||||
self.test_accel_per_hz = gcmd.get_float("ACCEL_PER_HZ",
|
||||
self.accel_per_hz, above=0.)
|
||||
(self.accel_per_hz_z if is_z
|
||||
else self.accel_per_hz),
|
||||
above=0.)
|
||||
self.test_hz_per_sec = gcmd.get_float("HZ_PER_SEC", self.hz_per_sec,
|
||||
above=0., maxval=2.)
|
||||
def gen_test(self):
|
||||
|
|
@ -83,12 +98,15 @@ class SweepingVibrationsTestGenerator:
|
|||
def __init__(self, config):
|
||||
self.vibration_generator = VibrationPulseTestGenerator(config)
|
||||
self.sweeping_accel = config.getfloat('sweeping_accel', 400., above=0.)
|
||||
self.sweeping_accel_z = config.getfloat('sweeping_accel_z', 50.,
|
||||
above=0.)
|
||||
self.sweeping_period = config.getfloat('sweeping_period', 1.2,
|
||||
minval=0.)
|
||||
def prepare_test(self, gcmd):
|
||||
self.vibration_generator.prepare_test(gcmd)
|
||||
def prepare_test(self, gcmd, is_z):
|
||||
self.vibration_generator.prepare_test(gcmd, is_z)
|
||||
self.test_sweeping_accel = gcmd.get_float(
|
||||
"SWEEPING_ACCEL", self.sweeping_accel, above=0.)
|
||||
"SWEEPING_ACCEL", (self.sweeping_accel_z if is_z
|
||||
else self.sweeping_accel), above=0.)
|
||||
self.test_sweeping_period = gcmd.get_float(
|
||||
"SWEEPING_PERIOD", self.sweeping_period, minval=0.)
|
||||
def gen_test(self):
|
||||
|
|
@ -120,73 +138,121 @@ class SweepingVibrationsTestGenerator:
|
|||
def get_max_freq(self):
|
||||
return self.vibration_generator.get_max_freq()
|
||||
|
||||
# Helper to lookup Z kinematics limits
|
||||
def lookup_z_limits(configfile):
|
||||
sconfig = configfile.get_status(None)['settings']
|
||||
printer_config = sconfig.get('printer')
|
||||
max_z_velocity = printer_config.get('max_z_velocity')
|
||||
if max_z_velocity is None:
|
||||
max_z_velocity = printer_config.get('max_velocity')
|
||||
max_z_accel = printer_config.get('max_z_accel')
|
||||
if max_z_accel is None:
|
||||
max_z_accel = printer_config.get('max_accel')
|
||||
return max_z_velocity, max_z_accel
|
||||
|
||||
class ResonanceTestExecutor:
|
||||
def __init__(self, config):
|
||||
self.printer = config.get_printer()
|
||||
self.gcode = self.printer.lookup_object('gcode')
|
||||
def run_test(self, test_seq, axis, gcmd):
|
||||
reactor = self.printer.get_reactor()
|
||||
configfile = self.printer.lookup_object('configfile')
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
tpos = toolhead.get_position()
|
||||
X, Y = tpos[:2]
|
||||
X, Y, Z = tpos[:3]
|
||||
# Override maximum acceleration and acceleration to
|
||||
# deceleration based on the maximum test frequency
|
||||
systime = reactor.monotonic()
|
||||
toolhead_info = toolhead.get_status(systime)
|
||||
old_max_velocity = toolhead_info['max_velocity']
|
||||
old_max_accel = toolhead_info['max_accel']
|
||||
old_minimum_cruise_ratio = toolhead_info['minimum_cruise_ratio']
|
||||
max_accel = max([abs(a) for _, a, _ in test_seq])
|
||||
max_velocity = 0.
|
||||
last_v = last_t = 0.
|
||||
for next_t, accel, freq in test_seq:
|
||||
v = last_v + accel * (next_t - last_t)
|
||||
max_velocity = max(max_velocity, abs(v))
|
||||
last_t, last_v = next_t, v
|
||||
if axis.get_dir()[2]:
|
||||
max_z_velocity, max_z_accel = lookup_z_limits(configfile)
|
||||
error_msg = ""
|
||||
if max_velocity > max_z_velocity:
|
||||
error_msg = (
|
||||
"Insufficient maximum Z velocity for these"
|
||||
" test parameters, increase at least to %.f mm/s"
|
||||
" for the resonance test." % (max_velocity+0.5))
|
||||
if max_accel > max_z_accel:
|
||||
if error_msg:
|
||||
error_msg += "\n"
|
||||
error_msg += (
|
||||
"Insufficient maximum Z acceleration for these"
|
||||
" test parameters, increase at least to %.f mm/s^2"
|
||||
" for the resonance test." % (max_accel+0.5))
|
||||
if error_msg:
|
||||
raise gcmd.error(error_msg)
|
||||
self.gcode.run_script_from_command(
|
||||
"SET_VELOCITY_LIMIT ACCEL=%.3f MINIMUM_CRUISE_RATIO=0"
|
||||
% (max_accel,))
|
||||
"SET_VELOCITY_LIMIT VELOCITY=%.f ACCEL=%.f MINIMUM_CRUISE_RATIO=0"
|
||||
% (max_velocity+0.5, max_accel+0.5,))
|
||||
input_shaper = self.printer.lookup_object('input_shaper', None)
|
||||
if input_shaper is not None and not gcmd.get_int('INPUT_SHAPING', 0):
|
||||
input_shaper.disable_shaping()
|
||||
gcmd.respond_info("Disabled [input_shaper] for resonance testing")
|
||||
else:
|
||||
input_shaper = None
|
||||
last_v = last_t = last_accel = last_freq = 0.
|
||||
last_v = last_t = last_freq = 0.
|
||||
for next_t, accel, freq in test_seq:
|
||||
t_seg = next_t - last_t
|
||||
toolhead.set_max_velocities(None, abs(accel), None, None)
|
||||
v = last_v + accel * t_seg
|
||||
abs_v = abs(v)
|
||||
if abs_v < 0.000001:
|
||||
v = abs_v = 0.
|
||||
abs_last_v = abs(last_v)
|
||||
v2 = v * v
|
||||
last_v2 = last_v * last_v
|
||||
half_inv_accel = .5 / accel
|
||||
d = (v2 - last_v2) * half_inv_accel
|
||||
dX, dY = axis.get_point(d)
|
||||
if abs(accel) < 0.000001:
|
||||
v, abs_v = last_v, abs_last_v
|
||||
if abs_v < 0.000001:
|
||||
toolhead.dwell(t_seg)
|
||||
last_t, last_freq = next_t, freq
|
||||
continue
|
||||
half_inv_accel = 0.
|
||||
d = v * t_seg
|
||||
else:
|
||||
toolhead.set_max_velocities(None, abs(accel), None, None)
|
||||
v = last_v + accel * t_seg
|
||||
abs_v = abs(v)
|
||||
if abs_v < 0.000001:
|
||||
v = abs_v = 0.
|
||||
half_inv_accel = .5 / accel
|
||||
d = (v * v - last_v2) * half_inv_accel
|
||||
dX, dY, dZ = axis.get_point(d)
|
||||
nX = X + dX
|
||||
nY = Y + dY
|
||||
nZ = Z + dZ
|
||||
toolhead.limit_next_junction_speed(abs_last_v)
|
||||
if v * last_v < 0:
|
||||
# The move first goes to a complete stop, then changes direction
|
||||
d_decel = -last_v2 * half_inv_accel
|
||||
decel_X, decel_Y = axis.get_point(d_decel)
|
||||
toolhead.move([X + decel_X, Y + decel_Y] + tpos[2:], abs_last_v)
|
||||
toolhead.move([nX, nY] + tpos[2:], abs_v)
|
||||
decel_X, decel_Y, decel_Z = axis.get_point(d_decel)
|
||||
toolhead.move([X + decel_X, Y + decel_Y, Z + decel_Z]
|
||||
+ tpos[3:], abs_last_v)
|
||||
toolhead.move([nX, nY, nZ] + tpos[3:], abs_v)
|
||||
else:
|
||||
toolhead.move([nX, nY] + tpos[2:], max(abs_v, abs_last_v))
|
||||
toolhead.move([nX, nY, nZ] + tpos[3:], max(abs_v, abs_last_v))
|
||||
if math.floor(freq) > math.floor(last_freq):
|
||||
gcmd.respond_info("Testing frequency %.0f Hz" % (freq,))
|
||||
reactor.pause(reactor.monotonic() + 0.01)
|
||||
X, Y = nX, nY
|
||||
X, Y, Z = nX, nY, nZ
|
||||
last_t = next_t
|
||||
last_v = v
|
||||
last_accel = accel
|
||||
last_freq = freq
|
||||
if last_v:
|
||||
d_decel = -.5 * last_v2 / old_max_accel
|
||||
decel_X, decel_Y = axis.get_point(d_decel)
|
||||
decel_X, decel_Y, decel_Z = axis.get_point(d_decel)
|
||||
toolhead.set_max_velocities(None, old_max_accel, None, None)
|
||||
toolhead.move([X + decel_X, Y + decel_Y] + tpos[2:], abs(last_v))
|
||||
toolhead.move([X + decel_X, Y + decel_Y, Z + decel_Z] + tpos[3:],
|
||||
abs(last_v))
|
||||
# Restore the original acceleration values
|
||||
self.gcode.run_script_from_command(
|
||||
"SET_VELOCITY_LIMIT ACCEL=%.3f MINIMUM_CRUISE_RATIO=%.3f"
|
||||
% (old_max_accel, old_minimum_cruise_ratio))
|
||||
("SET_VELOCITY_LIMIT VELOCITY=%.3f ACCEL=%.3f"
|
||||
+ " MINIMUM_CRUISE_RATIO=%.3f") % (old_max_velocity, old_max_accel,
|
||||
old_minimum_cruise_ratio))
|
||||
# Restore input shaper if it was disabled for resonance testing
|
||||
if input_shaper is not None:
|
||||
input_shaper.enable_shaping()
|
||||
|
|
@ -199,13 +265,21 @@ class ResonanceTester:
|
|||
self.generator = SweepingVibrationsTestGenerator(config)
|
||||
self.executor = ResonanceTestExecutor(config)
|
||||
if not config.get('accel_chip_x', None):
|
||||
self.accel_chip_names = [('xy', config.get('accel_chip').strip())]
|
||||
accel_chip_names = [
|
||||
('xy', config.get('accel_chip').strip()),
|
||||
('z', config.get('accel_chip_z', '').strip())]
|
||||
else:
|
||||
self.accel_chip_names = [
|
||||
('x', config.get('accel_chip_x').strip()),
|
||||
('y', config.get('accel_chip_y').strip())]
|
||||
if self.accel_chip_names[0][1] == self.accel_chip_names[1][1]:
|
||||
self.accel_chip_names = [('xy', self.accel_chip_names[0][1])]
|
||||
accel_chip_names = [
|
||||
('x', config.get('accel_chip_x').strip()),
|
||||
('y', config.get('accel_chip_y').strip()),
|
||||
('z', config.get('accel_chip_z', '').strip())]
|
||||
get_chip_name = lambda t: t[1]
|
||||
# Group chips by their axes
|
||||
self.accel_chip_names = [
|
||||
(''.join(sorted(axis for axis, _ in vals)), chip_name)
|
||||
for chip_name, vals in itertools.groupby(
|
||||
sorted(accel_chip_names, key=get_chip_name),
|
||||
key=get_chip_name)]
|
||||
self.max_smoothing = config.getfloat('max_smoothing', None, minval=0.05)
|
||||
self.probe_points = config.getlists('probe_points', seps=(',', '\n'),
|
||||
parser=float, count=3)
|
||||
|
|
@ -223,16 +297,25 @@ class ResonanceTester:
|
|||
self.printer.register_event_handler("klippy:connect", self.connect)
|
||||
|
||||
def connect(self):
|
||||
self.accel_chips = [
|
||||
(chip_axis, self.printer.lookup_object(chip_name))
|
||||
for chip_axis, chip_name in self.accel_chip_names]
|
||||
self.accel_chips = []
|
||||
for chip_axis, chip_name in self.accel_chip_names:
|
||||
if not chip_name:
|
||||
continue
|
||||
chip = self.printer.lookup_object(chip_name)
|
||||
if not hasattr(chip, 'start_internal_client'):
|
||||
raise self.printer.config_error(
|
||||
"'%s' is not an accelerometer" % chip_name)
|
||||
self.accel_chips.append((chip_axis, chip))
|
||||
|
||||
def _run_test(self, gcmd, axes, helper, raw_name_suffix=None,
|
||||
accel_chips=None, test_point=None):
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
calibration_data = {axis: None for axis in axes}
|
||||
|
||||
self.generator.prepare_test(gcmd)
|
||||
has_z = [axis.get_dir()[2] for axis in axes]
|
||||
if all(has_z) != any(has_z):
|
||||
raise gcmd.error("Cannot test Z axis together with other axes")
|
||||
self.generator.prepare_test(gcmd, is_z=all(has_z))
|
||||
|
||||
test_points = [test_point] if test_point else self.probe_points
|
||||
|
||||
|
|
@ -257,6 +340,10 @@ class ResonanceTester:
|
|||
for chip in accel_chips:
|
||||
aclient = chip.start_internal_client()
|
||||
raw_values.append((axis, aclient, chip.name))
|
||||
if not raw_values:
|
||||
raise gcmd.error(
|
||||
"No accelerometers specified that can measure"
|
||||
" resonances over axis '%s'" % axis.get_name())
|
||||
|
||||
# Generate moves
|
||||
test_seq = self.generator.gen_test()
|
||||
|
|
@ -267,7 +354,8 @@ class ResonanceTester:
|
|||
raw_name = self.get_filename(
|
||||
'raw_data', raw_name_suffix, axis,
|
||||
point if len(test_points) > 1 else None,
|
||||
chip_name if accel_chips is not None else None,)
|
||||
chip_name if (accel_chips is not None
|
||||
or len(raw_values) > 1) else None)
|
||||
aclient.write_to_file(raw_name)
|
||||
gcmd.respond_info(
|
||||
"Writing raw accelerometer data to "
|
||||
|
|
@ -288,7 +376,13 @@ class ResonanceTester:
|
|||
def _parse_chips(self, accel_chips):
|
||||
parsed_chips = []
|
||||
for chip_name in accel_chips.split(','):
|
||||
chip = self.printer.lookup_object(chip_name.strip())
|
||||
chip = self.printer.lookup_object(chip_name.strip(), None)
|
||||
if chip is None:
|
||||
raise self.printer.command_error("Name '%s' is not valid for"
|
||||
" CHIPS parameter" % chip_name)
|
||||
if not hasattr(chip, 'start_internal_client'):
|
||||
raise self.printer.command_error(
|
||||
"'%s' is not an accelerometer" % chip_name)
|
||||
parsed_chips.append(chip)
|
||||
return parsed_chips
|
||||
def _get_max_calibration_freq(self):
|
||||
|
|
@ -349,7 +443,7 @@ class ResonanceTester:
|
|||
axis = gcmd.get("AXIS", None)
|
||||
if not axis:
|
||||
calibrate_axes = [TestAxis('x'), TestAxis('y')]
|
||||
elif axis.lower() not in 'xy':
|
||||
elif axis.lower() not in 'xyz':
|
||||
raise gcmd.error("Unsupported axis '%s'" % (axis,))
|
||||
else:
|
||||
calibrate_axes = [TestAxis(axis.lower())]
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ SHAPER_VIBRATION_REDUCTION=20.
|
|||
DEFAULT_DAMPING_RATIO = 0.1
|
||||
|
||||
InputShaperCfg = collections.namedtuple(
|
||||
'InputShaperCfg', ('name', 'init_func', 'min_freq'))
|
||||
'InputShaperCfg',
|
||||
('name', 'init_func', 'min_freq', 'max_damping_ratio'))
|
||||
|
||||
def get_none_shaper():
|
||||
return ([], [])
|
||||
|
|
@ -46,57 +47,75 @@ def get_mzv_shaper(shaper_freq, damping_ratio):
|
|||
def get_ei_shaper(shaper_freq, damping_ratio):
|
||||
v_tol = 1. / SHAPER_VIBRATION_REDUCTION # vibration tolerance
|
||||
df = math.sqrt(1. - damping_ratio**2)
|
||||
K = math.exp(-damping_ratio * math.pi / df)
|
||||
t_d = 1. / (shaper_freq * df)
|
||||
dr = damping_ratio
|
||||
|
||||
a1 = .25 * (1. + v_tol)
|
||||
a2 = .5 * (1. - v_tol) * K
|
||||
a3 = a1 * K * K
|
||||
a1 = (0.24968 + 0.24961 * v_tol) + (( 0.80008 + 1.23328 * v_tol) +
|
||||
( 0.49599 + 3.17316 * v_tol) * dr) * dr
|
||||
a3 = (0.25149 + 0.21474 * v_tol) + ((-0.83249 + 1.41498 * v_tol) +
|
||||
( 0.85181 - 4.90094 * v_tol) * dr) * dr
|
||||
a2 = 1. - a1 - a3
|
||||
|
||||
t2 = 0.4999 + ((( 0.46159 + 8.57843 * v_tol) * v_tol) +
|
||||
(((4.26169 - 108.644 * v_tol) * v_tol) +
|
||||
((1.75601 + 336.989 * v_tol) * v_tol) * dr) * dr) * dr
|
||||
|
||||
A = [a1, a2, a3]
|
||||
T = [0., .5*t_d, t_d]
|
||||
T = [0., t2 * t_d, t_d]
|
||||
return (A, T)
|
||||
|
||||
def _get_shaper_from_expansion_coeffs(shaper_freq, damping_ratio, t, a):
|
||||
tau = 1. / shaper_freq
|
||||
T = []
|
||||
A = []
|
||||
n = len(a)
|
||||
k = len(a[0])
|
||||
for i in range(n):
|
||||
u = t[i][k-1]
|
||||
v = a[i][k-1]
|
||||
for j in range(k-1):
|
||||
u = u * damping_ratio + t[i][k-j-2]
|
||||
v = v * damping_ratio + a[i][k-j-2]
|
||||
T.append(u * tau)
|
||||
A.append(v)
|
||||
return (A, T)
|
||||
|
||||
def get_2hump_ei_shaper(shaper_freq, damping_ratio):
|
||||
v_tol = 1. / SHAPER_VIBRATION_REDUCTION # vibration tolerance
|
||||
df = math.sqrt(1. - damping_ratio**2)
|
||||
K = math.exp(-damping_ratio * math.pi / df)
|
||||
t_d = 1. / (shaper_freq * df)
|
||||
|
||||
V2 = v_tol**2
|
||||
X = pow(V2 * (math.sqrt(1. - V2) + 1.), 1./3.)
|
||||
a1 = (3.*X*X + 2.*X + 3.*V2) / (16.*X)
|
||||
a2 = (.5 - a1) * K
|
||||
a3 = a2 * K
|
||||
a4 = a1 * K * K * K
|
||||
|
||||
A = [a1, a2, a3, a4]
|
||||
T = [0., .5*t_d, t_d, 1.5*t_d]
|
||||
return (A, T)
|
||||
t = [[0., 0., 0., 0.],
|
||||
[0.49890, 0.16270, -0.54262, 6.16180],
|
||||
[0.99748, 0.18382, -1.58270, 8.17120],
|
||||
[1.49920, -0.09297, -0.28338, 1.85710]]
|
||||
a = [[0.16054, 0.76699, 2.26560, -1.22750],
|
||||
[0.33911, 0.45081, -2.58080, 1.73650],
|
||||
[0.34089, -0.61533, -0.68765, 0.42261],
|
||||
[0.15997, -0.60246, 1.00280, -0.93145]]
|
||||
return _get_shaper_from_expansion_coeffs(shaper_freq, damping_ratio, t, a)
|
||||
|
||||
def get_3hump_ei_shaper(shaper_freq, damping_ratio):
|
||||
v_tol = 1. / SHAPER_VIBRATION_REDUCTION # vibration tolerance
|
||||
df = math.sqrt(1. - damping_ratio**2)
|
||||
K = math.exp(-damping_ratio * math.pi / df)
|
||||
t_d = 1. / (shaper_freq * df)
|
||||
|
||||
K2 = K*K
|
||||
a1 = 0.0625 * (1. + 3. * v_tol + 2. * math.sqrt(2. * (v_tol + 1.) * v_tol))
|
||||
a2 = 0.25 * (1. - v_tol) * K
|
||||
a3 = (0.5 * (1. + v_tol) - 2. * a1) * K2
|
||||
a4 = a2 * K2
|
||||
a5 = a1 * K2 * K2
|
||||
|
||||
A = [a1, a2, a3, a4, a5]
|
||||
T = [0., .5*t_d, t_d, 1.5*t_d, 2.*t_d]
|
||||
return (A, T)
|
||||
t = [[0., 0., 0., 0.],
|
||||
[0.49974, 0.23834, 0.44559, 12.4720],
|
||||
[0.99849, 0.29808, -2.36460, 23.3990],
|
||||
[1.49870, 0.10306, -2.01390, 17.0320],
|
||||
[1.99960, -0.28231, 0.61536, 5.40450]]
|
||||
a = [[0.11275, 0.76632, 3.29160 -1.44380],
|
||||
[0.23698, 0.61164, -2.57850, 4.85220],
|
||||
[0.30008, -0.19062, -2.14560, 0.13744],
|
||||
[0.23775, -0.73297, 0.46885, -2.08650],
|
||||
[0.11244, -0.45439, 0.96382, -1.46000]]
|
||||
return _get_shaper_from_expansion_coeffs(shaper_freq, damping_ratio, t, a)
|
||||
|
||||
# min_freq for each shaper is chosen to have projected max_accel ~= 1500
|
||||
INPUT_SHAPERS = [
|
||||
InputShaperCfg('zv', get_zv_shaper, min_freq=21.),
|
||||
InputShaperCfg('mzv', get_mzv_shaper, min_freq=23.),
|
||||
InputShaperCfg('zvd', get_zvd_shaper, min_freq=29.),
|
||||
InputShaperCfg('ei', get_ei_shaper, min_freq=29.),
|
||||
InputShaperCfg('2hump_ei', get_2hump_ei_shaper, min_freq=39.),
|
||||
InputShaperCfg('3hump_ei', get_3hump_ei_shaper, min_freq=48.),
|
||||
InputShaperCfg(name='zv', init_func=get_zv_shaper,
|
||||
min_freq=21., max_damping_ratio=0.99),
|
||||
InputShaperCfg(name='mzv', init_func=get_mzv_shaper,
|
||||
min_freq=23., max_damping_ratio=0.99),
|
||||
InputShaperCfg(name='zvd', init_func=get_zvd_shaper,
|
||||
min_freq=29., max_damping_ratio=0.99),
|
||||
InputShaperCfg(name='ei', init_func=get_ei_shaper,
|
||||
min_freq=29., max_damping_ratio=0.4),
|
||||
InputShaperCfg(name='2hump_ei', init_func=get_2hump_ei_shaper,
|
||||
min_freq=39., max_damping_ratio=0.3),
|
||||
InputShaperCfg(name='3hump_ei', init_func=get_3hump_ei_shaper,
|
||||
min_freq=48., max_damping_ratio=0.2),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Support for enable pins on stepper motor drivers
|
||||
#
|
||||
# Copyright (C) 2019-2021 Kevin O'Connor <kevin@koconnor.net>
|
||||
# Copyright (C) 2019-2025 Kevin O'Connor <kevin@koconnor.net>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import logging
|
||||
|
|
@ -88,30 +88,38 @@ class PrinterStepperEnable:
|
|||
name = mcu_stepper.get_name()
|
||||
enable = setup_enable_pin(self.printer, config.get('enable_pin', None))
|
||||
self.enable_lines[name] = EnableTracking(mcu_stepper, enable)
|
||||
def set_motors_enable(self, stepper_names, enable):
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
# Flush steps to ensure all auto enable callbacks invoked
|
||||
toolhead.flush_step_generation()
|
||||
print_time = None
|
||||
did_change = False
|
||||
for stepper_name in stepper_names:
|
||||
el = self.enable_lines[stepper_name]
|
||||
if el.is_motor_enabled() == enable:
|
||||
continue
|
||||
if print_time is None:
|
||||
# Dwell for sufficient delay from any previous auto enable
|
||||
if not enable:
|
||||
toolhead.dwell(DISABLE_STALL_TIME)
|
||||
print_time = toolhead.get_last_move_time()
|
||||
if enable:
|
||||
el.motor_enable(print_time)
|
||||
else:
|
||||
el.motor_disable(print_time)
|
||||
did_change = True
|
||||
# Dwell to ensure sufficient delay prior to a future auto enable
|
||||
if did_change and not enable:
|
||||
toolhead.dwell(DISABLE_STALL_TIME)
|
||||
return did_change
|
||||
def motor_off(self):
|
||||
self.set_motors_enable(self.get_steppers(), False)
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
toolhead.dwell(DISABLE_STALL_TIME)
|
||||
print_time = toolhead.get_last_move_time()
|
||||
for el in self.enable_lines.values():
|
||||
el.motor_disable(print_time)
|
||||
toolhead.get_kinematics().clear_homing_state("xyz")
|
||||
self.printer.send_event("stepper_enable:motor_off", print_time)
|
||||
toolhead.dwell(DISABLE_STALL_TIME)
|
||||
def motor_debug_enable(self, stepper, enable):
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
toolhead.dwell(DISABLE_STALL_TIME)
|
||||
print_time = toolhead.get_last_move_time()
|
||||
el = self.enable_lines[stepper]
|
||||
if enable:
|
||||
el.motor_enable(print_time)
|
||||
logging.info("%s has been manually enabled", stepper)
|
||||
else:
|
||||
el.motor_disable(print_time)
|
||||
logging.info("%s has been manually disabled", stepper)
|
||||
toolhead.dwell(DISABLE_STALL_TIME)
|
||||
self.printer.send_event("stepper_enable:motor_off")
|
||||
def get_status(self, eventtime):
|
||||
steppers = { name: et.is_motor_enabled()
|
||||
for (name, et) in self.enable_lines.items() }
|
||||
for (name, et) in self.enable_lines.items() }
|
||||
return {'steppers': steppers}
|
||||
def _handle_request_restart(self, print_time):
|
||||
self.motor_off()
|
||||
|
|
@ -126,7 +134,11 @@ class PrinterStepperEnable:
|
|||
% (stepper_name,))
|
||||
return
|
||||
stepper_enable = gcmd.get_int('ENABLE', 1)
|
||||
self.motor_debug_enable(stepper_name, stepper_enable)
|
||||
self.set_motors_enable([stepper_name], stepper_enable)
|
||||
if stepper_enable:
|
||||
logging.info("%s has been manually enabled", stepper_name)
|
||||
else:
|
||||
logging.info("%s has been manually disabled", stepper_name)
|
||||
def lookup_enable(self, name):
|
||||
if name not in self.enable_lines:
|
||||
raise self.printer.config_error("Unknown stepper '%s'" % (name,))
|
||||
|
|
|
|||
|
|
@ -92,7 +92,8 @@ class SX1509(object):
|
|||
# Byte
|
||||
data += [self.reg_i_on_dict[reg] & 0xFF]
|
||||
clock = self._mcu.print_time_to_clock(print_time)
|
||||
self._i2c.i2c_write(data, minclock=self._last_clock, reqclock=clock)
|
||||
self._i2c.i2c_write_noack(data, minclock=self._last_clock,
|
||||
reqclock=clock)
|
||||
self._last_clock = clock
|
||||
|
||||
class SX1509_digital_out(object):
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ class TemperatureProbe:
|
|||
self.start_pos = []
|
||||
|
||||
# Register GCode Commands
|
||||
pname = self.name.split(maxsplit=1)[-1]
|
||||
pname = self.name.split(None, 1)[-1]
|
||||
self.gcode.register_mux_command(
|
||||
"TEMPERATURE_PROBE_CALIBRATE", "PROBE", pname,
|
||||
self.cmd_TEMPERATURE_PROBE_CALIBRATE,
|
||||
|
|
@ -357,8 +357,8 @@ class TemperatureProbe:
|
|||
self._check_homed()
|
||||
probe = self._get_probe()
|
||||
probe_name = probe.get_status(None)["name"]
|
||||
short_name = probe_name.split(maxsplit=1)[-1]
|
||||
if short_name != self.name.split(maxsplit=1)[-1]:
|
||||
short_name = probe_name.split(None, 1)[-1]
|
||||
if short_name != self.name.split(None, 1)[-1]:
|
||||
raise self.gcode.error(
|
||||
"[%s] not linked to registered probe [%s]."
|
||||
% (self.name, probe_name)
|
||||
|
|
@ -588,7 +588,7 @@ class EddyDriftCompensation:
|
|||
temps[idx] = cur_temp
|
||||
probe_samples[idx].append(sample)
|
||||
return True
|
||||
sect_name = "probe_eddy_current " + self.name.split(maxsplit=1)[-1]
|
||||
sect_name = "probe_eddy_current " + self.name.split(None, 1)[-1]
|
||||
self.printer.lookup_object(sect_name).add_client(_on_bulk_data_recd)
|
||||
for i in range(DRIFT_SAMPLE_COUNT):
|
||||
if i == 0:
|
||||
|
|
|
|||
|
|
@ -259,7 +259,7 @@ class VirtualSD:
|
|||
continue
|
||||
# Pause if any other request is pending in the gcode class
|
||||
if gcode_mutex.test():
|
||||
self.reactor.pause(self.reactor.monotonic() + 0.100)
|
||||
self.reactor.pause(self.reactor.monotonic() + 0.050)
|
||||
continue
|
||||
# Dispatch command
|
||||
self.cmd_from_sd = True
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ class ZAdjustStatus:
|
|||
self.applied = False
|
||||
def get_status(self, eventtime):
|
||||
return {'applied': self.applied}
|
||||
def _motor_off(self, print_time):
|
||||
def _motor_off(self):
|
||||
self.reset()
|
||||
|
||||
class RetryHelper:
|
||||
|
|
|
|||
|
|
@ -430,18 +430,17 @@ class GCodeIO:
|
|||
self.gcode.request_restart('exit')
|
||||
pending_commands.append("")
|
||||
# Handle case where multiple commands pending
|
||||
if self.is_processing_data or len(pending_commands) > 1:
|
||||
if len(pending_commands) < 20:
|
||||
# Check for M112 out-of-order
|
||||
for line in lines:
|
||||
if self.m112_r.match(line) is not None:
|
||||
self.gcode.cmd_M112(None)
|
||||
if self.is_processing_data:
|
||||
if len(pending_commands) >= 20:
|
||||
# Stop reading input
|
||||
self.reactor.unregister_fd(self.fd_handle)
|
||||
self.fd_handle = None
|
||||
return
|
||||
if len(pending_commands) < 20:
|
||||
# Check for M112 out-of-order
|
||||
for line in lines:
|
||||
if self.m112_r.match(line) is not None:
|
||||
self.gcode.cmd_M112(None)
|
||||
if self.is_processing_data:
|
||||
if len(pending_commands) >= 20:
|
||||
# Stop reading input
|
||||
self.reactor.unregister_fd(self.fd_handle)
|
||||
self.fd_handle = None
|
||||
return
|
||||
# Process commands
|
||||
self.is_processing_data = True
|
||||
while pending_commands:
|
||||
|
|
|
|||
|
|
@ -50,9 +50,11 @@ class ExtruderStepper:
|
|||
def sync_to_extruder(self, extruder_name):
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
toolhead.flush_step_generation()
|
||||
motion_queuing = self.printer.lookup_object('motion_queuing')
|
||||
if not extruder_name:
|
||||
self.stepper.set_trapq(None)
|
||||
self.motion_queue = None
|
||||
motion_queuing.check_step_generation_scan_windows()
|
||||
return
|
||||
extruder = self.printer.lookup_object(extruder_name, None)
|
||||
if extruder is None or not isinstance(extruder, PrinterExtruder):
|
||||
|
|
@ -61,6 +63,7 @@ class ExtruderStepper:
|
|||
self.stepper.set_position([extruder.last_position, 0., 0.])
|
||||
self.stepper.set_trapq(extruder.get_trapq())
|
||||
self.motion_queue = extruder_name
|
||||
motion_queuing.check_step_generation_scan_windows()
|
||||
def _set_pressure_advance(self, pressure_advance, smooth_time):
|
||||
old_smooth_time = self.pressure_advance_smooth_time
|
||||
if not self.pressure_advance:
|
||||
|
|
@ -69,14 +72,18 @@ class ExtruderStepper:
|
|||
if not pressure_advance:
|
||||
new_smooth_time = 0.
|
||||
toolhead = self.printer.lookup_object("toolhead")
|
||||
if new_smooth_time != old_smooth_time:
|
||||
toolhead.note_step_generation_scan_time(
|
||||
new_smooth_time * .5, old_delay=old_smooth_time * .5)
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
espa = ffi_lib.extruder_set_pressure_advance
|
||||
toolhead.register_lookahead_callback(
|
||||
lambda print_time: espa(self.sk_extruder, print_time,
|
||||
pressure_advance, new_smooth_time))
|
||||
if new_smooth_time != old_smooth_time:
|
||||
# Need full kinematic flush to change the smooth time
|
||||
toolhead.flush_step_generation()
|
||||
espa(self.sk_extruder, 0., pressure_advance, new_smooth_time)
|
||||
motion_queuing = self.printer.lookup_object('motion_queuing')
|
||||
motion_queuing.check_step_generation_scan_windows()
|
||||
else:
|
||||
toolhead.register_lookahead_callback(
|
||||
lambda print_time: espa(self.sk_extruder, print_time,
|
||||
pressure_advance, new_smooth_time))
|
||||
self.pressure_advance = pressure_advance
|
||||
self.pressure_advance_smooth_time = smooth_time
|
||||
cmd_SET_PRESSURE_ADVANCE_help = "Set pressure advance parameters"
|
||||
|
|
|
|||
|
|
@ -327,12 +327,16 @@ def main():
|
|||
extra_git_desc += "\nTracked URL: %s" % (git_info["url"])
|
||||
start_args['software_version'] = git_vers
|
||||
start_args['cpu_info'] = util.get_cpu_info()
|
||||
start_args['device'] = util.get_device_info()
|
||||
start_args['linux_version'] = util.get_linux_version()
|
||||
if bglogger is not None:
|
||||
versions = "\n".join([
|
||||
"Args: %s" % (sys.argv,),
|
||||
"Git version: %s%s" % (repr(start_args['software_version']),
|
||||
extra_git_desc),
|
||||
"CPU: %s" % (start_args['cpu_info'],),
|
||||
"Device: %s" % (start_args['device']),
|
||||
"Linux: %s" % (start_args['linux_version']),
|
||||
"Python: %s" % (repr(sys.version),)])
|
||||
logging.info(versions)
|
||||
elif not options.debugoutput:
|
||||
|
|
|
|||
807
klippy/mcu.py
807
klippy/mcu.py
|
|
@ -549,7 +549,7 @@ class MCU_adc:
|
|||
|
||||
|
||||
######################################################################
|
||||
# Main MCU class
|
||||
# Main MCU class (and its helper classes)
|
||||
######################################################################
|
||||
|
||||
# Minimum time host needs to get scheduled events queued into mcu
|
||||
|
|
@ -557,103 +557,31 @@ MIN_SCHEDULE_TIME = 0.100
|
|||
# Maximum time all MCUs can internally schedule into the future
|
||||
MAX_NOMINAL_DURATION = 3.0
|
||||
|
||||
class MCU:
|
||||
error = error
|
||||
def __init__(self, config, clocksync):
|
||||
# Support for restarting a micro-controller
|
||||
class MCURestartHelper:
|
||||
def __init__(self, config, conn_helper):
|
||||
self._printer = printer = config.get_printer()
|
||||
self._clocksync = clocksync
|
||||
self._conn_helper = conn_helper
|
||||
self._mcu = mcu = conn_helper.get_mcu()
|
||||
self._serial = conn_helper.get_serial()
|
||||
self._clocksync = conn_helper.get_clocksync()
|
||||
self._reactor = printer.get_reactor()
|
||||
self._name = config.get_name()
|
||||
if self._name.startswith('mcu '):
|
||||
self._name = self._name[4:]
|
||||
# Serial port
|
||||
name = self._name
|
||||
self._serial = serialhdl.SerialReader(self._reactor, mcu_name = name)
|
||||
self._baud = 0
|
||||
self._canbus_iface = None
|
||||
canbus_uuid = config.get('canbus_uuid', None)
|
||||
if canbus_uuid is not None:
|
||||
self._serialport = canbus_uuid
|
||||
self._canbus_iface = config.get('canbus_interface', 'can0')
|
||||
cbid = self._printer.load_object(config, 'canbus_ids')
|
||||
cbid.add_uuid(config, canbus_uuid, self._canbus_iface)
|
||||
self._printer.load_object(config, 'canbus_stats %s' % (self._name,))
|
||||
else:
|
||||
self._serialport = config.get('serial')
|
||||
if not (self._serialport.startswith("/dev/rpmsg_")
|
||||
or self._serialport.startswith("/tmp/klipper_host_")):
|
||||
self._baud = config.getint('baud', 250000, minval=2400)
|
||||
# Restarts
|
||||
self._name = mcu.get_name()
|
||||
# Restart tracking
|
||||
restart_methods = [None, 'arduino', 'cheetah', 'command', 'rpi_usb']
|
||||
self._restart_method = 'command'
|
||||
if self._baud:
|
||||
serialport, baud = conn_helper.get_serialport()
|
||||
if baud:
|
||||
self._restart_method = config.getchoice('restart_method',
|
||||
restart_methods, None)
|
||||
self._reset_cmd = self._config_reset_cmd = None
|
||||
self._is_mcu_bridge = False
|
||||
self._emergency_stop_cmd = None
|
||||
self._is_shutdown = self._is_timeout = False
|
||||
self._shutdown_clock = 0
|
||||
self._shutdown_msg = ""
|
||||
# Config building
|
||||
printer.lookup_object('pins').register_chip(self._name, self)
|
||||
self._oid_count = 0
|
||||
self._config_callbacks = []
|
||||
self._config_cmds = []
|
||||
self._restart_cmds = []
|
||||
self._init_cmds = []
|
||||
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._reserved_move_slots = 0
|
||||
self._steppersync = None
|
||||
# Stats
|
||||
self._get_status_info = {}
|
||||
self._stats_sumsq_base = 0.
|
||||
self._mcu_tick_avg = 0.
|
||||
self._mcu_tick_stddev = 0.
|
||||
self._mcu_tick_awake = 0.
|
||||
# Register handlers
|
||||
printer.load_object(config, "error_mcu")
|
||||
printer.register_event_handler("klippy:firmware_restart",
|
||||
self._firmware_restart)
|
||||
printer.register_event_handler("klippy:disconnect", self._disconnect)
|
||||
printer.register_event_handler("klippy:mcu_identify",
|
||||
self._mcu_identify)
|
||||
printer.register_event_handler("klippy:connect", self._connect)
|
||||
printer.register_event_handler("klippy:shutdown", self._shutdown)
|
||||
printer.register_event_handler("klippy:disconnect", self._disconnect)
|
||||
printer.register_event_handler("klippy:ready", self._ready)
|
||||
# Serial callbacks
|
||||
def _handle_mcu_stats(self, params):
|
||||
count = params['count']
|
||||
tick_sum = params['sum']
|
||||
c = 1.0 / (count * self._mcu_freq)
|
||||
self._mcu_tick_avg = tick_sum * c
|
||||
tick_sumsq = params['sumsq'] * self._stats_sumsq_base
|
||||
diff = count*tick_sumsq - tick_sum**2
|
||||
self._mcu_tick_stddev = c * math.sqrt(max(0., diff))
|
||||
self._mcu_tick_awake = tick_sum / self._mcu_freq
|
||||
def _handle_shutdown(self, params):
|
||||
if self._is_shutdown:
|
||||
return
|
||||
self._is_shutdown = True
|
||||
clock = params.get("clock")
|
||||
if clock is not None:
|
||||
self._shutdown_clock = self.clock32_to_clock64(clock)
|
||||
self._shutdown_msg = msg = params['static_string_id']
|
||||
event_type = params['#name']
|
||||
self._printer.invoke_async_shutdown(
|
||||
"MCU shutdown", {"reason": msg, "mcu": self._name,
|
||||
"event_type": event_type})
|
||||
logging.info("MCU '%s' %s: %s\n%s\n%s", self._name, event_type,
|
||||
self._shutdown_msg, self._clocksync.dump_debug(),
|
||||
self._serial.dump_debug())
|
||||
def _handle_starting(self, params):
|
||||
if not self._is_shutdown:
|
||||
self._printer.invoke_async_shutdown("MCU '%s' spontaneous restart"
|
||||
% (self._name,))
|
||||
# Connection phase
|
||||
def _check_restart(self, reason):
|
||||
start_reason = self._printer.get_start_args().get("start_reason")
|
||||
|
|
@ -664,160 +592,25 @@ class MCU:
|
|||
self._printer.request_exit('firmware_restart')
|
||||
self._reactor.pause(self._reactor.monotonic() + 2.000)
|
||||
raise error("Attempt MCU '%s' restart failed" % (self._name,))
|
||||
def _connect_file(self, pace=False):
|
||||
# In a debugging mode. Open debug output file and read data dictionary
|
||||
start_args = self._printer.get_start_args()
|
||||
if self._name == 'mcu':
|
||||
out_fname = start_args.get('debugoutput')
|
||||
dict_fname = start_args.get('dictionary')
|
||||
else:
|
||||
out_fname = start_args.get('debugoutput') + "-" + self._name
|
||||
dict_fname = start_args.get('dictionary_' + self._name)
|
||||
outfile = open(out_fname, 'wb')
|
||||
dfile = open(dict_fname, 'rb')
|
||||
dict_data = dfile.read()
|
||||
dfile.close()
|
||||
self._serial.connect_file(outfile, dict_data)
|
||||
self._clocksync.connect_file(self._serial, pace)
|
||||
# Handle pacing
|
||||
if not pace:
|
||||
def dummy_estimated_print_time(eventtime):
|
||||
return 0.
|
||||
self.estimated_print_time = dummy_estimated_print_time
|
||||
def _send_config(self, prev_crc):
|
||||
# Build config commands
|
||||
for cb in self._config_callbacks:
|
||||
cb()
|
||||
self._config_cmds.insert(0, "allocate_oids count=%d"
|
||||
% (self._oid_count,))
|
||||
# Resolve pin names
|
||||
ppins = self._printer.lookup_object('pins')
|
||||
pin_resolver = ppins.get_pin_resolver(self._name)
|
||||
for cmdlist in (self._config_cmds, self._restart_cmds, self._init_cmds):
|
||||
for i, cmd in enumerate(cmdlist):
|
||||
cmdlist[i] = pin_resolver.update_command(cmd)
|
||||
# Calculate config CRC
|
||||
encoded_config = '\n'.join(self._config_cmds).encode()
|
||||
config_crc = zlib.crc32(encoded_config) & 0xffffffff
|
||||
self.add_config_cmd("finalize_config crc=%d" % (config_crc,))
|
||||
if prev_crc is not None and config_crc != prev_crc:
|
||||
self._check_restart("CRC mismatch")
|
||||
raise error("MCU '%s' CRC does not match config" % (self._name,))
|
||||
# Transmit config messages (if needed)
|
||||
self.register_response(self._handle_starting, 'starting')
|
||||
try:
|
||||
if prev_crc is None:
|
||||
logging.info("Sending MCU '%s' printer configuration...",
|
||||
self._name)
|
||||
for c in self._config_cmds:
|
||||
self._serial.send(c)
|
||||
else:
|
||||
for c in self._restart_cmds:
|
||||
self._serial.send(c)
|
||||
# Transmit init messages
|
||||
for c in self._init_cmds:
|
||||
self._serial.send(c)
|
||||
except msgproto.enumeration_error as e:
|
||||
enum_name, enum_value = e.get_enum_params()
|
||||
if enum_name == 'pin':
|
||||
# Raise pin name errors as a config error (not a protocol error)
|
||||
raise self._printer.config_error(
|
||||
"Pin '%s' is not a valid pin name on mcu '%s'"
|
||||
% (enum_value, self._name))
|
||||
raise
|
||||
def _send_get_config(self):
|
||||
get_config_cmd = self.lookup_query_command(
|
||||
"get_config",
|
||||
"config is_config=%c crc=%u is_shutdown=%c move_count=%hu")
|
||||
if self.is_fileoutput():
|
||||
return { 'is_config': 0, 'move_count': 500, 'crc': 0 }
|
||||
config_params = get_config_cmd.send()
|
||||
if self._is_shutdown:
|
||||
raise error("MCU '%s' error during config: %s" % (
|
||||
self._name, self._shutdown_msg))
|
||||
if config_params['is_shutdown']:
|
||||
raise error("Can not update MCU '%s' config as it is shutdown" % (
|
||||
self._name,))
|
||||
return config_params
|
||||
def _log_info(self):
|
||||
msgparser = self._serial.get_msgparser()
|
||||
message_count = len(msgparser.get_messages())
|
||||
version, build_versions = msgparser.get_version_info()
|
||||
log_info = [
|
||||
"Loaded MCU '%s' %d commands (%s / %s)"
|
||||
% (self._name, message_count, version, build_versions),
|
||||
"MCU '%s' config: %s" % (self._name, " ".join(
|
||||
["%s=%s" % (k, v) for k, v in self.get_constants().items()]))]
|
||||
return "\n".join(log_info)
|
||||
def _connect(self):
|
||||
config_params = self._send_get_config()
|
||||
if not config_params['is_config']:
|
||||
if self._restart_method == 'rpi_usb':
|
||||
# Only configure mcu after usb power reset
|
||||
self._check_restart("full reset before config")
|
||||
# Not configured - send config and issue get_config again
|
||||
self._send_config(None)
|
||||
config_params = self._send_get_config()
|
||||
if not config_params['is_config'] and not self.is_fileoutput():
|
||||
raise error("Unable to configure MCU '%s'" % (self._name,))
|
||||
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,))
|
||||
# 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']
|
||||
if move_count < self._reserved_move_slots:
|
||||
raise error("Too few moves available on MCU '%s'" % (self._name,))
|
||||
ss_move_count = move_count - self._reserved_move_slots
|
||||
motion_queuing = self._printer.lookup_object('motion_queuing')
|
||||
self._steppersync = motion_queuing.allocate_steppersync(
|
||||
self, self._serial.get_serialqueue(), ss_move_count)
|
||||
self._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)
|
||||
log_info = self._log_info() + "\n" + move_msg
|
||||
self._printer.set_rollover_info(self._name, log_info, log=False)
|
||||
def check_restart_on_crc_mismatch(self):
|
||||
self._check_restart("CRC mismatch")
|
||||
def check_restart_on_send_config(self):
|
||||
if self._restart_method == 'rpi_usb':
|
||||
# Only configure mcu after usb power reset
|
||||
self._check_restart("full reset before config")
|
||||
def check_restart_on_attach(self):
|
||||
resmeth = self._restart_method
|
||||
serialport, baud = self._conn_helper.get_serialport()
|
||||
if resmeth == 'rpi_usb' and not os.path.exists(serialport):
|
||||
# Try toggling usb power
|
||||
self._check_restart("enable power")
|
||||
def lookup_attach_uart_rts(self):
|
||||
# Cheetah boards require RTS to be deasserted
|
||||
# else a reset will trigger the built-in bootloader.
|
||||
return (self._restart_method != "cheetah")
|
||||
def _mcu_identify(self):
|
||||
if self.is_fileoutput():
|
||||
self._connect_file()
|
||||
else:
|
||||
resmeth = self._restart_method
|
||||
if resmeth == 'rpi_usb' and not os.path.exists(self._serialport):
|
||||
# Try toggling usb power
|
||||
self._check_restart("enable power")
|
||||
try:
|
||||
if self._canbus_iface is not None:
|
||||
cbid = self._printer.lookup_object('canbus_ids')
|
||||
nodeid = cbid.get_nodeid(self._serialport)
|
||||
self._serial.connect_canbus(self._serialport, nodeid,
|
||||
self._canbus_iface)
|
||||
elif self._baud:
|
||||
# Cheetah boards require RTS to be deasserted
|
||||
# else a reset will trigger the built-in bootloader.
|
||||
rts = (resmeth != "cheetah")
|
||||
self._serial.connect_uart(self._serialport, self._baud, rts)
|
||||
else:
|
||||
self._serial.connect_pipe(self._serialport)
|
||||
self._clocksync.connect(self._serial)
|
||||
except serialhdl.error as e:
|
||||
raise error(str(e))
|
||||
logging.info(self._log_info())
|
||||
ppins = self._printer.lookup_object('pins')
|
||||
pin_resolver = ppins.get_pin_resolver(self._name)
|
||||
for cname, value in self.get_constants().items():
|
||||
if cname.startswith("RESERVE_PINS_"):
|
||||
for pin in value.split(','):
|
||||
pin_resolver.reserve_pin(pin, cname[13:])
|
||||
self._mcu_freq = self.get_constant_float('CLOCK_FREQ')
|
||||
self._stats_sumsq_base = self.get_constant_float('STATS_SUMSQ_BASE')
|
||||
self._emergency_stop_cmd = self.lookup_command("emergency_stop")
|
||||
self._reset_cmd = self.try_lookup_command("reset")
|
||||
self._config_reset_cmd = self.try_lookup_command("config_reset")
|
||||
self._reset_cmd = self._mcu.try_lookup_command("reset")
|
||||
self._config_reset_cmd = self._mcu.try_lookup_command("config_reset")
|
||||
ext_only = self._reset_cmd is None and self._config_reset_cmd is None
|
||||
msgparser = self._serial.get_msgparser()
|
||||
mbaud = msgparser.get_constant('SERIAL_BAUD', None)
|
||||
|
|
@ -827,112 +620,18 @@ class MCU:
|
|||
self._is_mcu_bridge = True
|
||||
self._printer.register_event_handler("klippy:firmware_restart",
|
||||
self._firmware_restart_bridge)
|
||||
version, build_versions = msgparser.get_version_info()
|
||||
self._get_status_info['mcu_version'] = version
|
||||
self._get_status_info['mcu_build_versions'] = build_versions
|
||||
self._get_status_info['mcu_constants'] = msgparser.get_constants()
|
||||
self.register_response(self._handle_shutdown, 'shutdown')
|
||||
self.register_response(self._handle_shutdown, 'is_shutdown')
|
||||
self.register_response(self._handle_mcu_stats, 'stats')
|
||||
def _ready(self):
|
||||
if self.is_fileoutput():
|
||||
return
|
||||
# Check that reported mcu frequency is in range
|
||||
mcu_freq = self._mcu_freq
|
||||
systime = self._reactor.monotonic()
|
||||
get_clock = self._clocksync.get_clock
|
||||
calc_freq = get_clock(systime + 1) - get_clock(systime)
|
||||
freq_diff = abs(mcu_freq - calc_freq)
|
||||
mcu_freq_mhz = int(mcu_freq / 1000000. + 0.5)
|
||||
calc_freq_mhz = int(calc_freq / 1000000. + 0.5)
|
||||
if freq_diff > mcu_freq*0.01 and mcu_freq_mhz != calc_freq_mhz:
|
||||
pconfig = self._printer.lookup_object('configfile')
|
||||
msg = ("MCU '%s' configured for %dMhz but running at %dMhz!"
|
||||
% (self._name, mcu_freq_mhz, calc_freq_mhz))
|
||||
pconfig.runtime_warning(msg)
|
||||
# Config creation helpers
|
||||
def setup_pin(self, pin_type, pin_params):
|
||||
pcs = {'endstop': MCU_endstop,
|
||||
'digital_out': MCU_digital_out, 'pwm': MCU_pwm, 'adc': MCU_adc}
|
||||
if pin_type not in pcs:
|
||||
raise pins.error("pin type %s not supported on mcu" % (pin_type,))
|
||||
return pcs[pin_type](self, pin_params)
|
||||
def create_oid(self):
|
||||
self._oid_count += 1
|
||||
return self._oid_count - 1
|
||||
def register_config_callback(self, cb):
|
||||
self._config_callbacks.append(cb)
|
||||
def add_config_cmd(self, cmd, is_init=False, on_restart=False):
|
||||
if is_init:
|
||||
self._init_cmds.append(cmd)
|
||||
elif on_restart:
|
||||
self._restart_cmds.append(cmd)
|
||||
else:
|
||||
self._config_cmds.append(cmd)
|
||||
def get_query_slot(self, oid):
|
||||
slot = self.seconds_to_clock(oid * .01)
|
||||
t = int(self.estimated_print_time(self._reactor.monotonic()) + 1.5)
|
||||
return self.print_time_to_clock(t) + slot
|
||||
def seconds_to_clock(self, time):
|
||||
return int(time * self._mcu_freq)
|
||||
def get_max_stepper_error(self):
|
||||
return self._max_stepper_error
|
||||
def min_schedule_time(self):
|
||||
return MIN_SCHEDULE_TIME
|
||||
def max_nominal_duration(self):
|
||||
return MAX_NOMINAL_DURATION
|
||||
# Wrapper functions
|
||||
def get_printer(self):
|
||||
return self._printer
|
||||
def get_name(self):
|
||||
return self._name
|
||||
def register_response(self, cb, msg, oid=None):
|
||||
self._serial.register_response(cb, msg, oid)
|
||||
def alloc_command_queue(self):
|
||||
return self._serial.alloc_command_queue()
|
||||
def lookup_command(self, msgformat, cq=None):
|
||||
return CommandWrapper(self._serial, msgformat, cq,
|
||||
debugoutput=self.is_fileoutput())
|
||||
def lookup_query_command(self, msgformat, respformat, oid=None,
|
||||
cq=None, is_async=False):
|
||||
return CommandQueryWrapper(self._serial, msgformat, respformat, oid,
|
||||
cq, is_async, self._printer.command_error)
|
||||
def try_lookup_command(self, msgformat):
|
||||
try:
|
||||
return self.lookup_command(msgformat)
|
||||
except self._serial.get_msgparser().error as e:
|
||||
return None
|
||||
def get_enumerations(self):
|
||||
return self._serial.get_msgparser().get_enumerations()
|
||||
def get_constants(self):
|
||||
return self._serial.get_msgparser().get_constants()
|
||||
def get_constant_float(self, name):
|
||||
return self._serial.get_msgparser().get_constant_float(name)
|
||||
def print_time_to_clock(self, print_time):
|
||||
return self._clocksync.print_time_to_clock(print_time)
|
||||
def clock_to_print_time(self, clock):
|
||||
return self._clocksync.clock_to_print_time(clock)
|
||||
def estimated_print_time(self, eventtime):
|
||||
return self._clocksync.estimated_print_time(eventtime)
|
||||
def clock32_to_clock64(self, clock32):
|
||||
return self._clocksync.clock32_to_clock64(clock32)
|
||||
# Restarts
|
||||
def _disconnect(self):
|
||||
self._serial.disconnect()
|
||||
self._steppersync = None
|
||||
def _shutdown(self, force=False):
|
||||
if (self._emergency_stop_cmd is None
|
||||
or (self._is_shutdown and not force)):
|
||||
return
|
||||
self._emergency_stop_cmd.send()
|
||||
def _restart_arduino(self):
|
||||
logging.info("Attempting MCU '%s' reset", self._name)
|
||||
self._disconnect()
|
||||
serialhdl.arduino_reset(self._serialport, self._reactor)
|
||||
serialport, baud = self._conn_helper.get_serialport()
|
||||
serialhdl.arduino_reset(serialport, self._reactor)
|
||||
def _restart_cheetah(self):
|
||||
logging.info("Attempting MCU '%s' Cheetah-style reset", self._name)
|
||||
self._disconnect()
|
||||
serialhdl.cheetah_reset(self._serialport, self._reactor)
|
||||
serialport, baud = self._conn_helper.get_serialport()
|
||||
serialhdl.cheetah_reset(serialport, self._reactor)
|
||||
def _restart_via_command(self):
|
||||
if ((self._reset_cmd is None and self._config_reset_cmd is None)
|
||||
or not self._clocksync.is_active()):
|
||||
|
|
@ -942,8 +641,7 @@ class MCU:
|
|||
if self._reset_cmd is None:
|
||||
# Attempt reset via config_reset command
|
||||
logging.info("Attempting MCU '%s' config_reset command", self._name)
|
||||
self._is_shutdown = True
|
||||
self._shutdown(force=True)
|
||||
self._conn_helper.force_local_shutdown()
|
||||
self._reactor.pause(self._reactor.monotonic() + 0.015)
|
||||
self._config_reset_cmd.send()
|
||||
else:
|
||||
|
|
@ -971,15 +669,133 @@ class MCU:
|
|||
self._restart_arduino()
|
||||
def _firmware_restart_bridge(self):
|
||||
self._firmware_restart(True)
|
||||
# Move queue tracking
|
||||
def request_move_queue_slot(self):
|
||||
self._reserved_move_slots += 1
|
||||
def check_active(self, print_time, eventtime):
|
||||
if self._steppersync is None:
|
||||
|
||||
# Low-level mcu connection management helper
|
||||
class MCUConnectHelper:
|
||||
def __init__(self, config, mcu, clocksync):
|
||||
self._mcu = mcu
|
||||
self._clocksync = clocksync
|
||||
self._printer = printer = config.get_printer()
|
||||
self._reactor = printer.get_reactor()
|
||||
self._name = name = mcu.get_name()
|
||||
# Serial port
|
||||
self._serial = serialhdl.SerialReader(self._reactor, mcu_name=name)
|
||||
self._baud = 0
|
||||
self._canbus_iface = None
|
||||
canbus_uuid = config.get('canbus_uuid', None)
|
||||
if canbus_uuid is not None:
|
||||
self._serialport = canbus_uuid
|
||||
self._canbus_iface = config.get('canbus_interface', 'can0')
|
||||
cbid = self._printer.load_object(config, 'canbus_ids')
|
||||
cbid.add_uuid(config, canbus_uuid, self._canbus_iface)
|
||||
self._printer.load_object(config, 'canbus_stats %s' % (name,))
|
||||
else:
|
||||
self._serialport = config.get('serial')
|
||||
if not (self._serialport.startswith("/dev/rpmsg_")
|
||||
or self._serialport.startswith("/tmp/klipper_host_")):
|
||||
self._baud = config.getint('baud', 250000, minval=2400)
|
||||
# Shutdown tracking
|
||||
self._emergency_stop_cmd = None
|
||||
self._is_shutdown = self._is_timeout = False
|
||||
self._shutdown_clock = 0
|
||||
self._shutdown_msg = ""
|
||||
# Register handlers
|
||||
printer.register_event_handler("klippy:mcu_identify",
|
||||
self._mcu_identify)
|
||||
self._restart_helper = MCURestartHelper(config, self)
|
||||
printer.register_event_handler("klippy:shutdown", self._shutdown)
|
||||
def get_mcu(self):
|
||||
return self._mcu
|
||||
def get_serial(self):
|
||||
return self._serial
|
||||
def get_clocksync(self):
|
||||
return self._clocksync
|
||||
def get_serialport(self):
|
||||
return self._serialport, self._baud
|
||||
def get_restart_helper(self):
|
||||
return self._restart_helper
|
||||
def _handle_shutdown(self, params):
|
||||
if self._is_shutdown:
|
||||
return
|
||||
offset, freq = self._clocksync.calibrate_clock(print_time, eventtime)
|
||||
self._ffi_lib.steppersync_set_time(self._steppersync, offset, freq)
|
||||
if (self._clocksync.is_active() or self.is_fileoutput()
|
||||
self._is_shutdown = True
|
||||
clock = params.get("clock")
|
||||
if clock is not None:
|
||||
self._shutdown_clock = self._mcu.clock32_to_clock64(clock)
|
||||
self._shutdown_msg = msg = params['static_string_id']
|
||||
event_type = params['#name']
|
||||
self._printer.invoke_async_shutdown(
|
||||
"MCU shutdown", {"reason": msg, "mcu": self._name,
|
||||
"event_type": event_type})
|
||||
logging.info("MCU '%s' %s: %s\n%s\n%s", self._name, event_type,
|
||||
self._shutdown_msg, self._clocksync.dump_debug(),
|
||||
self._serial.dump_debug())
|
||||
def _handle_starting(self, params):
|
||||
if not self._is_shutdown:
|
||||
self._printer.invoke_async_shutdown("MCU '%s' spontaneous restart"
|
||||
% (self._name,))
|
||||
def log_info(self):
|
||||
msgparser = self._serial.get_msgparser()
|
||||
message_count = len(msgparser.get_messages())
|
||||
version, build_versions = msgparser.get_version_info()
|
||||
log_info = [
|
||||
"Loaded MCU '%s' %d commands (%s / %s)"
|
||||
% (self._name, message_count, version, build_versions),
|
||||
"MCU '%s' config: %s" % (self._name, " ".join(
|
||||
["%s=%s" % (k, v)
|
||||
for k, v in msgparser.get_constants().items()]))]
|
||||
return "\n".join(log_info)
|
||||
def _attach_file(self):
|
||||
# In a debugging mode. Open debug output file and read data dictionary
|
||||
start_args = self._printer.get_start_args()
|
||||
if self._name == 'mcu':
|
||||
out_fname = start_args.get('debugoutput')
|
||||
dict_fname = start_args.get('dictionary')
|
||||
else:
|
||||
out_fname = start_args.get('debugoutput') + "-" + self._name
|
||||
dict_fname = start_args.get('dictionary_' + self._name)
|
||||
outfile = open(out_fname, 'wb')
|
||||
dfile = open(dict_fname, 'rb')
|
||||
dict_data = dfile.read()
|
||||
dfile.close()
|
||||
self._serial.connect_file(outfile, dict_data)
|
||||
self._clocksync.connect_file(self._serial)
|
||||
def _attach(self):
|
||||
self._restart_helper.check_restart_on_attach()
|
||||
try:
|
||||
if self._canbus_iface is not None:
|
||||
cbid = self._printer.lookup_object('canbus_ids')
|
||||
nodeid = cbid.get_nodeid(self._serialport)
|
||||
self._serial.connect_canbus(self._serialport, nodeid,
|
||||
self._canbus_iface)
|
||||
elif self._baud:
|
||||
rts = self._restart_helper.lookup_attach_uart_rts()
|
||||
self._serial.connect_uart(self._serialport, self._baud, rts)
|
||||
else:
|
||||
self._serial.connect_pipe(self._serialport)
|
||||
self._clocksync.connect(self._serial)
|
||||
except serialhdl.error as e:
|
||||
raise error(str(e))
|
||||
def _mcu_identify(self):
|
||||
if self._mcu.is_fileoutput():
|
||||
self._attach_file()
|
||||
else:
|
||||
self._attach()
|
||||
logging.info(self.log_info())
|
||||
# Setup shutdown handling
|
||||
self._emergency_stop_cmd = self._mcu.lookup_command("emergency_stop")
|
||||
self._mcu.register_response(self._handle_shutdown, 'shutdown')
|
||||
self._mcu.register_response(self._handle_shutdown, 'is_shutdown')
|
||||
self._mcu.register_response(self._handle_starting, 'starting')
|
||||
def _shutdown(self, force=False):
|
||||
if (self._emergency_stop_cmd is None
|
||||
or (self._is_shutdown and not force)):
|
||||
return
|
||||
self._emergency_stop_cmd.send()
|
||||
def force_local_shutdown(self):
|
||||
self._is_shutdown = True
|
||||
self._shutdown(force=True)
|
||||
def check_timeout(self, eventtime):
|
||||
if (self._clocksync.is_active() or self._mcu.is_fileoutput()
|
||||
or self._is_timeout):
|
||||
return
|
||||
self._is_timeout = True
|
||||
|
|
@ -987,13 +803,68 @@ class MCU:
|
|||
self._name, eventtime)
|
||||
self._printer.invoke_shutdown("Lost communication with MCU '%s'" % (
|
||||
self._name,))
|
||||
# Misc external commands
|
||||
def is_fileoutput(self):
|
||||
return self._printer.get_start_args().get('debugoutput') is not None
|
||||
def is_shutdown(self):
|
||||
return self._is_shutdown
|
||||
def get_shutdown_clock(self):
|
||||
return self._shutdown_clock
|
||||
def get_shutdown_msg(self):
|
||||
return self._shutdown_msg
|
||||
|
||||
# Handle statistics reporting
|
||||
class MCUStatsHelper:
|
||||
def __init__(self, config, conn_helper):
|
||||
self._printer = printer = config.get_printer()
|
||||
self._mcu = mcu = conn_helper.get_mcu()
|
||||
self._serial = conn_helper.get_serial()
|
||||
self._clocksync = conn_helper.get_clocksync()
|
||||
self._reactor = printer.get_reactor()
|
||||
self._name = mcu.get_name()
|
||||
# Statistics tracking
|
||||
self._mcu_freq = 0.
|
||||
self._get_status_info = {}
|
||||
self._stats_sumsq_base = 0.
|
||||
self._mcu_tick_avg = 0.
|
||||
self._mcu_tick_stddev = 0.
|
||||
self._mcu_tick_awake = 0.
|
||||
# Register handlers
|
||||
printer.register_event_handler("klippy:ready", self._ready)
|
||||
printer.register_event_handler("klippy:mcu_identify",
|
||||
self._mcu_identify)
|
||||
def _handle_mcu_stats(self, params):
|
||||
count = params['count']
|
||||
tick_sum = params['sum']
|
||||
c = 1.0 / (count * self._mcu_freq)
|
||||
self._mcu_tick_avg = tick_sum * c
|
||||
tick_sumsq = params['sumsq'] * self._stats_sumsq_base
|
||||
diff = count*tick_sumsq - tick_sum**2
|
||||
self._mcu_tick_stddev = c * math.sqrt(max(0., diff))
|
||||
self._mcu_tick_awake = tick_sum / self._mcu_freq
|
||||
def _mcu_identify(self):
|
||||
self._mcu_freq = self._mcu.get_constant_float('CLOCK_FREQ')
|
||||
self._stats_sumsq_base = self._mcu.get_constant_float(
|
||||
'STATS_SUMSQ_BASE')
|
||||
msgparser = self._serial.get_msgparser()
|
||||
version, build_versions = msgparser.get_version_info()
|
||||
self._get_status_info['mcu_version'] = version
|
||||
self._get_status_info['mcu_build_versions'] = build_versions
|
||||
self._get_status_info['mcu_constants'] = msgparser.get_constants()
|
||||
self._mcu.register_response(self._handle_mcu_stats, 'stats')
|
||||
def _ready(self):
|
||||
if self._mcu.is_fileoutput():
|
||||
return
|
||||
# Check that reported mcu frequency is in range
|
||||
mcu_freq = self._mcu_freq
|
||||
systime = self._reactor.monotonic()
|
||||
get_clock = self._clocksync.get_clock
|
||||
calc_freq = get_clock(systime + 1) - get_clock(systime)
|
||||
freq_diff = abs(mcu_freq - calc_freq)
|
||||
mcu_freq_mhz = int(mcu_freq / 1000000. + 0.5)
|
||||
calc_freq_mhz = int(calc_freq / 1000000. + 0.5)
|
||||
if freq_diff > mcu_freq*0.01 and mcu_freq_mhz != calc_freq_mhz:
|
||||
pconfig = self._printer.lookup_object('configfile')
|
||||
msg = ("MCU '%s' configured for %dMhz but running at %dMhz!"
|
||||
% (self._name, mcu_freq_mhz, calc_freq_mhz))
|
||||
pconfig.runtime_warning(msg)
|
||||
def get_status(self, eventtime=None):
|
||||
return dict(self._get_status_info)
|
||||
def stats(self, eventtime):
|
||||
|
|
@ -1006,6 +877,244 @@ class MCU:
|
|||
self._get_status_info['last_stats'] = last_stats
|
||||
return False, '%s: %s' % (self._name, stats)
|
||||
|
||||
# Handle process of configuring an mcu
|
||||
class MCUConfigHelper:
|
||||
def __init__(self, config, conn_helper):
|
||||
self._printer = printer = config.get_printer()
|
||||
self._conn_helper = conn_helper
|
||||
self._mcu = mcu = conn_helper.get_mcu()
|
||||
self._serial = conn_helper.get_serial()
|
||||
self._clocksync = conn_helper.get_clocksync()
|
||||
self._reactor = printer.get_reactor()
|
||||
self._name = mcu.get_name()
|
||||
# Configuration tracking
|
||||
self._oid_count = 0
|
||||
self._config_callbacks = []
|
||||
self._config_cmds = []
|
||||
self._restart_cmds = []
|
||||
self._init_cmds = []
|
||||
self._mcu_freq = 0.
|
||||
self._reserved_move_slots = 0
|
||||
# Register handlers
|
||||
printer.lookup_object('pins').register_chip(self._name, mcu)
|
||||
printer.register_event_handler("klippy:mcu_identify",
|
||||
self._mcu_identify)
|
||||
printer.register_event_handler("klippy:connect", self._connect)
|
||||
def _send_config(self, prev_crc):
|
||||
# Build config commands
|
||||
for cb in self._config_callbacks:
|
||||
cb()
|
||||
self._config_cmds.insert(0, "allocate_oids count=%d"
|
||||
% (self._oid_count,))
|
||||
# Resolve pin names
|
||||
ppins = self._printer.lookup_object('pins')
|
||||
pin_resolver = ppins.get_pin_resolver(self._name)
|
||||
for cmdlist in (self._config_cmds, self._restart_cmds, self._init_cmds):
|
||||
for i, cmd in enumerate(cmdlist):
|
||||
cmdlist[i] = pin_resolver.update_command(cmd)
|
||||
# Calculate config CRC
|
||||
encoded_config = '\n'.join(self._config_cmds).encode()
|
||||
config_crc = zlib.crc32(encoded_config) & 0xffffffff
|
||||
self.add_config_cmd("finalize_config crc=%d" % (config_crc,))
|
||||
if prev_crc is not None and config_crc != prev_crc:
|
||||
restart_helper = self._conn_helper.get_restart_helper()
|
||||
restart_helper.check_restart_on_crc_mismatch()
|
||||
raise error("MCU '%s' CRC does not match config" % (self._name,))
|
||||
# Transmit config messages (if needed)
|
||||
try:
|
||||
if prev_crc is None:
|
||||
logging.info("Sending MCU '%s' printer configuration...",
|
||||
self._name)
|
||||
for c in self._config_cmds:
|
||||
self._serial.send(c)
|
||||
else:
|
||||
for c in self._restart_cmds:
|
||||
self._serial.send(c)
|
||||
# Transmit init messages
|
||||
for c in self._init_cmds:
|
||||
self._serial.send(c)
|
||||
except msgproto.enumeration_error as e:
|
||||
enum_name, enum_value = e.get_enum_params()
|
||||
if enum_name == 'pin':
|
||||
# Raise pin name errors as a config error (not a protocol error)
|
||||
raise self._printer.config_error(
|
||||
"Pin '%s' is not a valid pin name on mcu '%s'"
|
||||
% (enum_value, self._name))
|
||||
raise
|
||||
def _send_get_config(self):
|
||||
get_config_cmd = self._mcu.lookup_query_command(
|
||||
"get_config",
|
||||
"config is_config=%c crc=%u is_shutdown=%c move_count=%hu")
|
||||
if self._mcu.is_fileoutput():
|
||||
return { 'is_config': 0, 'move_count': 500, 'crc': 0 }
|
||||
config_params = get_config_cmd.send()
|
||||
if self._conn_helper.is_shutdown():
|
||||
raise error("MCU '%s' error during config: %s" % (
|
||||
self._name, self._conn_helper.get_shutdown_msg()))
|
||||
if config_params['is_shutdown']:
|
||||
raise error("Can not update MCU '%s' config as it is shutdown" % (
|
||||
self._name,))
|
||||
return config_params
|
||||
def _connect(self):
|
||||
config_params = self._send_get_config()
|
||||
if not config_params['is_config']:
|
||||
restart_helper = self._conn_helper.get_restart_helper()
|
||||
restart_helper.check_restart_on_send_config()
|
||||
# Not configured - send config and issue get_config again
|
||||
self._send_config(None)
|
||||
config_params = self._send_get_config()
|
||||
if not config_params['is_config'] and not self._mcu.is_fileoutput():
|
||||
raise error("Unable to configure MCU '%s'" % (self._name,))
|
||||
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,))
|
||||
# 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']
|
||||
if move_count < self._reserved_move_slots:
|
||||
raise error("Too few moves available on MCU '%s'" % (self._name,))
|
||||
ss_move_count = move_count - self._reserved_move_slots
|
||||
motion_queuing = self._printer.lookup_object('motion_queuing')
|
||||
motion_queuing.setup_mcu_movequeue(
|
||||
self._mcu, self._serial.get_serialqueue(), ss_move_count)
|
||||
# Log config information
|
||||
move_msg = "Configured MCU '%s' (%d moves)" % (self._name, move_count)
|
||||
logging.info(move_msg)
|
||||
log_info = self._conn_helper.log_info() + "\n" + move_msg
|
||||
self._printer.set_rollover_info(self._name, log_info, log=False)
|
||||
def _mcu_identify(self):
|
||||
self._mcu_freq = self._mcu.get_constant_float('CLOCK_FREQ')
|
||||
ppins = self._printer.lookup_object('pins')
|
||||
pin_resolver = ppins.get_pin_resolver(self._name)
|
||||
for cname, value in self._mcu.get_constants().items():
|
||||
if cname.startswith("RESERVE_PINS_"):
|
||||
for pin in value.split(','):
|
||||
pin_resolver.reserve_pin(pin, cname[13:])
|
||||
# Config creation helpers
|
||||
def setup_pin(self, pin_type, pin_params):
|
||||
pcs = {'endstop': MCU_endstop,
|
||||
'digital_out': MCU_digital_out, 'pwm': MCU_pwm, 'adc': MCU_adc}
|
||||
if pin_type not in pcs:
|
||||
raise pins.error("pin type %s not supported on mcu" % (pin_type,))
|
||||
return pcs[pin_type](self._mcu, pin_params)
|
||||
def create_oid(self):
|
||||
self._oid_count += 1
|
||||
return self._oid_count - 1
|
||||
def register_config_callback(self, cb):
|
||||
self._config_callbacks.append(cb)
|
||||
def add_config_cmd(self, cmd, is_init=False, on_restart=False):
|
||||
if is_init:
|
||||
self._init_cmds.append(cmd)
|
||||
elif on_restart:
|
||||
self._restart_cmds.append(cmd)
|
||||
else:
|
||||
self._config_cmds.append(cmd)
|
||||
def get_query_slot(self, oid):
|
||||
slot = self.seconds_to_clock(oid * .01)
|
||||
t = int(self._mcu.estimated_print_time(self._reactor.monotonic()) + 1.5)
|
||||
return self._mcu.print_time_to_clock(t) + slot
|
||||
def seconds_to_clock(self, time):
|
||||
return int(time * self._mcu_freq)
|
||||
def request_move_queue_slot(self):
|
||||
self._reserved_move_slots += 1
|
||||
|
||||
# Main MCU class
|
||||
class MCU:
|
||||
error = error
|
||||
def __init__(self, config, clocksync):
|
||||
self._printer = printer = config.get_printer()
|
||||
self._clocksync = clocksync
|
||||
self._name = config.get_name()
|
||||
if self._name.startswith('mcu '):
|
||||
self._name = self._name[4:]
|
||||
# Low-level connection and helpers
|
||||
self._conn_helper = MCUConnectHelper(config, self, clocksync)
|
||||
self._serial = self._conn_helper.get_serial()
|
||||
self._config_helper = MCUConfigHelper(self, self._conn_helper)
|
||||
self._stats_helper = MCUStatsHelper(self, self._conn_helper)
|
||||
printer.load_object(config, "error_mcu")
|
||||
# Alter time reporting when debugging
|
||||
if self.is_fileoutput():
|
||||
def dummy_estimated_print_time(eventtime):
|
||||
return 0.
|
||||
self.estimated_print_time = dummy_estimated_print_time
|
||||
def get_name(self):
|
||||
return self._name
|
||||
def get_printer(self):
|
||||
return self._printer
|
||||
def is_fileoutput(self):
|
||||
return self._printer.get_start_args().get('debugoutput') is not None
|
||||
# MCU Configuration wrappers
|
||||
def setup_pin(self, pin_type, pin_params):
|
||||
return self._config_helper.setup_pin(pin_type, pin_params)
|
||||
def create_oid(self):
|
||||
return self._config_helper.create_oid()
|
||||
def register_config_callback(self, cb):
|
||||
self._config_helper.register_config_callback(cb)
|
||||
def add_config_cmd(self, cmd, is_init=False, on_restart=False):
|
||||
self._config_helper.add_config_cmd(cmd, is_init, on_restart)
|
||||
def request_move_queue_slot(self):
|
||||
self._config_helper.request_move_queue_slot()
|
||||
def get_query_slot(self, oid):
|
||||
return self._config_helper.get_query_slot(oid)
|
||||
def seconds_to_clock(self, time):
|
||||
return self._config_helper.seconds_to_clock(time)
|
||||
# Command Handler helpers
|
||||
def min_schedule_time(self):
|
||||
return MIN_SCHEDULE_TIME
|
||||
def max_nominal_duration(self):
|
||||
return MAX_NOMINAL_DURATION
|
||||
def lookup_command(self, msgformat, cq=None):
|
||||
return CommandWrapper(self._serial, msgformat, cq,
|
||||
debugoutput=self.is_fileoutput())
|
||||
def lookup_query_command(self, msgformat, respformat, oid=None,
|
||||
cq=None, is_async=False):
|
||||
return CommandQueryWrapper(self._serial, msgformat, respformat, oid,
|
||||
cq, is_async, self._printer.command_error)
|
||||
def try_lookup_command(self, msgformat):
|
||||
try:
|
||||
return self.lookup_command(msgformat)
|
||||
except self._serial.get_msgparser().error as e:
|
||||
return None
|
||||
# SerialHdl wrappers
|
||||
def register_response(self, cb, msg, oid=None):
|
||||
self._serial.register_response(cb, msg, oid)
|
||||
def alloc_command_queue(self):
|
||||
return self._serial.alloc_command_queue()
|
||||
# MsgParser wrappers
|
||||
def get_enumerations(self):
|
||||
return self._serial.get_msgparser().get_enumerations()
|
||||
def get_constants(self):
|
||||
return self._serial.get_msgparser().get_constants()
|
||||
def get_constant_float(self, name):
|
||||
return self._serial.get_msgparser().get_constant_float(name)
|
||||
# ClockSync wrappers
|
||||
def print_time_to_clock(self, print_time):
|
||||
return self._clocksync.print_time_to_clock(print_time)
|
||||
def clock_to_print_time(self, clock):
|
||||
return self._clocksync.clock_to_print_time(clock)
|
||||
def estimated_print_time(self, eventtime):
|
||||
return self._clocksync.estimated_print_time(eventtime)
|
||||
def clock32_to_clock64(self, clock32):
|
||||
return self._clocksync.clock32_to_clock64(clock32)
|
||||
def calibrate_clock(self, print_time, eventtime):
|
||||
offset, freq = self._clocksync.calibrate_clock(print_time, eventtime)
|
||||
self._conn_helper.check_timeout(eventtime)
|
||||
return offset, freq
|
||||
# Low-level connection wrappers
|
||||
def is_shutdown(self):
|
||||
return self._conn_helper.is_shutdown()
|
||||
def get_shutdown_clock(self):
|
||||
return self._conn_helper.get_shutdown_clock()
|
||||
# Statistics wrappers
|
||||
def get_status(self, eventtime=None):
|
||||
return self._stats_helper.get_status(eventtime)
|
||||
def stats(self, eventtime):
|
||||
return self._stats_helper.stats(eventtime)
|
||||
|
||||
def add_printer_objects(config):
|
||||
printer = config.get_printer()
|
||||
reactor = printer.get_reactor()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# File descriptor and timer event helper
|
||||
#
|
||||
# Copyright (C) 2016-2020 Kevin O'Connor <kevin@koconnor.net>
|
||||
# Copyright (C) 2016-2025 Kevin O'Connor <kevin@koconnor.net>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import os, gc, select, math, time, logging, queue
|
||||
|
|
@ -14,6 +14,7 @@ class ReactorTimer:
|
|||
def __init__(self, callback, waketime):
|
||||
self.callback = callback
|
||||
self.waketime = waketime
|
||||
self.timer_is_running = False
|
||||
|
||||
class ReactorCompletion:
|
||||
class sentinel: pass
|
||||
|
|
@ -54,8 +55,6 @@ class ReactorFileHandler:
|
|||
self.fd = fd
|
||||
self.read_callback = read_callback
|
||||
self.write_callback = write_callback
|
||||
def fileno(self):
|
||||
return self.fd
|
||||
|
||||
class ReactorGreenlet(greenlet.greenlet):
|
||||
def __init__(self, run):
|
||||
|
|
@ -108,8 +107,13 @@ class SelectReactor:
|
|||
self._pipe_fds = None
|
||||
self._async_queue = queue.Queue()
|
||||
# File descriptors
|
||||
self._dummy_fd_hdl = ReactorFileHandler(-1, (lambda e: None),
|
||||
(lambda e: None))
|
||||
self._fds = {}
|
||||
self._read_fds = []
|
||||
self._write_fds = []
|
||||
self._READ = 1
|
||||
self._WRITE = 2
|
||||
# Greenlets
|
||||
self._g_dispatch = None
|
||||
self._greenlets = []
|
||||
|
|
@ -118,6 +122,8 @@ class SelectReactor:
|
|||
return tuple(self._last_gc_times)
|
||||
# Timers
|
||||
def update_timer(self, timer_handler, waketime):
|
||||
if timer_handler.timer_is_running:
|
||||
return
|
||||
timer_handler.waketime = waketime
|
||||
self._next_timer = min(self._next_timer, waketime)
|
||||
def register_timer(self, callback, waketime=NEVER):
|
||||
|
|
@ -155,7 +161,9 @@ class SelectReactor:
|
|||
waketime = t.waketime
|
||||
if eventtime >= waketime:
|
||||
t.waketime = self.NEVER
|
||||
t.timer_is_running = True
|
||||
t.waketime = waketime = t.callback(eventtime)
|
||||
t.timer_is_running = False
|
||||
if g_dispatch is not self._g_dispatch:
|
||||
self._next_timer = min(self._next_timer, waketime)
|
||||
self._end_greenlet(g_dispatch)
|
||||
|
|
@ -240,48 +248,54 @@ class SelectReactor:
|
|||
# File descriptors
|
||||
def register_fd(self, fd, read_callback, write_callback=None):
|
||||
file_handler = ReactorFileHandler(fd, read_callback, write_callback)
|
||||
self._fds[fd] = file_handler
|
||||
self.set_fd_wake(file_handler, True, False)
|
||||
return file_handler
|
||||
def unregister_fd(self, file_handler):
|
||||
if file_handler in self._read_fds:
|
||||
self._read_fds.pop(self._read_fds.index(file_handler))
|
||||
if file_handler in self._write_fds:
|
||||
self._write_fds.pop(self._write_fds.index(file_handler))
|
||||
self.set_fd_wake(file_handler, False, False)
|
||||
del self._fds[file_handler.fd]
|
||||
def set_fd_wake(self, file_handler, is_readable=True, is_writeable=False):
|
||||
if file_handler in self._read_fds:
|
||||
fd = file_handler.fd
|
||||
if fd in self._read_fds:
|
||||
if not is_readable:
|
||||
self._read_fds.pop(self._read_fds.index(file_handler))
|
||||
self._read_fds.remove(fd)
|
||||
elif is_readable:
|
||||
self._read_fds.append(file_handler)
|
||||
if file_handler in self._write_fds:
|
||||
self._read_fds.append(fd)
|
||||
if fd in self._write_fds:
|
||||
if not is_writeable:
|
||||
self._write_fds.pop(self._write_fds.index(file_handler))
|
||||
self._write_fds.remove(fd)
|
||||
elif is_writeable:
|
||||
self._write_fds.append(file_handler)
|
||||
self._write_fds.append(fd)
|
||||
def _check_fds(self, eventtime, hdls):
|
||||
g_dispatch = self._g_dispatch
|
||||
for fd, event in hdls:
|
||||
hdl = self._fds.get(fd, self._dummy_fd_hdl)
|
||||
if event & self._READ:
|
||||
hdl.read_callback(eventtime)
|
||||
if g_dispatch is not self._g_dispatch:
|
||||
self._end_greenlet(g_dispatch)
|
||||
return self.monotonic()
|
||||
if event & self._WRITE:
|
||||
hdl.write_callback(eventtime)
|
||||
if g_dispatch is not self._g_dispatch:
|
||||
self._end_greenlet(g_dispatch)
|
||||
return self.monotonic()
|
||||
return eventtime
|
||||
# Main loop
|
||||
def _dispatch_loop(self):
|
||||
self._g_dispatch = g_dispatch = greenlet.getcurrent()
|
||||
self._g_dispatch = greenlet.getcurrent()
|
||||
busy = True
|
||||
eventtime = self.monotonic()
|
||||
while self._process:
|
||||
timeout = self._check_timers(eventtime, busy)
|
||||
busy = False
|
||||
res = select.select(self._read_fds, self.write_fds, [], timeout)
|
||||
res = select.select(self._read_fds, self._write_fds, [], timeout)
|
||||
eventtime = self.monotonic()
|
||||
for fd in res[0]:
|
||||
if res[0] or res[1]:
|
||||
busy = True
|
||||
fd.read_callback(eventtime)
|
||||
if g_dispatch is not self._g_dispatch:
|
||||
self._end_greenlet(g_dispatch)
|
||||
eventtime = self.monotonic()
|
||||
break
|
||||
for fd in res[1]:
|
||||
busy = True
|
||||
fd.write_callback(eventtime)
|
||||
if g_dispatch is not self._g_dispatch:
|
||||
self._end_greenlet(g_dispatch)
|
||||
eventtime = self.monotonic()
|
||||
break
|
||||
hdls = ([(fd, self._READ) for fd in res[0]]
|
||||
+ [(fd, self._WRITE) for fd in res[1]])
|
||||
eventtime = self._check_fds(eventtime, hdls)
|
||||
self._g_dispatch = None
|
||||
def run(self):
|
||||
if self._pipe_fds is None:
|
||||
|
|
@ -310,30 +324,27 @@ class PollReactor(SelectReactor):
|
|||
def __init__(self, gc_checking=False):
|
||||
SelectReactor.__init__(self, gc_checking)
|
||||
self._poll = select.poll()
|
||||
self._fds = {}
|
||||
self._READ = select.POLLIN | select.POLLHUP
|
||||
self._WRITE = select.POLLOUT
|
||||
# File descriptors
|
||||
def register_fd(self, fd, read_callback, write_callback=None):
|
||||
file_handler = ReactorFileHandler(fd, read_callback, write_callback)
|
||||
fds = self._fds.copy()
|
||||
fds[fd] = file_handler
|
||||
self._fds = fds
|
||||
self._poll.register(file_handler, select.POLLIN | select.POLLHUP)
|
||||
self._fds[fd] = file_handler
|
||||
self._poll.register(file_handler.fd, select.POLLIN | select.POLLHUP)
|
||||
return file_handler
|
||||
def unregister_fd(self, file_handler):
|
||||
self._poll.unregister(file_handler)
|
||||
fds = self._fds.copy()
|
||||
del fds[file_handler.fd]
|
||||
self._fds = fds
|
||||
self._poll.unregister(file_handler.fd)
|
||||
del self._fds[file_handler.fd]
|
||||
def set_fd_wake(self, file_handler, is_readable=True, is_writeable=False):
|
||||
flags = select.POLLHUP
|
||||
if is_readable:
|
||||
flags |= select.POLLIN
|
||||
if is_writeable:
|
||||
flags |= select.POLLOUT
|
||||
self._poll.modify(file_handler, flags)
|
||||
self._poll.modify(file_handler.fd, flags)
|
||||
# Main loop
|
||||
def _dispatch_loop(self):
|
||||
self._g_dispatch = g_dispatch = greenlet.getcurrent()
|
||||
self._g_dispatch = greenlet.getcurrent()
|
||||
busy = True
|
||||
eventtime = self.monotonic()
|
||||
while self._process:
|
||||
|
|
@ -341,50 +352,36 @@ class PollReactor(SelectReactor):
|
|||
busy = False
|
||||
res = self._poll.poll(int(math.ceil(timeout * 1000.)))
|
||||
eventtime = self.monotonic()
|
||||
for fd, event in res:
|
||||
if res:
|
||||
busy = True
|
||||
if event & (select.POLLIN | select.POLLHUP):
|
||||
self._fds[fd].read_callback(eventtime)
|
||||
if g_dispatch is not self._g_dispatch:
|
||||
self._end_greenlet(g_dispatch)
|
||||
eventtime = self.monotonic()
|
||||
break
|
||||
if event & select.POLLOUT:
|
||||
self._fds[fd].write_callback(eventtime)
|
||||
if g_dispatch is not self._g_dispatch:
|
||||
self._end_greenlet(g_dispatch)
|
||||
eventtime = self.monotonic()
|
||||
break
|
||||
eventtime = self._check_fds(eventtime, res)
|
||||
self._g_dispatch = None
|
||||
|
||||
class EPollReactor(SelectReactor):
|
||||
def __init__(self, gc_checking=False):
|
||||
SelectReactor.__init__(self, gc_checking)
|
||||
self._epoll = select.epoll()
|
||||
self._fds = {}
|
||||
self._READ = select.EPOLLIN | select.EPOLLHUP
|
||||
self._WRITE = select.EPOLLOUT
|
||||
# File descriptors
|
||||
def register_fd(self, fd, read_callback, write_callback=None):
|
||||
file_handler = ReactorFileHandler(fd, read_callback, write_callback)
|
||||
fds = self._fds.copy()
|
||||
fds[fd] = read_callback
|
||||
self._fds = fds
|
||||
self._fds[fd] = file_handler
|
||||
self._epoll.register(fd, select.EPOLLIN | select.EPOLLHUP)
|
||||
return file_handler
|
||||
def unregister_fd(self, file_handler):
|
||||
self._epoll.unregister(file_handler.fd)
|
||||
fds = self._fds.copy()
|
||||
del fds[file_handler.fd]
|
||||
self._fds = fds
|
||||
del self._fds[file_handler.fd]
|
||||
def set_fd_wake(self, file_handler, is_readable=True, is_writeable=False):
|
||||
flags = select.POLLHUP
|
||||
flags = select.EPOLLHUP
|
||||
if is_readable:
|
||||
flags |= select.EPOLLIN
|
||||
if is_writeable:
|
||||
flags |= select.EPOLLOUT
|
||||
self._epoll.modify(file_handler, flags)
|
||||
self._epoll.modify(file_handler.fd, flags)
|
||||
# Main loop
|
||||
def _dispatch_loop(self):
|
||||
self._g_dispatch = g_dispatch = greenlet.getcurrent()
|
||||
self._g_dispatch = greenlet.getcurrent()
|
||||
busy = True
|
||||
eventtime = self.monotonic()
|
||||
while self._process:
|
||||
|
|
@ -392,20 +389,9 @@ class EPollReactor(SelectReactor):
|
|||
busy = False
|
||||
res = self._epoll.poll(timeout)
|
||||
eventtime = self.monotonic()
|
||||
for fd, event in res:
|
||||
if res:
|
||||
busy = True
|
||||
if event & (select.EPOLLIN | select.EPOLLHUP):
|
||||
self._fds[fd].read_callback(eventtime)
|
||||
if g_dispatch is not self._g_dispatch:
|
||||
self._end_greenlet(g_dispatch)
|
||||
eventtime = self.monotonic()
|
||||
break
|
||||
if event & select.EPOLLOUT:
|
||||
self._fds[fd].write_callback(eventtime)
|
||||
if g_dispatch is not self._g_dispatch:
|
||||
self._end_greenlet(g_dispatch)
|
||||
eventtime = self.monotonic()
|
||||
break
|
||||
eventtime = self._check_fds(eventtime, res)
|
||||
self._g_dispatch = None
|
||||
|
||||
# Use the poll based reactor if it is available
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ class error(Exception):
|
|||
|
||||
MIN_BOTH_EDGE_DURATION = 0.000000500
|
||||
MIN_OPTIMIZED_BOTH_EDGE_DURATION = 0.000000150
|
||||
MAX_STEPCOMPRESS_ERROR = 0.000025
|
||||
|
||||
# Interface to low-level mcu and chelper code
|
||||
class MCU_stepper:
|
||||
|
|
@ -29,7 +30,7 @@ class MCU_stepper:
|
|||
self._units_in_radians = units_in_radians
|
||||
self._step_dist = rotation_dist / steps_per_rotation
|
||||
self._mcu = mcu = step_pin_params['chip']
|
||||
self._oid = oid = mcu.create_oid()
|
||||
self._oid = mcu.create_oid()
|
||||
mcu.register_config_callback(self._build_config)
|
||||
self._step_pin = step_pin_params['pin']
|
||||
self._invert_step = step_pin_params['invert']
|
||||
|
|
@ -44,8 +45,11 @@ class MCU_stepper:
|
|||
self._reset_cmd_tag = self._get_position_cmd = None
|
||||
self._active_callbacks = []
|
||||
motion_queuing = printer.load_object(config, 'motion_queuing')
|
||||
self._stepqueue = motion_queuing.allocate_stepcompress(mcu, oid)
|
||||
sname = self._name.split()[-1]
|
||||
self._syncemitter = motion_queuing.allocate_syncemitter(mcu, sname)
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
self._stepqueue = ffi_lib.syncemitter_get_stepcompress(
|
||||
self._syncemitter)
|
||||
ffi_lib.stepcompress_set_invert_sdir(self._stepqueue, self._invert_dir)
|
||||
self._stepper_kinematics = None
|
||||
self._itersolve_check_active = ffi_lib.itersolve_check_active
|
||||
|
|
@ -119,10 +123,9 @@ class MCU_stepper:
|
|||
self._get_position_cmd = self._mcu.lookup_query_command(
|
||||
"stepper_get_position oid=%c",
|
||||
"stepper_position oid=%c pos=%i", oid=self._oid)
|
||||
max_error = self._mcu.get_max_stepper_error()
|
||||
max_error_ticks = self._mcu.seconds_to_clock(max_error)
|
||||
max_error_ticks = self._mcu.seconds_to_clock(MAX_STEPCOMPRESS_ERROR)
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
ffi_lib.stepcompress_fill(self._stepqueue, max_error_ticks,
|
||||
ffi_lib.stepcompress_fill(self._stepqueue, self._oid, max_error_ticks,
|
||||
step_cmd_tag, dir_cmd_tag)
|
||||
def get_oid(self):
|
||||
return self._oid
|
||||
|
|
@ -192,7 +195,7 @@ class MCU_stepper:
|
|||
mcu_pos = self.get_mcu_position()
|
||||
self._stepper_kinematics = sk
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
ffi_lib.stepcompress_set_stepper_kinematics(self._stepqueue, sk);
|
||||
ffi_lib.syncemitter_set_stepper_kinematics(self._syncemitter, sk);
|
||||
self.set_trapq(self._trapq)
|
||||
self._set_mcu_position(mcu_pos)
|
||||
return old_sk
|
||||
|
|
@ -202,9 +205,7 @@ class MCU_stepper:
|
|||
if ret:
|
||||
raise error("Internal error in stepcompress")
|
||||
data = (self._reset_cmd_tag, self._oid, 0)
|
||||
ret = ffi_lib.stepcompress_queue_msg(self._stepqueue, data, len(data))
|
||||
if ret:
|
||||
raise error("Internal error in stepcompress")
|
||||
ffi_lib.syncemitter_queue_msg(self._syncemitter, 0, data, len(data))
|
||||
self._query_mcu_position()
|
||||
def _query_mcu_position(self):
|
||||
if self._mcu.is_fileoutput():
|
||||
|
|
|
|||
|
|
@ -45,9 +45,10 @@ class Move:
|
|||
self.max_start_v2 = 0.
|
||||
self.max_cruise_v2 = velocity**2
|
||||
self.delta_v2 = 2.0 * move_d * self.accel
|
||||
self.max_smoothed_v2 = 0.
|
||||
self.smooth_delta_v2 = 2.0 * move_d * toolhead.max_accel_to_decel
|
||||
self.next_junction_v2 = 999999999.9
|
||||
# Setup for minimum_cruise_ratio checks
|
||||
self.max_mcr_start_v2 = 0.
|
||||
self.mcr_delta_v2 = 2.0 * move_d * toolhead.mcr_pseudo_accel
|
||||
def limit_speed(self, speed, accel):
|
||||
speed2 = speed**2
|
||||
if speed2 < self.max_cruise_v2:
|
||||
|
|
@ -55,7 +56,7 @@ class Move:
|
|||
self.min_move_t = self.move_d / speed
|
||||
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)
|
||||
self.mcr_delta_v2 = min(self.mcr_delta_v2, self.delta_v2)
|
||||
def limit_next_junction_speed(self, speed):
|
||||
self.next_junction_v2 = min(self.next_junction_v2, speed**2)
|
||||
def move_error(self, msg="Move out of range"):
|
||||
|
|
@ -94,8 +95,8 @@ class Move:
|
|||
move_centripetal_v2, pmove_centripetal_v2)
|
||||
# Apply limits
|
||||
self.max_start_v2 = max_start_v2
|
||||
self.max_smoothed_v2 = min(
|
||||
max_start_v2, prev_move.max_smoothed_v2 + prev_move.smooth_delta_v2)
|
||||
self.max_mcr_start_v2 = min(
|
||||
max_start_v2, prev_move.max_mcr_start_v2 + prev_move.mcr_delta_v2)
|
||||
def set_junction(self, start_v2, cruise_v2, end_v2):
|
||||
# Determine accel, cruise, and decel portions of the move distance
|
||||
half_inv_accel = .5 / self.accel
|
||||
|
|
@ -112,7 +113,7 @@ class Move:
|
|||
self.cruise_t = cruise_d / cruise_v
|
||||
self.decel_t = decel_d / ((end_v + cruise_v) * 0.5)
|
||||
|
||||
LOOKAHEAD_FLUSH_TIME = 0.250
|
||||
LOOKAHEAD_FLUSH_TIME = 0.150
|
||||
|
||||
# Class to track a list of pending move requests and to facilitate
|
||||
# "look-ahead" across moves to reduce acceleration between moves.
|
||||
|
|
@ -125,6 +126,8 @@ class LookAheadQueue:
|
|||
self.junction_flush = LOOKAHEAD_FLUSH_TIME
|
||||
def set_flush_time(self, flush_time):
|
||||
self.junction_flush = flush_time
|
||||
def is_empty(self):
|
||||
return not self.queue
|
||||
def get_last(self):
|
||||
if self.queue:
|
||||
return self.queue[-1]
|
||||
|
|
@ -137,46 +140,45 @@ class LookAheadQueue:
|
|||
# Traverse queue from last to first move and determine maximum
|
||||
# junction speed assuming the robot comes to a complete stop
|
||||
# after the last move.
|
||||
delayed = []
|
||||
next_end_v2 = next_smoothed_v2 = peak_cruise_v2 = 0.
|
||||
junction_info = [None] * flush_count
|
||||
next_start_v2 = next_mcr_start_v2 = peak_cruise_v2 = 0.
|
||||
pending_cv2_assign = 0
|
||||
for i in range(flush_count-1, -1, -1):
|
||||
move = queue[i]
|
||||
reachable_start_v2 = next_end_v2 + move.delta_v2
|
||||
reachable_start_v2 = next_start_v2 + move.delta_v2
|
||||
start_v2 = min(move.max_start_v2, reachable_start_v2)
|
||||
reachable_smoothed_v2 = next_smoothed_v2 + move.smooth_delta_v2
|
||||
smoothed_v2 = min(move.max_smoothed_v2, reachable_smoothed_v2)
|
||||
if smoothed_v2 < reachable_smoothed_v2:
|
||||
cruise_v2 = None
|
||||
pending_cv2_assign += 1
|
||||
reach_mcr_start_v2 = next_mcr_start_v2 + move.mcr_delta_v2
|
||||
mcr_start_v2 = min(move.max_mcr_start_v2, reach_mcr_start_v2)
|
||||
if mcr_start_v2 < reach_mcr_start_v2:
|
||||
# It's possible for this move to accelerate
|
||||
if (smoothed_v2 + move.smooth_delta_v2 > next_smoothed_v2
|
||||
or delayed):
|
||||
# This move can decelerate or this is a full accel
|
||||
# move after a full decel move
|
||||
if (mcr_start_v2 + move.mcr_delta_v2 > next_mcr_start_v2
|
||||
or pending_cv2_assign > 1):
|
||||
# This move can both accel and decel, or this is a
|
||||
# full accel move followed by a full decel move
|
||||
if update_flush_count and peak_cruise_v2:
|
||||
flush_count = i
|
||||
flush_count = i + pending_cv2_assign
|
||||
update_flush_count = False
|
||||
peak_cruise_v2 = min(move.max_cruise_v2, (
|
||||
smoothed_v2 + reachable_smoothed_v2) * .5)
|
||||
if delayed:
|
||||
# Propagate peak_cruise_v2 to any delayed moves
|
||||
if not update_flush_count and i < flush_count:
|
||||
mc_v2 = peak_cruise_v2
|
||||
for m, ms_v2, me_v2 in reversed(delayed):
|
||||
mc_v2 = min(mc_v2, ms_v2)
|
||||
m.set_junction(min(ms_v2, mc_v2), mc_v2
|
||||
, min(me_v2, mc_v2))
|
||||
del delayed[:]
|
||||
if not update_flush_count and i < flush_count:
|
||||
cruise_v2 = min((start_v2 + reachable_start_v2) * .5
|
||||
, move.max_cruise_v2, peak_cruise_v2)
|
||||
move.set_junction(min(start_v2, cruise_v2), cruise_v2
|
||||
, min(next_end_v2, cruise_v2))
|
||||
else:
|
||||
# Delay calculating this move until peak_cruise_v2 is known
|
||||
delayed.append((move, start_v2, next_end_v2))
|
||||
next_end_v2 = start_v2
|
||||
next_smoothed_v2 = smoothed_v2
|
||||
peak_cruise_v2 = (mcr_start_v2 + reach_mcr_start_v2) * .5
|
||||
cruise_v2 = min((start_v2 + reachable_start_v2) * .5
|
||||
, move.max_cruise_v2, peak_cruise_v2)
|
||||
pending_cv2_assign = 0
|
||||
junction_info[i] = (move, start_v2, cruise_v2, next_start_v2)
|
||||
next_start_v2 = start_v2
|
||||
next_mcr_start_v2 = mcr_start_v2
|
||||
if update_flush_count or not flush_count:
|
||||
return []
|
||||
# Traverse queue in forward direction to propagate cruise_v2
|
||||
prev_cruise_v2 = 0.
|
||||
for i in range(flush_count):
|
||||
move, start_v2, cruise_v2, next_start_v2 = junction_info[i]
|
||||
if cruise_v2 is None:
|
||||
# This move can't accelerate - propagate cruise_v2 from previous
|
||||
cruise_v2 = min(prev_cruise_v2, start_v2)
|
||||
move.set_junction(min(start_v2, cruise_v2), cruise_v2
|
||||
, min(next_start_v2, cruise_v2))
|
||||
prev_cruise_v2 = cruise_v2
|
||||
# Remove processed moves from the queue
|
||||
res = queue[:flush_count]
|
||||
del queue[:flush_count]
|
||||
|
|
@ -190,28 +192,16 @@ class LookAheadQueue:
|
|||
# Check if enough moves have been queued to reach the target flush time.
|
||||
return self.junction_flush <= 0.
|
||||
|
||||
BUFFER_TIME_LOW = 1.0
|
||||
BUFFER_TIME_HIGH = 2.0
|
||||
BUFFER_TIME_HIGH = 1.0
|
||||
BUFFER_TIME_START = 0.250
|
||||
BGFLUSH_LOW_TIME = 0.200
|
||||
BGFLUSH_BATCH_TIME = 0.200
|
||||
BGFLUSH_EXTRA_TIME = 0.250
|
||||
MIN_KIN_TIME = 0.100
|
||||
MOVE_BATCH_TIME = 0.500
|
||||
STEPCOMPRESS_FLUSH_TIME = 0.050
|
||||
SDS_CHECK_TIME = 0.001 # step+dir+step filter in stepcompress.c
|
||||
|
||||
DRIP_SEGMENT_TIME = 0.050
|
||||
DRIP_TIME = 0.100
|
||||
PRIMING_CMD_TIME = 0.100
|
||||
|
||||
# Main code to track events (and their timing) on the printer toolhead
|
||||
class ToolHead:
|
||||
def __init__(self, config):
|
||||
self.printer = config.get_printer()
|
||||
self.reactor = self.printer.get_reactor()
|
||||
self.all_mcus = [
|
||||
m for n, m in self.printer.lookup_objects(module='mcu')]
|
||||
self.mcu = self.all_mcus[0]
|
||||
self.mcu = self.printer.lookup_object('mcu')
|
||||
self.lookahead = LookAheadQueue()
|
||||
self.lookahead.set_flush_time(BUFFER_TIME_HIGH)
|
||||
self.commanded_pos = [0., 0., 0., 0.]
|
||||
|
|
@ -222,7 +212,7 @@ class ToolHead:
|
|||
0.5, below=1., minval=0.)
|
||||
self.square_corner_velocity = config.getfloat(
|
||||
'square_corner_velocity', 5., minval=0.)
|
||||
self.junction_deviation = self.max_accel_to_decel = 0.
|
||||
self.junction_deviation = self.mcr_pseudo_accel = 0.
|
||||
self._calc_junction_deviation()
|
||||
# Input stall detection
|
||||
self.check_stall_time = 0.
|
||||
|
|
@ -236,16 +226,10 @@ class ToolHead:
|
|||
self.print_time = 0.
|
||||
self.special_queuing_state = "NeedPrime"
|
||||
self.priming_timer = None
|
||||
# Flush tracking
|
||||
self.flush_timer = self.reactor.register_timer(self._flush_handler)
|
||||
self.do_kick_flush_timer = True
|
||||
self.last_flush_time = self.min_restart_time = 0.
|
||||
self.need_flush_time = self.step_gen_time = 0.
|
||||
# Kinematic step generation scan window time tracking
|
||||
self.kin_flush_delay = SDS_CHECK_TIME
|
||||
self.kin_flush_times = []
|
||||
# Setup for generating moves
|
||||
self.motion_queuing = self.printer.load_object(config, 'motion_queuing')
|
||||
self.motion_queuing.register_flush_callback(self._handle_step_flush,
|
||||
can_add_trapq=True)
|
||||
self.trapq = self.motion_queuing.allocate_trapq()
|
||||
self.trapq_append = self.motion_queuing.lookup_trapq_append()
|
||||
# Create kinematics class
|
||||
|
|
@ -268,33 +252,13 @@ class ToolHead:
|
|||
# Register handlers
|
||||
self.printer.register_event_handler("klippy:shutdown",
|
||||
self._handle_shutdown)
|
||||
# Print time and flush tracking
|
||||
def _advance_flush_time(self, flush_time):
|
||||
flush_time = max(flush_time, self.last_flush_time)
|
||||
# Generate steps via itersolve
|
||||
sg_flush_want = min(flush_time + STEPCOMPRESS_FLUSH_TIME,
|
||||
self.print_time - self.kin_flush_delay)
|
||||
sg_flush_time = max(sg_flush_want, flush_time)
|
||||
trapq_free_time = sg_flush_time - self.kin_flush_delay
|
||||
self.motion_queuing.flush_motion_queues(flush_time, sg_flush_time,
|
||||
trapq_free_time)
|
||||
self.min_restart_time = max(self.min_restart_time, sg_flush_time)
|
||||
self.last_flush_time = flush_time
|
||||
# Print time tracking
|
||||
def _advance_move_time(self, next_print_time):
|
||||
pt_delay = self.kin_flush_delay + STEPCOMPRESS_FLUSH_TIME
|
||||
flush_time = max(self.last_flush_time, self.print_time - pt_delay)
|
||||
self.print_time = max(self.print_time, next_print_time)
|
||||
want_flush_time = max(flush_time, self.print_time - pt_delay)
|
||||
while 1:
|
||||
flush_time = min(flush_time + MOVE_BATCH_TIME, want_flush_time)
|
||||
self._advance_flush_time(flush_time)
|
||||
if flush_time >= want_flush_time:
|
||||
break
|
||||
def _calc_print_time(self):
|
||||
curtime = self.reactor.monotonic()
|
||||
est_print_time = self.mcu.estimated_print_time(curtime)
|
||||
kin_time = max(est_print_time + MIN_KIN_TIME, self.min_restart_time)
|
||||
kin_time += self.kin_flush_delay
|
||||
kin_time = self.motion_queuing.calc_step_gen_restart(est_print_time)
|
||||
min_print_time = max(est_print_time + BUFFER_TIME_START, kin_time)
|
||||
if min_print_time > self.print_time:
|
||||
self.print_time = min_print_time
|
||||
|
|
@ -328,19 +292,28 @@ class ToolHead:
|
|||
for cb in move.timing_callbacks:
|
||||
cb(next_move_time)
|
||||
# Generate steps for moves
|
||||
self.note_mcu_movequeue_activity(next_move_time + self.kin_flush_delay)
|
||||
self._advance_move_time(next_move_time)
|
||||
def _flush_lookahead(self):
|
||||
# Transit from "NeedPrime"/"Priming"/"Drip"/main state to "NeedPrime"
|
||||
self.motion_queuing.note_mcu_movequeue_activity(next_move_time)
|
||||
def _flush_lookahead(self, is_runout=False):
|
||||
# Transit from "NeedPrime"/"Priming"/main state to "NeedPrime"
|
||||
prev_print_time = self.print_time
|
||||
self._process_lookahead()
|
||||
self.special_queuing_state = "NeedPrime"
|
||||
self.need_check_pause = -1.
|
||||
self.lookahead.set_flush_time(BUFFER_TIME_HIGH)
|
||||
self.check_stall_time = 0.
|
||||
if is_runout and prev_print_time != self.print_time:
|
||||
self.check_stall_time = self.print_time
|
||||
def _handle_step_flush(self, flush_time, step_gen_time):
|
||||
if self.special_queuing_state:
|
||||
return
|
||||
# In "main" state - flush lookahead if buffer runs low
|
||||
kin_flush_delay = self.motion_queuing.get_kin_flush_delay()
|
||||
if step_gen_time >= self.print_time - kin_flush_delay - 0.001:
|
||||
self._flush_lookahead(is_runout=True)
|
||||
def flush_step_generation(self):
|
||||
self._flush_lookahead()
|
||||
self._advance_flush_time(self.step_gen_time)
|
||||
self.min_restart_time = max(self.min_restart_time, self.print_time)
|
||||
self.motion_queuing.flush_all_steps()
|
||||
def get_last_move_time(self):
|
||||
if self.special_queuing_state:
|
||||
self._flush_lookahead()
|
||||
|
|
@ -348,78 +321,59 @@ class ToolHead:
|
|||
else:
|
||||
self._process_lookahead()
|
||||
return self.print_time
|
||||
def _check_pause(self):
|
||||
eventtime = self.reactor.monotonic()
|
||||
est_print_time = self.mcu.estimated_print_time(eventtime)
|
||||
buffer_time = self.print_time - est_print_time
|
||||
if self.special_queuing_state:
|
||||
if self.check_stall_time:
|
||||
# Was in "NeedPrime" state and got there from idle input
|
||||
if est_print_time < self.check_stall_time:
|
||||
self.print_stall += 1
|
||||
self.check_stall_time = 0.
|
||||
# Transition from "NeedPrime"/"Priming" state to "Priming" state
|
||||
self.special_queuing_state = "Priming"
|
||||
self.need_check_pause = -1.
|
||||
if self.priming_timer is None:
|
||||
self.priming_timer = self.reactor.register_timer(
|
||||
self._priming_handler)
|
||||
wtime = eventtime + max(0.100, buffer_time - BUFFER_TIME_LOW)
|
||||
self.reactor.update_timer(self.priming_timer, wtime)
|
||||
# Check if there are lots of queued moves and pause if so
|
||||
while 1:
|
||||
pause_time = buffer_time - BUFFER_TIME_HIGH
|
||||
if pause_time <= 0.:
|
||||
break
|
||||
if not self.can_pause:
|
||||
self.need_check_pause = self.reactor.NEVER
|
||||
return
|
||||
eventtime = self.reactor.pause(eventtime + min(1., pause_time))
|
||||
est_print_time = self.mcu.estimated_print_time(eventtime)
|
||||
buffer_time = self.print_time - est_print_time
|
||||
if not self.special_queuing_state:
|
||||
# In main state - defer pause checking until needed
|
||||
self.need_check_pause = est_print_time + BUFFER_TIME_HIGH + 0.100
|
||||
def _priming_handler(self, eventtime):
|
||||
self.reactor.unregister_timer(self.priming_timer)
|
||||
self.priming_timer = None
|
||||
try:
|
||||
if self.special_queuing_state == "Priming":
|
||||
self._flush_lookahead()
|
||||
self.check_stall_time = self.print_time
|
||||
self._flush_lookahead(is_runout=True)
|
||||
except:
|
||||
logging.exception("Exception in priming_handler")
|
||||
self.printer.invoke_shutdown("Exception in priming_handler")
|
||||
return self.reactor.NEVER
|
||||
def _flush_handler(self, eventtime):
|
||||
try:
|
||||
def _check_priming_state(self, eventtime):
|
||||
if self.lookahead.is_empty():
|
||||
# In "NeedPrime" state and can remain there
|
||||
return
|
||||
est_print_time = self.mcu.estimated_print_time(eventtime)
|
||||
if self.check_stall_time:
|
||||
# Was in "NeedPrime" state and got there from idle input
|
||||
if est_print_time < self.check_stall_time:
|
||||
self.print_stall += 1
|
||||
self.check_stall_time = 0.
|
||||
# Transition from "NeedPrime"/"Priming" state to "Priming" state
|
||||
self.special_queuing_state = "Priming"
|
||||
self.need_check_pause = -1.
|
||||
if self.priming_timer is None:
|
||||
self.priming_timer = self.reactor.register_timer(
|
||||
self._priming_handler)
|
||||
will_pause_time = self.print_time - est_print_time - BUFFER_TIME_HIGH
|
||||
wtime = eventtime + max(0., will_pause_time) + PRIMING_CMD_TIME
|
||||
self.reactor.update_timer(self.priming_timer, wtime)
|
||||
def _check_pause(self):
|
||||
eventtime = self.reactor.monotonic()
|
||||
if self.special_queuing_state:
|
||||
# In "NeedPrime"/"Priming" state - update priming expiration timer
|
||||
self._check_priming_state(eventtime)
|
||||
# Check if there are lots of queued moves and pause if so
|
||||
did_pause = False
|
||||
while 1:
|
||||
est_print_time = self.mcu.estimated_print_time(eventtime)
|
||||
if not self.special_queuing_state:
|
||||
# In "main" state - flush lookahead if buffer runs low
|
||||
print_time = self.print_time
|
||||
buffer_time = print_time - est_print_time
|
||||
if buffer_time > BUFFER_TIME_LOW:
|
||||
# Running normally - reschedule check
|
||||
return eventtime + buffer_time - BUFFER_TIME_LOW
|
||||
# Under ran low buffer mark - flush lookahead queue
|
||||
self._flush_lookahead()
|
||||
if print_time != self.print_time:
|
||||
self.check_stall_time = self.print_time
|
||||
# In "NeedPrime"/"Priming" state - flush queues if needed
|
||||
while 1:
|
||||
end_flush = self.need_flush_time + BGFLUSH_EXTRA_TIME
|
||||
if self.last_flush_time >= end_flush:
|
||||
self.do_kick_flush_timer = True
|
||||
return self.reactor.NEVER
|
||||
buffer_time = self.last_flush_time - est_print_time
|
||||
if buffer_time > BGFLUSH_LOW_TIME:
|
||||
return eventtime + buffer_time - BGFLUSH_LOW_TIME
|
||||
ftime = est_print_time + BGFLUSH_LOW_TIME + BGFLUSH_BATCH_TIME
|
||||
self._advance_flush_time(min(end_flush, ftime))
|
||||
except:
|
||||
logging.exception("Exception in flush_handler")
|
||||
self.printer.invoke_shutdown("Exception in flush_handler")
|
||||
return self.reactor.NEVER
|
||||
pause_time = self.print_time - est_print_time - BUFFER_TIME_HIGH
|
||||
if pause_time <= 0.:
|
||||
break
|
||||
if not self.can_pause:
|
||||
self.need_check_pause = self.reactor.NEVER
|
||||
return
|
||||
pause_time = max(.005, min(1., pause_time))
|
||||
eventtime = self.reactor.pause(eventtime + pause_time)
|
||||
did_pause = True
|
||||
if not self.special_queuing_state:
|
||||
# In main state - defer pause checking
|
||||
self.need_check_pause = self.print_time
|
||||
if not did_pause:
|
||||
# May be falling behind - yield to avoid starving other tasks
|
||||
self.reactor.pause(self.reactor.NOW)
|
||||
# Movement commands
|
||||
def get_position(self):
|
||||
return list(self.commanded_pos)
|
||||
|
|
@ -458,6 +412,7 @@ class ToolHead:
|
|||
self.move(curpos, speed)
|
||||
self.printer.send_event("toolhead:manual_move")
|
||||
def dwell(self, delay):
|
||||
self._flush_lookahead()
|
||||
next_print_time = self.get_last_move_time() + max(0., delay)
|
||||
self._advance_move_time(next_print_time)
|
||||
self._check_pause()
|
||||
|
|
@ -491,32 +446,6 @@ class ToolHead:
|
|||
def get_extra_axes(self):
|
||||
return [None, None, None] + self.extra_axes
|
||||
# Homing "drip move" handling
|
||||
def drip_update_time(self, next_print_time, drip_completion):
|
||||
# Transition from "NeedPrime"/"Priming"/main state to "Drip" state
|
||||
self.special_queuing_state = "Drip"
|
||||
self.need_check_pause = self.reactor.NEVER
|
||||
self.reactor.update_timer(self.flush_timer, self.reactor.NEVER)
|
||||
self.do_kick_flush_timer = False
|
||||
self.lookahead.set_flush_time(BUFFER_TIME_HIGH)
|
||||
self.check_stall_time = 0.
|
||||
# Update print_time in segments until drip_completion signal
|
||||
flush_delay = DRIP_TIME + STEPCOMPRESS_FLUSH_TIME + self.kin_flush_delay
|
||||
while self.print_time < next_print_time:
|
||||
if drip_completion.test():
|
||||
break
|
||||
curtime = self.reactor.monotonic()
|
||||
est_print_time = self.mcu.estimated_print_time(curtime)
|
||||
wait_time = self.print_time - est_print_time - flush_delay
|
||||
if wait_time > 0. and self.can_pause:
|
||||
# Pause before sending more steps
|
||||
drip_completion.wait(curtime + wait_time)
|
||||
continue
|
||||
npt = min(self.print_time + DRIP_SEGMENT_TIME, next_print_time)
|
||||
self.note_mcu_movequeue_activity(npt + self.kin_flush_delay)
|
||||
self._advance_move_time(npt)
|
||||
# Exit "Drip" state
|
||||
self.reactor.update_timer(self.flush_timer, self.reactor.NOW)
|
||||
self.flush_step_generation()
|
||||
def _drip_load_trapq(self, submit_move):
|
||||
# Queue move into trapezoid motion queue (trapq)
|
||||
if submit_move.move_d:
|
||||
|
|
@ -524,18 +453,17 @@ class ToolHead:
|
|||
self.lookahead.add_move(submit_move)
|
||||
moves = self.lookahead.flush()
|
||||
self._calc_print_time()
|
||||
next_move_time = self.print_time
|
||||
start_time = end_time = self.print_time
|
||||
for move in moves:
|
||||
self.trapq_append(
|
||||
self.trapq, next_move_time,
|
||||
self.trapq, end_time,
|
||||
move.accel_t, move.cruise_t, move.decel_t,
|
||||
move.start_pos[0], move.start_pos[1], move.start_pos[2],
|
||||
move.axes_r[0], move.axes_r[1], move.axes_r[2],
|
||||
move.start_v, move.cruise_v, move.accel)
|
||||
next_move_time = (next_move_time + move.accel_t
|
||||
+ move.cruise_t + move.decel_t)
|
||||
end_time = end_time + move.accel_t + move.cruise_t + move.decel_t
|
||||
self.lookahead.reset()
|
||||
return next_move_time
|
||||
return start_time, end_time
|
||||
def drip_move(self, newpos, speed, drip_completion):
|
||||
# Create and verify move is valid
|
||||
newpos = newpos[:3] + self.commanded_pos[3:]
|
||||
|
|
@ -543,29 +471,25 @@ class ToolHead:
|
|||
if move.move_d:
|
||||
self.kin.check_move(move)
|
||||
# Make sure stepper movement doesn't start before nominal start time
|
||||
self.dwell(self.kin_flush_delay)
|
||||
kin_flush_delay = self.motion_queuing.get_kin_flush_delay()
|
||||
self.dwell(kin_flush_delay)
|
||||
# Transmit move in "drip" mode
|
||||
self._process_lookahead()
|
||||
next_move_time = self._drip_load_trapq(move)
|
||||
self.drip_update_time(next_move_time, drip_completion)
|
||||
start_time, end_time = self._drip_load_trapq(move)
|
||||
self.motion_queuing.drip_update_time(start_time, end_time,
|
||||
drip_completion)
|
||||
# Move finished; cleanup any remnants on trapq
|
||||
self.motion_queuing.wipe_trapq(self.trapq)
|
||||
# Misc commands
|
||||
def stats(self, eventtime):
|
||||
max_queue_time = max(self.print_time, self.last_flush_time)
|
||||
for m in self.all_mcus:
|
||||
m.check_active(max_queue_time, eventtime)
|
||||
est_print_time = self.mcu.estimated_print_time(eventtime)
|
||||
buffer_time = self.print_time - est_print_time
|
||||
is_active = buffer_time > -60. or not self.special_queuing_state
|
||||
if self.special_queuing_state == "Drip":
|
||||
buffer_time = 0.
|
||||
return is_active, "print_time=%.3f buffer_time=%.3f print_stall=%d" % (
|
||||
self.print_time, max(buffer_time, 0.), self.print_stall)
|
||||
def check_busy(self, eventtime):
|
||||
est_print_time = self.mcu.estimated_print_time(eventtime)
|
||||
lookahead_empty = not self.lookahead.queue
|
||||
return self.print_time, est_print_time, lookahead_empty
|
||||
return self.print_time, est_print_time, self.lookahead.is_empty()
|
||||
def get_status(self, eventtime):
|
||||
print_time = self.print_time
|
||||
estimated_print_time = self.mcu.estimated_print_time(eventtime)
|
||||
|
|
@ -588,33 +512,18 @@ class ToolHead:
|
|||
return self.kin
|
||||
def get_trapq(self):
|
||||
return self.trapq
|
||||
def note_step_generation_scan_time(self, delay, old_delay=0.):
|
||||
self.flush_step_generation()
|
||||
if old_delay:
|
||||
self.kin_flush_times.pop(self.kin_flush_times.index(old_delay))
|
||||
if delay:
|
||||
self.kin_flush_times.append(delay)
|
||||
new_delay = max(self.kin_flush_times + [SDS_CHECK_TIME])
|
||||
self.kin_flush_delay = new_delay
|
||||
def register_lookahead_callback(self, callback):
|
||||
last_move = self.lookahead.get_last()
|
||||
if last_move is None:
|
||||
callback(self.get_last_move_time())
|
||||
return
|
||||
last_move.timing_callbacks.append(callback)
|
||||
def note_mcu_movequeue_activity(self, mq_time, is_step_gen=True):
|
||||
self.need_flush_time = max(self.need_flush_time, mq_time)
|
||||
if is_step_gen:
|
||||
self.step_gen_time = max(self.step_gen_time, mq_time)
|
||||
if self.do_kick_flush_timer:
|
||||
self.do_kick_flush_timer = False
|
||||
self.reactor.update_timer(self.flush_timer, self.reactor.NOW)
|
||||
def get_max_velocity(self):
|
||||
return self.max_velocity, self.max_accel
|
||||
def _calc_junction_deviation(self):
|
||||
scv2 = self.square_corner_velocity**2
|
||||
self.junction_deviation = scv2 * (math.sqrt(2.) - 1.) / self.max_accel
|
||||
self.max_accel_to_decel = self.max_accel * (1. - self.min_cruise_ratio)
|
||||
self.mcr_pseudo_accel = self.max_accel * (1. - self.min_cruise_ratio)
|
||||
def set_max_velocities(self, max_velocity, max_accel,
|
||||
square_corner_velocity, min_cruise_ratio):
|
||||
if max_velocity is not None:
|
||||
|
|
@ -662,7 +571,7 @@ class ToolHeadCommandHelper:
|
|||
msg = ("max_velocity: %.6f\n"
|
||||
"max_accel: %.6f\n"
|
||||
"minimum_cruise_ratio: %.6f\n"
|
||||
"square_corner_velocity: %.6f" % (mv, ma, scv, mcr))
|
||||
"square_corner_velocity: %.6f" % (mv, ma, mcr, scv))
|
||||
self.printer.set_rollover_info("toolhead", "toolhead: %s" % (msg,))
|
||||
if (max_velocity is None and max_accel is None
|
||||
and square_corner_velocity is None and min_cruise_ratio is None):
|
||||
|
|
|
|||
|
|
@ -51,6 +51,15 @@ def create_pty(ptyname):
|
|||
# Helper code for extracting mcu build info
|
||||
######################################################################
|
||||
|
||||
def _try_read_file(filename, maxsize=32*1024):
|
||||
try:
|
||||
with open(filename, 'r') as f:
|
||||
return f.read(maxsize)
|
||||
except (IOError, OSError) as e:
|
||||
logging.debug("Exception on read %s: %s", filename,
|
||||
traceback.format_exc())
|
||||
return None
|
||||
|
||||
def dump_file_stats(build_dir, filename):
|
||||
fname = os.path.join(build_dir, filename)
|
||||
try:
|
||||
|
|
@ -66,20 +75,14 @@ def dump_mcu_build():
|
|||
build_dir = os.path.join(os.path.dirname(__file__), '..')
|
||||
# Try to log last mcu config
|
||||
dump_file_stats(build_dir, '.config')
|
||||
try:
|
||||
f = open(os.path.join(build_dir, '.config'), 'r')
|
||||
data = f.read(32*1024)
|
||||
f.close()
|
||||
data = _try_read_file(os.path.join(build_dir, '.config'))
|
||||
if data is not None:
|
||||
logging.info("========= Last MCU build config =========\n%s"
|
||||
"=======================", data)
|
||||
except:
|
||||
pass
|
||||
# Try to log last mcu build version
|
||||
dump_file_stats(build_dir, 'out/klipper.dict')
|
||||
try:
|
||||
f = open(os.path.join(build_dir, 'out/klipper.dict'), 'r')
|
||||
data = f.read(32*1024)
|
||||
f.close()
|
||||
data = _try_read_file(os.path.join(build_dir, 'out/klipper.dict'))
|
||||
data = json.loads(data)
|
||||
logging.info("Last MCU build version: %s", data.get('version', ''))
|
||||
logging.info("Last MCU build tools: %s", data.get('build_versions', ''))
|
||||
|
|
@ -111,13 +114,8 @@ setup_python2_wrappers()
|
|||
######################################################################
|
||||
|
||||
def get_cpu_info():
|
||||
try:
|
||||
f = open('/proc/cpuinfo', 'r')
|
||||
data = f.read()
|
||||
f.close()
|
||||
except (IOError, OSError) as e:
|
||||
logging.debug("Exception on read /proc/cpuinfo: %s",
|
||||
traceback.format_exc())
|
||||
data = _try_read_file('/proc/cpuinfo', maxsize=1024*1024)
|
||||
if data is None:
|
||||
return "?"
|
||||
lines = [l.split(':', 1) for l in data.split('\n')]
|
||||
lines = [(l[0].strip(), l[1].strip()) for l in lines if len(l) == 2]
|
||||
|
|
@ -125,13 +123,25 @@ def get_cpu_info():
|
|||
model_name = dict(lines).get("model name", "?")
|
||||
return "%d core %s" % (core_count, model_name)
|
||||
|
||||
def get_device_info():
|
||||
data = _try_read_file('/proc/device-tree/model')
|
||||
if data is None:
|
||||
data = _try_read_file("/sys/class/dmi/id/product_name")
|
||||
if data is None:
|
||||
return "?"
|
||||
return data.rstrip(' \0').strip()
|
||||
|
||||
def get_linux_version():
|
||||
data = _try_read_file('/proc/version')
|
||||
if data is None:
|
||||
return "?"
|
||||
return data.strip()
|
||||
|
||||
def get_version_from_file(klippy_src):
|
||||
try:
|
||||
with open(os.path.join(klippy_src, '.version')) as h:
|
||||
return h.read().rstrip()
|
||||
except IOError:
|
||||
pass
|
||||
return "?"
|
||||
data = _try_read_file(os.path.join(klippy_src, '.version'))
|
||||
if data is None:
|
||||
return "?"
|
||||
return data.rstrip()
|
||||
|
||||
def _get_repo_info(gitdir):
|
||||
repo_info = {"branch": "?", "remote": "?", "url": "?"}
|
||||
|
|
|
|||
|
|
@ -77,6 +77,9 @@ def calibrate_shaper(datas, csv_output, *, shapers, damping_ratio, scv,
|
|||
|
||||
def plot_freq_response(lognames, calibration_data, shapers,
|
||||
selected_shaper, max_freq):
|
||||
max_freq_bin = calibration_data.freq_bins.max()
|
||||
if max_freq > max_freq_bin:
|
||||
max_freq = max_freq_bin
|
||||
freqs = calibration_data.freq_bins
|
||||
psd = calibration_data.psd_sum[freqs <= max_freq]
|
||||
px = calibration_data.psd_x[freqs <= max_freq]
|
||||
|
|
@ -166,7 +169,7 @@ def main():
|
|||
default=None, help="shaper damping_ratio parameter")
|
||||
opts.add_option("--test_damping_ratios", type="string",
|
||||
dest="test_damping_ratios", default=None,
|
||||
help="a comma-separated liat of damping ratios to test " +
|
||||
help="a comma-separated list of damping ratios to test " +
|
||||
"input shaper for")
|
||||
options, args = opts.parse_args()
|
||||
if len(args) < 1:
|
||||
|
|
|
|||
|
|
@ -5,20 +5,15 @@
|
|||
# Copyright (C) 2020 Dmitry Butyugin <dmbutyugin@google.com>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import optparse, math
|
||||
import importlib, math, optparse, os, sys
|
||||
import matplotlib
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)),
|
||||
'..', 'klippy'))
|
||||
shaper_defs = importlib.import_module('.shaper_defs', 'extras')
|
||||
|
||||
# A set of damping ratios to calculate shaper response for
|
||||
DAMPING_RATIOS=[0.05, 0.1, 0.2]
|
||||
|
||||
# Parameters of the input shaper
|
||||
SHAPER_FREQ=50.0
|
||||
SHAPER_DAMPING_RATIO=0.1
|
||||
|
||||
# Simulate input shaping of step function for these true resonance frequency
|
||||
# and damping ratio
|
||||
STEP_SIMULATION_RESONANCE_FREQ=60.
|
||||
STEP_SIMULATION_DAMPING_RATIO=0.15
|
||||
DEFAULT_DAMPING_RATIOS=[0.075, 0.1, 0.15]
|
||||
|
||||
# If set, defines which range of frequencies to plot shaper frequency response
|
||||
PLOT_FREQ_RANGE = [] # If empty, will be automatically determined
|
||||
|
|
@ -30,86 +25,8 @@ PLOT_FREQ_STEP = .01
|
|||
# Input shapers
|
||||
######################################################################
|
||||
|
||||
def get_zv_shaper():
|
||||
df = math.sqrt(1. - SHAPER_DAMPING_RATIO**2)
|
||||
K = math.exp(-SHAPER_DAMPING_RATIO * math.pi / df)
|
||||
t_d = 1. / (SHAPER_FREQ * df)
|
||||
A = [1., K]
|
||||
T = [0., .5*t_d]
|
||||
return (A, T, "ZV")
|
||||
|
||||
def get_zvd_shaper():
|
||||
df = math.sqrt(1. - SHAPER_DAMPING_RATIO**2)
|
||||
K = math.exp(-SHAPER_DAMPING_RATIO * math.pi / df)
|
||||
t_d = 1. / (SHAPER_FREQ * df)
|
||||
A = [1., 2.*K, K**2]
|
||||
T = [0., .5*t_d, t_d]
|
||||
return (A, T, "ZVD")
|
||||
|
||||
def get_mzv_shaper():
|
||||
df = math.sqrt(1. - SHAPER_DAMPING_RATIO**2)
|
||||
K = math.exp(-.75 * SHAPER_DAMPING_RATIO * math.pi / df)
|
||||
t_d = 1. / (SHAPER_FREQ * df)
|
||||
|
||||
a1 = 1. - 1. / math.sqrt(2.)
|
||||
a2 = (math.sqrt(2.) - 1.) * K
|
||||
a3 = a1 * K * K
|
||||
|
||||
A = [a1, a2, a3]
|
||||
T = [0., .375*t_d, .75*t_d]
|
||||
return (A, T, "MZV")
|
||||
|
||||
def get_ei_shaper():
|
||||
v_tol = 0.05 # vibration tolerance
|
||||
df = math.sqrt(1. - SHAPER_DAMPING_RATIO**2)
|
||||
K = math.exp(-SHAPER_DAMPING_RATIO * math.pi / df)
|
||||
t_d = 1. / (SHAPER_FREQ * df)
|
||||
|
||||
a1 = .25 * (1. + v_tol)
|
||||
a2 = .5 * (1. - v_tol) * K
|
||||
a3 = a1 * K * K
|
||||
|
||||
A = [a1, a2, a3]
|
||||
T = [0., .5*t_d, t_d]
|
||||
return (A, T, "EI")
|
||||
|
||||
def get_2hump_ei_shaper():
|
||||
v_tol = 0.05 # vibration tolerance
|
||||
df = math.sqrt(1. - SHAPER_DAMPING_RATIO**2)
|
||||
K = math.exp(-SHAPER_DAMPING_RATIO * math.pi / df)
|
||||
t_d = 1. / (SHAPER_FREQ * df)
|
||||
|
||||
V2 = v_tol**2
|
||||
X = pow(V2 * (math.sqrt(1. - V2) + 1.), 1./3.)
|
||||
a1 = (3.*X*X + 2.*X + 3.*V2) / (16.*X)
|
||||
a2 = (.5 - a1) * K
|
||||
a3 = a2 * K
|
||||
a4 = a1 * K * K * K
|
||||
|
||||
A = [a1, a2, a3, a4]
|
||||
T = [0., .5*t_d, t_d, 1.5*t_d]
|
||||
return (A, T, "2-hump EI")
|
||||
|
||||
def get_3hump_ei_shaper():
|
||||
v_tol = 0.05 # vibration tolerance
|
||||
df = math.sqrt(1. - SHAPER_DAMPING_RATIO**2)
|
||||
K = math.exp(-SHAPER_DAMPING_RATIO * math.pi / df)
|
||||
t_d = 1. / (SHAPER_FREQ * df)
|
||||
|
||||
K2 = K*K
|
||||
a1 = 0.0625 * (1. + 3. * v_tol + 2. * math.sqrt(2. * (v_tol + 1.) * v_tol))
|
||||
a2 = 0.25 * (1. - v_tol) * K
|
||||
a3 = (0.5 * (1. + v_tol) - 2. * a1) * K2
|
||||
a4 = a2 * K2
|
||||
a5 = a1 * K2 * K2
|
||||
|
||||
A = [a1, a2, a3, a4, a5]
|
||||
T = [0., .5*t_d, t_d, 1.5*t_d, 2.*t_d]
|
||||
return (A, T, "3-hump EI")
|
||||
|
||||
|
||||
def estimate_shaper(shaper, freq, damping_ratio):
|
||||
A, T, _ = shaper
|
||||
A, T = shaper
|
||||
n = len(T)
|
||||
inv_D = 1. / sum(A)
|
||||
omega = 2. * math.pi * freq
|
||||
|
|
@ -123,14 +40,18 @@ def estimate_shaper(shaper, freq, damping_ratio):
|
|||
return math.sqrt(S*S + C*C) * inv_D
|
||||
|
||||
def shift_pulses(shaper):
|
||||
A, T, name = shaper
|
||||
A, T = shaper
|
||||
n = len(T)
|
||||
ts = sum([A[i] * T[i] for i in range(n)]) / sum(A)
|
||||
for i in range(n):
|
||||
T[i] -= ts
|
||||
|
||||
# Shaper selection
|
||||
get_shaper = get_ei_shaper
|
||||
def get_shaper(shaper_name, shaper_freq, damping_ratio):
|
||||
for s in shaper_defs.INPUT_SHAPERS:
|
||||
if shaper_name.lower() == s.name:
|
||||
return s.init_func(shaper_freq, damping_ratio)
|
||||
return shaper_defs.get_none_shaper()
|
||||
|
||||
|
||||
######################################################################
|
||||
|
|
@ -148,44 +69,46 @@ def bisect(func, left, right):
|
|||
right = mid
|
||||
return .5 * (left + right)
|
||||
|
||||
def find_shaper_plot_range(shaper, vib_tol):
|
||||
def find_shaper_plot_range(shaper, shaper_freq, test_damping_ratios, vib_tol):
|
||||
def eval_shaper(freq):
|
||||
return estimate_shaper(shaper, freq, DAMPING_RATIOS[0]) - vib_tol
|
||||
return estimate_shaper(shaper, freq, test_damping_ratios[0]) - vib_tol
|
||||
if not PLOT_FREQ_RANGE:
|
||||
left = bisect(eval_shaper, 0., SHAPER_FREQ)
|
||||
right = bisect(eval_shaper, SHAPER_FREQ, 2.4 * SHAPER_FREQ)
|
||||
left = bisect(eval_shaper, 0., shaper_freq)
|
||||
right = bisect(eval_shaper, shaper_freq, 2.4 * shaper_freq)
|
||||
else:
|
||||
left, right = PLOT_FREQ_RANGE
|
||||
return (left, right)
|
||||
|
||||
def gen_shaper_response(shaper):
|
||||
def gen_shaper_response(shaper, shaper_freq, test_damping_ratios):
|
||||
# Calculate shaper vibration response on a range of frequencies
|
||||
response = []
|
||||
freqs = []
|
||||
freq, freq_end = find_shaper_plot_range(shaper, vib_tol=0.25)
|
||||
freq, freq_end = find_shaper_plot_range(shaper, shaper_freq,
|
||||
test_damping_ratios, vib_tol=0.25)
|
||||
while freq <= freq_end:
|
||||
vals = []
|
||||
for damping_ratio in DAMPING_RATIOS:
|
||||
for damping_ratio in test_damping_ratios:
|
||||
vals.append(estimate_shaper(shaper, freq, damping_ratio))
|
||||
response.append(vals)
|
||||
freqs.append(freq)
|
||||
freq += PLOT_FREQ_STEP
|
||||
legend = ['damping ratio = %.3f' % d_r for d_r in DAMPING_RATIOS]
|
||||
legend = ['damping ratio = %.3f' % d_r for d_r in test_damping_ratios]
|
||||
return freqs, response, legend
|
||||
|
||||
def gen_shaped_step_function(shaper):
|
||||
def gen_shaped_step_function(shaper, shaper_freq,
|
||||
system_freq, system_damping_ratio):
|
||||
# Calculate shaping of a step function
|
||||
A, T, _ = shaper
|
||||
A, T = shaper
|
||||
inv_D = 1. / sum(A)
|
||||
n = len(T)
|
||||
|
||||
omega = 2. * math.pi * STEP_SIMULATION_RESONANCE_FREQ
|
||||
damping = STEP_SIMULATION_DAMPING_RATIO * omega
|
||||
omega_d = omega * math.sqrt(1. - STEP_SIMULATION_DAMPING_RATIO**2)
|
||||
phase = math.acos(STEP_SIMULATION_DAMPING_RATIO)
|
||||
omega = 2. * math.pi * system_freq
|
||||
damping = system_damping_ratio * omega
|
||||
omega_d = omega * math.sqrt(1. - system_damping_ratio**2)
|
||||
phase = math.acos(system_damping_ratio)
|
||||
|
||||
t_start = T[0] - .5 / SHAPER_FREQ
|
||||
t_end = T[-1] + 1.5 / STEP_SIMULATION_RESONANCE_FREQ
|
||||
t_start = T[0] - .5 / shaper_freq
|
||||
t_end = T[-1] + 1.5 / system_freq
|
||||
result = []
|
||||
time = []
|
||||
t = t_start
|
||||
|
|
@ -214,20 +137,24 @@ def gen_shaped_step_function(shaper):
|
|||
|
||||
result.append(val)
|
||||
time.append(t)
|
||||
t += .01 / SHAPER_FREQ
|
||||
t += .01 / shaper_freq
|
||||
legend = ['step', 'shaper commanded', 'system response']
|
||||
return time, result, legend
|
||||
|
||||
|
||||
def plot_shaper(shaper):
|
||||
def plot_shaper(shaper_name, shaper_freq, damping_ratio, test_damping_ratios,
|
||||
system_freq, system_damping_ratio):
|
||||
shaper = get_shaper(shaper_name, shaper_freq, damping_ratio)
|
||||
shift_pulses(shaper)
|
||||
freqs, response, response_legend = gen_shaper_response(shaper)
|
||||
time, step_vals, step_legend = gen_shaped_step_function(shaper)
|
||||
freqs, response, response_legend = gen_shaper_response(
|
||||
shaper, shaper_freq, test_damping_ratios)
|
||||
time, step_vals, step_legend = gen_shaped_step_function(
|
||||
shaper, shaper_freq, system_freq, system_damping_ratio)
|
||||
|
||||
fig, (ax1, ax2) = matplotlib.pyplot.subplots(nrows=2, figsize=(10,9))
|
||||
ax1.set_title("Vibration response simulation for shaper '%s',\n"
|
||||
"shaper_freq=%.1f Hz, damping_ratio=%.3f"
|
||||
% (shaper[-1], SHAPER_FREQ, SHAPER_DAMPING_RATIO))
|
||||
% (shaper_name, shaper_freq, damping_ratio))
|
||||
ax1.plot(freqs, response)
|
||||
ax1.set_ylim(bottom=0.)
|
||||
fontP = matplotlib.font_manager.FontProperties()
|
||||
|
|
@ -241,8 +168,7 @@ def plot_shaper(shaper):
|
|||
ax1.grid(which='minor', color='lightgrey')
|
||||
|
||||
ax2.set_title("Unit step input, resonance frequency=%.1f Hz, "
|
||||
"damping ratio=%.3f" % (STEP_SIMULATION_RESONANCE_FREQ,
|
||||
STEP_SIMULATION_DAMPING_RATIO))
|
||||
"damping ratio=%.3f" % (system_freq, system_damping_ratio))
|
||||
ax2.plot(time, step_vals)
|
||||
ax2.legend(step_legend, loc='best', prop=fontP)
|
||||
ax2.set_xlabel('Time, sec')
|
||||
|
|
@ -264,13 +190,48 @@ def main():
|
|||
opts = optparse.OptionParser(usage)
|
||||
opts.add_option("-o", "--output", type="string", dest="output",
|
||||
default=None, help="filename of output graph")
|
||||
opts.add_option("--shaper", type="string", dest="shaper", default="mzv",
|
||||
help="a shaper to plot")
|
||||
opts.add_option("--shaper_freq", type="float", dest="shaper_freq",
|
||||
default=50.0, help="shaper frequency")
|
||||
opts.add_option("--damping_ratio", type="float", dest="damping_ratio",
|
||||
default=shaper_defs.DEFAULT_DAMPING_RATIO,
|
||||
help="shaper damping_ratio parameter")
|
||||
opts.add_option("--test_damping_ratios", type="string",
|
||||
dest="test_damping_ratios",
|
||||
default=",".join(["%.3f" % dr
|
||||
for dr in DEFAULT_DAMPING_RATIOS]),
|
||||
help="a comma-separated list of damping ratios to test " +
|
||||
"input shaper for")
|
||||
opts.add_option("--system_freq", type="float", dest="system_freq",
|
||||
default=60.0,
|
||||
help="natural frequency of a system for step simulation")
|
||||
opts.add_option("--system_damping_ratio", type="float",
|
||||
dest="system_damping_ratio", default=0.15,
|
||||
help="damping_ratio of a system for step simulation")
|
||||
options, args = opts.parse_args()
|
||||
if len(args) != 0:
|
||||
opts.error("Incorrect number of arguments")
|
||||
|
||||
if options.shaper.lower() not in [
|
||||
s.name for s in shaper_defs.INPUT_SHAPERS]:
|
||||
opts.error("Invalid --shaper=%s specified" % options.shaper)
|
||||
|
||||
if options.test_damping_ratios:
|
||||
try:
|
||||
test_damping_ratios = [float(s) for s in
|
||||
options.test_damping_ratios.split(',')]
|
||||
except ValueError:
|
||||
opts.error("invalid floating point value in " +
|
||||
"--test_damping_ratios param")
|
||||
else:
|
||||
test_damping_ratios = None
|
||||
|
||||
# Draw graph
|
||||
setup_matplotlib(options.output is not None)
|
||||
fig = plot_shaper(get_shaper())
|
||||
fig = plot_shaper(options.shaper, options.shaper_freq,
|
||||
options.damping_ratio, test_damping_ratios,
|
||||
options.system_freq, options.system_damping_ratio)
|
||||
|
||||
# Show graph
|
||||
if options.output is None:
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
#!/usr/bin/env python
|
||||
# Script to parse a logging file, extract the stats, and graph them
|
||||
#
|
||||
# Copyright (C) 2016-2021 Kevin O'Connor <kevin@koconnor.net>
|
||||
# Copyright (C) 2016-2025 Kevin O'Connor <kevin@koconnor.net>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import optparse, datetime
|
||||
import matplotlib
|
||||
|
||||
MAXBANDWIDTH=25000.
|
||||
MAXBUFFER=2.
|
||||
MAXBUFFER=1.
|
||||
STATS_INTERVAL=5.
|
||||
TASK_MAX=0.0025
|
||||
|
||||
|
|
|
|||
|
|
@ -123,7 +123,8 @@ BOARD_DEFS = {
|
|||
'spi_bus': "swspi",
|
||||
'spi_pins': "PC8,PD2,PC12",
|
||||
'cs_pin': "PC11",
|
||||
'skip_verify': True
|
||||
'skip_verify': True,
|
||||
'requires_unique_fw_name': True
|
||||
},
|
||||
'monster8': {
|
||||
'mcu': "stm32f407xx",
|
||||
|
|
|
|||
|
|
@ -1380,7 +1380,32 @@ class MCUConnection:
|
|||
input_sha = hashlib.sha1()
|
||||
sd_sha = hashlib.sha1()
|
||||
klipper_bin_path = self.board_config['klipper_bin_path']
|
||||
add_ts = self.board_config.get('requires_unique_fw_name', False)
|
||||
fw_path = self.board_config.get('firmware_path', "firmware.bin")
|
||||
if add_ts:
|
||||
fw_dir = os.path.dirname(fw_path)
|
||||
fw_name, fw_ext = os.path.splitext(os.path.basename(fw_path))
|
||||
ts = time.strftime("%Y%m%d%H%M%S")
|
||||
fw_name_ts = f"{ts}{fw_name}{fw_ext}"
|
||||
if fw_dir:
|
||||
fw_path = os.path.join(fw_dir, fw_name_ts)
|
||||
else:
|
||||
fw_path = fw_name_ts
|
||||
list_dir = fw_dir if fw_dir else ""
|
||||
try:
|
||||
output_line("\nSD Card FW Directory Contents:")
|
||||
for f in self.fatfs.list_sd_directory(list_dir):
|
||||
fname = f['name'].decode('utf-8')
|
||||
if fname.endswith(fw_ext):
|
||||
self.fatfs.remove_item(
|
||||
os.path.join(list_dir, fname)
|
||||
)
|
||||
output_line(
|
||||
"Old firmware file %s found and deleted"
|
||||
% (fname,)
|
||||
)
|
||||
except Exception:
|
||||
logging.exception("Error cleaning old firmware files")
|
||||
try:
|
||||
with open(klipper_bin_path, 'rb') as local_f:
|
||||
with self.fatfs.open_file(fw_path, "wb") as sd_f:
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ lis2dw_query(struct lis2dw *ax, uint8_t oid)
|
|||
if (ax->model == LIS3DH)
|
||||
fifo_empty = fifo[1] & 0x20;
|
||||
else
|
||||
fifo_empty = fifo[1] & 0x3F;
|
||||
fifo_empty = ((fifo[1] & 0x3F) == 0);
|
||||
|
||||
fifo_ovrn = fifo[1] & 0x40;
|
||||
|
||||
|
|
@ -167,7 +167,7 @@ lis2dw_query(struct lis2dw *ax, uint8_t oid)
|
|||
if (ax->model == LIS3DH)
|
||||
fifo_empty = fifo[0] & 0x20;
|
||||
else
|
||||
fifo_empty = fifo[0] & 0x3F;
|
||||
fifo_empty = ((fifo[0] & 0x3F) == 0);
|
||||
|
||||
fifo_ovrn = fifo[0] & 0x40;
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ DECL_ENUMERATION_RANGE("pin", "PH0", GPIO('H', 0), 16);
|
|||
DECL_ENUMERATION_RANGE("pin", "PI0", GPIO('I', 0), 16);
|
||||
#endif
|
||||
|
||||
GPIO_TypeDef * const digital_regs[] = {
|
||||
static GPIO_TypeDef * const digital_regs[] = {
|
||||
['A' - 'A'] = GPIOA, GPIOB, GPIOC,
|
||||
#ifdef GPIOD
|
||||
['D' - 'A'] = GPIOD,
|
||||
|
|
@ -66,20 +66,20 @@ regs_to_pin(GPIO_TypeDef *regs, uint32_t bit)
|
|||
return 0;
|
||||
}
|
||||
|
||||
// Verify that a gpio is a valid pin
|
||||
static int
|
||||
gpio_valid(uint32_t pin)
|
||||
// Verify that a gpio is a valid pin and return its hardware register
|
||||
GPIO_TypeDef *
|
||||
gpio_pin_to_regs(uint32_t pin)
|
||||
{
|
||||
uint32_t port = GPIO2PORT(pin);
|
||||
return port < ARRAY_SIZE(digital_regs) && digital_regs[port];
|
||||
if (port >= ARRAY_SIZE(digital_regs) || !digital_regs[port])
|
||||
shutdown("Not a valid pin");
|
||||
return digital_regs[port];
|
||||
}
|
||||
|
||||
struct gpio_out
|
||||
gpio_out_setup(uint32_t pin, uint32_t val)
|
||||
{
|
||||
if (!gpio_valid(pin))
|
||||
shutdown("Not an output pin");
|
||||
GPIO_TypeDef *regs = digital_regs[GPIO2PORT(pin)];
|
||||
GPIO_TypeDef *regs = gpio_pin_to_regs(pin);
|
||||
gpio_clock_enable(regs);
|
||||
struct gpio_out g = { .regs=regs, .bit=GPIO2BIT(pin) };
|
||||
gpio_out_reset(g, val);
|
||||
|
|
@ -129,9 +129,7 @@ gpio_out_write(struct gpio_out g, uint32_t val)
|
|||
struct gpio_in
|
||||
gpio_in_setup(uint32_t pin, int32_t pull_up)
|
||||
{
|
||||
if (!gpio_valid(pin))
|
||||
shutdown("Not a valid input pin");
|
||||
GPIO_TypeDef *regs = digital_regs[GPIO2PORT(pin)];
|
||||
GPIO_TypeDef *regs = gpio_pin_to_regs(pin);
|
||||
struct gpio_in g = { .regs=regs, .bit=GPIO2BIT(pin) };
|
||||
gpio_in_reset(g, pull_up);
|
||||
return g;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
void
|
||||
gpio_peripheral(uint32_t gpio, uint32_t mode, int pullup)
|
||||
{
|
||||
GPIO_TypeDef *regs = digital_regs[GPIO2PORT(gpio)];
|
||||
GPIO_TypeDef *regs = gpio_pin_to_regs(gpio);
|
||||
|
||||
// Enable GPIO clock
|
||||
gpio_clock_enable(regs);
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
#endif
|
||||
|
||||
// gpio.c
|
||||
extern GPIO_TypeDef * const digital_regs[];
|
||||
GPIO_TypeDef *gpio_pin_to_regs(uint32_t pin);
|
||||
#define GPIO(PORT, NUM) (((PORT)-'A') * 16 + (NUM))
|
||||
#define GPIO2PORT(PIN) ((PIN) / 16)
|
||||
#define GPIO2BIT(PIN) (1<<((PIN) % 16))
|
||||
|
|
|
|||
328
src/stm32/spi.c
328
src/stm32/spi.c
|
|
@ -15,79 +15,281 @@ struct spi_info {
|
|||
uint8_t miso_pin, mosi_pin, sck_pin, miso_af, mosi_af, sck_af;
|
||||
};
|
||||
|
||||
DECL_ENUMERATION("spi_bus", "spi2", 0);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2", "PB14,PB15,PB13");
|
||||
DECL_ENUMERATION("spi_bus", "spi1", 1);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1", "PA6,PA7,PA5");
|
||||
DECL_ENUMERATION("spi_bus", "spi1a", 2);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1a", "PB4,PB5,PB3");
|
||||
|
||||
#if CONFIG_MACH_STM32G4
|
||||
DECL_ENUMERATION("spi_bus", "spi2_PA10_PA11_PF1", 3);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2_PA10_PA11_PF1", "PA10,PA11,PF1");
|
||||
#elif !CONFIG_MACH_STM32F1
|
||||
DECL_ENUMERATION("spi_bus", "spi2a", 3);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2a", "PC2,PC3,PB10");
|
||||
#endif
|
||||
#ifdef SPI3
|
||||
DECL_ENUMERATION("spi_bus", "spi3", 4);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi3", "PB4,PB5,PB3");
|
||||
#if CONFIG_MACH_STM32F4 || CONFIG_MACH_STM32G4
|
||||
#if CONFIG_MACH_STM32F0
|
||||
DECL_ENUMERATION("spi_bus", "spi2_PB14_PB15_PB13", 0);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2_PB14_PB15_PB13", "PB14,PB15,PB13");
|
||||
DECL_ENUMERATION("spi_bus", "spi1_PA6_PA7_PA5", 1);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1_PA6_PA7_PA5", "PA6,PA7,PA5");
|
||||
DECL_ENUMERATION("spi_bus", "spi1_PB4_PB5_PB3", 2);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1_PB4_PB5_PB3", "PB4,PB5,PB3");
|
||||
DECL_ENUMERATION("spi_bus", "spi2_PC2_PC3_PB10", 3);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2_PC2_PC3_PB10", "PC2,PC3,PB10");
|
||||
// Deprecated "spi1" style mappings
|
||||
DECL_ENUMERATION("spi_bus", "spi2", 0);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2", "PB14,PB15,PB13");
|
||||
DECL_ENUMERATION("spi_bus", "spi1", 1);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1", "PA6,PA7,PA5");
|
||||
DECL_ENUMERATION("spi_bus", "spi1a", 2);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1a", "PB4,PB5,PB3");
|
||||
DECL_ENUMERATION("spi_bus", "spi2a", 3);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2a", "PC2,PC3,PB10");
|
||||
#elif CONFIG_MACH_STM32F1
|
||||
DECL_ENUMERATION("spi_bus", "spi2_PB14_PB15_PB13", 0);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2_PB14_PB15_PB13", "PB14,PB15,PB13");
|
||||
DECL_ENUMERATION("spi_bus", "spi1_PA6_PA7_PA5", 1);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1_PA6_PA7_PA5", "PA6,PA7,PA5");
|
||||
DECL_ENUMERATION("spi_bus", "spi1_PB4_PB5_PB3", 2);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1_PB4_PB5_PB3", "PB4,PB5,PB3");
|
||||
DECL_ENUMERATION("spi_bus", "spi3_PB4_PB5_PB3", 3);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi3_PB4_PB5_PB3", "PB4,PB5,PB3");
|
||||
// Deprecated "spi1" style mappings
|
||||
DECL_ENUMERATION("spi_bus", "spi2", 0);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2", "PB14,PB15,PB13");
|
||||
DECL_ENUMERATION("spi_bus", "spi1", 1);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1", "PA6,PA7,PA5");
|
||||
DECL_ENUMERATION("spi_bus", "spi1a", 2);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1a", "PB4,PB5,PB3");
|
||||
DECL_ENUMERATION("spi_bus", "spi3", 3);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi3", "PB4,PB5,PB3");
|
||||
#elif CONFIG_MACH_STM32F2
|
||||
DECL_ENUMERATION("spi_bus", "spi2_PB14_PB15_PB13", 0);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2_PB14_PB15_PB13", "PB14,PB15,PB13");
|
||||
DECL_ENUMERATION("spi_bus", "spi1_PA6_PA7_PA5", 1);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1_PA6_PA7_PA5", "PA6,PA7,PA5");
|
||||
DECL_ENUMERATION("spi_bus", "spi1_PB4_PB5_PB3", 2);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1_PB4_PB5_PB3", "PB4,PB5,PB3");
|
||||
DECL_ENUMERATION("spi_bus", "spi2_PC2_PC3_PB10", 3);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2_PC2_PC3_PB10", "PC2,PC3,PB10");
|
||||
DECL_ENUMERATION("spi_bus", "spi3_PB4_PB5_PB3", 4);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi3_PB4_PB5_PB3", "PB4,PB5,PB3");
|
||||
// Deprecated "spi1" style mappings
|
||||
DECL_ENUMERATION("spi_bus", "spi2", 0);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2", "PB14,PB15,PB13");
|
||||
DECL_ENUMERATION("spi_bus", "spi1", 1);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1", "PA6,PA7,PA5");
|
||||
DECL_ENUMERATION("spi_bus", "spi1a", 2);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1a", "PB4,PB5,PB3");
|
||||
DECL_ENUMERATION("spi_bus", "spi2a", 3);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2a", "PC2,PC3,PB10");
|
||||
DECL_ENUMERATION("spi_bus", "spi3", 4);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi3", "PB4,PB5,PB3");
|
||||
#elif CONFIG_MACH_STM32F4
|
||||
DECL_ENUMERATION("spi_bus", "spi2_PB14_PB15_PB13", 0);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2_PB14_PB15_PB13", "PB14,PB15,PB13");
|
||||
DECL_ENUMERATION("spi_bus", "spi1_PA6_PA7_PA5", 1);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1_PA6_PA7_PA5", "PA6,PA7,PA5");
|
||||
DECL_ENUMERATION("spi_bus", "spi1_PB4_PB5_PB3", 2);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1_PB4_PB5_PB3", "PB4,PB5,PB3");
|
||||
DECL_ENUMERATION("spi_bus", "spi2_PC2_PC3_PB10", 3);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2_PC2_PC3_PB10", "PC2,PC3,PB10");
|
||||
DECL_ENUMERATION("spi_bus", "spi3_PB4_PB5_PB3", 4);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi3_PB4_PB5_PB3", "PB4,PB5,PB3");
|
||||
DECL_ENUMERATION("spi_bus", "spi3_PC11_PC12_PC10", 5);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi3_PC11_PC12_PC10", "PC11,PC12,PC10");
|
||||
DECL_ENUMERATION("spi_bus", "spi2_PI2_PI3_PI1", 6);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2_PI2_PI3_PI1", "PI2,PI3,PI1");
|
||||
#ifdef SPI4
|
||||
DECL_ENUMERATION("spi_bus", "spi4_PE13_PE14_PE12", 7);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi4_PE13_PE14_PE12", "PE13,PE14,PE12");
|
||||
#define SPI6_INDEX (1 + 7)
|
||||
#else
|
||||
#define SPI6_INDEX (0 + 7)
|
||||
#endif
|
||||
#ifdef SPI6
|
||||
DECL_ENUMERATION("spi_bus", "spi6_PG12_PG14_PG13", SPI6_INDEX);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi6_PG12_PG14_PG13", "PG12,PG14,PG13");
|
||||
#endif
|
||||
// Deprecated "spi1" style mappings
|
||||
DECL_ENUMERATION("spi_bus", "spi2", 0);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2", "PB14,PB15,PB13");
|
||||
DECL_ENUMERATION("spi_bus", "spi1", 1);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1", "PA6,PA7,PA5");
|
||||
DECL_ENUMERATION("spi_bus", "spi1a", 2);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1a", "PB4,PB5,PB3");
|
||||
DECL_ENUMERATION("spi_bus", "spi2a", 3);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2a", "PC2,PC3,PB10");
|
||||
DECL_ENUMERATION("spi_bus", "spi3", 4);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi3", "PB4,PB5,PB3");
|
||||
DECL_ENUMERATION("spi_bus", "spi3a", 5);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi3a", "PC11,PC12,PC10");
|
||||
DECL_ENUMERATION("spi_bus", "spi2b", 6);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2b", "PI2,PI3,PI1");
|
||||
#ifdef SPI4
|
||||
DECL_ENUMERATION("spi_bus", "spi4", 7);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi4", "PE13,PE14,PE12");
|
||||
#endif
|
||||
#elif CONFIG_MACH_STM32F7
|
||||
DECL_ENUMERATION("spi_bus", "spi2_PB14_PB15_PB13", 0);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2_PB14_PB15_PB13", "PB14,PB15,PB13");
|
||||
DECL_ENUMERATION("spi_bus", "spi1_PA6_PA7_PA5", 1);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1_PA6_PA7_PA5", "PA6,PA7,PA5");
|
||||
DECL_ENUMERATION("spi_bus", "spi1_PB4_PB5_PB3", 2);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1_PB4_PB5_PB3", "PB4,PB5,PB3");
|
||||
DECL_ENUMERATION("spi_bus", "spi2_PC2_PC3_PB10", 3);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2_PC2_PC3_PB10", "PC2,PC3,PB10");
|
||||
DECL_ENUMERATION("spi_bus", "spi3_PB4_PB5_PB3", 4);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi3_PB4_PB5_PB3", "PB4,PB5,PB3");
|
||||
// Deprecated "spi1" style mappings
|
||||
DECL_ENUMERATION("spi_bus", "spi2", 0);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2", "PB14,PB15,PB13");
|
||||
DECL_ENUMERATION("spi_bus", "spi1", 1);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1", "PA6,PA7,PA5");
|
||||
DECL_ENUMERATION("spi_bus", "spi1a", 2);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1a", "PB4,PB5,PB3");
|
||||
DECL_ENUMERATION("spi_bus", "spi2a", 3);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2a", "PC2,PC3,PB10");
|
||||
DECL_ENUMERATION("spi_bus", "spi3", 4);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi3", "PB4,PB5,PB3");
|
||||
#elif CONFIG_MACH_STM32G0
|
||||
DECL_ENUMERATION("spi_bus", "spi2_PB14_PB15_PB13", 0);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2_PB14_PB15_PB13", "PB14,PB15,PB13");
|
||||
DECL_ENUMERATION("spi_bus", "spi1_PA6_PA7_PA5", 1);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1_PA6_PA7_PA5", "PA6,PA7,PA5");
|
||||
DECL_ENUMERATION("spi_bus", "spi1_PB4_PB5_PB3", 2);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1_PB4_PB5_PB3", "PB4,PB5,PB3");
|
||||
DECL_ENUMERATION("spi_bus", "spi2_PC2_PC3_PB10", 3);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2_PC2_PC3_PB10", "PC2,PC3,PB10");
|
||||
DECL_ENUMERATION("spi_bus", "spi2_PB2_PB11_PB10", 4);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2_PB2_PB11_PB10", "PB2,PB11,PB10");
|
||||
DECL_ENUMERATION("spi_bus", "spi2_PB6_PB7_PB8", 5);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2_PB6_PB7_PB8", "PB6,PB7,PB8");
|
||||
#ifdef SPI3
|
||||
DECL_ENUMERATION("spi_bus", "spi3_PB4_PB5_PB3", 6);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi3_PB4_PB5_PB3", "PB4,PB5,PB3");
|
||||
DECL_ENUMERATION("spi_bus", "spi3_PC11_PC12_PC10", 7);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi3_PC11_PC12_PC10", "PC11,PC12,PC10");
|
||||
#endif
|
||||
// Deprecated "spi1" style mappings
|
||||
DECL_ENUMERATION("spi_bus", "spi2", 0);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2", "PB14,PB15,PB13");
|
||||
DECL_ENUMERATION("spi_bus", "spi1", 1);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1", "PA6,PA7,PA5");
|
||||
DECL_ENUMERATION("spi_bus", "spi1a", 2);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1a", "PB4,PB5,PB3");
|
||||
DECL_ENUMERATION("spi_bus", "spi2a", 3);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2a", "PC2,PC3,PB10");
|
||||
#ifdef SPI3
|
||||
DECL_ENUMERATION("spi_bus", "spi3", 6);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi3", "PB4,PB5,PB3");
|
||||
#endif
|
||||
#elif CONFIG_MACH_STM32G4
|
||||
DECL_ENUMERATION("spi_bus", "spi2_PB14_PB15_PB13", 0);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2_PB14_PB15_PB13", "PB14,PB15,PB13");
|
||||
DECL_ENUMERATION("spi_bus", "spi1_PA6_PA7_PA5", 1);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1_PA6_PA7_PA5", "PA6,PA7,PA5");
|
||||
DECL_ENUMERATION("spi_bus", "spi1_PB4_PB5_PB3", 2);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1_PB4_PB5_PB3", "PB4,PB5,PB3");
|
||||
DECL_ENUMERATION("spi_bus", "spi2_PA10_PA11_PF1", 3);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2_PA10_PA11_PF1", "PA10,PA11,PF1");
|
||||
DECL_ENUMERATION("spi_bus", "spi3_PB4_PB5_PB3", 4);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi3_PB4_PB5_PB3", "PB4,PB5,PB3");
|
||||
DECL_ENUMERATION("spi_bus", "spi3_PC11_PC12_PC10", 5);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi3_PC11_PC12_PC10", "PC11,PC12,PC10");
|
||||
#ifdef SPI4
|
||||
DECL_ENUMERATION("spi_bus", "spi4_PE13_PE14_PE12", 6);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi4_PE13_PE14_PE12", "PE13,PE14,PE12");
|
||||
#endif
|
||||
// Deprecated "spi1" style mappings
|
||||
DECL_ENUMERATION("spi_bus", "spi2", 0);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2", "PB14,PB15,PB13");
|
||||
DECL_ENUMERATION("spi_bus", "spi1", 1);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1", "PA6,PA7,PA5");
|
||||
DECL_ENUMERATION("spi_bus", "spi1a", 2);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1a", "PB4,PB5,PB3");
|
||||
DECL_ENUMERATION("spi_bus", "spi3", 4);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi3", "PB4,PB5,PB3");
|
||||
DECL_ENUMERATION("spi_bus", "spi3a", 5);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi3a", "PC11,PC12,PC10");
|
||||
#ifdef SPI4
|
||||
DECL_ENUMERATION("spi_bus", "spi4", 6);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi4", "PE13,PE14,PE12");
|
||||
#elif defined(GPIOI)
|
||||
DECL_ENUMERATION("spi_bus", "spi2b", 6);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2b", "PI2,PI3,PI1");
|
||||
DECL_ENUMERATION("spi_bus", "spi4", 6);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi4", "PE13,PE14,PE12");
|
||||
#endif
|
||||
#ifdef SPI6
|
||||
DECL_ENUMERATION("spi_bus", "spi6_PG12_PG14_PG13", 7);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi6_PG12_PG14_PG13", "PG12,PG14,PG13");
|
||||
#endif
|
||||
#endif
|
||||
#if CONFIG_MACH_STM32G0B1
|
||||
DECL_ENUMERATION("spi_bus", "spi2_PB2_PB11_PB10", 5);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2_PB2_PB11_PB10", "PB2,PB11,PB10");
|
||||
#endif
|
||||
#elif CONFIG_MACH_STM32L4
|
||||
DECL_ENUMERATION("spi_bus", "spi2_PB14_PB15_PB13", 0);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2_PB14_PB15_PB13", "PB14,PB15,PB13");
|
||||
DECL_ENUMERATION("spi_bus", "spi1_PA6_PA7_PA5", 1);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1_PA6_PA7_PA5", "PA6,PA7,PA5");
|
||||
DECL_ENUMERATION("spi_bus", "spi1_PB4_PB5_PB3", 2);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1_PB4_PB5_PB3", "PB4,PB5,PB3");
|
||||
DECL_ENUMERATION("spi_bus", "spi2_PC2_PC3_PB10", 3);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2_PC2_PC3_PB10", "PC2,PC3,PB10");
|
||||
// Deprecated "spi1" style mappings
|
||||
DECL_ENUMERATION("spi_bus", "spi2", 0);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2", "PB14,PB15,PB13");
|
||||
DECL_ENUMERATION("spi_bus", "spi1", 1);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1", "PA6,PA7,PA5");
|
||||
DECL_ENUMERATION("spi_bus", "spi1a", 2);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1a", "PB4,PB5,PB3");
|
||||
DECL_ENUMERATION("spi_bus", "spi2a", 3);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2a", "PC2,PC3,PB10");
|
||||
#endif
|
||||
|
||||
#define GPIO_FUNCTION_ALL(fn) GPIO_FUNCTION(fn), \
|
||||
GPIO_FUNCTION(fn), GPIO_FUNCTION(fn)
|
||||
|
||||
#if CONFIG_MACH_STM32F0 || CONFIG_MACH_STM32G0
|
||||
#define SPI_FUNCTION_ALL GPIO_FUNCTION_ALL(0)
|
||||
#else
|
||||
#define SPI_FUNCTION_ALL GPIO_FUNCTION_ALL(5)
|
||||
#endif
|
||||
#define SPI_FUNCTION(miso, mosi, sck) GPIO_FUNCTION(miso), \
|
||||
GPIO_FUNCTION(mosi), GPIO_FUNCTION(sck)
|
||||
|
||||
static const struct spi_info spi_bus[] = {
|
||||
{ SPI2, GPIO('B', 14), GPIO('B', 15), GPIO('B', 13), SPI_FUNCTION_ALL },
|
||||
{ SPI1, GPIO('A', 6), GPIO('A', 7), GPIO('A', 5), SPI_FUNCTION_ALL },
|
||||
{ SPI1, GPIO('B', 4), GPIO('B', 5), GPIO('B', 3), SPI_FUNCTION_ALL },
|
||||
#if CONFIG_MACH_STM32G4
|
||||
{ SPI2, GPIO('A', 10), GPIO('A', 11), GPIO('F', 1), SPI_FUNCTION_ALL },
|
||||
#else
|
||||
{ SPI2, GPIO('C', 2), GPIO('C', 3), GPIO('B', 10), SPI_FUNCTION_ALL },
|
||||
#endif
|
||||
#ifdef SPI3
|
||||
{ SPI3, GPIO('B', 4), GPIO('B', 5), GPIO('B', 3), GPIO_FUNCTION_ALL(6) },
|
||||
#if CONFIG_MACH_STM32F4 || CONFIG_MACH_STM32G4
|
||||
{ SPI3, GPIO('C', 11), GPIO('C', 12), GPIO('C', 10), GPIO_FUNCTION_ALL(6) },
|
||||
#ifdef SPI4
|
||||
{ SPI4, GPIO('E', 13), GPIO('E', 14), GPIO('E', 12), GPIO_FUNCTION_ALL(5) },
|
||||
#elif defined(GPIOI)
|
||||
{ SPI2, GPIO('I', 2), GPIO('I', 3), GPIO('I', 1), GPIO_FUNCTION_ALL(5) },
|
||||
#endif
|
||||
#ifdef SPI6
|
||||
{ SPI6, GPIO('G', 12), GPIO('G', 14), GPIO('G', 13), SPI_FUNCTION_ALL}
|
||||
#endif
|
||||
#if CONFIG_MACH_STM32F0
|
||||
{ SPI2, GPIO('B', 14), GPIO('B', 15), GPIO('B', 13), SPI_FUNCTION(0, 0, 0) },
|
||||
{ SPI1, GPIO('A', 6), GPIO('A', 7), GPIO('A', 5), SPI_FUNCTION(0, 0, 0) },
|
||||
{ SPI1, GPIO('B', 4), GPIO('B', 5), GPIO('B', 3), SPI_FUNCTION(0, 0, 0) },
|
||||
{ SPI2, GPIO('C', 2), GPIO('C', 3), GPIO('B', 10), SPI_FUNCTION(1, 1, 5) },
|
||||
#elif CONFIG_MACH_STM32F1
|
||||
{ SPI2, GPIO('B', 14), GPIO('B', 15), GPIO('B', 13), SPI_FUNCTION(0, 0, 0) },
|
||||
{ SPI1, GPIO('A', 6), GPIO('A', 7), GPIO('A', 5), SPI_FUNCTION(0, 0, 0) },
|
||||
{ SPI1, GPIO('B', 4), GPIO('B', 5), GPIO('B', 3), SPI_FUNCTION(5, 5, 5) },
|
||||
{ SPI3, GPIO('B', 4), GPIO('B', 5), GPIO('B', 3), SPI_FUNCTION(0, 0, 0) },
|
||||
#elif CONFIG_MACH_STM32F2
|
||||
{ SPI2, GPIO('B', 14), GPIO('B', 15), GPIO('B', 13), SPI_FUNCTION(5, 5, 5) },
|
||||
{ SPI1, GPIO('A', 6), GPIO('A', 7), GPIO('A', 5), SPI_FUNCTION(5, 5, 5) },
|
||||
{ SPI1, GPIO('B', 4), GPIO('B', 5), GPIO('B', 3), SPI_FUNCTION(5, 5, 5) },
|
||||
{ SPI2, GPIO('C', 2), GPIO('C', 3), GPIO('B', 10), SPI_FUNCTION(5, 5, 5) },
|
||||
{ SPI3, GPIO('B', 4), GPIO('B', 5), GPIO('B', 3), SPI_FUNCTION(6, 6, 6) },
|
||||
#elif CONFIG_MACH_STM32F4
|
||||
{ SPI2, GPIO('B', 14), GPIO('B', 15), GPIO('B', 13), SPI_FUNCTION(5, 5, 5) },
|
||||
{ SPI1, GPIO('A', 6), GPIO('A', 7), GPIO('A', 5), SPI_FUNCTION(5, 5, 5) },
|
||||
{ SPI1, GPIO('B', 4), GPIO('B', 5), GPIO('B', 3), SPI_FUNCTION(5, 5, 5) },
|
||||
{ SPI2, GPIO('C', 2), GPIO('C', 3), GPIO('B', 10), SPI_FUNCTION(5, 5, 5) },
|
||||
{ SPI3, GPIO('B', 4), GPIO('B', 5), GPIO('B', 3), SPI_FUNCTION(6, 6, 6) },
|
||||
{ SPI3, GPIO('C', 11), GPIO('C', 12), GPIO('C', 10), SPI_FUNCTION(6, 6, 6) },
|
||||
{ SPI2, GPIO('I', 2), GPIO('I', 3), GPIO('I', 1), SPI_FUNCTION(5, 5, 5) },
|
||||
#ifdef SPI4
|
||||
{ SPI4, GPIO('E', 13), GPIO('E', 14), GPIO('E', 12), SPI_FUNCTION(5, 5, 5) },
|
||||
#endif
|
||||
#if CONFIG_MACH_STM32G0B1
|
||||
{ SPI2, GPIO('B', 2), GPIO('B', 11), GPIO('B', 10),
|
||||
GPIO_FUNCTION(1), GPIO_FUNCTION(0), GPIO_FUNCTION(5) },
|
||||
#ifdef SPI6
|
||||
{ SPI6, GPIO('G', 12), GPIO('G', 14), GPIO('G', 13), SPI_FUNCTION(5, 5, 5)},
|
||||
#endif
|
||||
#elif CONFIG_MACH_STM32F7
|
||||
{ SPI2, GPIO('B', 14), GPIO('B', 15), GPIO('B', 13), SPI_FUNCTION(5, 5, 5) },
|
||||
{ SPI1, GPIO('A', 6), GPIO('A', 7), GPIO('A', 5), SPI_FUNCTION(5, 5, 5) },
|
||||
{ SPI1, GPIO('B', 4), GPIO('B', 5), GPIO('B', 3), SPI_FUNCTION(5, 5, 5) },
|
||||
{ SPI2, GPIO('C', 2), GPIO('C', 3), GPIO('B', 10), SPI_FUNCTION(5, 5, 5) },
|
||||
{ SPI3, GPIO('B', 4), GPIO('B', 5), GPIO('B', 3), SPI_FUNCTION(6, 6, 6) },
|
||||
#elif CONFIG_MACH_STM32G0
|
||||
{ SPI2, GPIO('B', 14), GPIO('B', 15), GPIO('B', 13), SPI_FUNCTION(0, 0, 0) },
|
||||
{ SPI1, GPIO('A', 6), GPIO('A', 7), GPIO('A', 5), SPI_FUNCTION(0, 0, 0) },
|
||||
{ SPI1, GPIO('B', 4), GPIO('B', 5), GPIO('B', 3), SPI_FUNCTION(0, 0, 0) },
|
||||
{ SPI2, GPIO('C', 2), GPIO('C', 3), GPIO('B', 10), SPI_FUNCTION(1, 1, 5) },
|
||||
{ SPI2, GPIO('B', 2), GPIO('B', 11), GPIO('B', 10), SPI_FUNCTION(1, 0, 5) },
|
||||
{ SPI2, GPIO('B', 6), GPIO('B', 7), GPIO('B', 8), SPI_FUNCTION(4, 1, 1) },
|
||||
#ifdef SPI3
|
||||
{ SPI3, GPIO('B', 4), GPIO('B', 5), GPIO('B', 3), SPI_FUNCTION(9, 9, 9) },
|
||||
{ SPI3, GPIO('C', 11), GPIO('C', 12), GPIO('C', 10), SPI_FUNCTION(4, 4, 4) },
|
||||
#endif
|
||||
#elif CONFIG_MACH_STM32G4
|
||||
{ SPI2, GPIO('B', 14), GPIO('B', 15), GPIO('B', 13), SPI_FUNCTION(5, 5, 5) },
|
||||
{ SPI1, GPIO('A', 6), GPIO('A', 7), GPIO('A', 5), SPI_FUNCTION(5, 5, 5) },
|
||||
{ SPI1, GPIO('B', 4), GPIO('B', 5), GPIO('B', 3), SPI_FUNCTION(5, 5, 5) },
|
||||
{ SPI2, GPIO('A', 10), GPIO('A', 11), GPIO('F', 1), SPI_FUNCTION(5, 5, 5) },
|
||||
{ SPI3, GPIO('B', 4), GPIO('B', 5), GPIO('B', 3), SPI_FUNCTION(6, 6, 6) },
|
||||
{ SPI3, GPIO('C', 11), GPIO('C', 12), GPIO('C', 10), SPI_FUNCTION(6, 6, 6) },
|
||||
#ifdef SPI4
|
||||
{ SPI4, GPIO('E', 13), GPIO('E', 14), GPIO('E', 12), SPI_FUNCTION(5, 5, 5) },
|
||||
#endif
|
||||
#elif CONFIG_MACH_STM32L4
|
||||
{ SPI2, GPIO('B', 14), GPIO('B', 15), GPIO('B', 13), SPI_FUNCTION(5, 5, 5) },
|
||||
{ SPI1, GPIO('A', 6), GPIO('A', 7), GPIO('A', 5), SPI_FUNCTION(5, 5, 5) },
|
||||
{ SPI1, GPIO('B', 4), GPIO('B', 5), GPIO('B', 3), SPI_FUNCTION(5, 5, 5) },
|
||||
{ SPI2, GPIO('C', 2), GPIO('C', 3), GPIO('B', 10), SPI_FUNCTION(5, 5, 5) },
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ stm32f1_alternative_remap(uint32_t mapr_mask, uint32_t mapr_value)
|
|||
void
|
||||
gpio_peripheral(uint32_t gpio, uint32_t mode, int pullup)
|
||||
{
|
||||
GPIO_TypeDef *regs = digital_regs[GPIO2PORT(gpio)];
|
||||
GPIO_TypeDef *regs = gpio_pin_to_regs(gpio);
|
||||
|
||||
// Enable GPIO clock
|
||||
gpio_clock_enable(regs);
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ DECL_ENUMERATION_RANGE("pin", "PH0", GPIO('H', 0), 16);
|
|||
DECL_ENUMERATION_RANGE("pin", "PI0", GPIO('I', 0), 16);
|
||||
#endif
|
||||
|
||||
GPIO_TypeDef * const digital_regs[] = {
|
||||
static GPIO_TypeDef * const digital_regs[] = {
|
||||
['A' - 'A'] = GPIOA, GPIOB, GPIOC,
|
||||
#ifdef GPIOD
|
||||
['D' - 'A'] = GPIOD,
|
||||
|
|
@ -66,12 +66,14 @@ regs_to_pin(GPIO_TypeDef *regs, uint32_t bit)
|
|||
return 0;
|
||||
}
|
||||
|
||||
// Verify that a gpio is a valid pin
|
||||
static int
|
||||
gpio_valid(uint32_t pin)
|
||||
// Verify that a gpio is a valid pin and return its hardware register
|
||||
GPIO_TypeDef *
|
||||
gpio_pin_to_regs(uint32_t pin)
|
||||
{
|
||||
uint32_t port = GPIO2PORT(pin);
|
||||
return port < ARRAY_SIZE(digital_regs) && digital_regs[port];
|
||||
if (port >= ARRAY_SIZE(digital_regs) || !digital_regs[port])
|
||||
shutdown("Not a valid pin");
|
||||
return digital_regs[port];
|
||||
}
|
||||
|
||||
// The stm32h7 has very slow read access to the gpio registers. Cache
|
||||
|
|
@ -83,10 +85,7 @@ static struct odr_cache {
|
|||
struct gpio_out
|
||||
gpio_out_setup(uint32_t pin, uint32_t val)
|
||||
{
|
||||
if (!gpio_valid(pin))
|
||||
shutdown("Not an output pin");
|
||||
uint32_t port = GPIO2PORT(pin);
|
||||
GPIO_TypeDef *regs = digital_regs[port];
|
||||
GPIO_TypeDef *regs = gpio_pin_to_regs(pin);
|
||||
gpio_clock_enable(regs);
|
||||
|
||||
struct gpio_out g = { .regs=regs, .oc=&ODR_CACHE[pin] };
|
||||
|
|
@ -141,9 +140,7 @@ gpio_out_write(struct gpio_out g, uint32_t val)
|
|||
struct gpio_in
|
||||
gpio_in_setup(uint32_t pin, int32_t pull_up)
|
||||
{
|
||||
if (!gpio_valid(pin))
|
||||
shutdown("Not a valid input pin");
|
||||
GPIO_TypeDef *regs = digital_regs[GPIO2PORT(pin)];
|
||||
GPIO_TypeDef *regs = gpio_pin_to_regs(pin);
|
||||
struct gpio_in g = { .regs=regs, .bit=GPIO2BIT(pin) };
|
||||
gpio_in_reset(g, pull_up);
|
||||
return g;
|
||||
|
|
|
|||
|
|
@ -16,68 +16,38 @@ struct spi_info {
|
|||
uint8_t miso_pin, mosi_pin, sck_pin, function;
|
||||
};
|
||||
|
||||
DECL_ENUMERATION("spi_bus", "spi2", __COUNTER__);
|
||||
DECL_ENUMERATION("spi_bus", "spi2", 0);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2", "PB14,PB15,PB13");
|
||||
|
||||
DECL_ENUMERATION("spi_bus", "spi1", __COUNTER__);
|
||||
DECL_ENUMERATION("spi_bus", "spi1", 1);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1", "PA6,PA7,PA5");
|
||||
DECL_ENUMERATION("spi_bus", "spi1a", __COUNTER__);
|
||||
DECL_ENUMERATION("spi_bus", "spi1a", 2);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi1a", "PB4,PB5,PB3");
|
||||
|
||||
#if !CONFIG_MACH_STM32F1
|
||||
DECL_ENUMERATION("spi_bus", "spi2a", __COUNTER__);
|
||||
DECL_ENUMERATION("spi_bus", "spi2a", 3);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2a", "PC2,PC3,PB10");
|
||||
#endif
|
||||
|
||||
#ifdef SPI3
|
||||
DECL_ENUMERATION("spi_bus", "spi3a", __COUNTER__);
|
||||
DECL_ENUMERATION("spi_bus", "spi3a", 4);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi3a", "PC11,PC12,PC10");
|
||||
#endif
|
||||
|
||||
#ifdef SPI4
|
||||
DECL_ENUMERATION("spi_bus", "spi4", __COUNTER__);
|
||||
DECL_ENUMERATION("spi_bus", "spi4", 5);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi4", "PE13,PE14,PE12");
|
||||
#endif
|
||||
|
||||
#ifdef GPIOI
|
||||
DECL_ENUMERATION("spi_bus", "spi2b", __COUNTER__);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2b", "PI2,PI3,PI1");
|
||||
#endif
|
||||
|
||||
#ifdef SPI5
|
||||
DECL_ENUMERATION("spi_bus", "spi5", __COUNTER__);
|
||||
DECL_ENUMERATION("spi_bus", "spi5", 6);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi5", "PF8,PF9,PF7");
|
||||
DECL_ENUMERATION("spi_bus", "spi5a", __COUNTER__);
|
||||
DECL_ENUMERATION("spi_bus", "spi5a", 7);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi5a", "PH7,PF11,PH6");
|
||||
#endif
|
||||
|
||||
#ifdef SPI6
|
||||
DECL_ENUMERATION("spi_bus", "spi6", __COUNTER__);
|
||||
DECL_ENUMERATION("spi_bus", "spi6", 8);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi6", "PG12,PG14,PG13");
|
||||
#endif
|
||||
|
||||
DECL_ENUMERATION("spi_bus", "spi2b", 9);
|
||||
DECL_CONSTANT_STR("BUS_PINS_spi2b", "PI2,PI3,PI1");
|
||||
|
||||
static const struct spi_info spi_bus[] = {
|
||||
{ SPI2, GPIO('B', 14), GPIO('B', 15), GPIO('B', 13), GPIO_FUNCTION(5) },
|
||||
{ SPI1, GPIO('A', 6), GPIO('A', 7), GPIO('A', 5), GPIO_FUNCTION(5) },
|
||||
{ SPI1, GPIO('B', 4), GPIO('B', 5), GPIO('B', 3), GPIO_FUNCTION(5) },
|
||||
#if !CONFIG_MACH_STM32F1
|
||||
{ SPI2, GPIO('C', 2), GPIO('C', 3), GPIO('B', 10), GPIO_FUNCTION(5) },
|
||||
#endif
|
||||
#ifdef SPI3
|
||||
{ SPI3, GPIO('C', 11), GPIO('C', 12), GPIO('C', 10), GPIO_FUNCTION(6) },
|
||||
#endif
|
||||
#ifdef SPI4
|
||||
{ SPI4, GPIO('E', 13), GPIO('E', 14), GPIO('E', 12), GPIO_FUNCTION(5) },
|
||||
#endif
|
||||
{ SPI2, GPIO('I', 2), GPIO('I', 3), GPIO('I', 1), GPIO_FUNCTION(5) },
|
||||
#ifdef SPI5
|
||||
{ SPI5, GPIO('F', 8), GPIO('F', 9), GPIO('F', 7), GPIO_FUNCTION(5) },
|
||||
{ SPI5, GPIO('H', 7), GPIO('F', 11), GPIO('H', 6), GPIO_FUNCTION(5) },
|
||||
#endif
|
||||
#ifdef SPI6
|
||||
{ SPI6, GPIO('G', 12), GPIO('G', 14), GPIO('G', 13), GPIO_FUNCTION(5)},
|
||||
#endif
|
||||
{ SPI2, GPIO('I', 2), GPIO('I', 3), GPIO('I', 1), GPIO_FUNCTION(5) },
|
||||
};
|
||||
|
||||
struct spi_config
|
||||
|
|
|
|||
|
|
@ -66,3 +66,19 @@ max_velocity: 300
|
|||
max_accel: 3000
|
||||
max_z_velocity: 5
|
||||
max_z_accel: 100
|
||||
|
||||
[filament_switch_sensor runout_switch]
|
||||
switch_pin = PD4
|
||||
|
||||
[filament_motion_sensor runout_encoder]
|
||||
switch_pin = PD5
|
||||
detection_length = 4
|
||||
extruder = extruder
|
||||
|
||||
[filament_switch_sensor runout_switch1]
|
||||
switch_pin = PL4
|
||||
|
||||
[filament_motion_sensor runout_encoder1]
|
||||
switch_pin = PL6
|
||||
detection_length = 4
|
||||
extruder = extruder_stepper my_extra_stepper
|
||||
|
|
|
|||
|
|
@ -70,8 +70,10 @@ max_z_accel: 100
|
|||
[input_shaper]
|
||||
shaper_type_x: mzv
|
||||
shaper_freq_x: 33.2
|
||||
shaper_type_x: ei
|
||||
shaper_freq_x: 39.3
|
||||
shaper_type_y: ei
|
||||
shaper_freq_y: 39.3
|
||||
damping_ratio_y: 0.4
|
||||
shaper_freq_z: 42
|
||||
|
||||
[adxl345]
|
||||
cs_pin: PK7
|
||||
|
|
@ -83,3 +85,4 @@ axes_map: -x,-y,z
|
|||
probe_points: 20,20,20
|
||||
accel_chip_x: adxl345
|
||||
accel_chip_y: mpu9250 my_mpu
|
||||
accel_chip_z: adxl345
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ DICTIONARY atmega2560.dict
|
|||
|
||||
# Simple command test
|
||||
SET_INPUT_SHAPER SHAPER_FREQ_X=22.2 DAMPING_RATIO_X=.1 SHAPER_TYPE_X=zv
|
||||
SET_INPUT_SHAPER SHAPER_FREQ_Y=33.3 DAMPING_RATIO_X=.11 SHAPER_TYPE_X=2hump_ei
|
||||
SET_INPUT_SHAPER SHAPER_FREQ_Y=33.3 DAMPING_RATIO_Y=.11 SHAPER_TYPE_Y=2hump_ei
|
||||
|
|
|
|||
|
|
@ -33,6 +33,10 @@ G28
|
|||
G1 X20 Y20 Z10
|
||||
G1 A10 X22
|
||||
|
||||
# Verify position query commands work with extra axis
|
||||
GET_POSITION
|
||||
M114
|
||||
|
||||
# Test unregistering
|
||||
MANUAL_STEPPER STEPPER=basic_stepper GCODE_AXIS=
|
||||
G1 X15
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue