Substantial rRework of 0.2.0 after the CAMEA test showed multiple
problems. Also improved the documentation.
This commit is contained in:
2
Makefile
2
Makefile
@ -21,6 +21,6 @@ HEADERS += src/sinqController.h
|
||||
TEMPLATES += db/asynRecord.db
|
||||
TEMPLATES += db/sinqMotor.db
|
||||
|
||||
USR_CFLAGS += -Wall -Wextra # -Werror
|
||||
USR_CFLAGS += -Wall -Wextra -Weffc++ -Wunused-result # -Werror
|
||||
|
||||
# MISCS would be the place to keep the stream device template files
|
||||
|
53
README.md
53
README.md
@ -4,7 +4,7 @@
|
||||
|
||||
This library offers base classes for EPICS motor drivers (`sinqAxis` and `sinqController`) of PSI SINQ. These classes are extensions of the classes `asynMotorAxis` and `asynMotorController` from the `asynMotor` framework (https://github.com/epics-modules/motor/tree/master/motorApp/MotorSrc) and bundle some common functionalities.
|
||||
|
||||
## Features
|
||||
## Base classes
|
||||
|
||||
sinqMotor offers a variety of additional methods for children classes to standardize certain patterns (e.g. writing messages to the IOC shell and the motor message PV). For a detailed description, please see the respective function documentation in the .h-files. All of these functions can be overwritten manually if e.g. a completely different implementation of `poll` is required. Some functions are marked as virtual, because they are called from other functions of sinqMotor and therefore need runtime polymorphism. Functions without that marker are not called anywhere in sinqMotor.
|
||||
|
||||
@ -17,31 +17,64 @@ sinqMotor offers a variety of additional methods for children classes to standar
|
||||
- `atFirstPoll`: This function is executed once before the first poll. If it returns anything but `asynSuccess`, it retries.
|
||||
- `setWatchdogEnabled`: Enables / disables the watchdog. This function is also available in the IOC shell.
|
||||
- `checkMovTimeoutWatchdog`: Manages a watchdog mechanism for movement operations. This watchdog compares the actual time spent in a movement operation with an expected time, which is calculated based on the distance of the current and the target position.
|
||||
- `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.
|
||||
- `doHome`: This is an empty function which should be overwritten by concrete driver implementations.
|
||||
- `poll`: This is a wrapper around `doPoll` which performs some bookkeeping tasks before and after calling `doPoll`:
|
||||
|
||||
Before calling `doPoll`:
|
||||
- Try to execute `atFirstPoll` once during the lifetime of the IOC (and retry, if that failed)
|
||||
|
||||
After calling `doPoll`:
|
||||
- Call `checkMovTimeoutWatchdog`. If the movement timed out, create an error message for the user
|
||||
- Update the readback-value for the axis enablement.
|
||||
- Reset `motorStatusProblem_`, `motorStatusCommsError_` and `motorMessageText_` if `doPoll` returned `asynSuccess`
|
||||
- Run `callParamCallbacks`
|
||||
- Return the status of `doPoll`
|
||||
- `doPoll`: 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.
|
||||
- `doHome`: This is an empty function which should be overwritten by concrete driver implementations.
|
||||
- `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.
|
||||
|
||||
## Database
|
||||
|
||||
Besides the base classes, this EPICS module also offers an accompanying db/sinqMotor.db file. It can be parametrized with the `dbLoadTemplate` function from the IOC shell:
|
||||
|
||||
```
|
||||
require sinqMotor, x.x.x # This is the three-digit version number of the sinqMotor module
|
||||
dbLoadTemplate "motor.substitutions"
|
||||
```
|
||||
with `motor.substitutions` looking like this:
|
||||
```
|
||||
file "$(sinqMotor_DB)/sinqMotor.db"
|
||||
{
|
||||
pattern
|
||||
{ AXIS, M, DESC, EGU, DIR, MRES, MOVWATCHDOG, LIMITSOFFSET }
|
||||
{ 1, "lin1", "lin1", mm, Pos, 0.001, 1, , 1.0 }
|
||||
{ 2, "rot1", "rot1", degree, Neg, 0.001, 0, , 1.0 }
|
||||
}
|
||||
```
|
||||
The variable `sinqMotor_DB` is generated automatically by `require` and corresponds to the module database path.
|
||||
The other parameters have the following meaning:
|
||||
- `AXIS`: Index of the axis, corresponds to the physical connection of the axis to the MCU
|
||||
- `M`: Name of the motor as shown in EPICS
|
||||
- `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 discussion of this value can be found in sinqAxis.cspp/startMovTimeoutWatchdog.
|
||||
- `MOVWATCHDOG`: Sets `setWatchdogEnabled` during IOC startup to the given value.
|
||||
- `LIMITSOFFSET`: If the motor limits are read out from the controller, they can be further reduced by this offset in order to avoid errors due to slight overshoot on the motor controller. For example, if this value is 1.0 and the read-out limits are [-10.0 10.0], the EPICS limits are set to [-9.0 9.0].
|
||||
|
||||
## Versioning
|
||||
|
||||
The versioning is done via git tags. Git tags are recognized by the PSI build system: If you tag a version as 1.0, it will be built into the directory /ioc/modules/sinqMotor/1.0. The tag is directly coupled to a commit so that it is always clear which source code was used to build which binary.
|
||||
|
||||
All existing tags can be listed with `git tag` in the sinqMotor directory. Detailed information (author, data, commit number, commit message) regarding a specific tag can be shown with `git show X.X`, where X.X is the name of your version. To create a new tag, use `git tag X.X`. If the tag `X.X` is already used by another commit, git will show a corresponding error.
|
||||
All existing tags can be listed with `git tag` in the sinqMotor directory. Detailed information (author, data, commit number, commit message) regarding a specific tag can be shown with `git show x.x.x`, where `x.x.x` is the name of your version. To create a new tag, use `git tag x.x.x`. If the tag `x.x.x` is already used by another commit, git will show a corresponding error.
|
||||
|
||||
## How to build it
|
||||
|
||||
The makefile in the top directory includes all necessary steps for compiling a shared library together with the header files into `/ioc/modules` (using the PSI EPICS build system).Therefore it is sufficient to run `make install -f Makefile` from the terminal.
|
||||
The makefile in the top directory includes all necessary steps for compiling a shared library together with the header files into `/ioc/modules` (using the PSI EPICS build system).Therefore it is sufficient to run `make install` from the terminal.
|
||||
|
||||
To use the library when writing a concrete motor driver, include it in the makefile of your application /library the same way as other libraries such as e.g. `asynMotor` by adding `REQUIRED+=sinqMotor` to your Makefile.
|
||||
To use the library when writing a concrete motor driver, include it in the makefile of your application /library the same way as other libraries such as e.g. `asynMotor` by adding `REQUIRED+=sinqMotor` to your Makefile. The version can be specified with `sinqMotor_VERSION=x.x.x.`
|
||||
|
@ -21,50 +21,6 @@ record(waveform, "$(P)$(M)-MsgTxt") {
|
||||
field(SCAN, "I/O Intr")
|
||||
}
|
||||
|
||||
# Encoder type
|
||||
record(waveform, "$(P)$(M):Encoder_Type") {
|
||||
field(DTYP, "asynOctetRead")
|
||||
field(INP, "@asyn($(CONTROLLER),$(AXIS),1) ENCODER_TYPE")
|
||||
field(FTVL, "CHAR")
|
||||
field(NELM, "80")
|
||||
field(SCAN, "I/O Intr")
|
||||
}
|
||||
|
||||
# enable axis
|
||||
record(longout, "$(P)$(M):Enable") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) ENABLE_AXIS")
|
||||
field(PINI, "NO")
|
||||
}
|
||||
|
||||
# enable axis
|
||||
record(longin, "$(P)$(M):Enable_RBV") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(INP, "@asyn($(CONTROLLER),$(AXIS),1) AXIS_ENABLED")
|
||||
field(PINI, "YES")
|
||||
}
|
||||
|
||||
# reread encoder
|
||||
record(longout, "$(P)$(M):Reread_Encoder") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) REREAD_ENCODER_POSITION")
|
||||
field(PINI, "NO")
|
||||
}
|
||||
|
||||
# reread encoder
|
||||
record(longin, "$(P)$(M):Reread_Encoder_RBV") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(INP, "@asyn($(CONTROLLER),$(AXIS),1) REREAD_ENCODER_POSITION_RBV")
|
||||
field(PINI, "YES")
|
||||
}
|
||||
|
||||
# reread encoder
|
||||
record(longout, "$(P)$(M):Read_Config") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) READ_CONFIG")
|
||||
field(PINI, "NO")
|
||||
}
|
||||
|
||||
# Provides the motor resolution MRES via an additional PV as explained here:
|
||||
# https://epics.anl.gov/tech-talk/2020/msg00378.php
|
||||
record(ao,"$(P)$(M):Resolution") {
|
||||
@ -75,6 +31,40 @@ record(ao,"$(P)$(M):Resolution") {
|
||||
field(OUT, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_REC_RESOLUTION")
|
||||
}
|
||||
|
||||
record(longout, "$(P)$(M):Enable") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) ENABLE_AXIS")
|
||||
field(PINI, "NO")
|
||||
}
|
||||
|
||||
record(longin, "$(P)$(M):Enable_RBV") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(INP, "@asyn($(CONTROLLER),$(AXIS),1) ENABLE_AXIS_RBV")
|
||||
field(PINI, "NO")
|
||||
field(SCAN, "I/O Intr")
|
||||
}
|
||||
|
||||
record(longout, "$(P)$(M):EnableWatchdog") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) ENABLE_MOV_WATCHDOG")
|
||||
field(PINI, "YES")
|
||||
field(VAL, "$(MOVWATCHDOG)")
|
||||
}
|
||||
|
||||
# The high and low limits of the axis are read
|
||||
# out directly from the MCU. However, since the axis might slightly
|
||||
# "overshoot" when moving to a position next to the limits, the MCU might go
|
||||
# into the "limits hit" error state. To prevent this, this value allows adding
|
||||
# a small offset, which is subtracted from the high limit and added to the
|
||||
# low limit.
|
||||
record(ao, "$(P)$(M):LimitsOffset") {
|
||||
field(DTYP, "asynFloat64")
|
||||
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) LIMITS_OFFSET")
|
||||
field(PINI, "YES")
|
||||
field(ASG, "READONLY") # Field is initialized during IOC startup
|
||||
field(VAL, "$(LIMITSOFFSET)")
|
||||
}
|
||||
|
||||
# ===================================================================
|
||||
# The following records read the high / low limits from the parameter
|
||||
# library and copy those values into the corresponding fields of the main motor record.
|
||||
|
189
src/sinqAxis.cpp
189
src/sinqAxis.cpp
@ -9,6 +9,8 @@ sinqAxis::sinqAxis(class sinqController *pC, int axisNo)
|
||||
initial_poll_ = true;
|
||||
watchdogMovActive_ = false;
|
||||
init_poll_counter_ = 0;
|
||||
scaleMovTimeout_ = 2.0;
|
||||
offsetMovTimeout_ = 30;
|
||||
}
|
||||
|
||||
asynStatus sinqAxis::atFirstPoll() { return asynSuccess; }
|
||||
@ -53,6 +55,11 @@ asynStatus sinqAxis::poll(bool *moving) {
|
||||
// return.
|
||||
poll_status = doPoll(moving);
|
||||
|
||||
// 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) {
|
||||
@ -74,6 +81,30 @@ asynStatus sinqAxis::poll(bool *moving) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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",
|
||||
__PRETTY_FUNCTION__, __LINE__,
|
||||
pC_->stringifyAsynStatus(poll_status));
|
||||
pl_status = setStringParam(pC_->motorMessageText_,
|
||||
"Could not check whether the motor is "
|
||||
"enabled or not. Please call the support");
|
||||
if (pl_status != asynSuccess) {
|
||||
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
} else {
|
||||
pl_status = setIntegerParam(pC_->enableMotorRBV_, axisIsEnabled);
|
||||
if (pl_status != asynSuccess) {
|
||||
return pC_->paramLibAccessFailed(pl_status, "enableMotorRBV_",
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
// According to the function documentation of asynMotorAxis::poll, this
|
||||
// function should be called at the end of a poll implementation.
|
||||
pl_status = callParamCallbacks();
|
||||
@ -186,66 +217,183 @@ asynStatus sinqAxis::doHome(double minVelocity, double maxVelocity,
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
asynStatus sinqAxis::setWatchdogEnabled(bool enable) {
|
||||
watchdogEnabled_ = enable;
|
||||
asynStatus sinqAxis::enable(bool on) { return asynSuccess; }
|
||||
|
||||
asynStatus sinqAxis::isEnabled(bool *on) {
|
||||
*on = true;
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
asynStatus sinqAxis::setWatchdogEnabled(bool enable) {
|
||||
return pC_->setIntegerParam(axisNo_, pC_->enableMovWatchdog_, enable);
|
||||
}
|
||||
|
||||
asynStatus sinqAxis::startMovTimeoutWatchdog() {
|
||||
if (watchdogEnabled_) {
|
||||
asynStatus pl_status;
|
||||
int enableMovWatchdog = 0;
|
||||
|
||||
pl_status = pC_->getIntegerParam(axisNo_, pC_->enableMovWatchdog_,
|
||||
&enableMovWatchdog);
|
||||
if (pl_status != asynSuccess) {
|
||||
return pC_->paramLibAccessFailed(pl_status, "enableMovWatchdog_",
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
|
||||
if (enableMovWatchdog == 1) {
|
||||
// These parameters are only needed in this branch
|
||||
double motorPosition = 0.0;
|
||||
double motorPositionRec = 0.0;
|
||||
double motorTargetPositionRec = 0.0;
|
||||
double motorTargetPosition = 0.0;
|
||||
double motorVelBase = 0.0;
|
||||
double motorVelocity = 0.0;
|
||||
double motorVelocityRec = 0.0;
|
||||
double motorAccel = 0.0;
|
||||
double motorAccelRec = 0.0;
|
||||
double motorRecResolution = 0.0;
|
||||
time_t timeContSpeed = 0;
|
||||
time_t timeAccel = 0;
|
||||
asynStatus pl_status;
|
||||
|
||||
// Activate the watchdog
|
||||
watchdogMovActive_ = true;
|
||||
|
||||
pl_status =
|
||||
pC_->getDoubleParam(axisNo_, pC_->motorPosition_, &motorPosition);
|
||||
/*
|
||||
The motor record resolution (index motorRecResolution_ in the parameter
|
||||
library, MRES in the motor record) is NOT a conversion factor between
|
||||
user units (e.g. mm) and motor units (e.g. encoder steps), but a scaling
|
||||
factor defining the resolution of the position readback field RRBV. This
|
||||
is due to an implementation detail inside EPICS described here:
|
||||
https://epics.anl.gov/tech-talk/2018/msg00089.php
|
||||
https://github.com/epics-modules/motor/issues/8
|
||||
|
||||
Basically, the position value in the parameter library is a double which
|
||||
is then truncated to an integer in devMotorAsyn.c (because it was
|
||||
originally meant for converting from engineering units to encoder steps,
|
||||
which are by definition integer values). Therefore, if we want a
|
||||
precision of 1 millimeter, we need to set MRES to 1. If we want one of
|
||||
1 micrometer, we need to set MRES to 0.001. The readback value needs to
|
||||
be multiplied with MRES to get the actual value.
|
||||
|
||||
In the driver, we use user units. Therefore, when we interact with the
|
||||
parameter library, we need to account for MRES. This means:
|
||||
- When writing position or speed to the parameter library, we divide the
|
||||
value by the motor record resolution.
|
||||
- When reading position or speed from the parameter library, we multiply
|
||||
the value with the motor record resolution.
|
||||
|
||||
Index and motor record field are coupled as follows:
|
||||
The parameter motorRecResolution_ is coupled to the field MRES of the
|
||||
motor record in the following manner:
|
||||
- In sinqMotor.db, the PV (motor_record_pv_name) MOTOR_REC_RESOLUTION
|
||||
is defined as a copy of the field (motor_record_pv_name).MRES:
|
||||
|
||||
record(ao,"$(P)$(M):Resolution") {
|
||||
field(DESC, "$(M) resolution")
|
||||
field(DOL, "$(P)$(M).MRES CP MS")
|
||||
field(OMSL, "closed_loop")
|
||||
field(DTYP, "asynFloat64")
|
||||
field(OUT, "@asyn($(PORT),$(ADDR))MOTOR_REC_RESOLUTION")
|
||||
field(PREC, "$(PREC)")
|
||||
}
|
||||
|
||||
- The PV name MOTOR_REC_RESOLUTION is coupled in asynMotorController.h
|
||||
to the constant motorRecResolutionString
|
||||
- ... which in turn is assigned to motorRecResolution_ in
|
||||
asynMotorController.cpp This way of making the field visible to the
|
||||
driver is described here:
|
||||
https://epics.anl.gov/tech-talk/2020/msg00378.php This is a one-way
|
||||
coupling, changes to the parameter library via setDoubleParam are NOT
|
||||
transferred to (motor_record_pv_name).MRES or to
|
||||
(motor_record_pv_name):Resolution.
|
||||
|
||||
NOTE: This function must not be called in the constructor (e.g. in order
|
||||
to save the read result to the member variable earlier), since the
|
||||
parameter library is updated at a later stage!
|
||||
*/
|
||||
pl_status = pC_->getDoubleParam(axisNo_, pC_->motorRecResolution_,
|
||||
&motorRecResolution);
|
||||
if (pl_status != asynSuccess) {
|
||||
return pC_->paramLibAccessFailed(pl_status, "motorRecResolution_",
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
|
||||
pl_status = pC_->getDoubleParam(axisNo_, pC_->motorPosition_,
|
||||
&motorPositionRec);
|
||||
if (pl_status != asynSuccess) {
|
||||
return pC_->paramLibAccessFailed(pl_status, "motorPosition",
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
|
||||
pl_status =
|
||||
pC_->getDoubleParam(axisNo_, pC_->motorVelBase_, &motorVelBase);
|
||||
// Only calculate timeContSpeed if the motorVelBase_ has been populated
|
||||
motorPosition = motorPositionRec * motorRecResolution;
|
||||
|
||||
/*
|
||||
We use motorVelocity, which corresponds to the record field VELO.
|
||||
From https://epics.anl.gov/docs/APS2015/14-Motor-Record.pdf:
|
||||
* VELO = motorVelocity_ = Slew velocity
|
||||
* VBAS = motorVelBase_ = Only used for stepper motors to minimize
|
||||
resonance.
|
||||
As documented in
|
||||
https://epics.anl.gov/docs/APS2015/17-Motor-Driver.pdf, the
|
||||
following relations apply: motorVelBase = VBAS / MRES motorVelocity
|
||||
= VELO / MRES motorAccel = (motorVelocity - motorVelBase) / ACCL
|
||||
Therefore, we need to correct the values from the parameter library.
|
||||
*/
|
||||
|
||||
// Read the velocity
|
||||
pl_status = pC_->getDoubleParam(axisNo_, pC_->motorVelocity_,
|
||||
&motorVelocityRec);
|
||||
|
||||
// Only calculate timeContSpeed if the motorVelocity has been populated
|
||||
// with a sensible value (e.g. > 0)
|
||||
if (pl_status == asynSuccess && motorVelBase > 0.0) {
|
||||
if (pl_status == asynSuccess && motorVelocityRec > 0.0) {
|
||||
// Convert back to the value in the VELO field
|
||||
motorVelocity = motorVelocityRec * motorRecResolution;
|
||||
|
||||
pl_status = pC_->getDoubleParam(axisNo_, pC_->motorTargetPosition_,
|
||||
&motorTargetPosition);
|
||||
&motorTargetPositionRec);
|
||||
motorTargetPosition = motorTargetPositionRec * motorRecResolution;
|
||||
if (pl_status == asynSuccess) {
|
||||
|
||||
timeContSpeed =
|
||||
std::ceil(std::fabs(motorTargetPosition - motorPosition) /
|
||||
motorVelBase);
|
||||
motorVelocity);
|
||||
}
|
||||
}
|
||||
|
||||
pl_status = pC_->getDoubleParam(axisNo_, pC_->motorAccel_, &motorAccel);
|
||||
if (pl_status == asynSuccess && motorVelBase > 0.0 &&
|
||||
motorAccel > 0.0) {
|
||||
timeAccel = 2 * std::ceil(motorVelBase / motorAccel);
|
||||
pl_status =
|
||||
pC_->getDoubleParam(axisNo_, pC_->motorAccel_, &motorAccelRec);
|
||||
if (pl_status == asynSuccess && motorVelocityRec > 0.0 &&
|
||||
motorAccelRec > 0.0) {
|
||||
|
||||
// Convert back to the value in the ACCL field
|
||||
motorAccel = motorVelocityRec / motorAccelRec;
|
||||
|
||||
// Calculate the time
|
||||
timeAccel = 2 * std::ceil(motorVelocity / motorAccel);
|
||||
}
|
||||
|
||||
// Calculate the expected arrival time
|
||||
expectedArrivalTime_ =
|
||||
time(NULL) + offsetMovTimeout_ +
|
||||
scaleMovTimeout_ * (timeContSpeed + 2 * timeAccel);
|
||||
} else {
|
||||
watchdogMovActive_ = false;
|
||||
}
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
asynStatus sinqAxis::checkMovTimeoutWatchdog(bool moving) {
|
||||
asynStatus pl_status;
|
||||
int enableMovWatchdog = 0;
|
||||
|
||||
// Not moving or watchdog not active
|
||||
if (!watchdogEnabled_ || !moving) {
|
||||
pl_status = pC_->getIntegerParam(axisNo_, pC_->enableMovWatchdog_,
|
||||
&enableMovWatchdog);
|
||||
if (pl_status != asynSuccess) {
|
||||
return pC_->paramLibAccessFailed(pl_status, "enableMovWatchdog_",
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
|
||||
// Not moving or watchdog not active / enabled
|
||||
if (enableMovWatchdog == 0 || !moving || !watchdogMovActive_) {
|
||||
watchdogMovActive_ = false;
|
||||
return asynSuccess;
|
||||
}
|
||||
@ -272,7 +420,8 @@ asynStatus sinqAxis::checkMovTimeoutWatchdog(bool moving) {
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
|
||||
return asynError;
|
||||
// Even if the movement timed out, the rest of the poll should continue.
|
||||
return asynSuccess;
|
||||
}
|
||||
return asynSuccess;
|
||||
}
|
@ -42,6 +42,8 @@ class epicsShareClass sinqAxis : public asynMotorAxis {
|
||||
After calling doPoll:
|
||||
- Reset motorStatusProblem_, motorStatusCommsError_ and motorMessageText_ if
|
||||
doPoll returned asynSuccess
|
||||
- If the movement timeout watchdog has been started, check it.
|
||||
- Update the parameter library entry enableMotorRBV_ by calling isEnabled.
|
||||
- Run `callParamCallbacks`
|
||||
- Return the status of `doPoll`
|
||||
*
|
||||
@ -141,6 +143,25 @@ class epicsShareClass sinqAxis : public asynMotorAxis {
|
||||
virtual asynStatus doHome(double minVelocity, double maxVelocity,
|
||||
double acceleration, int forwards);
|
||||
|
||||
/**
|
||||
* @brief This function enables / disables an axis. It should be implemented
|
||||
* by a child class of sinqAxis.
|
||||
*
|
||||
* @param on
|
||||
* @return asynStatus
|
||||
*/
|
||||
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 Start the watchdog for the movement, if the watchdog is not
|
||||
disabled. See the documentation of checkMovTimeoutWatchdog for more details.
|
||||
@ -235,7 +256,6 @@ class epicsShareClass sinqAxis : public asynMotorAxis {
|
||||
time_t expectedArrivalTime_;
|
||||
time_t offsetMovTimeout_;
|
||||
double scaleMovTimeout_;
|
||||
bool watchdogEnabled_;
|
||||
bool watchdogMovActive_;
|
||||
|
||||
private:
|
||||
|
@ -16,11 +16,16 @@ sinqController::sinqController(const char *portName,
|
||||
// added for better readability of the configuration.
|
||||
numAxes + 1,
|
||||
/*
|
||||
2 Parameters are added in sinqController:
|
||||
4 Parameters are added in sinqController:
|
||||
- MOTOR_MESSAGE_TEXT
|
||||
- MOTOR_TARGET_POSITION
|
||||
- ENABLE_AXIS
|
||||
- ENABLE_AXIS_RBV
|
||||
- ENABLE_MOV_WATCHDOG
|
||||
- LIMITS_OFFSET
|
||||
*/
|
||||
NUM_MOTOR_DRIVER_PARAMS + numExtraParams + 2,
|
||||
NUM_MOTOR_DRIVER_PARAMS + NUM_SINQMOTOR_DRIVER_PARAMS +
|
||||
numExtraParams,
|
||||
0, // No additional interfaces beyond those in base class
|
||||
0, // No additional callback interfaces beyond those in base class
|
||||
ASYN_CANBLOCK | ASYN_MULTIDEVICE,
|
||||
@ -76,6 +81,69 @@ sinqController::sinqController(const char *portName,
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
status = createParam("ENABLE_AXIS", asynParamInt32, &enableMotor_);
|
||||
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);
|
||||
}
|
||||
|
||||
status = createParam("ENABLE_AXIS_RBV", asynParamInt32, &enableMotorRBV_);
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
We need to introduce 2 new parameters in order to write the limits from the
|
||||
driver to the EPICS record. See the comment in sinqController.h next to
|
||||
the declaration of motorHighLimitFromDriver_.
|
||||
*/
|
||||
status = createParam("MOTOR_HIGH_LIMIT_FROM_DRIVER", asynParamFloat64,
|
||||
&motorHighLimitFromDriver_);
|
||||
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);
|
||||
}
|
||||
|
||||
status = createParam("MOTOR_LOW_LIMIT_FROM_DRIVER", asynParamFloat64,
|
||||
&motorLowLimitFromDriver_);
|
||||
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);
|
||||
}
|
||||
|
||||
status =
|
||||
createParam("ENABLE_MOV_WATCHDOG", asynParamInt32, &enableMovWatchdog_);
|
||||
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);
|
||||
}
|
||||
|
||||
status =
|
||||
createParam("LIMITS_OFFSET", asynParamFloat64, &motorLimitsOffset_);
|
||||
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) {
|
||||
@ -95,19 +163,53 @@ sinqController::~sinqController(void) {
|
||||
free(this->pAxes_);
|
||||
}
|
||||
|
||||
asynStatus sinqController::writeInt32(asynUser *pasynUser, epicsInt32 value) {
|
||||
int function = pasynUser->reason;
|
||||
|
||||
// =====================================================================
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Handle custom PVs
|
||||
if (function == enableMotor_) {
|
||||
return axis->enable(value != 0);
|
||||
} else {
|
||||
return asynMotorController::writeInt32(pasynUser, value);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Overloaded from asynMotorController to cover the readback value of enableMotor.
|
||||
*/
|
||||
asynStatus sinqController::readInt32(asynUser *pasynUser, epicsInt32 *value) {
|
||||
if (pasynUser->reason == enableMotorRBV_) {
|
||||
// Value is updated in the poll function of an axis
|
||||
return asynSuccess;
|
||||
} else {
|
||||
return asynMotorController::readInt32(pasynUser, value);
|
||||
}
|
||||
}
|
||||
|
||||
asynStatus sinqController::errMsgCouldNotParseResponse(const char *command,
|
||||
const char *response,
|
||||
int axisNo_,
|
||||
const char *functionName,
|
||||
int lineNumber) {
|
||||
|
||||
asynPrint(lowLevelPortUser_, ASYN_TRACE_ERROR,
|
||||
"%s => line %d:\n Could not interpret response %s for "
|
||||
"command %s.\n",
|
||||
functionName, lineNumber, response, command);
|
||||
|
||||
setStringParam(
|
||||
motorMessageText_,
|
||||
"Could not interpret MCU response. Please call the software support");
|
||||
setStringParam(motorMessageText_,
|
||||
"Could not interpret MCU response. Please call the support");
|
||||
setIntegerParam(motorStatusCommsError_, 1);
|
||||
return asynError;
|
||||
}
|
||||
@ -121,11 +223,11 @@ asynStatus sinqController::paramLibAccessFailed(asynStatus status,
|
||||
// Log the error message and try to propagate it
|
||||
asynPrint(lowLevelPortUser_, ASYN_TRACE_ERROR,
|
||||
"%s => line %d:\n Accessing the parameter library failed for "
|
||||
"parameter %s.\n",
|
||||
functionName, lineNumber, parameter);
|
||||
setStringParam(
|
||||
motorMessageText_,
|
||||
"Accessing paramLib failed. Please call the software support.");
|
||||
"parameter %s with error %s.\n",
|
||||
functionName, lineNumber, parameter,
|
||||
stringifyAsynStatus(status));
|
||||
setStringParam(motorMessageText_,
|
||||
"Accessing paramLib failed. Please call the support.");
|
||||
}
|
||||
|
||||
return status;
|
||||
|
@ -46,7 +46,28 @@ class epicsShareClass sinqController : public asynMotorController {
|
||||
*/
|
||||
virtual ~sinqController(void);
|
||||
|
||||
friend class sinqAxis;
|
||||
/**
|
||||
* @brief Overloaded function of asynMotorController
|
||||
*
|
||||
* The function is overloaded to allow enabling / disabling the motor.
|
||||
*
|
||||
* @param pasynUser Specify the axis via the asynUser
|
||||
* @param value New value
|
||||
* @return asynStatus
|
||||
*/
|
||||
asynStatus writeInt32(asynUser *pasynUser, epicsInt32 value);
|
||||
|
||||
/**
|
||||
* @brief Overloaded function of asynMotorController
|
||||
*
|
||||
* The function is overloaded to get readback values for the enabling /
|
||||
* disabling status.
|
||||
*
|
||||
* @param pasynUser Specify the axis via the asynUser
|
||||
* @param value Read-out value
|
||||
* @return asynStatus
|
||||
*/
|
||||
asynStatus readInt32(asynUser *pasynUser, epicsInt32 *value);
|
||||
|
||||
/**
|
||||
* @brief Error handling in case accessing the parameter library failed.
|
||||
@ -98,10 +119,31 @@ class epicsShareClass sinqController : public asynMotorController {
|
||||
*/
|
||||
const char *stringifyAsynStatus(asynStatus status);
|
||||
|
||||
friend class sinqAxis;
|
||||
|
||||
protected:
|
||||
asynUser *lowLevelPortUser_;
|
||||
|
||||
#define FIRST_SINQMOTOR_PARAM motorMessageText_
|
||||
int motorMessageText_;
|
||||
int motorTargetPosition_;
|
||||
int enableMotor_;
|
||||
int enableMotorRBV_;
|
||||
int enableMovWatchdog_;
|
||||
int motorLimitsOffset_;
|
||||
|
||||
/*
|
||||
These parameters are here to write the high and low limits from the MCU to
|
||||
the EPICS motor record. Using motorHighLimit_ / motorLowLimit_ does not
|
||||
work: https://epics.anl.gov/tech-talk/2023/msg00576.php.
|
||||
Therefore, some additional records are introduced which read from these
|
||||
parameters and write into the motor record. See the sinq_asyn_motor.db file.
|
||||
*/
|
||||
int motorHighLimitFromDriver_;
|
||||
int motorLowLimitFromDriver_;
|
||||
#define LAST_SINQMOTOR_PARAM motorLowLimitFromDriver_
|
||||
};
|
||||
#define NUM_SINQMOTOR_DRIVER_PARAMS \
|
||||
(&LAST_SINQMOTOR_PARAM - &FIRST_SINQMOTOR_PARAM + 1)
|
||||
|
||||
#endif
|
||||
|
Reference in New Issue
Block a user