Skip to content

Commit

Permalink
🚸 More flexible Probe Temperature Compensation (MarlinFirmware#23033)
Browse files Browse the repository at this point in the history
  • Loading branch information
tombrazier authored and AlexColello committed Jan 14, 2022
1 parent 244530a commit 949b49e
Show file tree
Hide file tree
Showing 15 changed files with 673 additions and 624 deletions.
100 changes: 52 additions & 48 deletions Marlin/Configuration_adv.h
Original file line number Diff line number Diff line change
Expand Up @@ -1988,65 +1988,69 @@

/**
* Thermal Probe Compensation
* Probe measurements are adjusted to compensate for temperature distortion.
* Use G76 to calibrate this feature. Use M871 to set values manually.
* For a more detailed explanation of the process see G76_M871.cpp.
*
* Adjust probe measurements to compensate for distortion associated with the temperature
* of the probe, bed, and/or hotend.
* Use G76 to automatically calibrate this feature for probe and bed temperatures.
* (Extruder temperature/offset values must be calibrated manually.)
* Use M871 to set temperature/offset values manually.
* For more details see https://marlinfw.org/docs/features/probe_temp_compensation.html
*/
#if HAS_BED_PROBE && TEMP_SENSOR_PROBE && TEMP_SENSOR_BED
// Enable thermal first layer compensation using bed and probe temperatures
#define PROBE_TEMP_COMPENSATION
//#define PTC_PROBE // Compensate based on probe temperature
//#define PTC_BED // Compensate based on bed temperature
//#define PTC_HOTEND // Compensate based on hotend temperature

#if ANY(PTC_PROBE, PTC_BED, PTC_HOTEND)
/**
* If the probe is outside the defined range, use linear extrapolation with the closest
* point and the point with index PTC_LINEAR_EXTRAPOLATION. e.g., If set to 4 it will use the
* linear extrapolation between data[0] and data[4] for values below PTC_PROBE_START.
*/
//#define PTC_LINEAR_EXTRAPOLATION 4

#if ENABLED(PTC_PROBE)
// Probe temperature calibration generates a table of values starting at PTC_PROBE_START
// (e.g., 30), in steps of PTC_PROBE_RES (e.g., 5) with PTC_PROBE_COUNT (e.g., 10) samples.
#define PTC_PROBE_START 30 // (°C)
#define PTC_PROBE_RES 5 // (°C)
#define PTC_PROBE_COUNT 10
#define PTC_PROBE_ZOFFS { 0 } // (µm) Z adjustments per sample
#endif

#if ENABLED(PTC_BED)
// Bed temperature calibration builds a similar table.
#define PTC_BED_START 60 // (°C)
#define PTC_BED_RES 5 // (°C)
#define PTC_BED_COUNT 10
#define PTC_BED_ZOFFS { 0 } // (µm) Z adjustments per sample
#endif

#if ENABLED(PTC_HOTEND)
// Note: There is no automatic calibration for the hotend. Use M871.
#define PTC_HOTEND_START 180 // (°C)
#define PTC_HOTEND_RES 5 // (°C)
#define PTC_HOTEND_COUNT 20
#define PTC_HOTEND_ZOFFS { 0 } // (µm) Z adjustments per sample
#endif

// Add additional compensation depending on hotend temperature
// Note: this values cannot be calibrated and have to be set manually
#if ENABLED(PROBE_TEMP_COMPENSATION)
// G76 options
#if BOTH(PTC_PROBE, PTC_BED)
// Park position to wait for probe cooldown
#define PTC_PARK_POS { 0, 0, 100 }

// Probe position to probe and wait for probe to reach target temperature
//#define PTC_PROBE_POS { 12.0f, 7.3f } // Example: MK52 magnetic heatbed
#define PTC_PROBE_POS { 90, 100 }

// Enable additional compensation using hotend temperature
// Note: this values cannot be calibrated automatically but have to be set manually via M871.
//#define USE_TEMP_EXT_COMPENSATION

// Probe temperature calibration generates a table of values starting at PTC_SAMPLE_START
// (e.g., 30), in steps of PTC_SAMPLE_RES (e.g., 5) with PTC_SAMPLE_COUNT (e.g., 10) samples.

//#define PTC_SAMPLE_START 30 // (°C)
//#define PTC_SAMPLE_RES 5 // (°C)
//#define PTC_SAMPLE_COUNT 10

// Bed temperature calibration builds a similar table.

//#define BTC_SAMPLE_START 60 // (°C)
//#define BTC_SAMPLE_RES 5 // (°C)
//#define BTC_SAMPLE_COUNT 10

#if ENABLED(USE_TEMP_EXT_COMPENSATION)
//#define ETC_SAMPLE_START 180 // (°C)
//#define ETC_SAMPLE_RES 5 // (°C)
//#define ETC_SAMPLE_COUNT 20
#endif

// The temperature the probe should be at while taking measurements during bed temperature
// calibration.
//#define BTC_PROBE_TEMP 30 // (°C)
// The temperature the probe should be at while taking measurements during
// bed temperature calibration.
#define PTC_PROBE_TEMP 30 // (°C)

// Height above Z=0.0 to raise the nozzle. Lowering this can help the probe to heat faster.
// Note: the Z=0.0 offset is determined by the probe offset which can be set using M851.
//#define PTC_PROBE_HEATING_OFFSET 0.5

// Height to raise the Z-probe between heating and taking the next measurement. Some probes
// may fail to untrigger if they have been triggered for a long time, which can be solved by
// increasing the height the probe is raised to.
//#define PTC_PROBE_RAISE 15

// If the probe is outside of the defined range, use linear extrapolation using the closest
// point and the PTC_LINEAR_EXTRAPOLATION'th next point. E.g. if set to 4 it will use data[0]
// and data[4] to perform linear extrapolation for values below PTC_SAMPLE_START.
//#define PTC_LINEAR_EXTRAPOLATION 4
// Note: The Z=0.0 offset is determined by the probe Z offset (e.g., as set with M851 Z).
#define PTC_PROBE_HEATING_OFFSET 0.5
#endif
#endif
#endif // PTC_PROBE || PTC_BED || PTC_HOTEND

// @section extras

Expand Down
86 changes: 44 additions & 42 deletions Marlin/src/feature/probe_temp_comp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,39 +22,53 @@

#include "../inc/MarlinConfigPre.h"

#if ENABLED(PROBE_TEMP_COMPENSATION)
#if HAS_PTC

//#define DEBUG_PTC // Print extra debug output with 'M871'

#include "probe_temp_comp.h"
#include <math.h>

ProbeTempComp temp_comp;
ProbeTempComp ptc;

int16_t ProbeTempComp::z_offsets_probe[cali_info_init[TSI_PROBE].measurements], // = {0}
ProbeTempComp::z_offsets_bed[cali_info_init[TSI_BED].measurements]; // = {0}
#if ENABLED(PTC_PROBE)
constexpr int16_t z_offsets_probe_default[PTC_PROBE_COUNT] = PTC_PROBE_ZOFFS;
int16_t ProbeTempComp::z_offsets_probe[PTC_PROBE_COUNT] = PTC_PROBE_ZOFFS;
#endif

#if ENABLED(USE_TEMP_EXT_COMPENSATION)
int16_t ProbeTempComp::z_offsets_ext[cali_info_init[TSI_EXT].measurements]; // = {0}
#if ENABLED(PTC_BED)
constexpr int16_t z_offsets_bed_default[PTC_BED_COUNT] = PTC_BED_ZOFFS;
int16_t ProbeTempComp::z_offsets_bed[PTC_BED_COUNT] = PTC_BED_ZOFFS;
#endif

int16_t *ProbeTempComp::sensor_z_offsets[TSI_COUNT] = {
ProbeTempComp::z_offsets_probe, ProbeTempComp::z_offsets_bed
OPTARG(USE_TEMP_EXT_COMPENSATION, ProbeTempComp::z_offsets_ext)
};
#if ENABLED(PTC_HOTEND)
constexpr int16_t z_offsets_hotend_default[PTC_HOTEND_COUNT] = PTC_HOTEND_ZOFFS;
int16_t ProbeTempComp::z_offsets_hotend[PTC_HOTEND_COUNT] = PTC_HOTEND_ZOFFS;
#endif

const temp_calib_t ProbeTempComp::cali_info[TSI_COUNT] = {
cali_info_init[TSI_PROBE], cali_info_init[TSI_BED]
OPTARG(USE_TEMP_EXT_COMPENSATION, cali_info_init[TSI_EXT])
int16_t *ProbeTempComp::sensor_z_offsets[TSI_COUNT] = {
#if ENABLED(PTC_PROBE)
ProbeTempComp::z_offsets_probe,
#endif
#if ENABLED(PTC_BED)
ProbeTempComp::z_offsets_bed,
#endif
#if ENABLED(PTC_HOTEND)
ProbeTempComp::z_offsets_hotend,
#endif
};

constexpr xyz_pos_t ProbeTempComp::park_point;
constexpr xy_pos_t ProbeTempComp::measure_point;
constexpr celsius_t ProbeTempComp::probe_calib_bed_temp;
constexpr temp_calib_t ProbeTempComp::cali_info[TSI_COUNT];

uint8_t ProbeTempComp::calib_idx; // = 0
float ProbeTempComp::init_measurement; // = 0.0

void ProbeTempComp::reset() {
TERN_(PTC_PROBE, LOOP_L_N(i, PTC_PROBE_COUNT) z_offsets_probe[i] = z_offsets_probe_default[i]);
TERN_(PTC_BED, LOOP_L_N(i, PTC_BED_COUNT) z_offsets_bed[i] = z_offsets_bed_default[i]);
TERN_(PTC_HOTEND, LOOP_L_N(i, PTC_HOTEND_COUNT) z_offsets_hotend[i] = z_offsets_hotend_default[i]);
}

void ProbeTempComp::clear_offsets(const TempSensorID tsi) {
LOOP_L_N(i, cali_info[tsi].measurements)
sensor_z_offsets[tsi][i] = 0;
Expand All @@ -71,10 +85,9 @@ void ProbeTempComp::print_offsets() {
LOOP_L_N(s, TSI_COUNT) {
celsius_t temp = cali_info[s].start_temp;
for (int16_t i = -1; i < cali_info[s].measurements; ++i) {
SERIAL_ECHOF(s == TSI_BED ? F("Bed") :
#if ENABLED(USE_TEMP_EXT_COMPENSATION)
s == TSI_EXT ? F("Extruder") :
#endif
SERIAL_ECHOF(
TERN_(PTC_BED, s == TSI_BED ? F("Bed") :)
TERN_(PTC_HOTEND, s == TSI_EXT ? F("Extruder") :)
F("Probe")
);
SERIAL_ECHOLNPGM(
Expand All @@ -100,21 +113,13 @@ void ProbeTempComp::prepare_new_calibration(const_float_t init_meas_z) {
}

void ProbeTempComp::push_back_new_measurement(const TempSensorID tsi, const_float_t meas_z) {
switch (tsi) {
case TSI_PROBE:
case TSI_BED:
//case TSI_EXT:
if (calib_idx >= cali_info[tsi].measurements) return;
sensor_z_offsets[tsi][calib_idx++] = static_cast<int16_t>(meas_z * 1000.0f - init_measurement * 1000.0f);
default: break;
}
if (calib_idx >= cali_info[tsi].measurements) return;
sensor_z_offsets[tsi][calib_idx++] = static_cast<int16_t>((meas_z - init_measurement) * 1000.0f);
}

bool ProbeTempComp::finish_calibration(const TempSensorID tsi) {
if (tsi != TSI_PROBE && tsi != TSI_BED) return false;

if (calib_idx < 3) {
SERIAL_ECHOLNPGM("!Insufficient measurements (min. 3).");
if (!calib_idx) {
SERIAL_ECHOLNPGM("!No measurements.");
clear_offsets(tsi);
return false;
}
Expand All @@ -130,16 +135,15 @@ bool ProbeTempComp::finish_calibration(const TempSensorID tsi) {
SERIAL_ECHOLNPGM("Got ", calib_idx, " measurements. ");
if (linear_regression(tsi, k, d)) {
SERIAL_ECHOPGM("Applying linear extrapolation");
calib_idx--;
for (; calib_idx < measurements; ++calib_idx) {
const celsius_float_t temp = start_temp + float(calib_idx) * res_temp;
const celsius_float_t temp = start_temp + float(calib_idx + 1) * res_temp;
data[calib_idx] = static_cast<int16_t>(k * temp + d);
}
}
else {
// Simply use the last measured value for higher temperatures
SERIAL_ECHOPGM("Failed to extrapolate");
const int16_t last_val = data[calib_idx];
const int16_t last_val = data[calib_idx-1];
for (; calib_idx < measurements; ++calib_idx)
data[calib_idx] = last_val;
}
Expand All @@ -157,7 +161,7 @@ bool ProbeTempComp::finish_calibration(const TempSensorID tsi) {
// Restrict the max. offset difference between two probings
if (calib_idx > 0 && ABS(data[calib_idx - 1] - data[calib_idx]) > 800) {
SERIAL_ECHOLNPGM("!Invalid Z-offset between two probings detected (0-0.8).");
clear_offsets(TSI_PROBE);
clear_offsets(tsi);
return false;
}
}
Expand All @@ -168,8 +172,8 @@ bool ProbeTempComp::finish_calibration(const TempSensorID tsi) {
void ProbeTempComp::compensate_measurement(const TempSensorID tsi, const celsius_t temp, float &meas_z) {
const uint8_t measurements = cali_info[tsi].measurements;
const celsius_t start_temp = cali_info[tsi].start_temp,
end_temp = cali_info[tsi].end_temp,
res_temp = cali_info[tsi].temp_resolution;
res_temp = cali_info[tsi].temp_resolution,
end_temp = start_temp + measurements * res_temp;
const int16_t * const data = sensor_z_offsets[tsi];

// Given a data index, return { celsius, zoffset } in the form { x, y }
Expand Down Expand Up @@ -208,9 +212,7 @@ void ProbeTempComp::compensate_measurement(const TempSensorID tsi, const celsius
}

bool ProbeTempComp::linear_regression(const TempSensorID tsi, float &k, float &d) {
if (tsi != TSI_PROBE && tsi != TSI_BED) return false;

if (!WITHIN(calib_idx, 2, cali_info[tsi].measurements)) return false;
if (!WITHIN(calib_idx, 1, cali_info[tsi].measurements)) return false;

const celsius_t start_temp = cali_info[tsi].start_temp,
res_temp = cali_info[tsi].temp_resolution;
Expand Down Expand Up @@ -243,4 +245,4 @@ bool ProbeTempComp::linear_regression(const TempSensorID tsi, float &k, float &d
return true;
}

#endif // PROBE_TEMP_COMPENSATION
#endif // HAS_PTC
Loading

0 comments on commit 949b49e

Please sign in to comment.