add break to M 204
[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);
b55cfff1
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 109void Robot::on_get_public_data(void* argument){
b55cfff1
JM
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 }
5647f709
JM
128}
129
130void Robot::on_set_public_data(void* argument){
b55cfff1 131 PublicDataRequest* pdr = static_cast<PublicDataRequest*>(argument);
5647f709 132
b55cfff1 133 if(!pdr->starts_with(robot_checksum)) return;
5647f709 134
b55cfff1
JM
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 }
5647f709
JM
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;
2da840d3
JM
207 case 204: // M204 Snnn - set acceleration to nnn, NB only Snnn is currently supported
208 gcode->mark_as_taken();
209 if (gcode->has_letter('S'))
210 {
211 double acc= gcode->get_value('S');
212 // enforce minimum
213 if (acc < 1.0)
214 acc = 1.0;
215 this->kernel->planner->acceleration= acc;
216 }
b4f56013 217 break;
2da840d3 218
7369629d 219 case 220: // M220 - speed override percentage
74b6303c 220 gcode->mark_as_taken();
7369629d
MM
221 if (gcode->has_letter('S'))
222 {
223 double factor = gcode->get_value('S');
224 // enforce minimum 1% speed
225 if (factor < 1.0)
226 factor = 1.0;
227 seconds_per_minute = factor * 0.6;
228 }
b4f56013 229 break;
6989211c 230 }
c83887ea
MM
231 }
232 if( this->motion_mode < 0)
233 return;
6bc4a00a 234
4cff3ded
AW
235 //Get parameters
236 double target[3], offset[3];
df27a6a3 237 clear_vector(target); clear_vector(offset);
6bc4a00a 238
4cff3ded 239 memcpy(target, this->current_position, sizeof(target)); //default to last target
6bc4a00a 240
df27a6a3 241 for(char letter = 'I'; letter <= 'K'; letter++){ if( gcode->has_letter(letter) ){ offset[letter-'I'] = this->to_millimeters(gcode->get_value(letter)); } }
a63da33c 242 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 243
7369629d
MM
244 if( gcode->has_letter('F') )
245 {
246 if( this->motion_mode == MOTION_MODE_SEEK )
247 this->seek_rate = this->to_millimeters( gcode->get_value('F') ) / 60.0;
248 else
249 this->feed_rate = this->to_millimeters( gcode->get_value('F') ) / 60.0;
250 }
6bc4a00a 251
4cff3ded
AW
252 //Perform any physical actions
253 switch( next_action ){
254 case NEXT_ACTION_DEFAULT:
255 switch(this->motion_mode){
256 case MOTION_MODE_CANCEL: break;
436a2cd1
AW
257 case MOTION_MODE_SEEK : this->append_line(gcode, target, this->seek_rate ); break;
258 case MOTION_MODE_LINEAR: this->append_line(gcode, target, this->feed_rate ); break;
df27a6a3 259 case MOTION_MODE_CW_ARC: case MOTION_MODE_CCW_ARC: this->compute_arc(gcode, offset, target ); break;
4cff3ded
AW
260 }
261 break;
262 }
13e4a3f9 263
4cff3ded
AW
264 // As far as the parser is concerned, the position is now == target. In reality the
265 // motion control system might still be processing the action and the real tool position
266 // in any intermediate location.
df27a6a3 267 memcpy(this->current_position, target, sizeof(double)*3); // this->position[] = target[];
4cff3ded 268
edac9072
AW
269}
270
5984acdf 271// We received a new gcode, and one of the functions
edac9072
AW
272// determined the distance for that given gcode. So now we can attach this gcode to the right block
273// and continue
274void Robot::distance_in_gcode_is_known(Gcode* gcode){
275
276 //If the queue is empty, execute immediatly, otherwise attach to the last added block
277 if( this->kernel->conveyor->queue.size() == 0 ){
278 this->kernel->call_event(ON_GCODE_EXECUTE, gcode );
279 }else{
280 Block* block = this->kernel->conveyor->queue.get_ref( this->kernel->conveyor->queue.size() - 1 );
281 block->append_gcode(gcode);
282 }
283
284}
285
286// Reset the position for all axes ( used in homing and G92 stuff )
287void Robot::reset_axis_position(double position, int axis) {
288 this->last_milestone[axis] = this->current_position[axis] = position;
289 this->arm_solution->millimeters_to_steps(this->current_position, this->kernel->planner->position);
4cff3ded
AW
290}
291
edac9072 292
4cff3ded
AW
293// Convert target from millimeters to steps, and append this to the planner
294void Robot::append_milestone( double target[], double rate ){
295 int steps[3]; //Holds the result of the conversion
6bc4a00a 296
edac9072 297 // We use an arm solution object so exotic arm solutions can be used and neatly abstracted
4cff3ded 298 this->arm_solution->millimeters_to_steps( target, steps );
6bc4a00a 299
aab6cbba
AW
300 double deltas[3];
301 for(int axis=X_AXIS;axis<=Z_AXIS;axis++){deltas[axis]=target[axis]-this->last_milestone[axis];}
302
edac9072 303 // Compute how long this move moves, so we can attach it to the block for later use
df27a6a3 304 double millimeters_of_travel = sqrt( pow( deltas[X_AXIS], 2 ) + pow( deltas[Y_AXIS], 2 ) + pow( deltas[Z_AXIS], 2 ) );
7b470506 305
edac9072 306 // Do not move faster than the configured limits
7b470506 307 for(int axis=X_AXIS;axis<=Z_AXIS;axis++){
df27a6a3 308 if( this->max_speeds[axis] > 0 ){
7369629d 309 double axis_speed = ( fabs(deltas[axis]) / ( millimeters_of_travel / rate )) * seconds_per_minute;
df27a6a3
MM
310 if( axis_speed > this->max_speeds[axis] ){
311 rate = rate * ( this->max_speeds[axis] / axis_speed );
436a2cd1 312 }
7b470506
AW
313 }
314 }
4cff3ded 315
edac9072 316 // Append the block to the planner
7369629d 317 this->kernel->planner->append_block( steps, rate * seconds_per_minute, millimeters_of_travel, deltas );
4cff3ded 318
edac9072 319 // Update the last_milestone to the current target for the next time we use last_milestone
df27a6a3 320 memcpy(this->last_milestone, target, sizeof(double)*3); // this->last_milestone[] = target[];
4cff3ded
AW
321
322}
323
edac9072 324// Append a move to the queue ( cutting it into segments if needed )
436a2cd1 325void Robot::append_line(Gcode* gcode, double target[], double rate ){
4cff3ded 326
edac9072 327 // Find out the distance for this gcode
df27a6a3 328 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 329
edac9072 330 // We ignore non-moves ( for example, extruder moves are not XYZ moves )
5dcb2ff3 331 if( gcode->millimeters_of_travel < 0.0001 ){ return; }
436a2cd1 332
edac9072 333 // Mark the gcode as having a known distance
5dcb2ff3 334 this->distance_in_gcode_is_known( gcode );
436a2cd1 335
4a0c8e14
JM
336 // We cut the line into smaller segments. This is not usefull in a cartesian robot, but necessary for robots with rotational axes.
337 // In cartesian robot, a high "mm_per_line_segment" setting will prevent waste.
338 // 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 339 uint16_t segments;
5984acdf 340
4a0c8e14
JM
341 if(this->delta_segments_per_second > 1.0) {
342 // enabled if set to something > 1, it is set to 0.0 by default
343 // segment based on current speed and requested segments per second
344 // the faster the travel speed the fewer segments needed
345 // NOTE rate is mm/sec and we take into account any speed override
346 float seconds = 60.0/seconds_per_minute * gcode->millimeters_of_travel / rate;
347 segments= max(1, ceil(this->delta_segments_per_second * seconds));
348 // TODO if we are only moving in Z on a delta we don't really need to segment at all
5984acdf 349
4a0c8e14
JM
350 }else{
351 if(this->mm_per_line_segment == 0.0){
352 segments= 1; // don't split it up
353 }else{
354 segments = ceil( gcode->millimeters_of_travel/ this->mm_per_line_segment);
355 }
356 }
5984acdf 357
4cff3ded
AW
358 // A vector to keep track of the endpoint of each segment
359 double temp_target[3];
360 //Initialize axes
df27a6a3 361 memcpy( temp_target, this->current_position, sizeof(double)*3); // temp_target[] = this->current_position[];
4cff3ded
AW
362
363 //For each segment
364 for( int i=0; i<segments-1; i++ ){
df27a6a3 365 for(int axis=X_AXIS; axis <= Z_AXIS; axis++ ){ temp_target[axis] += ( target[axis]-this->current_position[axis] )/segments; }
5984acdf 366 // Append the end of this segment to the queue
df27a6a3 367 this->append_milestone(temp_target, rate);
4cff3ded 368 }
5984acdf
MM
369
370 // Append the end of this full move to the queue
4cff3ded
AW
371 this->append_milestone(target, rate);
372}
373
4cff3ded 374
edac9072 375// Append an arc to the queue ( cutting it into segments as needed )
436a2cd1 376void Robot::append_arc(Gcode* gcode, double target[], double offset[], double radius, bool is_clockwise ){
aab6cbba 377
edac9072 378 // Scary math
aab6cbba
AW
379 double center_axis0 = this->current_position[this->plane_axis_0] + offset[this->plane_axis_0];
380 double center_axis1 = this->current_position[this->plane_axis_1] + offset[this->plane_axis_1];
381 double linear_travel = target[this->plane_axis_2] - this->current_position[this->plane_axis_2];
382 double r_axis0 = -offset[this->plane_axis_0]; // Radius vector from center to current location
383 double r_axis1 = -offset[this->plane_axis_1];
384 double rt_axis0 = target[this->plane_axis_0] - center_axis0;
385 double rt_axis1 = target[this->plane_axis_1] - center_axis1;
386
387 // CCW angle between position and target from circle center. Only one atan2() trig computation required.
388 double angular_travel = atan2(r_axis0*rt_axis1-r_axis1*rt_axis0, r_axis0*rt_axis0+r_axis1*rt_axis1);
389 if (angular_travel < 0) { angular_travel += 2*M_PI; }
390 if (is_clockwise) { angular_travel -= 2*M_PI; }
391
edac9072 392 // Find the distance for this gcode
436a2cd1
AW
393 gcode->millimeters_of_travel = hypot(angular_travel*radius, fabs(linear_travel));
394
edac9072 395 // We don't care about non-XYZ moves ( for example the extruder produces some of those )
5dcb2ff3
AW
396 if( gcode->millimeters_of_travel < 0.0001 ){ return; }
397
edac9072 398 // Mark the gcode as having a known distance
d149c730 399 this->distance_in_gcode_is_known( gcode );
5984acdf
MM
400
401 // Figure out how many segments for this gcode
436a2cd1 402 uint16_t segments = floor(gcode->millimeters_of_travel/this->mm_per_arc_segment);
aab6cbba
AW
403
404 double theta_per_segment = angular_travel/segments;
405 double linear_per_segment = linear_travel/segments;
406
407 /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
408 and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
409 r_T = [cos(phi) -sin(phi);
410 sin(phi) cos(phi] * r ;
411 For arc generation, the center of the circle is the axis of rotation and the radius vector is
412 defined from the circle center to the initial position. Each line segment is formed by successive
413 vector rotations. This requires only two cos() and sin() computations to form the rotation
414 matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
415 all double numbers are single precision on the Arduino. (True double precision will not have
416 round off issues for CNC applications.) Single precision error can accumulate to be greater than
417 tool precision in some cases. Therefore, arc path correction is implemented.
418
419 Small angle approximation may be used to reduce computation overhead further. This approximation
420 holds for everything, but very small circles and large mm_per_arc_segment values. In other words,
421 theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
422 to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
423 numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
424 issue for CNC machines with the single precision Arduino calculations.
425 This approximation also allows mc_arc to immediately insert a line segment into the planner
426 without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
427 a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead.
428 This is important when there are successive arc motions.
429 */
430 // Vector rotation matrix values
431 double cos_T = 1-0.5*theta_per_segment*theta_per_segment; // Small angle approximation
432 double sin_T = theta_per_segment;
433
434 double arc_target[3];
435 double sin_Ti;
436 double cos_Ti;
437 double r_axisi;
438 uint16_t i;
439 int8_t count = 0;
440
441 // Initialize the linear axis
442 arc_target[this->plane_axis_2] = this->current_position[this->plane_axis_2];
443
444 for (i = 1; i<segments; i++) { // Increment (segments-1)
445
b66fb830 446 if (count < this->arc_correction ) {
aab6cbba
AW
447 // Apply vector rotation matrix
448 r_axisi = r_axis0*sin_T + r_axis1*cos_T;
449 r_axis0 = r_axis0*cos_T - r_axis1*sin_T;
450 r_axis1 = r_axisi;
451 count++;
452 } else {
453 // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
454 // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
455 cos_Ti = cos(i*theta_per_segment);
456 sin_Ti = sin(i*theta_per_segment);
457 r_axis0 = -offset[this->plane_axis_0]*cos_Ti + offset[this->plane_axis_1]*sin_Ti;
458 r_axis1 = -offset[this->plane_axis_0]*sin_Ti - offset[this->plane_axis_1]*cos_Ti;
459 count = 0;
460 }
461
462 // Update arc_target location
463 arc_target[this->plane_axis_0] = center_axis0 + r_axis0;
464 arc_target[this->plane_axis_1] = center_axis1 + r_axis1;
465 arc_target[this->plane_axis_2] += linear_per_segment;
edac9072
AW
466
467 // Append this segment to the queue
aab6cbba
AW
468 this->append_milestone(arc_target, this->feed_rate);
469
470 }
edac9072 471
aab6cbba
AW
472 // Ensure last segment arrives at target location.
473 this->append_milestone(target, this->feed_rate);
474}
475
edac9072 476// Do the math for an arc and add it to the queue
436a2cd1 477void Robot::compute_arc(Gcode* gcode, double offset[], double target[]){
aab6cbba
AW
478
479 // Find the radius
480 double radius = hypot(offset[this->plane_axis_0], offset[this->plane_axis_1]);
481
482 // Set clockwise/counter-clockwise sign for mc_arc computations
483 bool is_clockwise = false;
df27a6a3 484 if( this->motion_mode == MOTION_MODE_CW_ARC ){ is_clockwise = true; }
aab6cbba
AW
485
486 // Append arc
436a2cd1 487 this->append_arc(gcode, target, offset, radius, is_clockwise );
aab6cbba
AW
488
489}
490
491
4cff3ded
AW
492double Robot::theta(double x, double y){
493 double t = atan(x/fabs(y));
494 if (y>0) {return(t);} else {if (t>0){return(M_PI-t);} else {return(-M_PI-t);}}
495}
496
497void Robot::select_plane(uint8_t axis_0, uint8_t axis_1, uint8_t axis_2){
498 this->plane_axis_0 = axis_0;
499 this->plane_axis_1 = axis_1;
500 this->plane_axis_2 = axis_2;
501}
502
503