Add public data access to robot for current override speed and current 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"
3fceb8eb 14#include "Conveyor.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 19#include "../communication/utils/Gcode.h"
5647f709 20#include "PublicDataRequest.h"
4cff3ded
AW
21#include "arm_solutions/BaseSolution.h"
22#include "arm_solutions/CartesianSolution.h"
c41d6d95 23#include "arm_solutions/RotatableCartesianSolution.h"
4e04bcd3 24#include "arm_solutions/RostockSolution.h"
bdaaa75d 25#include "arm_solutions/HBotSolution.h"
4cff3ded 26
edac9072
AW
27// The Robot converts GCodes into actual movements, and then adds them to the Planner, which passes them to the Conveyor so they can be added to the queue
28// It takes care of cutting arcs into segments, same thing for line that are too long
29
4cff3ded 30Robot::Robot(){
a1b7e9f0 31 this->inch_mode = false;
0e8b102e 32 this->absolute_mode = true;
df27a6a3 33 this->motion_mode = MOTION_MODE_SEEK;
4cff3ded
AW
34 this->select_plane(X_AXIS, Y_AXIS, Z_AXIS);
35 clear_vector(this->current_position);
df27a6a3 36 clear_vector(this->last_milestone);
0b804a41 37 this->arm_solution = NULL;
7369629d 38 seconds_per_minute = 60.0;
4cff3ded
AW
39}
40
41//Called when the module has just been loaded
42void Robot::on_module_loaded() {
476dcb96 43 register_for_event(ON_CONFIG_RELOAD);
4cff3ded 44 this->register_for_event(ON_GCODE_RECEIVED);
5647f709
JM
45 this->register_for_event(ON_GET_PUBLIC_DATA);
46 this->register_for_event(ON_SET_PUBLIC_DATA);
4cff3ded
AW
47
48 // Configuration
da24d6ae 49 this->on_config_reload(this);
feb204be
AW
50
51 // Make our 3 StepperMotors
e4fe5194
MM
52 this->alpha_stepper_motor = this->kernel->step_ticker->add_stepper_motor( new StepperMotor(&alpha_step_pin,&alpha_dir_pin,&alpha_en_pin) );
53 this->beta_stepper_motor = this->kernel->step_ticker->add_stepper_motor( new StepperMotor(&beta_step_pin, &beta_dir_pin, &beta_en_pin ) );
54 this->gamma_stepper_motor = this->kernel->step_ticker->add_stepper_motor( new StepperMotor(&gamma_step_pin,&gamma_dir_pin,&gamma_en_pin) );
feb204be 55
da24d6ae
AW
56}
57
58void Robot::on_config_reload(void* argument){
5984acdf 59
edac9072
AW
60 // Arm solutions are used to convert positions in millimeters into position in steps for each stepper motor.
61 // While for a cartesian arm solution, this is a simple multiplication, in other, less simple cases, there is some serious math to be done.
62 // To make adding those solution easier, they have their own, separate object.
5984acdf 63 // Here we read the config to find out which arm solution to use
0b804a41 64 if (this->arm_solution) delete this->arm_solution;
4e04bcd3 65 int solution_checksum = get_checksum(this->kernel->config->value(arm_solution_checksum)->by_default("cartesian")->as_string());
d149c730 66 // Note checksums are not const expressions when in debug mode, so don't use switch
bdaaa75d
L
67 if(solution_checksum == hbot_checksum) {
68 this->arm_solution = new HBotSolution(this->kernel->config);
69
70 }else if(solution_checksum == rostock_checksum) {
4a0c8e14 71 this->arm_solution = new RostockSolution(this->kernel->config);
73a4e3c0 72
d149c730 73 }else if(solution_checksum == delta_checksum) {
4a0c8e14
JM
74 // place holder for now
75 this->arm_solution = new RostockSolution(this->kernel->config);
73a4e3c0 76
b73a756d
L
77 }else if(solution_checksum == rotatable_cartesian_checksum) {
78 this->arm_solution = new RotatableCartesianSolution(this->kernel->config);
79
d149c730 80 }else if(solution_checksum == cartesian_checksum) {
4a0c8e14 81 this->arm_solution = new CartesianSolution(this->kernel->config);
73a4e3c0 82
d149c730 83 }else{
4a0c8e14 84 this->arm_solution = new CartesianSolution(this->kernel->config);
d149c730 85 }
73a4e3c0 86
0b804a41
MM
87
88 this->feed_rate = this->kernel->config->value(default_feed_rate_checksum )->by_default(100 )->as_number() / 60;
89 this->seek_rate = this->kernel->config->value(default_seek_rate_checksum )->by_default(100 )->as_number() / 60;
4a0c8e14
JM
90 this->mm_per_line_segment = this->kernel->config->value(mm_per_line_segment_checksum )->by_default(0.0 )->as_number();
91 this->delta_segments_per_second = this->kernel->config->value(delta_segments_per_second_checksum )->by_default(0.0 )->as_number();
0b804a41
MM
92 this->mm_per_arc_segment = this->kernel->config->value(mm_per_arc_segment_checksum )->by_default(0.5 )->as_number();
93 this->arc_correction = this->kernel->config->value(arc_correction_checksum )->by_default(5 )->as_number();
94 this->max_speeds[X_AXIS] = this->kernel->config->value(x_axis_max_speed_checksum )->by_default(60000 )->as_number();
95 this->max_speeds[Y_AXIS] = this->kernel->config->value(y_axis_max_speed_checksum )->by_default(60000 )->as_number();
96 this->max_speeds[Z_AXIS] = this->kernel->config->value(z_axis_max_speed_checksum )->by_default(300 )->as_number();
e4fe5194
MM
97 this->alpha_step_pin.from_string( this->kernel->config->value(alpha_step_pin_checksum )->by_default("2.0" )->as_string())->as_output();
98 this->alpha_dir_pin.from_string( this->kernel->config->value(alpha_dir_pin_checksum )->by_default("0.5" )->as_string())->as_output();
5984acdf 99 this->alpha_en_pin.from_string( this->kernel->config->value(alpha_en_pin_checksum )->by_default("0.4" )->as_string())->as_output();
e4fe5194
MM
100 this->beta_step_pin.from_string( this->kernel->config->value(beta_step_pin_checksum )->by_default("2.1" )->as_string())->as_output();
101 this->gamma_step_pin.from_string( this->kernel->config->value(gamma_step_pin_checksum )->by_default("2.2" )->as_string())->as_output();
102 this->gamma_dir_pin.from_string( this->kernel->config->value(gamma_dir_pin_checksum )->by_default("0.20" )->as_string())->as_output();
5984acdf 103 this->gamma_en_pin.from_string( this->kernel->config->value(gamma_en_pin_checksum )->by_default("0.19" )->as_string())->as_output();
e4fe5194 104 this->beta_dir_pin.from_string( this->kernel->config->value(beta_dir_pin_checksum )->by_default("0.11" )->as_string())->as_output();
5984acdf 105 this->beta_en_pin.from_string( this->kernel->config->value(beta_en_pin_checksum )->by_default("0.10" )->as_string())->as_output();
feb204be 106
4cff3ded
AW
107}
108
5647f709
JM
109void Robot::on_get_public_data(void* argument){
110 PublicDataRequest* pdr = static_cast<PublicDataRequest*>(argument);
111
112 if(!pdr->starts_with(robot_checksum)) return;
113
114 if(pdr->second_element_is(speed_override_percent_checksum)) {
115 static double return_data= 100*60/seconds_per_minute;
116 pdr->set_data_ptr(&return_data);
117 pdr->set_taken();
118
119 }else if(pdr->second_element_is(current_position_checksum)) {
120 static double return_data[3];
121 return_data[0]= from_millimeters(this->current_position[0]);
122 return_data[1]= from_millimeters(this->current_position[1]);
123 return_data[2]= from_millimeters(this->current_position[2]);
124
125 pdr->set_data_ptr(&return_data);
126 pdr->set_taken();
127 }
128}
129
130void Robot::on_set_public_data(void* argument){
131 PublicDataRequest* pdr = static_cast<PublicDataRequest*>(argument);
132
133 if(!pdr->starts_with(robot_checksum)) return;
134
135 if(pdr->second_element_is(speed_override_percent_checksum)) {
136 double t= *static_cast<double*>(pdr->get_data_ptr());
137 seconds_per_minute= t * 0.6;
138 pdr->set_taken();
139 }
140}
141
4cff3ded 142//A GCode has been received
edac9072 143//See if the current Gcode line has some orders for us
4cff3ded
AW
144void Robot::on_gcode_received(void * argument){
145 Gcode* gcode = static_cast<Gcode*>(argument);
6bc4a00a 146
4cff3ded
AW
147 //Temp variables, constant properties are stored in the object
148 uint8_t next_action = NEXT_ACTION_DEFAULT;
23c90ba6 149 this->motion_mode = -1;
4cff3ded
AW
150
151 //G-letter Gcodes are mostly what the Robot module is interrested in, other modules also catch the gcode event and do stuff accordingly
3c4f2dd8
AW
152 if( gcode->has_g){
153 switch( gcode->g ){
74b6303c
DD
154 case 0: this->motion_mode = MOTION_MODE_SEEK; gcode->mark_as_taken(); break;
155 case 1: this->motion_mode = MOTION_MODE_LINEAR; gcode->mark_as_taken(); break;
156 case 2: this->motion_mode = MOTION_MODE_CW_ARC; gcode->mark_as_taken(); break;
157 case 3: this->motion_mode = MOTION_MODE_CCW_ARC; gcode->mark_as_taken(); break;
158 case 17: this->select_plane(X_AXIS, Y_AXIS, Z_AXIS); gcode->mark_as_taken(); break;
159 case 18: this->select_plane(X_AXIS, Z_AXIS, Y_AXIS); gcode->mark_as_taken(); break;
160 case 19: this->select_plane(Y_AXIS, Z_AXIS, X_AXIS); gcode->mark_as_taken(); break;
161 case 20: this->inch_mode = true; gcode->mark_as_taken(); break;
162 case 21: this->inch_mode = false; gcode->mark_as_taken(); break;
163 case 90: this->absolute_mode = true; gcode->mark_as_taken(); break;
164 case 91: this->absolute_mode = false; gcode->mark_as_taken(); break;
0b804a41 165 case 92: {
6bc4a00a 166 if(gcode->get_num_args() == 0){
8a23b271 167 clear_vector(this->last_milestone);
6bc4a00a 168 }else{
eaf8a8a8
BG
169 for (char letter = 'X'; letter <= 'Z'; letter++){
170 if ( gcode->has_letter(letter) )
6bc4a00a 171 this->last_milestone[letter-'X'] = this->to_millimeters(gcode->get_value(letter));
eaf8a8a8 172 }
6bc4a00a
MM
173 }
174 memcpy(this->current_position, this->last_milestone, sizeof(double)*3); // current_position[] = last_milestone[];
175 this->arm_solution->millimeters_to_steps(this->current_position, this->kernel->planner->position);
74b6303c 176 gcode->mark_as_taken();
6bc4a00a
MM
177 return; // TODO: Wait until queue empty
178 }
179 }
3c4f2dd8
AW
180 }else if( gcode->has_m){
181 switch( gcode->m ){
0fb5b438
MM
182 case 92: // M92 - set steps per mm
183 double steps[3];
184 this->arm_solution->get_steps_per_millimeter(steps);
185 if (gcode->has_letter('X'))
186 steps[0] = this->to_millimeters(gcode->get_value('X'));
187 if (gcode->has_letter('Y'))
188 steps[1] = this->to_millimeters(gcode->get_value('Y'));
189 if (gcode->has_letter('Z'))
190 steps[2] = this->to_millimeters(gcode->get_value('Z'));
7369629d
MM
191 if (gcode->has_letter('F'))
192 seconds_per_minute = gcode->get_value('F');
0fb5b438
MM
193 this->arm_solution->set_steps_per_millimeter(steps);
194 // update current position in steps
195 this->arm_solution->millimeters_to_steps(this->current_position, this->kernel->planner->position);
7369629d 196 gcode->stream->printf("X:%g Y:%g Z:%g F:%g ", steps[0], steps[1], steps[2], seconds_per_minute);
0fb5b438 197 gcode->add_nl = true;
74b6303c 198 gcode->mark_as_taken();
0fb5b438 199 return;
6989211c 200 case 114: gcode->stream->printf("C: X:%1.3f Y:%1.3f Z:%1.3f ",
bce9410e
MM
201 from_millimeters(this->current_position[0]),
202 from_millimeters(this->current_position[1]),
203 from_millimeters(this->current_position[2]));
6989211c 204 gcode->add_nl = true;
74b6303c 205 gcode->mark_as_taken();
6989211c 206 return;
7369629d 207 case 220: // M220 - speed override percentage
74b6303c 208 gcode->mark_as_taken();
7369629d
MM
209 if (gcode->has_letter('S'))
210 {
211 double factor = gcode->get_value('S');
212 // enforce minimum 1% speed
213 if (factor < 1.0)
214 factor = 1.0;
215 seconds_per_minute = factor * 0.6;
216 }
6989211c 217 }
c83887ea
MM
218 }
219 if( this->motion_mode < 0)
220 return;
6bc4a00a 221
4cff3ded
AW
222 //Get parameters
223 double target[3], offset[3];
df27a6a3 224 clear_vector(target); clear_vector(offset);
6bc4a00a 225
4cff3ded 226 memcpy(target, this->current_position, sizeof(target)); //default to last target
6bc4a00a 227
df27a6a3 228 for(char letter = 'I'; letter <= 'K'; letter++){ if( gcode->has_letter(letter) ){ offset[letter-'I'] = this->to_millimeters(gcode->get_value(letter)); } }
a63da33c 229 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 230
7369629d
MM
231 if( gcode->has_letter('F') )
232 {
233 if( this->motion_mode == MOTION_MODE_SEEK )
234 this->seek_rate = this->to_millimeters( gcode->get_value('F') ) / 60.0;
235 else
236 this->feed_rate = this->to_millimeters( gcode->get_value('F') ) / 60.0;
237 }
6bc4a00a 238
4cff3ded
AW
239 //Perform any physical actions
240 switch( next_action ){
241 case NEXT_ACTION_DEFAULT:
242 switch(this->motion_mode){
243 case MOTION_MODE_CANCEL: break;
436a2cd1
AW
244 case MOTION_MODE_SEEK : this->append_line(gcode, target, this->seek_rate ); break;
245 case MOTION_MODE_LINEAR: this->append_line(gcode, target, this->feed_rate ); break;
df27a6a3 246 case MOTION_MODE_CW_ARC: case MOTION_MODE_CCW_ARC: this->compute_arc(gcode, offset, target ); break;
4cff3ded
AW
247 }
248 break;
249 }
13e4a3f9 250
4cff3ded
AW
251 // As far as the parser is concerned, the position is now == target. In reality the
252 // motion control system might still be processing the action and the real tool position
253 // in any intermediate location.
df27a6a3 254 memcpy(this->current_position, target, sizeof(double)*3); // this->position[] = target[];
4cff3ded 255
edac9072
AW
256}
257
5984acdf 258// We received a new gcode, and one of the functions
edac9072
AW
259// determined the distance for that given gcode. So now we can attach this gcode to the right block
260// and continue
261void Robot::distance_in_gcode_is_known(Gcode* gcode){
262
263 //If the queue is empty, execute immediatly, otherwise attach to the last added block
264 if( this->kernel->conveyor->queue.size() == 0 ){
265 this->kernel->call_event(ON_GCODE_EXECUTE, gcode );
266 }else{
267 Block* block = this->kernel->conveyor->queue.get_ref( this->kernel->conveyor->queue.size() - 1 );
268 block->append_gcode(gcode);
269 }
270
271}
272
273// Reset the position for all axes ( used in homing and G92 stuff )
274void Robot::reset_axis_position(double position, int axis) {
275 this->last_milestone[axis] = this->current_position[axis] = position;
276 this->arm_solution->millimeters_to_steps(this->current_position, this->kernel->planner->position);
4cff3ded
AW
277}
278
edac9072 279
4cff3ded
AW
280// Convert target from millimeters to steps, and append this to the planner
281void Robot::append_milestone( double target[], double rate ){
282 int steps[3]; //Holds the result of the conversion
6bc4a00a 283
edac9072 284 // We use an arm solution object so exotic arm solutions can be used and neatly abstracted
4cff3ded 285 this->arm_solution->millimeters_to_steps( target, steps );
6bc4a00a 286
aab6cbba
AW
287 double deltas[3];
288 for(int axis=X_AXIS;axis<=Z_AXIS;axis++){deltas[axis]=target[axis]-this->last_milestone[axis];}
289
edac9072 290 // Compute how long this move moves, so we can attach it to the block for later use
df27a6a3 291 double millimeters_of_travel = sqrt( pow( deltas[X_AXIS], 2 ) + pow( deltas[Y_AXIS], 2 ) + pow( deltas[Z_AXIS], 2 ) );
7b470506 292
edac9072 293 // Do not move faster than the configured limits
7b470506 294 for(int axis=X_AXIS;axis<=Z_AXIS;axis++){
df27a6a3 295 if( this->max_speeds[axis] > 0 ){
7369629d 296 double axis_speed = ( fabs(deltas[axis]) / ( millimeters_of_travel / rate )) * seconds_per_minute;
df27a6a3
MM
297 if( axis_speed > this->max_speeds[axis] ){
298 rate = rate * ( this->max_speeds[axis] / axis_speed );
436a2cd1 299 }
7b470506
AW
300 }
301 }
4cff3ded 302
edac9072 303 // Append the block to the planner
7369629d 304 this->kernel->planner->append_block( steps, rate * seconds_per_minute, millimeters_of_travel, deltas );
4cff3ded 305
edac9072 306 // Update the last_milestone to the current target for the next time we use last_milestone
df27a6a3 307 memcpy(this->last_milestone, target, sizeof(double)*3); // this->last_milestone[] = target[];
4cff3ded
AW
308
309}
310
edac9072 311// Append a move to the queue ( cutting it into segments if needed )
436a2cd1 312void Robot::append_line(Gcode* gcode, double target[], double rate ){
4cff3ded 313
edac9072 314 // Find out the distance for this gcode
df27a6a3 315 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 316
edac9072 317 // We ignore non-moves ( for example, extruder moves are not XYZ moves )
5dcb2ff3 318 if( gcode->millimeters_of_travel < 0.0001 ){ return; }
436a2cd1 319
edac9072 320 // Mark the gcode as having a known distance
5dcb2ff3 321 this->distance_in_gcode_is_known( gcode );
436a2cd1 322
4a0c8e14
JM
323 // We cut the line into smaller segments. This is not usefull in a cartesian robot, but necessary for robots with rotational axes.
324 // In cartesian robot, a high "mm_per_line_segment" setting will prevent waste.
325 // In delta robots either mm_per_line_segment can be used OR delta_segments_per_second The latter is more efficient and avoids splitting fast long lines into very small segments, like initial z move to 0, it is what Johanns Marlin delta port does
4a0c8e14 326 uint16_t segments;
5984acdf 327
4a0c8e14
JM
328 if(this->delta_segments_per_second > 1.0) {
329 // enabled if set to something > 1, it is set to 0.0 by default
330 // segment based on current speed and requested segments per second
331 // the faster the travel speed the fewer segments needed
332 // NOTE rate is mm/sec and we take into account any speed override
333 float seconds = 60.0/seconds_per_minute * gcode->millimeters_of_travel / rate;
334 segments= max(1, ceil(this->delta_segments_per_second * seconds));
335 // TODO if we are only moving in Z on a delta we don't really need to segment at all
5984acdf 336
4a0c8e14
JM
337 }else{
338 if(this->mm_per_line_segment == 0.0){
339 segments= 1; // don't split it up
340 }else{
341 segments = ceil( gcode->millimeters_of_travel/ this->mm_per_line_segment);
342 }
343 }
5984acdf 344
4cff3ded
AW
345 // A vector to keep track of the endpoint of each segment
346 double temp_target[3];
347 //Initialize axes
df27a6a3 348 memcpy( temp_target, this->current_position, sizeof(double)*3); // temp_target[] = this->current_position[];
4cff3ded
AW
349
350 //For each segment
351 for( int i=0; i<segments-1; i++ ){
df27a6a3 352 for(int axis=X_AXIS; axis <= Z_AXIS; axis++ ){ temp_target[axis] += ( target[axis]-this->current_position[axis] )/segments; }
5984acdf 353 // Append the end of this segment to the queue
df27a6a3 354 this->append_milestone(temp_target, rate);
4cff3ded 355 }
5984acdf
MM
356
357 // Append the end of this full move to the queue
4cff3ded
AW
358 this->append_milestone(target, rate);
359}
360
4cff3ded 361
edac9072 362// Append an arc to the queue ( cutting it into segments as needed )
436a2cd1 363void Robot::append_arc(Gcode* gcode, double target[], double offset[], double radius, bool is_clockwise ){
aab6cbba 364
edac9072 365 // Scary math
aab6cbba
AW
366 double center_axis0 = this->current_position[this->plane_axis_0] + offset[this->plane_axis_0];
367 double center_axis1 = this->current_position[this->plane_axis_1] + offset[this->plane_axis_1];
368 double linear_travel = target[this->plane_axis_2] - this->current_position[this->plane_axis_2];
369 double r_axis0 = -offset[this->plane_axis_0]; // Radius vector from center to current location
370 double r_axis1 = -offset[this->plane_axis_1];
371 double rt_axis0 = target[this->plane_axis_0] - center_axis0;
372 double rt_axis1 = target[this->plane_axis_1] - center_axis1;
373
374 // CCW angle between position and target from circle center. Only one atan2() trig computation required.
375 double angular_travel = atan2(r_axis0*rt_axis1-r_axis1*rt_axis0, r_axis0*rt_axis0+r_axis1*rt_axis1);
376 if (angular_travel < 0) { angular_travel += 2*M_PI; }
377 if (is_clockwise) { angular_travel -= 2*M_PI; }
378
edac9072 379 // Find the distance for this gcode
436a2cd1
AW
380 gcode->millimeters_of_travel = hypot(angular_travel*radius, fabs(linear_travel));
381
edac9072 382 // We don't care about non-XYZ moves ( for example the extruder produces some of those )
5dcb2ff3
AW
383 if( gcode->millimeters_of_travel < 0.0001 ){ return; }
384
edac9072 385 // Mark the gcode as having a known distance
d149c730 386 this->distance_in_gcode_is_known( gcode );
5984acdf
MM
387
388 // Figure out how many segments for this gcode
436a2cd1 389 uint16_t segments = floor(gcode->millimeters_of_travel/this->mm_per_arc_segment);
aab6cbba
AW
390
391 double theta_per_segment = angular_travel/segments;
392 double linear_per_segment = linear_travel/segments;
393
394 /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
395 and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
396 r_T = [cos(phi) -sin(phi);
397 sin(phi) cos(phi] * r ;
398 For arc generation, the center of the circle is the axis of rotation and the radius vector is
399 defined from the circle center to the initial position. Each line segment is formed by successive
400 vector rotations. This requires only two cos() and sin() computations to form the rotation
401 matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
402 all double numbers are single precision on the Arduino. (True double precision will not have
403 round off issues for CNC applications.) Single precision error can accumulate to be greater than
404 tool precision in some cases. Therefore, arc path correction is implemented.
405
406 Small angle approximation may be used to reduce computation overhead further. This approximation
407 holds for everything, but very small circles and large mm_per_arc_segment values. In other words,
408 theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
409 to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
410 numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
411 issue for CNC machines with the single precision Arduino calculations.
412 This approximation also allows mc_arc to immediately insert a line segment into the planner
413 without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
414 a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead.
415 This is important when there are successive arc motions.
416 */
417 // Vector rotation matrix values
418 double cos_T = 1-0.5*theta_per_segment*theta_per_segment; // Small angle approximation
419 double sin_T = theta_per_segment;
420
421 double arc_target[3];
422 double sin_Ti;
423 double cos_Ti;
424 double r_axisi;
425 uint16_t i;
426 int8_t count = 0;
427
428 // Initialize the linear axis
429 arc_target[this->plane_axis_2] = this->current_position[this->plane_axis_2];
430
431 for (i = 1; i<segments; i++) { // Increment (segments-1)
432
b66fb830 433 if (count < this->arc_correction ) {
aab6cbba
AW
434 // Apply vector rotation matrix
435 r_axisi = r_axis0*sin_T + r_axis1*cos_T;
436 r_axis0 = r_axis0*cos_T - r_axis1*sin_T;
437 r_axis1 = r_axisi;
438 count++;
439 } else {
440 // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
441 // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
442 cos_Ti = cos(i*theta_per_segment);
443 sin_Ti = sin(i*theta_per_segment);
444 r_axis0 = -offset[this->plane_axis_0]*cos_Ti + offset[this->plane_axis_1]*sin_Ti;
445 r_axis1 = -offset[this->plane_axis_0]*sin_Ti - offset[this->plane_axis_1]*cos_Ti;
446 count = 0;
447 }
448
449 // Update arc_target location
450 arc_target[this->plane_axis_0] = center_axis0 + r_axis0;
451 arc_target[this->plane_axis_1] = center_axis1 + r_axis1;
452 arc_target[this->plane_axis_2] += linear_per_segment;
edac9072
AW
453
454 // Append this segment to the queue
aab6cbba
AW
455 this->append_milestone(arc_target, this->feed_rate);
456
457 }
edac9072 458
aab6cbba
AW
459 // Ensure last segment arrives at target location.
460 this->append_milestone(target, this->feed_rate);
461}
462
edac9072 463// Do the math for an arc and add it to the queue
436a2cd1 464void Robot::compute_arc(Gcode* gcode, double offset[], double target[]){
aab6cbba
AW
465
466 // Find the radius
467 double radius = hypot(offset[this->plane_axis_0], offset[this->plane_axis_1]);
468
469 // Set clockwise/counter-clockwise sign for mc_arc computations
470 bool is_clockwise = false;
df27a6a3 471 if( this->motion_mode == MOTION_MODE_CW_ARC ){ is_clockwise = true; }
aab6cbba
AW
472
473 // Append arc
436a2cd1 474 this->append_arc(gcode, target, offset, radius, is_clockwise );
aab6cbba
AW
475
476}
477
478
4cff3ded
AW
479double Robot::theta(double x, double y){
480 double t = atan(x/fabs(y));
481 if (y>0) {return(t);} else {if (t>0){return(M_PI-t);} else {return(-M_PI-t);}}
482}
483
484void Robot::select_plane(uint8_t axis_0, uint8_t axis_1, uint8_t axis_2){
485 this->plane_axis_0 = axis_0;
486 this->plane_axis_1 = axis_1;
487 this->plane_axis_2 = axis_2;
488}
489
490