Stéphane Cachat
/
Smoothie
smoothie port to mbed online compiler (smoothieware.org)
Embed:
(wiki syntax)
Show/hide line numbers
Robot.cpp
00001 /* 00002 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) 00003 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. 00004 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. 00005 You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>. 00006 */ 00007 00008 #include "libs/Module.h" 00009 #include "libs/Kernel.h" 00010 #include <string> 00011 using std::string; 00012 #include <math.h> 00013 #include "Planner.h" 00014 #include "Player.h" 00015 #include "Robot.h" 00016 #include "libs/nuts_bolts.h" 00017 #include "../communication/utils/Gcode.h" 00018 #include "arm_solutions/BaseSolution.h" 00019 #include "arm_solutions/CartesianSolution.h" 00020 00021 Robot::Robot(){ 00022 this->inch_mode = false; 00023 this->absolute_mode = true; 00024 this->motion_mode = MOTION_MODE_SEEK; 00025 this->select_plane(X_AXIS, Y_AXIS, Z_AXIS); 00026 clear_vector(this->current_position); 00027 clear_vector(this->last_milestone); 00028 } 00029 00030 //Called when the module has just been loaded 00031 void Robot::on_module_loaded() { 00032 this->arm_solution = new CartesianSolution(this->kernel->config); 00033 this->register_for_event(ON_GCODE_RECEIVED); 00034 00035 // Configuration 00036 this->on_config_reload(this); 00037 } 00038 00039 void Robot::on_config_reload(void* argument){ 00040 this->feed_rate = this->kernel->config->value(default_feed_rate_checksum )->by_default(100)->as_number()/60; 00041 this->seek_rate = this->kernel->config->value(default_seek_rate_checksum )->by_default(100)->as_number()/60; 00042 this->mm_per_line_segment = this->kernel->config->value(mm_per_line_segment_checksum)->by_default(0.1)->as_number(); 00043 this->mm_per_arc_segment = this->kernel->config->value(mm_per_arc_segment_checksum )->by_default(10 )->as_number(); 00044 this->arc_correction = this->kernel->config->value(arc_correction_checksum )->by_default(5 )->as_number(); 00045 this->max_speeds[X_AXIS] = this->kernel->config->value(x_axis_max_speed_checksum )->by_default(0 )->as_number(); 00046 this->max_speeds[Y_AXIS] = this->kernel->config->value(y_axis_max_speed_checksum )->by_default(0 )->as_number(); 00047 this->max_speeds[Z_AXIS] = this->kernel->config->value(z_axis_max_speed_checksum )->by_default(0 )->as_number(); 00048 } 00049 00050 //A GCode has been received 00051 void Robot::on_gcode_received(void * argument){ 00052 Gcode* gcode = static_cast<Gcode*>(argument); 00053 gcode->call_on_gcode_execute_event_immediatly = false; 00054 gcode->on_gcode_execute_event_called = false; 00055 //If the queue is empty, execute immediatly, otherwise attach to the last added block 00056 if( this->kernel->player->queue.size() == 0 ){ 00057 gcode->call_on_gcode_execute_event_immediatly = true; 00058 this->execute_gcode(gcode); 00059 if( gcode->on_gcode_execute_event_called == false ){ 00060 this->kernel->call_event(ON_GCODE_EXECUTE, gcode ); 00061 } 00062 }else{ 00063 Block* block = this->kernel->player->queue.get_ref( this->kernel->player->queue.size() - 1 ); 00064 this->execute_gcode(gcode); 00065 block->append_gcode(gcode); 00066 } 00067 00068 } 00069 00070 00071 //See if the current Gcode line has some orders for us 00072 void Robot::execute_gcode(Gcode* gcode){ 00073 00074 //Temp variables, constant properties are stored in the object 00075 uint8_t next_action = NEXT_ACTION_DEFAULT; 00076 this->motion_mode = -1; 00077 00078 //G-letter Gcodes are mostly what the Robot module is interrested in, other modules also catch the gcode event and do stuff accordingly 00079 if( gcode->has_letter('G')){ 00080 switch( (int) gcode->get_value('G') ){ 00081 case 0: this->motion_mode = MOTION_MODE_SEEK; break; 00082 case 1: this->motion_mode = MOTION_MODE_LINEAR; break; 00083 case 2: this->motion_mode = MOTION_MODE_CW_ARC; break; 00084 case 3: this->motion_mode = MOTION_MODE_CCW_ARC; break; 00085 case 17: this->select_plane(X_AXIS, Y_AXIS, Z_AXIS); break; 00086 case 18: this->select_plane(X_AXIS, Z_AXIS, Y_AXIS); break; 00087 case 19: this->select_plane(Y_AXIS, Z_AXIS, X_AXIS); break; 00088 case 20:this->inch_mode = true; break; 00089 case 21:this->inch_mode = false; break; 00090 case 90:this->absolute_mode = true; break; 00091 case 91:this->absolute_mode = false; break; 00092 } 00093 }else{ return; } 00094 00095 //Get parameters 00096 double target[3], offset[3]; 00097 clear_vector(target); clear_vector(offset); 00098 00099 memcpy(target, this->current_position, sizeof(target)); //default to last target 00100 00101 for(char letter = 'I'; letter <= 'K'; letter++){ if( gcode->has_letter(letter) ){ offset[letter-'I'] = this->to_millimeters(gcode->get_value(letter)); } } 00102 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']); } } 00103 00104 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; } } 00105 00106 //Perform any physical actions 00107 switch( next_action ){ 00108 case NEXT_ACTION_DEFAULT: 00109 switch(this->motion_mode){ 00110 case MOTION_MODE_CANCEL: break; 00111 case MOTION_MODE_SEEK : this->append_line(gcode, target, this->seek_rate ); break; 00112 case MOTION_MODE_LINEAR: this->append_line(gcode, target, this->feed_rate ); break; 00113 case MOTION_MODE_CW_ARC: case MOTION_MODE_CCW_ARC: this->compute_arc(gcode, offset, target ); break; 00114 } 00115 break; 00116 } 00117 00118 // As far as the parser is concerned, the position is now == target. In reality the 00119 // motion control system might still be processing the action and the real tool position 00120 // in any intermediate location. 00121 memcpy(this->current_position, target, sizeof(double)*3); // this->position[] = target[]; 00122 00123 } 00124 00125 // Convert target from millimeters to steps, and append this to the planner 00126 void Robot::append_milestone( double target[], double rate ){ 00127 int steps[3]; //Holds the result of the conversion 00128 00129 this->arm_solution->millimeters_to_steps( target, steps ); 00130 00131 double deltas[3]; 00132 for(int axis=X_AXIS;axis<=Z_AXIS;axis++){deltas[axis]=target[axis]-this->last_milestone[axis];} 00133 00134 00135 double millimeters_of_travel = sqrt( pow( deltas[X_AXIS], 2 ) + pow( deltas[Y_AXIS], 2 ) + pow( deltas[Z_AXIS], 2 ) ); 00136 00137 double duration = 0; 00138 if( rate > 0 ){ duration = millimeters_of_travel / rate; } 00139 00140 for(int axis=X_AXIS;axis<=Z_AXIS;axis++){ 00141 if( this->max_speeds[axis] > 0 ){ 00142 double axis_speed = ( fabs(deltas[axis]) / ( millimeters_of_travel / rate )) * 60; 00143 if( axis_speed > this->max_speeds[axis] ){ 00144 rate = rate * ( this->max_speeds[axis] / axis_speed ); 00145 } 00146 } 00147 } 00148 00149 this->kernel->planner->append_block( steps, rate*60, millimeters_of_travel, deltas ); 00150 00151 memcpy(this->last_milestone, target, sizeof(double)*3); // this->last_milestone[] = target[]; 00152 00153 } 00154 00155 void Robot::append_line(Gcode* gcode, double target[], double rate ){ 00156 00157 00158 // We cut the line into smaller segments. This is not usefull in a cartesian robot, but necessary for robots with rotational axes. 00159 // In cartesian robot, a high "mm_per_line_segment" setting will prevent waste. 00160 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 ) ); 00161 00162 if( gcode->call_on_gcode_execute_event_immediatly == true ){ 00163 this->kernel->call_event(ON_GCODE_EXECUTE, gcode ); 00164 gcode->on_gcode_execute_event_called = true; 00165 } 00166 00167 if (gcode->millimeters_of_travel == 0.0) { 00168 this->append_milestone(this->current_position, 0.0); 00169 return; 00170 } 00171 00172 uint16_t segments = ceil( gcode->millimeters_of_travel/ this->mm_per_line_segment); 00173 // A vector to keep track of the endpoint of each segment 00174 double temp_target[3]; 00175 //Initialize axes 00176 memcpy( temp_target, this->current_position, sizeof(double)*3); // temp_target[] = this->current_position[]; 00177 00178 //For each segment 00179 for( int i=0; i<segments-1; i++ ){ 00180 for(int axis=X_AXIS; axis <= Z_AXIS; axis++ ){ temp_target[axis] += ( target[axis]-this->current_position[axis] )/segments; } 00181 this->append_milestone(temp_target, rate); 00182 } 00183 this->append_milestone(target, rate); 00184 } 00185 00186 00187 void Robot::append_arc(Gcode* gcode, double target[], double offset[], double radius, bool is_clockwise ){ 00188 00189 double center_axis0 = this->current_position[this->plane_axis_0] + offset[this->plane_axis_0]; 00190 double center_axis1 = this->current_position[this->plane_axis_1] + offset[this->plane_axis_1]; 00191 double linear_travel = target[this->plane_axis_2] - this->current_position[this->plane_axis_2]; 00192 double r_axis0 = -offset[this->plane_axis_0]; // Radius vector from center to current location 00193 double r_axis1 = -offset[this->plane_axis_1]; 00194 double rt_axis0 = target[this->plane_axis_0] - center_axis0; 00195 double rt_axis1 = target[this->plane_axis_1] - center_axis1; 00196 00197 // CCW angle between position and target from circle center. Only one atan2() trig computation required. 00198 double angular_travel = atan2(r_axis0*rt_axis1-r_axis1*rt_axis0, r_axis0*rt_axis0+r_axis1*rt_axis1); 00199 if (angular_travel < 0) { angular_travel += 2*M_PI; } 00200 if (is_clockwise) { angular_travel -= 2*M_PI; } 00201 00202 gcode->millimeters_of_travel = hypot(angular_travel*radius, fabs(linear_travel)); 00203 00204 if( gcode->call_on_gcode_execute_event_immediatly == true ){ 00205 this->kernel->call_event(ON_GCODE_EXECUTE, gcode ); 00206 gcode->on_gcode_execute_event_called = true; 00207 } 00208 00209 if (gcode->millimeters_of_travel == 0.0) { 00210 this->append_milestone(this->current_position, 0.0); 00211 return; 00212 } 00213 00214 uint16_t segments = floor(gcode->millimeters_of_travel/this->mm_per_arc_segment); 00215 00216 double theta_per_segment = angular_travel/segments; 00217 double linear_per_segment = linear_travel/segments; 00218 00219 /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector, 00220 and phi is the angle of rotation. Based on the solution approach by Jens Geisler. 00221 r_T = [cos(phi) -sin(phi); 00222 sin(phi) cos(phi] * r ; 00223 For arc generation, the center of the circle is the axis of rotation and the radius vector is 00224 defined from the circle center to the initial position. Each line segment is formed by successive 00225 vector rotations. This requires only two cos() and sin() computations to form the rotation 00226 matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since 00227 all double numbers are single precision on the Arduino. (True double precision will not have 00228 round off issues for CNC applications.) Single precision error can accumulate to be greater than 00229 tool precision in some cases. Therefore, arc path correction is implemented. 00230 00231 Small angle approximation may be used to reduce computation overhead further. This approximation 00232 holds for everything, but very small circles and large mm_per_arc_segment values. In other words, 00233 theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large 00234 to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for 00235 numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an 00236 issue for CNC machines with the single precision Arduino calculations. 00237 This approximation also allows mc_arc to immediately insert a line segment into the planner 00238 without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied 00239 a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead. 00240 This is important when there are successive arc motions. 00241 */ 00242 // Vector rotation matrix values 00243 double cos_T = 1-0.5*theta_per_segment*theta_per_segment; // Small angle approximation 00244 double sin_T = theta_per_segment; 00245 00246 double arc_target[3]; 00247 double sin_Ti; 00248 double cos_Ti; 00249 double r_axisi; 00250 uint16_t i; 00251 int8_t count = 0; 00252 00253 // Initialize the linear axis 00254 arc_target[this->plane_axis_2] = this->current_position[this->plane_axis_2]; 00255 00256 for (i = 1; i<segments; i++) { // Increment (segments-1) 00257 00258 if (count < this->arc_correction ) { 00259 // Apply vector rotation matrix 00260 r_axisi = r_axis0*sin_T + r_axis1*cos_T; 00261 r_axis0 = r_axis0*cos_T - r_axis1*sin_T; 00262 r_axis1 = r_axisi; 00263 count++; 00264 } else { 00265 // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments. 00266 // Compute exact location by applying transformation matrix from initial radius vector(=-offset). 00267 cos_Ti = cos(i*theta_per_segment); 00268 sin_Ti = sin(i*theta_per_segment); 00269 r_axis0 = -offset[this->plane_axis_0]*cos_Ti + offset[this->plane_axis_1]*sin_Ti; 00270 r_axis1 = -offset[this->plane_axis_0]*sin_Ti - offset[this->plane_axis_1]*cos_Ti; 00271 count = 0; 00272 } 00273 00274 // Update arc_target location 00275 arc_target[this->plane_axis_0] = center_axis0 + r_axis0; 00276 arc_target[this->plane_axis_1] = center_axis1 + r_axis1; 00277 arc_target[this->plane_axis_2] += linear_per_segment; 00278 this->append_milestone(arc_target, this->feed_rate); 00279 00280 } 00281 // Ensure last segment arrives at target location. 00282 this->append_milestone(target, this->feed_rate); 00283 } 00284 00285 00286 void Robot::compute_arc(Gcode* gcode, double offset[], double target[]){ 00287 00288 // Find the radius 00289 double radius = hypot(offset[this->plane_axis_0], offset[this->plane_axis_1]); 00290 00291 // Set clockwise/counter-clockwise sign for mc_arc computations 00292 bool is_clockwise = false; 00293 if( this->motion_mode == MOTION_MODE_CW_ARC ){ is_clockwise = true; } 00294 00295 // Append arc 00296 this->append_arc(gcode, target, offset, radius, is_clockwise ); 00297 00298 } 00299 00300 00301 // Convert from inches to millimeters ( our internal storage unit ) if needed 00302 inline double Robot::to_millimeters( double value ){ 00303 return this->inch_mode ? value/25.4 : value; 00304 } 00305 00306 double Robot::theta(double x, double y){ 00307 double t = atan(x/fabs(y)); 00308 if (y>0) {return(t);} else {if (t>0){return(M_PI-t);} else {return(-M_PI-t);}} 00309 } 00310 00311 void Robot::select_plane(uint8_t axis_0, uint8_t axis_1, uint8_t axis_2){ 00312 this->plane_axis_0 = axis_0; 00313 this->plane_axis_1 = axis_1; 00314 this->plane_axis_2 = axis_2; 00315 } 00316 00317
Generated on Tue Jul 12 2022 14:14:41 by 1.7.2