Compare commits

..

1 Commits
1.5.7 ... 1.6.0

Author SHA1 Message Date
6f639d7233 Added scaffolding for velocity mode
All checks were successful
Test And Build / Lint (push) Successful in 5s
Test And Build / Build (push) Successful in 5s
Added records to support detection of the current operation mode
(position or velocity), whether one is allowed to change between the two
and a record to actually change between the two. Also added a
doMoveVelocity method which should be implemented by derived drivers if
they support velocity mode.
2026-01-20 14:11:06 +01:00
7 changed files with 352 additions and 26 deletions

125
.gitignore vendored Normal file
View File

@@ -0,0 +1,125 @@
# Took these from the https://github.com/github/gitignore project on October 21, 2011
# **** 'Personal' entries don't belong in here - put them in your .git/info/exclude file ****
# Ignore text editor (e.g. emacs) autosave files
*~
# Build Artifacts
O.*_Common/
O.*_RHEL8-x86_64/
# Compiled Object files
*.slo
*.lo
*.o
*.obj
*.d
SICServer*
# Compiled Dynamic libraries
*.so
# Compiled Static libraries
*.lai
*.la
*.a
# Compiled python files
*.py[co]
# Eclipse-generated files
*.pydevproject
.project
.metadata
bin/**
tmp/**
tmp/**/*
*.tmp
*.bak
*.swp
*~.nib
local.properties
.classpath
.settings/
.loadpath
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# CDT-specific
.cproject
# PDT-specific
.buildpath
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
*.sln
*.vcproj
*.exe
*.vcxproj
*.filters
# User-specific files
*.suo
*.user
*.sln.docstates
*.sdf
.cvsignore
#Test results
*.log
# Build results
[Dd]ebug/
[Rr]elease/
*_i.c
*_p.c
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.vspscc
.builds
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
# Visual Studio profiler
*.psess
*.vsp
# ReSharper is a .NET coding add-in
_ReSharper*
# Others
*.autosave
# Windows image file caches
Thumbs.db
# Mac OS X Finder
.DS_Store
._*

View File

