Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
61087d2e44 | |||
26754e608d | |||
a866023957 | |||
8008ece919 | |||
dbe031ca79 |
@ -42,7 +42,7 @@ epicsEnvSet("INSTR","SQ:SINQTEST:")
|
||||
|
||||
iocInit()
|
||||
```
|
||||
The first line is a so-called shebang which instructs Linux to execute the file with the executable located at the given path - the IOC shell in this case. The controller script "mcu1.cmd" then look like this:
|
||||
The first line is a so-called shebang which instructs Linux to execute the file with the executable located at the given path - the IOC shell in this case. The controller script "mcu1.cmd" looks like this:
|
||||
```
|
||||
# Define some needed parameters (they can be safely overwritten in e.g. mcu2.cmd)
|
||||
epicsEnvSet("NAME","mcu1")
|
||||
@ -167,7 +167,6 @@ sinqMotor offers a variety of additional methods for children classes to standar
|
||||
- `setOffsetMovTimeout`: Set a linear offset for the expected movement time. This function is also available in the IOC shell.
|
||||
- `setScaleMovTimeout`: Set a scaling factor for the expected movement time. This function is also available in the IOC shell.
|
||||
- `enable`: This function is called if the "Enable" PV from db/sinqMotor.db is set. This is an empty function which should be overwritten by concrete driver implementations.
|
||||
- `isEnabled`: This function returns whether the axis is currently enabled or not. This is an empty function which should be overwritten by concrete driver implementations.
|
||||
- `move`: This function sets the absolute target position in the parameter library and then calls `doMove`.
|
||||
- `doMove`: This is an empty function which should be overwritten by concrete driver implementations.
|
||||
- `home`: This function sets the absolute target position in the parameter library and then calls `doHome`. The target position is assumed to be the high limit, if the distance of the current position to it is larger than that to the low limit, and the low limit otherwise.
|
||||
|
@ -1,18 +1,18 @@
|
||||
# The main asyn motor record. Some fields are populated from the substitution
|
||||
# files via macros:
|
||||
# - $(INSTR): Name of the instrument, e.g. "SQ:SINQTEST:"
|
||||
# - $(M): Name of the motor in EPICS, e.g. "lin1"
|
||||
# - $(DESC): Short description of the motor
|
||||
# - $(DIR): This value is usually set to "Pos". If the motor axis direction
|
||||
# - INSTR: Name of the instrument, e.g. "SQ:SINQTEST:"
|
||||
# - M: Name of the motor in EPICS, e.g. "lin1"
|
||||
# - DESC: Short description of the motor. If not given, this is equal to M
|
||||
# - DIR: This value is usually set to "Pos". If the motor axis direction
|
||||
# should be inverted, this value can be set to "Neg"
|
||||
# - $(CONTROLLER): Name of the motor controller, e.g. "mcu1"
|
||||
# - $(AXIS): Number of the axis, e.g. "1"
|
||||
# - $(MRES): Motor record resolution. See the README.md for a detailed discussion
|
||||
# - $(EGU): Engineering units. In case of a rotary axis, this is "degree", in
|
||||
# - CONTROLLER: Name of the motor controller, e.g. "mcu1"
|
||||
# - AXIS: Number of the axis, e.g. "1"
|
||||
# - MRES: Motor record resolution. See the README.md for a detailed discussion
|
||||
# - EGU: Engineering units. In case of a rotary axis, this is "degree", in
|
||||
# case of a linear axis this is "mm".
|
||||
record(motor,"$(INSTR)$(M)")
|
||||
{
|
||||
field(DESC,"$(DESC)")
|
||||
field(DESC,"$(DESC=$(M))")
|
||||
field(DTYP,"asynMotor")
|
||||
field(DIR,"$(DIR=Pos)")
|
||||
field(OUT,"@asyn($(CONTROLLER),$(AXIS))")
|
||||
@ -60,7 +60,7 @@ record(longout, "$(INSTR)$(M):Enable") {
|
||||
|
||||
# Readback value which returns 1 if the motor is disabled and 0 otherwise.
|
||||
# This record is coupled to the parameter library via motorEnableRBV_ -> MOTOR_ENABLE_RBV.
|
||||
record(longin, "$(INSTR)$(M):Enable_RBV") {
|
||||
record(longin, "$(INSTR)$(M):EnableRBV") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(INP, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_ENABLE_RBV")
|
||||
field(PINI, "NO")
|
||||
@ -223,3 +223,17 @@ record(ao, "$(INSTR)$(M):PushACCL2Field") {
|
||||
field(OMSL, "closed_loop")
|
||||
}
|
||||
|
||||
# Read out the encoder type in human-readable form. The output numbers are ASCII
|
||||
# codes and can be converted to chars in order to get the encoder type.
|
||||
# EPICS prepends the ASCII code with 80
|
||||
# The following encoder types are defined:
|
||||
# - "Absolute encoder" (array 80 65 98 115 111 108 117 116 101 32 101 110 99 111 100 101 114)
|
||||
# - "Incremental encoder" (array 80 73 110 99 114 101 109 101 110 116 97 108 32 101 110 99 111 100 101 114)
|
||||
# This record is coupled to the parameter library via encoderType -> ENCODER_TYPE.
|
||||
record(waveform, "$(INSTR)$(M):EncoderType") {
|
||||
field(DTYP, "asynOctetRead")
|
||||
field(INP, "@asyn($(CONTROLLER),$(AXIS),1) ENCODER_TYPE")
|
||||
field(FTVL, "CHAR")
|
||||
field(NELM, "80")
|
||||
field(SCAN, "I/O Intr")
|
||||
}
|
||||
|
@ -17,6 +17,20 @@ asynStatus sinqAxis::atFirstPoll() {
|
||||
asynStatus status = asynSuccess;
|
||||
int variableSpeed = 0;
|
||||
|
||||
// Motor is assumed to be enabled
|
||||
status = pC_->setIntegerParam(axisNo_, pC_->motorEnableRBV_, 1);
|
||||
if (status != asynSuccess) {
|
||||
return pC_->paramLibAccessFailed(status, "motorEnableRBV_",
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
|
||||
// By default, motors cannot be disabled
|
||||
status = pC_->setIntegerParam(axisNo_, pC_->motorCanDisable_, 0);
|
||||
if (status != asynSuccess) {
|
||||
return pC_->paramLibAccessFailed(status, "motorCanDisable_",
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
|
||||
status =
|
||||
pC_->getIntegerParam(axisNo_, pC_->motorCanSetSpeed_, &variableSpeed);
|
||||
if (status != asynSuccess) {
|
||||
@ -86,42 +100,50 @@ asynStatus sinqAxis::poll(bool *moving) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
At the beginning of the poll, it is assumed that the axis has no status
|
||||
problems and therefore all error indicators are reset. This does not affect
|
||||
the PVs until callParamCallbacks has been called!
|
||||
|
||||
The motorStatusProblem_ field changes the motor record fields SEVR and STAT.
|
||||
*/
|
||||
pl_status = setIntegerParam(pC_->motorStatusProblem_, false);
|
||||
if (pl_status != asynSuccess) {
|
||||
pC_->paramLibAccessFailed(pl_status, "motorStatusProblem_",
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
pl_status = setIntegerParam(pC_->motorStatusCommsError_, false);
|
||||
if (pl_status != asynSuccess) {
|
||||
pC_->paramLibAccessFailed(pl_status, "motorStatusCommsError_",
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
pl_status = setStringParam(pC_->motorMessageText_, "");
|
||||
if (pl_status != asynSuccess) {
|
||||
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
|
||||
// The poll function is just a wrapper around doPoll and
|
||||
// handles mainly the callParamCallbacks() function. This wrapper is used
|
||||
// to make sure callParamCallbacks() is called in case of a premature
|
||||
// return.
|
||||
poll_status = doPoll(moving);
|
||||
|
||||
// The poll did not succeed: Something went wrong and the motor has a status
|
||||
// problem.
|
||||
if (poll_status != asynSuccess) {
|
||||
pl_status = setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
if (pl_status != asynSuccess) {
|
||||
pC_->paramLibAccessFailed(pl_status, "motorStatusProblem_",
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
// Check and update the watchdog
|
||||
if (checkMovTimeoutWatchdog(*moving) != asynSuccess) {
|
||||
return asynError;
|
||||
}
|
||||
|
||||
// If the poll status is ok, reset the error indicators in the parameter
|
||||
// library
|
||||
if (poll_status == asynSuccess) {
|
||||
pl_status = setIntegerParam(pC_->motorStatusProblem_, false);
|
||||
if (pl_status != asynSuccess) {
|
||||
pC_->paramLibAccessFailed(pl_status, "motorStatusProblem_",
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
pl_status = setIntegerParam(pC_->motorStatusCommsError_, false);
|
||||
if (pl_status != asynSuccess) {
|
||||
pC_->paramLibAccessFailed(pl_status, "motorStatusCommsError_",
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
|
||||
pl_status = setStringParam(pC_->motorMessageText_, "");
|
||||
if (pl_status != asynSuccess) {
|
||||
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the enable RBV
|
||||
bool axisIsEnabled = false;
|
||||
|
||||
pl_status = isEnabled(&axisIsEnabled);
|
||||
if (pl_status != asynSuccess) {
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s => line %d:\nFunction isEnabled failed with %s.\n",
|
||||
@ -134,12 +156,6 @@ asynStatus sinqAxis::poll(bool *moving) {
|
||||
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
} else {
|
||||
pl_status = setIntegerParam(pC_->motorEnableRBV_, axisIsEnabled);
|
||||
if (pl_status != asynSuccess) {
|
||||
return pC_->paramLibAccessFailed(pl_status, "motorEnableRBV_",
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
// According to the function documentation of asynMotorAxis::poll, this
|
||||
@ -256,11 +272,6 @@ asynStatus sinqAxis::doHome(double minVelocity, double maxVelocity,
|
||||
|
||||
asynStatus sinqAxis::enable(bool on) { return asynSuccess; }
|
||||
|
||||
asynStatus sinqAxis::isEnabled(bool *on) {
|
||||
*on = true;
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
asynStatus sinqAxis::setVeloFields(double velo, double vbas, double vmax) {
|
||||
asynStatus status = asynSuccess;
|
||||
int variableSpeed = 0;
|
||||
@ -491,8 +502,9 @@ asynStatus sinqAxis::checkMovTimeoutWatchdog(bool moving) {
|
||||
// Check the watchdog
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s => line %d:\nAxis %d exceeded the expected arrival time "
|
||||
"(%ld).\n",
|
||||
__PRETTY_FUNCTION__, __LINE__, axisNo_, expectedArrivalTime_);
|
||||
"%ld (current time is %ld).\n",
|
||||
__PRETTY_FUNCTION__, __LINE__, axisNo_, expectedArrivalTime_,
|
||||
time(NULL));
|
||||
|
||||
pl_status = setStringParam(
|
||||
pC_->motorMessageText_,
|
||||
|
@ -152,16 +152,6 @@ class epicsShareClass sinqAxis : public asynMotorAxis {
|
||||
*/
|
||||
virtual asynStatus enable(bool on);
|
||||
|
||||
/**
|
||||
* @brief This function should set "on" to true, if the motor is enabled,
|
||||
* and false otherwise. It should be implemented by a child class of
|
||||
* sinqAxis.
|
||||
*
|
||||
* @param on
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus isEnabled(bool *on);
|
||||
|
||||
/**
|
||||
* @brief Populate the motor record fields VELO, VBAS and VMAX
|
||||
*
|
||||
|
@ -204,6 +204,15 @@ sinqController::sinqController(const char *portName,
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
status = createParam("ENCODER_TYPE", asynParamOctet, &encoderType_);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
|
||||
"with %s).\nTerminating IOC",
|
||||
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
// Poller configuration
|
||||
status = startPoller(movingPollPeriod, idlePollPeriod, 1);
|
||||
if (status != asynSuccess) {
|
||||
@ -247,12 +256,27 @@ asynStatus sinqController::writeInt32(asynUser *pasynUser, epicsInt32 value) {
|
||||
|
||||
asynStatus sinqController::readInt32(asynUser *pasynUser, epicsInt32 *value) {
|
||||
if (pasynUser->reason == motorEnableRBV_) {
|
||||
// Value is updated in the poll function of an axis
|
||||
return asynSuccess;
|
||||
// Read out the parameter library
|
||||
asynMotorAxis *asynAxis = getAxis(pasynUser);
|
||||
sinqAxis *axis = dynamic_cast<sinqAxis *>(asynAxis);
|
||||
if (axis == nullptr) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s => line %d:\nAxis %d is not an instance of sinqAxis",
|
||||
__PRETTY_FUNCTION__, __LINE__, axis->axisNo_);
|
||||
return asynError;
|
||||
}
|
||||
return getIntegerParam(axis->axisNo_, motorEnableRBV_, value);
|
||||
} else if (pasynUser->reason == motorCanDisable_) {
|
||||
// By default, motors cannot be disabled
|
||||
*value = 1;
|
||||
return asynSuccess;
|
||||
// Check if the motor can be disabled
|
||||
asynMotorAxis *asynAxis = getAxis(pasynUser);
|
||||
sinqAxis *axis = dynamic_cast<sinqAxis *>(asynAxis);
|
||||
if (axis == nullptr) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s => line %d:\nAxis %d is not an instance of sinqAxis",
|
||||
__PRETTY_FUNCTION__, __LINE__, axis->axisNo_);
|
||||
return asynError;
|
||||
}
|
||||
return getIntegerParam(axis->axisNo_, motorCanDisable_, value);
|
||||
} else {
|
||||
return asynMotorController::readInt32(pasynUser, value);
|
||||
}
|
||||
|
@ -10,6 +10,9 @@ Stefan Mathis, November 2024
|
||||
|
||||
#define motorMessageIsFromDriverString "MOTOR_MESSAGE_DRIVER"
|
||||
#define motorMessageTextString "MOTOR_MESSAGE_TEXT"
|
||||
#define IncrementalEncoder "incremental"
|
||||
#define AbsoluteEncoder "absolute"
|
||||
#define NoEncoder "none"
|
||||
|
||||
class epicsShareClass sinqController : public asynMotorController {
|
||||
public:
|
||||
@ -146,7 +149,8 @@ class epicsShareClass sinqController : public asynMotorController {
|
||||
int motorAcclFromDriver_;
|
||||
int motorHighLimitFromDriver_;
|
||||
int motorLowLimitFromDriver_;
|
||||
#define LAST_SINQMOTOR_PARAM motorLowLimitFromDriver_
|
||||
int encoderType_;
|
||||
#define LAST_SINQMOTOR_PARAM encoderType_
|
||||
};
|
||||
#define NUM_SINQMOTOR_DRIVER_PARAMS \
|
||||
(&LAST_SINQMOTOR_PARAM - &FIRST_SINQMOTOR_PARAM + 1)
|
||||
|
Reference in New Issue
Block a user