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