M114 only responds to requestor instead of broadcasting position
[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
AW
85 }
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){
92
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;
a12c7f07
L
111 case 92: clear_vector(this->last_milestone);
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);
116 }
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 }
eaf8a8a8
BG
122 }
123 memcpy(this->current_position, this->last_milestone, sizeof(double)*3); // current_position[] = last_milestone[];
6e87be14 124 this->arm_solution->millimeters_to_steps(this->current_position, this->kernel->planner->position);
eaf8a8a8 125 return; // TODO: Wait until queue empty
f6c04440 126 }
9a73896c
BG
127 }else if( gcode->has_letter('M')){
128 switch( (int) gcode->get_value('M') ){
f861c8b9 129 case 114: gcode->stream->printf("C: X:%1.3f Y:%1.3f Z:%1.3f\n",
9a73896c
BG
130 this->current_position[0],
131 this->current_position[1],
132 this->current_position[2]);
133 return;
134 }
135 }else{ return; }
4cff3ded
AW
136
137 //Get parameters
138 double target[3], offset[3];
df27a6a3 139 clear_vector(target); clear_vector(offset);
4cff3ded
AW
140
141 memcpy(target, this->current_position, sizeof(target)); //default to last target
142
df27a6a3
MM
143 for(char letter = 'I'; letter <= 'K'; letter++){ if( gcode->has_letter(letter) ){ offset[letter-'I'] = this->to_millimeters(gcode->get_value(letter)); } }
144 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']); } }
4cff3ded
AW
145
146 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; } }
147
148 //Perform any physical actions
149 switch( next_action ){
150 case NEXT_ACTION_DEFAULT:
151 switch(this->motion_mode){
152 case MOTION_MODE_CANCEL: break;
436a2cd1
AW
153 case MOTION_MODE_SEEK : this->append_line(gcode, target, this->seek_rate ); break;
154 case MOTION_MODE_LINEAR: this->append_line(gcode, target, this->feed_rate ); break;
df27a6a3 155 case MOTION_MODE_CW_ARC: case MOTION_MODE_CCW_ARC: this->compute_arc(gcode, offset, target ); break;
4cff3ded
AW
156 }
157 break;
158 }
13e4a3f9 159
4cff3ded
AW
160 // As far as the parser is concerned, the position is now == target. In reality the
161 // motion control system might still be processing the action and the real tool position
162 // in any intermediate location.
df27a6a3 163 memcpy(this->current_position, target, sizeof(double)*3); // this->position[] = target[];
4cff3ded
AW
164
165}
166
167// Convert target from millimeters to steps, and append this to the planner
168void Robot::append_milestone( double target[], double rate ){
169 int steps[3]; //Holds the result of the conversion
3add9a23 170
4cff3ded 171 this->arm_solution->millimeters_to_steps( target, steps );
0e8b102e 172
aab6cbba
AW
173 double deltas[3];
174 for(int axis=X_AXIS;axis<=Z_AXIS;axis++){deltas[axis]=target[axis]-this->last_milestone[axis];}
175
a1b7e9f0 176
df27a6a3 177 double millimeters_of_travel = sqrt( pow( deltas[X_AXIS], 2 ) + pow( deltas[Y_AXIS], 2 ) + pow( deltas[Z_AXIS], 2 ) );
cd011f58 178
436a2cd1
AW
179 double duration = 0;
180 if( rate > 0 ){ duration = millimeters_of_travel / rate; }
7b470506
AW
181
182 for(int axis=X_AXIS;axis<=Z_AXIS;axis++){
df27a6a3
MM
183 if( this->max_speeds[axis] > 0 ){
184 double axis_speed = ( fabs(deltas[axis]) / ( millimeters_of_travel / rate )) * 60;
185 if( axis_speed > this->max_speeds[axis] ){
186 rate = rate * ( this->max_speeds[axis] / axis_speed );
436a2cd1 187 }
7b470506
AW
188 }
189 }
4cff3ded 190
df27a6a3 191 this->kernel->planner->append_block( steps, rate*60, millimeters_of_travel, deltas );
4cff3ded 192
df27a6a3 193 memcpy(this->last_milestone, target, sizeof(double)*3); // this->last_milestone[] = target[];
4cff3ded
AW
194
195}
196
436a2cd1 197void Robot::append_line(Gcode* gcode, double target[], double rate ){
4cff3ded 198
a1b7e9f0 199
df27a6a3 200 // We cut the line into smaller segments. This is not usefull in a cartesian robot, but necessary for robots with rotational axes.
4cff3ded 201 // In cartesian robot, a high "mm_per_line_segment" setting will prevent waste.
df27a6a3 202 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 203
436a2cd1 204 if( gcode->call_on_gcode_execute_event_immediatly == true ){
df27a6a3
MM
205 //printf("GCODE B: %s \r\n", gcode->command.c_str() );
206 this->kernel->call_event(ON_GCODE_EXECUTE, gcode );
436a2cd1
AW
207 gcode->on_gcode_execute_event_called = true;
208 }
209
df27a6a3 210 if (gcode->millimeters_of_travel == 0.0) {
436a2cd1 211 this->append_milestone(this->current_position, 0.0);
df27a6a3 212 return;
436a2cd1
AW
213 }
214
df27a6a3 215 uint16_t segments = ceil( gcode->millimeters_of_travel/ this->mm_per_line_segment);
4cff3ded
AW
216 // A vector to keep track of the endpoint of each segment
217 double temp_target[3];
218 //Initialize axes
df27a6a3 219 memcpy( temp_target, this->current_position, sizeof(double)*3); // temp_target[] = this->current_position[];
4cff3ded
AW
220
221 //For each segment
222 for( int i=0; i<segments-1; i++ ){
df27a6a3
MM
223 for(int axis=X_AXIS; axis <= Z_AXIS; axis++ ){ temp_target[axis] += ( target[axis]-this->current_position[axis] )/segments; }
224 this->append_milestone(temp_target, rate);
4cff3ded
AW
225 }
226 this->append_milestone(target, rate);
227}
228
4cff3ded 229
436a2cd1 230void Robot::append_arc(Gcode* gcode, double target[], double offset[], double radius, bool is_clockwise ){
aab6cbba
AW
231
232 double center_axis0 = this->current_position[this->plane_axis_0] + offset[this->plane_axis_0];
233 double center_axis1 = this->current_position[this->plane_axis_1] + offset[this->plane_axis_1];
234 double linear_travel = target[this->plane_axis_2] - this->current_position[this->plane_axis_2];
235 double r_axis0 = -offset[this->plane_axis_0]; // Radius vector from center to current location
236 double r_axis1 = -offset[this->plane_axis_1];
237 double rt_axis0 = target[this->plane_axis_0] - center_axis0;
238 double rt_axis1 = target[this->plane_axis_1] - center_axis1;
239
240 // CCW angle between position and target from circle center. Only one atan2() trig computation required.
241 double angular_travel = atan2(r_axis0*rt_axis1-r_axis1*rt_axis0, r_axis0*rt_axis0+r_axis1*rt_axis1);
242 if (angular_travel < 0) { angular_travel += 2*M_PI; }
243 if (is_clockwise) { angular_travel -= 2*M_PI; }
244
436a2cd1
AW
245 gcode->millimeters_of_travel = hypot(angular_travel*radius, fabs(linear_travel));
246
247 if( gcode->call_on_gcode_execute_event_immediatly == true ){
df27a6a3
MM
248 //printf("GCODE C: %s \r\n", gcode->command.c_str() );
249 this->kernel->call_event(ON_GCODE_EXECUTE, gcode );
436a2cd1
AW
250 gcode->on_gcode_execute_event_called = true;
251 }
252
df27a6a3 253 if (gcode->millimeters_of_travel == 0.0) {
436a2cd1 254 this->append_milestone(this->current_position, 0.0);
df27a6a3 255 return;
436a2cd1
AW
256 }
257
258 uint16_t segments = floor(gcode->millimeters_of_travel/this->mm_per_arc_segment);
aab6cbba
AW
259
260 double theta_per_segment = angular_travel/segments;
261 double linear_per_segment = linear_travel/segments;
262
263 /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
264 and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
265 r_T = [cos(phi) -sin(phi);
266 sin(phi) cos(phi] * r ;
267 For arc generation, the center of the circle is the axis of rotation and the radius vector is
268 defined from the circle center to the initial position. Each line segment is formed by successive
269 vector rotations. This requires only two cos() and sin() computations to form the rotation
270 matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
271 all double numbers are single precision on the Arduino. (True double precision will not have
272 round off issues for CNC applications.) Single precision error can accumulate to be greater than
273 tool precision in some cases. Therefore, arc path correction is implemented.
274
275 Small angle approximation may be used to reduce computation overhead further. This approximation
276 holds for everything, but very small circles and large mm_per_arc_segment values. In other words,
277 theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
278 to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
279 numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
280 issue for CNC machines with the single precision Arduino calculations.
281 This approximation also allows mc_arc to immediately insert a line segment into the planner
282 without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
283 a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead.
284 This is important when there are successive arc motions.
285 */
286 // Vector rotation matrix values
287 double cos_T = 1-0.5*theta_per_segment*theta_per_segment; // Small angle approximation
288 double sin_T = theta_per_segment;
289
290 double arc_target[3];
291 double sin_Ti;
292 double cos_Ti;
293 double r_axisi;
294 uint16_t i;
295 int8_t count = 0;
296
297 // Initialize the linear axis
298 arc_target[this->plane_axis_2] = this->current_position[this->plane_axis_2];
299
300 for (i = 1; i<segments; i++) { // Increment (segments-1)
301
b66fb830 302 if (count < this->arc_correction ) {
aab6cbba
AW
303 // Apply vector rotation matrix
304 r_axisi = r_axis0*sin_T + r_axis1*cos_T;
305 r_axis0 = r_axis0*cos_T - r_axis1*sin_T;
306 r_axis1 = r_axisi;
307 count++;
308 } else {
309 // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
310 // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
311 cos_Ti = cos(i*theta_per_segment);
312 sin_Ti = sin(i*theta_per_segment);
313 r_axis0 = -offset[this->plane_axis_0]*cos_Ti + offset[this->plane_axis_1]*sin_Ti;
314 r_axis1 = -offset[this->plane_axis_0]*sin_Ti - offset[this->plane_axis_1]*cos_Ti;
315 count = 0;
316 }
317
318 // Update arc_target location
319 arc_target[this->plane_axis_0] = center_axis0 + r_axis0;
320 arc_target[this->plane_axis_1] = center_axis1 + r_axis1;
321 arc_target[this->plane_axis_2] += linear_per_segment;
322 this->append_milestone(arc_target, this->feed_rate);
323
324 }
325 // Ensure last segment arrives at target location.
326 this->append_milestone(target, this->feed_rate);
327}
328
4cff3ded 329
436a2cd1 330void Robot::compute_arc(Gcode* gcode, double offset[], double target[]){
aab6cbba
AW
331
332 // Find the radius
333 double radius = hypot(offset[this->plane_axis_0], offset[this->plane_axis_1]);
334
335 // Set clockwise/counter-clockwise sign for mc_arc computations
336 bool is_clockwise = false;
df27a6a3 337 if( this->motion_mode == MOTION_MODE_CW_ARC ){ is_clockwise = true; }
aab6cbba
AW
338
339 // Append arc
436a2cd1 340 this->append_arc(gcode, target, offset, radius, is_clockwise );
aab6cbba
AW
341
342}
343
344
4cff3ded
AW
345// Convert from inches to millimeters ( our internal storage unit ) if needed
346inline double Robot::to_millimeters( double value ){
df27a6a3 347 return this->inch_mode ? value/25.4 : value;
4cff3ded
AW
348}
349
350double Robot::theta(double x, double y){
351 double t = atan(x/fabs(y));
352 if (y>0) {return(t);} else {if (t>0){return(M_PI-t);} else {return(-M_PI-t);}}
353}
354
355void Robot::select_plane(uint8_t axis_0, uint8_t axis_1, uint8_t axis_2){
356 this->plane_axis_0 = axis_0;
357 this->plane_axis_1 = axis_1;
358 this->plane_axis_2 = axis_2;
359}
360
361