forked from epics_driver_modules/motorBase
1088 lines
38 KiB
C++
1088 lines
38 KiB
C++
/********************************************************************************/
|
|
/* H H Y Y TTTTT EEEEE CCC HYTEC ELECTRONICS LTD */
|
|
/* H H Y Y T E C 5 Cradock Road, */
|
|
/* HHHHH Y T EEE C Reading, Berks. Tel: 0118 9757770 */
|
|
/* H H Y T E C RG2 0JT Fax: 0118 9757566 */
|
|
/* H H Y T EEEEE CCC Web: www.hytec-electronics.co.uk */
|
|
/********************************************************************************/
|
|
/********************************************************************************/
|
|
/* _____________________________________________________________________ */
|
|
/* | H Y T E C 8 6 0 1 S T E P M O T E R A s y n D r i v e r | */
|
|
/* --------------------------------------------------------------------- */
|
|
/* */
|
|
/* Source file name :- HytecMotorDriver.c */
|
|
/* */
|
|
/* Initial creation date :- 29-Mar-2011 */
|
|
/* */
|
|
/* Original Developers :- Jim Chen. Hytec Electronics Ltd */
|
|
/* */
|
|
/********************************************************************************/
|
|
/* */
|
|
/* Description :- This is the "model 3" asyn motor driver for Hytec 8601 */
|
|
/* Stepper Motor IP module. The code is based on original */
|
|
/* Hytec drvHy8601asyn.c driver and also Mark Rivers' */
|
|
/* ACRMotorDriver. */
|
|
/* */
|
|
/* */
|
|
/* (C)2011 Hytec Electronics Ltd. */
|
|
/* */
|
|
/********************************************************************************/
|
|
/* */
|
|
/* Revision history: (comment and initial revisions) */
|
|
/* */
|
|
/* vers. revised modified by date */
|
|
/* ----- ----------- ---------------- --------------- */
|
|
/* 2.0 Continued version Jim Chen 29/03/2011 */
|
|
/* The main contents of this driver are the same as the original */
|
|
/* Hytec drvHy8601asyn.c driver but in "model 3" form as defined */
|
|
/* by Mark Rivers. Hence it starts from version 2.0. */
|
|
/* 2.1 New interfaces Jim Chen 04/04/2011 */
|
|
/* This version follows the asyn motor model 3 latest interface */
|
|
/* changes that include: */
|
|
/* a).New asynMotorAxis base class */
|
|
/* b).Moves the axis specific functions from the motor controller */
|
|
/* class to individual axis class */
|
|
/* c).Changes asynMotorDriver.cpp name to asynMotorController.cpp. */
|
|
/* 2.2 Bugs fix Jim Chen 14/04/2011 */
|
|
/* Fixed setPosition bug */
|
|
/* Added firmware version parameter */
|
|
/* 2.3 Bugs fix Jim Chen 21/04/2011 */
|
|
/* Fixed detecting encoder logic wrong bug */
|
|
/* 2.4 Bugs fix Jim Chen 24/06/2011 */
|
|
/* Fixed wrong ioc shell command function name */
|
|
/* 2.5 Changes for 64bit Linux Jim Chen 07/07/2011 */
|
|
/* Changed the cast modifier for "this" pointer in ipmIntConnect */
|
|
/* function by (long) rather than (int) to suit 64bit Linux */
|
|
/* build. */
|
|
/* */
|
|
/********************************************************************************/
|
|
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <epicsFindSymbol.h>
|
|
#include <epicsTime.h>
|
|
#include <epicsThread.h>
|
|
#include <epicsMutex.h>
|
|
#include <ellLib.h>
|
|
#include <epicsMessageQueue.h>
|
|
|
|
#include <drvSup.h>
|
|
#include <epicsExport.h>
|
|
#include <devLib.h>
|
|
#include <drvIpac.h>
|
|
#include <iocsh.h>
|
|
|
|
#include "HytecMotorDriver.h"
|
|
|
|
static void ISR_8601(int pDrv);
|
|
static void intQueuedTask( void *pDrv );
|
|
|
|
#define MAX_MESSAGES 100 /* maximum number of messages */
|
|
#define ENCODER_HARD_RATIO 0.25 /* encoder hardware ratio, it is 1/4 since the quadrature encoder */
|
|
|
|
|
|
// Constructor
|
|
HytecMotorController::HytecMotorController(const char *portName, int numAxes,
|
|
double movingPollPeriod, double idlePollPeriod, int cardnum,
|
|
int ip_carrier, int ipslot, int vector, int useencoder,
|
|
double encoderRatio0, double encoderRatio1, double encoderRatio2,
|
|
double encoderRatio3)
|
|
: asynMotorController(portName, numAxes, NUM_HYTEC_PARAMS,
|
|
asynInt32Mask | asynFloat64Mask | asynUInt32DigitalMask,
|
|
asynInt32Mask | asynFloat64Mask | asynUInt32DigitalMask,
|
|
ASYN_CANBLOCK | ASYN_MULTIDEVICE,
|
|
1, // autoconnect
|
|
0, 0) // Default priority and stack size
|
|
{
|
|
int axis;
|
|
double encoderRatio[4];
|
|
asynStatus status;
|
|
HytecMotorAxis *pAxis;
|
|
static const char *functionName = "HytecMotorController";
|
|
|
|
encoderRatio[0] = encoderRatio0;
|
|
encoderRatio[1] = encoderRatio1;
|
|
encoderRatio[2] = encoderRatio2;
|
|
encoderRatio[3] = encoderRatio3;
|
|
|
|
// Check the validity of the arguments
|
|
// Is Interrupt Vector Valid...
|
|
if (vector <= 0 || vector > 255)
|
|
{
|
|
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
|
"%s:%s: illegal interrupt vector number. must be in range [1..255]\n",
|
|
driverName, functionName);
|
|
// printf("ERROR! illegal interrupt vector number %d !\n", vector);
|
|
}
|
|
|
|
if (numAxes < 1 ) numAxes = 1;
|
|
this->numAxes = numAxes;
|
|
this->card = cardnum;
|
|
this->ip_carrier = ip_carrier;
|
|
this->ipslot = ipslot;
|
|
this->vector = vector & 0xFF;
|
|
this->ip_carrier = ip_carrier;
|
|
this->useencoder = useencoder;
|
|
|
|
// Create controller-specific parameters
|
|
createParam(HytecPowerControlString, asynParamInt32, &this->HytecPowerControl_);
|
|
createParam(HytecBrakeControlString, asynParamInt32, &this->HytecBrakeControl_);
|
|
createParam(HytecFirmwareString, asynParamInt32, &this->HytecFWVersion_);
|
|
createParam(HytecMoveAllString, asynParamInt32, &this->HytecMoveAll_);
|
|
|
|
if ((status = SetupCard()) != asynSuccess)
|
|
{
|
|
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
|
"%s:%s: setup motor IP card failed.\n",
|
|
driverName, functionName);
|
|
}
|
|
|
|
for (axis=0; axis<numAxes && status == asynSuccess; axis++ )
|
|
{
|
|
pAxis = new HytecMotorAxis(this, axis, encoderRatio[axis], vector & 0xFF);
|
|
}
|
|
|
|
// create a queue task for dealing interrupts
|
|
this->intMsgQId = epicsMessageQueueCreate(MAX_MESSAGES, (this->numAxes + 2)*sizeof(int));
|
|
if (epicsThreadCreate("drvHy8601intQueuedTask",
|
|
epicsThreadPriorityLow,
|
|
epicsThreadGetStackSize(epicsThreadStackMedium),
|
|
(EPICSTHREADFUNC)intQueuedTask,
|
|
(void *) this) == NULL)
|
|
{
|
|
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
|
"%s:%s: drvHy8601intQueuedTask epicsThreadCreate failure.\n",
|
|
driverName, functionName);
|
|
}
|
|
|
|
startPoller(movingPollPeriod, idlePollPeriod, 2);
|
|
}
|
|
|
|
// set up IP card
|
|
asynStatus HytecMotorController::SetupCard()
|
|
{
|
|
char *regbase;
|
|
int st;
|
|
static const char *functionName = "SetupCard";
|
|
|
|
/* Register the card in A16 address space */
|
|
regbase=(char*)ipmBaseAddr( this->ip_carrier,
|
|
this->ipslot,
|
|
ipac_addrIO);
|
|
|
|
/* If failed to register card... */
|
|
if (regbase == NULL)
|
|
{
|
|
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
|
"%s:%s: Cannot register Hy8601 A16 device.\n",
|
|
driverName, functionName);
|
|
return asynError;
|
|
}
|
|
|
|
/* Memory Check OK, so check PROM */
|
|
/* JSC. checkprom returns 1 = ok, 0 = failed. */
|
|
if (checkprom((char*)regbase+PROM_OFFS, PROM_MODEL)!=1)
|
|
{
|
|
return asynError;
|
|
}
|
|
|
|
/* Setup Interrupts. */
|
|
st=ipmIntConnect(this->ip_carrier,
|
|
this->ipslot,
|
|
this->vector,
|
|
&ISR_8601,
|
|
(long)this);
|
|
|
|
/* If Interrupts NOT Setup OK */
|
|
if (st!=OK)
|
|
{
|
|
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
|
"%s:%s: intConnect ERROR.\n",
|
|
driverName, functionName);
|
|
return asynError;
|
|
}
|
|
|
|
/* Setup Carrier Card */
|
|
ipmIrqCmd(this->ip_carrier,
|
|
this->ipslot,
|
|
0,
|
|
ipac_irqEnable);
|
|
|
|
this->regbase = regbase;
|
|
return asynSuccess;
|
|
}
|
|
|
|
/**********************************************************
|
|
|
|
NAME: checkprom()
|
|
|
|
Description:
|
|
|
|
Function -
|
|
1. Check for "VITA4 " string.
|
|
2. Check for Hytec ID.
|
|
3. Check for IP Model (0x8601).
|
|
4. Display Debug Information.
|
|
5. Return whether Check PROM Successful.
|
|
|
|
Parameters:
|
|
|
|
Inputs:
|
|
|
|
pr - The PROM base address (1st address of PROM).
|
|
expmodel - Expected IP Model as a Hex Value (i.e. 0x8601)
|
|
verbose - TRUE = Display Debug Information as you go.
|
|
|
|
Outputs: None.
|
|
Return: (ishytec && ismodel)
|
|
|
|
TRUE - Everything OK.
|
|
FALSE - Either the Hytec ID or IP Model number is wrong.
|
|
|
|
|
|
Notes:
|
|
|
|
Programmer(s): Darrell Nineham
|
|
Walter Scott
|
|
Steve Hunt
|
|
|
|
**********************************************************
|
|
|
|
Revision History
|
|
----------------
|
|
|
|
Date By Version Descripition
|
|
---- -- ------- ------------
|
|
29-MAR-2011 JSC 1.1.1.0 Intial Version.
|
|
|
|
**********************************************************/
|
|
int HytecMotorController::checkprom(char *pr,int expmodel)
|
|
{
|
|
#define PR_ID 0x06
|
|
#define PR_MODN 0x0a
|
|
#define PR_REVN 0x0c
|
|
#define PR_DRID1 0x10
|
|
#define PR_DRID2 0x12
|
|
#define PR_FLAGS 0x14
|
|
#define PR_BYTES 0x16
|
|
#define PR_SERN 0x1a
|
|
#define HYTECID 0x00800300
|
|
|
|
char* hytecstr=" (HyTec Electronics Ltd., Reading, UK)";
|
|
char lstr[7];
|
|
int manid;
|
|
epicsUInt16 modelnum;
|
|
int ishytec,ismodel,strok, ver;
|
|
|
|
/* Begin */
|
|
|
|
/* Debug - Display the passed Address */
|
|
printf("PROM CONTENTS AT: %p\n",pr);
|
|
/* Read the first 6 Characters from the passed Address */
|
|
strncpy(lstr, (char*)pr, 6);
|
|
/* Convert it to a String by Null terminating it */
|
|
lstr[6]=0;
|
|
|
|
/* Compare to Expected String (i.e. "VITA4 ") */
|
|
strok = (strcmp(lstr, IP_DETECT_STR) == 0);
|
|
|
|
printf("PROM header: '%6s'\n",lstr);
|
|
/* Set manid to PROM Address of the Hytec ID */
|
|
manid = *((int*)(pr+PR_ID));
|
|
/* Set ishytec to TRUE if Hytec ID is as expected */
|
|
ishytec = (manid ==HYTECID);
|
|
/* Set modelnum to PROM Address of the IP Model */
|
|
modelnum = *((epicsUInt16*)(pr+PR_MODN));
|
|
/* Set ismodel to TRUE if IP Model is as expected */
|
|
ismodel = (modelnum==expmodel);
|
|
|
|
/* Display Assorted Information from Reading the PROM */
|
|
printf("PROM manufacturer ID: 0x%08X",manid);
|
|
if (ishytec) printf(hytecstr);
|
|
|
|
printf("\nPROM model #: 0x%04hx, rev. 0x%04hx, serial # %hu\n",
|
|
modelnum,
|
|
*((short int*)(pr+PR_REVN)),
|
|
*((short int*)(pr+PR_SERN)));
|
|
|
|
printf("PROM driver ids: 0x%04hx, 0x%04hx\n",
|
|
*((short int*)(pr+PR_DRID1)),
|
|
*((short int*)(pr+PR_DRID2)));
|
|
|
|
ver = *((short int*)(pr+PR_REVN));
|
|
ver = ver/4096*1000 + (ver % 4096)/256*100 + ((ver % 4096) % 256)/16*10 + ((ver % 4096) % 256) % 16;
|
|
setIntegerParam( HytecFWVersion_, ver);
|
|
|
|
printf("PROM flags: 0x%04hx\nPROM number of bytes used: 0x%04hx (%hd)\n\n",
|
|
*((short int*)(pr+PR_FLAGS)),
|
|
*((short int*)(pr+PR_BYTES)),
|
|
*((short int*)(pr+PR_BYTES)));
|
|
|
|
|
|
/* Debug - Print error message if string is NOT as expected */
|
|
if (!strok)
|
|
{
|
|
printf("PROM INVALID PROM HEADER; EXPECTED '%s'\n",
|
|
IP_DETECT_STR);
|
|
}
|
|
|
|
/* Debug - Print error message if Hytec ID is NOT as expected */
|
|
if (!ishytec)
|
|
{
|
|
printf("PROM UNSUPPORTED MANUFACTURER ID;\nPROM EXPECTED 0x%08X, %s\n",
|
|
HYTECID,hytecstr);
|
|
}
|
|
|
|
/* Debug - Print error message if IP Model is NOT as expected */
|
|
if(!ismodel)
|
|
{
|
|
printf("PROM UNSUPPORTED BOARD MODEL NUMBER: EXPECTED 0x%04hx\n",
|
|
expmodel);
|
|
}
|
|
|
|
return (/* SCO strok && */ ismodel);
|
|
}
|
|
|
|
// report
|
|
void HytecMotorController::report(FILE *fp, int level)
|
|
{
|
|
int axis;
|
|
HytecMotorAxis *pAxis;
|
|
|
|
fprintf(fp, "Hytec motor driver %s, numAxes=%d\n", this->portName, this->numAxes);
|
|
|
|
if (level > 0) {
|
|
for (axis=0; axis<this->numAxes; axis++) {
|
|
pAxis = getAxis(axis);
|
|
fprintf(fp, " axis %d\n"
|
|
" encoder ratio = %f\n",
|
|
pAxis->axisNo_, pAxis->encoderRatio);
|
|
|
|
}
|
|
}
|
|
|
|
// Call the base class method
|
|
asynMotorController::report(fp, level);
|
|
}
|
|
|
|
// get axis
|
|
HytecMotorAxis * HytecMotorController::getAxis(asynUser *pasynUser)
|
|
{
|
|
return dynamic_cast<HytecMotorAxis*>(asynMotorController::getAxis(pasynUser));
|
|
}
|
|
|
|
HytecMotorAxis * HytecMotorController::getAxis(int axisNo)
|
|
{
|
|
return dynamic_cast<HytecMotorAxis*>(asynMotorController::getAxis(axisNo));
|
|
}
|
|
|
|
// Int32 read interface
|
|
asynStatus HytecMotorController::readInt32(asynUser *pasynUser, epicsInt32 *value)
|
|
{
|
|
int function = pasynUser->reason;
|
|
asynStatus status = asynSuccess;
|
|
HytecMotorAxis *pAxis = getAxis(pasynUser);
|
|
double dvalue;
|
|
bool moving;
|
|
// static const char *functionName = "readInt32";
|
|
|
|
if ((function == motorPosition_) || (function == motorEncoderPosition_))
|
|
{
|
|
//update all values and status
|
|
pAxis->poll(&moving);
|
|
getDoubleParam(pAxis->axisNo_, function, &dvalue);
|
|
*value = (int)dvalue;
|
|
}else if(function == HytecFWVersion_)
|
|
{
|
|
getIntegerParam( HytecFWVersion_, value);
|
|
}else
|
|
// Call base class call its method (if we have our parameters check this here)
|
|
status = asynPortDriver::readInt32(pasynUser, value);
|
|
return status;
|
|
}
|
|
|
|
// Int32 write interface
|
|
asynStatus HytecMotorController::writeInt32(asynUser *pasynUser, epicsInt32 value)
|
|
{
|
|
int function = pasynUser->reason;
|
|
asynStatus status = asynSuccess;
|
|
HytecMotorAxis *pAxis = getAxis(pasynUser);
|
|
// static const char *functionName = "writeInt32";
|
|
|
|
|
|
/* Set the parameter and readback in the parameter library. This may be overwritten when we read back the
|
|
* status at the end, but that's OK */
|
|
status = setIntegerParam(pAxis->axisNo_, function, value);
|
|
|
|
if(function == HytecPowerControl_)
|
|
{
|
|
if(value)
|
|
CSR_SET(pAxis->chanbase, CSR_AUX1);
|
|
else
|
|
CSR_CLR(pAxis->chanbase, CSR_AUX1);
|
|
} else if(function == HytecBrakeControl_)
|
|
{
|
|
if(value)
|
|
CSR_CLR(pAxis->chanbase, CSR_AUX2);
|
|
else
|
|
CSR_SET(pAxis->chanbase, CSR_AUX2);
|
|
} else if(function == HytecMoveAll_)
|
|
{
|
|
|
|
} else
|
|
/* Call base class call its method (if we have our parameters check this here) */
|
|
status = asynMotorController::writeInt32(pasynUser, value);
|
|
|
|
/* Do callbacks so higher layers see any changes */
|
|
callParamCallbacks(pAxis->axisNo_);
|
|
return status;
|
|
}
|
|
|
|
// Float64 write interface. This is currently useless.
|
|
asynStatus HytecMotorController::writeFloat64(asynUser *pasynUser, epicsFloat64 value)
|
|
{
|
|
int function = pasynUser->reason;
|
|
asynStatus status = asynSuccess;
|
|
HytecMotorAxis *pAxis = getAxis(pasynUser);
|
|
// static const char *functionName = "writeFloat64";
|
|
|
|
/* Set the parameter and readback in the parameter library. This may be overwritten when we read back the
|
|
* status at the end, but that's OK */
|
|
status = setDoubleParam(pAxis->axisNo_, function, value);
|
|
|
|
/*
|
|
* The following part are not implemented in this driver but is retained here
|
|
* in case of future requirements.
|
|
*/
|
|
// if(function == motorResolution_)
|
|
// {
|
|
/* Calculate the resolution. it is initialised as 1.0.
|
|
previous_resolution = pAxis->resolution;
|
|
pAxis->resolution = value;
|
|
pAxis->softLowLimit = pAxis->resolution * pAxis->softLowLimit / previous_resolution;
|
|
pAxis->softHighLimit = pAxis->resolution * pAxis->softHighLimit / previous_resolution; */
|
|
// }
|
|
// else if(function == motorEncRatio_)
|
|
// {
|
|
/* Calculate the encoder ratio and store. Note, encoder ratio is initialised as 1/4 since
|
|
* the hardware design counts 4 of each encoder pulse. encoderRatio is not used for 8601
|
|
* after consulting to Ron Sluiter on 21/01/2001
|
|
pAxis->encoderRatio = ENCODER_HARD_RATIO * value; */
|
|
// }
|
|
// else if(function == motorLowLimit_)
|
|
// {
|
|
// Calculate the soft low limit to engineering unit. softLowLimit is not used for 8601
|
|
//pAxis->softLowLimit = pAxis->resolution * value;
|
|
// }
|
|
// else if(function == motorHighLimit_)
|
|
// {
|
|
// Calculate the soft high limit to engineering unit. softHighLimit is not used for 8601
|
|
//pAxis->softHighLimit = pAxis->resolution * value;
|
|
// }
|
|
// else
|
|
/* Call base class call its method (if we have our parameters check this here) */
|
|
status = asynMotorController::writeFloat64(pasynUser, value);
|
|
|
|
/* Do callbacks so higher layers see any changes */
|
|
pAxis->callParamCallbacks();
|
|
return status;
|
|
}
|
|
|
|
// ISR8601 Routines to handle interrupts
|
|
static void ISR_8601(int pDrv)
|
|
{
|
|
HytecMotorController *pController = (HytecMotorController*) pDrv;
|
|
HytecMotorAxis * pAxis;
|
|
volatile char * chanbase;
|
|
epicsMessageQueueId qID;
|
|
int data[4];
|
|
int axis;
|
|
int numOfAxes;
|
|
|
|
numOfAxes = pController->getNumAxes();
|
|
for (axis = 0; axis < numOfAxes; axis++)
|
|
{
|
|
pAxis = pController->getAxis(axis);
|
|
chanbase = pAxis->getChanbase();
|
|
data[axis] = GET_REG(chanbase, REG_CSR);
|
|
SET_REG(chanbase, REG_INTMASK, 0); /* disable interrupt */
|
|
}
|
|
|
|
/* wake up INT queue */
|
|
qID = pController->getINTMsgQID();
|
|
if (epicsMessageQueueTrySend(qID, data, sizeof(data)) == 0)
|
|
pController->increaseMsgSent();
|
|
else
|
|
pController->increaseMsgFail();
|
|
}
|
|
|
|
// Interrupt queue task
|
|
static void intQueuedTask( void *pDrv )
|
|
{
|
|
HytecMotorController *pController = (HytecMotorController*) pDrv;
|
|
HytecMotorAxis * pAxis;
|
|
epicsMessageQueueId qID;
|
|
int data[4];
|
|
int axis;
|
|
int numOfAxes, ipslot, ipcarrier;
|
|
|
|
qID = pController->getINTMsgQID();
|
|
numOfAxes = pController->getNumAxes();
|
|
ipslot = pController->getIPSlot();
|
|
ipcarrier = pController->getIPCarrier();
|
|
|
|
while(1)
|
|
{
|
|
/* Wait for event from interrupt routine */
|
|
epicsMessageQueueReceive(qID, data, sizeof(data));
|
|
|
|
for (axis = 0; axis < numOfAxes; axis++)
|
|
{
|
|
pAxis = pController->getAxis(axis);
|
|
pController->drvHy8601GetAxisStatus( pAxis, data[axis] );
|
|
}
|
|
|
|
/* re-enable interrupt. for Linux IOCs */
|
|
ipmIrqCmd(ipcarrier, ipslot, 0, ipac_irqEnable);
|
|
}
|
|
}
|
|
|
|
// Interrupt queue task updates
|
|
void HytecMotorController::drvHy8601GetAxisStatus( HytecMotorAxis *pAxis, int csr_data )
|
|
{
|
|
this->lock();
|
|
|
|
setIntegerParam( pAxis->axisNo_, motorStatusDone_, ((csr_data & CSR_DONE) != 0 ) );
|
|
setIntegerParam( pAxis->axisNo_, motorStatusHighLimit_, ((csr_data & CSR_MAXLMT) != 0 ) );
|
|
setIntegerParam( pAxis->axisNo_, motorStatusHome_, ((csr_data & CSR_HOMELMT) != 0 ) );
|
|
|
|
if (csr_data & CSR_HOMELMT) CSR_CLR(pAxis->chanbase, CSR_HOMESTOP); /* after home limit is reported, clear Stop at home */
|
|
|
|
setIntegerParam( pAxis->axisNo_, motorStatusProblem_, ((csr_data & CSR_DRVSTAT) != 0) );
|
|
setIntegerParam( pAxis->axisNo_, motorStatusLowLimit_, ((csr_data & CSR_MINLMT) != 0) );
|
|
callParamCallbacks(pAxis->axisNo_);
|
|
|
|
this->unlock();
|
|
}
|
|
|
|
int HytecMotorController::getNumAxes()
|
|
{
|
|
return numAxes;
|
|
}
|
|
|
|
int HytecMotorController::getIPCarrier()
|
|
{
|
|
return ip_carrier;
|
|
}
|
|
|
|
int HytecMotorController::getIPSlot()
|
|
{
|
|
return ipslot;
|
|
}
|
|
|
|
epicsMessageQueueId HytecMotorController::getINTMsgQID()
|
|
{
|
|
return intMsgQId;
|
|
}
|
|
|
|
void HytecMotorController::increaseMsgSent()
|
|
{
|
|
messagesSent++;
|
|
}
|
|
|
|
void HytecMotorController::increaseMsgFail()
|
|
{
|
|
messagesFailed++;
|
|
}
|
|
|
|
|
|
/**************************************************************************************
|
|
*
|
|
* Motor Axis Methods
|
|
*
|
|
*************************************************************************************/
|
|
// Motor Axis methods
|
|
HytecMotorAxis::HytecMotorAxis(HytecMotorController *pC, int axisNo, double ratio, int vector)
|
|
: asynMotorAxis(pC, axisNo),
|
|
pC_(pC), vector(vector), encoderRatio(ratio)
|
|
{
|
|
InitialiseAxis();
|
|
}
|
|
|
|
int HytecMotorAxis::getVector()
|
|
{
|
|
return vector;
|
|
}
|
|
|
|
volatile char *HytecMotorAxis::getChanbase()
|
|
{
|
|
return chanbase;
|
|
}
|
|
|
|
|
|
// initialise axis
|
|
asynStatus HytecMotorAxis::InitialiseAxis()
|
|
{
|
|
volatile char *chanbase;
|
|
|
|
// Required for the motor axis infrastructure
|
|
this->absPosition = 0; // initialise the software counter to 0 to be in line with the reset which clears the absolute position
|
|
this->desiredMove = 0; // clear desired position
|
|
this->resolution = 1.0; // initial resolution is 1
|
|
this->softLowLimit = 0.0; // initialise soft low limit to 0
|
|
this->softHighLimit = 0.0; // initialise soft high limit to 0
|
|
this->times = 0;
|
|
|
|
// Initialisation specific to this model of controller
|
|
chanbase = this->chanbase = pC_->regbase + REG_BANK_OFFS + this->axisNo_ * REG_BANK_SZ;
|
|
|
|
// Reset Card
|
|
CSR_SET(chanbase, CSR_RESET);
|
|
CSR_CLR(chanbase, CSR_RESET);
|
|
|
|
/* Note: the useencoder in configure overrides the encoder detection. So if the user decides not
|
|
* to use encoder by setting the useencoder param to 0 in the configure call, even there is
|
|
* an encoder present, the software will not use the encoder. If the useencoder is 1, then
|
|
* driver will use encoder but if the encoder is not present, no value will be written to the position register */
|
|
|
|
this->useencoder = ((pC_->useencoder & (1<<this->axisNo_)) == 0) ? 0 : 1;
|
|
|
|
/* check use encoder set by the configure */
|
|
if(this->useencoder)
|
|
CSR_SET(chanbase, CSR_ENCODUSE);
|
|
|
|
/* enable interrupt, set vector */
|
|
/* Note, all 4 axis of 8601 have been set the same vector.
|
|
* It is the ISR's responsibility to check which axis generates the interrupt at runtime.
|
|
* If the mask of individual axis needs to be set differently, extra interface should
|
|
* be created, but they should still share the same ISR routine. */
|
|
CSR_SET(chanbase, CSR_INTEN);
|
|
SET_REG(chanbase,REG_INTVECTOR,this->vector);
|
|
|
|
CSR_SET(chanbase, CSR_CRASHSTOP);
|
|
epicsThreadSleep( 0.1 );
|
|
CSR_CLR(chanbase, CSR_CRASHSTOP);
|
|
|
|
return asynSuccess;
|
|
}
|
|
|
|
// Move axis
|
|
asynStatus HytecMotorAxis::move(double position, int relative, double min_velocity, double max_velocity, double acceleration)
|
|
{
|
|
// static const char *functionName = "moveAxis";
|
|
asynStatus status = asynSuccess;
|
|
double result;
|
|
int currpos;
|
|
|
|
if (min_velocity != 0)
|
|
{
|
|
/* Set start speed register, min_velocity is steps/s */
|
|
SET_REG(this->chanbase, REG_STARTSTOPSPD, (int)floor(min_velocity));
|
|
}
|
|
if (max_velocity != 0)
|
|
{
|
|
/* Set high speed register, max_velocity is steps/s */
|
|
SET_REG(this->chanbase, REG_HIGHSPD, (int)floor(max_velocity));
|
|
}
|
|
if (acceleration >= 64)
|
|
{
|
|
/* Set ramp register, acceleration is steps/s/s */
|
|
SET_REG(this->chanbase, REG_RAMPRATE, (int)floor(acceleration));
|
|
}
|
|
|
|
if (relative)
|
|
{
|
|
/* Write to step counter register abs(position)*/
|
|
SET_REG(this->chanbase, REG_STEPCNTLO, (abs((int)floor(position))) & 0xFFFF);
|
|
SET_REG(this->chanbase, REG_STEPCNTHI, (abs((int)floor(position))) >> 16);
|
|
/* Write dir flag based on relative position */
|
|
if (position >= 0)
|
|
{
|
|
CSR_SET(this->chanbase, CSR_DIRECTION);
|
|
}else
|
|
{
|
|
CSR_CLR(this->chanbase, CSR_DIRECTION);
|
|
}
|
|
this->desiredMove = position; /* store desired position */
|
|
}else
|
|
{
|
|
if(!this->useencoder) /* if encoder is not used, read the absolute position from register */
|
|
{
|
|
currpos = GET_REG(this->chanbase, REG_CURRPOSHI) << 16;
|
|
currpos += GET_REG(this->chanbase, REG_CURRPOSLO);
|
|
}
|
|
else /* otherwise, current position is from the software variable */
|
|
currpos = (int)this->absPosition;
|
|
|
|
/* calculate steps to move */
|
|
result = position - currpos;
|
|
this->desiredMove = result; /* store desired position */
|
|
|
|
/* Write to step counter abs(result) */
|
|
SET_REG(this->chanbase, REG_STEPCNTLO, (abs((int)floor(result))) & 0xFFFF);
|
|
SET_REG(this->chanbase, REG_STEPCNTHI, (abs((int)floor(result))) >> 16);
|
|
/* Write dir flag based on result */
|
|
if (result >= 0)
|
|
{
|
|
CSR_SET(this->chanbase, CSR_DIRECTION);
|
|
}else
|
|
{
|
|
CSR_CLR(this->chanbase, CSR_DIRECTION);
|
|
}
|
|
}
|
|
|
|
/* int csr = GET_REG(this->chanbase, REG_CSR);
|
|
printf("rel=%d askp=%d currp=%d, absp=%d res=%d, csr=%x acce=%d minsp=%d maxsp=%d\n", relative, (int)position, (int)currpos, (int)this->absPosition, (int)result, csr, (int)floor(acceleration), (int)floor(min_velocity), (int)floor(max_velocity));*/ /* for debug */
|
|
|
|
/* Write GO bit */
|
|
this->times = 0; /* clear done bit flag at the beginning of every move */
|
|
CSR_SET(this->chanbase, CSR_GO);
|
|
SET_REG(this->chanbase,REG_INTMASK,DONE_INT); /* to enable interrupt */
|
|
|
|
pC_->lock();
|
|
setIntegerParam(pC_->motorStatusDone_, 0);
|
|
callParamCallbacks();
|
|
pC_->unlock();
|
|
|
|
return status;
|
|
}
|
|
|
|
// Move home
|
|
asynStatus HytecMotorAxis::home(double min_velocity, double max_velocity, double acceleration, int forwards)
|
|
{
|
|
asynStatus status = asynSuccess;
|
|
// static const char *functionName = "homeAxis";
|
|
|
|
if (min_velocity != 0)
|
|
{
|
|
/* Set start speed register, min_velocity is steps/s */
|
|
SET_REG(this->chanbase, REG_STARTSTOPSPD, (int)floor(min_velocity));
|
|
}
|
|
if (max_velocity != 0)
|
|
{
|
|
/* Set high speed register, max_velocity is steps/s */
|
|
SET_REG(this->chanbase, REG_HIGHSPD, (int)floor(max_velocity));
|
|
}
|
|
if (acceleration != 0)
|
|
{
|
|
/* Set ramp register, acceleration is steps/s/s */
|
|
SET_REG(this->chanbase, REG_RAMPRATE, (int)floor(acceleration));
|
|
}
|
|
/* Write direction bit */
|
|
if (forwards)
|
|
{
|
|
CSR_SET(this->chanbase, CSR_DIRECTION);
|
|
}else
|
|
{
|
|
CSR_CLR(this->chanbase, CSR_DIRECTION);
|
|
}
|
|
/* Write SH bit and start moving towards home */
|
|
CSR_SET(this->chanbase, CSR_HOMESTOP | CSR_JOG);
|
|
SET_REG(this->chanbase,REG_INTMASK,DONE_INT); /* to enable interrupt */
|
|
|
|
pC_->lock();
|
|
setIntegerParam(pC_->motorStatusDone_, 0);
|
|
callParamCallbacks();
|
|
pC_->unlock();
|
|
|
|
return status;
|
|
}
|
|
|
|
// move velocity
|
|
asynStatus HytecMotorAxis::moveVelocity(double min_velocity, double max_velocity, double acceleration)
|
|
{
|
|
asynStatus status = asynSuccess;
|
|
// static const char *functionName = "moveVelocityAxis";
|
|
|
|
if (min_velocity != 0)
|
|
{
|
|
/* Set start speed register, min_velocity is steps/s */
|
|
SET_REG(this->chanbase, REG_STARTSTOPSPD, (int)floor(min_velocity));
|
|
}
|
|
if (max_velocity != 0)
|
|
{
|
|
/* Set high speed register, velocity is steps/s */
|
|
SET_REG(this->chanbase, REG_HIGHSPD, (int)floor(max_velocity));
|
|
}
|
|
if (acceleration != 0)
|
|
{
|
|
/* XXXX: set ramp register, acceleration is steps/s/s */
|
|
SET_REG(this->chanbase, REG_RAMPRATE, (int)floor(acceleration));
|
|
}
|
|
/* Write direction bit */
|
|
if (max_velocity >= 0)
|
|
{
|
|
CSR_SET(this->chanbase, CSR_DIRECTION);
|
|
} else
|
|
{
|
|
CSR_CLR(this->chanbase, CSR_DIRECTION);
|
|
}
|
|
/* Set Jog flag */
|
|
CSR_SET(this->chanbase, CSR_JOG);
|
|
SET_REG(this->chanbase,REG_INTMASK,DONE_INT); /* to enable interrupt */
|
|
|
|
pC_->lock();
|
|
setIntegerParam(pC_->motorStatusDone_, 0);
|
|
callParamCallbacks();
|
|
pC_->unlock();
|
|
|
|
return status;
|
|
}
|
|
|
|
// stop axis
|
|
asynStatus HytecMotorAxis::stop(double acceleration )
|
|
{
|
|
asynStatus status = asynSuccess;
|
|
// static const char *functionName = "stopAxis";
|
|
int value;
|
|
unsigned int acce;
|
|
|
|
acce = ((unsigned int)floor(acceleration));
|
|
if (acce != 0)
|
|
{
|
|
value = GET_REG(this->chanbase, REG_RAMPRATE);
|
|
if((acce > 64) || (acce <= 0xFFFF))
|
|
value = acce;
|
|
|
|
/* Set ramp register, acceleration is steps/s/s */
|
|
SET_REG(this->chanbase, REG_RAMPRATE, value);
|
|
/* Clear JOG and GO bits */
|
|
CSR_CLR(this->chanbase, CSR_JOG | CSR_GO);
|
|
}else
|
|
{
|
|
/* Write abort bit to 1 */
|
|
CSR_SET(this->chanbase, CSR_CRASHSTOP);
|
|
CSR_CLR(this->chanbase, CSR_CRASHSTOP);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
// timer task updates
|
|
asynStatus HytecMotorAxis::poll(bool *moving)
|
|
{
|
|
double position, error=0.0, stepMoved;
|
|
epicsUInt16 csr;
|
|
asynStatus status = asynSuccess;
|
|
|
|
pC_->lock();
|
|
|
|
/* Get position register reading */
|
|
position = GET_REG(this->chanbase, REG_CURRPOSHI) << 16;
|
|
position += GET_REG(this->chanbase, REG_CURRPOSLO);
|
|
|
|
/* For calculating absolute position if encoder is used */
|
|
csr = GET_REG(this->chanbase, REG_CSR);
|
|
error = GET_REG(this->chanbase, REG_STEPCNTHI) << 16;
|
|
error += GET_REG(this->chanbase, REG_STEPCNTLO);
|
|
|
|
if(this->desiredMove < 0) /* move reversely */
|
|
{
|
|
if(error >= 0)
|
|
{
|
|
stepMoved = (this->desiredMove + error) > 0 ? this->desiredMove + error : -(this->desiredMove + error);
|
|
this->desiredMove = -error;
|
|
}
|
|
else
|
|
{
|
|
stepMoved = (this->desiredMove) > 0 ? this->desiredMove : -(this->desiredMove);
|
|
this->desiredMove = 0;
|
|
}
|
|
}
|
|
else /* move forward */
|
|
{
|
|
if(error >= 0)
|
|
{
|
|
stepMoved = this->desiredMove - error;
|
|
this->desiredMove = error;
|
|
}
|
|
else
|
|
{
|
|
stepMoved = this->desiredMove;
|
|
this->desiredMove = 0;
|
|
}
|
|
}
|
|
|
|
if((csr & CSR_ENCODDET) && this->useencoder) /* use encoder */
|
|
{
|
|
setDoubleParam( pC_->motorEncoderPosition_, floor(position*this->encoderRatio+0.5) );
|
|
if(csr & CSR_DIRECTION) /* forward */
|
|
{
|
|
if(!(csr & CSR_DONE))
|
|
this->absPosition += stepMoved;
|
|
else
|
|
{
|
|
if(this->times == 0)
|
|
{
|
|
this->absPosition += stepMoved;
|
|
this->times = 1; /* set done bit hit the first time */
|
|
}
|
|
SET_REG(this->chanbase, REG_STEPCNTHI, 0);
|
|
SET_REG(this->chanbase, REG_STEPCNTLO, 0);
|
|
}
|
|
setDoubleParam( pC_->motorPosition_, floor(this->absPosition+0.5) );
|
|
}else /* reversed */
|
|
{
|
|
if(!(csr & CSR_DONE))
|
|
this->absPosition -= stepMoved;
|
|
else
|
|
{
|
|
if(this->times == 0)
|
|
{
|
|
this->absPosition -= stepMoved;
|
|
this->times = 1; /* set done bit hit the first time */
|
|
}
|
|
SET_REG(this->chanbase, REG_STEPCNTHI, 0);
|
|
SET_REG(this->chanbase, REG_STEPCNTLO, 0);
|
|
}
|
|
setDoubleParam( pC_->motorPosition_, floor(this->absPosition-0.5) );
|
|
}
|
|
}else /* do not use encoder */
|
|
{
|
|
setDoubleParam( pC_->motorEncoderPosition_, floor(position+0.5) );
|
|
setDoubleParam( pC_->motorPosition_, floor(position+0.5) );
|
|
}
|
|
|
|
/* //debug
|
|
int acc= GET_REG(this->chanbase, REG_RAMPRATE);
|
|
int lsp= GET_REG(this->chanbase, REG_STARTSTOPSPD);
|
|
int hsp=GET_REG(this->chanbase, REG_HIGHSPD);
|
|
if(this->axisNo_ == 0)
|
|
printf("%d: moved=%d, error=%d, desired=%d absp=%d csr=%x\n",this->axisNo_, (int)stepMoved, (int)error , (int)this->desiredMove, (int)this->absPosition, (int)csr);
|
|
*/
|
|
setIntegerParam( pC_->motorStatusDirection_, ((csr & CSR_DIRECTION) != 0 ) );
|
|
setIntegerParam( pC_->motorStatusHasEncoder_, ((csr & CSR_ENCODDET) != 0) );
|
|
|
|
*moving = (csr & CSR_DONE) != 0 ? true : false;
|
|
setIntegerParam( pC_->motorStatusDone_, ((csr & CSR_DONE) != 0 ) );
|
|
setIntegerParam( pC_->motorStatusHighLimit_, ((csr & CSR_MAXLMT) != 0 ) );
|
|
setIntegerParam( pC_->motorStatusHome_, ((csr & CSR_HOMELMT) != 0 ) );
|
|
setIntegerParam( pC_->motorStatusProblem_, ((csr & CSR_DRVSTAT) != 0) );
|
|
setIntegerParam( pC_->motorStatusLowLimit_, ((csr & CSR_MINLMT) != 0) );
|
|
|
|
callParamCallbacks();
|
|
pC_->unlock();
|
|
return status;
|
|
}
|
|
|
|
asynStatus HytecMotorAxis::setPosition(double position)
|
|
{
|
|
asynStatus status = asynSuccess;
|
|
|
|
if(this->useencoder) /* use encoder */
|
|
this->absPosition = position; /* update the software absolute position, note if encoder is not present, this is not used */
|
|
else
|
|
{
|
|
SET_REG(this->chanbase, REG_CURRPOSLO, ((int)floor(position)) & 0xFFFF);
|
|
SET_REG(this->chanbase, REG_CURRPOSHI, ((int)floor(position)) >> 16);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Configure the Hy8601 card
|
|
*
|
|
* This simply creates a HytecMotorController object and
|
|
* the constructor does everything for initialisation.
|
|
*
|
|
* @param portName asyn port name
|
|
* @param numAxes number of axis on the IP card
|
|
* @param movingPollPeriod The time in ms between polls when any axis is moving
|
|
* @param idlePollPeriod The time in ms between polls when no axis is moving
|
|
* @param cardnum Arbitrary card number to assign to this controller
|
|
* @param ip_carrier which previously configured IP carrier in the IOC
|
|
* @param ipslot which IP Slot on carrier card (0=A etc.)
|
|
* @param vector which Interrupt Vector (0 - Find One ?)
|
|
* 8601 interrupt mask is always set to 0x2000. Even though the logical AND of bits in this
|
|
* register and those in the CSR will cause the channel and then the IP module
|
|
* to generate an interrupt on IRQ0 as the graph below
|
|
*
|
|
* bit 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
|
|
* * | * | DN | * | * | * | * | * | * | * | * | FLT | HLIM | +LIM | -LIM | * |
|
|
*
|
|
* we only set Done bit to generate interrupt since no matter which bit of FLT, HLIM, +LIM
|
|
* or -LIM is set, the Done bit is set. As such, the interrupt source is identified by the
|
|
* ISR. Note, there is no interrupt clear concept. To stop the interrupt, we need to clear
|
|
* the mask bit. This means we need to enable the mask bit to enable the interrupt every time
|
|
* after a GO command which will also knock down the Done bit.
|
|
*
|
|
* @param useencoder - the least 4 bits determine to the 4 axes to use encoder when set (=1)
|
|
* @param encoderRatio0 ~ encoderRatio3 - hardware encoder ratio. For example, a quardrature encoder would
|
|
* have its ratio of 0.25 since every line generates 4 counts.
|
|
*
|
|
*/
|
|
extern "C" int Hytec8601Configure(const char *portName,
|
|
int numAxes,
|
|
int movingPollPeriod,
|
|
int idlePollPeriod,
|
|
int cardnum,
|
|
int ip_carrier,
|
|
int ipslot,
|
|
int vector,
|
|
int useencoder,
|
|
double encoderRatio0,
|
|
double encoderRatio1,
|
|
double encoderRatio2,
|
|
double encoderRatio3)
|
|
{
|
|
HytecMotorController *pHytecMotorController
|
|
= new HytecMotorController(portName, numAxes, movingPollPeriod/1000., idlePollPeriod/1000.,
|
|
cardnum, ip_carrier, ipslot, vector, useencoder, encoderRatio0,
|
|
encoderRatio1, encoderRatio2, encoderRatio3);
|
|
pHytecMotorController = NULL;
|
|
return(asynSuccess);
|
|
}
|
|
|
|
static const iocshArg Hy8601ConfigArg0 = {"portName", iocshArgString};
|
|
static const iocshArg Hy8601ConfigArg1 = {"numAxes", iocshArgInt};
|
|
static const iocshArg Hy8601ConfigArg2 = {"movingPollPeriod", iocshArgInt};
|
|
static const iocshArg Hy8601ConfigArg3 = {"idlePollPeriod", iocshArgInt};
|
|
static const iocshArg Hy8601ConfigArg4 = {"cardnum", iocshArgInt};
|
|
static const iocshArg Hy8601ConfigArg5 = {"carrier", iocshArgInt};
|
|
static const iocshArg Hy8601ConfigArg6 = {"ipslot", iocshArgInt};
|
|
static const iocshArg Hy8601ConfigArg7 = {"vector", iocshArgInt};
|
|
static const iocshArg Hy8601ConfigArg8 = {"useencoder", iocshArgInt};
|
|
static const iocshArg Hy8601ConfigArg9 = {"encoderRatio0", iocshArgDouble};
|
|
static const iocshArg Hy8601ConfigArg10 = {"encoderRatio1", iocshArgDouble};
|
|
static const iocshArg Hy8601ConfigArg11 = {"encoderRatio2", iocshArgDouble};
|
|
static const iocshArg Hy8601ConfigArg12 = {"encoderRatio3", iocshArgDouble};
|
|
|
|
static const iocshArg * const Hy8601ConfigArgs[] = {&Hy8601ConfigArg0,
|
|
&Hy8601ConfigArg1,
|
|
&Hy8601ConfigArg2,
|
|
&Hy8601ConfigArg3,
|
|
&Hy8601ConfigArg4,
|
|
&Hy8601ConfigArg5,
|
|
&Hy8601ConfigArg6,
|
|
&Hy8601ConfigArg7,
|
|
&Hy8601ConfigArg8,
|
|
&Hy8601ConfigArg9,
|
|
&Hy8601ConfigArg10,
|
|
&Hy8601ConfigArg11,
|
|
&Hy8601ConfigArg12};
|
|
|
|
static const iocshFuncDef configHy8601 = {"Hytec8601Configure", 13, Hy8601ConfigArgs};
|
|
static void configHy8601CallFunc(const iocshArgBuf *args)
|
|
{
|
|
Hytec8601Configure(args[0].sval, args[1].ival, args[2].ival, args[3].ival, args[4].ival,
|
|
args[5].ival, args[6].ival, args[7].ival, args[8].ival, args[9].dval,
|
|
args[10].dval, args[11].dval, args[12].dval);
|
|
}
|
|
|
|
static void Hytec8601Register(void)
|
|
{
|
|
iocshRegister(&configHy8601, configHy8601CallFunc);
|
|
}
|
|
|
|
epicsExportRegistrar(Hytec8601Register);
|
|
|
|
|