Compare commits

...

8 Commits
0.4.0 ... 0.6.2

Author SHA1 Message Date
b6e0f03a17 Moved the initialization of some parameters into sinqMotor 2024-12-23 09:30:24 +01:00
5946563372 Merge branch 'main' of https://git.psi.ch/sinq-epics-modules/sinqmotor 2024-12-11 09:59:36 +01:00
da96b4b973 Adjusted the encoder type naming to that of the new NICOS class
(https://forge.frm2.tum.de/review/c/frm2/nicos/nicos/+/35285)
2024-12-11 09:57:59 +01:00
61087d2e44 Adjusted the encoder type naming to that of the new NICOS class
(https://forge.frm2.tum.de/review/c/frm2/nicos/nicos/+/35285)
2024-12-11 09:49:17 +01:00
26754e608d Fixed a bug which resulted in motorStatusProblem not being set properly. 2024-12-10 09:09:02 +01:00
a866023957 Various improvements:
- Moved the encoder type PV to sinqMotor
- Unified the PV naming scheme
- Removed the IsEnabled function - this status is read out from the
parameter library (motorEnableRBV_)
2024-12-09 11:14:19 +01:00
8008ece919 Fixed typo
Conflicts:
2024-12-06 08:37:12 +01:00
dbe031ca79 Reworked documentation after discussion with Electronics on 04.12.2024 2024-12-06 08:35:43 +01:00
6 changed files with 144 additions and 69 deletions

View File

@ -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")
@ -75,7 +75,8 @@ dbLoadTemplate("$(TOP)/mcu1.substitutions", "INSTR=$(INSTR)$(NAME):,CONTROLLER=$
### Substitution file
The substitution file is a table containing axis-specific information which is used to create the axis-specific PVs. To work with sinqMotor, "mcu1.substitutions" needs to look like this:
The substitution file is a table containing axis-specific information which is used to create the axis-specific PVs.
To work with sinqMotor, "mcu1.substitutions" needs to look like this (the order of columns does not matter):
```
file "$(SINQDBPATH)"
{
@ -93,13 +94,13 @@ The variable `SINQDBPATH` has been set in "mcu1.cmd" before calling `dbLoadTempl
- `AXIS`: Index of the axis, corresponds to the physical connection of the axis to the MCU.
- `M`: The full PV name is created by concatenating the variables INSTR, NAME and M. For example, the PV of the first axis would be "SQ:SINQTEST:mcu1:lin1".
- `DESC`: Description of the motor. This field is just for documentation and is not needed for operating a motor.
- `EGU`: Engineering units. For a linear motor, this is mm, for a rotaty motor, this is degree.
- `DIR`: If set to "Neg", the axis direction is inverted.
- `MRES`: This is a scaling factor determining the resolution of the position readback value. For example, 0.001 means a precision of 1 um. A detailed description can be found in section [Motor record resolution MRES](#motor-record-resolution-mres).
#### Optional parameters
The default values for those parameters are given for the individual records in db/sinqMotor.db
- `DESC`: Description of the motor. This field is just for documentation and is not needed for operating a motor.
- `MSGTEXTSIZE`: Buffer size for the motor message record in characters
- `ENABLEMOVWATCHDOG`: Sets `setWatchdogEnabled` during IOC startup to the given value.
- `LIMITSOFFSET`: If the motor limits are read out from the controller, they can
@ -167,7 +168,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.

View File

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

View File

@ -5,12 +5,57 @@
sinqAxis::sinqAxis(class sinqController *pC, int axisNo)
: asynMotorAxis((asynMotorController *)pC, axisNo), pC_(pC) {
asynStatus status = asynSuccess;
initial_poll_ = true;
watchdogMovActive_ = false;
init_poll_counter_ = 0;
scaleMovTimeout_ = 2.0;
offsetMovTimeout_ = 30;
// Motor is assumed to be enabled
status = pC_->setIntegerParam(axisNo_, pC_->motorEnableRBV_, 1);
if (status != asynSuccess) {
asynPrint(
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (setting a parameter value failed "
"with %s)\n. Terminating IOC",
__PRETTY_FUNCTION__, __LINE__, pC_->stringifyAsynStatus(status));
exit(-1);
}
// By default, motors cannot be disabled
status = pC_->setIntegerParam(axisNo_, pC_->motorCanDisable_, 0);
if (status != asynSuccess) {
asynPrint(
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (setting a parameter value failed "
"with %s)\n. Terminating IOC",
__PRETTY_FUNCTION__, __LINE__, pC_->stringifyAsynStatus(status));
exit(-1);
}
// Provide a default value for the motor position.
status = pC_->setDoubleParam(axisNo_, pC_->motorPosition_, 0.0);
if (status != asynSuccess) {
asynPrint(
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (setting a parameter value failed "
"with %s)\n. Terminating IOC",
__PRETTY_FUNCTION__, __LINE__, pC_->stringifyAsynStatus(status));
exit(-1);
}
// We assume that the motor has no status problems initially
status = pC_->setIntegerParam(axisNo_, pC_->motorStatusProblem_, 0);
if (status != asynSuccess) {
asynPrint(
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (setting a parameter value failed "
"with %s)\n. Terminating IOC",
__PRETTY_FUNCTION__, __LINE__, pC_->stringifyAsynStatus(status));
exit(-1);
}
}
asynStatus sinqAxis::atFirstPoll() {
@ -86,42 +131,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 +187,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 +303,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 +533,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_,

View File

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

View File

@ -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);
}
@ -266,7 +290,7 @@ asynStatus sinqController::errMsgCouldNotParseResponse(const char *command,
asynStatus pl_status = asynSuccess;
asynPrint(lowLevelPortUser_, ASYN_TRACE_ERROR,
"%s => line %d:\nCould not interpret response %s for "
"%s => line %d:\nCould not interpret response '%s' for "
"command %s.\n",
functionName, lineNumber, response, command);

View File

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