@@ -117,11 +117,11 @@ does not matter):
file "$(SINQDBPATH)" file "$(SINQDBPATH)"
{ {
pattern pattern
{ AXIS, M, DESC, EGU, DIR, MRES, MSGTEXTSIZE, ENABLEMOVWATCHDOG, LIMITSOFFSET, CANSETSPEED, ADAPTPOLL } { AXIS, M, DESC, EGU, DIR, MRES, MSGTEXTSIZE, ENABLEMOVWATCHDOG, LIMITSOFFSET, CANSETSPEED, ADAPTPOLL, CANSETMODE }
{ 1, "lin1", "Linear motor doing whatever", mm, Pos, 0.001, 200, 1, 1.0, 1, 1 } { 1, "lin1", "Linear motor doing whatever", mm, Pos, 0.001, 200, 1, 1.0, 1, 1, 0 }
{ 2, "rot1", "First rotary motor", degree, Neg, 0.001, 200, 0, 1.0, 0, 1 } { 2, "rot1", "First rotary motor", degree, Neg, 0.001, 200, 0, 1.0, 0, 1, 0 }
{ 3, "rot2", "Second rotary motor", degree, Pos, 0.001, 200, 0, 0.0, 1, 0 } { 3, "rot2", "Second rotary motor", degree, Pos, 0.001, 200, 0, 0.0, 1, 0, 0 }
{ 5, "rot3", "Surprise: Third rotary motor", degree, Pos, 0.001, 200, 1, 2.0, 0, 0 } { 5, "rot3", "Surprise: Third rotary motor", degree, Pos, 0.001, 200, 1, 2.0, 0, 0, 1 }
} }
``` ```
The variable `SINQDBPATH` has been set in "mcu1.cmd" before calling `dbLoadTemplate`. The variable `SINQDBPATH` has been set in "mcu1.cmd" before calling `dbLoadTemplate`.
@@ -166,6 +166,9 @@ behaviour so that the affected axis is only polled with the busy / moving poll
period if it itself is moving. This setting is ignored for "forced fast polls" period if it itself is moving. This setting is ignored for "forced fast polls"
(when the poller is woken up, e.g. after an axis received a move command). (when the poller is woken up, e.g. after an axis received a move command).
Defaults to 1. Defaults to 1.
- `CANSETMODE`: If set to any value other than 0, the operation mode of the
motor can be changed. See section [Velocity mode](#velocity-mode).
### Motor record resolution MRES ### Motor record resolution MRES
@@ -212,6 +215,29 @@ transferred to (motor_record_pv_name).MRES or to
`sinqMotor` provides a variety of additional records. See `db/sinqMotor.db` for `sinqMotor` provides a variety of additional records. See `db/sinqMotor.db` for
the complete list and the documentation. the complete list and the documentation.
### Velocity mode
The motor record was originally designed for motors which operate in _position_
mode: They are given a certain position, move to that position and then stop
on their own. Some motors however operate in a continuous _velocity_ mode, where
they move (usually rotate) with a fixed velocity until told to stop or change
the velocity. To support this operation mode, `sinqMotor` provides three
additional records:
- `$(INSTR)$(M):Mode`: This read-only record returns 0 if the motor is in
position mode and 1 if it is in velocity mode. Defaults to 0.
- `$(INSTR)$(M):CanSetMode`: If the value of this record is other than zero, the
motor mode can be changed during operation. Defaults to 0, but can be
overwritten in the substitution file with `CANSETMODE` or from within the driver
(e.g. by reading out a parameter from the hardware).
- `$(INSTR)$(M):SetMode`: This record can be used to switch between operation
modes if `$(INSTR)$(M):CanSetMode` is not zero. Currently accepted values are
`0` for position mode and `1` for velocity mode.
When in velocity mode, writing to the `VELO` field of the motor record directly
triggers the corresponding velocity change if the driver supports it. Writing
to the `VAL` field does nothing in velocity mode.
## Developer guide ## Developer guide
### File structure ### File structure
@@ -251,7 +277,10 @@ This is an empty function which should be overwritten by concrete driver impleme
- `reset`: This function is called when the `$(INSTR)$(M):Reset` PV from `db/sinqMotor.db` is set. - `reset`: This function is called when the `$(INSTR)$(M):Reset` PV from `db/sinqMotor.db` is set.
It calls `doReset` and performs some fast polls after `doReset` returns. It calls `doReset` and performs some fast polls after `doReset` returns.
- `doReset`: This is an empty function which should be overwritten by concrete driver implementations. - `doReset`: 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`. - `moveVelocity`: This function checks if the motor is in velocity mode. If that is the case, it calls `doMoveVelocity`.
- `doMoveVelocity`: This is an empty function which should be overwritten by concrete driver implementations.
- `move`: This function checks if the motor is in position mode. If that is the
case, it then 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. - `doMove`: This is an empty function which should be overwritten by concrete driver implementations.
- `home`: This function sets the internal status flags for the homing process and then calls doHome. - `home`: This function sets the internal status flags for the homing process and then calls doHome.
- `doHome`: This is an empty function which should be overwritten by concrete driver implementations. - `doHome`: This is an empty function which should be overwritten by concrete driver implementations.

View File

@@ -347,3 +347,37 @@ record(waveform, "$(INSTR)$(M):EncoderType") {
field(NELM, "80") field(NELM, "80")
field(SCAN, "I/O Intr") field(SCAN, "I/O Intr")
} }
# Motors can operate either in position mode (0) or velocity mode (1). If the
# mode is stored within the controller, the driver writes the corresponding mode
# value into this record. If not, motors are always assumed to operate in
# position mode.
record(longin, "$(INSTR)$(M):Mode")
{
field(DTYP, "asynInt32")
field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_MODE")
field(SCAN, "I/O Intr")
field(PINI, "NO")
field(VAL, "0")
}
# Motors can operate either in position mode (0) or velocity mode (1). If it is
# possible to switch between the two, this record has a value of 1, otherwise
# its value is 0.
record(longin, "$(INSTR)$(M):CanSetMode")
{
field(DTYP, "asynInt32")
field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_CAN_SET_MODE")
field(SCAN, "I/O Intr")
field(PINI, "NO")
field(VAL, "$(CANSETMODE=0)")
}
# Set the operation mode of the motor to position mode (0) or velocity mode (1).
# If CanSetMode is 0, the mode cannot be changed and writing to this record
# will have no effect.
record(longout, "$(INSTR)$(M):SetMode") {
field(DTYP, "asynInt32")
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_SET_MODE")
field(PINI, "NO")
}

View File

@@ -333,18 +333,49 @@ asynStatus sinqAxis::doPoll(bool *moving) {
return asynSuccess; return asynSuccess;
} }
asynStatus sinqAxis::moveVelocity(double minVelocity, double maxVelocity,
double acceleration) {
int motMode = 0;
// If the motor is not in velocity mode, do nothing
getAxisParamChecked(this, motorMode, &motMode);
if (motMode != 0) {
return asynSuccess;
}
return doMoveVelocity(minVelocity, maxVelocity, acceleration);
}
asynStatus sinqAxis::doMoveVelocity(double minVelocity, double maxVelocity,
double acceleration) {
// Suppress unused variable warning - this is just a default fallback
// function.
(void)minVelocity;
(void)maxVelocity;
(void)acceleration;
return asynSuccess;
}
asynStatus sinqAxis::move(double position, int relative, double minVelocity, asynStatus sinqAxis::move(double position, int relative, double minVelocity,
double maxVelocity, double acceleration) { double maxVelocity, double acceleration) {
// Status of parameter library operations // Status of parameter library operations
asynStatus status = asynSuccess; asynStatus status = asynSuccess;
double motorRecRes = 0.0; double motRecRes = 0.0;
int motMode = 0;
// ========================================================================= // =========================================================================
// If the motor is not in position mode, do nothing
getAxisParamChecked(this, motorMode, &motMode);
if (motMode != 0) {
return asynSuccess;
}
// Store the target position internally // Store the target position internally
getAxisParamChecked(this, motorRecResolution, &motorRecRes); getAxisParamChecked(this, motorRecResolution, &motRecRes);
pSinqA_->targetPosition = position * motorRecRes; pSinqA_->targetPosition = position * motRecRes;
status = doMove(position, relative, minVelocity, maxVelocity, acceleration); status = doMove(position, relative, minVelocity, maxVelocity, acceleration);
if (status != asynSuccess) { if (status != asynSuccess) {
@@ -455,9 +486,9 @@ asynStatus sinqAxis::enable(bool on) {
asynStatus sinqAxis::motorPosition(double *motorPos) { asynStatus sinqAxis::motorPosition(double *motorPos) {
asynStatus status = asynSuccess; asynStatus status = asynSuccess;
double motorRecRes = 0.0; double motRecRes = 0.0;
getAxisParamChecked(this, motorRecResolution, &motorRecRes); getAxisParamChecked(this, motorRecResolution, &motRecRes);
/* /*
We cannot use getAxisParamChecked checked here, since the name of the index We cannot use getAxisParamChecked checked here, since the name of the index
@@ -470,16 +501,16 @@ asynStatus sinqAxis::motorPosition(double *motorPos) {
__PRETTY_FUNCTION__, __LINE__); __PRETTY_FUNCTION__, __LINE__);
} }
*motorPos = *motorPos * motorRecRes; *motorPos = *motorPos * motRecRes;
return status; return status;
} }
asynStatus sinqAxis::setMotorPosition(double motorPos) { asynStatus sinqAxis::setMotorPosition(double motorPos) {
asynStatus status = asynSuccess; asynStatus status = asynSuccess;
double motorRecRes = 0.0; double motRecRes = 0.0;
getAxisParamChecked(this, motorRecResolution, &motorRecRes); getAxisParamChecked(this, motorRecResolution, &motRecRes);
setAxisParamChecked(this, motorPosition, motorPos / motorRecRes); setAxisParamChecked(this, motorPosition, motorPos / motRecRes);
return status; return status;
} }
@@ -573,7 +604,7 @@ asynStatus sinqAxis::startMovTimeoutWatchdog() {
double motorVelocityRec = 0.0; double motorVelocityRec = 0.0;
double motorAccel = 0.0; double motorAccel = 0.0;
double motorAccelRec = 0.0; double motorAccelRec = 0.0;
double motorRecRes = 0.0; double motRecRes = 0.0;
time_t timeContSpeed = 0; time_t timeContSpeed = 0;
time_t timeAccel = 0; time_t timeAccel = 0;
@@ -602,7 +633,7 @@ asynStatus sinqAxis::startMovTimeoutWatchdog() {
= VELO / MRES motorAccel = (motorVelocity - motorVelBase) / ACCL = VELO / MRES motorAccel = (motorVelocity - motorVelBase) / ACCL
Therefore, we need to correct the values from the parameter library. Therefore, we need to correct the values from the parameter library.
*/ */
getAxisParamChecked(this, motorRecResolution, &motorRecRes); getAxisParamChecked(this, motorRecResolution, &motRecRes);
// Read the velocity // Read the velocity
getAxisParamChecked(this, motorVelocity, &motorVelocityRec); getAxisParamChecked(this, motorVelocity, &motorVelocityRec);
@@ -611,7 +642,7 @@ asynStatus sinqAxis::startMovTimeoutWatchdog() {
// with a sensible value (e.g. > 0) // with a sensible value (e.g. > 0)
if (pl_status == asynSuccess && motorVelocityRec > 0.0) { if (pl_status == asynSuccess && motorVelocityRec > 0.0) {
// Convert back to the value in the VELO field // Convert back to the value in the VELO field
motorVelocity = motorVelocityRec * motorRecRes; motorVelocity = motorVelocityRec * motRecRes;
if (pl_status == asynSuccess) { if (pl_status == asynSuccess) {
timeContSpeed = timeContSpeed =

View File

@@ -107,14 +107,46 @@ class HIDDEN sinqAxis : public asynMotorAxis {
*/ */
virtual asynStatus doPoll(bool *moving); virtual asynStatus doPoll(bool *moving);
/**
* @brief Perform some standardized operations before and after the concrete
`doMoveVelocity` implementation.
* Wrapper around `doMoveVelocity` which checks if the motor is in velocity
mode. If that is the case, it, it calls and returns `doMoveVelocity`.
Otherwise, it just returns `asynSuccess`.
*
* @param minVelocity Forwarded to `doMoveVelocity`.
* @param maxVelocity Forwarded to `doMoveVelocity`.
* @param acceleration Forwarded to `doMoveVelocity`.
* @return asynStatus Forward the status of `doMove`, unless one of
the parameter library operation fails (in that case, returns the failed
operation status).
*/
virtual asynStatus moveVelocity(double minVelocity, double maxVelocity,
double acceleration);
/**
* @brief Implementation of the "proper", device-specific move method. This
method should be implemented by a child class of sinqAxis.
*
* @param minVelocity Minimum velocity VBAS from the motor record
* @param maxVelocity Actual velocity VELO from the motor record
(yes, this is named badly. This is not VMAX!)
* @param acceleration Acceleration ACCEL from the motor record
* @return asynStatus
*/
virtual asynStatus doMoveVelocity(double minVelocity, double maxVelocity,
double acceleration);
/** /**
* @brief Perform some standardized operations before and after the concrete * @brief Perform some standardized operations before and after the concrete
`doMove` implementation. `doMove` implementation.
* Wrapper around `doMove` which calculates the (absolute) target position * Wrapper around `doMove` which checks if the motor is in position
and stores it in the member variable `targetPosition_`. This member variable mode. If that is the case, the function calculates the (absolute) target
is e.g. used for the movement watchdog. Afterwards, it calls and returns position and stores it in the member variable `targetPosition_`. This member
`doMove`. variable is e.g. used for the movement watchdog. Afterwards, it calls and
returns `doMove`. Otherwise, it just returns `asynSuccess`.
* *
* @param position Forwarded to `doMove`. * @param position Forwarded to `doMove`.
* @param relative Forwarded to `doMove`. * @param relative Forwarded to `doMove`.
@@ -135,8 +167,9 @@ class HIDDEN sinqAxis : public asynMotorAxis {
* @param position Target position `VAL` from the motor record * @param position Target position `VAL` from the motor record
* @param relative Specifies, whether the target position is * @param relative Specifies, whether the target position is
relative or absolute. relative or absolute.
* @param minVelocity Minimum velocity VMIN from the motor record * @param minVelocity Minimum velocity VBAS from the motor record
* @param maxVelocity Maximum velocity VMAX from the motor record * @param maxVelocity Actual velocity VELO from the motor record
(yes, this is named badly. This is not VMAX!)
* @param acceleration Acceleration ACCEL from the motor record * @param acceleration Acceleration ACCEL from the motor record
* @return asynStatus * @return asynStatus
*/ */

View File

@@ -100,6 +100,9 @@ struct sinqControllerImpl {
int motorHighLimitFromDriver; int motorHighLimitFromDriver;
int motorLowLimitFromDriver; int motorLowLimitFromDriver;
int motorPositionDeadband; int motorPositionDeadband;
int motorMode;
int motorCanSetMode;
int motorSetMode;
int adaptivePolling; int adaptivePolling;
int encoderType; int encoderType;
}; };
@@ -147,6 +150,9 @@ sinqController::sinqController(const char *portName,
.motorHighLimitFromDriver = 0, .motorHighLimitFromDriver = 0,
.motorLowLimitFromDriver = 0, .motorLowLimitFromDriver = 0,
.motorPositionDeadband = 0, .motorPositionDeadband = 0,
.motorMode = 0,
.motorCanSetMode = 0,
.motorSetMode = 0,
.adaptivePolling = 0, .adaptivePolling = 0,
.encoderType = 0, .encoderType = 0,
})) { })) {
@@ -390,6 +396,38 @@ sinqController::sinqController(const char *portName,
exit(-1); exit(-1);
} }
status = createParam("MOTOR_MODE", asynParamInt32, &pSinqC_->motorMode);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
"parameter failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
exit(-1);
}
status = createParam("MOTOR_CAN_SET_MODE", asynParamInt32,
&pSinqC_->motorCanSetMode);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
"parameter failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
exit(-1);
}
status =
createParam("MOTOR_SET_MODE", asynParamInt32, &pSinqC_->motorSetMode);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
"parameter failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
exit(-1);
}
// Register the hook function during construction of the first axis object // Register the hook function during construction of the first axis object
if (controller.empty()) { if (controller.empty()) {
initHookRegister(&epicsInithookFunction); initHookRegister(&epicsInithookFunction);
@@ -455,6 +493,35 @@ asynStatus sinqController::writeInt32(asynUser *pasynUser, epicsInt32 value) {
return axis->reset(); return axis->reset();
} else if (function == motorForceStop()) { } else if (function == motorForceStop()) {
return axis->stop(0.0); return axis->stop(0.0);
} else if (function == motorSetMode()) {
// Check if it is allowed to set the mode
int canSetMode = 0;
getAxisParamChecked(axis, motorCanSetMode, &canSetMode);
if (canSetMode == 0) {
int axisNo;
getAddress(pasynUser, &axisNo);
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\ncannot "
"change operation mode of the motor",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
return asynError;
} else {
// Check if the given value is valid (i.e. 0 for position mode or 1
// for velocity mode):
if (value == 0 || value == 1) {
setAxisParamChecked(axis, motorMode, value);
return asynMotorController::writeInt32(pasynUser, value);
} else {
int axisNo;
getAddress(pasynUser, &axisNo);
asynPrint(
this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\n given motor "
"mode must be 0 (position mode) or 1 (velocity mode).",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
return asynError;
}
}
} else { } else {
return asynMotorController::writeInt32(pasynUser, value); return asynMotorController::writeInt32(pasynUser, value);
} }
@@ -742,6 +809,9 @@ int sinqController::motorLowLimitFromDriver() {
int sinqController::motorPositionDeadband() { int sinqController::motorPositionDeadband() {
return pSinqC_->motorPositionDeadband; return pSinqC_->motorPositionDeadband;
} }
int sinqController::motorMode() { return pSinqC_->motorMode; }
int sinqController::motorCanSetMode() { return pSinqC_->motorCanSetMode; }
int sinqController::motorSetMode() { return pSinqC_->motorSetMode; }
int sinqController::adaptivePolling() { return pSinqC_->adaptivePolling; } int sinqController::adaptivePolling() { return pSinqC_->adaptivePolling; }
int sinqController::encoderType() { return pSinqC_->encoderType; } int sinqController::encoderType() { return pSinqC_->encoderType; }

View File

@@ -76,7 +76,8 @@ class HIDDEN sinqController : public asynMotorController {
/** /**
* @brief Overloaded function of asynMotorController * @brief Overloaded function of asynMotorController
* *
* The function is overloaded to allow enabling / disabling the motor. * The function is overloaded to allow enabling / disabling the motor and
* setting the operation mode of the motor.
* *
* @param pasynUser Specify the axis via the asynUser * @param pasynUser Specify the axis via the asynUser
* @param value New value * @param value New value
@@ -313,7 +314,7 @@ class HIDDEN sinqController : public asynMotorController {
int motorRecOffset() { return motorRecOffset_; } int motorRecOffset() { return motorRecOffset_; }
// Accessors for additional PVs defined in sinqController (which are hidden // Accessors for additional PVs defined in sinqController (which are hidden
// in pSinqC_) // behind pSinqC_)
int motorMessageText(); int motorMessageText();
int motorReset(); int motorReset();
int motorEnable(); int motorEnable();
@@ -331,6 +332,9 @@ class HIDDEN sinqController : public asynMotorController {
int motorHighLimitFromDriver(); int motorHighLimitFromDriver();
int motorLowLimitFromDriver(); int motorLowLimitFromDriver();
int motorPositionDeadband(); int motorPositionDeadband();
int motorMode();
int motorCanSetMode();
int motorSetMode();
int adaptivePolling(); int adaptivePolling();
int encoderType(); int encoderType();