diff --git a/motorApp/HytecSrc/HytecMotorControl.db b/motorApp/HytecSrc/HytecMotorControl.db new file mode 100644 index 00000000..26bd4aae --- /dev/null +++ b/motorApp/HytecSrc/HytecMotorControl.db @@ -0,0 +1,72 @@ +record(bo,"$(dev):$(area):$(locn):POWER") { + field(DTYP,"asynInt32") + field(OUT,"@asyn($(PORT) 0)HYTEC_POWER") + field(VAL, "1") + field(ZNAM, "On") + field(ONAM, "Off") +} + +record(bo,"$(dev):$(area):$(locn):BRAKE") { + field(DTYP,"asynInt32") + field(OUT,"@asyn($(PORT) 0)HYTEC_BRAKE") + field(VAL, "1") + field(ZNAM, "Set") + field(ONAM, "Rls") +} + +record(ai,"$(dev):$(area):$(locn):POSN") { + field(PINI, "YES") + field(DTYP,"asynInt32") + field(INP,"@asyn($(PORT) 0)MOTOR_POSITION") + field(SCAN, "1 second") +} + +record(ai,"$(dev):$(area):$(locn):FIRMWARE_VERSION") { + field(PINI, "YES") + field(DTYP,"asynInt32") + field(INP,"@asyn($(PORT) 0)HYTEC_FWVERSION") + field(SCAN, "1 second") +} + +record(ai,"$(dev):$(area):$(locn):EN_POSN") { + field(PINI, "YES") + field(DTYP,"asynInt32") + field(INP,"@asyn($(PORT) 0)MOTOR_ENCODER_POSITION") + field(SCAN, "1 second") +} + +record(ai, "$(dev):$(area):$(locn):LOWLMT") { + field(SCAN, "I/O Intr") + field(DTYP, "asynInt32") + field(INP, "@asyn($(PORT) 0) MOTOR_STATUS_LOW_LIMIT") +} + +record(ai, "$(dev):$(area):$(locn):HIGHLMT") { + field(SCAN, "I/O Intr") + field(DTYP, "asynInt32") + field(INP, "@asyn($(PORT) 0) MOTOR_STATUS_HIGH_LIMIT") +} + +record(ai, "$(dev):$(area):$(locn):DONE") { + field(SCAN, "I/O Intr") + field(DTYP, "asynInt32") + field(INP, "@asyn($(PORT) 0) MOTOR_STATUS_DONE") +} + +record(ai, "$(dev):$(area):$(locn):HOMELMT") { + field(SCAN, "I/O Intr") + field(DTYP, "asynInt32") + field(INP, "@asyn($(PORT) 0) MOTOR_STATUS_AT_HOME") +} + +record(ai, "$(dev):$(area):$(locn):FAULT") { + field(SCAN, "I/O Intr") + field(DTYP, "asynInt32") + field(INP, "@asyn($(PORT) 0) MOTOR_STATUS_PROBLEM") +} + +record(ai, "$(dev):$(area):$(locn):HASENCODER") { + field(SCAN, "I/O Intr") + field(DTYP, "asynInt32") + field(INP, "@asyn($(PORT) 0) MOTOR_STATUS_HAS_ENCODER") +} diff --git a/motorApp/HytecSrc/HytecMotorDriver.cpp b/motorApp/HytecSrc/HytecMotorDriver.cpp new file mode 100644 index 00000000..952363ec --- /dev/null +++ b/motorApp/HytecSrc/HytecMotorDriver.cpp @@ -0,0 +1,1083 @@ +/********************************************************************************/ +/* 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 */ +/* */ +/********************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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; axisintMsgQId = 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. THIS SEEMS NOT RIGHT. 8601 HAS 4 AXIS AND EACH ONE HAS A VECTOR SO 4 ISRs ARE NEEDED?????????????? */ + st=ipmIntConnect(this->ip_carrier, + this->ipslot, + this->vector, + &ISR_8601, + (int)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 && */ishytec && 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; axisnumAxes; 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(asynMotorController::getAxis(pasynUser)); +} + +HytecMotorAxis * HytecMotorController::getAxis(int axisNo) +{ + return dynamic_cast(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] ); + } + +//printf("\nasynDrover! Pointer=%x\n\n", data[0]); /* for debug */ + + /* 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<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("moved=%d, error=%d, desired=%d absp=%d csr=%x acc=%d minsp=%d maxsp=%d\n", (int)stepMoved, (int)error , (int)this->desiredMove, (int)this->absPosition, (int)csr, acc, lsp, hsp); +*/ + 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 = {"Hy8601AsynConfig", 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); + + diff --git a/motorApp/HytecSrc/HytecMotorDriver.dbd b/motorApp/HytecSrc/HytecMotorDriver.dbd new file mode 100644 index 00000000..a35acebf --- /dev/null +++ b/motorApp/HytecSrc/HytecMotorDriver.dbd @@ -0,0 +1,4 @@ +# Database Definition +registrar(Hytec8601Register) + + diff --git a/motorApp/HytecSrc/HytecMotorDriver.h b/motorApp/HytecSrc/HytecMotorDriver.h new file mode 100644 index 00000000..95c6b8c2 --- /dev/null +++ b/motorApp/HytecSrc/HytecMotorDriver.h @@ -0,0 +1,222 @@ +/********************************************************************************/ +/* 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 to asynMotorController.cpp. */ +/* 2.2 Bugs fix Jim Chen 14/04/2011 */ +/* Fixed setPosition bug */ +/* Added firmware version parameter */ +/* */ +/********************************************************************************/ + +#include "asynMotorController.h" +#include "asynMotorAxis.h" + +static const char *driverName = "HytecMotorDriver"; + +/* CSR Register bit definitions */ +#define CSR_HOMESTOP 0x8000 +#define CSR_INTEN 0x4000 +#define CSR_DONE 0x2000 +#define CSR_CRASHSTOP 0x1000 +#define CSR_DIRECTION 0x0800 +#define CSR_AUX2 0x0400 +#define CSR_AUX1 0x0200 +#define CSR_ENCODUSE 0x0100 +#define CSR_ENCODDET 0x0080 +#define CSR_JOG 0x0040 +#define CSR_GO 0x0020 +#define CSR_DRVSTAT 0x0010 +#define CSR_HOMELMT 0x0008 +#define CSR_MAXLMT 0x0004 +#define CSR_MINLMT 0x0002 +#define CSR_RESET 0x0001 + +/* New Hardware Register Map */ +#define REG_STEPCNTLO 0x00 +#define REG_STEPCNTHI 0x02 +#define REG_CURRPOSLO 0x04 +#define REG_CURRPOSHI 0x06 +#define REG_STARTSTOPSPD 0x08 +#define REG_HIGHSPD 0x0A +#define REG_RAMPRATE 0x0C +#define REG_CSR 0x0E +#define REG_INTMASK 0x10 +#define REG_INTVECTOR 0x12 +#define REG_INTREQUEST 0x14 +#define REG_CURRENTSPD 0x16 +#define REG_SPARE1 0x18 +#define REG_SPARE2 0x1A +#define REG_SPARE3 0x1C +#define REG_SPARE4 0x1E + +#define PROM_MODEL 0x8601 +#define PROM_OFFS 0x80 + +#define REG_BANK_OFFS 0x00 +#define REG_BANK_SZ 0x20 + +#define INT_LMT (CSR_MINLMT | CSR_MAXLMT | CSR_HOMELMT) +#define INT_SRCS (CSR_RESET | INT_LMT | CSR_DRVSTAT | CSR_DONE) + +/* define a mask to enable all sources of interrupts */ +#define ALL_INTS (INT_SRCS | CSR_INTEN) +#define DONE_INT (CSR_DONE) /* the only interrupt suggested to use. JC 12-Nov-2009 */ + +#define HY8601_NUM_AXES 4 + +#define IP_DETECT_STR "VITA4 " + +#define GET_REG(base,reg) (*(volatile epicsUInt16 *)((base)+(reg))) +#define SET_REG(base,reg,val) do { *(volatile epicsUInt16 *)((base)+(reg)) = (val);} while(0) +#define CSR_SET(base,bit) do { *(volatile epicsUInt16 *)((base)+REG_CSR) |= (bit);} while(0) +/* Please NOTE, The DONE bit is cleared by either ORing an "1" or ANDing an "1" (if it is already "1") to it. + * Yet neither ORing nor ANDing it with "0" would affect it. As such, clearing any other bit in the CSR with + * the AND operation need to be very careful so that it doesn't knock down the DONE bit. This is why in the + * following macro after inverting the bit in the CSR, we clear the DONE bit to the inversion result in order + * not to affect the DONE bit. */ +#define CSR_CLR(base,bit) do { *(volatile epicsUInt16 *)((base)+REG_CSR) &= ((~(bit)) & 0xDFFF);} while(0) + + +#define HytecPowerControlString "HYTEC_POWER" +#define HytecBrakeControlString "HYTEC_BRAKE" +#define HytecFirmwareString "HYTEC_FWVERSION" +#define HytecMoveAllString "HYTEC_MOVEALL" + + +class HytecMotorAxis : public asynMotorAxis +{ +public: + HytecMotorAxis(class HytecMotorController *pC, int axis, double ratio, int vector); + 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); + int getVector(); + volatile char * getChanbase(); + //the following are called from non-member function so they are here + +private: + asynStatus InitialiseAxis(); + HytecMotorController *pC_; + + volatile char *chanbase; + int vector; + + int useencoder; + double encoderRatio; /**< (double) Number of encoder counts in one motor count (encoder counts/motor counts) */ + double resolution; /**< (double) Number of motor units per engineering unit */ + double softLowLimit; /**< (double) Low soft limit in motor units ???? Shouldn't these two in engineering unit? */ + double softHighLimit; /**< (double) High soft limit in motor units ???? */ + double absPosition; + double desiredMove; + int times; /* to remember the the done bit set the first time */ + int absAskingPosition; + + +friend class HytecMotorController; +}; + + +class HytecMotorController : asynMotorController +{ +public: + 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); + + // These are the methods that we override from asynMotorDriver + asynStatus readInt32(asynUser *pasynUser, epicsInt32 *value); + asynStatus writeInt32(asynUser *pasynUser, epicsInt32 value); + asynStatus writeFloat64(asynUser *pasynUser, epicsFloat64 value); + void report(FILE *fp, int level); + + HytecMotorAxis* getAxis(asynUser *pasynUser); + HytecMotorAxis* getAxis(int axisNo); + + //the following are called from non-member function so they are here + void drvHy8601GetAxisStatus( HytecMotorAxis *pAxis, int csr_data ); + int getNumAxes(); + int getIPCarrier(); + int getIPSlot(); + epicsMessageQueueId getINTMsgQID(); + void increaseMsgSent(); + void increaseMsgFail(); + + +protected: + // New function codes + int HytecPowerControl_; +#define FIRST_HYTEC_PARAM HytecPowerControl_ + int HytecBrakeControl_; + int HytecFWVersion_; + int HytecMoveAll_; +#define LAST_HYTEC_PARAM HytecMoveAll_ + +#define NUM_HYTEC_PARAMS (&LAST_HYTEC_PARAM - &FIRST_HYTEC_PARAM + 1) + +private: + asynStatus SetupCard(); + int checkprom(char *pr,int expmodel); + + int numAxes; + epicsMessageQueueId intMsgQId; + int messagesSent; // for report + int messagesFailed; // for report + int ip_carrier; + int ipslot; + + asynUser *pasynUser; + + int card; + int vector; + int useencoder; + volatile char *regbase; + + epicsThreadId motorThread; + +friend class HytecMotorAxis; +}; diff --git a/motorApp/HytecSrc/README_Hy8601Asyn b/motorApp/HytecSrc/README_Hy8601Asyn new file mode 100644 index 00000000..6df5630f --- /dev/null +++ b/motorApp/HytecSrc/README_Hy8601Asyn @@ -0,0 +1,415 @@ +*********************************************************************************** +Hytec 8601 Step Motor Controller Asyn Driver Notes + +Author: Jim Chen, Hytec Electronics Ltd + jim.chen@hytec-electronics.co.uk + +First Initial: 12/NOV/2010 +Last updated: 05/MAY/2011 + +This driver is based on "model 3" motor asyn driver defined by Mark Rivers. +*********************************************************************************** + +Support modules +=============== + +1). asyn driver version asyn4-13-1 or later. + +2). ipac module version ipac-2.11 plus one of the Hytec carrier card drivers such as + drvHy8002.c for VxWorks or RTEMS under VME64x with 8002/8003/8004 carriers, + drvHyLinuxCarrier.c for Linux with IOC9010/PCIe6335/uTCA7002 carriers, + drvHyRTEMSCarrier.c for RTEMS with IOC9010 blade. + +3). devLib2 version 2.1 or later if using RTEMS on IOC9010 blade. + +4). motor module version later than motorR6-5-1. Currently the 'model 3' version can be + downloaded from subversion https://subversion.xor.aps.anl.gov/synApps/motor/trunk. + This will be formally released some stage on the official EPICS website. + +5). EPICS core R3.14.8.2 or later. + +6). RTEMS R4.9.4 or later. + + +Hytec 8002/8003/8004 carrier card configuration +=============================================== + +This board is a 6U VME64x carrier card. It provides four single size IP slots and +is configurable for many parameters. It supports interrupt level from 0 to 7. Any +IP cards can be enabled/disabled interrupt. The clock can be set to either 8MHz or +32MHz. For IPAC Memory space if required, the base address can be defined automatically +by geographical addressing when VME64x crate is used. For other type of crates, +the base address can be set by configuring a series of jumpers on the board or by +passing parameter to the memory offset register in the ipacAddCarrier call. + +The IPAC Carrier Driver for this board is found in the file drvHy8002.c which +implements two commands ipacAddHy8002 and Hy8002CarrierInfo. The ipacAddHy8002 +command is used to add a Hytec 8002/8003 board to the system. The Hy8002CarrierInfo +reports hardware information for a specified board or all boards in the system if +the parameter passed down is 0xFFFF. These commands are registered by the registrar +routine Hy8002Registrar to add them to the iocsh and link the driver into a final +IOC executable, for which it must be listed in the IOC's .dbd file thus: + + registrar(Hy8002Registrar) + + +1). Configuration Command and Parameter +-------------------------------------- + +- int ipacAddHYy8002(const char *cardParams); + +The parameter string should comprise two (2) to six (6) parameters which are comma +separated. The first two are mandate and have to be separated only by one comma. +The others are key/value pairs and are optional. The format is defined as + +s,i,IPMEM=d,IPCLCK=d,ROAK=d,MEMOFFS=d + +where d is a decimal integer number. +s defines the VME slot number of the carrier card. Valid number is 2 ~ 21 if + 1MB memory space is specified or 2~15 if 2MB memory space is specified +i defines the interrupt level. Valid number is 0 ~ 7. +IPMEM=d defines the maximum memory size of the IP module. The value d has to be 1, 2, + 4 or 8 that represent 1MB, 2MB, 4MB or 8MB respectively. Default is 1. +IPCLCK=d defines the clock that its value has to be either 8 for 8MHz or 32 for + 32Mhz. Default is 8. +ROAK=d if d =1, it defines carrier card to release the interrupt upon the + acknowledgment. If d=0, the interrupt is released by user interrupt + service routine. Default is 0. +MEMOFFS=d this is used to define the A32 memory space base address when geographic + addressing is not preferred. The value of MEMOFFS defines A16 ~ A31 of + the base address as shown below. It is derived from the VME slot number + of the carrier card plus the VME_A32_MSTR_LOCAL defined in the config.h + file of the BSP. + +D15 D14 D13 D12 D11 D10 D09 D08 D07 D06 D05 D04 D03 D02 D01 D00 +A31 A30 A29 A28 A27 A26 A25 A24 A23 A22 x x x x x x + +X= don't care, normally 0. So the actual meaningful bit starts from A22. + +The reason it starts from A22 is that the minimum VME carrier memory assignment is +4MB, i.e. each IP card has 1MB as defined by the IPMEM=1. As such, a carrier in +slot 1 would have base address 0x00400000 plus the VME_A32_MSTR_LOCAL define. +Of course slot 1 is occupied by processor. So for slot 2, the base address is 0x00800000 +and for slot 3, the base address is 0x00C00000 and so forth. + +If the VME_A32_MSTR_LOCAL is defined as 0x20000000, then for slot 3, the derived base +address is 0x00C00000 + 0x20000000 = 0x200C00000. Hence the MEMOFFS = 8204 (decimal, i.e. 0x200C). +For slot 5, the derived base address will be 0x01400000 + 0x20000000 = 0x21400000. +Hence the MEMOFFS = 8512 (decimal, i.e. 0x2140). And so forth. + +- int Hy8002CarrierInfo(int carrier); + +where 'carrier' is the registered carrier number in the system. If it is specified, +this function prints out the specified carrier hardware information. If carrier = 0xFFFF, +then all carriers' hardware information will be printed out. + + +2). Configuration Examples +------------------------- + +ipacAddHy8002("3,2") + +This indicates that the carrier is in slot 3 and the interrupt level is set to 2. +IP memory uses default 1MB. Clock uses default 8MHz. RORA as default. use geographical +addressing etc. + +ipacAddHy8002("5,4,IPMEM=1,IPCLCK=8,ROAK=1,MEMOFFS=192 ") + +Here the slot is 5, interrupt level is 4. IP memory size is 1MB, clock uses 8MHz. +Use ROAK but not using geographic addressing. The memory offset is 192 which means its +base address is 0x00C00000 assuming the VME_A32_MSTR_LOCAL is set to 0x00000000. + +3). Interrupt Commands Supported +------------------------------- + +The interrupt level can be set by the second parameter of the ipacAddHy8002 routine. +Individual IP module can be set to generate interrupt or not. The commands supported +for ipmIrqCmd are illustrated below. + +cmd Value Returned +ipac_irqGetLevel Carrier interrupt level (0 ~ 7) +ipac_irqEnable 0 = OK +ipac_irqDisable 0 = OK +ipac_irqPoll >0 if the interrupt line is active, else 0 +(other commands) S_IPAC_notImplemented + + +Hy8601 IP Asyn Driver Usage +============================ + +1). Configuration shell command +------------------------------- + +The configuration ioc shell command takes the form below. + +int Hytec8601Configure(char *portName, + epicsUInt16 numAxes, + epicsUInt16 cardnum, + epicsUInt16 movingPollPeriod, + epicsUInt16 idlePollPeriod, + epicsUInt16 ip_carrier, + epicsUInt16 ipslot, + epicsUInt16 vector, + epicsUInt16 useencoder, + epicsDouble encoderRatio0, + epicsDouble encoderRatio1, + epicsDouble encoderRatio2, + epicsDouble encoderRatio3) + +Where: + (1) portName asyn port name + (2) numAxes number of axes + (3) cardnum Arbitrary card number to assign to this controller + (4) movingPollPeriod status polling time period when motor is moving in ms + (5) idlePollPeriod status polling time period when motor is stopped in ms + (6) ip_carrier which previously configured IP carrier in the IOC + (7) ipslot which IP Slot on carrier card (0=A etc.) + (8) vector which Interrupt Vector (0 - Find One ?) + (9) useencoder - bit0 for axis0, bit1 for axis1, bit2 for axis2 and bit3 for axis3. + Other bits not used. 1=use encoder, 0=don't use encoder ! + (10) encoderRatio0 - axis0 hardware encoder ratio + (11) encoderRatio1 - axis1 hardware encoder ratio + (12) encoderRatio2 - axis2 hardware encoder ratio + (13) encoderRatio3 - axis3 hardware encoder ratio + +Example: + + Hytec8601Configure("Hy8601", 4, 0, 500, 1000, IPAC0, 0, 70, 15, 0.25, 0.25, 0.25, 0.25) + +This configures the 8601 card with + port name: Hy8601 + 4 axes + card number = 0 + poll status every 500ms when moving + poll status every 1000ms when stopped + carrier serial number = 0 + ipslot = site A + interrupt vector = 70 + all 4 axes use encoder + all 4 axes have quardrature encoder hence their ratios are 0.25 + + +2). Database definition file +---------------------------- + +Database definition file is: HytecMotorDriver.dbd. + +3). Databases +------------- + +Two databases can be loaded: + + dbLoadRecords("db/basic_asyn_motor.db") + dbLoadRecords("db/HytecMotorControl.db", "dev=IP8601,area=TEST,locn=LAB,PORT=Hy8601") + +Where: + basic_asyn_motor.db is the motor record database. It initialises 4 axes. + HytecMotorControl.db is the special controls for Hy8601 motor controller. It contains few extra asyn records + including POWER, BRAKE controls, synchronous read of absolute position and enocder position and firmware + version etc as below. + + record(bo,"$(dev):$(area):$(locn):POWER") { + field(DTYP,"asynInt32") + field(OUT,"@asyn($(PORT) 0)HYTEC_POWER") + field(VAL, "1") + field(ONAM, "On") + field(ZNAM, "Off") + } + + record(bo,"$(dev):$(area):$(locn):BRAKE") { + field(DTYP,"asynInt32") + field(OUT,"@asyn($(PORT) 0)HYTEC_BRAKE") + field(VAL, "1") + field(ZNAM, "Set") + field(ONAM, "Rls") + } + + record(ai,"$(dev):$(area):$(locn):POSN") { + field(PINI, "YES") + field(DTYP,"asynInt32") + field(INP,"@asyn($(PORT) 0)MOTOR_POSITION") + field(SCAN, "1 second") + } + + record(ai,"$(dev):$(area):$(locn):EN_POSN") { + field(PINI, "YES") + field(DTYP,"asynInt32") + field(INP,"@asyn($(PORT) 0)MOTOR_ENCODER_POSITION") + field(SCAN, "1 second") + } + + record(ai,"$(dev):$(area):$(locn):FIRMWARE_VERSION") { + field(PINI, "YES") + field(DTYP,"asynInt32") + field(INP,"@asyn($(PORT) 0)HYTEC_FWVERSION") + field(SCAN, "1 second") + } + +There are other records in the HytecMotorControl.db which can return the positions synchronously etc. + +Notes: + + - The power control uses AUX1 of the 8601 CSR. As it indicates in the record above, command + + caput $(dev):$(area):$(locn):POWER 1 + + means power "On" and it sets the AUX1 bit, i.e. AUX1 output high (1). + + caput $(dev):$(area):$(locn):POWER 0 + + means power "Off" so it clears AUX1 bit, i.e. AUX1 output low (0). Command + + - The brake control uses AUX2 of the 8601 CSR. As it indicates in the record above, command + + caput $(dev):$(area):$(locn):BRAKE 1 + + means to release the brake. It clears AUX2 bit, i.e. AUX2 output low (0). Command + + caput $(dev):$(area):$(locn):BRAKE 0 + + means set the brake. It sets the AUX2 bit, i.e. AUX2 output high (1). + + - The firmware version record returns the IP card PCB board issue number and the firmware version + + caget $(dev):$(area):$(locn):FIRMWARE_VERSION + + will return something like '2210' for example, whereas the first 2 is the PCB issue number and + 210 is the firmware version. + +4). To build an example application +----------------------------------- + +To build an example to test the 8601 asyn driver, use EPICS makeBaseApp.pl script to create +the example as usual. Then modify the following files to include the driver module(s). + + - /configure/RELEASE + + Change the EPICS_BASE and SUPPORT to the proper directories. + Add ASYN, IPAC and MOTOR moudles: + + ASYN=$(SUPPORT)/asyn/asyn4-14 + IPAC=$(SUPPORT)/ipac/ipac-2.11 + MOTOR=/motorR6-5-2 + + - xxxApp/src/Makefile + + In the Makefile of the example src, add following lines: + + example_DBD += asyn.dbd + example_DBD += motorSupport.dbd + example_DBD += motorRecord.dbd + example_DBD += drvIpac.dbd + example_DBD += HytecMotorDriver.dbd + + example_LIBS += HytecMotor + example_LIBS += motor + example_LIBS += Ipac + example_LIBS += asyn + + + - /xxxApp/Db/Makefile + + Copy HytecMotorControl.db to the Db directory and add the following lines in the Makefile of + the Db directory: + + DB += basic_asyn_motor.db + DB += HytecMotorControl.db + + - Build the application from + + - Modify st.cmd in iocBoot/iocexample as per next section and run the start up script from here. + +5). Start up script example +--------------------------- + +The following example is for an IOC that uses RTEMS R4.9.4, MVME5500 processor board. + + # Change directory to TOP of application + cd("../..") + iocBoot=pwd() + ld( "bin/RTEMS-mvme5500/MotionControl.obj") + #ld < bin/vxWorks-ppc604_long/MotionControl.munch #for VxWorks + + ## Set common environment variables + #< all/pre_st.cmd + epicsEnvSet("IOC_NAME", "MC02") + epicsEnvSet("LOCA_NAME", "B025") + epicsEnvSet("ENGINEER", "Condamoor, Shantha") + + epicsEnvSet( "EPICS_CA_MAX_ARRAY_BYTES", "30000") + + # Register all support components + dbLoadDatabase( "dbd/MotionControl.dbd") + MotionControl_registerRecordDeviceDriver( pdbbase) + + bspExtVerbosity=0 + + ########################################################### + # Configure Hytec 8002 carriers + # 8002 carrier VME slot: 3 + # INT level: 5 + # Memory per IP: 1MB + # Memory mapping offset: 8384 + # =================================================================== + #The typical output on beatnik looks like this: + # Cexp@till35>BSP_VMEOutboundPortsShow() + # Tsi148 Outbound Ports: + # Port VME-Addr Size PCI-Adrs Mode: + # 0: 0x20000000 0x0e000000 0x90000000 A32, SUP, D32, SCT + # 1: 0x00000000 0x00ff0000 0x9f000000 A24, SUP, D32, SCT + # 2: 0x00000000 0x00010000 0x9fff0000 A16, SUP, D32, SCT + # 7: 0x00000000 0x01000000 0x9e000000 CSR, SUP, D32, SCT + # =================================================================== + + # A32 space configured to start here: 0x20000000 + # Tell the carrier not to use geographic addressing + IPAC0=ipacAddHy8002("3,5,IPMEM=1,MEMOFFS=8384") + + # + # Hytec MDS-8 8601 driver setup parameters: + # int Hytec8601Configure(char *portName, + # epicsUInt16 numAxes, + # epicsUInt16 cardnum, + # epicsUInt16 movingPollPeriod, + # epicsUInt16 idlePollPeriod, + # epicsUInt16 ip_carrier, + # epicsUInt16 ipslot, + # epicsUInt16 vector, + # epicsUInt16 useencoder, + # epicsDouble encoderRatio0, + # epicsDouble encoderRatio1, + # epicsDouble encoderRatio2, + # epicsDouble encoderRatio3) + # (1) portName asyn port name + # (2) numAxes number of axes + # (3) cardnum Arbitrary card number to assign to this controller + # (4) movingPollPeriod status polling time period when motor is moving in ms + # (5) idlePollPeriod status polling time period when motor is stopped in ms + # (6) ip_carrier which previously configured IP carrier in the IOC + # (7) ipslot which IP Slot on carrier card (0=A etc.) + # (8) vector which Interrupt Vector (0 - Find One ?) + # (9) useencoder - bit0 for axis0, bit1 for axis1, bit2 for axis2 and bit3 for axis3. + # Other bits not used. 1=use encoder, 0=don't use encoder ! + # (10) encoderRatio0 - axis0 hardware encoder ratio + # (11) encoderRatio1 - axis1 hardware encoder ratio + # (12) encoderRatio2 - axis2 hardware encoder ratio + # (13) encoderRatio3 - axis3 hardware encoder ratio + + Hytec8601Configure("Hy8601", 4, 0, IPAC0, 0, 70, 0, 0.25, 0.25, 0.25, 0.25) + + + # ======================================================== + # New Hytec Motor Databases + # ======================================================= + dbLoadRecords("db/basic_asyn_motor.db") + dbLoadRecords("db/HytecMotorControl.db", "dev=IP8601,area=TEST,locn=LAB,PORT=Hy8601") + # ======================================================== + + iocInit() + + + + + + +