229 Commits

Author SHA1 Message Date
Kevin O'Connor
3aadda6fb3 mcu: Disable waiting in send_wait_ack() if in debugging mode
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-22 16:53:34 -04:00
Timofey Titovets
159b71e51e bus: drop obsolete i2c_write_wait_ack
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-08-22 16:44:54 -04:00
Timofey Titovets
718be7c6a3 sht3x: drop obsolete i2c_write_wait_ack
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-08-22 16:44:54 -04:00
Timofey Titovets
eb7bdf18ad bme280: drop obsolete i2c_write_wait_ack
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-08-22 16:44:54 -04:00
Timofey Titovets
fe44dd8baa bus: make i2c_write syncronous
When we introduce the host-side status check,
it will be synchronous.
There would be no sense in having an asynchronous call.
Preliminary migrate callers to synchronous call.

Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-08-22 16:44:54 -04:00
Kevin O'Connor
ae010215e7 chelper: Build library first in temporary file and then rename
Try to avoid cases where an incomplete library build causes confusing
future failures.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-22 16:34:45 -04:00
Timofey Titovets
eec81683eb bus: move early i2c writes to the connect phase
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-08-22 14:57:28 -04:00
Timofey Titovets
1965298ab0 sx1509: init pwm pin on connect
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-08-22 14:57:28 -04:00
Timofey Titovets
9a1ac45d19 sx1509: migrate i2c write to connect phase
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-08-22 14:57:28 -04:00
minicx
b817848567 stm32: enable 64KiB bootloader for n32g45x, clarify Makefile output
- Allow selection of 64KiB bootloader offset for MACH_N32G45x in Kconfig

