From f1c6f76a51dc2b11ab3cdc3fda97f8b9a5539993 Mon Sep 17 00:00:00 2001 From: camerony Date: Mon, 23 May 2022 12:05:53 -0700 Subject: [PATCH 001/138] docs: Update Config_Reference.md z_hop speed (#5514) The default z-hop speed is actually 15 mm/s according to the code in safe_z_home.py Signed-off-by: Cameron River --- docs/Config_Reference.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 3e2a53ae..aecf4b9d 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. From 00934e1378d408ed7abcb67705b8e417bd16c723 Mon Sep 17 00:00:00 2001 From: Fisheiyy <54205282+Fisheiyy@users.noreply.github.com> Date: Mon, 23 May 2022 14:23:22 -0500 Subject: [PATCH 002/138] config: Ender 3 S1/S1 Pro Default Configurations (#5332) Signed-off-by: Rob Casper --- config/printer-creality-ender3-s1-2021.cfg | 132 ++++++++++++++++++ config/printer-creality-ender3-s1pro-2022.cfg | 132 ++++++++++++++++++ test/klippy/printers.test | 2 + 3 files changed, 266 insertions(+) create mode 100644 config/printer-creality-ender3-s1-2021.cfg create mode 100644 config/printer-creality-ender3-s1pro-2022.cfg diff --git a/config/printer-creality-ender3-s1-2021.cfg b/config/printer-creality-ender3-s1-2021.cfg new file mode 100644 index 00000000..ab799e19 --- /dev/null +++ b/config/printer-creality-ender3-s1-2021.cfg @@ -0,0 +1,132 @@ +# This file contains pin mappings for the stock 2021 Creality Ender 3 +# S1. 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 USB PA10/PA9) communication for both depending on the +# STM32 Chip installed on your printers Motherboard. + +# If you prefer a direct serial connection, in "make menuconfig" +# select "Enable extra low-level configuration options" and select +# serial (on USB PA10/PA9), 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: 3.5:1 +rotation_distance: 26.3585 +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 +heater: extruder +heater_temp: 50.0 + +[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-ender3-s1pro-2022.cfg b/config/printer-creality-ender3-s1pro-2022.cfg new file mode 100644 index 00000000..17caae9f --- /dev/null +++ b/config/printer-creality-ender3-s1pro-2022.cfg @@ -0,0 +1,132 @@ +# This file contains pin mappings for the stock 2022 Creality Ender 3 +# 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 USB PA10/PA9) communication for both depending on the +# STM32 Chip installed on your printers Motherboard. + +# If you prefer a direct serial connection, in "make menuconfig" +# select "Enable extra low-level configuration options" and select +# serial (on USB PA10/PA9), 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: 3.5:1 +rotation_distance: 26.3585 +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: 300 + +[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 +heater: extruder +heater_temp: 50.0 + +[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/test/klippy/printers.test b/test/klippy/printers.test index 6fcb6704..7c25009a 100644 --- a/test/klippy/printers.test +++ b/test/klippy/printers.test @@ -159,6 +159,8 @@ 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-s1pro-2022.cfg CONFIG ../../config/printer-creality-ender3-v2-2020.cfg CONFIG ../../config/printer-creality-ender3max-2021.cfg CONFIG ../../config/printer-creality-ender3pro-2020.cfg From 5a94764c3889a262313723d477189146692c0d67 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 23 May 2022 15:27:17 -0400 Subject: [PATCH 003/138] config: Minor changes to ender3-s1 and ender3-s1pro configs Signed-off-by: Kevin O'Connor --- config/printer-creality-ender3-s1-2021.cfg | 5 +---- config/printer-creality-ender3-s1pro-2022.cfg | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/config/printer-creality-ender3-s1-2021.cfg b/config/printer-creality-ender3-s1-2021.cfg index ab799e19..13fde6db 100644 --- a/config/printer-creality-ender3-s1-2021.cfg +++ b/config/printer-creality-ender3-s1-2021.cfg @@ -56,8 +56,7 @@ step_pin: PB4 dir_pin: PB3 enable_pin: !PC3 microsteps: 16 -gear_ratio: 3.5:1 -rotation_distance: 26.3585 +rotation_distance: 7.531 nozzle_diameter: 0.400 filament_diameter: 1.750 heater_pin: PA1 @@ -83,8 +82,6 @@ max_temp: 110 [heater_fan hotend_fan] pin: PC0 -heater: extruder -heater_temp: 50.0 [fan] pin: PA0 diff --git a/config/printer-creality-ender3-s1pro-2022.cfg b/config/printer-creality-ender3-s1pro-2022.cfg index 17caae9f..393cbb5d 100644 --- a/config/printer-creality-ender3-s1pro-2022.cfg +++ b/config/printer-creality-ender3-s1pro-2022.cfg @@ -56,8 +56,7 @@ step_pin: PB4 dir_pin: PB3 enable_pin: !PC3 microsteps: 16 -gear_ratio: 3.5:1 -rotation_distance: 26.3585 +rotation_distance: 7.531 nozzle_diameter: 0.400 filament_diameter: 1.750 heater_pin: PA1 @@ -83,8 +82,6 @@ max_temp: 110 [heater_fan hotend_fan] pin: PC0 -heater: extruder -heater_temp: 50.0 [fan] pin: PA0 From 19a478de3763a80bf77c61e6328b3f9270b4f5fd Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 23 May 2022 15:35:05 -0400 Subject: [PATCH 004/138] stm32: Don't allow USB on internal clock for stm32f103/stm32f070 Reported by @kaidegit. Signed-off-by: Kevin O'Connor --- src/stm32/Kconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index d97246c0..bd613ad1 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -93,7 +93,8 @@ 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 From 3081899883d465be58b17ca50b55070bd1690496 Mon Sep 17 00:00:00 2001 From: Kevin Nguyen Date: Mon, 23 May 2022 15:49:59 -0400 Subject: [PATCH 005/138] docs: Documentation on screws_tilt_calculate MAX_DEVIATION parameter (#5522) Signed-off-by: Kevin Nguyen --- docs/G-Codes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/G-Codes.md b/docs/G-Codes.md index 5e562da4..7d6313d5 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -979,7 +979,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 +987,7 @@ 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] From c7e0372c5de4680760721674dc86959dc5d239ec Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 23 May 2022 15:50:43 -0400 Subject: [PATCH 006/138] docs: Line wrapping in G-Codes.md Signed-off-by: Kevin O'Connor --- docs/G-Codes.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/G-Codes.md b/docs/G-Codes.md index 7d6313d5..4dcbe729 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -987,7 +987,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. 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. +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] From af38d708cb3fad993422b8cf6bf4880acda568a3 Mon Sep 17 00:00:00 2001 From: Mikkel Schmidt Date: Tue, 24 May 2022 01:56:58 +0200 Subject: [PATCH 007/138] adxl345: Support recording data from multiple ADXL345's in one run, and more. (#5224) Add PROBE and CHIP to TEST_RESONANCES Since it's possible to specify more than one chip in TEST_RESONANCES the CHIP parameter has been renamed to CHIPS Signed-off-by: Mikkel Schmidt --- docs/G-Codes.md | 19 +++++--- klippy/extras/resonance_tester.py | 77 ++++++++++++++++++++++++------- 2 files changed, 72 insertions(+), 24 deletions(-) diff --git a/docs/G-Codes.md b/docs/G-Codes.md index 4dcbe729..eedb84cc 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -896,23 +896,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. 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 From 1ff72612033de0f4b857d960358c88eaa1046e4b Mon Sep 17 00:00:00 2001 From: BIGTREETECH <38851044+bigtreetech@users.noreply.github.com> Date: Wed, 1 Jun 2022 22:43:45 +0800 Subject: [PATCH 008/138] stm32: stm32g0b1 fdcan support (#5488) Signed-off-by: Alan.Ma from BigTreeTech --- src/stm32/Kconfig | 11 +- src/stm32/Makefile | 3 + src/stm32/can.c | 2 +- src/stm32/fdcan.c | 331 ++++++++++++++++++++++++++++++++++++++++++++ src/stm32/stm32g0.c | 2 + 5 files changed, 347 insertions(+), 2 deletions(-) create mode 100755 src/stm32/fdcan.c diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index bd613ad1..09652673 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -101,6 +101,9 @@ config HAVE_STM32_USBOTG 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 config MCU string @@ -275,6 +278,8 @@ config SERIAL bool config CANSERIAL bool +config FDCANSERIAL + bool choice prompt "Communication interface" config STM32_USB_PA11_PA12 @@ -342,10 +347,14 @@ choice bool "CAN bus (on PD0/PD1)" if LOW_LEVEL_OPTIONS depends on HAVE_STM32_CANBUS select CANSERIAL + config STM32_CANBUS_PB0_PB1 + bool "CAN bus (on PB0/PB1)" if LOW_LEVEL_OPTIONS + depends on HAVE_STM32_FDCANBUS + select FDCANSERIAL endchoice config CANBUS_FREQUENCY - int "CAN bus speed" if LOW_LEVEL_OPTIONS && CANSERIAL + int "CAN bus speed" if LOW_LEVEL_OPTIONS && (CANSERIAL || FDCANSERIAL) default 500000 endif diff --git a/src/stm32/Makefile b/src/stm32/Makefile index 793630a9..9d72c5a4 100644 --- a/src/stm32/Makefile +++ b/src/stm32/Makefile @@ -61,9 +61,12 @@ serial-src-$(CONFIG_MACH_STM32H7) := stm32/stm32h7_serial.c src-$(CONFIG_SERIAL) += $(serial-src-y) generic/serial_irq.c src-$(CONFIG_CANSERIAL) += stm32/can.c ../lib/fast-hash/fasthash.c src-$(CONFIG_CANSERIAL) += generic/canbus.c +src-$(CONFIG_FDCANSERIAL) += stm32/fdcan.c ../lib/fast-hash/fasthash.c +src-$(CONFIG_FDCANSERIAL) += generic/canbus.c src-$(CONFIG_HAVE_GPIO_HARD_PWM) += stm32/hard_pwm.c dirs-$(CONFIG_CANSERIAL) += lib/fast-hash +dirs-$(CONFIG_FDCANSERIAL) += lib/fast-hash # Binary output file rules target-y += $(OUT)klipper.bin diff --git a/src/stm32/can.c b/src/stm32/can.c index 613c1f27..ff88edb9 100644 --- a/src/stm32/can.c +++ b/src/stm32/can.c @@ -252,7 +252,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--) { diff --git a/src/stm32/fdcan.c b/src/stm32/fdcan.c new file mode 100755 index 00000000..63b1659e --- /dev/null +++ b/src/stm32/fdcan.c @@ -0,0 +1,331 @@ +// 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 "fasthash.h" // fasthash64 +#include "generic/armcm_boot.h" // armcm_enable_irq +#include "generic/canbus.h" // canbus_notify_tx +#include "generic/serial_irq.h" // serial_rx_byte +#include "internal.h" // enable_pclock +#include "sched.h" // DECL_INIT + +/* + FDCAN max date length = 64bytes + data_len[] is the data length & DLC mapping table + Required when the data length exceeds 64bytes + */ +uint8_t data_len[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64}; + +typedef struct +{ + uint32_t RESERVED0 : 18; + __IO uint32_t ID : 11; + __IO uint32_t RTR : 1; + __IO uint32_t XTD : 1; + __IO uint32_t ESI : 1; + __IO uint32_t RXTS : 16; + __IO uint32_t DLC : 4; + __IO uint32_t BRS : 1; + __IO uint32_t FDF : 1; + uint32_t RESERVED1 : 2; + __IO uint32_t FIDX : 7; + __IO uint32_t ANMF : 1; + __IO uint8_t data[64]; +}FDCAN_RX_FIFO_TypeDef; + +typedef struct +{ + __IO uint32_t id_section; + __IO uint32_t dlc_section; + __IO uint32_t data[64 / 4]; +}FDCAN_TX_FIFO_TypeDef; + +typedef struct +{ + __IO uint32_t FLS[28]; // Filter list standard + __IO uint32_t FLE[16]; // Filter list extended + FDCAN_RX_FIFO_TypeDef RXF0[3]; + FDCAN_RX_FIFO_TypeDef RXF1[3]; + __IO uint32_t TEF[6]; // Tx event FIFO + FDCAN_TX_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_RX_FIFO0 (FDCAN_IE_RF0NE | FDCAN_IE_RF0FE | FDCAN_IE_RF0LE) +#define FDCAN_IE_RX_FIFO1 (FDCAN_IE_RF1NE | FDCAN_IE_RF1FE | FDCAN_IE_RF1LE) +#define FDCAN_IE_TC (FDCAN_IE_TCE | FDCAN_IE_TCFE | FDCAN_IE_TFEE) + +#if 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) +#endif + +#if CONFIG_MACH_STM32G0 + #if CONFIG_STM32_CANBUS_PB0_PB1 + #define SOC_CAN FDCAN2 + #define MSG_RAM fdcan_ram->fdcan2 + #else + #error Uknown pins for STMF32G0 CAN + #endif + + #define CAN_IT0_IRQn TIM16_FDCAN_IT0_IRQn + #define CAN_IT1_IRQn TIM17_FDCAN_IT1_IRQn + #define CAN_FUNCTION GPIO_FUNCTION(3) // Alternative function mapping number +#endif + +#ifndef SOC_CAN + #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->RXF0S & FDCAN_RXF0S_F0FL)) { + // All rx mboxes empty, enable wake on rx IRQ + irq_disable(); + SOC_CAN->IE |= FDCAN_IE_RF0NE; + irq_enable(); + return -1; + } + + // Read and ack packet + uint32_t r_index = ((SOC_CAN->RXF0S & FDCAN_RXF0S_F0GI) + >> FDCAN_RXF0S_F0GI_Pos); + FDCAN_RX_FIFO_TypeDef *rxf0 = &MSG_RAM.RXF0[r_index]; + uint32_t dlc = rxf0->DLC; + *id = rxf0->ID; + for (uint8_t i = 0; i < dlc; i++) { + data[i] = rxf0->data[i]; + } + SOC_CAN->RXF0A = r_index; + + return dlc; +} + +// Transmit a packet +int +canbus_send(uint32_t id, uint32_t len, uint8_t *data) +{ + uint32_t txfqs = SOC_CAN->TXFQS; + if (txfqs & FDCAN_TXFQS_TFQF) { + // No space in transmit fifo - enable tx irq + irq_disable(); + SOC_CAN->IE |= FDCAN_IE_TC; + irq_enable(); + return -1; + } + + uint32_t w_index = ((txfqs & FDCAN_TXFQS_TFQPI) >> FDCAN_TXFQS_TFQPI_Pos); + FDCAN_TX_FIFO_TypeDef *txfifo = &MSG_RAM.TXFIFO[w_index]; + txfifo->id_section = id << 18; + txfifo->dlc_section = len << 16; + if (len) { + txfifo->data[0] = (((uint32_t)data[3] << 24) + | ((uint32_t)data[2] << 16) + | ((uint32_t)data[1] << 8) + | ((uint32_t)data[0] << 0)); + txfifo->data[1] = (((uint32_t)data[7] << 24) + | ((uint32_t)data[6] << 16) + | ((uint32_t)data[5] << 8) + | ((uint32_t)data[4] << 0)); + } + SOC_CAN->TXBAR = ((uint32_t)1 << w_index); + return len; +} + +void can_filter(uint32_t id, uint8_t index) +{ + 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) +{ + /* 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; + + can_filter(CANBUS_ID_ADMIN, 0); + + /* List size standard */ + SOC_CAN->RXGFC &= ~(FDCAN_RXGFC_LSS); + SOC_CAN->RXGFC |= 1 << FDCAN_RXGFC_LSS_Pos; + + /* Filter remote frames with 11-bit standard IDs + Non-matching frames standard reject or accept in Rx FIFO 1 */ + SOC_CAN->RXGFC &= ~(FDCAN_RXGFC_RRFS | FDCAN_RXGFC_ANFS); + SOC_CAN->RXGFC |= ((0 << FDCAN_RXGFC_RRFS_Pos) + | ((id ? 0x01 : 0x02) << FDCAN_RXGFC_ANFS_Pos)); + + /* 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; + uint32_t ie = SOC_CAN->IE; + + if (ir & FDCAN_IE_RX_FIFO1 && ie & FDCAN_IE_RX_FIFO1) { + SOC_CAN->IR = FDCAN_IE_RX_FIFO1; + + if (SOC_CAN->RXF1S & FDCAN_RXF1S_F1FL) { + // Read and ack data packet + uint32_t r_index = ((SOC_CAN->RXF1S & FDCAN_RXF1S_F1GI) + >> FDCAN_RXF1S_F1GI_Pos); + FDCAN_RX_FIFO_TypeDef *rxf1 = &MSG_RAM.RXF1[r_index]; + + uint32_t rir_id = rxf1->ID; + uint32_t dlc = rxf1->DLC; + uint8_t data[8]; + for (uint8_t i = 0; i < dlc; i++) { + data[i] = rxf1->data[i]; + } + SOC_CAN->RXF1A = r_index; + + // Process packet + canbus_process_data(rir_id, dlc, data); + } + } + if (ie & FDCAN_IE_RX_FIFO0 && ir & FDCAN_IE_RX_FIFO0) { + // Admin Rx + SOC_CAN->IR = FDCAN_IE_RX_FIFO0; + canbus_notify_rx(); + } + if (ie & FDCAN_IE_TC && 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; + + if (SOC_CAN == FDCAN1) + FDCAN_CONFIG->CKDIV = 0; + + /* Disable automatic retransmission */ + SOC_CAN->CCCR |= FDCAN_CCCR_DAR; + /* Disable protocol exception handling */ + SOC_CAN->CCCR |= FDCAN_CCCR_PXHD; + + SOC_CAN->NBTP = btr; + + /* 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); + if (CAN_IT0_IRQn != CAN_IT1_IRQn) + armcm_enable_irq(CAN_IRQHandler, CAN_IT1_IRQn, 0); + SOC_CAN->ILE |= 0x03; + SOC_CAN->IE |= FDCAN_IE_RX_FIFO0 | FDCAN_IE_RX_FIFO1; + + // Convert unique 96-bit chip id into 48 bit representation + uint64_t hash = fasthash64((uint8_t*)UID_BASE, 12, 0xA16231A7); + canbus_set_uuid(&hash); +} +DECL_INIT(can_init); diff --git a/src/stm32/stm32g0.c b/src/stm32/stm32g0.c index 6cb7c54c..4906f051 100644 --- a/src/stm32/stm32g0.c +++ b/src/stm32/stm32g0.c @@ -30,6 +30,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) From 907b47b2382e3267d8a5f0262af6beeafc9d02c1 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 26 May 2022 21:08:35 -0400 Subject: [PATCH 009/138] flash_usb: Rework flash_rp2040 code to be similar to other boards Signed-off-by: Kevin O'Connor --- scripts/flash_usb.py | 55 +++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/scripts/flash_usb.py b/scripts/flash_usb.py index 68e61570..df50915d 100755 --- a/scripts/flash_usb.py +++ b/scripts/flash_usb.py @@ -133,6 +133,32 @@ def flash_hidflash(device, binfile, sudo=True): pathname = wait_path(devpath) 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 +188,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 @@ -270,16 +280,9 @@ 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) - + 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) From 63affd7006fb5f4a3b14ac7d4f8cf12f42902c86 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 1 Jun 2022 10:59:10 -0400 Subject: [PATCH 010/138] stm32: Rework kconfig to use CONFIG_CANSERIAL for both can.c and fdcan.c Signed-off-by: Kevin O'Connor --- src/stm32/Kconfig | 8 +++----- src/stm32/Makefile | 13 +++++-------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index 09652673..62551dd8 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -278,8 +278,6 @@ config SERIAL bool config CANSERIAL bool -config FDCANSERIAL - bool choice prompt "Communication interface" config STM32_USB_PA11_PA12 @@ -348,13 +346,13 @@ choice depends on HAVE_STM32_CANBUS select CANSERIAL config STM32_CANBUS_PB0_PB1 - bool "CAN bus (on PB0/PB1)" if LOW_LEVEL_OPTIONS + bool "CAN bus (on PB0/PB1)" depends on HAVE_STM32_FDCANBUS - select FDCANSERIAL + select CANSERIAL endchoice config CANBUS_FREQUENCY - int "CAN bus speed" if LOW_LEVEL_OPTIONS && (CANSERIAL || FDCANSERIAL) + int "CAN bus speed" if LOW_LEVEL_OPTIONS && CANSERIAL default 500000 endif diff --git a/src/stm32/Makefile b/src/stm32/Makefile index 9d72c5a4..2f096369 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 @@ -59,15 +59,12 @@ serial-src-$(CONFIG_MACH_STM32F0) := stm32/stm32f0_serial.c serial-src-$(CONFIG_MACH_STM32G0) := stm32/stm32f0_serial.c serial-src-$(CONFIG_MACH_STM32H7) := stm32/stm32h7_serial.c src-$(CONFIG_SERIAL) += $(serial-src-y) generic/serial_irq.c -src-$(CONFIG_CANSERIAL) += stm32/can.c ../lib/fast-hash/fasthash.c -src-$(CONFIG_CANSERIAL) += generic/canbus.c -src-$(CONFIG_FDCANSERIAL) += stm32/fdcan.c ../lib/fast-hash/fasthash.c -src-$(CONFIG_FDCANSERIAL) += generic/canbus.c +canbus-src-y := generic/canbus.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) src-$(CONFIG_HAVE_GPIO_HARD_PWM) += stm32/hard_pwm.c -dirs-$(CONFIG_CANSERIAL) += lib/fast-hash -dirs-$(CONFIG_FDCANSERIAL) += lib/fast-hash - # Binary output file rules target-y += $(OUT)klipper.bin From dbc24ce339df0a4c3dae98f9f4e1399e12493f5a Mon Sep 17 00:00:00 2001 From: BIGTREETECH <38851044+bigtreetech@users.noreply.github.com> Date: Wed, 1 Jun 2022 23:28:10 +0800 Subject: [PATCH 011/138] config: Add BTT SKR 3 & EBB CAN V1.0 & V1.1 board cfg (#5529) Signed-off-by: Alan.Ma from BigTreeTech --- config/generic-bigtreetech-skr-3.cfg | 183 ++++++++++++++++++ config/sample-bigtreetech-ebb-canbus-v1.0.cfg | 66 +++++++ config/sample-bigtreetech-ebb-canbus-v1.1.cfg | 68 +++++++ 3 files changed, 317 insertions(+) create mode 100644 config/generic-bigtreetech-skr-3.cfg create mode 100644 config/sample-bigtreetech-ebb-canbus-v1.0.cfg create mode 100644 config/sample-bigtreetech-ebb-canbus-v1.1.cfg diff --git a/config/generic-bigtreetech-skr-3.cfg b/config/generic-bigtreetech-skr-3.cfg new file mode 100644 index 00000000..4b44a4d7 --- /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, the firmware should be compiled for the +# STM32H743 with a "128KiB bootloader". + +# 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/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 From c16eab212ee706ab8c0b52e3846e61030fc68606 Mon Sep 17 00:00:00 2001 From: Pedro Lamas Date: Sun, 15 May 2022 12:19:37 +0100 Subject: [PATCH 012/138] virtual_sdcard: Adds on_error_gcode Signed-off-by: Pedro Lamas --- docs/Config_Reference.md | 3 +++ klippy/extras/virtual_sdcard.py | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index aecf4b9d..c5347394 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -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] diff --git a/klippy/extras/virtual_sdcard.py b/klippy/extras/virtual_sdcard.py index 397c6513..870c099f 100644 --- a/klippy/extras/virtual_sdcard.py +++ b/klippy/extras/virtual_sdcard.py @@ -23,6 +23,10 @@ class VirtualSD: self.must_pause_work = self.cmd_from_sd = False self.next_file_position = 0 self.work_timer = None + # Error handling + gcode_macro = 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') for cmd in ['M20', 'M21', 'M23', 'M24', 'M25', 'M26', 'M27']: @@ -258,6 +262,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") From 06a31222d3ddc49c881b6dd634400d3344f62d0a Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 3 Jun 2022 11:28:44 -0400 Subject: [PATCH 013/138] COPYING: Update GPLv3 license to latest text (uses https instead of http) The latest text of the GNU GPLv3 license updates the web references to https (instead of the older http). Update to that latest text. Signed-off-by: Kevin O'Connor --- COPYING | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 -. +. From 742df16a517ce41d6332d1916b4849d70552bc23 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 3 Jun 2022 11:33:39 -0400 Subject: [PATCH 014/138] config: Update ender3 s1 configs to use gear_ratio The gearing mechanism is 42:12. Reported by @filipenobrerc. Signed-off-by: Kevin O'Connor --- config/printer-creality-ender3-s1-2021.cfg | 3 ++- config/printer-creality-ender3-s1pro-2022.cfg | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/config/printer-creality-ender3-s1-2021.cfg b/config/printer-creality-ender3-s1-2021.cfg index 13fde6db..33b77c7e 100644 --- a/config/printer-creality-ender3-s1-2021.cfg +++ b/config/printer-creality-ender3-s1-2021.cfg @@ -56,7 +56,8 @@ step_pin: PB4 dir_pin: PB3 enable_pin: !PC3 microsteps: 16 -rotation_distance: 7.531 +gear_ratio: 42:12 +rotation_distance: 26.359 nozzle_diameter: 0.400 filament_diameter: 1.750 heater_pin: PA1 diff --git a/config/printer-creality-ender3-s1pro-2022.cfg b/config/printer-creality-ender3-s1pro-2022.cfg index 393cbb5d..b742fbb5 100644 --- a/config/printer-creality-ender3-s1pro-2022.cfg +++ b/config/printer-creality-ender3-s1pro-2022.cfg @@ -56,7 +56,8 @@ step_pin: PB4 dir_pin: PB3 enable_pin: !PC3 microsteps: 16 -rotation_distance: 7.531 +gear_ratio: 42:12 +rotation_distance: 26.359 nozzle_diameter: 0.400 filament_diameter: 1.750 heater_pin: PA1 From 79d6b37ac9752f5b06e59cc7cf11c77e4f98f9bd Mon Sep 17 00:00:00 2001 From: Charles Pickering Date: Mon, 30 May 2022 14:57:27 -0700 Subject: [PATCH 015/138] config: Huvud KlipperToolhead config Pin information and basic flashing instructions for the Huvud CAN bus toolhead board. V0.61 Signed-off-by: Charles Pickering --- config/sample-huvud-v0_61.cfg | 58 +++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 config/sample-huvud-v0_61.cfg 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 From df39465534dc8d0056774baceab8a3ee2676904a Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 3 Jun 2022 11:43:34 -0400 Subject: [PATCH 016/138] config: Rename sample-huvud-v0.61.cfg Signed-off-by: Kevin O'Connor --- config/{sample-huvud-v0_61.cfg => sample-huvud-v0.61.cfg} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename config/{sample-huvud-v0_61.cfg => sample-huvud-v0.61.cfg} (100%) diff --git a/config/sample-huvud-v0_61.cfg b/config/sample-huvud-v0.61.cfg similarity index 100% rename from config/sample-huvud-v0_61.cfg rename to config/sample-huvud-v0.61.cfg From 2e04be4451515f4e9ee9719e372209dbdd08edac Mon Sep 17 00:00:00 2001 From: Troy Jacobson Date: Tue, 1 Mar 2022 07:52:14 -0700 Subject: [PATCH 017/138] virtual_sdcard: Add reset_file event Signed-off-by: Troy Jacobson Co-authored-by: Franklyn Tackitt --- klippy/extras/virtual_sdcard.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/klippy/extras/virtual_sdcard.py b/klippy/extras/virtual_sdcard.py index 870c099f..fb4c7d41 100644 --- a/klippy/extras/virtual_sdcard.py +++ b/klippy/extras/virtual_sdcard.py @@ -9,17 +9,18 @@ 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 @@ -28,7 +29,7 @@ class VirtualSD: 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']: @@ -129,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): From 04952db1e8806a5efe27771d568ce66e9eb126f9 Mon Sep 17 00:00:00 2001 From: Troy Jacobson Date: Tue, 1 Mar 2022 07:53:40 -0700 Subject: [PATCH 018/138] tuning_tower: add is_active() method Signed-off-by: Troy Jacobson Co-authored-by: Franklyn Tackitt --- klippy/extras/tuning_tower.py | 2 ++ 1 file changed, 2 insertions(+) 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) From 89c59b035e93e87d2fc22010d7030dc86434ce03 Mon Sep 17 00:00:00 2001 From: Frank Tackitt Date: Tue, 1 Mar 2022 13:04:08 -0700 Subject: [PATCH 019/138] exclude_objects: initial implementation Adding Klipper functionality to support cancelling objects while printing. This module keeps track of motion in and out of objects and adjusts movements as needed. It also tracks object status and provides that to clients. The Klipper module is relatively simple, and only provides one piece of the workflow. Moonraker already supports processing uploaded files to insert the required gcode markers for cancelling objects, using https://github.com/kageurufu/cancelobject-preprocessor. This library is also available as an executable for use in slicers, and pip installations also include the script as a callable. Mainsail has integrated support, and code changes for Fluidd are available. Support in other interfaces is planned, and we've spoken to several other developers about integrating frontend support in their projects. Signed-off-by: Troy Jacobson Co-authored-by: Franklyn Tackitt Co-authored-by: Eric Callahan --- klippy/extras/exclude_object.py | 302 ++++++++++++++++++++++++++++++++ test/klippy/exclude_object.cfg | 117 +++++++++++++ test/klippy/exclude_object.test | 126 +++++++++++++ 3 files changed, 545 insertions(+) create mode 100644 klippy/extras/exclude_object.py create mode 100644 test/klippy/exclude_object.cfg create mode 100644 test/klippy/exclude_object.test 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/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 From 638cd4d7810d017996438c2c993948af31b0a579 Mon Sep 17 00:00:00 2001 From: Frank Tackitt Date: Tue, 1 Mar 2022 13:12:35 -0700 Subject: [PATCH 020/138] docs: add exclude_object documentation Also include sample macros to add M486 compatibility. Signed-off-by: Franklyn Tackitt Co-authored-by: Troy Jacobson --- config/sample-macros.cfg | 52 ++++++++++++++++++++ docs/Config_Reference.md | 14 ++++++ docs/Exclude_Object.md | 99 ++++++++++++++++++++++++++++++++++++++ docs/G-Codes.md | 51 ++++++++++++++++++++ docs/Overview.md | 2 + docs/Status_Reference.md | 38 +++++++++++++++ docs/_klipper3d/mkdocs.yml | 1 + 7 files changed, 257 insertions(+) create mode 100644 docs/Exclude_Object.md diff --git a/config/sample-macros.cfg b/config/sample-macros.cfg index fcb144e0..97e39016 100644 --- a/config/sample-macros.cfg +++ b/config/sample-macros.cfg @@ -186,3 +186,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/Config_Reference.md b/docs/Config_Reference.md index c5347394..8d58e562 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -1435,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] diff --git a/docs/Exclude_Object.md b/docs/Exclude_Object.md new file mode 100644 index 00000000..d2681489 --- /dev/null +++ b/docs/Exclude_Object.md @@ -0,0 +1,99 @@ +# Exclude Obects + +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/G-Codes.md b/docs/G-Codes.md index eedb84cc..74757a48 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -320,6 +320,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 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/Status_Reference.md b/docs/Status_Reference.md index aac6fec0..a515901b 100644 --- a/docs/Status_Reference.md +++ b/docs/Status_Reference.md @@ -69,6 +69,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 diff --git a/docs/_klipper3d/mkdocs.yml b/docs/_klipper3d/mkdocs.yml index b2a59827..313f60c5 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 From 04eb72dcd5c0a1a3f223cfb47cb9dcf897ff0747 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 3 Jun 2022 14:23:56 -0400 Subject: [PATCH 021/138] virtual_sdcard: Fix merge error Signed-off-by: Kevin O'Connor --- klippy/extras/virtual_sdcard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/klippy/extras/virtual_sdcard.py b/klippy/extras/virtual_sdcard.py index fb4c7d41..daf19db9 100644 --- a/klippy/extras/virtual_sdcard.py +++ b/klippy/extras/virtual_sdcard.py @@ -25,7 +25,7 @@ class VirtualSD: self.next_file_position = 0 self.work_timer = None # Error handling - gcode_macro = printer.load_object(config, 'gcode_macro') + gcode_macro = self.printer.load_object(config, 'gcode_macro') self.on_error_gcode = gcode_macro.load_template( config, 'on_error_gcode', '') # Register commands From 8b1e3c3fb25e3d3fbec49158ba9dba9b07c9be00 Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Tue, 31 May 2022 06:51:25 -0400 Subject: [PATCH 022/138] armcm_reset: support canboot detection When CanBoot is detected set its bypass signature when a reset is requested. Add a "try_request_canboot()" method that may be called from from USB and Canbus bootloader requests. Signed-off-by: Eric Callahan --- src/generic/armcm_reset.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/generic/armcm_reset.c b/src/generic/armcm_reset.c index 19c2096d..b7aea955 100644 --- a/src/generic/armcm_reset.c +++ b/src/generic/armcm_reset.c @@ -6,10 +6,42 @@ #include "board/internal.h" // NVIC_SystemReset #include "command.h" // DECL_COMMAND_FLAGS +#include "autoconf.h" // CONFIG_FLASH_START +#include "irq.h" // irq_disable + +#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"); From 129091d81176c8a361802a19fc9ee6c567d2f558 Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Tue, 31 May 2022 06:55:13 -0400 Subject: [PATCH 023/138] canbus: use "try_request_canboot" method Signed-off-by: Eric Callahan --- src/generic/canbus.c | 16 +--------------- src/stm32/internal.h | 3 +++ 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/generic/canbus.c b/src/generic/canbus.c index a9bf3c8b..a1f112ac 100644 --- a/src/generic/canbus.c +++ b/src/generic/canbus.c @@ -95,10 +95,6 @@ console_sendf(const struct command_encoder *ce, va_list args) #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) @@ -166,17 +162,7 @@ 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(); - } + try_request_canboot(); } // Handle an "admin" command diff --git a/src/stm32/internal.h b/src/stm32/internal.h index f0535ab9..0033942d 100644 --- a/src/stm32/internal.h +++ b/src/stm32/internal.h @@ -42,4 +42,7 @@ struct cline lookup_clock_line(uint32_t periph_base); uint32_t get_pclock_frequency(uint32_t periph_base); void gpio_clock_enable(GPIO_TypeDef *regs); +// armcm_timer.c +void try_request_canboot(void); + #endif // internal.h From 3c7eea7336349475c03aa2fd186a5f73fc195d28 Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Tue, 31 May 2022 07:34:26 -0400 Subject: [PATCH 024/138] stm32: call "try_request_canboot" for bootloader requests Signed-off-by: Eric Callahan --- src/stm32/stm32f0.c | 1 + src/stm32/stm32f1.c | 1 + src/stm32/stm32f4.c | 1 + src/stm32/stm32g0.c | 1 + src/stm32/stm32h7.c | 1 + 5 files changed, 5 insertions(+) diff --git a/src/stm32/stm32f0.c b/src/stm32/stm32f0.c index 6b13d737..a751b546 100644 --- a/src/stm32/stm32f0.c +++ b/src/stm32/stm32f0.c @@ -163,6 +163,7 @@ check_usb_dfu_bootloader(void) void usb_request_bootloader(void) { + try_request_canboot(); usb_reboot_for_dfu_bootloader(); } diff --git a/src/stm32/stm32f1.c b/src/stm32/stm32f1.c index 526ec485..25bba83f 100644 --- a/src/stm32/stm32f1.c +++ b/src/stm32/stm32f1.c @@ -243,6 +243,7 @@ usb_stm32duino_bootloader(void) void usb_request_bootloader(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..58d8dbb4 100644 --- a/src/stm32/stm32f4.c +++ b/src/stm32/stm32f4.c @@ -231,6 +231,7 @@ check_usb_dfu_bootloader(void) void usb_request_bootloader(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 4906f051..a98cc811 100644 --- a/src/stm32/stm32g0.c +++ b/src/stm32/stm32g0.c @@ -133,6 +133,7 @@ check_usb_dfu_bootloader(void) void usb_request_bootloader(void) { + try_request_canboot(); usb_reboot_for_dfu_bootloader(); } diff --git a/src/stm32/stm32h7.c b/src/stm32/stm32h7.c index ebb71cfb..c615d286 100644 --- a/src/stm32/stm32h7.c +++ b/src/stm32/stm32h7.c @@ -190,6 +190,7 @@ clock_setup(void) void usb_request_bootloader(void) { + try_request_canboot(); } From 39535b15ba2492608a4d1b2854df4faee920da53 Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Fri, 3 Jun 2022 06:09:53 -0400 Subject: [PATCH 025/138] stm32: use TME bits to find the canbus tx mailbox Signed-off-by: Eric Callahan --- src/stm32/can.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/stm32/can.c b/src/stm32/can.c index ff88edb9..a82ad688 100644 --- a/src/stm32/can.c +++ b/src/stm32/can.c @@ -135,7 +135,11 @@ 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 */ From b829a89069e6de4014a10b47fbe0b5d19c7918fc Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Fri, 3 Jun 2022 08:27:03 -0400 Subject: [PATCH 026/138] flash_can: add usb support Signed-off-by: Eric Callahan --- lib/canboot/flash_can.py | 91 +++++++++++++++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 10 deletions(-) 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: From c43f62bdd6562866a78cdfa174ff53c9a76c54a0 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 3 Jun 2022 14:53:18 -0400 Subject: [PATCH 027/138] lib: Update lib/README with latest canboot changes Signed-off-by: Kevin O'Connor --- lib/README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/README b/lib/README index b4ae7cf1..3e243b9b 100644 --- a/lib/README +++ b/lib/README @@ -125,6 +125,6 @@ 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. From 7c8cf7661b49ef9298ff71ed67e24c4380511390 Mon Sep 17 00:00:00 2001 From: Stefan Dej Date: Fri, 3 Jun 2022 22:12:43 +0200 Subject: [PATCH 028/138] docs: fix typo in Exclude_Object.md Signed-off-by: Stefan Dej --- docs/Exclude_Object.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Exclude_Object.md b/docs/Exclude_Object.md index d2681489..5923afbe 100644 --- a/docs/Exclude_Object.md +++ b/docs/Exclude_Object.md @@ -1,4 +1,4 @@ -# Exclude Obects +# 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 From 9047702a00c716245c37dba72ddc0007c0b37f69 Mon Sep 17 00:00:00 2001 From: Yifei Ding Date: Sat, 21 May 2022 13:41:49 -0700 Subject: [PATCH 029/138] mkdocs: Replace TOC in Translations Signed-off-by: Yifei Ding --- docs/_klipper3d/build-translations.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) 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}/" From c1f4bdebf283ef8860179216fdc4d4d87e595213 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 4 Jun 2022 11:50:57 -0400 Subject: [PATCH 030/138] armcm_reset: Add a armcm_reset.h header file for try_request_canboot() Signed-off-by: Kevin O'Connor --- src/generic/armcm_reset.c | 5 +++-- src/generic/armcm_reset.h | 6 ++++++ src/generic/canbus.c | 8 ++++---- src/stm32/internal.h | 3 --- src/stm32/stm32f0.c | 1 + src/stm32/stm32f1.c | 1 + src/stm32/stm32f4.c | 1 + src/stm32/stm32g0.c | 1 + src/stm32/stm32h7.c | 1 + 9 files changed, 18 insertions(+), 9 deletions(-) create mode 100644 src/generic/armcm_reset.h diff --git a/src/generic/armcm_reset.c b/src/generic/armcm_reset.c index b7aea955..67ff5f57 100644 --- a/src/generic/armcm_reset.c +++ b/src/generic/armcm_reset.c @@ -4,10 +4,11 @@ // // 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 -#include "autoconf.h" // CONFIG_FLASH_START -#include "irq.h" // irq_disable #define CANBOOT_SIGNATURE 0x21746f6f426e6143 #define CANBOOT_REQUEST 0x5984E3FA6CA1589B 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 a1f112ac..a1a2f6b5 100644 --- a/src/generic/canbus.c +++ b/src/generic/canbus.c @@ -7,12 +7,12 @@ // This file may be distributed under the terms of the GNU GPLv3 license. #include // memcpy +#include "board/armcm_reset.h" // try_request_canboot +#include "board/io.h" // readb +#include "board/irq.h" // irq_save +#include "board/misc.h" // console_sendf #include "canbus.h" // canbus_set_uuid #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; diff --git a/src/stm32/internal.h b/src/stm32/internal.h index 0033942d..f0535ab9 100644 --- a/src/stm32/internal.h +++ b/src/stm32/internal.h @@ -42,7 +42,4 @@ struct cline lookup_clock_line(uint32_t periph_base); uint32_t get_pclock_frequency(uint32_t periph_base); void gpio_clock_enable(GPIO_TypeDef *regs); -// armcm_timer.c -void try_request_canboot(void); - #endif // internal.h diff --git a/src/stm32/stm32f0.c b/src/stm32/stm32f0.c index a751b546..8ea8f43c 100644 --- a/src/stm32/stm32f0.c +++ b/src/stm32/stm32f0.c @@ -6,6 +6,7 @@ #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 "command.h" // DECL_CONSTANT_STR #include "internal.h" // enable_pclock diff --git a/src/stm32/stm32f1.c b/src/stm32/stm32f1.c index 25bba83f..820a5518 100644 --- a/src/stm32/stm32f1.c +++ b/src/stm32/stm32f1.c @@ -6,6 +6,7 @@ #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 "internal.h" // enable_pclock diff --git a/src/stm32/stm32f4.c b/src/stm32/stm32f4.c index 58d8dbb4..9c6880cf 100644 --- a/src/stm32/stm32f4.c +++ b/src/stm32/stm32f4.c @@ -6,6 +6,7 @@ #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 "command.h" // DECL_CONSTANT_STR diff --git a/src/stm32/stm32g0.c b/src/stm32/stm32g0.c index a98cc811..f38fc9c5 100644 --- a/src/stm32/stm32g0.c +++ b/src/stm32/stm32g0.c @@ -6,6 +6,7 @@ #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 "command.h" // DECL_CONSTANT_STR #include "internal.h" // enable_pclock diff --git a/src/stm32/stm32h7.c b/src/stm32/stm32h7.c index c615d286..d6a32d96 100644 --- a/src/stm32/stm32h7.c +++ b/src/stm32/stm32h7.c @@ -6,6 +6,7 @@ #include "autoconf.h" // CONFIG_CLOCK_REF_FREQ #include "board/armcm_boot.h" // VectorTable +#include "board/armcm_reset.h" // try_request_canboot #include "command.h" // DECL_CONSTANT_STR #include "internal.h" // get_pclock_frequency #include "sched.h" // sched_main From db5a4351a5d580563fa4c0323997a4f48bfe120d Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 2 Jun 2022 12:43:19 -0400 Subject: [PATCH 031/138] Kconfig: Move CANBUS_FREQUENCY definition from src/stm32/Kconfig to src/Kconfig Signed-off-by: Kevin O'Connor --- src/Kconfig | 5 +++++ src/stm32/Kconfig | 4 ---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Kconfig b/src/Kconfig index ea2300ec..54c3497e 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -61,6 +61,11 @@ config USB_SERIAL_NUMBER_CHIPID config USB_SERIAL_NUMBER default "12345" +# Generic configuration options for CANbus +config CANBUS_FREQUENCY + int "CAN bus speed" if LOW_LEVEL_OPTIONS && CANSERIAL + default 500000 + menu "USB ids" depends on USBSERIAL && LOW_LEVEL_OPTIONS config USB_VENDOR_ID diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index 62551dd8..30c72414 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -351,8 +351,4 @@ choice select CANSERIAL endchoice -config CANBUS_FREQUENCY - int "CAN bus speed" if LOW_LEVEL_OPTIONS && CANSERIAL - default 500000 - endif From ada571ecb8528e57003e1f878e224ab230920b33 Mon Sep 17 00:00:00 2001 From: BIGTREETECH <38851044+bigtreetech@users.noreply.github.com> Date: Mon, 6 Jun 2022 22:59:18 +0800 Subject: [PATCH 032/138] stm32: Enable Automatic retransmission feature to avoid data loss caused by bus conflict for STM32G0B1 fdcan (#5550) Signed-off-by: Alan.Ma from BigTreeTech --- src/stm32/fdcan.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/stm32/fdcan.c b/src/stm32/fdcan.c index 63b1659e..aeb6eced 100755 --- a/src/stm32/fdcan.c +++ b/src/stm32/fdcan.c @@ -303,8 +303,6 @@ can_init(void) if (SOC_CAN == FDCAN1) FDCAN_CONFIG->CKDIV = 0; - /* Disable automatic retransmission */ - SOC_CAN->CCCR |= FDCAN_CCCR_DAR; /* Disable protocol exception handling */ SOC_CAN->CCCR |= FDCAN_CCCR_PXHD; From b0da191bee873d51590664e261a767a06391df13 Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Tue, 7 Jun 2022 11:54:54 -0400 Subject: [PATCH 033/138] makefile: disable null pointer checks Signed-off-by: Eric Callahan --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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)) From 2dc20c011dc0ce7f798ca543b96e93e5014b407b Mon Sep 17 00:00:00 2001 From: functionpointer Date: Sun, 5 Jun 2022 14:26:51 +0200 Subject: [PATCH 034/138] ds18b20: Allow some read errors Allows a limited number of DS18B20 read failures before stopping the printer. This is designed to tolerate spurious read errors, while still stopping for serious issues. The printer will stop when the sensor fails to report a value five times in a row. Implementation works as follows: The MCU reports any read errors using a new "fault" parameter in its answers. The Python code tracks the number of errors and triggers the shutdown. This paves the way for more sophisticated error handling in the future, as well as an example for other sensors to follow. Signed-off-by: Lorenzo Pfeifer --- klippy/extras/ds18b20.py | 7 +++-- src/linux/sensor_ds18b20.c | 55 +++++++++++++++++++++++--------------- 2 files changed, 38 insertions(+), 24 deletions(-) diff --git a/klippy/extras/ds18b20.py b/klippy/extras/ds18b20.py index 749ae520..76e38532 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): @@ -32,7 +33,7 @@ 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.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,7 +45,9 @@ class DS18B20: def _handle_ds18b20_response(self, params): temp = params['value'] / 1000.0 - if temp < self.min_temp or temp > self.max_temp: + if params["fault"] != 0: + temp = 0 # read error! report 0C and don't check temp range + elif 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)) 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 From d61d3ade23c493b4435ca6b3a4635471a26efe15 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 8 Jun 2022 12:46:07 -0400 Subject: [PATCH 035/138] ds18b20: Don't propagate incorrect temperature on a fault Just log an error on a fault. Remove the host check for min/max temperature as the micro-controller code already implements that check. Signed-off-by: Kevin O'Connor --- klippy/extras/ds18b20.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/klippy/extras/ds18b20.py b/klippy/extras/ds18b20.py index 76e38532..9e36dfe5 100644 --- a/klippy/extras/ds18b20.py +++ b/klippy/extras/ds18b20.py @@ -45,12 +45,10 @@ class DS18B20: def _handle_ds18b20_response(self, params): temp = params['value'] / 1000.0 - if params["fault"] != 0: - temp = 0 # read error! report 0C and don't check temp range - elif 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 From 33ecc6d62ee15afd921cce9ee37ca551f5d0b0ce Mon Sep 17 00:00:00 2001 From: functionpointer Date: Thu, 9 Jun 2022 00:05:27 +0200 Subject: [PATCH 036/138] ds18b20: fix string conversion error on startup (#5559) Signed-off-by: Lorenzo Pfeifer --- klippy/extras/ds18b20.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/klippy/extras/ds18b20.py b/klippy/extras/ds18b20.py index 9e36dfe5..37b30104 100644 --- a/klippy/extras/ds18b20.py +++ b/klippy/extras/ds18b20.py @@ -32,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, DS18_MAX_CONSECUTIVE_ERRORS)) + 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) From f42ce3e2fd6de3129e196507b7c862cb511f62de Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 8 Jun 2022 18:06:12 -0400 Subject: [PATCH 037/138] linuxtest: Add a test case for the ds18b20 sensor Signed-off-by: Kevin O'Connor --- test/klippy/linuxtest.cfg | 15 +++++++++++++++ test/klippy/linuxtest.test | 5 +++++ 2 files changed, 20 insertions(+) create mode 100644 test/klippy/linuxtest.cfg create mode 100644 test/klippy/linuxtest.test 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 From b6feda4eaed9ec20a1a077c2b734894bbf38a501 Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Mon, 6 Jun 2022 08:13:33 -0400 Subject: [PATCH 038/138] lpc176x: add support for canboot usb Signed-off-by: Eric Callahan --- src/lpc176x/usbserial.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lpc176x/usbserial.c b/src/lpc176x/usbserial.c index 95ce6e74..bbb90438 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 @@ -250,6 +251,7 @@ usb_request_bootloader(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); From 3e1719bdbbdca7c2eceea9aca8999d4b9908b0f2 Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Mon, 6 Jun 2022 11:53:28 -0400 Subject: [PATCH 039/138] flash_usb: add canboot support Signed-off-by: Eric Callahan --- scripts/flash_usb.py | 50 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/scripts/flash_usb.py b/scripts/flash_usb.py index df50915d..32f8be91 100755 --- a/scripts/flash_usb.py +++ b/scripts/flash_usb.py @@ -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,14 @@ 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): From 1b6ab6583301344711b79133cf71288ea85c5764 Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Thu, 9 Jun 2022 11:43:22 -0400 Subject: [PATCH 040/138] flash_usb: update shebang to python3 Signed-off-by: Eric Callahan --- scripts/flash_usb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/flash_usb.py b/scripts/flash_usb.py index 32f8be91..b45afeff 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 From 7ffd01de4cf3c954c7e5bf130fe7fb07ac1becbf Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 8 Jun 2022 18:25:10 -0400 Subject: [PATCH 041/138] stm32: Support 4KiB bootloader on stm32f1 and stm32f0 The CanBoot bootloader can often fit in 4KiB and that may be useful for some devices with small flash sizes. Signed-off-by: Kevin O'Connor --- src/stm32/Kconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index 30c72414..bad86a8c 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -204,6 +204,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 @@ -215,6 +217,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 From c30e5f847cb1c1f30376a3282cd03c77da364cff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aart=20R=C3=B6st?= Date: Fri, 10 Jun 2022 11:13:10 -0700 Subject: [PATCH 042/138] config: Correct UART pins for Ender 3 S1 & S1 Pro (#5553) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aart Röst --- config/printer-creality-ender3-s1-2021.cfg | 2 +- config/printer-creality-ender3-s1pro-2022.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/printer-creality-ender3-s1-2021.cfg b/config/printer-creality-ender3-s1-2021.cfg index 33b77c7e..766dd0f8 100644 --- a/config/printer-creality-ender3-s1-2021.cfg +++ b/config/printer-creality-ender3-s1-2021.cfg @@ -7,7 +7,7 @@ # If you prefer a direct serial connection, in "make menuconfig" # select "Enable extra low-level configuration options" and select -# serial (on USB PA10/PA9), which is broken out on the 10 pin IDC +# 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 diff --git a/config/printer-creality-ender3-s1pro-2022.cfg b/config/printer-creality-ender3-s1pro-2022.cfg index b742fbb5..9116eb05 100644 --- a/config/printer-creality-ender3-s1pro-2022.cfg +++ b/config/printer-creality-ender3-s1pro-2022.cfg @@ -7,7 +7,7 @@ # If you prefer a direct serial connection, in "make menuconfig" # select "Enable extra low-level configuration options" and select -# serial (on USB PA10/PA9), which is broken out on the 10 pin IDC +# 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 From ea4f6d6a77567e9277f775cdb50375fd3e6a25d4 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 7 Jun 2022 11:43:19 -0400 Subject: [PATCH 043/138] rp2040: Implement workaround for USB errata "rp2040-e5" The rp2040 USB may not connect after a reset. Implementation the recommended workaround. Signed-off-by: Lasse Dalegaard Signed-off-by: Kevin O'Connor --- docs/Config_Changes.md | 6 +++ src/rp2040/usbserial.c | 102 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 105 insertions(+), 3 deletions(-) diff --git a/docs/Config_Changes.md b/docs/Config_Changes.md index ca97a6cc..6961cbde 100644 --- a/docs/Config_Changes.md +++ b/docs/Config_Changes.md @@ -8,6 +8,12 @@ All dates in this document are approximate. ## Changes +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/src/rp2040/usbserial.c b/src/rp2040/usbserial.c index 3a6736ef..83838d2d 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 @@ -164,6 +168,89 @@ usb_request_bootloader(void) } +/**************************************************************** + * 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_errata_task(void) +{ + 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); + + /**************************************************************** * Setup and interrupts ****************************************************************/ @@ -191,6 +278,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 +319,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); From 29e9ac6ec5e12d2c4a2d938748b4a056826fb535 Mon Sep 17 00:00:00 2001 From: Devin Fritz <40574804+StaticFX@users.noreply.github.com> Date: Sun, 12 Jun 2022 19:03:50 +0200 Subject: [PATCH 044/138] config: Update generic-duet3-mini.cfg to include Tacho pins (#5551) Signed-off-by: Devin Fritz --- config/generic-duet3-mini.cfg | 1 + 1 file changed, 1 insertion(+) 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} From 7f9ea231b7b9e0f76bf2c5ec02f8af89e7017f76 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 13 Jun 2022 13:51:07 -0400 Subject: [PATCH 045/138] delta_calibrate: Fix Python3 error in DELTA_ANALYZE Reported by @CODeRUS. Signed-off-by: Kevin O'Connor --- klippy/extras/delta_calibrate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From c61db45613615997d6bf68704ab9cc98cb426a04 Mon Sep 17 00:00:00 2001 From: Stefan Dej Date: Wed, 15 Jun 2022 08:41:52 +0200 Subject: [PATCH 046/138] config: fix processor name in th3d ezboard lite v2.0 config Signed-off-by: Stefan Dej --- config/generic-th3d-ezboard-lite-v2.0.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 31f9bbf105e70a5a9ab758a36202cda275075693 Mon Sep 17 00:00:00 2001 From: CoreRasurae <38363426+CoreRasurae@users.noreply.github.com> Date: Thu, 16 Jun 2022 15:54:38 +0100 Subject: [PATCH 047/138] config: Creality CR10 v3 with BLTouch support config. (#5533) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce support for the stock Creality CR10 v3 printer, including the filament runout sensor. It also supports the optional BLTouch. Signed-off-by: Luís Palma Nunes Mendes --- config/printer-creality-cr10-v3-2020.cfg | 169 +++++++++++++++++++++++ test/klippy/printers.test | 1 + 2 files changed, 170 insertions(+) create mode 100644 config/printer-creality-cr10-v3-2020.cfg diff --git a/config/printer-creality-cr10-v3-2020.cfg b/config/printer-creality-cr10-v3-2020.cfg new file mode 100644 index 00000000..fec583fa --- /dev/null +++ b/config/printer-creality-cr10-v3-2020.cfg @@ -0,0 +1,169 @@ +# 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. +# +# To flash do "make flash" and if it does not and avrdude also +# does not work then one may need to flash a bootloader to the +# board - see the Klipper docs/Bootloaders.md file for more +# information. +# +# 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 right 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 right screw +#speed: 50 +#horizontal_move_z: 10 +#screw_thread: CW-M3 diff --git a/test/klippy/printers.test b/test/klippy/printers.test index 7c25009a..e0b0ecc5 100644 --- a/test/klippy/printers.test +++ b/test/klippy/printers.test @@ -57,6 +57,7 @@ CONFIG ../../config/printer-wanhao-duplicator-i3-plus-2017.cfg CONFIG ../../config/printer-wanhao-duplicator-i3-plus-mark2-2019.cfg CONFIG ../../config/printer-wanhao-duplicator-6-2016.cfg CONFIG ../../config/printer-wanhao-duplicator-9-2018.cfg +CONFIG ../../config/printer-creality-cr10-v3-2020.cfg # Printers using the atmega1280 DICTIONARY atmega1280.dict From e98a29bef37f2198fd8376f6cf72e66bc2796edf Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 16 Jun 2022 10:56:30 -0400 Subject: [PATCH 048/138] config: Minor comment changes to printer-creality-cr10-v3-2020.cfg Signed-off-by: Kevin O'Connor --- config/printer-creality-cr10-v3-2020.cfg | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/config/printer-creality-cr10-v3-2020.cfg b/config/printer-creality-cr10-v3-2020.cfg index fec583fa..8b6c355e 100644 --- a/config/printer-creality-cr10-v3-2020.cfg +++ b/config/printer-creality-cr10-v3-2020.cfg @@ -1,23 +1,16 @@ -# 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. -# -# To flash do "make flash" and if it does not and avrdude also -# does not work then one may need to flash a bootloader to the -# board - see the Klipper docs/Bootloaders.md file for more -# information. -# +# 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 From b9e195f098c3dd2e61410aa85e2784c9cbc01e5d Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 13 Jun 2022 11:40:41 -0400 Subject: [PATCH 049/138] flash_usb: Use hexid "2e8a:0003" for rp2040 already in bootloader mode Use a USB hex id to indicate that the device is already in bootloader mode. This makes the rp2040 flashing code similar to the other boards. Signed-off-by: Kevin O'Connor --- docs/Config_Changes.md | 4 ++++ scripts/flash_usb.py | 8 +++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/Config_Changes.md b/docs/Config_Changes.md index 6961cbde..bbf81fe1 100644 --- a/docs/Config_Changes.md +++ b/docs/Config_Changes.md @@ -8,6 +8,10 @@ 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 diff --git a/scripts/flash_usb.py b/scripts/flash_usb.py index b45afeff..4004ad55 100755 --- a/scripts/flash_usb.py +++ b/scripts/flash_usb.py @@ -315,17 +315,19 @@ 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": + if options.device.lower() == "2e8a:0003": call_picoboot(None, None, binfile, options.sudo) else: flash_picoboot(options.device, binfile, options.sudo) From da755c3c1b0288c6ea488a56be615b5878904fa6 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 10 Jun 2022 12:51:24 -0400 Subject: [PATCH 050/138] canbus: Move global variables into a struct Create a single CanData global variable to track the canbus state. ARM micro-controllers generally produce better code when global variables are in a struct. Signed-off-by: Kevin O'Connor --- src/generic/canbus.c | 120 +++++++++++++++++++++++-------------------- 1 file changed, 65 insertions(+), 55 deletions(-) diff --git a/src/generic/canbus.c b/src/generic/canbus.c index a1a2f6b5..de7952cb 100644 --- a/src/generic/canbus.c +++ b/src/generic/canbus.c @@ -15,44 +15,56 @@ #include "command.h" // DECL_CONSTANT #include "sched.h" // sched_wake_task -static uint32_t canbus_assigned_id; -static uint8_t canbus_uuid[CANBUS_UUID_LEN]; +// 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; + + // Transfer buffers + uint8_t transmit_buf[96]; + uint8_t receive_buf[192]; +} CanData; /**************************************************************** * Data transmission over CAN ****************************************************************/ -static struct task_wake canbus_tx_wake; -static uint8_t transmit_buf[96], transmit_pos, transmit_max; - void canbus_notify_tx(void) { - sched_wake_task(&canbus_tx_wake); + sched_wake_task(&CanData.tx_wake); } void canbus_tx_task(void) { - if (!sched_check_wake(&canbus_tx_wake)) + if (!sched_check_wake(&CanData.tx_wake)) return; - uint32_t id = canbus_assigned_id; + uint32_t id = CanData.assigned_id; if (!id) { - transmit_pos = transmit_max = 0; + CanData.transmit_pos = CanData.transmit_max = 0; return; } - uint32_t tpos = transmit_pos, tmax = transmit_max; + uint32_t tpos = CanData.transmit_pos, tmax = CanData.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]); + int ret = canbus_send(id + 1, now, &CanData.transmit_buf[tpos]); if (ret <= 0) break; tpos += now; } - transmit_pos = tpos; + CanData.transmit_pos = tpos; } DECL_TASK(canbus_tx_task); @@ -61,26 +73,27 @@ void console_sendf(const struct command_encoder *ce, va_list args) { // Verify space for message - uint32_t tpos = transmit_pos, tmax = transmit_max; + uint32_t tpos = CanData.transmit_pos, tmax = CanData.transmit_max; if (tpos >= tmax) - transmit_pos = transmit_max = tpos = tmax = 0; + CanData.transmit_pos = CanData.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)) + if (tmax + max_size > sizeof(CanData.transmit_buf)) { + if (tmax + max_size - tpos > sizeof(CanData.transmit_buf)) // Not enough space for message return; // Move buffer tmax -= tpos; - memmove(&transmit_buf[0], &transmit_buf[tpos], tmax); - transmit_pos = tpos = 0; - transmit_max = tmax; + 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(&transmit_buf[tmax], ce, args); + uint32_t msglen = command_encode_and_frame(&CanData.transmit_buf[tmax] + , ce, args); // Start message transmit - transmit_max = tmax + msglen; + CanData.transmit_max = tmax + msglen; canbus_notify_tx(); } @@ -99,16 +112,16 @@ console_sendf(const struct command_encoder *ce, va_list args) 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; + return len >= 7 && memcmp(&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 (!canbus_assigned_id) + if (!CanData.assigned_id) return 0; - return (canbus_assigned_id - 0x100) >> 1; + return (CanData.assigned_id - 0x100) >> 1; } static uint32_t can_decode_nodeid(int nodeid) @@ -119,11 +132,11 @@ can_decode_nodeid(int nodeid) static void can_process_query_unassigned(uint32_t id, uint32_t len, uint8_t *data) { - if (canbus_assigned_id) + if (CanData.assigned_id) return; uint8_t send[8]; send[0] = CANBUS_RESP_NEED_NODEID; - memcpy(&send[1], canbus_uuid, sizeof(canbus_uuid)); + memcpy(&send[1], CanData.uuid, sizeof(CanData.uuid)); send[7] = CANBUS_CMD_SET_KLIPPER_NODEID; // Send with retry for (;;) { @@ -136,8 +149,8 @@ can_process_query_unassigned(uint32_t id, uint32_t len, uint8_t *data) static void can_id_conflict(void) { - canbus_assigned_id = 0; - canbus_set_filter(canbus_assigned_id); + CanData.assigned_id = 0; + canbus_set_filter(CanData.assigned_id); shutdown("Another CAN node assigned this ID"); } @@ -148,11 +161,11 @@ can_process_set_klipper_nodeid(uint32_t id, uint32_t len, uint8_t *data) 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); + if (newid != CanData.assigned_id) { + CanData.assigned_id = newid; + canbus_set_filter(CanData.assigned_id); } - } else if (newid == canbus_assigned_id) { + } else if (newid == CanData.assigned_id) { can_id_conflict(); } } @@ -189,28 +202,25 @@ can_process(uint32_t id, uint32_t len, uint8_t *data) * CAN packet reading ****************************************************************/ -static struct task_wake canbus_rx_wake; - void canbus_notify_rx(void) { - sched_wake_task(&canbus_rx_wake); + sched_wake_task(&CanData.rx_wake); } -static uint8_t receive_buf[192], receive_pos; -DECL_CONSTANT("RECEIVE_WINDOW", ARRAY_SIZE(receive_buf)); +DECL_CONSTANT("RECEIVE_WINDOW", ARRAY_SIZE(CanData.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) + if (!id || id != CanData.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; + int rpos = CanData.receive_pos; + if (len > sizeof(CanData.receive_buf) - rpos) + len = sizeof(CanData.receive_buf) - rpos; + memcpy(&CanData.receive_buf[rpos], data, len); + CanData.receive_pos = rpos + len; canbus_notify_rx(); } @@ -220,21 +230,21 @@ console_pop_input(int len) { int copied = 0; for (;;) { - int rpos = readb(&receive_pos); + int rpos = readb(&CanData.receive_pos); int needcopy = rpos - len; if (needcopy) { - memmove(&receive_buf[copied], &receive_buf[copied + len] - , needcopy - copied); + memmove(&CanData.receive_buf[copied] + , &CanData.receive_buf[copied + len], needcopy - copied); copied = needcopy; canbus_notify_rx(); } irqstatus_t flag = irq_save(); - if (rpos != readb(&receive_pos)) { + if (rpos != readb(&CanData.receive_pos)) { // Raced with irq handler - retry irq_restore(flag); continue; } - receive_pos = needcopy; + CanData.receive_pos = needcopy; irq_restore(flag); break; } @@ -244,7 +254,7 @@ console_pop_input(int len) void canbus_rx_task(void) { - if (!sched_check_wake(&canbus_rx_wake)) + if (!sched_check_wake(&CanData.rx_wake)) return; // Read any pending CAN packets @@ -254,17 +264,17 @@ canbus_rx_task(void) int ret = canbus_read(&id, data); if (ret < 0) break; - if (id && id == canbus_assigned_id + 1) + if (id && id == CanData.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); + 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(receive_buf, pop_count); + command_dispatch(CanData.receive_buf, pop_count); if (ret) { console_pop_input(pop_count); if (ret > 0) @@ -282,14 +292,14 @@ 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()); + , sizeof(CanData.uuid), CanData.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)); + memcpy(CanData.uuid, uuid, sizeof(CanData.uuid)); canbus_notify_rx(); } From 84d798f516c4a67f21d8c25e02157e79d5497cce Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 10 Jun 2022 14:13:39 -0400 Subject: [PATCH 051/138] canbus: Use single method for reading canbus messages Previously the code had canbus_read() which was called from task context (for admin messages), and canbus_process_data() which was called from irq context (used for data messages). Change that to a single canbus_process_data() function that is called from irq context (used for all messages). This simplifies the low-level hardware specific canbus code and should make it easier to support other hardware implementations. Signed-off-by: Kevin O'Connor --- src/generic/canbus.c | 102 ++++++++++++++++++++++-------------- src/generic/canbus.h | 17 ++++-- src/stm32/can.c | 82 ++++++----------------------- src/stm32/fdcan.c | 122 ++++++++++++------------------------------- 4 files changed, 125 insertions(+), 198 deletions(-) diff --git a/src/generic/canbus.c b/src/generic/canbus.c index de7952cb..8526c5c1 100644 --- a/src/generic/canbus.c +++ b/src/generic/canbus.c @@ -27,8 +27,10 @@ static struct canbus_data { // 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; @@ -54,12 +56,16 @@ canbus_tx_task(void) 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; - int ret = canbus_send(id + 1, now, &CanData.transmit_buf[tpos]); + msg.dlc = now; + memcpy(msg.data, &CanData.transmit_buf[tpos], now); + int ret = canbus_send(&msg); if (ret <= 0) break; tpos += now; @@ -110,9 +116,10 @@ console_sendf(const struct command_encoder *ce, va_list args) // 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) +can_check_uuid(struct canbus_msg *msg) { - return len >= 7 && memcmp(&data[1], CanData.uuid, sizeof(CanData.uuid))==0; + 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" @@ -130,17 +137,19 @@ can_decode_nodeid(int nodeid) } static void -can_process_query_unassigned(uint32_t id, uint32_t len, uint8_t *data) +can_process_query_unassigned(struct canbus_msg *msg) { if (CanData.assigned_id) return; - uint8_t send[8]; - send[0] = CANBUS_RESP_NEED_NODEID; - memcpy(&send[1], CanData.uuid, sizeof(CanData.uuid)); - send[7] = CANBUS_CMD_SET_KLIPPER_NODEID; + 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 = canbus_send(CANBUS_ID_ADMIN_RESP, 8, send); + int ret = canbus_send(&send); if (ret >= 0) return; } @@ -155,12 +164,12 @@ can_id_conflict(void) } static void -can_process_set_klipper_nodeid(uint32_t id, uint32_t len, uint8_t *data) +can_process_set_klipper_nodeid(struct canbus_msg *msg) { - if (len < 8) + if (msg->dlc < 8) return; - uint32_t newid = can_decode_nodeid(data[7]); - if (can_check_uuid(id, len, data)) { + uint32_t newid = can_decode_nodeid(msg->data[7]); + if (can_check_uuid(msg)) { if (newid != CanData.assigned_id) { CanData.assigned_id = newid; canbus_set_filter(CanData.assigned_id); @@ -171,28 +180,28 @@ can_process_set_klipper_nodeid(uint32_t id, uint32_t len, uint8_t *data) } static void -can_process_request_bootloader(uint32_t id, uint32_t len, uint8_t *data) +can_process_request_bootloader(struct canbus_msg *msg) { - if (!can_check_uuid(id, len, data)) + if (!can_check_uuid(msg)) return; try_request_canboot(); } // Handle an "admin" command static void -can_process(uint32_t id, uint32_t len, uint8_t *data) +can_process_admin(struct canbus_msg *msg) { - if (!len) + if (!msg->dlc) return; - switch (data[0]) { + switch (msg->data[0]) { case CANBUS_CMD_QUERY_UNASSIGNED: - can_process_query_unassigned(id, len, data); + can_process_query_unassigned(msg); break; case CANBUS_CMD_SET_KLIPPER_NODEID: - can_process_set_klipper_nodeid(id, len, data); + can_process_set_klipper_nodeid(msg); break; case CANBUS_CMD_REQUEST_BOOTLOADER: - can_process_request_bootloader(id, len, data); + can_process_request_bootloader(msg); break; } } @@ -202,7 +211,7 @@ can_process(uint32_t id, uint32_t len, uint8_t *data) * CAN packet reading ****************************************************************/ -void +static void canbus_notify_rx(void) { sched_wake_task(&CanData.rx_wake); @@ -212,16 +221,30 @@ DECL_CONSTANT("RECEIVE_WINDOW", ARRAY_SIZE(CanData.receive_buf)); // Handle incoming data (called from IRQ handler) void -canbus_process_data(uint32_t id, uint32_t len, uint8_t *data) +canbus_process_data(struct canbus_msg *msg) { - if (!id || id != CanData.assigned_id) - return; - int rpos = CanData.receive_pos; - if (len > sizeof(CanData.receive_buf) - rpos) - len = sizeof(CanData.receive_buf) - rpos; - memcpy(&CanData.receive_buf[rpos], data, len); - CanData.receive_pos = rpos + len; - canbus_notify_rx(); + 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) + len = sizeof(CanData.receive_buf) - rpos; + memcpy(&CanData.receive_buf[rpos], msg->data, len); + CanData.receive_pos = rpos + len; + canbus_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; + uint32_t pos = pushp % ARRAY_SIZE(CanData.admin_queue); + memcpy(&CanData.admin_queue[pos], msg, sizeof(*msg)); + CanData.admin_push_pos = pushp + 1; + canbus_notify_rx(); + } } // Remove from the receive buffer the given number of bytes @@ -257,17 +280,20 @@ canbus_rx_task(void) if (!sched_check_wake(&CanData.rx_wake)) return; - // Read any pending CAN packets + // Process pending admin messages for (;;) { - uint8_t data[8]; - uint32_t id; - int ret = canbus_read(&id, data); - if (ret < 0) + uint32_t pushp = readl(&CanData.admin_push_pos); + uint32_t pullp = CanData.admin_pull_pos; + if (pushp == pullp) break; - if (id && id == CanData.assigned_id + 1) + 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(id, ret, data); + can_process_admin(msg); + CanData.admin_pull_pos = pullp + 1; } // Check for a complete message block and process it diff --git a/src/generic/canbus.h b/src/generic/canbus.h index a7df077d..a6463246 100644 --- a/src/generic/canbus.h +++ b/src/generic/canbus.h @@ -7,15 +7,24 @@ #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_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_process_data(struct canbus_msg *msg); void canbus_set_uuid(void *data); #endif // canbus.h diff --git a/src/stm32/can.c b/src/stm32/can.c index a82ad688..99fe98dc 100644 --- a/src/stm32/can.c +++ b/src/stm32/can.c @@ -91,41 +91,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))) { @@ -143,23 +111,15 @@ canbus_send(uint32_t id, uint32_t len, uint8_t *data) 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; + mb->TIR = (msg->id << CAN_TI0R_STID_Pos) | CAN_TI0R_TXRQ; + return CANMSG_DATA_LEN(msg); } // Setup the receive packet filter @@ -182,9 +142,6 @@ canbus_set_filter(uint32_t id) /* 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 */ @@ -195,27 +152,20 @@ 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]; + struct canbus_msg msg; + msg.id = (mb->RIR >> CAN_RI0R_STID_Pos) & 0x7FF; + 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 @@ -310,7 +260,7 @@ 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; + SOC_CAN->IER = CAN_IER_FMPIE0; // Convert unique 96-bit chip id into 48 bit representation uint64_t hash = fasthash64((uint8_t*)UID_BASE, 12, 0xA16231A7); diff --git a/src/stm32/fdcan.c b/src/stm32/fdcan.c index aeb6eced..99a76a02 100755 --- a/src/stm32/fdcan.c +++ b/src/stm32/fdcan.c @@ -17,13 +17,6 @@ #include "internal.h" // enable_pclock #include "sched.h" // DECL_INIT -/* - FDCAN max date length = 64bytes - data_len[] is the data length & DLC mapping table - Required when the data length exceeds 64bytes - */ -uint8_t data_len[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64}; - typedef struct { uint32_t RESERVED0 : 18; @@ -38,7 +31,7 @@ typedef struct uint32_t RESERVED1 : 2; __IO uint32_t FIDX : 7; __IO uint32_t ANMF : 1; - __IO uint8_t data[64]; + __IO uint32_t data[64 / 4]; }FDCAN_RX_FIFO_TypeDef; typedef struct @@ -66,8 +59,6 @@ typedef struct FDCAN_RAM_TypeDef *fdcan_ram = (FDCAN_RAM_TypeDef *)(SRAMCAN_BASE); -#define FDCAN_IE_RX_FIFO0 (FDCAN_IE_RF0NE | FDCAN_IE_RF0FE | FDCAN_IE_RF0LE) -#define FDCAN_IE_RX_FIFO1 (FDCAN_IE_RF1NE | FDCAN_IE_RF1FE | FDCAN_IE_RF1LE) #define FDCAN_IE_TC (FDCAN_IE_TCE | FDCAN_IE_TCFE | FDCAN_IE_TFEE) #if CONFIG_STM32_CANBUS_PB0_PB1 @@ -85,7 +76,6 @@ FDCAN_RAM_TypeDef *fdcan_ram = (FDCAN_RAM_TypeDef *)(SRAMCAN_BASE); #endif #define CAN_IT0_IRQn TIM16_FDCAN_IT0_IRQn - #define CAN_IT1_IRQn TIM17_FDCAN_IT1_IRQn #define CAN_FUNCTION GPIO_FUNCTION(3) // Alternative function mapping number #endif @@ -93,35 +83,9 @@ FDCAN_RAM_TypeDef *fdcan_ram = (FDCAN_RAM_TypeDef *)(SRAMCAN_BASE); #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->RXF0S & FDCAN_RXF0S_F0FL)) { - // All rx mboxes empty, enable wake on rx IRQ - irq_disable(); - SOC_CAN->IE |= FDCAN_IE_RF0NE; - irq_enable(); - return -1; - } - - // Read and ack packet - uint32_t r_index = ((SOC_CAN->RXF0S & FDCAN_RXF0S_F0GI) - >> FDCAN_RXF0S_F0GI_Pos); - FDCAN_RX_FIFO_TypeDef *rxf0 = &MSG_RAM.RXF0[r_index]; - uint32_t dlc = rxf0->DLC; - *id = rxf0->ID; - for (uint8_t i = 0; i < dlc; i++) { - data[i] = rxf0->data[i]; - } - SOC_CAN->RXF0A = r_index; - - 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 txfqs = SOC_CAN->TXFQS; if (txfqs & FDCAN_TXFQS_TFQF) { @@ -134,23 +98,16 @@ canbus_send(uint32_t id, uint32_t len, uint8_t *data) uint32_t w_index = ((txfqs & FDCAN_TXFQS_TFQPI) >> FDCAN_TXFQS_TFQPI_Pos); FDCAN_TX_FIFO_TypeDef *txfifo = &MSG_RAM.TXFIFO[w_index]; - txfifo->id_section = id << 18; - txfifo->dlc_section = len << 16; - if (len) { - txfifo->data[0] = (((uint32_t)data[3] << 24) - | ((uint32_t)data[2] << 16) - | ((uint32_t)data[1] << 8) - | ((uint32_t)data[0] << 0)); - txfifo->data[1] = (((uint32_t)data[7] << 24) - | ((uint32_t)data[6] << 16) - | ((uint32_t)data[5] << 8) - | ((uint32_t)data[4] << 0)); - } + txfifo->id_section = (msg->id & 0x1fffffff) << 18; + 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 len; + return CANMSG_DATA_LEN(msg); } -void can_filter(uint32_t id, uint8_t index) +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 @@ -170,17 +127,12 @@ canbus_set_filter(uint32_t id) /* Enable configuration change */ SOC_CAN->CCCR |= FDCAN_CCCR_CCE; - can_filter(CANBUS_ID_ADMIN, 0); - - /* List size standard */ - SOC_CAN->RXGFC &= ~(FDCAN_RXGFC_LSS); - SOC_CAN->RXGFC |= 1 << FDCAN_RXGFC_LSS_Pos; - - /* Filter remote frames with 11-bit standard IDs - Non-matching frames standard reject or accept in Rx FIFO 1 */ - SOC_CAN->RXGFC &= ~(FDCAN_RXGFC_RRFS | FDCAN_RXGFC_ANFS); - SOC_CAN->RXGFC |= ((0 << FDCAN_RXGFC_RRFS_Pos) - | ((id ? 0x01 : 0x02) << FDCAN_RXGFC_ANFS_Pos)); + // Load filter + can_filter(0, CANBUS_ID_ADMIN); + can_filter(1, id); + can_filter(2, id + 1); + SOC_CAN->RXGFC = ((id ? 3 : 1) << FDCAN_RXGFC_LSS_Pos + | 0x02 << FDCAN_RXGFC_ANFS_Pos); /* Leave the initialisation mode for the filter */ SOC_CAN->CCCR &= ~FDCAN_CCCR_CCE; @@ -191,36 +143,28 @@ canbus_set_filter(uint32_t id) void CAN_IRQHandler(void) { - uint32_t ir = SOC_CAN->IR; - uint32_t ie = SOC_CAN->IE; + uint32_t ir = SOC_CAN->IR & SOC_CAN->IE; - if (ir & FDCAN_IE_RX_FIFO1 && ie & FDCAN_IE_RX_FIFO1) { - SOC_CAN->IR = FDCAN_IE_RX_FIFO1; + if (ir & FDCAN_IE_RF0NE) { + SOC_CAN->IR = FDCAN_IE_RF0NE; - if (SOC_CAN->RXF1S & FDCAN_RXF1S_F1FL) { + uint32_t rxf0s = SOC_CAN->RXF0S; + if (rxf0s & FDCAN_RXF0S_F0FL) { // Read and ack data packet - uint32_t r_index = ((SOC_CAN->RXF1S & FDCAN_RXF1S_F1GI) - >> FDCAN_RXF1S_F1GI_Pos); - FDCAN_RX_FIFO_TypeDef *rxf1 = &MSG_RAM.RXF1[r_index]; - - uint32_t rir_id = rxf1->ID; - uint32_t dlc = rxf1->DLC; - uint8_t data[8]; - for (uint8_t i = 0; i < dlc; i++) { - data[i] = rxf1->data[i]; - } - SOC_CAN->RXF1A = r_index; + uint32_t idx = (rxf0s & FDCAN_RXF0S_F0GI) >> FDCAN_RXF0S_F0GI_Pos; + FDCAN_RX_FIFO_TypeDef *rxf0 = &MSG_RAM.RXF0[idx]; + struct canbus_msg msg; + msg.id = rxf0->ID; + msg.dlc = rxf0->DLC; + msg.data32[0] = rxf0->data[0]; + msg.data32[1] = rxf0->data[1]; + SOC_CAN->RXF0A = idx; // Process packet - canbus_process_data(rir_id, dlc, data); + canbus_process_data(&msg); } } - if (ie & FDCAN_IE_RX_FIFO0 && ir & FDCAN_IE_RX_FIFO0) { - // Admin Rx - SOC_CAN->IR = FDCAN_IE_RX_FIFO0; - canbus_notify_rx(); - } - if (ie & FDCAN_IE_TC && ir & FDCAN_IE_TC) { + if (ir & FDCAN_IE_TC) { // Tx SOC_CAN->IR = FDCAN_IE_TC; canbus_notify_tx(); @@ -317,10 +261,8 @@ can_init(void) /*##-3- Configure Interrupts #################################*/ armcm_enable_irq(CAN_IRQHandler, CAN_IT0_IRQn, 0); - if (CAN_IT0_IRQn != CAN_IT1_IRQn) - armcm_enable_irq(CAN_IRQHandler, CAN_IT1_IRQn, 0); - SOC_CAN->ILE |= 0x03; - SOC_CAN->IE |= FDCAN_IE_RX_FIFO0 | FDCAN_IE_RX_FIFO1; + SOC_CAN->ILE = FDCAN_ILE_EINT0; + SOC_CAN->IE = FDCAN_IE_RF0NE; // Convert unique 96-bit chip id into 48 bit representation uint64_t hash = fasthash64((uint8_t*)UID_BASE, 12, 0xA16231A7); From ce186c6af6e6b9f4656c0c09d1a92c27d5b1aa2d Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 10 Jun 2022 23:27:53 -0400 Subject: [PATCH 052/138] stm32: Simplify fdcan tx irq handling Signed-off-by: Kevin O'Connor --- src/stm32/fdcan.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/stm32/fdcan.c b/src/stm32/fdcan.c index 99a76a02..6a1dfe93 100755 --- a/src/stm32/fdcan.c +++ b/src/stm32/fdcan.c @@ -88,13 +88,9 @@ int canbus_send(struct canbus_msg *msg) { uint32_t txfqs = SOC_CAN->TXFQS; - if (txfqs & FDCAN_TXFQS_TFQF) { - // No space in transmit fifo - enable tx irq - irq_disable(); - SOC_CAN->IE |= FDCAN_IE_TC; - irq_enable(); + 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_TX_FIFO_TypeDef *txfifo = &MSG_RAM.TXFIFO[w_index]; @@ -143,7 +139,7 @@ canbus_set_filter(uint32_t id) void CAN_IRQHandler(void) { - uint32_t ir = SOC_CAN->IR & SOC_CAN->IE; + uint32_t ir = SOC_CAN->IR; if (ir & FDCAN_IE_RF0NE) { SOC_CAN->IR = FDCAN_IE_RF0NE; @@ -262,7 +258,7 @@ can_init(void) /*##-3- Configure Interrupts #################################*/ armcm_enable_irq(CAN_IRQHandler, CAN_IT0_IRQn, 0); SOC_CAN->ILE = FDCAN_ILE_EINT0; - SOC_CAN->IE = FDCAN_IE_RF0NE; + SOC_CAN->IE = FDCAN_IE_RF0NE | FDCAN_IE_TC; // Convert unique 96-bit chip id into 48 bit representation uint64_t hash = fasthash64((uint8_t*)UID_BASE, 12, 0xA16231A7); From 913c6a913dceb11eeaf9fa62b0f3ba5cd88463e3 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 10 Jun 2022 22:48:53 -0400 Subject: [PATCH 053/138] stm32: Support PA11/PA12 and PB8/PB9 on fdcan Signed-off-by: Kevin O'Connor --- src/stm32/Kconfig | 4 ++-- src/stm32/fdcan.c | 32 ++++++++++++++++++-------------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index bad86a8c..875d138e 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -322,7 +322,7 @@ 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 @@ -330,7 +330,7 @@ choice select CANSERIAL config STM32_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 bool "CAN bus (on PI9/PH13)" if LOW_LEVEL_OPTIONS diff --git a/src/stm32/fdcan.c b/src/stm32/fdcan.c index 6a1dfe93..afd59b5d 100755 --- a/src/stm32/fdcan.c +++ b/src/stm32/fdcan.c @@ -61,24 +61,31 @@ 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_PB0_PB1 +#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_PB0_PB1 DECL_CONSTANT_STR("RESERVE_PINS_CAN", "PB0,PB1"); #define GPIO_Rx GPIO('B', 0) #define GPIO_Tx GPIO('B', 1) #endif -#if CONFIG_MACH_STM32G0 - #if CONFIG_STM32_CANBUS_PB0_PB1 - #define SOC_CAN FDCAN2 - #define MSG_RAM fdcan_ram->fdcan2 - #else - #error Uknown pins for STMF32G0 CAN - #endif - - #define CAN_IT0_IRQn TIM16_FDCAN_IT0_IRQn - #define CAN_FUNCTION GPIO_FUNCTION(3) // Alternative function mapping number +#if !CONFIG_STM32_CANBUS_PB0_PB1 + #define SOC_CAN FDCAN1 + #define MSG_RAM fdcan_ram->fdcan1 +#else + #define SOC_CAN FDCAN2 + #define MSG_RAM fdcan_ram->fdcan2 #endif +#define CAN_IT0_IRQn TIM16_FDCAN_IT0_IRQn +#define CAN_FUNCTION GPIO_FUNCTION(3) // Alternative function mapping number + #ifndef SOC_CAN #error No known CAN device for configured MCU #endif @@ -240,9 +247,6 @@ can_init(void) /* Enable configuration change */ SOC_CAN->CCCR |= FDCAN_CCCR_CCE; - if (SOC_CAN == FDCAN1) - FDCAN_CONFIG->CKDIV = 0; - /* Disable protocol exception handling */ SOC_CAN->CCCR |= FDCAN_CCCR_PXHD; From 3f7d05dd18469927dff1cf5a7d35d67ec9fd7cdc Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 11 Jun 2022 19:00:54 -0400 Subject: [PATCH 054/138] stm32: Support passing through RTR and EFF canbus frames Signed-off-by: Kevin O'Connor --- src/generic/canbus.h | 3 +++ src/stm32/can.c | 15 ++++++++++++-- src/stm32/fdcan.c | 49 +++++++++++++++++++++----------------------- 3 files changed, 39 insertions(+), 28 deletions(-) diff --git a/src/generic/canbus.h b/src/generic/canbus.h index a6463246..224d7037 100644 --- a/src/generic/canbus.h +++ b/src/generic/canbus.h @@ -16,6 +16,9 @@ struct canbus_msg { }; }; +#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 diff --git a/src/stm32/can.c b/src/stm32/can.c index 99fe98dc..16623f4b 100644 --- a/src/stm32/can.c +++ b/src/stm32/can.c @@ -118,6 +118,12 @@ canbus_send(struct canbus_msg *msg) mb->TDHR = msg->data32[1]; /* Request transmission */ + 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); } @@ -131,7 +137,7 @@ 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; + 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; @@ -155,8 +161,13 @@ CAN_IRQHandler(void) if (SOC_CAN->RF0R & CAN_RF0R_FMP0) { // Read and ack data packet CAN_FIFOMailBox_TypeDef *mb = &SOC_CAN->sFIFOMailBox[0]; + uint32_t rir = mb->RIR; struct canbus_msg msg; - msg.id = (mb->RIR >> CAN_RI0R_STID_Pos) & 0x7FF; + 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; diff --git a/src/stm32/fdcan.c b/src/stm32/fdcan.c index afd59b5d..8a462f76 100755 --- a/src/stm32/fdcan.c +++ b/src/stm32/fdcan.c @@ -17,38 +17,24 @@ #include "internal.h" // enable_pclock #include "sched.h" // DECL_INIT -typedef struct -{ - uint32_t RESERVED0 : 18; - __IO uint32_t ID : 11; - __IO uint32_t RTR : 1; - __IO uint32_t XTD : 1; - __IO uint32_t ESI : 1; - __IO uint32_t RXTS : 16; - __IO uint32_t DLC : 4; - __IO uint32_t BRS : 1; - __IO uint32_t FDF : 1; - uint32_t RESERVED1 : 2; - __IO uint32_t FIDX : 7; - __IO uint32_t ANMF : 1; - __IO uint32_t data[64 / 4]; -}FDCAN_RX_FIFO_TypeDef; - typedef struct { __IO uint32_t id_section; __IO uint32_t dlc_section; __IO uint32_t data[64 / 4]; -}FDCAN_TX_FIFO_TypeDef; +}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_RX_FIFO_TypeDef RXF0[3]; - FDCAN_RX_FIFO_TypeDef RXF1[3]; + FDCAN_FIFO_TypeDef RXF0[3]; + FDCAN_FIFO_TypeDef RXF1[3]; __IO uint32_t TEF[6]; // Tx event FIFO - FDCAN_TX_FIFO_TypeDef TXFIFO[3]; + FDCAN_FIFO_TypeDef TXFIFO[3]; }FDCAN_MSG_RAM_TypeDef; typedef struct @@ -100,8 +86,14 @@ canbus_send(struct canbus_msg *msg) return -1; uint32_t w_index = ((txfqs & FDCAN_TXFQS_TFQPI) >> FDCAN_TXFQS_TFQPI_Pos); - FDCAN_TX_FIFO_TypeDef *txfifo = &MSG_RAM.TXFIFO[w_index]; - txfifo->id_section = (msg->id & 0x1fffffff) << 18; + 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]; @@ -155,10 +147,15 @@ CAN_IRQHandler(void) if (rxf0s & FDCAN_RXF0S_F0FL) { // Read and ack data packet uint32_t idx = (rxf0s & FDCAN_RXF0S_F0GI) >> FDCAN_RXF0S_F0GI_Pos; - FDCAN_RX_FIFO_TypeDef *rxf0 = &MSG_RAM.RXF0[idx]; + FDCAN_FIFO_TypeDef *rxf0 = &MSG_RAM.RXF0[idx]; + uint32_t ids = rxf0->id_section; struct canbus_msg msg; - msg.id = rxf0->ID; - msg.dlc = rxf0->DLC; + 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; From fc7838855f886383917076cee87b13938d8bbe40 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 12 Jun 2022 11:55:46 -0400 Subject: [PATCH 055/138] canbus: Move canbus uuid calculation to canbus.c Move the uuid hash calculation to canbus.c and call canbus_set_uuid() from src/stm32/chipid.c . This simplifies the low-level canbus hardware code. Signed-off-by: Kevin O'Connor --- src/Kconfig | 13 +++++++------ src/generic/canbus.c | 8 ++++++-- src/generic/canbus.h | 3 +-- src/stm32/Makefile | 2 +- src/stm32/can.c | 5 ----- src/stm32/chipid.c | 10 ++++++---- src/stm32/fdcan.c | 5 ----- 7 files changed, 21 insertions(+), 25 deletions(-) diff --git a/src/Kconfig b/src/Kconfig index 54c3497e..bb3ef3c2 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -56,16 +56,11 @@ config USB_VENDOR_ID config USB_DEVICE_ID default 0x614e config USB_SERIAL_NUMBER_CHIPID - depends on HAVE_CHIPID + depends on HAVE_CHIPID && USBSERIAL default y config USB_SERIAL_NUMBER default "12345" -# Generic configuration options for CANbus -config CANBUS_FREQUENCY - int "CAN bus speed" if LOW_LEVEL_OPTIONS && CANSERIAL - default 500000 - menu "USB ids" depends on USBSERIAL && LOW_LEVEL_OPTIONS config USB_VENDOR_ID @@ -78,6 +73,12 @@ config USB_SERIAL_NUMBER string "USB serial number" if !USB_SERIAL_NUMBER_CHIPID endmenu +# Generic configuration options for CANbus +config CANBUS_FREQUENCY + int "CAN bus speed" if LOW_LEVEL_OPTIONS && CANSERIAL + default 500000 + +# 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/generic/canbus.c b/src/generic/canbus.c index 8526c5c1..3cc051c8 100644 --- a/src/generic/canbus.c +++ b/src/generic/canbus.c @@ -13,8 +13,11 @@ #include "board/misc.h" // console_sendf #include "canbus.h" // canbus_set_uuid #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; @@ -323,9 +326,10 @@ command_get_canbus_id(uint32_t *args) DECL_COMMAND_FLAGS(command_get_canbus_id, HF_IN_SHUTDOWN, "get_canbus_id"); void -canbus_set_uuid(void *uuid) +canbus_set_uuid(uint8_t *raw_uuid, uint32_t raw_uuid_len) { - memcpy(CanData.uuid, uuid, sizeof(CanData.uuid)); + uint64_t hash = fasthash64(raw_uuid, raw_uuid_len, 0xA16231A7); + memcpy(CanData.uuid, &hash, sizeof(CanData.uuid)); canbus_notify_rx(); } diff --git a/src/generic/canbus.h b/src/generic/canbus.h index 224d7037..79cb3245 100644 --- a/src/generic/canbus.h +++ b/src/generic/canbus.h @@ -5,7 +5,6 @@ #define CANBUS_ID_ADMIN 0x3f0 #define CANBUS_ID_ADMIN_RESP 0x3f1 -#define CANBUS_UUID_LEN 6 struct canbus_msg { uint32_t id; @@ -28,6 +27,6 @@ void canbus_set_filter(uint32_t id); // canbus.c void canbus_notify_tx(void); void canbus_process_data(struct canbus_msg *msg); -void canbus_set_uuid(void *data); +void canbus_set_uuid(uint8_t *raw_uuid, uint32_t raw_uuid_len); #endif // canbus.h diff --git a/src/stm32/Makefile b/src/stm32/Makefile index 2f096369..fa662f91 100644 --- a/src/stm32/Makefile +++ b/src/stm32/Makefile @@ -62,7 +62,7 @@ src-$(CONFIG_SERIAL) += $(serial-src-y) generic/serial_irq.c canbus-src-y := generic/canbus.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) +src-$(CONFIG_CANSERIAL) += $(canbus-src-y) stm32/chipid.c src-$(CONFIG_HAVE_GPIO_HARD_PWM) += stm32/hard_pwm.c # Binary output file rules diff --git a/src/stm32/can.c b/src/stm32/can.c index 16623f4b..9cd72209 100644 --- a/src/stm32/can.c +++ b/src/stm32/can.c @@ -10,7 +10,6 @@ #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 "internal.h" // enable_pclock @@ -272,9 +271,5 @@ can_init(void) if (CAN_RX0_IRQn != CAN_TX_IRQn) armcm_enable_irq(CAN_IRQHandler, CAN_TX_IRQn, 0); SOC_CAN->IER = CAN_IER_FMPIE0; - - // Convert unique 96-bit chip id into 48 bit representation - uint64_t hash = fasthash64((uint8_t*)UID_BASE, 12, 0xA16231A7); - canbus_set_uuid(&hash); } DECL_INIT(can_init); diff --git a/src/stm32/chipid.c b/src/stm32/chipid.c index 73e27503..03517739 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/canbus.h" // canbus_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_CANSERIAL) + canbus_set_uuid((void*)UID_BASE, CHIP_UID_LEN); } DECL_INIT(chipid_init); diff --git a/src/stm32/fdcan.c b/src/stm32/fdcan.c index 8a462f76..cf2cc276 100755 --- a/src/stm32/fdcan.c +++ b/src/stm32/fdcan.c @@ -10,7 +10,6 @@ #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/serial_irq.h" // serial_rx_byte @@ -260,9 +259,5 @@ can_init(void) armcm_enable_irq(CAN_IRQHandler, CAN_IT0_IRQn, 0); SOC_CAN->ILE = FDCAN_ILE_EINT0; SOC_CAN->IE = FDCAN_IE_RF0NE | FDCAN_IE_TC; - - // Convert unique 96-bit chip id into 48 bit representation - uint64_t hash = fasthash64((uint8_t*)UID_BASE, 12, 0xA16231A7); - canbus_set_uuid(&hash); } DECL_INIT(can_init); From f55b9d3e5746b73d37f1f2de288aa06d9fe23138 Mon Sep 17 00:00:00 2001 From: bluesforte Date: Wed, 4 May 2022 16:02:37 -0700 Subject: [PATCH 056/138] mpu9250: Adding support for MPU-9250 (and MPU-6050) accelerometer Add support for mpu9250 accelerometer over I2C bus. Signed-off-by: Harry Beyel --- docs/Measuring_Resonances.md | 28 ++- klippy/extras/mpu9250.py | 461 +++++++++++++++++++++++++++++++++++ src/Makefile | 1 + src/i2ccmds.c | 11 +- src/i2ccmds.h | 13 + src/sensor_mpu9250.c | 277 +++++++++++++++++++++ 6 files changed, 786 insertions(+), 5 deletions(-) create mode 100644 klippy/extras/mpu9250.py create mode 100644 src/i2ccmds.h create mode 100644 src/sensor_mpu9250.c diff --git a/docs/Measuring_Resonances.md b/docs/Measuring_Resonances.md index 655a6494..42bb9fb7 100644 --- a/docs/Measuring_Resonances.md +++ b/docs/Measuring_Resonances.md @@ -31,6 +31,17 @@ 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) @@ -87,7 +98,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 +114,21 @@ 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: +``` +[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/klippy/extras/mpu9250.py b/klippy/extras/mpu9250.py new file mode 100644 index 00000000..a8f35bc6 --- /dev/null +++ b/klippy/extras/mpu9250.py @@ -0,0 +1,461 @@ +# 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 + +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')) + +# Helper method for getting the two's complement value of an unsigned int +def twos_complement(val, nbits): + if (val & (1 << (nbits - 1))) != 0: + val = val - (1 << nbits) + return val + +# Helper class to obtain measurements +class MPU9250QueryHelper: + def __init__(self, printer, cconn): + self.printer = printer + self.cconn = cconn + print_time = printer.lookup_object('toolhead').get_last_move_time() + self.request_start_time = self.request_end_time = print_time + self.samples = self.raw_samples = [] + def finish_measurements(self): + toolhead = self.printer.lookup_object('toolhead') + self.request_end_time = toolhead.get_last_move_time() + toolhead.wait_moves() + self.cconn.finalize() + def _get_raw_samples(self): + raw_samples = self.cconn.get_messages() + if raw_samples: + self.raw_samples = raw_samples + return self.raw_samples + def has_valid_samples(self): + raw_samples = self._get_raw_samples() + for msg in raw_samples: + data = msg['params']['data'] + first_sample_time = data[0][0] + last_sample_time = data[-1][0] + if (first_sample_time > self.request_end_time + or last_sample_time < self.request_start_time): + continue + # The time intervals [first_sample_time, last_sample_time] + # and [request_start_time, request_end_time] have non-zero + # intersection. It is still theoretically possible that none + # of the samples from raw_samples fall into the time interval + # [request_start_time, request_end_time] if it is too narrow + # or on very heavy data losses. In practice, that interval + # is at least 1 second, so this possibility is negligible. + return True + return False + def get_samples(self): + raw_samples = self._get_raw_samples() + if not raw_samples: + return self.samples + total = sum([len(m['params']['data']) for m in raw_samples]) + count = 0 + self.samples = samples = [None] * total + for msg in raw_samples: + for samp_time, x, y, z in msg['params']['data']: + if samp_time < self.request_start_time: + continue + if samp_time > self.request_end_time: + break + samples[count] = Accel_Measurement(samp_time, x, y, z) + count += 1 + del samples[count:] + return self.samples + def write_to_file(self, filename): + def write_impl(): + try: + # Try to re-nice writing process + os.nice(20) + except: + pass + f = open(filename, "w") + f.write("#time,accel_x,accel_y,accel_z\n") + samples = self.samples or self.get_samples() + for t, accel_x, accel_y, accel_z in samples: + f.write("%.6f,%.6f,%.6f,%.6f\n" % ( + t, accel_x, accel_y, accel_z)) + f.close() + write_proc = multiprocessing.Process(target=write_impl) + write_proc.daemon = True + write_proc.start() + +# Helper class for G-Code commands +class MPU9250CommandHelper: + def __init__(self, config, chip): + self.printer = config.get_printer() + self.chip = chip + self.bg_client = None + self.name = config.get_name().split()[-1] + self.register_commands(self.name) + if self.name == "mpu9250": + self.register_commands(None) + def register_commands(self, name): + # Register commands + gcode = self.printer.lookup_object('gcode') + gcode.register_mux_command("ACCELEROMETER_MEASURE", "CHIP", name, + self.cmd_ACCELEROMETER_MEASURE, + desc=self.cmd_ACCELEROMETER_MEASURE_help) + gcode.register_mux_command("ACCELEROMETER_QUERY", "CHIP", name, + self.cmd_ACCELEROMETER_QUERY, + desc=self.cmd_ACCELEROMETER_QUERY_help) + gcode.register_mux_command("ACCELEROMETER_DEBUG_READ", "CHIP", name, + self.cmd_ACCELEROMETER_DEBUG_READ, + desc=self.cmd_ACCELEROMETER_DEBUG_READ_help) + gcode.register_mux_command("ACCELEROMETER_DEBUG_WRITE", "CHIP", name, + self.cmd_ACCELEROMETER_DEBUG_WRITE, + desc=self.cmd_ACCELEROMETER_DEBUG_WRITE_help) + cmd_ACCELEROMETER_MEASURE_help = "Start/stop accelerometer" + def cmd_ACCELEROMETER_MEASURE(self, gcmd): + if self.bg_client is None: + # Start measurements + self.bg_client = self.chip.start_internal_client() + gcmd.respond_info("mpu9250 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 mpu9250 NAME parameter") + bg_client = self.bg_client + self.bg_client = None + bg_client.finish_measurements() + # Write data to file + if self.name == "mpu9250": + filename = "/tmp/mpu9250-%s.csv" % (name,) + else: + filename = "/tmp/mpu9250-%s-%s.csv" % (self.name, name,) + bg_client.write_to_file(filename) + gcmd.respond_info("Writing raw accelerometer data to %s file" + % (filename,)) + cmd_ACCELEROMETER_QUERY_help = "Query accelerometer for the current values" + def cmd_ACCELEROMETER_QUERY(self, gcmd): + aclient = self.chip.start_internal_client() + self.printer.lookup_object('toolhead').dwell(1.) + aclient.finish_measurements() + values = aclient.get_samples() + if not values: + raise gcmd.error("No mpu9250 measurements found") + _, accel_x, accel_y, accel_z = values[-1] + gcmd.respond_info("mpu9250 values (x, y, z): %.6f, %.6f, %.6f" + % (accel_x, accel_y, accel_z)) + cmd_ACCELEROMETER_DEBUG_READ_help = "Query mpu9250 register (for debugging)" + def cmd_ACCELEROMETER_DEBUG_READ(self, gcmd): + reg = gcmd.get("REG", minval=0, maxval=126, parser=lambda x: int(x, 0)) + val = self.chip.read_reg(reg) + gcmd.respond_info("MPU9250 REG[0x%x] = 0x%x" % (reg, val)) + cmd_ACCELEROMETER_DEBUG_WRITE_help = "Set mpu9250 register (for debugging)" + def cmd_ACCELEROMETER_DEBUG_WRITE(self, gcmd): + 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) + +# Helper class for chip clock synchronization via linear regression +class ClockSyncRegression: + def __init__(self, mcu, chip_clock_smooth, decay = 1. / 20.): + self.mcu = mcu + self.chip_clock_smooth = chip_clock_smooth + self.decay = decay + self.last_chip_clock = self.last_exp_mcu_clock = 0. + self.mcu_clock_avg = self.mcu_clock_variance = 0. + self.chip_clock_avg = self.chip_clock_covariance = 0. + def reset(self, mcu_clock, chip_clock): + self.mcu_clock_avg = self.last_mcu_clock = mcu_clock + self.chip_clock_avg = chip_clock + self.mcu_clock_variance = self.chip_clock_covariance = 0. + self.last_chip_clock = self.last_exp_mcu_clock = 0. + def update(self, mcu_clock, chip_clock): + # Update linear regression + decay = self.decay + diff_mcu_clock = mcu_clock - self.mcu_clock_avg + self.mcu_clock_avg += decay * diff_mcu_clock + self.mcu_clock_variance = (1. - decay) * ( + self.mcu_clock_variance + diff_mcu_clock**2 * decay) + diff_chip_clock = chip_clock - self.chip_clock_avg + self.chip_clock_avg += decay * diff_chip_clock + self.chip_clock_covariance = (1. - decay) * ( + self.chip_clock_covariance + diff_mcu_clock*diff_chip_clock*decay) + def set_last_chip_clock(self, chip_clock): + base_mcu, base_chip, inv_cfreq = self.get_clock_translation() + self.last_chip_clock = chip_clock + self.last_exp_mcu_clock = base_mcu + (chip_clock-base_chip) * inv_cfreq + def get_clock_translation(self): + inv_chip_freq = self.mcu_clock_variance / self.chip_clock_covariance + if not self.last_chip_clock: + return self.mcu_clock_avg, self.chip_clock_avg, inv_chip_freq + # Find mcu clock associated with future chip_clock + s_chip_clock = self.last_chip_clock + self.chip_clock_smooth + scdiff = s_chip_clock - self.chip_clock_avg + s_mcu_clock = self.mcu_clock_avg + scdiff * inv_chip_freq + # Calculate frequency to converge at future point + mdiff = s_mcu_clock - self.last_exp_mcu_clock + s_inv_chip_freq = mdiff / self.chip_clock_smooth + return self.last_exp_mcu_clock, self.last_chip_clock, s_inv_chip_freq + def get_time_translation(self): + base_mcu, base_chip, inv_cfreq = self.get_clock_translation() + clock_to_print_time = self.mcu.clock_to_print_time + base_time = clock_to_print_time(base_mcu) + inv_freq = clock_to_print_time(base_mcu + inv_cfreq) - base_time + return base_time, base_chip, inv_freq + +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() + MPU9250CommandHelper(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 = 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 + rx = twos_complement(xhigh << 8 | xlow, 16) + ry = twos_complement(yhigh << 8 | ylow, 16) + rz = twos_complement(zhigh << 8 | zlow, 16) + 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 MPU9250QueryHelper(self.printer, cconn) + +def load_config(config): + return MPU9250(config) + +def load_config_prefix(config): + return MPU9250(config) diff --git a/src/Makefile b/src/Makefile index 98c91a30..f5c32f1a 100644 --- a/src/Makefile +++ b/src/Makefile @@ -8,5 +8,6 @@ src-$(CONFIG_HAVE_GPIO_SPI) += spicmds.c thermocouple.c src-$(CONFIG_HAVE_GPIO_I2C) += i2ccmds.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/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/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); From 3f3713ee97ed58a5dcd8a196904a14e9c5b81ad9 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 16 Jun 2022 14:59:03 -0400 Subject: [PATCH 057/138] Kconfig: Move SERIAL, USBSERIAL, and CANSERIAL definitions to src/Kconfig There is no need to define these options in every board Kconfig file. Signed-off-by: Kevin O'Connor --- src/Kconfig | 6 ++++++ src/atsam/Kconfig | 4 ---- src/atsamd/Kconfig | 4 ---- src/lpc176x/Kconfig | 4 ---- src/rp2040/Kconfig | 4 ---- src/stm32/Kconfig | 6 ------ 6 files changed, 6 insertions(+), 22 deletions(-) diff --git a/src/Kconfig b/src/Kconfig index bb3ef3c2..eba05243 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,6 +53,8 @@ config SERIAL_BAUD to 250000. Read the FAQ before changing this value. # Generic configuration options for USB +config USBSERIAL + bool config USB_VENDOR_ID default 0x1d50 config USB_DEVICE_ID @@ -74,6 +78,8 @@ config USB_SERIAL_NUMBER endmenu # Generic configuration options for CANbus +config CANSERIAL + bool config CANBUS_FREQUENCY int "CAN bus speed" if LOW_LEVEL_OPTIONS && CANSERIAL default 500000 diff --git a/src/atsam/Kconfig b/src/atsam/Kconfig index 36785cfe..04c3c27f 100644 --- a/src/atsam/Kconfig +++ b/src/atsam/Kconfig @@ -89,10 +89,6 @@ config STACK_SIZE int default 512 -config USBSERIAL - bool -config SERIAL - bool choice prompt "Communication interface" config ATSAM_USB diff --git a/src/atsamd/Kconfig b/src/atsamd/Kconfig index 57c99c9f..baf9978d 100644 --- a/src/atsamd/Kconfig +++ b/src/atsamd/Kconfig @@ -134,10 +134,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/lpc176x/Kconfig b/src/lpc176x/Kconfig index f40c3943..def71fb0 100644 --- a/src/lpc176x/Kconfig +++ b/src/lpc176x/Kconfig @@ -62,10 +62,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/rp2040/Kconfig b/src/rp2040/Kconfig index c2a45e55..2b604e8c 100644 --- a/src/rp2040/Kconfig +++ b/src/rp2040/Kconfig @@ -51,10 +51,6 @@ config FLASH_START # Communication inteface ###################################################################### -config USBSERIAL - bool -config SERIAL - bool choice prompt "Communication interface" config RP2040_USB diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index 875d138e..ee79b373 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -275,12 +275,6 @@ config STM32F0_TRIM # Communication inteface ###################################################################### -config USBSERIAL - bool -config SERIAL - bool -config CANSERIAL - bool choice prompt "Communication interface" config STM32_USB_PA11_PA12 From 55d1c3728df1f2411dfe17bb4d52e1e1280f68f8 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 16 Jun 2022 18:38:31 -0400 Subject: [PATCH 058/138] docs: Note that i2c is not noise resilient in Config_Reference.md Signed-off-by: Kevin O'Connor --- docs/Config_Reference.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 8d58e562..9d720d3c 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -4199,6 +4199,13 @@ 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. + ``` #i2c_address: # The i2c address of the device. This must specified as a decimal @@ -4212,6 +4219,7 @@ 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. ``` From 78454dd3b13ecd8970266dcf474e7cfc68e60d1d Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Thu, 16 Jun 2022 20:09:56 -0400 Subject: [PATCH 059/138] bed_mesh: cache mesh status Prevent calls to `get_status()` from creating a new status dict on each request. Signed-off-by: Eric Callahan --- klippy/extras/bed_mesh.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) 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" From 6af931c4e1c5bf6f9590d0d6ad13d76e2594de77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Fr=C3=B6hlke?= <67454162+BOAndy1985@users.noreply.github.com> Date: Sat, 18 Jun 2022 19:08:30 +0200 Subject: [PATCH 060/138] spi_flash: add mks_monster8,robin_v3 (#5568) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by Andreas Fröhlke --- scripts/spi_flash/board_defs.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/spi_flash/board_defs.py b/scripts/spi_flash/board_defs.py index bb23db15..8ee48672 100644 --- a/scripts/spi_flash/board_defs.py +++ b/scripts/spi_flash/board_defs.py @@ -65,6 +65,11 @@ BOARD_DEFS = { 'mcu': 'stm32h743xx', 'spi_bus': 'spi3a', 'cs_pin': 'PA15' + }, + 'monster8': { + 'mcu': "stm32f407xx", + 'spi_bus': "spi3a", + "cs_pin": "PC9" } } @@ -99,7 +104,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(): From f2a5800cea0d87952af1a3e079eea9b3a7aaf43c Mon Sep 17 00:00:00 2001 From: Kurt Haenen Date: Mon, 20 Jun 2022 18:10:57 +0200 Subject: [PATCH 061/138] configfile: Expose options awaiting to be saved (#5270) Adds a save_config_pending_items to the status reported by configfile reflecting the items and values that a future SAVE_CONFIG would actually persist. Signed-off-by: Kurt Haenen --- docs/Status_Reference.md | 2 ++ klippy/configfile.py | 25 ++++++++++++++++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/docs/Status_Reference.md b/docs/Status_Reference.md index a515901b..4cc7512f 100644 --- a/docs/Status_Reference.md +++ b/docs/Status_Reference.md @@ -41,6 +41,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 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(): From 0256967defb760d2dc99c13728a0b969ae549f67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Mon, 20 Jun 2022 18:13:02 +0200 Subject: [PATCH 062/138] stm32: support stm32f401 adc_temperature sensor (#5572) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit > The temperature sensor is internally connected to the ADC_IN18 > The TSVREFE bit must be set to enable the conversion of both internal channels: the ADC1_IN16 or ADC1_IN18 (temperature sensor) and the ADC1_IN17 (VREFINT). Ref.: https://www.st.com/resource/en/reference_manual/dm00096844-stm32f401xb-c-and-stm32f401xd-e-advanced-arm-based-32-bit-mcus-stmicroelectronics.pdf Signed-off-by: Kamil Trzciński --- src/stm32/adc.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 { From cf9d96434c0216c695ee1907a12930a0b8e6759c Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 20 Jun 2022 12:37:23 -0400 Subject: [PATCH 063/138] stm32: Add Kconfig option for stm32f103x6 chip (with only 10KiB ram) Signed-off-by: Kevin O'Connor --- src/stm32/Kconfig | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index ee79b373..b1035ec1 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -75,6 +75,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 @@ -156,7 +160,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 From aea847501fb069736b1ee481ca806742179763bc Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 16 Jun 2022 11:31:37 -0400 Subject: [PATCH 064/138] test: Add mpu9250 to input_shaper.test regression test case Signed-off-by: Kevin O'Connor --- test/klippy/input_shaper.cfg | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 From 46842026b98a13b2497a9ef546a1b01a40fade68 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 16 Jun 2022 11:33:07 -0400 Subject: [PATCH 065/138] docs: Wrap lines in Measuring_Resonances.md Signed-off-by: Kevin O'Connor --- docs/Measuring_Resonances.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/Measuring_Resonances.md b/docs/Measuring_Resonances.md index 42bb9fb7..263a25b4 100644 --- a/docs/Measuring_Resonances.md +++ b/docs/Measuring_Resonances.md @@ -31,7 +31,8 @@ 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. +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 | From f0ba3a8c5218e60d77ede400593d0f838cc5ce6a Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 16 Jun 2022 11:36:14 -0400 Subject: [PATCH 066/138] mpu9250: Use adxl345.AccelQueryHelper directly The MPU9250QueryHelper() class is a duplicate of ADXL345QueryHelper(). Rename ADXL345QueryHelper() to AccelQueryHelper() and use it directly from mpu9250.py . Signed-off-by: Kevin O'Connor --- klippy/extras/adxl345.py | 4 +-- klippy/extras/mpu9250.py | 75 ++-------------------------------------- 2 files changed, 4 insertions(+), 75 deletions(-) diff --git a/klippy/extras/adxl345.py b/klippy/extras/adxl345.py index ad973698..c75da5ae 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 @@ -432,7 +432,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/mpu9250.py b/klippy/extras/mpu9250.py index a8f35bc6..813a9397 100644 --- a/klippy/extras/mpu9250.py +++ b/klippy/extras/mpu9250.py @@ -5,7 +5,7 @@ # # 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 +from . import bus, motion_report, adxl345 MPU9250_ADDR = 0x68 @@ -48,77 +48,6 @@ def twos_complement(val, nbits): val = val - (1 << nbits) return val -# Helper class to obtain measurements -class MPU9250QueryHelper: - def __init__(self, printer, cconn): - self.printer = printer - self.cconn = cconn - print_time = printer.lookup_object('toolhead').get_last_move_time() - self.request_start_time = self.request_end_time = print_time - self.samples = self.raw_samples = [] - def finish_measurements(self): - toolhead = self.printer.lookup_object('toolhead') - self.request_end_time = toolhead.get_last_move_time() - toolhead.wait_moves() - self.cconn.finalize() - def _get_raw_samples(self): - raw_samples = self.cconn.get_messages() - if raw_samples: - self.raw_samples = raw_samples - return self.raw_samples - def has_valid_samples(self): - raw_samples = self._get_raw_samples() - for msg in raw_samples: - data = msg['params']['data'] - first_sample_time = data[0][0] - last_sample_time = data[-1][0] - if (first_sample_time > self.request_end_time - or last_sample_time < self.request_start_time): - continue - # The time intervals [first_sample_time, last_sample_time] - # and [request_start_time, request_end_time] have non-zero - # intersection. It is still theoretically possible that none - # of the samples from raw_samples fall into the time interval - # [request_start_time, request_end_time] if it is too narrow - # or on very heavy data losses. In practice, that interval - # is at least 1 second, so this possibility is negligible. - return True - return False - def get_samples(self): - raw_samples = self._get_raw_samples() - if not raw_samples: - return self.samples - total = sum([len(m['params']['data']) for m in raw_samples]) - count = 0 - self.samples = samples = [None] * total - for msg in raw_samples: - for samp_time, x, y, z in msg['params']['data']: - if samp_time < self.request_start_time: - continue - if samp_time > self.request_end_time: - break - samples[count] = Accel_Measurement(samp_time, x, y, z) - count += 1 - del samples[count:] - return self.samples - def write_to_file(self, filename): - def write_impl(): - try: - # Try to re-nice writing process - os.nice(20) - except: - pass - f = open(filename, "w") - f.write("#time,accel_x,accel_y,accel_z\n") - samples = self.samples or self.get_samples() - for t, accel_x, accel_y, accel_z in samples: - f.write("%.6f,%.6f,%.6f,%.6f\n" % ( - t, accel_x, accel_y, accel_z)) - f.close() - write_proc = multiprocessing.Process(target=write_impl) - write_proc.daemon = True - write_proc.start() - # Helper class for G-Code commands class MPU9250CommandHelper: def __init__(self, config, chip): @@ -452,7 +381,7 @@ class MPU9250: web_request.send({'header': hdr}) def start_internal_client(self): cconn = self.api_dump.add_internal_client() - return MPU9250QueryHelper(self.printer, cconn) + return adxl345.AccelQueryHelper(self.printer, cconn) def load_config(config): return MPU9250(config) From 4a7b429115d7a3a27fe135d7e47674bb27a70c55 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 16 Jun 2022 11:39:04 -0400 Subject: [PATCH 067/138] mpu9250: Use adxl345.ClockSyncRegression directly The mpu9250.ClockSyncRegression() class is a duplicate of adxl345.ClockSyncRegression(). Remove the duplicate copy and use the code from adxl345.py . Signed-off-by: Kevin O'Connor --- klippy/extras/mpu9250.py | 50 +--------------------------------------- 1 file changed, 1 insertion(+), 49 deletions(-) diff --git a/klippy/extras/mpu9250.py b/klippy/extras/mpu9250.py index 813a9397..6fc0dcf0 100644 --- a/klippy/extras/mpu9250.py +++ b/klippy/extras/mpu9250.py @@ -117,54 +117,6 @@ class MPU9250CommandHelper: val = gcmd.get("VAL", minval=0, maxval=255, parser=lambda x: int(x, 0)) self.chip.set_reg(reg, val) -# Helper class for chip clock synchronization via linear regression -class ClockSyncRegression: - def __init__(self, mcu, chip_clock_smooth, decay = 1. / 20.): - self.mcu = mcu - self.chip_clock_smooth = chip_clock_smooth - self.decay = decay - self.last_chip_clock = self.last_exp_mcu_clock = 0. - self.mcu_clock_avg = self.mcu_clock_variance = 0. - self.chip_clock_avg = self.chip_clock_covariance = 0. - def reset(self, mcu_clock, chip_clock): - self.mcu_clock_avg = self.last_mcu_clock = mcu_clock - self.chip_clock_avg = chip_clock - self.mcu_clock_variance = self.chip_clock_covariance = 0. - self.last_chip_clock = self.last_exp_mcu_clock = 0. - def update(self, mcu_clock, chip_clock): - # Update linear regression - decay = self.decay - diff_mcu_clock = mcu_clock - self.mcu_clock_avg - self.mcu_clock_avg += decay * diff_mcu_clock - self.mcu_clock_variance = (1. - decay) * ( - self.mcu_clock_variance + diff_mcu_clock**2 * decay) - diff_chip_clock = chip_clock - self.chip_clock_avg - self.chip_clock_avg += decay * diff_chip_clock - self.chip_clock_covariance = (1. - decay) * ( - self.chip_clock_covariance + diff_mcu_clock*diff_chip_clock*decay) - def set_last_chip_clock(self, chip_clock): - base_mcu, base_chip, inv_cfreq = self.get_clock_translation() - self.last_chip_clock = chip_clock - self.last_exp_mcu_clock = base_mcu + (chip_clock-base_chip) * inv_cfreq - def get_clock_translation(self): - inv_chip_freq = self.mcu_clock_variance / self.chip_clock_covariance - if not self.last_chip_clock: - return self.mcu_clock_avg, self.chip_clock_avg, inv_chip_freq - # Find mcu clock associated with future chip_clock - s_chip_clock = self.last_chip_clock + self.chip_clock_smooth - scdiff = s_chip_clock - self.chip_clock_avg - s_mcu_clock = self.mcu_clock_avg + scdiff * inv_chip_freq - # Calculate frequency to converge at future point - mdiff = s_mcu_clock - self.last_exp_mcu_clock - s_inv_chip_freq = mdiff / self.chip_clock_smooth - return self.last_exp_mcu_clock, self.last_chip_clock, s_inv_chip_freq - def get_time_translation(self): - base_mcu, base_chip, inv_cfreq = self.get_clock_translation() - clock_to_print_time = self.mcu.clock_to_print_time - base_time = clock_to_print_time(base_mcu) - inv_freq = clock_to_print_time(base_mcu + inv_cfreq) - base_time - return base_time, base_chip, inv_freq - MIN_MSG_TIME = 0.100 BYTES_PER_SAMPLE = 6 @@ -201,7 +153,7 @@ class MPU9250: # Clock tracking self.last_sequence = self.max_query_duration = 0 self.last_limit_count = self.last_error_count = 0 - self.clock_sync = ClockSyncRegression(self.mcu, 640) + 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) From f6734d83b3015769372daffd80c68ecee6c84593 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 16 Jun 2022 11:54:31 -0400 Subject: [PATCH 068/138] mpu9250: Use adxl345.AccelCommandHelper directly The MPU9250CommandHelper() class is nearly a duplicate of ADXL345CommandHelper(). Rename ADXL345QueryHelper() to AccelCommandHelper and remove user facing references to "adxl345". Use it directly from mpu9250.py . Signed-off-by: Kevin O'Connor --- klippy/extras/adxl345.py | 37 +++++++++++---------- klippy/extras/mpu9250.py | 71 +--------------------------------------- 2 files changed, 21 insertions(+), 87 deletions(-) diff --git a/klippy/extras/adxl345.py b/klippy/extras/adxl345.py index c75da5ae..4ef8df07 100644 --- a/klippy/extras/adxl345.py +++ b/klippy/extras/adxl345.py @@ -101,15 +101,18 @@ class AccelQueryHelper: 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)} diff --git a/klippy/extras/mpu9250.py b/klippy/extras/mpu9250.py index 6fc0dcf0..bd882945 100644 --- a/klippy/extras/mpu9250.py +++ b/klippy/extras/mpu9250.py @@ -48,75 +48,6 @@ def twos_complement(val, nbits): val = val - (1 << nbits) return val -# Helper class for G-Code commands -class MPU9250CommandHelper: - def __init__(self, config, chip): - self.printer = config.get_printer() - self.chip = chip - self.bg_client = None - self.name = config.get_name().split()[-1] - self.register_commands(self.name) - if self.name == "mpu9250": - self.register_commands(None) - def register_commands(self, name): - # Register commands - gcode = self.printer.lookup_object('gcode') - gcode.register_mux_command("ACCELEROMETER_MEASURE", "CHIP", name, - self.cmd_ACCELEROMETER_MEASURE, - desc=self.cmd_ACCELEROMETER_MEASURE_help) - gcode.register_mux_command("ACCELEROMETER_QUERY", "CHIP", name, - self.cmd_ACCELEROMETER_QUERY, - desc=self.cmd_ACCELEROMETER_QUERY_help) - gcode.register_mux_command("ACCELEROMETER_DEBUG_READ", "CHIP", name, - self.cmd_ACCELEROMETER_DEBUG_READ, - desc=self.cmd_ACCELEROMETER_DEBUG_READ_help) - gcode.register_mux_command("ACCELEROMETER_DEBUG_WRITE", "CHIP", name, - self.cmd_ACCELEROMETER_DEBUG_WRITE, - desc=self.cmd_ACCELEROMETER_DEBUG_WRITE_help) - cmd_ACCELEROMETER_MEASURE_help = "Start/stop accelerometer" - def cmd_ACCELEROMETER_MEASURE(self, gcmd): - if self.bg_client is None: - # Start measurements - self.bg_client = self.chip.start_internal_client() - gcmd.respond_info("mpu9250 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 mpu9250 NAME parameter") - bg_client = self.bg_client - self.bg_client = None - bg_client.finish_measurements() - # Write data to file - if self.name == "mpu9250": - filename = "/tmp/mpu9250-%s.csv" % (name,) - else: - filename = "/tmp/mpu9250-%s-%s.csv" % (self.name, name,) - bg_client.write_to_file(filename) - gcmd.respond_info("Writing raw accelerometer data to %s file" - % (filename,)) - cmd_ACCELEROMETER_QUERY_help = "Query accelerometer for the current values" - def cmd_ACCELEROMETER_QUERY(self, gcmd): - aclient = self.chip.start_internal_client() - self.printer.lookup_object('toolhead').dwell(1.) - aclient.finish_measurements() - values = aclient.get_samples() - if not values: - raise gcmd.error("No mpu9250 measurements found") - _, accel_x, accel_y, accel_z = values[-1] - gcmd.respond_info("mpu9250 values (x, y, z): %.6f, %.6f, %.6f" - % (accel_x, accel_y, accel_z)) - cmd_ACCELEROMETER_DEBUG_READ_help = "Query mpu9250 register (for debugging)" - def cmd_ACCELEROMETER_DEBUG_READ(self, gcmd): - reg = gcmd.get("REG", minval=0, maxval=126, parser=lambda x: int(x, 0)) - val = self.chip.read_reg(reg) - gcmd.respond_info("MPU9250 REG[0x%x] = 0x%x" % (reg, val)) - cmd_ACCELEROMETER_DEBUG_WRITE_help = "Set mpu9250 register (for debugging)" - def cmd_ACCELEROMETER_DEBUG_WRITE(self, gcmd): - 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) - MIN_MSG_TIME = 0.100 BYTES_PER_SAMPLE = 6 @@ -126,7 +57,7 @@ SAMPLES_PER_BLOCK = 8 class MPU9250: def __init__(self, config): self.printer = config.get_printer() - MPU9250CommandHelper(config, self) + 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)} From 98a24172e7f238823a6faf2096980ecc923ac1a9 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 16 Jun 2022 12:05:08 -0400 Subject: [PATCH 069/138] docs: Add mpu9250 to Config_Reference.md Signed-off-by: Kevin O'Connor --- docs/Config_Reference.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 9d720d3c..ac77b941 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -1525,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: +# See the "common I2C settings" section for a description of the +# above parameters. +#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. From 247a409335ff48134dcb6abc41d2c464b465a6ec Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 16 Jun 2022 12:13:12 -0400 Subject: [PATCH 070/138] mpu9250: Inline twos_complement() code Calling python functions can have high overhead. Inline the twos_complement code in the _extract_samples() inner loop. Signed-off-by: Kevin O'Connor --- klippy/extras/mpu9250.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/klippy/extras/mpu9250.py b/klippy/extras/mpu9250.py index bd882945..c588eaf0 100644 --- a/klippy/extras/mpu9250.py +++ b/klippy/extras/mpu9250.py @@ -42,12 +42,6 @@ FIFO_SIZE = 512 Accel_Measurement = collections.namedtuple( 'Accel_Measurement', ('time', 'accel_x', 'accel_y', 'accel_z')) -# Helper method for getting the two's complement value of an unsigned int -def twos_complement(val, nbits): - if (val & (1 << (nbits - 1))) != 0: - val = val - (1 << nbits) - return val - MIN_MSG_TIME = 0.100 BYTES_PER_SAMPLE = 6 @@ -139,11 +133,12 @@ class MPU9250: 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 - rx = twos_complement(xhigh << 8 | xlow, 16) - ry = twos_complement(yhigh << 8 | ylow, 16) - rz = twos_complement(zhigh << 8 | zlow, 16) - raw_xyz = (rx, ry, rz) + # 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) From f2e27ae05eca2dac3f747d8cebfd482a535e9bb9 Mon Sep 17 00:00:00 2001 From: Dmitry Butyugin Date: Thu, 16 Jun 2022 23:10:57 +0200 Subject: [PATCH 071/138] docs: Updated accelerometer installation instructions for Python 3 Signed-off-by: Dmitry Butyugin --- docs/Measuring_Resonances.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/Measuring_Resonances.md b/docs/Measuring_Resonances.md index 263a25b4..53a60013 100644 --- a/docs/Measuring_Resonances.md +++ b/docs/Measuring_Resonances.md @@ -76,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 From 8804c1578de1422c8017299cc77abd438aede685 Mon Sep 17 00:00:00 2001 From: Dmitry Butyugin Date: Thu, 16 Jun 2022 23:19:30 +0200 Subject: [PATCH 072/138] docs: Added instructions how to configure I2C for MPU-9250 Signed-off-by: Dmitry Butyugin --- docs/Measuring_Resonances.md | 4 +++- docs/RPi_microcontroller.md | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/Measuring_Resonances.md b/docs/Measuring_Resonances.md index 53a60013..7f41bd4d 100644 --- a/docs/Measuring_Resonances.md +++ b/docs/Measuring_Resonances.md @@ -115,7 +115,9 @@ 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: +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 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 From 52a8afba43e4997764283b868c017bc261a56896 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 20 Jun 2022 12:58:05 -0400 Subject: [PATCH 073/138] docs: Note mcu types with i2c_speed support in Config_Reference.md Signed-off-by: Kevin O'Connor --- docs/Config_Reference.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index ac77b941..be33c723 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -1536,9 +1536,9 @@ number of sections with an "mpu9250" prefix). # Default is 104 (0x68). #i2c_mcu: #i2c_bus: -#i2c_speed: +#i2c_speed: 400000 # See the "common I2C settings" section for a description of the -# above parameters. +# above parameters. The default "i2c_speed" is 400000. #axes_map: x, y, z # See the "adxl345" section for information on this parameter. ``` @@ -4224,6 +4224,15 @@ 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 From 9e52dc337fdae5f4cb98744f75cdcd61a1fa2c07 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 18 Jun 2022 12:23:42 -0400 Subject: [PATCH 074/138] rp2040: Add options to Kconfig for "stage2" flash chip options Signed-off-by: Kevin O'Connor --- src/rp2040/Kconfig | 14 ++++++++++++++ src/rp2040/Makefile | 5 +++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/rp2040/Kconfig b/src/rp2040/Kconfig index 2b604e8c..95db68bc 100644 --- a/src/rp2040/Kconfig +++ b/src/rp2040/Kconfig @@ -47,6 +47,20 @@ config FLASH_START hex default 0x10000000 + +###################################################################### +# Bootloader options +###################################################################### + +config RP2040_STAGE2_FILE + string + default "boot2_w25q080.S" + +config RP2040_STAGE2_CLKDIV + int + default 2 + + ###################################################################### # Communication inteface ###################################################################### diff --git a/src/rp2040/Makefile b/src/rp2040/Makefile index f15302d1..ffc2c4c8 100644 --- a/src/rp2040/Makefile +++ b/src/rp2040/Makefile @@ -24,9 +24,10 @@ 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 From d3c4ba4839dd7a4339ae024752e6c6424884c185 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 18 Jun 2022 12:38:58 -0400 Subject: [PATCH 075/138] rp2040: Add a Kconfig option for selecting "generic_03H" flash type Signed-off-by: Kevin O'Connor --- lib/rp2040/boot_stage2/boot2_generic_03h.S | 2 +- lib/rp2040/rp2040.patch | 13 +++++++++++++ src/rp2040/Kconfig | 10 ++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) 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/src/rp2040/Kconfig b/src/rp2040/Kconfig index 95db68bc..8bad20c9 100644 --- a/src/rp2040/Kconfig +++ b/src/rp2040/Kconfig @@ -52,12 +52,22 @@ config FLASH_START # 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 From ae17b66a8825a443a762ab56adc0e7fa38e50733 Mon Sep 17 00:00:00 2001 From: Yifei Ding Date: Mon, 27 Jun 2022 06:58:48 -0700 Subject: [PATCH 076/138] docs: remove FAQ ToC (#5585) Signed-off-by: Yifei Ding --- docs/FAQ.md | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index daa92c62..598d5800 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -1,30 +1,5 @@ # 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: From a431900f7f90b8dca56bed5e5a2564411e780ee9 Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Tue, 21 Jun 2022 12:33:11 -0400 Subject: [PATCH 077/138] display_status: Implement SET_DISPLAY_TEXT command Signed-off-by: Eric Callahan --- klippy/extras/display_status.py | 6 ++++++ 1 file changed, 6 insertions(+) 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) From 6ad6e39ad227a6d782946501e95627a6843beb04 Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Tue, 21 Jun 2022 12:39:00 -0400 Subject: [PATCH 078/138] docs: add SET_DISPLAY_TEXT documentation Signed-off-by: Eric Callahan --- docs/Command_Templates.md | 9 +++++++-- docs/G-Codes.md | 5 +++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/Command_Templates.md b/docs/Command_Templates.md index 3aa68c13..c89d85bc 100644 --- a/docs/Command_Templates.md +++ b/docs/Command_Templates.md @@ -138,8 +138,13 @@ This is quite useful if you want to change the behavior of certain commands like [gcode_macro M117] rename_existing: M117.1 gcode: - M117.1 { rawparams } - M118 { rawparams } + {% if rawparams %} + {% set escaped_msg = rawparams|replace('"', '\\"') %} + SET_DISPLAY_TEXT MSG="{escaped_msg}" + RESPOND TYPE=command MSG="{escaped_msg}" + {% else %} + SET_DISPLAY_TEXT + {% endif %} ``` ### The "printer" Variable diff --git a/docs/G-Codes.md b/docs/G-Codes.md index 74757a48..78949980 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 From 045455648a34a113e73ee9056466b5e5047a0f17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Fri, 24 Jun 2022 13:17:20 +0200 Subject: [PATCH 079/138] klippy: properly set log level when logging to stderr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `logging.basicConfig` does not reconfigure default logger. This results in printing only warnings/errors to stderr instead of also info (or debug). This fixes the issue by setting log level on root logger. Signed-off-by: Kamil Trzciński --- klippy/klippy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/klippy/klippy.py b/klippy/klippy.py index d7b62453..dbd3cd37 100644 --- a/klippy/klippy.py +++ b/klippy/klippy.py @@ -342,7 +342,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() From 84b2bfe3135990983d55fd4d530139cf8ddb1f33 Mon Sep 17 00:00:00 2001 From: adelyser <12093019+adelyser@users.noreply.github.com> Date: Mon, 27 Jun 2022 08:16:09 -0600 Subject: [PATCH 080/138] stm32: Add MCU temp for Stm32h7 (#5606) Added mcu temperature to the stm32h7 processor. Signed-off-by: Aaron DeLyser --- klippy/extras/temperature_mcu.py | 6 ++++++ src/stm32/stm32h7_adc.c | 13 +++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) 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/src/stm32/stm32h7_adc.c b/src/stm32/stm32h7_adc.c index 2733b24f..ca149d3d 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 }; From f5d5f53914d46dfccdd21399e44f8af71a8f345a Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 11 Jun 2022 18:19:53 -0400 Subject: [PATCH 081/138] stm32: Add support for disabling the canbus filter Signed-off-by: Kevin O'Connor --- src/Kconfig | 3 +++ src/stm32/can.c | 20 +++++++++++++------- src/stm32/fdcan.c | 2 ++ 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/Kconfig b/src/Kconfig index eba05243..79cb4182 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -83,6 +83,9 @@ config CANSERIAL config CANBUS_FREQUENCY int "CAN bus speed" if LOW_LEVEL_OPTIONS && CANSERIAL default 500000 +config CANBUS_FILTER + bool + default y if CANSERIAL # Support setting gpio state at startup config INITIAL_PINS diff --git a/src/stm32/can.c b/src/stm32/can.c index 9cd72209..6e50cea9 100644 --- a/src/stm32/can.c +++ b/src/stm32/can.c @@ -136,13 +136,19 @@ canbus_set_filter(uint32_t id) /* Initialisation mode for the filter */ SOC_CAN->FA1R = 0; - 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; + 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); diff --git a/src/stm32/fdcan.c b/src/stm32/fdcan.c index cf2cc276..3af9ffa8 100755 --- a/src/stm32/fdcan.c +++ b/src/stm32/fdcan.c @@ -113,6 +113,8 @@ can_filter(uint32_t index, uint32_t id) void canbus_set_filter(uint32_t id) { + if (!CONFIG_CANBUS_FILTER) + return; /* Request initialisation */ SOC_CAN->CCCR |= FDCAN_CCCR_INIT; /* Wait the acknowledge */ From 11828387d93f1f68107858d98fc81e2e29b5edf6 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 11 Jun 2022 17:53:10 -0400 Subject: [PATCH 082/138] usb_cdc_ep: Change default endpoint numbers Change the default endpoint numbers to make it possible to implement the "gs_usb" canbus protocol. Signed-off-by: Kevin O'Connor --- src/atsamd/usbserial.c | 2 +- src/generic/usb_cdc_ep.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/atsamd/usbserial.c b/src/atsamd/usbserial.c index 273c3dbd..e3848926 100644 --- a/src/atsamd/usbserial.c +++ b/src/atsamd/usbserial.c @@ -26,7 +26,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, 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 From c8cc98ce5dce3771cf2728eee7b1bdb026504f75 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 11 Jun 2022 19:37:30 -0400 Subject: [PATCH 083/138] canserial: Rename canbus.c to canserial.c Rename the canbus.c code to canserial.c and introduce new wrapper functions in canbus.c that connect the low-level canbus hardware code to the high-level canserial.c code. This is in preparation for adding "usb to canbus bridge mode". Signed-off-by: Kevin O'Connor --- src/Kconfig | 5 +- src/generic/canbus.c | 342 ++------------------------------------- src/generic/canbus.h | 4 - src/generic/canserial.c | 343 ++++++++++++++++++++++++++++++++++++++++ src/generic/canserial.h | 19 +++ src/stm32/Makefile | 4 +- src/stm32/can.c | 1 + src/stm32/chipid.c | 6 +- src/stm32/fdcan.c | 1 + 9 files changed, 389 insertions(+), 336 deletions(-) create mode 100644 src/generic/canserial.c create mode 100644 src/generic/canserial.h diff --git a/src/Kconfig b/src/Kconfig index 79cb4182..921370f6 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -80,8 +80,11 @@ endmenu # Generic configuration options for CANbus config CANSERIAL bool +config CANBUS + bool + default y if CANSERIAL config CANBUS_FREQUENCY - int "CAN bus speed" if LOW_LEVEL_OPTIONS && CANSERIAL + int "CAN bus speed" if LOW_LEVEL_OPTIONS && CANBUS default 500000 config CANBUS_FILTER bool diff --git a/src/generic/canbus.c b/src/generic/canbus.c index 3cc051c8..a4f33aa1 100644 --- a/src/generic/canbus.c +++ b/src/generic/canbus.c @@ -1,342 +1,32 @@ -// 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 "board/armcm_reset.h" // try_request_canboot -#include "board/io.h" // readb -#include "board/irq.h" // irq_save -#include "board/misc.h" // console_sendf -#include "canbus.h" // canbus_set_uuid -#include "command.h" // DECL_CONSTANT -#include "fasthash.h" // fasthash64 -#include "sched.h" // sched_wake_task +#include "canbus.h" // canbus_send +#include "canserial.h" // canserial_send -#define CANBUS_UUID_LEN 6 +int +canserial_send(struct canbus_msg *msg) +{ + return canbus_send(msg); +} -// 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_set_filter(uint32_t id) +{ + canbus_set_filter(id); +} void canbus_notify_tx(void) { - sched_wake_task(&CanData.tx_wake); + canserial_notify_tx(); } -void -canbus_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 = canbus_send(&msg); - if (ret <= 0) - break; - tpos += now; - } - CanData.transmit_pos = tpos; -} -DECL_TASK(canbus_tx_task); - -// Encode and transmit a "response" message -void -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; - // 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; - canbus_notify_tx(); -} - - -/**************************************************************** - * 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 = canbus_send(&send); - if (ret >= 0) - return; - } -} - -static void -can_id_conflict(void) -{ - CanData.assigned_id = 0; - canbus_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; - canbus_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; - try_request_canboot(); -} - -// 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 -canbus_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) void canbus_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) - len = sizeof(CanData.receive_buf) - rpos; - memcpy(&CanData.receive_buf[rpos], msg->data, len); - CanData.receive_pos = rpos + len; - canbus_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; - uint32_t pos = pushp % ARRAY_SIZE(CanData.admin_queue); - memcpy(&CanData.admin_queue[pos], msg, sizeof(*msg)); - CanData.admin_push_pos = pushp + 1; - canbus_notify_rx(); - } + canserial_process_data(msg); } - -// 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; - canbus_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 -canbus_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(canbus_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 -canbus_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)); - 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 79cb3245..8032611a 100644 --- a/src/generic/canbus.h +++ b/src/generic/canbus.h @@ -3,9 +3,6 @@ #include // uint32_t -#define CANBUS_ID_ADMIN 0x3f0 -#define CANBUS_ID_ADMIN_RESP 0x3f1 - struct canbus_msg { uint32_t id; uint32_t dlc; @@ -27,6 +24,5 @@ void canbus_set_filter(uint32_t id); // canbus.c void canbus_notify_tx(void); void canbus_process_data(struct canbus_msg *msg); -void canbus_set_uuid(uint8_t *raw_uuid, uint32_t raw_uuid_len); #endif // canbus.h diff --git a/src/generic/canserial.c b/src/generic/canserial.c new file mode 100644 index 00000000..ba0ec461 --- /dev/null +++ b/src/generic/canserial.c @@ -0,0 +1,343 @@ +// 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/armcm_reset.h" // try_request_canboot +#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 +void +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; + // 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(); +} + + +/**************************************************************** + * 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; + try_request_canboot(); +} + +// 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) +void +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) + len = sizeof(CanData.receive_buf) - rpos; + 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; + 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(); + } +} + +// 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..ac4b7714 --- /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); +void canserial_process_data(struct canbus_msg *msg); +void canserial_set_uuid(uint8_t *raw_uuid, uint32_t raw_uuid_len); + +#endif // canbus.h diff --git a/src/stm32/Makefile b/src/stm32/Makefile index fa662f91..2fbf7549 100644 --- a/src/stm32/Makefile +++ b/src/stm32/Makefile @@ -59,10 +59,10 @@ serial-src-$(CONFIG_MACH_STM32F0) := stm32/stm32f0_serial.c serial-src-$(CONFIG_MACH_STM32G0) := stm32/stm32f0_serial.c serial-src-$(CONFIG_MACH_STM32H7) := stm32/stm32h7_serial.c src-$(CONFIG_SERIAL) += $(serial-src-y) generic/serial_irq.c -canbus-src-y := generic/canbus.c ../lib/fast-hash/fasthash.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) stm32/chipid.c +src-$(CONFIG_CANSERIAL) += $(canbus-src-y) generic/canbus.c stm32/chipid.c src-$(CONFIG_HAVE_GPIO_HARD_PWM) += stm32/hard_pwm.c # Binary output file rules diff --git a/src/stm32/can.c b/src/stm32/can.c index 6e50cea9..427a7855 100644 --- a/src/stm32/can.c +++ b/src/stm32/can.c @@ -12,6 +12,7 @@ #include "command.h" // DECL_CONSTANT_STR #include "generic/armcm_boot.h" // armcm_enable_irq #include "generic/canbus.h" // canbus_notify_tx +#include "generic/canserial.h" // CANBUS_ID_ADMIN #include "internal.h" // enable_pclock #include "sched.h" // DECL_INIT diff --git a/src/stm32/chipid.c b/src/stm32/chipid.c index 03517739..e30563cd 100644 --- a/src/stm32/chipid.c +++ b/src/stm32/chipid.c @@ -4,7 +4,7 @@ // // This file may be distributed under the terms of the GNU GPLv3 license. -#include "generic/canbus.h" // canbus_set_uuid +#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 @@ -29,7 +29,7 @@ chipid_init(void) if (CONFIG_USB_SERIAL_NUMBER_CHIPID) usb_fill_serial(&cdc_chipid.desc, ARRAY_SIZE(cdc_chipid.data) , (void*)UID_BASE); - if (CONFIG_CANSERIAL) - canbus_set_uuid((void*)UID_BASE, CHIP_UID_LEN); + 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 index 3af9ffa8..c142420e 100755 --- a/src/stm32/fdcan.c +++ b/src/stm32/fdcan.c @@ -12,6 +12,7 @@ #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 From 790ff4d8d7ac200527c272af3e58f0d9f06468e4 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 8 Jun 2022 21:03:11 -0400 Subject: [PATCH 084/138] usb_canbus: Initial support for USB to CAN bridge mode Support a USB interface that shows up as a canbus adapter to linux. Route both local and real canbus packets over that interface. Signed-off-by: Kevin O'Connor --- docs/CANBUS.md | 31 ++ src/Kconfig | 12 +- src/generic/canserial.c | 7 +- src/generic/canserial.h | 2 +- src/generic/usb_canbus.c | 675 +++++++++++++++++++++++++++++++++++++++ src/generic/usbstd.h | 6 + src/stm32/Kconfig | 62 +++- src/stm32/Makefile | 2 + 8 files changed, 782 insertions(+), 15 deletions(-) create mode 100644 src/generic/usb_canbus.c diff --git a/docs/CANBUS.md b/docs/CANBUS.md index fc85dc76..90efacf5 100644 --- a/docs/CANBUS.md +++ b/docs/CANBUS.md @@ -91,3 +91,34 @@ 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. Generally, this may require running + commands such as "ip up" to restart the interface. Thus, Klipper + FIRMWARE_RESTART commands (or regular RESTART after a config change) + may require restarting the `can0` interface. diff --git a/src/Kconfig b/src/Kconfig index 921370f6..d8d8a19e 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -55,22 +55,24 @@ config SERIAL_BAUD # Generic configuration options for USB config USBSERIAL bool +config USBCANBUS + bool config USB_VENDOR_ID default 0x1d50 config USB_DEVICE_ID default 0x614e config USB_SERIAL_NUMBER_CHIPID - depends on HAVE_CHIPID && USBSERIAL + depends on HAVE_CHIPID && (USBSERIAL || USBCANBUS) default y config USB_SERIAL_NUMBER default "12345" menu "USB ids" - depends on USBSERIAL && LOW_LEVEL_OPTIONS + depends on (USBSERIAL || USBCANBUS) && 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 @@ -82,7 +84,7 @@ config CANSERIAL bool config CANBUS bool - default y if CANSERIAL + default y if CANSERIAL || USBCANBUS config CANBUS_FREQUENCY int "CAN bus speed" if LOW_LEVEL_OPTIONS && CANBUS default 500000 diff --git a/src/generic/canserial.c b/src/generic/canserial.c index ba0ec461..34f0ce65 100644 --- a/src/generic/canserial.c +++ b/src/generic/canserial.c @@ -224,7 +224,7 @@ canserial_notify_rx(void) DECL_CONSTANT("RECEIVE_WINDOW", ARRAY_SIZE(CanData.receive_buf)); // Handle incoming data (called from IRQ handler) -void +int canserial_process_data(struct canbus_msg *msg) { uint32_t id = msg->id; @@ -233,7 +233,7 @@ canserial_process_data(struct canbus_msg *msg) int rpos = CanData.receive_pos; uint32_t len = CANMSG_DATA_LEN(msg); if (len > sizeof(CanData.receive_buf) - rpos) - len = sizeof(CanData.receive_buf) - rpos; + return -1; memcpy(&CanData.receive_buf[rpos], msg->data, len); CanData.receive_pos = rpos + len; canserial_notify_rx(); @@ -243,12 +243,13 @@ canserial_process_data(struct canbus_msg *msg) uint32_t pushp = CanData.admin_push_pos; if (pushp >= CanData.admin_pull_pos + ARRAY_SIZE(CanData.admin_queue)) // No space - drop message - return; + 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 diff --git a/src/generic/canserial.h b/src/generic/canserial.h index ac4b7714..d7a56d3b 100644 --- a/src/generic/canserial.h +++ b/src/generic/canserial.h @@ -13,7 +13,7 @@ void canserial_set_filter(uint32_t id); // canserial.c void canserial_notify_tx(void); -void canserial_process_data(struct canbus_msg *msg); +int canserial_process_data(struct canbus_msg *msg); void canserial_set_uuid(uint8_t *raw_uuid, uint32_t raw_uuid_len); #endif // canbus.h diff --git a/src/generic/usb_canbus.c b/src/generic/usb_canbus.c new file mode 100644 index 00000000..1631bebe --- /dev/null +++ b/src/generic/usb_canbus.c @@ -0,0 +1,675 @@ +// 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 "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, +}; + +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/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/stm32/Kconfig b/src/stm32/Kconfig index b1035ec1..355b132e 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -108,6 +108,12 @@ config HAVE_STM32_CANBUS config HAVE_STM32_FDCANBUS bool default y if MACH_STM32G0 +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 @@ -327,30 +333,74 @@ choice 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 || 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 select CANSERIAL - config STM32_CANBUS_PB0_PB1 + config STM32_MMENU_CANBUS_PB0_PB1 bool "CAN bus (on PB0/PB1)" 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 + config STM32_CMENU_CANBUS_PB0_PB1 + bool "CAN bus (on PB0/PB1)" + depends on HAVE_STM32_FDCANBUS endchoice + +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 + endif diff --git a/src/stm32/Makefile b/src/stm32/Makefile index 2fbf7549..2ff04f69 100644 --- a/src/stm32/Makefile +++ b/src/stm32/Makefile @@ -63,6 +63,8 @@ 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 # Binary output file rules From e952b021e89169637057c1ab40ec83b136b26b28 Mon Sep 17 00:00:00 2001 From: Bruno Melo <43672635+maxcalavera81@users.noreply.github.com> Date: Wed, 29 Jun 2022 23:07:26 +0100 Subject: [PATCH 085/138] config: Create printer-biqu-b1-se-plus.cfg (#5477) Signed-off-by: Bruno Melo --- config/printer-biqu-b1-se-plus-2022.cfg | 198 ++++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 config/printer-biqu-b1-se-plus-2022.cfg 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..e5b3af52 --- /dev/null +++ b/config/printer-biqu-b1-se-plus-2022.cfg @@ -0,0 +1,198 @@ +# 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 # exemple off bed mesh config +######################################## +[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 From 4404c98637e996939a6cc46657637b7b1734fa2e Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 29 Jun 2022 18:10:47 -0400 Subject: [PATCH 086/138] test: Add printer-biqu-b1-se-plus-2022.cfg to printers.test Signed-off-by: Kevin O'Connor --- config/printer-biqu-b1-se-plus-2022.cfg | 6 +----- test/klippy/printers.test | 1 + 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/config/printer-biqu-b1-se-plus-2022.cfg b/config/printer-biqu-b1-se-plus-2022.cfg index e5b3af52..db7238c9 100644 --- a/config/printer-biqu-b1-se-plus-2022.cfg +++ b/config/printer-biqu-b1-se-plus-2022.cfg @@ -99,7 +99,6 @@ max_temp: 250 uart_pin: PC6 run_current: 0.800 stealthchop_threshold: 999999 -######################################## ######################################## # Heater Bed Pins @@ -150,7 +149,7 @@ pin: PE5 value: 1 ######################################## -# Bed Mesh configuration # exemple off bed mesh config +# Bed Mesh configuration ######################################## [bed_mesh] speed: 2000 @@ -179,7 +178,6 @@ pin: PB5 max_power: 1.0 kick_start_time: 0.100 heater: extruder -######################################## ######################################## # Filament Sensor configuration @@ -188,8 +186,6 @@ heater: extruder switch_pin: !PC2 pause_on_runout: true #pause handled by macro -######################################## - ######################################## # Motor Power Pin ######################################## diff --git a/test/klippy/printers.test b/test/klippy/printers.test index e0b0ecc5..e4ab8837 100644 --- a/test/klippy/printers.test +++ b/test/klippy/printers.test @@ -198,6 +198,7 @@ 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-prusa-mini-plus-2020.cfg +CONFIG ../../config/printer-biqu-b1-se-plus-2022.cfg # Printers using the stm32f446 DICTIONARY stm32f446.dict From a8f08b08ca0b1c47312338438fe81809531e4cdb Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 29 Jun 2022 19:37:52 -0400 Subject: [PATCH 087/138] test: Fix ordering of printers.test Signed-off-by: Kevin O'Connor --- test/klippy/printers.test | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/klippy/printers.test b/test/klippy/printers.test index e4ab8837..57db5e04 100644 --- a/test/klippy/printers.test +++ b/test/klippy/printers.test @@ -29,6 +29,7 @@ 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-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 @@ -57,7 +58,6 @@ CONFIG ../../config/printer-wanhao-duplicator-i3-plus-2017.cfg CONFIG ../../config/printer-wanhao-duplicator-i3-plus-mark2-2019.cfg CONFIG ../../config/printer-wanhao-duplicator-6-2016.cfg CONFIG ../../config/printer-wanhao-duplicator-9-2018.cfg -CONFIG ../../config/printer-creality-cr10-v3-2020.cfg # Printers using the atmega1280 DICTIONARY atmega1280.dict @@ -135,13 +135,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 @@ -197,8 +197,8 @@ 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-prusa-mini-plus-2020.cfg CONFIG ../../config/printer-biqu-b1-se-plus-2022.cfg +CONFIG ../../config/printer-prusa-mini-plus-2020.cfg # Printers using the stm32f446 DICTIONARY stm32f446.dict From 167736ad1c127735806ba06858bc74c8ce6d49df Mon Sep 17 00:00:00 2001 From: chestwood96 Date: Thu, 30 Jun 2022 19:56:35 +0200 Subject: [PATCH 088/138] respond: No forced spaces (#5152) Signed-off-by: Adrian Joachim --- docs/G-Codes.md | 2 ++ klippy/extras/respond.py | 13 ++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/G-Codes.md b/docs/G-Codes.md index 78949980..afa808c8 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -1010,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`). 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) From 1636a9759bc2d5f162312ac8bf5823e95e0ad053 Mon Sep 17 00:00:00 2001 From: BIGTREETECH <38851044+bigtreetech@users.noreply.github.com> Date: Fri, 1 Jul 2022 01:58:00 +0800 Subject: [PATCH 089/138] stm32: stm32g0/h7 usb_dfu_bootloader support (#5596) Signed-off-by: Alan.Ma from BigTreeTech --- scripts/flash_usb.py | 3 ++- src/stm32/stm32g0.c | 3 ++- src/stm32/stm32h7.c | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/scripts/flash_usb.py b/scripts/flash_usb.py index 4004ad55..3d62a643 100755 --- a/scripts/flash_usb.py +++ b/scripts/flash_usb.py @@ -339,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/src/stm32/stm32g0.c b/src/stm32/stm32g0.c index f38fc9c5..36520dfb 100644 --- a/src/stm32/stm32g0.c +++ b/src/stm32/stm32g0.c @@ -147,7 +147,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) @@ -164,6 +163,8 @@ armcm_main(void) RCC->APBENR1 = 0x00000000; RCC->APBENR2 = 0x00000000; + check_usb_dfu_bootloader(); + // Set flash latency FLASH->ACR = (2<D1CCIPR = 0x00000000; + RCC->D2CCIP1R = 0x00000000; + RCC->D2CCIP2R = 0x00000000; + RCC->D3CCIPR = 0x00000000; SCB->VTOR = (uint32_t)VectorTable; + check_usb_dfu_bootloader(); + clock_setup(); sched_main(); From f10fd7c2fabec78f5b30a2f3e6ec79cd8a3e4805 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 2 Jun 2022 12:40:18 -0400 Subject: [PATCH 090/138] lib: Add can2040 code The "can2040" project implements "software canbus" support on rp2040 micro-controllers. Signed-off-by: Kevin O'Connor --- lib/README | 4 + lib/can2040/can2040.c | 1230 +++++++++++++++++++++++++++++++++++++++++ lib/can2040/can2040.h | 78 +++ 3 files changed, 1312 insertions(+) create mode 100644 lib/can2040/can2040.c create mode 100644 lib/can2040/can2040.h diff --git a/lib/README b/lib/README index 3e243b9b..ef9b5a98 100644 --- a/lib/README +++ b/lib/README @@ -128,3 +128,7 @@ The canboot directory contains code from: 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 17b8ace15584077cd0bf0c3e038c2a2a8edd70ed. diff --git a/lib/can2040/can2040.c b/lib/can2040/can2040.c new file mode 100644 index 00000000..eafefa16 --- /dev/null +++ b/lib/can2040/can2040.c @@ -0,0 +1,1230 @@ +// 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_ack_no_match 18u +#define can2040_offset_ack_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 "ack" state machine (state machine 2) +static void +pio_ack_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_ack_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_ack_no_match; // jmp ack_no_match +} + +// 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 +} + +// 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; +} + +// 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 irq 2 and 3 + // 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); + 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 "ack" state machine to check a given CRC sequence +static void +pio_ack_check(struct can2040 *cd, uint32_t crc_bits, uint32_t rx_bit_pos) +{ + pio_hw_t *pio_hw = cd->pio_hw; + uint32_t key = (crc_bits & 0x1fffff) | ((-rx_bit_pos) << 21); + pio_hw->txf[2] = key; +} + +// Set PIO "ack" state machine to check a CRC and inject an ack on success +static void +pio_ack_inject(struct can2040 *cd, uint32_t crc_bits, uint32_t rx_bit_pos) +{ + 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_ack_check(cd, crc_bits, rx_bit_pos); +} + +// Cancel any pending checks on PIO "ack" state machine +static void +pio_ack_cancel(struct can2040 *cd) +{ + pio_hw_t *pio_hw = cd->pio_hw; + pio_hw->txf[2] = 0; +} + +// 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; +} + +// Enable host irq on a "may start 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 "match tx" signal (sm irq 0 or 2) +static void +pio_irq_set_maytx_matchtx(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 start 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; +} + +// Set PIO "sync" machine to signal "start 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 "start 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] +} + +// 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_ack_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; +} + + +/**************************************************************** + * 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_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_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_tx_msg(struct can2040 *cd) +{ + cd->tx_pull_pos++; + cd->rx_cb(cd, CAN2040_NOTIFY_TX, &cd->parse_msg); +} + +// A new message is awaiting crc verification +static void +report_note_crc_start(struct can2040 *cd, int is_tx) +{ + cd->report_state = RS_IN_MSG | (is_tx ? RS_IS_TX : 0); +} + +// Ack phase succeeded +static void +report_note_ack_success(struct can2040 *cd) +{ + if (cd->report_state & RS_IN_MSG) + cd->report_state |= RS_AWAIT_EOF; +} + +// EOF phase complete - report message (rx or tx) to calling code +static void +report_note_eof(struct can2040 *cd) +{ + if (cd->report_state & RS_AWAIT_EOF) { + if (cd->report_state & RS_IS_TX) + report_tx_msg(cd); + else + report_rx_msg(cd); + } + cd->report_state = RS_IDLE; +} + +// Parser found unexpected data on input +static void +report_note_parse_error(struct can2040 *cd) +{ + cd->report_state = RS_IDLE; +} + +// 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); +} + + +/**************************************************************** + * 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) { + if (!pio_tx_did_conflict(cd)) + // Already queued or actively transmitting + return; + } else if (cd->tx_state != TS_IDLE) { + pio_ack_cancel(cd); + } + 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); +} + +// Parser found a new message start +static void +tx_note_message_start(struct can2040 *cd) +{ + pio_irq_set_maytx(cd); +} + +// Setup for ack injection (if receiving) or ack confirmation (if transmit) +static void +tx_note_crc_start(struct can2040 *cd, uint32_t parse_crc) +{ + 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) | parse_crc; + int crc_bitcount = bitstuff(&last, 15 + 1) - 1; + + struct can2040_transmit *qt = &cd->tx_queue[tx_qpos(cd, cd->tx_pull_pos)]; + struct can2040_msg *pm = &cd->parse_msg; + if (cd->tx_state == TS_QUEUED) { + if (qt->crc == cd->parse_crc + && qt->msg.id == pm->id && qt->msg.dlc == pm->dlc + && qt->msg.data32[0] == pm->data32[0] + && qt->msg.data32[1] == pm->data32[1]) { + // This is a self transmit - setup confirmation signal + report_note_crc_start(cd, 1); + cd->tx_state = TS_CONFIRM_TX; + last = (last << 10) | 0x02ff; + pio_ack_check(cd, last, crcstart_bitpos + crc_bitcount + 10); + return; + } + if (!pio_tx_did_conflict(cd) && pio_rx_fifo_level(cd) > 1) { + // Rx state is behind - acking wont succeed and may halt active tx + report_note_crc_start(cd, 0); + return; + } + } + + // Inject ack + report_note_crc_start(cd, 0); + cd->tx_state = TS_ACKING_RX; + last = (last << 1) | 0x01; + pio_ack_inject(cd, last, crcstart_bitpos + crc_bitcount + 1); + pio_irq_set_maytx_ackdone(cd); +} + +// Ack phase succeeded +static void +tx_note_ack_success(struct can2040 *cd) +{ + report_note_ack_success(cd); + if (cd->tx_state == TS_CONFIRM_TX) + pio_irq_set_maytx_matchtx(cd); +} + +// EOF phase succeeded - report message (rx or tx) to calling code +static void +tx_note_eof_success(struct can2040 *cd) +{ + report_note_eof(cd); + pio_sync_normal_start_signal(cd); +} + +// Parser found unexpected data on input +static void +tx_note_parse_error(struct can2040 *cd) +{ + report_note_parse_error(cd); + pio_sync_slow_start_signal(cd); + pio_irq_set_maytx(cd); +} + +// Received PIO rx "ackdone" irq +static void +tx_line_ackdone(struct can2040 *cd) +{ + pio_irq_set_maytx(cd); + tx_schedule_transmit(cd); +} + +// Received PIO "matchtx" irq +static void +tx_line_matchtx(struct can2040 *cd) +{ + tx_note_eof_success(cd); + pio_irq_set_none(cd); + tx_schedule_transmit(cd); +} + +// Received 10+ passive bits on the line (between 10 and 17 bits) +static void +tx_line_maytx(struct can2040 *cd) +{ + report_note_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) +{ + tx_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_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; + tx_note_crc_start(cd, cd->parse_crc); + 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; + tx_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; + } + tx_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 + tx_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 + tx_line_ackdone(cd); + else if (ints & PIO_IRQ0_INTE_SM2_BITS) + // Transmit message completed successfully + tx_line_matchtx(cd); + else if (ints & PIO_IRQ0_INTE_SM0_BITS) + // Bus is idle, but not all bits may have been flushed yet + tx_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..884c3caf --- /dev/null +++ b/lib/can2040/can2040.h @@ -0,0 +1,78 @@ +#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; + + // Transmits + uint32_t tx_state; + uint32_t tx_pull_pos, tx_push_pos; + struct can2040_transmit tx_queue[4]; +}; + +#endif // can2040.h From a831254e833e5713b85b87d5fa27d5d9f2cb3843 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 23 May 2022 01:04:32 -0400 Subject: [PATCH 091/138] rp2040: Initial support for CANbus Add support for CANbus on the rp2040 using the can2040 "software canbus" implementation. Signed-off-by: Kevin O'Connor --- src/rp2040/Kconfig | 13 ++++++++ src/rp2040/Makefile | 7 ++-- src/rp2040/can.c | 78 +++++++++++++++++++++++++++++++++++++++++++++ src/rp2040/chipid.c | 13 +++++--- 4 files changed, 104 insertions(+), 7 deletions(-) create mode 100644 src/rp2040/can.c diff --git a/src/rp2040/Kconfig b/src/rp2040/Kconfig index 8bad20c9..2e5f5019 100644 --- a/src/rp2040/Kconfig +++ b/src/rp2040/Kconfig @@ -83,6 +83,19 @@ choice config RP2040_SERIAL_UART0 bool "Serial (on UART0 GPIO1/GPIO0)" select SERIAL + config RP2040_CANBUS + bool "CAN bus" + select CANSERIAL 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 ffc2c4c8..f49d56ac 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 @@ -19,6 +19,9 @@ src-y += generic/timer_irq.c rp2040/timer.c rp2040/bootrom.c src-$(CONFIG_USBSERIAL) += rp2040/usbserial.c generic/usb_cdc.c src-$(CONFIG_USBSERIAL) += rp2040/chipid.c src-$(CONFIG_SERIAL) += rp2040/serial.c generic/serial_irq.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_HAVE_GPIO_HARD_PWM) += rp2040/hard_pwm.c src-$(CONFIG_HAVE_GPIO_SPI) += rp2040/spi.c src-$(CONFIG_HAVE_GPIO_I2C) += rp2040/i2c.c 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); From be503b2b9b6e553b77bc71e37ee64db5ef9f6ce0 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 12 Jun 2022 14:01:48 -0400 Subject: [PATCH 092/138] rp2040: Add support for USB to CANbus bridge mode Signed-off-by: Kevin O'Connor --- src/rp2040/Kconfig | 3 +++ src/rp2040/Makefile | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/rp2040/Kconfig b/src/rp2040/Kconfig index 2e5f5019..37862d34 100644 --- a/src/rp2040/Kconfig +++ b/src/rp2040/Kconfig @@ -86,6 +86,9 @@ choice 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 diff --git a/src/rp2040/Makefile b/src/rp2040/Makefile index f49d56ac..5bb572fb 100644 --- a/src/rp2040/Makefile +++ b/src/rp2040/Makefile @@ -22,6 +22,9 @@ src-$(CONFIG_SERIAL) += rp2040/serial.c generic/serial_irq.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 From 02dd0742c4dfea6f969ec45d25936d4dca327104 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 23 May 2022 21:23:56 -0400 Subject: [PATCH 093/138] reactor: Add support for waiting on fds becoming writable Signed-off-by: Kevin O'Connor --- klippy/reactor.py | 99 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 75 insertions(+), 24 deletions(-) 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 From 7b9583391ed135190c463552f1754c8faae51250 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 23 May 2022 21:57:04 -0400 Subject: [PATCH 094/138] webhooks: Use reactor to watch for writable fds Signed-off-by: Kevin O'Connor --- klippy/webhooks.py | 49 +++++++++++++++++++++------------------------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/klippy/webhooks.py b/klippy/webhooks.py index 6f6f8820..63342dd7 100644 --- a/klippy/webhooks.py +++ b/klippy/webhooks.py @@ -171,9 +171,9 @@ 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.set_client_info("?", "New connection") self.request_log = collections.deque([], REQUEST_LOG_SIZE) @@ -259,33 +259,28 @@ 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 + 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): From a283e0f7d4156baff4ab7eccfee6d50738b162aa Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 23 May 2022 22:04:18 -0400 Subject: [PATCH 095/138] webhooks: Close clients that become unresponsive Signed-off-by: Kevin O'Connor --- klippy/webhooks.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/klippy/webhooks.py b/klippy/webhooks.py index 63342dd7..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 @@ -174,6 +184,7 @@ class ClientConnection: self.sock.fileno(), self.process_received, self._do_send) self.partial_data = self.send_buffer = b"" self.is_blocking = False + self.blocking_count = 0 self.set_client_info("?", "New connection") self.request_log = collections.deque([], REQUEST_LOG_SIZE) @@ -277,6 +288,7 @@ class ClientConnection: 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 @@ -370,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( From 24a1b50e512f3038a51060e3cc8ce2a847b9fafa Mon Sep 17 00:00:00 2001 From: Cabia Rangris Date: Wed, 6 Jul 2022 23:56:02 +0400 Subject: [PATCH 096/138] config: Added Anet alt-wiring display example (#5605) Signed-off-by: Vladimir Serov --- config/sample-lcd.cfg | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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) ###################################################################### From 36887ce6fec3b98bc9f66aae675fb3b4fe712c3d Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 16 Jul 2022 00:16:51 -0400 Subject: [PATCH 097/138] lib: Update to the latest can2040 code Rename to "match" state machine instead of "ack". Minor simplification to tx_note_crc_start(). Call pio_match_clear() from report functions. Add pio_match_calc_key() helper function. Raise irq after 6 passive eof bits for faster rx message notification. Signed-off-by: Kevin O'Connor --- lib/README | 2 +- lib/can2040/can2040.c | 174 +++++++++++++++++++++++------------------- lib/can2040/can2040.h | 1 + 3 files changed, 96 insertions(+), 81 deletions(-) diff --git a/lib/README b/lib/README index ef9b5a98..8e92c012 100644 --- a/lib/README +++ b/lib/README @@ -131,4 +131,4 @@ used to upload firmware to devices flashed with the CanBoot bootloader. The can2040 directory contains code from: https://github.com/KevinOConnor/can2040 -revision 17b8ace15584077cd0bf0c3e038c2a2a8edd70ed. +revision c981fa7666ee9ce1650904e38f7287cb861df18c. diff --git a/lib/can2040/can2040.c b/lib/can2040/can2040.c index eafefa16..baac5937 100644 --- a/lib/can2040/can2040.c +++ b/lib/can2040/can2040.c @@ -74,8 +74,8 @@ rp2040_gpio_peripheral(uint32_t gpio, int func, int pull_up) #define can2040_offset_sync_end 13u #define can2040_offset_shared_rx_read 13u #define can2040_offset_shared_rx_end 15u -#define can2040_offset_ack_no_match 18u -#define can2040_offset_ack_end 25u +#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 @@ -152,21 +152,21 @@ pio_rx_setup(struct can2040 *cd) sm->instr = can2040_offset_shared_rx_read; // jmp shared_rx_read } -// Setup PIO "ack" state machine (state machine 2) +// Setup PIO "match" state machine (state machine 2) static void -pio_ack_setup(struct can2040 *cd) +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_ack_end - 1) << PIO_SM0_EXECCTRL_WRAP_TOP_LSB + (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_ack_no_match; // jmp ack_no_match + sm->instr = can2040_offset_match_load_next; // jmp match_load_next } // Setup PIO "tx" state machine (state machine 3) @@ -186,12 +186,60 @@ pio_tx_setup(struct can2040 *cd) sm->instr = 0xe081; // set pindirs, 1 } -// Check if the PIO "tx" state machine stopped due to passive/dominant conflict -static int -pio_tx_did_conflict(struct can2040 *cd) +// 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; - return pio_hw->sm[3].addr == can2040_offset_tx_conflict; + 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 @@ -201,12 +249,13 @@ 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 irq 2 and 3 + 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)); } @@ -228,18 +277,9 @@ pio_tx_send(struct can2040 *cd, uint32_t *data, uint32_t count) pio_hw->ctrl = 0x0f << PIO_CTRL_SM_ENABLE_LSB; } -// Set PIO "ack" state machine to check a given CRC sequence +// Set PIO "tx" state machine to inject an ack after a CRC match static void -pio_ack_check(struct can2040 *cd, uint32_t crc_bits, uint32_t rx_bit_pos) -{ - pio_hw_t *pio_hw = cd->pio_hw; - uint32_t key = (crc_bits & 0x1fffff) | ((-rx_bit_pos) << 21); - pio_hw->txf[2] = key; -} - -// Set PIO "ack" state machine to check a CRC and inject an ack on success -static void -pio_ack_inject(struct can2040 *cd, uint32_t crc_bits, uint32_t rx_bit_pos) +pio_tx_inject_ack(struct can2040 *cd, uint32_t match_key) { pio_hw_t *pio_hw = cd->pio_hw; pio_tx_reset(cd); @@ -251,34 +291,18 @@ pio_ack_inject(struct can2040 *cd, uint32_t crc_bits, uint32_t rx_bit_pos) sm->instr = 0x20c2; // wait 1 irq, 2 pio_hw->ctrl = 0x0f << PIO_CTRL_SM_ENABLE_LSB; - pio_ack_check(cd, crc_bits, rx_bit_pos); + pio_match_check(cd, match_key); } -// Cancel any pending checks on PIO "ack" state machine -static void -pio_ack_cancel(struct can2040 *cd) -{ - pio_hw_t *pio_hw = cd->pio_hw; - pio_hw->txf[2] = 0; -} - -// Test if PIO "rx" state machine has overflowed its fifos +// Check if the PIO "tx" state machine stopped due to passive/dominant conflict static int -pio_rx_check_stall(struct can2040 *cd) +pio_tx_did_conflict(struct can2040 *cd) { pio_hw_t *pio_hw = cd->pio_hw; - return pio_hw->fdebug & (1 << (PIO_FDEBUG_RXSTALL_LSB + 1)); + return pio_hw->sm[3].addr == can2040_offset_tx_conflict; } -// 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; -} - -// Enable host irq on a "may start transmit" signal (sm irq 0) +// Enable host irq on a "may transmit" signal (sm irq 0) static void pio_irq_set_maytx(struct can2040 *cd) { @@ -286,9 +310,9 @@ pio_irq_set_maytx(struct can2040 *cd) pio_hw->inte0 = PIO_IRQ0_INTE_SM0_BITS | PIO_IRQ0_INTE_SM1_RXNEMPTY_BITS; } -// Enable host irq on a "may transmit" or "match tx" signal (sm irq 0 or 2) +// Enable host irq on a "may transmit" or "matched" signal (sm irq 0 or 2) static void -pio_irq_set_maytx_matchtx(struct can2040 *cd) +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 @@ -304,7 +328,7 @@ pio_irq_set_maytx_ackdone(struct can2040 *cd) | PIO_IRQ0_INTE_SM1_RXNEMPTY_BITS); } -// Atomically enable "may start transmit" signal (sm irq 0) +// Atomically enable "may transmit" signal (sm irq 0) static void pio_irq_atomic_set_maytx(struct can2040 *cd) { @@ -320,24 +344,6 @@ pio_irq_set_none(struct can2040 *cd) pio_hw->inte0 = PIO_IRQ0_INTE_SM1_RXNEMPTY_BITS; } -// Set PIO "sync" machine to signal "start 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 "start 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] -} - // Setup PIO state machines static void pio_sm_setup(struct can2040 *cd) @@ -355,7 +361,7 @@ pio_sm_setup(struct can2040 *cd) // Set initial state machine state pio_sync_setup(cd); pio_rx_setup(cd); - pio_ack_setup(cd); + pio_match_setup(cd); pio_tx_setup(cd); // Start state machines @@ -658,6 +664,8 @@ report_note_ack_success(struct can2040 *cd) static void report_note_eof(struct can2040 *cd) { + if (cd->report_state == RS_IDLE) + return; if (cd->report_state & RS_AWAIT_EOF) { if (cd->report_state & RS_IS_TX) report_tx_msg(cd); @@ -665,13 +673,17 @@ report_note_eof(struct can2040 *cd) report_rx_msg(cd); } cd->report_state = RS_IDLE; + pio_match_clear(cd); } // Parser found unexpected data on input static void report_note_parse_error(struct can2040 *cd) { + if (cd->report_state == RS_IDLE) + return; cd->report_state = RS_IDLE; + pio_match_clear(cd); } // Check if in an rx ack is pending @@ -706,8 +718,6 @@ tx_schedule_transmit(struct can2040 *cd) if (!pio_tx_did_conflict(cd)) // Already queued or actively transmitting return; - } else if (cd->tx_state != TS_IDLE) { - pio_ack_cancel(cd); } if (cd->tx_push_pos == cd->tx_pull_pos) { // No new messages to transmit @@ -733,20 +743,20 @@ tx_note_crc_start(struct can2040 *cd, uint32_t parse_crc) 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) | parse_crc; - int crc_bitcount = bitstuff(&last, 15 + 1) - 1; + uint32_t crc_bitcount = bitstuff(&last, 15 + 1) - 1; + uint32_t crcend_bitpos = crcstart_bitpos + crc_bitcount; struct can2040_transmit *qt = &cd->tx_queue[tx_qpos(cd, cd->tx_pull_pos)]; - struct can2040_msg *pm = &cd->parse_msg; + struct can2040_msg *pm = &cd->parse_msg, *tm = &qt->msg; if (cd->tx_state == TS_QUEUED) { - if (qt->crc == cd->parse_crc - && qt->msg.id == pm->id && qt->msg.dlc == pm->dlc - && qt->msg.data32[0] == pm->data32[0] - && qt->msg.data32[1] == pm->data32[1]) { + if (qt->crc == 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 - setup confirmation signal report_note_crc_start(cd, 1); cd->tx_state = TS_CONFIRM_TX; last = (last << 10) | 0x02ff; - pio_ack_check(cd, last, crcstart_bitpos + crc_bitcount + 10); + pio_match_check(cd, pio_match_calc_key(last, crcend_bitpos + 10)); return; } if (!pio_tx_did_conflict(cd) && pio_rx_fifo_level(cd) > 1) { @@ -760,8 +770,10 @@ tx_note_crc_start(struct can2040 *cd, uint32_t parse_crc) report_note_crc_start(cd, 0); cd->tx_state = TS_ACKING_RX; last = (last << 1) | 0x01; - pio_ack_inject(cd, last, crcstart_bitpos + crc_bitcount + 1); + pio_tx_inject_ack(cd, pio_match_calc_key(last, crcend_bitpos + 1)); pio_irq_set_maytx_ackdone(cd); + last = (last << 8) | 0x7f; + cd->tx_eof_key = pio_match_calc_key(last, crcend_bitpos + 9); } // Ack phase succeeded @@ -770,7 +782,7 @@ tx_note_ack_success(struct can2040 *cd) { report_note_ack_success(cd); if (cd->tx_state == TS_CONFIRM_TX) - pio_irq_set_maytx_matchtx(cd); + pio_irq_set_maytx_matched(cd); } // EOF phase succeeded - report message (rx or tx) to calling code @@ -794,13 +806,15 @@ tx_note_parse_error(struct can2040 *cd) static void tx_line_ackdone(struct can2040 *cd) { - pio_irq_set_maytx(cd); + report_note_ack_success(cd); + pio_match_check(cd, cd->tx_eof_key); + pio_irq_set_maytx_matched(cd); tx_schedule_transmit(cd); } -// Received PIO "matchtx" irq +// Received PIO "matched" irq static void -tx_line_matchtx(struct can2040 *cd) +tx_line_matched(struct can2040 *cd) { tx_note_eof_success(cd); pio_irq_set_none(cd); @@ -1104,7 +1118,7 @@ can2040_pio_irq_handler(struct can2040 *cd) tx_line_ackdone(cd); else if (ints & PIO_IRQ0_INTE_SM2_BITS) // Transmit message completed successfully - tx_line_matchtx(cd); + tx_line_matched(cd); else if (ints & PIO_IRQ0_INTE_SM0_BITS) // Bus is idle, but not all bits may have been flushed yet tx_line_maytx(cd); diff --git a/lib/can2040/can2040.h b/lib/can2040/can2040.h index 884c3caf..0005db9e 100644 --- a/lib/can2040/can2040.h +++ b/lib/can2040/can2040.h @@ -71,6 +71,7 @@ struct can2040 { // Transmits uint32_t tx_state; + uint32_t tx_eof_key; uint32_t tx_pull_pos, tx_push_pos; struct can2040_transmit tx_queue[4]; }; From 06022b305f815a48c01e43218d471d470c5b017d Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 16 Jul 2022 10:43:53 -0400 Subject: [PATCH 098/138] mkdocs-requirements: Force markdown==3.3.7 A new release of markdown (v3.4.1) breaks the website deployment scripts. Force the existing version. Signed-off-by: Kevin O'Connor --- docs/_klipper3d/mkdocs-requirements.txt | 1 + 1 file changed, 1 insertion(+) 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 From 8a038c69411715cba36eaea92ee7a9944762ef6b Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 16 Jul 2022 10:45:26 -0400 Subject: [PATCH 099/138] workflows: Fix spurious path in klipper3d-deploy.yaml Signed-off-by: Kevin O'Connor --- .github/workflows/klipper3d-deploy.yaml | 1 - 1 file changed, 1 deletion(-) 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: From 2f9fe49cb85eed35676a2711686d6ddc0514ac5a Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 12 Jul 2022 17:42:48 -0400 Subject: [PATCH 100/138] docs: Add a Sponsors.md file Add a documentation page with information on how to support Klipper. Signed-off-by: Kevin O'Connor --- README.md | 4 +++- docs/FAQ.md | 4 ++-- docs/Sponsors.md | 30 ++++++++++++++++++++++++++++++ docs/_klipper3d/mkdocs.yml | 1 + docs/index.md | 1 + 5 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 docs/Sponsors.md 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/docs/FAQ.md b/docs/FAQ.md index 598d5800..417fb1d4 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -2,8 +2,8 @@ ## 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/Sponsors.md b/docs/Sponsors.md new file mode 100644 index 00000000..70755e7c --- /dev/null +++ b/docs/Sponsors.md @@ -0,0 +1,30 @@ +# Sponsors + +Klipper is Free Software. We depend on the generous support from +sponsors. Please consider sponsoring Klipper or supporting our +sponsors. + +## Klipper Developers + +### Kevin O'Connor + +Kevin is the original author and current maintainer of Klipper. Kevin +has a Patreon page at: +[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/_klipper3d/mkdocs.yml b/docs/_klipper3d/mkdocs.yml index 313f60c5..29c0bdd0 100644 --- a/docs/_klipper3d/mkdocs.yml +++ b/docs/_klipper3d/mkdocs.yml @@ -135,3 +135,4 @@ nav: - CANBUS.md - TSL1401CL_Filament_Width_Sensor.md - Hall_Filament_Width_Sensor.md + - Sponsors.md 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). From 75c4b1238e58f79112f2f3c0e908bfb0308a3ed4 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 16 Jul 2022 21:00:49 -0400 Subject: [PATCH 101/138] lib: Update to latest can2040 code Simplify tx state tracking. Signed-off-by: Kevin O'Connor --- lib/README | 2 +- lib/can2040/can2040.c | 288 ++++++++++++++++++++++-------------------- lib/can2040/can2040.h | 2 +- 3 files changed, 153 insertions(+), 139 deletions(-) diff --git a/lib/README b/lib/README index 8e92c012..eac029ff 100644 --- a/lib/README +++ b/lib/README @@ -131,4 +131,4 @@ used to upload firmware to devices flashed with the CanBoot bootloader. The can2040 directory contains code from: https://github.com/KevinOConnor/can2040 -revision c981fa7666ee9ce1650904e38f7287cb861df18c. +revision 9ca095c939a48391de60dd353f0cd91999bb9257. diff --git a/lib/can2040/can2040.c b/lib/can2040/can2040.c index baac5937..4460d75b 100644 --- a/lib/can2040/can2040.c +++ b/lib/can2040/can2040.c @@ -613,87 +613,6 @@ bs_finalize(struct bitstuffer_s *bs) } -/**************************************************************** - * 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_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_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_tx_msg(struct can2040 *cd) -{ - cd->tx_pull_pos++; - cd->rx_cb(cd, CAN2040_NOTIFY_TX, &cd->parse_msg); -} - -// A new message is awaiting crc verification -static void -report_note_crc_start(struct can2040 *cd, int is_tx) -{ - cd->report_state = RS_IN_MSG | (is_tx ? RS_IS_TX : 0); -} - -// Ack phase succeeded -static void -report_note_ack_success(struct can2040 *cd) -{ - if (cd->report_state & RS_IN_MSG) - cd->report_state |= RS_AWAIT_EOF; -} - -// EOF phase complete - report message (rx or tx) to calling code -static void -report_note_eof(struct can2040 *cd) -{ - if (cd->report_state == RS_IDLE) - return; - if (cd->report_state & RS_AWAIT_EOF) { - if (cd->report_state & RS_IS_TX) - report_tx_msg(cd); - else - report_rx_msg(cd); - } - cd->report_state = RS_IDLE; - pio_match_clear(cd); -} - -// Parser found unexpected data on input -static void -report_note_parse_error(struct can2040 *cd) -{ - if (cd->report_state == RS_IDLE) - return; - 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); -} - - /**************************************************************** * Transmit state tracking ****************************************************************/ @@ -714,11 +633,9 @@ tx_qpos(struct can2040 *cd, uint32_t pos) static void tx_schedule_transmit(struct can2040 *cd) { - if (cd->tx_state == TS_QUEUED) { - if (!pio_tx_did_conflict(cd)) - // Already queued or actively transmitting - return; - } + 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; @@ -729,103 +646,200 @@ tx_schedule_transmit(struct can2040 *cd) 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 -tx_note_message_start(struct can2040 *cd) +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 -tx_note_crc_start(struct can2040 *cd, uint32_t parse_crc) +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) | parse_crc; + 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; - 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 (cd->tx_state == TS_QUEUED) { - if (qt->crc == 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 - setup confirmation signal - report_note_crc_start(cd, 1); - cd->tx_state = TS_CONFIRM_TX; - last = (last << 10) | 0x02ff; - pio_match_check(cd, pio_match_calc_key(last, crcend_bitpos + 10)); - return; - } - if (!pio_tx_did_conflict(cd) && pio_rx_fifo_level(cd) > 1) { - // Rx state is behind - acking wont succeed and may halt active tx - report_note_crc_start(cd, 0); - return; - } + 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 - report_note_crc_start(cd, 0); - cd->tx_state = TS_ACKING_RX; + cd->report_state = RS_IN_MSG; last = (last << 1) | 0x01; - pio_tx_inject_ack(cd, pio_match_calc_key(last, crcend_bitpos + 1)); + 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->tx_eof_key = pio_match_calc_key(last, crcend_bitpos + 9); + cd->report_eof_key = pio_match_calc_key(last, crcend_bitpos + 9); } -// Ack phase succeeded +// Parser found successful ack static void -tx_note_ack_success(struct can2040 *cd) +report_note_ack_success(struct can2040 *cd) { - report_note_ack_success(cd); - if (cd->tx_state == TS_CONFIRM_TX) + 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); } -// EOF phase succeeded - report message (rx or tx) to calling code +// Parser found successful EOF static void -tx_note_eof_success(struct can2040 *cd) +report_note_eof_success(struct can2040 *cd) { - report_note_eof(cd); - pio_sync_normal_start_signal(cd); + report_handle_eof(cd); } // Parser found unexpected data on input static void -tx_note_parse_error(struct can2040 *cd) +report_note_parse_error(struct can2040 *cd) { - report_note_parse_error(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 -tx_line_ackdone(struct can2040 *cd) +report_line_ackdone(struct can2040 *cd) { - report_note_ack_success(cd); - pio_match_check(cd, cd->tx_eof_key); + 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 -tx_line_matched(struct can2040 *cd) +report_line_matched(struct can2040 *cd) { - tx_note_eof_success(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 -tx_line_maytx(struct can2040 *cd) +report_line_maytx(struct can2040 *cd) { - report_note_eof(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); } @@ -853,13 +867,13 @@ data_state_go_next(struct can2040 *cd, uint32_t state, uint32_t bits) static void data_state_go_discard(struct can2040 *cd) { - tx_note_parse_error(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_error(cd, 0); + report_callback_error(cd, 0); } data_state_go_next(cd, MS_DISCARD, 32); @@ -905,7 +919,7 @@ static void data_state_go_crc(struct can2040 *cd) { cd->parse_crc &= 0x7fff; - tx_note_crc_start(cd, cd->parse_crc); + report_note_crc_start(cd); data_state_go_next(cd, MS_CRC, 16); } @@ -937,7 +951,7 @@ static void data_state_update_start(struct can2040 *cd, uint32_t data) { cd->parse_msg.id = data; - tx_note_message_start(cd); + report_note_message_start(cd); data_state_go_next(cd, MS_HEADER, 17); } @@ -1012,7 +1026,7 @@ data_state_update_ack(struct can2040 *cd, uint32_t data) data_state_go_discard(cd); return; } - tx_note_ack_success(cd); + report_note_ack_success(cd); data_state_go_next(cd, MS_EOF0, 4); } @@ -1034,7 +1048,7 @@ 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 - tx_note_eof_success(cd); + report_note_eof_success(cd); if (data == 0x0f) data_state_go_next(cd, MS_START, 1); @@ -1115,13 +1129,13 @@ can2040_pio_irq_handler(struct can2040 *cd) if (ints & PIO_IRQ0_INTE_SM3_BITS) // Ack of received message completed successfully - tx_line_ackdone(cd); + report_line_ackdone(cd); else if (ints & PIO_IRQ0_INTE_SM2_BITS) // Transmit message completed successfully - tx_line_matched(cd); + report_line_matched(cd); else if (ints & PIO_IRQ0_INTE_SM0_BITS) // Bus is idle, but not all bits may have been flushed yet - tx_line_maytx(cd); + report_line_maytx(cd); } diff --git a/lib/can2040/can2040.h b/lib/can2040/can2040.h index 0005db9e..26a49ebc 100644 --- a/lib/can2040/can2040.h +++ b/lib/can2040/can2040.h @@ -68,10 +68,10 @@ struct can2040 { // Reporting uint32_t report_state; + uint32_t report_eof_key; // Transmits uint32_t tx_state; - uint32_t tx_eof_key; uint32_t tx_pull_pos, tx_push_pos; struct can2040_transmit tx_queue[4]; }; From d91939c4bf15792ea292f753d72fca5148ab1c37 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 16 Jul 2022 22:57:35 -0400 Subject: [PATCH 102/138] docs: Add BIGTREETECH to Sponsors.md file Signed-off-by: Kevin O'Connor --- docs/Sponsors.md | 10 ++++++++++ docs/img/sponsors/BTT_BTT.png | Bin 0 -> 44333 bytes 2 files changed, 10 insertions(+) create mode 100644 docs/img/sponsors/BTT_BTT.png diff --git a/docs/Sponsors.md b/docs/Sponsors.md index 70755e7c..100cf023 100644 --- a/docs/Sponsors.md +++ b/docs/Sponsors.md @@ -4,6 +4,16 @@ 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 diff --git a/docs/img/sponsors/BTT_BTT.png b/docs/img/sponsors/BTT_BTT.png new file mode 100644 index 0000000000000000000000000000000000000000..141b9b2b2e522bc834e86a1843a5019383d27010 GIT binary patch literal 44333 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

o=5d#(9JZ{oN@x$VBtOG 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 Date: Tue, 19 Jul 2022 12:44:18 -0400 Subject: [PATCH 103/138] config: Note 25Mhz clock in generic-bigtreetech-skr-3.cfg Reported by @kingtricky and confirmed by @bigtreetech. Signed-off-by: Kevin O'Connor --- config/generic-bigtreetech-skr-3.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/generic-bigtreetech-skr-3.cfg b/config/generic-bigtreetech-skr-3.cfg index 4b44a4d7..c769ae03 100644 --- a/config/generic-bigtreetech-skr-3.cfg +++ b/config/generic-bigtreetech-skr-3.cfg @@ -1,6 +1,6 @@ # This file contains common pin mappings for the BigTreeTech SKR 3. -# To use this config, the firmware should be compiled for the -# STM32H743 with a "128KiB bootloader". +# 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. From 6be114d728b6a243bb7af990cf03a879ea74c0b8 Mon Sep 17 00:00:00 2001 From: Pedro Lamas Date: Mon, 4 Jul 2022 09:37:24 +0100 Subject: [PATCH 104/138] docs: fix rawparams example by truncating comments Signed-off-by: Pedro Lamas --- config/sample-macros.cfg | 23 +++++++++++++++++++++++ docs/Command_Templates.md | 19 +++++-------------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/config/sample-macros.cfg b/config/sample-macros.cfg index 97e39016..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 diff --git a/docs/Command_Templates.md b/docs/Command_Templates.md index c89d85bc..3435d6c1 100644 --- a/docs/Command_Templates.md +++ b/docs/Command_Templates.md @@ -130,22 +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: - {% if rawparams %} - {% set escaped_msg = rawparams|replace('"', '\\"') %} - SET_DISPLAY_TEXT MSG="{escaped_msg}" - RESPOND TYPE=command MSG="{escaped_msg}" - {% else %} - SET_DISPLAY_TEXT - {% endif %} -``` +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 From dd03cca49b920762f959d8cce047a6cc4debf60b Mon Sep 17 00:00:00 2001 From: JamesH1978 <87171443+JamesH1978@users.noreply.github.com> Date: Tue, 19 Jul 2022 18:02:51 +0100 Subject: [PATCH 105/138] config: Change of alias for heater_fan in multiple configs (#5632) This PR serves to fix a longstanding misnomer in some config files. Many configs state a nozzle_cooling_fan alias for what is usually a "hotend cooling fan". This causes ambiguity and confusion with the parts fan. I have identified all 24 files with this and changed them here. Signed-off-by: James Hartley --- config/generic-archim2.cfg | 2 +- config/generic-bigtreetech-e3-rrf-v1.1.cfg | 2 +- config/generic-bigtreetech-skr-e3-turbo.cfg | 2 +- config/generic-bigtreetech-skr-mini-e3-v2.0.cfg | 2 +- config/generic-bigtreetech-skr-mini-e3-v3.0.cfg | 2 +- config/generic-bigtreetech-skr-mini-mz.cfg | 2 +- config/generic-bigtreetech-skr-pico-v1.0.cfg | 2 +- config/generic-duet2-duex.cfg | 2 +- config/generic-duet2-maestro.cfg | 2 +- config/generic-duet2.cfg | 2 +- config/generic-einsy-rambo.cfg | 2 +- config/generic-fysetc-cheetah-v1.2.cfg | 2 +- config/generic-mini-rambo.cfg | 2 +- config/generic-printrboard-g2.cfg | 2 +- config/generic-radds.cfg | 2 +- config/generic-rambo.cfg | 2 +- config/printer-eryone-er20-2021.cfg | 2 +- config/printer-eryone-thinker-series-v2-2020.cfg | 2 +- config/printer-lulzbot-mini1-2016.cfg | 2 +- config/printer-lulzbot-taz6-2017.cfg | 2 +- config/printer-lulzbot-taz6-dual-v3-2017.cfg | 2 +- config/printer-makergear-m2-2016.cfg | 2 +- config/printer-mtw-create-2015.cfg | 2 +- config/printer-seemecnc-rostock-max-v2-2015.cfg | 2 +- 24 files changed, 24 insertions(+), 24 deletions(-) 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-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-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/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 From e9c83d66e020fb14f6962a80cddd2d6bb5a67c70 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 19 Jul 2022 13:43:14 -0400 Subject: [PATCH 106/138] config: Fix screw typo in printer-creality-cr10-v3-2020.cfg Reported by @TonyRouse. Signed-off-by: Kevin O'Connor --- config/printer-creality-cr10-v3-2020.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/printer-creality-cr10-v3-2020.cfg b/config/printer-creality-cr10-v3-2020.cfg index 8b6c355e..9bf84df5 100644 --- a/config/printer-creality-cr10-v3-2020.cfg +++ b/config/printer-creality-cr10-v3-2020.cfg @@ -145,7 +145,7 @@ screw2_name: front right screw screw3: 273,269 screw3_name: rear right screw screw4: 33,269 -screw4_name: rear right screw +screw4_name: rear left screw #Uncomment the following lines if you have a BL-Touch #[screws_tilt_adjust] @@ -156,7 +156,7 @@ screw4_name: rear right screw #screw3: 228,269 #screw3_name: rear right screw #screw4: 0,269 -#screw4_name: rear right screw +#screw4_name: rear left screw #speed: 50 #horizontal_move_z: 10 #screw_thread: CW-M3 From a151aa8c7a2baa971f4257e5add7a3e846e58857 Mon Sep 17 00:00:00 2001 From: s6t <32667093+s6t@users.noreply.github.com> Date: Thu, 21 Jul 2022 03:57:08 +0200 Subject: [PATCH 107/138] spi_flash: Add board definition for Mellow FLY Gemini V2 (#5651) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tobias Schröder --- scripts/spi_flash/board_defs.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/spi_flash/board_defs.py b/scripts/spi_flash/board_defs.py index 8ee48672..db78cfc0 100644 --- a/scripts/spi_flash/board_defs.py +++ b/scripts/spi_flash/board_defs.py @@ -70,6 +70,11 @@ BOARD_DEFS = { 'mcu': "stm32f407xx", 'spi_bus': "spi3a", "cs_pin": "PC9" + }, + 'fly-gemini-v2': { + 'mcu': "stm32f405xx", + 'spi_bus': "spi1", + "cs_pin": "PA4" } } From 0c74b3d8bfb8d06ac6ddc208a0a53bba235ee89d Mon Sep 17 00:00:00 2001 From: S1NH Date: Fri, 22 Jul 2022 09:45:05 +0800 Subject: [PATCH 108/138] config: Integrate configuration file for Creality Sermoon V1. (#5621) Configuration for the stock Creality Sermoon V1. Signed-off-by: Du Chengyao --- config/printer-creality-sermoonV1-2022.cfg | 108 +++++++++++++++++++++ test/klippy/printers.test | 1 + 2 files changed, 109 insertions(+) create mode 100644 config/printer-creality-sermoonV1-2022.cfg diff --git a/config/printer-creality-sermoonV1-2022.cfg b/config/printer-creality-sermoonV1-2022.cfg new file mode 100644 index 00000000..ef100fb3 --- /dev/null +++ b/config/printer-creality-sermoonV1-2022.cfg @@ -0,0 +1,108 @@ +# 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 USB 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/test/klippy/printers.test b/test/klippy/printers.test index 57db5e04..4388fcb6 100644 --- a/test/klippy/printers.test +++ b/test/klippy/printers.test @@ -168,6 +168,7 @@ 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 From 407be177d5424bc237ca8fa756ae3351491b8678 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 21 Jul 2022 21:56:46 -0400 Subject: [PATCH 109/138] config: Fix wording of serial port selection on recent creality configs Signed-off-by: Kevin O'Connor --- config/printer-creality-ender3-s1-2021.cfg | 4 ++-- config/printer-creality-ender3-s1pro-2022.cfg | 4 ++-- config/printer-creality-sermoonV1-2022.cfg | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/config/printer-creality-ender3-s1-2021.cfg b/config/printer-creality-ender3-s1-2021.cfg index 766dd0f8..2be53726 100644 --- a/config/printer-creality-ender3-s1-2021.cfg +++ b/config/printer-creality-ender3-s1-2021.cfg @@ -2,8 +2,8 @@ # S1. 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 USB PA10/PA9) communication for both depending on the -# STM32 Chip installed on your printers Motherboard. +# 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 diff --git a/config/printer-creality-ender3-s1pro-2022.cfg b/config/printer-creality-ender3-s1pro-2022.cfg index 9116eb05..349fd725 100644 --- a/config/printer-creality-ender3-s1pro-2022.cfg +++ b/config/printer-creality-ender3-s1pro-2022.cfg @@ -2,8 +2,8 @@ # 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 USB PA10/PA9) communication for both depending on the -# STM32 Chip installed on your printers Motherboard. +# 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 diff --git a/config/printer-creality-sermoonV1-2022.cfg b/config/printer-creality-sermoonV1-2022.cfg index ef100fb3..ca8a91a6 100644 --- a/config/printer-creality-sermoonV1-2022.cfg +++ b/config/printer-creality-sermoonV1-2022.cfg @@ -2,7 +2,8 @@ # 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 USB PA10/PA9) communication. +# 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 From 282d1113e4beb0bdce3cfc9be87745aea1e7e38c Mon Sep 17 00:00:00 2001 From: Pedro Lamas Date: Wed, 20 Jul 2022 13:06:09 +0100 Subject: [PATCH 110/138] manual_probe: report status Signed-off-by: Pedro Lamas --- docs/Status_Reference.md | 11 +++++++++++ klippy/extras/manual_probe.py | 25 +++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/docs/Status_Reference.md b/docs/Status_Reference.md index 4cc7512f..4c7883c7 100644 --- a/docs/Status_Reference.md +++ b/docs/Status_Reference.md @@ -244,6 +244,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 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) From 3387a9c23d940c7d449f197b272616eda11a5e3d Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 24 Jul 2022 08:49:25 -0400 Subject: [PATCH 111/138] config: Use printer-creality-ender3-s1-2021.cfg for both s1 and s1 pro. Signed-off-by: Kevin O'Connor --- config/printer-creality-ender3-s1-2021.cfg | 11 +- config/printer-creality-ender3-s1pro-2022.cfg | 130 ------------------ test/klippy/printers.test | 1 - 3 files changed, 6 insertions(+), 136 deletions(-) delete mode 100644 config/printer-creality-ender3-s1pro-2022.cfg diff --git a/config/printer-creality-ender3-s1-2021.cfg b/config/printer-creality-ender3-s1-2021.cfg index 2be53726..bda56e75 100644 --- a/config/printer-creality-ender3-s1-2021.cfg +++ b/config/printer-creality-ender3-s1-2021.cfg @@ -1,9 +1,10 @@ # This file contains pin mappings for the stock 2021 Creality Ender 3 -# S1. 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. +# 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 diff --git a/config/printer-creality-ender3-s1pro-2022.cfg b/config/printer-creality-ender3-s1pro-2022.cfg deleted file mode 100644 index 349fd725..00000000 --- a/config/printer-creality-ender3-s1pro-2022.cfg +++ /dev/null @@ -1,130 +0,0 @@ -# This file contains pin mappings for the stock 2022 Creality Ender 3 -# 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: 300 - -[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/test/klippy/printers.test b/test/klippy/printers.test index 4388fcb6..647aeb74 100644 --- a/test/klippy/printers.test +++ b/test/klippy/printers.test @@ -161,7 +161,6 @@ 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-s1pro-2022.cfg CONFIG ../../config/printer-creality-ender3-v2-2020.cfg CONFIG ../../config/printer-creality-ender3max-2021.cfg CONFIG ../../config/printer-creality-ender3pro-2020.cfg From 3796a319599e84b58886ec6f733277bfe4f1a747 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 25 Jul 2022 10:21:29 -0400 Subject: [PATCH 112/138] stm32: Add CCRDY check to stm32g0 adc The stm32g0 specification states that it is required to wait for the CCRDY flag to be raised after changing the channel configuration. Signed-off-by: Kevin O'Connor --- src/stm32/stm32f0_adc.c | 9 +++++++++ 1 file changed, 9 insertions(+) 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: From c29e190696053310b7513c305d63667e67a293d1 Mon Sep 17 00:00:00 2001 From: Pedro Lamas Date: Thu, 28 Jul 2022 14:53:58 +0100 Subject: [PATCH 113/138] docs: fixes typo on heater_bed Signed-off-by: Pedro Lamas --- docs/Config_Reference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index be33c723..f4f9d39e 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -2144,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 From b725d971db3b4f722aa218b773005a0585910175 Mon Sep 17 00:00:00 2001 From: Pedro Lamas Date: Mon, 25 Jul 2022 16:49:21 +0100 Subject: [PATCH 114/138] bed_screws: report status Signed-off-by: Pedro Lamas --- docs/Status_Reference.md | 11 +++++++++++ klippy/extras/bed_screws.py | 19 ++++++++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/docs/Status_Reference.md b/docs/Status_Reference.md index 4c7883c7..7b6f1a42 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 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) From 2293e1506ffd54a5bb1e0bda90f1c497a7bdc20d Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 25 Jul 2022 12:39:39 -0400 Subject: [PATCH 115/138] canbus_ids: Use 4 as the first nodeid to reduce id bitstuffing Starting with nodeid 4 instead of nodeid 0 can reduce bitstuffing of the id field in common configurations. Signed-off-by: Kevin O'Connor --- klippy/extras/canbus_ids.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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): From db6346e7e55d34763904488deff4a67338b1acbd Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 25 Jul 2022 20:50:19 -0400 Subject: [PATCH 116/138] serialqueue: Improve canbus timing Adjust timing based on the minimum transmission time of canbus messages. Signed-off-by: Kevin O'Connor --- klippy/chelper/__init__.py | 4 +-- klippy/chelper/serialqueue.c | 63 +++++++++++++++++++++++++----------- klippy/chelper/serialqueue.h | 2 +- klippy/serialhdl.py | 13 ++++---- src/generic/canbus.c | 4 +++ 5 files changed, 58 insertions(+), 28 deletions(-) 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/serialhdl.py b/klippy/serialhdl.py index b916ab20..15d1cc21 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/src/generic/canbus.c b/src/generic/canbus.c index a4f33aa1..941ccd00 100644 --- a/src/generic/canbus.c +++ b/src/generic/canbus.c @@ -4,8 +4,12 @@ // // This file may be distributed under the terms of the GNU GPLv3 license. +#include "autoconf.h" // CONFIG_CANBUS_FREQUENCY #include "canbus.h" // canbus_send #include "canserial.h" // canserial_send +#include "command.h" // DECL_CONSTANT + +DECL_CONSTANT("CANBUS_FREQUENCY", CONFIG_CANBUS_FREQUENCY); int canserial_send(struct canbus_msg *msg) From 48b60a8021bd02d998fd3d6ea9e55645e3dc75e4 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 26 Jul 2022 14:15:06 -0400 Subject: [PATCH 117/138] graphstats: Normalize mcu frequency to microseconds when graphing multiple mcus Signed-off-by: Kevin O'Connor --- scripts/graphstats.py | 52 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 9 deletions(-) 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: From 18ff84aa04c3dbf39166d4bad210e91a9381960f Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 25 Jul 2022 13:17:23 -0400 Subject: [PATCH 118/138] usb_cdc: Rename usb_request_bootloader() to bootloader_request() Rename this board API function to a more generic name. This is in preparation for calling the function from the canbus code. Signed-off-by: Kevin O'Connor --- src/atsam/main.c | 4 ++-- src/atsamd/usbserial.c | 3 ++- src/avr/usbserial.c | 3 ++- src/generic/misc.h | 2 ++ src/generic/usb_cdc.c | 2 +- src/generic/usb_cdc.h | 1 - src/lpc176x/usbserial.c | 2 +- src/rp2040/main.c | 14 ++++++++++++++ src/rp2040/usbserial.c | 7 ------- src/stm32/stm32f0.c | 7 ++++--- src/stm32/stm32f1.c | 10 +++++----- src/stm32/stm32f4.c | 8 ++++---- src/stm32/stm32g0.c | 5 +++-- src/stm32/stm32h7.c | 9 +++++---- 14 files changed, 45 insertions(+), 32 deletions(-) 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/usbserial.c b/src/atsamd/usbserial.c index e3848926..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 @@ -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/misc.h b/src/generic/misc.h index a24e8b0a..fe0804e4 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_cdc.c b/src/generic/usb_cdc.c index 3a033cc9..40ff74ba 100644 --- a/src/generic/usb_cdc.c +++ b/src/generic/usb_cdc.c @@ -448,7 +448,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/lpc176x/usbserial.c b/src/lpc176x/usbserial.c index bbb90438..af7d66b5 100644 --- a/src/lpc176x/usbserial.c +++ b/src/lpc176x/usbserial.c @@ -247,7 +247,7 @@ usb_set_configure(void) } void -usb_request_bootloader(void) +bootloader_request(void) { if (!CONFIG_SMOOTHIEWARE_BOOTLOADER) return; 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 83838d2d..aafe53ef 100644 --- a/src/rp2040/usbserial.c +++ b/src/rp2040/usbserial.c @@ -160,13 +160,6 @@ usb_set_configure(void) USB_BUF_CTRL_AVAIL | USB_BUF_CTRL_LAST | DPBUF_SIZE); } -void -usb_request_bootloader(void) -{ - // Use the bootrom-provided code to reset into BOOTSEL mode - reset_to_usb_boot(0, 0); -} - /**************************************************************** * USB Errata workaround diff --git a/src/stm32/stm32f0.c b/src/stm32/stm32f0.c index 8ea8f43c..98933b9d 100644 --- a/src/stm32/stm32f0.c +++ b/src/stm32/stm32f0.c @@ -8,6 +8,7 @@ #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 @@ -130,7 +131,7 @@ hsi14_setup(void) /**************************************************************** - * USB bootloader + * Bootloader ****************************************************************/ #define USB_BOOT_FLAG_ADDR (CONFIG_RAM_START + CONFIG_RAM_SIZE - 1024) @@ -160,9 +161,9 @@ 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(); diff --git a/src/stm32/stm32f1.c b/src/stm32/stm32f1.c index 820a5518..9d3c3b97 100644 --- a/src/stm32/stm32f1.c +++ b/src/stm32/stm32f1.c @@ -1,6 +1,6 @@ // 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. @@ -8,7 +8,7 @@ #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 @@ -213,7 +213,7 @@ gpio_peripheral(uint32_t gpio, uint32_t mode, int pullup) /**************************************************************** - * USB bootloader + * Bootloader ****************************************************************/ // Reboot into USB "HID" bootloader @@ -240,9 +240,9 @@ 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) diff --git a/src/stm32/stm32f4.c b/src/stm32/stm32f4.c index 9c6880cf..0b032de7 100644 --- a/src/stm32/stm32f4.c +++ b/src/stm32/stm32f4.c @@ -8,7 +8,7 @@ #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 @@ -188,7 +188,7 @@ clock_setup(void) /**************************************************************** - * USB bootloader + * Bootloader ****************************************************************/ // Reboot into USB "HID" bootloader @@ -228,9 +228,9 @@ 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) diff --git a/src/stm32/stm32g0.c b/src/stm32/stm32g0.c index 36520dfb..36191ebf 100644 --- a/src/stm32/stm32g0.c +++ b/src/stm32/stm32g0.c @@ -8,6 +8,7 @@ #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 @@ -103,7 +104,7 @@ clock_setup(void) /**************************************************************** - * USB bootloader + * Bootloader ****************************************************************/ #define USB_BOOT_FLAG_ADDR (CONFIG_RAM_START + CONFIG_RAM_SIZE - 1024) @@ -132,7 +133,7 @@ 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(); diff --git a/src/stm32/stm32h7.c b/src/stm32/stm32h7.c index bb0fe454..565353dc 100644 --- a/src/stm32/stm32h7.c +++ b/src/stm32/stm32h7.c @@ -6,8 +6,9 @@ #include "autoconf.h" // CONFIG_CLOCK_REF_FREQ #include "board/armcm_boot.h" // VectorTable -#include "board/irq.h" // irq_disable #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" // get_pclock_frequency #include "sched.h" // sched_main @@ -185,7 +186,7 @@ clock_setup(void) /**************************************************************** - * USB bootloader + * Bootloader ****************************************************************/ #define USB_BOOT_FLAG_ADDR (CONFIG_RAM_START + CONFIG_RAM_SIZE - 1024) @@ -212,9 +213,9 @@ 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(); From 2d74b3d358b52b4bc5a301f6aca05690bd6cb941 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 25 Jul 2022 13:27:22 -0400 Subject: [PATCH 119/138] canserial: Request bootloader via bootloader_request() Use bootloader_request() instead of try_request_canboot(). This allows the bootloader machanism to work for more bootloaders. Signed-off-by: Kevin O'Connor --- src/generic/canserial.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/generic/canserial.c b/src/generic/canserial.c index 34f0ce65..8062cfe3 100644 --- a/src/generic/canserial.c +++ b/src/generic/canserial.c @@ -7,7 +7,6 @@ // This file may be distributed under the terms of the GNU GPLv3 license. #include // memcpy -#include "board/armcm_reset.h" // try_request_canboot #include "board/io.h" // readb #include "board/irq.h" // irq_save #include "board/misc.h" // console_sendf @@ -188,7 +187,7 @@ can_process_request_bootloader(struct canbus_msg *msg) { if (!can_check_uuid(msg)) return; - try_request_canboot(); + bootloader_request(); } // Handle an "admin" command From 751bff7d3886077bb0b81ad28e9f367e0d5a81eb Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 25 Jul 2022 19:01:17 -0400 Subject: [PATCH 120/138] mcu: Delay reset signaling for usb to canbus bridge nodes An mcu device acting as an "mcu bridge" should only be reset after other normal devices are reset - otherwise the bridge wont be able to pass along the reset message to the downstream mcus. Signed-off-by: Kevin O'Connor --- klippy/klippy.py | 3 +-- klippy/mcu.py | 15 +++++++++++++-- src/generic/usb_canbus.c | 3 +++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/klippy/klippy.py b/klippy/klippy.py index dbd3cd37..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") diff --git a/klippy/mcu.py b/klippy/mcu.py index 3fda90dc..4aaf6269 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 @@ -794,6 +797,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 @@ -911,7 +918,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': @@ -920,6 +929,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/src/generic/usb_canbus.c b/src/generic/usb_canbus.c index 1631bebe..1a4ac869 100644 --- a/src/generic/usb_canbus.c +++ b/src/generic/usb_canbus.c @@ -13,6 +13,7 @@ #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 @@ -125,6 +126,8 @@ enum { HS_TX_LOCAL = 4, }; +DECL_CONSTANT("CANBUS_BRIDGE", 1); + void canbus_notify_tx(void) { From 9e3feab0b4e68fd3a438276840a650f9f5fc503a Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 27 Jul 2022 13:12:53 -0400 Subject: [PATCH 121/138] stm32: Remove stm32f4 canbus warning The canbus code has been successfully tested on stm32f4. Signed-off-by: Kevin O'Connor --- src/stm32/can.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/stm32/can.c b/src/stm32/can.c index 427a7855..665338ad 100644 --- a/src/stm32/can.c +++ b/src/stm32/can.c @@ -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 From 2005d4dbf45bc15429ac372a290aa36111ab533e Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 27 Jul 2022 13:19:36 -0400 Subject: [PATCH 122/138] docs: Updates to CANBUS_protocol.md Update the document with latest details. Signed-off-by: Kevin O'Connor --- docs/CANBUS_protocol.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 From dc012f8659989f572eab1b839da5ab4c38376155 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 27 Jul 2022 13:38:23 -0400 Subject: [PATCH 123/138] docs: Recommend allow-hotplug in CANBUS.md when using USB to canbus bridge Signed-off-by: Kevin O'Connor --- docs/CANBUS.md | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/docs/CANBUS.md b/docs/CANBUS.md index 90efacf5..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, @@ -118,7 +118,13 @@ Some important notes when using this mode: menuconfig" and the bus speed specified in Linux is ignored. * Whenever the "bridge mcu" is reset, Linux will disable the - corresponding `can0` interface. Generally, this may require running - commands such as "ip up" to restart the interface. Thus, Klipper - FIRMWARE_RESTART commands (or regular RESTART after a config change) - may require restarting the `can0` interface. + 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 +``` From b026f1d2c975604a0ea7ff939f4c36ef3df80a41 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 27 Jul 2022 21:44:51 -0400 Subject: [PATCH 124/138] canserial: Fix typo in canserial.h Signed-off-by: Kevin O'Connor --- src/generic/canserial.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generic/canserial.h b/src/generic/canserial.h index d7a56d3b..e2f55521 100644 --- a/src/generic/canserial.h +++ b/src/generic/canserial.h @@ -16,4 +16,4 @@ 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 // canbus.h +#endif // canserial.h From 49d83bd3e26f4ea768bd978beb300f79f64f1707 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 29 Jul 2022 13:30:39 -0400 Subject: [PATCH 125/138] console: Add support for DUMP and FILEDUMP commands Add helper functions to dump memory via debug_read commands. Signed-off-by: Kevin O'Connor --- klippy/console.py | 53 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) 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]) From 18119858c675f40cf975053b8ff5bd129768d3a3 Mon Sep 17 00:00:00 2001 From: Rui Carmo Date: Fri, 29 Jul 2022 19:48:58 +0100 Subject: [PATCH 126/138] config: Create printer-bq-hephestos-2014.cfg (#5607) This is a working config with full LCD and stepper settings (only thing missing is the kill switch, which did not appear to work) Signed-off-by: Rui Carmo --- config/printer-bq-hephestos-2014.cfg | 99 ++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 config/printer-bq-hephestos-2014.cfg diff --git a/config/printer-bq-hephestos-2014.cfg b/config/printer-bq-hephestos-2014.cfg new file mode 100644 index 00000000..5d77696a --- /dev/null +++ b/config/printer-bq-hephestos-2014.cfg @@ -0,0 +1,99 @@ +# 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: ^PC6, ^PC4 +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: 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: 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 + +# 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 + +# 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 From a709ba43af8edaaa307775ed73cb49fac2b5e550 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 29 Jul 2022 14:52:22 -0400 Subject: [PATCH 127/138] Revert "config: Create printer-bq-hephestos-2014.cfg (#5607)" This reverts commit 18119858c675f40cf975053b8ff5bd129768d3a3. Signed-off-by: Kevin O'Connor --- config/printer-bq-hephestos-2014.cfg | 99 ---------------------------- 1 file changed, 99 deletions(-) delete mode 100644 config/printer-bq-hephestos-2014.cfg diff --git a/config/printer-bq-hephestos-2014.cfg b/config/printer-bq-hephestos-2014.cfg deleted file mode 100644 index 5d77696a..00000000 --- a/config/printer-bq-hephestos-2014.cfg +++ /dev/null @@ -1,99 +0,0 @@ -# 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: ^PC6, ^PC4 -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: 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: 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 - -# 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 - -# 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 From 6aec6efcc963c523d665adabc99e15a736c2dda1 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 16 Aug 2022 21:12:42 -0400 Subject: [PATCH 128/138] stm32: Use new CONFIG_USB to determine if USB needs to be configured Introduce a CONFIG_USB build symbol that is set whenever CONFIG_USBSERIAL or CONFIG_USBCANBUS is set. Use that symbol during setup so that the USB controller is properly initialized for both usb serial and usb canbus bridge configurations. This fixes the clock configuration for usb canbus bridge mode on stm32f446. Signed-off-by: Kevin O'Connor --- src/Kconfig | 7 +++++-- src/stm32/stm32f0.c | 9 ++++----- src/stm32/stm32f4.c | 4 ++-- src/stm32/stm32g0.c | 2 +- src/stm32/stm32h7.c | 4 ++-- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/Kconfig b/src/Kconfig index d8d8a19e..08a57c16 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -57,18 +57,21 @@ 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 && (USBSERIAL || USBCANBUS) + depends on USB && HAVE_CHIPID default y config USB_SERIAL_NUMBER default "12345" menu "USB ids" - depends on (USBSERIAL || USBCANBUS) && LOW_LEVEL_OPTIONS + depends on USB && LOW_LEVEL_OPTIONS config USB_VENDOR_ID hex "USB vendor ID" if USBSERIAL config USB_DEVICE_ID diff --git a/src/stm32/stm32f0.c b/src/stm32/stm32f0.c index 98933b9d..e63f1b5b 100644 --- a/src/stm32/stm32f0.c +++ b/src/stm32/stm32f0.c @@ -86,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 @@ -109,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; } @@ -150,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; @@ -204,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/stm32f4.c b/src/stm32/stm32f4.c index 0b032de7..4f1dc084 100644 --- a/src/stm32/stm32f4.c +++ b/src/stm32/stm32f4.c @@ -139,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; @@ -220,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; diff --git a/src/stm32/stm32g0.c b/src/stm32/stm32g0.c index 36191ebf..63edcbaa 100644 --- a/src/stm32/stm32g0.c +++ b/src/stm32/stm32g0.c @@ -123,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; diff --git a/src/stm32/stm32h7.c b/src/stm32/stm32h7.c index 565353dc..122ce64b 100644 --- a/src/stm32/stm32h7.c +++ b/src/stm32/stm32h7.c @@ -171,7 +171,7 @@ clock_setup(void) ; // 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); @@ -205,7 +205,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*)0x1FF09800; From 2357221bb4f74d0ff1d1cc96dfa235f5662ffb46 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 16 Aug 2022 21:16:08 -0400 Subject: [PATCH 129/138] atsamd: Use CONFIG_USB instead of CONFIG_USBSERIAL during clock init Signed-off-by: Kevin O'Connor --- src/atsamd/clock.c | 2 +- src/atsamd/samd51_clock.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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); From 20a28bc00ff1555e4d096af8f05f77474a3e43aa Mon Sep 17 00:00:00 2001 From: int_0x03 <42711898+int-0x03@users.noreply.github.com> Date: Fri, 19 Aug 2022 20:11:42 +0300 Subject: [PATCH 130/138] config: Anycubic 4 Max Pro 2.0 - Added two internal systems (beeper and PSU control). (#5629) Signed-off-by: Usachev Alexander Valer'evich --- config/printer-anycubic-4maxpro-2.0-2021.cfg | 49 +++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) 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 %} From 6a91824486decfe5ae581430b42b3418b7562140 Mon Sep 17 00:00:00 2001 From: Nitram <33714214+Hywelmartin@users.noreply.github.com> Date: Fri, 19 Aug 2022 19:27:44 +0200 Subject: [PATCH 131/138] delta: Added the possibility to get where the "cone shape" of the build volume starts from Macros (#5662) Added the possibility to get where the "cone shape" of the build volume starts from Macros Signed-off-by: Martin Malmqvist --- docs/Status_Reference.md | 2 ++ klippy/kinematics/delta.py | 1 + 2 files changed, 3 insertions(+) diff --git a/docs/Status_Reference.md b/docs/Status_Reference.md index 7b6f1a42..68e701bb 100644 --- a/docs/Status_Reference.md +++ b/docs/Status_Reference.md @@ -438,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/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 From b1dcd35b7a8919d99db5dc72fc0cf0fba4140352 Mon Sep 17 00:00:00 2001 From: Rui Carmo Date: Fri, 19 Aug 2022 18:57:18 +0100 Subject: [PATCH 132/138] config: Add BQ Hephestos printer (#5676) This is a working config with full LCD and stepper settings (only thing missing is the kill switch, which did not appear to work) Signed-off-by: Rui Carmo --- config/printer-bq-hephestos-2014.cfg | 107 +++++++++++++++++++++++++++ test/klippy/printers.test | 1 + 2 files changed, 108 insertions(+) create mode 100644 config/printer-bq-hephestos-2014.cfg 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/test/klippy/printers.test b/test/klippy/printers.test index 647aeb74..e5ab8fc1 100644 --- a/test/klippy/printers.test +++ b/test/klippy/printers.test @@ -29,6 +29,7 @@ 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 From ce27d359249e75fd546eaad2a393f55486a2fb59 Mon Sep 17 00:00:00 2001 From: Clifford Roche Date: Sat, 6 Aug 2022 13:40:41 -0400 Subject: [PATCH 133/138] palette2: Fix UART encoding Raised from issue #5645, UTF-8 encoded symbols or other unexpected symbols on the UART raise an exception which causes klipper to stop. This change support UTF-8 encoded characters (from file names) as well as ignoring unexpected bytes. Signed-off-by: Clifford Roche --- klippy/extras/palette2.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) 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) From 724b007c507832e7fbaf6b73b9ee2bb4f97af2c1 Mon Sep 17 00:00:00 2001 From: chestwood96 Date: Fri, 19 Aug 2022 20:39:30 +0200 Subject: [PATCH 134/138] rp2040: Enabled hw pullups for the I2C pins (#5710) Signed-off-by: Adrian Joachim --- src/rp2040/i2c.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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); From 9e4994cbdb5a3b1017a0dcca9808efde0de153d4 Mon Sep 17 00:00:00 2001 From: adelyser <12093019+adelyser@users.noreply.github.com> Date: Fri, 19 Aug 2022 14:42:20 -0400 Subject: [PATCH 135/138] stm32: Fix the STM32H743 mcu temp on SKR 3 (#5711) Signed-off-by: Aaron DeLyser --- src/stm32/stm32h7_adc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stm32/stm32h7_adc.c b/src/stm32/stm32h7_adc.c index ca149d3d..00be6209 100644 --- a/src/stm32/stm32h7_adc.c +++ b/src/stm32/stm32h7_adc.c @@ -190,7 +190,7 @@ gpio_adc_setup(uint32_t pin) } if (pin == ADC_TEMPERATURE_PIN) { - ADC3_COMMON->CCR = ADC_CCR_TSEN; + ADC3_COMMON->CCR |= ADC_CCR_TSEN; } else { gpio_peripheral(pin, GPIO_ANALOG, 0); } From a8883d83e377c5ab348099641498b2a621dd4c7b Mon Sep 17 00:00:00 2001 From: BIGTREETECH <38851044+bigtreetech@users.noreply.github.com> Date: Wed, 24 Aug 2022 05:53:35 +0800 Subject: [PATCH 136/138] stm32: add FDCAN support for STM32H743 (SKR-3 Series) (#5668) Signed-off-by: Chen.BJ from BigTreeTech --- src/stm32/Kconfig | 26 ++++++++++++++++++++--- src/stm32/fdcan.c | 52 ++++++++++++++++++++++++++++++++++++++++++--- src/stm32/stm32h7.c | 15 +++++++++++-- 3 files changed, 85 insertions(+), 8 deletions(-) diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index 355b132e..f78580f5 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -107,7 +107,7 @@ config HAVE_STM32_CANBUS default y if MACH_STM32F1 || MACH_STM32F2 || MACH_STM32F4x5 || MACH_STM32F446 || MACH_STM32F0x2 config HAVE_STM32_FDCANBUS bool - default y if MACH_STM32G0 + default y if MACH_STM32G0 || MACH_STM32H7 config HAVE_STM32_USBCANBUS bool depends on HAVE_STM32_USBFS || HAVE_STM32_USBOTG @@ -351,12 +351,20 @@ choice select CANSERIAL 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 @@ -377,10 +385,16 @@ choice depends on HAVE_STM32_CANBUS && MACH_STM32F4 config STM32_CMENU_CANBUS_PD0_PD1 bool "CAN bus (on PD0/PD1)" - depends on HAVE_STM32_CANBUS + 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 @@ -402,5 +416,11 @@ config STM32_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/fdcan.c b/src/stm32/fdcan.c index c142420e..7eac761b 100755 --- a/src/stm32/fdcan.c +++ b/src/stm32/fdcan.c @@ -55,13 +55,25 @@ FDCAN_RAM_TypeDef *fdcan_ram = (FDCAN_RAM_TypeDef *)(SRAMCAN_BASE); 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 +#if !(CONFIG_STM32_CANBUS_PB0_PB1 || CONFIG_STM32_CANBUS_PC2_PC3) #define SOC_CAN FDCAN1 #define MSG_RAM fdcan_ram->fdcan1 #else @@ -69,8 +81,13 @@ FDCAN_RAM_TypeDef *fdcan_ram = (FDCAN_RAM_TypeDef *)(SRAMCAN_BASE); #define MSG_RAM fdcan_ram->fdcan2 #endif -#define CAN_IT0_IRQn TIM16_FDCAN_IT0_IRQn -#define CAN_FUNCTION GPIO_FUNCTION(3) // Alternative function mapping number +#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 @@ -128,8 +145,14 @@ canbus_set_filter(uint32_t id) 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; @@ -251,6 +274,29 @@ can_init(void) 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; diff --git a/src/stm32/stm32h7.c b/src/stm32/stm32h7.c index 122ce64b..056f7be2 100644 --- a/src/stm32/stm32h7.c +++ b/src/stm32/stm32h7.c @@ -46,8 +46,16 @@ lookup_clock_line(uint32_t periph_base) uint32_t bit = 1 << ((periph_base - D2_APB2PERIPH_BASE) / 0x400); return (struct cline){.en=&RCC->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}; + } } } @@ -170,6 +178,9 @@ 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_USB) { SET_BIT(RCC->CR, RCC_CR_HSI48ON); From db507f89b94f28c84c0544519cad4966694a197c Mon Sep 17 00:00:00 2001 From: chestwood96 Date: Wed, 24 Aug 2022 19:30:33 +0200 Subject: [PATCH 137/138] scripts: Update install-debian.sh (#5704) Add pkg-config reference because hid-flash needs it to build. Signed-off-by: Adrian Joachim --- scripts/install-debian.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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..." From f7e29b276e5c2454eaa801e78cf3f6aff29c4ba9 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 24 Aug 2022 20:54:11 -0400 Subject: [PATCH 138/138] docs: Update koconnor donation links Signed-off-by: Kevin O'Connor --- .github/FUNDING.yml | 3 ++- docs/Sponsors.md | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) 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/docs/Sponsors.md b/docs/Sponsors.md index 100cf023..3dc487ff 100644 --- a/docs/Sponsors.md +++ b/docs/Sponsors.md @@ -18,8 +18,8 @@ serve the 3D printing community better. Follow them on ### Kevin O'Connor -Kevin is the original author and current maintainer of Klipper. Kevin -has a Patreon page at: +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