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