diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index b0a5439a..996dfcaa 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1,2 @@ -patreon: koconnor +ko_fi: koconnor +custom: https://www.klipper3d.org/Sponsors.html#klipper-developers diff --git a/.github/workflows/klipper3d-deploy.yaml b/.github/workflows/klipper3d-deploy.yaml index ba6c157b..2e6e2c9d 100644 --- a/.github/workflows/klipper3d-deploy.yaml +++ b/.github/workflows/klipper3d-deploy.yaml @@ -7,7 +7,6 @@ on: - master paths: - docs/** - - mkdocs.yml - .github/workflows/klipper3d-deploy.yaml jobs: deploy: diff --git a/COPYING b/COPYING index 94a9ed02..f288702d 100644 --- a/COPYING +++ b/COPYING @@ -1,7 +1,7 @@ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 - Copyright (C) 2007 Free Software Foundation, Inc. + Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -645,7 +645,7 @@ the "copyright" line and a pointer to where the full notice is found. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program. If not, see . + along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. @@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see -. +. The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read -. +. diff --git a/Makefile b/Makefile index a3d6215a..10615726 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ cc-option=$(shell if test -z "`$(1) $(2) -S -o /dev/null -xc /dev/null 2>&1`" \ CFLAGS := -I$(OUT) -Isrc -I$(OUT)board-generic/ -std=gnu11 -O2 -MD \ -Wall -Wold-style-definition $(call cc-option,$(CC),-Wtype-limits,) \ - -ffunction-sections -fdata-sections + -ffunction-sections -fdata-sections -fno-delete-null-pointer-checks CFLAGS += -flto -fwhole-program -fno-use-linker-plugin -ggdb3 OBJS_klipper.elf = $(patsubst %.c, $(OUT)src/%.o,$(src-y)) diff --git a/README.md b/README.md index b4c7a7b4..32fe7331 100644 --- a/README.md +++ b/README.md @@ -13,4 +13,6 @@ To begin using Klipper start by [installing](https://www.klipper3d.org/Installation.html) it. Klipper is Free Software. See the [license](COPYING) or read the -[documentation](https://www.klipper3d.org/Overview.html). +[documentation](https://www.klipper3d.org/Overview.html). We depend on +the generous support from our +[sponsors](https://www.klipper3d.org/Sponsors.html). diff --git a/config/generic-archim2.cfg b/config/generic-archim2.cfg index 8b159b14..a517909b 100644 --- a/config/generic-archim2.cfg +++ b/config/generic-archim2.cfg @@ -129,7 +129,7 @@ max_temp: 130 [fan] pin: PC26 -[heater_fan nozzle_cooling_fan] +[heater_fan heatbreak_cooling_fan] pin: PC25 [mcu] diff --git a/config/generic-bigtreetech-e3-rrf-v1.1.cfg b/config/generic-bigtreetech-e3-rrf-v1.1.cfg index 557009ee..b0153cba 100644 --- a/config/generic-bigtreetech-e3-rrf-v1.1.cfg +++ b/config/generic-bigtreetech-e3-rrf-v1.1.cfg @@ -90,7 +90,7 @@ pid_Kd: 948.182 min_temp: 0 max_temp: 130 -[heater_fan nozzle_cooling_fan] +[heater_fan heatbreak_cooling_fan] pin: PB6 [fan] diff --git a/config/generic-bigtreetech-skr-3.cfg b/config/generic-bigtreetech-skr-3.cfg new file mode 100644 index 00000000..c769ae03 --- /dev/null +++ b/config/generic-bigtreetech-skr-3.cfg @@ -0,0 +1,183 @@ +# This file contains common pin mappings for the BigTreeTech SKR 3. +# To use this config, during "make menuconfig" enable "low-level +# options", "STM32H743", "128KiB bootloader", and "25MHz clock". + +# See docs/Config_Reference.md for a description of parameters. + +[stepper_x] +step_pin: PD4 +dir_pin: PD3 +enable_pin: !PD6 +microsteps: 16 +rotation_distance: 40 +endstop_pin: ^PC1 +position_endstop: 0 +position_max: 200 +homing_speed: 50 + +[stepper_y] +step_pin: PA15 +dir_pin: !PA8 +enable_pin: !PD1 +microsteps: 16 +rotation_distance: 40 +endstop_pin: ^PC3 +position_endstop: 0 +position_max: 200 +homing_speed: 50 + +[stepper_z] +step_pin: PE2 +dir_pin: PE3 +enable_pin: !PE0 +microsteps: 16 +rotation_distance: 40 +endstop_pin: ^PC0 +position_endstop: 0.5 +position_max: 200 + +[extruder] +step_pin: PD15 +dir_pin: PD14 +enable_pin: !PC7 +microsteps: 16 +rotation_distance: 33.500 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PB3 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PA2 +control: pid +pid_Kp: 22.2 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 250 + +#[extruder1] +#step_pin: PD11 +#dir_pin: PD10 +#enable_pin: !PD13 +#heater_pin: PB4 +#sensor_pin: PA3 +#... + +[heater_bed] +heater_pin: PD7 +sensor_type: Generic 3950 +sensor_pin: PA1 +control: watermark +min_temp: 0 +max_temp: 130 + +[fan] +pin: PB7 + +#[heater_fan fan1] +#pin: PB6 + +#[heater_fan fan2] +#pin: PB5 + +[mcu] +serial: /dev/serial/by-id/usb-Klipper_Klipper_firmware_12345-if00 + +[printer] +kinematics: cartesian +max_velocity: 300 +max_accel: 3000 +max_z_velocity: 5 +max_z_accel: 100 + +######################################## +# EXP1 / EXP2 (display) pins +######################################## + +[board_pins] +aliases: + # EXP1 header + EXP1_1=PC5, EXP1_3=PB1, EXP1_5=PE9, EXP1_7=PE11, EXP1_9=, + EXP1_2=PB0, EXP1_4=PE8, EXP1_6=PE10, EXP1_8=PE12, EXP1_10=<5V>, + # EXP2 header + EXP2_1=PA6, EXP2_3=PE7, EXP2_5=PB2, EXP2_7=PC4, EXP2_9=, + EXP2_2=PA5, EXP2_4=PA4, EXP2_6=PA7, EXP2_8=, EXP2_10= + +# See the sample-lcd.cfg file for definitions of common LCD displays. + +######################################## +# TMC2209 configuration +######################################## + +#[tmc2209 stepper_x] +#uart_pin: PD5 +#run_current: 0.800 +#diag_pin: + +#[tmc2209 stepper_y] +#uart_pin: PD0 +#run_current: 0.800 +#diag_pin: + +#[tmc2209 stepper_z] +#uart_pin: PE1 +#run_current: 0.800 +#diag_pin: + +#[tmc2209 extruder] +#uart_pin: PC6 +#run_current: 0.600 +#diag_pin: + +#[tmc2209 extruder1] +#uart_pin: PD12 +#run_current: 0.600 +#diag_pin: + +######################################## +# TMC2130 configuration +######################################## + +#[tmc2130 stepper_x] +#cs_pin: PD5 +#spi_software_miso_pin: PE15 +#spi_software_mosi_pin: PE13 +#spi_software_sclk_pin: PE14 +#run_current: 0.800 +#stealthchop_threshold: 999999 +#diag1_pin: PC1 + +#[tmc2130 stepper_y] +#cs_pin: PD0 +#spi_software_miso_pin: PE15 +#spi_software_mosi_pin: PE13 +#spi_software_sclk_pin: PE14 +#run_current: 0.800 +#stealthchop_threshold: 999999 +#diag1_pin: PC3 + +#[tmc2130 stepper_z] +#cs_pin: PE1 +#spi_software_miso_pin: PE15 +#spi_software_mosi_pin: PE13 +#spi_software_sclk_pin: PE14 +#run_current: 0.650 +#stealthchop_threshold: 999999 +#diag1_pin: PC0 + +#[tmc2130 extruder] +#cs_pin: PC6 +#spi_software_miso_pin: PE15 +#spi_software_mosi_pin: PE13 +#spi_software_sclk_pin: PE14 +#run_current: 0.800 +#stealthchop_threshold: 999999 +#diag1_pin: PC2 + +#[tmc2130 extruder1] +#cs_pin: PD12 +#spi_software_miso_pin: PE15 +#spi_software_mosi_pin: PE13 +#spi_software_sclk_pin: PE14 +#run_current: 0.800 +#stealthchop_threshold: 999999 +#diag1_pin: PA0 diff --git a/config/generic-bigtreetech-skr-e3-turbo.cfg b/config/generic-bigtreetech-skr-e3-turbo.cfg index 1974afdb..4784a654 100644 --- a/config/generic-bigtreetech-skr-e3-turbo.cfg +++ b/config/generic-bigtreetech-skr-e3-turbo.cfg @@ -105,7 +105,7 @@ max_temp: 130 [fan] pin: P2.1 -[heater_fan nozzle_cooling_fan] +[heater_fan heatbreak_cooling_fan] pin: P2.2 [mcu] diff --git a/config/generic-bigtreetech-skr-mini-e3-v2.0.cfg b/config/generic-bigtreetech-skr-mini-e3-v2.0.cfg index 96605da1..eeacae33 100644 --- a/config/generic-bigtreetech-skr-mini-e3-v2.0.cfg +++ b/config/generic-bigtreetech-skr-mini-e3-v2.0.cfg @@ -100,7 +100,7 @@ pid_Kd: 948.182 min_temp: 0 max_temp: 130 -[heater_fan nozzle_cooling_fan] +[heater_fan heatbreak_cooling_fan] pin: PC7 [fan] diff --git a/config/generic-bigtreetech-skr-mini-e3-v3.0.cfg b/config/generic-bigtreetech-skr-mini-e3-v3.0.cfg index ed37afbd..2ea064b0 100644 --- a/config/generic-bigtreetech-skr-mini-e3-v3.0.cfg +++ b/config/generic-bigtreetech-skr-mini-e3-v3.0.cfg @@ -98,7 +98,7 @@ pid_Kd: 948.182 min_temp: 0 max_temp: 130 -[heater_fan nozzle_cooling_fan] +[heater_fan heatbreak_cooling_fan] pin: PC7 [heater_fan controller_fan] diff --git a/config/generic-bigtreetech-skr-mini-mz.cfg b/config/generic-bigtreetech-skr-mini-mz.cfg index 15f32864..92afc779 100644 --- a/config/generic-bigtreetech-skr-mini-mz.cfg +++ b/config/generic-bigtreetech-skr-mini-mz.cfg @@ -103,7 +103,7 @@ pid_Kd: 948.182 min_temp: 0 max_temp: 130 -[heater_fan nozzle_cooling_fan] +[heater_fan heatbreak_cooling_fan] pin: PC7 [fan] diff --git a/config/generic-bigtreetech-skr-pico-v1.0.cfg b/config/generic-bigtreetech-skr-pico-v1.0.cfg index 83f89d65..06a0c9f7 100644 --- a/config/generic-bigtreetech-skr-pico-v1.0.cfg +++ b/config/generic-bigtreetech-skr-pico-v1.0.cfg @@ -100,7 +100,7 @@ max_temp: 130 [fan] pin: gpio17 -[heater_fan nozzle_cooling_fan] +[heater_fan heatbreak_cooling_fan] pin: gpio18 [heater_fan controller_fan] diff --git a/config/generic-duet2-duex.cfg b/config/generic-duet2-duex.cfg index 38903ed2..e6357713 100644 --- a/config/generic-duet2-duex.cfg +++ b/config/generic-duet2-duex.cfg @@ -288,7 +288,7 @@ max_temp: 130 pin: PC23 # Fan1 controlled by extruder -[heater_fan nozzle_cooling_fan] +[heater_fan heatbreak_cooling_fan] pin: PC26 heater: extruder heater_temp: 45 diff --git a/config/generic-duet2-maestro.cfg b/config/generic-duet2-maestro.cfg index 82e57f72..c3618d78 100644 --- a/config/generic-duet2-maestro.cfg +++ b/config/generic-duet2-maestro.cfg @@ -123,7 +123,7 @@ max_temp: 130 [fan] pin: PC23 # FAN0 -#[heater_fan nozzle_cooling_fan] +#[heater_fan heatbreak_cooling_fan] #pin: PC22 # FAN1 #[heater_fan board_cooling_fan] diff --git a/config/generic-duet2.cfg b/config/generic-duet2.cfg index 79d3b324..fba45186 100644 --- a/config/generic-duet2.cfg +++ b/config/generic-duet2.cfg @@ -101,7 +101,7 @@ max_temp: 130 [fan] pin: PC23 # FAN0 -#[heater_fan nozzle_cooling_fan] +#[heater_fan heatbreak_cooling_fan] #pin: PC26 # FAN1 #[heater_fan board_cooling_fan] diff --git a/config/generic-duet3-mini.cfg b/config/generic-duet3-mini.cfg index 99e1af3d..452da742 100644 --- a/config/generic-duet3-mini.cfg +++ b/config/generic-duet3-mini.cfg @@ -26,6 +26,7 @@ # SBC SPISS pin:PA6, SBCTfrReady:PA3, SerComPins:{PA4, PA5, PA6, PA7} # CAN Pins - TX:PB14 RX:PB15 # Heaters, Fan outputs - {Out0:PB17 Out1:PC10 Out2:PB13 Out3:PB11 Out4:PA11, Out5:PB2, Out6:PB1} | Out6 is shared with VFD_Out +# Tach Pins for Fans - {Out3.Tach:PB27 Out4.Tach:PB26} # GPIO_out - {IO1:PB31 IO2:PD9 IO3:PB12 IO4:PD10} IO4 is shared with PSON # GPIO_in - {IO1:PB30 IO2:PD8 IO3:PB7 IO4:PC5 IO5:PC4 IO6:PC31} # Driver Diag - {D0:PA10, D1:PB8, D2:PA22, D3:PA23, D4:PC21, D5:PB10, D6:PA27} diff --git a/config/generic-einsy-rambo.cfg b/config/generic-einsy-rambo.cfg index 9015ae67..d532b8bf 100644 --- a/config/generic-einsy-rambo.cfg +++ b/config/generic-einsy-rambo.cfg @@ -95,7 +95,7 @@ max_temp: 130 [fan] pin: PH5 -#[heater_fan nozzle_cooling_fan] +#[heater_fan heatbreak_cooling_fan] #pin: PH3 [temperature_sensor board_sensor] diff --git a/config/generic-fysetc-cheetah-v1.2.cfg b/config/generic-fysetc-cheetah-v1.2.cfg index 1474b837..d2e3fb95 100644 --- a/config/generic-fysetc-cheetah-v1.2.cfg +++ b/config/generic-fysetc-cheetah-v1.2.cfg @@ -97,7 +97,7 @@ max_temp: 130 [fan] pin: PC8 -[heater_fan nozzle_cooling_fan] +[heater_fan heatbreak_cooling_fan] pin: PB0 [mcu] diff --git a/config/generic-mini-rambo.cfg b/config/generic-mini-rambo.cfg index 21c999ac..61e2ac84 100644 --- a/config/generic-mini-rambo.cfg +++ b/config/generic-mini-rambo.cfg @@ -65,7 +65,7 @@ max_temp: 130 [fan] pin: PH5 -#[heater_fan nozzle_cooling_fan] +#[heater_fan heatbreak_cooling_fan] #pin: PH3 [mcu] diff --git a/config/generic-printrboard-g2.cfg b/config/generic-printrboard-g2.cfg index 356d93f6..ffbe80cd 100644 --- a/config/generic-printrboard-g2.cfg +++ b/config/generic-printrboard-g2.cfg @@ -104,7 +104,7 @@ max_temp: 290 [fan] pin: PB27 -[heater_fan nozzle_cooling_fan] +[heater_fan heatbreak_cooling_fan] pin: PA6 [mcu] diff --git a/config/generic-radds.cfg b/config/generic-radds.cfg index f3cee435..f07f3cb4 100644 --- a/config/generic-radds.cfg +++ b/config/generic-radds.cfg @@ -81,7 +81,7 @@ max_temp: 130 [fan] pin: PC21 -#[heater_fan nozzle_cooling_fan] +#[heater_fan heatbreak_cooling_fan] #pin: PC22 [mcu] diff --git a/config/generic-rambo.cfg b/config/generic-rambo.cfg index 1686da25..8eb04c1f 100644 --- a/config/generic-rambo.cfg +++ b/config/generic-rambo.cfg @@ -75,7 +75,7 @@ max_temp: 130 [fan] pin: PH5 -#[heater_fan nozzle_cooling_fan] +#[heater_fan heatbreak_cooling_fan] #pin: PH3 [mcu] diff --git a/config/generic-th3d-ezboard-lite-v2.0.cfg b/config/generic-th3d-ezboard-lite-v2.0.cfg index 66a0863d..70e553e4 100644 --- a/config/generic-th3d-ezboard-lite-v2.0.cfg +++ b/config/generic-th3d-ezboard-lite-v2.0.cfg @@ -1,6 +1,6 @@ # This file contains common pin mappings for the TH3D EZBoard Lite v2. # To use this config, the firmware should be compiled for the -# STM32F407 with 12mhz Crystal, 48KiB Bootloader, and USB communication. +# STM32F405 with 12mhz Crystal, 48KiB Bootloader, and USB communication. # The "make flash" command does not work on this board. Instead, # after running "make", copy the generated "out/klipper.bin" file to a diff --git a/config/printer-anycubic-4maxpro-2.0-2021.cfg b/config/printer-anycubic-4maxpro-2.0-2021.cfg index 75d32411..e0c83b6a 100644 --- a/config/printer-anycubic-4maxpro-2.0-2021.cfg +++ b/config/printer-anycubic-4maxpro-2.0-2021.cfg @@ -11,7 +11,7 @@ # For Anycubic 4Max Pro (not 2.0) owners: # Be careful when using this config! This config tested only on Anycubic -# 4Max Pro 2.0 with klipper v0.9.1-667-g31761500! At first, you should +# 4Max Pro 2.0! At first, you should # set homing_speed on 5, and run homing and click on the endstops with # your fingers. It is necessary to make sure that all the motors are # spinning in the right direction, all the temperature sensors show the @@ -139,3 +139,50 @@ screw4: 265, 5 [filament_switch_sensor filament_sensor] switch_pin: ^!PC4 + +[output_pin buzz] +pin: PC6 +pwm: True + +[output_pin AUTO_POWEROFF] +pin: PD0 +pwm: True +cycle_time: 0.02 +value: 1 + + +# This macro (M300) uses internal integrated beeper +# Just use it in your G-code for making sounds. Example: M300 S1000 P500 +[gcode_macro M300] +gcode: + {% set S = params.S|default(800)|float %} + {% set P = params.P|default(100)|int %} + SET_PIN PIN=buzz VALUE=0.5 CYCLE_TIME={ 1.0 / S | float } + G4 P{P} + SET_PIN PIN=buzz VALUE=0 + +# This macro (M81) uses internal integrated PSU control-relay. +# Just use M81 in your end_gcode if you want to poweroff your printer after print. +# Note: as in original Marlin firmware, before powerdown, printer will be cool hotend +# until temperature will be below 45°С / 113°F. + +[gcode_macro M81] +gcode: + {% set required_extruder_temp = params.T|default(45)|int %} + {% if printer.extruder.temperature > required_extruder_temp|default(45)|int %} + M300 + M300 + M300 + M117 COOLING DOWN BEFORE POWER OFF + M109 S{required_extruder_temp} + SET_PIN PIN=AUTO_POWEROFF VALUE=0.5 + G4 P60 + SET_PIN PIN=AUTO_POWEROFF VALUE=1 + {% else %} + M300 + M117 POWER OFF SOON + G4 P10000 + SET_PIN PIN=AUTO_POWEROFF VALUE=0.5 + G4 P60 + SET_PIN PIN=AUTO_POWEROFF VALUE=1 + {% endif %} diff --git a/config/printer-biqu-b1-se-plus-2022.cfg b/config/printer-biqu-b1-se-plus-2022.cfg new file mode 100644 index 00000000..db7238c9 --- /dev/null +++ b/config/printer-biqu-b1-se-plus-2022.cfg @@ -0,0 +1,194 @@ +# This file contains common pin mappings for the Biqu B1 SE Plus. +# To use this config, the firmware should be compiled for the +# STM32F407 with a "32KiB bootloader". + +# In newer versions of this board shipped in late 2021 the STM32F429 +# is used, if this is the case compile for this with a "32KiB bootloader" +# You will need to check the chip on your board to identify which you have. +# +# The "make flash" command does not work on the SKR 2. Instead, +# after running "make", copy the generated "out/klipper.bin" file to a +# file named "firmware.bin" on an SD card and then restart the SKR 2 +# with that SD card. + +# See docs/Config_Reference.md for a description of parameters. + +[mcu] +serial: /dev/serial/by-id/usb-Klipper_stm32f407xx_1D0039000F47393438343535-if00 + +######################################## +# Stepper X Pins and TMC2208 configuration +######################################## +[stepper_x] +step_pin: PE2 +dir_pin: !PE1 +enable_pin: !PE3 +microsteps: 16 +rotation_distance: 40 +endstop_pin: !PC1 +position_endstop: 0 +position_max: 310 +homing_speed: 50 + +[tmc2208 stepper_x] +uart_pin: PE0 +run_current: 0.800 +stealthchop_threshold: 999999 + +######################################## +# Stepper Y Pins and TMC2208 configuration +######################################## +[stepper_y] +step_pin: PD5 +dir_pin: PD4 +enable_pin: !PD6 +microsteps: 16 +rotation_distance: 40 +endstop_pin: !PC3 +position_endstop: 0 +position_max: 310 +homing_speed: 50 + +[tmc2208 stepper_y] +uart_pin: PD3 +run_current: 0.800 +stealthchop_threshold: 999999 + +######################################## +# Stepper Z Pins and TMC2208 configuration +######################################## +[stepper_z] +step_pin: PA15 +dir_pin: PA8 +enable_pin: !PD1 +microsteps: 16 +rotation_distance: 8 +endstop_pin: probe:z_virtual_endstop +homing_speed: 10 +second_homing_speed: 1 +position_min: -2 +position_max: 340 + +[tmc2208 stepper_z] +uart_pin: PD0 +run_current: 0.800 +stealthchop_threshold: 999999 + +######################################## +# Extruder Pins and TMC2208 configuration +######################################## +[extruder] +step_pin: PD15 +dir_pin: !PD14 +enable_pin: !PC7 +microsteps: 16 +rotation_distance: 34.2152 # Calibrar - ver https://www.klipper3d.org/Rotation_Distance.html +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PB3 +sensor_type: Generic 3950 +sensor_pin: PA2 #thermistor pin +control: pid +pid_Kp: 22.2 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 250 + +[tmc2208 extruder] +uart_pin: PC6 +run_current: 0.800 +stealthchop_threshold: 999999 + +######################################## +# Heater Bed Pins +######################################## +[heater_bed] +heater_pin: PD7 +sensor_type: Generic 3950 +sensor_pin: PA1 +control: pid +pid_Kp: 54 +pid_Ki: 0.77 +pid_Kd: 900 +min_temp: 0 +max_temp: 110 + +######################################## +# Printer Configuration +######################################## +[printer] +kinematics: cartesian +max_velocity: 200 +max_accel: 1000 +max_z_velocity: 5 +max_z_accel: 100 + +######################################## +# Probe configuration +######################################## +[probe] +pin: ^!PE4 +z_offset: 0.0 +x_offset: 0.0 +y_offset: 0.0 +speed: 10.0 +samples: 2 +samples_result: average +sample_retract_dist: 2.0 +samples_tolerance: 0.2 + +[safe_z_home] +home_xy_position: 155,155 +speed: 100 +z_hop: 5 +z_hop_speed: 5 + +[output_pin probe_enable] +pin: PE5 +value: 1 + +######################################## +# Bed Mesh configuration +######################################## +[bed_mesh] +speed: 2000 +horizontal_move_z: 3 +mesh_min: 20, 20 +mesh_max: 290, 290 +probe_count: 7, 7 +mesh_pps: 2,2 +algorithm: bicubic +bicubic_tension: 0.2 + +######################################## +# Fan Nozzle configuration +######################################## +[fan] +pin: PB7 + +[heater_fan Cooling_fan] +pin: PB6 +max_power: 1.0 +kick_start_time: 0.100 +heater: heater_bed + +[heater_fan Board_fan] +pin: PB5 +max_power: 1.0 +kick_start_time: 0.100 +heater: extruder + +######################################## +# Filament Sensor configuration +######################################## +[filament_switch_sensor Sensor_Filamento] +switch_pin: !PC2 +pause_on_runout: true #pause handled by macro + +######################################## +# Motor Power Pin +######################################## +[output_pin motor_power] +pin: PC13 +value: 1 diff --git a/config/printer-bq-hephestos-2014.cfg b/config/printer-bq-hephestos-2014.cfg new file mode 100644 index 00000000..ce1c4298 --- /dev/null +++ b/config/printer-bq-hephestos-2014.cfg @@ -0,0 +1,107 @@ +# This file contains pin mappings for the BQ Prusa i3 Hephestos from 2014 +# (https://www.reprap.org/wiki/Prusa_i3_Hephestos) +# It was sold in kit form, and uses a RAMPS board with HD44780 display without +# heated bed or any modern amenities. + +# To use this config, the firmware should be compiled for the AVR atmega2560. + +# See docs/Config_Reference.md for a description of parameters. + +[display] +lcd_type: hd44780 +rs_pin: PH1 +e_pin: PH0 +d4_pin: PA1 +d5_pin: PA3 +d6_pin: PA5 +d7_pin: PA7 +encoder_pins: ^PC4, ^PC6 +click_pin: ^!PC2 +kill_pin: ^!PG0 + +[stepper_x] +step_pin: PF0 +dir_pin: !PF1 +enable_pin: !PD7 +microsteps: 16 +rotation_distance: 40 +endstop_pin: ^!PE5 +position_endstop: 0 +position_max: 215 +homing_speed: 50 + +[stepper_y] +step_pin: PF6 +dir_pin: PF7 +enable_pin: !PF2 +microsteps: 16 +rotation_distance: 40 +endstop_pin: ^!PJ1 +position_endstop: 0 +position_max: 210 +homing_speed: 50 + +[stepper_z] +step_pin: PL3 +dir_pin: !PL1 +enable_pin: !PK0 +microsteps: 16 +rotation_distance: 0.8 +endstop_pin: ^!PD3 +position_endstop: 0 +position_max: 200 +homing_speed: 3 + +[extruder] +step_pin: PA4 +dir_pin: PA6 +enable_pin: !PA2 +microsteps: 16 +# measured extruding 100mm of filament with stock Hephestos extruder +rotation_distance: 31.825 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PB4 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PK5 +min_temp: 0 +max_temp: 250 +control: pid +pid_kp: 19.462 +pid_ki: 0.713 +pid_kd: 132.830 + + +# 5 points for manual bed leveling that still leave room for accessing the stock screws +[bed_screws] +screw1: 40, 40 +screw2: 180, 40 +screw3: 180, 160 +screw4: 40, 160 +screw5: 110, 100 + +[fan] +pin: PH6 + +[printer] +kinematics: cartesian +max_velocity: 300 +max_accel: 3000 +# Must limit Z velocity, since RAMPS does not have enough timer resolution +max_z_velocity: 3 +max_z_accel: 100 + +[mcu] +serial: /dev/ttyUSB0 + +# Common EXP1 / EXP2 (display) pins +[board_pins] +aliases: + # Common EXP1 header found on many "all-in-one" ramps clones + EXP1_1=PC0, EXP1_3=PH0, EXP1_5=PA1, EXP1_7=PA5, EXP1_9=, + EXP1_2=PC2, EXP1_4=PH1, EXP1_6=PA3, EXP1_8=PA7, EXP1_10=<5V>, + # EXP2 header + EXP2_1=PB3, EXP2_3=PC6, EXP2_5=PC4, EXP2_7=PL0, EXP2_9=, + EXP2_2=PB1, EXP2_4=PB0, EXP2_6=PB2, EXP2_8=PG0, EXP2_10= + # Pins EXP2_1, EXP2_6, EXP2_2 are also MISO, MOSI, SCK of bus "spi" + # Note, some boards wire: EXP2_8=, EXP2_10=PG0 diff --git a/config/printer-creality-cr10-v3-2020.cfg b/config/printer-creality-cr10-v3-2020.cfg new file mode 100644 index 00000000..9bf84df5 --- /dev/null +++ b/config/printer-creality-cr10-v3-2020.cfg @@ -0,0 +1,162 @@ +# This file contains common pin mappings for the 2020 Creality CR-10 +# V3. The mainboard is a Creality 3D v2.5.2 (8-bit mainboard with +# ATMega2560). To use this config, the firmware should be compiled for +# the AVR atmega2560. + +# See docs/Config_Reference.md for a description of parameters. + +# For better compatibility with GCodes generated for Marlin, you +# may wish to add the following section, if you have BLTouch: +#[gcode_macro G29] +#gcode: +# BED_MESH_CALIBRATE + +[stepper_x] +step_pin: PF0 #ar54 +dir_pin: PF1 #ar55 +enable_pin: !PD7 #!ar38 +microsteps: 16 +rotation_distance: 40 +endstop_pin: ^PE5 #^ar3 +position_endstop: 0 +position_max: 300 +homing_speed: 50 + +[stepper_y] +step_pin: PF6 #ar60 +dir_pin: PF7 #ar61 +enable_pin: !PF2 #!ar56 +microsteps: 16 +rotation_distance: 40 +endstop_pin: ^PJ1 #^ar14 +position_endstop: 0 +position_max: 300 +homing_speed: 50 + +[stepper_z] +step_pin: PL3 #ar46 +dir_pin: !PL1 #!ar48 +enable_pin: !PK0 #!ar62 +microsteps: 16 +rotation_distance: 8 +position_max: 400 +#Uncomment if you have a BL-Touch: +#position_min: -4 +#endstop_pin: probe:z_virtual_endstop +#and comment the follwing lines: +position_endstop: 0.0 +endstop_pin: ^PD3 #ar18 + +[safe_z_home] +home_xy_position: 104.25,147.6 +speed: 80 +z_hop: 10 +z_hop_speed: 10 + +[extruder] +step_pin: PA4 # ar26 +dir_pin: !PA6 # !ar28 +enable_pin: !PA2 # !ar24 +microsteps: 16 +rotation_distance: 7.7201944 # 16 microsteps * 200 steps/rotation / steps/mm +#Correction formula is new_rotation_distance = old_rotation_distance * mmsExtracted / 100.0 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PB4 #ar10 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PK5 #analog13 +control: pid +pid_kp: 22.107 +pid_ki: 1.170 +pid_kd: 104.458 +min_temp: 0 +max_temp: 255 + +[heater_bed] +heater_pin: PH5 #ar8 +sensor_type: ATC Semitec 104GT-2 +sensor_pin: PK6 #analog14 +control: pid +#Stock PID configuration taken from Marlin +pid_Kp: 201.86 +pid_Ki: 10.67 +pid_Kd: 954.96 +min_temp: 0 +max_temp: 130 + +[fan] +pin: PH6 #ar9 + +[mcu] +serial: /dev/ttyUSB0 + +[printer] +kinematics: cartesian +max_velocity: 300 +max_accel: 3000 +max_z_velocity: 5 +max_z_accel: 100 + +[display] +lcd_type: st7920 +cs_pin: PH1 #ar16 +sclk_pin: PA1 #ar23 +sid_pin: PH0 #ar17 +encoder_pins: ^PC4, ^PC6 #^ar33, ^ar31 +click_pin: ^!PC2 #^!ar35 + + +#Uncomment the following lines if you have a BL-Touch +#[bltouch] +#sensor_pin: ^PD2 #^ar19 +#control_pin: PB5 #ar11 +#set_output_mode: 5V +#pin_move_time: 0.4 +#stow_on_each_sample: False +#probe_with_touch_mode: False +#x_offset: 45.75 +#y_offset: -3.40 +#z_offset: 3.28 +#samples: 2 +#sample_retract_dist: 2 +#samples_result: average + +#Uncomment the following lines if you have a BL-Touch +#[bed_mesh] +#speed: 50 +#horizontal_move_z: 6 +#mesh_min: 46.50,0.75 +#mesh_max: 253.5,295.85 +#probe_count: 7,7 +#algorithm: bicubic + +[pause_resume] +recover_velocity: 50 + +[filament_switch_sensor fil_runout_sensor] +pause_on_runout: True +switch_pin: PE4 #ar2 + +[bed_screws] +screw1: 33,29 +screw1_name: front left screw +screw2: 273,29 +screw2_name: front right screw +screw3: 273,269 +screw3_name: rear right screw +screw4: 33,269 +screw4_name: rear left screw + +#Uncomment the following lines if you have a BL-Touch +#[screws_tilt_adjust] +#screw1: 0,29 +#screw1_name: front left screw +#screw2: 228,29 +#screw2_name: front right screw +#screw3: 228,269 +#screw3_name: rear right screw +#screw4: 0,269 +#screw4_name: rear left screw +#speed: 50 +#horizontal_move_z: 10 +#screw_thread: CW-M3 diff --git a/config/printer-creality-ender3-s1-2021.cfg b/config/printer-creality-ender3-s1-2021.cfg new file mode 100644 index 00000000..bda56e75 --- /dev/null +++ b/config/printer-creality-ender3-s1-2021.cfg @@ -0,0 +1,131 @@ +# This file contains pin mappings for the stock 2021 Creality Ender 3 +# S1 (and S1 pro). To use this config, check the STM32 Chip on the +# V2.4S1 Board then during "make menuconfig" select either the +# STM32F103 with a "28KiB bootloader" or select the STM32F401 with a +# "64KiB bootloader" and serial (on USART1 PA10/PA9) communication for +# both depending on the STM32 chip installed on your printer's +# motherboard. + +# If you prefer a direct serial connection, in "make menuconfig" +# select "Enable extra low-level configuration options" and select +# Serial (on USART2 PA3/PA2), which is broken out on the 10 pin IDC +# cable used for the LCD module as follows: +# 3: Tx, 4: Rx, 9: GND, 10: VCC + +# Flash this firmware by copying "out/klipper.bin" to a SD card and +# turning on the printer with the card inserted. The firmware +# filename must changed to "firmware.bin" + +# See docs/Config_Reference.md for a description of parameters. + +[stepper_x] +step_pin: PC2 +dir_pin: PB9 +enable_pin: !PC3 +microsteps: 16 +rotation_distance: 40 +endstop_pin: !PA5 +position_endstop: -10 +position_max: 235 +position_min: -15 +homing_speed: 50 + +[stepper_y] +step_pin: PB8 +dir_pin: PB7 +enable_pin: !PC3 +microsteps: 16 +rotation_distance: 40 +endstop_pin: !PA6 +position_endstop: -10 +position_max: 241 +position_min: -15 +homing_speed: 50 + +[stepper_z] +step_pin: PB6 +dir_pin: !PB5 +enable_pin: !PC3 +microsteps: 16 +rotation_distance: 8 +endstop_pin: probe:z_virtual_endstop +position_max: 270 +position_min: -4 + +[extruder] +step_pin: PB4 +dir_pin: PB3 +enable_pin: !PC3 +microsteps: 16 +gear_ratio: 42:12 +rotation_distance: 26.359 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PA1 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PC5 +control: pid +pid_Kp: 23.561 +pid_Ki: 1.208 +pid_Kd: 114.859 +min_temp: 0 +max_temp: 250 + +[heater_bed] +heater_pin: PA7 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PC4 +control: pid +pid_Kp: 71.867 +pid_Ki: 1.536 +pid_Kd: 840.843 +min_temp: 0 +max_temp: 110 + +[heater_fan hotend_fan] +pin: PC0 + +[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: 2000 +max_z_velocity: 5 +max_z_accel: 100 + +[bltouch] +sensor_pin: ^PC14 +control_pin: PC13 +x_offset: -31.8 +y_offset: -40.5 +z_offset: 0 +probe_with_touch_mode: true +stow_on_each_sample: false + +[bed_mesh] +speed: 120 +mesh_min: 20, 20 +mesh_max: 200, 200 +probe_count: 4,4 +algorithm: bicubic + +[safe_z_home] +home_xy_position: 147, 154 +speed: 75 +z_hop: 5 +z_hop_speed: 5 +move_to_previous: true + +[filament_switch_sensor e0_sensor] +switch_pin: !PC15 +pause_on_runout: true +runout_gcode: PAUSE + +[pause_resume] +recover_velocity: 25 diff --git a/config/printer-creality-sermoonV1-2022.cfg b/config/printer-creality-sermoonV1-2022.cfg new file mode 100644 index 00000000..ca8a91a6 --- /dev/null +++ b/config/printer-creality-sermoonV1-2022.cfg @@ -0,0 +1,109 @@ +# This file contains pin mappings for the Creality Sermoon V1 +# with CR-FDM-v2.4.S1.200 motherboard. + +# To use this config, during "make menuconfig" select the STM32F401 +# with a "64KiB bootloader" and serial (on USART1 PA10/PA9) +# communication. + +# If you prefer a direct serial connection, in "make menuconfig" +# select "Enable extra low-level configuration options" and select +# Serial (on USART2 PA3/PA2), which is broken out on the 10 pin IDC +# cable used for the LCD module as follows: +# 3: Tx, 4: Rx, 9: GND, 10: VCC + +# Flash this firmware by copying "out/klipper.bin" to a SD card and +# turning on the printer with the card inserted. The firmware +# filename must changed to "firmware.bin" + +# See docs/Config_Reference.md for a description of parameters. + +[stepper_x] +step_pin: PA7 +dir_pin: !PA4 +enable_pin: !PB8 +microsteps: 16 +rotation_distance: 40 +endstop_pin: PC4 +position_endstop: 175 +position_max: 175 +position_min: 0 +homing_speed: 50 + +[stepper_y] +step_pin: PB0 +dir_pin: PB10 +enable_pin: !PB8 +microsteps: 16 +rotation_distance: 40 +endstop_pin: PB13 +position_endstop: 0 +position_max: 175 +position_min: 0 +homing_speed: 50 + +[stepper_z] +step_pin: PB7 +dir_pin: PB6 +enable_pin: !PB8 +microsteps: 16 +rotation_distance: 8 +endstop_pin: PB3 +position_endstop: 165 +position_max: 168 +position_min: -3 + +[extruder] +step_pin: PB1 +dir_pin: PB12 +enable_pin: !PB8 +microsteps: 16 +gear_ratio: 42:12 +rotation_distance: 26.359 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PC5 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PC1 +control: pid +pid_Kp: 30.090 +pid_Ki: 1.875 +pid_Kd: 120.735 +min_temp: 0 +max_temp: 290 + +[heater_bed] +heater_pin: PB9 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PC0 +control: pid +pid_Kp: 75.694 +pid_Ki: 1.160 +pid_Kd: 1234.759 +min_temp: 0 +max_temp: 90 + +[fan] +pin: PA5 + +[fan_generic side_fan] +pin: PC15 + +# [controller_fan controller_fan] +# In order to access the controller fan, the controller fan needs to be plugged +# in another location. See https://github.com/Klipper3d/klipper/pull/5621 +# for more information. +# pin: PB4 + +[mcu] +serial: /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0 +restart_method: command + +[printer] +kinematics: cartesian +max_velocity: 300 +max_accel: 2000 +max_z_velocity: 5 +max_z_accel: 100 + +[pause_resume] +recover_velocity: 25 diff --git a/config/printer-eryone-er20-2021.cfg b/config/printer-eryone-er20-2021.cfg index 9fc25f52..a23cafc8 100644 --- a/config/printer-eryone-er20-2021.cfg +++ b/config/printer-eryone-er20-2021.cfg @@ -112,7 +112,7 @@ max_temp: 100 [fan] pin: PB5 -[heater_fan nozzle_cooling_fan] +[heater_fan heatbreak_cooling_fan] pin: PB4 [mcu] diff --git a/config/printer-eryone-thinker-series-v2-2020.cfg b/config/printer-eryone-thinker-series-v2-2020.cfg index 78fb8aac..47899548 100644 --- a/config/printer-eryone-thinker-series-v2-2020.cfg +++ b/config/printer-eryone-thinker-series-v2-2020.cfg @@ -67,7 +67,7 @@ max_extrude_only_distance: 300 [fan] pin: PH5 -[heater_fan nozzle_cooling_fan] +[heater_fan heatbreak_cooling_fan] pin: PH3 [heater_bed] diff --git a/config/printer-lulzbot-mini1-2016.cfg b/config/printer-lulzbot-mini1-2016.cfg index b1471dd1..9be60cbd 100644 --- a/config/printer-lulzbot-mini1-2016.cfg +++ b/config/printer-lulzbot-mini1-2016.cfg @@ -114,7 +114,7 @@ max_temp: 130 #define FAN_PIN 8 pin: PH5 -[heater_fan nozzle_cooling_fan] +[heater_fan heatbreak_cooling_fan] #define FAN1_PIN 6 pin: PH3 diff --git a/config/printer-lulzbot-taz6-2017.cfg b/config/printer-lulzbot-taz6-2017.cfg index 8046381f..7f775b57 100644 --- a/config/printer-lulzbot-taz6-2017.cfg +++ b/config/printer-lulzbot-taz6-2017.cfg @@ -97,7 +97,7 @@ max_temp: 130 [fan] pin: PH5 -[heater_fan nozzle_cooling_fan] +[heater_fan heatbreak_cooling_fan] pin: PH3 [mcu] diff --git a/config/printer-lulzbot-taz6-dual-v3-2017.cfg b/config/printer-lulzbot-taz6-dual-v3-2017.cfg index 2ec91f9e..f94b0684 100644 --- a/config/printer-lulzbot-taz6-dual-v3-2017.cfg +++ b/config/printer-lulzbot-taz6-dual-v3-2017.cfg @@ -128,7 +128,7 @@ max_temp: 130 #On Dual v3 heat break fan is connected to PH3 (part cooling fan on single extruder) pin: PH3 -[heater_fan nozzle_cooling_fan] +[heater_fan heatbreak_cooling_fan] #On Dual v3 part fans are connected to PH5 (heat break fan on single extruder) pin: PH5 diff --git a/config/printer-makergear-m2-2016.cfg b/config/printer-makergear-m2-2016.cfg index db0cfbf8..4379a4be 100644 --- a/config/printer-makergear-m2-2016.cfg +++ b/config/printer-makergear-m2-2016.cfg @@ -70,7 +70,7 @@ max_temp: 90 [fan] pin: PH5 -[heater_fan nozzle_cooling_fan] +[heater_fan heatbreak_cooling_fan] pin: PH3 [mcu] diff --git a/config/printer-mtw-create-2015.cfg b/config/printer-mtw-create-2015.cfg index 674d0be9..7480c4bd 100644 --- a/config/printer-mtw-create-2015.cfg +++ b/config/printer-mtw-create-2015.cfg @@ -104,7 +104,7 @@ mesh_max: 225, 225 [fan] pin: PH5 -[heater_fan nozzle_cooling_fan] +[heater_fan heatbreak_cooling_fan] pin: PH3 [mcu] diff --git a/config/printer-seemecnc-rostock-max-v2-2015.cfg b/config/printer-seemecnc-rostock-max-v2-2015.cfg index 31eb100b..70ebd876 100644 --- a/config/printer-seemecnc-rostock-max-v2-2015.cfg +++ b/config/printer-seemecnc-rostock-max-v2-2015.cfg @@ -63,7 +63,7 @@ max_temp: 300 [fan] pin: PH5 -[heater_fan nozzle_cooling_fan] +[heater_fan heatbreak_cooling_fan] pin: PH4 heater: extruder diff --git a/config/sample-bigtreetech-ebb-canbus-v1.0.cfg b/config/sample-bigtreetech-ebb-canbus-v1.0.cfg new file mode 100644 index 00000000..3771b984 --- /dev/null +++ b/config/sample-bigtreetech-ebb-canbus-v1.0.cfg @@ -0,0 +1,66 @@ +# This file contains common pin mappings for the BIGTREETECH EBBCan +# Canbus board. To use this config, the firmware should be compiled for the +# STM32F072 with "8 MHz crystal" and "USB (on PA11/PA12)" or "CAN bus (on PB8/PB9)". +# The "EBB Can" micro-controller will be used to control the components on the nozzle. + +# See docs/Config_Reference.md for a description of parameters. + +[mcu EBBCan] +serial: /dev/serial/by-id/usb-Klipper_Klipper_firmware_12345-if00 +#canbus_uuid: 0e0d81e4210c + +[adxl345] +cs_pin: EBBCan: PB12 +spi_bus: spi2 +axes_map: x,y,z + +[extruder] +step_pin: EBBCan: PA9 +dir_pin: !EBBCan: PA8 +enable_pin: !EBBCan: PA10 +microsteps: 16 +rotation_distance: 33.500 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: EBBCan: PB1 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: EBBCan: PA0 +control: pid +pid_Kp: 21.527 +pid_Ki: 1.063 +pid_Kd: 108.982 +min_temp: 0 +max_temp: 250 + +#sensor_type:MAX31865 +#sensor_pin: EBBCan: PA15 +#spi_bus: spi1a +#rtd_nominal_r: 100 +#rtd_reference_r: 430 +#rtd_num_of_wires: 2 + +[tmc2209 extruder] +uart_pin: EBBCan: PA13 +run_current: 0.650 +stealthchop_threshold: 999999 + +[fan] +pin: EBBCan: PA1 + +[heater_fan hotend_fan] +pin: EBBCan: PA2 +heater: extruder +heater_temp: 50.0 + +#[neopixel hotend_rgb] +#pin: EBBCan:PA3 + +#[bltouch] +#sensor_pin: ^EBBCan:PA5 +#control_pin: EBBCan:PA4 + +#[filament_switch_sensor switch_sensor] +#switch_pin: EBBCan:PB6 + +#[filament_motion_sensor motion_sensor] +#switch_pin: ^EBBCan:PB7 diff --git a/config/sample-bigtreetech-ebb-canbus-v1.1.cfg b/config/sample-bigtreetech-ebb-canbus-v1.1.cfg new file mode 100644 index 00000000..d7165541 --- /dev/null +++ b/config/sample-bigtreetech-ebb-canbus-v1.1.cfg @@ -0,0 +1,68 @@ +# This file contains common pin mappings for the BIGTREETECH EBBCan +# Canbus board. To use this config, the firmware should be compiled for the +# STM32G0B1 with "8 MHz crystal" and "USB (on PA11/PA12)" or "CAN bus (on PB0/PB1)". +# The "EBB Can" micro-controller will be used to control the components on the nozzle. + +# See docs/Config_Reference.md for a description of parameters. + +[mcu EBBCan] +serial: /dev/serial/by-id/usb-Klipper_Klipper_firmware_12345-if00 +#canbus_uuid: 0e0d81e4210c + +[adxl345] +cs_pin: EBBCan: PB12 +spi_software_sclk_pin: EBBCan: PB10 +spi_software_mosi_pin: EBBCan: PB11 +spi_software_miso_pin: EBBCan: PB2 +axes_map: x,y,z + +[extruder] +step_pin: EBBCan: PD0 +dir_pin: !EBBCan: PD1 +enable_pin: !EBBCan: PD2 +microsteps: 16 +rotation_distance: 33.500 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: EBBCan: PA2 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: EBBCan: PA3 +control: pid +pid_Kp: 21.527 +pid_Ki: 1.063 +pid_Kd: 108.982 +min_temp: 0 +max_temp: 250 + +# sensor_type:MAX31865 +# sensor_pin: EBBCan: PA4 +# spi_bus: spi1 +# rtd_nominal_r: 100 +# rtd_reference_r: 430 +# rtd_num_of_wires: 2 + +[tmc2209 extruder] +uart_pin: EBBCan: PA15 +run_current: 0.650 +stealthchop_threshold: 999999 + +[fan] +pin: EBBCan: PA0 + +[heater_fan hotend_fan] +pin: EBBCan: PA1 +heater: extruder +heater_temp: 50.0 + +#[neopixel hotend_rgb] +#pin: EBBCan:PD3 + +#[bltouch] +#sensor_pin: ^EBBCan:PB8 +#control_pin: EBBCan:PB9 + +#[filament_switch_sensor switch_sensor] +#switch_pin: EBBCan:PB4 + +#[filament_motion_sensor motion_sensor] +#switch_pin: ^EBBCan:PB3 diff --git a/config/sample-huvud-v0.61.cfg b/config/sample-huvud-v0.61.cfg new file mode 100644 index 00000000..ac783189 --- /dev/null +++ b/config/sample-huvud-v0.61.cfg @@ -0,0 +1,58 @@ +# This file contains common pin mappings for the Huvud V0.61 by Bondus. +# https://github.com/bondus/Klipperhuvudboard +# To use this config, copy the contents into your main config file. + +# The huvud is not capable of running a printer on it's own. It +# needs to be paired with another board that will control other +# aspects of the printer. + +# The firmware should be compiled for the STM32F103 with a "2KiB +# bootloader" and a "8MHz crystal" clock reference. +# Select CAN bus (on PB8/PB9) or USB under communication interface. +# Flash by running make flash FLASH_DEVICE=1209:beba + +# See docs/Config_Reference.md for a description of parameters. + +[mcu huvud] +canbus_uuid: ac20f0bbda05 +# Identify your canbus_uuid by running: +# ~/klippy-env/bin/python ~/klipper/scripts/canbus_query.py can0 + +[extruder] +step_pin: huvud: PB3 +dir_pin: huvud: PB4 +enable_pin: !huvud: PB5 +rotation_distance: 22.52453125 +nozzle_diameter: 0.400 +filament_diameter: 1.75 +heater_pin: huvud: PA6 +sensor_type: NTC 100K MGB18-104F39050L32 +sensor_pin: huvud: PA0 +pullup_resistor: 2200 +min_temp: 0 +max_temp: 300 +control: pid +pid_kp: 26.213 +pid_ki: 1.304 +pid_kd: 131.721 + +[tmc2209 extruder] +uart_pin: huvud: PA10 +tx_pin: huvud: PA9 +run_current: 0.35 + +[probe] +pin: huvud: PB12 +z_offset: 0 + +[fan] +pin: huvud: PA8 + +[heater_fan extruder_fan] +pin: huvud: PA7 + +[adxl345] +cs_pin: PB1 + +[led huvud_led] +blue_pin: huvud: PC13 diff --git a/config/sample-lcd.cfg b/config/sample-lcd.cfg index dad8a90c..ee07bd6e 100644 --- a/config/sample-lcd.cfg +++ b/config/sample-lcd.cfg @@ -95,6 +95,21 @@ click_pin: ^!EXP1_2 pin: EXP1_1 +######################################################################################## +# Anet 128x64 LCD with alternative wiring (ANET_FULL_GRAPHICS_LCD_ALT_WIRING in Marlin) +######################################################################################## + +[display] +lcd_type: st7920 +cs_pin: EXP1_8 +sclk_pin: EXP1_4 +sid_pin: EXP1_6 +encoder_pins: ^!EXP1_7, ^!EXP1_5 +click_pin: ^!EXP1_3 + +[output_pin beeper] +pin: EXP1_1 + ###################################################################### # Fysetc Mini 12864Panel v2.1 (with neopixel backlight leds) ###################################################################### diff --git a/config/sample-macros.cfg b/config/sample-macros.cfg index fcb144e0..3590268c 100644 --- a/config/sample-macros.cfg +++ b/config/sample-macros.cfg @@ -175,6 +175,29 @@ gcode: sensor.temperature, sensor.humidity))} +###################################################################### +# Override M117 command with rawparams +###################################################################### + +# The macro below will override the default M117 command to echo the message. +# +# It uses the rawparams pseudo-variable that contains the full unparsed +# parameters that was passed to the M117 command. +# +# As this can include comments, we are trimming the text when a `;` or `#` is +# found, and escaping any existing `"` + +[gcode_macro M117] +rename_existing: M117.1 +gcode: + {% if rawparams %} + {% set escaped_msg = rawparams.split(';', 1)[0].split('\x23', 1)[0]|replace('"', '\\"') %} + SET_DISPLAY_TEXT MSG="{escaped_msg}" + RESPOND TYPE=command MSG="{escaped_msg}" + {% else %} + SET_DISPLAY_TEXT + {% endif %} + # SDCard 'looping' (aka Marlin M808 commands) support # # Support SDCard looping @@ -186,3 +209,55 @@ gcode: {% if params.K is not defined and params.L is defined %}SDCARD_LOOP_BEGIN COUNT={params.L|int}{% endif %} {% if params.K is not defined and params.L is not defined %}SDCARD_LOOP_END{% endif %} {% if params.K is defined and params.L is not defined %}SDCARD_LOOP_DESIST{% endif %} + +# Cancel object (aka Marlin/RRF M486 commands) support +# +# Enable object exclusion +[exclude_object] + +[gcode_macro M486] +gcode: + # Parameters known to M486 are as follows: + # [C] Cancel the current object + # [P] Cancel the object with the given index + # [S] Set the index of the current object. + # If the object with the given index has been canceled, this will cause + # the firmware to skip to the next object. The value -1 is used to + # indicate something that isn’t an object and shouldn’t be skipped. + # [T] Reset the state and set the number of objects + # [U] Un-cancel the object with the given index. This command will be + # ignored if the object has already been skipped + + {% if 'exclude_object' not in printer %} + {action_raise_error("[exclude_object] is not enabled")} + {% endif %} + + {% if 'T' in params %} + EXCLUDE_OBJECT RESET=1 + + {% for i in range(params.T | int) %} + EXCLUDE_OBJECT_DEFINE NAME={i} + {% endfor %} + {% endif %} + + {% if 'C' in params %} + EXCLUDE_OBJECT CURRENT=1 + {% endif %} + + {% if 'P' in params %} + EXCLUDE_OBJECT NAME={params.P} + {% endif %} + + {% if 'S' in params %} + {% if params.S == '-1' %} + {% if printer.exclude_object.current_object %} + EXCLUDE_OBJECT_END NAME={printer.exclude_object.current_object} + {% endif %} + {% else %} + EXCLUDE_OBJECT_START NAME={params.S} + {% endif %} + {% endif %} + + {% if 'U' in params %} + EXCLUDE_OBJECT RESET=1 NAME={params.U} + {% endif %} diff --git a/docs/CANBUS.md b/docs/CANBUS.md index fc85dc76..f8d64676 100644 --- a/docs/CANBUS.md +++ b/docs/CANBUS.md @@ -4,9 +4,9 @@ This document describes Klipper's CAN bus support. ## Device Hardware -Klipper currently only supports CAN on stm32 chips. In addition, the -micro-controller chip must support CAN and it must be on a board that -has a CAN transceiver. +Klipper currently supports CAN on stm32 and rp2040 chips. In addition, +the micro-controller chip must be on a board that has a CAN +transceiver. To compile for CAN, run `make menuconfig` and select "CAN bus" as the communication interface. Finally, compile the micro-controller code @@ -73,7 +73,7 @@ powered and wired correctly, and then run: If uninitialized CAN devices are detected the above command will report lines like the following: ``` -Found canbus_uuid=11aa22bb33cc +Found canbus_uuid=11aa22bb33cc, Application: Klipper ``` Each device will have a unique identifier. In the above example, @@ -91,3 +91,40 @@ the CAN bus to communicate with the device - for example: [mcu my_can_mcu] canbus_uuid: 11aa22bb33cc ``` + +## USB to CAN bus bridge mode + +Some micro-controllers support selecting "USB to CAN bus bridge" mode +during "make menuconfig". This mode may allow one to use a +micro-controller as both a "USB to CAN bus adapter" and as a Klipper +node. + +When Klipper uses this mode the micro-controller appears as a "USB CAN +bus adapter" under Linux. The "Klipper bridge mcu" itself will appear +as if was on this CAN bus - it can be identified via `canbus_query.py` +and configured like other CAN bus Klipper nodes. It will appear +alongside other devices that are actually on the CAN bus. + +Some important notes when using this mode: + +* The "bridge mcu" is not actually on the CAN bus. Messages to and + from it do not consume bandwidth on the CAN bus. The mcu can not be + seen by other adapters that may be on the CAN bus. + +* It is necessary to configure the `can0` (or similar) interface in + Linux in order to communicate with the bus. However, Linux CAN bus + speed and CAN bus bit-timing options are ignored by Klipper. + Currently, the CAN bus frequency is specified during "make + menuconfig" and the bus speed specified in Linux is ignored. + +* Whenever the "bridge mcu" is reset, Linux will disable the + corresponding `can0` interface. To ensure proper handling of + FIRMWARE_RESTART and RESTART commands, it is recommended to replace + `auto` with `allow-hotplug` in the `/etc/network/interfaces.d/can0` + file. For example: +``` +allow-hotplug can0 +iface can0 can static + bitrate 500000 + up ifconfig $IFACE txqueuelen 128 +``` diff --git a/docs/CANBUS_protocol.md b/docs/CANBUS_protocol.md index e1561eb5..8b6cc74f 100644 --- a/docs/CANBUS_protocol.md +++ b/docs/CANBUS_protocol.md @@ -38,23 +38,23 @@ with a RESP_NEED_NODEID response message. The CMD_QUERY_UNASSIGNED message format is: `<1-byte message_id = 0x00>` -### CMD_SET_NODEID message +### CMD_SET_KLIPPER_NODEID message This command assigns a `canbus_nodeid` to the micro-controller with a given `canbus_uuid`. -The CMD_SET_NODEID message format is: +The CMD_SET_KLIPPER_NODEID message format is: `<1-byte message_id = 0x01><6-byte canbus_uuid><1-byte canbus_nodeid>` ### RESP_NEED_NODEID message The RESP_NEED_NODEID message format is: -`<1-byte message_id = 0x20><6-byte canbus_uuid>` +`<1-byte message_id = 0x20><6-byte canbus_uuid><1-byte set_klipper_nodeid = 0x01>` ## Data Packets A micro-controller that has been assigned a nodeid via the -CMD_SET_NODEID command can send and receive data packets. +CMD_SET_KLIPPER_NODEID command can send and receive data packets. The packet data in messages using the node's receive CAN bus id (`canbus_nodeid * 2 + 256`) are simply appended to a buffer, and when diff --git a/docs/Command_Templates.md b/docs/Command_Templates.md index 87e584b7..1946d4db 100644 --- a/docs/Command_Templates.md +++ b/docs/Command_Templates.md @@ -130,17 +130,13 @@ gcode: ### The "rawparams" variable -The full unparsed parameters for the running macro can be access via the `rawparams` pseudo-variable. +The full unparsed parameters for the running macro can be access via the +`rawparams` pseudo-variable. -This is quite useful if you want to change the behavior of certain commands like the `M117`. For example: +Note that this will include any comments that were part of the original command. -``` -[gcode_macro M117] -rename_existing: M117.1 -gcode: - M117.1 { rawparams } - M118 { rawparams } -``` +See the [sample-macros.cfg](../config/sample-macros.cfg) file for an example +showing how to override the `M117` command using `rawparams`. ### The "printer" Variable diff --git a/docs/Config_Changes.md b/docs/Config_Changes.md index ca97a6cc..bbf81fe1 100644 --- a/docs/Config_Changes.md +++ b/docs/Config_Changes.md @@ -8,6 +8,16 @@ All dates in this document are approximate. ## Changes +20220616: It was previously possible to flash an rp2040 in bootloader +mode by running `make flash FLASH_DEVICE=first`. The equivalent +command is now `make flash FLASH_DEVICE=2e8a:0003`. + +20220612: The rp2040 micro-controller now has a workaround for the +"rp2040-e5" USB errata. This should make initial USB connections more +reliable. However, it may result in a change in behavior for the +gpio15 pin. It is unlikely the gpio15 behavior change will be +noticeable. + 20220407: The temperature_fan `pid_integral_max` config option has been removed (it was deprecated on 20210612). diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 62fe627a..3c3e068a 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -1154,9 +1154,9 @@ home_xy_position: # than z_hop, then this will lift the head to a height of z_hop. If # the Z axis is not already homed the head is lifted by z_hop. # The default is to not implement Z hop. -#z_hop_speed: 20.0 +#z_hop_speed: 15.0 # Speed (in mm/s) at which the Z axis is lifted prior to homing. The -# default is 20mm/s. +# default is 15 mm/s. #move_to_previous: False # When set to True, the X and Y axes are reset to their previous # positions after Z axis homing. The default is False. @@ -1334,6 +1334,9 @@ path: # are not supported). One may point this to OctoPrint's upload # directory (generally ~/.octoprint/uploads/ ). This parameter must # be provided. +#on_error_gcode: +# A list of G-Code commands to execute when an error is reported. + ``` ### [sdcard_loop] @@ -1432,6 +1435,20 @@ Enable the "M118" and "RESPOND" extended # override the "default_type". ``` +### [exclude_object] +Enables support to exclude or cancel individual objects during the printing +process. + +See the [exclude objects guide](Exclude_Object.md) and +[command reference](G-Codes.md#excludeobject) +for additional information. See the +[sample-macros.cfg](../config/sample-macros.cfg) file for a +Marlin/RepRapFirmware compatible M486 G-Code macro. + +``` +[exclude_object] +``` + ## Resonance compensation ### [input_shaper] @@ -1508,6 +1525,24 @@ cs_pin: # measurements. ``` +### [mpu9250] + +Support for mpu9250 and mpu6050 accelerometers (one may define any +number of sections with an "mpu9250" prefix). + +``` +[mpu9250 my_accelerometer] +#i2c_address: +# Default is 104 (0x68). +#i2c_mcu: +#i2c_bus: +#i2c_speed: 400000 +# See the "common I2C settings" section for a description of the +# above parameters. The default "i2c_speed" is 400000. +#axes_map: x, y, z +# See the "adxl345" section for information on this parameter. +``` + ### [resonance_tester] Support for resonance testing and automatic input shaper calibration. @@ -2109,7 +2144,7 @@ temperature sensors that are reported via the M105 command. Klipper includes definitions for many types of temperature sensors. These sensors may be used in any config section that requires a -temperature sensor (such as an `[extruder]` or `[heated_bed]` +temperature sensor (such as an `[extruder]` or `[heater_bed]` section). ### Common thermistors @@ -4463,6 +4498,22 @@ SPI bus. The following parameters are generally available for devices using an I2C bus. +Note that Klipper's current micro-controller support for i2c is +generally not tolerant to line noise. Unexpected errors on the i2c +wires may result in Klipper raising a run-time error. Klipper's +support for error recovery varies between each micro-controller type. +It is generally recommended to only use i2c devices that are on the +same printed circuit board as the micro-controller. + +Most Klipper micro-controller implementations only support an +`i2c_speed` of 100000. The Klipper "linux" micro-controller supports a +400000 speed, but it must be +[set in the operating system](RPi_microcontroller.md#optional-enabling-i2c) +and the `i2c_speed` parameter is otherwise ignored. The Klipper +"rp2040" micro-controller supports a rate of 400000 via the +`i2c_speed` parameter. All other Klipper micro-controllers use a +100000 rate and ignore the `i2c_speed` parameter. + ``` #i2c_address: # The i2c address of the device. This must specified as a decimal @@ -4476,8 +4527,9 @@ I2C bus. # the type of micro-controller. #i2c_speed: # The I2C speed (in Hz) to use when communicating with the device. -# On some micro-controllers changing this value has no effect. The -# default is 100000. +# The Klipper implementation on most micro-controllers is hard-coded +# to 100000 and changing this value has no effect. The default is +# 100000. ``` ### Common UART settings diff --git a/docs/Exclude_Object.md b/docs/Exclude_Object.md new file mode 100644 index 00000000..5923afbe --- /dev/null +++ b/docs/Exclude_Object.md @@ -0,0 +1,99 @@ +# Exclude Objects + +The `[exclude_object]` module allows Klipper to exclude objects while a print is +in progress. To enable this feature include an [exclude_object config +section](Config_Reference.md#exclude_object) (also see the [command +reference](G-Codes.md#exclude-object) and +[sample-macros.cfg](../config/sample-macros.cfg) file for a +Marlin/RepRapFirmware compatible M486 G-Code macro.) + +Unlike other 3D printer firmware options, a printer running Klipper utilizes a +suite of components and users have many options to choose from. Therefore, in +order to provide a a consistent user experience, the `[exclude_object]` module +will establish a contract or API of sorts. The contract covers the contents of +the gcode file, how the internal state of the module is controlled, and how that +state is provided to clients. + +## Workflow Overview +A typical workflow for printing a file might look like this: +1. Slicing is completed and the file is uploaded for printing. During the + upload, the file is processed and `[exclude_object]` markers are added to + the file. Alternately, slicers may be configured to prepare object exclusion + markers natively, or in it's own pre-processing step. +2. When printing starts, Klipper will reset the `[exclude_object]` + [status](Status_Reference.md#exclude_object). +3. When Klipper processes the `EXCLUDE_OBJECT_DEFINE` block, it will update the + status with the known objects and pass it on to clients. +4. The client may use that information to present a UI to the user so that + progress can be tracked. Klipper will update the status to include the + currently printing object which the client can use for display purposes. +5. If the user requests that an object be cancelled, the client will issue an + `EXCLUDE_OBJECT NAME=` command to Klipper. +6. When Klipper process the command, it will add the object to the list of + excluded objects and update the status for the client. +7. The client will receive the updated status from Klipper and can use that + information to reflect the object's status in the UI. +8. When printing finishes, the `[exclude_object]` status will continue to be + available until another action resets it. + +## The GCode File +The specialized gcode processing needed to support excluding objects does not +fit into Klipper's core design goals. Therefore, this module requires that the +file is processed before being sent to Klipper for printing. Using a +post-process script in the slicer or having middleware process the file on +upload are two possibilities for preparing the file for Klipper. A reference +post-processing script is available both as an executable and a python library, +see +[cancelobject-preprocessor](https://github.com/kageurufu/cancelobject-preprocessor). + +### Object Definitions + +The `EXCLUDE_OBJECT_DEFINE` command is used to provide a summary of each object +in the gcode file to be printed. Provides a summary of an object in the file. +Objects don't need to be defined in order to be referenced by other commands. +The primary purpose of this command is to provide information to the UI without +needing to parse the entire gcode file. + +Object definitions are named, to allow users to easily select an object to be +excluded, and additional metadata may be provided to allow for graphical +cancellation displays. Currently defined metadata includes a `CENTER` X,Y +coordinate, and a `POLYGON` list of X,Y points representing a minimal outline of +the object. This could be a simple bounding box, or a complicated hull for +showing more detailed visualizations of the printed objects. Especially when +gcode files include multiple parts with overlapping bounding regions, center +points become hard to visually distinguish. `POLYGONS` must be a json-compatible +array of point `[X,Y]` tuples without whitespace. Additional parameters will be +saved as strings in the object definition and provided in status updates. + +`EXCLUDE_OBJECT_DEFINE NAME=calibration_pyramid CENTER=50,50 +POLYGON=[[40,40],[50,60],[60,40]]` + +All available G-Code commands are documented in the [G-Code +Reference](./G-Codes.md#excludeobject) + +## Status Information +The state of this module is provided to clients by the [exclude_object +status](Status_Reference.md#exclude_object). + +The status is reset when: +- The Klipper firmware is restarted. +- There is a reset of the `[virtual_sdcard]`. Notably, this is reset by Klipper + at the start of a print. +- When an `EXCLUDE_OBJECT_DEFINE RESET=1` command is issued. + +The list of defined objects is represented in the `exclude_object.objects` +status field. In a well defined gcode file, this will be done with +`EXCLUDE_OBJECT_DEFINE` commands at the beginning of the file. This will +provide clients with object names and coordinates so the UI can provide a +graphical representation of the objects if desired. + +As the print progresses, the `exclude_object.current_object` status field will +be updated as Klipper processes `EXCLUDE_OBJECT_START` and `EXCLUDE_OBJECT_END` +commands. The `current_object` field will be set even if the object has been +excluded. Undefined objects marked with a `EXCLUDE_OBJECT_START` will be added +to the known objects to assist in UI hinting, without any additional metadata. + +As `EXCLUDE_OBJECT` commands are issued, the list of excluded objects is +provided in the `exclude_object.excluded_objects` array. Since Klipper looks +ahead to process upcoming gcode, there may be a delay between when the command +is issued and when the status is updated. diff --git a/docs/FAQ.md b/docs/FAQ.md index daa92c62..417fb1d4 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -1,34 +1,9 @@ # Frequently Asked Questions -1. [How can I donate to the project?](#how-can-i-donate-to-the-project) -2. [How do I calculate the rotation_distance config parameter?](#how-do-i-calculate-the-rotation_distance-config-parameter) -3. [Where's my serial port?](#wheres-my-serial-port) -4. [When the micro-controller restarts the device changes to /dev/ttyUSB1](#when-the-micro-controller-restarts-the-device-changes-to-devttyusb1) -5. [The "make flash" command doesn't work](#the-make-flash-command-doesnt-work) -6. [How do I change the serial baud rate?](#how-do-i-change-the-serial-baud-rate) -7. [Can I run Klipper on something other than a Raspberry Pi 3?](#can-i-run-klipper-on-something-other-than-a-raspberry-pi-3) -8. [Can I run multiple instances of Klipper on the same host machine?](#can-i-run-multiple-instances-of-klipper-on-the-same-host-machine) -9. [Do I have to use OctoPrint?](#do-i-have-to-use-octoprint) -10. [Why can't I move the stepper before homing the printer?](#why-cant-i-move-the-stepper-before-homing-the-printer) -11. [Why is the Z position_endstop set to 0.5 in the default configs?](#why-is-the-z-position_endstop-set-to-05-in-the-default-configs) -12. [I converted my config from Marlin and the X/Y axes work fine, but I just get a screeching noise when homing the Z axis](#i-converted-my-config-from-marlin-and-the-xy-axes-work-fine-but-i-just-get-a-screeching-noise-when-homing-the-z-axis) -13. [My TMC motor driver turns off in the middle of a print](#my-tmc-motor-driver-turns-off-in-the-middle-of-a-print) -14. [I keep getting random "Lost communication with MCU" errors](#i-keep-getting-random-lost-communication-with-mcu-errors) -15. [My Raspberry Pi keeps rebooting during prints](#my-raspberry-pi-keeps-rebooting-during-prints) -16. [When I set `restart_method=command` my AVR device just hangs on a restart](#when-i-set-restart_methodcommand-my-avr-device-just-hangs-on-a-restart) -17. [Will the heaters be left on if the Raspberry Pi crashes?](#will-the-heaters-be-left-on-if-the-raspberry-pi-crashes) -18. [How do I convert a Marlin pin number to a Klipper pin name?](#how-do-i-convert-a-marlin-pin-number-to-a-klipper-pin-name) -19. [Do I have to wire my device to a specific type of micro-controller pin?](#do-i-have-to-wire-my-device-to-a-specific-type-of-micro-controller-pin) -20. [How do I cancel an M109/M190 "wait for temperature" request?](#how-do-i-cancel-an-m109m190-wait-for-temperature-request) -21. [Can I find out whether the printer has lost steps?](#can-i-find-out-whether-the-printer-has-lost-steps) -22. [Why does Klipper report errors? I lost my print!](#why-does-klipper-report-errors-i-lost-my-print) -23. [How do I upgrade to the latest software?](#how-do-i-upgrade-to-the-latest-software) -24. [How do I uninstall klipper?](#how-do-i-uninstall-klipper) - ## How can I donate to the project? -Thanks. Kevin has a Patreon page at: -[https://www.patreon.com/koconnor](https://www.patreon.com/koconnor) +Thank you for your support. See the [Sponsors page](Sponsors.md) for +information. ## How do I calculate the rotation_distance config parameter? diff --git a/docs/G-Codes.md b/docs/G-Codes.md index 013afbe5..90d7c686 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -295,6 +295,11 @@ provides the following standard G-Code commands: - Display Message: `M117 ` - Set build percentage: `M73 P` +Also provided is the following extended G-Code command: +- `SET_DISPLAY_TEXT MSG=`: Performs the equivalent of M117, + setting the supplied `MSG` as the current display message. If + `MSG` is omitted the display will be cleared. + ### [dual_carriage] The following command is available when the @@ -320,6 +325,57 @@ parameter is provided it arranges for the given endstop phase setting to be written to the config file (in conjunction with the SAVE_CONFIG command). +### [exclude_object] + +The following commands are available when an +[exclude_object config section](Config_Reference.md#exclude_object) is +enabled (also see the [exclude object guide](Exclude_Object.md)): + +#### `EXCLUDE_OBJECT` +`EXCLUDE_OBJECT [NAME=object_name] [CURRENT=1] [RESET=1]`: +With no parameters, this will return a list of all currently excluded objects. + +When the `NAME` parameter is given, the named object will be excluded from +printing. + +When the `CURRENT` parameter is given, the current object will be excluded from +printing. + +When the `RESET` parameter is given, the list of excluded objects will be +cleared. Additionally including `NAME` will only reset the named object. This +**can** cause print failures, if layers were already skipped. + +#### `EXCLUDE_OBJECT_DEFINE` +`EXCLUDE_OBJECT_DEFINE [NAME=object_name [CENTER=X,Y] [POLYGON=[[x,y],...]] +[RESET=1] [JSON=1]`: +Provides a summary of an object in the file. + +With no parameters provided, this will list the defined objects known to +Klipper. Returns a list of strings, unless the `JSON` parameter is given, +when it will return object details in json format. + +When the `NAME` parameter is included, this defines an object to be excluded. + + - `NAME`: This parameter is required. It is the identifier used by other + commands in this module. + - `CENTER`: An X,Y coordinate for the object. + - `POLYGON`: An array of X,Y coordinates that provide an outline for the + object. + +When the `RESET` parameter is provided, all defined objects will be cleared, and +the `[exclude_object]` module will be reset. + +#### `EXCLUDE_OBJECT_START` +`EXCLUDE_OBJECT_START NAME=object_name`: +This command takes a `NAME` parameter and denotes the start of the gcode for an +object on the current layer. + +#### `EXCLUDE_OBJECT_END` +`EXCLUDE_OBJECT_END [NAME=object_name]`: +Denotes the end of the object's gcode for the layer. It is paired with +`EXCLUDE_OBJECT_START`. A `NAME` parameter is optional, and will only warn when +the provided name does not match the current object. + ### [extruder] The following commands are available if an @@ -896,23 +952,28 @@ all enabled accelerometer chips. #### TEST_RESONANCES `TEST_RESONANCES AXIS= OUTPUT= [NAME=] [FREQ_START=] [FREQ_END=] -[HZ_PER_SEC=] [INPUT_SHAPING=[<0:1>]]`: Runs the resonance +[HZ_PER_SEC=] [CHIPS=] +[POINT=x,y,z] [INPUT_SHAPING=[<0:1>]]`: Runs the resonance test in all configured probe points for the requested "axis" and measures the acceleration using the accelerometer chips configured for the respective axis. "axis" can either be X or Y, or specify an arbitrary direction as `AXIS=dx,dy`, where dx and dy are floating point numbers defining a direction vector (e.g. `AXIS=X`, `AXIS=Y`, or `AXIS=1,-1` to define a diagonal direction). Note that `AXIS=dx,dy` -and `AXIS=-dx,-dy` is equivalent. If `INPUT_SHAPING=0` or not set -(default), disables input shaping for the resonance testing, because +and `AXIS=-dx,-dy` is equivalent. `adxl345_chip_name` can be one or +more configured adxl345 chip,delimited with comma, for example +`CHIPS="adxl345, adxl345 rpi"`. Note that `adxl345` can be omitted from +named adxl345 chips. If POINT is specified it will override the point(s) +configured in `[resonance_tester]`. If `INPUT_SHAPING=0` or not set(default), +disables input shaping for the resonance testing, because it is not valid to run the resonance testing with the input shaper enabled. `OUTPUT` parameter is a comma-separated list of which outputs will be written. If `raw_data` is requested, then the raw accelerometer data is written into a file or a series of files -`/tmp/raw_data__[_].csv` with (`_` part of -the name generated only if more than 1 probe point is configured). If -`resonances` is specified, the frequency response is calculated -(across all probe points) and written into +`/tmp/raw_data__[_][_].csv` with +(`_` part of the name generated only if more than 1 probe point +is configured or POINT is specified). If `resonances` is specified, the +frequency response is calculated (across all probe points) and written into `/tmp/resonances__.csv` file. If unset, OUTPUT defaults to `resonances`, and NAME defaults to the current time in "YYYYMMDD_HHMMSS" format. @@ -949,6 +1010,8 @@ The following additional commands are also available. configured default prefix (or `echo: ` if no prefix is configured). - `RESPOND TYPE=echo MSG=""`: echo the message prepended with `echo: `. +- `RESPOND TYPE=echo_no_space MSG=""`: echo the message prepended with + `echo:` without a space between prefix and message, helpful for compatibility with some octoprint plugins that expect very specific formatting. - `RESPOND TYPE=command MSG=""`: echo the message prepended with `// `. OctoPrint can be configured to respond to these messages (e.g. `RESPOND TYPE=command MSG=action:pause`). @@ -979,7 +1042,7 @@ is enabled (also see the [manual level guide](Manual_Level.md#adjusting-bed-leveling-screws-using-the-bed-probe)). #### SCREWS_TILT_CALCULATE -`SCREWS_TILT_CALCULATE [DIRECTION=CW|CCW] +`SCREWS_TILT_CALCULATE [DIRECTION=CW|CCW] [MAX_DEVIATION=] [=]`: This command will invoke the bed screws adjustment tool. It will command the nozzle to different locations (as defined in the config file) probing the z height and calculate the @@ -987,7 +1050,9 @@ number of knob turns to adjust the bed level. If DIRECTION is specified, the knob turns will all be in the same direction, clockwise (CW) or counterclockwise (CCW). See the PROBE command for details on the optional probe parameters. IMPORTANT: You MUST always do a G28 -before using this command. +before using this command. If MAX_DEVIATION is specified, the command +will raise a gcode error if any difference in the screw height +relative to the base screw height is greater than the value provided. ### [sdcard_loop] diff --git a/docs/Measuring_Resonances.md b/docs/Measuring_Resonances.md index 655a6494..7f41bd4d 100644 --- a/docs/Measuring_Resonances.md +++ b/docs/Measuring_Resonances.md @@ -31,6 +31,18 @@ and **will not work**. The recommended connection scheme: | SDA | 19 | GPIO10 (SPI0_MOSI) | | SCL | 23 | GPIO11 (SPI0_SCLK) | +An alternative to the ADXL345 is the MPU-9250 (or MPU-6050). This +accelerometer has been tested to work over I2C on the RPi at 400kbaud. +Recommended connection scheme for I2C: + +| MPU-9250 pin | RPi pin | RPi pin name | +|:--:|:--:|:--:| +| 3V3 (or VCC) | 01 | 3.3v DC power | +| GND | 09 | Ground | +| SDA | 03 | GPIO02 (SDA1) | +| SCL | 05 | GPIO03 (SCL1) | + + Fritzing wiring diagrams for some of the ADXL345 boards: ![ADXL345-Rpi](img/adxl345-fritzing.png) @@ -64,21 +76,21 @@ the system that may damage the electronics. ### Software installation Note that resonance measurements and shaper auto-calibration require additional -software dependencies not installed by default. First, you will have to run on -your Raspberry Pi the following command: +software dependencies not installed by default. First, run on your Raspberry Pi +the following commands: +``` +sudo apt update +sudo apt install python3-numpy python3-matplotlib libatlas-base-dev +``` + +Next, in order to install NumPy in the Klipper environment, run the command: ``` ~/klippy-env/bin/pip install -v numpy ``` -to install `numpy` package. Note that, depending on the performance of the -CPU, it may take *a lot* of time, up to 10-20 minutes. Be patient and wait -for the completion of the installation. On some occasions, if the board has -too little RAM, the installation may fail and you will need to enable swap. - -Next, run the following commands to install the additional dependencies: -``` -sudo apt update -sudo apt install python3-numpy python3-matplotlib -``` +Note that, depending on the performance of the CPU, it may take *a lot* +of time, up to 10-20 minutes. Be patient and wait for the completion of +the installation. On some occasions, if the board has too little RAM +the installation may fail and you will need to enable swap. Afterwards, check and follow the instructions in the [RPi Microcontroller document](RPi_microcontroller.md) to setup the @@ -87,7 +99,7 @@ Afterwards, check and follow the instructions in the Make sure the Linux SPI driver is enabled by running `sudo raspi-config` and enabling SPI under the "Interfacing options" menu. -Add the following to the printer.cfg file: +For the ADXL345, add the following to the printer.cfg file: ``` [mcu rpi] serial: /tmp/klipper_host_mcu @@ -103,6 +115,23 @@ probe_points: It is advised to start with 1 probe point, in the middle of the print bed, slightly above it. +For the MPU-9250, make sure the Linux I2C driver is enabled and the baud rate is +set to 400000 (see [Enabling I2C](RPi_microcontroller.md#optional-enabling-i2c) +section for more details). Then, add the following to the printer.cfg: +``` +[mcu rpi] +serial: /tmp/klipper_host_mcu + +[mpu9250] +i2c_mcu: rpi +i2c_bus: i2c.1 + +[resonance_tester] +accel_chip: mpu9250 +probe_points: + 100, 100, 20 # an example +``` + Restart Klipper via the `RESTART` command. ## Measuring the resonances diff --git a/docs/Overview.md b/docs/Overview.md index 1bd85eb1..6b9a6cd9 100644 --- a/docs/Overview.md +++ b/docs/Overview.md @@ -54,6 +54,8 @@ communication with the Klipper developers. perfectly square. - [PWM tools](Using_PWM_Tools.md): Guide on how to use PWM controlled tools such as lasers or spindles. +- [Exclude Object](Exclude_Object.md): The guide to the Exclude Objecs + implementation. ## Developer Documentation diff --git a/docs/RPi_microcontroller.md b/docs/RPi_microcontroller.md index 7551d4f1..2e64650c 100644 --- a/docs/RPi_microcontroller.md +++ b/docs/RPi_microcontroller.md @@ -69,6 +69,15 @@ Make sure the Linux SPI driver is enabled by running `sudo raspi-config` and enabling SPI under the "Interfacing options" menu. +## Optional: Enabling I2C + +Make sure the Linux I2C driver is enabled by running `sudo raspi-config` +and enabling I2C under the "Interfacing options" menu. +If planning to use I2C for the MPU accelerometer, it is also required +to set the baud rate to 400000 by: adding/uncommenting +`dtparam=i2c_arm=on,i2c_arm_baudrate=400000` in `/boot/config.txt` +(or `/boot/firmware/config.txt` in some distros). + ## Optional: Identify the correct gpiochip On Raspberry Pi and on many clones the pins exposed on the GPIO belong diff --git a/docs/Sponsors.md b/docs/Sponsors.md new file mode 100644 index 00000000..3dc487ff --- /dev/null +++ b/docs/Sponsors.md @@ -0,0 +1,40 @@ +# Sponsors + +Klipper is Free Software. We depend on the generous support from +sponsors. Please consider sponsoring Klipper or supporting our +sponsors. + +## BIGTREETECH + +[](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 +serve the 3D printing community better. Follow them on +[Facebook](https://www.facebook.com/BIGTREETECH) or +[Twitter](https://twitter.com/BigTreeTech). + +## Klipper Developers + +### Kevin O'Connor + +Kevin is the original author and current maintainer of Klipper. Donate +at: [https://ko-fi.com/koconnor](https://ko-fi.com/koconnor) or +[https://www.patreon.com/koconnor](https://www.patreon.com/koconnor) + +### Eric Callahan + +Eric is the author of bed_mesh, spi_flash, and several other Klipper +modules. Eric has a donations page at: +[https://ko-fi.com/arksine](https://ko-fi.com/arksine) + +## Related Klipper Projects + +Klipper is frequently used with other Free Software. Consider using or +supporting these projects. + +* [Moonraker](https://github.com/Arksine/moonraker) +* [Mainsail](https://github.com/mainsail-crew/mainsail) +* [Fluidd](https://github.com/fluidd-core/fluidd) +* [OctoPrint](https://octoprint.org/) +* [KlipperScreen](https://github.com/jordanruthe/KlipperScreen) diff --git a/docs/Status_Reference.md b/docs/Status_Reference.md index 2fa16a83..48544da4 100644 --- a/docs/Status_Reference.md +++ b/docs/Status_Reference.md @@ -28,6 +28,17 @@ The following information is available in the - `profiles`: The set of currently defined profiles as setup using BED_MESH_PROFILE. +## bed_screws + +The following information is available in the +`Config_Reference.md#bed_screws` object: +- `is_active`: Returns True if the bed screws adjustment tool is currently +active. +- `state`: The bed screws adjustment tool state. It is one of +the following strings: "adjust", "fine". +- `current_screw`: The index for the current screw being adjusted. +- `accepted_screws`: The number of accepted screws. + ## configfile The following information is available in the `configfile` object @@ -41,6 +52,8 @@ The following information is available in the `configfile` object here.) All values are returned as strings. - `save_config_pending`: Returns true if there are updates that a `SAVE_CONFIG` command may persist to disk. +- `save_config_pending_items`: Contains the sections and options that + were changed and would be persisted by a `SAVE_CONFIG`. - `warnings`: A list of warnings about config options. Each entry in the list will be a dictionary containing a `type` and `message` field (both strings). Additional fields may be available depending @@ -69,6 +82,44 @@ The following information is available in the forward direction minus the total number of steps taken in the reverse direction since the micro-controller was last restarted. +## exclude_object + +The following information is available in the +[exclude_object](Exclude_Object.md) object: + +- `objects`: An array of the known objects as provided by the + `EXCLUDE_OBJECT_DEFINE` command. This is the same information provided by + the `EXCLUDE_OBJECT VERBOSE=1` command. The `center` and `polygon` fields will + only be present if provided in the original `EXCLUDE_OBJECT_DEFINE` + + Here is a JSON sample: +``` +[ + { + "polygon": [ + [ 156.25, 146.2511675 ], + [ 156.25, 153.7488325 ], + [ 163.75, 153.7488325 ], + [ 163.75, 146.2511675 ] + ], + "name": "CYLINDER_2_STL_ID_2_COPY_0", + "center": [ 160, 150 ] + }, + { + "polygon": [ + [ 146.25, 146.2511675 ], + [ 146.25, 153.7488325 ], + [ 153.75, 153.7488325 ], + [ 153.75, 146.2511675 ] + ], + "name": "CYLINDER_2_STL_ID_1_COPY_0", + "center": [ 150, 150 ] + } +] +``` +- `excluded_objects`: An array of strings listing the names of excluded objects. +- `current_object`: The name of the object currently being printed. + ## fan The following information is available in @@ -204,6 +255,17 @@ The following information is available for each `[led led_name]`, chain could be accessed at `printer["neopixel "].color_data[1][2]`. +## manual_probe + +The following information is available in the +`manual_probe` object: +- `is_active`: Returns True if a manual probing helper script is currently +active. +- `z_position`: The current height of the nozzle (as the printer currently +understands it). +- `z_position_lower`: Last probe attempt just lower than the current height. +- `z_position_upper`: Last probe attempt just greater than the current height. + ## mcu The following information is available in @@ -376,6 +438,8 @@ The following information is available in the `toolhead` object - `axis_minimum`, `axis_maximum`: The axis travel limits (mm) after homing. It is possible to access the x, y, z components of this limit value (eg, `axis_minimum.x`, `axis_maximum.z`). +- For Delta printers the `cone_start_z` is the max z height at + maximum radius (`printer.toolhead.cone_start_z`). - `max_velocity`, `max_accel`, `max_accel_to_decel`, `square_corner_velocity`: The current printing limits that are in effect. This may differ from the config file settings if a diff --git a/docs/_klipper3d/build-translations.sh b/docs/_klipper3d/build-translations.sh index 7094d1d9..4a0117c2 100755 --- a/docs/_klipper3d/build-translations.sh +++ b/docs/_klipper3d/build-translations.sh @@ -27,6 +27,16 @@ while IFS="," read dirname langsite langdesc langsearch; do new_docs_dir="${WORK_DIR}lang/${langsite}/docs/" locale_dir="${TRANS_DIR}/docs/locales/${dirname}" + # read toc + title=$(sed -n '1p' ${locale_dir}/Navigation.md) + installation_and_configuration=$(sed -n '3p' ${locale_dir}/Navigation.md) + configuration_reference=$(sed -n '5p' ${locale_dir}/Navigation.md) + bed_level=$(sed -n '7p' ${locale_dir}/Navigation.md) + resonance_compensation=$(sed -n '9p' ${locale_dir}/Navigation.md) + command_template=$(sed -n '11p' ${locale_dir}/Navigation.md) + developer_documentation=$(sed -n '13p' ${locale_dir}/Navigation.md) + device_specific_documents=$(sed -n '15p' ${locale_dir}/Navigation.md) + # Copy markdown files to new_docs_dir echo "Copying $dirname to $langsite" mkdir -p "${new_docs_dir}" @@ -56,6 +66,16 @@ while IFS="," read dirname langsite langdesc langsearch; do echo "replace site language" sed -i "s%^ language: en$% language: ${langsite}%" "${new_mkdocs_file}" + echo "replace toc" + sed -i "s%Klipper documentation$%${title}%" "${new_mkdocs_file}" + sed -i "s%Installation and Configuration:$%${installation_and_configuration}:%" "${new_mkdocs_file}" + sed -i "s%Configuration Reference:$%${configuration_reference}:%" "${new_mkdocs_file}" + sed -i "s%Bed Level:$%${bed_level}:%" "${new_mkdocs_file}" + sed -i "s%Resonance Compensation:$%${resonance_compensation}:%" "${new_mkdocs_file}" + sed -i "s%Command templates:$%${command_template}:%" "${new_mkdocs_file}" + sed -i "s%Developer Documentation:$%${developer_documentation}:%" "${new_mkdocs_file}" + sed -i "s%Device Specific Documents:$%${device_specific_documents}:%" "${new_mkdocs_file}" + # Build site echo "building site for ${langsite}" mkdir -p "${PWD}/site/${langsite}/" diff --git a/docs/_klipper3d/mkdocs-requirements.txt b/docs/_klipper3d/mkdocs-requirements.txt index 7dc01993..9ea6d219 100644 --- a/docs/_klipper3d/mkdocs-requirements.txt +++ b/docs/_klipper3d/mkdocs-requirements.txt @@ -7,3 +7,4 @@ mkdocs-exclude==1.0.2 mdx-truly-sane-lists==1.2 mdx-breakless-lists==1.0.1 py-gfm==1.0.2 +markdown==3.3.7 diff --git a/docs/_klipper3d/mkdocs.yml b/docs/_klipper3d/mkdocs.yml index b2a59827..29c0bdd0 100644 --- a/docs/_klipper3d/mkdocs.yml +++ b/docs/_klipper3d/mkdocs.yml @@ -113,6 +113,7 @@ nav: - Multi_MCU_Homing.md - Slicers.md - Skew_Correction.md + - Exclude_Object.md - Using_PWM_Tools.md - Developer Documentation: - Code_Overview.md @@ -134,3 +135,4 @@ nav: - CANBUS.md - TSL1401CL_Filament_Width_Sensor.md - Hall_Filament_Width_Sensor.md + - Sponsors.md diff --git a/docs/img/sponsors/BTT_BTT.png b/docs/img/sponsors/BTT_BTT.png new file mode 100644 index 00000000..141b9b2b Binary files /dev/null and b/docs/img/sponsors/BTT_BTT.png differ diff --git a/docs/index.md b/docs/index.md index efbae621..17192742 100644 --- a/docs/index.md +++ b/docs/index.md @@ -15,3 +15,4 @@ To begin using Klipper start by [installing](Installation.md) it. Klipper is Free Software. Read the [documentation](Overview.md) or view [the Klipper code on github](https://github.com/Klipper3d/klipper). +We depend on the generous support from our [sponsors](Sponsors.md). diff --git a/klippy/chelper/__init__.py b/klippy/chelper/__init__.py index c7e2155c..fa776d14 100644 --- a/klippy/chelper/__init__.py +++ b/klippy/chelper/__init__.py @@ -168,8 +168,8 @@ defs_serialqueue = """ , uint64_t notify_id); void serialqueue_pull(struct serialqueue *sq , struct pull_queue_message *pqm); - void serialqueue_set_baud_adjust(struct serialqueue *sq - , double baud_adjust); + void serialqueue_set_wire_frequency(struct serialqueue *sq + , double frequency); void serialqueue_set_receive_window(struct serialqueue *sq , int receive_window); void serialqueue_set_clock_est(struct serialqueue *sq, double est_freq diff --git a/klippy/chelper/serialqueue.c b/klippy/chelper/serialqueue.c index b74605ff..75d39d21 100644 --- a/klippy/chelper/serialqueue.c +++ b/klippy/chelper/serialqueue.c @@ -49,7 +49,7 @@ struct serialqueue { int receive_waiting; // Baud / clock tracking int receive_window; - double baud_adjust, idle_time; + double bittime_adjust, idle_time; struct clock_estimate ce; double last_receive_sent_time; // Retransmit support @@ -136,6 +136,23 @@ kick_bg_thread(struct serialqueue *sq) report_errno("pipe write", ret); } +// Minimum number of bits in a canbus message +#define CANBUS_PACKET_BITS ((1 + 11 + 3 + 4) + (16 + 2 + 7 + 3)) +#define CANBUS_IFS_BITS 4 + +// Determine minimum time needed to transmit a given number of bytes +static double +calculate_bittime(struct serialqueue *sq, uint32_t bytes) +{ + if (sq->serial_fd_type == SQT_CAN) { + uint32_t pkts = DIV_ROUND_UP(bytes, 8); + uint32_t bits = bytes * 8 + pkts * CANBUS_PACKET_BITS - CANBUS_IFS_BITS; + return sq->bittime_adjust * bits; + } else { + return sq->bittime_adjust * bytes; + } +} + // Update internal state when the receive sequence increases static void update_receive_seq(struct serialqueue *sq, double eventtime, uint64_t rseq) @@ -192,7 +209,7 @@ update_receive_seq(struct serialqueue *sq, double eventtime, uint64_t rseq) } else { struct queue_message *sent = list_first_entry( &sq->sent_queue, struct queue_message, node); - double nr = eventtime + sq->rto + sent->len * sq->baud_adjust; + double nr = eventtime + sq->rto + calculate_bittime(sq, sent->len); pollreactor_update_timer(sq->pr, SQPT_RETRANSMIT, nr); } } @@ -251,7 +268,7 @@ handle_message(struct serialqueue *sq, double eventtime, int len) qm->sent_time = (rseq > sq->retransmit_seq ? sq->last_receive_sent_time : 0.); qm->receive_time = get_monotonic(); // must be time post read() - qm->receive_time -= sq->baud_adjust * len; + qm->receive_time -= calculate_bittime(sq, len); list_add_tail(&qm->node, &sq->receive_queue); must_wake = 1; } @@ -407,8 +424,8 @@ retransmit_event(struct serialqueue *sq, double eventtime) } sq->retransmit_seq = sq->send_seq; sq->rtt_sample_seq = 0; - sq->idle_time = eventtime + buflen * sq->baud_adjust; - double waketime = eventtime + first_buflen * sq->baud_adjust + sq->rto; + sq->idle_time = eventtime + calculate_bittime(sq, buflen); + double waketime = eventtime + sq->rto + calculate_bittime(sq, first_buflen); pthread_mutex_unlock(&sq->lock); return waketime; @@ -416,7 +433,8 @@ retransmit_event(struct serialqueue *sq, double eventtime) // Construct a block of data to be sent to the serial port static int -build_and_send_command(struct serialqueue *sq, uint8_t *buf, double eventtime) +build_and_send_command(struct serialqueue *sq, uint8_t *buf, int pending + , double eventtime) { int len = MESSAGE_HEADER_SIZE; while (sq->ready_bytes) { @@ -463,17 +481,15 @@ build_and_send_command(struct serialqueue *sq, uint8_t *buf, double eventtime) buf[len - MESSAGE_TRAILER_SYNC] = MESSAGE_SYNC; // Store message block - if (eventtime > sq->idle_time) - sq->idle_time = eventtime; - sq->idle_time += len * sq->baud_adjust; + double idletime = eventtime > sq->idle_time ? eventtime : sq->idle_time; + idletime += calculate_bittime(sq, pending + len); struct queue_message *out = message_alloc(); memcpy(out->msg, buf, len); out->len = len; out->sent_time = eventtime; - out->receive_time = sq->idle_time; + out->receive_time = idletime; if (list_empty(&sq->sent_queue)) - pollreactor_update_timer(sq->pr, SQPT_RETRANSMIT - , sq->idle_time + sq->rto); + pollreactor_update_timer(sq->pr, SQPT_RETRANSMIT, idletime + sq->rto); if (!sq->rtt_sample_seq) sq->rtt_sample_seq = sq->send_seq; sq->send_seq++; @@ -484,7 +500,7 @@ build_and_send_command(struct serialqueue *sq, uint8_t *buf, double eventtime) // Determine the time the next serial data should be sent static double -check_send_command(struct serialqueue *sq, double eventtime) +check_send_command(struct serialqueue *sq, int pending, double eventtime) { if (sq->send_seq - sq->receive_seq >= MAX_PENDING_BLOCKS && sq->receive_seq != (uint64_t)-1) @@ -501,7 +517,7 @@ check_send_command(struct serialqueue *sq, double eventtime) // Check for stalled messages now ready double idletime = eventtime > sq->idle_time ? eventtime : sq->idle_time; - idletime += MESSAGE_MIN * sq->baud_adjust; + idletime += calculate_bittime(sq, pending + MESSAGE_MIN); uint64_t ack_clock = clock_from_time(&sq->ce, idletime); uint64_t min_stalled_clock = MAX_CLOCK, min_ready_clock = MAX_CLOCK; struct command_queue *cq; @@ -525,9 +541,10 @@ check_send_command(struct serialqueue *sq, double eventtime) struct queue_message *qm = list_first_entry( &cq->ready_queue, struct queue_message, node); uint64_t req_clock = qm->req_clock; + double bgtime = pending ? idletime : sq->idle_time; double bgoffset = MIN_REQTIME_DELTA + MIN_BACKGROUND_DELTA; if (req_clock == BACKGROUND_PRIORITY_CLOCK) - req_clock = clock_from_time(&sq->ce, sq->idle_time + bgoffset); + req_clock = clock_from_time(&sq->ce, bgtime + bgoffset); if (req_clock < min_ready_clock) min_ready_clock = req_clock; } @@ -561,18 +578,21 @@ command_event(struct serialqueue *sq, double eventtime) int buflen = 0; double waketime; for (;;) { - waketime = check_send_command(sq, eventtime); + waketime = check_send_command(sq, buflen, eventtime); if (waketime != PR_NOW || buflen + MESSAGE_MAX > sizeof(buf)) { if (buflen) { // Write message blocks do_write(sq, buf, buflen); sq->bytes_write += buflen; + double idletime = (eventtime > sq->idle_time + ? eventtime : sq->idle_time); + sq->idle_time = idletime + calculate_bittime(sq, buflen); buflen = 0; } if (waketime != PR_NOW) break; } - buflen += build_and_send_command(sq, &buf[buflen], eventtime); + buflen += build_and_send_command(sq, &buf[buflen], buflen, eventtime); } pthread_mutex_unlock(&sq->lock); return waketime; @@ -847,10 +867,15 @@ exit: } void __visible -serialqueue_set_baud_adjust(struct serialqueue *sq, double baud_adjust) +serialqueue_set_wire_frequency(struct serialqueue *sq, double frequency) { pthread_mutex_lock(&sq->lock); - sq->baud_adjust = baud_adjust; + if (sq->serial_fd_type == SQT_CAN) { + sq->bittime_adjust = 1. / frequency; + } else { + // An 8N1 serial line is 10 bits per byte (1 start, 8 data, 1 stop) + sq->bittime_adjust = 10. / frequency; + } pthread_mutex_unlock(&sq->lock); } diff --git a/klippy/chelper/serialqueue.h b/klippy/chelper/serialqueue.h index 724a86a5..4d447f2f 100644 --- a/klippy/chelper/serialqueue.h +++ b/klippy/chelper/serialqueue.h @@ -42,7 +42,7 @@ void serialqueue_send(struct serialqueue *sq, struct command_queue *cq , uint8_t *msg, int len, uint64_t min_clock , uint64_t req_clock, uint64_t notify_id); void serialqueue_pull(struct serialqueue *sq, struct pull_queue_message *pqm); -void serialqueue_set_baud_adjust(struct serialqueue *sq, double baud_adjust); +void serialqueue_set_wire_frequency(struct serialqueue *sq, double frequency); void serialqueue_set_receive_window(struct serialqueue *sq, int receive_window); void serialqueue_set_clock_est(struct serialqueue *sq, double est_freq , double conv_time, uint64_t conv_clock diff --git a/klippy/configfile.py b/klippy/configfile.py index 165e9320..63dd8149 100644 --- a/klippy/configfile.py +++ b/klippy/configfile.py @@ -140,6 +140,7 @@ class PrinterConfig: self.autosave = None self.deprecated = {} self.status_raw_config = {} + self.status_save_pending = {} self.status_settings = {} self.status_warnings = [] self.save_config_pending = False @@ -331,18 +332,36 @@ class PrinterConfig: return {'config': self.status_raw_config, 'settings': self.status_settings, 'warnings': self.status_warnings, - 'save_config_pending': self.save_config_pending} + 'save_config_pending': self.save_config_pending, + 'save_config_pending_items': self.status_save_pending} # Autosave functions def set(self, section, option, value): if not self.autosave.fileconfig.has_section(section): self.autosave.fileconfig.add_section(section) svalue = str(value) self.autosave.fileconfig.set(section, option, svalue) + pending = dict(self.status_save_pending) + if not section in pending or pending[section] is None: + pending[section] = {} + else: + pending[section] = dict(pending[section]) + pending[section][option] = svalue + self.status_save_pending = pending self.save_config_pending = True logging.info("save_config: set [%s] %s = %s", section, option, svalue) def remove_section(self, section): - self.autosave.fileconfig.remove_section(section) - self.save_config_pending = True + if self.autosave.fileconfig.has_section(section): + self.autosave.fileconfig.remove_section(section) + pending = dict(self.status_save_pending) + pending[section] = None + self.status_save_pending = pending + self.save_config_pending = True + elif (section in self.status_save_pending and + self.status_save_pending[section] is not None): + pending = dict(self.status_save_pending) + del pending[section] + self.status_save_pending = pending + self.save_config_pending = True def _disallow_include_conflicts(self, regular_data, cfgname, gcode): config = self._build_config_wrapper(regular_data, cfgname) for section in self.autosave.fileconfig.sections(): diff --git a/klippy/console.py b/klippy/console.py index 32108866..da32e18b 100755 --- a/klippy/console.py +++ b/klippy/console.py @@ -11,11 +11,12 @@ help_txt = """ This is a debugging console for the Klipper micro-controller. In addition to mcu commands, the following artificial commands are available: - PINS : Load pin name aliases (eg, "PINS arduino") DELAY : Send a command at a clock time (eg, "DELAY 9999 get_uptime") FLOOD : Send a command many times (eg, "FLOOD 22 .01 get_uptime") SUPPRESS : Suppress a response message (eg, "SUPPRESS analog_in_state 4") SET : Create a local variable (eg, "SET myvar 123.4") + DUMP : Dump memory (eg, "DUMP 0x12345678 100 32") + FILEDUMP : Dump to file (eg, "FILEDUMP data.bin 0x12345678 100 32") STATS : Report serial statistics LIST : List available mcu commands, local commands, and local variables HELP : Show this text @@ -48,6 +49,7 @@ class KeyboardReader: reactor.register_callback(self.connect) self.local_commands = { "SET": self.command_SET, + "DUMP": self.command_DUMP, "FILEDUMP": self.command_FILEDUMP, "DELAY": self.command_DELAY, "FLOOD": self.command_FLOOD, "SUPPRESS": self.command_SUPPRESS, "STATS": self.command_STATS, "LIST": self.command_LIST, "HELP": self.command_HELP, @@ -98,6 +100,55 @@ class KeyboardReader: except ValueError: pass self.eval_globals[parts[1]] = val + def command_DUMP(self, parts, filename=None): + # Extract command args + try: + addr = int(parts[1], 0) + count = int(parts[2], 0) + order = [2, 0, 1, 0][(addr | count) & 3] + if len(parts) > 3: + order = {'32': 2, '16': 1, '8': 0}[parts[3]] + except ValueError as e: + self.output("Error: %s" % (str(e),)) + return + bsize = 1 << order + # Query data from mcu + vals = [] + for i in range((count + bsize - 1) >> order): + caddr = addr + (i << order) + cmd = "debug_read order=%d addr=%d" % (order, caddr) + params = self.ser.send_with_response(cmd, "debug_result") + vals.append(params['val']) + # Report data + if filename is None and order == 2: + # Common 32bit hex dump + for i in range((len(vals) + 3) // 4): + p = i * 4 + hexvals = " ".join(["%08x" % (v,) for v in vals[p:p+4]]) + self.output("%08x %s" % (addr + p * 4, hexvals)) + return + # Convert to byte format + data = bytearray() + for val in vals: + for b in range(bsize): + data.append((val >> (8 * b)) & 0xff) + data = data[:count] + if filename is not None: + f = open(filename, 'wb') + f.write(data) + f.close() + self.output("Wrote %d bytes to '%s'" % (len(data), filename)) + return + for i in range((count + 15) // 16): + p = i * 16 + paddr = addr + p + d = data[p:p+16] + hexbytes = " ".join(["%02x" % (v,) for v in d]) + pb = "".join([chr(v) if v >= 0x20 and v < 0x7f else '.' for v in d]) + o = "%08x %-47s |%s|" % (paddr, hexbytes, pb) + self.output("%s %s" % (o[:34], o[34:])) + def command_FILEDUMP(self, parts): + self.command_DUMP(parts[1:], filename=parts[1]) def command_DELAY(self, parts): try: val = int(parts[1]) diff --git a/klippy/extras/adxl345.py b/klippy/extras/adxl345.py index ad973698..4ef8df07 100644 --- a/klippy/extras/adxl345.py +++ b/klippy/extras/adxl345.py @@ -30,7 +30,7 @@ Accel_Measurement = collections.namedtuple( 'Accel_Measurement', ('time', 'accel_x', 'accel_y', 'accel_z')) # Helper class to obtain measurements -class ADXL345QueryHelper: +class AccelQueryHelper: def __init__(self, printer, cconn): self.printer = printer self.cconn = cconn @@ -101,15 +101,18 @@ class ADXL345QueryHelper: write_proc.start() # Helper class for G-Code commands -class ADXLCommandHelper: +class AccelCommandHelper: def __init__(self, config, chip): self.printer = config.get_printer() self.chip = chip self.bg_client = None - self.name = config.get_name().split()[-1] + name_parts = config.get_name().split() + self.base_name = name_parts[0] + self.name = name_parts[-1] self.register_commands(self.name) - if self.name == "adxl345": - self.register_commands(None) + if len(name_parts) == 1: + if self.name == "adxl345" or not config.has_section("adxl345"): + self.register_commands(None) def register_commands(self, name): # Register commands gcode = self.printer.lookup_object('gcode') @@ -130,20 +133,20 @@ class ADXLCommandHelper: if self.bg_client is None: # Start measurements self.bg_client = self.chip.start_internal_client() - gcmd.respond_info("adxl345 measurements started") + gcmd.respond_info("accelerometer measurements started") return # End measurements name = gcmd.get("NAME", time.strftime("%Y%m%d_%H%M%S")) if not name.replace('-', '').replace('_', '').isalnum(): - raise gcmd.error("Invalid adxl345 NAME parameter") + raise gcmd.error("Invalid NAME parameter") bg_client = self.bg_client self.bg_client = None bg_client.finish_measurements() # Write data to file - if self.name == "adxl345": - filename = "/tmp/adxl345-%s.csv" % (name,) + if self.base_name == self.name: + filename = "/tmp/%s-%s.csv" % (self.base_name, name) else: - filename = "/tmp/adxl345-%s-%s.csv" % (self.name, name,) + filename = "/tmp/%s-%s-%s.csv" % (self.base_name, self.name, name) bg_client.write_to_file(filename) gcmd.respond_info("Writing raw accelerometer data to %s file" % (filename,)) @@ -154,18 +157,18 @@ class ADXLCommandHelper: aclient.finish_measurements() values = aclient.get_samples() if not values: - raise gcmd.error("No adxl345 measurements found") + raise gcmd.error("No accelerometer measurements found") _, accel_x, accel_y, accel_z = values[-1] - gcmd.respond_info("adxl345 values (x, y, z): %.6f, %.6f, %.6f" + gcmd.respond_info("accelerometer values (x, y, z): %.6f, %.6f, %.6f" % (accel_x, accel_y, accel_z)) - cmd_ACCELEROMETER_DEBUG_READ_help = "Query adxl345 register (for debugging)" + cmd_ACCELEROMETER_DEBUG_READ_help = "Query register (for debugging)" def cmd_ACCELEROMETER_DEBUG_READ(self, gcmd): - reg = gcmd.get("REG", minval=29, maxval=57, parser=lambda x: int(x, 0)) + reg = gcmd.get("REG", minval=0, maxval=126, parser=lambda x: int(x, 0)) val = self.chip.read_reg(reg) - gcmd.respond_info("ADXL345 REG[0x%x] = 0x%x" % (reg, val)) - cmd_ACCELEROMETER_DEBUG_WRITE_help = "Set adxl345 register (for debugging)" + gcmd.respond_info("Accelerometer REG[0x%x] = 0x%x" % (reg, val)) + cmd_ACCELEROMETER_DEBUG_WRITE_help = "Set register (for debugging)" def cmd_ACCELEROMETER_DEBUG_WRITE(self, gcmd): - reg = gcmd.get("REG", minval=29, maxval=57, parser=lambda x: int(x, 0)) + reg = gcmd.get("REG", minval=0, maxval=126, parser=lambda x: int(x, 0)) val = gcmd.get("VAL", minval=0, maxval=255, parser=lambda x: int(x, 0)) self.chip.set_reg(reg, val) @@ -226,7 +229,7 @@ SAMPLES_PER_BLOCK = 10 class ADXL345: def __init__(self, config): self.printer = config.get_printer() - ADXLCommandHelper(config, self) + 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)} @@ -432,7 +435,7 @@ class ADXL345: web_request.send({'header': hdr}) def start_internal_client(self): cconn = self.api_dump.add_internal_client() - return ADXL345QueryHelper(self.printer, cconn) + return AccelQueryHelper(self.printer, cconn) def load_config(config): return ADXL345(config) diff --git a/klippy/extras/bed_mesh.py b/klippy/extras/bed_mesh.py index 3812b46c..ec686cae 100644 --- a/klippy/extras/bed_mesh.py +++ b/klippy/extras/bed_mesh.py @@ -124,6 +124,8 @@ class BedMesh: # Register transform gcode_move = self.printer.load_object(config, 'gcode_move') gcode_move.set_move_transform(self) + # initialize status dict + self.update_status() def handle_connect(self): self.toolhead = self.printer.lookup_object('toolhead') self.bmc.print_generated_points(logging.info) @@ -162,6 +164,7 @@ class BedMesh: # cache the current position before a transform takes place gcode_move = self.printer.lookup_object('gcode_move') gcode_move.reset_last_position() + self.update_status() def get_z_factor(self, z_pos): if z_pos >= self.fade_end: return 0. @@ -216,7 +219,9 @@ class BedMesh: "Mesh Leveling: Error splitting move ") self.last_position[:] = newpos def get_status(self, eventtime=None): - status = { + return self.status + def update_status(self): + self.status = { "profile_name": "", "mesh_min": (0., 0.), "mesh_max": (0., 0.), @@ -230,12 +235,11 @@ class BedMesh: mesh_max = (params['max_x'], params['max_y']) probed_matrix = self.z_mesh.get_probed_matrix() mesh_matrix = self.z_mesh.get_mesh_matrix() - status['profile_name'] = self.pmgr.get_current_profile() - status['mesh_min'] = mesh_min - status['mesh_max'] = mesh_max - status['probed_matrix'] = probed_matrix - status['mesh_matrix'] = mesh_matrix - return status + self.status['profile_name'] = self.pmgr.get_current_profile() + self.status['mesh_min'] = mesh_min + self.status['mesh_max'] = mesh_max + self.status['probed_matrix'] = probed_matrix + self.status['mesh_matrix'] = mesh_matrix def get_mesh(self): return self.z_mesh cmd_BED_MESH_OUTPUT_help = "Retrieve interpolated grid of probed z-points" @@ -1180,6 +1184,7 @@ class ProfileManager: profile['mesh_params'] = collections.OrderedDict(mesh_params) self.profiles = profiles self.current_profile = prof_name + self.bedmesh.update_status() self.gcode.respond_info( "Bed Mesh state has been saved to profile [%s]\n" "for the current session. The SAVE_CONFIG command will\n" @@ -1206,6 +1211,7 @@ class ProfileManager: profiles = dict(self.profiles) del profiles[prof_name] self.profiles = profiles + self.bedmesh.update_status() self.gcode.respond_info( "Profile [%s] removed from storage for this session.\n" "The SAVE_CONFIG command will update the printer\n" diff --git a/klippy/extras/bed_screws.py b/klippy/extras/bed_screws.py index 4a6acb83..4749d4e1 100644 --- a/klippy/extras/bed_screws.py +++ b/klippy/extras/bed_screws.py @@ -7,9 +7,7 @@ class BedScrews: def __init__(self, config): self.printer = config.get_printer() - self.state = None - self.current_screw = 0 - self.accepted_screws = 0 + self.reset() self.number_of_screws = 0 # Read config screws = [] @@ -39,6 +37,10 @@ class BedScrews: self.gcode.register_command("BED_SCREWS_ADJUST", self.cmd_BED_SCREWS_ADJUST, desc=self.cmd_BED_SCREWS_ADJUST_help) + def reset(self): + self.state = None + self.current_screw = 0 + self.accepted_screws = 0 def move(self, coord, speed): self.printer.lookup_object('toolhead').manual_move(coord, speed) def move_to_screw(self, state, screw): @@ -64,6 +66,13 @@ class BedScrews: self.gcode.register_command('ACCEPT', None) self.gcode.register_command('ADJUSTED', None) self.gcode.register_command('ABORT', None) + def get_status(self, eventtime): + return { + 'is_active': self.state is not None, + 'state': self.state, + 'current_screw': self.current_screw, + 'accepted_screws': self.accepted_screws + } cmd_BED_SCREWS_ADJUST_help = "Tool to help adjust bed leveling screws" def cmd_BED_SCREWS_ADJUST(self, gcmd): if self.state is not None: @@ -92,7 +101,7 @@ class BedScrews: self.move_to_screw('fine', 0) return # Done - self.state = None + self.reset() self.move((None, None, self.horizontal_move_z), self.lift_speed) gcmd.respond_info("Bed screws tool completed successfully") cmd_ADJUSTED_help = "Accept bed screw position after notable adjustment" @@ -103,7 +112,7 @@ class BedScrews: cmd_ABORT_help = "Abort bed screws tool" def cmd_ABORT(self, gcmd): self.unregister_commands() - self.state = None + self.reset() def load_config(config): return BedScrews(config) diff --git a/klippy/extras/canbus_ids.py b/klippy/extras/canbus_ids.py index 5e70f8b9..f96510fa 100644 --- a/klippy/extras/canbus_ids.py +++ b/klippy/extras/canbus_ids.py @@ -4,6 +4,8 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. +NODEID_FIRST = 4 + class PrinterCANBus: def __init__(self, config): self.printer = config.get_printer() @@ -11,7 +13,7 @@ class PrinterCANBus: def add_uuid(self, config, canbus_uuid, canbus_iface): if canbus_uuid in self.ids: raise config.error("Duplicate canbus_uuid") - new_id = len(self.ids) + new_id = len(self.ids) + NODEID_FIRST self.ids[canbus_uuid] = new_id return new_id def get_nodeid(self, canbus_uuid): diff --git a/klippy/extras/delta_calibrate.py b/klippy/extras/delta_calibrate.py index 04cbe29e..4054e231 100644 --- a/klippy/extras/delta_calibrate.py +++ b/klippy/extras/delta_calibrate.py @@ -45,7 +45,7 @@ def measurements_to_distances(measured_params, delta_params): od - opw for od, opw in zip(mp['OUTER_DISTS'], mp['OUTER_PILLAR_WIDTHS']) ] # Convert angles in degrees to an XY multiplier - obj_angles = map(math.radians, MeasureAngles) + obj_angles = list(map(math.radians, MeasureAngles)) xy_angles = list(zip(map(math.cos, obj_angles), map(math.sin, obj_angles))) # Calculate stable positions for center measurements inner_ridge = MeasureRidgeRadius * scale diff --git a/klippy/extras/display_status.py b/klippy/extras/display_status.py index be7a58b8..5b2b6bec 100644 --- a/klippy/extras/display_status.py +++ b/klippy/extras/display_status.py @@ -16,6 +16,9 @@ class DisplayStatus: gcode = self.printer.lookup_object('gcode') gcode.register_command('M73', self.cmd_M73) gcode.register_command('M117', self.cmd_M117) + gcode.register_command( + 'SET_DISPLAY_TEXT', self.cmd_SET_DISPLAY_TEXT, + desc=self.cmd_SET_DISPLAY_TEXT_help) def get_status(self, eventtime): progress = self.progress if progress is not None and eventtime > self.expire_progress: @@ -39,6 +42,9 @@ class DisplayStatus: def cmd_M117(self, gcmd): msg = gcmd.get_raw_command_parameters() or None self.message = msg + cmd_SET_DISPLAY_TEXT_help = "Set or clear the display message" + def cmd_SET_DISPLAY_TEXT(self, gcmd): + self.message = gcmd.get("MSG", None) def load_config(config): return DisplayStatus(config) diff --git a/klippy/extras/ds18b20.py b/klippy/extras/ds18b20.py index 749ae520..37b30104 100644 --- a/klippy/extras/ds18b20.py +++ b/klippy/extras/ds18b20.py @@ -10,6 +10,7 @@ DS18_REPORT_TIME = 3.0 # Temperature can be sampled at any time but conversion time is ~750ms, so # setting the time too low will not make the reports come faster. DS18_MIN_REPORT_TIME = 1.0 +DS18_MAX_CONSECUTIVE_ERRORS = 4 class DS18B20: def __init__(self, config): @@ -31,8 +32,9 @@ class DS18B20: def _build_config(self): sid = "".join(["%02x" % (x,) for x in self.sensor_id]) - self._mcu.add_config_cmd("config_ds18b20 oid=%d serial=%s" - % (self.oid, sid)) + self._mcu.add_config_cmd( + "config_ds18b20 oid=%d serial=%s max_error_count=%d" + % (self.oid, sid, DS18_MAX_CONSECUTIVE_ERRORS)) clock = self._mcu.get_query_slot(self.oid) self._report_clock = self._mcu.seconds_to_clock(self.report_time) @@ -44,10 +46,10 @@ class DS18B20: def _handle_ds18b20_response(self, params): temp = params['value'] / 1000.0 - if temp < self.min_temp or temp > self.max_temp: - self.printer.invoke_shutdown( - "DS18B20 temperature %0.1f outside range of %0.1f:%.01f" - % (temp, self.min_temp, self.max_temp)) + if params["fault"]: + logging.info("ds18b20 reports fault %d (temp=%0.1f)", + params["fault"], temp) + return next_clock = self._mcu.clock32_to_clock64(params['next_clock']) last_read_clock = next_clock - self._report_clock diff --git a/klippy/extras/exclude_object.py b/klippy/extras/exclude_object.py new file mode 100644 index 00000000..0a68d9b5 --- /dev/null +++ b/klippy/extras/exclude_object.py @@ -0,0 +1,302 @@ +# Exclude moves toward and inside objects +# +# Copyright (C) 2019 Eric Callahan +# Copyright (C) 2021 Troy Jacobson +# +# This file may be distributed under the terms of the GNU GPLv3 license. + +import logging +import json + +class ExcludeObject: + def __init__(self, config): + self.printer = config.get_printer() + self.gcode = self.printer.lookup_object('gcode') + self.gcode_move = self.printer.load_object(config, 'gcode_move') + self.printer.register_event_handler('klippy:connect', + self._handle_connect) + self.printer.register_event_handler("virtual_sdcard:reset_file", + self._reset_file) + self.next_transform = None + self.last_position_extruded = [0., 0., 0., 0.] + self.last_position_excluded = [0., 0., 0., 0.] + + self._reset_state() + self.gcode.register_command( + 'EXCLUDE_OBJECT_START', self.cmd_EXCLUDE_OBJECT_START, + desc=self.cmd_EXCLUDE_OBJECT_START_help) + self.gcode.register_command( + 'EXCLUDE_OBJECT_END', self.cmd_EXCLUDE_OBJECT_END, + desc=self.cmd_EXCLUDE_OBJECT_END_help) + self.gcode.register_command( + 'EXCLUDE_OBJECT', self.cmd_EXCLUDE_OBJECT, + desc=self.cmd_EXCLUDE_OBJECT_help) + self.gcode.register_command( + 'EXCLUDE_OBJECT_DEFINE', self.cmd_EXCLUDE_OBJECT_DEFINE, + desc=self.cmd_EXCLUDE_OBJECT_DEFINE_help) + + def _register_transform(self): + if self.next_transform is None: + tuning_tower = self.printer.lookup_object('tuning_tower') + if tuning_tower.is_active(): + logging.info('The ExcludeObject move transform is not being ' + 'loaded due to Tuning tower being Active') + return + + self.next_transform = self.gcode_move.set_move_transform(self, + force=True) + self.extrusion_offsets = {} + self.max_position_extruded = 0 + self.max_position_excluded = 0 + self.extruder_adj = 0 + self.initial_extrusion_moves = 5 + self.last_position = [0., 0., 0., 0.] + + self.get_position() + self.last_position_extruded[:] = self.last_position + self.last_position_excluded[:] = self.last_position + + def _handle_connect(self): + self.toolhead = self.printer.lookup_object('toolhead') + + def _unregister_transform(self): + if self.next_transform: + tuning_tower = self.printer.lookup_object('tuning_tower') + if tuning_tower.is_active(): + logging.error('The Exclude Object move transform was not ' + 'unregistered because it is not at the head of the ' + 'transform chain.') + return + + self.gcode_move.set_move_transform(self.next_transform, force=True) + self.next_transform = None + self.gcode_move.reset_last_position() + + def _reset_state(self): + self.objects = [] + self.excluded_objects = [] + self.current_object = None + self.in_excluded_region = False + + def _reset_file(self): + self._reset_state() + self._unregister_transform() + + def _get_extrusion_offsets(self): + offset = self.extrusion_offsets.get( + self.toolhead.get_extruder().get_name()) + if offset is None: + offset = [0., 0., 0., 0.] + self.extrusion_offsets[self.toolhead.get_extruder().get_name()] = \ + offset + return offset + + def get_position(self): + offset = self._get_extrusion_offsets() + pos = self.next_transform.get_position() + for i in range(4): + self.last_position[i] = pos[i] + offset[i] + return list(self.last_position) + + def _normal_move(self, newpos, speed): + offset = self._get_extrusion_offsets() + + if self.initial_extrusion_moves > 0 and \ + self.last_position[3] != newpos[3]: + # Since the transform is not loaded until there is a request to + # exclude an object, the transform needs to track a few extrusions + # to get the state of the extruder + self.initial_extrusion_moves -= 1 + + self.last_position[:] = newpos + self.last_position_extruded[:] = self.last_position + self.max_position_extruded = max(self.max_position_extruded, newpos[3]) + + # These next few conditionals handle the moves immediately after leaving + # and excluded object. The toolhead is at the end of the last printed + # object and the gcode is at the end of the last excluded object. + # + # Ideally, there will be Z and E moves right away to adjust any offsets + # before moving away from the last position. Any remaining corrections + # will be made on the firs XY move. + if (offset[0] != 0 or offset[1] != 0) and \ + (newpos[0] != self.last_position_excluded[0] or \ + newpos[1] != self.last_position_excluded[1]): + offset[0] = 0 + offset[1] = 0 + offset[2] = 0 + offset[3] += self.extruder_adj + self.extruder_adj = 0 + + if offset[2] != 0 and newpos[2] != self.last_position_excluded[2]: + offset[2] = 0 + + if self.extruder_adj != 0 and \ + newpos[3] != self.last_position_excluded[3]: + offset[3] += self.extruder_adj + self.extruder_adj = 0 + + tx_pos = newpos[:] + for i in range(4): + tx_pos[i] = newpos[i] - offset[i] + self.next_transform.move(tx_pos, speed) + + def _ignore_move(self, newpos, speed): + offset = self._get_extrusion_offsets() + for i in range(3): + offset[i] = newpos[i] - self.last_position_extruded[i] + offset[3] = offset[3] + newpos[3] - self.last_position[3] + self.last_position[:] = newpos + self.last_position_excluded[:] =self.last_position + self.max_position_excluded = max(self.max_position_excluded, newpos[3]) + + def _move_into_excluded_region(self, newpos, speed): + self.in_excluded_region = True + self._ignore_move(newpos, speed) + + def _move_from_excluded_region(self, newpos, speed): + self.in_excluded_region = False + + # This adjustment value is used to compensate for any retraction + # differences between the last object printed and excluded one. + self.extruder_adj = self.max_position_excluded \ + - self.last_position_excluded[3] \ + - (self.max_position_extruded - self.last_position_extruded[3]) + self._normal_move(newpos, speed) + + def _test_in_excluded_region(self): + # Inside cancelled object + return self.current_object in self.excluded_objects \ + and self.initial_extrusion_moves == 0 + + def get_status(self, eventtime=None): + status = { + "objects": self.objects, + "excluded_objects": self.excluded_objects, + "current_object": self.current_object + } + return status + + def move(self, newpos, speed): + move_in_excluded_region = self._test_in_excluded_region() + self.last_speed = speed + + if move_in_excluded_region: + if self.in_excluded_region: + self._ignore_move(newpos, speed) + else: + self._move_into_excluded_region(newpos, speed) + else: + if self.in_excluded_region: + self._move_from_excluded_region(newpos, speed) + else: + self._normal_move(newpos, speed) + + cmd_EXCLUDE_OBJECT_START_help = "Marks the beginning the current object" \ + " as labeled" + def cmd_EXCLUDE_OBJECT_START(self, gcmd): + name = gcmd.get('NAME').upper() + if not any(obj["name"] == name for obj in self.objects): + self._add_object_definition({"name": name}) + self.current_object = name + self.was_excluded_at_start = self._test_in_excluded_region() + + cmd_EXCLUDE_OBJECT_END_help = "Marks the end the current object" + def cmd_EXCLUDE_OBJECT_END(self, gcmd): + if self.current_object == None and self.next_transform: + gcmd.respond_info("EXCLUDE_OBJECT_END called, but no object is" + " currently active") + return + name = gcmd.get('NAME', default=None) + if name != None and name.upper() != self.current_object: + gcmd.respond_info("EXCLUDE_OBJECT_END NAME=%s does not match the" + " current object NAME=%s" % + (name.upper(), self.current_object)) + + self.current_object = None + + cmd_EXCLUDE_OBJECT_help = "Cancel moves inside a specified objects" + def cmd_EXCLUDE_OBJECT(self, gcmd): + reset = gcmd.get('RESET', None) + current = gcmd.get('CURRENT', None) + name = gcmd.get('NAME', '').upper() + + if reset: + if name: + self._unexclude_object(name) + + else: + self.excluded_objects = [] + + elif name: + if name.upper() not in self.excluded_objects: + self._exclude_object(name.upper()) + + elif current: + if not self.current_object: + gcmd.respond_error('There is no current object to cancel') + + else: + self._exclude_object(self.current_object) + + else: + self._list_excluded_objects(gcmd) + + cmd_EXCLUDE_OBJECT_DEFINE_help = "Provides a summary of an object" + def cmd_EXCLUDE_OBJECT_DEFINE(self, gcmd): + reset = gcmd.get('RESET', None) + name = gcmd.get('NAME', '').upper() + + if reset: + self._reset_file() + + elif name: + parameters = gcmd.get_command_parameters().copy() + parameters.pop('NAME') + center = parameters.pop('CENTER', None) + polygon = parameters.pop('POLYGON', None) + + obj = {"name": name.upper()} + obj.update(parameters) + + if center != None: + obj['center'] = json.loads('[%s]' % center) + + if polygon != None: + obj['polygon'] = json.loads(polygon) + + self._add_object_definition(obj) + + else: + self._list_objects(gcmd) + + def _add_object_definition(self, definition): + self.objects = sorted(self.objects + [definition], + key=lambda o: o["name"]) + + def _exclude_object(self, name): + self._register_transform() + self.gcode.respond_info('Excluding object {}'.format(name.upper())) + if name not in self.excluded_objects: + self.excluded_objects = sorted(self.excluded_objects + [name]) + + def _unexclude_object(self, name): + self.gcode.respond_info('Unexcluding object {}'.format(name.upper())) + if name in self.excluded_objects: + excluded_objects = list(self.excluded_objects) + excluded_objects.remove(name) + self.excluded_objects = sorted(excluded_objects) + + def _list_objects(self, gcmd): + if gcmd.get('JSON', None) is not None: + object_list = json.dumps(self.objects) + else: + object_list = " ".join(obj['name'] for obj in self.objects) + gcmd.respond_info('Known objects: {}'.format(object_list)) + + def _list_excluded_objects(self, gcmd): + object_list = " ".join(self.excluded_objects) + gcmd.respond_info('Excluded objects: {}'.format(object_list)) + +def load_config(config): + return ExcludeObject(config) diff --git a/klippy/extras/manual_probe.py b/klippy/extras/manual_probe.py index eb74ff20..c6e9dc64 100644 --- a/klippy/extras/manual_probe.py +++ b/klippy/extras/manual_probe.py @@ -24,9 +24,19 @@ class ManualProbe: 'Z_OFFSET_APPLY_ENDSTOP', self.cmd_Z_OFFSET_APPLY_ENDSTOP, desc=self.cmd_Z_OFFSET_APPLY_ENDSTOP_help) + self.reset_status() def manual_probe_finalize(self, kin_pos): if kin_pos is not None: self.gcode.respond_info("Z position is %.3f" % (kin_pos[2],)) + def reset_status(self): + self.status = { + 'is_active': False, + 'z_position': None, + 'z_position_lower': None, + 'z_position_upper': None + } + def get_status(self, eventtime): + return self.status cmd_MANUAL_PROBE_help = "Start manual probe helper script" def cmd_MANUAL_PROBE(self, gcmd): ManualProbeHelper(self.printer, gcmd, self.manual_probe_finalize) @@ -78,6 +88,7 @@ class ManualProbeHelper: self.finalize_callback = finalize_callback self.gcode = self.printer.lookup_object('gcode') self.toolhead = self.printer.lookup_object('toolhead') + self.manual_probe = self.printer.lookup_object('manual_probe') self.speed = gcmd.get_float("SPEED", 5.) self.past_positions = [] self.last_toolhead_pos = self.last_kinematics_pos = None @@ -130,11 +141,20 @@ class ManualProbeHelper: prev_pos = next_pos - 1 if next_pos < len(pp) and pp[next_pos] == z_pos: next_pos += 1 + prev_pos_val = next_pos_val = None prev_str = next_str = "??????" if prev_pos >= 0: - prev_str = "%.3f" % (pp[prev_pos],) + prev_pos_val = pp[prev_pos] + prev_str = "%.3f" % (prev_pos_val,) if next_pos < len(pp): - next_str = "%.3f" % (pp[next_pos],) + next_pos_val = pp[next_pos] + next_str = "%.3f" % (next_pos_val,) + self.manual_probe.status = { + 'is_active': True, + 'z_position': z_pos, + 'z_position_lower': prev_pos_val, + 'z_position_upper': next_pos_val, + } # Find recent positions self.gcode.respond_info("Z position: %s --> %.3f <-- %s" % (prev_str, z_pos, next_str)) @@ -183,6 +203,7 @@ class ManualProbeHelper: self.move_z(next_z_pos) self.report_z_status(next_z_pos != z_pos, z_pos) def finalize(self, success): + self.manual_probe.reset_status() self.gcode.register_command('ACCEPT', None) self.gcode.register_command('NEXT', None) self.gcode.register_command('ABORT', None) diff --git a/klippy/extras/mpu9250.py b/klippy/extras/mpu9250.py new file mode 100644 index 00000000..c588eaf0 --- /dev/null +++ b/klippy/extras/mpu9250.py @@ -0,0 +1,268 @@ +# Support for reading acceleration data from an mpu9250 chip +# +# Copyright (C) 2022 Harry Beyel +# 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 + +MPU9250_ADDR = 0x68 + +MPU9250_DEV_ID = 0x73 +MPU6050_DEV_ID = 0x68 + +# MPU9250 registers +REG_DEVID = 0x75 +REG_FIFO_EN = 0x23 +REG_SMPLRT_DIV = 0x19 +REG_CONFIG = 0x1A +REG_ACCEL_CONFIG = 0x1C +REG_ACCEL_CONFIG2 = 0x1D +REG_USER_CTRL = 0x6A +REG_PWR_MGMT_1 = 0x6B +REG_PWR_MGMT_2 = 0x6C + +SAMPLE_RATE_DIVS = { 4000:0x00 } + +SET_CONFIG = 0x01 # FIFO mode 'stream' style +SET_ACCEL_CONFIG = 0x10 # 8g full scale +SET_ACCEL_CONFIG2 = 0x08 # 1046Hz BW, 0.503ms delay 4kHz sample rate +SET_PWR_MGMT_1_WAKE = 0x00 +SET_PWR_MGMT_1_SLEEP= 0x40 +SET_PWR_MGMT_2_ACCEL_ON = 0x07 +SET_PWR_MGMT_2_OFF = 0x3F + +FREEFALL_ACCEL = 9.80665 * 1000. +# SCALE = 1/4096 g/LSB @8g scale * Earth gravity in mm/s**2 +SCALE = 0.000244140625 * FREEFALL_ACCEL + +FIFO_SIZE = 512 + +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 MPU9250 chip +class MPU9250: + 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 mpu9250 axes_map parameter") + self.axes_map = [am[a.strip()] for a in axes_map] + self.data_rate = config.getint('rate', 4000) + if self.data_rate not in SAMPLE_RATE_DIVS: + raise config.error("Invalid rate parameter: %d" % (self.data_rate,)) + # Measurement storage (accessed from background thread) + self.lock = threading.Lock() + self.raw_samples = [] + # Setup mcu sensor_mpu9250 bulk query code + self.i2c = bus.MCU_I2C_from_config(config, + default_addr=MPU9250_ADDR, + default_speed=400000) + self.mcu = mcu = self.i2c.get_mcu() + self.oid = oid = mcu.create_oid() + self.query_mpu9250_cmd = self.query_mpu9250_end_cmd = None + self.query_mpu9250_status_cmd = None + mcu.register_config_callback(self._build_config) + mcu.register_response(self._handle_mpu9250_data, "mpu9250_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("mpu9250/dump_mpu9250", "sensor", self.name, + self._handle_dump_mpu9250) + def _build_config(self): + cmdqueue = self.i2c.get_command_queue() + self.mcu.add_config_cmd("config_mpu9250 oid=%d i2c_oid=%d" + % (self.oid, self.i2c.get_oid())) + self.mcu.add_config_cmd("query_mpu9250 oid=%d clock=0 rest_ticks=0" + % (self.oid,), on_restart=True) + self.query_mpu9250_cmd = self.mcu.lookup_command( + "query_mpu9250 oid=%c clock=%u rest_ticks=%u", cq=cmdqueue) + self.query_mpu9250_end_cmd = self.mcu.lookup_query_command( + "query_mpu9250 oid=%c clock=%u rest_ticks=%u", + "mpu9250_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" + " buffered=%c fifo=%u limit_count=%hu", oid=self.oid, cq=cmdqueue) + self.query_mpu9250_status_cmd = self.mcu.lookup_query_command( + "query_mpu9250_status oid=%c", + "mpu9250_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" + " buffered=%c fifo=%u limit_count=%hu", oid=self.oid, cq=cmdqueue) + def read_reg(self, reg): + params = self.i2c.i2c_read([reg], 1) + return bytearray(params['response'])[0] + + def set_reg(self, reg, val, minclock=0): + self.i2c.i2c_write([reg, val & 0xFF], minclock=minclock) + + # Measurement collection + def is_measuring(self): + return self.query_rate > 0 + def _handle_mpu9250_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] + xhigh, xlow, yhigh, ylow, zhigh, zlow = 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_mpu9250_status_cmd.send([self.oid], + minclock=minclock) + fifo = params['fifo'] & 0x1fff + if fifo <= FIFO_SIZE: + break + else: + raise self.printer.command_error("Unable to query mpu9250 fifo") + mcu_clock = self.mcu.clock32_to_clock64(params['clock']) + sequence = (self.last_sequence & ~0xffff) | params['next_sequence'] + if sequence < self.last_sequence: + sequence += 0x10000 + self.last_sequence = sequence + 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 mpu9250 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 MPU9250 device ID prevents treating + # noise or wrong signal as a correctly initialized device + dev_id = self.read_reg(REG_DEVID) + if dev_id != MPU9250_DEV_ID and dev_id != MPU6050_DEV_ID: + raise self.printer.command_error( + "Invalid mpu9250/mpu6050 id (got %x).\n" + "This is generally indicative of connection problems\n" + "(e.g. faulty wiring) or a faulty chip." + % (dev_id)) + # Setup chip in requested query rate + self.set_reg(REG_PWR_MGMT_1, SET_PWR_MGMT_1_WAKE) + self.set_reg(REG_PWR_MGMT_2, SET_PWR_MGMT_2_ACCEL_ON) + time.sleep(20. / 1000) # wait for accelerometer chip wake up + self.set_reg(REG_SMPLRT_DIV, SAMPLE_RATE_DIVS[self.data_rate]) + self.set_reg(REG_CONFIG, SET_CONFIG) + self.set_reg(REG_ACCEL_CONFIG, SET_ACCEL_CONFIG) + self.set_reg(REG_ACCEL_CONFIG2, SET_ACCEL_CONFIG2) + + # 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(1. / self.data_rate) + self.query_rate = self.data_rate + self.query_mpu9250_cmd.send([self.oid, reqclock, rest_ticks], + reqclock=reqclock) + logging.info("MPU9250 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_mpu9250_end_cmd.send([self.oid, 0, 0]) + self.query_rate = 0 + with self.lock: + self.raw_samples = [] + logging.info("MPU9250 finished '%s' measurements", self.name) + self.set_reg(REG_PWR_MGMT_1, SET_PWR_MGMT_1_SLEEP) + self.set_reg(REG_PWR_MGMT_2, SET_PWR_MGMT_2_OFF) + + # 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_mpu9250(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 MPU9250(config) + +def load_config_prefix(config): + return MPU9250(config) diff --git a/klippy/extras/palette2.py b/klippy/extras/palette2.py index c0289399..c3c43ea6 100644 --- a/klippy/extras/palette2.py +++ b/klippy/extras/palette2.py @@ -544,13 +544,15 @@ class Palette2: self.cmd_Disconnect() return self.reactor.NEVER if len(raw_bytes): - text_buffer = self.read_buffer + str(raw_bytes.decode()) + new_buffer = str(raw_bytes.decode(encoding='UTF-8', + errors='ignore')) + text_buffer = self.read_buffer + new_buffer while True: i = text_buffer.find("\n") if i >= 0: - line = text_buffer[0:i+1] + line = text_buffer[0:i + 1] self.read_queue.put(line.strip()) - text_buffer = text_buffer[i+1:] + text_buffer = text_buffer[i + 1:] else: break self.read_buffer = text_buffer @@ -566,7 +568,7 @@ class Palette2: heartbeat_strings = [COMMAND_HEARTBEAT, "Connection Okay"] if not any(x in text_line for x in heartbeat_strings): - logging.debug("%0.3f P2 -> : %s" %(eventtime, text_line)) + logging.debug("%0.3f P2 -> : %s" % (eventtime, text_line)) # Received a heartbeat from the device if text_line == COMMAND_HEARTBEAT: @@ -621,7 +623,7 @@ class Palette2: idle_time = est_print_time - print_time if not lookahead_empty or idle_time < 0.5: return eventtime + \ - max(0., min(1., print_time - est_print_time)) + max(0., min(1., print_time - est_print_time)) extrude = abs(self.remaining_load_length) extrude = min(50, extrude / 2) @@ -646,5 +648,6 @@ class Palette2: status["ping"] = self.omega_pings[-1] return status + def load_config(config): return Palette2(config) diff --git a/klippy/extras/resonance_tester.py b/klippy/extras/resonance_tester.py index 00797156..db03e320 100644 --- a/klippy/extras/resonance_tester.py +++ b/klippy/extras/resonance_tester.py @@ -147,15 +147,21 @@ class ResonanceTester: (chip_axis, self.printer.lookup_object(chip_name)) for chip_axis, chip_name in self.accel_chip_names] - def _run_test(self, gcmd, axes, helper, raw_name_suffix=None): + def _run_test(self, gcmd, axes, helper, raw_name_suffix=None, + accel_chips=None, test_point=None): toolhead = self.printer.lookup_object('toolhead') calibration_data = {axis: None for axis in axes} self.test.prepare_test(gcmd) - test_points = self.test.get_start_test_points() + + if test_point is not None: + test_points = [test_point] + else: + test_points = self.test.get_start_test_points() + for point in test_points: toolhead.manual_move(point, self.move_speed) - if len(test_points) > 1: + if len(test_points) > 1 or test_point is not None: gcmd.respond_info( "Probing point (%.3f, %.3f, %.3f)" % tuple(point)) for axis in axes: @@ -165,29 +171,36 @@ class ResonanceTester: gcmd.respond_info("Testing axis %s" % axis.get_name()) raw_values = [] - for chip_axis, chip in self.accel_chips: - if axis.matches(chip_axis): + if accel_chips is None: + for chip_axis, chip in self.accel_chips: + if axis.matches(chip_axis): + aclient = chip.start_internal_client() + raw_values.append((chip_axis, aclient, chip.name)) + else: + for chip in accel_chips: aclient = chip.start_internal_client() - raw_values.append((chip_axis, aclient)) + raw_values.append((axis, aclient, chip.name)) + # Generate moves self.test.run_test(axis, gcmd) - for chip_axis, aclient in raw_values: + for chip_axis, aclient, chip_name in raw_values: aclient.finish_measurements() if raw_name_suffix is not None: raw_name = self.get_filename( 'raw_data', raw_name_suffix, axis, - point if len(test_points) > 1 else None) + point if len(test_points) > 1 else None, + chip_name if accel_chips is not None else None,) aclient.write_to_file(raw_name) gcmd.respond_info( "Writing raw accelerometer data to " "%s file" % (raw_name,)) if helper is None: continue - for chip_axis, aclient in raw_values: + for chip_axis, aclient, chip_name in raw_values: if not aclient.has_valid_samples(): raise gcmd.error( - "%s-axis accelerometer measured no data" % ( - chip_axis,)) + "accelerometer '%s' measured no data" % ( + chip_name,)) new_data = helper.process_accelerometer_data(aclient) if calibration_data[axis] is None: calibration_data[axis] = new_data @@ -198,6 +211,28 @@ class ResonanceTester: def cmd_TEST_RESONANCES(self, gcmd): # Parse parameters axis = _parse_axis(gcmd, gcmd.get("AXIS").lower()) + accel_chips = gcmd.get("CHIPS", None) + test_point = gcmd.get("POINT", None) + + if test_point: + test_coords = test_point.split(',') + if len(test_coords) != 3: + raise gcmd.error("Invalid POINT parameter, must be 'x,y,z'") + try: + test_point = [float(p.strip()) for p in test_coords] + except ValueError: + raise gcmd.error("Invalid POINT parameter, must be 'x,y,z'" + " where x, y and z are valid floating point numbers") + + if accel_chips: + parsed_chips = [] + for chip_name in accel_chips.split(','): + if "adxl345" in chip_name: + chip_lookup_name = chip_name.strip() + else: + chip_lookup_name = "adxl345 " + chip_name.strip(); + chip = self.printer.lookup_object(chip_lookup_name) + parsed_chips.append(chip) outputs = gcmd.get("OUTPUT", "resonances").lower().split(',') for output in outputs: @@ -221,10 +256,13 @@ class ResonanceTester: data = self._run_test( gcmd, [axis], helper, - raw_name_suffix=name_suffix if raw_output else None)[axis] + raw_name_suffix=name_suffix if raw_output else None, + accel_chips=parsed_chips if accel_chips else None, + test_point=test_point)[axis] if csv_output: csv_name = self.save_calibration_data('resonances', name_suffix, - helper, axis, data) + helper, axis, data, + point=test_point) gcmd.respond_info( "Resonances data written to %s file" % (csv_name,)) cmd_SHAPER_CALIBRATE_help = ( @@ -287,7 +325,8 @@ class ResonanceTester: for chip_axis, aclient in raw_values: if not aclient.has_valid_samples(): raise gcmd.error( - "%s-axis accelerometer measured no data" % (chip_axis,)) + "%s-axis accelerometer measured no data" % ( + chip_axis,)) data = helper.process_accelerometer_data(aclient) vx = data.psd_x.mean() vy = data.psd_y.mean() @@ -299,18 +338,22 @@ class ResonanceTester: def is_valid_name_suffix(self, name_suffix): return name_suffix.replace('-', '').replace('_', '').isalnum() - def get_filename(self, base, name_suffix, axis=None, point=None): + def get_filename(self, base, name_suffix, axis=None, + point=None, chip_name=None): name = base if axis: name += '_' + axis.get_name() + if chip_name: + name += '_' + chip_name.replace(" ", "_") if point: name += "_%.3f_%.3f_%.3f" % (point[0], point[1], point[2]) name += '_' + name_suffix return os.path.join("/tmp", name + ".csv") def save_calibration_data(self, base_name, name_suffix, shaper_calibrate, - axis, calibration_data, all_shapers=None): - output = self.get_filename(base_name, name_suffix, axis) + axis, calibration_data, + all_shapers=None, point=None): + output = self.get_filename(base_name, name_suffix, axis, point) shaper_calibrate.save_calibration_data(output, calibration_data, all_shapers) return output diff --git a/klippy/extras/respond.py b/klippy/extras/respond.py index fb6eb194..047abb77 100644 --- a/klippy/extras/respond.py +++ b/klippy/extras/respond.py @@ -10,6 +10,10 @@ respond_types = { 'error' : '!!', } +respond_types_no_space = { + 'echo_no_space': 'echo:', +} + class HostResponder: def __init__(self, config): self.printer = config.get_printer() @@ -26,19 +30,26 @@ class HostResponder: gcmd.respond_raw("%s %s" % (self.default_prefix, msg)) cmd_RESPOND_help = ("Echo the message prepended with a prefix") def cmd_RESPOND(self, gcmd): + no_space = False respond_type = gcmd.get('TYPE', None) prefix = self.default_prefix if(respond_type != None): respond_type = respond_type.lower() if(respond_type in respond_types): prefix = respond_types[respond_type] + elif(respond_type in respond_types_no_space): + prefix = respond_types_no_space[respond_type] + no_space = True else: raise gcmd.error( "RESPOND TYPE '%s' is invalid. Must be one" " of 'echo', 'command', or 'error'" % (respond_type,)) prefix = gcmd.get('PREFIX', prefix) msg = gcmd.get('MSG', '') - gcmd.respond_raw("%s %s" % (prefix, msg)) + if(no_space): + gcmd.respond_raw("%s%s" % (prefix, msg)) + else: + gcmd.respond_raw("%s %s" % (prefix, msg)) def load_config(config): return HostResponder(config) diff --git a/klippy/extras/temperature_mcu.py b/klippy/extras/temperature_mcu.py index 349050a9..e82761b5 100644 --- a/klippy/extras/temperature_mcu.py +++ b/klippy/extras/temperature_mcu.py @@ -72,6 +72,7 @@ class PrinterTemperatureMCU: ('stm32f070', self.config_stm32f070), ('stm32f072', self.config_stm32f0x2), ('stm32g0', self.config_stm32g0), + ('stm32h7', self.config_stm32h7), ('', self.config_unknown)] for name, func in cfg_funcs: if self.mcu_type.startswith(name): @@ -143,6 +144,11 @@ class PrinterTemperatureMCU: cal_adc_130 = self.read16(0x1FFF75CA) * 3.0 / (3.3 * 4095.) self.slope = (130. - 30.) / (cal_adc_130 - cal_adc_30) self.base_temperature = self.calc_base(30., cal_adc_30) + def config_stm32h7(self): + cal_adc_30 = self.read16(0x1FF1E820) / 65535. + cal_adc_110 = self.read16(0x1FF1E840) / 65535. + self.slope = (110. - 30.) / (cal_adc_110 - cal_adc_30) + self.base_temperature = self.calc_base(30., cal_adc_30) def read16(self, addr): params = self.debug_read_cmd.send([1, addr]) return params['val'] diff --git a/klippy/extras/tuning_tower.py b/klippy/extras/tuning_tower.py index 493db2c8..4fec5b1b 100644 --- a/klippy/extras/tuning_tower.py +++ b/klippy/extras/tuning_tower.py @@ -99,6 +99,8 @@ class TuningTower: self.gcode.respond_info("Ending tuning test mode") self.gcode_move.set_move_transform(self.normal_transform, force=True) self.normal_transform = None + def is_active(self): + return self.normal_transform is not None def load_config(config): return TuningTower(config) diff --git a/klippy/extras/virtual_sdcard.py b/klippy/extras/virtual_sdcard.py index 397c6513..daf19db9 100644 --- a/klippy/extras/virtual_sdcard.py +++ b/klippy/extras/virtual_sdcard.py @@ -9,22 +9,27 @@ VALID_GCODE_EXTS = ['gcode', 'g', 'gco'] class VirtualSD: def __init__(self, config): - printer = config.get_printer() - printer.register_event_handler("klippy:shutdown", self.handle_shutdown) + self.printer = config.get_printer() + self.printer.register_event_handler("klippy:shutdown", + self.handle_shutdown) # sdcard state sd = config.get('path') self.sdcard_dirname = os.path.normpath(os.path.expanduser(sd)) self.current_file = None self.file_position = self.file_size = 0 # Print Stat Tracking - self.print_stats = printer.load_object(config, 'print_stats') + self.print_stats = self.printer.load_object(config, 'print_stats') # Work timer - self.reactor = printer.get_reactor() + self.reactor = self.printer.get_reactor() self.must_pause_work = self.cmd_from_sd = False self.next_file_position = 0 self.work_timer = None + # Error handling + gcode_macro = self.printer.load_object(config, 'gcode_macro') + self.on_error_gcode = gcode_macro.load_template( + config, 'on_error_gcode', '') # Register commands - self.gcode = printer.lookup_object('gcode') + self.gcode = self.printer.lookup_object('gcode') for cmd in ['M20', 'M21', 'M23', 'M24', 'M25', 'M26', 'M27']: self.gcode.register_command(cmd, getattr(self, 'cmd_' + cmd)) for cmd in ['M28', 'M29', 'M30']: @@ -125,6 +130,7 @@ class VirtualSD: self.current_file = None self.file_position = self.file_size = 0. self.print_stats.reset() + self.printer.send_event("virtual_sdcard:reset_file") cmd_SDCARD_RESET_FILE_help = "Clears a loaded SD File. Stops the print "\ "if necessary" def cmd_SDCARD_RESET_FILE(self, gcmd): @@ -258,6 +264,10 @@ class VirtualSD: self.gcode.run_script(line) except self.gcode.error as e: error_message = str(e) + try: + self.gcode.run_script(self.on_error_gcode.render()) + except: + logging.exception("virtual_sdcard on_error") break except: logging.exception("virtual_sdcard dispatch") diff --git a/klippy/kinematics/delta.py b/klippy/kinematics/delta.py index 06c72456..2278dbca 100644 --- a/klippy/kinematics/delta.py +++ b/klippy/kinematics/delta.py @@ -150,6 +150,7 @@ class DeltaKinematics: 'homed_axes': '' if self.need_home else 'xyz', 'axis_minimum': self.axes_min, 'axis_maximum': self.axes_max, + 'cone_start_z': self.limit_z, } def get_calibration(self): endstops = [rail.get_homing_info().position_endstop diff --git a/klippy/klippy.py b/klippy/klippy.py index d7b62453..8f2caf3b 100644 --- a/klippy/klippy.py +++ b/klippy/klippy.py @@ -231,8 +231,7 @@ class Printer: run_result = self.run_result try: if run_result == 'firmware_restart': - for n, m in self.lookup_objects(module='mcu'): - m.microcontroller_restart() + self.send_event("klippy:firmware_restart") self.send_event("klippy:disconnect") except: logging.exception("Unhandled exception during post run") @@ -342,7 +341,7 @@ def main(): start_args['log_file'] = options.logfile bglogger = queuelogger.setup_bg_logging(options.logfile, debuglevel) else: - logging.basicConfig(level=debuglevel) + logging.getLogger().setLevel(debuglevel) logging.info("Starting Klippy...") start_args['software_version'] = util.get_git_version() start_args['cpu_info'] = util.get_cpu_info() diff --git a/klippy/mcu.py b/klippy/mcu.py index 5f963c7e..dcd4f07d 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -563,6 +563,7 @@ class MCU: self._restart_method = config.getchoice('restart_method', rmethods, None) self._reset_cmd = self._config_reset_cmd = None + self._is_mcu_bridge = False self._emergency_stop_cmd = None self._is_shutdown = self._is_timeout = False self._shutdown_clock = 0 @@ -589,9 +590,11 @@ class MCU: self._mcu_tick_stddev = 0. self._mcu_tick_awake = 0. # Register handlers - printer.register_event_handler("klippy:connect", self._connect) + printer.register_event_handler("klippy:firmware_restart", + self._firmware_restart) printer.register_event_handler("klippy:mcu_identify", self._mcu_identify) + printer.register_event_handler("klippy:connect", self._connect) printer.register_event_handler("klippy:shutdown", self._shutdown) printer.register_event_handler("klippy:disconnect", self._disconnect) # Serial callbacks @@ -797,6 +800,10 @@ class MCU: mbaud = msgparser.get_constant('SERIAL_BAUD', None) if self._restart_method is None and mbaud is None and not ext_only: self._restart_method = 'command' + if msgparser.get_constant('CANBUS_BRIDGE', 0): + self._is_mcu_bridge = True + self._printer.register_event_handler("klippy:firmware_restart", + self._firmware_restart_bridge) version, build_versions = msgparser.get_version_info() self._get_status_info['mcu_version'] = version self._get_status_info['mcu_build_versions'] = build_versions @@ -914,7 +921,9 @@ class MCU: chelper.run_hub_ctrl(0) self._reactor.pause(self._reactor.monotonic() + 2.) chelper.run_hub_ctrl(1) - def microcontroller_restart(self): + def _firmware_restart(self, force=False): + if self._is_mcu_bridge and not force: + return if self._restart_method == 'rpi_usb': self._restart_rpi_usb() elif self._restart_method == 'command': @@ -923,6 +932,8 @@ class MCU: self._restart_cheetah() else: self._restart_arduino() + def _firmware_restart_bridge(self): + self._firmware_restart(True) # Misc external commands def is_fileoutput(self): return self._printer.get_start_args().get('debugoutput') is not None diff --git a/klippy/reactor.py b/klippy/reactor.py index 69eedcbd..5b1ec569 100644 --- a/klippy/reactor.py +++ b/klippy/reactor.py @@ -50,9 +50,10 @@ class ReactorCallback: return self.reactor.NEVER class ReactorFileHandler: - def __init__(self, fd, callback): + def __init__(self, fd, read_callback, write_callback): self.fd = fd - self.callback = callback + self.read_callback = read_callback + self.write_callback = write_callback def fileno(self): return self.fd @@ -107,7 +108,8 @@ class SelectReactor: self._pipe_fds = None self._async_queue = queue.Queue() # File descriptors - self._fds = [] + self._read_fds = [] + self._write_fds = [] # Greenlets self._g_dispatch = None self._greenlets = [] @@ -236,12 +238,26 @@ class SelectReactor: def mutex(self, is_locked=False): return ReactorMutex(self, is_locked) # File descriptors - def register_fd(self, fd, callback): - file_handler = ReactorFileHandler(fd, callback) - self._fds.append(file_handler) + def register_fd(self, fd, read_callback, write_callback=None): + file_handler = ReactorFileHandler(fd, read_callback, write_callback) + self.set_fd_wake(file_handle, True, False) return file_handler def unregister_fd(self, file_handler): - self._fds.pop(self._fds.index(file_handler)) + if file_handler in self._read_fds: + self._read_fds.pop(self._read_fds.index(file_handler)) + if file_handler in self._write_fds: + self._write_fds.pop(self._write_fds.index(file_handler)) + def set_fd_wake(self, file_handler, is_readable=True, is_writeable=False): + if file_hander in self._read_fds: + if not is_readable: + self._read_fds.pop(self._read_fds.index(file_handler)) + elif is_readable: + self._read_fds.append(file_handler) + if file_hander in self._write_fds: + if not is_writeable: + self._write_fds.pop(self._write_fds.index(file_handler)) + elif is_writeable: + self._write_fds.append(file_handler) # Main loop def _dispatch_loop(self): self._g_dispatch = g_dispatch = greenlet.getcurrent() @@ -250,11 +266,18 @@ class SelectReactor: while self._process: timeout = self._check_timers(eventtime, busy) busy = False - res = select.select(self._fds, [], [], timeout) + res = select.select(self._read_fds, self.write_fds, [], timeout) eventtime = self.monotonic() for fd in res[0]: busy = True - fd.callback(eventtime) + fd.read_callback(eventtime) + if g_dispatch is not self._g_dispatch: + self._end_greenlet(g_dispatch) + eventtime = self.monotonic() + break + for fd in res[1]: + busy = True + fd.write_callback(eventtime) if g_dispatch is not self._g_dispatch: self._end_greenlet(g_dispatch) eventtime = self.monotonic() @@ -289,10 +312,10 @@ class PollReactor(SelectReactor): self._poll = select.poll() self._fds = {} # File descriptors - def register_fd(self, fd, callback): - file_handler = ReactorFileHandler(fd, callback) + def register_fd(self, fd, read_callback, write_callback=None): + file_handler = ReactorFileHandler(fd, read_callback, write_callback) fds = self._fds.copy() - fds[fd] = callback + fds[fd] = file_handler self._fds = fds self._poll.register(file_handler, select.POLLIN | select.POLLHUP) return file_handler @@ -301,6 +324,13 @@ class PollReactor(SelectReactor): fds = self._fds.copy() del fds[file_handler.fd] self._fds = fds + def set_fd_wake(self, file_handler, is_readable=True, is_writeable=False): + flags = select.POLLHUP + if is_readable: + flags |= select.POLLIN + if is_writeable: + flags |= select.POLLOUT + self._poll.modify(file_handler, flags) # Main loop def _dispatch_loop(self): self._g_dispatch = g_dispatch = greenlet.getcurrent() @@ -313,11 +343,18 @@ class PollReactor(SelectReactor): eventtime = self.monotonic() for fd, event in res: busy = True - self._fds[fd](eventtime) - if g_dispatch is not self._g_dispatch: - self._end_greenlet(g_dispatch) - eventtime = self.monotonic() - break + if event & (select.POLLIN | select.POLLHUP): + self._fds[fd].read_callback(eventtime) + if g_dispatch is not self._g_dispatch: + self._end_greenlet(g_dispatch) + eventtime = self.monotonic() + break + if event & select.POLLOUT: + self._fds[fd].write_callback(eventtime) + if g_dispatch is not self._g_dispatch: + self._end_greenlet(g_dispatch) + eventtime = self.monotonic() + break self._g_dispatch = None class EPollReactor(SelectReactor): @@ -326,8 +363,8 @@ class EPollReactor(SelectReactor): self._epoll = select.epoll() self._fds = {} # File descriptors - def register_fd(self, fd, callback): - file_handler = ReactorFileHandler(fd, callback) + def register_fd(self, fd, read_callback, write_callback=None): + file_handler = ReactorFileHandler(fd, read_callback, write_callback) fds = self._fds.copy() fds[fd] = callback self._fds = fds @@ -338,6 +375,13 @@ class EPollReactor(SelectReactor): fds = self._fds.copy() del fds[file_handler.fd] self._fds = fds + def set_fd_wake(self, file_handler, is_readable=True, is_writeable=False): + flags = select.POLLHUP + if is_readable: + flags |= select.EPOLLIN + if is_writeable: + flags |= select.EPOLLOUT + self._epoll.modify(file_handler, flags) # Main loop def _dispatch_loop(self): self._g_dispatch = g_dispatch = greenlet.getcurrent() @@ -350,11 +394,18 @@ class EPollReactor(SelectReactor): eventtime = self.monotonic() for fd, event in res: busy = True - self._fds[fd](eventtime) - if g_dispatch is not self._g_dispatch: - self._end_greenlet(g_dispatch) - eventtime = self.monotonic() - break + if event & (select.EPOLLIN | select.EPOLLHUP): + self._fds[fd].read_callback(eventtime) + if g_dispatch is not self._g_dispatch: + self._end_greenlet(g_dispatch) + eventtime = self.monotonic() + break + if event & select.EPOLLOUT: + self._fds[fd].write_callback(eventtime) + if g_dispatch is not self._g_dispatch: + self._end_greenlet(g_dispatch) + eventtime = self.monotonic() + break self._g_dispatch = None # Use the poll based reactor if it is available diff --git a/klippy/serialhdl.py b/klippy/serialhdl.py index 02c5c341..b8694ad1 100644 --- a/klippy/serialhdl.py +++ b/klippy/serialhdl.py @@ -12,7 +12,6 @@ class error(Exception): pass class SerialReader: - BITS_PER_BYTE = 10. def __init__(self, reactor, warn_prefix=""): self.reactor = reactor self.warn_prefix = warn_prefix @@ -97,11 +96,13 @@ class SerialReader: self.msgparser = msgparser self.register_response(self.handle_unknown, '#unknown') # Setup baud adjust - mcu_baud = msgparser.get_constant_float('SERIAL_BAUD', None) - if mcu_baud is not None: - baud_adjust = self.BITS_PER_BYTE / mcu_baud - self.ffi_lib.serialqueue_set_baud_adjust( - self.serialqueue, baud_adjust) + if serial_fd_type == b'c': + wire_freq = msgparser.get_constant_float('CANBUS_FREQUENCY', None) + else: + wire_freq = msgparser.get_constant_float('SERIAL_BAUD', None) + if wire_freq is not None: + self.ffi_lib.serialqueue_set_wire_frequency(self.serialqueue, + wire_freq) receive_window = msgparser.get_constant_int('RECEIVE_WINDOW', None) if receive_window is not None: self.ffi_lib.serialqueue_set_receive_window( diff --git a/klippy/webhooks.py b/klippy/webhooks.py index 6f6f8820..43ff9a91 100644 --- a/klippy/webhooks.py +++ b/klippy/webhooks.py @@ -162,6 +162,16 @@ class ServerSocket: def pop_client(self, client_id): self.clients.pop(client_id, None) + def stats(self, eventtime): + # Called once per second - check for idle clients + for client in list(self.clients.values()): + if client.is_blocking: + client.blocking_count -= 1 + if client.blocking_count < 0: + logging.info("Closing unresponsive client %s", client.uid) + client.close() + return False, "" + class ClientConnection: def __init__(self, server, sock): self.printer = server.printer @@ -171,9 +181,10 @@ class ClientConnection: self.uid = id(self) self.sock = sock self.fd_handle = self.reactor.register_fd( - self.sock.fileno(), self.process_received) + self.sock.fileno(), self.process_received, self._do_send) self.partial_data = self.send_buffer = b"" - self.is_sending_data = False + self.is_blocking = False + self.blocking_count = 0 self.set_client_info("?", "New connection") self.request_log = collections.deque([], REQUEST_LOG_SIZE) @@ -259,33 +270,29 @@ class ClientConnection: def send(self, data): jmsg = json.dumps(data, separators=(',', ':')) self.send_buffer += jmsg.encode() + b"\x03" - if not self.is_sending_data: - self.is_sending_data = True - self.reactor.register_callback(self._do_send) + if not self.is_blocking: + self._do_send() - def _do_send(self, eventtime): - retries = 10 - while self.send_buffer: - try: - sent = self.sock.send(self.send_buffer) - except socket.error as e: - if e.errno == errno.EBADF or e.errno == errno.EPIPE \ - or not retries: - sent = 0 - else: - retries -= 1 - waketime = self.reactor.monotonic() + .001 - self.reactor.pause(waketime) - continue - retries = 10 - if sent > 0: - self.send_buffer = self.send_buffer[sent:] - else: - logging.info( - "webhooks: Error sending server data, closing socket") + def _do_send(self, eventtime=None): + if self.fd_handle is None: + return + try: + sent = self.sock.send(self.send_buffer) + except socket.error as e: + if e.errno not in [errno.EAGAIN, errno.EWOULDBLOCK]: + logging.info("webhooks: socket write error %d" % (self.uid,)) self.close() - break - self.is_sending_data = False + return + sent = 0 + if sent < len(self.send_buffer): + if not self.is_blocking: + self.reactor.set_fd_wake(self.fd_handle, False, True) + self.is_blocking = True + self.blocking_count = 5 + elif self.is_blocking: + self.reactor.set_fd_wake(self.fd_handle, True, False) + self.is_blocking = False + self.send_buffer = self.send_buffer[sent:] class WebHooks: def __init__(self, printer): @@ -375,6 +382,9 @@ class WebHooks: state_message, state = self.printer.get_state_message() return {'state': state, 'state_message': state_message} + def stats(self, eventtime): + return self.sconn.stats(eventtime) + def call_remote_method(self, method, **kwargs): if method not in self._remote_methods: raise self.printer.command_error( diff --git a/lib/README b/lib/README index b4ae7cf1..eac029ff 100644 --- a/lib/README +++ b/lib/README @@ -125,6 +125,10 @@ callbacks. The canboot directory contains code from: https://github.com/Arksine/CanBoot -revision ae687a404d0edb2724a084f78e565dbc7dd505aa. The Python module, +revision 870200826561b150137913d42fd7edc6515229ff. The Python module, flash_can.py, is taken from the repo's scripts directory. It may be used to upload firmware to devices flashed with the CanBoot bootloader. + +The can2040 directory contains code from: + https://github.com/KevinOConnor/can2040 +revision 9ca095c939a48391de60dd353f0cd91999bb9257. diff --git a/lib/can2040/can2040.c b/lib/can2040/can2040.c new file mode 100644 index 00000000..4460d75b --- /dev/null +++ b/lib/can2040/can2040.c @@ -0,0 +1,1258 @@ +// Software CANbus implementation for rp2040 +// +// Copyright (C) 2022 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include // uint32_t +#include // memset +#include "RP2040.h" // hw_set_bits +#include "can2040.h" // can2040_setup +#include "hardware/regs/dreq.h" // DREQ_PIO0_RX1 +#include "hardware/structs/dma.h" // dma_hw +#include "hardware/structs/iobank0.h" // iobank0_hw +#include "hardware/structs/padsbank0.h" // padsbank0_hw +#include "hardware/structs/pio.h" // pio0_hw +#include "hardware/structs/resets.h" // RESETS_RESET_PIO0_BITS + + +/**************************************************************** + * rp2040 and low-level helper functions + ****************************************************************/ + +// Helper compiler definitions +#define barrier() __asm__ __volatile__("": : :"memory") +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) +#define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d)) + +// Helper functions for writing to "io" memory +static inline void writel(void *addr, uint32_t val) { + barrier(); + *(volatile uint32_t *)addr = val; +} +static inline uint32_t readl(const void *addr) { + uint32_t val = *(volatile const uint32_t *)addr; + barrier(); + return val; +} + +// rp2040 helper function to clear a hardware reset bit +static void +rp2040_clear_reset(uint32_t reset_bit) +{ + if (resets_hw->reset & reset_bit) { + hw_clear_bits(&resets_hw->reset, reset_bit); + while (!(resets_hw->reset_done & reset_bit)) + ; + } +} + +// Helper to set the mode and extended function of a pin +static void +rp2040_gpio_peripheral(uint32_t gpio, int func, int pull_up) +{ + padsbank0_hw->io[gpio] = ( + PADS_BANK0_GPIO0_IE_BITS + | (PADS_BANK0_GPIO0_DRIVE_VALUE_4MA << PADS_BANK0_GPIO0_DRIVE_MSB) + | (pull_up > 0 ? PADS_BANK0_GPIO0_PUE_BITS : 0) + | (pull_up < 0 ? PADS_BANK0_GPIO0_PDE_BITS : 0)); + iobank0_hw->io[gpio].ctrl = func << IO_BANK0_GPIO0_CTRL_FUNCSEL_LSB; +} + + +/**************************************************************** + * rp2040 PIO support + ****************************************************************/ + +#define PIO_CLOCK_PER_BIT 32 + +#define can2040_offset_sync_found_end_of_message 2u +#define can2040_offset_sync_signal_start 4u +#define can2040_offset_sync_entry 6u +#define can2040_offset_sync_end 13u +#define can2040_offset_shared_rx_read 13u +#define can2040_offset_shared_rx_end 15u +#define can2040_offset_match_load_next 18u +#define can2040_offset_match_end 25u +#define can2040_offset_tx_got_recessive 25u +#define can2040_offset_tx_start 26u +#define can2040_offset_tx_conflict 31u + +static const uint16_t can2040_program_instructions[] = { + 0x0085, // 0: jmp y--, 5 + 0x0048, // 1: jmp x--, 8 + 0xe13a, // 2: set x, 26 [1] + 0x00cc, // 3: jmp pin, 12 + 0xc000, // 4: irq nowait 0 + 0x00c0, // 5: jmp pin, 0 + 0xc040, // 6: irq clear 0 + 0xe228, // 7: set x, 8 [2] + 0xf242, // 8: set y, 2 [18] + 0xc104, // 9: irq nowait 4 [1] + 0x03c5, // 10: jmp pin, 5 [3] + 0x0307, // 11: jmp 7 [3] + 0x0043, // 12: jmp x--, 3 + 0x20c4, // 13: wait 1 irq, 4 + 0x4001, // 14: in pins, 1 + 0xa046, // 15: mov y, isr + 0x00b2, // 16: jmp x != y, 18 + 0xc002, // 17: irq nowait 2 + 0x40eb, // 18: in osr, 11 + 0x4054, // 19: in y, 20 + 0xa047, // 20: mov y, osr + 0x8080, // 21: pull noblock + 0xa027, // 22: mov x, osr + 0x0098, // 23: jmp y--, 24 + 0xa0e2, // 24: mov osr, y + 0xa242, // 25: nop [2] + 0x6021, // 26: out x, 1 + 0xa001, // 27: mov pins, x + 0x20c4, // 28: wait 1 irq, 4 + 0x00d9, // 29: jmp pin, 25 + 0x023a, // 30: jmp !x, 26 [2] + 0xc027, // 31: irq wait 7 +}; + +// Setup PIO "sync" state machine (state machine 0) +static void +pio_sync_setup(struct can2040 *cd) +{ + pio_hw_t *pio_hw = cd->pio_hw; + struct pio_sm_hw *sm = &pio_hw->sm[0]; + sm->execctrl = ( + cd->gpio_rx << PIO_SM0_EXECCTRL_JMP_PIN_LSB + | (can2040_offset_sync_end - 1) << PIO_SM0_EXECCTRL_WRAP_TOP_LSB + | can2040_offset_sync_signal_start << PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB); + sm->pinctrl = ( + 1 << PIO_SM0_PINCTRL_SET_COUNT_LSB + | cd->gpio_rx << PIO_SM0_PINCTRL_SET_BASE_LSB); + sm->instr = 0xe080; // set pindirs, 0 + sm->pinctrl = 0; + pio_hw->txf[0] = PIO_CLOCK_PER_BIT / 2 * 8 - 5 - 1; + sm->instr = 0x80a0; // pull block + sm->instr = can2040_offset_sync_entry; // jmp sync_entry +} + +// Setup PIO "rx" state machine (state machine 1) +static void +pio_rx_setup(struct can2040 *cd) +{ + pio_hw_t *pio_hw = cd->pio_hw; + struct pio_sm_hw *sm = &pio_hw->sm[1]; + sm->execctrl = ( + (can2040_offset_shared_rx_end - 1) << PIO_SM0_EXECCTRL_WRAP_TOP_LSB + | can2040_offset_shared_rx_read << PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB); + sm->pinctrl = cd->gpio_rx << PIO_SM0_PINCTRL_IN_BASE_LSB; + sm->shiftctrl = 0; // flush fifo on a restart + sm->shiftctrl = (PIO_SM0_SHIFTCTRL_FJOIN_RX_BITS + | 8 << PIO_SM0_SHIFTCTRL_PUSH_THRESH_LSB + | PIO_SM0_SHIFTCTRL_AUTOPUSH_BITS); + sm->instr = can2040_offset_shared_rx_read; // jmp shared_rx_read +} + +// Setup PIO "match" state machine (state machine 2) +static void +pio_match_setup(struct can2040 *cd) +{ + pio_hw_t *pio_hw = cd->pio_hw; + struct pio_sm_hw *sm = &pio_hw->sm[2]; + sm->execctrl = ( + (can2040_offset_match_end - 1) << PIO_SM0_EXECCTRL_WRAP_TOP_LSB + | can2040_offset_shared_rx_read << PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB); + sm->pinctrl = cd->gpio_rx << PIO_SM0_PINCTRL_IN_BASE_LSB; + sm->shiftctrl = 0; + sm->instr = 0xe040; // set y, 0 + sm->instr = 0xa0e2; // mov osr, y + sm->instr = 0xa02a, // mov x, !y + sm->instr = can2040_offset_match_load_next; // jmp match_load_next +} + +// Setup PIO "tx" state machine (state machine 3) +static void +pio_tx_setup(struct can2040 *cd) +{ + pio_hw_t *pio_hw = cd->pio_hw; + struct pio_sm_hw *sm = &pio_hw->sm[3]; + sm->execctrl = cd->gpio_rx << PIO_SM0_EXECCTRL_JMP_PIN_LSB; + sm->shiftctrl = (PIO_SM0_SHIFTCTRL_FJOIN_TX_BITS + | PIO_SM0_SHIFTCTRL_AUTOPULL_BITS); + sm->pinctrl = (1 << PIO_SM0_PINCTRL_SET_COUNT_LSB + | 1 << PIO_SM0_PINCTRL_OUT_COUNT_LSB + | cd->gpio_tx << PIO_SM0_PINCTRL_SET_BASE_LSB + | cd->gpio_tx << PIO_SM0_PINCTRL_OUT_BASE_LSB); + sm->instr = 0xe001; // set pins, 1 + sm->instr = 0xe081; // set pindirs, 1 +} + +// Set PIO "sync" machine to signal "may transmit" (sm irq 0) on 11 idle bits +static void +pio_sync_normal_start_signal(struct can2040 *cd) +{ + pio_hw_t *pio_hw = cd->pio_hw; + uint32_t eom_idx = can2040_offset_sync_found_end_of_message; + pio_hw->instr_mem[eom_idx] = 0xe13a; // set x, 26 [1] +} + +// Set PIO "sync" machine to signal "may transmit" (sm irq 0) on 17 idle bits +static void +pio_sync_slow_start_signal(struct can2040 *cd) +{ + pio_hw_t *pio_hw = cd->pio_hw; + uint32_t eom_idx = can2040_offset_sync_found_end_of_message; + pio_hw->instr_mem[eom_idx] = 0xa127; // mov x, osr [1] +} + +// Test if PIO "rx" state machine has overflowed its fifos +static int +pio_rx_check_stall(struct can2040 *cd) +{ + pio_hw_t *pio_hw = cd->pio_hw; + return pio_hw->fdebug & (1 << (PIO_FDEBUG_RXSTALL_LSB + 1)); +} + +// Report number of bytes still pending in PIO "rx" fifo queue +static int +pio_rx_fifo_level(struct can2040 *cd) +{ + pio_hw_t *pio_hw = cd->pio_hw; + return (pio_hw->flevel & PIO_FLEVEL_RX1_BITS) >> PIO_FLEVEL_RX1_LSB; +} + +// Set PIO "match" state machine to raise a "matched" signal on a bit sequence +static void +pio_match_check(struct can2040 *cd, uint32_t match_key) +{ + pio_hw_t *pio_hw = cd->pio_hw; + pio_hw->txf[2] = match_key; +} + +// Calculate pos+bits identifier for PIO "match" state machine +static uint32_t +pio_match_calc_key(uint32_t raw_bits, uint32_t rx_bit_pos) +{ + return (raw_bits & 0x1fffff) | ((-rx_bit_pos) << 21); +} + +// Cancel any pending checks on PIO "match" state machine +static void +pio_match_clear(struct can2040 *cd) +{ + pio_match_check(cd, 0); +} + +// Flush and halt PIO "tx" state machine +static void +pio_tx_reset(struct can2040 *cd) +{ + pio_hw_t *pio_hw = cd->pio_hw; + pio_hw->ctrl = ((0x07 << PIO_CTRL_SM_ENABLE_LSB) + | (0x08 << PIO_CTRL_SM_RESTART_LSB)); + pio_hw->irq = (1 << 2) | (1<< 3); // clear "matched" and "ack done" signals + // Clear tx fifo + struct pio_sm_hw *sm = &pio_hw->sm[3]; + sm->shiftctrl = 0; + sm->shiftctrl = (PIO_SM0_SHIFTCTRL_FJOIN_TX_BITS + | PIO_SM0_SHIFTCTRL_AUTOPULL_BITS); + // Must reset again after clearing fifo + pio_hw->ctrl = ((0x07 << PIO_CTRL_SM_ENABLE_LSB) + | (0x08 << PIO_CTRL_SM_RESTART_LSB)); +} + +// Queue a message for transmission on PIO "tx" state machine +static void +pio_tx_send(struct can2040 *cd, uint32_t *data, uint32_t count) +{ + pio_hw_t *pio_hw = cd->pio_hw; + pio_tx_reset(cd); + pio_hw->instr_mem[can2040_offset_tx_got_recessive] = 0xa242; // nop [2] + int i; + for (i=0; itxf[3] = data[i]; + struct pio_sm_hw *sm = &pio_hw->sm[3]; + sm->instr = 0xe001; // set pins, 1 + sm->instr = can2040_offset_tx_start; // jmp tx_start + sm->instr = 0x20c0; // wait 1 irq, 0 + pio_hw->ctrl = 0x0f << PIO_CTRL_SM_ENABLE_LSB; +} + +// Set PIO "tx" state machine to inject an ack after a CRC match +static void +pio_tx_inject_ack(struct can2040 *cd, uint32_t match_key) +{ + pio_hw_t *pio_hw = cd->pio_hw; + pio_tx_reset(cd); + pio_hw->instr_mem[can2040_offset_tx_got_recessive] = 0xc023; // irq wait 3 + pio_hw->txf[3] = 0x7fffffff; + struct pio_sm_hw *sm = &pio_hw->sm[3]; + sm->instr = 0xe001; // set pins, 1 + sm->instr = can2040_offset_tx_start; // jmp tx_start + sm->instr = 0x20c2; // wait 1 irq, 2 + pio_hw->ctrl = 0x0f << PIO_CTRL_SM_ENABLE_LSB; + + pio_match_check(cd, match_key); +} + +// Check if the PIO "tx" state machine stopped due to passive/dominant conflict +static int +pio_tx_did_conflict(struct can2040 *cd) +{ + pio_hw_t *pio_hw = cd->pio_hw; + return pio_hw->sm[3].addr == can2040_offset_tx_conflict; +} + +// Enable host irq on a "may transmit" signal (sm irq 0) +static void +pio_irq_set_maytx(struct can2040 *cd) +{ + pio_hw_t *pio_hw = cd->pio_hw; + pio_hw->inte0 = PIO_IRQ0_INTE_SM0_BITS | PIO_IRQ0_INTE_SM1_RXNEMPTY_BITS; +} + +// Enable host irq on a "may transmit" or "matched" signal (sm irq 0 or 2) +static void +pio_irq_set_maytx_matched(struct can2040 *cd) +{ + pio_hw_t *pio_hw = cd->pio_hw; + pio_hw->inte0 = (PIO_IRQ0_INTE_SM0_BITS | PIO_IRQ0_INTE_SM2_BITS + | PIO_IRQ0_INTE_SM1_RXNEMPTY_BITS); +} + +// Enable host irq on a "may transmit" or "ack done" signal (sm irq 0 or 3) +static void +pio_irq_set_maytx_ackdone(struct can2040 *cd) +{ + pio_hw_t *pio_hw = cd->pio_hw; + pio_hw->inte0 = (PIO_IRQ0_INTE_SM0_BITS | PIO_IRQ0_INTE_SM3_BITS + | PIO_IRQ0_INTE_SM1_RXNEMPTY_BITS); +} + +// Atomically enable "may transmit" signal (sm irq 0) +static void +pio_irq_atomic_set_maytx(struct can2040 *cd) +{ + pio_hw_t *pio_hw = cd->pio_hw; + hw_set_bits(&pio_hw->inte0, PIO_IRQ0_INTE_SM0_BITS); +} + +// Disable PIO host irqs (except for normal data read irq) +static void +pio_irq_set_none(struct can2040 *cd) +{ + pio_hw_t *pio_hw = cd->pio_hw; + pio_hw->inte0 = PIO_IRQ0_INTE_SM1_RXNEMPTY_BITS; +} + +// Setup PIO state machines +static void +pio_sm_setup(struct can2040 *cd) +{ + // Reset state machines + pio_hw_t *pio_hw = cd->pio_hw; + pio_hw->ctrl = PIO_CTRL_SM_RESTART_BITS | PIO_CTRL_CLKDIV_RESTART_BITS; + pio_hw->fdebug = 0xffffffff; + + // Load pio program + int i; + for (i=0; iinstr_mem[i] = can2040_program_instructions[i]; + + // Set initial state machine state + pio_sync_setup(cd); + pio_rx_setup(cd); + pio_match_setup(cd); + pio_tx_setup(cd); + + // Start state machines + pio_hw->ctrl = 0x07 << PIO_CTRL_SM_ENABLE_LSB; +} + +#define PIO_FUNC 6 + +// Initial setup of gpio pins and PIO state machines +static void +pio_setup(struct can2040 *cd, uint32_t sys_clock, uint32_t bitrate) +{ + // Configure pio0 clock + uint32_t rb = cd->pio_num ? RESETS_RESET_PIO1_BITS : RESETS_RESET_PIO0_BITS; + rp2040_clear_reset(rb); + + // Setup and sync pio state machine clocks + pio_hw_t *pio_hw = cd->pio_hw; + uint32_t div = (256 / PIO_CLOCK_PER_BIT) * sys_clock / bitrate; + int i; + for (i=0; i<4; i++) + pio_hw->sm[i].clkdiv = div << PIO_SM0_CLKDIV_FRAC_LSB; + + // Configure state machines + pio_sm_setup(cd); + + // Map Rx/Tx gpios + rp2040_gpio_peripheral(cd->gpio_rx, PIO_FUNC, 1); + rp2040_gpio_peripheral(cd->gpio_tx, PIO_FUNC, 0); +} + + +/**************************************************************** + * CRC calculation + ****************************************************************/ + +// Calculated 8-bit crc table (see scripts/crc.py) +static const uint16_t crc_table[256] = { + 0x0000,0x4599,0x4eab,0x0b32,0x58cf,0x1d56,0x1664,0x53fd,0x7407,0x319e, + 0x3aac,0x7f35,0x2cc8,0x6951,0x6263,0x27fa,0x2d97,0x680e,0x633c,0x26a5, + 0x7558,0x30c1,0x3bf3,0x7e6a,0x5990,0x1c09,0x173b,0x52a2,0x015f,0x44c6, + 0x4ff4,0x0a6d,0x5b2e,0x1eb7,0x1585,0x501c,0x03e1,0x4678,0x4d4a,0x08d3, + 0x2f29,0x6ab0,0x6182,0x241b,0x77e6,0x327f,0x394d,0x7cd4,0x76b9,0x3320, + 0x3812,0x7d8b,0x2e76,0x6bef,0x60dd,0x2544,0x02be,0x4727,0x4c15,0x098c, + 0x5a71,0x1fe8,0x14da,0x5143,0x73c5,0x365c,0x3d6e,0x78f7,0x2b0a,0x6e93, + 0x65a1,0x2038,0x07c2,0x425b,0x4969,0x0cf0,0x5f0d,0x1a94,0x11a6,0x543f, + 0x5e52,0x1bcb,0x10f9,0x5560,0x069d,0x4304,0x4836,0x0daf,0x2a55,0x6fcc, + 0x64fe,0x2167,0x729a,0x3703,0x3c31,0x79a8,0x28eb,0x6d72,0x6640,0x23d9, + 0x7024,0x35bd,0x3e8f,0x7b16,0x5cec,0x1975,0x1247,0x57de,0x0423,0x41ba, + 0x4a88,0x0f11,0x057c,0x40e5,0x4bd7,0x0e4e,0x5db3,0x182a,0x1318,0x5681, + 0x717b,0x34e2,0x3fd0,0x7a49,0x29b4,0x6c2d,0x671f,0x2286,0x2213,0x678a, + 0x6cb8,0x2921,0x7adc,0x3f45,0x3477,0x71ee,0x5614,0x138d,0x18bf,0x5d26, + 0x0edb,0x4b42,0x4070,0x05e9,0x0f84,0x4a1d,0x412f,0x04b6,0x574b,0x12d2, + 0x19e0,0x5c79,0x7b83,0x3e1a,0x3528,0x70b1,0x234c,0x66d5,0x6de7,0x287e, + 0x793d,0x3ca4,0x3796,0x720f,0x21f2,0x646b,0x6f59,0x2ac0,0x0d3a,0x48a3, + 0x4391,0x0608,0x55f5,0x106c,0x1b5e,0x5ec7,0x54aa,0x1133,0x1a01,0x5f98, + 0x0c65,0x49fc,0x42ce,0x0757,0x20ad,0x6534,0x6e06,0x2b9f,0x7862,0x3dfb, + 0x36c9,0x7350,0x51d6,0x144f,0x1f7d,0x5ae4,0x0919,0x4c80,0x47b2,0x022b, + 0x25d1,0x6048,0x6b7a,0x2ee3,0x7d1e,0x3887,0x33b5,0x762c,0x7c41,0x39d8, + 0x32ea,0x7773,0x248e,0x6117,0x6a25,0x2fbc,0x0846,0x4ddf,0x46ed,0x0374, + 0x5089,0x1510,0x1e22,0x5bbb,0x0af8,0x4f61,0x4453,0x01ca,0x5237,0x17ae, + 0x1c9c,0x5905,0x7eff,0x3b66,0x3054,0x75cd,0x2630,0x63a9,0x689b,0x2d02, + 0x276f,0x62f6,0x69c4,0x2c5d,0x7fa0,0x3a39,0x310b,0x7492,0x5368,0x16f1, + 0x1dc3,0x585a,0x0ba7,0x4e3e,0x450c,0x0095 +}; + +// Update a crc with 8 bits of data +static uint32_t +crc_byte(uint32_t crc, uint32_t data) +{ + return (crc << 8) ^ crc_table[((crc >> 7) ^ data) & 0xff]; +} + +// Update a crc with 8, 16, 24, or 32 bits of data +static inline uint32_t +crc_bytes(uint32_t crc, uint32_t data, uint32_t num) +{ + switch (num) { + default: crc = crc_byte(crc, data >> 24); + case 3: crc = crc_byte(crc, data >> 16); + case 2: crc = crc_byte(crc, data >> 8); + case 1: crc = crc_byte(crc, data); + } + return crc; +} + + +/**************************************************************** + * Bit unstuffing + ****************************************************************/ + +// Add 'count' number of bits from 'data' to the 'bu' unstuffer +static void +unstuf_add_bits(struct can2040_bitunstuffer *bu, uint32_t data, uint32_t count) +{ + uint32_t mask = (1 << count) - 1; + bu->stuffed_bits = (bu->stuffed_bits << count) | (data & mask); + bu->count_stuff = count; +} + +// Reset state and set the next desired 'count' unstuffed bits to extract +static void +unstuf_set_count(struct can2040_bitunstuffer *bu, uint32_t count) +{ + bu->unstuffed_bits = 0; + bu->count_unstuff = count; +} + +// Clear bitstuffing state (used after crc field to avoid bitstuffing ack field) +static void +unstuf_clear_state(struct can2040_bitunstuffer *bu) +{ + uint32_t lb = 1 << bu->count_stuff; + bu->stuffed_bits = (bu->stuffed_bits & (lb - 1)) | lb; +} + +// Pull bits from unstuffer (as specified in unstuf_set_count() ) +static int +unstuf_pull_bits(struct can2040_bitunstuffer *bu) +{ + uint32_t sb = bu->stuffed_bits, edges = sb ^ (sb >> 1); + uint32_t e2 = edges | (edges >> 1), e4 = e2 | (e2 >> 2), rm_bits = ~e4; + uint32_t cs = bu->count_stuff, cu = bu->count_unstuff; + if (!cs) + // Need more data + return 1; + for (;;) { + uint32_t try_cnt = cs > cu ? cu : cs; + for (;;) { + uint32_t try_mask = ((1 << try_cnt) - 1) << (cs + 1 - try_cnt); + if (likely(!(rm_bits & try_mask))) { + // No stuff bits in try_cnt bits - copy into unstuffed_bits + bu->count_unstuff = cu = cu - try_cnt; + bu->count_stuff = cs = cs - try_cnt; + bu->unstuffed_bits |= ((sb >> cs) & ((1 << try_cnt) - 1)) << cu; + if (! cu) + // Extracted desired bits + return 0; + break; + } + bu->count_stuff = cs = cs - 1; + if (rm_bits & (1 << (cs + 1))) { + // High bit of try_cnt a stuff bit + if (unlikely(rm_bits & (1 << cs))) { + // Six consecutive bits - a bitstuff error + if ((sb >> cs) & 1) + return -1; + return -2; + } + break; + } + // High bit not a stuff bit - limit try_cnt and retry + bu->count_unstuff = cu = cu - 1; + bu->unstuffed_bits |= ((sb >> cs) & 1) << cu; + try_cnt /= 2; + } + if (likely(!cs)) + // Need more data + return 1; + } +} + + +/**************************************************************** + * Bit stuffing + ****************************************************************/ + +// Stuff 'num_bits' bits in '*pb' - upper bits must already be stuffed +static uint32_t +bitstuff(uint32_t *pb, uint32_t num_bits) +{ + uint32_t b = *pb, count = num_bits; + for (;;) { + uint32_t try_cnt = num_bits, edges = b ^ (b >> 1); + uint32_t e2 = edges | (edges >> 1), e4 = e2 | (e2 >> 2), add_bits = ~e4; + for (;;) { + uint32_t try_mask = ((1 << try_cnt) - 1) << (num_bits - try_cnt); + if (!(add_bits & try_mask)) { + // No stuff bits needed in try_cnt bits + if (try_cnt >= num_bits) + goto done; + num_bits -= try_cnt; + try_cnt = (num_bits + 1) / 2; + continue; + } + if (add_bits & (1 << (num_bits - 1))) { + // A stuff bit must be inserted prior to the high bit + uint32_t low_mask = (1 << num_bits) - 1, low = b & low_mask; + uint32_t high = (b & ~(low_mask >> 1)) << 1; + b = high ^ low ^ (1 << (num_bits - 1)); + count += 1; + if (num_bits <= 4) + goto done; + num_bits -= 4; + break; + } + // High bit doesn't need stuff bit - accept it, limit try_cnt, retry + num_bits--; + try_cnt /= 2; + } + } +done: + *pb = b; + return count; +} + +// State storage for building bit stuffed transmit messages +struct bitstuffer_s { + uint32_t prev_stuffed, bitpos, *buf; +}; + +// Push 'count' bits of 'data' into stuffer without performing bit stuffing +static void +bs_pushraw(struct bitstuffer_s *bs, uint32_t data, uint32_t count) +{ + uint32_t bitpos = bs->bitpos; + uint32_t wp = bitpos / 32, bitused = bitpos % 32, bitavail = 32 - bitused; + uint32_t *fb = &bs->buf[wp]; + if (bitavail >= count) { + fb[0] |= data << (bitavail - count); + } else { + fb[0] |= data >> (count - bitavail); + fb[1] |= data << (32 - (count - bitavail)); + } + bs->bitpos = bitpos + count; +} + +// Push 'count' bits of 'data' into stuffer +static void +bs_push(struct bitstuffer_s *bs, uint32_t data, uint32_t count) +{ + data &= (1 << count) - 1; + uint32_t stuf = (bs->prev_stuffed << count) | data; + uint32_t newcount = bitstuff(&stuf, count); + bs_pushraw(bs, stuf, newcount); + bs->prev_stuffed = stuf; +} + +// Pad final word of stuffer with high bits +static uint32_t +bs_finalize(struct bitstuffer_s *bs) +{ + uint32_t bitpos = bs->bitpos; + uint32_t words = DIV_ROUND_UP(bitpos, 32); + uint32_t extra = words * 32 - bitpos; + if (extra) + bs->buf[words - 1] |= (1 << extra) - 1; + return words; +} + + +/**************************************************************** + * Transmit state tracking + ****************************************************************/ + +// Transmit states (stored in cd->tx_state) +enum { + TS_IDLE = 0, TS_QUEUED = 1, TS_ACKING_RX = 2, TS_CONFIRM_TX = 3 +}; + +// Calculate queue array position from a transmit index +static uint32_t +tx_qpos(struct can2040 *cd, uint32_t pos) +{ + return pos % ARRAY_SIZE(cd->tx_queue); +} + +// Queue the next message for transmission in the PIO +static void +tx_schedule_transmit(struct can2040 *cd) +{ + if (cd->tx_state == TS_QUEUED && !pio_tx_did_conflict(cd)) + // Already queued or actively transmitting + return; + if (cd->tx_push_pos == cd->tx_pull_pos) { + // No new messages to transmit + cd->tx_state = TS_IDLE; + return; + } + cd->tx_state = TS_QUEUED; + struct can2040_transmit *qt = &cd->tx_queue[tx_qpos(cd, cd->tx_pull_pos)]; + pio_tx_send(cd, qt->stuffed_data, qt->stuffed_words); +} + +// Setup PIO state for ack injection +static int +tx_inject_ack(struct can2040 *cd, uint32_t match_key) +{ + if (cd->tx_state == TS_QUEUED && !pio_tx_did_conflict(cd) + && pio_rx_fifo_level(cd) > 1) + // Rx state is behind - acking wont succeed and may halt active tx + return -1; + cd->tx_state = TS_ACKING_RX; + pio_tx_inject_ack(cd, match_key); + return 0; +} + +// Check if the current parsed message is feedback from current transmit +static int +tx_check_local_message(struct can2040 *cd) +{ + if (cd->tx_state != TS_QUEUED) + return 0; + struct can2040_transmit *qt = &cd->tx_queue[tx_qpos(cd, cd->tx_pull_pos)]; + struct can2040_msg *pm = &cd->parse_msg, *tm = &qt->msg; + if (qt->crc == cd->parse_crc && tm->id == pm->id && tm->dlc == pm->dlc + && tm->data32[0] == pm->data32[0] && tm->data32[1] == pm->data32[1]) { + // This is a self transmit + cd->tx_state = TS_CONFIRM_TX; + return 1; + } + return 0; +} + + +/**************************************************************** + * Notification callbacks + ****************************************************************/ + +// Report state flags (stored in cd->report_state) +enum { + RS_IDLE = 0, RS_IS_TX = 1, RS_IN_MSG = 2, RS_AWAIT_EOF = 4, +}; + +// Report error to calling code (via callback interface) +static void +report_callback_error(struct can2040 *cd, uint32_t error_code) +{ + struct can2040_msg msg = {}; + cd->rx_cb(cd, CAN2040_NOTIFY_ERROR | error_code, &msg); +} + +// Report a received message to calling code (via callback interface) +static void +report_callback_rx_msg(struct can2040 *cd) +{ + cd->rx_cb(cd, CAN2040_NOTIFY_RX, &cd->parse_msg); +} + +// Report a message that was successfully transmited (via callback interface) +static void +report_callback_tx_msg(struct can2040 *cd) +{ + cd->tx_pull_pos++; + cd->rx_cb(cd, CAN2040_NOTIFY_TX, &cd->parse_msg); +} + +// EOF phase complete - report message (rx or tx) to calling code +static void +report_handle_eof(struct can2040 *cd) +{ + if (cd->report_state == RS_IDLE) + // Message already reported or an unexpected EOF + return; + if (cd->report_state & RS_AWAIT_EOF) { + // Successfully processed a new message - report to calling code + pio_sync_normal_start_signal(cd); + if (cd->report_state & RS_IS_TX) + report_callback_tx_msg(cd); + else + report_callback_rx_msg(cd); + } + cd->report_state = RS_IDLE; + pio_match_clear(cd); +} + +// Check if in an rx ack is pending +static int +report_is_acking_rx(struct can2040 *cd) +{ + return cd->report_state == (RS_IN_MSG | RS_AWAIT_EOF); +} + +// Parser found a new message start +static void +report_note_message_start(struct can2040 *cd) +{ + pio_irq_set_maytx(cd); +} + +// Setup for ack injection (if receiving) or ack confirmation (if transmit) +static void +report_note_crc_start(struct can2040 *cd) +{ + uint32_t cs = cd->unstuf.count_stuff; + uint32_t crcstart_bitpos = cd->raw_bit_count - cs - 1; + uint32_t last = ((cd->unstuf.stuffed_bits >> cs) << 15) | cd->parse_crc; + uint32_t crc_bitcount = bitstuff(&last, 15 + 1) - 1; + uint32_t crcend_bitpos = crcstart_bitpos + crc_bitcount; + + int ret = tx_check_local_message(cd); + if (ret) { + // This is a self transmit - setup tx eof "matched" signal + cd->report_state = RS_IN_MSG | RS_IS_TX; + last = (last << 10) | 0x02ff; + pio_match_check(cd, pio_match_calc_key(last, crcend_bitpos + 10)); + return; + } + + // Inject ack + cd->report_state = RS_IN_MSG; + last = (last << 1) | 0x01; + ret = tx_inject_ack(cd, pio_match_calc_key(last, crcend_bitpos + 1)); + if (ret) + // Ack couldn't be scheduled (due to lagged parsing state) + return; + pio_irq_set_maytx_ackdone(cd); + // Setup for future rx eof "matched" signal + last = (last << 8) | 0x7f; + cd->report_eof_key = pio_match_calc_key(last, crcend_bitpos + 9); +} + +// Parser found successful ack +static void +report_note_ack_success(struct can2040 *cd) +{ + if (!(cd->report_state & RS_IN_MSG)) + // Got rx "ackdone" and "matched" signals already + return; + cd->report_state |= RS_AWAIT_EOF; + if (cd->report_state & RS_IS_TX) + // Enable "matched" irq for fast back-to-back transmit scheduling + pio_irq_set_maytx_matched(cd); +} + +// Parser found successful EOF +static void +report_note_eof_success(struct can2040 *cd) +{ + report_handle_eof(cd); +} + +// Parser found unexpected data on input +static void +report_note_parse_error(struct can2040 *cd) +{ + if (cd->report_state != RS_IDLE) { + cd->report_state = RS_IDLE; + pio_match_clear(cd); + } + pio_sync_slow_start_signal(cd); + pio_irq_set_maytx(cd); +} + +// Received PIO rx "ackdone" irq +static void +report_line_ackdone(struct can2040 *cd) +{ + if (!(cd->report_state & RS_IN_MSG)) { + // Parser already processed ack and eof bits + pio_irq_set_maytx(cd); + return; + } + // Setup "matched" irq for fast rx callbacks + cd->report_state = RS_IN_MSG | RS_AWAIT_EOF; + pio_match_check(cd, cd->report_eof_key); + pio_irq_set_maytx_matched(cd); + // Schedule next transmit (so it is ready for next frame line arbitration) + tx_schedule_transmit(cd); +} + +// Received PIO "matched" irq +static void +report_line_matched(struct can2040 *cd) +{ + // Implement fast rx callback and/or fast back-to-back tx scheduling + report_handle_eof(cd); + pio_irq_set_none(cd); + tx_schedule_transmit(cd); +} + +// Received 10+ passive bits on the line (between 10 and 17 bits) +static void +report_line_maytx(struct can2040 *cd) +{ + // Line is idle - may be unexpected EOF, missed ack injection, + // missed "matched" signal, or can2040_transmit() kick. + report_handle_eof(cd); + pio_irq_set_none(cd); + tx_schedule_transmit(cd); +} + + +/**************************************************************** + * Input state tracking + ****************************************************************/ + +// Parsing states (stored in cd->parse_state) +enum { + MS_START, MS_HEADER, MS_EXT_HEADER, MS_DATA0, MS_DATA1, + MS_CRC, MS_ACK, MS_EOF0, MS_EOF1, MS_DISCARD +}; + +// Transition to the next parsing state +static void +data_state_go_next(struct can2040 *cd, uint32_t state, uint32_t bits) +{ + cd->parse_state = state; + unstuf_set_count(&cd->unstuf, bits); +} + +// Transition to the MS_DISCARD state - drop all bits until 6 passive 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; + pio_sm_setup(cd); + report_callback_error(cd, 0); + } + + data_state_go_next(cd, MS_DISCARD, 32); +} + +// Received six dominant bits on the line +static void +data_state_line_error(struct can2040 *cd) +{ + data_state_go_discard(cd); +} + +// Received six passive bits on the line +static void +data_state_line_passive(struct can2040 *cd) +{ + if (cd->parse_state != MS_DISCARD) { + // Bitstuff error + data_state_go_discard(cd); + return; + } + + uint32_t stuffed_bits = cd->unstuf.stuffed_bits >> cd->unstuf.count_stuff; + if (stuffed_bits == 0xffffffff) { + // Counter overflow in "sync" state machine - reset it + pio_sync_setup(cd); + cd->unstuf.stuffed_bits = 0; + data_state_go_discard(cd); + return; + } + + // Look for sof after 9 passive bits (most "PIO sync" will produce) + if (((stuffed_bits + 1) & 0x1ff) == 0) { + data_state_go_next(cd, MS_START, 1); + return; + } + + data_state_go_discard(cd); +} + +// Transition to MS_CRC state - await 16 bits of crc +static void +data_state_go_crc(struct can2040 *cd) +{ + cd->parse_crc &= 0x7fff; + report_note_crc_start(cd); + data_state_go_next(cd, MS_CRC, 16); +} + +// Transition to MS_DATA0 state (if applicable) - await data bits +static void +data_state_go_data(struct can2040 *cd, uint32_t id, uint32_t data) +{ + if (data & (0x03 << 4)) { + // Not a supported header + data_state_go_discard(cd); + return; + } + cd->parse_msg.data32[0] = cd->parse_msg.data32[1] = 0; + uint32_t dlc = data & 0x0f; + cd->parse_msg.dlc = dlc; + if (data & (1 << 6)) { + dlc = 0; + id |= CAN2040_ID_RTR; + } + cd->parse_msg.id = id; + if (dlc) + data_state_go_next(cd, MS_DATA0, dlc >= 4 ? 32 : dlc * 8); + else + data_state_go_crc(cd); +} + +// Handle reception of first bit of header (after start-of-frame (SOF)) +static void +data_state_update_start(struct can2040 *cd, uint32_t data) +{ + cd->parse_msg.id = data; + report_note_message_start(cd); + data_state_go_next(cd, MS_HEADER, 17); +} + +// Handle reception of next 17 header bits +static void +data_state_update_header(struct can2040 *cd, uint32_t data) +{ + data |= cd->parse_msg.id << 17; + if ((data & 0x60) == 0x60) { + // Extended header + cd->parse_msg.id = data; + data_state_go_next(cd, MS_EXT_HEADER, 20); + return; + } + cd->parse_crc = crc_bytes(0, data, 3); + data_state_go_data(cd, (data >> 7) & 0x7ff, data); +} + +// Handle reception of additional 20 bits of "extended header" +static void +data_state_update_ext_header(struct can2040 *cd, uint32_t data) +{ + uint32_t hdr1 = cd->parse_msg.id; + uint32_t crc = crc_bytes(0, hdr1 >> 4, 2); + cd->parse_crc = crc_bytes(crc, ((hdr1 & 0x0f) << 20) | data, 3); + uint32_t id = (((hdr1 << 11) & 0x1ffc0000) | ((hdr1 << 13) & 0x3e000) + | (data >> 7) | CAN2040_ID_EFF); + data_state_go_data(cd, id, data); +} + +// Handle reception of first 1-4 bytes of data content +static void +data_state_update_data0(struct can2040 *cd, uint32_t data) +{ + uint32_t dlc = cd->parse_msg.dlc, bits = dlc >= 4 ? 32 : dlc * 8; + cd->parse_crc = crc_bytes(cd->parse_crc, data, dlc); + cd->parse_msg.data32[0] = __builtin_bswap32(data << (32 - bits)); + if (dlc > 4) + data_state_go_next(cd, MS_DATA1, dlc >= 8 ? 32 : (dlc - 4) * 8); + else + data_state_go_crc(cd); +} + +// Handle reception of bytes 5-8 of data content +static void +data_state_update_data1(struct can2040 *cd, uint32_t data) +{ + uint32_t dlc = cd->parse_msg.dlc, bits = dlc >= 8 ? 32 : (dlc - 4) * 8; + cd->parse_crc = crc_bytes(cd->parse_crc, data, dlc - 4); + cd->parse_msg.data32[1] = __builtin_bswap32(data << (32 - bits)); + data_state_go_crc(cd); +} + +// Handle reception of 16 bits of message CRC (15 crc bits + crc delimiter) +static void +data_state_update_crc(struct can2040 *cd, uint32_t data) +{ + if (((cd->parse_crc << 1) | 1) != data) { + data_state_go_discard(cd); + return; + } + + unstuf_clear_state(&cd->unstuf); + data_state_go_next(cd, MS_ACK, 2); +} + +// Handle reception of 2 bits of ack phase (ack, ack delimiter) +static void +data_state_update_ack(struct can2040 *cd, uint32_t data) +{ + if (data != 0x01) { + data_state_go_discard(cd); + return; + } + report_note_ack_success(cd); + data_state_go_next(cd, MS_EOF0, 4); +} + +// Handle reception of first four end-of-frame (EOF) bits +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); + return; + } + unstuf_clear_state(&cd->unstuf); + data_state_go_next(cd, MS_EOF1, 4); +} + +// Handle reception of end-of-frame (EOF) bits 5-7 and first IFS bit +static void +data_state_update_eof1(struct can2040 *cd, uint32_t data) +{ + if (data >= 0x0e || (data >= 0x0c && report_is_acking_rx(cd))) + // Message is considered fully transmitted + report_note_eof_success(cd); + + if (data == 0x0f) + data_state_go_next(cd, MS_START, 1); + else + data_state_go_discard(cd); +} + +// Handle data received while in MS_DISCARD state +static void +data_state_update_discard(struct can2040 *cd, uint32_t data) +{ + data_state_go_discard(cd); +} + +// Update parsing state after reading the bits of the current field +static void +data_state_update(struct can2040 *cd, uint32_t data) +{ + switch (cd->parse_state) { + case MS_START: data_state_update_start(cd, data); break; + case MS_HEADER: data_state_update_header(cd, data); break; + case MS_EXT_HEADER: data_state_update_ext_header(cd, data); break; + case MS_DATA0: data_state_update_data0(cd, data); break; + case MS_DATA1: data_state_update_data1(cd, data); break; + case MS_CRC: data_state_update_crc(cd, data); break; + case MS_ACK: data_state_update_ack(cd, data); break; + case MS_EOF0: data_state_update_eof0(cd, data); break; + case MS_EOF1: data_state_update_eof1(cd, data); break; + case MS_DISCARD: data_state_update_discard(cd, data); break; + } +} + + +/**************************************************************** + * Input processing + ****************************************************************/ + +// Process an incoming byte of data from PIO "rx" state machine +static void +process_rx(struct can2040 *cd, uint32_t rx_byte) +{ + unstuf_add_bits(&cd->unstuf, rx_byte, 8); + cd->raw_bit_count += 8; + + // undo bit stuffing + for (;;) { + int ret = unstuf_pull_bits(&cd->unstuf); + if (likely(ret > 0)) { + // Need more data + break; + } else if (likely(!ret)) { + // Pulled the next field - process it + data_state_update(cd, cd->unstuf.unstuffed_bits); + } else { + if (ret == -1) + // 6 consecutive high bits + data_state_line_passive(cd); + else + // 6 consecutive low bits + data_state_line_error(cd); + } + } +} + +// Main API irq notification function +void +can2040_pio_irq_handler(struct can2040 *cd) +{ + pio_hw_t *pio_hw = cd->pio_hw; + uint32_t ints = pio_hw->ints0; + while (likely(ints & PIO_IRQ0_INTE_SM1_RXNEMPTY_BITS)) { + uint8_t rx_byte = pio_hw->rxf[1]; + process_rx(cd, rx_byte); + ints = pio_hw->ints0; + if (likely(!ints)) + return; + } + + if (ints & PIO_IRQ0_INTE_SM3_BITS) + // Ack of received message completed successfully + report_line_ackdone(cd); + else if (ints & PIO_IRQ0_INTE_SM2_BITS) + // Transmit message completed successfully + report_line_matched(cd); + else if (ints & PIO_IRQ0_INTE_SM0_BITS) + // Bus is idle, but not all bits may have been flushed yet + report_line_maytx(cd); +} + + +/**************************************************************** + * Transmit queuing + ****************************************************************/ + +// API function to check if transmit space available +int +can2040_check_transmit(struct can2040 *cd) +{ + uint32_t tx_pull_pos = readl(&cd->tx_pull_pos); + uint32_t tx_push_pos = cd->tx_push_pos; + uint32_t pending = tx_push_pos - tx_pull_pos; + return pending < ARRAY_SIZE(cd->tx_queue); +} + +// API function to transmit a message +int +can2040_transmit(struct can2040 *cd, struct can2040_msg *msg) +{ + uint32_t tx_pull_pos = readl(&cd->tx_pull_pos); + uint32_t tx_push_pos = cd->tx_push_pos; + uint32_t pending = tx_push_pos - tx_pull_pos; + if (pending >= ARRAY_SIZE(cd->tx_queue)) + // Tx queue full + return -1; + + // Copy msg into transmit queue + struct can2040_transmit *qt = &cd->tx_queue[tx_qpos(cd, tx_push_pos)]; + uint32_t id = msg->id; + if (id & CAN2040_ID_EFF) + qt->msg.id = id & ~0x20000000; + else + qt->msg.id = id & (CAN2040_ID_RTR | 0x7ff); + qt->msg.dlc = msg->dlc & 0x0f; + uint32_t data_len = qt->msg.dlc > 8 ? 8 : qt->msg.dlc; + if (qt->msg.id & CAN2040_ID_RTR) + data_len = 0; + qt->msg.data32[0] = qt->msg.data32[1] = 0; + memcpy(qt->msg.data, msg->data, data_len); + + // Calculate crc and stuff bits + uint32_t crc = 0; + memset(qt->stuffed_data, 0, sizeof(qt->stuffed_data)); + struct bitstuffer_s bs = { 1, 0, qt->stuffed_data }; + uint32_t edlc = qt->msg.dlc | (qt->msg.id & CAN2040_ID_RTR ? 0x40 : 0); + if (qt->msg.id & CAN2040_ID_EFF) { + // Extended header + uint32_t id = qt->msg.id; + uint32_t h1 = ((id & 0x1ffc0000) >> 11) | 0x60 | ((id & 0x3e000) >> 13); + uint32_t h2 = ((id & 0x1fff) << 7) | edlc; + crc = crc_bytes(crc, h1 >> 4, 2); + crc = crc_bytes(crc, ((h1 & 0x0f) << 20) | h2, 3); + bs_push(&bs, h1, 19); + bs_push(&bs, h2, 20); + } else { + // Standard header + uint32_t hdr = ((qt->msg.id & 0x7ff) << 7) | edlc; + crc = crc_bytes(crc, hdr, 3); + bs_push(&bs, hdr, 19); + } + int i; + for (i=0; imsg.data[i]; + crc = crc_byte(crc, v); + bs_push(&bs, v, 8); + } + qt->crc = crc & 0x7fff; + bs_push(&bs, qt->crc, 15); + bs_pushraw(&bs, 1, 1); + qt->stuffed_words = bs_finalize(&bs); + + // Submit + writel(&cd->tx_push_pos, tx_push_pos + 1); + + // Wakeup if in TS_IDLE state + pio_irq_atomic_set_maytx(cd); + + return 0; +} + + +/**************************************************************** + * Setup + ****************************************************************/ + +// API function to initialize can2040 code +void +can2040_setup(struct can2040 *cd, uint32_t pio_num) +{ + memset(cd, 0, sizeof(*cd)); + cd->pio_num = !!pio_num; + cd->pio_hw = cd->pio_num ? pio1_hw : pio0_hw; +} + +// API function to configure callback +void +can2040_callback_config(struct can2040 *cd, can2040_rx_cb rx_cb) +{ + cd->rx_cb = rx_cb; +} + +// API function to start CANbus interface +void +can2040_start(struct can2040 *cd, uint32_t sys_clock, uint32_t bitrate + , uint32_t gpio_rx, uint32_t gpio_tx) +{ + cd->gpio_rx = gpio_rx; + cd->gpio_tx = gpio_tx; + pio_setup(cd, sys_clock, bitrate); + data_state_go_discard(cd); +} + +// API function to stop and uninitialize can2040 code +void +can2040_shutdown(struct can2040 *cd) +{ + // XXX +} diff --git a/lib/can2040/can2040.h b/lib/can2040/can2040.h new file mode 100644 index 00000000..26a49ebc --- /dev/null +++ b/lib/can2040/can2040.h @@ -0,0 +1,79 @@ +#ifndef _CAN2040_H +#define _CAN2040_H + +#include // uint32_t + +struct can2040_msg { + uint32_t id; + uint32_t dlc; + union { + uint8_t data[8]; + uint32_t data32[2]; + }; +}; + +enum { + CAN2040_ID_RTR = 1<<30, + CAN2040_ID_EFF = 1<<31, +}; + +enum { + CAN2040_NOTIFY_RX = 1<<20, + CAN2040_NOTIFY_TX = 1<<21, + CAN2040_NOTIFY_ERROR = 1<<23, +}; +struct can2040; +typedef void (*can2040_rx_cb)(struct can2040 *cd, uint32_t notify + , struct can2040_msg *msg); + +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_pio_irq_handler(struct can2040 *cd); +int can2040_check_transmit(struct can2040 *cd); +int can2040_transmit(struct can2040 *cd, struct can2040_msg *msg); + + +/**************************************************************** + * Internal definitions + ****************************************************************/ + +struct can2040_bitunstuffer { + uint32_t stuffed_bits, count_stuff; + uint32_t unstuffed_bits, count_unstuff; +}; + +struct can2040_transmit { + struct can2040_msg msg; + uint32_t crc, stuffed_words, stuffed_data[5]; +}; + +struct can2040 { + // Setup + uint32_t pio_num; + void *pio_hw; + uint32_t gpio_rx, gpio_tx; + can2040_rx_cb rx_cb; + + // Bit unstuffing + struct can2040_bitunstuffer unstuf; + uint32_t raw_bit_count; + + // Input data state + uint32_t parse_state; + uint32_t parse_crc; + struct can2040_msg parse_msg; + + // Reporting + uint32_t report_state; + uint32_t report_eof_key; + + // Transmits + uint32_t tx_state; + uint32_t tx_pull_pos, tx_push_pos; + struct can2040_transmit tx_queue[4]; +}; + +#endif // can2040.h diff --git a/lib/canboot/flash_can.py b/lib/canboot/flash_can.py index 18100517..b7877097 100755 --- a/lib/canboot/flash_can.py +++ b/lib/canboot/flash_can.py @@ -496,9 +496,70 @@ class CanSocket: self._loop.remove_reader(self.cansock.fileno()) self.cansock.close() +class SerialSocket: + def __init__(self, loop: asyncio.AbstractEventLoop): + self._loop = loop + self.serial = self.serial_error = None + self.node = CanNode(0, self) + + def _handle_response(self) -> None: + try: + data = self.serial.read(4096) + except self.serial_error as e: + logging.exception("Error on serial read") + self.close() + self.node.feed_data(data) + + def send(self, can_id: int, payload: bytes = b"") -> None: + try: + self.serial.write(payload) + except self.serial_error as e: + logging.exception("Error on serial write") + self.close() + + async def run(self, intf: str, baud: int, fw_path: pathlib.Path) -> None: + if not fw_path.is_file(): + raise FlashCanError("Invalid firmware path '%s'" % (fw_path)) + import serial + self.serial_error = serial.SerialException + try: + serial_dev = serial.Serial(baudrate=baud, timeout=0, + exclusive=True) + serial_dev.port = intf + serial_dev.open() + except (OSError, IOError, self.serial_error) as e: + raise FlashCanError("Unable to open serial port: %s" % (e,)) + self.serial = serial_dev + self._loop.add_reader(self.serial.fileno(), self._handle_response) + flasher = CanFlasher(self.node, fw_path) + try: + await flasher.connect_btl() + await flasher.send_file() + await flasher.verify_file() + finally: + # always attempt to send the complete command. If + # there is an error it will exit the bootloader + # unless comms were broken + await flasher.finish() + + def close(self): + if self.serial is None: + return + self._loop.remove_reader(self.serial.fileno()) + self.serial.close() + self.serial = None + def main(): parser = argparse.ArgumentParser( description="Can Bootloader Flash Utility") + parser.add_argument( + "-d", "--device", metavar='', + help="Serial Device" + ) + parser.add_argument( + "-b", "--baud", default=250000, metavar='', + help="Serial baud rate" + ) parser.add_argument( "-i", "--interface", default="can0", metavar='', help="Can Interface" @@ -522,27 +583,37 @@ def main(): args = parser.parse_args() if not args.verbose: - logging.getLogger().setLevel(logging.CRITICAL) + logging.getLogger().setLevel(logging.ERROR) intf = args.interface fpath = pathlib.Path(args.firmware).expanduser().resolve() loop = asyncio.get_event_loop() + iscan = args.device is None + sock = None try: - cansock = CanSocket(loop) - if args.query: - loop.run_until_complete(cansock.run_query(intf)) + if iscan: + sock = CanSocket(loop) + if args.query: + loop.run_until_complete(sock.run_query(intf)) + else: + if args.uuid is None: + raise FlashCanError( + "The 'uuid' option must be specified to flash a device" + ) + uuid = int(args.uuid, 16) + loop.run_until_complete(sock.run(intf, uuid, fpath)) else: - if args.uuid is None: + if args.device is None: raise FlashCanError( - "The 'uuid' option must be specified to flash a device" + "The 'device' option must be specified to flash a device" ) - uuid = int(args.uuid, 16) - loop.run_until_complete(cansock.run(intf, uuid, fpath)) + sock = SerialSocket(loop) + loop.run_until_complete(sock.run(args.device, args.baud, fpath)) except Exception as e: logging.exception("Can Flash Error") sys.exit(-1) finally: - if cansock is not None: - cansock.close() + if sock is not None: + sock.close() if args.query: output_line("Query Complete") else: diff --git a/lib/rp2040/boot_stage2/boot2_generic_03h.S b/lib/rp2040/boot_stage2/boot2_generic_03h.S index a10e66ab..cc7e4fbc 100644 --- a/lib/rp2040/boot_stage2/boot2_generic_03h.S +++ b/lib/rp2040/boot_stage2/boot2_generic_03h.S @@ -16,7 +16,7 @@ // 4-byte checksum. Therefore code size cannot exceed 252 bytes. // ---------------------------------------------------------------------------- -#include "pico/asm_helper.S" +//#include "pico/asm_helper.S" #include "hardware/regs/addressmap.h" #include "hardware/regs/ssi.h" diff --git a/lib/rp2040/rp2040.patch b/lib/rp2040/rp2040.patch index 0aa24bd5..bae9e6d1 100644 --- a/lib/rp2040/rp2040.patch +++ b/lib/rp2040/rp2040.patch @@ -1,3 +1,16 @@ +diff --git a/lib/rp2040/boot_stage2/boot2_generic_03h.S b/lib/rp2040/boot_stage2/boot2_generic_03h.S +index a10e66abd..cc7e4fbc7 100644 +--- a/lib/rp2040/boot_stage2/boot2_generic_03h.S ++++ b/lib/rp2040/boot_stage2/boot2_generic_03h.S +@@ -16,7 +16,7 @@ + // 4-byte checksum. Therefore code size cannot exceed 252 bytes. + // ---------------------------------------------------------------------------- + +-#include "pico/asm_helper.S" ++//#include "pico/asm_helper.S" + #include "hardware/regs/addressmap.h" + #include "hardware/regs/ssi.h" + diff --git a/lib/rp2040/boot_stage2/boot2_w25q080.S b/lib/rp2040/boot_stage2/boot2_w25q080.S index ad3238e2..8fb3def4 100644 --- a/lib/rp2040/boot_stage2/boot2_w25q080.S diff --git a/scripts/flash_usb.py b/scripts/flash_usb.py index 68e61570..3d62a643 100755 --- a/scripts/flash_usb.py +++ b/scripts/flash_usb.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Tool to enter a USB bootloader and flash Klipper # # Copyright (C) 2019 Kevin O'Connor @@ -27,6 +27,8 @@ def enter_bootloader(device): # Translate a serial device name to a stable serial name in /dev/serial/by-path/ def translate_serial_to_tty(device): ttyname = os.path.realpath(device) + if not os.path.exists('/dev/serial/by-path/'): + raise error("Unable to find serial 'by-path' folder") for fname in os.listdir('/dev/serial/by-path/'): fname = '/dev/serial/by-path/' + fname if os.path.realpath(fname) == ttyname: @@ -71,6 +73,42 @@ def wait_path(path, alt_path=None): if cur_time > end_time: return path +CANBOOT_ID ="1d50:6177" + +def detect_canboot(devpath): + usbdir = os.path.dirname(devpath) + try: + with open(os.path.join(usbdir, "idVendor")) as f: + vid = f.read().strip().lower() + with open(os.path.join(usbdir, "idProduct")) as f: + pid = f.read().strip().lower() + except Exception: + return False + usbid = "%s:%s" % (vid, pid) + return usbid == CANBOOT_ID + +def call_flashcan(device, binfile): + try: + import serial + except ModuleNotFoundError: + sys.stderr.write( + "Python's pyserial module is required to update. Install\n" + "with the following command:\n" + " %s -m pip install pyserial\n\n" % (sys.executable,) + ) + sys.exit(-1) + args = [sys.executable, "lib/canboot/flash_can.py", "-d", + device, "-f", binfile] + sys.stderr.write(" ".join(args) + '\n\n') + res = subprocess.call(args) + if res != 0: + sys.stderr.write("Error running flash_can.py\n") + sys.exit(-1) + +def flash_canboot(options, binfile): + ttyname, pathname = translate_serial_to_tty(options.device) + call_flashcan(pathname, binfile) + # Flash via a call to bossac def flash_bossac(device, binfile, extra_flags=[]): ttyname, pathname = translate_serial_to_tty(device) @@ -108,10 +146,14 @@ def flash_dfuutil(device, binfile, extra_flags=[], sudo=True): if hexfmt_r.match(device.strip()): call_dfuutil(["-d", ","+device.strip()] + extra_flags, binfile, sudo) return + ttyname, serbypath = translate_serial_to_tty(device) buspath, devpath = translate_serial_to_usb_path(device) enter_bootloader(device) pathname = wait_path(devpath) - call_dfuutil(["-p", buspath] + extra_flags, binfile, sudo) + if detect_canboot(devpath): + call_flashcan(serbypath, binfile) + else: + call_dfuutil(["-p", buspath] + extra_flags, binfile, sudo) def call_hidflash(binfile, sudo): args = ["lib/hidflash/hid-flash", binfile] @@ -128,10 +170,40 @@ def flash_hidflash(device, binfile, sudo=True): if hexfmt_r.match(device.strip()): call_hidflash(binfile, sudo) return + ttyname, serbypath = translate_serial_to_tty(device) buspath, devpath = translate_serial_to_usb_path(device) enter_bootloader(device) pathname = wait_path(devpath) - call_hidflash(binfile, sudo) + if detect_canboot(devpath): + call_flashcan(serbypath, binfile) + else: + call_hidflash(binfile, sudo) + +# Call Klipper modified "picoboot" +def call_picoboot(bus, addr, binfile, sudo): + args = ["lib/rp2040_flash/rp2040_flash", binfile] + if bus is not None: + args.extend([bus, addr]) + if sudo: + args.insert(0, "sudo") + sys.stderr.write(" ".join(args) + '\n\n') + res = subprocess.call(args) + if res != 0: + raise error("Error running rp2040_flash") + +# Flash via Klipper modified "picoboot" +def flash_picoboot(device, binfile, sudo): + buspath, devpath = translate_serial_to_usb_path(device) + # We need one level up to get access to busnum/devnum files + usbdir = os.path.dirname(devpath) + enter_bootloader(device) + wait_path(usbdir) + with open(usbdir + "/busnum") as f: + bus = f.read().strip() + with open(usbdir + "/devnum") as f: + addr = f.read().strip() + call_picoboot(bus, addr, binfile, sudo) + ###################################################################### # Device specific helpers @@ -162,22 +234,6 @@ def flash_atsamd(options, binfile): options.device, str(e))) sys.exit(-1) -# Look for an rp2040 and flash it with rp2040_flash. -def rp2040_flash(devpath, binfile, sudo): - args = ["lib/rp2040_flash/rp2040_flash", binfile] - if len(devpath) > 0: - with open(devpath + "/busnum") as f: - bus = f.read().strip() - with open(devpath + "/devnum") as f: - addr = f.read().strip() - args += [bus, addr] - sys.stderr.write(" ".join(args) + '\n\n') - if sudo: - args.insert(0, "sudo") - res = subprocess.call(args) - if res != 0: - raise error("Error running rp2040_flash") - SMOOTHIE_HELP = """ Failed to flash to %s: %s @@ -259,27 +315,22 @@ def flash_stm32f4(options, binfile): RP2040_HELP = """ Failed to flash to %s: %s -If the device is already in bootloader mode, use 'first' as FLASH_DEVICE. -This will use rp2040_flash to flash the first available rp2040. +If the device is already in bootloader mode it can be flashed with the +following command: + make flash FLASH_DEVICE=2e8a:0003 Alternatively, one can flash rp2040 boards like the Pico by manually entering bootloader mode(hold bootsel button during powerup), mount the device as a usb drive, and copy klipper.uf2 to the device. + """ def flash_rp2040(options, binfile): try: - if options.device.lower() == "first": - rp2040_flash("", binfile, options.sudo) - return - - buspath, devpath = translate_serial_to_usb_path(options.device) - # We need one level up to get access to busnum/devnum files - devpath = os.path.dirname(devpath) - enter_bootloader(options.device) - wait_path(devpath) - rp2040_flash(devpath, binfile, options.sudo) - + if options.device.lower() == "2e8a:0003": + call_picoboot(None, None, binfile, options.sudo) + else: + flash_picoboot(options.device, binfile, options.sudo) except error as e: sys.stderr.write(RP2040_HELP % (options.device, str(e))) sys.exit(-1) @@ -288,7 +339,8 @@ MCUTYPES = { 'sam3': flash_atsam3, 'sam4': flash_atsam4, 'samd': flash_atsamd, 'same70': flash_atsam4, 'lpc176': flash_lpc176x, 'stm32f103': flash_stm32f1, 'stm32f4': flash_stm32f4, 'stm32f042': flash_stm32f4, - 'stm32f072': flash_stm32f4, 'rp2040': flash_rp2040 + 'stm32f072': flash_stm32f4, 'stm32g0b1': flash_stm32f4, + 'stm32h7': flash_stm32f4, 'rp2040': flash_rp2040 } diff --git a/scripts/graphstats.py b/scripts/graphstats.py index 7cd52964..5cd2ad34 100755 --- a/scripts/graphstats.py +++ b/scripts/graphstats.py @@ -181,14 +181,48 @@ def plot_system(data): ax1.grid(True) return fig -def plot_frequency(data, mcu): +def plot_mcu_frequencies(data): all_keys = {} for d in data: all_keys.update(d) - one_mcu = mcu is not None graph_keys = { key: ([], []) for key in all_keys - if (key in ("freq", "adj") or (not one_mcu and ( - key.endswith(":freq") or key.endswith(":adj")))) } + if (key in ("freq", "adj") + or (key.endswith(":freq") or key.endswith(":adj"))) } + for d in data: + st = datetime.datetime.utcfromtimestamp(d['#sampletime']) + for key, (times, values) in graph_keys.items(): + val = d.get(key) + if val not in (None, '0', '1'): + times.append(st) + values.append(float(val)) + est_mhz = { key: round((sum(values)/len(values)) / 1000000.) + for key, (times, values) in graph_keys.items() } + + # Build plot + fig, ax1 = matplotlib.pyplot.subplots() + ax1.set_title("MCU frequencies") + ax1.set_xlabel('Time') + ax1.set_ylabel('Microsecond deviation') + for key in sorted(graph_keys): + times, values = graph_keys[key] + mhz = est_mhz[key] + label = "%s(%dMhz)" % (key, mhz) + hz = mhz * 1000000. + ax1.plot_date(times, [(v - hz)/mhz for v in values], '.', label=label) + fontP = matplotlib.font_manager.FontProperties() + fontP.set_size('x-small') + ax1.legend(loc='best', prop=fontP) + ax1.xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%H:%M')) + ax1.yaxis.set_major_formatter(matplotlib.ticker.FormatStrFormatter('%d')) + ax1.grid(True) + return fig + +def plot_mcu_frequency(data, mcu): + all_keys = {} + for d in data: + all_keys.update(d) + graph_keys = { key: ([], []) for key in all_keys + if key in ("freq", "adj") } for d in data: st = datetime.datetime.utcfromtimestamp(d['#sampletime']) for key, (times, values) in graph_keys.items(): @@ -199,10 +233,7 @@ def plot_frequency(data, mcu): # Build plot fig, ax1 = matplotlib.pyplot.subplots() - if one_mcu: - ax1.set_title("MCU '%s' frequency" % (mcu,)) - else: - ax1.set_title("MCU frequency") + ax1.set_title("MCU '%s' frequency" % (mcu,)) ax1.set_xlabel('Time') ax1.set_ylabel('Frequency') for key in sorted(graph_keys): @@ -286,7 +317,10 @@ def main(): if options.heater is not None: fig = plot_temperature(data, options.heater) elif options.frequency: - fig = plot_frequency(data, options.mcu) + if options.mcu is not None: + fig = plot_mcu_frequency(data, options.mcu) + else: + fig = plot_mcu_frequencies(data) elif options.system: fig = plot_system(data) else: diff --git a/scripts/install-debian.sh b/scripts/install-debian.sh index d1d910ba..9c2cd3b4 100755 --- a/scripts/install-debian.sh +++ b/scripts/install-debian.sh @@ -20,7 +20,7 @@ install_packages() PKGLIST="${PKGLIST} avrdude gcc-avr binutils-avr avr-libc" # ARM chip installation and building PKGLIST="${PKGLIST} stm32flash libnewlib-arm-none-eabi" - PKGLIST="${PKGLIST} gcc-arm-none-eabi binutils-arm-none-eabi libusb-1.0" + PKGLIST="${PKGLIST} gcc-arm-none-eabi binutils-arm-none-eabi libusb-1.0 pkg-config" # Update system package info report_status "Running apt-get update..." diff --git a/scripts/spi_flash/board_defs.py b/scripts/spi_flash/board_defs.py index bb23db15..db78cfc0 100644 --- a/scripts/spi_flash/board_defs.py +++ b/scripts/spi_flash/board_defs.py @@ -65,6 +65,16 @@ BOARD_DEFS = { 'mcu': 'stm32h743xx', 'spi_bus': 'spi3a', 'cs_pin': 'PA15' + }, + 'monster8': { + 'mcu': "stm32f407xx", + 'spi_bus': "spi3a", + "cs_pin": "PC9" + }, + 'fly-gemini-v2': { + 'mcu': "stm32f405xx", + 'spi_bus': "spi1", + "cs_pin": "PA4" } } @@ -99,7 +109,9 @@ BOARD_ALIASES = { 'mks-robin-e3d': BOARD_DEFS['mks-robin-e3'], 'fysetc-spider-v1': BOARD_DEFS['fysetc-spider'], 'fysetc-s6-v1.2': BOARD_DEFS['fysetc-spider'], - 'fysetc-s6-v2': BOARD_DEFS['fysetc-spider'] + 'fysetc-s6-v2': BOARD_DEFS['fysetc-spider'], + 'monster8': BOARD_DEFS['monster8'], + 'robin_v3': BOARD_DEFS['monster8'] } def list_boards(): diff --git a/src/Kconfig b/src/Kconfig index ad2454d8..62efcbe6 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -42,6 +42,8 @@ source "src/linux/Kconfig" source "src/simulator/Kconfig" # Generic configuration options for serial ports +config SERIAL + bool config SERIAL_BAUD depends on SERIAL int "Baud rate for serial port" if LOW_LEVEL_OPTIONS @@ -51,28 +53,49 @@ config SERIAL_BAUD to 250000. Read the FAQ before changing this value. # Generic configuration options for USB +config USBSERIAL + bool +config USBCANBUS + bool +config USB + bool + default y if USBSERIAL || USBCANBUS config USB_VENDOR_ID default 0x1d50 config USB_DEVICE_ID default 0x614e config USB_SERIAL_NUMBER_CHIPID - depends on HAVE_CHIPID + depends on USB && HAVE_CHIPID default y config USB_SERIAL_NUMBER default "12345" menu "USB ids" - depends on USBSERIAL && LOW_LEVEL_OPTIONS + depends on USB && LOW_LEVEL_OPTIONS config USB_VENDOR_ID - hex "USB vendor ID" + hex "USB vendor ID" if USBSERIAL config USB_DEVICE_ID - hex "USB device ID" + hex "USB device ID" if USBSERIAL config USB_SERIAL_NUMBER_CHIPID bool "USB serial number from CHIPID" if HAVE_CHIPID config USB_SERIAL_NUMBER string "USB serial number" if !USB_SERIAL_NUMBER_CHIPID endmenu +# Generic configuration options for CANbus +config CANSERIAL + bool +config CANBUS + bool + default y if CANSERIAL || USBCANBUS +config CANBUS_FREQUENCY + int "CAN bus speed" if LOW_LEVEL_OPTIONS && CANBUS + default 500000 +config CANBUS_FILTER + bool + default y if CANSERIAL + +# Support setting gpio state at startup config INITIAL_PINS string "GPIO pins to set at micro-controller startup" depends on LOW_LEVEL_OPTIONS diff --git a/src/Makefile b/src/Makefile index 9ce4b3fe..94e896d2 100644 --- a/src/Makefile +++ b/src/Makefile @@ -9,5 +9,6 @@ src-$(CONFIG_HAVE_GPIO_I2C) += i2ccmds.c src-$(CONFIG_HAVE_GPIO_UART) += uartcmds.c src-$(CONFIG_HAVE_GPIO_HARD_PWM) += pwmcmds.c bb-src-$(CONFIG_HAVE_GPIO_SPI) := spi_software.c sensor_adxl345.c sensor_angle.c +bb-src-$(CONFIG_HAVE_GPIO_I2C) += sensor_mpu9250.c src-$(CONFIG_HAVE_GPIO_BITBANGING) += $(bb-src-y) lcd_st7920.c lcd_hd44780.c \ buttons.c tmcuart.c neopixel.c pulse_counter.c diff --git a/src/atsam/Kconfig b/src/atsam/Kconfig index 5aa5e72c..88d1882b 100644 --- a/src/atsam/Kconfig +++ b/src/atsam/Kconfig @@ -90,10 +90,6 @@ config STACK_SIZE int default 512 -config USBSERIAL - bool -config SERIAL - bool choice prompt "Communication interface" config ATSAM_USB diff --git a/src/atsam/main.c b/src/atsam/main.c index 5a19d529..8c2e4318 100644 --- a/src/atsam/main.c +++ b/src/atsam/main.c @@ -6,7 +6,7 @@ #include "board/armcm_boot.h" // armcm_main #include "board/irq.h" // irq_disable -#include "board/usb_cdc.h" // usb_request_bootloader +#include "board/misc.h" // bootloader_request #include "command.h" // DECL_COMMAND_FLAGS #include "internal.h" // WDT #include "sched.h" // sched_main @@ -94,7 +94,7 @@ DECL_COMMAND_FLAGS(command_reset, HF_IN_SHUTDOWN, "reset"); #endif void noinline __aligned(16) // align for predictable flash code access -usb_request_bootloader(void) +bootloader_request(void) { irq_disable(); // Request boot from ROM (instead of boot from flash) diff --git a/src/atsamd/Kconfig b/src/atsamd/Kconfig index d357c385..c5fe5494 100644 --- a/src/atsamd/Kconfig +++ b/src/atsamd/Kconfig @@ -135,10 +135,6 @@ config FLASH_START default 0x2000 if FLASH_START_2000 default 0x0000 -config USBSERIAL - bool -config SERIAL - bool choice prompt "Communication interface" config ATSAMD_USB diff --git a/src/atsamd/clock.c b/src/atsamd/clock.c index a60fc1eb..496ae56e 100644 --- a/src/atsamd/clock.c +++ b/src/atsamd/clock.c @@ -89,7 +89,7 @@ clock_init_internal(void) uint32_t fine = GET_FUSE(FUSES_DFLL48M_FINE_CAL); SYSCTRL->DFLLVAL.reg = (SYSCTRL_DFLLVAL_COARSE(coarse) | SYSCTRL_DFLLVAL_FINE(fine)); - if (CONFIG_USBSERIAL) { + if (CONFIG_USB) { // Enable USB clock recovery mode uint32_t mul = DIV_ROUND_CLOSEST(FREQ_MAIN, 1000); SYSCTRL->DFLLMUL.reg = (SYSCTRL_DFLLMUL_FSTEP(10) diff --git a/src/atsamd/samd51_clock.c b/src/atsamd/samd51_clock.c index cf7402e5..2f769435 100644 --- a/src/atsamd/samd51_clock.c +++ b/src/atsamd/samd51_clock.c @@ -157,7 +157,7 @@ static void clock_init_internal(void) { // Enable USB clock recovery mode if applicable - if (CONFIG_USBSERIAL) { + if (CONFIG_USB) { // Temporarily switch main clock to internal 32K clock gen_clock(CLKGEN_MAIN, GCLK_GENCTRL_SRC_OSCULP32K); diff --git a/src/atsamd/usbserial.c b/src/atsamd/usbserial.c index 273c3dbd..531c7dae 100644 --- a/src/atsamd/usbserial.c +++ b/src/atsamd/usbserial.c @@ -9,6 +9,7 @@ #include "board/armcm_boot.h" // armcm_enable_irq #include "board/io.h" // readl #include "board/irq.h" // irq_disable +#include "board/misc.h" // bootloader_request #include "board/usb_cdc.h" // usb_notify_ep0 #include "board/usb_cdc_ep.h" // USB_CDC_EP_BULK_IN #include "command.h" // DECL_CONSTANT_STR @@ -26,7 +27,7 @@ static uint8_t __aligned(4) acmin[USB_CDC_EP_ACM_SIZE]; static uint8_t __aligned(4) bulkout[USB_CDC_EP_BULK_OUT_SIZE]; static uint8_t __aligned(4) bulkin[USB_CDC_EP_BULK_IN_SIZE]; -static UsbDeviceDescriptor usb_desc[USB_CDC_EP_BULK_IN + 1] = { +static UsbDeviceDescriptor usb_desc[] = { [0] = { { { .ADDR.reg = (uint32_t)ep0out, @@ -172,7 +173,7 @@ usb_set_configure(void) } void -usb_request_bootloader(void) +bootloader_request(void) { if (!CONFIG_FLASH_START) return; diff --git a/src/avr/usbserial.c b/src/avr/usbserial.c index 442ef934..ab61fde2 100644 --- a/src/avr/usbserial.c +++ b/src/avr/usbserial.c @@ -7,6 +7,7 @@ #include // USB_COM_vect #include // NULL #include "autoconf.h" // CONFIG_MACH_at90usb1286 +#include "board/misc.h" // bootloader_request #include "board/usb_cdc.h" // usb_notify_ep0 #include "board/usb_cdc_ep.h" // USB_CDC_EP_BULK_IN #include "pgm.h" // READP @@ -179,7 +180,7 @@ usb_set_configure(void) } void -usb_request_bootloader(void) +bootloader_request(void) { } diff --git a/src/generic/armcm_reset.c b/src/generic/armcm_reset.c index 19c2096d..67ff5f57 100644 --- a/src/generic/armcm_reset.c +++ b/src/generic/armcm_reset.c @@ -4,12 +4,45 @@ // // This file may be distributed under the terms of the GNU GPLv3 license. +#include "armcm_reset.h" // try_request_canboot +#include "autoconf.h" // CONFIG_FLASH_START #include "board/internal.h" // NVIC_SystemReset +#include "board/irq.h" // irq_disable #include "command.h" // DECL_COMMAND_FLAGS +#define CANBOOT_SIGNATURE 0x21746f6f426e6143 +#define CANBOOT_REQUEST 0x5984E3FA6CA1589B +#define CANBOOT_BYPASS 0x7b06ec45a9a8243d + +static void +canboot_reset(uint64_t req_signature) +{ + if (!(CONFIG_FLASH_START & 0x00FFFFFF)) + // No bootloader + return; + uint32_t *bl_vectors = (uint32_t *)(CONFIG_FLASH_START & 0xFF000000); + uint64_t *boot_sig = (uint64_t *)(bl_vectors[1] - 9); + uint64_t *req_sig = (uint64_t *)bl_vectors[0]; + if (boot_sig == (void *)ALIGN((size_t)boot_sig, 8) && + *boot_sig == CANBOOT_SIGNATURE && + req_sig == (void *)ALIGN((size_t)req_sig, 8)) + { + irq_disable(); + *req_sig = req_signature; + NVIC_SystemReset(); + } +} + +void +try_request_canboot(void) +{ + canboot_reset(CANBOOT_REQUEST); +} + void command_reset(uint32_t *args) { + canboot_reset(CANBOOT_BYPASS); NVIC_SystemReset(); } DECL_COMMAND_FLAGS(command_reset, HF_IN_SHUTDOWN, "reset"); diff --git a/src/generic/armcm_reset.h b/src/generic/armcm_reset.h new file mode 100644 index 00000000..1627eddd --- /dev/null +++ b/src/generic/armcm_reset.h @@ -0,0 +1,6 @@ +#ifndef __GENERIC_ARMCM_RESET_H +#define __GENERIC_ARMCM_RESET_H + +void try_request_canboot(void); + +#endif // armcm_reset.h diff --git a/src/generic/canbus.c b/src/generic/canbus.c index bcae1589..941ccd00 100644 --- a/src/generic/canbus.c +++ b/src/generic/canbus.c @@ -1,317 +1,36 @@ -// Generic handling of serial over CAN support +// Wrapper functions connecting canserial.c to low-level can hardware // -// Copyright (C) 2019 Eug Krashtan -// Copyright (C) 2020 Pontus Borg -// Copyright (C) 2021 Kevin O'Connor +// Copyright (C) 2022 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. -#include // memcpy -#include "canbus.h" // canbus_set_uuid +#include "autoconf.h" // CONFIG_CANBUS_FREQUENCY +#include "canbus.h" // canbus_send +#include "canserial.h" // canserial_send #include "command.h" // DECL_CONSTANT -#include "generic/io.h" // readb -#include "generic/irq.h" // irq_disable -#include "generic/misc.h" // console_sendf -#include "board/internal.h" // NVIC_SystemReset -#include "sched.h" // sched_wake_task -static uint32_t canbus_assigned_id; -static uint8_t canbus_uuid[CANBUS_UUID_LEN]; +DECL_CONSTANT("CANBUS_FREQUENCY", CONFIG_CANBUS_FREQUENCY); +int +canserial_send(struct canbus_msg *msg) +{ + return canbus_send(msg); +} -/**************************************************************** - * Data transmission over CAN - ****************************************************************/ - -static struct task_wake canbus_tx_wake; -static uint8_t transmit_buf[96], transmit_pos, transmit_max; +void +canserial_set_filter(uint32_t id) +{ + canbus_set_filter(id); +} void canbus_notify_tx(void) { - sched_wake_task(&canbus_tx_wake); + canserial_notify_tx(); } void -canbus_tx_task(void) +canbus_process_data(struct canbus_msg *msg) { - if (!sched_check_wake(&canbus_tx_wake)) - return; - uint32_t id = canbus_assigned_id; - if (!id) { - transmit_pos = transmit_max = 0; - return; - } - uint32_t tpos = transmit_pos, tmax = transmit_max; - for (;;) { - int avail = tmax - tpos, now = avail > 8 ? 8 : avail; - if (avail <= 0) - break; - int ret = canbus_send(id + 1, now, &transmit_buf[tpos]); - if (ret <= 0) - break; - tpos += now; - } - transmit_pos = tpos; + canserial_process_data(msg); } -DECL_TASK(canbus_tx_task); - -// Encode and transmit a "response" message -uint_fast8_t -console_sendf(const struct command_encoder *ce, va_list args) -{ - // Verify space for message - uint32_t tpos = transmit_pos, tmax = transmit_max; - if (tpos >= tmax) - transmit_pos = transmit_max = tpos = tmax = 0; - uint32_t max_size = ce->max_size; - if (tmax + max_size > sizeof(transmit_buf)) { - if (tmax + max_size - tpos > sizeof(transmit_buf)) - // Not enough space for message - return max_size > sizeof(transmit_buf); - // Move buffer - tmax -= tpos; - memmove(&transmit_buf[0], &transmit_buf[tpos], tmax); - transmit_pos = tpos = 0; - transmit_max = tmax; - } - - // Generate message - uint32_t msglen = command_encode_and_frame(&transmit_buf[tmax], ce, args); - - // Start message transmit - transmit_max = tmax + msglen; - canbus_notify_tx(); - return 1; -} - - -/**************************************************************** - * CAN "admin" command handling - ****************************************************************/ - -// Available commands and responses -#define CANBUS_CMD_QUERY_UNASSIGNED 0x00 -#define CANBUS_CMD_SET_KLIPPER_NODEID 0x01 -#define CANBUS_CMD_REQUEST_BOOTLOADER 0x02 -#define CANBUS_RESP_NEED_NODEID 0x20 - -// CanBoot definitions -#define CANBOOT_SIGNATURE 0x21746f6f426e6143 -#define CANBOOT_REQUEST 0x5984E3FA6CA1589B - -// Helper to verify a UUID in a command matches this chip's UUID -static int -can_check_uuid(uint32_t id, uint32_t len, uint8_t *data) -{ - return len >= 7 && memcmp(&data[1], canbus_uuid, sizeof(canbus_uuid)) == 0; -} - -// Helpers to encode/decode a CAN identifier to a 1-byte "nodeid" -static int -can_get_nodeid(void) -{ - if (!canbus_assigned_id) - return 0; - return (canbus_assigned_id - 0x100) >> 1; -} -static uint32_t -can_decode_nodeid(int nodeid) -{ - return (nodeid << 1) + 0x100; -} - -static void -can_process_query_unassigned(uint32_t id, uint32_t len, uint8_t *data) -{ - if (canbus_assigned_id) - return; - uint8_t send[8]; - send[0] = CANBUS_RESP_NEED_NODEID; - memcpy(&send[1], canbus_uuid, sizeof(canbus_uuid)); - send[7] = CANBUS_CMD_SET_KLIPPER_NODEID; - // Send with retry - for (;;) { - int ret = canbus_send(CANBUS_ID_ADMIN_RESP, 8, send); - if (ret >= 0) - return; - } -} - -static void -can_id_conflict(void) -{ - canbus_assigned_id = 0; - canbus_set_filter(canbus_assigned_id); - shutdown("Another CAN node assigned this ID"); -} - -static void -can_process_set_klipper_nodeid(uint32_t id, uint32_t len, uint8_t *data) -{ - if (len < 8) - return; - uint32_t newid = can_decode_nodeid(data[7]); - if (can_check_uuid(id, len, data)) { - if (newid != canbus_assigned_id) { - canbus_assigned_id = newid; - canbus_set_filter(canbus_assigned_id); - } - } else if (newid == canbus_assigned_id) { - can_id_conflict(); - } -} - -static void -can_process_request_bootloader(uint32_t id, uint32_t len, uint8_t *data) -{ - if (!can_check_uuid(id, len, data)) - return; - uint32_t *bl_vectors = (uint32_t *)(CONFIG_FLASH_START & 0xFF000000); - uint64_t *boot_sig = (uint64_t *)(bl_vectors[1] - 9); - uint64_t *req_sig = (uint64_t *)bl_vectors[0]; - if (boot_sig == (void *)ALIGN((size_t)boot_sig, 8) && - *boot_sig == CANBOOT_SIGNATURE && - req_sig == (void *)ALIGN((size_t)req_sig, 8)) - { - irq_disable(); - *req_sig = CANBOOT_REQUEST; - NVIC_SystemReset(); - } -} - -// Handle an "admin" command -static void -can_process(uint32_t id, uint32_t len, uint8_t *data) -{ - if (!len) - return; - switch (data[0]) { - case CANBUS_CMD_QUERY_UNASSIGNED: - can_process_query_unassigned(id, len, data); - break; - case CANBUS_CMD_SET_KLIPPER_NODEID: - can_process_set_klipper_nodeid(id, len, data); - break; - case CANBUS_CMD_REQUEST_BOOTLOADER: - can_process_request_bootloader(id, len, data); - break; - } -} - - -/**************************************************************** - * CAN packet reading - ****************************************************************/ - -static struct task_wake canbus_rx_wake; - -void -canbus_notify_rx(void) -{ - sched_wake_task(&canbus_rx_wake); -} - -static uint8_t receive_buf[192], receive_pos; -DECL_CONSTANT("RECEIVE_WINDOW", ARRAY_SIZE(receive_buf)); - -// Handle incoming data (called from IRQ handler) -void -canbus_process_data(uint32_t id, uint32_t len, uint8_t *data) -{ - if (!id || id != canbus_assigned_id) - return; - int rpos = receive_pos; - if (len > sizeof(receive_buf) - rpos) - len = sizeof(receive_buf) - rpos; - memcpy(&receive_buf[rpos], data, len); - receive_pos = rpos + len; - canbus_notify_rx(); -} - -// Remove from the receive buffer the given number of bytes -static void -console_pop_input(int len) -{ - int copied = 0; - for (;;) { - int rpos = readb(&receive_pos); - int needcopy = rpos - len; - if (needcopy) { - memmove(&receive_buf[copied], &receive_buf[copied + len] - , needcopy - copied); - copied = needcopy; - canbus_notify_rx(); - } - irqstatus_t flag = irq_save(); - if (rpos != readb(&receive_pos)) { - // Raced with irq handler - retry - irq_restore(flag); - continue; - } - receive_pos = needcopy; - irq_restore(flag); - break; - } -} - -// Task to process incoming commands and admin messages -void -canbus_rx_task(void) -{ - if (!sched_check_wake(&canbus_rx_wake)) - return; - - // Read any pending CAN packets - for (;;) { - uint8_t data[8]; - uint32_t id; - int ret = canbus_read(&id, data); - if (ret < 0) - break; - if (id && id == canbus_assigned_id + 1) - can_id_conflict(); - else if (id == CANBUS_ID_ADMIN) - can_process(id, ret, data); - } - - // Check for a complete message block and process it - uint_fast8_t rpos = readb(&receive_pos), pop_count; - int ret = command_find_block(receive_buf, rpos, &pop_count); - if (ret > 0) - command_dispatch(receive_buf, pop_count); - if (ret) { - console_pop_input(pop_count); - if (ret > 0) - command_send_ack(); - } -} -DECL_TASK(canbus_rx_task); - - -/**************************************************************** - * Setup and shutdown - ****************************************************************/ - -void -command_get_canbus_id(uint32_t *args) -{ - sendf("canbus_id canbus_uuid=%.*s canbus_nodeid=%u" - , sizeof(canbus_uuid), canbus_uuid, can_get_nodeid()); -} -DECL_COMMAND_FLAGS(command_get_canbus_id, HF_IN_SHUTDOWN, "get_canbus_id"); - -void -canbus_set_uuid(void *uuid) -{ - memcpy(canbus_uuid, uuid, sizeof(canbus_uuid)); - canbus_notify_rx(); -} - -void -canbus_shutdown(void) -{ - canbus_notify_tx(); - canbus_notify_rx(); -} -DECL_SHUTDOWN(canbus_shutdown); diff --git a/src/generic/canbus.h b/src/generic/canbus.h index a7df077d..8032611a 100644 --- a/src/generic/canbus.h +++ b/src/generic/canbus.h @@ -3,19 +3,26 @@ #include // uint32_t -#define CANBUS_ID_ADMIN 0x3f0 -#define CANBUS_ID_ADMIN_RESP 0x3f1 -#define CANBUS_UUID_LEN 6 +struct canbus_msg { + uint32_t id; + uint32_t dlc; + union { + uint8_t data[8]; + uint32_t data32[2]; + }; +}; + +#define CANMSG_ID_RTR (1<<30) +#define CANMSG_ID_EFF (1<<31) + +#define CANMSG_DATA_LEN(msg) ((msg)->dlc > 8 ? 8 : (msg)->dlc) // callbacks provided by board specific code -int canbus_read(uint32_t *id, uint8_t *data); -int canbus_send(uint32_t id, uint32_t len, uint8_t *data); +int canbus_send(struct canbus_msg *msg); void canbus_set_filter(uint32_t id); // canbus.c void canbus_notify_tx(void); -void canbus_notify_rx(void); -void canbus_process_data(uint32_t id, uint32_t len, uint8_t *data); -void canbus_set_uuid(void *data); +void canbus_process_data(struct canbus_msg *msg); #endif // canbus.h diff --git a/src/generic/canserial.c b/src/generic/canserial.c new file mode 100644 index 00000000..a4b393e7 --- /dev/null +++ b/src/generic/canserial.c @@ -0,0 +1,344 @@ +// Generic handling of serial over CAN support +// +// Copyright (C) 2019 Eug Krashtan +// Copyright (C) 2020 Pontus Borg +// Copyright (C) 2021 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include // memcpy +#include "board/io.h" // readb +#include "board/irq.h" // irq_save +#include "board/misc.h" // console_sendf +#include "canbus.h" // canbus_set_uuid +#include "canserial.h" // canserial_notify_tx +#include "command.h" // DECL_CONSTANT +#include "fasthash.h" // fasthash64 +#include "sched.h" // sched_wake_task + +#define CANBUS_UUID_LEN 6 + +// Global storage +static struct canbus_data { + uint32_t assigned_id; + uint8_t uuid[CANBUS_UUID_LEN]; + + // Tx data + struct task_wake tx_wake; + uint8_t transmit_pos, transmit_max; + + // Rx data + struct task_wake rx_wake; + uint8_t receive_pos; + uint32_t admin_pull_pos, admin_push_pos; + + // Transfer buffers + struct canbus_msg admin_queue[8]; + uint8_t transmit_buf[96]; + uint8_t receive_buf[192]; +} CanData; + + +/**************************************************************** + * Data transmission over CAN + ****************************************************************/ + +void +canserial_notify_tx(void) +{ + sched_wake_task(&CanData.tx_wake); +} + +void +canserial_tx_task(void) +{ + if (!sched_check_wake(&CanData.tx_wake)) + return; + uint32_t id = CanData.assigned_id; + if (!id) { + CanData.transmit_pos = CanData.transmit_max = 0; + return; + } + struct canbus_msg msg; + msg.id = id + 1; + uint32_t tpos = CanData.transmit_pos, tmax = CanData.transmit_max; + for (;;) { + int avail = tmax - tpos, now = avail > 8 ? 8 : avail; + if (avail <= 0) + break; + msg.dlc = now; + memcpy(msg.data, &CanData.transmit_buf[tpos], now); + int ret = canserial_send(&msg); + if (ret <= 0) + break; + tpos += now; + } + CanData.transmit_pos = tpos; +} +DECL_TASK(canserial_tx_task); + +// Encode and transmit a "response" message +uint_fast8_t +console_sendf(const struct command_encoder *ce, va_list args) +{ + // Verify space for message + uint32_t tpos = CanData.transmit_pos, tmax = CanData.transmit_max; + if (tpos >= tmax) + CanData.transmit_pos = CanData.transmit_max = tpos = tmax = 0; + uint32_t max_size = ce->max_size; + if (tmax + max_size > sizeof(CanData.transmit_buf)) { + if (tmax + max_size - tpos > sizeof(CanData.transmit_buf)) + // Not enough space for message + return max_size > sizeof(CanData.transmit_buf); + // Move buffer + tmax -= tpos; + memmove(&CanData.transmit_buf[0], &CanData.transmit_buf[tpos], tmax); + CanData.transmit_pos = tpos = 0; + CanData.transmit_max = tmax; + } + + // Generate message + uint32_t msglen = command_encode_and_frame(&CanData.transmit_buf[tmax] + , ce, args); + + // Start message transmit + CanData.transmit_max = tmax + msglen; + canserial_notify_tx(); + return 1; +} + + +/**************************************************************** + * CAN "admin" command handling + ****************************************************************/ + +// Available commands and responses +#define CANBUS_CMD_QUERY_UNASSIGNED 0x00 +#define CANBUS_CMD_SET_KLIPPER_NODEID 0x01 +#define CANBUS_CMD_REQUEST_BOOTLOADER 0x02 +#define CANBUS_RESP_NEED_NODEID 0x20 + +// Helper to verify a UUID in a command matches this chip's UUID +static int +can_check_uuid(struct canbus_msg *msg) +{ + return (msg->dlc >= 7 + && memcmp(&msg->data[1], CanData.uuid, sizeof(CanData.uuid)) == 0); +} + +// Helpers to encode/decode a CAN identifier to a 1-byte "nodeid" +static int +can_get_nodeid(void) +{ + if (!CanData.assigned_id) + return 0; + return (CanData.assigned_id - 0x100) >> 1; +} +static uint32_t +can_decode_nodeid(int nodeid) +{ + return (nodeid << 1) + 0x100; +} + +static void +can_process_query_unassigned(struct canbus_msg *msg) +{ + if (CanData.assigned_id) + return; + struct canbus_msg send; + send.id = CANBUS_ID_ADMIN_RESP; + send.dlc = 8; + send.data[0] = CANBUS_RESP_NEED_NODEID; + memcpy(&send.data[1], CanData.uuid, sizeof(CanData.uuid)); + send.data[7] = CANBUS_CMD_SET_KLIPPER_NODEID; + // Send with retry + for (;;) { + int ret = canserial_send(&send); + if (ret >= 0) + return; + } +} + +static void +can_id_conflict(void) +{ + CanData.assigned_id = 0; + canserial_set_filter(CanData.assigned_id); + shutdown("Another CAN node assigned this ID"); +} + +static void +can_process_set_klipper_nodeid(struct canbus_msg *msg) +{ + if (msg->dlc < 8) + return; + uint32_t newid = can_decode_nodeid(msg->data[7]); + if (can_check_uuid(msg)) { + if (newid != CanData.assigned_id) { + CanData.assigned_id = newid; + canserial_set_filter(CanData.assigned_id); + } + } else if (newid == CanData.assigned_id) { + can_id_conflict(); + } +} + +static void +can_process_request_bootloader(struct canbus_msg *msg) +{ + if (!can_check_uuid(msg)) + return; + bootloader_request(); +} + +// Handle an "admin" command +static void +can_process_admin(struct canbus_msg *msg) +{ + if (!msg->dlc) + return; + switch (msg->data[0]) { + case CANBUS_CMD_QUERY_UNASSIGNED: + can_process_query_unassigned(msg); + break; + case CANBUS_CMD_SET_KLIPPER_NODEID: + can_process_set_klipper_nodeid(msg); + break; + case CANBUS_CMD_REQUEST_BOOTLOADER: + can_process_request_bootloader(msg); + break; + } +} + + +/**************************************************************** + * CAN packet reading + ****************************************************************/ + +static void +canserial_notify_rx(void) +{ + sched_wake_task(&CanData.rx_wake); +} + +DECL_CONSTANT("RECEIVE_WINDOW", ARRAY_SIZE(CanData.receive_buf)); + +// Handle incoming data (called from IRQ handler) +int +canserial_process_data(struct canbus_msg *msg) +{ + uint32_t id = msg->id; + if (CanData.assigned_id && id == CanData.assigned_id) { + // Add to incoming data buffer + int rpos = CanData.receive_pos; + uint32_t len = CANMSG_DATA_LEN(msg); + if (len > sizeof(CanData.receive_buf) - rpos) + return -1; + memcpy(&CanData.receive_buf[rpos], msg->data, len); + CanData.receive_pos = rpos + len; + canserial_notify_rx(); + } else if (id == CANBUS_ID_ADMIN + || (CanData.assigned_id && id == CanData.assigned_id + 1)) { + // Add to admin command queue + uint32_t pushp = CanData.admin_push_pos; + if (pushp >= CanData.admin_pull_pos + ARRAY_SIZE(CanData.admin_queue)) + // No space - drop message + return -1; + uint32_t pos = pushp % ARRAY_SIZE(CanData.admin_queue); + memcpy(&CanData.admin_queue[pos], msg, sizeof(*msg)); + CanData.admin_push_pos = pushp + 1; + canserial_notify_rx(); + } + return 0; +} + +// Remove from the receive buffer the given number of bytes +static void +console_pop_input(int len) +{ + int copied = 0; + for (;;) { + int rpos = readb(&CanData.receive_pos); + int needcopy = rpos - len; + if (needcopy) { + memmove(&CanData.receive_buf[copied] + , &CanData.receive_buf[copied + len], needcopy - copied); + copied = needcopy; + canserial_notify_rx(); + } + irqstatus_t flag = irq_save(); + if (rpos != readb(&CanData.receive_pos)) { + // Raced with irq handler - retry + irq_restore(flag); + continue; + } + CanData.receive_pos = needcopy; + irq_restore(flag); + break; + } +} + +// Task to process incoming commands and admin messages +void +canserial_rx_task(void) +{ + if (!sched_check_wake(&CanData.rx_wake)) + return; + + // Process pending admin messages + for (;;) { + uint32_t pushp = readl(&CanData.admin_push_pos); + uint32_t pullp = CanData.admin_pull_pos; + if (pushp == pullp) + break; + uint32_t pos = pullp % ARRAY_SIZE(CanData.admin_queue); + struct canbus_msg *msg = &CanData.admin_queue[pos]; + uint32_t id = msg->id; + if (CanData.assigned_id && id == CanData.assigned_id + 1) + can_id_conflict(); + else if (id == CANBUS_ID_ADMIN) + can_process_admin(msg); + CanData.admin_pull_pos = pullp + 1; + } + + // Check for a complete message block and process it + uint_fast8_t rpos = readb(&CanData.receive_pos), pop_count; + int ret = command_find_block(CanData.receive_buf, rpos, &pop_count); + if (ret > 0) + command_dispatch(CanData.receive_buf, pop_count); + if (ret) { + console_pop_input(pop_count); + if (ret > 0) + command_send_ack(); + } +} +DECL_TASK(canserial_rx_task); + + +/**************************************************************** + * Setup and shutdown + ****************************************************************/ + +void +command_get_canbus_id(uint32_t *args) +{ + sendf("canbus_id canbus_uuid=%.*s canbus_nodeid=%u" + , sizeof(CanData.uuid), CanData.uuid, can_get_nodeid()); +} +DECL_COMMAND_FLAGS(command_get_canbus_id, HF_IN_SHUTDOWN, "get_canbus_id"); + +void +canserial_set_uuid(uint8_t *raw_uuid, uint32_t raw_uuid_len) +{ + uint64_t hash = fasthash64(raw_uuid, raw_uuid_len, 0xA16231A7); + memcpy(CanData.uuid, &hash, sizeof(CanData.uuid)); + canserial_notify_rx(); +} + +void +canserial_shutdown(void) +{ + canserial_notify_tx(); + canserial_notify_rx(); +} +DECL_SHUTDOWN(canserial_shutdown); diff --git a/src/generic/canserial.h b/src/generic/canserial.h new file mode 100644 index 00000000..e2f55521 --- /dev/null +++ b/src/generic/canserial.h @@ -0,0 +1,19 @@ +#ifndef __CANSERIAL_H__ +#define __CANSERIAL_H__ + +#include // uint32_t + +#define CANBUS_ID_ADMIN 0x3f0 +#define CANBUS_ID_ADMIN_RESP 0x3f1 + +// callbacks provided by board specific code +struct canbus_msg; +int canserial_send(struct canbus_msg *msg); +void canserial_set_filter(uint32_t id); + +// canserial.c +void canserial_notify_tx(void); +int canserial_process_data(struct canbus_msg *msg); +void canserial_set_uuid(uint8_t *raw_uuid, uint32_t raw_uuid_len); + +#endif // canserial.h diff --git a/src/generic/misc.h b/src/generic/misc.h index dc4e0a91..29c932bf 100644 --- a/src/generic/misc.h +++ b/src/generic/misc.h @@ -18,4 +18,6 @@ void *dynmem_end(void); uint16_t crc16_ccitt(uint8_t *buf, uint_fast8_t len); +void bootloader_request(void); + #endif // misc.h diff --git a/src/generic/usb_canbus.c b/src/generic/usb_canbus.c new file mode 100644 index 00000000..1a4ac869 --- /dev/null +++ b/src/generic/usb_canbus.c @@ -0,0 +1,678 @@ +// Support for Linux "gs_usb" CANbus adapter emulation +// +// Copyright (C) 2018-2022 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include // memmove +#include "autoconf.h" // CONFIG_USB_VENDOR_ID +#include "board/canbus.h" // canbus_notify_tx +#include "board/canserial.h" // canserial_notify_tx +#include "board/io.h" // readl +#include "board/misc.h" // console_sendf +#include "board/pgm.h" // PROGMEM +#include "board/usb_cdc_ep.h" // USB_CDC_EP_BULK_IN +#include "byteorder.h" // cpu_to_le16 +#include "command.h" // DECL_CONSTANT +#include "generic/usbstd.h" // struct usb_device_descriptor +#include "sched.h" // sched_wake_task +#include "usb_cdc.h" // usb_notify_ep0 + + +/**************************************************************** + * Linux "gs_usb" definitions + ****************************************************************/ + +#define USB_GSUSB_1_VENDOR_ID 0x1d50 +#define USB_GSUSB_1_PRODUCT_ID 0x606f + +enum gs_usb_breq { + GS_USB_BREQ_HOST_FORMAT = 0, + GS_USB_BREQ_BITTIMING, + GS_USB_BREQ_MODE, + GS_USB_BREQ_BERR, + GS_USB_BREQ_BT_CONST, + GS_USB_BREQ_DEVICE_CONFIG, + GS_USB_BREQ_TIMESTAMP, + GS_USB_BREQ_IDENTIFY, + GS_USB_BREQ_GET_USER_ID, + GS_USB_BREQ_SET_USER_ID, + GS_USB_BREQ_DATA_BITTIMING, + GS_USB_BREQ_BT_CONST_EXT, +}; + +struct gs_host_config { + uint32_t byte_order; +} __packed; + +struct gs_device_config { + uint8_t reserved1; + uint8_t reserved2; + uint8_t reserved3; + uint8_t icount; + uint32_t sw_version; + uint32_t hw_version; +} __packed; + +struct gs_device_bt_const { + uint32_t feature; + uint32_t fclk_can; + uint32_t tseg1_min; + uint32_t tseg1_max; + uint32_t tseg2_min; + uint32_t tseg2_max; + uint32_t sjw_max; + uint32_t brp_min; + uint32_t brp_max; + uint32_t brp_inc; +} __packed; + +struct gs_device_bittiming { + uint32_t prop_seg; + uint32_t phase_seg1; + uint32_t phase_seg2; + uint32_t sjw; + uint32_t brp; +} __packed; + +struct gs_device_mode { + uint32_t mode; + uint32_t flags; +} __packed; + +struct gs_host_frame { + uint32_t echo_id; + uint32_t can_id; + + uint8_t can_dlc; + uint8_t channel; + uint8_t flags; + uint8_t reserved; + + union { + uint8_t data[8]; + uint32_t data32[2]; + }; +} __packed; + + +/**************************************************************** + * Message sending + ****************************************************************/ + +// Global storage +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; + uint32_t assigned_id; + + // Data from physical canbus interface + uint32_t pull_pos, push_pos; + struct canbus_msg queue[8]; +} UsbCan; + +enum { + HS_TX_ECHO = 1, + HS_TX_HW = 2, + HS_TX_LOCAL = 4, +}; + +DECL_CONSTANT("CANBUS_BRIDGE", 1); + +void +canbus_notify_tx(void) +{ + sched_wake_task(&UsbCan.wake); +} + +// Handle incoming data from hw canbus interface (called from IRQ handler) +void +canbus_process_data(struct canbus_msg *msg) +{ + // Add to admin command queue + uint32_t pushp = UsbCan.push_pos; + if (pushp - UsbCan.pull_pos >= ARRAY_SIZE(UsbCan.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; + usb_notify_bulk_out(); +} + +// Send a message to the Linux host +static int +send_frame(struct canbus_msg *msg) +{ + struct gs_host_frame gs = {}; + gs.echo_id = 0xffffffff; + gs.can_id = msg->id; + gs.can_dlc = msg->dlc; + gs.data32[0] = msg->data32[0]; + gs.data32[1] = msg->data32[1]; + return usb_send_bulk_in(&gs, sizeof(gs)); +} + +// Send any pending hw frames to host +static int +drain_hw_queue(void) +{ + for (;;) { + uint32_t push_pos = readl(&UsbCan.push_pos); + uint32_t pull_pos = UsbCan.pull_pos; + if (push_pos != pull_pos) { + uint32_t pos = pull_pos % ARRAY_SIZE(UsbCan.queue); + int ret = send_frame(&UsbCan.queue[pos]); + if (ret < 0) + return -1; + UsbCan.pull_pos = pull_pos + 1; + continue; + } + return 0; + } +} + +void +usbcan_task(void) +{ + if (!sched_check_wake(&UsbCan.wake)) + return; + for (;;) { + // Send any pending hw frames to host + int ret = drain_hw_queue(); + if (ret < 0) + return; + + // 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; + msg.data32[0] = gs->data32[0]; + msg.data32[1] = gs->data32[1]; + if (host_status & HS_TX_HW) { + ret = canbus_send(&msg); + if (ret < 0) + return; + UsbCan.host_status = host_status = host_status & ~HS_TX_HW; + } + if (host_status & HS_TX_LOCAL) { + ret = canserial_process_data(&msg); + if (ret < 0) { + usb_notify_bulk_out(); + return; + } + UsbCan.host_status = host_status & ~HS_TX_LOCAL; + } + continue; + } + + // Send any previous echo frames + if (host_status) { + ret = usb_send_bulk_in(&UsbCan.host_frame + , sizeof(UsbCan.host_frame)); + if (ret < 0) + return; + UsbCan.host_status = 0; + continue; + } + + // See if can read a new frame from host + ret = usb_read_bulk_out(&UsbCan.host_frame, USB_CDC_EP_BULK_OUT_SIZE); + if (ret > 0) { + uint32_t id = UsbCan.host_frame.can_id; + UsbCan.host_status = HS_TX_ECHO | HS_TX_HW; + if (id == CANBUS_ID_ADMIN) + UsbCan.host_status = HS_TX_ECHO | HS_TX_HW | HS_TX_LOCAL; + else if (UsbCan.assigned_id && UsbCan.assigned_id == id) + UsbCan.host_status = HS_TX_ECHO | HS_TX_LOCAL; + continue; + } + + // No more work to be done + if (UsbCan.notify_local) { + UsbCan.notify_local = 0; + canserial_notify_tx(); + } + return; + } +} +DECL_TASK(usbcan_task); + +int +canserial_send(struct canbus_msg *msg) +{ + int ret = drain_hw_queue(); + if (ret < 0) + goto retry_later; + ret = send_frame(msg); + if (ret < 0) + goto retry_later; + UsbCan.notify_local = 0; + return msg->dlc; +retry_later: + UsbCan.notify_local = 1; + return -1; +} + +void +canserial_set_filter(uint32_t id) +{ + UsbCan.assigned_id = id; +} + +void +usb_notify_bulk_out(void) +{ + canbus_notify_tx(); +} + +void +usb_notify_bulk_in(void) +{ + canbus_notify_tx(); +} + + +/**************************************************************** + * USB descriptors + ****************************************************************/ + +#define CONCAT1(a, b) a ## b +#define CONCAT(a, b) CONCAT1(a, b) +#define USB_STR_MANUFACTURER u"Klipper" +#define USB_STR_PRODUCT CONCAT(u,CONFIG_MCU) +#define USB_STR_SERIAL CONCAT(u,CONFIG_USB_SERIAL_NUMBER) + +// String descriptors +enum { + USB_STR_ID_MANUFACTURER = 1, USB_STR_ID_PRODUCT, USB_STR_ID_SERIAL, +}; + +#define SIZE_cdc_string_langids (sizeof(cdc_string_langids) + 2) + +static const struct usb_string_descriptor cdc_string_langids PROGMEM = { + .bLength = SIZE_cdc_string_langids, + .bDescriptorType = USB_DT_STRING, + .data = { cpu_to_le16(USB_LANGID_ENGLISH_US) }, +}; + +#define SIZE_cdc_string_manufacturer \ + (sizeof(cdc_string_manufacturer) + sizeof(USB_STR_MANUFACTURER) - 2) + +static const struct usb_string_descriptor cdc_string_manufacturer PROGMEM = { + .bLength = SIZE_cdc_string_manufacturer, + .bDescriptorType = USB_DT_STRING, + .data = USB_STR_MANUFACTURER, +}; + +#define SIZE_cdc_string_product \ + (sizeof(cdc_string_product) + sizeof(USB_STR_PRODUCT) - 2) + +static const struct usb_string_descriptor cdc_string_product PROGMEM = { + .bLength = SIZE_cdc_string_product, + .bDescriptorType = USB_DT_STRING, + .data = USB_STR_PRODUCT, +}; + +#define SIZE_cdc_string_serial \ + (sizeof(cdc_string_serial) + sizeof(USB_STR_SERIAL) - 2) + +static const struct usb_string_descriptor cdc_string_serial PROGMEM = { + .bLength = SIZE_cdc_string_serial, + .bDescriptorType = USB_DT_STRING, + .data = USB_STR_SERIAL, +}; + +// Device descriptor +static const struct usb_device_descriptor gs_device_descriptor PROGMEM = { + .bLength = sizeof(gs_device_descriptor), + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = cpu_to_le16(0x0200), + .bMaxPacketSize0 = USB_CDC_EP0_SIZE, + .idVendor = cpu_to_le16(USB_GSUSB_1_VENDOR_ID), + .idProduct = cpu_to_le16(USB_GSUSB_1_PRODUCT_ID), + .iManufacturer = USB_STR_ID_MANUFACTURER, + .iProduct = USB_STR_ID_PRODUCT, + .iSerialNumber = USB_STR_ID_SERIAL, + .bNumConfigurations = 1, +}; + +// Config descriptor +static const struct config_s { + struct usb_config_descriptor config; + struct usb_interface_descriptor iface0; + struct usb_endpoint_descriptor ep1; + struct usb_endpoint_descriptor ep2; +} PACKED gs_config_descriptor PROGMEM = { + .config = { + .bLength = sizeof(gs_config_descriptor.config), + .bDescriptorType = USB_DT_CONFIG, + .wTotalLength = cpu_to_le16(sizeof(gs_config_descriptor)), + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .bmAttributes = 0xC0, + .bMaxPower = 50, + }, + .iface0 = { + .bLength = sizeof(gs_config_descriptor.iface0), + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 2, + .bInterfaceClass = 255, + .bInterfaceSubClass = 255, + .bInterfaceProtocol = 255, + }, + .ep1 = { + .bLength = sizeof(gs_config_descriptor.ep1), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_CDC_EP_BULK_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(USB_CDC_EP_BULK_OUT_SIZE), + }, + .ep2 = { + .bLength = sizeof(gs_config_descriptor.ep2), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_CDC_EP_BULK_IN | USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(USB_CDC_EP_BULK_IN_SIZE), + }, +}; + +// List of available descriptors +static const struct descriptor_s { + uint_fast16_t wValue; + uint_fast16_t wIndex; + const void *desc; + uint_fast8_t size; +} usb_descriptors[] PROGMEM = { + { USB_DT_DEVICE<<8, 0x0000, + &gs_device_descriptor, sizeof(gs_device_descriptor) }, + { USB_DT_CONFIG<<8, 0x0000, + &gs_config_descriptor, sizeof(gs_config_descriptor) }, + { USB_DT_STRING<<8, 0x0000, + &cdc_string_langids, SIZE_cdc_string_langids }, + { (USB_DT_STRING<<8) | USB_STR_ID_MANUFACTURER, USB_LANGID_ENGLISH_US, + &cdc_string_manufacturer, SIZE_cdc_string_manufacturer }, + { (USB_DT_STRING<<8) | USB_STR_ID_PRODUCT, USB_LANGID_ENGLISH_US, + &cdc_string_product, SIZE_cdc_string_product }, +#if !CONFIG_USB_SERIAL_NUMBER_CHIPID + { (USB_DT_STRING<<8) | USB_STR_ID_SERIAL, USB_LANGID_ENGLISH_US, + &cdc_string_serial, SIZE_cdc_string_serial }, +#endif +}; + +// Fill in a USB serial string descriptor from a chip id +void +usb_fill_serial(struct usb_string_descriptor *desc, int strlen, void *id) +{ + desc->bLength = sizeof(*desc) + strlen * sizeof(desc->data[0]); + desc->bDescriptorType = USB_DT_STRING; + + uint8_t *src = id; + int i; + for (i = 0; i < strlen; i++) { + uint8_t c = i & 1 ? src[i/2] & 0x0f : src[i/2] >> 4; + desc->data[i] = c < 10 ? c + '0' : c - 10 + 'A'; + } +} + + +/**************************************************************** + * USB endpoint 0 control message handling + ****************************************************************/ + +// State tracking +enum { + UX_READ = 1<<0, UX_SEND = 1<<1, UX_SEND_PROGMEM = 1<<2, UX_SEND_ZLP = 1<<3 +}; + +static void *usb_xfer_data; +static uint8_t usb_xfer_size, usb_xfer_flags; + +// Set the USB "stall" condition +static void +usb_do_stall(void) +{ + usb_stall_ep0(); + usb_xfer_flags = 0; +} + +// Transfer data on the usb endpoint 0 +static void +usb_do_xfer(void *data, uint_fast8_t size, uint_fast8_t flags) +{ + for (;;) { + uint_fast8_t xs = size; + if (xs > USB_CDC_EP0_SIZE) + xs = USB_CDC_EP0_SIZE; + int_fast8_t ret; + if (flags & UX_READ) + ret = usb_read_ep0(data, xs); + else if (NEED_PROGMEM && flags & UX_SEND_PROGMEM) + ret = usb_send_ep0_progmem(data, xs); + else + ret = usb_send_ep0(data, xs); + if (ret == xs) { + // Success + data += xs; + size -= xs; + if (!size) { + // Entire transfer completed successfully + if (flags & UX_READ) { + // Send status packet at end of read + flags = UX_SEND; + continue; + } + if (xs == USB_CDC_EP0_SIZE && flags & UX_SEND_ZLP) + // Must send zero-length-packet + continue; + usb_xfer_flags = 0; + usb_notify_ep0(); + return; + } + continue; + } + if (ret == -1) { + // Interface busy - retry later + usb_xfer_data = data; + usb_xfer_size = size; + usb_xfer_flags = flags; + return; + } + // Error + usb_do_stall(); + return; + } +} + +static void +usb_req_get_descriptor(struct usb_ctrlrequest *req) +{ + if (req->bRequestType != USB_DIR_IN) + goto fail; + void *desc = NULL; + uint_fast8_t flags, size, i; + for (i=0; iwValue) == req->wValue + && READP(d->wIndex) == req->wIndex) { + flags = NEED_PROGMEM ? UX_SEND_PROGMEM : UX_SEND; + size = READP(d->size); + desc = (void*)READP(d->desc); + } + } + if (CONFIG_USB_SERIAL_NUMBER_CHIPID + && req->wValue == ((USB_DT_STRING<<8) | USB_STR_ID_SERIAL) + && req->wIndex == USB_LANGID_ENGLISH_US) { + struct usb_string_descriptor *usbserial_serialid; + usbserial_serialid = usbserial_get_serialid(); + flags = UX_SEND; + size = usbserial_serialid->bLength; + desc = (void*)usbserial_serialid; + } + if (desc) { + if (size > req->wLength) + size = req->wLength; + else if (size < req->wLength) + flags |= UX_SEND_ZLP; + usb_do_xfer(desc, size, flags); + return; + } +fail: + usb_do_stall(); +} + +static void +usb_req_set_address(struct usb_ctrlrequest *req) +{ + if (req->bRequestType || req->wIndex || req->wLength) { + usb_do_stall(); + return; + } + usb_set_address(req->wValue); +} + +static void +usb_req_set_configuration(struct usb_ctrlrequest *req) +{ + if (req->bRequestType || req->wValue != 1 || req->wIndex || req->wLength) { + usb_do_stall(); + return; + } + usb_set_configure(); + usb_notify_bulk_in(); + usb_notify_bulk_out(); + usb_do_xfer(NULL, 0, UX_SEND); +} + +struct gs_host_config host_config; + +static void +gs_breq_host_format(struct usb_ctrlrequest *req) +{ + // Like candlightfw, little-endian is always used. Read and ignore value. + usb_do_xfer(&host_config, sizeof(host_config), UX_READ); +} + +static const struct gs_device_config device_config PROGMEM = { + .sw_version = 2, + .hw_version = 1, +}; + +static void +gs_breq_device_config(struct usb_ctrlrequest *req) +{ + usb_do_xfer((void*)&device_config, sizeof(device_config), UX_SEND); +} + +static const struct gs_device_bt_const bt_const PROGMEM = { + // These are just dummy values for now + .feature = 0, + .fclk_can = 48000000, + .tseg1_min = 1, + .tseg1_max = 16, + .tseg2_min = 1, + .tseg2_max = 8, + .sjw_max = 4, + .brp_min = 1, + .brp_max = 1024, + .brp_inc = 1, +}; + +static void +gs_breq_bt_const(struct usb_ctrlrequest *req) +{ + usb_do_xfer((void*)&bt_const, sizeof(bt_const), UX_SEND); +} + +struct gs_device_bittiming device_bittiming; + +static void +gs_breq_bittiming(struct usb_ctrlrequest *req) +{ + // Bit timing is ignored for now + usb_do_xfer(&device_bittiming, sizeof(device_bittiming), UX_READ); +} + +struct gs_device_mode device_mode; + +static void +gs_breq_mode(struct usb_ctrlrequest *req) +{ + // Mode is ignored for now + usb_do_xfer(&device_mode, sizeof(device_mode), UX_READ); +} + +static void +usb_state_ready(void) +{ + struct usb_ctrlrequest req; + int_fast8_t ret = usb_read_ep0_setup(&req, sizeof(req)); + if (ret != sizeof(req)) + return; + uint32_t req_type = req.bRequestType & USB_TYPE_MASK; + if (req_type == USB_TYPE_STANDARD) { + switch (req.bRequest) { + case USB_REQ_GET_DESCRIPTOR: usb_req_get_descriptor(&req); break; + case USB_REQ_SET_ADDRESS: usb_req_set_address(&req); break; + case USB_REQ_SET_CONFIGURATION: usb_req_set_configuration(&req); break; + default: usb_do_stall(); break; + } + } else if (req_type == USB_TYPE_VENDOR) { + switch (req.bRequest) { + case GS_USB_BREQ_HOST_FORMAT: gs_breq_host_format(&req); break; + case GS_USB_BREQ_DEVICE_CONFIG: gs_breq_device_config(&req); break; + case GS_USB_BREQ_BT_CONST: gs_breq_bt_const(&req); break; + case GS_USB_BREQ_BITTIMING: gs_breq_bittiming(&req); break; + case GS_USB_BREQ_MODE: gs_breq_mode(&req); break; + default: usb_do_stall(); break; + } + } else { + usb_do_stall(); + } +} + +// State tracking dispatch +static struct task_wake usb_ep0_wake; + +void +usb_notify_ep0(void) +{ + sched_wake_task(&usb_ep0_wake); +} + +void +usb_ep0_task(void) +{ + if (!sched_check_wake(&usb_ep0_wake)) + return; + if (usb_xfer_flags) + usb_do_xfer(usb_xfer_data, usb_xfer_size, usb_xfer_flags); + else + usb_state_ready(); +} +DECL_TASK(usb_ep0_task); + +void +usb_shutdown(void) +{ + usb_notify_bulk_in(); + usb_notify_bulk_out(); + usb_notify_ep0(); +} +DECL_SHUTDOWN(usb_shutdown); diff --git a/src/generic/usb_cdc.c b/src/generic/usb_cdc.c index 5c27d29b..8180a286 100644 --- a/src/generic/usb_cdc.c +++ b/src/generic/usb_cdc.c @@ -449,7 +449,7 @@ check_reboot(void) { if (line_coding.dwDTERate == 1200 && !(line_control_state & 0x01)) // A baud of 1200 is an Arduino style request to enter the bootloader - usb_request_bootloader(); + bootloader_request(); } static void diff --git a/src/generic/usb_cdc.h b/src/generic/usb_cdc.h index f955c840..3c92ef13 100644 --- a/src/generic/usb_cdc.h +++ b/src/generic/usb_cdc.h @@ -21,7 +21,6 @@ int_fast8_t usb_send_ep0_progmem(const void *data, uint_fast8_t len); void usb_stall_ep0(void); void usb_set_address(uint_fast8_t addr); void usb_set_configure(void); -void usb_request_bootloader(void); struct usb_string_descriptor *usbserial_get_serialid(void); // usb_cdc.c diff --git a/src/generic/usb_cdc_ep.h b/src/generic/usb_cdc_ep.h index 1ca97a79..f7521580 100644 --- a/src/generic/usb_cdc_ep.h +++ b/src/generic/usb_cdc_ep.h @@ -3,9 +3,9 @@ // Default USB endpoint ids enum { - USB_CDC_EP_ACM = 1, + USB_CDC_EP_BULK_IN = 1, USB_CDC_EP_BULK_OUT = 2, - USB_CDC_EP_BULK_IN = 3, + USB_CDC_EP_ACM = 3, }; #endif // usb_cdc_ep.h diff --git a/src/generic/usbstd.h b/src/generic/usbstd.h index 07d0c1ca..2cec37df 100644 --- a/src/generic/usbstd.h +++ b/src/generic/usbstd.h @@ -8,6 +8,12 @@ #define USB_DIR_OUT 0 /* to device */ #define USB_DIR_IN 0x80 /* to host */ +#define USB_TYPE_MASK (0x03 << 5) +#define USB_TYPE_STANDARD (0x00 << 5) +#define USB_TYPE_CLASS (0x01 << 5) +#define USB_TYPE_VENDOR (0x02 << 5) +#define USB_TYPE_RESERVED (0x03 << 5) + #define USB_REQ_GET_STATUS 0x00 #define USB_REQ_CLEAR_FEATURE 0x01 #define USB_REQ_SET_FEATURE 0x03 diff --git a/src/i2ccmds.c b/src/i2ccmds.c index cde0f6fa..69af011b 100644 --- a/src/i2ccmds.c +++ b/src/i2ccmds.c @@ -9,10 +9,7 @@ #include "command.h" //sendf #include "sched.h" //DECL_COMMAND #include "board/gpio.h" //i2c_write/read/setup - -struct i2cdev_s { - struct i2c_config i2c_config; -}; +#include "i2ccmds.h" void command_config_i2c(uint32_t *args) @@ -25,6 +22,12 @@ command_config_i2c(uint32_t *args) DECL_COMMAND(command_config_i2c, "config_i2c oid=%c i2c_bus=%u rate=%u address=%u"); +struct i2cdev_s * +i2cdev_oid_lookup(uint8_t oid) +{ + return oid_lookup(oid, command_config_i2c); +} + void command_i2c_write(uint32_t *args) { diff --git a/src/i2ccmds.h b/src/i2ccmds.h new file mode 100644 index 00000000..49c05c93 --- /dev/null +++ b/src/i2ccmds.h @@ -0,0 +1,13 @@ +#ifndef __I2CCMDS_H +#define __I2CCMDS_H + +#include +#include "board/gpio.h" // i2c_config + +struct i2cdev_s { + struct i2c_config i2c_config; +}; + +struct i2cdev_s *i2cdev_oid_lookup(uint8_t oid); + +#endif diff --git a/src/linux/sensor_ds18b20.c b/src/linux/sensor_ds18b20.c index 4525a014..2e9151ae 100644 --- a/src/linux/sensor_ds18b20.c +++ b/src/linux/sensor_ds18b20.c @@ -13,7 +13,6 @@ #include // clock_gettime #include "basecmd.h" // oid_alloc #include "board/irq.h" // irq_disable -#include "board/misc.h" // output #include "command.h" // DECL_COMMAND #include "internal.h" // report_errno #include "sched.h" // DECL_SHUTDOWN @@ -51,17 +50,23 @@ struct ds18_s { int temperature; struct timespec request_time; uint8_t status; - const char* error; + uint8_t error_count; + uint8_t max_error_count; }; -// Lock ds18_s mutex, set error status and message, unlock mutex. +// Lock ds18_s mutex, set error status, unlock mutex. static void -locking_set_read_error(struct ds18_s *d, const char *error) +locking_handle_read_error(struct ds18_s *d) { pthread_mutex_lock(&d->lock); - d->error = error; d->status = W1_ERROR; - pthread_mutex_unlock(&d->lock); + d->error_count++; + if (d->error_count <= d->max_error_count) { + pthread_mutex_unlock(&d->lock); + } else { + pthread_mutex_unlock(&d->lock); + pthread_exit(NULL); + } } // The kernel interface to DS18B20 sensors is a sysfs entry that blocks for @@ -89,15 +94,14 @@ reader_start_routine(void *param) { int ret = read(d->fd, data, sizeof(data)-1); if (ret < 0) { report_errno("read DS18B20", ret); - locking_set_read_error(d, "Unable to read DS18B20"); - pthread_exit(NULL); + locking_handle_read_error(d); + continue; } data[ret] = '\0'; char *temp_string = strstr(data, "t="); if (temp_string == NULL || temp_string[2] == '\0') { - locking_set_read_error(d, - "Unable to find temperature value in DS18B20 report"); - pthread_exit(NULL); + locking_handle_read_error(d); + continue; } // Don't pass 't' and '=' to atoi temp_string += 2; @@ -113,8 +117,8 @@ reader_start_routine(void *param) { ret = lseek(d->fd, 0, SEEK_SET); if (ret < 0) { report_errno("seek DS18B20", ret); - locking_set_read_error(d, "Unable to seek DS18B20"); - pthread_exit(NULL); + locking_handle_read_error(d); + continue; } } pthread_exit(NULL); @@ -151,6 +155,8 @@ command_config_ds18b20(uint32_t *args) } struct ds18_s *d = oid_alloc(args[0], command_config_ds18b20, sizeof(*d)); + d->max_error_count = args[3]; + d->error_count = 0; d->timer.func = ds18_event; d->fd = fd; d->status = W1_IDLE; @@ -181,7 +187,8 @@ fail4: fail5: shutdown("Could not start DS18B20 reader thread"); } -DECL_COMMAND(command_config_ds18b20, "config_ds18b20 oid=%c serial=%*s"); +DECL_COMMAND(command_config_ds18b20, + "config_ds18b20 oid=%c serial=%*s max_error_count=%c"); void command_query_ds18b20(uint32_t *args) @@ -215,12 +222,15 @@ ds18_send_and_request(struct ds18_s *d, uint32_t next_begin_time, uint8_t oid) pthread_mutex_lock(&d->lock); if (d->status == W1_ERROR) { - // try_shutdown expects a static string. Output the specific error, - // then shut down with a generic error. - output("Error: %s", d->error); - pthread_mutex_unlock(&d->lock); - try_shutdown("Error reading DS18B20 sensor"); - return; + if (d->error_count > d->max_error_count) { + try_shutdown("Error reading DS18B20 sensor"); + pthread_mutex_unlock(&d->lock); + return; + } else { + sendf("ds18b20_result oid=%c next_clock=%u value=%i fault=%u" + , oid, next_begin_time, d->temperature, d->error_count); + d->status = W1_READ_REQUESTED; + } } else if (d->status == W1_IDLE) { // This happens the first time requesting a temperature. // Nothing to report yet. @@ -228,8 +238,8 @@ ds18_send_and_request(struct ds18_s *d, uint32_t next_begin_time, uint8_t oid) d->status = W1_READ_REQUESTED; } else if (d->status == W1_READY) { // Report the previous temperature and request a new one. - sendf("ds18b20_result oid=%c next_clock=%u value=%i" - , oid, next_begin_time, d->temperature); + sendf("ds18b20_result oid=%c next_clock=%u value=%i fault=%u" + , oid, next_begin_time, d->temperature, 0); if (d->temperature < d->min_value || d->temperature > d->max_value) { pthread_mutex_unlock(&d->lock); try_shutdown("DS18B20 out of range"); @@ -237,6 +247,7 @@ ds18_send_and_request(struct ds18_s *d, uint32_t next_begin_time, uint8_t oid) } d->request_time = request_time; d->status = W1_READ_REQUESTED; + d->error_count = 0; //successful reading, reset error count } else if (d->status == W1_READ_REQUESTED) { // Reader thread is already reading (or will be soon). // This can happen if two queries come in quick enough diff --git a/src/lpc176x/Kconfig b/src/lpc176x/Kconfig index 1cdfc007..f41c3bd6 100644 --- a/src/lpc176x/Kconfig +++ b/src/lpc176x/Kconfig @@ -63,10 +63,6 @@ config FLASH_START default 0x4000 if SMOOTHIEWARE_BOOTLOADER default 0x0000 -config USBSERIAL - bool -config SERIAL - bool choice prompt "Communication interface" config LPC_USB diff --git a/src/lpc176x/usbserial.c b/src/lpc176x/usbserial.c index 95ce6e74..af7d66b5 100644 --- a/src/lpc176x/usbserial.c +++ b/src/lpc176x/usbserial.c @@ -8,6 +8,7 @@ #include "autoconf.h" // CONFIG_SMOOTHIEWARE_BOOTLOADER #include "board/armcm_boot.h" // armcm_enable_irq #include "board/armcm_timer.h" // udelay +#include "board/armcm_reset.h" // try_request_canboot #include "board/irq.h" // irq_disable #include "board/misc.h" // timer_read_time #include "byteorder.h" // cpu_to_le32 @@ -246,10 +247,11 @@ usb_set_configure(void) } void -usb_request_bootloader(void) +bootloader_request(void) { if (!CONFIG_SMOOTHIEWARE_BOOTLOADER) return; + try_request_canboot(); // Disable USB and pause for 5ms so host recognizes a disconnect irq_disable(); sie_cmd_write(SIE_CMD_SET_DEVICE_STATUS, 0); diff --git a/src/rp2040/Kconfig b/src/rp2040/Kconfig index f9b68bb2..cbd89922 100644 --- a/src/rp2040/Kconfig +++ b/src/rp2040/Kconfig @@ -48,14 +48,34 @@ config FLASH_START hex default 0x10000000 + +###################################################################### +# Bootloader options +###################################################################### + +choice + prompt "Flash chip" if LOW_LEVEL_OPTIONS + config RP2040_FLASH_W25Q080 + bool "W25Q080 with CLKDIV 2" + config RP2040_FLASH_GENERIC_03 + bool "GENERIC_03H with CLKDIV 4" +endchoice + +config RP2040_STAGE2_FILE + string + default "boot2_generic_03h.S" if RP2040_FLASH_GENERIC_03 + default "boot2_w25q080.S" + +config RP2040_STAGE2_CLKDIV + int + default 4 if RP2040_FLASH_GENERIC_03 + default 2 + + ###################################################################### # Communication inteface ###################################################################### -config USBSERIAL - bool -config SERIAL - bool choice prompt "Communication interface" config RP2040_USB @@ -64,6 +84,22 @@ choice config RP2040_SERIAL_UART0 bool "Serial (on UART0 GPIO1/GPIO0)" select SERIAL + config RP2040_CANBUS + bool "CAN bus" + select CANSERIAL + config RP2040_USBCANBUS + bool "USB to CAN bus bridge" + select USBCANBUS endchoice +config RP2040_CANBUS_GPIO_RX + int "CAN RX gpio number" if CANBUS + default 4 + range 0 29 + +config RP2040_CANBUS_GPIO_TX + int "CAN TX gpio number" if CANBUS + default 5 + range 0 29 + endif diff --git a/src/rp2040/Makefile b/src/rp2040/Makefile index 227b041e..e058dccb 100644 --- a/src/rp2040/Makefile +++ b/src/rp2040/Makefile @@ -3,10 +3,10 @@ # Setup the toolchain CROSS_PREFIX=arm-none-eabi- -dirs-y += src/rp2040 src/generic lib/rp2040/elf2uf2 +dirs-y += src/rp2040 src/generic lib/rp2040/elf2uf2 lib/fast-hash lib/can2040 CFLAGS += -mcpu=cortex-m0plus -mthumb -Ilib/cmsis-core -CFLAGS += -Ilib/rp2040 -Ilib/rp2040/cmsis_include +CFLAGS += -Ilib/rp2040 -Ilib/rp2040/cmsis_include -Ilib/fast-hash -Ilib/can2040 CFLAGS_klipper.elf += --specs=nano.specs --specs=nosys.specs CFLAGS_klipper.elf += -T $(OUT)src/rp2040/rp2040_link.ld @@ -20,14 +20,21 @@ src-y += rp2040/serial.c generic/serial_irq.c src-$(CONFIG_USBSERIAL) += rp2040/usbserial.c generic/usb_cdc.c src-$(CONFIG_USBSERIAL) += rp2040/chipid.c src-$(CONFIG_SERIAL) += rp2040/serial_host.c +src-$(CONFIG_CANSERIAL) += rp2040/can.c rp2040/chipid.c ../lib/can2040/can2040.c +src-$(CONFIG_CANSERIAL) += generic/canserial.c generic/canbus.c +src-$(CONFIG_CANSERIAL) += ../lib/fast-hash/fasthash.c +src-$(CONFIG_USBCANBUS) += rp2040/can.c rp2040/chipid.c ../lib/can2040/can2040.c +src-$(CONFIG_USBCANBUS) += generic/canserial.c generic/usb_canbus.c +src-$(CONFIG_USBCANBUS) += ../lib/fast-hash/fasthash.c rp2040/usbserial.c src-$(CONFIG_HAVE_GPIO_HARD_PWM) += rp2040/hard_pwm.c src-$(CONFIG_HAVE_GPIO_SPI) += rp2040/spi.c src-$(CONFIG_HAVE_GPIO_I2C) += rp2040/i2c.c # rp2040 stage2 building -$(OUT)stage2.o: lib/rp2040/boot_stage2/boot2_w25q080.S +STAGE2_FILE := $(shell echo $(CONFIG_RP2040_STAGE2_FILE)) +$(OUT)stage2.o: lib/rp2040/boot_stage2/$(STAGE2_FILE) $(OUT)autoconf.h @echo " Building rp2040 stage2 $@" - $(Q)$(CC) $(CFLAGS) -Ilib/rp2040/boot_stage2 -Ilib/rp2040/boot_stage2/asminclude -DPICO_FLASH_SPI_CLKDIV=2 -c $< -o $(OUT)stage2raw1.o + $(Q)$(CC) $(CFLAGS) -Ilib/rp2040/boot_stage2 -Ilib/rp2040/boot_stage2/asminclude -DPICO_FLASH_SPI_CLKDIV=$(CONFIG_RP2040_STAGE2_CLKDIV) -c $< -o $(OUT)stage2raw1.o $(Q)$(LD) $(OUT)stage2raw1.o --script=lib/rp2040/boot_stage2/boot_stage2.ld -o $(OUT)stage2raw.o $(Q)$(OBJCOPY) -O binary $(OUT)stage2raw.o $(OUT)stage2raw.bin $(Q)lib/rp2040/boot_stage2/pad_checksum -s 0xffffffff $(OUT)stage2raw.bin $(OUT)stage2.S diff --git a/src/rp2040/can.c b/src/rp2040/can.c new file mode 100644 index 00000000..9c42cf36 --- /dev/null +++ b/src/rp2040/can.c @@ -0,0 +1,78 @@ +// Serial over CAN emulation for rp2040 using can2040 software canbus +// +// Copyright (C) 2022 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include // uint32_t +#include // memcpy +#include "autoconf.h" // CONFIG_CANBUS_FREQUENCY +#include "board/armcm_boot.h" // armcm_enable_irq +#include "board/io.h" // readl +#include "can2040.h" // can2040_setup +#include "command.h" // DECL_CONSTANT_STR +#include "fasthash.h" // fasthash64 +#include "generic/canbus.h" // canbus_notify_tx +#include "generic/canserial.h" // CANBUS_ID_ADMIN +#include "hardware/structs/resets.h" // RESETS_RESET_PIO0_BITS +#include "internal.h" // DMA_IRQ_0_IRQn +#include "sched.h" // DECL_INIT + +#define GPIO_STR_CAN_RX "gpio" __stringify(CONFIG_RP2040_CANBUS_GPIO_RX) +#define GPIO_STR_CAN_TX "gpio" __stringify(CONFIG_RP2040_CANBUS_GPIO_TX) +DECL_CONSTANT_STR("RESERVE_PINS_CAN", GPIO_STR_CAN_RX "," GPIO_STR_CAN_TX); + +static struct can2040 cbus; + +// Transmit a packet +int +canbus_send(struct canbus_msg *msg) +{ + int ret = can2040_transmit(&cbus, (void*)msg); + if (ret < 0) + return -1; + return CANMSG_DATA_LEN(msg); +} + +// Setup the receive packet filter +void +canbus_set_filter(uint32_t id) +{ + // Filter not implemented (and not necessary) +} + +// can2040 callback function - handle rx and tx notifications +static void +can2040_cb(struct can2040 *cd, uint32_t notify, struct can2040_msg *msg) +{ + if (notify & CAN2040_NOTIFY_TX) { + canbus_notify_tx(); + return; + } + if (notify & CAN2040_NOTIFY_RX) + canbus_process_data((void*)msg); +} + +// Main PIO irq handler +void +PIOx_IRQHandler(void) +{ + can2040_pio_irq_handler(&cbus); +} + +void +can_init(void) +{ + // Setup canbus + can2040_setup(&cbus, 0); + can2040_callback_config(&cbus, can2040_cb); + + // Enable irqs + armcm_enable_irq(PIOx_IRQHandler, PIO0_IRQ_0_IRQn, 1); + + // Start canbus + uint32_t pclk = get_pclock_frequency(RESETS_RESET_PIO0_RESET); + can2040_start(&cbus, pclk, CONFIG_CANBUS_FREQUENCY + , CONFIG_RP2040_CANBUS_GPIO_RX, CONFIG_RP2040_CANBUS_GPIO_TX); +} +DECL_INIT(can_init); diff --git a/src/rp2040/chipid.c b/src/rp2040/chipid.c index 95e760d1..80182f60 100644 --- a/src/rp2040/chipid.c +++ b/src/rp2040/chipid.c @@ -4,11 +4,10 @@ // // This file may be distributed under the terms of the GNU GPLv3 license. -#define CHIP_UID_LEN 8 - #include // memcpy #include "autoconf.h" // CONFIG_USB_SERIAL_NUMBER_CHIPID #include "board/irq.h" // irq_disable, irq_enable +#include "board/canserial.h" // canserial_set_uuid #include "generic/usb_cdc.h" // usb_fill_serial #include "generic/usbstd.h" // usb_string_descriptor #include "sched.h" // DECL_INIT @@ -16,6 +15,8 @@ #include "hardware/structs/ssi.h" // ssi_hw #include "internal.h" +#define CHIP_UID_LEN 8 + static struct { struct usb_string_descriptor desc; uint16_t data[CHIP_UID_LEN * 2]; @@ -120,13 +121,15 @@ read_unique_id(uint8_t *out) void chipid_init(void) { - if (!CONFIG_USB_SERIAL_NUMBER_CHIPID) + if (!(CONFIG_USB_SERIAL_NUMBER_CHIPID || CONFIG_CANBUS)) return; uint8_t data[8] = {0}; read_unique_id(data); - usb_fill_serial(&cdc_chipid.desc, ARRAY_SIZE(cdc_chipid.data) - , data); + if (CONFIG_USB_SERIAL_NUMBER_CHIPID) + usb_fill_serial(&cdc_chipid.desc, ARRAY_SIZE(cdc_chipid.data), data); + if (CONFIG_CANBUS) + canserial_set_uuid(data, CHIP_UID_LEN); } DECL_INIT(chipid_init); diff --git a/src/rp2040/i2c.c b/src/rp2040/i2c.c index 954de0e5..8a32417b 100644 --- a/src/rp2040/i2c.c +++ b/src/rp2040/i2c.c @@ -75,8 +75,8 @@ i2c_setup(uint32_t bus, uint32_t rate, uint8_t addr) const struct i2c_info *info = &i2c_bus[bus]; - gpio_peripheral(info->sda_pin, 3, 0); - gpio_peripheral(info->scl_pin, 3, 0); + gpio_peripheral(info->sda_pin, 3, 1); + gpio_peripheral(info->scl_pin, 3, 1); if (!is_enabled_pclock(info->pclk)) { enable_pclock(info->pclk); diff --git a/src/rp2040/main.c b/src/rp2040/main.c index fbbe3b56..462e69c6 100644 --- a/src/rp2040/main.c +++ b/src/rp2040/main.c @@ -5,11 +5,13 @@ // This file may be distributed under the terms of the GNU GPLv3 license. #include // uint32_t +#include "board/misc.h" // bootloader_request #include "hardware/structs/clocks.h" // clock_hw_t #include "hardware/structs/pll.h" // pll_hw_t #include "hardware/structs/resets.h" // sio_hw #include "hardware/structs/watchdog.h" // watchdog_hw #include "hardware/structs/xosc.h" // xosc_hw +#include "internal.h" // enable_pclock #include "sched.h" // sched_main @@ -36,6 +38,18 @@ watchdog_init(void) DECL_INIT(watchdog_init); +/**************************************************************** + * Bootloader + ****************************************************************/ + +void +bootloader_request(void) +{ + // Use the bootrom-provided code to reset into BOOTSEL mode + reset_to_usb_boot(0, 0); +} + + /**************************************************************** * Clock setup ****************************************************************/ diff --git a/src/rp2040/usbserial.c b/src/rp2040/usbserial.c index 3a6736ef..aafe53ef 100644 --- a/src/rp2040/usbserial.c +++ b/src/rp2040/usbserial.c @@ -7,9 +7,13 @@ #include // memcpy #include "board/armcm_boot.h" // armcm_enable_irq #include "board/io.h" // writeb +#include "board/misc.h" // timer_read_time #include "board/usb_cdc.h" // usb_notify_ep0 #include "board/usb_cdc_ep.h" // USB_CDC_EP_BULK_IN #include "board/usbstd.h" // USB_ENDPOINT_XFER_INT +#include "hardware/regs/sysinfo.h" // SYSINFO_CHIP_ID_OFFSET +#include "hardware/structs/iobank0.h" // iobank0_hw +#include "hardware/structs/padsbank0.h" // padsbank0_hw #include "hardware/structs/resets.h" // RESETS_RESET_USBCTRL_BITS #include "hardware/structs/usb.h" // usb_hw #include "internal.h" // enable_pclock @@ -156,12 +160,88 @@ usb_set_configure(void) USB_BUF_CTRL_AVAIL | USB_BUF_CTRL_LAST | DPBUF_SIZE); } + +/**************************************************************** + * USB Errata workaround + ****************************************************************/ + +// The rp2040 USB has an errata causing it to sometimes not connect +// after a reset. The following code has extracts from the PICO SDK. + +static struct task_wake usb_errata_wake; + +// Workaround for rp2040-e5 errata void -usb_request_bootloader(void) +usb_errata_task(void) { - // Use the bootrom-provided code to reset into BOOTSEL mode - reset_to_usb_boot(0, 0); + if (!sched_check_wake(&usb_errata_wake)) + return; + + if (usb_hw->sie_status & USB_SIE_STATUS_CONNECTED_BITS) + // Already connected - workaround not needed + return; + + // Wait for not in SE0 state + if (!(usb_hw->sie_status & USB_SIE_STATUS_LINE_STATE_BITS)) { + sched_wake_task(&usb_errata_wake); + return; + } + + // Backup GPIO15 pad state + uint32_t dp = 15; + uint32_t gpio_ctrl_prev = iobank0_hw->io[dp].ctrl; + uint32_t pad_ctrl_prev = padsbank0_hw->io[dp]; + + // Enable bus keep + hw_write_masked(&padsbank0_hw->io[dp], + PADS_BANK0_GPIO15_PUE_BITS | PADS_BANK0_GPIO15_PDE_BITS, + PADS_BANK0_GPIO15_PUE_BITS | PADS_BANK0_GPIO15_PDE_BITS); + // Disable pad output + hw_write_masked(&iobank0_hw->io[dp].ctrl, + 0x2 << IO_BANK0_GPIO15_CTRL_OEOVER_LSB, + IO_BANK0_GPIO15_CTRL_OEOVER_BITS); + // Enable USB debug muxing function + hw_write_masked(&iobank0_hw->io[dp].ctrl, + 8 << IO_BANK0_GPIO15_CTRL_FUNCSEL_LSB, + IO_BANK0_GPIO15_CTRL_FUNCSEL_BITS); + // Set input override + hw_write_masked(&iobank0_hw->io[dp].ctrl, + 0x3 << IO_BANK0_GPIO15_CTRL_INOVER_LSB, + IO_BANK0_GPIO15_CTRL_INOVER_BITS); + // PHY pullups need to stay on + hw_set_alias(usb_hw)->phy_direct = USB_USBPHY_DIRECT_DP_PULLUP_EN_BITS; + hw_set_alias(usb_hw)->phy_direct_override = + USB_USBPHY_DIRECT_OVERRIDE_DP_PULLUP_EN_OVERRIDE_EN_BITS; + // Switch from USB PHY to GPIO PHY, now with J forced + usb_hw->muxing = (USB_USB_MUXING_TO_DIGITAL_PAD_BITS + | USB_USB_MUXING_SOFTCON_BITS); + + // Wait 1ms + uint32_t endtime = timer_read_time() + timer_from_us(1000); + while (timer_is_before(timer_read_time(), endtime)) + ; + + // Verify in connected state + endtime += timer_from_us(1000); + for (;;) { + if (usb_hw->sie_status & USB_SIE_STATUS_CONNECTED_BITS) + break; + if (timer_is_before(endtime, timer_read_time())) + // Something went wrong - restore state and continue anyway + break; + } + + // Switch back to USB phy + usb_hw->muxing = USB_USB_MUXING_TO_PHY_BITS | USB_USB_MUXING_SOFTCON_BITS; + // Unset PHY pullup overrides + hw_clear_alias(usb_hw)->phy_direct_override = + USB_USBPHY_DIRECT_OVERRIDE_DP_PULLUP_EN_OVERRIDE_EN_BITS; + + // Restore GPIO control states + iobank0_hw->io[dp].ctrl = gpio_ctrl_prev; + padsbank0_hw->io[dp] = pad_ctrl_prev; } +DECL_TASK(usb_errata_task); /**************************************************************** @@ -191,6 +271,10 @@ USB_Handler(void) } } } + if (ints & USB_INTS_BUS_RESET_BITS) { + usb_hw->sie_status = USB_SIE_STATUS_BUS_RESET_BITS; + sched_wake_task(&usb_errata_wake); + } } static void @@ -228,15 +312,20 @@ usbserial_init(void) | USB_USB_PWR_VBUS_DETECT_OVERRIDE_EN_BITS); usb_hw->main_ctrl = USB_MAIN_CTRL_CONTROLLER_EN_BITS; + // Check if usb errata workaround needed + enable_pclock(RESETS_RESET_SYSINFO_BITS); + uint32_t chip_id = *((io_ro_32*)(SYSINFO_BASE + SYSINFO_CHIP_ID_OFFSET)); + uint32_t version = ((chip_id & SYSINFO_CHIP_ID_REVISION_BITS) + >> SYSINFO_CHIP_ID_REVISION_LSB); + // Enable irqs usb_hw->sie_ctrl = USB_SIE_CTRL_EP0_INT_1BUF_BITS; - usb_hw->inte = USB_INTE_BUFF_STATUS_BITS | USB_INTE_SETUP_REQ_BITS; + usb_hw->inte = (USB_INTE_BUFF_STATUS_BITS | USB_INTE_SETUP_REQ_BITS + | (version == 1 ? USB_INTE_BUS_RESET_BITS: 0)); armcm_enable_irq(USB_Handler, USBCTRL_IRQ_IRQn, 1); // Enable USB pullup usb_hw->sie_ctrl = (USB_SIE_CTRL_EP0_INT_1BUF_BITS | USB_SIE_CTRL_PULLUP_EN_BITS); - - // XXX - errata reset workaround?? } DECL_INIT(usbserial_init); diff --git a/src/sensor_mpu9250.c b/src/sensor_mpu9250.c new file mode 100644 index 00000000..d7f30928 --- /dev/null +++ b/src/sensor_mpu9250.c @@ -0,0 +1,277 @@ +// Support for gathering acceleration data from mpu9250 chip +// +// Copyright (C) 2022 Harry Beyel +// Copyright (C) 2020-2021 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 "board/gpio.h" // i2c_read +#include "i2ccmds.h" // i2cdev_oid_lookup + +// Chip registers +#define AR_FIFO_SIZE 512 + +#define AR_PWR_MGMT_1 0x6B +#define AR_PWR_MGMT_2 0x6C +#define AR_FIFO_EN 0x23 +#define AR_ACCEL_OUT_XH 0x3B +#define AR_USER_CTRL 0x6A +#define AR_FIFO_COUNT_H 0x72 +#define AR_FIFO 0x74 + +#define SET_ENABLE_FIFO 0x08 +#define SET_DISABLE_FIFO 0x00 +#define SET_USER_FIFO_RESET 0x04 +#define SET_USER_FIFO_EN 0x40 + +#define SET_PWR_SLEEP 0x40 +#define SET_PWR_WAKE 0x00 +#define SET_PWR_2_ACCEL 0x07 // only enable accelerometers +#define SET_PWR_2_NONE 0x3F // disable all sensors + +#define BYTES_PER_FIFO_ENTRY 6 + +struct mpu9250 { + struct timer timer; + uint32_t rest_ticks; + struct i2cdev_s *i2c; + uint16_t sequence, limit_count; + uint8_t flags, data_count; + // data size must be <= 255 due to i2c api + // = SAMPLES_PER_BLOCK (from mpu9250.py) * BYTES_PER_FIFO_ENTRY + 1 + uint8_t data[48]; +}; + +enum { + AX_HAVE_START = 1<<0, AX_RUNNING = 1<<1, AX_PENDING = 1<<2, +}; + +static struct task_wake mpu9250_wake; + +// Reads the fifo byte count from the device. +uint16_t +get_fifo_status (struct mpu9250 *mp) +{ + uint8_t regs[] = {AR_FIFO_COUNT_H}; + uint8_t msg[2]; + i2c_read(mp->i2c->i2c_config, sizeof(regs), regs, 2, msg); + msg[0] = 0x1F & msg[0]; // discard 3 MSB per datasheet + return (((uint16_t)msg[0]) << 8 | msg[1]); +} + +// Event handler that wakes mpu9250_task() periodically +static uint_fast8_t +mpu9250_event(struct timer *timer) +{ + struct mpu9250 *ax = container_of(timer, struct mpu9250, timer); + ax->flags |= AX_PENDING; + sched_wake_task(&mpu9250_wake); + return SF_DONE; +} + +void +command_config_mpu9250(uint32_t *args) +{ + struct mpu9250 *mp = oid_alloc(args[0], command_config_mpu9250 + , sizeof(*mp)); + mp->timer.func = mpu9250_event; + mp->i2c = i2cdev_oid_lookup(args[1]); +} +DECL_COMMAND(command_config_mpu9250, "config_mpu9250 oid=%c i2c_oid=%c"); + +// Report local measurement buffer +static void +mp9250_report(struct mpu9250 *mp, uint8_t oid) +{ + sendf("mpu9250_data oid=%c sequence=%hu data=%*s" + , oid, mp->sequence, mp->data_count, mp->data); + mp->data_count = 0; + mp->sequence++; +} + +// Report buffer and fifo status +static void +mp9250_status(struct mpu9250 *mp, uint_fast8_t oid + , uint32_t time1, uint32_t time2, uint16_t fifo) +{ + sendf("mpu9250_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" + " buffered=%c fifo=%u limit_count=%hu" + , oid, time1, time2-time1, mp->sequence + , mp->data_count, fifo, mp->limit_count); +} + +// Helper code to reschedule the mpu9250_event() timer +static void +mp9250_reschedule_timer(struct mpu9250 *mp) +{ + irq_disable(); + mp->timer.waketime = timer_read_time() + mp->rest_ticks; + sched_add_timer(&mp->timer); + irq_enable(); +} + +// Query accelerometer data +static void +mp9250_query(struct mpu9250 *mp, uint8_t oid) +{ + // Check fifo status + uint16_t fifo_bytes = get_fifo_status(mp); + if (fifo_bytes >= AR_FIFO_SIZE - BYTES_PER_FIFO_ENTRY) + mp->limit_count++; + + // Read data + // FIFO data are: [Xh, Xl, Yh, Yl, Zh, Zl] + uint8_t reg = AR_FIFO; + uint8_t bytes_to_read = fifo_bytes < sizeof(mp->data) - mp->data_count ? + fifo_bytes & 0xFF : + (sizeof(mp->data) - mp->data_count) & 0xFF; + + // round down to nearest full packet of data + bytes_to_read = bytes_to_read / BYTES_PER_FIFO_ENTRY * BYTES_PER_FIFO_ENTRY; + + // Extract x, y, z measurements into data holder and report + if (bytes_to_read > 0) { + i2c_read(mp->i2c->i2c_config, sizeof(reg), ®, + bytes_to_read, &mp->data[mp->data_count]); + mp->data_count += bytes_to_read; + + // report data when buffer is full + if (mp->data_count + BYTES_PER_FIFO_ENTRY > sizeof(mp->data)) { + mp9250_report(mp, oid); + } + } + + // check if we need to run the task again (more packets in fifo?) + if ( bytes_to_read > 0 && + bytes_to_read / BYTES_PER_FIFO_ENTRY < + fifo_bytes / BYTES_PER_FIFO_ENTRY) { + // more data still ready in the fifo buffer + sched_wake_task(&mpu9250_wake); + } + else if (mp->flags & AX_RUNNING) { + // No more fifo data, but actively running. Sleep until next check + sched_del_timer(&mp->timer); + mp->flags &= ~AX_PENDING; + mp9250_reschedule_timer(mp); + } +} + +// Startup measurements +static void +mp9250_start(struct mpu9250 *mp, uint8_t oid) +{ + sched_del_timer(&mp->timer); + mp->flags = AX_RUNNING; + uint8_t msg[2]; + + msg[0] = AR_FIFO_EN; + msg[1] = SET_DISABLE_FIFO; // disable FIFO + i2c_write(mp->i2c->i2c_config, sizeof(msg), msg); + + msg[0] = AR_USER_CTRL; + msg[1] = SET_USER_FIFO_RESET; // reset FIFO buffer + i2c_write(mp->i2c->i2c_config, sizeof(msg), msg); + + msg[0] = AR_USER_CTRL; + msg[1] = SET_USER_FIFO_EN; // enable FIFO buffer access + i2c_write(mp->i2c->i2c_config, sizeof(msg), msg); + + msg[0] = AR_FIFO_EN; + msg[1] = SET_ENABLE_FIFO; // enable accel output to FIFO + i2c_write(mp->i2c->i2c_config, sizeof(msg), msg); + + mp9250_reschedule_timer(mp); +} + +// End measurements +static void +mp9250_stop(struct mpu9250 *mp, uint8_t oid) +{ + // Disable measurements + sched_del_timer(&mp->timer); + mp->flags = 0; + + // disable accel FIFO + uint8_t msg[2] = { AR_FIFO_EN, SET_DISABLE_FIFO }; + uint32_t end1_time = timer_read_time(); + i2c_write(mp->i2c->i2c_config, sizeof(msg), msg); + uint32_t end2_time = timer_read_time(); + + // Drain any measurements still in fifo + uint16_t fifo_bytes = get_fifo_status(mp); + while (fifo_bytes >= BYTES_PER_FIFO_ENTRY) { + mp9250_query(mp, oid); + fifo_bytes = get_fifo_status(mp); + } + + // Report final data + if (mp->data_count > 0) + mp9250_report(mp, oid); + mp9250_status(mp, oid, end1_time, end2_time, + fifo_bytes / BYTES_PER_FIFO_ENTRY); +} + +void +command_query_mpu9250(uint32_t *args) +{ + struct mpu9250 *mp = oid_lookup(args[0], command_config_mpu9250); + + if (!args[2]) { + // End measurements + mp9250_stop(mp, args[0]); + return; + } + // Start new measurements query + sched_del_timer(&mp->timer); + mp->timer.waketime = args[1]; + mp->rest_ticks = args[2]; + mp->flags = AX_HAVE_START; + mp->sequence = mp->limit_count = 0; + mp->data_count = 0; + sched_add_timer(&mp->timer); +} +DECL_COMMAND(command_query_mpu9250, + "query_mpu9250 oid=%c clock=%u rest_ticks=%u"); + +void +command_query_mpu9250_status(uint32_t *args) +{ + struct mpu9250 *mp = oid_lookup(args[0], command_config_mpu9250); + uint8_t msg[2]; + uint32_t time1 = timer_read_time(); + uint8_t regs[] = {AR_FIFO_COUNT_H}; + i2c_read(mp->i2c->i2c_config, 1, regs, 2, msg); + uint32_t time2 = timer_read_time(); + msg[0] = 0x1F & msg[0]; // discard 3 MSB + uint16_t fifo_bytes = (((uint16_t)msg[0]) << 8) | msg[1]; + mp9250_status(mp, args[0], time1, time2, fifo_bytes / BYTES_PER_FIFO_ENTRY); +} +DECL_COMMAND(command_query_mpu9250_status, "query_mpu9250_status oid=%c"); + +void +mpu9250_task(void) +{ + if (!sched_check_wake(&mpu9250_wake)) + return; + uint8_t oid; + struct mpu9250 *mp; + foreach_oid(oid, mp, command_config_mpu9250) { + uint_fast8_t flags = mp->flags; + if (!(flags & AX_PENDING)) { + continue; + } + if (flags & AX_HAVE_START) { + mp9250_start(mp, oid); + } + else { + mp9250_query(mp, oid); + } + } +} +DECL_TASK(mpu9250_task); diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index 934a6920..3f01ef66 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -76,6 +76,10 @@ choice select MACH_STM32H7 endchoice +config MACH_STM32F103x6 + depends on LOW_LEVEL_OPTIONS && MACH_STM32F103 + bool "Only 10KiB of RAM (for rare stm32f103x6 variant)" + config MACH_STM32F0 bool config MACH_STM32F1 @@ -94,13 +98,23 @@ config MACH_STM32F4x5 # F405, F407, F429 series bool config HAVE_STM32_USBFS bool - default y if MACH_STM32F103 || MACH_STM32F0x2 || MACH_STM32F070 || MACH_STM32G0 + default y if MACH_STM32F0x2 || MACH_STM32G0 + default y if (MACH_STM32F103 || MACH_STM32F070) && !STM32_CLOCK_REF_INTERNAL config HAVE_STM32_USBOTG bool default y if MACH_STM32F2 || MACH_STM32F4 || MACH_STM32H7 config HAVE_STM32_CANBUS bool default y if MACH_STM32F1 || MACH_STM32F2 || MACH_STM32F4x5 || MACH_STM32F446 || MACH_STM32F0x2 +config HAVE_STM32_FDCANBUS + bool + default y if MACH_STM32G0 || MACH_STM32H7 +config HAVE_STM32_USBCANBUS + bool + depends on HAVE_STM32_USBFS || HAVE_STM32_USBOTG + depends on HAVE_STM32_CANBUS || HAVE_STM32_FDCANBUS + depends on !MACH_STM32F103 + default y config MCU string @@ -153,7 +167,8 @@ config RAM_SIZE default 0x1000 if MACH_STM32F031 default 0x1800 if MACH_STM32F042 default 0x4000 if MACH_STM32F070 || MACH_STM32F072 - default 0x5000 if MACH_STM32F103 # Ram size of stm32f103x8 (20KiB) + default 0x2800 if MACH_STM32F103x6 + default 0x5000 if MACH_STM32F103 && !MACH_STM32F103x6 # Ram size of stm32f103x8 default 0x20000 if MACH_STM32F207 default 0x10000 if MACH_STM32F401 default 0x20000 if MACH_STM32F4x5 || MACH_STM32F446 @@ -201,6 +216,8 @@ choice config STM32_FLASH_START_800 bool "2KiB bootloader (HID Bootloader)" if MACH_STM32F103 + config STM32_FLASH_START_1000 + bool "4KiB bootloader" if MACH_STM32F1 || MACH_STM32F0 config STM32_FLASH_START_4000 bool "16KiB bootloader (HID Bootloader)" if MACH_STM32F207 || MACH_STM32F401 || MACH_STM32F4x5 || MACH_STM32F103 || MACH_STM32F072 config STM32_FLASH_START_20000 @@ -212,6 +229,7 @@ endchoice config FLASH_START hex default 0x8000800 if STM32_FLASH_START_800 + default 0x8001000 if STM32_FLASH_START_1000 default 0x8002000 if STM32_FLASH_START_2000 default 0x8004000 if STM32_FLASH_START_4000 default 0x8005000 if STM32_FLASH_START_5000 @@ -269,12 +287,6 @@ config STM32F0_TRIM # Communication inteface ###################################################################### -config USBSERIAL - bool -config SERIAL - bool -config CANSERIAL - bool choice prompt "Communication interface" config STM32_USB_PA11_PA12 @@ -316,36 +328,100 @@ choice select SERIAL config STM32_CANBUS_PA11_PA12 bool "CAN bus (on PA11/PA12)" - depends on HAVE_STM32_CANBUS + depends on HAVE_STM32_CANBUS || HAVE_STM32_FDCANBUS select CANSERIAL config STM32_CANBUS_PA11_PA12_REMAP bool "CAN bus (on PA9/PA10)" if LOW_LEVEL_OPTIONS depends on HAVE_STM32_CANBUS && MACH_STM32F042 select CANSERIAL - config STM32_CANBUS_PB8_PB9 + config STM32_MMENU_CANBUS_PB8_PB9 bool "CAN bus (on PB8/PB9)" if LOW_LEVEL_OPTIONS - depends on HAVE_STM32_CANBUS + depends on HAVE_STM32_CANBUS || HAVE_STM32_FDCANBUS select CANSERIAL - config STM32_CANBUS_PI9_PH13 + config STM32_MMENU_CANBUS_PI9_PH13 bool "CAN bus (on PI9/PH13)" if LOW_LEVEL_OPTIONS depends on HAVE_STM32_CANBUS && MACH_STM32F4 select CANSERIAL - config STM32_CANBUS_PB5_PB6 + config STM32_MMENU_CANBUS_PB5_PB6 bool "CAN bus (on PB5/PB6)" if LOW_LEVEL_OPTIONS depends on HAVE_STM32_CANBUS && MACH_STM32F4 select CANSERIAL - config STM32_CANBUS_PB12_PB13 + config STM32_MMENU_CANBUS_PB12_PB13 bool "CAN bus (on PB12/PB13)" if LOW_LEVEL_OPTIONS depends on HAVE_STM32_CANBUS && MACH_STM32F4 select CANSERIAL - config STM32_CANBUS_PD0_PD1 + config STM32_MMENU_CANBUS_PD0_PD1 bool "CAN bus (on PD0/PD1)" if LOW_LEVEL_OPTIONS - depends on HAVE_STM32_CANBUS + depends on HAVE_STM32_CANBUS || HAVE_STM32_FDCANBUS select CANSERIAL + config STM32_MMENU_CANBUS_PB0_PB1 + bool "CAN bus (on PB0/PB1)" + depends on HAVE_STM32_FDCANBUS + select CANSERIAL + config STM32_MMENU_CANBUS_PD12_PD13 + bool "CAN bus (on PD12/PD13)" + depends on HAVE_STM32_FDCANBUS + select CANSERIAL + config STM32_MMENU_CANBUS_PC2_PC3 + bool "CAN bus (on PC2/PC3)" + depends on HAVE_STM32_FDCANBUS + select CANSERIAL + config STM32_USBCANBUS_PA11_PA12 + bool "USB to CAN bus bridge (USB on PA11/PA12)" + depends on HAVE_STM32_USBCANBUS + select USBCANBUS +endchoice +choice + prompt "CAN bus interface" if USBCANBUS + config STM32_CMENU_CANBUS_PB8_PB9 + bool "CAN bus (on PB8/PB9)" + config STM32_CMENU_CANBUS_PI9_PH13 + bool "CAN bus (on PI9/PH13)" + depends on HAVE_STM32_CANBUS && MACH_STM32F4 + config STM32_CMENU_CANBUS_PB5_PB6 + bool "CAN bus (on PB5/PB6)" + depends on HAVE_STM32_CANBUS && MACH_STM32F4 + config STM32_CMENU_CANBUS_PB12_PB13 + bool "CAN bus (on PB12/PB13)" + depends on HAVE_STM32_CANBUS && MACH_STM32F4 + config STM32_CMENU_CANBUS_PD0_PD1 + bool "CAN bus (on PD0/PD1)" + depends on HAVE_STM32_CANBUS || HAVE_STM32_FDCANBUS + config STM32_CMENU_CANBUS_PB0_PB1 + bool "CAN bus (on PB0/PB1)" + depends on HAVE_STM32_FDCANBUS + config STM32_CMENU_CANBUS_PD12_PD13 + bool "CAN bus (on PD12/PD13)" + depends on HAVE_STM32_FDCANBUS + config STM32_CMENU_CANBUS_PC2_PC3 + bool "CAN bus (on PC2/PC3)" + depends on HAVE_STM32_FDCANBUS endchoice -config CANBUS_FREQUENCY - int "CAN bus speed" if LOW_LEVEL_OPTIONS && CANSERIAL - default 500000 + +config STM32_CANBUS_PB8_PB9 + bool + default y if STM32_MMENU_CANBUS_PB8_PB9 || STM32_CMENU_CANBUS_PB8_PB9 +config STM32_CANBUS_PI9_PH13 + bool + default y if STM32_MMENU_CANBUS_PI9_PH13 || STM32_CMENU_CANBUS_PI9_PH13 +config STM32_CANBUS_PB5_PB6 + bool + default y if STM32_MMENU_CANBUS_PB5_PB6 || STM32_CMENU_CANBUS_PB5_PB6 +config STM32_CANBUS_PB12_PB13 + bool + default y if STM32_MMENU_CANBUS_PB12_PB13 || STM32_CMENU_CANBUS_PB12_PB13 +config STM32_CANBUS_PD0_PD1 + bool + default y if STM32_MMENU_CANBUS_PD0_PD1 || STM32_CMENU_CANBUS_PD0_PD1 +config STM32_CANBUS_PB0_PB1 + bool + default y if STM32_MMENU_CANBUS_PB0_PB1 || STM32_CMENU_CANBUS_PB0_PB1 +config STM32_CANBUS_PD12_PD13 + bool + default y if STM32_MMENU_CANBUS_PD12_PD13 || STM32_CMENU_CANBUS_PD12_PD13 +config STM32_CANBUS_PC2_PC3 + bool + default y if STM32_MMENU_CANBUS_PC2_PC3 || STM32_CMENU_CANBUS_PC2_PC3 endif diff --git a/src/stm32/Makefile b/src/stm32/Makefile index b4adf20e..982097d8 100644 --- a/src/stm32/Makefile +++ b/src/stm32/Makefile @@ -3,7 +3,7 @@ # Setup the toolchain CROSS_PREFIX=arm-none-eabi- -dirs-y += src/stm32 src/generic +dirs-y += src/stm32 src/generic lib/fast-hash dirs-$(CONFIG_MACH_STM32F0) += lib/stm32f0 dirs-$(CONFIG_MACH_STM32F1) += lib/stm32f1 dirs-$(CONFIG_MACH_STM32F2) += lib/stm32f2 @@ -58,12 +58,14 @@ serial-src-y := stm32/serial.c serial-src-$(CONFIG_MACH_STM32F031) := stm32/stm32f031_serial.c src-y += $(serial-src-y) generic/serial_irq.c src-$(CONFIG_SERIAL) += stm32/serial_host.c -src-$(CONFIG_CANSERIAL) += stm32/can.c ../lib/fast-hash/fasthash.c -src-$(CONFIG_CANSERIAL) += generic/canbus.c +canbus-src-y := generic/canserial.c ../lib/fast-hash/fasthash.c +canbus-src-$(CONFIG_HAVE_STM32_CANBUS) += stm32/can.c +canbus-src-$(CONFIG_HAVE_STM32_FDCANBUS) += stm32/fdcan.c +src-$(CONFIG_CANSERIAL) += $(canbus-src-y) generic/canbus.c stm32/chipid.c +src-$(CONFIG_USBCANBUS) += $(usb-src-y) $(canbus-src-y) +src-$(CONFIG_USBCANBUS) += stm32/chipid.c generic/usb_canbus.c src-$(CONFIG_HAVE_GPIO_HARD_PWM) += stm32/hard_pwm.c -dirs-$(CONFIG_CANSERIAL) += lib/fast-hash - # Binary output file rules target-y += $(OUT)klipper.bin diff --git a/src/stm32/adc.c b/src/stm32/adc.c index 02e54fba..39d4f643 100644 --- a/src/stm32/adc.c +++ b/src/stm32/adc.c @@ -28,7 +28,7 @@ static const uint8_t adc_pins[] = { ADC_TEMPERATURE_PIN, #elif CONFIG_MACH_STM32F2 || CONFIG_MACH_STM32F4x5 ADC_TEMPERATURE_PIN, 0x00, 0x00, -#elif CONFIG_MACH_STM32F446 +#elif CONFIG_MACH_STM32F401 || CONFIG_MACH_STM32F446 0x00, 0x00, ADC_TEMPERATURE_PIN, #endif @@ -108,7 +108,9 @@ gpio_adc_setup(uint32_t pin) } if (pin == ADC_TEMPERATURE_PIN) { -#if !(CONFIG_MACH_STM32F1 || CONFIG_MACH_STM32F401) +#if CONFIG_MACH_STM32F401 + ADC1_COMMON->CCR = ADC_CCR_TSVREFE; +#elif !CONFIG_MACH_STM32F1 ADC123_COMMON->CCR = ADC_CCR_TSVREFE; #endif } else { diff --git a/src/stm32/can.c b/src/stm32/can.c index 613c1f27..665338ad 100644 --- a/src/stm32/can.c +++ b/src/stm32/can.c @@ -10,9 +10,9 @@ #include "autoconf.h" // CONFIG_MACH_STM32F1 #include "board/irq.h" // irq_disable #include "command.h" // DECL_CONSTANT_STR -#include "fasthash.h" // fasthash64 #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 @@ -66,7 +66,6 @@ #endif #if CONFIG_MACH_STM32F4 - #warning CAN on STM32F4 is untested #if (CONFIG_STM32_CANBUS_PA11_PA12 || CONFIG_STM32_CANBUS_PB8_PB9 \ || CONFIG_STM32_CANBUS_PD0_PD1 || CONFIG_STM32_CANBUS_PI9_PH13) #define SOC_CAN CAN1 @@ -91,41 +90,9 @@ #error No known CAN device for configured MCU #endif -// Read the next CAN packet -int -canbus_read(uint32_t *id, uint8_t *data) -{ - if (!(SOC_CAN->RF0R & CAN_RF0R_FMP0)) { - // All rx mboxes empty, enable wake on rx IRQ - irq_disable(); - SOC_CAN->IER |= CAN_IER_FMPIE0; - irq_enable(); - return -1; - } - - // Read and ack packet - CAN_FIFOMailBox_TypeDef *mb = &SOC_CAN->sFIFOMailBox[0]; - uint32_t rir_id = (mb->RIR >> CAN_RI0R_STID_Pos) & 0x7FF; - uint32_t dlc = mb->RDTR & CAN_RDT0R_DLC; - uint32_t rdlr = mb->RDLR, rdhr = mb->RDHR; - SOC_CAN->RF0R = CAN_RF0R_RFOM0; - - // Return packet - *id = rir_id; - data[0] = (rdlr >> 0) & 0xff; - data[1] = (rdlr >> 8) & 0xff; - data[2] = (rdlr >> 16) & 0xff; - data[3] = (rdlr >> 24) & 0xff; - data[4] = (rdhr >> 0) & 0xff; - data[5] = (rdhr >> 8) & 0xff; - data[6] = (rdhr >> 16) & 0xff; - data[7] = (rdhr >> 24) & 0xff; - return dlc; -} - // Transmit a packet int -canbus_send(uint32_t id, uint32_t len, uint8_t *data) +canbus_send(struct canbus_msg *msg) { uint32_t tsr = SOC_CAN->TSR; if (!(tsr & (CAN_TSR_TME0|CAN_TSR_TME1|CAN_TSR_TME2))) { @@ -135,27 +102,29 @@ canbus_send(uint32_t id, uint32_t len, uint8_t *data) irq_enable(); return -1; } - int mbox = (tsr & CAN_TSR_CODE) >> CAN_TSR_CODE_Pos; + int mbox = 2; + if (tsr & CAN_TSR_TME0) + mbox = 0; + else if (tsr & CAN_TSR_TME1) + mbox = 1; CAN_TxMailBox_TypeDef *mb = &SOC_CAN->sTxMailBox[mbox]; /* Set up the DLC */ - mb->TDTR = (mb->TDTR & 0xFFFFFFF0) | (len & 0x0F); + mb->TDTR = (mb->TDTR & 0xFFFFFFF0) | (msg->dlc & 0x0F); /* Set up the data field */ - if (len) { - mb->TDLR = (((uint32_t)data[3] << 24) - | ((uint32_t)data[2] << 16) - | ((uint32_t)data[1] << 8) - | ((uint32_t)data[0] << 0)); - mb->TDHR = (((uint32_t)data[7] << 24) - | ((uint32_t)data[6] << 16) - | ((uint32_t)data[5] << 8) - | ((uint32_t)data[4] << 0)); - } + mb->TDLR = msg->data32[0]; + mb->TDHR = msg->data32[1]; /* Request transmission */ - mb->TIR = (id << CAN_TI0R_STID_Pos) | CAN_TI0R_TXRQ; - return len; + uint32_t tir; + if (msg->id & CANMSG_ID_EFF) + tir = ((msg->id & 0x1fffffff) << CAN_TI0R_EXID_Pos) | CAN_TI0R_IDE; + else + tir = (msg->id & 0x7ff) << CAN_TI0R_STID_Pos; + tir |= msg->id & CANMSG_ID_RTR ? CAN_TI0R_RTR : 0; + mb->TIR = (msg->id << CAN_TI0R_STID_Pos) | CAN_TI0R_TXRQ; + return CANMSG_DATA_LEN(msg); } // Setup the receive packet filter @@ -167,20 +136,23 @@ canbus_set_filter(uint32_t id) /* Initialisation mode for the filter */ SOC_CAN->FA1R = 0; - uint32_t mask = CAN_RI0R_STID | CAN_TI0R_IDE | CAN_TI0R_RTR; - SOC_CAN->sFilterRegister[0].FR1 = CANBUS_ID_ADMIN << CAN_RI0R_STID_Pos; - SOC_CAN->sFilterRegister[0].FR2 = mask; - SOC_CAN->sFilterRegister[1].FR1 = (id + 1) << CAN_RI0R_STID_Pos; - SOC_CAN->sFilterRegister[1].FR2 = mask; - SOC_CAN->sFilterRegister[2].FR1 = id << CAN_RI0R_STID_Pos; - SOC_CAN->sFilterRegister[2].FR2 = mask; + if (CONFIG_CANBUS_FILTER) { + uint32_t mask = CAN_TI0R_STID | CAN_TI0R_IDE | CAN_TI0R_RTR; + SOC_CAN->sFilterRegister[0].FR1 = CANBUS_ID_ADMIN << CAN_RI0R_STID_Pos; + SOC_CAN->sFilterRegister[0].FR2 = mask; + SOC_CAN->sFilterRegister[1].FR1 = (id + 1) << CAN_RI0R_STID_Pos; + SOC_CAN->sFilterRegister[1].FR2 = mask; + SOC_CAN->sFilterRegister[2].FR1 = id << CAN_RI0R_STID_Pos; + SOC_CAN->sFilterRegister[2].FR2 = mask; + } else { + SOC_CAN->sFilterRegister[0].FR1 = 0; + SOC_CAN->sFilterRegister[0].FR2 = 0; + id = 0; + } /* 32-bit scale for the filter */ SOC_CAN->FS1R = (1<<0) | (1<<1) | (1<<2); - /* FIFO 1 assigned to 'id' */ - SOC_CAN->FFA1R = (1<<2); - /* Filter activation */ SOC_CAN->FA1R = (1<<0) | (id ? (1<<1) | (1<<2) : 0); /* Leave the initialisation mode for the filter */ @@ -191,27 +163,25 @@ canbus_set_filter(uint32_t id) void CAN_IRQHandler(void) { - if (SOC_CAN->RF1R & CAN_RF1R_FMP1) { + if (SOC_CAN->RF0R & CAN_RF0R_FMP0) { // Read and ack data packet - CAN_FIFOMailBox_TypeDef *mb = &SOC_CAN->sFIFOMailBox[1]; - uint32_t rir_id = (mb->RIR >> CAN_RI0R_STID_Pos) & 0x7FF; - uint32_t dlc = mb->RDTR & CAN_RDT0R_DLC; - uint32_t rdlr = mb->RDLR, rdhr = mb->RDHR; - SOC_CAN->RF1R = CAN_RF1R_RFOM1; + CAN_FIFOMailBox_TypeDef *mb = &SOC_CAN->sFIFOMailBox[0]; + uint32_t rir = mb->RIR; + struct canbus_msg msg; + if (rir & CAN_RI0R_IDE) + msg.id = ((rir >> CAN_RI0R_EXID_Pos) & 0x1fffffff) | CANMSG_ID_EFF; + else + msg.id = (rir >> CAN_RI0R_STID_Pos) & 0x7ff; + msg.id |= rir & CAN_RI0R_RTR ? CANMSG_ID_RTR : 0; + msg.dlc = mb->RDTR & CAN_RDT0R_DLC; + msg.data32[0] = mb->RDLR; + msg.data32[1] = mb->RDHR; + SOC_CAN->RF0R = CAN_RF0R_RFOM0; // Process packet - union { - struct { uint32_t rdlr, rdhr; }; - uint8_t data[8]; - } rdata = { .rdlr = rdlr, .rdhr = rdhr }; - canbus_process_data(rir_id, dlc, rdata.data); + canbus_process_data(&msg); } uint32_t ier = SOC_CAN->IER; - if (ier & CAN_IER_FMPIE0 && SOC_CAN->RF0R & CAN_RF0R_FMP0) { - // Admin Rx - SOC_CAN->IER = ier = ier & ~CAN_IER_FMPIE0; - canbus_notify_rx(); - } if (ier & CAN_IER_TMEIE && SOC_CAN->TSR & (CAN_TSR_RQCP0|CAN_TSR_RQCP1|CAN_TSR_RQCP2)) { // Tx @@ -252,7 +222,7 @@ compute_btr(uint32_t pclock, uint32_t bitrate) uint32_t bit_clocks = pclock / bitrate; // clock ticks per bit - uint32_t sjw = 2; + 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--) { @@ -306,10 +276,6 @@ can_init(void) armcm_enable_irq(CAN_IRQHandler, CAN_RX1_IRQn, 0); if (CAN_RX0_IRQn != CAN_TX_IRQn) armcm_enable_irq(CAN_IRQHandler, CAN_TX_IRQn, 0); - SOC_CAN->IER = CAN_IER_FMPIE1; - - // Convert unique 96-bit chip id into 48 bit representation - uint64_t hash = fasthash64((uint8_t*)UID_BASE, 12, 0xA16231A7); - canbus_set_uuid(&hash); + SOC_CAN->IER = CAN_IER_FMPIE0; } DECL_INIT(can_init); diff --git a/src/stm32/chipid.c b/src/stm32/chipid.c index 73e27503..e30563cd 100644 --- a/src/stm32/chipid.c +++ b/src/stm32/chipid.c @@ -4,6 +4,7 @@ // // This file may be distributed under the terms of the GNU GPLv3 license. +#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" // UID_BASE @@ -25,9 +26,10 @@ usbserial_get_serialid(void) void chipid_init(void) { - if (!CONFIG_USB_SERIAL_NUMBER_CHIPID) - return; - usb_fill_serial(&cdc_chipid.desc, ARRAY_SIZE(cdc_chipid.data) - , (void*)UID_BASE); + if (CONFIG_USB_SERIAL_NUMBER_CHIPID) + usb_fill_serial(&cdc_chipid.desc, ARRAY_SIZE(cdc_chipid.data) + , (void*)UID_BASE); + if (CONFIG_CANBUS) + canserial_set_uuid((void*)UID_BASE, CHIP_UID_LEN); } DECL_INIT(chipid_init); diff --git a/src/stm32/fdcan.c b/src/stm32/fdcan.c new file mode 100755 index 00000000..7eac761b --- /dev/null +++ b/src/stm32/fdcan.c @@ -0,0 +1,312 @@ +// Serial over CAN emulation for STM32 boards. +// +// Copyright (C) 2019 Eug Krashtan +// Copyright (C) 2020 Pontus Borg +// Copyright (C) 2021 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include // memcpy +#include "autoconf.h" // CONFIG_MACH_STM32F1 +#include "board/irq.h" // irq_disable +#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 "generic/serial_irq.h" // serial_rx_byte +#include "internal.h" // enable_pclock +#include "sched.h" // DECL_INIT + +typedef struct +{ + __IO uint32_t id_section; + __IO uint32_t dlc_section; + __IO uint32_t data[64 / 4]; +}FDCAN_FIFO_TypeDef; + +#define FDCAN_XTD (1<<30) +#define FDCAN_RTR (1<<29) + +typedef struct +{ + __IO uint32_t FLS[28]; // Filter list standard + __IO uint32_t FLE[16]; // Filter list extended + FDCAN_FIFO_TypeDef RXF0[3]; + FDCAN_FIFO_TypeDef RXF1[3]; + __IO uint32_t TEF[6]; // Tx event FIFO + FDCAN_FIFO_TypeDef TXFIFO[3]; +}FDCAN_MSG_RAM_TypeDef; + +typedef struct +{ + FDCAN_MSG_RAM_TypeDef fdcan1; + FDCAN_MSG_RAM_TypeDef fdcan2; +}FDCAN_RAM_TypeDef; + +FDCAN_RAM_TypeDef *fdcan_ram = (FDCAN_RAM_TypeDef *)(SRAMCAN_BASE); + +#define FDCAN_IE_TC (FDCAN_IE_TCE | FDCAN_IE_TCFE | FDCAN_IE_TFEE) + +#if CONFIG_STM32_CANBUS_PA11_PA12 + DECL_CONSTANT_STR("RESERVE_PINS_CAN", "PA11,PA12"); + #define GPIO_Rx GPIO('A', 11) + #define GPIO_Tx GPIO('A', 12) +#elif CONFIG_STM32_CANBUS_PB8_PB9 + DECL_CONSTANT_STR("RESERVE_PINS_CAN", "PB8,PB9"); + #define GPIO_Rx GPIO('B', 8) + #define GPIO_Tx GPIO('B', 9) +#elif CONFIG_STM32_CANBUS_PD0_PD1 + DECL_CONSTANT_STR("RESERVE_PINS_CAN", "PD0,PD1"); + #define GPIO_Rx GPIO('D', 0) + #define GPIO_Tx GPIO('D', 1) +#elif CONFIG_STM32_CANBUS_PD12_PD13 + DECL_CONSTANT_STR("RESERVE_PINS_CAN", "PD12,PD13"); + #define GPIO_Rx GPIO('D', 12) + #define GPIO_Tx GPIO('D', 13) +#elif CONFIG_STM32_CANBUS_PB0_PB1 + DECL_CONSTANT_STR("RESERVE_PINS_CAN", "PB0,PB1"); + #define GPIO_Rx GPIO('B', 0) + #define GPIO_Tx GPIO('B', 1) +#elif CONFIG_STM32_CANBUS_PC2_PC3 + DECL_CONSTANT_STR("RESERVE_PINS_CAN", "PC2,PC3"); + #define GPIO_Rx GPIO('C', 2) + #define GPIO_Tx GPIO('C', 3) +#endif + +#if !(CONFIG_STM32_CANBUS_PB0_PB1 || CONFIG_STM32_CANBUS_PC2_PC3) + #define SOC_CAN FDCAN1 + #define MSG_RAM fdcan_ram->fdcan1 +#else + #define SOC_CAN FDCAN2 + #define MSG_RAM fdcan_ram->fdcan2 +#endif + +#if CONFIG_MACH_STM32G0 + #define CAN_IT0_IRQn TIM16_FDCAN_IT0_IRQn + #define CAN_FUNCTION GPIO_FUNCTION(3) // Alternative function mapping number +#elif CONFIG_MACH_STM32H7 + #define CAN_IT0_IRQn FDCAN1_IT0_IRQn + #define CAN_FUNCTION GPIO_FUNCTION(9) // Alternative function mapping number +#endif + +#ifndef SOC_CAN + #error No known CAN device for configured MCU +#endif + +// Transmit a packet +int +canbus_send(struct canbus_msg *msg) +{ + uint32_t txfqs = SOC_CAN->TXFQS; + if (txfqs & FDCAN_TXFQS_TFQF) + // No space in transmit fifo - wait for irq + return -1; + + uint32_t w_index = ((txfqs & FDCAN_TXFQS_TFQPI) >> FDCAN_TXFQS_TFQPI_Pos); + FDCAN_FIFO_TypeDef *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]; + SOC_CAN->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 +canbus_set_filter(uint32_t id) +{ + if (!CONFIG_CANBUS_FILTER) + return; + /* Request initialisation */ + SOC_CAN->CCCR |= FDCAN_CCCR_INIT; + /* Wait the acknowledge */ + while (!(SOC_CAN->CCCR & FDCAN_CCCR_INIT)) + ; + /* Enable configuration change */ + SOC_CAN->CCCR |= FDCAN_CCCR_CCE; + + // Load filter + can_filter(0, CANBUS_ID_ADMIN); + can_filter(1, id); + can_filter(2, id + 1); + +#if CONFIG_MACH_STM32G0 + SOC_CAN->RXGFC = ((id ? 3 : 1) << FDCAN_RXGFC_LSS_Pos + | 0x02 << FDCAN_RXGFC_ANFS_Pos); +#elif CONFIG_MACH_STM32H7 + SOC_CAN->SIDFC |= (id ? 3 : 1) << FDCAN_SIDFC_LSS_Pos; + SOC_CAN->GFC = 0x02 << FDCAN_GFC_ANFS_Pos; +#endif + + /* Leave the initialisation mode for the filter */ + SOC_CAN->CCCR &= ~FDCAN_CCCR_CCE; + SOC_CAN->CCCR &= ~FDCAN_CCCR_INIT; +} + +// This function handles CAN global interrupts +void +CAN_IRQHandler(void) +{ + uint32_t ir = SOC_CAN->IR; + + if (ir & FDCAN_IE_RF0NE) { + SOC_CAN->IR = FDCAN_IE_RF0NE; + + uint32_t rxf0s = SOC_CAN->RXF0S; + if (rxf0s & FDCAN_RXF0S_F0FL) { + // Read and ack data packet + uint32_t idx = (rxf0s & FDCAN_RXF0S_F0GI) >> FDCAN_RXF0S_F0GI_Pos; + FDCAN_FIFO_TypeDef *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]; + SOC_CAN->RXF0A = idx; + + // Process packet + canbus_process_data(&msg); + } + } + if (ir & FDCAN_IE_TC) { + // Tx + SOC_CAN->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)) << FDCAN_NBTP_NSJW_Pos + | ((uint32_t)(time_seg1-1)) << FDCAN_NBTP_NTSEG1_Pos + | ((uint32_t)(time_seg2-1)) << FDCAN_NBTP_NTSEG2_Pos + | ((uint32_t)(brp - 1)) << FDCAN_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) +{ + enable_pclock((uint32_t)SOC_CAN); + + gpio_peripheral(GPIO_Rx, CAN_FUNCTION, 1); + gpio_peripheral(GPIO_Tx, CAN_FUNCTION, 0); + + uint32_t pclock = get_pclock_frequency((uint32_t)SOC_CAN); + + uint32_t btr = compute_btr(pclock, CONFIG_CANBUS_FREQUENCY); + + /*##-1- Configure the CAN #######################################*/ + + /* Exit from sleep mode */ + SOC_CAN->CCCR &= ~FDCAN_CCCR_CSR; + /* Wait the acknowledge */ + while (SOC_CAN->CCCR & FDCAN_CCCR_CSA) + ; + /* Request initialisation */ + SOC_CAN->CCCR |= FDCAN_CCCR_INIT; + /* Wait the acknowledge */ + while (!(SOC_CAN->CCCR & FDCAN_CCCR_INIT)) + ; + /* Enable configuration change */ + SOC_CAN->CCCR |= FDCAN_CCCR_CCE; + + /* Disable protocol exception handling */ + SOC_CAN->CCCR |= FDCAN_CCCR_PXHD; + + SOC_CAN->NBTP = btr; + +#if CONFIG_MACH_STM32H7 + /* + The Message RAM of STM32H7 is settable + So we set it to be consistent with STM32G0 + */ + uint32_t flssa = (uint32_t)&MSG_RAM - (uint32_t)&fdcan_ram->fdcan1; + uint32_t f0sa = flssa + + (((uint32_t)MSG_RAM.RXF0 - (uint32_t)MSG_RAM.FLS) / 4); + uint32_t tbsa = f0sa + + (((uint32_t)MSG_RAM.TXFIFO - (uint32_t)MSG_RAM.RXF0) / 4); + + SOC_CAN->SIDFC = flssa << FDCAN_SIDFC_FLSSA_Pos; + + SOC_CAN->RXF0C = ((f0sa << FDCAN_RXF0C_F0SA_Pos) + | (3 << FDCAN_RXF0C_F0S_Pos)); + SOC_CAN->RXESC = ((7 << FDCAN_RXESC_F1DS_Pos) + | (7 << FDCAN_RXESC_F0DS_Pos)); + + SOC_CAN->TXBC = ((tbsa << FDCAN_TXBC_TBSA_Pos) + | (3 << FDCAN_TXBC_TFQS_Pos)); + SOC_CAN->TXESC = 7 << FDCAN_TXESC_TBDS_Pos; +#endif + + /* Leave the initialisation mode */ + SOC_CAN->CCCR &= ~FDCAN_CCCR_CCE; + SOC_CAN->CCCR &= ~FDCAN_CCCR_INIT; + + /*##-2- Configure the CAN Filter #######################################*/ + canbus_set_filter(0); + + /*##-3- Configure Interrupts #################################*/ + armcm_enable_irq(CAN_IRQHandler, CAN_IT0_IRQn, 0); + SOC_CAN->ILE = FDCAN_ILE_EINT0; + SOC_CAN->IE = FDCAN_IE_RF0NE | FDCAN_IE_TC; +} +DECL_INIT(can_init); diff --git a/src/stm32/stm32f0.c b/src/stm32/stm32f0.c index 6b13d737..e63f1b5b 100644 --- a/src/stm32/stm32f0.c +++ b/src/stm32/stm32f0.c @@ -6,7 +6,9 @@ #include "autoconf.h" // CONFIG_CLOCK_REF_FREQ #include "board/armcm_boot.h" // armcm_main +#include "board/armcm_reset.h" // try_request_canboot #include "board/irq.h" // irq_disable +#include "board/misc.h" // bootloader_request #include "command.h" // DECL_CONSTANT_STR #include "internal.h" // enable_pclock #include "sched.h" // sched_main @@ -84,7 +86,7 @@ pll_setup(void) // Setup CFGR3 register uint32_t cfgr3 = RCC_CFGR3_I2C1SW; -#if CONFIG_USBSERIAL +#if CONFIG_USB // Select PLL as source for USB clock cfgr3 |= RCC_CFGR3_USBSW; #endif @@ -107,7 +109,7 @@ hsi48_setup(void) ; // Enable USB clock recovery - if (CONFIG_USBSERIAL) { + if (CONFIG_USB) { enable_pclock(CRS_BASE); CRS->CR |= CRS_CR_AUTOTRIMEN | CRS_CR_CEN; } @@ -129,7 +131,7 @@ hsi14_setup(void) /**************************************************************** - * USB bootloader + * Bootloader ****************************************************************/ #define USB_BOOT_FLAG_ADDR (CONFIG_RAM_START + CONFIG_RAM_SIZE - 1024) @@ -148,7 +150,7 @@ usb_reboot_for_dfu_bootloader(void) static void check_usb_dfu_bootloader(void) { - if (!CONFIG_USBSERIAL || !CONFIG_MACH_STM32F0x2 + if (!CONFIG_USB || !CONFIG_MACH_STM32F0x2 || *(uint64_t*)USB_BOOT_FLAG_ADDR != USB_BOOT_FLAG) return; *(uint64_t*)USB_BOOT_FLAG_ADDR = 0; @@ -159,10 +161,11 @@ check_usb_dfu_bootloader(void) : : "r"(sysbase[0]), "r"(sysbase[1])); } -// Handle USB reboot requests +// Handle reboot requests void -usb_request_bootloader(void) +bootloader_request(void) { + try_request_canboot(); usb_reboot_for_dfu_bootloader(); } @@ -201,8 +204,7 @@ armcm_main(void) FLASH->ACR = (1 << FLASH_ACR_LATENCY_Pos) | FLASH_ACR_PRFTBE; // Configure main clock - if (CONFIG_MACH_STM32F0x2 && CONFIG_STM32_CLOCK_REF_INTERNAL - && CONFIG_USBSERIAL) + if (CONFIG_MACH_STM32F0x2 && CONFIG_STM32_CLOCK_REF_INTERNAL && CONFIG_USB) hsi48_setup(); else pll_setup(); diff --git a/src/stm32/stm32f0_adc.c b/src/stm32/stm32f0_adc.c index 8f27dce7..f46ccbde 100644 --- a/src/stm32/stm32f0_adc.c +++ b/src/stm32/stm32f0_adc.c @@ -119,7 +119,16 @@ gpio_adc_sample(struct gpio_adc g) return 0; goto need_delay; } +#if CONFIG_MACH_STM32G0 + if (adc->CHSELR != g.chan) { + adc->ISR = ADC_ISR_CCRDY; + adc->CHSELR = g.chan; + while (!(adc->ISR & ADC_ISR_CCRDY)) + ; + } +#else adc->CHSELR = g.chan; +#endif adc->CR = CR_FLAGS | ADC_CR_ADSTART; need_delay: diff --git a/src/stm32/stm32f1.c b/src/stm32/stm32f1.c index 526ec485..9d3c3b97 100644 --- a/src/stm32/stm32f1.c +++ b/src/stm32/stm32f1.c @@ -1,13 +1,14 @@ // Code to setup clocks and gpio on stm32f1 // -// Copyright (C) 2019-2021 Kevin O'Connor +// Copyright (C) 2019-2022 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. #include "autoconf.h" // CONFIG_CLOCK_REF_FREQ #include "board/armcm_boot.h" // VectorTable +#include "board/armcm_reset.h" // try_request_canboot #include "board/irq.h" // irq_disable -#include "board/usb_cdc.h" // usb_request_bootloader +#include "board/misc.h" // bootloader_request #include "internal.h" // enable_pclock #include "sched.h" // sched_main @@ -212,7 +213,7 @@ gpio_peripheral(uint32_t gpio, uint32_t mode, int pullup) /**************************************************************** - * USB bootloader + * Bootloader ****************************************************************/ // Reboot into USB "HID" bootloader @@ -239,10 +240,11 @@ usb_stm32duino_bootloader(void) NVIC_SystemReset(); } -// Handle USB reboot requests +// Handle reboot requests void -usb_request_bootloader(void) +bootloader_request(void) { + try_request_canboot(); if (CONFIG_STM32_FLASH_START_800) usb_hid_bootloader(); else if (CONFIG_STM32_FLASH_START_2000) diff --git a/src/stm32/stm32f4.c b/src/stm32/stm32f4.c index 149cd171..4f1dc084 100644 --- a/src/stm32/stm32f4.c +++ b/src/stm32/stm32f4.c @@ -6,8 +6,9 @@ #include "autoconf.h" // CONFIG_CLOCK_REF_FREQ #include "board/armcm_boot.h" // VectorTable +#include "board/armcm_reset.h" // try_request_canboot #include "board/irq.h" // irq_disable -#include "board/usb_cdc.h" // usb_request_bootloader +#include "board/misc.h" // bootloader_request #include "command.h" // DECL_CONSTANT_STR #include "internal.h" // enable_pclock #include "sched.h" // sched_main @@ -138,7 +139,7 @@ enable_clock_stm32f446(void) ; // Enable 48Mhz USB clock - if (CONFIG_USBSERIAL) { + if (CONFIG_USB) { uint32_t ref = (CONFIG_STM32_CLOCK_REF_INTERNAL ? 16000000 : CONFIG_CLOCK_REF_FREQ); uint32_t plls_base = 2000000, plls_freq = FREQ_USB * 4; @@ -187,7 +188,7 @@ clock_setup(void) /**************************************************************** - * USB bootloader + * Bootloader ****************************************************************/ // Reboot into USB "HID" bootloader @@ -219,7 +220,7 @@ usb_reboot_for_dfu_bootloader(void) static void check_usb_dfu_bootloader(void) { - if (!CONFIG_USBSERIAL || *(uint64_t*)USB_BOOT_FLAG_ADDR != USB_BOOT_FLAG) + if (!CONFIG_USB || *(uint64_t*)USB_BOOT_FLAG_ADDR != USB_BOOT_FLAG) return; *(uint64_t*)USB_BOOT_FLAG_ADDR = 0; uint32_t *sysbase = (uint32_t*)0x1fff0000; @@ -227,10 +228,11 @@ check_usb_dfu_bootloader(void) : : "r"(sysbase[0]), "r"(sysbase[1])); } -// Handle USB reboot requests +// Handle reboot requests void -usb_request_bootloader(void) +bootloader_request(void) { + try_request_canboot(); if (CONFIG_STM32_FLASH_START_4000) usb_hid_bootloader(); usb_reboot_for_dfu_bootloader(); diff --git a/src/stm32/stm32g0.c b/src/stm32/stm32g0.c index 6cb7c54c..63edcbaa 100644 --- a/src/stm32/stm32g0.c +++ b/src/stm32/stm32g0.c @@ -6,7 +6,9 @@ #include "autoconf.h" // CONFIG_CLOCK_REF_FREQ #include "board/armcm_boot.h" // armcm_main +#include "board/armcm_reset.h" // try_request_canboot #include "board/irq.h" // irq_disable +#include "board/misc.h" // bootloader_request #include "command.h" // DECL_CONSTANT_STR #include "internal.h" // enable_pclock #include "sched.h" // sched_main @@ -30,6 +32,8 @@ lookup_clock_line(uint32_t periph_base) uint32_t bit = 1 << ((periph_base - AHBPERIPH_BASE) / 0x400); return (struct cline){.en=&RCC->AHBENR, .rst=&RCC->AHBRSTR, .bit=bit}; } + if ((periph_base == FDCAN1_BASE) || (periph_base == FDCAN2_BASE)) + return (struct cline){.en=&RCC->APBENR1,.rst=&RCC->APBRSTR1,.bit=1<<12}; if (periph_base == USB_BASE) return (struct cline){.en=&RCC->APBENR1,.rst=&RCC->APBRSTR1,.bit=1<<13}; if (periph_base == CRS_BASE) @@ -100,7 +104,7 @@ clock_setup(void) /**************************************************************** - * USB bootloader + * Bootloader ****************************************************************/ #define USB_BOOT_FLAG_ADDR (CONFIG_RAM_START + CONFIG_RAM_SIZE - 1024) @@ -119,7 +123,7 @@ usb_reboot_for_dfu_bootloader(void) static void check_usb_dfu_bootloader(void) { - if (!CONFIG_USBSERIAL || *(uint64_t*)USB_BOOT_FLAG_ADDR != USB_BOOT_FLAG) + if (!CONFIG_USB || *(uint64_t*)USB_BOOT_FLAG_ADDR != USB_BOOT_FLAG) return; *(uint64_t*)USB_BOOT_FLAG_ADDR = 0; uint32_t *sysbase = (uint32_t*)0x1fff0000; @@ -129,8 +133,9 @@ check_usb_dfu_bootloader(void) // Handle USB reboot requests void -usb_request_bootloader(void) +bootloader_request(void) { + try_request_canboot(); usb_reboot_for_dfu_bootloader(); } @@ -143,7 +148,6 @@ usb_request_bootloader(void) void armcm_main(void) { - check_usb_dfu_bootloader(); SCB->VTOR = (uint32_t)VectorTable; // Reset clock registers (in case bootloader has changed them) @@ -160,6 +164,8 @@ armcm_main(void) RCC->APBENR1 = 0x00000000; RCC->APBENR2 = 0x00000000; + check_usb_dfu_bootloader(); + // Set flash latency FLASH->ACR = (2<APB2ENR, .rst=&RCC->APB2RSTR, .bit=bit}; } else { - uint32_t bit = 1 << ((periph_base - D2_APB1PERIPH_BASE) / 0x400); - return (struct cline){.en=&RCC->APB1LENR,.rst=&RCC->APB1LRSTR,.bit=bit}; + uint32_t offset = ((periph_base - D2_APB1PERIPH_BASE) / 0x400); + if (offset < 32) { + uint32_t bit = 1 << offset; + return (struct cline){ + .en=&RCC->APB1LENR, .rst=&RCC->APB1LRSTR, .bit=bit}; + } else { + uint32_t bit = 1 << (offset - 32); + return (struct cline){ + .en=&RCC->APB1HENR, .rst=&RCC->APB1HRSTR, .bit=bit}; + } } } @@ -167,8 +178,11 @@ clock_setup(void) while ((RCC->CFGR & RCC_CFGR_SWS_Msk) != RCC_CFGR_SWS_PLL1) ; + // Set the source of FDCAN clock + MODIFY_REG(RCC->D2CCIP1R, RCC_D2CCIP1R_FDCANSEL, RCC_D2CCIP1R_FDCANSEL_0); + // Configure HSI48 clock for USB - if (CONFIG_USBSERIAL) { + if (CONFIG_USB) { SET_BIT(RCC->CR, RCC_CR_HSI48ON); while((RCC->CR & RCC_CR_HSI48RDY) == 0); SET_BIT(RCC->APB1HENR, RCC_APB1HENR_CRSEN); @@ -183,13 +197,39 @@ clock_setup(void) /**************************************************************** - * USB bootloader + * Bootloader ****************************************************************/ -// Handle USB reboot requests -void -usb_request_bootloader(void) +#define USB_BOOT_FLAG_ADDR (CONFIG_RAM_START + CONFIG_RAM_SIZE - 1024) +#define USB_BOOT_FLAG 0x55534220424f4f54 // "USB BOOT" + +// Flag that bootloader is desired and reboot +static void +usb_reboot_for_dfu_bootloader(void) { + irq_disable(); + *(uint64_t*)USB_BOOT_FLAG_ADDR = USB_BOOT_FLAG; + NVIC_SystemReset(); +} + +// Check if rebooting into system DFU Bootloader +static void +check_usb_dfu_bootloader(void) +{ + if (!CONFIG_USB || *(uint64_t*)USB_BOOT_FLAG_ADDR != USB_BOOT_FLAG) + return; + *(uint64_t*)USB_BOOT_FLAG_ADDR = 0; + uint32_t *sysbase = (uint32_t*)0x1FF09800; + asm volatile("mov sp, %0\n bx %1" + : : "r"(sysbase[0]), "r"(sysbase[1])); +} + +// Handle reboot requests +void +bootloader_request(void) +{ + try_request_canboot(); + usb_reboot_for_dfu_bootloader(); } @@ -203,8 +243,14 @@ armcm_main(void) { // Run SystemInit() and then restore VTOR SystemInit(); + RCC->D1CCIPR = 0x00000000; + RCC->D2CCIP1R = 0x00000000; + RCC->D2CCIP2R = 0x00000000; + RCC->D3CCIPR = 0x00000000; SCB->VTOR = (uint32_t)VectorTable; + check_usb_dfu_bootloader(); + clock_setup(); sched_main(); diff --git a/src/stm32/stm32h7_adc.c b/src/stm32/stm32h7_adc.c index 2733b24f..00be6209 100644 --- a/src/stm32/stm32h7_adc.c +++ b/src/stm32/stm32h7_adc.c @@ -22,6 +22,9 @@ #define ADC_ISR_LDORDY_Msk (0x1UL << ADC_ISR_LDORDY_Pos) #define ADC_ISR_LDORDY ADC_ISR_LDORDY_Msk +#define ADC_TEMPERATURE_PIN 0xfe +DECL_ENUMERATION("pin", "ADC_TEMPERATURE", ADC_TEMPERATURE_PIN); + DECL_CONSTANT("ADC_MAX", 4095); // GPIOs like A0_C are not covered! @@ -88,7 +91,7 @@ static const uint8_t adc_pins[] = { GPIO('H', 4), // ADC3_INP15 GPIO('H', 5), // ADC3_INP16 0, // Vbat/4 - 0, // VSENSE + ADC_TEMPERATURE_PIN,// VSENSE 0, // VREFINT }; @@ -185,7 +188,13 @@ gpio_adc_setup(uint32_t pin) MODIFY_REG(adc->CFGR2, ADC_CFGR2_OVSS_Msk, OVERSAMPLES_EXPONENT << ADC_CFGR2_OVSS_Pos); } - gpio_peripheral(pin, GPIO_ANALOG, 0); + + if (pin == ADC_TEMPERATURE_PIN) { + ADC3_COMMON->CCR |= ADC_CCR_TSEN; + } else { + gpio_peripheral(pin, GPIO_ANALOG, 0); + } + // Preselect (connect) channel adc->PCSEL |= (1 << chan); return (struct gpio_adc){ .adc = adc, .chan = chan }; diff --git a/test/klippy/exclude_object.cfg b/test/klippy/exclude_object.cfg new file mode 100644 index 00000000..54041fa2 --- /dev/null +++ b/test/klippy/exclude_object.cfg @@ -0,0 +1,117 @@ +[stepper_x] +step_pin: PF0 +dir_pin: PF1 +enable_pin: !PD7 +microsteps: 16 +rotation_distance: 40 +endstop_pin: ^PE5 +position_endstop: 0 +position_max: 200 +homing_speed: 50 + +[stepper_y] +step_pin: PF6 +dir_pin: !PF7 +enable_pin: !PF2 +microsteps: 16 +rotation_distance: 40 +endstop_pin: ^PJ1 +position_endstop: 0 +position_max: 200 +homing_speed: 50 + +[stepper_z] +step_pin: PL3 +dir_pin: PL1 +enable_pin: !PK0 +microsteps: 16 +rotation_distance: 8 +endstop_pin: ^PD3 +position_endstop: 0.5 +position_max: 200 + +[extruder] +step_pin: PA4 +dir_pin: PA6 +enable_pin: !PA2 +microsteps: 16 +rotation_distance: 33.5 +nozzle_diameter: 0.500 +filament_diameter: 3.500 +heater_pin: PB4 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PK5 +control: pid +pid_Kp: 22.2 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 210 + +[heater_bed] +heater_pin: PH5 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PK6 +control: watermark +min_temp: 0 +max_temp: 110 + +[mcu] +serial: /dev/ttyACM0 + +[printer] +kinematics: cartesian +max_velocity: 300 +max_accel: 3000 +max_z_velocity: 5 +max_z_accel: 100 + +# Test config for exclude_object +[exclude_object] + +[gcode_macro M486] +gcode: + # Parameters known to M486 are as follows: + # [C] Cancel the current object + # [P] Cancel the object with the given index + # [S] Set the index of the current object. + # If the object with the given index has been canceled, this will cause + # the firmware to skip to the next object. The value -1 is used to + # indicate something that isn’t an object and shouldn’t be skipped. + # [T] Reset the state and set the number of objects + # [U] Un-cancel the object with the given index. This command will be + # ignored if the object has already been skipped + + {% if 'exclude_object' not in printer %} + {action_raise_error("[exclude_object] is not enabled")} + {% endif %} + + {% if 'T' in params %} + EXCLUDE_OBJECT RESET=1 + + {% for i in range(params.T | int) %} + EXCLUDE_OBJECT_DEFINE NAME={i} + {% endfor %} + {% endif %} + + {% if 'C' in params %} + EXCLUDE_OBJECT CURRENT=1 + {% endif %} + + {% if 'P' in params %} + EXCLUDE_OBJECT NAME={params.P} + {% endif %} + + {% if 'S' in params %} + {% if params.S == '-1' %} + {% if printer.exclude_object.current_object %} + EXCLUDE_OBJECT_END NAME={printer.exclude_object.current_object} + {% endif %} + {% else %} + EXCLUDE_OBJECT_START NAME={params.S} + {% endif %} + {% endif %} + + {% if 'U' in params %} + EXCLUDE_OBJECT RESET=1 NAME={params.U} + {% endif %} diff --git a/test/klippy/exclude_object.test b/test/klippy/exclude_object.test new file mode 100644 index 00000000..9abcc45e --- /dev/null +++ b/test/klippy/exclude_object.test @@ -0,0 +1,126 @@ +DICTIONARY atmega2560.dict +CONFIG exclude_object.cfg + + +G28 +M83 + +M486 T3 + +M486 S0 + G0 X10 + +M486 S-1 + G0 X0 + +M486 S1 + G0 X11 +M486 C + +# "Prime" the transform +G1 X140 E0.5 +G1 X160 E0.5 +G1 X140 E0.5 +G1 X160 E0.5 +G1 X140 E0.5 +G1 X160 E0.5 +G1 X140 E0.5 + +M486 S-1 + G0 X0 + +M486 S2 + G0 X13 + +M486 S0 + G0 X10 + +M486 S-1 + G0 X0 + +M486 S1 + G0 X-11 + +M486 S-1 + G0 X0 + +M486 S2 + G0 X13 + +M486 P2 +EXCLUDE_OBJECT + +M486 S0 + G0 X10 + +M486 S-1 + G0 X0 + +M486 S1 + G0 X-11 + +M486 S-1 + G0 X0 + +M486 S2 + G0 X-13 + +M486 U2 +EXCLUDE_OBJECT + +M486 S0 + G0 X10 + +M486 S-1 + G0 X0 + +M486 S1 + G0 X-11 + +M486 S-1 + G0 X0 + +M486 S2 + G0 X13 + +M486 P0 +M486 P1 +M486 P2 +EXCLUDE_OBJECT + +M486 S0 + G0 X-10 + +M486 S-1 + G0 X0 + +M486 S1 + G0 X-11 + +M486 S-1 + G0 X0 + +M486 S2 + G0 X-13 + + +M486 S66 + G0 X66 + +M486 S-1 + G0 X0 + M486 P66 + +M486 S66 + G0 X-66 + +M486 T3 + +M486 S0 + G0 X10 + +M486 S1 + G0 X11 + +M486 S2 + G0 X13 diff --git a/test/klippy/input_shaper.cfg b/test/klippy/input_shaper.cfg index fcfc7fd1..bca94eb7 100644 --- a/test/klippy/input_shaper.cfg +++ b/test/klippy/input_shaper.cfg @@ -77,6 +77,9 @@ shaper_freq_x: 39.3 cs_pin: PK7 axes_map: -x,-y,z +[mpu9250 my_mpu] + [resonance_tester] probe_points: 20,20,20 -accel_chip: adxl345 +accel_chip_x: adxl345 +accel_chip_y: mpu9250 my_mpu diff --git a/test/klippy/linuxtest.cfg b/test/klippy/linuxtest.cfg new file mode 100644 index 00000000..67737239 --- /dev/null +++ b/test/klippy/linuxtest.cfg @@ -0,0 +1,15 @@ +# Test config for linux process specific hardware +[mcu] +serial: /tmp/klipper_host_mcu + +[printer] +kinematics: none +max_velocity: 300 +max_accel: 3000 + +[temperature_sensor my_ds18b20] +min_temp: 0 +max_temp: 100 +serial_no: 12345678 +sensor_mcu: mcu +sensor_type: DS18B20 diff --git a/test/klippy/linuxtest.test b/test/klippy/linuxtest.test new file mode 100644 index 00000000..913ea61f --- /dev/null +++ b/test/klippy/linuxtest.test @@ -0,0 +1,5 @@ +# Tests for various temperature sensors +DICTIONARY linuxprocess.dict +CONFIG linuxtest.cfg + +G4 P1000 diff --git a/test/klippy/printers.test b/test/klippy/printers.test index 6fcb6704..e5ab8fc1 100644 --- a/test/klippy/printers.test +++ b/test/klippy/printers.test @@ -29,6 +29,8 @@ CONFIG ../../config/printer-anycubic-4maxpro-2.0-2021.cfg CONFIG ../../config/printer-anycubic-i3-mega-2017.cfg CONFIG ../../config/printer-anycubic-kossel-2016.cfg CONFIG ../../config/printer-anycubic-kossel-plus-2017.cfg +CONFIG ../../config/printer-bq-hephestos-2014.cfg +CONFIG ../../config/printer-creality-cr10-v3-2020.cfg CONFIG ../../config/printer-creality-cr10s-2017.cfg CONFIG ../../config/printer-creality-cr20-2018.cfg CONFIG ../../config/printer-creality-cr20-pro-2019.cfg @@ -134,13 +136,13 @@ CONFIG ../../config/printer-monoprice-select-mini-v2-2018.cfg # Printers using the stm32f103 DICTIONARY stm32f103.dict +CONFIG ../../config/generic-bigtreetech-skr-cr6-v1.0.cfg +CONFIG ../../config/generic-bigtreetech-skr-e3-dip.cfg CONFIG ../../config/generic-bigtreetech-skr-mini.cfg CONFIG ../../config/generic-bigtreetech-skr-mini-e3-v1.0.cfg CONFIG ../../config/generic-bigtreetech-skr-mini-e3-v1.2.cfg CONFIG ../../config/generic-bigtreetech-skr-mini-e3-v2.0.cfg CONFIG ../../config/generic-bigtreetech-skr-mini-mz.cfg -CONFIG ../../config/generic-bigtreetech-skr-cr6-v1.0.cfg -CONFIG ../../config/generic-bigtreetech-skr-e3-dip.cfg CONFIG ../../config/printer-anycubic-vyper-2021.cfg CONFIG ../../config/printer-monoprice-select-mini-v1-2016.cfg @@ -159,12 +161,14 @@ CONFIG ../../config/printer-creality-cr30-2021.cfg CONFIG ../../config/printer-creality-cr6se-2020.cfg CONFIG ../../config/printer-creality-cr6se-2021.cfg CONFIG ../../config/printer-creality-ender2pro-2021.cfg +CONFIG ../../config/printer-creality-ender3-s1-2021.cfg CONFIG ../../config/printer-creality-ender3-v2-2020.cfg CONFIG ../../config/printer-creality-ender3max-2021.cfg CONFIG ../../config/printer-creality-ender3pro-2020.cfg CONFIG ../../config/printer-creality-ender5pro-2020.cfg CONFIG ../../config/printer-creality-ender6-2020.cfg CONFIG ../../config/printer-creality-sermoonD1-2021.cfg +CONFIG ../../config/printer-creality-sermoonV1-2022.cfg CONFIG ../../config/printer-elegoo-neptune2-2021.cfg CONFIG ../../config/printer-eryone-er20-2021.cfg CONFIG ../../config/printer-flsun-q5-2020.cfg @@ -194,6 +198,7 @@ CONFIG ../../config/generic-mellow-super-infinty-hv.cfg CONFIG ../../config/generic-mks-robin-nano-v3.cfg CONFIG ../../config/generic-prusa-buddy.cfg CONFIG ../../config/generic-th3d-ezboard-lite-v2.0.cfg +CONFIG ../../config/printer-biqu-b1-se-plus-2022.cfg CONFIG ../../config/printer-prusa-mini-plus-2020.cfg # Printers using the stm32f446