tp.c
1 /******************************************************************** 2 * Description: tp.c 3 * Trajectory planner based on TC elements 4 * 5 * Derived from a work by Fred Proctor & Will Shackleford 6 * 7 * Author: 8 * License: GPL Version 2 9 * System: Linux 10 * 11 * Copyright (c) 2004 All rights reserved. 12 ********************************************************************/ 13 #include "rtapi.h" /* rtapi_print_msg */ 14 #include "posemath.h" /* Geometry types & functions */ 15 #include "tc.h" 16 #include "tp.h" 17 #include "emcpose.h" 18 #include "rtapi_math.h" 19 #include "mot_priv.h" 20 #include "motion_debug.h" 21 #include "motion_types.h" 22 #include "spherical_arc.h" 23 #include "blendmath.h" 24 //KLUDGE Don't include all of emc.hh here, just hand-copy the TERM COND 25 //definitions until we can break the emc constants out into a separate file. 26 //#include "emc.hh" 27 #define EMC_TRAJ_TERM_COND_STOP 0 28 #define EMC_TRAJ_TERM_COND_EXACT 1 29 #define EMC_TRAJ_TERM_COND_BLEND 2 30 31 /** 32 * @section tpdebugflags TP debugging flags 33 * Enable / disable various debugging functions here. 34 * These flags control debug printing from RTAPI. These functions are 35 * admittedly kludged on top of the existing rtapi_print framework. As written, 36 * though, it's an easy way to selectively compile functions as static or not, 37 * and selectively compile in assertions and debug printing. 38 */ 39 40 #include "tp_debug.h" 41 42 // FIXME: turn off this feature, which causes blends between rapids to 43 // use the feed override instead of the rapid override 44 #undef TP_SHOW_BLENDS 45 46 #define TP_OPTIMIZATION_LAZY 47 48 extern emcmot_status_t *emcmotStatus; 49 extern emcmot_debug_t *emcmotDebug; 50 extern emcmot_config_t *emcmotConfig; 51 52 /** static function primitives (ugly but less of a pain than moving code around)*/ 53 STATIC int tpComputeBlendVelocity( 54 TC_STRUCT const *tc, 55 TC_STRUCT const *nexttc, 56 double v_target_this, 57 double v_target_next, 58 double *v_blend_this, 59 double *v_blend_next, 60 double *v_blend_net); 61 62 STATIC double estimateParabolicBlendPerformance( 63 TP_STRUCT const *tp, 64 TC_STRUCT const *tc, 65 TC_STRUCT const *nexttc); 66 67 STATIC int tpCheckEndCondition(TP_STRUCT const * const tp, TC_STRUCT * const tc, TC_STRUCT const * const nexttc); 68 69 STATIC int tpUpdateCycle(TP_STRUCT * const tp, 70 TC_STRUCT * const tc, TC_STRUCT const * const nexttc); 71 72 STATIC int tpRunOptimization(TP_STRUCT * const tp); 73 74 STATIC inline int tpAddSegmentToQueue(TP_STRUCT * const tp, TC_STRUCT * const tc, int inc_id); 75 76 STATIC inline double tpGetMaxTargetVel(TP_STRUCT const * const tp, TC_STRUCT const * const tc); 77 78 /** 79 * @section tpcheck Internal state check functions. 80 * These functions compartmentalize some of the messy state checks. 81 * Hopefully this makes changes easier to track as much of the churn will be on small functions. 82 */ 83 84 /** 85 * Returns true if there is motion along ABC or UVW axes, false otherwise. 86 */ 87 STATIC int tcRotaryMotionCheck(TC_STRUCT const * const tc) { 88 switch (tc->motion_type) { 89 //Note lack of break statements due to every path returning 90 case TC_RIGIDTAP: 91 return false; 92 case TC_LINEAR: 93 if (tc->coords.line.abc.tmag_zero && tc->coords.line.uvw.tmag_zero) { 94 return false; 95 } else { 96 return true; 97 } 98 case TC_CIRCULAR: 99 if (tc->coords.circle.abc.tmag_zero && tc->coords.circle.uvw.tmag_zero) { 100 return false; 101 } else { 102 return true; 103 } 104 case TC_SPHERICAL: 105 return true; 106 default: 107 tp_debug_print("Unknown motion type!\n"); 108 return false; 109 } 110 } 111 112 113 /** 114 * @section tpgetset Internal Get/Set functions 115 * @brief Calculation / status functions for commonly used values. 116 * These functions return the "actual" values of things like a trajectory 117 * segment's feed override, while taking into account the status of tp itself. 118 */ 119 120 121 122 /** 123 * Wrapper to bounds-check the tangent kink ratio from HAL. 124 */ 125 STATIC double tpGetTangentKinkRatio(void) { 126 const double max_ratio = 0.7071; 127 const double min_ratio = 0.001; 128 129 return fmax(fmin(emcmotConfig->arcBlendTangentKinkRatio,max_ratio),min_ratio); 130 } 131 132 133 STATIC int tpGetMachineAccelBounds(PmCartesian * const acc_bound) { 134 if (!acc_bound) { 135 return TP_ERR_FAIL; 136 } 137 138 acc_bound->x = emcmotDebug->axes[0].acc_limit; //0==>x 139 acc_bound->y = emcmotDebug->axes[1].acc_limit; //1==>y 140 acc_bound->z = emcmotDebug->axes[2].acc_limit; //2==>z 141 return TP_ERR_OK; 142 } 143 144 145 STATIC int tpGetMachineVelBounds(PmCartesian * const vel_bound) { 146 if (!vel_bound) { 147 return TP_ERR_FAIL; 148 } 149 150 vel_bound->x = emcmotDebug->axes[0].vel_limit; //0==>x 151 vel_bound->y = emcmotDebug->axes[1].vel_limit; //1==>y 152 vel_bound->z = emcmotDebug->axes[2].vel_limit; //2==>z 153 return TP_ERR_OK; 154 } 155 156 STATIC int tpGetMachineActiveLimit(double * const act_limit, PmCartesian const * const bounds) { 157 if (!act_limit) { 158 return TP_ERR_FAIL; 159 } 160 //Start with max accel value 161 *act_limit = fmax(fmax(bounds->x,bounds->y),bounds->z); 162 163 // Compare only with active axes 164 if (bounds->x > 0) { 165 *act_limit = fmin(*act_limit, bounds->x); 166 } 167 if (bounds->y > 0) { 168 *act_limit = fmin(*act_limit, bounds->y); 169 } 170 if (bounds->z > 0) { 171 *act_limit = fmin(*act_limit, bounds->z); 172 } 173 tp_debug_print(" arc blending a_max=%f\n", *act_limit); 174 return TP_ERR_OK; 175 } 176 177 178 /** 179 * Get a segment's feed scale based on the current planner state and emcmotStatus. 180 * @note depends on emcmotStatus for system information. 181 */ 182 STATIC double tpGetFeedScale(TP_STRUCT const * const tp, 183 TC_STRUCT const * const tc) { 184 if (!tc) { 185 return 0.0; 186 } 187 //All reasons to disable feed override go here 188 bool pausing = tp->pausing && (tc->synchronized == TC_SYNC_NONE || tc->synchronized == TC_SYNC_VELOCITY); 189 bool aborting = tp->aborting; 190 if (pausing) { 191 tc_debug_print("pausing\n"); 192 return 0.0; 193 } else if (aborting) { 194 tc_debug_print("aborting\n"); 195 return 0.0; 196 } else if (tc->synchronized == TC_SYNC_POSITION ) { 197 return 1.0; 198 } else if (tc->is_blending) { 199 //KLUDGE: Don't allow feed override to keep blending from overruning max velocity 200 return fmin(emcmotStatus->net_feed_scale, 1.0); 201 } else { 202 return emcmotStatus->net_feed_scale; 203 } 204 } 205 206 207 /** 208 * Get target velocity for a tc based on the trajectory planner state. 209 * This gives the requested velocity, capped by the segments maximum velocity. 210 */ 211 STATIC inline double tpGetRealTargetVel(TP_STRUCT const * const tp, 212 TC_STRUCT const * const tc) { 213 214 if (!tc) { 215 return 0.0; 216 } 217 // Start with the scaled target velocity based on the current feed scale 218 double v_target = tc->synchronized ? tc->target_vel : tc->reqvel; 219 /*tc_debug_print("Initial v_target = %f\n",v_target);*/ 220 221 // Get the maximum allowed target velocity, and make sure we're below it 222 return fmin(v_target * tpGetFeedScale(tp,tc), tpGetMaxTargetVel(tp, tc)); 223 } 224 225 226 STATIC inline double getMaxFeedScale(TC_STRUCT const * tc) 227 { 228 //All reasons to disable feed override go here 229 if (tc && tc->synchronized == TC_SYNC_POSITION ) { 230 return 1.0; 231 } else { 232 return emcmotConfig->maxFeedScale; 233 } 234 } 235 236 237 /** 238 * Get the worst-case target velocity for a segment based on the trajectory planner state. 239 * Note that this factors in the user-specified velocity limit. 240 */ 241 STATIC inline double tpGetMaxTargetVel(TP_STRUCT const * const tp, TC_STRUCT const * const tc) 242 { 243 double max_scale = emcmotConfig->maxFeedScale; 244 if (tc->is_blending) { 245 //KLUDGE: Don't allow feed override to keep blending from overruning max velocity 246 max_scale = fmin(max_scale, 1.0); 247 } 248 // Get maximum reachable velocity from max feed override 249 double v_max_target = tc->target_vel * max_scale; 250 251 /* Check if the cartesian velocity limit applies and clip the maximum 252 * velocity. The vLimit is from the max velocity slider, and should 253 * restrict the maximum velocity during non-synced moves and velocity 254 * synchronization. However, position-synced moves have the target velocity 255 * computed in the TP, so it would disrupt position tracking to apply this 256 * limit here. 257 */ 258 if (!tcPureRotaryCheck(tc) && (tc->synchronized != TC_SYNC_POSITION)){ 259 /*tc_debug_print("Cartesian velocity limit active\n");*/ 260 v_max_target = fmin(v_max_target,tp->vLimit); 261 } 262 263 // Apply maximum segment velocity limit (must always be respected) 264 return fmin(v_max_target, tc->maxvel); 265 } 266 267 268 /** 269 * Get final velocity for a tc based on the trajectory planner state. 270 * This function factors in the feed override and TC limits. It clamps the 271 * final velocity to the maximum velocity and the next segment's target velocity 272 */ 273 STATIC inline double tpGetRealFinalVel(TP_STRUCT const * const tp, 274 TC_STRUCT const * const tc, TC_STRUCT const * const nexttc) { 275 /* If we're stepping, then it doesn't matter what the optimization says, we want to end at a stop. 276 * If the term_cond gets changed out from under us, detect this and force final velocity to zero 277 */ 278 if (emcmotDebug->stepping || tc->term_cond != TC_TERM_COND_TANGENT || tp->reverse_run) { 279 return 0.0; 280 } 281 282 // Get target velocities for this segment and next segment 283 double v_target_this = tpGetRealTargetVel(tp, tc); 284 double v_target_next = 0.0; 285 if (nexttc) { 286 v_target_next = tpGetRealTargetVel(tp, nexttc); 287 } 288 289 tc_debug_print("v_target_next = %f\n",v_target_next); 290 // Limit final velocity to minimum of this and next target velocities 291 double v_target = fmin(v_target_this, v_target_next); 292 return fmin(tc->finalvel, v_target); 293 } 294 295 296 /** 297 * Convert the 2-part spindle position and sign to a signed double. 298 */ 299 STATIC inline double tpGetSignedSpindlePosition(spindle_status_t *status) { 300 int spindle_dir; 301 double spindle_pos; 302 spindle_dir = status->direction; 303 spindle_pos = status->spindleRevs; 304 if (spindle_dir < 0.0) { 305 spindle_pos*=-1.0; 306 } 307 return spindle_pos; 308 } 309 310 /** 311 * @section tpaccess tp class-like API 312 */ 313 314 /** 315 * Create the trajectory planner structure with an empty queue. 316 */ 317 int tpCreate(TP_STRUCT * const tp, int _queueSize, TC_STRUCT * const tcSpace) 318 { 319 if (0 == tp) { 320 return TP_ERR_FAIL; 321 } 322 323 if (_queueSize <= 0) { 324 tp->queueSize = TP_DEFAULT_QUEUE_SIZE; 325 } else { 326 tp->queueSize = _queueSize; 327 } 328 329 /* create the queue */ 330 if (-1 == tcqCreate(&tp->queue, tp->queueSize, tcSpace)) { 331 return TP_ERR_FAIL; 332 } 333 334 /* init the rest of our data */ 335 return tpInit(tp); 336 } 337 338 /** 339 * Clears any potential DIO toggles and anychanged. 340 * If any DIOs need to be changed: dios[i] = 1, DIO needs to get turned on, -1 341 * = off 342 */ 343 int tpClearDIOs(TP_STRUCT * const tp) { 344 //XXX: All IO's will be flushed on next synced aio/dio! Is it ok? 345 int i; 346 tp->syncdio.anychanged = 0; 347 tp->syncdio.dio_mask = 0; 348 tp->syncdio.aio_mask = 0; 349 for (i = 0; i < emcmotConfig->numDIO; i++) { 350 tp->syncdio.dios[i] = 0; 351 } 352 for (i = 0; i < emcmotConfig->numAIO; i++) { 353 tp->syncdio.aios[i] = 0; 354 } 355 356 return TP_ERR_OK; 357 } 358 359 /** 360 * "Soft initialize" the trajectory planner tp. 361 * This is a "soft" initialization in that TP_STRUCT configuration 362 * parameters (cycleTime, vMax, and aMax) are left alone, but the queue is 363 * cleared, and the flags are set to an empty, ready queue. The currentPos 364 * is left alone, and goalPos is set to this position. This function is 365 * intended to put the motion queue in the state it would be if all queued 366 * motions finished at the current position. 367 */ 368 int tpClear(TP_STRUCT * const tp) 369 { 370 tcqInit(&tp->queue); 371 tp->queueSize = 0; 372 tp->goalPos = tp->currentPos; 373 tp->nextId = 0; 374 tp->execId = 0; 375 tp->motionType = 0; 376 tp->termCond = TC_TERM_COND_PARABOLIC; 377 tp->tolerance = 0.0; 378 tp->done = 1; 379 tp->depth = tp->activeDepth = 0; 380 tp->aborting = 0; 381 tp->pausing = 0; 382 tp->reverse_run = 0; 383 tp->synchronized = 0; 384 tp->uu_per_rev = 0.0; 385 emcmotStatus->current_vel = 0.0; 386 emcmotStatus->requested_vel = 0.0; 387 emcmotStatus->distance_to_go = 0.0; 388 ZERO_EMC_POSE(emcmotStatus->dtg); 389 SET_MOTION_INPOS_FLAG(1); 390 391 return tpClearDIOs(tp); 392 } 393 394 /** 395 * Fully initialize the tp structure. 396 * Sets tp configuration to default values and calls tpClear to create a fresh, 397 * empty queue. 398 */ 399 int tpInit(TP_STRUCT * const tp) 400 { 401 tp->cycleTime = 0.0; 402 //Velocity limits 403 tp->vLimit = 0.0; 404 tp->ini_maxvel = 0.0; 405 //Accelerations 406 tp->aLimit = 0.0; 407 PmCartesian acc_bound; 408 //FIXME this acceleration bound isn't valid (nor is it used) 409 tpGetMachineAccelBounds(&acc_bound); 410 tpGetMachineActiveLimit(&tp->aMax, &acc_bound); 411 //Angular limits 412 tp->wMax = 0.0; 413 tp->wDotMax = 0.0; 414 415 tp->spindle.offset = 0.0; 416 tp->spindle.revs = 0.0; 417 tp->spindle.waiting_for_index = MOTION_INVALID_ID; 418 tp->spindle.waiting_for_atspeed = MOTION_INVALID_ID; 419 420 tp->reverse_run = TC_DIR_FORWARD; 421 422 ZERO_EMC_POSE(tp->currentPos); 423 424 PmCartesian vel_bound; 425 tpGetMachineVelBounds(&vel_bound); 426 tpGetMachineActiveLimit(&tp->vMax, &vel_bound); 427 428 return tpClear(tp); 429 } 430 431 /** 432 * Set the cycle time for the trajectory planner. 433 */ 434 int tpSetCycleTime(TP_STRUCT * const tp, double secs) 435 { 436 if (0 == tp || secs <= 0.0) { 437 return TP_ERR_FAIL; 438 } 439 440 tp->cycleTime = secs; 441 442 return TP_ERR_OK; 443 } 444 445 /** 446 * Set requested velocity and absolute maximum velocity (bounded by machine). 447 * This is called before adding lines or circles, specifying vMax (the velocity 448 * requested by the F word) and ini_maxvel, the max velocity possible before 449 * meeting a machine constraint caused by an AXIS's max velocity. (the TP is 450 * allowed to go up to this high when feed override >100% is requested) These 451 * settings apply to subsequent moves until changed. 452 */ 453 int tpSetVmax(TP_STRUCT * const tp, double vMax, double ini_maxvel) 454 { 455 if (0 == tp || vMax <= 0.0 || ini_maxvel <= 0.0) { 456 return TP_ERR_FAIL; 457 } 458 459 tp->vMax = vMax; 460 tp->ini_maxvel = ini_maxvel; 461 462 return TP_ERR_OK; 463 } 464 465 /** 466 * Set the tool tip maximum velocity. 467 * This is the [TRAJ]MAX_LINEAR_VELOCITY. This should be the max velocity of 468 * const the TOOL TIP, not necessarily any particular axis. This applies to 469 * subsequent moves until changed. 470 */ 471 int tpSetVlimit(TP_STRUCT * const tp, double vLimit) 472 { 473 if (!tp) return TP_ERR_FAIL; 474 475 if (vLimit < 0.) 476 tp->vLimit = 0.; 477 else 478 tp->vLimit = vLimit; 479 480 return TP_ERR_OK; 481 } 482 483 /** Sets the max acceleration for the trajectory planner. */ 484 int tpSetAmax(TP_STRUCT * const tp, double aMax) 485 { 486 if (0 == tp || aMax <= 0.0) { 487 return TP_ERR_FAIL; 488 } 489 490 tp->aMax = aMax; 491 492 return TP_ERR_OK; 493 } 494 495 /** 496 * Sets the id that will be used for the next appended motions. 497 * nextId is incremented so that the next time a motion is appended its id will 498 * be one more than the previous one, modulo a signed int. If you want your own 499 * ids for each motion, call this before each motion you append and stick what 500 * you want in here. 501 */ 502 int tpSetId(TP_STRUCT * const tp, int id) 503 { 504 505 if (!MOTION_ID_VALID(id)) { 506 rtapi_print_msg(RTAPI_MSG_ERR, "tpSetId: invalid motion id %d\n", id); 507 return TP_ERR_FAIL; 508 } 509 510 if (0 == tp) { 511 return TP_ERR_FAIL; 512 } 513 514 tp->nextId = id; 515 516 return TP_ERR_OK; 517 } 518 519 /** Returns the id of the last motion that is currently 520 executing.*/ 521 int tpGetExecId(TP_STRUCT * const tp) 522 { 523 if (0 == tp) { 524 return TP_ERR_FAIL; 525 } 526 527 return tp->execId; 528 } 529 530 /** 531 * Sets the termination condition for all subsequent queued moves. 532 * If cond is TC_TERM_COND_STOP, motion comes to a stop before a subsequent move 533 * begins. If cond is TC_TERM_COND_PARABOLIC, the following move is begun when the 534 * current move slows below a calculated blend velocity. 535 */ 536 int tpSetTermCond(TP_STRUCT * const tp, int cond, double tolerance) 537 { 538 if (!tp) { 539 return TP_ERR_FAIL; 540 } 541 542 switch (cond) { 543 //Purposeful waterfall for now 544 case TC_TERM_COND_PARABOLIC: 545 case TC_TERM_COND_TANGENT: 546 case TC_TERM_COND_EXACT: 547 case TC_TERM_COND_STOP: 548 tp->termCond = cond; 549 tp->tolerance = tolerance; 550 break; 551 default: 552 //Invalid condition 553 return -1; 554 } 555 556 return TP_ERR_OK; 557 } 558 559 /** 560 * Used to tell the tp the initial position. 561 * It sets the current position AND the goal position to be the same. Used 562 * only at TP initialization and when switching modes. 563 */ 564 int tpSetPos(TP_STRUCT * const tp, EmcPose const * const pos) 565 { 566 if (0 == tp) { 567 return TP_ERR_FAIL; 568 } 569 570 int res_invalid = tpSetCurrentPos(tp, pos); 571 if (res_invalid) { 572 return TP_ERR_FAIL; 573 } 574 575 tp->goalPos = *pos; 576 return TP_ERR_OK; 577 } 578 579 580 /** 581 * Set current position. 582 * It sets the current position AND the goal position to be the same. Used 583 * only at TP initialization and when switching modes. 584 */ 585 int tpSetCurrentPos(TP_STRUCT * const tp, EmcPose const * const pos) 586 { 587 if (0 == tp) { 588 return TP_ERR_FAIL; 589 } 590 591 if (emcPoseValid(pos)) { 592 tp->currentPos = *pos; 593 return TP_ERR_OK; 594 } else { 595 rtapi_print_msg(RTAPI_MSG_ERR, "Tried to set invalid pose in tpSetCurrentPos on id %d!" 596 "pos is %.12g, %.12g, %.12g\n", 597 tp->execId, 598 pos->tran.x, 599 pos->tran.y, 600 pos->tran.z); 601 return TP_ERR_INVALID; 602 } 603 } 604 605 606 int tpAddCurrentPos(TP_STRUCT * const tp, EmcPose const * const disp) 607 { 608 if (!tp || !disp) { 609 return TP_ERR_MISSING_INPUT; 610 } 611 612 if (emcPoseValid(disp)) { 613 emcPoseSelfAdd(&tp->currentPos, disp); 614 return TP_ERR_OK; 615 } else { 616 rtapi_print_msg(RTAPI_MSG_ERR, "Tried to set invalid pose in tpAddCurrentPos on id %d!" 617 "disp is %.12g, %.12g, %.12g\n", 618 tp->execId, 619 disp->tran.x, 620 disp->tran.y, 621 disp->tran.z); 622 return TP_ERR_INVALID; 623 } 624 } 625 626 627 /** 628 * Check for valid tp before queueing additional moves. 629 */ 630 int tpErrorCheck(TP_STRUCT const * const tp) { 631 632 if (!tp) { 633 rtapi_print_msg(RTAPI_MSG_ERR, "TP is null\n"); 634 return TP_ERR_FAIL; 635 } 636 if (tp->aborting) { 637 rtapi_print_msg(RTAPI_MSG_ERR, "TP is aborting\n"); 638 return TP_ERR_FAIL; 639 } 640 return TP_ERR_OK; 641 } 642 643 644 /** 645 * Find the "peak" velocity a segment can achieve if its velocity profile is triangular. 646 * This is used to estimate blend velocity, though by itself is not enough 647 * (since requested velocity and max velocity could be lower). 648 */ 649 STATIC double tpCalculateTriangleVel(TC_STRUCT const *tc) { 650 //Compute peak velocity for blend calculations 651 double acc_scaled = tcGetTangentialMaxAccel(tc); 652 double length = tc->target; 653 if (!tc->finalized) { 654 // blending may remove up to 1/2 of the segment 655 length /= 2.0; 656 } 657 return findVPeak(acc_scaled, length); 658 } 659 660 661 /** 662 * Handles the special case of blending into an unfinalized segment. 663 * The problem here is that the last segment in the queue can always be cut 664 * short by a blend to the next segment. However, we can only ever consume at 665 * most 1/2 of the segment. This function computes the worst-case final 666 * velocity the previous segment can have, if we want to exactly stop at the 667 * halfway point. 668 */ 669 STATIC double tpCalculateOptimizationInitialVel(TP_STRUCT const * const tp, TC_STRUCT * const tc) 670 { 671 double acc_scaled = tcGetTangentialMaxAccel(tc); 672 double triangle_vel = findVPeak(acc_scaled, tc->target); 673 double max_vel = tpGetMaxTargetVel(tp, tc); 674 tp_debug_json_start(tpCalculateOptimizationInitialVel); 675 tp_debug_json_double(triangle_vel); 676 tp_debug_json_end(); 677 return fmin(triangle_vel, max_vel); 678 } 679 680 681 /** 682 * Initialize a blend arc from its parent lines. 683 * This copies and initializes properties from the previous and next lines to 684 * initialize a blend arc. This function does not handle connecting the 685 * segments together, however. 686 */ 687 STATIC int tpInitBlendArcFromPrev(TP_STRUCT const * const tp, 688 TC_STRUCT const * const prev_tc, 689 TC_STRUCT* const blend_tc, 690 double vel, 691 double ini_maxvel, 692 double acc) { 693 694 #ifdef TP_SHOW_BLENDS 695 int canon_motion_type = EMC_MOTION_TYPE_ARC; 696 #else 697 int canon_motion_type = prev_tc->canon_motion_type; 698 #endif 699 700 tcInit(blend_tc, 701 TC_SPHERICAL, 702 canon_motion_type, 703 tp->cycleTime, 704 prev_tc->enables, 705 false); // NOTE: blend arc never needs the atspeed flag, since the previous line will have it (and cannot be consumed). 706 707 // Copy over state data from TP 708 tcSetupState(blend_tc, tp); 709 710 // Set kinematics parameters from blend calculations 711 tcSetupMotion(blend_tc, 712 vel, 713 ini_maxvel, 714 acc); 715 716 // Skip syncdio setup since this blend extends the previous line 717 blend_tc->syncdio = prev_tc->syncdio; //enqueue the list of DIOs that need toggling 718 719 // find "helix" length for target 720 double length; 721 arcLength(&blend_tc->coords.arc.xyz, &length); 722 tp_info_print("blend tc length = %f\n",length); 723 blend_tc->target = length; 724 blend_tc->nominal_length = length; 725 726 // Set the blend arc to be tangent to the next segment 727 tcSetTermCond(blend_tc, NULL, TC_TERM_COND_TANGENT); 728 729 //NOTE: blend arc radius and everything else is finalized, so set this to 1. 730 //In the future, radius may be adjustable. 731 tcFinalizeLength(blend_tc); 732 733 return TP_ERR_OK; 734 } 735 736 STATIC int tcSetLineXYZ(TC_STRUCT * const tc, PmCartLine const * const line) 737 { 738 739 //Update targets with new arc length 740 if (!line || tc->motion_type != TC_LINEAR) { 741 return TP_ERR_FAIL; 742 } 743 if (!tc->coords.line.abc.tmag_zero || !tc->coords.line.uvw.tmag_zero) { 744 rtapi_print_msg(RTAPI_MSG_ERR, "SetLineXYZ does not supportABC or UVW motion\n"); 745 return TP_ERR_FAIL; 746 } 747 748 tc->coords.line.xyz = *line; 749 tc->target = line->tmag; 750 return TP_ERR_OK; 751 } 752 753 754 static inline int find_max_element(double arr[], int sz) 755 { 756 if (sz < 1) { 757 return -1; 758 } 759 // Assumes at least one element 760 int max_idx = 0; 761 int idx; 762 for (idx = 0; idx < sz; ++idx) { 763 if (arr[idx] > arr[max_idx]) { 764 max_idx = idx; 765 } 766 } 767 return max_idx; 768 } 769 770 /** 771 * Compare performance of blend arc and equivalent tangent speed. 772 * If we can go faster by assuming the segments are already tangent (and 773 * slowing down some), then prefer this over using the blend arc. This is 774 * mostly useful for some odd arc-to-arc cases where the blend arc becomes very 775 * short (and therefore slow). 776 */ 777 STATIC tc_blend_type_t tpChooseBestBlend(TP_STRUCT const * const tp, 778 TC_STRUCT * const prev_tc, 779 TC_STRUCT * const tc, 780 TC_STRUCT * const blend_tc) 781 { 782 if (!tc || !prev_tc) { 783 return NO_BLEND; 784 } 785 786 // Can't blend segments that are explicitly disallowed 787 switch (prev_tc->term_cond) 788 { 789 case TC_TERM_COND_EXACT: 790 case TC_TERM_COND_STOP: 791 return NO_BLEND; 792 } 793 794 // Compute performance measures ("perf_xxx") for each method. This is 795 // basically the blend velocity. However, because parabolic blends require 796 // halving the acceleration of both blended segments, they in effect slow 797 // down the next and previous blends as well. We model this loss by scaling 798 // the blend velocity down to find an "equivalent" velocity. 799 double perf_parabolic = estimateParabolicBlendPerformance(tp, prev_tc, tc) / 2.0; 800 double perf_tangent = prev_tc->kink_vel; 801 double perf_arc_blend = blend_tc ? blend_tc->maxvel : 0.0; 802 803 tp_debug_print("Blend performance: parabolic %f, tangent %f, arc_blend %f, ", 804 perf_parabolic, 805 perf_tangent, 806 perf_arc_blend); 807 808 // KLUDGE Order the performance measurements so that they match the enum values 809 double perf[3] = {perf_parabolic, perf_tangent, perf_arc_blend}; 810 tc_blend_type_t best_blend = find_max_element(perf, 3); 811 812 switch (best_blend) { 813 case PARABOLIC_BLEND: // parabolic 814 tp_debug_print("using parabolic blend\n"); 815 tcRemoveKinkProperties(prev_tc, tc); 816 tcSetTermCond(prev_tc, tc, TC_TERM_COND_PARABOLIC); 817 break; 818 case TANGENT_SEGMENTS_BLEND: // tangent 819 tp_debug_print("using approximate tangent blend\n"); 820 // NOTE: acceleration / velocity reduction is done dynamically in functions that access TC_STRUCT properties 821 tcSetTermCond(prev_tc, tc, TC_TERM_COND_TANGENT); 822 break; 823 case ARC_BLEND: // arc blend 824 tp_debug_print("using blend arc\n"); 825 tcRemoveKinkProperties(prev_tc, tc); 826 827 break; 828 case NO_BLEND: 829 break; 830 } 831 return best_blend; 832 } 833 834 835 STATIC tp_err_t tpCreateLineArcBlend(TP_STRUCT * const tp, TC_STRUCT * const prev_tc, TC_STRUCT * const tc, TC_STRUCT * const blend_tc) 836 { 837 tp_debug_print("-- Starting LineArc blend arc --\n"); 838 839 PmCartesian acc_bound, vel_bound; 840 841 //Get machine limits 842 tpGetMachineAccelBounds(&acc_bound); 843 tpGetMachineVelBounds(&vel_bound); 844 845 //Populate blend geometry struct 846 BlendGeom3 geom; 847 BlendParameters param; 848 BlendPoints3 points_approx; 849 BlendPoints3 points_exact; 850 851 int res_init = blendInit3FromLineArc(&geom, ¶m, 852 prev_tc, 853 tc, 854 &acc_bound, 855 &vel_bound, 856 emcmotConfig->maxFeedScale); 857 858 if (res_init != TP_ERR_OK) { 859 tp_debug_print("blend init failed with code %d, aborting blend arc\n", 860 res_init); 861 return res_init; 862 } 863 864 // Check for coplanarity based on binormal and tangents 865 int coplanar = pmUnitCartsColinear(&geom.binormal, 866 &tc->coords.circle.xyz.normal); 867 868 if (!coplanar) { 869 tp_debug_print("aborting arc, not coplanar\n"); 870 return TP_ERR_FAIL; 871 } 872 873 int res_param = blendComputeParameters(¶m); 874 875 int res_points = blendFindPoints3(&points_approx, &geom, ¶m); 876 877 int res_post = blendLineArcPostProcess(&points_exact, 878 &points_approx, 879 ¶m, 880 &geom, &prev_tc->coords.line.xyz, 881 &tc->coords.circle.xyz); 882 883 //Catch errors in blend setup 884 if (res_init || res_param || res_points || res_post) { 885 tp_debug_print("Got %d, %d, %d, %d for init, param, points, post, aborting arc\n", 886 res_init, 887 res_param, 888 res_points, 889 res_post); 890 return TP_ERR_FAIL; 891 } 892 893 /* If blend calculations were successful, then we're ready to create the 894 * blend arc. 895 */ 896 897 if (points_exact.trim2 > param.phi2_max) { 898 tp_debug_print("trim2 %f > phi2_max %f, aborting arc...\n", 899 points_exact.trim2, 900 param.phi2_max); 901 return TP_ERR_FAIL; 902 } 903 904 blendCheckConsume(¶m, &points_exact, prev_tc, emcmotConfig->arcBlendGapCycles); 905 //Store working copies of geometry 906 PmCartLine line1_temp = prev_tc->coords.line.xyz; 907 PmCircle circ2_temp = tc->coords.circle.xyz; 908 909 // Change lengths of circles 910 double new_len1 = line1_temp.tmag - points_exact.trim1; 911 int res_stretch1 = pmCartLineStretch(&line1_temp, 912 new_len1, 913 false); 914 915 double phi2_new = tc->coords.circle.xyz.angle - points_exact.trim2; 916 917 tp_debug_print("phi2_new = %f\n",phi2_new); 918 int res_stretch2 = pmCircleStretch(&circ2_temp, 919 phi2_new, 920 true); 921 //TODO create blends 922 if (res_stretch1 || res_stretch2) { 923 tp_debug_print("segment resize failed, aborting arc\n"); 924 return TP_ERR_FAIL; 925 } 926 927 //Get exact start and end points to account for spiral in arcs 928 pmCartLinePoint(&line1_temp, 929 line1_temp.tmag, 930 &points_exact.arc_start); 931 pmCirclePoint(&circ2_temp, 932 0.0, 933 &points_exact.arc_end); 934 //TODO deal with large spiral values, or else detect and fall back? 935 936 blendPoints3Print(&points_exact); 937 int res_arc = arcFromBlendPoints3(&blend_tc->coords.arc.xyz, 938 &points_exact, 939 &geom, 940 ¶m); 941 if (res_arc < 0) { 942 tp_debug_print("arc creation failed, aborting arc\n"); 943 return TP_ERR_FAIL; 944 } 945 946 // Note that previous restrictions don't allow ABC or UVW movement, so the 947 // end and start points should be identical 948 blend_tc->coords.arc.abc = prev_tc->coords.line.abc.end; 949 blend_tc->coords.arc.uvw = prev_tc->coords.line.uvw.end; 950 951 //set the max velocity to v_plan, since we'll violate constraints otherwise. 952 tpInitBlendArcFromPrev(tp, prev_tc, blend_tc, param.v_req, 953 param.v_plan, param.a_max); 954 955 int res_tangent = checkTangentAngle(&circ2_temp, 956 &blend_tc->coords.arc.xyz, 957 &geom, 958 ¶m, 959 tp->cycleTime, 960 true); 961 962 if (res_tangent < 0) { 963 tp_debug_print("failed tangent check, aborting arc...\n"); 964 return TP_ERR_FAIL; 965 } 966 967 if (tpChooseBestBlend(tp, prev_tc, tc, blend_tc) != ARC_BLEND) { 968 return TP_ERR_NO_ACTION; 969 } 970 971 tp_debug_print("Passed all tests, updating segments\n"); 972 //TODO refactor to pass consume to connect function 973 if (param.consume) { 974 //Since we're consuming the previous segment, pop the last line off of the queue 975 int res_pop = tcqPopBack(&tp->queue); 976 if (res_pop) { 977 tp_debug_print("failed to pop segment, aborting arc\n"); 978 return TP_ERR_FAIL; 979 } 980 } else { 981 tcSetLineXYZ(prev_tc, &line1_temp); 982 //KLUDGE the previous segment is still there, so we don't need the at-speed flag on the blend too 983 blend_tc->atspeed=0; 984 } 985 tcSetCircleXYZ(tc, &circ2_temp); 986 987 tcSetTermCond(prev_tc, tc, TC_TERM_COND_TANGENT); 988 989 return TP_ERR_OK; 990 } 991 992 993 STATIC tp_err_t tpCreateArcLineBlend(TP_STRUCT * const tp, TC_STRUCT * const prev_tc, TC_STRUCT * const tc, TC_STRUCT * const blend_tc) 994 { 995 996 tp_debug_print("-- Starting ArcLine blend arc --\n"); 997 PmCartesian acc_bound, vel_bound; 998 999 //Get machine limits 1000 tpGetMachineAccelBounds(&acc_bound); 1001 tpGetMachineVelBounds(&vel_bound); 1002 1003 //Populate blend geometry struct 1004 BlendGeom3 geom; 1005 BlendParameters param; 1006 BlendPoints3 points_approx; 1007 BlendPoints3 points_exact; 1008 param.consume = 0; 1009 1010 int res_init = blendInit3FromArcLine(&geom, ¶m, 1011 prev_tc, 1012 tc, 1013 &acc_bound, 1014 &vel_bound, 1015 emcmotConfig->maxFeedScale); 1016 if (res_init != TP_ERR_OK) { 1017 tp_debug_print("blend init failed with code %d, aborting blend arc\n", 1018 res_init); 1019 return res_init; 1020 } 1021 1022 // Check for coplanarity based on binormal 1023 int coplanar = pmUnitCartsColinear(&geom.binormal, 1024 &prev_tc->coords.circle.xyz.normal); 1025 1026 if (!coplanar) { 1027 tp_debug_print("aborting arc, not coplanar\n"); 1028 return TP_ERR_FAIL; 1029 } 1030 1031 int res_param = blendComputeParameters(¶m); 1032 1033 int res_points = blendFindPoints3(&points_approx, &geom, ¶m); 1034 1035 int res_post = blendArcLinePostProcess(&points_exact, 1036 &points_approx, 1037 ¶m, 1038 &geom, &prev_tc->coords.circle.xyz, 1039 &tc->coords.line.xyz); 1040 1041 //Catch errors in blend setup 1042 if (res_init || res_param || res_points || res_post) { 1043 tp_debug_print("Got %d, %d, %d, %d for init, param, points, post\n", 1044 res_init, 1045 res_param, 1046 res_points, 1047 res_post); 1048 return TP_ERR_FAIL; 1049 } 1050 1051 blendCheckConsume(¶m, &points_exact, prev_tc, emcmotConfig->arcBlendGapCycles); 1052 1053 /* If blend calculations were successful, then we're ready to create the 1054 * blend arc. 1055 */ 1056 1057 // Store working copies of geometry 1058 PmCircle circ1_temp = prev_tc->coords.circle.xyz; 1059 PmCartLine line2_temp = tc->coords.line.xyz; 1060 1061 // Update start and end points of segment copies 1062 double phi1_new = circ1_temp.angle - points_exact.trim1; 1063 1064 if (points_exact.trim1 > param.phi1_max) { 1065 tp_debug_print("trim1 %f > phi1_max %f, aborting arc...\n", 1066 points_exact.trim1, 1067 param.phi1_max); 1068 return TP_ERR_FAIL; 1069 } 1070 1071 int res_stretch1 = pmCircleStretch(&circ1_temp, 1072 phi1_new, 1073 false); 1074 if (res_stretch1 != TP_ERR_OK) { 1075 return TP_ERR_FAIL; 1076 } 1077 1078 double new_len2 = tc->target - points_exact.trim2; 1079 int res_stretch2 = pmCartLineStretch(&line2_temp, 1080 new_len2, 1081 true); 1082 1083 if (res_stretch1 || res_stretch2) { 1084 tp_debug_print("segment resize failed, aborting arc\n"); 1085 return TP_ERR_FAIL; 1086 } 1087 1088 pmCirclePoint(&circ1_temp, 1089 circ1_temp.angle, 1090 &points_exact.arc_start); 1091 1092 pmCartLinePoint(&line2_temp, 1093 0.0, 1094 &points_exact.arc_end); 1095 1096 blendPoints3Print(&points_exact); 1097 1098 int res_arc = arcFromBlendPoints3(&blend_tc->coords.arc.xyz, &points_exact, &geom, ¶m); 1099 if (res_arc < 0) { 1100 return TP_ERR_FAIL; 1101 } 1102 1103 // Note that previous restrictions don't allow ABC or UVW movement, so the 1104 // end and start points should be identical 1105 blend_tc->coords.arc.abc = tc->coords.line.abc.start; 1106 blend_tc->coords.arc.uvw = tc->coords.line.uvw.start; 1107 1108 //set the max velocity to v_plan, since we'll violate constraints otherwise. 1109 tpInitBlendArcFromPrev(tp, prev_tc, blend_tc, param.v_req, 1110 param.v_plan, param.a_max); 1111 1112 int res_tangent = checkTangentAngle(&circ1_temp, &blend_tc->coords.arc.xyz, &geom, ¶m, tp->cycleTime, false); 1113 if (res_tangent) { 1114 tp_debug_print("failed tangent check, aborting arc...\n"); 1115 return TP_ERR_FAIL; 1116 } 1117 1118 if (tpChooseBestBlend(tp, prev_tc, tc, blend_tc) != ARC_BLEND) { 1119 return TP_ERR_NO_ACTION; 1120 } 1121 1122 tp_debug_print("Passed all tests, updating segments\n"); 1123 1124 tcSetCircleXYZ(prev_tc, &circ1_temp); 1125 tcSetLineXYZ(tc, &line2_temp); 1126 1127 //Cleanup any mess from parabolic 1128 tc->blend_prev = 0; 1129 blend_tc->atspeed=0; 1130 tcSetTermCond(prev_tc, tc, TC_TERM_COND_TANGENT); 1131 return TP_ERR_OK; 1132 } 1133 1134 STATIC tp_err_t tpCreateArcArcBlend(TP_STRUCT * const tp, TC_STRUCT * const prev_tc, TC_STRUCT * const tc, TC_STRUCT * const blend_tc) 1135 { 1136 1137 tp_debug_print("-- Starting ArcArc blend arc --\n"); 1138 //TODO type checks 1139 int colinear = pmUnitCartsColinear(&prev_tc->coords.circle.xyz.normal, 1140 &tc->coords.circle.xyz.normal); 1141 if (!colinear) { 1142 // Fail out if not collinear 1143 tp_debug_print("arc abort: not coplanar\n"); 1144 return TP_ERR_FAIL; 1145 } 1146 1147 PmCartesian acc_bound, vel_bound; 1148 1149 //Get machine limits 1150 tpGetMachineAccelBounds(&acc_bound); 1151 tpGetMachineVelBounds(&vel_bound); 1152 1153 //Populate blend geometry struct 1154 BlendGeom3 geom; 1155 BlendParameters param; 1156 BlendPoints3 points_approx; 1157 BlendPoints3 points_exact; 1158 1159 int res_init = blendInit3FromArcArc(&geom, ¶m, 1160 prev_tc, 1161 tc, 1162 &acc_bound, 1163 &vel_bound, 1164 emcmotConfig->maxFeedScale); 1165 1166 if (res_init != TP_ERR_OK) { 1167 tp_debug_print("blend init failed with code %d, aborting blend arc\n", 1168 res_init); 1169 return res_init; 1170 } 1171 1172 int coplanar1 = pmUnitCartsColinear(&geom.binormal, 1173 &prev_tc->coords.circle.xyz.normal); 1174 1175 if (!coplanar1) { 1176 tp_debug_print("aborting blend arc, arc id %d is not coplanar with binormal\n", prev_tc->id); 1177 return TP_ERR_FAIL; 1178 } 1179 1180 int coplanar2 = pmUnitCartsColinear(&geom.binormal, 1181 &tc->coords.circle.xyz.normal); 1182 if (!coplanar2) { 1183 tp_debug_print("aborting blend arc, arc id %d is not coplanar with binormal\n", tc->id); 1184 return TP_ERR_FAIL; 1185 } 1186 1187 1188 1189 int res_param = blendComputeParameters(¶m); 1190 int res_points = blendFindPoints3(&points_approx, &geom, ¶m); 1191 1192 int res_post = blendArcArcPostProcess(&points_exact, 1193 &points_approx, 1194 ¶m, 1195 &geom, &prev_tc->coords.circle.xyz, 1196 &tc->coords.circle.xyz); 1197 1198 //Catch errors in blend setup 1199 if (res_init || res_param || res_points || res_post) { 1200 tp_debug_print("Got %d, %d, %d, %d for init, param, points, post\n", 1201 res_init, 1202 res_param, 1203 res_points, 1204 res_post); 1205 1206 return TP_ERR_FAIL; 1207 } 1208 1209 blendCheckConsume(¶m, &points_exact, prev_tc, emcmotConfig->arcBlendGapCycles); 1210 1211 /* If blend calculations were successful, then we're ready to create the 1212 * blend arc. Begin work on temp copies of each circle here: 1213 */ 1214 1215 double phi1_new = prev_tc->coords.circle.xyz.angle - points_exact.trim1; 1216 double phi2_new = tc->coords.circle.xyz.angle - points_exact.trim2; 1217 1218 // TODO pare down this debug output 1219 tp_debug_print("phi1_new = %f, trim1 = %f\n", phi1_new, points_exact.trim1); 1220 tp_debug_print("phi2_new = %f, trim2 = %f\n", phi2_new, points_exact.trim2); 1221 1222 if (points_exact.trim1 > param.phi1_max) { 1223 tp_debug_print("trim1 %f > phi1_max %f, aborting arc...\n", 1224 points_exact.trim1, 1225 param.phi1_max); 1226 return TP_ERR_FAIL; 1227 } 1228 1229 if (points_exact.trim2 > param.phi2_max) { 1230 tp_debug_print("trim2 %f > phi2_max %f, aborting arc...\n", 1231 points_exact.trim2, 1232 param.phi2_max); 1233 return TP_ERR_FAIL; 1234 } 1235 1236 //Store working copies of geometry 1237 PmCircle circ1_temp = prev_tc->coords.circle.xyz; 1238 PmCircle circ2_temp = tc->coords.circle.xyz; 1239 1240 int res_stretch1 = pmCircleStretch(&circ1_temp, 1241 phi1_new, 1242 false); 1243 if (res_stretch1 != TP_ERR_OK) { 1244 return TP_ERR_FAIL; 1245 } 1246 1247 int res_stretch2 = pmCircleStretch(&circ2_temp, 1248 phi2_new, 1249 true); 1250 if (res_stretch1 || res_stretch2) { 1251 tp_debug_print("segment resize failed, aborting arc\n"); 1252 return TP_ERR_FAIL; 1253 } 1254 1255 //Get exact start and end points to account for spiral in arcs 1256 pmCirclePoint(&circ1_temp, 1257 circ1_temp.angle, 1258 &points_exact.arc_start); 1259 pmCirclePoint(&circ2_temp, 1260 0.0, 1261 &points_exact.arc_end); 1262 1263 tp_debug_print("Modified arc points\n"); 1264 blendPoints3Print(&points_exact); 1265 int res_arc = arcFromBlendPoints3(&blend_tc->coords.arc.xyz, &points_exact, &geom, ¶m); 1266 if (res_arc < 0) { 1267 return TP_ERR_FAIL; 1268 } 1269 1270 // Note that previous restrictions don't allow ABC or UVW movement, so the 1271 // end and start points should be identical 1272 blend_tc->coords.arc.abc = prev_tc->coords.circle.abc.end; 1273 blend_tc->coords.arc.uvw = prev_tc->coords.circle.uvw.end; 1274 1275 //set the max velocity to v_plan, since we'll violate constraints otherwise. 1276 tpInitBlendArcFromPrev(tp, prev_tc, blend_tc, param.v_req, 1277 param.v_plan, param.a_max); 1278 1279 int res_tangent1 = checkTangentAngle(&circ1_temp, &blend_tc->coords.arc.xyz, &geom, ¶m, tp->cycleTime, false); 1280 int res_tangent2 = checkTangentAngle(&circ2_temp, &blend_tc->coords.arc.xyz, &geom, ¶m, tp->cycleTime, true); 1281 if (res_tangent1 || res_tangent2) { 1282 tp_debug_print("failed tangent check, aborting arc...\n"); 1283 return TP_ERR_FAIL; 1284 } 1285 1286 if (tpChooseBestBlend(tp, prev_tc, tc, blend_tc) != ARC_BLEND) { 1287 return TP_ERR_NO_ACTION; 1288 } 1289 1290 tp_debug_print("Passed all tests, updating segments\n"); 1291 1292 tcSetCircleXYZ(prev_tc, &circ1_temp); 1293 tcSetCircleXYZ(tc, &circ2_temp); 1294 1295 //Cleanup any mess from parabolic 1296 tc->blend_prev = 0; 1297 blend_tc->atspeed=0; 1298 tcSetTermCond(prev_tc, tc, TC_TERM_COND_TANGENT); 1299 return TP_ERR_OK; 1300 } 1301 1302 1303 STATIC tp_err_t tpCreateLineLineBlend(TP_STRUCT * const tp, TC_STRUCT * const prev_tc, 1304 TC_STRUCT * const tc, TC_STRUCT * const blend_tc) 1305 { 1306 1307 tp_debug_print("-- Starting LineLine blend arc --\n"); 1308 PmCartesian acc_bound, vel_bound; 1309 1310 //Get machine limits 1311 tpGetMachineAccelBounds(&acc_bound); 1312 tpGetMachineVelBounds(&vel_bound); 1313 1314 // Setup blend data structures 1315 BlendGeom3 geom; 1316 BlendParameters param; 1317 BlendPoints3 points; 1318 1319 int res_init = blendInit3FromLineLine(&geom, ¶m, 1320 prev_tc, 1321 tc, 1322 &acc_bound, 1323 &vel_bound, 1324 emcmotConfig->maxFeedScale); 1325 1326 if (res_init != TP_ERR_OK) { 1327 tp_debug_print("blend init failed with code %d, aborting blend arc\n", 1328 res_init); 1329 return res_init; 1330 } 1331 1332 int res_blend = blendComputeParameters(¶m); 1333 if (res_blend != TP_ERR_OK) { 1334 return res_blend; 1335 } 1336 1337 blendFindPoints3(&points, &geom, ¶m); 1338 1339 blendCheckConsume(¶m, &points, prev_tc, emcmotConfig->arcBlendGapCycles); 1340 1341 // Set up actual blend arc here 1342 int res_arc = arcFromBlendPoints3(&blend_tc->coords.arc.xyz, &points, &geom, ¶m); 1343 if (res_arc < 0) { 1344 return TP_ERR_FAIL; 1345 } 1346 1347 // Note that previous restrictions don't allow ABC or UVW movement, so the 1348 // end and start points should be identical 1349 blend_tc->coords.arc.abc = prev_tc->coords.line.abc.end; 1350 blend_tc->coords.arc.uvw = prev_tc->coords.line.uvw.end; 1351 1352 //set the max velocity to v_plan, since we'll violate constraints otherwise. 1353 tpInitBlendArcFromPrev(tp, prev_tc, blend_tc, param.v_req, 1354 param.v_plan, param.a_max); 1355 1356 tp_debug_print("blend_tc target_vel = %g\n", blend_tc->target_vel); 1357 1358 if (tpChooseBestBlend(tp, prev_tc, tc, blend_tc) != ARC_BLEND) { 1359 return TP_ERR_NO_ACTION; 1360 } 1361 1362 int retval = TP_ERR_FAIL; 1363 1364 //TODO refactor to pass consume to connect function 1365 if (param.consume) { 1366 //Since we're consuming the previous segment, pop the last line off of the queue 1367 retval = tcqPopBack(&tp->queue); 1368 if (retval) { 1369 //This is unrecoverable since we've already changed the line. Something is wrong if we get here... 1370 rtapi_print_msg(RTAPI_MSG_ERR, "PopBack failed\n"); 1371 return TP_ERR_FAIL; 1372 } 1373 //Since the blend arc meets the end of the previous line, we only need 1374 //to "connect" to the next line 1375 retval = tcConnectBlendArc(NULL, tc, &points.arc_start, &points.arc_end); 1376 } else { 1377 //TODO refactor connect function to stretch lines and check for bad stretching 1378 tp_debug_print("keeping previous line\n"); 1379 retval = tcConnectBlendArc(prev_tc, tc, &points.arc_start, &points.arc_end); 1380 blend_tc->atspeed=0; 1381 } 1382 return retval; 1383 } 1384 1385 1386 /** 1387 * Add a newly created motion segment to the tp queue. 1388 * Returns an error code if the queue operation fails, otherwise adds a new 1389 * segment to the queue and updates the end point of the trajectory planner. 1390 */ 1391 STATIC inline int tpAddSegmentToQueue(TP_STRUCT * const tp, TC_STRUCT * const tc, int inc_id) { 1392 1393 tc->id = tp->nextId; 1394 if (tcqPut(&tp->queue, tc) == -1) { 1395 rtapi_print_msg(RTAPI_MSG_ERR, "tcqPut failed.\n"); 1396 return TP_ERR_FAIL; 1397 } 1398 if (inc_id) { 1399 tp->nextId++; 1400 } 1401 1402 // Store end of current move as new final goal of TP 1403 // KLUDGE: endpoint is garbage for rigid tap since it's supposed to retract past the start point. 1404 if (tc->motion_type != TC_RIGIDTAP) { 1405 tcGetEndpoint(tc, &tp->goalPos); 1406 } 1407 tp->done = 0; 1408 tp->depth = tcqLen(&tp->queue); 1409 //Fixing issue with duplicate id's? 1410 tp_debug_print("Adding TC id %d of type %d, total length %0.08f\n",tc->id,tc->motion_type,tc->target); 1411 1412 return TP_ERR_OK; 1413 } 1414 1415 STATIC int tpCheckCanonType(TC_STRUCT * prev_tc, TC_STRUCT * tc) 1416 { 1417 if (!tc || !prev_tc) { 1418 return TP_ERR_FAIL; 1419 } 1420 if ((prev_tc->canon_motion_type == EMC_MOTION_TYPE_TRAVERSE) ^ 1421 (tc->canon_motion_type == EMC_MOTION_TYPE_TRAVERSE)) { 1422 tp_debug_print("Can't blend between rapid and feed move, aborting arc\n"); 1423 tcSetTermCond(prev_tc, tc, TC_TERM_COND_STOP); 1424 } 1425 return TP_ERR_OK; 1426 } 1427 1428 STATIC int tpSetupSyncedIO(TP_STRUCT * const tp, TC_STRUCT * const tc) { 1429 if (tp->syncdio.anychanged != 0) { 1430 tc->syncdio = tp->syncdio; //enqueue the list of DIOs that need toggling 1431 tpClearDIOs(tp); // clear out the list, in order to prepare for the next time we need to use it 1432 return TP_ERR_OK; 1433 } else { 1434 tc->syncdio.anychanged = 0; 1435 return TP_ERR_NO_ACTION; 1436 } 1437 1438 1439 } 1440 1441 1442 /** 1443 * Adds a rigid tap cycle to the motion queue. 1444 */ 1445 int tpAddRigidTap(TP_STRUCT * const tp, EmcPose end, double vel, double ini_maxvel, 1446 double acc, unsigned char enables, double scale) { 1447 if (tpErrorCheck(tp)) { 1448 return TP_ERR_FAIL; 1449 } 1450 1451 tp_info_print("== AddRigidTap ==\n"); 1452 1453 if(!tp->synchronized) { 1454 rtapi_print_msg(RTAPI_MSG_ERR, "Cannot add unsynchronized rigid tap move.\n"); 1455 return TP_ERR_FAIL; 1456 } 1457 1458 TC_STRUCT tc = {0}; 1459 1460 /* Initialize rigid tap move. 1461 * NOTE: rigid tapping does not have a canonical type. 1462 * NOTE: always need atspeed since this is a synchronized movement. 1463 * */ 1464 tcInit(&tc, 1465 TC_RIGIDTAP, 1466 0, 1467 tp->cycleTime, 1468 enables, 1469 1); 1470 1471 // Setup any synced IO for this move 1472 tpSetupSyncedIO(tp, &tc); 1473 1474 // Copy over state data from the trajectory planner 1475 tcSetupState(&tc, tp); 1476 1477 // Copy in motion parameters 1478 tcSetupMotion(&tc, 1479 vel, 1480 ini_maxvel, 1481 acc); 1482 1483 // Setup rigid tap geometry 1484 pmRigidTapInit(&tc.coords.rigidtap, 1485 &tp->goalPos, 1486 &end, scale); 1487 tc.target = pmRigidTapTarget(&tc.coords.rigidtap, tp->uu_per_rev); 1488 1489 // Force exact stop mode after rigid tapping regardless of TP setting 1490 tcSetTermCond(&tc, NULL, TC_TERM_COND_STOP); 1491 1492 TC_STRUCT *prev_tc; 1493 //Assume non-zero error code is failure 1494 prev_tc = tcqLast(&tp->queue); 1495 tcFinalizeLength(prev_tc); 1496 tcFlagEarlyStop(prev_tc, &tc); 1497 int retval = tpAddSegmentToQueue(tp, &tc, true); 1498 tpRunOptimization(tp); 1499 return retval; 1500 } 1501 1502 STATIC blend_type_t tpCheckBlendArcType( 1503 TC_STRUCT const * const prev_tc, 1504 TC_STRUCT const * const tc) { 1505 1506 if (!prev_tc || !tc) { 1507 tp_debug_print("prev_tc or tc doesn't exist\n"); 1508 return BLEND_NONE; 1509 } 1510 1511 //If exact stop, we don't compute the arc 1512 if (prev_tc->term_cond != TC_TERM_COND_PARABOLIC) { 1513 tp_debug_print("Wrong term cond = %u\n", prev_tc->term_cond); 1514 return BLEND_NONE; 1515 } 1516 1517 //If we have any rotary axis motion, then don't create a blend arc 1518 if (tcRotaryMotionCheck(tc) || tcRotaryMotionCheck(prev_tc)) { 1519 tp_debug_print("One of the segments has rotary motion, aborting blend arc\n"); 1520 return BLEND_NONE; 1521 } 1522 1523 if (tc->finalized || prev_tc->finalized) { 1524 tp_debug_print("Can't create blend when segment lengths are finalized\n"); 1525 return BLEND_NONE; 1526 } 1527 1528 tp_debug_print("Motion types: prev_tc = %u, tc = %u\n", 1529 prev_tc->motion_type,tc->motion_type); 1530 //If not linear blends, we can't easily compute an arc 1531 if ((prev_tc->motion_type == TC_LINEAR) && (tc->motion_type == TC_LINEAR)) { 1532 return BLEND_LINE_LINE; 1533 } else if (prev_tc->motion_type == TC_LINEAR && tc->motion_type == TC_CIRCULAR) { 1534 return BLEND_LINE_ARC; 1535 } else if (prev_tc->motion_type == TC_CIRCULAR && tc->motion_type == TC_LINEAR) { 1536 return BLEND_ARC_LINE; 1537 } else if (prev_tc->motion_type == TC_CIRCULAR && tc->motion_type == TC_CIRCULAR) { 1538 return BLEND_ARC_ARC; 1539 } else { 1540 return BLEND_NONE; 1541 } 1542 } 1543 1544 1545 /** 1546 * Based on the nth and (n-1)th segment, find a safe final velocity for the (n-1)th segment. 1547 * This function also caps the target velocity if velocity ramping is enabled. If we 1548 * don't do this, then the linear segments (with higher tangential 1549 * acceleration) will speed up and slow down to reach their target velocity, 1550 * creating "humps" in the velocity profile. 1551 */ 1552 STATIC int tpComputeOptimalVelocity(TP_STRUCT const * const tp, TC_STRUCT * const tc, TC_STRUCT * const prev1_tc) { 1553 //Calculate the maximum starting velocity vs_back of segment tc, given the 1554 //trajectory parameters 1555 double acc_this = tcGetTangentialMaxAccel(tc); 1556 1557 // Find the reachable velocity of tc, moving backwards in time 1558 double vs_back = pmSqrt(pmSq(tc->finalvel) + 2.0 * acc_this * tc->target); 1559 // Find the reachable velocity of prev1_tc, moving forwards in time 1560 1561 double vf_limit_this = tc->maxvel; 1562 double vf_limit_prev = prev1_tc->maxvel; 1563 if (prev1_tc->kink_vel >=0 && prev1_tc->term_cond == TC_TERM_COND_TANGENT) { 1564 // Only care about kink_vel with tangent segments 1565 vf_limit_prev = fmin(vf_limit_prev, prev1_tc->kink_vel); 1566 } 1567 //Limit the PREVIOUS velocity by how much we can overshoot into 1568 double vf_limit = fmin(vf_limit_this, vf_limit_prev); 1569 1570 if (vs_back >= vf_limit ) { 1571 //If we've hit the requested velocity, then prev_tc is definitely a "peak" 1572 vs_back = vf_limit; 1573 prev1_tc->optimization_state = TC_OPTIM_AT_MAX; 1574 tp_debug_print("found peak due to v_limit %f\n", vf_limit); 1575 } 1576 1577 //Limit tc's target velocity to avoid creating "humps" in the velocity profile 1578 prev1_tc->finalvel = vs_back; 1579 1580 //Reduce max velocity to match sample rate 1581 double sample_maxvel = tc->target / (tp->cycleTime * TP_MIN_SEGMENT_CYCLES); 1582 tc->maxvel = fmin(tc->maxvel, sample_maxvel); 1583 1584 tp_info_print(" prev1_tc-> fv = %f, tc->fv = %f\n", 1585 prev1_tc->finalvel, tc->finalvel); 1586 1587 return TP_ERR_OK; 1588 } 1589 1590 1591 /** 1592 * Do "rising tide" optimization to find allowable final velocities for each queued segment. 1593 * Walk along the queue from the back to the front. Based on the "current" 1594 * segment's final velocity, calculate the previous segment's maximum allowable 1595 * final velocity. The depth we walk along the queue is controlled by the 1596 * TP_LOOKAHEAD_DEPTH constant for now. The process safetly aborts early due to 1597 * a short queue or other conflicts. 1598 */ 1599 STATIC int tpRunOptimization(TP_STRUCT * const tp) { 1600 // Pointers to the "current", previous, and 2nd previous trajectory 1601 // components. Current in this context means the segment being optimized, 1602 // NOT the currently excecuting segment. 1603 1604 TC_STRUCT *tc; 1605 TC_STRUCT *prev1_tc; 1606 1607 int ind, x; 1608 int len = tcqLen(&tp->queue); 1609 //TODO make lookahead depth configurable from the INI file 1610 1611 int hit_peaks = 0; 1612 // Flag that says we've hit at least 1 non-tangent segment 1613 bool hit_non_tangent = false; 1614 1615 /* Starting at the 2nd to last element in the queue, work backwards towards 1616 * the front. We can't do anything with the very last element because its 1617 * length may change if a new line is added to the queue.*/ 1618 1619 for (x = 1; x < emcmotConfig->arcBlendOptDepth + 2; ++x) { 1620 tp_info_print("==== Optimization step %d ====\n",x); 1621 1622 // Update the pointers to the trajectory segments in use 1623 ind = len-x; 1624 tc = tcqItem(&tp->queue, ind); 1625 prev1_tc = tcqItem(&tp->queue, ind-1); 1626 1627 if ( !prev1_tc || !tc) { 1628 tp_debug_print(" Reached end of queue in optimization\n"); 1629 return TP_ERR_OK; 1630 } 1631 1632 // stop optimizing if we hit a non-tangent segment (final velocity 1633 // stays zero) 1634 if (prev1_tc->term_cond != TC_TERM_COND_TANGENT) { 1635 if (hit_non_tangent) { 1636 // 2 or more non-tangent segments means we're past where the optimizer can help 1637 tp_debug_print("Found 2nd non-tangent segment, stopping optimization\n"); 1638 return TP_ERR_OK; 1639 } else { 1640 tp_debug_print("Found first non-tangent segment, contining\n"); 1641 hit_non_tangent = true; 1642 continue; 1643 } 1644 } 1645 1646 double progress_ratio = prev1_tc->progress / prev1_tc->target; 1647 // can safely decelerate to halfway point of segment from 25% of segment 1648 double cutoff_ratio = BLEND_DIST_FRACTION / 2.0; 1649 1650 if (progress_ratio >= cutoff_ratio) { 1651 tp_debug_print("segment %d has moved past %f percent progress, cannot blend safely!\n", 1652 ind-1, cutoff_ratio * 100.0); 1653 return TP_ERR_OK; 1654 } 1655 1656 //Somewhat pedantic check for other conditions that would make blending unsafe 1657 if (prev1_tc->splitting || prev1_tc->blending_next) { 1658 tp_debug_print("segment %d is already blending, cannot optimize safely!\n", 1659 ind-1); 1660 return TP_ERR_OK; 1661 } 1662 1663 tp_info_print(" current term = %u, type = %u, id = %u, accel_mode = %d\n", 1664 tc->term_cond, tc->motion_type, tc->id, tc->accel_mode); 1665 tp_info_print(" prev term = %u, type = %u, id = %u, accel_mode = %d\n", 1666 prev1_tc->term_cond, prev1_tc->motion_type, prev1_tc->id, prev1_tc->accel_mode); 1667 1668 if (tc->atspeed) { 1669 //Assume worst case that we have a stop at this point. This may cause a 1670 //slight hiccup, but the alternative is a sudden hard stop. 1671 tp_debug_print("Found atspeed at id %d\n",tc->id); 1672 tc->finalvel = 0.0; 1673 } 1674 1675 if (!tc->finalized) { 1676 tp_debug_print("Segment %d, type %d not finalized, continuing\n",tc->id,tc->motion_type); 1677 // use worst-case final velocity that allows for up to 1/2 of a segment to be consumed. 1678 1679 prev1_tc->finalvel = fmin(prev1_tc->maxvel, tpCalculateOptimizationInitialVel(tp,tc)); 1680 1681 // Fixes acceleration violations when last segment is not finalized, and previous segment is tangent. 1682 if (prev1_tc->kink_vel >=0 && prev1_tc->term_cond == TC_TERM_COND_TANGENT) { 1683 prev1_tc->finalvel = fmin(prev1_tc->finalvel, prev1_tc->kink_vel); 1684 } 1685 tc->finalvel = 0.0; 1686 } else { 1687 tpComputeOptimalVelocity(tp, tc, prev1_tc); 1688 } 1689 1690 tc->active_depth = x - 2 - hit_peaks; 1691 #ifdef TP_OPTIMIZATION_LAZY 1692 if (tc->optimization_state == TC_OPTIM_AT_MAX) { 1693 hit_peaks++; 1694 } 1695 if (hit_peaks > TP_OPTIMIZATION_CUTOFF) { 1696 return TP_ERR_OK; 1697 } 1698 #endif 1699 1700 } 1701 tp_debug_print("Reached optimization depth limit\n"); 1702 return TP_ERR_OK; 1703 } 1704 1705 1706 /** 1707 * Check for tangency between the current segment and previous segment. 1708 * If the current and previous segment are tangent, then flag the previous 1709 * segment as tangent, and limit the current segment's velocity by the sampling 1710 * rate. 1711 */ 1712 STATIC int tpSetupTangent(TP_STRUCT const * const tp, 1713 TC_STRUCT * const prev_tc, TC_STRUCT * const tc) { 1714 if (!tc || !prev_tc) { 1715 tp_debug_print("missing tc or prev tc in tangent check\n"); 1716 return TP_ERR_FAIL; 1717 } 1718 //If we have ABCUVW movement, then don't check for tangency 1719 if (tcRotaryMotionCheck(tc) || tcRotaryMotionCheck(prev_tc)) { 1720 tp_debug_print("found rotary axis motion\n"); 1721 return TP_ERR_FAIL; 1722 } 1723 1724 if (emcmotConfig->arcBlendOptDepth < 2) { 1725 tp_debug_print("Optimization depth %d too low for tangent optimization\n", 1726 emcmotConfig->arcBlendOptDepth); 1727 return TP_ERR_FAIL; 1728 } 1729 1730 if (prev_tc->term_cond == TC_TERM_COND_STOP) { 1731 tp_debug_print("Found exact stop condition\n"); 1732 return TP_ERR_FAIL; 1733 } 1734 1735 PmCartesian prev_tan, this_tan; 1736 1737 int res_endtan = tcGetEndTangentUnitVector(prev_tc, &prev_tan); 1738 int res_starttan = tcGetStartTangentUnitVector(tc, &this_tan); 1739 if (res_endtan || res_starttan) { 1740 tp_debug_print("Got %d and %d from tangent vector calc\n", 1741 res_endtan, res_starttan); 1742 } 1743 1744 tp_debug_print("prev tangent vector: %f %f %f\n", prev_tan.x, prev_tan.y, prev_tan.z); 1745 tp_debug_print("this tangent vector: %f %f %f\n", this_tan.x, this_tan.y, this_tan.z); 1746 1747 // Assume small angle approximation here 1748 const double SHARP_CORNER_DEG = 2.0; 1749 const double SHARP_CORNER_EPSILON = pmSq(PM_PI * ( SHARP_CORNER_DEG / 180.0)); 1750 if (pmCartCartAntiParallel(&prev_tan, &this_tan, SHARP_CORNER_EPSILON)) 1751 { 1752 tp_debug_print("Found sharp corner\n"); 1753 tcSetTermCond(prev_tc, tc, TC_TERM_COND_STOP); 1754 return TP_ERR_FAIL; 1755 } 1756 1757 // Calculate instantaneous acceleration required for change in direction 1758 // from v1 to v2, assuming constant speed 1759 double v_max1 = tcGetMaxTargetVel(prev_tc, getMaxFeedScale(prev_tc)); 1760 double v_max2 = tcGetMaxTargetVel(tc, getMaxFeedScale(tc)); 1761 // Note that this is a minimum since the velocity at the intersection must 1762 // be the slower of the two segments not to violate constraints. 1763 double v_max = fmin(v_max1, v_max2); 1764 tp_debug_print("tangent v_max = %f\n",v_max); 1765 1766 // Account for acceleration past final velocity during a split cycle 1767 // (e.g. next segment starts accelerating again so the average velocity is higher at the end of the split cycle) 1768 double a_inst = v_max / tp->cycleTime + tc->maxaccel; 1769 // Set up worst-case final velocity 1770 // Compute the actual magnitude of acceleration required given the tangent directions 1771 // Do this by assuming that we decelerate to a stop on the previous segment, 1772 // and simultaneously accelerate up to the maximum speed on the next one. 1773 PmCartesian acc1, acc2, acc_diff; 1774 pmCartScalMult(&prev_tan, a_inst, &acc1); 1775 pmCartScalMult(&this_tan, a_inst, &acc2); 1776 pmCartCartSub(&acc2,&acc1,&acc_diff); 1777 1778 //TODO store this in TP struct instead? 1779 PmCartesian acc_bound; 1780 tpGetMachineAccelBounds(&acc_bound); 1781 1782 PmCartesian acc_scale; 1783 findAccelScale(&acc_diff,&acc_bound,&acc_scale); 1784 tp_debug_print("acc_diff: %f %f %f\n", 1785 acc_diff.x, 1786 acc_diff.y, 1787 acc_diff.z); 1788 tp_debug_print("acc_scale: %f %f %f\n", 1789 acc_scale.x, 1790 acc_scale.y, 1791 acc_scale.z); 1792 1793 //FIXME this ratio is arbitrary, should be more easily tunable 1794 double acc_scale_max = pmCartAbsMax(&acc_scale); 1795 //KLUDGE lumping a few calculations together here 1796 if (prev_tc->motion_type == TC_CIRCULAR || tc->motion_type == TC_CIRCULAR) { 1797 acc_scale_max /= BLEND_ACC_RATIO_TANGENTIAL; 1798 } 1799 1800 // Controls the tradeoff between reduction of final velocity, and reduction of allowed segment acceleration 1801 // TODO: this should ideally depend on some function of segment length and acceleration for better optimization 1802 const double kink_ratio = tpGetTangentKinkRatio(); 1803 1804 if (acc_scale_max < kink_ratio) { 1805 tp_debug_print(" Kink acceleration within %g, using tangent blend\n", kink_ratio); 1806 tcSetTermCond(prev_tc, tc, TC_TERM_COND_TANGENT); 1807 tcSetKinkProperties(prev_tc, tc, v_max, acc_scale_max); 1808 return TP_ERR_OK; 1809 } else { 1810 tcSetKinkProperties(prev_tc, tc, v_max * kink_ratio / acc_scale_max, kink_ratio); 1811 tp_debug_print("Kink acceleration scale %f above %f, kink vel = %f, blend arc may be faster\n", 1812 acc_scale_max, 1813 kink_ratio, 1814 prev_tc->kink_vel); 1815 // NOTE: acceleration will be reduced later if tangent blend is used 1816 return TP_ERR_NO_ACTION; 1817 } 1818 } 1819 1820 static bool tpCreateBlendIfPossible( 1821 TP_STRUCT *tp, 1822 TC_STRUCT *prev_tc, 1823 TC_STRUCT *tc, 1824 TC_STRUCT *blend_tc) 1825 { 1826 tp_err_t res_create = TP_ERR_FAIL; 1827 blend_type_t blend_requested = tpCheckBlendArcType(prev_tc, tc); 1828 1829 switch (blend_requested) { 1830 case BLEND_LINE_LINE: 1831 res_create = tpCreateLineLineBlend(tp, prev_tc, tc, blend_tc); 1832 break; 1833 case BLEND_LINE_ARC: 1834 res_create = tpCreateLineArcBlend(tp, prev_tc, tc, blend_tc); 1835 break; 1836 case BLEND_ARC_LINE: 1837 res_create = tpCreateArcLineBlend(tp, prev_tc, tc, blend_tc); 1838 break; 1839 case BLEND_ARC_ARC: 1840 res_create = tpCreateArcArcBlend(tp, prev_tc, tc, blend_tc); 1841 break; 1842 case BLEND_NONE: 1843 default: 1844 tp_debug_print("intersection type not recognized, aborting arc\n"); 1845 res_create = TP_ERR_FAIL; 1846 break; 1847 } 1848 1849 return res_create == TP_ERR_OK; 1850 } 1851 1852 1853 /** 1854 * Handle creating a blend arc when a new line segment is about to enter the queue. 1855 * This function handles the checks, setup, and calculations for creating a new 1856 * blend arc. Essentially all of the blend arc functions are called through 1857 * here to isolate the process. 1858 */ 1859 STATIC tc_blend_type_t tpHandleBlendArc(TP_STRUCT * const tp, TC_STRUCT * const tc) { 1860 1861 tp_debug_print("*****************************************\n** Handle Blend Arc **\n"); 1862 1863 TC_STRUCT *prev_tc; 1864 prev_tc = tcqLast(&tp->queue); 1865 1866 //If the previous segment has already started, then don't create a blend 1867 //arc for the next pair. 1868 // TODO May be able to lift this restriction if we can ensure that we leave 1869 // 1 timestep's worth of distance in prev_tc 1870 if ( !prev_tc) { 1871 tp_debug_print(" queue empty\n"); 1872 return NO_BLEND; 1873 } 1874 if (prev_tc->progress > prev_tc->target / 2.0) { 1875 tp_debug_print(" prev_tc progress (%f) is too large, aborting blend arc\n", prev_tc->progress); 1876 return NO_BLEND; 1877 } 1878 1879 // Check for tangency between segments and handle any errors 1880 // TODO possibly refactor this into a macro? 1881 int res_tan = tpSetupTangent(tp, prev_tc, tc); 1882 switch (res_tan) { 1883 // Abort blend arc creation in these cases 1884 case TP_ERR_FAIL: 1885 tp_debug_print(" tpSetupTangent failed, aborting blend arc\n"); 1886 case TP_ERR_OK: 1887 return res_tan; 1888 case TP_ERR_NO_ACTION: 1889 default: 1890 //Continue with creation 1891 break; 1892 } 1893 1894 TC_STRUCT blend_tc = {0}; 1895 1896 tc_blend_type_t blend_used = NO_BLEND; 1897 1898 bool arc_blend_ok = tpCreateBlendIfPossible(tp, prev_tc, tc, &blend_tc); 1899 1900 if (arc_blend_ok) { 1901 //Need to do this here since the length changed 1902 blend_used = ARC_BLEND; 1903 tpAddSegmentToQueue(tp, &blend_tc, false); 1904 } else { 1905 // If blend arc creation failed early on, catch it here and find the best blend 1906 blend_used = tpChooseBestBlend(tp, prev_tc, tc, NULL) ; 1907 } 1908 1909 return blend_used; 1910 } 1911 1912 //TODO final setup steps as separate functions 1913 // 1914 /** 1915 * Add a straight line to the tc queue. 1916 * end of the previous move to the new end specified here at the 1917 * currently-active accel and vel settings from the tp struct. 1918 */ 1919 int tpAddLine(TP_STRUCT * const tp, EmcPose end, int canon_motion_type, double vel, double 1920 ini_maxvel, double acc, unsigned char enables, char atspeed, int indexer_jnum) { 1921 1922 if (tpErrorCheck(tp) < 0) { 1923 return TP_ERR_FAIL; 1924 } 1925 tp_info_print("== AddLine ==\n"); 1926 1927 // Initialize new tc struct for the line segment 1928 TC_STRUCT tc = {0}; 1929 tcInit(&tc, 1930 TC_LINEAR, 1931 canon_motion_type, 1932 tp->cycleTime, 1933 enables, 1934 atspeed); 1935 1936 // Setup any synced IO for this move 1937 tpSetupSyncedIO(tp, &tc); 1938 1939 // Copy over state data from the trajectory planner 1940 tcSetupState(&tc, tp); 1941 1942 // Copy in motion parameters 1943 tcSetupMotion(&tc, 1944 vel, 1945 ini_maxvel, 1946 acc); 1947 1948 // Setup line geometry 1949 pmLine9Init(&tc.coords.line, 1950 &tp->goalPos, 1951 &end); 1952 tc.target = pmLine9Target(&tc.coords.line); 1953 if (tc.target < TP_POS_EPSILON) { 1954 rtapi_print_msg(RTAPI_MSG_DBG,"failed to create line id %d, zero-length segment\n",tp->nextId); 1955 return TP_ERR_ZERO_LENGTH; 1956 } 1957 tc.nominal_length = tc.target; 1958 tcClampVelocityByLength(&tc); 1959 1960 // For linear move, set joint corresponding to a locking indexer axis 1961 tc.indexer_jnum = indexer_jnum; 1962 1963 //TODO refactor this into its own function 1964 TC_STRUCT *prev_tc; 1965 prev_tc = tcqLast(&tp->queue); 1966 tpCheckCanonType(prev_tc, &tc); 1967 if (emcmotConfig->arcBlendEnable){ 1968 tpHandleBlendArc(tp, &tc); 1969 } 1970 tcFinalizeLength(prev_tc); 1971 tcFlagEarlyStop(prev_tc, &tc); 1972 1973 int retval = tpAddSegmentToQueue(tp, &tc, true); 1974 //Run speed optimization (will abort safely if there are no tangent segments) 1975 tpRunOptimization(tp); 1976 1977 return retval; 1978 } 1979 1980 1981 /** 1982 * Adds a circular (circle, arc, helix) move from the end of the 1983 * last move to this new position. 1984 * 1985 * @param end is the xyz/abc point of the destination. 1986 * 1987 * see pmCircleInit for further details on how arcs are specified. Note that 1988 * degenerate arcs/circles are not allowed. We are guaranteed to have a move in 1989 * xyz so the target is always the circle/arc/helical length. 1990 */ 1991 int tpAddCircle(TP_STRUCT * const tp, 1992 EmcPose end, 1993 PmCartesian center, 1994 PmCartesian normal, 1995 int turn, 1996 int canon_motion_type, 1997 double vel, 1998 double ini_maxvel, 1999 double acc, 2000 unsigned char enables, 2001 char atspeed) 2002 { 2003 if (tpErrorCheck(tp)<0) { 2004 return TP_ERR_FAIL; 2005 } 2006 2007 tp_info_print("== AddCircle ==\n"); 2008 tp_debug_print("ini_maxvel = %f\n",ini_maxvel); 2009 2010 TC_STRUCT tc = {0}; 2011 2012 tcInit(&tc, 2013 TC_CIRCULAR, 2014 canon_motion_type, 2015 tp->cycleTime, 2016 enables, 2017 atspeed); 2018 // Setup any synced IO for this move 2019 tpSetupSyncedIO(tp, &tc); 2020 2021 // Copy over state data from the trajectory planner 2022 tcSetupState(&tc, tp); 2023 2024 // Setup circle geometry 2025 int res_init = pmCircle9Init(&tc.coords.circle, 2026 &tp->goalPos, 2027 &end, 2028 ¢er, 2029 &normal, 2030 turn); 2031 2032 if (res_init) return res_init; 2033 2034 // Update tc target with existing circular segment 2035 tc.target = pmCircle9Target(&tc.coords.circle); 2036 if (tc.target < TP_POS_EPSILON) { 2037 return TP_ERR_ZERO_LENGTH; 2038 } 2039 tp_debug_print("tc.target = %f\n",tc.target); 2040 tc.nominal_length = tc.target; 2041 2042 // Copy in motion parameters 2043 tcSetupMotion(&tc, 2044 vel, 2045 ini_maxvel, 2046 acc); 2047 2048 //Reduce max velocity to match sample rate 2049 tcClampVelocityByLength(&tc); 2050 2051 TC_STRUCT *prev_tc; 2052 prev_tc = tcqLast(&tp->queue); 2053 2054 tpCheckCanonType(prev_tc, &tc); 2055 if (emcmotConfig->arcBlendEnable){ 2056 tpHandleBlendArc(tp, &tc); 2057 findSpiralArcLengthFit(&tc.coords.circle.xyz, &tc.coords.circle.fit); 2058 } 2059 tcFinalizeLength(prev_tc); 2060 tcFlagEarlyStop(prev_tc, &tc); 2061 2062 int retval = tpAddSegmentToQueue(tp, &tc, true); 2063 2064 tpRunOptimization(tp); 2065 return retval; 2066 } 2067 2068 2069 /** 2070 * Adjusts blend velocity and acceleration to safe limits. 2071 * If we are blending between tc and nexttc, then we need to figure out what a 2072 * safe blend velocity is based on the known trajectory parameters. This 2073 * function updates the TC_STRUCT data with a safe blend velocity. 2074 * 2075 * @note This function will compute the parabolic blend start / end velocities 2076 * regardless of the current terminal condition (useful for planning). 2077 */ 2078 STATIC int tpComputeBlendVelocity( 2079 TC_STRUCT const *tc, 2080 TC_STRUCT const *nexttc, 2081 double target_vel_this, 2082 double target_vel_next, 2083 double *v_blend_this, 2084 double *v_blend_next, 2085 double *v_blend_net) 2086 { 2087 /* Pre-checks for valid pointers */ 2088 if (!nexttc || !tc || !v_blend_this || !v_blend_next ) { 2089 return TP_ERR_FAIL; 2090 } 2091 2092 double acc_this = tcGetTangentialMaxAccel(tc); 2093 double acc_next = tcGetTangentialMaxAccel(nexttc); 2094 2095 double v_reachable_this = fmin(tpCalculateTriangleVel(tc), target_vel_this); 2096 double v_reachable_next = fmin(tpCalculateTriangleVel(nexttc), target_vel_next); 2097 2098 /* Compute the maximum allowed blend time for each segment. 2099 * This corresponds to the minimum acceleration that will just barely reach 2100 * max velocity as we are 1/2 done the segment. 2101 */ 2102 2103 double t_max_this = tc->target / v_reachable_this; 2104 double t_max_next = nexttc->target / v_reachable_next; 2105 double t_max_reachable = fmin(t_max_this, t_max_next); 2106 2107 // How long the blend phase would be at maximum acceleration 2108 double t_min_blend_this = v_reachable_this / acc_this; 2109 double t_min_blend_next = v_reachable_next / acc_next; 2110 2111 double t_max_blend = fmax(t_min_blend_this, t_min_blend_next); 2112 // The longest blend time we can get that's still within the 1/2 segment restriction 2113 double t_blend = fmin(t_max_reachable, t_max_blend); 2114 2115 // Now, use this blend time to find the best acceleration / velocity for each segment 2116 *v_blend_this = fmin(v_reachable_this, t_blend * acc_this); 2117 *v_blend_next = fmin(v_reachable_next, t_blend * acc_next); 2118 2119 double theta; 2120 2121 PmCartesian v1, v2; 2122 2123 tcGetEndAccelUnitVector(tc, &v1); 2124 tcGetStartAccelUnitVector(nexttc, &v2); 2125 findIntersectionAngle(&v1, &v2, &theta); 2126 2127 double cos_theta = cos(theta); 2128 2129 if (tc->tolerance > 0) { 2130 /* see diagram blend.fig. T (blend tolerance) is given, theta 2131 * is calculated from dot(s1, s2) 2132 * 2133 * blend criteria: we are decelerating at the end of segment s1 2134 * and we pass distance d from the end. 2135 * find the corresponding velocity v when passing d. 2136 * 2137 * in the drawing note d = 2T/cos(theta) 2138 * 2139 * when v1 is decelerating at a to stop, v = at, t = v/a 2140 * so required d = .5 a (v/a)^2 2141 * 2142 * equate the two expressions for d and solve for v 2143 */ 2144 double tblend_vel; 2145 /* Minimum value of cos(theta) to prevent numerical instability */ 2146 const double min_cos_theta = cos(PM_PI / 2.0 - TP_MIN_ARC_ANGLE); 2147 if (cos_theta > min_cos_theta) { 2148 tblend_vel = 2.0 * pmSqrt(acc_this * tc->tolerance / cos_theta); 2149 *v_blend_this = fmin(*v_blend_this, tblend_vel); 2150 *v_blend_next = fmin(*v_blend_next, tblend_vel); 2151 } 2152 } 2153 if (v_blend_net) { 2154 /* 2155 * Find net velocity in the direction tangent to the blend. 2156 * When theta ~ 0, net velocity in tangent direction is very small. 2157 * When the segments are nearly tangent (theta ~ pi/2), the blend 2158 * velocity is almost entirely in the tangent direction. 2159 */ 2160 *v_blend_net = sin(theta) * (*v_blend_this + *v_blend_next) / 2.0; 2161 } 2162 2163 return TP_ERR_OK; 2164 } 2165 2166 STATIC double estimateParabolicBlendPerformance( 2167 TP_STRUCT const *tp, 2168 TC_STRUCT const *tc, 2169 TC_STRUCT const *nexttc) 2170 { 2171 double v_this = 0.0, v_next = 0.0; 2172 2173 // Use maximum possible target velocity to get best-case performance 2174 double target_vel_this = tpGetMaxTargetVel(tp, tc); 2175 double target_vel_next = tpGetMaxTargetVel(tp, nexttc); 2176 2177 double v_net = 0.0; 2178 tpComputeBlendVelocity(tc, nexttc, target_vel_this, target_vel_next, &v_this, &v_next, &v_net); 2179 2180 return v_net; 2181 } 2182 2183 2184 2185 /** 2186 * Calculate distance update from velocity and acceleration. 2187 */ 2188 STATIC int tcUpdateDistFromAccel(TC_STRUCT * const tc, double acc, double vel_desired, int reverse_run) 2189 { 2190 // If the resulting velocity is less than zero, than we're done. This 2191 // causes a small overshoot, but in practice it is very small. 2192 double v_next = tc->currentvel + acc * tc->cycle_time; 2193 // update position in this tc using trapezoidal integration 2194 // Note that progress can be greater than the target after this step. 2195 if (v_next < 0.0) { 2196 v_next = 0.0; 2197 //KLUDGE: the trapezoidal planner undershoots by half a cycle time, so 2198 //forcing the endpoint here is necessary. However, velocity undershoot 2199 //also occurs during pausing and stopping, which can happen far from 2200 //the end. If we could "cruise" to the endpoint within a cycle at our 2201 //current speed, then assume that we want to be at the end. 2202 if (tcGetDistanceToGo(tc,reverse_run) < (tc->currentvel * tc->cycle_time)) { 2203 tc->progress = tcGetTarget(tc,reverse_run); 2204 } 2205 } else { 2206 double displacement = (v_next + tc->currentvel) * 0.5 * tc->cycle_time; 2207 // Account for reverse run (flip sign if need be) 2208 double disp_sign = reverse_run ? -1 : 1; 2209 tc->progress += (disp_sign * displacement); 2210 2211 //Progress has to be within the allowable range 2212 tc->progress = bisaturate(tc->progress, tcGetTarget(tc, TC_DIR_FORWARD), tcGetTarget(tc, TC_DIR_REVERSE)); 2213 } 2214 tc->currentvel = v_next; 2215 2216 // Check if we can make the desired velocity 2217 tc->on_final_decel = (fabs(vel_desired - tc->currentvel) < TP_VEL_EPSILON) && (acc < 0.0); 2218 2219 return TP_ERR_OK; 2220 } 2221 2222 STATIC void tpDebugCycleInfo(TP_STRUCT const * const tp, TC_STRUCT const * const tc, TC_STRUCT const * const nexttc, double acc) { 2223 #ifdef TC_DEBUG 2224 // Find maximum allowed velocity from feed and machine limits 2225 double tc_target_vel = tpGetRealTargetVel(tp, tc); 2226 // Store a copy of final velocity 2227 double tc_finalvel = tpGetRealFinalVel(tp, tc, nexttc); 2228 2229 /* Debug Output */ 2230 tc_debug_print("tc state: vr = %f, vf = %f, maxvel = %f\n", 2231 tc_target_vel, tc_finalvel, tc->maxvel); 2232 tc_debug_print(" currentvel = %f, fs = %f, tc = %f, term = %d\n", 2233 tc->currentvel, tpGetFeedScale(tp,tc), tc->cycle_time, tc->term_cond); 2234 tc_debug_print(" acc = %f, T = %f, DTG = %.12g\n", acc, 2235 tcGetTarget(tc,tp->reverse_run), tcGetDistanceToGo(tc,tp->reverse_run)); 2236 tc_debug_print(" reverse_run = %d\n", tp->reverse_run); 2237 tc_debug_print(" motion type %d\n", tc->motion_type); 2238 2239 if (tc->on_final_decel) { 2240 rtapi_print(" on final decel\n"); 2241 } 2242 #endif 2243 } 2244 2245 /** 2246 * Compute updated position and velocity for a timestep based on a trapezoidal 2247 * motion profile. 2248 * @param tc trajectory segment being processed. 2249 * 2250 * Creates the trapezoidal velocity profile based on the segment's velocity and 2251 * acceleration limits. The formula has been tweaked slightly to allow a 2252 * non-zero velocity at the instant the target is reached. 2253 */ 2254 void tpCalculateTrapezoidalAccel(TP_STRUCT const * const tp, TC_STRUCT * const tc, TC_STRUCT const * const nexttc, 2255 double * const acc, double * const vel_desired) 2256 { 2257 tc_debug_print("using trapezoidal acceleration\n"); 2258 2259 // Find maximum allowed velocity from feed and machine limits 2260 double tc_target_vel = tpGetRealTargetVel(tp, tc); 2261 // Store a copy of final velocity 2262 double tc_finalvel = tpGetRealFinalVel(tp, tc, nexttc); 2263 2264 #ifdef TP_PEDANTIC 2265 if (tc_finalvel > 0.0 && tc->term_cond != TC_TERM_COND_TANGENT) { 2266 rtapi_print_msg(RTAPI_MSG_ERR, "Final velocity of %f with non-tangent segment!\n",tc_finalvel); 2267 tc_finalvel = 0.0; 2268 } 2269 #endif 2270 2271 /* Calculations for desired velocity based on trapezoidal profile */ 2272 double dx = tcGetDistanceToGo(tc, tp->reverse_run); 2273 double maxaccel = tcGetTangentialMaxAccel(tc); 2274 2275 double discr_term1 = pmSq(tc_finalvel); 2276 double discr_term2 = maxaccel * (2.0 * dx - tc->currentvel * tc->cycle_time); 2277 double tmp_adt = maxaccel * tc->cycle_time * 0.5; 2278 double discr_term3 = pmSq(tmp_adt); 2279 2280 double discr = discr_term1 + discr_term2 + discr_term3; 2281 2282 // Descriminant is a little more complicated with final velocity term. If 2283 // descriminant < 0, we've overshot (or are about to). Do the best we can 2284 // in this situation 2285 #ifdef TP_PEDANTIC 2286 if (discr < 0.0) { 2287 rtapi_print_msg(RTAPI_MSG_ERR, 2288 "discriminant %f < 0 in velocity calculation!\n", discr); 2289 } 2290 #endif 2291 //Start with -B/2 portion of quadratic formula 2292 double maxnewvel = -tmp_adt; 2293 2294 //If the discriminant term brings our velocity above zero, add it to the total 2295 //We can ignore the calculation otherwise because negative velocities are clipped to zero 2296 if (discr > discr_term3) { 2297 maxnewvel += pmSqrt(discr); 2298 } 2299 2300 // Find bounded new velocity based on target velocity 2301 // Note that we use a separate variable later to check if we're on final decel 2302 double newvel = saturate(maxnewvel, tc_target_vel); 2303 2304 // Calculate acceleration needed to reach newvel, bounded by machine maximum 2305 double dt = fmax(tc->cycle_time, TP_TIME_EPSILON); 2306 double maxnewaccel = (newvel - tc->currentvel) / dt; 2307 *acc = saturate(maxnewaccel, maxaccel); 2308 *vel_desired = maxnewvel; 2309 } 2310 2311 /** 2312 * Calculate "ramp" acceleration for a cycle. 2313 */ 2314 STATIC int tpCalculateRampAccel(TP_STRUCT const * const tp, 2315 TC_STRUCT * const tc, 2316 TC_STRUCT const * const nexttc, 2317 double * const acc, 2318 double * const vel_desired) 2319 { 2320 tc_debug_print("using ramped acceleration\n"); 2321 // displacement remaining in this segment 2322 double dx = tcGetDistanceToGo(tc, tp->reverse_run); 2323 2324 if (!tc->blending_next) { 2325 tc->vel_at_blend_start = tc->currentvel; 2326 } 2327 2328 double vel_final = tpGetRealFinalVel(tp, tc, nexttc); 2329 2330 /* Check if the final velocity is too low to properly ramp up.*/ 2331 if (vel_final < TP_VEL_EPSILON) { 2332 tp_debug_print(" vel_final %f too low for velocity ramping\n", vel_final); 2333 return TP_ERR_FAIL; 2334 } 2335 2336 double vel_avg = (tc->currentvel + vel_final) / 2.0; 2337 2338 // Calculate time remaining in this segment assuming constant acceleration 2339 double dt = 1e-16; 2340 if (vel_avg > TP_VEL_EPSILON) { 2341 dt = fmax( dx / vel_avg, 1e-16); 2342 } 2343 2344 // Calculate velocity change between final and current velocity 2345 double dv = vel_final - tc->currentvel; 2346 2347 // Estimate constant acceleration required 2348 double acc_final = dv / dt; 2349 2350 // Saturate estimated acceleration against maximum allowed by segment 2351 double acc_max = tcGetTangentialMaxAccel(tc); 2352 2353 // Output acceleration and velocity for position update 2354 *acc = saturate(acc_final, acc_max); 2355 *vel_desired = vel_final; 2356 2357 return TP_ERR_OK; 2358 } 2359 2360 void tpToggleDIOs(TC_STRUCT * const tc) { 2361 2362 int i=0; 2363 if (tc->syncdio.anychanged != 0) { // we have DIO's to turn on or off 2364 for (i=0; i < emcmotConfig->numDIO; i++) { 2365 if (!(tc->syncdio.dio_mask & (1 << i))) continue; 2366 if (tc->syncdio.dios[i] > 0) emcmotDioWrite(i, 1); // turn DIO[i] on 2367 if (tc->syncdio.dios[i] < 0) emcmotDioWrite(i, 0); // turn DIO[i] off 2368 } 2369 for (i=0; i < emcmotConfig->numAIO; i++) { 2370 if (!(tc->syncdio.aio_mask & (1 << i))) continue; 2371 emcmotAioWrite(i, tc->syncdio.aios[i]); // set AIO[i] 2372 } 2373 tc->syncdio.anychanged = 0; //we have turned them all on/off, nothing else to do for this TC the next time 2374 } 2375 } 2376 2377 2378 /** 2379 * Handle special cases for rigid tapping. 2380 * This function deals with updating the goal position and spindle position 2381 * during a rigid tap cycle. In particular, the target and spindle goal need to 2382 * be carefully handled since we're reversing direction. 2383 */ 2384 STATIC void tpUpdateRigidTapState(TP_STRUCT const * const tp, 2385 TC_STRUCT * const tc) { 2386 2387 static double old_spindlepos; 2388 double new_spindlepos = emcmotStatus->spindle_status[tp->spindle.spindle_num].spindleRevs; 2389 if (emcmotStatus->spindle_status[tp->spindle.spindle_num].direction < 0) 2390 new_spindlepos = -new_spindlepos; 2391 2392 switch (tc->coords.rigidtap.state) { 2393 case TAPPING: 2394 tc_debug_print("TAPPING\n"); 2395 if (tc->progress >= tc->coords.rigidtap.reversal_target) { 2396 // command reversal 2397 emcmotStatus->spindle_status[tp->spindle.spindle_num].speed *= -1.0 * tc->coords.rigidtap.reversal_scale; 2398 tc->coords.rigidtap.state = REVERSING; 2399 } 2400 break; 2401 case REVERSING: 2402 tc_debug_print("REVERSING\n"); 2403 if (new_spindlepos < old_spindlepos) { 2404 PmCartesian start, end; 2405 PmCartLine *aux = &tc->coords.rigidtap.aux_xyz; 2406 // we've stopped, so set a new target at the original position 2407 tc->coords.rigidtap.spindlerevs_at_reversal = new_spindlepos + tp->spindle.offset; 2408 2409 pmCartLinePoint(&tc->coords.rigidtap.xyz, tc->progress, &start); 2410 end = tc->coords.rigidtap.xyz.start; 2411 pmCartLineInit(aux, &start, &end); 2412 rtapi_print_msg(RTAPI_MSG_DBG, "old target = %f", tc->target); 2413 tc->coords.rigidtap.reversal_target = aux->tmag; 2414 tc->target = aux->tmag + 10. * tc->uu_per_rev; 2415 tc->progress = 0.0; 2416 rtapi_print_msg(RTAPI_MSG_DBG, "new target = %f", tc->target); 2417 2418 tc->coords.rigidtap.state = RETRACTION; 2419 } 2420 old_spindlepos = new_spindlepos; 2421 tc_debug_print("Spindlepos = %f\n", new_spindlepos); 2422 break; 2423 case RETRACTION: 2424 tc_debug_print("RETRACTION\n"); 2425 if (tc->progress >= tc->coords.rigidtap.reversal_target) { 2426 emcmotStatus->spindle_status[tp->spindle.spindle_num].speed *= -1 / tc->coords.rigidtap.reversal_scale; 2427 tc->coords.rigidtap.state = FINAL_REVERSAL; 2428 } 2429 break; 2430 case FINAL_REVERSAL: 2431 tc_debug_print("FINAL_REVERSAL\n"); 2432 if (new_spindlepos > old_spindlepos) { 2433 PmCartesian start, end; 2434 PmCartLine *aux = &tc->coords.rigidtap.aux_xyz; 2435 pmCartLinePoint(aux, tc->progress, &start); 2436 end = tc->coords.rigidtap.xyz.start; 2437 pmCartLineInit(aux, &start, &end); 2438 tc->target = aux->tmag; 2439 tc->progress = 0.0; 2440 //No longer need spindle sync at this point 2441 tc->synchronized = 0; 2442 tc->target_vel = tc->maxvel; 2443 2444 tc->coords.rigidtap.state = FINAL_PLACEMENT; 2445 } 2446 old_spindlepos = new_spindlepos; 2447 break; 2448 case FINAL_PLACEMENT: 2449 tc_debug_print("FINAL_PLACEMENT\n"); 2450 // this is a regular move now, it'll stop at target above. 2451 break; 2452 } 2453 } 2454 2455 2456 /** 2457 * Update emcMotStatus with information about trajectory motion. 2458 * Based on the specified trajectory segment tc, read its progress and status 2459 * flags. Then, update the emcmotStatus structure with this information. 2460 */ 2461 STATIC int tpUpdateMovementStatus(TP_STRUCT * const tp, TC_STRUCT const * const tc ) { 2462 2463 2464 if (!tp) { 2465 return TP_ERR_FAIL; 2466 } 2467 2468 if (!tc) { 2469 // Assume that we have no active segment, so we should clear out the status fields 2470 emcmotStatus->distance_to_go = 0; 2471 emcmotStatus->enables_queued = emcmotStatus->enables_new; 2472 emcmotStatus->requested_vel = 0; 2473 emcmotStatus->current_vel = 0; 2474 emcPoseZero(&emcmotStatus->dtg); 2475 2476 tp->motionType = 0; 2477 tp->activeDepth = 0; 2478 return TP_ERR_STOPPED; 2479 } 2480 2481 EmcPose tc_pos; 2482 tcGetEndpoint(tc, &tc_pos); 2483 2484 tc_debug_print("tc id = %u canon_type = %u mot type = %u\n", 2485 tc->id, tc->canon_motion_type, tc->motion_type); 2486 tp->motionType = tc->canon_motion_type; 2487 tp->activeDepth = tc->active_depth; 2488 emcmotStatus->distance_to_go = tc->target - tc->progress; 2489 emcmotStatus->enables_queued = tc->enables; 2490 // report our line number to the guis 2491 tp->execId = tc->id; 2492 emcmotStatus->requested_vel = tc->reqvel; 2493 emcmotStatus->current_vel = tc->currentvel; 2494 2495 emcPoseSub(&tc_pos, &tp->currentPos, &emcmotStatus->dtg); 2496 return TP_ERR_OK; 2497 } 2498 2499 2500 /** 2501 * Do a parabolic blend by updating the nexttc. 2502 * Perform the actual blending process by updating the target velocity for the 2503 * next segment, then running a cycle update. 2504 */ 2505 STATIC void tpUpdateBlend(TP_STRUCT * const tp, TC_STRUCT * const tc, 2506 TC_STRUCT * const nexttc) { 2507 2508 if (!nexttc) { 2509 return; 2510 } 2511 double save_vel = nexttc->target_vel; 2512 2513 if (tpGetFeedScale(tp, nexttc) > TP_VEL_EPSILON) { 2514 double dv = tc->vel_at_blend_start - tc->currentvel; 2515 double vel_start = fmax(tc->vel_at_blend_start, TP_VEL_EPSILON); 2516 // Clip the ratio at 1 and 0 2517 double blend_progress = fmax(fmin(dv / vel_start, 1.0), 0.0); 2518 double blend_scale = tc->vel_at_blend_start / tc->blend_vel; 2519 nexttc->target_vel = blend_progress * nexttc->blend_vel * blend_scale; 2520 // Mark the segment as blending so we handle the new target velocity properly 2521 nexttc->is_blending = true; 2522 } else { 2523 // Drive the target velocity to zero since we're stopping 2524 nexttc->target_vel = 0.0; 2525 } 2526 2527 tpUpdateCycle(tp, nexttc, NULL); 2528 //Restore the original target velocity 2529 nexttc->target_vel = save_vel; 2530 } 2531 2532 2533 /** 2534 * Cleanup if tc is not valid (empty queue). 2535 * If the program ends, or we hit QUEUE STARVATION, do a soft reset on the trajectory planner. 2536 * TODO merge with tpClear? 2537 */ 2538 STATIC void tpHandleEmptyQueue(TP_STRUCT * const tp) 2539 { 2540 2541 tcqInit(&tp->queue); 2542 tp->goalPos = tp->currentPos; 2543 tp->done = 1; 2544 tp->depth = tp->activeDepth = 0; 2545 tp->aborting = 0; 2546 tp->execId = 0; 2547 tp->motionType = 0; 2548 2549 tpUpdateMovementStatus(tp, NULL); 2550 2551 tpResume(tp); 2552 } 2553 2554 /** Wrapper function to unlock rotary axes */ 2555 STATIC void tpSetRotaryUnlock(int axis, int unlock) { 2556 emcmotSetRotaryUnlock(axis, unlock); 2557 } 2558 2559 /** Wrapper function to check rotary axis lock */ 2560 STATIC int tpGetRotaryIsUnlocked(int axis) { 2561 return emcmotGetRotaryIsUnlocked(axis); 2562 } 2563 2564 2565 /** 2566 * Cleanup after a trajectory segment is complete. 2567 * If the current move is complete and we're not waiting on the spindle for 2568 * const this move, then pop if off the queue and perform cleanup operations. 2569 * Finally, get the next move in the queue. 2570 */ 2571 STATIC int tpCompleteSegment(TP_STRUCT * const tp, 2572 TC_STRUCT * const tc) { 2573 2574 if (tp->spindle.waiting_for_atspeed == tc->id) { 2575 return TP_ERR_FAIL; 2576 } 2577 2578 // if we're synced, and this move is ending, save the 2579 // spindle position so the next synced move can be in 2580 // the right place. 2581 if(tc->synchronized != TC_SYNC_NONE) { 2582 tp->spindle.offset += tc->target / tc->uu_per_rev; 2583 } else { 2584 tp->spindle.offset = 0.0; 2585 } 2586 2587 if(tc->indexer_jnum != -1) { 2588 // this was an indexing move, so before we remove it we must 2589 // relock the joint for the locking indexer axis 2590 tpSetRotaryUnlock(tc->indexer_jnum, 0); 2591 // if it is now locked, fall through and remove the finished move. 2592 // otherwise, just come back later and check again 2593 if(tpGetRotaryIsUnlocked(tc->indexer_jnum)) { 2594 return TP_ERR_FAIL; 2595 } 2596 } 2597 2598 //Clear status flags associated since segment is done 2599 //TODO stuff into helper function? 2600 tc->active = 0; 2601 tc->remove = 0; 2602 tc->is_blending = 0; 2603 tc->splitting = 0; 2604 tc->cycle_time = tp->cycleTime; 2605 //Velocities are by definition zero for a non-active segment 2606 tc->currentvel = 0.0; 2607 tc->term_vel = 0.0; 2608 //TODO make progress to match target? 2609 // done with this move 2610 if (tp->reverse_run) { 2611 tcqBackStep(&tp->queue); 2612 tp_debug_print("Finished reverse run of tc id %d\n", tc->id); 2613 } else { 2614 int res_pop = tcqPop(&tp->queue); 2615 if (res_pop) rtapi_print_msg(RTAPI_MSG_ERR,"Got error %d from tcqPop!\n", res_pop); 2616 tp_debug_print("Finished tc id %d\n", tc->id); 2617 } 2618 2619 return TP_ERR_OK; 2620 } 2621 2622 2623 /** 2624 * Handle an abort command. 2625 * Based on the current motion state, handle the consequences of an abort command. 2626 */ 2627 STATIC tp_err_t tpHandleAbort(TP_STRUCT * const tp, TC_STRUCT * const tc, 2628 TC_STRUCT * const nexttc) { 2629 2630 if(!tp->aborting) { 2631 //Don't need to do anything if not aborting 2632 return TP_ERR_NO_ACTION; 2633 } 2634 //If the motion has stopped, then it's safe to reset the TP struct. 2635 if( MOTION_ID_VALID(tp->spindle.waiting_for_index) || 2636 MOTION_ID_VALID(tp->spindle.waiting_for_atspeed) || 2637 (tc->currentvel == 0.0 && (!nexttc || nexttc->currentvel == 0.0))) { 2638 tcqInit(&tp->queue); 2639 tp->goalPos = tp->currentPos; 2640 tp->done = 1; 2641 tp->depth = tp->activeDepth = 0; 2642 tp->aborting = 0; 2643 tp->execId = 0; 2644 tp->motionType = 0; 2645 tp->synchronized = 0; 2646 tp->reverse_run = 0; 2647 tp->spindle.waiting_for_index = MOTION_INVALID_ID; 2648 tp->spindle.waiting_for_atspeed = MOTION_INVALID_ID; 2649 tpResume(tp); 2650 return TP_ERR_STOPPED; 2651 } //FIXME consistent error codes 2652 return TP_ERR_SLOWING; 2653 } 2654 2655 2656 /** 2657 * Check if the spindle has reached the required speed for a move. 2658 * Returns a "wait" code if the spindle needs to spin up before a move and it 2659 * has not reached the requested speed, or the spindle index has not been 2660 * detected. 2661 */ 2662 STATIC tp_err_t tpCheckAtSpeed(TP_STRUCT * const tp, TC_STRUCT * const tc) 2663 { 2664 int s; 2665 // this is no longer the segment we were waiting_for_index for 2666 if (MOTION_ID_VALID(tp->spindle.waiting_for_index) && tp->spindle.waiting_for_index != tc->id) 2667 { 2668 rtapi_print_msg(RTAPI_MSG_ERR, 2669 "Was waiting for index on motion id %d, but reached id %d\n", 2670 tp->spindle.waiting_for_index, tc->id); 2671 tp->spindle.waiting_for_index = MOTION_INVALID_ID; 2672 } 2673 2674 if (MOTION_ID_VALID(tp->spindle.waiting_for_atspeed) && tp->spindle.waiting_for_atspeed != tc->id) 2675 { 2676 2677 rtapi_print_msg(RTAPI_MSG_ERR, 2678 "Was waiting for atspeed on motion id %d, but reached id %d\n", 2679 tp->spindle.waiting_for_atspeed, tc->id); 2680 tp->spindle.waiting_for_atspeed = MOTION_INVALID_ID; 2681 } 2682 2683 if (MOTION_ID_VALID(tp->spindle.waiting_for_atspeed)) { 2684 for (s = 0; s < emcmotConfig->numSpindles; s++){ 2685 if(!emcmotStatus->spindle_status[s].at_speed) { 2686 // spindle is still not at the right speed, so wait another cycle 2687 return TP_ERR_WAITING; 2688 } 2689 } 2690 // not waiting any more 2691 tp->spindle.waiting_for_atspeed = MOTION_INVALID_ID; 2692 } 2693 2694 if (MOTION_ID_VALID(tp->spindle.waiting_for_index)) { 2695 if (emcmotStatus->spindle_status[tp->spindle.spindle_num].spindle_index_enable) { 2696 /* haven't passed index yet */ 2697 return TP_ERR_WAITING; 2698 } else { 2699 rtapi_print_msg(RTAPI_MSG_DBG, "Index seen on spindle %d\n", tp->spindle.spindle_num); 2700 /* passed index, start the move */ 2701 emcmotStatus->spindleSync = 1; 2702 tp->spindle.waiting_for_index = MOTION_INVALID_ID; 2703 tc->sync_accel = 1; 2704 tp->spindle.revs = 0; 2705 } 2706 } 2707 return TP_ERR_OK; 2708 } 2709 2710 /** 2711 * "Activate" a segment being read for the first time. 2712 * This function handles initial setup of a new segment read off of the queue 2713 * for the first time. 2714 */ 2715 STATIC tp_err_t tpActivateSegment(TP_STRUCT * const tp, TC_STRUCT * const tc) { 2716 2717 //Check if already active 2718 if (!tc || tc->active) { 2719 return TP_ERR_OK; 2720 } 2721 2722 if (!tp) { 2723 return TP_ERR_MISSING_INPUT; 2724 } 2725 2726 if (tp->reverse_run && (tc->motion_type == TC_RIGIDTAP || tc->synchronized != TC_SYNC_NONE)) { 2727 //Can't activate a segment with synced motion in reverse 2728 return TP_ERR_REVERSE_EMPTY; 2729 } 2730 2731 /* Based on the INI setting for "cutoff frequency", this calculation finds 2732 * short segments that can have their acceleration be simple ramps, instead 2733 * of a trapezoidal motion. This leads to fewer jerk spikes, at a slight 2734 * performance cost. 2735 * */ 2736 double cutoff_time = 1.0 / (fmax(emcmotConfig->arcBlendRampFreq, TP_TIME_EPSILON)); 2737 2738 double length = tcGetDistanceToGo(tc, tp->reverse_run); 2739 // Given what velocities we can actually reach, estimate the total time for the segment under ramp conditions 2740 double segment_time = 2.0 * length / (tc->currentvel + fmin(tc->finalvel,tpGetRealTargetVel(tp,tc))); 2741 2742 2743 if (segment_time < cutoff_time && 2744 tc->canon_motion_type != EMC_MOTION_TYPE_TRAVERSE && 2745 tc->term_cond == TC_TERM_COND_TANGENT && 2746 tc->motion_type != TC_RIGIDTAP && 2747 length != 0) 2748 { 2749 tp_debug_print("segment_time = %f, cutoff_time = %f, ramping\n", 2750 segment_time, cutoff_time); 2751 tc->accel_mode = TC_ACCEL_RAMP; 2752 } 2753 2754 // Do at speed checks that only happen once 2755 int needs_atspeed = tc->atspeed || 2756 (tc->synchronized == TC_SYNC_POSITION && !(emcmotStatus->spindleSync)); 2757 2758 if (needs_atspeed){ 2759 int s; 2760 for (s = 0; s < emcmotConfig->numSpindles; s++){ 2761 if (!emcmotStatus->spindle_status[s].at_speed) { 2762 tp->spindle.waiting_for_atspeed = tc->id; 2763 return TP_ERR_WAITING; 2764 } 2765 } 2766 } 2767 2768 if (tc->indexer_jnum != -1) { 2769 // request that the joint for the locking indexer axis unlock 2770 tpSetRotaryUnlock(tc->indexer_jnum, 1); 2771 // if it is unlocked, fall through and start the move. 2772 // otherwise, just come back later and check again 2773 if (!tpGetRotaryIsUnlocked(tc->indexer_jnum)) 2774 return TP_ERR_WAITING; 2775 } 2776 2777 // Temporary debug message 2778 tp_debug_print("Activate tc id = %d target_vel = %f req_vel = %f final_vel = %f length = %f\n", 2779 tc->id, 2780 tc->target_vel, 2781 tc->reqvel, 2782 tc->finalvel, 2783 tc->target); 2784 2785 tc->active = 1; 2786 //Do not change initial velocity here, since tangent blending already sets this up 2787 tp->motionType = tc->canon_motion_type; 2788 tc->blending_next = 0; 2789 tc->on_final_decel = 0; 2790 2791 if (TC_SYNC_POSITION == tc->synchronized && !(emcmotStatus->spindleSync)) { 2792 tp_debug_print("Setting up position sync\n"); 2793 // if we aren't already synced, wait 2794 tp->spindle.waiting_for_index = tc->id; 2795 // ask for an index reset 2796 emcmotStatus->spindle_status[tp->spindle.spindle_num].spindle_index_enable = 1; 2797 tp->spindle.offset = 0.0; 2798 rtapi_print_msg(RTAPI_MSG_DBG, "Waiting on sync. spindle_num %d..\n", tp->spindle.spindle_num); 2799 return TP_ERR_WAITING; 2800 } 2801 2802 return TP_ERR_OK; 2803 } 2804 2805 2806 /** 2807 * Run velocity mode synchronization. 2808 * Update requested velocity to follow the spindle's velocity (scaled by feed rate). 2809 */ 2810 STATIC void tpSyncVelocityMode(TP_STRUCT * const tp, TC_STRUCT * const tc, TC_STRUCT * const nexttc) { 2811 double speed = emcmotStatus->spindle_status[tp->spindle.spindle_num].spindleSpeedIn; 2812 double pos_error = fabs(speed) * tc->uu_per_rev; 2813 // Account for movement due to parabolic blending with next segment 2814 if(nexttc) { 2815 pos_error -= nexttc->progress; 2816 } 2817 tc->target_vel = pos_error; 2818 2819 if (nexttc && nexttc->synchronized) { 2820 //If the next move is synchronized too, then match it's 2821 //requested velocity to the current move 2822 nexttc->target_vel = tc->target_vel; 2823 } 2824 } 2825 2826 2827 /** 2828 * Run position mode synchronization. 2829 * Updates requested velocity for a trajectory segment to track the spindle's position. 2830 */ 2831 STATIC void tpSyncPositionMode(TP_STRUCT * const tp, TC_STRUCT * const tc, 2832 TC_STRUCT * const nexttc ) { 2833 2834 double spindle_pos = tpGetSignedSpindlePosition(&emcmotStatus->spindle_status[tp->spindle.spindle_num]); 2835 tp_debug_print("Spindle at %f\n",spindle_pos); 2836 double spindle_vel, target_vel; 2837 double oldrevs = tp->spindle.revs; 2838 2839 if ((tc->motion_type == TC_RIGIDTAP) && (tc->coords.rigidtap.state == RETRACTION || 2840 tc->coords.rigidtap.state == FINAL_REVERSAL)) { 2841 tp->spindle.revs = tc->coords.rigidtap.spindlerevs_at_reversal - 2842 spindle_pos; 2843 } else { 2844 tp->spindle.revs = spindle_pos; 2845 } 2846 2847 double pos_desired = (tp->spindle.revs - tp->spindle.offset) * tc->uu_per_rev; 2848 double pos_error = pos_desired - tc->progress; 2849 2850 if(nexttc) { 2851 pos_error -= nexttc->progress; 2852 } 2853 2854 if(tc->sync_accel) { 2855 // detect when velocities match, and move the target accordingly. 2856 // acceleration will abruptly stop and we will be on our new target. 2857 // FIX: this is driven by TP cycle time, not the segment cycle time 2858 double dt = fmax(tp->cycleTime, TP_TIME_EPSILON); 2859 spindle_vel = tp->spindle.revs / ( dt * tc->sync_accel++); 2860 target_vel = spindle_vel * tc->uu_per_rev; 2861 if(tc->currentvel >= target_vel) { 2862 tc_debug_print("Hit accel target in pos sync\n"); 2863 // move target so as to drive pos_error to 0 next cycle 2864 tp->spindle.offset = tp->spindle.revs - tc->progress / tc->uu_per_rev; 2865 tc->sync_accel = 0; 2866 tc->target_vel = target_vel; 2867 } else { 2868 tc_debug_print("accelerating in pos_sync\n"); 2869 // beginning of move and we are behind: accel as fast as we can 2870 tc->target_vel = tc->maxvel; 2871 } 2872 } else { 2873 // we have synced the beginning of the move as best we can - 2874 // track position (minimize pos_error). 2875 tc_debug_print("tracking in pos_sync\n"); 2876 double errorvel; 2877 spindle_vel = (tp->spindle.revs - oldrevs) / tp->cycleTime; 2878 target_vel = spindle_vel * tc->uu_per_rev; 2879 errorvel = pmSqrt(fabs(pos_error) * tcGetTangentialMaxAccel(tc)); 2880 if(pos_error<0) { 2881 errorvel *= -1.0; 2882 } 2883 tc->target_vel = target_vel + errorvel; 2884 } 2885 2886 //Finally, clip requested velocity at zero 2887 if (tc->target_vel < 0.0) { 2888 tc->target_vel = 0.0; 2889 } 2890 2891 if (nexttc && nexttc->synchronized) { 2892 //If the next move is synchronized too, then match it's 2893 //requested velocity to the current move 2894 nexttc->target_vel = tc->target_vel; 2895 } 2896 } 2897 2898 2899 /** 2900 * Perform parabolic blending if needed between segments and handle status updates. 2901 * This isolates most of the parabolic blend stuff to make the code path 2902 * between tangent and parabolic blends easier to follow. 2903 */ 2904 STATIC int tpDoParabolicBlending(TP_STRUCT * const tp, TC_STRUCT * const tc, 2905 TC_STRUCT * const nexttc) { 2906 2907 tc_debug_print("in DoParabolicBlend\n"); 2908 tpUpdateBlend(tp,tc,nexttc); 2909 2910 /* Status updates */ 2911 //Decide which segment we're in depending on which is moving faster 2912 if(tc->currentvel > nexttc->currentvel) { 2913 tpUpdateMovementStatus(tp, tc); 2914 } else { 2915 tpToggleDIOs(nexttc); 2916 tpUpdateMovementStatus(tp, nexttc); 2917 } 2918 #ifdef TP_SHOW_BLENDS 2919 // hack to show blends in axis 2920 tp->motionType = 0; 2921 #endif 2922 2923 //Update velocity status based on both tc and nexttc 2924 emcmotStatus->current_vel = tc->currentvel + nexttc->currentvel; 2925 2926 return TP_ERR_OK; 2927 } 2928 2929 2930 /** 2931 * Do a complete update on one segment. 2932 * Handles the majority of updates on a single segment for the current cycle. 2933 */ 2934 STATIC int tpUpdateCycle(TP_STRUCT * const tp, 2935 TC_STRUCT * const tc, TC_STRUCT const * const nexttc) { 2936 2937 //placeholders for position for this update 2938 EmcPose before; 2939 2940 //Store the current position due to this TC 2941 tcGetPos(tc, &before); 2942 2943 // Update the start velocity if we're not blending yet 2944 if (!tc->blending_next) { 2945 tc->vel_at_blend_start = tc->currentvel; 2946 } 2947 2948 // Run cycle update with stored cycle time 2949 int res_accel = 1; 2950 double acc=0, vel_desired=0; 2951 2952 // If the slowdown is not too great, use velocity ramping instead of trapezoidal velocity 2953 // Also, don't ramp up for parabolic blends 2954 if (tc->accel_mode && tc->term_cond == TC_TERM_COND_TANGENT) { 2955 res_accel = tpCalculateRampAccel(tp, tc, nexttc, &acc, &vel_desired); 2956 } 2957 2958 // Check the return in case the ramp calculation failed, fall back to trapezoidal 2959 if (res_accel != TP_ERR_OK) { 2960 tpCalculateTrapezoidalAccel(tp, tc, nexttc, &acc, &vel_desired); 2961 } 2962 2963 tcUpdateDistFromAccel(tc, acc, vel_desired, tp->reverse_run); 2964 tpDebugCycleInfo(tp, tc, nexttc, acc); 2965 2966 //Check if we're near the end of the cycle and set appropriate changes 2967 tpCheckEndCondition(tp, tc, nexttc); 2968 2969 EmcPose displacement; 2970 2971 // Calculate displacement 2972 tcGetPos(tc, &displacement); 2973 emcPoseSelfSub(&displacement, &before); 2974 2975 //Store displacement (checking for valid pose) 2976 int res_set = tpAddCurrentPos(tp, &displacement); 2977 2978 #ifdef TC_DEBUG 2979 double mag; 2980 emcPoseMagnitude(&displacement, &mag); 2981 tc_debug_print("cycle movement = %f\n", mag); 2982 #endif 2983 2984 return res_set; 2985 } 2986 2987 2988 /** 2989 * Send default values to status structure. 2990 */ 2991 STATIC int tpUpdateInitialStatus(TP_STRUCT const * const tp) { 2992 // Update queue length 2993 emcmotStatus->tcqlen = tcqLen(&tp->queue); 2994 // Set default value for requested speed 2995 emcmotStatus->requested_vel = 0.0; 2996 //FIXME test if we can do this safely 2997 emcmotStatus->current_vel = 0.0; 2998 return TP_ERR_OK; 2999 } 3000 3001 3002 /** 3003 * Flag a segment as needing a split cycle. 3004 * In addition to flagging a segment as splitting, do any preparations to store 3005 * data for the next cycle. 3006 */ 3007 STATIC inline int tcSetSplitCycle(TC_STRUCT * const tc, double split_time, 3008 double v_f) 3009 { 3010 tp_debug_print("split time for id %d is %.16g\n", tc->id, split_time); 3011 if (tc->splitting != 0 && split_time > 0.0) { 3012 rtapi_print_msg(RTAPI_MSG_ERR,"already splitting on id %d with cycle time %.16g, dx = %.16g, split time %.12g\n", 3013 tc->id, 3014 tc->cycle_time, 3015 tc->target-tc->progress, 3016 split_time); 3017 return TP_ERR_FAIL; 3018 } 3019 tc->splitting = 1; 3020 tc->cycle_time = split_time; 3021 tc->term_vel = v_f; 3022 return 0; 3023 } 3024 3025 3026 /** 3027 * Check remaining time in a segment and calculate split cycle if necessary. 3028 * This function estimates how much time we need to complete the next segment. 3029 * If it's greater than one timestep, then we do nothing and carry on. If not, 3030 * then we flag the segment as "splitting", so that during the next cycle, 3031 * it handles the transition to the next segment. 3032 */ 3033 STATIC int tpCheckEndCondition(TP_STRUCT const * const tp, TC_STRUCT * const tc, TC_STRUCT const * const nexttc) { 3034 3035 //Assume no split time unless we find otherwise 3036 tc->cycle_time = tp->cycleTime; 3037 //Initial guess at dt for next round 3038 double dx = tcGetDistanceToGo(tc, tp->reverse_run); 3039 tc_debug_print("tpCheckEndCondition: dx = %e\n",dx); 3040 3041 if (dx <= TP_POS_EPSILON) { 3042 //If the segment is close to the target position, then we assume that it's done. 3043 tp_debug_print("close to target, dx = %.12f\n",dx); 3044 //Force progress to land exactly on the target to prevent numerical errors. 3045 tc->progress = tcGetTarget(tc, tp->reverse_run); 3046 3047 if (!tp->reverse_run) { 3048 tcSetSplitCycle(tc, 0.0, tc->currentvel); 3049 } 3050 if (tc->term_cond == TC_TERM_COND_STOP || tc->term_cond == TC_TERM_COND_EXACT || tp->reverse_run) { 3051 tc->remove = 1; 3052 } 3053 return TP_ERR_OK; 3054 } else if (tp->reverse_run) { 3055 return TP_ERR_NO_ACTION; 3056 } else if (tc->term_cond == TC_TERM_COND_STOP || tc->term_cond == TC_TERM_COND_EXACT) { 3057 return TP_ERR_NO_ACTION; 3058 } 3059 3060 3061 double v_f = tpGetRealFinalVel(tp, tc, nexttc); 3062 double v_avg = (tc->currentvel + v_f) / 2.0; 3063 3064 //Check that we have a non-zero "average" velocity between now and the 3065 //finish. If not, it means that we have to accelerate from a stop, which 3066 //will take longer than the minimum 2 timesteps that each segment takes, so 3067 //we're safely far form the end. 3068 3069 //Get dt assuming that we can magically reach the final velocity at 3070 //the end of the move. 3071 // 3072 //KLUDGE: start with a value below the cutoff 3073 double dt = TP_TIME_EPSILON / 2.0; 3074 if (v_avg > TP_VEL_EPSILON) { 3075 //Get dt from distance and velocity (avoid div by zero) 3076 dt = fmax(dt, dx / v_avg); 3077 } else { 3078 if ( dx > (v_avg * tp->cycleTime) && dx > TP_POS_EPSILON) { 3079 tc_debug_print(" below velocity threshold, assuming far from end\n"); 3080 return TP_ERR_NO_ACTION; 3081 } 3082 } 3083 3084 //Calculate the acceleration this would take: 3085 3086 double dv = v_f - tc->currentvel; 3087 double a_f = dv / dt; 3088 3089 //If this is a valid acceleration, then we're done. If not, then we solve 3090 //for v_f and dt given the max acceleration allowed. 3091 double a_max = tcGetTangentialMaxAccel(tc); 3092 3093 //If we exceed the maximum acceleration, then the dt estimate is too small. 3094 double a = a_f; 3095 int recalc = sat_inplace(&a, a_max); 3096 3097 //Need to recalculate vf and above 3098 if (recalc) { 3099 tc_debug_print(" recalculating with a_f = %f, a = %f\n", a_f, a); 3100 double disc = pmSq(tc->currentvel / a) + 2.0 / a * dx; 3101 if (disc < 0) { 3102 //Should mean that dx is too big, i.e. we're not close enough 3103 tc_debug_print(" dx = %f, too large, not at end yet\n",dx); 3104 return TP_ERR_NO_ACTION; 3105 } 3106 3107 if (disc < TP_TIME_EPSILON * TP_TIME_EPSILON) { 3108 tc_debug_print("disc too small, skipping sqrt\n"); 3109 dt = -tc->currentvel / a; 3110 } else if (a > 0) { 3111 tc_debug_print("using positive sqrt\n"); 3112 dt = -tc->currentvel / a + pmSqrt(disc); 3113 } else { 3114 tc_debug_print("using negative sqrt\n"); 3115 dt = -tc->currentvel / a - pmSqrt(disc); 3116 } 3117 3118 tc_debug_print(" revised dt = %f\n", dt); 3119 //Update final velocity with actual result 3120 v_f = tc->currentvel + dt * a; 3121 } 3122 3123 if (dt < TP_TIME_EPSILON) { 3124 //Close enough, call it done 3125 tc_debug_print("revised dt small, finishing tc\n"); 3126 tc->progress = tcGetTarget(tc, tp->reverse_run); 3127 tcSetSplitCycle(tc, 0.0, v_f); 3128 } else if (dt < tp->cycleTime ) { 3129 tc_debug_print(" corrected v_f = %f, a = %f\n", v_f, a); 3130 tcSetSplitCycle(tc, dt, v_f); 3131 } else { 3132 tc_debug_print(" dt = %f, not at end yet\n",dt); 3133 return TP_ERR_NO_ACTION; 3134 } 3135 return TP_ERR_OK; 3136 } 3137 3138 3139 STATIC int tpHandleSplitCycle(TP_STRUCT * const tp, TC_STRUCT * const tc, 3140 TC_STRUCT * const nexttc) 3141 { 3142 if (tc->remove) { 3143 //Don't need to update since this segment is flagged for removal 3144 return TP_ERR_NO_ACTION; 3145 } 3146 3147 //Pose data to calculate movement due to finishing current TC 3148 EmcPose before; 3149 tcGetPos(tc, &before); 3150 3151 tp_debug_print("tc id %d splitting\n",tc->id); 3152 //Shortcut tc update by assuming we arrive at end 3153 tc->progress = tcGetTarget(tc,tp->reverse_run); 3154 //Get displacement from prev. position 3155 EmcPose displacement; 3156 tcGetPos(tc, &displacement); 3157 emcPoseSelfSub(&displacement, &before); 3158 3159 // Update tp's position (checking for valid pose) 3160 tpAddCurrentPos(tp, &displacement); 3161 3162 #ifdef TC_DEBUG 3163 double mag; 3164 emcPoseMagnitude(&displacement, &mag); 3165 tc_debug_print("cycle movement = %f\n",mag); 3166 #endif 3167 3168 // Trigger removal of current segment at the end of the cycle 3169 tc->remove = 1; 3170 3171 if (!nexttc) { 3172 tp_debug_print("no nexttc in split cycle\n"); 3173 return TP_ERR_OK; 3174 } 3175 3176 switch (tc->term_cond) { 3177 case TC_TERM_COND_TANGENT: 3178 nexttc->cycle_time = tp->cycleTime - tc->cycle_time; 3179 nexttc->currentvel = tc->term_vel; 3180 tp_debug_print("Doing tangent split\n"); 3181 break; 3182 case TC_TERM_COND_PARABOLIC: 3183 break; 3184 case TC_TERM_COND_STOP: 3185 break; 3186 case TC_TERM_COND_EXACT: 3187 break; 3188 default: 3189 rtapi_print_msg(RTAPI_MSG_ERR,"unknown term cond %d in segment %d\n", 3190 tc->term_cond, 3191 tc->id); 3192 } 3193 3194 // Run split cycle update with remaining time in nexttc 3195 // KLUDGE: use next cycle after nextc to prevent velocity dip (functions fail gracefully w/ NULL) 3196 int queue_dir_step = tp->reverse_run ? -1 : 1; 3197 TC_STRUCT *next2tc = tcqItem(&tp->queue, queue_dir_step*2); 3198 3199 tpUpdateCycle(tp, nexttc, next2tc); 3200 3201 // Update status for the split portion 3202 // FIXME redundant tangent check, refactor to switch 3203 if (tc->cycle_time > nexttc->cycle_time && tc->term_cond == TC_TERM_COND_TANGENT) { 3204 //Majority of time spent in current segment 3205 tpToggleDIOs(tc); 3206 tpUpdateMovementStatus(tp, tc); 3207 } else { 3208 tpToggleDIOs(nexttc); 3209 tpUpdateMovementStatus(tp, nexttc); 3210 } 3211 3212 return TP_ERR_OK; 3213 } 3214 3215 STATIC int tpHandleRegularCycle(TP_STRUCT * const tp, 3216 TC_STRUCT * const tc, 3217 TC_STRUCT * const nexttc) 3218 { 3219 if (tc->remove) { 3220 //Don't need to update since this segment is flagged for removal 3221 return TP_ERR_NO_ACTION; 3222 } 3223 //Run with full cycle time 3224 tc_debug_print("Normal cycle\n"); 3225 tc->cycle_time = tp->cycleTime; 3226 tpUpdateCycle(tp, tc, nexttc); 3227 3228 /* Parabolic blending */ 3229 3230 double v_this = 0.0, v_next = 0.0; 3231 3232 // cap the blend velocity at the current requested speed (factoring in feed override) 3233 double target_vel_this = tpGetRealTargetVel(tp, tc); 3234 double target_vel_next = tpGetRealTargetVel(tp, nexttc); 3235 3236 tpComputeBlendVelocity(tc, nexttc, target_vel_this, target_vel_next, &v_this, &v_next, NULL); 3237 tc->blend_vel = v_this; 3238 if (nexttc) { 3239 nexttc->blend_vel = v_next; 3240 } 3241 3242 if (nexttc && tcIsBlending(tc)) { 3243 tpDoParabolicBlending(tp, tc, nexttc); 3244 } else { 3245 //Update status for a normal step 3246 tpToggleDIOs(tc); 3247 tpUpdateMovementStatus(tp, tc); 3248 } 3249 return TP_ERR_OK; 3250 } 3251 3252 3253 /** 3254 * Calculate an updated goal position for the next timestep. 3255 * This is the brains of the operation. It's called every TRAJ period and is 3256 * expected to set tp->currentPos to the new machine position. Lots of other 3257 * const tp fields (depth, done, etc) have to be twiddled to communicate the 3258 * status; I think those are spelled out here correctly and I can't clean it up 3259 * without breaking the API that the TP presents to motion. 3260 */ 3261 int tpRunCycle(TP_STRUCT * const tp, long period) 3262 { 3263 //Pointers to current and next trajectory component 3264 TC_STRUCT *tc; 3265 TC_STRUCT *nexttc; 3266 3267 /* Get pointers to current and relevant future segments. It's ok here if 3268 * future segments don't exist (NULL pointers) as we check for this later). 3269 */ 3270 3271 int queue_dir_step = tp->reverse_run ? -1 : 1; 3272 tc = tcqItem(&tp->queue, 0); 3273 nexttc = tcqItem(&tp->queue, queue_dir_step * 1); 3274 3275 //Set GUI status to "zero" state 3276 tpUpdateInitialStatus(tp); 3277 3278 #ifdef TC_DEBUG 3279 //Hack debug output for timesteps 3280 // NOTE: need to track every timestep, even those where the trajectory planner is idle 3281 static double time_elapsed = 0; 3282 time_elapsed+=tp->cycleTime; 3283 #endif 3284 3285 //If we have a NULL pointer, then the queue must be empty, so we're done. 3286 if(!tc) { 3287 tpHandleEmptyQueue(tp); 3288 return TP_ERR_WAITING; 3289 } 3290 3291 tc_debug_print("-------------------\n"); 3292 3293 3294 /* If the queue empties enough, assume that the program is near the end. 3295 * This forces the last segment to be "finalized" to let the optimizer run.*/ 3296 /*tpHandleLowQueue(tp);*/ 3297 3298 /* If we're aborting or pausing and the velocity has reached zero, then we 3299 * don't need additional planning and can abort here. */ 3300 if (tpHandleAbort(tp, tc, nexttc) == TP_ERR_STOPPED) { 3301 return TP_ERR_STOPPED; 3302 } 3303 3304 //Return early if we have a reason to wait (i.e. not ready for motion) 3305 if (tpCheckAtSpeed(tp, tc) != TP_ERR_OK){ 3306 return TP_ERR_WAITING; 3307 } 3308 3309 int res_activate = tpActivateSegment(tp, tc); 3310 if (res_activate != TP_ERR_OK ) { 3311 return res_activate; 3312 } 3313 3314 // Preprocess rigid tap move (handles threading direction reversals) 3315 if (tc->motion_type == TC_RIGIDTAP) { 3316 tpUpdateRigidTapState(tp, tc); 3317 } 3318 3319 /** If synchronized with spindle, calculate requested velocity to track 3320 * spindle motion.*/ 3321 switch (tc->synchronized) { 3322 case TC_SYNC_NONE: 3323 emcmotStatus->spindleSync = 0; 3324 break; 3325 case TC_SYNC_VELOCITY: 3326 tp_debug_print("sync velocity\n"); 3327 tpSyncVelocityMode(tp, tc, nexttc); 3328 break; 3329 case TC_SYNC_POSITION: 3330 tp_debug_print("sync position\n"); 3331 tpSyncPositionMode(tp, tc, nexttc); 3332 break; 3333 default: 3334 tp_debug_print("unrecognized spindle sync state!\n"); 3335 break; 3336 } 3337 3338 #ifdef TC_DEBUG 3339 EmcPose pos_before = tp->currentPos; 3340 #endif 3341 3342 3343 tcClearFlags(tc); 3344 tcClearFlags(nexttc); 3345 // Update the current tc 3346 if (tc->splitting) { 3347 tpHandleSplitCycle(tp, tc, nexttc); 3348 } else { 3349 tpHandleRegularCycle(tp, tc, nexttc); 3350 } 3351 3352 #ifdef TC_DEBUG 3353 double mag; 3354 EmcPose disp; 3355 emcPoseSub(&tp->currentPos, &pos_before, &disp); 3356 emcPoseMagnitude(&disp, &mag); 3357 tc_debug_print("time: %.12e total movement = %.12e vel = %.12e\n", 3358 time_elapsed, 3359 mag, emcmotStatus->current_vel); 3360 3361 tc_debug_print("tp_displacement = %.12e %.12e %.12e time = %.12e\n", 3362 disp.tran.x, 3363 disp.tran.y, 3364 disp.tran.z, 3365 time_elapsed); 3366 #endif 3367 3368 // If TC is complete, remove it from the queue. 3369 if (tc->remove) { 3370 tpCompleteSegment(tp, tc); 3371 } 3372 3373 return TP_ERR_OK; 3374 } 3375 3376 int tpSetSpindleSync(TP_STRUCT * const tp, int spindle, double sync, int mode) { 3377 if(sync) { 3378 if (mode) { 3379 tp->synchronized = TC_SYNC_VELOCITY; 3380 } else { 3381 tp->synchronized = TC_SYNC_POSITION; 3382 } 3383 tp->uu_per_rev = sync; 3384 tp->spindle.spindle_num = spindle; 3385 } else 3386 tp->synchronized = 0; 3387 3388 return TP_ERR_OK; 3389 } 3390 3391 int tpPause(TP_STRUCT * const tp) 3392 { 3393 if (0 == tp) { 3394 return TP_ERR_FAIL; 3395 } 3396 tp->pausing = 1; 3397 return TP_ERR_OK; 3398 } 3399 3400 int tpResume(TP_STRUCT * const tp) 3401 { 3402 if (0 == tp) { 3403 return TP_ERR_FAIL; 3404 } 3405 tp->pausing = 0; 3406 return TP_ERR_OK; 3407 } 3408 3409 int tpAbort(TP_STRUCT * const tp) 3410 { 3411 if (0 == tp) { 3412 return TP_ERR_FAIL; 3413 } 3414 3415 if (!tp->aborting) { 3416 /* const to abort, signal a pause and set our abort flag */ 3417 tpPause(tp); 3418 tp->aborting = 1; 3419 } 3420 return tpClearDIOs(tp); //clears out any already cached DIOs 3421 } 3422 3423 int tpGetMotionType(TP_STRUCT * const tp) 3424 { 3425 return tp->motionType; 3426 } 3427 3428 int tpGetPos(TP_STRUCT const * const tp, EmcPose * const pos) 3429 { 3430 3431 if (0 == tp) { 3432 ZERO_EMC_POSE((*pos)); 3433 return TP_ERR_FAIL; 3434 } else { 3435 *pos = tp->currentPos; 3436 } 3437 3438 return TP_ERR_OK; 3439 } 3440 3441 int tpIsDone(TP_STRUCT * const tp) 3442 { 3443 if (0 == tp) { 3444 return TP_ERR_OK; 3445 } 3446 3447 return tp->done; 3448 } 3449 3450 int tpQueueDepth(TP_STRUCT * const tp) 3451 { 3452 if (0 == tp) { 3453 return TP_ERR_OK; 3454 } 3455 3456 return tp->depth; 3457 } 3458 3459 int tpActiveDepth(TP_STRUCT * const tp) 3460 { 3461 if (0 == tp) { 3462 return TP_ERR_OK; 3463 } 3464 3465 return tp->activeDepth; 3466 } 3467 3468 int tpSetAout(TP_STRUCT * const tp, unsigned char index, double start, double end) { 3469 if (0 == tp) { 3470 return TP_ERR_FAIL; 3471 } 3472 tp->syncdio.anychanged = 1; //something has changed 3473 tp->syncdio.aio_mask |= (1 << index); 3474 tp->syncdio.aios[index] = start; 3475 return TP_ERR_OK; 3476 } 3477 3478 int tpSetDout(TP_STRUCT * const tp, int index, unsigned char start, unsigned char end) { 3479 if (0 == tp) { 3480 return TP_ERR_FAIL; 3481 } 3482 tp->syncdio.anychanged = 1; //something has changed 3483 tp->syncdio.dio_mask |= (1 << index); 3484 if (start > 0) 3485 tp->syncdio.dios[index] = 1; // the end value can't be set from canon currently, and has the same value as start 3486 else 3487 tp->syncdio.dios[index] = -1; 3488 return TP_ERR_OK; 3489 } 3490 3491 int tpSetRunDir(TP_STRUCT * const tp, tc_direction_t dir) 3492 { 3493 // Can't change direction while moving 3494 if (tpIsMoving(tp)) { 3495 return TP_ERR_FAIL; 3496 } 3497 3498 switch (dir) { 3499 case TC_DIR_FORWARD: 3500 case TC_DIR_REVERSE: 3501 tp->reverse_run = dir; 3502 return TP_ERR_OK; 3503 default: 3504 rtapi_print_msg(RTAPI_MSG_ERR,"Invalid direction flag in SetRunDir"); 3505 return TP_ERR_FAIL; 3506 } 3507 } 3508 3509 int tpIsMoving(TP_STRUCT const * const tp) 3510 { 3511 3512 //TODO may be better to explicitly check velocities on the first 2 segments, but this is messy 3513 if (emcmotStatus->current_vel >= TP_VEL_EPSILON ) { 3514 tp_debug_print("TP moving, current_vel = %.16g\n", emcmotStatus->current_vel); 3515 return true; 3516 } else if (tp->spindle.waiting_for_index != MOTION_INVALID_ID || tp->spindle.waiting_for_atspeed != MOTION_INVALID_ID) { 3517 tp_debug_print("TP moving, waiting for index or atspeed\n"); 3518 return true; 3519 } 3520 return false; 3521 } 3522 3523 3524 // vim:sw=4:sts=4:et: