From 38c61e12cd0a30b57e8197c385b81c78c1e5f5d2 Mon Sep 17 00:00:00 2001 From: kmpeters Date: Wed, 12 Dec 2012 20:26:52 +0000 Subject: [PATCH] Added support for Micos SMC hydra. --- documentation/motorRecord.html | 2 +- iocBoot/iocWithAsyn/motor.cmd.SMChydra | 32 ++ .../iocWithAsyn/motor.substitutions.SMChydra | 8 + iocBoot/iocWithAsyn/st.cmd.Vx | 3 + motorApp/MicosSrc/Makefile | 3 + motorApp/MicosSrc/SMChydraDriver.cpp | 483 ++++++++++++++++++ motorApp/MicosSrc/SMChydraDriver.h | 56 ++ 7 files changed, 586 insertions(+), 1 deletion(-) create mode 100644 iocBoot/iocWithAsyn/motor.cmd.SMChydra create mode 100644 iocBoot/iocWithAsyn/motor.substitutions.SMChydra create mode 100644 motorApp/MicosSrc/SMChydraDriver.cpp create mode 100644 motorApp/MicosSrc/SMChydraDriver.h diff --git a/documentation/motorRecord.html b/documentation/motorRecord.html index 8976a121..ddd4f210 100644 --- a/documentation/motorRecord.html +++ b/documentation/motorRecord.html @@ -69,7 +69,7 @@ Channel support): MicroMo model MVP 2001 B02.
  • - Micos model MoCo dc controller. + Micos model MoCo dc controller, SMC hydra controller.
  • Delta Tau PMAC2-VME controller. diff --git a/iocBoot/iocWithAsyn/motor.cmd.SMChydra b/iocBoot/iocWithAsyn/motor.cmd.SMChydra new file mode 100644 index 00000000..90130ec2 --- /dev/null +++ b/iocBoot/iocWithAsyn/motor.cmd.SMChydra @@ -0,0 +1,32 @@ +### motor.cmd.SMChydra +# +# This file is designed to be sourced: +# * after the communication port has been configured +# * before iocInit +# + +# Show serial communication +#asynSetTraceMask("serial5", 0, 3) +# Only show errors +asynSetTraceMask("serial5", 0, 1) +# Leave ascii selected so traces can be turned on with a single click +asynSetTraceIOMask("serial5", 0, 1) + +dbLoadTemplate("motor.substitutions.SMChydra") + +# SMChydraCreateController( +# port name, +# asyn port name, +# num axes, +# moving poll period (ms), +# idle poll period (ms) ) +SMChydraCreateController("Hydra1", "serial5", 2, 100, 500) +# Use the following line to disable idle polling +#!SMChydraCreateController("Hydra1", "serial5", 2, 100, 0) + +# This sleep is useful when asyn traces are enabled +#!epicsThreadSleep(3.0) + +# Include an asyn record for each motor to make it easy to enable diagnostic messages +dbLoadRecords("$(ASYN)/db/asynRecord.db","P=IOC:,R=hydraAsyn1,PORT=Hydra1,ADDR=0,OMAX=256,IMAX=256") +dbLoadRecords("$(ASYN)/db/asynRecord.db","P=IOC:,R=hydraAsyn2,PORT=Hydra1,ADDR=1,OMAX=256,IMAX=256") diff --git a/iocBoot/iocWithAsyn/motor.substitutions.SMChydra b/iocBoot/iocWithAsyn/motor.substitutions.SMChydra new file mode 100644 index 00000000..1e60736a --- /dev/null +++ b/iocBoot/iocWithAsyn/motor.substitutions.SMChydra @@ -0,0 +1,8 @@ +file "$(TOP)/db/basic_asyn_motor.db" +{ + pattern + {P, M, DTYP, PORT, ADDR, DESC, EGU, DIR, VMAX, VELO, VBAS, ACCL, BDST, BVEL, BACC, MRES, PREC, DHLM, DLLM, INIT} + {IOC:, m1, "asynMotor", Hydra1, 0, "m1", degrees, Pos, 15., 3., .05, .5, 0, 1.0, 2, 0.01, 2, 175, -175, ""} + {IOC:, m2, "asynMotor", Hydra1, 1, "m2", degrees, Pos, 15., 3., .05, .5, 0, 1.0, 2, 0.01, 2, 175, -175, ""} +} + diff --git a/iocBoot/iocWithAsyn/st.cmd.Vx b/iocBoot/iocWithAsyn/st.cmd.Vx index a544e664..50f3cb2e 100644 --- a/iocBoot/iocWithAsyn/st.cmd.Vx +++ b/iocBoot/iocWithAsyn/st.cmd.Vx @@ -196,6 +196,9 @@ dbLoadRecords("$(MOTOR)/db/motorUtil.db", "P=IOC:") #!MicosConfig(0, "a-Serial[0]") #!drvMicosDebug = 4 +# Micos SMC hydra +#!< motor.cmd.SMChydra + # IMS MDrive driver setup parameters: # (1) maximum number of controllers in system # (2) motor task polling rate (min=1Hz,max=60Hz) diff --git a/motorApp/MicosSrc/Makefile b/motorApp/MicosSrc/Makefile index 4e5532fe..86a68cbf 100644 --- a/motorApp/MicosSrc/Makefile +++ b/motorApp/MicosSrc/Makefile @@ -14,6 +14,9 @@ LIBRARY_IOC = Micos SRCS += MicosRegister.cc SRCS += devMicos.cc drvMicos.cc +# SMC hydra support +SRCS += SMChydraDriver.cpp + Micos_LIBS += motor asyn Micos_LIBS += $(EPICS_BASE_IOC_LIBS) diff --git a/motorApp/MicosSrc/SMChydraDriver.cpp b/motorApp/MicosSrc/SMChydraDriver.cpp new file mode 100644 index 00000000..dab3174b --- /dev/null +++ b/motorApp/MicosSrc/SMChydraDriver.cpp @@ -0,0 +1,483 @@ +/* +FILENAME... SMChydraDriver.cpp +USAGE... Motor driver support for the Micos SMC hydra controller. + +Note: This driver was tested with the Micos SMC hydra CM and + motor forms 0 (stepper) and 1 (linear). + +*/ + + +#include +#include +#include +#include + +#include +#include + +#include + +#include "SMChydraDriver.h" +#include + +#define NINT(f) (int)((f)>0 ? (f)+0.5 : (f)-0.5) + +/** Creates a new SMChydraController object. + * \param[in] portName The name of the asyn port that will be created for this driver + * \param[in] SMChydraPortName The name of the drvAsynSerialPort that was created previously to connect to the SMC hydra controller + * \param[in] numAxes The number of axes that this controller supports + * \param[in] movingPollPeriod The time between polls when any axis is moving + * \param[in] idlePollPeriod The time between polls when no axis is moving + */ +SMChydraController::SMChydraController(const char *portName, const char *SMChydraPortName, int numAxes, + double movingPollPeriod, double idlePollPeriod) + : asynMotorController(portName, numAxes, NUM_SMCHYDRA_PARAMS, + 0, // No additional interfaces beyond those in base class + 0, // No additional callback interfaces beyond those in base class + ASYN_CANBLOCK | ASYN_MULTIDEVICE, + 1, // autoconnect + 0, 0) // Default priority and stack size +{ + int axis; + asynStatus status; + SMChydraAxis *pAxis; + static const char *functionName = "SMChydraController::SMChydraController"; + + /* Connect to SMC hydra controller */ + status = pasynOctetSyncIO->connect(SMChydraPortName, 0, &pasynUserController_, NULL); + if (status) { + asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, + "%s: cannot connect to SMC hydra controller\n", + functionName); + } + for (axis=0; axis 0 then information is printed about each axis. + * After printing controller-specific information it calls asynMotorController::report() + */ +void SMChydraController::report(FILE *fp, int level) +{ + fprintf(fp, "SMC hydra motor driver %s, numAxes=%d, moving poll period=%f, idle poll period=%f\n", + this->portName, numAxes_, movingPollPeriod_, idlePollPeriod_); + + // Call the base class method + asynMotorController::report(fp, level); +} + +/** Returns a pointer to an SMChydraAxis object. + * Returns NULL if the axis number encoded in pasynUser is invalid. + * \param[in] pasynUser asynUser structure that encodes the axis index number. */ +SMChydraAxis* SMChydraController::getAxis(asynUser *pasynUser) +{ + return static_cast(asynMotorController::getAxis(pasynUser)); +} + +/** Returns a pointer to an SMChydraAxis object. + * Returns NULL if the axis number encoded in pasynUser is invalid. + * \param[in] axisNo Axis index number. */ +SMChydraAxis* SMChydraController::getAxis(int axisNo) +{ + return static_cast(asynMotorController::getAxis(axisNo)); +} + + +// These are the SMChydraAxis methods + +/** Creates a new SMChydraAxis object. + * \param[in] pC Pointer to the SMChydraController to which this axis belongs. + * \param[in] axisNo Index number of this axis, range 0 to pC->numAxes_-1. + * + * Initializes register numbers, etc. + */ +SMChydraAxis::SMChydraAxis(SMChydraController *pC, int axisNo) + : asynMotorAxis(pC, axisNo), + pC_(pC) +{ + sprintf(pC_->outString_, "%i getmotor", (axisNo + 1)); + pC_->writeReadController(); + motorForm_ = atoi( (char *) &pC_->inString_ ); + + sprintf(pC_->outString_, "%i getpitch", (axisNo + 1)); + pC_->writeReadController(); + pitch_ = atof( (char *) &pC_->inString_ ); + + sprintf(pC_->outString_, "%i getpolepairs", (axisNo + 1)); + pC_->writeReadController(); + polePairs_ = atoi( (char *) &pC_->inString_ ); + + sprintf(pC_->outString_, "%i getclperiod", (axisNo + 1)); + pC_->writeReadController(); + clPeriod_ = atof( (char *) &pC_->inString_ ); + + switch (motorForm_) + { + case 0: + // Stepper motor + axisRes_ = pitch_ / ( 4.0 * polePairs_); + break; + + case 1: + // Linear or torque motor + axisRes_ = clPeriod_; + break; + + default: + // For now assume clPeriod_ works for other motor forms + axisRes_ = clPeriod_; + break; + } + + /* Enable gain support so that the CNEN field can be used to send + the init command to clear a motor fault for stepper motors, even + though they lack closed-loop support. */ + setIntegerParam(pC_->motorStatusGainSupport_, 1); + + // Determine the travel limits (will change after homing) + sprintf(pC_->outString_, "%i getnlimit", (axisNo + 1)); + pC_->writeReadController(); + sscanf(pC_->inString_, "%lf %lf", &negTravelLimit_, &posTravelLimit_); + +} + +/** Reports on status of the axis + * \param[in] fp The file pointer on which report information will be written + * \param[in] level The level of report detail desired + * + * After printing device-specific information calls asynMotorAxis::report() + */ +void SMChydraAxis::report(FILE *fp, int level) +{ + if (level > 0) { + fprintf(fp, " axis %d\n", axisNo_); + fprintf(fp, " motorForm %d\n", motorForm_); + fprintf(fp, " pitch %f\n", pitch_); + fprintf(fp, " polePairs %d\n", polePairs_); + fprintf(fp, " clPeriod %f\n", clPeriod_); + fprintf(fp, " axisRes %f\n", axisRes_); + fprintf(fp, " lowLimitConfig %d\n", lowLimitConfig_); + fprintf(fp, " highLimitConfig %d\n", highLimitConfig_); + fprintf(fp, " posTravelLimit %f\n", posTravelLimit_); + fprintf(fp, " negTravelLimit %f\n", negTravelLimit_); + } + + // Call the base class method + asynMotorAxis::report(fp, level); +} + +asynStatus SMChydraAxis::sendAccelAndVelocity(double acceleration, double velocity) +{ + asynStatus status; + // static const char *functionName = "SMChydraAxis::sendAccelAndVelocity"; + + // Send the velocity + sprintf(pC_->outString_, "%f %i snv", (velocity * axisRes_), (axisNo_ + 1)); + status = pC_->writeController(); + + // Send the acceleration + // acceleration is in units/sec/sec + sprintf(pC_->outString_, "%f %i sna", (acceleration * axisRes_), (axisNo_ + 1)); + status = pC_->writeController(); + return status; +} + + +asynStatus SMChydraAxis::move(double position, int relative, double baseVelocity, double slewVelocity, double acceleration) +{ + asynStatus status; + // static const char *functionName = "SMChydraAxis::move"; + + status = sendAccelAndVelocity(acceleration, slewVelocity); + + if (relative) { + sprintf(pC_->outString_, "%f %i nr", (position * axisRes_), (axisNo_ + 1)); + } else { + sprintf(pC_->outString_, "%f %i nm", (position * axisRes_), (axisNo_ + 1)); + } + status = pC_->writeController(); + return status; +} + +asynStatus SMChydraAxis::home(double baseVelocity, double slewVelocity, double acceleration, int forwards) +{ + asynStatus status; + // static const char *functionName = "SMChydraAxis::home"; + + status = sendAccelAndVelocity(acceleration, slewVelocity); + + if (forwards) { + sprintf(pC_->outString_, "%i nrm", (axisNo_ + 1)); + } else { + sprintf(pC_->outString_, "%i ncal", (axisNo_ + 1)); + } + status = pC_->writeController(); + return status; +} + +asynStatus SMChydraAxis::moveVelocity(double baseVelocity, double slewVelocity, double acceleration) +{ + asynStatus status; + static const char *functionName = "SMChydraAxis::moveVelocity"; + + asynPrint(pasynUser_, ASYN_TRACE_FLOW, + "%s: baseVelocity=%f, slewVelocity=%f, acceleration=%f\n", + functionName, baseVelocity, slewVelocity, acceleration); + + /* SMC hydra does not have jog command. Move to a limit*/ + if (slewVelocity > 0.) { + status = sendAccelAndVelocity(acceleration, slewVelocity); + sprintf(pC_->outString_, "%f %i nm", posTravelLimit_, (axisNo_ + 1)); + } else { + status = sendAccelAndVelocity(acceleration, (slewVelocity * -1.0)); + sprintf(pC_->outString_, "%f %i nm", negTravelLimit_, (axisNo_ + 1)); + } + status = pC_->writeController(); + return status; +} + +asynStatus SMChydraAxis::stop(double acceleration ) +{ + asynStatus status; + //static const char *functionName = "SMChydraAxis::stop"; + + sprintf(pC_->outString_, "%i nabort", (axisNo_ + 1)); + status = pC_->writeController(); + return status; +} + +asynStatus SMChydraAxis::setPosition(double position) +{ + asynStatus status; + //static const char *functionName = "SMChydraAxis::setPosition"; + + sprintf(pC_->outString_, "%f %i setnpos", (position * axisRes_), (axisNo_ + 1)); + status = pC_->writeController(); + return status; +} + +asynStatus SMChydraAxis::setClosedLoop(bool closedLoop) +{ + asynStatus status = asynSuccess; + //static const char *functionName = "SMChydraAxis::setClosedLoop"; + + switch (motorForm_) + { + case 0: + // Stepper motor + + // reinit to clear a motor fault when CNEN=1 + if (closedLoop) + { + sprintf(pC_->outString_, "%i init", (axisNo_ + 1)); + status = pC_->writeController(); + + // a delay is required after the init command is sent + epicsThreadSleep(0.2); + + } + + break; + + case 1: + // Linear or torque motor + + // enable closed-loop control + sprintf(pC_->outString_, "%i %i setcloop", closedLoop ? 1:0, (axisNo_ + 1)); + status = pC_->writeController(); + + // reinit so the closed-loop setting takes effect + sprintf(pC_->outString_, "%i init", (axisNo_ + 1)); + status = pC_->writeController(); + + // a delay is required after the init command is sent + epicsThreadSleep(0.2); + + break; + + default: + // Do nothing + break; + } + + return status; +} + +/** Polls the axis. + * This function reads the motor position, the limit status, the home status, the moving status, + * and the drive power-on status. + * It calls setIntegerParam() and setDoubleParam() for each item that it polls, + * and then calls callParamCallbacks() at the end. + * \param[out] moving A flag that is set indicating that the axis is moving (true) or done (false). */ +asynStatus SMChydraAxis::poll(bool *moving) +{ + int done; + int driveOn; + int lowLimit; + int highLimit; + int ignoreLowLimit; + int ignoreHighLimit; + int axisStatus=-1; + double position=0.0; + asynStatus comStatus; + + static const char *functionName = "SMChydraAxis::poll"; + + // Read the current motor position + sprintf(pC_->outString_, "%i np", (axisNo_ + 1)); + comStatus = pC_->writeReadController(); + if (comStatus) goto skip; + // The response string is a double + position = atof( (char *) &pC_->inString_); + setDoubleParam(pC_->motorPosition_, (position / axisRes_) ); + + // Read the status of this motor + sprintf(pC_->outString_, "%i nst", (axisNo_ + 1)); + comStatus = pC_->writeReadController(); + if (comStatus) goto skip; + // The response string is an int + axisStatus = atoi( (char *) &pC_->inString_); + + // Check the moving bit + done = !(axisStatus & 0x1); + setIntegerParam(pC_->motorStatusDone_, done); + setIntegerParam(pC_->motorStatusMoving_, !done); + *moving = done ? false:true; + + // Read the commanded velocity and acceleration + sprintf(pC_->outString_, "%i gnv", (axisNo_ + 1)); + comStatus = pC_->writeReadController(); + + sprintf(pC_->outString_, "%i gna", (axisNo_ + 1)); + comStatus = pC_->writeReadController(); + + + // Check the limit bit (0x40) + if (axisStatus & 0x40) + { + asynPrint(this->pasynUser_, ASYN_TRACEIO_DRIVER, + "%s: axis %i limit indicator active.\n", + functionName, (axisNo_ + 1)); + + // query limits? + } + + // Check the e-stop bit (0x80) + if (axisStatus & 0x80) + { + asynPrint(this->pasynUser_, ASYN_TRACEIO_DRIVER, + "%s: axis %i emergency stopped.\n", + functionName, (axisNo_ + 1)); + } + + // Check the e-stop switch active bit (0x200) + if (axisStatus & 0x200) + { + asynPrint(this->pasynUser_, ASYN_TRACEIO_DRIVER, + "%s: axis %i emergency stop switch active.\n", + functionName, (axisNo_ + 1)); + } + + // Check the device busy bit (0x400) + if (axisStatus & 0x400) + { + asynPrint(this->pasynUser_, ASYN_TRACE_ERROR, + "%s: axis %i device is busy - move commands discarded.\n", + functionName, (axisNo_ + 1)); + } + + // Read the limit status + // Note: calibration switch = low limit; range measure switch = high limit + // also need to read the switch confiruation to see if limits are ignored" + + // Read switch confiruation + // Bit 0: polarity (0 = NO, 1 = NC) + // Bit 1: mask (0 = enabled, 1 = disabled) + sprintf(pC_->outString_, "%i getsw", (axisNo_ + 1)); + comStatus = pC_->writeReadController(); + if (comStatus) goto skip; + sscanf(pC_->inString_, "%i %i", &lowLimitConfig_, &highLimitConfig_); + ignoreLowLimit = lowLimitConfig_ & 0x2; + ignoreHighLimit = highLimitConfig_ & 0x2; + + // Read status of switches 0=inactive 1=active + sprintf(pC_->outString_, "%i getswst", (axisNo_ + 1)); + comStatus = pC_->writeReadController(); + if (comStatus) goto skip; + // The response string is of the form "0 0" + sscanf(pC_->inString_, "%i %i", &lowLimit, &highLimit); + // + if (ignoreLowLimit) + setIntegerParam(pC_->motorStatusLowLimit_, 0); + else + setIntegerParam(pC_->motorStatusLowLimit_, lowLimit); + + if (ignoreHighLimit) + setIntegerParam(pC_->motorStatusHighLimit_, 0); + else + setIntegerParam(pC_->motorStatusHighLimit_, highLimit); + + /*setIntegerParam(pC_->motorStatusAtHome_, limit);*/ + + // Check the drive power bit (0x100) + driveOn = (axisStatus & 0x100) ? 0 : 1; + setIntegerParam(pC_->motorStatusPowerOn_, driveOn); + setIntegerParam(pC_->motorStatusProblem_, 0); + + skip: + setIntegerParam(pC_->motorStatusProblem_, comStatus ? 1:0); + callParamCallbacks(); + return comStatus ? asynError : asynSuccess; +} + +/** Code for iocsh registration */ +static const iocshArg SMChydraCreateControllerArg0 = {"Port name", iocshArgString}; +static const iocshArg SMChydraCreateControllerArg1 = {"SMC hydra port name", iocshArgString}; +static const iocshArg SMChydraCreateControllerArg2 = {"Number of axes", iocshArgInt}; +static const iocshArg SMChydraCreateControllerArg3 = {"Moving poll period (ms)", iocshArgInt}; +static const iocshArg SMChydraCreateControllerArg4 = {"Idle poll period (ms)", iocshArgInt}; +static const iocshArg * const SMChydraCreateControllerArgs[] = {&SMChydraCreateControllerArg0, + &SMChydraCreateControllerArg1, + &SMChydraCreateControllerArg2, + &SMChydraCreateControllerArg3, + &SMChydraCreateControllerArg4}; +static const iocshFuncDef SMChydraCreateControllerDef = {"SMChydraCreateController", 5, SMChydraCreateControllerArgs}; +static void SMChydraCreateControllerCallFunc(const iocshArgBuf *args) +{ + SMChydraCreateController(args[0].sval, args[1].sval, args[2].ival, args[3].ival, args[4].ival); +} + +static void SMChydraRegister(void) +{ + iocshRegister(&SMChydraCreateControllerDef, SMChydraCreateControllerCallFunc); +} + +extern "C" { +epicsExportRegistrar(SMChydraRegister); +} diff --git a/motorApp/MicosSrc/SMChydraDriver.h b/motorApp/MicosSrc/SMChydraDriver.h new file mode 100644 index 00000000..b54ab97c --- /dev/null +++ b/motorApp/MicosSrc/SMChydraDriver.h @@ -0,0 +1,56 @@ +/* +FILENAME... SMChydraDriver.h +USAGE... Motor driver support for the Micos SMC hydra controller. + +*/ + +#include "asynMotorController.h" +#include "asynMotorAxis.h" + +#define MAX_SMCHYDRA_AXES 2 + +// No controller-specific parameters yet +#define NUM_SMCHYDRA_PARAMS 0 + +class SMChydraAxis : public asynMotorAxis +{ +public: + /* These are the methods we override from the base class */ + SMChydraAxis(class SMChydraController *pC, int axis); + void report(FILE *fp, int level); + asynStatus move(double position, int relative, double min_velocity, double max_velocity, double acceleration); + asynStatus moveVelocity(double min_velocity, double max_velocity, double acceleration); + asynStatus home(double min_velocity, double max_velocity, double acceleration, int forwards); + asynStatus stop(double acceleration); + asynStatus poll(bool *moving); + asynStatus setPosition(double position); + asynStatus setClosedLoop(bool closedLoop); + +private: + SMChydraController *pC_; /* Pointer to the asynMotorController to which this axis belongs. + Abbreviated because it is used very frequently */ + asynStatus sendAccelAndVelocity(double accel, double velocity); + int motorForm_; + double pitch_; + int polePairs_; + double clPeriod_; + double axisRes_; + double mres_; + int lowLimitConfig_; + int highLimitConfig_; + double posTravelLimit_; + double negTravelLimit_; + +friend class SMChydraController; +}; + +class SMChydraController : public asynMotorController { +public: + SMChydraController(const char *portName, const char *SMChydraPortName, int numAxes, double movingPollPeriod, double idlePollPeriod); + + void report(FILE *fp, int level); + SMChydraAxis* getAxis(asynUser *pasynUser); + SMChydraAxis* getAxis(int axisNo); + +friend class SMChydraAxis; +};