75 Commits

Author SHA1 Message Date
Kevin O'Connor
61c0c8d2ef docs: Note the release of v0.13.0
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-11 21:07:28 -04:00
Kevin O'Connor
ce7657e537 docs: Update Features.md to reflect recent work
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-11 21:07:28 -04:00
Kevin O'Connor
037377b927 led: Fix off-by-one bug in SET_LED_TEMPLATE INDEX parameter
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-11 12:00:34 -04:00
Kevin O'Connor
5493bdfb48 ci-install: Use prebuilt pru gcc binaries
Don't build the pru binaries directly in the build test cases, instead
use the upstream binaries provided.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-08 23:34:17 -04:00
Kevin O'Connor
4b9cb36247 force_move: Make sure to use lower() on SET_KINEMATIC_POSITION CLEAR_HOMED
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-08 23:30:08 -04:00
Kevin O'Connor
f3e89e25c5 force_move: Support a SET_HOMED parameter to SET_KINEMATIC_POSITION
Commit 70838797 added support for clearing the homing state in
SET_KINEMATIC_POSITION commands.  However, it can be difficult to use
that support as the default for SET_KINEMATIC_POSITION is to set all
axes as homed.

Add a new SET_HOMED parameter to allow one to explicitly request which
axes to consider in a homed state.

Also introduce a CLEAR_HOMED parameter and prefer that to the existing
CLEAR parameter.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-08 23:23:05 -04:00
Kevin O'Connor
655861cf12 i2c_software: Fix i2c_delay()
The i2c_delay() function did not properly handle counter rollovers.
It also performed an expensive run-time divide.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-08 10:10:25 -04:00
Kevin O'Connor
050bc33241 docs: Fixup G-Codes.md so that sections are sorted alphabetically
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-07 15:26:56 -04:00
Maksim Bolgov
46ee920b93 axis_twist_compensation: Fix AttributeError on klippy connect state (#6881)
Object 'configfile' has no attribute 'error'

Signed-off-by: Maksim Bolgov <maksim8024@gmail.com>
2025-04-05 21:36:35 -04:00
Timofey Titovets
3a9e9a4bef temperature_combined: avoid crash with temperature monitors
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-04-05 21:27:37 -04:00
Timofey Titovets
3beb465247 temperature_combined: delay initialization
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-04-05 21:27:37 -04:00
MRX8024
2ec69ae361 docs: Add icm20948 description
Signed-off-by: Maksim Bolgov maksim8024@gmail.com
2025-04-03 14:08:23 -04:00
MRX8024
52b07f467e adxl345: Allow read and write 127 register address
icm20948 accelerometer has an ACCEL_CONFIG register at address 127

Signed-off-by: Maksim Bolgov maksim8024@gmail.com
2025-04-03 14:08:23 -04:00
MRX8024
81a1a03ed0 icm20948: Formatting refactor
Signed-off-by: Maksim Bolgov maksim8024@gmail.com
2025-04-03 14:08:23 -04:00
MRX8024
869440a7ed icm20948: Transition from 8g to 16g accels scale
During standard resonance measurements, the icm20948 in 8g mode may reach the accels max threshold.

Signed-off-by: Maksim Bolgov maksim8024@gmail.com
2025-04-03 14:08:23 -04:00
MRX8024
20f26b534d icm20948: Fix sample rate and accels scale selection
To set a value in the SET_ACCEL_CONFIG register, you must first go to BANK_2.

Signed-off-by: Maksim Bolgov maksim8024@gmail.com
2025-04-03 14:08:23 -04:00
Kevin O'Connor
91cba8a17f mkdocs-requirements: Update to Jinja 3.1.6
A security vulnerability was found in Jinja 3.1.5 .  The software is
not impacted by this vulnerability, but there is no harm in updating
to the fixed version.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-03 13:47:00 -04:00
Kevin O'Connor
be429caba3 output_pin: Make it possible to assign dicts/lists as template parameters
The output_pin template code has a cache to speed up duplicate
rendering of templates.  However, this cache doesn't work if one of
the parameters is a Python list or dictionary.  Just disable the cache
in this case.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-03 13:38:31 -04:00
Kevin O'Connor
8176ba22aa stm32: Turn on can.c error interrupts
It seems both ERRIE and LECIE must be enabled to get hardware error
interrupts.  Without this, the rx_error and tx_error reports are
likely to always be zero.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-03 13:32:23 -04:00
Russell Cloran
4b9add2fc3 stm32: Add support for additional i2c bus
Signed-off-by: Russell Cloran <rcloran@gmail.com>
2025-04-02 10:07:52 -04:00
Kevin O'Connor
55f60601ca stm32: Fix RESERVE_PINS_CAN pin ordering in fdcan.c
Always report the reserved pins in the same order (rx,tx).

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-04-01 21:25:34 -04:00
Russell Cloran
876f351127 docs: Fix link syntax typo for bed_screws
Signed-off-by: Russell Cloran <rcloran@gmail.com>
2025-04-01 21:13:46 -04:00
Russell Cloran
089516a6f2 docs: Fix typo in docs generation documentation
Signed-off-by: Russell Cloran <rcloran@gmail.com>
2025-04-01 21:13:46 -04:00
Tobias Rumiz
f511e201f9 docs: Fix typos in installation.md
Fixed typos, hyphenation, and minor phrasing for better readability.

Signed-off-by: Tobias Rumiz <TobiasRumiz@gmail.com>
2025-03-31 11:28:04 -04:00
XiaoK
52617455ce ldc_1612: Supports configurable external crystal frequency (#6734)
You can use the 40Mhz crystal oscillator recommended by TI official manual to get the best performance.
refer to: [ldc1612.pdf](https://www.ti.com/cn/lit/ds/symlink/ldc1612.pdf) 7.3.4

Signed-off-by: Xiaokui Zhao <xiaok@zxkxzk.cn>
2025-03-29 21:53:52 -04:00
FrY Sennberg
d679f711eb stm32: Added PH13/14 CAN pin option for stm32h743 (#6857)
Added the option to select PH13/PH14 as CAN pins.

Signed-off-by:  Christoph Frei <fryakatkop@gmail.com>
2025-03-27 19:25:26 -04:00
Timofey Titovets
68dbbc8d41 rp2040: define spi bus on pins 12,11,10
Mellow FLY SHT36 Pro toolboard uses those pins

Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-03-25 18:53:46 -04:00
Kevin O'Connor
59ebdce605 output_pin: Fix handling of template rendering errors
Make sure to assign 'value' on a rendering error to avoid an internal
error.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-03-25 18:46:38 -04:00
Kevin O'Connor
310747a636 fan_generic: Fix handling of template rendering errors
Make sure to assign 'value' on a rendering error to avoid an internal
error.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-03-25 18:45:12 -04:00
Kevin O'Connor
a3b4b39ff1 config: Add LED definitions to generic-bigtreetech-skr-mini-mz.cfg
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-03-24 17:43:41 -04:00
Pedro Lamas
fb91aad583 buttons: fixes incorrect parameters
Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>
2025-03-21 13:36:27 -04:00
Kevin O'Connor
825d4baf90 stepper: Support disabling optimized "step on both edges" in "make menuconfig"
Add a new "low level option" to allow users to configure if they want
to optimize for Trinamic drivers or traditional stepper motor drivers.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-03-20 19:56:55 -04:00
Kevin O'Connor
8faed8d9fe stepper: Support step on both edges with custom minimum pulse duration
Add support for "step on both edges" to the main stepper_event_full()
code.  This makes that mode of operation available even when the
micro-controller is not compiled for "optimized step on both edges".
It also enables the custom pulse duration support (step_pulse_ticks)
when in "step on both edges" mode.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-03-20 19:56:55 -04:00
Gareth Farrington
272e815522 buttons: Debounce gcode_button and filament_switch_sensor (#6848)
Add `debounce_delay` config option which sets the debounce time, defaults to 0

Signed-off-by: Gareth Farrington <gareth@waves.ky>
2025-03-20 19:55:33 -04:00
Gareth Farrington
06d65ef5ac load_cell: Load cell gram scale (#6729)
* Add gram scale features to load_cell
* Convert sensor counts to grams and make this available via unix socket and object status
* Basic GCodes for tearing and reading the load cell
* Guided Calibration
* Diagnostic gcode to check the health of the load cell
* Update load_cell Documentation
* Add API server load_cell/dump_force endpoint
* Update [load_cell] config with calibration fields
* Add G-Code commands for working with load cells
* Add status reference for load_cell objects

Signed-off-by: Gareth Farrington <gareth@waves.ky>
2025-03-20 19:53:44 -04:00
Philippe Daouadi
d886c1761b axis_twist_compensation: allow compensating both axis at once
Restores the behavior before #6739 since people seemed to rely on it,
even if the math is not exact.

Signed-off-by: Philippe Daouadi <philippe@ud2.org>
2025-03-12 18:29:20 -04:00
Dmitry Butyugin
47aa28e530 input_shaper: Fix for polar kinematics
Forward post_cb calls from itersolve to the original kinematics.

Signed-off-by: Dmitry Butyugin <dmbutyugin@google.com>
2025-03-11 19:15:23 -04:00
Kevin O'Connor
fbd5b49215 docs: Note AXIS_TWIST_COMPENSATION_CALIBRATE AUTO removal in Config_Changes.md
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-03-08 22:28:13 -05:00
yochiwarez
dad2196776 axis_twist_compensation: Remove the auto parameter
from axis_twist_compensation

Signed-off-by: Jorge Apaza Merma <yochiwarez@gmail.com>
2025-03-08 22:23:35 -05:00
Timofey Titovets
b50d740542 gcode_macro: Expand template syntax errors (#6839)
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-03-07 18:32:31 -05:00
Marius Petcu
3e7efe5ef1 stm32: Add support for USART6 on STM32F401
STM32F401 has USART6 on PA12/PA11 and PC7/PC6 with alternate
function mapping AF08. This can be used, for example, to connect
to the Elegoo Neptune 3, where PA12/PA11 are wired to an RJ10 plug
going to the stock screen.

Signed-off-by: Marius Petcu <marius@petcu.me>
2025-03-07 17:52:46 -05:00
Paul Hansel
75a10bfcaf icm20948: Add support for ICM20948 accelerometer (#6756)
Signed-off-by: Paul Hansel <github@paulhansel.com>
2025-03-04 17:12:26 -05:00
Thijs Triemstra
730e5951bc docs: fix markup in Axis_Twist_Compensation.md (#6827)
Signed-off-by: Thijs Triemstra <info@collab.nl>
2025-02-28 19:38:38 -05:00
Kevin O'Connor
941fb5a367 usb_canbus: Send echo frame before processing the frame
The Linux kernel reports a canbus message as transmitted when it gets
the echo frame back.  Processing the message prior to sending the echo
frame can lead to odd looking debugging logs (as the response messages
may appear to predate the request messages).  This doesn't impact the
Klipper code, but it does make analyzing logs harder.  Fix by sending
the echo frame prior to processing the frame.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-02-27 13:24:19 -05:00
Kevin O'Connor
17d471c07c usb_canbus: Minor code cleanup - add new drain_host_queue() helper
Separate code in usbcan_task() to new drain_host_queue().

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-02-27 13:24:19 -05:00
Kevin O'Connor
ef1d8bc3bd usb_canbus: Minor code cleanup - code movement and comment updates
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-02-27 13:24:19 -05:00
Kevin O'Connor
581c954f40 usb_canbus: Wake usbcan_task when sending from canbus_send
Don't limit the canbus_notify_tx() wakeup to cases where notify_local
is set - perform the wakeup whenever the host_status field indicates
the main task has work pending.

This fixes a small race condition where the main task could block
sending a usb echo frame, and the canbus_send() code gets called as
the usb bandwidth becomes available but before a usb wakeup
notification is sent.  In that situation, the usb code may not issue a
wake event and the echo frames may be delayed.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-02-27 13:24:19 -05:00
GofranChang
98068beca0 skew_correction: Supports retrieving the name of the currently loaded skew correction … (#6821)
Signed-off-by: Zhang Gaofan <zhanggaofan0827@gmail.com>
2025-02-27 13:18:59 -05:00
Kevin O'Connor
3c1bf4ccfe test: Add rp2350 build to test cases
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-02-26 20:28:50 -05:00
Kevin O'Connor
1836ec431c docs: Update benchmarks for rp2040
Update benchmarks now that the rp2040 runs at 200Mhz.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-02-26 20:25:18 -05:00
Kevin O'Connor
14c105b86e rp2040: Fix build of rp2350
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-02-26 20:24:22 -05:00
Timofey Titovets
2f6d240900 rp2040: set clock to 200Mhz
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-02-26 20:11:17 -05:00
Eric Callahan
edc3d34beb bed_mesh: reduce generated point logging
With the introduction of "scanning" probes it has
become common for configurations to generate a large
number of points.  This can overwhelm both the log and
the pty when new points are generated.

This patch limits the initial points logged to 50.  In
addition points are no longer logged or pushed over
the pty when the mesh configuration changes.

Signed-off-by:  Eric Callahan <arksine.code@gmail.com>
2025-02-26 18:48:33 -05:00
Wulfsta
53f1bf2af2 sensor_lis2dw: remove commented code and fix formatting
Signed-off-by: Luke Vuksta <wulfstawulfsta@gmail.com>
2025-02-26 15:17:31 -05:00
Kevin O'Connor
1fc6d214f4 stm32: Add support for stm32f070x6 mcus
This mcu has smaller memory and may require remapping of PA11/PA12.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-02-16 13:57:59 -05:00
Kevin O'Connor
bf5c4daf86 usb_cdc: Avoid ending a transmission with a max size usb packet
It seems the Linux kernel will consider a maximum size usb packet to
be a transaction that will continue into the next usb packet.  It will
thus hold on to the traffic from the first packet until it gets the
next packet.  However, if the mcu has no further data to send after
the first packet then the data could get delayed for an extended
period of time.

To avoid this, check for transmissions that could end on a maximum
sized packet and send that data in two packets instead.  This avoids
this unusual corner case.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-02-15 18:22:45 -05:00
Kevin O'Connor
ec56167032 usb_cdc_ep: Define endpoint sizes in usb_cdc_ep.h
Move the definition of the usb endpoint sizes from usb_cdc.h to
usb_cdc_ep.h .  This allows individual boards to override the default
endpoint sizes.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-02-15 18:22:45 -05:00
Kevin O'Connor
15339aec64 docs: Improve suggestions on bytes_invalid in CANBUS_Troubleshooting.md
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-02-15 18:20:04 -05:00
Kevin O'Connor
a90110d9ba docs: Note stealthchop_threshold doesn't impact sensorless homing
Reported by @paulfertser.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-02-15 13:55:19 -05:00
Nicholas Parry
e24ea3652c docs: Fixed incorrect spelling in Config_Reference.md (#6819)
changed spelling of single word

Signed-off-by: Nicholas Parry <rounded-gully-5r@icloud.com>
2025-02-15 13:06:29 -05:00
JamesH1978
508c28e689 spi_flash: Update board_defs.py - BTT Octopus Max EZ (#6817)
Addition to the board_defs file for the BTT Octopus Max EZ, written and confirmed by discord user Nikki @winningfaith81

Signed-off-by: James Hartley <james@hartleyns.com>
2025-02-15 13:04:57 -05:00
Timofey Titovets
fec3e685c9 stm32: h7 spi support reload mode & frequency
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-02-06 12:10:29 -05:00
Lexi Beavil
b16cb6575d docs: Fix broken link to MainsailOS
The documentation install page has a link to the old mainsail OS site, which is no longer available

Signed-off-by: Lexi Beavil <github@aeroniemi.com>
2025-02-05 15:07:54 -05:00
Auxon
329fbd01d8 docs: Update Pressure_Advance.md (#6808)
Added language to disable "scarf joint" seams as it messes with the TUNING_TOWER script.

Signed-off-by: Roman Simanovich <romsimanovich@gmail.com>
2025-02-04 19:08:05 -05:00
Kevin O'Connor
01b0e98ab2 klippy-requirements: Require setuptools on python 3.12
The python-can v3.3.4 package requires setuptools to be an explicit
dependency when run on python v3.12, but there is no single version of
setuptools that runs on all supported versions of python.  So, tie
setuptools to python versions 3.12 or later.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-02-02 20:53:33 -05:00
Kevin O'Connor
638c085ffa mkdocs-requirements: Update jinja dependency to 3.1.5
It appears there was a security vulnerability in Jinja v3.1.4 .  The
Klipper docs are not impacted by this vulnerability, but it's simple
enough to increment the version to avoid warnings.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-02-02 19:01:09 -05:00
Pedro Lamas
8a2de5f23e save_variables: Check lowercase variable names
Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>
2025-02-02 18:52:02 -05:00
Kevin O'Connor
2c90c97ccd usb_canbus: Detect canbus stalls when in usb to canbus bridge mode
If the low-level canbus stops working then it could become impossible
to send messages to and from the canbus bridge node itself.  This can
make it difficult to diagnose canbus problems.

Change the canbus bridge code to detect if message transmits become
stalled for 50+ milliseconds and go into a "discarding" state.  In
this discarding state, messages destined for the canbus will be
discarded until the canbus becomes active again.  In this discarding
state it will therefore be possible to transmit messages to and from
the canbus bridge node.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-02-02 18:43:34 -05:00
Kevin O'Connor
2db2ef82f2 canbus_stats: Periodically report canbus interface statistics
Add support for a new get_canbus_status command to canserial.c .

Add new canbus_stats.py module that will periodically query canbus
mcus for connection status information.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-02-02 18:43:34 -05:00
Kevin O'Connor
eb0581c264 atsam: Add support for reporting canbus state
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-02-02 18:43:34 -05:00
Kevin O'Connor
61fb5fe29c atsamd: Add support for reporting canbus state
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-02-02 18:43:34 -05:00
Kevin O'Connor
9fd415d3f5 rp2040: Add support for reporting canbus state
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-02-02 18:43:34 -05:00
Kevin O'Connor
b7366ae3fc stm32: Add support for reporting canbus state from fdcan.c
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-02-02 18:43:34 -05:00
Kevin O'Connor
6cdcf75e6b stm32: Add support for reporting canbus state from can.c
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-02-02 18:43:34 -05:00
Branden Cash
d57fe4395e garbage_collection: freeze objects on klippy ready (#6794)
This significantly reduces the amount of data in the generation 2 garbage collection bucket from the initial startup of klipper.

Signed-off-by: Branden Cash <203336+ammmze@users.noreply.github.com>
2025-02-02 18:40:43 -05:00
85 changed files with 2299 additions and 639 deletions

View File

@@ -122,6 +122,12 @@ max_z_accel: 100
[static_digital_output usb_pullup_enable]
pins: !PA14
#[neopixel my_neopixel]
#pin: PA8
[output_pin red_led]
pin: PA13
[board_pins]
aliases:
# EXP1 header

View File

@@ -364,37 +364,21 @@ 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.
### hx71x/dump_hx71x
### load_cell/dump_force
This endpoint is used to subscribe to raw HX711 and HX717 ADC data.
Obtaining these low-level ADC updates may be useful for diagnostic
and debugging purposes. Using this endpoint may increase Klipper's
system load.
This endpoint is used to subscribe to force data produced by a load_cell.
Using this endpoint may increase Klipper's system load.
A request may look like:
`{"id": 123, "method":"hx71x/dump_hx71x",
`{"id": 123, "method":"load_cell/dump_force",
"params": {"sensor": "load_cell", "response_template": {}}}`
and might return:
`{"id": 123,"result":{"header":["time","counts","value"]}}`
`{"id": 123,"result":{"header":["time", "force (g)", "counts", "tare_counts"]}}`
and might later produce asynchronous messages such as:
`{"params":{"data":[[3292.432935, 562534, 0.067059278],
[3292.4394937, 5625322, 0.670590639]]}}`
`{"params":{"data":[[3292.432935, 40.65, 562534, -234467]]}}`
### ads1220/dump_ads1220
This endpoint is used to subscribe to raw ADS1220 ADC data.
Obtaining these low-level ADC updates may be useful for diagnostic
and debugging purposes. Using this endpoint may increase Klipper's
system load.
A request may look like:
`{"id": 123, "method":"ads1220/dump_ads1220",
"params": {"sensor": "load_cell", "response_template": {}}}`
and might return:
`{"id": 123,"result":{"header":["time","counts","value"]}}`
and might later produce asynchronous messages such as:
`{"params":{"data":[[3292.432935, 562534, 0.067059278],
[3292.4394937, 5625322, 0.670590639]]}}`
The "header" field in the initial query response is used to describe
the fields found in later "data" responses.
### pause_resume/cancel

View File

@@ -1,6 +1,6 @@
# Axis Twist Compensation
This document describes the [axis_twist_compensation] module.
This document describes the `[axis_twist_compensation]` module.
Some printers may have a small twist in their X rail which can skew the results
of a probe attached to the X carriage.
@@ -25,31 +25,31 @@ try to probe the bed without attaching the probe if you use it.
> correctly set as they greatly influence calibration.
### Basic Usage: X-Axis Calibration
1. After setting up the ```[axis_twist_compensation]``` module, run:
1. After setting up the `[axis_twist_compensation]` module, run:
```
AXIS_TWIST_COMPENSATION_CALIBRATE
```
This command will calibrate the X-axis by default.
- The calibration wizard will prompt you to measure the probe Z offset at
- The calibration wizard will prompt you to measure the probe Z offset at
several points along the bed.
- By default, the calibration uses 3 points, but you can specify a different
- By default, the calibration uses 3 points, but you can specify a different
number with the option:
``
SAMPLE_COUNT=<value>
``
2. **Adjust Your Z Offset:**
After completing the calibration, be sure to [adjust your Z offset]
(Probe_Calibrate.md#calibrating-probe-z-offset).
After completing the calibration, be sure to
[adjust your Z offset](Probe_Calibrate.md#calibrating-probe-z-offset).
3. **Perform Bed Leveling Operations:**
Use probe-based operations as needed, such as:
- [Screws Tilt Adjust](G-Codes.md#screws_tilt_adjust)
- [Z Tilt Adjust](G-Codes.md#z_tilt_adjust)
- [Screws Tilt Adjust](G-Codes.md#screws_tilt_adjust)
- [Z Tilt Adjust](G-Codes.md#z_tilt_adjust)
4. **Finalize the Setup:**
- Home all axes, and perform a [Bed Mesh](Bed_Mesh.md) if necessary.
- Run a test print, followed by any
- Home all axes, and perform a [Bed Mesh](Bed_Mesh.md) if necessary.
- Run a test print, followed by any
[fine-tuning](Axis_Twist_Compensation.md#fine-tuning)
if needed.
@@ -61,22 +61,13 @@ AXIS_TWIST_COMPENSATION_CALIBRATE AXIS=Y
```
This will guide you through the same measuring process as for the X-axis.
### Automatic Calibration for Both Axes
To perform automatic calibration for both the X and Y axes without manual
intervention, use:
```
AXIS_TWIST_COMPENSATION_CALIBRATE AUTO=True
```
In this mode, the calibration process will run for both axes automatically.
> **Tip:** Bed temperature and nozzle temperature and size do not seem to have
> an influence to the calibration process.
## [axis_twist_compensation] setup and commands
Configuration options for [axis_twist_compensation] can be found in the
Configuration options for `[axis_twist_compensation]` can be found in the
[Configuration Reference](Config_Reference.md#axis_twist_compensation).
Commands for [axis_twist_compensation] can be found in the
Commands for `[axis_twist_compensation]` can be found in the
[G-Codes Reference](G-Codes.md#axis_twist_compensation)

View File

@@ -407,14 +407,14 @@ config_stepper oid=2 step_pin=gpio27 dir_pin=gpio5 invert_step=-1 step_pulse_tic
finalize_config crc=0
```
The test was last run on commit `f6718291` with gcc version
The test was last run on commit `14c105b8` with gcc version
`arm-none-eabi-gcc (Fedora 14.1.0-1.fc40) 14.1.0` on Raspberry Pi
Pico and Pico 2 boards.
| rp2040 (*) | ticks |
| -------------------- | ----- |
| 1 stepper | 5 |
| 3 stepper | 22 |
| 1 stepper | 3 |
| 3 stepper | 14 |
| rp2350 | ticks |
| -------------------- | ----- |
@@ -422,9 +422,9 @@ Pico and Pico 2 boards.
| 3 stepper | 169 |
(*) Note that the reported rp2040 ticks are relative to a 12Mhz
scheduling timer and do not correspond to its 125Mhz internal ARM
scheduling timer and do not correspond to its 200Mhz internal ARM
processing rate. It is expected that 5 scheduling ticks corresponds to
~47 ARM core cycles and 22 scheduling ticks corresponds to ~224 ARM
~42 ARM core cycles and 14 scheduling ticks corresponds to ~225 ARM
core cycles.
### Linux MCU step rate benchmark

View File

@@ -37,20 +37,36 @@ hours or more frequently) then it is an indication of a severe
problem.
Incrementing `bytes_invalid` on a CAN bus connection is a symptom of
reordered messages on the CAN bus. There are two known causes of
reordered messages:
1. Old versions of the popular candlight_firmware for USB CAN adapters
had a bug that could cause reordered messages. If using a USB CAN
adapter running this firmware then make sure to update to the
latest firmware if incrementing `bytes_invalid` is observed.
2. Some Linux kernel builds for embedded devices have been known to
reorder CAN bus messages. It may be necessary to use an alternative
Linux kernel or to use alternative hardware that supports
mainstream Linux kernels that do not exhibit this problem.
reordered messages on the CAN bus. If seen, make sure to:
* Use a Linux kernel version 6.6.0 or later.
* If using a USB-to-CANBUS adapter running candlelight firmware, use
v2.0 or later of candleLight_fw.
* If using Klipper's USB-to-CANBUS bridge mode, make sure the bridge
node is flashed with Klipper v0.12.0 or later.
Reordered messages is a severe problem that must be fixed. It will
result in unstable behavior and can lead to confusing errors at any
part of a print.
part of a print. An incrementing `bytes_invalid` is not caused by
wiring or similar hardware issues and can only be fixed by identifying
and updating the faulty software.
Older versions of the Linux kernel had a bug in the gs_usb canbus
driver code that could cause reordered canbus packets. The issue is
thought to be fixed in
[Linux commit 24bc41b4](https://github.com/torvalds/linux/commit/24bc41b4558347672a3db61009c339b1f5692169)
which was released in v6.6.0. In some cases, older Linux versions may
not show the problem (due to how hardware interrupts are configured),
however if problems are seen the recommended solution is to upgrade to
a newer kernel.
Older versions of candlelight firmware could reorder canbus packets,
and the issue is thought to be fixed in
[candlelight_fw commit 8b3a7b45](https://github.com/candle-usb/candleLight_fw/commit/8b3a7b4565a3c9521b762b154c94c72c5acb2bcf).
Older versions of Klipper's USB-to-CANBUS bridge code could
incorrectly drop canbus messages. This is not as severe as reordering
messages, but it should still be fixed. It is thought to be fixed with
[Klipper PR #6175](https://github.com/Klipper3d/klipper/pull/6175).
## Use an appropriate txqueuelen setting

View File

@@ -8,6 +8,13 @@ All dates in this document are approximate.
## Changes
20250308: The `AUTO` parameter of the
`AXIS_TWIST_COMPENSATION_CALIBRATE` command has been removed.
20250131: Option `VARIABLE=<name>` in `SAVE_VARIABLE` requires lowercase
value. For example, `extruder` instead of mixedcase `Extruder` or
uppercase `EXTRUDER`. Using any uppercase letter will raise an error.
20241203: The resonance test has been changed to include slow sweeping
moves. This change requires that testing point(s) have some clearance
in X/Y plane (+/- 30 mm from the test point should suffice when using

View File

@@ -1669,6 +1669,25 @@ cs_pin:
# measurements.
```
### [icm20948]
Support for icm20948 accelerometers.
```
[icm20948]
#i2c_address:
# Default is 104 (0x68). If AD0 is high, it would be 0x69 instead.
#i2c_mcu:
#i2c_bus:
#i2c_software_scl_pin:
#i2c_software_sda_pin:
#i2c_speed: 400000
# See the "common I2C settings" section for a description of the
# above parameters. The default "i2c_speed" is 400000.
#axes_map: x, y, z
# See the "adxl345" section for information on this parameter.
```
### [lis2dw]
Support for LIS2DW accelerometers.
@@ -2065,6 +2084,9 @@ Support for eddy current inductive probes. One may define this section
sensor_type: ldc1612
# The sensor chip used to perform eddy current measurements. This
# parameter must be provided and must be set to ldc1612.
#frequency:
# The external crystal frequency (in Hz) of the LDC1612 chip.
# The default is 12000000.
#intb_pin:
# MCU gpio pin connected to the ldc1612 sensor's INTB pin (if
# available). The default is to not use the INTB pin.
@@ -3278,6 +3300,10 @@ pin:
# A list of G-Code commands to execute when the button is released.
# G-Code templates are supported. The default is to not run any
# commands on a button release.
#debounce_delay:
# A period of time in seconds to debounce events prior to running the
# button gcode. If the button is pressed and released during this
# delay, the entire button press is ignored. Default is 0.
```
### [output_pin]
@@ -3456,8 +3482,9 @@ run_current:
#stealthchop_threshold: 0
# The velocity (in mm/s) to set the "stealthChop" threshold to. When
# set, "stealthChop" mode will be enabled if the stepper motor
# velocity is below this value. The default is 0, which disables
# "stealthChop" mode.
# velocity is below this value. Note that the "sensorless homing"
# code may temporarily override this setting during homing
# operations. The default is 0, which disables "stealthChop" mode.
#coolstep_threshold:
# The velocity (in mm/s) to set the TMC driver internal "CoolStep"
# threshold to. If set, the coolstep feature will be enabled when
@@ -3569,8 +3596,9 @@ run_current:
#stealthchop_threshold: 0
# The velocity (in mm/s) to set the "stealthChop" threshold to. When
# set, "stealthChop" mode will be enabled if the stepper motor
# velocity is below this value. The default is 0, which disables
# "stealthChop" mode.
# velocity is below this value. Note that the "sensorless homing"
# code may temporarily override this setting during homing
# operations. The default is 0, which disables "stealthChop" mode.
#driver_MULTISTEP_FILT: True
#driver_IHOLDDELAY: 8
#driver_TPOWERDOWN: 20
@@ -3772,8 +3800,9 @@ run_current:
#stealthchop_threshold: 0
# The velocity (in mm/s) to set the "stealthChop" threshold to. When
# set, "stealthChop" mode will be enabled if the stepper motor
# velocity is below this value. The default is 0, which disables
# "stealthChop" mode.
# velocity is below this value. Note that the "sensorless homing"
# code may temporarily override this setting during homing
# operations. The default is 0, which disables "stealthChop" mode.
#coolstep_threshold:
# The velocity (in mm/s) to set the TMC driver internal "CoolStep"
# threshold to. If set, the coolstep feature will be enabled when
@@ -3906,8 +3935,9 @@ run_current:
#stealthchop_threshold: 0
# The velocity (in mm/s) to set the "stealthChop" threshold to. When
# set, "stealthChop" mode will be enabled if the stepper motor
# velocity is below this value. The default is 0, which disables
# "stealthChop" mode.
# velocity is below this value. Note that the "sensorless homing"
# code may temporarily override this setting during homing
# operations. The default is 0, which disables "stealthChop" mode.
#coolstep_threshold:
# The velocity (in mm/s) to set the TMC driver internal "CoolStep"
# threshold to. If set, the coolstep feature will be enabled when
@@ -4637,6 +4667,11 @@ more information.
# dispatch and execution of the runout_gcode. It may be useful to
# increase this delay if OctoPrint exhibits strange pause behavior.
# Default is 0.5 seconds.
#debounce_delay:
# A period of time in seconds to debounce events prior to running the
# switch gcode. The switch must he held in a single state for at least
# this long to activate. If the switch is toggled on/off during this delay,
# the event is ignored. Default is 0.
#switch_pin:
# The pin on which the switch is connected. This parameter must be
# provided.
@@ -4754,6 +4789,16 @@ scale.
[load_cell]
sensor_type:
# This must be one of the supported sensor types, see below.
#counts_per_gram:
# The floating point number of sensor counts that indicates 1 gram of force.
# This value is calculated by the LOAD_CELL_CALIBRATE command.
#reference_tare_counts:
# The integer tare value, in raw sensor counts, taken when LOAD_CELL_CALIBRATE
# is run. This is the default tare value when klipper starts up.
#sensor_orientation:
# Change the sensor's orientation. Can be either 'normal' or 'inverted'.
# The default is 'normal'. Use 'inverted' if the sensor reports a
# decreasing force value when placed under load.
```
#### HX711
@@ -5061,7 +5106,7 @@ Octoprint as they will conflict, and 1 will fail to initialize
properly likely aborting your print.
If you use Octoprint and stream gcode over the serial port instead of
printing from virtual_sd, then remo **M1** and **M0** from *Pausing commands*
printing from virtual_sd, then remove **M1** and **M0** from *Pausing commands*
in *Settings > Serial Connection > Firmware & protocol* will prevent
the need to start print on the Palette 2 and unpausing in Octoprint
for your print to begin.

View File

@@ -102,11 +102,13 @@ Klipper supports many standard 3d printer features:
printers.
* Automatic bed leveling support. Klipper can be configured for basic
bed tilt detection or full mesh bed leveling. If the bed uses
bed tilt detection or full mesh bed leveling. The bed mesh can be
customized to the print size (adaptive bed mesh). If the bed uses
multiple Z steppers then Klipper can also level by independently
manipulating the Z steppers. Most Z height probes are supported,
including BL-Touch probes and servo activated probes. Probes may be
calibrated for axis twist compensation.
calibrated for axis twist compensation. If using an "eddy current
probe" then one can utilize fast bed mesh scanning,
* Automatic delta calibration support. The calibration tool can
perform basic height calibration as well as an enhanced X and Y
@@ -118,7 +120,7 @@ Klipper supports many standard 3d printer features:
* Support for common temperature sensors (eg, common thermistors,
AD595, AD597, AD849x, PT100, PT1000, MAX6675, MAX31855, MAX31856,
MAX31865, BME280, HTU21D, DS18B20, AHT10, and LM75). Custom
MAX31865, BME280, HTU21D, DS18B20, AHT10, SHT3x, and LM75). Custom
thermistors and custom analog temperature sensors can also be
configured. One can monitor the internal micro-controller
temperature sensor and the internal temperature sensor of a
@@ -128,7 +130,8 @@ Klipper supports many standard 3d printer features:
* Support for standard fans, nozzle fans, and temperature controlled
fans. No need to keep fans running when the printer is idle. Fan
speed can be monitored on fans that have a tachometer.
speed can be monitored on fans that have a tachometer. One can
assign a "math formula" to a fan for automatic fan speed updating.
* Support for run-time configuration of TMC2130, TMC2208/TMC2224,
TMC2209, TMC2240, TMC2660, and TMC5160 stepper motor drivers. There
@@ -154,7 +157,7 @@ Klipper supports many standard 3d printer features:
filament width sensors.
* Support for measuring and recording acceleration using adxl345,
mpu9250, mpu6050, and lis2dw12 accelerometers.
mpu9250, mpu6050, lis2dw12, lis3dh, and icm20948 accelerometers.
* Support for limiting the top speed of short "zigzag" moves to reduce
printer vibration and noise. See the [kinematics](Kinematics.md)
@@ -184,12 +187,12 @@ represent total number of steps per second on the micro-controller.
| SAM4S8C | 1690K | 1385K |
| LPC1768 | 1923K | 1351K |
| LPC1769 | 2353K | 1622K |
| RP2040 | 2400K | 1636K |
| SAM4E8E | 2500K | 1674K |
| SAMD51 | 3077K | 1885K |
| AR100 | 3529K | 2507K |
| STM32F407 | 3652K | 2459K |
| STM32F446 | 3913K | 2634K |
| RP2040 | 4000K | 2571K |
| RP2350 | 4167K | 2663K |
| SAME70 | 6667K | 4737K |
| STM32H743 | 9091K | 6061K |

View File

@@ -154,8 +154,7 @@ The following commands are available when the
section](Config_Reference.md#axis_twist_compensation) is enabled.
#### AXIS_TWIST_COMPENSATION_CALIBRATE
`AXIS_TWIST_COMPENSATION_CALIBRATE [AXIS=<X|Y>] [AUTO=<True|False>]
[SAMPLE_COUNT=<value>]`
`AXIS_TWIST_COMPENSATION_CALIBRATE [AXIS=<X|Y>] [SAMPLE_COUNT=<value>]`
Calibrates axis twist compensation by specifying the target axis or
enabling automatic calibration.
@@ -163,11 +162,6 @@ enabling automatic calibration.
- **AXIS:** Define the axis (`X` or `Y`) for which the twist compensation
will be calibrated. If not specified, the axis defaults to `'X'`.
- **AUTO:** Enables automatic calibration mode. When `AUTO=True`, the
calibration will run for both the X and Y axes. In this mode, `AXIS`
cannot be specified. If both `AXIS` and `AUTO` are provided, an error
will be raised.
### [bed_mesh]
The following commands are available when the
@@ -585,18 +579,51 @@ state; issue a G28 afterwards to reset the kinematics. This command is
intended for low-level diagnostics and debugging.
#### SET_KINEMATIC_POSITION
`SET_KINEMATIC_POSITION [X=<value>] [Y=<value>] [Z=<value>]
[CLEAR=<[X][Y][Z]>]`: Force the low-level kinematic code to believe the
toolhead is at the given cartesian position. This is a diagnostic and
debugging command; use SET_GCODE_OFFSET and/or G92 for regular axis
transformations. If an axis is not specified then it will default to the
position that the head was last commanded to. Setting an incorrect or
invalid position may lead to internal software errors. Use the CLEAR
parameter to forget the homing state for the given axes. Note that CLEAR
will not override the previous functionality; if an axis is not specified
to CLEAR it will have its kinematic position set as per above. This
command may invalidate future boundary checks; issue a G28 afterwards to
reset the kinematics.
[SET_HOMED=<[X][Y][Z]>] [CLEAR_HOMED=<[X][Y][Z]>]`: Force the
low-level kinematic code to believe the toolhead is at the given
cartesian position and set/clear homed status. This is a diagnostic
and debugging command; use SET_GCODE_OFFSET and/or G92 for regular
axis transformations. Setting an incorrect or invalid position may
lead to internal software errors.
The `X`, `Y`, and `Z` parameters are used to alter the low-level
kinematic position tracking. If any of these parameters are not set
then the position is not changed - for example `SET_KINEMATIC_POSITION
Z=10` would set all axes as homed, set the internal Z position to 10,
and leave the X and Y positions unchanged. Changing the internal
position tracking is not dependent on the internal homing state - one
may alter the position for both homed and not homed axes, and
similarly one may set or clear the homing state of an axis without
altering its internal position.
The `SET_HOMED` parameter defaults to `XYZ` which instructs the
kinematics to consider all axes as homed. A bare
`SET_KINEMATIC_POSITION` command will result in all axes being
considered homed (and not change its current position). If it is not
desired to change the state of homed axes then assign `SET_HOMED` to
an empty string - for example:
`SET_KINEMATIC_POSITION SET_HOMED= X=10`. It is also possible to
request an individual axis be considered homed (eg, `SET_HOMED=X`),
but note that non-cartesian style kinematics (such as delta
kinematics) may not support setting an individual axis as homed.
The `CLEAR_HOMED` parameter instructs the kinematics to consider the
given axes as not homed. For example, `CLEAR_HOMED=XYZ` would request
all axes to be considered not homed (and thus require homing prior to
movement on those axes). The default is `SET_HOMED=XYZ` even if
`CLEAR_HOMED` is present, so the command `SET_KINEMATIC_POSITION
CLEAR_HOMED=Z` will set X and Y as homed and clear the homing state
for Z. Use `SET_KINEMATIC_POSITION SET_HOMED= CLEAR_HOMED=Z` if the
goal is to clear only the Z homing state. If an axis is specified in
neither `SET_HOMED` nor `CLEAR_HOMED` then its homing state is not
changed and if it is specified in both then `CLEAR_HOMED` has
precedence. It is possible to request clearing of an individual axis,
but on non-cartesian style kinematics (such as delta kinematics) doing
so may result in clearing the homing state of additional axes. Note
the `CLEAR` parameter is currently an alias for the `CLEAR_HOMED`
parameter, but this alias will be removed in the future.
### [gcode]
@@ -766,6 +793,82 @@ together with either of SHAPER_TYPE_X and SHAPER_TYPE_Y parameters.
See [config reference](Config_Reference.md#input_shaper) for more
details on each of these parameters.
### [led]
The following command is available when any of the
[led config sections](Config_Reference.md#leds) are enabled.
#### SET_LED
`SET_LED LED=<config_name> RED=<value> GREEN=<value> BLUE=<value>
WHITE=<value> [INDEX=<index>] [TRANSMIT=0] [SYNC=1]`: This sets the
LED output. Each color `<value>` must be between 0.0 and 1.0. The
WHITE option is only valid on RGBW LEDs. If the LED supports multiple
chips in a daisy-chain then one may specify INDEX to alter the color
of just the given chip (1 for the first chip, 2 for the second,
etc.). If INDEX is not provided then all LEDs in the daisy-chain will
be set to the provided color. If TRANSMIT=0 is specified then the
color change will only be made on the next SET_LED command that does
not specify TRANSMIT=0; this may be useful in combination with the
INDEX parameter to batch multiple updates in a daisy-chain. By
default, the SET_LED command will sync it's changes with other ongoing
gcode commands. This can lead to undesirable behavior if LEDs are
being set while the printer is not printing as it will reset the idle
timeout. If careful timing is not needed, the optional SYNC=0
parameter can be specified to apply the changes without resetting the
idle timeout.
#### SET_LED_TEMPLATE
`SET_LED_TEMPLATE LED=<led_name> TEMPLATE=<template_name>
[<param_x>=<literal>] [INDEX=<index>]`: Assign a
[display_template](Config_Reference.md#display_template) to a given
[LED](Config_Reference.md#leds). For example, if one defined a
`[display_template my_led_template]` config section then one could
assign `TEMPLATE=my_led_template` here. The display_template should
produce a comma separated string containing four floating point
numbers corresponding to red, green, blue, and white color settings.
The template will be continuously evaluated and the LED will be
automatically set to the resulting colors. One may set
display_template parameters to use during template evaluation
(parameters will be parsed as Python literals). If INDEX is not
specified then all chips in the LED's daisy-chain will be set to the
template, otherwise only the chip with the given index will be
updated. If TEMPLATE is an empty string then this command will clear
any previous template assigned to the LED (one can then use `SET_LED`
commands to manage the LED's color settings).
### [load_cell]
The following commands are enabled if a
[load_cell config section](Config_Reference.md#load_cell) has been enabled.
### LOAD_CELL_DIAGNOSTIC
`LOAD_CELL_DIAGNOSTIC [LOAD_CELL=<config_name>]`: This command collects 10
seconds of load cell data and reports statistics that can help you verify proper
operation of the load cell. This command can be run on both calibrated and
uncalibrated load cells.
### LOAD_CELL_CALIBRATE
`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
`CALIBRATE GRAMS=nnn` command
1. Finally use the `ACCEPT` command to save the results
You can cancel the calibration process at any time with `ABORT`.
### LOAD_CELL_TARE
`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.
### LOAD_CELL_READ load_cell="name"
`LOAD_CELL_READ [LOAD_CELL=<config_name>]`:
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.
### [manual_probe]
The manual_probe module is automatically loaded.
@@ -836,49 +939,6 @@ be between 0.0 and 1.0, unless a 'scale' is defined in the config.
When 'scale' is defined, then this value should be between 0.0 and
'scale'.
### [led]
The following command is available when any of the
[led config sections](Config_Reference.md#leds) are enabled.
#### SET_LED
`SET_LED LED=<config_name> RED=<value> GREEN=<value> BLUE=<value>
WHITE=<value> [INDEX=<index>] [TRANSMIT=0] [SYNC=1]`: This sets the
LED output. Each color `<value>` must be between 0.0 and 1.0. The
WHITE option is only valid on RGBW LEDs. If the LED supports multiple
chips in a daisy-chain then one may specify INDEX to alter the color
of just the given chip (1 for the first chip, 2 for the second,
etc.). If INDEX is not provided then all LEDs in the daisy-chain will
be set to the provided color. If TRANSMIT=0 is specified then the
color change will only be made on the next SET_LED command that does
not specify TRANSMIT=0; this may be useful in combination with the
INDEX parameter to batch multiple updates in a daisy-chain. By
default, the SET_LED command will sync it's changes with other ongoing
gcode commands. This can lead to undesirable behavior if LEDs are
being set while the printer is not printing as it will reset the idle
timeout. If careful timing is not needed, the optional SYNC=0
parameter can be specified to apply the changes without resetting the
idle timeout.
#### SET_LED_TEMPLATE
`SET_LED_TEMPLATE LED=<led_name> TEMPLATE=<template_name>
[<param_x>=<literal>] [INDEX=<index>]`: Assign a
[display_template](Config_Reference.md#display_template) to a given
[LED](Config_Reference.md#leds). For example, if one defined a
`[display_template my_led_template]` config section then one could
assign `TEMPLATE=my_led_template` here. The display_template should
produce a comma separated string containing four floating point
numbers corresponding to red, green, blue, and white color settings.
The template will be continuously evaluated and the LED will be
automatically set to the resulting colors. One may set
display_template parameters to use during template evaluation
(parameters will be parsed as Python literals). If INDEX is not
specified then all chips in the LED's daisy-chain will be set to the
template, otherwise only the chip with the given index will be
updated. If TEMPLATE is an empty string then this command will clear
any previous template assigned to the LED (one can then use `SET_LED`
commands to manage the LED's color settings).
### [output_pin]
The following command is available when an
@@ -941,20 +1001,6 @@ Palette 2 once the loading has been completed. This command is the
same as pressing **Smart Load** directly on the Palette 2 screen after
the filament load is complete.
### [pid_calibrate]
The pid_calibrate module is automatically loaded if a heater is defined
in the config file.
#### PID_CALIBRATE
`PID_CALIBRATE HEATER=<config_name> TARGET=<temperature>
[WRITE_FILE=1]`: Perform a PID calibration test. The specified heater
will be enabled until the specified target temperature is reached, and
then the heater will be turned off and on for several cycles. If the
WRITE_FILE parameter is enabled, then the file /tmp/heattest.txt will
be created with a log of all temperature samples taken during the
test.
### [pause_resume]
The following commands are available when the
@@ -980,6 +1026,20 @@ the paused state is fresh for each print.
#### CANCEL_PRINT
`CANCEL_PRINT`: Cancels the current print.
### [pid_calibrate]
The pid_calibrate module is automatically loaded if a heater is defined
in the config file.
#### PID_CALIBRATE
`PID_CALIBRATE HEATER=<config_name> TARGET=<temperature>
[WRITE_FILE=1]`: Perform a PID calibration test. The specified heater
will be enabled until the specified target temperature is reached, and
then the heater will be turned off and on for several cycles. If the
WRITE_FILE parameter is enabled, then the file /tmp/heattest.txt will
be created with a log of all temperature samples taken during the
test.
### [print_stats]
The print_stats module is automatically loaded.
@@ -1201,8 +1261,9 @@ has been enabled.
#### SAVE_VARIABLE
`SAVE_VARIABLE VARIABLE=<name> VALUE=<value>`: Saves the variable to
disk so that it can be used across restarts. All stored variables are
loaded into the `printer.save_variables.variables` dict at startup and
disk so that it can be used across restarts. The VARIABLE must be lowercase.
All stored variables are loaded into the
`printer.save_variables.variables` dict at startup and
can be used in gcode macros. The provided VALUE is parsed as a Python
literal.
@@ -1346,6 +1407,42 @@ temperature_fan. If a target is not supplied, it is set to the
specified temperature in the config file. If speeds are not supplied,
no change is applied.
### [temperature_probe]
The following commands are available when a
[temperature_probe config section](Config_Reference.md#temperature_probe)
is enabled.
#### TEMPERATURE_PROBE_CALIBRATE
`TEMPERATURE_PROBE_CALIBRATE [PROBE=<probe name>] [TARGET=<value>] [STEP=<value>]`:
Initiates probe drift calibration for eddy current based probes. The `TARGET`
is a target temperature for the last sample. When the temperature recorded
during a sample exceeds the `TARGET` calibration will complete. The `STEP`
parameter sets temperature delta (in C) between samples. After a sample has
been taken, this delta is used to schedule a call to `TEMPERATURE_PROBE_NEXT`.
The default `STEP` is 2.
#### TEMPERATURE_PROBE_NEXT
`TEMPERATURE_PROBE_NEXT`: After calibration has started this command is run to
take the next sample. It is automatically scheduled to run when the delta
specified by `STEP` has been reached, however its also possible to manually run
this command to force a new sample. This command is only available during
calibration.
#### TEMPERATURE_PROBE_COMPLETE:
`TEMPERATURE_PROBE_COMPLETE`: Can be used to end calibration and save the
current result before the `TARGET` temperature is reached. This command
is only available during calibration.
#### ABORT
`ABORT`: Aborts the calibration process, discarding the current results.
This command is only available during drift calibration.
### TEMPERATURE_PROBE_ENABLE
`TEMPERATURE_PROBE_ENABLE ENABLE=[0|1]`: Sets temperature drift
compensation on or off. If ENABLE is set to 0, drift compensation
will be disabled, if set to 1 it is enabled.
### [tmcXXXX]
The following commands are available when any of the
@@ -1481,39 +1578,3 @@ independent adjustments to each Z stepper to compensate for tilt. See
the PROBE command for details on the optional probe parameters. The
optional `RETRIES`, `RETRY_TOLERANCE`, and `HORIZONTAL_MOVE_Z` values
override those options specified in the config file.
### [temperature_probe]
The following commands are available when a
[temperature_probe config section](Config_Reference.md#temperature_probe)
is enabled.
#### TEMPERATURE_PROBE_CALIBRATE
`TEMPERATURE_PROBE_CALIBRATE [PROBE=<probe name>] [TARGET=<value>] [STEP=<value>]`:
Initiates probe drift calibration for eddy current based probes. The `TARGET`
is a target temperature for the last sample. When the temperature recorded
during a sample exceeds the `TARGET` calibration will complete. The `STEP`
parameter sets temperature delta (in C) between samples. After a sample has
been taken, this delta is used to schedule a call to `TEMPERATURE_PROBE_NEXT`.
The default `STEP` is 2.
#### TEMPERATURE_PROBE_NEXT
`TEMPERATURE_PROBE_NEXT`: After calibration has started this command is run to
take the next sample. It is automatically scheduled to run when the delta
specified by `STEP` has been reached, however its also possible to manually run
this command to force a new sample. This command is only available during
calibration.
#### TEMPERATURE_PROBE_COMPLETE:
`TEMPERATURE_PROBE_COMPLETE`: Can be used to end calibration and save the
current result before the `TARGET` temperature is reached. This command
is only available during calibration.
#### ABORT
`ABORT`: Aborts the calibration process, discarding the current results.
This command is only available during drift calibration.
### TEMPERATURE_PROBE_ENABLE
`TEMPERATURE_PROBE_ENABLE ENABLE=[0|1]`: Sets temperature drift
compensation on or off. If ENABLE is set to 0, drift compensation
will be disabled, if set to 1 it is enabled.

View File

@@ -1,14 +1,14 @@
# Installation
These instructions assume the software will run on a linux based host
running a Klipper compatible front end. It is recommended that a
SBC(Small Board Computer) such as a Raspberry Pi or Debian based Linux
These instructions assume the software will run on a Linux-based host
running a Klipper-compatible front end. It is recommended that a
SBC(Small Board Computer) such as a Raspberry Pi or Debian-based Linux
device be used as the host machine (see the
[FAQ](FAQ.md#can-i-run-klipper-on-something-other-than-a-raspberry-pi-3)
for other options).
For the purposes of these instructions host relates to the Linux device and
mcu relates to the printboard. SBC relates to the term Small Board Computer
For the purposes of these instructions, host relates to the Linux device and
mcu relates to the printer board. SBC relates to the term Small Board Computer
such as the Raspberry Pi.
## Obtain a Klipper Configuration File
@@ -56,13 +56,13 @@ make an informed decision.
## Obtaining an OS image for SBC's
There are many ways to obtain an OS image for Klipper for SBC use, most depend on
what front end you wish to use. Some manafactures of these SBC boards also provide
what front end you wish to use. Some manufacturers of these SBC boards also provide
their own Klipper-centric images.
The two main Moonraker based front ends are [Fluidd](https://docs.fluidd.xyz/)
The two main Moonraker-based front ends are [Fluidd](https://docs.fluidd.xyz/)
and [Mainsail](https://docs.mainsail.xyz/), the latter of which has a premade install
image ["MainsailOS"](http://docs.mainsailOS.xyz), this has the option for Raspberry Pi
and some OrangePi varianta.
image ["MainsailOS"](https://docs-os.mainsail.xyz/), this has the option for Raspberry Pi
and some OrangePi variants.
Fluidd can be installed via KIAUH(Klipper Install And Update Helper), which
is explained below and is a 3rd party installer for all things Klipper.
@@ -73,12 +73,12 @@ process is explained in [OctoPrint.md](OctoPrint.md)
## Installing via KIAUH
Normally you would start with a base image for your SBC, RPiOS Lite for example,
or in the case of a x86 Linux device, Ubuntu Server. Please note that Desktop
or in the case of an x86 Linux device, Ubuntu Server. Please note that Desktop
variants are not recommended due to certain helper programs that can stop some
Klipper functions working and even mask access to some print boards.
Klipper functions from working and even mask access to some printer boards.
KIAUH can be used to install Klipper and its associated programs on a variety
of Linux based systems that run a form of Debian. More information can be found
of Linux-based systems that run a form of Debian. More information can be found
at https://github.com/dw-0/kiauh
## Building and flashing the micro-controller
@@ -106,7 +106,7 @@ make
If the comments at the top of the
[printer configuration file](#obtain-a-klipper-configuration-file)
describe custom steps for "flashing" the final image to the printer
control board then follow those steps and then proceed to
control board, then follow those steps and then proceed to
[configuring OctoPrint](#configuring-octoprint-to-use-klipper).
Otherwise, the following steps are often used to "flash" the printer
@@ -132,12 +132,12 @@ run the command again, the missing item will be your print board(see the
[FAQ](FAQ.md#wheres-my-serial-port) for more information).
For common micro-controllers with STM32 or clone chips, LPC chips and
others it is usual that these need an initial Klipper flash via SD card.
others, it is usual that these need an initial Klipper flash via SD card.
When flashing with this method, it is important to make sure that the
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
occuring.
occurring.
For common micro-controllers using Atmega chips, for example the 2560,
the code can be flashed with something
@@ -172,7 +172,7 @@ The next step is to copy the
the host.
Arguably the easiest way to set the Klipper configuration file is using the
built in editors in Mainsail or Fluidd. These will allow the user to open
built-in editors in Mainsail or Fluidd. These will allow the user to open
the configuration examples and save them to be printer.cfg.
Another option is to use a desktop editor that supports editing files
@@ -183,7 +183,7 @@ named "printer.cfg" in the home directory of the pi user
(ie, /home/pi/printer.cfg).
Alternatively, one can also copy and edit the file directly on the
host via ssh. That may look something like the following (be
host via SSH. That may look something like the following (be
sure to update the command to use the appropriate printer config
filename):
@@ -214,9 +214,9 @@ the `[mcu]` section to look something similar to:
serial: /dev/serial/by-id/usb-1a86_USB2.0-Serial-if00-port0
```
After creating and editing the file it will be necessary to issue a
After creating and editing the file, it will be necessary to issue a
"restart" command in the command console to load the config. A
"status" command will report the printer is ready if the Klipper
"status" command will report that the printer is ready if the Klipper
config file is successfully read and the micro-controller is
successfully found and configured.
@@ -225,10 +225,10 @@ Klipper to report a configuration error. If an error occurs, make any
necessary corrections to the printer config file and issue "restart"
until "status" reports the printer is ready.
Klipper reports error messages via the command console and via pop up in
Klipper reports error messages via the command console and pop-ups in
Fluidd and Mainsail. The "status" command can be used to re-report error
messages. A log is available and usually located in ~/printer_data/logs
this is named klippy.log
messages. A log is available and usually located at
`~/printer_data/logs/klippy.log`.
After Klipper reports that the printer is ready, proceed to the
[config check document](Config_checks.md) to perform some basic checks

122
docs/Load_Cell.md Normal file
View File

@@ -0,0 +1,122 @@
# Load Cells
This document describes Klipper's support for load cells. Basic load cell
functionality can be used to read force data and to weigh things like filament.
A calibrated force sensor is an important part of a load cell based probe.
## Related Documentation
* [load_cell Config Reference](Config_Reference.md#load_cell)
* [load_cell G-Code Commands](G-Codes.md#load_cell)
* [load_cell Status Reference](Status_Reference.md#load_cell)
## Using `LOAD_CELL_DIAGNOSTIC`
When you first connect a load cell its good practice to check for issues by
running `LOAD_CELL_DIAGNOSTIC`. This tool collects 10 seconds of data from the
load cell and resport statistics:
```
$ LOAD_CELL_DIAGNOSTIC
// Collecting load cell data for 10 seconds...
// Samples Collected: 3211
// Measured samples per second: 332.0
// Good samples: 3211, Saturated samples: 0, Unique values: 900
// Sample range: [4.01% to 4.02%]
// Sample range / sensor capacity: 0.00524%
```
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.
* 'Saturated samples' should be 0. If you have saturated samples it means the
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.
* Tap or push on the sensor while `LOAD_CELL_DIAGNOSTIC` runs. If
things are working correctly ths 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.
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.
3. Finally, use the `ACCEPT` command to save the results.
You can cancel the calibration process at any time with `ABORT`.
### Applying a Known Force or Load
The `CALIBRATE GRAMS=nnn` step can be accomplished in a number of ways. If your
load cell is under a platform like a bed or filament holder it might be easiest
to put a known mass on the platform. E.g. you could use a couple of 1KG filament
spools.
If your load cell is in the printer's toolhead a different approach is easier.
Put a digital scale on the printers bed and gently lower the toolhead onto the
scale (or raise the bed into the toolhead if your bed moves). You may be able to
do this using the `FORCE_MOVE` command. But more likely you will have to
manually moving the z axis with the motors off until the toolhead presses on the
scale.
A good calibration force would ideally be a large percentage of the load cell's
rated capacity. E.g. if you have a 5Kg load cell you would ideally calibrate it
with a 5kg mass. This might work well with under-bed sensors that have to
support a lot of weight. For toolhead probes this may not be a load that your
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
critical for 32bit and 24bit sensors but is much more critical for low bit width
sensors.
## Reading Force Data
Force data can be read with a GCode command:
```
LOAD_CELL_READ
// 10.6g (1.94%)
```
Data is also continuously read and can be consumed from the load_cell printer
object in a macro:
```
{% set grams = printer.load_cell.force_g %}
```
This provides an average force over the last 1 second, similar to how
temperature sensors work.
## Taring a Load Cell
Taring, sometimes called zeroing, sets the current weight reported by the
load_cell to 0. This is useful for measuring relative to a known weight. e.g.
when measuring a filament spool, using `LOAD_CELL_TARE` sets the weight to 0.
Then as filament is printed the load_cell will report the weight of the
filament used.
```
LOAD_CELL_TARE
// Load cell tare value: 5.32% (445903)
```
The current tare value is reported in the printers status and can be read in
a macro:
```
{% set tare_counts = printer.load_cell.tare_counts %}
```

View File

@@ -18,9 +18,9 @@ board designs and different clones of them. If it is going to be connected to a
For ADXL345s, make sure that the board supports SPI mode (a small number of
boards appear to be hard-configured for I2C by pulling SDO to GND).
For MPU-9250/MPU-9255/MPU-6515/MPU-6050/MPU-6500s and LIS2DW/LIS3DH there are also
a variety of board designs and clones with different I2C pull-up resistors which
will need supplementing.
For MPU-9250/MPU-9255/MPU-6515/MPU-6050/MPU-6500/ICM20948s and LIS2DW/LIS3DH there
are also a variety of board designs and clones with different I2C pull-up resistors
which will need supplementing.
## MCUs with Klipper I2C *fast-mode* Support
@@ -136,7 +136,7 @@ GND+SCL
Note that unlike a cable shield, any GND(s) should be connected at both ends.
#### MPU-9250/MPU-9255/MPU-6515/MPU-6050/MPU-6500
#### MPU-9250/MPU-9255/MPU-6515/MPU-6050/MPU-6500/ICM20948
These accelerometers have been tested to work over I2C on the RPi, RP2040 (Pico)
and AVR at 400kbit/s (*fast mode*). Some MPU accelerometer modules include
@@ -355,6 +355,7 @@ accel_chip: mpu9250
probe_points:
100, 100, 20 # an example
```
If you are using the ICM20948, replace instances of "mpu9250" with "icm20948".
#### Configure MPU-9520 Compatibles With Pico
@@ -377,6 +378,7 @@ probe_points:
[static_digital_output pico_3V3pwm] # Improve power stability
pins: pico:gpio23
```
If you are using the ICM20948, replace instances of "mpu9250" with "icm20948".
#### Configure MPU-9520 Compatibles with AVR
@@ -395,6 +397,7 @@ accel_chip: mpu9250
probe_points:
100, 100, 20 # an example
```
If you are using the ICM20948, replace instances of "mpu9250" with "icm20948".
Restart Klipper via the `RESTART` command.

View File

@@ -101,3 +101,4 @@ communication with the Klipper developers.
- [TSL1401CL filament width sensor](TSL1401CL_Filament_Width_Sensor.md)
- [Hall filament width sensor](Hall_Filament_Width_Sensor.md)
- [Eddy Current Inductive probe](Eddy_Probe.md)
- [Load Cells](Load_Cell.md)

View File

@@ -22,7 +22,7 @@ Use a slicer to generate g-code for the large hollow square found in
[docs/prints/square_tower.stl](prints/square_tower.stl). Use a high
speed (eg, 100mm/s), zero infill, and a coarse layer height (the layer
height should be around 75% of the nozzle diameter). Make sure any
"dynamic acceleration control" is disabled in the slicer.
"dynamic acceleration control" and "scarf joint" seams are disabled in the slicer.
Prepare for the test by issuing the following G-Code command:
```

View File

@@ -3,6 +3,35 @@
History of Klipper releases. Please see
[installation](Installation.md) for information on installing Klipper.
## Klipper 0.13.0
Available on 20250411. Major changes in this release:
* New "sweeping vibrations" resonance testing mechanism for input
shaper.
* Fans and GPIO pins can now be assigned a formula (via Jinja2
"templates").
* The bed_mesh code now supports "adaptive bed mesh". The area probed
can be adjusted for the size of the print.
* A new `minimum_cruise_ratio` kinematic parameter has been added (it
replaces the previous `max_accel_to_decel` parameter).
* Several new sensors added:
* Support for ldc1612 "eddy" current sensors. This includes probing
support, fast "scan" probing, and temperature calibration.
* New support for "load cell" measurements. Support for connecting
these load cells to hx71x and ads1220 ADC sensors.
* Support for BMP180, BMP388, and SHT3x temperature sensors. Support
for measuring temperature with ADS1x1x ADC chips.
* New lis3dh and icm20948 accelerometer support.
* Support for mt6816 and mt6826s "hall angle" sensors.
* New micro-controller improvements:
* New support for rp2350 micro-controllers.
* Existing rp2040 chips now run at 200MHz (up from 125Mhz).
* The micro-controller code can now define many more commands (up to
16384 from 128).
* Other modules added: aip31068_spi, canbus_stats, error_mcu,
garbage_collection, pwm_cycle_time, pwm_tool, garbage_collection.
* Several bug fixes and code cleanups.
## Klipper 0.12.0
Available on 20231110. Major changes in this release:

View File

@@ -31,7 +31,7 @@ The following information is available in the
## bed_screws
The following information is available in the
`Config_Reference.md#bed_screws` object:
[bed_screws](Config_Reference.md#bed_screws) object:
- `is_active`: Returns True if the bed screws adjustment tool is currently
active.
- `state`: The bed screws adjustment tool state. It is one of
@@ -39,6 +39,27 @@ the following strings: "adjust", "fine".
- `current_screw`: The index for the current screw being adjusted.
- `accepted_screws`: The number of accepted screws.
## canbus_stats
The following information is available in the `canbus_stats
some_mcu_name` object (this object is automatically available if an
mcu is configured to use canbus):
- `rx_error`: The number of receive errors detected by the
micro-controller canbus hardware.
- `tx_error`: The number of transmit errors detected by the
micro-controller canbus hardware.
- `tx_retries`: The number of transmit attempts that were retried due
to bus contention or errors.
- `bus_state`: The status of the interface (typically "active" for a
bus in normal operation, "warn" for a bus with recent errors,
"passive" for a bus that will no longer transmit canbus error
frames, or "off" for a bus that will no longer transmit or receive
messages).
Note that only the rp2XXX micro-controllers report a non-zero
`tx_retries` field and the rp2XXX micro-controllers always report
`tx_error` as zero and `bus_state` as "active".
## configfile
The following information is available in the `configfile` object
@@ -282,6 +303,17 @@ The following information is available for each `[led led_name]`,
chain could be accessed at
`printer["neopixel <config_name>"].color_data[1][2]`.
## load_cell
The following information is available for each `[load_cell name]`:
- 'is_calibrated': True/False is the load cell calibrated
- 'counts_per_gram': The number of raw sensor counts that equals 1 gram of force
- 'reference_tare_counts': The reference number of raw sensor counts for 0 force
- 'tare_counts': The current number of raw sensor counts for 0 force
- 'force_g': The force in grams, averaged over the last polling period.
- '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.
## manual_probe
The following information is available in the
@@ -426,6 +458,12 @@ The following information is available in
- `printer["servo <config_name>"].value`: The last setting of the PWM
pin (a value between 0.0 and 1.0) associated with the servo.
## skew_correction.py
The following information is available in the `skew_correction` object (this
object is available if any skew_correction is defined):
- `current_profile_name`: Returns the name of the currently loaded SKEW_PROFILE.
## stepper_enable
The following information is available in the `stepper_enable` object (this

View File

@@ -83,6 +83,10 @@ setting `stealthchop_threshold` to 999999). Unfortunately, the drivers
often produce poor and confusing results if the mode changes while the
motor is at a non-zero velocity.
Note that the `stealthchop_threshold` config option does not impact
sensorless homing as Klipper automatically switches the TMC driver to
an appropriate mode during sensorless homing operations.
## TMC interpolate setting introduces small position deviation
The TMC driver `interpolate` setting may reduce the audible noise of

View File

@@ -8,13 +8,13 @@ directory, the docs/CNAME file also controls the website generation.
To test deploy the main English site locally one can use commands
similar to the following:
virtualenv ~/mkdocs-env && ~/python-env/bin/pip install -r ~/klipper/docs/_klipper3d/mkdocs-requirements.txt
virtualenv ~/mkdocs-env && ~/mkdocs-env/bin/pip install -r ~/klipper/docs/_klipper3d/mkdocs-requirements.txt
cd ~/klipper && ~/mkdocs-env/bin/mkdocs serve --config-file ~/klipper/docs/_klipper3d/mkdocs.yml -a 0.0.0.0:8000
To test deploy the multi-language site locally one can use commands
similar to the following:
virtualenv ~/mkdocs-env && ~/python-env/bin/pip install -r ~/klipper/docs/_klipper3d/mkdocs-requirements.txt
virtualenv ~/mkdocs-env && ~/mkdocs-env/bin/pip install -r ~/klipper/docs/_klipper3d/mkdocs-requirements.txt
source ~/mkdocs-env/bin/activate
cd ~/klipper && ./docs/_klipper3d/build-translations.sh
cd ~/klipper/site/ && python3 -m http.server 8000

View File

@@ -1,5 +1,5 @@
# Python virtualenv module requirements for mkdocs
jinja2==3.1.4
jinja2==3.1.6
mkdocs==1.2.4
mkdocs-material==8.1.3
mkdocs-simple-hooks==0.1.3

View File

@@ -141,4 +141,5 @@ nav:
- TSL1401CL_Filament_Width_Sensor.md
- Hall_Filament_Width_Sensor.md
- Eddy_Probe.md
- Load_Cell.md
- Sponsors.md

View File

@@ -156,6 +156,16 @@ shaper_xy_calc_position(struct stepper_kinematics *sk, struct move *m
return is->orig_sk->calc_position_cb(is->orig_sk, &is->m, DUMMY_T);
}
// A callback that forwards post_cb call to the original kinematics
static void
shaper_commanded_pos_post_fixup(struct stepper_kinematics *sk)
{
struct input_shaper *is = container_of(sk, struct input_shaper, sk);
is->orig_sk->commanded_pos = sk->commanded_pos;
is->orig_sk->post_cb(is->orig_sk);
sk->commanded_pos = is->orig_sk->commanded_pos;
}
int __visible
input_shaper_set_sk(struct stepper_kinematics *sk
, struct stepper_kinematics *orig_sk)
@@ -174,6 +184,9 @@ input_shaper_set_sk(struct stepper_kinematics *sk
is->sk.commanded_pos = orig_sk->commanded_pos;
is->sk.last_flush_time = orig_sk->last_flush_time;
is->sk.last_move_time = orig_sk->last_move_time;
if (orig_sk->post_cb) {
is->sk.post_cb = shaper_commanded_pos_post_fixup;
}
return 0;
}

View File

@@ -95,10 +95,6 @@ class ADS1220:
self.batch_bulk = bulk_sensor.BatchBulkHelper(
self.printer, self._process_batch, self._start_measurements,
self._finish_measurements, UPDATE_INTERVAL)
# publish raw samples to the socket
hdr = {'header': ('time', 'counts', 'value')}
self.batch_bulk.add_mux_endpoint("ads1220/dump_ads1220", "sensor",
self.name, hdr)
# Command Configuration
mcu.add_config_cmd(
"config_ads1220 oid=%d spi_oid=%d data_ready_pin=%s"

View File

@@ -166,12 +166,12 @@ class AccelCommandHelper:
% (accel_x, accel_y, accel_z))
cmd_ACCELEROMETER_DEBUG_READ_help = "Query register (for debugging)"
def cmd_ACCELEROMETER_DEBUG_READ(self, gcmd):
reg = gcmd.get("REG", minval=0, maxval=126, parser=lambda x: int(x, 0))
reg = gcmd.get("REG", minval=0, maxval=127, parser=lambda x: int(x, 0))
val = self.chip.read_reg(reg)
gcmd.respond_info("Accelerometer REG[0x%x] = 0x%x" % (reg, val))
cmd_ACCELEROMETER_DEBUG_WRITE_help = "Set register (for debugging)"
def cmd_ACCELEROMETER_DEBUG_WRITE(self, gcmd):
reg = gcmd.get("REG", minval=0, maxval=126, parser=lambda x: int(x, 0))
reg = gcmd.get("REG", minval=0, maxval=127, parser=lambda x: int(x, 0))
val = gcmd.get("VAL", minval=0, maxval=255, parser=lambda x: int(x, 0))
self.chip.set_reg(reg, val)

View File

@@ -125,9 +125,8 @@ class Calibrater:
def _handle_connect(self):
self.probe = self.printer.lookup_object('probe', None)
if (self.probe is None):
config = self.printer.lookup_object('configfile')
raise config.error(
if self.probe is None:
raise self.printer.config_error(
"AXIS_TWIST_COMPENSATION requires [probe] to be defined")
self.lift_speed = self.probe.get_probe_params()['lift_speed']
self.probe_x_offset, self.probe_y_offset, _ = \
@@ -150,20 +149,7 @@ class Calibrater:
def cmd_AXIS_TWIST_COMPENSATION_CALIBRATE(self, gcmd):
self.gcmd = gcmd
sample_count = gcmd.get_int('SAMPLE_COUNT', DEFAULT_SAMPLE_COUNT)
axis = gcmd.get('AXIS', None)
auto = gcmd.get('AUTO', False)
if axis is not None and auto:
raise self.gcmd.error(
"Cannot use both 'AXIS' and 'AUTO' at the same time."
)
if auto:
self._start_autocalibration(sample_count)
return
if axis is None and not auto:
axis = 'X'
axis = gcmd.get('AXIS', 'X')
# check for valid sample_count
if sample_count < 2:
@@ -244,153 +230,6 @@ class Calibrater:
self.current_axis = axis
self._calibration(probe_points, nozzle_points, interval_dist)
def _calculate_corrections(self, coordinates):
# Extracting x, y, and z values from coordinates
x_coords = [coord[0] for coord in coordinates]
y_coords = [coord[1] for coord in coordinates]
z_coords = [coord[2] for coord in coordinates]
# Calculate the desired point (average of all corner points in z)
# For a general case, we should extract the unique
# combinations of corner points
z_corners = [z_coords[i] for i, coord in enumerate(coordinates)
if (coord[0] in [x_coords[0], x_coords[-1]])
and (coord[1] in [y_coords[0], y_coords[-1]])]
z_desired = sum(z_corners) / len(z_corners)
# Calculate average deformation per axis
unique_x_coords = sorted(set(x_coords))
unique_y_coords = sorted(set(y_coords))
avg_z_x = []
for x in unique_x_coords:
indices = [i for i, coord in enumerate(coordinates)
if coord[0] == x]
avg_z = sum(z_coords[i] for i in indices) / len(indices)
avg_z_x.append(avg_z)
avg_z_y = []
for y in unique_y_coords:
indices = [i for i, coord in enumerate(coordinates)
if coord[1] == y]
avg_z = sum(z_coords[i] for i in indices) / len(indices)
avg_z_y.append(avg_z)
# Calculate corrections to reach the desired point
x_corrections = [z_desired - avg for avg in avg_z_x]
y_corrections = [z_desired - avg for avg in avg_z_y]
return x_corrections, y_corrections
def _start_autocalibration(self, sample_count):
if not all([
self.x_start_point[0],
self.x_end_point[0],
self.y_start_point[0],
self.y_end_point[0]
]):
raise self.gcmd.error(
"""AXIS_TWIST_COMPENSATION_AUTOCALIBRATE requires
calibrate_start_x, calibrate_end_x, calibrate_start_y
and calibrate_end_y to be defined
"""
)
# check for valid sample_count
if sample_count is None or sample_count < 2:
raise self.gcmd.error(
"SAMPLE_COUNT to probe must be at least 2")
# verify no other manual probe is in progress
manual_probe.verify_no_manual_probe(self.printer)
# clear the current config
self.compensation.clear_compensations()
min_x = self.x_start_point[0]
max_x = self.x_end_point[0]
min_y = self.y_start_point[1]
max_y = self.y_end_point[1]
# calculate x positions
interval_x = (max_x - min_x) / (sample_count - 1)
xps = [min_x + interval_x * i for i in range(sample_count)]
# Calculate points array
interval_y = (max_y - min_y) / (sample_count - 1)
flip = False
points = []
for i in range(sample_count):
for j in range(sample_count):
if(not flip):
idx = j
else:
idx = sample_count -1 - j
points.append([xps[i], min_y + interval_y * idx ])
flip = not flip
# calculate the points to put the nozzle at, and probe
probe_points = []
for i in range(len(points)):
x = points[i][0] - self.probe_x_offset
y = points[i][1] - self.probe_y_offset
probe_points.append([x, y, self._auto_calibration((x,y))[2]])
# calculate corrections
x_corr, y_corr = self._calculate_corrections(probe_points)
x_corr_str = ', '.join(["{:.6f}".format(x)
for x in x_corr])
y_corr_str = ', '.join(["{:.6f}".format(x)
for x in y_corr])
# finalize
configfile = self.printer.lookup_object('configfile')
configfile.set(self.configname, 'z_compensations', x_corr_str)
configfile.set(self.configname, 'compensation_start_x',
self.x_start_point[0])
configfile.set(self.configname, 'compensation_end_x',
self.x_end_point[0])
configfile.set(self.configname, 'zy_compensations', y_corr_str)
configfile.set(self.configname, 'compensation_start_y',
self.y_start_point[1])
configfile.set(self.configname, 'compensation_end_y',
self.y_end_point[1])
self.gcode.respond_info(
"AXIS_TWIST_COMPENSATION state has been saved "
"for the current session. The SAVE_CONFIG command will "
"update the printer config file and restart the printer.")
# output result
self.gcmd.respond_info(
"AXIS_TWIST_COMPENSATION_AUTOCALIBRATE: Calibration complete: ")
self.gcmd.respond_info("\n".join(map(str, [x_corr, y_corr])), log=False)
def _auto_calibration(self, probe_point):
# horizontal_move_z (to prevent probe trigger or hitting bed)
self._move_helper((None, None, self.horizontal_move_z))
# move to point to probe
self._move_helper((probe_point[0],
probe_point[1], None))
# probe the point
pos = probe.run_single_probe(self.probe, self.gcmd)
# horizontal_move_z (to prevent probe trigger or hitting bed)
self._move_helper((None, None, self.horizontal_move_z))
return pos
def _calculate_probe_points(self, nozzle_points,
probe_x_offset, probe_y_offset):
# calculate the points to put the nozzle at

View File

@@ -133,7 +133,7 @@ class BedMesh:
self.update_status()
def handle_connect(self):
self.toolhead = self.printer.lookup_object('toolhead')
self.bmc.print_generated_points(logging.info)
self.bmc.print_generated_points(logging.info, truncate=True)
def set_mesh(self, mesh):
if mesh is not None and self.fade_end != self.FADE_DISABLE:
self.log_fade_complete = True
@@ -346,7 +346,7 @@ class BedMeshCalibrate:
self.gcode.register_command(
'BED_MESH_CALIBRATE', self.cmd_BED_MESH_CALIBRATE,
desc=self.cmd_BED_MESH_CALIBRATE_help)
def print_generated_points(self, print_func):
def print_generated_points(self, print_func, truncate=False):
x_offset = y_offset = 0.
probe = self.printer.lookup_object('probe', None)
if probe is not None:
@@ -355,6 +355,10 @@ class BedMeshCalibrate:
" | Tool Adjusted | Probe")
points = self.probe_mgr.get_base_points()
for i, (x, y) in enumerate(points):
if i >= 50 and truncate:
end = len(points) - 1
print_func("...points %d through %d truncated" % (i, end))
break
adj_pt = "(%.1f, %.1f)" % (x - x_offset, y - y_offset)
mesh_pt = "(%.1f, %.1f)" % (x, y)
print_func(
@@ -613,8 +617,6 @@ class BedMeshCalibrate:
self.mesh_config, self.mesh_min, self.mesh_max,
self.radius, self.origin, probe_method
)
gcmd.respond_info("Generating new points...")
self.print_generated_points(gcmd.respond_info)
msg = "\n".join(["%s: %s" % (k, v)
for k, v in self.mesh_config.items()])
logging.info("Updated Mesh Configuration:\n" + msg)

View File

@@ -244,6 +244,33 @@ class HalfStepRotaryEncoder(BaseRotaryEncoder):
BaseRotaryEncoder.R_START | BaseRotaryEncoder.R_DIR_CCW),
)
class DebounceButton:
def __init__(self, config, button_action):
self.printer = config.get_printer()
self.reactor = self.printer.get_reactor()
self.button_action = button_action
self.debounce_delay = config.getfloat('debounce_delay', 0., minval=0.)
self.logical_state = None
self.physical_state = None
self.latest_eventtime = None
def button_handler(self, eventtime, state):
self.physical_state = state
self.latest_eventtime = eventtime
# if there would be no state transition, ignore the event:
if self.logical_state == self.physical_state:
return
trigger_time = eventtime + self.debounce_delay
self.reactor.register_callback(self._debounce_event, trigger_time)
def _debounce_event(self, eventtime):
# if there would be no state transition, ignore the event:
if self.logical_state == self.physical_state:
return
# if there were more recent events, they supersede this one:
if (eventtime - self.debounce_delay) < self.latest_eventtime:
return
# enact state transition and trigger action
self.logical_state = self.physical_state
self.button_action(self.latest_eventtime, self.logical_state)
######################################################################
# Button registration code
@@ -261,6 +288,14 @@ class PrinterButtons:
self.adc_buttons[pin] = adc_buttons = MCU_ADC_buttons(
self.printer, pin, pullup)
adc_buttons.setup_button(min_val, max_val, callback)
def register_debounce_button(self, pin, callback, config):
debounce = DebounceButton(config, callback)
return self.register_buttons([pin], debounce.button_handler)
def register_debounce_adc_button(self, pin, min_val, max_val, pullup
, callback, config):
debounce = DebounceButton(config, callback)
return self.register_adc_button(pin, min_val, max_val, pullup
, debounce.button_handler)
def register_adc_button_push(self, pin, min_val, max_val, pullup, callback):
def helper(eventtime, state, callback=callback):
if state:

View File

@@ -0,0 +1,80 @@
# Report canbus connection status
#
# Copyright (C) 2025 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
class PrinterCANBusStats:
def __init__(self, config):
self.printer = config.get_printer()
self.reactor = self.printer.get_reactor()
self.name = config.get_name().split()[-1]
self.mcu = None
self.get_canbus_status_cmd = None
self.status = {'rx_error': None, 'tx_error': None, 'tx_retries': None,
'bus_state': None}
self.printer.register_event_handler("klippy:connect",
self.handle_connect)
self.printer.register_event_handler("klippy:shutdown",
self.handle_shutdown)
def handle_shutdown(self):
status = self.status.copy()
if status['bus_state'] is not None:
# Clear bus_state on shutdown to note that the values may be stale
status['bus_state'] = 'unknown'
self.status = status
def handle_connect(self):
# Lookup mcu
mcu_name = self.name
if mcu_name != 'mcu':
mcu_name = 'mcu ' + mcu_name
self.mcu = self.printer.lookup_object(mcu_name)
# Lookup status query command
if self.mcu.try_lookup_command("get_canbus_status") is None:
return
self.get_canbus_status_cmd = self.mcu.lookup_query_command(
"get_canbus_status",
"canbus_status rx_error=%u tx_error=%u tx_retries=%u"
" canbus_bus_state=%u")
# Register usb_canbus_state message handling (for usb to canbus bridge)
self.mcu.register_response(self.handle_usb_canbus_state,
"usb_canbus_state")
# Register periodic query timer
self.reactor.register_timer(self.query_event, self.reactor.NOW)
def handle_usb_canbus_state(self, params):
discard = params['discard']
if discard:
logging.warning("USB CANBUS bridge '%s' is discarding!"
% (self.name,))
else:
logging.warning("USB CANBUS bridge '%s' is no longer discarding."
% (self.name,))
def query_event(self, eventtime):
prev_rx = self.status['rx_error']
prev_tx = self.status['tx_error']
prev_retries = self.status['tx_retries']
if prev_rx is None:
prev_rx = prev_tx = prev_retries = 0
params = self.get_canbus_status_cmd.send()
rx = prev_rx + ((params['rx_error'] - prev_rx) & 0xffffffff)
tx = prev_tx + ((params['tx_error'] - prev_tx) & 0xffffffff)
retries = prev_retries + ((params['tx_retries'] - prev_retries)
& 0xffffffff)
state = params['canbus_bus_state']
self.status = {'rx_error': rx, 'tx_error': tx, 'tx_retries': retries,
'bus_state': state}
return self.reactor.monotonic() + 1.
def stats(self, eventtime):
status = self.status
if status['rx_error'] is None:
return (False, '')
return (False, 'canstat_%s: bus_state=%s rx_error=%d'
' tx_error=%d tx_retries=%d'
% (self.name, status['bus_state'], status['rx_error'],
status['tx_error'], status['tx_retries']))
def get_status(self, eventtime):
return self.status
def load_config_prefix(config):
return PrinterCANBusStats(config)

View File

@@ -29,6 +29,7 @@ class PrinterFanGeneric:
value = float(text)
except ValueError as e:
logging.exception("fan_generic template render error")
value = 0.
self.fan.set_speed(value)
def cmd_SET_FAN_SPEED(self, gcmd):
speed = gcmd.get_float('SPEED', None, 0.)

View File

@@ -63,7 +63,7 @@ class EncoderSensor:
def _extruder_pos_update_event(self, eventtime):
extruder_pos = self._get_extruder_pos(eventtime)
# Check for filament runout
self.runout_helper.note_filament_present(
self.runout_helper.note_filament_present(eventtime,
extruder_pos < self.filament_runout_pos)
return eventtime + CHECK_RUNOUT_TIMEOUT
def encoder_event(self, eventtime, state):
@@ -71,7 +71,7 @@ class EncoderSensor:
self._update_filament_runout_pos(eventtime)
# Check for filament insertion
# Filament is always assumed to be present on an encoder event
self.runout_helper.note_filament_present(True)
self.runout_helper.note_filament_present(eventtime, True)
def load_config_prefix(config):
return EncoderSensor(config)

View File

@@ -5,6 +5,7 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
class RunoutHelper:
def __init__(self, config):
self.name = config.get_name().split()[-1]
@@ -24,7 +25,7 @@ class RunoutHelper:
self.insert_gcode = gcode_macro.load_template(
config, 'insert_gcode')
self.pause_delay = config.getfloat('pause_delay', .5, above=.0)
self.event_delay = config.getfloat('event_delay', 3., above=0.)
self.event_delay = config.getfloat('event_delay', 3., minval=.0)
# Internal state
self.min_event_systime = self.reactor.NEVER
self.filament_present = False
@@ -59,19 +60,20 @@ class RunoutHelper:
except Exception:
logging.exception("Script running error")
self.min_event_systime = self.reactor.monotonic() + self.event_delay
def note_filament_present(self, is_filament_present):
def note_filament_present(self, eventtime, is_filament_present):
if is_filament_present == self.filament_present:
return
self.filament_present = is_filament_present
eventtime = self.reactor.monotonic()
if eventtime < self.min_event_systime or not self.sensor_enabled:
# do not process during the initialization time, duplicates,
# during the event delay time, while an event is running, or
# when the sensor is disabled
return
# Determine "printing" status
now = self.reactor.monotonic()
idle_timeout = self.printer.lookup_object("idle_timeout")
is_printing = idle_timeout.get_status(eventtime)["state"] == "Printing"
is_printing = idle_timeout.get_status(now)["state"] == "Printing"
# Perform filament action associated with status change (if any)
if is_filament_present:
if not is_printing and self.insert_gcode is not None:
@@ -79,14 +81,14 @@ class RunoutHelper:
self.min_event_systime = self.reactor.NEVER
logging.info(
"Filament Sensor %s: insert event detected, Time %.2f" %
(self.name, eventtime))
(self.name, now))
self.reactor.register_callback(self._insert_event_handler)
elif is_printing and self.runout_gcode is not None:
# runout detected
self.min_event_systime = self.reactor.NEVER
logging.info(
"Filament Sensor %s: runout event detected, Time %.2f" %
(self.name, eventtime))
(self.name, now))
self.reactor.register_callback(self._runout_event_handler)
def get_status(self, eventtime):
return {
@@ -108,11 +110,12 @@ class SwitchSensor:
printer = config.get_printer()
buttons = printer.load_object(config, 'buttons')
switch_pin = config.get('switch_pin')
buttons.register_buttons([switch_pin], self._button_handler)
buttons.register_debounce_button(switch_pin, self._button_handler
, config)
self.runout_helper = RunoutHelper(config)
self.get_status = self.runout_helper.get_status
def _button_handler(self, eventtime, state):
self.runout_helper.note_filament_present(state)
self.runout_helper.note_filament_present(eventtime, state)
def load_config_prefix(config):
return SwitchSensor(config)

View File

@@ -131,12 +131,19 @@ class ForceMove:
x = gcmd.get_float('X', curpos[0])
y = gcmd.get_float('Y', curpos[1])
z = gcmd.get_float('Z', curpos[2])
clear = gcmd.get('CLEAR', '').lower()
clear_axes = "".join([a for a in "xyz" if a in clear])
logging.info("SET_KINEMATIC_POSITION pos=%.3f,%.3f,%.3f clear=%s",
x, y, z, clear_axes)
toolhead.set_position([x, y, z, curpos[3]], homing_axes="xyz")
toolhead.get_kinematics().clear_homing_state(clear_axes)
set_homed = gcmd.get('SET_HOMED', 'xyz').lower()
set_homed_axes = "".join([a for a in "xyz" if a in set_homed])
if gcmd.get('CLEAR_HOMED', None) is None:
# "CLEAR" is an alias for "CLEAR_HOMED"; should deprecate
clear_homed = gcmd.get('CLEAR', '').lower()
else:
clear_homed = gcmd.get('CLEAR_HOMED', '').lower()
clear_homed_axes = "".join([a for a in "xyz" if a in clear_homed])
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.get_kinematics().clear_homing_state(clear_homed_axes)
def load_config(config):
return ForceMove(config)

View File

@@ -0,0 +1,31 @@
# Garbage collection optimizations
#
# Copyright (C) 2025 Branden Cash <ammmze@gmail.com>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import gc
import logging
class GarbageCollection:
def __init__(self, config):
self.printer = config.get_printer()
# feature check ... freeze/unfreeze is only available in python 3.7+
can_freeze = hasattr(gc, 'freeze') and hasattr(gc, 'unfreeze')
if can_freeze:
self.printer.register_event_handler("klippy:ready",
self._handle_ready)
self.printer.register_event_handler("klippy:disconnect",
self._handle_disconnect)
def _handle_ready(self):
logging.debug("Running full garbage collection and freezing")
for n in range(3):
gc.collect(n)
gc.freeze()
def _handle_disconnect(self):
logging.debug("Unfreezing garbage collection")
gc.unfreeze()
def load_config(config):
return GarbageCollection(config)

View File

@@ -5,6 +5,7 @@
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
class GCodeButton:
def __init__(self, config):
self.printer = config.get_printer()
@@ -13,12 +14,13 @@ class GCodeButton:
self.last_state = 0
buttons = self.printer.load_object(config, "buttons")
if config.get('analog_range', None) is None:
buttons.register_buttons([self.pin], self.button_callback)
buttons.register_debounce_button(self.pin, self.button_callback
, config)
else:
amin, amax = config.getfloatlist('analog_range', count=2)
pullup = config.getfloat('analog_pullup_resistor', 4700., above=0.)
buttons.register_adc_button(self.pin, amin, amax, pullup,
self.button_callback)
buttons.register_debounce_adc_button(self.pin, amin, amax, pullup,
self.button_callback, config)
gcode_macro = self.printer.load_object(config, 'gcode_macro')
self.press_template = gcode_macro.load_template(config, 'press_gcode')
self.release_template = gcode_macro.load_template(config,

View File

@@ -49,6 +49,12 @@ class TemplateWrapper:
self.create_template_context = gcode_macro.create_template_context
try:
self.template = env.from_string(script)
except jinja2.exceptions.TemplateSyntaxError as e:
lines = script.splitlines()
msg = "Error loading template '%s'\nline %s: %s # %s" % (
name, e.lineno, lines[e.lineno-1], e.message)
logging.exception(msg)
raise self.gcode.error(msg)
except Exception as e:
msg = "Error loading template '%s': %s" % (
name, traceback.format_exception_only(type(e), e)[-1])

View File

@@ -125,7 +125,7 @@ class HallFilamentWidthSensor:
# Update filament array for lastFilamentWidthReading
self.update_filament_array(last_epos)
# Check runout
self.runout_helper.note_filament_present(
self.runout_helper.note_filament_present(eventtime,
self.runout_dia_min <= self.diameter <= self.runout_dia_max)
# Does filament exists
if self.diameter > 0.5:

View File

@@ -51,10 +51,6 @@ class HX71xBase:
self.batch_bulk = bulk_sensor.BatchBulkHelper(
self.printer, self._process_batch, self._start_measurements,
self._finish_measurements, UPDATE_INTERVAL)
# publish raw samples to the socket
dump_path = "%s/dump_%s" % (sensor_type, sensor_type)
hdr = {'header': ('time', 'counts', 'value')}
self.batch_bulk.add_mux_endpoint(dump_path, "sensor", self.name, hdr)
# Command Configuration
self.query_hx71x_cmd = None
mcu.add_config_cmd(

173
klippy/extras/icm20948.py Normal file
View File

@@ -0,0 +1,173 @@
# Support for reading acceleration data from an icm20948 chip
#
# Copyright (C) 2024 Paul Hansel <github@paulhansel.com>
# Copyright (C) 2022 Harry Beyel <harry3b9@gmail.com>
# Copyright (C) 2020-2021 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
# From https://invensense.tdk.com/wp-content/uploads/
# 2016/06/DS-000189-ICM-20948-v1.3.pdf
import logging
from . import bus, adxl345, bulk_sensor
ICM20948_ADDR = 0x68
ICM_DEV_IDS = {
0xEA: "icm-20948",
#everything above are normal ICM IDs
}
# ICM20948 registers
REG_DEVID = 0x00 # 0xEA
REG_FIFO_EN = 0x67 # FIFO_EN_2
REG_ACCEL_SMPLRT_DIV1 = 0x10 # MSB
REG_ACCEL_SMPLRT_DIV2 = 0x11 # LSB
REG_ACCEL_CONFIG = 0x14
REG_USER_CTRL = 0x03
REG_PWR_MGMT_1 = 0x06
REG_PWR_MGMT_2 = 0x07
REG_INT_STATUS = 0x19
REG_BANK_SEL = 0x7F
SAMPLE_RATE_DIVS = { 4500: 0x00 }
SET_BANK_0 = 0x00
SET_BANK_1 = 0x10
SET_BANK_2 = 0x20
SET_BANK_3 = 0x30
SET_ACCEL_CONFIG = 0x06 # 16g full scale, 1209Hz BW, 4.5kHz samp rate
SET_PWR_MGMT_1_WAKE = 0x01
SET_PWR_MGMT_1_SLEEP = 0x41
SET_PWR_MGMT_2_ACCEL_ON = 0x07
SET_PWR_MGMT_2_OFF = 0x3F
SET_USER_FIFO_RESET = 0x0E
SET_USER_FIFO_EN = 0x40
SET_ENABLE_FIFO = 0x10
SET_DISABLE_FIFO = 0x00
FREEFALL_ACCEL = 9.80665 * 1000.
# SCALE = 1/2048 g/LSB @16g scale * Earth gravity in mm/s**2
SCALE = 0.00048828125 * FREEFALL_ACCEL
FIFO_SIZE = 512
BATCH_UPDATES = 0.100
# Printer class that controls ICM20948 chip
class ICM20948:
def __init__(self, config):
self.printer = config.get_printer()
adxl345.AccelCommandHelper(config, self)
self.axes_map = adxl345.read_axes_map(config, SCALE, SCALE, SCALE)
self.data_rate = config.getint('rate', 4500)
if self.data_rate not in SAMPLE_RATE_DIVS:
raise config.error("Invalid rate parameter: %d" % (self.data_rate,))
# Setup mcu sensor_icm20948 bulk query code
self.i2c = bus.MCU_I2C_from_config(config,
default_addr=ICM20948_ADDR,
default_speed=400000)
self.mcu = mcu = self.i2c.get_mcu()
self.oid = mcu.create_oid()
self.query_icm20948_cmd = None
mcu.register_config_callback(self._build_config)
# Bulk sample message reading
chip_smooth = self.data_rate * BATCH_UPDATES * 2
self.ffreader = bulk_sensor.FixedFreqReader(mcu, chip_smooth, ">hhh")
self.last_error_count = 0
# Process messages in batches
self.batch_bulk = bulk_sensor.BatchBulkHelper(
self.printer, self._process_batch,
self._start_measurements, self._finish_measurements, BATCH_UPDATES)
self.name = config.get_name().split()[-1]
hdr = ('time', 'x_acceleration', 'y_acceleration', 'z_acceleration')
self.batch_bulk.add_mux_endpoint("icm20948/dump_icm20948", "sensor",
self.name, {'header': hdr})
def _build_config(self):
cmdqueue = self.i2c.get_command_queue()
self.mcu.add_config_cmd("config_icm20948 oid=%d i2c_oid=%d"
% (self.oid, self.i2c.get_oid()))
self.mcu.add_config_cmd("query_icm20948 oid=%d rest_ticks=0"
% (self.oid,), on_restart=True)
self.query_icm20948_cmd = self.mcu.lookup_command(
"query_icm20948 oid=%c rest_ticks=%u", cq=cmdqueue)
self.ffreader.setup_query_command("query_icm20948_status oid=%c",
oid=self.oid, cq=cmdqueue)
def read_reg(self, reg):
params = self.i2c.i2c_read([reg], 1)
return bytearray(params['response'])[0]
def set_reg(self, reg, val, minclock=0):
self.i2c.i2c_write([reg, val & 0xFF], minclock=minclock)
def start_internal_client(self):
aqh = adxl345.AccelQueryHelper(self.printer)
self.batch_bulk.add_client(aqh.handle_batch)
return aqh
# Measurement decoding
def _convert_samples(self, samples):
(x_pos, x_scale), (y_pos, y_scale), (z_pos, z_scale) = self.axes_map
count = 0
for ptime, rx, ry, rz in samples:
raw_xyz = (rx, ry, rz)
x = round(raw_xyz[x_pos] * x_scale, 6)
y = round(raw_xyz[y_pos] * y_scale, 6)
z = round(raw_xyz[z_pos] * z_scale, 6)
samples[count] = (round(ptime, 6), x, y, z)
count += 1
# Start, stop, and process message batches
def _start_measurements(self):
# In case of miswiring, testing ICM20948 device ID prevents treating
# noise or wrong signal as a correctly initialized device
dev_id = self.read_reg(REG_DEVID)
if dev_id not in ICM_DEV_IDS.keys():
raise self.printer.command_error(
"Invalid mpu id (got %x).\n"
"This is generally indicative of connection problems\n"
"(e.g. faulty wiring) or a faulty chip."
% (dev_id))
else:
logging.info("Found %s with id %x"% (ICM_DEV_IDS[dev_id], dev_id))
# Setup chip in requested query rate
self.set_reg(REG_PWR_MGMT_1, SET_PWR_MGMT_1_WAKE)
self.set_reg(REG_PWR_MGMT_2, SET_PWR_MGMT_2_ACCEL_ON)
# Don't add 20ms pause for accelerometer chip wake up
self.read_reg(REG_DEVID) # Dummy read to ensure queues flushed
self.set_reg(REG_ACCEL_SMPLRT_DIV1, SAMPLE_RATE_DIVS[self.data_rate])
self.set_reg(REG_ACCEL_SMPLRT_DIV2, SAMPLE_RATE_DIVS[self.data_rate])
self.set_reg(REG_BANK_SEL, SET_BANK_2)
self.set_reg(REG_ACCEL_CONFIG, SET_ACCEL_CONFIG)
self.set_reg(REG_BANK_SEL, SET_BANK_0)
# Reset fifo
self.set_reg(REG_FIFO_EN, SET_DISABLE_FIFO)
self.set_reg(REG_USER_CTRL, SET_USER_FIFO_RESET)
self.set_reg(REG_USER_CTRL, SET_USER_FIFO_EN)
self.read_reg(REG_INT_STATUS) # clear FIFO overflow flag
# Start bulk reading
rest_ticks = self.mcu.seconds_to_clock(4. / self.data_rate)
self.query_icm20948_cmd.send([self.oid, rest_ticks])
self.set_reg(REG_FIFO_EN, SET_ENABLE_FIFO)
logging.info("ICM20948 starting '%s' measurements", self.name)
# Initialize clock tracking
self.ffreader.note_start()
self.last_error_count = 0
def _finish_measurements(self):
# Halt bulk reading
self.set_reg(REG_FIFO_EN, SET_DISABLE_FIFO)
self.query_icm20948_cmd.send_wait_ack([self.oid, 0])
self.ffreader.note_end()
logging.info("ICM20948 finished '%s' measurements", self.name)
self.set_reg(REG_PWR_MGMT_1, SET_PWR_MGMT_1_SLEEP)
self.set_reg(REG_PWR_MGMT_2, SET_PWR_MGMT_2_OFF)
def _process_batch(self, eventtime):
samples = self.ffreader.pull_samples()
self._convert_samples(samples)
if not samples:
return {}
return {'data': samples, 'errors': self.last_error_count,
'overflows': self.ffreader.get_last_overflows()}
def load_config(config):
return ICM20948(config)
def load_config_prefix(config):
return ICM20948(config)

View File

@@ -12,7 +12,7 @@ BATCH_UPDATES = 0.100
LDC1612_ADDR = 0x2a
LDC1612_FREQ = 12000000
DEFAULT_LDC1612_FREQ = 12000000
SETTLETIME = 0.005
DRIVECUR = 15
DEGLITCH = 0x05 # 10 Mhz
@@ -87,6 +87,8 @@ class LDC1612:
self.oid = oid = mcu.create_oid()
self.query_ldc1612_cmd = None
self.ldc1612_setup_home_cmd = self.query_ldc1612_home_state_cmd = None
self.frequency = config.getint("frequency", DEFAULT_LDC1612_FREQ,
2000000, 40000000)
if config.get('intb_pin', None) is not None:
ppins = config.get_printer().lookup_object("pins")
pin_params = ppins.lookup_pin(config.get('intb_pin'))
@@ -141,7 +143,7 @@ class LDC1612:
def setup_home(self, print_time, trigger_freq,
trsync_oid, hit_reason, err_reason):
clock = self.mcu.print_time_to_clock(print_time)
tfreq = int(trigger_freq * (1<<28) / float(LDC1612_FREQ) + 0.5)
tfreq = int(trigger_freq * (1<<28) / float(self.frequency) + 0.5)
self.ldc1612_setup_home_cmd.send(
[self.oid, clock, tfreq, trsync_oid, hit_reason, err_reason])
def clear_home(self):
@@ -153,7 +155,7 @@ class LDC1612:
return self.mcu.clock_to_print_time(tclock)
# Measurement decoding
def _convert_samples(self, samples):
freq_conv = float(LDC1612_FREQ) / (1<<28)
freq_conv = float(self.frequency) / (1<<28)
count = 0
for ptime, val in samples:
mv = val & 0x0fffffff
@@ -174,10 +176,10 @@ class LDC1612:
"(e.g. faulty wiring) or a faulty ldc1612 chip."
% (manuf_id, dev_id, LDC1612_MANUF_ID, LDC1612_DEV_ID))
# Setup chip in requested query rate
rcount0 = LDC1612_FREQ / (16. * (self.data_rate - 4))
rcount0 = self.frequency / (16. * (self.data_rate - 4))
self.set_reg(REG_RCOUNT0, int(rcount0 + 0.5))
self.set_reg(REG_OFFSET0, 0)
self.set_reg(REG_SETTLECOUNT0, int(SETTLETIME*LDC1612_FREQ/16. + .5))
self.set_reg(REG_SETTLECOUNT0, int(SETTLETIME*self.frequency/16. + .5))
self.set_reg(REG_CLOCK_DIVIDERS0, (1 << 12) | 1)
self.set_reg(REG_ERROR_CONFIG, (0x1f << 11) | 1)
self.set_reg(REG_MUX_CONFIG, 0x0208 | DEGLITCH)

View File

@@ -21,7 +21,7 @@ class LEDHelper:
self.led_state = [(red, green, blue, white)] * led_count
# Support setting an led template
self.template_eval = output_pin.lookup_template_eval(config)
self.tcallbacks = [(lambda text, s=self, index=i:
self.tcallbacks = [(lambda text, s=self, index=i+1:
s._template_update(index, text))
for i in range(led_count)]
# Register commands

View File

@@ -25,7 +25,6 @@ REG_LIS2DW_OUT_ZH_ADDR = 0x2D
REG_LIS2DW_FIFO_CTRL = 0x2E
REG_LIS2DW_FIFO_SAMPLES = 0x2F
REG_MOD_READ = 0x80
# REG_MOD_MULTI = 0x40
LIS2DW_DEV_ID = 0x44
LIS3DH_DEV_ID = 0x33
@@ -40,7 +39,6 @@ LIS3DH_SCALE = FREEFALL_ACCEL * 3.906 / 16
BATCH_UPDATES = 0.100
# "Enums" that should be compatible with all python versions
LIS2DW_TYPE = 'LIS2DW'
LIS3DH_TYPE = 'LIS3DH'
@@ -94,7 +92,6 @@ class LIS2DW:
hdr = ('time', 'x_acceleration', 'y_acceleration', 'z_acceleration')
self.batch_bulk.add_mux_endpoint("lis2dw/dump_lis2dw", "sensor",
self.name, {'header': hdr})
def _build_config(self):
cmdqueue = self.bus.get_command_queue()
self.query_lis2dw_cmd = self.mcu.lookup_command(

View File

@@ -3,21 +3,516 @@
# Copyright (C) 2024 Gareth Farrington <gareth@waves.ky>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
from . import hx71x
from . import ads1220
from .bulk_sensor import BatchWebhooksClient
import collections, itertools
# We want either Python 3's zip() or Python 2's izip() but NOT 2's zip():
zip_impl = zip
try:
from itertools import izip as zip_impl # python 2.x izip
except ImportError: # will be Python 3.x
pass
# Printer class that controls a load cell
# alternative to numpy's column selection:
def select_column(data, column_idx):
return list(zip_impl(*data))[column_idx]
def avg(data):
return sum(data) / len(data)
# Helper for event driven webhooks and subscription based API clients
class ApiClientHelper(object):
def __init__(self, printer):
self.printer = printer
self.client_cbs = []
self.webhooks_start_resp = {}
# send data to clients
def send(self, msg):
for client_cb in list(self.client_cbs):
res = client_cb(msg)
if not res:
# This client no longer needs updates - unregister it
self.client_cbs.remove(client_cb)
# Add a client that gets data callbacks
def add_client(self, client_cb):
self.client_cbs.append(client_cb)
# Add Webhooks client and send header
def _add_webhooks_client(self, web_request):
whbatch = BatchWebhooksClient(web_request)
self.add_client(whbatch.handle_batch)
web_request.send(self.webhooks_start_resp)
# Set up a webhooks endpoint with a static header
def add_mux_endpoint(self, path, key, value, webhooks_start_resp):
self.webhooks_start_resp = webhooks_start_resp
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 LoadCellCommandHelper:
def __init__(self, config, load_cell):
self.printer = config.get_printer()
self.load_cell = load_cell
name_parts = config.get_name().split()
self.name = name_parts[-1]
self.register_commands(self.name)
if len(name_parts) == 1:
self.register_commands(None)
def register_commands(self, name):
# Register commands
gcode = self.printer.lookup_object('gcode')
gcode.register_mux_command("LOAD_CELL_TARE", "LOAD_CELL", name,
self.cmd_LOAD_CELL_TARE,
desc=self.cmd_LOAD_CELL_TARE_help)
gcode.register_mux_command("LOAD_CELL_CALIBRATE", "LOAD_CELL", name,
self.cmd_LOAD_CELL_CALIBRATE,
desc=self.cmd_CALIBRATE_LOAD_CELL_help)
gcode.register_mux_command("LOAD_CELL_READ", "LOAD_CELL", name,
self.cmd_LOAD_CELL_READ,
desc=self.cmd_LOAD_CELL_READ_help)
gcode.register_mux_command("LOAD_CELL_DIAGNOSTIC", "LOAD_CELL", name,
self.cmd_LOAD_CELL_DIAGNOSTIC,
desc=self.cmd_LOAD_CELL_DIAGNOSTIC_help)
cmd_LOAD_CELL_TARE_help = "Set the Zero point of the load cell"
def cmd_LOAD_CELL_TARE(self, gcmd):
tare_counts = self.load_cell.avg_counts()
self.load_cell.tare(tare_counts)
tare_percent = self.load_cell.counts_to_percent(tare_counts)
gcmd.respond_info("Load cell tare value: %.2f%% (%i)"
% (tare_percent, tare_counts))
cmd_CALIBRATE_LOAD_CELL_help = "Start interactive calibration tool"
def cmd_LOAD_CELL_CALIBRATE(self, gcmd):
LoadCellGuidedCalibrationHelper(self.printer, self.load_cell)
cmd_LOAD_CELL_READ_help = "Take a reading from the load cell"
def cmd_LOAD_CELL_READ(self, gcmd):
counts = self.load_cell.avg_counts()
percent = self.load_cell.counts_to_percent(counts)
force = self.load_cell.counts_to_grams(counts)
if percent >= 100 or percent <= -100:
gcmd.respond_info("Err (%.2f%%)" % (percent,))
if force is None:
gcmd.respond_info("---.-g (%.2f%%)" % (percent,))
else:
gcmd.respond_info("%.1fg (%.2f%%)" % (force, percent))
cmd_LOAD_CELL_DIAGNOSTIC_help = "Check the health of the load cell"
def cmd_LOAD_CELL_DIAGNOSTIC(self, gcmd):
gcmd.respond_info("Collecting load cell data for 10 seconds...")
collector = self.load_cell.get_collector()
reactor = self.printer.get_reactor()
collector.start_collecting()
reactor.pause(reactor.monotonic() + 10.)
samples, errors = collector.stop_collecting()
if errors:
gcmd.respond_info("Sensor reported errors: %i errors,"
" %i overflows" % (errors[0], errors[1]))
else:
gcmd.respond_info("Sensor reported no errors")
if not samples:
raise gcmd.error("No samples returned from sensor!")
counts = select_column(samples, 2)
range_min, range_max = self.load_cell.saturation_range()
good_count = 0
saturation_count = 0
for sample in counts:
if sample >= range_max or sample <= range_min:
saturation_count += 1
else:
good_count += 1
gcmd.respond_info("Samples Collected: %i" % (len(samples)))
if len(samples) > 2:
sensor_sps = self.load_cell.sensor.get_samples_per_second()
sps = float(len(samples)) / (samples[-1][0] - samples[0][0])
gcmd.respond_info("Measured samples per second: %.1f, "
"configured: %.1f" % (sps, sensor_sps))
gcmd.respond_info("Good samples: %i, Saturated samples: %i, Unique"
" values: %i" % (good_count, saturation_count,
len(set(counts))))
max_pct = self.load_cell.counts_to_percent(max(counts))
min_pct = self.load_cell.counts_to_percent(min(counts))
gcmd.respond_info("Sample range: [%.2f%% to %.2f%%]"
% (min_pct, max_pct))
gcmd.respond_info("Sample range / sensor capacity: %.5f%%"
% ((max_pct - min_pct) / 2.))
# Class to guide the user through calibrating a load cell
class LoadCellGuidedCalibrationHelper:
def __init__(self, printer, load_cell):
self.printer = printer
self.gcode = printer.lookup_object('gcode')
self.load_cell = load_cell
self._tare_counts = self._counts_per_gram = None
self.tare_percent = 0.
self.register_commands()
self.gcode.respond_info(
"Starting load cell calibration. \n"
"1.) Remove all load and run TARE. \n"
"2.) Apply a known load, run CALIBRATE GRAMS=nnn. \n"
"Complete calibration with the ACCEPT command.\n"
"Use the ABORT command to quit.")
def verify_no_active_calibration(self,):
try:
self.gcode.register_command('TARE', 'dummy')
except self.printer.config_error as e:
raise self.gcode.error(
"Already Calibrating a Load Cell. Use ABORT to quit.")
self.gcode.register_command('TARE', None)
def register_commands(self):
self.verify_no_active_calibration()
register_command = self.gcode.register_command
register_command("ABORT", self.cmd_ABORT, desc=self.cmd_ABORT_help)
register_command("ACCEPT", self.cmd_ACCEPT, desc=self.cmd_ACCEPT_help)
register_command("TARE", self.cmd_TARE, desc=self.cmd_TARE_help)
register_command("CALIBRATE", self.cmd_CALIBRATE,
desc=self.cmd_CALIBRATE_help)
# convert the delta of counts to a counts/gram metric
def counts_per_gram(self, grams, cal_counts):
return float(abs(int(self._tare_counts - cal_counts))) / grams
# calculate max force that the load cell can register
# given tare bias, at saturation in kilograms
def capacity_kg(self, counts_per_gram):
range_min, range_max = self.load_cell.saturation_range()
return (int((range_max - abs(self._tare_counts)) / counts_per_gram)
/ 1000.)
def finalize(self, save_results=False):
for name in ['ABORT', 'ACCEPT', 'TARE', 'CALIBRATE']:
self.gcode.register_command(name, None)
if not save_results:
self.gcode.respond_info("Load cell calibration aborted")
return
if self._counts_per_gram is None or self._tare_counts is None:
self.gcode.respond_info("Calibration process is incomplete, "
"aborting")
self.load_cell.set_calibration(self._counts_per_gram, self._tare_counts)
self.gcode.respond_info("Load cell calibration settings:\n\n"
"counts_per_gram: %.6f\n"
"reference_tare_counts: %i\n\n"
"The SAVE_CONFIG command will update the printer config file"
" with the above and restart the printer."
% (self._counts_per_gram, self._tare_counts))
self.load_cell.tare(self._tare_counts)
cmd_ABORT_help = "Abort load cell calibration tool"
def cmd_ABORT(self, gcmd):
self.finalize(False)
cmd_ACCEPT_help = "Accept calibration results and apply to load cell"
def cmd_ACCEPT(self, gcmd):
self.finalize(True)
cmd_TARE_help = "Tare the load cell"
def cmd_TARE(self, gcmd):
self._tare_counts = self.load_cell.avg_counts()
self._counts_per_gram = None # require re-calibration on tare
self.tare_percent = self.load_cell.counts_to_percent(self._tare_counts)
gcmd.respond_info("Load cell tare value: %.2f%% (%i)"
% (self.tare_percent, self._tare_counts))
if self.tare_percent > 2.:
gcmd.respond_info(
"WARNING: tare value is more than 2% away from 0!\n"
"The load cell's range will be impacted.\n"
"Check for external force on the load cell.")
gcmd.respond_info("Now apply a known force to the load cell and enter \
the force value with:\n CALIBRATE GRAMS=nnn")
cmd_CALIBRATE_help = "Enter the load cell value in grams"
def cmd_CALIBRATE(self, gcmd):
if self._tare_counts is None:
gcmd.respond_info("You must use TARE first.")
return
grams = gcmd.get_float("GRAMS", minval=50., maxval=25000.)
cal_counts = self.load_cell.avg_counts()
cal_percent = self.load_cell.counts_to_percent(cal_counts)
c_per_g = self.counts_per_gram(grams, cal_counts)
cap_kg = self.capacity_kg(c_per_g)
gcmd.respond_info("Calibration value: %.2f%% (%i), Counts/gram: %.5f, \
Total capacity: +/- %0.2fKg"
% (cal_percent, cal_counts, c_per_g, cap_kg))
range_min, range_max = self.load_cell.saturation_range()
if cal_counts >= range_max or cal_counts <= range_min:
raise self.printer.command_error(
"ERROR: Sensor is saturated with too much load!\n"
"Use less force to calibrate the load cell.")
if cal_counts == self._tare_counts:
raise self.printer.command_error(
"ERROR: Tare and Calibration readings are the same!\n"
"Check wiring and validate sensor with READ_LOAD_CELL command.")
if (abs(cal_percent - self.tare_percent)) < 1.:
raise self.printer.command_error(
"ERROR: Tare and Calibration readings are less than 1% "
"different!\n"
"Use more force when calibrating or a higher sensor gain.")
# only set _counts_per_gram after all errors are raised
self._counts_per_gram = c_per_g
if cap_kg < 1.:
gcmd.respond_info("WARNING: Load cell capacity is less than 1kg!\n"
"Check wiring and consider using a lower sensor gain.")
if cap_kg > 25.:
gcmd.respond_info("WARNING: Load cell capacity is more than 25Kg!\n"
"Check wiring and consider using a higher sensor gain.")
gcmd.respond_info("Accept calibration with the ACCEPT command.")
# Utility to collect some samples from the LoadCell for later analysis
# Optionally blocks execution while collecting with reactor.pause()
# can collect a minimum n samples or collect until a specific print_time
# samples returned in [[time],[force],[counts]] arrays for easy processing
RETRY_DELAY = 0.05 # 20Hz
class LoadCellSampleCollector:
def __init__(self, printer, load_cell):
self._printer = printer
self._load_cell = load_cell
self._reactor = printer.get_reactor()
self._mcu = load_cell.sensor.get_mcu()
self.min_time = 0.
self.max_time = float("inf")
self.min_count = float("inf") # In Python 3.5 math.inf is better
self.is_started = False
self._samples = []
self._errors = 0
self._overflows = 0
def _on_samples(self, msg):
if not self.is_started:
return False # already stopped, ignore
self._errors += msg['errors']
self._overflows += msg['overflows']
samples = msg['data']
for sample in samples:
time = sample[0]
if self.min_time <= time <= self.max_time:
self._samples.append(sample)
if time > self.max_time:
self.is_started = False
if len(self._samples) >= self.min_count:
self.is_started = False
return self.is_started
def _finish_collecting(self):
self.is_started = False
self.min_time = 0.
self.max_time = float("inf")
self.min_count = float("inf") # In Python 3.5 math.inf is better
samples = self._samples
self._samples = []
errors = self._errors
self._errors = 0
overflows = self._overflows
self._overflows = 0
return samples, (errors, overflows) if errors or overflows else 0
def _collect_until(self, timeout):
self.start_collecting()
while self.is_started:
now = self._reactor.monotonic()
if self._mcu.estimated_print_time(now) > timeout:
self._finish_collecting()
raise self._printer.command_error(
"LoadCellSampleCollector timed out! Errors: %i,"
" Overflows: %i" % (self._errors, self._overflows))
self._reactor.pause(now + RETRY_DELAY)
return self._finish_collecting()
# start collecting with no automatic end to collection
def start_collecting(self, min_time=None):
if self.is_started:
return
self.min_time = min_time if min_time is not None else self.min_time
self.is_started = True
self._load_cell.add_client(self._on_samples)
# stop collecting immediately and return results
def stop_collecting(self):
return self._finish_collecting()
# block execution until at least min_count samples are collected
# will return all samples collected, not just up to min_count
def collect_min(self, min_count=1):
self.min_count = min_count
if len(self._samples) >= min_count:
return self._finish_collecting()
print_time = self._mcu.estimated_print_time(self._reactor.monotonic())
start_time = max(print_time, self.min_time)
sps = self._load_cell.sensor.get_samples_per_second()
return self._collect_until(start_time + 1. + (min_count / sps))
# returns when a sample is collected with a timestamp after print_time
def collect_until(self, print_time=None):
self.max_time = print_time
if len(self._samples) and self._samples[-1][0] >= print_time:
return self._finish_collecting()
return self._collect_until(self.max_time + 1.)
# Printer class that controls the load cell
MIN_COUNTS_PER_GRAM = 1.
class LoadCell:
def __init__(self, config, sensor):
self.printer = printer = config.get_printer()
self.sensor = sensor # must implement BulkAdcSensor
self.config_name = config.get_name()
self.name = config.get_name().split()[-1]
self.sensor = sensor # must implement BulkSensorAdc
buffer_size = sensor.get_samples_per_second() // 2
self._force_buffer = collections.deque(maxlen=buffer_size)
self.reference_tare_counts = config.getint('reference_tare_counts',
default=None)
self.tare_counts = self.reference_tare_counts
self.counts_per_gram = config.getfloat('counts_per_gram',
minval=MIN_COUNTS_PER_GRAM, default=None)
self.invert = config.getchoice('sensor_orientation',
{'normal': 1., 'inverted': -1.}, default="normal")
LoadCellCommandHelper(config, self)
# Client support:
self.clients = ApiClientHelper(printer)
header = {"header": ["time", "force (g)", "counts", "tare_counts"]}
self.clients.add_mux_endpoint("load_cell/dump_force",
"load_cell", self.name, header)
# startup, when klippy is ready, start capturing data
printer.register_event_handler("klippy:ready", self._handle_ready)
def _on_sample(self, msg):
def _handle_ready(self):
self.sensor.add_client(self._sensor_data_event)
self.add_client(self._track_force)
# announce calibration status on ready
if self.is_calibrated():
self.printer.send_event("load_cell:calibrate", self)
if self.is_tared():
self.printer.send_event("load_cell:tare", self)
# convert raw counts to grams and broadcast to clients
def _sensor_data_event(self, msg):
data = msg.get("data")
errors = msg.get("errors")
overflows = msg.get("overflows")
if data is None:
return None
samples = []
for row in data:
# [time, grams, counts, tare_counts]
samples.append([row[0], self.counts_to_grams(row[1]), row[1],
self.tare_counts])
msg = {'data': samples, 'errors': errors, 'overflows': overflows}
self.clients.send(msg)
return True
# get internal events of force data
def add_client(self, callback):
self.clients.add_client(callback)
def tare(self, tare_counts):
self.tare_counts = int(tare_counts)
self.printer.send_event("load_cell:tare", self)
def set_calibration(self, counts_per_gram, tare_counts):
if (counts_per_gram is None
or abs(counts_per_gram) < MIN_COUNTS_PER_GRAM):
raise self.printer.command_error("Invalid counts per gram value")
if tare_counts is None:
raise self.printer.command_error("Missing tare counts")
self.counts_per_gram = counts_per_gram
self.reference_tare_counts = int(tare_counts)
configfile = self.printer.lookup_object('configfile')
configfile.set(self.config_name, 'counts_per_gram',
"%.5f" % (self.counts_per_gram,))
configfile.set(self.config_name, 'reference_tare_counts',
"%i" % (self.reference_tare_counts,))
self.printer.send_event("load_cell:calibrate", self)
def counts_to_grams(self, sample):
if not self.is_calibrated() or not self.is_tared():
return None
sample_delta = float(sample - self.tare_counts)
return self.invert * (sample_delta / self.counts_per_gram)
# The maximum range of the sensor based on its bit width
def saturation_range(self):
return self.sensor.get_range()
# convert raw counts to a +/- percentage of the sensors range
def counts_to_percent(self, counts):
range_min, range_max = self.saturation_range()
return (float(counts) / float(range_max)) * 100.
# read 1 second of load cell data and average it
# performs safety checks for saturation
def avg_counts(self, num_samples=None):
if num_samples is None:
num_samples = self.sensor.get_samples_per_second()
samples, errors = self.get_collector().collect_min(num_samples)
if errors:
raise self.printer.command_error(
"Sensor reported %i errors while sampling"
% (errors[0] + errors[1]))
# check samples for saturated readings
range_min, range_max = self.saturation_range()
for sample in samples:
if sample[2] >= range_max or sample[2] <= range_min:
raise self.printer.command_error(
"Some samples are saturated (+/-100%)")
return avg(select_column(samples, 2))
# Provide ongoing force tracking/averaging for status updates
def _track_force(self, msg):
if not (self.is_calibrated() and self.is_tared()):
return True
samples = msg['data']
# selectColumn unusable here because Python 2 lacks deque.extend
for sample in samples:
self._force_buffer.append(sample[1])
return True
def _force_g(self):
if (self.is_calibrated() and self.is_tared()
and len(self._force_buffer) > 0):
return {"force_g": round(avg(self._force_buffer), 1),
"min_force_g": round(min(self._force_buffer), 1),
"max_force_g": round(max(self._force_buffer), 1)}
return {}
def is_tared(self):
return self.tare_counts is not None
def is_calibrated(self):
return (self.counts_per_gram is not None
and self.reference_tare_counts is not None)
def get_sensor(self):
return self.sensor
def get_reference_tare_counts(self):
return self.reference_tare_counts
def get_tare_counts(self):
return self.tare_counts
def get_counts_per_gram(self):
return self.counts_per_gram
def get_collector(self):
return LoadCellSampleCollector(self.printer, self)
def get_status(self, eventtime):
status = self._force_g()
status.update({'is_calibrated': self.is_calibrated(),
'counts_per_gram': self.counts_per_gram,
'reference_tare_counts': self.reference_tare_counts,
'tare_counts': self.tare_counts})
return status
def load_config(config):
# Sensor types
sensors = {}

View File

@@ -102,7 +102,13 @@ class PrinterTemplateEvaluator:
self.render_timer = reactor.register_timer(self._render, reactor.NOW)
def _activate_template(self, callback, template, lparams, flush_callback):
if template is not None:
# Build a unique id to make it possible to cache duplicate rendering
uid = (template,) + tuple(sorted(lparams.items()))
try:
{}.get(uid)
except TypeError as e:
# lparams is not static, so disable caching
uid = None
self.active_templates[callback] = (
uid, template, lparams, flush_callback)
return
@@ -122,17 +128,18 @@ class PrinterTemplateEvaluator:
context['render'] = render
# Render all templates
flush_callbacks = {}
rendered = {}
render_cache = {}
template_info = self.active_templates.items()
for callback, (uid, template, lparams, flush_callback) in template_info:
text = rendered.get(uid)
text = render_cache.get(uid)
if text is None:
try:
text = template.render(context, **lparams)
except Exception as e:
logging.exception("display template render error")
text = ""
rendered[uid] = text
if uid is not None:
render_cache[uid] = text
if flush_callback is not None:
flush_callbacks[flush_callback] = 1
callback(text)
@@ -228,6 +235,7 @@ class PrinterOutputPin:
value = float(text)
except ValueError as e:
logging.exception("output_pin template render error")
value = 0.
self.gcrq.send_async_request(value)
cmd_SET_PIN_help = "Set the value of an output pin"
def cmd_SET_PIN(self, gcmd):

View File

@@ -36,6 +36,8 @@ class SaveVariables:
cmd_SAVE_VARIABLE_help = "Save arbitrary variables to disk"
def cmd_SAVE_VARIABLE(self, gcmd):
varname = gcmd.get('VARIABLE')
if (varname.lower() != varname):
raise gcmd.error("VARIABLE must not contain upper case")
value = gcmd.get('VALUE')
try:
value = ast.literal_eval(value)

View File

@@ -20,6 +20,7 @@ class PrinterSkew:
def __init__(self, config):
self.printer = config.get_printer()
self.name = config.get_name()
self.current_profile_name = ""
self.toolhead = None
self.xy_factor = 0.
self.xz_factor = 0.
@@ -117,6 +118,7 @@ class PrinterSkew:
def cmd_SKEW_PROFILE(self, gcmd):
if gcmd.get('LOAD', None) is not None:
name = gcmd.get('LOAD')
self.current_profile_name = name
prof = self.skew_profiles.get(name)
if prof is None:
gcmd.respond_info(
@@ -156,7 +158,10 @@ class PrinterSkew:
gcmd.respond_info(
"skew_correction: No profile named [%s] to remove"
% (name))
def get_status(self, eventtime):
return {
'current_profile_name': self.current_profile_name
}
def load_config(config):
return PrinterSkew(config)

View File

@@ -41,19 +41,29 @@ class PrinterSensorCombined:
sensor = self.printer.lookup_object(sensor_name)
# check if sensor has get_status function and
# get_status has a 'temperature' value
if (hasattr(sensor, 'get_status') and
'temperature' in sensor.get_status(
self.reactor.monotonic())):
self.sensors.append(sensor)
else:
if not hasattr(sensor, 'get_status'):
raise self.printer.config_error(
"'%s' does not have a status."
% (sensor_name,))
status = sensor.get_status(self.reactor.monotonic())
if 'temperature' not in status:
raise self.printer.config_error(
"'%s' does not report a temperature."
% (sensor_name,))
# Handle temperature monitors
if status["temperature"] is None:
raise self.printer.config_error(
"Temperature monitor '%s' is not supported"
% (sensor_name,))
self.sensors.append(sensor)
def _handle_ready(self):
# Start temperature update timer
# There is a race condition with sensors where they can be not ready,
# and return 0 or None - initialize a little bit later.
self.reactor.update_timer(self.temperature_update_timer,
self.reactor.NOW)
self.reactor.monotonic() + 1.)
def setup_minmax(self, min_temp, max_temp):
self.min_temp = min_temp

View File

@@ -565,6 +565,7 @@ class MCU:
self._canbus_iface = config.get('canbus_interface', 'can0')
cbid = self._printer.load_object(config, 'canbus_ids')
cbid.add_uuid(config, canbus_uuid, self._canbus_iface)
self._printer.load_object(config, 'canbus_stats %s' % (self._name,))
else:
self._serialport = config.get('serial')
if not (self._serialport.startswith("/dev/rpmsg_")

View File

@@ -1,6 +1,6 @@
# Printer stepper support
#
# 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 math, logging, collections
@@ -14,7 +14,8 @@ class error(Exception):
# Steppers
######################################################################
MIN_BOTH_EDGE_DURATION = 0.000000200
MIN_BOTH_EDGE_DURATION = 0.000000500
MIN_OPTIMIZED_BOTH_EDGE_DURATION = 0.000000150
# Interface to low-level mcu and chelper code
class MCU_stepper:
@@ -75,13 +76,33 @@ class MCU_stepper:
if self._step_pulse_duration is None:
self._step_pulse_duration = .000002
invert_step = self._invert_step
sbe = int(self._mcu.get_constants().get('STEPPER_BOTH_EDGE', '0'))
if (self._req_step_both_edge and sbe
and self._step_pulse_duration <= MIN_BOTH_EDGE_DURATION):
# Enable stepper optimized step on both edges
# Check if can enable "step on both edges"
constants = self._mcu.get_constants()
ssbe = int(constants.get('STEPPER_STEP_BOTH_EDGE', '0'))
sbe = int(constants.get('STEPPER_BOTH_EDGE', '0'))
sou = int(constants.get('STEPPER_OPTIMIZED_UNSTEP', '0'))
want_both_edges = self._req_step_both_edge
if self._step_pulse_duration > MIN_BOTH_EDGE_DURATION:
# If user has requested a very large step pulse duration
# then disable step on both edges (rise and fall times may
# not be symetric)
want_both_edges = False
elif sbe and self._step_pulse_duration>MIN_OPTIMIZED_BOTH_EDGE_DURATION:
# Older MCU and user has requested large pulse duration
want_both_edges = False
elif not sbe and not ssbe:
# Older MCU that doesn't support step on both edges
want_both_edges = False
elif sou:
# MCU has optimized step/unstep - better to use that
want_both_edges = False
if want_both_edges:
self._step_both_edge = True
self._step_pulse_duration = 0.
invert_step = -1
if sbe:
# Older MCU requires setting step_pulse_ticks=0 to enable
self._step_pulse_duration = 0.
# Configure stepper object
step_pulse_ticks = self._mcu.seconds_to_clock(self._step_pulse_duration)
self._mcu.add_config_cmd(
"config_stepper oid=%d step_pin=%s dir_pin=%s invert_step=%d"

View File

@@ -290,7 +290,7 @@ class ToolHead:
self._handle_shutdown)
# Load some default modules
modules = ["gcode_move", "homing", "idle_timeout", "statistics",
"manual_probe", "tuning_tower"]
"manual_probe", "tuning_tower", "garbage_collection"]
for module_name in modules:
self.printer.load_object(config, module_name)
# Print time and flush tracking

View File

@@ -7,7 +7,7 @@ set -eu
# Paths to tools installed by ci-install.sh
MAIN_DIR=${PWD}
BUILD_DIR=${PWD}/ci_build
export PATH=${BUILD_DIR}/pru-gcc/bin:${PATH}
export PATH=${BUILD_DIR}/pru-elf/bin:${PATH}
export PATH=${BUILD_DIR}/or1k-linux-musl-cross/bin:${PATH}
PYTHON=${BUILD_DIR}/python-env/bin/python
PYTHON2=${BUILD_DIR}/python2-env/bin/python

View File

@@ -19,34 +19,24 @@ echo -e "\n\n=============== Install system dependencies\n\n"
PKGS="virtualenv python2-dev libffi-dev build-essential"
PKGS="${PKGS} gcc-avr avr-libc"
PKGS="${PKGS} libnewlib-arm-none-eabi gcc-arm-none-eabi binutils-arm-none-eabi"
PKGS="${PKGS} pv libmpfr-dev libgmp-dev libmpc-dev texinfo bison flex"
sudo apt-get update
sudo apt-get install ${PKGS}
######################################################################
# Install (or build) pru gcc
# Install pru gcc
######################################################################
echo -e "\n\n=============== Install embedded pru gcc\n\n"
PRU_FILE=${CACHE_DIR}/gnupru.tar.gz
PRU_DIR=${BUILD_DIR}/pru-gcc
PRU_ARCHIVE="pru-elf-2024.05.amd64.tar.xz"
PRU_URL="https://github.com/dinuxbg/gnupru/releases/download/2024.05/${PRU_ARCHIVE}"
if [ ! -f ${PRU_FILE} ]; then
cd ${BUILD_DIR}
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
git clone https://github.com/dinuxbg/gnupru -b 2024.05 --depth 1
cd gnupru
export PREFIX=${PRU_DIR}
./download-and-prepare.sh 2>&1 | pv -nli 30 > ${BUILD_DIR}/gnupru-build.log
./build.sh 2>&1 | pv -nli 30 >> ${BUILD_DIR}/gnupru-build.log
cd ${BUILD_DIR}
tar cfz ${PRU_FILE} pru-gcc/
else
cd ${BUILD_DIR}
tar xfz ${PRU_FILE}
if [ ! -f ${CACHE_DIR}/${PRU_ARCHIVE} ]; then
wget "${PRU_URL}" -O "${CACHE_DIR}/${PRU_ARCHIVE}"
fi
cd ${BUILD_DIR}
tar xJf ${CACHE_DIR}/${PRU_ARCHIVE}
######################################################################
# Install or1k-linux-musl toolchain
@@ -59,10 +49,12 @@ GCC_VERSION=10
TOOLCHAIN_ZIP_V=${TOOLCHAIN}-${GCC_VERSION}.tgz
URL=https://more.musl.cc/${GCC_VERSION}/x86_64-linux-musl/
if [ ! -f ${CACHE_DIR}/${TOOLCHAIN_ZIP_V} ]; then
curl ${URL}/${TOOLCHAIN_ZIP} -o ${CACHE_DIR}/${TOOLCHAIN_ZIP_V}
wget "${URL}/${TOOLCHAIN_ZIP}" -O "${CACHE_DIR}/${TOOLCHAIN_ZIP_V}"
fi
cd ${BUILD_DIR}
tar xf ${CACHE_DIR}/${TOOLCHAIN_ZIP_V}
######################################################################
# Create python3 virtualenv environment
######################################################################

View File

@@ -9,3 +9,4 @@ greenlet==3.0.3 ; python_version >= '3.12'
Jinja2==2.11.3
python-can==3.3.4
markupsafe==1.1.1
setuptools==75.6.0 ; python_version >= '3.12' # Needed by python-can

View File

@@ -130,6 +130,13 @@ BOARD_DEFS = {
"cs_pin": "PA4",
"current_firmware_path": "OLD.BIN"
},
'btt-octopus-max-ez': {
'mcu': "stm32h723xx",
'spi_bus': "swspi",
'spi_pins': "PE13,PE14,PE12",
'cs_pin': "PB12",
'skip_verify': True
},
'btt-skrat': {
'mcu': "stm32g0b1xx",
'spi_bus': "spi1",

View File

@@ -112,6 +112,10 @@ config WANT_MPU9250
bool
depends on HAVE_GPIO_I2C
default y
config WANT_ICM20948
bool
depends on HAVE_GPIO_I2C
default y
config WANT_HX71X
bool
depends on WANT_GPIO_BITBANGING
@@ -138,7 +142,7 @@ config WANT_SOFTWARE_SPI
default y
config NEED_SENSOR_BULK
bool
depends on WANT_ADXL345 || WANT_LIS2DW || WANT_MPU9250 \
depends on WANT_ADXL345 || WANT_LIS2DW || WANT_MPU9250 || WANT_ICM20948 \
|| WANT_HX71X || WANT_ADS1220 || WANT_LDC1612 || WANT_SENSOR_ANGLE
default y
menu "Optional features (to reduce code size)"
@@ -161,6 +165,9 @@ config WANT_LIS2DW
config WANT_MPU9250
bool "Support MPU accelerometers"
depends on HAVE_GPIO_I2C
config WANT_ICM20948
bool "Support ICM20948 accelerometer"
depends on HAVE_GPIO_I2C
config WANT_HX71X
bool "Support HX711 and HX717 ADC chips"
depends on WANT_GPIO_BITBANGING
@@ -194,6 +201,29 @@ config CANBUS_FILTER
bool
default y if CANSERIAL
# Stepper optimizations
config INLINE_STEPPER_HACK
# Enables gcc to inline stepper_event() into the main timer irq handler
bool
depends on HAVE_GPIO
default y
config HAVE_STEPPER_OPTIMIZED_BOTH_EDGE
bool
config WANT_STEPPER_OPTIMIZED_BOTH_EDGE
bool "Optimize stepper code for 'step on both edges'" if LOW_LEVEL_OPTIONS
depends on INLINE_STEPPER_HACK && HAVE_STEPPER_OPTIMIZED_BOTH_EDGE
default y
help
Optimize the stepper code for Trinamic stepper motor drivers
that are configured in UART or SPI mode (and thus can perform
a step on both "edges" of the step pin). Enabling this option
typically improves the stepper cpu performance by about 20%
when using these drivers. If this option is disabled the code
will instead deploy optimizations that improve the cpu
performance by about 20% for traditional drivers (those that
take a step only on the "rising" or "falling" level of the
step pin).
# Support setting gpio state at startup
config INITIAL_PINS
string "GPIO pins to set at micro-controller startup"
@@ -222,15 +252,7 @@ config HAVE_STRICT_TIMING
bool
config HAVE_CHIPID
bool
config HAVE_STEPPER_BOTH_EDGE
bool
config HAVE_BOOTLOADER_REQUEST
bool
config HAVE_LIMITED_CODE_SIZE
bool
config INLINE_STEPPER_HACK
# Enables gcc to inline stepper_event() into the main timer irq handler
bool
depends on HAVE_GPIO
default y

View File

@@ -18,6 +18,7 @@ src-$(CONFIG_WANT_THERMOCOUPLE) += thermocouple.c
src-$(CONFIG_WANT_ADXL345) += sensor_adxl345.c
src-$(CONFIG_WANT_LIS2DW) += sensor_lis2dw.c
src-$(CONFIG_WANT_MPU9250) += sensor_mpu9250.c
src-$(CONFIG_WANT_ICM20948) += sensor_icm20948.c
src-$(CONFIG_WANT_HX71X) += sensor_hx71x.c
src-$(CONFIG_WANT_ADS1220) += sensor_ads1220.c
src-$(CONFIG_WANT_LDC1612) += sensor_ldc1612.c

View File

@@ -7,7 +7,7 @@ config AR100_SELECT
default y
select HAVE_GPIO
select HAVE_GPIO_SPI
select HAVE_STEPPER_BOTH_EDGE
select HAVE_STEPPER_OPTIMIZED_BOTH_EDGE
select HAVE_LIMITED_CODE_SIZE
config BOARD_DIRECTORY

View File

@@ -12,7 +12,7 @@ config ATSAM_SELECT
select HAVE_GPIO_HARD_PWM if !MACH_SAME70
select HAVE_STRICT_TIMING
select HAVE_CHIPID
select HAVE_STEPPER_BOTH_EDGE
select HAVE_STEPPER_OPTIMIZED_BOTH_EDGE
select HAVE_BOOTLOADER_REQUEST
config BOARD_DIRECTORY

View File

@@ -1,11 +1,12 @@
// CANbus support on atsame70 chips
//
// Copyright (C) 2021-2022 Kevin O'Connor <kevin@koconnor.net>
// Copyright (C) 2021-2025 Kevin O'Connor <kevin@koconnor.net>
// Copyright (C) 2019 Eug Krashtan <eug.krashtan@gmail.com>
// Copyright (C) 2020 Pontus Borg <glpontus@gmail.com>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include "board/irq.h" // irq_save
#include "command.h" // DECL_CONSTANT_STR
#include "generic/armcm_boot.h" // armcm_enable_irq
#include "generic/canbus.h" // canbus_notify_tx
@@ -147,6 +148,38 @@ canhw_set_filter(uint32_t id)
CANx->MCAN_CCCR &= ~MCAN_CCCR_INIT;
}
static struct {
uint32_t rx_error, tx_error;
} CAN_Errors;
// Report interface status
void
canhw_get_status(struct canbus_status *status)
{
irqstatus_t flag = irq_save();
uint32_t psr = CANx->MCAN_PSR, lec = psr & MCAN_PSR_LEC_Msk;
if (lec && lec != 7) {
// Reading PSR clears it - so update state here
if (lec >= 3 && lec <= 5)
CAN_Errors.tx_error += 1;
else
CAN_Errors.rx_error += 1;
}
uint32_t rx_error = CAN_Errors.rx_error, tx_error = CAN_Errors.tx_error;
irq_restore(flag);
status->rx_error = rx_error;
status->tx_error = tx_error;
if (psr & MCAN_PSR_BO)
status->bus_state = CANBUS_STATE_OFF;
else if (psr & MCAN_PSR_EP)
status->bus_state = CANBUS_STATE_PASSIVE;
else if (psr & MCAN_PSR_EW)
status->bus_state = CANBUS_STATE_WARN;
else
status->bus_state = 0;
}
// This function handles CAN global interrupts
void
CAN_IRQHandler(void)
@@ -183,6 +216,18 @@ CAN_IRQHandler(void)
CANx->MCAN_IR = FDCAN_IE_TC;
canbus_notify_tx();
}
if (ir & (MCAN_IR_PED | MCAN_IR_PEA)) {
// Bus error
uint32_t psr = CANx->MCAN_PSR;
CANx->MCAN_IR = MCAN_IR_PED | MCAN_IR_PEA;
uint32_t lec = psr & MCAN_PSR_LEC_Msk;
if (lec && lec != 7) {
if (lec >= 3 && lec <= 5)
CAN_Errors.tx_error += 1;
else
CAN_Errors.rx_error += 1;
}
}
}
static inline const uint32_t
@@ -302,6 +347,6 @@ can_init(void)
/*##-3- Configure Interrupts #################################*/
armcm_enable_irq(CAN_IRQHandler, CANx_IRQn, 1);
CANx->MCAN_ILE = MCAN_ILE_EINT0;
CANx->MCAN_IE = MCAN_IE_RF0NE | FDCAN_IE_TC;
CANx->MCAN_IE = MCAN_IE_RF0NE | FDCAN_IE_TC | MCAN_IE_PEDE | MCAN_IE_PEAE;
}
DECL_INIT(can_init);

View File

@@ -12,7 +12,7 @@ config ATSAMD_SELECT
select HAVE_GPIO_HARD_PWM if MACH_SAMX2
select HAVE_STRICT_TIMING
select HAVE_CHIPID
select HAVE_STEPPER_BOTH_EDGE
select HAVE_STEPPER_OPTIMIZED_BOTH_EDGE
select HAVE_BOOTLOADER_REQUEST
config HAVE_SERCOM

View File

@@ -1,11 +1,12 @@
// CANbus support on atsame51 chips
//
// Copyright (C) 2021-2022 Kevin O'Connor <kevin@koconnor.net>
// Copyright (C) 2021-2025 Kevin O'Connor <kevin@koconnor.net>
// Copyright (C) 2019 Eug Krashtan <eug.krashtan@gmail.com>
// Copyright (C) 2020 Pontus Borg <glpontus@gmail.com>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include "board/irq.h" // irq_save
#include "command.h" // DECL_CONSTANT_STR
#include "generic/armcm_boot.h" // armcm_enable_irq
#include "generic/canbus.h" // canbus_notify_tx
@@ -163,6 +164,38 @@ canhw_set_filter(uint32_t id)
CANx->CCCR.reg &= ~CAN_CCCR_INIT;
}
static struct {
uint32_t rx_error, tx_error;
} CAN_Errors;
// Report interface status
void
canhw_get_status(struct canbus_status *status)
{
irqstatus_t flag = irq_save();
uint32_t psr = CANx->PSR.reg, lec = psr & CAN_PSR_LEC_Msk;
if (lec && lec != 7) {
// Reading PSR clears it - so update state here
if (lec >= 3 && lec <= 5)
CAN_Errors.tx_error += 1;
else
CAN_Errors.rx_error += 1;
}
uint32_t rx_error = CAN_Errors.rx_error, tx_error = CAN_Errors.tx_error;
irq_restore(flag);
status->rx_error = rx_error;
status->tx_error = tx_error;
if (psr & CAN_PSR_BO)
status->bus_state = CANBUS_STATE_OFF;
else if (psr & CAN_PSR_EP)
status->bus_state = CANBUS_STATE_PASSIVE;
else if (psr & CAN_PSR_EW)
status->bus_state = CANBUS_STATE_WARN;
else
status->bus_state = 0;
}
// This function handles CAN global interrupts
void
CAN_IRQHandler(void)
@@ -199,6 +232,18 @@ CAN_IRQHandler(void)
CANx->IR.reg = FDCAN_IE_TC;
canbus_notify_tx();
}
if (ir & (CAN_IR_PED | CAN_IR_PEA)) {
// Bus error
uint32_t psr = CANx->PSR.reg;
CANx->IR.reg = CAN_IR_PED | CAN_IR_PEA;
uint32_t lec = psr & CAN_PSR_LEC_Msk;
if (lec && lec != 7) {
if (lec >= 3 && lec <= 5)
CAN_Errors.tx_error += 1;
else
CAN_Errors.rx_error += 1;
}
}
}
static inline const uint32_t
@@ -309,6 +354,6 @@ can_init(void)
/*##-3- Configure Interrupts #################################*/
armcm_enable_irq(CAN_IRQHandler, CANx_IRQn, 1);
CANx->ILE.reg = CAN_ILE_EINT0;
CANx->IE.reg = CAN_IE_RF0NE | FDCAN_IE_TC;
CANx->IE.reg = CAN_IE_RF0NE | FDCAN_IE_TC | CAN_IE_PEDE | CAN_IE_PEAE;
}
DECL_INIT(can_init);

View File

@@ -17,9 +17,20 @@ struct canbus_msg {
#define CANMSG_DATA_LEN(msg) ((msg)->dlc > 8 ? 8 : (msg)->dlc)
struct canbus_status {
uint32_t rx_error, tx_error, tx_retries;
uint32_t bus_state;
};
enum {
CANBUS_STATE_ACTIVE, CANBUS_STATE_WARN, CANBUS_STATE_PASSIVE,
CANBUS_STATE_OFF,
};
// callbacks provided by board specific code
int canhw_send(struct canbus_msg *msg);
void canhw_set_filter(uint32_t id);
void canhw_get_status(struct canbus_status *status);
// canbus.c
int canbus_send(struct canbus_msg *msg);

View File

@@ -2,7 +2,7 @@
//
// Copyright (C) 2019 Eug Krashtan <eug.krashtan@gmail.com>
// Copyright (C) 2020 Pontus Borg <glpontus@gmail.com>
// Copyright (C) 2021 Kevin O'Connor <kevin@koconnor.net>
// Copyright (C) 2021-2025 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
@@ -318,6 +318,25 @@ DECL_TASK(canserial_rx_task);
* Setup and shutdown
****************************************************************/
DECL_ENUMERATION("canbus_bus_state", "active", CANBUS_STATE_ACTIVE);
DECL_ENUMERATION("canbus_bus_state", "warn", CANBUS_STATE_WARN);
DECL_ENUMERATION("canbus_bus_state", "passive", CANBUS_STATE_PASSIVE);
DECL_ENUMERATION("canbus_bus_state", "off", CANBUS_STATE_OFF);
void
command_get_canbus_status(uint32_t *args)
{
struct canbus_status status;
memset(&status, 0, sizeof(status));
canhw_get_status(&status);
sendf("canbus_status rx_error=%u tx_error=%u tx_retries=%u"
" canbus_bus_state=%u"
, status.rx_error, status.tx_error, status.tx_retries
, status.bus_state);
}
DECL_COMMAND_FLAGS(command_get_canbus_status, HF_IN_SHUTDOWN
, "get_canbus_status");
void
command_get_canbus_id(uint32_t *args)
{

View File

@@ -1,6 +1,6 @@
// Support for Linux "gs_usb" CANbus adapter emulation
//
// Copyright (C) 2018-2022 Kevin O'Connor <kevin@koconnor.net>
// Copyright (C) 2018-2025 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
@@ -18,6 +18,8 @@
#include "sched.h" // sched_wake_task
#include "usb_cdc.h" // usb_notify_ep0
DECL_CONSTANT("CANBUS_BRIDGE", 1);
/****************************************************************
* Linux "gs_usb" definitions
@@ -97,7 +99,7 @@ struct gs_host_frame {
/****************************************************************
* Message sending
* Main usbcan task (read requests from usb and send msgs to usb)
****************************************************************/
// Global storage
@@ -108,6 +110,10 @@ static struct usbcan_data {
uint8_t notify_local, usb_send_busy;
uint32_t assigned_id;
// State tracking for messages to be sent from host to canbus
uint32_t bus_send_discard_time;
uint8_t bus_send_state;
// Canbus data from host
uint8_t host_status;
uint32_t host_pull_pos, host_push_pos;
@@ -118,38 +124,16 @@ static struct usbcan_data {
struct canbus_msg canhw_queue[32];
} UsbCan;
enum {
BSS_READY = 0, BSS_BLOCKING, BSS_DISCARDING
};
enum {
HS_TX_ECHO = 1,
HS_TX_HW = 2,
HS_TX_LOCAL = 4,
};
DECL_CONSTANT("CANBUS_BRIDGE", 1);
void
canbus_notify_tx(void)
{
sched_wake_task(&UsbCan.wake);
}
// Handle incoming data from hw canbus interface (called from IRQ handler)
void
canbus_process_data(struct canbus_msg *msg)
{
// Add to admin command queue
uint32_t pushp = UsbCan.canhw_push_pos;
if (pushp - UsbCan.canhw_pull_pos >= ARRAY_SIZE(UsbCan.canhw_queue))
// No space - drop message
return;
if (UsbCan.assigned_id && (msg->id & ~1) == UsbCan.assigned_id)
// Id reserved for local
return;
uint32_t pos = pushp % ARRAY_SIZE(UsbCan.canhw_queue);
memcpy(&UsbCan.canhw_queue[pos], msg, sizeof(*msg));
UsbCan.canhw_push_pos = pushp + 1;
usb_notify_bulk_out();
}
// Send a message to the Linux host
static int
send_frame(struct canbus_msg *msg)
@@ -163,7 +147,7 @@ send_frame(struct canbus_msg *msg)
return usb_send_bulk_in(&gs, sizeof(gs));
}
// Send any pending hw frames to host
// Send any pending messages read from canbus hw to host
static void
drain_canhw_queue(void)
{
@@ -186,9 +170,9 @@ drain_canhw_queue(void)
}
}
// Fill local queue with any USB messages sent from host
// Fill local queue with any USB messages read from host
static void
fill_usb_host_queue(void)
drain_usb_host_messages(void)
{
uint32_t pull_pos = UsbCan.host_pull_pos, push_pos = UsbCan.host_push_pos;
for (;;) {
@@ -205,67 +189,124 @@ fill_usb_host_queue(void)
}
}
void
usbcan_task(void)
// Report bus stall state
static void
note_discard_state(uint32_t discard)
{
if (!sched_check_wake(&UsbCan.wake))
return;
sendf("usb_canbus_state discard=%u", discard);
}
// Send any pending hw frames to host
drain_canhw_queue();
// Check if canbus queue has gotten stuck
static int
check_need_discard(void)
{
if (UsbCan.bus_send_state != BSS_BLOCKING)
return 0;
return timer_is_before(UsbCan.bus_send_discard_time, timer_read_time());
}
// Fill local queue with any USB messages arriving from host
fill_usb_host_queue();
// Attempt to send a message on the canbus
static int
try_canmsg_send(struct canbus_msg *msg)
{
int ret = canhw_send(msg);
if (ret >= 0) {
// Success
if (UsbCan.bus_send_state == BSS_DISCARDING)
note_discard_state(0);
UsbCan.bus_send_state = BSS_READY;
return ret;
}
// Route messages received from host
// Unable to send message
if (check_need_discard()) {
// The canbus is stalled - start discarding messages
note_discard_state(1);
UsbCan.bus_send_state = BSS_DISCARDING;
}
if (UsbCan.bus_send_state == BSS_DISCARDING)
// Queue is stalled - just discard the message
return 0;
if (UsbCan.bus_send_state == BSS_READY) {
// Just starting to block - setup stall detection after 50ms
UsbCan.bus_send_state = BSS_BLOCKING;
UsbCan.bus_send_discard_time = timer_read_time() + timer_from_us(50000);
}
return ret;
}
// Process new requests arriving from the host
static void
drain_host_queue(void)
{
uint32_t pull_pos = UsbCan.host_pull_pos, push_pos = UsbCan.host_push_pos;
uint32_t pullp = pull_pos % ARRAY_SIZE(UsbCan.host_frames);
struct gs_host_frame *gs = &UsbCan.host_frames[pullp];
for (;;) {
// See if previous host frame needs to be transmitted
uint32_t pullp = pull_pos % ARRAY_SIZE(UsbCan.host_frames);
struct gs_host_frame *gs = &UsbCan.host_frames[pullp];
uint_fast8_t host_status = UsbCan.host_status;
if (host_status & (HS_TX_HW | HS_TX_LOCAL)) {
struct canbus_msg msg;
msg.id = gs->can_id;
msg.dlc = gs->can_dlc;
msg.data32[0] = gs->data32[0];
msg.data32[1] = gs->data32[1];
if (host_status & HS_TX_LOCAL) {
canserial_process_data(&msg);
UsbCan.host_status = host_status = host_status & ~HS_TX_LOCAL;
}
if (host_status & HS_TX_HW) {
int ret = canhw_send(&msg);
if (ret < 0)
break;
UsbCan.host_status = host_status = host_status & ~HS_TX_HW;
}
// Extract next frame from host
if (! host_status) {
if (pull_pos == push_pos)
// No frame available - no more work to be done
break;
uint32_t id = gs->can_id;
host_status = HS_TX_ECHO | HS_TX_HW;
if (id == CANBUS_ID_ADMIN)
host_status = HS_TX_ECHO | HS_TX_HW | HS_TX_LOCAL;
else if (UsbCan.assigned_id && UsbCan.assigned_id == id)
host_status = HS_TX_ECHO | HS_TX_LOCAL;
UsbCan.host_status = host_status;
}
// Send any previous echo frames
if (host_status) {
// Send echo frames back to host
if (host_status & HS_TX_ECHO) {
if (UsbCan.notify_local || UsbCan.usb_send_busy)
// Don't send echo frame until other traffic is sent
break;
int ret = usb_send_bulk_in(gs, sizeof(*gs));
if (ret < 0)
return;
UsbCan.host_status = 0;
UsbCan.host_pull_pos = pull_pos = pull_pos + 1;
break;
UsbCan.host_status = host_status = host_status & ~HS_TX_ECHO;
}
// Process next frame from host
if (pull_pos == push_pos)
// No frame available - no more work to be done
break;
gs = &UsbCan.host_frames[pull_pos % ARRAY_SIZE(UsbCan.host_frames)];
uint32_t id = gs->can_id;
UsbCan.host_status = HS_TX_ECHO | HS_TX_HW;
if (id == CANBUS_ID_ADMIN)
UsbCan.host_status = HS_TX_ECHO | HS_TX_HW | HS_TX_LOCAL;
else if (UsbCan.assigned_id && UsbCan.assigned_id == id)
UsbCan.host_status = HS_TX_ECHO | HS_TX_LOCAL;
// See if host frame needs to be transmitted
struct canbus_msg msg;
msg.id = gs->can_id;
msg.dlc = gs->can_dlc;
msg.data32[0] = gs->data32[0];
msg.data32[1] = gs->data32[1];
if (host_status & HS_TX_LOCAL) {
canserial_process_data(&msg);
UsbCan.host_status = host_status = host_status & ~HS_TX_LOCAL;
}
if (host_status & HS_TX_HW) {
int ret = try_canmsg_send(&msg);
if (ret < 0)
break;
UsbCan.host_status = host_status = host_status & ~HS_TX_HW;
}
// Note message fully processed
UsbCan.host_pull_pos = pull_pos = pull_pos + 1;
}
}
// Main message routing task
void
usbcan_task(void)
{
if (!sched_check_wake(&UsbCan.wake) && !check_need_discard())
return;
// Send messages read from canbus hardware to host
drain_canhw_queue();
// Fill local queue with any USB messages arriving from host
drain_usb_host_messages();
// Route messages received from host
drain_host_queue();
// Wake up local message response handling (if usb is not busy)
if (UsbCan.notify_local && !UsbCan.usb_send_busy)
@@ -273,6 +314,47 @@ usbcan_task(void)
}
DECL_TASK(usbcan_task);
// Helper function to wake usbcan_task()
static void
wake_usbcan_task(void)
{
sched_wake_task(&UsbCan.wake);
}
/****************************************************************
* Interface to canbus hardware (read canbus hw msgs and tx notifications)
****************************************************************/
void
canbus_notify_tx(void)
{
wake_usbcan_task();
}
// Handle incoming data from hw canbus interface (called from IRQ handler)
void
canbus_process_data(struct canbus_msg *msg)
{
// Add to admin command queue
uint32_t pushp = UsbCan.canhw_push_pos;
if (pushp - UsbCan.canhw_pull_pos >= ARRAY_SIZE(UsbCan.canhw_queue))
// No space - drop message
return;
if (UsbCan.assigned_id && (msg->id & ~1) == UsbCan.assigned_id)
// Id reserved for local
return;
uint32_t pos = pushp % ARRAY_SIZE(UsbCan.canhw_queue);
memcpy(&UsbCan.canhw_queue[pos], msg, sizeof(*msg));
UsbCan.canhw_push_pos = pushp + 1;
wake_usbcan_task();
}
/****************************************************************
* Handle messages routed locally (canserial.c interface)
****************************************************************/
int
canbus_send(struct canbus_msg *msg)
{
@@ -281,8 +363,8 @@ canbus_send(struct canbus_msg *msg)
int ret = send_frame(msg);
if (ret < 0)
goto retry_later;
if (UsbCan.notify_local && UsbCan.host_status)
canbus_notify_tx();
if (UsbCan.host_status)
wake_usbcan_task();
UsbCan.notify_local = 0;
return msg->dlc;
retry_later:
@@ -296,16 +378,21 @@ canbus_set_filter(uint32_t id)
UsbCan.assigned_id = id;
}
/****************************************************************
* USB bulk wakeup interface
****************************************************************/
void
usb_notify_bulk_out(void)
{
canbus_notify_tx();
wake_usbcan_task();
}
void
usb_notify_bulk_in(void)
{
canbus_notify_tx();
wake_usbcan_task();
}
@@ -576,8 +663,7 @@ usb_req_set_configuration(struct usb_ctrlrequest *req)
return;
}
usb_set_configure();
usb_notify_bulk_in();
usb_notify_bulk_out();
wake_usbcan_task();
usb_do_xfer(NULL, 0, UX_SEND);
}
@@ -692,8 +778,7 @@ DECL_TASK(usb_ep0_task);
void
usb_shutdown(void)
{
usb_notify_bulk_in();
usb_notify_bulk_out();
wake_usbcan_task();
usb_notify_ep0();
}
DECL_SHUTDOWN(usb_shutdown);

View File

@@ -44,11 +44,13 @@ usb_bulk_in_task(void)
{
if (!sched_check_wake(&usb_bulk_in_wake))
return;
uint_fast8_t tpos = transmit_pos;
uint_fast8_t tpos = transmit_pos, max_tpos = tpos;
if (!tpos)
return;
uint_fast8_t max_tpos = (tpos > USB_CDC_EP_BULK_IN_SIZE
? USB_CDC_EP_BULK_IN_SIZE : tpos);
if (max_tpos > USB_CDC_EP_BULK_IN_SIZE)
max_tpos = USB_CDC_EP_BULK_IN_SIZE;
else if (max_tpos == USB_CDC_EP_BULK_IN_SIZE)
max_tpos = USB_CDC_EP_BULK_IN_SIZE-1; // Avoid zero-length-packets
int_fast8_t ret = usb_send_bulk_in(transmit_buf, max_tpos);
if (ret <= 0)
return;

View File

@@ -3,14 +3,6 @@
#include <stdint.h> // uint_fast8_t
// endpoint sizes
enum {
USB_CDC_EP0_SIZE = 16,
USB_CDC_EP_ACM_SIZE = 8,
USB_CDC_EP_BULK_OUT_SIZE = 64,
USB_CDC_EP_BULK_IN_SIZE = 64,
};
// callbacks provided by board specific code
int_fast8_t usb_read_bulk_out(void *data, uint_fast8_t max_len);
int_fast8_t usb_send_bulk_in(void *data, uint_fast8_t len);

View File

@@ -8,4 +8,12 @@ enum {
USB_CDC_EP_ACM = 3,
};
// Default endpoint sizes
enum {
USB_CDC_EP0_SIZE = 16,
USB_CDC_EP_ACM_SIZE = 8,
USB_CDC_EP_BULK_OUT_SIZE = 64,
USB_CDC_EP_BULK_IN_SIZE = 64,
};
#endif // usb_cdc_ep.h

View File

@@ -9,7 +9,7 @@ config HC32F460_SELECT
select HAVE_GPIO_ADC
select HAVE_STRICT_TIMING
select HAVE_GPIO_HARD_PWM
select HAVE_STEPPER_BOTH_EDGE
select HAVE_STEPPER_OPTIMIZED_BOTH_EDGE
config BOARD_DIRECTORY
string

View File

@@ -1,11 +1,12 @@
// Software I2C emulation
//
// Copyright (C) 2023 Kevin O'Connor <kevin@koconnor.net>
// Copyright (C) 2023-2025 Kevin O'Connor <kevin@koconnor.net>
// Copyright (C) 2023 Alan.Ma <tech@biqu3d.com>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include <string.h> // memcpy
#include "autoconf.h" // CONFIG_CLOCK_FREQ
#include "board/gpio.h" // gpio_out_setup
#include "board/internal.h" // gpio_peripheral
#include "board/misc.h" // timer_read_time
@@ -17,7 +18,7 @@ struct i2c_software {
struct gpio_out scl_out, sda_out;
struct gpio_in scl_in, sda_in;
uint8_t addr;
unsigned int ticks;
uint32_t ticks;
};
void
@@ -25,7 +26,7 @@ command_i2c_set_software_bus(uint32_t *args)
{
struct i2cdev_s *i2c = i2cdev_oid_lookup(args[0]);
struct i2c_software *is = alloc_chunk(sizeof(*is));
is->ticks = 1000000 / 100 / 2; // 100KHz
is->ticks = CONFIG_CLOCK_FREQ / (100000 * 2); // 100KHz
is->addr = (args[4] & 0x7f) << 1; // address format shifted
is->scl_in = gpio_in_setup(args[1], 1);
is->scl_out = gpio_out_setup(args[1], 1);
@@ -44,16 +45,12 @@ DECL_COMMAND(command_i2c_set_software_bus,
#else
static unsigned int
nsecs_to_ticks(uint32_t ns)
{
return timer_from_us(ns * 1000) / 1000000;
}
static void
i2c_delay(unsigned int ticks) {
unsigned int t = timer_read_time() + nsecs_to_ticks(ticks);
while (t > timer_read_time());
i2c_delay(uint32_t ticks)
{
uint32_t end = timer_read_time() + ticks;
while (timer_is_before(timer_read_time(), end))
;
}
#endif

View File

@@ -12,7 +12,7 @@ config LPC_SELECT
select HAVE_GPIO_HARD_PWM
select HAVE_STRICT_TIMING
select HAVE_CHIPID
select HAVE_STEPPER_BOTH_EDGE
select HAVE_STEPPER_OPTIMIZED_BOTH_EDGE
select HAVE_BOOTLOADER_REQUEST
config BOARD_DIRECTORY

View File

@@ -7,4 +7,11 @@ enum {
USB_CDC_EP_BULK_IN = 5,
};
enum {
USB_CDC_EP0_SIZE = 16,
USB_CDC_EP_ACM_SIZE = 8,
USB_CDC_EP_BULK_OUT_SIZE = 64,
USB_CDC_EP_BULK_IN_SIZE = 64,
};
#endif // usb_cdc_ep.h

View File

@@ -12,7 +12,7 @@ config RPXXXX_SELECT
select HAVE_STRICT_TIMING
select HAVE_CHIPID
select HAVE_GPIO_HARD_PWM
select HAVE_STEPPER_BOTH_EDGE
select HAVE_STEPPER_OPTIMIZED_BOTH_EDGE
select HAVE_BOOTLOADER_REQUEST
config BOARD_DIRECTORY

View File

@@ -1,6 +1,6 @@
// Serial over CAN emulation for rp2040 using can2040 software canbus
//
// Copyright (C) 2022 Kevin O'Connor <kevin@koconnor.net>
// Copyright (C) 2022-2025 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
@@ -41,6 +41,23 @@ canhw_set_filter(uint32_t id)
// Filter not implemented (and not necessary)
}
static uint32_t last_tx_retries;
// Report interface status
void
canhw_get_status(struct canbus_status *status)
{
struct can2040_stats stats;
can2040_get_statistics(&cbus, &stats);
uint32_t tx_extra = stats.tx_attempt - stats.tx_total;
if (last_tx_retries != tx_extra)
last_tx_retries = tx_extra - 1;
status->rx_error = stats.parse_error;
status->tx_retries = last_tx_retries;
status->bus_state = CANBUS_STATE_ACTIVE;
}
// can2040 callback function - handle rx and tx notifications
static void
can2040_cb(struct can2040 *cd, uint32_t notify, struct can2040_msg *msg)

View File

@@ -15,7 +15,9 @@
#include "internal.h" // enable_pclock
#include "sched.h" // sched_main
#if !CONFIG_MACH_RP2040
#if CONFIG_MACH_RP2040
#include "hardware/structs/vreg_and_chip_reset.h" // vreg_and_chip_reset_hw
#else
#include "hardware/structs/ticks.h" // ticks_hw
#endif
@@ -58,9 +60,22 @@ bootloader_request(void)
****************************************************************/
#define FREQ_XOSC 12000000
#define FREQ_SYS (CONFIG_MACH_RP2040 ? 125000000 : CONFIG_CLOCK_FREQ)
#define FREQ_SYS (CONFIG_MACH_RP2040 ? 200000000 : CONFIG_CLOCK_FREQ)
#define FBDIV (FREQ_SYS == 200000000 ? 100 : 125)
#define FREQ_USB 48000000
void set_vsel(void)
{
// Set internal voltage regulator output to 1.15V on rp2040
#if CONFIG_MACH_RP2040
uint32_t cval = vreg_and_chip_reset_hw->vreg;
uint32_t vref = VREG_AND_CHIP_RESET_VREG_VSEL_RESET + 1;
cval &= ~VREG_AND_CHIP_RESET_VREG_VSEL_BITS;
cval |= vref << VREG_AND_CHIP_RESET_VREG_VSEL_LSB;
vreg_and_chip_reset_hw->vreg = cval;
#endif
}
void
enable_pclock(uint32_t reset_bit)
{
@@ -141,7 +156,8 @@ clock_setup(void)
// Setup xosc, pll_sys, and switch clk_sys
xosc_setup();
enable_pclock(RESETS_RESET_PLL_SYS_BITS);
pll_setup(pll_sys_hw, 125, 125*FREQ_XOSC/FREQ_SYS);
set_vsel();
pll_setup(pll_sys_hw, FBDIV, FBDIV * FREQ_XOSC / FREQ_SYS);
csys->ctrl = 0;
csys->div = 1<<CLOCKS_CLK_SYS_DIV_INT_LSB;
csys->ctrl = CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX;

View File

@@ -29,6 +29,8 @@ DECL_ENUMERATION("spi_bus", "spi1_gpio12_gpio15_gpio14", 6);
DECL_CONSTANT_STR("BUS_PINS_spi1_gpio12_gpio15_gpio14", "gpio12,gpio15,gpio14");
DECL_ENUMERATION("spi_bus", "spi1_gpio24_gpio27_gpio26", 7);
DECL_CONSTANT_STR("BUS_PINS_spi1_gpio24_gpio27_gpio26", "gpio24,gpio27,gpio26");
DECL_ENUMERATION("spi_bus", "spi1_gpio12_gpio11_gpio10", 8);
DECL_CONSTANT_STR("BUS_PINS_spi1_gpio12_gpio11_gpio10", "gpio12,gpio11,gpio10");
//Deprecated "spi0a" style mappings
DECL_ENUMERATION("spi_bus", "spi0a", 0);
@@ -63,6 +65,7 @@ static const struct spi_info spi_bus[] = {
{spi1_hw, 8, 11, 10, RESETS_RESET_SPI1_BITS},
{spi1_hw, 12, 15, 14, RESETS_RESET_SPI1_BITS},
{spi1_hw, 24, 27, 26, RESETS_RESET_SPI1_BITS},
{spi1_hw, 12, 11, 10, RESETS_RESET_SPI1_BITS},
};
struct spi_config

184
src/sensor_icm20948.c Normal file
View File

@@ -0,0 +1,184 @@
// Support for gathering acceleration data from icm20948 chip
//
// Copyright (C) 2024 Paul Hansel <github@paulhansel.com>
// Copyright (C) 2023 Matthew Swabey <matthew@swabey.org>
// Copyright (C) 2022 Harry Beyel <harry3b9@gmail.com>
// Copyright (C) 2020-2023 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include <string.h> // memcpy
#include "board/irq.h" // irq_disable
#include "board/misc.h" // timer_read_time
#include "basecmd.h" // oid_alloc
#include "command.h" // DECL_COMMAND
#include "sched.h" // DECL_TASK
#include "sensor_bulk.h" // sensor_bulk_report
#include "i2ccmds.h" // i2cdev_oid_lookup
// Chip registers
#define AR_FIFO_COUNT_H 0x70
#define AR_FIFO 0x72
#define AR_INT_STATUS 0x1B // INT_STATUS_2
#define FIFO_OVERFLOW_INT 0x0F // from datasheet
#define BYTES_PER_FIFO_ENTRY 6
#define BYTES_PER_BLOCK 48
struct icm20948 {
struct timer timer;
uint32_t rest_ticks;
struct i2cdev_s *i2c;
uint16_t fifo_max, fifo_pkts_bytes;
uint8_t flags;
struct sensor_bulk sb;
};
enum {
AX_PENDING = 1<<0,
};
static struct task_wake icm20948_wake;
// Event handler that wakes icm20948_task() periodically
static uint_fast8_t
icm20948_event(struct timer *timer)
{
struct icm20948 *ax = container_of(timer, struct icm20948, timer);
ax->flags |= AX_PENDING;
sched_wake_task(&icm20948_wake);
return SF_DONE;
}
void
command_config_icm20948(uint32_t *args)
{
struct icm20948 *ic = oid_alloc(args[0], command_config_icm20948
, sizeof(*ic));
ic->timer.func = icm20948_event;
ic->i2c = i2cdev_oid_lookup(args[1]);
}
DECL_COMMAND(command_config_icm20948, "config_icm20948 oid=%c i2c_oid=%c");
// Helper code to reschedule the icm20948_event() timer
static void
ic20948_reschedule_timer(struct icm20948 *ic)
{
irq_disable();
ic->timer.waketime = timer_read_time() + ic->rest_ticks;
sched_add_timer(&ic->timer);
irq_enable();
}
static void
read_mpu(struct i2cdev_s *i2c, uint8_t reg_len, uint8_t *reg
, uint8_t read_len, uint8_t *read)
{
int ret = i2c_dev_read(i2c, reg_len, reg, read_len, read);
i2c_shutdown_on_err(ret);
}
// Reads the fifo byte count from the device.
static uint16_t
get_fifo_status(struct icm20948 *ic)
{
uint8_t reg[] = {AR_FIFO_COUNT_H};
uint8_t msg[2];
read_mpu(ic->i2c, sizeof(reg), reg, sizeof(msg), msg);
uint16_t fifo_bytes = ((msg[0] & 0x1f) << 8) | msg[1];
if (fifo_bytes > ic->fifo_max)
ic->fifo_max = fifo_bytes;
return fifo_bytes;
}
// Query accelerometer data
static void
ic20948_query(struct icm20948 *ic, uint8_t oid)
{
// If not enough bytes to fill report read MPU FIFO's fill
if (ic->fifo_pkts_bytes < BYTES_PER_BLOCK)
ic->fifo_pkts_bytes = get_fifo_status(ic);
// If we have enough bytes to fill the buffer do it and send report
if (ic->fifo_pkts_bytes >= BYTES_PER_BLOCK) {
uint8_t reg = AR_FIFO;
read_mpu(ic->i2c, sizeof(reg), &reg, BYTES_PER_BLOCK, &ic->sb.data[0]);
ic->sb.data_count = BYTES_PER_BLOCK;
ic->fifo_pkts_bytes -= BYTES_PER_BLOCK;
sensor_bulk_report(&ic->sb, oid);
}
// If we have enough bytes remaining to fill another report wake again
// otherwise schedule timed wakeup
if (ic->fifo_pkts_bytes >= BYTES_PER_BLOCK) {
sched_wake_task(&icm20948_wake);
} else {
ic->flags &= ~AX_PENDING;
ic20948_reschedule_timer(ic);
}
}
void
command_query_icm20948(uint32_t *args)
{
struct icm20948 *ic = oid_lookup(args[0], command_config_icm20948);
sched_del_timer(&ic->timer);
ic->flags = 0;
if (!args[1]) {
// End measurements
// Uncomment and rebuilt to check for FIFO overruns when tuning
//output("mpu9240 fifo_max=%u", ic->fifo_max);
return;
}
// Start new measurements query
ic->rest_ticks = args[1];
sensor_bulk_reset(&ic->sb);
ic->fifo_max = 0;
ic->fifo_pkts_bytes = 0;
ic20948_reschedule_timer(ic);
}
DECL_COMMAND(command_query_icm20948, "query_icm20948 oid=%c rest_ticks=%u");
void
command_query_icm20948_status(uint32_t *args)
{
struct icm20948 *ic = oid_lookup(args[0], command_config_icm20948);
// Detect if a FIFO overrun occurred
uint8_t int_reg[] = {AR_INT_STATUS};
uint8_t int_msg;
read_mpu(ic->i2c, sizeof(int_reg), int_reg, sizeof(int_msg), &int_msg);
if (int_msg & FIFO_OVERFLOW_INT)
ic->sb.possible_overflows++;
// Read latest FIFO count (with precise timing)
uint8_t reg[] = {AR_FIFO_COUNT_H};
uint8_t msg[2];
uint32_t time1 = timer_read_time();
read_mpu(ic->i2c, sizeof(reg), reg, sizeof(msg), msg);
uint32_t time2 = timer_read_time();
uint16_t fifo_bytes = ((msg[0] & 0x1f) << 8) | msg[1];
// Report status
sensor_bulk_status(&ic->sb, args[0], time1, time2-time1, fifo_bytes);
}
DECL_COMMAND(command_query_icm20948_status, "query_icm20948_status oid=%c");
void
icm20948_task(void)
{
if (!sched_check_wake(&icm20948_wake))
return;
uint8_t oid;
struct icm20948 *ic;
foreach_oid(oid, ic, command_config_icm20948) {
uint_fast8_t flags = ic->flags;
if (flags & AX_PENDING)
ic20948_query(ic, oid);
}
}
DECL_TASK(icm20948_task);

View File

@@ -1,6 +1,6 @@
// Handling of stepper drivers.
//
// 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.
@@ -14,17 +14,18 @@
#include "stepper.h" // stepper_event
#include "trsync.h" // trsync_add_signal
#if CONFIG_INLINE_STEPPER_HACK && CONFIG_HAVE_STEPPER_BOTH_EDGE
#define HAVE_SINGLE_SCHEDULE 1
DECL_CONSTANT("STEPPER_STEP_BOTH_EDGE", 1);
#if CONFIG_INLINE_STEPPER_HACK && CONFIG_WANT_STEPPER_OPTIMIZED_BOTH_EDGE
#define HAVE_OPTIMIZED_PATH 1
#define HAVE_EDGE_OPTIMIZATION 1
#define HAVE_AVR_OPTIMIZATION 0
DECL_CONSTANT("STEPPER_BOTH_EDGE", 1);
#elif CONFIG_INLINE_STEPPER_HACK && CONFIG_MACH_AVR
#define HAVE_SINGLE_SCHEDULE 1
#define HAVE_OPTIMIZED_PATH 1
#define HAVE_EDGE_OPTIMIZATION 0
#define HAVE_AVR_OPTIMIZATION 1
#else
#define HAVE_SINGLE_SCHEDULE 0
#define HAVE_OPTIMIZED_PATH 0
#define HAVE_EDGE_OPTIMIZATION 0
#define HAVE_AVR_OPTIMIZATION 0
#endif
@@ -57,7 +58,7 @@ enum { POSITION_BIAS=0x40000000 };
enum {
SF_LAST_DIR=1<<0, SF_NEXT_DIR=1<<1, SF_INVERT_STEP=1<<2, SF_NEED_RESET=1<<3,
SF_SINGLE_SCHED=1<<4, SF_HAVE_ADD=1<<5
SF_SINGLE_SCHED=1<<4, SF_OPTIMIZED_PATH=1<<5, SF_HAVE_ADD=1<<6
};
// Setup a stepper for the next move in its queue
@@ -75,7 +76,7 @@ stepper_load_next(struct stepper *s)
struct stepper_move *m = container_of(mn, struct stepper_move, node);
s->add = m->add;
s->interval = m->interval + m->add;
if (HAVE_SINGLE_SCHEDULE && s->flags & SF_SINGLE_SCHED) {
if (HAVE_OPTIMIZED_PATH && s->flags & SF_OPTIMIZED_PATH) {
s->time.waketime += m->interval;
if (HAVE_AVR_OPTIMIZATION)
s->flags = m->add ? s->flags|SF_HAVE_ADD : s->flags & ~SF_HAVE_ADD;
@@ -85,7 +86,7 @@ stepper_load_next(struct stepper *s)
// twice as many events.
s->next_step_time += m->interval;
s->time.waketime = s->next_step_time;
s->count = (uint32_t)m->count * 2;
s->count = s->flags & SF_SINGLE_SCHED ? m->count : (uint32_t)m->count*2;
}
// Add all steps to s->position (stepper_get_position() can calc mid-move)
if (m->flags & MF_DIR) {
@@ -99,8 +100,14 @@ stepper_load_next(struct stepper *s)
return SF_RESCHEDULE;
}
// Edge optimization only enabled when fastest rate notably slower than 100ns
#define EDGE_STEP_TICKS DIV_ROUND_UP(CONFIG_CLOCK_FREQ, 8000000)
#if HAVE_EDGE_OPTIMIZATION
DECL_CONSTANT("STEPPER_OPTIMIZED_EDGE", EDGE_STEP_TICKS);
#endif
// Optimized step function to step on each step pin edge
uint_fast8_t
static uint_fast8_t
stepper_event_edge(struct timer *t)
{
struct stepper *s = container_of(t, struct stepper, time);
@@ -115,7 +122,10 @@ stepper_event_edge(struct timer *t)
return stepper_load_next(s);
}
#define AVR_STEP_INSNS 40 // minimum instructions between step gpio pulses
#define AVR_STEP_TICKS 40 // minimum instructions between step gpio pulses
#if HAVE_AVR_OPTIMIZATION
DECL_CONSTANT("STEPPER_OPTIMIZED_UNSTEP", AVR_STEP_TICKS);
#endif
// AVR optimized step function
static uint_fast8_t
@@ -137,8 +147,8 @@ stepper_event_avr(struct timer *t)
return ret;
}
// Regular "double scheduled" step function
uint_fast8_t
// Regular "fully scheduled" step function
static uint_fast8_t
stepper_event_full(struct timer *t)
{
struct stepper *s = container_of(t, struct stepper, time);
@@ -146,7 +156,7 @@ stepper_event_full(struct timer *t)
uint32_t curtime = timer_read_time();
uint32_t min_next_time = curtime + s->step_pulse_ticks;
s->count--;
if (likely(s->count & 1))
if (likely(s->count & 1 && !(s->flags & SF_SINGLE_SCHED)))
// Schedule unstep event
goto reschedule_min;
if (likely(s->count)) {
@@ -186,20 +196,23 @@ command_config_stepper(uint32_t *args)
{
struct stepper *s = oid_alloc(args[0], command_config_stepper, sizeof(*s));
int_fast8_t invert_step = args[3];
s->flags = invert_step > 0 ? SF_INVERT_STEP : 0;
if (invert_step > 0)
s->flags = SF_INVERT_STEP;
else if (invert_step < 0)
s->flags = SF_SINGLE_SCHED;
s->step_pin = gpio_out_setup(args[1], s->flags & SF_INVERT_STEP);
s->dir_pin = gpio_out_setup(args[2], 0);
s->position = -POSITION_BIAS;
s->step_pulse_ticks = args[4];
move_queue_setup(&s->mq, sizeof(struct stepper_move));
if (HAVE_EDGE_OPTIMIZATION) {
if (!s->step_pulse_ticks && invert_step < 0)
s->flags |= SF_SINGLE_SCHED;
if (invert_step < 0 && s->step_pulse_ticks <= EDGE_STEP_TICKS)
s->flags |= SF_OPTIMIZED_PATH;
else
s->time.func = stepper_event_full;
} else if (HAVE_AVR_OPTIMIZATION) {
if (s->step_pulse_ticks <= AVR_STEP_INSNS)
s->flags |= SF_SINGLE_SCHED;
if (invert_step >= 0 && s->step_pulse_ticks <= AVR_STEP_TICKS)
s->flags |= SF_SINGLE_SCHED | SF_OPTIMIZED_PATH;
else
s->time.func = stepper_event_full;
} else if (!CONFIG_INLINE_STEPPER_HACK) {
@@ -284,7 +297,7 @@ stepper_get_position(struct stepper *s)
{
uint32_t position = s->position;
// If stepper is mid-move, subtract out steps not yet taken
if (HAVE_SINGLE_SCHEDULE && s->flags & SF_SINGLE_SCHED)
if (s->flags & SF_SINGLE_SCHED)
position -= s->count;
else
position -= s->count / 2;
@@ -316,9 +329,12 @@ stepper_stop(struct trsync_signal *tss, uint8_t reason)
s->next_step_time = s->time.waketime = 0;
s->position = -stepper_get_position(s);
s->count = 0;
s->flags = (s->flags & (SF_INVERT_STEP|SF_SINGLE_SCHED)) | SF_NEED_RESET;
s->flags = ((s->flags & (SF_INVERT_STEP|SF_SINGLE_SCHED|SF_OPTIMIZED_PATH))
| SF_NEED_RESET);
gpio_out_write(s->dir_pin, 0);
if (!(HAVE_EDGE_OPTIMIZATION && s->flags & SF_SINGLE_SCHED))
if (!(s->flags & SF_SINGLE_SCHED)
|| (HAVE_AVR_OPTIMIZATION && s->flags & SF_OPTIMIZED_PATH))
// Must return step pin to "unstep" state
gpio_out_write(s->step_pin, s->flags & SF_INVERT_STEP);
while (!move_queue_empty(&s->mq)) {
struct move_node *mn = move_queue_pop(&s->mq);

View File

@@ -13,9 +13,9 @@ config STM32_SELECT
select HAVE_GPIO_HARD_PWM if MACH_STM32F070 || MACH_STM32F072 || MACH_STM32F1 || MACH_STM32F4 || MACH_STM32F7 || MACH_STM32G0 || MACH_STM32H7
select HAVE_STRICT_TIMING
select HAVE_CHIPID
select HAVE_STEPPER_BOTH_EDGE
select HAVE_STEPPER_OPTIMIZED_BOTH_EDGE
select HAVE_BOOTLOADER_REQUEST
select HAVE_LIMITED_CODE_SIZE if MACH_STM32F031 || MACH_STM32F042
select HAVE_LIMITED_CODE_SIZE if FLASH_SIZE < 0x10000
config BOARD_DIRECTORY
string
@@ -117,6 +117,10 @@ config MACH_STM32F103x6
depends on LOW_LEVEL_OPTIONS && MACH_STM32F103
bool "Only 10KiB of RAM (for rare stm32f103x6 variant)"
config MACH_STM32F070x6
depends on LOW_LEVEL_OPTIONS && MACH_STM32F070
bool "Only 6KiB of RAM (for rare stm32f070x6 variant)"
config MACH_STM32F0
bool
config MACH_STM32F1
@@ -211,8 +215,8 @@ config CLOCK_FREQ
config FLASH_SIZE
hex
default 0x8000 if MACH_STM32F031 || MACH_STM32F042
default 0x20000 if MACH_STM32F070 || MACH_STM32F072
default 0x8000 if MACH_STM32F031 || MACH_STM32F042 || MACH_STM32F070x6
default 0x20000 if (MACH_STM32F070 || MACH_STM32F072) && !MACH_STM32F070x6
default 0x10000 if MACH_STM32F103 || MACH_STM32L412 # Flash size of stm32f103x8 (64KiB)
default 0x40000 if MACH_STM32F2 || MACH_STM32F401 || MACH_STM32H723
default 0x80000 if MACH_STM32F4x5 || MACH_STM32F446
@@ -234,7 +238,8 @@ config RAM_SIZE
hex
default 0x1000 if MACH_STM32F031
default 0x1800 if MACH_STM32F042
default 0x4000 if MACH_STM32F070 || MACH_STM32F072
default 0x1800 if MACH_STM32F070x6
default 0x4000 if (MACH_STM32F070 || MACH_STM32F072) && !MACH_STM32F070x6
default 0x2800 if MACH_STM32F103x6
default 0x5000 if MACH_STM32F103 && !MACH_STM32F103x6 # Ram size of stm32f103x8
default 0x8000 if MACH_STM32G431
@@ -384,7 +389,8 @@ choice
bool "USB (on PA11/PA12)" if HAVE_STM32_USBFS || HAVE_STM32_USBOTG
select USBSERIAL
config STM32_USB_PA11_PA12_REMAP
bool "USB (on PA9/PA10)" if LOW_LEVEL_OPTIONS && MACH_STM32F042
bool "USB (on PA9/PA10)"
depends on MACH_STM32F042 || MACH_STM32F070x6
select USBSERIAL
config STM32_USB_PB14_PB15
bool "USB (on PB14/PB15)"
@@ -428,13 +434,19 @@ choice
bool "Serial (on USART5 PD2/PD3)" if LOW_LEVEL_OPTIONS
depends on MACH_STM32G0Bx
select SERIAL
config STM32_SERIAL_USART6
bool "Serial (on USART6 PA12/PA11)" if LOW_LEVEL_OPTIONS && MACH_STM32F401
select SERIAL
config STM32_SERIAL_USART6_ALT_PC7_PC6
bool "Serial (on USART6 PC7/PC6)" if LOW_LEVEL_OPTIONS && MACH_STM32F401
select SERIAL
config STM32_CANBUS_PA11_PA12
bool "CAN bus (on PA11/PA12)"
depends on HAVE_STM32_CANBUS || HAVE_STM32_FDCANBUS
select CANSERIAL
config STM32_CANBUS_PA11_PA12_REMAP
bool "CAN bus (on PA9/PA10)" if LOW_LEVEL_OPTIONS
depends on HAVE_STM32_CANBUS && MACH_STM32F042
depends on HAVE_STM32_CANBUS && (MACH_STM32F042 || MACH_STM32F070x6)
select CANSERIAL
config STM32_CANBUS_PA11_PB9
bool "CAN bus (on PA11/PB9)"
@@ -472,6 +484,10 @@ choice
bool "CAN bus (on PC2/PC3)"
depends on HAVE_STM32_FDCANBUS
select CANSERIAL
config STM32_MMENU_CANBUS_PH13_PH14
bool "CAN bus (on PH13/PH14)" if MACH_STM32H743
depends on HAVE_STM32_FDCANBUS
select CANSERIAL
config STM32_USBCANBUS_PA11_PA12
bool "USB to CAN bus bridge (USB on PA11/PA12)"
depends on HAVE_STM32_USBCANBUS
@@ -502,6 +518,9 @@ choice
config STM32_CMENU_CANBUS_PC2_PC3
bool "CAN bus (on PC2/PC3)"
depends on HAVE_STM32_FDCANBUS
config STM32_CMENU_CANBUS_PH13_PH14
bool "CAN bus (on PH13/PH14)" if MACH_STM32H743
depends on HAVE_STM32_FDCANBUS
endchoice
@@ -529,5 +548,8 @@ config STM32_CANBUS_PD12_PD13
config STM32_CANBUS_PC2_PC3
bool
default y if STM32_MMENU_CANBUS_PC2_PC3 || STM32_CMENU_CANBUS_PC2_PC3
config STM32_CANBUS_PH13_PH14
bool
default y if STM32_MMENU_CANBUS_PH13_PH14 || STM32_CMENU_CANBUS_PH13_PH14
endif

View File

@@ -2,7 +2,7 @@
//
// Copyright (C) 2019 Eug Krashtan <eug.krashtan@gmail.com>
// Copyright (C) 2020 Pontus Borg <glpontus@gmail.com>
// Copyright (C) 2021 Kevin O'Connor <kevin@koconnor.net>
// Copyright (C) 2021-2025 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
@@ -168,6 +168,31 @@ canhw_set_filter(uint32_t id)
fcan->FMR = fmr & ~CAN_FMR_FINIT;
}
static struct {
uint32_t rx_error, tx_error;
} CAN_Errors;
// Report interface status
void
canhw_get_status(struct canbus_status *status)
{
irqstatus_t flag = irq_save();
uint32_t esr = SOC_CAN->ESR;
uint32_t rx_error = CAN_Errors.rx_error, tx_error = CAN_Errors.tx_error;
irq_restore(flag);
status->rx_error = rx_error;
status->tx_error = tx_error;
if (esr & CAN_ESR_BOFF)
status->bus_state = CANBUS_STATE_OFF;
else if (esr & CAN_ESR_EPVF)
status->bus_state = CANBUS_STATE_PASSIVE;
else if (esr & CAN_ESR_EWGF)
status->bus_state = CANBUS_STATE_WARN;
else
status->bus_state = 0;
}
// This function handles CAN global interrupts
void
CAN_IRQHandler(void)
@@ -190,6 +215,8 @@ CAN_IRQHandler(void)
// Process packet
canbus_process_data(&msg);
}
// Check for transmit ready
uint32_t ier = SOC_CAN->IER;
if (ier & CAN_IER_TMEIE
&& SOC_CAN->TSR & (CAN_TSR_RQCP0|CAN_TSR_RQCP1|CAN_TSR_RQCP2)) {
@@ -197,6 +224,21 @@ CAN_IRQHandler(void)
SOC_CAN->IER = ier & ~CAN_IER_TMEIE;
canbus_notify_tx();
}
// Check for error irq
uint32_t msr = SOC_CAN->MSR;
if (msr & CAN_MSR_ERRI) {
uint32_t esr = SOC_CAN->ESR;
uint32_t lec = (esr & CAN_ESR_LEC_Msk) >> CAN_ESR_LEC_Pos;
if (lec && lec != 7) {
SOC_CAN->ESR = 7 << CAN_ESR_LEC_Pos;
if (lec >= 3 && lec <= 5)
CAN_Errors.tx_error += 1;
else
CAN_Errors.rx_error += 1;
}
SOC_CAN->MSR = CAN_MSR_ERRI;
}
}
static inline const uint32_t
@@ -289,6 +331,8 @@ can_init(void)
armcm_enable_irq(CAN_IRQHandler, CAN_RX1_IRQn, 0);
if (CAN_RX0_IRQn != CAN_TX_IRQn)
armcm_enable_irq(CAN_IRQHandler, CAN_TX_IRQn, 0);
SOC_CAN->IER = CAN_IER_FMPIE0;
if (CAN_RX0_IRQn != CAN_SCE_IRQn)
armcm_enable_irq(CAN_IRQHandler, CAN_SCE_IRQn, 0);
SOC_CAN->IER = CAN_IER_FMPIE0 | CAN_IER_ERRIE | CAN_IER_LECIE;
}
DECL_INIT(can_init);

View File

@@ -1,11 +1,12 @@
// FDCAN support on stm32 chips
//
// Copyright (C) 2021-2022 Kevin O'Connor <kevin@koconnor.net>
// Copyright (C) 2021-2025 Kevin O'Connor <kevin@koconnor.net>
// Copyright (C) 2019 Eug Krashtan <eug.krashtan@gmail.com>
// Copyright (C) 2020 Pontus Borg <glpontus@gmail.com>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include "board/irq.h" // irq_save
#include "command.h" // DECL_CONSTANT_STR
#include "generic/armcm_boot.h" // armcm_enable_irq
#include "generic/canbus.h" // canbus_notify_tx
@@ -54,6 +55,10 @@
DECL_CONSTANT_STR("RESERVE_PINS_CAN", "PB12,PB13");
#define GPIO_Rx GPIO('B', 12)
#define GPIO_Tx GPIO('B', 13)
#elif CONFIG_STM32_CANBUS_PH13_PH14
DECL_CONSTANT_STR("RESERVE_PINS_CAN", "PH14,PH13");
#define GPIO_Rx GPIO('H', 14)
#define GPIO_Tx GPIO('H', 13)
#endif
#if !(CONFIG_STM32_CANBUS_PB0_PB1 || CONFIG_STM32_CANBUS_PC2_PC3 \
@@ -184,6 +189,38 @@ canhw_set_filter(uint32_t id)
SOC_CAN->CCCR &= ~FDCAN_CCCR_INIT;
}
static struct {
uint32_t rx_error, tx_error;
} CAN_Errors;
// Report interface status
void
canhw_get_status(struct canbus_status *status)
{
irqstatus_t flag = irq_save();
uint32_t psr = SOC_CAN->PSR, lec = psr & FDCAN_PSR_LEC_Msk;
if (lec && lec != 7) {
// Reading PSR clears it - so update state here
if (lec >= 3 && lec <= 5)
CAN_Errors.tx_error += 1;
else
CAN_Errors.rx_error += 1;
}
uint32_t rx_error = CAN_Errors.rx_error, tx_error = CAN_Errors.tx_error;
irq_restore(flag);
status->rx_error = rx_error;
status->tx_error = tx_error;
if (psr & FDCAN_PSR_BO)
status->bus_state = CANBUS_STATE_OFF;
else if (psr & FDCAN_PSR_EP)
status->bus_state = CANBUS_STATE_PASSIVE;
else if (psr & FDCAN_PSR_EW)
status->bus_state = CANBUS_STATE_WARN;
else
status->bus_state = 0;
}
// This function handles CAN global interrupts
void
CAN_IRQHandler(void)
@@ -220,6 +257,18 @@ CAN_IRQHandler(void)
SOC_CAN->IR = FDCAN_IE_TC;
canbus_notify_tx();
}
if (ir & (FDCAN_IR_PED | FDCAN_IR_PEA)) {
// Bus error
uint32_t psr = SOC_CAN->PSR;
SOC_CAN->IR = FDCAN_IR_PED | FDCAN_IR_PEA;
uint32_t lec = psr & FDCAN_PSR_LEC_Msk;
if (lec && lec != 7) {
if (lec >= 3 && lec <= 5)
CAN_Errors.tx_error += 1;
else
CAN_Errors.rx_error += 1;
}
}
}
static inline const uint32_t
@@ -320,6 +369,6 @@ can_init(void)
/*##-3- Configure Interrupts #################################*/
armcm_enable_irq(CAN_IRQHandler, CAN_IT0_IRQn, 1);
SOC_CAN->ILE = FDCAN_ILE_EINT0;
SOC_CAN->IE = FDCAN_IE_RF0NE | FDCAN_IE_TC;
SOC_CAN->IE = FDCAN_IE_RF0NE | FDCAN_IE_TC | FDCAN_IE_PEDE | FDCAN_IE_PEAE;
}
DECL_INIT(can_init);

View File

@@ -38,7 +38,13 @@ void gpio_adc_cancel_sample(struct gpio_adc g);
struct spi_config {
void *spi;
uint32_t spi_cr1;
union {
uint32_t spi_cr1;
struct {
uint8_t div;
uint8_t mode;
};
};
};
struct spi_config spi_setup(uint32_t bus, uint8_t mode, uint32_t rate);
void spi_prepare(struct spi_config config);

View File

@@ -32,6 +32,8 @@ DECL_ENUMERATION("i2c_bus", "i2c2a", 4);
DECL_CONSTANT_STR("BUS_PINS_i2c2a", "PH4,PH5");
DECL_ENUMERATION("i2c_bus", "i2c3a", 5);
DECL_CONSTANT_STR("BUS_PINS_i2c3a", "PH7,PH8");
DECL_ENUMERATION("i2c_bus", "i2c2_PF1_PF0", 6);
DECL_CONSTANT_STR("BUS_PINS_i2c2_PF1_PF0", "PF1,PF0");
#endif
#endif
@@ -44,6 +46,7 @@ static const struct i2c_info i2c_bus[] = {
#if CONFIG_MACH_STM32F2 || CONFIG_MACH_STM32F4x5
{ I2C2, GPIO('H', 4), GPIO('H', 5) },
{ I2C3, GPIO('H', 7), GPIO('H', 8) },
{ I2C2, GPIO('F', 1), GPIO('F', 0) },
#endif
#endif
};

View File

@@ -16,38 +16,58 @@
DECL_CONSTANT_STR("RESERVE_PINS_serial", "PA10,PA9");
#define GPIO_Rx GPIO('A', 10)
#define GPIO_Tx GPIO('A', 9)
#define GPIO_AF_MODE 7
#define USARTx USART1
#define USARTx_IRQn USART1_IRQn
#elif CONFIG_STM32_SERIAL_USART1_ALT_PB7_PB6
DECL_CONSTANT_STR("RESERVE_PINS_serial", "PB7,PB6");
#define GPIO_Rx GPIO('B', 7)
#define GPIO_Tx GPIO('B', 6)
#define GPIO_AF_MODE 7
#define USARTx USART1
#define USARTx_IRQn USART1_IRQn
#elif CONFIG_STM32_SERIAL_USART2
DECL_CONSTANT_STR("RESERVE_PINS_serial", "PA3,PA2");
#define GPIO_Rx GPIO('A', 3)
#define GPIO_Tx GPIO('A', 2)
#define GPIO_AF_MODE 7
#define USARTx USART2
#define USARTx_IRQn USART2_IRQn
#elif CONFIG_STM32_SERIAL_USART2_ALT_PD6_PD5
DECL_CONSTANT_STR("RESERVE_PINS_serial", "PD6,PD5");
#define GPIO_Rx GPIO('D', 6)
#define GPIO_Tx GPIO('D', 5)
#define GPIO_AF_MODE 7
#define USARTx USART2
#define USARTx_IRQn USART2_IRQn
#elif CONFIG_STM32_SERIAL_USART3
DECL_CONSTANT_STR("RESERVE_PINS_serial", "PB11,PB10");
#define GPIO_Rx GPIO('B', 11)
#define GPIO_Tx GPIO('B', 10)
#define GPIO_AF_MODE 7
#define USARTx USART3
#define USARTx_IRQn USART3_IRQn
#elif CONFIG_STM32_SERIAL_USART3_ALT_PD9_PD8
DECL_CONSTANT_STR("RESERVE_PINS_serial", "PD9,PD8");
#define GPIO_Rx GPIO('D', 9)
#define GPIO_Tx GPIO('D', 8)
#define GPIO_AF_MODE 7
#define USARTx USART3
#define USARTx_IRQn USART3_IRQn
#elif CONFIG_STM32_SERIAL_USART6
DECL_CONSTANT_STR("RESERVE_PINS_serial", "PA12,PA11");
#define GPIO_Rx GPIO('A', 12)
#define GPIO_Tx GPIO('A', 11)
#define GPIO_AF_MODE 8
#define USARTx USART6
#define USARTx_IRQn USART6_IRQn
#elif CONFIG_STM32_SERIAL_USART6_ALT_PC7_PC6
DECL_CONSTANT_STR("RESERVE_PINS_serial", "PC7,PC6");
#define GPIO_Rx GPIO('C', 7)
#define GPIO_Tx GPIO('C', 6)
#define GPIO_AF_MODE 8
#define USARTx USART6
#define USARTx_IRQn USART6_IRQn
#endif
#define CR1_FLAGS (USART_CR1_UE | USART_CR1_RE | USART_CR1_TE \
@@ -90,7 +110,7 @@ serial_init(void)
USARTx->CR1 = CR1_FLAGS;
armcm_enable_irq(USARTx_IRQHandler, USARTx_IRQn, 0);
gpio_peripheral(GPIO_Rx, GPIO_FUNCTION(7), 1);
gpio_peripheral(GPIO_Tx, GPIO_FUNCTION(7), 0);
gpio_peripheral(GPIO_Rx, GPIO_FUNCTION(GPIO_AF_MODE), 1);
gpio_peripheral(GPIO_Tx, GPIO_FUNCTION(GPIO_AF_MODE), 0);
}
DECL_INIT(serial_init);

View File

@@ -186,10 +186,8 @@ armcm_main(void)
hsi14_setup();
// Support pin remapping USB/CAN pins on low pinout stm32f042
#ifdef SYSCFG_CFGR1_PA11_PA12_RMP
if (CONFIG_STM32_USB_PA11_PA12_REMAP || CONFIG_STM32_CANBUS_PA11_PA12_REMAP)
SYSCFG->CFGR1 |= SYSCFG_CFGR1_PA11_PA12_RMP;
#endif
SYSCFG->CFGR1 |= 1<<4; // SYSCFG_CFGR1_PA11_PA12_RMP
sched_main();
}

View File

@@ -100,19 +100,27 @@ spi_setup(uint32_t bus, uint8_t mode, uint32_t rate)
while ((pclk >> (div + 1)) > rate && div < 7)
div++;
uint32_t cr1 = SPI_CR1_SPE;
spi->CFG1 |= (div << SPI_CFG1_MBR_Pos) | (7 << SPI_CFG1_DSIZE_Pos);
CLEAR_BIT(spi->CFG1, SPI_CFG1_CRCSIZE);
spi->CFG2 |= ((mode << SPI_CFG2_CPHA_Pos) | SPI_CFG2_MASTER | SPI_CFG2_SSM
| SPI_CFG2_AFCNTR | SPI_CFG2_SSOE);
spi->CR1 |= SPI_CR1_SSI;
return (struct spi_config){ .spi = spi, .spi_cr1 = cr1 };
return (struct spi_config){ .spi = spi, .div = div, .mode = mode };
}
void
spi_prepare(struct spi_config config)
{
uint32_t div = config.div;
uint32_t mode = config.mode;
SPI_TypeDef *spi = config.spi;
// Reload frequency
spi->CFG1 = (spi->CFG1 & ~SPI_CFG1_MBR_Msk);
spi->CFG1 |= (div << SPI_CFG1_MBR_Pos);
// Reload mode
spi->CFG2 = (spi->CFG2 & ~SPI_CFG2_CPHA_Msk);
spi->CFG2 |= (mode << SPI_CFG2_CPHA_Pos);
}
void

View File

@@ -1,2 +1,3 @@
# Base config file for rp2040 boards
CONFIG_MACH_RPXXXX=y
CONFIG_MACH_RP2040=y

View File

@@ -0,0 +1,3 @@
# Base config file for rp2350 boards
CONFIG_MACH_RPXXXX=y
CONFIG_MACH_RP2350=y