/* $FabBSD$ */ /* * Copyright (c) 2007-2011 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 #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 "cnc_spotweldervar.h" #include "cncvar.h" #include "cnc_capturevar.h" #include "servo.h" #include "spindle.h" #include "estop.h" #include "encoder.h" #include "mpg.h" #include "cnclcd.h" #include "cncstatled.h" #include "spotwelder.h" /* For cnc_pos_t (uint64), /STEPDIV */ #define STEPLEN 20336 #define STEPMAX (INT64_MAX-1) #define MAXSTEPS (STEPMAX/STEPLEN) /* #define CNC_DEBOUNCE */ 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 */ }; int cnc_opened; struct cnc_vector cnc_pos; struct cnc_timings cnc_timings; struct cnc_stats cnc_stats; struct servo_softc *cnc_servos[CNC_MAX_SERVOS]; struct spindle_softc *cnc_spindles[CNC_MAX_SPINDLES]; struct atc_softc *cnc_atcs[CNC_MAX_ATCS]; 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; struct spotwelder_softc *cnc_spotwelders[CNC_MAX_SPOTWELDERS]; int cnc_debug = 0; int cnc_simulate = 0; int cnc_capture = 0; int cnc_nservos = 0; int cnc_nspindles = 0; int cnc_natcs = 0; int cnc_nestops = 0; int cnc_nencoders = 0; int cnc_nmpgs = 0; int cnc_nlcds = 0; int cnc_nspotwelders = 0; void cncattach(int num) { int i; if (num > 1) return; cnc_opened = 0; for (i = 0; i < CNC_NAXES; i++) cnc_pos.v[i] = 0; cnc_timings.hz = 0; cnc_stats.peak_velocity = 0; cnc_stats.estops = 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 #ifdef MOTIONCAPTURE if (cnc_capture) cnc_capture_close(); #endif 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) { #ifdef MOTIONCAPTURE if (cnc_capture) cnc_capture_close(); #endif cnc_opened = 0; return (0); } int cncread(dev_t dev, struct uio *uio, int ioflag) { #ifdef MOTIONCAPTURE return cnc_capture_read(dev, uio, ioflag); #else return (ENODEV); #endif } int cncwrite(dev_t dev, struct uio *uio, int ioflag) { return (ENODEV); #if 0 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); } 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++; } return (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_open(cnc_lcds[i], 9600, 7, CNCLCD_PEVEN, 1) == -1) { printf("%s: cannot open\n", ((struct device *)lcd)->dv_xname); continue; } cnclcd_puts(lcd, msg); cnclcd_putc(lcd, '\n'); } #endif } /* 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); splx(s); preempt(NULL); s = splhigh(); 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); } 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_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++) { if (cnc_debug) { printf("cnc: SETPOS axis#%d: %lu -> %lu\n", i, cnc_pos.v[i], (u_long)pos->v[i]); } cnc_pos.v[i] = pos->v[i]; } return (0); case CNC_GETDEVICEINFO: { struct cnc_device_info *di = (struct cnc_device_info *)data; di->nservos = cnc_nservos; di->nspindles = cnc_nspindles; di->natcs = cnc_natcs; di->nestops = cnc_nestops; di->nencoders = cnc_nencoders; di->nmpgs = 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(); } bcopy(&cnc_timings, (void *)data, sizeof(struct cnc_timings)); return (0); case CNC_SETCAPTUREMODE: #ifdef MOTIONCAPTURE if (*(int *)data) { cnc_capture_open(); } else { cnc_capture_close(); } return (0); #else return (ENODEV); #endif case CNC_GETSTATS: bcopy(&cnc_stats, (void *)data, sizeof(struct cnc_stats)); return (0); case CNC_CLRSTATS: bzero(&cnc_stats, sizeof(struct cnc_stats)); return (0); case CNC_GETNTOOLS: *(int *)data = cnc_ntools; return (0); case CNC_GETTOOLINFO: { struct cnc_tool *tool = (struct cnc_tool *)data; int idx = tool->idx; if (idx < 0 || idx >= cnc_ntools) { return (EINVAL); } bcopy(&cnc_tools[idx], (void *)data, sizeof(struct cnc_tool)); } return (0); case CNC_SETTOOLINFO: { struct cnc_tool *tool = (struct cnc_tool *)data; int idx = tool->idx; if (idx < 0 || idx >= cnc_ntools) { return (EINVAL); } bcopy((void *)data, &cnc_tools[idx], sizeof(struct cnc_tool)); } return (0); default: break; } return (ENODEV); } /* Return 1 if one of the registered e-stops is raised. */ int cnc_estop_raised(void) { #if NESTOP > 0 int i; for (i = 0; i < cnc_nestops; i++) { struct estop_softc *estop = cnc_estops[i]; if (!estop_get_state(estop)) { continue; } if (estop->sc_open) { cnc_stats.estops++; estop->sc_queued = 1; continue; } if (estop->sc_flags & ESTOP_DEBOUNCE) { if (estop_debounce(estop)) { cnc_stats.estops++; return (1); } } else { cnc_stats.estops++; return (1); } } #endif /* NESTOP > 0 */ return (0); } #if NSERVO > 0 /* Move an axis one increment in the specified direction. */ void cnc_inc_axis(enum cnc_axis axis, int dir) { servo_step(cnc_servos[axis], dir); cnc_pos.v[axis] += dir; } void cnc_clip_velocity(const struct cnc_quintic_profile *Q, cnc_real_t *vel) { int clipped = 0; if (*vel > (cnc_real_t)cnc_kinlimits.Fmax) { *vel = (cnc_real_t)cnc_kinlimits.Fmax; clipped = 1; } if (*vel < Q->v0) { *vel = Q->v0; clipped = 1; } #if NCNCSTATLED > 0 cncstatled_set(clipped); #endif if (*vel > cnc_stats.peak_velocity) cnc_stats.peak_velocity = *vel; } /* * 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 sys_cncmove(struct proc *p, void *v, register_t *retval) { struct sys_cncmove_args /* { syscallarg(const struct cnc_velocity *) vel; syscallarg(const cnc_vec_t *) tgt; } */ *uap = v; const struct cnc_velocity *Vp = SCARG(uap,vel); const cnc_vec_t *v2 = SCARG(uap,tgt); cnc_real_t t, dt, L; cnc_vec_t d, v1=cnc_pos; cnc_pos_t inc, incMin, vMajor=0; struct cnc_quintic_profile Q; int dir[CNC_NAXES], s, i, nonzero=0, axisMajor=0; if (cnc_calibrated()) return (ENXIO); if (Vp->F == 0.0 || Vp->Amax == 0.0 || Vp->Jmax == 0.0) return (EINVAL); if (cnc_debug) { printf("cncmove: v0=%lus/s, F=%lus/s Amax=%lus/us^2 " "Jmax=%lus/us^3\n", Vp->v0, Vp->F, Vp->Amax, Vp->Jmax); } /* * 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. */ L = (cnc_real_t)d.v[axisMajor]; if (cnc_quintic_init(&Q, L, (cnc_real_t)Vp->v0, /* in steps/s */ (cnc_real_t)Vp->F, /* in steps/s */ ((cnc_real_t)Vp->Amax)*1000.0, /* ms -> us */ ((cnc_real_t)Vp->Jmax)*1000.0) /* ms -> us */ == -1) { return (EINVAL); } dt = (Q.Ta+Q.To)/L; if (cnc_debug) { printf("cnc: Linear Move: 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)); } /* * Enter the signal generation loop. We iterate over the displacement * of the major axis. */ if (!cnc_capture) { s = splhigh(); } for (inc=0, t=0.0; inc < d.v[axisMajor] && t < MAXSTEPS; inc++, t+=dt) { cnc_real_t vel; cnc_utime_t delay, j; #ifdef MOTIONCAPTURE struct cnc_capture_frame cf = CNC_CAPTURE_FRAME_INITIALIZER(t); #endif if (cnc_estop_raised()) goto stop; #if NENCODER > 0 for (i = 0; i < cnc_nencoders; i++) encoder_update(cnc_encoders[i]); #endif /* Increment the major axis once per iteration. */ cnc_inc_axis(axisMajor, dir[axisMajor]); CNC_CAPTURE_STEP(&cf, 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_inc_axis(i, dir[i]); CNC_CAPTURE_STEP(&cf, i, dir[i]); } } } CNC_CAPTURE_FRAME(&cf); /* * Evaluate optimal velocity at t in steps/second, and * enter the corresponding delay loop. */ vel = cnc_quintic_step(&Q, t); /* cnc_clip_velocity(&Q, &vel); */ delay = (cnc_utime_t)(cnc_timings.hz/vel); for (j = 0; j < delay; j++) ;; } if (!cnc_capture) { splx(s); } return (0); stop: if (!cnc_capture) { splx(s); } if (cnc_debug) { printf("cnc: emergency stop!\n"); } return (EINTR); } #else /* NSERVO == 0 */ int sys_cncmove(struct proc *p, void *v, register_t *retval) { return (ENODEV); } #endif /* NSERVO > 0 */ #if NSPOTWELDER > 0 /* Return spot welding trigger status */ int sys_cncspotweldtrig(struct proc *p, void *v, register_t *retval) { struct sys_cncspotweldtrig_args /* { syscallarg(int) welder; } */ *uap = v; int welder = SCARG(uap,welder); if (welder < 0 || welder >= cnc_nspotwelders) { return (ENODEV); } return spotwelder_trig(cnc_spotwelders[welder]); } /* Return spot welding select switch status */ int sys_cncspotweldselect(struct proc *p, void *v, register_t *retval) { struct sys_cncspotweldselect_args /* { syscallarg(int) welder; } */ *uap = v; int welder = SCARG(uap,welder); if (welder < 0 || welder >= cnc_nspotwelders) { return (ENODEV); } return spotwelder_select(cnc_spotwelders[welder]); } /* Spot weld */ int sys_cncspotweld(struct proc *p, void *v, register_t *retval) { struct sys_cncspotweld_args /* { syscallarg(int) welder; syscallarg(int) cycles; } */ *uap = v; int welder = SCARG(uap,welder); int cycles = SCARG(uap,cycles); if (welder < 0 || welder >= cnc_nspotwelders) { return (ENODEV); } if (cycles < 0 || cycles > SPOTWELDER_MAXCYCLES) { return (EINVAL); } return spotwelder_weld(cnc_spotwelders[welder], cycles); } #else /* NSPOTWELDER == 0 */ int sys_cncspotweldtrig(struct proc *p, void *v, register_t *retval) { return (ENODEV); } int sys_cncspotweldselect(struct proc *p, void *v, register_t *retval) { return (ENODEV); } int sys_cncspotweld(struct proc *p, void *v, register_t *retval) { return (ENODEV); } #endif /* NSPOTWELDER > 0 */ int sys_spinctl(struct proc *p, void *v, register_t *retval) { return (ENOSYS); } int sys_atcctl(struct proc *p, void *v, register_t *retval) { return (ENOSYS); } int sys_laserctl(struct proc *p, void *v, register_t *retval) { return (ENOSYS); } int sys_pickplacectl(struct proc *p, void *v, register_t *retval) { return (ENOSYS); } int sys_coolantctl(struct proc *p, void *v, register_t *retval) { return (ENOSYS); } int sys_estop(struct proc *p, void *v, register_t *retval) { return cnc_estop_raised() ? 1 : 0; } int sys_cncpos(struct proc *p, void *v, register_t *retval) { struct sys_cncpos_args /* { syscallarg(cnc_vec_t *) vec; } */ *uap = v; cnc_vec_t *pos = SCARG(uap,vec); int i; for (i = 0; i < CNC_NAXES; i++) { pos->v[i] = cnc_pos.v[i]; } return (0); }