mirror of
https://github.com/Klipper3d/klipper.git
synced 2026-03-05 01:24:41 -07:00
Merge branch 'Klipper3d:master' into feature/vsdlist_sorters
This commit is contained in:
commit
8a953675e4
55 changed files with 1405 additions and 153 deletions
2
.github/workflows/build-test.yaml
vendored
2
.github/workflows/build-test.yaml
vendored
|
|
@ -21,7 +21,7 @@ jobs:
|
|||
run: ./scripts/ci-build.sh 2>&1
|
||||
|
||||
- name: Upload micro-controller data dictionaries
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: data-dict
|
||||
path: ci_build/dict
|
||||
|
|
|
|||
15
README.md
15
README.md
|
|
@ -4,15 +4,14 @@ Welcome to the Klipper project!
|
|||
|
||||
https://www.klipper3d.org/
|
||||
|
||||
Klipper is a 3d-Printer firmware. It combines the power of a general
|
||||
purpose computer with one or more micro-controllers. See the
|
||||
The Klipper firmware controls 3d-Printers. It combines the power of a
|
||||
general purpose computer with one or more micro-controllers. See the
|
||||
[features document](https://www.klipper3d.org/Features.html) for more
|
||||
information on why you should use Klipper.
|
||||
information on why you should use the Klipper software.
|
||||
|
||||
To begin using Klipper start by
|
||||
[installing](https://www.klipper3d.org/Installation.html) it.
|
||||
Start by [installing Klipper software](https://www.klipper3d.org/Installation.html).
|
||||
|
||||
Klipper is Free Software. See the [license](COPYING) or read the
|
||||
[documentation](https://www.klipper3d.org/Overview.html). We depend on
|
||||
the generous support from our
|
||||
Klipper software is Free Software. See the [license](COPYING) or read
|
||||
the [documentation](https://www.klipper3d.org/Overview.html). We
|
||||
depend on the generous support from our
|
||||
[sponsors](https://www.klipper3d.org/Sponsors.html).
|
||||
|
|
|
|||
|
|
@ -95,4 +95,4 @@ max_z_accel: 100
|
|||
aliases:
|
||||
EXP1_1=PC6,EXP1_3=PB10,EXP1_5=PB14,EXP1_7=PB12,EXP1_9=<GND>,
|
||||
EXP1_2=PB2,EXP1_4=PB11,EXP1_6=PB13,EXP1_8=PB15,EXP1_10=<5V>,
|
||||
PROBE_IN=PB0,PROBE_OUT=PB1,FIL_RUNOUT=PC6
|
||||
PROBE_IN=PB0,PROBE_OUT=PB1,FIL_RUNOUT=PA4
|
||||
|
|
|
|||
256
config/printer-geeetech-A10T-A20T-2021.cfg
Normal file
256
config/printer-geeetech-A10T-A20T-2021.cfg
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
# This file contains common pin mappings for the Geeetech GT2560 v4.0 and v4.1b
|
||||
# boards. These boards use a firmware compiled for the AVR atmega2560.
|
||||
# For default Geeetech A10/A20 (1 extruder),
|
||||
# A10M/A20M (mixing 2 in 1 out),
|
||||
# A10T/A20T (mixing 3 in 1 out) printers
|
||||
# Installation: https://www.klipper3d.org/Installation.html
|
||||
# Always read for first start: https://www.klipper3d.org/Config_checks.html
|
||||
|
||||
[mcu]
|
||||
# Might need to be changed: https://www.klipper3d.org/Installation.html
|
||||
serial: /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0
|
||||
|
||||
[printer]
|
||||
kinematics: cartesian
|
||||
max_velocity: 200
|
||||
max_accel: 1500
|
||||
max_z_velocity: 20
|
||||
max_z_accel: 500
|
||||
|
||||
# # uncomment for BLTouch/3DTouch
|
||||
# [bltouch]
|
||||
# sensor_pin: PC7 # there is an external pull up so no need in ^
|
||||
# control_pin: PB5
|
||||
# speed: 3.0
|
||||
# samples: 2
|
||||
# x_offset: -42.0
|
||||
# y_offset: -1.0
|
||||
# z_offset: 1.0 # during calibration this line is commented out and new record added at the end of file
|
||||
|
||||
[safe_z_home]
|
||||
home_xy_position: 100, 100 # Change coordinates to the center of your print bed
|
||||
speed: 50
|
||||
z_hop: 10 # Move up 10mm
|
||||
z_hop_speed: 5
|
||||
|
||||
[stepper_x]
|
||||
enable_pin: !PC2
|
||||
dir_pin: !PG2
|
||||
step_pin: PC0
|
||||
microsteps: 16
|
||||
rotation_distance: 40
|
||||
endstop_pin: !PA2 # there are external pull ups
|
||||
position_endstop: 0
|
||||
position_max: 220 # for A10/M/T / change to 250 for A20/M/T
|
||||
homing_speed: 40
|
||||
|
||||
[stepper_y]
|
||||
enable_pin: !PA7
|
||||
dir_pin: !PC4
|
||||
step_pin: PC6
|
||||
microsteps: 16
|
||||
rotation_distance: 40
|
||||
endstop_pin: !PA6 # there are external pull ups
|
||||
position_endstop: 0
|
||||
position_max: 220 # for A10/M/T / change to 250 for A20/M/T
|
||||
homing_speed: 40
|
||||
|
||||
[stepper_z]
|
||||
enable_pin: !PA5
|
||||
dir_pin: PA1
|
||||
step_pin: PA3
|
||||
microsteps: 16
|
||||
rotation_distance: 8
|
||||
#endstop_pin: probe:z_virtual_endstop # uncomment for BLTouch/3DTouch
|
||||
endstop_pin: !PC7 # comment for BLTouch/3DTouch
|
||||
position_endstop: 0 # comment for BLTouch/3DTouch
|
||||
position_max: 230 # for A10/M/T / change to 250 for A20/M/T
|
||||
position_min: -5
|
||||
homing_speed: 20
|
||||
|
||||
[extruder]
|
||||
enable_pin: !PB6
|
||||
dir_pin: PL5
|
||||
step_pin: PL3
|
||||
microsteps: 16
|
||||
rotation_distance: 8 # Needs to be optimized: https://www.klipper3d.org/Rotation_Distance.html#calibrating-rotation_distance-on-extruders
|
||||
nozzle_diameter: 0.4
|
||||
filament_diameter: 1.750
|
||||
heater_pin: PB4
|
||||
sensor_type: EPCOS 100K B57560G104F
|
||||
sensor_pin: PK3
|
||||
min_temp: 0
|
||||
max_temp: 250
|
||||
max_extrude_only_distance: 200.0
|
||||
# Parameters for stock hotend on A10M
|
||||
# Please recalibrate according to https://www.klipper3d.org/Config_checks.html#calibrate-pid-settings
|
||||
control: pid
|
||||
pid_kp: 54.722
|
||||
pid_ki: 4.800
|
||||
pid_kd: 155.958
|
||||
|
||||
[extruder_stepper extruder_1]
|
||||
extruder:
|
||||
enable_pin: !PL1
|
||||
dir_pin: PL2
|
||||
step_pin: PL0
|
||||
microsteps: 16
|
||||
rotation_distance: 8 # Needs to be optimized: https://www.klipper3d.org/Rotation_Distance.html#calibrating-rotation_distance-on-extruders
|
||||
|
||||
[extruder_stepper extruder_2]
|
||||
extruder:
|
||||
enable_pin: !PG0
|
||||
dir_pin: PL4
|
||||
step_pin: PL6
|
||||
microsteps: 16
|
||||
rotation_distance: 8 # Needs to be optimized: https://www.klipper3d.org/Rotation_Distance.html#calibrating-rotation_distance-on-extruders
|
||||
|
||||
[heater_bed]
|
||||
heater_pin: PG5
|
||||
sensor_type: EPCOS 100K B57560G104F
|
||||
sensor_pin: PK2
|
||||
min_temp: 0
|
||||
max_temp: 120
|
||||
# Parameters for `SuperPlate` on A10M
|
||||
# Please recalibrate according to https://www.klipper3d.org/Config_checks.html#calibrate-pid-settings
|
||||
control: pid
|
||||
pid_kp: 70.936
|
||||
pid_ki: 1.785
|
||||
pid_kd: 704.924
|
||||
|
||||
[fan]
|
||||
pin: PH6
|
||||
cycle_time: 0.150
|
||||
kick_start_time: 0.300
|
||||
|
||||
# # for GT2560V4.0 with 20pin flat cable toward the display
|
||||
# [display]
|
||||
# lcd_type: hd44780
|
||||
# hd44780_protocol_init: True
|
||||
# rs_pin: PD1
|
||||
# e_pin: PH0
|
||||
# d4_pin: PH1
|
||||
# d5_pin: PD0
|
||||
# d6_pin: PE3
|
||||
# d7_pin: PC1
|
||||
# encoder_pins: ^PG1, ^PL7
|
||||
# click_pin: ^!PD2
|
||||
|
||||
|
||||
# for GT2560V4.1B with 12pin flat cable toward the display YHCB2004-06 ver3.0
|
||||
# the aip31068_spi driver was added to Klipper on 2024-12-02, commit aecb29d2
|
||||
[display]
|
||||
lcd_type: aip31068_spi
|
||||
latch_pin: PE3
|
||||
spi_software_sclk_pin: PD0
|
||||
spi_software_mosi_pin: PC1
|
||||
spi_software_miso_pin: PH7 # any unused pin
|
||||
encoder_pins: ^PH0, ^PH1
|
||||
click_pin: ^!PD2
|
||||
|
||||
|
||||
[filament_switch_sensor sensor_e0]
|
||||
switch_pin: !PK4
|
||||
|
||||
[filament_switch_sensor sensor_e1]
|
||||
switch_pin: !PK5
|
||||
|
||||
[filament_switch_sensor sensor_e2]
|
||||
# switch_pin: !PE2 # for GT2560V4.0
|
||||
switch_pin: !PF0 # for GT2560V4.1B
|
||||
|
||||
# to enable M118 echo command
|
||||
[respond]
|
||||
|
||||
# Specific macros for mixing colors.
|
||||
# Add in slicer new filament color and in filament start G-Code add desired mixing factor:
|
||||
# M163 S0 P50 ; set extruder 0 to 50%
|
||||
# M163 S1 P40 ; set extruder 1 to 40%
|
||||
# M163 S2 P10 ; set extruder 2 to 10%
|
||||
# M164 ; commit the mix factors
|
||||
[gcode_macro M163]
|
||||
description: M163 [P<factor>] [S<index>] Set a single mix factor (in proportion to the sum total of all mix factors). The mix must be committed to a virtual tool by M164 before it takes effect.
|
||||
gcode:
|
||||
{% if 'P' in params %}
|
||||
{% set s = params.S|default(0)| int %}
|
||||
{% if s == 0 %}
|
||||
SET_GCODE_VARIABLE MACRO=M164 VARIABLE=e0_parts VALUE={params.P|default(0)|float}
|
||||
M118 Set Mixing factor for extruder 0 to {params.P|default(0)|float}
|
||||
{% elif s == 1 %}
|
||||
SET_GCODE_VARIABLE MACRO=M164 VARIABLE=e1_parts VALUE={params.P|default(0)|float}
|
||||
M118 Set Mixing factor for extruder 1 to {params.P|default(0)|float}
|
||||
{% elif s == 2 %}
|
||||
SET_GCODE_VARIABLE MACRO=M164 VARIABLE=e2_parts VALUE={params.P|default(0)|float}
|
||||
M118 Set Mixing factor for extruder 2 to {params.P|default(0)|float}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
M118 No Mixing factor set, missing value for P
|
||||
{% endif %}
|
||||
M118 {e0_parts} {e1_parts} {e2_parts}
|
||||
|
||||
|
||||
[gcode_macro M164]
|
||||
description: Applies the set mixing factors to the extruders
|
||||
# default values:
|
||||
variable_e0_parts : 100
|
||||
variable_e1_parts : 0
|
||||
variable_e2_parts : 0
|
||||
gcode:
|
||||
# normalize the parts to sum of 1
|
||||
{% set e0 = e0_parts / (e0_parts + e1_parts + e2_parts) | float %}
|
||||
{% set e1 = e1_parts / (e0_parts + e1_parts + e2_parts) | float %}
|
||||
{% set e2 = e2_parts / (e0_parts + e1_parts + e2_parts) | float %}
|
||||
M118 scaled rot-dist_e0 { printer.configfile.settings.extruder.rotation_distance / (e0 + 0.000001) | float }
|
||||
M118 scaled rot-dist_e1 { printer.configfile.settings['extruder_stepper extruder_1'].rotation_distance / (e1 + 0.000001) | float }
|
||||
M118 scaled rot-dist_e2 { printer.configfile.settings['extruder_stepper extruder_2'].rotation_distance / (e2 + 0.000001) |float }
|
||||
# activate stepper percentages
|
||||
SYNC_EXTRUDER_MOTION EXTRUDER=extruder MOTION_QUEUE=extruder
|
||||
SYNC_EXTRUDER_MOTION EXTRUDER=extruder_1 MOTION_QUEUE=extruder
|
||||
SYNC_EXTRUDER_MOTION EXTRUDER=extruder_2 MOTION_QUEUE=extruder
|
||||
SET_EXTRUDER_ROTATION_DISTANCE EXTRUDER=extruder DISTANCE={ printer.configfile.settings.extruder.rotation_distance / (e0+0.000001)|float }
|
||||
SET_EXTRUDER_ROTATION_DISTANCE EXTRUDER=extruder_1 DISTANCE={ printer.configfile.settings['extruder_stepper extruder_1'].rotation_distance / (e1+0.000001)|float }
|
||||
SET_EXTRUDER_ROTATION_DISTANCE EXTRUDER=extruder_2 DISTANCE={ printer.configfile.settings['extruder_stepper extruder_2'].rotation_distance / (e2+0.000001)|float }
|
||||
M118 Mixing factors {e0} {e1} {e2} are activated
|
||||
|
||||
# In PrusaSlicer:
|
||||
# - you can add as many extruders as mixing ratios you want
|
||||
# - in Printer Settings -> Custom G-code -> Tool change G-code add:
|
||||
# TOOL_CHANGE EXTRUDER={next_extruder}
|
||||
# - in this config file add:
|
||||
# [gcode_macro TOOL_CHANGE]
|
||||
# description: Tool change macro with mix ratio setup for 11 extruders
|
||||
# variable_extruder: 0
|
||||
# gcode:
|
||||
# {% set extruder = params.EXTRUDER|default(0)| int %}
|
||||
# {% if extruder == 0 %}
|
||||
# M163 S0 P100
|
||||
# M163 S1 P0
|
||||
# M163 S2 P0
|
||||
# M164
|
||||
# M118 Switching to Extruder 0
|
||||
# {% elif extruder == 1 %}
|
||||
# M163 S0 P90
|
||||
# M163 S1 P10
|
||||
# M163 S2P0
|
||||
# M164
|
||||
# M118 Switching to Extruder 1
|
||||
# {% elif extruder == 2 %}
|
||||
# # and so on ...
|
||||
# {% else %}
|
||||
# M118 Unknown extruder number: {extruder}
|
||||
# {% endif %}
|
||||
|
||||
# In OrcaSlicer:
|
||||
# you can add as many filaments as mixing ratios you want
|
||||
# in Material settings -> Advanced -> Filament start G-code add desired mixing ratio:
|
||||
# ; filament start gcode
|
||||
# M163 S0 P100 ; set extruder 0
|
||||
# M163 S1 P0 ; set extruder 1
|
||||
# M163 S2 P0 ; set extruder 2
|
||||
# M164 ; commit the mix factors
|
||||
|
||||
# For gradient over Z axis:
|
||||
# In `Printer -> Custom G-code -> After layer change G-code` add:
|
||||
# M163 S0 P{ layer_num * 100 / total_layer_count } ; Gradient 0-100
|
||||
# M163 S1 P{(total_layer_count-layer_num) * 100 / total_layer_count} ; Gradient 100-0
|
||||
# M164 ; commit the mix factors
|
||||
|
|
@ -359,10 +359,10 @@ Useful steps:
|
|||
be efficient as it is typically only called during homing and
|
||||
probing operations.
|
||||
5. Other methods. Implement the `check_move()`, `get_status()`,
|
||||
`get_steppers()`, `home()`, and `set_position()` methods. These
|
||||
functions are typically used to provide kinematic specific checks.
|
||||
However, at the start of development one can use boiler-plate code
|
||||
here.
|
||||
`get_steppers()`, `home()`, `clear_homing_state()`, and `set_position()`
|
||||
methods. These functions are typically used to provide kinematic
|
||||
specific checks. However, at the start of development one can use
|
||||
boiler-plate code here.
|
||||
6. Implement test cases. Create a g-code file with a series of moves
|
||||
that can test important cases for the given kinematics. Follow the
|
||||
[debugging documentation](Debugging.md) to convert this g-code file
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ All dates in this document are approximate.
|
|||
|
||||
## Changes
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -3846,6 +3846,7 @@ run_current:
|
|||
#driver_SEIMIN: 0
|
||||
#driver_SFILT: 0
|
||||
#driver_SG4_ANGLE_OFFSET: 1
|
||||
#driver_SLOPE_CONTROL: 0
|
||||
# Set the given register during the configuration of the TMC2240
|
||||
# chip. This may be used to set custom motor parameters. The
|
||||
# defaults for each parameter are next to the parameter name in the
|
||||
|
|
@ -4950,6 +4951,50 @@ vssa_pin:
|
|||
# noise. The default is 2 seconds.
|
||||
```
|
||||
|
||||
### [ads1x1x]
|
||||
|
||||
ADS1013, ADS1014, ADS1015, ADS1113, ADS1114 and ADS1115 are I2C based Analog to
|
||||
Digital Converters that can be used for temperature sensors. They provide 4
|
||||
analog input pins either as single line or as differential input.
|
||||
|
||||
Note: Use caution if using this sensor to control heaters. The heater min_temp
|
||||
and max_temp are only verified in the host and only if the host is running and
|
||||
operating normally. (ADC inputs directly connected to the micro-controller
|
||||
verify min_temp and max_temp within the micro-controller and do not require a
|
||||
working connection to the host.)
|
||||
|
||||
```
|
||||
[ads1x1x my_ads1x1x]
|
||||
chip: ADS1115
|
||||
#pga: 4.096V
|
||||
# Default value is 4.096V. The maximum voltage range used for the input. This
|
||||
# scales all values read from the ADC. Options are: 6.144V, 4.096V, 2.048V,
|
||||
# 1.024V, 0.512V, 0.256V
|
||||
#adc_voltage: 3.3
|
||||
# The suppy voltage for the device. This allows additional software scaling
|
||||
# for all values read from the ADC.
|
||||
i2c_mcu: host
|
||||
i2c_bus: i2c.1
|
||||
#address_pin: GND
|
||||
# Default value is GND. There can be up to four addressed devices depending
|
||||
# upon wiring of the device. Check the datasheet for details. The i2c_address
|
||||
# can be specified directly instead of using the address_pin.
|
||||
```
|
||||
|
||||
The chip provides pins that can be used on other sensors.
|
||||
|
||||
```
|
||||
sensor_type: ...
|
||||
# Can be any thermistor or adc_temperature.
|
||||
sensor_pin: my_ads1x1x:AIN0
|
||||
# A combination of the name of the ads1x1x chip and the pin. Possible
|
||||
# pin values are AIN0, AIN1, AIN2 and AIN3 for single ended lines and
|
||||
# DIFF01, DIFF03, DIFF13 and DIFF23 for differential between their
|
||||
# correspoding lines. For example
|
||||
# DIFF03 measures the differential between line 0 and 3. Only specific
|
||||
# combinations for the differentials are allowed.
|
||||
```
|
||||
|
||||
### [replicape]
|
||||
|
||||
Replicape support - see the [beaglebone guide](Beaglebone.md) and the
|
||||
|
|
|
|||
|
|
@ -132,3 +132,10 @@ There are several
|
|||
you have questions on the code then you can also ask in the
|
||||
[Klipper Discourse Forum](#discourse-forum) or on the
|
||||
[Klipper Discord Chat](#discord-chat).
|
||||
|
||||
## Professional Services
|
||||
|
||||

|
||||
|
||||
Custom software development, software support, and solutions:
|
||||
[https://ko-fi.com/koconnor](https://ko-fi.com/koconnor)
|
||||
|
|
|
|||
|
|
@ -585,15 +585,18 @@ 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>]`: 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. This command may invalidate
|
||||
future boundary checks; issue a G28 afterwards to reset the
|
||||
kinematics.
|
||||
`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.
|
||||
|
||||
### [gcode]
|
||||
|
||||
|
|
@ -1198,8 +1201,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.
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ their own Klipper-centric images.
|
|||
|
||||
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
|
||||
image ["MainsailOS"](https://docs-os.mainsail.xyz/), this has the option for Raspberry Pi
|
||||
and some OrangePi varianta.
|
||||
|
||||
Fluidd can be installed via KIAUH(Klipper Install And Update Helper), which
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# Python virtualenv module requirements for mkdocs
|
||||
jinja2==3.1.4
|
||||
jinja2==3.1.5
|
||||
mkdocs==1.2.4
|
||||
mkdocs-material==8.1.3
|
||||
mkdocs-simple-hooks==0.1.3
|
||||
|
|
|
|||
|
|
@ -6,13 +6,16 @@ title: Welcome
|
|||
|
||||
{ .center-image }
|
||||
|
||||
Klipper is a 3d-Printer firmware. It combines the power of a general
|
||||
purpose computer with one or more micro-controllers. See the
|
||||
[features](Features.md) document for more information on why you
|
||||
should use Klipper.
|
||||
The Klipper firmware controls 3d-Printers. It combines the power of a
|
||||
general purpose computer with one or more micro-controllers. See the
|
||||
[features document](https://www.klipper3d.org/Features.html) for more
|
||||
information on why you should use the Klipper software.
|
||||
|
||||
To begin using Klipper start by [installing](Installation.md) it.
|
||||
Start by [installing Klipper software](https://www.klipper3d.org/Installation.html).
|
||||
|
||||
Klipper is Free Software. Read the [documentation](Overview.md) or
|
||||
view [the Klipper code on github](https://github.com/Klipper3d/klipper).
|
||||
We depend on the generous support from our [sponsors](Sponsors.md).
|
||||
Klipper software is Free Software. Read the
|
||||
[documentation](https://www.klipper3d.org/Overview.html), see the
|
||||
[license](COPYING), or
|
||||
[download](https://github.com/Klipper3d/Klipper) the software. We
|
||||
depend on the generous support from our
|
||||
[sponsors](https://www.klipper3d.org/Sponsors.html).
|
||||
|
|
|
|||
393
klippy/extras/ads1x1x.py
Normal file
393
klippy/extras/ads1x1x.py
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
# Support for I2C based ADS1013, ADS1014, ADS1015, ADS1113, ADS1114 and ADS1115
|
||||
#
|
||||
# Copyright (C) 2024 Konstantin Koch <korsarnek@gmail.com>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import logging
|
||||
import pins
|
||||
from . import bus
|
||||
|
||||
# Supported chip types
|
||||
ADS1X1X_CHIP_TYPE = {
|
||||
'ADS1013': 3,
|
||||
'ADS1014': 4,
|
||||
'ADS1015': 5,
|
||||
'ADS1113': 13,
|
||||
'ADS1114': 14,
|
||||
'ADS1115': 15
|
||||
}
|
||||
|
||||
def isADS101X(chip):
|
||||
return (chip == ADS1X1X_CHIP_TYPE['ADS1013'] \
|
||||
or chip == ADS1X1X_CHIP_TYPE['ADS1014'] \
|
||||
or chip == ADS1X1X_CHIP_TYPE['ADS1015'])
|
||||
|
||||
def isADS111X(chip):
|
||||
return (chip == ADS1X1X_CHIP_TYPE['ADS1113'] \
|
||||
or chip == ADS1X1X_CHIP_TYPE['ADS1114'] \
|
||||
or chip == ADS1X1X_CHIP_TYPE['ADS1115'])
|
||||
|
||||
# Address is defined by how the address pin is wired
|
||||
ADS1X1X_CHIP_ADDR = {
|
||||
'GND': 0x48,
|
||||
'VCC': 0x49,
|
||||
'SDA': 0x4a,
|
||||
'SCL': 0x4b
|
||||
}
|
||||
|
||||
# Chip "pointer" registers
|
||||
ADS1X1X_REG_POINTER_MASK = 0x03
|
||||
ADS1X1X_REG_POINTER = {
|
||||
'CONVERSION': 0x00,
|
||||
'CONFIG': 0x01,
|
||||
'LO_THRESH': 0x02,
|
||||
'HI_THRESH': 0x03
|
||||
}
|
||||
|
||||
# Config register masks
|
||||
ADS1X1X_REG_CONFIG = {
|
||||
'OS_MASK': 0x8000,
|
||||
'MULTIPLEXER_MASK': 0x7000,
|
||||
'PGA_MASK': 0x0E00,
|
||||
'MODE_MASK': 0x0100,
|
||||
'DATA_RATE_MASK': 0x00E0,
|
||||
'COMPARATOR_MODE_MASK': 0x0010,
|
||||
'COMPARATOR_POLARITY_MASK': 0x0008,
|
||||
# Determines if ALERT/RDY pin latches once asserted
|
||||
'COMPARATOR_LATCHING_MASK': 0x0004,
|
||||
'COMPARATOR_QUEUE_MASK': 0x0003
|
||||
}
|
||||
|
||||
#
|
||||
# The following enums are to be used with the configuration functions.
|
||||
#
|
||||
ADS1X1X_OS = {
|
||||
'OS_IDLE': 0x8000, # Device is not performing a conversion
|
||||
'OS_SINGLE': 0x8000 # Single-conversion
|
||||
}
|
||||
|
||||
ADS1X1X_MUX = {
|
||||
'DIFF01': 0x0000, # Differential P = AIN0, N = AIN1 0
|
||||
'DIFF03': 0x1000, # Differential P = AIN0, N = AIN3 4096
|
||||
'DIFF13': 0x2000, # Differential P = AIN1, N = AIN3 8192
|
||||
'DIFF23': 0x3000, # Differential P = AIN2, N = AIN3 12288
|
||||
'AIN0': 0x4000, # Single-ended (ADS1015: AIN0 16384)
|
||||
'AIN1': 0x5000, # Single-ended (ADS1015: AIN1 20480)
|
||||
'AIN2': 0x6000, # Single-ended (ADS1015: AIN2 24576)
|
||||
'AIN3': 0x7000 # Single-ended (ADS1015: AIN3 28672)
|
||||
}
|
||||
|
||||
ADS1X1X_PGA = {
|
||||
'6.144V': 0x0000, # +/-6.144V range = Gain 2/3
|
||||
'4.096V': 0x0200, # +/-4.096V range = Gain 1
|
||||
'2.048V': 0x0400, # +/-2.048V range = Gain 2
|
||||
'1.024V': 0x0600, # +/-1.024V range = Gain 4
|
||||
'0.512V': 0x0800, # +/-0.512V range = Gain 8
|
||||
'0.256V': 0x0A00 # +/-0.256V range = Gain 16
|
||||
}
|
||||
ADS1X1X_PGA_VALUE = {
|
||||
0x0000: 6.144,
|
||||
0x0200: 4.096,
|
||||
0x0400: 2.048,
|
||||
0x0600: 1.024,
|
||||
0x0800: 0.512,
|
||||
0x0A00: 0.256,
|
||||
}
|
||||
ADS111X_RESOLUTION = 32767.0
|
||||
ADS111X_PGA_SCALAR = {
|
||||
0x0000: 6.144 / ADS111X_RESOLUTION, # +/-6.144V range = Gain 2/3
|
||||
0x0200: 4.096 / ADS111X_RESOLUTION, # +/-4.096V range = Gain 1
|
||||
0x0400: 2.048 / ADS111X_RESOLUTION, # +/-2.048V range = Gain 2
|
||||
0x0600: 1.024 / ADS111X_RESOLUTION, # +/-1.024V range = Gain 4
|
||||
0x0800: 0.512 / ADS111X_RESOLUTION, # +/-0.512V range = Gain 8
|
||||
0x0A00: 0.256 / ADS111X_RESOLUTION # +/-0.256V range = Gain 16
|
||||
}
|
||||
ADS101X_RESOLUTION = 2047.0
|
||||
ADS101X_PGA_SCALAR = {
|
||||
0x0000: 6.144 / ADS101X_RESOLUTION, # +/-6.144V range = Gain 2/3
|
||||
0x0200: 4.096 / ADS101X_RESOLUTION, # +/-4.096V range = Gain 1
|
||||
0x0400: 2.048 / ADS101X_RESOLUTION, # +/-2.048V range = Gain 2
|
||||
0x0600: 1.024 / ADS101X_RESOLUTION, # +/-1.024V range = Gain 4
|
||||
0x0800: 0.512 / ADS101X_RESOLUTION, # +/-0.512V range = Gain 8
|
||||
0x0A00: 0.256 / ADS101X_RESOLUTION # +/-0.256V range = Gain 16
|
||||
}
|
||||
ADS1X1X_MODE = {
|
||||
'continuous': 0x0000, # Continuous conversion mode
|
||||
'single': 0x0100 # Power-down single-shot mode
|
||||
}
|
||||
|
||||
# Lesser samples per second means it takes and averages more samples before
|
||||
# returning a result.
|
||||
ADS101X_SAMPLES_PER_SECOND = {
|
||||
'128': 0x0000, # 128 samples per second
|
||||
'250': 0x0020, # 250 samples per second
|
||||
'490': 0x0040, # 490 samples per second
|
||||
'920': 0x0060, # 920 samples per second
|
||||
'1600': 0x0080, # 1600 samples per second
|
||||
'2400': 0x00a0, # 2400 samples per second
|
||||
'3300': 0x00c0, # 3300 samples per second
|
||||
}
|
||||
|
||||
ADS111X_SAMPLES_PER_SECOND = {
|
||||
'8': 0x0000, # 8 samples per second
|
||||
'16': 0x0020, # 16 samples per second
|
||||
'32': 0x0040, # 32 samples per second
|
||||
'64': 0x0060, # 64 samples per second
|
||||
'128': 0x0080, # 128 samples per second
|
||||
'250': 0x00a0, # 250 samples per second
|
||||
'475': 0x00c0, # 475 samples per second
|
||||
'860': 0x00e0 # 860 samples per second
|
||||
}
|
||||
|
||||
ADS1X1X_COMPARATOR_MODE = {
|
||||
'TRADITIONAL': 0x0000, # Traditional comparator with hysteresis
|
||||
'WINDOW': 0x0010 # Window comparator
|
||||
}
|
||||
|
||||
ADS1X1X_COMPARATOR_POLARITY = {
|
||||
'ACTIVE_LO': 0x0000, # ALERT/RDY pin is low when active
|
||||
'ACTIVE_HI': 0x0008 # ALERT/RDY pin is high when active
|
||||
}
|
||||
|
||||
ADS1X1X_COMPARATOR_LATCHING = {
|
||||
'NON_LATCHING': 0x0000, # Non-latching comparator
|
||||
'LATCHING': 0x0004 # Latching comparator
|
||||
}
|
||||
|
||||
ADS1X1X_COMPARATOR_QUEUE = {
|
||||
'QUEUE_1': 0x0000, # Assert ALERT/RDY after one conversions
|
||||
'QUEUE_2': 0x0001, # Assert ALERT/RDY after two conversions
|
||||
'QUEUE_4': 0x0002, # Assert ALERT/RDY after four conversions
|
||||
'QUEUE_NONE': 0x0003 # Disable the comparator and put ALERT/RDY
|
||||
# in high state
|
||||
}
|
||||
|
||||
ADS1X1_OPERATIONS = {
|
||||
'SET_MUX': 0,
|
||||
'READ_CONVERSION': 1
|
||||
}
|
||||
|
||||
class ADS1X1X_chip:
|
||||
|
||||
def __init__(self, config):
|
||||
self._printer = config.get_printer()
|
||||
self._reactor = self._printer.get_reactor()
|
||||
|
||||
self.name = config.get_name().split()[-1]
|
||||
self.chip = config.getchoice('chip', ADS1X1X_CHIP_TYPE)
|
||||
address = ADS1X1X_CHIP_ADDR['GND']
|
||||
# If none is specified, i2c_address can be used for a specific address
|
||||
if config.get('address_pin', None) is not None:
|
||||
address = config.getchoice('address_pin', ADS1X1X_CHIP_ADDR)
|
||||
|
||||
self._ppins = self._printer.lookup_object("pins")
|
||||
self._ppins.register_chip(self.name, self)
|
||||
|
||||
self.pga = config.getchoice('pga', ADS1X1X_PGA, '4.096V')
|
||||
self.adc_voltage = config.getfloat('adc_voltage', above=0., default=3.3)
|
||||
# Comparators are not implemented, they would only be useful if the
|
||||
# alert pin is used, which we haven't made configurable.
|
||||
# But that wouldn't be useful for a normal temperature sensor anyway.
|
||||
self.comp_mode = ADS1X1X_COMPARATOR_MODE['TRADITIONAL']
|
||||
self.comp_polarity = ADS1X1X_COMPARATOR_POLARITY['ACTIVE_LO']
|
||||
self.comp_latching = ADS1X1X_COMPARATOR_LATCHING['NON_LATCHING']
|
||||
self.comp_queue = ADS1X1X_COMPARATOR_QUEUE['QUEUE_NONE']
|
||||
self._i2c = bus.MCU_I2C_from_config(config, address)
|
||||
|
||||
self.mcu = self._i2c.get_mcu()
|
||||
|
||||
self._printer.add_object("ads1x1x " + self.name, self)
|
||||
self._printer.register_event_handler("klippy:connect", \
|
||||
self._handle_connect)
|
||||
|
||||
self._pins = {}
|
||||
self._mutex = self._reactor.mutex()
|
||||
|
||||
def setup_pin(self, pin_type, pin_params):
|
||||
pin = pin_params['pin']
|
||||
if pin_type == 'adc':
|
||||
if (pin not in ADS1X1X_MUX):
|
||||
raise pins.error('ADS1x1x pin %s is not valid' % \
|
||||
pin_params['pin'])
|
||||
|
||||
config = 0
|
||||
config |= (ADS1X1X_OS['OS_SINGLE'] & \
|
||||
ADS1X1X_REG_CONFIG['OS_MASK'])
|
||||
config |= (ADS1X1X_MUX[pin_params['pin']] & \
|
||||
ADS1X1X_REG_CONFIG['MULTIPLEXER_MASK'])
|
||||
config |= (self.pga & ADS1X1X_REG_CONFIG['PGA_MASK'])
|
||||
# Have to use single mode, because in continuous, it never reaches
|
||||
# idle state, which we use to determine if the sampling is done.
|
||||
config |= (ADS1X1X_MODE['single'] & \
|
||||
ADS1X1X_REG_CONFIG['MODE_MASK'])
|
||||
# lowest sample rate per default, until report time has been set in
|
||||
# setup_adc_sample
|
||||
config |= (self.comp_mode \
|
||||
& ADS1X1X_REG_CONFIG['COMPARATOR_MODE_MASK'])
|
||||
config |= (self.comp_polarity \
|
||||
& ADS1X1X_REG_CONFIG['COMPARATOR_POLARITY_MASK'])
|
||||
config |= (self.comp_latching \
|
||||
& ADS1X1X_REG_CONFIG['COMPARATOR_LATCHING_MASK'])
|
||||
config |= (self.comp_queue \
|
||||
& ADS1X1X_REG_CONFIG['COMPARATOR_QUEUE_MASK'])
|
||||
|
||||
pin_obj = ADS1X1X_pin(self, config)
|
||||
if pin in self._pins:
|
||||
raise pins.error(
|
||||
'pin %s for chip %s is used multiple times' \
|
||||
% (pin, self.name))
|
||||
self._pins[pin] = pin_obj
|
||||
|
||||
return pin_obj
|
||||
raise pins.error('Wrong pin or incompatible type: %s with type %s! ' % (
|
||||
pin, pin_type))
|
||||
|
||||
def _handle_connect(self):
|
||||
try:
|
||||
# Init all devices on bus for this kind of device
|
||||
self._i2c.i2c_write([0x06, 0x00, 0x00])
|
||||
except Exception:
|
||||
logging.exception("ADS1X1X: error while resetting device")
|
||||
|
||||
def is_ready(self):
|
||||
config = self._read_register(ADS1X1X_REG_POINTER['CONFIG'])
|
||||
return bool((config & ADS1X1X_REG_CONFIG['OS_MASK']) == \
|
||||
ADS1X1X_OS['OS_IDLE'])
|
||||
|
||||
def calculate_sample_rate(self):
|
||||
pin_count = len(self._pins)
|
||||
lowest_report_time = 1
|
||||
for pin in self._pins.values():
|
||||
lowest_report_time = min(lowest_report_time, pin.report_time)
|
||||
|
||||
sample_rate = 1 / lowest_report_time * pin_count
|
||||
samples_per_second = ADS111X_SAMPLES_PER_SECOND
|
||||
if isADS101X(self.chip):
|
||||
samples_per_second = ADS101X_SAMPLES_PER_SECOND
|
||||
|
||||
# make sure the samples list is sorted correctly by number.
|
||||
samples_per_second = sorted(samples_per_second.items(), \
|
||||
key=lambda t: int(t[0]))
|
||||
for rate, bits in samples_per_second:
|
||||
rate_number = int(rate)
|
||||
if sample_rate <= rate_number:
|
||||
return (rate_number, bits)
|
||||
logging.warning(
|
||||
"ADS1X1X: requested sample rate %s is higher than supported by %s."\
|
||||
% (sample_rate, self.name))
|
||||
return (rate_number, bits)
|
||||
|
||||
def handle_report_time_update(self):
|
||||
(sample_rate, sample_rate_bits) = self.calculate_sample_rate()
|
||||
|
||||
for pin in self._pins.values():
|
||||
pin.config = (pin.config & ~ADS1X1X_REG_CONFIG['DATA_RATE_MASK']) \
|
||||
| (sample_rate_bits & ADS1X1X_REG_CONFIG['DATA_RATE_MASK'])
|
||||
|
||||
self.delay = 1 / float(sample_rate)
|
||||
|
||||
def sample(self, pin):
|
||||
with self._mutex:
|
||||
try:
|
||||
self._write_register(ADS1X1X_REG_POINTER['CONFIG'], pin.config)
|
||||
self._reactor.pause(self._reactor.monotonic() + self.delay)
|
||||
start_time = self._reactor.monotonic()
|
||||
while not self.is_ready():
|
||||
self._reactor.pause(self._reactor.monotonic() + 0.001)
|
||||
# if we waited twice the expected time, mark this an error
|
||||
if start_time + self.delay < self._reactor.monotonic():
|
||||
logging.warning("ADS1X1X: timeout during sampling")
|
||||
return None
|
||||
return self._read_register(ADS1X1X_REG_POINTER['CONVERSION'])
|
||||
except Exception as e:
|
||||
logging.exception("ADS1X1X: error while sampling: %s" % str(e))
|
||||
return None
|
||||
|
||||
def _read_register(self, reg):
|
||||
# read a single register
|
||||
params = self._i2c.i2c_read([reg], 2)
|
||||
buff = bytearray(params['response'])
|
||||
return (buff[0]<<8 | buff[1])
|
||||
|
||||
def _write_register(self, reg, data):
|
||||
data = [
|
||||
(reg & 0xFF), # Control register
|
||||
((data>>8) & 0xFF), # High byte
|
||||
(data & 0xFF), # Lo byte
|
||||
]
|
||||
self._i2c.i2c_write(data)
|
||||
|
||||
class ADS1X1X_pin:
|
||||
def __init__(self, chip, config):
|
||||
self.mcu = chip.mcu
|
||||
self.chip = chip
|
||||
self.config = config
|
||||
|
||||
self.invalid_count = 0
|
||||
|
||||
self.chip._printer.register_event_handler("klippy:connect", \
|
||||
self._handle_connect)
|
||||
|
||||
def _handle_connect(self):
|
||||
self._reactor = self.chip._printer.get_reactor()
|
||||
self._sample_timer = \
|
||||
self._reactor.register_timer(self._process_sample, \
|
||||
self._reactor.NOW)
|
||||
|
||||
def _process_sample(self, eventtime):
|
||||
sample = self.chip.sample(self)
|
||||
if sample is not None:
|
||||
# The sample is encoded in the top 12 or full 16 bits
|
||||
# Value's meaning is defined by ADS1X1X_REG_CONFIG['PGA_MASK']
|
||||
if isADS101X(self.chip.chip):
|
||||
sample >>= 4
|
||||
target_value = sample / ADS101X_RESOLUTION
|
||||
else:
|
||||
target_value = sample / ADS111X_RESOLUTION
|
||||
|
||||
# Thermistors expect a value between 0 and 1 to work. If we use a
|
||||
# PGA with 4.096V but supply only 3.3V, the reference voltage for
|
||||
# voltage divider is only 3.3V, not 4.096V. So we remap the range
|
||||
# from what the PGA allows as range to end up between 0 and 1 for
|
||||
# the thermistor logic to work as expected.
|
||||
target_value = target_value * (ADS1X1X_PGA_VALUE[self.chip.pga] / \
|
||||
self.chip.adc_voltage)
|
||||
|
||||
if target_value > self.maxval or target_value < self.minval:
|
||||
self.invalid_count = self.invalid_count + 1
|
||||
logging.warning("ADS1X1X: temperature outside range")
|
||||
self.check_invalid()
|
||||
else:
|
||||
self.invalid_count = 0
|
||||
|
||||
# Publish result
|
||||
measured_time = self._reactor.monotonic()
|
||||
self.callback(self.chip.mcu.estimated_print_time(measured_time),
|
||||
target_value)
|
||||
else:
|
||||
self.invalid_count = self.invalid_count + 1
|
||||
self.check_invalid()
|
||||
|
||||
return eventtime + self.report_time
|
||||
|
||||
def check_invalid(self):
|
||||
if self.invalid_count > self.range_check_count:
|
||||
self.chip._printer.invoke_shutdown(
|
||||
"ADS1X1X temperature check failed")
|
||||
|
||||
def get_mcu(self):
|
||||
return self.mcu
|
||||
|
||||
def setup_adc_callback(self, report_time, callback):
|
||||
self.report_time = report_time
|
||||
self.callback = callback
|
||||
self.chip.handle_report_time_update()
|
||||
|
||||
def setup_adc_sample(self, sample_time, sample_count,
|
||||
minval=0., maxval=1., range_check_count=0):
|
||||
self.minval = minval
|
||||
self.maxval = maxval
|
||||
self.range_check_count = range_check_count
|
||||
|
||||
def load_config_prefix(config):
|
||||
return ADS1X1X_chip(config)
|
||||
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)
|
||||
|
|
@ -131,8 +131,12 @@ class ForceMove:
|
|||
x = gcmd.get_float('X', curpos[0])
|
||||
y = gcmd.get_float('Y', curpos[1])
|
||||
z = gcmd.get_float('Z', curpos[2])
|
||||
logging.info("SET_KINEMATIC_POSITION pos=%.3f,%.3f,%.3f", x, y, z)
|
||||
toolhead.set_position([x, y, z, curpos[3]], homing_axes=(0, 1, 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)
|
||||
|
||||
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)
|
||||
|
|
@ -14,6 +14,7 @@ KELVIN_TO_CELSIUS = -273.15
|
|||
MAX_HEAT_TIME = 5.0
|
||||
AMBIENT_TEMP = 25.
|
||||
PID_PARAM_BASE = 255.
|
||||
MAX_MAINTHREAD_TIME = 5.0
|
||||
|
||||
class Heater:
|
||||
def __init__(self, config, sensor):
|
||||
|
|
@ -37,7 +38,7 @@ class Heater:
|
|||
self.max_power = config.getfloat('max_power', 1., above=0., maxval=1.)
|
||||
self.smooth_time = config.getfloat('smooth_time', 1., above=0.)
|
||||
self.inv_smooth_time = 1. / self.smooth_time
|
||||
self.is_shutdown = False
|
||||
self.verify_mainthread_time = -999.
|
||||
self.lock = threading.Lock()
|
||||
self.last_temp = self.smoothed_temp = self.target_temp = 0.
|
||||
self.last_temp_time = 0.
|
||||
|
|
@ -66,7 +67,7 @@ class Heater:
|
|||
self.printer.register_event_handler("klippy:shutdown",
|
||||
self._handle_shutdown)
|
||||
def set_pwm(self, read_time, value):
|
||||
if self.target_temp <= 0. or self.is_shutdown:
|
||||
if self.target_temp <= 0. or read_time > self.verify_mainthread_time:
|
||||
value = 0.
|
||||
if ((read_time < self.next_pwm_time or not self.last_pwm_value)
|
||||
and abs(value - self.last_pwm_value) < 0.05):
|
||||
|
|
@ -91,7 +92,7 @@ class Heater:
|
|||
self.can_extrude = (self.smoothed_temp >= self.min_extrude_temp)
|
||||
#logging.debug("temp: %.3f %f = %f", read_time, temp)
|
||||
def _handle_shutdown(self):
|
||||
self.is_shutdown = True
|
||||
self.verify_mainthread_time = -999.
|
||||
# External commands
|
||||
def get_name(self):
|
||||
return self.name
|
||||
|
|
@ -129,6 +130,9 @@ class Heater:
|
|||
target_temp = max(self.min_temp, min(self.max_temp, target_temp))
|
||||
self.target_temp = target_temp
|
||||
def stats(self, eventtime):
|
||||
est_print_time = self.mcu_pwm.get_mcu().estimated_print_time(eventtime)
|
||||
if not self.printer.is_shutdown():
|
||||
self.verify_mainthread_time = est_print_time + MAX_MAINTHREAD_TIME
|
||||
with self.lock:
|
||||
target_temp = self.target_temp
|
||||
last_temp = self.last_temp
|
||||
|
|
|
|||
|
|
@ -187,7 +187,8 @@ class Homing:
|
|||
# Notify of upcoming homing operation
|
||||
self.printer.send_event("homing:home_rails_begin", self, rails)
|
||||
# Alter kinematics class to think printer is at forcepos
|
||||
homing_axes = [axis for axis in range(3) if forcepos[axis] is not None]
|
||||
force_axes = [axis for axis in range(3) if forcepos[axis] is not None]
|
||||
homing_axes = "".join(["xyz"[i] for i in force_axes])
|
||||
startpos = self._fill_coord(forcepos)
|
||||
homepos = self._fill_coord(movepos)
|
||||
self.toolhead.set_position(startpos, homing_axes=homing_axes)
|
||||
|
|
@ -231,7 +232,7 @@ class Homing:
|
|||
+ self.adjust_pos.get(s.get_name(), 0.))
|
||||
for s in kin.get_steppers()}
|
||||
newpos = kin.calc_position(kin_spos)
|
||||
for axis in homing_axes:
|
||||
for axis in force_axes:
|
||||
homepos[axis] = newpos[axis]
|
||||
self.toolhead.set_position(homepos)
|
||||
|
||||
|
|
|
|||
|
|
@ -46,11 +46,11 @@ class HomingOverride:
|
|||
# Calculate forced position (if configured)
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
pos = toolhead.get_position()
|
||||
homing_axes = []
|
||||
homing_axes = ""
|
||||
for axis, loc in enumerate(self.start_pos):
|
||||
if loc is not None:
|
||||
pos[axis] = loc
|
||||
homing_axes.append(axis)
|
||||
homing_axes += "xyz"[axis]
|
||||
toolhead.set_position(pos, homing_axes=homing_axes)
|
||||
# Perform homing
|
||||
context = self.template.create_template_context()
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ class ManualStepper:
|
|||
self.sync_print_time()
|
||||
def get_position(self):
|
||||
return [self.rail.get_commanded_position(), 0., 0., 0.]
|
||||
def set_position(self, newpos, homing_axes=()):
|
||||
def set_position(self, newpos, homing_axes=""):
|
||||
self.do_set_position(newpos[0])
|
||||
def get_last_move_time(self):
|
||||
self.sync_print_time()
|
||||
|
|
|
|||
|
|
@ -37,11 +37,10 @@ class SafeZHoming:
|
|||
if 'z' not in kin_status['homed_axes']:
|
||||
# Always perform the z_hop if the Z axis is not homed
|
||||
pos[2] = 0
|
||||
toolhead.set_position(pos, homing_axes=[2])
|
||||
toolhead.set_position(pos, homing_axes="z")
|
||||
toolhead.manual_move([None, None, self.z_hop],
|
||||
self.z_hop_speed)
|
||||
if hasattr(toolhead.get_kinematics(), "note_z_not_homed"):
|
||||
toolhead.get_kinematics().note_z_not_homed()
|
||||
toolhead.get_kinematics().clear_homing_state("z")
|
||||
elif pos[2] < self.z_hop:
|
||||
# If the Z axis is homed, and below z_hop, lift it to z_hop
|
||||
toolhead.manual_move([None, None, self.z_hop],
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class ScrewsTiltAdjust:
|
|||
self.config = config
|
||||
self.printer = config.get_printer()
|
||||
self.screws = []
|
||||
self.results = []
|
||||
self.results = {}
|
||||
self.max_diff = None
|
||||
self.max_diff_error = False
|
||||
# Read config
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ class PrinterStepperEnable:
|
|||
print_time = toolhead.get_last_move_time()
|
||||
for el in self.enable_lines.values():
|
||||
el.motor_disable(print_time)
|
||||
toolhead.get_kinematics().clear_homing_state("xyz")
|
||||
self.printer.send_event("stepper_enable:motor_off", print_time)
|
||||
toolhead.dwell(DISABLE_STALL_TIME)
|
||||
def motor_debug_enable(self, stepper, enable):
|
||||
|
|
|
|||
|
|
@ -408,6 +408,8 @@ class TMC2240:
|
|||
set_config_field(config, "tpowerdown", 10)
|
||||
# SG4_THRS
|
||||
set_config_field(config, "sg4_angle_offset", 1)
|
||||
# DRV_CONF
|
||||
set_config_field(config, "slope_control", 0)
|
||||
|
||||
def load_config_prefix(config):
|
||||
return TMC2240(config)
|
||||
|
|
|
|||
|
|
@ -40,8 +40,6 @@ class CartKinematics:
|
|||
for s in self.get_steppers():
|
||||
s.set_trapq(toolhead.get_trapq())
|
||||
toolhead.register_step_generator(s.generate_steps)
|
||||
self.printer.register_event_handler("stepper_enable:motor_off",
|
||||
self._motor_off)
|
||||
# Setup boundary checks
|
||||
max_velocity, max_accel = toolhead.get_max_velocity()
|
||||
self.max_z_velocity = config.getfloat('max_z_velocity', max_velocity,
|
||||
|
|
@ -67,15 +65,17 @@ class CartKinematics:
|
|||
def set_position(self, newpos, homing_axes):
|
||||
for i, rail in enumerate(self.rails):
|
||||
rail.set_position(newpos)
|
||||
for axis in homing_axes:
|
||||
for axis_name in homing_axes:
|
||||
axis = "xyz".index(axis_name)
|
||||
if self.dc_module and axis == self.dc_module.axis:
|
||||
rail = self.dc_module.get_primary_rail().get_rail()
|
||||
else:
|
||||
rail = self.rails[axis]
|
||||
self.limits[axis] = rail.get_range()
|
||||
def note_z_not_homed(self):
|
||||
# Helper for Safe Z Home
|
||||
self.limits[2] = (1.0, -1.0)
|
||||
def clear_homing_state(self, clear_axes):
|
||||
for axis, axis_name in enumerate("xyz"):
|
||||
if axis_name in clear_axes:
|
||||
self.limits[axis] = (1.0, -1.0)
|
||||
def home_axis(self, homing_state, axis, rail):
|
||||
# Determine movement
|
||||
position_min, position_max = rail.get_range()
|
||||
|
|
@ -96,8 +96,6 @@ class CartKinematics:
|
|||
self.dc_module.home(homing_state)
|
||||
else:
|
||||
self.home_axis(homing_state, axis, self.rails[axis])
|
||||
def _motor_off(self, print_time):
|
||||
self.limits = [(1.0, -1.0)] * 3
|
||||
def _check_endstops(self, move):
|
||||
end_pos = move.end_pos
|
||||
for i in (0, 1, 2):
|
||||
|
|
|
|||
|
|
@ -21,8 +21,6 @@ class CoreXYKinematics:
|
|||
for s in self.get_steppers():
|
||||
s.set_trapq(toolhead.get_trapq())
|
||||
toolhead.register_step_generator(s.generate_steps)
|
||||
config.get_printer().register_event_handler("stepper_enable:motor_off",
|
||||
self._motor_off)
|
||||
# Setup boundary checks
|
||||
max_velocity, max_accel = toolhead.get_max_velocity()
|
||||
self.max_z_velocity = config.getfloat(
|
||||
|
|
@ -41,11 +39,12 @@ class CoreXYKinematics:
|
|||
def set_position(self, newpos, homing_axes):
|
||||
for i, rail in enumerate(self.rails):
|
||||
rail.set_position(newpos)
|
||||
if i in homing_axes:
|
||||
if "xyz"[i] in homing_axes:
|
||||
self.limits[i] = rail.get_range()
|
||||
def note_z_not_homed(self):
|
||||
# Helper for Safe Z Home
|
||||
self.limits[2] = (1.0, -1.0)
|
||||
def clear_homing_state(self, clear_axes):
|
||||
for axis, axis_name in enumerate("xyz"):
|
||||
if axis_name in clear_axes:
|
||||
self.limits[axis] = (1.0, -1.0)
|
||||
def home(self, homing_state):
|
||||
# Each axis is homed independently and in order
|
||||
for axis in homing_state.get_axes():
|
||||
|
|
@ -62,8 +61,6 @@ class CoreXYKinematics:
|
|||
forcepos[axis] += 1.5 * (position_max - hi.position_endstop)
|
||||
# Perform homing
|
||||
homing_state.home_rails([rail], forcepos, homepos)
|
||||
def _motor_off(self, print_time):
|
||||
self.limits = [(1.0, -1.0)] * 3
|
||||
def _check_endstops(self, move):
|
||||
end_pos = move.end_pos
|
||||
for i in (0, 1, 2):
|
||||
|
|
|
|||
|
|
@ -21,8 +21,6 @@ class CoreXZKinematics:
|
|||
for s in self.get_steppers():
|
||||
s.set_trapq(toolhead.get_trapq())
|
||||
toolhead.register_step_generator(s.generate_steps)
|
||||
config.get_printer().register_event_handler("stepper_enable:motor_off",
|
||||
self._motor_off)
|
||||
# Setup boundary checks
|
||||
max_velocity, max_accel = toolhead.get_max_velocity()
|
||||
self.max_z_velocity = config.getfloat(
|
||||
|
|
@ -41,11 +39,12 @@ class CoreXZKinematics:
|
|||
def set_position(self, newpos, homing_axes):
|
||||
for i, rail in enumerate(self.rails):
|
||||
rail.set_position(newpos)
|
||||
if i in homing_axes:
|
||||
if "xyz"[i] in homing_axes:
|
||||
self.limits[i] = rail.get_range()
|
||||
def note_z_not_homed(self):
|
||||
# Helper for Safe Z Home
|
||||
self.limits[2] = (1.0, -1.0)
|
||||
def clear_homing_state(self, clear_axes):
|
||||
for axis, axis_name in enumerate("xyz"):
|
||||
if axis_name in clear_axes:
|
||||
self.limits[axis] = (1.0, -1.0)
|
||||
def home(self, homing_state):
|
||||
# Each axis is homed independently and in order
|
||||
for axis in homing_state.get_axes():
|
||||
|
|
@ -62,8 +61,6 @@ class CoreXZKinematics:
|
|||
forcepos[axis] += 1.5 * (position_max - hi.position_endstop)
|
||||
# Perform homing
|
||||
homing_state.home_rails([rail], forcepos, homepos)
|
||||
def _motor_off(self, print_time):
|
||||
self.limits = [(1.0, -1.0)] * 3
|
||||
def _check_endstops(self, move):
|
||||
end_pos = move.end_pos
|
||||
for i in (0, 1, 2):
|
||||
|
|
|
|||
|
|
@ -23,8 +23,6 @@ class DeltaKinematics:
|
|||
stepper_configs[2], need_position_minmax = False,
|
||||
default_position_endstop=a_endstop)
|
||||
self.rails = [rail_a, rail_b, rail_c]
|
||||
config.get_printer().register_event_handler("stepper_enable:motor_off",
|
||||
self._motor_off)
|
||||
# Setup max velocity
|
||||
self.max_velocity, self.max_accel = toolhead.get_max_velocity()
|
||||
self.max_z_velocity = config.getfloat(
|
||||
|
|
@ -90,7 +88,7 @@ class DeltaKinematics:
|
|||
math.sqrt(self.very_slow_xy2)))
|
||||
self.axes_min = toolhead.Coord(-max_xy, -max_xy, self.min_z, 0.)
|
||||
self.axes_max = toolhead.Coord(max_xy, max_xy, self.max_z, 0.)
|
||||
self.set_position([0., 0., 0.], ())
|
||||
self.set_position([0., 0., 0.], "")
|
||||
def get_steppers(self):
|
||||
return [s for rail in self.rails for s in rail.get_steppers()]
|
||||
def _actuator_to_cartesian(self, spos):
|
||||
|
|
@ -103,17 +101,19 @@ class DeltaKinematics:
|
|||
for rail in self.rails:
|
||||
rail.set_position(newpos)
|
||||
self.limit_xy2 = -1.
|
||||
if tuple(homing_axes) == (0, 1, 2):
|
||||
if homing_axes == "xyz":
|
||||
self.need_home = False
|
||||
def clear_homing_state(self, clear_axes):
|
||||
# Clearing homing state for each axis individually is not implemented
|
||||
if clear_axes:
|
||||
self.limit_xy2 = -1
|
||||
self.need_home = True
|
||||
def home(self, homing_state):
|
||||
# All axes are homed simultaneously
|
||||
homing_state.set_axes([0, 1, 2])
|
||||
forcepos = list(self.home_position)
|
||||
forcepos[2] = -1.5 * math.sqrt(max(self.arm2)-self.max_xy2)
|
||||
homing_state.home_rails(self.rails, forcepos, self.home_position)
|
||||
def _motor_off(self, print_time):
|
||||
self.limit_xy2 = -1.
|
||||
self.need_home = True
|
||||
def check_move(self, move):
|
||||
end_pos = move.end_pos
|
||||
end_xy2 = end_pos[0]**2 + end_pos[1]**2
|
||||
|
|
|
|||
|
|
@ -41,8 +41,6 @@ class DeltesianKinematics:
|
|||
for s in self.get_steppers():
|
||||
s.set_trapq(toolhead.get_trapq())
|
||||
toolhead.register_step_generator(s.generate_steps)
|
||||
config.get_printer().register_event_handler(
|
||||
"stepper_enable:motor_off", self._motor_off)
|
||||
self.limits = [(1.0, -1.0)] * 3
|
||||
# X axis limits
|
||||
min_angle = config.getfloat('min_angle', MIN_ANGLE,
|
||||
|
|
@ -89,7 +87,7 @@ class DeltesianKinematics:
|
|||
self.axes_min = toolhead.Coord(*[l[0] for l in self.limits], e=0.)
|
||||
self.axes_max = toolhead.Coord(*[l[1] for l in self.limits], e=0.)
|
||||
self.homed_axis = [False] * 3
|
||||
self.set_position([0., 0., 0.], ())
|
||||
self.set_position([0., 0., 0.], "")
|
||||
def get_steppers(self):
|
||||
return [s for rail in self.rails for s in rail.get_steppers()]
|
||||
def _actuator_to_cartesian(self, sp):
|
||||
|
|
@ -115,8 +113,13 @@ class DeltesianKinematics:
|
|||
def set_position(self, newpos, homing_axes):
|
||||
for rail in self.rails:
|
||||
rail.set_position(newpos)
|
||||
for n in homing_axes:
|
||||
self.homed_axis[n] = True
|
||||
for axis_name in homing_axes:
|
||||
axis = "xyz".index(axis_name)
|
||||
self.homed_axis[axis] = True
|
||||
def clear_homing_state(self, clear_axes):
|
||||
for axis, axis_name in enumerate("xyz"):
|
||||
if axis_name in clear_axes:
|
||||
self.homed_axis[axis] = False
|
||||
def home(self, homing_state):
|
||||
homing_axes = homing_state.get_axes()
|
||||
home_xz = 0 in homing_axes or 2 in homing_axes
|
||||
|
|
@ -142,8 +145,6 @@ class DeltesianKinematics:
|
|||
else:
|
||||
forcepos[1] += 1.5 * (position_max - hi.position_endstop)
|
||||
homing_state.home_rails([self.rails[2]], forcepos, homepos)
|
||||
def _motor_off(self, print_time):
|
||||
self.homed_axis = [False] * 3
|
||||
def check_move(self, move):
|
||||
limits = list(map(list, self.limits))
|
||||
spos, epos = move.start_pos, move.end_pos
|
||||
|
|
|
|||
|
|
@ -42,8 +42,6 @@ class HybridCoreXYKinematics:
|
|||
for s in self.get_steppers():
|
||||
s.set_trapq(toolhead.get_trapq())
|
||||
toolhead.register_step_generator(s.generate_steps)
|
||||
self.printer.register_event_handler("stepper_enable:motor_off",
|
||||
self._motor_off)
|
||||
# Setup boundary checks
|
||||
max_velocity, max_accel = toolhead.get_max_velocity()
|
||||
self.max_z_velocity = config.getfloat(
|
||||
|
|
@ -69,15 +67,17 @@ class HybridCoreXYKinematics:
|
|||
def set_position(self, newpos, homing_axes):
|
||||
for i, rail in enumerate(self.rails):
|
||||
rail.set_position(newpos)
|
||||
for axis in homing_axes:
|
||||
for axis_name in homing_axes:
|
||||
axis = "xyz".index(axis_name)
|
||||
if self.dc_module and axis == self.dc_module.axis:
|
||||
rail = self.dc_module.get_primary_rail().get_rail()
|
||||
else:
|
||||
rail = self.rails[axis]
|
||||
self.limits[axis] = rail.get_range()
|
||||
def note_z_not_homed(self):
|
||||
# Helper for Safe Z Home
|
||||
self.limits[2] = (1.0, -1.0)
|
||||
def clear_homing_state(self, clear_axes):
|
||||
for axis, axis_name in enumerate("xyz"):
|
||||
if axis_name in clear_axes:
|
||||
self.limits[axis] = (1.0, -1.0)
|
||||
def home_axis(self, homing_state, axis, rail):
|
||||
position_min, position_max = rail.get_range()
|
||||
hi = rail.get_homing_info()
|
||||
|
|
@ -96,8 +96,6 @@ class HybridCoreXYKinematics:
|
|||
self.dc_module.home(homing_state)
|
||||
else:
|
||||
self.home_axis(homing_state, axis, self.rails[axis])
|
||||
def _motor_off(self, print_time):
|
||||
self.limits = [(1.0, -1.0)] * 3
|
||||
def _check_endstops(self, move):
|
||||
end_pos = move.end_pos
|
||||
for i in (0, 1, 2):
|
||||
|
|
|
|||
|
|
@ -42,8 +42,6 @@ class HybridCoreXZKinematics:
|
|||
for s in self.get_steppers():
|
||||
s.set_trapq(toolhead.get_trapq())
|
||||
toolhead.register_step_generator(s.generate_steps)
|
||||
self.printer.register_event_handler("stepper_enable:motor_off",
|
||||
self._motor_off)
|
||||
# Setup boundary checks
|
||||
max_velocity, max_accel = toolhead.get_max_velocity()
|
||||
self.max_z_velocity = config.getfloat(
|
||||
|
|
@ -69,15 +67,17 @@ class HybridCoreXZKinematics:
|
|||
def set_position(self, newpos, homing_axes):
|
||||
for i, rail in enumerate(self.rails):
|
||||
rail.set_position(newpos)
|
||||
for axis in homing_axes:
|
||||
for axis_name in homing_axes:
|
||||
axis = "xyz".index(axis_name)
|
||||
if self.dc_module and axis == self.dc_module.axis:
|
||||
rail = self.dc_module.get_primary_rail().get_rail()
|
||||
else:
|
||||
rail = self.rails[axis]
|
||||
self.limits[axis] = rail.get_range()
|
||||
def note_z_not_homed(self):
|
||||
# Helper for Safe Z Home
|
||||
self.limits[2] = (1.0, -1.0)
|
||||
def clear_homing_state(self, clear_axes):
|
||||
for axis, axis_name in enumerate("xyz"):
|
||||
if axis_name in clear_axes:
|
||||
self.limits[axis] = (1.0, -1.0)
|
||||
def home_axis(self, homing_state, axis, rail):
|
||||
position_min, position_max = rail.get_range()
|
||||
hi = rail.get_homing_info()
|
||||
|
|
@ -96,8 +96,6 @@ class HybridCoreXZKinematics:
|
|||
self.dc_module.home(homing_state)
|
||||
else:
|
||||
self.home_axis(homing_state, axis, self.rails[axis])
|
||||
def _motor_off(self, print_time):
|
||||
self.limits = [(1.0, -1.0)] * 3
|
||||
def _check_endstops(self, move):
|
||||
end_pos = move.end_pos
|
||||
for i in (0, 1, 2):
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ class NoneKinematics:
|
|||
return [0, 0, 0]
|
||||
def set_position(self, newpos, homing_axes):
|
||||
pass
|
||||
def clear_homing_state(self, clear_axes):
|
||||
pass
|
||||
def home(self, homing_state):
|
||||
pass
|
||||
def check_move(self, move):
|
||||
|
|
|
|||
|
|
@ -22,8 +22,6 @@ class PolarKinematics:
|
|||
for s in self.get_steppers():
|
||||
s.set_trapq(toolhead.get_trapq())
|
||||
toolhead.register_step_generator(s.generate_steps)
|
||||
config.get_printer().register_event_handler("stepper_enable:motor_off",
|
||||
self._motor_off)
|
||||
# Setup boundary checks
|
||||
max_velocity, max_accel = toolhead.get_max_velocity()
|
||||
self.max_z_velocity = config.getfloat(
|
||||
|
|
@ -47,13 +45,16 @@ class PolarKinematics:
|
|||
def set_position(self, newpos, homing_axes):
|
||||
for s in self.steppers:
|
||||
s.set_position(newpos)
|
||||
if 2 in homing_axes:
|
||||
if "z" in homing_axes:
|
||||
self.limit_z = self.rails[1].get_range()
|
||||
if 0 in homing_axes and 1 in homing_axes:
|
||||
if "x" in homing_axes and "y" in homing_axes:
|
||||
self.limit_xy2 = self.rails[0].get_range()[1]**2
|
||||
def note_z_not_homed(self):
|
||||
# Helper for Safe Z Home
|
||||
self.limit_z = (1.0, -1.0)
|
||||
def clear_homing_state(self, clear_axes):
|
||||
if "x" in clear_axes or "y" in clear_axes:
|
||||
# X and Y cannot be cleared separately
|
||||
self.limit_xy2 = -1.
|
||||
if "z" in clear_axes:
|
||||
self.limit_z = (1.0, -1.0)
|
||||
def _home_axis(self, homing_state, axis, rail):
|
||||
# Determine movement
|
||||
position_min, position_max = rail.get_range()
|
||||
|
|
@ -85,9 +86,6 @@ class PolarKinematics:
|
|||
self._home_axis(homing_state, 0, self.rails[0])
|
||||
if home_z:
|
||||
self._home_axis(homing_state, 2, self.rails[1])
|
||||
def _motor_off(self, print_time):
|
||||
self.limit_z = (1.0, -1.0)
|
||||
self.limit_xy2 = -1.
|
||||
def check_move(self, move):
|
||||
end_pos = move.end_pos
|
||||
xy2 = end_pos[0]**2 + end_pos[1]**2
|
||||
|
|
|
|||
|
|
@ -21,8 +21,6 @@ class RotaryDeltaKinematics:
|
|||
stepper_configs[2], need_position_minmax=False,
|
||||
default_position_endstop=a_endstop, units_in_radians=True)
|
||||
self.rails = [rail_a, rail_b, rail_c]
|
||||
config.get_printer().register_event_handler("stepper_enable:motor_off",
|
||||
self._motor_off)
|
||||
# Read config
|
||||
max_velocity, max_accel = toolhead.get_max_velocity()
|
||||
self.max_z_velocity = config.getfloat('max_z_velocity', max_velocity,
|
||||
|
|
@ -76,7 +74,7 @@ class RotaryDeltaKinematics:
|
|||
max_xy = math.sqrt(self.max_xy2)
|
||||
self.axes_min = toolhead.Coord(-max_xy, -max_xy, self.min_z, 0.)
|
||||
self.axes_max = toolhead.Coord(max_xy, max_xy, self.max_z, 0.)
|
||||
self.set_position([0., 0., 0.], ())
|
||||
self.set_position([0., 0., 0.], "")
|
||||
def get_steppers(self):
|
||||
return [s for rail in self.rails for s in rail.get_steppers()]
|
||||
def calc_position(self, stepper_positions):
|
||||
|
|
@ -86,8 +84,13 @@ class RotaryDeltaKinematics:
|
|||
for rail in self.rails:
|
||||
rail.set_position(newpos)
|
||||
self.limit_xy2 = -1.
|
||||
if tuple(homing_axes) == (0, 1, 2):
|
||||
if homing_axes == "xyz":
|
||||
self.need_home = False
|
||||
def clear_homing_state(self, clear_axes):
|
||||
# Clearing homing state for each axis individually is not implemented
|
||||
if clear_axes:
|
||||
self.limit_xy2 = -1
|
||||
self.need_home = True
|
||||
def home(self, homing_state):
|
||||
# All axes are homed simultaneously
|
||||
homing_state.set_axes([0, 1, 2])
|
||||
|
|
@ -96,9 +99,6 @@ class RotaryDeltaKinematics:
|
|||
#forcepos[2] = self.calibration.actuator_to_cartesian(min_angles)[2]
|
||||
forcepos[2] = -1.
|
||||
homing_state.home_rails(self.rails, forcepos, self.home_position)
|
||||
def _motor_off(self, print_time):
|
||||
self.limit_xy2 = -1.
|
||||
self.need_home = True
|
||||
def check_move(self, move):
|
||||
end_pos = move.end_pos
|
||||
end_xy2 = end_pos[0]**2 + end_pos[1]**2
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ class WinchKinematics:
|
|||
acoords = list(zip(*self.anchors))
|
||||
self.axes_min = toolhead.Coord(*[min(a) for a in acoords], e=0.)
|
||||
self.axes_max = toolhead.Coord(*[max(a) for a in acoords], e=0.)
|
||||
self.set_position([0., 0., 0.], ())
|
||||
self.set_position([0., 0., 0.], "")
|
||||
def get_steppers(self):
|
||||
return list(self.steppers)
|
||||
def calc_position(self, stepper_positions):
|
||||
|
|
@ -36,6 +36,9 @@ class WinchKinematics:
|
|||
def set_position(self, newpos, homing_axes):
|
||||
for s in self.steppers:
|
||||
s.set_position(newpos)
|
||||
def clear_homing_state(self, clear_axes):
|
||||
# XXX - homing not implemented
|
||||
pass
|
||||
def home(self, homing_state):
|
||||
# XXX - homing not implemented
|
||||
homing_state.set_axes([0, 1, 2])
|
||||
|
|
|
|||
|
|
@ -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_")
|
||||
|
|
|
|||
|
|
@ -403,7 +403,7 @@ class PrinterRail:
|
|||
changed_invert = pin_params['invert'] != endstop['invert']
|
||||
changed_pullup = pin_params['pullup'] != endstop['pullup']
|
||||
if changed_invert or changed_pullup:
|
||||
raise error("Pinter rail %s shared endstop pin %s "
|
||||
raise error("Printer rail %s shared endstop pin %s "
|
||||
"must specify the same pullup/invert settings" % (
|
||||
self.get_name(), pin_name))
|
||||
mcu_endstop.add_stepper(stepper)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -457,7 +457,7 @@ class ToolHead:
|
|||
# Movement commands
|
||||
def get_position(self):
|
||||
return list(self.commanded_pos)
|
||||
def set_position(self, newpos, homing_axes=()):
|
||||
def set_position(self, newpos, homing_axes=""):
|
||||
self.flush_step_generation()
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
ffi_lib.trapq_set_position(self.trapq, self.print_time,
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ used to upload firmware to devices flashed with the CanBoot bootloader.
|
|||
|
||||
The can2040 directory contains code from:
|
||||
https://github.com/KevinOConnor/can2040
|
||||
commit 13321ce2bc046e059a47def70f977a579a984462.
|
||||
version v1.7.0 (90515f53ce89442f1bcc3033aae222e9eb77818c).
|
||||
|
||||
The Huada HC32F460 directory contains code from:
|
||||
https://www.hdsc.com.cn/Category83-1490
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Software CANbus implementation for rp2040
|
||||
//
|
||||
// Copyright (C) 2022,2023 Kevin O'Connor <kevin@koconnor.net>
|
||||
// Copyright (C) 2022-2024 Kevin O'Connor <kevin@koconnor.net>
|
||||
//
|
||||
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
|
||||
|
|
@ -20,6 +20,13 @@
|
|||
* rp2040 and low-level helper functions
|
||||
****************************************************************/
|
||||
|
||||
// Determine if the target is an rp2350
|
||||
#ifdef PICO_RP2350
|
||||
#define IS_RP2350 1
|
||||
#else
|
||||
#define IS_RP2350 0
|
||||
#endif
|
||||
|
||||
// Helper compiler definitions
|
||||
#define barrier() __asm__ __volatile__("": : :"memory")
|
||||
#define likely(x) __builtin_expect(!!(x), 1)
|
||||
|
|
@ -123,19 +130,29 @@ static const uint16_t can2040_program_instructions[] = {
|
|||
#define SI_RX_DATA PIO_IRQ0_INTE_SM1_RXNEMPTY_BITS
|
||||
#define SI_TXPENDING PIO_IRQ0_INTE_SM1_BITS // Misc bit manually forced
|
||||
|
||||
// Return the gpio bank offset (on rp2350 chips)
|
||||
static uint32_t
|
||||
pio_gpiobase(struct can2040 *cd)
|
||||
{
|
||||
if (!IS_RP2350)
|
||||
return 0;
|
||||
return (cd->gpio_rx > 31 || cd->gpio_tx > 31) ? 16 : 0;
|
||||
}
|
||||
|
||||
// Setup PIO "sync" state machine (state machine 0)
|
||||
static void
|
||||
pio_sync_setup(struct can2040 *cd)
|
||||
{
|
||||
pio_hw_t *pio_hw = cd->pio_hw;
|
||||
pio_sm_hw_t *sm = &pio_hw->sm[0];
|
||||
uint32_t gpio_rx = (cd->gpio_rx - pio_gpiobase(cd)) & 0x1f;
|
||||
sm->execctrl = (
|
||||
cd->gpio_rx << PIO_SM0_EXECCTRL_JMP_PIN_LSB
|
||||
gpio_rx << PIO_SM0_EXECCTRL_JMP_PIN_LSB
|
||||
| (can2040_offset_sync_end - 1) << PIO_SM0_EXECCTRL_WRAP_TOP_LSB
|
||||
| can2040_offset_sync_signal_start << PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB);
|
||||
sm->pinctrl = (
|
||||
1 << PIO_SM0_PINCTRL_SET_COUNT_LSB
|
||||
| cd->gpio_rx << PIO_SM0_PINCTRL_SET_BASE_LSB);
|
||||
| gpio_rx << PIO_SM0_PINCTRL_SET_BASE_LSB);
|
||||
sm->instr = 0xe080; // set pindirs, 0
|
||||
sm->pinctrl = 0;
|
||||
pio_hw->txf[0] = 9 + 6 * PIO_CLOCK_PER_BIT / 2;
|
||||
|
|
@ -149,10 +166,11 @@ pio_rx_setup(struct can2040 *cd)
|
|||
{
|
||||
pio_hw_t *pio_hw = cd->pio_hw;
|
||||
pio_sm_hw_t *sm = &pio_hw->sm[1];
|
||||
uint32_t gpio_rx = (cd->gpio_rx - pio_gpiobase(cd)) & 0x1f;
|
||||
sm->execctrl = (
|
||||
(can2040_offset_shared_rx_end - 1) << PIO_SM0_EXECCTRL_WRAP_TOP_LSB
|
||||
| can2040_offset_shared_rx_read << PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB);
|
||||
sm->pinctrl = cd->gpio_rx << PIO_SM0_PINCTRL_IN_BASE_LSB;
|
||||
sm->pinctrl = gpio_rx << PIO_SM0_PINCTRL_IN_BASE_LSB;
|
||||
sm->shiftctrl = 0; // flush fifo on a restart
|
||||
sm->shiftctrl = (PIO_SM0_SHIFTCTRL_FJOIN_RX_BITS
|
||||
| PIO_RX_WAKE_BITS << PIO_SM0_SHIFTCTRL_PUSH_THRESH_LSB
|
||||
|
|
@ -169,7 +187,8 @@ pio_match_setup(struct can2040 *cd)
|
|||
sm->execctrl = (
|
||||
(can2040_offset_match_end - 1) << PIO_SM0_EXECCTRL_WRAP_TOP_LSB
|
||||
| can2040_offset_shared_rx_read << PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB);
|
||||
sm->pinctrl = cd->gpio_rx << PIO_SM0_PINCTRL_IN_BASE_LSB;
|
||||
uint32_t gpio_rx = (cd->gpio_rx - pio_gpiobase(cd)) & 0x1f;
|
||||
sm->pinctrl = gpio_rx << PIO_SM0_PINCTRL_IN_BASE_LSB;
|
||||
sm->shiftctrl = 0;
|
||||
sm->instr = 0xe040; // set y, 0
|
||||
sm->instr = 0xa0e2; // mov osr, y
|
||||
|
|
@ -183,16 +202,18 @@ pio_tx_setup(struct can2040 *cd)
|
|||
{
|
||||
pio_hw_t *pio_hw = cd->pio_hw;
|
||||
pio_sm_hw_t *sm = &pio_hw->sm[3];
|
||||
uint32_t gpio_rx = (cd->gpio_rx - pio_gpiobase(cd)) & 0x1f;
|
||||
uint32_t gpio_tx = (cd->gpio_tx - pio_gpiobase(cd)) & 0x1f;
|
||||
sm->execctrl = (
|
||||
cd->gpio_rx << PIO_SM0_EXECCTRL_JMP_PIN_LSB
|
||||
gpio_rx << PIO_SM0_EXECCTRL_JMP_PIN_LSB
|
||||
| can2040_offset_tx_conflict << PIO_SM0_EXECCTRL_WRAP_TOP_LSB
|
||||
| can2040_offset_tx_conflict << PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB);
|
||||
sm->shiftctrl = (PIO_SM0_SHIFTCTRL_FJOIN_TX_BITS
|
||||
| PIO_SM0_SHIFTCTRL_AUTOPULL_BITS);
|
||||
sm->pinctrl = (1 << PIO_SM0_PINCTRL_SET_COUNT_LSB
|
||||
| 1 << PIO_SM0_PINCTRL_OUT_COUNT_LSB
|
||||
| cd->gpio_tx << PIO_SM0_PINCTRL_SET_BASE_LSB
|
||||
| cd->gpio_tx << PIO_SM0_PINCTRL_OUT_BASE_LSB);
|
||||
| gpio_tx << PIO_SM0_PINCTRL_SET_BASE_LSB
|
||||
| gpio_tx << PIO_SM0_PINCTRL_OUT_BASE_LSB);
|
||||
sm->instr = 0xe001; // set pins, 1
|
||||
sm->instr = 0xe081; // set pindirs, 1
|
||||
}
|
||||
|
|
@ -382,6 +403,10 @@ pio_setup(struct can2040 *cd, uint32_t sys_clock, uint32_t bitrate)
|
|||
{
|
||||
// Configure pio0 clock
|
||||
uint32_t rb = cd->pio_num ? RESETS_RESET_PIO1_BITS : RESETS_RESET_PIO0_BITS;
|
||||
#if IS_RP2350
|
||||
if (cd->pio_num == 2)
|
||||
rb = RESETS_RESET_PIO2_BITS;
|
||||
#endif
|
||||
rp2040_clear_reset(rb);
|
||||
|
||||
// Setup and sync pio state machine clocks
|
||||
|
|
@ -391,11 +416,16 @@ pio_setup(struct can2040 *cd, uint32_t sys_clock, uint32_t bitrate)
|
|||
for (i=0; i<4; i++)
|
||||
pio_hw->sm[i].clkdiv = div << PIO_SM0_CLKDIV_FRAC_LSB;
|
||||
|
||||
// Configure gpiobase (on rp2350)
|
||||
#if IS_RP2350
|
||||
pio_hw->gpiobase = pio_gpiobase(cd);
|
||||
#endif
|
||||
|
||||
// Configure state machines
|
||||
pio_sm_setup(cd);
|
||||
|
||||
// Map Rx/Tx gpios
|
||||
uint32_t pio_func = cd->pio_num ? 7 : 6;
|
||||
uint32_t pio_func = 6 + cd->pio_num;
|
||||
rp2040_gpio_peripheral(cd->gpio_rx, pio_func, 1);
|
||||
rp2040_gpio_peripheral(cd->gpio_tx, pio_func, 0);
|
||||
}
|
||||
|
|
@ -495,7 +525,7 @@ unstuf_restore_state(struct can2040_bitunstuffer *bu, uint32_t data)
|
|||
|
||||
// Pull bits from unstuffer (as specified in unstuf_set_count() )
|
||||
static int
|
||||
unstuf_pull_bits(struct can2040_bitunstuffer *bu)
|
||||
unstuf_pull_bits_rp2040(struct can2040_bitunstuffer *bu)
|
||||
{
|
||||
uint32_t sb = bu->stuffed_bits, edges = sb ^ (sb >> 1);
|
||||
uint32_t e2 = edges | (edges >> 1), e4 = e2 | (e2 >> 2), rm_bits = ~e4;
|
||||
|
|
@ -539,6 +569,49 @@ unstuf_pull_bits(struct can2040_bitunstuffer *bu)
|
|||
}
|
||||
}
|
||||
|
||||
// Pull bits from unstuffer (optimized for rp2350)
|
||||
static int
|
||||
unstuf_pull_bits(struct can2040_bitunstuffer *bu)
|
||||
{
|
||||
if (!IS_RP2350)
|
||||
return unstuf_pull_bits_rp2040(bu);
|
||||
uint32_t sb = bu->stuffed_bits, edges = sb ^ (sb >> 1);
|
||||
uint32_t e2 = edges | (edges >> 1), e4 = e2 | (e2 >> 2), rm_bits = ~e4;
|
||||
uint32_t cs = bu->count_stuff, cu = bu->count_unstuff;
|
||||
for (;;) {
|
||||
if (!cs)
|
||||
// Need more data
|
||||
return 1;
|
||||
uint32_t try_cnt = cs > cu ? cu : cs;
|
||||
uint32_t try_mask = ((1 << try_cnt) - 1) << (cs + 1 - try_cnt);
|
||||
uint32_t rm_masked_bits = rm_bits & try_mask;
|
||||
if (likely(!rm_masked_bits)) {
|
||||
// No stuff bits in try_cnt bits - copy into unstuffed_bits
|
||||
bu->count_unstuff = cu = cu - try_cnt;
|
||||
bu->count_stuff = cs = cs - try_cnt;
|
||||
bu->unstuffed_bits |= ((sb >> cs) & ((1 << try_cnt) - 1)) << cu;
|
||||
if (! cu)
|
||||
// Extracted desired bits
|
||||
return 0;
|
||||
// Need more data
|
||||
return 1;
|
||||
}
|
||||
// Copy any leading bits prior to stuff bit (may be zero)
|
||||
uint32_t copy_cnt = cs - (31 - __builtin_clz(rm_masked_bits));
|
||||
cs -= copy_cnt;
|
||||
bu->count_unstuff = cu = cu - copy_cnt;
|
||||
bu->unstuffed_bits |= ((sb >> cs) & ((1 << copy_cnt) - 1)) << cu;
|
||||
// High bit is now a stuff bit - remove it
|
||||
bu->count_stuff = cs = cs - 1;
|
||||
if (unlikely(rm_bits & (1 << cs))) {
|
||||
// Six consecutive bits - a bitstuff error
|
||||
if (sb & (1 << cs))
|
||||
return -1;
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return most recent raw (still stuffed) bits
|
||||
static uint32_t
|
||||
unstuf_get_raw(struct can2040_bitunstuffer *bu)
|
||||
|
|
@ -553,7 +626,7 @@ unstuf_get_raw(struct can2040_bitunstuffer *bu)
|
|||
|
||||
// Stuff 'num_bits' bits in '*pb' - upper bits must already be stuffed
|
||||
static uint32_t
|
||||
bitstuff(uint32_t *pb, uint32_t num_bits)
|
||||
bitstuff_rp2040(uint32_t *pb, uint32_t num_bits)
|
||||
{
|
||||
uint32_t b = *pb, count = num_bits;
|
||||
for (;;) {
|
||||
|
|
@ -590,6 +663,34 @@ done:
|
|||
return count;
|
||||
}
|
||||
|
||||
// Stuff 'num_bits' bits in '*pb' (optimized for rp2350)
|
||||
static uint32_t
|
||||
bitstuff(uint32_t *pb, uint32_t num_bits)
|
||||
{
|
||||
if (!IS_RP2350)
|
||||
return bitstuff_rp2040(pb, num_bits);
|
||||
uint32_t b = *pb, count = num_bits;
|
||||
for (;;) {
|
||||
uint32_t edges = b ^ (b >> 1);
|
||||
uint32_t e2 = edges | (edges >> 1), e4 = e2 | (e2 >> 2), add_bits = ~e4;
|
||||
uint32_t mask = (1 << num_bits) - 1, add_masked_bits = add_bits & mask;
|
||||
if (!add_masked_bits)
|
||||
// No more stuff bits needed
|
||||
break;
|
||||
// Insert a stuff bit
|
||||
uint32_t stuff_pos = 1 + 31 - __builtin_clz(add_masked_bits);
|
||||
uint32_t low_mask = (1 << stuff_pos) - 1, low = b & low_mask;
|
||||
uint32_t high = (b & ~(low_mask >> 1)) << 1;
|
||||
b = high ^ low ^ (1 << (stuff_pos - 1));
|
||||
count += 1;
|
||||
if (stuff_pos <= 4)
|
||||
break;
|
||||
num_bits = stuff_pos - 4;
|
||||
}
|
||||
*pb = b;
|
||||
return count;
|
||||
}
|
||||
|
||||
// State storage for building bit stuffed transmit messages
|
||||
struct bitstuffer_s {
|
||||
uint32_t prev_stuffed, bitpos, *buf;
|
||||
|
|
@ -1326,6 +1427,12 @@ can2040_setup(struct can2040 *cd, uint32_t pio_num)
|
|||
memset(cd, 0, sizeof(*cd));
|
||||
cd->pio_num = !!pio_num;
|
||||
cd->pio_hw = cd->pio_num ? pio1_hw : pio0_hw;
|
||||
#if IS_RP2350
|
||||
if (pio_num == 2) {
|
||||
cd->pio_num = pio_num;
|
||||
cd->pio_hw = pio2_hw;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// API function to configure callback
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
@ -108,6 +108,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,6 +122,10 @@ 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,
|
||||
|
|
@ -205,10 +213,56 @@ fill_usb_host_queue(void)
|
|||
}
|
||||
}
|
||||
|
||||
// Report bus stall state
|
||||
static void
|
||||
note_discard_state(uint32_t discard)
|
||||
{
|
||||
sendf("usb_canbus_state discard=%u", discard);
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
void
|
||||
usbcan_task(void)
|
||||
{
|
||||
if (!sched_check_wake(&UsbCan.wake))
|
||||
if (!sched_check_wake(&UsbCan.wake) && !check_need_discard())
|
||||
return;
|
||||
|
||||
// Send any pending hw frames to host
|
||||
|
|
@ -235,7 +289,7 @@ usbcan_task(void)
|
|||
UsbCan.host_status = host_status = host_status & ~HS_TX_LOCAL;
|
||||
}
|
||||
if (host_status & HS_TX_HW) {
|
||||
int ret = canhw_send(&msg);
|
||||
int ret = try_canmsg_send(&msg);
|
||||
if (ret < 0)
|
||||
break;
|
||||
UsbCan.host_status = host_status = host_status & ~HS_TX_HW;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
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
|
||||
|
|
@ -184,6 +185,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 +253,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 +365,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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ CONFIG ../../config/printer-creality-cr20-pro-2019.cfg
|
|||
CONFIG ../../config/printer-creality-ender5plus-2019.cfg
|
||||
CONFIG ../../config/printer-eryone-thinker-series-v2-2020.cfg
|
||||
CONFIG ../../config/printer-flashforge-creator-pro-2018.cfg
|
||||
CONFIG ../../config/printer-geeetech-A10T-A20T-2021.cfg
|
||||
CONFIG ../../config/printer-hiprecy-leo-2019.cfg
|
||||
CONFIG ../../config/printer-longer-lk4-pro-2019.cfg
|
||||
CONFIG ../../config/printer-lulzbot-mini1-2016.cfg
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue