diff --git a/iocBoot/iocsinqEPICS/motor.substitutions.phytron b/iocBoot/iocsinqEPICS/motor.substitutions.phytron new file mode 100644 index 0000000..14c774e --- /dev/null +++ b/iocBoot/iocsinqEPICS/motor.substitutions.phytron @@ -0,0 +1,7 @@ +file "$(MOTOR)/db/basic_asyn_motor.db" +{ +pattern +{P, N, M, DTYP, PORT, ADDR, DESC, EGU, DIR, VELO, VBAS, ACCL, BDST, BVEL, BACC, MRES, PREC, DHLM, DLLM, INIT} +{KM36:phytron:, 1, "m$(N)", "asynMotor", phy, 1, "m1", mm, Pos, 2.0, 0.1, .2, 0, 1, .2, .001, 3, 100, -100, "1"} +{KM36:phytron:, 2, "m$(N)", "asynMotor", phy, 2, "m2", mm, Pos, 2.0, 0.1, .2, 0, 1, .2, .001, 3, 100, -100, "10"} +} diff --git a/iocBoot/iocsinqEPICS/phytrontest.cmd b/iocBoot/iocsinqEPICS/phytrontest.cmd new file mode 100755 index 0000000..138ce5a --- /dev/null +++ b/iocBoot/iocsinqEPICS/phytrontest.cmd @@ -0,0 +1,47 @@ +#!../../bin/linux-x86-debug/sinqEPICS + + +## You may have to change sinqEPICS to something else +## everywhere it appears in this file + +epicsEnvSet("ARCH","linux-x86-debug") +epicsEnvSet("IOC","sinqEPICS") +epicsEnvSet("ASYN","/usr/local/epics/support/asyn-4-18") +epicsEnvSet("MOTOR","/usr/local/epics/support/motor-6-7") +epicsEnvSet("ANC","/usr/local/epics/anc350v17") +epicsEnvSet("EPICS_BASE","/usr/local/epics") + +epicsEnvSet("TOP","/afs/psi.ch/project/sinqdev/sinqepicsapp/iocBoot/iocsinqEPICS") +epicsEnvSet("dbPATH","${EPICS_BASE}/dbd::${ASYN}/dbd:${MOTOR}/dbd:${ANC}/dbd") + + +cd ${TOP} + +## Register all support components +dbLoadDatabase "../../dbd/sinqEPICS.dbd" +#dbLoadDatabase "dbd/sinq.dbd" +sinqEPICS_registerRecordDeviceDriver pdbbase + +## Load record instances +#dbLoadRecords("db/xxx.db","user=koenneckeHost") + + +#---------- load Nanotec motor controller +#drvAsynIPPortConfigure("serial1", "narziss-ts:3002",0,0,0) +#drvAsynIPPortConfigure("serial1", "localhost:5050",0,0,0) +drvAsynIPPortConfigure("serial1", "localhost:8080",0,0,0) +PhytronCreateController("phy","serial1",1,1); + +### Motors + +dbLoadRecords("$(ASYN)/db/asynRecord.db","P=KM36:,R=serial1,PORT=serial1,ADDR=0,OMAX=80,IMAX=80") + + +cd ${TOP}/iocBoot/${IOC} +dbLoadTemplate "motor.substitutions.phytron" + + +iocInit + +## Start any sequence programs +#seq sncxxx,"user=koenneckeHost" diff --git a/sinqEPICSApp/src/Makefile b/sinqEPICSApp/src/Makefile index 9324de3..8813b7e 100644 --- a/sinqEPICSApp/src/Makefile +++ b/sinqEPICSApp/src/Makefile @@ -27,6 +27,7 @@ sinqEPICS_SRCS += sinqEPICS_registerRecordDeviceDriver.cpp sinqEPICS_SRCS += EL734Driver.cpp devScalerEL737.c pmacAsynIPPort.c sinqEPICS_SRCS += pmacController.cpp pmacAxis.cpp sinqEPICS_SRCS += NanotecDriver.cpp stptok.cpp +sinqEPICS_SRCS += PhytronDriver.cpp # Build the main IOC entry point on workstation OSs. diff --git a/sinqEPICSApp/src/PhytronDriver.cpp b/sinqEPICSApp/src/PhytronDriver.cpp new file mode 100644 index 0000000..598b3bf --- /dev/null +++ b/sinqEPICSApp/src/PhytronDriver.cpp @@ -0,0 +1,464 @@ +/* +FILENAME... PhytronDriver.cpp +USAGE... Motor driver support for the Phytron MCC-2 motor controller. + +This is a motor driver for the Phytron MCC-2 motor controller as used at +PSI. The MCC-2 can do two motors per controller. At PSI, X and Y. The MCC-2 can +be chained. We do not do this at PSI, thus it is not supported in this driver. The +driver assumes the controller to be at address 0. + +The ASCII table actually holds two separate characters, STX and ETX for annotating +the start and end of messages. I have heard of no one using this feature. But the +Phytron people use it. The ASCII table also holds two special characters ACK and NACK for +acknowledge and not acknowledged. Again, no one except the phytron people use this. And for NACK, they +use 0x05 instead of 0x15 as in the ASCII table. Thus I have to do some translation in this driver. + +At PSI we talk to the MCC-2 thing via RS-232 and a terminal server. The MCC-2 principally +supports more means of communications. If this driver works with any other then the RS-232 +option is not known. The likelihood of NO is very high. + +Though this driver has been written in 2016, the MCC-2 version used is probably of +2008-2009 vintage. This is the date when the non-EPICS drivers were written. + +Mark Koennecke +September 2016 + +*/ + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "PhytronDriver.h" +#include + +#define IDLEPOLL 60 + +/** Creates a new PhytronController object. + * \param[in] portName The name of the asyn port that will be created for this driver + * \param[in] PhytronPortName The name of the drvAsynSerialPort that was created previously to connect to the Phytron controller + * \param[in] numAxes The number of axes that this controller supports + */ +PhytronController::PhytronController(const char *portName, const char *PhytronPortName, int encX, int encY) + : asynMotorController(portName, 3, 0, + 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 +{ + asynStatus status; + PhytronAxis *pAxis; + static const char *functionName = "PhytronController::PhytronController"; + char etx[2]; + + /* Connect to Phytron controller */ + status = pasynOctetSyncIO->connect(PhytronPortName, 0, &pasynUserController_, NULL); + if (status) { + asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, + "%s: cannot connect to Phytron controller\n", + functionName); + } + etx[0] = 0x03; + etx[1] = '\0'; + pasynOctetSyncIO->setOutputEos(pasynUserController_,etx,strlen(etx)); + pasynOctetSyncIO->setInputEos(pasynUserController_,etx,strlen(etx)); + + pAxis = new PhytronAxis(this, 1, encX); + pAxis = new PhytronAxis(this, 2, encY); + + startPoller(1000./1000., IDLEPOLL, 2); +} + + +/** Creates a new PhytronController object. + * Configuration command, called directly or from iocsh + * \param[in] portName The name of the asyn port that will be created for this driver + * \param[in] PhytronPortName The name of the drvAsynIPPPort that was created previously to connect to the Phytron controller + * \param[in] numAxes The number of axes that this controller supports + */ +extern "C" int PhytronCreateController(const char *portName, const char *PhytronPortName, int encX, int encY) +{ + PhytronController *pPhytronController + = new PhytronController(portName, PhytronPortName, encX, encY); + pPhytronController = NULL; + return(asynSuccess); +} + +/** Reports on status of the driver + * \param[in] fp The file pointer on which report information will be written + * \param[in] level The level of report detail desired + * + * If details > 0 then information is printed about each axis. + * After printing controller-specific information it calls asynMotorController::report() + */ +void PhytronController::report(FILE *fp, int level) +{ + fprintf(fp, "Phytron motor driver %s, numAxes=%d\n", + this->portName, numAxes_); + + // Call the base class method + asynMotorController::report(fp, level); +} + +/** Returns a pointer to an PhytronAxis object. + * Returns NULL if the axis number encoded in pasynUser is invalid. + * \param[in] pasynUser asynUser structure that encodes the axis index number. */ +PhytronAxis* PhytronController::getAxis(asynUser *pasynUser) +{ + return static_cast(asynMotorController::getAxis(pasynUser)); +} + +/** Returns a pointer to an PhytronAxis object. + * Returns NULL if the axis number encoded in pasynUser is invalid. + * \param[in] axisNo Axis index number. */ +PhytronAxis* PhytronController::getAxis(int axisNo) +{ + return static_cast(asynMotorController::getAxis(axisNo)); +} + +/** + * send a command to the Phytron and read the reply. Do some error and controller + * issue fixing on the way + * \param[in] command The command to send + * \param[out] reply The controllers reply + */ + +asynStatus PhytronController::transactController(char command[COMLEN], char reply[COMLEN]) +{ + asynStatus status; + size_t in, out; + int reason; + char myReply[COMLEN+10], myCommand[COMLEN+10], *pPtr; + + pasynOctetSyncIO->flush(pasynUserController_); + + + memset(myCommand,0,sizeof(myCommand)); + memset(myReply,0,sizeof(myReply)); + /* + add the stx + */ + myCommand[0] = 0x02; + pPtr = myCommand+1; + strcpy(pPtr,command); + status = pasynOctetSyncIO->writeRead(pasynUserController_, myCommand, strlen(myCommand), + myReply,sizeof(myReply), 1.,&out,&in,&reason); + if(status != asynSuccess){ + return status; + } + + /* + skip over the stx + */ + pPtr = myReply+1; + /* + handle ACK, NACK + */ + if(*pPtr == 0x05) { + strcpy(reply,"NACK"); + pPtr++; + } else if(*pPtr == 0x06){ + strcpy(reply,"ACK"); + pPtr++; + } + /* + I may need to replace the ETX. But I am not sure if asyn did + not remove it for me. + */ + strncat(reply,pPtr,sizeof(reply)); + + + return status; +} + +// These are the PhytronAxis methods + +/** Creates a new PhytronAxis object. + * \param[in] pC Pointer to the PhytronController to which this axis belongs. + * \param[in] axisNo Index number of this axis, range 0 to pC->numAxes_-1. + * + * Initializes register numbers, etc. + */ +PhytronAxis::PhytronAxis(PhytronController *pC, int axisNo, int enc) + : asynMotorAxis(pC, axisNo), + pC_(pC) +{ + encoder = enc; + if(axisNo == 1){ + phytronChar = 'X'; + } else { + phytronChar = 'Y'; + } + +} + + + +/** 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 PhytronAxis::report(FILE *fp, int level) +{ + if (level > 0) { + fprintf(fp, " axis %d\n", + axisNo_); + } + + // Call the base class method + //asynMotorAxis::report(fp, level); +} + + +asynStatus PhytronAxis::move(double position, int relative, double minVelocity, double maxVelocity, double acceleration) +{ + asynStatus status; + //static const char *functionName = "PhytronAxis::move"; + char command[COMLEN], reply[COMLEN]; + + + if (relative) { + position += this->position; + } + homing = 0; + sprintf(command, "0%cA%f", phytronChar,position/1000.); + status = pC_->transactController(command,reply); + if(strstr(reply,"NACK") != NULL){ + errlogSevPrintf(errlogMajor, "Drive command not acknowledged on %d", axisNo_); + setIntegerParam(pC_->motorStatusProblem_, true); + return asynError; + } + next_poll = -1; + return status; +} + +asynStatus PhytronAxis::home(double minVelocity, double maxVelocity, double acceleration, int forwards) +{ + asynStatus status; + //static const char *functionName = "PhytronAxis::home"; + char command[COMLEN], reply[COMLEN]; + + sprintf(command, "0%cO-",phytronChar); + homing = 1; + next_poll= -1; + status = pC_->transactController(command,reply); + if(strstr(reply,"NACK") != NULL){ + errlogSevPrintf(errlogMajor, "Home command not acknowledged on %d", axisNo_); + setIntegerParam(pC_->motorStatusProblem_, true); + return asynError; + } + return status; +} + +asynStatus PhytronAxis::moveVelocity(double minVelocity, double maxVelocity, double acceleration) +{ + asynStatus status; + double target; + + //static const char *functionName = "PhytronAxis::moveVelocity"; + + // asynPrint(pasynUser_, ASYN_TRACE_FLOW, + // "%s: minVelocity=%f, maxVelocity=%f, acceleration=%f\n", + // functionName, minVelocity, maxVelocity, acceleration); + + + + if (maxVelocity > 0.) { + /* This is a positive move */ + pC_->getDoubleParam(axisNo_,pC_->motorHighLimit_,&target); + } else { + /* This is a negative move */ + pC_->getDoubleParam(axisNo_,pC_->motorLowLimit_,&target); + } + status = move(target,0,0,0,0); + next_poll = -1; + return status; +} + +asynStatus PhytronAxis::stop(double acceleration ) +{ + asynStatus status; + //static const char *functionName = "PhytronAxis::stop"; + char command[COMLEN], reply[COMLEN]; + + sprintf(command, "0%cSN", phytronChar); + status = pC_->transactController(command,reply); + errlogPrintf("Sent STOP on Axis %d\n", axisNo_); + + return status; +} + +asynStatus PhytronAxis::setPosition(double position) +{ + asynStatus status; + //static const char *functionName = "PhytronAxis::setPosition"; + char command[COMLEN], reply[COMLEN]; + + sprintf(command, "0%cP22S%f", phytronChar, position/1000.); + status = pC_->transactController(command,reply); + sprintf(command, "0%cP20S%f", phytronChar, position/1000.); + status = pC_->transactController(command,reply); + next_poll = -1; + + return status; +} + +asynStatus PhytronAxis::setClosedLoop(bool closedLoop) +{ + //static const char *functionName = "PhytronAxis::setClosedLoop"; + + /* + This belongs into the Kingdom of Electronics. + We do not do this. + */ + + return asynError; +} + +/** 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 PhytronAxis::poll(bool *moving) +{ + asynStatus comStatus; + char command[COMLEN], reply[COMLEN]; + double lowlim; + + + // protect against excessive polling + if(time(NULL) < next_poll){ + *moving = false; + return asynSuccess; + } + + // Read the current motor position + if(encoder) { + sprintf(command,"0%cP22R",phytronChar); + } else { + sprintf(command,"0%cP20R",phytronChar); + } + + comStatus = pC_->transactController(command,reply); + if(comStatus) goto skip; + + if(strstr(reply,"NACK") != NULL){ + setIntegerParam(pC_->motorStatusProblem_, true); + errlogSevPrintf(errlogMajor, "Bad reply for position on %d", axisNo_); + goto skip; + } + /* + read over the ACK + */ + sscanf(reply+3,"%lf", &position); + /* errlogPrintf("Axis %d, reply %s, position %lf\n", axisNo_, reply, position); */ + setDoubleParam(pC_->motorPosition_, position*1000); + setDoubleParam(pC_->motorEncoderPosition_, position); + + + // Read the moving status of this motor + sprintf(command,"0%c=H",phytronChar); + comStatus = pC_->transactController(command,reply); + if(comStatus) goto skip; + /* errlogPrintf("Axis %d, status reply %s, position %lf\n", axisNo_, reply, position); */ + if(strstr(reply,"ACKN") != NULL){ + *moving = true; + next_poll = -1; + setIntegerParam(pC_->motorStatusDone_, false); + } else if(strstr(reply,"ACKE") != NULL){ + *moving = false; + next_poll = time(NULL)+IDLEPOLL; + setIntegerParam(pC_->motorStatusDone_, true); + } + + + if(!*moving) { + if(homing){ + pC_->getDoubleParam(axisNo_,pC_->motorLowLimit_,&lowlim); + setPosition(lowlim); + setIntegerParam(pC_->motorStatusAtHome_, true); + } else { + /* + check limits and errors, upper + */ + sprintf(command,"0%c=I+",phytronChar); + comStatus = pC_->transactController(command,reply); + if(comStatus) goto skip; + if(strstr(reply,"ACKE") != NULL){ + setIntegerParam(pC_->motorStatusHighLimit_, true); + } else { + setIntegerParam(pC_->motorStatusHighLimit_, false); + } + + /* + lower limit + */ + sprintf(command,"0%c=I-",phytronChar); + comStatus = pC_->transactController(command,reply); + if(comStatus) goto skip; + if(strstr(reply,"ACKE") != NULL){ + setIntegerParam(pC_->motorStatusLowLimit_, true); + } else { + setIntegerParam(pC_->motorStatusLowLimit_, false); + } + + /* + error + */ + sprintf(command,"0%c=E",phytronChar); + comStatus = pC_->transactController(command,reply); + if(comStatus) goto skip; + if(strstr(reply,"ACKE") != NULL){ + setIntegerParam(pC_->motorStatusProblem_, true); + errlogSevPrintf(errlogMajor, "Electronics on %d", axisNo_); + } else { + setIntegerParam(pC_->motorStatusProblem_, false); + } + } + } + + + skip: + setIntegerParam(pC_->motorStatusProblem_, comStatus ? 1:0); + callParamCallbacks(); + return comStatus ? asynError : asynSuccess; +} + +/** Code for iocsh registration */ +static const iocshArg PhytronCreateControllerArg0 = {"Port name", iocshArgString}; +static const iocshArg PhytronCreateControllerArg1 = {"Phytron port name", iocshArgString}; +static const iocshArg PhytronCreateControllerArg2 = {"EnoderX", iocshArgInt}; +static const iocshArg PhytronCreateControllerArg3 = {"EnoderY", iocshArgInt}; +static const iocshArg * const PhytronCreateControllerArgs[] = {&PhytronCreateControllerArg0, + &PhytronCreateControllerArg1, + &PhytronCreateControllerArg2, + &PhytronCreateControllerArg3}; +static const iocshFuncDef PhytronCreateControllerDef = {"PhytronCreateController", 4, PhytronCreateControllerArgs}; +static void PhytronCreateContollerCallFunc(const iocshArgBuf *args) +{ + PhytronCreateController(args[0].sval, args[1].sval, args[2].ival,args[3].ival); +} + +static void PhytronRegister(void) +{ + iocshRegister(&PhytronCreateControllerDef, PhytronCreateContollerCallFunc); +} + +extern "C" { +epicsExportRegistrar(PhytronRegister); +} diff --git a/sinqEPICSApp/src/PhytronDriver.h b/sinqEPICSApp/src/PhytronDriver.h new file mode 100644 index 0000000..4538f01 --- /dev/null +++ b/sinqEPICSApp/src/PhytronDriver.h @@ -0,0 +1,56 @@ +/* +FILENAME... PhytronDriver.h +USAGE... Motor driver support for the Phytron MCC-2 motor controller. + +Mark Koennecke +September 2016 + +*/ + +#include "asynMotorController.h" +#include "asynMotorAxis.h" + +#define COMLEN 80 + +class PhytronAxis : public asynMotorAxis +{ +public: + /* These are the methods we override from the base class */ + PhytronAxis(class PhytronController *pC, int axis, int enc); + 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: + char phytronChar; + PhytronController *pC_; /**< Pointer to the asynMotorController to which this axis belongs. + * Abbreviated because it is used very frequently */ + double position; + int homing; + time_t next_poll; + int encoder; + +friend class PhytronController; +}; + +class PhytronController : public asynMotorController { +public: + PhytronController(const char *portName, const char *PhytronPortName, int encX, int encY); + + void report(FILE *fp, int level); + PhytronAxis* getAxis(asynUser *pasynUser); + PhytronAxis* getAxis(int axisNo); + +friend class PhytronAxis; + private: + asynUser *pasynUserController_; + + asynStatus transactController(char command[COMLEN], char reply[COMLEN]); + + +}; diff --git a/sinqEPICSApp/src/sinq.dbd b/sinqEPICSApp/src/sinq.dbd index f844a03..2fa4b8c 100644 --- a/sinqEPICSApp/src/sinq.dbd +++ b/sinqEPICSApp/src/sinq.dbd @@ -2,6 +2,7 @@ # SINQ specific DB definitions #--------------------------------------------- registrar(EL734Register) +registrar(PhytronRegister) registrar(NanotecRegister) addpath "/usr/local/epics/support/asyn-4-18/dbd" addpath "/usr/local/epics/dbd"