diff --git a/configure/CONFIG_SITE b/configure/CONFIG_SITE index 704bc42..aa5f0b5 100644 --- a/configure/CONFIG_SITE +++ b/configure/CONFIG_SITE @@ -23,7 +23,8 @@ CHECK_RELEASE = YES # To install files into a location other than $(TOP) define # INSTALL_LOCATION here. -INSTALL_LOCATION=/usr/local/epics/sinqApp +#INSTALL_LOCATION=/usr/local/epics/sinqApp +INSTALL_LOCATION=/afs/psi.ch/project/sinqdev/sinqepicsapp # Set this when your IOC and the host use different paths # to access the application. This will be needed to boot diff --git a/iocBoot/iocsinqEPICS/envPaths b/iocBoot/iocsinqEPICS/envPaths new file mode 100644 index 0000000..c9e200e --- /dev/null +++ b/iocBoot/iocsinqEPICS/envPaths @@ -0,0 +1,6 @@ +epicsEnvSet("ARCH","linux-x86-debug") +epicsEnvSet("IOC","iocsinqEPICS") +epicsEnvSet("TOP","/afs/psi.ch/project/sinqdev/sinqepicsapp") +epicsEnvSet("EPICS_BASE","/usr/local/epics") +epicsEnvSet("ASYN","/usr/local/epics/support/asyn-4-18") +epicsEnvSet("MOTOR","/usr/local/epics/support/motor-6-7") diff --git a/iocBoot/iocsinqEPICS/motor.substitutions.nanotec b/iocBoot/iocsinqEPICS/motor.substitutions.nanotec new file mode 100644 index 0000000..4056ede --- /dev/null +++ b/iocBoot/iocsinqEPICS/motor.substitutions.nanotec @@ -0,0 +1,8 @@ +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:nano:, 1, "m$(N)", "asynMotor", nano, 1, "m1", mm, Pos, 2.0, 0.1, .2, 0, 1, .2, .0001, 3, 30, -30, "1"} +{KM36:nano:, 2, "m$(N)", "asynMotor", nano, 2, "m2", mm, Pos, 2.0, 0.1, .2, 0, 1, .2, .0001, 3, 60, -60, "10"} +{KM36:nano:, 3, "m$(N)", "asynMotor", nano, 3, "m3", mm, Pos, 2.0, 0.1, .2, 0, 1, .2, .0001, 3, 100, -100, "9"} +} diff --git a/iocBoot/iocsinqEPICS/nanotest.cmd b/iocBoot/iocsinqEPICS/nanotest.cmd new file mode 100755 index 0000000..08b0c07 --- /dev/null +++ b/iocBoot/iocsinqEPICS/nanotest.cmd @@ -0,0 +1,37 @@ +#!../../bin/linux-x86-debug/sinqEPICS + + +## You may have to change sinqEPICS to something else +## everywhere it appears in this file + +< envPaths + +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:2020",0,0,0) +NanotecCreateController("nano","serial1",3,"1,10,9"); + +### 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.nanotec" + + +iocInit + +## Start any sequence programs +#seq sncxxx,"user=koenneckeHost" diff --git a/sinqEPICSApp/src/Makefile b/sinqEPICSApp/src/Makefile index f24f6b9..5ddf24c 100644 --- a/sinqEPICSApp/src/Makefile +++ b/sinqEPICSApp/src/Makefile @@ -26,6 +26,7 @@ sinqEPICS_LIBS += motor asyn std anc350 anc350AsynMotor 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 # Build the main IOC entry point on workstation OSs. diff --git a/sinqEPICSApp/src/NanotecDriver.cpp b/sinqEPICSApp/src/NanotecDriver.cpp new file mode 100644 index 0000000..db25f8c --- /dev/null +++ b/sinqEPICSApp/src/NanotecDriver.cpp @@ -0,0 +1,451 @@ +/* +FILENAME... NanotecDriver.cpp +USAGE... Motor driver support for the Nanotec SMCI controllers + connected to a RS-485 bus. + +Mark Koennecke +July 2015 + +*/ + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "stptok.h" +#include "NanotecDriver.h" +#include + +#define IDLEPOLL 60 + + +/** Creates a new NanotecController object. + * \param[in] portName The name of the asyn port that will be created for this driver + * \param[in] NanotecPortName The name of the drvAsynSerialPort that was created previously to connect to the Nanotec controller + */ +NanotecController::NanotecController(const char *portName, const char *NanotecPortName, int motCount, const char *bus) + : asynMotorController(portName, motCount+1, 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 +{ + int axis, busAddress; + asynStatus status; + NanotecAxis *pAxis; + static const char *functionName = "NanotecController::NanotecController"; + char *pPtr, busNoString[20]; + + + /* Connect to Nanotec controller */ + status = pasynOctetSyncIO->connect(NanotecPortName, 0, &pasynUserController_, NULL); + if (status) { + asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, + "%s: cannot connect to Nanotec controller\n", + functionName); + } + pasynOctetSyncIO->setOutputEos(pasynUserController_,"\r",strlen("\r")); + pasynOctetSyncIO->setInputEos(pasynUserController_,"\r",strlen("\r")); + + axis = 1; + pPtr = (char *)bus; + while((pPtr = stptok(pPtr,busNoString,sizeof(busNoString),",")) != NULL){ + busAddress = atoi(busNoString); + pAxis = new NanotecAxis(this, axis,busAddress); + errlogPrintf("Axis %d, busAddress = %d\n", axis, busAddress); + axis++; + } + + startPoller(1000./1000., IDLEPOLL, 2); +} + + +/** Creates a new NanotecController 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] NanotecPortName The name of the drvAsynIPPPort that was created previously to connect to the Nanotec controller + */ +extern "C" int NanotecCreateController(const char *portName, const char *NanotecPortName, int numMot, const char *busAddresses) +{ + NanotecController *pNanotecController + = new NanotecController(portName, NanotecPortName,numMot,busAddresses); + pNanotecController = 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 NanotecController::report(FILE *fp, int level) +{ + fprintf(fp, "Nanotec motor driver %s, numAxes=%d\n", + this->portName, numAxes_); + + // Call the base class method + asynMotorController::report(fp, level); +} + +/** Returns a pointer to an NanotecAxis object. + * Returns NULL if the axis number encoded in pasynUser is invalid. + * \param[in] pasynUser asynUser structure that encodes the axis index number. */ +NanotecAxis* NanotecController::getAxis(asynUser *pasynUser) +{ + return static_cast(asynMotorController::getAxis(pasynUser)); +} + +/** Returns a pointer to an NanotecAxis object. + * Returns NULL if the axis number encoded in pasynUser is invalid. + * \param[in] axisNo Axis index number. */ +NanotecAxis* NanotecController::getAxis(int axisNo) +{ + return static_cast(asynMotorController::getAxis(axisNo)); +} + + + +// These are the NanotecAxis methods + +/** Creates a new NanotecAxis object. + * \param[in] pC Pointer to the NanotecController to which this axis belongs. + * \param[in] axisNo Index number of this axis, range 0 to pC->numAxes_-1. + * + * Initializes register numbers, etc. + */ +NanotecAxis::NanotecAxis(NanotecController *pC, int axisNo, int busAddress) + : asynMotorAxis(pC, axisNo), + pC_(pC) +{ + this->busAddress = busAddress; + +} + + + +/** 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 NanotecAxis::report(FILE *fp, int level) +{ + if (level > 0) { + fprintf(fp, " axis %d\n", + axisNo_); + } + + // Call the base class method + //asynMotorAxis::report(fp, level); +} + +asynStatus NanotecController::transactController(char command[COMLEN], char reply[COMLEN]) +{ + asynStatus status; + size_t in, out; + int reason; + + pasynOctetSyncIO->flush(pasynUserController_); + + status = pasynOctetSyncIO->writeRead(pasynUserController_, command, strlen(command), + reply,COMLEN, 1.,&out,&in,&reason); + if(status != asynSuccess){ + return status; + } + + /* + check for Nanotec errors + */ + if(strstr(reply,"?") != NULL){ + errlogSevPrintf(errlogMajor, "Bad command %s", command); + return asynError; + } + + if(strlen(reply) < 1) { + errlogSevPrintf(errlogMajor, "No reply received for %s", command); + return asynError; + } + + return status; +} + +asynStatus NanotecAxis::move(double position, int relative, double minVelocity, double maxVelocity, double acceleration) +{ + asynStatus status; + //static const char *functionName = "NanotecAxis::move"; + char command[COMLEN], reply[COMLEN]; + size_t in, out; + int reason; + + // status = sendAccelAndVelocity(acceleration, maxVelocity); + + if (relative) { + position += this->position; + } + + homing = 0; + + /* + set mode + */ + snprintf(command,sizeof(command),"#%dp2",busAddress); + status = pC_->transactController(command,reply); + if(status != asynSuccess){ + return status; + } + + /* + set target + */ + snprintf(command,sizeof(command),"#%ds%d",busAddress, (int)position); + status = pC_->transactController(command,reply); + if(status != asynSuccess){ + return status; + } + + /* + and start.. + */ + snprintf(command,sizeof(command),"#%dA",busAddress); + status = pC_->transactController(command,reply); + if(status != asynSuccess){ + return status; + } + + next_poll = -1; + return status; +} + +asynStatus NanotecAxis::home(double minVelocity, double maxVelocity, double acceleration, int forwards) +{ + asynStatus status; + //static const char *functionName = "NanotecAxis::home"; + char command[COMLEN], reply[COMLEN]; + + /* + set mode + */ + snprintf(command,sizeof(command),"#%dp4",busAddress); + status = pC_->transactController(command,reply); + if(status != asynSuccess){ + return status; + } + + /* + reset positioning errors + */ + snprintf(command,sizeof(command),"#%dD",busAddress); + status = pC_->transactController(command,reply); + if(status != asynSuccess){ + return status; + } + + /* + set direction + */ + snprintf(command,sizeof(command),"#%dd0",busAddress); + status = pC_->transactController(command,reply); + if(status != asynSuccess){ + return status; + } + + + /* + and start.. + */ + snprintf(command,sizeof(command),"#%dA",busAddress); + status = pC_->transactController(command,reply); + if(status != asynSuccess){ + return status; + } + + + homing = 1; + next_poll= -1; + return status; +} + +asynStatus NanotecAxis::moveVelocity(double minVelocity, double maxVelocity, double acceleration) +{ + asynStatus status; + //static const char *functionName = "NanotecAxis::moveVelocity"; + double target; + + // 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); + + return status; +} + +asynStatus NanotecAxis::stop(double acceleration ) +{ + asynStatus status; + //static const char *functionName = "NanotecAxis::stop"; + char command[COMLEN], reply[COMLEN]; + + sprintf(command, "#%dS1", busAddress); + status = pC_->transactController(command,reply); + errlogPrintf("Sent STOP on Axis %d\n", axisNo_); + + return status; +} + +asynStatus NanotecAxis::setPosition(double position) +{ + asynStatus status; + //static const char *functionName = "NanotecAxis::setPosition"; + char command[COMLEN], reply[COMLEN]; + + sprintf(command, "#%dD%d", busAddress, (int)position); + status = pC_->transactController(command,reply); + next_poll = -1; + + return status; +} + +asynStatus NanotecAxis::setClosedLoop(bool closedLoop) +{ + //static const char *functionName = "NanotecAxis::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 NanotecAxis::poll(bool *moving) +{ + asynStatus comStatus; + char command[COMLEN], reply[COMLEN]; + char *pPtr; + int posVal, statVal; + + + // protect against excessive polling + if(time(NULL) < next_poll){ + *moving = false; + return asynSuccess; + } + + // Read the current motor position + sprintf(command,"#%dC", busAddress); + comStatus = pC_->transactController(command,reply); + if(comStatus) goto skip; + + pPtr = strchr(reply,'C'); + pPtr++; + posVal = atoi(pPtr); + + errlogPrintf("Axis %d, reply %s, position %d\n", axisNo_, reply, posVal); + setDoubleParam(pC_->motorPosition_, (double)posVal); + //setDoubleParam(pC_->motorEncoderPosition_, position); + + + // Read the moving status of this motor + sprintf(command,"#%d$",busAddress); + comStatus = pC_->transactController(command,reply); + if(comStatus) goto skip; + + pPtr = strchr(reply,'$'); + pPtr++; + statVal = atoi(pPtr); + errlogPrintf("Axis %d, reply %s, statVal = %d\n", + axisNo_, reply, statVal); + + setIntegerParam(pC_->motorStatusDone_, false); + *moving = true; + if(homing){ + switch(statVal) { + case 164: + setPosition(.0); + break; + case 163: + case 161: + *moving = false; + setIntegerParam(pC_->motorStatusAtHome_, true); + setIntegerParam(pC_->motorStatusDone_, true); + break; + default : + if(statVal & 1) { + *moving = false; + setIntegerParam(pC_->motorStatusAtHome_, true); + setIntegerParam(pC_->motorStatusDone_, true); + } + break; + } + } else { + if(statVal & 1) { + *moving = false; + setIntegerParam(pC_->motorStatusDone_, true); + } + } + + if(*moving == true){ + next_poll = -1; + } + + skip: + setIntegerParam(pC_->motorStatusProblem_, comStatus ? 1:0); + callParamCallbacks(); + return comStatus ? asynError : asynSuccess; +} + +/** Code for iocsh registration */ +static const iocshArg NanotecCreateControllerArg0 = {"Port name", iocshArgString}; +static const iocshArg NanotecCreateControllerArg1 = {"Nanotec port name", iocshArgString}; +static const iocshArg NanotecCreateControllerArg2 = {"Number of axes", iocshArgInt}; +static const iocshArg NanotecCreateControllerArg3 = {"Komma separated list of bus addresses", iocshArgString}; +static const iocshArg * const NanotecCreateControllerArgs[] = {&NanotecCreateControllerArg0, + &NanotecCreateControllerArg1, + &NanotecCreateControllerArg2, + &NanotecCreateControllerArg3 + }; +static const iocshFuncDef NanotecCreateControllerDef = {"NanotecCreateController", 4, NanotecCreateControllerArgs}; +static void NanotecCreateContollerCallFunc(const iocshArgBuf *args) +{ + NanotecCreateController(args[0].sval, args[1].sval, args[2].ival,args[3].sval); +} + +static void NanotecRegister(void) +{ + iocshRegister(&NanotecCreateControllerDef, NanotecCreateContollerCallFunc); +} + +extern "C" { +epicsExportRegistrar(NanotecRegister); +} diff --git a/sinqEPICSApp/src/NanotecDriver.h b/sinqEPICSApp/src/NanotecDriver.h new file mode 100644 index 0000000..5108f51 --- /dev/null +++ b/sinqEPICSApp/src/NanotecDriver.h @@ -0,0 +1,57 @@ +/* +FILENAME... NanotecDriver.h +USAGE... Motor driver support for the Nanotec SMCI controller. + +Mark Koennecke +July 2015 + +*/ + +#include "asynMotorController.h" +#include "asynMotorAxis.h" + +#define COMLEN 80 +#define MAXMOT 99 + +class NanotecAxis : public asynMotorAxis +{ +public: + /* These are the methods we override from the base class */ + NanotecAxis(class NanotecController *pC, int axis, int busAddress); + 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: + NanotecController *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 busAddress; + +friend class NanotecController; +}; + +class NanotecController : public asynMotorController { +public: + NanotecController(const char *portName, const char *NanotecPortName, int numMot, const char *busAddresses); + + void report(FILE *fp, int level); + NanotecAxis* getAxis(asynUser *pasynUser); + NanotecAxis* getAxis(int axisNo); + +friend class NanotecAxis; + private: + asynUser *pasynUserController_; + + asynStatus transactController(char command[COMLEN], char reply[COMLEN]); + + + +}; diff --git a/sinqEPICSApp/src/sinq.dbd b/sinqEPICSApp/src/sinq.dbd index 38418e8..b0ce13a 100644 --- a/sinqEPICSApp/src/sinq.dbd +++ b/sinqEPICSApp/src/sinq.dbd @@ -2,6 +2,7 @@ # SINQ specific DB definitions #--------------------------------------------- registrar(EL734Register) +registrar(NanotecRegister) addpath "/usr/local/epics/support/asyn-4-18/dbd" addpath "/usr/local/epics/dbd" addpath "/usr/local/epics/support/motor-6-7/dbd" @@ -13,4 +14,5 @@ include "motorSupport.dbd" include "anc350AsynMotor.dbd" include "scalerRecord.dbd" -device(scaler,INST_IO,devScalerEL737,"asynScalerEL737") \ No newline at end of file +device(scaler,INST_IO,devScalerEL737,"asynScalerEL737") + diff --git a/sinqEPICSApp/src/stptok.cpp b/sinqEPICSApp/src/stptok.cpp new file mode 100644 index 0000000..d1232d5 --- /dev/null +++ b/sinqEPICSApp/src/stptok.cpp @@ -0,0 +1,50 @@ +/* +** stptok() -- public domain by Ray Gardner, modified by Bob Stout +** +** You pass this function a string to parse, a buffer to receive the +** "token" that gets scanned, the length of the buffer, and a string of +** "break" characters that stop the scan. It will copy the string into +** the buffer up to any of the break characters, or until the buffer is +** full, and will always leave the buffer null-terminated. It will +** return a pointer to the first non-breaking character after the one +** that stopped the scan. +*/ + +#include +#include + +char *stptok(char *s, char *tok, size_t toklen, char *brk) +{ + char *lim, *b; + + if (!*s) + return NULL; + + lim = tok + toklen - 1; + while (*s && tok < lim) { + for (b = brk; *b; b++) { + if (*s == *b) { + *tok = 0; + return (char *) (s + 1); + } + } + *tok++ = *s++; + } + *tok = 0; + return (char *) s; +} + +/*---------------------------------------------------------------------------*/ +char *SkipSpace(char *pText) +{ + char *pRes; + + pRes = pText; + while (*pRes) { + if ((*pRes != ' ') && (*pRes != '\t') && (*pRes != '\r')) { + return pRes; + } + pRes++; + } + return NULL; +} diff --git a/sinqEPICSApp/src/stptok.h b/sinqEPICSApp/src/stptok.h new file mode 100644 index 0000000..c27f131 --- /dev/null +++ b/sinqEPICSApp/src/stptok.h @@ -0,0 +1,16 @@ +/* +** stptok() -- public domain by Ray Gardner, modified by Bob Stout +** +** You pass this function a string to parse, a buffer to receive the +** "token" that gets scanned, the length of the buffer, and a string of +** "break" characters that stop the scan. It will copy the string into +** the buffer up to any of the break characters, or until the buffer is +** full, and will always leave the buffer null-terminated. It will +** return a pointer to the first non-breaking character after the one +** that stopped the scan. +*/ +#ifndef STPSTPTOK +#define STPSTPTOK +char *stptok(char *s, char *tok, size_t toklen, char *brk); + +#endif