From 20d87c73fe8750118741b8fc9f057c5861ee5df0 Mon Sep 17 00:00:00 2001 From: Donna Whisnant Date: Mon, 24 Jul 2023 19:07:11 -0500 Subject: [PATCH 01/48] scripts: Support both H743 and H723 BTT SKR3 boards sdflash Signed-off-by: Donna Whisnant --- scripts/spi_flash/board_defs.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/spi_flash/board_defs.py b/scripts/spi_flash/board_defs.py index fe2e8c063..7dc44afc9 100644 --- a/scripts/spi_flash/board_defs.py +++ b/scripts/spi_flash/board_defs.py @@ -87,13 +87,20 @@ 'spi_bus': 'spi3a', 'cs_pin': 'PA15' }, - 'btt-skr-3': { + 'btt-skr-3-h743': { 'mcu': 'stm32h743xx', 'spi_bus': 'swspi', 'spi_pins': "PC8,PD2,PC12", 'cs_pin': 'PC11', 'skip_verify': True }, + 'btt-skr-3-h723': { + 'mcu': 'stm32h723xx', + 'spi_bus': 'swspi', + 'spi_pins': "PC8,PD2,PC12", + 'cs_pin': 'PC11', + 'skip_verify': True + }, 'creality-v4.2.2': { 'mcu': "stm32f103xe", 'spi_bus': "swspi", From 00b78c68ccf42af6c75ad6051e68d494446bac94 Mon Sep 17 00:00:00 2001 From: Donna Whisnant Date: Thu, 10 Aug 2023 20:23:17 -0500 Subject: [PATCH 02/48] docs: Update Config_Changes.md to reflect new SKR-3 flash-sdcard naming. Signed-off-by: Donna Whisnant --- docs/Config_Changes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/Config_Changes.md b/docs/Config_Changes.md index 8f8ec37a3..063af8ac3 100644 --- a/docs/Config_Changes.md +++ b/docs/Config_Changes.md @@ -8,6 +8,10 @@ All dates in this document are approximate. ## Changes +20230810: The flash-sdcard.sh script now supports both variants of the +Bigtreetech SKR-3, STM32H743 and STM32H723. For this, the original tag +of btt-skr-3 now has changed to be either btt-skr-3-h743 or btt-skr-3-h723. + 20230729: The exported status for `dual_carriage` is changed. Instead of exporting `mode` and `active_carriage`, the individual modes for each carriage are exported as `printer.dual_carriage.carriage_0` and From 594c024355b68e16104f36600da2975c1a38ac40 Mon Sep 17 00:00:00 2001 From: Thijs Triemstra Date: Sat, 12 Aug 2023 03:17:13 +0200 Subject: [PATCH 03/48] github: update dependencies (#6293) Signed-off-by: Thijs Triemstra --- .github/workflows/build-test.yaml | 6 +++--- .github/workflows/klipper3d-deploy.yaml | 8 ++++---- .github/workflows/stale-issue-bot.yaml | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml index 99666686f..fda7baf86 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build-test.yaml @@ -6,10 +6,10 @@ jobs: build: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ci_cache key: ${{ runner.os }}-build-${{ hashFiles('scripts/ci-install.sh') }} @@ -21,7 +21,7 @@ jobs: run: ./scripts/ci-build.sh 2>&1 - name: Upload micro-controller data dictionaries - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: data-dict path: ci_build/dict diff --git a/.github/workflows/klipper3d-deploy.yaml b/.github/workflows/klipper3d-deploy.yaml index 2e6e2c9d0..609644bbc 100644 --- a/.github/workflows/klipper3d-deploy.yaml +++ b/.github/workflows/klipper3d-deploy.yaml @@ -12,12 +12,12 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: '3.8' - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('docs/_klipper3d/mkdocs-requirements.txt') }} @@ -28,7 +28,7 @@ jobs: - name: Build MkDocs Pages run: docs/_klipper3d/build-translations.sh - name: Deploy - uses: JamesIves/github-pages-deploy-action@v4.2.5 + uses: JamesIves/github-pages-deploy-action@v4.4.3 with: branch: gh-pages # The branch the action should deploy to. folder: site # The folder the action should deploy. diff --git a/.github/workflows/stale-issue-bot.yaml b/.github/workflows/stale-issue-bot.yaml index b6369d8dc..9d4b62670 100644 --- a/.github/workflows/stale-issue-bot.yaml +++ b/.github/workflows/stale-issue-bot.yaml @@ -9,7 +9,7 @@ jobs: if: github.repository == 'Klipper3d/klipper' runs-on: ubuntu-latest steps: - - uses: actions/stale@v3 + - uses: actions/stale@v8 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: | From aa8ba30f8a8453c739a009870752fb645ed4c27b Mon Sep 17 00:00:00 2001 From: D4SK <42315676+D4SK@users.noreply.github.com> Date: Sat, 12 Aug 2023 03:21:46 +0200 Subject: [PATCH 04/48] cartesian: fix x-axis being hardcoded for dual_carriage (#6313) Signed-off-by: Konstantin Vogel --- klippy/kinematics/cartesian.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/klippy/kinematics/cartesian.py b/klippy/kinematics/cartesian.py index 596041976..6d576b5b5 100644 --- a/klippy/kinematics/cartesian.py +++ b/klippy/kinematics/cartesian.py @@ -30,7 +30,8 @@ def __init__(self, toolhead, config): self.rails[3].setup_itersolve('cartesian_stepper_alloc', dc_axis.encode()) dc_rail_0 = idex_modes.DualCarriagesRail( - self.rails[0], axis=self.dual_carriage_axis, active=True) + self.rails[self.dual_carriage_axis], + axis=self.dual_carriage_axis, active=True) dc_rail_1 = idex_modes.DualCarriagesRail( self.rails[3], axis=self.dual_carriage_axis, active=False) self.dc_module = idex_modes.DualCarriages( From 07792a02b45460359faf48e8a47cea1e958a436c Mon Sep 17 00:00:00 2001 From: Chris Lombardi Date: Fri, 11 Aug 2023 16:58:57 -0500 Subject: [PATCH 05/48] scripts: Added stm32f070 to mcus supported by flash_usb.py Verified with an Monoprice Mini Select v2 using katapult. Signed-off-by: Chris Lombardi --- scripts/flash_usb.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/flash_usb.py b/scripts/flash_usb.py index ed139dfee..33c437602 100755 --- a/scripts/flash_usb.py +++ b/scripts/flash_usb.py @@ -344,8 +344,8 @@ def flash_rp2040(options, binfile): 'samd': flash_atsamd, 'same5': flash_atsamd, 'lpc176': flash_lpc176x, 'stm32f103': flash_stm32f1, 'stm32f4': flash_stm32f4, 'stm32f042': flash_stm32f4, - 'stm32f072': flash_stm32f4, 'stm32g0b1': flash_stm32f4, - 'stm32f7': flash_stm32f4, + 'stm32f070': flash_stm32f4, 'stm32f072': flash_stm32f4, + 'stm32g0b1': flash_stm32f4, 'stm32f7': flash_stm32f4, 'stm32h7': flash_stm32f4, 'stm32l4': flash_stm32f4, 'stm32g4': flash_stm32f4, 'rp2040': flash_rp2040, } From dd368c107c099cd60514d34f17122baf2667e81d Mon Sep 17 00:00:00 2001 From: FatalBulletHit <37146617+FatalBulletHit@users.noreply.github.com> Date: Sun, 13 Aug 2023 21:23:58 +0200 Subject: [PATCH 06/48] docs: Update RPi_microcontroller.md (pwm-2chan) (#6304) Added a more detailed explanation for pwm-2chan. Signed-off-by: Alfie Batthullt --- docs/RPi_microcontroller.md | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/docs/RPi_microcontroller.md b/docs/RPi_microcontroller.md index 58688d70e..4e3c057fe 100644 --- a/docs/RPi_microcontroller.md +++ b/docs/RPi_microcontroller.md @@ -198,18 +198,28 @@ default on a Raspberry and can be activated by adding a line to dtoverlay=pwm,pin=12,func=4 ``` This example enables only PWM0 and routes it to gpio12. If both PWM -channels need to be enabled you can use `pwm-2chan`. +channels need to be enabled you can use `pwm-2chan`: +``` +# Enable pwmchip sysfs interface +dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=4 +``` +This example additionaly enables PWM1 and routes it to gpio13. The overlay does not expose the pwm line on sysfs on boot and needs to be exported by echo'ing the number of the pwm channel to -`/sys/class/pwm/pwmchip0/export`: +`/sys/class/pwm/pwmchip0/export`. This will create device `/sys/class/pwm/pwmchip0/pwm0` in the +filesystem. The easiest way to do this is by adding this to +`/etc/rc.local` before the `exit 0` line: ``` +# Enable pwmchip sysfs interface echo 0 > /sys/class/pwm/pwmchip0/export ``` - -This will create device `/sys/class/pwm/pwmchip0/pwm0` in the -filesystem. The easiest way to do this is by adding this to -`/etc/rc.local` before the `exit 0` line. +When using both PWM channels, the number of the second channel needs to be echo'd as well: +``` +# Enable pwmchip sysfs interface +echo 0 > /sys/class/pwm/pwmchip0/export +echo 1 > /sys/class/pwm/pwmchip0/export +``` With the sysfs in place, you can now use either the pwm channel(s) by adding the following piece of configuration to your `printer.cfg`: @@ -219,9 +229,17 @@ pin: host:pwmchip0/pwm0 pwm: True hardware_pwm: True cycle_time: 0.000001 + +[output_pin beeper] +pin: host:pwmchip0/pwm1 +pwm: True +hardware_pwm: True +value: 0 +shutdown_value: 0 +cycle_time: 0.0005 ``` -This will add hardware pwm control to gpio12 on the Pi (because the -overlay was configured to route pwm0 to pin=12). +This will add hardware pwm control to gpio12 and gpio13 on the Pi (because the +overlay was configured to route pwm0 to pin=12 and pwm1 to pin=13). PWM0 can be routed to gpio12 and gpio18, PWM1 can be routed to gpio13 and gpio19: From 15c3824e9b15a0b5436995723e97599145fc588d Mon Sep 17 00:00:00 2001 From: flatline-84 <2100452+flatline-84@users.noreply.github.com> Date: Mon, 14 Aug 2023 05:26:56 +1000 Subject: [PATCH 07/48] config: Add config for printer Creality CR10s Pro V2 (#6300) Created a base config for the Creality CR10s Pro v2 as described here: https://www.creality.com/products/cr-10s-pro-v2-3d-printer This will allow users with this printer to more easily setup Klipper firmware. Signed-off-by: Peter Kydas --- config/printer-creality-cr10s-pro-v2-2020.cfg | 154 ++++++++++++++++++ test/klippy/printers.test | 1 + 2 files changed, 155 insertions(+) create mode 100644 config/printer-creality-cr10s-pro-v2-2020.cfg diff --git a/config/printer-creality-cr10s-pro-v2-2020.cfg b/config/printer-creality-cr10s-pro-v2-2020.cfg new file mode 100644 index 000000000..e06cd82c1 --- /dev/null +++ b/config/printer-creality-cr10s-pro-v2-2020.cfg @@ -0,0 +1,154 @@ +# This file contains pin mappings for the Creality CR-10S Pro V2. To use +# this config, the firmware should be compiled for the AVR atmega2560. + +# See docs/Config_Reference.md for a description of parameters. + +## General Config +[mcu] +serial: /dev/serial/by-id/ + +[printer] +kinematics: cartesian +max_velocity: 200 +max_accel: 1500 +max_z_velocity: 10 +max_z_accel: 100 + +## Stepper Motors and Extruder +[stepper_x] +step_pin: PF0 #ar54 +dir_pin: PF1 #ar55 +enable_pin: !PD7 #!ar38 +rotation_distance: 40 +microsteps: 16 +full_steps_per_rotation: 200 +endstop_pin: ^PE5 #^ar3 +position_endstop: 0 +position_min: 0 +position_max: 300 +homing_speed: 50 +homing_retract_dist: 5 + +[stepper_y] +step_pin: PF6 #ar60 +dir_pin: !PF7 #ar61 +enable_pin: !PF2 #!ar56 +rotation_distance: 40 +microsteps: 16 +full_steps_per_rotation: 200 +endstop_pin: ^PJ1 #^ar14 +position_endstop: 0 +position_min: 0 +position_max: 310 +homing_speed: 50 +homing_retract_dist: 5 + +[stepper_z] +step_pin: PL3 #ar46 +dir_pin: !PL1 #!ar48 +enable_pin: !PK0 #!ar62 +rotation_distance: 8 +microsteps: 16 +full_steps_per_rotation: 200 +endstop_pin: probe:z_virtual_endstop +position_min: -3 +position_max: 363 # you can go higher but then the cables crimp + +[extruder] +step_pin: PA4 # ar26 +dir_pin: PA6 # !ar28 +enable_pin: !PA2 # !ar24 +rotation_distance: 22.900 +microsteps: 16 +full_steps_per_rotation: 200 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +max_extrude_only_distance: 500.0 +max_extrude_only_velocity: 70.0 +max_extrude_only_accel: 1000.0 +heater_pin: PB4 #ar10 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PK5 #analog13 +control: pid #calibrated in Klipper, you will need to run this for your machine +pid_kp: 28.359 +pid_ki: 1.616 +pid_kd: 124.426 +min_extrude_temp: 170 +min_temp: 5 +max_temp: 275 + +[fan] +pin: PH6 #ar9 + +[heater_bed] +heater_pin: PH5 #ar8 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PK6 #analog14 +control: pid #calibrated in Klipper, you will need to run this for your machine +pid_kp: 70.936 +pid_ki: 1.151 +pid_kd: 1093.298 +min_temp: 5 +max_temp: 140 + +## BLTouch and Safe Z Settings +[bltouch] +sensor_pin: ^PD2 +control_pin: PB5 +stow_on_each_sample: False # Be careful +probe_with_touch_mode: True +x_offset: -27 +y_offset: -2 +z_offset: 2 # you will need to calibrate this in Klipper +speed: 4.0 +samples: 2 +sample_retract_dist: 3.0 + +[safe_z_home] +home_xy_position: 177,155 +speed: 80.0 +z_hop: 10.0 +z_hop_speed: 5.0 + +[bed_mesh] +speed: 120 +horizontal_move_z: 5 +mesh_min: 5, 5 +mesh_max: 268, 305 +probe_count: 5,5 +fade_start: 1 +fade_end: 10 + +[filament_switch_sensor e0_sensor] +switch_pin: PE4 #ar2 +pause_on_runout: False +runout_gcode: + PAUSE_PARK + +## Calibrating the Bed +# this is for the bltouch to calibrate the bed +[screws_tilt_adjust] +screw1: 40,40 +screw1_name: front left screw +screw2: 295,40 +screw2_name: front right screw +screw3: 295,280 +screw3_name: rear right screw +screw4: 40,280 +screw4_name: rear left screw +speed: 50 +horizontal_move_z: 10 +screw_thread: CW-M3 + +[bed_screws] +screw1: 13,38 +screw1_name: front left screw +screw2: 268,38 +screw2_name: front right screw +screw3: 268,278 +screw3_name: rear right screw +screw4: 13,38 +screw4_name: rear left screw +horizontal_move_z: 5 + +[pause_resume] diff --git a/test/klippy/printers.test b/test/klippy/printers.test index 9db20bed9..5aa78ac1d 100644 --- a/test/klippy/printers.test +++ b/test/klippy/printers.test @@ -34,6 +34,7 @@ CONFIG ../../config/printer-bq-hephestos-2014.cfg CONFIG ../../config/printer-creality-cr5pro-ht-2022.cfg CONFIG ../../config/printer-creality-cr10-v3-2020.cfg CONFIG ../../config/printer-creality-cr10s-2017.cfg +CONFIG ../../config/printer-creality-cr10s-pro-v2-2020.cfg CONFIG ../../config/printer-creality-cr20-2018.cfg CONFIG ../../config/printer-creality-cr20-pro-2019.cfg CONFIG ../../config/printer-creality-ender5plus-2019.cfg From 4292136b16e2dcc60dca4cefda3a90065dcd0eca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Falk=20H=C3=B6ppner?= Date: Sat, 24 Jun 2023 11:34:06 +0200 Subject: [PATCH 08/48] hc32f460: Add app address 0x10000 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Required for the Anycubic Kobra 2. Signed-off-by: Falk Höppner --- src/hc32f460/Kconfig | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/hc32f460/Kconfig b/src/hc32f460/Kconfig index 9de4a7fc6..5dee95f9a 100644 --- a/src/hc32f460/Kconfig +++ b/src/hc32f460/Kconfig @@ -51,16 +51,19 @@ config FLASH_SIZE choice "Application Address" prompt "Application Address" - config HC32F460_FLASH_APPLICATION_ADDRESS_0x8000 - bool "0x8000" - config HC32F460_FLASH_APPLICATION_ADDRESS_0xC000 - bool "0xC000" + config HC32F460_FLASH_APPLICATION_ADDRESS_0x008000 + bool "0x008000" + config HC32F460_FLASH_APPLICATION_ADDRESS_0x00C000 + bool "0x00C000" + config HC32F460_FLASH_APPLICATION_ADDRESS_0x010000 + bool "0x010000" endchoice config FLASH_APPLICATION_ADDRESS hex - default 0x8000 if HC32F460_FLASH_APPLICATION_ADDRESS_0x8000 - default 0xC000 if HC32F460_FLASH_APPLICATION_ADDRESS_0xC000 + default 0x008000 if HC32F460_FLASH_APPLICATION_ADDRESS_0x008000 + default 0x00C000 if HC32F460_FLASH_APPLICATION_ADDRESS_0x00C000 + default 0x010000 if HC32F460_FLASH_APPLICATION_ADDRESS_0x010000 config FLASH_BOOT_ADDRESS hex From 261efdd86c5fcf97c26e240b1d7b5e65f07920ac Mon Sep 17 00:00:00 2001 From: Wulfsta Date: Thu, 20 Jul 2023 23:32:27 -0400 Subject: [PATCH 09/48] config: Add 1LC example configuration and docs Signed-off-by: Luke Vuksta --- config/sample-duet3-1lc.cfg | 81 +++++++++++++++++++++++++++++++++++++ docs/Bootloaders.md | 44 ++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 config/sample-duet3-1lc.cfg diff --git a/config/sample-duet3-1lc.cfg b/config/sample-duet3-1lc.cfg new file mode 100644 index 000000000..298c6fafb --- /dev/null +++ b/config/sample-duet3-1lc.cfg @@ -0,0 +1,81 @@ +# This file contains common pin mappings for the Duet3 1LC. To use +# this config, the firmware should be compiled for the SAMC21G18 with: +# Bootloader offset of "No Bootloader" +# Clock Reference of "25 Mhz crystal" if the board version is v1.1 or later +# Clock Reference of "12 Mhz crystal" if the board version is v1.0 or earlier +# Communication interface of "CAN bus (on PA25/PA24)" + +# To flash the board use a debugger, or use a raspberry pi and follow +# the instructions at docs/Bootloaders.md fot the SAMC21. You may +# supply power to the 1LC by connecting the 3.3v rail on the Pi to the +# 5v input of the SWD header on the 1LC. + +# See docs/Config_Reference.md for a description of parameters. + + +# Pins for reference, v1.3 board: +# Driver Step Pin - PA27 +# Driver Dir Pin - PA28 +# Driver Enable - !PB2 +# Thermistor Pins - TEMP0:PB9 TEMP1:PA2 +# Pullup Resistor - 2200 +# Vssa Sense:PA6 | Vref Sense:PA7 +# Current Sense resistor for drivers - 0.091ohm +# CAN Pins - CAN0_TX:PA24 CAN0_RX:PA25 +# Heaters - OUT_0:PA11 +# Fan outputs - OUT_1:PA10 OUT_2:PB11 +# Tach Pins for Fans - OUT_1_TACHO:PA13 OUT_2_TACHO:PB10 +# GPIO_out - IO0:PA12 +# GPIO_in - IO0:PA9 IO1:PA21 IO2:PA18 +# Driver Diag - 0:PB3 + +[adc_scaled toolboard_vref_scaled] +vref_pin: toolboard:PA7 +vssa_pin: toolboard:PA6 + +[extruder] +step_pin: toolboard:PA27 +dir_pin: toolboard:PA28 +enable_pin: !toolboard:PB2 +rotation_distance: 23.1336867485061 +gear_ratio: 50:10 +microsteps: 64 +full_steps_per_rotation: 200 +nozzle_diameter: 0.400 +filament_diameter: 1.75 +heater_pin: toolboard:PA11 +sensor_type: PT1000 +sensor_pin: toolboard_vref_scaled:PB9 +pullup_resistor: 2200 +min_temp: 0 +max_temp: 280 +max_power: 1.0 +control: pid +pwm_cycle_time: 0.01666 +pid_Kp: 26.454 +pid_Ki: 1.357 +pid_Kd: 128.955 + +[tmc2209 extruder] +uart_pin: toolboard:PA20 +tx_pin: toolboard:PA22 +interpolate: False +run_current: 0.35 +sense_resistor: 0.091 + +[fan] +pin: toolboard:PA10 +tachometer_pin: toolboard:PA13 + +[heater_fan hotend_fan] +pin: toolboard:PB11 +tachometer_pin: toolboard:PB10 +heater: extruder +heater_temp: 50.0 + +[probe] +pin: toolboard:PA9 +z_offset: 20 + +[mcu toolboard] +canbus_uuid: 4b194673554e diff --git a/docs/Bootloaders.md b/docs/Bootloaders.md index 1f7aa24db..56a810955 100644 --- a/docs/Bootloaders.md +++ b/docs/Bootloaders.md @@ -185,6 +185,50 @@ To flash an application use something like: bossac --port=/dev/ttyACM0 -b -U -e -w -v -R out/klipper.bin ``` +## SAMDC21 micro-controllers (Duet3D Toolboard 1LC) + +The SAMC21 is flashed via the ARM Serial Wire Debug (SWD) interface. +This is commonly done with a dedicated SWD hardware dongle. +Alternatively, one can use a +[Raspberry Pi with OpenOCD](#running-openocd-on-the-raspberry-pi). + +When using OpenOCD with the SAMC21, extra steps must be taken to first +put the chip into Cold Plugging mode if the board makes use of the +SWD pins for other purposes. If using OpenOCD on a Rasberry Pi, this +can be done by running the following commands before invoking OpenOCD. +``` +SWCLK=25 +SWDIO=24 +SRST=18 + +echo "Exporting SWCLK and SRST pins." +echo $SWCLK > /sys/class/gpio/export +echo $SRST > /sys/class/gpio/export +echo "out" > /sys/class/gpio/gpio$SWCLK/direction +echo "out" > /sys/class/gpio/gpio$SRST/direction + +echo "Setting SWCLK low and pulsing SRST." +echo "0" > /sys/class/gpio/gpio$SWCLK/value +echo "0" > /sys/class/gpio/gpio$SRST/value +echo "1" > /sys/class/gpio/gpio$SRST/value + +echo "Unexporting SWCLK and SRST pins." +echo $SWCLK > /sys/class/gpio/unexport +echo $SRST > /sys/class/gpio/unexport +``` + +To flash a program with OpenOCD use the following chip config: +``` +source [find target/at91samdXX.cfg] +``` +Obtain a program; for instance, klipper can be built for this chip. +Flash with OpenOCD commands similar to: +``` +at91samd chip-erase +at91samd bootloader 0 +program out/klipper.elf verify +``` + ## SAMD21 micro-controllers (Arduino Zero) The SAMD21 bootloader is flashed via the ARM Serial Wire Debug (SWD) From 01ed8096d8b53fd58f760b413533f1ef15f1b667 Mon Sep 17 00:00:00 2001 From: locki-cz <30490842+locki-cz@users.noreply.github.com> Date: Tue, 15 Aug 2023 20:11:51 +0200 Subject: [PATCH 10/48] spi_flash: Update board_defs.py (#6318) Added alias for Octopus pro h723 v1.1 for sdcard update script. Signed-off-by: David Bucek --- scripts/spi_flash/board_defs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/spi_flash/board_defs.py b/scripts/spi_flash/board_defs.py index 7dc44afc9..4f84d7229 100644 --- a/scripts/spi_flash/board_defs.py +++ b/scripts/spi_flash/board_defs.py @@ -166,6 +166,7 @@ 'btt-octopus-f446-v1.1': BOARD_DEFS['btt-octopus-f446-v1'], 'btt-octopus-pro-f429-v1.0': BOARD_DEFS['btt-octopus-f429-v1'], 'btt-octopus-pro-f446-v1.0': BOARD_DEFS['btt-octopus-f446-v1'], + 'btt-octopus-pro-h723-v1.1': BOARD_DEFS['btt-skr-3-h723'], 'btt-skr-pro-v1.1': BOARD_DEFS['btt-skr-pro'], 'btt-skr-pro-v1.2': BOARD_DEFS['btt-skr-pro'], 'btt-gtr-v1': BOARD_DEFS['btt-gtr'], From c9aa7ac871480651ed29ac211734641449c9cdb8 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 21 Aug 2023 13:12:09 -0400 Subject: [PATCH 11/48] github: Fix regression due to update of actions/stale Commit 594c0243 updated the workflows from actions/stale@v3 to actions/stale@v8, but did not make the corresponding updates to the config parameters. This resulted in the closing of some PRs. Update the config parameters to avoid this regression. Signed-off-by: Kevin O'Connor --- .github/workflows/stale-issue-bot.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/stale-issue-bot.yaml b/.github/workflows/stale-issue-bot.yaml index 9d4b62670..3ada94d61 100644 --- a/.github/workflows/stale-issue-bot.yaml +++ b/.github/workflows/stale-issue-bot.yaml @@ -31,6 +31,8 @@ jobs: exempt-issue-labels: 'enhancement,bug' days-before-stale: 35 days-before-close: 7 + days-before-pr-stale: -1 + days-before-pr-close: -1 # Close tickets marked with "not on github" label close_not_on_github: if: github.repository == 'Klipper3d/klipper' From ec1dcf3bd207a4102d6617878f32d0a99825c84c Mon Sep 17 00:00:00 2001 From: bigtreetech Date: Wed, 5 Jul 2023 20:23:40 +0800 Subject: [PATCH 12/48] lis2dw12: Add support for lis2dw12 accelerometer lis2dw12 is an accelerometer from STMicroelectronics(https://www.st.com/resource/en/datasheet/lis2dw12.pdf) With better performance than the ADXL345 according to the datasheet. Signed-off-by: XM.Zhou from BigTreeTech zhouxm@biqu3d.com Signed-off-by: Alan.Ma from BigTreeTech tech@biqu3d.com --- klippy/extras/lis2dw.py | 265 ++++++++++++++++++++++++++++++++++++++++ src/Makefile | 2 +- src/sensor_lis2dw.c | 221 +++++++++++++++++++++++++++++++++ 3 files changed, 487 insertions(+), 1 deletion(-) create mode 100644 klippy/extras/lis2dw.py create mode 100644 src/sensor_lis2dw.c diff --git a/klippy/extras/lis2dw.py b/klippy/extras/lis2dw.py new file mode 100644 index 000000000..5cf676393 --- /dev/null +++ b/klippy/extras/lis2dw.py @@ -0,0 +1,265 @@ +# Support for reading acceleration data from an LIS2DW chip +# +# Copyright (C) 2023 Zhou.XianMing +# Copyright (C) 2020-2021 Kevin O'Connor +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import logging, time, collections, threading, multiprocessing, os +from . import bus, motion_report, adxl345 + +# LIS2DW registers +REG_LIS2DW_WHO_AM_I_ADDR = 0x0F +REG_LIS2DW_CTRL_REG1_ADDR = 0x20 +REG_LIS2DW_CTRL_REG2_ADDR = 0x21 +REG_LIS2DW_CTRL_REG3_ADDR = 0x22 +REG_LIS2DW_CTRL_REG6_ADDR = 0x25 +REG_LIS2DW_STATUS_REG_ADDR = 0x27 +REG_LIS2DW_OUT_XL_ADDR = 0x28 +REG_LIS2DW_OUT_XH_ADDR = 0x29 +REG_LIS2DW_OUT_YL_ADDR = 0x2A +REG_LIS2DW_OUT_YH_ADDR = 0x2B +REG_LIS2DW_OUT_ZL_ADDR = 0x2C +REG_LIS2DW_OUT_ZH_ADDR = 0x2D +REG_LIS2DW_FIFO_CTRL = 0x2E +REG_LIS2DW_FIFO_SAMPLES = 0x2F +REG_MOD_READ = 0x80 +# REG_MOD_MULTI = 0x40 + +LIS2DW_DEV_ID = 0x44 + +FREEFALL_ACCEL = 9.80665 +SCALE = FREEFALL_ACCEL * 1.952 / 4 + +Accel_Measurement = collections.namedtuple( + 'Accel_Measurement', ('time', 'accel_x', 'accel_y', 'accel_z')) + +MIN_MSG_TIME = 0.100 + +BYTES_PER_SAMPLE = 6 +SAMPLES_PER_BLOCK = 8 + +# Printer class that controls LIS2DW chip +class LIS2DW: + def __init__(self, config): + self.printer = config.get_printer() + adxl345.AccelCommandHelper(config, self) + self.query_rate = 0 + am = {'x': (0, SCALE), 'y': (1, SCALE), 'z': (2, SCALE), + '-x': (0, -SCALE), '-y': (1, -SCALE), '-z': (2, -SCALE)} + axes_map = config.getlist('axes_map', ('x','y','z'), count=3) + if any([a not in am for a in axes_map]): + raise config.error("Invalid lis2dw axes_map parameter") + self.axes_map = [am[a.strip()] for a in axes_map] + self.data_rate = 1600 + # Measurement storage (accessed from background thread) + self.lock = threading.Lock() + self.raw_samples = [] + # Setup mcu sensor_lis2dw bulk query code + self.spi = bus.MCU_SPI_from_config(config, 3, default_speed=5000000) + self.mcu = mcu = self.spi.get_mcu() + self.oid = oid = mcu.create_oid() + self.query_lis2dw_cmd = self.query_lis2dw_end_cmd = None + self.query_lis2dw_status_cmd = None + mcu.add_config_cmd("config_lis2dw oid=%d spi_oid=%d" + % (oid, self.spi.get_oid())) + mcu.add_config_cmd("query_lis2dw oid=%d clock=0 rest_ticks=0" + % (oid,), on_restart=True) + mcu.register_config_callback(self._build_config) + mcu.register_response(self._handle_lis2dw_data, "lis2dw_data", oid) + # Clock tracking + self.last_sequence = self.max_query_duration = 0 + self.last_limit_count = self.last_error_count = 0 + self.clock_sync = adxl345.ClockSyncRegression(self.mcu, 640) + # API server endpoints + self.api_dump = motion_report.APIDumpHelper( + self.printer, self._api_update, self._api_startstop, 0.100) + self.name = config.get_name().split()[-1] + wh = self.printer.lookup_object('webhooks') + wh.register_mux_endpoint("lis2dw/dump_lis2dw", "sensor", self.name, + self._handle_dump_lis2dw) + + def _build_config(self): + cmdqueue = self.spi.get_command_queue() + self.query_lis2dw_cmd = self.mcu.lookup_command( + "query_lis2dw oid=%c clock=%u rest_ticks=%u", cq=cmdqueue) + self.query_lis2dw_end_cmd = self.mcu.lookup_query_command( + "query_lis2dw oid=%c clock=%u rest_ticks=%u", + "lis2dw_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" + " buffered=%c fifo=%c limit_count=%hu", oid=self.oid, cq=cmdqueue) + self.query_lis2dw_status_cmd = self.mcu.lookup_query_command( + "query_lis2dw_status oid=%c", + "lis2dw_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" + " buffered=%c fifo=%c limit_count=%hu", oid=self.oid, cq=cmdqueue) + def read_reg(self, reg): + params = self.spi.spi_transfer([reg | REG_MOD_READ, 0x00]) + response = bytearray(params['response']) + return response[1] + def set_reg(self, reg, val, minclock=0): + self.spi.spi_send([reg, val & 0xFF], minclock=minclock) + stored_val = self.read_reg(reg) + if stored_val != val: + raise self.printer.command_error( + "Failed to set LIS2DW register [0x%x] to 0x%x: got 0x%x. " + "This is generally indicative of connection problems " + "(e.g. faulty wiring) or a faulty lis2dw chip." % ( + reg, val, stored_val)) + # Measurement collection + def is_measuring(self): + return self.query_rate > 0 + def _handle_lis2dw_data(self, params): + with self.lock: + self.raw_samples.append(params) + def _extract_samples(self, raw_samples): + # Load variables to optimize inner loop below + (x_pos, x_scale), (y_pos, y_scale), (z_pos, z_scale) = self.axes_map + last_sequence = self.last_sequence + time_base, chip_base, inv_freq = self.clock_sync.get_time_translation() + # Process every message in 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 -= (seq_diff & 0x8000) << 1 + 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): + d_xyz = d[i*BYTES_PER_SAMPLE:(i+1)*BYTES_PER_SAMPLE] + xlow, xhigh, ylow, yhigh, zlow, zhigh = d_xyz + # Merge and perform twos-complement + + rx = (((xhigh << 8) | xlow)) - ((xhigh & 0x80) << 9) + ry = (((yhigh << 8) | ylow)) - ((yhigh & 0x80) << 9) + rz = (((zhigh << 8) | zlow)) - ((zhigh & 0x80) << 9) + + raw_xyz = (rx, ry, rz) + + x = round(raw_xyz[x_pos] * x_scale, 6) + y = round(raw_xyz[y_pos] * y_scale, 6) + z = round(raw_xyz[z_pos] * z_scale, 6) + + ptime = round(time_base + (msg_cdiff + i) * inv_freq, 6) + samples[count] = (ptime, x, y, z) + count += 1 + self.clock_sync.set_last_chip_clock(seq * SAMPLES_PER_BLOCK + i) + del samples[count:] + return samples + def _update_clock(self, minclock=0): + # Query current state + for retry in range(5): + params = self.query_lis2dw_status_cmd.send([self.oid], + minclock=minclock) + fifo = params['fifo'] & 0x1f + if fifo <= 32: + break + 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 + 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 + duration = params['query_ticks'] + if duration > self.max_query_duration: + # Skip measurement as a high query time could skew clock tracking + self.max_query_duration = max(2 * self.max_query_duration, + self.mcu.seconds_to_clock(.000005)) + return + self.max_query_duration = 2 * duration + msg_count = (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 + # of lis2dw hw processing time. + chip_clock = msg_count + 1 + self.clock_sync.update(mcu_clock + duration // 2, chip_clock) + def _start_measurements(self): + if self.is_measuring(): + return + # In case of miswiring, testing LIS2DW device ID prevents treating + # noise or wrong signal as a correctly initialized device + dev_id = self.read_reg(REG_LIS2DW_WHO_AM_I_ADDR) + logging.info("lis2dw_dev_id: %x", dev_id) + if dev_id != LIS2DW_DEV_ID: + raise self.printer.command_error( + "Invalid lis2dw id (got %x vs %x).\n" + "This is generally indicative of connection problems\n" + "(e.g. faulty wiring) or a faulty lis2dw chip." + % (dev_id, LIS2DW_DEV_ID)) + # Setup chip in requested query rate + # ODR/2, +-16g, low-pass filter, Low-noise abled + self.set_reg(REG_LIS2DW_CTRL_REG6_ADDR, 0x34) + # Continuous mode: If the FIFO is full + # the new sample overwrites the older sample. + self.set_reg(REG_LIS2DW_FIFO_CTRL, 0xC0) + # High-Performance / Low-Power mode 1600/200 Hz + # High-Performance Mode (14-bit resolution) + self.set_reg(REG_LIS2DW_CTRL_REG1_ADDR, 0x94) + + # Setup samples + with self.lock: + self.raw_samples = [] + # Start bulk reading + systime = self.printer.get_reactor().monotonic() + print_time = self.mcu.estimated_print_time(systime) + MIN_MSG_TIME + reqclock = self.mcu.print_time_to_clock(print_time) + rest_ticks = self.mcu.seconds_to_clock(4. / self.data_rate) + self.query_rate = self.data_rate + self.query_lis2dw_cmd.send([self.oid, reqclock, rest_ticks], + reqclock=reqclock) + logging.info("LIS2DW starting '%s' measurements", self.name) + # Initialize clock tracking + self.last_sequence = 0 + self.last_limit_count = self.last_error_count = 0 + self.clock_sync.reset(reqclock, 0) + self.max_query_duration = 1 << 31 + self._update_clock(minclock=reqclock) + self.max_query_duration = 1 << 31 + def _finish_measurements(self): + if not self.is_measuring(): + return + # Halt bulk reading + params = self.query_lis2dw_end_cmd.send([self.oid, 0, 0]) + self.query_rate = 0 + with self.lock: + self.raw_samples = [] + logging.info("LIS2DW finished '%s' measurements", self.name) + self.set_reg(REG_LIS2DW_FIFO_CTRL, 0x00) + # API interface + def _api_update(self, eventtime): + self._update_clock() + with self.lock: + raw_samples = self.raw_samples + self.raw_samples = [] + if not raw_samples: + return {} + samples = self._extract_samples(raw_samples) + if not samples: + return {} + return {'data': samples, 'errors': self.last_error_count, + 'overflows': self.last_limit_count} + def _api_startstop(self, is_start): + if is_start: + self._start_measurements() + else: + self._finish_measurements() + def _handle_dump_lis2dw(self, web_request): + self.api_dump.add_client(web_request) + hdr = ('time', 'x_acceleration', 'y_acceleration', 'z_acceleration') + web_request.send({'header': hdr}) + def start_internal_client(self): + cconn = self.api_dump.add_internal_client() + return adxl345.AccelQueryHelper(self.printer, cconn) + + +def load_config(config): + return LIS2DW(config) + +def load_config_prefix(config): + return LIS2DW(config) diff --git a/src/Makefile b/src/Makefile index e89845549..0fcfacecd 100644 --- a/src/Makefile +++ b/src/Makefile @@ -15,6 +15,6 @@ src-$(CONFIG_WANT_DISPLAYS) += lcd_st7920.c lcd_hd44780.c src-$(CONFIG_WANT_SOFTWARE_SPI) += spi_software.c src-$(CONFIG_WANT_SOFTWARE_I2C) += i2c_software.c sensors-src-$(CONFIG_HAVE_GPIO_SPI) := thermocouple.c sensor_adxl345.c \ - sensor_angle.c + sensor_angle.c sensor_lis2dw.c sensors-src-$(CONFIG_HAVE_GPIO_I2C) += sensor_mpu9250.c src-$(CONFIG_WANT_SENSORS) += $(sensors-src-y) diff --git a/src/sensor_lis2dw.c b/src/sensor_lis2dw.c new file mode 100644 index 000000000..52612623f --- /dev/null +++ b/src/sensor_lis2dw.c @@ -0,0 +1,221 @@ +// Support for gathering acceleration data from LIS2DW chip +// +// Copyright (C) 2023 Zhou.XianMing +// Copyright (C) 2020 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include // memcpy +#include "board/irq.h" // irq_disable +#include "board/misc.h" // timer_read_time +#include "basecmd.h" // oid_alloc +#include "command.h" // DECL_COMMAND +#include "sched.h" // DECL_TASK +#include "spicmds.h" // spidev_transfer + +#define LIS_AR_DATAX0 0x28 +#define LIS_AM_READ 0x80 +#define LIS_FIFO_CTRL 0x2E +#define LIS_FIFO_SAMPLES 0x2F + +struct lis2dw { + struct timer timer; + uint32_t rest_ticks; + struct spidev_s *spi; + uint16_t sequence, limit_count; + uint8_t flags, data_count, fifo_disable; + uint8_t data[48]; +}; + +enum { + LIS_HAVE_START = 1<<0, LIS_RUNNING = 1<<1, LIS_PENDING = 1<<2, +}; + +static struct task_wake lis2dw_wake; + +// Event handler that wakes lis2dw_task() periodically +static uint_fast8_t +lis2dw_event(struct timer *timer) +{ + struct lis2dw *ax = container_of(timer, struct lis2dw, timer); + ax->flags |= LIS_PENDING; + sched_wake_task(&lis2dw_wake); + return SF_DONE; +} + +void +command_config_lis2dw(uint32_t *args) +{ + struct lis2dw *ax = oid_alloc(args[0], command_config_lis2dw + , sizeof(*ax)); + ax->timer.func = lis2dw_event; + ax->spi = spidev_oid_lookup(args[1]); +} +DECL_COMMAND(command_config_lis2dw, "config_lis2dw oid=%c spi_oid=%c"); + +// Report local measurement buffer +static void +lis2dw_report(struct lis2dw *ax, uint8_t oid) +{ + sendf("lis2dw_data oid=%c sequence=%hu data=%*s" + , oid, ax->sequence, ax->data_count, ax->data); + ax->data_count = 0; + ax->sequence++; +} + +// Report buffer and fifo status +static void +lis2dw_status(struct lis2dw *ax, uint_fast8_t oid + , uint32_t time1, uint32_t time2, uint_fast8_t fifo) +{ + sendf("lis2dw_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" + " buffered=%c fifo=%c limit_count=%hu" + , oid, time1, time2-time1, ax->sequence + , ax->data_count, fifo, ax->limit_count); +} + +// Helper code to reschedule the lis2dw_event() timer +static void +lis2dw_reschedule_timer(struct lis2dw *ax) +{ + irq_disable(); + ax->timer.waketime = timer_read_time() + ax->rest_ticks; + sched_add_timer(&ax->timer); + irq_enable(); +} + +// Query accelerometer data +static void +lis2dw_query(struct lis2dw *ax, uint8_t oid) +{ + uint8_t msg[7] = {0}; + uint8_t fifo[2] = {LIS_FIFO_SAMPLES| LIS_AM_READ , 0}; + uint8_t fifo_empty,fifo_ovrn = 0; + + msg[0] = LIS_AR_DATAX0 | LIS_AM_READ ; + uint8_t *d = &ax->data[ax->data_count]; + + spidev_transfer(ax->spi, 1, sizeof(msg), msg); + + spidev_transfer(ax->spi, 1, sizeof(fifo), fifo); + fifo_empty = fifo[1]&0x3F; + fifo_ovrn = fifo[1]&0x40; + + d[0] = msg[1]; // x low bits + d[1] = msg[2]; // x high bits + d[2] = msg[3]; // y low bits + d[3] = msg[4]; // y high bits + d[4] = msg[5]; // z low bits + d[5] = msg[6]; // z high bits + + ax->data_count += 6; + if (ax->data_count + 6 > ARRAY_SIZE(ax->data)) + lis2dw_report(ax, oid); + + // Check fifo status + if (fifo_ovrn) + ax->limit_count++; + + // check if we need to run the task again (more packets in fifo?) + if (!fifo_empty&&!(ax->fifo_disable)) { + // More data in fifo - wake this task again + sched_wake_task(&lis2dw_wake); + } else if (ax->flags & LIS_RUNNING) { + // Sleep until next check time + sched_del_timer(&ax->timer); + ax->flags &= ~LIS_PENDING; + lis2dw_reschedule_timer(ax); + } +} + +// Startup measurements +static void +lis2dw_start(struct lis2dw *ax, uint8_t oid) +{ + sched_del_timer(&ax->timer); + ax->flags = LIS_RUNNING; + ax->fifo_disable = 0; + uint8_t ctrl[2] = {LIS_FIFO_CTRL , 0xC0}; + spidev_transfer(ax->spi, 0, sizeof(ctrl), ctrl); + lis2dw_reschedule_timer(ax); +} + +// End measurements +static void +lis2dw_stop(struct lis2dw *ax, uint8_t oid) +{ + // Disable measurements + sched_del_timer(&ax->timer); + ax->flags = 0; + // Drain any measurements still in fifo + ax->fifo_disable = 1; + lis2dw_query(ax, oid); + + uint8_t ctrl[2] = {LIS_FIFO_CTRL , 0}; + uint32_t end1_time = timer_read_time(); + spidev_transfer(ax->spi, 0, sizeof(ctrl), ctrl); + uint32_t end2_time = timer_read_time(); + + uint8_t msg[2] = { LIS_FIFO_SAMPLES | LIS_AM_READ , 0}; + spidev_transfer(ax->spi, 1, sizeof(msg), msg); + uint8_t fifo_status = msg[1]&0x1f; + + //Report final data + if (ax->data_count) + lis2dw_report(ax, oid); + lis2dw_status(ax, oid, end1_time, end2_time, fifo_status); +} + +void +command_query_lis2dw(uint32_t *args) +{ + struct lis2dw *ax = oid_lookup(args[0], command_config_lis2dw); + + if (!args[2]) { + // End measurements + lis2dw_stop(ax, args[0]); + return; + } + // Start new measurements query + sched_del_timer(&ax->timer); + ax->timer.waketime = args[1]; + ax->rest_ticks = args[2]; + ax->flags = LIS_HAVE_START; + ax->sequence = ax->limit_count = 0; + ax->data_count = 0; + ax->fifo_disable = 0; + sched_add_timer(&ax->timer); +} +DECL_COMMAND(command_query_lis2dw, + "query_lis2dw oid=%c clock=%u rest_ticks=%u"); + +void +command_query_lis2dw_status(uint32_t *args) +{ + struct lis2dw *ax = oid_lookup(args[0], command_config_lis2dw); + uint8_t msg[2] = { LIS_FIFO_SAMPLES | LIS_AM_READ, 0x00 }; + uint32_t time1 = timer_read_time(); + spidev_transfer(ax->spi, 1, sizeof(msg), msg); + uint32_t time2 = timer_read_time(); + lis2dw_status(ax, args[0], time1, time2, msg[1]&0x1f); +} +DECL_COMMAND(command_query_lis2dw_status, "query_lis2dw_status oid=%c"); + +void +lis2dw_task(void) +{ + if (!sched_check_wake(&lis2dw_wake)) + return; + uint8_t oid; + struct lis2dw *ax; + foreach_oid(oid, ax, command_config_lis2dw) { + uint_fast8_t flags = ax->flags; + if (!(flags & LIS_PENDING)) + continue; + if (flags & LIS_HAVE_START) + lis2dw_start(ax, oid); + else + lis2dw_query(ax, oid); + } +} +DECL_TASK(lis2dw_task); From 073665cebb335f6efbcdc22debf0cc090e732a3d Mon Sep 17 00:00:00 2001 From: bigtreetech Date: Mon, 7 Aug 2023 15:58:48 +0800 Subject: [PATCH 13/48] docs: Add how to configure LIS2DW instructions Signed-off-by: Alan.Ma from BigTreeTech tech@biqu3d.com --- docs/Config_Reference.md | 21 +++++++++++++++++++++ docs/Measuring_Resonances.md | 28 ++++++++++++++++++++++++---- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index d0b617a36..5b8038f7d 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -1646,6 +1646,27 @@ cs_pin: # measurements. ``` +### [lis2dw] + +Support for LIS2DW accelerometers. + +``` +[lis2dw] +cs_pin: +# The SPI enable pin for the sensor. This parameter must be provided. +#spi_speed: 5000000 +# The SPI speed (in hz) to use when communicating with the chip. +# The default is 5000000. +#spi_bus: +#spi_software_sclk_pin: +#spi_software_mosi_pin: +#spi_software_miso_pin: +# See the "common SPI settings" section for a description of the +# above parameters. +#axes_map: x, y, z +# See the "adxl345" section for information on this parameter. +``` + ### [mpu9250] Support for MPU-9250, MPU-9255, MPU-6515, MPU-6050, and MPU-6500 diff --git a/docs/Measuring_Resonances.md b/docs/Measuring_Resonances.md index cf43e31d3..28cda9d0c 100644 --- a/docs/Measuring_Resonances.md +++ b/docs/Measuring_Resonances.md @@ -1,11 +1,11 @@ # Measuring Resonances -Klipper has built-in support for the ADXL345 and MPU-9250 compatible +Klipper has built-in support for the ADXL345, MPU-9250 and LIS2DW compatible accelerometers which can be used to measure resonance frequencies of the printer for different axes, and auto-tune [input shapers](Resonance_Compensation.md) to compensate for resonances. Note that using accelerometers requires some -soldering and crimping. The ADXL345 can be connected to the SPI interface of a -Raspberry Pi or MCU board (it needs to be reasonably fast). The MPU family can +soldering and crimping. The ADXL345/LIS2DW can be connected to the SPI interface +of a Raspberry Pi or MCU board (it needs to be reasonably fast). The MPU family can be connected to the I2C interface of a Raspberry Pi directly, or to an I2C interface of an MCU board that supports 400kbit/s *fast mode* in Klipper. @@ -13,7 +13,7 @@ When sourcing accelerometers, be aware that there are a variety of different PCB board designs and different clones of them. If it is going to be connected to a 5V printer MCU ensure it has a voltage regulator and level shifters. -For ADXL345s, make sure that the board supports SPI mode (a small number of +For ADXL345s/LIS2DWs, make sure that the board supports SPI mode (a small number of boards appear to be hard-configured for I2C by pulling SDO to GND). For MPU-9250/MPU-9255/MPU-6515/MPU-6050/MPU-6500s there are also a variety of @@ -305,6 +305,26 @@ you'll also want to modify your `printer.cfg` file to include this: Restart Klipper via the `RESTART` command. +#### Configure LIS2DW series + +``` +[mcu lis] +# Change to whatever you found above. For example, +# usb-Klipper_rp2040_E661640843545B2E-if00 +serial: /dev/serial/by-id/usb-Klipper_rp2040_ + +[lis2dw] +cs_pin: lis:gpio1 +spi_bus: spi0a +axes_map: x,z,y + +[resonance_tester] +accel_chip: lis2dw +probe_points: + # Somewhere slightly above the middle of your print bed + 147,154, 20 +``` + #### Configure MPU-6000/9000 series With RPi Make sure the Linux I2C driver is enabled and the baud rate is From 5f990f93d533247d3a675e8c423280f4333ad8ce Mon Sep 17 00:00:00 2001 From: bigtreetech Date: Thu, 10 Aug 2023 09:49:37 +0800 Subject: [PATCH 14/48] Kconfig: Support Enable/Disable lis2dw on chips with small flash size Signed-off-by: Alan.Ma from BigTreeTech tech@biqu3d.com --- src/Kconfig | 7 +++++++ src/Makefile | 3 ++- test/configs/ar100.config | 1 + test/configs/stm32f042.config | 1 + 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Kconfig b/src/Kconfig index 915af2956..aaf506539 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -100,6 +100,10 @@ config WANT_SENSORS bool depends on HAVE_GPIO_I2C || HAVE_GPIO_SPI default y +config WANT_LIS2DW + bool + depends on HAVE_GPIO_SPI + default y config WANT_SOFTWARE_I2C bool depends on HAVE_GPIO && HAVE_GPIO_I2C @@ -119,6 +123,9 @@ config WANT_DISPLAYS config WANT_SENSORS bool "Support external sensor devices" depends on HAVE_GPIO_I2C || HAVE_GPIO_SPI +config WANT_LIS2DW + bool "Support lis2dw 3-axis accelerometer" + depends on HAVE_GPIO_SPI config WANT_SOFTWARE_I2C bool "Support software based I2C \"bit-banging\"" depends on HAVE_GPIO && HAVE_GPIO_I2C diff --git a/src/Makefile b/src/Makefile index 0fcfacecd..8d771f9eb 100644 --- a/src/Makefile +++ b/src/Makefile @@ -15,6 +15,7 @@ src-$(CONFIG_WANT_DISPLAYS) += lcd_st7920.c lcd_hd44780.c src-$(CONFIG_WANT_SOFTWARE_SPI) += spi_software.c src-$(CONFIG_WANT_SOFTWARE_I2C) += i2c_software.c sensors-src-$(CONFIG_HAVE_GPIO_SPI) := thermocouple.c sensor_adxl345.c \ - sensor_angle.c sensor_lis2dw.c + sensor_angle.c +src-$(CONFIG_WANT_LIS2DW) += sensor_lis2dw.c sensors-src-$(CONFIG_HAVE_GPIO_I2C) += sensor_mpu9250.c src-$(CONFIG_WANT_SENSORS) += $(sensors-src-y) diff --git a/test/configs/ar100.config b/test/configs/ar100.config index a717fe3d0..6c9174824 100644 --- a/test/configs/ar100.config +++ b/test/configs/ar100.config @@ -3,3 +3,4 @@ CONFIG_MACH_AR100=y CONFIG_WANT_DISPLAYS=n CONFIG_WANT_SOFTWARE_I2C=n CONFIG_WANT_SOFTWARE_SPI=n +CONFIG_WANT_LIS2DW=n diff --git a/test/configs/stm32f042.config b/test/configs/stm32f042.config index 85338f2e7..7f1e879fb 100644 --- a/test/configs/stm32f042.config +++ b/test/configs/stm32f042.config @@ -2,3 +2,4 @@ CONFIG_MACH_STM32=y CONFIG_MACH_STM32F042=y CONFIG_WANT_SOFTWARE_I2C=n +CONFIG_WANT_LIS2DW=n From 5f58fe7484065edb18227a0987c13f0f8753679f Mon Sep 17 00:00:00 2001 From: JamesH1978 <87171443+JamesH1978@users.noreply.github.com> Date: Sun, 10 Sep 2023 19:52:33 +0200 Subject: [PATCH 15/48] config: Add HC32F460 variant of Ender 2 Pro (#6334) PR to add the newer version of the Ender 2 Pro with the CR-FDM-v2.5.54.170 motherboard with the HC3232F460 chip. Confirmed as working with discord member NyftHeart and using configs from Steve Gotthardt, with his permission to use and submit. Signed-off-by: James Hartley --- .../printer-creality-ender2pro-hc32-2022.cfg | 95 +++++++++++++++++++ test/configs/hc32f460-serial-PA7PA8.config | 3 + test/klippy/printers.test | 6 +- 3 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 config/printer-creality-ender2pro-hc32-2022.cfg create mode 100644 test/configs/hc32f460-serial-PA7PA8.config diff --git a/config/printer-creality-ender2pro-hc32-2022.cfg b/config/printer-creality-ender2pro-hc32-2022.cfg new file mode 100644 index 000000000..50f33aae8 --- /dev/null +++ b/config/printer-creality-ender2pro-hc32-2022.cfg @@ -0,0 +1,95 @@ +# This file contains pin mappings for the Creality Ender2 Pro +# with the HC32F460. The board is CR-FDM-v2.5.S4.170 +# To use this config, during "make menuconfig" select HC32F460 + +# Flash this firmware by copying "out/klipper.bin" to a SD card +# as /user/firmware.bin +# Turn on the printer with the card inserted. + +# See docs/Config_Reference.md for a description of parameters. + +[stepper_x] +step_pin: PC1 +dir_pin: PC0 +enable_pin: !PC2 +microsteps: 16 +rotation_distance: 40 +endstop_pin: ^PA5 +position_min: -20 +position_endstop: -20 +position_max: 165 +homing_speed: 50 + +[stepper_y] +step_pin: PB9 +dir_pin: PB8 +enable_pin: !PH2 +microsteps: 16 +rotation_distance: 40 +endstop_pin: ^PA6 +position_min: -5 +position_endstop: -5 +position_max: 165 +homing_speed: 50 + +[stepper_z] +step_pin: PB6 +dir_pin: !PB5 +enable_pin: !PB7 +microsteps: 16 +rotation_distance: 8 +endstop_pin: ^PB0 +position_endstop: 0.0 +position_max: 180 + +[extruder] +max_extrude_only_distance: 100.0 +step_pin: PB3 +dir_pin: PA15 +enable_pin: !PB4 +microsteps: 16 +rotation_distance: 27.53480577 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PA1 +sensor_pin: PC5 +sensor_type: Generic 3950 +control: pid +pid_Kp: 29.634 +pid_Ki: 2.102 +pid_Kd: 104.459 +min_temp: 0 +max_temp: 260 + +[heater_bed] +heater_pin: PB10 +sensor_type: Generic 3950 +sensor_pin: PC4 +control: pid +pid_Kp: 72.921 +pid_Ki: 1.594 +pid_Kd: 834.031 +min_temp: 0 +max_temp: 80 + +[fan] +pin: PA0 + +[mcu] +serial: /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0 +restart_method: command + +[printer] +kinematics: cartesian +max_velocity: 300 +max_accel: 3000 +max_z_velocity: 5 +max_z_accel: 100 + +[display] +lcd_type: st7920 +cs_pin: PB15 +sclk_pin: PB14 +sid_pin: PB12 +encoder_pins: ^PB13,^PA2 +click_pin: ^!PC7 diff --git a/test/configs/hc32f460-serial-PA7PA8.config b/test/configs/hc32f460-serial-PA7PA8.config new file mode 100644 index 000000000..7fdbc17ba --- /dev/null +++ b/test/configs/hc32f460-serial-PA7PA8.config @@ -0,0 +1,3 @@ +# Base config file for boards using HC32F460 +CONFIG_MACH_HC32F460=y +CONFIG_HC32F460_SERIAL_PA7_PA8=y diff --git a/test/klippy/printers.test b/test/klippy/printers.test index 5aa78ac1d..28c172ae4 100644 --- a/test/klippy/printers.test +++ b/test/klippy/printers.test @@ -257,11 +257,15 @@ CONFIG ../../config/generic-bigtreetech-skr-mini-e3-v3.0.cfg DICTIONARY rp2040.dict CONFIG ../../config/generic-bigtreetech-skr-pico-v1.0.cfg -# Anycubic Printers using trigorilla board with the hc32f460 +# Printers using the hc32f460 with serial on PA3 PA2 DICTIONARY hc32f460-serial-PA3PA2.dict CONFIG ../../config/printer-anycubic-kobra-go-2022.cfg CONFIG ../../config/printer-anycubic-kobra-plus-2022.cfg +# Printers using the hc32f460 with serial on PA7 PA8 +DICTIONARY hc32f460-serial-PA7PA8.dict +CONFIG ../../config/printer-creality-ender2pro-hc32-2022.cfg + # Printers using the PRU DICTIONARY pru.dict host=linuxprocess.dict CONFIG ../../config/generic-cramps.cfg From 8f178b892a4e81611e24d0a79df2f0601509aa02 Mon Sep 17 00:00:00 2001 From: Bomberski <51887282+Bomberski@users.noreply.github.com> Date: Sun, 10 Sep 2023 13:55:37 -0400 Subject: [PATCH 16/48] config: Add MKS Monster 8 generic config (#6336) Signed-off-by: Allen Cox --- config/generic-mks-monster8.cfg | 268 ++++++++++++++++++++++++++++++++ test/klippy/printers.test | 1 + 2 files changed, 269 insertions(+) create mode 100644 config/generic-mks-monster8.cfg diff --git a/config/generic-mks-monster8.cfg b/config/generic-mks-monster8.cfg new file mode 100644 index 000000000..583d49bff --- /dev/null +++ b/config/generic-mks-monster8.cfg @@ -0,0 +1,268 @@ +# This file contains common pin mappings for MKS Monster8 +# boards. To use this config, the firmware should be compiled for the +# stm32f407. When running "make menuconfig", select the 48KiB +# bootloader, and enable "USB for communication". + +# The "make flash" command does not work on the MKS Monster8. Instead, +# after running "make", copy the generated "out/klipper.bin" file to a +# file named "mks_monster8.bin" on an SD card or Udisk and then restart the +# MKS Monster8 with that SD card or Udisk. + +# See docs/Config_Reference.md for a description of parameters. + +[stepper_x] +step_pin: PC14 +dir_pin: PC13 +enable_pin: !PC15 +microsteps: 16 +rotation_distance: 40 +endstop_pin: !PA14 # PA13 for X-max; endstop have'!' is NO +position_endstop: 0 +position_max: 200 +homing_speed: 50 + +[stepper_y] +step_pin: PE5 +dir_pin: !PE4 +enable_pin: !PC15 +microsteps: 16 +rotation_distance: 40 +endstop_pin: !PA15 # PC5 for Y-max; endstop have'!' is NO +position_endstop: 0 +position_max: 200 +homing_speed: 50 + +[stepper_z] +step_pin: PE1 +dir_pin: PE0 +enable_pin: !PE2 +microsteps: 16 +rotation_distance: 8 +endstop_pin: !PB13 # PB12 for Z-max; endstop have'!' is NO +position_endstop: 0 +position_max: 220 + +[extruder] +step_pin: PB5 +dir_pin: !PB4 +enable_pin: !PB6 +microsteps: 16 +rotation_distance: 33.500 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PB1 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PC1 +control: pid +pid_Kp: 22 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 260 + +#[extruder1] +#step_pin: PD6 +#dir_pin: !PD5 +#enable_pin: !PD7 +#heater_pin: PB0 +#sensor_pin: PC2 + +#[extruder2] +#step_pin: PD2 +#dir_pin: !PD1 +#enable_pin: !PD3 +#heater_pin: PA3 +#sensor_pin: PC3 + +#[extruder3] +#step_pin: PC7 +#dir_pin: PC6 +#enable_pin: !PC8 + +#[extruder4] +#step_pin: PD13 +#dir_pin: !PD12 +#enable_pin: !PD14 + +[heater_bed] +heater_pin: PB10 +sensor_type: NTC 100K MGB18-104F39050L32 +sensor_pin: PC0 +max_power: 1.0 +control: pid +pid_kp: 71.039 +pid_ki: 2.223 +pid_kd: 567.421 +min_temp: 0 +max_temp: 200 + +#fan for printed model FAN0 +[fan] +pin: PA2 + +#fan for hotend FAN1 +#[heater_fan my_nozzle_fan] +[heater_fan fan1] +pin: PA1 +shutdown_speed: 1 + +#fan for control board FAN2 +#[heater_fan my_control_fan] +[heater_fan fan2] +pin: PA0 +shutdown_speed: 1 + +[mcu] +serial: /dev/serial/by-id/usb-Klipper_stm32f407xx_4D0045001850314335393520-if00 + +[printer] +kinematics: cartesian +max_velocity: 10000 +max_accel: 20000 +max_z_velocity: 100 +max_z_accel: 1000 + +##################################################################### +# LED Control +##################################################################### + +#[output_pin caselight ](Use PA9) +## Chamber Lighting - In 5V-RGB Position +#pin: PA9 +#pwm: true +#shutdown_value: 0 +#value:100 +#cycle_time: 0.01 + +######################################## +# TMC UART configuration +######################################## + +#[tmc2208 stepper_x] +#uart_pin: PE6 +#run_current: 0.8 +#hold_current: 0.5 +#stealthchop_threshold: 999999 + +#[tmc2208 stepper_y] +#uart_pin: PE3 +#run_current: 0.8 +#hold_current: 0.5 +#stealthchop_threshold: 999999 + +#[tmc2208 stepper_z] +#uart_pin: PB7 +#run_current: 0.8 +#hold_current: 0.5 +#stealthchop_threshold: 999999 + +#[tmc2208 extruder] +#uart_pin: PB3 +#run_current: 0.8 +#hold_current: 0.5 +#sense_resistor: 0.110 +#stealthchop_threshold: 999999 + +#[tmc2208 extruder1] +#uart_pin: PD4 +#run_current: 0.8 +#hold_current: 0.5 +#stealthchop_threshold: 999999 + +#[tmc2208 extruder2] +#uart_pin: PD0 +#run_current: 0.8 +#hold_current: 0.5 +#stealthchop_threshold: 999999 + +#[tmc2208 extruder3] +#uart_pin: PD15 +#run_current: 0.8 +#hold_current: 0.5 +#stealthchop_threshold: 999999 + +#[tmc2208 extruder4] +#uart_pin: PD11 +#run_current: 0.8 +#hold_current: 0.5 +#stealthchop_threshold: 999999 + +######################################## +# TMC SPI configuration +######################################## + +#[tmc2130 stepper_x] +#spi_bus: spi4 +#cs_pin: PE6 +#diag1_pin: PA14 +#run_current: 0.800 +#hold_current: 0.500 +#stealthchop_threshold: 999999 + +#[tmc2130 stepper_y] +#spi_bus: spi4 +#cs_pin: PE3 +#diag1_pin: PA15 +#run_current: 0.800 +#hold_current: 0.500 +#stealthchop_threshold: 999999 + +#[tmc2130 stepper_z] +#spi_bus: spi4 +#cs_pin: PB7 +#diag1_pin: PB13 +#run_current: 0.800 +#hold_current: 0.500 +#stealthchop_threshold: 999999 + +#[tmc2130 extruder] +#spi_bus: spi4 +#cs_pin: PB3 +#diag1_pin: PA13 +#run_current: 0.800 +#hold_current: 0.500 +#stealthchop_threshold: 999999 + +#[tmc2130 extruder1] +#spi_bus: spi4 +#cs_pin: PD4 +#diag1_pin: PC5 +#run_current: 0.800 +#hold_current: 0.500 +#stealthchop_threshold: 999999 + +#[tmc2130 extruder2] +#spi_bus: spi4 +#cs_pin: PD0 +#diag1_pin: PB12 +#run_current: 0.800 +#hold_current: 0.500 +#stealthchop_threshold: 999999 + +#[tmc2130 extruder3] +#spi_bus: spi4 +#cs_pin: PD15 +#run_current: 0.800 +#hold_current: 0.500 +#stealthchop_threshold: 999999 + +#[tmc2130 extruder4] +#spi_bus: spi4 +#cs_pin: PD11 +#run_current: 0.800 +#hold_current: 0.500 +#stealthchop_threshold: 999999 + +######################################## +# EXP1 / EXP2 (display) pins +######################################## + +[board_pins] +aliases: + # EXP1 header + EXP1_1=PB2, EXP1_3=PE11, EXP1_5=PD9, EXP1_7=PE15, EXP1_9=, + EXP1_2=PE10, EXP1_4=PD10, EXP1_6=PD8, EXP1_8=PE7, EXP1_10=<5V>, + # EXP2 header + EXP2_1=PA6, EXP2_3=PE9, EXP2_5=PE8, EXP2_7=PB11, EXP2_9=, + EXP2_2=PA5, EXP2_4=PA4, EXP2_6=PA7, EXP2_8=, EXP2_10=<3.3v> + # Pins EXP2_1, EXP2_6, EXP2_2 are also MISO, MOSI, SCK of bus "ssp1" diff --git a/test/klippy/printers.test b/test/klippy/printers.test index 28c172ae4..8038ad4c0 100644 --- a/test/klippy/printers.test +++ b/test/klippy/printers.test @@ -220,6 +220,7 @@ CONFIG ../../config/generic-bigtreetech-skr-2.cfg CONFIG ../../config/generic-flyboard.cfg CONFIG ../../config/generic-mellow-fly-cdy-v3.cfg CONFIG ../../config/generic-mellow-super-infinty-hv.cfg +CONFIG ../../config/generic-mks-monster8.cfg CONFIG ../../config/generic-mks-robin-nano-v3.cfg CONFIG ../../config/generic-prusa-buddy.cfg CONFIG ../../config/generic-th3d-ezboard-v2.0.cfg From 2acfa282942c13c725af6a2cb3ea314edf53406f Mon Sep 17 00:00:00 2001 From: FrY Sennberg Date: Sun, 10 Sep 2023 20:41:59 +0200 Subject: [PATCH 17/48] tmc2240: Adding UART interface support to tmc2240 (#6305) Signed-off-by: Christoph Frei --- docs/Config_Reference.md | 5 ++++- klippy/extras/tmc2240.py | 12 +++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 5b8038f7d..25e7cc46e 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -3442,7 +3442,7 @@ run_current: ### [tmc2240] -Configure a TMC2240 stepper motor driver via SPI bus. To use this +Configure a TMC2240 stepper motor driver via SPI bus or UART. To use this feature, define a config section with a "tmc2240" prefix followed by the name of the corresponding stepper config section (for example, "[tmc2240 stepper_x]"). @@ -3460,6 +3460,9 @@ cs_pin: #spi_software_miso_pin: # See the "common SPI settings" section for a description of the # above parameters. +#uart_pin: +# The pin connected to the TMC2240 DIAG1/SW line. If this parameter +# is provided UART communication is used rather then SPI. #chain_position: #chain_length: # These parameters configure an SPI daisy chain. The two parameters diff --git a/klippy/extras/tmc2240.py b/klippy/extras/tmc2240.py index 19e62ab76..aef2280f2 100644 --- a/klippy/extras/tmc2240.py +++ b/klippy/extras/tmc2240.py @@ -5,7 +5,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import math, logging -from . import bus, tmc, tmc2130 +from . import bus, tmc, tmc2130, tmc_uart TMC_FREQUENCY=12500000. @@ -343,8 +343,14 @@ class TMC2240: def __init__(self, config): # Setup mcu communication self.fields = tmc.FieldHelper(Fields, SignedFields, FieldFormatters) - self.mcu_tmc = tmc2130.MCU_TMC_SPI(config, Registers, self.fields, - TMC_FREQUENCY) + if config.get("uart_pin", None) is not None: + # use UART for communication + self.mcu_tmc = tmc_uart.MCU_TMC_uart(config, Registers, self.fields, + 3, TMC_FREQUENCY) + else: + # Use SPI bus for communication + self.mcu_tmc = tmc2130.MCU_TMC_SPI(config, Registers, self.fields, + TMC_FREQUENCY) # Allow virtual pins to be created tmc.TMCVirtualPinHelper(config, self.mcu_tmc) # Register commands From 8ef0f7d7e3d3b2ac7bc1e80ed3295ceca6bba4e7 Mon Sep 17 00:00:00 2001 From: DavidvtWout Date: Mon, 11 Sep 2023 00:51:14 +0200 Subject: [PATCH 18/48] spi_temperature: Limit maximum temperature in MAX31865.calc_adc() to melting point of platinum (#6320) Limit the maximum temperature in MAX31865.calc_adc() to the melting point of platinum. Above this temperature the Callendar-Van Dusem formula does not make sense. The default value for max_temp is 99999999.9 and at this temperature the result of this formula is negative. This sets max_sample_value to 0 which causes the mcu to shutdown. Set max adc value to (1<<15)-1 . This is needed because the max value of the adc register of the MAX31865 is 0b1111 1111 1111 1110 which represents 32767 and not 32768. Signed-off-by: David van 't Wout --- klippy/extras/spi_temperature.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/klippy/extras/spi_temperature.py b/klippy/extras/spi_temperature.py index 9970a28db..77033b1b1 100644 --- a/klippy/extras/spi_temperature.py +++ b/klippy/extras/spi_temperature.py @@ -320,9 +320,10 @@ def calc_temp(self, adc): def calc_adc(self, temp): # Calculate relative resistance via Callendar-Van Dusen formula: # resistance = rtd_nominal_r * (1 + CVD_A * temp + CVD_B * temp**2) + temp = min(temp, 1768.3) # Melting point of platinum R_div_nominal = 1. + CVD_A * temp + CVD_B * temp * temp adc = int(R_div_nominal / self.adc_to_resist_div_nominal + 0.5) - adc = max(0, min(MAX31865_ADC_MAX, adc)) + adc = max(0, min(MAX31865_ADC_MAX - 1, adc)) adc = adc << 1 # Add fault bit return adc def build_spi_init(self, config): From e9bf2d4c5c02e8fbf1130928bf08f8de37e6ee79 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 21 Sep 2023 14:42:44 -0400 Subject: [PATCH 19/48] mcu: Enhance RetryAsyncCommand to handle low-level retransmits The RetryAsyncCommand code needs to ensure that any response messages are not from a previous (unrelated) query. To do that it compares the '#sent_time' from potential responses to ensure they are not from a previous session. However, if there are any low-level serial retransmits then the low-level code sets the '#sent_time' to zero (to indicate that the query send time is not strictly known). That could result in a valid response not being accepted by RetryAsyncCommand. If a low-level connection is experiencing a small amount of periodic retransmits it could result in multiple high-level retry attempts failing to the point that there is a user-facing error. This could result in "Timeout on wait for 'tmcuart_response' response" errors. Fix by accepting responses even if there is a low-level retransmit once the code can confirm that there can be no previous query still in progress. Signed-off-by: Kevin O'Connor --- klippy/mcu.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/klippy/mcu.py b/klippy/mcu.py index 76c70506b..68179bdd4 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -25,14 +25,16 @@ def __init__(self, serial, name, oid=None): self.reactor = serial.get_reactor() self.completion = self.reactor.completion() self.min_query_time = self.reactor.monotonic() + self.need_response = True self.serial.register_response(self.handle_callback, name, oid) def handle_callback(self, params): - if params['#sent_time'] >= self.min_query_time: - self.min_query_time = self.reactor.NEVER + if self.need_response and params['#sent_time'] >= self.min_query_time: + self.need_response = False self.reactor.async_complete(self.completion, params) def get_response(self, cmds, cmd_queue, minclock=0, reqclock=0): cmd, = cmds self.serial.raw_send_wait_ack(cmd, minclock, reqclock, cmd_queue) + self.min_query_time = 0. first_query_time = query_time = self.reactor.monotonic() while 1: params = self.completion.wait(query_time + self.RETRY_TIME) From 21b784297916ac31b0e83161976effea048b53cb Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 26 Sep 2023 16:30:58 -0400 Subject: [PATCH 20/48] docs: Add Peopoly to Sponsors.md Signed-off-by: Kevin O'Connor --- docs/Sponsors.md | 5 +++-- docs/img/sponsors/peopoly-logo.png | Bin 0 -> 67472 bytes 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 docs/img/sponsors/peopoly-logo.png diff --git a/docs/Sponsors.md b/docs/Sponsors.md index 7376cb31c..a226bb57b 100644 --- a/docs/Sponsors.md +++ b/docs/Sponsors.md @@ -6,7 +6,7 @@ sponsors. ## BIGTREETECH -[](https://bigtree-tech.com/collections/all-products) +[](https://bigtree-tech.com/collections/all-products) BIGTREETECH is the official mainboard sponsor of Klipper. BIGTREETECH is committed to developing innovative and competitive products to @@ -16,7 +16,8 @@ serve the 3D printing community better. Follow them on ## Sponsors -[](https://obico.io/klipper.html?source=klipper_sponsor) +[](https://obico.io/klipper.html?source=klipper_sponsor) +[](https://peopoly.net) ## Klipper Developers diff --git a/docs/img/sponsors/peopoly-logo.png b/docs/img/sponsors/peopoly-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..bcf1bd9ce61301b7f244b57f2ea7c73089639b8c GIT binary patch literal 67472 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 LSgYtf>W}{g&O_S_ literal 0 HcmV?d00001 From a4cd0336bdd9df78c1b2a1296a481e84efc2b821 Mon Sep 17 00:00:00 2001 From: Dmitry Butyugin Date: Sat, 30 Sep 2023 02:46:42 +0900 Subject: [PATCH 21/48] idex_modes: Fixed the case when carriages home in the same direction (#6310) Previous version of the code assumed that dual carriages home away from each other, which is not true on some machines, which have the second dual carriage homing on the first carriage. The new code correctly identifies the relative order of the carriages now. This fixes discrepancies between the documentation and the actual implementation of the carriages kinematic ranges calculation. Notes about dual_carriage homing and proximity checks changes Fixed clearing of homing state after homing in certain modes In case of multi-MCU homing it is possible that the carriage position will end up outside of the allowed motion range due to latencies in data transmission between MCUs. Selecting certain modes after homing could result in home state clearing instead of blocking the motion of the active carriage. This commit fixes this undesired behavior. Signed-off-by: Dmitry Butyugin --- docs/Config_Changes.md | 9 ++++++ docs/Config_Reference.md | 8 +++++ klippy/kinematics/idex_modes.py | 53 ++++++++++++++++++++++++++++----- 3 files changed, 62 insertions(+), 8 deletions(-) diff --git a/docs/Config_Changes.md b/docs/Config_Changes.md index 063af8ac3..6d83238d0 100644 --- a/docs/Config_Changes.md +++ b/docs/Config_Changes.md @@ -8,6 +8,15 @@ All dates in this document are approximate. ## Changes +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 +of the carriages with each other. Additionally, the homing order of the primary +and the dual carriage is changed in some configurations (certain configurations +when both carriages home in the same direction, see +[[dual_carriage] configuration reference](./Config_Reference.md#dual_carriage) +for more details). + 20230810: The flash-sdcard.sh script now supports both variants of the Bigtreetech SKR-3, STM32H743 and STM32H723. For this, the original tag of btt-skr-3 now has changed to be either btt-skr-3-h743 or btt-skr-3-h723. diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 25e7cc46e..241391834 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -2065,6 +2065,14 @@ in this section (CARRIAGE=0 will return activation to the primary carriage). Dual carriage support is typically combined with extra extruders - the SET_DUAL_CARRIAGE command is often called at the same time as the ACTIVATE_EXTRUDER command. Be sure to park the carriages during deactivation. +Note that during G28 homing, typically the primary carriage is homed first +followed by the carriage defined in the `[dual_carriage]` config section. +However, the `[dual_carriage]` carriage will be homed first if both carriages +home in a positive direction and the [dual_carriage] carriage has a +`position_endstop` greater than the primary carriage, or if both carriages home +in a negative direction and the `[dual_carriage]` carriage has a +`position_endstop` less than the primary carriage. + Additionally, one could use "SET_DUAL_CARRIAGE CARRIAGE=1 MODE=COPY" or "SET_DUAL_CARRIAGE CARRIAGE=1 MODE=MIRROR" commands to activate either copying or mirroring mode of the dual carriage, in which case it will follow the diff --git a/klippy/kinematics/idex_modes.py b/klippy/kinematics/idex_modes.py index b54f96579..2ce91afe8 100644 --- a/klippy/kinematics/idex_modes.py +++ b/klippy/kinematics/idex_modes.py @@ -65,7 +65,13 @@ def toggle_active_dc_rail(self, index, override_rail=False): kin.update_limits(self.axis, target_dc.get_rail().get_range()) def home(self, homing_state): kin = self.printer.lookup_object('toolhead').get_kinematics() - for i, dc_rail in enumerate(self.dc): + enumerated_dcs = list(enumerate(self.dc)) + if (self.get_dc_order(0, 1) > 0) != \ + self.dc[0].get_rail().get_homing_info().positive_dir: + # The second carriage must home first, because the carriages home in + # the same direction and the first carriage homes on the second one + enumerated_dcs.reverse() + for i, dc_rail in enumerated_dcs: self.toggle_active_dc_rail(i, override_rail=True) kin.home_axis(homing_state, self.axis, dc_rail.get_rail()) # Restore the original rails ordering @@ -78,9 +84,15 @@ def get_kin_range(self, toolhead, mode): axes_pos = [dc.get_axis_position(pos) for dc in self.dc] dc0_rail = self.dc[0].get_rail() dc1_rail = self.dc[1].get_rail() - range_min = dc0_rail.position_min - range_max = dc0_rail.position_max + if mode != PRIMARY or self.dc[0].is_active(): + range_min = dc0_rail.position_min + range_max = dc0_rail.position_max + else: + range_min = dc1_rail.position_min + range_max = dc1_rail.position_max safe_dist = self.safe_dist + if not safe_dist: + return (range_min, range_max) if mode == COPY: range_min = max(range_min, @@ -88,7 +100,7 @@ def get_kin_range(self, toolhead, mode): range_max = min(range_max, axes_pos[0] - axes_pos[1] + dc1_rail.position_max) elif mode == MIRROR: - if dc0_rail.get_homing_info().positive_dir: + if self.get_dc_order(0, 1) > 0: range_min = max(range_min, 0.5 * (sum(axes_pos) + safe_dist)) range_max = min(range_max, @@ -102,14 +114,39 @@ def get_kin_range(self, toolhead, mode): # mode == PRIMARY active_idx = 1 if self.dc[1].is_active() else 0 inactive_idx = 1 - active_idx - if active_idx: - range_min = dc1_rail.position_min - range_max = dc1_rail.position_max - if self.dc[active_idx].get_rail().get_homing_info().positive_dir: + if self.get_dc_order(active_idx, inactive_idx) > 0: range_min = max(range_min, axes_pos[inactive_idx] + safe_dist) else: range_max = min(range_max, axes_pos[inactive_idx] - safe_dist) + if range_min > range_max: + # During multi-MCU homing it is possible that the carriage + # position will end up below position_min or above position_max + # if position_endstop is too close to the rail motion ends due + # to inherent latencies of the data transmission between MCUs. + # This can result in an invalid range_min > range_max range + # in certain modes, which may confuse the kinematics code. + # So, return an empty range instead, which will correctly + # block the carriage motion until a different mode is selected + # which actually permits carriage motion. + return (range_min, range_min) return (range_min, range_max) + def get_dc_order(self, first, second): + if first == second: + return 0 + # Check the relative order of the first and second carriages and + # return -1 if the first carriage position is always smaller + # than the second one and 1 otherwise + first_rail = self.dc[first].get_rail() + second_rail = self.dc[second].get_rail() + first_homing_info = first_rail.get_homing_info() + second_homing_info = second_rail.get_homing_info() + if first_homing_info.positive_dir != second_homing_info.positive_dir: + # Carriages home away from each other + return 1 if first_homing_info.positive_dir else -1 + # Carriages home in the same direction + if first_rail.position_endstop > second_rail.position_endstop: + return 1 + return -1 def activate_dc_mode(self, index, mode): toolhead = self.printer.lookup_object('toolhead') toolhead.flush_step_generation() From 7bd32994d4ee7eff613413d7a813bb3b17b8f6d3 Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Mon, 18 Sep 2023 22:23:35 +0900 Subject: [PATCH 22/48] docs: fix typo in RPi_microcontroller.md additionaly -> additionally Signed-off-by: Ikko Eltociear Ashimine --- docs/RPi_microcontroller.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/RPi_microcontroller.md b/docs/RPi_microcontroller.md index 4e3c057fe..96ac5626f 100644 --- a/docs/RPi_microcontroller.md +++ b/docs/RPi_microcontroller.md @@ -203,7 +203,7 @@ channels need to be enabled you can use `pwm-2chan`: # Enable pwmchip sysfs interface dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=4 ``` -This example additionaly enables PWM1 and routes it to gpio13. +This example additionally enables PWM1 and routes it to gpio13. The overlay does not expose the pwm line on sysfs on boot and needs to be exported by echo'ing the number of the pwm channel to From aa726cb7cbf6c58607885efe8f0cb07d009d5546 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 3 Oct 2023 23:24:18 -0400 Subject: [PATCH 23/48] lib: Update to latest can2040 code Add support for can2040_stop() Add data_state_go_error() helper Add new can2040_get_statistics() API function Call report_note_discarding() after setting MS_DISCARD state Convert report_is_rx_eof_pending() to report_is_not_in_tx() Signed-off-by: Kevin O'Connor --- lib/README | 2 +- lib/can2040/can2040.c | 97 ++++++++++++++++++++++++++++++++----------- lib/can2040/can2040.h | 10 ++++- 3 files changed, 82 insertions(+), 27 deletions(-) diff --git a/lib/README b/lib/README index b3bd95e56..e981df59f 100644 --- a/lib/README +++ b/lib/README @@ -167,7 +167,7 @@ used to upload firmware to devices flashed with the CanBoot bootloader. The can2040 directory contains code from: https://github.com/KevinOConnor/can2040 -revision d1190afcaa6245c20da28199d06e453d2e743099. +version v1.6.0 (af3d21e5d61b8408c63fbdfb0aceb21d69d91693) The Huada HC32F460 directory contains code from: https://www.hdsc.com.cn/Category83-1490 diff --git a/lib/can2040/can2040.c b/lib/can2040/can2040.c index 926893d94..c2bd0061a 100644 --- a/lib/can2040/can2040.c +++ b/lib/can2040/can2040.c @@ -1,6 +1,6 @@ // Software CANbus implementation for rp2040 // -// Copyright (C) 2022 Kevin O'Connor +// Copyright (C) 2022,2023 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -318,6 +318,14 @@ pio_irq_set(struct can2040 *cd, uint32_t sm_irqs) pio_hw->inte0 = sm_irqs | SI_RX_DATA; } +// Completely disable host irqs +static void +pio_irq_disable(struct can2040 *cd) +{ + pio_hw_t *pio_hw = cd->pio_hw; + pio_hw->inte0 = 0; +} + // Return current host irq mask static uint32_t pio_irq_get(struct can2040 *cd) @@ -662,6 +670,7 @@ tx_schedule_transmit(struct can2040 *cd) pio_signal_set_txpending(cd); } cd->tx_state = TS_QUEUED; + cd->stats.tx_attempt++; struct can2040_transmit *qt = &cd->tx_queue[tx_qpos(cd, tx_pull_pos)]; pio_tx_send(cd, qt->stuffed_data, qt->stuffed_words); return 0; @@ -721,6 +730,7 @@ report_callback_error(struct can2040 *cd, uint32_t error_code) static void report_callback_rx_msg(struct can2040 *cd) { + cd->stats.rx_total++; cd->rx_cb(cd, CAN2040_NOTIFY_RX, &cd->parse_msg); } @@ -729,6 +739,7 @@ static void report_callback_tx_msg(struct can2040 *cd) { writel(&cd->tx_pull_pos, cd->tx_pull_pos + 1); + cd->stats.tx_total++; cd->rx_cb(cd, CAN2040_NOTIFY_TX, &cd->parse_msg); } @@ -748,11 +759,11 @@ report_handle_eof(struct can2040 *cd) pio_match_clear(cd); } -// Check if in an rx message is being processed +// Check if message being processed is an rx message (not self feedback from tx) static int -report_is_rx_eof_pending(struct can2040 *cd) +report_is_not_in_tx(struct can2040 *cd) { - return cd->report_state == RS_NEED_RX_EOF; + return !(cd->report_state & RS_NEED_TX_ACK); } // Parser found a new message start @@ -817,7 +828,7 @@ report_note_eof_success(struct can2040 *cd) // Parser found unexpected data on input static void -report_note_parse_error(struct can2040 *cd) +report_note_discarding(struct can2040 *cd) { if (cd->report_state != RS_IDLE) { cd->report_state = RS_IDLE; @@ -880,7 +891,7 @@ report_line_txpending(struct can2040 *cd) return; } // Tx request from can2040_transmit(), report_note_eof_success(), - // or report_note_parse_error(). + // or report_note_discarding(). uint32_t check_txpending = tx_schedule_transmit(cd); pio_irq_set(cd, (pio_irqs & ~SI_TXPENDING) | check_txpending); } @@ -896,6 +907,13 @@ enum { MS_CRC, MS_ACK, MS_EOF0, MS_EOF1, MS_DISCARD }; +// Reset any bits in the incoming parsing state +static void +data_state_clear_bits(struct can2040 *cd) +{ + cd->raw_bit_count = cd->unstuf.stuffed_bits = cd->unstuf.count_stuff = 0; +} + // Transition to the next parsing state static void data_state_go_next(struct can2040 *cd, uint32_t state, uint32_t num_bits) @@ -908,23 +926,35 @@ data_state_go_next(struct can2040 *cd, uint32_t state, uint32_t num_bits) static void data_state_go_discard(struct can2040 *cd) { - report_note_parse_error(cd); - if (pio_rx_check_stall(cd)) { // CPU couldn't keep up for some read data - must reset pio state - cd->raw_bit_count = cd->unstuf.count_stuff = 0; + data_state_clear_bits(cd); pio_sm_setup(cd); report_callback_error(cd, 0); } data_state_go_next(cd, MS_DISCARD, 32); + + // Clear report state and update hw irqs after transition to MS_DISCARD + report_note_discarding(cd); +} + +// Note a data parse error and transition to discard state +static void +data_state_go_error(struct can2040 *cd) +{ + cd->stats.parse_error++; + data_state_go_discard(cd); } // Received six dominant bits on the line static void data_state_line_error(struct can2040 *cd) { - data_state_go_discard(cd); + if (cd->parse_state == MS_DISCARD) + data_state_go_discard(cd); + else + data_state_go_error(cd); } // Received six unexpected passive bits on the line @@ -933,7 +963,7 @@ data_state_line_passive(struct can2040 *cd) { if (cd->parse_state != MS_DISCARD && cd->parse_state != MS_START) { // Bitstuff error - data_state_go_discard(cd); + data_state_go_error(cd); return; } @@ -941,8 +971,7 @@ data_state_line_passive(struct can2040 *cd) uint32_t dom_bits = ~stuffed_bits; if (!dom_bits) { // Counter overflow in "sync" state machine - reset it - cd->unstuf.stuffed_bits = 0; - cd->raw_bit_count = cd->unstuf.count_stuff = 0; + data_state_clear_bits(cd); pio_sm_setup(cd); data_state_go_discard(cd); return; @@ -972,7 +1001,7 @@ data_state_go_crc(struct can2040 *cd) int ret = report_note_crc_start(cd); if (ret) { - data_state_go_discard(cd); + data_state_go_error(cd); return; } data_state_go_next(cd, MS_CRC, 16); @@ -1065,7 +1094,7 @@ static void data_state_update_crc(struct can2040 *cd, uint32_t data) { if (((cd->parse_crc << 1) | 1) != data) { - data_state_go_discard(cd); + data_state_go_error(cd); return; } @@ -1083,7 +1112,7 @@ data_state_update_ack(struct can2040 *cd, uint32_t data) // data_state_line_passive() unstuf_restore_state(&cd->unstuf, (cd->parse_crc_bits << 2) | data); - data_state_go_discard(cd); + data_state_go_error(cd); return; } report_note_ack_success(cd); @@ -1095,7 +1124,7 @@ static void data_state_update_eof0(struct can2040 *cd, uint32_t data) { if (data != 0x0f || pio_rx_check_stall(cd)) { - data_state_go_discard(cd); + data_state_go_error(cd); return; } unstuf_clear_state(&cd->unstuf); @@ -1106,14 +1135,17 @@ data_state_update_eof0(struct can2040 *cd, uint32_t data) static void data_state_update_eof1(struct can2040 *cd, uint32_t data) { - if (data >= 0x1c || (data >= 0x18 && report_is_rx_eof_pending(cd))) - // Message is considered fully transmitted + if (data == 0x1f) { + // Success report_note_eof_success(cd); - - if (data == 0x1f) data_state_go_next(cd, MS_START, 1); - else + } else if (data >= 0x1c || (data >= 0x18 && report_is_not_in_tx(cd))) { + // Message fully transmitted - followed by "overload frame" + report_note_eof_success(cd); data_state_go_discard(cd); + } else { + data_state_go_error(cd); + } } // Handle data received while in MS_DISCARD state @@ -1310,13 +1342,28 @@ can2040_start(struct can2040 *cd, uint32_t sys_clock, uint32_t bitrate { cd->gpio_rx = gpio_rx; cd->gpio_tx = gpio_tx; + data_state_clear_bits(cd); pio_setup(cd, sys_clock, bitrate); data_state_go_discard(cd); } -// API function to stop and uninitialize can2040 code +// API function to stop can2040 code void -can2040_shutdown(struct can2040 *cd) +can2040_stop(struct can2040 *cd) { - // XXX + pio_irq_disable(cd); + pio_sm_setup(cd); +} + +// API function to access can2040 statistics +void +can2040_get_statistics(struct can2040 *cd, struct can2040_stats *stats) +{ + for (;;) { + memcpy(stats, &cd->stats, sizeof(*stats)); + if (memcmp(stats, &cd->stats, sizeof(*stats)) == 0) + // Successfully copied data + return; + // Raced with irq handler update - retry copy + } } diff --git a/lib/can2040/can2040.h b/lib/can2040/can2040.h index fc0bdd627..7dbee1162 100644 --- a/lib/can2040/can2040.h +++ b/lib/can2040/can2040.h @@ -26,11 +26,18 @@ struct can2040; typedef void (*can2040_rx_cb)(struct can2040 *cd, uint32_t notify , struct can2040_msg *msg); +struct can2040_stats { + uint32_t rx_total, tx_total; + uint32_t tx_attempt; + uint32_t parse_error; +}; + void can2040_setup(struct can2040 *cd, uint32_t pio_num); void can2040_callback_config(struct can2040 *cd, can2040_rx_cb rx_cb); void can2040_start(struct can2040 *cd, uint32_t sys_clock, uint32_t bitrate , uint32_t gpio_rx, uint32_t gpio_tx); -void can2040_shutdown(struct can2040 *cd); +void can2040_stop(struct can2040 *cd); +void can2040_get_statistics(struct can2040 *cd, struct can2040_stats *stats); void can2040_pio_irq_handler(struct can2040 *cd); int can2040_check_transmit(struct can2040 *cd); int can2040_transmit(struct can2040 *cd, struct can2040_msg *msg); @@ -56,6 +63,7 @@ struct can2040 { void *pio_hw; uint32_t gpio_rx, gpio_tx; can2040_rx_cb rx_cb; + struct can2040_stats stats; // Bit unstuffing struct can2040_bitunstuffer unstuf; From 1e3ace2170b2b610c3e08d452aefe72f92bf898c Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 23 Sep 2023 12:26:21 -0400 Subject: [PATCH 24/48] stm32: Improve usbfs epr register handling Replace the set_stat_x_bits() functions with a single calc_epr_bits() function. This new function supports setting bits other than the stat field in the epr register. Signed-off-by: Kevin O'Connor --- src/stm32/usbfs.c | 81 ++++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/src/stm32/usbfs.c b/src/stm32/usbfs.c index fda2ce9d2..ffdfb07b0 100644 --- a/src/stm32/usbfs.c +++ b/src/stm32/usbfs.c @@ -1,6 +1,6 @@ // Hardware interface to "fullspeed USB controller" // -// Copyright (C) 2018-2021 Kevin O'Connor +// Copyright (C) 2018-2023 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -175,26 +175,19 @@ btable_write_packet(int ep, const uint8_t *src, int count) #define USB_EPR ((volatile uint32_t *)USB_BASE) +#define EPR_TBITS (USB_EP_DTOG_RX | USB_EPRX_STAT \ + | USB_EP_DTOG_TX | USB_EPTX_STAT) #define EPR_RWBITS (USB_EPADDR_FIELD | USB_EP_KIND | USB_EP_TYPE_MASK) #define EPR_RWCBITS (USB_EP_CTR_RX | USB_EP_CTR_TX) +// Calculate the memory update needed to set the epr register static uint32_t -set_stat_rx_bits(uint32_t epr, uint32_t bits) +calc_epr_bits(uint32_t epr, uint32_t mask, uint32_t value) { - return ((epr & (EPR_RWBITS | USB_EPRX_STAT)) ^ bits) | EPR_RWCBITS; -} - -static uint32_t -set_stat_tx_bits(uint32_t epr, uint32_t bits) -{ - return ((epr & (EPR_RWBITS | USB_EPTX_STAT)) ^ bits) | EPR_RWCBITS; -} - -static uint32_t -set_stat_rxtx_bits(uint32_t epr, uint32_t bits) -{ - uint32_t mask = EPR_RWBITS | USB_EPRX_STAT | USB_EPTX_STAT; - return ((epr & mask) ^ bits) | EPR_RWCBITS; + uint32_t tmask = mask & EPR_TBITS, tvalue = value & tmask; + uint32_t rwmask = mask & EPR_RWBITS, rwbits = value & rwmask; + uint32_t cbits = EPR_RWCBITS & ~mask; + return (((epr & (EPR_RWBITS | tmask)) ^ tvalue) & ~rwmask) | rwbits | cbits; } @@ -205,36 +198,37 @@ set_stat_rxtx_bits(uint32_t epr, uint32_t bits) int_fast8_t usb_read_bulk_out(void *data, uint_fast8_t max_len) { - uint32_t epr = USB_EPR[USB_CDC_EP_BULK_OUT]; + uint32_t ep = USB_CDC_EP_BULK_OUT, epr = USB_EPR[ep]; if ((epr & USB_EPRX_STAT) == USB_EP_RX_VALID) // No data ready return -1; - uint32_t count = btable_read_packet(USB_CDC_EP_BULK_OUT, data, max_len); - USB_EPR[USB_CDC_EP_BULK_OUT] = set_stat_rx_bits(epr, USB_EP_RX_VALID); + uint32_t count = btable_read_packet(ep, data, max_len); + USB_EPR[ep] = calc_epr_bits(epr, USB_EPRX_STAT, USB_EP_RX_VALID); return count; } int_fast8_t usb_send_bulk_in(void *data, uint_fast8_t len) { - uint32_t epr = USB_EPR[USB_CDC_EP_BULK_IN]; + uint32_t ep = USB_CDC_EP_BULK_IN, epr = USB_EPR[ep]; if ((epr & USB_EPTX_STAT) != USB_EP_TX_NAK) // No buffer space available return -1; - btable_write_packet(USB_CDC_EP_BULK_IN, data, len); - USB_EPR[USB_CDC_EP_BULK_IN] = set_stat_tx_bits(epr, USB_EP_TX_VALID); + btable_write_packet(ep, data, len); + USB_EPR[ep] = calc_epr_bits(epr, USB_EPTX_STAT, USB_EP_TX_VALID); return len; } int_fast8_t usb_read_ep0(void *data, uint_fast8_t max_len) { - uint32_t epr = USB_EPR[0]; + uint32_t ep = 0, epr = USB_EPR[ep]; if ((epr & USB_EPRX_STAT) != USB_EP_RX_NAK) // No data ready return -1; - uint32_t count = btable_read_packet(0, data, max_len); - USB_EPR[0] = set_stat_rxtx_bits(epr, USB_EP_RX_VALID | USB_EP_TX_NAK); + uint32_t count = btable_read_packet(ep, data, max_len); + USB_EPR[ep] = calc_epr_bits(epr, USB_EPRX_STAT | USB_EPTX_STAT + , USB_EP_RX_VALID | USB_EP_TX_NAK); return count; } @@ -247,23 +241,24 @@ usb_read_ep0_setup(void *data, uint_fast8_t max_len) int_fast8_t usb_send_ep0(const void *data, uint_fast8_t len) { - uint32_t epr = USB_EPR[0]; + uint32_t ep = 0, epr = USB_EPR[ep]; if ((epr & USB_EPRX_STAT) != USB_EP_RX_VALID) // Transfer interrupted return -2; if ((epr & USB_EPTX_STAT) != USB_EP_TX_NAK) // No buffer space available return -1; - btable_write_packet(0, data, len); - USB_EPR[0] = set_stat_tx_bits(epr, USB_EP_TX_VALID); + btable_write_packet(ep, data, len); + USB_EPR[ep] = calc_epr_bits(epr, USB_EPTX_STAT, USB_EP_TX_VALID); return len; } void usb_stall_ep0(void) { - USB_EPR[0] = set_stat_rxtx_bits(USB_EPR[0] - , USB_EP_RX_STALL | USB_EP_TX_STALL); + uint32_t ep = 0, epr = USB_EPR[ep]; + USB_EPR[ep] = calc_epr_bits(epr, USB_EPRX_STAT | USB_EPTX_STAT + , USB_EP_RX_STALL | USB_EP_TX_STALL); } static uint8_t set_address; @@ -289,13 +284,20 @@ usb_set_configure(void) static void usb_reset(void) { - USB_EPR[0] = 0 | USB_EP_CONTROL | USB_EP_RX_VALID | USB_EP_TX_NAK; - USB_EPR[USB_CDC_EP_ACM] = (USB_CDC_EP_ACM | USB_EP_INTERRUPT - | USB_EP_RX_NAK | USB_EP_TX_NAK); - USB_EPR[USB_CDC_EP_BULK_OUT] = (USB_CDC_EP_BULK_OUT | USB_EP_BULK - | USB_EP_RX_VALID | USB_EP_TX_NAK); - USB_EPR[USB_CDC_EP_BULK_IN] = (USB_CDC_EP_BULK_IN | USB_EP_BULK - | USB_EP_RX_NAK | USB_EP_TX_NAK); + uint32_t ep = 0; + USB_EPR[ep] = 0 | USB_EP_CONTROL | USB_EP_RX_VALID | USB_EP_TX_NAK; + + ep = USB_CDC_EP_ACM; + USB_EPR[ep] = (USB_CDC_EP_ACM | USB_EP_INTERRUPT + | USB_EP_RX_NAK | USB_EP_TX_NAK); + + ep = USB_CDC_EP_BULK_OUT; + USB_EPR[ep] = (USB_CDC_EP_BULK_OUT | USB_EP_BULK + | USB_EP_RX_VALID | USB_EP_TX_NAK); + + ep = USB_CDC_EP_BULK_IN; + USB_EPR[ep] = (USB_CDC_EP_BULK_IN | USB_EP_BULK + | USB_EP_RX_NAK | USB_EP_TX_NAK); USB->CNTR = USB_CNTR_CTRM | USB_CNTR_RESETM; USB->DADDR = USB_DADDR_EF; @@ -308,9 +310,8 @@ USB_IRQHandler(void) uint32_t istr = USB->ISTR; if (istr & USB_ISTR_CTR) { // Endpoint activity - uint32_t ep = istr & USB_ISTR_EP_ID; - uint32_t epr = USB_EPR[ep]; - USB_EPR[ep] = epr & EPR_RWBITS; + uint32_t ep = istr & USB_ISTR_EP_ID, epr = USB_EPR[ep]; + USB_EPR[ep] = calc_epr_bits(epr, USB_EP_CTR_RX | USB_EP_CTR_TX, 0); if (ep == 0) { usb_notify_ep0(); if (epr & USB_EP_CTR_TX && set_address) { From 01ac5334e90d5ba57636e1c56b8e2fee901d4585 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 21 Sep 2023 15:33:37 -0400 Subject: [PATCH 25/48] stm32: Update usbfs to support setting both buffers for each endpoint The usbfs device supports two buffers for each endpoint - typically one for rx and one for tx. Add support for explicit handling of both buffers. This is in preparation for improved "double buffering" support. Signed-off-by: Kevin O'Connor --- src/stm32/usbfs.c | 72 ++++++++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/src/stm32/usbfs.c b/src/stm32/usbfs.c index ffdfb07b0..3c96fd4dd 100644 --- a/src/stm32/usbfs.c +++ b/src/stm32/usbfs.c @@ -55,52 +55,50 @@ // Layout of the USB transfer memory #define EPM ((epmword_t*)USB_PMAADDR) -#define EPM_EP_DESC(ep) (&EPM[(ep) * (8 / WSIZE)]) +#define EPM_EP_DESC(ep, bufnum) (&EPM[((ep)*2 + (bufnum)) * (4 / WSIZE)]) #define EPM_BUF_OFFSET 0x10 #define EPM_EP_BUF_SIZE (64 / WSIZE + 1) -#define EPM_EP_TX_BUF(ep) (&EPM[EPM_BUF_OFFSET + (ep)*2*EPM_EP_BUF_SIZE]) -#define EPM_EP_RX_BUF(ep) (&EPM[EPM_BUF_OFFSET + (1+(ep)*2)*EPM_EP_BUF_SIZE]) +#define EPM_EP_BUF(ep, bufnum) \ + (&EPM[EPM_BUF_OFFSET + ((ep)*2 + (bufnum)) * EPM_EP_BUF_SIZE]) +#define BUFTX 0 +#define BUFRX 1 // Configure the usb descriptor for an endpoint static void -epm_ep_desc_setup(int ep, int rx_size) +epm_ep_desc_setup(int ep, int bufnum, int rx_size) { - uint32_t addr_tx = (EPM_EP_TX_BUF(ep) - EPM) * WSIZE, count_tx = 0; - uint32_t addr_rx = (EPM_EP_RX_BUF(ep) - EPM) * WSIZE; + uint32_t addr = (EPM_EP_BUF(ep, bufnum) - EPM) * WSIZE; uint32_t count_rx = (rx_size <= 30 ? DIV_ROUND_UP(rx_size, 2) << 10 : ((DIV_ROUND_UP(rx_size, 32) - 1) << 10) | 0x8000); - epmword_t *desc = EPM_EP_DESC(ep); + epmword_t *desc = EPM_EP_DESC(ep, bufnum); if (WSIZE == 2) { - desc[0] = addr_tx; - desc[1] = count_tx; - desc[2] = addr_rx; - desc[3] = count_rx; + desc[0] = addr; + desc[1] = count_rx; } else { - desc[0] = addr_tx | (count_tx << 16); - desc[1] = addr_rx | (count_rx << 16); + *desc = addr | (count_rx << 16); } } // Return number of read bytes on an rx endpoint static uint32_t -epm_get_ep_count_rx(int ep) +epm_get_ep_count_rx(int ep, int bufnum) { - epmword_t *desc = EPM_EP_DESC(ep); + epmword_t *desc = EPM_EP_DESC(ep, bufnum); if (WSIZE == 2) - return desc[3] & 0x3ff; - return (desc[1] >> 16) & 0x3ff; + return desc[1] & 0x3ff; + return (*desc >> 16) & 0x3ff; } // Set number of bytes ready to be transmitted on a tx endpoint static void -epm_set_ep_count_tx(int ep, uint32_t count) +epm_set_ep_count_tx(int ep, int bufnum, uint32_t count) { - epmword_t *desc = EPM_EP_DESC(ep); + epmword_t *desc = EPM_EP_DESC(ep, bufnum); if (WSIZE == 2) { desc[1] = count; } else { - uint32_t addr_tx = (EPM_EP_TX_BUF(ep) - EPM) * WSIZE; - desc[0] = addr_tx | (count << 16); + uint32_t addr_tx = (EPM_EP_BUF(ep, bufnum) - EPM) * WSIZE; + *desc = addr_tx | (count << 16); } } @@ -108,18 +106,22 @@ epm_set_ep_count_tx(int ep, uint32_t count) static void btable_configure(void) { - epm_ep_desc_setup(0, USB_CDC_EP0_SIZE); - epm_ep_desc_setup(USB_CDC_EP_ACM, 0); - epm_ep_desc_setup(USB_CDC_EP_BULK_OUT, USB_CDC_EP_BULK_OUT_SIZE); - epm_ep_desc_setup(USB_CDC_EP_BULK_IN, 0); + epm_ep_desc_setup(0, BUFTX, 0); + epm_ep_desc_setup(0, BUFRX, USB_CDC_EP0_SIZE); + epm_ep_desc_setup(USB_CDC_EP_ACM, BUFTX, 0); + epm_ep_desc_setup(USB_CDC_EP_ACM, BUFRX, 0); + epm_ep_desc_setup(USB_CDC_EP_BULK_OUT, BUFTX, 0); + epm_ep_desc_setup(USB_CDC_EP_BULK_OUT, BUFRX, USB_CDC_EP_BULK_OUT_SIZE); + epm_ep_desc_setup(USB_CDC_EP_BULK_IN, BUFTX, 0); + epm_ep_desc_setup(USB_CDC_EP_BULK_IN, BUFRX, 0); } // Read a packet stored in dedicated usb memory static uint32_t -btable_read_packet(int ep, uint8_t *dest, int max_len) +btable_read_packet(int ep, int bufnum, uint8_t *dest, int max_len) { - epmword_t *src = EPM_EP_RX_BUF(ep); - uint32_t count = epm_get_ep_count_rx(ep); + epmword_t *src = EPM_EP_BUF(ep, bufnum); + uint32_t count = epm_get_ep_count_rx(ep, bufnum); if (count > max_len) count = max_len; int i; @@ -145,9 +147,9 @@ btable_read_packet(int ep, uint8_t *dest, int max_len) // Write a packet to dedicated usb memory static void -btable_write_packet(int ep, const uint8_t *src, int count) +btable_write_packet(int ep, int bufnum, const uint8_t *src, int count) { - epmword_t *dest = EPM_EP_TX_BUF(ep); + epmword_t *dest = EPM_EP_BUF(ep, bufnum); int i; for (i=0; i Date: Sat, 23 Sep 2023 11:45:39 -0400 Subject: [PATCH 26/48] stm32: Add usbfs double buffer support for bulk rx messages Implement the usbfs fast buffer switching mechanism on the "bulk out" endpoint. This can improve the overall USB throughput and bus utilization. Signed-off-by: Kevin O'Connor --- src/stm32/usbfs.c | 63 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/src/stm32/usbfs.c b/src/stm32/usbfs.c index 3c96fd4dd..661aa1b15 100644 --- a/src/stm32/usbfs.c +++ b/src/stm32/usbfs.c @@ -48,6 +48,12 @@ #define USB_CNTR_FRES USB_CNTR_USBRST #endif +// Some chip variants do not define these fields +#ifndef USB_EP_DTOG_TX_Pos +#define USB_EP_DTOG_TX_Pos 6 +#define USB_EP_DTOG_RX_Pos 14 +#endif + /**************************************************************** * USB transfer memory @@ -110,8 +116,8 @@ btable_configure(void) epm_ep_desc_setup(0, BUFRX, USB_CDC_EP0_SIZE); epm_ep_desc_setup(USB_CDC_EP_ACM, BUFTX, 0); epm_ep_desc_setup(USB_CDC_EP_ACM, BUFRX, 0); - epm_ep_desc_setup(USB_CDC_EP_BULK_OUT, BUFTX, 0); - epm_ep_desc_setup(USB_CDC_EP_BULK_OUT, BUFRX, USB_CDC_EP_BULK_OUT_SIZE); + epm_ep_desc_setup(USB_CDC_EP_BULK_OUT, 0, USB_CDC_EP_BULK_OUT_SIZE); + epm_ep_desc_setup(USB_CDC_EP_BULK_OUT, 1, USB_CDC_EP_BULK_OUT_SIZE); epm_ep_desc_setup(USB_CDC_EP_BULK_IN, BUFTX, 0); epm_ep_desc_setup(USB_CDC_EP_BULK_IN, BUFRX, 0); } @@ -192,20 +198,41 @@ calc_epr_bits(uint32_t epr, uint32_t mask, uint32_t value) return (((epr & (EPR_RWBITS | tmask)) ^ tvalue) & ~rwmask) | rwbits | cbits; } +// Check if double buffering endpoint hardware can no longer send/receive +static int +epr_is_dbuf_blocking(uint32_t epr) +{ + return !(((epr >> (USB_EP_DTOG_RX_Pos - USB_EP_DTOG_TX_Pos)) ^ epr) + & USB_EP_DTOG_TX); +} + /**************************************************************** * USB interface ****************************************************************/ +static uint32_t bulk_out_pop_count, bulk_out_push_flag; + int_fast8_t usb_read_bulk_out(void *data, uint_fast8_t max_len) { - uint32_t ep = USB_CDC_EP_BULK_OUT, epr = USB_EPR[ep]; - if ((epr & USB_EPRX_STAT) == USB_EP_RX_VALID) + if (readl(&bulk_out_push_flag)) // No data ready return -1; - uint32_t count = btable_read_packet(ep, BUFRX, data, max_len); - USB_EPR[ep] = calc_epr_bits(epr, USB_EPRX_STAT, USB_EP_RX_VALID); + uint32_t ep = USB_CDC_EP_BULK_OUT; + int bufnum = bulk_out_pop_count & 1; + bulk_out_pop_count++; + uint32_t count = btable_read_packet(ep, bufnum, data, max_len); + writel(&bulk_out_push_flag, USB_EP_DTOG_TX); + + // Check if irq handler pulled another packet before push flag update + uint32_t epr = USB_EPR[ep]; + if (epr_is_dbuf_blocking(epr) && readl(&bulk_out_push_flag)) { + // Second packet was already read - must notify hardware + writel(&bulk_out_push_flag, 0); + USB_EPR[ep] = calc_epr_bits(epr, 0, 0) | USB_EP_DTOG_TX; + } + return count; } @@ -275,6 +302,9 @@ usb_set_address(uint_fast8_t addr) void usb_set_configure(void) { + uint32_t ep = USB_CDC_EP_BULK_OUT; + bulk_out_pop_count = 0; + USB_EPR[ep] = calc_epr_bits(USB_EPR[ep], USB_EPRX_STAT, USB_EP_RX_VALID); } @@ -294,8 +324,9 @@ usb_reset(void) | USB_EP_RX_NAK | USB_EP_TX_NAK); ep = USB_CDC_EP_BULK_OUT; - USB_EPR[ep] = (USB_CDC_EP_BULK_OUT | USB_EP_BULK - | USB_EP_RX_VALID | USB_EP_TX_NAK); + USB_EPR[ep] = (USB_CDC_EP_BULK_OUT | USB_EP_BULK | USB_EP_KIND + | USB_EP_RX_VALID | USB_EP_TX_NAK | USB_EP_DTOG_TX); + bulk_out_push_flag = USB_EP_DTOG_TX; ep = USB_CDC_EP_BULK_IN; USB_EPR[ep] = (USB_CDC_EP_BULK_IN | USB_EP_BULK @@ -313,18 +344,22 @@ USB_IRQHandler(void) if (istr & USB_ISTR_CTR) { // Endpoint activity uint32_t ep = istr & USB_ISTR_EP_ID, epr = USB_EPR[ep]; - USB_EPR[ep] = calc_epr_bits(epr, USB_EP_CTR_RX | USB_EP_CTR_TX, 0); - if (ep == 0) { + if (ep == USB_CDC_EP_BULK_OUT) { + USB_EPR[ep] = (calc_epr_bits(epr, USB_EP_CTR_RX | USB_EP_CTR_TX, 0) + | bulk_out_push_flag); + bulk_out_push_flag = 0; + usb_notify_bulk_out(); + } else if (ep == USB_CDC_EP_BULK_IN) { + USB_EPR[ep] = calc_epr_bits(epr, USB_EP_CTR_RX | USB_EP_CTR_TX, 0); + usb_notify_bulk_in(); + } else if (ep == 0) { + USB_EPR[ep] = calc_epr_bits(epr, USB_EP_CTR_RX | USB_EP_CTR_TX, 0); usb_notify_ep0(); if (epr & USB_EP_CTR_TX && set_address) { // Apply address after last "in" message transmitted USB->DADDR = set_address; set_address = 0; } - } else if (ep == USB_CDC_EP_BULK_OUT) { - usb_notify_bulk_out(); - } else if (ep == USB_CDC_EP_BULK_IN) { - usb_notify_bulk_in(); } } if (istr & USB_ISTR_RESET) { From cd8d57c2c68049e8fbbeedd88eba01fe6495c7c5 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 23 Sep 2023 15:31:52 -0400 Subject: [PATCH 27/48] stm32: Add usbfs double buffer support for bulk tx messages Implement the usbfs fast buffer switching mechanism on the "bulk in" endpoint. This can improve the overall USB throughput and bus utilization. Signed-off-by: Kevin O'Connor --- src/stm32/usbfs.c | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/stm32/usbfs.c b/src/stm32/usbfs.c index 661aa1b15..d166fbb7a 100644 --- a/src/stm32/usbfs.c +++ b/src/stm32/usbfs.c @@ -118,8 +118,8 @@ btable_configure(void) epm_ep_desc_setup(USB_CDC_EP_ACM, BUFRX, 0); epm_ep_desc_setup(USB_CDC_EP_BULK_OUT, 0, USB_CDC_EP_BULK_OUT_SIZE); epm_ep_desc_setup(USB_CDC_EP_BULK_OUT, 1, USB_CDC_EP_BULK_OUT_SIZE); - epm_ep_desc_setup(USB_CDC_EP_BULK_IN, BUFTX, 0); - epm_ep_desc_setup(USB_CDC_EP_BULK_IN, BUFRX, 0); + epm_ep_desc_setup(USB_CDC_EP_BULK_IN, 0, 0); + epm_ep_desc_setup(USB_CDC_EP_BULK_IN, 1, 0); } // Read a packet stored in dedicated usb memory @@ -236,15 +236,27 @@ usb_read_bulk_out(void *data, uint_fast8_t max_len) return count; } +static uint32_t bulk_in_push_count, bulk_in_pop_flag; + int_fast8_t usb_send_bulk_in(void *data, uint_fast8_t len) { - uint32_t ep = USB_CDC_EP_BULK_IN, epr = USB_EPR[ep]; - if ((epr & USB_EPTX_STAT) != USB_EP_TX_NAK) + if (readl(&bulk_in_pop_flag)) // No buffer space available return -1; - btable_write_packet(ep, BUFTX, data, len); - USB_EPR[ep] = calc_epr_bits(epr, USB_EPTX_STAT, USB_EP_TX_VALID); + uint32_t ep = USB_CDC_EP_BULK_IN; + int bufnum = bulk_in_push_count & 1; + bulk_in_push_count++; + btable_write_packet(ep, bufnum, data, len); + writel(&bulk_in_pop_flag, USB_EP_DTOG_RX); + + // Check if hardware needs to be notified + uint32_t epr = USB_EPR[ep]; + if (epr_is_dbuf_blocking(epr) && readl(&bulk_in_pop_flag)) { + writel(&bulk_in_pop_flag, 0); + USB_EPR[ep] = calc_epr_bits(epr, 0, 0) | USB_EP_DTOG_RX; + } + return len; } @@ -305,6 +317,11 @@ usb_set_configure(void) uint32_t ep = USB_CDC_EP_BULK_OUT; bulk_out_pop_count = 0; USB_EPR[ep] = calc_epr_bits(USB_EPR[ep], USB_EPRX_STAT, USB_EP_RX_VALID); + + ep = USB_CDC_EP_BULK_IN; + bulk_in_push_count = 0; + writel(&bulk_in_pop_flag, 0); + USB_EPR[ep] = calc_epr_bits(USB_EPR[ep], USB_EPTX_STAT, USB_EP_TX_VALID); } @@ -329,8 +346,9 @@ usb_reset(void) bulk_out_push_flag = USB_EP_DTOG_TX; ep = USB_CDC_EP_BULK_IN; - USB_EPR[ep] = (USB_CDC_EP_BULK_IN | USB_EP_BULK + USB_EPR[ep] = (USB_CDC_EP_BULK_IN | USB_EP_BULK | USB_EP_KIND | USB_EP_RX_NAK | USB_EP_TX_NAK); + bulk_in_pop_flag = USB_EP_DTOG_RX; USB->CNTR = USB_CNTR_CTRM | USB_CNTR_RESETM; USB->DADDR = USB_DADDR_EF; @@ -350,7 +368,9 @@ USB_IRQHandler(void) bulk_out_push_flag = 0; usb_notify_bulk_out(); } else if (ep == USB_CDC_EP_BULK_IN) { - USB_EPR[ep] = calc_epr_bits(epr, USB_EP_CTR_RX | USB_EP_CTR_TX, 0); + USB_EPR[ep] = (calc_epr_bits(epr, USB_EP_CTR_RX | USB_EP_CTR_TX, 0) + | bulk_in_pop_flag); + bulk_in_pop_flag = 0; usb_notify_bulk_in(); } else if (ep == 0) { USB_EPR[ep] = calc_epr_bits(epr, USB_EP_CTR_RX | USB_EP_CTR_TX, 0); From 83eecae0281634279a7b891561af353105f2ad2c Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 24 Sep 2023 01:22:31 -0400 Subject: [PATCH 28/48] rp2040: Add helper functions to usbserial.c Add helper functions for manipulating the buffer memory and packet control registers. This is in preparation for double buffer support. Signed-off-by: Kevin O'Connor --- src/rp2040/usbserial.c | 76 +++++++++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 20 deletions(-) diff --git a/src/rp2040/usbserial.c b/src/rp2040/usbserial.c index e63e590dc..f2db3b5e7 100644 --- a/src/rp2040/usbserial.c +++ b/src/rp2040/usbserial.c @@ -26,29 +26,67 @@ #define DPBUF_SIZE 64 +// Get the offset of a given endpoint's base buffer static uint32_t usb_buf_offset(uint32_t ep) { return 0x100 + ep * DPBUF_SIZE * 2; } +// Obtain a pointer to an endpoint buffer +static void* +usb_buf_addr(uint32_t ep, int bufnum) +{ + return (void*)usb_dpram + usb_buf_offset(ep) + bufnum * DPBUF_SIZE; +} + +// Return a pointer to the ep_buf_ctrl register for an endpoint +static volatile uint16_t * +lookup_epbufctrl(uint32_t ep, int is_rx, int bufnum) +{ + volatile uint16_t *epbp; + if (is_rx) + epbp = (void*)&usb_dpram->ep_buf_ctrl[ep].out; + else + epbp = (void*)&usb_dpram->ep_buf_ctrl[ep].in; + return &epbp[bufnum]; +} + +// Determine the next transfer PID id from the last PID +static uint32_t +next_data_pid(uint32_t epb) +{ + return (epb ^ USB_BUF_CTRL_DATA1_PID) & USB_BUF_CTRL_DATA1_PID; +} + +// Extract the number of bytes in an rx buffer +static uint32_t +get_rx_count(uint32_t epb, uint32_t max_len) +{ + uint32_t c = epb & USB_BUF_CTRL_LEN_MASK; + if (c > max_len) + c = max_len; + return c; +} + static int_fast8_t usb_write_packet(uint32_t ep, const void *data, uint_fast8_t len) { // Check if there is room for this packet - uint32_t epb = usb_dpram->ep_buf_ctrl[ep].in; + volatile uint16_t *epbp = lookup_epbufctrl(ep, 0, 0); + uint32_t epb = *epbp; if (epb & (USB_BUF_CTRL_AVAIL|USB_BUF_CTRL_FULL)) return -1; - uint32_t pid = (epb ^ USB_BUF_CTRL_DATA1_PID) & USB_BUF_CTRL_DATA1_PID; + // Determine the next packet header + uint32_t pid = next_data_pid(epb); uint32_t new_epb = USB_BUF_CTRL_FULL | USB_BUF_CTRL_LAST | pid | len; - usb_dpram->ep_buf_ctrl[ep].in = new_epb; - // Copy the packet to the hw buffer - void *addr = (void*)usb_dpram + usb_buf_offset(ep); - barrier(); - memcpy(addr, data, len); + *epbp = new_epb; barrier(); + // Copy the packet to the hw buffer + memcpy(usb_buf_addr(ep, 0), data, len); // Inform the USB hardware of the available packet - usb_dpram->ep_buf_ctrl[ep].in = new_epb | USB_BUF_CTRL_AVAIL; + barrier(); + *epbp = new_epb | USB_BUF_CTRL_AVAIL; return len; } @@ -56,22 +94,20 @@ static int_fast8_t usb_read_packet(uint32_t ep, void *data, uint_fast8_t max_len) { // Check if there is a packet ready - uint32_t epb = usb_dpram->ep_buf_ctrl[ep].out; + volatile uint16_t *epbp = lookup_epbufctrl(ep, 1, 0); + uint32_t epb = *epbp; if ((epb & (USB_BUF_CTRL_AVAIL|USB_BUF_CTRL_FULL)) != USB_BUF_CTRL_FULL) return -1; - // Copy the packet to the given buffer - uint32_t pid = (epb ^ USB_BUF_CTRL_DATA1_PID) & USB_BUF_CTRL_DATA1_PID; - uint32_t new_epb = USB_BUF_CTRL_LAST | pid | DPBUF_SIZE; - usb_dpram->ep_buf_ctrl[ep].out = new_epb; - uint32_t c = epb & USB_BUF_CTRL_LEN_MASK; - if (c > max_len) - c = max_len; - void *addr = (void*)usb_dpram + usb_buf_offset(ep); - barrier(); - memcpy(data, addr, c); + // Determine the next packet header + uint32_t new_epb = USB_BUF_CTRL_LAST | next_data_pid(epb) | DPBUF_SIZE; + *epbp = new_epb; barrier(); + // Copy the packet to the given buffer + uint32_t c = get_rx_count(epb, max_len); + memcpy(data, usb_buf_addr(ep, 0), c); // Notify the USB hardware that the space is now available - usb_dpram->ep_buf_ctrl[ep].out = new_epb | USB_BUF_CTRL_AVAIL; + barrier(); + *epbp = new_epb | USB_BUF_CTRL_AVAIL; return c; } From bdeec0f56da9b4f8a9e2f9e34a7e7f599a2ba34d Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 24 Sep 2023 23:39:44 -0400 Subject: [PATCH 29/48] rp2040: Open code usb_read_packet() and usb_write_packet() in callers Copy the code for these two functions to their respective callers. This is in preparation for double buffer support. Signed-off-by: Kevin O'Connor --- src/rp2040/usbserial.c | 94 ++++++++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 36 deletions(-) diff --git a/src/rp2040/usbserial.c b/src/rp2040/usbserial.c index f2db3b5e7..ad08cb46d 100644 --- a/src/rp2040/usbserial.c +++ b/src/rp2040/usbserial.c @@ -69,31 +69,16 @@ get_rx_count(uint32_t epb, uint32_t max_len) return c; } -static int_fast8_t -usb_write_packet(uint32_t ep, const void *data, uint_fast8_t len) -{ - // Check if there is room for this packet - volatile uint16_t *epbp = lookup_epbufctrl(ep, 0, 0); - uint32_t epb = *epbp; - if (epb & (USB_BUF_CTRL_AVAIL|USB_BUF_CTRL_FULL)) - return -1; - // Determine the next packet header - uint32_t pid = next_data_pid(epb); - uint32_t new_epb = USB_BUF_CTRL_FULL | USB_BUF_CTRL_LAST | pid | len; - *epbp = new_epb; - barrier(); - // Copy the packet to the hw buffer - memcpy(usb_buf_addr(ep, 0), data, len); - // Inform the USB hardware of the available packet - barrier(); - *epbp = new_epb | USB_BUF_CTRL_AVAIL; - return len; -} -static int_fast8_t -usb_read_packet(uint32_t ep, void *data, uint_fast8_t max_len) +/**************************************************************** + * Interface + ****************************************************************/ + +int_fast8_t +usb_read_bulk_out(void *data, uint_fast8_t max_len) { // Check if there is a packet ready + uint32_t ep = USB_CDC_EP_BULK_OUT; volatile uint16_t *epbp = lookup_epbufctrl(ep, 1, 0); uint32_t epb = *epbp; if ((epb & (USB_BUF_CTRL_AVAIL|USB_BUF_CTRL_FULL)) != USB_BUF_CTRL_FULL) @@ -111,21 +96,26 @@ usb_read_packet(uint32_t ep, void *data, uint_fast8_t max_len) return c; } - -/**************************************************************** - * Interface - ****************************************************************/ - -int_fast8_t -usb_read_bulk_out(void *data, uint_fast8_t max_len) -{ - return usb_read_packet(USB_CDC_EP_BULK_OUT, data, max_len); -} - int_fast8_t usb_send_bulk_in(void *data, uint_fast8_t len) { - return usb_write_packet(USB_CDC_EP_BULK_IN, data, len); + // Check if there is room for this packet + uint32_t ep = USB_CDC_EP_BULK_IN; + volatile uint16_t *epbp = lookup_epbufctrl(ep, 0, 0); + uint32_t epb = *epbp; + if (epb & (USB_BUF_CTRL_AVAIL|USB_BUF_CTRL_FULL)) + return -1; + // Determine the next packet header + uint32_t pid = next_data_pid(epb); + uint32_t new_epb = USB_BUF_CTRL_FULL | USB_BUF_CTRL_LAST | pid | len; + *epbp = new_epb; + barrier(); + // Copy the packet to the hw buffer + memcpy(usb_buf_addr(ep, 0), data, len); + // Inform the USB hardware of the available packet + barrier(); + *epbp = new_epb | USB_BUF_CTRL_AVAIL; + return len; } int_fast8_t @@ -154,19 +144,51 @@ usb_read_ep0_setup(void *data, uint_fast8_t max_len) int_fast8_t usb_read_ep0(void *data, uint_fast8_t max_len) { + // Check if there is a packet ready + uint32_t ep = 0; if (usb_hw->intr & USB_INTR_SETUP_REQ_BITS) // Early end of transmission return -2; - return usb_read_packet(0, data, max_len); + volatile uint16_t *epbp = lookup_epbufctrl(ep, 1, 0); + uint32_t epb = *epbp; + if ((epb & (USB_BUF_CTRL_AVAIL|USB_BUF_CTRL_FULL)) != USB_BUF_CTRL_FULL) + return -1; + // Determine the next packet header + uint32_t new_epb = USB_BUF_CTRL_LAST | next_data_pid(epb) | DPBUF_SIZE; + *epbp = new_epb; + barrier(); + // Copy the packet to the given buffer + uint32_t c = get_rx_count(epb, max_len); + memcpy(data, usb_buf_addr(ep, 0), c); + // Notify the USB hardware that the space is now available + barrier(); + *epbp = new_epb | USB_BUF_CTRL_AVAIL; + return c; } int_fast8_t usb_send_ep0(const void *data, uint_fast8_t len) { + // Check if there is room for this packet + uint32_t ep = 0; if (usb_hw->intr & USB_INTR_SETUP_REQ_BITS || usb_hw->buf_status & 2) // Early end of transmission return -2; - return usb_write_packet(0, data, len); + volatile uint16_t *epbp = lookup_epbufctrl(ep, 0, 0); + uint32_t epb = *epbp; + if (epb & (USB_BUF_CTRL_AVAIL|USB_BUF_CTRL_FULL)) + return -1; + // Determine the next packet header + uint32_t pid = next_data_pid(epb); + uint32_t new_epb = USB_BUF_CTRL_FULL | USB_BUF_CTRL_LAST | pid | len; + *epbp = new_epb; + barrier(); + // Copy the packet to the hw buffer + memcpy(usb_buf_addr(ep, 0), data, len); + // Inform the USB hardware of the available packet + barrier(); + *epbp = new_epb | USB_BUF_CTRL_AVAIL; + return len; } void From 90427fe30ee518f22f2ebba716daf8cedf4020e2 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 24 Sep 2023 02:10:57 -0400 Subject: [PATCH 30/48] rp2040: Add support for double buffering on USB bulk rx packets Signed-off-by: Kevin O'Connor --- src/rp2040/usbserial.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/rp2040/usbserial.c b/src/rp2040/usbserial.c index ad08cb46d..673adc6bd 100644 --- a/src/rp2040/usbserial.c +++ b/src/rp2040/usbserial.c @@ -1,6 +1,6 @@ // Hardware interface to USB on rp2040 // -// Copyright (C) 2021 Kevin O'Connor +// Copyright (C) 2021-2023 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -74,22 +74,27 @@ get_rx_count(uint32_t epb, uint32_t max_len) * Interface ****************************************************************/ +static uint32_t bulk_out_push_count; + int_fast8_t usb_read_bulk_out(void *data, uint_fast8_t max_len) { // Check if there is a packet ready + uint32_t bopc = bulk_out_push_count, bufnum = bopc & 1; uint32_t ep = USB_CDC_EP_BULK_OUT; - volatile uint16_t *epbp = lookup_epbufctrl(ep, 1, 0); + volatile uint16_t *epbp = lookup_epbufctrl(ep, 1, bufnum); uint32_t epb = *epbp; if ((epb & (USB_BUF_CTRL_AVAIL|USB_BUF_CTRL_FULL)) != USB_BUF_CTRL_FULL) return -1; // Determine the next packet header - uint32_t new_epb = USB_BUF_CTRL_LAST | next_data_pid(epb) | DPBUF_SIZE; + bulk_out_push_count = bopc + 1; + uint32_t pid = bufnum ? USB_BUF_CTRL_DATA1_PID : 0; + uint32_t new_epb = USB_BUF_CTRL_LAST | pid | DPBUF_SIZE; *epbp = new_epb; barrier(); // Copy the packet to the given buffer uint32_t c = get_rx_count(epb, max_len); - memcpy(data, usb_buf_addr(ep, 0), c); + memcpy(data, usb_buf_addr(ep, bufnum), c); // Notify the USB hardware that the space is now available barrier(); *epbp = new_epb | USB_BUF_CTRL_AVAIL; @@ -215,8 +220,11 @@ void usb_set_configure(void) { usb_dpram->ep_buf_ctrl[USB_CDC_EP_BULK_IN].in = USB_BUF_CTRL_DATA1_PID; - usb_dpram->ep_buf_ctrl[USB_CDC_EP_BULK_OUT].out = ( - USB_BUF_CTRL_AVAIL | USB_BUF_CTRL_LAST | DPBUF_SIZE); + + bulk_out_push_count = 0; + uint32_t epb0 = USB_BUF_CTRL_AVAIL | USB_BUF_CTRL_LAST | DPBUF_SIZE; + uint32_t epb1 = epb0 | USB_BUF_CTRL_DATA1_PID; + usb_dpram->ep_buf_ctrl[USB_CDC_EP_BULK_OUT].out = epb0 | (epb1 << 16); } @@ -348,6 +356,7 @@ endpoint_setup(void) usb_dpram->ep_ctrl[USB_CDC_EP_ACM-1].in = ep_acm; // BULK uint32_t ep_out = (EP_CTRL_ENABLE_BITS | usb_buf_offset(USB_CDC_EP_BULK_OUT) + | EP_CTRL_DOUBLE_BUFFERED_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | (USB_ENDPOINT_XFER_BULK << EP_CTRL_BUFFER_TYPE_LSB)); usb_dpram->ep_ctrl[USB_CDC_EP_BULK_OUT-1].out = ep_out; From 472fd32cabadf6590a32f41c89d55095cc7ab59f Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 24 Sep 2023 02:24:54 -0400 Subject: [PATCH 31/48] rp2040: Add support for double buffering on USB bulk tx packets Signed-off-by: Kevin O'Connor --- src/rp2040/usbserial.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/rp2040/usbserial.c b/src/rp2040/usbserial.c index 673adc6bd..61d04d189 100644 --- a/src/rp2040/usbserial.c +++ b/src/rp2040/usbserial.c @@ -101,22 +101,26 @@ usb_read_bulk_out(void *data, uint_fast8_t max_len) return c; } +static uint32_t bulk_in_pop_count; + int_fast8_t usb_send_bulk_in(void *data, uint_fast8_t len) { // Check if there is room for this packet + uint32_t bipc = bulk_in_pop_count, bufnum = bipc & 1; uint32_t ep = USB_CDC_EP_BULK_IN; - volatile uint16_t *epbp = lookup_epbufctrl(ep, 0, 0); + volatile uint16_t *epbp = lookup_epbufctrl(ep, 0, bufnum); uint32_t epb = *epbp; if (epb & (USB_BUF_CTRL_AVAIL|USB_BUF_CTRL_FULL)) return -1; // Determine the next packet header - uint32_t pid = next_data_pid(epb); + bulk_in_pop_count = bipc + 1; + uint32_t pid = bufnum ? USB_BUF_CTRL_DATA1_PID : 0; uint32_t new_epb = USB_BUF_CTRL_FULL | USB_BUF_CTRL_LAST | pid | len; *epbp = new_epb; barrier(); // Copy the packet to the hw buffer - memcpy(usb_buf_addr(ep, 0), data, len); + memcpy(usb_buf_addr(ep, bufnum), data, len); // Inform the USB hardware of the available packet barrier(); *epbp = new_epb | USB_BUF_CTRL_AVAIL; @@ -219,7 +223,8 @@ usb_set_address(uint_fast8_t addr) void usb_set_configure(void) { - usb_dpram->ep_buf_ctrl[USB_CDC_EP_BULK_IN].in = USB_BUF_CTRL_DATA1_PID; + bulk_in_pop_count = 0; + usb_dpram->ep_buf_ctrl[USB_CDC_EP_BULK_IN].in = 0; bulk_out_push_count = 0; uint32_t epb0 = USB_BUF_CTRL_AVAIL | USB_BUF_CTRL_LAST | DPBUF_SIZE; @@ -361,6 +366,7 @@ endpoint_setup(void) | (USB_ENDPOINT_XFER_BULK << EP_CTRL_BUFFER_TYPE_LSB)); usb_dpram->ep_ctrl[USB_CDC_EP_BULK_OUT-1].out = ep_out; uint32_t ep_in = (EP_CTRL_ENABLE_BITS | usb_buf_offset(USB_CDC_EP_BULK_IN) + | EP_CTRL_DOUBLE_BUFFERED_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | (USB_ENDPOINT_XFER_BULK << EP_CTRL_BUFFER_TYPE_LSB)); usb_dpram->ep_ctrl[USB_CDC_EP_BULK_IN-1].in = ep_in; From 5b204866c52bea7629f1e95ab345d0bbe8637764 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 25 Sep 2023 11:09:40 -0400 Subject: [PATCH 32/48] usb_canbus: Rename UsbCan.queue to UsbCan.canhw_queue Rename the internal variable names. This is in preparation for support of a USB message queue. Signed-off-by: Kevin O'Connor --- src/generic/usb_canbus.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/generic/usb_canbus.c b/src/generic/usb_canbus.c index d776d45f2..0cafac48f 100644 --- a/src/generic/usb_canbus.c +++ b/src/generic/usb_canbus.c @@ -116,8 +116,8 @@ static struct usbcan_data { uint32_t assigned_id; // Data from physical canbus interface - uint32_t pull_pos, push_pos; - struct canbus_msg queue[32]; + uint32_t canhw_pull_pos, canhw_push_pos; + struct canbus_msg canhw_queue[32]; } UsbCan; enum { @@ -139,16 +139,16 @@ void canbus_process_data(struct canbus_msg *msg) { // Add to admin command queue - uint32_t pushp = UsbCan.push_pos; - if (pushp - UsbCan.pull_pos >= ARRAY_SIZE(UsbCan.queue)) + uint32_t pushp = UsbCan.canhw_push_pos; + if (pushp - UsbCan.canhw_pull_pos >= ARRAY_SIZE(UsbCan.canhw_queue)) // No space - drop message return; if (UsbCan.assigned_id && (msg->id & ~1) == UsbCan.assigned_id) // Id reserved for local return; - uint32_t pos = pushp % ARRAY_SIZE(UsbCan.queue); - memcpy(&UsbCan.queue[pos], msg, sizeof(*msg)); - UsbCan.push_pos = pushp + 1; + uint32_t pos = pushp % ARRAY_SIZE(UsbCan.canhw_queue); + memcpy(&UsbCan.canhw_queue[pos], msg, sizeof(*msg)); + UsbCan.canhw_push_pos = pushp + 1; usb_notify_bulk_out(); } @@ -167,24 +167,24 @@ send_frame(struct canbus_msg *msg) // Send any pending hw frames to host static void -drain_hw_queue(void) +drain_canhw_queue(void) { - uint32_t pull_pos = UsbCan.pull_pos; + uint32_t pull_pos = UsbCan.canhw_pull_pos; for (;;) { - uint32_t push_pos = readl(&UsbCan.push_pos); + uint32_t push_pos = readl(&UsbCan.canhw_push_pos); if (push_pos == pull_pos) { // No more data to send UsbCan.usb_send_busy = 0; return; } - uint32_t pos = pull_pos % ARRAY_SIZE(UsbCan.queue); - int ret = send_frame(&UsbCan.queue[pos]); + uint32_t pos = pull_pos % ARRAY_SIZE(UsbCan.canhw_queue); + int ret = send_frame(&UsbCan.canhw_queue[pos]); if (ret < 0) { // USB is busy - retry later UsbCan.usb_send_busy = 1; return; } - UsbCan.pull_pos = pull_pos = pull_pos + 1; + UsbCan.canhw_pull_pos = pull_pos = pull_pos + 1; } } @@ -195,7 +195,7 @@ usbcan_task(void) return; // Send any pending hw frames to host - drain_hw_queue(); + drain_canhw_queue(); for (;;) { // See if previous host frame needs to be transmitted From 78ae83c3141e5e46ca109e92adc015e3698f078b Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 25 Sep 2023 11:34:31 -0400 Subject: [PATCH 33/48] usb_canbus: Add a local queue for USB messages received from host Read USB messages arriving from the host into a queue. This makes it less likely that USB "bulk out" packets will be NAK'ed on the USB bus, which improves USB bus utilization. Signed-off-by: Kevin O'Connor --- src/generic/usb_canbus.c | 53 ++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/src/generic/usb_canbus.c b/src/generic/usb_canbus.c index 0cafac48f..9d09adf9e 100644 --- a/src/generic/usb_canbus.c +++ b/src/generic/usb_canbus.c @@ -104,17 +104,15 @@ struct gs_host_frame { static struct usbcan_data { struct task_wake wake; - // Canbus data from host - union { - struct gs_host_frame host_frame; - uint8_t rx_frame_pad[USB_CDC_EP_BULK_OUT_SIZE]; - }; - uint8_t host_status; - // Canbus data routed locally uint8_t notify_local, usb_send_busy; uint32_t assigned_id; + // Canbus data from host + uint8_t host_status; + uint32_t host_pull_pos, host_push_pos; + struct gs_host_frame host_frames[16]; + // Data from physical canbus interface uint32_t canhw_pull_pos, canhw_push_pos; struct canbus_msg canhw_queue[32]; @@ -188,6 +186,25 @@ drain_canhw_queue(void) } } +// Fill local queue with any USB messages sent from host +static void +fill_usb_host_queue(void) +{ + uint32_t pull_pos = UsbCan.host_pull_pos, push_pos = UsbCan.host_push_pos; + for (;;) { + if (push_pos - pull_pos >= ARRAY_SIZE(UsbCan.host_frames)) + // No more space in queue + break; + uint32_t pushp = push_pos % ARRAY_SIZE(UsbCan.host_frames); + struct gs_host_frame *gs = &UsbCan.host_frames[pushp]; + int ret = usb_read_bulk_out(gs, sizeof(*gs)); + if (ret <= 0) + // No more messages ready + break; + UsbCan.host_push_pos = push_pos = push_pos + 1; + } +} + void usbcan_task(void) { @@ -197,11 +214,17 @@ usbcan_task(void) // Send any pending hw frames to host drain_canhw_queue(); + // Fill local queue with any USB messages arriving from host + fill_usb_host_queue(); + + // Route messages received from host + uint32_t pull_pos = UsbCan.host_pull_pos, push_pos = UsbCan.host_push_pos; + uint32_t pullp = pull_pos % ARRAY_SIZE(UsbCan.host_frames); + struct gs_host_frame *gs = &UsbCan.host_frames[pullp]; for (;;) { // See if previous host frame needs to be transmitted uint_fast8_t host_status = UsbCan.host_status; if (host_status & (HS_TX_HW | HS_TX_LOCAL)) { - struct gs_host_frame *gs = &UsbCan.host_frame; struct canbus_msg msg; msg.id = gs->can_id; msg.dlc = gs->can_dlc; @@ -224,20 +247,19 @@ usbcan_task(void) if (UsbCan.usb_send_busy) // Don't send echo frame until other traffic is sent return; - int ret = usb_send_bulk_in(&UsbCan.host_frame - , sizeof(UsbCan.host_frame)); + int ret = usb_send_bulk_in(gs, sizeof(*gs)); if (ret < 0) return; UsbCan.host_status = 0; + UsbCan.host_pull_pos = pull_pos = pull_pos + 1; } - // Read next frame from host - int ret = usb_read_bulk_out(&UsbCan.host_frame - , USB_CDC_EP_BULK_OUT_SIZE); - if (ret <= 0) + // Process next frame from host + if (pull_pos == push_pos) // No frame available - no more work to be done break; - uint32_t id = UsbCan.host_frame.can_id; + gs = &UsbCan.host_frames[pull_pos % ARRAY_SIZE(UsbCan.host_frames)]; + uint32_t id = gs->can_id; UsbCan.host_status = HS_TX_ECHO | HS_TX_HW; if (id == CANBUS_ID_ADMIN) UsbCan.host_status = HS_TX_ECHO | HS_TX_HW | HS_TX_LOCAL; @@ -245,6 +267,7 @@ usbcan_task(void) UsbCan.host_status = HS_TX_ECHO | HS_TX_LOCAL; } + // Wake up local message response handling (if usb is not busy) if (UsbCan.notify_local && !UsbCan.usb_send_busy) canserial_notify_tx(); } From 6adff3954b149877c8a587893b18b291e34856b2 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 26 Sep 2023 15:24:29 -0400 Subject: [PATCH 34/48] usb_canbus: Prioritize local response sending over new host messages Prioritize sending responses back to the host over transmitting new messages from the host. Otherwise, the gs_usb host usb acknowledgments could saturate the usb bandwidth for extended periods. Signed-off-by: Kevin O'Connor --- src/generic/usb_canbus.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/generic/usb_canbus.c b/src/generic/usb_canbus.c index 9d09adf9e..9c2893bf2 100644 --- a/src/generic/usb_canbus.c +++ b/src/generic/usb_canbus.c @@ -244,9 +244,9 @@ usbcan_task(void) // Send any previous echo frames if (host_status) { - if (UsbCan.usb_send_busy) + if (UsbCan.notify_local || UsbCan.usb_send_busy) // Don't send echo frame until other traffic is sent - return; + break; int ret = usb_send_bulk_in(gs, sizeof(*gs)); if (ret < 0) return; @@ -281,6 +281,8 @@ canbus_send(struct canbus_msg *msg) int ret = send_frame(msg); if (ret < 0) goto retry_later; + if (UsbCan.notify_local && UsbCan.host_status) + canbus_notify_tx(); UsbCan.notify_local = 0; return msg->dlc; retry_later: From 615db729e7f35949860f0c9791d6124e1a8fb725 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 4 Oct 2023 18:49:46 -0400 Subject: [PATCH 35/48] stm32: Only enable one direction on usbfs double buffered end points The bulk out endpoint should not be enabled in tx mode, and the bulk in endpoint should not be enabled in rx mode. Signed-off-by: Kevin O'Connor --- src/stm32/usbfs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/stm32/usbfs.c b/src/stm32/usbfs.c index d166fbb7a..4349b6af1 100644 --- a/src/stm32/usbfs.c +++ b/src/stm32/usbfs.c @@ -342,12 +342,12 @@ usb_reset(void) ep = USB_CDC_EP_BULK_OUT; USB_EPR[ep] = (USB_CDC_EP_BULK_OUT | USB_EP_BULK | USB_EP_KIND - | USB_EP_RX_VALID | USB_EP_TX_NAK | USB_EP_DTOG_TX); + | USB_EP_RX_NAK | USB_EP_DTOG_TX); bulk_out_push_flag = USB_EP_DTOG_TX; ep = USB_CDC_EP_BULK_IN; USB_EPR[ep] = (USB_CDC_EP_BULK_IN | USB_EP_BULK | USB_EP_KIND - | USB_EP_RX_NAK | USB_EP_TX_NAK); + | USB_EP_TX_NAK); bulk_in_pop_flag = USB_EP_DTOG_RX; USB->CNTR = USB_CNTR_CTRM | USB_CNTR_RESETM; From 043f18da260378d11e550248c032702017dafb49 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 4 Oct 2023 19:34:22 -0400 Subject: [PATCH 36/48] stm32: Fix usbfs spurious USB packet transmit on startup Commit cd8d57c2 added USB double buffering mode on transmits. However, when enabling double buffering mode, the hardware seems to always send at least two packets. Spurious transmissions could cause the Linux gs_usb driver to get confused, which could lead to the can0 device becoming unavailable on restarts. Fix by waiting for two USB packets to be available before enabling the endpoint. Signed-off-by: Kevin O'Connor --- src/stm32/usbfs.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/stm32/usbfs.c b/src/stm32/usbfs.c index 4349b6af1..0ed2e0ee5 100644 --- a/src/stm32/usbfs.c +++ b/src/stm32/usbfs.c @@ -236,7 +236,8 @@ usb_read_bulk_out(void *data, uint_fast8_t max_len) return count; } -static uint32_t bulk_in_push_count, bulk_in_pop_flag; +static uint32_t bulk_in_push_pos, bulk_in_pop_flag; +#define BI_START 2 int_fast8_t usb_send_bulk_in(void *data, uint_fast8_t len) @@ -245,8 +246,8 @@ usb_send_bulk_in(void *data, uint_fast8_t len) // No buffer space available return -1; uint32_t ep = USB_CDC_EP_BULK_IN; - int bufnum = bulk_in_push_count & 1; - bulk_in_push_count++; + uint32_t bipp = bulk_in_push_pos, bufnum = bipp & 1; + bulk_in_push_pos = bipp ^ 1; btable_write_packet(ep, bufnum, data, len); writel(&bulk_in_pop_flag, USB_EP_DTOG_RX); @@ -254,7 +255,17 @@ usb_send_bulk_in(void *data, uint_fast8_t len) uint32_t epr = USB_EPR[ep]; if (epr_is_dbuf_blocking(epr) && readl(&bulk_in_pop_flag)) { writel(&bulk_in_pop_flag, 0); - USB_EPR[ep] = calc_epr_bits(epr, 0, 0) | USB_EP_DTOG_RX; + if (bipp & BI_START) { + // Two packets are always sent when starting in double + // buffering mode, so wait for second packet before starting. + if (bipp == (BI_START | 1)) { + bulk_in_push_pos = 0; + USB_EPR[ep] = calc_epr_bits(epr, USB_EPTX_STAT + , USB_EP_TX_VALID); + } + } else { + USB_EPR[ep] = calc_epr_bits(epr, 0, 0) | USB_EP_DTOG_RX; + } } return len; @@ -319,9 +330,8 @@ usb_set_configure(void) USB_EPR[ep] = calc_epr_bits(USB_EPR[ep], USB_EPRX_STAT, USB_EP_RX_VALID); ep = USB_CDC_EP_BULK_IN; - bulk_in_push_count = 0; + bulk_in_push_pos = BI_START; writel(&bulk_in_pop_flag, 0); - USB_EPR[ep] = calc_epr_bits(USB_EPR[ep], USB_EPTX_STAT, USB_EP_TX_VALID); } From 447125faae62bfad4a0e483264453a39c9ba52d8 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 4 Oct 2023 22:26:10 -0400 Subject: [PATCH 37/48] serialqueue: Eventually time out if unable to write CANbus messages Klipper logs an error on a failed CANbus write. Unfortunately, if the bus becomes permanently disabled (eg, due to a user removing power to devices on the CANbus) then it can result in the logs filling with error messages. Permanently disable the low-level processing of messages if CANbus writes continually fail for at least 10 seconds. This avoids filling the log with redundant messages. Signed-off-by: Kevin O'Connor --- klippy/chelper/serialqueue.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/klippy/chelper/serialqueue.c b/klippy/chelper/serialqueue.c index b6500fe62..e6810933a 100644 --- a/klippy/chelper/serialqueue.c +++ b/klippy/chelper/serialqueue.c @@ -62,6 +62,7 @@ struct serialqueue { int ready_bytes, upcoming_bytes, need_ack_bytes, last_ack_bytes; uint64_t need_kick_clock; struct list_head notify_queue; + double last_write_fail_time; // Received messages struct list_head receive_queue; // Fastreader support @@ -376,8 +377,16 @@ do_write(struct serialqueue *sq, void *buf, int buflen) int ret = write(sq->serial_fd, &cf, sizeof(cf)); if (ret < 0) { report_errno("can write", ret); + double curtime = get_monotonic(); + if (!sq->last_write_fail_time) { + sq->last_write_fail_time = curtime; + } else if (curtime > sq->last_write_fail_time + 10.0) { + errorf("Halting reads due to CAN write errors."); + pollreactor_do_exit(sq->pr); + } return; } + sq->last_write_fail_time = 0.0; buf += size; buflen -= size; } From 83ef0e135ef820f73c8579069c347953b9d6d56e Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 5 Oct 2023 00:03:07 -0400 Subject: [PATCH 38/48] stm32: Make sure to limit tx during usbfs startup Wait for two tx packets before startup, and make sure one of those packets is acked before sending a third tx packet. Signed-off-by: Kevin O'Connor --- src/stm32/usbfs.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/stm32/usbfs.c b/src/stm32/usbfs.c index 0ed2e0ee5..ad2e7b3eb 100644 --- a/src/stm32/usbfs.c +++ b/src/stm32/usbfs.c @@ -255,11 +255,12 @@ usb_send_bulk_in(void *data, uint_fast8_t len) uint32_t epr = USB_EPR[ep]; if (epr_is_dbuf_blocking(epr) && readl(&bulk_in_pop_flag)) { writel(&bulk_in_pop_flag, 0); - if (bipp & BI_START) { + if (unlikely(bipp & BI_START)) { // Two packets are always sent when starting in double // buffering mode, so wait for second packet before starting. if (bipp == (BI_START | 1)) { bulk_in_push_pos = 0; + writel(&bulk_in_pop_flag, USB_EP_KIND); // Dummy flag USB_EPR[ep] = calc_epr_bits(epr, USB_EPTX_STAT , USB_EP_TX_VALID); } From 7eabf02f5bd44c7966e283b67336be7ef0e8f07f Mon Sep 17 00:00:00 2001 From: ghostoverflow256 <46546505+GhostDog98@users.noreply.github.com> Date: Tue, 10 Oct 2023 04:28:49 +1100 Subject: [PATCH 39/48] config: Update printer-creality-ender5-2019.cfg to add instructions for silent boards (#6326) Recently tested on my ender 5 pro that came from creality with a v1.1.5 board. Works. Tested all endstops, motors, and heaters. Signed-off-by: Jake Aronleigh --- config/printer-creality-ender5-2019.cfg | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/config/printer-creality-ender5-2019.cfg b/config/printer-creality-ender5-2019.cfg index 0130321ba..a1b9d1593 100644 --- a/config/printer-creality-ender5-2019.cfg +++ b/config/printer-creality-ender5-2019.cfg @@ -1,10 +1,12 @@ # This file contains common pin mappings for the 2019 Creality # Ender 5. To use this config, the firmware should be compiled for the -# AVR atmega1284p. +# AVR atmega1284p. This also works for the v1.1.5 silent boards. # Note, a number of Melzi boards are shipped with a bootloader that # requires the following command to flash the board: # avrdude -p atmega1284p -c arduino -b 57600 -P /dev/ttyUSB0 -U out/klipper.elf.hex +# For v1.1.5 silent boards, the following command is used: +# avrdude -p atmega1284p -c arduino -P /dev/ttyUSB0 -b 115200 -U flash:w:out/klipper.elf.hex # If the above command does not work and "make flash" does not work # then one may need to flash a bootloader to the board - see the # Klipper docs/Bootloaders.md file for more information. @@ -80,6 +82,8 @@ pin: PB4 [mcu] serial: /dev/serial/by-id/usb-1a86_USB2.0-Serial-if00-port0 +# Silent boards tend to have the exact same serial ID, except without USB2.0, using USB instead. +# e.g. /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0 [printer] kinematics: cartesian From 5edc7fee7e4560806533ec6ed1550cb5faad12fc Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 9 Oct 2023 13:34:58 -0400 Subject: [PATCH 40/48] config: Fix trailing space in printer-creality-ender5-2019.cfg Signed-off-by: Kevin O'Connor --- config/printer-creality-ender5-2019.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/printer-creality-ender5-2019.cfg b/config/printer-creality-ender5-2019.cfg index a1b9d1593..cf0d4bed0 100644 --- a/config/printer-creality-ender5-2019.cfg +++ b/config/printer-creality-ender5-2019.cfg @@ -1,6 +1,6 @@ # This file contains common pin mappings for the 2019 Creality # Ender 5. To use this config, the firmware should be compiled for the -# AVR atmega1284p. This also works for the v1.1.5 silent boards. +# AVR atmega1284p. This also works for the v1.1.5 silent boards. # Note, a number of Melzi boards are shipped with a bootloader that # requires the following command to flash the board: From ecc9bbf52a7dfb69a535b6bb87ad887c6e957387 Mon Sep 17 00:00:00 2001 From: "Aaron B. Haun" Date: Mon, 18 Sep 2023 19:48:42 -0400 Subject: [PATCH 41/48] docs: add info about requesting bootloaders Signed-off-by: Aaron B. Haun --- docs/Bootloader_Entry.md | 125 +++++++++++++++++++++++++++++++++++++ docs/Overview.md | 1 + docs/_klipper3d/mkdocs.yml | 1 + 3 files changed, 127 insertions(+) create mode 100644 docs/Bootloader_Entry.md diff --git a/docs/Bootloader_Entry.md b/docs/Bootloader_Entry.md new file mode 100644 index 000000000..f42602bd7 --- /dev/null +++ b/docs/Bootloader_Entry.md @@ -0,0 +1,125 @@ +# Bootloader Entry + +Klipper can be instructed to reboot into a [Bootloader](Bootloaders.md) in one +of the following ways: + +## Requesting the bootloader + +### Virtual Serial + +If a virtual (USB-ACM) serial port is in use, pulsing DTR while at 1200 baud +will request the bootloader. + +#### Python (with `flash_usb`) + +To enter the bootloader using python (using `flash_usb`): + +```shell +> cd klipper/scripts +> python3 -c 'import flash_usb as u; u.enter_bootloader("")' +Entering bootloader on +``` + +Where `` is your serial device, such as +`/dev/serial.by-id/usb-Klipper[...]` or `/dev/ttyACM0` + +Note that if this fails, no output will be printed, success is indicated by +printing `Entering bootloader on `. + +#### Picocom + +```shell +picocom -b 1200 + +``` + +Where `` is your serial device, such as +`/dev/serial.by-id/usb-Klipper[...]` or `/dev/ttyACM0` + +`` means +holding `Ctrl`, pressing and releasing `a`, pressing and releasing `p`, then +releasing `Ctrl` + +### Physical serial + +If a physical serial port is being used on the MCU (even if a USB serial adapter +is being used to connect to it), sending the string +`Request Serial Bootloader!!~`. + +`` is an ASCII literal space, 0x20. + +`` is the ASCII File Separator, +0x1c. + +Note that this is not a valid message as per the +[MCU Protocol](Protocol.md#micro-controller-interface), but sync characters(`~`) +are still respected. + +Because this message must be the only thing in the "block" +it is received in, prefixing an extra sync character can increase reliability if +other tools were previously accessing the serial port. + +#### Shell + +```shell +stty < /dev/ +echo $'~ \x1c Request Serial Bootloader!! ~' >> /dev/ +``` + +Where `` is your serial port, such as `/dev/ttyS0`, or +`/dev/serial/by-id/gpio-serial2`, and + +`` is the baud rate of the serial +port, such as `115200`. + +### CANBUS + +If CANBUS is in use, a special +[admin message](CANBUS_protocol.md#admin-messages) will request the bootloader. +This message will be respected even if the device already has a nodeid, and will +also be processed if the mcu is shutdown. + +This method also applies to devices operating in +[CANBridge](CANBUS.md#usb-to-can-bus-bridge-mode) mode. + +#### Katapult's flashtool.py + +```shell +python3 ./katapult/scripts/flashtool.py -i -u -r +``` + +Where `` is the can interface to use. If using `can0`, both the `-i` +and `` may be omitted. + +`` is the UUID of your CAN device. + +See the +[CANBUS Documentation](CANBUS.md#finding-the-canbus_uuid-for-new-micro-controllers) +for information on finding the CAN UUID of your devices. + +## Entering the bootloader + +When klipper receives one of the above bootloader requests: + +If Katapult (formerly known as CANBoot) is available, klipper will request that +Katapult stay active on the next boot, then reset the MCU (therefore entering +Katapult). + +If Katapult is not available, klipper will then try to enter a +platform-specific bootloader, such as STM32's DFU +mode([see note](#stm32-dfu-warning)). + +In short, Klipper will reboot to Katapult if installed, then a hardware specific +bootloader if available. + +For details about the specific bootloaders on various platforms see +[Bootloaders](Bootloaders.md) + +## Notes + +### STM32 DFU Warning + +Note that on some boards, like the Octopus Pro v1, entering DFU mode can cause +undesired actions (such as powering the heater while in DFU mode). It is +recommended to disconnect heaters, and otherwise prevent undesired operations +when using DFU mode. Consult the documentation for your board for more details. diff --git a/docs/Overview.md b/docs/Overview.md index a387ea81d..477bc68b7 100644 --- a/docs/Overview.md +++ b/docs/Overview.md @@ -93,6 +93,7 @@ communication with the Klipper developers. Beaglebone PRU. - [Bootloaders](Bootloaders.md): Developer information on micro-controller flashing. +- [Bootloader Entry](Bootloader_Entry.md): Requesting the bootloader. - [CAN bus](CANBUS.md): Information on using CAN bus with Klipper. - [CAN bus troubleshooting](CANBUS_Troubleshooting.md): Tips for troubleshooting CAN bus. diff --git a/docs/_klipper3d/mkdocs.yml b/docs/_klipper3d/mkdocs.yml index 6db7fe394..d290a45f5 100644 --- a/docs/_klipper3d/mkdocs.yml +++ b/docs/_klipper3d/mkdocs.yml @@ -133,6 +133,7 @@ nav: - RPi_microcontroller.md - Beaglebone.md - Bootloaders.md + - Bootloader_Entry.md - CANBUS.md - CANBUS_Troubleshooting.md - TSL1401CL_Filament_Width_Sensor.md From b1f597c550dc90386f417a2d3b4fc35b4189c902 Mon Sep 17 00:00:00 2001 From: Alex Maclean Date: Fri, 13 Oct 2023 02:02:26 +0100 Subject: [PATCH 42/48] atsam: Remove USB endpoint header (#6365) The atsam USB hardware only requires that the ACM endpoint be endpoint 3. As of commit 11828387 the atsam chips can therefore use the default USB endpoints. This will allow CAN bridge support for the SAME70 to function (upstream host driver has hardcoded endpoints). Signed-off-by: Alex Maclean --- src/atsam/usb_cdc_ep.h | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 src/atsam/usb_cdc_ep.h diff --git a/src/atsam/usb_cdc_ep.h b/src/atsam/usb_cdc_ep.h deleted file mode 100644 index bcf1d3e38..000000000 --- a/src/atsam/usb_cdc_ep.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef __SAM3_USB_CDC_EP_H -#define __SAM3_USB_CDC_EP_H - -enum { - USB_CDC_EP_ACM = 3, - USB_CDC_EP_BULK_OUT = 1, - USB_CDC_EP_BULK_IN = 2, -}; - -#endif // usb_cdc_ep.h From dd01e99c595023a5724d993f300f038eea918f5a Mon Sep 17 00:00:00 2001 From: Luke V Date: Thu, 19 Oct 2023 13:01:59 -0400 Subject: [PATCH 43/48] atsam: Add support for CAN on atsame70 (#6366) Signed-off-by: Luke Vuksta --- src/atsam/Kconfig | 29 ++++ src/atsam/Makefile | 8 +- src/atsam/chipid.c | 9 +- src/atsam/fdcan.c | 307 +++++++++++++++++++++++++++++++++++++ src/atsam/main.c | 6 + src/atsam/same70_sysinit.c | 5 +- 6 files changed, 359 insertions(+), 5 deletions(-) create mode 100644 src/atsam/fdcan.c diff --git a/src/atsam/Kconfig b/src/atsam/Kconfig index 7fac95f50..4a10a0f7d 100644 --- a/src/atsam/Kconfig +++ b/src/atsam/Kconfig @@ -50,6 +50,9 @@ config MACH_SAM4E select MACH_SAM4 config MACH_SAME70 bool +config HAVE_SAM_CANBUS + bool + default y if MACH_SAME70 config MCU string @@ -101,6 +104,32 @@ choice config ATSAM_SERIAL bool "Serial" select SERIAL + config ATSAM_MMENU_CANBUS_PC12_PD12 + bool "CAN bus (on PC12/PD12)" + depends on HAVE_SAM_CANBUS + select CANSERIAL + config ATSAM_MMENU_CANBUS_PB3_PB2 + bool "CAN bus (on PB3/PB2)" + depends on HAVE_SAM_CANBUS + select CANSERIAL + config ATSAM_USBCANBUS + bool "USB to CAN bus bridge" + depends on HAVE_SAM_CANBUS + select USBCANBUS +endchoice +choice + prompt "CAN bus interface" if USBCANBUS + config ATSAM_CMENU_CANBUS_PC12_PD12 + bool "CAN bus (on PC12/PD12)" + config ATSAM_CMENU_CANBUS_PB3_PB2 + bool "CAN bus (on PB3/PB2)" endchoice +config ATSAM_CANBUS_PC12_PD12 + bool + default y if ATSAM_MMENU_CANBUS_PC12_PD12 || ATSAM_CMENU_CANBUS_PC12_PD12 +config ATSAM_CANBUS_PB3_PB2 + bool + default y if ATSAM_MMENU_CANBUS_PB3_PB2 || ATSAM_CMENU_CANBUS_PB3_PB2 + endif diff --git a/src/atsam/Makefile b/src/atsam/Makefile index 1c32cb40c..7ab69b823 100644 --- a/src/atsam/Makefile +++ b/src/atsam/Makefile @@ -3,7 +3,7 @@ # Setup the toolchain CROSS_PREFIX=arm-none-eabi- -dirs-y += src/atsam src/generic +dirs-y += src/atsam src/generic lib/fast-hash dirs-$(CONFIG_MACH_SAM3X) += lib/sam3x/gcc dirs-$(CONFIG_MACH_SAM4S) += lib/sam4s/gcc dirs-$(CONFIG_MACH_SAM4E) += lib/sam4e/gcc @@ -18,7 +18,7 @@ CFLAGS-$(CONFIG_MACH_SAM3X) += -Ilib/sam3x/include CFLAGS-$(CONFIG_MACH_SAM4S) += -Ilib/sam4s/include CFLAGS-$(CONFIG_MACH_SAM4E) += -Ilib/sam4e/include CFLAGS-$(CONFIG_MACH_SAME70) += -Ilib/same70b/include -CFLAGS += $(CFLAGS-y) -D__$(MCU)__ -mthumb -Ilib/cmsis-core +CFLAGS += $(CFLAGS-y) -D__$(MCU)__ -mthumb -Ilib/cmsis-core -Ilib/fast-hash CFLAGS_klipper.elf += --specs=nano.specs --specs=nosys.specs CFLAGS_klipper.elf += -T $(OUT)src/generic/armcm_link.ld @@ -33,6 +33,10 @@ usb-src-$(CONFIG_MACH_SAM4) := atsam/sam4_usb.c usb-src-$(CONFIG_MACH_SAME70) := atsam/sam3_usb.c src-$(CONFIG_USBSERIAL) += $(usb-src-y) atsam/chipid.c generic/usb_cdc.c src-$(CONFIG_SERIAL) += atsam/serial.c generic/serial_irq.c +canbus-src-y := generic/canserial.c ../lib/fast-hash/fasthash.c +canbus-src-y += atsam/fdcan.c atsam/chipid.c +src-$(CONFIG_USBCANBUS) += $(canbus-src-y) $(usb-src-y) generic/usb_canbus.c +src-$(CONFIG_CANSERIAL) += $(canbus-src-y) generic/canbus.c src-$(CONFIG_MACH_SAM3X) += atsam/adc.c atsam/hard_pwm.c src-$(CONFIG_MACH_SAM4) += atsam/hard_pwm.c src-$(CONFIG_MACH_SAM4S) += atsam/adc.c diff --git a/src/atsam/chipid.c b/src/atsam/chipid.c index b4ab289ca..dda4c5d00 100644 --- a/src/atsam/chipid.c +++ b/src/atsam/chipid.c @@ -5,6 +5,7 @@ // This file may be distributed under the terms of the GNU GPLv3 license. #include "generic/irq.h" // irq_disable +#include "generic/canserial.h" // canserial_set_uuid #include "generic/usb_cdc.h" // usb_fill_serial #include "generic/usbstd.h" // usb_string_descriptor #include "internal.h" // EFC0 @@ -61,7 +62,7 @@ read_chip_id(uint32_t *id) void chipid_init(void) { - if (!CONFIG_USB_SERIAL_NUMBER_CHIPID) + if (!CONFIG_USB_SERIAL_NUMBER_CHIPID && !CONFIG_CANBUS) return; uint32_t id[4]; @@ -69,6 +70,10 @@ chipid_init(void) read_chip_id(id); irq_enable(); - usb_fill_serial(&cdc_chipid.desc, ARRAY_SIZE(cdc_chipid.data), id); + if (CONFIG_USB_SERIAL_NUMBER_CHIPID) + usb_fill_serial(&cdc_chipid.desc, ARRAY_SIZE(cdc_chipid.data), id); + + if (CONFIG_CANBUS) + canserial_set_uuid((void*)id, CHIP_UID_LEN); } DECL_INIT(chipid_init); diff --git a/src/atsam/fdcan.c b/src/atsam/fdcan.c new file mode 100644 index 000000000..a536a7be1 --- /dev/null +++ b/src/atsam/fdcan.c @@ -0,0 +1,307 @@ +// CANbus support on atsame70 chips +// +// Copyright (C) 2021-2022 Kevin O'Connor +// Copyright (C) 2019 Eug Krashtan +// Copyright (C) 2020 Pontus Borg +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include "command.h" // DECL_CONSTANT_STR +#include "generic/armcm_boot.h" // armcm_enable_irq +#include "generic/canbus.h" // canbus_notify_tx +#include "generic/canserial.h" // CANBUS_ID_ADMIN +#include "internal.h" // enable_pclock +#include "sched.h" // DECL_INIT + + +/**************************************************************** + * Pin configuration + ****************************************************************/ + +#if CONFIG_ATSAM_CANBUS_PB3_PB2 + DECL_CONSTANT_STR("RESERVE_PINS_CAN", "PB3,PB2"); + #define GPIO_Rx GPIO('B', 3) + #define GPIO_Tx GPIO('B', 2) + #define CANx_GCLK_ID MCAN0_CLOCK_ID +#elif CONFIG_ATSAM_CANBUS_PC12_PD12 + DECL_CONSTANT_STR("RESERVE_PINS_CAN", "PC12,PD12"); + #define GPIO_Rx GPIO('C', 12) + #define GPIO_Tx GPIO('D', 12) + #define CANx_GCLK_ID MCAN1_CLOCK_ID +#endif + +#if CANx_GCLK_ID == MCAN0_CLOCK_ID + #define CAN_FUNCTION_Rx 'A' + #define CAN_FUNCTION_Tx 'A' + #define CANx MCAN0 + #define CANx_IRQn MCAN0_INT0_IRQn + #define CCFG_CANxDMABA MATRIX->CCFG_CAN0 + #define CCFG_CANxDMABA_Msk CCFG_CAN0_CAN0DMABA_Msk + #define CCFG_CANxDMABA_Pos CCFG_CAN0_CAN0DMABA_Pos +#else + #define CAN_FUNCTION_Rx 'C' + #define CAN_FUNCTION_Tx 'B' + #define CANx MCAN1 + #define CANx_IRQn MCAN1_INT0_IRQn + #define CCFG_CANxDMABA MATRIX->CCFG_SYSIO + #define CCFG_CANxDMABA_Msk CCFG_SYSIO_CAN1DMABA_Msk + #define CCFG_CANxDMABA_Pos CCFG_SYSIO_CAN1DMABA_Pos +#endif + + +/**************************************************************** + * Message ram layout + ****************************************************************/ + +struct fdcan_fifo { + uint32_t id_section; + uint32_t dlc_section; + uint32_t data[64 / 4]; +}; + +#define FDCAN_XTD (1<<30) +#define FDCAN_RTR (1<<29) + +struct fdcan_msg_ram { + uint32_t FLS[28]; // Filter list standard + uint32_t FLE[16]; // Filter list extended + struct fdcan_fifo RXF0[3]; + struct fdcan_fifo RXF1[3]; + uint32_t TEF[6]; // Tx event FIFO + struct fdcan_fifo TXFIFO[3]; +}; + +// Message ram is in regular memory +static struct fdcan_msg_ram MSG_RAM; + + +/**************************************************************** + * CANbus code + ****************************************************************/ + +#define FDCAN_IE_TC (MCAN_IE_TCE | MCAN_IE_TCFE | MCAN_IE_TFEE) + +// Transmit a packet +int +canhw_send(struct canbus_msg *msg) +{ + uint32_t txfqs = CANx->MCAN_TXFQS; + if (txfqs & MCAN_TXFQS_TFQF) + // No space in transmit fifo - wait for irq + return -1; + + uint32_t w_index = ((txfqs & MCAN_TXFQS_TFQPI_Msk) >> MCAN_TXFQS_TFQPI_Pos); + struct fdcan_fifo *txfifo = &MSG_RAM.TXFIFO[w_index]; + uint32_t ids; + if (msg->id & CANMSG_ID_EFF) + ids = (msg->id & 0x1fffffff) | FDCAN_XTD; + else + ids = (msg->id & 0x7ff) << 18; + ids |= msg->id & CANMSG_ID_RTR ? FDCAN_RTR : 0; + txfifo->id_section = ids; + txfifo->dlc_section = (msg->dlc & 0x0f) << 16; + txfifo->data[0] = msg->data32[0]; + txfifo->data[1] = msg->data32[1]; + __DMB(); + CANx->MCAN_TXBAR; + CANx->MCAN_TXBAR = ((uint32_t)1 << w_index); + return CANMSG_DATA_LEN(msg); +} + +static void +can_filter(uint32_t index, uint32_t id) +{ + MSG_RAM.FLS[index] = ((0x2 << 30) // Classic filter + | (0x1 << 27) // Store in Rx FIFO 0 if filter matches + | (id << 16) + | 0x7FF); // mask all enabled +} + +// Setup the receive packet filter +void +canhw_set_filter(uint32_t id) +{ + if (!CONFIG_CANBUS_FILTER) + return; + /* Request initialisation */ + CANx->MCAN_CCCR |= MCAN_CCCR_INIT; + /* Wait the acknowledge */ + while (!(CANx->MCAN_CCCR & MCAN_CCCR_INIT)) + ; + /* Enable configuration change */ + CANx->MCAN_CCCR |= MCAN_CCCR_CCE; + + // Load filter + can_filter(0, CANBUS_ID_ADMIN); + can_filter(1, id); + can_filter(2, id + 1); + + uint32_t flssa = (uint32_t)MSG_RAM.FLS + - (CCFG_CANxDMABA_Msk & CCFG_CANxDMABA); + CANx->MCAN_SIDFC = flssa | ((id ? 3 : 1) << MCAN_SIDFC_LSS_Pos); + CANx->MCAN_GFC = 0x02 << MCAN_GFC_ANFS_Pos; + + /* Leave the initialisation mode for the filter */ + barrier(); + CANx->MCAN_CCCR &= ~MCAN_CCCR_CCE; + CANx->MCAN_CCCR &= ~MCAN_CCCR_INIT; +} + +// This function handles CAN global interrupts +void +CAN_IRQHandler(void) +{ + uint32_t ir = CANx->MCAN_IR; + + if (ir & MCAN_IE_RF0NE) { + CANx->MCAN_IR = MCAN_IE_RF0NE; + + uint32_t rxf0s = CANx->MCAN_RXF0S; + if (rxf0s & MCAN_RXF0S_F0FL_Msk) { + // Read and ack data packet + uint32_t idx = (rxf0s & MCAN_RXF0S_F0GI_Msk) >> MCAN_RXF0S_F0GI_Pos; + struct fdcan_fifo *rxf0 = &MSG_RAM.RXF0[idx]; + uint32_t ids = rxf0->id_section; + struct canbus_msg msg; + if (ids & FDCAN_XTD) + msg.id = (ids & 0x1fffffff) | CANMSG_ID_EFF; + else + msg.id = (ids >> 18) & 0x7ff; + msg.id |= ids & FDCAN_RTR ? CANMSG_ID_RTR : 0; + msg.dlc = (rxf0->dlc_section >> 16) & 0x0f; + msg.data32[0] = rxf0->data[0]; + msg.data32[1] = rxf0->data[1]; + barrier(); + CANx->MCAN_RXF0A = idx; + + // Process packet + canbus_process_data(&msg); + } + } + if (ir & FDCAN_IE_TC) { + // Tx + CANx->MCAN_IR = FDCAN_IE_TC; + canbus_notify_tx(); + } +} + +static inline const uint32_t +make_btr(uint32_t sjw, // Sync jump width, ... hmm + uint32_t time_seg1, // time segment before sample point, 1 .. 16 + uint32_t time_seg2, // time segment after sample point, 1 .. 8 + uint32_t brp) // Baud rate prescaler, 1 .. 1024 +{ + return (((uint32_t)(sjw-1)) << MCAN_NBTP_NSJW_Pos + | ((uint32_t)(time_seg1-1)) << MCAN_NBTP_NTSEG1_Pos + | ((uint32_t)(time_seg2-1)) << MCAN_NBTP_NTSEG2_Pos + | ((uint32_t)(brp - 1)) << MCAN_NBTP_NBRP_Pos); +} + +static inline const uint32_t +compute_btr(uint32_t pclock, uint32_t bitrate) +{ + /* + Some equations: + Tpclock = 1 / pclock + Tq = brp * Tpclock + Tbs1 = Tq * TS1 + Tbs2 = Tq * TS2 + NominalBitTime = Tq + Tbs1 + Tbs2 + BaudRate = 1/NominalBitTime + Bit value sample point is after Tq+Tbs1. Ideal sample point + is at 87.5% of NominalBitTime + Use the lowest brp where ts1 and ts2 are in valid range + */ + + uint32_t bit_clocks = pclock / bitrate; // clock ticks per bit + + uint32_t sjw = 2; + uint32_t qs; + // Find number of time quantas that gives us the exact wanted bit time + for (qs = 18; qs > 9; qs--) { + // check that bit_clocks / quantas is an integer + uint32_t brp_rem = bit_clocks % qs; + if (brp_rem == 0) + break; + } + uint32_t brp = bit_clocks / qs; + uint32_t time_seg2 = qs / 8; // sample at ~87.5% + uint32_t time_seg1 = qs - (1 + time_seg2); + + return make_btr(sjw, time_seg1, time_seg2, brp); +} + +void +can_init(void) +{ + if (!CONFIG_USBCANBUS) { + // Configure UPLL for USB and CAN + PMC->CKGR_UCKR = CKGR_UCKR_UPLLCOUNT(3) | CKGR_UCKR_UPLLEN; + while (!(PMC->PMC_SR & PMC_SR_LOCKU)) + ; + } + + // Configure PCK5 for CAN use + PMC->PMC_PCK[5] = PMC_PCK_CSS_UPLL_CLK | PMC_PCK_PRES(5); + while (!(PMC->PMC_SR & PMC_SR_PCKRDY5)) + ; + PMC->PMC_SCER |= PMC_SCER_PCK5; + + enable_pclock(CANx_GCLK_ID); + + gpio_peripheral(GPIO_Rx, CAN_FUNCTION_Rx, 1); + gpio_peripheral(GPIO_Tx, CAN_FUNCTION_Tx, 0); + + uint32_t pclock = get_pclock_frequency(CANx_GCLK_ID); + + uint32_t btr = compute_btr(pclock, CONFIG_CANBUS_FREQUENCY); + + /*##-1- Configure the CAN #######################################*/ + + /* Exit from sleep mode */ + CANx->MCAN_CCCR &= ~MCAN_CCCR_CSR; + /* Wait the acknowledge */ + while (CANx->MCAN_CCCR & MCAN_CCCR_CSA) + ; + /* Request initialization */ + CANx->MCAN_CCCR |= MCAN_CCCR_INIT; + /* Wait the acknowledge */ + while (!(CANx->MCAN_CCCR & MCAN_CCCR_INIT)) + ; + /* Enable configuration change */ + CANx->MCAN_CCCR |= MCAN_CCCR_CCE; + + /* Disable protocol exception handling */ + CANx->MCAN_CCCR |= MCAN_CCCR_PXHD; + + CANx->MCAN_NBTP = btr; + + /* Setup message RAM addresses */ + uint32_t ccfg = (CCFG_CANxDMABA & ~CCFG_CANxDMABA_Msk); + CCFG_CANxDMABA = (ccfg | (((uint32_t)&MSG_RAM) + & CCFG_CANxDMABA_Msk)); + uint32_t f0sa = (uint32_t)MSG_RAM.RXF0 + - (CCFG_CANxDMABA_Msk & CCFG_CANxDMABA); + CANx->MCAN_RXF0C = f0sa + | (ARRAY_SIZE(MSG_RAM.RXF0) << MCAN_RXF0C_F0S_Pos); + CANx->MCAN_RXESC = (7 << MCAN_RXESC_F1DS_Pos) + | (7 << MCAN_RXESC_F0DS_Pos); + uint32_t tbsa = (uint32_t)MSG_RAM.TXFIFO + - (CCFG_CANxDMABA_Msk & CCFG_CANxDMABA); + CANx->MCAN_TXBC = tbsa + | (ARRAY_SIZE(MSG_RAM.TXFIFO) << MCAN_TXBC_TFQS_Pos); + CANx->MCAN_TXESC = 7 << MCAN_TXESC_TBDS_Pos; + + /* Leave the initialisation mode */ + CANx->MCAN_CCCR &= ~MCAN_CCCR_CCE; + CANx->MCAN_CCCR &= ~MCAN_CCCR_INIT; + + /*##-2- Configure the CAN Filter #######################################*/ + canhw_set_filter(0); + + /*##-3- Configure Interrupts #################################*/ + armcm_enable_irq(CAN_IRQHandler, CANx_IRQn, 1); + CANx->MCAN_ILE = MCAN_ILE_EINT0; + CANx->MCAN_IE = MCAN_IE_RF0NE | FDCAN_IE_TC; +} +DECL_INIT(can_init); diff --git a/src/atsam/main.c b/src/atsam/main.c index 8c2e4318d..3728b7349 100644 --- a/src/atsam/main.c +++ b/src/atsam/main.c @@ -14,6 +14,8 @@ #define FREQ_PERIPH_DIV (CONFIG_MACH_SAME70 ? 2 : 1) #define FREQ_PERIPH (CONFIG_CLOCK_FREQ / FREQ_PERIPH_DIV) +#define FREQ_SAME70_CAN 80000000 + /**************************************************************** * watchdog handler ****************************************************************/ @@ -62,6 +64,10 @@ enable_pclock(uint32_t id) uint32_t get_pclock_frequency(uint32_t id) { +#if CONFIG_MACH_SAME70 + if (id == MCAN0_CLOCK_ID || id == MCAN1_CLOCK_ID) + return FREQ_SAME70_CAN; +#endif return FREQ_PERIPH; } diff --git a/src/atsam/same70_sysinit.c b/src/atsam/same70_sysinit.c index d4d98483f..4cb5f48cc 100644 --- a/src/atsam/same70_sysinit.c +++ b/src/atsam/same70_sysinit.c @@ -70,5 +70,8 @@ void SystemInit( void ) // Configure PCK6 for TC use PMC->PMC_PCK[6] = PMC_PCK_CSS_MCK | PMC_PCK_PRES(2); - PMC->PMC_SCER = PMC_SCER_PCK6; + while ( !(PMC->PMC_SR & PMC_SR_PCKRDY6) ) + { + } + PMC->PMC_SCER |= PMC_SCER_PCK6; } From 593486fadc13d3d204762070ae915f3b8968a807 Mon Sep 17 00:00:00 2001 From: "Aaron B. Haun" Date: Sun, 15 Oct 2023 15:27:40 -0400 Subject: [PATCH 44/48] docs: Fix a typo in serial bootloader entry docs Signed-off-by: Aaron B. Haun --- docs/Bootloader_Entry.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Bootloader_Entry.md b/docs/Bootloader_Entry.md index f42602bd7..ec27baad4 100644 --- a/docs/Bootloader_Entry.md +++ b/docs/Bootloader_Entry.md @@ -44,7 +44,7 @@ releasing `Ctrl` If a physical serial port is being used on the MCU (even if a USB serial adapter is being used to connect to it), sending the string -`Request Serial Bootloader!!~`. +`Request Serial Bootloader!!~` requests the bootloader. `` is an ASCII literal space, 0x20. From 0c521b601d11ce236a98b4e557a0ab0a6d136b1a Mon Sep 17 00:00:00 2001 From: Tom Dunn Date: Tue, 10 Oct 2023 23:38:17 -0400 Subject: [PATCH 45/48] Config_checks: Missing ! in pin inversion example in Verify endstops In the "Verify endstops" section there is an example of adding an exclamation point to the pin definition to invert its logic. I believe the intention in the example is to keep the hardware pull-up ^ and follow it with ! to invert the pin logic, but the ! is missing. Signed-off-by: Tom Dunn --- docs/Config_checks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Config_checks.md b/docs/Config_checks.md index ab65678ea..ba4a5015f 100644 --- a/docs/Config_checks.md +++ b/docs/Config_checks.md @@ -67,7 +67,7 @@ The QUERY_ENDSTOPS command should report the endstop as "TRIGGERED". If the endstop appears inverted (it reports "open" when triggered and vice-versa) then add a "!" to the pin definition (for example, -"endstop_pin: ^PA2"), or remove the "!" if there is already one +"endstop_pin: ^!PA2"), or remove the "!" if there is already one present. If the endstop does not change at all then it generally indicates that From 6749985302fe002a9cb5672dab9badb11e4e3c36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viesturs=20Zari=C5=86=C5=A1?= Date: Thu, 19 Oct 2023 19:28:16 +0200 Subject: [PATCH 46/48] toolhead: Use dict for step generation flush times. (#6303) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Makes the API to extruder and input shaper more robust, avoiding the need to track the old delay. Signed-off-by: Viesturs Zariņš --- klippy/extras/input_shaper.py | 5 +---- klippy/kinematics/extruder.py | 6 +----- klippy/toolhead.py | 17 +++++++++-------- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/klippy/extras/input_shaper.py b/klippy/extras/input_shaper.py index 7f37d302b..3edd8478b 100644 --- a/klippy/extras/input_shaper.py +++ b/klippy/extras/input_shaper.py @@ -135,16 +135,13 @@ def _update_input_shaping(self, error=None): is_sk = self._get_input_shaper_stepper_kinematics(s) if is_sk is None: continue - old_delay = ffi_lib.input_shaper_get_step_generation_window(is_sk) for shaper in self.shapers: if shaper in failed_shapers: continue if not shaper.set_shaper_kinematics(is_sk): failed_shapers.append(shaper) new_delay = ffi_lib.input_shaper_get_step_generation_window(is_sk) - if old_delay != new_delay: - self.toolhead.note_step_generation_scan_time(new_delay, - old_delay) + self.toolhead.note_step_generation_scan_time(self, new_delay) if failed_shapers: error = error or self.printer.command_error raise error("Failed to configure shaper(s) %s with given parameters" diff --git a/klippy/kinematics/extruder.py b/klippy/kinematics/extruder.py index ea422b6ec..9c913bf1a 100644 --- a/klippy/kinematics/extruder.py +++ b/klippy/kinematics/extruder.py @@ -70,15 +70,11 @@ def sync_to_extruder(self, extruder_name): self.stepper.set_trapq(extruder.get_trapq()) self.motion_queue = extruder_name def _set_pressure_advance(self, pressure_advance, smooth_time): - old_smooth_time = self.pressure_advance_smooth_time - if not self.pressure_advance: - old_smooth_time = 0. new_smooth_time = smooth_time if not pressure_advance: new_smooth_time = 0. toolhead = self.printer.lookup_object("toolhead") - toolhead.note_step_generation_scan_time(new_smooth_time * .5, - old_delay=old_smooth_time * .5) + toolhead.note_step_generation_scan_time(self, new_smooth_time * .5) ffi_main, ffi_lib = chelper.get_ffi() espa = ffi_lib.extruder_set_pressure_advance espa(self.sk_extruder, pressure_advance, new_smooth_time) diff --git a/klippy/toolhead.py b/klippy/toolhead.py index d8b938257..8334291ef 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -239,7 +239,8 @@ def __init__(self, config): self.drip_completion = None # Kinematic step generation scan window time tracking self.kin_flush_delay = SDS_CHECK_TIME - self.kin_flush_times = [] + # Map from requester to requested time + self.kin_flush_times = {self: SDS_CHECK_TIME} self.force_flush_time = self.last_kin_move_time = 0. # Setup iterative solver ffi_main, ffi_lib = chelper.get_ffi() @@ -526,15 +527,15 @@ def get_trapq(self): return self.trapq def register_step_generator(self, handler): self.step_generators.append(handler) - def note_step_generation_scan_time(self, delay, old_delay=0.): + def note_step_generation_scan_time(self, requester, delay): self.flush_step_generation() - cur_delay = self.kin_flush_delay - if old_delay: - self.kin_flush_times.pop(self.kin_flush_times.index(old_delay)) + if delay == self.kin_flush_times.get(requester, None): + return if delay: - self.kin_flush_times.append(delay) - new_delay = max(self.kin_flush_times + [SDS_CHECK_TIME]) - self.kin_flush_delay = new_delay + self.kin_flush_times[requester] = delay + elif requester in self.kin_flush_times: + del self.kin_flush_times[requester] + self.kin_flush_delay = max(self.kin_flush_times.values()) def register_lookahead_callback(self, callback): last_move = self.move_queue.get_last() if last_move is None: From f7567a0db954eabe4c6b8da3f73ce68693698646 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 21 Oct 2023 15:45:47 -0400 Subject: [PATCH 47/48] Revert "toolhead: Use dict for step generation flush times. (#6303)" This reverts commit 6749985302fe002a9cb5672dab9badb11e4e3c36. A defect was found in the above commit (the input shaper code calls note_step_generateion_scan_time() for many steppers, so the input_shaper class can't be used as the index). Signed-off-by: Kevin O'Connor --- klippy/extras/input_shaper.py | 5 ++++- klippy/kinematics/extruder.py | 6 +++++- klippy/toolhead.py | 17 ++++++++--------- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/klippy/extras/input_shaper.py b/klippy/extras/input_shaper.py index 3edd8478b..7f37d302b 100644 --- a/klippy/extras/input_shaper.py +++ b/klippy/extras/input_shaper.py @@ -135,13 +135,16 @@ def _update_input_shaping(self, error=None): is_sk = self._get_input_shaper_stepper_kinematics(s) if is_sk is None: continue + old_delay = ffi_lib.input_shaper_get_step_generation_window(is_sk) for shaper in self.shapers: if shaper in failed_shapers: continue if not shaper.set_shaper_kinematics(is_sk): failed_shapers.append(shaper) new_delay = ffi_lib.input_shaper_get_step_generation_window(is_sk) - self.toolhead.note_step_generation_scan_time(self, new_delay) + if old_delay != new_delay: + self.toolhead.note_step_generation_scan_time(new_delay, + old_delay) if failed_shapers: error = error or self.printer.command_error raise error("Failed to configure shaper(s) %s with given parameters" diff --git a/klippy/kinematics/extruder.py b/klippy/kinematics/extruder.py index 9c913bf1a..ea422b6ec 100644 --- a/klippy/kinematics/extruder.py +++ b/klippy/kinematics/extruder.py @@ -70,11 +70,15 @@ def sync_to_extruder(self, extruder_name): self.stepper.set_trapq(extruder.get_trapq()) self.motion_queue = extruder_name def _set_pressure_advance(self, pressure_advance, smooth_time): + old_smooth_time = self.pressure_advance_smooth_time + if not self.pressure_advance: + old_smooth_time = 0. new_smooth_time = smooth_time if not pressure_advance: new_smooth_time = 0. toolhead = self.printer.lookup_object("toolhead") - toolhead.note_step_generation_scan_time(self, new_smooth_time * .5) + toolhead.note_step_generation_scan_time(new_smooth_time * .5, + old_delay=old_smooth_time * .5) ffi_main, ffi_lib = chelper.get_ffi() espa = ffi_lib.extruder_set_pressure_advance espa(self.sk_extruder, pressure_advance, new_smooth_time) diff --git a/klippy/toolhead.py b/klippy/toolhead.py index 8334291ef..d8b938257 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -239,8 +239,7 @@ def __init__(self, config): self.drip_completion = None # Kinematic step generation scan window time tracking self.kin_flush_delay = SDS_CHECK_TIME - # Map from requester to requested time - self.kin_flush_times = {self: SDS_CHECK_TIME} + self.kin_flush_times = [] self.force_flush_time = self.last_kin_move_time = 0. # Setup iterative solver ffi_main, ffi_lib = chelper.get_ffi() @@ -527,15 +526,15 @@ def get_trapq(self): return self.trapq def register_step_generator(self, handler): self.step_generators.append(handler) - def note_step_generation_scan_time(self, requester, delay): + def note_step_generation_scan_time(self, delay, old_delay=0.): self.flush_step_generation() - if delay == self.kin_flush_times.get(requester, None): - return + cur_delay = self.kin_flush_delay + if old_delay: + self.kin_flush_times.pop(self.kin_flush_times.index(old_delay)) if delay: - self.kin_flush_times[requester] = delay - elif requester in self.kin_flush_times: - del self.kin_flush_times[requester] - self.kin_flush_delay = max(self.kin_flush_times.values()) + self.kin_flush_times.append(delay) + new_delay = max(self.kin_flush_times + [SDS_CHECK_TIME]) + self.kin_flush_delay = new_delay def register_lookahead_callback(self, callback): last_move = self.move_queue.get_last() if last_move is None: From e7893db70fc9e416807e452d2d5f999a20241844 Mon Sep 17 00:00:00 2001 From: Rogerio Goncalves Date: Wed, 18 Oct 2023 22:32:03 +0100 Subject: [PATCH 48/48] disable displays for stm32f042 testing --- test/configs/stm32f042.config | 1 + 1 file changed, 1 insertion(+) diff --git a/test/configs/stm32f042.config b/test/configs/stm32f042.config index 7f1e879fb..216508b65 100644 --- a/test/configs/stm32f042.config +++ b/test/configs/stm32f042.config @@ -3,3 +3,4 @@ CONFIG_MACH_STM32=y CONFIG_MACH_STM32F042=y CONFIG_WANT_SOFTWARE_I2C=n CONFIG_WANT_LIS2DW=n +CONFIG_WANT_DISPLAYS=n