diff --git a/config/generic-mks-monster8.cfg b/config/generic-mks-monster8.cfg new file mode 100644 index 000000000..583d49bff --- /dev/null +++ b/config/generic-mks-monster8.cfg @@ -0,0 +1,268 @@ +# This file contains common pin mappings for MKS Monster8 +# boards. To use this config, the firmware should be compiled for the +# stm32f407. When running "make menuconfig", select the 48KiB +# bootloader, and enable "USB for communication". + +# The "make flash" command does not work on the MKS Monster8. Instead, +# after running "make", copy the generated "out/klipper.bin" file to a +# file named "mks_monster8.bin" on an SD card or Udisk and then restart the +# MKS Monster8 with that SD card or Udisk. + +# See docs/Config_Reference.md for a description of parameters. + +[stepper_x] +step_pin: PC14 +dir_pin: PC13 +enable_pin: !PC15 +microsteps: 16 +rotation_distance: 40 +endstop_pin: !PA14 # PA13 for X-max; endstop have'!' is NO +position_endstop: 0 +position_max: 200 +homing_speed: 50 + +[stepper_y] +step_pin: PE5 +dir_pin: !PE4 +enable_pin: !PC15 +microsteps: 16 +rotation_distance: 40 +endstop_pin: !PA15 # PC5 for Y-max; endstop have'!' is NO +position_endstop: 0 +position_max: 200 +homing_speed: 50 + +[stepper_z] +step_pin: PE1 +dir_pin: PE0 +enable_pin: !PE2 +microsteps: 16 +rotation_distance: 8 +endstop_pin: !PB13 # PB12 for Z-max; endstop have'!' is NO +position_endstop: 0 +position_max: 220 + +[extruder] +step_pin: PB5 +dir_pin: !PB4 +enable_pin: !PB6 +microsteps: 16 +rotation_distance: 33.500 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PB1 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PC1 +control: pid +pid_Kp: 22 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 260 + +#[extruder1] +#step_pin: PD6 +#dir_pin: !PD5 +#enable_pin: !PD7 +#heater_pin: PB0 +#sensor_pin: PC2 + +#[extruder2] +#step_pin: PD2 +#dir_pin: !PD1 +#enable_pin: !PD3 +#heater_pin: PA3 +#sensor_pin: PC3 + +#[extruder3] +#step_pin: PC7 +#dir_pin: PC6 +#enable_pin: !PC8 + +#[extruder4] +#step_pin: PD13 +#dir_pin: !PD12 +#enable_pin: !PD14 + +[heater_bed] +heater_pin: PB10 +sensor_type: NTC 100K MGB18-104F39050L32 +sensor_pin: PC0 +max_power: 1.0 +control: pid +pid_kp: 71.039 +pid_ki: 2.223 +pid_kd: 567.421 +min_temp: 0 +max_temp: 200 + +#fan for printed model FAN0 +[fan] +pin: PA2 + +#fan for hotend FAN1 +#[heater_fan my_nozzle_fan] +[heater_fan fan1] +pin: PA1 +shutdown_speed: 1 + +#fan for control board FAN2 +#[heater_fan my_control_fan] +[heater_fan fan2] +pin: PA0 +shutdown_speed: 1 + +[mcu] +serial: /dev/serial/by-id/usb-Klipper_stm32f407xx_4D0045001850314335393520-if00 + +[printer] +kinematics: cartesian +max_velocity: 10000 +max_accel: 20000 +max_z_velocity: 100 +max_z_accel: 1000 + +##################################################################### +# LED Control +##################################################################### + +#[output_pin caselight ](Use PA9) +## Chamber Lighting - In 5V-RGB Position +#pin: PA9 +#pwm: true +#shutdown_value: 0 +#value:100 +#cycle_time: 0.01 + +######################################## +# TMC UART configuration +######################################## + +#[tmc2208 stepper_x] +#uart_pin: PE6 +#run_current: 0.8 +#hold_current: 0.5 +#stealthchop_threshold: 999999 + +#[tmc2208 stepper_y] +#uart_pin: PE3 +#run_current: 0.8 +#hold_current: 0.5 +#stealthchop_threshold: 999999 + +#[tmc2208 stepper_z] +#uart_pin: PB7 +#run_current: 0.8 +#hold_current: 0.5 +#stealthchop_threshold: 999999 + +#[tmc2208 extruder] +#uart_pin: PB3 +#run_current: 0.8 +#hold_current: 0.5 +#sense_resistor: 0.110 +#stealthchop_threshold: 999999 + +#[tmc2208 extruder1] +#uart_pin: PD4 +#run_current: 0.8 +#hold_current: 0.5 +#stealthchop_threshold: 999999 + +#[tmc2208 extruder2] +#uart_pin: PD0 +#run_current: 0.8 +#hold_current: 0.5 +#stealthchop_threshold: 999999 + +#[tmc2208 extruder3] +#uart_pin: PD15 +#run_current: 0.8 +#hold_current: 0.5 +#stealthchop_threshold: 999999 + +#[tmc2208 extruder4] +#uart_pin: PD11 +#run_current: 0.8 +#hold_current: 0.5 +#stealthchop_threshold: 999999 + +######################################## +# TMC SPI configuration +######################################## + +#[tmc2130 stepper_x] +#spi_bus: spi4 +#cs_pin: PE6 +#diag1_pin: PA14 +#run_current: 0.800 +#hold_current: 0.500 +#stealthchop_threshold: 999999 + +#[tmc2130 stepper_y] +#spi_bus: spi4 +#cs_pin: PE3 +#diag1_pin: PA15 +#run_current: 0.800 +#hold_current: 0.500 +#stealthchop_threshold: 999999 + +#[tmc2130 stepper_z] +#spi_bus: spi4 +#cs_pin: PB7 +#diag1_pin: PB13 +#run_current: 0.800 +#hold_current: 0.500 +#stealthchop_threshold: 999999 + +#[tmc2130 extruder] +#spi_bus: spi4 +#cs_pin: PB3 +#diag1_pin: PA13 +#run_current: 0.800 +#hold_current: 0.500 +#stealthchop_threshold: 999999 + +#[tmc2130 extruder1] +#spi_bus: spi4 +#cs_pin: PD4 +#diag1_pin: PC5 +#run_current: 0.800 +#hold_current: 0.500 +#stealthchop_threshold: 999999 + +#[tmc2130 extruder2] +#spi_bus: spi4 +#cs_pin: PD0 +#diag1_pin: PB12 +#run_current: 0.800 +#hold_current: 0.500 +#stealthchop_threshold: 999999 + +#[tmc2130 extruder3] +#spi_bus: spi4 +#cs_pin: PD15 +#run_current: 0.800 +#hold_current: 0.500 +#stealthchop_threshold: 999999 + +#[tmc2130 extruder4] +#spi_bus: spi4 +#cs_pin: PD11 +#run_current: 0.800 +#hold_current: 0.500 +#stealthchop_threshold: 999999 + +######################################## +# EXP1 / EXP2 (display) pins +######################################## + +[board_pins] +aliases: + # EXP1 header + EXP1_1=PB2, EXP1_3=PE11, EXP1_5=PD9, EXP1_7=PE15, EXP1_9=, + EXP1_2=PE10, EXP1_4=PD10, EXP1_6=PD8, EXP1_8=PE7, EXP1_10=<5V>, + # EXP2 header + EXP2_1=PA6, EXP2_3=PE9, EXP2_5=PE8, EXP2_7=PB11, EXP2_9=, + EXP2_2=PA5, EXP2_4=PA4, EXP2_6=PA7, EXP2_8=, EXP2_10=<3.3v> + # Pins EXP2_1, EXP2_6, EXP2_2 are also MISO, MOSI, SCK of bus "ssp1" diff --git a/config/printer-creality-cr10s-pro-v2-2020.cfg b/config/printer-creality-cr10s-pro-v2-2020.cfg new file mode 100644 index 000000000..e06cd82c1 --- /dev/null +++ b/config/printer-creality-cr10s-pro-v2-2020.cfg @@ -0,0 +1,154 @@ +# This file contains pin mappings for the Creality CR-10S Pro V2. To use +# this config, the firmware should be compiled for the AVR atmega2560. + +# See docs/Config_Reference.md for a description of parameters. + +## General Config +[mcu] +serial: /dev/serial/by-id/ + +[printer] +kinematics: cartesian +max_velocity: 200 +max_accel: 1500 +max_z_velocity: 10 +max_z_accel: 100 + +## Stepper Motors and Extruder +[stepper_x] +step_pin: PF0 #ar54 +dir_pin: PF1 #ar55 +enable_pin: !PD7 #!ar38 +rotation_distance: 40 +microsteps: 16 +full_steps_per_rotation: 200 +endstop_pin: ^PE5 #^ar3 +position_endstop: 0 +position_min: 0 +position_max: 300 +homing_speed: 50 +homing_retract_dist: 5 + +[stepper_y] +step_pin: PF6 #ar60 +dir_pin: !PF7 #ar61 +enable_pin: !PF2 #!ar56 +rotation_distance: 40 +microsteps: 16 +full_steps_per_rotation: 200 +endstop_pin: ^PJ1 #^ar14 +position_endstop: 0 +position_min: 0 +position_max: 310 +homing_speed: 50 +homing_retract_dist: 5 + +[stepper_z] +step_pin: PL3 #ar46 +dir_pin: !PL1 #!ar48 +enable_pin: !PK0 #!ar62 +rotation_distance: 8 +microsteps: 16 +full_steps_per_rotation: 200 +endstop_pin: probe:z_virtual_endstop +position_min: -3 +position_max: 363 # you can go higher but then the cables crimp + +[extruder] +step_pin: PA4 # ar26 +dir_pin: PA6 # !ar28 +enable_pin: !PA2 # !ar24 +rotation_distance: 22.900 +microsteps: 16 +full_steps_per_rotation: 200 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +max_extrude_only_distance: 500.0 +max_extrude_only_velocity: 70.0 +max_extrude_only_accel: 1000.0 +heater_pin: PB4 #ar10 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PK5 #analog13 +control: pid #calibrated in Klipper, you will need to run this for your machine +pid_kp: 28.359 +pid_ki: 1.616 +pid_kd: 124.426 +min_extrude_temp: 170 +min_temp: 5 +max_temp: 275 + +[fan] +pin: PH6 #ar9 + +[heater_bed] +heater_pin: PH5 #ar8 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PK6 #analog14 +control: pid #calibrated in Klipper, you will need to run this for your machine +pid_kp: 70.936 +pid_ki: 1.151 +pid_kd: 1093.298 +min_temp: 5 +max_temp: 140 + +## BLTouch and Safe Z Settings +[bltouch] +sensor_pin: ^PD2 +control_pin: PB5 +stow_on_each_sample: False # Be careful +probe_with_touch_mode: True +x_offset: -27 +y_offset: -2 +z_offset: 2 # you will need to calibrate this in Klipper +speed: 4.0 +samples: 2 +sample_retract_dist: 3.0 + +[safe_z_home] +home_xy_position: 177,155 +speed: 80.0 +z_hop: 10.0 +z_hop_speed: 5.0 + +[bed_mesh] +speed: 120 +horizontal_move_z: 5 +mesh_min: 5, 5 +mesh_max: 268, 305 +probe_count: 5,5 +fade_start: 1 +fade_end: 10 + +[filament_switch_sensor e0_sensor] +switch_pin: PE4 #ar2 +pause_on_runout: False +runout_gcode: + PAUSE_PARK + +## Calibrating the Bed +# this is for the bltouch to calibrate the bed +[screws_tilt_adjust] +screw1: 40,40 +screw1_name: front left screw +screw2: 295,40 +screw2_name: front right screw +screw3: 295,280 +screw3_name: rear right screw +screw4: 40,280 +screw4_name: rear left screw +speed: 50 +horizontal_move_z: 10 +screw_thread: CW-M3 + +[bed_screws] +screw1: 13,38 +screw1_name: front left screw +screw2: 268,38 +screw2_name: front right screw +screw3: 268,278 +screw3_name: rear right screw +screw4: 13,38 +screw4_name: rear left screw +horizontal_move_z: 5 + +[pause_resume] diff --git a/config/printer-creality-ender2pro-hc32-2022.cfg b/config/printer-creality-ender2pro-hc32-2022.cfg new file mode 100644 index 000000000..50f33aae8 --- /dev/null +++ b/config/printer-creality-ender2pro-hc32-2022.cfg @@ -0,0 +1,95 @@ +# This file contains pin mappings for the Creality Ender2 Pro +# with the HC32F460. The board is CR-FDM-v2.5.S4.170 +# To use this config, during "make menuconfig" select HC32F460 + +# Flash this firmware by copying "out/klipper.bin" to a SD card +# as /user/firmware.bin +# Turn on the printer with the card inserted. + +# See docs/Config_Reference.md for a description of parameters. + +[stepper_x] +step_pin: PC1 +dir_pin: PC0 +enable_pin: !PC2 +microsteps: 16 +rotation_distance: 40 +endstop_pin: ^PA5 +position_min: -20 +position_endstop: -20 +position_max: 165 +homing_speed: 50 + +[stepper_y] +step_pin: PB9 +dir_pin: PB8 +enable_pin: !PH2 +microsteps: 16 +rotation_distance: 40 +endstop_pin: ^PA6 +position_min: -5 +position_endstop: -5 +position_max: 165 +homing_speed: 50 + +[stepper_z] +step_pin: PB6 +dir_pin: !PB5 +enable_pin: !PB7 +microsteps: 16 +rotation_distance: 8 +endstop_pin: ^PB0 +position_endstop: 0.0 +position_max: 180 + +[extruder] +max_extrude_only_distance: 100.0 +step_pin: PB3 +dir_pin: PA15 +enable_pin: !PB4 +microsteps: 16 +rotation_distance: 27.53480577 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PA1 +sensor_pin: PC5 +sensor_type: Generic 3950 +control: pid +pid_Kp: 29.634 +pid_Ki: 2.102 +pid_Kd: 104.459 +min_temp: 0 +max_temp: 260 + +[heater_bed] +heater_pin: PB10 +sensor_type: Generic 3950 +sensor_pin: PC4 +control: pid +pid_Kp: 72.921 +pid_Ki: 1.594 +pid_Kd: 834.031 +min_temp: 0 +max_temp: 80 + +[fan] +pin: PA0 + +[mcu] +serial: /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0 +restart_method: command + +[printer] +kinematics: cartesian +max_velocity: 300 +max_accel: 3000 +max_z_velocity: 5 +max_z_accel: 100 + +[display] +lcd_type: st7920 +cs_pin: PB15 +sclk_pin: PB14 +sid_pin: PB12 +encoder_pins: ^PB13,^PA2 +click_pin: ^!PC7 diff --git a/config/printer-creality-ender5-2019.cfg b/config/printer-creality-ender5-2019.cfg index 0130321ba..cf0d4bed0 100644 --- a/config/printer-creality-ender5-2019.cfg +++ b/config/printer-creality-ender5-2019.cfg @@ -1,10 +1,12 @@ # This file contains common pin mappings for the 2019 Creality # Ender 5. To use this config, the firmware should be compiled for the -# AVR atmega1284p. +# AVR atmega1284p. This also works for the v1.1.5 silent boards. # Note, a number of Melzi boards are shipped with a bootloader that # requires the following command to flash the board: # avrdude -p atmega1284p -c arduino -b 57600 -P /dev/ttyUSB0 -U out/klipper.elf.hex +# For v1.1.5 silent boards, the following command is used: +# avrdude -p atmega1284p -c arduino -P /dev/ttyUSB0 -b 115200 -U flash:w:out/klipper.elf.hex # If the above command does not work and "make flash" does not work # then one may need to flash a bootloader to the board - see the # Klipper docs/Bootloaders.md file for more information. @@ -80,6 +82,8 @@ pin: PB4 [mcu] serial: /dev/serial/by-id/usb-1a86_USB2.0-Serial-if00-port0 +# Silent boards tend to have the exact same serial ID, except without USB2.0, using USB instead. +# e.g. /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0 [printer] kinematics: cartesian diff --git a/config/sample-duet3-1lc.cfg b/config/sample-duet3-1lc.cfg new file mode 100644 index 000000000..298c6fafb --- /dev/null +++ b/config/sample-duet3-1lc.cfg @@ -0,0 +1,81 @@ +# This file contains common pin mappings for the Duet3 1LC. To use +# this config, the firmware should be compiled for the SAMC21G18 with: +# Bootloader offset of "No Bootloader" +# Clock Reference of "25 Mhz crystal" if the board version is v1.1 or later +# Clock Reference of "12 Mhz crystal" if the board version is v1.0 or earlier +# Communication interface of "CAN bus (on PA25/PA24)" + +# To flash the board use a debugger, or use a raspberry pi and follow +# the instructions at docs/Bootloaders.md fot the SAMC21. You may +# supply power to the 1LC by connecting the 3.3v rail on the Pi to the +# 5v input of the SWD header on the 1LC. + +# See docs/Config_Reference.md for a description of parameters. + + +# Pins for reference, v1.3 board: +# Driver Step Pin - PA27 +# Driver Dir Pin - PA28 +# Driver Enable - !PB2 +# Thermistor Pins - TEMP0:PB9 TEMP1:PA2 +# Pullup Resistor - 2200 +# Vssa Sense:PA6 | Vref Sense:PA7 +# Current Sense resistor for drivers - 0.091ohm +# CAN Pins - CAN0_TX:PA24 CAN0_RX:PA25 +# Heaters - OUT_0:PA11 +# Fan outputs - OUT_1:PA10 OUT_2:PB11 +# Tach Pins for Fans - OUT_1_TACHO:PA13 OUT_2_TACHO:PB10 +# GPIO_out - IO0:PA12 +# GPIO_in - IO0:PA9 IO1:PA21 IO2:PA18 +# Driver Diag - 0:PB3 + +[adc_scaled toolboard_vref_scaled] +vref_pin: toolboard:PA7 +vssa_pin: toolboard:PA6 + +[extruder] +step_pin: toolboard:PA27 +dir_pin: toolboard:PA28 +enable_pin: !toolboard:PB2 +rotation_distance: 23.1336867485061 +gear_ratio: 50:10 +microsteps: 64 +full_steps_per_rotation: 200 +nozzle_diameter: 0.400 +filament_diameter: 1.75 +heater_pin: toolboard:PA11 +sensor_type: PT1000 +sensor_pin: toolboard_vref_scaled:PB9 +pullup_resistor: 2200 +min_temp: 0 +max_temp: 280 +max_power: 1.0 +control: pid +pwm_cycle_time: 0.01666 +pid_Kp: 26.454 +pid_Ki: 1.357 +pid_Kd: 128.955 + +[tmc2209 extruder] +uart_pin: toolboard:PA20 +tx_pin: toolboard:PA22 +interpolate: False +run_current: 0.35 +sense_resistor: 0.091 + +[fan] +pin: toolboard:PA10 +tachometer_pin: toolboard:PA13 + +[heater_fan hotend_fan] +pin: toolboard:PB11 +tachometer_pin: toolboard:PB10 +heater: extruder +heater_temp: 50.0 + +[probe] +pin: toolboard:PA9 +z_offset: 20 + +[mcu toolboard] +canbus_uuid: 4b194673554e diff --git a/docs/Bootloader_Entry.md b/docs/Bootloader_Entry.md new file mode 100644 index 000000000..ec27baad4 --- /dev/null +++ b/docs/Bootloader_Entry.md @@ -0,0 +1,125 @@ +# Bootloader Entry + +Klipper can be instructed to reboot into a [Bootloader](Bootloaders.md) in one +of the following ways: + +## Requesting the bootloader + +### Virtual Serial + +If a virtual (USB-ACM) serial port is in use, pulsing DTR while at 1200 baud +will request the bootloader. + +#### Python (with `flash_usb`) + +To enter the bootloader using python (using `flash_usb`): + +```shell +> cd klipper/scripts +> python3 -c 'import flash_usb as u; u.enter_bootloader("")' +Entering bootloader on +``` + +Where `` is your serial device, such as +`/dev/serial.by-id/usb-Klipper[...]` or `/dev/ttyACM0` + +Note that if this fails, no output will be printed, success is indicated by +printing `Entering bootloader on `. + +#### Picocom + +```shell +picocom -b 1200 + +``` + +Where `` is your serial device, such as +`/dev/serial.by-id/usb-Klipper[...]` or `/dev/ttyACM0` + +`` means +holding `Ctrl`, pressing and releasing `a`, pressing and releasing `p`, then +releasing `Ctrl` + +### Physical serial + +If a physical serial port is being used on the MCU (even if a USB serial adapter +is being used to connect to it), sending the string +`Request Serial Bootloader!!~` requests the bootloader. + +`` is an ASCII literal space, 0x20. + +`` is the ASCII File Separator, +0x1c. + +Note that this is not a valid message as per the +[MCU Protocol](Protocol.md#micro-controller-interface), but sync characters(`~`) +are still respected. + +Because this message must be the only thing in the "block" +it is received in, prefixing an extra sync character can increase reliability if +other tools were previously accessing the serial port. + +#### Shell + +```shell +stty < /dev/ +echo $'~ \x1c Request Serial Bootloader!! ~' >> /dev/ +``` + +Where `` is your serial port, such as `/dev/ttyS0`, or +`/dev/serial/by-id/gpio-serial2`, and + +`` is the baud rate of the serial +port, such as `115200`. + +### CANBUS + +If CANBUS is in use, a special +[admin message](CANBUS_protocol.md#admin-messages) will request the bootloader. +This message will be respected even if the device already has a nodeid, and will +also be processed if the mcu is shutdown. + +This method also applies to devices operating in +[CANBridge](CANBUS.md#usb-to-can-bus-bridge-mode) mode. + +#### Katapult's flashtool.py + +```shell +python3 ./katapult/scripts/flashtool.py -i -u -r +``` + +Where `` is the can interface to use. If using `can0`, both the `-i` +and `` may be omitted. + +`` is the UUID of your CAN device. + +See the +[CANBUS Documentation](CANBUS.md#finding-the-canbus_uuid-for-new-micro-controllers) +for information on finding the CAN UUID of your devices. + +## Entering the bootloader + +When klipper receives one of the above bootloader requests: + +If Katapult (formerly known as CANBoot) is available, klipper will request that +Katapult stay active on the next boot, then reset the MCU (therefore entering +Katapult). + +If Katapult is not available, klipper will then try to enter a +platform-specific bootloader, such as STM32's DFU +mode([see note](#stm32-dfu-warning)). + +In short, Klipper will reboot to Katapult if installed, then a hardware specific +bootloader if available. + +For details about the specific bootloaders on various platforms see +[Bootloaders](Bootloaders.md) + +## Notes + +### STM32 DFU Warning + +Note that on some boards, like the Octopus Pro v1, entering DFU mode can cause +undesired actions (such as powering the heater while in DFU mode). It is +recommended to disconnect heaters, and otherwise prevent undesired operations +when using DFU mode. Consult the documentation for your board for more details. diff --git a/docs/Bootloaders.md b/docs/Bootloaders.md index 1f7aa24db..56a810955 100644 --- a/docs/Bootloaders.md +++ b/docs/Bootloaders.md @@ -185,6 +185,50 @@ To flash an application use something like: bossac --port=/dev/ttyACM0 -b -U -e -w -v -R out/klipper.bin ``` +## SAMDC21 micro-controllers (Duet3D Toolboard 1LC) + +The SAMC21 is flashed via the ARM Serial Wire Debug (SWD) interface. +This is commonly done with a dedicated SWD hardware dongle. +Alternatively, one can use a +[Raspberry Pi with OpenOCD](#running-openocd-on-the-raspberry-pi). + +When using OpenOCD with the SAMC21, extra steps must be taken to first +put the chip into Cold Plugging mode if the board makes use of the +SWD pins for other purposes. If using OpenOCD on a Rasberry Pi, this +can be done by running the following commands before invoking OpenOCD. +``` +SWCLK=25 +SWDIO=24 +SRST=18 + +echo "Exporting SWCLK and SRST pins." +echo $SWCLK > /sys/class/gpio/export +echo $SRST > /sys/class/gpio/export +echo "out" > /sys/class/gpio/gpio$SWCLK/direction +echo "out" > /sys/class/gpio/gpio$SRST/direction + +echo "Setting SWCLK low and pulsing SRST." +echo "0" > /sys/class/gpio/gpio$SWCLK/value +echo "0" > /sys/class/gpio/gpio$SRST/value +echo "1" > /sys/class/gpio/gpio$SRST/value + +echo "Unexporting SWCLK and SRST pins." +echo $SWCLK > /sys/class/gpio/unexport +echo $SRST > /sys/class/gpio/unexport +``` + +To flash a program with OpenOCD use the following chip config: +``` +source [find target/at91samdXX.cfg] +``` +Obtain a program; for instance, klipper can be built for this chip. +Flash with OpenOCD commands similar to: +``` +at91samd chip-erase +at91samd bootloader 0 +program out/klipper.elf verify +``` + ## SAMD21 micro-controllers (Arduino Zero) The SAMD21 bootloader is flashed via the ARM Serial Wire Debug (SWD) diff --git a/docs/Config_Changes.md b/docs/Config_Changes.md index 379ba1d72..6bfb123ae 100644 --- a/docs/Config_Changes.md +++ b/docs/Config_Changes.md @@ -8,6 +8,19 @@ All dates in this document are approximate. ## Changes +20230826: If `safe_distance` is set or calculated to be 0 in `[dual_carriage]`, +the carriages proximity checks will be disabled as per documentation. A user +may wish to configure `safe_distance` explicitly to prevent accidental crashes +of the carriages with each other. Additionally, the homing order of the primary +and the dual carriage is changed in some configurations (certain configurations +when both carriages home in the same direction, see +[[dual_carriage] configuration reference](./Config_Reference.md#dual_carriage) +for more details). + +20230810: The flash-sdcard.sh script now supports both variants of the +Bigtreetech SKR-3, STM32H743 and STM32H723. For this, the original tag +of btt-skr-3 now has changed to be either btt-skr-3-h743 or btt-skr-3-h723. + 20230801: The setting `fan.off_bellow` has been changed to `fan.min_power`. However, this change will not affect users who do not utilize this setting. With this update, a PWM scaling has been introduced between `min_power` and @@ -21,7 +34,6 @@ If you had previously set `max_power` to anything below 1.0 (default), it is advisable to recalibrate `min_power` and `kick_start_time` again, with the settings `min_power: 0` and `max_power: 1`. - 20230729: The exported status for `dual_carriage` is changed. Instead of exporting `mode` and `active_carriage`, the individual modes for each carriage are exported as `printer.dual_carriage.carriage_0` and diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 31aa013a3..05cacbba7 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -1712,6 +1712,27 @@ cs_pin: # measurements. ``` +### [lis2dw] + +Support for LIS2DW accelerometers. + +``` +[lis2dw] +cs_pin: +# The SPI enable pin for the sensor. This parameter must be provided. +#spi_speed: 5000000 +# The SPI speed (in hz) to use when communicating with the chip. +# The default is 5000000. +#spi_bus: +#spi_software_sclk_pin: +#spi_software_mosi_pin: +#spi_software_miso_pin: +# See the "common SPI settings" section for a description of the +# above parameters. +#axes_map: x, y, z +# See the "adxl345" section for information on this parameter. +``` + ### [mpu9250] Support for MPU-9250, MPU-9255, MPU-6515, MPU-6050, and MPU-6500 @@ -2275,6 +2296,14 @@ in this section (CARRIAGE=0 will return activation to the primary carriage). Dual carriage support is typically combined with extra extruders - the SET_DUAL_CARRIAGE command is often called at the same time as the ACTIVATE_EXTRUDER command. Be sure to park the carriages during deactivation. +Note that during G28 homing, typically the primary carriage is homed first +followed by the carriage defined in the `[dual_carriage]` config section. +However, the `[dual_carriage]` carriage will be homed first if both carriages +home in a positive direction and the [dual_carriage] carriage has a +`position_endstop` greater than the primary carriage, or if both carriages home +in a negative direction and the `[dual_carriage]` carriage has a +`position_endstop` less than the primary carriage. + Additionally, one could use "SET_DUAL_CARRIAGE CARRIAGE=1 MODE=COPY" or "SET_DUAL_CARRIAGE CARRIAGE=1 MODE=MIRROR" commands to activate either copying or mirroring mode of the dual carriage, in which case it will follow the @@ -3662,7 +3691,7 @@ run_current: ### [tmc2240] -Configure a TMC2240 stepper motor driver via SPI bus. To use this +Configure a TMC2240 stepper motor driver via SPI bus or UART. To use this feature, define a config section with a "tmc2240" prefix followed by the name of the corresponding stepper config section (for example, "[tmc2240 stepper_x]"). @@ -3680,6 +3709,9 @@ cs_pin: #spi_software_miso_pin: # See the "common SPI settings" section for a description of the # above parameters. +#uart_pin: +# The pin connected to the TMC2240 DIAG1/SW line. If this parameter +# is provided UART communication is used rather then SPI. #chain_position: #chain_length: # These parameters configure an SPI daisy chain. The two parameters diff --git a/docs/Config_checks.md b/docs/Config_checks.md index ab65678ea..ba4a5015f 100644 --- a/docs/Config_checks.md +++ b/docs/Config_checks.md @@ -67,7 +67,7 @@ The QUERY_ENDSTOPS command should report the endstop as "TRIGGERED". If the endstop appears inverted (it reports "open" when triggered and vice-versa) then add a "!" to the pin definition (for example, -"endstop_pin: ^PA2"), or remove the "!" if there is already one +"endstop_pin: ^!PA2"), or remove the "!" if there is already one present. If the endstop does not change at all then it generally indicates that diff --git a/docs/Measuring_Resonances.md b/docs/Measuring_Resonances.md index cf43e31d3..28cda9d0c 100644 --- a/docs/Measuring_Resonances.md +++ b/docs/Measuring_Resonances.md @@ -1,11 +1,11 @@ # Measuring Resonances -Klipper has built-in support for the ADXL345 and MPU-9250 compatible +Klipper has built-in support for the ADXL345, MPU-9250 and LIS2DW compatible accelerometers which can be used to measure resonance frequencies of the printer for different axes, and auto-tune [input shapers](Resonance_Compensation.md) to compensate for resonances. Note that using accelerometers requires some -soldering and crimping. The ADXL345 can be connected to the SPI interface of a -Raspberry Pi or MCU board (it needs to be reasonably fast). The MPU family can +soldering and crimping. The ADXL345/LIS2DW can be connected to the SPI interface +of a Raspberry Pi or MCU board (it needs to be reasonably fast). The MPU family can be connected to the I2C interface of a Raspberry Pi directly, or to an I2C interface of an MCU board that supports 400kbit/s *fast mode* in Klipper. @@ -13,7 +13,7 @@ When sourcing accelerometers, be aware that there are a variety of different PCB board designs and different clones of them. If it is going to be connected to a 5V printer MCU ensure it has a voltage regulator and level shifters. -For ADXL345s, make sure that the board supports SPI mode (a small number of +For ADXL345s/LIS2DWs, make sure that the board supports SPI mode (a small number of boards appear to be hard-configured for I2C by pulling SDO to GND). For MPU-9250/MPU-9255/MPU-6515/MPU-6050/MPU-6500s there are also a variety of @@ -305,6 +305,26 @@ you'll also want to modify your `printer.cfg` file to include this: Restart Klipper via the `RESTART` command. +#### Configure LIS2DW series + +``` +[mcu lis] +# Change to whatever you found above. For example, +# usb-Klipper_rp2040_E661640843545B2E-if00 +serial: /dev/serial/by-id/usb-Klipper_rp2040_ + +[lis2dw] +cs_pin: lis:gpio1 +spi_bus: spi0a +axes_map: x,z,y + +[resonance_tester] +accel_chip: lis2dw +probe_points: + # Somewhere slightly above the middle of your print bed + 147,154, 20 +``` + #### Configure MPU-6000/9000 series With RPi Make sure the Linux I2C driver is enabled and the baud rate is diff --git a/docs/Overview.md b/docs/Overview.md index da6a88b8a..5d1a87342 100644 --- a/docs/Overview.md +++ b/docs/Overview.md @@ -93,6 +93,7 @@ communication with the Klipper developers. Beaglebone PRU. - [Bootloaders](Bootloaders.md): Developer information on micro-controller flashing. +- [Bootloader Entry](Bootloader_Entry.md): Requesting the bootloader. - [CAN bus](CANBUS.md): Information on using CAN bus with Klipper. - [CAN bus troubleshooting](CANBUS_Troubleshooting.md): Tips for troubleshooting CAN bus. diff --git a/docs/RPi_microcontroller.md b/docs/RPi_microcontroller.md index 58688d70e..96ac5626f 100644 --- a/docs/RPi_microcontroller.md +++ b/docs/RPi_microcontroller.md @@ -198,18 +198,28 @@ default on a Raspberry and can be activated by adding a line to dtoverlay=pwm,pin=12,func=4 ``` This example enables only PWM0 and routes it to gpio12. If both PWM -channels need to be enabled you can use `pwm-2chan`. +channels need to be enabled you can use `pwm-2chan`: +``` +# Enable pwmchip sysfs interface +dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=4 +``` +This example additionally enables PWM1 and routes it to gpio13. The overlay does not expose the pwm line on sysfs on boot and needs to be exported by echo'ing the number of the pwm channel to -`/sys/class/pwm/pwmchip0/export`: +`/sys/class/pwm/pwmchip0/export`. This will create device `/sys/class/pwm/pwmchip0/pwm0` in the +filesystem. The easiest way to do this is by adding this to +`/etc/rc.local` before the `exit 0` line: ``` +# Enable pwmchip sysfs interface echo 0 > /sys/class/pwm/pwmchip0/export ``` - -This will create device `/sys/class/pwm/pwmchip0/pwm0` in the -filesystem. The easiest way to do this is by adding this to -`/etc/rc.local` before the `exit 0` line. +When using both PWM channels, the number of the second channel needs to be echo'd as well: +``` +# Enable pwmchip sysfs interface +echo 0 > /sys/class/pwm/pwmchip0/export +echo 1 > /sys/class/pwm/pwmchip0/export +``` With the sysfs in place, you can now use either the pwm channel(s) by adding the following piece of configuration to your `printer.cfg`: @@ -219,9 +229,17 @@ pin: host:pwmchip0/pwm0 pwm: True hardware_pwm: True cycle_time: 0.000001 + +[output_pin beeper] +pin: host:pwmchip0/pwm1 +pwm: True +hardware_pwm: True +value: 0 +shutdown_value: 0 +cycle_time: 0.0005 ``` -This will add hardware pwm control to gpio12 on the Pi (because the -overlay was configured to route pwm0 to pin=12). +This will add hardware pwm control to gpio12 and gpio13 on the Pi (because the +overlay was configured to route pwm0 to pin=12 and pwm1 to pin=13). PWM0 can be routed to gpio12 and gpio18, PWM1 can be routed to gpio13 and gpio19: diff --git a/docs/Sponsors.md b/docs/Sponsors.md index 7376cb31c..a226bb57b 100644 --- a/docs/Sponsors.md +++ b/docs/Sponsors.md @@ -6,7 +6,7 @@ sponsors. ## BIGTREETECH -[](https://bigtree-tech.com/collections/all-products) +[](https://bigtree-tech.com/collections/all-products) BIGTREETECH is the official mainboard sponsor of Klipper. BIGTREETECH is committed to developing innovative and competitive products to @@ -16,7 +16,8 @@ serve the 3D printing community better. Follow them on ## Sponsors -[](https://obico.io/klipper.html?source=klipper_sponsor) +[](https://obico.io/klipper.html?source=klipper_sponsor) +[](https://peopoly.net) ## Klipper Developers diff --git a/docs/_klipper3d/mkdocs.yml b/docs/_klipper3d/mkdocs.yml index 13a10825a..9373fb6f7 100644 --- a/docs/_klipper3d/mkdocs.yml +++ b/docs/_klipper3d/mkdocs.yml @@ -133,6 +133,7 @@ nav: - RPi_microcontroller.md - Beaglebone.md - Bootloaders.md + - Bootloader_Entry.md - CANBUS.md - CANBUS_Troubleshooting.md - TSL1401CL_Filament_Width_Sensor.md diff --git a/docs/img/sponsors/peopoly-logo.png b/docs/img/sponsors/peopoly-logo.png new file mode 100644 index 000000000..bcf1bd9ce Binary files /dev/null and b/docs/img/sponsors/peopoly-logo.png differ diff --git a/klippy/chelper/serialqueue.c b/klippy/chelper/serialqueue.c index b6500fe62..e6810933a 100644 --- a/klippy/chelper/serialqueue.c +++ b/klippy/chelper/serialqueue.c @@ -62,6 +62,7 @@ struct serialqueue { int ready_bytes, upcoming_bytes, need_ack_bytes, last_ack_bytes; uint64_t need_kick_clock; struct list_head notify_queue; + double last_write_fail_time; // Received messages struct list_head receive_queue; // Fastreader support @@ -376,8 +377,16 @@ do_write(struct serialqueue *sq, void *buf, int buflen) int ret = write(sq->serial_fd, &cf, sizeof(cf)); if (ret < 0) { report_errno("can write", ret); + double curtime = get_monotonic(); + if (!sq->last_write_fail_time) { + sq->last_write_fail_time = curtime; + } else if (curtime > sq->last_write_fail_time + 10.0) { + errorf("Halting reads due to CAN write errors."); + pollreactor_do_exit(sq->pr); + } return; } + sq->last_write_fail_time = 0.0; buf += size; buflen -= size; } diff --git a/klippy/extras/lis2dw.py b/klippy/extras/lis2dw.py new file mode 100644 index 000000000..bfddb0177 --- /dev/null +++ b/klippy/extras/lis2dw.py @@ -0,0 +1,307 @@ +# Support for reading acceleration data from an LIS2DW chip +# +# Copyright (C) 2023 Zhou.XianMing +# Copyright (C) 2020-2021 Kevin O'Connor +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import collections +import logging +import threading +from . import bus, motion_report, adxl345 + +# LIS2DW registers +REG_LIS2DW_WHO_AM_I_ADDR = 0x0F +REG_LIS2DW_CTRL_REG1_ADDR = 0x20 +REG_LIS2DW_CTRL_REG2_ADDR = 0x21 +REG_LIS2DW_CTRL_REG3_ADDR = 0x22 +REG_LIS2DW_CTRL_REG6_ADDR = 0x25 +REG_LIS2DW_STATUS_REG_ADDR = 0x27 +REG_LIS2DW_OUT_XL_ADDR = 0x28 +REG_LIS2DW_OUT_XH_ADDR = 0x29 +REG_LIS2DW_OUT_YL_ADDR = 0x2A +REG_LIS2DW_OUT_YH_ADDR = 0x2B +REG_LIS2DW_OUT_ZL_ADDR = 0x2C +REG_LIS2DW_OUT_ZH_ADDR = 0x2D +REG_LIS2DW_FIFO_CTRL = 0x2E +REG_LIS2DW_FIFO_SAMPLES = 0x2F +REG_MOD_READ = 0x80 +# REG_MOD_MULTI = 0x40 + +LIS2DW_DEV_ID = 0x44 + +FREEFALL_ACCEL = 9.80665 +SCALE = FREEFALL_ACCEL * 1.952 / 4 + +Accel_Measurement = collections.namedtuple( + "Accel_Measurement", ("time", "accel_x", "accel_y", "accel_z") +) + +MIN_MSG_TIME = 0.100 + +BYTES_PER_SAMPLE = 6 +SAMPLES_PER_BLOCK = 8 + +# Printer class that controls LIS2DW chip +class LIS2DW: + def __init__(self, config): + self.printer = config.get_printer() + adxl345.AccelCommandHelper(config, self) + self.query_rate = 0 + am = { + "x": (0, SCALE), + "y": (1, SCALE), + "z": (2, SCALE), + "-x": (0, -SCALE), + "-y": (1, -SCALE), + "-z": (2, -SCALE), + } + axes_map = config.getlist("axes_map", ("x", "y", "z"), count=3) + if any([a not in am for a in axes_map]): + raise config.error("Invalid lis2dw axes_map parameter") + self.axes_map = [am[a.strip()] for a in axes_map] + self.data_rate = 1600 + # Measurement storage (accessed from background thread) + self.lock = threading.Lock() + self.raw_samples = [] + # Setup mcu sensor_lis2dw bulk query code + self.spi = bus.MCU_SPI_from_config(config, 3, default_speed=5000000) + self.mcu = mcu = self.spi.get_mcu() + self.oid = oid = mcu.create_oid() + self.query_lis2dw_cmd = self.query_lis2dw_end_cmd = None + self.query_lis2dw_status_cmd = None + mcu.add_config_cmd( + "config_lis2dw oid=%d spi_oid=%d" % (oid, self.spi.get_oid()) + ) + mcu.add_config_cmd( + "query_lis2dw oid=%d clock=0 rest_ticks=0" % (oid,), on_restart=True + ) + mcu.register_config_callback(self._build_config) + mcu.register_response(self._handle_lis2dw_data, "lis2dw_data", oid) + # Clock tracking + self.last_sequence = self.max_query_duration = 0 + self.last_limit_count = self.last_error_count = 0 + self.clock_sync = adxl345.ClockSyncRegression(self.mcu, 640) + # API server endpoints + self.api_dump = motion_report.APIDumpHelper( + self.printer, self._api_update, self._api_startstop, 0.100 + ) + self.name = config.get_name().split()[-1] + wh = self.printer.lookup_object("webhooks") + wh.register_mux_endpoint( + "lis2dw/dump_lis2dw", "sensor", self.name, self._handle_dump_lis2dw + ) + + def _build_config(self): + cmdqueue = self.spi.get_command_queue() + self.query_lis2dw_cmd = self.mcu.lookup_command( + "query_lis2dw oid=%c clock=%u rest_ticks=%u", cq=cmdqueue + ) + self.query_lis2dw_end_cmd = self.mcu.lookup_query_command( + "query_lis2dw oid=%c clock=%u rest_ticks=%u", + "lis2dw_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" + " buffered=%c fifo=%c limit_count=%hu", + oid=self.oid, + cq=cmdqueue, + ) + self.query_lis2dw_status_cmd = self.mcu.lookup_query_command( + "query_lis2dw_status oid=%c", + "lis2dw_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" + " buffered=%c fifo=%c limit_count=%hu", + oid=self.oid, + cq=cmdqueue, + ) + + def read_reg(self, reg): + params = self.spi.spi_transfer([reg | REG_MOD_READ, 0x00]) + response = bytearray(params["response"]) + return response[1] + + def set_reg(self, reg, val, minclock=0): + self.spi.spi_send([reg, val & 0xFF], minclock=minclock) + stored_val = self.read_reg(reg) + if stored_val != val: + raise self.printer.command_error( + "Failed to set LIS2DW register [0x%x] to 0x%x: got 0x%x. " + "This is generally indicative of connection problems " + "(e.g. faulty wiring) or a faulty lis2dw chip." + % (reg, val, stored_val) + ) + + # Measurement collection + def is_measuring(self): + return self.query_rate > 0 + + def _handle_lis2dw_data(self, params): + with self.lock: + self.raw_samples.append(params) + + def _extract_samples(self, raw_samples): + # Load variables to optimize inner loop below + (x_pos, x_scale), (y_pos, y_scale), (z_pos, z_scale) = self.axes_map + last_sequence = self.last_sequence + time_base, chip_base, inv_freq = self.clock_sync.get_time_translation() + # Process every message in raw_samples + count = seq = 0 + samples = [None] * (len(raw_samples) * SAMPLES_PER_BLOCK) + for params in raw_samples: + seq_diff = (last_sequence - params["sequence"]) & 0xFFFF + seq_diff -= (seq_diff & 0x8000) << 1 + seq = last_sequence - seq_diff + d = bytearray(params["data"]) + msg_cdiff = seq * SAMPLES_PER_BLOCK - chip_base + + for i in range(len(d) // BYTES_PER_SAMPLE): + d_xyz = d[i * BYTES_PER_SAMPLE : (i + 1) * BYTES_PER_SAMPLE] + xlow, xhigh, ylow, yhigh, zlow, zhigh = d_xyz + # Merge and perform twos-complement + + rx = (((xhigh << 8) | xlow)) - ((xhigh & 0x80) << 9) + ry = (((yhigh << 8) | ylow)) - ((yhigh & 0x80) << 9) + rz = (((zhigh << 8) | zlow)) - ((zhigh & 0x80) << 9) + + raw_xyz = (rx, ry, rz) + + x = round(raw_xyz[x_pos] * x_scale, 6) + y = round(raw_xyz[y_pos] * y_scale, 6) + z = round(raw_xyz[z_pos] * z_scale, 6) + + ptime = round(time_base + (msg_cdiff + i) * inv_freq, 6) + samples[count] = (ptime, x, y, z) + count += 1 + self.clock_sync.set_last_chip_clock(seq * SAMPLES_PER_BLOCK + i) + del samples[count:] + return samples + + def _update_clock(self, minclock=0): + # Query current state + for retry in range(5): + params = self.query_lis2dw_status_cmd.send( + [self.oid], minclock=minclock + ) + fifo = params["fifo"] & 0x1F + if fifo <= 32: + break + else: + raise self.printer.command_error("Unable to query lis2dw fifo") + mcu_clock = self.mcu.clock32_to_clock64(params["clock"]) + sequence = (self.last_sequence & ~0xFFFF) | params["next_sequence"] + if sequence < self.last_sequence: + sequence += 0x10000 + self.last_sequence = sequence + buffered = params["buffered"] + limit_count = (self.last_limit_count & ~0xFFFF) | params["limit_count"] + if limit_count < self.last_limit_count: + limit_count += 0x10000 + self.last_limit_count = limit_count + duration = params["query_ticks"] + if duration > self.max_query_duration: + # Skip measurement as a high query time could skew clock tracking + self.max_query_duration = max( + 2 * self.max_query_duration, self.mcu.seconds_to_clock(0.000005) + ) + return + self.max_query_duration = 2 * duration + msg_count = ( + sequence * SAMPLES_PER_BLOCK + buffered // BYTES_PER_SAMPLE + fifo + ) + # The "chip clock" is the message counter plus .5 for average + # inaccuracy of query responses and plus .5 for assumed offset + # of lis2dw hw processing time. + chip_clock = msg_count + 1 + self.clock_sync.update(mcu_clock + duration // 2, chip_clock) + + def _start_measurements(self): + if self.is_measuring(): + return + # In case of miswiring, testing LIS2DW device ID prevents treating + # noise or wrong signal as a correctly initialized device + dev_id = self.read_reg(REG_LIS2DW_WHO_AM_I_ADDR) + logging.info("lis2dw_dev_id: %x", dev_id) + if dev_id != LIS2DW_DEV_ID: + raise self.printer.command_error( + "Invalid lis2dw id (got %x vs %x).\n" + "This is generally indicative of connection problems\n" + "(e.g. faulty wiring) or a faulty lis2dw chip." + % (dev_id, LIS2DW_DEV_ID) + ) + # Setup chip in requested query rate + # ODR/2, +-16g, low-pass filter, Low-noise abled + self.set_reg(REG_LIS2DW_CTRL_REG6_ADDR, 0x34) + # Continuous mode: If the FIFO is full + # the new sample overwrites the older sample. + self.set_reg(REG_LIS2DW_FIFO_CTRL, 0xC0) + # High-Performance / Low-Power mode 1600/200 Hz + # High-Performance Mode (14-bit resolution) + self.set_reg(REG_LIS2DW_CTRL_REG1_ADDR, 0x94) + + # Setup samples + with self.lock: + self.raw_samples = [] + # Start bulk reading + systime = self.printer.get_reactor().monotonic() + print_time = self.mcu.estimated_print_time(systime) + MIN_MSG_TIME + reqclock = self.mcu.print_time_to_clock(print_time) + rest_ticks = self.mcu.seconds_to_clock(4.0 / self.data_rate) + self.query_rate = self.data_rate + self.query_lis2dw_cmd.send( + [self.oid, reqclock, rest_ticks], reqclock=reqclock + ) + logging.info("LIS2DW starting '%s' measurements", self.name) + # Initialize clock tracking + self.last_sequence = 0 + self.last_limit_count = self.last_error_count = 0 + self.clock_sync.reset(reqclock, 0) + self.max_query_duration = 1 << 31 + self._update_clock(minclock=reqclock) + self.max_query_duration = 1 << 31 + + def _finish_measurements(self): + if not self.is_measuring(): + return + # Halt bulk reading + params = self.query_lis2dw_end_cmd.send([self.oid, 0, 0]) + self.query_rate = 0 + with self.lock: + self.raw_samples = [] + logging.info("LIS2DW finished '%s' measurements", self.name) + self.set_reg(REG_LIS2DW_FIFO_CTRL, 0x00) + + # API interface + def _api_update(self, eventtime): + self._update_clock() + with self.lock: + raw_samples = self.raw_samples + self.raw_samples = [] + if not raw_samples: + return {} + samples = self._extract_samples(raw_samples) + if not samples: + return {} + return { + "data": samples, + "errors": self.last_error_count, + "overflows": self.last_limit_count, + } + + def _api_startstop(self, is_start): + if is_start: + self._start_measurements() + else: + self._finish_measurements() + + def _handle_dump_lis2dw(self, web_request): + self.api_dump.add_client(web_request) + hdr = ("time", "x_acceleration", "y_acceleration", "z_acceleration") + web_request.send({"header": hdr}) + + def start_internal_client(self): + cconn = self.api_dump.add_internal_client() + return adxl345.AccelQueryHelper(self.printer, cconn) + + +def load_config(config): + return LIS2DW(config) + + +def load_config_prefix(config): + return LIS2DW(config) diff --git a/klippy/extras/spi_temperature.py b/klippy/extras/spi_temperature.py index 8c1722df3..7cc9e4ce6 100644 --- a/klippy/extras/spi_temperature.py +++ b/klippy/extras/spi_temperature.py @@ -362,9 +362,10 @@ def calc_temp(self, adc): def calc_adc(self, temp): # Calculate relative resistance via Callendar-Van Dusen formula: # resistance = rtd_nominal_r * (1 + CVD_A * temp + CVD_B * temp**2) + temp = min(temp, 1768.3) # Melting point of platinum R_div_nominal = 1.0 + CVD_A * temp + CVD_B * temp * temp adc = int(R_div_nominal / self.adc_to_resist_div_nominal + 0.5) - adc = max(0, min(MAX31865_ADC_MAX, adc)) + adc = max(0, min(MAX31865_ADC_MAX - 1, adc)) adc = adc << 1 # Add fault bit return adc diff --git a/klippy/extras/tmc2240.py b/klippy/extras/tmc2240.py index 14ba8fa67..5d7f3f08e 100644 --- a/klippy/extras/tmc2240.py +++ b/klippy/extras/tmc2240.py @@ -5,7 +5,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import math -from . import tmc, tmc2130 +from . import tmc, tmc2130, tmc_uart TMC_FREQUENCY = 12500000.0 @@ -377,9 +377,16 @@ class TMC2240: def __init__(self, config): # Setup mcu communication self.fields = tmc.FieldHelper(Fields, SignedFields, FieldFormatters) - self.mcu_tmc = tmc2130.MCU_TMC_SPI( - config, Registers, self.fields, TMC_FREQUENCY - ) + if config.get("uart_pin", None) is not None: + # use UART for communication + self.mcu_tmc = tmc_uart.MCU_TMC_uart( + config, Registers, self.fields, 3, TMC_FREQUENCY + ) + else: + # Use SPI bus for communication + self.mcu_tmc = tmc2130.MCU_TMC_SPI( + config, Registers, self.fields, TMC_FREQUENCY + ) # Allow virtual pins to be created tmc.TMCVirtualPinHelper(config, self.mcu_tmc) # Register commands diff --git a/klippy/kinematics/cartesian.py b/klippy/kinematics/cartesian.py index 4eae45d3f..896e7738b 100644 --- a/klippy/kinematics/cartesian.py +++ b/klippy/kinematics/cartesian.py @@ -33,7 +33,9 @@ def __init__(self, toolhead, config): "cartesian_stepper_alloc", dc_axis.encode() ) dc_rail_0 = idex_modes.DualCarriagesRail( - self.rails[0], axis=self.dual_carriage_axis, active=True + self.rails[self.dual_carriage_axis], + axis=self.dual_carriage_axis, + active=True, ) dc_rail_1 = idex_modes.DualCarriagesRail( self.rails[3], axis=self.dual_carriage_axis, active=False diff --git a/klippy/kinematics/idex_modes.py b/klippy/kinematics/idex_modes.py index e1ed0d8de..5edf9c612 100644 --- a/klippy/kinematics/idex_modes.py +++ b/klippy/kinematics/idex_modes.py @@ -78,7 +78,14 @@ def toggle_active_dc_rail(self, index, override_rail=False): def home(self, homing_state): kin = self.printer.lookup_object("toolhead").get_kinematics() - for i, dc_rail in enumerate(self.dc): + enumerated_dcs = list(enumerate(self.dc)) + if (self.get_dc_order(0, 1) > 0) != self.dc[ + 0 + ].get_rail().get_homing_info().positive_dir: + # The second carriage must home first, because the carriages home in + # the same direction and the first carriage homes on the second one + enumerated_dcs.reverse() + for i, dc_rail in enumerated_dcs: self.toggle_active_dc_rail(i, override_rail=True) kin.home_axis(homing_state, self.axis, dc_rail.get_rail()) # Restore the original rails ordering @@ -94,9 +101,15 @@ def get_kin_range(self, toolhead, mode): axes_pos = [dc.get_axis_position(pos) for dc in self.dc] dc0_rail = self.dc[0].get_rail() dc1_rail = self.dc[1].get_rail() - range_min = dc0_rail.position_min - range_max = dc0_rail.position_max + if mode != PRIMARY or self.dc[0].is_active(): + range_min = dc0_rail.position_min + range_max = dc0_rail.position_max + else: + range_min = dc1_rail.position_min + range_max = dc1_rail.position_max safe_dist = self.safe_dist + if not safe_dist: + return (range_min, range_max) if mode == COPY: range_min = max( @@ -106,7 +119,7 @@ def get_kin_range(self, toolhead, mode): range_max, axes_pos[0] - axes_pos[1] + dc1_rail.position_max ) elif mode == MIRROR: - if dc0_rail.get_homing_info().positive_dir: + if self.get_dc_order(0, 1) > 0: range_min = max(range_min, 0.5 * (sum(axes_pos) + safe_dist)) range_max = min( range_max, sum(axes_pos) - dc1_rail.position_min @@ -120,15 +133,41 @@ def get_kin_range(self, toolhead, mode): # mode == PRIMARY active_idx = 1 if self.dc[1].is_active() else 0 inactive_idx = 1 - active_idx - if active_idx: - range_min = dc1_rail.position_min - range_max = dc1_rail.position_max - if self.dc[active_idx].get_rail().get_homing_info().positive_dir: + if self.get_dc_order(active_idx, inactive_idx) > 0: range_min = max(range_min, axes_pos[inactive_idx] + safe_dist) else: range_max = min(range_max, axes_pos[inactive_idx] - safe_dist) + if range_min > range_max: + # During multi-MCU homing it is possible that the carriage + # position will end up below position_min or above position_max + # if position_endstop is too close to the rail motion ends due + # to inherent latencies of the data transmission between MCUs. + # This can result in an invalid range_min > range_max range + # in certain modes, which may confuse the kinematics code. + # So, return an empty range instead, which will correctly + # block the carriage motion until a different mode is selected + # which actually permits carriage motion. + return (range_min, range_min) return (range_min, range_max) + def get_dc_order(self, first, second): + if first == second: + return 0 + # Check the relative order of the first and second carriages and + # return -1 if the first carriage position is always smaller + # than the second one and 1 otherwise + first_rail = self.dc[first].get_rail() + second_rail = self.dc[second].get_rail() + first_homing_info = first_rail.get_homing_info() + second_homing_info = second_rail.get_homing_info() + if first_homing_info.positive_dir != second_homing_info.positive_dir: + # Carriages home away from each other + return 1 if first_homing_info.positive_dir else -1 + # Carriages home in the same direction + if first_rail.position_endstop > second_rail.position_endstop: + return 1 + return -1 + def activate_dc_mode(self, index, mode): toolhead = self.printer.lookup_object("toolhead") toolhead.flush_step_generation() diff --git a/klippy/mcu.py b/klippy/mcu.py index 8db088e8b..966f4c820 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -31,16 +31,18 @@ def __init__(self, serial, name, oid=None): self.reactor = serial.get_reactor() self.completion = self.reactor.completion() self.min_query_time = self.reactor.monotonic() + self.need_response = True self.serial.register_response(self.handle_callback, name, oid) def handle_callback(self, params): - if params["#sent_time"] >= self.min_query_time: - self.min_query_time = self.reactor.NEVER + if self.need_response and params["#sent_time"] >= self.min_query_time: + self.need_response = False self.reactor.async_complete(self.completion, params) def get_response(self, cmds, cmd_queue, minclock=0, reqclock=0): (cmd,) = cmds self.serial.raw_send_wait_ack(cmd, minclock, reqclock, cmd_queue) + self.min_query_time = 0.0 first_query_time = query_time = self.reactor.monotonic() while 1: params = self.completion.wait(query_time + self.RETRY_TIME) diff --git a/lib/README b/lib/README index b3bd95e56..e981df59f 100644 --- a/lib/README +++ b/lib/README @@ -167,7 +167,7 @@ used to upload firmware to devices flashed with the CanBoot bootloader. The can2040 directory contains code from: https://github.com/KevinOConnor/can2040 -revision d1190afcaa6245c20da28199d06e453d2e743099. +version v1.6.0 (af3d21e5d61b8408c63fbdfb0aceb21d69d91693) The Huada HC32F460 directory contains code from: https://www.hdsc.com.cn/Category83-1490 diff --git a/lib/can2040/can2040.c b/lib/can2040/can2040.c index 926893d94..c2bd0061a 100644 --- a/lib/can2040/can2040.c +++ b/lib/can2040/can2040.c @@ -1,6 +1,6 @@ // Software CANbus implementation for rp2040 // -// Copyright (C) 2022 Kevin O'Connor +// Copyright (C) 2022,2023 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -318,6 +318,14 @@ pio_irq_set(struct can2040 *cd, uint32_t sm_irqs) pio_hw->inte0 = sm_irqs | SI_RX_DATA; } +// Completely disable host irqs +static void +pio_irq_disable(struct can2040 *cd) +{ + pio_hw_t *pio_hw = cd->pio_hw; + pio_hw->inte0 = 0; +} + // Return current host irq mask static uint32_t pio_irq_get(struct can2040 *cd) @@ -662,6 +670,7 @@ tx_schedule_transmit(struct can2040 *cd) pio_signal_set_txpending(cd); } cd->tx_state = TS_QUEUED; + cd->stats.tx_attempt++; struct can2040_transmit *qt = &cd->tx_queue[tx_qpos(cd, tx_pull_pos)]; pio_tx_send(cd, qt->stuffed_data, qt->stuffed_words); return 0; @@ -721,6 +730,7 @@ report_callback_error(struct can2040 *cd, uint32_t error_code) static void report_callback_rx_msg(struct can2040 *cd) { + cd->stats.rx_total++; cd->rx_cb(cd, CAN2040_NOTIFY_RX, &cd->parse_msg); } @@ -729,6 +739,7 @@ static void report_callback_tx_msg(struct can2040 *cd) { writel(&cd->tx_pull_pos, cd->tx_pull_pos + 1); + cd->stats.tx_total++; cd->rx_cb(cd, CAN2040_NOTIFY_TX, &cd->parse_msg); } @@ -748,11 +759,11 @@ report_handle_eof(struct can2040 *cd) pio_match_clear(cd); } -// Check if in an rx message is being processed +// Check if message being processed is an rx message (not self feedback from tx) static int -report_is_rx_eof_pending(struct can2040 *cd) +report_is_not_in_tx(struct can2040 *cd) { - return cd->report_state == RS_NEED_RX_EOF; + return !(cd->report_state & RS_NEED_TX_ACK); } // Parser found a new message start @@ -817,7 +828,7 @@ report_note_eof_success(struct can2040 *cd) // Parser found unexpected data on input static void -report_note_parse_error(struct can2040 *cd) +report_note_discarding(struct can2040 *cd) { if (cd->report_state != RS_IDLE) { cd->report_state = RS_IDLE; @@ -880,7 +891,7 @@ report_line_txpending(struct can2040 *cd) return; } // Tx request from can2040_transmit(), report_note_eof_success(), - // or report_note_parse_error(). + // or report_note_discarding(). uint32_t check_txpending = tx_schedule_transmit(cd); pio_irq_set(cd, (pio_irqs & ~SI_TXPENDING) | check_txpending); } @@ -896,6 +907,13 @@ enum { MS_CRC, MS_ACK, MS_EOF0, MS_EOF1, MS_DISCARD }; +// Reset any bits in the incoming parsing state +static void +data_state_clear_bits(struct can2040 *cd) +{ + cd->raw_bit_count = cd->unstuf.stuffed_bits = cd->unstuf.count_stuff = 0; +} + // Transition to the next parsing state static void data_state_go_next(struct can2040 *cd, uint32_t state, uint32_t num_bits) @@ -908,23 +926,35 @@ data_state_go_next(struct can2040 *cd, uint32_t state, uint32_t num_bits) static void data_state_go_discard(struct can2040 *cd) { - report_note_parse_error(cd); - if (pio_rx_check_stall(cd)) { // CPU couldn't keep up for some read data - must reset pio state - cd->raw_bit_count = cd->unstuf.count_stuff = 0; + data_state_clear_bits(cd); pio_sm_setup(cd); report_callback_error(cd, 0); } data_state_go_next(cd, MS_DISCARD, 32); + + // Clear report state and update hw irqs after transition to MS_DISCARD + report_note_discarding(cd); +} + +// Note a data parse error and transition to discard state +static void +data_state_go_error(struct can2040 *cd) +{ + cd->stats.parse_error++; + data_state_go_discard(cd); } // Received six dominant bits on the line static void data_state_line_error(struct can2040 *cd) { - data_state_go_discard(cd); + if (cd->parse_state == MS_DISCARD) + data_state_go_discard(cd); + else + data_state_go_error(cd); } // Received six unexpected passive bits on the line @@ -933,7 +963,7 @@ data_state_line_passive(struct can2040 *cd) { if (cd->parse_state != MS_DISCARD && cd->parse_state != MS_START) { // Bitstuff error - data_state_go_discard(cd); + data_state_go_error(cd); return; } @@ -941,8 +971,7 @@ data_state_line_passive(struct can2040 *cd) uint32_t dom_bits = ~stuffed_bits; if (!dom_bits) { // Counter overflow in "sync" state machine - reset it - cd->unstuf.stuffed_bits = 0; - cd->raw_bit_count = cd->unstuf.count_stuff = 0; + data_state_clear_bits(cd); pio_sm_setup(cd); data_state_go_discard(cd); return; @@ -972,7 +1001,7 @@ data_state_go_crc(struct can2040 *cd) int ret = report_note_crc_start(cd); if (ret) { - data_state_go_discard(cd); + data_state_go_error(cd); return; } data_state_go_next(cd, MS_CRC, 16); @@ -1065,7 +1094,7 @@ static void data_state_update_crc(struct can2040 *cd, uint32_t data) { if (((cd->parse_crc << 1) | 1) != data) { - data_state_go_discard(cd); + data_state_go_error(cd); return; } @@ -1083,7 +1112,7 @@ data_state_update_ack(struct can2040 *cd, uint32_t data) // data_state_line_passive() unstuf_restore_state(&cd->unstuf, (cd->parse_crc_bits << 2) | data); - data_state_go_discard(cd); + data_state_go_error(cd); return; } report_note_ack_success(cd); @@ -1095,7 +1124,7 @@ static void data_state_update_eof0(struct can2040 *cd, uint32_t data) { if (data != 0x0f || pio_rx_check_stall(cd)) { - data_state_go_discard(cd); + data_state_go_error(cd); return; } unstuf_clear_state(&cd->unstuf); @@ -1106,14 +1135,17 @@ data_state_update_eof0(struct can2040 *cd, uint32_t data) static void data_state_update_eof1(struct can2040 *cd, uint32_t data) { - if (data >= 0x1c || (data >= 0x18 && report_is_rx_eof_pending(cd))) - // Message is considered fully transmitted + if (data == 0x1f) { + // Success report_note_eof_success(cd); - - if (data == 0x1f) data_state_go_next(cd, MS_START, 1); - else + } else if (data >= 0x1c || (data >= 0x18 && report_is_not_in_tx(cd))) { + // Message fully transmitted - followed by "overload frame" + report_note_eof_success(cd); data_state_go_discard(cd); + } else { + data_state_go_error(cd); + } } // Handle data received while in MS_DISCARD state @@ -1310,13 +1342,28 @@ can2040_start(struct can2040 *cd, uint32_t sys_clock, uint32_t bitrate { cd->gpio_rx = gpio_rx; cd->gpio_tx = gpio_tx; + data_state_clear_bits(cd); pio_setup(cd, sys_clock, bitrate); data_state_go_discard(cd); } -// API function to stop and uninitialize can2040 code +// API function to stop can2040 code void -can2040_shutdown(struct can2040 *cd) +can2040_stop(struct can2040 *cd) { - // XXX + pio_irq_disable(cd); + pio_sm_setup(cd); +} + +// API function to access can2040 statistics +void +can2040_get_statistics(struct can2040 *cd, struct can2040_stats *stats) +{ + for (;;) { + memcpy(stats, &cd->stats, sizeof(*stats)); + if (memcmp(stats, &cd->stats, sizeof(*stats)) == 0) + // Successfully copied data + return; + // Raced with irq handler update - retry copy + } } diff --git a/lib/can2040/can2040.h b/lib/can2040/can2040.h index fc0bdd627..7dbee1162 100644 --- a/lib/can2040/can2040.h +++ b/lib/can2040/can2040.h @@ -26,11 +26,18 @@ struct can2040; typedef void (*can2040_rx_cb)(struct can2040 *cd, uint32_t notify , struct can2040_msg *msg); +struct can2040_stats { + uint32_t rx_total, tx_total; + uint32_t tx_attempt; + uint32_t parse_error; +}; + void can2040_setup(struct can2040 *cd, uint32_t pio_num); void can2040_callback_config(struct can2040 *cd, can2040_rx_cb rx_cb); void can2040_start(struct can2040 *cd, uint32_t sys_clock, uint32_t bitrate , uint32_t gpio_rx, uint32_t gpio_tx); -void can2040_shutdown(struct can2040 *cd); +void can2040_stop(struct can2040 *cd); +void can2040_get_statistics(struct can2040 *cd, struct can2040_stats *stats); void can2040_pio_irq_handler(struct can2040 *cd); int can2040_check_transmit(struct can2040 *cd); int can2040_transmit(struct can2040 *cd, struct can2040_msg *msg); @@ -56,6 +63,7 @@ struct can2040 { void *pio_hw; uint32_t gpio_rx, gpio_tx; can2040_rx_cb rx_cb; + struct can2040_stats stats; // Bit unstuffing struct can2040_bitunstuffer unstuf; diff --git a/scripts/flash_usb.py b/scripts/flash_usb.py index 724d55b1f..3f75392f5 100755 --- a/scripts/flash_usb.py +++ b/scripts/flash_usb.py @@ -394,6 +394,7 @@ def flash_rp2040(options, binfile): "stm32f103": flash_stm32f1, "stm32f4": flash_stm32f4, "stm32f042": flash_stm32f4, + "stm32f070": flash_stm32f4, "stm32f072": flash_stm32f4, "stm32g0b1": flash_stm32f4, "stm32f7": flash_stm32f4, diff --git a/scripts/spi_flash/board_defs.py b/scripts/spi_flash/board_defs.py index eb75e0714..173170cf9 100644 --- a/scripts/spi_flash/board_defs.py +++ b/scripts/spi_flash/board_defs.py @@ -59,9 +59,36 @@ "spi_pins": "PA6,PB5,PA5", "cs_pin": "PA4", }, - "btt-gtr": {"mcu": "stm32f407xx", "spi_bus": "spi1", "cs_pin": "PA4"}, - "fysetc-spider": { - "mcu": "stm32f446xx", + "btt-skr-se-bx": { + "mcu": "stm32h743xx", + "spi_bus": "spi3a", + "cs_pin": "PA15", + }, + "btt-skr-3-h743": { + "mcu": "stm32h743xx", + "spi_bus": "swspi", + "spi_pins": "PC8,PD2,PC12", + "cs_pin": "PC11", + "skip_verify": True, + }, + "btt-skr-3-h723": { + "mcu": "stm32h723xx", + "spi_bus": "swspi", + "spi_pins": "PC8,PD2,PC12", + "cs_pin": "PC11", + "skip_verify": True, + }, + "creality-v4.2.2": { + "mcu": "stm32f103xe", + "spi_bus": "swspi", + "spi_pins": "PC8,PD2,PC12", + "cs_pin": "PC11", + "skip_verify": True, + }, + "monster8": {"mcu": "stm32f407xx", "spi_bus": "spi3a", "cs_pin": "PC9"}, + "fly-gemini-v2": {"mcu": "stm32f405xx", "spi_bus": "spi1", "cs_pin": "PA4"}, + "fysetc-cheetah": { + "mcu": "stm32f401xc", "spi_bus": "spi1", "cs_pin": "PA4", "current_firmware_path": "OLD.BIN", @@ -104,6 +131,7 @@ "btt-octopus-f446-v1.1": BOARD_DEFS["btt-octopus-f446-v1"], "btt-octopus-pro-f429-v1.0": BOARD_DEFS["btt-octopus-f429-v1"], "btt-octopus-pro-f446-v1.0": BOARD_DEFS["btt-octopus-f446-v1"], + "btt-octopus-pro-h723-v1.1": BOARD_DEFS["btt-skr-3-h723"], "btt-skr-pro-v1.1": BOARD_DEFS["btt-skr-pro"], "btt-skr-pro-v1.2": BOARD_DEFS["btt-skr-pro"], "btt-gtr-v1": BOARD_DEFS["btt-gtr"], diff --git a/src/Kconfig b/src/Kconfig index 3c49a73f9..c4a9d8828 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -102,6 +102,10 @@ config WANT_SENSORS bool depends on HAVE_GPIO_I2C || HAVE_GPIO_SPI default y +config WANT_LIS2DW + bool + depends on HAVE_GPIO_SPI + default y config WANT_SOFTWARE_I2C bool depends on HAVE_GPIO && HAVE_GPIO_I2C @@ -121,6 +125,9 @@ config WANT_DISPLAYS config WANT_SENSORS bool "Support external sensor devices" depends on HAVE_GPIO_I2C || HAVE_GPIO_SPI +config WANT_LIS2DW + bool "Support lis2dw 3-axis accelerometer" + depends on HAVE_GPIO_SPI config WANT_SOFTWARE_I2C bool "Support software based I2C \"bit-banging\"" depends on HAVE_GPIO && HAVE_GPIO_I2C diff --git a/src/Makefile b/src/Makefile index e89845549..8d771f9eb 100644 --- a/src/Makefile +++ b/src/Makefile @@ -16,5 +16,6 @@ src-$(CONFIG_WANT_SOFTWARE_SPI) += spi_software.c src-$(CONFIG_WANT_SOFTWARE_I2C) += i2c_software.c sensors-src-$(CONFIG_HAVE_GPIO_SPI) := thermocouple.c sensor_adxl345.c \ sensor_angle.c +src-$(CONFIG_WANT_LIS2DW) += sensor_lis2dw.c sensors-src-$(CONFIG_HAVE_GPIO_I2C) += sensor_mpu9250.c src-$(CONFIG_WANT_SENSORS) += $(sensors-src-y) diff --git a/src/atsam/Kconfig b/src/atsam/Kconfig index 7fac95f50..4a10a0f7d 100644 --- a/src/atsam/Kconfig +++ b/src/atsam/Kconfig @@ -50,6 +50,9 @@ config MACH_SAM4E select MACH_SAM4 config MACH_SAME70 bool +config HAVE_SAM_CANBUS + bool + default y if MACH_SAME70 config MCU string @@ -101,6 +104,32 @@ choice config ATSAM_SERIAL bool "Serial" select SERIAL + config ATSAM_MMENU_CANBUS_PC12_PD12 + bool "CAN bus (on PC12/PD12)" + depends on HAVE_SAM_CANBUS + select CANSERIAL + config ATSAM_MMENU_CANBUS_PB3_PB2 + bool "CAN bus (on PB3/PB2)" + depends on HAVE_SAM_CANBUS + select CANSERIAL + config ATSAM_USBCANBUS + bool "USB to CAN bus bridge" + depends on HAVE_SAM_CANBUS + select USBCANBUS +endchoice +choice + prompt "CAN bus interface" if USBCANBUS + config ATSAM_CMENU_CANBUS_PC12_PD12 + bool "CAN bus (on PC12/PD12)" + config ATSAM_CMENU_CANBUS_PB3_PB2 + bool "CAN bus (on PB3/PB2)" endchoice +config ATSAM_CANBUS_PC12_PD12 + bool + default y if ATSAM_MMENU_CANBUS_PC12_PD12 || ATSAM_CMENU_CANBUS_PC12_PD12 +config ATSAM_CANBUS_PB3_PB2 + bool + default y if ATSAM_MMENU_CANBUS_PB3_PB2 || ATSAM_CMENU_CANBUS_PB3_PB2 + endif diff --git a/src/atsam/Makefile b/src/atsam/Makefile index 1c32cb40c..7ab69b823 100644 --- a/src/atsam/Makefile +++ b/src/atsam/Makefile @@ -3,7 +3,7 @@ # Setup the toolchain CROSS_PREFIX=arm-none-eabi- -dirs-y += src/atsam src/generic +dirs-y += src/atsam src/generic lib/fast-hash dirs-$(CONFIG_MACH_SAM3X) += lib/sam3x/gcc dirs-$(CONFIG_MACH_SAM4S) += lib/sam4s/gcc dirs-$(CONFIG_MACH_SAM4E) += lib/sam4e/gcc @@ -18,7 +18,7 @@ CFLAGS-$(CONFIG_MACH_SAM3X) += -Ilib/sam3x/include CFLAGS-$(CONFIG_MACH_SAM4S) += -Ilib/sam4s/include CFLAGS-$(CONFIG_MACH_SAM4E) += -Ilib/sam4e/include CFLAGS-$(CONFIG_MACH_SAME70) += -Ilib/same70b/include -CFLAGS += $(CFLAGS-y) -D__$(MCU)__ -mthumb -Ilib/cmsis-core +CFLAGS += $(CFLAGS-y) -D__$(MCU)__ -mthumb -Ilib/cmsis-core -Ilib/fast-hash CFLAGS_klipper.elf += --specs=nano.specs --specs=nosys.specs CFLAGS_klipper.elf += -T $(OUT)src/generic/armcm_link.ld @@ -33,6 +33,10 @@ usb-src-$(CONFIG_MACH_SAM4) := atsam/sam4_usb.c usb-src-$(CONFIG_MACH_SAME70) := atsam/sam3_usb.c src-$(CONFIG_USBSERIAL) += $(usb-src-y) atsam/chipid.c generic/usb_cdc.c src-$(CONFIG_SERIAL) += atsam/serial.c generic/serial_irq.c +canbus-src-y := generic/canserial.c ../lib/fast-hash/fasthash.c +canbus-src-y += atsam/fdcan.c atsam/chipid.c +src-$(CONFIG_USBCANBUS) += $(canbus-src-y) $(usb-src-y) generic/usb_canbus.c +src-$(CONFIG_CANSERIAL) += $(canbus-src-y) generic/canbus.c src-$(CONFIG_MACH_SAM3X) += atsam/adc.c atsam/hard_pwm.c src-$(CONFIG_MACH_SAM4) += atsam/hard_pwm.c src-$(CONFIG_MACH_SAM4S) += atsam/adc.c diff --git a/src/atsam/chipid.c b/src/atsam/chipid.c index b4ab289ca..dda4c5d00 100644 --- a/src/atsam/chipid.c +++ b/src/atsam/chipid.c @@ -5,6 +5,7 @@ // This file may be distributed under the terms of the GNU GPLv3 license. #include "generic/irq.h" // irq_disable +#include "generic/canserial.h" // canserial_set_uuid #include "generic/usb_cdc.h" // usb_fill_serial #include "generic/usbstd.h" // usb_string_descriptor #include "internal.h" // EFC0 @@ -61,7 +62,7 @@ read_chip_id(uint32_t *id) void chipid_init(void) { - if (!CONFIG_USB_SERIAL_NUMBER_CHIPID) + if (!CONFIG_USB_SERIAL_NUMBER_CHIPID && !CONFIG_CANBUS) return; uint32_t id[4]; @@ -69,6 +70,10 @@ chipid_init(void) read_chip_id(id); irq_enable(); - usb_fill_serial(&cdc_chipid.desc, ARRAY_SIZE(cdc_chipid.data), id); + if (CONFIG_USB_SERIAL_NUMBER_CHIPID) + usb_fill_serial(&cdc_chipid.desc, ARRAY_SIZE(cdc_chipid.data), id); + + if (CONFIG_CANBUS) + canserial_set_uuid((void*)id, CHIP_UID_LEN); } DECL_INIT(chipid_init); diff --git a/src/atsam/fdcan.c b/src/atsam/fdcan.c new file mode 100644 index 000000000..a536a7be1 --- /dev/null +++ b/src/atsam/fdcan.c @@ -0,0 +1,307 @@ +// CANbus support on atsame70 chips +// +// Copyright (C) 2021-2022 Kevin O'Connor +// Copyright (C) 2019 Eug Krashtan +// Copyright (C) 2020 Pontus Borg +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include "command.h" // DECL_CONSTANT_STR +#include "generic/armcm_boot.h" // armcm_enable_irq +#include "generic/canbus.h" // canbus_notify_tx +#include "generic/canserial.h" // CANBUS_ID_ADMIN +#include "internal.h" // enable_pclock +#include "sched.h" // DECL_INIT + + +/**************************************************************** + * Pin configuration + ****************************************************************/ + +#if CONFIG_ATSAM_CANBUS_PB3_PB2 + DECL_CONSTANT_STR("RESERVE_PINS_CAN", "PB3,PB2"); + #define GPIO_Rx GPIO('B', 3) + #define GPIO_Tx GPIO('B', 2) + #define CANx_GCLK_ID MCAN0_CLOCK_ID +#elif CONFIG_ATSAM_CANBUS_PC12_PD12 + DECL_CONSTANT_STR("RESERVE_PINS_CAN", "PC12,PD12"); + #define GPIO_Rx GPIO('C', 12) + #define GPIO_Tx GPIO('D', 12) + #define CANx_GCLK_ID MCAN1_CLOCK_ID +#endif + +#if CANx_GCLK_ID == MCAN0_CLOCK_ID + #define CAN_FUNCTION_Rx 'A' + #define CAN_FUNCTION_Tx 'A' + #define CANx MCAN0 + #define CANx_IRQn MCAN0_INT0_IRQn + #define CCFG_CANxDMABA MATRIX->CCFG_CAN0 + #define CCFG_CANxDMABA_Msk CCFG_CAN0_CAN0DMABA_Msk + #define CCFG_CANxDMABA_Pos CCFG_CAN0_CAN0DMABA_Pos +#else + #define CAN_FUNCTION_Rx 'C' + #define CAN_FUNCTION_Tx 'B' + #define CANx MCAN1 + #define CANx_IRQn MCAN1_INT0_IRQn + #define CCFG_CANxDMABA MATRIX->CCFG_SYSIO + #define CCFG_CANxDMABA_Msk CCFG_SYSIO_CAN1DMABA_Msk + #define CCFG_CANxDMABA_Pos CCFG_SYSIO_CAN1DMABA_Pos +#endif + + +/**************************************************************** + * Message ram layout + ****************************************************************/ + +struct fdcan_fifo { + uint32_t id_section; + uint32_t dlc_section; + uint32_t data[64 / 4]; +}; + +#define FDCAN_XTD (1<<30) +#define FDCAN_RTR (1<<29) + +struct fdcan_msg_ram { + uint32_t FLS[28]; // Filter list standard + uint32_t FLE[16]; // Filter list extended + struct fdcan_fifo RXF0[3]; + struct fdcan_fifo RXF1[3]; + uint32_t TEF[6]; // Tx event FIFO + struct fdcan_fifo TXFIFO[3]; +}; + +// Message ram is in regular memory +static struct fdcan_msg_ram MSG_RAM; + + +/**************************************************************** + * CANbus code + ****************************************************************/ + +#define FDCAN_IE_TC (MCAN_IE_TCE | MCAN_IE_TCFE | MCAN_IE_TFEE) + +// Transmit a packet +int +canhw_send(struct canbus_msg *msg) +{ + uint32_t txfqs = CANx->MCAN_TXFQS; + if (txfqs & MCAN_TXFQS_TFQF) + // No space in transmit fifo - wait for irq + return -1; + + uint32_t w_index = ((txfqs & MCAN_TXFQS_TFQPI_Msk) >> MCAN_TXFQS_TFQPI_Pos); + struct fdcan_fifo *txfifo = &MSG_RAM.TXFIFO[w_index]; + uint32_t ids; + if (msg->id & CANMSG_ID_EFF) + ids = (msg->id & 0x1fffffff) | FDCAN_XTD; + else + ids = (msg->id & 0x7ff) << 18; + ids |= msg->id & CANMSG_ID_RTR ? FDCAN_RTR : 0; + txfifo->id_section = ids; + txfifo->dlc_section = (msg->dlc & 0x0f) << 16; + txfifo->data[0] = msg->data32[0]; + txfifo->data[1] = msg->data32[1]; + __DMB(); + CANx->MCAN_TXBAR; + CANx->MCAN_TXBAR = ((uint32_t)1 << w_index); + return CANMSG_DATA_LEN(msg); +} + +static void +can_filter(uint32_t index, uint32_t id) +{ + MSG_RAM.FLS[index] = ((0x2 << 30) // Classic filter + | (0x1 << 27) // Store in Rx FIFO 0 if filter matches + | (id << 16) + | 0x7FF); // mask all enabled +} + +// Setup the receive packet filter +void +canhw_set_filter(uint32_t id) +{ + if (!CONFIG_CANBUS_FILTER) + return; + /* Request initialisation */ + CANx->MCAN_CCCR |= MCAN_CCCR_INIT; + /* Wait the acknowledge */ + while (!(CANx->MCAN_CCCR & MCAN_CCCR_INIT)) + ; + /* Enable configuration change */ + CANx->MCAN_CCCR |= MCAN_CCCR_CCE; + + // Load filter + can_filter(0, CANBUS_ID_ADMIN); + can_filter(1, id); + can_filter(2, id + 1); + + uint32_t flssa = (uint32_t)MSG_RAM.FLS + - (CCFG_CANxDMABA_Msk & CCFG_CANxDMABA); + CANx->MCAN_SIDFC = flssa | ((id ? 3 : 1) << MCAN_SIDFC_LSS_Pos); + CANx->MCAN_GFC = 0x02 << MCAN_GFC_ANFS_Pos; + + /* Leave the initialisation mode for the filter */ + barrier(); + CANx->MCAN_CCCR &= ~MCAN_CCCR_CCE; + CANx->MCAN_CCCR &= ~MCAN_CCCR_INIT; +} + +// This function handles CAN global interrupts +void +CAN_IRQHandler(void) +{ + uint32_t ir = CANx->MCAN_IR; + + if (ir & MCAN_IE_RF0NE) { + CANx->MCAN_IR = MCAN_IE_RF0NE; + + uint32_t rxf0s = CANx->MCAN_RXF0S; + if (rxf0s & MCAN_RXF0S_F0FL_Msk) { + // Read and ack data packet + uint32_t idx = (rxf0s & MCAN_RXF0S_F0GI_Msk) >> MCAN_RXF0S_F0GI_Pos; + struct fdcan_fifo *rxf0 = &MSG_RAM.RXF0[idx]; + uint32_t ids = rxf0->id_section; + struct canbus_msg msg; + if (ids & FDCAN_XTD) + msg.id = (ids & 0x1fffffff) | CANMSG_ID_EFF; + else + msg.id = (ids >> 18) & 0x7ff; + msg.id |= ids & FDCAN_RTR ? CANMSG_ID_RTR : 0; + msg.dlc = (rxf0->dlc_section >> 16) & 0x0f; + msg.data32[0] = rxf0->data[0]; + msg.data32[1] = rxf0->data[1]; + barrier(); + CANx->MCAN_RXF0A = idx; + + // Process packet + canbus_process_data(&msg); + } + } + if (ir & FDCAN_IE_TC) { + // Tx + CANx->MCAN_IR = FDCAN_IE_TC; + canbus_notify_tx(); + } +} + +static inline const uint32_t +make_btr(uint32_t sjw, // Sync jump width, ... hmm + uint32_t time_seg1, // time segment before sample point, 1 .. 16 + uint32_t time_seg2, // time segment after sample point, 1 .. 8 + uint32_t brp) // Baud rate prescaler, 1 .. 1024 +{ + return (((uint32_t)(sjw-1)) << MCAN_NBTP_NSJW_Pos + | ((uint32_t)(time_seg1-1)) << MCAN_NBTP_NTSEG1_Pos + | ((uint32_t)(time_seg2-1)) << MCAN_NBTP_NTSEG2_Pos + | ((uint32_t)(brp - 1)) << MCAN_NBTP_NBRP_Pos); +} + +static inline const uint32_t +compute_btr(uint32_t pclock, uint32_t bitrate) +{ + /* + Some equations: + Tpclock = 1 / pclock + Tq = brp * Tpclock + Tbs1 = Tq * TS1 + Tbs2 = Tq * TS2 + NominalBitTime = Tq + Tbs1 + Tbs2 + BaudRate = 1/NominalBitTime + Bit value sample point is after Tq+Tbs1. Ideal sample point + is at 87.5% of NominalBitTime + Use the lowest brp where ts1 and ts2 are in valid range + */ + + uint32_t bit_clocks = pclock / bitrate; // clock ticks per bit + + uint32_t sjw = 2; + uint32_t qs; + // Find number of time quantas that gives us the exact wanted bit time + for (qs = 18; qs > 9; qs--) { + // check that bit_clocks / quantas is an integer + uint32_t brp_rem = bit_clocks % qs; + if (brp_rem == 0) + break; + } + uint32_t brp = bit_clocks / qs; + uint32_t time_seg2 = qs / 8; // sample at ~87.5% + uint32_t time_seg1 = qs - (1 + time_seg2); + + return make_btr(sjw, time_seg1, time_seg2, brp); +} + +void +can_init(void) +{ + if (!CONFIG_USBCANBUS) { + // Configure UPLL for USB and CAN + PMC->CKGR_UCKR = CKGR_UCKR_UPLLCOUNT(3) | CKGR_UCKR_UPLLEN; + while (!(PMC->PMC_SR & PMC_SR_LOCKU)) + ; + } + + // Configure PCK5 for CAN use + PMC->PMC_PCK[5] = PMC_PCK_CSS_UPLL_CLK | PMC_PCK_PRES(5); + while (!(PMC->PMC_SR & PMC_SR_PCKRDY5)) + ; + PMC->PMC_SCER |= PMC_SCER_PCK5; + + enable_pclock(CANx_GCLK_ID); + + gpio_peripheral(GPIO_Rx, CAN_FUNCTION_Rx, 1); + gpio_peripheral(GPIO_Tx, CAN_FUNCTION_Tx, 0); + + uint32_t pclock = get_pclock_frequency(CANx_GCLK_ID); + + uint32_t btr = compute_btr(pclock, CONFIG_CANBUS_FREQUENCY); + + /*##-1- Configure the CAN #######################################*/ + + /* Exit from sleep mode */ + CANx->MCAN_CCCR &= ~MCAN_CCCR_CSR; + /* Wait the acknowledge */ + while (CANx->MCAN_CCCR & MCAN_CCCR_CSA) + ; + /* Request initialization */ + CANx->MCAN_CCCR |= MCAN_CCCR_INIT; + /* Wait the acknowledge */ + while (!(CANx->MCAN_CCCR & MCAN_CCCR_INIT)) + ; + /* Enable configuration change */ + CANx->MCAN_CCCR |= MCAN_CCCR_CCE; + + /* Disable protocol exception handling */ + CANx->MCAN_CCCR |= MCAN_CCCR_PXHD; + + CANx->MCAN_NBTP = btr; + + /* Setup message RAM addresses */ + uint32_t ccfg = (CCFG_CANxDMABA & ~CCFG_CANxDMABA_Msk); + CCFG_CANxDMABA = (ccfg | (((uint32_t)&MSG_RAM) + & CCFG_CANxDMABA_Msk)); + uint32_t f0sa = (uint32_t)MSG_RAM.RXF0 + - (CCFG_CANxDMABA_Msk & CCFG_CANxDMABA); + CANx->MCAN_RXF0C = f0sa + | (ARRAY_SIZE(MSG_RAM.RXF0) << MCAN_RXF0C_F0S_Pos); + CANx->MCAN_RXESC = (7 << MCAN_RXESC_F1DS_Pos) + | (7 << MCAN_RXESC_F0DS_Pos); + uint32_t tbsa = (uint32_t)MSG_RAM.TXFIFO + - (CCFG_CANxDMABA_Msk & CCFG_CANxDMABA); + CANx->MCAN_TXBC = tbsa + | (ARRAY_SIZE(MSG_RAM.TXFIFO) << MCAN_TXBC_TFQS_Pos); + CANx->MCAN_TXESC = 7 << MCAN_TXESC_TBDS_Pos; + + /* Leave the initialisation mode */ + CANx->MCAN_CCCR &= ~MCAN_CCCR_CCE; + CANx->MCAN_CCCR &= ~MCAN_CCCR_INIT; + + /*##-2- Configure the CAN Filter #######################################*/ + canhw_set_filter(0); + + /*##-3- Configure Interrupts #################################*/ + armcm_enable_irq(CAN_IRQHandler, CANx_IRQn, 1); + CANx->MCAN_ILE = MCAN_ILE_EINT0; + CANx->MCAN_IE = MCAN_IE_RF0NE | FDCAN_IE_TC; +} +DECL_INIT(can_init); diff --git a/src/atsam/main.c b/src/atsam/main.c index 8c2e4318d..3728b7349 100644 --- a/src/atsam/main.c +++ b/src/atsam/main.c @@ -14,6 +14,8 @@ #define FREQ_PERIPH_DIV (CONFIG_MACH_SAME70 ? 2 : 1) #define FREQ_PERIPH (CONFIG_CLOCK_FREQ / FREQ_PERIPH_DIV) +#define FREQ_SAME70_CAN 80000000 + /**************************************************************** * watchdog handler ****************************************************************/ @@ -62,6 +64,10 @@ enable_pclock(uint32_t id) uint32_t get_pclock_frequency(uint32_t id) { +#if CONFIG_MACH_SAME70 + if (id == MCAN0_CLOCK_ID || id == MCAN1_CLOCK_ID) + return FREQ_SAME70_CAN; +#endif return FREQ_PERIPH; } diff --git a/src/atsam/same70_sysinit.c b/src/atsam/same70_sysinit.c index d4d98483f..4cb5f48cc 100644 --- a/src/atsam/same70_sysinit.c +++ b/src/atsam/same70_sysinit.c @@ -70,5 +70,8 @@ void SystemInit( void ) // Configure PCK6 for TC use PMC->PMC_PCK[6] = PMC_PCK_CSS_MCK | PMC_PCK_PRES(2); - PMC->PMC_SCER = PMC_SCER_PCK6; + while ( !(PMC->PMC_SR & PMC_SR_PCKRDY6) ) + { + } + PMC->PMC_SCER |= PMC_SCER_PCK6; } diff --git a/src/atsam/usb_cdc_ep.h b/src/atsam/usb_cdc_ep.h deleted file mode 100644 index bcf1d3e38..000000000 --- a/src/atsam/usb_cdc_ep.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef __SAM3_USB_CDC_EP_H -#define __SAM3_USB_CDC_EP_H - -enum { - USB_CDC_EP_ACM = 3, - USB_CDC_EP_BULK_OUT = 1, - USB_CDC_EP_BULK_IN = 2, -}; - -#endif // usb_cdc_ep.h diff --git a/src/generic/usb_canbus.c b/src/generic/usb_canbus.c index d776d45f2..9c2893bf2 100644 --- a/src/generic/usb_canbus.c +++ b/src/generic/usb_canbus.c @@ -104,20 +104,18 @@ struct gs_host_frame { static struct usbcan_data { struct task_wake wake; - // Canbus data from host - union { - struct gs_host_frame host_frame; - uint8_t rx_frame_pad[USB_CDC_EP_BULK_OUT_SIZE]; - }; - uint8_t host_status; - // Canbus data routed locally uint8_t notify_local, usb_send_busy; uint32_t assigned_id; + // Canbus data from host + uint8_t host_status; + uint32_t host_pull_pos, host_push_pos; + struct gs_host_frame host_frames[16]; + // Data from physical canbus interface - uint32_t pull_pos, push_pos; - struct canbus_msg queue[32]; + uint32_t canhw_pull_pos, canhw_push_pos; + struct canbus_msg canhw_queue[32]; } UsbCan; enum { @@ -139,16 +137,16 @@ void canbus_process_data(struct canbus_msg *msg) { // Add to admin command queue - uint32_t pushp = UsbCan.push_pos; - if (pushp - UsbCan.pull_pos >= ARRAY_SIZE(UsbCan.queue)) + uint32_t pushp = UsbCan.canhw_push_pos; + if (pushp - UsbCan.canhw_pull_pos >= ARRAY_SIZE(UsbCan.canhw_queue)) // No space - drop message return; if (UsbCan.assigned_id && (msg->id & ~1) == UsbCan.assigned_id) // Id reserved for local return; - uint32_t pos = pushp % ARRAY_SIZE(UsbCan.queue); - memcpy(&UsbCan.queue[pos], msg, sizeof(*msg)); - UsbCan.push_pos = pushp + 1; + uint32_t pos = pushp % ARRAY_SIZE(UsbCan.canhw_queue); + memcpy(&UsbCan.canhw_queue[pos], msg, sizeof(*msg)); + UsbCan.canhw_push_pos = pushp + 1; usb_notify_bulk_out(); } @@ -167,24 +165,43 @@ send_frame(struct canbus_msg *msg) // Send any pending hw frames to host static void -drain_hw_queue(void) +drain_canhw_queue(void) { - uint32_t pull_pos = UsbCan.pull_pos; + uint32_t pull_pos = UsbCan.canhw_pull_pos; for (;;) { - uint32_t push_pos = readl(&UsbCan.push_pos); + uint32_t push_pos = readl(&UsbCan.canhw_push_pos); if (push_pos == pull_pos) { // No more data to send UsbCan.usb_send_busy = 0; return; } - uint32_t pos = pull_pos % ARRAY_SIZE(UsbCan.queue); - int ret = send_frame(&UsbCan.queue[pos]); + uint32_t pos = pull_pos % ARRAY_SIZE(UsbCan.canhw_queue); + int ret = send_frame(&UsbCan.canhw_queue[pos]); if (ret < 0) { // USB is busy - retry later UsbCan.usb_send_busy = 1; return; } - UsbCan.pull_pos = pull_pos = pull_pos + 1; + UsbCan.canhw_pull_pos = pull_pos = pull_pos + 1; + } +} + +// Fill local queue with any USB messages sent from host +static void +fill_usb_host_queue(void) +{ + uint32_t pull_pos = UsbCan.host_pull_pos, push_pos = UsbCan.host_push_pos; + for (;;) { + if (push_pos - pull_pos >= ARRAY_SIZE(UsbCan.host_frames)) + // No more space in queue + break; + uint32_t pushp = push_pos % ARRAY_SIZE(UsbCan.host_frames); + struct gs_host_frame *gs = &UsbCan.host_frames[pushp]; + int ret = usb_read_bulk_out(gs, sizeof(*gs)); + if (ret <= 0) + // No more messages ready + break; + UsbCan.host_push_pos = push_pos = push_pos + 1; } } @@ -195,13 +212,19 @@ usbcan_task(void) return; // Send any pending hw frames to host - drain_hw_queue(); + drain_canhw_queue(); + + // Fill local queue with any USB messages arriving from host + fill_usb_host_queue(); + // Route messages received from host + uint32_t pull_pos = UsbCan.host_pull_pos, push_pos = UsbCan.host_push_pos; + uint32_t pullp = pull_pos % ARRAY_SIZE(UsbCan.host_frames); + struct gs_host_frame *gs = &UsbCan.host_frames[pullp]; for (;;) { // See if previous host frame needs to be transmitted uint_fast8_t host_status = UsbCan.host_status; if (host_status & (HS_TX_HW | HS_TX_LOCAL)) { - struct gs_host_frame *gs = &UsbCan.host_frame; struct canbus_msg msg; msg.id = gs->can_id; msg.dlc = gs->can_dlc; @@ -221,23 +244,22 @@ usbcan_task(void) // Send any previous echo frames if (host_status) { - if (UsbCan.usb_send_busy) + if (UsbCan.notify_local || UsbCan.usb_send_busy) // Don't send echo frame until other traffic is sent - return; - int ret = usb_send_bulk_in(&UsbCan.host_frame - , sizeof(UsbCan.host_frame)); + break; + int ret = usb_send_bulk_in(gs, sizeof(*gs)); if (ret < 0) return; UsbCan.host_status = 0; + UsbCan.host_pull_pos = pull_pos = pull_pos + 1; } - // Read next frame from host - int ret = usb_read_bulk_out(&UsbCan.host_frame - , USB_CDC_EP_BULK_OUT_SIZE); - if (ret <= 0) + // Process next frame from host + if (pull_pos == push_pos) // No frame available - no more work to be done break; - uint32_t id = UsbCan.host_frame.can_id; + gs = &UsbCan.host_frames[pull_pos % ARRAY_SIZE(UsbCan.host_frames)]; + uint32_t id = gs->can_id; UsbCan.host_status = HS_TX_ECHO | HS_TX_HW; if (id == CANBUS_ID_ADMIN) UsbCan.host_status = HS_TX_ECHO | HS_TX_HW | HS_TX_LOCAL; @@ -245,6 +267,7 @@ usbcan_task(void) UsbCan.host_status = HS_TX_ECHO | HS_TX_LOCAL; } + // Wake up local message response handling (if usb is not busy) if (UsbCan.notify_local && !UsbCan.usb_send_busy) canserial_notify_tx(); } @@ -258,6 +281,8 @@ canbus_send(struct canbus_msg *msg) int ret = send_frame(msg); if (ret < 0) goto retry_later; + if (UsbCan.notify_local && UsbCan.host_status) + canbus_notify_tx(); UsbCan.notify_local = 0; return msg->dlc; retry_later: diff --git a/src/hc32f460/Kconfig b/src/hc32f460/Kconfig index 9de4a7fc6..5dee95f9a 100644 --- a/src/hc32f460/Kconfig +++ b/src/hc32f460/Kconfig @@ -51,16 +51,19 @@ config FLASH_SIZE choice "Application Address" prompt "Application Address" - config HC32F460_FLASH_APPLICATION_ADDRESS_0x8000 - bool "0x8000" - config HC32F460_FLASH_APPLICATION_ADDRESS_0xC000 - bool "0xC000" + config HC32F460_FLASH_APPLICATION_ADDRESS_0x008000 + bool "0x008000" + config HC32F460_FLASH_APPLICATION_ADDRESS_0x00C000 + bool "0x00C000" + config HC32F460_FLASH_APPLICATION_ADDRESS_0x010000 + bool "0x010000" endchoice config FLASH_APPLICATION_ADDRESS hex - default 0x8000 if HC32F460_FLASH_APPLICATION_ADDRESS_0x8000 - default 0xC000 if HC32F460_FLASH_APPLICATION_ADDRESS_0xC000 + default 0x008000 if HC32F460_FLASH_APPLICATION_ADDRESS_0x008000 + default 0x00C000 if HC32F460_FLASH_APPLICATION_ADDRESS_0x00C000 + default 0x010000 if HC32F460_FLASH_APPLICATION_ADDRESS_0x010000 config FLASH_BOOT_ADDRESS hex diff --git a/src/rp2040/usbserial.c b/src/rp2040/usbserial.c index e63e590dc..61d04d189 100644 --- a/src/rp2040/usbserial.c +++ b/src/rp2040/usbserial.c @@ -1,6 +1,6 @@ // Hardware interface to USB on rp2040 // -// Copyright (C) 2021 Kevin O'Connor +// Copyright (C) 2021-2023 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -26,52 +26,46 @@ #define DPBUF_SIZE 64 +// Get the offset of a given endpoint's base buffer static uint32_t usb_buf_offset(uint32_t ep) { return 0x100 + ep * DPBUF_SIZE * 2; } -static int_fast8_t -usb_write_packet(uint32_t ep, const void *data, uint_fast8_t len) +// Obtain a pointer to an endpoint buffer +static void* +usb_buf_addr(uint32_t ep, int bufnum) { - // Check if there is room for this packet - uint32_t epb = usb_dpram->ep_buf_ctrl[ep].in; - if (epb & (USB_BUF_CTRL_AVAIL|USB_BUF_CTRL_FULL)) - return -1; - uint32_t pid = (epb ^ USB_BUF_CTRL_DATA1_PID) & USB_BUF_CTRL_DATA1_PID; - uint32_t new_epb = USB_BUF_CTRL_FULL | USB_BUF_CTRL_LAST | pid | len; - usb_dpram->ep_buf_ctrl[ep].in = new_epb; - // Copy the packet to the hw buffer - void *addr = (void*)usb_dpram + usb_buf_offset(ep); - barrier(); - memcpy(addr, data, len); - barrier(); - // Inform the USB hardware of the available packet - usb_dpram->ep_buf_ctrl[ep].in = new_epb | USB_BUF_CTRL_AVAIL; - return len; + return (void*)usb_dpram + usb_buf_offset(ep) + bufnum * DPBUF_SIZE; } -static int_fast8_t -usb_read_packet(uint32_t ep, void *data, uint_fast8_t max_len) +// Return a pointer to the ep_buf_ctrl register for an endpoint +static volatile uint16_t * +lookup_epbufctrl(uint32_t ep, int is_rx, int bufnum) +{ + volatile uint16_t *epbp; + if (is_rx) + epbp = (void*)&usb_dpram->ep_buf_ctrl[ep].out; + else + epbp = (void*)&usb_dpram->ep_buf_ctrl[ep].in; + return &epbp[bufnum]; +} + +// Determine the next transfer PID id from the last PID +static uint32_t +next_data_pid(uint32_t epb) +{ + return (epb ^ USB_BUF_CTRL_DATA1_PID) & USB_BUF_CTRL_DATA1_PID; +} + +// Extract the number of bytes in an rx buffer +static uint32_t +get_rx_count(uint32_t epb, uint32_t max_len) { - // Check if there is a packet ready - uint32_t epb = usb_dpram->ep_buf_ctrl[ep].out; - if ((epb & (USB_BUF_CTRL_AVAIL|USB_BUF_CTRL_FULL)) != USB_BUF_CTRL_FULL) - return -1; - // Copy the packet to the given buffer - uint32_t pid = (epb ^ USB_BUF_CTRL_DATA1_PID) & USB_BUF_CTRL_DATA1_PID; - uint32_t new_epb = USB_BUF_CTRL_LAST | pid | DPBUF_SIZE; - usb_dpram->ep_buf_ctrl[ep].out = new_epb; uint32_t c = epb & USB_BUF_CTRL_LEN_MASK; if (c > max_len) c = max_len; - void *addr = (void*)usb_dpram + usb_buf_offset(ep); - barrier(); - memcpy(data, addr, c); - barrier(); - // Notify the USB hardware that the space is now available - usb_dpram->ep_buf_ctrl[ep].out = new_epb | USB_BUF_CTRL_AVAIL; return c; } @@ -80,16 +74,57 @@ usb_read_packet(uint32_t ep, void *data, uint_fast8_t max_len) * Interface ****************************************************************/ +static uint32_t bulk_out_push_count; + int_fast8_t usb_read_bulk_out(void *data, uint_fast8_t max_len) { - return usb_read_packet(USB_CDC_EP_BULK_OUT, data, max_len); + // Check if there is a packet ready + uint32_t bopc = bulk_out_push_count, bufnum = bopc & 1; + uint32_t ep = USB_CDC_EP_BULK_OUT; + volatile uint16_t *epbp = lookup_epbufctrl(ep, 1, bufnum); + uint32_t epb = *epbp; + if ((epb & (USB_BUF_CTRL_AVAIL|USB_BUF_CTRL_FULL)) != USB_BUF_CTRL_FULL) + return -1; + // Determine the next packet header + bulk_out_push_count = bopc + 1; + uint32_t pid = bufnum ? USB_BUF_CTRL_DATA1_PID : 0; + uint32_t new_epb = USB_BUF_CTRL_LAST | pid | DPBUF_SIZE; + *epbp = new_epb; + barrier(); + // Copy the packet to the given buffer + uint32_t c = get_rx_count(epb, max_len); + memcpy(data, usb_buf_addr(ep, bufnum), c); + // Notify the USB hardware that the space is now available + barrier(); + *epbp = new_epb | USB_BUF_CTRL_AVAIL; + return c; } +static uint32_t bulk_in_pop_count; + int_fast8_t usb_send_bulk_in(void *data, uint_fast8_t len) { - return usb_write_packet(USB_CDC_EP_BULK_IN, data, len); + // Check if there is room for this packet + uint32_t bipc = bulk_in_pop_count, bufnum = bipc & 1; + uint32_t ep = USB_CDC_EP_BULK_IN; + volatile uint16_t *epbp = lookup_epbufctrl(ep, 0, bufnum); + uint32_t epb = *epbp; + if (epb & (USB_BUF_CTRL_AVAIL|USB_BUF_CTRL_FULL)) + return -1; + // Determine the next packet header + bulk_in_pop_count = bipc + 1; + uint32_t pid = bufnum ? USB_BUF_CTRL_DATA1_PID : 0; + uint32_t new_epb = USB_BUF_CTRL_FULL | USB_BUF_CTRL_LAST | pid | len; + *epbp = new_epb; + barrier(); + // Copy the packet to the hw buffer + memcpy(usb_buf_addr(ep, bufnum), data, len); + // Inform the USB hardware of the available packet + barrier(); + *epbp = new_epb | USB_BUF_CTRL_AVAIL; + return len; } int_fast8_t @@ -118,19 +153,51 @@ usb_read_ep0_setup(void *data, uint_fast8_t max_len) int_fast8_t usb_read_ep0(void *data, uint_fast8_t max_len) { + // Check if there is a packet ready + uint32_t ep = 0; if (usb_hw->intr & USB_INTR_SETUP_REQ_BITS) // Early end of transmission return -2; - return usb_read_packet(0, data, max_len); + volatile uint16_t *epbp = lookup_epbufctrl(ep, 1, 0); + uint32_t epb = *epbp; + if ((epb & (USB_BUF_CTRL_AVAIL|USB_BUF_CTRL_FULL)) != USB_BUF_CTRL_FULL) + return -1; + // Determine the next packet header + uint32_t new_epb = USB_BUF_CTRL_LAST | next_data_pid(epb) | DPBUF_SIZE; + *epbp = new_epb; + barrier(); + // Copy the packet to the given buffer + uint32_t c = get_rx_count(epb, max_len); + memcpy(data, usb_buf_addr(ep, 0), c); + // Notify the USB hardware that the space is now available + barrier(); + *epbp = new_epb | USB_BUF_CTRL_AVAIL; + return c; } int_fast8_t usb_send_ep0(const void *data, uint_fast8_t len) { + // Check if there is room for this packet + uint32_t ep = 0; if (usb_hw->intr & USB_INTR_SETUP_REQ_BITS || usb_hw->buf_status & 2) // Early end of transmission return -2; - return usb_write_packet(0, data, len); + volatile uint16_t *epbp = lookup_epbufctrl(ep, 0, 0); + uint32_t epb = *epbp; + if (epb & (USB_BUF_CTRL_AVAIL|USB_BUF_CTRL_FULL)) + return -1; + // Determine the next packet header + uint32_t pid = next_data_pid(epb); + uint32_t new_epb = USB_BUF_CTRL_FULL | USB_BUF_CTRL_LAST | pid | len; + *epbp = new_epb; + barrier(); + // Copy the packet to the hw buffer + memcpy(usb_buf_addr(ep, 0), data, len); + // Inform the USB hardware of the available packet + barrier(); + *epbp = new_epb | USB_BUF_CTRL_AVAIL; + return len; } void @@ -156,9 +223,13 @@ usb_set_address(uint_fast8_t addr) void usb_set_configure(void) { - usb_dpram->ep_buf_ctrl[USB_CDC_EP_BULK_IN].in = USB_BUF_CTRL_DATA1_PID; - usb_dpram->ep_buf_ctrl[USB_CDC_EP_BULK_OUT].out = ( - USB_BUF_CTRL_AVAIL | USB_BUF_CTRL_LAST | DPBUF_SIZE); + bulk_in_pop_count = 0; + usb_dpram->ep_buf_ctrl[USB_CDC_EP_BULK_IN].in = 0; + + bulk_out_push_count = 0; + uint32_t epb0 = USB_BUF_CTRL_AVAIL | USB_BUF_CTRL_LAST | DPBUF_SIZE; + uint32_t epb1 = epb0 | USB_BUF_CTRL_DATA1_PID; + usb_dpram->ep_buf_ctrl[USB_CDC_EP_BULK_OUT].out = epb0 | (epb1 << 16); } @@ -290,10 +361,12 @@ endpoint_setup(void) usb_dpram->ep_ctrl[USB_CDC_EP_ACM-1].in = ep_acm; // BULK uint32_t ep_out = (EP_CTRL_ENABLE_BITS | usb_buf_offset(USB_CDC_EP_BULK_OUT) + | EP_CTRL_DOUBLE_BUFFERED_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | (USB_ENDPOINT_XFER_BULK << EP_CTRL_BUFFER_TYPE_LSB)); usb_dpram->ep_ctrl[USB_CDC_EP_BULK_OUT-1].out = ep_out; uint32_t ep_in = (EP_CTRL_ENABLE_BITS | usb_buf_offset(USB_CDC_EP_BULK_IN) + | EP_CTRL_DOUBLE_BUFFERED_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | (USB_ENDPOINT_XFER_BULK << EP_CTRL_BUFFER_TYPE_LSB)); usb_dpram->ep_ctrl[USB_CDC_EP_BULK_IN-1].in = ep_in; diff --git a/src/sensor_lis2dw.c b/src/sensor_lis2dw.c new file mode 100644 index 000000000..52612623f --- /dev/null +++ b/src/sensor_lis2dw.c @@ -0,0 +1,221 @@ +// Support for gathering acceleration data from LIS2DW chip +// +// Copyright (C) 2023 Zhou.XianMing +// Copyright (C) 2020 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include // memcpy +#include "board/irq.h" // irq_disable +#include "board/misc.h" // timer_read_time +#include "basecmd.h" // oid_alloc +#include "command.h" // DECL_COMMAND +#include "sched.h" // DECL_TASK +#include "spicmds.h" // spidev_transfer + +#define LIS_AR_DATAX0 0x28 +#define LIS_AM_READ 0x80 +#define LIS_FIFO_CTRL 0x2E +#define LIS_FIFO_SAMPLES 0x2F + +struct lis2dw { + struct timer timer; + uint32_t rest_ticks; + struct spidev_s *spi; + uint16_t sequence, limit_count; + uint8_t flags, data_count, fifo_disable; + uint8_t data[48]; +}; + +enum { + LIS_HAVE_START = 1<<0, LIS_RUNNING = 1<<1, LIS_PENDING = 1<<2, +}; + +static struct task_wake lis2dw_wake; + +// Event handler that wakes lis2dw_task() periodically +static uint_fast8_t +lis2dw_event(struct timer *timer) +{ + struct lis2dw *ax = container_of(timer, struct lis2dw, timer); + ax->flags |= LIS_PENDING; + sched_wake_task(&lis2dw_wake); + return SF_DONE; +} + +void +command_config_lis2dw(uint32_t *args) +{ + struct lis2dw *ax = oid_alloc(args[0], command_config_lis2dw + , sizeof(*ax)); + ax->timer.func = lis2dw_event; + ax->spi = spidev_oid_lookup(args[1]); +} +DECL_COMMAND(command_config_lis2dw, "config_lis2dw oid=%c spi_oid=%c"); + +// Report local measurement buffer +static void +lis2dw_report(struct lis2dw *ax, uint8_t oid) +{ + sendf("lis2dw_data oid=%c sequence=%hu data=%*s" + , oid, ax->sequence, ax->data_count, ax->data); + ax->data_count = 0; + ax->sequence++; +} + +// Report buffer and fifo status +static void +lis2dw_status(struct lis2dw *ax, uint_fast8_t oid + , uint32_t time1, uint32_t time2, uint_fast8_t fifo) +{ + sendf("lis2dw_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" + " buffered=%c fifo=%c limit_count=%hu" + , oid, time1, time2-time1, ax->sequence + , ax->data_count, fifo, ax->limit_count); +} + +// Helper code to reschedule the lis2dw_event() timer +static void +lis2dw_reschedule_timer(struct lis2dw *ax) +{ + irq_disable(); + ax->timer.waketime = timer_read_time() + ax->rest_ticks; + sched_add_timer(&ax->timer); + irq_enable(); +} + +// Query accelerometer data +static void +lis2dw_query(struct lis2dw *ax, uint8_t oid) +{ + uint8_t msg[7] = {0}; + uint8_t fifo[2] = {LIS_FIFO_SAMPLES| LIS_AM_READ , 0}; + uint8_t fifo_empty,fifo_ovrn = 0; + + msg[0] = LIS_AR_DATAX0 | LIS_AM_READ ; + uint8_t *d = &ax->data[ax->data_count]; + + spidev_transfer(ax->spi, 1, sizeof(msg), msg); + + spidev_transfer(ax->spi, 1, sizeof(fifo), fifo); + fifo_empty = fifo[1]&0x3F; + fifo_ovrn = fifo[1]&0x40; + + d[0] = msg[1]; // x low bits + d[1] = msg[2]; // x high bits + d[2] = msg[3]; // y low bits + d[3] = msg[4]; // y high bits + d[4] = msg[5]; // z low bits + d[5] = msg[6]; // z high bits + + ax->data_count += 6; + if (ax->data_count + 6 > ARRAY_SIZE(ax->data)) + lis2dw_report(ax, oid); + + // Check fifo status + if (fifo_ovrn) + ax->limit_count++; + + // check if we need to run the task again (more packets in fifo?) + if (!fifo_empty&&!(ax->fifo_disable)) { + // More data in fifo - wake this task again + sched_wake_task(&lis2dw_wake); + } else if (ax->flags & LIS_RUNNING) { + // Sleep until next check time + sched_del_timer(&ax->timer); + ax->flags &= ~LIS_PENDING; + lis2dw_reschedule_timer(ax); + } +} + +// Startup measurements +static void +lis2dw_start(struct lis2dw *ax, uint8_t oid) +{ + sched_del_timer(&ax->timer); + ax->flags = LIS_RUNNING; + ax->fifo_disable = 0; + uint8_t ctrl[2] = {LIS_FIFO_CTRL , 0xC0}; + spidev_transfer(ax->spi, 0, sizeof(ctrl), ctrl); + lis2dw_reschedule_timer(ax); +} + +// End measurements +static void +lis2dw_stop(struct lis2dw *ax, uint8_t oid) +{ + // Disable measurements + sched_del_timer(&ax->timer); + ax->flags = 0; + // Drain any measurements still in fifo + ax->fifo_disable = 1; + lis2dw_query(ax, oid); + + uint8_t ctrl[2] = {LIS_FIFO_CTRL , 0}; + uint32_t end1_time = timer_read_time(); + spidev_transfer(ax->spi, 0, sizeof(ctrl), ctrl); + uint32_t end2_time = timer_read_time(); + + uint8_t msg[2] = { LIS_FIFO_SAMPLES | LIS_AM_READ , 0}; + spidev_transfer(ax->spi, 1, sizeof(msg), msg); + uint8_t fifo_status = msg[1]&0x1f; + + //Report final data + if (ax->data_count) + lis2dw_report(ax, oid); + lis2dw_status(ax, oid, end1_time, end2_time, fifo_status); +} + +void +command_query_lis2dw(uint32_t *args) +{ + struct lis2dw *ax = oid_lookup(args[0], command_config_lis2dw); + + if (!args[2]) { + // End measurements + lis2dw_stop(ax, args[0]); + return; + } + // Start new measurements query + sched_del_timer(&ax->timer); + ax->timer.waketime = args[1]; + ax->rest_ticks = args[2]; + ax->flags = LIS_HAVE_START; + ax->sequence = ax->limit_count = 0; + ax->data_count = 0; + ax->fifo_disable = 0; + sched_add_timer(&ax->timer); +} +DECL_COMMAND(command_query_lis2dw, + "query_lis2dw oid=%c clock=%u rest_ticks=%u"); + +void +command_query_lis2dw_status(uint32_t *args) +{ + struct lis2dw *ax = oid_lookup(args[0], command_config_lis2dw); + uint8_t msg[2] = { LIS_FIFO_SAMPLES | LIS_AM_READ, 0x00 }; + uint32_t time1 = timer_read_time(); + spidev_transfer(ax->spi, 1, sizeof(msg), msg); + uint32_t time2 = timer_read_time(); + lis2dw_status(ax, args[0], time1, time2, msg[1]&0x1f); +} +DECL_COMMAND(command_query_lis2dw_status, "query_lis2dw_status oid=%c"); + +void +lis2dw_task(void) +{ + if (!sched_check_wake(&lis2dw_wake)) + return; + uint8_t oid; + struct lis2dw *ax; + foreach_oid(oid, ax, command_config_lis2dw) { + uint_fast8_t flags = ax->flags; + if (!(flags & LIS_PENDING)) + continue; + if (flags & LIS_HAVE_START) + lis2dw_start(ax, oid); + else + lis2dw_query(ax, oid); + } +} +DECL_TASK(lis2dw_task); diff --git a/src/stm32/usbfs.c b/src/stm32/usbfs.c index fda2ce9d2..ad2e7b3eb 100644 --- a/src/stm32/usbfs.c +++ b/src/stm32/usbfs.c @@ -1,6 +1,6 @@ // Hardware interface to "fullspeed USB controller" // -// Copyright (C) 2018-2021 Kevin O'Connor +// Copyright (C) 2018-2023 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -48,6 +48,12 @@ #define USB_CNTR_FRES USB_CNTR_USBRST #endif +// Some chip variants do not define these fields +#ifndef USB_EP_DTOG_TX_Pos +#define USB_EP_DTOG_TX_Pos 6 +#define USB_EP_DTOG_RX_Pos 14 +#endif + /**************************************************************** * USB transfer memory @@ -55,52 +61,50 @@ // Layout of the USB transfer memory #define EPM ((epmword_t*)USB_PMAADDR) -#define EPM_EP_DESC(ep) (&EPM[(ep) * (8 / WSIZE)]) +#define EPM_EP_DESC(ep, bufnum) (&EPM[((ep)*2 + (bufnum)) * (4 / WSIZE)]) #define EPM_BUF_OFFSET 0x10 #define EPM_EP_BUF_SIZE (64 / WSIZE + 1) -#define EPM_EP_TX_BUF(ep) (&EPM[EPM_BUF_OFFSET + (ep)*2*EPM_EP_BUF_SIZE]) -#define EPM_EP_RX_BUF(ep) (&EPM[EPM_BUF_OFFSET + (1+(ep)*2)*EPM_EP_BUF_SIZE]) +#define EPM_EP_BUF(ep, bufnum) \ + (&EPM[EPM_BUF_OFFSET + ((ep)*2 + (bufnum)) * EPM_EP_BUF_SIZE]) +#define BUFTX 0 +#define BUFRX 1 // Configure the usb descriptor for an endpoint static void -epm_ep_desc_setup(int ep, int rx_size) +epm_ep_desc_setup(int ep, int bufnum, int rx_size) { - uint32_t addr_tx = (EPM_EP_TX_BUF(ep) - EPM) * WSIZE, count_tx = 0; - uint32_t addr_rx = (EPM_EP_RX_BUF(ep) - EPM) * WSIZE; + uint32_t addr = (EPM_EP_BUF(ep, bufnum) - EPM) * WSIZE; uint32_t count_rx = (rx_size <= 30 ? DIV_ROUND_UP(rx_size, 2) << 10 : ((DIV_ROUND_UP(rx_size, 32) - 1) << 10) | 0x8000); - epmword_t *desc = EPM_EP_DESC(ep); + epmword_t *desc = EPM_EP_DESC(ep, bufnum); if (WSIZE == 2) { - desc[0] = addr_tx; - desc[1] = count_tx; - desc[2] = addr_rx; - desc[3] = count_rx; + desc[0] = addr; + desc[1] = count_rx; } else { - desc[0] = addr_tx | (count_tx << 16); - desc[1] = addr_rx | (count_rx << 16); + *desc = addr | (count_rx << 16); } } // Return number of read bytes on an rx endpoint static uint32_t -epm_get_ep_count_rx(int ep) +epm_get_ep_count_rx(int ep, int bufnum) { - epmword_t *desc = EPM_EP_DESC(ep); + epmword_t *desc = EPM_EP_DESC(ep, bufnum); if (WSIZE == 2) - return desc[3] & 0x3ff; - return (desc[1] >> 16) & 0x3ff; + return desc[1] & 0x3ff; + return (*desc >> 16) & 0x3ff; } // Set number of bytes ready to be transmitted on a tx endpoint static void -epm_set_ep_count_tx(int ep, uint32_t count) +epm_set_ep_count_tx(int ep, int bufnum, uint32_t count) { - epmword_t *desc = EPM_EP_DESC(ep); + epmword_t *desc = EPM_EP_DESC(ep, bufnum); if (WSIZE == 2) { desc[1] = count; } else { - uint32_t addr_tx = (EPM_EP_TX_BUF(ep) - EPM) * WSIZE; - desc[0] = addr_tx | (count << 16); + uint32_t addr_tx = (EPM_EP_BUF(ep, bufnum) - EPM) * WSIZE; + *desc = addr_tx | (count << 16); } } @@ -108,18 +112,22 @@ epm_set_ep_count_tx(int ep, uint32_t count) static void btable_configure(void) { - epm_ep_desc_setup(0, USB_CDC_EP0_SIZE); - epm_ep_desc_setup(USB_CDC_EP_ACM, 0); - epm_ep_desc_setup(USB_CDC_EP_BULK_OUT, USB_CDC_EP_BULK_OUT_SIZE); - epm_ep_desc_setup(USB_CDC_EP_BULK_IN, 0); + epm_ep_desc_setup(0, BUFTX, 0); + epm_ep_desc_setup(0, BUFRX, USB_CDC_EP0_SIZE); + epm_ep_desc_setup(USB_CDC_EP_ACM, BUFTX, 0); + epm_ep_desc_setup(USB_CDC_EP_ACM, BUFRX, 0); + epm_ep_desc_setup(USB_CDC_EP_BULK_OUT, 0, USB_CDC_EP_BULK_OUT_SIZE); + epm_ep_desc_setup(USB_CDC_EP_BULK_OUT, 1, USB_CDC_EP_BULK_OUT_SIZE); + epm_ep_desc_setup(USB_CDC_EP_BULK_IN, 0, 0); + epm_ep_desc_setup(USB_CDC_EP_BULK_IN, 1, 0); } // Read a packet stored in dedicated usb memory static uint32_t -btable_read_packet(int ep, uint8_t *dest, int max_len) +btable_read_packet(int ep, int bufnum, uint8_t *dest, int max_len) { - epmword_t *src = EPM_EP_RX_BUF(ep); - uint32_t count = epm_get_ep_count_rx(ep); + epmword_t *src = EPM_EP_BUF(ep, bufnum); + uint32_t count = epm_get_ep_count_rx(ep, bufnum); if (count > max_len) count = max_len; int i; @@ -145,9 +153,9 @@ btable_read_packet(int ep, uint8_t *dest, int max_len) // Write a packet to dedicated usb memory static void -btable_write_packet(int ep, const uint8_t *src, int count) +btable_write_packet(int ep, int bufnum, const uint8_t *src, int count) { - epmword_t *dest = EPM_EP_TX_BUF(ep); + epmword_t *dest = EPM_EP_BUF(ep, bufnum); int i; for (i=0; i> (USB_EP_DTOG_RX_Pos - USB_EP_DTOG_TX_Pos)) ^ epr) + & USB_EP_DTOG_TX); } @@ -202,39 +211,77 @@ set_stat_rxtx_bits(uint32_t epr, uint32_t bits) * USB interface ****************************************************************/ +static uint32_t bulk_out_pop_count, bulk_out_push_flag; + int_fast8_t usb_read_bulk_out(void *data, uint_fast8_t max_len) { - uint32_t epr = USB_EPR[USB_CDC_EP_BULK_OUT]; - if ((epr & USB_EPRX_STAT) == USB_EP_RX_VALID) + if (readl(&bulk_out_push_flag)) // No data ready return -1; - uint32_t count = btable_read_packet(USB_CDC_EP_BULK_OUT, data, max_len); - USB_EPR[USB_CDC_EP_BULK_OUT] = set_stat_rx_bits(epr, USB_EP_RX_VALID); + uint32_t ep = USB_CDC_EP_BULK_OUT; + int bufnum = bulk_out_pop_count & 1; + bulk_out_pop_count++; + uint32_t count = btable_read_packet(ep, bufnum, data, max_len); + writel(&bulk_out_push_flag, USB_EP_DTOG_TX); + + // Check if irq handler pulled another packet before push flag update + uint32_t epr = USB_EPR[ep]; + if (epr_is_dbuf_blocking(epr) && readl(&bulk_out_push_flag)) { + // Second packet was already read - must notify hardware + writel(&bulk_out_push_flag, 0); + USB_EPR[ep] = calc_epr_bits(epr, 0, 0) | USB_EP_DTOG_TX; + } + return count; } +static uint32_t bulk_in_push_pos, bulk_in_pop_flag; +#define BI_START 2 + int_fast8_t usb_send_bulk_in(void *data, uint_fast8_t len) { - uint32_t epr = USB_EPR[USB_CDC_EP_BULK_IN]; - if ((epr & USB_EPTX_STAT) != USB_EP_TX_NAK) + if (readl(&bulk_in_pop_flag)) // No buffer space available return -1; - btable_write_packet(USB_CDC_EP_BULK_IN, data, len); - USB_EPR[USB_CDC_EP_BULK_IN] = set_stat_tx_bits(epr, USB_EP_TX_VALID); + uint32_t ep = USB_CDC_EP_BULK_IN; + uint32_t bipp = bulk_in_push_pos, bufnum = bipp & 1; + bulk_in_push_pos = bipp ^ 1; + btable_write_packet(ep, bufnum, data, len); + writel(&bulk_in_pop_flag, USB_EP_DTOG_RX); + + // Check if hardware needs to be notified + uint32_t epr = USB_EPR[ep]; + if (epr_is_dbuf_blocking(epr) && readl(&bulk_in_pop_flag)) { + writel(&bulk_in_pop_flag, 0); + if (unlikely(bipp & BI_START)) { + // Two packets are always sent when starting in double + // buffering mode, so wait for second packet before starting. + if (bipp == (BI_START | 1)) { + bulk_in_push_pos = 0; + writel(&bulk_in_pop_flag, USB_EP_KIND); // Dummy flag + USB_EPR[ep] = calc_epr_bits(epr, USB_EPTX_STAT + , USB_EP_TX_VALID); + } + } else { + USB_EPR[ep] = calc_epr_bits(epr, 0, 0) | USB_EP_DTOG_RX; + } + } + return len; } int_fast8_t usb_read_ep0(void *data, uint_fast8_t max_len) { - uint32_t epr = USB_EPR[0]; + uint32_t ep = 0, epr = USB_EPR[ep]; if ((epr & USB_EPRX_STAT) != USB_EP_RX_NAK) // No data ready return -1; - uint32_t count = btable_read_packet(0, data, max_len); - USB_EPR[0] = set_stat_rxtx_bits(epr, USB_EP_RX_VALID | USB_EP_TX_NAK); + uint32_t count = btable_read_packet(ep, BUFRX, data, max_len); + USB_EPR[ep] = calc_epr_bits(epr, USB_EPRX_STAT | USB_EPTX_STAT + , USB_EP_RX_VALID | USB_EP_TX_NAK); return count; } @@ -247,23 +294,24 @@ usb_read_ep0_setup(void *data, uint_fast8_t max_len) int_fast8_t usb_send_ep0(const void *data, uint_fast8_t len) { - uint32_t epr = USB_EPR[0]; + uint32_t ep = 0, epr = USB_EPR[ep]; if ((epr & USB_EPRX_STAT) != USB_EP_RX_VALID) // Transfer interrupted return -2; if ((epr & USB_EPTX_STAT) != USB_EP_TX_NAK) // No buffer space available return -1; - btable_write_packet(0, data, len); - USB_EPR[0] = set_stat_tx_bits(epr, USB_EP_TX_VALID); + btable_write_packet(ep, BUFTX, data, len); + USB_EPR[ep] = calc_epr_bits(epr, USB_EPTX_STAT, USB_EP_TX_VALID); return len; } void usb_stall_ep0(void) { - USB_EPR[0] = set_stat_rxtx_bits(USB_EPR[0] - , USB_EP_RX_STALL | USB_EP_TX_STALL); + uint32_t ep = 0, epr = USB_EPR[ep]; + USB_EPR[ep] = calc_epr_bits(epr, USB_EPRX_STAT | USB_EPTX_STAT + , USB_EP_RX_STALL | USB_EP_TX_STALL); } static uint8_t set_address; @@ -278,6 +326,13 @@ usb_set_address(uint_fast8_t addr) void usb_set_configure(void) { + uint32_t ep = USB_CDC_EP_BULK_OUT; + bulk_out_pop_count = 0; + USB_EPR[ep] = calc_epr_bits(USB_EPR[ep], USB_EPRX_STAT, USB_EP_RX_VALID); + + ep = USB_CDC_EP_BULK_IN; + bulk_in_push_pos = BI_START; + writel(&bulk_in_pop_flag, 0); } @@ -289,13 +344,22 @@ usb_set_configure(void) static void usb_reset(void) { - USB_EPR[0] = 0 | USB_EP_CONTROL | USB_EP_RX_VALID | USB_EP_TX_NAK; - USB_EPR[USB_CDC_EP_ACM] = (USB_CDC_EP_ACM | USB_EP_INTERRUPT - | USB_EP_RX_NAK | USB_EP_TX_NAK); - USB_EPR[USB_CDC_EP_BULK_OUT] = (USB_CDC_EP_BULK_OUT | USB_EP_BULK - | USB_EP_RX_VALID | USB_EP_TX_NAK); - USB_EPR[USB_CDC_EP_BULK_IN] = (USB_CDC_EP_BULK_IN | USB_EP_BULK - | USB_EP_RX_NAK | USB_EP_TX_NAK); + uint32_t ep = 0; + USB_EPR[ep] = 0 | USB_EP_CONTROL | USB_EP_RX_VALID | USB_EP_TX_NAK; + + ep = USB_CDC_EP_ACM; + USB_EPR[ep] = (USB_CDC_EP_ACM | USB_EP_INTERRUPT + | USB_EP_RX_NAK | USB_EP_TX_NAK); + + ep = USB_CDC_EP_BULK_OUT; + USB_EPR[ep] = (USB_CDC_EP_BULK_OUT | USB_EP_BULK | USB_EP_KIND + | USB_EP_RX_NAK | USB_EP_DTOG_TX); + bulk_out_push_flag = USB_EP_DTOG_TX; + + ep = USB_CDC_EP_BULK_IN; + USB_EPR[ep] = (USB_CDC_EP_BULK_IN | USB_EP_BULK | USB_EP_KIND + | USB_EP_TX_NAK); + bulk_in_pop_flag = USB_EP_DTOG_RX; USB->CNTR = USB_CNTR_CTRM | USB_CNTR_RESETM; USB->DADDR = USB_DADDR_EF; @@ -308,20 +372,25 @@ USB_IRQHandler(void) uint32_t istr = USB->ISTR; if (istr & USB_ISTR_CTR) { // Endpoint activity - uint32_t ep = istr & USB_ISTR_EP_ID; - uint32_t epr = USB_EPR[ep]; - USB_EPR[ep] = epr & EPR_RWBITS; - if (ep == 0) { + uint32_t ep = istr & USB_ISTR_EP_ID, epr = USB_EPR[ep]; + if (ep == USB_CDC_EP_BULK_OUT) { + USB_EPR[ep] = (calc_epr_bits(epr, USB_EP_CTR_RX | USB_EP_CTR_TX, 0) + | bulk_out_push_flag); + bulk_out_push_flag = 0; + usb_notify_bulk_out(); + } else if (ep == USB_CDC_EP_BULK_IN) { + USB_EPR[ep] = (calc_epr_bits(epr, USB_EP_CTR_RX | USB_EP_CTR_TX, 0) + | bulk_in_pop_flag); + bulk_in_pop_flag = 0; + usb_notify_bulk_in(); + } else if (ep == 0) { + USB_EPR[ep] = calc_epr_bits(epr, USB_EP_CTR_RX | USB_EP_CTR_TX, 0); usb_notify_ep0(); if (epr & USB_EP_CTR_TX && set_address) { // Apply address after last "in" message transmitted USB->DADDR = set_address; set_address = 0; } - } else if (ep == USB_CDC_EP_BULK_OUT) { - usb_notify_bulk_out(); - } else if (ep == USB_CDC_EP_BULK_IN) { - usb_notify_bulk_in(); } } if (istr & USB_ISTR_RESET) { diff --git a/test/configs/ar100.config b/test/configs/ar100.config index a717fe3d0..6c9174824 100644 --- a/test/configs/ar100.config +++ b/test/configs/ar100.config @@ -3,3 +3,4 @@ CONFIG_MACH_AR100=y CONFIG_WANT_DISPLAYS=n CONFIG_WANT_SOFTWARE_I2C=n CONFIG_WANT_SOFTWARE_SPI=n +CONFIG_WANT_LIS2DW=n diff --git a/test/configs/hc32f460-serial-PA7PA8.config b/test/configs/hc32f460-serial-PA7PA8.config new file mode 100644 index 000000000..7fdbc17ba --- /dev/null +++ b/test/configs/hc32f460-serial-PA7PA8.config @@ -0,0 +1,3 @@ +# Base config file for boards using HC32F460 +CONFIG_MACH_HC32F460=y +CONFIG_HC32F460_SERIAL_PA7_PA8=y diff --git a/test/configs/stm32f042.config b/test/configs/stm32f042.config index 85338f2e7..216508b65 100644 --- a/test/configs/stm32f042.config +++ b/test/configs/stm32f042.config @@ -2,3 +2,5 @@ CONFIG_MACH_STM32=y CONFIG_MACH_STM32F042=y CONFIG_WANT_SOFTWARE_I2C=n +CONFIG_WANT_LIS2DW=n +CONFIG_WANT_DISPLAYS=n diff --git a/test/klippy/printers.test b/test/klippy/printers.test index fd1ba9099..8d891fb6c 100644 --- a/test/klippy/printers.test +++ b/test/klippy/printers.test @@ -84,7 +84,7 @@ DICTIONARY stm32g0b1.dict # Printers using the rp2040 DICTIONARY rp2040.dict -# Anycubic Printers using trigorilla board with the hc32f460 +# Printers using the hc32f460 with serial on PA3 PA2 DICTIONARY hc32f460-serial-PA3PA2.dict # Tests with multiple mcus