Browse Source

PINDA v2 temperature sensor / compensation (#16293)

tompe-proj 4 years ago
parent
commit
a338dce83f

+ 1
- 0
Marlin/Configuration.h View File

@@ -412,6 +412,7 @@
412 412
 #define TEMP_SENSOR_4 0
413 413
 #define TEMP_SENSOR_5 0
414 414
 #define TEMP_SENSOR_BED 0
415
+#define TEMP_SENSOR_PROBE 0
415 416
 #define TEMP_SENSOR_CHAMBER 0
416 417
 
417 418
 // Dummy thermistor constant temperature readings, for use with 998 and 999

+ 32
- 0
Marlin/Configuration_adv.h View File

@@ -1529,6 +1529,38 @@
1529 1529
 
1530 1530
 #endif
1531 1531
 
1532
+/**
1533
+ * Thermal Probe Compensation
1534
+ * Probe measurements are adjusted to compensate for temperature distortion.
1535
+ * Use G76 to calibrate this feature. Use M871 to set values manually.
1536
+ * For a more detailed explanation of the process see G76_M871.cpp.
1537
+ */
1538
+#if HAS_BED_PROBE && TEMP_SENSOR_PROBE && TEMP_SENSOR_BED
1539
+  // Enable thermal first layer compensation using bed and probe temperatures
1540
+  #define PROBE_TEMP_COMPENSATION
1541
+
1542
+  // Add additional compensation depending on hotend temperature
1543
+  // Note: this values cannot be calibrated and have to be set manually
1544
+  #ifdef PROBE_TEMP_COMPENSATION
1545
+    // Max temperature that can be reached by heated bed.
1546
+    // This is required only for the calibration process.
1547
+    #define PTC_MAX_BED_TEMP 110
1548
+
1549
+    // Park position to wait for probe cooldown
1550
+    #define PTC_PARK_POS_X 0.0F
1551
+    #define PTC_PARK_POS_Y 0.0F
1552
+    #define PTC_PARK_POS_Z 100.0F
1553
+
1554
+    // Probe position to probe and wait for probe to reach target temperature
1555
+    #define PTC_PROBE_POS_X  90.0F
1556
+    #define PTC_PROBE_POS_Y 100.0F
1557
+
1558
+    // Enable additional compensation using hotend temperature
1559
+    // Note: this values cannot be calibrated automatically but have to be set manually
1560
+    //#define USE_TEMP_EXT_COMPENSATION
1561
+  #endif
1562
+#endif
1563
+
1532 1564
 // @section extras
1533 1565
 
1534 1566
 //

+ 223
- 0
Marlin/src/feature/probe_temp_compensation.cpp View File

@@ -0,0 +1,223 @@
1
+/**
2
+ * Marlin 3D Printer Firmware
3
+ * Copyright (c) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
4
+ *
5
+ * Based on Sprinter and grbl.
6
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
7
+ *
8
+ * This program is free software: you can redistribute it and/or modify
9
+ * it under the terms of the GNU General Public License as published by
10
+ * the Free Software Foundation, either version 3 of the License, or
11
+ * (at your option) any later version.
12
+ *
13
+ * This program is distributed in the hope that it will be useful,
14
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
+ * GNU General Public License for more details.
17
+ *
18
+ * You should have received a copy of the GNU General Public License
19
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
+ *
21
+ */
22
+
23
+#include "../inc/MarlinConfigPre.h"
24
+
25
+#if ENABLED(PROBE_TEMP_COMPENSATION)
26
+
27
+#include "probe_temp_compensation.h"
28
+#include <math.h>
29
+
30
+ProbeTempComp temp_comp;
31
+
32
+int16_t ProbeTempComp::z_offsets_probe[ProbeTempComp::cali_info_init[TSI_PROBE].measurements],  // = {0}
33
+        ProbeTempComp::z_offsets_bed[ProbeTempComp::cali_info_init[TSI_BED].measurements];      // = {0}
34
+
35
+#if ENABLED(USE_TEMP_EXT_COMPENSATION)
36
+  int16_t ProbeTempComp::z_offsets_ext[ProbeTempComp::cali_info_init[TSI_EXT].measurements];    // = {0}
37
+#endif
38
+
39
+int16_t *ProbeTempComp::sensor_z_offsets[TSI_COUNT] = {
40
+  ProbeTempComp::z_offsets_probe, ProbeTempComp::z_offsets_bed
41
+  #if ENABLED(USE_TEMP_EXT_COMPENSATION)
42
+    , ProbeTempComp::z_offsets_ext
43
+  #endif
44
+};
45
+
46
+const temp_calib_t ProbeTempComp::cali_info[TSI_COUNT] = {
47
+  ProbeTempComp::cali_info_init[TSI_PROBE], ProbeTempComp::cali_info_init[TSI_BED]
48
+  #if ENABLED(USE_TEMP_EXT_COMPENSATION)
49
+    , ProbeTempComp::cali_info_init[TSI_EXT]
50
+  #endif
51
+};
52
+
53
+uint8_t ProbeTempComp::calib_idx; // = 0
54
+float ProbeTempComp::init_measurement; // = 0.0
55
+
56
+void ProbeTempComp::clear_offsets(const TempSensorID tsi) {
57
+  for (uint8_t i = 0; i < cali_info[tsi].measurements; ++i)
58
+    sensor_z_offsets[tsi][i] = 0;
59
+  calib_idx = 0;
60
+}
61
+
62
+bool ProbeTempComp::set_offset(const TempSensorID tsi, const uint8_t idx, const int16_t offset) {
63
+  if (idx >= cali_info[tsi].measurements) return false;
64
+  sensor_z_offsets[tsi][idx] = offset;
65
+  return true;
66
+}
67
+
68
+void ProbeTempComp::print_offsets() {
69
+  for (uint8_t s = 0; s < TSI_COUNT; s++) {
70
+    float temp = cali_info[s].start_temp;
71
+    for (int16_t i = -1; i < cali_info[s].measurements; ++i) {
72
+      serialprintPGM(s == TSI_BED ? PSTR("Bed") :
73
+        #if ENABLED(USE_TEMP_EXT_COMPENSATION)
74
+          s == TSI_EXT ? PSTR("Extruder") :
75
+        #endif
76
+        PSTR("Probe")
77
+      );
78
+      SERIAL_ECHOLNPAIR(
79
+        " temp: ", temp,
80
+        "C; Offset: ", i < 0 ? 0.0f : sensor_z_offsets[s][i], " um"
81
+      );
82
+      temp += cali_info[s].temp_res;
83
+    }
84
+  }
85
+}
86
+
87
+void ProbeTempComp::prepare_new_calibration(const float &init_meas_z) {
88
+  calib_idx = 0;
89
+  init_measurement = init_meas_z;
90
+}
91
+
92
+void ProbeTempComp::push_back_new_measurement(const TempSensorID tsi, const float &meas_z) {
93
+  switch (tsi) {
94
+    case TSI_PROBE:
95
+    case TSI_BED:
96
+    //case TSI_EXT:
97
+      if (calib_idx >= cali_info[tsi].measurements) return;
98
+      sensor_z_offsets[tsi][calib_idx++] = static_cast<int16_t>(meas_z * 1000.0f - init_measurement * 1000.0f);
99
+    default: break;
100
+  }
101
+}
102
+
103
+bool ProbeTempComp::finish_calibration(const TempSensorID tsi) {
104
+  if (tsi != TSI_PROBE && tsi != TSI_BED) return false;
105
+
106
+  if (calib_idx < 3) {
107
+    SERIAL_ECHOLNPGM("!Insufficient measurements (min. 3).");
108
+    clear_offsets(tsi);
109
+    return false;
110
+  }
111
+
112
+  const uint8_t measurements = cali_info[tsi].measurements;
113
+  const float start_temp = cali_info[tsi].start_temp,
114
+                res_temp = cali_info[tsi].temp_res;
115
+  int16_t * const data = sensor_z_offsets[tsi];
116
+
117
+  // Extrapolate
118
+  float k, d;
119
+  if (calib_idx < measurements) {
120
+    SERIAL_ECHOLNPAIR("Got ", calib_idx, " measurements. ");
121
+    if (linear_regression(tsi, k, d)) {
122
+      SERIAL_ECHOPGM("Applying linear extrapolation");
123
+      calib_idx--;
124
+      for (; calib_idx < measurements; ++calib_idx) {
125
+        const float temp = start_temp + float(calib_idx) * res_temp;
126
+        data[calib_idx] = static_cast<int16_t>(k * temp + d);
127
+      }
128
+    }
129
+    else {
130
+      // Simply use the last measured value for higher temperatures
131
+      SERIAL_ECHOPGM("Failed to extrapolate");
132
+      const int16_t last_val = data[calib_idx];
133
+      for (; calib_idx < measurements; ++calib_idx)
134
+        data[calib_idx] = last_val;
135
+    }
136
+    SERIAL_ECHOLNPGM(" for higher temperatures.");
137
+  }
138
+
139
+  // Sanity check
140
+  for (calib_idx = 0; calib_idx < measurements; ++calib_idx) {
141
+    // Restrict the max. offset
142
+    if (abs(data[calib_idx]) > 2000) {
143
+      SERIAL_ECHOLNPGM("!Invalid Z-offset detected (0-2).");
144
+      clear_offsets(tsi);
145
+      return false;
146
+    }
147
+    // Restrict the max. offset difference between two probings
148
+    if (calib_idx > 0 && abs(data[calib_idx - 1] - data[calib_idx]) > 800) {
149
+      SERIAL_ECHOLNPGM("!Invalid Z-offset between two probings detected (0-0.8).");
150
+      clear_offsets(TSI_PROBE);
151
+      return false;
152
+    }
153
+  }
154
+
155
+  return true;
156
+}
157
+
158
+void ProbeTempComp::compensate_measurement(const TempSensorID tsi, const float &temp, float &meas_z) {
159
+  if (WITHIN(temp, cali_info[tsi].start_temp, cali_info[tsi].end_temp))
160
+    meas_z -= get_offset_for_temperature(tsi, temp);
161
+}
162
+
163
+float ProbeTempComp::get_offset_for_temperature(const TempSensorID tsi, const float &temp) {
164
+
165
+  const uint8_t measurements = cali_info[tsi].measurements;
166
+  const float start_temp = cali_info[tsi].start_temp,
167
+                end_temp = cali_info[tsi].end_temp,
168
+                res_temp = cali_info[tsi].temp_res;
169
+  const int16_t * const data = sensor_z_offsets[tsi];
170
+
171
+  if (temp <= start_temp) return 0.0f;
172
+  if (temp >= end_temp) return static_cast<float>(data[measurements - 1]) / 1000.0f;
173
+
174
+  // Linear interpolation
175
+  int16_t val1 = 0, val2 = data[0];
176
+  uint8_t idx = 0;
177
+  float meas_temp = start_temp + res_temp;
178
+  while (meas_temp < temp) {
179
+    if (++idx >= measurements) return static_cast<float>(val2) / 1000.0f;
180
+    meas_temp += res_temp;
181
+    val1 = val2;
182
+    val2 = data[idx];
183
+  }
184
+  const float factor = (meas_temp - temp) / static_cast<float>(res_temp);
185
+  return (static_cast<float>(val2) - static_cast<float>(val2 - val1) * factor) / 1000.0f;
186
+}
187
+
188
+bool ProbeTempComp::linear_regression(const TempSensorID tsi, float &k, float &d) {
189
+  if (tsi != TSI_PROBE && tsi != TSI_BED) return false;
190
+
191
+  if (!WITHIN(calib_idx, 2, cali_info[tsi].measurements)) return false;
192
+
193
+  const float start_temp = cali_info[tsi].start_temp,
194
+                res_temp = cali_info[tsi].temp_res;
195
+  const int16_t * const data = sensor_z_offsets[tsi];
196
+
197
+  float sum_x = start_temp,
198
+        sum_x2 = sq(start_temp),
199
+        sum_xy = 0, sum_y = 0;
200
+
201
+  for (uint8_t i = 0; i < calib_idx; ++i) {
202
+    const float xi = start_temp + (i + 1) * res_temp,
203
+                yi = static_cast<float>(data[i]);
204
+    sum_x += xi;
205
+    sum_x2 += sq(xi);
206
+    sum_xy += xi * yi;
207
+    sum_y += yi;
208
+  }
209
+
210
+  const float denom = static_cast<float>(calib_idx + 1) * sum_x2 - sq(sum_x);
211
+  if (fabs(denom) <= 10e-5) {
212
+    // Singularity - unable to solve
213
+    k = d = 0.0;
214
+    return false;
215
+  }
216
+
217
+  k = (static_cast<float>(calib_idx + 1) * sum_xy - sum_x * sum_y) / denom;
218
+  d = (sum_y - k * sum_x) / static_cast<float>(calib_idx + 1);
219
+
220
+  return true;
221
+}
222
+
223
+#endif // PROBE_TEMP_COMPENSATION

+ 116
- 0
Marlin/src/feature/probe_temp_compensation.h View File

@@ -0,0 +1,116 @@
1
+/**
2
+ * Marlin 3D Printer Firmware
3
+ * Copyright (c) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
4
+ *
5
+ * Based on Sprinter and grbl.
6
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
7
+ *
8
+ * This program is free software: you can redistribute it and/or modify
9
+ * it under the terms of the GNU General Public License as published by
10
+ * the Free Software Foundation, either version 3 of the License, or
11
+ * (at your option) any later version.
12
+ *
13
+ * This program is distributed in the hope that it will be useful,
14
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
+ * GNU General Public License for more details.
17
+ *
18
+ * You should have received a copy of the GNU General Public License
19
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
+ *
21
+ */
22
+#pragma once
23
+
24
+#include "../inc/MarlinConfig.h"
25
+
26
+enum TempSensorID : uint8_t {
27
+  TSI_PROBE,
28
+  TSI_BED,
29
+  #if ENABLED(USE_TEMP_EXT_COMPENSATION)
30
+    TSI_EXT,
31
+  #endif
32
+  TSI_COUNT
33
+};
34
+
35
+typedef struct {
36
+  uint8_t measurements; // Max. number of measurements to be stored (35 - 80°C)
37
+  float   temp_res,     // Resolution in °C between measurements
38
+          start_temp,   // Base measurement; z-offset == 0
39
+          end_temp;
40
+} temp_calib_t;
41
+
42
+/**
43
+ * Probe temperature compensation implementation.
44
+ * Z-probes like the P.I.N.D.A V2 allow for compensation of
45
+ * measurement errors/shifts due to changed temperature.
46
+ */
47
+class ProbeTempComp {
48
+  public:
49
+
50
+    static constexpr temp_calib_t cali_info_init[TSI_COUNT] = {
51
+        {  30, 10,  5,  30 + 10 *  5 },       // Probe
52
+        {  60, 10,  5,  60 + 10 *  5 },       // Bed
53
+      #if ENABLED(USE_TEMP_EXT_COMPENSATION)
54
+        { 180,  5, 20, 180 +  5 * 20 }        // Extruder
55
+      #endif
56
+    };
57
+    static const temp_calib_t cali_info[TSI_COUNT];
58
+
59
+    // Where to park nozzle to wait for probe cooldown
60
+    static constexpr xyz_pos_t park_point = { PTC_PARK_POS_X, PTC_PARK_POS_Y, PTC_PARK_POS_Z };
61
+
62
+    static constexpr int  max_bed_temp         = PTC_MAX_BED_TEMP,  // Max temperature to avoid heating errors
63
+
64
+                          // XY coordinates of nozzle for probing the bed
65
+                          measure_point_x      = PTC_PROBE_POS_X,   // X-coordinate to probe
66
+                          measure_point_y      = PTC_PROBE_POS_Y,   // Y-coordinate to probe
67
+                          //measure_point_x    = 12.0f,             // X-coordinate to probe on MK52 magnetic heatbed
68
+                          //measure_point_y    =  7.3f,             // Y-coordinate to probe on MK52 magnetic heatbed
69
+
70
+                          probe_calib_bed_temp = max_bed_temp,      // Bed temperature while calibrating probe
71
+                          bed_calib_probe_temp = 30;                // Probe temperature while calibrating bed
72
+
73
+    static int16_t *sensor_z_offsets[TSI_COUNT],
74
+                   z_offsets_probe[cali_info_init[TSI_PROBE].measurements], // (µm)
75
+                   z_offsets_bed[cali_info_init[TSI_BED].measurements];     // (µm)
76
+
77
+    #if ENABLED(USE_TEMP_EXT_COMPENSATION)
78
+      static int16_t z_offsets_ext[cali_info_init[TSI_EXT].measurements];   // (µm)
79
+    #endif
80
+
81
+    static inline void reset_index() { calib_idx = 0; };
82
+    static inline uint8_t get_index() { return calib_idx; }
83
+    static void clear_offsets(const TempSensorID tsi);
84
+    static inline void clear_all_offsets() {
85
+      clear_offsets(TSI_BED);
86
+      clear_offsets(TSI_PROBE);
87
+      #if ENABLED(USE_TEMP_EXT_COMPENSATION)
88
+        clear_offsets(TSI_EXT);
89
+      #endif
90
+    }
91
+    static bool set_offset(const TempSensorID tsi, const uint8_t idx, const int16_t offset);
92
+    static void print_offsets();
93
+    static void prepare_new_calibration(const float &init_meas_z);
94
+    static void push_back_new_measurement(const TempSensorID tsi, const float &meas_z);
95
+    static bool finish_calibration(const TempSensorID tsi);
96
+    static void compensate_measurement(const TempSensorID tsi, const float &temp, float &meas_z);
97
+
98
+  private:
99
+    static uint8_t calib_idx;
100
+
101
+    /**
102
+     * Base value. Temperature compensation values will be deltas
103
+     * to this value, set at first probe.
104
+     */
105
+    static float init_measurement;
106
+
107
+    static float get_offset_for_temperature(const TempSensorID tsi, const float &temp);
108
+
109
+    /**
110
+     * Fit a linear function in measured temperature offsets
111
+     * to allow generating values of higher temperatures.
112
+     */
113
+    static bool linear_regression(const TempSensorID tsi, float &k, float &d);
114
+};
115
+
116
+extern ProbeTempComp temp_comp;

+ 13
- 0
Marlin/src/gcode/bedlevel/abl/G29.cpp View File

@@ -36,6 +36,11 @@
36 36
 #include "../../../module/probe.h"
37 37
 #include "../../queue.h"
38 38
 
39
+#if ENABLED(PROBE_TEMP_COMPENSATION)
40
+  #include "../../../feature/probe_temp_compensation.h"
41
+  #include "../../../module/temperature.h"
42
+#endif
43
+
39 44
 #if HAS_DISPLAY
40 45
   #include "../../../lcd/ultralcd.h"
41 46
 #endif
@@ -714,6 +719,14 @@ G29_TYPE GcodeSuite::G29() {
714 719
             break; // Breaks out of both loops
715 720
           }
716 721
 
722
+          #if ENABLED(PROBE_TEMP_COMPENSATION)
723
+            temp_comp.compensate_measurement(TSI_BED, thermalManager.degBed(), measured_z);
724
+            temp_comp.compensate_measurement(TSI_PROBE, thermalManager.degProbe(), measured_z);
725
+            #if ENABLED(USE_TEMP_EXT_COMPENSATION)
726
+              temp_comp.compensate_measurement(TSI_EXT, thermalManager.degHotend(), measured_z);
727
+            #endif
728
+          #endif
729
+
717 730
           #if ENABLED(AUTO_BED_LEVELING_LINEAR)
718 731
 
719 732
             mean += measured_z;

+ 407
- 0
Marlin/src/gcode/calibrate/G76_M871.cpp View File

@@ -0,0 +1,407 @@
1
+/**
2
+ * Marlin 3D Printer Firmware
3
+ * Copyright (c) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
4
+ *
5
+ * Based on Sprinter and grbl.
6
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
7
+ *
8
+ * This program is free software: you can redistribute it and/or modify
9
+ * it under the terms of the GNU General Public License as published by
10
+ * the Free Software Foundation, either version 3 of the License, or
11
+ * (at your option) any later version.
12
+ *
13
+ * This program is distributed in the hope that it will be useful,
14
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
+ * GNU General Public License for more details.
17
+ *
18
+ * You should have received a copy of the GNU General Public License
19
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
+ *
21
+ */
22
+
23
+/**
24
+ * G76_M871.cpp - Temperature calibration/compensation for z-probing
25
+ */
26
+
27
+#include "../../inc/MarlinConfig.h"
28
+
29
+#if ENABLED(PROBE_TEMP_COMPENSATION)
30
+
31
+#include "../gcode.h"
32
+#include "../../module/motion.h"
33
+#include "../../module/planner.h"
34
+#include "../../module/probe.h"
35
+#include "../../feature/bedlevel/bedlevel.h"
36
+#include "../../module/temperature.h"
37
+#include "../../module/probe.h"
38
+#include "../../feature/probe_temp_compensation.h"
39
+
40
+/**
41
+ * G76: calibrate probe and/or bed temperature offsets
42
+ *  Notes:
43
+ *  - When calibrating probe, bed temperature is held constant.
44
+ *    Compensation values are deltas to first probe measurement at probe temp. = 30°C.
45
+ *  - When calibrating bed, probe temperature is held constant.
46
+ *    Compensation values are deltas to first probe measurement at bed temp. = 60°C.
47
+ *  - The hotend will not be heated at any time.
48
+ *  - On my Prusa MK3S clone I put a piece of paper between the probe and the hotend
49
+ *    so the hotend fan would not cool my probe constantly. Alternativly you could just
50
+ *    make sure the fan is not running while running the calibration process.
51
+ *
52
+ *  Probe calibration:
53
+ *  - Moves probe to cooldown point.
54
+ *  - Heats up bed to 100°C.
55
+ *  - Moves probe to probing point (1mm above heatbed).
56
+ *  - Waits until probe reaches target temperature (30°C).
57
+ *  - Does a z-probing (=base value) and increases target temperature by 5°C.
58
+ *  - Waits until probe reaches increased target temperature.
59
+ *  - Does a z-probing (delta to base value will be a compensation value) and increases target temperature by 5°C.
60
+ *  - Repeats last two steps until max. temperature reached or timeout (i.e. probe does not heat up any further).
61
+ *  - Compensation values of higher temperatures will be extrapolated (using linear regression first).
62
+ *    While this is not exact by any means it is still better than simply using the last compensation value.
63
+ *
64
+ *  Bed calibration:
65
+ *  - Moves probe to cooldown point.
66
+ *  - Heats up bed to 60°C.
67
+ *  - Moves probe to probing point (1mm above heatbed).
68
+ *  - Waits until probe reaches target temperature (30°C).
69
+ *  - Does a z-probing (=base value) and increases bed temperature by 5°C.
70
+ *  - Moves probe to cooldown point.
71
+ *  - Waits until probe is below 30°C and bed has reached target temperature.
72
+ *  - Moves probe to probing point and waits until it reaches target temperature (30°C).
73
+ *  - Does a z-probing (delta to base value will be a compensation value) and increases bed temperature by 5°C.
74
+ *  - Repeats last four points until max. bed temperature reached (110°C) or timeout.
75
+ *  - Compensation values of higher temperatures will be extrapolated (using linear regression first).
76
+ *    While this is not exact by any means it is still better than simply using the last compensation value.
77
+ *
78
+ *  G76 [B | P]
79
+ *  - no flag - Both calibration procedures will be run.
80
+ *  - `B` - Run bed temperature calibration.
81
+ *  - `P` - Run probe temperature calibration.
82
+ */
83
+void GcodeSuite::G76() {
84
+  // Check if heated bed is available and z-homing is done with probe
85
+  #if TEMP_SENSOR_BED == 0 || !(HOMING_Z_WITH_PROBE)
86
+    return;
87
+  #endif
88
+
89
+  #if ENABLED(BLTOUCH)
90
+    // Make sure any BLTouch error condition is cleared
91
+    bltouch_command(BLTOUCH_RESET, BLTOUCH_RESET_DELAY);
92
+    set_bltouch_deployed(false);
93
+  #endif
94
+
95
+  bool do_bed_cal = parser.boolval('B'),
96
+       do_probe_cal = parser.boolval('P');
97
+  if (!do_bed_cal && !do_probe_cal)
98
+    do_bed_cal = do_probe_cal = true;
99
+
100
+  // Synchronize with planner
101
+  planner.synchronize();
102
+
103
+  // Report temperatures every second and handle heating timeouts
104
+  millis_t next_temp_report = millis() + 1000;
105
+
106
+  if (do_bed_cal || do_probe_cal) {
107
+    // Ensure park position is reachable
108
+    if (!position_is_reachable(ProbeTempComp::park_point.x, ProbeTempComp::park_point.y)
109
+      || !(WITHIN(ProbeTempComp::park_point.z, Z_MIN_POS - 0.001f, Z_MAX_POS + 0.001f))
110
+    ) {
111
+      SERIAL_ECHOLNPGM("!Park position unreachable - aborting.");
112
+      return;
113
+    }
114
+    // Ensure probe position is reachable
115
+    destination.set(
116
+      temp_comp.measure_point_x - probe_offset.x,
117
+      temp_comp.measure_point_y - probe_offset.y
118
+    );
119
+    if (!position_is_reachable_by_probe(destination)) {
120
+      SERIAL_ECHOLNPGM("!Probe position unreachable - aborting.");
121
+      return;
122
+    }
123
+
124
+    G28(true);
125
+  }
126
+
127
+  /******************************************
128
+   * Calibrate bed temperature offsets
129
+   ******************************************/
130
+
131
+  if (do_bed_cal) {
132
+
133
+    uint16_t target_bed = temp_comp.cali_info_init[TSI_BED].start_temp,
134
+             target_probe = temp_comp.bed_calib_probe_temp;
135
+
136
+    SERIAL_ECHOLNPGM("Waiting for printer to cool down.");
137
+    while (thermalManager.degBed() > target_bed
138
+      || thermalManager.degProbe() > target_probe
139
+    ) {
140
+      idle(
141
+        #if ENABLED(ADVANCED_PAUSE_FEATURE)
142
+          true
143
+        #endif
144
+      );
145
+      const millis_t ms = millis();
146
+      if (ELAPSED(ms, next_temp_report)) {
147
+        thermalManager.print_heater_states(active_extruder);
148
+        next_temp_report = ms + 1000;
149
+      }
150
+    }
151
+
152
+    // Disable leveling so it won't mess with us
153
+    #if HAS_LEVELING
154
+      set_bed_leveling_enabled(false);
155
+    #endif
156
+
157
+    bool timeout = false;
158
+    while (true) {
159
+      thermalManager.setTargetBed(target_bed);
160
+
161
+      SERIAL_ECHOLNPAIR("Target Bed: ", target_bed, "; Probe: ", target_probe);
162
+
163
+      // Park nozzle
164
+      do_blocking_move_to(ProbeTempComp::park_point.x, ProbeTempComp::park_point.y, ProbeTempComp::park_point.z);
165
+
166
+      // Wait for heatbed to reach target temp and probe to cool below target temp
167
+      SERIAL_ECHOLNPGM("Waiting for bed and probe to reach target temp.");
168
+      const millis_t probe_timeout_ms = millis() + 900UL * 1000UL;
169
+      while (fabs(thermalManager.degBed() - float(target_bed)) > 0.1 || thermalManager.degProbe() > target_probe) {
170
+        idle(
171
+          #if ENABLED(ADVANCED_PAUSE_FEATURE)
172
+            true
173
+          #endif
174
+        );
175
+        const millis_t ms = millis();
176
+        if (ELAPSED(ms, next_temp_report)) {
177
+          thermalManager.print_heater_states(active_extruder);
178
+          next_temp_report = ms + 1000;
179
+        }
180
+        if (ELAPSED(ms, probe_timeout_ms)) {
181
+          SERIAL_ECHOLNPGM("!Bed heating timeout.");
182
+          timeout = true;
183
+          break;
184
+        }
185
+      }
186
+
187
+      if (timeout) break;
188
+
189
+      // Move probe to probing point and wait for probe to reach target temp
190
+      destination.set(temp_comp.measure_point_x, temp_comp.measure_point_y, 0.5);
191
+      do_blocking_move_to(destination.x, destination.y, destination.z);
192
+      SERIAL_ECHOLNPGM("Waiting for probe heating.");
193
+      while (thermalManager.degProbe() < target_probe) {
194
+        idle(
195
+          #if ENABLED(ADVANCED_PAUSE_FEATURE)
196
+            true
197
+          #endif
198
+        );
199
+        const millis_t ms = millis();
200
+        if (ELAPSED(ms, next_temp_report)) {
201
+          thermalManager.print_heater_states(active_extruder);
202
+          next_temp_report = ms + 1000;
203
+        }
204
+      }
205
+
206
+      // Raise nozzle before probing
207
+      destination.z = 5.0;
208
+      do_blocking_move_to_z(destination.z);
209
+
210
+      // Do a single probe
211
+      remember_feedrate_scaling_off();
212
+      const float measured_z = probe_at_point(
213
+        destination.x + probe_offset.x,
214
+        destination.y + probe_offset.y,
215
+        PROBE_PT_NONE
216
+      );
217
+      restore_feedrate_and_scaling();
218
+
219
+      if (isnan(measured_z)) {
220
+        SERIAL_ECHOLNPGM("!Received NAN measurement - aborting.");
221
+        break;
222
+      }
223
+      else
224
+        SERIAL_ECHOLNPAIR_F("Measured: ", measured_z);
225
+
226
+      if (target_bed == temp_comp.cali_info_init[TSI_BED].start_temp)
227
+        temp_comp.prepare_new_calibration(measured_z);
228
+      else
229
+        temp_comp.push_back_new_measurement(TSI_BED, measured_z);
230
+
231
+      target_bed += temp_comp.cali_info_init[TSI_BED].temp_res;
232
+      if (target_bed > temp_comp.max_bed_temp) break;
233
+    }
234
+
235
+    SERIAL_ECHOLNPAIR("Retrieved measurements: ", temp_comp.get_index());
236
+    if (temp_comp.finish_calibration(TSI_BED))
237
+      SERIAL_ECHOLNPGM("Successfully calibrated bed.");
238
+    else
239
+      SERIAL_ECHOLNPGM("!Failed to calibrated bed - reset calibration values.");
240
+
241
+    // Cleanup
242
+    thermalManager.setTargetBed(0);
243
+    #if HAS_LEVELING
244
+      set_bed_leveling_enabled(true);
245
+    #endif
246
+  } // do_bed_cal
247
+
248
+  /********************************************
249
+   * Calibrate probe temperature offsets
250
+   ********************************************/
251
+
252
+  if (do_probe_cal) {
253
+
254
+    // Park nozzle
255
+    do_blocking_move_to(ProbeTempComp::park_point.x, ProbeTempComp::park_point.y, ProbeTempComp::park_point.z);
256
+
257
+    // Initialize temperatures
258
+    uint16_t target_bed = temp_comp.probe_calib_bed_temp,
259
+             target_probe = temp_comp.cali_info_init[TSI_BED].start_temp;
260
+    thermalManager.setTargetBed(target_bed);
261
+    SERIAL_ECHOLNPGM("Waiting for bed and probe temperature.");
262
+    while (fabs(thermalManager.degBed() - float(target_bed)) > 0.1f
263
+           || thermalManager.degProbe() > target_probe
264
+    ) {
265
+      idle(
266
+        #if ENABLED(ADVANCED_PAUSE_FEATURE)
267
+          true
268
+        #endif
269
+      );
270
+      const millis_t ms = millis();
271
+      if (ELAPSED(ms, next_temp_report)) {
272
+        thermalManager.print_heater_states(active_extruder);
273
+        next_temp_report = ms + 1000;
274
+      }
275
+    }
276
+
277
+    // Disable leveling so it won't mess with us
278
+    #if HAS_LEVELING
279
+      set_bed_leveling_enabled(false);
280
+    #endif
281
+
282
+    bool timeout = false;
283
+    while (true) {
284
+      // Move probe to probing point and wait for it to reach target temperature
285
+      destination.set(temp_comp.measure_point_x, temp_comp.measure_point_y, 0.5);
286
+      do_blocking_move_to(destination);
287
+
288
+      SERIAL_ECHOLNPAIR(
289
+        "Bed temp: ", target_bed,
290
+        "; Probe temp: ", target_probe,
291
+        "  Waiting for probe heating."
292
+      );
293
+
294
+      const millis_t probe_timeout_ms = millis() + 900UL * 1000UL;
295
+      while (thermalManager.degProbe() < target_probe) {
296
+        idle(
297
+          #if ENABLED(ADVANCED_PAUSE_FEATURE)
298
+            true
299
+          #endif
300
+        );
301
+        const millis_t ms = millis();
302
+        if (ELAPSED(ms, next_temp_report)) {
303
+          thermalManager.print_heater_states(active_extruder);
304
+          next_temp_report = ms + 1000;
305
+        }
306
+        if (ELAPSED(ms, probe_timeout_ms)) {
307
+          SERIAL_ECHOLNPGM("!Probe heating aborted due to timeout.");
308
+          timeout = true;
309
+          break;
310
+        }
311
+      }
312
+
313
+      if (timeout) break;
314
+
315
+      // Raise nozzle before probing
316
+      destination.z = 5.0;
317
+      do_blocking_move_to_z(destination.z);
318
+
319
+      // Do a single probe
320
+      remember_feedrate_scaling_off();
321
+      const float measured_z = probe_at_point(
322
+        destination.x + probe_offset.x,
323
+        destination.y + probe_offset.y,
324
+        PROBE_PT_NONE
325
+      );
326
+      restore_feedrate_and_scaling();
327
+
328
+      if (isnan(measured_z)) {
329
+        SERIAL_ECHOLNPGM("!Received NAN measurement - aborting.");
330
+        break;
331
+      }
332
+      else
333
+        SERIAL_ECHOLNPAIR_F("Measured: ", measured_z);
334
+
335
+      if (target_probe == temp_comp.cali_info_init[TSI_BED].start_temp)
336
+        temp_comp.prepare_new_calibration(measured_z);
337
+      else
338
+        temp_comp.push_back_new_measurement(TSI_PROBE, measured_z);
339
+
340
+      target_probe += temp_comp.cali_info_init[TSI_BED].temp_res;
341
+      if (target_probe > temp_comp.cali_info_init[TSI_BED].end_temp) break;
342
+    }
343
+
344
+    SERIAL_ECHOLNPAIR("Retrieved measurements: ", temp_comp.get_index());
345
+    if (temp_comp.finish_calibration(TSI_PROBE))
346
+      SERIAL_ECHOLNPGM("Successfully calibrated probe.");
347
+    else
348
+      SERIAL_ECHOLNPGM("!Failed to calibrated probe.");
349
+
350
+    // Cleanup
351
+    thermalManager.setTargetBed(0);
352
+    #if HAS_LEVELING
353
+      set_bed_leveling_enabled(true);
354
+    #endif
355
+
356
+    SERIAL_ECHOLNPGM("Final compensation values:");
357
+    temp_comp.print_offsets();
358
+  } // do_probe_cal
359
+}
360
+
361
+/**
362
+ * M871: Report / reset temperature compensation offsets.
363
+ *       Note: This does not affect values in EEPROM until M500.
364
+ *
365
+ *   M871 [ R | B | P | E ]
366
+ *
367
+ *    No Parameters - Print current offset values.
368
+ *
369
+ * Select only one of these flags:
370
+ *    R - Reset all offsets to zero (i.e., disable compensation).
371
+ *    B - Manually set offset for bed
372
+ *    P - Manually set offset for probe
373
+ *    E - Manually set offset for extruder
374
+ *
375
+ * With B, P, or E:
376
+ *    I[index] - Index in the array
377
+ *    V[value] - Adjustment in µm
378
+ */
379
+void GcodeSuite::M871() {
380
+
381
+  if (parser.seen('R')) {
382
+    // Reset z-probe offsets to factory defaults
383
+    temp_comp.clear_all_offsets();
384
+    SERIAL_ECHOLNPGM("Offsets reset to default.");
385
+  }
386
+  else if (parser.seen("BPE")) {
387
+    if (!parser.seenval('V')) return;
388
+    const int16_t val = parser.value_int();
389
+    if (!parser.seenval('I')) return;
390
+    const int16_t idx = parser.value_int();
391
+    const TempSensorID mod = (parser.seen('B') ? TSI_BED :
392
+                              #if ENABLED(USE_TEMP_EXT_COMPENSATION)
393
+                                parser.seen('E') ? TSI_EXT :
394
+                              #endif
395
+                              TSI_PROBE
396
+                              );
397
+    if (idx > 0 && temp_comp.set_offset(mod, idx - 1, val))
398
+      SERIAL_ECHOLNPAIR("Set value: ", val);
399
+    else
400
+      SERIAL_ECHOLNPGM("!Invalid index. Failed to set value (note: value at index 0 is constant).");
401
+
402
+  }
403
+  else // Print current Z-probe adjustments. Note: Values in EEPROM might differ.
404
+    temp_comp.print_offsets();
405
+}
406
+
407
+#endif // PROBE_TEMP_COMPENSATION

+ 8
- 0
Marlin/src/gcode/gcode.cpp View File

@@ -323,6 +323,10 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
323 323
         case 59: G59(); break;
324 324
       #endif
325 325
 
326
+      #if ENABLED(PROBE_TEMP_COMPENSATION)
327
+        case 76: G76(); break;                                    // G76: Calibrate first layer compensation values
328
+      #endif
329
+
326 330
       #if ENABLED(GCODE_MOTION_MODES)
327 331
         case 80: G80(); break;                                    // G80: Reset the current motion mode
328 332
       #endif
@@ -753,6 +757,10 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
753 757
         M810_819(); break;                                        // M810-M819: Define/execute G-code macro
754 758
       #endif
755 759
 
760
+      #if ENABLED(PROBE_TEMP_COMPENSATION)
761
+        case 871: M871(); break;                                  // M871: Print/reset/clear first layer temperature offset values
762
+      #endif
763
+
756 764
       #if ENABLED(LIN_ADVANCE)
757 765
         case 900: M900(); break;                                  // M900: Set advance K factor.
758 766
       #endif

+ 10
- 0
Marlin/src/gcode/gcode.h View File

@@ -67,6 +67,7 @@
67 67
  * G34  - Z Stepper automatic alignment using probe: I<iterations> T<accuracy> A<amplification> (Requires Z_STEPPER_AUTO_ALIGN)
68 68
  * G38  - Probe in any direction using the Z_MIN_PROBE (Requires G38_PROBE_TARGET)
69 69
  * G42  - Coordinated move to a mesh point (Requires MESH_BED_LEVELING, AUTO_BED_LEVELING_BLINEAR, or AUTO_BED_LEVELING_UBL)
70
+ * G76  - Calibrate first layer temperature offsets. (Requires PROBE_TEMP_COMPENSATION)
70 71
  * G80  - Cancel current motion mode (Requires GCODE_MOTION_MODES)
71 72
  * G90  - Use Absolute Coordinates
72 73
  * G91  - Use Relative Coordinates
@@ -243,6 +244,7 @@
243 244
  * M867 - Enable/disable or toggle error correction for position encoder modules.
244 245
  * M868 - Report or set position encoder module error correction threshold.
245 246
  * M869 - Report position encoder module error.
247
+ * M871 - Print/reset/clear first layer temperature offset values. (Requires PROBE_TEMP_COMPENSATION)
246 248
  * M876 - Handle Prompt Response. (Requires HOST_PROMPT_SUPPORT and not EMERGENCY_PARSER)
247 249
  * M900 - Get or Set Linear Advance K-factor. (Requires LIN_ADVANCE)
248 250
  * M906 - Set or get motor current in milliamps using axis codes X, Y, Z, E. Report values if no axis codes given. (Requires at least one _DRIVER_TYPE defined as TMC2130/2160/5130/5160/2208/2209/2660 or L6470)
@@ -464,6 +466,10 @@ private:
464 466
     static void G59();
465 467
   #endif
466 468
 
469
+  #if ENABLED(PROBE_TEMP_COMPENSATION)
470
+    static void G76();
471
+  #endif
472
+
467 473
   #if ENABLED(GCODE_MOTION_MODES)
468 474
     static void G80();
469 475
   #endif
@@ -874,6 +880,10 @@ private:
874 880
     FORCE_INLINE static void M869() { I2CPEM.M869(); }
875 881
   #endif
876 882
 
883
+  #if ENABLED(PROBE_TEMP_COMPENSATION)
884
+    static void M871();
885
+  #endif
886
+
877 887
   #if ENABLED(LIN_ADVANCE)
878 888
     static void M900();
879 889
   #endif

+ 47
- 27
Marlin/src/inc/Conditionals_post.h View File

@@ -341,7 +341,7 @@
341 341
  * Temp Sensor defines
342 342
  */
343 343
 
344
-#define ANY_TEMP_SENSOR_IS(n) (TEMP_SENSOR_0 == (n) || TEMP_SENSOR_1 == (n) || TEMP_SENSOR_2 == (n) || TEMP_SENSOR_3 == (n) || TEMP_SENSOR_4 == (n) || TEMP_SENSOR_5 == (n) || TEMP_SENSOR_BED == (n) || TEMP_SENSOR_CHAMBER == (n))
344
+#define ANY_TEMP_SENSOR_IS(n) (TEMP_SENSOR_0 == (n) || TEMP_SENSOR_1 == (n) || TEMP_SENSOR_2 == (n) || TEMP_SENSOR_3 == (n) || TEMP_SENSOR_4 == (n) || TEMP_SENSOR_5 == (n) || TEMP_SENSOR_BED == (n) || TEMP_SENSOR_PROBE == (n) || TEMP_SENSOR_CHAMBER == (n))
345 345
 
346 346
 #define HAS_USER_THERMISTORS ANY_TEMP_SENSOR_IS(1000)
347 347
 
@@ -521,7 +521,25 @@
521 521
   #undef CHAMBER_MAXTEMP
522 522
 #endif
523 523
 
524
-#define HOTEND_USES_THERMISTOR ANY(HEATER_0_USES_THERMISTOR, HEATER_1_USES_THERMISTOR, HEATER_2_USES_THERMISTOR, HEATER_3_USES_THERMISTOR, HEATER_4_USES_THERMISTOR)
524
+#if TEMP_SENSOR_PROBE == -4
525
+  #define HEATER_PROBE_USES_AD8495
526
+#elif TEMP_SENSOR_PROBE == -3
527
+  #error "MAX31855 Thermocouples (-3) not supported for TEMP_SENSOR_PROBE."
528
+#elif TEMP_SENSOR_PROBE == -2
529
+  #error "MAX6675 Thermocouples (-2) not supported for TEMP_SENSOR_PROBE."
530
+#elif TEMP_SENSOR_PROBE == -1
531
+  #define HEATER_PROBE_USES_AD595
532
+#elif TEMP_SENSOR_PROBE > 0
533
+  #define THERMISTORPROBE TEMP_SENSOR_PROBE
534
+  #define PROBE_USES_THERMISTOR
535
+  #if TEMP_SENSOR_PROBE == 1000
536
+    #define PROBE_USER_THERMISTOR
537
+  #endif
538
+#endif
539
+
540
+#define HOTEND_USES_THERMISTOR ANY( \
541
+  HEATER_0_USES_THERMISTOR, HEATER_1_USES_THERMISTOR, HEATER_2_USES_THERMISTOR, \
542
+  HEATER_3_USES_THERMISTOR, HEATER_4_USES_THERMISTOR, HEATER_5_USES_THERMISTOR)
525 543
 
