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