smoothie port to mbed online compiler (smoothieware.org)
For documentation, license, ..., please check http://smoothieware.org/
This version has been tested with a 3 axis machine
modules/robot/Robot.cpp
- Committer:
- scachat
- Date:
- 2012-07-31
- Revision:
- 0:31e91bb0ef3c
File content as of revision 0:31e91bb0ef3c:
/* 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) 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. 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. You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>. */ #include "libs/Module.h" #include "libs/Kernel.h" #include <string> using std::string; #include <math.h> #include "Planner.h" #include "Player.h" #include "Robot.h" #include "libs/nuts_bolts.h" #include "../communication/utils/Gcode.h" #include "arm_solutions/BaseSolution.h" #include "arm_solutions/CartesianSolution.h" Robot::Robot(){ this->inch_mode = false; this->absolute_mode = true; this->motion_mode = MOTION_MODE_SEEK; this->select_plane(X_AXIS, Y_AXIS, Z_AXIS); clear_vector(this->current_position); clear_vector(this->last_milestone); } //Called when the module has just been loaded void Robot::on_module_loaded() { this->arm_solution = new CartesianSolution(this->kernel->config); this->register_for_event(ON_GCODE_RECEIVED); // Configuration this->on_config_reload(this); } void Robot::on_config_reload(void* argument){ this->feed_rate = this->kernel->config->value(default_feed_rate_checksum )->by_default(100)->as_number()/60; this->seek_rate = this->kernel->config->value(default_seek_rate_checksum )->by_default(100)->as_number()/60; this->mm_per_line_segment = this->kernel->config->value(mm_per_line_segment_checksum)->by_default(0.1)->as_number(); this->mm_per_arc_segment = this->kernel->config->value(mm_per_arc_segment_checksum )->by_default(10 )->as_number(); this->arc_correction = this->kernel->config->value(arc_correction_checksum )->by_default(5 )->as_number(); this->max_speeds[X_AXIS] = this->kernel->config->value(x_axis_max_speed_checksum )->by_default(0 )->as_number(); this->max_speeds[Y_AXIS] = this->kernel->config->value(y_axis_max_speed_checksum )->by_default(0 )->as_number(); this->max_speeds[Z_AXIS] = this->kernel->config->value(z_axis_max_speed_checksum )->by_default(0 )->as_number(); } //A GCode has been received void Robot::on_gcode_received(void * argument){ Gcode* gcode = static_cast<Gcode*>(argument); gcode->call_on_gcode_execute_event_immediatly = false; gcode->on_gcode_execute_event_called = false; //If the queue is empty, execute immediatly, otherwise attach to the last added block if( this->kernel->player->queue.size() == 0 ){ gcode->call_on_gcode_execute_event_immediatly = true; this->execute_gcode(gcode); if( gcode->on_gcode_execute_event_called == false ){ this->kernel->call_event(ON_GCODE_EXECUTE, gcode ); } }else{ Block* block = this->kernel->player->queue.get_ref( this->kernel->player->queue.size() - 1 ); this->execute_gcode(gcode); block->append_gcode(gcode); } } //See if the current Gcode line has some orders for us void Robot::execute_gcode(Gcode* gcode){ //Temp variables, constant properties are stored in the object uint8_t next_action = NEXT_ACTION_DEFAULT; this->motion_mode = -1; //G-letter Gcodes are mostly what the Robot module is interrested in, other modules also catch the gcode event and do stuff accordingly if( gcode->has_letter('G')){ switch( (int) gcode->get_value('G') ){ case 0: this->motion_mode = MOTION_MODE_SEEK; break; case 1: this->motion_mode = MOTION_MODE_LINEAR; break; case 2: this->motion_mode = MOTION_MODE_CW_ARC; break; case 3: this->motion_mode = MOTION_MODE_CCW_ARC; break; case 17: this->select_plane(X_AXIS, Y_AXIS, Z_AXIS); break; case 18: this->select_plane(X_AXIS, Z_AXIS, Y_AXIS); break; case 19: this->select_plane(Y_AXIS, Z_AXIS, X_AXIS); break; case 20:this->inch_mode = true; break; case 21:this->inch_mode = false; break; case 90:this->absolute_mode = true; break; case 91:this->absolute_mode = false; break; } }else{ return; } //Get parameters double target[3], offset[3]; clear_vector(target); clear_vector(offset); memcpy(target, this->current_position, sizeof(target)); //default to last target for(char letter = 'I'; letter <= 'K'; letter++){ if( gcode->has_letter(letter) ){ offset[letter-'I'] = this->to_millimeters(gcode->get_value(letter)); } } 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']); } } 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; } } //Perform any physical actions switch( next_action ){ case NEXT_ACTION_DEFAULT: switch(this->motion_mode){ case MOTION_MODE_CANCEL: break; case MOTION_MODE_SEEK : this->append_line(gcode, target, this->seek_rate ); break; case MOTION_MODE_LINEAR: this->append_line(gcode, target, this->feed_rate ); break; case MOTION_MODE_CW_ARC: case MOTION_MODE_CCW_ARC: this->compute_arc(gcode, offset, target ); break; } break; } // As far as the parser is concerned, the position is now == target. In reality the // motion control system might still be processing the action and the real tool position // in any intermediate location. memcpy(this->current_position, target, sizeof(double)*3); // this->position[] = target[]; } // Convert target from millimeters to steps, and append this to the planner void Robot::append_milestone( double target[], double rate ){ int steps[3]; //Holds the result of the conversion this->arm_solution->millimeters_to_steps( target, steps ); double deltas[3]; for(int axis=X_AXIS;axis<=Z_AXIS;axis++){deltas[axis]=target[axis]-this->last_milestone[axis];} double millimeters_of_travel = sqrt( pow( deltas[X_AXIS], 2 ) + pow( deltas[Y_AXIS], 2 ) + pow( deltas[Z_AXIS], 2 ) ); double duration = 0; if( rate > 0 ){ duration = millimeters_of_travel / rate; } for(int axis=X_AXIS;axis<=Z_AXIS;axis++){ if( this->max_speeds[axis] > 0 ){ double axis_speed = ( fabs(deltas[axis]) / ( millimeters_of_travel / rate )) * 60; if( axis_speed > this->max_speeds[axis] ){ rate = rate * ( this->max_speeds[axis] / axis_speed ); } } } this->kernel->planner->append_block( steps, rate*60, millimeters_of_travel, deltas ); memcpy(this->last_milestone, target, sizeof(double)*3); // this->last_milestone[] = target[]; } void Robot::append_line(Gcode* gcode, double target[], double rate ){ // We cut the line into smaller segments. This is not usefull in a cartesian robot, but necessary for robots with rotational axes. // In cartesian robot, a high "mm_per_line_segment" setting will prevent waste. 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 ) ); if( gcode->call_on_gcode_execute_event_immediatly == true ){ this->kernel->call_event(ON_GCODE_EXECUTE, gcode ); gcode->on_gcode_execute_event_called = true; } if (gcode->millimeters_of_travel == 0.0) { this->append_milestone(this->current_position, 0.0); return; } uint16_t segments = ceil( gcode->millimeters_of_travel/ this->mm_per_line_segment); // A vector to keep track of the endpoint of each segment double temp_target[3]; //Initialize axes memcpy( temp_target, this->current_position, sizeof(double)*3); // temp_target[] = this->current_position[]; //For each segment for( int i=0; i<segments-1; i++ ){ for(int axis=X_AXIS; axis <= Z_AXIS; axis++ ){ temp_target[axis] += ( target[axis]-this->current_position[axis] )/segments; } this->append_milestone(temp_target, rate); } this->append_milestone(target, rate); } void Robot::append_arc(Gcode* gcode, double target[], double offset[], double radius, bool is_clockwise ){ double center_axis0 = this->current_position[this->plane_axis_0] + offset[this->plane_axis_0]; double center_axis1 = this->current_position[this->plane_axis_1] + offset[this->plane_axis_1]; double linear_travel = target[this->plane_axis_2] - this->current_position[this->plane_axis_2]; double r_axis0 = -offset[this->plane_axis_0]; // Radius vector from center to current location double r_axis1 = -offset[this->plane_axis_1]; double rt_axis0 = target[this->plane_axis_0] - center_axis0; double rt_axis1 = target[this->plane_axis_1] - center_axis1; // CCW angle between position and target from circle center. Only one atan2() trig computation required. double angular_travel = atan2(r_axis0*rt_axis1-r_axis1*rt_axis0, r_axis0*rt_axis0+r_axis1*rt_axis1); if (angular_travel < 0) { angular_travel += 2*M_PI; } if (is_clockwise) { angular_travel -= 2*M_PI; } gcode->millimeters_of_travel = hypot(angular_travel*radius, fabs(linear_travel)); if( gcode->call_on_gcode_execute_event_immediatly == true ){ this->kernel->call_event(ON_GCODE_EXECUTE, gcode ); gcode->on_gcode_execute_event_called = true; } if (gcode->millimeters_of_travel == 0.0) { this->append_milestone(this->current_position, 0.0); return; } uint16_t segments = floor(gcode->millimeters_of_travel/this->mm_per_arc_segment); double theta_per_segment = angular_travel/segments; double linear_per_segment = linear_travel/segments; /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector, and phi is the angle of rotation. Based on the solution approach by Jens Geisler. r_T = [cos(phi) -sin(phi); sin(phi) cos(phi] * r ; For arc generation, the center of the circle is the axis of rotation and the radius vector is defined from the circle center to the initial position. Each line segment is formed by successive vector rotations. This requires only two cos() and sin() computations to form the rotation matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since all double numbers are single precision on the Arduino. (True double precision will not have round off issues for CNC applications.) Single precision error can accumulate to be greater than tool precision in some cases. Therefore, arc path correction is implemented. Small angle approximation may be used to reduce computation overhead further. This approximation holds for everything, but very small circles and large mm_per_arc_segment values. In other words, theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an issue for CNC machines with the single precision Arduino calculations. This approximation also allows mc_arc to immediately insert a line segment into the planner without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead. This is important when there are successive arc motions. */ // Vector rotation matrix values double cos_T = 1-0.5*theta_per_segment*theta_per_segment; // Small angle approximation double sin_T = theta_per_segment; double arc_target[3]; double sin_Ti; double cos_Ti; double r_axisi; uint16_t i; int8_t count = 0; // Initialize the linear axis arc_target[this->plane_axis_2] = this->current_position[this->plane_axis_2]; for (i = 1; i<segments; i++) { // Increment (segments-1) if (count < this->arc_correction ) { // Apply vector rotation matrix r_axisi = r_axis0*sin_T + r_axis1*cos_T; r_axis0 = r_axis0*cos_T - r_axis1*sin_T; r_axis1 = r_axisi; count++; } else { // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments. // Compute exact location by applying transformation matrix from initial radius vector(=-offset). cos_Ti = cos(i*theta_per_segment); sin_Ti = sin(i*theta_per_segment); r_axis0 = -offset[this->plane_axis_0]*cos_Ti + offset[this->plane_axis_1]*sin_Ti; r_axis1 = -offset[this->plane_axis_0]*sin_Ti - offset[this->plane_axis_1]*cos_Ti; count = 0; } // Update arc_target location arc_target[this->plane_axis_0] = center_axis0 + r_axis0; arc_target[this->plane_axis_1] = center_axis1 + r_axis1; arc_target[this->plane_axis_2] += linear_per_segment; this->append_milestone(arc_target, this->feed_rate); } // Ensure last segment arrives at target location. this->append_milestone(target, this->feed_rate); } void Robot::compute_arc(Gcode* gcode, double offset[], double target[]){ // Find the radius double radius = hypot(offset[this->plane_axis_0], offset[this->plane_axis_1]); // Set clockwise/counter-clockwise sign for mc_arc computations bool is_clockwise = false; if( this->motion_mode == MOTION_MODE_CW_ARC ){ is_clockwise = true; } // Append arc this->append_arc(gcode, target, offset, radius, is_clockwise ); } // Convert from inches to millimeters ( our internal storage unit ) if needed inline double Robot::to_millimeters( double value ){ return this->inch_mode ? value/25.4 : value; } double Robot::theta(double x, double y){ double t = atan(x/fabs(y)); if (y>0) {return(t);} else {if (t>0){return(M_PI-t);} else {return(-M_PI-t);}} } void Robot::select_plane(uint8_t axis_0, uint8_t axis_1, uint8_t axis_2){ this->plane_axis_0 = axis_0; this->plane_axis_1 = axis_1; this->plane_axis_2 = axis_2; }