diff --git a/.github/workflows/klipper3d-deploy.yaml b/.github/workflows/klipper3d-deploy.yaml index 609644bbc..076be8dac 100644 --- a/.github/workflows/klipper3d-deploy.yaml +++ b/.github/workflows/klipper3d-deploy.yaml @@ -26,7 +26,7 @@ jobs: - name: Install dependencies run: pip install -r docs/_klipper3d/mkdocs-requirements.txt - name: Build MkDocs Pages - run: docs/_klipper3d/build-translations.sh + run: docs/_klipper3d/build-website.sh - name: Deploy uses: JamesIves/github-pages-deploy-action@v4.4.3 with: diff --git a/config/printer-creality-ender5pro-2020.cfg b/config/printer-creality-ender5pro-2020.cfg index ded26b408..2321cfb6d 100644 --- a/config/printer-creality-ender5pro-2020.cfg +++ b/config/printer-creality-ender5pro-2020.cfg @@ -1,7 +1,7 @@ # This file contains pin mappings for the stock 2020 Creality Ender 5 # Pro with the 32-bit Creality 4.2.2 board. To use this config, during # "make menuconfig" select the STM32F103 with a "28KiB bootloader" and -# with "Use USB for communication" disabled. +# communication interface set to "Serial (on USART1 PA10/PA9)". # If you prefer a direct serial connection, in "make menuconfig" # select "Enable extra low-level configuration options" and select the diff --git a/config/sample-cartographer-v3.cfg b/config/sample-cartographer-v3.cfg new file mode 100644 index 000000000..ee1a6fd41 --- /dev/null +++ b/config/sample-cartographer-v3.cfg @@ -0,0 +1,52 @@ +# This file contains common pin mappings for the Cartographer board V3 +# To use this config, the firmware should be compiled for the +# STM32F042 with "24 MHz crystal" and +# "USB (on PA9/PA10)" or "CAN bus (on PA9/PA10)". +# CAN bus requires PA1 GPIO pin to be set at micro-controller start-up +# The "carto" micro-controller will be used to control +# the components on the board. + +# See docs/Config_Reference.md for a description of parameters. + +[mcu carto] +serial: /dev/serial/by-id/usb-Klipper_stm32f042x6_29000380114330394D363620-if00 +#canbus_uuid: 92cf532ef122 + +#[adxl345 carto] +#cs_pin: carto:PA3 +#spi_bus: spi1_PA6_PA7_PA5 +#axes_map: x,y,z + +[thermistor 50k] +temperature1: 25 +resistance1: 50000 +temperature2: 50 +resistance2: 17940 +temperature3: 100 +resistance3: 3090 + +[temperature_probe carto] +pullup_resistor: 10000 +sensor_type: 50k +sensor_pin: carto:PA4 +min_temp: 0 +max_temp: 125 + +[led carto_led] +white_pin: carto:PB5 +initial_WHITE: 0.03 + +[output_pin _LDC1612_en] +pin: carto:PA15 +value: 0 # enable + +[static_pwm_clock ldc1612_clk_in] +pin: carto:PB4 +frequency: 24000000 + +[probe_eddy_current carto] +sensor_type: ldc1612 +frequency: 24000000 +i2c_address: 42 +i2c_mcu: carto +i2c_bus: i2c1_PB6_PB7 diff --git a/docs/Config_Changes.md b/docs/Config_Changes.md index 6846e034d..cf2d53fd9 100644 --- a/docs/Config_Changes.md +++ b/docs/Config_Changes.md @@ -8,9 +8,29 @@ All dates in this document are approximate. ## Changes -20251122: An option `axis` has been added to `[carriage ]` sections -for `generic_cartesian` kinematics, allowing arbitrary names for primary -carriages. Users are encouraged to explicitly specify `axis` option now. +20260109: The status value `{printer.probe.last_z_result}` is +deprecated; it will be removed in the near future. Use +`{printer.probe.last_probe_position}` instead, and note that this new +value already has the probe's configured xyz offsets applied. + +20260109: The g-code console text output from the `PROBE`, +`PROBE_ACCURACY`, and similar commands has changed. Now Z heights are +reported relative to the nominal bed Z position instead of relative to +the probe's configured `z_offset`. Similarly, intermediate probe x and +y console reports will also have the probe's configured `x_offset` and +`y_offset` applied. + +20260109: The `[screws_tilt_adjust]` module now reports the status +variable `{printer.screws_tilt_adjust.result.screw1.z}` with the +probe's `z_offset` applied. That is, one would previously need to +subtract the probe's configured `z_offset` to find the absolute Z +deviation at the given screw location and now one must not apply the +`z_offset`. + +20251122: An option `axis` has been added to `[carriage ]` +sections for `generic_cartesian` kinematics, allowing arbitrary names +for primary carriages. Users are encouraged to explicitly specify +`axis` option now. 20251106: The status fields `{printer.toolhead.position}`, `{printer.gcode_move.position}`, diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 705690642..60a7818f4 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -743,7 +743,7 @@ max_accel: ``` -Then a user must define three carriages for X, Y, and Z axes, e.g.: +Then a user must define three primary carriages for X, Y, and Z axes, e.g.: ``` [carriage carriage_x] axis: @@ -2293,6 +2293,9 @@ sensor_type: ldc1612 #samples_tolerance: #samples_tolerance_retries: # See the "probe" section for information on these parameters. +#tap_threshold: 0 +# Noise cutoff/stop trigger threshold delta Hz per sample +# See the Eddy_Probe.md for explanation ``` ### [axis_twist_compensation] @@ -2452,10 +2455,16 @@ Please note that in this case the `[dual_carriage]` configuration deviates from the configuration described above: ``` [dual_carriage my_dc_carriage] -primary_carriage: -# Defines the matching primary carriage of this dual carriage and -# the corresponding IDEX axis. Must match a name of a defined `[carriage]`. -# This parameter must be provided. +#primary_carriage: +# Defines the matching carriage on the same gantry as this dual carriage and +# the corresponding dual axis. Must match a name of a defined `[carriage]` or +# another independent `[dual_carriage]`. If not set, which is a default, +# defines a dual carriage independent of a `[carriage]` with the same axis +# as this one (e.g. on a different gantry). +#axis: +# Axis of a carriage, either x or y. If 'primary_carriage' is defined, then +# this parameter defaults to the 'axis' parameter of that primary carriage, +# otherwise this parameter must be defined. #safe_distance: # The minimum distance (in mm) to enforce between the dual and the primary # carriages. If a G-Code command is executed that will bring the carriages @@ -2464,7 +2473,8 @@ primary_carriage: # position_min and position_max for the dual and primary carriages. If set # to 0 (or safe_distance is unset and position_min and position_max are # identical for the primary and dual carriages), the carriages proximity -# checks will be disabled. +# checks will be disabled. Only valid for a dual_carriage with a defined +# 'primary_carriage'. endstop_pin: #position_min: position_endstop: @@ -3616,6 +3626,20 @@ pin: # These options are deprecated and should no longer be specified. ``` +### [static_pwm_clock] + +Static configurable output pin (one may define any number of +sections with an "static_pwm_clock" prefix). +Pins configured here will be set up as clock output pins. +Generally used to provide clock input to other hardware on the board. +``` +[static_pwm_clock my_pin] +pin: +# The pin to configure as an output. This parameter must be provided. +#frequency: 100 +# Target output frequency. +``` + ### [pwm_tool] Pulse width modulation digital output pins capable of high speed diff --git a/docs/Eddy_Probe.md b/docs/Eddy_Probe.md index 8d81bd88b..8b229545e 100644 --- a/docs/Eddy_Probe.md +++ b/docs/Eddy_Probe.md @@ -4,8 +4,10 @@ This document describes how to use an [eddy current](https://en.wikipedia.org/wiki/Eddy_current) inductive probe in Klipper. -Currently, an eddy current probe can not be used for Z homing. The -sensor can only be used for Z probing. +Currently, an eddy current probe can not precisely home Z (i.e., `G28 Z`). +The sensor can precisely do Z probing (i.e., `PROBE ...`). +Look at the [homing correction](Eddy_Probe.md#homing-correction-macros) +for further details. Start by declaring a [probe_eddy_current config section](Config_Reference.md#probe_eddy_current) @@ -24,6 +26,7 @@ named `[probe_eddy_current my_eddy_probe]` then one would run complete in a few seconds. After it completes, issue a `SAVE_CONFIG` command to save the results to the printer.cfg and restart. +Eddy current is used as a proximity/distance sensor (similar to a laser ruler). The second step in calibration is to correlate the sensor readings to the corresponding Z heights. Home the printer and navigate the toolhead so that the nozzle is near the center of the bed. Then run a @@ -35,7 +38,17 @@ those steps are complete one can `ACCEPT` the position. The tool will then move the toolhead so that the sensor is above the point where the nozzle used to be and run a series of movements to correlate the sensor to Z positions. This will take a couple of minutes. After the -tool completes, issue a `SAVE_CONFIG` command to save the results to +tool completes it will output the sensor performance data: +``` +probe_eddy_current: noise 0.000642mm, MAD_Hz=11.314 in 2525 queries +Total frequency range: 45000.012 Hz +z_offset: 0.250 # noise 0.000200mm, MAD_Hz=11.000 +z_offset: 0.530 # noise 0.000300mm, MAD_Hz=12.000 +z_offset: 1.010 # noise 0.000400mm, MAD_Hz=14.000 +z_offset: 2.010 # noise 0.000600mm, MAD_Hz=12.000 +z_offset: 3.010 # noise 0.000700mm, MAD_Hz=9.000 +``` +issue a `SAVE_CONFIG` command to save the results to the printer.cfg and restart. After initial calibration it is a good idea to verify that the @@ -55,6 +68,134 @@ surface temperature or sensor hardware temperature can skew the results. It is important that calibration and probing is only done when the printer is at a stable temperature. +## Homing correction macros + +Because of current limitations, homing and probing +are implemented differently for the eddy sensors. +As a result, homing suffers from an offset error, +while probing handles this correctly. + +To correct the homing offset. +One can use the suggested macro inside the homing override or +inside the starting G-Code. + +[Force move](Config_Reference.md#force_move) section +have to be defined in the config. + +``` +[gcode_macro _RELOAD_Z_OFFSET_FROM_PROBE] +gcode: + {% set Z = printer.toolhead.position.z %} + SET_KINEMATIC_POSITION Z={Z - printer.probe.last_probe_position.z} + +[gcode_macro SET_Z_FROM_PROBE] +gcode: + {% set METHOD = params.METHOD | default("automatic") %} + PROBE METHOD={METHOD} + _RELOAD_Z_OFFSET_FROM_PROBE + G0 Z5 +``` + +## Tap calibration + +The Eddy probe measures the resonance frequency of the coil. +By the absolute value of the frequency and the calibration curve from +`PROBE_EDDY_CURRENT_CALIBRATE`, it is therefore possible to detect +where the bed is without physical contact. + +By use of the same knowledge, we know that frequency changes with +the distance. It is possible to track that change in real time and +detect the time/position where contact happens - a change of frequency +starts to change in a different way. +For example, stopped to change because of the collision. + +Because eddy output is not perfect: there is sensor noise, +mechanical oscillation, thermal expansion and other discrepancies, +it is required to calibrate the stop threshold for your machine. +Practically, it ensures that the Eddy's output data absolute value +change per second (velocity) is high enough - higher than the noise level, +and that upon collision it always decreases by at least this value. + +``` +[probe_eddy_current my_probe] +# eddy probe configuration... +# Recommended starting values for the tap +#samples: 3 +#samples_tolerance: 0.025 +#samples_tolerance_retries: 3 +tap_threshold: 0 # 0 means tap is disabled +``` + +Before setting it to any other value, it is necessary to install `scipy`: + +```bash +~/klippy-env/bin/pip install scipy +``` + +The suggested calibration routine works as follows: +1. Home Z +2. Place the toolhead at the center of the bed. +3. Move the Z axis far away (30 mm, for example). +4. Run `PROBE METHOD=tap` +5. If it stops before colliding, increase the `tap_threshold`. + +Repeat until the nozzle softly touches the bed. +This is easier to do with a clean nozzle and +by visually inspecting the process. + +You can streamline the process by placing the toolhead in the center once. +Then, upon config restart, trick the machine into thinking that Z is homed. +``` +SET_KINEMATIC_POSITION X= Y= Z=0 +G0 Z5 # Optional retract +PROBE METHOD=tap +``` + +Here is an example sequence of threshold values to test: +``` +1 -> 5 -> 10 -> 20 -> 40 -> 80 -> 160 +160 -> 120 -> 100 +``` +Your value will normally be between those. +- Too high a value leaves a less safe margin for early collision - +if something is between the nozzle and the bed, or if the nozzle +is too close to the bed before the tap. +- Too low - can make the toolhead stop in mid-air +because of the noise. + +You can estimate the initial threshold value by analyzing your own +calibration routine output: +``` +probe_eddy_current: noise 0.000642mm, MAD_Hz=11.314 +... +z_offset: 1.010 # noise 0.000400mm, MAD_Hz=14.000 +``` +The estimation will be: +``` +MAD_Hz * 2 +11.314 * 2 = 22.628 +``` + +To further fine tune threshold, one can use `PROBE_ACCURACY METHOD=tap`. +The range is expected to be about 0.02 mm, +with the default probe speed of 5 mm/s. +Elevated coil temperature may increase noise and may require additional tuning. + +You can validate the tap precision by measuring the paper thickness +from the initial calibration guide. It is expected to be ~0.1mm. + +Tap precision is limited by the sampling frequency and +the speed of the descent. +If you take 24 photos per second of the moving train, you can only estimate +where the train was between photos. + +It is possible to reduce the descending speed. It may require decrease of +absolute `tap_threshold` value. + +It is possible to tap over non-conductive surfaces as long as there is metal +behind it within the sensor's sensitivity range. +Max distance can be approximated to be about 1.5x of the coil's narrowest part. + ## Thermal Drift Calibration As with all inductive probes, eddy current probes are subject to @@ -144,3 +285,38 @@ to perform thermal drift calibration: As one may conclude, the calibration process outlined above is more challenging and time consuming than most other procedures. It may require practice and several attempts to achieve an optimal calibration. + +## Errors description + +Possible homing errors and actionables: + +- Sensor error + - Check logs for detailed error +- Eddy I2C STATUS/DATA error. + - Check loose wiring. + - Try software I2C/decrease I2C rate +- Invalid read data + - Same as I2C + +Possible sensor errors and actionables: +- Frequency over valid hard range + - Check frequency configuration + - Hardware fault +- Frequency over valid soft range + - Check frequency configuration +- Conversion Watchdog timeout + - Hardware fault + +Amplitude Low/High warning messages can mean: +- Sensor close to the bed +- Sensor far from the bed +- Higher temperature than was at the current calibration +- Capacitor missing + +On some sensors, it is not possible to completely avoid amplitude +warning indicator. + +You can try to redo the `LDC_CALIBRATE_DRIVE_CURRENT` calibration at work +temperature or increase `reg_drive_current` by 1-2 from the calibrated value. + +Generally, it is like an engine check light. It may indicate an issue. diff --git a/docs/G-Codes.md b/docs/G-Codes.md index 893993e85..915537411 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -350,8 +350,9 @@ reference a defined primary or dual carriage for `generic_cartesian` kinematics or be 0 (for primary carriage) or 1 (for dual carriage) for all other kinematics supporting IDEX. Setting the mode to `PRIMARY` deactivates the other carriage and makes the specified carriage execute -subsequent G-Code commands as-is. `COPY` and `MIRROR` modes are supported -only for dual carriages. When set to either of these modes, dual carriage +subsequent G-Code commands as-is. Before activating `COPY` or `MIRROR` +mode for a carriage, a different one must be activated as `PRIMARY` on +the same axis. When set to either of these two modes, the carriage will then track the subsequent moves of its primary carriage and either copy relative movements of it (in `COPY` mode) or execute them in the opposite (mirror) direction (in `MIRROR` mode). @@ -1160,23 +1161,25 @@ The following commands are available when a see the [probe calibrate guide](Probe_Calibrate.md)). #### PROBE -`PROBE [PROBE_SPEED=] [LIFT_SPEED=] [SAMPLES=] -[SAMPLE_RETRACT_DIST=] [SAMPLES_TOLERANCE=] +`PROBE [METHOD=] [PROBE_SPEED=] [LIFT_SPEED=] +[SAMPLES=] [SAMPLE_RETRACT_DIST=] [SAMPLES_TOLERANCE=] [SAMPLES_TOLERANCE_RETRIES=] [SAMPLES_RESULT=median|average]`: Move the nozzle downwards until the probe triggers. If any of the optional parameters are provided they override their equivalent setting in the [probe config section](Config_Reference.md#probe). +The optional parameter `METHOD` is probe-specific. #### QUERY_PROBE `QUERY_PROBE`: Report the current status of the probe ("triggered" or "open"). #### PROBE_ACCURACY -`PROBE_ACCURACY [PROBE_SPEED=] [SAMPLES=] +`PROBE_ACCURACY [METHOD=] [PROBE_SPEED=] [SAMPLES=] [SAMPLE_RETRACT_DIST=]`: Calculate the maximum, minimum, average, median, and standard deviation of multiple probe samples. By default, 10 SAMPLES are taken. Otherwise the optional parameters default to their equivalent setting in the probe config section. +The optional parameter `METHOD` is probe-specific. #### PROBE_CALIBRATE `PROBE_CALIBRATE [SPEED=] [=]`: Run a @@ -1237,13 +1240,14 @@ The following commands are available when the is enabled. #### QUAD_GANTRY_LEVEL -`QUAD_GANTRY_LEVEL [RETRIES=] [RETRY_TOLERANCE=] +`QUAD_GANTRY_LEVEL [METHOD=] [RETRIES=] [RETRY_TOLERANCE=] [HORIZONTAL_MOVE_Z=] [=]`: This command will probe the points specified in the config and then make independent adjustments to each Z stepper to compensate for tilt. See the PROBE command for details on the optional probe parameters. The optional `RETRIES`, `RETRY_TOLERANCE`, and `HORIZONTAL_MOVE_Z` values override those options specified in the config file. +The optional parameter `METHOD` is probe-specific. ### [query_adc] @@ -1672,10 +1676,11 @@ The following commands are available when the [z_tilt config section](Config_Reference.md#z_tilt) is enabled. #### Z_TILT_ADJUST -`Z_TILT_ADJUST [RETRIES=] [RETRY_TOLERANCE=] +`Z_TILT_ADJUST [METHOD=] [RETRIES=] [RETRY_TOLERANCE=] [HORIZONTAL_MOVE_Z=] [=]`: This command will probe the points specified in the config and then make independent adjustments to each Z stepper to compensate for tilt. See the PROBE command for details on the optional probe parameters. The optional `RETRIES`, `RETRY_TOLERANCE`, and `HORIZONTAL_MOVE_Z` values override those options specified in the config file. +The optional parameter `METHOD` is probe-specific. diff --git a/docs/Load_Cell.md b/docs/Load_Cell.md index 8345cae49..c6cf9ce49 100644 --- a/docs/Load_Cell.md +++ b/docs/Load_Cell.md @@ -252,12 +252,12 @@ macro. This requires setting up Here is a simple macro that can accomplish this. Note that the `_HOME_Z_FROM_LAST_PROBE` macro has to be separate because of the way macros work. The sub-call is needed so that the `_HOME_Z_FROM_LAST_PROBE` macro can -see the result of the probe in `printer.probe.last_z_result`. +see the result of the probe in `printer.probe.last_probe_position`. ```gcode [gcode_macro _HOME_Z_FROM_LAST_PROBE] gcode: - {% set z_probed = printer.probe.last_z_result %} + {% set z_probed = printer.probe.last_probe_position.z %} {% set z_position = printer.toolhead.position[2] %} {% set z_actual = z_position - z_probed %} SET_KINEMATIC_POSITION Z={z_actual} diff --git a/docs/Status_Reference.md b/docs/Status_Reference.md index 4e4ed5c76..1f6704acc 100644 --- a/docs/Status_Reference.md +++ b/docs/Status_Reference.md @@ -419,10 +419,18 @@ is defined): during the last QUERY_PROBE command. Note, if this is used in a macro, due to the order of template expansion, the QUERY_PROBE command must be run prior to the macro containing this reference. -- `last_z_result`: Returns the Z result value of the last PROBE - command. Note, if this is used in a macro, due to the order of - template expansion, the PROBE (or similar) command must be run prior - to the macro containing this reference. +- `last_probe_position`: The results of the last `PROBE` command. This + value is encoded as a [coordinate](#accessing-coordinates). The + probe hardware estimates that if one were to command the toolhead to + XY position `last_probe_position.x`,`last_probe_position.y` and + descend then the tip of the toolhead would first contact the bed at + a Z height of `last_probe_position.z`. These coordinates are + relative to the frame (that is, they use the coordinate system + specified in the config file). Note, if this is used in a macro, + due to the order of template expansion, the `PROBE` command must be + run prior to the macro containing this reference. +- `last_z_result`: This value is deprecated; it will be removed in the + near future. ## pwm_cycle_time diff --git a/docs/_klipper3d/build-translations.sh b/docs/_klipper3d/build-website.sh similarity index 82% rename from docs/_klipper3d/build-translations.sh rename to docs/_klipper3d/build-website.sh index 4a0117c24..9c1ae0205 100755 --- a/docs/_klipper3d/build-translations.sh +++ b/docs/_klipper3d/build-website.sh @@ -1,7 +1,8 @@ #!/bin/bash -# This script extracts the Klipper translations and builds multiple -# mdocs sites - one for each supported language. See the README file -# for additional details. +# This script creates the main klipper3d.org website hosted on github. +# It extracts the Klipper translations and builds multiple mdocs sites +# - one for each supported language. See the README file for +# additional details. MKDOCS_DIR="docs/_klipper3d/" WORK_DIR="work/" @@ -22,6 +23,11 @@ done < <(egrep -v '^ *(#|$)' ${TRANS_FILE}) echo "building site for en" mkdocs build -f ${MKDOCS_MAIN} +# Cleanup files (mkdocs copies _klipper3d dir and its sitemap doesn't work) +rm -rf ${PWD}/site/_klipper3d/__pycache__ +find ${PWD}/site/_klipper3d ! -name '*.css' -type f -delete +rm ${PWD}/site/sitemap.xml ${PWD}/site/sitemap.xml.gz + # Build each additional language website while IFS="," read dirname langsite langdesc langsearch; do new_docs_dir="${WORK_DIR}lang/${langsite}/docs/" @@ -81,4 +87,10 @@ while IFS="," read dirname langsite langdesc langsearch; do mkdir -p "${PWD}/site/${langsite}/" ln -sf "${PWD}/site/${langsite}/" "${WORK_DIR}lang/${langsite}/site" mkdocs build -f "${new_mkdocs_file}" + + # Cleanup files (mkdocs copies _klipper3d dir and its sitemap doesn't work) + rm -rf "${PWD}/site/${langsite}/_klipper3d/__pycache__" + find "${PWD}/site/${langsite}/_klipper3d" ! -name '*.css' -type f -delete + rm "${PWD}/site/${langsite}/sitemap.xml" "${PWD}/site/${langsite}/sitemap.xml.gz" + done < <(egrep -v '^ *(#|$)' ${TRANS_FILE}) diff --git a/klippy/chelper/serialqueue.c b/klippy/chelper/serialqueue.c index d30433554..3ab7216b9 100644 --- a/klippy/chelper/serialqueue.c +++ b/klippy/chelper/serialqueue.c @@ -107,7 +107,7 @@ struct serialqueue { #define MIN_RTO 0.025 #define MAX_RTO 5.000 #define MAX_PENDING_BLOCKS 12 -#define MIN_REQTIME_DELTA 0.250 +#define MIN_REQTIME_DELTA 0.100 #define MIN_BACKGROUND_DELTA 0.005 #define IDLE_QUERY_TIME 1.0 @@ -886,9 +886,10 @@ serialqueue_send_batch(struct serialqueue *sq, struct command_queue *cq int len = 0; struct queue_message *qm; list_for_each_entry(qm, msgs, node) { - if (qm->min_clock + (1LL<<31) < qm->req_clock + if (qm->min_clock + (3LL<<29) < qm->req_clock && qm->req_clock != BACKGROUND_PRIORITY_CLOCK) - qm->min_clock = qm->req_clock - (1LL<<31); + // Avoid mcu clock comparison 31-bit overflow issues + qm->min_clock = qm->req_clock - (3LL<<29); len += qm->len; } if (! len) diff --git a/klippy/extras/ads1220.py b/klippy/extras/ads1220.py index 16080dc72..891783922 100644 --- a/klippy/extras/ads1220.py +++ b/klippy/extras/ads1220.py @@ -96,7 +96,6 @@ class ADS1220: self.printer, self._process_batch, self._start_measurements, self._finish_measurements, UPDATE_INTERVAL) # Command Configuration - self.attach_probe_cmd = None mcu.add_config_cmd( "config_ads1220 oid=%d spi_oid=%d data_ready_pin=%s" % (self.oid, self.spi.get_oid(), self.data_ready_pin)) @@ -105,12 +104,15 @@ class ADS1220: mcu.register_config_callback(self._build_config) self.query_ads1220_cmd = None + def setup_trigger_analog(self, trigger_analog_oid): + self.mcu.add_config_cmd( + "ads1220_attach_trigger_analog oid=%d trigger_analog_oid=%d" + % (self.oid, trigger_analog_oid), is_init=True) + def _build_config(self): cmdqueue = self.spi.get_command_queue() self.query_ads1220_cmd = self.mcu.lookup_command( "query_ads1220 oid=%c rest_ticks=%u", cq=cmdqueue) - self.attach_probe_cmd = self.mcu.lookup_command( - "ads1220_attach_load_cell_probe oid=%c load_cell_probe_oid=%c") self.ffreader.setup_query_command("query_ads1220_status oid=%c", oid=self.oid, cq=cmdqueue) @@ -120,6 +122,9 @@ class ADS1220: def get_samples_per_second(self): return self.sps + def lookup_sensor_error(self, error_code): + return "Unknown ads1220 error" % (error_code,) + # returns a tuple of the minimum and maximum value of the sensor, used to # detect if a data value is saturated def get_range(self): @@ -129,9 +134,6 @@ class ADS1220: def add_client(self, callback): self.batch_bulk.add_client(callback) - def attach_load_cell_probe(self, load_cell_probe_oid): - self.attach_probe_cmd.send([self.oid, load_cell_probe_oid]) - # Measurement decoding def _convert_samples(self, samples): adc_factor = 1. / (1 << 23) @@ -175,6 +177,8 @@ class ADS1220: # read startup register state and validate val = self.read_reg(0x0, 4) if val != RESET_STATE: + if self.mcu.is_fileoutput(): + return raise self.printer.command_error( "Invalid ads1220 reset state (got %s vs %s).\n" "This is generally indicative of connection problems\n" @@ -209,6 +213,8 @@ class ADS1220: self.spi.spi_send(write_command) stored_val = self.read_reg(reg, len(register_bytes)) if bytearray(register_bytes) != stored_val: + if self.mcu.is_fileoutput(): + return raise self.printer.command_error( "Failed to set ADS1220 register [0x%x] to %s: got %s. " "This may be a connection problem (e.g. faulty wiring)" % ( diff --git a/klippy/extras/axis_twist_compensation.py b/klippy/extras/axis_twist_compensation.py index 908ac4dae..081af9269 100644 --- a/klippy/extras/axis_twist_compensation.py +++ b/klippy/extras/axis_twist_compensation.py @@ -51,21 +51,27 @@ class AxisTwistCompensation: self.printer.register_event_handler("probe:update_results", self._update_z_compensation_value) - def _update_z_compensation_value(self, pos): + def _update_z_compensation_value(self, poslist): + pos = poslist[0] + zo = 0. if self.z_compensations: - pos[2] += self._get_interpolated_z_compensation( - pos[0], self.z_compensations, + zo += self._get_interpolated_z_compensation( + pos.test_x, self.z_compensations, self.compensation_start_x, self.compensation_end_x ) if self.zy_compensations: - pos[2] += self._get_interpolated_z_compensation( - pos[1], self.zy_compensations, + zo += self._get_interpolated_z_compensation( + pos.test_y, self.zy_compensations, self.compensation_start_y, self.compensation_end_y ) + pos = manual_probe.ProbeResult(pos.bed_x, pos.bed_y, pos.bed_z + zo, + pos.test_x, pos.test_y, pos.test_z) + poslist[0] = pos + def _get_interpolated_z_compensation( self, coord, z_compensations, comp_start, @@ -101,8 +107,7 @@ class Calibrater: self.gcode = self.printer.lookup_object('gcode') self.probe = None # probe settings are set to none, until they are available - self.lift_speed, self.probe_x_offset, self.probe_y_offset, _ = \ - None, None, None, None + self.lift_speed = None self.printer.register_event_handler("klippy:connect", self._handle_connect) self.speed = compensation.speed @@ -129,8 +134,6 @@ class Calibrater: raise self.printer.config_error( "AXIS_TWIST_COMPENSATION requires [probe] to be defined") self.lift_speed = self.probe.get_probe_params()['lift_speed'] - self.probe_x_offset, self.probe_y_offset, _ = \ - self.probe.get_offsets() def _register_gcode_handlers(self): # register gcode handlers @@ -148,6 +151,7 @@ class Calibrater: def cmd_AXIS_TWIST_COMPENSATION_CALIBRATE(self, gcmd): self.gcmd = gcmd + probe_x_offset, probe_y_offset, _ = self.probe.get_offsets(gcmd) sample_count = gcmd.get_int('SAMPLE_COUNT', DEFAULT_SAMPLE_COUNT) axis = gcmd.get('AXIS', 'X') @@ -219,7 +223,7 @@ class Calibrater: "Invalid axis.") probe_points = self._calculate_probe_points( - nozzle_points, self.probe_x_offset, self.probe_y_offset) + nozzle_points, probe_x_offset, probe_y_offset) # verify no other manual probe is in progress manual_probe.verify_no_manual_probe(self.printer) @@ -231,7 +235,7 @@ class Calibrater: self._calibration(probe_points, nozzle_points, interval_dist) def _calculate_probe_points(self, nozzle_points, - probe_x_offset, probe_y_offset): + probe_x_offset, probe_y_offset): # calculate the points to put the nozzle at # returned as a list of tuples probe_points = [] @@ -267,7 +271,7 @@ class Calibrater: # probe the point pos = probe.run_single_probe(self.probe, self.gcmd) - self.current_measured_z = pos[2] + self.current_measured_z = pos.bed_z # horizontal_move_z (to prevent probe trigger or hitting bed) self._move_helper((None, None, self.horizontal_move_z)) @@ -286,14 +290,14 @@ class Calibrater: # returns a callback function for the manual probe is_end = self.current_point_index == len(probe_points) - 1 - def callback(kin_pos): - if kin_pos is None: + def callback(mpresult): + if mpresult is None: # probe was cancelled self.gcmd.respond_info( "AXIS_TWIST_COMPENSATION_CALIBRATE: Probe cancelled, " "calibration aborted") return - z_offset = self.current_measured_z - kin_pos[2] + z_offset = self.current_measured_z - mpresult.bed_z self.results.append(z_offset) if is_end: # end of calibration diff --git a/klippy/extras/bed_mesh.py b/klippy/extras/bed_mesh.py index a8e5764c0..dbe4f331c 100644 --- a/klippy/extras/bed_mesh.py +++ b/klippy/extras/bed_mesh.py @@ -308,7 +308,7 @@ class BedMesh: result["calibration"] = self.bmc.dump_calibration(gcmd) else: result["calibration"] = self.bmc.dump_calibration() - offsets = [0, 0, 0] if prb is None else prb.get_offsets() + offsets = [0, 0, 0] if prb is None else prb.get_offsets(gcmd) result["probe_offsets"] = offsets result["axis_minimum"] = th_sts["axis_minimum"] result["axis_maximum"] = th_sts["axis_maximum"] @@ -651,9 +651,9 @@ class BedMeshCalibrate: except BedMeshError as e: raise gcmd.error(str(e)) self.probe_mgr.start_probe(gcmd) - def probe_finalize(self, offsets, positions): - z_offset = offsets[2] - positions = [[round(p[0], 2), round(p[1], 2), p[2]] + def probe_finalize(self, positions): + z_offset = 0. + positions = [[round(p.bed_x, 2), round(p.bed_y, 2), p.bed_z] for p in positions] if self.probe_mgr.get_zero_ref_mode() == ZrefMode.PROBE: ref_pos = positions.pop() @@ -682,7 +682,7 @@ class BedMeshCalibrate: idx_offset = 0 start_idx = 0 for i, pts in substitutes.items(): - fpt = [p - o for p, o in zip(base_points[i], offsets[:2])] + fpt = list(base_points[i][:2]) # offset the index to account for additional samples idx = i + idx_offset # Add "normal" points @@ -702,7 +702,7 @@ class BedMeshCalibrate: # validate length of result if len(base_points) != len(positions): - self._dump_points(probed_pts, positions, offsets) + self._dump_points(probed_pts, positions) raise self.gcode.error( "bed_mesh: invalid position list size, " "generated count: %d, probed count: %d" @@ -713,7 +713,7 @@ class BedMeshCalibrate: row = [] prev_pos = base_points[0] for pos, result in zip(base_points, positions): - offset_pos = [p - o for p, o in zip(pos, offsets[:2])] + offset_pos = pos[:2] if ( not isclose(offset_pos[0], result[0], abs_tol=.5) or not isclose(offset_pos[1], result[1], abs_tol=.5) @@ -786,7 +786,7 @@ class BedMeshCalibrate: self.gcode.respond_info("Mesh Bed Leveling Complete") if self._profile_name is not None: self.bedmesh.save_profile(self._profile_name) - def _dump_points(self, probed_pts, corrected_pts, offsets): + def _dump_points(self, probed_pts, corrected_pts): # logs generated points with offset applied, points received # from the finalize callback, and the list of corrected points points = self.probe_mgr.get_base_points() @@ -797,7 +797,7 @@ class BedMeshCalibrate: for i in list(range(max_len)): gen_pt = probed_pt = corr_pt = "" if i < len(points): - off_pt = [p - o for p, o in zip(points[i], offsets[:2])] + off_pt = points[i][:2] gen_pt = "(%.2f, %.2f)" % tuple(off_pt) if i < len(probed_pts): probed_pt = "(%.2f, %.2f, %.4f)" % tuple(probed_pts[i]) @@ -1209,7 +1209,7 @@ class RapidScanHelper: gcmd_params["SAMPLE_TIME"] = half_window * 2 self._raise_tool(gcmd, scan_height) probe_session = pprobe.start_probe_session(gcmd) - offsets = pprobe.get_offsets() + offsets = pprobe.get_offsets(gcmd) initial_move = True for pos, is_probe_pt in self.probe_manager.iter_rapid_path(): pos = self._apply_offsets(pos[:2], offsets) @@ -1221,7 +1221,7 @@ class RapidScanHelper: probe_session.run_probe(gcmd) results = probe_session.pull_probed_results() toolhead.get_last_move_time() - self.finalize_callback(offsets, results) + self.finalize_callback(results) probe_session.end_probe_session() def _raise_tool(self, gcmd, scan_height): diff --git a/klippy/extras/bed_tilt.py b/klippy/extras/bed_tilt.py index e5686cbeb..feb924997 100644 --- a/klippy/extras/bed_tilt.py +++ b/klippy/extras/bed_tilt.py @@ -58,19 +58,17 @@ class BedTiltCalibrate: cmd_BED_TILT_CALIBRATE_help = "Bed tilt calibration script" def cmd_BED_TILT_CALIBRATE(self, gcmd): self.probe_helper.start_probe(gcmd) - def probe_finalize(self, offsets, positions): + def probe_finalize(self, positions): # Setup for coordinate descent analysis - z_offset = offsets[2] logging.info("Calculating bed_tilt with: %s", positions) params = { 'x_adjust': self.bedtilt.x_adjust, 'y_adjust': self.bedtilt.y_adjust, - 'z_adjust': z_offset } + 'z_adjust': 0. } logging.info("Initial bed_tilt parameters: %s", params) # Perform coordinate descent def adjusted_height(pos, params): - x, y, z = pos - return (z - x*params['x_adjust'] - y*params['y_adjust'] - - params['z_adjust']) + return (pos.bed_z - pos.bed_x*params['x_adjust'] + - pos.bed_y*params['y_adjust'] - params['z_adjust']) def errorfunc(params): total_error = 0. for pos in positions: @@ -81,8 +79,7 @@ class BedTiltCalibrate: # Update current bed_tilt calculations x_adjust = new_params['x_adjust'] y_adjust = new_params['y_adjust'] - z_adjust = (new_params['z_adjust'] - z_offset - - x_adjust * offsets[0] - y_adjust * offsets[1]) + z_adjust = new_params['z_adjust'] self.bedtilt.update_adjust(x_adjust, y_adjust, z_adjust) # Log and report results logging.info("Calculated bed_tilt parameters: %s", new_params) diff --git a/klippy/extras/bltouch.py b/klippy/extras/bltouch.py index 2bcb9cc10..85fd03e9a 100644 --- a/klippy/extras/bltouch.py +++ b/klippy/extras/bltouch.py @@ -65,8 +65,8 @@ class BLTouchProbe: config, self, self.mcu_endstop.query_endstop) self.probe_offsets = probe.ProbeOffsetsHelper(config) self.param_helper = probe.ProbeParameterHelper(config) - self.homing_helper = probe.HomingViaProbeHelper(config, self, - self.param_helper) + self.homing_helper = probe.HomingViaProbeHelper( + config, self, self.probe_offsets, self.param_helper) self.probe_session = probe.ProbeSessionHelper( config, self.param_helper, self.homing_helper.start_probe_session) # Register BLTOUCH_DEBUG command @@ -80,8 +80,8 @@ class BLTouchProbe: self.handle_connect) def get_probe_params(self, gcmd=None): return self.param_helper.get_probe_params(gcmd) - def get_offsets(self): - return self.probe_offsets.get_offsets() + def get_offsets(self, gcmd=None): + return self.probe_offsets.get_offsets(gcmd) def get_status(self, eventtime): return self.cmd_helper.get_status(eventtime) def start_probe_session(self, gcmd): diff --git a/klippy/extras/bus.py b/klippy/extras/bus.py index b04fbe764..19e278418 100644 --- a/klippy/extras/bus.py +++ b/klippy/extras/bus.py @@ -240,6 +240,10 @@ def MCU_I2C_from_config(config, default_addr=None, default_speed=100000): for name in ['scl', 'sda']] sw_pin_params = [ppins.lookup_pin(config.get(name), share_type=name) for name in sw_pin_names] + for pin_params in sw_pin_params: + if pin_params['chip'] != i2c_mcu: + raise ppins.error("%s: i2c pins must be on same mcu" % ( + config.get_name(),)) sw_pins = tuple([pin_params['pin'] for pin_params in sw_pin_params]) bus = None else: diff --git a/klippy/extras/delta_calibrate.py b/klippy/extras/delta_calibrate.py index 4054e2310..df7f39356 100644 --- a/klippy/extras/delta_calibrate.py +++ b/klippy/extras/delta_calibrate.py @@ -152,12 +152,12 @@ class DeltaCalibrate: "%.3f,%.3f,%.3f" % tuple(spos1)) configfile.set(section, "distance%d_pos2" % (i,), "%.3f,%.3f,%.3f" % tuple(spos2)) - def probe_finalize(self, offsets, positions): + def probe_finalize(self, positions): # Convert positions into (z_offset, stable_position) pairs - z_offset = offsets[2] kin = self.printer.lookup_object('toolhead').get_kinematics() - delta_params = kin.get_calibration() - probe_positions = [(z_offset, delta_params.calc_stable_position(p)) + csp = kin.get_calibration().calc_stable_position + probe_positions = [(p.test_z - p.bed_z, + csp([p.test_x, p.test_y, p.test_z])) for p in positions] # Perform analysis self.calculate_params(probe_positions, self.last_distances) diff --git a/klippy/extras/exclude_object.py b/klippy/extras/exclude_object.py index c80dd000d..3ca6d53eb 100644 --- a/klippy/extras/exclude_object.py +++ b/klippy/extras/exclude_object.py @@ -89,7 +89,7 @@ class ExcludeObject: offset = [0.] * num_coord self.extrusion_offsets[ename] = offset if len(offset) < num_coord: - offset.extend([0.] * (len(num_coord) - len(offset))) + offset.extend([0.] * (num_coord - len(offset))) return offset def get_position(self): diff --git a/klippy/extras/fan.py b/klippy/extras/fan.py index c5677ba06..2bcc512d7 100644 --- a/klippy/extras/fan.py +++ b/klippy/extras/fan.py @@ -63,7 +63,7 @@ class Fan: self.last_req_value = value self.last_fan_value = self.max_power self.mcu_fan.set_pwm(print_time, self.max_power) - return "delay", self.kick_start_time + return "repeat", print_time + self.kick_start_time self.last_fan_value = self.last_req_value = value self.mcu_fan.set_pwm(print_time, value) def set_speed(self, value, print_time=None): diff --git a/klippy/extras/gcode_move.py b/klippy/extras/gcode_move.py index c2b307663..3980f556e 100644 --- a/klippy/extras/gcode_move.py +++ b/klippy/extras/gcode_move.py @@ -94,7 +94,7 @@ class GCodeMove: def _get_gcode_position(self): p = [lp - bp for lp, bp in zip(self.last_position, self.base_position)] p[3] /= self.extrude_factor - return p[:4] + return p def _get_gcode_speed(self): return self.speed / self.speed_factor def _get_gcode_speed_override(self): @@ -191,7 +191,7 @@ class GCodeMove: def cmd_M114(self, gcmd): # Get Current Position p = self._get_gcode_position() - gcmd.respond_raw("X:%.3f Y:%.3f Z:%.3f E:%.3f" % tuple(p)) + gcmd.respond_raw("X:%.3f Y:%.3f Z:%.3f E:%.3f" % tuple(p[:4])) def cmd_M220(self, gcmd): # Set speed factor override percentage value = gcmd.get_float('S', 100., above=0.) / (60. * 100.) diff --git a/klippy/extras/hx71x.py b/klippy/extras/hx71x.py index a7f49f8ad..a1bc20529 100644 --- a/klippy/extras/hx71x.py +++ b/klippy/extras/hx71x.py @@ -53,7 +53,6 @@ class HX71xBase: self._finish_measurements, UPDATE_INTERVAL) # Command Configuration self.query_hx71x_cmd = None - self.attach_probe_cmd = None mcu.add_config_cmd( "config_hx71x oid=%d gain_channel=%d dout_pin=%s sclk_pin=%s" % (self.oid, self.gain_channel, self.dout_pin, self.sclk_pin)) @@ -62,14 +61,17 @@ class HX71xBase: mcu.register_config_callback(self._build_config) + def setup_trigger_analog(self, trigger_analog_oid): + self.mcu.add_config_cmd( + "hx71x_attach_trigger_analog oid=%d trigger_analog_oid=%d" + % (self.oid, trigger_analog_oid), is_init=True) + def _build_config(self): + cmd_queue = self.mcu.alloc_command_queue() self.query_hx71x_cmd = self.mcu.lookup_command( - "query_hx71x oid=%c rest_ticks=%u") - self.attach_probe_cmd = self.mcu.lookup_command( - "hx71x_attach_load_cell_probe oid=%c load_cell_probe_oid=%c") + "query_hx71x oid=%c rest_ticks=%u", cq=cmd_queue) self.ffreader.setup_query_command("query_hx71x_status oid=%c", - oid=self.oid, - cq=self.mcu.alloc_command_queue()) + oid=self.oid, cq=cmd_queue) def get_mcu(self): @@ -78,6 +80,9 @@ class HX71xBase: def get_samples_per_second(self): return self.sps + def lookup_sensor_error(self, error_code): + return "Unknown hx71x error %d" % (error_code,) + # returns a tuple of the minimum and maximum value of the sensor, used to # detect if a data value is saturated def get_range(self): @@ -87,9 +92,6 @@ class HX71xBase: def add_client(self, callback): self.batch_bulk.add_client(callback) - def attach_load_cell_probe(self, load_cell_probe_oid): - self.attach_probe_cmd.send([self.oid, load_cell_probe_oid]) - # Measurement decoding def _convert_samples(self, samples): adc_factor = 1. / (1 << 23) diff --git a/klippy/extras/ldc1612.py b/klippy/extras/ldc1612.py index dd41b43ae..085dc2a55 100644 --- a/klippy/extras/ldc1612.py +++ b/klippy/extras/ldc1612.py @@ -84,11 +84,15 @@ class LDC1612: default_addr=LDC1612_ADDR, default_speed=400000) self.mcu = mcu = self.i2c.get_mcu() + self._sensor_errors = {} self.oid = oid = mcu.create_oid() self.query_ldc1612_cmd = None - self.ldc1612_setup_home_cmd = self.query_ldc1612_home_state_cmd = None - self.frequency = config.getint("frequency", DEFAULT_LDC1612_FREQ, - 2000000, 40000000) + self.clock_freq = config.getint("frequency", DEFAULT_LDC1612_FREQ, + 2000000, 40000000) + # Coil frequency divider, assume 12MHz is BTT Eddy + # BTT Eddy's coil frequency is > 1/4 of reference clock + self.sensor_div = 1 if self.clock_freq != DEFAULT_LDC1612_FREQ else 2 + self.freq_conv = float(self.clock_freq * self.sensor_div) / (1<<28) if config.get('intb_pin', None) is not None: ppins = config.get_printer().lookup_object("pins") pin_params = ppins.lookup_pin(config.get('intb_pin')) @@ -115,22 +119,25 @@ class LDC1612: hdr = ('time', 'frequency', 'z') self.batch_bulk.add_mux_endpoint("ldc1612/dump_ldc1612", "sensor", self.name, {'header': hdr}) + def setup_trigger_analog(self, trigger_analog_oid): + self.mcu.add_config_cmd( + "ldc1612_attach_trigger_analog oid=%d trigger_analog_oid=%d" + % (self.oid, trigger_analog_oid), is_init=True) def _build_config(self): cmdqueue = self.i2c.get_command_queue() self.query_ldc1612_cmd = self.mcu.lookup_command( "query_ldc1612 oid=%c rest_ticks=%u", cq=cmdqueue) self.ffreader.setup_query_command("query_status_ldc1612 oid=%c", oid=self.oid, cq=cmdqueue) - self.ldc1612_setup_home_cmd = self.mcu.lookup_command( - "ldc1612_setup_home oid=%c clock=%u threshold=%u" - " trsync_oid=%c trigger_reason=%c error_reason=%c", cq=cmdqueue) - self.query_ldc1612_home_state_cmd = self.mcu.lookup_query_command( - "query_ldc1612_home_state oid=%c", - "ldc1612_home_state oid=%c homing=%c trigger_clock=%u", - oid=self.oid, cq=cmdqueue) + errors = self.mcu.get_enumerations().get("ldc1612_error:", {}) + self._sensor_errors = {v: k for k, v in errors.items()} def get_mcu(self): return self.i2c.get_mcu() + def get_samples_per_second(self): + return self.data_rate def read_reg(self, reg): + if self.mcu.is_fileoutput(): + return 0 params = self.i2c.i2c_read([reg], 2) response = bytearray(params['response']) return (response[0] << 8) | response[1] @@ -139,48 +146,61 @@ class LDC1612: minclock=minclock) def add_client(self, cb): self.batch_bulk.add_client(cb) - # Homing - def setup_home(self, print_time, trigger_freq, - trsync_oid, hit_reason, err_reason): - clock = self.mcu.print_time_to_clock(print_time) - tfreq = int(trigger_freq * (1<<28) / float(self.frequency) + 0.5) - self.ldc1612_setup_home_cmd.send( - [self.oid, clock, tfreq, trsync_oid, hit_reason, err_reason]) - def clear_home(self): - self.ldc1612_setup_home_cmd.send([self.oid, 0, 0, 0, 0, 0]) - if self.mcu.is_fileoutput(): - return 0. - params = self.query_ldc1612_home_state_cmd.send([self.oid]) - tclock = self.mcu.clock32_to_clock64(params['trigger_clock']) - return self.mcu.clock_to_print_time(tclock) + def lookup_sensor_error(self, error): + return self._sensor_errors.get(error, "Unknown ldc1612 error") + def convert_frequency(self, freq): + return int(freq / self.freq_conv + 0.5) # Measurement decoding def _convert_samples(self, samples): - freq_conv = float(self.frequency) / (1<<28) + freq_conv = self.freq_conv count = 0 + errors = {} + def log_once(msg): + if not errors.get(msg, 0): + errors[msg] = 0 + errors[msg] += 1 for ptime, val in samples: mv = val & 0x0fffffff - if mv != val: + if val > 0x03ffffff or val == 0x0: self.last_error_count += 1 + if (val >> 16 & 0xffff) == 0xffff: + # Encoded error from sensor_ldc1612.c + log_once(self.lookup_sensor_error(val & 0xffff)) + continue + error_bits = (val >> 28) & 0x0f + if error_bits & 0x8 or mv == 0x0000000: + log_once("Frequency under valid range") + if error_bits & 0x4 or mv > 0x3ffffff: + type = "hard" if error_bits & 0x4 else "soft" + log_once("Frequency over valid %s range" % (type)) + if error_bits & 0x2: + log_once("Conversion Watchdog timeout") + if error_bits & 0x1: + log_once("Amplitude Low/High warning") samples[count] = (round(ptime, 6), round(freq_conv * mv, 3), 999.9) count += 1 + del samples[count:] + for msg in errors: + logging.error("%s: %s (%d)" % (self.name, msg, errors[msg])) # Start, stop, and process message batches def _start_measurements(self): # In case of miswiring, testing LDC1612 device ID prevents treating # noise or wrong signal as a correctly initialized device manuf_id = self.read_reg(REG_MANUFACTURER_ID) dev_id = self.read_reg(REG_DEVICE_ID) - if manuf_id != LDC1612_MANUF_ID or dev_id != LDC1612_DEV_ID: + if ((manuf_id != LDC1612_MANUF_ID or dev_id != LDC1612_DEV_ID) + and not self.mcu.is_fileoutput()): raise self.printer.command_error( "Invalid ldc1612 id (got %x,%x vs %x,%x).\n" "This is generally indicative of connection problems\n" "(e.g. faulty wiring) or a faulty ldc1612 chip." % (manuf_id, dev_id, LDC1612_MANUF_ID, LDC1612_DEV_ID)) # Setup chip in requested query rate - rcount0 = self.frequency / (16. * (self.data_rate - 4)) + rcount0 = self.clock_freq / (16. * self.data_rate) self.set_reg(REG_RCOUNT0, int(rcount0 + 0.5)) self.set_reg(REG_OFFSET0, 0) - self.set_reg(REG_SETTLECOUNT0, int(SETTLETIME*self.frequency/16. + .5)) - self.set_reg(REG_CLOCK_DIVIDERS0, (1 << 12) | 1) + self.set_reg(REG_SETTLECOUNT0, int(SETTLETIME*self.clock_freq/16. + .5)) + self.set_reg(REG_CLOCK_DIVIDERS0, (self.sensor_div << 12) | 1) self.set_reg(REG_ERROR_CONFIG, (0x1f << 11) | 1) self.set_reg(REG_MUX_CONFIG, 0x0208 | DEGLITCH) self.set_reg(REG_CONFIG, 0x001 | (1<<12) | (1<<10) | (1<<9)) diff --git a/klippy/extras/load_cell.py b/klippy/extras/load_cell.py index f370cad90..361c937fc 100644 --- a/klippy/extras/load_cell.py +++ b/klippy/extras/load_cell.py @@ -313,6 +313,8 @@ class LoadCellSampleCollector: self._errors = 0 overflows = self._overflows self._overflows = 0 + if self._mcu.is_fileoutput(): + samples = [(0., 0., 0.)] return samples, (errors, overflows) if errors or overflows else 0 def _collect_until(self, timeout): @@ -324,6 +326,8 @@ class LoadCellSampleCollector: raise self._printer.command_error( "LoadCellSampleCollector timed out! Errors: %i," " Overflows: %i" % (self._errors, self._overflows)) + if self._mcu.is_fileoutput(): + break self._reactor.pause(now + RETRY_DELAY) return self._finish_collecting() diff --git a/klippy/extras/load_cell_probe.py b/klippy/extras/load_cell_probe.py index db2f2a650..1e361d5d1 100644 --- a/klippy/extras/load_cell_probe.py +++ b/klippy/extras/load_cell_probe.py @@ -5,15 +5,12 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import logging, math import mcu -from . import probe, sos_filter, load_cell, hx71x, ads1220 +from . import probe, trigger_analog, load_cell, hx71x, ads1220 np = None # delay NumPy import until configuration time -# constants for fixed point numbers -Q2_INT_BITS = 2 -Q2_FRAC_BITS = (32 - (1 + Q2_INT_BITS)) -Q16_INT_BITS = 16 -Q16_FRAC_BITS = (32 - (1 + Q16_INT_BITS)) +# MCU SOS filter scaled to "fractional grams" for consistent sensor precision +FRAC_GRAMS_CONV = 32768.0 class TapAnalysis: @@ -163,19 +160,16 @@ class ContinuousTareFilter: # create a filter design from the parameters def design_filter(self, error_func): - design = sos_filter.DigitalFilter(self.sps, error_func, self.drift, + return trigger_analog.DigitalFilter(self.sps, error_func, self.drift, self.drift_delay, self.buzz, self.buzz_delay, self.notches, self.notch_quality) - fixed_filter = sos_filter.FixedPointSosFilter( - design.get_filter_sections(), design.get_initial_state(), - Q2_INT_BITS, Q16_INT_BITS) - return fixed_filter # Combine ContinuousTareFilter and SosFilter into an easy-to-use class class ContinuousTareFilterHelper: - def __init__(self, config, sensor, cmd_queue): + def __init__(self, config, sensor, sos_filter): self._sensor = sensor + self._sos_filter = sos_filter self._sps = self._sensor.get_samples_per_second() max_filter_frequency = math.floor(self._sps / 2.) # setup filter parameters @@ -200,8 +194,8 @@ class ContinuousTareFilterHelper: self._config_design = self._build_filter() # filter design currently inside the MCU self._active_design = self._config_design - self._sos_filter = self._create_filter( - self._active_design.design_filter(config.error), cmd_queue) + design = self._active_design.design_filter(config.error) + self._sos_filter.set_filter_design(design) def _build_filter(self, gcmd=None): drift = self._drift_param.get(gcmd) @@ -214,21 +208,14 @@ class ContinuousTareFilterHelper: return ContinuousTareFilter(self._sps, drift, drift_delay, buzz, buzz_delay, notches, notch_quality) - def _create_filter(self, fixed_filter, cmd_queue): - return sos_filter.SosFilter(self._sensor.get_mcu(), cmd_queue, - fixed_filter, 4) - def update_from_command(self, gcmd, cq=None): gcmd_filter = self._build_filter(gcmd) # if filters are identical, no change required if self._active_design == gcmd_filter: return # update MCU filter from GCode command - self._sos_filter.change_filter( - self._active_design.design_filter(gcmd.error)) - - def get_sos_filter(self): - return self._sos_filter + design = self._active_design.design_filter(gcmd.error) + self._sos_filter.set_filter_design(design) # check results from the collector for errors and raise an exception is found @@ -246,7 +233,6 @@ class LoadCellProbeConfigHelper: self._printer = config.get_printer() self._load_cell = load_cell_inst self._sensor = load_cell_inst.get_sensor() - self._rest_time = 1. / float(self._sensor.get_samples_per_second()) # Collect 4 x 60hz power cycles of data to average across power noise self._tare_time_param = floatParamHelper(config, 'tare_time', default=4. / 60., minval=0.01, maxval=1.0) @@ -267,9 +253,6 @@ class LoadCellProbeConfigHelper: def get_safety_limit_grams(self, gcmd=None): return self._force_safety_limit_param.get(gcmd) - def get_rest_time(self): - return self._rest_time - def get_safety_range(self, gcmd=None): counts_per_gram = self._load_cell.get_counts_per_gram() # calculate the safety band @@ -284,137 +267,33 @@ class LoadCellProbeConfigHelper: raise cmd_err("Load cell force_safety_limit exceeds sensor range!") return safety_min, safety_max - # calculate 1/counts_per_gram in Q2 fixed point + # calculate 1/counts_per_gram def get_grams_per_count(self): counts_per_gram = self._load_cell.get_counts_per_gram() # The counts_per_gram could be so large that it becomes 0.0 when - # converted to Q2 format. This would mean the ADC range only measures a - # few grams which seems very unlikely. Treat this as an error: - if counts_per_gram >= 2**Q2_FRAC_BITS: + # sent to the mcu. This would mean the ADC range only measures + # a few grams which seems very unlikely. Treat this as an error: + if counts_per_gram >= (1<<29): raise OverflowError("counts_per_gram value is too large to filter") - return sos_filter.to_fixed_32((1. / counts_per_gram), Q2_INT_BITS) + return 1. / counts_per_gram -# McuLoadCellProbe is the interface to `load_cell_probe` on the MCU -# This also manages the SosFilter so all commands use one command queue -class McuLoadCellProbe: - WATCHDOG_MAX = 3 - ERROR_SAFETY_RANGE = mcu.MCU_trsync.REASON_COMMS_TIMEOUT + 1 - ERROR_OVERFLOW = mcu.MCU_trsync.REASON_COMMS_TIMEOUT + 2 - ERROR_WATCHDOG = mcu.MCU_trsync.REASON_COMMS_TIMEOUT + 3 - - def __init__(self, config, load_cell_inst, sos_filter_inst, config_helper, - trigger_dispatch): - self._printer = config.get_printer() - self._load_cell = load_cell_inst - self._sos_filter = sos_filter_inst - self._config_helper = config_helper - self._sensor = load_cell_inst.get_sensor() - self._mcu = self._sensor.get_mcu() - # configure MCU objects - self._dispatch = trigger_dispatch - self._cmd_queue = self._dispatch.get_command_queue() - self._oid = self._mcu.create_oid() - self._config_commands() - self._home_cmd = None - self._query_cmd = None - self._set_range_cmd = None - self._mcu.register_config_callback(self._build_config) - self._printer.register_event_handler("klippy:connect", self._on_connect) - - def _config_commands(self): - self._sos_filter.create_filter() - self._mcu.add_config_cmd( - "config_load_cell_probe oid=%d sos_filter_oid=%d" % ( - self._oid, self._sos_filter.get_oid())) - - def _build_config(self): - # Lookup commands - self._query_cmd = self._mcu.lookup_query_command( - "load_cell_probe_query_state oid=%c", - "load_cell_probe_state oid=%c is_homing_trigger=%c " - "trigger_ticks=%u", oid=self._oid, cq=self._cmd_queue) - self._set_range_cmd = self._mcu.lookup_command( - "load_cell_probe_set_range" - " oid=%c safety_counts_min=%i safety_counts_max=%i tare_counts=%i" - " trigger_grams=%u grams_per_count=%i", cq=self._cmd_queue) - self._home_cmd = self._mcu.lookup_command( - "load_cell_probe_home oid=%c trsync_oid=%c trigger_reason=%c" - " error_reason=%c clock=%u rest_ticks=%u timeout=%u", - cq=self._cmd_queue) - - # the sensor data stream is connected on the MCU at the ready event - def _on_connect(self): - self._sensor.attach_load_cell_probe(self._oid) - - def get_oid(self): - return self._oid - - def get_mcu(self): - return self._mcu - - def get_load_cell(self): - return self._load_cell - - def get_dispatch(self): - return self._dispatch - - def set_endstop_range(self, tare_counts, gcmd=None): - # update the load cell so it reflects the new tare value - self._load_cell.tare(tare_counts) - # update internal tare value - safety_min, safety_max = self._config_helper.get_safety_range(gcmd) - args = [self._oid, safety_min, safety_max, int(tare_counts), - self._config_helper.get_trigger_force_grams(gcmd), - self._config_helper.get_grams_per_count()] - self._set_range_cmd.send(args) - self._sos_filter.reset_filter() - - def home_start(self, print_time): - clock = self._mcu.print_time_to_clock(print_time) - rest_time = self._config_helper.get_rest_time() - rest_ticks = self._mcu.seconds_to_clock(rest_time) - self._home_cmd.send([self._oid, self._dispatch.get_oid(), - mcu.MCU_trsync.REASON_ENDSTOP_HIT, self.ERROR_SAFETY_RANGE, clock, - rest_ticks, self.WATCHDOG_MAX], reqclock=clock) - - def clear_home(self): - params = self._query_cmd.send([self._oid]) - # The time of the first sample that triggered is in "trigger_ticks" - trigger_ticks = self._mcu.clock32_to_clock64(params['trigger_ticks']) - # clear trsync from load_cell_endstop - self._home_cmd.send([self._oid, 0, 0, 0, 0, 0, 0, 0]) - return self._mcu.clock_to_print_time(trigger_ticks) - - -# Execute probing moves using the McuLoadCellProbe +# Execute probing moves using the MCU_trigger_analog class LoadCellProbingMove: - ERROR_MAP = { - mcu.MCU_trsync.REASON_COMMS_TIMEOUT: "Communication timeout during " - "homing", - McuLoadCellProbe.ERROR_SAFETY_RANGE: "Load Cell Probe Error: load " - "exceeds safety limit", - McuLoadCellProbe.ERROR_OVERFLOW: "Load Cell Probe Error: fixed point " - "math overflow", - McuLoadCellProbe.ERROR_WATCHDOG: "Load Cell Probe Error: timed out " - "waiting for sensor data" - } - - def __init__(self, config, mcu_load_cell_probe, param_helper, + def __init__(self, config, load_cell_inst, mcu_trigger_analog, param_helper, continuous_tare_filter_helper, config_helper): self._printer = config.get_printer() - self._mcu_load_cell_probe = mcu_load_cell_probe + self._load_cell = load_cell_inst + self._mcu_trigger_analog = mcu_trigger_analog self._param_helper = param_helper self._continuous_tare_filter_helper = continuous_tare_filter_helper self._config_helper = config_helper - self._mcu = mcu_load_cell_probe.get_mcu() - self._load_cell = mcu_load_cell_probe.get_load_cell() + self._mcu = mcu_trigger_analog.get_mcu() self._z_min_position = probe.lookup_minimum_z(config) - self._dispatch = mcu_load_cell_probe.get_dispatch() - probe.LookupZSteppers(config, self._dispatch.add_stepper) + dispatch = mcu_trigger_analog.get_dispatch() + probe.LookupZSteppers(config, dispatch.add_stepper) # internal state tracking self._tare_counts = 0 - self._last_trigger_time = 0 def _start_collector(self): toolhead = self._printer.lookup_object('toolhead') @@ -436,37 +315,22 @@ class LoadCellProbingMove: tare_counts = np.average(np.array(tare_samples)[:, 2].astype(float)) # update sos_filter with any gcode parameter changes self._continuous_tare_filter_helper.update_from_command(gcmd) - self._mcu_load_cell_probe.set_endstop_range(tare_counts, gcmd) + # update the load cell so it reflects the new tare value + self._load_cell.tare(tare_counts) + # update raw range + safety_min, safety_max = self._config_helper.get_safety_range(gcmd) + self._mcu_trigger_analog.set_raw_range(safety_min, safety_max) + # update internal tare value + gpc = self._config_helper.get_grams_per_count() * FRAC_GRAMS_CONV + sos_filter = self._mcu_trigger_analog.get_sos_filter() + Q17_14_FRAC_BITS = 14 + sos_filter.set_offset_scale(int(-tare_counts), gpc, Q17_14_FRAC_BITS) + # update trigger + trigger_val = self._config_helper.get_trigger_force_grams(gcmd) + trigger_frac_grams = trigger_val * FRAC_GRAMS_CONV + self._mcu_trigger_analog.set_trigger("abs_ge", trigger_frac_grams) - def _home_start(self, print_time): - # start trsync - trigger_completion = self._dispatch.start(print_time) - self._mcu_load_cell_probe.home_start(print_time) - return trigger_completion - - def home_start(self, print_time, sample_time, sample_count, rest_time, - triggered=True): - return self._home_start(print_time) - - def home_wait(self, home_end_time): - self._dispatch.wait_end(home_end_time) - # trigger has happened, now to find out why... - res = self._dispatch.stop() - # clear the homing state so it stops processing samples - self._last_trigger_time = self._mcu_load_cell_probe.clear_home() - if res >= mcu.MCU_trsync.REASON_COMMS_TIMEOUT: - error = "Load Cell Probe Error: unknown reason code %i" % (res,) - if res in self.ERROR_MAP: - error = self.ERROR_MAP[res] - raise self._printer.command_error(error) - if res != mcu.MCU_trsync.REASON_ENDSTOP_HIT: - return 0. - return self._last_trigger_time - - def get_steppers(self): - return self._dispatch.get_steppers() - - # Probe towards z_min until the load_cell_probe on the MCU triggers + # Probe towards z_min until the trigger_analog on the MCU triggers def probing_move(self, gcmd): # do not permit probing if the load cell is not calibrated if not self._load_cell.is_calibrated(): @@ -482,20 +346,22 @@ class LoadCellProbingMove: # start collector after tare samples are consumed collector = self._start_collector() # do homing move - return phoming.probing_move(self, pos, speed), collector + epos = phoming.probing_move(self._mcu_trigger_analog, pos, speed) + return epos, collector # Wait for the MCU to trigger with no movement def probing_test(self, gcmd, timeout): self._pause_and_tare(gcmd) toolhead = self._printer.lookup_object('toolhead') print_time = toolhead.get_last_move_time() - self._home_start(print_time) - return self.home_wait(print_time + timeout) + self._mcu_trigger_analog.home_start(print_time, 0., 0, 0.) + return self._mcu_trigger_analog.home_wait(print_time + timeout) def get_status(self, eventtime): + trig_time = self._mcu_trigger_analog.get_last_trigger_time() return { 'tare_counts': self._tare_counts, - 'last_trigger_time': self._last_trigger_time, + 'last_trigger_time': trig_time, } @@ -542,9 +408,11 @@ class TappingMove: # ProbeSession that implements Tap logic class TapSession: - def __init__(self, config, tapping_move, probe_params_helper): + def __init__(self, config, tapping_move, + probe_offsets, probe_params_helper): self._printer = config.get_printer() self._tapping_move = tapping_move + self._probe_offsets = probe_offsets self._probe_params_helper = probe_params_helper # Session state self._results = [] @@ -558,7 +426,8 @@ class TapSession: # probe until a single good sample is returned or retries are exhausted def run_probe(self, gcmd): epos, is_good = self._tapping_move.run_tap(gcmd) - self._results.append(epos) + res = self._probe_offsets.create_probe_result(epos) + self._results.append(res) def pull_probed_results(self): res = self._results @@ -615,22 +484,23 @@ class LoadCellPrinterProbe: # Read all user configuration and build modules config_helper = LoadCellProbeConfigHelper(config, self._load_cell) self._mcu = self._load_cell.get_sensor().get_mcu() - trigger_dispatch = mcu.TriggerDispatch(self._mcu) - continuous_tare_filter_helper = ContinuousTareFilterHelper(config, - sensor, trigger_dispatch.get_command_queue()) + self._mcu_trigger_analog = trigger_analog.MCU_trigger_analog(sensor) + cmd_queue = self._mcu_trigger_analog.get_dispatch().get_command_queue() + sos_filter = trigger_analog.MCU_SosFilter(self._mcu, cmd_queue, 4) + self._mcu_trigger_analog.setup_sos_filter(sos_filter) + continuous_tare_filter_helper = ContinuousTareFilterHelper( + config, sensor, sos_filter) # Probe Interface self._param_helper = probe.ProbeParameterHelper(config) self._cmd_helper = probe.ProbeCommandHelper(config, self) self._probe_offsets = probe.ProbeOffsetsHelper(config) - self._mcu_load_cell_probe = McuLoadCellProbe(config, self._load_cell, - continuous_tare_filter_helper.get_sos_filter(), config_helper, - trigger_dispatch) - load_cell_probing_move = LoadCellProbingMove(config, - self._mcu_load_cell_probe, self._param_helper, + load_cell_probing_move = LoadCellProbingMove(config, self._load_cell, + self._mcu_trigger_analog, self._param_helper, continuous_tare_filter_helper, config_helper) self._tapping_move = TappingMove(config, load_cell_probing_move, config_helper) - tap_session = TapSession(config, self._tapping_move, self._param_helper) + tap_session = TapSession(config, self._tapping_move, + self._probe_offsets, self._param_helper) self._probe_session = probe.ProbeSessionHelper(config, self._param_helper, tap_session.start_probe_session) # printer integration @@ -641,8 +511,8 @@ class LoadCellPrinterProbe: def get_probe_params(self, gcmd=None): return self._param_helper.get_probe_params(gcmd) - def get_offsets(self): - return self._probe_offsets.get_offsets() + def get_offsets(self, gcmd=None): + return self._probe_offsets.get_offsets(gcmd) def start_probe_session(self, gcmd): return self._probe_session.start_probe_session(gcmd) diff --git a/klippy/extras/manual_probe.py b/klippy/extras/manual_probe.py index 44b2c719f..727526916 100644 --- a/klippy/extras/manual_probe.py +++ b/klippy/extras/manual_probe.py @@ -1,9 +1,18 @@ # Helper script for manual z height probing # -# Copyright (C) 2019 Kevin O'Connor +# Copyright (C) 2019-2025 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import logging, bisect +import logging, bisect, collections + +# Main probe results tuple. The probe estimates that if the toollhead +# is commanded to xy position (bed_x, bed_y) and then descends, the +# nozzle will contact the bed at a toolhead z position of bed_z. The +# probe test itself was performed while the toolhead was at xyz +# position (test_x, test_y, test_z). All coordinates are relative to +# the frame (the coordinate system used in the config file). +ProbeResult = collections.namedtuple('probe_result', [ + 'bed_x', 'bed_y', 'bed_z', 'test_x', 'test_y', 'test_z']) # Helper to lookup the Z stepper config section def lookup_z_endstop_config(config): @@ -62,9 +71,9 @@ class ManualProbe: self.cmd_Z_OFFSET_APPLY_DELTA_ENDSTOPS, desc=self.cmd_Z_OFFSET_APPLY_ENDSTOP_help) self.reset_status() - def manual_probe_finalize(self, kin_pos): - if kin_pos is not None: - self.gcode.respond_info("Z position is %.3f" % (kin_pos[2],)) + def manual_probe_finalize(self, mpresult): + if mpresult is not None: + self.gcode.respond_info("Z position is %.3f" % (mpresult.bed_z,)) def reset_status(self): self.status = { 'is_active': False, @@ -77,10 +86,10 @@ class ManualProbe: cmd_MANUAL_PROBE_help = "Start manual probe helper script" def cmd_MANUAL_PROBE(self, gcmd): ManualProbeHelper(self.printer, gcmd, self.manual_probe_finalize) - def z_endstop_finalize(self, kin_pos): - if kin_pos is None: + def z_endstop_finalize(self, mpresult): + if mpresult is None: return - z_pos = self.z_position_endstop - kin_pos[2] + z_pos = self.z_position_endstop - mpresult.bed_z self.gcode.respond_info( "%s: position_endstop: %.3f\n" "The SAVE_CONFIG command will update the printer config file\n" @@ -271,10 +280,11 @@ class ManualProbeHelper: self.gcode.register_command('NEXT', None) self.gcode.register_command('ABORT', None) self.gcode.register_command('TESTZ', None) - kin_pos = None + mpresult = None if success: kin_pos = self.get_kinematics_pos() - self.finalize_callback(kin_pos) + mpresult = ProbeResult(*(kin_pos[:3] + kin_pos[:3])) + self.finalize_callback(mpresult) def load_config(config): return ManualProbe(config) diff --git a/klippy/extras/multi_pin.py b/klippy/extras/multi_pin.py index c834ee077..f126f928c 100644 --- a/klippy/extras/multi_pin.py +++ b/klippy/extras/multi_pin.py @@ -46,6 +46,8 @@ class PrinterMultiPin: def set_digital(self, print_time, value): for mcu_pin in self.mcu_pins: mcu_pin.set_digital(print_time, value) + def next_aligned_print_time(self, print_time, allow_early=0.): + return print_time def set_pwm(self, print_time, value): for mcu_pin in self.mcu_pins: mcu_pin.set_pwm(print_time, value) diff --git a/klippy/extras/output_pin.py b/klippy/extras/output_pin.py index a51292990..58a7302ca 100644 --- a/klippy/extras/output_pin.py +++ b/klippy/extras/output_pin.py @@ -38,18 +38,23 @@ class GCodeRequestQueue: pos += 1 req_pt, req_val = rqueue[pos] # Invoke callback for the request - min_wait = 0. ret = self.callback(next_time, req_val) if ret is not None: # Handle special cases - action, min_wait = ret + action, next_min_time = ret + self.next_min_flush_time = max(self.next_min_flush_time, + next_min_time) if action == "discard": del rqueue[:pos+1] continue - if action == "delay": + if action == "reschedule": + del rqueue[:pos] + continue + if action == "repeat": pos -= 1 del rqueue[:pos+1] - self.next_min_flush_time = next_time + max(min_wait, min_sched_time) + self.next_min_flush_time = max(self.next_min_flush_time, + next_time + min_sched_time) # Ensure following queue items are flushed self.motion_queuing.note_mcu_movequeue_activity( self.next_min_flush_time, is_step_gen=False) @@ -68,15 +73,20 @@ class GCodeRequestQueue: while 1: next_time = max(print_time, self.next_min_flush_time) # Invoke callback for the request - action, min_wait = "normal", 0. + action, next_min_time = "normal", 0. ret = self.callback(next_time, value) if ret is not None: # Handle special cases - action, min_wait = ret + action, next_min_time = ret + self.next_min_flush_time = max(self.next_min_flush_time, + next_min_time) if action == "discard": break - self.next_min_flush_time = next_time + max(min_wait, min_sched_time) - if action != "delay": + if action == "reschedule": + continue + self.next_min_flush_time = max(self.next_min_flush_time, + next_time + min_sched_time) + if action != "repeat": break diff --git a/klippy/extras/probe.py b/klippy/extras/probe.py index bfeb33cea..c57f9ef1d 100644 --- a/klippy/extras/probe.py +++ b/klippy/extras/probe.py @@ -17,11 +17,12 @@ can travel further (the Z minimum position can be negative). def calc_probe_z_average(positions, method='average'): if method != 'median': # Use mean average - count = float(len(positions)) - return [sum([pos[i] for pos in positions]) / count - for i in range(3)] + inv_count = 1. / float(len(positions)) + return manual_probe.ProbeResult( + *[sum([pos[i] for pos in positions]) * inv_count + for i in range(len(positions[0]))]) # Use median - z_sorted = sorted(positions, key=(lambda p: p[2])) + z_sorted = sorted(positions, key=(lambda p: p.bed_z)) middle = len(positions) // 2 if (len(positions) & 1) == 1: # odd number of samples @@ -36,7 +37,8 @@ def calc_probe_z_average(positions, method='average'): # Helper to implement common probing commands class ProbeCommandHelper: - def __init__(self, config, probe, query_endstop=None): + def __init__(self, config, probe, query_endstop=None, + can_set_z_offset=True): self.printer = config.get_printer() self.probe = probe self.query_endstop = query_endstop @@ -47,24 +49,28 @@ class ProbeCommandHelper: gcode.register_command('QUERY_PROBE', self.cmd_QUERY_PROBE, desc=self.cmd_QUERY_PROBE_help) # PROBE command + self.last_probe_position = gcode.Coord((0., 0., 0.)) self.last_z_result = 0. gcode.register_command('PROBE', self.cmd_PROBE, desc=self.cmd_PROBE_help) # PROBE_CALIBRATE command - self.probe_calibrate_z = 0. - gcode.register_command('PROBE_CALIBRATE', self.cmd_PROBE_CALIBRATE, - desc=self.cmd_PROBE_CALIBRATE_help) + self.probe_calibrate_info = None + if can_set_z_offset: + gcode.register_command('PROBE_CALIBRATE', self.cmd_PROBE_CALIBRATE, + desc=self.cmd_PROBE_CALIBRATE_help) # Other commands gcode.register_command('PROBE_ACCURACY', self.cmd_PROBE_ACCURACY, desc=self.cmd_PROBE_ACCURACY_help) - gcode.register_command('Z_OFFSET_APPLY_PROBE', - self.cmd_Z_OFFSET_APPLY_PROBE, - desc=self.cmd_Z_OFFSET_APPLY_PROBE_help) + if can_set_z_offset: + gcode.register_command('Z_OFFSET_APPLY_PROBE', + self.cmd_Z_OFFSET_APPLY_PROBE, + desc=self.cmd_Z_OFFSET_APPLY_PROBE_help) def _move(self, coord, speed): self.printer.lookup_object('toolhead').manual_move(coord, speed) def get_status(self, eventtime): return {'name': self.name, 'last_query': self.last_state, + 'last_probe_position': self.last_probe_position, 'last_z_result': self.last_z_result} cmd_QUERY_PROBE_help = "Return the status of the z-probe" def cmd_QUERY_PROBE(self, gcmd): @@ -78,12 +84,18 @@ class ProbeCommandHelper: cmd_PROBE_help = "Probe Z-height at current XY position" def cmd_PROBE(self, gcmd): pos = run_single_probe(self.probe, gcmd) - gcmd.respond_info("Result is z=%.6f" % (pos[2],)) - self.last_z_result = pos[2] - def probe_calibrate_finalize(self, kin_pos): - if kin_pos is None: + gcmd.respond_info("Result: at %.3f,%.3f estimate contact at z=%.6f" + % (pos.bed_x, pos.bed_y, pos.bed_z)) + gcode = self.printer.lookup_object('gcode') + self.last_probe_position = gcode.Coord((pos.bed_x, pos.bed_y, + pos.bed_z)) + x_offset, y_offset, z_offset = self.probe.get_offsets(gcmd) + self.last_z_result = pos.bed_z + z_offset # Deprecated + def probe_calibrate_finalize(self, mpresult): + if mpresult is None: return - z_offset = self.probe_calibrate_z - kin_pos[2] + ppos, offsets = self.probe_calibrate_info + z_offset = offsets[2] - mpresult.bed_z + ppos.bed_z gcode = self.printer.lookup_object('gcode') gcode.respond_info( "%s: z_offset: %.3f\n" @@ -96,17 +108,17 @@ class ProbeCommandHelper: manual_probe.verify_no_manual_probe(self.printer) params = self.probe.get_probe_params(gcmd) # Perform initial probe - curpos = run_single_probe(self.probe, gcmd) + ppos = run_single_probe(self.probe, gcmd) # Move away from the bed - self.probe_calibrate_z = curpos[2] + curpos = self.printer.lookup_object('toolhead').get_position() curpos[2] += 5. self._move(curpos, params['lift_speed']) # Move the nozzle over the probe point - x_offset, y_offset, z_offset = self.probe.get_offsets() - curpos[0] += x_offset - curpos[1] += y_offset + curpos[0] = ppos.bed_x + curpos[1] = ppos.bed_y self._move(curpos, params['probe_speed']) # Start manual probe + self.probe_calibrate_info = (ppos, self.probe.get_offsets(gcmd)) manual_probe.ManualProbeHelper(self.printer, gcmd, self.probe_calibrate_finalize) cmd_PROBE_ACCURACY_help = "Probe Z-height accuracy at current XY position" @@ -114,11 +126,11 @@ class ProbeCommandHelper: params = self.probe.get_probe_params(gcmd) sample_count = gcmd.get_int("SAMPLES", 10, minval=1) toolhead = self.printer.lookup_object('toolhead') - pos = toolhead.get_position() + start_pos = toolhead.get_position() gcmd.respond_info("PROBE_ACCURACY at X:%.3f Y:%.3f Z:%.3f" " (samples=%d retract=%.3f" " speed=%.1f lift_speed=%.1f)\n" - % (pos[0], pos[1], pos[2], + % (start_pos[0], start_pos[1], start_pos[2], sample_count, params['sample_retract_dist'], params['probe_speed'], params['lift_speed'])) # Create dummy gcmd with SAMPLES=1 @@ -134,21 +146,21 @@ class ProbeCommandHelper: probe_session.run_probe(fo_gcmd) probe_num += 1 # Retract - pos = toolhead.get_position() - liftpos = [None, None, pos[2] + params['sample_retract_dist']] + lift_z = toolhead.get_position()[2] + params['sample_retract_dist'] + liftpos = [start_pos[0], start_pos[1], lift_z] self._move(liftpos, params['lift_speed']) positions = probe_session.pull_probed_results() probe_session.end_probe_session() # Calculate maximum, minimum and average values - max_value = max([p[2] for p in positions]) - min_value = min([p[2] for p in positions]) + max_value = max([p.bed_z for p in positions]) + min_value = min([p.bed_z for p in positions]) range_value = max_value - min_value - avg_value = calc_probe_z_average(positions, 'average')[2] - median = calc_probe_z_average(positions, 'median')[2] + avg_value = calc_probe_z_average(positions, 'average').bed_z + median = calc_probe_z_average(positions, 'median').bed_z # calculate the standard deviation deviation_sum = 0 for i in range(len(positions)): - deviation_sum += pow(positions[i][2] - avg_value, 2.) + deviation_sum += pow(positions[i].bed_z - avg_value, 2.) sigma = (deviation_sum / len(positions)) ** 0.5 # Show information gcmd.respond_info( @@ -162,7 +174,7 @@ class ProbeCommandHelper: if offset == 0: gcmd.respond_info("Nothing to do: Z Offset is 0") return - z_offset = self.probe.get_offsets()[2] + z_offset = self.probe.get_offsets(gcmd)[2] new_calibrate = z_offset - offset gcmd.respond_info( "%s: z_offset: %.3f\n" @@ -195,9 +207,10 @@ class LookupZSteppers: # Homing via probe:z_virtual_endstop class HomingViaProbeHelper: - def __init__(self, config, mcu_probe, param_helper): + def __init__(self, config, mcu_probe, probe_offsets, param_helper): self.printer = config.get_printer() self.mcu_probe = mcu_probe + self.probe_offsets = probe_offsets self.param_helper = param_helper self.multi_probe_pending = False self.z_min_position = lookup_minimum_z(config) @@ -256,7 +269,9 @@ class HomingViaProbeHelper: pos[2] = self.z_min_position speed = self.param_helper.get_probe_params(gcmd)['probe_speed'] phoming = self.printer.lookup_object('homing') - self.results.append(phoming.probing_move(self.mcu_probe, pos, speed)) + ppos = phoming.probing_move(self.mcu_probe, pos, speed) + res = self.probe_offsets.create_probe_result(ppos) + self.results.append(res) def pull_probed_results(self): res = self.results self.results = [] @@ -364,12 +379,12 @@ class ProbeSessionHelper: reason += HINT_TIMEOUT raise self.printer.command_error(reason) # Allow axis_twist_compensation to update results - self.printer.send_event("probe:update_results", epos) + self.printer.send_event("probe:update_results", [epos]) # Report results gcode = self.printer.lookup_object('gcode') - gcode.respond_info("probe at %.3f,%.3f is z=%.6f" - % (epos[0], epos[1], epos[2])) - return epos[:3] + gcode.respond_info("probe: at %.3f,%.3f bed will contact at z=%.6f" + % (epos.bed_x, epos.bed_y, epos.bed_z)) + return epos def run_probe(self, gcmd): if self.hw_probe_session is None: self._probe_state_error() @@ -384,7 +399,7 @@ class ProbeSessionHelper: pos = self._probe(gcmd) positions.append(pos) # Check samples tolerance - z_positions = [p[2] for p in positions] + z_positions = [p.bed_z for p in positions] if max(z_positions)-min(z_positions) > params['samples_tolerance']: if retries >= params['samples_tolerance_retries']: raise gcmd.error("Probe samples exceed samples_tolerance") @@ -393,8 +408,9 @@ class ProbeSessionHelper: positions = [] # Retract if len(positions) < sample_count: + cur_z = toolhead.get_position()[2] toolhead.manual_move( - probexy + [pos[2] + params['sample_retract_dist']], + probexy + [cur_z + params['sample_retract_dist']], params['lift_speed']) # Calculate result epos = calc_probe_z_average(positions, params['samples_result']) @@ -410,8 +426,12 @@ class ProbeOffsetsHelper: self.x_offset = config.getfloat('x_offset', 0.) self.y_offset = config.getfloat('y_offset', 0.) self.z_offset = config.getfloat('z_offset') - def get_offsets(self): + def get_offsets(self, gcmd=None): return self.x_offset, self.y_offset, self.z_offset + def create_probe_result(self, test_pos): + return manual_probe.ProbeResult( + test_pos[0]+self.x_offset, test_pos[1]+self.y_offset, + test_pos[2]-self.z_offset, test_pos[0], test_pos[1], test_pos[2]) ###################################################################### @@ -463,7 +483,7 @@ class ProbePointsHelper: toolhead = self.printer.lookup_object('toolhead') toolhead.get_last_move_time() # Invoke callback - res = self.finalize_callback(self.probe_offsets, results) + res = self.finalize_callback(results) return res != "retry" def _move_next(self, probe_num): # Move to next XY probe point @@ -489,7 +509,7 @@ class ProbePointsHelper: return # Perform automatic probing self.lift_speed = probe.get_probe_params(gcmd)['lift_speed'] - self.probe_offsets = probe.get_offsets() + self.probe_offsets = probe.get_offsets(gcmd) if self.horizontal_move_z < self.probe_offsets[2]: raise gcmd.error("horizontal_move_z can't be less than" " probe's z_offset") @@ -520,10 +540,10 @@ class ProbePointsHelper: gcmd = self.gcode.create_gcode_command("", "", {}) manual_probe.ManualProbeHelper(self.printer, gcmd, self._manual_probe_finalize) - def _manual_probe_finalize(self, kin_pos): - if kin_pos is None: + def _manual_probe_finalize(self, mpresult): + if mpresult is None: return - self.manual_results.append(kin_pos) + self.manual_results.append(mpresult) self._manual_probe_start() # Helper to obtain a single probe measurement @@ -606,14 +626,14 @@ class PrinterProbe: self.mcu_probe.query_endstop) self.probe_offsets = ProbeOffsetsHelper(config) self.param_helper = ProbeParameterHelper(config) - self.homing_helper = HomingViaProbeHelper(config, self.mcu_probe, - self.param_helper) + self.homing_helper = HomingViaProbeHelper( + config, self.mcu_probe, self.probe_offsets, self.param_helper) self.probe_session = ProbeSessionHelper( config, self.param_helper, self.homing_helper.start_probe_session) def get_probe_params(self, gcmd=None): return self.param_helper.get_probe_params(gcmd) - def get_offsets(self): - return self.probe_offsets.get_offsets() + def get_offsets(self, gcmd=None): + return self.probe_offsets.get_offsets(gcmd) def get_status(self, eventtime): return self.cmd_helper.get_status(eventtime) def start_probe_session(self, gcmd): diff --git a/klippy/extras/probe_eddy_current.py b/klippy/extras/probe_eddy_current.py index 96c766708..6952480a3 100644 --- a/klippy/extras/probe_eddy_current.py +++ b/klippy/extras/probe_eddy_current.py @@ -5,7 +5,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import logging, math, bisect import mcu -from . import ldc1612, probe, manual_probe +from . import ldc1612, trigger_analog, probe, manual_probe OUT_OF_RANGE = 99.9 @@ -31,8 +31,15 @@ class EddyCalibration: gcode.register_mux_command("PROBE_EDDY_CURRENT_CALIBRATE", "CHIP", cname, self.cmd_EDDY_CALIBRATE, desc=self.cmd_EDDY_CALIBRATE_help) - def is_calibrated(self): - return len(self.cal_freqs) > 2 + gcode.register_command('Z_OFFSET_APPLY_PROBE', + self.cmd_Z_OFFSET_APPLY_PROBE, + desc=self.cmd_Z_OFFSET_APPLY_PROBE_help) + def get_printer(self): + return self.printer + def verify_calibrated(self): + if len(self.cal_freqs) <= 2: + raise self.printer.command_error( + "Must calibrate probe_eddy_current first") def load_calibration(self, cal): cal = sorted([(c[1], c[0]) for c in cal]) self.cal_freqs = [c[0] for c in cal] @@ -134,21 +141,80 @@ class EddyCalibration: raise self.printer.command_error( "Failed calibration - incomplete sensor data") return cal + + def _median(self, values): + values = sorted(values) + n = len(values) + if n % 2 == 0: + return (values[n//2 - 1] + values[n//2]) / 2.0 + return values[n // 2] def calc_freqs(self, meas): - total_count = total_variance = 0 positions = {} for pos, freqs in meas.items(): count = len(freqs) freq_avg = float(sum(freqs)) / count - positions[pos] = freq_avg - total_count += count - total_variance += sum([(f - freq_avg)**2 for f in freqs]) - return positions, math.sqrt(total_variance / total_count), total_count - def post_manual_probe(self, kin_pos): - if kin_pos is None: + mads = [abs(f - freq_avg) for f in freqs] + mad = self._median(mads) + positions[pos] = (freq_avg, mad, count) + return positions + def validate_calibration_data(self, positions): + last_freq = 40000000. + last_pos = last_mad = .0 + gcode = self.printer.lookup_object("gcode") + filtered = [] + mad_hz_total = .0 + mad_mm_total = .0 + samples_count = 0 + for pos, (freq_avg, mad_hz, count) in sorted(positions.items()): + if freq_avg > last_freq: + gcode.respond_info( + "Frequency stops decreasing at step %.3f" % (pos)) + break + diff_mad = math.sqrt(last_mad**2 + mad_hz**2) + # Calculate if samples have a significant difference + freq_diff = last_freq - freq_avg + last_freq = freq_avg + if freq_diff < 2.5 * diff_mad: + gcode.respond_info( + "Frequency too noisy at step %.3f -> %.3f" % ( + last_pos, pos)) + gcode.respond_info( + "Frequency diff: %.3f, MAD_Hz: %.3f -> MAD_Hz: %.3f" % ( + freq_diff, last_mad, mad_hz + )) + break + last_mad = mad_hz + delta_dist = pos - last_pos + last_pos = pos + # MAD is Median Absolute Deviation to Frequency avg ~ delta_hz_1 + # Signal is delta_hz_2 / delta_dist + # SNR ~= delta_hz_1 / (delta_hz_2 / delta_mm) = d_1 * d_mm / d_2 + mad_mm = mad_hz * delta_dist / freq_diff + filtered.append((pos, freq_avg, mad_hz, mad_mm)) + mad_hz_total += mad_hz + mad_mm_total += mad_mm + samples_count += count + avg_mad = mad_hz_total / len(filtered) + avg_mad_mm = mad_mm_total / len(filtered) + gcode.respond_info( + "probe_eddy_current: noise %.6fmm, MAD_Hz=%.3f in %d queries\n" % ( + avg_mad_mm, avg_mad, samples_count)) + freq_list = [freq for _, freq, _, _ in filtered] + freq_diff = max(freq_list) - min(freq_list) + gcode.respond_info("Total frequency range: %.3f Hz\n" % (freq_diff)) + points = [0.25, 0.5, 1.0, 2.0, 3.0] + for pos, _, mad_hz, mad_mm in filtered: + if len(points) and points[0] <= pos: + points.pop(0) + msg = "z_offset: %.3f # noise %.6fmm, MAD_Hz=%.3f\n" % ( + pos, mad_mm, mad_hz) + gcode.respond_info(msg) + return filtered + def post_manual_probe(self, mpresult): + if mpresult is None: # Manual Probe was aborted return - curpos = list(kin_pos) + curpos = [mpresult.bed_x, mpresult.bed_y, mpresult.bed_z] move = self.printer.lookup_object('toolhead').manual_move # Move away from the bed probe_calibrate_z = curpos[2] @@ -166,24 +232,30 @@ class EddyCalibration: # Perform calibration movement and capture cal = self.do_calibration_moves(self.probe_speed) # Calculate each sample position average and variance - positions, std, total = self.calc_freqs(cal) - last_freq = 0. - for pos, freq in reversed(sorted(positions.items())): - if freq <= last_freq: - raise self.printer.command_error( - "Failed calibration - frequency not increasing each step") - last_freq = freq + _positions = self.calc_freqs(cal) + # Fix Z position offset + positions = {} + for k in _positions: + v = _positions[k] + k = k - probe_calibrate_z + positions[k] = v + filtered = self.validate_calibration_data(positions) + if len(filtered) <= 8: + raise self.printer.command_error( + "Failed calibration - No usable data") + z_freq_pairs = [(pos, freq) for pos, freq, _, _ in filtered] + self._save_calibration(z_freq_pairs) + def _save_calibration(self, z_freq_pairs): gcode = self.printer.lookup_object("gcode") gcode.respond_info( - "probe_eddy_current: stddev=%.3f in %d queries\n" "The SAVE_CONFIG command will update the printer config file\n" - "and restart the printer." % (std, total)) + "and restart the printer.") # Save results cal_contents = [] - for i, (pos, freq) in enumerate(sorted(positions.items())): + for i, (pos, freq) in enumerate(z_freq_pairs): if not i % 3: cal_contents.append('\n') - cal_contents.append("%.6f:%.3f" % (pos - probe_calibrate_z, freq)) + cal_contents.append("%.6f:%.3f" % (pos, freq)) cal_contents.append(',') cal_contents.pop() configfile = self.printer.lookup_object('configfile') @@ -194,54 +266,49 @@ class EddyCalibration: # Start manual probe manual_probe.ManualProbeHelper(self.printer, gcmd, self.post_manual_probe) + cmd_Z_OFFSET_APPLY_PROBE_help = "Adjust the probe's z_offset" + def cmd_Z_OFFSET_APPLY_PROBE(self, gcmd): + gcode_move = self.printer.lookup_object("gcode_move") + offset = gcode_move.get_status()['homing_origin'].z + if offset == 0: + gcmd.respond_info("Nothing to do: Z Offset is 0") + return + cal_zpos = [z - offset for z in self.cal_zpos] + z_freq_pairs = zip(cal_zpos, self.cal_freqs) + z_freq_pairs = sorted(z_freq_pairs) + self._save_calibration(z_freq_pairs) def register_drift_compensation(self, comp): self.drift_comp = comp # Tool to gather samples and convert them to probe positions class EddyGatherSamples: - def __init__(self, printer, sensor_helper, calibration, z_offset): + def __init__(self, printer, sensor_helper): self._printer = printer self._sensor_helper = sensor_helper - self._calibration = calibration - self._z_offset = z_offset - # Results storage - self._samples = [] - self._probe_times = [] - self._probe_results = [] + # Sensor reading + self._sensor_messages = [] self._need_stop = False + # Probe request and results storage + self._probe_requests = [] + self._analysis_results = [] # Start samples - if not self._calibration.is_calibrated(): - raise self._printer.command_error( - "Must calibrate probe_eddy_current first") - sensor_helper.add_client(self._add_measurement) - def _add_measurement(self, msg): + sensor_helper.add_client(self._add_sensor_message) + # Sensor reading and measurement extraction + def _add_sensor_message(self, msg): if self._need_stop: - del self._samples[:] + del self._sensor_messages[:] return False - self._samples.append(msg) - self._check_samples() + self._sensor_messages.append(msg) + self._check_sensor_messages() return True def finish(self): self._need_stop = True - def _await_samples(self): - # Make sure enough samples have been collected - reactor = self._printer.get_reactor() - mcu = self._sensor_helper.get_mcu() - while self._probe_times: - start_time, end_time, pos_time, toolhead_pos = self._probe_times[0] - systime = reactor.monotonic() - est_print_time = mcu.estimated_print_time(systime) - if est_print_time > end_time + 1.0: - raise self._printer.command_error( - "probe_eddy_current sensor outage") - reactor.pause(systime + 0.010) - def _pull_freq(self, start_time, end_time): - # Find average sensor frequency between time range + def _pull_measurements(self, start_time, end_time): + # Extract measurements from sensor messages for given time range + measures = [] msg_num = discard_msgs = 0 - samp_sum = 0. - samp_count = 0 - while msg_num < len(self._samples): - msg = self._samples[msg_num] + while msg_num < len(self._sensor_messages): + msg = self._sensor_messages[msg_num] msg_num += 1 data = msg['data'] if data[0][0] > end_time: @@ -249,104 +316,107 @@ class EddyGatherSamples: if data[-1][0] < start_time: discard_msgs = msg_num continue - for time, freq, z in data: - if time >= start_time and time <= end_time: - samp_sum += freq - samp_count += 1 - del self._samples[:discard_msgs] - if not samp_count: - # No sensor readings - raise error in pull_probed() - return 0. - return samp_sum / samp_count - def _lookup_toolhead_pos(self, pos_time): - toolhead = self._printer.lookup_object('toolhead') - kin = toolhead.get_kinematics() - kin_spos = {s.get_name(): s.mcu_to_commanded_position( - s.get_past_mcu_position(pos_time)) - for s in kin.get_steppers()} - return kin.calc_position(kin_spos) - def _check_samples(self): - while self._samples and self._probe_times: - start_time, end_time, pos_time, toolhead_pos = self._probe_times[0] - if self._samples[-1]['data'][-1][0] < end_time: + for measure in data: + time = measure[0] + if time < start_time: + continue + if time > end_time: + break + measures.append(measure) + del self._sensor_messages[:discard_msgs] + return measures + def _check_sensor_messages(self): + while self._sensor_messages and self._probe_requests: + cb, start_time, end_time, args = self._probe_requests[0] + if self._sensor_messages[-1]['data'][-1][0] < end_time: break - freq = self._pull_freq(start_time, end_time) - if pos_time is not None: - toolhead_pos = self._lookup_toolhead_pos(pos_time) - sensor_z = None - if freq: - sensor_z = self._calibration.freq_to_height(freq) - self._probe_results.append((sensor_z, toolhead_pos)) - self._probe_times.pop(0) + measures = self._pull_measurements(start_time, end_time) + errmsg = res = None + try: + # Call analysis callback to process measurements + res = cb(measures, *args) + except self._printer.command_error as e: + # Defer raising of errors to pull_probed() + errmsg = str(e) + self._analysis_results.append((res, errmsg)) + self._probe_requests.pop(0) + def add_probe_request(self, cb, start_time, end_time, *args): + self._probe_requests.append((cb, start_time, end_time, args)) + self._check_sensor_messages() + # Extract probe results + def _await_sensor_messages(self): + # Make sure enough samples have been collected + reactor = self._printer.get_reactor() + mcu = self._sensor_helper.get_mcu() + while self._probe_requests: + cb, start_time, end_time, args = self._probe_requests[0] + systime = reactor.monotonic() + est_print_time = mcu.estimated_print_time(systime) + if est_print_time > end_time + 1.0: + raise self._printer.command_error( + "probe_eddy_current sensor outage") + if mcu.is_fileoutput(): + # In debugging mode - just create dummy response + dummy_pr = manual_probe.ProbeResult(0., 0., 0., 0., 0., 0.) + self._analysis_results.append((dummy_pr, None)) + self._probe_requests.pop(0) + continue + reactor.pause(systime + 0.010) def pull_probed(self): - self._await_samples() + self._await_sensor_messages() results = [] - for sensor_z, toolhead_pos in self._probe_results: - if sensor_z is None: - raise self._printer.command_error( - "Unable to obtain probe_eddy_current sensor readings") - if sensor_z <= -OUT_OF_RANGE or sensor_z >= OUT_OF_RANGE: - raise self._printer.command_error( - "probe_eddy_current sensor not in valid range") - # Callers expect position relative to z_offset, so recalculate - bed_deviation = toolhead_pos[2] - sensor_z - toolhead_pos[2] = self._z_offset + bed_deviation - results.append(toolhead_pos) - del self._probe_results[:] + for res, errmsg in self._analysis_results: + if errmsg is not None: + raise self._printer.command_error(errmsg) + results.append(res) + del self._analysis_results[:] return results - def note_probe(self, start_time, end_time, toolhead_pos): - self._probe_times.append((start_time, end_time, None, toolhead_pos)) - self._check_samples() - def note_probe_and_position(self, start_time, end_time, pos_time): - self._probe_times.append((start_time, end_time, pos_time, None)) - self._check_samples() + +# Generate a ProbeResult from the average of a set of measurements +def probe_results_from_avg(measures, toolhead_pos, calibration, offsets): + cmderr = calibration.get_printer().command_error + if not measures: + raise cmderr("Unable to obtain probe_eddy_current sensor readings") + # Determine average of measurements + freq_sum = sum([m[1] for m in measures]) + freq_avg = freq_sum / len(measures) + # Determine height associated with frequency + sensor_z = calibration.freq_to_height(freq_avg) + if sensor_z <= -OUT_OF_RANGE or sensor_z >= OUT_OF_RANGE: + raise cmderr("probe_eddy_current sensor not in valid range") + return manual_probe.ProbeResult( + toolhead_pos[0] + offsets[0], toolhead_pos[1] + offsets[1], + toolhead_pos[2] - sensor_z, + toolhead_pos[0], toolhead_pos[1], toolhead_pos[2]) + +MAX_VALID_RAW_VALUE=0x03ffffff # Helper for implementing PROBE style commands (descend until trigger) class EddyDescend: - REASON_SENSOR_ERROR = mcu.MCU_trsync.REASON_COMMS_TIMEOUT + 1 - def __init__(self, config, sensor_helper, calibration, param_helper): + def __init__(self, config, sensor_helper, calibration, + probe_offsets, param_helper, trigger_analog): self._printer = config.get_printer() self._sensor_helper = sensor_helper - self._mcu = sensor_helper.get_mcu() self._calibration = calibration + self._probe_offsets = probe_offsets self._param_helper = param_helper + self._trigger_analog = trigger_analog self._z_min_position = probe.lookup_minimum_z(config) - self._z_offset = config.getfloat('z_offset', minval=0.) - self._dispatch = mcu.TriggerDispatch(self._mcu) - self._trigger_time = 0. self._gather = None - probe.LookupZSteppers(config, self._dispatch.add_stepper) - # Interface for phoming.probing_move() - def get_steppers(self): - return self._dispatch.get_steppers() - def home_start(self, print_time, sample_time, sample_count, rest_time, - triggered=True): - self._trigger_time = 0. - trigger_freq = self._calibration.height_to_freq(self._z_offset) - trigger_completion = self._dispatch.start(print_time) - self._sensor_helper.setup_home( - print_time, trigger_freq, self._dispatch.get_oid(), - mcu.MCU_trsync.REASON_ENDSTOP_HIT, self.REASON_SENSOR_ERROR) - return trigger_completion - def home_wait(self, home_end_time): - self._dispatch.wait_end(home_end_time) - trigger_time = self._sensor_helper.clear_home() - res = self._dispatch.stop() - if res >= mcu.MCU_trsync.REASON_COMMS_TIMEOUT: - if res == mcu.MCU_trsync.REASON_COMMS_TIMEOUT: - raise self._printer.command_error( - "Communication timeout during homing") - raise self._printer.command_error("Eddy current sensor error") - if res != mcu.MCU_trsync.REASON_ENDSTOP_HIT: - return 0. - if self._mcu.is_fileoutput(): - return home_end_time - self._trigger_time = trigger_time - return trigger_time + def _prep_trigger_analog(self): + sos_filter = self._trigger_analog.get_sos_filter() + sos_filter.set_filter_design(None) + sos_filter.set_offset_scale(0, 1.) + self._trigger_analog.set_raw_range(0, MAX_VALID_RAW_VALUE) + z_offset = self._probe_offsets.get_offsets()[2] + trigger_freq = self._calibration.height_to_freq(z_offset) + conv_freq = self._sensor_helper.convert_frequency(trigger_freq) + self._trigger_analog.set_trigger('gt', conv_freq) # Probe session interface def start_probe_session(self, gcmd): - self._gather = EddyGatherSamples(self._printer, self._sensor_helper, - self._calibration, self._z_offset) + self._calibration.verify_calibrated() + self._prep_trigger_analog() + self._gather = EddyGatherSamples(self._printer, self._sensor_helper) return self def run_probe(self, gcmd): toolhead = self._printer.lookup_object('toolhead') @@ -355,14 +425,15 @@ class EddyDescend: speed = self._param_helper.get_probe_params(gcmd)['probe_speed'] # Perform probing move phoming = self._printer.lookup_object('homing') - trig_pos = phoming.probing_move(self, pos, speed) - if not self._trigger_time: - return trig_pos + trig_pos = phoming.probing_move(self._trigger_analog, pos, speed) # Extract samples - start_time = self._trigger_time + 0.050 + start_time = self._trigger_analog.get_last_trigger_time() + 0.050 end_time = start_time + 0.100 toolhead_pos = toolhead.get_position() - self._gather.note_probe(start_time, end_time, toolhead_pos) + offsets = self._probe_offsets.get_offsets() + self._gather.add_probe_request(probe_results_from_avg, + start_time, end_time, + toolhead_pos, self._calibration, offsets) def pull_probed_results(self): return self._gather.pull_probed() def end_probe_session(self): @@ -382,13 +453,13 @@ class EddyEndstopWrapper: def add_stepper(self, stepper): pass def get_steppers(self): - return self._eddy_descend.get_steppers() + return self._eddy_descend._trigger_analog.get_steppers() def home_start(self, print_time, sample_time, sample_count, rest_time, triggered=True): - return self._eddy_descend.home_start( + return self._eddy_descend._trigger_analog.home_start( print_time, sample_time, sample_count, rest_time, triggered) def home_wait(self, home_end_time): - return self._eddy_descend.home_wait(home_end_time) + return self._eddy_descend._trigger_analog.home_wait(home_end_time) def query_endstop(self, print_time): return False # XXX # Interface for HomingViaProbeHelper @@ -402,24 +473,165 @@ class EddyEndstopWrapper: def probe_finish(self, hmove): pass def get_position_endstop(self): - return self._eddy_descend._z_offset + z_offset = self._eddy_descend._probe_offsets.get_offsets()[2] + return z_offset + +# Probing helper for "tap" requests +class EddyTap: + def __init__(self, config, sensor_helper, param_helper, trigger_analog): + self._printer = config.get_printer() + self._sensor_helper = sensor_helper + self._param_helper = param_helper + self._trigger_analog = trigger_analog + self._z_min_position = probe.lookup_minimum_z(config) + self._gather = None + self._filter_design = None + self._tap_threshold = config.getfloat('tap_threshold', 0., minval=0.) + if self._tap_threshold: + self._setup_tap() + # Setup for "tap" probe request + def _setup_tap(self): + # Create sos filter "design" + cfg_error = self._printer.config_error + sps = self._sensor_helper.get_samples_per_second() + design = trigger_analog.DigitalFilter(sps, cfg_error, + lowpass=25.0, lowpass_order=4) + # Create the derivative (sample to sample difference) post filter + self._filter_design = trigger_analog.DerivativeFilter(design) + # Create SOS filter + cmd_queue = self._trigger_analog.get_dispatch().get_command_queue() + mcu = self._sensor_helper.get_mcu() + sos_filter = trigger_analog.MCU_SosFilter(mcu, cmd_queue, 5) + self._trigger_analog.setup_sos_filter(sos_filter) + def _prep_trigger_analog_tap(self): + if not self._tap_threshold: + raise self._printer.command_error("Tap not configured") + sos_filter = self._trigger_analog.get_sos_filter() + sos_filter.set_filter_design(self._filter_design) + sos_filter.set_offset_scale(0, 1., auto_offset=True) + self._trigger_analog.set_raw_range(0, MAX_VALID_RAW_VALUE) + convert_frequency = self._sensor_helper.convert_frequency + raw_threshold = convert_frequency(self._tap_threshold) + self._trigger_analog.set_trigger('diff_peak_gt', raw_threshold) + # Measurement analysis to determine "tap" position + def central_diff(self, times, values): + velocity = [0.0] * len(values) + for i in range(1, len(values) - 1): + delta_v = (values[i+1] - values[i-1]) + delta_t = (times[i+1] - times[i-1]) + velocity[i] = delta_v / delta_t + velocity[0] = (values[1] - values[0]) / (times[1] - times[0]) + velocity[-1] = (values[-1] - values[-2]) / (times[-1] - times[-2]) + return velocity + def validate_samples_time(self, timestamps): + sps = self._sensor_helper.get_samples_per_second() + cycle_time = 1.0 / sps + SYNC_SLACK = 0.001 + for i in range(1, len(timestamps)): + tdiff = timestamps[i] - timestamps[i-1] + if cycle_time + SYNC_SLACK < tdiff: + logging.error("Eddy: Gaps in the data: %.3f < %.3f" % ( + (cycle_time + SYNC_SLACK, tdiff) + )) + break + if cycle_time - SYNC_SLACK > tdiff: + logging.error( + "Eddy: CLKIN frequency too low: %.3f > %.3f" % ( + (cycle_time - SYNC_SLACK, tdiff) + )) + break + def _pull_tap_time(self, measures): + tap_time = [] + tap_value = [] + for time, freq, z in measures: + tap_time.append(time) + tap_value.append(freq) + # If samples have gaps this will not produce adequate data + self.validate_samples_time(tap_time) + # Do the same filtering as on the MCU but without induced lag + main_design = self._filter_design.get_main_filter() + try: + fvals = main_design.filtfilt(tap_value) + except ValueError as e: + raise self._printer.command_error(str(e)) + velocity = self.central_diff(tap_time, fvals) + peak_velocity = max(velocity) + i = velocity.index(peak_velocity) + return tap_time[i] + def _lookup_toolhead_pos(self, pos_time): + toolhead = self._printer.lookup_object('toolhead') + kin = toolhead.get_kinematics() + kin_spos = {s.get_name(): s.mcu_to_commanded_position( + s.get_past_mcu_position(pos_time)) + for s in kin.get_steppers()} + return kin.calc_position(kin_spos) + def _analyze_tap(self, measures): + pos_time = self._pull_tap_time(measures) + trig_pos = self._lookup_toolhead_pos(pos_time) + return manual_probe.ProbeResult(trig_pos[0], trig_pos[1], trig_pos[2], + trig_pos[0], trig_pos[1], trig_pos[2]) + # Probe session interface + def start_probe_session(self, gcmd): + self._prep_trigger_analog_tap() + self._gather = EddyGatherSamples(self._printer, self._sensor_helper) + return self + def run_probe(self, gcmd): + toolhead = self._printer.lookup_object('toolhead') + pos = toolhead.get_position() + pos[2] = self._z_min_position + speed = self._param_helper.get_probe_params(gcmd)['probe_speed'] + move_start_time = toolhead.get_last_move_time() + # Perform probing move + phoming = self._printer.lookup_object('homing') + trig_pos = phoming.probing_move(self._trigger_analog, pos, speed) + # Extract samples + trigger_time = self._trigger_analog.get_last_trigger_time() + start_time = trigger_time - 0.250 + if start_time < move_start_time: + # Filter short move + start_time = move_start_time + end_time = trigger_time + self._gather.add_probe_request(self._analyze_tap, start_time, end_time) + def pull_probed_results(self): + return self._gather.pull_probed() + def end_probe_session(self): + self._gather.finish() + self._gather = None # Implementing probing with "METHOD=scan" class EddyScanningProbe: - def __init__(self, printer, sensor_helper, calibration, z_offset, gcmd): - self._printer = printer + def __init__(self, config, sensor_helper, calibration, probe_offsets): + self._printer = config.get_printer() self._sensor_helper = sensor_helper self._calibration = calibration - self._z_offset = z_offset - self._gather = EddyGatherSamples(printer, sensor_helper, - calibration, z_offset) + self._offsets = probe_offsets.get_offsets() + self._gather = None self._sample_time_delay = 0.050 - self._sample_time = gcmd.get_float("SAMPLE_TIME", 0.100, above=0.0) - self._is_rapid = gcmd.get("METHOD", "scan") == 'rapid_scan' + self._sample_time = 0. + self._is_rapid = False + def _lookup_toolhead_pos(self, pos_time): + toolhead = self._printer.lookup_object('toolhead') + kin = toolhead.get_kinematics() + kin_spos = {s.get_name(): s.mcu_to_commanded_position( + s.get_past_mcu_position(pos_time)) + for s in kin.get_steppers()} + return kin.calc_position(kin_spos) + def _analyze_scan(self, measures, pos_time): + toolhead_pos = self._lookup_toolhead_pos(pos_time) + return probe_results_from_avg(measures, toolhead_pos, + self._calibration, self._offsets) def _rapid_lookahead_cb(self, printtime): start_time = printtime - self._sample_time / 2 - self._gather.note_probe_and_position( - start_time, start_time + self._sample_time, printtime) + end_time = start_time + self._sample_time + self._gather.add_probe_request(self._analyze_scan, start_time, end_time, + printtime) + # Probe session interface + def start_probe_session(self, gcmd): + self._calibration.verify_calibrated() + self._gather = EddyGatherSamples(self._printer, self._sensor_helper) + self._sample_time = gcmd.get_float("SAMPLE_TIME", 0.100, above=0.0) + self._is_rapid = gcmd.get("METHOD", "scan") == 'rapid_scan' + return self def run_probe(self, gcmd): toolhead = self._printer.lookup_object("toolhead") if self._is_rapid: @@ -428,8 +640,9 @@ class EddyScanningProbe: printtime = toolhead.get_last_move_time() toolhead.dwell(self._sample_time_delay + self._sample_time) start_time = printtime + self._sample_time_delay - self._gather.note_probe_and_position( - start_time, start_time + self._sample_time, start_time) + end_time = start_time + self._sample_time + self._gather.add_probe_request(self._analyze_scan, start_time, end_time, + start_time) def pull_probed_results(self): if self._is_rapid: # Flush lookahead (so all lookahead callbacks are invoked) @@ -438,7 +651,7 @@ class EddyScanningProbe: results = self._gather.pull_probed() # Allow axis_twist_compensation to update results for epos in results: - self._printer.send_event("probe:update_results", epos) + self._printer.send_event("probe:update_results", [epos]) return results def end_probe_session(self): self._gather.finish() @@ -453,31 +666,51 @@ class PrinterEddyProbe: sensors = { "ldc1612": ldc1612.LDC1612 } sensor_type = config.getchoice('sensor_type', {s: s for s in sensors}) self.sensor_helper = sensors[sensor_type](config, self.calibration) - # Probe interface + # Create trigger_analog interface + trig_analog = trigger_analog.MCU_trigger_analog(self.sensor_helper) + probe.LookupZSteppers(config, trig_analog.get_dispatch().add_stepper) + # Basic probe requests + self.probe_offsets = probe.ProbeOffsetsHelper(config) self.param_helper = probe.ProbeParameterHelper(config) self.eddy_descend = EddyDescend( - config, self.sensor_helper, self.calibration, self.param_helper) - self.cmd_helper = probe.ProbeCommandHelper(config, self) - self.probe_offsets = probe.ProbeOffsetsHelper(config) - self.probe_session = probe.ProbeSessionHelper( - config, self.param_helper, self.eddy_descend.start_probe_session) + config, self.sensor_helper, self.calibration, self.probe_offsets, + self.param_helper, trig_analog) + # Create wrapper to support Z homing with probe mcu_probe = EddyEndstopWrapper(self.sensor_helper, self.eddy_descend) - probe.HomingViaProbeHelper(config, mcu_probe, self.param_helper) + probe.HomingViaProbeHelper(config, mcu_probe, + self.probe_offsets, self.param_helper) + # Probing via "tap" interface + self.eddy_tap = EddyTap(config, self.sensor_helper, + self.param_helper, trig_analog) + # Probing via "scan" and "rapid_scan" requests + self.eddy_scan = EddyScanningProbe(config, self.sensor_helper, + self.calibration, self.probe_offsets) + # Register with main probe interface + self.cmd_helper = probe.ProbeCommandHelper(config, self, + can_set_z_offset=False) + self.probe_session = probe.ProbeSessionHelper( + config, self.param_helper, self._start_descend_wrapper) self.printer.add_object('probe', self) def add_client(self, cb): self.sensor_helper.add_client(cb) def get_probe_params(self, gcmd=None): return self.param_helper.get_probe_params(gcmd) - def get_offsets(self): - return self.probe_offsets.get_offsets() + def get_offsets(self, gcmd=None): + if gcmd is not None and gcmd.get('METHOD', '').lower() == "tap": + return (0., 0., 0.) + return self.probe_offsets.get_offsets(gcmd) def get_status(self, eventtime): return self.cmd_helper.get_status(eventtime) + def _start_descend_wrapper(self, gcmd): + method = gcmd.get('METHOD', 'automatic').lower() + if method == "tap": + return self.eddy_tap.start_probe_session(gcmd) + return self.eddy_descend.start_probe_session(gcmd) def start_probe_session(self, gcmd): method = gcmd.get('METHOD', 'automatic').lower() if method in ('scan', 'rapid_scan'): - z_offset = self.get_offsets()[2] - return EddyScanningProbe(self.printer, self.sensor_helper, - self.calibration, z_offset, gcmd) + return self.eddy_scan.start_probe_session(gcmd) + # For "tap" and normal, probe_session can average multiple attempts return self.probe_session.start_probe_session(gcmd) def register_drift_compensation(self, comp): self.calibration.register_drift_compensation(comp) diff --git a/klippy/extras/quad_gantry_level.py b/klippy/extras/quad_gantry_level.py index 98cd53c5a..5556d5e1d 100644 --- a/klippy/extras/quad_gantry_level.py +++ b/klippy/extras/quad_gantry_level.py @@ -51,34 +51,34 @@ class QuadGantryLevel: self.z_status.reset() self.retry_helper.start(gcmd) self.probe_helper.start_probe(gcmd) - def probe_finalize(self, offsets, positions): + def probe_finalize(self, positions): # Mirror our perspective so the adjustments make sense # from the perspective of the gantry - z_positions = [self.horizontal_move_z - p[2] for p in positions] + z_positions = [self.horizontal_move_z - p.bed_z for p in positions] points_message = "Gantry-relative probe points:\n%s\n" % ( " ".join(["%s: %.6f" % (z_id, z_positions[z_id]) for z_id in range(len(z_positions))])) self.gcode.respond_info(points_message) # Calculate slope along X axis between probe point 0 and 3 - ppx0 = [positions[0][0] + offsets[0], z_positions[0]] - ppx3 = [positions[3][0] + offsets[0], z_positions[3]] + ppx0 = [positions[0].bed_x, z_positions[0]] + ppx3 = [positions[3].bed_x, z_positions[3]] slope_x_pp03 = self.linefit(ppx0, ppx3) # Calculate slope along X axis between probe point 1 and 2 - ppx1 = [positions[1][0] + offsets[0], z_positions[1]] - ppx2 = [positions[2][0] + offsets[0], z_positions[2]] + ppx1 = [positions[1].bed_x, z_positions[1]] + ppx2 = [positions[2].bed_x, z_positions[2]] slope_x_pp12 = self.linefit(ppx1, ppx2) logging.info("quad_gantry_level f1: %s, f2: %s" % (slope_x_pp03, slope_x_pp12)) # Calculate gantry slope along Y axis between stepper 0 and 1 - a1 = [positions[0][1] + offsets[1], + a1 = [positions[0].bed_y, self.plot(slope_x_pp03, self.gantry_corners[0][0])] - a2 = [positions[1][1] + offsets[1], + a2 = [positions[1].bed_y, self.plot(slope_x_pp12, self.gantry_corners[0][0])] slope_y_s01 = self.linefit(a1, a2) # Calculate gantry slope along Y axis between stepper 2 and 3 - b1 = [positions[0][1] + offsets[1], + b1 = [positions[0].bed_y, self.plot(slope_x_pp03, self.gantry_corners[1][0])] - b2 = [positions[1][1] + offsets[1], + b2 = [positions[1].bed_y, self.plot(slope_x_pp12, self.gantry_corners[1][0])] slope_y_s23 = self.linefit(b1, b2) logging.info("quad_gantry_level af: %s, bf: %s" diff --git a/klippy/extras/replicape.py b/klippy/extras/replicape.py index f7f7bb64b..eaca8b83d 100644 --- a/klippy/extras/replicape.py +++ b/klippy/extras/replicape.py @@ -67,6 +67,8 @@ class pca9685_pwm: cmd_queue = self._mcu.alloc_command_queue() self._set_cmd = self._mcu.lookup_command( "queue_pca9685_out oid=%c clock=%u value=%hu", cq=cmd_queue) + def next_aligned_print_time(self, print_time, allow_early=0.): + return print_time def set_pwm(self, print_time, value): clock = self._mcu.print_time_to_clock(print_time) if self._invert: diff --git a/klippy/extras/screws_tilt_adjust.py b/klippy/extras/screws_tilt_adjust.py index b988c7cef..b3327ba49 100644 --- a/klippy/extras/screws_tilt_adjust.py +++ b/klippy/extras/screws_tilt_adjust.py @@ -63,7 +63,7 @@ class ScrewsTiltAdjust: 'max_deviation': self.max_diff, 'results': self.results} - def probe_finalize(self, offsets, positions): + def probe_finalize(self, positions): self.results = {} self.max_diff_error = False # Factors used for CW-M3, CCW-M3, CW-M4, CCW-M4, CW-M5, CCW-M5, CW-M6 @@ -79,15 +79,15 @@ class ScrewsTiltAdjust: or (not is_clockwise_thread and self.direction == 'CCW')) min_or_max = max if use_max else min i_base, z_base = min_or_max( - enumerate([pos[2] for pos in positions]), key=lambda v: v[1]) + enumerate([pos.bed_z for pos in positions]), key=lambda v: v[1]) else: # First screw is the base position used for comparison - i_base, z_base = 0, positions[0][2] + i_base, z_base = 0, positions[0].bed_z # Provide the user some information on how to read the results self.gcode.respond_info("01:20 means 1 full turn and 20 minutes, " "CW=clockwise, CCW=counter-clockwise") for i, screw in enumerate(self.screws): - z = positions[i][2] + z = positions[i].bed_z coord, name = screw if i == i_base: # Show the results diff --git a/klippy/extras/servo.py b/klippy/extras/servo.py index f1ce99763..303e39377 100644 --- a/klippy/extras/servo.py +++ b/klippy/extras/servo.py @@ -6,6 +6,7 @@ from . import output_pin SERVO_SIGNAL_PERIOD = 0.020 +RESCHEDULE_SLACK = 0.000500 class PrinterServo: def __init__(self, config): @@ -47,8 +48,12 @@ class PrinterServo: def _set_pwm(self, print_time, value): if value == self.last_value: return "discard", 0. + aligned_ptime = self.mcu_servo.next_aligned_print_time(print_time, + RESCHEDULE_SLACK) + if aligned_ptime > print_time + RESCHEDULE_SLACK: + return "reschedule", aligned_ptime self.last_value = value - self.mcu_servo.set_pwm(print_time, value) + self.mcu_servo.set_pwm(aligned_ptime, value) def _get_pwm_from_angle(self, angle): angle = max(0., min(self.max_angle, angle)) width = self.min_width + angle * self.angle_to_width diff --git a/klippy/extras/smart_effector.py b/klippy/extras/smart_effector.py index 3caa4e6b7..3422dc247 100644 --- a/klippy/extras/smart_effector.py +++ b/klippy/extras/smart_effector.py @@ -70,8 +70,8 @@ class SmartEffectorProbe: config, self, self.probe_wrapper.query_endstop) self.probe_offsets = probe.ProbeOffsetsHelper(config) self.param_helper = probe.ProbeParameterHelper(config) - self.homing_helper = probe.HomingViaProbeHelper(config, self, - self.param_helper) + self.homing_helper = probe.HomingViaProbeHelper( + config, self, self.probe_offsets, self.param_helper) self.probe_session = probe.ProbeSessionHelper( config, self.param_helper, self.homing_helper.start_probe_session) # SmartEffector control @@ -90,8 +90,8 @@ class SmartEffectorProbe: desc=self.cmd_SET_SMART_EFFECTOR_help) def get_probe_params(self, gcmd=None): return self.param_helper.get_probe_params(gcmd) - def get_offsets(self): - return self.probe_offsets.get_offsets() + def get_offsets(self, gcmd=None): + return self.probe_offsets.get_offsets(gcmd) def get_status(self, eventtime): return self.cmd_helper.get_status(eventtime) def start_probe_session(self, gcmd): diff --git a/klippy/extras/sos_filter.py b/klippy/extras/sos_filter.py deleted file mode 100644 index cbd6c51c9..000000000 --- a/klippy/extras/sos_filter.py +++ /dev/null @@ -1,234 +0,0 @@ -# Second Order Sections Filter -# -# Copyright (C) 2025 Gareth Farrington -# -# This file may be distributed under the terms of the GNU GPLv3 license. - -MAX_INT32 = (2 ** 31) -MIN_INT32 = -(2 ** 31) - 1 -def assert_is_int32(value, error): - if value > MAX_INT32 or value < MIN_INT32: - raise OverflowError(error) - return value - -# convert a floating point value to a 32 bit fixed point representation -# checks for overflow -def to_fixed_32(value, int_bits): - fractional_bits = (32 - (1 + int_bits)) - fixed_val = int(value * (2 ** fractional_bits)) - return assert_is_int32(fixed_val, "Fixed point Q%i overflow" - % (int_bits,)) - - -# Digital filter designer and container -class DigitalFilter: - def __init__(self, sps, cfg_error, highpass=None, highpass_order=1, - lowpass=None, lowpass_order=1, notches=None, notch_quality=2.0): - self.filter_sections = [] - self.initial_state = [] - self.sample_frequency = sps - # an empty filter can be created without SciPi/numpy - if not (highpass or lowpass or notches): - return - try: - import scipy.signal as signal - except: - raise cfg_error("DigitalFilter require the SciPy module") - if highpass: - self.filter_sections.extend( - self._butter(highpass, "highpass", highpass_order)) - if lowpass: - self.filter_sections.extend( - self._butter(lowpass, "lowpass", lowpass_order)) - if notches is None: - notches = [] - for notch_freq in notches: - self.filter_sections.append(self._notch(notch_freq, notch_quality)) - if len(self.filter_sections) > 0: - self.initial_state = signal.sosfilt_zi(self.filter_sections) - - def _butter(self, frequency, btype, order): - import scipy.signal as signal - return signal.butter(order, Wn=frequency, btype=btype, - fs=self.sample_frequency, output='sos') - - def _notch(self, freq, quality): - import scipy.signal as signal - b, a = signal.iirnotch(freq, Q=quality, fs=self.sample_frequency) - return signal.tf2sos(b, a)[0] - - def get_filter_sections(self): - return self.filter_sections - - def get_initial_state(self): - return self.initial_state - -# container that accepts SciPy formatted SOS filter data and converts it to a -# selected fixed point representation. This data could come from DigitalFilter, -# static data, config etc. -class FixedPointSosFilter: - # filter_sections is an array of SciPy formatted SOS filter sections (sos) - # initial_state is an array of SciPy formatted SOS state sections (zi) - def __init__(self, filter_sections=None, initial_state=None, - coeff_int_bits=2, value_int_bits=15): - filter_sections = [] if filter_sections is None else filter_sections - initial_state = [] if initial_state is None else initial_state - num_sections = len(filter_sections) - num_state = len(initial_state) - if num_state != num_sections: - raise ValueError("The number of filter sections (%i) and state " - "sections (%i) must be equal" % ( - num_sections, num_state)) - self._coeff_int_bits = self._validate_int_bits(coeff_int_bits) - self._value_int_bits = self._validate_int_bits(value_int_bits) - self._filter = self._convert_filter(filter_sections) - self._state = self._convert_state(initial_state) - - def get_filter_sections(self): - return self._filter - - def get_initial_state(self): - return self._state - - def get_coeff_int_bits(self): - return self._coeff_int_bits - - def get_value_int_bits(self): - return self._value_int_bits - - def get_num_sections(self): - return len(self._filter) - - def _validate_int_bits(self, int_bits): - if int_bits < 1 or int_bits > 30: - raise ValueError("The number of integer bits (%i) must be a" - " value between 1 and 30" % (int_bits,)) - return int_bits - - # convert the SciPi SOS filters to fixed point format - def _convert_filter(self, filter_sections): - sos_fixed = [] - for section in filter_sections: - nun_coeff = len(section) - if nun_coeff != 6: - raise ValueError("The number of filter coefficients is %i" - ", must be 6" % (nun_coeff,)) - fixed_section = [] - for col, coeff in enumerate(section): - if col != 3: # omit column 3 - fixed_coeff = to_fixed_32(coeff, self._coeff_int_bits) - fixed_section.append(fixed_coeff) - elif coeff != 1.0: # double check column 3 is always 1.0 - raise ValueError("Coefficient 3 is expected to be 1.0" - " but was %f" % (coeff,)) - sos_fixed.append(fixed_section) - return sos_fixed - - # convert the SOS filter state matrix (zi) to fixed point format - def _convert_state(self, filter_state): - sos_state = [] - for section in filter_state: - nun_states = len(section) - if nun_states != 2: - raise ValueError( - "The number of state elements is %i, must be 2" - % (nun_states,)) - fixed_state = [] - for col, value in enumerate(section): - fixed_state.append(to_fixed_32(value, self._value_int_bits)) - sos_state.append(fixed_state) - return sos_state - - -# Control an `sos_filter` object on the MCU -class SosFilter: - # fixed_point_filter should be an FixedPointSosFilter instance. A filter of - # size 0 will create a passthrough filter. - # max_sections should be the largest number of sections you expect - # to use at runtime. The default is the size of the fixed_point_filter. - def __init__(self, mcu, cmd_queue, fixed_point_filter, max_sections=None): - self._mcu = mcu - self._cmd_queue = cmd_queue - self._oid = self._mcu.create_oid() - self._filter = fixed_point_filter - self._max_sections = max_sections - if self._max_sections is None: - self._max_sections = self._filter.get_num_sections() - self._cmd_set_section = [ - "sos_filter_set_section oid=%d section_idx=%d" - " sos0=%i sos1=%i sos2=%i sos3=%i sos4=%i", - "sos_filter_set_section oid=%c section_idx=%c" - " sos0=%i sos1=%i sos2=%i sos3=%i sos4=%i"] - self._cmd_config_state = [ - "sos_filter_set_state oid=%d section_idx=%d state0=%i state1=%i", - "sos_filter_set_state oid=%c section_idx=%c state0=%i state1=%i"] - self._cmd_activate = [ - "sos_filter_set_active oid=%d n_sections=%d coeff_int_bits=%d", - "sos_filter_set_active oid=%c n_sections=%c coeff_int_bits=%c"] - self._mcu.register_config_callback(self._build_config) - - def _build_config(self): - cmds = [self._cmd_set_section, self._cmd_config_state, - self._cmd_activate] - for cmd in cmds: - cmd.append(self._mcu.lookup_command(cmd[1], cq=self._cmd_queue)) - - def get_oid(self): - return self._oid - - # create an uninitialized filter object - def create_filter(self): - self._mcu.add_config_cmd("config_sos_filter oid=%d max_sections=%d" - % (self._oid, self._max_sections)) - self._configure_filter(is_init=True) - - # either setup an init command or send the command based on a flag - def _cmd(self, command, args, is_init=False): - if is_init: - self._mcu.add_config_cmd(command[0] % args, is_init=True) - else: - command[2].send(args) - - def _set_filter_sections(self, is_init=False): - for i, section in enumerate(self._filter.get_filter_sections()): - args = (self._oid, i, section[0], section[1], section[2], - section[3], section[4]) - self._cmd(self._cmd_set_section, args, is_init) - - def _set_filter_state(self, is_init=False): - for i, state in enumerate(self._filter.get_initial_state()): - args = (self._oid, i, state[0], state[1]) - self._cmd(self._cmd_config_state, args, is_init) - - def _activate_filter(self, is_init=False): - args = (self._oid, self._filter.get_num_sections(), - self._filter.get_coeff_int_bits()) - self._cmd(self._cmd_activate, args, is_init) - - # configure the filter sections on the mcu - # filters should be an array of filter sections in SciPi SOS format - # sos_filter_state should be an array of zi filter state elements - def _configure_filter(self, is_init=False): - num_sections = self._filter.get_num_sections() - if num_sections > self._max_sections: - raise ValueError("Too many filter sections: %i, The max is %i" - % (num_sections, self._max_sections,)) - # convert to fixed point to find errors - # no errors, state is accepted - # configure MCU filter and activate - self._set_filter_sections(is_init) - self._set_filter_state(is_init,) - self._activate_filter(is_init) - - # Change the filter coefficients and state at runtime - # fixed_point_filter should be an FixedPointSosFilter instance - # cq is an optional command queue to for command sequencing - def change_filter(self, fixed_point_filter): - self._filter = fixed_point_filter - self._configure_filter(False) - - # Resets the filter state back to initial conditions at runtime - # cq is an optional command queue to for command sequencing - def reset_filter(self): - self._set_filter_state(False) - self._activate_filter(False) diff --git a/klippy/extras/static_pwm_clock.py b/klippy/extras/static_pwm_clock.py new file mode 100644 index 000000000..e9c018c00 --- /dev/null +++ b/klippy/extras/static_pwm_clock.py @@ -0,0 +1,41 @@ +# Define GPIO as clock output +# +# Copyright (C) 2025 Timofey Titovets +# +# This file may be distributed under the terms of the GNU GPLv3 license. + +import logging + +class PrinterClockOutputPin: + def __init__(self, config): + self.name = config.get_name() + self.printer = config.get_printer() + ppins = self.printer.lookup_object('pins') + self.mcu_pin = ppins.setup_pin('pwm', config.get('pin')) + self.mcu = self.mcu_pin.get_mcu() + self.frequency = config.getfloat('frequency', 100, above=(1/0.3), + maxval=520000000) + self.mcu_pin.setup_cycle_time(1. / self.frequency, True) + self.mcu_pin.setup_max_duration(0.) + self.mcu_pin.setup_start_value(0.5, 0.5) + self.mcu.register_config_callback(self._build_config) + def _build_config(self): + mcu_freq = self.mcu.seconds_to_clock(1.0) + cycle_ticks = int(mcu_freq // self.frequency) + # validate frequency + mcu_freq_rev = int(cycle_ticks * self.frequency) + if mcu_freq_rev != mcu_freq: + msg = """ + # Frequency output must be without remainder, %i != %i + [%s] + frequency = %f + """ % (mcu_freq, mcu_freq_rev, self.name, self.frequency) + raise self.printer.config_error(msg) + value = int(0.5 * cycle_ticks) + if value/cycle_ticks < 0.4: + logging.warning("[%s] pulse width < 40%%" % (self.name)) + if value/cycle_ticks > 0.6: + logging.warning("[%s] pulse width > 60%%" % (self.name)) + +def load_config_prefix(config): + return PrinterClockOutputPin(config) diff --git a/klippy/extras/sx1509.py b/klippy/extras/sx1509.py index 99df55df3..ce25bd027 100644 --- a/klippy/extras/sx1509.py +++ b/klippy/extras/sx1509.py @@ -178,6 +178,8 @@ class SX1509_pwm(object): self._shutdown_value = max(0., min(1., shutdown_value)) self._sx1509.set_register(self._i_on_reg, ~int(255 * self._start_value) & 0xFF) + def next_aligned_print_time(self, print_time, allow_early=0.): + return print_time def set_pwm(self, print_time, value): self._sx1509.set_register(self._i_on_reg, ~int(255 * value) if not self._invert diff --git a/klippy/extras/temperature_combined.py b/klippy/extras/temperature_combined.py index d032bce03..51468bc9b 100644 --- a/klippy/extras/temperature_combined.py +++ b/klippy/extras/temperature_combined.py @@ -26,6 +26,7 @@ class PrinterSensorCombined: self.apply_mode = config.getchoice('combination_method', algos) # set default values self.last_temp = self.min_temp = self.max_temp = 0.0 + self.humidity = self.pressure = self.gas = None # add object self.printer.add_object("temperature_combined " + self.name, self) # time-controlled sensor update @@ -96,13 +97,56 @@ class PrinterSensorCombined: def get_temp(self, eventtime): return self.last_temp, 0. + def update_additional(self, eventtime): + values_humidity = [] + values_pressure = [] + values_gas = [] + for sensor in self.sensors: + sensor_status = sensor.get_status(eventtime) + if 'humidity' in sensor_status: + sensor_humidity = sensor_status['humidity'] + if sensor_humidity is not None: + values_humidity.append(sensor_humidity) + if 'pressure' in sensor_status: + sensor_pressure = sensor_status['pressure'] + if sensor_pressure is not None: + values_pressure.append(sensor_pressure) + if 'gas' in sensor_status: + sensor_gas = sensor_status['gas'] + if sensor_gas is not None: + values_gas.append(sensor_gas) + + if values_humidity: + humidity = self.apply_mode(values_humidity) + if humidity: + self.humidity = humidity + + if values_pressure: + pressure = self.apply_mode(values_pressure) + if pressure: + self.pressure = pressure + + if values_gas: + gas = self.apply_mode(values_gas) + if gas: + self.gas = gas + def get_status(self, eventtime): - return {'temperature': round(self.last_temp, 2), - } + data = { + 'temperature': round(self.last_temp, 2), + } + if self.humidity is not None: + data['humidity'] = self.humidity + if self.pressure is not None: + data['pressure'] = self.pressure + if self.gas is not None: + data['gas'] = self.gas + return data def _temperature_update_event(self, eventtime): # update sensor value self.update_temp(eventtime) + self.update_additional(eventtime) # check min / max temp values if self.last_temp < self.min_temp: diff --git a/klippy/extras/temperature_probe.py b/klippy/extras/temperature_probe.py index aebb10764..f47c4e848 100644 --- a/klippy/extras/temperature_probe.py +++ b/klippy/extras/temperature_probe.py @@ -185,7 +185,7 @@ class TemperatureProbe: def get_temp(self, eventtime=None): return self.last_measurement[0], self.target_temp - def _collect_sample(self, kin_pos, tool_zero_z): + def _collect_sample(self, mpresult, tool_zero_z): probe = self._get_probe() x_offset, y_offset, _ = probe.get_offsets() speeds = self._get_speeds() @@ -198,7 +198,7 @@ class TemperatureProbe: cur_pos[0] -= x_offset cur_pos[1] -= y_offset toolhead.manual_move(cur_pos, move_speed) - return self.cal_helper.collect_sample(kin_pos, tool_zero_z, speeds) + return self.cal_helper.collect_sample(mpresult, tool_zero_z, speeds) def _prepare_next_sample(self, last_temp, tool_zero_z): # Register our own abort command now that the manual @@ -221,23 +221,23 @@ class TemperatureProbe: % (self.name, cnt, exp_cnt, last_temp, self.next_auto_temp) ) - def _manual_probe_finalize(self, kin_pos): - if kin_pos is None: + def _manual_probe_finalize(self, mpresult): + if mpresult is None: # Calibration aborted self._finalize_drift_cal(False) return if self.last_zero_pos is not None: - z_diff = self.last_zero_pos[2] - kin_pos[2] + z_diff = self.last_zero_pos - mpresult.bed_z self.total_expansion += z_diff logging.info( "Estimated Total Thermal Expansion: %.6f" % (self.total_expansion,) ) - self.last_zero_pos = kin_pos + self.last_zero_pos = mpresult.bed_z toolhead = self.printer.lookup_object("toolhead") tool_zero_z = toolhead.get_position()[2] try: - last_temp = self._collect_sample(kin_pos, tool_zero_z) + last_temp = self._collect_sample(mpresult, tool_zero_z) except Exception: self._finalize_drift_cal(False) raise @@ -562,7 +562,7 @@ class EddyDriftCompensation: % (self.name, self.cal_temp) ) - def collect_sample(self, kin_pos, tool_zero_z, speeds): + def collect_sample(self, mpresult, tool_zero_z, speeds): if self.calibration_samples is None: self.calibration_samples = [[] for _ in range(DRIFT_SAMPLE_COUNT)] move_times = [] @@ -616,7 +616,7 @@ class EddyDriftCompensation: zvals = [d[2] for d in data] avg_freq = sum(freqs) / len(freqs) avg_z = sum(zvals) / len(zvals) - kin_z = i * .5 + .05 + kin_pos[2] + kin_z = i * .5 + .05 + mpresult.bed_z logging.info( "Probe Values at Temp %.2fC, Z %.4fmm: Avg Freq = %.6f, " "Avg Measured Z = %.6f" diff --git a/klippy/extras/tmc.py b/klippy/extras/tmc.py index aad19cf4d..23b05305e 100644 --- a/klippy/extras/tmc.py +++ b/klippy/extras/tmc.py @@ -323,24 +323,29 @@ class TMCCommandHelper: self.name = config.get_name().split()[-1] self.mcu_tmc = mcu_tmc self.current_helper = current_helper + self.fields = mcu_tmc.get_fields() + self.stepper = None + # Stepper phase tracking + self.mcu_phase_offset = None + # Stepper enable/disable tracking + self.toff = None + self.stepper_enable = self.printer.load_object(config, "stepper_enable") + self.enable_mutex = self.printer.get_reactor().mutex() + # DUMP_TMC support + self.read_registers = self.read_translate = None + # Common tmc helpers self.echeck_helper = TMCErrorCheck(config, mcu_tmc) self.record_helper = TMCStallguardDump(config, mcu_tmc) - self.fields = mcu_tmc.get_fields() - self.read_registers = self.read_translate = None - self.toff = None - self.mcu_phase_offset = None - self.stepper = None - self.stepper_enable = self.printer.load_object(config, "stepper_enable") + TMCMicrostepHelper(config, mcu_tmc) + # Register callbacks self.printer.register_event_handler("stepper:sync_mcu_position", self._handle_sync_mcu_pos) - self.printer.register_event_handler("stepper:set_sdir_inverted", + self.printer.register_event_handler("stepper:set_dir_inverted", self._handle_sync_mcu_pos) self.printer.register_event_handler("klippy:mcu_identify", self._handle_mcu_identify) self.printer.register_event_handler("klippy:connect", self._handle_connect) - # Set microstep config options - TMCMicrostepHelper(config, mcu_tmc) # Register commands gcode = self.printer.lookup_object("gcode") gcode.register_mux_command("SET_TMC_FIELD", "STEPPER", self.name, @@ -438,48 +443,48 @@ class TMCCommandHelper: self.mcu_phase_offset = moff # Stepper enable/disable tracking def _do_enable(self, print_time): - try: - if self.toff is not None: - # Shared enable via comms handling - self.fields.set_field("toff", self.toff) - self._init_registers() - did_reset = self.echeck_helper.start_checks() - if did_reset: - self.mcu_phase_offset = None - # Calculate phase offset + if self.toff is not None: + # Shared enable via comms handling + self.fields.set_field("toff", self.toff) + self._init_registers() + did_reset = self.echeck_helper.start_checks() + if did_reset: + self.mcu_phase_offset = None + # Calculate phase offset + if self.mcu_phase_offset is not None: + return + gcode = self.printer.lookup_object("gcode") + with gcode.get_mutex(): if self.mcu_phase_offset is not None: return - gcode = self.printer.lookup_object("gcode") - with gcode.get_mutex(): - if self.mcu_phase_offset is not None: - return - logging.info("Pausing toolhead to calculate %s phase offset", - self.stepper_name) - self.printer.lookup_object('toolhead').wait_moves() - self._handle_sync_mcu_pos(self.stepper) - except self.printer.command_error as e: - self.printer.invoke_shutdown(str(e)) + logging.info("Pausing toolhead to calculate %s phase offset", + self.stepper_name) + self.printer.lookup_object('toolhead').wait_moves() + self._handle_sync_mcu_pos(self.stepper) def _do_disable(self, print_time): - try: - if self.toff is not None: - val = self.fields.set_field("toff", 0) - reg_name = self.fields.lookup_register("toff") - self.mcu_tmc.set_register(reg_name, val, print_time) - self.echeck_helper.stop_checks() - except self.printer.command_error as e: - self.printer.invoke_shutdown(str(e)) + if self.toff is not None: + val = self.fields.set_field("toff", 0) + reg_name = self.fields.lookup_register("toff") + self.mcu_tmc.set_register(reg_name, val, print_time) + self.echeck_helper.stop_checks() + def _handle_stepper_enable(self, print_time, is_enable): + def enable_disable_cb(eventtime): + try: + with self.enable_mutex: + if is_enable: + self._do_enable(print_time) + else: + self._do_disable(print_time) + except self.printer.command_error as e: + self.printer.invoke_shutdown(str(e)) + self.printer.get_reactor().register_callback(enable_disable_cb) + # Initial startup handling def _handle_mcu_identify(self): # Lookup stepper object force_move = self.printer.lookup_object("force_move") self.stepper = force_move.lookup_stepper(self.stepper_name) # Note pulse duration and step_both_edge optimizations available self.stepper.setup_default_pulse_duration(.000000100, True) - def _handle_stepper_enable(self, print_time, is_enable): - if is_enable: - cb = (lambda ev: self._do_enable(print_time)) - else: - cb = (lambda ev: self._do_disable(print_time)) - self.printer.get_reactor().register_callback(cb) def _handle_connect(self): # Check if using step on both edges optimization pulse_duration, step_both_edge = self.stepper.get_pulse_duration() diff --git a/klippy/extras/trigger_analog.py b/klippy/extras/trigger_analog.py new file mode 100644 index 000000000..d38bcd777 --- /dev/null +++ b/klippy/extras/trigger_analog.py @@ -0,0 +1,380 @@ +# Wrapper around mcu trigger_analog objects +# +# Copyright (C) 2025 Gareth Farrington +# Copyright (C) 2026 Kevin O'Connor +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import mcu + + +###################################################################### +# SOS filters (Second Order Sectional) +###################################################################### + +MAX_INT32 = (2 ** 31) +MIN_INT32 = -(2 ** 31) - 1 +def assert_is_int32(value, frac_bits): + if value > MAX_INT32 or value < MIN_INT32: + raise OverflowError("Fixed point Q%d.%d overflow" + % (31-frac_bits, frac_bits)) + return value + +# convert a floating point value to a 32 bit fixed point representation +# checks for overflow +def to_fixed_32(value, frac_bits=0): + fixed_val = int(value * (2**frac_bits)) + return assert_is_int32(fixed_val, frac_bits) + + +# Digital filter designer and container +class DigitalFilter: + def __init__(self, sps, cfg_error, highpass=None, highpass_order=1, + lowpass=None, lowpass_order=1, notches=None, notch_quality=2.0): + self.filter_sections = [] + self.initial_state = [] + self.sample_frequency = sps + # an empty filter can be created without SciPi/numpy + if not (highpass or lowpass or notches): + return + try: + import scipy.signal as signal + import numpy + except: + raise cfg_error("DigitalFilter require the SciPy module") + if highpass: + self.filter_sections.extend( + self._butter(highpass, "highpass", highpass_order)) + if lowpass: + self.filter_sections.extend( + self._butter(lowpass, "lowpass", lowpass_order)) + if notches is None: + notches = [] + for notch_freq in notches: + self.filter_sections.append(self._notch(notch_freq, notch_quality)) + if len(self.filter_sections) > 0: + self.initial_state = signal.sosfilt_zi(self.filter_sections) + + def _butter(self, frequency, btype, order): + import scipy.signal as signal + return signal.butter(order, Wn=frequency, btype=btype, + fs=self.sample_frequency, output='sos') + + def _notch(self, freq, quality): + import scipy.signal as signal + b, a = signal.iirnotch(freq, Q=quality, fs=self.sample_frequency) + return signal.tf2sos(b, a)[0] + + def get_filter_sections(self): + return self.filter_sections + + def get_initial_state(self): + return self.initial_state + + def filtfilt(self, data): + import scipy.signal as signal + import numpy + data = numpy.array(data) + return signal.sosfiltfilt(self.filter_sections, data) + +# Produce sample to sample difference (derivative) of a DigitalFilter +class DerivativeFilter: + def __init__(self, main_filter): + self._main_filter = main_filter + + def get_main_filter(self): + return self._main_filter + + def get_filter_sections(self): + s = list(self._main_filter.get_filter_sections()) + return s + [(1., -1., 0., 1., 0., 0.)] + + def get_initial_state(self): + s = list(self._main_filter.get_initial_state()) + return s + [(-1., 0.)] + +# Control an `sos_filter` object on the MCU +class MCU_SosFilter: + # max_sections should be the largest number of sections you expect + # to use at runtime. + def __init__(self, mcu, cmd_queue, max_sections): + self._mcu = mcu + self._cmd_queue = cmd_queue + self._max_sections = max_sections + # SOS filter "design" + self._design = None + self._coeff_frac_bits = 0 + self._start_value = 0. + # Offset and scaling + self._offset = 0 + self._scale = 1. + self._scale_frac_bits = 0 + self._auto_offset = False + # MCU commands + self._oid = self._mcu.create_oid() + self._set_section_cmd = self._set_state_cmd = None + self._set_active_cmd = self._set_offset_scale_cmd = None + self._last_sent_coeffs = [None] * self._max_sections + self._last_sent_offset_scale = None + self._mcu.add_config_cmd("config_sos_filter oid=%d max_sections=%d" + % (self._oid, self._max_sections)) + self._mcu.register_config_callback(self._build_config) + + def _build_config(self): + self._set_section_cmd = self._mcu.lookup_command( + "sos_filter_set_section oid=%c section_idx=%c" + " sos0=%i sos1=%i sos2=%i sos3=%i sos4=%i", cq=self._cmd_queue) + self._set_state_cmd = self._mcu.lookup_command( + "sos_filter_set_state oid=%c section_idx=%c state0=%i state1=%i", + cq=self._cmd_queue) + self._set_offset_scale_cmd = self._mcu.lookup_command( + "sos_filter_set_offset_scale oid=%c offset=%i" + " scale=%i scale_frac_bits=%c auto_offset=%c", cq=self._cmd_queue) + self._set_active_cmd = self._mcu.lookup_command( + "sos_filter_set_active oid=%c n_sections=%c coeff_frac_bits=%c", + cq=self._cmd_queue) + + def get_oid(self): + return self._oid + + # convert the SciPi SOS filters to fixed point format + def _convert_filter(self): + if self._design is None: + return [] + filter_sections = self._design.get_filter_sections() + sos_fixed = [] + for section in filter_sections: + nun_coeff = len(section) + if nun_coeff != 6: + raise ValueError("The number of filter coefficients is %i" + ", must be 6" % (nun_coeff,)) + fixed_section = [] + for col, coeff in enumerate(section): + if col != 3: # omit column 3 + fixed_coeff = to_fixed_32(coeff, self._coeff_frac_bits) + fixed_section.append(fixed_coeff) + elif coeff != 1.0: # double check column 3 is always 1.0 + raise ValueError("Coefficient 3 is expected to be 1.0" + " but was %f" % (coeff,)) + sos_fixed.append(fixed_section) + return sos_fixed + + # convert the SOS filter state matrix (zi) to fixed point format + def _convert_state(self): + if self._design is None: + return [] + filter_state = self._design.get_initial_state() + sos_state = [] + for section in filter_state: + nun_states = len(section) + if nun_states != 2: + raise ValueError( + "The number of state elements is %i, must be 2" + % (nun_states,)) + fixed_state = [] + for col, value in enumerate(section): + adjval = value * self._start_value + fixed_state.append(to_fixed_32(adjval)) + sos_state.append(fixed_state) + return sos_state + + # Set expected state when filter first starts (avoids filter + # "ringing" if sensor data has a known static offset) + def set_start_state(self, start_value): + self._start_value = start_value + + # Set conversion of a raw value 1 to a 1.0 value processed by sos filter + def set_offset_scale(self, offset=0, scale=1., scale_frac_bits=0, + auto_offset=False): + self._offset = offset + self._scale = scale + self._scale_frac_bits = scale_frac_bits + self._auto_offset = auto_offset + + # Change the filter coefficients and state at runtime + def set_filter_design(self, design, coeff_frac_bits=29): + self._design = design + self._coeff_frac_bits = coeff_frac_bits + + # Resets the filter state back to initial conditions at runtime + def reset_filter(self): + # Generate filter parameters + sos_fixed = self._convert_filter() + sos_state = self._convert_state() + num_sections = len(sos_fixed) + if num_sections > self._max_sections: + raise ValueError("Too many filter sections: %i, The max is %i" + % (num_sections, self._max_sections,)) + if len(sos_state) != num_sections: + raise ValueError("The number of filter sections (%i) and state " + "sections (%i) must be equal" + % (num_sections, len(sos_state))) + # Send section coefficients (if they have changed) + for i, section in enumerate(sos_fixed): + args = (self._oid, i, section[0], section[1], section[2], + section[3], section[4]) + if args == self._last_sent_coeffs[i]: + continue + self._set_section_cmd.send(args) + self._last_sent_coeffs[i] = args + # Send section initial states + for i, state in enumerate(sos_state): + self._set_state_cmd.send([self._oid, i, state[0], state[1]]) + # Send offset/scale (if they have changed) + su = to_fixed_32(self._scale, self._scale_frac_bits) + args = (self._oid, self._offset, su, self._scale_frac_bits, + self._auto_offset) + if args != self._last_sent_offset_scale or self._auto_offset: + self._set_offset_scale_cmd.send(args) + self._last_sent_offset_scale = args + # Activate filter + if self._max_sections: + self._set_active_cmd.send([self._oid, num_sections, + self._coeff_frac_bits]) + + +###################################################################### +# Trigger Analog +###################################################################### + +# MCU_trigger_analog is the interface to `trigger_analog` on the MCU +class MCU_trigger_analog: + MONITOR_MAX = 3 + REASON_TRIGGER_ANALOG = mcu.MCU_trsync.REASON_COMMS_TIMEOUT + 1 + def __init__(self, sensor_inst): + self._printer = sensor_inst.get_mcu().get_printer() + self._sensor = sensor_inst + self._mcu = self._sensor.get_mcu() + self._sos_filter = None + self._dispatch = mcu.TriggerDispatch(self._mcu) + self._last_trigger_time = 0. + # Raw range checking + self._raw_min = self._raw_max = 0 + self._last_range_args = None + # Trigger type + self._trigger_type = "unspecified" + self._trigger_value = 0. + self._last_trigger_args = None + # Error codes from MCU + self._error_map = {} + self._sensor_specific_error = 0 + # Configure MCU objects + self._oid = self._mcu.create_oid() + self._home_cmd = self._query_state_cmd = None + self._set_raw_range_cmd = self._set_trigger_cmd = None + self._mcu.register_config_callback(self._build_config) + + def setup_sos_filter(self, sos_filter): + self._sos_filter = sos_filter + + def _build_config(self): + self._sensor.setup_trigger_analog(self._oid) + cmd_queue = self._dispatch.get_command_queue() + if self._sos_filter is None: + self.setup_sos_filter(MCU_SosFilter(self._mcu, cmd_queue, 0)) + self._mcu.add_config_cmd( + "config_trigger_analog oid=%d sos_filter_oid=%d" % ( + self._oid, self._sos_filter.get_oid())) + # Lookup commands + self._query_state_cmd = self._mcu.lookup_query_command( + "trigger_analog_query_state oid=%c", + "trigger_analog_state oid=%c homing=%c homing_clock=%u", + oid=self._oid, cq=cmd_queue) + self._set_raw_range_cmd = self._mcu.lookup_command( + "trigger_analog_set_raw_range oid=%c raw_min=%i raw_max=%i", + cq=cmd_queue) + self._set_trigger_cmd = self._mcu.lookup_command( + "trigger_analog_set_trigger oid=%c trigger_analog_type=%c" + " trigger_value=%i", cq=cmd_queue) + self._home_cmd = self._mcu.lookup_command( + "trigger_analog_home oid=%c trsync_oid=%c trigger_reason=%c" + " error_reason=%c clock=%u monitor_ticks=%u monitor_max=%u", + cq=cmd_queue) + # Load errors from mcu + errors = self._mcu.get_enumerations().get("trigger_analog_error:", {}) + self._error_map = {v: k for k, v in errors.items()} + self._sensor_specific_error = errors.get("SENSOR_SPECIFIC", 0) + + def get_oid(self): + return self._oid + + def get_mcu(self): + return self._mcu + + def get_sos_filter(self): + return self._sos_filter + + def get_dispatch(self): + return self._dispatch + + def get_last_trigger_time(self): + return self._last_trigger_time + + def set_trigger(self, trigger_type, trigger_value): + self._trigger_type = trigger_type + self._trigger_value = trigger_value + + def set_raw_range(self, raw_min, raw_max): + self._raw_min = raw_min + self._raw_max = raw_max + + def _reset_filter(self): + # Update raw range parameters in mcu (if they have changed) + args = [self._oid, self._raw_min, self._raw_max] + if args != self._last_range_args: + self._set_raw_range_cmd.send(args) + self._last_range_args = args + # Update trigger in mcu (if it has changed) + args = [self._oid, self._trigger_type, to_fixed_32(self._trigger_value)] + if args != self._last_trigger_args: + self._set_trigger_cmd.send(args) + self._last_trigger_args = args + # Update sos filter in mcu + self._sos_filter.reset_filter() + + def _clear_home(self): + self._home_cmd.send([self._oid, 0, 0, 0, 0, 0, 0, 0]) + params = self._query_state_cmd.send([self._oid]) + trigger_ticks = self._mcu.clock32_to_clock64(params['homing_clock']) + return self._mcu.clock_to_print_time(trigger_ticks) + + def get_steppers(self): + return self._dispatch.get_steppers() + + def home_start(self, print_time, sample_time, sample_count, rest_time, + triggered=True): + self._last_trigger_time = 0. + self._reset_filter() + trigger_completion = self._dispatch.start(print_time) + clock = self._mcu.print_time_to_clock(print_time) + sensor_update = 1. / self._sensor.get_samples_per_second() + sm_ticks = self._mcu.seconds_to_clock(sensor_update) + self._home_cmd.send([self._oid, self._dispatch.get_oid(), + mcu.MCU_trsync.REASON_ENDSTOP_HIT, self.REASON_TRIGGER_ANALOG, + clock, sm_ticks, self.MONITOR_MAX], reqclock=clock) + return trigger_completion + + def home_wait(self, home_end_time): + self._dispatch.wait_end(home_end_time) + # trigger has happened, now to find out why... + res = self._dispatch.stop() + # clear the homing state so it stops processing samples + trigger_time = self._clear_home() + if res >= mcu.MCU_trsync.REASON_COMMS_TIMEOUT: + if res == mcu.MCU_trsync.REASON_COMMS_TIMEOUT: + raise self._printer.command_error( + "Communication timeout during homing") + error_code = res - self.REASON_TRIGGER_ANALOG + if error_code >= self._sensor_specific_error: + sensor_err = error_code - self._sensor_specific_error + error_msg = self._sensor.lookup_sensor_error(sensor_err) + else: + defmsg = "Unknown code %i" % (error_code,) + error_msg = self._error_map.get(error_code, defmsg) + raise self._printer.command_error("Trigger analog error: %s" + % (error_msg,)) + if res != mcu.MCU_trsync.REASON_ENDSTOP_HIT: + return 0. + if self._mcu.is_fileoutput(): + trigger_time = home_end_time + self._last_trigger_time = trigger_time + return trigger_time diff --git a/klippy/extras/z_tilt.py b/klippy/extras/z_tilt.py index 0316ee721..28763f1f0 100644 --- a/klippy/extras/z_tilt.py +++ b/klippy/extras/z_tilt.py @@ -143,16 +143,14 @@ class ZTilt: self.z_status.reset() self.retry_helper.start(gcmd) self.probe_helper.start_probe(gcmd) - def probe_finalize(self, offsets, positions): + def probe_finalize(self, positions): # Setup for coordinate descent analysis - z_offset = offsets[2] logging.info("Calculating bed tilt with: %s", positions) - params = { 'x_adjust': 0., 'y_adjust': 0., 'z_adjust': z_offset } + params = { 'x_adjust': 0., 'y_adjust': 0., 'z_adjust': 0. } # Perform coordinate descent def adjusted_height(pos, params): - x, y, z = pos - return (z - x*params['x_adjust'] - y*params['y_adjust'] - - params['z_adjust']) + return (pos.bed_z - pos.bed_x*params['x_adjust'] + - pos.bed_y*params['y_adjust'] - params['z_adjust']) def errorfunc(params): total_error = 0. for pos in positions: @@ -165,8 +163,7 @@ class ZTilt: logging.info("Calculated bed tilt parameters: %s", new_params) x_adjust = new_params['x_adjust'] y_adjust = new_params['y_adjust'] - z_adjust = (new_params['z_adjust'] - z_offset - - x_adjust * offsets[0] - y_adjust * offsets[1]) + z_adjust = new_params['z_adjust'] adjustments = [x*x_adjust + y*y_adjust + z_adjust for x, y in self.z_positions] self.z_helper.adjust_steppers(adjustments, speed) diff --git a/klippy/kinematics/cartesian.py b/klippy/kinematics/cartesian.py index 5362e57d0..67858d0bb 100644 --- a/klippy/kinematics/cartesian.py +++ b/klippy/kinematics/cartesian.py @@ -32,8 +32,8 @@ class CartKinematics: self.dc_module = idex_modes.DualCarriages( self.printer, [self.rails[self.dual_carriage_axis]], [self.rails[3]], axes=[self.dual_carriage_axis], - safe_dist=dc_config.getfloat( - 'safe_distance', None, minval=0.)) + safe_dist=[dc_config.getfloat( + 'safe_distance', None, minval=0.)]) for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) # Setup boundary checks diff --git a/klippy/kinematics/generic_cartesian.py b/klippy/kinematics/generic_cartesian.py index 230997be3..afb5d47ac 100644 --- a/klippy/kinematics/generic_cartesian.py +++ b/klippy/kinematics/generic_cartesian.py @@ -4,11 +4,13 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. -import copy, itertools, logging, math +import collections, copy, itertools, logging, math import gcode, mathutil, stepper from . import idex_modes from . import kinematic_stepper as ks +VALID_AXES = ['x', 'y', 'z'] + def mat_mul(a, b): if len(a[0]) != len(b): return None @@ -35,13 +37,11 @@ class MainCarriage: def __init__(self, config): self.rail = stepper.GenericPrinterRail(config) carriage_name = self.rail.get_name(short=True) - valid_axes = ['x', 'y', 'z'] - if carriage_name in valid_axes: - axis_name = config.getchoice('axis', valid_axes, carriage_name) + if carriage_name in VALID_AXES: + self.axis_name = config.getchoice('axis', VALID_AXES, carriage_name) else: - axis_name = config.getchoice('axis', valid_axes) - self.axis = ord(axis_name) - ord('x') - self.axis_name = axis_name + self.axis_name = config.getchoice('axis', VALID_AXES) + self.axis = ord(self.axis_name) - ord('x') self.dual_carriage = None def get_name(self): return self.rail.get_name(short=True) @@ -56,9 +56,7 @@ class MainCarriage: return True return self.dual_carriage.get_dc_module().is_active(self.rail) def set_dual_carriage(self, carriage): - old_dc = self.dual_carriage self.dual_carriage = carriage - return old_dc def get_dual_carriage(self): return self.dual_carriage @@ -78,23 +76,49 @@ class ExtraCarriage: self.endstop_pin, self.name) class DualCarriage: - def __init__(self, config, carriages): + def __init__(self, config): self.printer = config.get_printer() self.rail = stepper.GenericPrinterRail(config) - self.primary_carriage = config.getchoice('primary_carriage', carriages) - if self.primary_carriage.set_dual_carriage(self) is not None: - raise config.error( - "Redefinition of dual_carriage for carriage '%s'" % - self.primary_carriage.get_name()) + self.primary_carriage_name = config.get('primary_carriage', None) + if self.primary_carriage_name is None: + self.axis_name = config.getchoice('axis', VALID_AXES) + self.axis = ord(self.axis_name) - ord('x') + self.safe_dist = None + else: + self.axis_name = config.getchoice('axis', VALID_AXES + [None], None) + self.safe_dist = config.getfloat('safe_distance', None, minval=0.) + self.primary_carriage = self.dual_carriage = None + self.config_error = config.error + def resolve_primary_carriage(self, carriages): + if self.primary_carriage_name is None: + return + if self.primary_carriage_name not in carriages: + raise self.config_error( + "primary_carriage = '%s' for '%s' is not a valid choice" + % (self.primary_carriage_name, self.get_name())) + self.primary_carriage = carriages[self.primary_carriage_name] + axis_name = self.axis_name or self.primary_carriage.axis_name + if axis_name != self.primary_carriage.axis_name: + raise self.config_error("Mismatching axes between carriage '%s' " + "(axis=%s) and dual_carriage '%s' (axis=%s)" + % (self.primary_carriage.get_name(), + self.primary_carriage.axis_name, + self.get_name(), axis_name)) + self.axis = ord(axis_name) - ord('x') + if self.primary_carriage.get_dual_carriage(): + raise self.config_error( + "Multiple dual carriages ('%s', '%s') for carriage '%s'" % + (self.primary_carriage.get_dual_carriage().get_name(), + self.get_name(), self.primary_carriage.get_name())) + self.primary_carriage.set_dual_carriage(self) self.axis = self.primary_carriage.get_axis() if self.axis > 1: - raise config.error("Invalid axis '%s' for dual_carriage" % - "xyz"[self.axis]) - self.safe_dist = config.getfloat('safe_distance', None, minval=0.) + raise self.config_error("Invalid axis '%s' for dual_carriage '%s'" % + ("xyz"[self.axis], self.get_name())) def get_name(self): return self.rail.get_name(short=True) def get_axis(self): - return self.primary_carriage.get_axis() + return self.axis def get_rail(self): return self.rail def get_safe_dist(self): @@ -103,7 +127,13 @@ class DualCarriage: return self.printer.lookup_object('dual_carriage') def is_active(self): return self.get_dc_module().is_active(self.rail) + def set_dual_carriage(self, carriage): + self.dual_carriage = carriage def get_dual_carriage(self): + if self.dual_carriage is not None: + return self.dual_carriage + return self.primary_carriage + def get_primary_carriage(self): return self.primary_carriage def add_stepper(self, kin_stepper): self.rail.add_stepper(kin_stepper.get_stepper()) @@ -116,27 +146,38 @@ class GenericCartesianKinematics: s.set_trapq(toolhead.get_trapq()) self.dc_module = None if self.dc_carriages: - pcs = [dc.get_dual_carriage() for dc in self.dc_carriages] + dc_axes = set(dc.get_axis() for dc in self.dc_carriages) + pcs = ([pc for pc in self.primary_carriages + if pc.get_axis() in dc_axes] + + [dc for dc in self.dc_carriages + if dc.get_primary_carriage() is None]) + dcs = [pc.get_dual_carriage() for pc in pcs] primary_rails = [pc.get_rail() for pc in pcs] - dual_rails = [dc.get_rail() for dc in self.dc_carriages] - axes = [dc.get_axis() for dc in self.dc_carriages] - safe_dist = {dc.get_axis() : dc.get_safe_dist() - for dc in self.dc_carriages} + dual_rails = [dc.get_rail() if dc else None for dc in dcs] + axes = [pc.get_axis() for pc in pcs] + safe_dist = [dc.get_safe_dist() if dc else None for dc in dcs] self.dc_module = dc_module = idex_modes.DualCarriages( self.printer, primary_rails, dual_rails, axes, safe_dist) zero_pos = (0., 0.) - for acs in itertools.product(*zip(pcs, self.dc_carriages)): + for acs in itertools.product(*zip(pcs, dcs)): for c in acs: + if c is None: + continue dc_module.get_dc_rail_wrapper(c.get_rail()).activate( idex_modes.PRIMARY, zero_pos) - dc_rail = c.get_dual_carriage().get_rail() - dc_module.get_dc_rail_wrapper(dc_rail).inactivate(zero_pos) + dc = c.get_dual_carriage() + if dc is not None: + dc_module.get_dc_rail_wrapper(dc.get_rail()).inactivate( + zero_pos) self._check_kinematics(config.error) - for c in pcs: - dc_module.get_dc_rail_wrapper(c.get_rail()).activate( + for dc in self.dc_carriages: + dc_module.get_dc_rail_wrapper(dc.get_rail()).inactivate( + zero_pos) + for pc in self.primary_carriages: + if pc.get_axis() not in dc_axes: + continue + dc_module.get_dc_rail_wrapper(pc.get_rail()).activate( idex_modes.PRIMARY, zero_pos) - dc_rail = c.get_dual_carriage().get_rail() - dc_module.get_dc_rail_wrapper(dc_rail).inactivate(zero_pos) else: self._check_kinematics(config.error) # Setup boundary checks @@ -152,25 +193,32 @@ class GenericCartesianKinematics: self.cmd_SET_STEPPER_CARRIAGES, desc=self.cmd_SET_STEPPER_CARRIAGES_help) def _load_kinematics(self, config): - carriages = {} + primary_carriages = [] for mcconfig in config.get_prefix_sections('carriage '): - c = MainCarriage(mcconfig) - axis = c.get_axis() - dups = [mc for mc in carriages.values() if mc.get_axis() == axis] - if dups: + primary_carriages.append(MainCarriage(mcconfig)) + for axis, axis_name in enumerate(VALID_AXES): + dups = [pc.get_name() for pc in primary_carriages + if pc.get_axis() == axis] + if len(dups) > 1: raise config.error( - "Axis '%s' referenced by multiple carriages (%s, %s)" - % ("xyz"[axis], c.get_name(), dups[0].get_name())) - carriages[c.get_name()] = c + "Axis '%s' is set for multiple primary carriages (%s)" + % (axis_name, ', '.join(dups))) + elif not dups: + raise config.error( + "No carriage defined for axis '%s'" % axis_name) dc_carriages = [] for dcconfig in config.get_prefix_sections('dual_carriage '): - dc_carriages.append(DualCarriage(dcconfig, carriages)) - for dc in dc_carriages: - name = dc.get_name() + dc_carriages.append(DualCarriage(dcconfig)) + carriages = {} + for carriage in primary_carriages + dc_carriages: + name = carriage.get_name() if name in carriages: raise config.error("Redefinition of carriage %s" % name) - carriages[name] = dc + carriages[name] = carriage + for dc in dc_carriages: + dc.resolve_primary_carriage(carriages) self.carriages = dict(carriages) + self.primary_carriages = primary_carriages self.dc_carriages = dc_carriages ec_carriages = [] for ecconfig in config.get_prefix_sections('extra_carriage '): @@ -207,16 +255,19 @@ class GenericCartesianKinematics: def get_steppers(self): return [s.get_stepper() for s in self.kin_steppers] def get_primary_carriages(self): - carriages = [None] * 3 - for carriage in self.carriages.values(): - a = carriage.get_axis() - if carriage.get_dual_carriage() is not None: + carriages = [] + for a in range(3): + c = None + if self.dc_module is not None and a in self.dc_module.get_axes(): primary_rail = self.dc_module.get_primary_rail(a) for c in self.carriages.values(): if c.get_rail() == primary_rail: - carriages[a] = c + break else: - carriages[a] = carriage + for c in self.primary_carriages: + if c.get_axis() == a: + break + carriages.append(c) return carriages def _get_kinematics_coeffs(self): matr = {s.get_name() : list(s.get_kin_coeffs()) @@ -227,9 +278,9 @@ class GenericCartesianKinematics: [0. for s in self.kin_steppers]) axes = [dc.get_axis() for dc in self.dc_carriages] orig_matr = copy.deepcopy(matr) - for dc in self.dc_carriages: - axis = dc.get_axis() - for c in [dc.get_dual_carriage(), dc]: + for c in self.carriages.values(): + axis = c.get_axis() + if axis in self.dc_module.get_axes(): m, o = self.dc_module.get_transform(c.get_rail()) for s in c.get_rail().get_steppers(): matr[s.get_name()][axis] *= m @@ -289,16 +340,14 @@ class GenericCartesianKinematics: homing_state.home_rails([rail], forcepos, homepos) def home(self, homing_state): self._check_kinematics(self.printer.command_error) + primary_carriages = self.get_primary_carriages() # Each axis is homed independently and in order for axis in homing_state.get_axes(): - for carriage in self.carriages.values(): - if carriage.get_axis() != axis: - continue - if carriage.get_dual_carriage() != None: - self.dc_module.home(homing_state, axis) - else: - self.home_axis(homing_state, axis, carriage.get_rail()) - break + if self.dc_module is not None and axis in self.dc_module.get_axes(): + self.dc_module.home(homing_state, axis) + else: + carriage = primary_carriages[axis] + self.home_axis(homing_state, axis, carriage.get_rail()) def _check_endstops(self, move): end_pos = move.end_pos for i in (0, 1, 2): diff --git a/klippy/kinematics/hybrid_corexy.py b/klippy/kinematics/hybrid_corexy.py index bbe7bfa55..1cd2aa8bc 100644 --- a/klippy/kinematics/hybrid_corexy.py +++ b/klippy/kinematics/hybrid_corexy.py @@ -35,8 +35,8 @@ class HybridCoreXYKinematics: self.rails[3].setup_itersolve('corexy_stepper_alloc', b'+') self.dc_module = idex_modes.DualCarriages( self.printer, [self.rails[0]], [self.rails[3]], axes=[0], - safe_dist=dc_config.getfloat( - 'safe_distance', None, minval=0.)) + safe_dist=[dc_config.getfloat( + 'safe_distance', None, minval=0.)]) for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) # Setup boundary checks diff --git a/klippy/kinematics/hybrid_corexz.py b/klippy/kinematics/hybrid_corexz.py index 3e2dd4788..03e889376 100644 --- a/klippy/kinematics/hybrid_corexz.py +++ b/klippy/kinematics/hybrid_corexz.py @@ -35,8 +35,8 @@ class HybridCoreXZKinematics: self.rails[3].setup_itersolve('corexz_stepper_alloc', b'+') self.dc_module = idex_modes.DualCarriages( self.printer, [self.rails[0]], [self.rails[3]], axes=[0], - safe_dist=dc_config.getfloat( - 'safe_distance', None, minval=0.)) + safe_dist=[dc_config.getfloat( + 'safe_distance', None, minval=0.)]) for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) # Setup boundary checks diff --git a/klippy/kinematics/idex_modes.py b/klippy/kinematics/idex_modes.py index 46b0be08b..4fe6df830 100644 --- a/klippy/kinematics/idex_modes.py +++ b/klippy/kinematics/idex_modes.py @@ -14,36 +14,37 @@ MIRROR = 'MIRROR' class DualCarriages: VALID_MODES = [PRIMARY, COPY, MIRROR] - def __init__(self, printer, primary_rails, dual_rails, axes, - safe_dist={}): + def __init__(self, printer, primary_rails, dual_rails, axes, safe_dist): self.printer = printer - self.axes = axes self._init_steppers(primary_rails + dual_rails) - self.primary_rails = [ - DualCarriagesRail(printer, c, dual_rails[i], - axes[i], active=True) - for i, c in enumerate(primary_rails)] + safe_dist = list(safe_dist) + for i, dc in enumerate(dual_rails): + if dc is None or safe_dist[i] is not None: + continue + pc = primary_rails[i] + safe_dist[i] = min(abs(pc.position_min - dc.position_min), + abs(pc.position_max - dc.position_max)) + self.primary_mode_dcs = [None] * 3 + self.primary_rails = [] + for i, c in enumerate(primary_rails): + activate = self.primary_mode_dcs[axes[i]] is None + dc_rail = DualCarriagesRail( + printer, c, dual_rails[i], axes[i], safe_dist[i], + active=activate) + if activate: + self.primary_mode_dcs[axes[i]] = dc_rail + self.primary_rails.append(dc_rail) self.dual_rails = [ DualCarriagesRail(printer, c, primary_rails[i], - axes[i], active=False) + axes[i], safe_dist[i], active=False) + if c is not None else None for i, c in enumerate(dual_rails)] self.dc_rails = collections.OrderedDict( [(c.rail.get_name(short=True), c) - for c in self.primary_rails + self.dual_rails]) + for c in self.primary_rails + self.dual_rails + if c is not None]) self.saved_states = {} - self.safe_dist = {} - for i, dc in enumerate(dual_rails): - axis = axes[i] - if isinstance(safe_dist, dict): - if axis in safe_dist: - self.safe_dist[axis] = safe_dist[axis] - continue - elif safe_dist is not None: - self.safe_dist[axis] = safe_dist - continue - pc = primary_rails[i] - self.safe_dist[axis] = min(abs(pc.position_min - dc.position_min), - abs(pc.position_max - dc.position_max)) + self.axes = sorted(set(axes)) self.printer.add_object('dual_carriage', self) self.printer.register_event_handler("klippy:ready", self._handle_ready) gcode = self.printer.lookup_object('gcode') @@ -64,6 +65,8 @@ class DualCarriages: self.orig_stepper_kinematics = [] steppers = set() for rail in rails: + if rail is None: + continue c_steppers = rail.get_steppers() if not c_steppers: raise self.printer.config_error( @@ -80,10 +83,9 @@ class DualCarriages: def get_axes(self): return self.axes def get_primary_rail(self, axis): - for dc_rail in self.dc_rails.values(): - if dc_rail.mode == PRIMARY and dc_rail.axis == axis: - return dc_rail.rail - return None + if self.primary_mode_dcs[axis] is None: + return None + return self.primary_mode_dcs[axis].rail def get_dc_rail_wrapper(self, rail): for dc_rail in self.dc_rails.values(): if dc_rail.rail == rail: @@ -109,22 +111,27 @@ class DualCarriages: if target_dc.mode != PRIMARY: newpos = pos[:axis] + [target_dc.get_axis_position(pos)] \ + pos[axis+1:] + self.primary_mode_dcs[axis] = target_dc target_dc.activate(PRIMARY, newpos, old_position=pos) toolhead.set_position(newpos) kin.update_limits(axis, target_dc.rail.get_range()) def home(self, homing_state, axis): kin = self.printer.lookup_object('toolhead').get_kinematics() - dcs = [dc for dc in self.dc_rails.values() if dc.axis == axis] - if (self.get_dc_order(dcs[0], dcs[1]) > 0) != \ - dcs[0].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 - dcs.reverse() - for dc in dcs: - self.toggle_active_dc_rail(dc) - kin.home_axis(homing_state, axis, dc.rail) - # Restore the original rails ordering - self.activate_dc_mode(dcs[0], PRIMARY) + homing_rails = [r for r in self.primary_rails if r.axis == axis] + for dc_rail in homing_rails: + dcs = [dc for dc in self.dc_rails.values() + if dc_rail.rail in [dc.rail, dc.dual_rail]] + if len(dcs) > 1 and (self.get_dc_order(dcs[0], dcs[1]) > 0) != \ + dcs[0].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, so reversing the oder + dcs.reverse() + for dc in dcs: + self.toggle_active_dc_rail(dc) + kin.home_axis(homing_state, dc.axis, dc.rail) + # Restore the first rail as primary after all homed + self.activate_dc_mode(homing_rails[0], PRIMARY) def get_status(self, eventtime=None): status = {'carriages' : {dc.get_name() : dc.mode for dc in self.dc_rails.values()}} @@ -132,46 +139,62 @@ class DualCarriages: status.update({('carriage_%d' % (i,)) : dc.mode for i, dc in enumerate(self.dc_rails.values())}) return status - def get_kin_range(self, toolhead, mode, axis): + def get_kin_range(self, toolhead, axis): pos = toolhead.get_position() - dcs = [dc for dc in self.dc_rails.values() if dc.axis == axis] - axes_pos = [dc.get_axis_position(pos) for dc in dcs] - dc0_rail = dcs[0].rail - dc1_rail = dcs[1].rail - if mode != PRIMARY or dcs[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[axis] - if not safe_dist: - return (range_min, range_max) + primary_carriage = self.primary_mode_dcs[axis] + if primary_carriage is None: + return (1.0, -1.0) + primary_pos = primary_carriage.get_axis_position(pos) + range_min = primary_carriage.rail.position_min + range_max = primary_carriage.rail.position_max + for carriage in self.dc_rails.values(): + if carriage.axis != axis: + continue + dcs = [carriage] + [dc for dc in self.dc_rails.values() + if carriage.rail is dc.dual_rail] + axes_pos = [dc.get_axis_position(pos) for dc in dcs] + # Check how dcs[0] affects the motion range of primary_carriage + if not dcs[0].is_active(): + continue + elif dcs[0].mode == COPY: + range_min = max(range_min, primary_pos + + dcs[0].rail.position_min - axes_pos[0]) + range_max = min(range_max, primary_pos + + dcs[0].rail.position_max - axes_pos[0]) + elif dcs[0].mode == MIRROR: + range_min = max(range_min, primary_pos + + axes_pos[0] - dcs[0].rail.position_max) + range_max = min(range_max, primary_pos + + axes_pos[0] - dcs[0].rail.position_min) + safe_dist = dcs[0].safe_dist + if not safe_dist or len(dcs) == 1: + continue + if dcs[0].mode == dcs[1].mode or \ + set((dcs[0].mode, dcs[1].mode)) == set((PRIMARY, COPY)): + # dcs[0] and dcs[1] carriages move in the same direction and + # cannot collide with each other + continue - if mode == COPY: - range_min = max(range_min, - axes_pos[0] - axes_pos[1] + dc1_rail.position_min) - range_max = min(range_max, - axes_pos[0] - axes_pos[1] + dc1_rail.position_max) - elif mode == MIRROR: + # Compute how much dcs[0] can move towards dcs[1] + dcs_dist = axes_pos[1] - axes_pos[0] if self.get_dc_order(dcs[0], dcs[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) + safe_move_dist = dcs_dist + safe_dist else: - range_max = min(range_max, - 0.5 * (sum(axes_pos) - safe_dist)) - range_min = max(range_min, - sum(axes_pos) - dc1_rail.position_max) - else: - # mode == PRIMARY - active_idx = 1 if dcs[1].is_active() else 0 - inactive_idx = 1 - active_idx - if self.get_dc_order(dcs[active_idx], dcs[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) + safe_move_dist = dcs_dist - safe_dist + if dcs[1].is_active(): + safe_move_dist *= 0.5 + + if dcs[0].mode in (PRIMARY, COPY): + if self.get_dc_order(dcs[0], dcs[1]) > 0: + range_min = max(range_min, primary_pos + safe_move_dist) + else: + range_max = min(range_max, primary_pos + safe_move_dist) + else: # dcs[0].mode == MIRROR + if self.get_dc_order(dcs[0], dcs[1]) > 0: + range_max = min(range_max, primary_pos - safe_move_dist) + else: + range_min = max(range_min, primary_pos - safe_move_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 @@ -208,12 +231,13 @@ class DualCarriages: axis = dc.axis if mode == INACTIVE: dc.inactivate(toolhead.get_position()) + if self.primary_mode_dcs[axis] is dc: + self.primary_mode_dcs[axis] = None elif mode == PRIMARY: self.toggle_active_dc_rail(dc) else: - self.toggle_active_dc_rail(self.get_dc_rail_wrapper(dc.dual_rail)) dc.activate(mode, toolhead.get_position()) - kin.update_limits(axis, self.get_kin_range(toolhead, mode, axis)) + kin.update_limits(axis, self.get_kin_range(toolhead, axis)) def _handle_ready(self): for dc_rail in self.dc_rails.values(): dc_rail.apply_transform() @@ -241,10 +265,9 @@ class DualCarriages: if mode not in self.VALID_MODES: raise gcmd.error("Invalid mode=%s specified" % (mode,)) if mode in [COPY, MIRROR]: - if dc_rail in self.primary_rails: + if self.primary_mode_dcs[dc_rail.axis] in [None, dc_rail]: raise gcmd.error( - "Mode=%s is not supported for carriage=%s" % ( - mode, dc_rail.get_name())) + "Must activate another carriage as PRIMARY first") curtime = self.printer.get_reactor().monotonic() kin = self.printer.lookup_object('toolhead').get_kinematics() axis = 'xyz'[dc_rail.axis] @@ -291,18 +314,21 @@ class DualCarriages: for i, dc in enumerate(dcs)] for axis in self.axes: dc_ind = [i for i, dc in enumerate(dcs) if dc.axis == axis] - if abs(dl[dc_ind[0]]) >= abs(dl[dc_ind[1]]): - primary_ind, secondary_ind = dc_ind[0], dc_ind[1] - else: - primary_ind, secondary_ind = dc_ind[1], dc_ind[0] + abs_dl = [abs(dl[i]) for i in dc_ind] + primary_ind = dc_ind[abs_dl.index(max(abs_dl))] primary_dc = dcs[primary_ind] self.toggle_active_dc_rail(primary_dc) move_pos[axis] = carriage_positions[primary_dc.get_name()] - dc_mode = INACTIVE if min(abs(dl[primary_ind]), - abs(dl[secondary_ind])) < .000000001 \ - else COPY if dl[primary_ind] * dl[secondary_ind] > 0 \ - else MIRROR - if dc_mode != INACTIVE: + for secondary_ind in dc_ind: + if secondary_ind == primary_ind: + continue + if min(abs(dl[primary_ind]), + abs(dl[secondary_ind])) < .000000001: + continue + if dl[primary_ind] * dl[secondary_ind] > 0: + dc_mode = COPY + else: + dc_mode = MIRROR dcs[secondary_ind].activate(dc_mode, cur_pos[primary_ind]) dcs[secondary_ind].override_axis_scaling( abs(dl[secondary_ind] / dl[primary_ind]), @@ -312,18 +338,26 @@ class DualCarriages: # Make sure the scaling coefficients are restored with the mode for dc in dcs: dc.inactivate(move_pos) + saved_modes = saved_state['carriage_modes'] + saved_primary_dcs = [dc for dc in self.dc_rails.values() + if saved_modes[dc.get_name()] == PRIMARY] + # First activate all primary carriages + for dc in saved_primary_dcs: + self.activate_dc_mode(dc, PRIMARY) + # Then set the modes the remaining carriages for dc in self.dc_rails.values(): - saved_mode = saved_state['carriage_modes'][dc.get_name()] - self.activate_dc_mode(dc, saved_mode) + if dc not in saved_primary_dcs: + self.activate_dc_mode(dc, saved_modes[dc.get_name()]) class DualCarriagesRail: ENC_AXES = [b'x', b'y'] - def __init__(self, printer, rail, dual_rail, axis, active): + def __init__(self, printer, rail, dual_rail, axis, safe_dist, active): self.printer = printer self.rail = rail self.dual_rail = dual_rail self.sks = [s.get_stepper_kinematics() for s in rail.get_steppers()] self.axis = axis + self.safe_dist = safe_dist self.mode = (INACTIVE, PRIMARY)[active] self.offset = 0. self.scale = 1. if active else 0. diff --git a/klippy/mcu.py b/klippy/mcu.py index 14be8a5c0..909b8ddb5 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -22,6 +22,20 @@ MAX_NOMINAL_DURATION = 3.0 # Command transmit helper classes ###################################################################### +# Generate a dummy response to query commands when in debugging mode +class DummyResponse: + def __init__(self, serial, name, oid=None): + params = {} + if oid is not None: + params['oid'] = oid + msgparser = serial.get_msgparser() + resp = msgparser.create_dummy_response(name, params) + resp['#sent_time'] = 0. + resp['#receive_time'] = 0. + self._response = resp + def get_response(self, cmds, cmd_queue, minclock=0, reqclock=0, retry=True): + return dict(self._response) + # Class to retry sending of a query command until a given response is received class RetryAsyncCommand: TIMEOUT_TIME = 5.0 @@ -60,16 +74,18 @@ class RetryAsyncCommand: # Wrapper around query commands class CommandQueryWrapper: - def __init__(self, serial, msgformat, respformat, oid=None, - cmd_queue=None, is_async=False, error=serialhdl.error): - self._serial = serial + def __init__(self, conn_helper, msgformat, respformat, oid=None, + cmd_queue=None, is_async=False): + self._serial = serial = conn_helper.get_serial() self._cmd = serial.get_msgparser().lookup_command(msgformat) serial.get_msgparser().lookup_command(respformat) self._response = respformat.split()[0] self._oid = oid - self._error = error + self._error = conn_helper.get_mcu().get_printer().command_error self._xmit_helper = serialhdl.SerialRetryCommand - if is_async: + if conn_helper.get_mcu().is_fileoutput(): + self._xmit_helper = DummyResponse + elif is_async: self._xmit_helper = RetryAsyncCommand if cmd_queue is None: cmd_queue = serial.get_default_command_queue() @@ -92,15 +108,15 @@ class CommandQueryWrapper: # Wrapper around command sending class CommandWrapper: - def __init__(self, serial, msgformat, cmd_queue=None, debugoutput=False): - self._serial = serial + def __init__(self, conn_helper, msgformat, cmd_queue=None): + self._serial = serial = conn_helper.get_serial() msgparser = serial.get_msgparser() self._cmd = msgparser.lookup_command(msgformat) if cmd_queue is None: cmd_queue = serial.get_default_command_queue() self._cmd_queue = cmd_queue self._msgtag = msgparser.lookup_msgid(msgformat) & 0xffffffff - if debugoutput: + if conn_helper.get_mcu().is_fileoutput(): # Can't use send_wait_ack when in debugging mode self.send_wait_ack = self.send def send(self, data=(), minclock=0, reqclock=0): @@ -219,12 +235,11 @@ class MCU_trsync: self._mcu.register_response(self._handle_trsync_state, "trsync_state", self._oid) self._trsync_start_cmd.send([self._oid, report_clock, report_ticks, - self.REASON_COMMS_TIMEOUT], - reqclock=report_clock) + self.REASON_COMMS_TIMEOUT], reqclock=clock) for s in self._steppers: self._stepper_stop_cmd.send([s.get_oid(), self._oid]) self._trsync_set_timeout_cmd.send([self._oid, expire_clock], - reqclock=expire_clock) + reqclock=clock) def set_home_end_time(self, home_end_time): self._home_end_clock = self._mcu.print_time_to_clock(home_end_time) def stop(self): @@ -422,6 +437,7 @@ class MCU_pwm: self._invert = pin_params['invert'] self._start_value = self._shutdown_value = float(self._invert) self._last_clock = 0 + self._last_value = .0 self._pwm_max = 0. self._set_cmd = None def get_mcu(self): @@ -437,6 +453,7 @@ class MCU_pwm: shutdown_value = 1. - shutdown_value self._start_value = max(0., min(1., start_value)) self._shutdown_value = max(0., min(1., shutdown_value)) + self._last_value = self._start_value def _build_config(self): if self._max_duration and self._start_value != self._shutdown_value: raise pins.error("Pin with max duration must have start" @@ -488,6 +505,20 @@ class MCU_pwm: % (self._oid, self._last_clock, svalue), is_init=True) self._set_cmd = self._mcu.lookup_command( "queue_digital_out oid=%c clock=%u on_ticks=%u", cq=cmd_queue) + def next_aligned_print_time(self, print_time, allow_early=0.): + # Filter cases where there is no need to sync anything + if self._hardware_pwm: + return print_time + if self._last_value == 1. or self._last_value == .0: + return print_time + # Simplify the calling and allow scheduling slightly earlier + req_ptime = print_time - min(allow_early, 0.5 * self._cycle_time) + cycle_ticks = self._mcu.seconds_to_clock(self._cycle_time) + req_clock = self._mcu.print_time_to_clock(req_ptime) + last_clock = self._last_clock + pulses = (req_clock - last_clock + cycle_ticks - 1) // cycle_ticks + next_clock = last_clock + pulses * cycle_ticks + return self._mcu.clock_to_print_time(next_clock) def set_pwm(self, print_time, value): if self._invert: value = 1. - value @@ -496,6 +527,7 @@ class MCU_pwm: self._set_cmd.send([self._oid, clock, v], minclock=self._last_clock, reqclock=clock) self._last_clock = clock + self._last_value = value class MCU_adc: def __init__(self, mcu, pin_params): @@ -1080,12 +1112,11 @@ class MCU: def max_nominal_duration(self): return MAX_NOMINAL_DURATION def lookup_command(self, msgformat, cq=None): - return CommandWrapper(self._serial, msgformat, cq, - debugoutput=self.is_fileoutput()) + return CommandWrapper(self._conn_helper, msgformat, cq) def lookup_query_command(self, msgformat, respformat, oid=None, cq=None, is_async=False): - return CommandQueryWrapper(self._serial, msgformat, respformat, oid, - cq, is_async, self._printer.command_error) + return CommandQueryWrapper(self._conn_helper, msgformat, respformat, + oid, cq, is_async) def try_lookup_command(self, msgformat): try: return self.lookup_command(msgformat) diff --git a/klippy/msgproto.py b/klippy/msgproto.py index 25701df36..021a025a9 100644 --- a/klippy/msgproto.py +++ b/klippy/msgproto.py @@ -353,6 +353,27 @@ class MessageParser: #logging.exception("Unable to encode") self._error("Unable to encode: %s", msgname) return cmd + def create_dummy_response(self, msgname, params={}): + mp = self.messages_by_name.get(msgname) + if mp is None: + self._error("Unknown response: %s", msgname) + argparts = dict(params) + for name, t in mp.name_to_type.items(): + if name not in argparts: + tval = 0 + if t.is_dynamic_string: + tval = () + argparts[name] = tval + try: + msg = mp.encode_by_name(**argparts) + except error as e: + raise + except: + #logging.exception("Unable to encode") + self._error("Unable to encode: %s", msgname) + res, pos = mp.parse(msg, 0) + res['#name'] = msgname + return res def fill_enumerations(self, enumerations): for add_name, add_enums in enumerations.items(): enums = self.enumerations.setdefault(add_name, {}) diff --git a/lib/README b/lib/README index a106abfd0..387714105 100644 --- a/lib/README +++ b/lib/README @@ -107,7 +107,7 @@ taken from the Drivers/CMSIS/Device/ST/STM32H7xx/ directory. The pico-sdk directory contains code from the pico sdk: https://github.com/raspberrypi/pico-sdk.git -version 2.0.0 (efe2103f9b28458a1615ff096054479743ade236). It has been +version 2.2.0 (a1438dff1d38bd9c65dbd693f0e5db4b9ae91779). It has been modified so that it can build outside of the pico sdk. See pico-sdk.patch for the modifications. diff --git a/lib/pico-sdk/boot/bootrom_constants.h b/lib/pico-sdk/boot/bootrom_constants.h new file mode 100644 index 000000000..e7fe6f63b --- /dev/null +++ b/lib/pico-sdk/boot/bootrom_constants.h @@ -0,0 +1,344 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _BOOT_BOOTROM_CONSTANTS_H +#define _BOOT_BOOTROM_CONSTANTS_H + +#ifndef NO_PICO_PLATFORM +#include "pico/platform.h" +#endif + +// ROOT ADDRESSES +#define BOOTROM_MAGIC_OFFSET 0x10 +#define BOOTROM_FUNC_TABLE_OFFSET 0x14 +#if PICO_RP2040 +#define BOOTROM_DATA_TABLE_OFFSET 0x16 +#endif + +#if PICO_RP2040 +#define BOOTROM_VTABLE_OFFSET 0x00 +#define BOOTROM_TABLE_LOOKUP_OFFSET 0x18 +#else +#define BOOTROM_WELL_KNOWN_PTR_SIZE 2 +#if defined(__riscv) +#define BOOTROM_ENTRY_OFFSET 0x7dfc +#define BOOTROM_TABLE_LOOKUP_ENTRY_OFFSET (BOOTROM_ENTRY_OFFSET - BOOTROM_WELL_KNOWN_PTR_SIZE) +#define BOOTROM_TABLE_LOOKUP_OFFSET (BOOTROM_ENTRY_OFFSET - BOOTROM_WELL_KNOWN_PTR_SIZE*2) +#else +#define BOOTROM_VTABLE_OFFSET 0x00 +#define BOOTROM_TABLE_LOOKUP_OFFSET (BOOTROM_FUNC_TABLE_OFFSET + BOOTROM_WELL_KNOWN_PTR_SIZE) +#endif +#endif + +#if !PICO_RP2040 || PICO_COMBINED_DOCS + +#define BOOTROM_OK 0 +//#define BOOTROM_ERROR_TIMEOUT (-1) +//#define BOOTROM_ERROR_GENERIC (-2) +//#define BOOTROM_ERROR_NO_DATA (-3) // E.g. read from an empty buffer/FIFO +#define BOOTROM_ERROR_NOT_PERMITTED (-4) // Permission violation e.g. write to read-only flash partition +#define BOOTROM_ERROR_INVALID_ARG (-5) // Argument is outside of range of supported values` +//#define BOOTROM_ERROR_IO (-6) +//#define BOOTROM_ERROR_BADAUTH (-7) +//#define BOOTROM_ERROR_CONNECT_FAILED (-8) +//#define BOOTROM_ERROR_INSUFFICIENT_RESOURCES (-9) // Dynamic allocation of resources failed +#define BOOTROM_ERROR_INVALID_ADDRESS (-10) // Address argument was out-of-bounds or was determined to be an address that the caller may not access +#define BOOTROM_ERROR_BAD_ALIGNMENT (-11) // Address modulo transfer chunk size was nonzero (e.g. word-aligned transfer with address % 4 != 0) +#define BOOTROM_ERROR_INVALID_STATE (-12) // Something happened or failed to happen in the past, and consequently we (currently) can't service the request +#define BOOTROM_ERROR_BUFFER_TOO_SMALL (-13) // A user-allocated buffer was too small to hold the result or working state of this function +#define BOOTROM_ERROR_PRECONDITION_NOT_MET (-14) // This call failed because another ROM function must be called first +#define BOOTROM_ERROR_MODIFIED_DATA (-15) // Cached data was determined to be inconsistent with the full version of the data it was calculated from +#define BOOTROM_ERROR_INVALID_DATA (-16) // A data structure failed to validate +#define BOOTROM_ERROR_NOT_FOUND (-17) // Attempted to access something that does not exist; or, a search failed +#define BOOTROM_ERROR_UNSUPPORTED_MODIFICATION (-18) // Write is impossible based on previous writes; e.g. attempted to clear an OTP bit +#define BOOTROM_ERROR_LOCK_REQUIRED (-19) // A required lock is not owned +#define BOOTROM_ERROR_LAST (-19) + +#define RT_FLAG_FUNC_RISCV 0x0001 +#define RT_FLAG_FUNC_RISCV_FAR 0x0003 +#define RT_FLAG_FUNC_ARM_SEC 0x0004 +// reserved for 32-bit pointer: 0x0008 +#define RT_FLAG_FUNC_ARM_NONSEC 0x0010 +// reserved for 32-bit pointer: 0x0020 +#define RT_FLAG_DATA 0x0040 +// reserved for 32-bit pointer: 0x0080 + +#define PARTITION_TABLE_MAX_PARTITIONS 16 +// note this is deliberately > MAX_PARTITIONs is likely to be, and also -1 as a signed byte +#define PARTITION_TABLE_NO_PARTITION_INDEX 0xff + +// todo these are duplicated in picoboot_constants.h +// values 0-7 are secure/non-secure +#define BOOT_TYPE_NORMAL 0 +#define BOOT_TYPE_BOOTSEL 2 +#define BOOT_TYPE_RAM_IMAGE 3 +#define BOOT_TYPE_FLASH_UPDATE 4 + +// values 8-15 are secure only +#define BOOT_TYPE_PC_SP 0xd + +// ORed in if a bootloader chained into the image +#define BOOT_TYPE_CHAINED_FLAG 0x80 + +// call from NS to S +#ifndef __riscv +#define BOOTROM_API_CALLBACK_secure_call 0 +#endif +#define BOOTROM_API_CALLBACK_COUNT 1 + +#define BOOTROM_LOCK_SHA_256 0 +#define BOOTROM_LOCK_FLASH_OP 1 +#define BOOTROM_LOCK_OTP 2 +#define BOOTROM_LOCK_MAX 2 + +#define BOOTROM_LOCK_ENABLE 7 + +#define BOOT_PARTITION_NONE (-1) +#define BOOT_PARTITION_SLOT0 (-2) +#define BOOT_PARTITION_SLOT1 (-3) +#define BOOT_PARTITION_WINDOW (-4) + +#define BOOT_DIAGNOSTIC_WINDOW_SEARCHED 0x01 +// note if both BOOT_DIAGNOSTIC_INVALID_BLOCK_LOOP and BOOT_DIAGNOSTIC_VALID_BLOCK_LOOP then the block loop was valid +// but it has a PARTITION_TABLE which while it passed the initial verification (and hash/sig) had invalid contents +// (discovered when it was later loaded) +#define BOOT_DIAGNOSTIC_INVALID_BLOCK_LOOP 0x02 +#define BOOT_DIAGNOSTIC_VALID_BLOCK_LOOP 0x04 +#define BOOT_DIAGNOSTIC_VALID_IMAGE_DEF 0x08 +#define BOOT_DIAGNOSTIC_HAS_PARTITION_TABLE 0x10 +#define BOOT_DIAGNOSTIC_CONSIDERED 0x20 +#define BOOT_DIAGNOSTIC_CHOSEN 0x40 +#define BOOT_DIAGNOSTIC_PARTITION_TABLE_LSB 7 +#define BOOT_DIAGNOSTIC_PARTITION_TABLE_MATCHING_KEY_FOR_VERIFY 0x80 +#define BOOT_DIAGNOSTIC_PARTITION_TABLE_HASH_FOR_VERIFY 0x100 +#define BOOT_DIAGNOSTIC_PARTITION_TABLE_VERIFIED_OK 0x200 +#define BOOT_DIAGNOSTIC_IMAGE_DEF_LSB 10 +#define BOOT_DIAGNOSTIC_IMAGE_DEF_MATCHING_KEY_FOR_VERIFY 0x400 +#define BOOT_DIAGNOSTIC_IMAGE_DEF_HASH_FOR_VERIFY 0x800 +#define BOOT_DIAGNOSTIC_IMAGE_DEF_VERIFIED_OK 0x1000 + +#define BOOT_DIAGNOSTIC_LOAD_MAP_ENTRIES_LOADED 0x2000 +#define BOOT_DIAGNOSTIC_IMAGE_LAUNCHED 0x4000 +#define BOOT_DIAGNOSTIC_IMAGE_CONDITION_FAILURE 0x8000 + +#define BOOT_PARSED_BLOCK_DIAGNOSTIC_MATCHING_KEY_FOR_VERIFY 0x1 // if this is present and VERIFIED_OK isn't the sig check failed +#define BOOT_PARSED_BLOCK_DIAGNOSTIC_HASH_FOR_VERIFY 0x2 // if this is present and VERIFIED_OL isn't then hash check failed +#define BOOT_PARSED_BLOCK_DIAGNOSTIC_VERIFIED_OK 0x4 + +#define BOOT_TBYB_AND_UPDATE_FLAG_BUY_PENDING 0x1 +#define BOOT_TBYB_AND_UPDATE_FLAG_OTP_VERSION_APPLIED 0x2 +#define BOOT_TBYB_AND_UPDATE_FLAG_OTHER_ERASED 0x4 + +#ifndef __ASSEMBLER__ +// Limited to 3 arguments in case of varm multiplex hint (trashes Arm r3) +typedef int (*bootrom_api_callback_generic_t)(uint32_t r0, uint32_t r1, uint32_t r2); +// Return negative for error, else number of bytes transferred: +//typedef int (*bootrom_api_callback_stdout_put_blocking_t)(const uint8_t *buffer, uint32_t size); +//typedef int (*bootrom_api_callback_stdin_get_t)(uint8_t *buffer, uint32_t size); +//typedef void (*bootrom_api_callback_core1_security_setup_t)(void); +#endif + +#endif + +/*! \brief Return a bootrom lookup code based on two ASCII characters + * \ingroup pico_bootrom + * + * These codes are uses to lookup data or function addresses in the bootrom + * + * \param c1 the first character + * \param c2 the second character + * \return the 'code' to use in rom_func_lookup() or rom_data_lookup() + */ +#define ROM_TABLE_CODE(c1, c2) ((c1) | ((c2) << 8)) + +// ROM FUNCTIONS + +// RP2040 & RP2350 +#define ROM_DATA_SOFTWARE_GIT_REVISION ROM_TABLE_CODE('G', 'R') +#define ROM_FUNC_FLASH_ENTER_CMD_XIP ROM_TABLE_CODE('C', 'X') +#define ROM_FUNC_FLASH_EXIT_XIP ROM_TABLE_CODE('E', 'X') +#define ROM_FUNC_FLASH_FLUSH_CACHE ROM_TABLE_CODE('F', 'C') +#define ROM_FUNC_CONNECT_INTERNAL_FLASH ROM_TABLE_CODE('I', 'F') +#define ROM_FUNC_FLASH_RANGE_ERASE ROM_TABLE_CODE('R', 'E') +#define ROM_FUNC_FLASH_RANGE_PROGRAM ROM_TABLE_CODE('R', 'P') + + +#if PICO_RP2040 +// RP2040 only +#define ROM_FUNC_MEMCPY44 ROM_TABLE_CODE('C', '4') +#define ROM_DATA_COPYRIGHT ROM_TABLE_CODE('C', 'R') +#define ROM_FUNC_CLZ32 ROM_TABLE_CODE('L', '3') +#define ROM_FUNC_MEMCPY ROM_TABLE_CODE('M', 'C') +#define ROM_FUNC_MEMSET ROM_TABLE_CODE('M', 'S') +#define ROM_FUNC_POPCOUNT32 ROM_TABLE_CODE('P', '3') +#define ROM_FUNC_REVERSE32 ROM_TABLE_CODE('R', '3') +#define ROM_FUNC_MEMSET4 ROM_TABLE_CODE('S', '4') +#define ROM_FUNC_CTZ32 ROM_TABLE_CODE('T', '3') +#define ROM_FUNC_RESET_USB_BOOT ROM_TABLE_CODE('U', 'B') +#endif + +#if !PICO_RP2040 || PICO_COMBINED_DOCS +// RP2350 only +#define ROM_FUNC_PICK_AB_PARTITION ROM_TABLE_CODE('A', 'B') +#define ROM_FUNC_CHAIN_IMAGE ROM_TABLE_CODE('C', 'I') +#define ROM_FUNC_EXPLICIT_BUY ROM_TABLE_CODE('E', 'B') +#define ROM_FUNC_FLASH_RUNTIME_TO_STORAGE_ADDR ROM_TABLE_CODE('F', 'A') +#define ROM_DATA_FLASH_DEVINFO16_PTR ROM_TABLE_CODE('F', 'D') +#define ROM_FUNC_FLASH_OP ROM_TABLE_CODE('F', 'O') +#define ROM_FUNC_GET_B_PARTITION ROM_TABLE_CODE('G', 'B') +#define ROM_FUNC_GET_PARTITION_TABLE_INFO ROM_TABLE_CODE('G', 'P') +#define ROM_FUNC_GET_SYS_INFO ROM_TABLE_CODE('G', 'S') +#define ROM_FUNC_GET_UF2_TARGET_PARTITION ROM_TABLE_CODE('G', 'U') +#define ROM_FUNC_LOAD_PARTITION_TABLE ROM_TABLE_CODE('L', 'P') +#define ROM_FUNC_OTP_ACCESS ROM_TABLE_CODE('O', 'A') +#define ROM_DATA_PARTITION_TABLE_PTR ROM_TABLE_CODE('P', 'T') +#define ROM_FUNC_FLASH_RESET_ADDRESS_TRANS ROM_TABLE_CODE('R', 'A') +#define ROM_FUNC_REBOOT ROM_TABLE_CODE('R', 'B') +#define ROM_FUNC_SET_ROM_CALLBACK ROM_TABLE_CODE('R', 'C') +#define ROM_FUNC_SECURE_CALL ROM_TABLE_CODE('S', 'C') +#define ROM_FUNC_SET_NS_API_PERMISSION ROM_TABLE_CODE('S', 'P') +#define ROM_FUNC_BOOTROM_STATE_RESET ROM_TABLE_CODE('S', 'R') +#define ROM_FUNC_SET_BOOTROM_STACK ROM_TABLE_CODE('S', 'S') +#define ROM_DATA_SAVED_XIP_SETUP_FUNC_PTR ROM_TABLE_CODE('X', 'F') +#define ROM_FUNC_FLASH_SELECT_XIP_READ_MODE ROM_TABLE_CODE('X', 'M') +#define ROM_FUNC_VALIDATE_NS_BUFFER ROM_TABLE_CODE('V', 'B') +#endif + +// these form a bit set +#define BOOTROM_STATE_RESET_CURRENT_CORE 0x01 +#define BOOTROM_STATE_RESET_OTHER_CORE 0x02 +#define BOOTROM_STATE_RESET_GLOBAL_STATE 0x04 // reset any global state (e.g. permissions) + +// partition level stuff is returned first (note PT_INFO flags is only 16 bits) + +// 3 words: pt_count, unpartitioned_perm_loc, unpartioned_perm_flags +#define PT_INFO_PT_INFO 0x0001 +#define PT_INFO_SINGLE_PARTITION 0x8000 // marker to just include a single partition in the results) + +// then in order per partition selected + +// 2 words: unpartitioned_perm_loc, unpartioned_perm_flags +#define PT_INFO_PARTITION_LOCATION_AND_FLAGS 0x0010 +// 2 words: id lsb first +#define PT_INFO_PARTITION_ID 0x0020 +// n+1 words: n, family_id... +#define PT_INFO_PARTITION_FAMILY_IDS 0x0040 +// (n+3)/4 words... bytes are: n (len), c0, c1, ... cn-1 padded to word boundary with zeroes +#define PT_INFO_PARTITION_NAME 0x0080 + +// items are returned in order +// 3 words package_id, device_id_lo, device_id_hi +#define SYS_INFO_CHIP_INFO 0x0001 +// 1 word: chip specific critical bits +#define SYS_INFO_CRITICAL 0x0002 +// 1 word: bytes: cpu_type, supported_cpu_type_bitfield +#define SYS_INFO_CPU_INFO 0x0004 +// 1 word: same as FLASH_DEVINFO row in OTP +#define SYS_INFO_FLASH_DEV_INFO 0x0008 +// 4 words +#define SYS_INFO_BOOT_RANDOM 0x0010 +// 2 words lsb first +#define SYS_INFO_NONCE 0x0020 +// 4 words boot_info, boot_diagnostic, boot_param0, boot_param1 +#define SYS_INFO_BOOT_INFO 0x0040 + +#define BOOTROM_NS_API_get_sys_info 0 +#define BOOTROM_NS_API_checked_flash_op 1 +#define BOOTROM_NS_API_flash_runtime_to_storage_addr 2 +#define BOOTROM_NS_API_get_partition_table_info 3 +#define BOOTROM_NS_API_secure_call 4 +#define BOOTROM_NS_API_otp_access 5 +#define BOOTROM_NS_API_reboot 6 +#define BOOTROM_NS_API_get_b_partition 7 +#define BOOTROM_NS_API_COUNT 8 + +#define OTP_CMD_ROW_BITS _u(0x0000ffff) +#define OTP_CMD_ROW_LSB _u(0) +#define OTP_CMD_WRITE_BITS _u(0x00010000) +#define OTP_CMD_WRITE_LSB _u(16) +#define OTP_CMD_ECC_BITS _u(0x00020000) +#define OTP_CMD_ECC_LSB _u(17) + +#ifndef __ASSEMBLER__ +static_assert(OTP_CMD_WRITE_BITS == (1 << OTP_CMD_WRITE_LSB), ""); +static_assert(OTP_CMD_ECC_BITS == (1 << OTP_CMD_ECC_LSB), ""); + +typedef struct { + uint32_t permissions_and_location; + uint32_t permissions_and_flags; +} resident_partition_t; +static_assert(sizeof(resident_partition_t) == 8, ""); + +typedef struct otp_cmd { + uint32_t flags; +} otp_cmd_t; + +typedef enum { + BOOTROM_XIP_MODE_03H_SERIAL = 0, + BOOTROM_XIP_MODE_0BH_SERIAL, + BOOTROM_XIP_MODE_BBH_DUAL, + BOOTROM_XIP_MODE_EBH_QUAD, + BOOTROM_XIP_MODE_N_MODES +} bootrom_xip_mode_t; + +// The checked flash API wraps the low-level flash routines from generic_flash, adding bounds +// checking, permission checking against the resident partition table, and simple address +// translation. The low-level API deals with flash offsets (i.e. distance from the start of the +// first flash device, measured in bytes) but the checked flash API accepts one of two types of +// address: +// +// - Flash runtime addresses: the address of some flash-resident data or code in the currently +// running image. The flash addresses your binary is "linked at" by the linker. +// - Flash storage addresses: a flash offset, plus the address base where QSPI hardware is first +// mapped on the system bus (XIP_BASE constant from addressmap.h) +// +// These addresses are one and the same *if* the currently running program is stored at the +// beginning of flash. They are different if the start of your image has been "rolled" by the flash +// boot path to make it appear at the address it was linked at even though it is stored at a +// different location in flash, which is necessary when you have A/B images for example. +// +// The address translation between flash runtime and flash storage addresses is configured in +// hardware by the QMI_ATRANSx registers, and this API assumes those registers contain a valid +// address mapping which it can use to translate runtime to storage addresses. + +typedef struct cflash_flags { + uint32_t flags; +} cflash_flags_t; + +#endif // #ifdef __ASSEMBLER__ + +// Bits which are permitted to be set in a flags variable -- any other bits being set is an error +#define CFLASH_FLAGS_BITS 0x00070301u + +// Used to tell checked flash API which space a given address belongs to +#define CFLASH_ASPACE_BITS 0x00000001u +#define CFLASH_ASPACE_LSB _u(0) +#define CFLASH_ASPACE_VALUE_STORAGE _u(0) +#define CFLASH_ASPACE_VALUE_RUNTIME _u(1) + +// Used to tell checked flash APIs the effective security level of a flash access (may be forced to +// one of these values for the NonSecure-exported version of this API) +#define CFLASH_SECLEVEL_BITS 0x00000300u +#define CFLASH_SECLEVEL_LSB _u(8) +// Zero is not a valid security level: +#define CFLASH_SECLEVEL_VALUE_SECURE _u(1) +#define CFLASH_SECLEVEL_VALUE_NONSECURE _u(2) +#define CFLASH_SECLEVEL_VALUE_BOOTLOADER _u(3) + +#define CFLASH_OP_BITS 0x00070000u +#define CFLASH_OP_LSB _u(16) +// Erase size_bytes bytes of flash, starting at address addr. Both addr and size_bytes must be a +// multiple of 4096 bytes (one flash sector). +#define CFLASH_OP_VALUE_ERASE _u(0) +// Program size_bytes bytes of flash, starting at address addr. Both addr and size_bytes must be a +// multiple of 256 bytes (one flash page). +#define CFLASH_OP_VALUE_PROGRAM _u(1) +// Read size_bytes bytes of flash, starting at address addr. There are no alignment restrictions on +// addr or size_bytes. +#define CFLASH_OP_VALUE_READ _u(2) +#define CFLASH_OP_MAX _u(2) + +#endif diff --git a/lib/pico-sdk/boot/picoboot_constants.h b/lib/pico-sdk/boot/picoboot_constants.h index ac78ea213..ffb3b8cc4 100644 --- a/lib/pico-sdk/boot/picoboot_constants.h +++ b/lib/pico-sdk/boot/picoboot_constants.h @@ -9,11 +9,11 @@ #define REBOOT2_TYPE_MASK 0x0f -// note these match REBOOT_TYPE in pico/bootrom_constants.h (also 0 is used for PC_SP for backwards compatibility with RP2040) +// note these match REBOOT_TYPE in pico/bootrom_constants.h // values 0-7 are secure/non-secure #define REBOOT2_FLAG_REBOOT_TYPE_NORMAL 0x0 // param0 = diagnostic partition -#define REBOOT2_FLAG_REBOOT_TYPE_BOOTSEL 0x2 // param0 = bootsel_flags, param1 = gpio_config -#define REBOOT2_FLAG_REBOOT_TYPE_RAM_IMAGE 0x3 // param0 = image_base, param1 = image_end +#define REBOOT2_FLAG_REBOOT_TYPE_BOOTSEL 0x2 // param0 = gpio_pin_number, param1 = flags +#define REBOOT2_FLAG_REBOOT_TYPE_RAM_IMAGE 0x3 // param0 = image_region_base, param1 = image_region_size #define REBOOT2_FLAG_REBOOT_TYPE_FLASH_UPDATE 0x4 // param0 = update_base // values 8-15 are secure only @@ -39,4 +39,4 @@ #define UF2_STATUS_ABORT_BAD_ADDRESS 0x20 #define UF2_STATUS_ABORT_WRITE_ERROR 0x40 #define UF2_STATUS_ABORT_REBOOT_FAILED 0x80 -#endif \ No newline at end of file +#endif diff --git a/lib/pico-sdk/boot/uf2.h b/lib/pico-sdk/boot/uf2.h index 271540a20..279d4a131 100644 --- a/lib/pico-sdk/boot/uf2.h +++ b/lib/pico-sdk/boot/uf2.h @@ -20,19 +20,30 @@ #define UF2_MAGIC_START1 0x9E5D5157u #define UF2_MAGIC_END 0x0AB16F30u -#define UF2_FLAG_NOT_MAIN_FLASH 0x00000001u -#define UF2_FLAG_FILE_CONTAINER 0x00001000u -#define UF2_FLAG_FAMILY_ID_PRESENT 0x00002000u -#define UF2_FLAG_MD5_PRESENT 0x00004000u +#define UF2_FLAG_NOT_MAIN_FLASH 0x00000001u +#define UF2_FLAG_FILE_CONTAINER 0x00001000u +#define UF2_FLAG_FAMILY_ID_PRESENT 0x00002000u +#define UF2_FLAG_MD5_PRESENT 0x00004000u +#define UF2_FLAG_EXTENSION_FLAGS_PRESENT 0x00008000u +// Extra family IDs +#define CYW43_FIRMWARE_FAMILY_ID 0xe48bff55u + +// Bootrom supported family IDs #define RP2040_FAMILY_ID 0xe48bff56u #define ABSOLUTE_FAMILY_ID 0xe48bff57u #define DATA_FAMILY_ID 0xe48bff58u #define RP2350_ARM_S_FAMILY_ID 0xe48bff59u #define RP2350_RISCV_FAMILY_ID 0xe48bff5au #define RP2350_ARM_NS_FAMILY_ID 0xe48bff5bu -#define FAMILY_ID_MAX 0xe48bff5bu +#define BOOTROM_FAMILY_ID_MIN RP2040_FAMILY_ID +#define BOOTROM_FAMILY_ID_MAX RP2350_ARM_NS_FAMILY_ID +// Defined for backwards compatibility +#define FAMILY_ID_MAX BOOTROM_FAMILY_ID_MAX + +// 04 e3 57 99 +#define UF2_EXTENSION_RP2_IGNORE_BLOCK 0x9957e304 struct uf2_block { // 32 byte header diff --git a/lib/pico-sdk/hardware/platform_defs.h b/lib/pico-sdk/hardware/platform_defs.h index 924336a92..c1a6f5f44 100644 --- a/lib/pico-sdk/hardware/platform_defs.h +++ b/lib/pico-sdk/hardware/platform_defs.h @@ -15,6 +15,9 @@ #define NUM_ALARMS 4u #define NUM_IRQS 32u +#define NUM_USER_IRQS 6u +#define FIRST_USER_IRQ (NUM_IRQS - NUM_USER_IRQS) +#define VTABLE_FIRST_IRQ 16 #define NUM_SPIN_LOCKS 32u diff --git a/lib/pico-sdk/pico-sdk.patch b/lib/pico-sdk/pico-sdk.patch index 0a27b4605..91b5dfd9c 100644 --- a/lib/pico-sdk/pico-sdk.patch +++ b/lib/pico-sdk/pico-sdk.patch @@ -1,3 +1,29 @@ +This file summarizes the local changes from the upstream pico-sdk +repository (version 2.2.0). In brief, the following steps can be used +to recreate the code here from the main pico-sdk code: + +cp /pico-sdk/src/common/boot_uf2_headers/include/boot/*.h boot/ +cp /pico-sdk/src/common/boot_picoboot_headers/include/boot/*.h boot/ +cp /pico-sdk/src/rp2_common/boot_bootrom_headers/include/boot/*.h boot/ + +cp /pico-sdk/src/rp2_common/hardware_base/include/hardware/*.h hardware/ +cp /pico-sdk/src/host/pico_platform/include/hardware/*.h hardware/ + +cp /pico-sdk/src/rp2_common/pico_bootrom/include/pico/bootrom_constants.h pico/ +cp /pico-sdk/src/host/pico_platform/include/pico/*.h pico/ + +cp -a /pico-sdk/src/rp2040/boot_stage2/ rp2040/ +cp /pico-sdk/src/rp2_common/cmsis/stub/CMSIS/Device/RP2040/Include/*.h rp2040/cmsis_include/ +cp -a /pico-sdk/src/rp2040/hardware_regs/include/hardware rp2040/ +cp -a /pico-sdk/src/rp2040/hardware_structs/include/hardware rp2040/ +cp /pico-sdk/src/rp2040/pico_platform/include/pico/*.S rp2040/pico/ + +cp /pico-sdk/src/rp2_common/cmsis/stub/CMSIS/Device/RP2350/Include/*.h rp2350/cmsis_include/ +cp /pico-sdk/src/rp2350/hardware_regs/include/hardware/regs/*.h rp2350/hardware/regs/ +cp /pico-sdk/src/rp2350/hardware_structs/include/hardware/structs/*.h rp2350/hardware/structs/ + +patch -p3 < pico-sdk.patch + diff --git a/lib/pico-sdk/hardware/address_mapped.h b/lib/pico-sdk/hardware/address_mapped.h index b384f5572..635a275b5 100644 --- a/lib/pico-sdk/hardware/address_mapped.h diff --git a/lib/pico-sdk/pico/bootrom_constants.h b/lib/pico-sdk/pico/bootrom_constants.h index 924487f8c..b3bfd47ea 100644 --- a/lib/pico-sdk/pico/bootrom_constants.h +++ b/lib/pico-sdk/pico/bootrom_constants.h @@ -4,339 +4,5 @@ * SPDX-License-Identifier: BSD-3-Clause */ -#ifndef _PICO_BOOTROM_CONSTANTS_H -#define _PICO_BOOTROM_CONSTANTS_H - -#ifndef NO_PICO_PLATFORM -#include "pico/platform.h" -#endif - -// ROOT ADDRESSES -#define BOOTROM_MAGIC_OFFSET 0x10 -#define BOOTROM_FUNC_TABLE_OFFSET 0x14 -#if PICO_RP2040 -#define BOOTROM_DATA_TABLE_OFFSET 0x16 -#endif - -#if PICO_RP2040 -#define BOOTROM_VTABLE_OFFSET 0x00 -#define BOOTROM_TABLE_LOOKUP_OFFSET 0x18 -#else -// todo remove this (or #ifdef it for A1/A2) -#define BOOTROM_IS_A2() ((*(volatile uint8_t *)0x13) == 2) -#define BOOTROM_WELL_KNOWN_PTR_SIZE (BOOTROM_IS_A2() ? 2 : 4) -#if defined(__riscv) -#define BOOTROM_ENTRY_OFFSET 0x7dfc -#define BOOTROM_TABLE_LOOKUP_ENTRY_OFFSET (BOOTROM_ENTRY_OFFSET - BOOTROM_WELL_KNOWN_PTR_SIZE) -#define BOOTROM_TABLE_LOOKUP_OFFSET (BOOTROM_ENTRY_OFFSET - BOOTROM_WELL_KNOWN_PTR_SIZE*2) -#else -#define BOOTROM_VTABLE_OFFSET 0x00 -#define BOOTROM_TABLE_LOOKUP_OFFSET (BOOTROM_FUNC_TABLE_OFFSET + BOOTROM_WELL_KNOWN_PTR_SIZE) -#endif -#endif - -#if !PICO_RP2040 || PICO_COMBINED_DOCS - -#define BOOTROM_OK 0 -//#define BOOTROM_ERROR_TIMEOUT (-1) -//#define BOOTROM_ERROR_GENERIC (-2) -//#define BOOTROM_ERROR_NO_DATA (-3) // E.g. read from an empty buffer/FIFO -#define BOOTROM_ERROR_NOT_PERMITTED (-4) // Permission violation e.g. write to read-only flash partition -#define BOOTROM_ERROR_INVALID_ARG (-5) // Argument is outside of range of supported values` -//#define BOOTROM_ERROR_IO (-6) -//#define BOOTROM_ERROR_BADAUTH (-7) -//#define BOOTROM_ERROR_CONNECT_FAILED (-8) -//#define BOOTROM_ERROR_INSUFFICIENT_RESOURCES (-9) // Dynamic allocation of resources failed -#define BOOTROM_ERROR_INVALID_ADDRESS (-10) // Address argument was out-of-bounds or was determined to be an address that the caller may not access -#define BOOTROM_ERROR_BAD_ALIGNMENT (-11) // Address modulo transfer chunk size was nonzero (e.g. word-aligned transfer with address % 4 != 0) -#define BOOTROM_ERROR_INVALID_STATE (-12) // Something happened or failed to happen in the past, and consequently we (currently) can't service the request -#define BOOTROM_ERROR_BUFFER_TOO_SMALL (-13) // A user-allocated buffer was too small to hold the result or working state of this function -#define BOOTROM_ERROR_PRECONDITION_NOT_MET (-14) // This call failed because another ROM function must be called first -#define BOOTROM_ERROR_MODIFIED_DATA (-15) // Cached data was determined to be inconsistent with the full version of the data it was calculated from -#define BOOTROM_ERROR_INVALID_DATA (-16) // A data structure failed to validate -#define BOOTROM_ERROR_NOT_FOUND (-17) // Attempted to access something that does not exist; or, a search failed -#define BOOTROM_ERROR_UNSUPPORTED_MODIFICATION (-18) // Write is impossible based on previous writes; e.g. attempted to clear an OTP bit -#define BOOTROM_ERROR_LOCK_REQUIRED (-19) // A required lock is not owned -#define BOOTROM_ERROR_LAST (-19) - -#define RT_FLAG_FUNC_RISCV 0x0001 -#define RT_FLAG_FUNC_RISCV_FAR 0x0003 -#define RT_FLAG_FUNC_ARM_SEC 0x0004 -// reserved for 32-bit pointer: 0x0008 -#define RT_FLAG_FUNC_ARM_NONSEC 0x0010 -// reserved for 32-bit pointer: 0x0020 -#define RT_FLAG_DATA 0x0040 -// reserved for 32-bit pointer: 0x0080 - -#define PARTITION_TABLE_MAX_PARTITIONS 16 -// note this is deliberately > MAX_PARTITIONs is likely to be, and also -1 as a signed byte -#define PARTITION_TABLE_NO_PARTITION_INDEX 0xff - -// todo these are duplicated in picoboot_constants.h -// values 0-7 are secure/non-secure -#define BOOT_TYPE_NORMAL 0 -#define BOOT_TYPE_BOOTSEL 2 -#define BOOT_TYPE_RAM_IMAGE 3 -#define BOOT_TYPE_FLASH_UPDATE 4 - -// values 8-15 are secure only -#define BOOT_TYPE_PC_SP 0xd - -// ORed in if a bootloader chained into the image -#define BOOT_TYPE_CHAINED_FLAG 0x80 - -// call from NS to S -#ifndef __riscv -#define BOOTROM_API_CALLBACK_secure_call 0 -#endif -#define BOOTROM_API_CALLBACK_COUNT 1 - -#define BOOTROM_LOCK_SHA_256 0 -#define BOOTROM_LOCK_FLASH_OP 1 -#define BOOTROM_LOCK_OTP 2 -#define BOOTROM_LOCK_MAX 2 - -#define BOOTROM_LOCK_ENABLE 7 - -#define BOOT_PARTITION_NONE (-1) -#define BOOT_PARTITION_SLOT0 (-2) -#define BOOT_PARTITION_SLOT1 (-3) -#define BOOT_PARTITION_WINDOW (-4) - -#define BOOT_DIAGNOSTIC_WINDOW_SEARCHED 0x01 -// note if both BOOT_DIAGNOSTIC_INVALID_BLOCK_LOOP and BOOT_DIAGNOSTIC_VALID_BLOCK_LOOP then the block loop was valid -// but it has a PARTITION_TABLE which while it passed the initial verification (and hash/sig) had invalid contents -// (discovered when it was later loaded) -#define BOOT_DIAGNOSTIC_INVALID_BLOCK_LOOP 0x02 -#define BOOT_DIAGNOSTIC_VALID_BLOCK_LOOP 0x04 -#define BOOT_DIAGNOSTIC_VALID_IMAGE_DEF 0x08 -#define BOOT_DIAGNOSTIC_HAS_PARTITION_TABLE 0x10 -#define BOOT_DIAGNOSTIC_CONSIDERED 0x20 -#define BOOT_DIAGNOSTIC_CHOSEN 0x40 -#define BOOT_DIAGNOSTIC_PARTITION_TABLE_LSB 7 -#define BOOT_DIAGNOSTIC_PARTITION_TABLE_MATCHING_KEY_FOR_VERIFY 0x80 -#define BOOT_DIAGNOSTIC_PARTITION_TABLE_HASH_FOR_VERIFY 0x100 -#define BOOT_DIAGNOSTIC_PARTITION_TABLE_VERIFIED_OK 0x200 -#define BOOT_DIAGNOSTIC_IMAGE_DEF_LSB 10 -#define BOOT_DIAGNOSTIC_IMAGE_DEF_MATCHING_KEY_FOR_VERIFY 0x400 -#define BOOT_DIAGNOSTIC_IMAGE_DEF_HASH_FOR_VERIFY 0x800 -#define BOOT_DIAGNOSTIC_IMAGE_DEF_VERIFIED_OK 0x1000 - -#define BOOT_DIAGNOSTIC_LOAD_MAP_ENTRIES_LOADED 0x2000 -#define BOOT_DIAGNOSTIC_IMAGE_LAUNCHED 0x4000 -#define BOOT_DIAGNOSTIC_IMAGE_CONDITION_FAILURE 0x8000 - -#define BOOT_PARSED_BLOCK_DIAGNOSTIC_MATCHING_KEY_FOR_VERIFY 0x1 // if this is present and VERIFIED_OK isn't the sig check failed -#define BOOT_PARSED_BLOCK_DIAGNOSTIC_HASH_FOR_VERIFY 0x2 // if this is present and VERIFIED_OL isn't then hash check failed -#define BOOT_PARSED_BLOCK_DIAGNOSTIC_VERIFIED_OK 0x4 - -#define BOOT_TBYB_AND_UPDATE_FLAG_BUY_PENDING 0x1 -#define BOOT_TBYB_AND_UPDATE_FLAG_OTP_VERSION_APPLIED 0x2 -#define BOOT_TBYB_AND_UPDATE_FLAG_OTHER_ERASED 0x4 - -#ifndef __ASSEMBLER__ -// Limited to 3 arguments in case of varm multiplex hint (trashes Arm r3) -typedef int (*bootrom_api_callback_generic_t)(uint32_t r0, uint32_t r1, uint32_t r2); -// Return negative for error, else number of bytes transferred: -//typedef int (*bootrom_api_callback_stdout_put_blocking_t)(const uint8_t *buffer, uint32_t size); -//typedef int (*bootrom_api_callback_stdin_get_t)(uint8_t *buffer, uint32_t size); -//typedef void (*bootrom_api_callback_core1_security_setup_t)(void); -#endif - -#endif - -/*! \brief Return a bootrom lookup code based on two ASCII characters - * \ingroup pico_bootrom - * - * These codes are uses to lookup data or function addresses in the bootrom - * - * \param c1 the first character - * \param c2 the second character - * \return the 'code' to use in rom_func_lookup() or rom_data_lookup() - */ -#define ROM_TABLE_CODE(c1, c2) ((c1) | ((c2) << 8)) - -// ROM FUNCTIONS - -// RP2040 & RP2350 -#define ROM_DATA_SOFTWARE_GIT_REVISION ROM_TABLE_CODE('G', 'R') -#define ROM_FUNC_FLASH_ENTER_CMD_XIP ROM_TABLE_CODE('C', 'X') -#define ROM_FUNC_FLASH_EXIT_XIP ROM_TABLE_CODE('E', 'X') -#define ROM_FUNC_FLASH_FLUSH_CACHE ROM_TABLE_CODE('F', 'C') -#define ROM_FUNC_CONNECT_INTERNAL_FLASH ROM_TABLE_CODE('I', 'F') -#define ROM_FUNC_FLASH_RANGE_ERASE ROM_TABLE_CODE('R', 'E') -#define ROM_FUNC_FLASH_RANGE_PROGRAM ROM_TABLE_CODE('R', 'P') - - -#if PICO_RP2040 -// RP2040 only -#define ROM_FUNC_MEMCPY44 ROM_TABLE_CODE('C', '4') -#define ROM_DATA_COPYRIGHT ROM_TABLE_CODE('C', 'R') -#define ROM_FUNC_CLZ32 ROM_TABLE_CODE('L', '3') -#define ROM_FUNC_MEMCPY ROM_TABLE_CODE('M', 'C') -#define ROM_FUNC_MEMSET ROM_TABLE_CODE('M', 'S') -#define ROM_FUNC_POPCOUNT32 ROM_TABLE_CODE('P', '3') -#define ROM_FUNC_REVERSE32 ROM_TABLE_CODE('R', '3') -#define ROM_FUNC_MEMSET4 ROM_TABLE_CODE('S', '4') -#define ROM_FUNC_CTZ32 ROM_TABLE_CODE('T', '3') -#define ROM_FUNC_RESET_USB_BOOT ROM_TABLE_CODE('U', 'B') -#endif - -#if !PICO_RP2040 || PICO_COMBINED_DOCS -// RP2350 only -#define ROM_FUNC_PICK_AB_PARTITION ROM_TABLE_CODE('A', 'B') -#define ROM_FUNC_CHAIN_IMAGE ROM_TABLE_CODE('C', 'I') -#define ROM_FUNC_EXPLICIT_BUY ROM_TABLE_CODE('E', 'B') -#define ROM_FUNC_FLASH_RUNTIME_TO_STORAGE_ADDR ROM_TABLE_CODE('F', 'A') -#define ROM_DATA_FLASH_DEVINFO16_PTR ROM_TABLE_CODE('F', 'D') -#define ROM_FUNC_FLASH_OP ROM_TABLE_CODE('F', 'O') -#define ROM_FUNC_GET_B_PARTITION ROM_TABLE_CODE('G', 'B') -#define ROM_FUNC_GET_PARTITION_TABLE_INFO ROM_TABLE_CODE('G', 'P') -#define ROM_FUNC_GET_SYS_INFO ROM_TABLE_CODE('G', 'S') -#define ROM_FUNC_GET_UF2_TARGET_PARTITION ROM_TABLE_CODE('G', 'U') -#define ROM_FUNC_LOAD_PARTITION_TABLE ROM_TABLE_CODE('L', 'P') -#define ROM_FUNC_OTP_ACCESS ROM_TABLE_CODE('O', 'A') -#define ROM_DATA_PARTITION_TABLE_PTR ROM_TABLE_CODE('P', 'T') -#define ROM_FUNC_FLASH_RESET_ADDRESS_TRANS ROM_TABLE_CODE('R', 'A') -#define ROM_FUNC_REBOOT ROM_TABLE_CODE('R', 'B') -#define ROM_FUNC_SET_ROM_CALLBACK ROM_TABLE_CODE('R', 'C') -#define ROM_FUNC_SECURE_CALL ROM_TABLE_CODE('S', 'C') -#define ROM_FUNC_SET_NS_API_PERMISSION ROM_TABLE_CODE('S', 'P') -#define ROM_FUNC_BOOTROM_STATE_RESET ROM_TABLE_CODE('S', 'R') -#define ROM_FUNC_SET_BOOTROM_STACK ROM_TABLE_CODE('S', 'S') -#define ROM_DATA_SAVED_XIP_SETUP_FUNC_PTR ROM_TABLE_CODE('X', 'F') -#define ROM_FUNC_FLASH_SELECT_XIP_READ_MODE ROM_TABLE_CODE('X', 'M') -#define ROM_FUNC_VALIDATE_NS_BUFFER ROM_TABLE_CODE('V', 'B') -#endif - -// these form a bit set -#define BOOTROM_STATE_RESET_CURRENT_CORE 0x01 -#define BOOTROM_STATE_RESET_OTHER_CORE 0x02 -#define BOOTROM_STATE_RESET_GLOBAL_STATE 0x04 // reset any global state (e.g. permissions) - -// partition level stuff is returned first (note PT_INFO flags is only 16 bits) - -// 3 words: pt_count, unpartitioned_perm_loc, unpartioned_perm_flags -#define PT_INFO_PT_INFO 0x0001 -#define PT_INFO_SINGLE_PARTITION 0x8000 // marker to just include a single partition in the results) - -// then in order per partition selected - -// 2 words: unpartitioned_perm_loc, unpartioned_perm_flags -#define PT_INFO_PARTITION_LOCATION_AND_FLAGS 0x0010 -// 2 words: id lsb first -#define PT_INFO_PARTITION_ID 0x0020 -// n+1 words: n, family_id... -#define PT_INFO_PARTITION_FAMILY_IDS 0x0040 -// (n+3)/4 words... bytes are: n (len), c0, c1, ... cn-1 padded to word boundary with zeroes -#define PT_INFO_PARTITION_NAME 0x0080 - -// items are returned in order -// 3 words package_id, device_id, wafer_id -#define SYS_INFO_CHIP_INFO 0x0001 -// 1 word: chip specific critical bits -#define SYS_INFO_CRITICAL 0x0002 -// 1 word: bytes: cpu_type, supported_cpu_type_bitfield -#define SYS_INFO_CPU_INFO 0x0004 -// 1 word: same as FLASH_DEVINFO row in OTP -#define SYS_INFO_FLASH_DEV_INFO 0x0008 -// 4 words -#define SYS_INFO_BOOT_RANDOM 0x0010 -// 2 words lsb first -#define SYS_INFO_NONCE 0x0020 -// 4 words boot_info, boot_diagnostic, boot_param0, boot_param1 -#define SYS_INFO_BOOT_INFO 0x0040 - -#define BOOTROM_NS_API_get_sys_info 0 -#define BOOTROM_NS_API_checked_flash_op 1 -#define BOOTROM_NS_API_flash_runtime_to_storage_addr 2 -#define BOOTROM_NS_API_get_partition_table_info 3 -#define BOOTROM_NS_API_secure_call 4 -#define BOOTROM_NS_API_otp_access 5 -#define BOOTROM_NS_API_reboot 6 -#define BOOTROM_NS_API_get_b_partition 7 -#define BOOTROM_NS_API_COUNT 8 - -#ifndef __ASSEMBLER__ - -typedef struct { - uint32_t permissions_and_location; - uint32_t permissions_and_flags; -} resident_partition_t; -static_assert(sizeof(resident_partition_t) == 8, ""); - -#define OTP_CMD_ROW_BITS 0x0000ffffu -#define OTP_CMD_ROW_LSB 0u -#define OTP_CMD_WRITE_BITS 0x00010000u -#define OTP_CMD_ECC_BITS 0x00020000u - -typedef struct otp_cmd { - uint32_t flags; -} otp_cmd_t; - -typedef enum { - BOOTROM_XIP_MODE_03H_SERIAL = 0, - BOOTROM_XIP_MODE_0BH_SERIAL, - BOOTROM_XIP_MODE_BBH_DUAL, - BOOTROM_XIP_MODE_EBH_QUAD, - BOOTROM_XIP_MODE_N_MODES -} bootrom_xip_mode_t; - -// The checked flash API wraps the low-level flash routines from generic_flash, adding bounds -// checking, permission checking against the resident partition table, and simple address -// translation. The low-level API deals with flash offsets (i.e. distance from the start of the -// first flash device, measured in bytes) but the checked flash API accepts one of two types of -// address: -// -// - Flash runtime addresses: the address of some flash-resident data or code in the currently -// running image. The flash addresses your binary is "linked at" by the linker. -// - Flash storage addresses: a flash offset, plus the address base where QSPI hardware is first -// mapped on the system bus (XIP_BASE constant from addressmap.h) -// -// These addresses are one and the same *if* the currently running program is stored at the -// beginning of flash. They are different if the start of your image has been "rolled" by the flash -// boot path to make it appear at the address it was linked at even though it is stored at a -// different location in flash, which is necessary when you have A/B images for example. -// -// The address translation between flash runtime and flash storage addresses is configured in -// hardware by the QMI_ATRANSx registers, and this API assumes those registers contain a valid -// address mapping which it can use to translate runtime to storage addresses. - -typedef struct cflash_flags { - uint32_t flags; -} cflash_flags_t; - -// Bits which are permitted to be set in a flags variable -- any other bits being set is an error -#define CFLASH_FLAGS_BITS 0x00070301u - -// Used to tell checked flash API which space a given address belongs to -#define CFLASH_ASPACE_BITS 0x00000001u -#define CFLASH_ASPACE_LSB 0u -#define CFLASH_ASPACE_VALUE_STORAGE 0u -#define CFLASH_ASPACE_VALUE_RUNTIME 1u - -// Used to tell checked flash APIs the effective security level of a flash access (may be forced to -// one of these values for the NonSecure-exported version of this API) -#define CFLASH_SECLEVEL_BITS 0x00000300u -#define CFLASH_SECLEVEL_LSB 8u -// Zero is not a valid security level: -#define CFLASH_SECLEVEL_VALUE_SECURE 1u -#define CFLASH_SECLEVEL_VALUE_NONSECURE 2u -#define CFLASH_SECLEVEL_VALUE_BOOTLOADER 3u - -#define CFLASH_OP_BITS 0x00070000u -#define CFLASH_OP_LSB 16u -// Erase size_bytes bytes of flash, starting at address addr. Both addr and size_bytes must be a -// multiple of 4096 bytes (one flash sector). -#define CFLASH_OP_VALUE_ERASE 0u -// Program size_bytes bytes of flash, starting at address addr. Both addr and size_bytes must be a -// multiple of 256 bytes (one flash page). -#define CFLASH_OP_VALUE_PROGRAM 1u -// Read size_bytes bytes of flash, starting at address addr. There are no alignment restrictions on -// addr or size_bytes. -#define CFLASH_OP_VALUE_READ 2u -#define CFLASH_OP_MAX 2u - -#endif - -#endif +// new location; this file kept for backwards compatibility +#include "boot/bootrom_constants.h" diff --git a/lib/pico-sdk/pico/platform.h b/lib/pico-sdk/pico/platform.h index dca69f265..0338354e5 100644 --- a/lib/pico-sdk/pico/platform.h +++ b/lib/pico-sdk/pico/platform.h @@ -11,7 +11,7 @@ #include #include -#ifdef __unix__ +#if defined __unix__ && defined __GLIBC__ #include @@ -47,7 +47,7 @@ extern void tight_loop_contents(); #define __STRING(x) #x #endif -#ifndef _MSC_VER +#if !defined(_MSC_VER) || defined(__clang__) #ifndef __noreturn #define __noreturn __attribute((noreturn)) #endif @@ -60,6 +60,12 @@ extern void tight_loop_contents(); #define __noinline __attribute__((noinline)) #endif +#ifndef __force_inline +// don't think it is critical to inline in host mode, and this is simpler than picking the +// correct attribute incantation for always_inline on different compiler versions +#define __force_inline inline +#endif + #ifndef __aligned #define __aligned(x) __attribute__((aligned(x))) #endif @@ -148,7 +154,11 @@ uint get_core_num(); static inline uint __get_current_exception(void) { return 0; + } + +void busy_wait_at_least_cycles(uint32_t minimum_cycles); + #ifdef __cplusplus } #endif diff --git a/lib/pico-sdk/rp2040/boot_stage2/BUILD.bazel b/lib/pico-sdk/rp2040/boot_stage2/BUILD.bazel index 65c9e76b2..56ed5f3c3 100644 --- a/lib/pico-sdk/rp2040/boot_stage2/BUILD.bazel +++ b/lib/pico-sdk/rp2040/boot_stage2/BUILD.bazel @@ -76,6 +76,11 @@ cc_binary( copts = ["-fPIC"], # Incompatible with section garbage collection. features = ["-gc_sections"], + # Platforms will commonly depend on bootloader components in every + # binary via `link_extra_libs`, so we must drop these deps when + # building the bootloader binaries themselves in order to avoid a + # circular dependency. + link_extra_lib = "//bazel:empty_cc_lib", linkopts = [ "-Wl,--no-gc-sections", "-nostartfiles", diff --git a/lib/pico-sdk/rp2040/boot_stage2/CMakeLists.txt b/lib/pico-sdk/rp2040/boot_stage2/CMakeLists.txt index c5768785b..2798b3640 100644 --- a/lib/pico-sdk/rp2040/boot_stage2/CMakeLists.txt +++ b/lib/pico-sdk/rp2040/boot_stage2/CMakeLists.txt @@ -30,10 +30,16 @@ pico_register_common_scope_var(PICO_DEFAULT_BOOT_STAGE2_FILE) # needed by function below set(PICO_BOOT_STAGE2_DIR "${CMAKE_CURRENT_LIST_DIR}" CACHE INTERNAL "") -add_library(boot_stage2_headers INTERFACE) +pico_add_library(boot_stage2_headers) target_include_directories(boot_stage2_headers SYSTEM INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) -# by convention the first source file name without extension is used for the binary info name +# pico_define_boot_stage2(NAME SOURCES) +# \brief\ Define a boot stage 2 target. +# +# By convention the first source file name without extension is used for the binary info name +# +# \param\ NAME The name of the boot stage 2 target +# \param\ SOURCES The source files to link into the boot stage 2 function(pico_define_boot_stage2 NAME SOURCES) add_executable(${NAME} ${SOURCES} @@ -66,15 +72,12 @@ function(pico_define_boot_stage2 NAME SOURCES) add_custom_command(OUTPUT ${ORIGINAL_BIN} DEPENDS ${NAME} COMMAND ${CMAKE_OBJCOPY} -Obinary $ ${ORIGINAL_BIN} VERBATIM) - add_custom_target(${NAME}_padded_checksummed_asm DEPENDS ${PADDED_CHECKSUMMED_ASM}) add_custom_command(OUTPUT ${PADDED_CHECKSUMMED_ASM} DEPENDS ${ORIGINAL_BIN} COMMAND ${Python3_EXECUTABLE} ${PICO_BOOT_STAGE2_DIR}/pad_checksum -s 0xffffffff ${ORIGINAL_BIN} ${PADDED_CHECKSUMMED_ASM} VERBATIM) - add_library(${NAME}_library INTERFACE) - add_dependencies(${NAME}_library ${NAME}_padded_checksummed_asm) - # not strictly (or indeed actually) a link library, but this avoids dependency cycle - target_link_libraries(${NAME}_library INTERFACE ${PADDED_CHECKSUMMED_ASM}) + add_library(${NAME}_library OBJECT ${PADDED_CHECKSUMMED_ASM}) + target_link_libraries(${NAME}_library INTERFACE "$") target_link_libraries(${NAME}_library INTERFACE boot_stage2_headers) list(GET SOURCES 0 FIRST_SOURCE) @@ -100,7 +103,12 @@ endmacro() pico_define_boot_stage2(bs2_default ${PICO_DEFAULT_BOOT_STAGE2_FILE}) +# pico_clone_default_boot_stage2(NAME) +# \brief_nodesc\ Clone the default boot stage 2 target. +# # Create a new boot stage 2 target using the default implementation for the current build (PICO_BOARD derived) +# +# \param\ NAME The name of the new boot stage 2 target function(pico_clone_default_boot_stage2 NAME) pico_define_boot_stage2(${NAME} ${PICO_DEFAULT_BOOT_STAGE2_FILE}) endfunction() diff --git a/lib/pico-sdk/rp2040/boot_stage2/boot2_w25x10cl.S b/lib/pico-sdk/rp2040/boot_stage2/boot2_w25x10cl.S index 9aa51ac57..b0e6a10fc 100644 --- a/lib/pico-sdk/rp2040/boot_stage2/boot2_w25x10cl.S +++ b/lib/pico-sdk/rp2040/boot_stage2/boot2_w25x10cl.S @@ -144,10 +144,10 @@ regular_func _stage2_boot // status register and checking for the "RX FIFO Not Empty" flag to assert. movs r1, #SSI_SR_RFNE_BITS -00: +1: ldr r0, [r3, #SSI_SR_OFFSET] // Read status register tst r0, r1 // RFNE status flag set? - beq 00b // If not then wait + beq 1b // If not then wait // At this point CN# will be deasserted and the SPI clock will not be running. // The Winbond WX25X10CL device will be in continuous read, dual I/O mode and diff --git a/lib/pico-sdk/rp2040/boot_stage2/boot_stage2.ld b/lib/pico-sdk/rp2040/boot_stage2/boot_stage2.ld index f8669ab64..32978a16e 100644 --- a/lib/pico-sdk/rp2040/boot_stage2/boot_stage2.ld +++ b/lib/pico-sdk/rp2040/boot_stage2/boot_stage2.ld @@ -7,6 +7,7 @@ MEMORY { SECTIONS { . = ORIGIN(SRAM); .text : { + _start = .; /* make LLVM happy */ *(.entry) *(.text) } >SRAM diff --git a/lib/pico-sdk/rp2040/boot_stage2/include/boot_stage2/config.h b/lib/pico-sdk/rp2040/boot_stage2/include/boot_stage2/config.h index e4d32628c..568aca1ef 100644 --- a/lib/pico-sdk/rp2040/boot_stage2/include/boot_stage2/config.h +++ b/lib/pico-sdk/rp2040/boot_stage2/include/boot_stage2/config.h @@ -11,13 +11,15 @@ #include "pico.h" -// PICO_CONFIG: PICO_BUILD_BOOT_STAGE2_NAME, The name of the boot stage 2 if selected by the build, group=boot_stage2 +// PICO_CONFIG: PICO_FLASH_SPI_CLKDIV, Clock divider from clk_sys to use for serial flash communications in boot stage 2. On RP2040 this must be a multiple of 2. This define applies to compilation of the boot stage 2 not the main application, type=int, default=varies; often specified in board header, advanced=true, group=boot_stage2 + +// PICO_CONFIG: PICO_BUILD_BOOT_STAGE2_NAME, Name of the boot stage 2 if selected in the build system. This define applies to compilation of the boot stage 2 not the main application, group=boot_stage2 #ifdef PICO_BUILD_BOOT_STAGE2_NAME #define _BOOT_STAGE2_SELECTED #else // check that multiple boot stage 2 options haven't been set... -// PICO_CONFIG: PICO_BOOT_STAGE2_CHOOSE_IS25LP080, Select boot2_is25lp080 as the boot stage 2 when no boot stage 2 selection is made by the CMake build, type=bool, default=0, group=boot_stage2 +// PICO_CONFIG: PICO_BOOT_STAGE2_CHOOSE_IS25LP080, Select boot2_is25lp080 as the boot stage 2 when no boot stage 2 selection is made by the CMake build. This define applies to compilation of the boot stage 2 not the main application, type=bool, default=0, group=boot_stage2 #ifndef PICO_BOOT_STAGE2_CHOOSE_IS25LP080 #define PICO_BOOT_STAGE2_CHOOSE_IS25LP080 0 #elif PICO_BOOT_STAGE2_CHOOSE_IS25LP080 @@ -26,7 +28,7 @@ #endif #define _BOOT_STAGE2_SELECTED #endif -// PICO_CONFIG: PICO_BOOT_STAGE2_CHOOSE_W25Q080, Select boot2_w25q080 as the boot stage 2 when no boot stage 2 selection is made by the CMake build, type=bool, default=0, group=boot_stage2 +// PICO_CONFIG: PICO_BOOT_STAGE2_CHOOSE_W25Q080, Select boot2_w25q080 as the boot stage 2 when no boot stage 2 selection is made by the CMake build. This define applies to compilation of the boot stage 2 not the main application, type=bool, default=0, group=boot_stage2 #ifndef PICO_BOOT_STAGE2_CHOOSE_W25Q080 #define PICO_BOOT_STAGE2_CHOOSE_W25Q080 0 #elif PICO_BOOT_STAGE2_CHOOSE_W25Q080 @@ -35,7 +37,7 @@ #endif #define _BOOT_STAGE2_SELECTED #endif -// PICO_CONFIG: PICO_BOOT_STAGE2_CHOOSE_W25X10CL, Select boot2_w25x10cl as the boot stage 2 when no boot stage 2 selection is made by the CMake build, type=bool, default=0, group=boot_stage2 +// PICO_CONFIG: PICO_BOOT_STAGE2_CHOOSE_W25X10CL, Select boot2_w25x10cl as the boot stage 2 when no boot stage 2 selection is made by the CMake build. This define applies to compilation of the boot stage 2 not the main application, type=bool, default=0, group=boot_stage2 #ifndef PICO_BOOT_STAGE2_CHOOSE_W25X10CL #define PICO_BOOT_STAGE2_CHOOSE_W25X10CL 0 #elif PICO_BOOT_STAGE2_CHOOSE_W25X10CL @@ -44,7 +46,7 @@ #endif #define _BOOT_STAGE2_SELECTED #endif -// PICO_CONFIG: PICO_BOOT_STAGE2_CHOOSE_AT25SF128A, Select boot2_at25sf128a as the boot stage 2 when no boot stage 2 selection is made by the CMake build, type=bool, default=0, group=boot_stage2 +// PICO_CONFIG: PICO_BOOT_STAGE2_CHOOSE_AT25SF128A, Select boot2_at25sf128a as the boot stage 2 when no boot stage 2 selection is made by the CMake build. This define applies to compilation of the boot stage 2 not the main application, type=bool, default=0, group=boot_stage2 #ifndef PICO_BOOT_STAGE2_CHOOSE_AT25SF128A #define PICO_BOOT_STAGE2_CHOOSE_AT25SF128A 0 #elif PICO_BOOT_STAGE2_CHOOSE_AT25SF128A @@ -54,7 +56,7 @@ #define _BOOT_STAGE2_SELECTED #endif -// PICO_CONFIG: PICO_BOOT_STAGE2_CHOOSE_GENERIC_03H, Select boot2_generic_03h as the boot stage 2 when no boot stage 2 selection is made by the CMake build, type=bool, default=1, group=boot_stage2 +// PICO_CONFIG: PICO_BOOT_STAGE2_CHOOSE_GENERIC_03H, Select boot2_generic_03h as the boot stage 2 when no boot stage 2 selection is made by the CMake build. This define applies to compilation of the boot stage 2 not the main application, type=bool, default=1, group=boot_stage2 #if defined(PICO_BOOT_STAGE2_CHOOSE_GENERIC_03H) && PICO_BOOT_STAGE2_CHOOSE_GENERIC_03H #ifdef _BOOT_STAGE2_SELECTED #error multiple boot stage 2 options chosen diff --git a/lib/pico-sdk/rp2040/hardware/platform_defs.h b/lib/pico-sdk/rp2040/hardware/platform_defs.h index 54d9344c8..a877710d0 100644 --- a/lib/pico-sdk/rp2040/hardware/platform_defs.h +++ b/lib/pico-sdk/rp2040/hardware/platform_defs.h @@ -46,6 +46,15 @@ #define HAS_SIO_DIVIDER 1 #define HAS_RP2040_RTC 1 + +#ifndef FPGA_CLK_SYS_HZ +#define FPGA_CLK_SYS_HZ (48 * MHZ) +#endif + +#ifndef FPGA_CLK_REF_HZ +#define FPGA_CLK_REF_HZ (12 * MHZ) +#endif + // PICO_CONFIG: XOSC_HZ, Crystal oscillator frequency in Hz, type=int, default=12000000, advanced=true, group=hardware_base // NOTE: The system and USB clocks are generated from the frequency using two PLLs. // If you override this define, or SYS_CLK_HZ/USB_CLK_HZ below, you will *also* need to add your own adjusted PLL set-up defines to @@ -61,6 +70,11 @@ #endif #endif +// PICO_CONFIG: PICO_USE_FASTEST_SUPPORTED_CLOCK, Use the fastest officially supported clock by default, type=bool, default=0, group=hardware_base +#ifndef PICO_USE_FASTEST_SUPPORTED_CLOCK +#define PICO_USE_FASTEST_SUPPORTED_CLOCK 0 +#endif + // PICO_CONFIG: SYS_CLK_HZ, System operating frequency in Hz, type=int, default=125000000, advanced=true, group=hardware_base #ifndef SYS_CLK_HZ #ifdef SYS_CLK_KHZ @@ -68,9 +82,13 @@ #elif defined(SYS_CLK_MHZ) #define SYS_CLK_HZ ((SYS_CLK_MHZ) * _u(1000000)) #else +#if PICO_USE_FASTEST_SUPPORTED_CLOCK +#define SYS_CLK_HZ _u(200000000) +#else #define SYS_CLK_HZ _u(125000000) #endif #endif +#endif // PICO_CONFIG: USB_CLK_HZ, USB clock frequency. Must be 48MHz for the USB interface to operate correctly, type=int, default=48000000, advanced=true, group=hardware_base #ifndef USB_CLK_HZ @@ -116,4 +134,6 @@ #define FIRST_USER_IRQ (NUM_IRQS - NUM_USER_IRQS) #define VTABLE_FIRST_IRQ 16 +#define REG_FIELD_WIDTH(f) (f ## _MSB + 1 - f ## _LSB) + #endif diff --git a/lib/pico-sdk/rp2040/hardware/regs/intctrl.h b/lib/pico-sdk/rp2040/hardware/regs/intctrl.h index 3190b413d..71c6eb90b 100644 --- a/lib/pico-sdk/rp2040/hardware/regs/intctrl.h +++ b/lib/pico-sdk/rp2040/hardware/regs/intctrl.h @@ -39,6 +39,12 @@ #define I2C0_IRQ 23 #define I2C1_IRQ 24 #define RTC_IRQ 25 +#define SPARE_IRQ_0 26 +#define SPARE_IRQ_1 27 +#define SPARE_IRQ_2 28 +#define SPARE_IRQ_3 29 +#define SPARE_IRQ_4 30 +#define SPARE_IRQ_5 31 #else /** * \brief Interrupt numbers on RP2040 (used as typedef \ref irq_num_t) @@ -71,6 +77,12 @@ typedef enum irq_num_rp2040 { I2C0_IRQ = 23, ///< Select I2C0's IRQ output I2C1_IRQ = 24, ///< Select I2C1's IRQ output RTC_IRQ = 25, ///< Select RTC's IRQ output + SPARE_IRQ_0 = 26, ///< Select SPARE IRQ 0 + SPARE_IRQ_1 = 27, ///< Select SPARE IRQ 1 + SPARE_IRQ_2 = 28, ///< Select SPARE IRQ 2 + SPARE_IRQ_3 = 29, ///< Select SPARE IRQ 3 + SPARE_IRQ_4 = 30, ///< Select SPARE IRQ 4 + SPARE_IRQ_5 = 31, ///< Select SPARE IRQ 5 IRQ_COUNT } irq_num_t; #endif @@ -101,6 +113,12 @@ typedef enum irq_num_rp2040 { #define isr_i2c0 isr_irq23 #define isr_i2c1 isr_irq24 #define isr_rtc isr_irq25 +#define isr_spare_0 isr_irq26 +#define isr_spare_1 isr_irq27 +#define isr_spare_2 isr_irq28 +#define isr_spare_3 isr_irq29 +#define isr_spare_4 isr_irq30 +#define isr_spare_5 isr_irq31 #endif // _INTCTRL_H diff --git a/lib/pico-sdk/rp2040/hardware/structs/busctrl.h b/lib/pico-sdk/rp2040/hardware/structs/busctrl.h index 65893227d..2302025e7 100644 --- a/lib/pico-sdk/rp2040/hardware/structs/busctrl.h +++ b/lib/pico-sdk/rp2040/hardware/structs/busctrl.h @@ -24,7 +24,6 @@ // BITMASK [BITRANGE] FIELDNAME (RESETVALUE) DESCRIPTION /** \brief Bus fabric performance counters on RP2040 (used as typedef \ref bus_ctrl_perf_counter_t) - * \ingroup hardware_busctrl */ typedef enum bus_ctrl_perf_counter_rp2040 { arbiter_rom_perf_event_access = 19, diff --git a/lib/pico-sdk/rp2350/cmsis_include/RP2350.h b/lib/pico-sdk/rp2350/cmsis_include/RP2350.h index 94d0f178c..77869ea80 100644 --- a/lib/pico-sdk/rp2350/cmsis_include/RP2350.h +++ b/lib/pico-sdk/rp2350/cmsis_include/RP2350.h @@ -4,10 +4,10 @@ * @file src/rp2_common/cmsis/stub/CMSIS/Device/RP2350/Include/RP2350.h * @brief CMSIS HeaderFile * @version 0.1 - * @date Thu Aug 8 04:04:02 2024 - * @note Generated by SVDConv V3.3.47 - * from File 'src/rp2_common/cmsis/../../rp2350/hardware_regs/RP2350.svd', - * last modified on Thu Aug 8 03:59:33 2024 + * @date Mon Jul 28 11:37:41 2025 + * @note Generated by SVDConv V3.3.45 + * from File 'src/rp2350/hardware_regs/RP2350.svd', + * last modified on Mon Jul 28 11:35:05 2025 */ @@ -2028,7 +2028,7 @@ typedef struct { /*!< POWMAN Structure ignore the power down requests. To do nothing would risk entering an unrecoverable lock-up state. Invalid requests are: any combination of power up and power down requests - any request that results in swcore boing powered and xip + any request that results in swcore being powered and xip unpowered If the request is to power down the switched-core domain then POWMAN_STATE_WAITING stays active until the processors halt. During this time the POWMAN_STATE_REQ diff --git a/lib/pico-sdk/rp2350/cmsis_include/system_RP2350.h b/lib/pico-sdk/rp2350/cmsis_include/system_RP2350.h index 30881ccc6..d85fbeb6c 100644 --- a/lib/pico-sdk/rp2350/cmsis_include/system_RP2350.h +++ b/lib/pico-sdk/rp2350/cmsis_include/system_RP2350.h @@ -1,9 +1,9 @@ /*************************************************************************//** - * @file system_RP2040.h + * @file system_RP2350.h * @brief CMSIS-Core(M) Device Peripheral Access Layer Header File for - * Device RP2040 - * @version V1.0.0 - * @date 5. May 2021 + * Device RP2350 + * @version V1.0.1 + * @date 6. Sep 2024 *****************************************************************************/ /* * Copyright (c) 2009-2021 Arm Limited. All rights reserved. @@ -26,8 +26,8 @@ * SPDX-License-Identifier: BSD-3-Clause */ -#ifndef _CMSIS_SYSTEM_RP2040_H -#define _CMSIS_SYSTEM_RP2040_H +#ifndef _CMSIS_SYSTEM_RP2350_H +#define _CMSIS_SYSTEM_RP2350_H #ifdef __cplusplus extern "C" { @@ -62,4 +62,4 @@ extern void SystemCoreClockUpdate (void); } #endif -#endif /* _CMSIS_SYSTEM_RP2040_H */ +#endif /* _CMSIS_SYSTEM_RP2350_H */ diff --git a/lib/pico-sdk/rp2350/hardware/regs/clocks.h b/lib/pico-sdk/rp2350/hardware/regs/clocks.h index fd560c910..ce46345f8 100644 --- a/lib/pico-sdk/rp2350/hardware/regs/clocks.h +++ b/lib/pico-sdk/rp2350/hardware/regs/clocks.h @@ -615,7 +615,7 @@ // Description : Clock control, can be changed on-the-fly (except for auxsrc) #define CLOCKS_CLK_SYS_CTRL_OFFSET _u(0x0000003c) #define CLOCKS_CLK_SYS_CTRL_BITS _u(0x000000e1) -#define CLOCKS_CLK_SYS_CTRL_RESET _u(0x00000000) +#define CLOCKS_CLK_SYS_CTRL_RESET _u(0x00000041) // ----------------------------------------------------------------------------- // Field : CLOCKS_CLK_SYS_CTRL_AUXSRC // Description : Selects the auxiliary clock source, will glitch when switching @@ -625,7 +625,7 @@ // 0x3 -> xosc_clksrc // 0x4 -> clksrc_gpin0 // 0x5 -> clksrc_gpin1 -#define CLOCKS_CLK_SYS_CTRL_AUXSRC_RESET _u(0x0) +#define CLOCKS_CLK_SYS_CTRL_AUXSRC_RESET _u(0x2) #define CLOCKS_CLK_SYS_CTRL_AUXSRC_BITS _u(0x000000e0) #define CLOCKS_CLK_SYS_CTRL_AUXSRC_MSB _u(7) #define CLOCKS_CLK_SYS_CTRL_AUXSRC_LSB _u(5) @@ -642,7 +642,7 @@ // fly // 0x0 -> clk_ref // 0x1 -> clksrc_clk_sys_aux -#define CLOCKS_CLK_SYS_CTRL_SRC_RESET _u(0x0) +#define CLOCKS_CLK_SYS_CTRL_SRC_RESET _u(0x1) #define CLOCKS_CLK_SYS_CTRL_SRC_BITS _u(0x00000001) #define CLOCKS_CLK_SYS_CTRL_SRC_MSB _u(0) #define CLOCKS_CLK_SYS_CTRL_SRC_LSB _u(0) diff --git a/lib/pico-sdk/rp2350/hardware/regs/dreq.h b/lib/pico-sdk/rp2350/hardware/regs/dreq.h index 6d126c0df..edcdae60b 100644 --- a/lib/pico-sdk/rp2350/hardware/regs/dreq.h +++ b/lib/pico-sdk/rp2350/hardware/regs/dreq.h @@ -121,8 +121,8 @@ typedef enum dreq_num_rp2350 { DREQ_PWM_WRAP7 = 39, ///< Select PWM Counter 7's Wrap Value as DREQ DREQ_PWM_WRAP8 = 40, ///< Select PWM Counter 8's Wrap Value as DREQ DREQ_PWM_WRAP9 = 41, ///< Select PWM Counter 9's Wrap Value as DREQ - DREQ_PWM_WRAP10 = 42, ///< Select PWM Counter 0's Wrap Value as DREQ - DREQ_PWM_WRAP11 = 43, ///< Select PWM Counter 1's Wrap Value as DREQ + DREQ_PWM_WRAP10 = 42, ///< Select PWM Counter 10's Wrap Value as DREQ + DREQ_PWM_WRAP11 = 43, ///< Select PWM Counter 11's Wrap Value as DREQ DREQ_I2C0_TX = 44, ///< Select I2C0's TX FIFO as DREQ DREQ_I2C0_RX = 45, ///< Select I2C0's RX FIFO as DREQ DREQ_I2C1_TX = 46, ///< Select I2C1's TX FIFO as DREQ @@ -135,8 +135,8 @@ typedef enum dreq_num_rp2350 { DREQ_CORESIGHT = 53, ///< Select CORESIGHT as DREQ DREQ_SHA256 = 54, ///< Select SHA256 as DREQ DREQ_DMA_TIMER0 = 59, ///< Select DMA_TIMER0 as DREQ - DREQ_DMA_TIMER1 = 60, ///< Select DMA_TIMER0 as DREQ - DREQ_DMA_TIMER2 = 61, ///< Select DMA_TIMER1 as DREQ + DREQ_DMA_TIMER1 = 60, ///< Select DMA_TIMER1 as DREQ + DREQ_DMA_TIMER2 = 61, ///< Select DMA_TIMER2 as DREQ DREQ_DMA_TIMER3 = 62, ///< Select DMA_TIMER3 as DREQ DREQ_FORCE = 63, ///< Select FORCE as DREQ DREQ_COUNT diff --git a/lib/pico-sdk/rp2350/hardware/regs/glitch_detector.h b/lib/pico-sdk/rp2350/hardware/regs/glitch_detector.h index efdf434b3..6e108e2b7 100644 --- a/lib/pico-sdk/rp2350/hardware/regs/glitch_detector.h +++ b/lib/pico-sdk/rp2350/hardware/regs/glitch_detector.h @@ -37,8 +37,7 @@ #define GLITCH_DETECTOR_ARM_VALUE_YES _u(0x0000) // ============================================================================= // Register : GLITCH_DETECTOR_DISARM -// Description : None -// Forcibly disarm the glitch detectors, if they are armed by OTP. +// Description : Forcibly disarm the glitch detectors, if they are armed by OTP. // Ignored if ARM is YES. // // This register is Secure read/write only. @@ -142,8 +141,7 @@ #define GLITCH_DETECTOR_SENSITIVITY_DET0_ACCESS "RW" // ============================================================================= // Register : GLITCH_DETECTOR_LOCK -// Description : None -// Write any nonzero value to disable writes to ARM, DISARM, +// Description : Write any nonzero value to disable writes to ARM, DISARM, // SENSITIVITY and LOCK. This register is Secure read/write only. #define GLITCH_DETECTOR_LOCK_OFFSET _u(0x0000000c) #define GLITCH_DETECTOR_LOCK_BITS _u(0x000000ff) diff --git a/lib/pico-sdk/rp2350/hardware/regs/intctrl.h b/lib/pico-sdk/rp2350/hardware/regs/intctrl.h index 96ce815e4..1e19e33d1 100644 --- a/lib/pico-sdk/rp2350/hardware/regs/intctrl.h +++ b/lib/pico-sdk/rp2350/hardware/regs/intctrl.h @@ -59,12 +59,12 @@ #define PLL_USB_IRQ 43 #define POWMAN_IRQ_POW 44 #define POWMAN_IRQ_TIMER 45 -#define SPAREIRQ_IRQ_0 46 -#define SPAREIRQ_IRQ_1 47 -#define SPAREIRQ_IRQ_2 48 -#define SPAREIRQ_IRQ_3 49 -#define SPAREIRQ_IRQ_4 50 -#define SPAREIRQ_IRQ_5 51 +#define SPARE_IRQ_0 46 +#define SPARE_IRQ_1 47 +#define SPARE_IRQ_2 48 +#define SPARE_IRQ_3 49 +#define SPARE_IRQ_4 50 +#define SPARE_IRQ_5 51 #else /** * \brief Interrupt numbers on RP2350 (used as typedef \ref irq_num_t) @@ -79,8 +79,8 @@ typedef enum irq_num_rp2350 { TIMER1_IRQ_1 = 5, ///< Select TIMER1's IRQ 1 output TIMER1_IRQ_2 = 6, ///< Select TIMER1's IRQ 2 output TIMER1_IRQ_3 = 7, ///< Select TIMER1's IRQ 3 output - PWM_IRQ_WRAP_0 = 8, ///< Select PWM's IRQ_WRAP 0 output - PWM_IRQ_WRAP_1 = 9, ///< Select PWM's IRQ_WRAP 1 output + PWM_IRQ_WRAP_0 = 8, ///< Select PWM's WRAP_0 IRQ output + PWM_IRQ_WRAP_1 = 9, ///< Select PWM's WRAP_1 IRQ output DMA_IRQ_0 = 10, ///< Select DMA's IRQ 0 output DMA_IRQ_1 = 11, ///< Select DMA's IRQ 1 output DMA_IRQ_2 = 12, ///< Select DMA's IRQ 2 output @@ -96,27 +96,27 @@ typedef enum irq_num_rp2350 { IO_IRQ_BANK0_NS = 22, ///< Select IO_BANK0_NS's IRQ output IO_IRQ_QSPI = 23, ///< Select IO_QSPI's IRQ output IO_IRQ_QSPI_NS = 24, ///< Select IO_QSPI_NS's IRQ output - SIO_IRQ_FIFO = 25, ///< Select SIO's IRQ_FIFO output - SIO_IRQ_BELL = 26, ///< Select SIO's IRQ_BELL output - SIO_IRQ_FIFO_NS = 27, ///< Select SIO_NS's IRQ_FIFO output - SIO_IRQ_BELL_NS = 28, ///< Select SIO_NS's IRQ_BELL output - SIO_IRQ_MTIMECMP = 29, ///< Select SIO_IRQ_MTIMECMP's IRQ output + SIO_IRQ_FIFO = 25, ///< Select SIO's FIFO IRQ output + SIO_IRQ_BELL = 26, ///< Select SIO's BELL IRQ output + SIO_IRQ_FIFO_NS = 27, ///< Select SIO_NS's FIFO IRQ output + SIO_IRQ_BELL_NS = 28, ///< Select SIO_NS's BELL IRQ output + SIO_IRQ_MTIMECMP = 29, ///< Select SIO's MTIMECMP IRQ output CLOCKS_IRQ = 30, ///< Select CLOCKS's IRQ output SPI0_IRQ = 31, ///< Select SPI0's IRQ output SPI1_IRQ = 32, ///< Select SPI1's IRQ output UART0_IRQ = 33, ///< Select UART0's IRQ output UART1_IRQ = 34, ///< Select UART1's IRQ output - ADC_IRQ_FIFO = 35, ///< Select ADC's IRQ_FIFO output + ADC_IRQ_FIFO = 35, ///< Select ADC's FIFO IRQ output I2C0_IRQ = 36, ///< Select I2C0's IRQ output I2C1_IRQ = 37, ///< Select I2C1's IRQ output OTP_IRQ = 38, ///< Select OTP's IRQ output TRNG_IRQ = 39, ///< Select TRNG's IRQ output - PROC0_IRQ_CTI = 40, ///< Select PROC0's IRQ_CTI output - PROC1_IRQ_CTI = 41, ///< Select PROC1's IRQ_CTI output + PROC0_IRQ_CTI = 40, ///< Select PROC0's CTI IRQ output + PROC1_IRQ_CTI = 41, ///< Select PROC1's CTI IRQ output PLL_SYS_IRQ = 42, ///< Select PLL_SYS's IRQ output PLL_USB_IRQ = 43, ///< Select PLL_USB's IRQ output - POWMAN_IRQ_POW = 44, ///< Select POWMAN's IRQ_POW output - POWMAN_IRQ_TIMER = 45, ///< Select POWMAN's IRQ_TIMER output + POWMAN_IRQ_POW = 44, ///< Select POWMAN's POW IRQ output + POWMAN_IRQ_TIMER = 45, ///< Select POWMAN's TIMER IRQ output SPARE_IRQ_0 = 46, ///< Select SPARE IRQ 0 SPARE_IRQ_1 = 47, ///< Select SPARE IRQ 1 SPARE_IRQ_2 = 48, ///< Select SPARE IRQ 2 diff --git a/lib/pico-sdk/rp2350/hardware/regs/pio.h b/lib/pico-sdk/rp2350/hardware/regs/pio.h index 4a18b5c6f..d20569708 100644 --- a/lib/pico-sdk/rp2350/hardware/regs/pio.h +++ b/lib/pico-sdk/rp2350/hardware/regs/pio.h @@ -461,8 +461,7 @@ // ============================================================================= // Register : PIO_DBG_PADOUT // Description : Read to sample the pad output values PIO is currently driving -// to the GPIOs. On RP2040 there are 30 GPIOs, so the two most -// significant bits are hardwired to 0. +// to the GPIOs. #define PIO_DBG_PADOUT_OFFSET _u(0x0000003c) #define PIO_DBG_PADOUT_BITS _u(0xffffffff) #define PIO_DBG_PADOUT_RESET _u(0x00000000) @@ -472,8 +471,7 @@ // ============================================================================= // Register : PIO_DBG_PADOE // Description : Read to sample the pad output enables (direction) PIO is -// currently driving to the GPIOs. On RP2040 there are 30 GPIOs, -// so the two most significant bits are hardwired to 0. +// currently driving to the GPIOs. #define PIO_DBG_PADOE_OFFSET _u(0x00000040) #define PIO_DBG_PADOE_BITS _u(0xffffffff) #define PIO_DBG_PADOE_RESET _u(0x00000000) diff --git a/lib/pico-sdk/rp2350/hardware/regs/powman.h b/lib/pico-sdk/rp2350/hardware/regs/powman.h index edfbabbcc..8beb5650b 100644 --- a/lib/pico-sdk/rp2350/hardware/regs/powman.h +++ b/lib/pico-sdk/rp2350/hardware/regs/powman.h @@ -944,7 +944,7 @@ // requests and ignore the power down requests. To do nothing // would risk entering an unrecoverable lock-up state. Invalid // requests are: any combination of power up and power down -// requests any request that results in swcore boing powered and +// requests any request that results in swcore being powered and // xip unpowered If the request is to power down the switched-core // domain then POWMAN_STATE_WAITING stays active until the // processors halt. During this time the POWMAN_STATE_REQ field @@ -957,6 +957,7 @@ #define POWMAN_STATE_RESET _u(0x0000000f) // ----------------------------------------------------------------------------- // Field : POWMAN_STATE_CHANGING +// Description : Indicates a power state change is in progress #define POWMAN_STATE_CHANGING_RESET _u(0x0) #define POWMAN_STATE_CHANGING_BITS _u(0x00002000) #define POWMAN_STATE_CHANGING_MSB _u(13) @@ -964,6 +965,9 @@ #define POWMAN_STATE_CHANGING_ACCESS "RO" // ----------------------------------------------------------------------------- // Field : POWMAN_STATE_WAITING +// Description : Indicates the power manager has received a state change request +// and is waiting for other actions to complete before executing +// it #define POWMAN_STATE_WAITING_RESET _u(0x0) #define POWMAN_STATE_WAITING_BITS _u(0x00001000) #define POWMAN_STATE_WAITING_MSB _u(12) @@ -971,8 +975,8 @@ #define POWMAN_STATE_WAITING_ACCESS "RO" // ----------------------------------------------------------------------------- // Field : POWMAN_STATE_BAD_HW_REQ -// Description : Bad hardware initiated state request. Went back to state 0 -// (i.e. everything powered up) +// Description : Invalid hardware initiated state request, power up requests +// actioned, power down requests ignored #define POWMAN_STATE_BAD_HW_REQ_RESET _u(0x0) #define POWMAN_STATE_BAD_HW_REQ_BITS _u(0x00000800) #define POWMAN_STATE_BAD_HW_REQ_MSB _u(11) @@ -980,7 +984,7 @@ #define POWMAN_STATE_BAD_HW_REQ_ACCESS "RO" // ----------------------------------------------------------------------------- // Field : POWMAN_STATE_BAD_SW_REQ -// Description : Bad software initiated state request. No action taken. +// Description : Invalid software initiated state request ignored #define POWMAN_STATE_BAD_SW_REQ_RESET _u(0x0) #define POWMAN_STATE_BAD_SW_REQ_BITS _u(0x00000400) #define POWMAN_STATE_BAD_SW_REQ_MSB _u(10) @@ -988,9 +992,8 @@ #define POWMAN_STATE_BAD_SW_REQ_ACCESS "RO" // ----------------------------------------------------------------------------- // Field : POWMAN_STATE_PWRUP_WHILE_WAITING -// Description : Request ignored because of a pending pwrup request. See -// current_pwrup_req. Note this blocks powering up AND powering -// down. +// Description : Indicates that a power state change request was ignored because +// of a pending power state change request #define POWMAN_STATE_PWRUP_WHILE_WAITING_RESET _u(0x0) #define POWMAN_STATE_PWRUP_WHILE_WAITING_BITS _u(0x00000200) #define POWMAN_STATE_PWRUP_WHILE_WAITING_MSB _u(9) @@ -998,6 +1001,8 @@ #define POWMAN_STATE_PWRUP_WHILE_WAITING_ACCESS "WC" // ----------------------------------------------------------------------------- // Field : POWMAN_STATE_REQ_IGNORED +// Description : Indicates that a software state change request was ignored +// because it clashed with an ongoing hardware or debugger request #define POWMAN_STATE_REQ_IGNORED_RESET _u(0x0) #define POWMAN_STATE_REQ_IGNORED_BITS _u(0x00000100) #define POWMAN_STATE_REQ_IGNORED_MSB _u(8) @@ -1005,6 +1010,8 @@ #define POWMAN_STATE_REQ_IGNORED_ACCESS "WC" // ----------------------------------------------------------------------------- // Field : POWMAN_STATE_REQ +// Description : This is written by software or hardware to request a new power +// state #define POWMAN_STATE_REQ_RESET _u(0x0) #define POWMAN_STATE_REQ_BITS _u(0x000000f0) #define POWMAN_STATE_REQ_MSB _u(7) @@ -1012,6 +1019,7 @@ #define POWMAN_STATE_REQ_ACCESS "RW" // ----------------------------------------------------------------------------- // Field : POWMAN_STATE_CURRENT +// Description : Indicates the current power state #define POWMAN_STATE_CURRENT_RESET _u(0xf) #define POWMAN_STATE_CURRENT_BITS _u(0x0000000f) #define POWMAN_STATE_CURRENT_MSB _u(3) @@ -1019,8 +1027,7 @@ #define POWMAN_STATE_CURRENT_ACCESS "RO" // ============================================================================= // Register : POWMAN_POW_FASTDIV -// Description : None -// divides the POWMAN clock to provide a tick for the delay module +// Description : divides the POWMAN clock to provide a tick for the delay module // and state machines // when clk_pow is running from the slow clock it is not divided // when clk_pow is running from the fast clock it is divided by @@ -1187,6 +1194,10 @@ #define POWMAN_EXT_TIME_REF_SOURCE_SEL_MSB _u(1) #define POWMAN_EXT_TIME_REF_SOURCE_SEL_LSB _u(0) #define POWMAN_EXT_TIME_REF_SOURCE_SEL_ACCESS "RW" +#define POWMAN_EXT_TIME_REF_SOURCE_SEL_VALUE_GPIO12 _u(0x0) +#define POWMAN_EXT_TIME_REF_SOURCE_SEL_VALUE_GPIO20 _u(0x1) +#define POWMAN_EXT_TIME_REF_SOURCE_SEL_VALUE_GPIO14 _u(0x2) +#define POWMAN_EXT_TIME_REF_SOURCE_SEL_VALUE_GPIO22 _u(0x3) // ============================================================================= // Register : POWMAN_LPOSC_FREQ_KHZ_INT // Description : Informs the AON Timer of the integer component of the clock @@ -1241,8 +1252,7 @@ #define POWMAN_XOSC_FREQ_KHZ_FRAC_ACCESS "RW" // ============================================================================= // Register : POWMAN_SET_TIME_63TO48 -// Description : None -// For setting the time, do not use for reading the time, use +// Description : For setting the time, do not use for reading the time, use // POWMAN_READ_TIME_UPPER and POWMAN_READ_TIME_LOWER. This field // must only be written when POWMAN_TIMER_RUN=0 #define POWMAN_SET_TIME_63TO48_OFFSET _u(0x00000060) @@ -1253,8 +1263,7 @@ #define POWMAN_SET_TIME_63TO48_ACCESS "RW" // ============================================================================= // Register : POWMAN_SET_TIME_47TO32 -// Description : None -// For setting the time, do not use for reading the time, use +// Description : For setting the time, do not use for reading the time, use // POWMAN_READ_TIME_UPPER and POWMAN_READ_TIME_LOWER. This field // must only be written when POWMAN_TIMER_RUN=0 #define POWMAN_SET_TIME_47TO32_OFFSET _u(0x00000064) @@ -1265,8 +1274,7 @@ #define POWMAN_SET_TIME_47TO32_ACCESS "RW" // ============================================================================= // Register : POWMAN_SET_TIME_31TO16 -// Description : None -// For setting the time, do not use for reading the time, use +// Description : For setting the time, do not use for reading the time, use // POWMAN_READ_TIME_UPPER and POWMAN_READ_TIME_LOWER. This field // must only be written when POWMAN_TIMER_RUN=0 #define POWMAN_SET_TIME_31TO16_OFFSET _u(0x00000068) @@ -1277,8 +1285,7 @@ #define POWMAN_SET_TIME_31TO16_ACCESS "RW" // ============================================================================= // Register : POWMAN_SET_TIME_15TO0 -// Description : None -// For setting the time, do not use for reading the time, use +// Description : For setting the time, do not use for reading the time, use // POWMAN_READ_TIME_UPPER and POWMAN_READ_TIME_LOWER. This field // must only be written when POWMAN_TIMER_RUN=0 #define POWMAN_SET_TIME_15TO0_OFFSET _u(0x0000006c) @@ -1289,8 +1296,7 @@ #define POWMAN_SET_TIME_15TO0_ACCESS "RW" // ============================================================================= // Register : POWMAN_READ_TIME_UPPER -// Description : None -// For reading bits 63:32 of the timer. When reading all 64 bits +// Description : For reading bits 63:32 of the timer. When reading all 64 bits // it is possible for the LOWER count to rollover during the read. // It is recommended to read UPPER, then LOWER, then re-read UPPER // and, if it has changed, re-read LOWER. @@ -1302,8 +1308,7 @@ #define POWMAN_READ_TIME_UPPER_ACCESS "RO" // ============================================================================= // Register : POWMAN_READ_TIME_LOWER -// Description : None -// For reading bits 31:0 of the timer. +// Description : For reading bits 31:0 of the timer. #define POWMAN_READ_TIME_LOWER_OFFSET _u(0x00000074) #define POWMAN_READ_TIME_LOWER_BITS _u(0xffffffff) #define POWMAN_READ_TIME_LOWER_RESET _u(0x00000000) @@ -1312,8 +1317,7 @@ #define POWMAN_READ_TIME_LOWER_ACCESS "RO" // ============================================================================= // Register : POWMAN_ALARM_TIME_63TO48 -// Description : None -// This field must only be written when POWMAN_ALARM_ENAB=0 +// Description : This field must only be written when POWMAN_ALARM_ENAB=0 #define POWMAN_ALARM_TIME_63TO48_OFFSET _u(0x00000078) #define POWMAN_ALARM_TIME_63TO48_BITS _u(0x0000ffff) #define POWMAN_ALARM_TIME_63TO48_RESET _u(0x00000000) @@ -1322,8 +1326,7 @@ #define POWMAN_ALARM_TIME_63TO48_ACCESS "RW" // ============================================================================= // Register : POWMAN_ALARM_TIME_47TO32 -// Description : None -// This field must only be written when POWMAN_ALARM_ENAB=0 +// Description : This field must only be written when POWMAN_ALARM_ENAB=0 #define POWMAN_ALARM_TIME_47TO32_OFFSET _u(0x0000007c) #define POWMAN_ALARM_TIME_47TO32_BITS _u(0x0000ffff) #define POWMAN_ALARM_TIME_47TO32_RESET _u(0x00000000) @@ -1332,8 +1335,7 @@ #define POWMAN_ALARM_TIME_47TO32_ACCESS "RW" // ============================================================================= // Register : POWMAN_ALARM_TIME_31TO16 -// Description : None -// This field must only be written when POWMAN_ALARM_ENAB=0 +// Description : This field must only be written when POWMAN_ALARM_ENAB=0 #define POWMAN_ALARM_TIME_31TO16_OFFSET _u(0x00000080) #define POWMAN_ALARM_TIME_31TO16_BITS _u(0x0000ffff) #define POWMAN_ALARM_TIME_31TO16_RESET _u(0x00000000) @@ -1342,8 +1344,7 @@ #define POWMAN_ALARM_TIME_31TO16_ACCESS "RW" // ============================================================================= // Register : POWMAN_ALARM_TIME_15TO0 -// Description : None -// This field must only be written when POWMAN_ALARM_ENAB=0 +// Description : This field must only be written when POWMAN_ALARM_ENAB=0 #define POWMAN_ALARM_TIME_15TO0_OFFSET _u(0x00000084) #define POWMAN_ALARM_TIME_15TO0_BITS _u(0x0000ffff) #define POWMAN_ALARM_TIME_15TO0_RESET _u(0x00000000) diff --git a/lib/pico-sdk/rp2350/hardware/regs/rosc.h b/lib/pico-sdk/rp2350/hardware/regs/rosc.h index 4865c2ee3..4caa07a7b 100644 --- a/lib/pico-sdk/rp2350/hardware/regs/rosc.h +++ b/lib/pico-sdk/rp2350/hardware/regs/rosc.h @@ -39,9 +39,9 @@ // Field : ROSC_CTRL_FREQ_RANGE // Description : Controls the number of delay stages in the ROSC ring // LOW uses stages 0 to 7 -// MEDIUM uses stages 2 to 7 -// HIGH uses stages 4 to 7 -// TOOHIGH uses stages 6 to 7 and should not be used because its +// MEDIUM uses stages 0 to 5 +// HIGH uses stages 0 to 3 +// TOOHIGH uses stages 0 to 1 and should not be used because its // frequency exceeds design specifications // The clock output will not glitch when changing the range up one // step at a time @@ -77,7 +77,7 @@ // DS1_RANDOM=1 #define ROSC_FREQA_OFFSET _u(0x00000004) #define ROSC_FREQA_BITS _u(0xffff77ff) -#define ROSC_FREQA_RESET _u(0x00000000) +#define ROSC_FREQA_RESET _u(0x00000088) // ----------------------------------------------------------------------------- // Field : ROSC_FREQA_PASSWD // Description : Set to 0x9696 to apply the settings @@ -108,7 +108,7 @@ // ----------------------------------------------------------------------------- // Field : ROSC_FREQA_DS1_RANDOM // Description : Randomises the stage 1 drive strength -#define ROSC_FREQA_DS1_RANDOM_RESET _u(0x0) +#define ROSC_FREQA_DS1_RANDOM_RESET _u(0x1) #define ROSC_FREQA_DS1_RANDOM_BITS _u(0x00000080) #define ROSC_FREQA_DS1_RANDOM_MSB _u(7) #define ROSC_FREQA_DS1_RANDOM_LSB _u(7) @@ -124,7 +124,7 @@ // ----------------------------------------------------------------------------- // Field : ROSC_FREQA_DS0_RANDOM // Description : Randomises the stage 0 drive strength -#define ROSC_FREQA_DS0_RANDOM_RESET _u(0x0) +#define ROSC_FREQA_DS0_RANDOM_RESET _u(0x1) #define ROSC_FREQA_DS0_RANDOM_BITS _u(0x00000008) #define ROSC_FREQA_DS0_RANDOM_MSB _u(3) #define ROSC_FREQA_DS0_RANDOM_LSB _u(3) @@ -206,7 +206,7 @@ // On power-up this field is initialised to WAKE // An invalid write will also select WAKE // Warning: setup the irq before selecting dormant mode -// 0x636f6d61 -> dormant +// 0x636f6d61 -> DORMANT // 0x77616b65 -> WAKE #define ROSC_DORMANT_OFFSET _u(0x00000010) #define ROSC_DORMANT_BITS _u(0xffffffff) diff --git a/lib/pico-sdk/rp2350/hardware/regs/rvcsr.h b/lib/pico-sdk/rp2350/hardware/regs/rvcsr.h index f5ff378ab..a375a19d1 100644 --- a/lib/pico-sdk/rp2350/hardware/regs/rvcsr.h +++ b/lib/pico-sdk/rp2350/hardware/regs/rvcsr.h @@ -86,7 +86,7 @@ // In addition the following custom extensions are configured: // Xh3bm, Xh3power, Xh3irq, Xh3pmpm #define RVCSR_MISA_OFFSET _u(0x00000301) -#define RVCSR_MISA_BITS _u(0xc0901107) +#define RVCSR_MISA_BITS _u(0xc0b511bf) #define RVCSR_MISA_RESET _u(0x40901105) // ----------------------------------------------------------------------------- // Field : RVCSR_MISA_MXL @@ -106,6 +106,14 @@ #define RVCSR_MISA_X_LSB _u(23) #define RVCSR_MISA_X_ACCESS "RO" // ----------------------------------------------------------------------------- +// Field : RVCSR_MISA_V +// Description : Vector extension (not implemented). +#define RVCSR_MISA_V_RESET _u(0x0) +#define RVCSR_MISA_V_BITS _u(0x00200000) +#define RVCSR_MISA_V_MSB _u(21) +#define RVCSR_MISA_V_LSB _u(21) +#define RVCSR_MISA_V_ACCESS "RO" +// ----------------------------------------------------------------------------- // Field : RVCSR_MISA_U // Description : Value of 1 indicates U-mode is implemented. #define RVCSR_MISA_U_RESET _u(0x1) @@ -114,6 +122,22 @@ #define RVCSR_MISA_U_LSB _u(20) #define RVCSR_MISA_U_ACCESS "RO" // ----------------------------------------------------------------------------- +// Field : RVCSR_MISA_S +// Description : Supervisor extension (not implemented). +#define RVCSR_MISA_S_RESET _u(0x0) +#define RVCSR_MISA_S_BITS _u(0x00040000) +#define RVCSR_MISA_S_MSB _u(18) +#define RVCSR_MISA_S_LSB _u(18) +#define RVCSR_MISA_S_ACCESS "RO" +// ----------------------------------------------------------------------------- +// Field : RVCSR_MISA_Q +// Description : Quad-precision floating point extension (not implemented). +#define RVCSR_MISA_Q_RESET _u(0x0) +#define RVCSR_MISA_Q_BITS _u(0x00010000) +#define RVCSR_MISA_Q_MSB _u(16) +#define RVCSR_MISA_Q_LSB _u(16) +#define RVCSR_MISA_Q_ACCESS "RO" +// ----------------------------------------------------------------------------- // Field : RVCSR_MISA_M // Description : Value of 1 indicates the M extension (integer multiply/divide) // is implemented. @@ -132,6 +156,39 @@ #define RVCSR_MISA_I_LSB _u(8) #define RVCSR_MISA_I_ACCESS "RO" // ----------------------------------------------------------------------------- +// Field : RVCSR_MISA_H +// Description : Hypervisor extension (not implemented, I agree it would be +// pretty cool on a microcontroller through). +#define RVCSR_MISA_H_RESET _u(0x0) +#define RVCSR_MISA_H_BITS _u(0x00000080) +#define RVCSR_MISA_H_MSB _u(7) +#define RVCSR_MISA_H_LSB _u(7) +#define RVCSR_MISA_H_ACCESS "RO" +// ----------------------------------------------------------------------------- +// Field : RVCSR_MISA_F +// Description : Single-precision floating point extension (not implemented). +#define RVCSR_MISA_F_RESET _u(0x0) +#define RVCSR_MISA_F_BITS _u(0x00000020) +#define RVCSR_MISA_F_MSB _u(5) +#define RVCSR_MISA_F_LSB _u(5) +#define RVCSR_MISA_F_ACCESS "RO" +// ----------------------------------------------------------------------------- +// Field : RVCSR_MISA_E +// Description : RV32E/64E base ISA (not implemented). +#define RVCSR_MISA_E_RESET _u(0x0) +#define RVCSR_MISA_E_BITS _u(0x00000010) +#define RVCSR_MISA_E_MSB _u(4) +#define RVCSR_MISA_E_LSB _u(4) +#define RVCSR_MISA_E_ACCESS "RO" +// ----------------------------------------------------------------------------- +// Field : RVCSR_MISA_D +// Description : Double-precision floating point extension (not implemented). +#define RVCSR_MISA_D_RESET _u(0x0) +#define RVCSR_MISA_D_BITS _u(0x00000008) +#define RVCSR_MISA_D_MSB _u(3) +#define RVCSR_MISA_D_LSB _u(3) +#define RVCSR_MISA_D_ACCESS "RO" +// ----------------------------------------------------------------------------- // Field : RVCSR_MISA_C // Description : Value of 1 indicates the C extension (compressed instructions) // is implemented. @@ -207,7 +264,7 @@ // Description : Timer interrupt enable. The processor transfers to the timer // interrupt vector when `mie.mtie`, `mip.mtip` and `mstatus.mie` // are all 1, unless a software or external interrupt request is -// also valid at this time. +// also both pending and enabled at this time. #define RVCSR_MIE_MTIE_RESET _u(0x0) #define RVCSR_MIE_MTIE_BITS _u(0x00000080) #define RVCSR_MIE_MTIE_MSB _u(7) @@ -216,9 +273,9 @@ // ----------------------------------------------------------------------------- // Field : RVCSR_MIE_MSIE // Description : Software interrupt enable. The processor transfers to the -// software interrupt vector `mie.msie`, `mip.msip` and +// software interrupt vector when `mie.msie`, `mip.msip` and // `mstatus.mie` are all 1, unless an external interrupt request -// is also valid at this time. +// is also both pending and enabled at this time. #define RVCSR_MIE_MSIE_RESET _u(0x0) #define RVCSR_MIE_MSIE_BITS _u(0x00000008) #define RVCSR_MIE_MSIE_MSB _u(3) @@ -336,7 +393,7 @@ #define RVCSR_MENVCFGH_RESET _u(0x00000000) #define RVCSR_MENVCFGH_MSB _u(31) #define RVCSR_MENVCFGH_LSB _u(0) -#define RVCSR_MENVCFGH_ACCESS "RW" +#define RVCSR_MENVCFGH_ACCESS "-" // ============================================================================= // Register : RVCSR_MCOUNTINHIBIT // Description : Count inhibit register for `mcycle`/`minstret` @@ -732,7 +789,7 @@ // Description : Timer interrupt pending. The processor transfers to the timer // interrupt vector when `mie.mtie`, `mip.mtip` and `mstatus.mie` // are all 1, unless a software or external interrupt request is -// also valid at this time. +// also both pending and enabled at this time. #define RVCSR_MIP_MTIP_RESET _u(0x0) #define RVCSR_MIP_MTIP_BITS _u(0x00000080) #define RVCSR_MIP_MTIP_MSB _u(7) @@ -741,9 +798,9 @@ // ----------------------------------------------------------------------------- // Field : RVCSR_MIP_MSIP // Description : Software interrupt pending. The processor transfers to the -// software interrupt vector `mie.msie`, `mip.msip` and +// software interrupt vector when `mie.msie`, `mip.msip` and // `mstatus.mie` are all 1, unless an external interrupt request -// is also valid at this time. +// is also both pending and enabled at this time. #define RVCSR_MIP_MSIP_RESET _u(0x0) #define RVCSR_MIP_MSIP_BITS _u(0x00000008) #define RVCSR_MIP_MSIP_MSB _u(3) @@ -3099,14 +3156,18 @@ #define RVCSR_MVENDORID_RESET _u(0x00000000) // ----------------------------------------------------------------------------- // Field : RVCSR_MVENDORID_BANK -#define RVCSR_MVENDORID_BANK_RESET "-" +// Description : Value of 9 indicates 9 continuation codes, which is JEP106 bank +// 10. +#define RVCSR_MVENDORID_BANK_RESET _u(0x0000009) #define RVCSR_MVENDORID_BANK_BITS _u(0xffffff80) #define RVCSR_MVENDORID_BANK_MSB _u(31) #define RVCSR_MVENDORID_BANK_LSB _u(7) #define RVCSR_MVENDORID_BANK_ACCESS "RO" // ----------------------------------------------------------------------------- // Field : RVCSR_MVENDORID_OFFSET -#define RVCSR_MVENDORID_OFFSET_RESET "-" +// Description : ID 0x13 in bank 10 is the JEP106 ID for Raspberry Pi Ltd, the +// vendor of RP2350. +#define RVCSR_MVENDORID_OFFSET_RESET _u(0x13) #define RVCSR_MVENDORID_OFFSET_BITS _u(0x0000007f) #define RVCSR_MVENDORID_OFFSET_MSB _u(6) #define RVCSR_MVENDORID_OFFSET_LSB _u(0) @@ -3122,10 +3183,11 @@ #define RVCSR_MARCHID_ACCESS "RO" // ============================================================================= // Register : RVCSR_MIMPID -// Description : Implementation ID +// Description : Implementation ID. On RP2350 this reads as 0x86fc4e3f, which is +// release v1.0-rc1 of Hazard3. #define RVCSR_MIMPID_OFFSET _u(0x00000f13) #define RVCSR_MIMPID_BITS _u(0xffffffff) -#define RVCSR_MIMPID_RESET "-" +#define RVCSR_MIMPID_RESET _u(0x86fc4e3f) #define RVCSR_MIMPID_MSB _u(31) #define RVCSR_MIMPID_LSB _u(0) #define RVCSR_MIMPID_ACCESS "RO" diff --git a/lib/pico-sdk/rp2350/hardware/regs/syscfg.h b/lib/pico-sdk/rp2350/hardware/regs/syscfg.h index 455ebf175..74fb596f4 100644 --- a/lib/pico-sdk/rp2350/hardware/regs/syscfg.h +++ b/lib/pico-sdk/rp2350/hardware/regs/syscfg.h @@ -255,7 +255,10 @@ // ============================================================================= // Register : SYSCFG_AUXCTRL // Description : Auxiliary system control register -// * Bits 7:2: Reserved +// * Bits 7:3: Reserved +// +// * Bit 2: Set to mask OTP power analogue power supply detection +// from resetting OTP controller and PSM // // * Bit 1: When clear, the LPOSC output is XORed into the TRNG // ROSC output as an additional, uncorrelated entropy source. When diff --git a/lib/pico-sdk/rp2350/hardware/regs/ticks.h b/lib/pico-sdk/rp2350/hardware/regs/ticks.h index 79e13523d..3dac57216 100644 --- a/lib/pico-sdk/rp2350/hardware/regs/ticks.h +++ b/lib/pico-sdk/rp2350/hardware/regs/ticks.h @@ -36,8 +36,7 @@ #define TICKS_PROC0_CTRL_ENABLE_ACCESS "RW" // ============================================================================= // Register : TICKS_PROC0_CYCLES -// Description : None -// Total number of clk_tick cycles before the next tick. +// Description : Total number of clk_tick cycles before the next tick. #define TICKS_PROC0_CYCLES_OFFSET _u(0x00000004) #define TICKS_PROC0_CYCLES_BITS _u(0x000001ff) #define TICKS_PROC0_CYCLES_RESET _u(0x00000000) @@ -46,8 +45,7 @@ #define TICKS_PROC0_CYCLES_ACCESS "RW" // ============================================================================= // Register : TICKS_PROC0_COUNT -// Description : None -// Count down timer: the remaining number clk_tick cycles before +// Description : Count down timer: the remaining number clk_tick cycles before // the next tick is generated. #define TICKS_PROC0_COUNT_OFFSET _u(0x00000008) #define TICKS_PROC0_COUNT_BITS _u(0x000001ff) @@ -79,8 +77,7 @@ #define TICKS_PROC1_CTRL_ENABLE_ACCESS "RW" // ============================================================================= // Register : TICKS_PROC1_CYCLES -// Description : None -// Total number of clk_tick cycles before the next tick. +// Description : Total number of clk_tick cycles before the next tick. #define TICKS_PROC1_CYCLES_OFFSET _u(0x00000010) #define TICKS_PROC1_CYCLES_BITS _u(0x000001ff) #define TICKS_PROC1_CYCLES_RESET _u(0x00000000) @@ -89,8 +86,7 @@ #define TICKS_PROC1_CYCLES_ACCESS "RW" // ============================================================================= // Register : TICKS_PROC1_COUNT -// Description : None -// Count down timer: the remaining number clk_tick cycles before +// Description : Count down timer: the remaining number clk_tick cycles before // the next tick is generated. #define TICKS_PROC1_COUNT_OFFSET _u(0x00000014) #define TICKS_PROC1_COUNT_BITS _u(0x000001ff) @@ -122,8 +118,7 @@ #define TICKS_TIMER0_CTRL_ENABLE_ACCESS "RW" // ============================================================================= // Register : TICKS_TIMER0_CYCLES -// Description : None -// Total number of clk_tick cycles before the next tick. +// Description : Total number of clk_tick cycles before the next tick. #define TICKS_TIMER0_CYCLES_OFFSET _u(0x0000001c) #define TICKS_TIMER0_CYCLES_BITS _u(0x000001ff) #define TICKS_TIMER0_CYCLES_RESET _u(0x00000000) @@ -132,8 +127,7 @@ #define TICKS_TIMER0_CYCLES_ACCESS "RW" // ============================================================================= // Register : TICKS_TIMER0_COUNT -// Description : None -// Count down timer: the remaining number clk_tick cycles before +// Description : Count down timer: the remaining number clk_tick cycles before // the next tick is generated. #define TICKS_TIMER0_COUNT_OFFSET _u(0x00000020) #define TICKS_TIMER0_COUNT_BITS _u(0x000001ff) @@ -165,8 +159,7 @@ #define TICKS_TIMER1_CTRL_ENABLE_ACCESS "RW" // ============================================================================= // Register : TICKS_TIMER1_CYCLES -// Description : None -// Total number of clk_tick cycles before the next tick. +// Description : Total number of clk_tick cycles before the next tick. #define TICKS_TIMER1_CYCLES_OFFSET _u(0x00000028) #define TICKS_TIMER1_CYCLES_BITS _u(0x000001ff) #define TICKS_TIMER1_CYCLES_RESET _u(0x00000000) @@ -175,8 +168,7 @@ #define TICKS_TIMER1_CYCLES_ACCESS "RW" // ============================================================================= // Register : TICKS_TIMER1_COUNT -// Description : None -// Count down timer: the remaining number clk_tick cycles before +// Description : Count down timer: the remaining number clk_tick cycles before // the next tick is generated. #define TICKS_TIMER1_COUNT_OFFSET _u(0x0000002c) #define TICKS_TIMER1_COUNT_BITS _u(0x000001ff) @@ -208,8 +200,7 @@ #define TICKS_WATCHDOG_CTRL_ENABLE_ACCESS "RW" // ============================================================================= // Register : TICKS_WATCHDOG_CYCLES -// Description : None -// Total number of clk_tick cycles before the next tick. +// Description : Total number of clk_tick cycles before the next tick. #define TICKS_WATCHDOG_CYCLES_OFFSET _u(0x00000034) #define TICKS_WATCHDOG_CYCLES_BITS _u(0x000001ff) #define TICKS_WATCHDOG_CYCLES_RESET _u(0x00000000) @@ -218,8 +209,7 @@ #define TICKS_WATCHDOG_CYCLES_ACCESS "RW" // ============================================================================= // Register : TICKS_WATCHDOG_COUNT -// Description : None -// Count down timer: the remaining number clk_tick cycles before +// Description : Count down timer: the remaining number clk_tick cycles before // the next tick is generated. #define TICKS_WATCHDOG_COUNT_OFFSET _u(0x00000038) #define TICKS_WATCHDOG_COUNT_BITS _u(0x000001ff) @@ -251,8 +241,7 @@ #define TICKS_RISCV_CTRL_ENABLE_ACCESS "RW" // ============================================================================= // Register : TICKS_RISCV_CYCLES -// Description : None -// Total number of clk_tick cycles before the next tick. +// Description : Total number of clk_tick cycles before the next tick. #define TICKS_RISCV_CYCLES_OFFSET _u(0x00000040) #define TICKS_RISCV_CYCLES_BITS _u(0x000001ff) #define TICKS_RISCV_CYCLES_RESET _u(0x00000000) @@ -261,8 +250,7 @@ #define TICKS_RISCV_CYCLES_ACCESS "RW" // ============================================================================= // Register : TICKS_RISCV_COUNT -// Description : None -// Count down timer: the remaining number clk_tick cycles before +// Description : Count down timer: the remaining number clk_tick cycles before // the next tick is generated. #define TICKS_RISCV_COUNT_OFFSET _u(0x00000044) #define TICKS_RISCV_COUNT_BITS _u(0x000001ff) diff --git a/lib/pico-sdk/rp2350/hardware/regs/usb.h b/lib/pico-sdk/rp2350/hardware/regs/usb.h index fbf1b7b36..4bb142bf5 100644 --- a/lib/pico-sdk/rp2350/hardware/regs/usb.h +++ b/lib/pico-sdk/rp2350/hardware/regs/usb.h @@ -1082,13 +1082,14 @@ #define USB_SIE_STATUS_SPEED_ACCESS "RO" // ----------------------------------------------------------------------------- // Field : USB_SIE_STATUS_SUSPENDED -// Description : Bus in suspended state. Valid for device. Device will go into -// suspend if neither Keep Alive / SOF frames are enabled. +// Description : Bus in suspended state. Valid for device and host. Host and +// device will go into suspend if neither Keep Alive / SOF frames +// are enabled. #define USB_SIE_STATUS_SUSPENDED_RESET _u(0x0) #define USB_SIE_STATUS_SUSPENDED_BITS _u(0x00000010) #define USB_SIE_STATUS_SUSPENDED_MSB _u(4) #define USB_SIE_STATUS_SUSPENDED_LSB _u(4) -#define USB_SIE_STATUS_SUSPENDED_ACCESS "WC" +#define USB_SIE_STATUS_SUSPENDED_ACCESS "RO" // ----------------------------------------------------------------------------- // Field : USB_SIE_STATUS_LINE_STATE // Description : USB bus line state diff --git a/lib/pico-sdk/rp2350/hardware/structs/busctrl.h b/lib/pico-sdk/rp2350/hardware/structs/busctrl.h index 2eb83a992..b38797b25 100644 --- a/lib/pico-sdk/rp2350/hardware/structs/busctrl.h +++ b/lib/pico-sdk/rp2350/hardware/structs/busctrl.h @@ -24,7 +24,6 @@ // BITMASK [BITRANGE] FIELDNAME (RESETVALUE) DESCRIPTION /** \brief Bus fabric performance counters on RP2350 (used as typedef \ref bus_ctrl_perf_counter_t) - * \ingroup hardware_busctrl */ typedef enum bus_ctrl_perf_counter_rp2350 { arbiter_rom_perf_event_access = 19, diff --git a/lib/pico-sdk/rp2350/hardware/structs/powman.h b/lib/pico-sdk/rp2350/hardware/structs/powman.h index a81890e3c..3aa8015c0 100644 --- a/lib/pico-sdk/rp2350/hardware/structs/powman.h +++ b/lib/pico-sdk/rp2350/hardware/structs/powman.h @@ -137,14 +137,14 @@ typedef struct { _REG_(POWMAN_STATE_OFFSET) // POWMAN_STATE // This register controls the power state of the 4 power domains - // 0x00002000 [13] CHANGING (0) - // 0x00001000 [12] WAITING (0) - // 0x00000800 [11] BAD_HW_REQ (0) Bad hardware initiated state request - // 0x00000400 [10] BAD_SW_REQ (0) Bad software initiated state request - // 0x00000200 [9] PWRUP_WHILE_WAITING (0) Request ignored because of a pending pwrup request - // 0x00000100 [8] REQ_IGNORED (0) - // 0x000000f0 [7:4] REQ (0x0) - // 0x0000000f [3:0] CURRENT (0xf) + // 0x00002000 [13] CHANGING (0) Indicates a power state change is in progress + // 0x00001000 [12] WAITING (0) Indicates the power manager has received a state change... + // 0x00000800 [11] BAD_HW_REQ (0) Invalid hardware initiated state request, power up... + // 0x00000400 [10] BAD_SW_REQ (0) Invalid software initiated state request ignored + // 0x00000200 [9] PWRUP_WHILE_WAITING (0) Indicates that a power state change request was ignored... + // 0x00000100 [8] REQ_IGNORED (0) Indicates that a software state change request was... + // 0x000000f0 [7:4] REQ (0x0) This is written by software or hardware to request a new... + // 0x0000000f [3:0] CURRENT (0xf) Indicates the current power state io_rw_32 state; _REG_(POWMAN_POW_FASTDIV_OFFSET) // POWMAN_POW_FASTDIV diff --git a/lib/pico-sdk/rp2350/hardware/structs/rosc.h b/lib/pico-sdk/rp2350/hardware/structs/rosc.h index 73503cc15..4147cfb41 100644 --- a/lib/pico-sdk/rp2350/hardware/structs/rosc.h +++ b/lib/pico-sdk/rp2350/hardware/structs/rosc.h @@ -35,9 +35,9 @@ typedef struct { // 0xffff0000 [31:16] PASSWD (0x0000) Set to 0x9696 to apply the settings + // 0x00007000 [14:12] DS3 (0x0) Stage 3 drive strength // 0x00000700 [10:8] DS2 (0x0) Stage 2 drive strength - // 0x00000080 [7] DS1_RANDOM (0) Randomises the stage 1 drive strength + // 0x00000080 [7] DS1_RANDOM (1) Randomises the stage 1 drive strength // 0x00000070 [6:4] DS1 (0x0) Stage 1 drive strength - // 0x00000008 [3] DS0_RANDOM (0) Randomises the stage 0 drive strength + // 0x00000008 [3] DS0_RANDOM (1) Randomises the stage 0 drive strength // 0x00000007 [2:0] DS0 (0x0) Stage 0 drive strength io_rw_32 freqa; diff --git a/lib/pico-sdk/rp2350/hardware/structs/syscfg.h b/lib/pico-sdk/rp2350/hardware/structs/syscfg.h index 8909c0dbf..71660e5fb 100644 --- a/lib/pico-sdk/rp2350/hardware/structs/syscfg.h +++ b/lib/pico-sdk/rp2350/hardware/structs/syscfg.h @@ -72,7 +72,7 @@ typedef struct { _REG_(SYSCFG_AUXCTRL_OFFSET) // SYSCFG_AUXCTRL // Auxiliary system control register - // 0x000000ff [7:0] AUXCTRL (0x00) * Bits 7:2: Reserved + // 0x000000ff [7:0] AUXCTRL (0x00) * Bits 7:3: Reserved io_rw_32 auxctrl; } syscfg_hw_t; diff --git a/scripts/buildcommands.py b/scripts/buildcommands.py index b35873840..1e2ac1041 100644 --- a/scripts/buildcommands.py +++ b/scripts/buildcommands.py @@ -483,11 +483,22 @@ def git_version(): logging.debug("Got git version: %s" % (repr(ver),)) return ver +# Obtain version info from "klippy/.version" file +def file_version(): + if not os.path.exists('klippy/.version'): + logging.debug("No 'klippy/.version' file/directory found") + return "" + ver = check_output("cat klippy/.version").strip() + logging.debug("Got klippy version: %s" % (repr(ver),)) + return ver + def build_version(extra, cleanbuild): version = git_version() if not version: cleanbuild = False - version = "?" + version = file_version() + if not version: + version = "?" elif 'dirty' in version: cleanbuild = False if not cleanbuild: diff --git a/scripts/ci-install.sh b/scripts/ci-install.sh index 28f7b6540..88b346bc1 100755 --- a/scripts/ci-install.sh +++ b/scripts/ci-install.sh @@ -61,6 +61,7 @@ echo -e "\n\n=============== Install python3 virtualenv\n\n" cd ${MAIN_DIR} virtualenv -p python3 ${BUILD_DIR}/python-env ${BUILD_DIR}/python-env/bin/pip install -r ${MAIN_DIR}/scripts/klippy-requirements.txt +${BUILD_DIR}/python-env/bin/pip install -r ${MAIN_DIR}/scripts/tests-requirements.txt ###################################################################### @@ -71,3 +72,4 @@ echo -e "\n\n=============== Install python2 virtualenv\n\n" cd ${MAIN_DIR} virtualenv -p python2 ${BUILD_DIR}/python2-env ${BUILD_DIR}/python2-env/bin/pip install -r ${MAIN_DIR}/scripts/klippy-requirements.txt +${BUILD_DIR}/python2-env/bin/pip install -r ${MAIN_DIR}/scripts/tests-requirements.txt diff --git a/scripts/spi_flash/spi_flash.py b/scripts/spi_flash/spi_flash.py index e9394dbe5..af93b53e9 100644 --- a/scripts/spi_flash/spi_flash.py +++ b/scripts/spi_flash/spi_flash.py @@ -21,7 +21,6 @@ import util import reactor import serialhdl import clocksync -import mcu ########################################################### # @@ -143,11 +142,38 @@ class SPIFlashError(Exception): class MCUConfigError(SPIFlashError): pass +# Wrapper around query commands +class CommandQueryWrapper: + def __init__(self, serial, msgformat, respformat, oid=None): + self._serial = serial + self._cmd = serial.get_msgparser().lookup_command(msgformat) + serial.get_msgparser().lookup_command(respformat) + self._response = respformat.split()[0] + self._oid = oid + self._cmd_queue = serial.get_default_command_queue() + def send(self, data=(), minclock=0, reqclock=0, retry=True): + cmds = [self._cmd.encode(data)] + xh = serialhdl.SerialRetryCommand(self._serial, self._response, + self._oid) + reqclock = max(minclock, reqclock) + return xh.get_response(cmds, self._cmd_queue, minclock, reqclock, retry) + +# Wrapper around command sending +class CommandWrapper: + def __init__(self, serial, msgformat): + self._serial = serial + msgparser = serial.get_msgparser() + self._cmd = msgparser.lookup_command(msgformat) + self._cmd_queue = serial.get_default_command_queue() + def send(self, data=(), minclock=0, reqclock=0): + cmd = self._cmd.encode(data) + self._serial.raw_send(cmd, minclock, reqclock, self._cmd_queue) + class SPIDirect: def __init__(self, ser): self.oid = SPI_OID - self._spi_send_cmd = mcu.CommandWrapper(ser, SPI_SEND_CMD) - self._spi_transfer_cmd = mcu.CommandQueryWrapper( + self._spi_send_cmd = CommandWrapper(ser, SPI_SEND_CMD) + self._spi_transfer_cmd = CommandQueryWrapper( ser, SPI_XFER_CMD, SPI_XFER_RESPONSE, self.oid) def spi_send(self, data): @@ -159,18 +185,18 @@ class SPIDirect: class SDIODirect: def __init__(self, ser): self.oid = SDIO_OID - self._sdio_send_cmd = mcu.CommandQueryWrapper( + self._sdio_send_cmd = CommandQueryWrapper( ser, SDIO_SEND_CMD, SDIO_SEND_CMD_RESPONSE, self.oid) - self._sdio_read_data = mcu.CommandQueryWrapper( + self._sdio_read_data = CommandQueryWrapper( ser, SDIO_READ_DATA, SDIO_READ_DATA_RESPONSE, self.oid) - self._sdio_write_data = mcu.CommandQueryWrapper( + self._sdio_write_data = CommandQueryWrapper( ser, SDIO_WRITE_DATA, SDIO_WRITE_DATA_RESPONSE, self.oid) - self._sdio_read_data_buffer = mcu.CommandQueryWrapper( + self._sdio_read_data_buffer = CommandQueryWrapper( ser, SDIO_READ_DATA_BUFFER, SDIO_READ_DATA_BUFFER_RESPONSE, self.oid) - self._sdio_write_data_buffer = mcu.CommandWrapper(ser, + self._sdio_write_data_buffer = CommandWrapper(ser, SDIO_WRITE_DATA_BUFFER) - self._sdio_set_speed = mcu.CommandWrapper(ser, SDIO_SET_SPEED) + self._sdio_set_speed = CommandWrapper(ser, SDIO_SET_SPEED) def sdio_send_cmd(self, cmd, argument, wait): return self._sdio_send_cmd.send([self.oid, cmd, argument, wait]) @@ -1254,7 +1280,7 @@ class MCUConnection: # Iterate through backwards compatible response strings for response in GET_CFG_RESPONSES: try: - get_cfg_cmd = mcu.CommandQueryWrapper( + get_cfg_cmd = CommandQueryWrapper( self._serial, GET_CFG_CMD, response) break except Exception as err: diff --git a/scripts/tests-requirements.txt b/scripts/tests-requirements.txt new file mode 100644 index 000000000..a3936f8ba --- /dev/null +++ b/scripts/tests-requirements.txt @@ -0,0 +1,7 @@ +# This file describes the Python virtualenv package requirements for +# the Klipper regression test cases. This is in addition to the +# package requirements listed in the klippy-requirements.txt file. +# Typically the packages listed here are installed via the command: +# pip install -r tests-requirements.txt +scipy==1.2.3 ; python_version < '3.0' +scipy==1.15.3 ; python_version >= '3.0' diff --git a/src/Kconfig b/src/Kconfig index 56b749424..539f7c5b1 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -181,13 +181,13 @@ config NEED_SENSOR_BULK depends on WANT_ADXL345 || WANT_LIS2DW || WANT_MPU9250 || WANT_ICM20948 \ || WANT_HX71X || WANT_ADS1220 || WANT_LDC1612 || WANT_SENSOR_ANGLE default y -config WANT_LOAD_CELL_PROBE +config WANT_TRIGGER_ANALOG bool - depends on WANT_HX71X || WANT_ADS1220 + depends on WANT_HX71X || WANT_ADS1220 || WANT_LDC1612 default y config NEED_SOS_FILTER bool - depends on WANT_LOAD_CELL_PROBE + depends on WANT_TRIGGER_ANALOG default y menu "Optional features (to reduce code size)" depends on HAVE_LIMITED_CODE_SIZE diff --git a/src/Makefile b/src/Makefile index 6bdd3dbba..86832d785 100644 --- a/src/Makefile +++ b/src/Makefile @@ -29,4 +29,4 @@ src-$(CONFIG_WANT_LDC1612) += sensor_ldc1612.c src-$(CONFIG_WANT_SENSOR_ANGLE) += sensor_angle.c src-$(CONFIG_NEED_SENSOR_BULK) += sensor_bulk.c src-$(CONFIG_NEED_SOS_FILTER) += sos_filter.c -src-$(CONFIG_WANT_LOAD_CELL_PROBE) += load_cell_probe.c +src-$(CONFIG_WANT_TRIGGER_ANALOG) += trigger_analog.c diff --git a/src/atsamd/usbserial.c b/src/atsamd/usbserial.c index 460de4645..d083beb86 100644 --- a/src/atsamd/usbserial.c +++ b/src/atsamd/usbserial.c @@ -25,27 +25,30 @@ static uint8_t __aligned(4) acmin[USB_CDC_EP_ACM_SIZE]; static uint8_t __aligned(4) bulkout[USB_CDC_EP_BULK_OUT_SIZE]; static uint8_t __aligned(4) bulkin[USB_CDC_EP_BULK_IN_SIZE]; +// Convert 64, 32, 16, 8 sized buffer to 3, 2, 1, 0 for PCKSIZE.SIZE register +#define BSIZE(bufname) (__builtin_ctz(sizeof(bufname)) - 3) + static UsbDeviceDescriptor usb_desc[] = { [0] = { { { .ADDR.reg = (uint32_t)ep0out, - .PCKSIZE.reg = USB_DEVICE_PCKSIZE_SIZE(sizeof(ep0out) >> 4), + .PCKSIZE.reg = USB_DEVICE_PCKSIZE_SIZE(BSIZE(ep0out)), }, { .ADDR.reg = (uint32_t)ep0in, - .PCKSIZE.reg = USB_DEVICE_PCKSIZE_SIZE(sizeof(ep0in) >> 4), + .PCKSIZE.reg = USB_DEVICE_PCKSIZE_SIZE(BSIZE(ep0in)), }, } }, [USB_CDC_EP_ACM] = { { { }, { .ADDR.reg = (uint32_t)acmin, - .PCKSIZE.reg = USB_DEVICE_PCKSIZE_SIZE(sizeof(acmin) >> 4), + .PCKSIZE.reg = USB_DEVICE_PCKSIZE_SIZE(BSIZE(acmin)), }, } }, [USB_CDC_EP_BULK_OUT] = { { { .ADDR.reg = (uint32_t)bulkout, - .PCKSIZE.reg = USB_DEVICE_PCKSIZE_SIZE(sizeof(bulkout) >> 4), + .PCKSIZE.reg = USB_DEVICE_PCKSIZE_SIZE(BSIZE(bulkout)), }, { }, } }, @@ -53,7 +56,7 @@ static UsbDeviceDescriptor usb_desc[] = { { }, { .ADDR.reg = (uint32_t)bulkin, - .PCKSIZE.reg = USB_DEVICE_PCKSIZE_SIZE(sizeof(bulkin) >> 4), + .PCKSIZE.reg = USB_DEVICE_PCKSIZE_SIZE(BSIZE(bulkin)), }, } }, }; diff --git a/src/avr/Kconfig b/src/avr/Kconfig index d4d78d279..74271ec82 100644 --- a/src/avr/Kconfig +++ b/src/avr/Kconfig @@ -11,7 +11,7 @@ config AVR_SELECT select HAVE_GPIO_I2C select HAVE_GPIO_HARD_PWM select HAVE_STRICT_TIMING - select HAVE_LIMITED_CODE_SIZE if MACH_atmega168 || MACH_atmega328 || MACH_atmega328p || MACH_atmega32u4 + select HAVE_LIMITED_CODE_SIZE if MACH_atmega168 || MACH_atmega328 || MACH_atmega328p || MACH_atmega32u4 || MACH_lgt8f328p config BOARD_DIRECTORY string @@ -39,6 +39,8 @@ choice bool "atmega328" config MACH_atmega168 bool "atmega168" + config MACH_lgt8f328p + bool "lgt8f328p" endchoice config MCU @@ -53,6 +55,12 @@ config MCU default "atmega32u4" if MACH_atmega32u4 default "atmega1280" if MACH_atmega1280 default "atmega2560" if MACH_atmega2560 + default "lgt8f328p" if MACH_lgt8f328p + +config AVR_BUILD_MCU + string + default MCU if !MACH_lgt8f328p + default "atmega328p" if MACH_lgt8f328p config AVRDUDE_PROTOCOL string @@ -62,6 +70,9 @@ config AVRDUDE_PROTOCOL choice prompt "Processor speed" if LOW_LEVEL_OPTIONS + config AVR_FREQ_32000000 + bool "32Mhz" + depends on MACH_lgt8f328p config AVR_FREQ_16000000 bool "16Mhz" config AVR_FREQ_20000000 @@ -75,11 +86,12 @@ config CLOCK_FREQ int default 8000000 if AVR_FREQ_8000000 default 20000000 if AVR_FREQ_20000000 + default 32000000 if AVR_FREQ_32000000 default 16000000 config CLEAR_PRESCALER bool "Manually clear the CPU prescaler field at startup" if LOW_LEVEL_OPTIONS - depends on MACH_at90usb1286 || MACH_at90usb646 || MACH_atmega32u4 + depends on MACH_at90usb1286 || MACH_at90usb646 || MACH_atmega32u4 || MACH_lgt8f328p default y help Some AVR chips ship with a "clock prescaler" that causes the diff --git a/src/avr/Makefile b/src/avr/Makefile index f667de3b7..7cf6d68f2 100644 --- a/src/avr/Makefile +++ b/src/avr/Makefile @@ -6,7 +6,7 @@ CROSS_PREFIX=avr- dirs-y += src/avr src/generic CFLAGS-$(CONFIG_HAVE_LIMITED_CODE_SIZE) += -Os -CFLAGS += $(CFLAGS-y) -mmcu=$(CONFIG_MCU) +CFLAGS += $(CFLAGS-y) -mmcu=$(CONFIG_AVR_BUILD_MCU) # Add avr source files src-y += avr/main.c avr/timer.c diff --git a/src/avr/adc.c b/src/avr/adc.c index 1d16368d0..99fd063f6 100644 --- a/src/avr/adc.c +++ b/src/avr/adc.c @@ -30,6 +30,10 @@ static const uint8_t adc_pins[] PROGMEM = { GPIO('F', 4), GPIO('F', 5), GPIO('F', 6), GPIO('F', 7), GPIO('K', 0), GPIO('K', 1), GPIO('K', 2), GPIO('K', 3), GPIO('K', 4), GPIO('K', 5), GPIO('K', 6), GPIO('K', 7), +#elif CONFIG_MACH_lgt8f328p + GPIO('C', 0), GPIO('C', 1), GPIO('C', 2), GPIO('C', 3), + GPIO('C', 4), GPIO('C', 5), GPIO('E', 1), GPIO('E', 3), + GPIO('C', 7), GPIO('F', 0), GPIO('E', 6), GPIO('E', 7), #endif }; @@ -41,7 +45,11 @@ DECL_ENUMERATION_RANGE("pin", "PE2", GPIO('E', 2), 2); enum { ADMUX_DEFAULT = 0x40 }; enum { ADC_ENABLE = (1<= 8) DIDR2 |= 1 << (chan & 0x07); else +#elif CONFIG_MACH_lgt8f328p + if (chan >= 8) + switch (chan) { + case 8: + DIDR1 |= (1 << 2); + break; + case 9: + DIDR1 |= (1 << 3); + break; + case 10: + DIDR1 |= (1 << 6); + break; + case 11: + DIDR1 |= (1 << 7); + break; + } + else #endif DIDR0 |= 1 << chan; diff --git a/src/avr/gpio.c b/src/avr/gpio.c index f52251770..6164a49d5 100644 --- a/src/avr/gpio.c +++ b/src/avr/gpio.c @@ -20,6 +20,9 @@ DECL_ENUMERATION_RANGE("pin", "PC0", GPIO('C', 0), 8); DECL_ENUMERATION_RANGE("pin", "PD0", GPIO('D', 0), 8); #if CONFIG_MACH_atmega328p DECL_ENUMERATION_RANGE("pin", "PE0", GPIO('E', 0), 8); +#elif CONFIG_MACH_lgt8f328p +DECL_ENUMERATION_RANGE("pin", "PE0", GPIO('E', 0), 8); +DECL_ENUMERATION_RANGE("pin", "PF0", GPIO('F', 0), 8); #endif #ifdef PINE DECL_ENUMERATION_RANGE("pin", "PE0", GPIO('E', 0), 8); @@ -42,6 +45,9 @@ volatile uint8_t * const digital_regs[] PROGMEM = { &PINB, &PINC, &PIND, #if CONFIG_MACH_atmega328p &_SFR_IO8(0x0C), // PINE on atmega328pb +#elif CONFIG_MACH_lgt8f328p + &_SFR_IO8(0x0C), // lgt8f328p have PINE and PINF + &_SFR_IO8(0x12) #endif #ifdef PINE &PINE, &PINF, diff --git a/src/avr/hard_pwm.c b/src/avr/hard_pwm.c index 01d0bd9e6..d7cf4c015 100644 --- a/src/avr/hard_pwm.c +++ b/src/avr/hard_pwm.c @@ -22,7 +22,8 @@ struct gpio_pwm_info { enum { GP_8BIT=1, GP_AFMT=2 }; static const struct gpio_pwm_info pwm_regs[] PROGMEM = { -#if CONFIG_MACH_atmega168 || CONFIG_MACH_atmega328 || CONFIG_MACH_atmega328p +#if CONFIG_MACH_atmega168 || CONFIG_MACH_atmega328 \ + || CONFIG_MACH_atmega328p || CONFIG_MACH_lgt8f328p { GPIO('D', 6), &OCR0A, &TCCR0A, &TCCR0B, 1< -// -// This file may be distributed under the terms of the GNU GPLv3 license. - -#include "basecmd.h" // oid_alloc -#include "command.h" // DECL_COMMAND -#include "sched.h" // shutdown -#include "trsync.h" // trsync_do_trigger -#include "board/misc.h" // timer_read_time -#include "sos_filter.h" // fixedQ12_t -#include "load_cell_probe.h" //load_cell_probe_report_sample -#include // int32_t -#include // abs - -// Q2.29 -typedef int32_t fixedQ2_t; -#define FIXEDQ2 2 -#define FIXEDQ2_FRAC_BITS ((32 - FIXEDQ2) - 1) - -// Q32.29 - a Q2.29 value stored in int64 -typedef int64_t fixedQ32_t; -#define FIXEDQ32_FRAC_BITS FIXEDQ2_FRAC_BITS - -// Q16.15 -typedef int32_t fixedQ16_t; -#define FIXEDQ16 16 -#define FIXEDQ16_FRAC_BITS ((32 - FIXEDQ16) - 1) - -// Q48.15 - a Q16.15 value stored in int64 -typedef int64_t fixedQ48_t; -#define FIXEDQ48_FRAC_BITS FIXEDQ16_FRAC_BITS - -#define MAX_TRIGGER_GRAMS ((1L << FIXEDQ16) - 1) -#define ERROR_SAFETY_RANGE 0 -#define ERROR_OVERFLOW 1 -#define ERROR_WATCHDOG 2 - -// Flags -enum {FLAG_IS_HOMING = 1 << 0 - , FLAG_IS_HOMING_TRIGGER = 1 << 1 - , FLAG_AWAIT_HOMING = 1 << 2 - }; - -// Endstop Structure -struct load_cell_probe { - struct timer time; - uint32_t trigger_grams, trigger_ticks, last_sample_ticks, rest_ticks; - uint32_t homing_start_time; - struct trsync *ts; - int32_t safety_counts_min, safety_counts_max, tare_counts; - uint8_t flags, trigger_reason, error_reason, watchdog_max - , watchdog_count; - fixedQ16_t trigger_grams_fixed; - fixedQ2_t grams_per_count; - struct sos_filter *sf; -}; - -static inline uint8_t -overflows_int32(int64_t value) { - return value > (int64_t)INT32_MAX || value < (int64_t)INT32_MIN; -} - -// returns the integer part of a fixedQ48_t -static inline int64_t -round_fixedQ48(const int64_t fixed_value) { - return fixed_value >> FIXEDQ48_FRAC_BITS; -} - -// Convert sensor counts to grams -static inline fixedQ48_t -counts_to_grams(struct load_cell_probe *lce, const int32_t counts) { - // tearing ensures readings are referenced to 0.0g - const int32_t delta = counts - lce->tare_counts; - // convert sensor counts to grams by multiplication: 124 * 0.051 = 6.324 - // this optimizes to single cycle SMULL instruction - const fixedQ32_t product = (int64_t)delta * (int64_t)lce->grams_per_count; - // after multiplication there are 30 fraction bits, reduce to 15 - // caller verifies this wont overflow a 32bit int when truncated - const fixedQ48_t grams = product >> - (FIXEDQ32_FRAC_BITS - FIXEDQ48_FRAC_BITS); - return grams; -} - -static inline uint8_t -is_flag_set(const uint8_t mask, struct load_cell_probe *lce) -{ - return !!(mask & lce->flags); -} - -static inline void -set_flag(uint8_t mask, struct load_cell_probe *lce) -{ - lce->flags |= mask; -} - -static inline void -clear_flag(uint8_t mask, struct load_cell_probe *lce) -{ - lce->flags &= ~mask; -} - -void -try_trigger(struct load_cell_probe *lce, uint32_t ticks) -{ - uint8_t is_homing_triggered = is_flag_set(FLAG_IS_HOMING_TRIGGER, lce); - if (!is_homing_triggered) { - // the first triggering sample when homing sets the trigger time - lce->trigger_ticks = ticks; - // this flag latches until a reset, disabling further triggering - set_flag(FLAG_IS_HOMING_TRIGGER, lce); - trsync_do_trigger(lce->ts, lce->trigger_reason); - } -} - -void -trigger_error(struct load_cell_probe *lce, uint8_t error_code) -{ - trsync_do_trigger(lce->ts, lce->error_reason + error_code); -} - -// Used by Sensors to report new raw ADC sample -void -load_cell_probe_report_sample(struct load_cell_probe *lce - , const int32_t sample) -{ - // only process samples when homing - uint8_t is_homing = is_flag_set(FLAG_IS_HOMING, lce); - if (!is_homing) { - return; - } - - // save new sample - uint32_t ticks = timer_read_time(); - lce->last_sample_ticks = ticks; - lce->watchdog_count = 0; - - // do not trigger before homing start time - uint8_t await_homing = is_flag_set(FLAG_AWAIT_HOMING, lce); - if (await_homing && timer_is_before(ticks, lce->homing_start_time)) { - return; - } - clear_flag(FLAG_AWAIT_HOMING, lce); - - // check for safety limit violations - const uint8_t is_safety_trigger = sample <= lce->safety_counts_min - || sample >= lce->safety_counts_max; - // too much force, this is an error while homing - if (is_safety_trigger) { - trigger_error(lce, ERROR_SAFETY_RANGE); - return; - } - - // convert sample to grams - const fixedQ48_t raw_grams = counts_to_grams(lce, sample); - if (overflows_int32(raw_grams)) { - trigger_error(lce, ERROR_OVERFLOW); - return; - } - - // perform filtering - const fixedQ16_t filtered_grams = sosfilt(lce->sf, (fixedQ16_t)raw_grams); - - // update trigger state - if (abs(filtered_grams) >= lce->trigger_grams_fixed) { - try_trigger(lce, lce->last_sample_ticks); - } -} - -// Timer callback that monitors for timeouts -static uint_fast8_t -watchdog_event(struct timer *t) -{ - struct load_cell_probe *lce = container_of(t, struct load_cell_probe - , time); - uint8_t is_homing = is_flag_set(FLAG_IS_HOMING, lce); - uint8_t is_homing_trigger = is_flag_set(FLAG_IS_HOMING_TRIGGER, lce); - // the watchdog stops when not homing or when trsync becomes triggered - if (!is_homing || is_homing_trigger) { - return SF_DONE; - } - - if (lce->watchdog_count > lce->watchdog_max) { - trigger_error(lce, ERROR_WATCHDOG); - } - lce->watchdog_count += 1; - - // A sample was recently delivered, continue monitoring - lce->time.waketime += lce->rest_ticks; - return SF_RESCHEDULE; -} - -static void -set_endstop_range(struct load_cell_probe *lce - , int32_t safety_counts_min, int32_t safety_counts_max - , int32_t tare_counts, uint32_t trigger_grams - , fixedQ2_t grams_per_count) -{ - if (!(safety_counts_max >= safety_counts_min)) { - shutdown("Safety range reversed"); - } - if (trigger_grams > MAX_TRIGGER_GRAMS) { - shutdown("trigger_grams too large"); - } - // grams_per_count must be a positive fraction in Q2 format - const fixedQ2_t one = 1L << FIXEDQ2_FRAC_BITS; - if (grams_per_count < 0 || grams_per_count >= one) { - shutdown("grams_per_count is invalid"); - } - lce->safety_counts_min = safety_counts_min; - lce->safety_counts_max = safety_counts_max; - lce->tare_counts = tare_counts; - lce->trigger_grams = trigger_grams; - lce->trigger_grams_fixed = trigger_grams << FIXEDQ16_FRAC_BITS; - lce->grams_per_count = grams_per_count; -} - -// Create a load_cell_probe -void -command_config_load_cell_probe(uint32_t *args) -{ - struct load_cell_probe *lce = oid_alloc(args[0] - , command_config_load_cell_probe, sizeof(*lce)); - lce->flags = 0; - lce->trigger_ticks = 0; - lce->watchdog_max = 0; - lce->watchdog_count = 0; - lce->sf = sos_filter_oid_lookup(args[1]); - set_endstop_range(lce, 0, 0, 0, 0, 0); -} -DECL_COMMAND(command_config_load_cell_probe, "config_load_cell_probe" - " oid=%c sos_filter_oid=%c"); - -// Lookup a load_cell_probe -struct load_cell_probe * -load_cell_probe_oid_lookup(uint8_t oid) -{ - return oid_lookup(oid, command_config_load_cell_probe); -} - -// Set the triggering range and tare value -void -command_load_cell_probe_set_range(uint32_t *args) -{ - struct load_cell_probe *lce = load_cell_probe_oid_lookup(args[0]); - set_endstop_range(lce, args[1], args[2], args[3], args[4] - , (fixedQ16_t)args[5]); -} -DECL_COMMAND(command_load_cell_probe_set_range, "load_cell_probe_set_range" - " oid=%c safety_counts_min=%i safety_counts_max=%i tare_counts=%i" - " trigger_grams=%u grams_per_count=%i"); - -// Home an axis -void -command_load_cell_probe_home(uint32_t *args) -{ - struct load_cell_probe *lce = load_cell_probe_oid_lookup(args[0]); - sched_del_timer(&lce->time); - // clear the homing trigger flag - clear_flag(FLAG_IS_HOMING_TRIGGER, lce); - clear_flag(FLAG_IS_HOMING, lce); - lce->trigger_ticks = 0; - lce->ts = NULL; - // 0 samples indicates homing is finished - if (args[3] == 0) { - // Disable end stop checking - return; - } - lce->ts = trsync_oid_lookup(args[1]); - lce->trigger_reason = args[2]; - lce->error_reason = args[3]; - lce->time.waketime = args[4]; - lce->homing_start_time = args[4]; - lce->rest_ticks = args[5]; - lce->watchdog_max = args[6]; - lce->watchdog_count = 0; - lce->time.func = watchdog_event; - set_flag(FLAG_IS_HOMING, lce); - set_flag(FLAG_AWAIT_HOMING, lce); - sched_add_timer(&lce->time); -} -DECL_COMMAND(command_load_cell_probe_home, - "load_cell_probe_home oid=%c trsync_oid=%c trigger_reason=%c" - " error_reason=%c clock=%u rest_ticks=%u timeout=%u"); - -void -command_load_cell_probe_query_state(uint32_t *args) -{ - uint8_t oid = args[0]; - struct load_cell_probe *lce = load_cell_probe_oid_lookup(args[0]); - sendf("load_cell_probe_state oid=%c is_homing_trigger=%c trigger_ticks=%u" - , oid - , is_flag_set(FLAG_IS_HOMING_TRIGGER, lce) - , lce->trigger_ticks); -} -DECL_COMMAND(command_load_cell_probe_query_state - , "load_cell_probe_query_state oid=%c"); diff --git a/src/load_cell_probe.h b/src/load_cell_probe.h deleted file mode 100644 index e67c16e55..000000000 --- a/src/load_cell_probe.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef __LOAD_CELL_PROBE_H -#define __LOAD_CELL_PROBE_H - -#include // uint8_t - -struct load_cell_probe *load_cell_probe_oid_lookup(uint8_t oid); -void load_cell_probe_report_sample(struct load_cell_probe *lce - , int32_t sample); - -#endif // load_cell_probe.h diff --git a/src/rp2040/adc.c b/src/rp2040/adc.c index 2daf380a6..477858c54 100644 --- a/src/rp2040/adc.c +++ b/src/rp2040/adc.c @@ -1,6 +1,6 @@ // ADC functions on rp2040 // -// Copyright (C) 2021 Kevin O'Connor +// Copyright (C) 2021-2025 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -10,6 +10,7 @@ #include "hardware/structs/adc.h" // adc_hw #include "hardware/structs/padsbank0.h" // padsbank0_hw #include "hardware/structs/resets.h" // RESETS_RESET_ADC_BITS +#include "hardware/structs/sysinfo.h" // sysinfo_hw #include "internal.h" // enable_pclock #include "sched.h" // sched_shutdown @@ -21,7 +22,19 @@ DECL_ENUMERATION("pin", "ADC_TEMPERATURE", ADC_TEMPERATURE_PIN); struct gpio_adc gpio_adc_setup(uint32_t pin) { - if ((pin < 26 || pin > 29) && pin != ADC_TEMPERATURE_PIN) + uint32_t min_gpio = 26, max_gpio = 29, adc_temp_chan = 4; +#if CONFIG_MACH_RP2350 + // Check for rp2350b package + if (!is_enabled_pclock(RESETS_RESET_SYSINFO_BITS)) + enable_pclock(RESETS_RESET_SYSINFO_BITS); + if (!(sysinfo_hw->package_sel & SYSINFO_PACKAGE_SEL_BITS)) { + min_gpio = 40; + max_gpio = 47; + adc_temp_chan = 8; + } +#endif + + if ((pin < min_gpio || pin > max_gpio) && pin != ADC_TEMPERATURE_PIN) shutdown("Not a valid ADC pin"); // Enable the ADC @@ -32,10 +45,10 @@ gpio_adc_setup(uint32_t pin) uint8_t chan; if (pin == ADC_TEMPERATURE_PIN) { - chan = 4; + chan = adc_temp_chan; adc_hw->cs |= ADC_CS_TS_EN_BITS; } else { - chan = pin - 26; + chan = pin - min_gpio; padsbank0_hw->io[pin] = PADS_BANK0_GPIO0_OD_BITS; } diff --git a/src/rp2040/gpio.c b/src/rp2040/gpio.c index 98e077897..a564d1282 100644 --- a/src/rp2040/gpio.c +++ b/src/rp2040/gpio.c @@ -1,6 +1,6 @@ // GPIO functions on rp2040 // -// Copyright (C) 2021 Kevin O'Connor +// Copyright (C) 2021-2025 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -19,12 +19,16 @@ * Pin mappings ****************************************************************/ -DECL_ENUMERATION_RANGE("pin", "gpio0", 0, 30); +#define NUM_GPIO (CONFIG_MACH_RP2040 ? 30 : 48) + +DECL_ENUMERATION_RANGE("pin", "gpio0", 0, NUM_GPIO); // Set the mode and extended function of a pin void gpio_peripheral(uint32_t gpio, int func, int pull_up) { + if (gpio >= NUM_GPIO) + shutdown("Not a valid pin"); padsbank0_hw->io[gpio] = ( PADS_BANK0_GPIO0_IE_BITS | (PADS_BANK0_GPIO0_DRIVE_VALUE_4MA << PADS_BANK0_GPIO0_DRIVE_MSB) @@ -35,9 +39,12 @@ gpio_peripheral(uint32_t gpio, int func, int pull_up) // Convert a register and bit location back to an integer pin identifier static int -mask_to_pin(uint32_t mask) +mask_to_pin(void *sio, uint32_t mask) { - return ffs(mask)-1; + int pin = ffs(mask)-1; + if (CONFIG_MACH_RP2350 && sio != (void*)sio_hw) + pin += 32; + return pin; } @@ -48,22 +55,26 @@ mask_to_pin(uint32_t mask) struct gpio_out gpio_out_setup(uint8_t pin, uint8_t val) { - if (pin >= 30) - goto fail; - struct gpio_out g = { .bit=1<= NUM_GPIO) + shutdown("Not a valid pin"); + void *sio = (void*)sio_hw; + if (CONFIG_MACH_RP2350 && pin >= 32) { + pin -= 32; + sio += 4; + } + struct gpio_out g = { .sio=sio, .bit=1<gpio_oe_set = g.bit; + sio_hw_t *sio = g.sio; + sio->gpio_oe_set = g.bit; gpio_peripheral(pin, 5, 0); irq_restore(flag); } @@ -71,7 +82,8 @@ gpio_out_reset(struct gpio_out g, uint8_t val) void gpio_out_toggle_noirq(struct gpio_out g) { - sio_hw->gpio_togl = g.bit; + sio_hw_t *sio = g.sio; + sio->gpio_togl = g.bit; } void @@ -83,37 +95,43 @@ gpio_out_toggle(struct gpio_out g) void gpio_out_write(struct gpio_out g, uint8_t val) { + sio_hw_t *sio = g.sio; if (val) - sio_hw->gpio_set = g.bit; + sio->gpio_set = g.bit; else - sio_hw->gpio_clr = g.bit; + sio->gpio_clr = g.bit; } struct gpio_in gpio_in_setup(uint8_t pin, int8_t pull_up) { - if (pin >= 30) - goto fail; - struct gpio_in g = { .bit=1<= NUM_GPIO) + shutdown("Not a valid pin"); + void *sio = (void*)sio_hw; + if (CONFIG_MACH_RP2350 && pin >= 32) { + pin -= 32; + sio += 4; + } + struct gpio_in g = { .sio=sio, .bit=1<gpio_oe_clr = g.bit; + sio_hw_t *sio = g.sio; + sio->gpio_oe_clr = g.bit; irq_restore(flag); } uint8_t gpio_in_read(struct gpio_in g) { - return !!(sio_hw->gpio_in & g.bit); + sio_hw_t *sio = g.sio; + return !!(sio->gpio_in & g.bit); } diff --git a/src/rp2040/gpio.h b/src/rp2040/gpio.h index 0dd393bfe..23d432f63 100644 --- a/src/rp2040/gpio.h +++ b/src/rp2040/gpio.h @@ -4,6 +4,7 @@ #include // uint32_t struct gpio_out { + void *sio; uint32_t bit; }; struct gpio_out gpio_out_setup(uint8_t pin, uint8_t val); @@ -13,6 +14,7 @@ void gpio_out_toggle(struct gpio_out g); void gpio_out_write(struct gpio_out g, uint8_t val); struct gpio_in { + void *sio; uint32_t bit; }; struct gpio_in gpio_in_setup(uint8_t pin, int8_t pull_up); diff --git a/src/rp2040/rp2350_bootrom.c b/src/rp2040/rp2350_bootrom.c index 257fb3d44..e691ac7e8 100644 --- a/src/rp2040/rp2350_bootrom.c +++ b/src/rp2040/rp2350_bootrom.c @@ -5,10 +5,10 @@ // This file may be distributed under the terms of the GNU GPLv3 license. #include // uint32_t -#include "boot/picoboot_constants.h" // REBOOT2_FLAG_REBOOT_TYPE_BOOTSEL #include "hardware/address_mapped.h" // static_assert +#include "boot/bootrom_constants.h" // RT_FLAG_FUNC_ARM_NONSEC +#include "boot/picoboot_constants.h" // REBOOT2_FLAG_REBOOT_TYPE_BOOTSEL #include "internal.h" // bootrom_read_unique_id -#include "pico/bootrom_constants.h" // RT_FLAG_FUNC_ARM_NONSEC static void * rom_func_lookup(uint32_t code) diff --git a/src/sensor_ads1220.c b/src/sensor_ads1220.c index f51dc355a..fdf770cf6 100644 --- a/src/sensor_ads1220.c +++ b/src/sensor_ads1220.c @@ -4,16 +4,16 @@ // // This file may be distributed under the terms of the GNU GPLv3 license. +#include +#include "basecmd.h" // oid_alloc #include "board/irq.h" // irq_disable #include "board/gpio.h" // gpio_out_write #include "board/misc.h" // timer_read_time -#include "basecmd.h" // oid_alloc #include "command.h" // DECL_COMMAND #include "sched.h" // sched_add_timer #include "sensor_bulk.h" // sensor_bulk_report -#include "load_cell_probe.h" // load_cell_probe_report_sample #include "spicmds.h" // spidev_transfer -#include +#include "trigger_analog.h" // trigger_analog_update struct ads1220_adc { struct timer timer; @@ -22,7 +22,7 @@ struct ads1220_adc { struct spidev_s *spi; uint8_t pending_flag, data_count; struct sensor_bulk sb; - struct load_cell_probe *lce; + struct trigger_analog *ta; }; // Flag types @@ -97,9 +97,7 @@ ads1220_read_adc(struct ads1220_adc *ads1220, uint8_t oid) counts |= 0xFF000000; // endstop is optional, report if enabled and no errors - if (ads1220->lce) { - load_cell_probe_report_sample(ads1220->lce, counts); - } + trigger_analog_update(ads1220->ta, counts); add_sample(ads1220, oid, counts); } @@ -119,13 +117,13 @@ DECL_COMMAND(command_config_ads1220, "config_ads1220 oid=%c" " spi_oid=%c data_ready_pin=%u"); void -ads1220_attach_load_cell_probe(uint32_t *args) { +ads1220_attach_trigger_analog(uint32_t *args) { uint8_t oid = args[0]; struct ads1220_adc *ads1220 = oid_lookup(oid, command_config_ads1220); - ads1220->lce = load_cell_probe_oid_lookup(args[1]); + ads1220->ta = trigger_analog_oid_lookup(args[1]); } -DECL_COMMAND(ads1220_attach_load_cell_probe, - "ads1220_attach_load_cell_probe oid=%c load_cell_probe_oid=%c"); +DECL_COMMAND(ads1220_attach_trigger_analog, + "ads1220_attach_trigger_analog oid=%c trigger_analog_oid=%c"); // start/stop capturing ADC data void diff --git a/src/sensor_hx71x.c b/src/sensor_hx71x.c index 74575eec1..7d869d9d5 100644 --- a/src/sensor_hx71x.c +++ b/src/sensor_hx71x.c @@ -4,17 +4,17 @@ // // This file may be distributed under the terms of the GNU GPLv3 license. +#include +#include #include "autoconf.h" // CONFIG_MACH_AVR +#include "basecmd.h" // oid_alloc #include "board/gpio.h" // gpio_out_write #include "board/irq.h" // irq_poll #include "board/misc.h" // timer_read_time -#include "basecmd.h" // oid_alloc #include "command.h" // DECL_COMMAND #include "sched.h" // sched_add_timer #include "sensor_bulk.h" // sensor_bulk_report -#include "load_cell_probe.h" // load_cell_probe_report_sample -#include -#include +#include "trigger_analog.h" // trigger_analog_update struct hx71x_adc { struct timer timer; @@ -25,7 +25,7 @@ struct hx71x_adc { struct gpio_in dout; // pin used to receive data from the hx71x struct gpio_out sclk; // pin used to generate clock for the hx71x struct sensor_bulk sb; - struct load_cell_probe *lce; + struct trigger_analog *ta; }; enum { @@ -178,8 +178,8 @@ hx71x_read_adc(struct hx71x_adc *hx71x, uint8_t oid) } // probe is optional, report if enabled - if (hx71x->last_error == 0 && hx71x->lce) { - load_cell_probe_report_sample(hx71x->lce, counts); + if (hx71x->last_error == 0) { + trigger_analog_update(hx71x->ta, counts); } // Add measurement to buffer @@ -206,13 +206,13 @@ DECL_COMMAND(command_config_hx71x, "config_hx71x oid=%c gain_channel=%c" " dout_pin=%u sclk_pin=%u"); void -hx71x_attach_load_cell_probe(uint32_t *args) { +hx71x_attach_trigger_analog(uint32_t *args) { uint8_t oid = args[0]; struct hx71x_adc *hx71x = oid_lookup(oid, command_config_hx71x); - hx71x->lce = load_cell_probe_oid_lookup(args[1]); + hx71x->ta = trigger_analog_oid_lookup(args[1]); } -DECL_COMMAND(hx71x_attach_load_cell_probe, "hx71x_attach_load_cell_probe oid=%c" - " load_cell_probe_oid=%c"); +DECL_COMMAND(hx71x_attach_trigger_analog, "hx71x_attach_trigger_analog oid=%c" + " trigger_analog_oid=%c"); // start/stop capturing ADC data void diff --git a/src/sensor_ldc1612.c b/src/sensor_ldc1612.c index 8b67884f1..2c794fafe 100644 --- a/src/sensor_ldc1612.c +++ b/src/sensor_ldc1612.c @@ -13,11 +13,10 @@ #include "i2ccmds.h" // i2cdev_oid_lookup #include "sched.h" // DECL_TASK #include "sensor_bulk.h" // sensor_bulk_report -#include "trsync.h" // trsync_do_trigger +#include "trigger_analog.h" // trigger_analog_update enum { LDC_PENDING = 1<<0, LDC_HAVE_INTB = 1<<1, - LH_AWAIT_HOMING = 1<<1, LH_CAN_TRIGGER = 1<<2 }; struct ldc1612 { @@ -27,16 +26,21 @@ struct ldc1612 { uint8_t flags; struct sensor_bulk sb; struct gpio_in intb_pin; - // homing - struct trsync *ts; - uint8_t homing_flags; - uint8_t trigger_reason, error_reason; - uint32_t trigger_threshold; - uint32_t homing_clock; + struct trigger_analog *ta; }; static struct task_wake ldc1612_wake; +// Internal errors transmitted in sample reports (or trsync error) +enum { + SE_SENSOR_ERROR, SE_I2C_STATUS, SE_I2C_DATA, SE_INVALID_DATA +}; + +DECL_ENUMERATION("ldc1612_error:", "SENSOR_REPORTS_ERROR", SE_SENSOR_ERROR); +DECL_ENUMERATION("ldc1612_error:", "I2C_STATUS_ERROR", SE_I2C_STATUS); +DECL_ENUMERATION("ldc1612_error:", "I2C_DATA_ERROR", SE_I2C_DATA); +DECL_ENUMERATION("ldc1612_error:", "INVALID_READ_DATA", SE_INVALID_DATA); + // Check if the intb line is "asserted" static int check_intb_asserted(struct ldc1612 *ld) @@ -81,61 +85,14 @@ DECL_COMMAND(command_config_ldc1612_with_intb, "config_ldc1612_with_intb oid=%c i2c_oid=%c intb_pin=%c"); void -command_ldc1612_setup_home(uint32_t *args) -{ +ldc1612_attach_trigger_analog(uint32_t *args) { struct ldc1612 *ld = oid_lookup(args[0], command_config_ldc1612); - - ld->trigger_threshold = args[2]; - if (!ld->trigger_threshold) { - ld->ts = NULL; - ld->homing_flags = 0; - return; - } - ld->homing_clock = args[1]; - ld->ts = trsync_oid_lookup(args[3]); - ld->trigger_reason = args[4]; - ld->error_reason = args[5]; - ld->homing_flags = LH_AWAIT_HOMING | LH_CAN_TRIGGER; + ld->ta = trigger_analog_oid_lookup(args[1]); } -DECL_COMMAND(command_ldc1612_setup_home, - "ldc1612_setup_home oid=%c clock=%u threshold=%u" - " trsync_oid=%c trigger_reason=%c error_reason=%c"); +DECL_COMMAND(ldc1612_attach_trigger_analog, + "ldc1612_attach_trigger_analog oid=%c trigger_analog_oid=%c"); -void -command_query_ldc1612_home_state(uint32_t *args) -{ - struct ldc1612 *ld = oid_lookup(args[0], command_config_ldc1612); - sendf("ldc1612_home_state oid=%c homing=%c trigger_clock=%u" - , args[0], !!(ld->homing_flags & LH_CAN_TRIGGER), ld->homing_clock); -} -DECL_COMMAND(command_query_ldc1612_home_state, - "query_ldc1612_home_state oid=%c"); - -// Check if a sample should trigger a homing event -static void -check_home(struct ldc1612 *ld, uint32_t data) -{ - uint8_t homing_flags = ld->homing_flags; - if (!(homing_flags & LH_CAN_TRIGGER)) - return; - if (data > 0x0fffffff) { - // Sensor reports an issue - cancel homing - ld->homing_flags = 0; - trsync_do_trigger(ld->ts, ld->error_reason); - return; - } - uint32_t time = timer_read_time(); - if ((homing_flags & LH_AWAIT_HOMING) - && timer_is_before(time, ld->homing_clock)) - return; - homing_flags &= ~LH_AWAIT_HOMING; - if (data > ld->trigger_threshold) { - homing_flags = 0; - ld->homing_clock = time; - trsync_do_trigger(ld->ts, ld->trigger_reason); - } - ld->homing_flags = homing_flags; -} +#define DATA_ERROR_AMPLITUDE (1L << 28) // Chip registers #define REG_DATA0_MSB 0x00 @@ -143,49 +100,80 @@ check_home(struct ldc1612 *ld, uint32_t data) #define REG_STATUS 0x18 // Read a register on the ldc1612 -static void +static int read_reg(struct ldc1612 *ld, uint8_t reg, uint8_t *res) { - int ret = i2c_dev_read(ld->i2c, sizeof(reg), ®, 2, res); - i2c_shutdown_on_err(ret); + return i2c_dev_read(ld->i2c, sizeof(reg), ®, 2, res); } // Read the status register on the ldc1612 -static uint16_t -read_reg_status(struct ldc1612 *ld) +static int +read_reg_status(struct ldc1612 *ld, uint16_t *status) { uint8_t data_status[2]; - read_reg(ld, REG_STATUS, data_status); - return (data_status[0] << 8) | data_status[1]; + int ret = read_reg(ld, REG_STATUS, data_status); + *status = (data_status[0] << 8) | data_status[1]; + return ret; } +#define STATUS_UNREADCONV0 (1 << 3) #define BYTES_PER_SAMPLE 4 +static void +report_sample_error(struct ldc1612 *ld, int error_code) +{ + trigger_analog_note_error(ld->ta, error_code); + + uint8_t *d = &ld->sb.data[ld->sb.data_count]; + d[0] = 0xff; + d[1] = 0xff; + d[2] = 0; + d[3] = error_code; +} + // Query ldc1612 data static void ldc1612_query(struct ldc1612 *ld, uint8_t oid) { // Check if data available (and clear INTB line) - uint16_t status = read_reg_status(ld); + uint16_t status; + int ret = read_reg_status(ld, &status); irq_disable(); ld->flags &= ~LDC_PENDING; irq_enable(); - if (!(status & 0x08)) + if (ret) { + report_sample_error(ld, SE_I2C_STATUS); + goto out; + } + if (!(status & STATUS_UNREADCONV0)) + // No data available return; // Read coil0 frequency uint8_t *d = &ld->sb.data[ld->sb.data_count]; - read_reg(ld, REG_DATA0_MSB, &d[0]); - read_reg(ld, REG_DATA0_LSB, &d[2]); + ret |= read_reg(ld, REG_DATA0_MSB, &d[0]); + ret |= read_reg(ld, REG_DATA0_LSB, &d[2]); + + if (ret) { + report_sample_error(ld, SE_I2C_DATA); + goto out; + } + if (d[0] == 0xff && d[1] == 0xff) { + // Invalid data from sensor (conflict with internal error indicator) + report_sample_error(ld, SE_INVALID_DATA); + goto out; + } + + // Check for homing trigger + uint32_t raw_data = (((uint32_t)d[0] << 24) | ((uint32_t)d[1] << 16) + | ((uint32_t)d[2] << 8) | ((uint32_t)d[3])); + if (raw_data & 0xe0000000) + trigger_analog_note_error(ld->ta, SE_SENSOR_ERROR); + else + trigger_analog_update(ld->ta, raw_data & 0x0fffffff); + +out: ld->sb.data_count += BYTES_PER_SAMPLE; - - // Check for endstop trigger - uint32_t data = ((uint32_t)d[0] << 24) - | ((uint32_t)d[1] << 16) - | ((uint32_t)d[2] << 8) - | ((uint32_t)d[3]); - check_home(ld, data); - // Flush local buffer if needed if (ld->sb.data_count + BYTES_PER_SAMPLE > ARRAY_SIZE(ld->sb.data)) sensor_bulk_report(&ld->sb, oid); @@ -228,11 +216,15 @@ command_query_status_ldc1612(uint32_t *args) } // Query sensor to see if a sample is pending + uint16_t status; uint32_t time1 = timer_read_time(); - uint16_t status = read_reg_status(ld); + int ret = read_reg_status(ld, &status); uint32_t time2 = timer_read_time(); - uint32_t fifo = status & 0x08 ? BYTES_PER_SAMPLE : 0; + if (ret) + // Query error - don't send response - host will retry + return; + uint32_t fifo = status & STATUS_UNREADCONV0 ? BYTES_PER_SAMPLE : 0; sensor_bulk_status(&ld->sb, args[0], time1, time2-time1, fifo); } DECL_COMMAND(command_query_status_ldc1612, "query_status_ldc1612 oid=%c"); diff --git a/src/sos_filter.c b/src/sos_filter.c index 6e64bc0a2..00e8d9b09 100644 --- a/src/sos_filter.c +++ b/src/sos_filter.c @@ -9,78 +9,93 @@ #include "sched.h" // shutdown #include "sos_filter.h" // sos_filter -typedef int32_t fixedQ_coeff_t; -typedef int32_t fixedQ_value_t; - // filter strucutre sizes #define SECTION_WIDTH 5 #define STATE_WIDTH 2 struct sos_filter_section { // filter composed of second order sections - fixedQ_coeff_t coeff[SECTION_WIDTH]; // aka sos - fixedQ_value_t state[STATE_WIDTH]; // aka zi + int32_t coeff[SECTION_WIDTH]; // aka sos + int32_t state[STATE_WIDTH]; // aka zi }; struct sos_filter { - uint8_t max_sections, n_sections, coeff_frac_bits, is_active; - uint32_t coeff_rounding; + uint8_t max_sections, n_sections, coeff_frac_bits, scale_frac_bits; + uint8_t auto_offset; + int32_t offset, scale; // filter composed of second order sections struct sos_filter_section filter[0]; }; -static inline uint8_t -overflows_int32(int64_t value) { +static inline int +overflows_int32(int64_t value) +{ return value > (int64_t)INT32_MAX || value < (int64_t)INT32_MIN; } -// Multiply a coefficient in fixedQ_coeff_t by a value fixedQ_value_t -static inline fixedQ_value_t -fixed_mul(struct sos_filter *sf, const fixedQ_coeff_t coeff - , const fixedQ_value_t value) { +// Multiply a coeff*value and shift result by coeff_frac_bits +static int +fixed_mul(int32_t coeff, int32_t value, uint_fast8_t frac_bits, int32_t *res) +{ // This optimizes to single cycle SMULL on Arm Coretex M0+ - int64_t product = (int64_t)coeff * (int64_t)value; - // round up at the last bit to be shifted away - product += sf->coeff_rounding; - // shift the decimal right to discard the coefficient fractional bits - int64_t result = product >> sf->coeff_frac_bits; - // check for overflow of int32_t - if (overflows_int32(result)) { - shutdown("fixed_mul: overflow"); + int64_t result = (int64_t)coeff * (int64_t)value; + if (frac_bits) { + // round up at the last bit to be shifted away + result += 1 << (frac_bits - 1); + // shift the decimal right to discard the coefficient fractional bits + result >>= frac_bits; } // truncate significant 32 bits - return (fixedQ_value_t)result; + *res = (int32_t)result; + // check for overflow of int32_t + if (overflows_int32(result)) + return -1; + return 0; } // Apply the sosfilt algorithm to a new datapoint -// returns the fixedQ_value_t filtered value -int32_t -sosfilt(struct sos_filter *sf, const int32_t unfiltered_value) { - if (!sf->is_active) { - shutdown("sos_filter not property initialized"); +int +sos_filter_apply(struct sos_filter *sf, int32_t *pvalue) +{ + int32_t raw_val = *pvalue; + + // Automatically apply offset (if requested) + if (sf->auto_offset) { + sf->offset = -raw_val; + sf->auto_offset = 0; } - // an empty filter performs no filtering - if (sf->n_sections == 0) { - return unfiltered_value; - } + // Apply offset and scale + int32_t offset = sf->offset, offset_val = raw_val + offset, cur_val; + if ((offset >= 0) != (offset_val >= raw_val)) + // Overflow + return -1; + int ret = fixed_mul(sf->scale, offset_val, sf->scale_frac_bits, &cur_val); + if (ret) + return -1; - fixedQ_value_t cur_val = unfiltered_value; // foreach section + uint_fast8_t cfb = sf->coeff_frac_bits; for (int section_idx = 0; section_idx < sf->n_sections; section_idx++) { struct sos_filter_section *section = &(sf->filter[section_idx]); // apply the section's filter coefficients to input - fixedQ_value_t next_val = fixed_mul(sf, section->coeff[0], cur_val); + int32_t next_val, c1_cur, c2_cur, c3_next, c4_next; + int ret = fixed_mul(section->coeff[0], cur_val, cfb, &next_val); next_val += section->state[0]; - section->state[0] = fixed_mul(sf, section->coeff[1], cur_val) - - fixed_mul(sf, section->coeff[3], next_val) - + (section->state[1]); - section->state[1] = fixed_mul(sf, section->coeff[2], cur_val) - - fixed_mul(sf, section->coeff[4], next_val); + ret |= fixed_mul(section->coeff[1], cur_val, cfb, &c1_cur); + ret |= fixed_mul(section->coeff[3], next_val, cfb, &c3_next); + ret |= fixed_mul(section->coeff[2], cur_val, cfb, &c2_cur); + ret |= fixed_mul(section->coeff[4], next_val, cfb, &c4_next); + if (ret) + // Overflow + return -1; + section->state[0] = c1_cur - c3_next + section->state[1]; + section->state[1] = c2_cur - c4_next; cur_val = next_val; } - return (int32_t)cur_val; + *pvalue = cur_val; + return 0; } // Create an sos_filter @@ -92,7 +107,6 @@ command_config_sos_filter(uint32_t *args) struct sos_filter *sf = oid_alloc(args[0] , command_config_sos_filter, size); sf->max_sections = max_sections; - sf->is_active = 0; } DECL_COMMAND(command_config_sos_filter, "config_sos_filter oid=%c" " max_sections=%c"); @@ -118,11 +132,11 @@ command_sos_filter_set_section(uint32_t *args) { struct sos_filter *sf = sos_filter_oid_lookup(args[0]); // setting a section marks the filter as inactive - sf->is_active = 0; + sf->n_sections = 0; uint8_t section_idx = args[1]; validate_section_index(sf, section_idx); // copy section data - const uint8_t arg_base = 2; + uint8_t arg_base = 2; for (uint8_t i = 0; i < SECTION_WIDTH; i++) { sf->filter[section_idx].coeff[i] = args[i + arg_base]; } @@ -137,17 +151,31 @@ command_sos_filter_set_state(uint32_t *args) { struct sos_filter *sf = sos_filter_oid_lookup(args[0]); // setting a section's state marks the filter as inactive - sf->is_active = 0; + sf->n_sections = 0; // copy state data uint8_t section_idx = args[1]; validate_section_index(sf, section_idx); - const uint8_t arg_base = 2; + uint8_t arg_base = 2; sf->filter[section_idx].state[0] = args[0 + arg_base]; sf->filter[section_idx].state[1] = args[1 + arg_base]; } DECL_COMMAND(command_sos_filter_set_state , "sos_filter_set_state oid=%c section_idx=%c state0=%i state1=%i"); +// Set incoming sample offset/scaling +void +command_trigger_analog_set_offset_scale(uint32_t *args) +{ + struct sos_filter *sf = sos_filter_oid_lookup(args[0]); + sf->offset = args[1]; + sf->scale = args[2]; + sf->scale_frac_bits = args[3] & 0x3f; + sf->auto_offset = args[4]; +} +DECL_COMMAND(command_trigger_analog_set_offset_scale, + "sos_filter_set_offset_scale oid=%c offset=%i scale=%i scale_frac_bits=%c" + " auto_offset=%c"); + // Set one section of the filter void command_sos_filter_activate(uint32_t *args) @@ -157,11 +185,7 @@ command_sos_filter_activate(uint32_t *args) if (n_sections > sf->max_sections) shutdown("Filter section index larger than max_sections"); sf->n_sections = n_sections; - const uint8_t coeff_int_bits = args[2]; - sf->coeff_frac_bits = (31 - coeff_int_bits); - sf->coeff_rounding = (1 << (sf->coeff_frac_bits - 1)); - // mark filter as ready to use - sf->is_active = 1; + sf->coeff_frac_bits = args[2] & 0x3f; } DECL_COMMAND(command_sos_filter_activate - , "sos_filter_set_active oid=%c n_sections=%c coeff_int_bits=%c"); + , "sos_filter_set_active oid=%c n_sections=%c coeff_frac_bits=%c"); diff --git a/src/sos_filter.h b/src/sos_filter.h index 6c215dda3..b697a2686 100644 --- a/src/sos_filter.h +++ b/src/sos_filter.h @@ -4,9 +4,7 @@ #include struct sos_filter; - -int32_t sosfilt(struct sos_filter *sf - , const int32_t unfiltered_value); +int sos_filter_apply(struct sos_filter *sf, int32_t *pvalue); struct sos_filter *sos_filter_oid_lookup(uint8_t oid); #endif // sos_filter.h diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index 1e0df93d2..c8466df38 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -10,7 +10,7 @@ config STM32_SELECT select HAVE_GPIO_I2C if !MACH_STM32F031 select HAVE_GPIO_SPI if !MACH_STM32F031 select HAVE_GPIO_SDIO if MACH_STM32F4 - select HAVE_GPIO_HARD_PWM if MACH_STM32F070 || MACH_STM32F072 || MACH_STM32F1 || MACH_STM32F4 || MACH_STM32F7 || MACH_STM32G0 || MACH_STM32H7 + select HAVE_GPIO_HARD_PWM if MACH_STM32F042 || MACH_STM32F070 || MACH_STM32F072 || MACH_STM32F1 || MACH_STM32F4 || MACH_STM32F7 || MACH_STM32G0 || MACH_STM32H7 select HAVE_STRICT_TIMING select HAVE_CHIPID select HAVE_STEPPER_OPTIMIZED_BOTH_EDGE if !MACH_STM32H7 @@ -156,6 +156,9 @@ config HAVE_STM32_USBFS config HAVE_STM32_USBOTG bool default y if MACH_STM32F2 || MACH_STM32F4 || MACH_STM32F7 || MACH_STM32H7 +config STM32_USB_DOUBLE_BUFFER_TX + bool + default y config HAVE_STM32_CANBUS bool default y if MACH_STM32F1 || MACH_STM32F2 || MACH_STM32F4x5 || MACH_STM32F446 || MACH_STM32F0x2 diff --git a/src/stm32/gpio.h b/src/stm32/gpio.h index 0bfffc4b4..d4c565d3c 100644 --- a/src/stm32/gpio.h +++ b/src/stm32/gpio.h @@ -25,7 +25,8 @@ void gpio_in_reset(struct gpio_in g, int32_t pull_up); uint8_t gpio_in_read(struct gpio_in g); struct gpio_pwm { - void *reg; + void *reg; + uint32_t hwpwm_ticks; }; struct gpio_pwm gpio_pwm_setup(uint8_t pin, uint32_t cycle_time, uint32_t val); void gpio_pwm_write(struct gpio_pwm g, uint32_t val); diff --git a/src/stm32/hard_pwm.c b/src/stm32/hard_pwm.c index 6ed27a305..08defce22 100644 --- a/src/stm32/hard_pwm.c +++ b/src/stm32/hard_pwm.c @@ -11,7 +11,7 @@ #include "internal.h" // GPIO #include "sched.h" // sched_shutdown -#define MAX_PWM (256 + 1) +#define MAX_PWM (1<<15) DECL_CONSTANT("PWM_MAX", MAX_PWM); struct gpio_pwm_info { @@ -21,6 +21,9 @@ struct gpio_pwm_info { static const struct gpio_pwm_info pwm_regs[] = { #if CONFIG_MACH_STM32F0 + #if CONFIG_MACH_STM32F042 + {TIM3, GPIO('B', 4), 1, GPIO_FUNCTION(1)}, + #endif #if CONFIG_MACH_STM32F070 {TIM15, GPIO('A', 2), 1, GPIO_FUNCTION(0)}, {TIM15, GPIO('A', 3), 2, GPIO_FUNCTION(0)}, @@ -307,37 +310,50 @@ gpio_pwm_setup(uint8_t pin, uint32_t cycle_time, uint32_t val) if (p->pin == pin) break; } + gpio_peripheral(p->pin, p->function, 0); // Map cycle_time to pwm clock divisor uint32_t pclk = get_pclock_frequency((uint32_t)p->timer); uint32_t pclock_div = CONFIG_CLOCK_FREQ / pclk; if (pclock_div > 1) pclock_div /= 2; // Timers run at twice the normal pclock frequency - uint32_t prescaler = cycle_time / (pclock_div * (MAX_PWM - 1)); - if (prescaler > UINT16_MAX) { - prescaler = UINT16_MAX; - } else if (prescaler > 0) { - prescaler -= 1; + uint32_t pcycle_time = cycle_time / pclock_div; + + // Convert requested cycle time (cycle_time/CLOCK_FREQ) to actual + // cycle time (hwpwm_ticks*prescaler*pclock_div/CLOCK_FREQ). + uint32_t hwpwm_ticks = pcycle_time, prescaler = 1, shift = 0; + while (hwpwm_ticks > UINT16_MAX) { + shift += 1; + hwpwm_ticks = (pcycle_time + (1 << (shift-1))) >> shift; + prescaler = 1 << shift; } + if (prescaler > UINT16_MAX + 1) { + prescaler = UINT16_MAX + 1; + hwpwm_ticks = UINT16_MAX; + } + if (hwpwm_ticks < 2) + hwpwm_ticks = 2; - gpio_peripheral(p->pin, p->function, 0); - - // Enable clock + // Enable requested pwm hardware block if (!is_enabled_pclock((uint32_t) p->timer)) { enable_pclock((uint32_t) p->timer); } - if (p->timer->CR1 & TIM_CR1_CEN) { - if (p->timer->PSC != (uint16_t) prescaler) { + if (p->timer->PSC != (uint16_t) (prescaler - 1)) { shutdown("PWM already programmed at different speed"); } + if (p->timer->ARR != (uint16_t) (hwpwm_ticks - 1)) { + shutdown("PWM already programmed with different pulse duration"); + } } else { - p->timer->PSC = (uint16_t) prescaler; - p->timer->ARR = MAX_PWM - 1; + p->timer->PSC = prescaler - 1; + p->timer->ARR = hwpwm_ticks - 1; p->timer->EGR |= TIM_EGR_UG; } + // Enable requested channel of hardware pwm block struct gpio_pwm channel; + channel.hwpwm_ticks = hwpwm_ticks; switch (p->channel) { case 1: { channel.reg = (void*) &p->timer->CCR1; @@ -382,15 +398,19 @@ gpio_pwm_setup(uint8_t pin, uint32_t cycle_time, uint32_t val) default: shutdown("Invalid PWM channel"); } + // Enable PWM output p->timer->CR1 |= TIM_CR1_CEN; -#if CONFIG_MACH_STM32H7 || CONFIG_MACH_STM32G0 - p->timer->BDTR |= TIM_BDTR_MOE; -#endif + + // Advanced timers need MOE enabled. On standard timers this is a + // write to reserved memory, but that seems harmless in practice. + p->timer->BDTR = TIM_BDTR_MOE; + return channel; } void gpio_pwm_write(struct gpio_pwm g, uint32_t val) { - *(volatile uint32_t*) g.reg = val; + uint32_t r = DIV_ROUND_CLOSEST(val * g.hwpwm_ticks, MAX_PWM); + *(volatile uint32_t*) g.reg = r; } diff --git a/src/stm32/usbfs.c b/src/stm32/usbfs.c index 5385c956c..3751a4f5a 100644 --- a/src/stm32/usbfs.c +++ b/src/stm32/usbfs.c @@ -1,6 +1,6 @@ // Hardware interface to "fullspeed USB controller" // -// Copyright (C) 2018-2023 Kevin O'Connor +// Copyright (C) 2018-2025 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -19,29 +19,27 @@ // Transfer memory is accessed with 32bits, but contains only 16bits of data typedef volatile uint32_t epmword_t; #define WSIZE 2 - #define USBx_IRQn USB_LP_IRQn -#elif CONFIG_MACH_STM32F0 || CONFIG_MACH_STM32L4 +#elif CONFIG_MACH_STM32F0 || CONFIG_MACH_STM32L4 || CONFIG_MACH_STM32G4 // Transfer memory is accessed with 16bits and contains 16bits of data typedef volatile uint16_t epmword_t; #define WSIZE 2 - #define USBx_IRQn USB_IRQn -#elif CONFIG_MACH_STM32G4 - // Transfer memory is accessed with 16bits and contains 16bits of data - typedef volatile uint16_t epmword_t; - #define WSIZE 2 - #define USBx_IRQn USB_LP_IRQn #elif CONFIG_MACH_STM32G0 // Transfer memory is accessed with 32bits and contains 32bits of data typedef volatile uint32_t epmword_t; #define WSIZE 4 +#endif + +// Different chips have different names for the USB irq +#if CONFIG_MACH_STM32F1 || CONFIG_MACH_STM32G4 + #define USBx_IRQn USB_LP_IRQn +#elif CONFIG_MACH_STM32G0B1 + #define USBx_IRQn USB_UCPD1_2_IRQn +#else #define USBx_IRQn USB_IRQn #endif // The stm32g0 has slightly different register names #if CONFIG_MACH_STM32G0 - #if CONFIG_MACH_STM32G0B1 - #define USB_IRQn USB_UCPD1_2_IRQn - #endif #define USB USB_DRD_FS #define USB_PMAADDR USB_DRD_PMAADDR #define USB_EPADDR_FIELD USB_CHEP_ADDR @@ -55,8 +53,8 @@ // 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 + #define USB_EP_DTOG_TX_Pos 6 + #define USB_EP_DTOG_RX_Pos 14 #endif @@ -244,8 +242,9 @@ usb_read_bulk_out(void *data, uint_fast8_t max_len) 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) +// Send bulk packet to host with double buffering optimization +static int_fast8_t +usb_send_bulk_in_double_buffer(void *data, uint_fast8_t len) { if (readl(&bulk_in_pop_flag)) // No buffer space available @@ -277,6 +276,21 @@ usb_send_bulk_in(void *data, uint_fast8_t len) return len; } +// Send bulk usb packet to host +int_fast8_t +usb_send_bulk_in(void *data, uint_fast8_t len) +{ + if (CONFIG_STM32_USB_DOUBLE_BUFFER_TX) + return usb_send_bulk_in_double_buffer(data, len); + uint32_t ep = USB_CDC_EP_BULK_IN, epr = USB_EPR[ep]; + if ((epr & USB_EPTX_STAT) != USB_EP_TX_NAK) + // No buffer space available + return -1; + btable_write_packet(ep, BUFTX, data, len); + USB_EPR[ep] = calc_epr_bits(epr, USB_EPTX_STAT, USB_EP_TX_VALID); + return len; +} + int_fast8_t usb_read_ep0(void *data, uint_fast8_t max_len) { @@ -335,9 +349,10 @@ usb_set_configure(void) 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); + if (CONFIG_STM32_USB_DOUBLE_BUFFER_TX) { + bulk_in_push_pos = BI_START; + writel(&bulk_in_pop_flag, 0); + } } @@ -362,9 +377,12 @@ usb_reset(void) bulk_out_push_flag = USB_EP_DTOG_TX; ep = USB_CDC_EP_BULK_IN; - USB_EPR[ep] = (USB_CDC_EP_BULK_IN | USB_EP_BULK | USB_EP_KIND - | USB_EP_TX_NAK); - bulk_in_pop_flag = USB_EP_DTOG_RX; + uint32_t bi_epr_flags = USB_CDC_EP_BULK_IN | USB_EP_BULK | USB_EP_TX_NAK; + if (CONFIG_STM32_USB_DOUBLE_BUFFER_TX) { + bi_epr_flags |= USB_EP_KIND; + bulk_in_pop_flag = USB_EP_DTOG_RX; + } + USB_EPR[ep] = bi_epr_flags; USB->CNTR = USB_CNTR_CTRM | USB_CNTR_RESETM; USB->DADDR = USB_DADDR_EF; @@ -384,9 +402,12 @@ USB_IRQHandler(void) bulk_out_push_flag = 0; usb_notify_bulk_out(); } else if (ep == USB_CDC_EP_BULK_IN) { - USB_EPR[ep] = (calc_epr_bits(epr, USB_EP_CTR_RX | USB_EP_CTR_TX, 0) - | bulk_in_pop_flag); - bulk_in_pop_flag = 0; + uint32_t ne = calc_epr_bits(epr, USB_EP_CTR_RX | USB_EP_CTR_TX, 0); + if (CONFIG_STM32_USB_DOUBLE_BUFFER_TX) { + ne |= bulk_in_pop_flag; + bulk_in_pop_flag = 0; + } + USB_EPR[ep] = ne; usb_notify_bulk_in(); } else if (ep == 0) { USB_EPR[ep] = calc_epr_bits(epr, USB_EP_CTR_RX | USB_EP_CTR_TX, 0); diff --git a/src/stm32/usbotg.c b/src/stm32/usbotg.c index b2d52456d..b41daf48c 100644 --- a/src/stm32/usbotg.c +++ b/src/stm32/usbotg.c @@ -1,6 +1,6 @@ // Hardware interface to "USB OTG (on the go) controller" on stm32 // -// Copyright (C) 2019 Kevin O'Connor +// Copyright (C) 2019-2025 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -119,6 +119,24 @@ fifo_write_packet(uint32_t ep, const uint8_t *src, uint32_t len) return len; } +// Write a packet to a tx fifo (optimized for already aligned data) +static int +fifo_write_packet_fast(uint32_t ep, const uint32_t *src, uint32_t len) +{ + void *fifo = EPFIFO(ep); + USB_OTG_INEndpointTypeDef *epi = EPIN(ep); + uint32_t ctl = epi->DIEPCTL; + if (ctl & USB_OTG_DIEPCTL_EPENA) + return -1; + epi->DIEPINT = USB_OTG_DIEPINT_XFRC; + epi->DIEPTSIZ = len | (1 << USB_OTG_DIEPTSIZ_PKTCNT_Pos); + epi->DIEPCTL = ctl | USB_OTG_DIEPCTL_EPENA | USB_OTG_DIEPCTL_CNAK; + uint32_t i; + for (i=0; i < DIV_ROUND_UP(len, sizeof(uint32_t)); i++) + writel(fifo, src[i]); + return 0; +} + // Read a packet from the rx queue static int_fast8_t fifo_read_packet(uint8_t *dest, uint_fast8_t max_len) @@ -208,6 +226,12 @@ usb_read_bulk_out(void *data, uint_fast8_t max_len) return ret; } +// Storage for "bulk in" transmissions for a kind of manual "double buffering" +static struct { + uint32_t len; + uint32_t buf[USB_CDC_EP_BULK_IN_SIZE / sizeof(uint32_t)]; +} TX_BUF; + int_fast8_t usb_send_bulk_in(void *data, uint_fast8_t len) { @@ -219,10 +243,21 @@ usb_send_bulk_in(void *data, uint_fast8_t len) return len; } if (ctl & USB_OTG_DIEPCTL_EPENA) { - // Wait for space to transmit + if (!CONFIG_STM32_USB_DOUBLE_BUFFER_TX || TX_BUF.len || !len) { + // Wait for space to transmit + OTGD->DAINTMSK |= 1 << USB_CDC_EP_BULK_IN; + usb_irq_enable(); + return -1; + } + // Buffer next packet for transmission from irq handler + len = len > USB_CDC_EP_BULK_IN_SIZE ? USB_CDC_EP_BULK_IN_SIZE : len; + uint32_t blocks = DIV_ROUND_UP(len, sizeof(uint32_t)); + TX_BUF.buf[blocks-1] = 0; + memcpy(TX_BUF.buf, data, len); + TX_BUF.len = len; OTGD->DAINTMSK |= 1 << USB_CDC_EP_BULK_IN; usb_irq_enable(); - return -1; + return len; } int_fast8_t ret = fifo_write_packet(USB_CDC_EP_BULK_IN, data, len); usb_irq_enable(); @@ -373,6 +408,8 @@ usb_set_configure(void) | USB_OTG_GRSTCTL_TXFFLSH); while (OTG->GRSTCTL & USB_OTG_GRSTCTL_TXFFLSH) ; + if (CONFIG_STM32_USB_DOUBLE_BUFFER_TX) + TX_BUF.len = 0; usb_irq_enable(); } @@ -401,8 +438,15 @@ OTG_FS_IRQHandler(void) OTGD->DAINTMSK = msk & ~daint; if (pend & (1 << 0)) usb_notify_ep0(); - if (pend & (1 << USB_CDC_EP_BULK_IN)) + if (pend & (1 << USB_CDC_EP_BULK_IN)) { usb_notify_bulk_in(); + if (CONFIG_STM32_USB_DOUBLE_BUFFER_TX && TX_BUF.len) { + int ret = fifo_write_packet_fast(USB_CDC_EP_BULK_IN + , TX_BUF.buf, TX_BUF.len); + if (!ret) + TX_BUF.len = 0; + } + } } } diff --git a/src/trigger_analog.c b/src/trigger_analog.c new file mode 100644 index 000000000..ce9b998bf --- /dev/null +++ b/src/trigger_analog.c @@ -0,0 +1,254 @@ +// Support homing/probing "trigger" notification from analog sensors +// +// Copyright (C) 2025 Gareth Farrington +// Copyright (C) 2024-2026 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include // abs +#include "basecmd.h" // oid_alloc +#include "board/io.h" // writeb +#include "board/misc.h" // timer_read_time +#include "command.h" // DECL_COMMAND +#include "sched.h" // shutdown +#include "sos_filter.h" // sos_filter_apply +#include "trigger_analog.h" // trigger_analog_update +#include "trsync.h" // trsync_do_trigger + +// Main trigger_analog storage +struct trigger_analog { + // Raw value range check + int32_t raw_min, raw_max; + // Filtering + struct sos_filter *sf; + // Trigger value checking + int32_t trigger_value, trigger_peak; + uint8_t trigger_type; + // Trsync triggering + uint8_t flags, trigger_reason, error_reason; + struct trsync *ts; + uint32_t homing_clock; + // Sensor activity monitoring + uint8_t monitor_max, monitor_count; + struct timer time; + uint32_t monitor_ticks; +}; + +// Homing flags +enum { + TA_AWAIT_HOMING = 1<<1, TA_CAN_TRIGGER = 1<<2 +}; + +// Trigger types +enum { + TT_ABS_GE, TT_GT, TT_DIFF_PEAK_GT +}; +DECL_ENUMERATION("trigger_analog_type", "abs_ge", TT_ABS_GE); +DECL_ENUMERATION("trigger_analog_type", "gt", TT_GT); +DECL_ENUMERATION("trigger_analog_type", "diff_peak_gt", TT_DIFF_PEAK_GT); + +// Sample errors sent via trsync error code +enum { + TE_RAW_RANGE, TE_OVERFLOW, TE_MONITOR, TE_SENSOR_SPECIFIC +}; +DECL_ENUMERATION("trigger_analog_error:", "RAW_RANGE", TE_RAW_RANGE); +DECL_ENUMERATION("trigger_analog_error:", "OVERFLOW", TE_OVERFLOW); +DECL_ENUMERATION("trigger_analog_error:", "MONITOR", TE_MONITOR); +DECL_ENUMERATION("trigger_analog_error:", "SENSOR_SPECIFIC" + , TE_SENSOR_SPECIFIC); + +// Timer callback that monitors for sensor timeouts +static uint_fast8_t +monitor_event(struct timer *t) +{ + struct trigger_analog *ta = container_of(t, struct trigger_analog, time); + + if (!(ta->flags & TA_CAN_TRIGGER)) + return SF_DONE; + + if (ta->monitor_count > ta->monitor_max) { + trsync_do_trigger(ta->ts, ta->error_reason + TE_MONITOR); + return SF_DONE; + } + + // A sample was recently delivered, continue monitoring + ta->monitor_count++; + ta->time.waketime += ta->monitor_ticks; + return SF_RESCHEDULE; +} + +// Note recent activity +static void +monitor_note_activity(struct trigger_analog *ta) +{ + writeb(&ta->monitor_count, 0); +} + +// Check if a value should signal a "trigger" event +static int +check_trigger(struct trigger_analog *ta, uint32_t time, int32_t value) +{ + switch (ta->trigger_type) { + case TT_ABS_GE: + return abs(value) >= ta->trigger_value; + case TT_GT: + return value > ta->trigger_value; + case TT_DIFF_PEAK_GT: + if (value > ta->trigger_peak) { + ta->trigger_peak = value; + return 0; + } + uint32_t delta = ta->trigger_peak - value; + return delta > ta->trigger_value; + } + return 0; +} + +// Reset fields associated with trigger checking +static void +trigger_reset(struct trigger_analog *ta) +{ + ta->trigger_peak = INT32_MIN; +} + +// Stop homing due to an error +static void +cancel_homing(struct trigger_analog *ta, uint8_t error_code) +{ + if (!(ta->flags & TA_CAN_TRIGGER)) + return; + ta->flags = 0; + trsync_do_trigger(ta->ts, ta->error_reason + error_code); +} + +// Handle an error reported by the sensor +void +trigger_analog_note_error(struct trigger_analog *ta, uint8_t sensor_code) +{ + if (!ta) + return; + cancel_homing(ta, sensor_code + TE_SENSOR_SPECIFIC); +} + +// Used by Sensors to report new raw ADC sample +void +trigger_analog_update(struct trigger_analog *ta, int32_t sample) +{ + // Check homing is active + if (!ta) + return; + uint8_t flags = ta->flags; + if (!(flags & TA_CAN_TRIGGER)) + return; + + // Check if homing has started + uint32_t time = timer_read_time(); + if ((flags & TA_AWAIT_HOMING) && timer_is_before(time, ta->homing_clock)) + return; + flags &= ~TA_AWAIT_HOMING; + + // Reset the sensor timeout checking + monitor_note_activity(ta); + + // Check that raw value is in range + if (sample < ta->raw_min || sample > ta->raw_max) { + cancel_homing(ta, TE_RAW_RANGE); + return; + } + + // Perform filtering + int32_t filtered_value = sample; + int ret = sos_filter_apply(ta->sf, &filtered_value); + if (ret) { + cancel_homing(ta, TE_OVERFLOW); + return; + } + + // Check if this is a "trigger" + ret = check_trigger(ta, time, filtered_value); + if (ret) { + trsync_do_trigger(ta->ts, ta->trigger_reason); + flags = 0; + ta->homing_clock = time; + } + + ta->flags = flags; +} + +// Create a trigger_analog +void +command_config_trigger_analog(uint32_t *args) +{ + struct trigger_analog *ta = oid_alloc( + args[0], command_config_trigger_analog, sizeof(*ta)); + ta->sf = sos_filter_oid_lookup(args[1]); +} +DECL_COMMAND(command_config_trigger_analog + , "config_trigger_analog oid=%c sos_filter_oid=%c"); + +// Lookup a trigger_analog +struct trigger_analog * +trigger_analog_oid_lookup(uint8_t oid) +{ + return oid_lookup(oid, command_config_trigger_analog); +} + +// Set valid raw range +void +command_trigger_analog_set_raw_range(uint32_t *args) +{ + struct trigger_analog *ta = trigger_analog_oid_lookup(args[0]); + ta->raw_min = args[1]; + ta->raw_max = args[2]; +} +DECL_COMMAND(command_trigger_analog_set_raw_range, + "trigger_analog_set_raw_range oid=%c raw_min=%i raw_max=%i"); + +// Set the triggering type and value +void +command_trigger_analog_set_trigger(uint32_t *args) +{ + struct trigger_analog *ta = trigger_analog_oid_lookup(args[0]); + ta->trigger_type = args[1]; + ta->trigger_value = args[2]; +} +DECL_COMMAND(command_trigger_analog_set_trigger, "trigger_analog_set_trigger" + " oid=%c trigger_analog_type=%c trigger_value=%i"); + +// Home an axis +void +command_trigger_analog_home(uint32_t *args) +{ + struct trigger_analog *ta = trigger_analog_oid_lookup(args[0]); + sched_del_timer(&ta->time); + ta->monitor_ticks = args[5]; + if (!ta->monitor_ticks) { + ta->flags = 0; + ta->ts = NULL; + return; + } + ta->ts = trsync_oid_lookup(args[1]); + ta->trigger_reason = args[2]; + ta->error_reason = args[3]; + ta->time.waketime = ta->homing_clock = args[4]; + ta->monitor_max = args[6]; + ta->monitor_count = 0; + ta->time.func = monitor_event; + ta->flags = TA_AWAIT_HOMING | TA_CAN_TRIGGER; + trigger_reset(ta); + sched_add_timer(&ta->time); +} +DECL_COMMAND(command_trigger_analog_home, + "trigger_analog_home oid=%c trsync_oid=%c trigger_reason=%c" + " error_reason=%c clock=%u monitor_ticks=%u monitor_max=%u"); + +void +command_trigger_analog_query_state(uint32_t *args) +{ + uint8_t oid = args[0]; + struct trigger_analog *ta = trigger_analog_oid_lookup(args[0]); + sendf("trigger_analog_state oid=%c homing=%c homing_clock=%u" + , oid, !!(ta->flags & TA_CAN_TRIGGER), ta->homing_clock); +} +DECL_COMMAND(command_trigger_analog_query_state + , "trigger_analog_query_state oid=%c"); diff --git a/src/trigger_analog.h b/src/trigger_analog.h new file mode 100644 index 000000000..53b36ce63 --- /dev/null +++ b/src/trigger_analog.h @@ -0,0 +1,10 @@ +#ifndef __TRIGGER_ANALOG_H +#define __TRIGGER_ANALOG_H + +#include // uint8_t + +struct trigger_analog *trigger_analog_oid_lookup(uint8_t oid); +void trigger_analog_note_error(struct trigger_analog *ta, uint8_t sensor_code); +void trigger_analog_update(struct trigger_analog *ta, int32_t sample); + +#endif // trigger_analog.h diff --git a/test/configs/lgt8f328p.config b/test/configs/lgt8f328p.config new file mode 100644 index 000000000..dd7a21428 --- /dev/null +++ b/test/configs/lgt8f328p.config @@ -0,0 +1,15 @@ +# Base config file for lgt8f328p +CONFIG_MACH_AVR=y +CONFIG_MACH_lgt8f328p=y +CONFIG_CLOCK_FREQ=32000000 +CONFIG_WANT_ADC=n +CONFIG_WANT_SPI=n +CONFIG_WANT_I2C=n +CONFIG_WANT_HARD_PWM=n +CONFIG_WANT_BUTTONS=n +CONFIG_WANT_TMCUART=n +CONFIG_WANT_NEOPIXEL=n +CONFIG_WANT_PULSE_COUNTER=n +CONFIG_WANT_ST7920=n +CONFIG_WANT_HD44780=n +CONFIG_WANT_HX71X=n diff --git a/test/klippy/bed_mesh.cfg b/test/klippy/bed_mesh.cfg new file mode 100644 index 000000000..a3a56d92a --- /dev/null +++ b/test/klippy/bed_mesh.cfg @@ -0,0 +1,81 @@ +# Test config for bed_mesh +[stepper_x] +step_pin: PF0 +dir_pin: PF1 +enable_pin: !PD7 +microsteps: 16 +rotation_distance: 40 +endstop_pin: ^PE5 +position_endstop: 0 +position_max: 200 +homing_speed: 50 + +[stepper_y] +step_pin: PF6 +dir_pin: !PF7 +enable_pin: !PF2 +microsteps: 16 +rotation_distance: 40 +endstop_pin: ^PJ1 +position_endstop: 0 +position_max: 200 +homing_speed: 50 + +[stepper_z] +step_pin: PL3 +dir_pin: PL1 +enable_pin: !PK0 +microsteps: 16 +rotation_distance: 8 +endstop_pin: probe:z_virtual_endstop +position_max: 200 + +[extruder] +step_pin: PA4 +dir_pin: PA6 +enable_pin: !PA2 +microsteps: 16 +rotation_distance: 33.5 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PB4 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PK5 +control: pid +pid_Kp: 22.2 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 250 + +[heater_bed] +heater_pin: PH5 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PK6 +control: watermark +min_temp: 0 +max_temp: 130 + +[probe] +pin: PC7 +z_offset: 1.15 + +[bed_mesh] +mesh_min: 10,10 +mesh_max: 180,180 +probe_count: 7, 7 +algorithm: bicubic +faulty_region_1_min: 21.422, 87.126 +faulty_region_1_max: 42.922, 129.126 +faulty_region_2_min: 54.172, 97.376 +faulty_region_2_max: 100.172, 150.876 + +[mcu] +serial: /dev/ttyACM0 + +[printer] +kinematics: cartesian +max_velocity: 300 +max_accel: 3000 +max_z_velocity: 5 +max_z_accel: 100 diff --git a/test/klippy/bed_mesh.test b/test/klippy/bed_mesh.test new file mode 100644 index 000000000..bf104755d --- /dev/null +++ b/test/klippy/bed_mesh.test @@ -0,0 +1,13 @@ +# Test case for bed_mesh tests +CONFIG bed_mesh.cfg +DICTIONARY atmega2560.dict + +# Start by homing the printer. +G28 +G1 F6000 +G1 X60 Y60 Z10 + +# Run bed_mesh_calibrate +BED_MESH_CALIBRATE + +G1 Z10 diff --git a/test/klippy/corexyuv.cfg b/test/klippy/corexyuv.cfg index e9b0cd02a..e6fd37006 100644 --- a/test/klippy/corexyuv.cfg +++ b/test/klippy/corexyuv.cfg @@ -24,6 +24,7 @@ primary_carriage: carriage_z endstop_pin: ^PD2 [dual_carriage carriage_u] +axis: x primary_carriage: carriage_x safe_distance: 70 position_endstop: 300 @@ -32,6 +33,7 @@ homing_speed: 50 endstop_pin: ^PE4 [dual_carriage carriage_v] +axis: y primary_carriage: carriage_y safe_distance: 50 position_endstop: 200 diff --git a/test/klippy/eddy.cfg b/test/klippy/eddy.cfg new file mode 100644 index 000000000..6f26899ef --- /dev/null +++ b/test/klippy/eddy.cfg @@ -0,0 +1,85 @@ +# Test config for probe_eddy_current +[stepper_x] +step_pin: PF0 +dir_pin: PF1 +enable_pin: !PD7 +microsteps: 16 +rotation_distance: 40 +endstop_pin: ^PE5 +position_endstop: 0 +position_max: 200 +homing_speed: 50 + +[stepper_y] +step_pin: PF6 +dir_pin: !PF7 +enable_pin: !PF2 +microsteps: 16 +rotation_distance: 40 +endstop_pin: ^PJ1 +position_endstop: 0 +position_max: 200 +homing_speed: 50 + +[stepper_z] +step_pin: PL3 +dir_pin: PL1 +enable_pin: !PK0 +microsteps: 16 +rotation_distance: 8 +endstop_pin: probe:z_virtual_endstop +position_max: 200 + +[extruder] +step_pin: PA4 +dir_pin: PA6 +enable_pin: !PA2 +microsteps: 16 +rotation_distance: 33.5 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PB4 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PK5 +control: pid +pid_Kp: 22.2 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 250 + +[heater_bed] +heater_pin: PH5 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PK6 +control: watermark +min_temp: 0 +max_temp: 130 + +[probe_eddy_current eddy] +z_offset: 0.4 +x_offset: -5 +y_offset: -4 +sensor_type: ldc1612 +speed: 10.0 +intb_pin: PK7 +tap_threshold: 30 + +[bed_mesh] +mesh_min: 10,10 +mesh_max: 180,180 + +[mcu] +serial: /dev/ttyACM0 + +[printer] +kinematics: cartesian +max_velocity: 300 +max_accel: 3000 +max_z_velocity: 5 +max_z_accel: 100 + +# Dummy calibration data +[probe_eddy_current eddy] +calibrate = + 0.050000:3300000.000,1.000000:3200000.000,5.000000:3000000.000 diff --git a/test/klippy/eddy.test b/test/klippy/eddy.test new file mode 100644 index 000000000..beeacf949 --- /dev/null +++ b/test/klippy/eddy.test @@ -0,0 +1,34 @@ +# Test case for probe_eddy_current support +CONFIG eddy.cfg +DICTIONARY atmega2560.dict + +# Start by homing the printer. +G28 +G1 F6000 + +# Z / X / Y moves +G1 Z1 +G1 X1 +G1 Y1 + +# Run bed_mesh_calibrate +BED_MESH_CALIBRATE + +G1 Z2 +BED_MESH_CALIBRATE METHOD=scan + +G1 Z2 +BED_MESH_CALIBRATE METHOD=rapid_scan + +# Move again +G1 Z5 X0 Y0 + +# Do "tap" probe +PROBE METHOD=tap + +# Do regular probe +G1 Z5 +PROBE + +# Move again +G1 Z9 diff --git a/test/klippy/generic_cartesian_iqex.cfg b/test/klippy/generic_cartesian_iqex.cfg new file mode 100644 index 000000000..aa4c2f5cf --- /dev/null +++ b/test/klippy/generic_cartesian_iqex.cfg @@ -0,0 +1,386 @@ +# Test config for generic cartesian kinematics with quad independent extruders +[mcu] +serial: /dev/ttyACM0 + +[mcu extboard] +serial: /dev/ttyACM1 + +[carriage carriage_t0] +axis: x +position_endstop: 0 +position_max: 300 +homing_speed: 50 +endstop_pin: extboard:PG6 + +[dual_carriage carriage_t1] +primary_carriage: carriage_t0 +safe_distance: 70 +position_endstop: 300 +position_max: 300 +homing_speed: 50 +endstop_pin: extboard:PG9 + +[dual_carriage carriage_t2] +axis: x +position_endstop: 0 +position_max: 300 +homing_speed: 50 +endstop_pin: extboard:PG10 + +[dual_carriage carriage_t3] +primary_carriage: carriage_t2 +safe_distance: 70 +position_endstop: 300 +position_max: 300 +homing_speed: 50 +endstop_pin: extboard:PG11 + +[carriage carriage_gantry0_left] +axis: y +position_endstop: 0 +position_max: 200 +homing_speed: 50 +endstop_pin: PG6 + +[extra_carriage carriage_gantry0_right] +primary_carriage: carriage_gantry0_left +endstop_pin: PG9 + +[dual_carriage carriage_gantry1_left] +primary_carriage: carriage_gantry0_left +safe_distance: 50 +position_endstop: 200 +position_max: 200 +homing_speed: 50 +endstop_pin: PG10 + +[extra_carriage carriage_gantry1_right] +primary_carriage: carriage_gantry1_left +endstop_pin: PG11 + +[carriage carriage_z0] +axis: z +position_endstop: 0.5 +position_max: 100 +endstop_pin: PG12 + +[extra_carriage carriage_z1] +primary_carriage: carriage_z0 +endstop_pin: PG13 + +[extra_carriage carriage_z2] +primary_carriage: carriage_z0 +endstop_pin: PG14 + +[stepper stepper_t0_x] +carriages: carriage_t0 +step_pin: extboard:PF13 +dir_pin: extboard:PF12 +enable_pin: !extboard:PF14 +microsteps: 16 +rotation_distance: 40 + +[stepper stepper_t1_x] +carriages: carriage_t1 +step_pin: extboard:PG0 +dir_pin: extboard:PG1 +enable_pin: !extboard:PF15 +microsteps: 16 +rotation_distance: 40 + +[stepper stepper_t2_x] +carriages: carriage_t2 +step_pin: extboard:PF11 +dir_pin: extboard:PG3 +enable_pin: !extboard:PG5 +microsteps: 16 +rotation_distance: 40 + +[stepper stepper_t3_x] +carriages: carriage_t3 +step_pin: extboard:PG4 +dir_pin: extboard:PC1 +enable_pin: !extboard:PA2 +microsteps: 16 +rotation_distance: 40 + +[stepper gantry0_left] +carriages: carriage_gantry0_left +step_pin: PF13 +dir_pin: PF12 +enable_pin: !PF14 +microsteps: 16 +rotation_distance: 40 + +[stepper gantry0_right] +carriages: carriage_gantry0_right +step_pin: PG0 +dir_pin: PG1 +enable_pin: !PF15 +microsteps: 16 +rotation_distance: 40 + +[stepper gantry1_left] +carriages: carriage_gantry1_left +step_pin: PF11 +dir_pin: PG3 +enable_pin: !PG5 +microsteps: 16 +rotation_distance: 40 + +[stepper gantry1_right] +carriages: carriage_gantry1_right +step_pin: PG4 +dir_pin: PC1 +enable_pin: !PA2 +microsteps: 16 +rotation_distance: 40 + +[stepper z0] +carriages: carriage_z0 +step_pin: PF9 +dir_pin: PF10 +enable_pin: !PG2 +microsteps: 16 +rotation_distance: 8 + +[stepper z1] +carriages: carriage_z1 +step_pin: PC13 +dir_pin: PF0 +enable_pin: !PF1 +microsteps: 16 +rotation_distance: 8 + +[stepper z2] +carriages: carriage_z2 +step_pin: PE2 +dir_pin: PE3 +enable_pin: !PD4 +microsteps: 16 +rotation_distance: 8 + +[extruder] +step_pin: extboard:PF9 +dir_pin: extboard:PF10 +enable_pin: !extboard:PG2 +heater_pin: extboard:PA0 # HE0 +sensor_pin: extboard:PF4 # T0 +microsteps: 16 +rotation_distance: 33.500 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +sensor_type: EPCOS 100K B57560G104F +control: pid +pid_Kp: 22.2 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 250 + +[extruder1] +step_pin: extboard:PC13 +dir_pin: extboard:PF0 +enable_pin: !extboard:PF1 +heater_pin: extboard:PA3 # HE1 +sensor_pin: extboard:PF5 # T1 +microsteps: 16 +rotation_distance: 33.5 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +sensor_type: EPCOS 100K B57560G104F +control: pid +pid_Kp: 22.2 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 250 + +[extruder2] +step_pin: extboard:PE2 +dir_pin: extboard:PE3 +enable_pin: !extboard:PD4 +heater_pin: extboard:PB0 # HE2 +sensor_pin: extboard:PF6 # T2 +microsteps: 16 +rotation_distance: 33.5 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +sensor_type: EPCOS 100K B57560G104F +control: pid +pid_Kp: 22.2 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 250 + +[extruder3] +step_pin: extboard:PE6 +dir_pin: extboard:PA14 +enable_pin: !extboard:PE0 +heater_pin: extboard:PB11 # HE3 +sensor_pin: extboard:PF7 # T3 +microsteps: 16 +rotation_distance: 33.5 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +sensor_type: EPCOS 100K B57560G104F +control: pid +pid_Kp: 22.2 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 250 + +[gcode_macro PARK_EXTRUDERS] +gcode: + G90 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1_left + G1 Y{printer.configfile.settings["dual_carriage carriage_gantry1_left"].position_max} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left + G1 Y{printer.configfile.settings["carriage carriage_gantry0_left"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t3 + G1 X{printer.configfile.settings["dual_carriage carriage_t3"].position_max} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 + G1 X{printer.configfile.settings["dual_carriage carriage_t1"].position_max} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 + G1 X{printer.configfile.settings["dual_carriage carriage_t2"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 + G1 X{printer.configfile.settings["carriage carriage_t0"].position_min} F12000 + +[gcode_macro T0] +gcode: + PARK_EXTRUDERS + ACTIVATE_EXTRUDER EXTRUDER=extruder + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 + +[gcode_macro T1] +gcode: + PARK_EXTRUDERS + SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder1 + ACTIVATE_EXTRUDER EXTRUDER=extruder1 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 + +[gcode_macro T2] +gcode: + PARK_EXTRUDERS + SYNC_EXTRUDER_MOTION EXTRUDER=extruder2 MOTION_QUEUE=extruder2 + ACTIVATE_EXTRUDER EXTRUDER=extruder2 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1_left + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 + +[gcode_macro T3] +gcode: + PARK_EXTRUDERS + SYNC_EXTRUDER_MOTION EXTRUDER=extruder3 MOTION_QUEUE=extruder3 + ACTIVATE_EXTRUDER EXTRUDER=extruder3 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1_left + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 + +[gcode_macro SET_COPY_MODE] +gcode: + G90 + {% set y_center = 0.5 * (printer.configfile.settings["dual_carriage carriage_gantry1_left"].position_max + printer.configfile.settings["carriage carriage_gantry0_left"].position_min) %} + {% set x_max = [printer.configfile.settings["dual_carriage carriage_t3"].position_max, printer.configfile.settings["dual_carriage carriage_t1"].position_max]|min %} + {% set x_min = [printer.configfile.settings["dual_carriage carriage_t2"].position_min, printer.configfile.settings["carriage carriage_t0"].position_min]|max %} + {% set x_center = 0.5 * (x_max + x_min) %} + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left + G1 Y{printer.configfile.settings["carriage carriage_gantry0_left"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1_left + G1 Y{y_center} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 + G1 X{printer.configfile.settings["dual_carriage carriage_t2"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 + G1 X{printer.configfile.settings["carriage carriage_t0"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t3 + G1 X{x_center} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 + G1 X{x_center} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 MODE=PRIMARY + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 MODE=COPY + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 MODE=COPY + SET_DUAL_CARRIAGE CARRIAGE=carriage_t3 MODE=COPY + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left MODE=PRIMARY + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1_left MODE=COPY + ACTIVATE_EXTRUDER EXTRUDER=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder2 MOTION_QUEUE=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder3 MOTION_QUEUE=extruder + +[gcode_macro SET_MIRROR_MODE1] +gcode: + G90 + {% set y_center = 0.5 * (printer.configfile.settings["dual_carriage carriage_gantry1_left"].position_max + printer.configfile.settings["carriage carriage_gantry0_left"].position_min) %} + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left + G1 Y{printer.configfile.settings["carriage carriage_gantry0_left"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1_left + G1 Y{y_center} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 + G1 X{printer.configfile.settings["dual_carriage carriage_t2"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 + G1 X{printer.configfile.settings["carriage carriage_t0"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t3 + G1 X{printer.configfile.settings["dual_carriage carriage_t3"].position_max} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 + G1 X{printer.configfile.settings["dual_carriage carriage_t1"].position_max} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 MODE=PRIMARY + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 MODE=MIRROR + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 MODE=COPY + SET_DUAL_CARRIAGE CARRIAGE=carriage_t3 MODE=MIRROR + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left MODE=PRIMARY + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1_left MODE=COPY + ACTIVATE_EXTRUDER EXTRUDER=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder2 MOTION_QUEUE=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder3 MOTION_QUEUE=extruder + +[gcode_macro SET_MIRROR_MODE2] +gcode: + G90 + {% set x_max = [printer.configfile.settings["dual_carriage carriage_t3"].position_max, printer.configfile.settings["dual_carriage carriage_t1"].position_max]|min %} + {% set x_min = [printer.configfile.settings["dual_carriage carriage_t2"].position_min, printer.configfile.settings["carriage carriage_t0"].position_min]|max %} + {% set x_center = 0.5 * (x_max + x_min) %} + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left + G1 Y{printer.configfile.settings["carriage carriage_gantry0_left"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1_left + G1 Y{printer.configfile.settings["dual_carriage carriage_gantry1_left"].position_max} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 + G1 X{printer.configfile.settings["dual_carriage carriage_t2"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 + G1 X{printer.configfile.settings["carriage carriage_t0"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t3 + G1 X{x_center} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 + G1 X{x_center} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 MODE=PRIMARY + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 MODE=COPY + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 MODE=COPY + SET_DUAL_CARRIAGE CARRIAGE=carriage_t3 MODE=COPY + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left MODE=PRIMARY + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1_left MODE=MIRROR + ACTIVATE_EXTRUDER EXTRUDER=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder2 MOTION_QUEUE=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder3 MOTION_QUEUE=extruder + +[heater_bed] +heater_pin: PA1 +sensor_pin: PF3 # TB +sensor_type: ATC Semitec 104GT-2 +control: watermark +min_temp: 0 +max_temp: 130 + +[fan] +pin: PA8 + +[printer] +kinematics: generic_cartesian +max_velocity: 300 +max_accel: 3000 +max_z_velocity: 5 +max_z_accel: 100 + +[input_shaper] diff --git a/test/klippy/generic_cartesian_iqex.test b/test/klippy/generic_cartesian_iqex.test new file mode 100644 index 000000000..b317dbf21 --- /dev/null +++ b/test/klippy/generic_cartesian_iqex.test @@ -0,0 +1,71 @@ +# Test cases on printers with quad independent extruders +DICTIONARY stm32h723.dict extboard=stm32h723.dict +CONFIG generic_cartesian_iqex.cfg + +# First home the printer +G90 +M83 +G28 + +# Perform a dummy move +G1 X10 Y20 Z10 F6000 +G1 X11 E0.1 F3000 + +# Test other tools +T1 +G1 X120 Y50 F6000 +G1 X119 E0.1 F3000 + +T2 +G1 X200 Y70 F6000 +G1 X199 E0.1 F3000 + +T3 +G1 X70 Y50 F6000 +G1 X71 E0.1 F3000 + +# Go back to main tool +T0 +G1 X20 Y100 F6000 + +# Save dual carriage state +SAVE_DUAL_CARRIAGE_STATE + +G1 Y100 F6000 + +T2 +# Activate the dual carriage on Y axis +SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1_left +G1 X10 Y150 F6000 + +# Restore dual carriage state +RESTORE_DUAL_CARRIAGE_STATE + +QUERY_ENDSTOPS + +# Configure input shaper +SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1_left +SET_DUAL_CARRIAGE CARRIAGE=carriage_t3 +SET_INPUT_SHAPER SHAPER_TYPE_X=ei SHAPER_FREQ_X=50 SHAPER_TYPE_Y=2hump_ei SHAPER_FREQ_Y=80 +SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 +SET_INPUT_SHAPER SHAPER_TYPE_X=ei SHAPER_FREQ_X=45 SHAPER_TYPE_Y=2hump_ei SHAPER_FREQ_Y=80 +SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left +SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 +SET_INPUT_SHAPER SHAPER_TYPE_X=mzv SHAPER_FREQ_X=50 SHAPER_TYPE_Y=2hump_ei SHAPER_FREQ_Y=70 +SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 +SET_INPUT_SHAPER SHAPER_TYPE_X=zvd SHAPER_FREQ_X=55 SHAPER_TYPE_Y=2hump_ei SHAPER_FREQ_Y=70 + +T0 +G1 X100 Y150 F6000 + +SET_COPY_MODE +G1 X10 Y10 F6000 +G1 X11 E0.1 F3000 + +SET_MIRROR_MODE1 +G1 X10 Y10 F6000 +G1 X11 E0.1 F3000 + +SET_MIRROR_MODE2 +G1 X10 Y10 F6000 +G1 X11 E0.1 F3000 diff --git a/test/klippy/generic_cartesian_itex.cfg b/test/klippy/generic_cartesian_itex.cfg new file mode 100644 index 000000000..dc0ae115a --- /dev/null +++ b/test/klippy/generic_cartesian_itex.cfg @@ -0,0 +1,320 @@ +# Test config for generic cartesian kinematics with triple independent extruders +[mcu] +serial: /dev/ttyACM0 + +[mcu extboard] +serial: /dev/ttyACM1 + +[carriage carriage_t0] +axis: x +position_endstop: 0 +position_max: 300 +homing_speed: 50 +endstop_pin: extboard:PG6 + +[dual_carriage carriage_t1] +primary_carriage: carriage_t0 +safe_distance: 70 +position_endstop: 300 +position_max: 300 +homing_speed: 50 +endstop_pin: extboard:PG9 + +[dual_carriage carriage_t2] +axis: x +position_endstop: 0 +position_max: 300 +homing_speed: 50 +endstop_pin: extboard:PG10 + +[carriage carriage_gantry0_left] +axis: y +position_endstop: 0 +position_max: 200 +homing_speed: 50 +endstop_pin: PG6 + +[extra_carriage carriage_gantry0_right] +primary_carriage: carriage_gantry0_left +endstop_pin: PG9 + +[dual_carriage carriage_gantry1] +primary_carriage: carriage_gantry0_left +safe_distance: 50 +position_endstop: 200 +position_max: 200 +homing_speed: 50 +endstop_pin: PG10 + +[carriage carriage_z0] +axis: z +position_endstop: 0.5 +position_max: 100 +endstop_pin: PG12 + +[extra_carriage carriage_z1] +primary_carriage: carriage_z0 +endstop_pin: PG13 + +[extra_carriage carriage_z2] +primary_carriage: carriage_z0 +endstop_pin: PG14 + +[stepper stepper_t0_x] +carriages: carriage_t0 +step_pin: extboard:PF13 +dir_pin: extboard:PF12 +enable_pin: !extboard:PF14 +microsteps: 16 +rotation_distance: 40 + +[stepper stepper_t1_x] +carriages: carriage_t1 +step_pin: extboard:PG0 +dir_pin: extboard:PG1 +enable_pin: !extboard:PF15 +microsteps: 16 +rotation_distance: 40 + +[stepper gantry0_left] +carriages: carriage_gantry0_left +step_pin: PF13 +dir_pin: PF12 +enable_pin: !PF14 +microsteps: 16 +rotation_distance: 40 + +[stepper gantry0_right] +carriages: carriage_gantry0_right +step_pin: PG0 +dir_pin: PG1 +enable_pin: !PF15 +microsteps: 16 +rotation_distance: 40 + +[stepper gantry1_a] +carriages: carriage_t2-carriage_gantry1 +step_pin: PF11 +dir_pin: PG3 +enable_pin: !PG5 +microsteps: 16 +rotation_distance: 40 + +[stepper gantry1_b] +carriages: carriage_t2+carriage_gantry1 +step_pin: PG4 +dir_pin: PC1 +enable_pin: !PA2 +microsteps: 16 +rotation_distance: 40 + +[stepper z0] +carriages: carriage_z0 +step_pin: PF9 +dir_pin: PF10 +enable_pin: !PG2 +microsteps: 16 +rotation_distance: 8 + +[stepper z1] +carriages: carriage_z1 +step_pin: PC13 +dir_pin: PF0 +enable_pin: !PF1 +microsteps: 16 +rotation_distance: 8 + +[stepper z2] +carriages: carriage_z2 +step_pin: PE2 +dir_pin: PE3 +enable_pin: !PD4 +microsteps: 16 +rotation_distance: 8 + +[extruder] +step_pin: extboard:PF9 +dir_pin: extboard:PF10 +enable_pin: !extboard:PG2 +heater_pin: extboard:PA0 # HE0 +sensor_pin: extboard:PF4 # T0 +microsteps: 16 +rotation_distance: 33.500 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +sensor_type: EPCOS 100K B57560G104F +control: pid +pid_Kp: 22.2 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 250 + +[extruder1] +step_pin: extboard:PC13 +dir_pin: extboard:PF0 +enable_pin: !extboard:PF1 +heater_pin: extboard:PA3 # HE1 +sensor_pin: extboard:PF5 # T1 +microsteps: 16 +rotation_distance: 33.5 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +sensor_type: EPCOS 100K B57560G104F +control: pid +pid_Kp: 22.2 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 250 + +[extruder2] +step_pin: extboard:PE2 +dir_pin: extboard:PE3 +enable_pin: !extboard:PD4 +heater_pin: extboard:PB0 # HE2 +sensor_pin: extboard:PF6 # T2 +microsteps: 16 +rotation_distance: 33.5 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +sensor_type: EPCOS 100K B57560G104F +control: pid +pid_Kp: 22.2 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 250 + +[gcode_macro PARK_EXTRUDERS] +gcode: + G90 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1 + G1 Y{printer.configfile.settings["dual_carriage carriage_gantry1"].position_max} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left + G1 Y{printer.configfile.settings["carriage carriage_gantry0_left"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 + G1 X{printer.configfile.settings["dual_carriage carriage_t1"].position_max} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 + G1 X{printer.configfile.settings["dual_carriage carriage_t2"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 + G1 X{printer.configfile.settings["carriage carriage_t0"].position_min} F12000 + +[gcode_macro T0] +gcode: + PARK_EXTRUDERS + ACTIVATE_EXTRUDER EXTRUDER=extruder + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 + +[gcode_macro T1] +gcode: + PARK_EXTRUDERS + SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder1 + ACTIVATE_EXTRUDER EXTRUDER=extruder1 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 + +[gcode_macro T2] +gcode: + PARK_EXTRUDERS + SYNC_EXTRUDER_MOTION EXTRUDER=extruder2 MOTION_QUEUE=extruder2 + ACTIVATE_EXTRUDER EXTRUDER=extruder2 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 + +[gcode_macro SET_COPY_MODE] +gcode: + G90 + {% set y_center = 0.5 * (printer.configfile.settings["dual_carriage carriage_gantry1"].position_max + printer.configfile.settings["carriage carriage_gantry0_left"].position_min) %} + {% set x_max = printer.configfile.settings["dual_carriage carriage_t1"].position_max %} + {% set x_min = [printer.configfile.settings["dual_carriage carriage_t2"].position_min, printer.configfile.settings["carriage carriage_t0"].position_min]|max %} + {% set x_center = 0.5 * (x_max + x_min) %} + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left + G1 Y{printer.configfile.settings["carriage carriage_gantry0_left"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1 + G1 Y{y_center} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 + G1 X{printer.configfile.settings["dual_carriage carriage_t2"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 + G1 X{printer.configfile.settings["carriage carriage_t0"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 + G1 X{x_center} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 MODE=PRIMARY + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 MODE=COPY + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 MODE=COPY + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left MODE=PRIMARY + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1 MODE=COPY + ACTIVATE_EXTRUDER EXTRUDER=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder2 MOTION_QUEUE=extruder + +[gcode_macro SET_MIRROR_MODE1] +gcode: + G90 + {% set y_center = 0.5 * (printer.configfile.settings["dual_carriage carriage_gantry1"].position_max + printer.configfile.settings["carriage carriage_gantry0_left"].position_min) %} + {% set x_max = printer.configfile.settings["dual_carriage carriage_t1"].position_max %} + {% set x_min = [printer.configfile.settings["dual_carriage carriage_t2"].position_min, printer.configfile.settings["carriage carriage_t0"].position_min]|max %} + {% set x_center = 0.5 * (x_max + x_min) %} + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left + G1 Y{printer.configfile.settings["carriage carriage_gantry0_left"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1 + G1 Y{y_center} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 + G1 X{printer.configfile.settings["dual_carriage carriage_t2"].position_max} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 + G1 X{printer.configfile.settings["carriage carriage_t0"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 + G1 X{x_center} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 MODE=PRIMARY + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 MODE=COPY + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 MODE=MIRROR + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left MODE=PRIMARY + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1 MODE=COPY + ACTIVATE_EXTRUDER EXTRUDER=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder2 MOTION_QUEUE=extruder + +[gcode_macro SET_MIRROR_MODE2] +gcode: + {% set y_center = 0.5 * (printer.configfile.settings["dual_carriage carriage_gantry1"].position_max + printer.configfile.settings["carriage carriage_gantry0_left"].position_min) %} + G90 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left + G1 Y{printer.configfile.settings["carriage carriage_gantry0_left"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1 + G1 Y{y_center} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 + G1 X{printer.configfile.settings["dual_carriage carriage_t2"].position_max} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 + G1 X{printer.configfile.settings["carriage carriage_t0"].position_min} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 + G1 X{printer.configfile.settings["dual_carriage carriage_t1"].position_max} F12000 + SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 MODE=PRIMARY + SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 MODE=MIRROR + SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 MODE=MIRROR + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left MODE=PRIMARY + SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1 MODE=COPY + ACTIVATE_EXTRUDER EXTRUDER=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder2 MOTION_QUEUE=extruder + +[heater_bed] +heater_pin: PA1 +sensor_pin: PF3 # TB +sensor_type: ATC Semitec 104GT-2 +control: watermark +min_temp: 0 +max_temp: 130 + +[fan] +pin: PA8 + +[printer] +kinematics: generic_cartesian +max_velocity: 300 +max_accel: 3000 +max_z_velocity: 5 +max_z_accel: 100 + +[input_shaper] diff --git a/test/klippy/generic_cartesian_itex.test b/test/klippy/generic_cartesian_itex.test new file mode 100644 index 000000000..bcf80771d --- /dev/null +++ b/test/klippy/generic_cartesian_itex.test @@ -0,0 +1,65 @@ +# Test cases on printers with triple independent extruders +DICTIONARY stm32h723.dict extboard=stm32h723.dict +CONFIG generic_cartesian_itex.cfg + +# First home the printer +G90 +M83 +G28 + +# Perform a dummy move +G1 X10 Y20 Z10 F6000 +G1 X11 E0.1 F3000 + +# Test other tools +T1 +G1 X120 Y50 F6000 +G1 X119 E0.1 F3000 + +T2 +G1 X200 Y70 F6000 +G1 X199 E0.1 F3000 + +# Go back to main tool +T0 +G1 X20 Y100 F6000 + +# Save dual carriage state +SAVE_DUAL_CARRIAGE_STATE + +G1 Y100 F6000 + +T2 +# Activate the dual carriage on Y axis +SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1 +G1 X10 Y150 F6000 + +# Restore dual carriage state +RESTORE_DUAL_CARRIAGE_STATE + +QUERY_ENDSTOPS + +# Configure input shaper +SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1 +SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 +SET_INPUT_SHAPER SHAPER_TYPE_X=ei SHAPER_FREQ_X=45 SHAPER_TYPE_Y=2hump_ei SHAPER_FREQ_Y=80 +SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0_left +SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 +SET_INPUT_SHAPER SHAPER_TYPE_X=mzv SHAPER_FREQ_X=50 SHAPER_TYPE_Y=2hump_ei SHAPER_FREQ_Y=70 +SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 +SET_INPUT_SHAPER SHAPER_TYPE_X=zvd SHAPER_FREQ_X=55 SHAPER_TYPE_Y=2hump_ei SHAPER_FREQ_Y=70 + +T0 +G1 X100 Y150 F6000 + +SET_COPY_MODE +G1 X10 Y10 F6000 +G1 X11 E0.1 F3000 + +SET_MIRROR_MODE1 +G1 X10 Y10 F6000 +G1 X11 E0.1 F3000 + +SET_MIRROR_MODE2 +G1 X10 Y10 F6000 +G1 X11 E0.1 F3000 diff --git a/test/klippy/load_cell.cfg b/test/klippy/load_cell.cfg index fa599d10e..48ceca3ca 100644 --- a/test/klippy/load_cell.cfg +++ b/test/klippy/load_cell.cfg @@ -1,11 +1,75 @@ -# Test config for load_cell +# Test config for load cells +[stepper_x] +step_pin: PF0 +dir_pin: PF1 +enable_pin: !PD7 +microsteps: 16 +rotation_distance: 40 +endstop_pin: ^PE5 +position_endstop: 0 +position_max: 200 +homing_speed: 50 + +[stepper_y] +step_pin: PF6 +dir_pin: !PF7 +enable_pin: !PF2 +microsteps: 16 +rotation_distance: 40 +endstop_pin: ^PJ1 +position_endstop: 0 +position_max: 200 +homing_speed: 50 + +[stepper_z] +step_pin: PL3 +dir_pin: PL1 +enable_pin: !PK0 +microsteps: 16 +rotation_distance: 8 +endstop_pin: PK1 +position_endstop: 0.0 +position_max: 200 + +[extruder] +step_pin: PA4 +dir_pin: PA6 +enable_pin: !PA2 +microsteps: 16 +rotation_distance: 33.5 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PB4 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PK5 +control: pid +pid_Kp: 22.2 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 250 + +[heater_bed] +heater_pin: PH5 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PK6 +control: watermark +min_temp: 0 +max_temp: 130 + +[bed_mesh] +mesh_min: 10,10 +mesh_max: 180,180 + [mcu] serial: /dev/ttyACM0 [printer] -kinematics: none +kinematics: cartesian max_velocity: 300 max_accel: 3000 +max_z_velocity: 5 +max_z_accel: 100 [load_cell my_ads1220] sensor_type: ads1220 @@ -14,10 +78,22 @@ data_ready_pin: PA1 [load_cell my_hx711] sensor_type: hx711 -sclk_pin: PA2 -dout_pin: PA3 +sclk_pin: PA3 +dout_pin: PA5 [load_cell my_hx717] sensor_type: hx717 -sclk_pin: PA4 -dout_pin: PA5 +sclk_pin: PA7 +dout_pin: PJ0 + +[load_cell_probe] +z_offset: 0 +sensor_type: ads1220 +speed: 10.0 +cs_pin: PJ2 +data_ready_pin: PJ3 +counts_per_gram: 100 +reference_tare_counts: 1000 +drift_filter_cutoff_frequency: 0.8 +buzz_filter_cutoff_frequency: 100.0 +notch_filter_frequencies: 50, 60 diff --git a/test/klippy/load_cell.test b/test/klippy/load_cell.test index 880f840aa..c22de2be7 100644 --- a/test/klippy/load_cell.test +++ b/test/klippy/load_cell.test @@ -2,4 +2,23 @@ DICTIONARY atmega2560.dict CONFIG load_cell.cfg -G4 P1000 +# Start by homing the printer. +G28 +G1 F6000 + +# Z / X / Y moves +G1 Z1 +G1 X1 +G1 Y1 + +# Run bed_mesh_calibrate +BED_MESH_CALIBRATE + +# Move again +G1 Z5 X0 Y0 + +# Do regular probe +PROBE + +# Move again +G1 Z9