make extra sure that heater outputs get turned off when target is set to 0
[clinton/Smoothieware.git] / src / modules / robot / Robot.cpp
CommitLineData
df27a6a3 1/*
aab6cbba 2 This file is part of Smoothie (http://smoothieware.org/). The motion control part is heavily based on Grbl (https://github.com/simen/grbl) with additions from Sungeun K. Jeon (https://github.com/chamnit/grbl)
4cff3ded
AW
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.
df27a6a3 5 You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>.
4cff3ded
AW
6*/
7
8#include "libs/Module.h"
9#include "libs/Kernel.h"
10#include <string>
11using std::string;
4cff3ded
AW
12#include <math.h>
13#include "Planner.h"
3a4fa0c1 14#include "Player.h"
4cff3ded
AW
15#include "Robot.h"
16#include "libs/nuts_bolts.h"
feb204be
AW
17#include "libs/Pin.h"
18#include "libs/StepperMotor.h"
4cff3ded
AW
19#include "../communication/utils/Gcode.h"
20#include "arm_solutions/BaseSolution.h"
21#include "arm_solutions/CartesianSolution.h"
22
23Robot::Robot(){
a1b7e9f0 24 this->inch_mode = false;
0e8b102e 25 this->absolute_mode = true;
df27a6a3 26 this->motion_mode = MOTION_MODE_SEEK;
4cff3ded
AW
27 this->select_plane(X_AXIS, Y_AXIS, Z_AXIS);
28 clear_vector(this->current_position);
df27a6a3 29 clear_vector(this->last_milestone);
4cff3ded
AW
30}
31
32//Called when the module has just been loaded
33void Robot::on_module_loaded() {
34 this->arm_solution = new CartesianSolution(this->kernel->config);
35 this->register_for_event(ON_GCODE_RECEIVED);
36
37 // Configuration
da24d6ae 38 this->on_config_reload(this);
feb204be
AW
39
40 // Make our 3 StepperMotors
df27a6a3
MM
41 this->alpha_stepper_motor = this->kernel->step_ticker->add_stepper_motor( new StepperMotor(this->alpha_step_pin,this->alpha_dir_pin,this->alpha_en_pin) );
42 this->beta_stepper_motor = this->kernel->step_ticker->add_stepper_motor( new StepperMotor(this->beta_step_pin, this->beta_dir_pin, this->beta_en_pin ) );
43 this->gamma_stepper_motor = this->kernel->step_ticker->add_stepper_motor( new StepperMotor(this->gamma_step_pin,this->gamma_dir_pin,this->gamma_en_pin) );
feb204be 44
da24d6ae
AW
45}
46
47void Robot::on_config_reload(void* argument){
df27a6a3 48 this->feed_rate = this->kernel->config->value(default_feed_rate_checksum )->by_default(100)->as_number()/60;
b66fb830
AW
49 this->seek_rate = this->kernel->config->value(default_seek_rate_checksum )->by_default(100)->as_number()/60;
50 this->mm_per_line_segment = this->kernel->config->value(mm_per_line_segment_checksum)->by_default(0.1)->as_number();
51 this->mm_per_arc_segment = this->kernel->config->value(mm_per_arc_segment_checksum )->by_default(10 )->as_number();
7b470506
AW
52 this->arc_correction = this->kernel->config->value(arc_correction_checksum )->by_default(5 )->as_number();
53 this->max_speeds[X_AXIS] = this->kernel->config->value(x_axis_max_speed_checksum )->by_default(0 )->as_number();
54 this->max_speeds[Y_AXIS] = this->kernel->config->value(y_axis_max_speed_checksum )->by_default(0 )->as_number();
55 this->max_speeds[Z_AXIS] = this->kernel->config->value(z_axis_max_speed_checksum )->by_default(0 )->as_number();
feb204be
AW
56 this->alpha_step_pin = this->kernel->config->value(alpha_step_pin_checksum )->by_default("1.21" )->as_pin()->as_output();
57 this->beta_step_pin = this->kernel->config->value(beta_step_pin_checksum )->by_default("1.23" )->as_pin()->as_output();
58 this->gamma_step_pin = this->kernel->config->value(gamma_step_pin_checksum )->by_default("1.22!" )->as_pin()->as_output();
59 this->alpha_dir_pin = this->kernel->config->value(alpha_dir_pin_checksum )->by_default("1.18" )->as_pin()->as_output();
60 this->beta_dir_pin = this->kernel->config->value(beta_dir_pin_checksum )->by_default("1.20" )->as_pin()->as_output();
61 this->gamma_dir_pin = this->kernel->config->value(gamma_dir_pin_checksum )->by_default("1.19" )->as_pin()->as_output();
62 this->alpha_en_pin = this->kernel->config->value(alpha_en_pin_checksum )->by_default("0.4" )->as_pin()->as_output()->as_open_drain();
63 this->beta_en_pin = this->kernel->config->value(beta_en_pin_checksum )->by_default("0.10" )->as_pin()->as_output()->as_open_drain();
64 this->gamma_en_pin = this->kernel->config->value(gamma_en_pin_checksum )->by_default("0.19" )->as_pin()->as_output()->as_open_drain();
65
4cff3ded
AW
66}
67
68//A GCode has been received
69void Robot::on_gcode_received(void * argument){
70 Gcode* gcode = static_cast<Gcode*>(argument);
df27a6a3 71 gcode->call_on_gcode_execute_event_immediatly = false;
436a2cd1 72 gcode->on_gcode_execute_event_called = false;
436a2cd1 73 //If the queue is empty, execute immediatly, otherwise attach to the last added block
3a4fa0c1 74 if( this->kernel->player->queue.size() == 0 ){
436a2cd1
AW
75 gcode->call_on_gcode_execute_event_immediatly = true;
76 this->execute_gcode(gcode);
77 if( gcode->on_gcode_execute_event_called == false ){
df27a6a3
MM
78 //printf("GCODE A: %s \r\n", gcode->command.c_str() );
79 this->kernel->call_event(ON_GCODE_EXECUTE, gcode );
436a2cd1
AW
80 }
81 }else{
3a4fa0c1 82 Block* block = this->kernel->player->queue.get_ref( this->kernel->player->queue.size() - 1 );
436a2cd1 83 this->execute_gcode(gcode);
e0aa02f6 84 block->append_gcode(gcode);
436a2cd1 85 }
6bc4a00a 86
4cff3ded
AW
87}
88
436a2cd1 89
4cff3ded
AW
90//See if the current Gcode line has some orders for us
91void Robot::execute_gcode(Gcode* gcode){
6bc4a00a 92
4cff3ded
AW
93 //Temp variables, constant properties are stored in the object
94 uint8_t next_action = NEXT_ACTION_DEFAULT;
23c90ba6 95 this->motion_mode = -1;
4cff3ded
AW
96
97 //G-letter Gcodes are mostly what the Robot module is interrested in, other modules also catch the gcode event and do stuff accordingly
98 if( gcode->has_letter('G')){
99 switch( (int) gcode->get_value('G') ){
100 case 0: this->motion_mode = MOTION_MODE_SEEK; break;
101 case 1: this->motion_mode = MOTION_MODE_LINEAR; break;
102 case 2: this->motion_mode = MOTION_MODE_CW_ARC; break;
103 case 3: this->motion_mode = MOTION_MODE_CCW_ARC; break;
104 case 17: this->select_plane(X_AXIS, Y_AXIS, Z_AXIS); break;
105 case 18: this->select_plane(X_AXIS, Z_AXIS, Y_AXIS); break;
106 case 19: this->select_plane(Y_AXIS, Z_AXIS, X_AXIS); break;
107 case 20:this->inch_mode = true; break;
108 case 21:this->inch_mode = false; break;
109 case 90:this->absolute_mode = true; break;
110 case 91:this->absolute_mode = false; break;
6bc4a00a
MM
111 case 92: {
112 if(gcode->get_num_args() == 0){
113 for (char letter = 'X'; letter <= 'Z'; letter++){
114 if ( gcode->has_letter(letter) )
115 this->last_milestone[letter-'X'] = this->to_millimeters(0.0);
eaf8a8a8 116 }
6bc4a00a
MM
117 }else{
118 for (char letter = 'X'; letter <= 'Z'; letter++){
119 if ( gcode->has_letter(letter) )
120 this->last_milestone[letter-'X'] = this->to_millimeters(gcode->get_value(letter));
121 }
122 }
123 memcpy(this->current_position, this->last_milestone, sizeof(double)*3); // current_position[] = last_milestone[];
124 this->arm_solution->millimeters_to_steps(this->current_position, this->kernel->planner->position);
125 return; // TODO: Wait until queue empty
126 }
127 }
9a73896c
BG
128 }else if( gcode->has_letter('M')){
129 switch( (int) gcode->get_value('M') ){
0fb5b438
MM
130 case 92: // M92 - set steps per mm
131 double steps[3];
132 this->arm_solution->get_steps_per_millimeter(steps);
133 if (gcode->has_letter('X'))
134 steps[0] = this->to_millimeters(gcode->get_value('X'));
135 if (gcode->has_letter('Y'))
136 steps[1] = this->to_millimeters(gcode->get_value('Y'));
137 if (gcode->has_letter('Z'))
138 steps[2] = this->to_millimeters(gcode->get_value('Z'));
139 this->arm_solution->set_steps_per_millimeter(steps);
140 // update current position in steps
141 this->arm_solution->millimeters_to_steps(this->current_position, this->kernel->planner->position);
142 gcode->stream->printf("X:%g Y:%g Z:%g ", steps[0], steps[1], steps[2]);
143 gcode->add_nl = true;
144 return;
145 case 114: gcode->stream->printf("C: X:%1.3f Y:%1.3f Z:%1.3f\n",
9a73896c
BG
146 this->current_position[0],
147 this->current_position[1],
148 this->current_position[2]);
149 return;
150 }
c83887ea
MM
151 }
152 if( this->motion_mode < 0)
153 return;
6bc4a00a 154
4cff3ded
AW
155 //Get parameters
156 double target[3], offset[3];
df27a6a3 157 clear_vector(target); clear_vector(offset);
6bc4a00a 158
4cff3ded 159 memcpy(target, this->current_position, sizeof(target)); //default to last target
6bc4a00a 160
df27a6a3
MM
161 for(char letter = 'I'; letter <= 'K'; letter++){ if( gcode->has_letter(letter) ){ offset[letter-'I'] = this->to_millimeters(gcode->get_value(letter)); } }
162 for(char letter = 'X'; letter <= 'Z'; letter++){ if( gcode->has_letter(letter) ){ target[letter-'X'] = this->to_millimeters(gcode->get_value(letter)) + ( this->absolute_mode ? 0 : target[letter-'X']); } }
6bc4a00a 163
4cff3ded 164 if( gcode->has_letter('F') ){ if( this->motion_mode == MOTION_MODE_SEEK ){ this->seek_rate = this->to_millimeters( gcode->get_value('F') ) / 60; }else{ this->feed_rate = this->to_millimeters( gcode->get_value('F') ) / 60; } }
6bc4a00a 165
4cff3ded
AW
166 //Perform any physical actions
167 switch( next_action ){
168 case NEXT_ACTION_DEFAULT:
169 switch(this->motion_mode){
170 case MOTION_MODE_CANCEL: break;
436a2cd1
AW
171 case MOTION_MODE_SEEK : this->append_line(gcode, target, this->seek_rate ); break;
172 case MOTION_MODE_LINEAR: this->append_line(gcode, target, this->feed_rate ); break;
df27a6a3 173 case MOTION_MODE_CW_ARC: case MOTION_MODE_CCW_ARC: this->compute_arc(gcode, offset, target ); break;
4cff3ded
AW
174 }
175 break;
176 }
13e4a3f9 177
4cff3ded
AW
178 // As far as the parser is concerned, the position is now == target. In reality the
179 // motion control system might still be processing the action and the real tool position
180 // in any intermediate location.
df27a6a3 181 memcpy(this->current_position, target, sizeof(double)*3); // this->position[] = target[];
4cff3ded
AW
182
183}
184
185// Convert target from millimeters to steps, and append this to the planner
186void Robot::append_milestone( double target[], double rate ){
187 int steps[3]; //Holds the result of the conversion
6bc4a00a 188
4cff3ded 189 this->arm_solution->millimeters_to_steps( target, steps );
6bc4a00a 190
aab6cbba
AW
191 double deltas[3];
192 for(int axis=X_AXIS;axis<=Z_AXIS;axis++){deltas[axis]=target[axis]-this->last_milestone[axis];}
193
6bc4a00a 194
df27a6a3 195 double millimeters_of_travel = sqrt( pow( deltas[X_AXIS], 2 ) + pow( deltas[Y_AXIS], 2 ) + pow( deltas[Z_AXIS], 2 ) );
6bc4a00a 196
436a2cd1
AW
197 double duration = 0;
198 if( rate > 0 ){ duration = millimeters_of_travel / rate; }
7b470506
AW
199
200 for(int axis=X_AXIS;axis<=Z_AXIS;axis++){
df27a6a3
MM
201 if( this->max_speeds[axis] > 0 ){
202 double axis_speed = ( fabs(deltas[axis]) / ( millimeters_of_travel / rate )) * 60;
203 if( axis_speed > this->max_speeds[axis] ){
204 rate = rate * ( this->max_speeds[axis] / axis_speed );
436a2cd1 205 }
7b470506
AW
206 }
207 }
4cff3ded 208
df27a6a3 209 this->kernel->planner->append_block( steps, rate*60, millimeters_of_travel, deltas );
4cff3ded 210
df27a6a3 211 memcpy(this->last_milestone, target, sizeof(double)*3); // this->last_milestone[] = target[];
4cff3ded
AW
212
213}
214
436a2cd1 215void Robot::append_line(Gcode* gcode, double target[], double rate ){
4cff3ded 216
a1b7e9f0 217
df27a6a3 218 // We cut the line into smaller segments. This is not usefull in a cartesian robot, but necessary for robots with rotational axes.
4cff3ded 219 // In cartesian robot, a high "mm_per_line_segment" setting will prevent waste.
df27a6a3 220 gcode->millimeters_of_travel = sqrt( pow( target[X_AXIS]-this->current_position[X_AXIS], 2 ) + pow( target[Y_AXIS]-this->current_position[Y_AXIS], 2 ) + pow( target[Z_AXIS]-this->current_position[Z_AXIS], 2 ) );
4cff3ded 221
436a2cd1 222 if( gcode->call_on_gcode_execute_event_immediatly == true ){
df27a6a3
MM
223 //printf("GCODE B: %s \r\n", gcode->command.c_str() );
224 this->kernel->call_event(ON_GCODE_EXECUTE, gcode );
436a2cd1
AW
225 gcode->on_gcode_execute_event_called = true;
226 }
227
df27a6a3 228 if (gcode->millimeters_of_travel == 0.0) {
436a2cd1 229 this->append_milestone(this->current_position, 0.0);
df27a6a3 230 return;
436a2cd1
AW
231 }
232
df27a6a3 233 uint16_t segments = ceil( gcode->millimeters_of_travel/ this->mm_per_line_segment);
4cff3ded
AW
234 // A vector to keep track of the endpoint of each segment
235 double temp_target[3];
236 //Initialize axes
df27a6a3 237 memcpy( temp_target, this->current_position, sizeof(double)*3); // temp_target[] = this->current_position[];
4cff3ded
AW
238
239 //For each segment
240 for( int i=0; i<segments-1; i++ ){
df27a6a3
MM
241 for(int axis=X_AXIS; axis <= Z_AXIS; axis++ ){ temp_target[axis] += ( target[axis]-this->current_position[axis] )/segments; }
242 this->append_milestone(temp_target, rate);
4cff3ded
AW
243 }
244 this->append_milestone(target, rate);
245}
246
4cff3ded 247
436a2cd1 248void Robot::append_arc(Gcode* gcode, double target[], double offset[], double radius, bool is_clockwise ){
aab6cbba
AW
249
250 double center_axis0 = this->current_position[this->plane_axis_0] + offset[this->plane_axis_0];
251 double center_axis1 = this->current_position[this->plane_axis_1] + offset[this->plane_axis_1];
252 double linear_travel = target[this->plane_axis_2] - this->current_position[this->plane_axis_2];
253 double r_axis0 = -offset[this->plane_axis_0]; // Radius vector from center to current location
254 double r_axis1 = -offset[this->plane_axis_1];
255 double rt_axis0 = target[this->plane_axis_0] - center_axis0;
256 double rt_axis1 = target[this->plane_axis_1] - center_axis1;
257
258 // CCW angle between position and target from circle center. Only one atan2() trig computation required.
259 double angular_travel = atan2(r_axis0*rt_axis1-r_axis1*rt_axis0, r_axis0*rt_axis0+r_axis1*rt_axis1);
260 if (angular_travel < 0) { angular_travel += 2*M_PI; }
261 if (is_clockwise) { angular_travel -= 2*M_PI; }
262
436a2cd1
AW
263 gcode->millimeters_of_travel = hypot(angular_travel*radius, fabs(linear_travel));
264
265 if( gcode->call_on_gcode_execute_event_immediatly == true ){
df27a6a3
MM
266 //printf("GCODE C: %s \r\n", gcode->command.c_str() );
267 this->kernel->call_event(ON_GCODE_EXECUTE, gcode );
436a2cd1
AW
268 gcode->on_gcode_execute_event_called = true;
269 }
270
df27a6a3 271 if (gcode->millimeters_of_travel == 0.0) {
436a2cd1 272 this->append_milestone(this->current_position, 0.0);
df27a6a3 273 return;
436a2cd1 274 }
6bc4a00a 275
436a2cd1 276 uint16_t segments = floor(gcode->millimeters_of_travel/this->mm_per_arc_segment);
aab6cbba
AW
277
278 double theta_per_segment = angular_travel/segments;
279 double linear_per_segment = linear_travel/segments;
280
281 /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
282 and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
283 r_T = [cos(phi) -sin(phi);
284 sin(phi) cos(phi] * r ;
285 For arc generation, the center of the circle is the axis of rotation and the radius vector is
286 defined from the circle center to the initial position. Each line segment is formed by successive
287 vector rotations. This requires only two cos() and sin() computations to form the rotation
288 matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
289 all double numbers are single precision on the Arduino. (True double precision will not have
290 round off issues for CNC applications.) Single precision error can accumulate to be greater than
291 tool precision in some cases. Therefore, arc path correction is implemented.
292
293 Small angle approximation may be used to reduce computation overhead further. This approximation
294 holds for everything, but very small circles and large mm_per_arc_segment values. In other words,
295 theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
296 to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
297 numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
298 issue for CNC machines with the single precision Arduino calculations.
299 This approximation also allows mc_arc to immediately insert a line segment into the planner
300 without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
301 a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead.
302 This is important when there are successive arc motions.
303 */
304 // Vector rotation matrix values
305 double cos_T = 1-0.5*theta_per_segment*theta_per_segment; // Small angle approximation
306 double sin_T = theta_per_segment;
307
308 double arc_target[3];
309 double sin_Ti;
310 double cos_Ti;
311 double r_axisi;
312 uint16_t i;
313 int8_t count = 0;
314
315 // Initialize the linear axis
316 arc_target[this->plane_axis_2] = this->current_position[this->plane_axis_2];
317
318 for (i = 1; i<segments; i++) { // Increment (segments-1)
319
b66fb830 320 if (count < this->arc_correction ) {
aab6cbba
AW
321 // Apply vector rotation matrix
322 r_axisi = r_axis0*sin_T + r_axis1*cos_T;
323 r_axis0 = r_axis0*cos_T - r_axis1*sin_T;
324 r_axis1 = r_axisi;
325 count++;
326 } else {
327 // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
328 // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
329 cos_Ti = cos(i*theta_per_segment);
330 sin_Ti = sin(i*theta_per_segment);
331 r_axis0 = -offset[this->plane_axis_0]*cos_Ti + offset[this->plane_axis_1]*sin_Ti;
332 r_axis1 = -offset[this->plane_axis_0]*sin_Ti - offset[this->plane_axis_1]*cos_Ti;
333 count = 0;
334 }
335
336 // Update arc_target location
337 arc_target[this->plane_axis_0] = center_axis0 + r_axis0;
338 arc_target[this->plane_axis_1] = center_axis1 + r_axis1;
339 arc_target[this->plane_axis_2] += linear_per_segment;
340 this->append_milestone(arc_target, this->feed_rate);
341
342 }
343 // Ensure last segment arrives at target location.
344 this->append_milestone(target, this->feed_rate);
345}
346
4cff3ded 347
436a2cd1 348void Robot::compute_arc(Gcode* gcode, double offset[], double target[]){
aab6cbba
AW
349
350 // Find the radius
351 double radius = hypot(offset[this->plane_axis_0], offset[this->plane_axis_1]);
352
353 // Set clockwise/counter-clockwise sign for mc_arc computations
354 bool is_clockwise = false;
df27a6a3 355 if( this->motion_mode == MOTION_MODE_CW_ARC ){ is_clockwise = true; }
aab6cbba
AW
356
357 // Append arc
436a2cd1 358 this->append_arc(gcode, target, offset, radius, is_clockwise );
aab6cbba
AW
359
360}
361
362
4cff3ded
AW
363// Convert from inches to millimeters ( our internal storage unit ) if needed
364inline double Robot::to_millimeters( double value ){
df27a6a3 365 return this->inch_mode ? value/25.4 : value;
4cff3ded
AW
366}
367
368double Robot::theta(double x, double y){
369 double t = atan(x/fabs(y));
370 if (y>0) {return(t);} else {if (t>0){return(M_PI-t);} else {return(-M_PI-t);}}
371}
372
373void Robot::select_plane(uint8_t axis_0, uint8_t axis_1, uint8_t axis_2){
374 this->plane_axis_0 = axis_0;
375 this->plane_axis_1 = axis_1;
376 this->plane_axis_2 = axis_2;
377}
378
379