Merge branch 'Klipper3d:master' into gc9a01

This commit is contained in:
Eugene Krashtan 2026-02-07 16:42:09 +02:00 committed by GitHub
commit 906453cd5a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
130 changed files with 4484 additions and 2115 deletions

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -8,9 +8,29 @@ All dates in this document are approximate.
## Changes
20251122: An option `axis` has been added to `[carriage <name>]` 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 <name>]`
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}`,

View file

@ -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

View file

@ -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=<middle> Y=<middle> 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.

View file

@ -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=<mm/s>] [LIFT_SPEED=<mm/s>] [SAMPLES=<count>]
[SAMPLE_RETRACT_DIST=<mm>] [SAMPLES_TOLERANCE=<mm>]
`PROBE [METHOD=<value>] [PROBE_SPEED=<mm/s>] [LIFT_SPEED=<mm/s>]
[SAMPLES=<count>] [SAMPLE_RETRACT_DIST=<mm>] [SAMPLES_TOLERANCE=<mm>]
[SAMPLES_TOLERANCE_RETRIES=<count>] [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=<mm/s>] [SAMPLES=<count>]
`PROBE_ACCURACY [METHOD=<value>] [PROBE_SPEED=<mm/s>] [SAMPLES=<count>]
[SAMPLE_RETRACT_DIST=<mm>]`: 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=<speed>] [<probe_parameter>=<value>]`: Run a
@ -1237,13 +1240,14 @@ The following commands are available when the
is enabled.
#### QUAD_GANTRY_LEVEL
`QUAD_GANTRY_LEVEL [RETRIES=<value>] [RETRY_TOLERANCE=<value>]
`QUAD_GANTRY_LEVEL [METHOD=<value>] [RETRIES=<value>] [RETRY_TOLERANCE=<value>]
[HORIZONTAL_MOVE_Z=<value>] [<probe_parameter>=<value>]`: 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=<value>] [RETRY_TOLERANCE=<value>]
`Z_TILT_ADJUST [METHOD=<value>] [RETRIES=<value>] [RETRY_TOLERANCE=<value>]
[HORIZONTAL_MOVE_Z=<value>] [<probe_parameter>=<value>]`: 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.

View file

@ -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}

View file

@ -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

View file

@ -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})

View file

@ -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)

View file

@ -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)" % (

View file

@ -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

View file

@ -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):

View file

@ -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)

View file

@ -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):

View file

@ -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:

View file

@ -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)

View file

@ -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):

View file

@ -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):

View file

@ -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.)

View file

@ -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)

View file

@ -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))

View file

@ -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()

View file

@ -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)

View file

@ -1,9 +1,18 @@
# Helper script for manual z height probing
#
# Copyright (C) 2019 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2019-2025 Kevin O'Connor <kevin@koconnor.net>
#
# 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)

View file

@ -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)

View file

@ -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

View file

@ -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):

View file

@ -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)

View file

@ -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"

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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):

View file

@ -1,234 +0,0 @@
# Second Order Sections Filter
#
# Copyright (C) 2025 Gareth Farrington <gareth@waves.ky>
#
# 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)

View file

@ -0,0 +1,41 @@
# Define GPIO as clock output
#
# Copyright (C) 2025 Timofey Titovets <nefelim4ag@gmail.com>
#
# 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)

View file

@ -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

View file

@ -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:

View file

@ -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"

View file

@ -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()

View file

@ -0,0 +1,380 @@
# Wrapper around mcu trigger_analog objects
#
# Copyright (C) 2025 Gareth Farrington <gareth@waves.ky>
# Copyright (C) 2026 Kevin O'Connor <kevin@koconnor.net>
#
# 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

View file

@ -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)

View file

@ -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

View file

@ -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):

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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)

View file

@ -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, {})

View file

@ -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.

View file

@ -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

View file

@ -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
#endif

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -11,7 +11,7 @@
#include <stdint.h>
#include <stddef.h>
#ifdef __unix__
#if defined __unix__ && defined __GLIBC__
#include <sys/cdefs.h>
@ -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

View file

@ -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",

View file

@ -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 $<TARGET_FILE:${NAME}> ${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_OBJECTS:${NAME}_library>")
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()

View file

@ -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

View file

@ -7,6 +7,7 @@ MEMORY {
SECTIONS {
. = ORIGIN(SRAM);
.text : {
_start = .; /* make LLVM happy */
*(.entry)
*(.text)
} >SRAM

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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,

View file

@ -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

View file

@ -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 */

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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"

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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,

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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:

View file

@ -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

View file

@ -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:

View file

@ -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'

View file

@ -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

View file

@ -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

View file

@ -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)),
},
} },
};

View file

@ -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

View file

@ -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

View file

@ -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<<ADPS0)|(1<<ADPS1)|(1<<ADPS2)|(1<<ADEN)|(1<<ADIF) };
#if CONFIG_MACH_lgt8f328p
DECL_CONSTANT("ADC_MAX", 4095);
#else
DECL_CONSTANT("ADC_MAX", 1023);
#endif
struct gpio_adc
gpio_adc_setup(uint8_t pin)
@ -59,10 +67,27 @@ gpio_adc_setup(uint8_t pin)
ADCSRA = ADC_ENABLE;
// Disable digital input for this pin
#ifdef DIDR2
#if defined(DIDR2)
if (chan >= 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;

View file

@ -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,

View file

@ -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<<COM0A1, GP_8BIT },
{ GPIO('D', 5), &OCR0B, &TCCR0A, &TCCR0B, 1<<COM0B1, GP_8BIT },
{ GPIO('B', 1), &OCR1A, &TCCR1A, &TCCR1B, 1<<COM1A1, 0 },

View file

@ -15,7 +15,8 @@
DECL_ENUMERATION("i2c_bus", "twi", 0);
#if CONFIG_MACH_atmega168 || CONFIG_MACH_atmega328 || CONFIG_MACH_atmega328p
#if CONFIG_MACH_atmega168 || CONFIG_MACH_atmega328 \
|| CONFIG_MACH_atmega328p || CONFIG_MACH_lgt8f328p
static const uint8_t SCL = GPIO('C', 5), SDA = GPIO('C', 4);
DECL_CONSTANT_STR("BUS_PINS_twi", "PC5,PC4");
#elif CONFIG_MACH_atmega644p || CONFIG_MACH_atmega1284p

View file

@ -13,7 +13,8 @@
DECL_ENUMERATION("spi_bus", "spi", 0);
#if CONFIG_MACH_atmega168 || CONFIG_MACH_atmega328 || CONFIG_MACH_atmega328p
#if CONFIG_MACH_atmega168 || CONFIG_MACH_atmega328 \
|| CONFIG_MACH_atmega328p || CONFIG_MACH_lgt8f328p
static const uint8_t MISO = GPIO('B', 4), MOSI = GPIO('B', 3);
static const uint8_t SCK = GPIO('B', 5), SS = GPIO('B', 2);
DECL_CONSTANT_STR("BUS_PINS_spi", "PB4,PB3,PB5");

View file

@ -154,7 +154,6 @@ static struct timer wrap_timer = {
.waketime = 0x8000,
};
#define TIMER_IDLE_REPEAT_TICKS 8000
#define TIMER_REPEAT_TICKS 3000
#define TIMER_MIN_ENTRY_TICKS 44
@ -202,7 +201,7 @@ ISR(TIMER1_COMPA_vect)
next = now + TIMER_DEFER_REPEAT_TICKS;
goto done;
}
timer_repeat_set(now + TIMER_IDLE_REPEAT_TICKS);
timer_repeat_set(now + TIMER_REPEAT_TICKS);
timer_set(now);
}
}

View file

@ -112,10 +112,9 @@ timer_init(void)
DECL_INIT(timer_init);
static uint32_t timer_repeat_until;
#define TIMER_IDLE_REPEAT_TICKS timer_from_us(500)
#define TIMER_REPEAT_TICKS timer_from_us(100)
#define TIMER_MIN_TRY_TICKS timer_from_us(2)
#define TIMER_MIN_TRY_TICKS 90
#define TIMER_DEFER_REPEAT_TICKS timer_from_us(5)
// Invoke timers
@ -141,7 +140,7 @@ timer_dispatch_many(void)
timer_repeat_until = now + TIMER_REPEAT_TICKS;
return TIMER_DEFER_REPEAT_TICKS;
}
timer_repeat_until = tru = now + TIMER_IDLE_REPEAT_TICKS;
timer_repeat_until = tru = now + TIMER_REPEAT_TICKS;
}
// Next timer in the past or near future - wait for it to be ready

View file

@ -30,7 +30,6 @@ timer_is_before(uint32_t time1, uint32_t time2)
}
static uint32_t timer_repeat_until;
#define TIMER_IDLE_REPEAT_TICKS timer_from_us(500)
#define TIMER_REPEAT_TICKS timer_from_us(100)
#define TIMER_MIN_TRY_TICKS timer_from_us(2)
@ -59,7 +58,7 @@ timer_dispatch_many(void)
timer_repeat_until = now + TIMER_REPEAT_TICKS;
return now + TIMER_DEFER_REPEAT_TICKS;
}
timer_repeat_until = tru = now + TIMER_IDLE_REPEAT_TICKS;
timer_repeat_until = tru = now + TIMER_REPEAT_TICKS;
}
// Next timer in the past or near future - wait for it to be ready

Some files were not shown because too many files have changed in this diff Show more