526 544
 /**
527 545
  * Default hotend offsets, if not defined
@@ -1014,19 +1032,20 @@
1014 1032
 
1015 1033
 // ADC Temp Sensors (Thermistor or Thermocouple with amplifier ADC interface)
1016 1034
 #define HAS_ADC_TEST(P) (PIN_EXISTS(TEMP_##P) && TEMP_SENSOR_##P != 0 && DISABLED(HEATER_##P##_USES_MAX6675))
1017
-#define HAS_TEMP_ADC_0 HAS_ADC_TEST(0)
1018
-#define HAS_TEMP_ADC_1 HAS_ADC_TEST(1)
1019
-#define HAS_TEMP_ADC_2 HAS_ADC_TEST(2)
1020
-#define HAS_TEMP_ADC_3 HAS_ADC_TEST(3)
1021
-#define HAS_TEMP_ADC_4 HAS_ADC_TEST(4)
1022
-#define HAS_TEMP_ADC_5 HAS_ADC_TEST(5)
1023
-#define HAS_TEMP_ADC_BED HAS_ADC_TEST(BED)
1024
-#define HAS_TEMP_ADC_CHAMBER HAS_ADC_TEST(CHAMBER)
1025
-
1026
-#define HAS_TEMP_HOTEND (HOTENDS > 0 && (HAS_TEMP_ADC_0 || ENABLED(HEATER_0_USES_MAX6675)))
1027
-#define HAS_TEMP_BED HAS_TEMP_ADC_BED
1028
-#define HAS_TEMP_CHAMBER HAS_TEMP_ADC_CHAMBER
1029
-#define HAS_HEATED_CHAMBER (HAS_TEMP_CHAMBER && PIN_EXISTS(HEATER_CHAMBER))
1035
+#define HAS_TEMP_ADC_0        HAS_ADC_TEST(0)
1036
+#define HAS_TEMP_ADC_1        HAS_ADC_TEST(1)
1037
+#define HAS_TEMP_ADC_2        HAS_ADC_TEST(2)
1038
+#define HAS_TEMP_ADC_3        HAS_ADC_TEST(3)
1039
+#define HAS_TEMP_ADC_4        HAS_ADC_TEST(4)
1040
+#define HAS_TEMP_ADC_5        HAS_ADC_TEST(5)
1041
+#define HAS_TEMP_ADC_BED      HAS_ADC_TEST(BED)
1042
+#define HAS_TEMP_ADC_PROBE    HAS_ADC_TEST(PROBE)
1043
+#define HAS_TEMP_ADC_CHAMBER  HAS_ADC_TEST(CHAMBER)
1044
+
1045
+#define HAS_TEMP_HOTEND   ((HAS_TEMP_ADC_0 || ENABLED(HEATER_0_USES_MAX6675)) && HOTENDS)
1046
+#define HAS_TEMP_BED        HAS_TEMP_ADC_BED
1047
+#define HAS_TEMP_PROBE      HAS_TEMP_ADC_PROBE
1048
+#define HAS_TEMP_CHAMBER    HAS_TEMP_ADC_CHAMBER
1030 1049
 
1031 1050
 #if ENABLED(JOYSTICK)
1032 1051
   #define HAS_JOY_ADC_X  PIN_EXISTS(JOY_X)
@@ -1036,22 +1055,19 @@
1036 1055
 #endif
1037 1056
 
1038 1057
 // Heaters
1039
-#define HAS_HEATER_0 (PIN_EXISTS(HEATER_0))
1040
-#define HAS_HEATER_1 (PIN_EXISTS(HEATER_1))
1041
-#define HAS_HEATER_2 (PIN_EXISTS(HEATER_2))
1042
-#define HAS_HEATER_3 (PIN_EXISTS(HEATER_3))
1043
-#define HAS_HEATER_4 (PIN_EXISTS(HEATER_4))
1044
-#define HAS_HEATER_5 (PIN_EXISTS(HEATER_5))
1045
-#define HAS_HEATER_BED (PIN_EXISTS(HEATER_BED))
1058
+#define HAS_HEATER_0    (PIN_EXISTS(HEATER_0))
1059
+#define HAS_HEATER_1    (PIN_EXISTS(HEATER_1))
1060
+#define HAS_HEATER_2    (PIN_EXISTS(HEATER_2))
1061
+#define HAS_HEATER_3    (PIN_EXISTS(HEATER_3))
1062
+#define HAS_HEATER_4    (PIN_EXISTS(HEATER_4))
1063
+#define HAS_HEATER_5    (PIN_EXISTS(HEATER_5))
1064
+#define HAS_HEATER_BED  (PIN_EXISTS(HEATER_BED))
1046 1065
 
1047 1066
 // Shorthand for common combinations
1048 1067
 #define HAS_HEATED_BED (HAS_TEMP_BED && HAS_HEATER_BED)
1049 1068
 #define BED_OR_CHAMBER (HAS_HEATED_BED || HAS_TEMP_CHAMBER)
1050
-#define HAS_TEMP_SENSOR (HAS_TEMP_HOTEND || BED_OR_CHAMBER)
1051
-
1052
-#if !HAS_TEMP_SENSOR
1053
-  #undef AUTO_REPORT_TEMPERATURES
1054
-#endif
1069
+#define HAS_TEMP_SENSOR (HAS_TEMP_HOTEND || BED_OR_CHAMBER || HAS_TEMP_PROBE)
1070
+#define HAS_HEATED_CHAMBER (HAS_TEMP_CHAMBER && PIN_EXISTS(HEATER_CHAMBER))
1055 1071
 
1056 1072
 // PID heating
1057 1073
 #if !HAS_HEATED_BED
@@ -1081,6 +1097,10 @@
1081 1097
   #define AUTO_CHAMBER_IS_E (_FANOVERLAP(CHAMBER,0) || _FANOVERLAP(CHAMBER,1) || _FANOVERLAP(CHAMBER,2) || _FANOVERLAP(CHAMBER,3) || _FANOVERLAP(CHAMBER,4) || _FANOVERLAP(CHAMBER,5))
1082 1098
 #endif
1083 1099
 
1100
+#if !HAS_TEMP_SENSOR
1101
+  #undef AUTO_REPORT_TEMPERATURES
1102
+#endif
1103
+
1084 1104
 #if !HAS_AUTO_CHAMBER_FAN || AUTO_CHAMBER_IS_E
1085 1105
   #undef AUTO_POWER_CHAMBER_FAN
1086 1106
 #endif

+ 10
- 0
Marlin/src/inc/SanityCheck.h View File

@@ -1571,6 +1571,16 @@ static_assert(Y_MAX_LENGTH >= Y_BED_SIZE, "Movement bounds (Y_MIN_POS, Y_MAX_POS
1571 1571
   #error "TEMP_SENSOR_5 shouldn't be set with only 1 HOTEND."
1572 1572
 #endif
1573 1573
 
1574
+#if TEMP_SENSOR_PROBE
1575
+  #if !PIN_EXISTS(TEMP_PROBE)
1576
+    #error "TEMP_SENSOR_PROBE requires TEMP_PROBE_PIN."
1577
+  #elif !HAS_TEMP_ADC_PROBE
1578
+    #error "TEMP_PROBE_PIN must be an ADC pin."
1579
+  #elif !ENABLED(FIX_MOUNTED_PROBE)
1580
+    #error "TEMP_SENSOR_PROBE shouldn't be set without FIX_MOUNTED_PROBE."
1581
+  #endif
1582
+#endif
1583
+
1574 1584
 #if ENABLED(TEMP_SENSOR_1_AS_REDUNDANT) && TEMP_SENSOR_1 == 0
1575 1585
   #error "TEMP_SENSOR_1 is required with TEMP_SENSOR_1_AS_REDUNDANT."
1576 1586
 #endif

+ 43
- 0
Marlin/src/module/configuration_store.cpp View File

@@ -114,6 +114,10 @@
114 114
   #include "../feature/tmc_util.h"
115 115
 #endif
116 116
 
117
+#if ENABLED(PROBE_TEMP_COMPENSATION)
118
+  #include "../feature/probe_temp_compensation.h"
119
+#endif
120
+
117 121
 #pragma pack(push, 1) // No padding between variables
118 122
 
119 123
 typedef struct { uint16_t X, Y, Z, X2, Y2, Z2, Z3, E0, E1, E2, E3, E4, E5; } tmc_stepper_current_t;
@@ -213,6 +217,18 @@ typedef struct SettingsDataStruct {
213 217
   uint16_t servo_angles[EEPROM_NUM_SERVOS][2];          // M281 P L U
214 218
 
215 219
   //
220
+  // Temperature first layer compensation values
221
+  //
222
+  #if ENABLED(PROBE_TEMP_COMPENSATION)
223
+    int16_t z_offsets_probe[COUNT(temp_comp.z_offsets_probe)], // M871 P I V
224
+            z_offsets_bed[COUNT(temp_comp.z_offsets_bed)]      // M871 B I V
225
+            #if ENABLED(USE_TEMP_EXT_COMPENSATION)
226
+              , z_offsets_ext[COUNT(temp_comp.z_offsets_ext)]  // M871 E I V
227
+            #endif
228
+          ;
229
+  #endif
230
+
231
+  //
216 232
   // BLTOUCH
217 233
   //
218 234
   bool bltouch_last_written_mode;
@@ -700,6 +716,19 @@ void MarlinSettings::postprocess() {
700 716
     }
701 717
 
702 718
     //
719
+    // Thermal first layer compensation values
720
+    //
721
+    #if ENABLED(PROBE_TEMP_COMPENSATION)
722
+      EEPROM_WRITE(temp_comp.z_offsets_probe);
723
+      EEPROM_WRITE(temp_comp.z_offsets_bed);
724
+      #if ENABLED(USE_TEMP_EXT_COMPENSATION)
725
+        EEPROM_WRITE(temp_comp.z_offsets_ext);
726
+      #endif
727
+    #else
728
+      // No placeholder data for this feature
729
+    #endif
730
+
731
+    //
703 732
     // BLTOUCH
704 733
     //
705 734
     {
@@ -1515,6 +1544,20 @@ void MarlinSettings::postprocess() {
1515 1544
       }
1516 1545
 
1517 1546
       //
1547
+      // Thermal first layer compensation values
1548
+      //
1549
+      #if ENABLED(PROBE_TEMP_COMPENSATION)
1550
+        EEPROM_READ(temp_comp.z_offsets_probe);
1551
+        EEPROM_READ(temp_comp.z_offsets_bed);
1552
+        #if ENABLED(USE_TEMP_EXT_COMPENSATION)
1553
+          EEPROM_READ(temp_comp.z_offsets_ext);
1554
+        #endif
1555
+        temp_comp.reset_index();
1556
+      #else
1557
+        // No placeholder data for this feature
1558
+      #endif
1559
+
1560
+      //
1518 1561
       // BLTOUCH
1519 1562
       //
1520 1563
       {

+ 61
- 6
Marlin/src/module/temperature.cpp View File

@@ -262,6 +262,10 @@ Temperature thermalManager;
262 262
   #endif // HAS_HEATED_CHAMBER
263 263
 #endif // HAS_TEMP_CHAMBER
264 264
 
265
+#if HAS_TEMP_PROBE
266
+  probe_info_t Temperature::temp_probe; // = { 0 }
267
+#endif
268
+
265 269
 // Initialized by settings.load()
266 270
 #if ENABLED(PIDTEMP)
267 271
   //hotend_pid_t Temperature::pid[HOTENDS];
@@ -654,11 +658,11 @@ int16_t Temperature::getHeaterPower(const heater_ind_t heater_id) {
654 658
       case H_CHAMBER: return temp_chamber.soft_pwm_amount;
655 659
     #endif
656 660
     default:
657
-      #if HOTENDS
658
-        return temp_hotend[heater_id].soft_pwm_amount;
659
-      #else
660
-        return 0;
661
-      #endif
661
+      return (0
662
+        #if HOTENDS
663
+          + temp_hotend[heater_id].soft_pwm_amount
664
+        #endif
665
+      );
662 666
   }
663 667
 }
664 668
 
@@ -1398,7 +1402,7 @@ void Temperature::manage_heater() {
1398 1402
         SERIAL_ECHO((int)e);
1399 1403
         SERIAL_ECHOLNPGM(MSG_INVALID_EXTRUDER_NUM);
1400 1404
         kill();
1401
-        return 0.0;
1405
+        return 0;
1402 1406
       }
1403 1407
 
1404 1408
     switch (e) {
@@ -1498,6 +1502,7 @@ void Temperature::manage_heater() {
1498 1502
     #elif ENABLED(HEATER_BED_USES_AD8495)
1499 1503
       return TEMP_AD8495(raw);
1500 1504
     #else
1505
+      UNUSED(raw);
1501 1506
       return 0;
1502 1507
     #endif
1503 1508
   }
@@ -1516,11 +1521,31 @@ void Temperature::manage_heater() {
1516 1521
     #elif ENABLED(HEATER_CHAMBER_USES_AD8495)
1517 1522
       return TEMP_AD8495(raw);
1518 1523
     #else
1524
+      UNUSED(raw);
1519 1525
       return 0;
1520 1526
     #endif
1521 1527
   }
1522 1528
 #endif // HAS_TEMP_CHAMBER
1523 1529
 
1530
+#if HAS_TEMP_PROBE
1531
+  // Derived from RepRap FiveD extruder::getTemperature()
1532
+  // For probe temperature measurement.
1533
+  float Temperature::analog_to_celsius_probe(const int raw) {
1534
+    #if ENABLED(PROBE_USER_THERMISTOR)
1535
+      return user_thermistor_to_deg_c(CTI_PROBE, raw);
1536
+    #elif ENABLED(PROBE_USES_THERMISTOR)
1537
+      SCAN_THERMISTOR_TABLE(PROBE_TEMPTABLE, PROBE_TEMPTABLE_LEN);
1538
+    #elif ENABLED(PROBE_USES_AD595)
1539
+      return TEMP_AD595(raw);
1540
+    #elif ENABLED(PROBE_USES_AD8495)
1541
+      return TEMP_AD8495(raw);
1542
+    #else
1543
+      UNUSED(raw);
1544
+      return 0;
1545
+    #endif
1546
+  }
1547
+#endif // HAS_TEMP_PROBE
1548
+
1524 1549
 /**
1525 1550
  * Get the raw values into the actual temperatures.
1526 1551
  * The raw values are created in interrupt context,
@@ -1543,6 +1568,9 @@ void Temperature::updateTemperaturesFromRawValues() {
1543 1568
   #if HAS_TEMP_CHAMBER
1544 1569
     temp_chamber.celsius = analog_to_celsius_chamber(temp_chamber.raw);
1545 1570
   #endif
1571
+  #if HAS_TEMP_PROBE
1572
+    temp_probe.celsius = analog_to_celsius_probe(temp_probe.raw);
1573
+  #endif
1546 1574
   #if ENABLED(TEMP_SENSOR_1_AS_REDUNDANT)
1547 1575
     redundant_temperature = analog_to_celsius_hotend(redundant_temperature_raw, 1);
1548 1576
   #endif
@@ -1721,6 +1749,9 @@ void Temperature::init() {
1721 1749
   #if HAS_TEMP_CHAMBER
1722 1750
     HAL_ANALOG_SELECT(TEMP_CHAMBER_PIN);
1723 1751
   #endif
1752
+  #if HAS_TEMP_PROBE
1753
+    HAL_ANALOG_SELECT(TEMP_PROBE_PIN);
1754
+  #endif
1724 1755
   #if ENABLED(FILAMENT_WIDTH_SENSOR)
1725 1756
     HAL_ANALOG_SELECT(FILWIDTH_PIN);
1726 1757
   #endif
@@ -2215,6 +2246,10 @@ void Temperature::set_current_temp_raw() {
2215 2246
     temp_chamber.update();
2216 2247
   #endif
2217 2248
 
2249
+  #if HAS_TEMP_PROBE
2250
+    temp_probe.update();
2251
+  #endif
2252
+
2218 2253
   #if HAS_JOY_ADC_X
2219 2254
     joystick.x.update();
2220 2255
   #endif
@@ -2253,6 +2288,10 @@ void Temperature::readings_ready() {
2253 2288
     temp_chamber.reset();
2254 2289
   #endif
2255 2290
 
2291
+  #if HAS_TEMP_PROBE
2292
+    temp_probe.reset();
2293
+  #endif
2294
+
2256 2295
   #if HAS_JOY_ADC_X
2257 2296
     joystick.x.reset();
2258 2297
   #endif
@@ -2661,6 +2700,11 @@ void Temperature::tick() {
2661 2700
       case MeasureTemp_CHAMBER: ACCUMULATE_ADC(temp_chamber); break;
2662 2701
     #endif
2663 2702
 
2703
+    #if HAS_TEMP_PROBE
2704
+      case PrepareTemp_PROBE: HAL_START_ADC(TEMP_PROBE_PIN); break;
2705
+      case MeasureTemp_PROBE: ACCUMULATE_ADC(temp_probe); break;
2706
+    #endif
2707
+
2664 2708
     #if HAS_TEMP_ADC_1
2665 2709
       case PrepareTemp_1: HAL_START_ADC(TEMP_1_PIN); break;
2666 2710
       case MeasureTemp_1: ACCUMULATE_ADC(temp_hotend[1]); break;
@@ -2774,6 +2818,9 @@ void Temperature::tick() {
2774 2818
       #if HAS_TEMP_CHAMBER
2775 2819
         case H_CHAMBER: k = 'C'; break;
2776 2820
       #endif
2821
+      #if HAS_TEMP_PROBE
2822
+        case H_PROBE: k = 'P'; break;
2823
+      #endif
2777 2824
       #if HAS_TEMP_HOTEND
2778 2825
         default: k = 'T'; break;
2779 2826
         #if HAS_HEATED_BED
@@ -2842,6 +2889,14 @@ void Temperature::tick() {
2842 2889
         , H_CHAMBER
2843 2890
       );
2844 2891
     #endif // HAS_TEMP_CHAMBER
2892
+    #if HAS_TEMP_PROBE
2893
+      print_heater_state(degProbe(), 0
2894
+        #if ENABLED(SHOW_TEMP_ADC_VALUES)
2895
+          , rawProbeTemp()
2896
+        #endif
2897
+        , H_PROBE
2898
+      );
2899
+    #endif // HAS_TEMP_PROBE
2845 2900
     #if HOTENDS > 1
2846 2901
       HOTEND_LOOP() print_heater_state(degHotend(e), degTargetHotend(e)
2847 2902
         #if ENABLED(SHOW_TEMP_ADC_VALUES)

+ 30
- 5
Marlin/src/module/temperature.h View File

@@ -47,8 +47,8 @@
47 47
 
48 48
 // Identifiers for other heaters
49 49
 typedef enum : int8_t {
50
-  INDEX_NONE = -4,
51
-  H_REDUNDANT, H_CHAMBER, H_BED,
50
+  INDEX_NONE = -5,
51
+  H_PROBE, H_REDUNDANT, H_CHAMBER, H_BED,
52 52
   H_E0, H_E1, H_E2, H_E3, H_E4, H_E5
53 53
 } heater_ind_t;
54 54
 
@@ -114,6 +114,9 @@ enum ADCSensorState : char {
114 114
   #if HAS_TEMP_CHAMBER
115 115
     PrepareTemp_CHAMBER, MeasureTemp_CHAMBER,
116 116
   #endif
117
+  #if HAS_TEMP_PROBE
118
+    PrepareTemp_PROBE, MeasureTemp_PROBE,
119
+  #endif
117 120
   #if HAS_TEMP_ADC_1
118 121
     PrepareTemp_1, MeasureTemp_1,
119 122
   #endif
@@ -202,6 +205,9 @@ struct PIDHeaterInfo : public HeaterInfo {
202 205
     typedef heater_info_t bed_info_t;
203 206
   #endif
204 207
 #endif
208
+#if HAS_TEMP_PROBE
209
+  typedef temp_info_t probe_info_t;
210
+#endif
205 211
 #if HAS_HEATED_CHAMBER
206 212
   typedef heater_info_t chamber_info_t;
207 213
 #elif HAS_TEMP_CHAMBER
@@ -258,6 +264,9 @@ typedef struct { int16_t raw_min, raw_max, mintemp, maxtemp; } temp_range_t;
258 264
     #if ENABLED(HEATER_BED_USER_THERMISTOR)
259 265
       CTI_BED,
260 266
     #endif
267
+    #if ENABLED(HEATER_PROBE_USER_THERMISTOR)
268
+      CTI_PROBE,
269
+    #endif
261 270
     #if ENABLED(HEATER_CHAMBER_USER_THERMISTOR)
262 271
       CTI_CHAMBER,
263 272
     #endif
@@ -289,11 +298,12 @@ class Temperature {
289 298
       #endif
290 299
       static hotend_info_t temp_hotend[HOTEND_TEMPS];
291 300
     #endif
292
-
293 301
     #if HAS_HEATED_BED
294 302
       static bed_info_t temp_bed;
295 303
     #endif
296
-
304
+    #if HAS_TEMP_PROBE
305
+      static probe_info_t temp_probe;
306
+    #endif
297 307
     #if HAS_TEMP_CHAMBER
298 308
       static chamber_info_t temp_chamber;
299 309
     #endif
@@ -301,7 +311,6 @@ class Temperature {
301 311
     #if ENABLED(AUTO_POWER_E_FANS)
302 312
       static uint8_t autofan_speed[HOTENDS];
303 313
     #endif
304
-
305 314
     #if ENABLED(AUTO_POWER_CHAMBER_FAN)
306 315
       static uint8_t chamberfan_speed;
307 316
     #endif
@@ -467,6 +476,9 @@ class Temperature {
467 476
     #if HAS_HEATED_BED
468 477
       static float analog_to_celsius_bed(const int raw);
469 478
     #endif
479
+    #if HAS_TEMP_PROBE
480
+      static float analog_to_celsius_probe(const int raw);
481
+    #endif
470 482
     #if HAS_TEMP_CHAMBER
471 483
       static float analog_to_celsius_chamber(const int raw);
472 484
     #endif
@@ -662,6 +674,19 @@ class Temperature {
662 674
 
663 675
     #endif // HAS_HEATED_BED
664 676
 
677
+    #if HAS_TEMP_PROBE
678
+      #if ENABLED(SHOW_TEMP_ADC_VALUES)
679
+        FORCE_INLINE static int16_t rawProbeTemp()    { return temp_probe.raw; }
680
+      #endif
681
+      FORCE_INLINE static float degProbe()            { return temp_probe.celsius; }
682
+    #endif
683
+
684
+    #if WATCH_PROBE
685
+      static void start_watching_probe();
686
+    #else
687
+      static inline void start_watching_probe() {}
688
+    #endif
689
+
665 690
     #if HAS_TEMP_CHAMBER
666 691
       #if ENABLED(SHOW_TEMP_ADC_VALUES)
667 692
         FORCE_INLINE static int16_t rawChamberTemp()    { return temp_chamber.raw; }

+ 9
- 2
Marlin/src/module/thermistor/thermistors.h View File

@@ -39,7 +39,7 @@
39 39
 
40 40
 #define OV(N) int16_t((N) * (OVERSAMPLENR) * (THERMISTOR_TABLE_SCALE))
41 41
 
42
-#define ANY_THERMISTOR_IS(n) (THERMISTOR_HEATER_0 == n || THERMISTOR_HEATER_1 == n || THERMISTOR_HEATER_2 == n || THERMISTOR_HEATER_3 == n || THERMISTOR_HEATER_4 == n || THERMISTOR_HEATER_5 == n || THERMISTORBED == n || THERMISTORCHAMBER == n)
42
+#define ANY_THERMISTOR_IS(n) (THERMISTOR_HEATER_0 == n || THERMISTOR_HEATER_1 == n || THERMISTOR_HEATER_2 == n || THERMISTOR_HEATER_3 == n || THERMISTOR_HEATER_4 == n || THERMISTOR_HEATER_5 == n || THERMISTORBED == n || THERMISTORCHAMBER == n || THERMISTORPROBE == n)
43 43
 
44 44
 // Pt1000 and Pt100 handling
45 45
 //
@@ -249,13 +249,20 @@
249 249
 #else
250 250
   #define CHAMBER_TEMPTABLE_LEN 0
251 251
 #endif
252
+#ifdef THERMISTORPROBE
253
+  #define PROBE_TEMPTABLE TT_NAME(THERMISTORPROBE)
254
+  #define PROBE_TEMPTABLE_LEN COUNT(PROBE_TEMPTABLE)
255
+#else
256
+  #define PROBE_TEMPTABLE_LEN 0
257
+#endif
252 258
 
253 259
 // The SCAN_THERMISTOR_TABLE macro needs alteration?
254 260
 static_assert(
255 261
      HEATER_0_TEMPTABLE_LEN < 256 && HEATER_1_TEMPTABLE_LEN < 256
256 262
   && HEATER_2_TEMPTABLE_LEN < 256 && HEATER_3_TEMPTABLE_LEN < 256
257 263
   && HEATER_4_TEMPTABLE_LEN < 256 && HEATER_5_TEMPTABLE_LEN < 256
258
-  &&      BED_TEMPTABLE_LEN < 256 &&  CHAMBER_TEMPTABLE_LEN < 256,
264
+  &&      BED_TEMPTABLE_LEN < 256 &&  CHAMBER_TEMPTABLE_LEN < 256
265
+  &&    PROBE_TEMPTABLE_LEN < 256,
259 266
   "Temperature conversion tables over 255 entries need special consideration."
260 267
 );
261 268
 

+ 3
- 0
Marlin/src/pins/lpc1768/pins_BTT_SKR.h View File

@@ -58,6 +58,9 @@
58 58
 #ifndef TEMP_BED_PIN
59 59
   #define TEMP_BED_PIN     P0_23_A0   // A0 (T0) - (67) - TEMP_BED_PIN
60 60
 #endif
61
+#if HOTENDS == 1 && TEMP_SENSOR_PROBE
62
+  #define TEMP_PROBE_PIN   P0_25_A2   // TEMP_1_PIN
63
+#endif
61 64
 
62 65
 //
63 66
 // Heaters / Fans

+ 4
- 2
buildroot/share/tests/megaatmega2560-tests View File

@@ -23,6 +23,10 @@ opt_set EXTRUDERS 2
23 23
 opt_set TEMP_SENSOR_0 -2
24 24
 opt_set TEMP_SENSOR_1 1
25 25
 opt_set TEMP_SENSOR_BED 2
26
+opt_set TEMP_SENSOR_PROBE 1
27
+opt_add TEMP_PROBE_PIN 12
28
+opt_set TEMP_SENSOR_CHAMBER 3
29
+opt_add HEATER_CHAMBER_PIN 45
26 30
 opt_set GRID_MAX_POINTS_X 16
27 31
 opt_set FANMUX0_PIN 53
28 32
 opt_disable USE_WATCHDOG
@@ -42,8 +46,6 @@ opt_enable REPRAP_DISCOUNT_SMART_CONTROLLER LCD_PROGRESS_BAR LCD_PROGRESS_BAR_TE
42 46
            PSU_CONTROL AUTO_POWER_CONTROL POWER_LOSS_RECOVERY POWER_LOSS_PIN POWER_LOSS_STATE \
43 47
            SLOW_PWM_HEATERS THERMAL_PROTECTION_CHAMBER LIN_ADVANCE \
44 48
            HOST_ACTION_COMMANDS HOST_PROMPT_SUPPORT PINS_DEBUGGING MAX7219_DEBUG M114_DETAIL
45
-opt_set TEMP_SENSOR_CHAMBER 3
46
-opt_set HEATER_CHAMBER_PIN 45
47 49
 exec_test $1 $2 "RAMPS | EXTRUDERS 2 | CHAR LCD + SD | FIX Probe | ABL-Linear | Advanced Pause | PLR | LEDs ..."
48 50
 
49 51
 #

Loading…
Cancel
Save