forked from epics_driver_modules/motorBase
1178 lines
40 KiB
Smalltalk
1178 lines
40 KiB
Smalltalk
program EnsembleTrajectoryScan("P=13IDC:,R=traj1,M1=M1,M2=M2,M3=M3,M4=M4,M5=M5,M6=M6,M7=M7,M8=M8,PORT=serial1")
|
|
|
|
/* sample program invocation:
|
|
* dbLoadRecords("$(MOTOR)/motorApp/Db/trajectoryScan.db","P=xxx:,R=traj1:,NAXES=2,NELM=100,NPULSE=100")
|
|
* ...
|
|
* iocInit()
|
|
* ...
|
|
* seq &EnsembleTrajectoryScan, "P=xxx:,R=traj1:,M1=m1,M2=m2,M3=m3,M4=m4,M5=m5,M6=m6,M7=m7,M8=m8,PORT=none"
|
|
*/
|
|
|
|
/* This sequencer program works with trajectoryScan.db. It implements
|
|
* coordinated trajectory motion with an Aerotech Ensemble motor controller.
|
|
*
|
|
* Tim Mooney -- based on MM4000_trajectoryScan.st by Mark Rivers.
|
|
*/
|
|
|
|
%% #include <stdlib.h>
|
|
%% #include <string.h>
|
|
%% #include <ctype.h>
|
|
%% #include <stdio.h>
|
|
%% #include <math.h>
|
|
%% #include <epicsString.h>
|
|
%% #include <epicsStdio.h>
|
|
%% #include <asynOctetSyncIO.h>
|
|
|
|
#define MAX(a,b) ((a) > (b) ? (a) : (b))
|
|
#define MIN(a,b) ((a) > (b) ? (b) : (a))
|
|
#define NINT(f) (int)((f)>0 ? (f)+0.5 : (f)-0.5)
|
|
|
|
/* This program must be compiled with the recursive option */
|
|
option +r;
|
|
|
|
/* Maximum # of trajectory elements. The variable motorTrajectory
|
|
* uses MAX_AXES*MAX_ELEMENTS*8 bytes in this SNL program (up to 128KB).
|
|
* Similar memory will be required for the records in the database.
|
|
* (Note that currently MAX_AXES is fixed at 8, in trajectoryScan.h.)
|
|
*/
|
|
#define MAX_ELEMENTS 1000
|
|
|
|
/* Maximum # of output pulses. For now, we emit a pulse at the beginning of
|
|
* every trajectory element.
|
|
*/
|
|
#define MAX_PULSES 1000
|
|
|
|
/* Note that MAX_ELEMENTS, and MAX_PULSES must be defined before including
|
|
* trajectoryScan.h, which defines MAX_AXES. */
|
|
#include "EnsembleTrajectoryScan.h"
|
|
|
|
/* Maximum size of string messages we'll be sending to the Ensemble controller */
|
|
#define MAX_MESSAGE_STRING 100
|
|
|
|
/* Buffer sizes */
|
|
#define NAME_LEN 100
|
|
|
|
/* Maximum size of string in EPICS string PVs. This is defined in
|
|
* epicsTypes.h, but in order to include that file it must be escaped, and then
|
|
* SNL compiler gives a warning. */
|
|
#define MAX_STRING_SIZE 40
|
|
|
|
/* Polling interval in seconds for waiting for motors to reach their targets */
|
|
#define POLL_INTERVAL (1/5.)
|
|
#define READ_INTERVAL (1/10.)
|
|
|
|
char stringOut[MAX_MESSAGE_STRING];
|
|
char sbuf[MAX_MESSAGE_STRING];
|
|
char stringIn[MAX_MESSAGE_STRING];
|
|
char abortCommand[MAX_MESSAGE_STRING];
|
|
char *asynPort;
|
|
char *pasynUser; /* This is really asynUser* */
|
|
int status;
|
|
int i;
|
|
int j;
|
|
int k;
|
|
int n;
|
|
int anyMoving;
|
|
int npoints;
|
|
double dtime;
|
|
double dpos;
|
|
double posActual;
|
|
double expectedTime;
|
|
double initialPos[MAX_AXES];
|
|
char macroBuf[NAME_LEN];
|
|
char motorName[NAME_LEN];
|
|
int initStatus;
|
|
int limitViolation;
|
|
int loadingTrajectory;
|
|
|
|
/* All PVs which will be accessed in local C functions need to have their index
|
|
* extracted with pvIndex() */
|
|
int motorCurrentIndex[MAX_AXES];
|
|
int epicsMotorDoneIndex[MAX_AXES];
|
|
int elapsedTimeIndex;
|
|
|
|
#define ABORT_STATE_NONE 0
|
|
#define ABORT_STATE_NOTED 1
|
|
#define ABORT_STATE_SENT 2
|
|
#define ABORT_STATE_DONE 3
|
|
int abortState;
|
|
|
|
%%epicsTimeStamp startTime;
|
|
%%epicsTimeStamp currTime;
|
|
%%epicsTimeStamp lastPollTime;
|
|
|
|
/* Define escaped C functions at end of file */
|
|
%% static int writeOnly(SS_ID ssId, struct UserVar *pVar, char *command);
|
|
%% static int writeRead(SS_ID ssId, struct UserVar *pVar, char *command, char *reply);
|
|
%% static int getMotorPositions(SS_ID ssId, struct UserVar *pVar, double *pos);
|
|
%% static int getMotorMoving(SS_ID ssId, struct UserVar *pVar, int movingMask);
|
|
%% static int getEpicsMotorMoving(SS_ID ssId, struct UserVar *pVar);
|
|
%% static int waitEpicsMotors(SS_ID ssId, struct UserVar *pVar);
|
|
%% static int buildTrajectory(SS_ID ssId, struct UserVar *pVar, double *timeTrajectory,
|
|
%% double *motorTrajectory, int epicsMotorDir, int moveMode, int npoints, int npulses, double motorOffset,
|
|
%% double motorResolution, double *velocity);
|
|
%% static int loadTrajectory(SS_ID ssId, struct UserVar *pVar);
|
|
%% static int userToRaw(double user, double off, int dir, double res);
|
|
%% static double rawToUser(int raw, double off, int dir, double res);
|
|
%% static int sendReceiveCommand(SS_ID ssId, struct UserVar *pVar, char *cmd, char *callerReply);
|
|
%% static int testAbort(SS_ID ssId, struct UserVar *pVar);
|
|
|
|
double position[MAX_AXES][MAX_ELEMENTS];
|
|
double velocity[MAX_AXES][MAX_ELEMENTS];
|
|
double motorStart[MAX_AXES];
|
|
double motorCurr[MAX_AXES];
|
|
double dbuf[MAX_PULSES];
|
|
|
|
/* variables for constructing trajectory commands */
|
|
int movingMask;
|
|
|
|
/* temporary variables to hold mav speed and acceleration for a motor */
|
|
double vmax;
|
|
double amax;
|
|
double d;
|
|
|
|
ss EnsembleTrajectoryScan {
|
|
|
|
/* Initialize things when first starting */
|
|
state init {
|
|
when() {
|
|
initStatus = STATUS_UNDEFINED;
|
|
/* Force numAxes to be <= MAX_AXES */
|
|
if (numAxes > MAX_AXES) numAxes = MAX_AXES;
|
|
for (i=0; i<numAxes; i++) {
|
|
sprintf(macroBuf, "M%d", i+1);
|
|
|
|
sprintf(motorName, "%s%s.VAL", macValueGet("P"), macValueGet(macroBuf));
|
|
pvAssign(epicsMotorPos[i], motorName);
|
|
pvMonitor(epicsMotorPos[i]);
|
|
|
|
sprintf(motorName, "%s%s.DIR", macValueGet("P"), macValueGet(macroBuf));
|
|
pvAssign(epicsMotorDir[i], motorName);
|
|
pvMonitor(epicsMotorDir[i]);
|
|
|
|
sprintf(motorName, "%s%s.OFF", macValueGet("P"), macValueGet(macroBuf));
|
|
pvAssign(epicsMotorOff[i], motorName);
|
|
pvMonitor(epicsMotorOff[i]);
|
|
|
|
sprintf(motorName, "%s%s.DMOV", macValueGet("P"), macValueGet(macroBuf));
|
|
pvAssign(epicsMotorDone[i], motorName);
|
|
pvMonitor(epicsMotorDone[i]);
|
|
|
|
sprintf(motorName, "%s%s.MRES", macValueGet("P"), macValueGet(macroBuf));
|
|
pvAssign(epicsMotorMres[i], motorName);
|
|
pvMonitor(epicsMotorMres[i]);
|
|
|
|
sprintf(motorName, "%s%s.CARD", macValueGet("P"), macValueGet(macroBuf));
|
|
pvAssign(epicsMotorCard[i], motorName);
|
|
pvMonitor(epicsMotorCard[i]);
|
|
|
|
sprintf(motorName, "%s%s.HLM", macValueGet("P"), macValueGet(macroBuf));
|
|
pvAssign(epicsMotorHLM[i], motorName);
|
|
pvMonitor(epicsMotorHLM[i]);
|
|
|
|
sprintf(motorName, "%s%s.LLM", macValueGet("P"), macValueGet(macroBuf));
|
|
pvAssign(epicsMotorLLM[i], motorName);
|
|
pvMonitor(epicsMotorLLM[i]);
|
|
|
|
sprintf(motorName, "%s%s.VELO", macValueGet("P"), macValueGet(macroBuf));
|
|
pvAssign(epicsMotorVELO[i], motorName);
|
|
pvMonitor(epicsMotorVELO[i]);
|
|
|
|
sprintf(motorName, "%s%s.VMAX", macValueGet("P"), macValueGet(macroBuf));
|
|
pvAssign(epicsMotorVMAX[i], motorName);
|
|
pvMonitor(epicsMotorVMAX[i]);
|
|
|
|
sprintf(motorName, "%s%s.VBAS", macValueGet("P"), macValueGet(macroBuf));
|
|
pvAssign(epicsMotorVMIN[i], motorName);
|
|
pvMonitor(epicsMotorVMIN[i]);
|
|
|
|
sprintf(motorName, "%s%s.ACCL", macValueGet("P"), macValueGet(macroBuf));
|
|
pvAssign(epicsMotorACCL[i], motorName);
|
|
pvMonitor(epicsMotorACCL[i]);
|
|
|
|
}
|
|
asynPort = macValueGet("PORT");
|
|
%%pVar->status = pasynOctetSyncIO->connect(pVar->asynPort, 0, (asynUser **)&pVar->pasynUser, NULL);
|
|
if (status != 0) {
|
|
printf("trajectoryScan error in pasynOctetSyncIO->connect\n");
|
|
printf(" status=%d, port=%s\n", status, asynPort);
|
|
}
|
|
for (j=0; j<numAxes; j++) {
|
|
motorCurrentIndex[j] = pvIndex(motorCurrent[j]);
|
|
epicsMotorDoneIndex[j] = pvIndex(epicsMotorDone[j]);
|
|
}
|
|
elapsedTimeIndex = pvIndex(elapsedTime);
|
|
abortState = ABORT_STATE_NONE;
|
|
|
|
/* Clear all event flags */
|
|
efClear(buildMon);
|
|
efClear(executeMon);
|
|
efClear(abortMon);
|
|
efClear(readbackMon);
|
|
efClear(nelementsMon);
|
|
efClear(motorMDVSMon); /* we don't use this */
|
|
if (initStatus == STATUS_UNDEFINED) initStatus = STATUS_SUCCESS;
|
|
loadingTrajectory = 0;
|
|
} state monitor_inputs
|
|
}
|
|
|
|
/* Monitor inputs which control what to do (Build, Execute, Read) */
|
|
state monitor_inputs {
|
|
when(efTestAndClear(buildMon) && (build==1) && (initStatus == STATUS_SUCCESS)) {
|
|
} state build
|
|
|
|
when(efTestAndClear(executeMon) && (execute==1) && ((buildStatus == STATUS_SUCCESS) || (buildStatus == STATUS_WARNING))) {
|
|
} state execute
|
|
|
|
when(efTestAndClear(readbackMon) && (readback==1) /*&& (execStatus == STATUS_SUCCESS)*/) {
|
|
} state readback
|
|
|
|
when(efTestAndClear(nelementsMon) && (nelements>=1)) {
|
|
/* If nelements changes, then change endPulses to this value,
|
|
* since this is what the user normally wants. endPulses can be
|
|
* changed again after changing nelements if this is desired. */
|
|
endPulses = nelements;
|
|
pvPut(endPulses);
|
|
} state monitor_inputs
|
|
|
|
when(efTestAndClear(motorMDVSMon)) {
|
|
/* We don't use this. */
|
|
} state monitor_inputs
|
|
}
|
|
|
|
/* Build trajectory */
|
|
state build {
|
|
when() {
|
|
/* Set busy flag while building */
|
|
buildState = BUILD_STATE_BUSY;
|
|
pvPut(buildState);
|
|
buildStatus=STATUS_UNDEFINED;
|
|
pvPut(buildStatus);
|
|
epicsSnprintf(buildMessage, MSGSIZE, "Building...");
|
|
pvPut(buildMessage);
|
|
|
|
|
|
buildStatus = STATUS_SUCCESS; /* presume we'll be successful */
|
|
/* Initialize new trajectory */
|
|
/* If time mode is TIME_MODE_TOTAL then construct timeTrajectory and post it */
|
|
if (timeMode == TIME_MODE_TOTAL) {
|
|
dtime = time/(nelements-1);
|
|
for (i=0; i<nelements; i++) timeTrajectory[i] = dtime;
|
|
pvPut(timeTrajectory);
|
|
} else {
|
|
for (i=0, time=0.; i<nelements; i++) {
|
|
time += timeTrajectory[i];
|
|
}
|
|
pvPut(time);
|
|
}
|
|
|
|
npoints = nelements;
|
|
|
|
/* calculate time at which motor should reach each trajectory point */
|
|
realTimeTrajectory[0] = 0.;
|
|
for (i=1; i<npoints; i++) {
|
|
realTimeTrajectory[i] = realTimeTrajectory[i-1] + timeTrajectory[i];
|
|
}
|
|
for (i=0; i<npoints; i++) realTimeTrajectory[i] *= timeScale;
|
|
|
|
/* For MEDM plotting */
|
|
for (; i<MAX_ELEMENTS; i++) realTimeTrajectory[i] = realTimeTrajectory[i-1];
|
|
pvPut(realTimeTrajectory);
|
|
|
|
/* Calculate velocities and accelerations for trajectories. Build abort command, in case we need it. */
|
|
n = sprintf(abortCommand, "ABORT");
|
|
for (j=0; j<MAX_AXES; j++) {
|
|
if (moveAxis[j]) {
|
|
%%buildTrajectory(ssId, pVar, pVar->realTimeTrajectory, pVar->motorTrajectory[pVar->j],
|
|
%% pVar->epicsMotorDir[pVar->j], pVar->moveMode, pVar->npoints, pVar->npulses,
|
|
%% pVar->epicsMotorOff[pVar->j], pVar->epicsMotorMres[pVar->j], pVar->velocity[pVar->j]);
|
|
n += sprintf(&abortCommand[n], " @%d", j);
|
|
}
|
|
}
|
|
|
|
/* Compute expected time for trajectory. This includes timeScale factor. */
|
|
expectedTime = realTimeTrajectory[npoints-1];
|
|
|
|
/* Check trajectories against motor soft limits */
|
|
limitViolation = 0;
|
|
for (j=0; j<numAxes && !limitViolation; j++) {
|
|
if (moveAxis[j]) {
|
|
vmax = epicsMotorVMAX[j];
|
|
if (fabs(vmax) < .001) vmax = epicsMotorVELO[j];
|
|
amax = vmax/epicsMotorACCL[j];
|
|
for (k=0; k<npoints && !limitViolation; k++) {
|
|
posActual = motorTrajectory[j][k];
|
|
if (moveMode != MOVE_MODE_ABSOLUTE) posActual += epicsMotorPos[j];
|
|
limitViolation |= (posActual > epicsMotorHLM[j]) || (posActual < epicsMotorLLM[j]);
|
|
if (limitViolation) {
|
|
epicsSnprintf(buildMessage, MSGSIZE, "Limit: m%d at pt. %d (%f)", j+1, k+1, posActual);
|
|
}
|
|
if (velocity[j][k]*epicsMotorMres[j] > vmax) {
|
|
limitViolation |= 1;
|
|
epicsSnprintf(buildMessage, MSGSIZE, "V limit: m%d at pt. %d (%f)", j+1, k+1,
|
|
velocity[j][k]*epicsMotorMres[j]);
|
|
}
|
|
if (k > 1) {
|
|
dtime = realTimeTrajectory[k]-realTimeTrajectory[k-1];
|
|
d = (velocity[j][k] - velocity[j][k-1]) / dtime;
|
|
if (fabs(d) > amax) {
|
|
limitViolation |= 1;
|
|
epicsSnprintf(buildMessage, MSGSIZE, "A limit: m%d at pt. %d (%f)", j+1, k+1, d);
|
|
}
|
|
if ((dtime < .03) && (buildStatus != STATUS_WARNING)) {
|
|
buildStatus = STATUS_WARNING;
|
|
epicsSnprintf(buildMessage, MSGSIZE, "!! deltaT(=%.3f) < .03 at pt. %d", dtime, k+1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (limitViolation) {
|
|
buildStatus = STATUS_FAILURE;
|
|
}
|
|
|
|
/* Set status and message string */
|
|
|
|
/* Clear busy flag, post status */
|
|
buildState = BUILD_STATE_DONE;
|
|
pvPut(buildState);
|
|
pvPut(buildStatus);
|
|
pvPut(buildMessage);
|
|
/* Clear build command, post. This is a "busy" record, don't want
|
|
* to do this until build is complete. */
|
|
build=0;
|
|
pvPut(build);
|
|
if (buildStatus == STATUS_SUCCESS) {
|
|
epicsSnprintf(buildMessage, MSGSIZE, "Done");
|
|
pvPut(buildMessage);
|
|
}
|
|
} state monitor_inputs
|
|
}
|
|
|
|
|
|
state execute {
|
|
when () {
|
|
/* Set busy flag */
|
|
execState = EXECUTE_STATE_MOVE_START;
|
|
pvPut(execState);
|
|
abortState = ABORT_STATE_NONE;
|
|
/* Set status to INVALID */
|
|
execStatus = STATUS_UNDEFINED;
|
|
pvPut(execStatus);
|
|
/* Erase the readback and error arrays */
|
|
for (j=0; j<numAxes; j++) {
|
|
for (i=0; i<MAX_PULSES; i++) {
|
|
motorReadbacks[j][i] = 0.;
|
|
motorError[j][i] = 0.;
|
|
}
|
|
}
|
|
/* Get the initial (pretrajectory) and start (preaccel) positions of the motors */
|
|
for (j=0; j<numAxes; j++) {
|
|
initialPos[j] = epicsMotorPos[j];
|
|
motorStart[j] = motorTrajectory[j][0] - (velocity[j][0] * epicsMotorACCL[j]) / 2;
|
|
if (moveMode != MOVE_MODE_ABSOLUTE) motorStart[j] += epicsMotorPos[j];
|
|
}
|
|
/* Move to start position */
|
|
for (j=0; j<numAxes; j++) {
|
|
if (moveAxis[j]) {
|
|
epicsMotorPos[j] = motorStart[j];
|
|
pvPut(epicsMotorPos[j]);
|
|
}
|
|
}
|
|
%%waitEpicsMotors(ssId, pVar);
|
|
|
|
/* Check that motors went to start positions */
|
|
%%getMotorPositions(ssId, pVar, pVar->motorCurr);
|
|
for (j=0; j<numAxes; j++) {
|
|
if (moveAxis[j]) {
|
|
if (fabs(motorCurr[j] - motorStart[j]) > .01) {
|
|
printf("state execute: motor %d didn't move to start\n", j);
|
|
if (execStatus != STATUS_ABORT) {
|
|
execStatus = STATUS_ABORT;
|
|
%%writeOnly(ssId, pVar, pVar->abortCommand);
|
|
abortState = ABORT_STATE_SENT;
|
|
if (debugLevel) printf("abort: sent command '%s'\n", abortCommand);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (execStatus != STATUS_ABORT) {
|
|
|
|
%%getMotorPositions(ssId, pVar, pVar->motorStart);
|
|
%%epicsTimeGetCurrent(&lastPollTime);
|
|
|
|
/* Get start time of execute */
|
|
elapsedTime = 0.;
|
|
pvPut(elapsedTime);
|
|
%%epicsTimeGetCurrent(&startTime);
|
|
execState = EXECUTE_STATE_EXECUTING;
|
|
pvPut(execState);
|
|
for (j=0, movingMask = 0; j<numAxes; j++) {
|
|
if (moveAxis[j]) movingMask |= (1<<j);
|
|
}
|
|
/* The trajectory will usually begin executing while it is being loaded,
|
|
* because the Ensemble has only a 14-point FIFO for trajectory commands.
|
|
*/
|
|
%%loadTrajectory(ssId, pVar);
|
|
}
|
|
} state wait_execute
|
|
}
|
|
|
|
/* Wait for trajectory to complete */
|
|
state wait_execute {
|
|
|
|
when (execStatus == STATUS_ABORT) {
|
|
/* The trajectory_abort state set has detected an abort. It has
|
|
* already posted the status and message. Don't execute flyback
|
|
* return to top */
|
|
execState = EXECUTE_STATE_DONE;
|
|
pvPut(execState);
|
|
/* Clear execute command, post. This is a "busy" record, don't
|
|
* want to do this until execution is complete. */
|
|
execute = 0;
|
|
pvPut(execute);
|
|
} state monitor_inputs
|
|
|
|
when (execState==EXECUTE_STATE_EXECUTING) {
|
|
|
|
/* Get the current motor positions, post them */
|
|
%%epicsTimeGetCurrent(&currTime);
|
|
%%pVar->dtime = epicsTimeDiffInSeconds(&currTime, &lastPollTime);
|
|
if (dtime > POLL_INTERVAL) {
|
|
%%pVar->elapsedTime = epicsTimeDiffInSeconds(&currTime, &startTime);
|
|
pvPut(elapsedTime);
|
|
%%epicsTimeGetCurrent(&lastPollTime);
|
|
%%getMotorPositions(ssId, pVar, pVar->motorCurrent);
|
|
for (j=0; j<numAxes; j++) {
|
|
if (moveAxis[j]) {
|
|
pvPut(motorCurrent[j]);
|
|
}
|
|
}
|
|
%%pVar->anyMoving = getMotorMoving(ssId, pVar, pVar->movingMask);
|
|
if (anyMoving == 0) {
|
|
execState = EXECUTE_STATE_FLYBACK;
|
|
execStatus = STATUS_SUCCESS;
|
|
strcpy(execMessage, " ");
|
|
}
|
|
/* See if the elapsed time is more than twice expected, time out */
|
|
if (elapsedTime > expectedTime*2.) {
|
|
execState = EXECUTE_STATE_FLYBACK;
|
|
execStatus = STATUS_TIMEOUT;
|
|
strcpy(execMessage, "Timeout");
|
|
|
|
/* abort motion of selected axes */
|
|
%%writeOnly(ssId, pVar, pVar->abortCommand);
|
|
abortState = ABORT_STATE_SENT;
|
|
if (debugLevel) printf("timeout: sent command '%s'\n", abortCommand);
|
|
strcpy(stringOut, "PROGRAM STOP 5");
|
|
%%writeOnly(ssId, pVar, pVar->stringOut);
|
|
|
|
%%waitEpicsMotors(ssId, pVar); /* wait until all motors are done */
|
|
abortState = ABORT_STATE_DONE;
|
|
}
|
|
}
|
|
/* Check for errors while trajectories are in progress */
|
|
} state wait_execute
|
|
|
|
when (execState==EXECUTE_STATE_FLYBACK) {
|
|
if (debugLevel) printf("\nflyback.\n");
|
|
pvPut(elapsedTime);
|
|
pvPut(execState);
|
|
pvPut(execStatus);
|
|
pvPut(execMessage);
|
|
/* Get the current motor positions, post them */
|
|
%%getMotorPositions(ssId, pVar, pVar->motorCurrent);
|
|
for (j=0; j<numAxes; j++) pvPut(motorCurrent[j]);
|
|
for (j=0; j<numAxes; j++) {
|
|
if (moveAxis[j]) {
|
|
epicsMotorPos[j] = motorCurrent[j];
|
|
pvPut(epicsMotorPos[j]);
|
|
}
|
|
}
|
|
%%waitEpicsMotors(ssId, pVar);
|
|
if (debugLevel) printf("\n...flyback done\n");
|
|
|
|
execState = EXECUTE_STATE_DONE;
|
|
pvPut(execState);
|
|
/* Clear execute command, post. This is a "busy" record, don't
|
|
* want to do this until execution is complete. */
|
|
execute=0;
|
|
pvPut(execute);
|
|
} state monitor_inputs
|
|
}
|
|
|
|
/* Read back actual positions */
|
|
state readback {
|
|
when() {
|
|
/* Set busy flag */
|
|
readState = READ_STATE_BUSY;
|
|
pvPut(readState);
|
|
readStatus=STATUS_UNDEFINED;
|
|
pvPut(readStatus);
|
|
|
|
/* Post the readback arrays */
|
|
/*for (j=0; j<numAxes; j++) {*/
|
|
for (j=0; j<MAX_AXES; j++) {
|
|
pvPut(motorReadbacks[j]);
|
|
pvPut(motorError[j]);
|
|
}
|
|
/* Clear busy flag */
|
|
readState = READ_STATE_DONE;
|
|
pvPut(readState);
|
|
/* For now we are not handling read errors */
|
|
readStatus = STATUS_SUCCESS;
|
|
pvPut(readStatus);
|
|
strcpy(readMessage, " ");
|
|
pvPut(readMessage);
|
|
/* Clear readback command, post. This is a "busy" record, don't
|
|
* want to do this until readback is complete. */
|
|
readback=0;
|
|
pvPut(readback);
|
|
} state monitor_inputs
|
|
}
|
|
}
|
|
|
|
/* This state set simply monitors the abort input. It is a separate state set
|
|
* so that it is always active, no matter what the state of the trajectoryScan
|
|
* state set. If an abort is received it tells the controller to abort,
|
|
* sets the execStatus to STATUS_ABORT and writes a message to execMessage */
|
|
ss trajectoryAbort {
|
|
state monitorAbort {
|
|
when (efTest(abortMon) && (abort==1) && delay(0.1)) {
|
|
if (debugLevel) printf("trajectoryAbort: loadingTrajectory=%d\n", loadingTrajectory);
|
|
abortState = ABORT_STATE_NOTED;
|
|
if (!loadingTrajectory) {
|
|
efClear(abortMon);
|
|
%%writeOnly(ssId, pVar, pVar->abortCommand);
|
|
if (debugLevel) printf("trajectoryAbort: sent command '%s'\n", abortCommand);
|
|
abortState = ABORT_STATE_SENT;
|
|
|
|
execStatus = STATUS_ABORT;
|
|
pvPut(execStatus);
|
|
strcpy(execMessage, "Abort command sent");
|
|
pvPut(execMessage);
|
|
pvPut(elapsedTime);
|
|
/* Clear abort command, post. This is a "busy" record, don't
|
|
* want to do this until abort command has been sent. */
|
|
abort=0;
|
|
pvPut(abort);
|
|
}
|
|
if (debugLevel==3) {
|
|
printf("trajectoryAbort: calling testAbort\n");
|
|
%%testAbort(ssId, pVar);
|
|
}
|
|
} state monitorAbort
|
|
}
|
|
}
|
|
|
|
/***********************************************************************************************************/
|
|
/* C functions */
|
|
%{
|
|
|
|
/* writeOnly sends a command to the Ensemble controller */
|
|
static int writeOnly(SS_ID ssId, struct UserVar *pVar, char *command)
|
|
{
|
|
asynStatus status=0;
|
|
int debug_out=0;
|
|
size_t nwrite;
|
|
char buffer[MAX_MESSAGE_STRING];
|
|
|
|
/* Copy command so we can add terminator */
|
|
strncpy(buffer, command, MAX_MESSAGE_STRING-3);
|
|
strcat(buffer, "\n");
|
|
if (!(pVar->simMode)) {
|
|
status = pasynOctetSyncIO->write((asynUser *)pVar->pasynUser, buffer,
|
|
strlen(buffer), 1.0, &nwrite);
|
|
}
|
|
if (pVar->execState==EXECUTE_STATE_EXECUTING)
|
|
debug_out = (pVar->debugLevel >= 7);
|
|
else
|
|
debug_out = (pVar->debugLevel >= 2);
|
|
if (pVar->simMode || debug_out) printf(" writeOnly:command='%s'\n", command);
|
|
return(status);
|
|
}
|
|
|
|
|
|
/* writeRead sends a command to the Ensemble controller and reads the response into
|
|
* the global character buffer, stringIn.
|
|
*/
|
|
static int writeRead(SS_ID ssId, struct UserVar *pVar, char *command, char *reply)
|
|
{
|
|
asynStatus status=0;
|
|
char buffer[MAX_MESSAGE_STRING];
|
|
size_t nwrite, nread;
|
|
int eomReason;
|
|
|
|
strncpy(buffer, command, MAX_MESSAGE_STRING-3);
|
|
|
|
strcat(buffer, "\n");
|
|
/* Use 30 second timeout, some commands take a long time to reply */
|
|
if (!(pVar->simMode)) {
|
|
status = pasynOctetSyncIO->writeRead((asynUser *)pVar->pasynUser, buffer,
|
|
strlen(buffer), reply, MAX_MESSAGE_STRING,
|
|
30.0, &nwrite, &nread, &eomReason);
|
|
}
|
|
if (pVar->simMode || (pVar->debugLevel >= 2)) {
|
|
printf(" writeRead:command='%s', reply='%s'\n", buffer, reply);
|
|
}
|
|
return(status);
|
|
}
|
|
|
|
|
|
/* getMotorPositions returns the positions of each motor */
|
|
#define ASCII_ACK_CHAR '%'
|
|
static int getMotorPositions(SS_ID ssId, struct UserVar *pVar, double *pos)
|
|
{
|
|
int j, status;
|
|
int dir, rawP;
|
|
double rawF;
|
|
char inputBuff[MAX_MESSAGE_STRING], outputBuff[MAX_MESSAGE_STRING];
|
|
size_t nwrite, nread;
|
|
int eomReason;
|
|
|
|
for (j=0; j<pVar->numAxes; j++) {
|
|
sprintf(outputBuff, "PFBKPROG(@%d)", j);
|
|
status = pasynOctetSyncIO->writeRead((asynUser *)pVar->pasynUser, outputBuff,
|
|
strlen(outputBuff), inputBuff, MAX_MESSAGE_STRING,
|
|
30.0, &nwrite, &nread, &eomReason);
|
|
if (inputBuff[0] != ASCII_ACK_CHAR)
|
|
rawF = 0;
|
|
else
|
|
rawF = atof(&inputBuff[1]);
|
|
rawP = rawF / fabs(pVar->epicsMotorMres[j]);
|
|
if (pVar->epicsMotorDir[j] == 0) dir=1; else dir=-1;
|
|
pos[j] = rawToUser(rawP, pVar->epicsMotorOff[j], dir, pVar->epicsMotorMres[j]);
|
|
}
|
|
|
|
if (pVar->debugLevel > 2) {
|
|
printf("\npos[0]=%f", pos[0]);
|
|
}
|
|
/*epicsThreadSleep(READ_INTERVAL);*/
|
|
return(0);
|
|
}
|
|
|
|
|
|
/* getMotorMoving returns 1 if any of the motors in movingMask are moving */
|
|
static int getMotorMoving(SS_ID ssId, struct UserVar *pVar, int movingMask)
|
|
{
|
|
char inputBuff[MAX_MESSAGE_STRING], outputBuff[MAX_MESSAGE_STRING];
|
|
size_t nwrite, nread;
|
|
int eomReason;
|
|
int move_active;
|
|
int status;
|
|
|
|
sprintf(outputBuff, "PLANESTATUS(0)");
|
|
status = pasynOctetSyncIO->writeRead((asynUser *)pVar->pasynUser, outputBuff,
|
|
strlen(outputBuff), inputBuff, MAX_MESSAGE_STRING,
|
|
30.0, &nwrite, &nread, &eomReason);
|
|
if (pVar->debugLevel > 2) {
|
|
printf("\ngetMotorMoving: inputBuff='%s'", inputBuff);
|
|
}
|
|
move_active = 0x01 & atoi(&inputBuff[1]);
|
|
|
|
if (move_active) return(1);
|
|
return(0);
|
|
}
|
|
|
|
|
|
/* getEpicsMotorMoving returns the EPICS moving status of each motor, packed into
|
|
* a single int. Bit 0 = motor 1, bit 1 = motor 2, etc. 0=not moving, 1=moving.
|
|
* If the entire int is 0 then no motors are moving */
|
|
static int getEpicsMotorMoving(SS_ID ssId, struct UserVar *pVar)
|
|
{
|
|
int j;
|
|
int result=0, mask=0x01;
|
|
|
|
for (j=0; j<pVar->numAxes; j++) {
|
|
seq_pvGet(ssId, pVar->epicsMotorDoneIndex[j], 0);
|
|
if (pVar->epicsMotorDone[j] == 0) result |= mask;
|
|
mask = mask << 1;
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
/* waitEpicsMotors waits for all motors to stop moving using the EPICS motor
|
|
* records.. It reads and posts the motor positions during each loop. */
|
|
static int waitEpicsMotors(SS_ID ssId, struct UserVar *pVar)
|
|
{
|
|
int j;
|
|
|
|
/* Logic is that we always want to post position motor positions
|
|
* after the end of move is detected. */
|
|
while (getEpicsMotorMoving(ssId, pVar)) {
|
|
/* Get the current motor positions, post them */
|
|
for (j=0; j<pVar->numAxes; j++) {
|
|
pVar->motorCurrent[j] = pVar->epicsMotorPos[j];
|
|
seq_pvPut(ssId, pVar->motorCurrentIndex[j], 0);
|
|
}
|
|
epicsThreadSleep(POLL_INTERVAL);
|
|
}
|
|
for (j=0; j<pVar->numAxes; j++) {
|
|
pVar->motorCurrent[j] = pVar->epicsMotorPos[j];
|
|
seq_pvPut(ssId, pVar->motorCurrentIndex[j], 0);
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/* Calculate velocities suitable for Ensemble PVT commands.
|
|
* We're given x(t) in the form x[i], t[i]. We need to calculate v(x) that will produce x(t).
|
|
* Assume someone else will take care of accelerating onto, and decelerating off of trajectory.
|
|
*/
|
|
#define VELOCITY_LINEAR 0
|
|
static int buildTrajectory(SS_ID ssId, struct UserVar *pVar, double *realTimeTrajectory,
|
|
double *motorTrajectory, int epicsMotorDir, int moveMode, int npoints, int npulses, double motorOffset,
|
|
double motorResolution, double *velocity)
|
|
{
|
|
double dp, dt;
|
|
int i;
|
|
|
|
#if VELOCITY_LINEAR
|
|
for (i=0; i<npoints; i++) {
|
|
if (i==0) {
|
|
dp = motorTrajectory[1] - motorTrajectory[0];
|
|
dt = realTimeTrajectory[1] - realTimeTrajectory[0];
|
|
velocity[i] = dp/dt;
|
|
} else {
|
|
dp = motorTrajectory[i] - motorTrajectory[i-1];
|
|
dt = realTimeTrajectory[i] - realTimeTrajectory[i-1];
|
|
velocity[i] = 2*dp/dt - velocity[i-1];
|
|
}
|
|
if (pVar->debugLevel > 0) {
|
|
printf("point %d: pos=%f, vel=%f, time=%f\n", i, motorTrajectory[i], velocity[i], realTimeTrajectory[i]);
|
|
}
|
|
}
|
|
#else
|
|
for (i=0; i<npoints; i++) {
|
|
if (i==0) {
|
|
dp = motorTrajectory[1] - motorTrajectory[0];
|
|
dt = realTimeTrajectory[1] - realTimeTrajectory[0];
|
|
velocity[i] = dp/dt;
|
|
} else if (i < npoints-1) {
|
|
dp = motorTrajectory[i+1] - motorTrajectory[i-1];
|
|
dt = realTimeTrajectory[i+1] - realTimeTrajectory[i-1];
|
|
velocity[i] = dp/dt;
|
|
} else {
|
|
dp = motorTrajectory[i] - motorTrajectory[i-1];
|
|
dt = realTimeTrajectory[i] - realTimeTrajectory[i-1];
|
|
velocity[i] = 2*dp/dt - velocity[i-1];
|
|
}
|
|
if (pVar->debugLevel > 0) {
|
|
printf("point %d: pos=%f, vel=%f, time=%f\n", i, motorTrajectory[i], velocity[i], realTimeTrajectory[i]);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return(0);
|
|
}
|
|
|
|
static int userToRaw(double user, double off, int dir, double res) {
|
|
return (NINT((user-off)*dir/res));
|
|
}
|
|
|
|
static double rawToUser(int raw, double off, int dir, double res) {
|
|
/*printf("rawToUser: raw=%d, off=%f, dir=%d, res=%f, user=%f\n", raw, off, dir, res, raw*res*dir+off);*/
|
|
return (raw*res*dir+off);
|
|
}
|
|
|
|
/* This is going to get messy. We need to use Ensemble commands like "VELOCITY" and "PVT",
|
|
* But they aren't available via the ASCII interface we're using to send commands. So we
|
|
* use ASCII-legal commands to tell an Aerobasic program the commands we want to execute,
|
|
* and have the AeroBasic program execute those commands.
|
|
*/
|
|
|
|
/* defines for IGLOBAL values to tell AeroBasic program which command to invoke */
|
|
#define cmdDONE 0
|
|
#define cmdVELOCITY_ON 1
|
|
#define cmdVELOCITY_OFF 2
|
|
#define cmdHALT 3
|
|
#define cmdSTART 4
|
|
#define cmdPVT_INIT_TIME_ABS 5
|
|
#define cmdPVT_INIT_TIME_INC 6
|
|
#define cmdPVT1 7 /* PVT command for one motor (PVT i1 d1, d2, TIME d3) */
|
|
#define cmdPVT2 8 /* PVT command for two motors (PVT i1 d1, d2 i2 d3, d4 TIME d5)*/
|
|
#define cmdPVT3 9
|
|
#define cmdPVT4 10
|
|
#define cmdABORT 11
|
|
#define cmdSTARTABORT 12
|
|
|
|
#define cmdVar 45
|
|
#define iarg1Var 46
|
|
#define iarg2Var 47
|
|
#define iarg3Var 48
|
|
#define iarg4Var 49
|
|
#define darg1Var 1
|
|
#define darg2Var 2
|
|
#define darg3Var 3
|
|
#define numIArg 44
|
|
#define numDArg 43
|
|
|
|
#define WAITLOOPS 100
|
|
#define WAITTIME 0.01
|
|
|
|
/* Many of the Ensemble commands needed for trajectories are not supported via the ASCII
|
|
* interface, but must be executed from a precompiled AeroBasic program, which loadTrajectory()
|
|
* has already started for us with the command "PROGRAM RUN 5 doCommand.bcx". We get the
|
|
* Aerobasic program to execute for us by setting Ensemble global variables to specify the
|
|
* command and any arguments, and waiting for the AeroBasic program to notice, execute, and
|
|
* reply by setting the command global variable back to 0.
|
|
*/
|
|
int sendReceiveCommand(SS_ID ssId, struct UserVar *pVar, char *cmd, char *callerReply) {
|
|
int i, i1, i2, i3, i4;
|
|
double d1, d2, d3, d4, d5, d6, d7, d8, d9;
|
|
char stringOut[MAX_MESSAGE_STRING], reply[MAX_MESSAGE_STRING];
|
|
int status, saveDebug;
|
|
|
|
strcpy(stringOut, "TASKSTATE(5)");
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
status = atoi(&reply[1]);
|
|
if (reply[0] == '!') {
|
|
printf("sendReceiveCommand: 'TASKSTATE(5)' returned error.\n");
|
|
return(-1);
|
|
}
|
|
switch (status) {
|
|
case 0:
|
|
printf("sendReceiveCommand: Can't run AeroBasic program\n");
|
|
return(-1);
|
|
case 3: /* running */
|
|
break;
|
|
case 1: /* idle */
|
|
case 2: /* ready but not running */
|
|
case 4: /* paused */
|
|
case 5: /* done */
|
|
strcpy(stringOut, "PROGRAM RUN 5, \"doCommand.bcx\"");
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
epicsThreadSleep(.1);
|
|
break;
|
|
case 6: /* in error */
|
|
strcpy(stringOut, "PROGRAM STOP 5");
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
epicsThreadSleep(.1);
|
|
strcpy(stringOut, "PROGRAM RUN 5, \"doCommand.bcx\"");
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
epicsThreadSleep(.1);
|
|
break;
|
|
default:
|
|
/* do nothing */
|
|
break;
|
|
}
|
|
|
|
if (pVar->debugLevel > 0) {
|
|
printf("command: '%s'\n", cmd);
|
|
}
|
|
|
|
/* Nobody wants to see debug output from all these IGLOBAL commands, so depress the debugLevel. */
|
|
saveDebug = pVar->debugLevel;
|
|
pVar->debugLevel = pVar->debugLevel - 1;
|
|
|
|
if (strncmp(cmd, "VELOCITY ON", strlen("VELOCITY ON")) == 0) {
|
|
sprintf(stringOut, "IGLOBAL(%d) = %d", cmdVar, cmdVELOCITY_ON);
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
} else if (strncmp(cmd, "VELOCITY OFF", strlen("VELOCITY OFF")) == 0) {
|
|
sprintf(stringOut, "IGLOBAL(%d) = %d", cmdVar, cmdVELOCITY_OFF);
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
} else if (strncmp(cmd, "HALT", strlen("HALT")) == 0) {
|
|
sprintf(stringOut, "IGLOBAL(%d) = %d", cmdVar, cmdHALT);
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
} else if (strncmp(cmd, "STARTABORT", strlen("STARTABORT")) == 0) {
|
|
sprintf(stringOut, "IGLOBAL(%d) = %d", cmdVar, cmdSTARTABORT);
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
} else if (strncmp(cmd, "START", strlen("START")) == 0) {
|
|
sprintf(stringOut, "IGLOBAL(%d) = %d", cmdVar, cmdSTART);
|
|
if (callerReply != NULL) {
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
} else {
|
|
writeOnly(ssId, pVar, stringOut);
|
|
}
|
|
} else if (strncmp(cmd, "PVT INIT TIME ABS", strlen("PVT INIT TIME ABS")) == 0) {
|
|
sprintf(stringOut, "IGLOBAL(%d) = %d", cmdVar, cmdPVT_INIT_TIME_ABS);
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
} else if (strncmp(cmd, "PVT INIT TIME INC", strlen("PVT INIT TIME INC")) == 0) {
|
|
sprintf(stringOut, "IGLOBAL(%d) = %d", cmdVar, cmdPVT_INIT_TIME_INC);
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
} else if (strncmp(cmd, "PVT1", strlen("PVT1")) == 0) {
|
|
sscanf(cmd, "PVT1 %d %lf,%lf TIME %lf", &i1, &d1, &d2, &d3);
|
|
sprintf(stringOut, "IGLOBAL(%d) = %d", iarg1Var, i1);
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
sprintf(stringOut, "DGLOBAL(%d) = %f", darg1Var, d1);
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
sprintf(stringOut, "DGLOBAL(%d) = %f", darg2Var, d2);
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
sprintf(stringOut, "DGLOBAL(%d) = %f", darg3Var, d3);
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
sprintf(stringOut, "IGLOBAL(%d) = %d", cmdVar, cmdPVT1);
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
} else if (strncmp(cmd, "ABORT", strlen("ABORT")) == 0) {
|
|
i1 = i2 = i3 = i4 = -1;
|
|
i = sscanf(cmd, "ABORT @%d @%d @%d @%d", &i1, &i2, &i3, &i4);
|
|
sprintf(stringOut, "IGLOBAL(%d) = %d", numIArg, i);
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
sprintf(stringOut, "IGLOBAL(%d) = %d", iarg1Var, i1);
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
sprintf(stringOut, "IGLOBAL(%d) = %d", iarg2Var, i2);
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
sprintf(stringOut, "IGLOBAL(%d) = %d", iarg3Var, i3);
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
sprintf(stringOut, "IGLOBAL(%d) = %d", iarg4Var, i4);
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
sprintf(stringOut, "IGLOBAL(%d) = %d", cmdVar, cmdABORT);
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
}
|
|
|
|
/* Wait for IGLOBAL(cmdVar) to be reset to zero, but don't wait for the "START" command,
|
|
* because it doesn't complete until the trajectory is finished.
|
|
*/
|
|
i = 0;
|
|
if (strncmp(cmd, "START", strlen("START")) != 0) {
|
|
sprintf(stringOut, "IGLOBAL(%d)", cmdVar);
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
for (i=0; i<WAITLOOPS && (strncmp(reply, "%0", strlen("%0")) != 0); i++) {
|
|
epicsThreadSleep(WAITTIME);
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
}
|
|
}
|
|
pVar->debugLevel = saveDebug;
|
|
|
|
if (i>WAITLOOPS-1) {
|
|
printf("sendReceiveCommand: timeout (%.2f s) executing '%s'; reply='%s'\n",
|
|
WAITLOOPS*WAITTIME, cmd, reply);
|
|
if (callerReply != NULL) {
|
|
printf("... reply='%s'\n", reply);
|
|
}
|
|
return(-1);
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
#define PSO_DISTANCE_ARRAY 0
|
|
/**************************************************************************************/
|
|
static int loadTrajectory(SS_ID ssId, struct UserVar *pVar) {
|
|
int i, j, k, status;
|
|
char stringOut[MAX_MESSAGE_STRING], reply[MAX_MESSAGE_STRING];
|
|
double position, dp, dt, dtime;
|
|
int dir;
|
|
int iGlobalIndex, iGlobalIndexStart = 50;
|
|
int nPulses = 1 + pVar->endPulses - pVar->startPulses;
|
|
int intPosition, intPositionLast;
|
|
double accelDist, accelTime, decelDist, decelTime;
|
|
|
|
pVar->loadingTrajectory = 1;
|
|
iGlobalIndex = iGlobalIndexStart;
|
|
|
|
/* digital I/O commands */
|
|
if ((pVar->outBitNum >= 0) && (pVar->outBitNum <= 15)) {
|
|
strcpy(stringOut, "PSOCONTROL @0 RESET");
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
|
|
/*strcpy(stringOut, "PSOOUTPUT @0 CONTROL 1");*/
|
|
/*writeRead(ssId, pVar, stringOut, reply);*/
|
|
|
|
/* (total time, on time) in microseconds */
|
|
/* strcpy(stringOut, "PSOPULSE @0 TIME 50,25"); */
|
|
strcpy(stringOut, "PSOPULSE @0 TIME 5000,2500");
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
strcpy(stringOut, "PSOOUTPUT @0 PULSE");
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
|
|
/* some controllers need to track source 3 instead of 1. I think 3 is the interpolated analog sin/cos */
|
|
strcpy(stringOut, "PSOTRACK @0 INPUT 1");
|
|
/* strcpy(stringOut, "PSOTRACK @0 INPUT 3"); */
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
|
|
/* Not all Ensemble controllers support the "PSODISTANCE X ARRAY" command */
|
|
#if PSO_DISTANCE_ARRAY
|
|
/* Send a PSO pulse at every trajectory point. */
|
|
strcpy(stringOut, "PSODISTANCE @0 ARRAY"); /* use IGLOBAL array */
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
sprintf(stringOut, "PSOARRAY @0,%d,%d", iGlobalIndexStart, nPulses);
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
#else
|
|
/* Just send fixed-distance pulses. For now, don't even worry about StartPulses, EndPulses */
|
|
dp = (pVar->motorTrajectory[0][pVar->npoints-1] - pVar->motorTrajectory[0][0]) / pVar->npoints;
|
|
sprintf(stringOut, "PSODISTANCE @0 FIXED %f UNITS", dp);
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
#endif
|
|
/* When motor is in taxi position, issue "PSOCONTROL @0 ARM" */
|
|
}
|
|
|
|
/* trajectory commands */
|
|
getMotorPositions(ssId, pVar, pVar->motorCurrent);
|
|
|
|
if ((pVar->outBitNum >= 0) && (pVar->outBitNum <= 15)) {
|
|
strcpy(stringOut, "PSOCONTROL @0 ARM");
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
}
|
|
|
|
#if PSO_DISTANCE_ARRAY
|
|
/* Program PSO array output */
|
|
intPositionLast = 0;
|
|
for (j=0; j<1; j++) {
|
|
/* Calc accel portion of trajectory. Note epicsMotorACCL is accel time. */
|
|
accelTime = pVar->epicsMotorACCL[j];
|
|
accelDist = (pVar->velocity[j][0] * pVar->epicsMotorACCL[j]) / 2;
|
|
|
|
decelTime = pVar->epicsMotorACCL[j];
|
|
decelDist = (pVar->velocity[j][pVar->npoints - 1] * pVar->epicsMotorACCL[j]) / 2;
|
|
|
|
if (pVar->moveAxis[j]) {
|
|
for (i=0; i<pVar->npoints; i++) {
|
|
position = pVar->motorTrajectory[j][i] + accelDist;
|
|
/* enable pulses iferror: two or more data types in declaration specifiers we're within user specified range */
|
|
if ((pVar->outBitNum >= 0) && (pVar->outBitNum <= 15)) {
|
|
if (i >= pVar->startPulses && i <= pVar->endPulses) {
|
|
/* Add this pulse-out location to the list */
|
|
intPosition = NINT(((position - pVar->motorStart[j]) + accelDist)/pVar->epicsMotorMres[j]);
|
|
sprintf(stringOut, "IGLOBAL(%d) = %d", iGlobalIndex++, intPosition-intPositionLast);
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
intPositionLast = intPosition;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
strcpy(stringOut, "TASKSTATE(5)");
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
status = atoi(&reply[1]);
|
|
if ((reply[0] == '!') || ((status != 1) && (status != 2) && (status != 5))) {
|
|
strcpy(stringOut, "PROGRAM STOP 5");
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
strcpy(stringOut, "TASKSTATE(5)");
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
}
|
|
|
|
sprintf(stringOut, "IGLOBAL(%d)=0", cmdVar);
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
strcpy(stringOut, "PROGRAM RUN 5, \"doCommand.bcx\"");
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
epicsThreadSleep(.1);
|
|
strcpy(stringOut, "PVT INIT TIME INC");
|
|
status = sendReceiveCommand(ssId, pVar, stringOut, reply);
|
|
if (status) goto bad;
|
|
/* Don't start until I tell you to start */
|
|
strcpy(stringOut, "HALT");
|
|
status = sendReceiveCommand(ssId, pVar, stringOut, reply);
|
|
if (status) goto bad;
|
|
strcpy(stringOut, "VELOCITY ON");
|
|
status = sendReceiveCommand(ssId, pVar, stringOut, reply);
|
|
if (status) goto bad;
|
|
/* Program motion */
|
|
for (j=0; j<MAX_AXES; j++) {
|
|
|
|
/* Calc accel portion of trajectory. Note epicsMotorACCL is accel time. */
|
|
accelTime = pVar->epicsMotorACCL[j];
|
|
accelDist = (pVar->velocity[j][0] * pVar->epicsMotorACCL[j]) / 2;
|
|
|
|
decelTime = pVar->epicsMotorACCL[j];
|
|
decelDist = (pVar->velocity[j][pVar->npoints - 1] * pVar->epicsMotorACCL[j]) / 2;
|
|
|
|
if (pVar->moveAxis[j]) {
|
|
|
|
sprintf(stringOut, "PVT1 %d %f, %f TIME %f", j, accelDist, pVar->velocity[j][0], accelTime);
|
|
status = sendReceiveCommand(ssId, pVar, stringOut, reply);
|
|
if (status) goto bad;
|
|
|
|
for (i=1; i<pVar->npoints; i++) {
|
|
/* Because trajectories > 14 points can't be fully preloaded, but must be loaded
|
|
* while they are executing, we have to handle aborts and user notification here.
|
|
*/
|
|
if (pVar->abortState != ABORT_STATE_NONE) goto abort;
|
|
dp = pVar->motorTrajectory[j][i] - pVar->motorTrajectory[j][i-1];
|
|
dt = pVar->realTimeTrajectory[i] - pVar->realTimeTrajectory[i-1];
|
|
sprintf(stringOut, "PVT1 %d %f, %f TIME %f", j, dp, pVar->velocity[j][i], dt);
|
|
status = sendReceiveCommand(ssId, pVar, stringOut, reply);
|
|
if (status) goto bad;
|
|
epicsTimeGetCurrent(&currTime);
|
|
dtime = epicsTimeDiffInSeconds(&currTime, &lastPollTime);
|
|
if (dtime > POLL_INTERVAL) {
|
|
pVar->elapsedTime = epicsTimeDiffInSeconds(&currTime, &startTime);
|
|
seq_pvPut(ssId, pVar->elapsedTimeIndex, 0);
|
|
epicsTimeGetCurrent(&lastPollTime);
|
|
getMotorPositions(ssId, pVar, pVar->motorCurrent);
|
|
for (k=0; k<pVar->numAxes; k++) {
|
|
if (pVar->moveAxis[k]) {
|
|
seq_pvPut(ssId, pVar->motorCurrentIndex[k], 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
strcpy(stringOut, "VELOCITY OFF");
|
|
status = sendReceiveCommand(ssId, pVar, stringOut, reply);
|
|
if (status) goto bad;
|
|
sprintf(stringOut, "PVT1 %d %f, %f TIME %f", j, decelDist, 0., decelTime);
|
|
status = sendReceiveCommand(ssId, pVar, stringOut, reply);
|
|
if (status) goto bad;
|
|
}
|
|
}
|
|
/*epicsThreadSleep(5.);*/
|
|
strcpy(stringOut, "START");
|
|
/* Tell sendReceiveCommand not to wait for reply by setting reply pointer to NULL. */
|
|
status = sendReceiveCommand(ssId, pVar, stringOut, NULL);
|
|
if (status) goto bad;
|
|
|
|
if (pVar->abortState != ABORT_STATE_NONE) goto abort;
|
|
pVar->loadingTrajectory = 0;
|
|
return(0);
|
|
|
|
bad:
|
|
printf("loadTrajectory: error\n");
|
|
pVar->loadingTrajectory = 0;
|
|
/* return(-1); */
|
|
printf("loadTrajectory: aborting because of error\n");
|
|
|
|
abort:
|
|
printf("loadTrajectory: aborted\n");
|
|
strcpy(stringOut, "VELOCITY OFF");
|
|
status = sendReceiveCommand(ssId, pVar, stringOut, reply);
|
|
strcpy(stringOut, "STARTABORT");
|
|
status = sendReceiveCommand(ssId, pVar, stringOut, reply);
|
|
writeOnly(ssId, pVar, pVar->abortCommand);
|
|
pVar->abortState = ABORT_STATE_SENT;
|
|
pVar->loadingTrajectory = 0;
|
|
return(-1);
|
|
}
|
|
|
|
static int testAbort(SS_ID ssId, struct UserVar *pVar) {
|
|
int j, n, status;
|
|
char stringOut[MAX_MESSAGE_STRING], reply[MAX_MESSAGE_STRING], abortCmd[MAX_MESSAGE_STRING]="";
|
|
|
|
n = sprintf(abortCmd, "ABORT");
|
|
for (j=0; j<MAX_AXES; j++) {
|
|
if (pVar->moveAxis[j]) {
|
|
n += sprintf(&abortCmd[n], " @%d", j);
|
|
}
|
|
}
|
|
|
|
strcpy(stringOut, "enable x");
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
strcpy(stringOut, "acknowledgeall");
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
|
|
strcpy(stringOut, "TASKSTATE(5)");
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
printf("loadTrajectory: TASKSTATE(5) reply='%s'\n", reply);
|
|
|
|
sprintf(stringOut, "IGLOBAL(%d)=0", cmdVar);
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
strcpy(stringOut, "PROGRAM RUN 5, \"doCommand.bcx\"");
|
|
writeRead(ssId, pVar, stringOut, reply);
|
|
epicsThreadSleep(.1);
|
|
|
|
strcpy(stringOut, "VELOCITY OFF");
|
|
status = sendReceiveCommand(ssId, pVar, stringOut, reply);
|
|
/*strcpy(stringOut, "STARTABORT");*/
|
|
strcpy(stringOut, "START");
|
|
status = sendReceiveCommand(ssId, pVar, stringOut, reply);
|
|
writeOnly(ssId, pVar, abortCmd);
|
|
|
|
|
|
|
|
pVar->abortState = ABORT_STATE_SENT;
|
|
return(0);
|
|
}
|
|
|
|
}%
|