/*
* Copyright (c) 2007-2009 Hypertriton, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Real-time control interface for CNC machinery and robots.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "cnc_devicevar.h"
#include "cnc_servovar.h"
#include "cnc_spindlevar.h"
#include "cnc_estopvar.h"
#include "cnc_encodervar.h"
#include "cnc_mpgvar.h"
#include "cnc_statledvar.h"
#include "cnc_lcdvar.h"
#include "cncvar.h"
#include "servo.h"
#include "spindle.h"
#include "estop.h"
#include "encoder.h"
#include "mpg.h"
#include "cnclcd.h"
#include "cncstatled.h"
#define STEPMAX (INT64_MAX-1)
#define STEPLEN 20336 /* 20336 units = 1 step */
#define MAXSTEPS (STEPMAX/STEPLEN)
struct cnc_kinlimits cnc_kinlimits = {
20000, /* Max steps/sec */
200000, /* Max steps/ms^2 */
8000000 /* Max steps/ms^3 */
};
const char *cnc_axis_names[] = {
"X", "Y", "Z", /* Primary axes */
"U", "V", "W", /* Secondary axes */
"I", "J", "K", /* Arc center vectors */
"A", "B", "C" /* Rotary axes */
};
const char *cnc_insn_names[] = {
"MOVE",
"JOG",
"SET_INTERP",
"SPINDLE_DIR",
"SPINDLE_SPEED",
"SPINDLE_START",
"SPINDLE_STOP",
"ATC_PREPARE",
"ATC_CHANGE",
"LASER_ON",
"LASER_OFF",
"LASER_CURRENT",
"PICKPLACE_REEL",
"PICKPLACE_SUCTION",
"PICKPLACE_RELEASE",
"PREEMPT",
"DWELL",
"COOL_MIST",
"COOL_FLOOD",
"COOL_VORTEX"
};
struct pool cnc_insnpl;
int cnc_opened;
TAILQ_HEAD(,cnc_insn) cnc_prog;
struct cnc_vector cnc_pos;
struct cnc_timings cnc_timings;
struct servo_softc *cnc_servos[CNC_MAX_SERVOS];
struct spindle_softc *cnc_spindles[CNC_MAX_SPINDLES];
struct estop_softc *cnc_estops[CNC_MAX_ESTOPS];
struct encoder_softc *cnc_encoders[CNC_MAX_ENCODERS];
struct mpg_softc *cnc_mpgs[CNC_MAX_MPGS];
struct cnclcd_softc *cnc_lcds[CNC_MAX_LCDS];
struct cncstatled_softc *cnc_status_led = NULL;
int cnc_nservos = 0;
int cnc_nspindles = 0;
int cnc_nestops = 0;
int cnc_nencoders = 0;
int cnc_nmpgs = 0;
int cnc_nlcds = 0;
void
cncattach(int num)
{
int i;
if (num > 1)
return;
cnc_opened = 0;
pool_init(&cnc_insnpl, sizeof(struct cnc_insn), 0, 0, 0, "cncinsnpl",
NULL);
pool_setlowat(&cnc_insnpl, 1024);
TAILQ_INIT(&cnc_prog);
for (i = 0; i < CNC_NAXES; i++)
cnc_pos.v[i] = 0;
cnc_timings.hz = 0;
cnc_timings.move_jog = 0;
}
int
cncdetach(struct device *self, int flags)
{
#if NCNCLCD > 0
int i;
for (i = 0; i < cnc_nlcds; i++)
cnclcd_close(cnc_lcds[i]);
#endif
cnc_prog_reset();
return (0);
}
int
cncactivate(struct device *self, enum devact a)
{
return (0);
}
int
cncopen(dev_t dev, int flag, int mode, struct proc *p)
{
if (cnc_opened) {
return (EBUSY);
}
cnc_opened = 1;
return (0);
}
int
cncclose(dev_t dev, int flag, int mode, struct proc *p)
{
cnc_prog_reset();
cnc_opened = 0;
return (0);
}
int
cncwrite(dev_t dev, struct uio *uio, int ioflag)
{
const char *cause;
struct cnc_insn insn, *iNew;
u_int ip = 0;
while (uio->uio_resid > 0) {
int rv;
rv = uiomove(&insn, sizeof(struct cnc_insn), uio);
if (rv != 0) {
printf("cncwrite: uiomove: %d\n", rv);
return (EIO);
}
if (insn.i_type < 0 || insn.i_type >= CNC_LAST_INSN) {
printf("cncwrite: bad insn %d\n", insn.i_type);
return (ENODEV);
}
/*
* We do the best we can to validate the program
* before it is executed.
*/
switch (insn.i_type) {
#if NSERVO > 0
case CNC_MOVE:
if (cnc_vec_distance(&cnc_pos, &insn.i_pos) == 0) {
cause = "L=0";
goto fail;
}
if (insn.i_Amax == 0) { cause = "Amax=0"; goto fail; }
if (insn.i_Jmax == 0) { cause = "Jmax=0"; goto fail; }
break;
case CNC_JOG:
break;
#else
case CNC_MOVE:
case CNC_JOG:
cause = "no servo devices available";
goto fail;
#endif /* NSERVO */
case CNC_SET_INTERP:
if (insn.i_interp_mode < 0 ||
insn.i_interp_mode >= CNC_INTERP_LAST) {
cause = "bad interpolation mode";
goto fail;
}
break;
#if NSPINDLE > 0
case CNC_SPINDLE_DIR:
if (spindle_find(insn.i_spindle_id) == NULL) {
cause = "no such spindle";
goto fail;
}
if (insn.i_spindle_dir != -1 &&
insn.i_spindle_dir != 1) {
cause = "bad direction";
goto fail;
}
break;
case CNC_SPINDLE_SPEED:
{
struct spindle_soft *sp;
if ((sp = spindle_find(insn.i_spindle_id)) != NULL) {
if (insn.i_spindle_speed > sp->sc_speed_max) {
cause = "requested speed exceeds "
"spindle capabilities";
goto fail;
}
} else {
cause = "no such spindle";
goto fail;
}
}
break;
case CNC_SPINDLE_START:
case CNC_SPINDLE_STOP:
if (spindle_find(insn.i_spindle_id) == NULL) {
cause = "no such spindle";
goto fail;
}
break;
#else /* NSPINDLE */
case CNC_SPINDLE_DIR:
case CNC_SPINDLE_SPEED:
case CNC_SPINDLE_START:
case CNC_SPINDLE_STOP:
cause = "no spindle devices available";
goto fail;
#endif /* NSPINDLE */
default:
break;
}
if ((iNew = pool_get(&cnc_insnpl, 0)) == NULL) {
cause = "out of memory for instruction";
goto fail;
}
memcpy(iNew, &insn, sizeof(struct cnc_insn));
TAILQ_INSERT_TAIL(&cnc_prog, iNew, prog);
ip++;
}
printf("cnc: added %u insns to program queue\n", ip);
return (0);
fail:
printf("%s[%d]: %s\n", cnc_insn_names[insn.i_type], ip, cause);
return (EINVAL);
}
void
cnc_prog_reset(void)
{
struct cnc_insn *in;
u_int ninsns = 0;
while ((in = TAILQ_FIRST(&cnc_prog)) != NULL) {
TAILQ_REMOVE(&cnc_prog, in, prog);
pool_put(&cnc_insnpl, in);
ninsns++;
}
/* printf("cnc: deleted %u insns\n", ninsns); */
#if NCNCSTATLED > 0
cncstatled_set(0);
#endif
}
void
cnc_message(const char *msg)
{
#if NCNCLCD > 0
int i;
for (i = 0; i < cnc_nlcds; i++) {
struct cnclcd_softc *lcd = cnc_lcds[i];
if (lcd->sc_open) {
cnclcd_puts(lcd, msg);
cnclcd_putc(lcd, '\n');
}
}
#endif
}
int
cnc_prog_exec(void)
{
struct cnc_insn *insn;
u_int ninsns = 0;
int s;
s = splhigh();
#if NCNCSTATLED > 0
/* Initialize error LEDs and establish communication with LCDs. */
cncstatled_set(0);
#endif
#if NCNCLCD > 0
{
int i;
for (i = 0; i < cnc_nlcds; i++) {
struct cnclcd_softc *lcd = cnc_lcds[i];
if (!lcd->sc_open &&
cnclcd_open(cnc_lcds[i], 9600, 7, CNCLCD_PEVEN, 1) == -1) {
printf("%s: cannot open\n", ((struct device *)lcd)->dv_xname);
continue;
}
cnclcd_puts(lcd, ((struct device *)lcd)->dv_xname);
cnclcd_putc(lcd, '\n');
}
}
#endif
if (cnc_timings.hz == 0 || cnc_timings.move_jog == 0) {
printf("cnc: timings are not calibrated!\n");
goto fail;
}
if (cnc_nservos != CNC_NAXES) {
/* XXX */
printf("cnc: nservos=%d, but CNC_NAXES=%d\n", cnc_nservos,
CNC_NAXES);
goto fail;
}
cnc_message("Executing program\n");
TAILQ_FOREACH(insn, &cnc_prog, prog) {
if (insn->i_type >= CNC_LAST_INSN) {
continue;
}
cnc_message(cnc_insn_names[insn->i_type]);
cnc_message("\n");
switch (insn->i_type) {
#if NSERVO > 0
case CNC_MOVE:
if (cnc_move(insn) == -1) {
goto fail;
}
break;
# if NMPG > 0
case CNC_JOG:
if (cnc_nmpgs < 1) {
printf("cnc: JOG: no MPGs are available\n");
goto fail;
}
if (insn->i_mult == 1) {
if (cnc_move_jog_singlestep(insn) == -1)
goto fail;
} else {
if (cnc_move_jog(insn, 0) == -1)
goto fail;
}
break;
# endif /* NMPG */
#endif /* NSERVO */
#if NSPINDLE > 0
case CNC_SPINDLE_START:
if (spindle_start(cnc_spindles[insn->i_spindle_id],
insn) == -1) {
goto fail;
}
break;
case CNC_SPINDLE_STOP:
if (spindle_stop(cnc_spindles[insn->i_spindle_id],
insn) == -1) {
goto fail;
}
break;
case CNC_SPINDLE_SPEED:
if (spindle_speed(cnc_spindles[insn->i_spindle_id],
insn) == -1) {
goto fail;
}
break;
#endif /* NSPINDLE */
default:
printf("cnc: illegal instruction 0x%x\n", insn->i_type);
break;
}
ninsns++;
}
#if 0
{
int i;
for (i = 0; i < cnc_nlcds; i++) {
struct cnclcd_softc *lcd = cnc_lcds[i];
if (lcd->sc_open)
cnclcd_close(lcd);
}
}
#endif
splx(s);
return (0);
fail:
#if NCNCSTATLED > 0
cncstatled_set(1);
#endif
splx(s);
return (EIO);
}
/* Calibrate the delay loop for 1Hz reference. */
cnc_utime_t
cnc_calibrate_hz(void)
{
const int maxIters = 100;
const cnc_utime_t tTgt = 1e6; /* 1s */
struct timeval tv1, tv2;
cnc_utime_t t, r, i;
cnc_time_t d;
int s, j;
/* Start from an arbitrary value. XXX TODO use cpu info */
r = 380000000U;
s = splhigh();
printf("cnc: calibrating delay loop for 1Hz (%d iters max)...",
maxIters);
for (j = 0; j < maxIters; j++) {
microtime(&tv1);
for (i = 0; i < r; i++)
;;
microtime(&tv2);
t = (tv2.tv_sec - tv1.tv_sec)*1e6 +
(tv2.tv_usec - tv1.tv_usec);
printf(" %ld", tTgt-t);
if (t < tTgt) {
d = tTgt - t;
if (d < 10) { break; }
r += d*4;
} else if (t > tTgt) {
d = t - tTgt;
if (d < 10) { break; }
r -= d*4;
} else {
break;
}
}
splx(s);
printf("...using %llu\n", r);
return (r);
}
/* Benchmark cnc_move_jog(). */
cnc_utime_t
cnc_calibrate_move_jog(void)
{
#if NMPG > 0
struct timespec tv1, tv2;
struct cnc_insn insn;
cnc_utime_t r;
int i, s;
if (cnc_nmpgs < 1)
return (1);
printf("cnc: simulating cnc_move_jog()...");
insn.i_type = CNC_JOG;
insn.i_v0 = 100;
insn.i_F = 10000;
insn.i_Amax = 1234;
insn.i_Jmax = 1234;
insn.i_mult = 100;
for (i = 0; i < CNC_NAXES; i++)
cnc_pos.v[i] = 0.0;
/* Benchmark the routine using the system clock. */
s = splhigh();
nanotime(&tv1);
cnc_move_jog(&insn, 1);
nanotime(&tv2);
splx(s);
r = (cnc_utime_t)(tv2.tv_sec - tv1.tv_sec)*1e12 +
(tv2.tv_nsec - tv1.tv_nsec);
printf("...%lluns\n", r);
return (r);
#else
return (1);
#endif
}
int
cncioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
{
cnc_vec_t *pos;
int i;
switch (cmd) {
case CNC_EXECPROG:
return (cnc_prog_exec());
case CNC_RESETPROG:
return (cnc_prog_exec());
case CNC_GETPOS:
pos = (cnc_vec_t *)data;
for (i = 0; i < CNC_NAXES; i++) {
pos->v[i] = cnc_pos.v[i];
}
return (0);
case CNC_SETPOS:
pos = (cnc_vec_t *)data;
for (i = 0; i < CNC_NAXES; i++) {
printf("cnc: SETPOS axis#%d: %lu -> %lu\n", i,
cnc_pos.v[i], pos->v[i]);
cnc_pos.v[i] = pos->v[i];
}
return (0);
case CNC_GETNSERVOS:
*(int *)data = cnc_nservos;
return (0);
case CNC_GETNSPINDLES:
*(int *)data = cnc_nspindles;
return (0);
case CNC_GETNESTOPS:
*(int *)data = cnc_nestops;
return (0);
case CNC_GETNENCODERS:
*(int *)data = cnc_nencoders;
return (0);
case CNC_GETNMPGS:
*(int *)data = cnc_nmpgs;
return (0);
case CNC_GETKINLIMITS:
bcopy(&cnc_kinlimits, (void *)data, sizeof(struct cnc_kinlimits));
return (0);
case CNC_SETKINLIMITS:
bcopy((const void *)data, &cnc_kinlimits, sizeof(struct cnc_kinlimits));
return (0);
case CNC_GETTIMINGS:
bcopy(&cnc_timings, (void *)data, sizeof(struct cnc_timings));
return (0);
case CNC_SETTIMINGS:
bcopy((const void *)data, &cnc_timings, sizeof(struct cnc_timings));
return (0);
case CNC_CALTIMINGS:
if (cnc_timings.hz == 0) {
cnc_timings.hz = cnc_calibrate_hz();
}
if (cnc_timings.move_jog == 0) {
cnc_timings.move_jog = cnc_calibrate_move_jog();
}
bcopy(&cnc_timings, (void *)data, sizeof(struct cnc_timings));
return (0);
default:
break;
}
return (ENODEV);
}
#if NSERVO > 0
void
cnc_move_axis(enum cnc_axis axis, int dir)
{
servo_step(cnc_servos[axis], dir);
cnc_pos.v[axis] += dir;
}
#endif
/*
* General-purpose "update" callback. This is invoked periodically during
* program execution. If it returns -1, the program should abort.
*/
int
cnc_update(void)
{
#if NESTOP > 0
if (estop_raised()) {
printf("cnc: e-stop!\n");
return (-1);
}
#endif
#if NENCODER > 0
{
int i;
for (i = 0; i < cnc_nencoders; i++)
encoder_update(cnc_encoders[i]);
}
#endif
return (0);
}
#if NSERVO > 0
/*
* Move a group of servo/stepper driven axes in a coordinated fashion at
* the specified velocity, subject to our kinematic limits (Amax/Jmax/F).
*
* TODO Implement simultaneous acceleration/deceleration of axes at the
* cost of contour errors, for specialized applications.
*/
int
cnc_move(struct cnc_insn *insn)
{
struct cnc_quintic_profile Q;
cnc_real_t t;
cnc_vec_t v1 = cnc_pos;
cnc_vec_t v2 = insn->i_pos;
cnc_vec_t d;
cnc_pos_t inc, incMin;
int dir[CNC_NAXES];
int i, nonzero = 0;
int axisMajor = 0;
cnc_pos_t vMajor = 0;
/*
* Compute the difference from the current to the new position,
* set axisMajor to the axis with the greatest displacement.
*/
for (i = 0; i < CNC_NAXES; i++) {
d.v[i] = abs(v2.v[i] - v1.v[i]);
if (d.v[i] > vMajor) {
vMajor = d.v[i];
axisMajor = i;
}
dir[i] = (v2.v[i] > v1.v[i]) ? 1 : -1;
if (d.v[i] != 0) { nonzero++; }
}
if (!nonzero)
return (0); /* Already at target position */
/*
* Rescale the d[] values of the other axes to STEPLEN over the
* displacement of axisMajor.
*/
for (i = 0; i < CNC_NAXES; i++) {
if (i != axisMajor)
d.v[i] = d.v[i]*STEPLEN/d.v[axisMajor];
}
/* Compute the time constants for our quintic velocity profile. */
if (cnc_quintic_init(&Q,
(cnc_real_t)d.v[axisMajor],
(cnc_real_t)insn->i_v0,
(cnc_real_t)insn->i_F,
(cnc_real_t)insn->i_Amax,
(cnc_real_t)insn->i_Jmax)
== -1) {
goto fail;
}
/*
* Enter the signal generation loop. We iterate over the displacement
* of the major axis.
*/
for (inc=0, t=0.0;
inc < d.v[axisMajor] && t < MAXSTEPS;
inc++, t+=1.0) {
cnc_real_t v;
cnc_utime_t delay, j;
if (cnc_update() == -1)
goto fail;
/* Increment the major axis once per iteration. */
cnc_move_axis(axisMajor, dir[axisMajor]);
/* Increment the other axes by their scaled intervals. */
for (i = 0; i < CNC_NAXES; i++) {
if (i != axisMajor) {
incMin = (dir[i]*d.v[i]*t)/STEPLEN;
if (v1.v[i]+incMin != cnc_pos.v[i])
cnc_move_axis(i, dir[i]);
}
}
/*
* Evaluate optimal velocity at t in steps/second, and
* enter the corresponding delay loop.
*/
v = cnc_quintic_step(&Q,
t*(Q.Ta + Q.To)/((cnc_real_t)d.v[axisMajor]))*Q.F /
(Q.v0 + Q.v1 + Q.v2) +
Q.v0;
if (v > 0) {
delay = (cnc_utime_t)(cnc_timings.hz/v);
for (j = 0; j < delay; j++)
;;
}
}
return (0);
fail:
printf("cnc: MOVE: Aborted instruction\n");
return (-1);
}
#if NMPG > 0
/*
* Manually control the servos with a MPG until estop is raised.
* The MPG pulses are translated directly to servo pulses.
*/
int
cnc_move_jog_singlestep(struct cnc_insn *insn)
{
int i;
if (cnc_nmpgs < 1) {
printf("cnc: JOG: No MPGs\n");
return (-1);
}
/* Fetch the initial quadrature signal state. */
for (i = 0; i < cnc_nmpgs; i++)
mpg_jog_init(cnc_mpgs[i]);
/*
* Loop translating the quadrature signal to directly to step
* by step motion.
*/
while (!estop_raised()) {
for (i = 0; i < cnc_nmpgs; i++) {
struct mpg_softc *mpg = cnc_mpgs[i];
struct mpg_axis *axis;
int axisIdx;
axisIdx = mpg_get_axis(mpg);
axis = &mpg->sc_axes[axisIdx];
axis->A = gpio_pin_read(mpg->sc_gpio, &mpg->sc_map, MPG_PIN_A);
axis->B = gpio_pin_read(mpg->sc_gpio, &mpg->sc_map, MPG_PIN_B);
if (axis->A == axis->Aprev &&
axis->B == axis->Bprev)
continue;
if (MPG_TRANSITION_CW(axis)) { cnc_move_axis(axisIdx, 1); }
if (MPG_TRANSITION_CCW(axis)) { cnc_move_axis(axisIdx, -1); }
axis->Aprev = axis->A;
axis->Bprev = axis->B;
}
}
return (0);
}
/*
* Variant of cnc_move() for JOG mode. We use the MPGs to manipulate a
* desired target position. The routine spins moving the axes to the
* target position following a quintic velocity profile.
*/
int
cnc_move_jog(struct cnc_insn *insn, int simulate)
{
struct mpg_softc *mpg;
struct cnc_quintic_profile Q, Qprev;
cnc_vec_t vTgt, vTgtLast;
cnc_real_t t = 0.0, dt = 0.0, L;
int dir, axis;
cnc_utime_t Telapsed = 0;
int moving = 0;
/* int Tprint1 = 0; */
/* Initialize the MPG for jog. */
if (cnc_nmpgs < 1) {
printf("cnc: JOG: No MPGs\n");
return (-1);
}
mpg = cnc_mpgs[0]; /* TODO allow multiple mpgs */
mpg_jog_init(mpg);
/* Initialize the target position. */
vTgt = cnc_pos;
vTgtLast = vTgt;
Q.Ta = 0.0;
Q.To = 0.0;
/* Enter the jog loop. */
while (!estop_raised()) {
/*
* Use the MPG to control the target position, fetch
* the currently selected axis.
*/
mpg_jog(mpg, &vTgt, insn->i_mult);
axis = mpg->sc_sel_axis;
/* Compute direction and distance to target. */
L = (cnc_real_t)(vTgt.v[axis] - cnc_pos.v[axis]);
if (L < 0.0) { L = -L; }
dir = (vTgt.v[axis] > cnc_pos.v[axis]) ? +1 : -1;
#if 0
if (++Tprint1 > 10000) {
Tprint1 = 0;
printf("[t=%s/", cnc_fmt_real(t));
printf("%s] d=%lld (cnc_pos=%lld)\n",
cnc_fmt_real(Q.Ta+Q.To),
vTgt.v[axis] - cnc_pos.v[axis],
cnc_pos.v[axis]);
}
#endif
/*
* If the target position has changed, recompute the time
* constants and time increment.
*/
if (!cnc_vec_same(&vTgtLast, &vTgt)) {
cnc_real_t t7prev;
if (!moving) {
moving = 1;
if (!simulate) {
printf("%s: JOG: Moving to %s=%lld\n",
((struct device *)mpg)->dv_xname,
cnc_axis_names[axis], vTgt.v[axis]);
cnc_message("Moving ");
cnc_message(cnc_axis_names[axis]);
cnc_message("\n");
}
}
vTgtLast = vTgt;
Qprev = Q;
if (cnc_quintic_init(&Q, L,
insn->i_v0,
insn->i_F,
insn->i_Amax,
insn->i_Jmax) == -1) {
goto fail;
}
dt = (Q.Ta+Q.To)/L;
printf("cnc: Quintic: L=%s ", cnc_fmt_real(L));
printf("dt=%s ", cnc_fmt_real(dt));
printf("F=%s ", cnc_fmt_real(Q.F));
printf("v0=%s ", cnc_fmt_real(Q.v0));
printf("v1=%s ", cnc_fmt_real(Q.v1));
printf("v2=%s ", cnc_fmt_real(Q.v2));
printf("Aref=%s ", cnc_fmt_real(Q.Aref));
printf("[Ts=%s", cnc_fmt_real(Q.Ts));
printf(" Ta=%s", cnc_fmt_real(Q.Ta));
printf(" To=%s]\n", cnc_fmt_real(Q.To));
t = 0.0;
Telapsed = 0;
if (t7prev >= 1e-6) {
printf("cnc: Rescaling t for (Ta+To)=%s: ",
cnc_fmt_real(Q.Ta+Q.To));
t = t/t7prev*(Q.Ta+Q.To); /* Rescale time */
printf("t=%ss\n", cnc_fmt_real(t));
Telapsed = 0;
}
}
/*
* Move one step to the target position if the velocity profile
* has determined that this iteration must move one step.
*/
if (moving) {
if (L > CNC_POS_ERROR) {
cnc_utime_t v; /* Steps/second */
int clipped = 0;
v = (cnc_utime_t)cnc_quintic_step(&Q, t);
if (v > (cnc_real_t)cnc_kinlimits.Fmax) {
v = (cnc_real_t)cnc_kinlimits.Fmax;
clipped = 1;
}
if (v < Q.v0) {
v = Q.v0;
clipped = =1;
}
#if NCNCSTATLED > 0
cncstatled_set(clipped);
#endif
/*
* Compare the estimated time elapsed (ns)
* to the current velocity (steps/sec).
*/
if ((Telapsed += cnc_timings.move_jog) >
1000000000UL/v) {
Telapsed = 0;
if (!simulate) {
cnc_move_axis(axis, dir);
} else {
cnc_pos.v[axis] += dir;
}
t += dt;
}
} else {
if (!simulate) {
printf("%s: JOG: Reached target %s=%lld ",
((struct device *)mpg)->dv_xname,
cnc_axis_names[axis], cnc_pos.v[axis]);
printf("in %s seconds\n", cnc_fmt_real(t));
}
t = 0.0;
moving = 0;
}
}
if (simulate)
break;
}
return (0);
fail:
printf("cnc: JOG: Aborted instruction\n");
return (-1);
}
#endif /* NMPG > 0 */
#endif /* NSERVO > 0 */