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)"
{
pattern
{ AXIS, M, DESC, EGU, DIR, MRES, MSGTEXTSIZE, ENABLEMOVWATCHDOG, LIMITSOFFSET, CANSETSPEED, ADAPTPOLL }
{ 1, "lin1", "Linear motor doing whatever", mm, Pos, 0.001, 200, 1, 1.0, 1, 1 }
{ 2, "rot1", "First rotary motor", degree, Neg, 0.001, 200, 0, 1.0, 0, 1 }
{ 3, "rot2", "Second rotary motor", degree, Pos, 0.001, 200, 0, 0.0, 1, 0 }
{ 5, "rot3", "Surprise: Third rotary motor", degree, Pos, 0.001, 200, 1, 2.0, 0, 0 }
{ 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, 0 }
{ 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, 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`.
@@ -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"
(when the poller is woken up, e.g. after an axis received a move command).
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
@@ -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
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
### 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.
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.
- `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.
- `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.

View File

@@ -347,3 +347,37 @@ record(waveform, "$(INSTR)$(M):EncoderType") {
field(NELM, "80")
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;
}
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,
double maxVelocity, double acceleration) {
// Status of parameter library operations
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
getAxisParamChecked(this, motorRecResolution, &motorRecRes);
pSinqA_->targetPosition = position * motorRecRes;
getAxisParamChecked(this, motorRecResolution, &motRecRes);
pSinqA_->targetPosition = position * motRecRes;
status = doMove(position, relative, minVelocity, maxVelocity, acceleration);
if (status != asynSuccess) {
@@ -455,9 +486,9 @@ asynStatus sinqAxis::enable(bool on) {
asynStatus sinqAxis::motorPosition(double *motorPos) {
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
@@ -470,16 +501,16 @@ asynStatus sinqAxis::motorPosition(double *motorPos) {
__PRETTY_FUNCTION__, __LINE__);
}
*motorPos = *motorPos * motorRecRes;
*motorPos = *motorPos * motRecRes;
return status;
}
asynStatus sinqAxis::setMotorPosition(double motorPos) {
asynStatus status = asynSuccess;
double motorRecRes = 0.0;
double motRecRes = 0.0;
getAxisParamChecked(this, motorRecResolution, &motorRecRes);
setAxisParamChecked(this, motorPosition, motorPos / motorRecRes);
getAxisParamChecked(this, motorRecResolution, &motRecRes);
setAxisParamChecked(this, motorPosition, motorPos / motRecRes);
return status;
}
@@ -573,7 +604,7 @@ asynStatus sinqAxis::startMovTimeoutWatchdog() {
double motorVelocityRec = 0.0;
double motorAccel = 0.0;
double motorAccelRec = 0.0;
double motorRecRes = 0.0;
double motRecRes = 0.0;
time_t timeContSpeed = 0;
time_t timeAccel = 0;
@@ -602,7 +633,7 @@ asynStatus sinqAxis::startMovTimeoutWatchdog() {
= VELO / MRES motorAccel = (motorVelocity - motorVelBase) / ACCL
Therefore, we need to correct the values from the parameter library.
*/
getAxisParamChecked(this, motorRecResolution, &motorRecRes);
getAxisParamChecked(this, motorRecResolution, &motRecRes);
// Read the velocity
getAxisParamChecked(this, motorVelocity, &motorVelocityRec);
@@ -611,7 +642,7 @@ asynStatus sinqAxis::startMovTimeoutWatchdog() {
// with a sensible value (e.g. > 0)
if (pl_status == asynSuccess && motorVelocityRec > 0.0) {
// Convert back to the value in the VELO field
motorVelocity = motorVelocityRec * motorRecRes;
motorVelocity = motorVelocityRec * motRecRes;
if (pl_status == asynSuccess) {
timeContSpeed =

View File

@@ -107,14 +107,46 @@ class HIDDEN sinqAxis : public asynMotorAxis {
*/
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
`doMove` implementation.
* Wrapper around `doMove` which calculates the (absolute) target position
and stores it in the member variable `targetPosition_`. This member variable
is e.g. used for the movement watchdog. Afterwards, it calls and returns
`doMove`.
* Wrapper around `doMove` which checks if the motor is in position
mode. If that is the case, the function calculates the (absolute) target
position and stores it in the member variable `targetPosition_`. This member
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 relative Forwarded to `doMove`.
@@ -135,8 +167,9 @@ class HIDDEN sinqAxis : public asynMotorAxis {
* @param position Target position `VAL` from the motor record
* @param relative Specifies, whether the target position is
relative or absolute.
* @param minVelocity Minimum velocity VMIN from the motor record
* @param maxVelocity Maximum velocity VMAX from the motor record
* @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
*/

View File

@@ -100,6 +100,9 @@ struct sinqControllerImpl {
int motorHighLimitFromDriver;
int motorLowLimitFromDriver;
int motorPositionDeadband;
int motorMode;
int motorCanSetMode;
int motorSetMode;
int adaptivePolling;
int encoderType;
};
@@ -147,6 +150,9 @@ sinqController::sinqController(const char *portName,
.motorHighLimitFromDriver = 0,
.motorLowLimitFromDriver = 0,
.motorPositionDeadband = 0,
.motorMode = 0,
.motorCanSetMode = 0,
.motorSetMode = 0,
.adaptivePolling = 0,
.encoderType = 0,
})) {
@@ -390,6 +396,38 @@ sinqController::sinqController(const char *portName,
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
if (controller.empty()) {
initHookRegister(&epicsInithookFunction);
@@ -455,6 +493,35 @@ asynStatus sinqController::writeInt32(asynUser *pasynUser, epicsInt32 value) {
return axis->reset();
} else if (function == motorForceStop()) {
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 {
return asynMotorController::writeInt32(pasynUser, value);
}
@@ -742,6 +809,9 @@ int sinqController::motorLowLimitFromDriver() {
int sinqController::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::encoderType() { return pSinqC_->encoderType; }

View File

@@ -76,7 +76,8 @@ class HIDDEN sinqController : public 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 value New value
@@ -313,7 +314,7 @@ class HIDDEN sinqController : public asynMotorController {
int motorRecOffset() { return motorRecOffset_; }
// Accessors for additional PVs defined in sinqController (which are hidden
// in pSinqC_)
// behind pSinqC_)
int motorMessageText();
int motorReset();
int motorEnable();
@@ -331,6 +332,9 @@ class HIDDEN sinqController : public asynMotorController {
int motorHighLimitFromDriver();
int motorLowLimitFromDriver();
int motorPositionDeadband();
int motorMode();
int motorCanSetMode();
int motorSetMode();
int adaptivePolling();
int encoderType();