diff --git a/.github/workflows/upstream-sync.yaml b/.github/workflows/upstream-sync.yaml deleted file mode 100644 index d3869a01f..000000000 --- a/.github/workflows/upstream-sync.yaml +++ /dev/null @@ -1,141 +0,0 @@ -name: "Upstream Sync" - -on: - # schedule: - # - cron: '0 0 * * *' - # every day at midnight - - workflow_dispatch: # click the button on Github repo! - -# Remotes: -# - origin: our fork -# - klipper3d: original Klipper repository - -# Git references at play: -# - prev_upstream: tag pointing to the last mainline commit that was formatted -# - prev_upstream..klipper3d/master: the new commits that we want to rebase on top of upstream -# - upstream: branch on our fork containing formatted klipper with new commits being rebased + formatted -# - upstream_pr: copy of the upstream branch, allowing to edit the PR - -# Fetching: (trying to remain minimalist) -# - origin/prev_upstream: single commit deep -# - klipper3d/master: tries to select the range prev_upstream..klipper3d/master by date (--shallow-exclude not supported by github?) -# After tat the presence of new commits is confirmed: -# - origin/master: single commit deep, for getting the tooling config -# - origin/upstream: single commit deep, for rebasing on top - -# Workspace: -# - Root contains scripts/python-format.sh pyproject.toml .flake8 -# - repo contains the checked out repository at prev_upstream - -jobs: - sync_latest_from_klipper3d: - runs-on: ubuntu-20.04 - name: Sync latest commits from klipper3d repository - - steps: - # Fetch origin/prev_upstream - - uses: actions/checkout@v3 - with: - ref: prev_upstream - path: "repo" - - # Setup klipper3d remote and fetch klipper3d/master - - name: "Checks for new commits" - id: "fetch" - run: | - cd repo - git remote add klipper3d https://github.com/Klipper3d/klipper.git - git fetch --prune --no-tags --shallow-since="$(git show -s --format=%ct prev_upstream)" klipper3d master - COMMITS=$(git rev-list prev_upstream..klipper3d/master) - - if [ "$COMMITS" ]; then - echo "Upstream commits processed $(git rev-parse prev_upstream)..$(git rev-parse klipper3d/master):" >> $GITHUB_STEP_SUMMARY - echo "$COMMITS" | tee -a $GITHUB_STEP_SUMMARY - echo >> $GITHUB_STEP_SUMMARY - echo "::set-output name=new_commits::1" - else - echo "::set-output name=new_commits::0" - fi - cat $GITHUB_STEP_SUMMARY >> "$RUNNER_TEMP/summary" - - # Fetch origin/upstream (branch on which we add rebased commits) and origin/master (for tooling configs) - - name: Checkout tooling configs - if: ${{ steps.fetch.outputs.new_commits }} == '1' - run: | - cd repo - git fetch --depth=1 --prune --no-tags origin master upstream - git --work-tree=.. checkout origin/master -- ../scripts/requirements_dev.txt ../scripts/python-format.sh ../pyproject.toml ../.flake8 - git reset - echo "tooling configs checked out from: $(git rev-parse origin/master)" >> $GITHUB_STEP_SUMMARY - cat $GITHUB_STEP_SUMMARY >> "$RUNNER_TEMP/summary" - - - name: Setup python - if: ${{ steps.fetch.outputs.new_commits }} == '1' - uses: actions/setup-python@v4 - with: - python-version: 3.10.3 - cache-dependency-path: scripts/requirements_dev.txt - cache: "pip" - - name: Pip install - if: ${{ steps.fetch.outputs.new_commits }} == '1' - run: pip install -r scripts/requirements_dev.txt - - - name: Rebase + Formatting - if: ${{ steps.fetch.outputs.new_commits }} == '1' - run: | - cd repo - git config --global user.name 'GitHub Action' - git config --global user.email 'action@github.com' - GIT_COMMIT=HEAD AMEND=Y BLACK_ARGS="--config ../pyproject.toml"\ - git rebase prev_upstream klipper3d/master --onto=origin/upstream \ - -Xtheirs -Xignore-all-space --exec ../scripts/python-format.sh - git checkout origin/master .github - git add .github - git commit --amend --no-edit - git push origin +klipper3d/master:refs/tags/prev_upstream +HEAD:refs/heads/upstream +HEAD:refs/heads/upstream_pr - - echo "formatted \`upstream\` branch was: $(git rev-parse origin/upstream)" >> $GITHUB_STEP_SUMMARY - echo "new \`upstream\`: $(git rev-parse HEAD)" >> $GITHUB_STEP_SUMMARY - cd .. - echo "tooling sha1sums (from master):" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - sha1sum scripts/python-format.sh pyproject.toml .flake8 >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - cat $GITHUB_STEP_SUMMARY >> "$RUNNER_TEMP/summary" - - - name: Create Pull Request - if: ${{ steps.fetch.outputs.new_commits }} == '1' - uses: actions/github-script@v6 - with: - script: | - const fs = require('fs') - const { repo, owner } = context.repo; - const result = await github.rest.pulls.create({ - title: "[GA] Upstream changes", - owner, - repo, - head: "upstream_pr", - base: "master", - body: [ - "New commits from Klipper3D.", - "", - fs.readFileSync(process.env.RUNNER_TEMP + '/summary'), - "", - "*Please don't squash me.*", - "When there are conflicts, this branch can be manually rebased on top of master:", - "```", - "git checkout upstream_pr", - "git pull -f # If your alread have an old local version of that branch", - "git rebase origin/master", - "", - "git push -f", - "```", - ].join("\n"), - }); - github.rest.issues.addLabels({ - owner, - repo, - issue_number: result.data.number, - labels: ["upstream"], - }); diff --git a/.gitignore b/.gitignore index 4b87b0495..14b15b235 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ ci_cache/ _test_.* klippy/plugins/* !klippy/plugins/__init__.py +.vscode diff --git a/README.md b/README.md index 460f87319..f5fbd7d2f 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Action Status](https://github.com/DangerKlippers/danger-klipper/actions/workflows/ci-build_test.yaml/badge.svg?branch=master)](https://github.com/DangerKlippers/danger-klipper/actions/workflows/ci-build_test.yaml) -Welcome to the Danger Klipper project! +# Welcome to the Danger Klipper project! This is a community-maintained fork of the [Klipper](https://github.com/Klipper3d/klipper) firmware. @@ -10,28 +10,30 @@ Our goal is to support features and behavior that could be "risky" if used incor If I want my printer to light itself on fire, I should be able to make my printer light itself on fire. -Features merged into the master branch: +## Features merged into the master branch: -- [core: No Python2 tests; No PRU boards](https://github.com/DangerKlippers/danger-klipper/pull/39) +- [core: no Python2 tests; no PRU boards](https://github.com/DangerKlippers/danger-klipper/pull/39) -- [fan: Normalising Fan PWM power](https://github.com/DangerKlippers/danger-klipper/pull/44) ([klipper#6307](https://github.com/Klipper3d/klipper/pull/6307)) +- [fan: normalising Fan PWM power](https://github.com/DangerKlippers/danger-klipper/pull/44) ([klipper#6307](https://github.com/Klipper3d/klipper/pull/6307)) -- [fan: Reverse FAN](https://github.com/DangerKlippers/danger-klipper/pull/51) ([klipper#4983](https://github.com/Klipper3d/klipper/pull/4983)) +- [fan: reverse FAN](https://github.com/DangerKlippers/danger-klipper/pull/51) ([klipper#4983](https://github.com/Klipper3d/klipper/pull/4983)) -- [heater: Modify PID without reload](https://github.com/DangerKlippers/danger-klipper/pull/35) +- [heater: modify PID without reload](https://github.com/DangerKlippers/danger-klipper/pull/35) -- [heater: Velocity PID](https://github.com/DangerKlippers/danger-klipper/pull/47) ([klipper#6272](https://github.com/Klipper3d/klipper/pull/6272)) +- [heater: velocity PID](https://github.com/DangerKlippers/danger-klipper/pull/47) ([klipper#6272](https://github.com/Klipper3d/klipper/pull/6272)) -- [gcode: Jinja2.ext.do extension](https://github.com/DangerKlippers/danger-klipper/pull/26) ([klipper#5149](https://github.com/Klipper3d/klipper/pull/5149)) +- [gcode: jinja2.ext.do extension](https://github.com/DangerKlippers/danger-klipper/pull/26) ([klipper#5149](https://github.com/Klipper3d/klipper/pull/5149)) - [gcode: gcode_shell_command](https://github.com/DangerKlippers/danger-klipper/pull/26) ([klipper#2173](https://github.com/Klipper3d/klipper/pull/2173) / [kiuah](https://github.com/dw-0/kiauh/blob/master/resources/gcode_shell_command.py) ) -- [probe: Dockable Probe](https://github.com/DangerKlippers/danger-klipper/pull/43) ([klipper#4328](https://github.com/Klipper3d/klipper/pull/4328)) +- [probe: dockable Probe](https://github.com/DangerKlippers/danger-klipper/pull/43) ([klipper#4328](https://github.com/Klipper3d/klipper/pull/4328)) -- [probe: Drop the first result](https://github.com/DangerKlippers/danger-klipper/pull/2) ([klipper#3397](https://github.com/Klipper3d/klipper/issues/3397)) +- [probe: drop the first result](https://github.com/DangerKlippers/danger-klipper/pull/2) ([klipper#3397](https://github.com/Klipper3d/klipper/issues/3397)) - [probe: z_calibration](https://github.com/DangerKlippers/danger-klipper/pull/31) ([klipper#4614](https://github.com/Klipper3d/klipper/pull/4614) / [protoloft/z_calibration](https://github.com/protoloft/klipper_z_calibration)) +- [z_tilt: z-tilt calibration](https://github.com/DangerKlippers/danger-klipper/pull/105) ([klipper3d#4083](https://github.com/Klipper3d/klipper/pull/4083) / [dk/ztilt_calibration](https://github.com/DangerKlippers/danger-klipper/pull/54)) + - [core: danger_options](https://github.com/DangerKlippers/danger-klipper/pull/67) - [stepper: home_current](https://github.com/DangerKlippers/danger-klipper/pull/65) @@ -50,23 +52,85 @@ Features merged into the master branch: - [danger_options: expose the multi mcu homing timeout as a parameter](https://github.com/DangerKlippers/danger-klipper/pull/93) +- [danger_options: option to configure the homing elapsed distance tolerance](https://github.com/DangerKlippers/danger-klipper/pull/110) + - [temperature_mcu: add reference_voltage](https://github.com/DangerKlippers/danger-klipper/pull/99) ([klipper#5713](https://github.com/Klipper3d/klipper/pull/5713)) +- [stepper: current_change_dwell_time](https://github.com/DangerKlippers/danger-klipper/pull/90) + +- [homing: min_home_dist](https://github.com/DangerKlippers/danger-klipper/pull/90) + - [tla2518 support](https://github.com/DangerKlippers/danger-klipper/pull/103) If you're feeling adventurous, take a peek at the extra features in the bleeding-edge branch: - - [dmbutyugin's advanced-features branch](https://github.com/DangerKlippers/danger-klipper/pull/69) [dmbutyugin/advanced-features](https://github.com/dmbutyugin/klipper/commits/advanced-features) +- [dmbutyugin's advanced-features branch](https://github.com/DangerKlippers/danger-klipper/pull/69) [dmbutyugin/advanced-features](https://github.com/dmbutyugin/klipper/commits/advanced-features) + +- [stepper: high precision stepping protocol](https://github.com/DangerKlippers/danger-klipper/pull/69) + +- [extruder: sync extruder motion with input shaper](https://github.com/DangerKlippers/danger-klipper/pull/69) + +- [extruder: new print_pa_tower utility](https://github.com/DangerKlippers/danger-klipper/pull/69) + +- [input_shaper: smooth input shapers](https://github.com/DangerKlippers/danger-klipper/pull/69) + +- [input_shaper: new print_ringing_tower utility](https://github.com/DangerKlippers/danger-klipper/pull/69) + +## Switch to Danger Klipper + +> [!NOTE] +> Any add-on modules you are using will need to be reinstalled after switching to Danger Klipper. This includes things like Beacon support, led-effect, etc. +> +> Any data in ~/printer_data such as printer configs and macros will be unaffected. + +### Manually clone the repository + +If desired, make a backup copy of your existing Klipper installation by running: + +```bash +mv ~/klipper ~/klipper_old +``` + +Then clone the Danger Klipper repo and restart the klipper service: + +```bash +git clone https://github.com/DangerKlippers/danger-klipper.git ~/klipper +sudo systemctl restart klipper +``` + +### Using KIAUH + +For users that are not comfortable using Git directly, [KIAUH](https://github.com/dw-0/kiauh) is able to use custom repositories. + +To do this, add the Danger Klipper repo to KIAUH's repo list and run the script with the following commands: + +```bash +echo "DangerKlippers/danger-klipper" >> ~/kiauh/klipper_repos.txt +~/kiauh/kiauh.sh +``` - - [stepper: high precision stepping protocol](https://github.com/DangerKlippers/danger-klipper/pull/69) +From the KIAUH menu select: - - [extruder: sync extruder motion with input shaper](https://github.com/DangerKlippers/danger-klipper/pull/69) +- 6 ) Settings +- 1 ) Set custom Klipper repository +- Select the number corresponding to DangerKlipper from the list shown +- Select 'Y' to confirm replacing your existing Klipper install +- Enter 'B' for back twice +- 'Q' to quit - - [extruder: new print_pa_tower utility](https://github.com/DangerKlippers/danger-klipper/pull/69) +### Adding a git-remote to the existing installation - - [input_shaper: smooth input shapers](https://github.com/DangerKlippers/danger-klipper/pull/69) +```bash +cd ~/klipper +git remote add danger https://github.com/DangerKlippers/danger-klipper.git +git checkout -b upstream-master origin/master +git branch -D master +git checkout -b master danger/master +sudo systemctl restart klipper +sudo systemctl restart moonraker +``` - - [input_shaper: new print_ringing_tower utility](https://github.com/DangerKlippers/danger-klipper/pull/69) +--- "Dangerous Klipper for dangerous users" diff --git a/config/generic-bigtreetech-octopus-pro-v1.0.cfg b/config/generic-bigtreetech-octopus-pro-v1.0.cfg new file mode 100644 index 000000000..109257f65 --- /dev/null +++ b/config/generic-bigtreetech-octopus-pro-v1.0.cfg @@ -0,0 +1,289 @@ +# This file contains common pin mappings for the BigTreeTech Octopus +# Pro v1.0 board. + +# Important! Do not use this config with an Octopus Pro v1.1 board as +# doing so could result in a heater being inadvertently enabled. + +# To use this config, start by identifying the micro-controller on the +# board - it may be an STM32F446, STM32F429, or an STM32H723. Select +# the appropriate micro-controller in "make menuconfig" and select +# "Enable low-level configuration options". For STM32F446 boards the +# firmware should be compiled with a "32KiB bootloader" and a "12MHz +# crystal" clock reference. For STM32F429 boards use a "32KiB +# bootloader" and an "8MHz crystal". For STM32H723 boards use a +# "128KiB bootloader" and a "25Mhz crystal". + +# See docs/Config_Reference.md for a description of parameters. + +# Driver0 +[stepper_x] +step_pin: PF13 +dir_pin: PF12 +enable_pin: !PF14 +microsteps: 16 +rotation_distance: 40 +endstop_pin: PG6 +position_endstop: 0 +position_max: 200 +homing_speed: 50 + +# Driver1 +[stepper_y] +step_pin: PG0 +dir_pin: PG1 +enable_pin: !PF15 +microsteps: 16 +rotation_distance: 40 +endstop_pin: PG9 +position_endstop: 0 +position_max: 200 +homing_speed: 50 + +# Driver2 +[stepper_z] +step_pin: PF11 +dir_pin: PG3 +enable_pin: !PG5 +microsteps: 16 +rotation_distance: 8 +endstop_pin: PG10 +position_endstop: 0.5 +position_max: 200 + +# Driver3 +# The Octopus only has 4 heater outputs which leaves an extra stepper +# This can be used for a second Z stepper, dual_carriage, extruder co-stepper, +# or other accesory such as an MMU +#[stepper_] +#step_pin: PG4 +#dir_pin: PC1 +#enable_pin: !PA0 +#endstop_pin: PG11 +#... + +# Driver4 +[extruder] +step_pin: PF9 +dir_pin: PF10 +enable_pin: !PG2 +microsteps: 16 +rotation_distance: 33.500 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PA2 # HE0 +sensor_pin: PF4 # T0 +sensor_type: EPCOS 100K B57560G104F +control: pid +pid_Kp: 22.2 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 250 + +#[filament_switch_sensor material_0] +#switch_pin: PG12 + +# Driver5 +#[extruder1] +#step_pin: PC13 +#dir_pin: PF0 +#enable_pin: !PF1 +#heater_pin: PA3 # HE1 +#sensor_pin: PF5 # T1 +#... + +#[filament_switch_sensor material_1] +#switch_pin: PG13 + +# Driver6 +#[extruder2] +#step_pin: PE2 +#dir_pin: PE3 +#enable_pin: !PD4 +#heater_pin: PB10 # HE2 +#sensor_pin: PF6 # T2 +#... + +#[filament_switch_sensor material_2] +#switch_pin: PG14 + +# Driver7 +#[extruder3] +#step_pin: PE6 +#dir_pin: PA14 +#enable_pin: !PE0 +#heater_pin: PB11 # HE3 +#sensor_pin: PF7 # T3 +#... + +#[filament_switch_sensor material_3] +#switch_pin: PG15 + +[heater_bed] +heater_pin: PA1 +sensor_pin: PF3 # TB +sensor_type: ATC Semitec 104GT-2 +control: watermark +min_temp: 0 +max_temp: 130 + +[fan] +pin: PA8 + +#[heater_fan fan1] +#pin: PE5 + +#[heater_fan fan2] +#pin: PD12 + +#[heater_fan fan3] +#pin: PD13 + +#[heater_fan fan4] +#pin: PD14 + +#[controller_fan fan5] +#pin: PD15 + +[mcu] +serial: /dev/serial/by-id/usb-Klipper_Klipper_firmware_12345-if00 +# CAN bus is also available on this board + +[printer] +kinematics: cartesian +max_velocity: 300 +max_accel: 3000 +max_z_velocity: 5 +max_z_accel: 100 + +######################################## +# TMC2209 configuration +######################################## + +#[tmc2209 stepper_x] +#uart_pin: PC4 +##diag_pin: PG6 +#run_current: 0.800 +#stealthchop_threshold: 999999 + +#[tmc2209 stepper_y] +#uart_pin: PD11 +##diag_pin: PG9 +#run_current: 0.800 +#stealthchop_threshold: 999999 + +#[tmc2209 stepper_z] +#uart_pin: PC6 +##diag_pin: PG10 +#run_current: 0.650 +#stealthchop_threshold: 999999 + +#[tmc2209 stepper_] +#uart_pin: PC7 +##diag_pin: PG11 +#run_current: 0.650 +#stealthchop_threshold: 999999 + +#[tmc2209 extruder] +#uart_pin: PF2 +#run_current: 0.800 +#stealthchop_threshold: 999999 + +#[tmc2209 extruder1] +#uart_pin: PE4 +#run_current: 0.800 +#stealthchop_threshold: 999999 + +#[tmc2209 extruder2] +#uart_pin: PE1 +#run_current: 0.800 +#stealthchop_threshold: 999999 + +#[tmc2209 extruder3] +#uart_pin: PD3 +#run_current: 0.800 +#stealthchop_threshold: 999999 + +######################################## +# TMC2130 configuration +######################################## + +#[tmc2130 stepper_x] +#cs_pin: PC4 +#spi_bus: spi1 +##diag1_pin: PG6 +#run_current: 0.800 +#stealthchop_threshold: 999999 + +#[tmc2130 stepper_y] +#cs_pin: PD11 +#spi_bus: spi1 +##diag1_pin: PG9 +#run_current: 0.800 +#stealthchop_threshold: 999999 + +#[tmc2130 stepper_z] +#cs_pin: PC6 +#spi_bus: spi1 +##diag1_pin: PG10 +#run_current: 0.650 +#stealthchop_threshold: 999999 + +#[tmc2130 stepper_] +#cs_pin: PC7 +#spi_bus: spi1 +##diag1_pin: PG11 +#run_current: 0.800 +#stealthchop_threshold: 999999 + +#[tmc2130 extruder] +#cs_pin: PF2 +#spi_bus: spi1 +#run_current: 0.800 +#stealthchop_threshold: 999999 + +#[tmc2130 extruder1] +#cs_pin: PE4 +#spi_bus: spi1 +#run_current: 0.800 +#stealthchop_threshold: 999999 + +#[tmc2130 extruder2] +#cs_pin: PE1 +#spi_bus: spi1 +#run_current: 0.800 +#stealthchop_threshold: 999999 + +#[tmc2130 extruder3] +#cs_pin: PD3 +#spi_bus: spi1 +#run_current: 0.800 +#stealthchop_threshold: 999999 + +[board_pins] +aliases: + # EXP1 header + EXP1_1=PE8, EXP1_2=PE7, + EXP1_3=PE9, EXP1_4=PE10, + EXP1_5=PE12, EXP1_6=PE13, # Slot in the socket on this side + EXP1_7=PE14, EXP1_8=PE15, + EXP1_9=, EXP1_10=<5V>, + + # EXP2 header + EXP2_1=PA6, EXP2_2=PA5, + EXP2_3=PB1, EXP2_4=PA4, + EXP2_5=PB2, EXP2_6=PA7, # Slot in the socket on this side + EXP2_7=PC15, EXP2_8=, + EXP2_9=, EXP2_10=PC5 + +# See the sample-lcd.cfg file for definitions of common LCD displays. + +# A [probe] section can be defined instead with a pin: setting identical +# to the sensor_pin: for a bltouch +#[bltouch] +#sensor_pin: PB7 +#control_pin: PB6 +#z_offset: 0 + +#[neopixel my_neopixel] +#pin: PB0 diff --git a/config/generic-bigtreetech-octopus-pro-v1.1.cfg b/config/generic-bigtreetech-octopus-pro-v1.1.cfg new file mode 100644 index 000000000..920ca9f38 --- /dev/null +++ b/config/generic-bigtreetech-octopus-pro-v1.1.cfg @@ -0,0 +1,285 @@ +# This file contains common pin mappings for the BigTreeTech Octopus +# Pro v1.1 board. + +# Important! Do not use this config with an Octopus Pro v1.0 board nor +# non-Pro board. + +# To use this config, during "make menuconfig", select "Enable +# low-level configuration options", select the STM32H723 +# micro-controller, select a "128KiB bootloader", and select a "25Mhz +# crystal". + +# See docs/Config_Reference.md for a description of parameters. + +# Driver0 +[stepper_x] +step_pin: PF13 +dir_pin: PF12 +enable_pin: !PF14 +microsteps: 16 +rotation_distance: 40 +endstop_pin: PG6 +position_endstop: 0 +position_max: 200 +homing_speed: 50 + +# Driver1 +[stepper_y] +step_pin: PG0 +dir_pin: PG1 +enable_pin: !PF15 +microsteps: 16 +rotation_distance: 40 +endstop_pin: PG9 +position_endstop: 0 +position_max: 200 +homing_speed: 50 + +# Driver2 +[stepper_z] +step_pin: PF11 +dir_pin: PG3 +enable_pin: !PG5 +microsteps: 16 +rotation_distance: 8 +endstop_pin: PG10 +position_endstop: 0.5 +position_max: 200 + +# Driver3 +# The Octopus only has 4 heater outputs which leaves an extra stepper +# This can be used for a second Z stepper, dual_carriage, extruder co-stepper, +# or other accesory such as an MMU +#[stepper_] +#step_pin: PG4 +#dir_pin: PC1 +#enable_pin: !PA2 +#endstop_pin: PG11 +#... + +# Driver4 +[extruder] +step_pin: PF9 +dir_pin: PF10 +enable_pin: !PG2 +microsteps: 16 +rotation_distance: 33.500 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PA0 # HE0 +sensor_pin: PF4 # T0 +sensor_type: EPCOS 100K B57560G104F +control: pid +pid_Kp: 22.2 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 250 + +#[filament_switch_sensor material_0] +#switch_pin: PG12 + +# Driver5 +#[extruder1] +#step_pin: PC13 +#dir_pin: PF0 +#enable_pin: !PF1 +#heater_pin: PA3 # HE1 +#sensor_pin: PF5 # T1 +#... + +#[filament_switch_sensor material_1] +#switch_pin: PG13 + +# Driver6 +#[extruder2] +#step_pin: PE2 +#dir_pin: PE3 +#enable_pin: !PD4 +#heater_pin: PB0 # HE2 +#sensor_pin: PF6 # T2 +#... + +#[filament_switch_sensor material_2] +#switch_pin: PG14 + +# Driver7 +#[extruder3] +#step_pin: PE6 +#dir_pin: PA14 +#enable_pin: !PE0 +#heater_pin: PB11 # HE3 +#sensor_pin: PF7 # T3 +#... + +#[filament_switch_sensor material_3] +#switch_pin: PG15 + +[heater_bed] +heater_pin: PA1 +sensor_pin: PF3 # TB +sensor_type: ATC Semitec 104GT-2 +control: watermark +min_temp: 0 +max_temp: 130 + +[fan] +pin: PA8 + +#[heater_fan fan1] +#pin: PE5 + +#[heater_fan fan2] +#pin: PD12 + +#[heater_fan fan3] +#pin: PD13 + +#[heater_fan fan4] +#pin: PD14 + +#[controller_fan fan5] +#pin: PD15 + +[mcu] +serial: /dev/serial/by-id/usb-Klipper_Klipper_firmware_12345-if00 +# CAN bus is also available on this board + +[printer] +kinematics: cartesian +max_velocity: 300 +max_accel: 3000 +max_z_velocity: 5 +max_z_accel: 100 + +######################################## +# TMC2209 configuration +######################################## + +#[tmc2209 stepper_x] +#uart_pin: PC4 +##diag_pin: PG6 +#run_current: 0.800 +#stealthchop_threshold: 999999 + +#[tmc2209 stepper_y] +#uart_pin: PD11 +##diag_pin: PG9 +#run_current: 0.800 +#stealthchop_threshold: 999999 + +#[tmc2209 stepper_z] +#uart_pin: PC6 +##diag_pin: PG10 +#run_current: 0.650 +#stealthchop_threshold: 999999 + +#[tmc2209 stepper_] +#uart_pin: PC7 +##diag_pin: PG11 +#run_current: 0.650 +#stealthchop_threshold: 999999 + +#[tmc2209 extruder] +#uart_pin: PF2 +#run_current: 0.800 +#stealthchop_threshold: 999999 + +#[tmc2209 extruder1] +#uart_pin: PE4 +#run_current: 0.800 +#stealthchop_threshold: 999999 + +#[tmc2209 extruder2] +#uart_pin: PE1 +#run_current: 0.800 +#stealthchop_threshold: 999999 + +#[tmc2209 extruder3] +#uart_pin: PD3 +#run_current: 0.800 +#stealthchop_threshold: 999999 + +######################################## +# TMC2130 configuration +######################################## + +#[tmc2130 stepper_x] +#cs_pin: PC4 +#spi_bus: spi1 +##diag1_pin: PG6 +#run_current: 0.800 +#stealthchop_threshold: 999999 + +#[tmc2130 stepper_y] +#cs_pin: PD11 +#spi_bus: spi1 +##diag1_pin: PG9 +#run_current: 0.800 +#stealthchop_threshold: 999999 + +#[tmc2130 stepper_z] +#cs_pin: PC6 +#spi_bus: spi1 +##diag1_pin: PG10 +#run_current: 0.650 +#stealthchop_threshold: 999999 + +#[tmc2130 stepper_] +#cs_pin: PC7 +#spi_bus: spi1 +##diag1_pin: PG11 +#run_current: 0.800 +#stealthchop_threshold: 999999 + +#[tmc2130 extruder] +#cs_pin: PF2 +#spi_bus: spi1 +#run_current: 0.800 +#stealthchop_threshold: 999999 + +#[tmc2130 extruder1] +#cs_pin: PE4 +#spi_bus: spi1 +#run_current: 0.800 +#stealthchop_threshold: 999999 + +#[tmc2130 extruder2] +#cs_pin: PE1 +#spi_bus: spi1 +#run_current: 0.800 +#stealthchop_threshold: 999999 + +#[tmc2130 extruder3] +#cs_pin: PD3 +#spi_bus: spi1 +#run_current: 0.800 +#stealthchop_threshold: 999999 + +[board_pins] +aliases: + # EXP1 header + EXP1_1=PE8, EXP1_2=PE7, + EXP1_3=PE9, EXP1_4=PE10, + EXP1_5=PE12, EXP1_6=PE13, # Slot in the socket on this side + EXP1_7=PE14, EXP1_8=PE15, + EXP1_9=, EXP1_10=<5V>, + + # EXP2 header + EXP2_1=PA6, EXP2_2=PA5, + EXP2_3=PB1, EXP2_4=PA4, + EXP2_5=PB2, EXP2_6=PA7, # Slot in the socket on this side + EXP2_7=PC15, EXP2_8=, + EXP2_9=, EXP2_10=PC5 + +# See the sample-lcd.cfg file for definitions of common LCD displays. + +# A [probe] section can be defined instead with a pin: setting identical +# to the sensor_pin: for a bltouch +#[bltouch] +#sensor_pin: PB7 +#control_pin: PB6 +#z_offset: 0 + +#[neopixel my_neopixel] +#pin: PB10 diff --git a/config/generic-bigtreetech-octopus.cfg b/config/generic-bigtreetech-octopus-v1.1.cfg similarity index 89% rename from config/generic-bigtreetech-octopus.cfg rename to config/generic-bigtreetech-octopus-v1.1.cfg index 73bd584ca..f3a2b87c3 100644 --- a/config/generic-bigtreetech-octopus.cfg +++ b/config/generic-bigtreetech-octopus-v1.1.cfg @@ -1,12 +1,16 @@ # This file contains common pin mappings for the BigTreeTech Octopus -# and Octopus Pro boards. To use this config, start by identifying the -# micro-controller on the board - it may be an STM32F446, STM32F429, -# or an STM32H723. Select the appropriate micro-controller in "make -# menuconfig" and select "Enable low-level configuration options". For -# STM32F446 boards the firmware should be compiled with a "32KiB -# bootloader" and a "12MHz crystal" clock reference. For STM32F429 -# boards use a "32KiB bootloader" and an "8MHz crystal". For STM32H723 -# boards use a "128KiB bootloader" and a "25Mhz crystal". +# (non-Pro) boards. + +# Important! Do not use this config with an Octopus Pro v1.1 board as +# doing so could result in a heater being inadvertently enabled. + +# To use this config, start by identifying the micro-controller on the +# board - it may be an STM32F446, or STM32F429. Select the +# appropriate micro-controller in "make menuconfig" and select "Enable +# low-level configuration options". For STM32F446 boards the firmware +# should be compiled with a "32KiB bootloader" and a "12MHz crystal" +# clock reference. For STM32F429 boards use a "32KiB bootloader" and +# an "8MHz crystal". # See docs/Config_Reference.md for a description of parameters. @@ -52,7 +56,7 @@ position_max: 200 #[stepper_] #step_pin: PG4 #dir_pin: PC1 -#enable_pin: PA0 +#enable_pin: !PA0 #endstop_pin: PG11 #... diff --git a/config/generic-bigtreetech-skr-pico-v1.0.cfg b/config/generic-bigtreetech-skr-pico-v1.0.cfg index 06a0c9f7a..4dd750f5c 100644 --- a/config/generic-bigtreetech-skr-pico-v1.0.cfg +++ b/config/generic-bigtreetech-skr-pico-v1.0.cfg @@ -106,6 +106,9 @@ pin: gpio18 [heater_fan controller_fan] pin: gpio20 +[temperature_sensor pico] +sensor_type: temperature_mcu + [mcu] serial: /dev/serial/by-id/usb-Klipper_Klipper_firmware_12345-if00 diff --git a/config/generic-ldo-leviathan-v1.2.cfg b/config/generic-ldo-leviathan-v1.2.cfg new file mode 100644 index 000000000..61fc1cd71 --- /dev/null +++ b/config/generic-ldo-leviathan-v1.2.cfg @@ -0,0 +1,241 @@ +# This file contains common pin mappings for the LDO Leviathan v1.2. + +# To use this config, during "make menuconfig", select "Enable +# low-level configuration options", select the STM32F446 micro-controller, +# select a "32KiB bootloader", and select a "12Mhz crystal". + +# See docs/Config_Reference.md for a description of parameters. + +# HV-STEPPER-0 +[stepper_x] +step_pin: PB10 +dir_pin: PB11 +enable_pin: !PG0 +microsteps: 32 +rotation_distance: 40 +endstop_pin: PC1 # X-ENDSTOP +position_endstop: 0 +position_max: 200 +homing_speed: 50 + +[tmc5160 stepper_x] +spi_bus: spi4 +cs_pin: PE15 +#diag0_pin: PG1 +interpolate: False +sense_resistor: 0.075 +run_current: 0.8 +stealthchop_threshold: 0 + +# HV-STEPPER-1 +[stepper_y] +step_pin: PF15 +dir_pin: PF14 +enable_pin: !PE9 +microsteps: 32 +rotation_distance: 40 +endstop_pin: PC2 # Y-ENDSTOP +position_endstop: 0 +position_max: 200 +homing_speed: 50 + +[tmc5160 stepper_y] +spi_bus: spi4 +cs_pin: PE11 +#diag0_pin: PE10 +interpolate: False +sense_resistor: 0.075 +run_current: 0.8 +stealthchop_threshold: 0 + +# STEPPER-0 +[stepper_z] +step_pin: PD4 +dir_pin: PD3 +enable_pin: !PD7 +microsteps: 32 +rotation_distance: 8 +endstop_pin: PC3 # Z-ENDSTOP +position_endstop: 0 +position_max: 200 + +[tmc2209 stepper_z] +uart_pin: PD5 +#diag_pin: PD6 +interpolate: False +run_current: 0.6 +stealthchop_threshold: 999999 + +# The Leviathan was developed for Voron printers. It therefore has several +# steppers for the z-axes, but only one heater for one extruder. + +# STEPPER-1 +#[stepper_z1] +#step_pin: PC12 +#dir_pin: PC11 +#enable_pin: !PD2 +#microsteps: 32 +#rotation_distance: 8 +# +#[tmc2209 stepper_z1] +#uart_pin: PD5 +##diag_pin: PD6 +#interpolate: False +#run_current: 0.6 +#stealthchop_threshold: 999999 + +# STEPPER-2 +#[stepper_z2] +#step_pin: PC9 +#dir_pin: PC8 +#enable_pin: !PC10 +#microsteps: 32 +#rotation_distance: 8 +# +#[tmc2209 stepper_z2] +#uart_pin: PA8 +##diag_pin: PA15 +#interpolate: False +#run_current: 0.6 +#stealthchop_threshold: 999999 + +# STEPPER-3 +#[stepper_z3] +#step_pin: PG7 +#dir_pin: PG6 +#enable_pin: !PC7 +#microsteps: 32 +#rotation_distance: 8 +# +#[tmc2209 stepper_z2] +#uart_pin: PG8 +##diag_pin: PC6 +#interpolate: False +#run_current: 0.6 +#stealthchop_threshold: 999999 + +# STEPPER-4 +[extruder] +step_pin: PD10 +dir_pin: PD9 +enable_pin: !PD13 +microsteps: 32 +rotation_distance: 22.67 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PG10 # HEATER +sensor_pin: PA2 # TH1 +sensor_type: ATC Semitec 104NT-4-R025H42G +pullup_resistor: 2200 +control: pid +pid_Kp: 36.787 +pid_Ki: 4.716 +pid_Kd: 71.735 +min_temp: 0 +max_temp: 250 + +[tmc2209 stepper_z] +uart_pin: PD11 +#diag_pin: PD12 +interpolate: False +run_current: 0.5 +stealthchop_threshold: 0 + +#[filament_switch_sensor material_0] +#switch_pin: PC0 # FILAMENT-SENSOR + +[heater_bed] +heater_pin: PG11 # HEATBED +sensor_pin: PA1 # TH0 +sensor_type: ATC Semitec 104GT-2 +pullup_resistor: 2200 +control: pid +pid_kp: 56.723 +pid_ki: 5.561 +pid_kd: 144.642 +min_temp: 0 +max_temp: 130 + +[fan] +pin: PB7 # FAN0 +#tachometer_pin: PB0 + +#[heater_fan fan1] +#pin: PB3 +#tachometer_pin: PB4 + +#[heater_fan fan2] +#pin: PF7 +#tachometer_pin: PF6 + +#[controller_fan fan3] +#pin: PF9 +#tachometer_pin: PF8 + +[mcu] +serial: /dev/serial/by-id/usb-Klipper_Klipper_firmware_12345-if00 +# CAN bus is also available on this board + +[printer] +kinematics: cartesian +max_velocity: 300 +max_accel: 3000 +max_z_velocity: 5 +max_z_accel: 100 + +[board_pins] +aliases: + # EXP1 header + EXP1_1=PG9, EXP1_2=PG12, + EXP1_3=PG13, EXP1_4=PG14, + EXP1_5=PC13, EXP1_6=PC14, + EXP1_7=PC15, EXP1_8=PF0, + EXP1_9=, EXP1_10=<5V>, + + # EXP2 header + EXP2_1=PA6, EXP2_2=PA5, + EXP2_3=PE2, EXP2_4=PE4, + EXP2_5=PE3, EXP2_6=PA7, + EXP2_7=PE5, EXP2_8=, + EXP2_9=, EXP2_10=PE4, + + # See the sample-lcd.cfg file for definitions of common LCD displays. + + # EXTENSION PORT + EXP3_1=<5V>, EXP3_2=<5V>, # max. 0.5A + EXP3_3=, EXP3_4=, + EXP3_5=<3.3V>, EXP3_6=<3.3V>, # max. 0.5A + EXP3_7=PF5, EXP3_8=PF4, + EXP3_9=PF3, EXP3_10=PF2, + EXP3_11=PC4, EXP3_12=PC5, # EXP3_11 and EXP3_12 are ADC inputs + EXP3_13=PB0, EXP3_14=PB1, # EXP3_13 and EXP3_14 are ADC inputs + EXP3_15=PE8, EXP3_16=PE7, # EXP3_15 is UART5_TX, EXP3_16 is UART5_RX + EXP3_17=PG5, EXP3_18=PG4, + EXP3_19=PG3, EXP3_20=PG2, + EXP3_21=PD15, EXP3_22=PD14, + EXP3_23=PB15, EXP3_24=PB14, # EXP3_23 is SPI2_MOSI + # EXP3_24 is SPI2_MISO + EXP3_25=PB13, EXP3_26=PB12, # EXP3_25 is SPI2_SCK + CAN2_TX + # EXP3_26 is SPI2_CS + CAN2_RX + EXP3_27=, EXP3_28=, + EXP3_29=<24V>, EXP3_30=<24V>, # max. 0.5A + +#[probe] +#sensor_pin: PF1 # Z-PROBE +#z_offset: 0 + +#[led my_led] +#white_pin: PE6 # LED-Strip + +#[neopixel my_neopixel] +#pin: PF10 # NEOPIXEL + +#[temperature_sensor TH2] +#sensor_type: ATC Semitec 104GT-2 +#sensor_pin: PA0 # TH2 +#pullup_resistor: 2200 + +#[temperature_sensor TH3] +#sensor_type: ATC Semitec 104GT-2 +#sensor_pin: PA3 # TH3 +#pullup_resistor: 2200 diff --git a/config/printer-sovol-sv06-2022.cfg b/config/printer-sovol-sv06-2022.cfg index 78a627c7d..e9f179eea 100644 --- a/config/printer-sovol-sv06-2022.cfg +++ b/config/printer-sovol-sv06-2022.cfg @@ -28,7 +28,7 @@ microsteps: 16 rotation_distance: 40 endstop_pin: tmc2209_stepper_x:virtual_endstop position_endstop: 0 -position_max: 225 +position_max: 220 homing_speed: 40 homing_retract_dist: 0 @@ -50,7 +50,7 @@ microsteps: 16 rotation_distance: 40 endstop_pin: tmc2209_stepper_y:virtual_endstop position_endstop: 0 -position_max: 225 +position_max: 220 homing_speed: 40 homing_retract_dist: 0 @@ -72,7 +72,7 @@ microsteps: 16 rotation_distance: 4 endstop_pin: probe:z_virtual_endstop position_min: -4 -position_max: 261 +position_max: 250 homing_speed: 4 [tmc2209 stepper_z] @@ -127,7 +127,7 @@ pin: PA0 [probe] pin: PB1 -x_offset: 28 +x_offset: 27 y_offset: -20 z_offset: 0 samples: 2 diff --git a/config/printer-sovol-sv06-plus-2023.cfg b/config/printer-sovol-sv06-plus-2023.cfg new file mode 100644 index 000000000..8c72192f3 --- /dev/null +++ b/config/printer-sovol-sv06-plus-2023.cfg @@ -0,0 +1,147 @@ +# This file contains pin mappings for the stock Sovol SV06 Plus +# To use this config, during "make menuconfig" select the +# STM32F103 with a "28KiB bootloader" and serial (on USART1 PA10/PA9) communication. +# Also, since it is using the GD32F103, please select Disable SWD at startup +# +# Flash this firmware by copying "out/klipper.bin" to a SD card and +# turning on the printer with the card inserted. The firmware +# filename must end in ".bin" and must not match the last filename +# that was flashed. +# +# Note: The stock LCD display does not currently work with Klipper +# +# See docs/Config_Reference.md for a description of parameters. +[mcu] +serial: /dev/serial/by-id/usb-1a86_USB2.0-Serial-if00-port0 +restart_method: command + +[printer] +kinematics: cartesian +max_velocity: 500 +max_accel: 2000 +max_z_velocity: 10 +max_z_accel: 100 + +[stepper_x] +step_pin: PC2 +dir_pin: !PB9 +enable_pin: !PC3 +microsteps: 16 +rotation_distance: 40 +endstop_pin: tmc2209_stepper_x:virtual_endstop +position_endstop: 0 +position_max: 305 +homing_speed: 40 +homing_retract_dist: 0 + +[tmc2209 stepper_x] +uart_pin: PC1 +run_current: 0.860 +sense_resistor: 0.150 +uart_address: 3 +driver_SGTHRS: 86 +diag_pin: PA5 + +[stepper_y] +step_pin: PB8 +dir_pin: PB7 +enable_pin: !PC3 +microsteps: 16 +rotation_distance: 40 +endstop_pin: tmc2209_stepper_y:virtual_endstop +position_endstop: 0 +position_max: 305 +homing_speed: 40 +homing_retract_dist: 0 + +[tmc2209 stepper_y] +uart_pin: PC0 +run_current: 0.900 +sense_resistor: 0.150 +uart_address: 3 +driver_SGTHRS: 110 +diag_pin: PA6 + +[stepper_z] +step_pin: PB6 +dir_pin: !PB5 +enable_pin: !PC3 +microsteps: 16 +rotation_distance: 4 +endstop_pin: probe:z_virtual_endstop +position_min: -4 +position_max: 350 +homing_speed: 4 + +[tmc2209 stepper_z] +uart_pin: PA15 +run_current: 1.000 +interpolate: False +sense_resistor: 0.150 +uart_address: 3 +diag_pin: PA7 + +[extruder] +max_extrude_only_distance: 100.0 +step_pin: PB4 +dir_pin: !PB3 +enable_pin: !PC3 +microsteps: 16 +rotation_distance: 4.56 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PA1 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PC5 +control: pid +pid_kd: 41.96 +pid_kp: 15.66 +pid_ki: 1.49 +min_temp: 0 +max_temp: 300 + +[tmc2209 extruder] +uart_pin: PC14 +run_current: 0.550 +stealthchop_threshold: 0 +interpolate: False +sense_resistor: 0.150 +uart_address: 3 + +[heater_bed] +heater_pin: PA2 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PC4 +control: pid +pid_kp: 186.38 +pid_ki: 36.12 +pid_kd: 637.30 +min_temp: 0 +max_temp: 130 + +[fan] +pin: PA0 + +[probe] +pin: PB1 +x_offset: 28 +y_offset: -20 +z_offset: 0 + +[safe_z_home] +home_xy_position: 123,170 +z_hop: 10 +z_hop_speed: 5 + +[bed_mesh] +speed: 120 +mesh_min: 28, 20 +mesh_max: 270, 270 +probe_count: 5 +algorithm: bicubic +fade_end: 10 +fade_target: 0 + +[filament_switch_sensor filament_runout_sensor] +switch_pin: PA4 +pause_on_runout: True diff --git a/config/sample-pwm-tool.cfg b/config/sample-pwm-tool.cfg index 4adcea6f1..489860617 100644 --- a/config/sample-pwm-tool.cfg +++ b/config/sample-pwm-tool.cfg @@ -2,9 +2,8 @@ # such as a laser or spindle. # See docs/Using_PWM_Tools.md for a more detailed description. -[output_pin TOOL] +[pwm_tool TOOL] pin: !ar9 # use your fan's pin number -pwm: True hardware_pwm: True cycle_time: 0.001 shutdown_value: 0 @@ -36,9 +35,9 @@ gcode: [menu __main __control __toolonoff] type: input -enable: {'output_pin TOOL' in printer} +enable: {'pwm_tool TOOL' in printer} name: Fan: {'ON ' if menu.input else 'OFF'} -input: {printer['output_pin TOOL'].value} +input: {printer['pwm_tool TOOL'].value} input_min: 0 input_max: 1 input_step: 1 @@ -47,9 +46,9 @@ gcode: [menu __main __control __toolspeed] type: input -enable: {'output_pin TOOL' in printer} +enable: {'pwm_tool TOOL' in printer} name: Tool speed: {'%3d' % (menu.input*100)}% -input: {printer['output_pin TOOL'].value} +input: {printer['pwm_tool TOOL'].value} input_min: 0 input_max: 1 input_step: 0.01 diff --git a/docs/Config_Changes.md b/docs/Config_Changes.md index 6bfb123ae..2768c811f 100644 --- a/docs/Config_Changes.md +++ b/docs/Config_Changes.md @@ -8,6 +8,18 @@ All dates in this document are approximate. ## Changes +20231216: The `[hall_filament_width_sensor]` is changed to trigger filament runout +when the thickness of the filament exceeds `max_diameter`. The maximum diameter +defaults to `default_nominal_filament_diameter + max_difference`. See +[[hall_filament_width_sensor] configuration +reference](./Config_Reference.md#hall_filament_width_sensor) for more details. + +20231207: Several undocumented config parameters in the `[printer]` +config section have been removed (the buffer_time_low, +buffer_time_high, buffer_time_start, and move_flush_time parameters). + +20231110: Klipper v0.12.0 released. + 20230826: If `safe_distance` is set or calculated to be 0 in `[dual_carriage]`, the carriages proximity checks will be disabled as per documentation. A user may wish to configure `safe_distance` explicitly to prevent accidental crashes diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index eb9977a01..29c159d22 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -73,36 +73,43 @@ pins such as "extra_mcu:ar9" may then be used elsewhere in the config [mcu my_extra_mcu] # See the "mcu" section for configuration parameters. ``` + ## ⚠️ Danger Options + A collection of DangerKlipper-specific system options + ``` [danger_options] - -# If an unused config option or section should cause an error -# if False, will warn but allow klipper to still run -error_on_unused_config_options: True - -# If statistics should be logged -# (helpful for keeping the log clean during development) -log_statistics: True - -# If the config file should be logged at startup -log_config_file_at_startup: True - -# If the bed mesh should be logged on startup -# (helpful for keeping the log clean during development) -log_bed_mesh_at_startup: True - -# If we should log detailed crash info when an exception occurs -# Most of it is overly-verbose and fluff and we still get a stack trace -# for normal exceptions, so setting to False can help save time while developing -log_shutdown_info: True - -# Allows modules in `plugins` to override modules of the same name in `extras` -allow_plugin_override: False - -# The timeout (in seconds) for MCU synchronization during the homing process when multiple MCUs are in use. -multi_mcu_trsync_timeout: 0.025 +#error_on_unused_config_options: True +# If an unused config option or section should cause an error +# if False, will warn but allow klipper to still run. +# The default is True. +#log_statistics: True +# If statistics should be logged +# (helpful for keeping the log clean during development) +# The default is True. +#log_config_file_at_startup: True +# If the config file should be logged at startup +# The default is True. +#log_bed_mesh_at_startup: True +# If the bed mesh should be logged on startup +# (helpful for keeping the log clean during development) +# The default is True. +#log_shutdown_info: True +# If we should log detailed crash info when an exception occurs +# Most of it is overly-verbose and fluff and we still get a stack trace +# for normal exceptions, so setting to False can help save time while developing +# The default is True. +#allow_plugin_override: False +# Allows modules in `plugins` to override modules of the same name in `extras` +# The default is False. +#multi_mcu_trsync_timeout: 0.025 +# The timeout (in seconds) for MCU synchronization during the homing process when +# multiple MCUs are in use. The default is 0.025 +#homing_elapsed_distance_tolerance: 0.5 +# Tolerance (in mm) for distance moved in the second homing. Ensures the +# second homing distance closely matches the `min_home_dist` when using +# sensorless homing. The default is 0.5mm. ``` ## Common kinematic settings @@ -119,16 +126,22 @@ kinematics: # deltesian, polar, winch, or none. This parameter must be specified. max_velocity: # Maximum velocity (in mm/s) of the toolhead (relative to the -# print). This parameter must be specified. +# print). This value may be changed at runtime using the +# SET_VELOCITY_LIMIT command. This parameter must be specified. max_accel: # Maximum acceleration (in mm/s^2) of the toolhead (relative to the -# print). This parameter must be specified. +# print). Although this parameter is described as a "maximum" +# acceleration, in practice most moves that accelerate or decelerate +# will do so at the rate specified here. The value specified here +# may be changed at runtime using the SET_VELOCITY_LIMIT command. +# This parameter must be specified. #max_accel_to_decel: # A pseudo acceleration (in mm/s^2) controlling how fast the # toolhead may go from acceleration to deceleration. It is used to # reduce the top speed of short zig-zag moves (and thus reduce -# printer vibration from these moves). The default is half of -# max_accel. +# printer vibration from these moves). The value specified here may +# be changed at runtime using the SET_VELOCITY_LIMIT command. The +# default is half of max_accel. #square_corner_velocity: 5.0 # The maximum velocity (in mm/s) that the toolhead may travel a 90 # degree corner at. A non-zero value can reduce changes in extruder @@ -138,7 +151,9 @@ max_accel: # larger than 90 degrees will have a higher cornering velocity while # corners with angles less than 90 degrees will have a lower # cornering velocity. If this is set to zero then the toolhead will -# decelerate to zero at each corner. The default is 5mm/s. +# decelerate to zero at each corner. The value specified here may be +# changed at runtime using the SET_VELOCITY_LIMIT command. The +# default is 5mm/s. ``` ### [stepper] @@ -218,6 +233,11 @@ position_max: # Speed to use on the retract move after homing in case this should # be different from the homing speed, which is the default for this # parameter +#min_home_dist: +# Minimum distance (in mm) for toolhead before sensorless homing. If closer +# than `min_home_dist` to endstop, it moves away to this distance, then homes. +# If further, it directly homes and retracts to `homing_retract_dist`. +# The default is equal to `homing_retract_dist`. #second_homing_speed: # Velocity (in mm/s) of the stepper when performing the second home. # The default is homing_speed/2. @@ -360,7 +380,7 @@ example deltesian kinematics config file. Only parameters specific to deltesian printers are described here - see [common kinematic settings](#common-kinematic-settings) for available - parameters. +parameters. ``` [printer] @@ -909,6 +929,7 @@ See the [bed mesh guide](Bed_Mesh.md) and [command reference](G-Codes.md#bed_mesh) for additional information. Visual Examples: + ``` rectangular bed, probe_count = 3, 3: x---x---x (max_point) @@ -1162,15 +1183,7 @@ extended [G-Code command](G-Codes.md#z_tilt) becomes available. # stepper. It is described using nozzle coordinates (the X, Y position # of the nozzle if it could move directly above the point). The # first entry corresponds to stepper_z, the second to stepper_z1, -# the third to stepper_z2, etc. This parameter must be provided, -# unless the parameter "extra_points" is provided. In that case only -# the command Z_TILT_AUTODETECT can be run to automatically determine -# the z_positions. See 'extra_points' below. -#z_offsets: -# A list of Z offsets for each z_position. The z_offset is added to each -# probed value during Z_TILT_ADJUST to offset for unevenness of the bed. -# This values can also be automatically detected by running -# Z_TILT_CALIBRATE. See "extra_points" below. +# the third to stepper_z2, etc. This parameter must be provided. #points: # A list of X, Y coordinates (one per line; subsequent lines # indented) that should be probed during a Z_TILT_ADJUST command. @@ -1193,6 +1206,30 @@ extended [G-Code command](G-Codes.md#z_tilt) becomes available. # more points than steppers then you will likely have a fixed # minimum value for the range of probed points which you can learn # by observing command output. +``` + +``` +[z_tilt_ng] +#z_positions: +# See [z_tilt]. This parameter must be provided, +# unless the parameter "extra_points" is provided. In that case only +# the command Z_TILT_AUTODETECT can be run to automatically determine +# the z_positions. See 'extra_points' below. +#z_offsets: +# A list of Z offsets for each z_position. The z_offset is added to each +# probed value during Z_TILT_ADJUST to offset for unevenness of the bed. +# This values can also be automatically detected by running +# Z_TILT_CALIBRATE. See "extra_points" below. +#points: +# See [z_tilt] +#speed: 50 +# See [z_tilt] +#horizontal_move_z: 5 +# See [z_tilt] +#retries: 0 +# See [z_tilt] +#retry_tolerance: 0 +# See [z_tilt] #extra_points: # A list in the same format as "points" above. This list contains # additional points to be probed during the two calibration commands @@ -1229,6 +1266,7 @@ WARNING: Using this on a moving bed may lead to undesirable results. If this section is present then a QUAD_GANTRY_LEVEL extended G-Code command becomes available. This routine assumes the following Z motor configuration: + ``` ---------------- |Z1 Z2| @@ -1239,6 +1277,7 @@ configuration: |Z Z3| ---------------- ``` + Where x is the 0, 0 point on the bed ``` @@ -1647,6 +1686,7 @@ Enable the "M118" and "RESPOND" extended ``` ### [exclude_object] + Enables support to exclude or cancel individual objects during the printing process. @@ -1863,7 +1903,7 @@ aliases_: Include file support. One may include additional config file from the main printer config file. Wildcards may also be used (eg, -"configs/*.cfg"). +"configs/\*.cfg"). ``` [include my_other_config.cfg] @@ -2695,9 +2735,9 @@ sensor_pin: # name in the above list. ``` -### BMP280/BME280/BME680 temperature sensor +### BMP180/BMP280/BME280/BME680 temperature sensor -BMP280/BME280/BME680 two wire interface (I2C) environmental sensors. +BMP180/BMP280/BME280/BME680 two wire interface (I2C) environmental sensors. Note that these sensors are not intended for use with extruders and heater beds, but rather for monitoring ambient temperature (C), pressure (hPa), relative humidity and in case of the BME680 gas level. @@ -2708,7 +2748,7 @@ temperature. ``` sensor_type: BME280 #i2c_address: -# Default is 118 (0x76). Some BME280 sensors have an address of 119 +# Default is 118 (0x76). The BMP180 and some BME280 sensors have an address of 119 # (0x77). #i2c_mcu: #i2c_bus: @@ -2806,7 +2846,7 @@ sensor_type: LM75 ### Builtin micro-controller temperature sensor -The atsam, atsamd, and stm32 micro-controllers contain an internal +The atsam, atsamd, stm32 and rp2040 micro-controllers contain an internal temperature sensor. One can use the "temperature_mcu" sensor to monitor these temperatures. @@ -3384,6 +3424,27 @@ pin: # parameter. ``` +### [pwm_tool] + +Pulse width modulation digital output pins capable of high speed +updates (one may define any number of sections with an "output_pin" +prefix). Pins configured here will be setup as output pins and one may +modify them at run-time using "SET_PIN PIN=my_pin VALUE=.1" type +extended [g-code commands](G-Codes.md#output_pin). + +``` +[pwm_tool my_tool] +pin: +# The pin to configure as an output. This parameter must be provided. +#value: +#shutdown_value: +#maximum_mcu_duration: +#cycle_time: 0.100 +#hardware_pwm: False +#scale: +# See the "output_pin" section for the definition of these parameters. +``` + ### [static_digital_output] Statically configured digital output pins (one may define any number @@ -3464,6 +3525,9 @@ run_current: #home_current: # The amount of current (in amps RMS) to configure the driver to use # during homing procedures. The default is to not reduce the current. +#current_change_dwell_time: +# The amount of time (in seconds) to wait after changing homing current. +# The default is 0.5 seconds. #sense_resistor: 0.110 # The resistance (in ohms) of the motor sense resistor. The default # is 0.110 ohms. @@ -3560,6 +3624,9 @@ run_current: #home_current: # The amount of current (in amps RMS) to configure the driver to use # during homing procedures. The default is to not reduce the current. +#current_change_dwell_time: +# The amount of time (in seconds) to wait after changing homing current. +# The default is 0.5 seconds. #sense_resistor: 0.110 # The resistance (in ohms) of the motor sense resistor. The default # is 0.110 ohms. @@ -3604,6 +3671,7 @@ uart_pin: run_current: #hold_current: #home_current: +#current_change_dwell_time: #sense_resistor: 0.110 #stealthchop_threshold: 0 # See the "tmc2208" section for the definition of these parameters. @@ -3675,6 +3743,9 @@ run_current: #home_current: # The amount of current (in amps RMS) to configure the driver to use # during homing procedures. The default is to not reduce the current. +#current_change_dwell_time: +# The amount of time (in seconds) to wait after changing homing current. +# The default is 0.5 seconds. #sense_resistor: # The resistance (in ohms) of the motor sense resistor. This # parameter must be provided. @@ -3758,6 +3829,9 @@ run_current: #home_current: # The amount of current (in amps RMS) to configure the driver to use # during homing procedures. The default is to not reduce the current. +#current_change_dwell_time: +# The amount of time (in seconds) to wait after changing homing current. +# The default is 0.5 seconds. #rref: 12000 # The resistance (in ohms) of the resistor between IREF and GND. The # default is 12000. @@ -3882,6 +3956,9 @@ run_current: #home_current: # The amount of current (in amps RMS) to configure the driver to use # during homing procedures. The default is to not reduce the current. +#current_change_dwell_time: +# The amount of time (in seconds) to wait after changing homing current. +# The default is 0.5 seconds. #sense_resistor: 0.075 # The resistance (in ohms) of the motor sense resistor. The default # is 0.075 ohms. @@ -4673,6 +4750,9 @@ adc2: # command. #min_diameter: 1.0 # Minimal diameter for trigger virtual filament_switch_sensor. +#max_diameter: +# Maximum diameter for triggering virtual filament_switch_sensor. +# The default is default_nominal_filament_diameter + max_difference. #use_current_dia_while_delay: False # Use the current diameter instead of the nominal diameter while # the measurement delay has not run through. @@ -4882,8 +4962,8 @@ Octoprint as they will conflict, and 1 will fail to initialize properly likely aborting your print. If you use Octoprint and stream gcode over the serial port instead of -printing from virtual_sd, then remo **M1** and **M0** from *Pausing commands* -in *Settings > Serial Connection > Firmware & protocol* will prevent +printing from virtual_sd, then remo **M1** and **M0** from _Pausing commands_ +in _Settings > Serial Connection > Firmware & protocol_ will prevent the need to start print on the Palette 2 and unpausing in Octoprint for your print to begin. @@ -4906,7 +4986,7 @@ serial: ### [angle] Magnetic hall angle sensor support for reading stepper motor angle -shaft measurements using a1333, as5047d, or tle5012b SPI chips. The +shaft measurements using a1333, as5047d, or tle5012b SPI chips. The measurements are available via the [API Server](API_Server.md) and [motion analysis tool](Debugging.md#motion-analysis-and-data-logging). See the [G-Code reference](G-Codes.md#angle) for available commands. @@ -4974,8 +5054,8 @@ It is generally recommended to only use I2C devices that are on the same printed circuit board as the micro-controller. Most Klipper micro-controller implementations only support an -`i2c_speed` of 100000 (*standard mode*, 100kbit/s). The Klipper "Linux" -micro-controller supports a 400000 speed (*fast mode*, 400kbit/s), but it must be +`i2c_speed` of 100000 (_standard mode_, 100kbit/s). The Klipper "Linux" +micro-controller supports a 400000 speed (_fast mode_, 400kbit/s), but it must be [set in the operating system](RPi_microcontroller.md#optional-enabling-i2c) and the `i2c_speed` parameter is otherwise ignored. The Klipper "RP2040" micro-controller and ATmega AVR family support a rate of 400000 diff --git a/docs/Contact.md b/docs/Contact.md index 0d5a69ff2..b95578d6f 100644 --- a/docs/Contact.md +++ b/docs/Contact.md @@ -2,25 +2,18 @@ This document provides contact information for Klipper. -1. [Community Forum](#community-forum) -2. [Discord Chat](#discord-chat) -3. [I have a question about Klipper](#i-have-a-question-about-klipper) -4. [I have a feature request](#i-have-a-feature-request) -5. [Help! It doesn't work!](#help-it-doesnt-work) -6. [I found a bug in the Klipper software](#i-found-a-bug-in-the-klipper-software) -7. [I am making changes that I'd like to include in Klipper](#i-am-making-changes-that-id-like-to-include-in-klipper) -8. [Klipper github](#klipper-github) - -## Community Forum +## Discourse Forum There is a [Klipper Community Discourse server](https://community.klipper3d.org) -for discussions on Klipper. +for "forum" style discussions on Klipper. Note that Discourse is not +Discord. ## Discord Chat There is a Discord server dedicated to Klipper at: -[https://discord.klipper3d.org](https://discord.klipper3d.org). +[https://discord.klipper3d.org](https://discord.klipper3d.org). Note +that Discord is not Discourse. This server is run by a community of Klipper enthusiasts dedicated to discussions on Klipper. It allows users to chat with other users in @@ -33,37 +26,29 @@ Many questions we receive are already answered in the documentation and follow the directions provided there. It is also possible to search for similar questions in the -[Klipper Community Forum](#community-forum). +[Klipper Discourse Forum](#discourse-forum). If you are interested in sharing your knowledge and experience with other Klipper users then you can join the -[Klipper Community Forum](#community-forum) or +[Klipper Discourse Forum](#discourse-forum) or [Klipper Discord Chat](#discord-chat). Both are communities where Klipper users can discuss Klipper with other users. -Many questions we receive are general 3d-printing questions that are -not specific to Klipper. If you have a general question or are -experiencing general printing problems, then you will likely get a -better response by asking in a general 3d-printing forum or a forum -dedicated to your printer hardware. +If you have a general question or are experiencing general printing +problems, then also consider a general 3d-printing forum or a forum +dedicated to the printer hardware. ## I have a feature request All new features require someone interested and able to implement that feature. If you are interested in helping to implement or test a new feature, you can search for ongoing developments in the -[Klipper Community Forum](#community-forum). There is also +[Klipper Discourse Forum](#discourse-forum). There is also [Klipper Discord Chat](#discord-chat) for discussions between collaborators. ## Help! It doesn't work! -Unfortunately, we receive many more requests for help than we could -possibly answer. Most problem reports we see are eventually tracked -down to: -1. Subtle errors in the hardware, or -2. Not following all the steps described in the Klipper documentation. - If you are experiencing problems we recommend you carefully read the [Danger Klipper](Overview.md) and double check that all steps were followed. @@ -72,16 +57,15 @@ If you are experiencing a printing problem, then we recommend carefully inspecting the printer hardware (all joints, wires, screws, etc.) and verify nothing is abnormal. We find most printing problems are not related to the Klipper software. If you do find a problem with -the printer hardware then you will likely get a better response by -searching in a general 3d-printing forum or in a forum dedicated to -your printer hardware. +the printer hardware then consider searching general 3d-printing +forums or forums dedicated to the printer hardware. It is also possible to search for similar issues in the -[Klipper Community Forum](#community-forum). +[Klipper Discourse Forum](#discourse-forum). If you are interested in sharing your knowledge and experience with other Klipper users then you can join the -[Klipper Community Forum](#community-forum) or +[Klipper Discourse Forum](#discourse-forum) or [Klipper Discord Chat](#discord-chat). Both are communities where Klipper users can discuss Klipper with other users. @@ -91,7 +75,7 @@ Klipper is an open-source project and we appreciate when collaborators diagnose errors in the software. Problems should be reported in the -[Klipper Community Forum](#community-forum). +[Klipper Discourse Forum](#discourse-forum). There is important information that will be needed in order to fix a bug. Please follow these steps: @@ -110,23 +94,28 @@ bug. Please follow these steps: about the software and its environment (software version, hardware type, configuration, event timing, and hundreds of other questions). - 1. The Klipper log file is located in `/tmp/klippy.log` on the - Klipper "host" computer (the Raspberry Pi). - 2. An "scp" or "sftp" utility is needed to copy this log file to - your desktop computer. The "scp" utility comes standard with - Linux and MacOS desktops. There are freely available scp - utilities for other desktops (eg, WinSCP). If using a graphical - scp utility that can not directly copy `/tmp/klippy.log` then - repeatedly click on `..` or `parent folder` until you get to the - root directory, click on the `tmp` folder, and then select the - `klippy.log` file. - 3. Copy the log file to your desktop so that it can be attached to + 1. Dedicated Klipper web interfaces have the ability to directly + obtain the Klipper log file. It's the easiest way to obtain the + log when using one of these interfaces. Otherwise, an "scp" or + "sftp" utility is needed to copy the log file to your desktop + computer. The "scp" utility comes standard with Linux and MacOS + desktops. There are freely available scp utilities for other + desktops (eg, WinSCP). The log file may be located in the + `~/printer_data/logs/klippy.log` file (if using a graphical scp + utility, look for the "printer_data" folder, then the "logs" + folder under that, then the `klippy.log` file). The log file may + alternatively be located in the `/tmp/klippy.log` file (if using + a graphical scp utility that can not directly copy + `/tmp/klippy.log` then repeatedly click on `..` or + "parent folder" until reaching the root directory, click on + the `tmp` folder, and then select the `klippy.log` file). + 2. Copy the log file to your desktop so that it can be attached to an issue report. - 4. Do not modify the log file in any way; do not provide a snippet + 3. Do not modify the log file in any way; do not provide a snippet of the log. Only the full unmodified log file provides the necessary information. - 5. It is a good idea to compress the log file with zip or gzip. -5. Open a new topic on the [Klipper Community Forum](#community-forum) + 4. It is a good idea to compress the log file with zip or gzip. +5. Open a new topic on the [Klipper Discourse Forum](#discourse-forum) and provide a clear description of the problem. Other Klipper contributors will need to understand what steps were taken, what the desired outcome was, and what outcome actually occurred. The @@ -136,22 +125,10 @@ bug. Please follow these steps: Klipper is open-source software and we appreciate new contributions. -New contributions (for both code and documentation) are submitted via -Github Pull Requests. See the [CONTRIBUTING document](CONTRIBUTING.md) -for important information. +See the [CONTRIBUTING document](CONTRIBUTING.md) for information. There are several [documents for developers](Overview.md#developer-documentation). If you have questions on the code then you can also ask in the -[Klipper Community Forum](#community-forum) or on the -[Klipper Community Discord](#discord-chat). - -## Klipper github - -Klipper github may be used by contributors to share the status of -their work to improve Klipper. It is expected that the person opening -a github ticket is actively working on the given task and will be the -one performing all the work necessary to accomplish it. The Klipper -github is not used for requests, nor to report bugs, nor to ask -questions. Use the [Klipper Community Forum](#community-forum) or the -[Klipper Community Discord](#discord-chat) instead. +[Klipper Discourse Forum](#discourse-forum) or on the +[Klipper Discord Chat](#discord-chat). diff --git a/docs/Features.md b/docs/Features.md index 96b17a756..9c61ccf72 100644 --- a/docs/Features.md +++ b/docs/Features.md @@ -60,7 +60,7 @@ Klipper has several compelling features: if it is unable to). This makes it easier to use available hardware, to upgrade to new hardware, and to have confidence in the hardware. -* Portable code. Klipper works on ARM, AVR, and PRU based +* Portable code. Klipper works on ARM, AVR, PRU, and other micro-controllers. Existing "reprap" style printers can run Klipper without hardware modification - just add a Raspberry Pi. Klipper's internal code layout makes it easier to support other @@ -105,7 +105,8 @@ Klipper supports many standard 3d printer features: bed tilt detection or full mesh bed leveling. If the bed uses multiple Z steppers then Klipper can also level by independently manipulating the Z steppers. Most Z height probes are supported, - including BL-Touch probes and servo activated probes. + including BL-Touch probes and servo activated probes. Probes may be + calibrated for axis twist compensation. * Automatic delta calibration support. The calibration tool can perform basic height calibration as well as an enhanced X and Y @@ -117,10 +118,11 @@ Klipper supports many standard 3d printer features: * Support for common temperature sensors (eg, common thermistors, AD595, AD597, AD849x, PT100, PT1000, MAX6675, MAX31855, MAX31856, - MAX31865, BME280, HTU21D, DS18B20, and LM75). Custom thermistors and - custom analog temperature sensors can also be configured. One can - monitor the internal micro-controller temperature sensor and the - internal temperature sensor of a Raspberry Pi. + MAX31865, BME280, HTU21D, DS18B20, AHT10, and LM75). Custom + thermistors and custom analog temperature sensors can also be + configured. One can monitor the internal micro-controller + temperature sensor and the internal temperature sensor of a + Raspberry Pi. * Basic thermal heater protection enabled by default. @@ -129,9 +131,9 @@ Klipper supports many standard 3d printer features: speed can be monitored on fans that have a tachometer. * Support for run-time configuration of TMC2130, TMC2208/TMC2224, - TMC2209, TMC2660, and TMC5160 stepper motor drivers. There is also - support for current control of traditional stepper drivers via - AD5206, DAC084S085, MCP4451, MCP4728, MCP4018, and PWM pins. + TMC2209, TMC2240, TMC2660, and TMC5160 stepper motor drivers. There + is also support for current control of traditional stepper drivers + via AD5206, DAC084S085, MCP4451, MCP4728, MCP4018, and PWM pins. * Support for common LCD displays attached directly to the printer. A default menu is also available. The contents of the display and menu @@ -151,8 +153,8 @@ Klipper supports many standard 3d printer features: * Support for filament presence sensors, filament motion sensors, and filament width sensors. -* Support for measuring and recording acceleration using an adxl345, - mpu9250, and mpu6050 accelerometers. +* Support for measuring and recording acceleration using adxl345, + mpu9250, mpu6050, and lis2dw12 accelerometers. * Support for limiting the top speed of short "zigzag" moves to reduce printer vibration and noise. See the [kinematics](Kinematics.md) diff --git a/docs/G-Codes.md b/docs/G-Codes.md index d066b6c7b..eb64894dc 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -893,7 +893,8 @@ commands to manage the LED's color settings). ### [output_pin] The following command is available when an -[output_pin config section](Config_Reference.md#output_pin) is +[output_pin config section](Config_Reference.md#output_pin) or +[pwm_tool config section](Config_Reference.md#pwm_tool) is enabled. #### SET_PIN @@ -1348,8 +1349,11 @@ The toolhead module is automatically loaded. #### SET_VELOCITY_LIMIT `SET_VELOCITY_LIMIT [VELOCITY=] [ACCEL=] -[ACCEL_TO_DECEL=] [SQUARE_CORNER_VELOCITY=]`: Modify the -printer's velocity limits. +[ACCEL_TO_DECEL=] [SQUARE_CORNER_VELOCITY=]`: This +command can alter the velocity limits that were specified in the +printer config file. See the +[printer config section](Config_Reference.md#printer) for a +description of each parameter. ### [tuning_tower] @@ -1464,7 +1468,7 @@ adjustments to each Z stepper to compensate for tilt. See the PROBE command for details on the optional probe parameters. The optional `HORIZONTAL_MOVE_Z` value overrides the `horizontal_move_z` option specified in the config file. The follwing commands are availabe when the parameter "extra_points" is -configured in the z_tilt_section: +configured in the z_tilt_ng section: - `Z_TILT_CALIBRATE [AVGLEN=]`: This command does multiple probe runs similar to Z_TILT_ADJUST, but with the additional points given in "extra_points". This leads to a more balanced bed adjustment in case the diff --git a/docs/Installation.md b/docs/Installation.md index 0a4697175..1acfcf6bd 100644 --- a/docs/Installation.md +++ b/docs/Installation.md @@ -2,7 +2,7 @@ These instructions assume the software will run on a Raspberry Pi computer in conjunction with OctoPrint. It is recommended that a -Raspberry Pi 2, 3, or 4 computer be used as the host machine (see the +Raspberry Pi 2 (or later) be used as the host machine (see the [FAQ](FAQ.md#can-i-run-klipper-on-something-other-than-a-raspberry-pi-3) for other machines). @@ -50,7 +50,7 @@ using a Linux or MacOS desktop, then the "ssh" software should already be installed on the desktop. There are free ssh clients available for other desktops (eg, [PuTTY](https://www.chiark.greenend.org.uk/~sgtatham/putty/)). Use the -ssh utility to connect to the Raspberry Pi (ssh pi@octopi -- password +ssh utility to connect to the Raspberry Pi (`ssh pi@octopi` -- password is "raspberry") and run the following commands: ``` @@ -135,18 +135,18 @@ web page and then configure the following items: Navigate to the Settings tab (the wrench icon at the top of the page). Under "Serial Connection" in "Additional serial ports" add -"/tmp/printer". Then click "Save". +`/tmp/printer`. Then click "Save". Enter the Settings tab again and under "Serial Connection" change the -"Serial Port" setting to "/tmp/printer". +"Serial Port" setting to `/tmp/printer`. In the Settings tab, navigate to the "Behavior" sub-tab and select the "Cancel any ongoing prints but stay connected to the printer" option. Click "Save". From the main page, under the "Connection" section (at the top left of -the page) make sure the "Serial Port" is set to "/tmp/printer" and -click "Connect". (If "/tmp/printer" is not an available selection then +the page) make sure the "Serial Port" is set to `/tmp/printer` and +click "Connect". (If `/tmp/printer` is not an available selection then try reloading the page.) Once connected, navigate to the "Terminal" tab and type "status" @@ -165,8 +165,8 @@ Arguably the easiest way to set the Klipper configuration file is to use a desktop editor that supports editing files over the "scp" and/or "sftp" protocols. There are freely available tools that support this (eg, Notepad++, WinSCP, and Cyberduck). Load the printer config file -in the editor and then save it as a file named "printer.cfg" in the -home directory of the pi user (ie, /home/pi/printer.cfg). +in the editor and then save it as a file named `printer.cfg` in the +home directory of the pi user (ie, `/home/pi/printer.cfg`). Alternatively, one can also copy and edit the file directly on the Raspberry Pi via ssh. That may look something like the following (be diff --git a/docs/Releases.md b/docs/Releases.md index b3eca858b..3e9dd9391 100644 --- a/docs/Releases.md +++ b/docs/Releases.md @@ -3,6 +3,24 @@ History of Klipper releases. Please see [installation](Installation.md) for information on installing Klipper. +## Klipper 0.12.0 + +Available on 20231110. Major changes in this release: +* Support for COPY and MIRROR modes on IDEX printers. +* Several micro-controller improvements: + * Support for new ar100 and hc32f460 architectures. + * Support for stm32f7, stm32g0b0, stm32g07x, stm32g4, stm32h723, + n32g45x, samc21, and samd21j18 chip variants. + * Improved DFU and Katapult reboot handling. + * Improved performance on USB to CANbus bridge mode. + * Improved performance on "linux mcu". + * New support for software based i2c. +* New hardware support for tmc2240 stepper motor drivers, lis2dw12 + accelerometers, and aht10 temperature sensors. +* New axis_twist_compensation and temperature_combined modules added. +* New support for gcode arcs in XY, XZ, and YZ planes. +* Several bug fixes and code cleanups. + ## Klipper 0.11.0 Available on 20221128. Major changes in this release: diff --git a/docs/SDCard_Updates.md b/docs/SDCard_Updates.md index 77a586157..432446de7 100644 --- a/docs/SDCard_Updates.md +++ b/docs/SDCard_Updates.md @@ -170,7 +170,7 @@ BOARD_ALIASES = { If you need a new board definition and you are uncomfortable with the procedure outlined above it is recommended that you request one in -the [Klipper Community Discord](Contact.md#discord). +the [Klipper Discord](Contact.md). ## Flashing Boards that use SDIO diff --git a/docs/Status_Reference.md b/docs/Status_Reference.md index ce39cf0c4..52f16467b 100644 --- a/docs/Status_Reference.md +++ b/docs/Status_Reference.md @@ -188,6 +188,12 @@ The following information is available in the available is as follows. - `retract_state`: Returns 'True' if filament is retracted. +## gcode + +The following information is available in the `gcode` object: +- `commands`: Returns a list of all currently available commands. For each + command, if a help string is defined it will also be provided. + ## gcode_button The following information is available in @@ -338,7 +344,8 @@ is defined): ## output_pin The following information is available in -[output_pin some_name](Config_Reference.md#output_pin) objects: +[output_pin some_name](Config_Reference.md#output_pin) and +[pwm_tool some_name](Config_Reference.md#pwm_tool) objects: - `value`: The "value" of the pin, as set by a `SET_PIN` command. ## palette2 diff --git a/docs/Using_PWM_Tools.md b/docs/Using_PWM_Tools.md index a67df84d3..108ae37ad 100644 --- a/docs/Using_PWM_Tools.md +++ b/docs/Using_PWM_Tools.md @@ -1,7 +1,7 @@ # Using PWM tools This document describes how to setup a PWM-controlled laser or spindle -using `output_pin` and some macros. +using `pwm_tool` and some macros. ## How does it work? @@ -26,14 +26,6 @@ so that when your host or MCU encounters an error, the tool will stop. For an example configuration, see [config/sample-pwm-tool.cfg](/config/sample-pwm-tool.cfg). -## Current Limitations - -There is a limitation of how frequent PWM updates may occur. -While being very precise, a PWM update may only occur every 0.1 seconds, -rendering it almost useless for raster engraving. -However, there exists an [experimental branch](https://github.com/Cirromulus/klipper/tree/laser_tool) with its own tradeoffs. -In long term, it is planned to add this functionality to main-line klipper. - ## Commands `M3/M4 S` : Set PWM duty-cycle. Values between 0 and 255. diff --git a/klippy/chelper/__init__.py b/klippy/chelper/__init__.py index 2576105d5..608e62d1a 100644 --- a/klippy/chelper/__init__.py +++ b/klippy/chelper/__init__.py @@ -71,6 +71,8 @@ , uint64_t clock); int stepcompress_queue_msg(struct stepcompress *sc , uint32_t *data, int len); + int stepcompress_queue_mq_msg(struct stepcompress *sc, uint64_t req_clock + , uint32_t *data, int len); int stepcompress_extract_old(struct stepcompress *sc , struct pull_history_steps *p, int max , uint64_t start_clock, uint64_t end_clock); diff --git a/klippy/chelper/serialqueue.c b/klippy/chelper/serialqueue.c index e6810933a..c207495cd 100644 --- a/klippy/chelper/serialqueue.c +++ b/klippy/chelper/serialqueue.c @@ -222,12 +222,11 @@ handle_message(struct serialqueue *sq, double eventtime, int len) pthread_mutex_lock(&sq->lock); // Calculate receive sequence number - uint64_t rseq = ((sq->receive_seq & ~MESSAGE_SEQ_MASK) - | (sq->input_buf[MESSAGE_POS_SEQ] & MESSAGE_SEQ_MASK)); + uint32_t rseq_delta = ((sq->input_buf[MESSAGE_POS_SEQ] - sq->receive_seq) + & MESSAGE_SEQ_MASK); + uint64_t rseq = sq->receive_seq + rseq_delta; if (rseq != sq->receive_seq) { // New sequence number - if (rseq < sq->receive_seq) - rseq += MESSAGE_SEQ_MASK+1; if (rseq > sq->send_seq && sq->receive_seq != 1) { // An ack for a message not sent? Out of order message? sq->bytes_invalid += len; diff --git a/klippy/chelper/stepcompress.c b/klippy/chelper/stepcompress.c index e261f1d30..e5514b952 100644 --- a/klippy/chelper/stepcompress.c +++ b/klippy/chelper/stepcompress.c @@ -623,6 +623,21 @@ stepcompress_queue_msg(struct stepcompress *sc, uint32_t *data, int len) return 0; } +// Queue an mcu command that will consume space in the mcu move queue +int __visible +stepcompress_queue_mq_msg(struct stepcompress *sc, uint64_t req_clock + , uint32_t *data, int len) +{ + int ret = stepcompress_flush(sc, UINT64_MAX); + if (ret) + return ret; + + struct queue_message *qm = message_alloc_and_encode(data, len); + qm->min_clock = qm->req_clock = req_clock; + list_add_tail(&qm->node, &sc->msg_queue); + return 0; +} + // Return history of queue_step commands int __visible stepcompress_extract_old(struct stepcompress *sc, struct pull_history_steps *p diff --git a/klippy/chelper/stepcompress.h b/klippy/chelper/stepcompress.h index b8a950575..bfc0dfcde 100644 --- a/klippy/chelper/stepcompress.h +++ b/klippy/chelper/stepcompress.h @@ -29,6 +29,8 @@ int stepcompress_set_last_position(struct stepcompress *sc, uint64_t clock int64_t stepcompress_find_past_position(struct stepcompress *sc , uint64_t clock); int stepcompress_queue_msg(struct stepcompress *sc, uint32_t *data, int len); +int stepcompress_queue_mq_msg(struct stepcompress *sc, uint64_t req_clock + , uint32_t *data, int len); int stepcompress_extract_old(struct stepcompress *sc , struct pull_history_steps *p, int max , uint64_t start_clock, uint64_t end_clock); diff --git a/klippy/clocksync.py b/klippy/clocksync.py index 33859af70..5da85af3f 100644 --- a/klippy/clocksync.py +++ b/klippy/clocksync.py @@ -71,10 +71,8 @@ def _handle_clock(self, params): self.queries_pending = 0 # Extend clock to 64bit last_clock = self.last_clock - clock = (last_clock & ~0xFFFFFFFF) | params["clock"] - if clock < last_clock: - clock += 0x100000000 - self.last_clock = clock + clock_delta = (params["clock"] - last_clock) & 0xFFFFFFFF + self.last_clock = clock = last_clock + clock_delta # Check if this is the best round-trip-time seen so far sent_time = params["#sent_time"] if not sent_time: @@ -177,10 +175,9 @@ def estimated_print_time(self, eventtime): # misc commands def clock32_to_clock64(self, clock32): last_clock = self.last_clock - clock_diff = (last_clock - clock32) & 0xFFFFFFFF - if clock_diff & 0x80000000: - return last_clock + 0x100000000 - clock_diff - return last_clock - clock_diff + clock_diff = (clock32 - last_clock) & 0xFFFFFFFF + clock_diff -= (clock_diff & 0x80000000) << 1 + return last_clock + clock_diff def is_active(self): return self.queries_pending <= 4 diff --git a/klippy/configfile.py b/klippy/configfile.py index dc7215da7..385541740 100644 --- a/klippy/configfile.py +++ b/klippy/configfile.py @@ -297,7 +297,7 @@ def _find_autosave_data(self, data): autosave_data = data[pos + len(AUTOSAVE_HEADER) :].strip() # Check for errors and strip line prefixes if "\n#*# " in regular_data: - logging.warn( + logging.warning( "Can't read autosave from config file" " - autosave state corrupted" ) @@ -308,7 +308,7 @@ def _find_autosave_data(self, data): not line.startswith("#*#") or (len(line) >= 4 and not line.startswith("#*# ")) ) and autosave_data: - logging.warn( + logging.warning( "Can't read autosave from config file" " - modifications after header" ) @@ -350,7 +350,10 @@ def _parse_config_buffer(self, buffer, filename, fileconfig): data = "\n".join(buffer) del buffer[:] sbuffer = io.StringIO(data) - fileconfig.readfp(sbuffer, filename) + if sys.version_info.major >= 3: + fileconfig.read_file(sbuffer, filename) + else: + fileconfig.readfp(sbuffer, filename) def _resolve_include( self, source_filename, include_spec, fileconfig, visited diff --git a/klippy/extras/adc_temperature.py b/klippy/extras/adc_temperature.py index 54aefa568..29faf3097 100644 --- a/klippy/extras/adc_temperature.py +++ b/klippy/extras/adc_temperature.py @@ -107,7 +107,7 @@ def __init__(self, config, params): for temp, volt in params: adc = (volt - voltage_offset) / adc_voltage if adc < 0.0 or adc > 1.0: - logging.warn( + logging.warning( "Ignoring adc sample %.3f/%.3f in heater %s", temp, volt, diff --git a/klippy/extras/adxl345.py b/klippy/extras/adxl345.py index 7a38698ee..03e4d8b4b 100644 --- a/klippy/extras/adxl345.py +++ b/klippy/extras/adxl345.py @@ -392,9 +392,9 @@ def _extract_samples(self, raw_samples): count = seq = 0 samples = [None] * (len(raw_samples) * SAMPLES_PER_BLOCK) for params in raw_samples: - seq_diff = (last_sequence - params["sequence"]) & 0xFFFF + seq_diff = (params["sequence"] - last_sequence) & 0xFFFF seq_diff -= (seq_diff & 0x8000) << 1 - seq = last_sequence - seq_diff + seq = last_sequence + seq_diff d = bytearray(params["data"]) msg_cdiff = seq * SAMPLES_PER_BLOCK - chip_base for i in range(len(d) // BYTES_PER_SAMPLE): @@ -431,15 +431,11 @@ def _update_clock(self, minclock=0): else: raise self.printer.command_error("Unable to query adxl345 fifo") mcu_clock = self.mcu.clock32_to_clock64(params["clock"]) - sequence = (self.last_sequence & ~0xFFFF) | params["next_sequence"] - if sequence < self.last_sequence: - sequence += 0x10000 - self.last_sequence = sequence + seq_diff = (params["next_sequence"] - self.last_sequence) & 0xFFFF + self.last_sequence += seq_diff buffered = params["buffered"] - limit_count = (self.last_limit_count & ~0xFFFF) | params["limit_count"] - if limit_count < self.last_limit_count: - limit_count += 0x10000 - self.last_limit_count = limit_count + lc_diff = (params["limit_count"] - self.last_limit_count) & 0xFFFF + self.last_limit_count += lc_diff duration = params["query_ticks"] if duration > self.max_query_duration: # Skip measurement as a high query time could skew clock tracking @@ -449,7 +445,9 @@ def _update_clock(self, minclock=0): return self.max_query_duration = 2 * duration msg_count = ( - sequence * SAMPLES_PER_BLOCK + buffered // BYTES_PER_SAMPLE + fifo + self.last_sequence * SAMPLES_PER_BLOCK + + buffered // BYTES_PER_SAMPLE + + fifo ) # The "chip clock" is the message counter plus .5 for average # inaccuracy of query responses and plus .5 for assumed offset diff --git a/klippy/extras/angle.py b/klippy/extras/angle.py index 5175ef044..0cfb5e687 100644 --- a/klippy/extras/angle.py +++ b/klippy/extras/angle.py @@ -99,9 +99,9 @@ def apply_calibration(self, samples): cal2 = calibration[bucket + 1] adj = (angle & interp_mask) * (cal2 - cal1) adj = cal1 + ((adj + interp_round) >> interp_bits) - angle_diff = (angle - adj) & 0xFFFF + angle_diff = (adj - angle) & 0xFFFF angle_diff -= (angle_diff & 0x8000) << 1 - new_angle = angle - angle_diff + new_angle = angle + angle_diff if calibration_reversed: new_angle = -new_angle samples[i] = (samp_time, new_angle) @@ -436,9 +436,9 @@ def update_clock(self): mcu_clock, chip_clock = self._query_clock() mdiff = mcu_clock - self.last_chip_mcu_clock chip_mclock = self.last_chip_clock + int(mdiff * self.chip_freq + 0.5) - cdiff = (chip_mclock - chip_clock) & 0xFFFF + cdiff = (chip_clock - chip_mclock) & 0xFFFF cdiff -= (cdiff & 0x8000) << 1 - new_chip_clock = chip_mclock - cdiff + new_chip_clock = chip_mclock + cdiff self.chip_freq = float(new_chip_clock - self.last_chip_clock) / mdiff self.last_chip_clock = new_chip_clock self.last_chip_mcu_clock = mcu_clock @@ -579,21 +579,19 @@ def _extract_samples(self, raw_samples): count = error_count = 0 samples = [None] * (len(raw_samples) * 16) for params in raw_samples: - seq = (last_sequence & ~0xFFFF) | params["sequence"] - if seq < last_sequence: - seq += 0x10000 - last_sequence = seq + seq_diff = (params["sequence"] - last_sequence) & 0xFFFF + last_sequence += seq_diff d = bytearray(params["data"]) - msg_mclock = start_clock + seq * 16 * sample_ticks + msg_mclock = start_clock + last_sequence * 16 * sample_ticks for i in range(len(d) // 3): tcode = d[i * 3] if tcode == TCODE_ERROR: error_count += 1 continue raw_angle = d[i * 3 + 1] | (d[i * 3 + 2] << 8) - angle_diff = (last_angle - raw_angle) & 0xFFFF + angle_diff = (raw_angle - last_angle) & 0xFFFF angle_diff -= (angle_diff & 0x8000) << 1 - last_angle -= angle_diff + last_angle += angle_diff mclock = msg_mclock + i * sample_ticks if is_tcode_absolute: # tcode is tle5012b frame counter diff --git a/klippy/extras/bed_screws.py b/klippy/extras/bed_screws.py index 97aef3119..f0530633d 100644 --- a/klippy/extras/bed_screws.py +++ b/klippy/extras/bed_screws.py @@ -47,7 +47,12 @@ def reset(self): self.accepted_screws = 0 def move(self, coord, speed): - self.printer.lookup_object("toolhead").manual_move(coord, speed) + try: + self.printer.lookup_object("toolhead").manual_move(coord, speed) + except self.printer.command_error as e: + self.unregister_commands() + self.reset() + raise def move_to_screw(self, state, screw): # Move up, over, and then down diff --git a/klippy/extras/bme280.py b/klippy/extras/bme280.py index 73e3bad6b..176712691 100644 --- a/klippy/extras/bme280.py +++ b/klippy/extras/bme280.py @@ -8,6 +8,7 @@ REPORT_TIME = 0.8 BME280_CHIP_ADDR = 0x76 + BME280_REGS = { "RESET": 0xE0, "CTRL_HUM": 0xF2, @@ -74,6 +75,16 @@ 15: (1.0, 244.140625), } +BMP180_REGS = { + "RESET": 0xE0, + "CAL_1": 0xAA, + "CTRL_MEAS": 0xF4, + "REG_MSB": 0xF6, + "REG_LSB": 0xF7, + "CRV_TEMP": 0x2E, + "CRV_PRES": 0x34, +} + STATUS_MEASURING = 1 << 3 STATUS_IM_UPDATE = 1 MODE = 1 @@ -84,7 +95,7 @@ MEASURE_DONE = 1 << 5 RESET_CHIP_VALUE = 0xB6 -BME_CHIPS = {0x58: "BMP280", 0x60: "BME280", 0x61: "BME680"} +BME_CHIPS = {0x58: "BMP280", 0x60: "BME280", 0x61: "BME680", 0x55: "BMP180"} BME_CHIP_ID_REG = 0xD0 @@ -107,6 +118,15 @@ def get_signed_byte(bits): return get_twos_complement(bits, 8) +def get_unsigned_short_msb(bits): + return bits[0] << 8 | bits[1] + + +def get_signed_short_msb(bits): + val = get_unsigned_short_msb(bits) + return get_twos_complement(val, 16) + + class BME280: def __init__(self, config): self.printer = config.get_printer() @@ -225,6 +245,23 @@ def read_calibration_data_bme680(calib_data_1, calib_data_2): dig["G3"] = get_signed_byte(calib_data_2[13]) return dig + def read_calibration_data_bmp180(calib_data_1): + dig = {} + dig["AC1"] = get_signed_short_msb(calib_data_1[0:2]) + dig["AC2"] = get_signed_short_msb(calib_data_1[2:4]) + dig["AC3"] = get_signed_short_msb(calib_data_1[4:6]) + dig["AC4"] = get_unsigned_short_msb(calib_data_1[6:8]) + dig["AC5"] = get_unsigned_short_msb(calib_data_1[8:10]) + dig["AC6"] = get_unsigned_short_msb(calib_data_1[10:12]) + + dig["B1"] = get_signed_short_msb(calib_data_1[12:14]) + dig["B2"] = get_signed_short_msb(calib_data_1[14:16]) + + dig["MB"] = get_signed_short_msb(calib_data_1[16:18]) + dig["MC"] = get_signed_short_msb(calib_data_1[18:20]) + dig["MD"] = get_signed_short_msb(calib_data_1[20:22]) + return dig + chip_id = self.read_id() if chip_id not in BME_CHIPS.keys(): logging.info("bme280: Unknown Chip ID received %#x" % chip_id) @@ -240,15 +277,23 @@ def read_calibration_data_bme680(calib_data_1, calib_data_2): self.reactor.pause(self.reactor.monotonic() + 0.5) # Make sure non-volatile memory has been copied to registers - status = self.read_register("STATUS", 1)[0] - while status & STATUS_IM_UPDATE: - self.reactor.pause(self.reactor.monotonic() + 0.01) + if self.chip_type != "BMP180": + # BMP180 has no status register available status = self.read_register("STATUS", 1)[0] + while status & STATUS_IM_UPDATE: + self.reactor.pause(self.reactor.monotonic() + 0.01) + status = self.read_register("STATUS", 1)[0] if self.chip_type == "BME680": self.max_sample_time = 0.5 self.sample_timer = self.reactor.register_timer(self._sample_bme680) self.chip_registers = BME680_REGS + elif self.chip_type == "BMP180": + self.max_sample_time = ( + 1.25 + ((2.3 * self.os_pres) + 0.575) + ) / 1000 + self.sample_timer = self.reactor.register_timer(self._sample_bmp180) + self.chip_registers = BMP180_REGS else: self.max_sample_time = ( 1.25 @@ -263,14 +308,19 @@ def read_calibration_data_bme680(calib_data_1, calib_data_2): self.write_register("CONFIG", (self.iir_filter & 0x07) << 2) # Read out and calculate the trimming parameters - cal_1 = self.read_register("CAL_1", 26) - cal_2 = self.read_register("CAL_2", 16) + if self.chip_type == "BMP180": + cal_1 = self.read_register("CAL_1", 22) + else: + cal_1 = self.read_register("CAL_1", 26) + cal_2 = self.read_register("CAL_2", 16) if self.chip_type == "BME280": self.dig = read_calibration_data_bme280(cal_1, cal_2) elif self.chip_type == "BMP280": self.dig = read_calibration_data_bmp280(cal_1) elif self.chip_type == "BME680": self.dig = read_calibration_data_bme680(cal_1, cal_2) + elif self.chip_type == "BMP180": + self.dig = read_calibration_data_bmp180(cal_1) def _sample_bme280(self, eventtime): # Enter forced mode @@ -381,6 +431,44 @@ def data_ready(stat): self._callback(self.mcu.estimated_print_time(measured_time), self.temp) return measured_time + REPORT_TIME + def _sample_bmp180(self, eventtime): + meas = self.chip_registers["CRV_TEMP"] + self.write_register("CTRL_MEAS", meas) + + try: + self.reactor.pause(self.reactor.monotonic() + 0.01) + data = self.read_register("REG_MSB", 2) + temp_raw = (data[0] << 8) | data[1] + except Exception: + logging.exception("BMP180: Error reading temperature") + self.temp = self.pressure = 0.0 + return self.reactor.NEVER + + meas = self.chip_registers["CRV_PRES"] | (self.os_pres << 6) + self.write_register("CTRL_MEAS", meas) + + try: + self.reactor.pause(self.reactor.monotonic() + 0.01) + data = self.read_register("REG_MSB", 3) + pressure_raw = ((data[0] << 16) | (data[1] << 8) | data[2]) >> ( + 8 - self.os_pres + ) + except Exception: + logging.exception("BMP180: Error reading pressure") + self.temp = self.pressure = 0.0 + return self.reactor.NEVER + + self.temp = self._compensate_temp_bmp180(temp_raw) + self.pressure = self._compensate_pressure_bmp180(pressure_raw) / 100.0 + if self.temp < self.min_temp or self.temp > self.max_temp: + self.printer.invoke_shutdown( + "BMP180 temperature %0.1f outside range of %0.1f:%.01f" + % (self.temp, self.min_temp, self.max_temp) + ) + measured_time = self.reactor.monotonic() + self._callback(self.mcu.estimated_print_time(measured_time), self.temp) + return measured_time + REPORT_TIME + def _compensate_temp(self, raw_temp): dig = self.dig var1 = (raw_temp / 16384.0 - (dig["T1"] / 1024.0)) * dig["T2"] @@ -519,6 +607,37 @@ def _calculate_gas_heater_duration(self, duration_ms): return duration_reg + def _compensate_temp_bmp180(self, raw_temp): + dig = self.dig + x1 = (raw_temp - dig["AC6"]) * dig["AC5"] / 32768.0 + x2 = dig["MC"] * 2048 / (x1 + dig["MD"]) + b5 = x1 + x2 + self.t_fine = b5 + return (b5 + 8) / 16.0 / 10.0 + + def _compensate_pressure_bmp180(self, raw_pressure): + dig = self.dig + b5 = self.t_fine + b6 = b5 - 4000 + x1 = (dig["B2"] * (b6 * b6 / 4096)) / 2048 + x2 = dig["AC2"] * b6 / 2048 + x3 = x1 + x2 + b3 = ((int(dig["AC1"] * 4 + x3) << self.os_pres) + 2) / 4 + x1 = dig["AC3"] * b6 / 8192 + x2 = (dig["B1"] * (b6 * b6 / 4096)) / 65536 + x3 = ((x1 + x2) + 2) / 4 + b4 = dig["AC4"] * (x3 + 32768) / 32768 + b7 = (raw_pressure - b3) * (50000 >> self.os_pres) + if b7 < 0x80000000: + p = (b7 * 2) / b4 + else: + p = (b7 / b4) * 2 + x1 = (p / 256) * (p / 256) + x1 = (x1 * 3038) / 65536 + x2 = (-7357 * p) / 65536 + p = p + (x1 + x2 + 3791) / 16.0 + return p + def read_id(self): # read chip id register regs = [BME_CHIP_ID_REG] diff --git a/klippy/extras/buttons.py b/klippy/extras/buttons.py index f94918d92..e4415eb76 100644 --- a/klippy/extras/buttons.py +++ b/klippy/extras/buttons.py @@ -67,10 +67,9 @@ def build_config(self): def handle_buttons_state(self, params): # Expand the message ack_count from 8-bit ack_count = self.ack_count - ack_diff = (ack_count - params["ack_count"]) & 0xFF - if ack_diff & 0x80: - ack_diff -= 0x100 - msg_ack_count = ack_count - ack_diff + ack_diff = (params["ack_count"] - ack_count) & 0xFF + ack_diff -= (ack_diff & 0x80) << 1 + msg_ack_count = ack_count + ack_diff # Determine new buttons buttons = bytearray(params["state"]) new_count = msg_ack_count + len(buttons) - self.ack_count diff --git a/klippy/extras/danger_options.py b/klippy/extras/danger_options.py index 314b6f092..71774a278 100644 --- a/klippy/extras/danger_options.py +++ b/klippy/extras/danger_options.py @@ -19,6 +19,9 @@ def __init__(self, config): self.multi_mcu_trsync_timeout = config.getfloat( "multi_mcu_trsync_timeout", 0.025, minval=0.0 ) + self.homing_elapsed_distance_tolerance = config.getfloat( + "homing_elapsed_distance_tolerance", 0.5, minval=0.0 + ) def load_config(config): diff --git a/klippy/extras/exclude_object.py b/klippy/extras/exclude_object.py index a39326bb0..75d52f463 100644 --- a/klippy/extras/exclude_object.py +++ b/klippy/extras/exclude_object.py @@ -268,7 +268,7 @@ def cmd_EXCLUDE_OBJECT(self, gcmd): elif current: if not self.current_object: - gcmd.respond_error("There is no current object to cancel") + raise self.gcode.error("There is no current object to cancel") else: self._exclude_object(self.current_object) diff --git a/klippy/extras/hall_filament_width_sensor.py b/klippy/extras/hall_filament_width_sensor.py index da4e302a5..ffe5368e6 100644 --- a/klippy/extras/hall_filament_width_sensor.py +++ b/klippy/extras/hall_filament_width_sensor.py @@ -34,7 +34,8 @@ def __init__(self, config): ) self.diameter = self.nominal_filament_dia self.is_active = config.getboolean("enable", False) - self.runout_dia = config.getfloat("min_diameter", 1.0) + self.runout_dia_min = config.getfloat("min_diameter", 1.0) + self.runout_dia_max = config.getfloat("max_diameter", self.max_diameter) self.is_log = config.getboolean("logging", False) # Use the current diameter instead of nominal while the first # measurement isn't in place @@ -150,7 +151,7 @@ def extrude_factor_update_event(self, eventtime): self.update_filament_array(last_epos) # Check runout self.runout_helper.note_filament_present( - self.diameter > self.runout_dia + self.runout_dia_min <= self.diameter <= self.runout_dia_max ) # Does filament exists if self.diameter > 0.5: diff --git a/klippy/extras/homing.py b/klippy/extras/homing.py index 12decd06d..adfd9d062 100644 --- a/klippy/extras/homing.py +++ b/klippy/extras/homing.py @@ -50,6 +50,7 @@ def __init__(self, printer, endstops, toolhead=None): self.toolhead = toolhead self.stepper_positions = [] self.distance_elapsed = [] + self.danger_options = printer.lookup_object("danger_options") def get_mcu_endstops(self): return [es for es, name in self.endstops] @@ -197,6 +198,25 @@ def check_no_movement(self): return sp.endstop_name return None + def moved_less_than_dist(self, min_dist, homing_axes): + homing_axis_distances = [ + dist + for i, dist in enumerate(self.distance_elapsed) + if i in homing_axes + ] + distance_tolerance = ( + self.danger_options.homing_elapsed_distance_tolerance + ) + if any( + [ + abs(dist) < min_dist + and min_dist - abs(dist) >= distance_tolerance + for dist in homing_axis_distances + ] + ): + return True + return False + # State tracking of homing requests class Homing: @@ -240,9 +260,9 @@ def _set_current_homing(self, homing_axes): for rail in affected_rails: ch = rail.get_tmc_current_helper() - if ch is not None: + if ch is not None and ch.needs_home_current_change(): ch.set_current_for_homing(print_time) - self.toolhead.dwell(0.5) + self.toolhead.dwell(ch.current_change_dwell_time) def _set_current_post_homing(self, homing_axes): print_time = self.toolhead.get_last_move_time() @@ -254,9 +274,9 @@ def _set_current_post_homing(self, homing_axes): for rail in affected_rails: ch = rail.get_tmc_current_helper() - if ch is not None: + if ch is not None and ch.needs_home_current_change(): ch.set_current_for_normal(print_time) - self.toolhead.dwell(0.5) + self.toolhead.dwell(ch.current_change_dwell_time) def home_rails(self, rails, forcepos, movepos): # Notify of upcoming homing operation @@ -275,31 +295,26 @@ def home_rails(self, rails, forcepos, movepos): hmove.homing_move(homepos, hi.speed) - homing_axis_distances = [ - dist - for i, dist in enumerate(hmove.distance_elapsed) - if i in homing_axes - ] + needs_rehome = False + retract_dist = hi.retract_dist + if hmove.moved_less_than_dist(hi.min_home_dist, homing_axes): + needs_rehome = True + retract_dist = hi.min_home_dist # Perform second home - if hi.retract_dist: - needs_rehome = False - if any([dist < hi.retract_dist for dist in homing_axis_distances]): - needs_rehome = True - + if retract_dist: logging.info("needs rehome: %s", needs_rehome) # Retract startpos = self._fill_coord(forcepos) homepos = self._fill_coord(movepos) axes_d = [hp - sp for hp, sp in zip(homepos, startpos)] move_d = math.sqrt(sum([d * d for d in axes_d[:3]])) - retract_r = min(1.0, hi.retract_dist / move_d) + retract_r = min(1.0, retract_dist / move_d) retractpos = [ hp - ad * retract_r for hp, ad in zip(homepos, axes_d) ] self.toolhead.move(retractpos, hi.retract_speed) if not hi.use_sensorless_homing or needs_rehome: - self.toolhead.dwell(0.5) # Home again startpos = [ rp - ad * retract_r for rp, ad in zip(retractpos, axes_d) @@ -309,6 +324,7 @@ def home_rails(self, rails, forcepos, movepos): for endstop in endstops: # re-querying a tmc endstop seems to reset the state # otherwise it triggers almost immediately upon second home + # this seems to be an adequate substitute for a 2 second dwell. endstop[0].query_endstop(print_time) hmove = HomingMove(self.printer, endstops) hmove.homing_move(homepos, hi.second_homing_speed) @@ -317,17 +333,27 @@ def home_rails(self, rails, forcepos, movepos): "Endstop %s still triggered after retract" % (hmove.check_no_movement(),) ) - - # Retract (again) - startpos = self._fill_coord(forcepos) - homepos = self._fill_coord(movepos) - axes_d = [hp - sp for hp, sp in zip(homepos, startpos)] - move_d = math.sqrt(sum([d * d for d in axes_d[:3]])) - retract_r = min(1.0, hi.retract_dist / move_d) - retractpos = [ - hp - ad * retract_r for hp, ad in zip(homepos, axes_d) - ] - self.toolhead.move(retractpos, hi.retract_speed) + if ( + hi.use_sensorless_homing + and needs_rehome + and hmove.moved_less_than_dist( + hi.min_home_dist, homing_axes + ) + ): + raise self.printer.command_error( + "Early homing trigger on second home!" + ) + if hi.retract_dist: + # Retract (again) + startpos = self._fill_coord(forcepos) + homepos = self._fill_coord(movepos) + axes_d = [hp - sp for hp, sp in zip(homepos, startpos)] + move_d = math.sqrt(sum([d * d for d in axes_d[:3]])) + retract_r = min(1.0, hi.retract_dist / move_d) + retractpos = [ + hp - ad * retract_r for hp, ad in zip(homepos, axes_d) + ] + self.toolhead.move(retractpos, hi.retract_speed) self._set_current_post_homing(homing_axes) # Signal home operation complete self.toolhead.flush_step_generation() diff --git a/klippy/extras/htu21d.py b/klippy/extras/htu21d.py index fc7d6f700..c1d6ca3d4 100644 --- a/klippy/extras/htu21d.py +++ b/klippy/extras/htu21d.py @@ -141,7 +141,7 @@ def _init_htu21d(self): rdevId |= response[1] checksum = response[2] if self._chekCRC8(rdevId) != checksum: - logging.warn("htu21d: Reading deviceId !Checksum error!") + logging.warning("htu21d: Reading deviceId !Checksum error!") rdevId = rdevId >> 8 deviceId_list = list( filter( @@ -152,10 +152,10 @@ def _init_htu21d(self): if len(deviceId_list) != 0: logging.info("htu21d: Found Device Type %s" % deviceId_list[0]) else: - logging.warn("htu21d: Unknown Device ID %#x " % rdevId) + logging.warning("htu21d: Unknown Device ID %#x " % rdevId) if self.deviceId != deviceId_list[0]: - logging.warn( + logging.warning( "htu21d: Found device %s. Forcing to type %s as config.", deviceId_list[0], self.deviceId, @@ -189,7 +189,9 @@ def _sample_htu21d(self, eventtime): rtemp = response[0] << 8 rtemp |= response[1] if self._chekCRC8(rtemp) != response[2]: - logging.warn("htu21d: Checksum error on Temperature reading!") + logging.warning( + "htu21d: Checksum error on Temperature reading!" + ) else: self.temp = 0.002681 * float(rtemp) - 46.85 logging.debug("htu21d: Temperature %.2f " % self.temp) @@ -212,7 +214,7 @@ def _sample_htu21d(self, eventtime): rhumid = response[0] << 8 rhumid |= response[1] if self._chekCRC8(rhumid) != response[2]: - logging.warn("htu21d: Checksum error on Humidity reading!") + logging.warning("htu21d: Checksum error on Humidity reading!") else: # clear status bits, # humidity always returns xxxxxx10 in the LSB field diff --git a/klippy/extras/lis2dw.py b/klippy/extras/lis2dw.py index bfddb0177..dd23c672d 100644 --- a/klippy/extras/lis2dw.py +++ b/klippy/extras/lis2dw.py @@ -144,9 +144,9 @@ def _extract_samples(self, raw_samples): count = seq = 0 samples = [None] * (len(raw_samples) * SAMPLES_PER_BLOCK) for params in raw_samples: - seq_diff = (last_sequence - params["sequence"]) & 0xFFFF + seq_diff = (params["sequence"] - last_sequence) & 0xFFFF seq_diff -= (seq_diff & 0x8000) << 1 - seq = last_sequence - seq_diff + seq = last_sequence + seq_diff d = bytearray(params["data"]) msg_cdiff = seq * SAMPLES_PER_BLOCK - chip_base @@ -184,15 +184,11 @@ def _update_clock(self, minclock=0): else: raise self.printer.command_error("Unable to query lis2dw fifo") mcu_clock = self.mcu.clock32_to_clock64(params["clock"]) - sequence = (self.last_sequence & ~0xFFFF) | params["next_sequence"] - if sequence < self.last_sequence: - sequence += 0x10000 - self.last_sequence = sequence + seq_diff = (params["next_sequence"] - self.last_sequence) & 0xFFFF + self.last_sequence += seq_diff buffered = params["buffered"] - limit_count = (self.last_limit_count & ~0xFFFF) | params["limit_count"] - if limit_count < self.last_limit_count: - limit_count += 0x10000 - self.last_limit_count = limit_count + lc_diff = (params["limit_count"] - self.last_limit_count) & 0xFFFF + self.last_limit_count += lc_diff duration = params["query_ticks"] if duration > self.max_query_duration: # Skip measurement as a high query time could skew clock tracking @@ -202,7 +198,9 @@ def _update_clock(self, minclock=0): return self.max_query_duration = 2 * duration msg_count = ( - sequence * SAMPLES_PER_BLOCK + buffered // BYTES_PER_SAMPLE + fifo + self.last_sequence * SAMPLES_PER_BLOCK + + buffered // BYTES_PER_SAMPLE + + fifo ) # The "chip clock" is the message counter plus .5 for average # inaccuracy of query responses and plus .5 for assumed offset diff --git a/klippy/extras/mpu9250.py b/klippy/extras/mpu9250.py index 3368b05fc..9c67aa85a 100644 --- a/klippy/extras/mpu9250.py +++ b/klippy/extras/mpu9250.py @@ -161,9 +161,9 @@ def _extract_samples(self, raw_samples): count = seq = 0 samples = [None] * (len(raw_samples) * SAMPLES_PER_BLOCK) for params in raw_samples: - seq_diff = (last_sequence - params["sequence"]) & 0xFFFF + seq_diff = (params["sequence"] - last_sequence) & 0xFFFF seq_diff -= (seq_diff & 0x8000) << 1 - seq = last_sequence - seq_diff + seq = last_sequence + seq_diff d = bytearray(params["data"]) msg_cdiff = seq * SAMPLES_PER_BLOCK - chip_base @@ -198,15 +198,11 @@ def _update_clock(self, minclock=0): else: raise self.printer.command_error("Unable to query mpu9250 fifo") mcu_clock = self.mcu.clock32_to_clock64(params["clock"]) - sequence = (self.last_sequence & ~0xFFFF) | params["next_sequence"] - if sequence < self.last_sequence: - sequence += 0x10000 - self.last_sequence = sequence + seq_diff = (params["next_sequence"] - self.last_sequence) & 0xFFFF + self.last_sequence += seq_diff buffered = params["buffered"] - limit_count = (self.last_limit_count & ~0xFFFF) | params["limit_count"] - if limit_count < self.last_limit_count: - limit_count += 0x10000 - self.last_limit_count = limit_count + lc_diff = (params["limit_count"] - self.last_limit_count) & 0xFFFF + self.last_limit_count += lc_diff duration = params["query_ticks"] if duration > self.max_query_duration: # Skip measurement as a high query time could skew clock tracking @@ -216,7 +212,9 @@ def _update_clock(self, minclock=0): return self.max_query_duration = 2 * duration msg_count = ( - sequence * SAMPLES_PER_BLOCK + buffered // BYTES_PER_SAMPLE + fifo + self.last_sequence * SAMPLES_PER_BLOCK + + buffered // BYTES_PER_SAMPLE + + fifo ) # The "chip clock" is the message counter plus .5 for average # inaccuracy of query responses and plus .5 for assumed offset diff --git a/klippy/extras/pwm_tool.py b/klippy/extras/pwm_tool.py new file mode 100644 index 000000000..d89293a51 --- /dev/null +++ b/klippy/extras/pwm_tool.py @@ -0,0 +1,236 @@ +# Queued PWM gpio output +# +# Copyright (C) 2017-2023 Kevin O'Connor +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import chelper + +MAX_SCHEDULE_TIME = 5.0 +CLOCK_SYNC_EXTRA_TIME = 0.050 + + +class error(Exception): + pass + + +class MCU_queued_pwm: + def __init__(self, pin_params): + self._mcu = pin_params["chip"] + self._hardware_pwm = False + self._cycle_time = 0.100 + self._max_duration = 2.0 + self._oid = self._mcu.create_oid() + ffi_main, ffi_lib = chelper.get_ffi() + self._stepqueue = ffi_main.gc( + ffi_lib.stepcompress_alloc(self._oid), ffi_lib.stepcompress_free + ) + self._mcu.register_stepqueue(self._stepqueue) + self._stepcompress_queue_mq_msg = ffi_lib.stepcompress_queue_mq_msg + self._mcu.register_config_callback(self._build_config) + self._pin = pin_params["pin"] + self._invert = pin_params["invert"] + self._start_value = self._shutdown_value = float(self._invert) + self._last_clock = self._last_value = self._default_value = 0 + self._duration_ticks = 0 + self._pwm_max = 0.0 + self._set_cmd_tag = None + self._toolhead = None + printer = self._mcu.get_printer() + printer.register_event_handler("klippy:connect", self._handle_connect) + + def _handle_connect(self): + self._toolhead = self._mcu.get_printer().lookup_object("toolhead") + + def get_mcu(self): + return self._mcu + + def setup_max_duration(self, max_duration): + self._max_duration = max_duration + + def setup_cycle_time(self, cycle_time, hardware_pwm=False): + self._cycle_time = cycle_time + self._hardware_pwm = hardware_pwm + + def setup_start_value(self, start_value, shutdown_value): + if self._invert: + start_value = 1.0 - start_value + shutdown_value = 1.0 - shutdown_value + self._start_value = max(0.0, min(1.0, start_value)) + self._shutdown_value = max(0.0, min(1.0, shutdown_value)) + + def _build_config(self): + config_error = self._mcu.get_printer().config_error + if self._max_duration and self._start_value != self._shutdown_value: + raise config_error( + "Pin with max duration must have start" + " value equal to shutdown value" + ) + cmd_queue = self._mcu.alloc_command_queue() + curtime = self._mcu.get_printer().get_reactor().monotonic() + printtime = self._mcu.estimated_print_time(curtime) + self._last_clock = self._mcu.print_time_to_clock(printtime + 0.200) + cycle_ticks = self._mcu.seconds_to_clock(self._cycle_time) + if cycle_ticks >= 1 << 31: + raise config_error("PWM pin cycle time too large") + self._duration_ticks = self._mcu.seconds_to_clock(self._max_duration) + if self._duration_ticks >= 1 << 31: + raise config_error("PWM pin max duration too large") + if self._duration_ticks: + self._mcu.register_flush_callback(self._flush_notification) + if self._hardware_pwm: + self._pwm_max = self._mcu.get_constant_float("PWM_MAX") + self._default_value = self._shutdown_value * self._pwm_max + self._mcu.add_config_cmd( + "config_pwm_out oid=%d pin=%s cycle_ticks=%d value=%d" + " default_value=%d max_duration=%d" + % ( + self._oid, + self._pin, + cycle_ticks, + self._start_value * self._pwm_max, + self._default_value, + self._duration_ticks, + ) + ) + self._last_value = int(self._start_value * self._pwm_max + 0.5) + self._mcu.add_config_cmd( + "queue_pwm_out oid=%d clock=%d value=%d" + % (self._oid, self._last_clock, self._last_value), + on_restart=True, + ) + self._set_cmd_tag = self._mcu.lookup_command( + "queue_pwm_out oid=%c clock=%u value=%hu", cq=cmd_queue + ).get_command_tag() + return + # Software PWM + if self._shutdown_value not in [0.0, 1.0]: + raise config_error("shutdown value must be 0.0 or 1.0 on soft pwm") + self._mcu.add_config_cmd( + "config_digital_out oid=%d pin=%s value=%d" + " default_value=%d max_duration=%d" + % ( + self._oid, + self._pin, + self._start_value >= 1.0, + self._shutdown_value >= 0.5, + self._duration_ticks, + ) + ) + self._default_value = int(self._shutdown_value >= 0.5) * cycle_ticks + self._mcu.add_config_cmd( + "set_digital_out_pwm_cycle oid=%d cycle_ticks=%d" + % (self._oid, cycle_ticks) + ) + self._pwm_max = float(cycle_ticks) + self._last_value = int(self._start_value * self._pwm_max + 0.5) + self._mcu.add_config_cmd( + "queue_digital_out oid=%d clock=%d on_ticks=%d" + % (self._oid, self._last_clock, self._last_value), + is_init=True, + ) + self._set_cmd_tag = self._mcu.lookup_command( + "queue_digital_out oid=%c clock=%u on_ticks=%u", cq=cmd_queue + ).get_command_tag() + + def _send_update(self, clock, val): + self._last_clock = clock = max(self._last_clock, clock) + self._last_value = val + data = (self._set_cmd_tag, self._oid, clock & 0xFFFFFFFF, val) + ret = self._stepcompress_queue_mq_msg( + self._stepqueue, clock, data, len(data) + ) + if ret: + raise error("Internal error in stepcompress") + # Notify toolhead so that it will flush this update + wakeclock = clock + if self._last_value != self._default_value: + # Continue flushing to resend time + wakeclock += self._duration_ticks + wake_print_time = self._mcu.clock_to_print_time(wakeclock) + self._toolhead.note_kinematic_activity( + wake_print_time + CLOCK_SYNC_EXTRA_TIME + ) + + def set_pwm(self, print_time, value): + clock = self._mcu.print_time_to_clock(print_time) + if self._invert: + value = 1.0 - value + v = int(max(0.0, min(1.0, value)) * self._pwm_max + 0.5) + self._send_update(clock, v) + + def _flush_notification(self, print_time, clock): + if self._last_value != self._default_value: + while clock >= self._last_clock + self._duration_ticks: + self._send_update( + self._last_clock + self._duration_ticks, self._last_value + ) + + +class PrinterOutputPin: + def __init__(self, config): + self.printer = config.get_printer() + ppins = self.printer.lookup_object("pins") + # Determine pin type + pin_params = ppins.lookup_pin(config.get("pin"), can_invert=True) + self.mcu_pin = MCU_queued_pwm(pin_params) + cycle_time = config.getfloat( + "cycle_time", 0.100, above=0.0, maxval=MAX_SCHEDULE_TIME + ) + hardware_pwm = config.getboolean("hardware_pwm", False) + self.mcu_pin.setup_cycle_time(cycle_time, hardware_pwm) + self.scale = config.getfloat("scale", 1.0, above=0.0) + self.last_print_time = 0.0 + # Support mcu checking for maximum duration + max_mcu_duration = config.getfloat( + "maximum_mcu_duration", 0.0, minval=0.500, maxval=MAX_SCHEDULE_TIME + ) + self.mcu_pin.setup_max_duration(max_mcu_duration) + # Determine start and shutdown values + self.last_value = ( + config.getfloat("value", 0.0, minval=0.0, maxval=self.scale) + / self.scale + ) + self.shutdown_value = ( + config.getfloat( + "shutdown_value", 0.0, minval=0.0, maxval=self.scale + ) + / self.scale + ) + self.mcu_pin.setup_start_value(self.last_value, self.shutdown_value) + # Register commands + pin_name = config.get_name().split()[1] + gcode = self.printer.lookup_object("gcode") + gcode.register_mux_command( + "SET_PIN", + "PIN", + pin_name, + self.cmd_SET_PIN, + desc=self.cmd_SET_PIN_help, + ) + + def get_status(self, eventtime): + return {"value": self.last_value} + + def _set_pin(self, print_time, value): + if value == self.last_value: + return + print_time = max(print_time, self.last_print_time) + self.mcu_pin.set_pwm(print_time, value) + self.last_value = value + self.last_print_time = print_time + + cmd_SET_PIN_help = "Set the value of an output pin" + + def cmd_SET_PIN(self, gcmd): + # Read requested value + value = gcmd.get_float("VALUE", minval=0.0, maxval=self.scale) + value /= self.scale + # Obtain print_time and apply requested settings + toolhead = self.printer.lookup_object("toolhead") + toolhead.register_lookahead_callback( + lambda print_time: self._set_pin(print_time, value) + ) + + +def load_config_prefix(config): + return PrinterOutputPin(config) diff --git a/klippy/extras/spi_temperature.py b/klippy/extras/spi_temperature.py index 7cc9e4ce6..ff981d9da 100644 --- a/klippy/extras/spi_temperature.py +++ b/klippy/extras/spi_temperature.py @@ -79,7 +79,7 @@ def _handle_spi_response(self, params): self._callback(last_read_time, temp) def report_fault(self, msg): - logging.warn(msg) + logging.warning(msg) ###################################################################### diff --git a/klippy/extras/thermistor.py b/klippy/extras/thermistor.py index c4fe56081..88f87c208 100644 --- a/klippy/extras/thermistor.py +++ b/klippy/extras/thermistor.py @@ -35,7 +35,9 @@ def setup_coefficients(self, t1, r1, t2, r2, t3, r3, name=""): ) if self.c3 <= 0.0: beta = ln_r13 / inv_t13 - logging.warn("Using thermistor beta %.3f in heater %s", beta, name) + logging.warning( + "Using thermistor beta %.3f in heater %s", beta, name + ) self.setup_coefficients_beta(t1, r1, beta) return self.c2 = (inv_t12 - self.c3 * ln3_r12) / ln_r12 diff --git a/klippy/extras/tmc.py b/klippy/extras/tmc.py index cb8f4662c..3998bffa9 100644 --- a/klippy/extras/tmc.py +++ b/klippy/extras/tmc.py @@ -310,7 +310,8 @@ def __init__(self, config, mcu_tmc, current_helper): def _init_registers(self, print_time=None): # Send registers - for reg_name, val in self.fields.registers.items(): + for reg_name in list(self.fields.registers.keys()): + val = self.fields.registers[reg_name] # Val may change during loop self.mcu_tmc.set_register(reg_name, val, print_time) cmd_INIT_TMC_help = "Initialize TMC stepper driver registers" diff --git a/klippy/extras/tmc2130.py b/klippy/extras/tmc2130.py index 374a53608..f3b6f7577 100644 --- a/klippy/extras/tmc2130.py +++ b/klippy/extras/tmc2130.py @@ -212,6 +212,9 @@ def __init__(self, config, mcu_tmc): self._home_current = config.getfloat( "home_current", self.run_current, above=0.0, maxval=MAX_CURRENT ) + self.current_change_dwell_time = config.getfloat( + "current_change_dwell_time", 0.5, above=0.0 + ) self.prev_current = self.run_current self.req_hold_current = self.hold_current self.sense_resistor = config.getfloat( @@ -224,6 +227,9 @@ def __init__(self, config, mcu_tmc): self.fields.set_field("ihold", ihold) self.fields.set_field("irun", irun) + def needs_home_current_change(self): + return self._home_current != self.run_current + def set_home_current(self, new_home_current): self._home_current = min(MAX_CURRENT, new_home_current) diff --git a/klippy/extras/tmc2240.py b/klippy/extras/tmc2240.py index 5d7f3f08e..de8ca986a 100644 --- a/klippy/extras/tmc2240.py +++ b/klippy/extras/tmc2240.py @@ -281,6 +281,9 @@ def __init__(self, config, mcu_tmc): self._home_current = config.getfloat( "home_current", self.run_current, above=0.0, maxval=self.max_cur ) + self.current_change_dwell_time = config.getfloat( + "current_change_dwell_time", 0.5, above=0.0 + ) self.prev_current = self.run_current self.req_hold_current = self.hold_current current_range = self._calc_current_range(self.run_current) @@ -292,6 +295,9 @@ def __init__(self, config, mcu_tmc): self.fields.set_field("ihold", ihold) self.fields.set_field("irun", irun) + def needs_home_current_change(self): + return self._home_current != self.run_current + def set_home_current(self, new_home_current): self._home_current = min(self.max_cur, new_home_current) diff --git a/klippy/extras/tmc2660.py b/klippy/extras/tmc2660.py index 85a891969..5aa020f97 100644 --- a/klippy/extras/tmc2660.py +++ b/klippy/extras/tmc2660.py @@ -126,6 +126,9 @@ def __init__(self, config, mcu_tmc): self._home_current = config.getfloat( "home_current", self.current, above=0.0, maxval=MAX_CURRENT ) + self.current_change_dwell_time = config.getfloat( + "current_change_dwell_time", 0.5, above=0.0 + ) self._prev_current = self.current self.sense_resistor = config.getfloat("sense_resistor") vsense, cs = self._calc_current(self.current) @@ -144,6 +147,9 @@ def __init__(self, config, mcu_tmc): "idle_timeout:ready", self._handle_ready ) + def needs_home_current_change(self): + return self._home_current != self.current + def set_home_current(self, new_home_current): self._home_current = min(MAX_CURRENT, new_home_current) diff --git a/klippy/extras/tmc5160.py b/klippy/extras/tmc5160.py index ecf8d3ac8..fc738e170 100644 --- a/klippy/extras/tmc5160.py +++ b/klippy/extras/tmc5160.py @@ -247,7 +247,7 @@ ###################################################################### VREF = 0.325 -MAX_CURRENT = 3.000 +MAX_CURRENT = 10.000 # Maximum dependent on board, but 10 is safe sanity check class TMC5160CurrentHelper: @@ -265,6 +265,9 @@ def __init__(self, config, mcu_tmc): self._home_current = config.getfloat( "home_current", self.run_current, above=0.0, maxval=MAX_CURRENT ) + self.current_change_dwell_time = config.getfloat( + "current_change_dwell_time", 0.5, above=0.0 + ) self._prev_current = self.run_current self.req_hold_current = self.hold_current self.sense_resistor = config.getfloat( @@ -277,6 +280,9 @@ def __init__(self, config, mcu_tmc): self.fields.set_field("ihold", ihold) self.fields.set_field("irun", irun) + def needs_home_current_change(self): + return self._home_current != self.run_current + def set_home_current(self, new_home_current): self._home_current = min(MAX_CURRENT, new_home_current) diff --git a/klippy/extras/z_tilt.py b/klippy/extras/z_tilt.py index f632aa856..948551f05 100644 --- a/klippy/extras/z_tilt.py +++ b/klippy/extras/z_tilt.py @@ -5,25 +5,9 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import logging import mathutil -import importlib from . import probe -def params_to_normal_form(np, params, offsets): - v = np.array([offsets[0], offsets[1], params["z_adjust"]]) - r = np.array([1, 0, params["x_adjust"]]) - s = np.array([0, 1, params["y_adjust"]]) - cp = np.cross(r, s) - return np.append(cp, np.dot(cp, v)) - - -def intersect_3_planes(np, p1, p2, p3): - a = np.array([p1[0:3], p2[0:3], p3[0:3]]) - b = np.array([p1[3], p2[3], p3[3]]) - sol = np.linalg.solve(a, b) - return sol - - class ZAdjustHelper: def __init__(self, config, z_count): self.printer = config.get_printer() @@ -37,13 +21,7 @@ def __init__(self, config, z_count): def handle_connect(self): kin = self.printer.lookup_object("toolhead").get_kinematics() z_steppers = [s for s in kin.get_steppers() if s.is_active_axis("z")] - if self.z_count is None: - if len(z_steppers) != 3: - raise self.printer.config_error( - "%s z_positions needs exactly 3 items for calibration" - % (self.name) - ) - elif len(z_steppers) != self.z_count: + if len(z_steppers) != self.z_count: raise self.printer.config_error( "%s z_positions needs exactly %d items" % (self.name, len(z_steppers)) @@ -181,53 +159,14 @@ def check_retry(self, z_positions): class ZTilt: def __init__(self, config): self.printer = config.get_printer() - self.section = config.get_name() - - try: - self.numpy = importlib.import_module("numpy") - except ImportError: - logging.info( - "numpy not installed, Z_TILT_CALIBRATE will not be " "available" - ) - self.numpy = None - self.z_positions = config.getlists( "z_positions", seps=(",", "\n"), parser=float, count=2 ) - z_count = len(self.z_positions) - self.retry_helper = RetryHelper(config) self.probe_helper = probe.ProbePointsHelper(config, self.probe_finalize) self.probe_helper.minimum_points(2) - - self.z_offsets = config.getlists( - "z_offsets", parser=float, count=z_count, default=None - ) - self.z_status = ZAdjustStatus(self.printer) - self.z_helper = ZAdjustHelper(config, z_count) - # probe points for calibrate/autodetect - cal_probe_points = list(self.probe_helper.get_probe_points()) - self.num_probe_points = len(cal_probe_points) - self.cal_helper = None - if config.get("extra_points", None) is not None: - self.cal_helper = probe.ProbePointsHelper( - config, self.cal_finalize, option_name="extra_points" - ) - cal_probe_points.extend(self.cal_helper.get_probe_points()) - self.cal_helper.update_probe_points(cal_probe_points, 3) - self.ad_helper = probe.ProbePointsHelper(config, self.ad_finalize) - self.ad_helper.update_probe_points(cal_probe_points, 3) - self.cal_conf_avg_len = config.getint("averaging_len", 3, minval=1) - self.ad_conf_delta = config.getfloat( - "autodetect_delta", 1.0, minval=0.1 - ) - if ( - config.get("autodetect_delta", None) is not None - or self.z_positions is None - ) and self.numpy is None: - raise config.error(self.err_missing_numpy) - + self.z_helper = ZAdjustHelper(config, len(self.z_positions)) # Register Z_TILT_ADJUST command gcode = self.printer.lookup_object("gcode") gcode.register_command( @@ -235,44 +174,19 @@ def __init__(self, config): self.cmd_Z_TILT_ADJUST, desc=self.cmd_Z_TILT_ADJUST_help, ) - if self.cal_helper is not None: - gcode.register_command( - "Z_TILT_CALIBRATE", - self.cmd_Z_TILT_CALIBRATE, - desc=self.cmd_Z_TILT_CALIBRATE_help, - ) - gcode.register_command( - "Z_TILT_AUTODETECT", - self.cmd_Z_TILT_AUTODETECT, - desc=self.cmd_Z_TILT_AUTODETECT_help, - ) cmd_Z_TILT_ADJUST_help = "Adjust the Z tilt" - cmd_Z_TILT_CALIBRATE_help = ( - "Calibrate Z tilt with additional probing " "points" - ) - cmd_Z_TILT_AUTODETECT_help = "Autodetect pivot point of Z motors" - err_missing_numpy = ( - "Failed to import `numpy` module, make sure it was " - "installed via `~/klippy-env/bin/pip install`" - ) def cmd_Z_TILT_ADJUST(self, gcmd): - if self.z_positions is None: - gcmd.respond_info( - "No z_positions configured. Run Z_TILT_AUTODETECT first" - ) - return self.z_status.reset() self.retry_helper.start(gcmd) self.probe_helper.start_probe(gcmd) - def perform_coordinate_descent(self, offsets, positions): + def probe_finalize(self, offsets, positions): # Setup for coordinate descent analysis z_offset = offsets[2] logging.info("Calculating bed tilt with: %s", positions) params = {"x_adjust": 0.0, "y_adjust": 0.0, "z_adjust": z_offset} - # Perform coordinate descent def adjusted_height(pos, params): x, y, z = pos @@ -289,21 +203,12 @@ def errorfunc(params): total_error += adjusted_height(pos, params) ** 2 return total_error - new_params = mathutil.coordinate_descent( - params.keys(), params, errorfunc - ) - new_params = mathutil.coordinate_descent( params.keys(), params, errorfunc ) # Apply results speed = self.probe_helper.get_lift_speed() logging.info("Calculated bed tilt parameters: %s", new_params) - return new_params - - def apply_adjustments(self, offsets, new_params): - z_offset = offsets[2] - speed = self.probe_helper.get_lift_speed() x_adjust = new_params["x_adjust"] y_adjust = new_params["y_adjust"] z_adjust = ( @@ -316,212 +221,10 @@ def apply_adjustments(self, offsets, new_params): x * x_adjust + y * y_adjust + z_adjust for x, y in self.z_positions ] self.z_helper.adjust_steppers(adjustments, speed) - - def probe_finalize(self, offsets, positions): - if self.z_offsets is not None: - positions = [ - [p[0], p[1], p[2] - o] - for (p, o) in zip(positions, self.z_offsets) - ] - new_params = self.perform_coordinate_descent(offsets, positions) - self.apply_adjustments(offsets, new_params) return self.z_status.check_retry_result( self.retry_helper.check_retry([p[2] for p in positions]) ) - def cmd_Z_TILT_CALIBRATE(self, gcmd): - if self.numpy is None: - gcmd.respond_info(self.err_missing_numpy) - return - self.cal_avg_len = gcmd.get_int("AVGLEN", self.cal_conf_avg_len) - self.cal_gcmd = gcmd - self.cal_runs = [] - self.cal_helper.start_probe(gcmd) - - def cal_finalize(self, offsets, positions): - np = self.numpy - avlen = self.cal_avg_len - new_params = self.perform_coordinate_descent(offsets, positions) - self.apply_adjustments(offsets, new_params) - self.cal_runs.append([p[2] for p in positions]) - if len(self.cal_runs) < avlen + 1: - return "retry" - prev_error = np.std(self.cal_runs[-avlen - 1 : -1], axis=0) - prev_error = np.std(prev_error) - this_error = np.std(self.cal_runs[-avlen:], axis=0) - this_error = np.std(this_error) - self.cal_gcmd.respond_info( - "previous error: %.6f current error: %.6f" - % (prev_error, this_error) - ) - if this_error < prev_error: - return "retry" - z_offsets = np.mean(self.cal_runs[-avlen:], axis=0) - z_offsets = [z - offsets[2] for z in z_offsets] - self.z_offsets = z_offsets - s_zoff = "" - for off in z_offsets[0 : self.num_probe_points]: - s_zoff += "%.6f, " % off - s_zoff = s_zoff[:-2] - self.cal_gcmd.respond_info("final z_offsets are: %s" % (s_zoff)) - configfile = self.printer.lookup_object("configfile") - section = self.section - configfile.set(section, "z_offsets", s_zoff) - self.cal_gcmd.respond_info( - "The SAVE_CONFIG command will update the printer config\n" - "file with these parameters and restart the printer." - ) - - def ad_init(self): - self.ad_phase = 0 - self.ad_params = [] - - def cmd_Z_TILT_AUTODETECT(self, gcmd): - if self.numpy is None: - gcmd.respond_info(self.err_missing_numpy) - self.cal_avg_len = gcmd.get_int("AVGLEN", self.cal_conf_avg_len) - self.ad_delta = gcmd.get_float("DELTA", self.ad_conf_delta, minval=0.1) - self.ad_init() - self.ad_gcmd = gcmd - self.ad_runs = [] - self.ad_points = [] - self.ad_error = None - self.ad_helper.start_probe(gcmd) - - ad_adjustments = [ - [0.5, -0.5, -0.5], # p1 up - [-1, 1, 0], # p2 up - [0, -1, 1], # p3 up - [0, 1, 0], # p3 + p2 up - [1, -1, 0], # p3 + p1 up - [0, 1, -1], # p2 + p1 up - [-0.5, -0.5, 0.5], # back to level - ] - - def ad_finalize(self, offsets, positions): - np = self.numpy - avlen = self.cal_avg_len - delta = self.ad_delta - speed = self.probe_helper.get_lift_speed() - new_params = self.perform_coordinate_descent(offsets, positions) - if self.ad_phase in range(1, 4): - new_params["z_adjust"] -= delta / 2 - if self.ad_phase in range(4, 7): - new_params["z_adjust"] += delta / 2 - if self.ad_phase == 0: - self.ad_points.append( - [z for _, _, z in positions[: self.num_probe_points]] - ) - self.ad_params.append(new_params) - adjustments = [_a * delta for _a in self.ad_adjustments[self.ad_phase]] - self.z_helper.adjust_steppers(adjustments, speed) - if self.ad_phase < 6: - self.ad_phase += 1 - return "retry" - # calculcate results - p = [] - for i in range(7): - p.append(params_to_normal_form(np, self.ad_params[i], offsets)) - - # This is how it works. - # To find the pivot point, we take 3 planes: - # a) the original untilted plane - # b) the plane with one motor raised, on one corner opposite the - # one we want to determine the pivot point of - # c) the plane with the other motor opposite the one we want to - # determine the pivot point raised - # The intersection of all 3 planes is a point very near the pivot - # point we search for. If the pivot point would be a point on the - # bed surface, we would already be done. But as the actual pivot - # point is in most cases below the bed, the intersection of the 3 - # points is behind or in front of the actual point (in X/Y). To - # compensate for this error, we do the same calculation again, but - # with the planes b) and c) tilted in the opposite direction and - # take the average of the 2 points. - - z_p1 = ( - intersect_3_planes(np, p[0], p[2], p[3])[:2], - intersect_3_planes(np, p[0], p[1], p[3])[:2], - intersect_3_planes(np, p[0], p[1], p[2])[:2], - ) - - z_p2 = ( - intersect_3_planes(np, p[0], p[5], p[6])[:2], - intersect_3_planes(np, p[0], p[4], p[6])[:2], - intersect_3_planes(np, p[0], p[4], p[5])[:2], - ) - - # take the average of positive and negative measurement - z_pos = [] - for _zp1, _zp2 in zip(z_p1, z_p2): - _z = [] - for _z1, _z2 in zip(_zp1, _zp2): - _z.append((_z1 + _z2) / 2) - z_pos.append(_z) - s_zpos = "" - for zp in z_pos: - s_zpos += "%.6f, %.6f\n" % tuple(zp) - self.ad_gcmd.respond_info("current estimated z_positions %s" % (s_zpos)) - self.ad_runs.append(z_pos) - if len(self.ad_runs) >= avlen: - self.z_positions = np.mean(self.ad_runs[-avlen:], axis=0) - else: - self.z_positions = np.mean(self.ad_runs, axis=0) - - # We got a first estimate of the pivot points. Now apply the - # adjustemts to all motors and repeat the process until the result - # converges. We determine convergence by keeping track of the last - # + 1 runs and compare the standard deviation over that - # len between the last two runs. When the error stops to decrease, we - # are done. The final z_positions are determined by calculating the - # average over the last calculated positions. - - self.apply_adjustments(offsets, self.ad_params[0]) - if len(self.ad_runs) >= avlen: - errors = np.std(self.ad_runs[-avlen:], axis=0) - error = np.std(errors) - if self.ad_error is None: - self.ad_gcmd.respond_info("current error: %.6f" % (error)) - else: - self.ad_gcmd.respond_info( - "previous error: %.6f current error: %.6f" - % (self.ad_error, error) - ) - if self.ad_error is not None: - if error >= self.ad_error: - self.ad_finalize_done(offsets) - return - self.ad_error = error - # restart - self.ad_init() - return "retry" - - def ad_finalize_done(self, offsets): - np = self.numpy - avlen = self.cal_avg_len - # calculate probe point z offsets - z_offsets = np.mean(self.ad_points[-avlen:], axis=0) - z_offsets = [z - offsets[2] for z in z_offsets] - self.z_offsets = z_offsets - logging.info("final z_offsets %s", (z_offsets)) - configfile = self.printer.lookup_object("configfile") - section = self.section - s_zoff = "" - for off in z_offsets: - s_zoff += "%.6f, " % off - s_zoff = s_zoff[:-2] - configfile.set(section, "z_offsets", s_zoff) - s_zpos = "" - for zpos in self.z_positions: - s_zpos += "%.6f, %.6f\n" % tuple(zpos) - configfile.set(section, "z_positions", s_zpos) - self.ad_gcmd.respond_info("final z_positions are %s" % (s_zpos)) - self.ad_gcmd.respond_info("final z_offsets are: %s" % (s_zoff)) - self.ad_gcmd.respond_info( - "The SAVE_CONFIG command will update the printer config\n" - "file with these parameters and restart the printer." - ) - def get_status(self, eventtime): return self.z_status.get_status(eventtime) diff --git a/klippy/extras/z_tilt_ng.py b/klippy/extras/z_tilt_ng.py new file mode 100644 index 000000000..f632aa856 --- /dev/null +++ b/klippy/extras/z_tilt_ng.py @@ -0,0 +1,530 @@ +# Mechanical bed tilt calibration with multiple Z steppers +# +# Copyright (C) 2018-2019 Kevin O'Connor +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import logging +import mathutil +import importlib +from . import probe + + +def params_to_normal_form(np, params, offsets): + v = np.array([offsets[0], offsets[1], params["z_adjust"]]) + r = np.array([1, 0, params["x_adjust"]]) + s = np.array([0, 1, params["y_adjust"]]) + cp = np.cross(r, s) + return np.append(cp, np.dot(cp, v)) + + +def intersect_3_planes(np, p1, p2, p3): + a = np.array([p1[0:3], p2[0:3], p3[0:3]]) + b = np.array([p1[3], p2[3], p3[3]]) + sol = np.linalg.solve(a, b) + return sol + + +class ZAdjustHelper: + def __init__(self, config, z_count): + self.printer = config.get_printer() + self.name = config.get_name() + self.z_count = z_count + self.z_steppers = [] + self.printer.register_event_handler( + "klippy:connect", self.handle_connect + ) + + def handle_connect(self): + kin = self.printer.lookup_object("toolhead").get_kinematics() + z_steppers = [s for s in kin.get_steppers() if s.is_active_axis("z")] + if self.z_count is None: + if len(z_steppers) != 3: + raise self.printer.config_error( + "%s z_positions needs exactly 3 items for calibration" + % (self.name) + ) + elif len(z_steppers) != self.z_count: + raise self.printer.config_error( + "%s z_positions needs exactly %d items" + % (self.name, len(z_steppers)) + ) + if len(z_steppers) < 2: + raise self.printer.config_error( + "%s requires multiple z steppers" % (self.name,) + ) + self.z_steppers = z_steppers + + def adjust_steppers(self, adjustments, speed): + toolhead = self.printer.lookup_object("toolhead") + gcode = self.printer.lookup_object("gcode") + curpos = toolhead.get_position() + # Report on movements + stepstrs = [ + "%s = %.6f" % (s.get_name(), a) + for s, a in zip(self.z_steppers, adjustments) + ] + msg = "Making the following Z adjustments:\n%s" % ("\n".join(stepstrs),) + gcode.respond_info(msg) + # Disable Z stepper movements + toolhead.flush_step_generation() + for s in self.z_steppers: + s.set_trapq(None) + # Move each z stepper (sorted from lowest to highest) until they match + positions = [(-a, s) for a, s in zip(adjustments, self.z_steppers)] + positions.sort(key=(lambda k: k[0])) + first_stepper_offset, first_stepper = positions[0] + z_low = curpos[2] - first_stepper_offset + for i in range(len(positions) - 1): + stepper_offset, stepper = positions[i] + next_stepper_offset, next_stepper = positions[i + 1] + toolhead.flush_step_generation() + stepper.set_trapq(toolhead.get_trapq()) + curpos[2] = z_low + next_stepper_offset + try: + toolhead.move(curpos, speed) + toolhead.set_position(curpos) + except: + logging.exception("ZAdjustHelper adjust_steppers") + toolhead.flush_step_generation() + for s in self.z_steppers: + s.set_trapq(toolhead.get_trapq()) + raise + # Z should now be level - do final cleanup + last_stepper_offset, last_stepper = positions[-1] + toolhead.flush_step_generation() + last_stepper.set_trapq(toolhead.get_trapq()) + curpos[2] += first_stepper_offset + toolhead.set_position(curpos) + + +class ZAdjustStatus: + def __init__(self, printer): + self.applied = False + printer.register_event_handler( + "stepper_enable:motor_off", self._motor_off + ) + + def check_retry_result(self, retry_result): + if retry_result == "done": + self.applied = True + return retry_result + + def reset(self): + self.applied = False + + def get_status(self, eventtime): + return {"applied": self.applied} + + def _motor_off(self, print_time): + self.reset() + + +class RetryHelper: + def __init__(self, config, error_msg_extra=""): + self.gcode = config.get_printer().lookup_object("gcode") + self.default_max_retries = config.getint("retries", 0, minval=0) + self.default_retry_tolerance = config.getfloat( + "retry_tolerance", 0.0, above=0.0 + ) + self.value_label = "Probed points range" + self.error_msg_extra = error_msg_extra + + def start(self, gcmd): + self.max_retries = gcmd.get_int( + "RETRIES", self.default_max_retries, minval=0, maxval=30 + ) + self.retry_tolerance = gcmd.get_float( + "RETRY_TOLERANCE", + self.default_retry_tolerance, + minval=0.0, + maxval=1.0, + ) + self.current_retry = 0 + self.previous = None + self.increasing = 0 + + def check_increase(self, error): + if self.previous and error > self.previous + 0.0000001: + self.increasing += 1 + elif self.increasing > 0: + self.increasing -= 1 + self.previous = error + return self.increasing > 1 + + def check_retry(self, z_positions): + if self.max_retries == 0: + return + error = round(max(z_positions) - min(z_positions), 6) + self.gcode.respond_info( + "Retries: %d/%d %s: %0.6f tolerance: %0.6f" + % ( + self.current_retry, + self.max_retries, + self.value_label, + error, + self.retry_tolerance, + ) + ) + if self.check_increase(error): + raise self.gcode.error( + "Retries aborting: %s is increasing. %s" + % (self.value_label, self.error_msg_extra) + ) + if error <= self.retry_tolerance: + return "done" + self.current_retry += 1 + if self.current_retry > self.max_retries: + raise self.gcode.error("Too many retries") + return "retry" + + +class ZTilt: + def __init__(self, config): + self.printer = config.get_printer() + self.section = config.get_name() + + try: + self.numpy = importlib.import_module("numpy") + except ImportError: + logging.info( + "numpy not installed, Z_TILT_CALIBRATE will not be " "available" + ) + self.numpy = None + + self.z_positions = config.getlists( + "z_positions", seps=(",", "\n"), parser=float, count=2 + ) + z_count = len(self.z_positions) + + self.retry_helper = RetryHelper(config) + self.probe_helper = probe.ProbePointsHelper(config, self.probe_finalize) + self.probe_helper.minimum_points(2) + + self.z_offsets = config.getlists( + "z_offsets", parser=float, count=z_count, default=None + ) + + self.z_status = ZAdjustStatus(self.printer) + self.z_helper = ZAdjustHelper(config, z_count) + # probe points for calibrate/autodetect + cal_probe_points = list(self.probe_helper.get_probe_points()) + self.num_probe_points = len(cal_probe_points) + self.cal_helper = None + if config.get("extra_points", None) is not None: + self.cal_helper = probe.ProbePointsHelper( + config, self.cal_finalize, option_name="extra_points" + ) + cal_probe_points.extend(self.cal_helper.get_probe_points()) + self.cal_helper.update_probe_points(cal_probe_points, 3) + self.ad_helper = probe.ProbePointsHelper(config, self.ad_finalize) + self.ad_helper.update_probe_points(cal_probe_points, 3) + self.cal_conf_avg_len = config.getint("averaging_len", 3, minval=1) + self.ad_conf_delta = config.getfloat( + "autodetect_delta", 1.0, minval=0.1 + ) + if ( + config.get("autodetect_delta", None) is not None + or self.z_positions is None + ) and self.numpy is None: + raise config.error(self.err_missing_numpy) + + # Register Z_TILT_ADJUST command + gcode = self.printer.lookup_object("gcode") + gcode.register_command( + "Z_TILT_ADJUST", + self.cmd_Z_TILT_ADJUST, + desc=self.cmd_Z_TILT_ADJUST_help, + ) + if self.cal_helper is not None: + gcode.register_command( + "Z_TILT_CALIBRATE", + self.cmd_Z_TILT_CALIBRATE, + desc=self.cmd_Z_TILT_CALIBRATE_help, + ) + gcode.register_command( + "Z_TILT_AUTODETECT", + self.cmd_Z_TILT_AUTODETECT, + desc=self.cmd_Z_TILT_AUTODETECT_help, + ) + + cmd_Z_TILT_ADJUST_help = "Adjust the Z tilt" + cmd_Z_TILT_CALIBRATE_help = ( + "Calibrate Z tilt with additional probing " "points" + ) + cmd_Z_TILT_AUTODETECT_help = "Autodetect pivot point of Z motors" + err_missing_numpy = ( + "Failed to import `numpy` module, make sure it was " + "installed via `~/klippy-env/bin/pip install`" + ) + + def cmd_Z_TILT_ADJUST(self, gcmd): + if self.z_positions is None: + gcmd.respond_info( + "No z_positions configured. Run Z_TILT_AUTODETECT first" + ) + return + self.z_status.reset() + self.retry_helper.start(gcmd) + self.probe_helper.start_probe(gcmd) + + def perform_coordinate_descent(self, offsets, positions): + # Setup for coordinate descent analysis + z_offset = offsets[2] + logging.info("Calculating bed tilt with: %s", positions) + params = {"x_adjust": 0.0, "y_adjust": 0.0, "z_adjust": z_offset} + + # Perform coordinate descent + def adjusted_height(pos, params): + x, y, z = pos + return ( + z + - x * params["x_adjust"] + - y * params["y_adjust"] + - params["z_adjust"] + ) + + def errorfunc(params): + total_error = 0.0 + for pos in positions: + total_error += adjusted_height(pos, params) ** 2 + return total_error + + new_params = mathutil.coordinate_descent( + params.keys(), params, errorfunc + ) + + new_params = mathutil.coordinate_descent( + params.keys(), params, errorfunc + ) + # Apply results + speed = self.probe_helper.get_lift_speed() + logging.info("Calculated bed tilt parameters: %s", new_params) + return new_params + + def apply_adjustments(self, offsets, new_params): + z_offset = offsets[2] + speed = self.probe_helper.get_lift_speed() + x_adjust = new_params["x_adjust"] + y_adjust = new_params["y_adjust"] + z_adjust = ( + new_params["z_adjust"] + - z_offset + - x_adjust * offsets[0] + - y_adjust * offsets[1] + ) + adjustments = [ + x * x_adjust + y * y_adjust + z_adjust for x, y in self.z_positions + ] + self.z_helper.adjust_steppers(adjustments, speed) + + def probe_finalize(self, offsets, positions): + if self.z_offsets is not None: + positions = [ + [p[0], p[1], p[2] - o] + for (p, o) in zip(positions, self.z_offsets) + ] + new_params = self.perform_coordinate_descent(offsets, positions) + self.apply_adjustments(offsets, new_params) + return self.z_status.check_retry_result( + self.retry_helper.check_retry([p[2] for p in positions]) + ) + + def cmd_Z_TILT_CALIBRATE(self, gcmd): + if self.numpy is None: + gcmd.respond_info(self.err_missing_numpy) + return + self.cal_avg_len = gcmd.get_int("AVGLEN", self.cal_conf_avg_len) + self.cal_gcmd = gcmd + self.cal_runs = [] + self.cal_helper.start_probe(gcmd) + + def cal_finalize(self, offsets, positions): + np = self.numpy + avlen = self.cal_avg_len + new_params = self.perform_coordinate_descent(offsets, positions) + self.apply_adjustments(offsets, new_params) + self.cal_runs.append([p[2] for p in positions]) + if len(self.cal_runs) < avlen + 1: + return "retry" + prev_error = np.std(self.cal_runs[-avlen - 1 : -1], axis=0) + prev_error = np.std(prev_error) + this_error = np.std(self.cal_runs[-avlen:], axis=0) + this_error = np.std(this_error) + self.cal_gcmd.respond_info( + "previous error: %.6f current error: %.6f" + % (prev_error, this_error) + ) + if this_error < prev_error: + return "retry" + z_offsets = np.mean(self.cal_runs[-avlen:], axis=0) + z_offsets = [z - offsets[2] for z in z_offsets] + self.z_offsets = z_offsets + s_zoff = "" + for off in z_offsets[0 : self.num_probe_points]: + s_zoff += "%.6f, " % off + s_zoff = s_zoff[:-2] + self.cal_gcmd.respond_info("final z_offsets are: %s" % (s_zoff)) + configfile = self.printer.lookup_object("configfile") + section = self.section + configfile.set(section, "z_offsets", s_zoff) + self.cal_gcmd.respond_info( + "The SAVE_CONFIG command will update the printer config\n" + "file with these parameters and restart the printer." + ) + + def ad_init(self): + self.ad_phase = 0 + self.ad_params = [] + + def cmd_Z_TILT_AUTODETECT(self, gcmd): + if self.numpy is None: + gcmd.respond_info(self.err_missing_numpy) + self.cal_avg_len = gcmd.get_int("AVGLEN", self.cal_conf_avg_len) + self.ad_delta = gcmd.get_float("DELTA", self.ad_conf_delta, minval=0.1) + self.ad_init() + self.ad_gcmd = gcmd + self.ad_runs = [] + self.ad_points = [] + self.ad_error = None + self.ad_helper.start_probe(gcmd) + + ad_adjustments = [ + [0.5, -0.5, -0.5], # p1 up + [-1, 1, 0], # p2 up + [0, -1, 1], # p3 up + [0, 1, 0], # p3 + p2 up + [1, -1, 0], # p3 + p1 up + [0, 1, -1], # p2 + p1 up + [-0.5, -0.5, 0.5], # back to level + ] + + def ad_finalize(self, offsets, positions): + np = self.numpy + avlen = self.cal_avg_len + delta = self.ad_delta + speed = self.probe_helper.get_lift_speed() + new_params = self.perform_coordinate_descent(offsets, positions) + if self.ad_phase in range(1, 4): + new_params["z_adjust"] -= delta / 2 + if self.ad_phase in range(4, 7): + new_params["z_adjust"] += delta / 2 + if self.ad_phase == 0: + self.ad_points.append( + [z for _, _, z in positions[: self.num_probe_points]] + ) + self.ad_params.append(new_params) + adjustments = [_a * delta for _a in self.ad_adjustments[self.ad_phase]] + self.z_helper.adjust_steppers(adjustments, speed) + if self.ad_phase < 6: + self.ad_phase += 1 + return "retry" + # calculcate results + p = [] + for i in range(7): + p.append(params_to_normal_form(np, self.ad_params[i], offsets)) + + # This is how it works. + # To find the pivot point, we take 3 planes: + # a) the original untilted plane + # b) the plane with one motor raised, on one corner opposite the + # one we want to determine the pivot point of + # c) the plane with the other motor opposite the one we want to + # determine the pivot point raised + # The intersection of all 3 planes is a point very near the pivot + # point we search for. If the pivot point would be a point on the + # bed surface, we would already be done. But as the actual pivot + # point is in most cases below the bed, the intersection of the 3 + # points is behind or in front of the actual point (in X/Y). To + # compensate for this error, we do the same calculation again, but + # with the planes b) and c) tilted in the opposite direction and + # take the average of the 2 points. + + z_p1 = ( + intersect_3_planes(np, p[0], p[2], p[3])[:2], + intersect_3_planes(np, p[0], p[1], p[3])[:2], + intersect_3_planes(np, p[0], p[1], p[2])[:2], + ) + + z_p2 = ( + intersect_3_planes(np, p[0], p[5], p[6])[:2], + intersect_3_planes(np, p[0], p[4], p[6])[:2], + intersect_3_planes(np, p[0], p[4], p[5])[:2], + ) + + # take the average of positive and negative measurement + z_pos = [] + for _zp1, _zp2 in zip(z_p1, z_p2): + _z = [] + for _z1, _z2 in zip(_zp1, _zp2): + _z.append((_z1 + _z2) / 2) + z_pos.append(_z) + s_zpos = "" + for zp in z_pos: + s_zpos += "%.6f, %.6f\n" % tuple(zp) + self.ad_gcmd.respond_info("current estimated z_positions %s" % (s_zpos)) + self.ad_runs.append(z_pos) + if len(self.ad_runs) >= avlen: + self.z_positions = np.mean(self.ad_runs[-avlen:], axis=0) + else: + self.z_positions = np.mean(self.ad_runs, axis=0) + + # We got a first estimate of the pivot points. Now apply the + # adjustemts to all motors and repeat the process until the result + # converges. We determine convergence by keeping track of the last + # + 1 runs and compare the standard deviation over that + # len between the last two runs. When the error stops to decrease, we + # are done. The final z_positions are determined by calculating the + # average over the last calculated positions. + + self.apply_adjustments(offsets, self.ad_params[0]) + if len(self.ad_runs) >= avlen: + errors = np.std(self.ad_runs[-avlen:], axis=0) + error = np.std(errors) + if self.ad_error is None: + self.ad_gcmd.respond_info("current error: %.6f" % (error)) + else: + self.ad_gcmd.respond_info( + "previous error: %.6f current error: %.6f" + % (self.ad_error, error) + ) + if self.ad_error is not None: + if error >= self.ad_error: + self.ad_finalize_done(offsets) + return + self.ad_error = error + # restart + self.ad_init() + return "retry" + + def ad_finalize_done(self, offsets): + np = self.numpy + avlen = self.cal_avg_len + # calculate probe point z offsets + z_offsets = np.mean(self.ad_points[-avlen:], axis=0) + z_offsets = [z - offsets[2] for z in z_offsets] + self.z_offsets = z_offsets + logging.info("final z_offsets %s", (z_offsets)) + configfile = self.printer.lookup_object("configfile") + section = self.section + s_zoff = "" + for off in z_offsets: + s_zoff += "%.6f, " % off + s_zoff = s_zoff[:-2] + configfile.set(section, "z_offsets", s_zoff) + s_zpos = "" + for zpos in self.z_positions: + s_zpos += "%.6f, %.6f\n" % tuple(zpos) + configfile.set(section, "z_positions", s_zpos) + self.ad_gcmd.respond_info("final z_positions are %s" % (s_zpos)) + self.ad_gcmd.respond_info("final z_offsets are: %s" % (s_zoff)) + self.ad_gcmd.respond_info( + "The SAVE_CONFIG command will update the printer config\n" + "file with these parameters and restart the printer." + ) + + def get_status(self, eventtime): + return self.z_status.get_status(eventtime) + + +def load_config(config): + return ZTilt(config) diff --git a/klippy/gcode.py b/klippy/gcode.py index 2a8b316c8..e326cec94 100644 --- a/klippy/gcode.py +++ b/klippy/gcode.py @@ -153,6 +153,7 @@ def __init__(self, printer): self.ready_gcode_handlers = {} self.mux_commands = {} self.gcode_help = {} + self.status_commands = {} # Register commands needed before config file is loaded handlers = [ "M110", @@ -185,6 +186,7 @@ def register_command(self, cmd, func, when_not_ready=False, desc=None): del self.ready_gcode_handlers[cmd] if cmd in self.base_gcode_handlers: del self.base_gcode_handlers[cmd] + self._build_status_commands() return old_cmd if cmd in self.ready_gcode_handlers: raise self.printer.config_error( @@ -201,6 +203,7 @@ def func(params): self.base_gcode_handlers[cmd] = func if desc is not None: self.gcode_help[cmd] = desc + self._build_status_commands() def register_mux_command(self, cmd, key, value, func, desc=None): prev = self.mux_commands.get(cmd) @@ -227,6 +230,16 @@ def handler(gcmd): def get_command_help(self): return dict(self.gcode_help) + def get_status(self, eventtime): + return {"commands": self.status_commands} + + def _build_status_commands(self): + commands = {cmd: {} for cmd in self.gcode_handlers} + for cmd in self.gcode_help: + if cmd in commands: + commands[cmd]["help"] = self.gcode_help[cmd] + self.status_commands = commands + def register_output_handler(self, cb): self.output_callbacks.append(cb) @@ -235,6 +248,7 @@ def _handle_shutdown(self): return self.is_printer_ready = False self.gcode_handlers = self.base_gcode_handlers + self._build_status_commands() self._respond_state("Shutdown") def _handle_disconnect(self): @@ -243,6 +257,7 @@ def _handle_disconnect(self): def _handle_ready(self): self.is_printer_ready = True self.gcode_handlers = self.ready_gcode_handlers + self._build_status_commands() self._respond_state("Ready") # Parse input into commands diff --git a/klippy/mcu.py b/klippy/mcu.py index daeda98d5..9c43d7a9c 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -803,6 +803,7 @@ def __init__(self, config, clocksync): self._reserved_move_slots = 0 self._stepqueues = [] self._steppersync = None + self._flush_callbacks = [] # Stats self._get_status_info = {} self._stats_sumsq_base = 0.0 @@ -1104,12 +1105,6 @@ def get_query_slot(self, oid): t = int(self.estimated_print_time(self._reactor.monotonic()) + 1.5) return self.print_time_to_clock(t) + slot - def register_stepqueue(self, stepqueue): - self._stepqueues.append(stepqueue) - - def request_move_queue_slot(self): - self._reserved_move_slots += 1 - def seconds_to_clock(self, time): return int(time * self._mcu_freq) @@ -1238,15 +1233,15 @@ def _firmware_restart(self, force=False): def _firmware_restart_bridge(self): self._firmware_restart(True) - # Misc external commands - def is_fileoutput(self): - return self._printer.get_start_args().get("debugoutput") is not None + # Move queue tracking + def register_stepqueue(self, stepqueue): + self._stepqueues.append(stepqueue) - def is_shutdown(self): - return self._is_shutdown + def request_move_queue_slot(self): + self._reserved_move_slots += 1 - def get_shutdown_clock(self): - return self._shutdown_clock + def register_flush_callback(self, callback): + self._flush_callbacks.append(callback) def flush_moves(self, print_time): if self._steppersync is None: @@ -1254,6 +1249,8 @@ def flush_moves(self, print_time): clock = self.print_time_to_clock(print_time) if clock < 0: return + for cb in self._flush_callbacks: + cb(print_time, clock) ret = self._ffi_lib.steppersync_flush(self._steppersync, clock) if ret: raise error( @@ -1279,6 +1276,16 @@ def check_active(self, print_time, eventtime): "Lost communication with MCU '%s'" % (self._name,) ) + # Misc external commands + def is_fileoutput(self): + return self._printer.get_start_args().get("debugoutput") is not None + + def is_shutdown(self): + return self._is_shutdown + + def get_shutdown_clock(self): + return self._shutdown_clock + def get_status(self, eventtime=None): return dict(self._get_status_info) diff --git a/klippy/serialhdl.py b/klippy/serialhdl.py index 8ce5d5a2c..d16624487 100644 --- a/klippy/serialhdl.py +++ b/klippy/serialhdl.py @@ -158,7 +158,7 @@ def connect_canbus(self, canbus_uuid, canbus_nodeid, canbus_iface="can0"): ) bus.send(set_id_msg) except (can.CanError, os.error) as e: - logging.warn( + logging.warning( "%sUnable to open CAN port: %s", self.warn_prefix, e ) self.reactor.pause(self.reactor.monotonic() + 5.0) diff --git a/klippy/stepper.py b/klippy/stepper.py index 77f3b26c5..0a79f88ea 100644 --- a/klippy/stepper.py +++ b/klippy/stepper.py @@ -454,6 +454,9 @@ def __init__( self.use_sensorless_homing = config.getboolean( "use_sensorless_homing", endstop_is_virtual ) + self.min_home_dist = config.getfloat( + "min_home_dist", self.homing_retract_dist, minval=0.0 + ) if self.homing_positive_dir is None: axis_len = self.position_max - self.position_min @@ -493,6 +496,7 @@ def get_homing_info(self): "positive_dir", "second_homing_speed", "use_sensorless_homing", + "min_home_dist", ], )( self.homing_speed, @@ -502,6 +506,7 @@ def get_homing_info(self): self.homing_positive_dir, self.second_homing_speed, self.use_sensorless_homing, + self.min_home_dist, ) return homing_info diff --git a/klippy/toolhead.py b/klippy/toolhead.py index 4b30f3c23..d38eda6fa 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -225,8 +225,14 @@ def add_move(self, move): self.flush(lazy=True) +BUFFER_TIME_LOW = 1.0 +BUFFER_TIME_HIGH = 2.0 +BUFFER_TIME_START = 0.250 +BGFLUSH_LOW_TIME = 0.200 +BGFLUSH_BATCH_TIME = 0.200 MIN_KIN_TIME = 0.100 MOVE_BATCH_TIME = 0.500 +STEPCOMPRESS_FLUSH_TIME = 0.050 SDS_CHECK_TIME = 0.001 # step+dir+step filter in stepcompress.c DRIP_SEGMENT_TIME = 0.050 @@ -246,14 +252,9 @@ def __init__(self, config): m for n, m in self.printer.lookup_objects(module="mcu") ] self.mcu = self.all_mcus[0] - self.can_pause = True - if self.mcu.is_fileoutput(): - self.can_pause = False self.move_queue = MoveQueue(self) + self.move_queue.set_flush_time(BUFFER_TIME_HIGH) self.commanded_pos = [0.0, 0.0, 0.0, 0.0] - self.printer.register_event_handler( - "klippy:shutdown", self._handle_shutdown - ) # Velocity and acceleration control self.max_velocity = config.getfloat("max_velocity", above=0.0) self.max_accel = config.getfloat("max_accel", above=0.0) @@ -266,31 +267,26 @@ def __init__(self, config): ) self.junction_deviation = 0.0 self._calc_junction_deviation() + # Input stall detection + self.check_stall_time = 0.0 + self.print_stall = 0 + # Input pause tracking + self.can_pause = True + if self.mcu.is_fileoutput(): + self.can_pause = False + self.need_check_pause = -1.0 # Print time tracking - self.buffer_time_low = config.getfloat( - "buffer_time_low", 1.000, above=0.0 - ) - self.buffer_time_high = config.getfloat( - "buffer_time_high", 2.000, above=self.buffer_time_low - ) - self.buffer_time_start = config.getfloat( - "buffer_time_start", 0.250, above=0.0 - ) - self.move_flush_time = config.getfloat( - "move_flush_time", 0.050, above=0.0 - ) self.print_time = 0.0 - self.special_queuing_state = "Flushed" - self.need_check_stall = -1.0 - self.flush_timer = self.reactor.register_timer(self._flush_handler) - self.move_queue.set_flush_time(self.buffer_time_high) - self.idle_flush_print_time = 0.0 - self.print_stall = 0 + self.special_queuing_state = "NeedPrime" + self.priming_timer = None self.drip_completion = None + # Flush tracking + self.flush_timer = self.reactor.register_timer(self._flush_handler) + self.do_kick_flush_timer = True + self.last_flush_time = self.need_flush_time = self.step_gen_time = 0.0 # Kinematic step generation scan window time tracking self.kin_flush_delay = SDS_CHECK_TIME self.kin_flush_times = [] - self.force_flush_time = self.last_kin_move_time = 0.0 # Setup iterative solver ffi_main, ffi_lib = chelper.get_ffi() self.trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free) @@ -322,6 +318,9 @@ def __init__(self, config): desc=self.cmd_SET_VELOCITY_LIMIT_help, ) gcode.register_command("M204", self.cmd_M204) + self.printer.register_event_handler( + "klippy:shutdown", self._handle_shutdown + ) # Load some default modules modules = [ "gcode_move", @@ -345,31 +344,40 @@ def get_active_rails_for_axis(self, axis): break return active_rails - # Print time tracking - def _update_move_time(self, next_print_time): - batch_time = MOVE_BATCH_TIME - kin_flush_delay = self.kin_flush_delay - fft = self.force_flush_time + # Print time and flush tracking + def _advance_flush_time(self, flush_time): + flush_time = max(flush_time, self.last_flush_time) + # Generate steps via itersolve + sg_flush_ceil = max(flush_time, self.print_time - self.kin_flush_delay) + sg_flush_time = min(flush_time + STEPCOMPRESS_FLUSH_TIME, sg_flush_ceil) + for sg in self.step_generators: + sg(sg_flush_time) + # Free trapq entries that are no longer needed + free_time = sg_flush_time - self.kin_flush_delay + self.trapq_finalize_moves(self.trapq, free_time) + self.extruder.update_move_time(free_time) + # Flush stepcompress and mcu steppersync + for m in self.all_mcus: + m.flush_moves(flush_time) + self.last_flush_time = flush_time + + def _advance_move_time(self, next_print_time): + pt_delay = self.kin_flush_delay + STEPCOMPRESS_FLUSH_TIME + flush_time = max(self.last_flush_time, self.print_time - pt_delay) + self.print_time = max(self.print_time, next_print_time) + want_flush_time = max(flush_time, self.print_time - pt_delay) while 1: - self.print_time = min(self.print_time + batch_time, next_print_time) - sg_flush_time = max(fft, self.print_time - kin_flush_delay) - for sg in self.step_generators: - sg(sg_flush_time) - free_time = max(fft, sg_flush_time - kin_flush_delay) - self.trapq_finalize_moves(self.trapq, free_time) - self.extruder.update_move_time(free_time) - mcu_flush_time = max(fft, sg_flush_time - self.move_flush_time) - for m in self.all_mcus: - m.flush_moves(mcu_flush_time) - if self.print_time >= next_print_time: + flush_time = min(flush_time + MOVE_BATCH_TIME, want_flush_time) + self._advance_flush_time(flush_time) + if flush_time >= want_flush_time: break def _calc_print_time(self): curtime = self.reactor.monotonic() est_print_time = self.mcu.estimated_print_time(curtime) - kin_time = max(est_print_time + MIN_KIN_TIME, self.force_flush_time) + kin_time = max(est_print_time + MIN_KIN_TIME, self.last_flush_time) kin_time += self.kin_flush_delay - min_print_time = max(est_print_time + self.buffer_time_start, kin_time) + min_print_time = max(est_print_time + BUFFER_TIME_START, kin_time) if min_print_time > self.print_time: self.print_time = min_print_time self.printer.send_event( @@ -383,10 +391,9 @@ def _process_moves(self, moves): # Resync print_time if necessary if self.special_queuing_state: if self.special_queuing_state != "Drip": - # Transition from "Flushed"/"Priming" state to main state + # Transition from "NeedPrime"/"Priming" state to main state self.special_queuing_state = "" - self.need_check_stall = -1.0 - self.reactor.update_timer(self.flush_timer, self.reactor.NOW) + self.need_check_pause = -1.0 self._calc_print_time() # Queue moves into trapezoid motion queue (trapq) next_move_time = self.print_time @@ -418,80 +425,101 @@ def _process_moves(self, moves): # Generate steps for moves if self.special_queuing_state: self._update_drip_move_time(next_move_time) - self._update_move_time(next_move_time) - self.last_kin_move_time = max(self.last_kin_move_time, next_move_time) - - def flush_step_generation(self): - # Transition from "Flushed"/"Priming"/main state to "Flushed" state - self.move_queue.flush() - self.special_queuing_state = "Flushed" - self.need_check_stall = -1.0 - self.reactor.update_timer(self.flush_timer, self.reactor.NEVER) - self.move_queue.set_flush_time(self.buffer_time_high) - self.idle_flush_print_time = 0.0 - # Determine actual last "itersolve" flush time - lastf = self.print_time - self.kin_flush_delay - # Calculate flush time that includes kinematic scan windows - flush_time = max(lastf, self.last_kin_move_time + self.kin_flush_delay) - if flush_time > self.print_time: - # Flush in small time chunks - self._update_move_time(flush_time) - # Flush kinematic scan windows and step buffers - self.force_flush_time = max(self.force_flush_time, flush_time) - self._update_move_time(max(self.print_time, self.force_flush_time)) + self.note_kinematic_activity( + next_move_time + self.kin_flush_delay, set_step_gen_time=True + ) + self._advance_move_time(next_move_time) def _flush_lookahead(self): - if self.special_queuing_state: - return self.flush_step_generation() + # Transit from "NeedPrime"/"Priming"/"Drip"/main state to "NeedPrime" self.move_queue.flush() + self.special_queuing_state = "NeedPrime" + self.need_check_pause = -1.0 + self.move_queue.set_flush_time(BUFFER_TIME_HIGH) + self.check_stall_time = 0.0 - def get_last_move_time(self): + def flush_step_generation(self): self._flush_lookahead() + self._advance_flush_time(self.step_gen_time) + + def get_last_move_time(self): if self.special_queuing_state: + self._flush_lookahead() self._calc_print_time() + else: + self.move_queue.flush() return self.print_time - def _check_stall(self): + def _check_pause(self): eventtime = self.reactor.monotonic() + est_print_time = self.mcu.estimated_print_time(eventtime) + buffer_time = self.print_time - est_print_time if self.special_queuing_state: - if self.idle_flush_print_time: - # Was in "Flushed" state and got there from idle input - est_print_time = self.mcu.estimated_print_time(eventtime) - if est_print_time < self.idle_flush_print_time: + if self.check_stall_time: + # Was in "NeedPrime" state and got there from idle input + if est_print_time < self.check_stall_time: self.print_stall += 1 - self.idle_flush_print_time = 0.0 - # Transition from "Flushed"/"Priming" state to "Priming" state + self.check_stall_time = 0.0 + # Transition from "NeedPrime"/"Priming" state to "Priming" state self.special_queuing_state = "Priming" - self.need_check_stall = -1.0 - self.reactor.update_timer(self.flush_timer, eventtime + 0.100) - # Check if there are lots of queued moves and stall if so + self.need_check_pause = -1.0 + if self.priming_timer is None: + self.priming_timer = self.reactor.register_timer( + self._priming_handler + ) + wtime = eventtime + max(0.100, buffer_time - BUFFER_TIME_LOW) + self.reactor.update_timer(self.priming_timer, wtime) + # Check if there are lots of queued moves and pause if so while True: - est_print_time = self.mcu.estimated_print_time(eventtime) - buffer_time = self.print_time - est_print_time - stall_time = buffer_time - self.buffer_time_high - if stall_time <= 0.0: + pause_time = buffer_time - BUFFER_TIME_HIGH + if pause_time <= 0.0: break if not self.can_pause: - self.need_check_stall = self.reactor.NEVER + self.need_check_pause = self.reactor.NEVER return - eventtime = self.reactor.pause(eventtime + min(1.0, stall_time)) + eventtime = self.reactor.pause(eventtime + min(1.0, pause_time)) + est_print_time = self.mcu.estimated_print_time(eventtime) + buffer_time = self.print_time - est_print_time if not self.special_queuing_state: - # In main state - defer stall checking until needed - self.need_check_stall = ( - est_print_time + self.buffer_time_high + 0.100 - ) + # In main state - defer pause checking until needed + self.need_check_pause = est_print_time + BUFFER_TIME_HIGH + 0.100 + + def _priming_handler(self, eventtime): + self.reactor.unregister_timer(self.priming_timer) + self.priming_timer = None + try: + if self.special_queuing_state == "Priming": + self._flush_lookahead() + self.check_stall_time = self.print_time + except: + logging.exception("Exception in priming_handler") + self.printer.invoke_shutdown("Exception in priming_handler") + return self.reactor.NEVER def _flush_handler(self, eventtime): try: - print_time = self.print_time - buffer_time = print_time - self.mcu.estimated_print_time(eventtime) - if buffer_time > self.buffer_time_low: - # Running normally - reschedule check - return eventtime + buffer_time - self.buffer_time_low - # Under ran low buffer mark - flush lookahead queue - self.flush_step_generation() - if print_time != self.print_time: - self.idle_flush_print_time = self.print_time + est_print_time = self.mcu.estimated_print_time(eventtime) + if not self.special_queuing_state: + # In "main" state - flush lookahead if buffer runs low + print_time = self.print_time + buffer_time = print_time - est_print_time + if buffer_time > BUFFER_TIME_LOW: + # Running normally - reschedule check + return eventtime + buffer_time - BUFFER_TIME_LOW + # Under ran low buffer mark - flush lookahead queue + self._flush_lookahead() + if print_time != self.print_time: + self.check_stall_time = self.print_time + # In "NeedPrime"/"Priming" state - flush queues if needed + while 1: + if self.last_flush_time >= self.need_flush_time: + self.do_kick_flush_timer = True + return self.reactor.NEVER + buffer_time = self.last_flush_time - est_print_time + if buffer_time > BGFLUSH_LOW_TIME: + return eventtime + buffer_time - BGFLUSH_LOW_TIME + ftime = est_print_time + BGFLUSH_LOW_TIME + BGFLUSH_BATCH_TIME + self._advance_flush_time(min(self.need_flush_time, ftime)) except: logging.exception("Exception in flush_handler") self.printer.invoke_shutdown("Exception in flush_handler") @@ -521,8 +549,8 @@ def move(self, newpos, speed): self.extruder.check_move(move) self.commanded_pos[:] = move.end_pos self.move_queue.add_move(move) - if self.print_time > self.need_check_stall: - self._check_stall() + if self.print_time > self.need_check_pause: + self._check_pause() def manual_move(self, coord, speed): curpos = list(self.commanded_pos) @@ -534,8 +562,8 @@ def manual_move(self, coord, speed): def dwell(self, delay): next_print_time = self.get_last_move_time() + max(0.0, delay) - self._update_move_time(next_print_time) - self._check_stall() + self._advance_move_time(next_print_time) + self._check_pause() def wait_moves(self): self._flush_lookahead() @@ -557,7 +585,7 @@ def get_extruder(self): # Homing "drip move" handling def _update_drip_move_time(self, next_print_time): - flush_delay = DRIP_TIME + self.move_flush_time + self.kin_flush_delay + flush_delay = DRIP_TIME + STEPCOMPRESS_FLUSH_TIME + self.kin_flush_delay while self.print_time < next_print_time: if self.drip_completion.test(): raise DripModeEndSignal() @@ -569,22 +597,27 @@ def _update_drip_move_time(self, next_print_time): self.drip_completion.wait(curtime + wait_time) continue npt = min(self.print_time + DRIP_SEGMENT_TIME, next_print_time) - self._update_move_time(npt) + self.note_kinematic_activity( + npt + self.kin_flush_delay, set_step_gen_time=True + ) + self._advance_move_time(npt) def drip_move(self, newpos, speed, drip_completion): self.dwell(self.kin_flush_delay) - # Transition from "Flushed"/"Priming"/main state to "Drip" state + # Transition from "NeedPrime"/"Priming"/main state to "Drip" state self.move_queue.flush() self.special_queuing_state = "Drip" - self.need_check_stall = self.reactor.NEVER + self.need_check_pause = self.reactor.NEVER self.reactor.update_timer(self.flush_timer, self.reactor.NEVER) - self.move_queue.set_flush_time(self.buffer_time_high) - self.idle_flush_print_time = 0.0 + self.do_kick_flush_timer = False + self.move_queue.set_flush_time(BUFFER_TIME_HIGH) + self.check_stall_time = 0.0 self.drip_completion = drip_completion # Submit move try: self.move(newpos, speed) except self.printer.command_error as e: + self.reactor.update_timer(self.flush_timer, self.reactor.NOW) self.flush_step_generation() raise # Transmit move in "drip" mode @@ -594,12 +627,14 @@ def drip_move(self, newpos, speed, drip_completion): self.move_queue.reset() self.trapq_finalize_moves(self.trapq, self.reactor.NEVER) # Exit "Drip" state + self.reactor.update_timer(self.flush_timer, self.reactor.NOW) self.flush_step_generation() # Misc commands def stats(self, eventtime): + max_queue_time = max(self.print_time, self.last_flush_time) for m in self.all_mcus: - m.check_active(self.print_time, eventtime) + m.check_active(max_queue_time, eventtime) buffer_time = self.print_time - self.mcu.estimated_print_time(eventtime) is_active = buffer_time > -60.0 or not self.special_queuing_state if self.special_queuing_state == "Drip": @@ -664,8 +699,13 @@ def register_lookahead_callback(self, callback): return last_move.timing_callbacks.append(callback) - def note_kinematic_activity(self, kin_time): - self.last_kin_move_time = max(self.last_kin_move_time, kin_time) + def note_kinematic_activity(self, kin_time, set_step_gen_time=False): + self.need_flush_time = max(self.need_flush_time, kin_time) + if set_step_gen_time: + self.step_gen_time = max(self.step_gen_time, kin_time) + if self.do_kick_flush_timer: + self.do_kick_flush_timer = False + self.reactor.update_timer(self.flush_timer, self.reactor.NOW) def get_max_velocity(self): return self.max_velocity, self.max_accel diff --git a/src/avr/Kconfig b/src/avr/Kconfig index ac3ffa2ab..f1ad54395 100644 --- a/src/avr/Kconfig +++ b/src/avr/Kconfig @@ -11,7 +11,7 @@ config AVR_SELECT select HAVE_GPIO_I2C select HAVE_GPIO_HARD_PWM select HAVE_STRICT_TIMING - select HAVE_LIMITED_CODE_SIZE if MACH_atmega168 + select HAVE_LIMITED_CODE_SIZE if MACH_atmega168 || MACH_atmega328 || MACH_atmega328p config BOARD_DIRECTORY string diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index e39611764..c06bb6ffb 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -10,7 +10,7 @@ config STM32_SELECT select HAVE_GPIO_I2C if !MACH_STM32F031 select HAVE_GPIO_SPI if !MACH_STM32F031 select HAVE_GPIO_SDIO if MACH_STM32F4 - select HAVE_GPIO_HARD_PWM if MACH_STM32F1 || MACH_STM32F4 || MACH_STM32F7 || MACH_STM32G0 || MACH_STM32H7 + select HAVE_GPIO_HARD_PWM if MACH_STM32F070 || MACH_STM32F072 || MACH_STM32F1 || MACH_STM32F4 || MACH_STM32F7 || MACH_STM32G0 || MACH_STM32H7 select HAVE_STRICT_TIMING select HAVE_CHIPID select HAVE_STEPPER_BOTH_EDGE @@ -288,7 +288,7 @@ choice config STM32_FLASH_START_C000 bool "48KiB bootloader (MKS Robin Nano V3)" if MACH_STM32F4x5 config STM32_FLASH_START_10000 - bool "64KiB bootloader" if MACH_STM32F103 || MACH_STM32F446 || MACH_STM32F401 + bool "64KiB bootloader" if MACH_STM32F103 || MACH_STM32F4 config STM32_FLASH_START_800 bool "2KiB bootloader (HID Bootloader)" if MACH_STM32F103 diff --git a/src/stm32/hard_pwm.c b/src/stm32/hard_pwm.c index ad30051f8..bbdd18b21 100644 --- a/src/stm32/hard_pwm.c +++ b/src/stm32/hard_pwm.c @@ -20,7 +20,70 @@ struct gpio_pwm_info { }; static const struct gpio_pwm_info pwm_regs[] = { -#if CONFIG_MACH_STM32F1 +#if CONFIG_MACH_STM32F0 + #if CONFIG_MACH_STM32F070 + {TIM15, GPIO('A', 2), 1, GPIO_FUNCTION(0)}, + {TIM15, GPIO('A', 3), 2, GPIO_FUNCTION(0)}, + {TIM14, GPIO('A', 4), 1, GPIO_FUNCTION(4)}, + {TIM3, GPIO('A', 6), 1, GPIO_FUNCTION(1)}, + {TIM3, GPIO('A', 7), 2, GPIO_FUNCTION(1)}, + {TIM1, GPIO('A', 8), 1, GPIO_FUNCTION(2)}, + {TIM1, GPIO('A', 9), 2, GPIO_FUNCTION(2)}, + {TIM1, GPIO('A', 10), 3, GPIO_FUNCTION(2)}, + {TIM1, GPIO('A', 11), 4, GPIO_FUNCTION(2)}, + {TIM3, GPIO('B', 0), 3, GPIO_FUNCTION(1)}, + {TIM3, GPIO('B', 1), 4, GPIO_FUNCTION(1)}, + {TIM3, GPIO('B', 4), 1, GPIO_FUNCTION(1)}, + {TIM3, GPIO('B', 5), 2, GPIO_FUNCTION(1)}, + {TIM16, GPIO('B', 8), 1, GPIO_FUNCTION(2)}, + {TIM17, GPIO('B', 9), 1, GPIO_FUNCTION(2)}, + {TIM15, GPIO('B', 14), 1, GPIO_FUNCTION(1)}, + {TIM15, GPIO('B', 15), 2, GPIO_FUNCTION(1)}, + {TIM3, GPIO('C', 6), 1, GPIO_FUNCTION(0)}, + {TIM3, GPIO('C', 7), 2, GPIO_FUNCTION(0)}, + {TIM3, GPIO('C', 8), 3, GPIO_FUNCTION(0)}, + {TIM3, GPIO('C', 9), 4, GPIO_FUNCTION(0)} + #endif + #if CONFIG_MACH_STM32F072 + {TIM2, GPIO('A', 1), 2, GPIO_FUNCTION(2)}, + {TIM2, GPIO('A', 2), 3, GPIO_FUNCTION(2)}, + {TIM2, GPIO('A', 3), 4, GPIO_FUNCTION(2)}, + {TIM14, GPIO('A', 4), 1, GPIO_FUNCTION(4)}, + {TIM3, GPIO('A', 6), 1, GPIO_FUNCTION(1)}, + {TIM3, GPIO('A', 7), 2, GPIO_FUNCTION(1)}, + {TIM1, GPIO('A', 8), 1, GPIO_FUNCTION(2)}, + {TIM1, GPIO('A', 9), 2, GPIO_FUNCTION(2)}, + {TIM1, GPIO('A', 10), 3, GPIO_FUNCTION(2)}, + {TIM1, GPIO('A', 11), 4, GPIO_FUNCTION(2)}, + {TIM3, GPIO('B', 0), 3, GPIO_FUNCTION(1)}, + {TIM3, GPIO('B', 1), 4, GPIO_FUNCTION(1)}, + {TIM2, GPIO('B', 3), 2, GPIO_FUNCTION(2)}, + {TIM3, GPIO('B', 4), 1, GPIO_FUNCTION(1)}, + {TIM3, GPIO('B', 5), 2, GPIO_FUNCTION(1)}, + {TIM16, GPIO('B', 8), 1, GPIO_FUNCTION(2)}, + {TIM17, GPIO('B', 9), 1, GPIO_FUNCTION(2)}, + {TIM2, GPIO('B', 10), 3, GPIO_FUNCTION(2)}, + {TIM2, GPIO('B', 11), 4, GPIO_FUNCTION(2)}, + {TIM15, GPIO('B', 14), 1, GPIO_FUNCTION(1)}, + {TIM15, GPIO('B', 15), 2, GPIO_FUNCTION(1)}, + {TIM3, GPIO('C', 6), 1, GPIO_FUNCTION(0)}, + {TIM3, GPIO('C', 7), 2, GPIO_FUNCTION(0)}, + {TIM3, GPIO('C', 8), 3, GPIO_FUNCTION(0)}, + {TIM3, GPIO('C', 9), 4, GPIO_FUNCTION(0)}, + {TIM16, GPIO('E', 0), 1, GPIO_FUNCTION(0)}, + {TIM17, GPIO('E', 1), 1, GPIO_FUNCTION(0)}, + {TIM3, GPIO('E', 3), 1, GPIO_FUNCTION(0)}, + {TIM3, GPIO('E', 4), 2, GPIO_FUNCTION(0)}, + {TIM3, GPIO('E', 5), 3, GPIO_FUNCTION(0)}, + {TIM3, GPIO('E', 6), 4, GPIO_FUNCTION(0)}, + {TIM1, GPIO('E', 9), 1, GPIO_FUNCTION(0)}, + {TIM1, GPIO('E', 11), 2, GPIO_FUNCTION(0)}, + {TIM1, GPIO('E', 13), 3, GPIO_FUNCTION(0)}, + {TIM1, GPIO('E', 14), 4, GPIO_FUNCTION(0)}, + {TIM15, GPIO('F', 9), 1, GPIO_FUNCTION(0)}, + {TIM15, GPIO('F', 10), 2, GPIO_FUNCTION(0)} + #endif +#elif CONFIG_MACH_STM32F1 {TIM2, GPIO('A', 0), 1, GPIO_FUNCTION(2)}, {TIM2, GPIO('A', 1), 2, GPIO_FUNCTION(2)}, {TIM2, GPIO('A', 2), 3, GPIO_FUNCTION(2)}, diff --git a/test/klippy/printers.test b/test/klippy/printers.test index 8d891fb6c..4d5e0929a 100644 --- a/test/klippy/printers.test +++ b/test/klippy/printers.test @@ -56,6 +56,7 @@ DICTIONARY stm32f070.dict # Printers using the stm32f103 DICTIONARY stm32f103.dict +CONFIG ../../config/printer-sovol-sv06-plus-2023.cfg # Printers using the stm32f103 via serial DICTIONARY stm32f103-serial.dict @@ -69,11 +70,17 @@ DICTIONARY stm32f405.dict # Printers using the stm32f407 DICTIONARY stm32f407.dict +# Printers using the stm32f429 +DICTIONARY stm32f429.dict +CONFIG ../../config/generic-bigtreetech-octopus-v1.1.cfg + # Printers using the stm32f446 DICTIONARY stm32f446.dict +CONFIG ../../config/generic-ldo-leviathan-v1.2.cfg # Printers using the stm32h723 DICTIONARY stm32h723.dict +CONFIG ../../config/generic-bigtreetech-octopus-pro-v1.1.cfg # Printers using the stm32h743 DICTIONARY stm32h743.dict diff --git a/test/klippy/pwm.cfg b/test/klippy/pwm.cfg index 45555ca87..fbda91269 100644 --- a/test/klippy/pwm.cfg +++ b/test/klippy/pwm.cfg @@ -13,6 +13,12 @@ value: 0 shutdown_value: 0 cycle_time: 0.01 +[pwm_tool test_pwm_tool] +pin: PH4 +value: 0 +shutdown_value: 0 +cycle_time: 0.01 + [mcu] serial: /dev/ttyACM0 diff --git a/test/klippy/pwm.test b/test/klippy/pwm.test index d204cbc6d..5e74a3e05 100644 --- a/test/klippy/pwm.test +++ b/test/klippy/pwm.test @@ -28,3 +28,11 @@ SET_PIN PIN=soft_pwm_pin VALUE=0.5 CYCLE_TIME=0.5 SET_PIN PIN=soft_pwm_pin VALUE=0.5 CYCLE_TIME=0.5 SET_PIN PIN=soft_pwm_pin VALUE=0.75 CYCLE_TIME=0.5 SET_PIN PIN=soft_pwm_pin VALUE=0.75 CYCLE_TIME=0.75 + +# PWM tool +# Basic test +SET_PIN PIN=test_pwm_tool VALUE=0 +SET_PIN PIN=test_pwm_tool VALUE=0.5 +SET_PIN PIN=test_pwm_tool VALUE=0.5 +SET_PIN PIN=test_pwm_tool VALUE=0.25 +SET_PIN PIN=test_pwm_tool VALUE=1 diff --git a/test/klippy/tmc.cfg b/test/klippy/tmc.cfg index 560691d73..1d68007d7 100644 --- a/test/klippy/tmc.cfg +++ b/test/klippy/tmc.cfg @@ -43,6 +43,7 @@ use_sensorless_homing: true cs_pin: PG2 run_current: .7 home_current: .5 +current_change_dwell_time: 1 sense_resistor: 0.220 diag1_pin: !PK7 diff --git a/test/klippy/z_tilt_ng.cfg b/test/klippy/z_tilt_ng.cfg new file mode 100644 index 000000000..300d3089f --- /dev/null +++ b/test/klippy/z_tilt_ng.cfg @@ -0,0 +1,101 @@ +# Test config for z_tilt and quad_gantry_level +[stepper_x] +step_pin: PF0 +dir_pin: PF1 +enable_pin: !PD7 +microsteps: 16 +rotation_distance: 40 +endstop_pin: ^PE5 +position_endstop: 0 +position_max: 250 +homing_speed: 50 + +[stepper_y] +step_pin: PF6 +dir_pin: !PF7 +enable_pin: !PF2 +microsteps: 16 +rotation_distance: 40 +endstop_pin: ^PJ1 +position_endstop: 0 +position_max: 250 +homing_speed: 50 + +[stepper_z] +step_pin: PL3 +dir_pin: PL1 +enable_pin: !PK0 +microsteps: 16 +rotation_distance: 8 +endstop_pin: ^PD3 +position_endstop: 0.5 +position_max: 250 + +[stepper_z1] +step_pin: PC1 +dir_pin: PC3 +enable_pin: !PC7 +microsteps: 16 +rotation_distance: 8 +endstop_pin: ^PD2 + +[stepper_z2] +step_pin: PH1 +dir_pin: PH0 +enable_pin: !PA1 +microsteps: 16 +rotation_distance: 8 + +[z_tilt_ng] +z_positions: + -55,-7 + -55,320 + 305,-7 +points: + 50,50 + 50,195 + 195,195 +extra_points: + 50,50 + 50,195 + 195,195 + +[extruder] +step_pin: PA4 +dir_pin: PA6 +enable_pin: !PA2 +microsteps: 16 +rotation_distance: 33.5 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PB4 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PK5 +control: pid +pid_Kp: 22.2 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 250 + +[heater_bed] +heater_pin: PH5 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PK6 +control: watermark +min_temp: 0 +max_temp: 130 + +[probe] +pin: PH6 +z_offset: 1.15 + +[mcu] +serial: /dev/ttyACM0 + +[printer] +kinematics: cartesian +max_velocity: 300 +max_accel: 3000 +max_z_velocity: 5 +max_z_accel: 100 diff --git a/test/klippy/z_tilt_ng.test b/test/klippy/z_tilt_ng.test new file mode 100644 index 000000000..2649097ad --- /dev/null +++ b/test/klippy/z_tilt_ng.test @@ -0,0 +1,28 @@ +# Test case for z_tilt_ng +CONFIG z_tilt_ng.cfg +DICTIONARY atmega2560.dict + +# Start by homing the printer. +G28 +G1 Z5 X10 Y10 F6000 +M400 +GET_POSITION + +# Run Z_TILT_ADJUST in manual mode +Z_TILT_ADJUST METHOD=MANUAL +G1 Z2.909972 +ACCEPT +G1 Z2.924972 +ACCEPT +G1 Z2.959972 +ACCEPT +G1 Z2.924972 +ACCEPT + +# Report position +G1 Z5 X10 Y10 F6000 +M400 +GET_POSITION + +# Run again in automatic mode +Z_TILT_ADJUST