Compare commits
75 Commits
work-close
...
v0.13.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61c0c8d2ef | ||
|
|
ce7657e537 | ||
|
|
037377b927 | ||
|
|
5493bdfb48 | ||
|
|
4b9cb36247 | ||
|
|
f3e89e25c5 | ||
|
|
655861cf12 | ||
|
|
050bc33241 | ||
|
|
46ee920b93 | ||
|
|
3a9e9a4bef | ||
|
|
3beb465247 | ||
|
|
2ec69ae361 | ||
|
|
52b07f467e | ||
|
|
81a1a03ed0 | ||
|
|
869440a7ed | ||
|
|
20f26b534d | ||
|
|
91cba8a17f | ||
|
|
be429caba3 | ||
|
|
8176ba22aa | ||
|
|
4b9add2fc3 | ||
|
|
55f60601ca | ||
|
|
876f351127 | ||
|
|
089516a6f2 | ||
|
|
f511e201f9 | ||
|
|
52617455ce | ||
|
|
d679f711eb | ||
|
|
68dbbc8d41 | ||
|
|
59ebdce605 | ||
|
|
310747a636 | ||
|
|
a3b4b39ff1 | ||
|
|
fb91aad583 | ||
|
|
825d4baf90 | ||
|
|
8faed8d9fe | ||
|
|
272e815522 | ||
|
|
06d65ef5ac | ||
|
|
d886c1761b | ||
|
|
47aa28e530 | ||
|
|
fbd5b49215 | ||
|
|
dad2196776 | ||
|
|
b50d740542 | ||
|
|
3e7efe5ef1 | ||
|
|
75a10bfcaf | ||
|
|
730e5951bc | ||
|
|
941fb5a367 | ||
|
|
17d471c07c | ||
|
|
ef1d8bc3bd | ||
|
|
581c954f40 | ||
|
|
98068beca0 | ||
|
|
3c1bf4ccfe | ||
|
|
1836ec431c | ||
|
|
14c105b86e | ||
|
|
2f6d240900 | ||
|
|
edc3d34beb | ||
|
|
53f1bf2af2 | ||
|
|
1fc6d214f4 | ||
|
|
bf5c4daf86 | ||
|
|
ec56167032 | ||
|
|
15339aec64 | ||
|
|
a90110d9ba | ||
|
|
e24ea3652c | ||
|
|
508c28e689 | ||
|
|
fec3e685c9 | ||
|
|
b16cb6575d | ||
|
|
329fbd01d8 | ||
|
|
01b0e98ab2 | ||
|
|
638c085ffa | ||
|
|
8a2de5f23e | ||
|
|
2c90c97ccd | ||
|
|
2db2ef82f2 | ||
|
|
eb0581c264 | ||
|
|
61fb5fe29c | ||
|
|
9fd415d3f5 | ||
|
|
b7366ae3fc | ||
|
|
6cdcf75e6b | ||
|
|
d57fe4395e |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 |
|
||||
|
||||
287
docs/G-Codes.md
287
docs/G-Codes.md
@@ -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.
|
||||
|
||||
@@ -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
122
docs/Load_Cell.md
Normal 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 %}
|
||||
```
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
```
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -141,4 +141,5 @@ nav:
|
||||
- TSL1401CL_Filament_Width_Sensor.md
|
||||
- Hall_Filament_Width_Sensor.md
|
||||
- Eddy_Probe.md
|
||||
- Load_Cell.md
|
||||
- Sponsors.md
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
80
klippy/extras/canbus_stats.py
Normal file
80
klippy/extras/canbus_stats.py
Normal 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)
|
||||
@@ -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.)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
31
klippy/extras/garbage_collection.py
Normal file
31
klippy/extras/garbage_collection.py
Normal 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)
|
||||
@@ -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,
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
173
klippy/extras/icm20948.py
Normal 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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 = {}
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_")
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
######################################################################
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
40
src/Kconfig
40
src/Kconfig
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
184
src/sensor_icm20948.c
Normal 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), ®, 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);
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
# Base config file for rp2040 boards
|
||||
CONFIG_MACH_RPXXXX=y
|
||||
CONFIG_MACH_RP2040=y
|
||||
|
||||
3
test/configs/rp2350.config
Normal file
3
test/configs/rp2350.config
Normal file
@@ -0,0 +1,3 @@
|
||||
# Base config file for rp2350 boards
|
||||
CONFIG_MACH_RPXXXX=y
|
||||
CONFIG_MACH_RP2350=y
|
||||
Reference in New Issue
Block a user