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/>.
10 #include "libs/Module.h"
11 #include "libs/Kernel.h"
13 #include "modules/robot/Conveyor.h"
14 #include "modules/robot/Block.h"
15 #include "StepperMotor.h"
16 #include "SlowTicker.h"
18 #include "StepperMotor.h"
20 #include "checksumm.h"
21 #include "ConfigValue.h"
23 #include "libs/StreamOutput.h"
24 #include "PublicDataRequest.h"
25 #include "StreamOutputPool.h"
26 #include "ExtruderPublicAccess.h"
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")
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")
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")
62 #define PI 3.14159265358979F
66 As the actual motion is handled by the planner and the stepticker, this module just handles Extruder specific gcodes
68 In a multi extruder setting it must be selected to be addressed. (using T0 T1 etc)
71 Extruder::Extruder( uint16_t config_identifier
)
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
));
89 void Extruder::on_module_loaded()
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
);
101 void Extruder::config_load()
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();
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();
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();
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
124 if(filament_diameter
> 0.01F
) {
125 this->volumetric_multiplier
= 1.0F
/ (powf(this->filament_diameter
/ 2, 2) * PI
);
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
);
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
138 void Extruder::select()
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
; };
146 void Extruder::deselect()
149 stepper_motor
->set_selected(false);
150 THEROBOT
->get_e_scale_fnc
= nullptr;
153 void Extruder::on_get_public_data(void *argument
)
155 PublicDataRequest
*pdr
= static_cast<PublicDataRequest
*>(argument
);
157 if(!pdr
->starts_with(extruder_checksum
)) return;
159 if(!this->selected
) return;
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();
172 void Extruder::on_set_public_data(void *argument
)
174 PublicDataRequest
*pdr
= static_cast<PublicDataRequest
*>(argument
);
176 if(!pdr
->starts_with(extruder_checksum
)) return;
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;
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
187 // check against maximum speeds and return rate modifier
188 d
[1] = check_max_speeds(delta
, isecs
);
193 // save or restore extruder state
194 if(pdr
->second_element_is(save_state_checksum
)) {
196 this->saved_selected
= this->selected
;
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
207 void Extruder::save_position()
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());
213 void Extruder::restore_position()
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
));
219 // check against maximum speeds and return the rate modifier
220 float Extruder::check_max_speeds(float delta
, float isecs
)
222 float rm
= 1.0F
; // default no rate modification
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
228 // return the rate change needed to stay within the max rate
229 if(v
> max_volumetric_rate
) {
230 rm
= max_volumetric_rate
/ v
;
232 //THEKERNEL->streams->printf("requested flow rate: %f mm³/sec, corrected flow rate: %f mm³/sec\n", v, v * rm);
238 void Extruder::on_gcode_received(void *argument
)
240 Gcode
*gcode
= static_cast<Gcode
*>(argument
);
242 // M codes most execute immediately, most only execute if enabled
244 if (gcode
->m
== 114 && this->selected
) {
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
);
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
);
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
);
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
);
268 gcode
->stream
->printf("E:%f ", spm
);
269 gcode
->add_nl
= true;
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
);
278 this->volumetric_multiplier
= 1.0F
;
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
);
288 if(filament_diameter
> 0.01F
) {
289 gcode
->stream
->printf("Filament Diameter: %f\n", this->filament_diameter
);
291 gcode
->stream
->printf("Volumetric extrusion is disabled\n");
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;
302 if(gcode
->has_letter('E')) {
303 this->stepper_motor
->set_max_rate(gcode
->get_value('E'));
305 if(gcode
->has_letter('V')) {
306 this->max_volumetric_rate
= gcode
->get_value('V');
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'));
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
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
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
);
339 gcode
->stream
->printf("Flow rate at %6.2f %%\n", this->extruder_multiplier
* 100.0F
);
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
);
354 } else if( gcode
->has_g
&& this->selected
) {
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;
366 return; // ignore duplicates
370 float delta
[motor_id
+ 1];
371 for (int i
= 0; i
< motor_id
; ++i
) {
375 delta
[motor_id
] = -retract_length
/ volumetric_multiplier
; // convert from mm to mm³
376 THEROBOT
->delta_move(delta
, retract_feedrate
, motor_id
+ 1);
379 if(retract_zlift_length
> 0) {
380 float delta
[3] {0, 0, retract_zlift_length
};
381 THEROBOT
->delta_move(delta
, retract_zlift_feedrate
, 3);
384 } else if(gcode
->g
== 11) {
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);
393 float delta
[motor_id
+ 1];
394 for (int i
= 0; i
< motor_id
; ++i
) {
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();
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;
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;