disable compensation transform for G30 probe
[clinton/Smoothieware.git] / src / modules / tools / extruder / Extruder.cpp
1 /*
2 This file is part of Smoothie (http://smoothieware.org/). The motion control part is heavily based on Grbl (https://github.com/simen/grbl).
3 Smoothie 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.
4 Smoothie 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.
5 You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>.
6 */
7
8 #include "Extruder.h"
9
10 #include "libs/Module.h"
11 #include "libs/Kernel.h"
12
13 #include "modules/robot/Conveyor.h"
14 #include "modules/robot/Block.h"
15 #include "StepperMotor.h"
16 #include "SlowTicker.h"
17 #include "Config.h"
18 #include "StepperMotor.h"
19 #include "Robot.h"
20 #include "checksumm.h"
21 #include "ConfigValue.h"
22 #include "Gcode.h"
23 #include "libs/StreamOutput.h"
24 #include "PublicDataRequest.h"
25 #include "StreamOutputPool.h"
26 #include "ExtruderPublicAccess.h"
27
28 #include <mri.h>
29
30 // OLD config names for backwards compatibility, NOTE new configs will not be added here
31 #define extruder_module_enable_checksum CHECKSUM("extruder_module_enable")
32 #define extruder_steps_per_mm_checksum CHECKSUM("extruder_steps_per_mm")
33 #define extruder_filament_diameter_checksum CHECKSUM("extruder_filament_diameter")
34 #define extruder_acceleration_checksum CHECKSUM("extruder_acceleration")
35 #define extruder_step_pin_checksum CHECKSUM("extruder_step_pin")
36 #define extruder_dir_pin_checksum CHECKSUM("extruder_dir_pin")
37 #define extruder_en_pin_checksum CHECKSUM("extruder_en_pin")
38 #define extruder_max_speed_checksum CHECKSUM("extruder_max_speed")
39 #define extruder_default_feed_rate_checksum CHECKSUM("extruder_default_feed_rate")
40
41 // NEW config names
42
43 #define default_feed_rate_checksum CHECKSUM("default_feed_rate")
44 #define steps_per_mm_checksum CHECKSUM("steps_per_mm")
45 #define filament_diameter_checksum CHECKSUM("filament_diameter")
46 #define acceleration_checksum CHECKSUM("acceleration")
47 #define step_pin_checksum CHECKSUM("step_pin")
48 #define dir_pin_checksum CHECKSUM("dir_pin")
49 #define en_pin_checksum CHECKSUM("en_pin")
50 #define max_speed_checksum CHECKSUM("max_speed")
51 #define x_offset_checksum CHECKSUM("x_offset")
52 #define y_offset_checksum CHECKSUM("y_offset")
53 #define z_offset_checksum CHECKSUM("z_offset")
54
55 #define retract_length_checksum CHECKSUM("retract_length")
56 #define retract_feedrate_checksum CHECKSUM("retract_feedrate")
57 #define retract_recover_length_checksum CHECKSUM("retract_recover_length")
58 #define retract_recover_feedrate_checksum CHECKSUM("retract_recover_feedrate")
59 #define retract_zlift_length_checksum CHECKSUM("retract_zlift_length")
60 #define retract_zlift_feedrate_checksum CHECKSUM("retract_zlift_feedrate")
61
62 #define PI 3.14159265358979F
63
64
65 /*
66 As the actual motion is handled by the planner and the stepticker, this module just handles Extruder specific gcodes
67 and settings.
68 In a multi extruder setting it must be selected to be addressed. (using T0 T1 etc)
69 */
70
71 Extruder::Extruder( uint16_t config_identifier)
72 {
73 this->selected = false;
74 this->identifier = config_identifier;
75 this->retracted = false;
76 this->volumetric_multiplier = 1.0F;
77 this->extruder_multiplier = 1.0F;
78 this->stepper_motor = nullptr;
79 this->max_volumetric_rate = 0;
80 this->g92e0_detected = false;
81 memset(this->offset, 0, sizeof(this->offset));
82 }
83
84 Extruder::~Extruder()
85 {
86 delete stepper_motor;
87 }
88
89 void Extruder::on_module_loaded()
90 {
91 // Settings
92 this->config_load();
93
94 // We work on the same Block as Stepper, so we need to know when it gets a new one and drops one
95 this->register_for_event(ON_GCODE_RECEIVED);
96 this->register_for_event(ON_GET_PUBLIC_DATA);
97 this->register_for_event(ON_SET_PUBLIC_DATA);
98 }
99
100 // Get config
101 void Extruder::config_load()
102 {
103
104 Pin step_pin, dir_pin, en_pin;
105 step_pin.from_string( THEKERNEL->config->value(extruder_checksum, this->identifier, step_pin_checksum )->by_default("nc" )->as_string())->as_output();
106 dir_pin.from_string( THEKERNEL->config->value(extruder_checksum, this->identifier, dir_pin_checksum )->by_default("nc" )->as_string())->as_output();
107 en_pin.from_string( THEKERNEL->config->value(extruder_checksum, this->identifier, en_pin_checksum )->by_default("nc" )->as_string())->as_output();
108
109 float steps_per_millimeter = THEKERNEL->config->value(extruder_checksum, this->identifier, steps_per_mm_checksum)->by_default(1)->as_number();
110 float acceleration = THEKERNEL->config->value(extruder_checksum, this->identifier, acceleration_checksum)->by_default(1000)->as_number();
111
112 this->offset[X_AXIS] = THEKERNEL->config->value(extruder_checksum, this->identifier, x_offset_checksum )->by_default(0)->as_number();
113 this->offset[Y_AXIS] = THEKERNEL->config->value(extruder_checksum, this->identifier, y_offset_checksum )->by_default(0)->as_number();
114 this->offset[Z_AXIS] = THEKERNEL->config->value(extruder_checksum, this->identifier, z_offset_checksum )->by_default(0)->as_number();
115
116 this->filament_diameter = THEKERNEL->config->value(extruder_checksum, this->identifier, filament_diameter_checksum )->by_default(0)->as_number();
117 this->retract_length = THEKERNEL->config->value(extruder_checksum, this->identifier, retract_length_checksum)->by_default(3)->as_number();
118 this->retract_feedrate = THEKERNEL->config->value(extruder_checksum, this->identifier, retract_feedrate_checksum)->by_default(45)->as_number();
119 this->retract_recover_length = THEKERNEL->config->value(extruder_checksum, this->identifier, retract_recover_length_checksum)->by_default(0)->as_number();
120 this->retract_recover_feedrate = THEKERNEL->config->value(extruder_checksum, this->identifier, retract_recover_feedrate_checksum)->by_default(8)->as_number();
121 this->retract_zlift_length = THEKERNEL->config->value(extruder_checksum, this->identifier, retract_zlift_length_checksum)->by_default(0)->as_number();
122 this->retract_zlift_feedrate = THEKERNEL->config->value(extruder_checksum, this->identifier, retract_zlift_feedrate_checksum)->by_default(100 * 60)->as_number() / 60.0F; // mm/min
123
124 if(filament_diameter > 0.01F) {
125 this->volumetric_multiplier = 1.0F / (powf(this->filament_diameter / 2, 2) * PI);
126 }
127
128 // Stepper motor object for the extruder
129 stepper_motor = new StepperMotor(step_pin, dir_pin, en_pin);
130 motor_id = THEROBOT->register_motor(stepper_motor);
131
132 stepper_motor->set_max_rate(THEKERNEL->config->value(extruder_checksum, this->identifier, max_speed_checksum)->by_default(1000)->as_number());
133 stepper_motor->set_acceleration(acceleration);
134 stepper_motor->change_steps_per_mm(steps_per_millimeter);
135 stepper_motor->set_selected(false); // not selected by default
136 }
137
138 void Extruder::select()
139 {
140 selected = true;
141 stepper_motor->set_selected(true);
142 // set the function pointer to return the current scaling
143 THEROBOT->get_e_scale_fnc = [this](void) { return volumetric_multiplier * extruder_multiplier; };
144 }
145
146 void Extruder::deselect()
147 {
148 selected = false;
149 stepper_motor->set_selected(false);
150 THEROBOT->get_e_scale_fnc = nullptr;
151 }
152
153 void Extruder::on_get_public_data(void *argument)
154 {
155 PublicDataRequest *pdr = static_cast<PublicDataRequest *>(argument);
156
157 if(!pdr->starts_with(extruder_checksum)) return;
158
159 if(!this->selected) return;
160
161 // pointer to structure to return data to is provided
162 pad_extruder_t *e = static_cast<pad_extruder_t *>(pdr->get_data_ptr());
163 e->steps_per_mm = stepper_motor->get_steps_per_mm();
164 e->filament_diameter = this->filament_diameter;
165 e->flow_rate = this->extruder_multiplier;
166 e->accleration = stepper_motor->get_acceleration();
167 e->retract_length = this->retract_length;
168 e->current_position = stepper_motor->get_current_position();
169 pdr->set_taken();
170 }
171
172 void Extruder::on_set_public_data(void *argument)
173 {
174 PublicDataRequest *pdr = static_cast<PublicDataRequest *>(argument);
175
176 if(!pdr->starts_with(extruder_checksum)) return;
177
178 // handle extrude rates request from robot
179 if(pdr->second_element_is(target_checksum)) {
180 // disabled extruders do not reply NOTE only one enabled extruder supported
181 if(!this->selected) return;
182
183 float *d = static_cast<float *>(pdr->get_data_ptr());
184 float delta = d[0]; // the E passed in on Gcode is the delta volume in mm³
185 float isecs = d[1]; // inverted secs
186
187 // check against maximum speeds and return rate modifier
188 d[1] = check_max_speeds(delta, isecs);
189 pdr->set_taken();
190 return;
191 }
192
193 // save or restore extruder state
194 if(pdr->second_element_is(save_state_checksum)) {
195 save_position();
196 this->saved_selected = this->selected;
197 pdr->set_taken();
198
199 } else if(pdr->second_element_is(restore_state_checksum)) {
200 this->selected = this->saved_selected;
201 // NOTE this only gets called when the queue is empty so the milestones will be the same
202 restore_position();
203 pdr->set_taken();
204 }
205 }
206
207 void Extruder::save_position()
208 {
209 // we need to save these separately as they may have been scaled
210 this->saved_position = std::make_tuple(THEROBOT->get_axis_position(motor_id), stepper_motor->get_last_milestone(), stepper_motor->get_last_milestone_steps());
211 }
212
213 void Extruder::restore_position()
214 {
215 THEROBOT->reset_axis_position(std::get<0>(this->saved_position), motor_id);
216 stepper_motor->set_last_milestones(std::get<1>(this->saved_position), std::get<2>(this->saved_position));
217 }
218
219 // check against maximum speeds and return the rate modifier
220 float Extruder::check_max_speeds(float delta, float isecs)
221 {
222 float rm = 1.0F; // default no rate modification
223
224 if(this->max_volumetric_rate > 0 && this->filament_diameter > 0.01F) {
225 // volumetric enabled and check for volumetric rate
226 float v = delta * isecs; // the flow rate in mm³/sec
227
228 // return the rate change needed to stay within the max rate
229 if(v > max_volumetric_rate) {
230 rm = max_volumetric_rate / v;
231 }
232 //THEKERNEL->streams->printf("requested flow rate: %f mm³/sec, corrected flow rate: %f mm³/sec\n", v, v * rm);
233 }
234
235 return rm;
236 }
237
238 void Extruder::on_gcode_received(void *argument)
239 {
240 Gcode *gcode = static_cast<Gcode *>(argument);
241
242 // M codes most execute immediately, most only execute if enabled
243 if (gcode->has_m) {
244 if (gcode->m == 114 && this->selected) {
245 char buf[16];
246 if(gcode->subcode == 0) {
247 float pos = THEROBOT->get_axis_position(motor_id);
248 int n = snprintf(buf, sizeof(buf), " E:%1.3f ", pos);
249 gcode->txt_after_ok.append(buf, n);
250
251 } else if(gcode->subcode == 1) { // realtime position
252 int n = snprintf(buf, sizeof(buf), " E:%1.3f ", stepper_motor->get_current_position() / (volumetric_multiplier * extruder_multiplier));
253 gcode->txt_after_ok.append(buf, n);
254
255 } else if(gcode->subcode == 3) { // realtime actuator position
256 int n = snprintf(buf, sizeof(buf), " E:%1.3f ", stepper_motor->get_current_position());
257 gcode->txt_after_ok.append(buf, n);
258 }
259
260
261 } else if (gcode->m == 92 && ( (this->selected && !gcode->has_letter('P')) || (gcode->has_letter('P') && gcode->get_value('P') == this->identifier) ) ) {
262 float spm = stepper_motor->get_steps_per_mm();
263 if (gcode->has_letter('E')) {
264 spm = gcode->get_value('E');
265 stepper_motor->change_steps_per_mm(spm);
266 }
267
268 gcode->stream->printf("E:%f ", spm);
269 gcode->add_nl = true;
270
271 } else if (gcode->m == 200 && ( (this->selected && !gcode->has_letter('P')) || (gcode->has_letter('P') && gcode->get_value('P') == this->identifier)) ) {
272 if (gcode->has_letter('D')) {
273 this->filament_diameter = gcode->get_value('D');
274 float last_scale = this->volumetric_multiplier;
275 if(filament_diameter > 0.01F) {
276 this->volumetric_multiplier = 1.0F / (powf(this->filament_diameter / 2, 2) * PI);
277 } else {
278 this->volumetric_multiplier = 1.0F;
279 }
280 // the trouble here is that the last milestone will be for the previous multiplier so a change can cause a big blob
281 // so we must change the E last milestone accordingly so it continues smoothly....
282 // change E last milestone to what it would have been if it had used this new multiplier
283 float delta = this->volumetric_multiplier / last_scale;
284 float nm = this->stepper_motor->get_last_milestone() * delta;
285 this->stepper_motor->change_last_milestone(nm);
286
287 } else {
288 if(filament_diameter > 0.01F) {
289 gcode->stream->printf("Filament Diameter: %f\n", this->filament_diameter);
290 } else {
291 gcode->stream->printf("Volumetric extrusion is disabled\n");
292 }
293 }
294
295 } else if (gcode->m == 203 && ( (this->selected && !gcode->has_letter('P')) || (gcode->has_letter('P') && gcode->get_value('P') == this->identifier)) ) {
296 // M203 Exxx Vyyy Set maximum feedrates xxx mm/sec and/or yyy mm³/sec
297 if(gcode->get_num_args() == 0) {
298 gcode->stream->printf("E:%g V:%g", this->stepper_motor->get_max_rate(), this->max_volumetric_rate);
299 gcode->add_nl = true;
300
301 } else {
302 if(gcode->has_letter('E')) {
303 this->stepper_motor->set_max_rate(gcode->get_value('E'));
304 }
305 if(gcode->has_letter('V')) {
306 this->max_volumetric_rate = gcode->get_value('V');
307 }
308 }
309
310 } else if (gcode->m == 204 && gcode->has_letter('E') &&
311 ( (this->selected && !gcode->has_letter('P')) || (gcode->has_letter('P') && gcode->get_value('P') == this->identifier)) ) {
312 // extruder acceleration M204 Ennn mm/sec^2 (Pnnn sets the specific extruder for M500)
313 stepper_motor->set_acceleration(gcode->get_value('E'));
314
315 } else if (gcode->m == 207 && ( (this->selected && !gcode->has_letter('P')) || (gcode->has_letter('P') && gcode->get_value('P') == this->identifier)) ) {
316 // M207 - set retract length S[positive mm] F[feedrate mm/min] Z[additional zlift/hop] Q[zlift feedrate mm/min]
317 if(gcode->has_letter('S')) retract_length = gcode->get_value('S');
318 if(gcode->has_letter('F')) retract_feedrate = gcode->get_value('F') / 60.0F; // specified in mm/min converted to mm/sec
319 if(gcode->has_letter('Z')) retract_zlift_length = gcode->get_value('Z'); // specified in mm
320 if(gcode->has_letter('Q')) retract_zlift_feedrate = gcode->get_value('Q') / 60.0F; // specified in mm/min converted to mm/sec
321
322 } else if (gcode->m == 208 && ( (this->selected && !gcode->has_letter('P')) || (gcode->has_letter('P') && gcode->get_value('P') == this->identifier)) ) {
323 // M208 - set retract recover length S[positive mm surplus to the M207 S*] F[feedrate mm/min]
324 if(gcode->has_letter('S')) retract_recover_length = gcode->get_value('S');
325 if(gcode->has_letter('F')) retract_recover_feedrate = gcode->get_value('F') / 60.0F; // specified in mm/min converted to mm/sec
326
327 } else if (gcode->m == 221 && this->selected) { // M221 S100 change flow rate by percentage
328 if(gcode->has_letter('S')) {
329 float last_scale = this->extruder_multiplier;
330 this->extruder_multiplier = gcode->get_value('S') / 100.0F;
331 // the trouble here is that the last milestone will be for the previous multiplier so a change can cause a big blob
332 // so we must change the E last milestone accordingly so it continues smoothly....
333 // change E last milestone to what it would have been if it had used this new multiplier
334 float delta = this->extruder_multiplier / last_scale;
335 float nm = this->stepper_motor->get_last_milestone() * delta;
336 this->stepper_motor->change_last_milestone(nm);
337
338 } else {
339 gcode->stream->printf("Flow rate at %6.2f %%\n", this->extruder_multiplier * 100.0F);
340 }
341
342 } else if (gcode->m == 500 || gcode->m == 503) { // M500 saves some volatile settings to config override file, M503 just prints the settings
343 gcode->stream->printf(";E Steps per mm:\nM92 E%1.4f P%d\n", stepper_motor->get_steps_per_mm(), this->identifier);
344 gcode->stream->printf(";E Filament diameter:\nM200 D%1.4f P%d\n", this->filament_diameter, this->identifier);
345 gcode->stream->printf(";E retract length, feedrate:\nM207 S%1.4f F%1.4f Z%1.4f Q%1.4f P%d\n", this->retract_length, this->retract_feedrate * 60.0F, this->retract_zlift_length, this->retract_zlift_feedrate * 60.0F, this->identifier);
346 gcode->stream->printf(";E retract recover length, feedrate:\nM208 S%1.4f F%1.4f P%d\n", this->retract_recover_length, this->retract_recover_feedrate * 60.0F, this->identifier);
347 gcode->stream->printf(";E acceleration mm/sec²:\nM204 E%1.4f P%d\n", stepper_motor->get_acceleration(), this->identifier);
348 gcode->stream->printf(";E max feed rate mm/sec:\nM203 E%1.4f P%d\n", stepper_motor->get_max_rate(), this->identifier);
349 if(this->max_volumetric_rate > 0) {
350 gcode->stream->printf(";E max volumetric rate mm³/sec:\nM203 V%1.4f P%d\n", this->max_volumetric_rate, this->identifier);
351 }
352 }
353
354 } else if( gcode->has_g && this->selected ) {
355
356 if( (gcode->g == 10 || gcode->g == 11) && !gcode->has_letter('L') ) {
357 // firmware retract command (Ignore if has L parameter that is not for us)
358 // check we are in the correct state of retract or unretract
359 if(gcode->g == 10 && !retracted) {
360 this->retracted = true;
361 this->cancel_zlift_restore = false;
362 this->g92e0_detected = false;
363 } else if(gcode->g == 11 && retracted) {
364 this->retracted = false;
365 } else
366 return; // ignore duplicates
367
368 if(gcode->g == 10) {
369 // retract
370 float delta[motor_id + 1];
371 for (int i = 0; i < motor_id; ++i) {
372 delta[i] = 0;
373 }
374
375 delta[motor_id] = -retract_length / volumetric_multiplier; // convert from mm to mm³
376 THEROBOT->delta_move(delta, retract_feedrate, motor_id + 1);
377
378 // zlift
379 if(retract_zlift_length > 0) {
380 float delta[3] {0, 0, retract_zlift_length};
381 THEROBOT->delta_move(delta, retract_zlift_feedrate, 3);
382 }
383
384 } else if(gcode->g == 11) {
385 // unretract
386 if(retract_zlift_length > 0 && !this->cancel_zlift_restore) {
387 // reverse zlift happens before unretract
388 // NOTE we do not do this if cancel_zlift_restore is set to true, which happens if there is an absolute Z move inbetween G10 and G11
389 float delta[3] {0, 0, -retract_zlift_length};
390 THEROBOT->delta_move(delta, retract_zlift_feedrate, 3);
391 }
392
393 float delta[motor_id + 1];
394 for (int i = 0; i < motor_id; ++i) {
395 delta[i] = 0;
396 }
397 // HACK ALERT due to certain slicers reseting E with G92 E0 between the G10 and G11 we need to restore
398 // the current position after we do the unretract, this is horribly hacky :(
399 // also as the move has not completed yet, when we restore the current position will be incorrect once the move finishes,
400 // however this is not fatal for an extruder
401 if(g92e0_detected) save_position();
402 delta[motor_id] = (retract_length + retract_recover_length) / volumetric_multiplier; // convert from mm to mm³
403 THEROBOT->delta_move(delta, retract_recover_feedrate, motor_id + 1);
404 if(g92e0_detected) restore_position();
405 }
406
407
408 } else if( this->retracted && (gcode->g == 0 || gcode->g == 1) && gcode->has_letter('Z')) {
409 // NOTE we cancel the zlift restore for the following G11 as we have moved to an absolute Z which we need to stay at
410 this->cancel_zlift_restore = true;
411
412 } else if( this->retracted && gcode->g == 92 && gcode->has_letter('E')) {
413 // old versions of slic3rs issued a G92 E0 after G10, handle that case
414 this->g92e0_detected= true;
415 }
416
417 }
418
419 }