Added a driver for the phytron MCC-2 motor controller. It now works against the

simulation.
This commit is contained in:
2016-09-01 16:13:39 +02:00
parent 4b377d1b2e
commit 002210b7a6
6 changed files with 576 additions and 0 deletions

View File

@ -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"}
}

View File

@ -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"

View File

@ -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.

View File

@ -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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <ctype.h>
#include <time.h>
#include <iocsh.h>
#include <epicsThread.h>
#include <errlog.h>
#include <asynOctetSyncIO.h>
#include "PhytronDriver.h"
#include <epicsExport.h>
#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<PhytronAxis*>(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<PhytronAxis*>(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);
}

View File

@ -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]);
};

View File

@ -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"