Signed-off-by: Lev Voronov <minicx@disroot.org>
Co-authored-by: Alexander Simonov <me@darksimpson.com>
2025-08-21 15:24:46 -04:00
minicx
3a11645afe stm32: Fix N32G45x ADC pin mapping (#7016)
Fixes PA0 (GPIO 0) incorrectly mapping to ADC1_IN0 due to
collision with placeholder zeros.

Signed-off-by: Lev Voronov <minicx@disroot.org>
Co-authored-by: Alexander Simonov <me@darksimpson.com>
2025-08-21 11:41:07 -04:00
C0co
7ed7791723 spi_flash: Update board_defs.py (#7006)
Added X-Smart3, X-Plus3 and X-Max3 mainboards

Signed-off-by: Phil Groenewold <philgroenewold@gmx.de>
2025-08-21 11:32:33 -04:00
Hendrik Poernama
3b68769ea5 tmc2240: Add OTW_OV_VTH to FieldFormatters (#6987)
This register is readable and contains the overvoltage and overtemp
threshold settings.

Signed-off-by: Hendrik Poernama <poernahi@gmail.com>
2025-08-21 09:33:31 -04:00
Kevin O'Connor
2ddfa32dd8 heaters: Reduce next_pwm_time window
Commit 0f94f6c8 decreased the MAX_HEAT_TIME from 5 seconds to 3
seconds.  However, that also decreased the amount of tolerance for
lost temperature updates from 1.25 seconds to 0.75 seconds.  With the
default temperature update every 300ms, only 2 consecutive missing
temperature updates could lead to a fault.

Tweak the internal "next_pwm_time" setting so that it is more tolerant
of two consecutive lost temperature updates.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-21 09:25:42 -04:00
Kevin O'Connor
371647109f test: Add a long move test to manual_stepper.test
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-20 16:47:32 -04:00
Kevin O'Connor
91b5e8e942 manual_stepper: Internally track commanded_pos
Commit 9399e738 changed the manual_stepper class to no longer
explicitly flush all steps after each move.  As a result, calls to
self.rail.get_commanded_position() may no longer reflect the last
requested position.  This discrepancy could result in "internal
stepcompress" errors.

Change the manual_stepper code to internally track the last requested
position and use that when scheduling moves.  This allows the
manual_stepper code to utilize the standard "lazy" step flushing
mechanism.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-20 16:41:41 -04:00
BIGTREETECH
d34d3b05b8 stm32: Add i2c2_PA7_PA6 and i2c3_PA7_PA6 for stm32g0 (#7007)
Signed-off-by: Alan.Ma from BigTreeTech <tech@biqu3d.com>
2025-08-15 13:43:43 -04:00
Kevin O'Connor
78462cff4c docs: Remove "relative_reference_index" documentation from Bed_Mesh.md
The "relative_reference_index" was deprecated on 20230619 and removed
on 20240215.  So, remove the last references from the documentation.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-15 12:57:19 -04:00
Timofey Titovets
edbfc6f856 resonance_tester: replace missing M204 call
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-08-14 15:10:13 -04:00
Kevin O'Connor
d6d8587289 motion_queuing: Remove clean_motion_queues()
Merge the clean_motion_queues() code into the existing
flush_motion_queues() code.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:35 -04:00
Kevin O'Connor
2919f37343 stepcompress: Store a reference to 'struct stepper_kinematics'
Support storing a reference to 'struct stepper_kinematics' in 'struct
stepcompress' and support globally generating steps via the
steppersync mechanism.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:35 -04:00
Kevin O'Connor
dd4cc8eb4c itersolve: Do not store a reference to 'struct stepcompress'
Pass in the 'struct stepcompress' reference to each call of
itersolve_generate_steps().

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:35 -04:00
Kevin O'Connor
c520bf981d steppersync: Split steppersync code from stepcompress.c to new file
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:35 -04:00
Kevin O'Connor
c454e88d9a stepper: Implement active callbacks via motion_queuing.register_flush_callback()
Use the existing register_flush_callback() system to implement motor
activity checking.  This simplifies the generate_steps() code.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:35 -04:00
Kevin O'Connor
b5e573957c motion_queuing: Move clear_history_time from toolhead to motion_queuing
Implement the 30 second clear_history_time offset checking directly in
the motion_queuing module.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:35 -04:00
Kevin O'Connor
6d59279438 statistics: Avoid adding extra blank spaces on empty stats reports
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:35 -04:00
Kevin O'Connor
1d569a6631 motion_queuing: Remove flush_steppersync()
Move code from flush_steppersync() to existing flush_motion_queues()
and clean_motion_queues() functions.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:35 -04:00
Kevin O'Connor
7b25d1c06f stepcompress: Export steppersync_history_expire()
Don't implement history expiration from the main steppersync_flush()
code.  Instead, have callers directly invoke
steppersync_history_expire().

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:35 -04:00
Kevin O'Connor
864c78f24a motion_queueing: Add flush_steppersync()
Move the mcu.flush_moves() code to motion_queuing.flush_steppersync().

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:35 -04:00
Kevin O'Connor
c09ca4cf5a motion_queuing: Add register_flush_callback()
Move register_flush_callback() from mcu.py code to motion_queuing
module.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:34 -04:00
Kevin O'Connor
6f685e9e01 motion_queuing: Add allocate_stepcompress() call
Allocate the low-level C stepcompress object in the motion_queuing
module.  This simplifies the mcu.py code as it no longer needs to
track the stepqueues for the steppersync object.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:34 -04:00
Kevin O'Connor
128226fe8a motion_queuing: Add allocate_steppersync() call
Allocate the low-level C steppersync object from the motion_queuing
module.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:34 -04:00
Kevin O'Connor
5cbe7d83e8 motion_queuing: Track all trapqs and globally flush all trapqs
Add an allocate_trapq() helper function to facilitate the creation of
a low-level C trapq object.  Track all trapq objects and clear history
on them globally when the main motion queues are flushed.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:34 -04:00
Kevin O'Connor
9399e738bc motion_queuing: Add new module to help with motion queues and flushing
Create a new module to assist with host management of motion queues.
Register all MCU_stepper objects with this module and use the module
for step generation.

All steppers will now automatically generate steps whenever
toolhead._advance_flush_time() is invoked.  It is no longer necessary
for callers to individually call stepper.generate_steps().

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:34 -04:00
Kevin O'Connor
126275d1f4 toolhead: Default note_mcu_movequeue_activity() to set step_gen_time
Change note_mcu_movequeue_activity() to default to setting the
step_gen_time (instead of the previous default to not set it).

Most users of the mcu "move queue" will be for stepper activity.
There is also little harm in incrementing the tracking of the last
possible step generation time, but accidentally generating a step
without incrementing the tracking can lead to very hard to debug
failures.

The two cases (output_pin.py and pwm_tool.py) where
note_mcu_movequeue_activity() is called and definitely not related to
step generation can explicitly pass 'is_step_gen=False'.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:34 -04:00
Kevin O'Connor
f8da8099d5 toolhead: Move g-code command handlers to new ToolHeadCommandHelper() class
Move the g-code command handlers to a new class.  This reduces the
size of the main Toolhead() class.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:34 -04:00
Kevin O'Connor
bcd4510958 toolhead: Move extra module loading out of core Toolhead() class
Load these extra modules from add_printer_objects() instead.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:34 -04:00
Kevin O'Connor
3ef760c18f toolhead: Remove support for max_accel_to_decel
This support was deprecated on 20240313.  Remove the remaining
compatibility code.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:14:37 -04:00
Ben Lye
cfc58d3ce7 spi_flash: Add qidi-x7 to board_defs.py (#6979)
Added board definition for stm32f401xc-based Qidi X-7 board used in Qidi Q1 Pro and Plus4.

Signed-off-by: Ben Lye <ben@lye.co.nz>
2025-08-11 17:49:35 -04:00
Dmitry Butyugin
5eb07966b5 idex_modes: Fixed dual_carriage axis range calculation after homing
Signed-off-by: Dmitry Butyugin <dmbutyugin@google.com>
2025-08-03 14:20:10 -04:00
Kevin O'Connor
e1ba7c17ce Revert "queuelogger: set thread name"
This reverts commit 73c6674306.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-01 13:08:03 -04:00
Kevin O'Connor
0df40b43e8 serialqueue: Be sure sq->name is null terminated
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-01 12:46:32 -04:00
Timofey Titovets
17ce45d212 serialqueue: name the threads per mcu
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-08-01 12:42:53 -04:00
Timofey Titovets
39d01158ba serialhdl: name the threads per mcu
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-08-01 12:42:53 -04:00
Timofey Titovets
73c6674306 queuelogger: set thread name
Python 2.7 does not allow loading the cffi lib
inside the thread, but function calls are allowed

Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-08-01 12:42:53 -04:00
Timofey Titovets
c78dd6a00a pyhelper: define set_thread_name() helper
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-08-01 12:42:53 -04:00
Contomo
d5c031bc13 idle_timeout: Add status field for current idle timeout (#6982)
Signed-off-by: Eric Billmeyer <eric.billmeyer@freenet.de>
2025-08-01 12:37:47 -04:00
Kevin O'Connor
2cbb895978 tmc2240: Add OTW_OV_VTH to list of ReadRegisters
Reported by @poernahi.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-01 12:33:50 -04:00
Burrito
e1176e4dfb spi_flash: Add ZNP Robin Nano v2.2 to board defs (#6986)
Adds support for the ZNP Robin Nano DW v2.2 board, used in the Neptune 3
Pro/Plus/Max.

Signed-off-by: Zyjay Cruz <burrito@burrito.software>
2025-07-28 18:25:48 -04:00
Thijs Triemstra
6773ab074b docs: Fix typos in config and docs (#6991)
* fix typos in configs

* fix typos in docs

Signed-off-by: Thijs Triemstra <info@collab.nl>
2025-07-27 12:12:48 -04:00
Sasquatch
4a567c8d10 spi_flash: FATFS fix missing long filenames support needed by flash-sdcard.sh (#6981)
enable long file support, needed for boards using swspi and long filenames for firmware like mks robin 1.1/1.2

added MKS robin nano 1.2 board with description what and why

Signed-off-by: Leszek Zajac <zajc3w@gmail.com>
2025-07-27 12:09:11 -04:00
Thijs Triemstra
60879fd298 klippy: fix typos in python code (#6989)
Signed-off-by: Thijs Triemstra <info@collab.nl>
2025-07-25 12:31:19 -04:00
Paul Arthur
ef4c76fe94 safe_z_home: correct error call
Signed-off-by: Paul Arthur <paul.arthur@flowerysong.com>
2025-07-22 14:17:43 -04:00
Kevin O'Connor
116b304541 avr: Switch to input state prior to enabling pullup in gpio_in_reset()
If switching a pin from output low to input with pullup, there is an
intermediate state of either driven high or high impedance without a
pullup.  Similarly, when switching from output high to input without a
pullup, there is an intermediate state of either driven low or high
impedence with a pullup.  In both cases it is preferable for the
latter transition.

Also, calculate the final setting prior to making any changes to
reduce the time in that intermediate state.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-07-22 14:11:17 -04:00
Kevin O'Connor
3219712c17 i2c_software: Place wires in high impedance state after setup
Don't leave the wires in a high output state during setup - leave them
in a high-impedance with pullup state.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-07-22 14:04:50 -04:00
Kevin O'Connor
b761b8c654 i2c_software: Implement regular timing even on AVR chips
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-07-22 14:04:50 -04:00
Kevin O'Connor
a209d4db5b mcp4018: Remove support for manual i2c - use standard mcu software i2c instead
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-07-22 14:04:50 -04:00
Kevin O'Connor
354b1e666b pca9632: Remove custom software i2c - use normal mcu software i2c instead
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-07-22 14:04:50 -04:00
Kevin O'Connor
4691243179 heaters: Increase time before clearing the temperature of an inactive heater
The get_temp() code will stop reporting the last temperature of the
heater if there hasn't been any recent temperature updates.  However,
on a full mcu communication loss this can cause the verify_heater code
to report a heating error prior to the mcu code reporting the
communication failure.  Increase the heater timeout from 5 to 7
seconds to make it more likely the mcu failure is reported first.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-07-19 11:24:59 -04:00
Timofey Titovets
4e4a5c6336 stm32: make i2c distinguish I2C NACKs
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-07-17 19:36:01 -04:00
Timofey Titovets
9323a5dfe2 readlog.py: add support for stallguard data
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-07-12 16:17:22 -04:00
Timofey Titovets
b724b3a348 data_logger.py: add tmc/stallguard_dump endpoint
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-07-12 16:17:22 -04:00
Timofey Titovets
317f8c94c8 tmc.py: add track of stallguard
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-07-12 16:17:22 -04:00
Timofey Titovets
9c0d0f6a72 tmc: add enriched UART read
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-07-12 16:17:22 -04:00
Timofey Titovets
5923a2e3a1 tmc: add spi status decode
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-07-12 16:17:22 -04:00
Timofey Titovets
8d67e1a4e9 tmc2660: add enriched SPI read
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-07-12 16:17:22 -04:00
Timofey Titovets
33bd67f9b7 tmc: add enriched SPI read
Currently TMC spi just drop the data that could be useful.
Export that data.

Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-07-12 16:17:22 -04:00
Findlay Feng
993cec0891 sos_filter: fix overflows_int32 (#6976)
Modify the inline function overflows_int32 to static inline
Inline functions cannot be debugged in -O mode
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=49653

Signed-off-by: Findlay Feng <i@fengch.me>
2025-07-11 11:08:35 -04:00
Kevin O'Connor
697c6e8d28 mcu: Avoid altering self.TIMEOUT_TIME in RetryAsyncCommand
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-07-11 10:56:03 -04:00
Timofey Titovets
2585accfeb sht3x: reads should be retried with at least 0.5s pause
SHT3x would return a read NACK on host retries.
When the MCU receives the I2C CMD, it reads out data.
SHT3x clears the data buffer.
The MCU fails to deliver a response to the host.
The host retries, the device returns NACK,
then the MCU goes into the shutdown state.

Make sure there is at least 0.5s between retries.

Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-07-11 10:46:57 -04:00
Timofey Titovets
37ddab223f mcu: allow disable send retries
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-07-11 10:46:57 -04:00
Timofey Titovets
119d007058 stm32: f0 do not send empty write on read
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-07-09 15:45:52 -04:00
Timofey Titovets
1931b11001 stm32: f0 make i2c distinguish I2C NACKs
Some devices can return a read NACK on host retries.
When the MCU receives the I2C CMD, reads out data,
but fails to deliver a response to the host.
The host retries, the device returns NACK,
and the MCU goes into the shutdown state.

Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-07-09 15:45:52 -04:00
Kevin O'Connor
c01e6eee1d ads1x1x: Rename local 'config' variable to pcfg
Avoid using the name "config" as a local register storage variable as
it can be confused with the common "config" configfile reference.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-07-08 18:55:25 -04:00
Kevin O'Connor
42fbf8256f ads1x1x: Revert incorrect removal of assignment to self.config
Commit d120a313b incorrectly removed an assignment to self.config - in
this instance the reference was to a local variable not related to the
global configfile storage.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-07-08 18:49:12 -04:00
Kevin O'Connor
9346ad1914 load_cell_probe: Fix warnings on avr builds
On AVR, integers are 16bit, so be sure to promote math to 32bit where
needed.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-06-18 11:09:58 -04:00
jimmyjon711
0e52f03b5b stm32: Adding more hardware pwm capable pins for STM32Hx series chips (#6965)
Signed-off-by: Jim Madill <jcmadill1@gmail.com>
2025-06-18 11:05:17 -04:00
Kevin O'Connor
f54b7b9376 sos_filter: Fix validate_section_index() check
A section_idx equal to max_sections is also invalid.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-06-11 17:59:02 -04:00
Kevin O'Connor
5666b88c69 ar100: Convert to or1k-elf toolchain
The more.musl.cc site is blocking downloads from all github actions,
which makes it difficult to use that site for the ar100 cross build
toolchain.  Convert to the openrisc or1k-elf toolchain as a
replacement.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-06-08 14:15:50 -04:00
Kevin O'Connor
889be5b275 docs: Fix typo in Benchmarks.md
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-06-08 12:23:02 -04:00
Timofey Titovets
607d0b4237 input_shaper: fix printer obj reference
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-06-06 15:43:29 -04:00
Kevin O'Connor
d120a313b7 docs: Note 'config' object shouldn't be accessed after initial load
Update Code_Overview.md to note that the config object should not be
stored after the "config loading phase".

Remove a few inadvertent cases where a 'config' object was stored
in module member variables.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-06-04 13:46:28 -04:00
Dmitry Butyugin
4d4b9684a5 input_shaper: Track kinematics updates by dual_carriage
Signed-off-by: Dmitry Butyugin <dmbutyugin@google.com>
2025-06-04 13:40:58 -04:00
Kevin O'Connor
14cbb8dd2d rp2040: Prefer larger postdiv1 on rp2040 chips
The rp2040 uses a pll vco divider of 6.  Prefer setting postdiv1=6 and
postdiv2=1 (instead of the previous postdiv1=3 and postdiv2=2).

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-06-02 14:10:42 -04:00
Kevin O'Connor
aa3388cc59 klippy-requirements: Update setuptools revision to 78.1.1
A security vulnerability was found in setuptools - increase package
dependency to fixed version.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-06-02 13:31:17 -04:00
Timofey Titovets
d6902240dd htu21: fix crash on unknown dev id
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-06-02 13:17:41 -04:00
Kevin O'Connor
105ce35e1b stm32: Add comments on PLL frequency requirements to clock setup code
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-06-02 13:15:53 -04:00
Kevin O'Connor
c0ca4c5cc7 docs: Update benchmarks with stm32g431 chip
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-06-02 13:15:48 -04:00
Kevin O'Connor
cfa48fe39f stm32: Run stm32g431 at 170Mhz
The chip supports 170Mhz, so no need to run at 150Mhz.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-31 15:20:12 -04:00
Ingo Donasch
2dd73d0431 print_stats: Fix for filament statistics bug in print_stats.py for toolchangers (#6946)
added extruder:activate_extruder event hook to print_stats.py to update self.last_epos

Signed-off-by: Ingo Donasch <ingo@donasch.net>
2025-05-31 13:06:44 -04:00
Kevin O'Connor
d25602e88d docs: Update CAN bus command rate benchmarks
Add a benchmark for the rp2350 device when running via CAN bus.
Remove the old stm32f042 CAN bus benchmark as that test predates a
number of importnat CAN bus code changes and is likely no longer
accurate.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-31 12:24:40 -04:00
Kevin O'Connor
1f3b4cc749 stm32: Fix spi overflow issue on stm32h7
Completely filling the spi transmit fifo could lead to a situation
where the rx fifo overflows.  Make sure not to write past the rx fifo
size.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-30 20:36:09 -04:00
Kevin O'Connor
8e58f8fb39 rp2040: Fix spi overflow issue
Completely filling the spi transmit fifo could lead to a situation
where the rx fifo overflows.  Make sure not to write past the rx fifo
size.

Also, be sure to wait for the transmission to fully complete before
exiting spi_transfer().

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-30 16:34:49 -04:00
Timofey Titovets
f4130aa948 rp2040: spi - enable fifo
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-05-30 15:18:07 -04:00
Kevin O'Connor
de182b1d14 stm32: Support using CANBUS on PB5/PB6 on stm32h7 chips
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-30 15:15:13 -04:00
Kevin O'Connor
f5956b5395 stm32: Simplify Kconfig HAVE_STM32_CANBUS checks
Avoid unnecessary (HAVE_STM32_CANBUS && MACH_STM32xx) checks in
Kconfig.  The HAVE_STM32_CANBUS is a helper symbol for all the chips
that support canbus, there's no need to mix it with a check for a chip
that is already known to have canbus.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-30 15:15:13 -04:00
Kevin O'Connor
8d7e487149 sos_filter: Improve error checking on section_idx
Validate host provided index prior to accessing memory using that
index.

Also, consistently use a uint8_t for max_sections (to account for
integer overflow issues).

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-29 19:30:45 -04:00
Kevin O'Connor
eb43b20e3b load_cell_probe: Avoid peeking directly at config.section member variable
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-29 19:17:45 -04:00
Gareth Farrington
388fe1b23f docs: Load Cell Probe Documentation
Add documentation updates for Homing & Probing with load cell probe

Signed-off-by: Gareth Farrington <gareth@waves.ky>
2025-05-29 19:11:05 -04:00
Gareth Farrington
f6d878a898 filter_workbench: Add Filter Workbench
Add a filter workbench Jupiter notebook to help printer developers tune filters based on probing data

Signed-off-by: Gareth Farrington <gareth@waves.ky>
2025-05-29 19:10:51 -04:00
Gareth Farrington
b3e894f241 load_cell_probe: Create LoadCellProbe
Initial setup of Load Cell Probing. This implementation supports triggering from the Load Cell Probe on the MCU. It also supports, optiopnal, filtering of the force signal by sos filter to eliminate drift caused by bowden tubes or other mechanical causes.

Signed-off-by: Gareth Farrington <gareth@waves.ky>
2025-05-29 19:10:21 -04:00
Gareth Farrington
3dbac01e1d probe: Create ProbeVirtualEndstopDeprecation
As probes stop supporting `probe:z_virtual_endstop` this class will give users a polite and specific configuration error.

Signed-off-by: Gareth Farrington <gareth@waves.ky>
2025-05-29 19:08:32 -04:00
Gareth Farrington
69507a0354 sensor_hx71x: Update Sensors to report to load_cell_probe
Signed-off-by: Gareth Farrington <gareth@waves.ky>
2025-05-29 19:07:49 -04:00
Gareth Farrington
42c9031c81 load_cell_probe: Create load_cell_probe MCU object
Implement MCU features that enable using an adc to stop an axis

Signed-off-by: Gareth Farrington <gareth@waves.ky>
2025-05-29 19:06:48 -04:00
Gareth Farrington
cb0c38f7d8 sos_filter: Second Order Sections MCU Filter
This is an implementation of the SOS fliltering algorithm that runs on the MCU.

The filter opperates on data in fixed point format to avoid use of the FPU as klipper does not support FPU usage.

This host object handles duties of initalizing and resetting the filter so client dont have to declare their own commands for these opperations. Clients can select how many integer bits they want to use for both the filter coefficients and the filters output value. An arbitrary number of filter sections can be configured. Filters can be designed on the fly with the SciPy library or loaded from another source.

Signed-off-by: Gareth Farrington <gareth@waves.ky>
2025-05-29 19:01:38 -04:00
Timofey Titovets
0181023954 lis2dw: if spi is used - disable i2c
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-05-26 18:44:29 -04:00
Timofey Titovets
07b3726d31 stm32: h7 spi - add a delay on SCK polarity change
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-05-26 18:44:29 -04:00
Timofey Titovets
28a4baf95c spi_software: add a delay on mode change
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-05-26 18:44:29 -04:00
Timofey Titovets
14685bf77f rp2040: add a delay on SCK polarity change
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-05-26 18:44:29 -04:00
Dmitry Butyugin
b1011e3fb1 dual_carriage: Fixed input shaper stepper kinematics initialization
Signed-off-by: Dmitry Butyugin <dmbutyugin@google.com>
2025-05-22 14:44:09 -04:00
Kevin O'Connor
17b8ce4c6b docs: Remove SHAPER_CALIBRATE and usb-to-canbus bridge warning in CANBUS.md
There have been several optimization to the usb to canbus bridge code
since that statement and it is likely many setups can run a
SHAPER_CALIBRATE with all activity on a single 1mbit canbus.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-19 12:54:26 -04:00
Kevin O'Connor
9090377bbc stm32: Allow stm32g4 chips to select a bootloader
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-19 12:31:46 -04:00
JamesH1978
2d4589949c docs: Update Installation.md - Flash loop protection (#6935)
Added a paragraph about flash loop protection on some motherboards. It has been noted that not all people know about the need to change the bin filename on some stock boards or other methods that may hinder progress.

Signed-off-by: James Hartley <james@hartleyns.com>
2025-05-19 12:30:39 -04:00
Timofey Titovets
8c01be8c75 stm32: spi enable fifo if supported (#6936)
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-19 12:24:29 -04:00
Timofey Titovets
3a015cd00d stm32: H7 spi enable use of fifo
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-05-19 12:21:33 -04:00
Kevin O'Connor
841a9ca2f7 stm32: Avoid read-modify-write register access in stm32h7_spi.c
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-16 12:26:52 -04:00
Kevin O'Connor
fe9eff8ce3 docs: Fix index.md links
Use relative links in index.md and correct the location of the COPYING
file.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-16 12:14:45 -04:00
Rowland
82f540bb73 docs: BED_MESH_CALIBRATE makes a mesh that is immediately available. (#6919)
The docs aren't particularly clear that if you generate a mesh in our start g-code, you can just use it without additional commands. This is causing issues with support on r/klippers

Signed-off-by: Rowland Straylight <rowlandstraylight@gmail.com>
2025-05-14 13:53:10 -04:00
Kevin O'Connor
ed36041b67 resonance_tester: Fix typo
Fix typo introduced in 307c03e48.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-13 11:14:30 -04:00
Kevin O'Connor
1af219fad6 klippy_requirements: Update dependencies to support Python v3.13
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-12 21:51:53 -04:00
Kevin O'Connor
6c1d5d912a manual_stepper: Support LIMIT_VELOCITY and LIMIT_ACCEL when using gcode axis
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-12 20:15:03 -04:00
Kevin O'Connor
ee0bc3d697 manual_stepper: Support position_min and position_max options
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-12 20:15:03 -04:00
Kevin O'Connor
64e01f03a2 manual_stepper: Support INSTANTANEOUS_CORNER_VELOCITY on gcode axes
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-12 20:15:03 -04:00
Kevin O'Connor
7201f41664 manual_stepper: Support registering as an additional axis
Add a new G-Code command that can register a manual_stepper as an
additional axis on standard G-Code G1 commands.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-12 20:15:03 -04:00
Kevin O'Connor
d40fd2190d gcode_move: Support additional toolhead axes
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-12 20:15:03 -04:00
Kevin O'Connor
4c21e1d00f gcode_move: Internally track an axis_map to map gcode axis names
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-12 20:15:03 -04:00
Kevin O'Connor
2082300309 z_thermal_adjust: Support toolhead positions with more than 4 axes
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-12 20:15:03 -04:00
Kevin O'Connor
53acdfd0a5 skew_correction: Support toolhead positions with more than 4 axes
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-12 20:15:03 -04:00
Kevin O'Connor
a537ae0ceb exclude_object: Support toolhead positions with more than 4 axes
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-12 20:15:03 -04:00
Kevin O'Connor
307c03e480 resonance_tester: Support toolhead positions with more than 4 axes
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-12 20:15:03 -04:00
Kevin O'Connor
64d6f110a9 bed_tilt: Support toolhead positions with more than 4 axes
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-12 20:15:03 -04:00
Kevin O'Connor
447908ce0c bed_mesh: Support toolhead positions with more than 4 axes
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-12 20:15:03 -04:00
Kevin O'Connor
bb281834b0 toolhead: Initial support for adding extra axes to toolhead moves
Add a new add_extra_axes() to support adding additional axes.  Once
called, toolhead.get_position() will return a list object with more
than 4 items, and toolhead.move() requires the same size list.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-12 20:15:03 -04:00
Kevin O'Connor
9dbfc76d9d force_move: No need to pass 4 parameters to toolhead.set_position()
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-12 20:15:03 -04:00
Kevin O'Connor
ae536b4786 toolhead: Only alter XYZ coordinates on set_position() calls
It's not valid to alter the extruder position from a call to
set_position(), so don't allow callers to attempt that.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-12 20:15:03 -04:00
Kevin O'Connor
8928c394cf toolhead: Support unregister_step_generator() call
Allow both registering and unregistering step generation callbacks.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-12 20:15:03 -04:00
Kevin O'Connor
01422da951 extruder: Remove update_move_time() call
The toolhead can obtain the underlying extruder trapq via
extruder.get_trapq().

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-12 20:15:03 -04:00
Kevin O'Connor
f06eeb5c7a extruder: Rename extruder.move() to extruder.process_move()
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-12 20:15:03 -04:00
Dmitry Butyugin
ca83c13f37 generic_cartesian: Fixed safe_z_home and manual_probe for new kinematics
Signed-off-by: Dmitry Butyugin <dmbutyugin@google.com>
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-10 20:16:00 -04:00
Dmitry Butyugin
8627c94d6a stepper: Fix broken manual_stepper rail naming (#6929)
The naming got broken during refactoring for generic_cartesian.

Signed-off-by: Dmitry Butyugin <dmbutyugin@google.com>
2025-05-10 20:10:54 -04:00
Kevin O'Connor
6f87a4e685 stepper: Minor code tweak - remove unneeded parenthesis
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-09 12:58:01 -04:00
Kevin O'Connor
b5aea5b774 stepper: Minor code reorg - remove unneeded HAVE_OPTIMIZED_PATH definition
Make it more clear that stepper_load_next() has three separate code
paths - one for each of the optimized stepper_event_X() functions.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-09 12:38:25 -04:00
Kevin O'Connor
fd55dd9e9d stepper: Also ensure minimum time after dir change and next step
In practice the host will not schedule any steps immediately after a
direction change (due to acceleration limits and the host
"step+dir+step filter").  However, there is also no harm in enforcing
a minimum duration in the mcu.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-09 09:52:05 -04:00
Kevin O'Connor
885f63cff0 stepper: Ensure minimum time between step pin and dir pin change
Commit 8faed8d9 made it possible to utilize stepper_event_full() while
utilizing tmc "step on both edges" optimation.  That commit would
ensure a minimum step pulse duration, but it did not ensure a minimum
duration between step pin and dir pin changes.  Commits 0d27195f and
554ae78d optimized the gpio handling on stm32h7 chips, which could
potentially cause a very small amount of time between step pin and dir
pin changes.

Enforce a minimum time after a step pin update before updating the dir
pin.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-09 09:52:05 -04:00
Kevin O'Connor
efabe63357 stepper: Move timer checks from stepper_event_full() to stepper_load_next()
This simplifies the stepper_event_full() and makes it easier to
implement more complex checks.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-09 09:52:05 -04:00
Kevin O'Connor
1dc9aa8e19 stepper: Free stepper_move struct near top of stepper_load_next()
Move up the freeing of the stepper_move struct and setting of
s->position in stepper_load_next().  This simplifies the code and
will make it easier to add more logic to this function.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-09 09:52:05 -04:00
Matszwe02
9aba1a8536 gcode_macro: more descriptive "unable to parse as a literal" error - display faulty command
Signed-off-by: Mateusz Szwedka <matszwe02@gmail.com>
2025-05-08 11:37:38 -04:00
JamesH1978
81277154d2 config: Update CR6-SE 2021 config with board revision (#6924)
As per #6923 this PR adds the fact that this config works for the late revision CR-ERA_V1.1.0.3 board as well.

Signed-off-by: James Hartley <james@hartleyns.com>
2025-05-08 11:36:04 -04:00
JamesH1978
d444289111 config: Update CR6-SE 2020 config with board revision (#6923)
It has been noted that there are 3 possible boards with 2 possible configs, which we have both of, but this one does not state that it is for the 4.5.2 early kickstarter version. Which was causing some confusion.

Signed-off-by: James Hartley <james@hartleyns.com>
2025-05-08 11:35:38 -04:00
Dmitry Butyugin
89ffbbed4c dual_carriage: Fixed broken safe_distance parameter
Signed-off-by: Dmitry Butyugin <dmbutyugin@google.com>
2025-05-07 14:23:39 -04:00
Dmitry Butyugin
cc6736c3e3 kinematics: Generic Cartesian kinematics implementation (#6815)
* tests: Added a regression test for generic_cartesian kinematics

* kinematics: An intial implementation of generic_cartesian kinematics

* generic_cartesian: Refactored kinematics configuration API

* generic_cartesian: Use stepper instead of kinematic_stepper in configs

* generic_cartesian: Added SET_STEPPER_KINEMATICS command

* generic_cartesian: Fixed parsing of section names

* docs: Generic Caretsian kinematics documentation and config samples

* generic_cartesian: Implemented multi-mcu homing validation

* generic_cartesian: Fixed typos in docs, minor fixes

* generic_cartesian: Renamed `kinematics` option to `carriages`

* generic_cartesian: Moved kinematic_stepper.py file

* idex_modes: Internal refactoring of handling dual carriages

* stepper: Refactored the code to not store a reference to config object

* config: Updated example-generic-cartesian config

* generic_cartesian: Restricted SET_STEPPER_CARRIAGES and exported status

* idex_modes: Fixed handling stepper kinematics with input shaper enabled

* config: Updated configs and tests for SET_DUAL_CARRIAGE new params

* generic_cartesian: Avoid inheritance in the added classes

Signed-off-by: Dmitry Butyugin <dmbutyugin@google.com>
2025-05-06 18:06:36 -04:00
Kevin O'Connor
1cc6398074 klippy-requirements: Add comments to each of the requirements
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-02 13:52:20 -04:00
Eric Callahan
1e045e8ee0 build: add msgspec python requirement
Signed-off-by:  Eric Callahan <arksine.code@gmail.com>
2025-05-02 12:19:13 -04:00
Eric Callahan
f7e33df99d webhooks: support msgspec json serialization
Signed-off-by:  Eric Callahan <arksine.code@gmail.com>
2025-05-02 12:19:13 -04:00
Kevin O'Connor
4504c0333f docs: Update stm32h723 benchmarks now that it runs at 520Mhz
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-02 12:06:59 -04:00
Kevin O'Connor
554ae78d8c stm32: Run stm32h723 at 520Mhz
Increase speed of stm32h723 chips from 400Mhz to 520Mhz.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-02 11:44:31 -04:00
Kevin O'Connor
ee79d0e307 stm32: Support over 400Mhz main clock in stm32h7_adc.c
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-02 11:44:31 -04:00
Kevin O'Connor
7b697105b3 stm32: Use 12Mhz nominal internal clock in stm32f0_i2c.c
Increase the internal nominal clock from 8Mhz to 12Mhz - this improves
support for higher chip frequencies.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-05-02 11:44:31 -04:00
Kevin O'Connor
3cf8899a5a docs: Note canbus_query.py limitations in CANBUS_Troubleshooting.md
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-29 13:52:57 -04:00
Kevin O'Connor
b7c243db19 docs: Note functioning canbus required even in bridge mode in CANBUS.md
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-29 13:02:34 -04:00
Kevin O'Connor
5b2f8104c7 neopixel: Round up in nsecs_to_ticks()
The rp2040 operates at a fast internal clock with a relatively slow
external timer and dividing down could result in a too small delay.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-28 19:55:30 -04:00
Kevin O'Connor
cf3bedfbdc stm32: Enable VOS0 power mode on stm32h723 if frequency above 400Mhz
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-28 19:36:55 -04:00
Kevin O'Connor
7f4f696f10 stm32: Don't try to set incorrect PWR->CR3 register on stm32h7
It's not valid to set BYPASS and LDOEN at the same time.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-28 19:36:55 -04:00
Kevin O'Connor
9c37a918db stm32: Set the PLL frequency equal to CONFIG_CLOCK_FREQ on stm32h723
There is no reason to use a higher internal PLL frequency.  This
change also makes it possible to enable higher clock frequencies on
the stm32h723.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-28 19:36:55 -04:00
Kevin O'Connor
f2b68fef73 stm32: Avoid read-modify-write register updates in stm32h7 clock_setup()
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-28 19:36:55 -04:00
Kevin O'Connor
c352617c30 stm32: Use enable_pclock() in stm32h7 clock_setup()
Use the helper functions to enable the peripheral clock instead of
directly manipulating the clock enable bits.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-28 19:36:55 -04:00
Kevin O'Connor
5d1f773ffb stm32h7: Always clear AHB1ENR at startup on stm32h7
Entirely clear the AHB1ENR register.  There is no need to modify
AHB1LPENR.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-28 19:36:55 -04:00
Kevin O'Connor
da8e0a6e50 docs: Update date of cycle_time change in Config_Changes.md
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-28 19:36:12 -04:00
Kevin O'Connor
42faa962fc mcu: Decrease mcu.max_nominal_duration() to 3 seconds from 5
This allows the mcu to utilize faster internal speeds.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-28 19:28:52 -04:00
Kevin O'Connor
0f94f6c8e3 heaters: Confirm heater setting in mcu every 3 seconds instead of 5
Increase the confirmation rate of heater enable settings.  This allows
the mcu to utilize faster internal speeds.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-28 19:28:52 -04:00
Kevin O'Connor
c917bd893d pwm_tool: Use mcu.min_schedule_time() and mcu.max_nominal_duration()
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-28 19:28:52 -04:00
Kevin O'Connor
d57bc253c5 led: Use mcu.min_schedule_time() and mcu.max_nominal_duration()
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-28 19:28:52 -04:00
Kevin O'Connor
0dce120a20 pwm_cycle_time: Use mcu.min_schedule_time() and mcu.max_nominal_duration()
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-28 19:28:52 -04:00
Kevin O'Connor
ab61b0a435 output_pin: Use mcu.min_schedule_time() and mcu.max_nominal_duration()
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-28 19:28:52 -04:00
Kevin O'Connor
cc919a5f8d mcu: Add new min_schedule_time() and max_nominal_duration() helpers
Add a function that returns the minimum amount of time the host needs
to reserve for messages to be sent from host to micro-controller.

Add a function that returns the maximum amount of time (in seconds)
that all micro-controllers should be able to schedule future timers
at.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-28 19:28:52 -04:00
Kevin O'Connor
8e107b2280 basecmd: Update stats timing check to support 32bit duration
Use a 32bit duration check instead of the previous 31bit check.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-28 19:28:52 -04:00
Wulfsta
f1e0730701 lis3dh: increase scale from 8g to 16g
Signed-off-by: Luke Vuksta <wulfstawulfsta@gmail.com>
2025-04-28 19:27:43 -04:00
Timofey Titovets
2e82fc4790 spi_flash: fix hw bus
_try_send_command() expects a list of args,
But receives a string.

Fixes abc76ee963.

Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-04-28 15:58:55 -04:00
Timofey Titovets
bfda326c24 spi_flash: fix spi bus switch (#6906)
Fixes abc76ee963.

Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-04-20 11:39:24 -04:00
Russell Cloran
f2b27d17b7 stm32: Add support for spi6 on stm32f42x chips
Signed-off-by: Russell Cloran <rcloran@gmail.com>
2025-04-19 12:12:55 -04:00
Kevin O'Connor
5001983d34 stm32: Fix pll_base on stm32h7 when using a clock other than 25Mhz
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-19 12:09:58 -04:00
Kevin O'Connor
73e27aee4f docs: Update benchmarks for stm32h7
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-19 12:03:55 -04:00
Kevin O'Connor
0d27195fd4 stm32: Add optimized stm32h7_gpio.c
Add optimized gpio functions for stm32h7 - caching the ODR register
can notably improve the performance of the gpio_out_toggle() code.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-19 11:48:58 -04:00
Kevin O'Connor
1f5783a250 probe: Remove ProbeEndstopSessionHelper
Have all callers instantiate the individual helper classes directly.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-18 14:38:34 -04:00
Kevin O'Connor
37952e8686 probe_eddy_current: Separate probe style commands from homing operations
Separate homing operations (as called from probe:z_virtual_endstop)
from the normal probe command handling.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-18 14:38:34 -04:00
Kevin O'Connor
ab9b9e8584 probe_eddy_current: Do not support QUERY_PROBE command
Report an error if a user issues a QUERY_PROBE command (instead of
always returning not-triggered).

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-18 14:38:34 -04:00
Kevin O'Connor
3fb1191cad probe: Add a new LookupZSteppers helper class
Split code to lookup the Z stepper from HomingViaProbeHelper to new
LookupZSteppers class.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-18 14:38:34 -04:00
Kevin O'Connor
f3a1c914a4 probe: Convert probing_move() callback to use regular probe sessions system
Use the normal probe_session_start(), run_probe(),
pull_probed_results(), and end_probe_session() API from
ProbeSessionHelper.  This removes the custom probing_move() callback.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-18 14:38:34 -04:00
Kevin O'Connor
b2e36e5d98 probe: Change probing_move() to pass a gcmd instead of (pos, speed)
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-18 14:38:34 -04:00
Kevin O'Connor
ff0ffedd17 probe: Add a new ProbeParameterHelper class
Split multi-sample config reading from ProbeSessionHelper to a new
ProbeParameterHelper class.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-18 14:38:34 -04:00
Kevin O'Connor
1e87d26707 probe: Add a new lookup_minimum_z() helper function
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-18 14:38:34 -04:00
Kevin O'Connor
f8de9ae080 probe: Add a new ProbeEndstopSessionHelper class
Move the HomingViaProbeHelper() instance from ProbeSessionHelper to a
new ProbeEndstopSessionHelper class.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-18 14:38:34 -04:00
Kevin O'Connor
6a87c5e9f5 probe: Add a default probing_move() function to HomingViaProbeHelper
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-18 14:38:34 -04:00
Kevin O'Connor
db7a9cf071 manual_stepper: Implement "drip moves" for manual stepper STOP_ON_ENDSTOP
Currently, `MANUAL_STEPPER STOP_ON_ENDSTOP=1` type commands will move
until hitting the endstop, but it will still always consume the total
amount of move time.  That is, following moves can't be started until
the total possible time of the homing move is completed.

Implement "drip moves" so that the code only schedules the movement in
small segments.  This allows following movements to be scheduled
without a significant delay.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-18 14:32:23 -04:00
Kevin O'Connor
765de72f9e toolhead: Avoid toolhead.move() and toolhead._process_moves() in drip_move()
Implement move checking and trapq loading directly from drip_move().
This simplifies the interactions between these components.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-18 14:32:23 -04:00
Kevin O'Connor
6202a0f3bc toolhead: Avoid LookAheadQueue calling back into toolhead class
Avoid lookahead.flush() calling back into toolhead._process_moves().
Instead, rename toolhead._process_moves() to
toolhead._process_lookahead(), have it call lookahead.flush(), and
consistently use it when flushing the lookahead queue.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-18 14:32:23 -04:00
Kevin O'Connor
413ff19ea8 neopixel: Add comments on timing
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-17 13:06:22 -04:00
Kevin O'Connor
4e7fcc2704 check-software-div: Add a new build check for software divide
Update the build checks to include a check for unexpected software
divide operations.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-17 12:09:19 -04:00
Kevin O'Connor
871637d3f2 Kconfig: Note which chips require software divide operations
Add a new HAVE_SOFTWARE_DIVIDE_REQUIRED that indicates which chips
require software divide.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-17 12:09:19 -04:00
Kevin O'Connor
0fbcc156c5 neopixel: Make sure nsecs_to_ticks() is always inlined
It is a compile-time calculation that needs to be inlined to work.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-17 12:09:19 -04:00
Kevin O'Connor
56d3f4e64c lcd_st7920: Make sure nsecs_to_ticks() is always inlined
It is a compile-time calculation that needs to be inlined to work.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-17 12:09:19 -04:00
Kevin O'Connor
cb6828ec34 lcd_hd44780: Make sure nsecs_to_ticks() is always inlined
It is a compile-time calculation that needs to be inlined to work.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-17 12:09:19 -04:00
Kevin O'Connor
3656006a30 stm32: Change hard_pwm.c MAX_PWM to 257
Choose a value for MAX_PWM that avoids an expensive run-time division.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-17 12:07:44 -04:00
Kevin O'Connor
7a9b06ad86 stm32: Fix prescaler overflow check in hard_pwm.c
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-17 12:07:44 -04:00
Kevin O'Connor
acd96047de docs: Update Config_Changes.md to note software spi is now rate limited
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-17 11:54:25 -04:00
Russell Cloran
516ef1d361 hall_filament_width_sensor: Add filament switch values to status
`hall_filament_width_sensor` contains a runout sensor object internally.
This exposes those values in the API status result.

```
SEND: {"id":123,"method":"objects/query","params":{"objects":{"hall_filament_width_sensor":["enabled","filament_detected","is_active","Diameter","Raw"]}}}
GOT: b'{"id":123,"result":{"eventtime":199567.823596603,"status":{"hall_filament_width_sensor":{"enabled":true,"filament_detected":true,"is_active":true,"Diameter":1.9499999999999986,"Raw":6113}}}}'
```

The duplication of `is_active` and `enabled` seems confusing, but both
of these can be independently manipulated by GCode:

```
SEND: {"id":123,"method":"gcode/script","params":{"script":"DISABLE_FILAMENT_WIDTH_SENSOR"}}
GOT: b'{"id":123,"result":{}}'
SEND: {"id":123,"method":"objects/query","params":{"objects":{"hall_filament_width_sensor":["enabled","is_active"]}}}
GOT: b'{"id":123,"result":{"eventtime":199770.446013297,"status":{"hall_filament_width_sensor":{"enabled":true,"is_active":false}}}}'

SEND: {"id":123,"method":"gcode/script","params":{"script":"SET_FILAMENT_SENSOR SENSOR=hall_filament_width_sensor ENABLE=0"}}
GOT: b'{"id":123,"result":{}}'
SEND: {"id":123,"method":"objects/query","params":{"objects":{"hall_filament_width_sensor":["enabled","is_active"]}}}
GOT: b'{"id":123,"result":{"eventtime":199847.927726196,"status":{"hall_filament_width_sensor":{"enabled":false,"is_active":false}}}}'
```

Signed-off-by: Russell Cloran <rcloran@gmail.com>
2025-04-17 11:44:26 -04:00
Timofey Titovets
b9757c9b69 tmc: add missing freewheel config options
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-04-17 11:42:44 -04:00
Timofey Titovets
a9b04e8536 i2c_software: pass pulse ticks from host
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-04-17 11:41:49 -04:00
Timofey Titovets
841adcfff7 i2c_software: reduce gpio calls count
gpio reset calls are heavy.
gpio state are persistent between calls.
Drop useless calls.
Avoid calls if SDA does not changed.

Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-04-17 11:41:49 -04:00
Timofey Titovets
8ab12c86bf i2c_software: allow freq adjust
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-04-17 11:41:49 -04:00
Timofey Titovets
abc76ee963 software_spi: set rate limiting ticks from the host
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-04-17 11:37:12 -04:00
Timofey Titovets
b826844b34 spi_software: respect expected rate
On fast MCU software spi may violate maximally supported by TMC driver rate.

Add dynamic limits to overcome that.

Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-04-17 11:37:12 -04:00
Kevin O'Connor
017371b744 Revert "Makefile: Don't disable gcc's use-linker-plugin option"
This reverts commit 8087200ffe.

The change can break the build on some versions of gcc.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-16 15:24:05 -04:00
Kevin O'Connor
4aa2250221 test: Disable all additional features in atmega328 build
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-16 15:24:05 -04:00
JamesH1978
49d9ed22bf config: Update generic-bigtreetech-skr-2.cfg - SPI Drivers (#6895)
Added SPI tmc2130 driver config

Signed-off-by: James Hartley <james@hartleyns.com>
2025-04-16 14:13:17 -04:00
Kevin O'Connor
51311948be atsamd: Enable HAVE_LIMITED_CODE_SIZE on small atsamd chips
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-16 13:54:44 -04:00
Kevin O'Connor
61bbd455cf Kconfig: Add some user visible comments to the optional features menu
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-16 13:54:44 -04:00
Kevin O'Connor
d93645a750 stm32: Simplify Makefile
Breakout selection of timer and gpioperiph objects.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-16 13:54:44 -04:00
Kevin O'Connor
8c67adc164 Kconfig: Add new WANT_ADC option to reduce code size
Make it possible to not compile in support for ADC on chips with small
flash sizes.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-16 13:54:44 -04:00
Kevin O'Connor
04e7eb20fd Kconfig: Add new WANT_I2C option to reduce code size
Make it possible to not compile in support for I2C on chips with small
flash sizes.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-16 13:54:44 -04:00
Kevin O'Connor
868760f5b1 Kconfig: Add new WANT_SPI option to reduce code size
Make it possible to not compile in support for SPI on chips with small
flash sizes.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-16 13:54:44 -04:00
Kevin O'Connor
b0fa36e221 Kconfig: Add new WANT_HARD_PWM option to reduce code size
Make it possible to not compile in support for hardware pwm on chips
with small flash sizes.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-16 13:54:44 -04:00
Kevin O'Connor
6356e3d35c stm32: Enable gcc -Os option on CONFIG_HAVE_LIMITED_CODE_SIZE
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-16 13:54:44 -04:00
Kevin O'Connor
6e9b5b309c avr: Enable gcc -Os option on CONFIG_HAVE_LIMITED_CODE_SIZE
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-16 13:54:44 -04:00
Kevin O'Connor
d98abfc5db Kconfig: Replace WANT_DISPLAYS with individual options
Support setting WANT_ST7920 and WANT_HD44780 individually.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-16 13:54:39 -04:00
Kevin O'Connor
c3c64adc32 Kconfig: Replace WANT_GPIO_BITBANGING with individual options
Support setting individual options instead of one global option (
WANT_BUTTONS, WANT_TMCUART, WANT_NEOPIXEL, WANT_PULSE_COUNTER,
WANT_HX71X).

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-16 13:54:27 -04:00
Kevin O'Connor
efc2d9b364 workflows: Update github build-test.yaml to ubuntu-22.04
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-16 13:19:54 -04:00
Kevin O'Connor
d96bb6ca82 test: Disable optional features in atmega328 build
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-16 13:19:54 -04:00
Kevin O'Connor
8087200ffe Makefile: Don't disable gcc's use-linker-plugin option
This option seems to be confusing ld's region usage checks (builds
that could fit in small chips are being reported as not fitting).  The
option was disabled back in commit 4e8674d5 because it showed worse
results.  However, recent versions of gcc seem to produce the same
results even if this option is enabled, so change the build to avoid
disabling that option on newer versions of gcc (those that have the
-ffat-lto-objects option - which is needed to ensure
compile_time_requests sections can be extracted with objcopy).

The PRU build is dependent on -fuse-linker-plugin, so enable that
option explicitly in its build.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-16 13:17:19 -04:00
210 changed files with 7332 additions and 1862 deletions

View File

@@ -4,7 +4,7 @@ on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3

View File

@@ -0,0 +1,138 @@
# This file is an example config file for cartesian style printers.
# One may copy and edit this file to configure a new printer with
# a generic cartesian kinematics.
# DO NOT COPY THIS FILE WITHOUT CAREFULLY READING AND UPDATING IT
# FIRST. Incorrectly configured parameters may cause damage.
# See docs/Config_Reference.md for a description of parameters.
[carriage x]
position_endstop: 0
position_max: 300
homing_speed: 50
endstop_pin: ^PE5
[carriage y]
position_endstop: 0
position_max: 200
homing_speed: 50
endstop_pin: ^PJ1
[extra_carriage y1]
primary_carriage: y
endstop_pin: ^PB6
[carriage z]
position_endstop: 0.5
position_max: 100
endstop_pin: ^PD3
[dual_carriage u]
primary_carriage: x
position_endstop: 300
position_max: 300
homing_speed: 50
endstop_pin: ^PE4
[stepper my_stepper_x]
carriages: x+y
step_pin: PF0
dir_pin: PF1
enable_pin: !PD7
microsteps: 16
rotation_distance: 40
[stepper my_stepper_u]
carriages: u-y1
step_pin: PH1
dir_pin: PH0
enable_pin: !PA1
microsteps: 16
rotation_distance: 40
[stepper my_stepper_y0]
carriages: y
step_pin: PF6
dir_pin: !PF7
enable_pin: !PF2
microsteps: 16
rotation_distance: 40
[stepper my_stepper_y1]
carriages: y1
step_pin: PE3
dir_pin: !PH6
enable_pin: !PG5
microsteps: 16
rotation_distance: 40
[stepper my_stepper_z0]
carriages: z
step_pin: PL3
dir_pin: PL1
enable_pin: !PK0
microsteps: 16
rotation_distance: 8
[stepper my_stepper_z1]
carriages: z
step_pin: PG1
dir_pin: PG0
enable_pin: !PH3
microsteps: 16
rotation_distance: 8
[extruder]
step_pin: PA4
dir_pin: PA6
enable_pin: !PA2
microsteps: 16
rotation_distance: 33.5
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: PB4
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PK5
control: pid
pid_Kp: 22.2
pid_Ki: 1.08
pid_Kd: 114
min_temp: 0
max_temp: 250
[extruder1]
step_pin: PC1
dir_pin: PC3
enable_pin: !PC7
microsteps: 16
rotation_distance: 33.5
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: PB5
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PK7
control: pid
pid_Kp: 22.2
pid_Ki: 1.08
pid_Kd: 114
min_temp: 0
max_temp: 250
[heater_bed]
heater_pin: PH5
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PK6
control: watermark
min_temp: 0
max_temp: 110
[mcu]
serial: /dev/ttyACM0
[printer]
kinematics: generic_cartesian
max_velocity: 500
max_accel: 3000
max_z_velocity: 20
max_z_accel: 100

View File

@@ -39,7 +39,7 @@ position_max: 270
# Motor4
# The M8P only has 4 heater outputs which leaves an extra stepper
# This can be used for a second Z stepper, dual_carriage, extruder co-stepper,
# or other accesory such as an MMU
# or other accessory such as an MMU
#[stepper_]
#step_pin: PD3
#dir_pin: PD2

View File

@@ -40,7 +40,7 @@ position_max: 270
# Motor4
# The M8P only has 4 heater outputs which leaves an extra stepper
# This can be used for a second Z stepper, dual_carriage, extruder co-stepper,
# or other accesory such as an MMU
# or other accessory such as an MMU
#[stepper_]
#step_pin: PD3
#dir_pin: PD2

View File

@@ -43,7 +43,7 @@ position_max: 200
# Motor-4
# The Octopus only has 4 heater outputs which leaves an extra stepper
# This can be used for a second Z stepper, dual_carriage, extruder co-stepper,
# or other accesory such as an MMU
# or other accessory such as an MMU
#[stepper_]
#step_pin: PB8
#dir_pin: PB9

View File

@@ -52,7 +52,7 @@ position_max: 200
# Driver3
# The Octopus only has 4 heater outputs which leaves an extra stepper
# This can be used for a second Z stepper, dual_carriage, extruder co-stepper,
# or other accesory such as an MMU
# or other accessory such as an MMU
#[stepper_]
#step_pin: PG4
#dir_pin: PC1

View File

@@ -153,3 +153,48 @@ aliases:
#uart_pin: PD12
#run_current: 0.600
#diag_pin:
########################################
# TMC2130 configuration
########################################
#[tmc2130 stepper_x]
#cs_pin: PE0
#spi_software_miso_pin: PA14
#spi_software_mosi_pin: PE14
#spi_software_sclk_pin: PE15
#run_current: 0.800
#diag1_pin: PC1
#[tmc2130 stepper_y]
#cs_pin: PD3
#spi_software_miso_pin: PA14
#spi_software_mosi_pin: PE14
#spi_software_sclk_pin: PE15
#run_current: 0.800
#diag1_pin: PC3
#[tmc2130 stepper_z]
#cs_pin: PD0
#spi_software_miso_pin: PA14
#spi_software_mosi_pin: PE14
#spi_software_sclk_pin: PE15
#run_current: 0.800
#diag1_pin: PC0
#[tmc2130 extruder]
#cs_pin: PC6
#spi_software_miso_pin: PA14
#spi_software_mosi_pin: PE14
#spi_software_sclk_pin: PE15
#run_current: 0.600
#diag1_pin: PC2
#[tmc2130 extruder1]
#cs_pin: PD12
#spi_software_miso_pin: PA14
#spi_software_mosi_pin: PE14
#spi_software_sclk_pin: PE15
#run_current: 0.600
#stealthchop_threshold: 999999
#diag1_pin: PA0

View File

@@ -89,32 +89,32 @@ max_z_velocity: 5
max_z_accel: 100
[mcp4018 x_axis_pot]
scl_pin: PJ5
sda_pin: PF3
i2c_software_scl_pin: PJ5
i2c_software_sda_pin: PF3
wiper: 0.50
scale: 0.773
[mcp4018 y_axis_pot]
scl_pin: PJ5
sda_pin: PF7
i2c_software_scl_pin: PJ5
i2c_software_sda_pin: PF7
wiper: 0.50
scale: 0.773
[mcp4018 z_axis_pot]
scl_pin: PJ5
sda_pin: PK3
i2c_software_scl_pin: PJ5
i2c_software_sda_pin: PK3
wiper: 0.50
scale: 0.773
[mcp4018 a_axis_pot]
scl_pin: PJ5
sda_pin: PA5
i2c_software_scl_pin: PJ5
i2c_software_sda_pin: PA5
wiper: 0.50
scale: 0.773
[mcp4018 b_axis_pot]
scl_pin: PJ5
sda_pin: PJ6
i2c_software_scl_pin: PJ5
i2c_software_sda_pin: PJ6
wiper: 0.50
scale: 0.773

View File

@@ -19,7 +19,7 @@
# FSR switch (z endstop) location [homing_override] section
# FSR switch (z endstop) offset for Z0 [stepper_z] section
# Probe points [quad_gantry_level] section
# Min & Max gantry corner postions [quad_gantry_level] section
# Min & Max gantry corner positions [quad_gantry_level] section
# PID tune [extruder] and [heater_bed] sections
# Fine tune E steps [extruder] section

View File

@@ -20,7 +20,7 @@
# FSR switch (z endstop) location [homing_override] section
# FSR switch (z endstop) offset for Z0 [stepper_z] section
# Probe points [quad_gantry_level] section
# Min & Max gantry corner postions [quad_gantry_level] section
# Min & Max gantry corner positions [quad_gantry_level] section
# PID tune [extruder] and [heater_bed] sections
# Fine tune E steps [extruder] section

View File

@@ -17,7 +17,7 @@ endstop_pin: ^PE4
homing_speed: 60
# The next parameter needs to be adjusted for
# your printer. You may want to start with 280
# and meassure the distance from nozzle to bed.
# and measure the distance from nozzle to bed.
# This value then needs to be added.
position_endstop: 273.0
arm_length: 229.4

View File

@@ -43,7 +43,7 @@ position_max: 400
#Uncomment if you have a BL-Touch:
#position_min: -4
#endstop_pin: probe:z_virtual_endstop
#and comment the follwing lines:
#and comment the following lines:
position_endstop: 0.0
endstop_pin: ^PD3 #ar18

View File

@@ -1,4 +1,5 @@
# This file contains pin mappings for the stock 2020 Creality CR6-SE.
# This file contains pin mappings for the stock 2020 Creality CR6-SE
# with the early 4.5.2 board only.
# To use this config, during "make menuconfig" select the STM32F103
# with a "28KiB bootloader" and serial (on USART1 PA10/PA9)
# communication.

View File

@@ -1,4 +1,6 @@
# This file contains pin mappings for the Creality CR6-SE with Rev. 4.5.3 Motherboard (Late 2020/2021) as the heater pins changed.
# This file contains pin mappings for the Creality CR6-SE
# with Rev. 4.5.3 Motherboard (Late 2020/2021) as the heater pins changed.
# This config also works for the CR-ERA_V1.1.0.3
# To use this config, during "make menuconfig" select the STM32F103
# with a "28KiB bootloader" and serial (on USART1 PA10/PA9)
# communication.

View File

@@ -81,7 +81,7 @@ pin: PA0
kick_start_time: 0.5
# Hotend fan
# set fan runnig when extruder temperature is over 60
# set fan running when extruder temperature is over 60
[heater_fan heatbreak_fan]
pin: PC0
heater:extruder

View File

@@ -127,32 +127,32 @@ max_z_velocity: 5
max_z_accel: 100
[mcp4018 x_axis_pot]
scl_pin: PJ5
sda_pin: PF3
i2c_software_scl_pin: PJ5
i2c_software_sda_pin: PF3
wiper: 118
scale: 127
[mcp4018 y_axis_pot]
scl_pin: PJ5
sda_pin: PF7
i2c_software_scl_pin: PJ5
i2c_software_sda_pin: PF7
wiper: 118
scale: 127
[mcp4018 z_axis_pot]
scl_pin: PJ5
sda_pin: PK3
i2c_software_scl_pin: PJ5
i2c_software_sda_pin: PK3
wiper: 40
scale: 127
[mcp4018 a_axis_pot]
scl_pin: PJ5
sda_pin: PA5
i2c_software_scl_pin: PJ5
i2c_software_sda_pin: PA5
wiper: 118
scale: 127
[mcp4018 b_axis_pot]
scl_pin: PJ5
sda_pin: PJ6
i2c_software_scl_pin: PJ5
i2c_software_sda_pin: PJ6
wiper: 118
scale: 127

View File

@@ -195,7 +195,7 @@ samples_tolerance: 0.200
samples_tolerance_retries: 2
[bed_tilt]
# Enable bed tilt measurments using the probe we defined above
# Enable bed tilt measurements using the probe we defined above
# Probe points using X0 Y0 offsets @ 0.01mm/step
points: -2, -6
156, -6

View File

@@ -183,7 +183,7 @@ samples: 2
samples_tolerance: 0.100
[bed_tilt]
#Enable bed tilt measurments using the probe we defined above
#Enable bed tilt measurements using the probe we defined above
#Probe points using X0 Y0 offsets @ 0.01mm/step
points: -3, -6
282, -6

View File

@@ -37,7 +37,7 @@ microsteps: 16
rotation_distance: 4
# Required if not using probe for the virtual endstop
# endstop_pin: ^PD3
# position_endstop: 250 # Will need ajustment
# position_endstop: 250 # Will need adjustment
endstop_pin: probe:z_virtual_endstop
homing_speed: 10.0
position_max: 250

View File

@@ -1,4 +1,4 @@
# This file constains the pin mappings for the SeeMeCNC Rostock Max
# This file contains the pin mappings for the SeeMeCNC Rostock Max
# (version 2) delta printer from 2015. To use this config, the
# firmware should be compiled for the AVR atmega2560.

177
config/sample-corexyuv.cfg Normal file
View File

@@ -0,0 +1,177 @@
# This file contains a configuration snippet for a CoreXYUV
# printer with an independent dual extruder moving over X and Y axes.
# See docs/Config_Reference.md for a description of parameters.
[carriage x]
position_endstop: 0
position_max: 300
homing_speed: 50
endstop_pin: ^PE5
[carriage y]
position_endstop: 0
position_max: 200
homing_speed: 50
endstop_pin: ^PJ1
[dual_carriage u]
primary_carriage: x
safe_distance: 70
position_endstop: 300
position_max: 300
homing_speed: 50
endstop_pin: ^PE4
[dual_carriage v]
primary_carriage: y
safe_distance: 50
position_endstop: 200
position_max: 200
homing_speed: 50
endstop_pin: ^PD4
[stepper a]
carriages: x+y
step_pin: PF0
dir_pin: PF1
enable_pin: !PD7
microsteps: 16
rotation_distance: 40
[stepper b]
carriages: u-v
step_pin: PH1
dir_pin: PH0
enable_pin: !PA1
microsteps: 16
rotation_distance: 40
[stepper c]
carriages: x-y
step_pin: PF6
dir_pin: !PF7
enable_pin: !PF2
microsteps: 16
rotation_distance: 40
[stepper d]
carriages: u+v
step_pin: PE3
dir_pin: !PH6
enable_pin: !PG5
microsteps: 16
rotation_distance: 40
[extruder]
step_pin: PA4
dir_pin: PA6
enable_pin: !PA2
microsteps: 16
rotation_distance: 33.5
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: PB4
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PK5
control: pid
pid_Kp: 22.2
pid_Ki: 1.08
pid_Kd: 114
min_temp: 0
max_temp: 250
[gcode_macro PARK_extruder]
gcode:
SET_DUAL_CARRIAGE CARRIAGE=x
SET_DUAL_CARRIAGE CARRIAGE=y
G90
G1 X0 Y0
[gcode_macro T0]
gcode:
PARK_{printer.toolhead.extruder}
ACTIVATE_EXTRUDER EXTRUDER=extruder
SET_DUAL_CARRIAGE CARRIAGE=x
SET_DUAL_CARRIAGE CARRIAGE=y
[extruder1]
step_pin: PC1
dir_pin: PC3
enable_pin: !PC7
microsteps: 16
rotation_distance: 33.5
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: PB5
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PK7
control: pid
pid_Kp: 22.2
pid_Ki: 1.08
pid_Kd: 114
min_temp: 0
max_temp: 250
[gcode_macro PARK_extruder1]
gcode:
SET_DUAL_CARRIAGE CARRIAGE=u
SET_DUAL_CARRIAGE CARRIAGE=v
G90
G1 X300 Y200
[gcode_macro T1]
gcode:
PARK_{printer.toolhead.extruder}
ACTIVATE_EXTRUDER EXTRUDER=extruder1
SET_DUAL_CARRIAGE CARRIAGE=u
SET_DUAL_CARRIAGE CARRIAGE=v
# A helper script to activate copy mode
[gcode_macro ACTIVATE_COPY_MODE]
gcode:
SET_DUAL_CARRIAGE CARRIAGE=x MODE=PRIMARY
SET_DUAL_CARRIAGE CARRIAGE=y MODE=PRIMARY
G1 X0 Y0
ACTIVATE_EXTRUDER EXTRUDER=extruder
SET_DUAL_CARRIAGE CARRIAGE=u MODE=PRIMARY
SET_DUAL_CARRIAGE CARRIAGE=v MODE=PRIMARY
G1 X150 Y100
SET_DUAL_CARRIAGE CARRIAGE=u MODE=COPY
SET_DUAL_CARRIAGE CARRIAGE=v MODE=COPY
SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder
# A helper script to activate mirror mode
[gcode_macro ACTIVATE_MIRROR_MODE]
gcode:
SET_DUAL_CARRIAGE CARRIAGE=x MODE=PRIMARY
SET_DUAL_CARRIAGE CARRIAGE=y MODE=PRIMARY
G1 X0 Y0
ACTIVATE_EXTRUDER EXTRUDER=extruder
SET_DUAL_CARRIAGE CARRIAGE=u MODE=PRIMARY
SET_DUAL_CARRIAGE CARRIAGE=v MODE=PRIMARY
G1 X300 Y100
SET_DUAL_CARRIAGE CARRIAGE=u MODE=MIRROR
SET_DUAL_CARRIAGE CARRIAGE=v MODE=COPY
SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder
[printer]
kinematics: generic_cartesian
max_velocity: 300
max_accel: 3000
max_z_velocity: 5
max_z_accel: 100
## An optional input shaper support
#[input_shaper]
## The section is intentionally empty
#
#[delayed_gcode init_shaper]
#initial_duration: 0.1
#gcode:
# SET_DUAL_CARRIAGE CARRIAGE=u
# SET_DUAL_CARRIAGE CARRIAGE=v
# SET_INPUT_SHAPER SHAPER_TYPE_X=<dual_carriage_x_shaper> SHAPER_FREQ_X=<dual_carriage_x_freq> SHAPER_TYPE_Y=<dual_carriage_y_shaper> SHAPER_FREQ_Y=<dual_carriage_y_freq>
# SET_DUAL_CARRIAGE CARRIAGE=x MODE=PRIMARY
# SET_DUAL_CARRIAGE CARRIAGE=y MODE=PRIMARY
# SET_INPUT_SHAPER SHAPER_TYPE_X=<primary_carriage_x_shaper> SHAPER_FREQ_X=<primary_carriage_x_freq> SHAPER_TYPE_Y=<primary_carriage_y_shaper> SHAPER_FREQ_Y=<primary_carriage_y_freq>

View File

@@ -6,7 +6,7 @@
# Communication interface of "CAN bus (on PA25/PA24)"
# To flash the board use a debugger, or use a raspberry pi and follow
# the instructions at docs/Bootloaders.md fot the SAMC21. You may
# the instructions at docs/Bootloaders.md for the SAMC21. You may
# supply power to the 1LC by connecting the 3.3v rail on the Pi to the
# 5v input of the SWD header on the 1LC.

View File

@@ -96,7 +96,7 @@ switch_pin: !P1.28 # P1.28 for X-max
# variable_pause_z : z lift when MMU2S need intervention and the printer is paused
# variable_min_temp_extruder : minimal required heater temperature to load/unload filament from the extruder gear to the nozzle
# variable_extruder_eject_temp : heater temperature used to eject filament during home if the filament is already loaded
# variable_enable_5in1 : pass from MMU2S standart (0) to MMU2S-5in1 mode with splitter
# variable_enable_5in1 : pass from MMU2S standard (0) to MMU2S-5in1 mode with splitter
#
################################
[gcode_macro VAR_MMU2S]
@@ -394,7 +394,7 @@ gcode:
{% endif %}
{% endif %}
# Retry unload, try correct misalignement of bondtech gear
# Retry unload, try correct misalignment of bondtech gear
[gcode_macro RETRY_UNLOAD_FILAMENT_IN_EXTRUDER]
gcode:
{% if printer["filament_switch_sensor ir_sensor"].filament_detected == True %}
@@ -444,7 +444,7 @@ gcode:
{% endif %}
{% endif %}
# Ramming process for standart PLA, code extracted from slic3r gcode
# Ramming process for standard PLA, code extracted from slic3r gcode
[gcode_macro RAMMING_SLICER]
gcode:
G91

View File

@@ -380,6 +380,27 @@ and might later produce asynchronous messages such as:
The "header" field in the initial query response is used to describe
the fields found in later "data" responses.
### load_cell_probe/dump_taps
This endpoint is used to subscribe to details of probing "tap" events.
Using this endpoint may increase Klipper's system load.
A request may look like:
`{"id": 123, "method":"load_cell/dump_force",
"params": {"sensor": "load_cell", "response_template": {}}}`
and might return:
`{"id": 123,"result":{"header":["probe_tap_event"]}}`
and might later produce asynchronous messages such as:
```
{"params":{"tap":'{
"time": [118032.28039, 118032.2834, ...],
"force": [-459.4213119680034, -458.1640702543264, ...],
}}}
```
This data can be used to render:
* The time/force graph
### pause_resume/cancel
This endpoint is similar to running the "PRINT_CANCEL" G-Code command.

View File

@@ -267,7 +267,7 @@ by heat or interference. This can make calculating the probe's z-offset
challenging, particularly at different bed temperatures. As such, some
printers use an endstop for homing the Z axis and a probe for calibrating the
mesh. In this configuration it is possible offset the mesh so that the (X, Y)
`reference position` applies zero adjustment. The `reference postion` should
`reference position` applies zero adjustment. The `reference position` should
be the location on the bed where a
[Z_ENDSTOP_CALIBRATE](./Manual_Level.md#calibrating-a-z-endstop)
paper test is performed. The bed_mesh module provides the
@@ -292,33 +292,6 @@ probe_count: 5, 3
z-offset. Note that this coordinate must NOT be in a location specified as
a `faulty_region` if a probe is necessary.
#### The deprecated relative_reference_index
Existing configurations using the `relative_reference_index` option must be
updated to use the `zero_reference_position`. The response to the
[BED_MESH_OUTPUT PGP=1](#output) gcode command will include the (X, Y)
coordinate associated with the index; this position may be used as the value for
the `zero_reference_position`. The output will look similar to the following:
```
// bed_mesh: generated points
// Index | Tool Adjusted | Probe
// 0 | (1.0, 1.0) | (24.0, 6.0)
// 1 | (36.7, 1.0) | (59.7, 6.0)
// 2 | (72.3, 1.0) | (95.3, 6.0)
// 3 | (108.0, 1.0) | (131.0, 6.0)
... (additional generated points)
// bed_mesh: relative_reference_index 24 is (131.5, 108.0)
```
_Note: The above output is also printed in `klippy.log` during initialization._
Using the example above we see that the `relative_reference_index` is
printed along with its coordinate. Thus the `zero_reference_position`
is `131.5, 108`.
### Faulty Regions
It is possible for some areas of a bed to report inaccurate results when
@@ -497,7 +470,8 @@ _Default Adaptive Margin: 0_
Initiates the probing procedure for Bed Mesh Calibration.
The mesh will be saved into a profile specified by the `PROFILE` parameter,
The mesh will be immediately ready to use when the command completes and saved
into a profile specified by the `PROFILE` parameter,
or `default` if unspecified. The `METHOD` parameter takes one of the following
values:
@@ -561,6 +535,10 @@ load the `default` profile it is recommended to add
`BED_MESH_PROFILE LOAD=default` to either their `START_PRINT` macro or their
slicer's "Start G-Code" configuration, whichever is applicable.
Note that this is not required if a new mesh is generated with
`BED_MESH_CALIBRATE` in the `START_PRINT` macro or the slicer's "Start G-Code"
and may produce unexpected results, especially with adaptive meshing.
Alternatively the old behavior of loading a profile at startup can be
restored with a `[delayed_gcode]`:

View File

@@ -250,23 +250,22 @@ results were obtained by running an STM32F407 binary on an STM32F446
### STM32H7 step rate benchmark
The following configuration sequence is used on a STM32H743VIT6:
The following configuration sequence is used on STM32H723:
```
allocate_oids count=3
config_stepper oid=0 step_pin=PD4 dir_pin=PD3 invert_step=-1 step_pulse_ticks=0
config_stepper oid=1 step_pin=PA15 dir_pin=PA8 invert_step=-1 step_pulse_ticks=0
config_stepper oid=2 step_pin=PE2 dir_pin=PE3 invert_step=-1 step_pulse_ticks=0
config_stepper oid=0 step_pin=PA13 dir_pin=PB5 invert_step=-1 step_pulse_ticks=52
config_stepper oid=1 step_pin=PB2 dir_pin=PB6 invert_step=-1 step_pulse_ticks=52
config_stepper oid=2 step_pin=PB3 dir_pin=PB7 invert_step=-1 step_pulse_ticks=52
finalize_config crc=0
```
The test was last run on commit `00191b5c` with gcc version
`arm-none-eabi-gcc (15:8-2019-q3-1+b1) 8.3.1 20190703 (release)
[gcc-8-branch revision 273027]`.
The test was last run on commit `554ae78d` with gcc version
`arm-none-eabi-gcc (Fedora 14.1.0-1.fc40) 14.1.0`.
| stm32h7 | ticks |
| stm32h723 | ticks |
| -------------------- | ----- |
| 1 stepper | 44 |
| 3 stepper | 198 |
| 1 stepper | 70 |
| 3 stepper | 181 |
### STM32G0B1 step rate benchmark
@@ -287,6 +286,25 @@ The test was last run on commit `247cd753` with gcc version
| 1 stepper | 58 |
| 3 stepper | 243 |
### STM32G4 step rate benchmark
The following configuration sequence is used on the STM32G431:
```
allocate_oids count=3
config_stepper oid=0 step_pin=PA0 dir_pin=PB5 invert_step=-1 step_pulse_ticks=17
config_stepper oid=1 step_pin=PB2 dir_pin=PB6 invert_step=-1 step_pulse_ticks=17
config_stepper oid=2 step_pin=PB3 dir_pin=PB7 invert_step=-1 step_pulse_ticks=17
finalize_config crc=0
```
The test was last run on commit `cfa48fe3` with gcc version
`arm-none-eabi-gcc (Fedora 14.1.0-1.fc40) 14.1.0`.
| stm32g431 | ticks |
| ---------------- | ----- |
| 1 stepper | 47 |
| 3 stepper | 208 |
### LPC176x step rate benchmark
The following configuration sequence is used on the LPC176x:
@@ -423,7 +441,7 @@ Pico and Pico 2 boards.
(*) Note that the reported rp2040 ticks are relative to a 12Mhz
scheduling timer and do not correspond to its 200Mhz internal ARM
processing rate. It is expected that 5 scheduling ticks corresponds to
processing rate. It is expected that 3 scheduling ticks corresponds to
~42 ARM core cycles and 14 scheduling ticks corresponds to ~225 ARM
core cycles.
@@ -464,18 +482,23 @@ When the test completes, determine the difference between the clocks
reported in the two "uptime" response messages. The total number of
commands per second is then `100000 * mcu_frequency / clock_diff`.
Note that this test may saturate the USB/CPU capacity of a Raspberry
Pi. If running on a Raspberry Pi, Beaglebone, or similar host computer
then increase the delay (eg, `DELAY {clock + 20*freq} get_uptime`).
Where applicable, the benchmarks below are with console.py running on
a desktop class machine with the device connected via a high-speed
hub.
The USB tests may exceed the CPU capacity of a Raspberry Pi. If
running on a Raspberry Pi, Beaglebone, or similar host computer then
increase the delay (eg, `DELAY {clock + 20*freq} get_uptime`). Where
applicable, the benchmarks below are with console.py running on a
desktop class machine with the device connected via a super-speed hub.
The CAN bus tests may saturate the USB host controller of a Raspberry
Pi (when testing via a standard gs_usb USB to CAN bus adapter). Where
applicable, the CAN bus benchmarks below are with console.py running
on a desktop class machine with a USB to CAN bus adapter connected via
a super-speed USB hub.
| MCU | Rate | Build | Build compiler |
| ------------------- | ---- | -------- | ------------------- |
| stm32f042 (CAN) | 18K | c105adc8 | arm-none-eabi-gcc (GNU Tools 7-2018-q3-update) 7.3.1 |
| atmega2560 (serial) | 23K | b161a69e | avr-gcc (GCC) 4.8.1 |
| sam3x8e (serial) | 23K | b161a69e | arm-none-eabi-gcc (Fedora 7.1.0-5.fc27) 7.1.0 |
| rp2350 (CAN) | 59K | 17b8ce4c | arm-none-eabi-gcc (Fedora 14.1.0-1.fc40) 14.1.0 |
| at90usb1286 (USB) | 75K | 01d2183f | avr-gcc (GCC) 5.4.0 |
| ar100 (serial) | 138K | 08d037c6 | or1k-linux-musl-gcc 9.3.0 |
| samd21 (USB) | 223K | 01d2183f | arm-none-eabi-gcc (Fedora 7.4.0-1.fc30) 7.4.0 |

View File

@@ -194,7 +194,7 @@ Alternatively, one can use a
When using OpenOCD with the SAMC21, extra steps must be taken to first
put the chip into Cold Plugging mode if the board makes use of the
SWD pins for other purposes. If using OpenOCD on a Rasberry Pi, this
SWD pins for other purposes. If using OpenOCD on a Raspberry Pi, this
can be done by running the following commands before invoking OpenOCD.
```
SWCLK=25

View File

@@ -125,10 +125,14 @@ iface can0 can static
frequency. As a result, it is recommended to use a CAN bus frequency
of 1000000 when using "USB to CAN bus bridge mode".
Even at a CAN bus frequency of 1000000, there may not be sufficient
bandwidth to run a `SHAPER_CALIBRATE` test if both the XY steppers
and the accelerometer all communicate via a single "USB to CAN bus"
interface.
* It is only valid to use USB to CAN bridge mode if there is a
functioning CAN bus with at least one other node available (in
addition to the bridge node itself). Use a standard USB
configuration if the goal is to communicate only with the single USB
device. Using USB to CAN bridge mode without a fully functioning CAN
bus (including terminating resistors and an additional node) may
result in sporadic errors even when communicating with the bridge
node.
* A USB to CAN bridge board will not appear as a USB serial device, it
will not show up when running `ls /dev/serial/by-id`, and it can not

View File

@@ -118,6 +118,23 @@ necessary to increase the `txqueuelen` above the recommended value
of 128. However, as above, care should be taken when selecting a new
value to avoid excessive round-trip-time latency.
## Use `canbus_query.py` only to identify nodes never previously seen
It is only valid to use the
[`canbus_query.py` tool](CANBUS.md#finding-the-canbus_uuid-for-new-micro-controllers)
to identify micro-controllers that have never been previously
identified. Once all nodes on a bus are identified, record the
resulting uuids in the printer.cfg, and avoid running the tool
unnecessarily.
The tool is implemented using a low-level mechanism that can cause
nodes to internally observe bus errors. These internal errors may
result in communication interruptions and may result is some nodes
disconnecting from the bus.
It is not valid to use the tool to "ping" if a node is connected. Do
not run the tool during an active print.
## Obtaining candump logs
The CAN bus messages sent to and from the micro-controller are handled

View File

@@ -323,7 +323,7 @@ a month without updates.
Once the requirements are met, you need to:
1. update klipper-tranlations repository
1. update klipper-translations repository
[active_translations](https://github.com/Klipper3d/klipper-translations/blob/translations/active_translations)
2. Optional: add a manual-index.md file in klipper-translations repository's
`docs\locals\<lang>` folder to replace the language specific index.md (generated

View File

@@ -286,6 +286,11 @@ The following may also be useful:
during the `load_config()` or "connect event" phases. Use either
`raise config.error("my error")` or `raise printer.config_error("my
error")` to report the error.
* Do not store a reference to the `config` object in a class member
variable (nor in any similar location that may persist past initial
module loading). The `config` object is a reference to a "config
loading phase" class and it is not valid to invoke its methods after
the "config loading phase" has completed.
* Use the "pins" module to configure a pin on a micro-controller. This
is typically done with something similar to
`printer.lookup_object("pins").setup_pin("pwm",

View File

@@ -8,6 +8,34 @@ All dates in this document are approximate.
## Changes
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
been removed. These capabilities were deprecated on 20240313.
20250721: The `[pca9632]` and `[mcp4018]` modules no longer accept the
`scl_pin` and `sda_pin` options. Use `i2c_software_scl_pin` and
`i2c_software_sda_pin` instead.
20250428: The maximum `cycle_time` for pwm `[output_pin]`,
`[pwm_cycle_time]`, `[pwm_tool]`, and similar config sections is now 3
seconds (reduced from 5 seconds). The `maximum_mcu_duration` in
`[pwm_tool]` is now also 3 seconds.
20250418: The manual_stepper `STOP_ON_ENDSTOP` feature may now take
less time to complete. Previously, the command would wait the entire
time the move could possibly take even if the endstop triggered
earlier. Now, the command finishes shortly after the endstop trigger.
20250417: SPI devices using "software SPI" are now rate limited.
Previously, the `spi_speed` in the config was ignored and the
transmission speed was only limited by the processing speed of the
micro-controller. Now, speeds are limited by the `spi_speed` config
parameter (actual hardware speeds are likely to be lower than the
configured value due to software overhead).
20250411: Klipper v0.13.0 released.
20250308: The `AUTO` parameter of the
`AXIS_TWIST_COMPENSATION_CALIBRATE` command has been removed.
@@ -39,7 +67,7 @@ object were issued faster than the minimum scheduling time (typically
100ms) then actual updates could be queued far into the future. Now if
many updates are issued in rapid succession then it is possible that
only the latest request will be applied. If the previous behavior is
requried then consider adding explicit `G4` delay commands between
required then consider adding explicit `G4` delay commands between
updates.
20240912: Support for `maximum_mcu_duration` and `static_value`
@@ -112,7 +140,7 @@ carriage are exported as `printer.dual_carriage.carriage_0` and
`printer.dual_carriage.carriage_1`.
20230619: The `relative_reference_index` option has been deprecated
and superceded by the `zero_reference_position` option. Refer to the
and superseded by the `zero_reference_position` option. Refer to the
[Bed Mesh Documentation](./Bed_Mesh.md#the-deprecated-relative_reference_index)
for details on how to update the configuration. With this deprecation
the `RELATIVE_REFERENCE_INDEX` is no longer available as a parameter
@@ -346,7 +374,7 @@ endstop phases by running the ENDSTOP_PHASE_CALIBRATE command.
`gear_ratio` for their rotary steppers, and they may no longer specify
a `step_distance` parameter. See the
[config reference](Config_Reference.md#stepper) for the format of the
new gear_ratio paramter.
new gear_ratio parameter.
20201213: It is not valid to specify a Z "position_endstop" when using
"probe:z_virtual_endstop". An error will now be raised if a Z

View File

@@ -84,8 +84,9 @@ The printer section controls high level printer settings.
[printer]
kinematics:
# The type of printer in use. This option may be one of: cartesian,
# corexy, corexz, hybrid_corexy, hybrid_corexz, rotary_delta, delta,
# deltesian, polar, winch, or none. This parameter must be specified.
# corexy, corexz, hybrid_corexy, hybrid_corexz, generic_cartesian,
# rotary_delta, delta, deltesian, polar, winch, or none.
# This parameter must be specified.
max_velocity:
# Maximum velocity (in mm/s) of the toolhead (relative to the
# print). This value may be changed at runtime using the
@@ -125,8 +126,6 @@ max_accel:
# decelerate to zero at each corner. The value specified here may be
# changed at runtime using the SET_VELOCITY_LIMIT command. The
# default is 5mm/s.
#max_accel_to_decel:
# This parameter is deprecated and should no longer be used.
```
### [stepper]
@@ -712,6 +711,171 @@ anchor_z:
# These parameters must be provided.
```
### Generic Cartesian Kinematics
See [example-generic-cartesian.cfg](../config/example-generic-caretesian.cfg)
for an example generic Cartesian kinematics config file.
This printer kinematic class allows a user to define in a pretty flexible
manner an arbitrary Cartesian-style kinematics. In principle, the regular
cartesian, corexy, hybrid_corexy can be defined this way too. However,
more importantly, various otherwise unsupported kinematics such as
inverted hybrid_corexy or corexyuv can be defined using this kinematic.
Notably, the definition of a generic Cartesian kinematic deviates
significantly from the other kinematic types. It follows the following
convention: a user defines a set of carriages with certain range of motion
that can move independently from each other (they should move over the
Cartesian axes X, Y, and Z, hence the name of the kinematic) and
corresponding endstops that allow the firmware to determine the position
of carriages during homing, as well as a set of steppers that move those
carriages. The `[printer]` section must specify the kinematic and
other printer-level settings same as the regular Cartesian kinematic:
```
[printer]
kinematics: generic_cartesian
max_velocity:
max_accel:
#minimum_cruise_ratio:
#square_corner_velocity:
#max_z_velocity:
#max_z_accel:
```
Then a user must define the following three carriages: `[carriage x]`,
`[carriage y]`, and `[carriage z]`, e.g.
```
[carriage x]
endstop_pin:
# Endstop switch detection pin. If this endstop pin is on a
# different mcu than the stepper motor(s) moving this carriage,
# then it enables "multi-mcu homing". This parameter must be provided.
#position_min: 0
# Minimum valid distance (in mm) the user may command the carriage to
# move to. The default is 0mm.
position_endstop:
# Location of the endstop (in mm). This parameter must be provided.
position_max:
# Maximum valid distance (in mm) the user may command the stepper to
# move to. This parameter must be provided.
#homing_speed: 5.0
# Maximum velocity (in mm/s) of the carriage when homing. The default
# is 5mm/s.
#homing_retract_dist: 5.0
# Distance to backoff (in mm) before homing a second time during
# homing. Set this to zero to disable the second home. The default
# is 5mm.
#homing_retract_speed:
# Speed to use on the retract move after homing in case this should
# be different from the homing speed, which is the default for this
# parameter
#second_homing_speed:
# Velocity (in mm/s) of the carriage when performing the second home.
# The default is homing_speed/2.
#homing_positive_dir:
# If true, homing will cause the carriage to move in a positive
# direction (away from zero); if false, home towards zero. It is
# better to use the default than to specify this parameter. The
# default is true if position_endstop is near position_max and false
# if near position_min.
```
Afterwards, a user specifies the stepper motors that move these carriages,
for instance
```
[stepper my_stepper]
carriages:
# A string describing the carriages the stepper moves. All defined
# carriages can be specified here, as well as their linear combinations,
# e.g. x, x+y, y-0.5*z, x-z, etc. This parameter must be provided.
step_pin:
dir_pin:
enable_pin:
rotation_distance:
microsteps:
#full_steps_per_rotation: 200
#gear_ratio:
#step_pulse_duration:
```
See [stepper](#stepper) section for more information on the regular
stepper parameters. The `carriages` parameter defines how the stepper
affects the motion of the carriages. For example, `x+y` indicates that
the motion of the stepper in the positive direction by the distance `d`
moves the carriages `x` and `y` by the same distance `d` in the positive
direction, while `x-0.5*y` means the motion of the stepper in the positive
direction by the distance `d` moves the carriage `x` by the distance `d`
in the positive direction, but the carriage `y` will travel distance `d/2`
in the negative direction.
More than a single stepper motor can be defined to drive the same axis
or belt. For example, on a CoreXY AWD setups two motors driving the same
belt can be defined as
```
[carriage x]
endstop_pin: ...
...
[carriage y]
endstop_pin: ...
...
[stepper a0]
carriages: x-y
step_pin: ...
dir_pin: ...
enable_pin: ...
rotation_distance: ...
...
[stepper a1]
carriages: x-y
step_pin: ...
dir_pin: ...
enable_pin: ...
rotation_distance: ...
...
```
with `a0` and `a1` steppers having their own control pins, but
sharing the same `carriages` and corresponding endstops.
There are situations when a user wants to have more than one endstop
per axis. Examples of such configurations include Y axis driven by
two independent stepper motors with belts attached to both ends of the
X beam, with effectively two carriages on Y axis each having an
independent endstop, and multi-stepper Z axis with each stepper having
its own endstop (not to be confused with the configurations with
multiple Z motors but only a single endstop). These configurations
can be declared by specifying additional carriage(s) with their endstops:
```
[extra_carriage my_carriage]
primary_carriage:
# The name of the primary carriage this carriage corresponds to.
# It also effectively defines the axis the carriage moves over.
# This parameter must be provided.
endstop_pin:
# Endstop switch detection pin. This parameter must be provided.
```
and the corresponding stepper motors, for example:
```
[extra_carriage y1]
primary_carriage: y
endstop_pin: ...
[stepper sy1]
carriages: y1
...
```
Notably, an `[extra_carriage]` does not define parameters such as
`position_min`, `position_max`, and `position_endstop`, but instead
inherits them from the specified `primary_carriage`, thus sharing
the same range of motion with the primary carriage.
For the references on how to configure IDEX setups, see the
[dual carriage](#dual-carriage) section.
### None Kinematics
It is possible to define a special "none" kinematics to disable
@@ -2207,8 +2371,8 @@ for an example configuration.
### [dual_carriage]
Support for cartesian and hybrid_corexy/z printers with dual carriages
on a single axis. The carriage mode can be set via the
Support for cartesian, generic_cartesian and hybrid_corexy/z printers with
dual carriages on a single axis. The carriage mode can be set via the
SET_DUAL_CARRIAGE extended g-code command. For example,
"SET_DUAL_CARRIAGE CARRIAGE=1" command will activate the carriage defined
in this section (CARRIAGE=0 will return activation to the primary carriage).
@@ -2235,7 +2399,7 @@ typically be achieved with
or a similar command.
See [sample-idex.cfg](../config/sample-idex.cfg) for an example
configuration.
configuration with a regular Cartesian kinematic.
```
[dual_carriage]
@@ -2249,7 +2413,7 @@ axis:
# error. If safe_distance is not provided, it will be inferred from
# position_min and position_max for the dual and primary carriages. If set
# to 0 (or safe_distance is unset and position_min and position_max are
# identical for the primary and dual carraiges), the carriages proximity
# identical for the primary and dual carriages), the carriages proximity
# checks will be disabled.
#step_pin:
#dir_pin:
@@ -2263,6 +2427,83 @@ axis:
# See the "stepper" section for the definition of the above parameters.
```
For an example of dual carriage configuration with `generic_cartesian`
kinematic, see the following configuration
[sample](../config/example-generic-caretesian.cfg).
Please note that in this case the `[dual_carriage]` configuration deviates
from the configuration described above:
```
[dual_carriage my_dc_carriage]
primary_carriage:
# Defines the matching primary carriage of this dual carriage and
# the corresponding IDEX axis. Valid choices are x, y, z.
# This parameter must be provided.
#safe_distance:
# The minimum distance (in mm) to enforce between the dual and the primary
# carriages. If a G-Code command is executed that will bring the carriages
# closer than the specified limit, such a command will be rejected with an
# error. If safe_distance is not provided, it will be inferred from
# position_min and position_max for the dual and primary carriages. If set
# to 0 (or safe_distance is unset and position_min and position_max are
# identical for the primary and dual carriages), the carriages proximity
# checks will be disabled.
endstop_pin:
#position_min:
position_endstop:
position_max:
#homing_speed:
#homing_retract_dist:
#homing_retract_speed:
#second_homing_speed:
#homing_positive_dir:
...
```
Refer to [generic cartesian](#generic-cartesian) section for more information
on the regular `carriage` parameters.
Then a user must define one or more stepper motors moving the dual carriage
(and other carriages as appropriate), for instance
```
[carriage x]
...
[carriage y]
...
[dual_carriage u]
primary_carriage: x
...
[stepper dc_stepper]
carriages: u-y
...
```
`[dual_carriage]` requires special configuration for the input shaper.
In general, it is necessary to run input shaper calibration twice -
for the `dual_carriage` and its `primary_carriage` for the axis they
share. Then the input shaper can be configured as follows, assuming the
example above:
```
[input_shaper]
# Intentionally empty
[delayed_gcode init_shaper]
initial_duration: 0.1
gcode:
SET_DUAL_CARRIAGE CARRIAGE=u
SET_INPUT_SHAPER SHAPER_TYPE_X=<dual_carriage_x_shaper> SHAPER_FREQ_X=<dual_carriage_x_freq> SHAPER_TYPE_Y=<y_shaper> SHAPER_FREQ_Y=<y_freq>
SET_DUAL_CARRIAGE CARRIAGE=x
SET_INPUT_SHAPER SHAPER_TYPE_X=<primary_carriage_x_shaper> SHAPER_FREQ_X=<primary_carriage_x_freq> SHAPER_TYPE_Y=<y_shaper> SHAPER_FREQ_Y=<y_freq>
```
Note that `SHAPER_TYPE_Y` and `SHAPER_FREQ_Y` must be the same in both
commands in this case, since the same motors drive Y axis when either
of the `x` and `u` carriages are active.
It is worth noting that `generic_cartesian` kinematic can support two
dual carriages for X and Y axes. For reference, see for instance a
[sample](../config/sample-corexyuv.cfg) of CoreXYUV configuration.
### [extruder_stepper]
Support for additional steppers synchronized to the movement of an
@@ -2317,6 +2558,13 @@ printer kinematics.
# Endstop switch detection pin. If specified, then one may perform
# "homing moves" by adding a STOP_ON_ENDSTOP parameter to
# MANUAL_STEPPER movement commands.
#position_min:
#position_max:
# The minimum and maximum position the stepper can be commanded to
# move to. If specified then one may not command the stepper to move
# past the given position. Note that these limits do not prevent
# setting an arbitrary position with the `MANUAL_STEPPER
# SET_POSITION=x` command. The default is to not enforce a limit.
```
## Custom heaters and sensors
@@ -3226,11 +3474,6 @@ PCA9632 LED support. The PCA9632 is used on the FlashForge Dreamer.
#i2c_speed:
# See the "common I2C settings" section for a description of the
# above parameters.
#scl_pin:
#sda_pin:
# Alternatively, if the pca9632 is not connected to a hardware I2C
# bus, then one may specify the "clock" (scl_pin) and "data"
# (sda_pin) pins. The default is to use hardware I2C.
#color_order: RGBW
# Set the pixel order of the LED (using a string containing the
# letters R, G, B, W). The default is RGBW.
@@ -3533,6 +3776,7 @@ run_current:
#driver_PWM_FREQ: 1
#driver_PWM_GRAD: 4
#driver_PWM_AMPL: 128
#driver_FREEWHEEL: 0
#driver_SGT: 0
#driver_SEMIN: 0
#driver_SEUP: 0
@@ -3613,6 +3857,7 @@ run_current:
#driver_PWM_FREQ: 1
#driver_PWM_GRAD: 14
#driver_PWM_OFS: 36
#driver_FREEWHEEL: 0
# Set the given register during the configuration of the TMC2208
# chip. This may be used to set custom motor parameters. The
# defaults for each parameter are next to the parameter name in the
@@ -3662,6 +3907,7 @@ run_current:
#driver_PWM_FREQ: 1
#driver_PWM_GRAD: 14
#driver_PWM_OFS: 36
#driver_FREEWHEEL: 0
#driver_SGTHRS: 0
#driver_SEMIN: 0
#driver_SEUP: 0
@@ -4143,16 +4389,21 @@ prefix).
### [mcp4018]
Statically configured MCP4018 digipot connected via two gpio "bit
banging" pins (one may define any number of sections with an "mcp4018"
prefix).
Statically configured MCP4018 digipot connected via i2c (one may
define any number of sections with an "mcp4018" prefix).
```
[mcp4018 my_digipot]
scl_pin:
# The SCL "clock" pin. This parameter must be provided.
sda_pin:
# The SDA "data" pin. This parameter must be provided.
#i2c_address: 47
# The i2c address that the chip is using on the i2c bus. The default
# is 47.
#i2c_mcu:
#i2c_bus:
#i2c_software_scl_pin:
#i2c_software_sda_pin:
#i2c_speed:
# See the "common I2C settings" section for a description of the
# above parameters.
wiper:
# The value to statically set the given MCP4018 "wiper" to. This is
# typically set to a number between 0.0 and 1.0 with 1.0 being the
@@ -4895,6 +5146,65 @@ data_ready_pin:
# and 'analog_supply'. Default is 'internal'.
```
### [load_cell_probe]
Load Cell Probe. This combines the functionality of a [probe] and a [load_cell].
```
[load_cell_probe]
sensor_type:
# This must be one of the supported bulk ADC sensor types and support
# load cell endstops on the mcu.
#counts_per_gram:
#reference_tare_counts:
#sensor_orientation:
# These parameters must be configured before the probe will operate.
# See the [load_cell] section for further details.
#force_safety_limit: 2000
# The safe limit for probing force relative to the reference_tare_counts on
# the load_cell. The default is +/-2Kg.
#trigger_force: 75.0
# The force that the probe will trigger at. 75g is the default.
#drift_filter_cutoff_frequency: 0.8
# Enable optional continuous taring while homing & probing to reject drift.
# The value is a frequency, in Hz, below which drift will be ignored. This
# option requires the SciPy library. Default: None
#drift_filter_delay: 2
# The delay, or 'order', of the drift filter. This controls the number of
# samples required to make a trigger detection. Can be 1 or 2, the default
# is 2.
#buzz_filter_cutoff_frequency: 100.0
# The value is a frequency, in Hz, above which high frequency noise in the
# load cell will be igfiltered outnored. This option requires the SciPy
# library. Default: None
#buzz_filter_delay: 2
# The delay, or 'order', of the buzz filter. This controls the number of
# samples required to make a trigger detection. Can be 1 or 2, the default
# is 2.
#notch_filter_frequencies: 50, 60
# 1 or 2 frequencies, in Hz, to filter out of the load cell data. This is
# intended to reject power line noise. This option requires the SciPy
# library. Default: None
#notch_filter_quality: 2.0
# Controls how narrow the range of frequencies are that the notch filter
# removes. Larger numbers produce a narrower filter. Minimum value is 0.5 and
# maximum is 3.0. Default: 2.0
#tare_time:
# The rime in seconds used for taring the load_cell before each probe. The
# default value is: 4 / 60 = 0.066. This collects samples from 4 cycles of
# 60Hz mains power to cancel power line noise.
#z_offset:
#speed:
#samples:
#sample_retract_dist:
#lift_speed:
#samples_result:
#samples_tolerance:
#samples_tolerance_retries:
#activate_gcode:
#deactivate_gcode:
# See the "[probe]" section for a description of the above parameters.
```
## Board specific hardware support
### [sx1509]
@@ -5001,7 +5311,7 @@ chip: ADS1115
# scales all values read from the ADC. Options are: 6.144V, 4.096V, 2.048V,
# 1.024V, 0.512V, 0.256V
#adc_voltage: 3.3
# The suppy voltage for the device. This allows additional software scaling
# The supply voltage for the device. This allows additional software scaling
# for all values read from the ADC.
i2c_mcu: host
i2c_bus: i2c.1
@@ -5020,7 +5330,7 @@ sensor_pin: my_ads1x1x:AIN0
# A combination of the name of the ads1x1x chip and the pin. Possible
# pin values are AIN0, AIN1, AIN2 and AIN3 for single ended lines and
# DIFF01, DIFF03, DIFF13 and DIFF23 for differential between their
# correspoding lines. For example
# corresponding lines. For example
# DIFF03 measures the differential between line 0 and 3. Only specific
# combinations for the differentials are allowed.
```

View File

@@ -190,12 +190,13 @@ represent total number of steps per second on the micro-controller.
| SAM4E8E | 2500K | 1674K |
| SAMD51 | 3077K | 1885K |
| AR100 | 3529K | 2507K |
| STM32G431 | 3617K | 2452K |
| STM32F407 | 3652K | 2459K |
| STM32F446 | 3913K | 2634K |
| RP2040 | 4000K | 2571K |
| RP2350 | 4167K | 2663K |
| SAME70 | 6667K | 4737K |
| STM32H743 | 9091K | 6061K |
| STM32H723 | 7429K | 8619K |
If unsure of the micro-controller on a particular board, find the
appropriate [config file](../config/), and look for the

View File

@@ -174,8 +174,10 @@ The following commands are available when the
[ADAPTIVE_MARGIN=<value>]`: This command probes the bed using generated points
specified by the parameters in the config. After probing, a mesh is generated
and z-movement is adjusted according to the mesh.
The mesh is immediately active after successful completion of `BED_MESH_CALIBRATE`.
The mesh will be saved into a profile specified by the `PROFILE` parameter,
or `default` if unspecified.
or `default` if unspecified. If ADAPTIVE=1 is specified then the profile
name will begin with `adaptive-` and should not be saved for reuse.
See the PROBE command for details on the optional probe parameters. If
METHOD=manual is specified then the manual probing tool is activated - see the
MANUAL_PROBE command above for details on the additional commands available
@@ -341,15 +343,18 @@ The following command is available when the
enabled.
#### SET_DUAL_CARRIAGE
`SET_DUAL_CARRIAGE CARRIAGE=[0|1] [MODE=[PRIMARY|COPY|MIRROR]]`:
`SET_DUAL_CARRIAGE CARRIAGE=<carriage> [MODE=[PRIMARY|COPY|MIRROR]]`:
This command will change the mode of the specified carriage.
If no `MODE` is provided it defaults to `PRIMARY`. Setting the mode
to `PRIMARY` deactivates the other carriage and makes the specified
carriage execute subsequent G-Code commands as-is. `COPY` and `MIRROR`
modes are supported only for `CARRIAGE=1`. When set to either of these
modes, carriage 1 will then track the subsequent moves of the carriage 0
and either copy relative movements of it (in `COPY` mode) or execute them
in the opposite (mirror) direction (in `MIRROR` mode).
If no `MODE` is provided it defaults to `PRIMARY`. `<carriage>` must
reference a defined primary or dual carriage for `generic_cartesian`
kinematics or be 0 (for primary carriage) or 1 (for dual carriage)
for all other kinematics supporting IDEX. Setting the mode to `PRIMARY`
deactivates the other carriage and makes the specified carriage execute
subsequent G-Code commands as-is. `COPY` and `MIRROR` modes are supported
only for dual carriages. When set to either of these modes, dual carriage
will then track the subsequent moves of its primary carriage and either
copy relative movements of it (in `COPY` mode) or execute them in the
opposite (mirror) direction (in `MIRROR` mode).
#### SAVE_DUAL_CARRIAGE_STATE
`SAVE_DUAL_CARRIAGE_STATE [NAME=<state_name>]`: Save the current positions
@@ -367,7 +372,7 @@ restored and "MOVE_SPEED" is specified, then the toolhead moves will be
performed with the given speed (in mm/s); otherwise the toolhead move will
use the rail homing speed. Note that the carriages restore their positions
only over their own axis, which may be necessary to correctly restore COPY
and MIRROR mode of the dual carraige.
and MIRROR mode of the dual carriage.
### [endstop_phase]
@@ -715,6 +720,46 @@ is specified then the toolhead move will be performed with the given
speed (in mm/s); otherwise the toolhead move will use the restored
g-code speed.
### [generic_cartesian]
The commands in this section become automatically available when
`kinematics: generic_cartesian` is specified as the printer kinematics.
#### SET_STEPPER_CARRIAGES
`SET_STEPPER_CARRIAGES STEPPER=<stepper_name> CARRIAGES=<carriages>
[DISABLE_CHECKS=[0|1]]`: Set or update the stepper carriages.
`<stepper_name>` must reference an existing stepper defined in `printer.cfg`,
and `<carriages>` describes the carriages the stepper moves. See
[Generic Cartesian Kinematics](Config_Reference.md#generic-cartesian-kinematics)
for a more detailed overview of the `carriages` parameter in the
stepper configuration section. Note that it is only possible
to change the coefficients or signs of the carriages with this
command, but a user cannot add or remove the carriages that the stepper
controls.
`SET_STEPPER_CARRIAGES` is an advanced tool, and the user is advised
to exercise an extreme caution using it, since specifying incorrect
configuration may physically damage the printer.
Note that `SET_STEPPER_CARRIAGES` performs certain internal validations
of the new printer kinematics after the change. Keep in mind that if it
detects an issue, it may leave printer kinematics in an invalid state.
This means that if `SET_STEPPER_CARRIAGES` reports an error, it is unsafe
to issue other GCode commands, and the user must inspect the error message
and either fix the problem, or manually restore the previous stepper(s)
configuration.
Since `SET_STEPPER_CARRIAGES` can update a configuration of a single
stepper at a time, some sequences of changes can lead to invalid
intermediate kinematic configurations, even if the final configuration
is valid. In such cases a user can pass `DISABLE_CHECKS=1` parameters to
all but the last command to disable intermediate checks. For example,
if `stepper a` and `stepper b` initially have `x-y` and `x+y` carriages
correspondingly, then the following sequence of commands will let a user
effectively swap the carriage controls:
`SET_STEPPER_CARRIAGES STEPPER=a CARRIAGES=x+y DISABLE_CHECKS=1`
and `SET_STEPPER_CARRIAGES STEPPER=b CARRIAGES=x-y`, while
still validating the final kinematics state.
### [hall_filament_width_sensor]
The following commands are available when the
@@ -851,9 +896,9 @@ uncalibrated load cells.
`LOAD_CELL_CALIBRATE [LOAD_CELL=<config_name>]`: Start the guided calibration
utility. Calibration is a 3 step process:
1. First you remove all load from the load cell and run the `TARE` command
1. Next you apply a known load to the load cell and run the
2. Next you apply a known load to the load cell and run the
`CALIBRATE GRAMS=nnn` command
1. Finally use the `ACCEPT` command to save the results
3. Finally use the `ACCEPT` command to save the results
You can cancel the calibration process at any time with `ABORT`.
@@ -861,7 +906,8 @@ You can cancel the calibration process at any time with `ABORT`.
`LOAD_CELL_TARE [LOAD_CELL=<config_name>]`: This works just like the tare button
on digital scale. It sets the current raw reading of the load cell to be the
zero point reference value. The response is the percentage of the sensors range
that was read and the raw value in counts.
that was read and the raw value in counts. If the load cell is calibrated a
force in grams is also reported.
### LOAD_CELL_READ load_cell="name"
`LOAD_CELL_READ [LOAD_CELL=<config_name>]`:
@@ -869,6 +915,39 @@ This command takes a reading from the load cell. The response is the percentage
of the sensors range that was read and the raw value in counts. If the load cell
is calibrated a force in grams is also reported.
### [load_cell_probe]
The following commands are enabled if a
[load_cell config section](Config_Reference.md#load_cell_probe) has been
enabled.
### LOAD_CELL_TEST_TAP
`LOAD_CELL_TEST_TAP [TAPS=<taps>] [TIMEOUT=<timeout>]`: Run a testing routine
that reports taps on the load cell. The toolhead will not move but the load cell
probe will sense taps just as if it was probing. This can be used as a
sanity check to make sure that the probe works. This tool replaces
QUERY_ENDSTOPS and QUERY_PROBE for load cell probes.
- `TAPS`: the number of taps the tool expects
- `TIMEOOUT`: the time, in seconds, that the tool waits for each tab before
aborting.
### Load Cell Command Extensions
Commands that perform probes, such as [`PROBE`](#probe),
[`PROBE_ACCURACY`](#probe_accuracy),
[`BED_MESH_CALIBRATE`](#bed_mesh_calibrate) etc. will accept additional
parameters if a `[load_cell_probe]` is defined. The parameters override the
corresponding settings from the
[`[load_cell_probe]`](./Config_Reference.md#load_cell_probe) configuration:
- `FORCE_SAFETY_LIMIT=<grams>`
- `TRIGGER_FORCE=<grams>`
- `DRIFT_FILTER_CUTOFF_FREQUENCY=<frequency_hz>`
- `DRIFT_FILTER_DELAY=<1|2>`
- `BUZZ_FILTER_CUTOFF_FREQUENCY=<frequency_hz>`
- `BUZZ_FILTER_DELAY=<1|2>`
- `NOTCH_FILTER_FREQUENCIES=<list of frequency_hz>`
- `NOTCH_FILTER_QUALITY=<quality>`
- `TARE_TIME=<seconds>`
### [manual_probe]
The manual_probe module is automatically loaded.
@@ -925,6 +1004,25 @@ scheduled to run after the stepper move completes, however if a manual
stepper move uses SYNC=0 then future G-Code movement commands may run
in parallel with the stepper movement.
`MANUAL_STEPPER STEPPER=config_name GCODE_AXIS=[A-Z]
[LIMIT_VELOCITY=<velocity>] [LIMIT_ACCEL=<accel>]
[INSTANTANEOUS_CORNER_VELOCITY=<velocity>]`: If the `GCODE_AXIS`
parameter is specified then it configures the stepper motor as an
extra axis on `G1` move commands. For example, if one were to issue a
`MANUAL_STEPPER ... GCODE_AXIS=R` command then one could issue
commands like `G1 X10 Y20 R30` to move the stepper motor. The
resulting moves will occur synchronously with the associated toolhead
xyz movements. If the motor is associated with a `GCODE_AXIS` then
one may no longer issue movements using the above `MANUAL_STEPPER`
command - one may unregister the stepper with a `MANUAL_STEPPER
... GCODE_AXIS=` command to resume manual control of the motor. The
`LIMIT_VELOCITY` and `LIMIT_ACCEL` parameters allow one to reduce the
speed of `G1` moves if those moves would result in a velocity or
acceleration above the specified limits. The
`INSTANTANEOUS_CORNER_VELOCITY` specifies the maximum instantaneous
velocity change (in mm/s) of the motor during the junction of two
moves (the default is 1mm/s).
### [mcp4018]
The following command is available when a

View File

@@ -139,6 +139,25 @@ print board is not connected with USB to the host, due to some boards
being able to feed power back to the board and stopping a flash from
occurring.
Please note, that most print boards that use SD cards for flash will
implement some kind of flash loop protection for when the sd card is left
in place. There are two common methods:
Filename Change Required (usually "stock" print boards):
These boards require the firmware file to have a different name each
time you flash (for example, firmware1.bin, firmware2.bin, etc.).
If you reuse the same filename, the board may ignore it and not update.
Automatic File Renaming (usually aftermarket print boards:
Other boards allow using the same filename, commonly firmware.bin,
but after flashing, the board renames the file to firmware.cur.
This helps indicate the firmware was successfully flashed and prevents
it from flashing again on the next startup.
Before flashing, make sure to check which behavior your board follows.
For common micro-controllers using Atmega chips, for example the 2560,
the code can be flashed with something
similar to:

View File

@@ -27,26 +27,28 @@ $ LOAD_CELL_DIAGNOSTIC
```
Things you can check with this data:
* The configured sample rate of the sensor should be close to the 'Measured
samples per second' value. If it is not you may have a configuration or wiring
issue.
samples per second' value. If it is not you may have a configuration or wiring
issue.
* 'Saturated samples' should be 0. If you have saturated samples it means the
load sell is seeing more force than it can measure.
load sell is seeing more force than it can measure.
* 'Unique values' should be a large percentage of the 'Samples
Collected' value. If 'Unique values' is 1 it is very likely a wiring issue.
Collected' value. If 'Unique values' is 1 it is very likely a wiring issue.
* Tap or push on the sensor while `LOAD_CELL_DIAGNOSTIC` runs. If
things are working correctly ths should increase the 'Sample range'.
things are working correctly this should increase the 'Sample range'.
## Calibrating a Load Cell
Load cells are calibrated using the `LOAD_CELL_CALIBRATE` command. This is an
interactive calibration utility that walks you though a 3 step process:
1. First use the `TARE` command to establish the zero force value. This is the
`reference_tare_counts` config value.
`reference_tare_counts` config value.
2. Next you apply a known load or force to the load cell and run the
`CALIBRATE GRAMS=nnn` command. From this the `counts_per_gram` value is
calculated. See [the next section](#applying-a-known-force-or-load) for some
suggestions on how to do this.
`CALIBRATE GRAMS=nnn` command. From this the `counts_per_gram` value is
calculated. See [the next section](#applying-a-known-force-or-load) for some
suggestions on how to do this.
3. Finally, use the `ACCEPT` command to save the results.
You can cancel the calibration process at any time with `ABORT`.
@@ -73,11 +75,13 @@ printer bed or toolhead can tolerate without damage. Do try to use at least 1Kg
of force, most printers should tolerate this without issue.
When calibrating make careful note of the values reported:
```
$ CALIBRATE GRAMS=555
// Calibration value: -2.78% (-59803108), Counts/gram: 73039.78739,
Total capacity: +/- 29.14Kg
```
The `Total capacity` should be close to the theoretical rating of the load cell
based on the sensor's capacity. If it is much larger you could have used a
higher gain setting in the sensor or a more sensitive load cell. This isn't as
@@ -120,3 +124,366 @@ a macro:
```
{% set tare_counts = printer.load_cell.tare_counts %}
```
# Load Cell Probes
## Related Documentation
* [load_cell_probe Config Reference](Config_Reference.md#load_cell_probe)
* [load_cell_probe G-Code Commands](G-Codes.md#load_cell_probe)
* [load_cell_probe Statuc Reference](Status_Reference.md#load_cell_probe)
## Load Cell Probe Safety
Because load cells are a direct nozzle contact probe there is a risk of
damage to your printer if too much force is used. The load cell probing system
includes a number of safety checks that try to keep your machine safe from
excessive force to the toolhead. It's important to understand what they are
and how they work as you can defeat most of them with poorly chosen config
values.
#### Calibration Check
Every time a homing move starts, load_cell_probe checks
that the load_cell is calibrated. If not it will stop the move with an error:
`!! Load Cell not calibrated`.
#### `counts_per_gram`
This setting is used to convert raw sensor counts into grams. All the safety
limits are in gram units for your convenience. If the `counts_per_gram`
setting is not accurate you can easily exceed the safe force on the toolhead.
You should never guess this value. Use `LOAD_CELL_CALIBRATE` to find your load
cells actual `counts_per_gram`.
#### `trigger_force`
This is the force in grams that triggers the endstop to halt the homing move.
When a homing move starts the endstop tares itself with the current reading
from the load cell. `trigger_force` is measured from that tare value. There is
always some overshoot of this value when the probe collides with the bed,
so be conservative. e.g. a setting of 100g could result in 350g of peak force
before the toolhead stops. This overshoot will increase with faster probing
`speed`, a low ADC sample rate or [multi MCU homing](Multi_MCU_Homing.md).
#### `reference_tare_counts`
This is the baseline tare value that is set by `LOAD_CELL_CALIBRATE`.
This value works with `force_safety_limit` to limit the maximum force on the
toolhead.
#### `force_safety_limit`
This is the maximum absolute force, relative to `reference_tare_counts`,
that the probe will allow while homing or probing. If the MCU sees this
force exceeded it will shut down the printer with the error `!! Load cell
endstop: too much force!`. There are a number of ways this can be triggered:
The first risk this protects against is picking too large of a value for
`drift_filter_cutoff_frequency`. This can cause the drift filter to filter out
a probe event and continue the homing move. If this happens the
`force_safety_limit` acts as a backup protection.
The second problem is probing repeatedly in one place. Klipper does not retract
the probe when doing a single `PROBE` command. This can result
in force applied to the toolhead at the end of a probing cycle. Because
external forces can vary greatly between probing locations,
`load_cell_probe` performs a tare before beginning each probe. If you repeat
the `PROBE` command, load_cell_probe will tare the endstop at the current force.
Multiple cycles of this will result in ever-increasing force on the toolhead.
`force_safety_limit` stops this cycle from running out of control.
Another way this run-away can happen is damage to a strain gauge. If the metal
part is permanently bent it will change the `reference_tare_counts` of the
device. This puts the starting tare value much closer to the limit making it
more likely to be violated. You want to be notified if this is happening
because your hardware has been permanently damaged.
The final way this can be triggered is due to temperature changes. If your
strain gauges are heated their `reference_tare_counts` may be very different
at ambient temperature vs operating temperature. In this case you may need
to increase the `force_safety_limit` to allow for thermal changes.
#### Load Cell Endstop Watchdog Task
When homing the load_cell_endstop starts a task on the MCU to trac
measurements arriving from the sensor. If the sensor fails to send
measurements for 2 sample periods the watchdog will shut down the printer
with an error `!! LoadCell Endstop timed out waiting on ADC data`.
If this happens, the most likely cause is a fault from the ADC. Inadequate
grounding of your printer can be the root cause. The frame, power supply
case and pint bed should all be connected to ground. You may need to ground
the frame in multiple places. Anodized aluminum extrusions do not conduct
electricity well. You might need to sand the area where the grounding wire
is attached to make good electrical contact.
## Load Cell Probe Setup
This section covers the process for commissioning a load cell probe.
### Verify the Load Cell First
A `[load_cell_probe]` is also a `[load_cell]` and G-code commands related to
`[load_cell]` work with `[load_cell_probe]`. Before attempting to use a load
cell probe, follow the directions for
[calibrating the load cell](Load_Cell.md#calibrating-a-load-cell) with
`CALIBRATE_LOAD_CELL` and checking its operation with `LOAD_CELL_DIAGNOSTIC`.
### Verify Probe Operation With LOAD_CELL_TEST_TAP
Use the command `LOAD_CELL_TEST_TAP` to test the operation of the load cell
probe before actually trying to probe with it. This command detects taps,
just like the PROBE command, but it does not move the z axis. By default, it
listens for 3 taps before ending the test. You have 30 seconds to do each
tap, if no taps are detected the command will time out.
If this test fails, check your configuration and `LOAD_CELL_DIAGNOSTIC`
carefully to look for issues.
Load cell probes don't support the `QUERY_ENDSTOPS` or `QUERY_PROBE`
commands. Use `LOAD_CELL_TEST_TAP` for testing functionality before probing.
### Homing Macros
Load cell probe is not an endstop and doesn't support `endstop:
prove:z_virtual_endstop`. For the time being you'll need to configure your z
axis with an MCU pin as its endstop. You won't actually be using the pin but
for the time being you have to configure something.
To home the axis with just the probe you need to set up a custom homing
macro. This requires setting up
[homing_override](Config_Reference.md#homing_override).
Here is a simple macro that can accomplish this. Note that the
`_HOME_Z_FROM_LAST_PROBE` macro has to be separate because of the way macros
work. The sub-call is needed so that the `_HOME_Z_FROM_LAST_PROBE` macro can
see the result of the probe in `printer.probe.last_z_result`.
```gcode
[gcode_macro _HOME_Z_FROM_LAST_PROBE]
gcode:
{% set z_probed = printer.probe.last_z_result %}
{% set z_position = printer.toolhead.position[2] %}
{% set z_actual = z_position - z_probed %}
SET_KINEMATIC_POSITION Z={z_actual}
[gcode_macro _HOME_Z]
gcode:
SET_GCODE_OFFSET Z=0 # load cell probes dont need a Z offset
# position toolhead for homing Z, edit for your printers size
#G90 # absolute move
#G1 Y50 X50 F{5 * 60} # move to X/Y position for homing
# soft home the z axis to its limit so it can be moved:
SET_KINEMATIC_POSITION Z={printer.toolhead.axis_maximum[2]}
# Fast approach and tap
PROBE PROBE_SPEED={5 * 60} # override the speed for faster homing
_HOME_Z_FROM_LAST_PROBE
# lift z to 2mm
G91 # relative move
G1 Z2 F{5 * 60}
# probe at standard speed
PROBE
_HOME_Z_FROM_LAST_PROBE
# lift z to 10mm for clearance
G91 # relative move
G1 Z10 F{5 * 60}
```
### Suggested Probing Temperature
Currently, we suggest keeping the nozzle temperature below the level that causes
the filament to ooze while homing and probing. 140C is a good starting
point. This temperature is also low enough not to scar PEI build surfaces.
Fouling of the nozzle and the print bed due to oozing filament is the #1 source
of probing error with the load cell probe. Klipper does not yet have a universal
way to detect poor quality taps due to filament ooze. The existing code may
decide that a tap is valid when it is of poor quality. Classifying these poor
quality taps is an area of active research.
Klipper also lacks support for re-locating a probe point if the
location has become fouled by filament ooze. Modules like `quad_gantry_level`
will repeatedly probe the same coordinates even if a probe previously failed
there.
Give the above it is strongly suggested not to probe at printing temperatures.
### Hot Nozzle Protection
The Voron project has a great macro for protecting your print surface from the
hot nozzle. See [Voron Tap's
`activate_gcode`](https://github.com/VoronDesign/Voron-Tap/blob/main/config/tap_klipper_instructions.md)
It is highly suggested to add something like this to your config.
### Nozzle Cleaning
Before probing the nozzle should be clean. You could do this manually before
every print. You can also implement a nozzle scrubber and automate the process.
Here is a suggested sequence:
1. Wait for the nozzle to heat up to probing temp (e.g. `M109 S140`)
1. Home the machine (`G28`)
1. Scrub the nozzle on a brush
1. Heat soak the print bed
1. Perform probing tasks: QGL, bed mesh etc.
### Temperature Compensation for Nozzle Growth
If you are probing at a safe temperature, the nozzle will expand after
heating to printing temperatures. This will cause the nozzle to get longer
and closer to the print surface. You can compensate for this with
[[z_thermal_adjust]](Config_Reference.md#z_thermal_adjust). This adjustment will
work across a range of printing
temperatures from PLA to PC.
#### Calculating the `temp_coeff` for `[z_thermal_adjust]`
The easiest way to do this is to measure at 2 different temperatures.
Ideally these should be the upper and lower limits of the printing
temperature range. E.g. 180C and 290C. You can perform a `PROBE_ACCURACY` at
both temperatures and then calculate the difference of the `average z` at both.
The adjustment value is the change in nozzle length divided by the change in
temperature. e.g.
```
temp_coeff = -0.05 / (290 - 180) = -0.00045455
```
The expected result is a negative number. Positive values for `temp_coeff` move
the nozzle closer to the bed and negative values move it further away.
Expect to have to move the nozzle further away as it gets longer when hot.
#### Configure `[z_thermal_adjust]`
Set up z_thermal_adjust to reference the `extruder` as the source of temperature
data. E.g.:
```
[z_thermal_adjust nozzle]
temp_coeff=-0.00045455
sensor_type: temperature_combined
sensor_list: extruder
combination_method: max
min_temp: 0
max_temp: 400
max_z_adjustment: 0.1
```
## Continuous Tare Filters for Toolhead Load Cells
Klipper implements a configurable IIR filter on the MCU to provide continuous
tareing of the load cell while probing. Continuous taring means the 0 value
moves with drift caused by external factors like bowden tubes and thermal
changes. This is aimed at toolhead sensors and moving beds that experience lots
of external forces that change while probing.
### Installing SciPy
The filtering code uses the excellent [SciPy](https://scipy.org/) library to
compute the filter coefficients based on the values your enter into the config.
Pre-compiled SciPi builds are available for Python 3 on 32 bit Raspberry Pi
systems. 32 bit + Python 3 is strongly recommended because it will streamline
your installation experience. It does work with Python 2 but installation can
take 30+ minutes and require installing additional tools.
```bash
~/klippy-env/bin/pip install scipy
```
### Filter Workbench
The filter parameters should be selected based on drift seen on the printer
during normal operation. A Jupyter notebook is provided in scripts,
[filter_workbench.ipynb](../scripts/filter_workbench.ipynb), to perform a
detailed investigation with real captured data and FFTs.
### Filtering Suggestions
For those just trying to get a filter working follow these suggestions:
* The only essential option is `drift_filter_cutoff_frequency`. A conservative
starting value is `0.5`Hz. Prusa shipped the MK4 with a setting of `0.8`Hz and
the XL with `11.2`Hz. This is probably a safe range to experiment with. This
value should be increased only until normal drift due to bowden tube force is
eliminated. Setting this value too high will result in slow triggering and
excess force going through the toolhead.
* Keep `trigger_force` low. The default is `75`g. The drift filter keeps the
internal grams value very close to 0 so a large trigger force is not needed.
* Keep `force_safety_limit` to a conservative value. The default value is 2Kg
and should keep your toolhead safe while experimenting. If you hit this limit
the `drift_filter_cutoff_frequency` value may be too high.
## Suggestions for Load Cell Tool Boards
This section covers suggestions for those developing toolhead boards that want
to support [load_cell_probe]
### ADC Sensor Selection & Board Development Hints
Ideally a sensor would meet these criteria:
* At least 24 bits wide
* Use SPI communications
* Has a pin can be used to indicate sample ready without SPI communications.
This is often called the "data ready" or "DRDY" pin. Checking a pin is much
faster than running an SPI query.
* Has a programmable gain amplifier gain setting of 128. This should eliminate
the need for a separate amplifier.
* Indicates via SPI if the sensor has been reset. Detecting resets avoids
timing errors in homing and using noisy data at startup. It can also help
users
track down wiring and grounding issues.
* A selectable sample rate between 350Hz and 2Khz. Very high sample rates don't
turn out to be beneficial in our 3D printers because they produce so much
noise
when moving fast. Sample rates below 250Hz will require slower probing speeds.
They also increase the force on the toolhead due to longer delays between
measurements. E.g. a 500Hz sensor moving at 5mm/s has the same safety factor
as
a 100Hz sensor moving at only 1mm/s.
* If designing for under-bed applications, and you want to sense multiple load
cells, use a chip that can sample all of its inputs simultaneously. Multiplex
ADCs that require switching channels have a settling of several samples after
each channel switch making them unsuitable for probing applications.
Implementing support for a new sensor chip is not particularly difficult with
Klipper's `bulk_sensor` and `load_cell_endstop` infrastructure.
### 5V Power Filtering
It is strongly suggested to use larger capacitors than specified by the ADC chip
manufacturer. ADC chips are usually targeted at low noise environments, like
battery powered devices. Sensor manufacturers suggested application notes
generally assume a quiet power supply. Treat their suggested capacitor values as
minimums.
3D printers put huge amounts of noise onto the 5V bus and this can ruin the
sensor's accuracy. Test the sensor on the board with a typical 3D printer power
supply and active stepper drivers before deciding on smoothing capacitor sizes.
### Grounding & Ground Planes
Analog ADC chips contain components that are very vulnerable to noise and
ESD. A large ground plane on the first board layer under the chip can help with
noise. Keep the chip away from power sections and DC to DC converters. The board
should have proper grounding back to the DC supply.
### HX711 and HX717 Notes
This sensor is popular because of its low cost and availability in the
supply chain. However, this is a sensor with some drawbacks:
* The HX71x sensors use bit-bang communication which has a high overhead on the
MCU. Using a sensor that communicates via SPI would save resources on the tool
board's CPU.
* The HX71x lacks a way to communicate reset events to the MCU. Klipper detects
resets with a timing heuristic but this is not ideal. Resets indicate a
problem with wiring or grounding.
* For probing applications the HX717 version is strongly preferred because
of its higher sample rate (320 vs 80). Probing speed on the HX711 should be
limited to less than 2mm/s.
* The sample rate on the HX71x cannot be set from klipper's config. If you have
the 10SPS version of the sensor (which is widely distributed) it needs to
be physically re-wired to run at 80SPS.

View File

@@ -152,7 +152,7 @@ Recommended connection scheme for I2C on the Raspberry Pi:
| SDA | 03 | GPIO02 (SDA1) |
| SCL | 05 | GPIO03 (SCL1) |
The RPi has buit-in 1.8K pull-ups on both SCL and SDA.
The RPi has built-in 1.8K pull-ups on both SCL and SDA.
![MPU-9250 connected to Pi](img/mpu9250-PI-fritzing.png)

View File

@@ -31,7 +31,7 @@ AD do not include the flats on the corners that some test objects provide.
## Configure your skew
Make sure `[skew_correction]` is in printer.cfg. You may now use the `SET_SKEW`
gcode to configure skew_correcton. For example, if your measured lengths
gcode to configure skew_correction. For example, if your measured lengths
along XY are as follows:
```

View File

@@ -121,5 +121,5 @@ M104 S0
before the macro call. Also note that SuperSlicer has a
"custom gcode only" button option, which achieves the same outcome.
An example of a START_PRINT macro using these paramaters can
An example of a START_PRINT macro using these parameters can
be found in config/sample-macros.cfg

View File

@@ -242,6 +242,8 @@ The following information is available in the `gcode_move` object
The following information is available in the
[hall_filament_width_sensor](Config_Reference.md#hall_filament_width_sensor)
object:
- all items from
[filament_switch_sensor](Status_Reference.md#filament_switch_sensor)
- `is_active`: Returns True if the sensor is currently active.
- `Diameter`: The last reading from the sensor in mm.
- `Raw`: The last raw ADC reading from the sensor.
@@ -289,6 +291,9 @@ is always available):
- `printing_time`: The amount of time (in seconds) the printer has
been in the "Printing" state (as tracked by the idle_timeout
module).
- `idle_timeout`: The current 'timeout' (in seconds)
to wait for the gcode to be triggered.
(as set by [SET_IDLE_TIMEOUT](G-Codes.md#set_idle_timeout))
## led
@@ -298,7 +303,7 @@ The following information is available for each `[led led_name]`,
- `color_data`: A list of color lists containing the RGBW values for a
led in the chain. Each value is represented as a float from 0.0 to
1.0. Each color list contains 4 items (red, green, blue, white) even
if the underyling LED supports fewer color channels. For example,
if the underlying LED supports fewer color channels. For example,
the blue value (3rd item in color list) of the second neopixel in a
chain could be accessed at
`printer["neopixel <config_name>"].color_data[1][2]`.
@@ -314,6 +319,15 @@ The following information is available for each `[load_cell name]`:
- 'min_force_g': The minimum force in grams, over the last polling period.
- 'max_force_g': The maximum force in grams, over the last polling period.
## load_cell_probe
The following information is available for `[load_cell_probe]`:
- all items from [load_cell](Status_Reference.md#load_cell)
- all items from [probe](Status_Reference.md#probe)
- 'endstop_tare_counts': the load cell probe keeps a tare value independent of
the load cell. This re-set at the start of each probe.
- 'last_trigger_time': timestamp of the last homing trigger
## manual_probe
The following information is available in the
@@ -568,6 +582,12 @@ on a cartesian, hybrid_corexy or hybrid_corexz robot
- `carriage_1`: The mode of the carriage 1. Possible values are:
"INACTIVE", "PRIMARY", "COPY", and "MIRROR".
On a `generic_cartesian` kinematic, the following information is
available in `dual_carriage`:
- `carriages["<carriage>"]`: The mode of the carriage `<carriage>`. Possible
values are "INACTIVE" and "PRIMARY" for the primary carriage and "INACTIVE",
"PRIMARY", "COPY", and "MIRROR" for the dual carriage.
## virtual_sdcard
The following information is available in the

View File

@@ -8,14 +8,12 @@ title: Welcome
The Klipper firmware controls 3d-Printers. It combines the power of a
general purpose computer with one or more micro-controllers. See the
[features document](https://www.klipper3d.org/Features.html) for more
information on why you should use the Klipper software.
[features document](Features.md) for more information on why you
should use the Klipper software.
Start by [installing Klipper software](https://www.klipper3d.org/Installation.html).
Start by [installing Klipper software](Installation.md).
Klipper software is Free Software. Read the
[documentation](https://www.klipper3d.org/Overview.html), see the
[license](COPYING), or
[documentation](Overview.md), see the [license](../COPYING), or
[download](https://github.com/Klipper3d/Klipper) the software. We
depend on the generous support from our
[sponsors](https://www.klipper3d.org/Sponsors.html).
depend on the generous support from our [sponsors](Sponsors.md).

View File

@@ -17,16 +17,16 @@ COMPILE_ARGS = ("-Wall -g -O2 -shared -fPIC"
" -o %s %s")
SSE_FLAGS = "-mfpmath=sse -msse2"
SOURCE_FILES = [
'pyhelper.c', 'serialqueue.c', 'stepcompress.c', 'itersolve.c', 'trapq.c',
'pollreactor.c', 'msgblock.c', 'trdispatch.c',
'pyhelper.c', 'serialqueue.c', 'stepcompress.c', 'steppersync.c',
'itersolve.c', 'trapq.c', 'pollreactor.c', 'msgblock.c', 'trdispatch.c',
'kin_cartesian.c', 'kin_corexy.c', 'kin_corexz.c', 'kin_delta.c',
'kin_deltesian.c', 'kin_polar.c', 'kin_rotary_delta.c', 'kin_winch.c',
'kin_extruder.c', 'kin_shaper.c', 'kin_idex.c',
'kin_extruder.c', 'kin_shaper.c', 'kin_idex.c', 'kin_generic.c'
]
DEST_LIB = "c_helper.so"
OTHER_FILES = [
'list.h', 'serialqueue.h', 'stepcompress.h', 'itersolve.h', 'pyhelper.h',
'trapq.h', 'pollreactor.h', 'msgblock.h'
'list.h', 'serialqueue.h', 'stepcompress.h', 'steppersync.h',
'itersolve.h', 'pyhelper.h', 'trapq.h', 'pollreactor.h', 'msgblock.h'
]
defs_stepcompress = """
@@ -54,25 +54,28 @@ defs_stepcompress = """
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);
void steppersync_set_time(struct steppersync *ss
, double time_offset, double mcu_freq);
int steppersync_flush(struct steppersync *ss, uint64_t move_clock
, uint64_t clear_history_clock);
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);
"""
defs_itersolve = """
int32_t itersolve_generate_steps(struct stepper_kinematics *sk
, double flush_time);
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);
void itersolve_set_stepcompress(struct stepper_kinematics *sk
, struct stepcompress *sc, double step_dist);
void itersolve_set_trapq(struct stepper_kinematics *sk, struct trapq *tq
, double step_dist);
double itersolve_calc_position_from_coord(struct stepper_kinematics *sk
, double x, double y, double z);
void itersolve_set_position(struct stepper_kinematics *sk
@@ -106,6 +109,12 @@ defs_trapq = """
defs_kin_cartesian = """
struct stepper_kinematics *cartesian_stepper_alloc(char axis);
"""
defs_kin_generic_cartesian = """
struct stepper_kinematics *generic_cartesian_stepper_alloc(double a_x
, double a_y, double a_z);
void generic_cartesian_stepper_set_coeffs(struct stepper_kinematics *sk
, double a_x, double a_y, double a_z);
"""
defs_kin_corexy = """
struct stepper_kinematics *corexy_stepper_alloc(char type);
@@ -154,6 +163,7 @@ defs_kin_shaper = """
, int n, double a[], double t[]);
int input_shaper_set_sk(struct stepper_kinematics *sk
, struct stepper_kinematics *orig_sk);
void input_shaper_update_sk(struct stepper_kinematics *sk);
struct stepper_kinematics * input_shaper_alloc(void);
"""
@@ -175,7 +185,7 @@ defs_serialqueue = """
};
struct serialqueue *serialqueue_alloc(int serial_fd, char serial_fd_type
, int client_id);
, int client_id, char name[16]);
void serialqueue_exit(struct serialqueue *sq);
void serialqueue_free(struct serialqueue *sq);
struct command_queue *serialqueue_alloc_commandqueue(void);
@@ -212,6 +222,7 @@ defs_trdispatch = """
defs_pyhelper = """
void set_python_logging_callback(void (*func)(const char *));
double get_monotonic(void);
int set_thread_name(char name[16]);
"""
defs_std = """
@@ -220,10 +231,11 @@ defs_std = """
defs_all = [
defs_pyhelper, defs_serialqueue, defs_std, defs_stepcompress,
defs_itersolve, defs_trapq, defs_trdispatch,
defs_steppersync, defs_itersolve, defs_trapq, defs_trdispatch,
defs_kin_cartesian, defs_kin_corexy, defs_kin_corexz, defs_kin_delta,
defs_kin_deltesian, defs_kin_polar, defs_kin_rotary_delta, defs_kin_winch,
defs_kin_extruder, defs_kin_shaper, defs_kin_idex,
defs_kin_generic_cartesian,
]
# Update filenames to an absolute path
@@ -262,11 +274,33 @@ def do_build_code(cmd):
logging.error(msg)
raise Exception(msg)
# Build the main c_helper.so c code library
def check_build_c_library():
srcdir = os.path.dirname(os.path.realpath(__file__))
srcfiles = get_abs_files(srcdir, SOURCE_FILES)
ofiles = get_abs_files(srcdir, OTHER_FILES)
destlib = get_abs_files(srcdir, [DEST_LIB])[0]
if not check_build_code(srcfiles+ofiles+[__file__], destlib):
# Code already built
return destlib
# Select command line options
if check_gcc_option(SSE_FLAGS):
cmd = "%s %s %s" % (GCC_CMD, SSE_FLAGS, COMPILE_ARGS)
else:
cmd = "%s %s" % (GCC_CMD, COMPILE_ARGS)
# Invoke compiler
logging.info("Building C code module %s", DEST_LIB)
tempdestlib = get_abs_files(srcdir, ["_temp_" + DEST_LIB])[0]
do_build_code(cmd % (tempdestlib, ' '.join(srcfiles)))
# Rename from temporary file to final file name
os.rename(tempdestlib, destlib)
return destlib
FFI_main = None
FFI_lib = None
pyhelper_logging_callback = None
# Hepler invoked from C errorf() code to log errors
# Helper invoked from C errorf() code to log errors
def logging_callback(msg):
logging.error(FFI_main.string(msg))
@@ -274,17 +308,9 @@ def logging_callback(msg):
def get_ffi():
global FFI_main, FFI_lib, pyhelper_logging_callback
if FFI_lib is None:
srcdir = os.path.dirname(os.path.realpath(__file__))
srcfiles = get_abs_files(srcdir, SOURCE_FILES)
ofiles = get_abs_files(srcdir, OTHER_FILES)
destlib = get_abs_files(srcdir, [DEST_LIB])[0]
if check_build_code(srcfiles+ofiles+[__file__], destlib):
if check_gcc_option(SSE_FLAGS):
cmd = "%s %s %s" % (GCC_CMD, SSE_FLAGS, COMPILE_ARGS)
else:
cmd = "%s %s" % (GCC_CMD, COMPILE_ARGS)
logging.info("Building C code module %s", DEST_LIB)
do_build_code(cmd % (destlib, ' '.join(srcfiles)))
# Check if library needs to be built, and build if so
destlib = check_build_c_library()
# Open library
FFI_main = cffi.FFI()
for d in defs_all:
FFI_main.cdef(d)

View File

@@ -26,8 +26,8 @@ struct timepos {
// Generate step times for a portion of a move
static int32_t
itersolve_gen_steps_range(struct stepper_kinematics *sk, struct move *m
, double abs_start, double abs_end)
itersolve_gen_steps_range(struct stepper_kinematics *sk, struct stepcompress *sc
, struct move *m, double abs_start, double abs_end)
{
sk_calc_callback calc_position_cb = sk->calc_position_cb;
double half_step = .5 * sk->step_dist;
@@ -37,7 +37,7 @@ itersolve_gen_steps_range(struct stepper_kinematics *sk, struct move *m
if (end > m->move_t)
end = m->move_t;
struct timepos old_guess = {start, sk->commanded_pos}, guess = old_guess;
int sdir = stepcompress_get_step_dir(sk->sc);
int sdir = stepcompress_get_step_dir(sc);
int is_dir_change = 0, have_bracket = 0, check_oscillate = 0;
double target = sk->commanded_pos + (sdir ? half_step : -half_step);
double last_time=start, low_time=start, high_time=start + SEEK_TIME_RESET;
@@ -99,13 +99,13 @@ itersolve_gen_steps_range(struct stepper_kinematics *sk, struct move *m
if (!have_bracket || high_time - low_time > .000000001) {
if (!is_dir_change && rel_dist >= -half_step)
// Avoid rollback if stepper fully reaches step position
stepcompress_commit(sk->sc);
stepcompress_commit(sc);
// Guess is not close enough - guess again with new time
continue;
}
}
// Found next step - submit it
int ret = stepcompress_append(sk->sc, sdir, m->print_time, guess.time);
int ret = stepcompress_append(sc, sdir, m->print_time, guess.time);
if (ret)
return ret;
target = sdir ? target+half_step+half_step : target-half_step-half_step;
@@ -143,8 +143,9 @@ check_active(struct stepper_kinematics *sk, struct move *m)
}
// Generate step times for a range of moves on the trapq
int32_t __visible
itersolve_generate_steps(struct stepper_kinematics *sk, double flush_time)
int32_t
itersolve_generate_steps(struct stepper_kinematics *sk, struct stepcompress *sc
, double flush_time)
{
double last_flush_time = sk->last_flush_time;
sk->last_flush_time = flush_time;
@@ -170,15 +171,15 @@ itersolve_generate_steps(struct stepper_kinematics *sk, double flush_time)
while (--skip_count && pm->print_time > abs_start)
pm = list_prev_entry(pm, node);
do {
int32_t ret = itersolve_gen_steps_range(sk, pm, abs_start
, flush_time);
int32_t ret = itersolve_gen_steps_range(
sk, sc, pm, abs_start, flush_time);
if (ret)
return ret;
pm = list_next_entry(pm, node);
} while (pm != m);
}
// Generate steps for this move
int32_t ret = itersolve_gen_steps_range(sk, m, last_flush_time
int32_t ret = itersolve_gen_steps_range(sk, sc, m, last_flush_time
, flush_time);
if (ret)
return ret;
@@ -195,8 +196,8 @@ itersolve_generate_steps(struct stepper_kinematics *sk, double flush_time)
double abs_end = force_steps_time;
if (abs_end > flush_time)
abs_end = flush_time;
int32_t ret = itersolve_gen_steps_range(sk, m, last_flush_time
, abs_end);
int32_t ret = itersolve_gen_steps_range(
sk, sc, m, last_flush_time, abs_end);
if (ret)
return ret;
skip_count = 1;
@@ -240,16 +241,10 @@ itersolve_is_active_axis(struct stepper_kinematics *sk, char axis)
}
void __visible
itersolve_set_trapq(struct stepper_kinematics *sk, struct trapq *tq)
itersolve_set_trapq(struct stepper_kinematics *sk, struct trapq *tq
, double step_dist)
{
sk->tq = tq;
}
void __visible
itersolve_set_stepcompress(struct stepper_kinematics *sk
, struct stepcompress *sc, double step_dist)
{
sk->sc = sc;
sk->step_dist = step_dist;
}

View File

@@ -26,12 +26,11 @@ struct stepper_kinematics {
};
int32_t itersolve_generate_steps(struct stepper_kinematics *sk
, double flush_time);
, struct stepcompress *sc, double flush_time);
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);
void itersolve_set_stepcompress(struct stepper_kinematics *sk
, struct stepcompress *sc, double step_dist);
void itersolve_set_trapq(struct stepper_kinematics *sk, struct trapq *tq
, double step_dist);
double itersolve_calc_position_from_coord(struct stepper_kinematics *sk
, double x, double y, double z);
void itersolve_set_position(struct stepper_kinematics *sk

View File

@@ -0,0 +1,52 @@
// Generic cartesian kinematics stepper position calculation
//
// Copyright (C) 2024 Dmitry Butyugin <dmbutyugin@google.com>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include <stddef.h> // offsetof
#include <stdlib.h> // malloc
#include <string.h> // memset
#include "compiler.h" // __visible
#include "itersolve.h" // struct stepper_kinematics
#include "trapq.h" // move_get_coord
struct generic_cartesian_stepper {
struct stepper_kinematics sk;
struct coord a;
};
static double
generic_cartesian_stepper_calc_position(struct stepper_kinematics *sk
, struct move *m, double move_time)
{
struct generic_cartesian_stepper *cs = container_of(
sk, struct generic_cartesian_stepper, sk);
struct coord c = move_get_coord(m, move_time);
return cs->a.x * c.x + cs->a.y * c.y + cs->a.z * c.z;
}
void __visible
generic_cartesian_stepper_set_coeffs(struct stepper_kinematics *sk
, double a_x, double a_y, double a_z)
{
struct generic_cartesian_stepper *cs = container_of(
sk, struct generic_cartesian_stepper, sk);
cs->a.x = a_x;
cs->a.y = a_y;
cs->a.z = a_z;
cs->sk.active_flags = 0;
if (a_x) cs->sk.active_flags |= AF_X;
if (a_y) cs->sk.active_flags |= AF_Y;
if (a_z) cs->sk.active_flags |= AF_Z;
}
struct stepper_kinematics * __visible
generic_cartesian_stepper_alloc(double a_x, double a_y, double a_z)
{
struct generic_cartesian_stepper *cs = malloc(sizeof(*cs));
memset(cs, 0, sizeof(*cs));
cs->sk.calc_position_cb = generic_cartesian_stepper_calc_position;
generic_cartesian_stepper_set_coeffs(&cs->sk, a_x, a_y, a_z);
return &cs->sk;
}

View File

@@ -77,5 +77,6 @@ dual_carriage_alloc(void)
struct dual_carriage_stepper *dc = malloc(sizeof(*dc));
memset(dc, 0, sizeof(*dc));
dc->m.move_t = 2. * DUMMY_T;
dc->x_scale = dc->y_scale = 1.0;
return &dc->sk;
}

View File

@@ -166,6 +166,38 @@ shaper_commanded_pos_post_fixup(struct stepper_kinematics *sk)
sk->commanded_pos = is->orig_sk->commanded_pos;
}
static void
shaper_note_generation_time(struct input_shaper *is)
{
double pre_active = 0., post_active = 0.;
if ((is->sk.active_flags & AF_X) && is->sx.num_pulses) {
pre_active = is->sx.pulses[is->sx.num_pulses-1].t;
post_active = -is->sx.pulses[0].t;
}
if ((is->sk.active_flags & AF_Y) && is->sy.num_pulses) {
pre_active = is->sy.pulses[is->sy.num_pulses-1].t > pre_active
? is->sy.pulses[is->sy.num_pulses-1].t : pre_active;
post_active = -is->sy.pulses[0].t > post_active
? -is->sy.pulses[0].t : post_active;
}
is->sk.gen_steps_pre_active = pre_active;
is->sk.gen_steps_post_active = post_active;
}
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)
is->sk.calc_position_cb = shaper_x_calc_position;
else if (is->orig_sk->active_flags & AF_Y)
is->sk.calc_position_cb = shaper_y_calc_position;
is->sk.active_flags = is->orig_sk->active_flags;
shaper_note_generation_time(is);
}
int __visible
input_shaper_set_sk(struct stepper_kinematics *sk
, struct stepper_kinematics *orig_sk)
@@ -190,24 +222,6 @@ input_shaper_set_sk(struct stepper_kinematics *sk
return 0;
}
static void
shaper_note_generation_time(struct input_shaper *is)
{
double pre_active = 0., post_active = 0.;
if ((is->sk.active_flags & AF_X) && is->sx.num_pulses) {
pre_active = is->sx.pulses[is->sx.num_pulses-1].t;
post_active = -is->sx.pulses[0].t;
}
if ((is->sk.active_flags & AF_Y) && is->sy.num_pulses) {
pre_active = is->sy.pulses[is->sy.num_pulses-1].t > pre_active
? is->sy.pulses[is->sy.num_pulses-1].t : pre_active;
post_active = -is->sy.pulses[0].t > post_active
? -is->sy.pulses[0].t : post_active;
}
is->sk.gen_steps_pre_active = pre_active;
is->sk.gen_steps_post_active = post_active;
}
int __visible
input_shaper_set_shaper_params(struct stepper_kinematics *sk, char axis
, int n, double a[], double t[])

View File

@@ -10,6 +10,8 @@
#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
@@ -92,3 +94,10 @@ dump_string(char *outbuf, int outbuf_size, char *inbuf, int inbuf_size)
*o = '\0';
return outbuf;
}
// Set custom thread names
int __visible
set_thread_name(char name[16])
{
return prctl(PR_SET_NAME, name);
}

View File

@@ -7,5 +7,6 @@ void set_python_logging_callback(void (*func)(const char *));
void errorf(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
void report_errno(char *where, int rc);
char *dump_string(char *outbuf, int outbuf_size, char *inbuf, int inbuf_size);
int set_thread_name(char name[16]);
#endif // pyhelper.h

View File

@@ -43,6 +43,7 @@ struct serialqueue {
uint8_t need_sync;
int input_pos;
// Threading
char name[16];
pthread_t tid;
pthread_mutex_t lock; // protects variables below
pthread_cond_t cond;
@@ -612,6 +613,7 @@ static void *
background_thread(void *data)
{
struct serialqueue *sq = data;
set_thread_name(sq->name);
pollreactor_run(sq->pr);
pthread_mutex_lock(&sq->lock);
@@ -623,13 +625,16 @@ background_thread(void *data)
// Create a new 'struct serialqueue' object
struct serialqueue * __visible
serialqueue_alloc(int serial_fd, char serial_fd_type, int client_id)
serialqueue_alloc(int serial_fd, char serial_fd_type, int client_id
, char name[16])
{
struct serialqueue *sq = malloc(sizeof(*sq));
memset(sq, 0, sizeof(*sq));
sq->serial_fd = serial_fd;
sq->serial_fd_type = serial_fd_type;
sq->client_id = client_id;
strncpy(sq->name, name, sizeof(sq->name));
sq->name[sizeof(sq->name)-1] = '\0';
int ret = pipe(sq->pipe_fds);
if (ret)

View File

@@ -27,7 +27,7 @@ struct pull_queue_message {
struct serialqueue;
struct serialqueue *serialqueue_alloc(int serial_fd, char serial_fd_type
, int client_id);
, int client_id, char name[16]);
void serialqueue_exit(struct serialqueue *sq);
void serialqueue_free(struct serialqueue *sq);
struct command_queue *serialqueue_alloc_commandqueue(void);

View File

@@ -1,6 +1,6 @@
// Stepper pulse schedule compression
//
// 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.
@@ -21,6 +21,7 @@
#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
@@ -46,6 +47,8 @@ struct stepcompress {
// History tracking
int64_t last_position;
struct list_head history_list;
// Itersolve reference
struct stepper_kinematics *sk;
};
struct step_move {
@@ -276,9 +279,9 @@ stepcompress_set_invert_sdir(struct stepcompress *sc, uint32_t invert_sdir)
}
}
// Helper to free items from the history_list
static void
free_history(struct stepcompress *sc, uint64_t end_clock)
// Expire the stepcompress history older than the given clock
void
stepcompress_history_expire(struct stepcompress *sc, uint64_t end_clock)
{
while (!list_empty(&sc->history_list)) {
struct history_steps *hs = list_last_entry(
@@ -290,13 +293,6 @@ free_history(struct stepcompress *sc, uint64_t end_clock)
}
}
// Expire the stepcompress history older than the given clock
static void
stepcompress_history_expire(struct stepcompress *sc, uint64_t end_clock)
{
free_history(sc, end_clock);
}
// Free memory associated with a 'stepcompress' object
void __visible
stepcompress_free(struct stepcompress *sc)
@@ -305,7 +301,7 @@ stepcompress_free(struct stepcompress *sc)
return;
free(sc->queue);
message_queue_free(&sc->msg_queue);
free_history(sc, UINT64_MAX);
stepcompress_history_expire(sc, UINT64_MAX);
free(sc);
}
@@ -321,6 +317,12 @@ 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)
@@ -330,7 +332,7 @@ calc_last_step_print_time(struct stepcompress *sc)
}
// Set the conversion rate of 'print_time' to mcu clock
static void
void
stepcompress_set_time(struct stepcompress *sc
, double time_offset, double mcu_freq)
{
@@ -664,164 +666,25 @@ stepcompress_extract_old(struct stepcompress *sc, struct pull_history_steps *p
return res;
}
/****************************************************************
* Step compress synchronization
****************************************************************/
// The steppersync object is used to synchronize the output of mcu
// step commands. The mcu can only queue a limited number of step
// commands - this code tracks when items on the mcu step queue become
// free so that new commands can be transmitted. It also ensures the
// mcu step queue is ordered between steppers so that no stepper
// starves the other steppers of space in the mcu step queue.
struct steppersync {
// Serial port
struct serialqueue *sq;
struct command_queue *cq;
// Storage for associated stepcompress objects
struct stepcompress **sc_list;
int sc_num;
// 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)
{
struct steppersync *ss = malloc(sizeof(*ss));
memset(ss, 0, sizeof(*ss));
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
// Store a reference to stepper_kinematics
void __visible
steppersync_free(struct steppersync *ss)
stepcompress_set_stepper_kinematics(struct stepcompress *sc
, struct stepper_kinematics *sk)
{
if (!ss)
return;
free(ss->sc_list);
free(ss->move_clocks);
serialqueue_free_commandqueue(ss->cq);
free(ss);
sc->sk = sk;
}
// Set the conversion rate of 'print_time' to mcu clock
void __visible
steppersync_set_time(struct steppersync *ss, double time_offset
, double mcu_freq)
// Generate steps (via itersolve) and flush
int32_t
stepcompress_generate_steps(struct stepcompress *sc, 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];
stepcompress_set_time(sc, time_offset, mcu_freq);
}
}
// Expire the stepcompress history before the given clock time
static void
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);
}
}
// Implement a binary heap algorithm to track when the next available
// 'struct move' in the mcu will be available
static void
heap_replace(struct steppersync *ss, uint64_t req_clock)
{
uint64_t *mc = ss->move_clocks;
int nmc = ss->num_move_clocks, pos = 0;
for (;;) {
int child1_pos = 2*pos+1, child2_pos = 2*pos+2;
uint64_t child2_clock = child2_pos < nmc ? mc[child2_pos] : UINT64_MAX;
uint64_t child1_clock = child1_pos < nmc ? mc[child1_pos] : UINT64_MAX;
if (req_clock <= child1_clock && req_clock <= child2_clock) {
mc[pos] = req_clock;
break;
}
if (child1_clock < child2_clock) {
mc[pos] = child1_clock;
pos = child1_pos;
} else {
mc[pos] = child2_clock;
pos = child2_pos;
}
}
}
// Find and transmit any scheduled steps prior to the given 'move_clock'
int __visible
steppersync_flush(struct steppersync *ss, uint64_t move_clock
, uint64_t clear_history_clock)
{
// Flush each stepcompress to the specified move_clock
int i;
for (i=0; i<ss->sc_num; i++) {
int ret = stepcompress_flush(ss->sc_list[i], move_clock);
if (ret)
return ret;
}
// Order commands by the reqclock of each pending command
struct list_head msgs;
list_init(&msgs);
for (;;) {
// Find message with lowest reqclock
uint64_t req_clock = MAX_CLOCK;
struct queue_message *qm = NULL;
for (i=0; i<ss->sc_num; i++) {
struct stepcompress *sc = ss->sc_list[i];
if (!list_empty(&sc->msg_queue)) {
struct queue_message *m = list_first_entry(
&sc->msg_queue, struct queue_message, node);
if (m->req_clock < req_clock) {
qm = m;
req_clock = m->req_clock;
}
}
}
if (!qm || (qm->min_clock && req_clock > move_clock))
break;
uint64_t next_avail = ss->move_clocks[0];
if (qm->min_clock)
// The qm->min_clock field is overloaded to indicate that
// the command uses the 'move queue' and to store the time
// that move queue item becomes available.
heap_replace(ss, qm->min_clock);
// Reset the min_clock to its normal meaning (minimum transmit time)
qm->min_clock = next_avail;
// Batch this command
list_del(&qm->node);
list_add_tail(&qm->node, &msgs);
}
// Transmit commands
if (!list_empty(&msgs))
serialqueue_send_batch(ss->sq, ss->cq, &msgs);
steppersync_history_expire(ss, clear_history_clock);
return 0;
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);
}

View File

@@ -17,9 +17,13 @@ void stepcompress_fill(struct stepcompress *sc, uint32_t max_error
, int32_t set_next_step_dir_msgtag);
void stepcompress_set_invert_sdir(struct stepcompress *sc
, uint32_t invert_sdir);
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);
@@ -34,15 +38,11 @@ int stepcompress_queue_mq_msg(struct stepcompress *sc, uint64_t req_clock
int stepcompress_extract_old(struct stepcompress *sc
, struct pull_history_steps *p, int max
, uint64_t start_clock, uint64_t end_clock);
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);
void steppersync_set_time(struct steppersync *ss, double time_offset
, double mcu_freq);
int steppersync_flush(struct steppersync *ss, uint64_t move_clock
, uint64_t clear_history_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

View File

@@ -0,0 +1,177 @@
// Stepper step transmit synchronization
//
// Copyright (C) 2016-2025 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
// The steppersync object is used to synchronize the output of mcu
// step commands. The mcu can only queue a limited number of step
// commands - this code tracks when items on the mcu step queue become
// free so that new commands can be transmitted. It also ensures the
// mcu step queue is ordered between steppers so that no stepper
// starves the other steppers of space in the mcu step queue.
#include <stddef.h> // offsetof
#include <stdlib.h> // malloc
#include <string.h> // memset
#include "compiler.h" // __visible
#include "serialqueue.h" // struct queue_message
#include "stepcompress.h" // stepcompress_flush
#include "steppersync.h" // steppersync_alloc
struct steppersync {
// Serial port
struct serialqueue *sq;
struct command_queue *cq;
// Storage for associated stepcompress objects
struct stepcompress **sc_list;
int sc_num;
// 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)
{
struct steppersync *ss = malloc(sizeof(*ss));
memset(ss, 0, sizeof(*ss));
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
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);
}
}
// Implement a binary heap algorithm to track when the next available
// 'struct move' in the mcu will be available
static void
heap_replace(struct steppersync *ss, uint64_t req_clock)
{
uint64_t *mc = ss->move_clocks;
int nmc = ss->num_move_clocks, pos = 0;
for (;;) {
int child1_pos = 2*pos+1, child2_pos = 2*pos+2;
uint64_t child2_clock = child2_pos < nmc ? mc[child2_pos] : UINT64_MAX;
uint64_t child1_clock = child1_pos < nmc ? mc[child1_pos] : UINT64_MAX;
if (req_clock <= child1_clock && req_clock <= child2_clock) {
mc[pos] = req_clock;
break;
}
if (child1_clock < child2_clock) {
mc[pos] = child1_clock;
pos = child1_pos;
} else {
mc[pos] = child2_clock;
pos = child2_pos;
}
}
}
// Find and transmit any scheduled steps prior to the given 'move_clock'
int __visible
steppersync_flush(struct steppersync *ss, uint64_t move_clock)
{
// Order commands by the reqclock of each pending command
struct list_head msgs;
list_init(&msgs);
for (;;) {
// 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 queue_message *m = list_first_entry(
sc_mq, struct queue_message, node);
if (m->req_clock < req_clock) {
qm = m;
req_clock = m->req_clock;
}
}
}
if (!qm || (qm->min_clock && req_clock > move_clock))
break;
uint64_t next_avail = ss->move_clocks[0];
if (qm->min_clock)
// The qm->min_clock field is overloaded to indicate that
// the command uses the 'move queue' and to store the time
// that move queue item becomes available.
heap_replace(ss, qm->min_clock);
// Reset the min_clock to its normal meaning (minimum transmit time)
qm->min_clock = next_avail;
// Batch this command
list_del(&qm->node);
list_add_tail(&qm->node, &msgs);
}
// Transmit commands
if (!list_empty(&msgs))
serialqueue_send_batch(ss->sq, ss->cq, &msgs);
return 0;
}

View File

@@ -0,0 +1,18 @@
#ifndef STEPPERSYNC_H
#define STEPPERSYNC_H
#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);
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);
#endif // steppersync.h

View File

@@ -96,6 +96,7 @@ class ADS1220:
self.printer, self._process_batch, self._start_measurements,
self._finish_measurements, UPDATE_INTERVAL)
# Command Configuration
self.attach_probe_cmd = None
mcu.add_config_cmd(
"config_ads1220 oid=%d spi_oid=%d data_ready_pin=%s"
% (self.oid, self.spi.get_oid(), self.data_ready_pin))
@@ -108,6 +109,8 @@ class ADS1220:
cmdqueue = self.spi.get_command_queue()
self.query_ads1220_cmd = self.mcu.lookup_command(
"query_ads1220 oid=%c rest_ticks=%u", cq=cmdqueue)
self.attach_probe_cmd = self.mcu.lookup_command(
"ads1220_attach_load_cell_probe oid=%c load_cell_probe_oid=%c")
self.ffreader.setup_query_command("query_ads1220_status oid=%c",
oid=self.oid, cq=cmdqueue)
@@ -126,6 +129,9 @@ class ADS1220:
def add_client(self, callback):
self.batch_bulk.add_client(callback)
def attach_load_cell_probe(self, load_cell_probe_oid):
self.attach_probe_cmd.send([self.oid, load_cell_probe_oid])
# Measurement decoding
def _convert_samples(self, samples):
adc_factor = 1. / (1 << 23)

View File

@@ -210,28 +210,28 @@ class ADS1X1X_chip:
raise pins.error('ADS1x1x pin %s is not valid' % \
pin_params['pin'])
config = 0
config |= (ADS1X1X_OS['OS_SINGLE'] & \
ADS1X1X_REG_CONFIG['OS_MASK'])
config |= (ADS1X1X_MUX[pin_params['pin']] & \
ADS1X1X_REG_CONFIG['MULTIPLEXER_MASK'])
config |= (self.pga & ADS1X1X_REG_CONFIG['PGA_MASK'])
pcfg = 0
pcfg |= (ADS1X1X_OS['OS_SINGLE'] & \
ADS1X1X_REG_CONFIG['OS_MASK'])
pcfg |= (ADS1X1X_MUX[pin_params['pin']] & \
ADS1X1X_REG_CONFIG['MULTIPLEXER_MASK'])
pcfg |= (self.pga & ADS1X1X_REG_CONFIG['PGA_MASK'])
# Have to use single mode, because in continuous, it never reaches
# idle state, which we use to determine if the sampling is done.
config |= (ADS1X1X_MODE['single'] & \
pcfg |= (ADS1X1X_MODE['single'] & \
ADS1X1X_REG_CONFIG['MODE_MASK'])
# lowest sample rate per default, until report time has been set in
# setup_adc_sample
config |= (self.comp_mode \
& ADS1X1X_REG_CONFIG['COMPARATOR_MODE_MASK'])
config |= (self.comp_polarity \
& ADS1X1X_REG_CONFIG['COMPARATOR_POLARITY_MASK'])
config |= (self.comp_latching \
& ADS1X1X_REG_CONFIG['COMPARATOR_LATCHING_MASK'])
config |= (self.comp_queue \
& ADS1X1X_REG_CONFIG['COMPARATOR_QUEUE_MASK'])
pcfg |= (self.comp_mode \
& ADS1X1X_REG_CONFIG['COMPARATOR_MODE_MASK'])
pcfg |= (self.comp_polarity \
& ADS1X1X_REG_CONFIG['COMPARATOR_POLARITY_MASK'])
pcfg |= (self.comp_latching \
& ADS1X1X_REG_CONFIG['COMPARATOR_LATCHING_MASK'])
pcfg |= (self.comp_queue \
& ADS1X1X_REG_CONFIG['COMPARATOR_QUEUE_MASK'])
pin_obj = ADS1X1X_pin(self, config)
pin_obj = ADS1X1X_pin(self, pcfg)
if pin in self._pins:
raise pins.error(
'pin %s for chip %s is used multiple times' \
@@ -250,8 +250,8 @@ class ADS1X1X_chip:
logging.exception("ADS1X1X: error while resetting device")
def is_ready(self):
config = self._read_register(ADS1X1X_REG_POINTER['CONFIG'])
return bool((config & ADS1X1X_REG_CONFIG['OS_MASK']) == \
cfg = self._read_register(ADS1X1X_REG_POINTER['CONFIG'])
return bool((cfg & ADS1X1X_REG_CONFIG['OS_MASK']) == \
ADS1X1X_OS['OS_IDLE'])
def calculate_sample_rate(self):
@@ -281,7 +281,7 @@ class ADS1X1X_chip:
(sample_rate, sample_rate_bits) = self.calculate_sample_rate()
for pin in self._pins.values():
pin.config = (pin.config & ~ADS1X1X_REG_CONFIG['DATA_RATE_MASK']) \
pin.pcfg = (pin.pcfg & ~ADS1X1X_REG_CONFIG['DATA_RATE_MASK']) \
| (sample_rate_bits & ADS1X1X_REG_CONFIG['DATA_RATE_MASK'])
self.delay = 1 / float(sample_rate)
@@ -289,7 +289,7 @@ class ADS1X1X_chip:
def sample(self, pin):
with self._mutex:
try:
self._write_register(ADS1X1X_REG_POINTER['CONFIG'], pin.config)
self._write_register(ADS1X1X_REG_POINTER['CONFIG'], pin.pcfg)
self._reactor.pause(self._reactor.monotonic() + self.delay)
start_time = self._reactor.monotonic()
while not self.is_ready():
@@ -318,10 +318,10 @@ class ADS1X1X_chip:
self._i2c.i2c_write(data)
class ADS1X1X_pin:
def __init__(self, chip, config):
def __init__(self, chip, pcfg):
self.mcu = chip.mcu
self.chip = chip
self.config = config
self.pcfg = pcfg
self.invalid_count = 0

View File

@@ -97,7 +97,7 @@ class AngleCalibration:
return None
return self.mcu_stepper.mcu_to_commanded_position(self.mcu_pos_offset)
def load_calibration(self, angles):
# Calculate linear intepolation calibration buckets by solving
# Calculate linear interpolation calibration buckets by solving
# linear equations
angle_max = 1 << ANGLE_BITS
calibration_count = 1 << CALIBRATION_BITS

View File

@@ -34,7 +34,7 @@ def constrain(val, min_val, max_val):
def lerp(t, v0, v1):
return (1. - t) * v0 + t * v1
# retreive commma separated pair from config
# retrieve comma separated pair from config
def parse_config_pair(config, option, default, minval=None, maxval=None):
pair = config.getintlist(option, (default, default))
if len(pair) != 2:
@@ -54,7 +54,7 @@ def parse_config_pair(config, option, default, minval=None, maxval=None):
% (option, str(maxval)))
return pair
# retreive commma separated pair from a g-code command
# retrieve comma separated pair from a g-code command
def parse_gcmd_pair(gcmd, name, minval=None, maxval=None):
try:
pair = [int(v.strip()) for v in gcmd.get(name).split(',')]
@@ -74,7 +74,7 @@ def parse_gcmd_pair(gcmd, name, minval=None, maxval=None):
% (name, maxval))
return pair
# retreive commma separated coordinate from a g-code command
# retrieve comma separated coordinate from a g-code command
def parse_gcmd_coord(gcmd, name):
try:
v1, v2 = [float(v.strip()) for v in gcmd.get(name).split(',')]
@@ -186,7 +186,8 @@ class BedMesh:
self.last_position[2] -= self.fade_target
else:
# return current position minus the current z-adjustment
x, y, z, e = self.toolhead.get_position()
cur_pos = self.toolhead.get_position()
x, y, z = cur_pos[:3]
max_adj = self.z_mesh.calc_z(x, y)
factor = 1.
z_adj = max_adj - self.fade_target
@@ -202,19 +203,19 @@ class BedMesh:
(self.fade_dist - z_adj))
factor = constrain(factor, 0., 1.)
final_z_adj = factor * z_adj + self.fade_target
self.last_position[:] = [x, y, z - final_z_adj, e]
self.last_position[:] = [x, y, z - final_z_adj] + cur_pos[3:]
return list(self.last_position)
def move(self, newpos, speed):
factor = self.get_z_factor(newpos[2])
if self.z_mesh is None or not factor:
# No mesh calibrated, or mesh leveling phased out.
x, y, z, e = newpos
x, y, z = newpos[:3]
if self.log_fade_complete:
self.log_fade_complete = False
logging.info(
"bed_mesh fade complete: Current Z: %.4f fade_target: %.4f "
% (z, self.fade_target))
self.toolhead.move([x, y, z + self.fade_target, e], speed)
self.toolhead.move([x, y, z + self.fade_target] + newpos[3:], speed)
else:
self.splitter.build_move(self.last_position, newpos, factor)
while not self.splitter.traverse_complete:
@@ -913,7 +914,7 @@ class ProbeManager:
for i in range(y_cnt):
for j in range(x_cnt):
if not i % 2:
# move in positive directon
# move in positive direction
pos_x = min_x + j * x_dist
else:
# move in negative direction
@@ -1163,7 +1164,7 @@ class ProbeManager:
def _gen_arc(self, origin, radius, start, step, count):
end = start + step * count
# create a segent for every 3 degress of travel
# create a segent for every 3 degrees of travel
for angle in range(start, end, step):
rad = math.radians(angle % 360)
opp = math.sin(rad) * radius
@@ -1273,7 +1274,7 @@ class MoveSplitter:
self.z_offset = self._calc_z_offset(prev_pos)
self.traverse_complete = False
self.distance_checked = 0.
axes_d = [self.next_pos[i] - self.prev_pos[i] for i in range(4)]
axes_d = [np - pp for np, pp in zip(self.next_pos, self.prev_pos)]
self.total_move_length = math.sqrt(sum([d*d for d in axes_d[:3]]))
self.axis_move = [not isclose(d, 0., abs_tol=1e-10) for d in axes_d]
def _calc_z_offset(self, pos):
@@ -1286,7 +1287,7 @@ class MoveSplitter:
raise self.gcode.error(
"bed_mesh: Slice distance is negative "
"or greater than entire move length")
for i in range(4):
for i in range(len(self.next_pos)):
if self.axis_move[i]:
self.current_pos[i] = lerp(
t, self.prev_pos[i], self.next_pos[i])
@@ -1301,9 +1302,9 @@ class MoveSplitter:
next_z = self._calc_z_offset(self.current_pos)
if abs(next_z - self.z_offset) >= self.split_delta_z:
self.z_offset = next_z
return self.current_pos[0], self.current_pos[1], \
self.current_pos[2] + self.z_offset, \
self.current_pos[3]
newpos = list(self.current_pos)
newpos[2] += self.z_offset
return newpos
# end of move reached
self.current_pos[:] = self.next_pos
self.z_offset = self._calc_z_offset(self.current_pos)

View File

@@ -24,12 +24,14 @@ class BedTilt:
def handle_connect(self):
self.toolhead = self.printer.lookup_object('toolhead')
def get_position(self):
x, y, z, e = self.toolhead.get_position()
return [x, y, z - x*self.x_adjust - y*self.y_adjust - self.z_adjust, e]
pos = self.toolhead.get_position()
x, y, z = pos[:3]
z -= x*self.x_adjust + y*self.y_adjust + self.z_adjust
return [x, y, z] + pos[3:]
def move(self, newpos, speed):
x, y, z, e = newpos
self.toolhead.move([x, y, z + x*self.x_adjust + y*self.y_adjust
+ self.z_adjust, e], speed)
x, y, z = newpos[:3]
z += x*self.x_adjust + y*self.y_adjust + self.z_adjust
self.toolhead.move([x, y, z] + newpos[3:], speed)
def update_adjust(self, x_adjust, y_adjust, z_adjust):
self.x_adjust = x_adjust
self.y_adjust = y_adjust

View File

@@ -64,7 +64,11 @@ class BLTouchProbe:
self.cmd_helper = probe.ProbeCommandHelper(
config, self, self.mcu_endstop.query_endstop)
self.probe_offsets = probe.ProbeOffsetsHelper(config)
self.probe_session = probe.ProbeSessionHelper(config, self)
self.param_helper = probe.ProbeParameterHelper(config)
self.homing_helper = probe.HomingViaProbeHelper(config, self,
self.param_helper)
self.probe_session = probe.ProbeSessionHelper(
config, self.param_helper, self.homing_helper.start_probe_session)
# Register BLTOUCH_DEBUG command
self.gcode = self.printer.lookup_object('gcode')
self.gcode.register_command("BLTOUCH_DEBUG", self.cmd_BLTOUCH_DEBUG,
@@ -75,7 +79,7 @@ class BLTouchProbe:
self.printer.register_event_handler("klippy:connect",
self.handle_connect)
def get_probe_params(self, gcmd=None):
return self.probe_session.get_probe_params(gcmd)
return self.param_helper.get_probe_params(gcmd)
def get_offsets(self):
return self.probe_offsets.get_offsets()
def get_status(self, eventtime):
@@ -191,9 +195,6 @@ class BLTouchProbe:
self.verify_raise_probe()
self.sync_print_time()
self.multi = 'OFF'
def probing_move(self, pos, speed):
phoming = self.printer.lookup_object('homing')
return phoming.probing_move(self, pos, speed)
def probe_prepare(self, hmove):
if self.multi == 'OFF' or self.multi == 'FIRST':
self.lower_probe()

View File

@@ -284,7 +284,7 @@ class BME280:
self.chip_type, self.i2c.i2c_address))
# Reset chip
self.write_register('RESET', [RESET_CHIP_VALUE], wait=True)
self.write_register('RESET', [RESET_CHIP_VALUE])
self.reactor.pause(self.reactor.monotonic() + .5)
# Make sure non-volatile memory has been copied to registers
@@ -394,7 +394,7 @@ class BME280:
self.write_register('CTRL_HUM', self.os_hum)
# Enter normal (periodic) mode
meas = self.os_temp << 5 | self.os_pres << 2 | MODE_PERIODIC
self.write_register('CTRL_MEAS', meas, wait=True)
self.write_register('CTRL_MEAS', meas)
if self.chip_type == 'BME680':
self.write_register('CONFIG', self.iir_filter << 2)
@@ -528,7 +528,7 @@ class BME280:
# Enter forced mode
meas = self.os_temp << 5 | self.os_pres << 2 | MODE
self.write_register('CTRL_MEAS', meas, wait=True)
self.write_register('CTRL_MEAS', meas)
max_sample_time = self.max_sample_time
if run_gas:
max_sample_time += self.gas_heat_duration / 1000
@@ -776,15 +776,12 @@ class BME280:
params = self.i2c.i2c_read(regs, read_len)
return bytearray(params['response'])
def write_register(self, reg_name, data, wait = False):
def write_register(self, reg_name, data):
if type(data) is not list:
data = [data]
reg = self.chip_registers[reg_name]
data.insert(0, reg)
if not wait:
self.i2c.i2c_write(data)
else:
self.i2c.i2c_write_wait_ack(data)
self.i2c.i2c_write(data)
def get_status(self, eventtime):
data = {

View File

@@ -43,6 +43,7 @@ class MCU_SPI:
cs_active_high=False):
self.mcu = mcu
self.bus = bus
self.speed = speed
# Config SPI object (set all CS pins high before spi_set_bus commands)
self.oid = mcu.create_oid()
if pin is None:
@@ -51,11 +52,17 @@ class MCU_SPI:
mcu.add_config_cmd("config_spi oid=%d pin=%s cs_active_high=%d"
% (self.oid, pin, cs_active_high))
# Generate SPI bus config message
self.config_fmt_ticks = None
if sw_pins is not None:
self.config_fmt = (
"spi_set_software_bus oid=%d"
" miso_pin=%s mosi_pin=%s sclk_pin=%s mode=%d rate=%d"
% (self.oid, sw_pins[0], sw_pins[1], sw_pins[2], mode, speed))
self.config_fmt_ticks = (
"spi_set_sw_bus oid=%d"
" miso_pin=%s mosi_pin=%s sclk_pin=%s mode=%d pulse_ticks=%%d"
% (self.oid, sw_pins[0], sw_pins[1],
sw_pins[2], mode))
else:
self.config_fmt = (
"spi_set_bus oid=%d spi_bus=%%s mode=%d rate=%d"
@@ -78,6 +85,12 @@ class MCU_SPI:
if '%' in self.config_fmt:
bus = resolve_bus_name(self.mcu, "spi_bus", self.bus)
self.config_fmt = self.config_fmt % (bus,)
if self.config_fmt_ticks:
if self.mcu.try_lookup_command("spi_set_sw_bus oid=%c miso_pin=%u "
"mosi_pin=%u sclk_pin=%u "
"mode=%u pulse_ticks=%u"):
pulse_ticks = self.mcu.seconds_to_clock(1./self.speed)
self.config_fmt = self.config_fmt_ticks % (pulse_ticks,)
self.mcu.add_config_cmd(self.config_fmt)
self.spi_send_cmd = self.mcu.lookup_command(
"spi_send oid=%c data=%*s", cq=self.cmd_queue)
@@ -147,6 +160,8 @@ class MCU_I2C:
self.bus = bus
self.i2c_address = addr
self.oid = self.mcu.create_oid()
self.speed = speed
self.config_fmt_ticks = None
mcu.add_config_cmd("config_i2c oid=%d" % (self.oid,))
# Generate I2C bus config message
if sw_pins is not None:
@@ -154,6 +169,10 @@ class MCU_I2C:
"i2c_set_software_bus oid=%d"
" scl_pin=%s sda_pin=%s rate=%d address=%d"
% (self.oid, sw_pins[0], sw_pins[1], speed, addr))
self.config_fmt_ticks = (
"i2c_set_sw_bus oid=%d"
" scl_pin=%s sda_pin=%s pulse_ticks=%%d address=%d"
% (self.oid, sw_pins[0], sw_pins[1], addr))
else:
self.config_fmt = (
"i2c_set_bus oid=%d i2c_bus=%%s rate=%d address=%d"
@@ -161,6 +180,13 @@ class MCU_I2C:
self.cmd_queue = self.mcu.alloc_command_queue()
self.mcu.register_config_callback(self.build_config)
self.i2c_write_cmd = self.i2c_read_cmd = None
printer = self.mcu.get_printer()
printer.register_event_handler("klippy:connect", self._handle_connect)
# backward support i2c_write inside the init section
self._to_write = []
def _handle_connect(self):
for data in self._to_write:
self.i2c_write(data)
def get_oid(self):
return self.oid
def get_mcu(self):
@@ -173,6 +199,12 @@ class MCU_I2C:
if '%' in self.config_fmt:
bus = resolve_bus_name(self.mcu, "i2c_bus", self.bus)
self.config_fmt = self.config_fmt % (bus,)
if self.config_fmt_ticks:
if self.mcu.try_lookup_command("i2c_set_sw_bus oid=%c"
" scl_pin=%u sda_pin=%u"
" pulse_ticks=%u address=%u"):
pulse_ticks = self.mcu.seconds_to_clock(1./self.speed/2)
self.config_fmt = self.config_fmt_ticks % (pulse_ticks,)
self.mcu.add_config_cmd(self.config_fmt)
self.i2c_write_cmd = self.mcu.lookup_command(
"i2c_write oid=%c data=%*s", cq=self.cmd_queue)
@@ -182,18 +214,12 @@ class MCU_I2C:
cq=self.cmd_queue)
def i2c_write(self, data, minclock=0, reqclock=0):
if self.i2c_write_cmd is None:
# Send setup message via mcu initialization
data_msg = "".join(["%02x" % (x,) for x in data])
self.mcu.add_config_cmd("i2c_write oid=%d data=%s" % (
self.oid, data_msg), is_init=True)
self._to_write.append(data)
return
self.i2c_write_cmd.send([self.oid, data],
minclock=minclock, reqclock=reqclock)
def i2c_write_wait_ack(self, data, minclock=0, reqclock=0):
self.i2c_write_cmd.send_wait_ack([self.oid, data],
minclock=minclock, reqclock=reqclock)
def i2c_read(self, write, read_len):
return self.i2c_read_cmd.send([self.oid, write, read_len])
minclock=minclock, reqclock=reqclock)
def i2c_read(self, write, read_len, retry=True):
return self.i2c_read_cmd.send([self.oid, write, read_len], retry)
def MCU_I2C_from_config(config, default_addr=None, default_speed=100000):
# Load bus parameters

View File

@@ -12,7 +12,7 @@ def load_config_prefix(config):
if not config.has_section('display'):
raise config.error(
"A primary [display] section must be defined in printer.cfg "
"to use auxilary displays")
"to use auxiliary displays")
name = config.get_name().split()[-1]
if name == "display":
raise config.error(

View File

@@ -13,7 +13,7 @@
# ftp://ftp.simtel.net/pub/simtelnet/msdos/screen/fntcol16.zip
# (c) Joseph Gil
#
# Indivdual fonts are public domain
# Individual fonts are public domain
######################################################################
VGA_FONT = [

View File

@@ -52,7 +52,7 @@ class PhaseCalc:
class EndstopPhase:
def __init__(self, config):
self.printer = config.get_printer()
self.name = config.get_name().split()[1]
self.name = " ".join(config.get_name().split()[1:])
# Obtain step_distance and microsteps from stepper config section
sconfig = config.getsection(self.name)
rotation_dist, steps_per_rotation = stepper.parse_step_distance(sconfig)
@@ -118,7 +118,7 @@ class EndstopPhase:
return delta * self.step_dist
def handle_home_rails_end(self, homing_state, rails):
for rail in rails:
stepper = rail.get_steppers()[0]
stepper = rail.get_endstops()[0][0].get_steppers()[0]
if stepper.get_name() == self.name:
trig_mcu_pos = homing_state.get_trigger_position(self.name)
align = self.align_endstop(rail)

View File

@@ -82,24 +82,25 @@ class ExcludeObject:
self._reset_state()
self._unregister_transform()
def _get_extrusion_offsets(self):
offset = self.extrusion_offsets.get(
self.toolhead.get_extruder().get_name())
def _get_extrusion_offsets(self, num_coord):
ename = self.toolhead.get_extruder().get_name()
offset = self.extrusion_offsets.get(ename)
if offset is None:
offset = [0., 0., 0., 0.]
self.extrusion_offsets[self.toolhead.get_extruder().get_name()] = \
offset
offset = [0.] * num_coord
self.extrusion_offsets[ename] = offset
if len(offset) < num_coord:
offset.extend([0.] * (len(num_coord) - len(offset)))
return offset
def get_position(self):
offset = self._get_extrusion_offsets()
pos = self.next_transform.get_position()
for i in range(4):
offset = self._get_extrusion_offsets(len(pos))
for i in range(len(pos)):
self.last_position[i] = pos[i] + offset[i]
return list(self.last_position)
def _normal_move(self, newpos, speed):
offset = self._get_extrusion_offsets()
offset = self._get_extrusion_offsets(len(newpos))
if self.initial_extrusion_moves > 0 and \
self.last_position[3] != newpos[3]:
@@ -122,9 +123,9 @@ class ExcludeObject:
if (offset[0] != 0 or offset[1] != 0) and \
(newpos[0] != self.last_position_excluded[0] or \
newpos[1] != self.last_position_excluded[1]):
offset[0] = 0
offset[1] = 0
offset[2] = 0
for i in range(len(newpos)):
if i != 3:
offset[i] = 0
offset[3] += self.extruder_adj
self.extruder_adj = 0
@@ -137,17 +138,18 @@ class ExcludeObject:
self.extruder_adj = 0
tx_pos = newpos[:]
for i in range(4):
for i in range(len(newpos)):
tx_pos[i] = newpos[i] - offset[i]
self.next_transform.move(tx_pos, speed)
def _ignore_move(self, newpos, speed):
offset = self._get_extrusion_offsets()
for i in range(3):
offset[i] = newpos[i] - self.last_position_extruded[i]
offset = self._get_extrusion_offsets(len(newpos))
for i in range(len(newpos)):
if i != 3:
offset[i] = newpos[i] - self.last_position_extruded[i]
offset[3] = offset[3] + newpos[3] - self.last_position[3]
self.last_position[:] = newpos
self.last_position_excluded[:] =self.last_position
self.last_position_excluded[:] = self.last_position
self.max_position_excluded = max(self.max_position_excluded, newpos[3])
def _move_into_excluded_region(self, newpos, speed):

View File

@@ -43,7 +43,7 @@ class FirmwareRetraction:
self.unretract_length = (self.retract_length
+ self.unretract_extra_length)
self.is_retracted = False
cmd_GET_RETRACTION_help = ("Report firmware retraction paramters")
cmd_GET_RETRACTION_help = ("Report firmware retraction parameters")
def cmd_GET_RETRACTION(self, gcmd):
gcmd.respond_info("RETRACT_LENGTH=%.5f RETRACT_SPEED=%.5f"
" UNRETRACT_EXTRA_LENGTH=%.5f UNRETRACT_SPEED=%.5f"

View File

@@ -33,10 +33,10 @@ class ForceMove:
self.printer = config.get_printer()
self.steppers = {}
# Setup iterative solver
self.motion_queuing = self.printer.load_object(config, 'motion_queuing')
self.trapq = self.motion_queuing.allocate_trapq()
self.trapq_append = self.motion_queuing.lookup_trapq_append()
ffi_main, ffi_lib = chelper.get_ffi()
self.trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free)
self.trapq_append = ffi_lib.trapq_append
self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves
self.stepper_kinematics = ffi_main.gc(
ffi_lib.cartesian_stepper_alloc(b'x'), ffi_lib.free)
# Register commands
@@ -85,14 +85,12 @@ 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
stepper.generate_steps(print_time)
self.trapq_finalize_moves(self.trapq, print_time + 99999.9,
print_time + 99999.9)
stepper.set_trapq(prev_trapq)
stepper.set_stepper_kinematics(prev_sk)
toolhead.note_mcu_movequeue_activity(print_time)
toolhead.dwell(accel_t + cruise_t + accel_t)
toolhead.flush_step_generation()
stepper.set_trapq(prev_trapq)
stepper.set_stepper_kinematics(prev_sk)
self.motion_queuing.wipe_trapq(self.trapq)
def _lookup_stepper(self, gcmd):
name = gcmd.get('STEPPER')
if name not in self.steppers:
@@ -142,7 +140,7 @@ class ForceMove:
logging.info("SET_KINEMATIC_POSITION pos=%.3f,%.3f,%.3f"
" set_homed=%s clear_homed=%s",
x, y, z, set_homed_axes, clear_homed_axes)
toolhead.set_position([x, y, z, curpos[3]], homing_axes=set_homed_axes)
toolhead.set_position([x, y, z], homing_axes=set_homed_axes)
toolhead.get_kinematics().clear_homing_state(clear_homed_axes)
def load_config(config):

View File

@@ -178,8 +178,8 @@ class GCodeMacro:
literal = ast.literal_eval(value)
json.dumps(literal, separators=(',', ':'))
except (SyntaxError, TypeError, ValueError) as e:
raise gcmd.error("Unable to parse '%s' as a literal: %s" %
(value, e))
raise gcmd.error("Unable to parse '%s' as a literal: %s in '%s'" %
(value, e, gcmd.get_commandline()))
v = dict(self.variables)
v[variable] = literal
self.variables = v

View File

@@ -1,6 +1,6 @@
# G-Code G1 movement commands (and associated coordinate manipulation)
#
# 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 logging
@@ -14,6 +14,8 @@ class GCodeMove:
self.reset_last_position)
printer.register_event_handler("toolhead:manual_move",
self.reset_last_position)
printer.register_event_handler("toolhead:update_extra_axes",
self._update_extra_axes)
printer.register_event_handler("gcode:command_error",
self.reset_last_position)
printer.register_event_handler("extruder:activate_extruder",
@@ -42,6 +44,7 @@ class GCodeMove:
self.base_position = [0.0, 0.0, 0.0, 0.0]
self.last_position = [0.0, 0.0, 0.0, 0.0]
self.homing_position = [0.0, 0.0, 0.0, 0.0]
self.axis_map = {'X':0, 'Y': 1, 'Z': 2, 'E': 3}
self.speed = 25.
self.speed_factor = 1. / 60.
self.extrude_factor = 1.
@@ -102,35 +105,46 @@ class GCodeMove:
'extrude_factor': self.extrude_factor,
'absolute_coordinates': self.absolute_coord,
'absolute_extrude': self.absolute_extrude,
'homing_origin': self.Coord(*self.homing_position),
'position': self.Coord(*self.last_position),
'gcode_position': self.Coord(*move_position),
'homing_origin': self.Coord(*self.homing_position[:4]),
'position': self.Coord(*self.last_position[:4]),
'gcode_position': self.Coord(*move_position[:4]),
}
def reset_last_position(self):
if self.is_printer_ready:
self.last_position = self.position_with_transform()
def _update_extra_axes(self):
toolhead = self.printer.lookup_object('toolhead')
axis_map = {'X':0, 'Y': 1, 'Z': 2, 'E': 3}
extra_axes = toolhead.get_extra_axes()
for index, ea in enumerate(extra_axes):
if ea is None:
continue
gcode_id = ea.get_axis_gcode_id()
if gcode_id is None or gcode_id in axis_map or gcode_id in "FN":
continue
axis_map[gcode_id] = index
self.axis_map = axis_map
self.base_position[4:] = [0.] * (len(extra_axes) - 4)
self.reset_last_position()
# G-Code movement commands
def cmd_G1(self, gcmd):
# Move
params = gcmd.get_command_parameters()
try:
for pos, axis in enumerate('XYZ'):
for axis, pos in self.axis_map.items():
if axis in params:
v = float(params[axis])
if not self.absolute_coord:
absolute_coord = self.absolute_coord
if axis == 'E':
v *= self.extrude_factor
if not self.absolute_extrude:
absolute_coord = False
if not absolute_coord:
# value relative to position of last move
self.last_position[pos] += v
else:
# value relative to base coordinate position
self.last_position[pos] = v + self.base_position[pos]
if 'E' in params:
v = float(params['E']) * self.extrude_factor
if not self.absolute_coord or not self.absolute_extrude:
# value relative to position of last move
self.last_position[3] += v
else:
# value relative to base coordinate position
self.last_position[3] = v + self.base_position[3]
if 'F' in params:
gcode_speed = float(params['F'])
if gcode_speed <= 0.:
@@ -169,7 +183,7 @@ class GCodeMove:
offset *= self.extrude_factor
self.base_position[i] = self.last_position[i] - offset
if offsets == [None, None, None, None]:
self.base_position = list(self.last_position)
self.base_position[:4] = self.last_position[:4]
def cmd_M114(self, gcmd):
# Get Current Position
p = self._get_gcode_position()
@@ -227,7 +241,7 @@ class GCodeMove:
# Restore state
self.absolute_coord = state['absolute_coord']
self.absolute_extrude = state['absolute_extrude']
self.base_position = list(state['base_position'])
self.base_position[:4] = state['base_position'][:4]
self.homing_position = list(state['homing_position'])
self.speed = state['speed']
self.speed_factor = state['speed_factor']
@@ -255,7 +269,7 @@ class GCodeMove:
kinfo = zip("XYZ", kin.calc_position(dict(cinfo)))
kin_pos = " ".join(["%s:%.6f" % (a, v) for a, v in kinfo])
toolhead_pos = " ".join(["%s:%.6f" % (a, v) for a, v in zip(
"XYZE", toolhead.get_position())])
"XYZE", toolhead.get_position()[:4])])
gcode_pos = " ".join(["%s:%.6f" % (a, v)
for a, v in zip("XYZE", self.last_position)])
base_pos = " ".join(["%s:%.6f" % (a, v)

View File

@@ -209,10 +209,12 @@ class HallFilamentWidthSensor:
+self.lastFilamentWidthReading2))
gcmd.respond_info(response)
def get_status(self, eventtime):
return {'Diameter': self.diameter,
status = self.runout_helper.get_status(eventtime)
status.update({'Diameter': self.diameter,
'Raw':(self.lastFilamentWidthReading+
self.lastFilamentWidthReading2),
'is_active':self.is_active}
'is_active':self.is_active})
return status
def cmd_log_enable(self, gcmd):
self.is_log = True
gcmd.respond_info("Filament width logging Turned On")

View File

@@ -1,6 +1,6 @@
# Tracking of PWM controlled heaters and their temperature control
#
# 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, logging, threading
@@ -11,10 +11,11 @@ import os, logging, threading
######################################################################
KELVIN_TO_CELSIUS = -273.15
MAX_HEAT_TIME = 5.0
MAX_HEAT_TIME = 3.0
AMBIENT_TEMP = 25.
PID_PARAM_BASE = 255.
MAX_MAINTHREAD_TIME = 5.0
QUELL_STALE_TIME = 7.0
class Heater:
def __init__(self, config, sensor):
@@ -74,7 +75,8 @@ class Heater:
# No significant change in value - can suppress update
return
pwm_time = read_time + self.pwm_delay
self.next_pwm_time = pwm_time + 0.75 * MAX_HEAT_TIME
self.next_pwm_time = (pwm_time + MAX_HEAT_TIME
- (3. * self.pwm_delay + 0.001))
self.last_pwm_value = value
self.mcu_pwm.set_pwm(pwm_time, value)
#logging.debug("%s: pwm=%.3f@%.3f (from %.3f@%.3f [%.3f])",
@@ -110,9 +112,10 @@ class Heater:
with self.lock:
self.target_temp = degrees
def get_temp(self, eventtime):
print_time = self.mcu_pwm.get_mcu().estimated_print_time(eventtime) - 5.
est_print_time = self.mcu_pwm.get_mcu().estimated_print_time(eventtime)
quell_time = est_print_time - QUELL_STALE_TIME
with self.lock:
if self.last_temp_time < print_time:
if self.last_temp_time < quell_time:
return 0., self.target_temp
return self.smoothed_temp, self.target_temp
def check_busy(self, eventtime):

View File

@@ -45,7 +45,7 @@ class StepperPosition:
class HomingMove:
def __init__(self, printer, endstops, toolhead=None):
self.printer = printer
self.endstops = endstops
self.endstops = [es for es in endstops if es[0].get_steppers()]
if toolhead is None:
toolhead = printer.lookup_object('toolhead')
self.toolhead = toolhead
@@ -71,7 +71,9 @@ class HomingMove:
sname = stepper.get_name()
kin_spos[sname] += offsets.get(sname, 0) * stepper.get_step_dist()
thpos = self.toolhead.get_position()
return list(kin.calc_position(kin_spos))[:3] + thpos[3:]
cpos = kin.calc_position(kin_spos)
return [cp if cp is not None else tp
for cp, tp in zip(cpos, thpos[:3])] + thpos[3:]
def homing_move(self, movepos, speed, probe_pos=False,
triggered=True, check_triggered=True):
# Notify start of homing/probing move
@@ -233,6 +235,10 @@ class Homing:
for s in kin.get_steppers()}
newpos = kin.calc_position(kin_spos)
for axis in force_axes:
if newpos[axis] is None:
raise self.printer.command_error(
"Cannot determine position of toolhead on "
"axis %s after homing" % "xyz"[axis])
homepos[axis] = newpos[axis]
self.toolhead.set_position(homepos)

View File

@@ -15,7 +15,7 @@ from . import bus
# Si7013 - Untested
# Si7020 - Untested
# Si7021 - Tested on Pico MCU
# SHT21 - Untested
# SHT21 - Tested on Linux MCU.
#
######################################################################
@@ -34,7 +34,7 @@ HTU21D_COMMANDS = {
}
HTU21D_RESOLUTION_MASK = 0x7E;
HTU21D_RESOLUTION_MASK = 0x7E
HTU21D_RESOLUTIONS = {
'TEMP14_HUM12':int('00000000',2),
'TEMP13_HUM10':int('10000000',2),
@@ -42,31 +42,40 @@ HTU21D_RESOLUTIONS = {
'TEMP11_HUM11':int('10000001',2)
}
ID_MAP = {
0x0D: 'SI7013',
0x14: 'SI7020',
0x15: 'SI7021',
0x31: 'SHT21',
0x01: 'SHT21',
0x32: 'HTU21D',
}
# Device with conversion time for tmp/resolution bit
# The format is:
# <CHIPNAME>:{id:<ID>, ..<RESOlUTION>:[<temp time>,<humidity time>].. }
HTU21D_DEVICES = {
'SI7013':{'id':0x0D,
'SI7013':{
'TEMP14_HUM12':[.11,.12],
'TEMP13_HUM10':[ .7, .5],
'TEMP12_HUM08':[ .4, .4],
'TEMP11_HUM11':[ .3, .7]},
'SI7020':{'id':0x14,
'SI7020':{
'TEMP14_HUM12':[.11,.12],
'TEMP13_HUM10':[ .7, .5],
'TEMP12_HUM08':[ .4, .4],
'TEMP11_HUM11':[ .3, .7]},
'SI7021':{'id':0x15,
'SI7021':{
'TEMP14_HUM12':[.11,.12],
'TEMP13_HUM10':[ .7, .5],
'TEMP12_HUM08':[ .4, .4],
'TEMP11_HUM11':[ .3, .7]},
'SHT21': {'id':0x31,
'SHT21': {
'TEMP14_HUM12':[.85,.29],
'TEMP13_HUM10':[.43, .9],
'TEMP12_HUM08':[.22, .4],
'TEMP11_HUM11':[.11,.15]},
'HTU21D':{'id':0x32,
'HTU21D':{
'TEMP14_HUM12':[.50,.16],
'TEMP13_HUM10':[.25, .5],
'TEMP12_HUM08':[.13, .3],
@@ -128,19 +137,16 @@ class HTU21D:
if self._chekCRC8(rdevId) != checksum:
logging.warning("htu21d: Reading deviceId !Checksum error!")
rdevId = rdevId >> 8
deviceId_list = list(
filter(
lambda elem: HTU21D_DEVICES[elem]['id'] == rdevId,HTU21D_DEVICES)
)
if len(deviceId_list) != 0:
logging.info("htu21d: Found Device Type %s" % deviceId_list[0])
guess_dev = ID_MAP.get(rdevId, "Unknown")
if guess_dev == self.deviceId:
logging.info("htu21d: Found Device Type %s" % guess_dev)
else:
logging.warning("htu21d: Unknown Device ID %#x " % rdevId)
if self.deviceId != deviceId_list[0]:
if self.deviceId != guess_dev:
logging.warning(
"htu21d: Found device %s. Forcing to type %s as config.",
deviceId_list[0],self.deviceId)
"htu21d: Found device %s. Forcing to type %s as config." %
(guess_dev, self.deviceId))
# Set Resolution
params = self.i2c.i2c_read([HTU21D_COMMANDS['READ']], 1)
@@ -152,7 +158,7 @@ class HTU21D:
def _sample_htu21d(self, eventtime):
try:
# Read Temeprature
# Read Temperature
if self.hold_master_mode:
params = self.i2c.i2c_write([HTU21D_COMMANDS['HTU21D_TEMP']])
else:

View File

@@ -53,6 +53,7 @@ class HX71xBase:
self._finish_measurements, UPDATE_INTERVAL)
# Command Configuration
self.query_hx71x_cmd = None
self.attach_probe_cmd = None
mcu.add_config_cmd(
"config_hx71x oid=%d gain_channel=%d dout_pin=%s sclk_pin=%s"
% (self.oid, self.gain_channel, self.dout_pin, self.sclk_pin))
@@ -64,10 +65,13 @@ class HX71xBase:
def _build_config(self):
self.query_hx71x_cmd = self.mcu.lookup_command(
"query_hx71x oid=%c rest_ticks=%u")
self.attach_probe_cmd = self.mcu.lookup_command(
"hx71x_attach_load_cell_probe oid=%c load_cell_probe_oid=%c")
self.ffreader.setup_query_command("query_hx71x_status oid=%c",
oid=self.oid,
cq=self.mcu.alloc_command_queue())
def get_mcu(self):
return self.mcu
@@ -83,6 +87,9 @@ class HX71xBase:
def add_client(self, callback):
self.batch_bulk.add_client(callback)
def attach_load_cell_probe(self, load_cell_probe_oid):
self.attach_probe_cmd.send([self.oid, load_cell_probe_oid])
# Measurement decoding
def _convert_samples(self, samples):
adc_factor = 1. / (1 << 23)

View File

@@ -35,7 +35,9 @@ class IdleTimeout:
printing_time = 0.
if self.state == "Printing":
printing_time = eventtime - self.last_print_start_systime
return { "state": self.state, "printing_time": printing_time }
return {"state": self.state,
"printing_time": printing_time,
"idle_timeout": self.idle_timeout}
def handle_ready(self):
self.toolhead = self.printer.lookup_object('toolhead')
self.timeout_timer = self.reactor.register_timer(self.timeout_handler)

View File

@@ -69,6 +69,8 @@ class AxisInputShaper:
ffi_lib.input_shaper_set_shaper_params(
sk, self.axis.encode(), self.n, self.A, self.T)
return success
def is_enabled(self):
return self.n > 0
def disable_shaping(self):
if self.saved is None and self.n:
self.saved = (self.n, self.A, self.T)
@@ -89,6 +91,8 @@ class InputShaper:
def __init__(self, config):
self.printer = config.get_printer()
self.printer.register_event_handler("klippy:connect", self.connect)
self.printer.register_event_handler("dual_carriage:update_kinematics",
self._update_kinematics)
self.toolhead = None
self.shapers = [AxisInputShaper('x', config),
AxisInputShaper('y', config)]
@@ -103,17 +107,23 @@ class InputShaper:
return self.shapers
def connect(self):
self.toolhead = self.printer.lookup_object("toolhead")
dual_carriage = self.printer.lookup_object('dual_carriage', None)
if dual_carriage is not None:
for shaper in self.shapers:
if shaper.is_enabled():
raise self.printer.config_error(
'Input shaper parameters cannot be configured via'
' [input_shaper] section with dual_carriage(s) '
' enabled. Refer to Klipper documentation on how '
' to configure input shaper for dual_carriage(s).')
return
# Configure initial values
self._update_input_shaping(error=self.printer.config_error)
def _get_input_shaper_stepper_kinematics(self, stepper):
# Lookup stepper kinematics
sk = stepper.get_stepper_kinematics()
if sk in self.orig_stepper_kinematics:
# Already processed this stepper kinematics unsuccessfully
return None
if sk in self.input_shaper_stepper_kinematics:
return sk
self.orig_stepper_kinematics.append(sk)
ffi_main, ffi_lib = chelper.get_ffi()
is_sk = ffi_main.gc(ffi_lib.input_shaper_alloc(), ffi_lib.free)
stepper.set_stepper_kinematics(is_sk)
@@ -121,8 +131,27 @@ class InputShaper:
if res < 0:
stepper.set_stepper_kinematics(sk)
return None
self.orig_stepper_kinematics.append(sk)
self.input_shaper_stepper_kinematics.append(is_sk)
return is_sk
def _update_kinematics(self):
if self.toolhead is None:
# Klipper initialization is not yet completed
return
ffi_main, ffi_lib = chelper.get_ffi()
kin = self.toolhead.get_kinematics()
for s in kin.get_steppers():
if s.get_trapq() is None:
continue
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)
def _update_input_shaping(self, error=None):
self.toolhead.flush_step_generation()
ffi_main, ffi_lib = chelper.get_ffi()

View File

@@ -1,6 +1,6 @@
# Support for PWM driven LEDs
#
# Copyright (C) 2019-2024 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
@@ -97,17 +97,15 @@ class LEDHelper:
for i in range(self.led_count):
set_template(gcmd, self.tcallbacks[i], self._check_transmit)
PIN_MIN_TIME = 0.100
MAX_SCHEDULE_TIME = 5.0
# Handler for PWM controlled LEDs
class PrinterPWMLED:
def __init__(self, config):
self.printer = printer = config.get_printer()
# Configure pwm pins
ppins = printer.lookup_object('pins')
max_duration = printer.lookup_object('mcu').max_nominal_duration()
cycle_time = config.getfloat('cycle_time', 0.010, above=0.,
maxval=MAX_SCHEDULE_TIME)
maxval=max_duration)
hardware_pwm = config.getboolean('hardware_pwm', False)
self.pins = []
for i, name in enumerate(("red", "green", "blue", "white")):
@@ -128,11 +126,12 @@ class PrinterPWMLED:
for idx, mcu_pin in self.pins:
mcu_pin.setup_start_value(color[idx], 0.)
def update_leds(self, led_state, print_time):
mcu = self.pins[0][1].get_mcu()
min_sched_time = mcu.min_schedule_time()
if print_time is None:
eventtime = self.printer.get_reactor().monotonic()
mcu = self.pins[0][1].get_mcu()
print_time = mcu.estimated_print_time(eventtime) + PIN_MIN_TIME
print_time = max(print_time, self.last_print_time + PIN_MIN_TIME)
print_time = mcu.estimated_print_time(eventtime) + min_sched_time
print_time = max(print_time, self.last_print_time + min_sched_time)
color = led_state[0]
for idx, mcu_pin in self.pins:
if self.prev_color[idx] != color[idx]:

View File

@@ -34,7 +34,7 @@ LIS_I2C_ADDR = 0x19
# Right shift for left justified registers.
FREEFALL_ACCEL = 9.80665
LIS2DW_SCALE = FREEFALL_ACCEL * 1.952 / 4
LIS3DH_SCALE = FREEFALL_ACCEL * 3.906 / 16
LIS3DH_SCALE = FREEFALL_ACCEL * 11.718 / 16
BATCH_UPDATES = 0.100
@@ -145,6 +145,9 @@ class LIS2DW:
"This is generally indicative of connection problems\n"
"(e.g. faulty wiring) or a faulty lis2dw chip."
% (dev_id, LIS2DW_DEV_ID))
if self.bus_type == SPI_SERIAL_TYPE:
# Disable I2C
self.set_reg(REG_LIS2DW_CTRL_REG2_ADDR, 0x06)
# Setup chip in requested query rate
# ODR/2, +-16g, low-pass filter, Low-noise abled
self.set_reg(REG_LIS2DW_CTRL_REG6_ADDR, 0x34)
@@ -167,8 +170,8 @@ class LIS2DW:
self.set_reg(REG_LIS2DW_CTRL_REG1_ADDR, 0x97)
# Disable all filtering
self.set_reg(REG_LIS2DW_CTRL_REG2_ADDR, 0)
# Set +-8g, High Resolution mode
self.set_reg(REG_LIS2DW_CTRL_REG4_ADDR, 0x28)
# Set +-16g, High Resolution mode
self.set_reg(REG_LIS2DW_CTRL_REG4_ADDR, 0x38)
# Enable FIFO
self.set_reg(REG_LIS2DW_CTRL_REG5_ADDR, 0x40)
# Stream mode

View File

@@ -53,7 +53,7 @@ class ApiClientHelper(object):
wh = self.printer.lookup_object('webhooks')
wh.register_mux_endpoint(path, key, value, self._add_webhooks_client)
# Class for handling commands related ot load cells
# Class for handling commands related to load cells
class LoadCellCommandHelper:
def __init__(self, config, load_cell):
self.printer = config.get_printer()

View File

@@ -0,0 +1,658 @@
# Load Cell Probe
#
# Copyright (C) 2025 Gareth Farrington <gareth@waves.ky>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging, math
import mcu
from . import probe, sos_filter, load_cell, hx71x, ads1220
np = None # delay NumPy import until configuration time
# constants for fixed point numbers
Q2_INT_BITS = 2
Q2_FRAC_BITS = (32 - (1 + Q2_INT_BITS))
Q16_INT_BITS = 16
Q16_FRAC_BITS = (32 - (1 + Q16_INT_BITS))
class TapAnalysis:
def __init__(self, samples):
nd_samples = np.asarray(samples, dtype=np.float64)
self.time = nd_samples[:, 0]
self.force = nd_samples[:, 1]
# convert to dictionary for JSON encoder
def to_dict(self):
return {
'time': self.time.tolist(), 'force': self.force.tolist(),
'is_valid': True,
}
# Access a parameter from config or GCode command via a consistent interface
# stores name and constraints to keep things DRY
class ParamHelper:
def __init__(self, config, name, type_name, default=None, minval=None,
maxval=None, above=None, below=None, max_len=None):
self._config_section = config.get_name()
self._config_error = config.error
self.name = name
self._type_name = type_name
self.value = default
self.minval = minval
self.maxval = maxval
self.above = above
self.below = below
self.max_len = max_len
# read from config once
self.value = self.get(config=config)
def _get_name(self, gcmd):
return self.name.upper() if gcmd else self.name
def _validate_float(self, description, error, value, above, below):
above = above or self.above
if above is not None and value <= above:
raise error("%s must be above %s" % (description, above))
below = below or self.below
if below is not None and value >= below:
raise error("%s must be below %s" % (description, below))
# support for validating individual options in a list of floats
def _validate_float_list(self, gcmd, values, above, below):
if gcmd:
description = ("Error on '%s': %s" % (
gcmd.get_commandline(), self._get_name(gcmd)))
error = gcmd.error
else:
description = ("Option '%s' in section '%s'" % (
self._get_name(gcmd), self._config_section))
error = self._config_error
if self.max_len is not None and len(values) > self.max_len:
raise error(
"%s has maximum length %s" % (description, self.max_len))
for value in values:
self._validate_float(description, error, value, above, below)
def _get_int(self, config, gcmd, minval, maxval):
get = gcmd.get_int if gcmd else config.getint
return get(self._get_name(gcmd), self.value, minval or self.minval,
maxval or self.maxval)
def _get_float(self, config, gcmd, minval, maxval, above, below):
get = gcmd.get_float if gcmd else config.getfloat
return get(self._get_name(gcmd), self.value, minval or self.minval,
maxval or self.maxval, above or self.above, below or self.below)
def _get_float_list(self, config, gcmd, above, below):
# this code defaults to the empty list, never return None
default = (self.value or [])
if gcmd:
# if the parameter isn't part of the command, return the default
if not self._get_name(gcmd) in gcmd.get_command_parameters():
return default
# parameter exists, always prefer whatever is in the command
value = gcmd.get(self._get_name(gcmd), default='')
# Return an empty list for empty value
if len(value.strip()) == 0:
return []
try:
float_list = [float(p.strip()) for p in value.split(',')]
except:
raise gcmd.error("Error on '%s': unable to parse %s" % (
gcmd.get_commandline(), value))
else:
float_list = config.getfloatlist(self._get_name(gcmd),
default=default)
if float_list:
self._validate_float_list(gcmd, float_list, above, below)
return float_list
def get(self, gcmd=None, minval=None, maxval=None, above=None, below=None,
config=None):
if config is None and gcmd is None:
return self.value
if self._type_name == 'int':
return self._get_int(config, gcmd, minval, maxval)
elif self._type_name == 'float':
return self._get_float(config, gcmd, minval, maxval, above, below)
else:
return self._get_float_list(config, gcmd, above, below)
def intParamHelper(config, name, default=None, minval=None, maxval=None):
return ParamHelper(config, name, 'int', default, minval=minval,
maxval=maxval)
def floatParamHelper(config, name, default=None, minval=None, maxval=None,
above=None, below=None):
return ParamHelper(config, name, 'float', default, minval=minval,
maxval=maxval, above=above, below=below)
def floatListParamHelper(config, name, default=None, above=None, below=None,
max_len=None):
return ParamHelper(config, name, 'float_list', default, above=above,
below=below, max_len=max_len)
# container for filter parameters
# allows different filter configurations to be compared
class ContinuousTareFilter:
def __init__(self, sps=None, drift=None, drift_delay=None, buzz=None,
buzz_delay=None, notches=None, notch_quality=None):
self.sps = sps
self.drift = drift
self.drift_delay = drift_delay
self.buzz = buzz
self.buzz_delay = buzz_delay
self.notches = notches
self.notch_quality = notch_quality
def __eq__(self, other):
if not isinstance(other, ContinuousTareFilter):
return False
return (
self.sps == other.sps and self.drift == other.drift and
self.drift_delay == other.drift_delay and self.buzz ==
other.buzz and self.buzz_delay == other.buzz_delay and
self.notches == other.notches and self.notch_quality ==
other.notch_quality)
# create a filter design from the parameters
def design_filter(self, error_func):
design = sos_filter.DigitalFilter(self.sps, error_func, self.drift,
self.drift_delay, self.buzz, self.buzz_delay, self.notches,
self.notch_quality)
fixed_filter = sos_filter.FixedPointSosFilter(
design.get_filter_sections(), design.get_initial_state(),
Q2_INT_BITS, Q16_INT_BITS)
return fixed_filter
# Combine ContinuousTareFilter and SosFilter into an easy-to-use class
class ContinuousTareFilterHelper:
def __init__(self, config, sensor, cmd_queue):
self._sensor = sensor
self._sps = self._sensor.get_samples_per_second()
max_filter_frequency = math.floor(self._sps / 2.)
# setup filter parameters
self._drift_param = floatParamHelper(config,
"drift_filter_cutoff_frequency", default=None, minval=0.1,
maxval=20.0)
self._drift_delay_param = intParamHelper(config, "drift_filter_delay",
default=2, minval=1, maxval=2)
self._buzz_param = floatParamHelper(config,
"buzz_filter_cutoff_frequency", default=None,
above=min(80.0, max_filter_frequency - 1.0),
below=max_filter_frequency)
self._buzz_delay_param = intParamHelper(config, "buzz_filter_delay",
default=2, minval=1, maxval=2)
self._notches_param = floatListParamHelper(config,
"notch_filter_frequencies", default=[], above=0.,
below=max_filter_frequency, max_len=2)
self._notch_quality_param = floatParamHelper(config,
"notch_filter_quality", default=2.0, minval=0.5, maxval=6.0)
# filter design specified in the config file, used for defaults
self._config_design = ContinuousTareFilter() # empty filter
self._config_design = self._build_filter()
# filter design currently inside the MCU
self._active_design = self._config_design
self._sos_filter = self._create_filter(
self._active_design.design_filter(config.error), cmd_queue)
def _build_filter(self, gcmd=None):
drift = self._drift_param.get(gcmd)
drift_delay = self._drift_delay_param.get(gcmd)
buzz = self._buzz_param.get(gcmd)
buzz_delay = self._buzz_delay_param.get(gcmd)
# notches must be between drift and buzz:
notches = self._notches_param.get(gcmd, above=drift, below=buzz)
notch_quality = self._notch_quality_param.get(gcmd)
return ContinuousTareFilter(self._sps, drift, drift_delay, buzz,
buzz_delay, notches, notch_quality)
def _create_filter(self, fixed_filter, cmd_queue):
return sos_filter.SosFilter(self._sensor.get_mcu(), cmd_queue,
fixed_filter, 4)
def update_from_command(self, gcmd, cq=None):
gcmd_filter = self._build_filter(gcmd)
# if filters are identical, no change required
if self._active_design == gcmd_filter:
return
# update MCU filter from GCode command
self._sos_filter.change_filter(
self._active_design.design_filter(gcmd.error))
def get_sos_filter(self):
return self._sos_filter
# check results from the collector for errors and raise an exception is found
def check_sensor_errors(results, printer):
samples, errors = results
if errors:
raise printer.command_error("Load cell sensor reported errors while"
" probing: %i errors, %i overflows" % (
errors[0], errors[1]))
return samples
class LoadCellProbeConfigHelper:
def __init__(self, config, load_cell_inst):
self._printer = config.get_printer()
self._load_cell = load_cell_inst
self._sensor = load_cell_inst.get_sensor()
self._rest_time = 1. / float(self._sensor.get_samples_per_second())
# Collect 4 x 60hz power cycles of data to average across power noise
self._tare_time_param = floatParamHelper(config, 'tare_time',
default=4. / 60., minval=0.01, maxval=1.0)
# triggering options
self._trigger_force_param = intParamHelper(config, 'trigger_force',
default=75, minval=10, maxval=250)
self._force_safety_limit_param = intParamHelper(config,
'force_safety_limit', minval=100, maxval=5000, default=2000)
def get_tare_samples(self, gcmd=None):
tare_time = self._tare_time_param.get(gcmd)
sps = self._sensor.get_samples_per_second()
return max(2, math.ceil(tare_time * sps))
def get_trigger_force_grams(self, gcmd=None):
return self._trigger_force_param.get(gcmd)
def get_safety_limit_grams(self, gcmd=None):
return self._force_safety_limit_param.get(gcmd)
def get_rest_time(self):
return self._rest_time
def get_safety_range(self, gcmd=None):
counts_per_gram = self._load_cell.get_counts_per_gram()
# calculate the safety band
zero = self._load_cell.get_reference_tare_counts()
safety_counts = int(counts_per_gram * self.get_safety_limit_grams(gcmd))
safety_min = int(zero - safety_counts)
safety_max = int(zero + safety_counts)
# don't allow a safety range outside the sensor's real range
sensor_min, sensor_max = self._load_cell.get_sensor().get_range()
if safety_min <= sensor_min or safety_max >= sensor_max:
cmd_err = self._printer.command_error
raise cmd_err("Load cell force_safety_limit exceeds sensor range!")
return safety_min, safety_max
# calculate 1/counts_per_gram in Q2 fixed point
def get_grams_per_count(self):
counts_per_gram = self._load_cell.get_counts_per_gram()
# The counts_per_gram could be so large that it becomes 0.0 when
# converted to Q2 format. This would mean the ADC range only measures a
# few grams which seems very unlikely. Treat this as an error:
if counts_per_gram >= 2**Q2_FRAC_BITS:
raise OverflowError("counts_per_gram value is too large to filter")
return sos_filter.to_fixed_32((1. / counts_per_gram), Q2_INT_BITS)
# McuLoadCellProbe is the interface to `load_cell_probe` on the MCU
# This also manages the SosFilter so all commands use one command queue
class McuLoadCellProbe:
WATCHDOG_MAX = 3
ERROR_SAFETY_RANGE = mcu.MCU_trsync.REASON_COMMS_TIMEOUT + 1
ERROR_OVERFLOW = mcu.MCU_trsync.REASON_COMMS_TIMEOUT + 2
ERROR_WATCHDOG = mcu.MCU_trsync.REASON_COMMS_TIMEOUT + 3
def __init__(self, config, load_cell_inst, sos_filter_inst, config_helper,
trigger_dispatch):
self._printer = config.get_printer()
self._load_cell = load_cell_inst
self._sos_filter = sos_filter_inst
self._config_helper = config_helper
self._sensor = load_cell_inst.get_sensor()
self._mcu = self._sensor.get_mcu()
# configure MCU objects
self._dispatch = trigger_dispatch
self._cmd_queue = self._dispatch.get_command_queue()
self._oid = self._mcu.create_oid()
self._config_commands()
self._home_cmd = None
self._query_cmd = None
self._set_range_cmd = None
self._mcu.register_config_callback(self._build_config)
self._printer.register_event_handler("klippy:connect", self._on_connect)
def _config_commands(self):
self._sos_filter.create_filter()
self._mcu.add_config_cmd(
"config_load_cell_probe oid=%d sos_filter_oid=%d" % (
self._oid, self._sos_filter.get_oid()))
def _build_config(self):
# Lookup commands
self._query_cmd = self._mcu.lookup_query_command(
"load_cell_probe_query_state oid=%c",
"load_cell_probe_state oid=%c is_homing_trigger=%c "
"trigger_ticks=%u", oid=self._oid, cq=self._cmd_queue)
self._set_range_cmd = self._mcu.lookup_command(
"load_cell_probe_set_range"
" oid=%c safety_counts_min=%i safety_counts_max=%i tare_counts=%i"
" trigger_grams=%u grams_per_count=%i", cq=self._cmd_queue)
self._home_cmd = self._mcu.lookup_command(
"load_cell_probe_home oid=%c trsync_oid=%c trigger_reason=%c"
" error_reason=%c clock=%u rest_ticks=%u timeout=%u",
cq=self._cmd_queue)
# the sensor data stream is connected on the MCU at the ready event
def _on_connect(self):
self._sensor.attach_load_cell_probe(self._oid)
def get_oid(self):
return self._oid
def get_mcu(self):
return self._mcu
def get_load_cell(self):
return self._load_cell
def get_dispatch(self):
return self._dispatch
def set_endstop_range(self, tare_counts, gcmd=None):
# update the load cell so it reflects the new tare value
self._load_cell.tare(tare_counts)
# update internal tare value
safety_min, safety_max = self._config_helper.get_safety_range(gcmd)
args = [self._oid, safety_min, safety_max, int(tare_counts),
self._config_helper.get_trigger_force_grams(gcmd),
self._config_helper.get_grams_per_count()]
self._set_range_cmd.send(args)
self._sos_filter.reset_filter()
def home_start(self, print_time):
clock = self._mcu.print_time_to_clock(print_time)
rest_time = self._config_helper.get_rest_time()
rest_ticks = self._mcu.seconds_to_clock(rest_time)
self._home_cmd.send([self._oid, self._dispatch.get_oid(),
mcu.MCU_trsync.REASON_ENDSTOP_HIT, self.ERROR_SAFETY_RANGE, clock,
rest_ticks, self.WATCHDOG_MAX], reqclock=clock)
def clear_home(self):
params = self._query_cmd.send([self._oid])
# The time of the first sample that triggered is in "trigger_ticks"
trigger_ticks = self._mcu.clock32_to_clock64(params['trigger_ticks'])
# clear trsync from load_cell_endstop
self._home_cmd.send([self._oid, 0, 0, 0, 0, 0, 0, 0])
return self._mcu.clock_to_print_time(trigger_ticks)
# Execute probing moves using the McuLoadCellProbe
class LoadCellProbingMove:
ERROR_MAP = {
mcu.MCU_trsync.REASON_COMMS_TIMEOUT: "Communication timeout during "
"homing",
McuLoadCellProbe.ERROR_SAFETY_RANGE: "Load Cell Probe Error: load "
"exceeds safety limit",
McuLoadCellProbe.ERROR_OVERFLOW: "Load Cell Probe Error: fixed point "
"math overflow",
McuLoadCellProbe.ERROR_WATCHDOG: "Load Cell Probe Error: timed out "
"waiting for sensor data"
}
def __init__(self, config, mcu_load_cell_probe, param_helper,
continuous_tare_filter_helper, config_helper):
self._printer = config.get_printer()
self._mcu_load_cell_probe = mcu_load_cell_probe
self._param_helper = param_helper
self._continuous_tare_filter_helper = continuous_tare_filter_helper
self._config_helper = config_helper
self._mcu = mcu_load_cell_probe.get_mcu()
self._load_cell = mcu_load_cell_probe.get_load_cell()
self._z_min_position = probe.lookup_minimum_z(config)
self._dispatch = mcu_load_cell_probe.get_dispatch()
probe.LookupZSteppers(config, self._dispatch.add_stepper)
# internal state tracking
self._tare_counts = 0
self._last_trigger_time = 0
def _start_collector(self):
toolhead = self._printer.lookup_object('toolhead')
# homing uses the toolhead last move time which gets special handling
# to significantly buffer print_time if the move queue has drained
print_time = toolhead.get_last_move_time()
collector = self._load_cell.get_collector()
collector.start_collecting(min_time=print_time)
return collector
# pauses for the last move to complete and then
# sets the endstop tare value and range
def _pause_and_tare(self, gcmd):
collector = self._start_collector()
num_samples = self._config_helper.get_tare_samples(gcmd)
# use collect_min collected samples are not wasted
results = collector.collect_min(num_samples)
tare_samples = check_sensor_errors(results, self._printer)
tare_counts = np.average(np.array(tare_samples)[:, 2].astype(float))
# update sos_filter with any gcode parameter changes
self._continuous_tare_filter_helper.update_from_command(gcmd)
self._mcu_load_cell_probe.set_endstop_range(tare_counts, gcmd)
def _home_start(self, print_time):
# start trsync
trigger_completion = self._dispatch.start(print_time)
self._mcu_load_cell_probe.home_start(print_time)
return trigger_completion
def home_start(self, print_time, sample_time, sample_count, rest_time,
triggered=True):
return self._home_start(print_time)
def home_wait(self, home_end_time):
self._dispatch.wait_end(home_end_time)
# trigger has happened, now to find out why...
res = self._dispatch.stop()
# clear the homing state so it stops processing samples
self._last_trigger_time = self._mcu_load_cell_probe.clear_home()
if res >= mcu.MCU_trsync.REASON_COMMS_TIMEOUT:
error = "Load Cell Probe Error: unknown reason code %i" % (res,)
if res in self.ERROR_MAP:
error = self.ERROR_MAP[res]
raise self._printer.command_error(error)
if res != mcu.MCU_trsync.REASON_ENDSTOP_HIT:
return 0.
return self._last_trigger_time
def get_steppers(self):
return self._dispatch.get_steppers()
# Probe towards z_min until the load_cell_probe on the MCU triggers
def probing_move(self, gcmd):
# do not permit probing if the load cell is not calibrated
if not self._load_cell.is_calibrated():
raise self._printer.command_error("Load Cell not calibrated")
# tare the sensor just before probing
self._pause_and_tare(gcmd)
# get params for the homing move
toolhead = self._printer.lookup_object('toolhead')
pos = toolhead.get_position()
pos[2] = self._z_min_position
speed = self._param_helper.get_probe_params(gcmd)['probe_speed']
phoming = self._printer.lookup_object('homing')
# start collector after tare samples are consumed
collector = self._start_collector()
# do homing move
return phoming.probing_move(self, pos, speed), collector
# Wait for the MCU to trigger with no movement
def probing_test(self, gcmd, timeout):
self._pause_and_tare(gcmd)
toolhead = self._printer.lookup_object('toolhead')
print_time = toolhead.get_last_move_time()
self._home_start(print_time)
return self.home_wait(print_time + timeout)
def get_status(self, eventtime):
return {
'tare_counts': self._tare_counts,
'last_trigger_time': self._last_trigger_time,
}
# Perform a single complete tap
class TappingMove:
def __init__(self, config, load_cell_probing_move, config_helper):
self._printer = config.get_printer()
self._load_cell_probing_move = load_cell_probing_move
self._config_helper = config_helper
# track results of the last tap
self._last_result = None
self._is_last_result_valid = False
# webhooks support
self._clients = load_cell.ApiClientHelper(config.get_printer())
name = config.get_name()
header = {"header": ["probe_tap_event"]}
self._clients.add_mux_endpoint("load_cell_probe/dump_taps",
"load_cell_probe", name, header)
# perform a probing move and a pullback move
def run_tap(self, gcmd):
# do the descending move
epos, collector = self._load_cell_probing_move.probing_move(gcmd)
# collect samples from the tap
toolhead = self._printer.lookup_object('toolhead')
toolhead.flush_step_generation()
move_end = toolhead.get_last_move_time()
results = collector.collect_until(move_end)
samples = check_sensor_errors(results, self._printer)
# Analyze the tap data
ppa = TapAnalysis(samples)
# broadcast tap event data:
self._clients.send({'tap': ppa.to_dict()})
self._is_last_result_valid = True
self._last_result = epos[2]
return epos, self._is_last_result_valid
def get_status(self, eventtime):
return {
'last_z_result': self._last_result,
'is_last_tap_valid': self._is_last_result_valid
}
# ProbeSession that implements Tap logic
class TapSession:
def __init__(self, config, tapping_move, probe_params_helper):
self._printer = config.get_printer()
self._tapping_move = tapping_move
self._probe_params_helper = probe_params_helper
# Session state
self._results = []
def start_probe_session(self, gcmd):
return self
def end_probe_session(self):
self._results = []
# probe until a single good sample is returned or retries are exhausted
def run_probe(self, gcmd):
epos, is_good = self._tapping_move.run_tap(gcmd)
self._results.append(epos)
def pull_probed_results(self):
res = self._results
self._results = []
return res
class LoadCellProbeCommands:
def __init__(self, config, load_cell_probing_move):
self._printer = config.get_printer()
self._load_cell_probing_move = load_cell_probing_move
self._register_commands()
def _register_commands(self):
# Register commands
gcode = self._printer.lookup_object('gcode')
gcode.register_command("LOAD_CELL_TEST_TAP",
self.cmd_LOAD_CELL_TEST_TAP, desc=self.cmd_LOAD_CELL_TEST_TAP_help)
cmd_LOAD_CELL_TEST_TAP_help = "Tap the load cell probe to verify operation"
def cmd_LOAD_CELL_TEST_TAP(self, gcmd):
taps = gcmd.get_int("TAPS", 3, minval=1, maxval=10)
timeout = gcmd.get_float("TIMEOUT", 30., minval=1., maxval=120.)
gcmd.respond_info("Tap the load cell %s times:" % (taps,))
reactor = self._printer.get_reactor()
for i in range(0, taps):
result = self._load_cell_probing_move.probing_test(gcmd, timeout)
if result == 0.:
# notify of error, likely due to timeout
raise gcmd.error("Test timeout out")
gcmd.respond_info("Tap Detected!")
# give the user some time for their finger to move away
reactor.pause(reactor.monotonic() + 0.2)
gcmd.respond_info("Test complete, %s taps detected" % (taps,))
class LoadCellPrinterProbe:
def __init__(self, config):
cfg_error = config.error
try:
global np
import numpy as np
except:
raise cfg_error("[load_cell_probe] requires the NumPy module")
self._printer = config.get_printer()
# Sensor types supported by load_cell_probe
sensors = {}
sensors.update(hx71x.HX71X_SENSOR_TYPES)
sensors.update(ads1220.ADS1220_SENSOR_TYPE)
sensor_class = config.getchoice('sensor_type', sensors)
sensor = sensor_class(config)
self._load_cell = load_cell.LoadCell(config, sensor)
# Read all user configuration and build modules
config_helper = LoadCellProbeConfigHelper(config, self._load_cell)
self._mcu = self._load_cell.get_sensor().get_mcu()
trigger_dispatch = mcu.TriggerDispatch(self._mcu)
continuous_tare_filter_helper = ContinuousTareFilterHelper(config,
sensor, trigger_dispatch.get_command_queue())
# Probe Interface
self._param_helper = probe.ProbeParameterHelper(config)
self._cmd_helper = probe.ProbeCommandHelper(config, self)
self._probe_offsets = probe.ProbeOffsetsHelper(config)
self._mcu_load_cell_probe = McuLoadCellProbe(config, self._load_cell,
continuous_tare_filter_helper.get_sos_filter(), config_helper,
trigger_dispatch)
load_cell_probing_move = LoadCellProbingMove(config,
self._mcu_load_cell_probe, self._param_helper,
continuous_tare_filter_helper, config_helper)
self._tapping_move = TappingMove(config, load_cell_probing_move,
config_helper)
tap_session = TapSession(config, self._tapping_move, self._param_helper)
self._probe_session = probe.ProbeSessionHelper(config,
self._param_helper, tap_session.start_probe_session)
# printer integration
LoadCellProbeCommands(config, load_cell_probing_move)
probe.ProbeVirtualEndstopDeprecation(config)
self._printer.add_object('probe', self)
def get_probe_params(self, gcmd=None):
return self._param_helper.get_probe_params(gcmd)
def get_offsets(self):
return self._probe_offsets.get_offsets()
def start_probe_session(self, gcmd):
return self._probe_session.start_probe_session(gcmd)
def get_status(self, eventtime):
status = self._cmd_helper.get_status(eventtime)
status.update(self._load_cell.get_status(eventtime))
status.update(self._tapping_move.get_status(eventtime))
return status
def load_config(config):
return LoadCellPrinterProbe(config)

View File

@@ -5,6 +5,14 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging, bisect
# Helper to lookup the Z stepper config section
def lookup_z_endstop_config(config):
if config.has_section('stepper_z'):
return config.getsection('stepper_z')
elif config.has_section('carriage z'):
return config.getsection('carriage z')
return None
class ManualProbe:
def __init__(self, config):
self.printer = config.get_printer()
@@ -14,9 +22,13 @@ class ManualProbe:
self.gcode.register_command('MANUAL_PROBE', self.cmd_MANUAL_PROBE,
desc=self.cmd_MANUAL_PROBE_help)
# Endstop value for cartesian printers with separate Z axis
zconfig = config.getsection('stepper_z')
self.z_position_endstop = zconfig.getfloat('position_endstop', None,
note_valid=False)
zconfig = lookup_z_endstop_config(config)
if zconfig is not None:
self.z_position_endstop = zconfig.getfloat('position_endstop', None,
note_valid=False)
self.z_endstop_config_name = zconfig.get_name()
else:
self.z_position_endstop = self.z_endstop_config_name = None
# Endstop values for linear delta printers with vertical A,B,C towers
a_tower_config = config.getsection('stepper_a')
self.a_position_endstop = a_tower_config.getfloat('position_endstop',
@@ -67,11 +79,13 @@ class ManualProbe:
return
z_pos = self.z_position_endstop - kin_pos[2]
self.gcode.respond_info(
"stepper_z: position_endstop: %.3f\n"
"%s: position_endstop: %.3f\n"
"The SAVE_CONFIG command will update the printer config file\n"
"with the above and restart the printer." % (z_pos,))
"with the above and restart the printer." % (
self.z_endstop_config_name, z_pos,))
configfile = self.printer.lookup_object('configfile')
configfile.set('stepper_z', 'position_endstop', "%.3f" % (z_pos,))
configfile.set(self.z_endstop_config_name, 'position_endstop',
"%.3f" % (z_pos,))
cmd_Z_ENDSTOP_CALIBRATE_help = "Calibrate a Z endstop"
def cmd_Z_ENDSTOP_CALIBRATE(self, gcmd):
ManualProbeHelper(self.printer, gcmd, self.z_endstop_finalize)
@@ -83,11 +97,12 @@ class ManualProbe:
else:
new_calibrate = self.z_position_endstop - offset
self.gcode.respond_info(
"stepper_z: position_endstop: %.3f\n"
"%s: position_endstop: %.3f\n"
"The SAVE_CONFIG command will update the printer config file\n"
"with the above and restart the printer." % (new_calibrate))
configfile.set('stepper_z', 'position_endstop',
"%.3f" % (new_calibrate,))
"with the above and restart the printer." % (
self.z_endstop_config_name, new_calibrate))
configfile.set(self.z_endstop_config_name, 'position_endstop',
"%.3f" % (new_calibrate,))
def cmd_Z_OFFSET_APPLY_DELTA_ENDSTOPS(self,gcmd):
offset = self.gcode_move.get_status()['homing_origin'].z
configfile = self.printer.lookup_object('configfile')

View File

@@ -1,8 +1,9 @@
# Support for a manual controlled stepper
#
# 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
import stepper, chelper
from . import force_move
@@ -11,7 +12,7 @@ class ManualStepper:
self.printer = config.get_printer()
if config.get('endstop_pin', None) is not None:
self.can_home = True
self.rail = stepper.PrinterRail(
self.rail = stepper.LookupRail(
config, need_position_minmax=False, default_position_endstop=0.)
self.steppers = self.rail.get_steppers()
else:
@@ -21,13 +22,19 @@ class ManualStepper:
self.velocity = config.getfloat('velocity', 5., above=0.)
self.accel = self.homing_accel = config.getfloat('accel', 0., minval=0.)
self.next_cmd_time = 0.
self.commanded_pos = 0.
self.pos_min = config.getfloat('position_min', None)
self.pos_max = config.getfloat('position_max', None)
# Setup iterative solver
ffi_main, ffi_lib = chelper.get_ffi()
self.trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free)
self.trapq_append = ffi_lib.trapq_append
self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves
self.motion_queuing = self.printer.load_object(config, 'motion_queuing')
self.trapq = self.motion_queuing.allocate_trapq()
self.trapq_append = self.motion_queuing.lookup_trapq_append()
self.rail.setup_itersolve('cartesian_stepper_alloc', b'x')
self.rail.set_trapq(self.trapq)
# Registered with toolhead as an axtra axis
self.axis_gcode_id = None
self.instant_corner_v = 0.
self.gaxis_limit_velocity = self.gaxis_limit_accel = 0.
# Register commands
stepper_name = config.get_name().split()[1]
gcode = self.printer.lookup_object('gcode')
@@ -54,21 +61,25 @@ class ManualStepper:
se.motor_disable(self.next_cmd_time)
self.sync_print_time()
def do_set_position(self, setpos):
self.rail.set_position([setpos, 0., 0.])
def do_move(self, movepos, speed, accel, sync=True):
self.sync_print_time()
cp = self.rail.get_commanded_position()
toolhead = self.printer.lookup_object('toolhead')
toolhead.flush_step_generation()
self.commanded_pos = setpos
self.rail.set_position([self.commanded_pos, 0., 0.])
def _submit_move(self, movetime, movepos, speed, accel):
cp = self.commanded_pos
dist = movepos - cp
axis_r, accel_t, cruise_t, cruise_v = force_move.calc_move_time(
dist, speed, accel)
self.trapq_append(self.trapq, self.next_cmd_time,
self.trapq_append(self.trapq, movetime,
accel_t, cruise_t, accel_t,
cp, 0., 0., axis_r, 0., 0.,
0., cruise_v, accel)
self.next_cmd_time = self.next_cmd_time + accel_t + cruise_t + accel_t
self.rail.generate_steps(self.next_cmd_time)
self.trapq_finalize_moves(self.trapq, self.next_cmd_time + 99999.9,
self.next_cmd_time + 99999.9)
self.commanded_pos = movepos
return movetime + accel_t + cruise_t + accel_t
def do_move(self, movepos, speed, accel, sync=True):
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)
if sync:
@@ -85,6 +96,10 @@ class ManualStepper:
triggered, check_trigger)
cmd_MANUAL_STEPPER_help = "Command a manually configured stepper"
def cmd_MANUAL_STEPPER(self, gcmd):
if gcmd.get('GCODE_AXIS', None) is not None:
return self.command_with_gcode_axis(gcmd)
if self.axis_gcode_id is not None:
raise gcmd.error("Must unregister from gcode axis first")
enable = gcmd.get_int('ENABLE', None)
if enable is not None:
self.do_enable(enable)
@@ -96,19 +111,90 @@ class ManualStepper:
homing_move = gcmd.get_int('STOP_ON_ENDSTOP', 0)
if homing_move:
movepos = gcmd.get_float('MOVE')
if ((self.pos_min is not None and movepos < self.pos_min)
or (self.pos_max is not None and movepos > self.pos_max)):
raise gcmd.error("Move out of range")
self.do_homing_move(movepos, speed, accel,
homing_move > 0, abs(homing_move) == 1)
elif gcmd.get_float('MOVE', None) is not None:
movepos = gcmd.get_float('MOVE')
if ((self.pos_min is not None and movepos < self.pos_min)
or (self.pos_max is not None and movepos > self.pos_max)):
raise gcmd.error("Move out of range")
sync = gcmd.get_int('SYNC', 1)
self.do_move(movepos, speed, accel, sync)
elif gcmd.get_int('SYNC', 0):
self.sync_print_time()
# Register as a gcode axis
def command_with_gcode_axis(self, gcmd):
gcode_move = self.printer.lookup_object("gcode_move")
toolhead = self.printer.lookup_object('toolhead')
gcode_axis = gcmd.get('GCODE_AXIS').upper()
instant_corner_v = gcmd.get_float('INSTANTANEOUS_CORNER_VELOCITY', 1.,
minval=0.)
limit_velocity = gcmd.get_float('LIMIT_VELOCITY', 999999.9, above=0.)
limit_accel = gcmd.get_float('LIMIT_ACCEL', 999999.9, above=0.)
if self.axis_gcode_id is not None:
if gcode_axis:
raise gcmd.error("Must unregister axis first")
# Unregister
toolhead.remove_extra_axis(self)
self.axis_gcode_id = None
return
if (len(gcode_axis) != 1 or not gcode_axis.isupper()
or gcode_axis in "XYZEFN"):
if not gcode_axis:
# Request to unregister already unregistered axis
return
raise gcmd.error("Not a valid GCODE_AXIS")
for ea in toolhead.get_extra_axes():
if ea is not None and ea.get_axis_gcode_id() == gcode_axis:
raise gcmd.error("Axis '%s' already registered" % (gcode_axis,))
self.axis_gcode_id = gcode_axis
self.instant_corner_v = instant_corner_v
self.gaxis_limit_velocity = limit_velocity
self.gaxis_limit_accel = limit_accel
toolhead.add_extra_axis(self, self.commanded_pos)
def process_move(self, print_time, move, ea_index):
axis_r = move.axes_r[ea_index]
start_pos = move.start_pos[ea_index]
accel = move.accel * axis_r
start_v = move.start_v * axis_r
cruise_v = move.cruise_v * axis_r
self.trapq_append(self.trapq, print_time,
move.accel_t, move.cruise_t, move.decel_t,
start_pos, 0., 0.,
1., 0., 0.,
start_v, cruise_v, accel)
self.commanded_pos = move.end_pos[ea_index]
def check_move(self, move, ea_index):
# Check move is in bounds
movepos = move.end_pos[ea_index]
if ((self.pos_min is not None and movepos < self.pos_min)
or (self.pos_max is not None and movepos > self.pos_max)):
raise move.move_error()
# Check if need to limit maximum velocity and acceleration
axis_ratio = move.move_d / abs(move.axes_d[ea_index])
limit_velocity = self.gaxis_limit_velocity * axis_ratio
limit_accel = self.gaxis_limit_accel * axis_ratio
if not move.is_kinematic_move and self.accel:
limit_accel = min(limit_accel, self.accel * axis_ratio)
move.limit_speed(limit_velocity, limit_accel)
def calc_junction(self, prev_move, move, ea_index):
diff_r = move.axes_r[ea_index] - prev_move.axes_r[ea_index]
if diff_r:
return (self.instant_corner_v / abs(diff_r))**2
return move.max_cruise_v2
def get_axis_gcode_id(self):
return self.axis_gcode_id
def get_trapq(self):
return self.trapq
# Toolhead wrappers to support homing
def flush_step_generation(self):
self.sync_print_time()
toolhead = self.printer.lookup_object('toolhead')
toolhead.flush_step_generation()
def get_position(self):
return [self.rail.get_commanded_position(), 0., 0., 0.]
return [self.commanded_pos, 0., 0., 0.]
def set_position(self, newpos, homing_axes=""):
self.do_set_position(newpos[0])
def get_last_move_time(self):
@@ -117,7 +203,18 @@ class ManualStepper:
def dwell(self, delay):
self.next_cmd_time += max(0., delay)
def drip_move(self, newpos, speed, drip_completion):
self.do_move(newpos[0], speed, self.homing_accel)
# Submit move to trapq
self.sync_print_time()
maxtime = self._submit_move(self.next_cmd_time, newpos[0],
speed, self.homing_accel)
# Drip updates to motors
toolhead = self.printer.lookup_object('toolhead')
toolhead.drip_update_time(maxtime, 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()
def get_kinematics(self):
return self
def get_steppers(self):

View File

@@ -1,75 +1,14 @@
# MCP4018 digipot support (via bit-banging)
# MCP4018 digipot support
#
# Copyright (C) 2019 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.
class SoftwareI2C:
def __init__(self, config, addr):
self.addr = addr << 1
self.update_pin_cmd = None
# Lookup pins
ppins = config.get_printer().lookup_object('pins')
scl_pin = config.get('scl_pin')
scl_params = ppins.lookup_pin(scl_pin, share_type='sw_scl')
self.mcu = scl_params['chip']
self.scl_pin = scl_params['pin']
self.scl_main = scl_params.get('class')
if self.scl_main is None:
self.scl_main = scl_params['class'] = self
self.scl_oid = self.mcu.create_oid()
self.cmd_queue = self.mcu.alloc_command_queue()
self.mcu.register_config_callback(self.build_config)
else:
self.scl_oid = self.scl_main.scl_oid
self.cmd_queue = self.scl_main.cmd_queue
sda_params = ppins.lookup_pin(config.get('sda_pin'))
self.sda_oid = self.mcu.create_oid()
if sda_params['chip'] != self.mcu:
raise ppins.error("%s: scl_pin and sda_pin must be on same mcu" % (
config.get_name(),))
self.mcu.add_config_cmd("config_digital_out oid=%d pin=%s"
" value=%d default_value=%d max_duration=%d" % (
self.sda_oid, sda_params['pin'], 1, 1, 0))
def get_mcu(self):
return self.mcu
def build_config(self):
self.mcu.add_config_cmd("config_digital_out oid=%d pin=%s value=%d"
" default_value=%d max_duration=%d" % (
self.scl_oid, self.scl_pin, 1, 1, 0))
self.update_pin_cmd = self.mcu.lookup_command(
"update_digital_out oid=%c value=%c", cq=self.cmd_queue)
def i2c_write(self, msg, minclock=0, reqclock=0):
msg = [self.addr] + msg
send = self.scl_main.update_pin_cmd.send
# Send ack
send([self.sda_oid, 0], minclock=minclock, reqclock=reqclock)
send([self.scl_oid, 0], minclock=minclock, reqclock=reqclock)
# Send bytes
sda_last = 0
for data in msg:
# Transmit 8 data bits
for i in range(8):
sda_next = not not (data & (0x80 >> i))
if sda_last != sda_next:
sda_last = sda_next
send([self.sda_oid, sda_last],
minclock=minclock, reqclock=reqclock)
send([self.scl_oid, 1], minclock=minclock, reqclock=reqclock)
send([self.scl_oid, 0], minclock=minclock, reqclock=reqclock)
# Transmit clock for ack
send([self.scl_oid, 1], minclock=minclock, reqclock=reqclock)
send([self.scl_oid, 0], minclock=minclock, reqclock=reqclock)
# Send stop
if sda_last:
send([self.sda_oid, 0], minclock=minclock, reqclock=reqclock)
send([self.scl_oid, 1], minclock=minclock, reqclock=reqclock)
send([self.sda_oid, 1], minclock=minclock, reqclock=reqclock)
from . import bus
class mcp4018:
def __init__(self, config):
self.printer = config.get_printer()
self.i2c = SoftwareI2C(config, 0x2f)
self.i2c = bus.MCU_I2C_from_config(config, default_addr=0x2f)
self.scale = config.getfloat('scale', 1., above=0.)
self.start_value = config.getfloat('wiper',
minval=0., maxval=self.scale)

View File

@@ -0,0 +1,100 @@
# Helper code for low-level motion queuing and flushing
#
# Copyright (C) 2025 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
import chelper
MOVE_HISTORY_EXPIRE = 30.
class PrinterMotionQueuing:
def __init__(self, config):
self.printer = config.get_printer()
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
self.clear_history_time = 0.
is_debug = self.printer.get_start_args().get('debugoutput') is not None
self.is_debugoutput = is_debug
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):
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)
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)
self.steppersyncs.append((mcu, ss))
return ss
def register_flush_callback(self, callback):
self.flush_callbacks.append(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):
# 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(),))
# Determine maximum history to keep
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
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 load_config(config):
return PrinterMotionQueuing(config)

View File

@@ -1,6 +1,6 @@
# PWM and digital output pin handling
#
# Copyright (C) 2017-2024 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2017-2025 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging, ast
@@ -11,8 +11,6 @@ from .display import display
# G-Code request queuing helper
######################################################################
PIN_MIN_TIME = 0.100
# Helper code to queue g-code requests
class GCodeRequestQueue:
def __init__(self, config, mcu, callback):
@@ -22,15 +20,17 @@ class GCodeRequestQueue:
self.rqueue = []
self.next_min_flush_time = 0.
self.toolhead = None
mcu.register_flush_callback(self._flush_notification)
motion_queuing = printer.load_object(config, 'motion_queuing')
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')
def _flush_notification(self, print_time, clock):
def _flush_notification(self, must_flush_time, max_step_gen_time):
min_sched_time = self.mcu.min_schedule_time()
rqueue = self.rqueue
while rqueue:
next_time = max(rqueue[0][0], self.next_min_flush_time)
if next_time > print_time:
if next_time > must_flush_time:
return
# Skip requests that have been overridden with a following request
pos = 0
@@ -49,19 +49,21 @@ class GCodeRequestQueue:
if action == "delay":
pos -= 1
del rqueue[:pos+1]
self.next_min_flush_time = next_time + max(min_wait, PIN_MIN_TIME)
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)
self.toolhead.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)
self.toolhead.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)))
def send_async_request(self, value, print_time=None):
min_sched_time = self.mcu.min_schedule_time()
if print_time is None:
systime = self.printer.get_reactor().monotonic()
print_time = self.mcu.estimated_print_time(systime + PIN_MIN_TIME)
print_time = self.mcu.estimated_print_time(systime + min_sched_time)
while 1:
next_time = max(print_time, self.next_min_flush_time)
# Invoke callback for the request
@@ -72,7 +74,7 @@ class GCodeRequestQueue:
action, min_wait = ret
if action == "discard":
break
self.next_min_flush_time = next_time + max(min_wait, PIN_MIN_TIME)
self.next_min_flush_time = next_time + max(min_wait, min_sched_time)
if action != "delay":
break
@@ -184,8 +186,6 @@ def lookup_template_eval(config):
# Main output pin handling
######################################################################
MAX_SCHEDULE_TIME = 5.0
class PrinterOutputPin:
def __init__(self, config):
self.printer = config.get_printer()
@@ -194,8 +194,9 @@ class PrinterOutputPin:
self.is_pwm = config.getboolean('pwm', False)
if self.is_pwm:
self.mcu_pin = ppins.setup_pin('pwm', config.get('pin'))
max_duration = self.mcu_pin.get_mcu().max_nominal_duration()
cycle_time = config.getfloat('cycle_time', 0.100, above=0.,
maxval=MAX_SCHEDULE_TIME)
maxval=max_duration)
hardware_pwm = config.getboolean('hardware_pwm', False)
self.mcu_pin.setup_cycle_time(cycle_time, hardware_pwm)
self.scale = config.getfloat('scale', 1., above=0.)

View File

@@ -235,7 +235,7 @@ class Palette2:
"Initialize the print, and check connection with the Palette 2")
def cmd_O1(self, gcmd):
logging.info("Initializing print with Pallete 2")
logging.info("Initializing print with Palette 2")
if not self._check_P2(gcmd):
raise self.printer.command_error(
"Cannot initialize print, palette 2 is not connected")

View File

@@ -3,7 +3,7 @@
# Copyright (C) 2022 Ricardo Alcantara <ricardo@vulcanolabs.com>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
from . import bus, led, mcp4018
from . import bus, led
BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000
@@ -25,10 +25,7 @@ PCA9632_LED3 = 0x06
class PCA9632:
def __init__(self, config):
self.printer = printer = config.get_printer()
if config.get("scl_pin", None) is not None:
self.i2c = mcp4018.SoftwareI2C(config, 98)
else:
self.i2c = bus.MCU_I2C_from_config(config, default_addr=98)
self.i2c = bus.MCU_I2C_from_config(config, default_addr=98)
color_order = config.get("color_order", "RGBW")
if sorted(color_order) != sorted("RGBW"):
raise config.error("Invalid color_order '%s'" % (color_order,))

View File

@@ -15,6 +15,11 @@ class PrintStats:
self.gcode.register_command(
"SET_PRINT_STATS_INFO", self.cmd_SET_PRINT_STATS_INFO,
desc=self.cmd_SET_PRINT_STATS_INFO_help)
printer.register_event_handler("extruder:activate_extruder",
self._handle_activate_extruder)
def _handle_activate_extruder(self):
gc_status = self.gcode_move.get_status()
self.last_epos = gc_status['position'].e
def _update_filament_usage(self, eventtime):
gc_status = self.gcode_move.get_status(eventtime)
cur_epos = gc_status['position'].e

View File

@@ -172,17 +172,40 @@ class ProbeCommandHelper:
configfile = self.printer.lookup_object('configfile')
configfile.set(self.name, 'z_offset', "%.3f" % (new_calibrate,))
# Helper to lookup the minimum Z position for the printer
def lookup_minimum_z(config):
zconfig = manual_probe.lookup_z_endstop_config(config)
if zconfig is not None:
return zconfig.getfloat('position_min', 0., note_valid=False)
pconfig = config.getsection('printer')
return pconfig.getfloat('minimum_z_position', 0., note_valid=False)
# Helper to lookup all the Z axis steppers
class LookupZSteppers:
def __init__(self, config, add_stepper_cb):
self.printer = config.get_printer()
self.add_stepper_cb = add_stepper_cb
self.printer.register_event_handler('klippy:mcu_identify',
self._handle_mcu_identify)
def _handle_mcu_identify(self):
kin = self.printer.lookup_object('toolhead').get_kinematics()
for stepper in kin.get_steppers():
if stepper.is_active_axis('z'):
self.add_stepper_cb(stepper)
# Homing via probe:z_virtual_endstop
class HomingViaProbeHelper:
def __init__(self, config, mcu_probe):
def __init__(self, config, mcu_probe, param_helper):
self.printer = config.get_printer()
self.mcu_probe = mcu_probe
self.param_helper = param_helper
self.multi_probe_pending = False
self.z_min_position = lookup_minimum_z(config)
self.results = []
LookupZSteppers(config, self.mcu_probe.add_stepper)
# Register z_virtual_endstop pin
self.printer.lookup_object('pins').register_chip('probe', self)
# Register event handlers
self.printer.register_event_handler('klippy:mcu_identify',
self._handle_mcu_identify)
self.printer.register_event_handler("homing:homing_move_begin",
self._handle_homing_move_begin)
self.printer.register_event_handler("homing:homing_move_end",
@@ -193,11 +216,6 @@ class HomingViaProbeHelper:
self._handle_home_rails_end)
self.printer.register_event_handler("gcode:command_error",
self._handle_command_error)
def _handle_mcu_identify(self):
kin = self.printer.lookup_object('toolhead').get_kinematics()
for stepper in kin.get_steppers():
if stepper.is_active_axis('z'):
self.mcu_probe.add_stepper(stepper)
def _handle_homing_move_begin(self, hmove):
if self.mcu_probe in hmove.get_mcu_endstops():
self.mcu_probe.probe_prepare(hmove)
@@ -227,24 +245,42 @@ class HomingViaProbeHelper:
if pin_params['invert'] or pin_params['pullup']:
raise pins.error("Can not pullup/invert probe virtual endstop")
return self.mcu_probe
# Helper to convert probe based commands to use homing module
def start_probe_session(self, gcmd):
self.mcu_probe.multi_probe_begin()
self.results = []
return self
def run_probe(self, gcmd):
toolhead = self.printer.lookup_object('toolhead')
pos = toolhead.get_position()
pos[2] = self.z_min_position
speed = self.param_helper.get_probe_params(gcmd)['probe_speed']
phoming = self.printer.lookup_object('homing')
self.results.append(phoming.probing_move(self.mcu_probe, pos, speed))
def pull_probed_results(self):
res = self.results
self.results = []
return res
def end_probe_session(self):
self.results = []
self.mcu_probe.multi_probe_end()
# Helper to track multiple probe attempts in a single command
class ProbeSessionHelper:
def __init__(self, config, mcu_probe):
self.printer = config.get_printer()
self.mcu_probe = mcu_probe
gcode = self.printer.lookup_object('gcode')
class ProbeVirtualEndstopDeprecation:
def __init__(self, config):
self._name = config.get_name()
self._printer = config.get_printer()
# Register z_virtual_endstop pin
self._printer.lookup_object('pins').register_chip('probe', self)
def setup_pin(self, pin_type, pin_params):
raise self._printer.config_error(
"Module [%s] does not support `probe:z_virtual_endstop`"
", use a pin instead." % (self._name,))
# Helper to read multi-sample parameters from config
class ProbeParameterHelper:
def __init__(self, config):
gcode = config.get_printer().lookup_object('gcode')
self.dummy_gcode_cmd = gcode.create_gcode_command("", "", {})
# Infer Z position to move to during a probe
if config.has_section('stepper_z'):
zconfig = config.getsection('stepper_z')
self.z_position = zconfig.getfloat('position_min', 0.,
note_valid=False)
else:
pconfig = config.getsection('printer')
self.z_position = pconfig.getfloat('minimum_z_position', 0.,
note_valid=False)
self.homing_helper = HomingViaProbeHelper(config, mcu_probe)
# Configurable probing speeds
self.speed = config.getfloat('speed', 5.0, above=0.)
self.lift_speed = config.getfloat('lift_speed', self.speed, above=0.)
@@ -259,34 +295,6 @@ class ProbeSessionHelper:
minval=0.)
self.samples_retries = config.getint('samples_tolerance_retries', 0,
minval=0)
# Session state
self.multi_probe_pending = False
self.results = []
# Register event handlers
self.printer.register_event_handler("gcode:command_error",
self._handle_command_error)
def _handle_command_error(self):
if self.multi_probe_pending:
try:
self.end_probe_session()
except:
logging.exception("Multi-probe end")
def _probe_state_error(self):
raise self.printer.command_error(
"Internal probe error - start/end probe session mismatch")
def start_probe_session(self, gcmd):
if self.multi_probe_pending:
self._probe_state_error()
self.mcu_probe.multi_probe_begin()
self.multi_probe_pending = True
self.results = []
return self
def end_probe_session(self):
if not self.multi_probe_pending:
self._probe_state_error()
self.results = []
self.multi_probe_pending = False
self.mcu_probe.multi_probe_end()
def get_probe_params(self, gcmd=None):
if gcmd is None:
gcmd = self.dummy_gcode_cmd
@@ -307,15 +315,49 @@ class ProbeSessionHelper:
'samples_tolerance': samples_tolerance,
'samples_tolerance_retries': samples_retries,
'samples_result': samples_result}
def _probe(self, speed):
# Helper to track multiple probe attempts in a single command
class ProbeSessionHelper:
def __init__(self, config, param_helper, start_session_cb):
self.printer = config.get_printer()
self.param_helper = param_helper
self.start_session_cb = start_session_cb
# Session state
self.hw_probe_session = None
self.results = []
# Register event handlers
self.printer.register_event_handler("gcode:command_error",
self._handle_command_error)
def _handle_command_error(self):
if self.hw_probe_session is not None:
try:
self.end_probe_session()
except:
logging.exception("Multi-probe end")
def _probe_state_error(self):
raise self.printer.command_error(
"Internal probe error - start/end probe session mismatch")
def start_probe_session(self, gcmd):
if self.hw_probe_session is not None:
self._probe_state_error()
self.hw_probe_session = self.start_session_cb(gcmd)
self.results = []
return self
def end_probe_session(self):
hw_probe_session = self.hw_probe_session
if hw_probe_session is None:
self._probe_state_error()
self.results = []
self.hw_probe_session = None
hw_probe_session.end_probe_session()
def _probe(self, gcmd):
toolhead = self.printer.lookup_object('toolhead')
curtime = self.printer.get_reactor().monotonic()
if 'z' not in toolhead.get_status(curtime)['homed_axes']:
raise self.printer.command_error("Must home before probe")
pos = toolhead.get_position()
pos[2] = self.z_position
try:
epos = self.mcu_probe.probing_move(pos, speed)
self.hw_probe_session.run_probe(gcmd)
epos = self.hw_probe_session.pull_probed_results()[0]
except self.printer.command_error as e:
reason = str(e)
if "Timeout during endstop homing" in reason:
@@ -329,9 +371,9 @@ class ProbeSessionHelper:
% (epos[0], epos[1], epos[2]))
return epos[:3]
def run_probe(self, gcmd):
if not self.multi_probe_pending:
if self.hw_probe_session is None:
self._probe_state_error()
params = self.get_probe_params(gcmd)
params = self.param_helper.get_probe_params(gcmd)
toolhead = self.printer.lookup_object('toolhead')
probexy = toolhead.get_position()[:2]
retries = 0
@@ -339,7 +381,7 @@ class ProbeSessionHelper:
sample_count = params['samples']
while len(positions) < sample_count:
# Probe position
pos = self._probe(params['probe_speed'])
pos = self._probe(gcmd)
positions.append(pos)
# Check samples tolerance
z_positions = [p[2] for p in positions]
@@ -544,9 +586,6 @@ class ProbeEndstopWrapper:
return
self._raise_probe()
self.multi = 'OFF'
def probing_move(self, pos, speed):
phoming = self.printer.lookup_object('homing')
return phoming.probing_move(self, pos, speed)
def probe_prepare(self, hmove):
if self.multi == 'OFF' or self.multi == 'FIRST':
self._lower_probe()
@@ -566,9 +605,13 @@ class PrinterProbe:
self.cmd_helper = ProbeCommandHelper(config, self,
self.mcu_probe.query_endstop)
self.probe_offsets = ProbeOffsetsHelper(config)
self.probe_session = ProbeSessionHelper(config, self.mcu_probe)
self.param_helper = ProbeParameterHelper(config)
self.homing_helper = HomingViaProbeHelper(config, self.mcu_probe,
self.param_helper)
self.probe_session = ProbeSessionHelper(
config, self.param_helper, self.homing_helper.start_probe_session)
def get_probe_params(self, gcmd=None):
return self.probe_session.get_probe_params(gcmd)
return self.param_helper.get_probe_params(gcmd)
def get_offsets(self):
return self.probe_offsets.get_offsets()
def get_status(self, eventtime):

View File

@@ -302,22 +302,21 @@ class EddyGatherSamples:
self._check_samples()
# Helper for implementing PROBE style commands (descend until trigger)
class EddyEndstopWrapper:
class EddyDescend:
REASON_SENSOR_ERROR = mcu.MCU_trsync.REASON_COMMS_TIMEOUT + 1
def __init__(self, config, sensor_helper, calibration):
def __init__(self, config, sensor_helper, calibration, param_helper):
self._printer = config.get_printer()
self._sensor_helper = sensor_helper
self._mcu = sensor_helper.get_mcu()
self._calibration = calibration
self._param_helper = param_helper
self._z_min_position = probe.lookup_minimum_z(config)
self._z_offset = config.getfloat('z_offset', minval=0.)
self._dispatch = mcu.TriggerDispatch(self._mcu)
self._trigger_time = 0.
self._gather = None
# Interface for MCU_endstop
def get_mcu(self):
return self._mcu
def add_stepper(self, stepper):
self._dispatch.add_stepper(stepper)
probe.LookupZSteppers(config, self._dispatch.add_stepper)
# Interface for phoming.probing_move()
def get_steppers(self):
return self._dispatch.get_steppers()
def home_start(self, print_time, sample_time, sample_count, rest_time,
@@ -344,10 +343,16 @@ class EddyEndstopWrapper:
return home_end_time
self._trigger_time = trigger_time
return trigger_time
def query_endstop(self, print_time):
return False # XXX
# Interface for ProbeEndstopWrapper
def probing_move(self, pos, speed):
# Probe session interface
def start_probe_session(self, gcmd):
self._gather = EddyGatherSamples(self._printer, self._sensor_helper,
self._calibration, self._z_offset)
return self
def run_probe(self, gcmd):
toolhead = self._printer.lookup_object('toolhead')
pos = toolhead.get_position()
pos[2] = self._z_min_position
speed = self._param_helper.get_probe_params(gcmd)['probe_speed']
# Perform probing move
phoming = self._printer.lookup_object('homing')
trig_pos = phoming.probing_move(self, pos, speed)
@@ -356,22 +361,48 @@ class EddyEndstopWrapper:
# Extract samples
start_time = self._trigger_time + 0.050
end_time = start_time + 0.100
toolhead = self._printer.lookup_object("toolhead")
toolhead_pos = toolhead.get_position()
self._gather.note_probe(start_time, end_time, toolhead_pos)
return self._gather.pull_probed()[0]
def multi_probe_begin(self):
self._gather = EddyGatherSamples(self._printer, self._sensor_helper,
self._calibration, self._z_offset)
def multi_probe_end(self):
def pull_probed_results(self):
return self._gather.pull_probed()
def end_probe_session(self):
self._gather.finish()
self._gather = None
# Wrapper to emulate mcu_endstop for probe:z_virtual_endstop
# Note that this does not provide accurate results
class EddyEndstopWrapper:
def __init__(self, sensor_helper, eddy_descend):
self._sensor_helper = sensor_helper
self._eddy_descend = eddy_descend
self._hw_probe_session = None
# Interface for MCU_endstop
def get_mcu(self):
return self._sensor_helper.get_mcu()
def add_stepper(self, stepper):
pass
def get_steppers(self):
return self._eddy_descend.get_steppers()
def home_start(self, print_time, sample_time, sample_count, rest_time,
triggered=True):
return self._eddy_descend.home_start(
print_time, sample_time, sample_count, rest_time, triggered)
def home_wait(self, home_end_time):
return self._eddy_descend.home_wait(home_end_time)
def query_endstop(self, print_time):
return False # XXX
# Interface for HomingViaProbeHelper
def multi_probe_begin(self):
self._hw_probe_session = self._eddy_descend.start_probe_session(None)
def multi_probe_end(self):
self._hw_probe_session.end_probe_session()
self._hw_probe_session = None
def probe_prepare(self, hmove):
pass
def probe_finish(self, hmove):
pass
def get_position_endstop(self):
return self._z_offset
return self._eddy_descend._z_offset
# Implementing probing with "METHOD=scan"
class EddyScanningProbe:
@@ -423,17 +454,20 @@ class PrinterEddyProbe:
sensor_type = config.getchoice('sensor_type', {s: s for s in sensors})
self.sensor_helper = sensors[sensor_type](config, self.calibration)
# Probe interface
self.mcu_probe = EddyEndstopWrapper(config, self.sensor_helper,
self.calibration)
self.cmd_helper = probe.ProbeCommandHelper(
config, self, self.mcu_probe.query_endstop)
self.param_helper = probe.ProbeParameterHelper(config)
self.eddy_descend = EddyDescend(
config, self.sensor_helper, self.calibration, self.param_helper)
self.cmd_helper = probe.ProbeCommandHelper(config, self)
self.probe_offsets = probe.ProbeOffsetsHelper(config)
self.probe_session = probe.ProbeSessionHelper(config, self.mcu_probe)
self.probe_session = probe.ProbeSessionHelper(
config, self.param_helper, self.eddy_descend.start_probe_session)
mcu_probe = EddyEndstopWrapper(self.sensor_helper, self.eddy_descend)
probe.HomingViaProbeHelper(config, mcu_probe, self.param_helper)
self.printer.add_object('probe', self)
def add_client(self, cb):
self.sensor_helper.add_client(cb)
def get_probe_params(self, gcmd=None):
return self.probe_session.get_probe_params(gcmd)
return self.param_helper.get_probe_params(gcmd)
def get_offsets(self):
return self.probe_offsets.get_offsets()
def get_status(self, eventtime):

View File

@@ -1,12 +1,9 @@
# Handle pwm output pins with variable frequency
#
# Copyright (C) 2017-2023 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2017-2025 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
PIN_MIN_TIME = 0.100
MAX_SCHEDULE_TIME = 5.0
class MCU_pwm_cycle:
def __init__(self, pin_params, cycle_time, start_value, shutdown_value):
self._mcu = pin_params['chip']
@@ -22,6 +19,8 @@ class MCU_pwm_cycle:
self._shutdown_value = max(0., min(1., shutdown_value))
self._last_clock = self._cycle_ticks = 0
self._set_cmd = self._set_cycle_ticks = None
def get_mcu(self):
return self._mcu
def _build_config(self):
cmd_queue = self._mcu.alloc_command_queue()
curtime = self._mcu.get_printer().get_reactor().monotonic()
@@ -77,9 +76,6 @@ class PrinterOutputPWMCycle:
def __init__(self, config):
self.printer = config.get_printer()
self.last_print_time = 0.
cycle_time = config.getfloat('cycle_time', 0.100, above=0.,
maxval=MAX_SCHEDULE_TIME)
self.last_cycle_time = self.default_cycle_time = cycle_time
# Determine start and shutdown values
self.scale = config.getfloat('scale', 1., above=0.)
self.last_value = config.getfloat(
@@ -89,8 +85,12 @@ class PrinterOutputPWMCycle:
# Create pwm pin object
ppins = self.printer.lookup_object('pins')
pin_params = ppins.lookup_pin(config.get('pin'), can_invert=True)
max_duration = pin_params['chip'].max_nominal_duration()
cycle_time = config.getfloat('cycle_time', 0.100, above=0.,
maxval=max_duration)
self.mcu_pin = MCU_pwm_cycle(pin_params, cycle_time,
self.last_value, self.shutdown_value)
self.last_cycle_time = self.default_cycle_time = cycle_time
# Register commands
pin_name = config.get_name().split()[1]
gcode = self.printer.lookup_object('gcode')
@@ -102,7 +102,8 @@ class PrinterOutputPWMCycle:
def _set_pin(self, print_time, value, cycle_time):
if value == self.last_value and cycle_time == self.last_cycle_time:
return
print_time = max(print_time, self.last_print_time + PIN_MIN_TIME)
min_sched_time = self.mcu_pin.get_mcu().min_schedule_time()
print_time = max(print_time, self.last_print_time + min_sched_time)
self.mcu_pin.set_pwm_cycle(print_time, value, cycle_time)
self.last_value = value
self.last_cycle_time = cycle_time
@@ -112,8 +113,9 @@ class PrinterOutputPWMCycle:
# Read requested value
value = gcmd.get_float('VALUE', minval=0., maxval=self.scale)
value /= self.scale
max_duration = self.mcu_pin.get_mcu().max_nominal_duration()
cycle_time = gcmd.get_float('CYCLE_TIME', self.default_cycle_time,
above=0., maxval=MAX_SCHEDULE_TIME)
above=0., maxval=max_duration)
# Obtain print_time and apply requested settings
toolhead = self.printer.lookup_object('toolhead')
toolhead.register_lookahead_callback(

View File

@@ -1,28 +1,26 @@
# Queued PWM gpio output
#
# Copyright (C) 2017-2023 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2017-2025 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import chelper
MAX_SCHEDULE_TIME = 5.0
class error(Exception):
pass
class MCU_queued_pwm:
def __init__(self, pin_params):
self._mcu = pin_params['chip']
def __init__(self, config, pin_params):
self._mcu = mcu = pin_params['chip']
self._hardware_pwm = False
self._cycle_time = 0.100
self._max_duration = 2.
self._oid = self._mcu.create_oid()
self._oid = oid = mcu.create_oid()
printer = mcu.get_printer()
motion_queuing = printer.load_object(config, 'motion_queuing')
self._stepqueue = motion_queuing.allocate_stepcompress(mcu, oid)
ffi_main, ffi_lib = chelper.get_ffi()
self._stepqueue = ffi_main.gc(ffi_lib.stepcompress_alloc(self._oid),
ffi_lib.stepcompress_free)
self._mcu.register_stepqueue(self._stepqueue)
self._stepcompress_queue_mq_msg = ffi_lib.stepcompress_queue_mq_msg
self._mcu.register_config_callback(self._build_config)
mcu.register_config_callback(self._build_config)
self._pin = pin_params['pin']
self._invert = pin_params['invert']
self._start_value = self._shutdown_value = float(self._invert)
@@ -31,7 +29,6 @@ class MCU_queued_pwm:
self._pwm_max = 0.
self._set_cmd_tag = None
self._toolhead = None
printer = self._mcu.get_printer()
printer.register_event_handler("klippy:connect", self._handle_connect)
def _handle_connect(self):
self._toolhead = self._mcu.get_printer().lookup_object("toolhead")
@@ -49,12 +46,13 @@ class MCU_queued_pwm:
self._start_value = max(0., min(1., start_value))
self._shutdown_value = max(0., min(1., shutdown_value))
def _build_config(self):
config_error = self._mcu.get_printer().config_error
printer = self._mcu.get_printer()
config_error = printer.config_error
if self._max_duration and self._start_value != self._shutdown_value:
raise config_error("Pin with max duration must have start"
" value equal to shutdown value")
cmd_queue = self._mcu.alloc_command_queue()
curtime = self._mcu.get_printer().get_reactor().monotonic()
curtime = printer.get_reactor().monotonic()
printtime = self._mcu.estimated_print_time(curtime)
self._last_clock = self._mcu.print_time_to_clock(printtime + 0.200)
cycle_ticks = self._mcu.seconds_to_clock(self._cycle_time)
@@ -64,7 +62,8 @@ class MCU_queued_pwm:
if self._duration_ticks >= 1<<31:
raise config_error("PWM pin max duration too large")
if self._duration_ticks:
self._mcu.register_flush_callback(self._flush_notification)
motion_queuing = printer.lookup_object('motion_queuing')
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
@@ -117,14 +116,16 @@ class MCU_queued_pwm:
# 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)
self._toolhead.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:
value = 1. - value
v = int(max(0., min(1., value)) * self._pwm_max + 0.5)
self._send_update(clock, v)
def _flush_notification(self, print_time, clock):
def _flush_notification(self, must_flush_time, max_step_gen_time):
clock = self._mcu.print_time_to_clock(must_flush_time)
if self._last_value != self._default_value:
while clock >= self._last_clock + self._duration_ticks:
self._send_update(self._last_clock + self._duration_ticks,
@@ -136,17 +137,17 @@ class PrinterOutputPin:
ppins = self.printer.lookup_object('pins')
# Determine pin type
pin_params = ppins.lookup_pin(config.get('pin'), can_invert=True)
self.mcu_pin = MCU_queued_pwm(pin_params)
self.mcu_pin = MCU_queued_pwm(config, pin_params)
max_duration = self.mcu_pin.get_mcu().max_nominal_duration()
cycle_time = config.getfloat('cycle_time', 0.100, above=0.,
maxval=MAX_SCHEDULE_TIME)
maxval=max_duration)
hardware_pwm = config.getboolean('hardware_pwm', False)
self.mcu_pin.setup_cycle_time(cycle_time, hardware_pwm)
self.scale = config.getfloat('scale', 1., above=0.)
self.last_print_time = 0.
# Support mcu checking for maximum duration
max_mcu_duration = config.getfloat('maximum_mcu_duration', 0.,
minval=0.500,
maxval=MAX_SCHEDULE_TIME)
minval=0.500, maxval=max_duration)
self.mcu_pin.setup_max_duration(max_mcu_duration)
# Determine start and shutdown values
self.last_value = config.getfloat(

View File

@@ -1,4 +1,4 @@
# Mechanicaly conforms a moving gantry to the bed with 4 Z steppers
# Mechanically conforms a moving gantry to the bed with 4 Z steppers
#
# Copyright (C) 2018 Maks Zolin <mzolin@vorondesign.com>
#

View File

@@ -127,7 +127,8 @@ class ResonanceTestExecutor:
def run_test(self, test_seq, axis, gcmd):
reactor = self.printer.get_reactor()
toolhead = self.printer.lookup_object('toolhead')
X, Y, Z, E = toolhead.get_position()
tpos = toolhead.get_position()
X, Y = tpos[:2]
# Override maximum acceleration and acceleration to
# deceleration based on the maximum test frequency
systime = reactor.monotonic()
@@ -147,8 +148,7 @@ class ResonanceTestExecutor:
last_v = last_t = last_accel = last_freq = 0.
for next_t, accel, freq in test_seq:
t_seg = next_t - last_t
toolhead.cmd_M204(self.gcode.create_gcode_command(
"M204", "M204", {"S": abs(accel)}))
toolhead.set_max_velocities(None, abs(accel), None, None)
v = last_v + accel * t_seg
abs_v = abs(v)
if abs_v < 0.000001:
@@ -166,10 +166,10 @@ class ResonanceTestExecutor:
# 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, Z, E], abs_last_v)
toolhead.move([nX, nY, Z, E], abs_v)
toolhead.move([X + decel_X, Y + decel_Y] + tpos[2:], abs_last_v)
toolhead.move([nX, nY] + tpos[2:], abs_v)
else:
toolhead.move([nX, nY, Z, E], max(abs_v, abs_last_v))
toolhead.move([nX, nY] + tpos[2:], 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)
@@ -181,9 +181,8 @@ class ResonanceTestExecutor:
if last_v:
d_decel = -.5 * last_v2 / old_max_accel
decel_X, decel_Y = axis.get_point(d_decel)
toolhead.cmd_M204(self.gcode.create_gcode_command(
"M204", "M204", {"S": old_max_accel}))
toolhead.move([X + decel_X, Y + decel_Y, Z, E], abs(last_v))
toolhead.set_max_velocities(None, old_max_accel, None, None)
toolhead.move([X + decel_X, Y + decel_Y] + tpos[2:], abs(last_v))
# Restore the original acceleration values
self.gcode.run_script_from_command(
"SET_VELOCITY_LIMIT ACCEL=%.3f MINIMUM_CRUISE_RATIO=%.3f"
@@ -294,7 +293,7 @@ class ResonanceTester:
return parsed_chips
def _get_max_calibration_freq(self):
return 1.5 * self.generator.get_max_freq()
cmd_TEST_RESONANCES_help = ("Runs the resonance test for a specifed axis")
cmd_TEST_RESONANCES_help = ("Runs the resonance test for a specified axis")
def cmd_TEST_RESONANCES(self, gcmd):
# Parse parameters
axis = _parse_axis(gcmd, gcmd.get("AXIS").lower())
@@ -344,7 +343,7 @@ class ResonanceTester:
gcmd.respond_info(
"Resonances data written to %s file" % (csv_name,))
cmd_SHAPER_CALIBRATE_help = (
"Simular to TEST_RESONANCES but suggest input shaper config")
"Similar to TEST_RESONANCES but suggest input shaper config")
def cmd_SHAPER_CALIBRATE(self, gcmd):
# Parse parameters
axis = gcmd.get("AXIS", None)

View File

@@ -3,6 +3,7 @@
# Copyright (C) 2019 Florian Heilmann <Florian.Heilmann@gmx.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
from . import manual_probe
class SafeZHoming:
def __init__(self, config):
@@ -11,7 +12,9 @@ class SafeZHoming:
self.home_x_pos, self.home_y_pos = x_pos, y_pos
self.z_hop = config.getfloat("z_hop", default=0.0)
self.z_hop_speed = config.getfloat('z_hop_speed', 15., above=0.)
zconfig = config.getsection('stepper_z')
zconfig = manual_probe.lookup_z_endstop_config(config)
if zconfig is None:
raise config.error('Missing Z endstop config for safe_z_homing')
self.max_z = zconfig.getfloat('position_max', note_valid=False)
self.speed = config.getfloat('speed', 50.0, above=0.)
self.move_to_previous = config.getboolean('move_to_previous', False)

View File

@@ -9,7 +9,6 @@ from . import probe
class ScrewsTiltAdjust:
def __init__(self, config):
self.config = config
self.printer = config.get_printer()
self.screws = []
self.results = {}
@@ -33,7 +32,7 @@ class ScrewsTiltAdjust:
default='CW-M3')
# Initialize ProbePointsHelper
points = [coord for coord, name in self.screws]
self.probe_helper = probe.ProbePointsHelper(self.config,
self.probe_helper = probe.ProbePointsHelper(config,
self.probe_finalize,
default_points=points)
self.probe_helper.minimum_points(3)

View File

@@ -56,6 +56,7 @@ class SHT3X:
self.reactor = self.printer.get_reactor()
self.i2c = bus.MCU_I2C_from_config(
config, default_addr=SHT3X_I2C_ADDR, default_speed=100000)
self._error = self.i2c.get_mcu().error
self.report_time = config.getint('sht3x_report_time', 1, minval=1)
self.deviceId = config.get('sensor_type')
self.temp = self.min_temp = self.max_temp = self.humidity = 0.
@@ -79,10 +80,10 @@ class SHT3X:
def _init_sht3x(self):
# Device Soft Reset
self.i2c.i2c_write_wait_ack(SHT3X_CMD['OTHER']['BREAK'])
self.i2c.i2c_write(SHT3X_CMD['OTHER']['BREAK'])
# Break takes ~ 1ms
self.reactor.pause(self.reactor.monotonic() + .0015)
self.i2c.i2c_write_wait_ack(SHT3X_CMD['OTHER']['SOFTRESET'])
self.i2c.i2c_write(SHT3X_CMD['OTHER']['SOFTRESET'])
# Wait <=1.5ms after reset
self.reactor.pause(self.reactor.monotonic() + .0015)
@@ -96,16 +97,29 @@ class SHT3X:
logging.warning("sht3x: Reading status - checksum error!")
# Enable periodic mode
self.i2c.i2c_write_wait_ack(
self.i2c.i2c_write(
SHT3X_CMD['PERIODIC']['2HZ']['HIGH_REP']
)
# Wait <=15.5ms for first measurment
# Wait <=15.5ms for first measurement
self.reactor.pause(self.reactor.monotonic() + .0155)
def _sample_sht3x(self, eventtime):
try:
# Read measurment
params = self.i2c.i2c_read(SHT3X_CMD['OTHER']['FETCH'], 6)
# Read measurement
retries = 5
params = None
error = None
while retries > 0 and params is None:
try:
params = self.i2c.i2c_read(
SHT3X_CMD['OTHER']['FETCH'], 6, retry=False
)
except self._error as e:
error = e
self.reactor.pause(self.reactor.monotonic() + .5)
retries -= 1
if params is None:
raise error
response = bytearray(params['response'])
rtemp = response[0] << 8

Some files were not shown because too many files have changed in this diff Show More